-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathshowKeyBoard.ahk
More file actions
862 lines (827 loc) · 25.3 KB
/
Copy pathshowKeyBoard.ahk
File metadata and controls
862 lines (827 loc) · 25.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
;编译信息
;@Ahk2Exe-SetName ShowKeyBoard
;@Ahk2Exe-SetDescription Show and Analyse Mouse/KeyBoard
;@Ahk2Exe-SetProductVersion 1.61.0.0
;@Ahk2Exe-SetFileVersion 1.61.0.0
;@Ahk2Exe-SetCopyright Austing.Young (2023 - )
;@Ahk2Exe-SetMainIcon res\keyboard.ico
;@Ahk2Exe-ExeName build/release/ShowKeyBoard.exe
#Requires AutoHotkey v2
#SingleInstance Ignore
global APPName := "ShowKeyBoard", ver:= "1.61"
#Include "lib/JSON.ahk"
#include common.ahk
#include langVars.ahk
#Include events.ahk
myWindowTitle := 'ShowKeyBoardMainUI'
; 判断是否有实例正在运行
lastProcHwnd := WinExist(myWindowTitle)
if lastProcHwnd {
; 找到旧实例
; 可以选择:通知旧实例退出
SendMessage 0x0010, 0, 0, , "ahk_id " lastProcHwnd
; 或者直接退出新实例
;MsgBox "程序已在运行,将退出新实例"
;ExitApp
}
; 正式代码开始
; 切换是否显示按键
Switch4show(*) {
global needShowKey := not needShowKey
UpdatMenu4Show()
}
; 尝试绑定热键
try {
Hotkey hotkey4Show, Switch4show
}
; ; 不要阻塞按键
; CountCtrlKey()
; {
; global ctrlKeyCount := 0
; }
; ~LCtrl Up:: CountCtrlKey
; ~RCtrl Up:: CountCtrlKey
; ~LShift Up:: CountCtrlKey
; ~RShift Up:: CountCtrlKey
; ~LWin Up:: CountCtrlKey
; ~RWin Up:: CountCtrlKey
; ~LAlt Up:: CountCtrlKey
; ~RAlt Up:: CountCtrlKey
;
; ~LCtrl:: SendCtrlKey
; ~RCtrl:: SendCtrlKey
; ~LShift:: SendCtrlKey
; ~RShift:: SendCtrlKey
; ~LWin:: SendCtrlKey
; ~RWin:: SendCtrlKey
; ~LAlt:: SendCtrlKey
; ~RAlt:: SendCtrlKey
; SendCtrlKey()
; {
; if (skipCtrlKey = 0) {
; ; pressKey := GetKeyName(StrReplace(StrReplace(A_ThisHotkey,'~',''),' Up',''))
; if ctrlKeyCount < maxCtrlpressCount
; {
; pressKey := GetKeyName(StrReplace(A_ThisHotkey, '~', ''))
; PushTxt pressKey
; global ctrlKeyCount += 1
; }
; }
; }
; 鼠标事件
#HotIf InStr(skipKeys, "{WheelUp}") = 0
~*WheelUp:: SendMouse
#HotIf InStr(skipKeys, "{WheelDown}") = 0
~*WheelDown:: SendMouse
#HotIf InStr(skipKeys, "{LButton}") = 0
~*LButton:: SendMouse
#HotIf InStr(skipKeys, "{MButton}") = 0
~*MButton:: SendMouse
#HotIf InStr(skipKeys, "{RButton}") = 0
~*RButton:: SendMouse
#HotIf InStr(skipKeys, "{WheelLeft}") = 0
~*WheelLeft:: SendMouse
#HotIf InStr(skipKeys, "{WheelRight}") = 0
~*WheelRight:: SendMouse
#HotIf InStr(skipKeys, "{XButton1}") = 0
~*XButton1:: SendMouse
#HotIf InStr(skipKeys, "{XButton2}") = 0
~*XButton2:: SendMouse
SendMouse()
{
;OutputDebug('AHK hotkey:' A_ThisHotkey)
if (showMouseEvent > 0) {
; 需要加组合键
fullKey := AddModifier() GetKeyName(StrReplace(A_ThisHotkey, '~*', ''))
PushTxt(fullKey, True)
}
}
; 添加 <^ 这些符号,以确保一致
AddModifier() {
Modfier := ''
; 判断 Shift 键是否按下
for key, val in ModifierMapping
{
if (GetKeyState(key))
{
Modfier .= val
}
}
return Modfier
}
CoordMode "ToolTip", "Screen"
CoordMode "Mouse", "Screen"
GetDistance(){
try {
GetDistanceCore()
}
}
GetDistanceCore() {
MouseGetPos ¤tX, ¤tY
if currentX < minLeft || currentX > maxRight || currentY < minTop || currentY > maxBottom
{
; OutputDebug 'AHK currentX:' currentX ',currentY:' currentY ' minLeft ' minLeft ' maxRight' maxRight ' minTop ' minTop ' maxBottom ' maxBottom
if (currentY > maxBottom * 100 || currentX > maxRight * 100) {
; 坐标差异太大,可能锁屏状态下直接抛弃数据
return
}
; 暂时抛弃异常数据,更新屏幕信息
; MsgBox 'currentX:' currentX ',currentY:' currentY ' minLeft ' minLeft ' maxRight' maxRight ' minTop ' minTop ' maxBottom ' maxBottom
SendPCInfo(1)
; Sleep(500) ; 可能是锁屏状态,等待下,不必马上循环 ; 防止影响按键响应取消sleep
return
}
; 计算鼠标移动距离
; 计算两点间的直线距离
distance := Integer(Sqrt((currentX - mouseStartX) ** 2 + (currentY - mouseStartY) ** 2))
if distance > 0 {
global mouseDistance += distance
; 在命令行窗口中输出距离
;ShowTxt 'Distance: ' mouseDistance
AllKeyRecord['mouseDistance'] := mouseDistance
global mouseStartX := currentX
global mouseStartY := currentY
}
}
; 是否需要记录距离
if recordMouseMove = 1 {
SetTimer(GetDistance, 100) ; 每n毫秒记录一次位置
}
; 判断某内容是否在数组中
HasVal(arr, val) {
For Index, Value in arr {
if Value == val {
return Index
}
}
return 0
}
; 是否统计分钟数据
GetMinuteData(isMouse, isPress) {
; 如果同时调用
if (GetMinuteDataFlag) {
OutputDebug ("AutoHotkey GetMinuteData ")
return
}
;startT := A_TickCount
global GetMinuteDataFlag := True
GetMinuteDataCore(isMouse, isPress)
global GetMinuteDataFlag := False
;OutputDebug ("AutoHotkey GetMinuteData: " (A_TickCount - startT) ' : ' A_TickCount)
}
; 统计核心
GetMinuteDataCore(isMouse, isPress) {
currMinute := FormatTime(A_Now, "yyyyMMddHHmm")
initMouse := 0
initKey := 0
if isPress {
if isMouse {
initMouse := 1
} else {
initKey := 1
}
}
AppPath := GetProcPath()
if(AppPath=''){
return
}
; 有操作数据才更新APP信息
currData := Map("Minute", currMinute, "MouseCount", globalMouseCount, "KeyCount", globalKeyCount
, "Distance", mouseDistance, "Apps", Map(AppPath, Map("Mouse", initMouse, "Key", initKey)))
Len := MinuteRecords.Length ; 最后的数据不准确,不能计算
MaxLen := 10
if Len > MaxLen {
; 清理 MaxLen 个以外 的数据,为了减少带宽,至少为 MaxLen 分钟数据
; MaxLen分钟内没有任何点击,但有鼠标移动,因为只有按键或点击才上传数据
MinuteRecords.removeAt(1)
}
if Len == 0 {
MinuteRecords.push(currData)
return ; 刚才开始不用做任何计算
}
; 最后一个时间不一样则插入
last := MinuteRecords[-1]
if last['Minute'] != currMinute {
; 更新之前的数据,由总数变成统计数据
last['MouseCount'] := globalMouseCount - last['MouseCount']
last['KeyCount'] := globalKeyCount - last['KeyCount']
last['Distance'] := mouseDistance - last['Distance']
; 如果 Distance 距离小于 50 ,则认为没移动
if last['MouseCount'] = 0 && last['KeyCount'] = 0 && last['Distance'] <50 {
MinuteRecords[-1] := currData ; 因为为空数据,那么则直接替换
} else {
; 插入新数据
MinuteRecords.push(currData)
}
} else {
; 同一分钟内,那么需要添加不同的APP名
if isPress {
if (last["Apps"].has(AppPath)) {
if isMouse {
last["Apps"][AppPath]["Mouse"] += 1
} else {
last["Apps"][AppPath]["Key"] += 1
}
} else {
last["Apps"][AppPath] := Map("Mouse", initMouse, "Key", initKey)
}
}
}
}
GetMinuteDataTimer() {
GetMinuteData(False, False)
}
if recordMinute = 1 {
SetTimer(GetMinuteDataTimer, 1000) ; 每1秒刷新一次分钟数据
}
; 计算重启时间
today := SubStr(A_Now, 1, 8)
tomorrow := DateAdd(today, 1, "Days")
ShutDownLeft()
{
; 距离明天凌晨 0:00:05 的秒数,+5秒是为了给系统时间不准留点余量
return (DateDiff(tomorrow, A_Now, "Seconds") + 5) * 1000
}
; 设置一个计时器用于跨夜时重启进程以便保存当日数据并开始新的一天
SetTimer(Reload, ShutDownLeft())
; 托盘相关
global MyMenu
global LinkPath := A_Startup "\" APPName ".Lnk"
MenuHandler(ItemName, ItemPos, MyMenu) {
if (ItemName = L_menu_startup)
{
If FileExist(LinkPath)
{
FileDelete LinkPath
MyMenu.Uncheck(L_menu_startup)
}
else
{
FileCreateShortcut A_ScriptFullPath, A_Startup "\" APPName ".Lnk", A_ScriptDir
MyMenu.Check(L_menu_startup)
}
}
if (ItemName = L_menu_reload)
{
Reload()
}
if (ItemName = L_menu_reset)
{
ExitServer()
Reload()
}
if (ItemName = L_menu_exit)
{
ExitServer()
ExitApp()
}
if (ItemName = L_menu_set)
{
if serverState = 1 {
Run serverUrl "/Setting"
} else {
MsgBox menu_msg_noserver
}
}
if (ItemName = L_menu_stat)
{
if serverState = 1 {
Run serverUrl "/Today"
} else {
MsgBox menu_msg_noserver
}
}
if (ItemName = L_menu_hook)
{
CloseGetKeyInput(1)
CreateGetKeyInput()
}
if (ItemName = L_menu_4show)
{
Switch4show()
}
}
UpdatMenu4Show() {
if needShowKey {
A_TrayMenu.Check(L_menu_4show)
} else {
A_TrayMenu.Uncheck(L_menu_4show)
}
}
CreateMenu()
{
A_IconTip := APPName " v" ver
if (needTraytip) {
TrayTip(A_IconTip) ; 托盘提示信息
}
MyMenu := A_TrayMenu
; 清空默认菜单
MyMenu.Delete()
MyMenu.Add(L_menu_startup, MenuHandler)
MyMenu.Add(L_menu_reload, MenuHandler)
if needRecordKey = 1 {
MyMenu.Add(L_menu_reset, MenuHandler)
}
MyMenu.Add(L_menu_hook, MenuHandler)
MyMenu.Add(L_menu_4show, MenuHandler)
UpdatMenu4Show()
MyMenu.Add(L_menu_set, MenuHandler)
MyMenu.Add(L_menu_stat, MenuHandler)
MyMenu.Add(L_menu_exit, MenuHandler)
; 初始化默认状态
If FileExist(LinkPath)
{
FileGetShortcut LinkPath, &OutTarget, &OutDir, &OutArgs, &OutDesc, &OutIcon, &OutIconNum, &OutRunState
if (OutTarget = A_ScriptFullPath) {
MyMenu.Check(L_menu_startup)
}
}
}
CreateMenu()
; 是否可以控制隐藏的窗口
DetectHiddenWindows(true)
; flag = 1 是重启底层,需要发送成功
CloseGetKeyInput(flag){
str := "ahk_id " getKeyInputHwnd
if WinExist(str) {
OutputDebug("[KeyInput] Close " str)
if(flag == 1){
SendMessage 0x0010, 0, 0, , str
Sleep(1000) ; 退出需要时间,只允许一个实例
}else{
PostMessage 0x0010, 0, 0, , str
}
return
}
str := "ahk_class " getKeyInputClass
if WinExist(str) {
OutputDebug("[KeyInput] Close " str)
if(flag == 1){
SendMessage 0x0010, 0, 0, , str
Sleep(1000) ; 退出需要时间,只允许一个实例
}else{
PostMessage 0x0010, 0, 0, , str
}
return
}
}
; 关闭前需要退出后台服务
OnExit ExitFunc
ExitFunc(ExitReason, ExitCode)
{
;if ExitReason != "Logoff" and ExitReason != "Shutdown"
;{
;Result := MsgBox("Are you sure you want to exit?",, 4)
;if Result = "No"
; return 1 ; Callbacks must return non-zero to avoid exit.
;}
; 需要将 临时的配置开关保存
If FileExist(IniFile) {
; 当文件中的参数没有被人修改时才写入状态,否则以文件中数据为准
tempVal := DescRead("common", "needShowKey", "1")
if tempVal != needShowKey {
IniWrite(needShowKey, IniFile, "common", "needShowKey")
}
}
; 检查后台服务情况
pidPath := httpPath 'kbserver.pid'
lastRecordPath := httpPath 'lastRecord.json'
If FileExist(pidPath) {
; 如果存在进程文件,那么要保存到临时文件中,后端重启启动后优先载入临时文件内容更新数据
If FileExist(lastRecordPath) {
FileDelete lastRecordPath ; 先删
}
; 对于 AllKeyRecord 中最后的数据需要处理
if MinuteRecords.Length > 0 {
last := MinuteRecords[-1]
last['MouseCount'] := globalMouseCount - last['MouseCount']
last['KeyCount'] := globalKeyCount - last['KeyCount']
last['Distance'] := mouseDistance - last['Distance']
last['LastFlag'] := 1 ; 标注为最后的一次
}
FileAppend(JSON.stringify(AllKeyRecord, 0), lastRecordPath)
}
CloseGetKeyInput(0)
}
#Include dialog.ahk
; 原先的方法监控全局按键,缺点:快速按键时候容易丢失
; ; 建立钩子抓取数据,默认不要阻塞 V I0
; ih := InputHook("V I99") ; Level 定为100,可以忽略一些 send 发送的字符,默认send的level 为0
; ; 设置所有按键的监听
; ih.KeyOpt("{All}", "NE") ; End
; ; 去掉控制按键的响应计数
; ih.KeyOpt(skipKeys, "-E")
; MyKeyUp(ih ,VK, SC)
; {
; ; OutputDebug ("AutoHotkey Up:" GetKeyName(Format("vk{:x}sc{:x}", VK, SC)) )
; global repeatRecord := 0
; }
; ih.OnKeyUp := MyKeyUp
; KeyWaitCombo()
; {
; ;InputHook.VisibleText := true
; ;InputHook.VisibleNonText := true
; ; 开始监控
; ih.Start()
; ;OutputDebug ("AutoHotkey InProgress " . ih.InProgress )
; ih.Wait()
; global repeatRecord
; if(repeatRecord < maxKeypressCount)
; {
; ; OutputDebug ("AutoHotkey " . ih.EndMods . ih.EndKey ) ; 类似 <^<+Esc
; ;OutputDebug ("AutoHotkey KeyState " . GetKeyState(ih.EndKey, "P") ) ; 类似 <^<+Esc
; PushTxt( ih.EndMods . ih.EndKey )
; repeatRecord += 1 ;防止重复记录
; }
; ;OutputDebug ("AutoHotkey : " . ih.EndMods . ih.EndKey . " InProgress:" . ih.InProgress )
; ;return ih.EndMods . ih.EndKey ; Return a string like <^<+Esc
; }
; ; 这个可能导致死循环,必须最后
; Loop {
; KeyWaitCombo()
; }
; 第三种方法监控全局按键,缺点: 可能丢失一些未知的按键
; LogKey(thisKey) {
; ; Critical -1
; k := GetKeyName(vksc := SubStr(A_ThisHotkey, 3))
; k := StrReplace(k, "Control", "Ctrl"), r := SubStr(k, 2)
; ; 判断 Shift 键是否按下
; isShiftDown := GetKeyState("Shift")
; ; 判断 Control 键是否按下
; isCtrlDown := GetKeyState("Control")
; OutputDebug ("AutoHotkey key:" thisKey " k: " k ' shift: ' isShiftDown ' Control: ' isCtrlDown )
; }
; SetHotkey(f := 0) {
; ; These keys are already used as hotkeys
; global UsedKeys
; f := f ? "On" : "Off"
; Loop 254 {
; k := GetKeyName(vk := Format("vk{:X}", A_Index))
; if k != "" && !("Control" = k) && !("Alt" = k) && !("Shift" = k) &&
; !("F1" = k) && !("F2" = k) && !("F3" = k) && !("F4" = k) &&
; !("F5" = k) && !("F6" = k) && !("F9" = k) {
; try {
; Hotkey "~*" vk, LogKey, f
; } catch as e {
; }
; }
; }
; For i, k in StrSplit("NumpadEnter|Home|End|PgUp"
; . "|PgDn|Left|Right|Up|Down|Delete|Insert", "|")
; {
; sc := Format("sc{:03X}", GetKeySC(k))
; if not k = "" && !("Control" = k) && !("Alt" = k) && !("Shift" = k) &&
; !("F1" = k) && !("F2" = k) && !("F3" = k) && !("F4" = k) &&
; !("F5" = k) && !("F6" = k) && !("F9" = k) {
; try {
; Hotkey "~*" sc, LogKey, f
; } catch as e {
; }
; }
; }
; }
; SetHotkey(1)
; 启动接收函数用于接收按键消息
ReceiveKeyInput(){
; 监听 WM_COPYDATA 消息 (0x004A)
sendKeySep := '|' ; 链接分隔符,和发送端一致,用于合并快速发送的的消息
OnMessage(0x004A, Receive_WM_COPYDATA)
Receive_WM_COPYDATA(wParam, lParam, msg, hwnd) {
; 从 lParam 指向的 COPYDATASTRUCT 结构中读取数据
; 结构布局: [dwData, cbData, lpData]
; 每个字段在 64 位系统中占 8 字节 (A_PtrSize)
; 1. 获取字符串数据的内存地址 (lpData)
; 偏移量为 2 * A_PtrSize 是因为前两个字段各占 A_PtrSize 字节
dataAddress := NumGet(lParam + 2 * A_PtrSize, "Ptr")
; 2. 获取字符串数据的字节大小 (cbData)
dataSize := NumGet(lParam + A_PtrSize, "UInt")
; 3. 从内存地址中读取字符串
; 除以 2 得到字符数 (因为是 UTF-16)
receivedString := StrGet(dataAddress, dataSize // 2, "UTF-16")
; 显示接收到的内容
; 使用 ToolTip 而不是 MsgBox 以避免阻塞消息处理
;PushTxt( receivedString )
keyArr := StrSplit(receivedString, sendKeySep)
For index, key in keyArr
{
PushTxt(key)
}
; 返回值 1 表示已处理该消息(这是惯例)
return 1
}
}
ReceiveKeyInput()
; 启动进程用于读取按键
CreateGetKeyInput(){
try {
global getKeyInputHwnd
; 先检查是否已经存在进程
str := "ahk_class " getKeyInputClass
lastHwnd := WinExist(str)
if( lastHwnd ){
if ( getKeyInputHwnd == lastHwnd){
MsgBox('Not Closed')
}
getKeyInputHwnd := lastHwnd
}else{
OutputVarPID := 0
cmd := getKeyInputExe " " maxKeypressCount " " skipKeys
Run(cmd,,,&OutputVarPID)
; MsgBox("OutputVarPID: " OutputVarPID)
; 根据 PID 获取窗口句柄
str := "ahk_pid " OutputVarPID
; 等待窗口(关键)
myHWnd := WinWait(str,,1.5)
if( myHWnd > 0 ){
getKeyInputHwnd := myHWnd
}else{
MsgBox(msgNotLaunchHook ":" getKeyInputExe "," str)
}
}
} catch {
MsgBox(msgNotLaunchHook ":" getKeyInputExe)
}
}
; 以下为对 游戏手柄的按键读取
; 可以支持多个摇杆
maxJoyPressCount := 1 ; 设置为长按只有一次,如果某个按键一直按住没松开过,那么不大于 maxKeypressCount 次
joyNameMap := Map()
; 以下为方案1的手柄读取方案
ControllerNumber := [] ; 默认无摇杆,可能出现1没有,但2有的情况
JoyNameList := [] ; 游戏手柄清单
TestJoyList := ["Z", "R", "U", "V", "P"] ; ["JoyX","JoyY","JoyZ","JoyR","JoyU","JoyV","JoyPOV"]
JoyList := Map()
; 检查游戏手柄整体信息
checkJoyInfo() {
global ControllerNumber
global JoyNameList
global JoyList
tmpControllerNumber := [] ; 默认无摇杆
tmpJoyNameList := []
Loop 4 ; Query each controller number to find out which ones exist.
{
joyName := GetKeyState(A_Index "JoyName")
; 需要有控制器名,且能获取到X的浮点数据
if joyName
{
JoyXType := Type(GetKeyState(A_Index "JoyX")) ; Float
if (JoyXType != "Float") {
Reload() ; 识别数据异常,可能手柄拔出了,目前AHK存在bug,只有重启可正确识别
return
}
tmpJoyNameList.Push(joyName)
tmpControllerNumber.Push(A_Index)
}
}
; 检查新的游戏列表是否和之前的一样,一样则跳过
if (arrayEqual(tmpJoyNameList, JoyNameList)) {
; OutputDebug("Joy same")
return
} else {
; 获取摇杆后,获取最大按键数,绑定所有按键
for index in tmpControllerNumber {
cont_buttons := GetKeyState(index "JoyButtons")
ControllerPrefix := index "Joy"
Loop cont_buttons {
Hotkey ControllerPrefix . A_Index, MyShowKey ; () => MyShowKey
}
}
JoyList := Map()
for index in tmpControllerNumber {
cont_info := GetKeyState(index "JoyInfo")
JoyList[index] := ["X", "Y"] ; 必然有 X Y
for value in TestJoyList {
if InStr(cont_info, value) {
if (value == "P")
{
value := "POV" ; 需要转换下
}
JoyList[index].push(value) ; 真实有效的摇杆清单
}
}
}
; 需要设置生效
ControllerNumber := tmpControllerNumber
JoyNameList := tmpJoyNameList
}
; for controlId in ControllerNumber {
; OutputDebug("Joy controlId:" controlId)
; }
; for joyname in JoyNameList {
; OutputDebug("Joy joyname:" joyname)
; }
}
; 比较数组一致性
arrayEqual(arr1, arr2) {
if (arr1.Length != arr2.Length)
return false
for i, v in arr1
if (v != arr2[i])
return false
return true
}
MyShowKey(*) {
PushTxt(A_ThisHotkey)
; OutputDebug( "Joy :" A_ThisHotkey)
}
; 不同类别的按键数据代表不同含义
getJoyInfo(controlId, joyN) {
global joyNameMap
joyFullName := controlId "Joy" joyN
if (!joyNameMap.has(joyFullName)) {
joyNameMap[joyFullName] := 0
}
keyName := ""
val := 50 ; 默认50,表示没动作或复位
; 存在才计算
val := Round(GetKeyState(joyFullName))
Switch joyN
{
Case "Z":
; LT / RT
if (val < 50) {
keyName := controlId "JoyRT"
}
if (val > 50) {
keyName := controlId "JoyLT"
}
Case "POV":
; -1 0,4500,9000,13500,18000,22500,27000,31500, 有8个方向
initPov := 0
loop 8 {
if (val == initPov) {
keyName := joyFullName A_Index
break
}
initPov += 4500
}
Default:
if (val != 50) {
keyName := joyFullName
}
}
if keyName != "" {
joyNameMap[joyFullName] += 1
if (joyNameMap[joyFullName] <= maxJoyPressCount) {
PushTxt(keyName)
; OutputDebug "Joy :" keyName
}
} else {
joyNameMap[joyFullName] := 0 ; 松开
}
}
checkJoyState()
{
global JoyList
global ControllerNumber
for controlId in ControllerNumber {
try {
for index, value in JoyList[controlId] {
getJoyInfo(controlId, value)
}
}
}
}
; 以下为方案2的手柄监控方案,直接调用XINPUT接口
#Include "lib/XInput.ahk"
; 不同类别的按键数据代表不同含义
getJoyInfo2(controlId, State) {
; Constants for gamepad buttons
XINPUT_GAMEPAD_DPAD_UP := 0x0001
XINPUT_GAMEPAD_DPAD_DOWN := 0x0002
XINPUT_GAMEPAD_DPAD_LEFT := 0x0004
XINPUT_GAMEPAD_DPAD_RIGHT := 0x0008
XINPUT_GAMEPAD_START := 0x0010
XINPUT_GAMEPAD_BACK := 0x0020
XINPUT_GAMEPAD_LEFT_THUMB := 0x0040
XINPUT_GAMEPAD_RIGHT_THUMB := 0x0080
XINPUT_GAMEPAD_LEFT_SHOULDER := 0x0100
XINPUT_GAMEPAD_RIGHT_SHOULDER := 0x0200
XINPUT_GAMEPAD_GUIDE := 0x0400
XINPUT_GAMEPAD_A := 0x1000
XINPUT_GAMEPAD_B := 0x2000
XINPUT_GAMEPAD_X := 0x4000
XINPUT_GAMEPAD_Y := 0x8000
global joyNameMap
keyNameArr := [] ; 按键是个数组
; 方向键,有8个方向,按 上,上右,右,顺时针排序数组
POVArr := [XINPUT_GAMEPAD_DPAD_UP, XINPUT_GAMEPAD_DPAD_UP | XINPUT_GAMEPAD_DPAD_RIGHT, XINPUT_GAMEPAD_DPAD_RIGHT, XINPUT_GAMEPAD_DPAD_RIGHT | XINPUT_GAMEPAD_DPAD_DOWN,
XINPUT_GAMEPAD_DPAD_DOWN, XINPUT_GAMEPAD_DPAD_DOWN | XINPUT_GAMEPAD_DPAD_LEFT, XINPUT_GAMEPAD_DPAD_LEFT, XINPUT_GAMEPAD_DPAD_LEFT | XINPUT_GAMEPAD_DPAD_UP]
low_4_bits := State.wButtons & 0x000F ; 低4位为方向
for index, poVal in POVArr {
if (low_4_bits == poVal) {
keyNameArr.Push(controlId "JoyPOV" index)
break ; 只可能满足1个,所以可以退出
}
}
; 按键的顺序编号
ButtonArr := [XINPUT_GAMEPAD_A, XINPUT_GAMEPAD_B, XINPUT_GAMEPAD_X, XINPUT_GAMEPAD_Y, XINPUT_GAMEPAD_LEFT_SHOULDER, XINPUT_GAMEPAD_RIGHT_SHOULDER,
XINPUT_GAMEPAD_BACK, XINPUT_GAMEPAD_START, XINPUT_GAMEPAD_LEFT_THUMB, XINPUT_GAMEPAD_RIGHT_THUMB, XINPUT_GAMEPAD_GUIDE
]
for index, val in ButtonArr {
if (State.wButtons & val) {
keyNameArr.Push(controlId "Joy" index)
}
}
; 扳机 ,假设10为触发
triggerHolds := 10
thumBholds := 2000 ; 数据较为灵敏
if (State.bLeftTrigger > triggerHolds) {
keyNameArr.Push(controlId "JoyLT")
}
if (State.bRightTrigger > triggerHolds) {
keyNameArr.Push(controlId "JoyRT")
}
; X ,Y (左摇杆),R,U(右摇杆)
if (Abs(State.sThumbLX) > thumBholds) {
keyNameArr.Push(controlId "JoyX")
}
if (Abs(State.sThumbLY) > thumBholds) {
keyNameArr.Push(controlId "JoyY")
}
if (Abs(State.sThumbRX) > thumBholds) {
keyNameArr.Push(controlId "JoyU")
}
if (Abs(State.sThumbRY) > thumBholds) {
keyNameArr.Push(controlId "JoyR")
}
; 按下则 +1
for index, joyFullName in keyNameArr {
; 不存在就创建一个对象
if (!joyNameMap.has(joyFullName)) {
joyNameMap[joyFullName] := 0
}
joyNameMap[joyFullName] += 1
if (joyNameMap[joyFullName] <= maxJoyPressCount) {
PushTxt(joyFullName)
; OutputDebug "Joy :" keyName
}
}
; 不在 keyNameArr 中的按键则全部复位为 0
for key, val in joyNameMap {
; 如果不是当前设备的,跳过
if(InStr(key, controlId "Joy") == 0){
continue
}
if (!IsInArray(keyNameArr, key)) {
joyNameMap[key] := 0 ; 松开
}
}
}
; 判断字符串是否存在于数组中(区分大小写)
IsInArray(arr, searchStr) {
for element in arr {
if (element = searchStr)
return true
}
return false
}
checkJoyState2()
{
Loop 4 {
if State := XInput_GetState(A_Index - 1) {
; OutputDebug("Joy " A_Index " dwPacketNumber: " State.dwPacketNumber " wButtons:" State.wButtons
; " LT:" State.bLeftTrigger " RT:" State.bRightTrigger " sThumbLX:" State.sThumbLX " sThumbLY:" State.sThumbLY " sThumbRX:" State.sThumbRX " sThumbRY:" State.sThumbRY)
; 解析 State
getJoyInfo2(A_Index, State)
}
}
}
; 正式启用方案1
if ( joyMethod == 1) {
SetTimer(checkJoyState, 100) ; 0.1秒检测一次
SetTimer(checkJoyInfo, 2000) ; 2秒检测一次
} else if (joyMethod == 2)
{
; 方案2
try{
XInput_Init()
SetTimer(checkJoyState2, 100) ; 0.1秒检测一次
}
}
; 初始化托盘图标的核心函数
InitTrayIcon() {
currentPath := A_ScriptFullPath
; 获取不带扩展名的文件名(用于构造ICO文件名)
fileNameNoExt := SubStr(A_ScriptName, 1, -StrLen('EXE') - 1)
; 构造ICO文件路径(与脚本/程序同目录,同名ICO)
icoPath := StrReplace(currentPath, A_ScriptName, fileNameNoExt ".ico")
; 检查ICO文件是否存在
if (FileExist(icoPath)) {
; 存在同名ICO文件,使用它
TraySetIcon(icoPath)
}
}
InitTrayIcon()
setMyPid(){
; 不能修改title否则会导致reload异常
;WinSetTitle( myWindowTitle,"ahk_id " A_ScriptHwnd)
; 写入PID
pidFile := A_ScriptDir . "\kb.pid"
myPid := ProcessExist()
try {
FileOpen(pidFile, "w").Write(myPid)
}
CreateGetKeyInput()
}
setMyPid()