-
Notifications
You must be signed in to change notification settings - Fork 0
/
sethook.go
190 lines (153 loc) · 5.07 KB
/
sethook.go
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
package main
import (
"fmt"
"log"
"unsafe"
. "github.com/ttimt/Short_Cutteer/hook/windows"
)
const (
nrOfKeys = 129 // Number of possible keys
invalidRune = -1
invalidWord = 0
)
var (
currentKeyStrokeSignal = make(chan rune)
hhook HHOOK
autoCompleteJustDone bool
bufferStr string
)
// Initialize DLLs and start listening to keyboard hook
func setupWindowsHook() {
log.Println("Keyboard listener started ......")
// Load all required DLLs
_ = LoadDLLs()
// Setup hook annd receive message
go receiveHook()
}
// Detect hook
func receiveHook() {
// Declare a keyboard hook callback function (type HOOKPROC)
hookCallback := func(code int, wParam WPARAM, lParam LPARAM) LRESULT {
// If keystroke is pressed down
if wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN {
// Retrieve the keyboard hook struct
keyboardHookData := (*TagKBDLLHOOKSTRUCT)(unsafe.Pointer(uintptr(lParam))) // skipcq: GSC-G103
// Retrieve current keystroke from keyboard hook struct's vkCode
currentKeystroke := rune((*keyboardHookData).VkCode)
// Send the keystroke to be processed
select {
case currentKeyStrokeSignal <- currentKeystroke:
default:
// Skip if the channel currentKeyStrokeSignal is busy,
// as it means the current keystroke is sent by the processHook while processing,
// we want to ignore keystrokes that we sent ourself
}
}
// Return CallNextHookEx result to allow keystroke to be displayed on user screen
return CallNextHookEx(0, code, wParam, lParam)
}
// Install a Windows hook that listen to keyboard input
hhook, _ = SetWindowsHookExW(WH_KEYBOARD_LL, hookCallback, 0, 0)
if hhook == 0 {
panic("Failed to set Windows hook")
}
// Run hook processing goroutine
go processHook()
// Start retrieving message from the hook
if b, _ := GetMessageW(0, 0, 0, 0); !b {
panic("Failed to get message")
}
}
// Process your received keystroke here
func processHook() {
for {
// Receive keystroke as rune from channel
currentKeyStroke := <-currentKeyStrokeSignal
// Process keystroke
fmt.Printf("Current key: %d 0x0%X %c\n", currentKeyStroke, currentKeyStroke, currentKeyStroke)
shiftKeyState, _ := GetKeyState(VK_SHIFT)
capsLockState, _ := GetKeyState(VK_CAPITAL)
isShiftEnabled := getKeyState(shiftKeyState)
isCapsEnabled := getKeyState(capsLockState, true)
key := getKeyByKeyCode(uint16(currentKeyStroke), isShiftEnabled, isCapsEnabled)
var char rune
if key == nil {
char = invalidRune
} else {
char = key.Char
}
if isAutoComplete(char) {
// Process auto complete
tagInputs := processAutoComplete(char, isShiftEnabled, isCapsEnabled)
// Send input
_, _ = SendInput(uint(len(tagInputs)), (*LPINPUT)(&tagInputs[0]), int(unsafe.Sizeof(tagInputs[0]))) // skipcq: GSC-G103
continue
}
// Reset if character is not a letter/symbol/number
if char == -1 {
bufferStr = ""
if currentKeyStroke != VK_SHIFT && currentKeyStroke != VK_LSHIFT && currentKeyStroke != VK_RSHIFT {
autoCompleteJustDone = false
}
continue
}
// User pre-collectionCommands can be CTRL, ALT, SHIFT and 1 letter afterwards
// User can create shortcut MODIFIER + a key or text collectionCommands + tab or space (remove collectionCommands)
readSingleCharacter(char, isShiftEnabled, isCapsEnabled)
autoCompleteJustDone = false
fmt.Println("Buffer string:", bufferStr)
}
}
// Read single character and update buffer, for ending key like tab or enter
func readSingleCharacter(char rune, isShiftEnabled, isCapsEnabled bool) {
switch char {
case '\b':
if len(bufferStr) > 0 {
bufferStr = bufferStr[:len(bufferStr)-1]
}
if autoCompleteJustDone {
// Send input
tagInputs := getKeyByKeyCode(VK_DELETE).KeyPress()
_, _ = SendInput(uint(len(tagInputs)), (*LPINPUT)(&tagInputs[0]), int(unsafe.Sizeof(tagInputs[0]))) // skipcq: GSC-G103
}
case '\r':
bufferStr += windowsNewLine
bufferStr = ""
case '\t':
if ok, command := hasValidCommand(); ok {
// Send input
sendInput(createTagInputs(command.Output, isShiftEnabled, isCapsEnabled))
}
bufferStr = ""
case ' ':
if ok, command := hasValidCommand(); ok {
// Send input
fmt.Println(len(bufferStr) - len(command.Command))
tagInputsBackspace := getKeyByKeyCode(VK_BACK).KeyPress(len(command.Command) + 1)
_, _ = SendInput(uint(len(tagInputsBackspace)), (*LPINPUT)(&tagInputsBackspace[0]), int(unsafe.Sizeof(tagInputsBackspace[0]))) // skipcq: GSC-G103
sendInput(createTagInputs(command.Output, isShiftEnabled, isCapsEnabled))
}
bufferStr = ""
default:
// If buffer full, trim
if len(bufferStr) >= maxCommandLen && len(bufferStr) > 0 {
bufferStr = bufferStr[1:]
}
bufferStr += string(char)
}
}
func sendInput(tagInputs []TagINPUT) {
_, _ = SendInput(uint(len(tagInputs)), (*LPINPUT)(&tagInputs[0]), int(unsafe.Sizeof(tagInputs[0]))) // skipcq: GSC-G103
}
func hasValidCommand() (bool, *Command) {
bufferLen := len(bufferStr)
for k := range sliceCommandLen {
if k > bufferLen {
break
}
if command, ok := userCommands[bufferStr[bufferLen-k:]]; ok {
return true, command
}
}
return false, nil
}