-
Notifications
You must be signed in to change notification settings - Fork 1
/
hook.h
277 lines (247 loc) · 16.1 KB
/
hook.h
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
/*
AutoHotkey
Copyright 2003-2008 Chris Mallett (support@autohotkey.com)
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#ifndef hook_h
#define hook_h
#include "stdafx.h" // pre-compiled headers
#include "hotkey.h" // Use here and also by hook.cpp for ChangeHookState(), which reads from static Hotkey class vars.
// WM_USER is the lowest number that can be a user-defined message. Anything above that is also valid.
// NOTE: Any msg about WM_USER will be kept buffered (unreplied-to) whenever the script is uninterruptible.
// If this is a problem, try making the msg have an ID less than WM_USER via a technique such as that used
// for AHK_USER_MENU (perhaps WM_COMMNOTIFY can be "overloaded" to contain more than one type of msg).
// Also, it has been announced in OnMessage() that message numbers between WM_USER and 0x1000 are earmarked
// for possible future use by the program, so don't use a message above 0x1000 without good reason.
enum UserMessages {AHK_HOOK_HOTKEY = WM_USER, AHK_HOTSTRING, AHK_USER_MENU, AHK_DIALOG, AHK_NOTIFYICON
, AHK_RETURN_PID, AHK_EXIT_BY_RELOAD, AHK_EXIT_BY_SINGLEINSTANCE
// Allow some room here in between for more "exit" type msgs to be added in the future (see below comment).
, AHK_GUI_ACTION = WM_USER+20 // Avoid WM_USER+100/101 and vicinity. See below comment.
// v1.0.43.05: On second thought, it seems better to stay close to WM_USER because the OnMessage page
// documents, "it is best to choose a number greater than 4096 (0x1000) to the extent you have a choice.
// This reduces the chance of interfering with messages used internally by current and future versions..."
// v1.0.43.03: Changed above msg number because Micha reports that previous number (WM_USER+100) conflicts
// with msgs sent by HTML control (AHK_CLIPBOARD_CHANGE) and possibly others (I think WM_USER+100 may be the
// start of a range used by other common controls too). So trying a higher number that's (hopefully) very
// unlikely to be used by OS features.
, AHK_CLIPBOARD_CHANGE, AHK_HOOK_TEST_MSG, AHK_CHANGE_HOOK_STATE, AHK_GETWINDOWTEXT
, AHK_EXECUTE // ahkx
, AHK_EXECUTE_FUNCTION
, AHK_EXECUTE_LABEL
, AHK_SENDKEYS
};
// NOTE: TRY NEVER TO CHANGE the specific numbers of the above messages, since some users might be
// using the Post/SendMessage commands to automate AutoHotkey itself. Here is the original order
// that should be maintained:
// AHK_HOOK_HOTKEY = WM_USER, AHK_HOTSTRING, AHK_USER_MENU, AHK_DIALOG, AHK_NOTIFYICON, AHK_RETURN_PID
// UPDATE to the below comment: After a careful review, it seems best to buffer a user's selection of
// a custom menu item if the current quasi-thread is uninterruptible. This is exactly the same way
// hotkeys are buffered. By making a thread truly uninterruptible, behavior is more consistent with
// what most users would want, though it should be noted that this also makes it impossible for the
// OnExit subroutine to be interrupted by a custom menu item, even one that is designed to simply
// exit the script (by means of aborting the on-exit subroutine). Another reason to buffer the
// selection of a custom menu item is that we don't want to interrupt the current thread if is
// right in the middle of executing a single line, e.g. doing something with the deref buffer
// (during which time an interruption might destroy the buffer's contents) or trying to open the
// clipboard or save data to it. This particular weakness may be resolved when/if a dedicated
// thread is created to maintain the hook(s), thus allowing critical operations such as opening
// the clipboard to use a true Sleep() rather than MsgSleep().
// Since WM_COMMNOTIFY is never generated by the Win32 API, and since we want AHK_USER_MENU to be
// an ID less than WM_HOTKEY so that it doesn't get filtered out when the script is uninterruptible,
// the following trick is used to map our user-defined messages onto WM_COMMNOTIFY by sacrificing the
// wParam part of the message (using it as an indicator of what the message really is).
// Another reserved msg that might be fairly safe is WM_MDIICONARRANGE, but it's far less preferable:
#define TRANSLATE_AHK_MSG(msg, wparam) \
if (msg == WM_COMMNOTIFY)\
{\
msg = (UINT)wparam;\
wparam = 0;\
} // In the above, wparam is made zero to help catch bugs.
// And these macros are kept here so that all this trickery is centrally located and thus more maintainable:
#define ASK_INSTANCE_TO_CLOSE(hwnd, reason) PostMessage(hwnd, WM_COMMNOTIFY, reason, 0);
// POST_AHK_USER_MENU: A gui_index value >= 0 is passed with the message if it came from a GUI's menu bar.
// This is done because it's good way to pass the info, but also so that its value will be in sync with the
// timestamp of the message (in case the message is stuck in the queue for a long time). No pointer is
// passed in this case since they might become invalid between the time the msg is posted vs. processed.
#define POST_AHK_USER_MENU(hwnd, menu, gui_index) PostMessage(hwnd, AHK_USER_MENU, gui_index, menu);
#define POST_AHK_GUI_ACTION(hwnd, control_index, gui_event, event_info) PostMessage(hwnd, AHK_GUI_ACTION \
, (WPARAM)(((control_index) << 16) | (gui_event)), (LPARAM)(event_info)); // Caller must ensure that gui_event is less than 0xFFFF.
// POST_AHK_DIALOG:
// Post a special msg that will attempt to force it to the foreground after it has been displayed,
// since the dialog often will flash in the task bar instead of becoming foreground.
// It's enough just to queue up a single message that dialog's message pump will forward to our
// main window proc once the dialog window has been displayed. This avoids the overhead of creating
// and destroying the timer (although the timer may be needed anyway if any timed subroutines are
// enabled). My only concern about this is that on some OS's, or on slower CPUs, the message may be
// received too soon (before the dialog window actually exists) resulting in our window proc not
// being able to ensure that it's the foreground window. That seems unlikely, however, since
// MessageBox() and the other dialog invocating API calls (for FileSelectFile/Folder) likely
// ensures its window really exists before dispatching messages.
#define POST_AHK_DIALOG(timeout) PostMessage(g_hWnd, WM_COMMNOTIFY, AHK_DIALOG, (LPARAM)timeout);
// Some reasoning behind the below data structures: Could build a new array for [sc][sc] and [vk][vk]
// (since only two keys are allowed in a ModifierVK/SC combination, only 2 dimensions are needed).
// But this would be a 512x512 array of shorts just for the SC part, which is 512K. Instead, what we
// do is check whenever a key comes in: if it's a suffix and if a non-standard modifier key of any kind
// is currently down: consider action. Most of the time, an action be found because the user isn't
// likely to be holding down a ModifierVK/SC, while pressing another key, unless it's modifying that key.
// Nor is he likely to have more than one ModifierVK/SC held down at a time. It's still somewhat
// inefficient because have to look up the right prefix in a loop. But most suffixes probably won't
// have more than one ModifierVK/SC anyway, so the lookup will usually find a match on the first
// iteration.
struct vk_hotkey
{
vk_type vk;
HotkeyIDType id_with_flags;
};
struct sc_hotkey
{
sc_type sc;
HotkeyIDType id_with_flags;
};
// Style reminder: Any POD structs (those without any methods) don't use the "m" prefix
// for member variables because there's no need: the variables are always prefixed by
// the struct that owns them, so there's never any ambiguity:
struct key_type
{
ToggleValueType *pForceToggle; // Pointer to a global variable for toggleable keys only. NULL for others.
modLR_type as_modifiersLR; // If this key is a modifier, this will have the corresponding bit(s) for that key.
HotkeyIDType hotkey_to_fire_upon_release; // A up-event hotkey queued by a prior down-event.
// Keep sub-32-bit members contiguous to save memory without having to sacrifice performance of
// 32-bit alignment:
#define PREFIX_ACTUAL 1 // Values for used_as_prefix below, for places that need to distinguish between type of prefix.
#define PREFIX_FORCED 2 // v1.0.44: Added so that a neutral hotkey like Control can be forced to fire on key-up even though it isn't actually a prefix key.
UCHAR used_as_prefix; // Whether a given virtual key or scan code is even used by a hotkey.
bool used_as_suffix; //
bool used_as_key_up; // Whether this suffix also has an enabled key-up hotkey.
UCHAR no_suppress; // Contains bitwise flags such as NO_SUPPRESS_PREFIX.
bool is_down; // this key is currently down.
bool it_put_alt_down; // this key resulted in ALT being pushed down (due to alt-tab).
bool it_put_shift_down; // this key resulted in SHIFT being pushed down (due to shift-alt-tab).
bool down_performed_action; // the last key-down resulted in an action (modifiers matched those of a valid hotkey)
bool hotkey_down_was_suppressed; // Whether the down-event for a key was suppressed (thus its up-event should be too).
// The values for "was_just_used" (zero is the inialized default, meaning it wasn't just used):
char was_just_used; // a non-modifier key of any kind was pressed while this prefix key was down.
// And these are the values for the above (besides 0):
#define AS_PREFIX 1
#define AS_PREFIX_FOR_HOTKEY 2
bool sc_takes_precedence; // used only by the scan code array: this scan code should take precedence over vk.
UCHAR nModifierVK;
UCHAR nModifierSC;
// User is likely to use more modifying vks than we do sc's, since sc's are rare:
#define MAX_MODIFIER_VKS_PER_SUFFIX 50
#define MAX_MODIFIER_SCS_PER_SUFFIX 16
vk_hotkey ModifierVK[MAX_MODIFIER_VKS_PER_SUFFIX];
sc_hotkey ModifierSC[MAX_MODIFIER_SCS_PER_SUFFIX];
}; // Keep the macro below in sync with the above.
// Fix for v1.0.43.01: Caller wants item.no_suppress initialized to remove all flags except NO_SUPPRESS_STATES.
// Otherwise, a hotkey that removes the mouse hook will cause a non-suppressed key-up to become suppressed,
// causing the key to get stuck down. The following two-line script can reproduce this:
// ~LCtrl::Hotkey, RButton, Off
// RButton::return
#define RESET_KEYTYPE_ATTRIB(item) \
{\
item.nModifierVK = 0;\
item.nModifierSC = 0;\
item.used_as_prefix = 0;\
item.used_as_suffix = false;\
item.used_as_key_up = false;\
item.no_suppress &= NO_SUPPRESS_STATES;\
item.sc_takes_precedence = false;\
}
// Since index zero is a placeholder for the invalid virtual key or scan code, add one to each MAX value
// to compute the number of elements actually needed to accomodate 0 up to and including VK_MAX or SC_MAX:
#define VK_ARRAY_COUNT (VK_MAX + 1)
#define SC_ARRAY_COUNT (SC_MAX + 1)
#define INPUT_BUFFER_SIZE 16384
enum InputStatusType {INPUT_OFF, INPUT_IN_PROGRESS, INPUT_TIMED_OUT, INPUT_TERMINATED_BY_MATCH
, INPUT_TERMINATED_BY_ENDKEY, INPUT_LIMIT_REACHED};
// Bitwise flags for the end-key arrays:
#define END_KEY_ENABLED 0x01
#define END_KEY_WITH_SHIFT 0x02
#define END_KEY_WITHOUT_SHIFT 0x04
struct input_type
{
InputStatusType status;
UCHAR *EndVK; // A sparse array that indicates which VKs terminate the input.
UCHAR *EndSC; // A sparse array that indicates which SCs terminate the input.
vk_type EndingVK; // The hook puts the terminating key into one of these if that's how it was terminated.
sc_type EndingSC;
bool EndedBySC; // Whether the Ending key was one handled by VK or SC.
bool EndingRequiredShift; // Whether the key that terminated the input was one that needed the SHIFT key.
char **match; // Array of strings, each string is a match-phrase which if entered, terminates the input.
UINT MatchCount; // The number of strings currently in the array.
UINT MatchCountMax; // The maximum number of strings that the match array can contain.
#define INPUT_ARRAY_BLOCK_SIZE 1024 // The increment by which the above array expands.
char *MatchBuf; // The is the buffer whose contents are pointed to by the match array.
UINT MatchBufSize; // The capacity of the above above buffer.
bool BackspaceIsUndo;
bool CaseSensitive;
bool IgnoreAHKInput; // Whether input from any AHK script is ignored for the purpose of finding a match.
bool TranscribeModifiedKeys; // Whether the input command will attempt to transcribe modified keys such as ^c.
bool Visible;
bool FindAnywhere;
char *buffer; // Stores the user's actual input.
int BufferLength; // The current length of what the user entered.
int BufferLengthMax; // The maximum allowed length of the input.
input_type() // A simple constructor to initialize the fields that need it.
: status(INPUT_OFF), match(NULL), MatchBuf(NULL), MatchBufSize(0), buffer(NULL)
{}
};
//-------------------------------------------
struct KeyHistoryItem
{
vk_type vk;
sc_type sc;
char event_type; // space=none, i=ignored, s=suppressed, h=hotkey, etc.
bool key_up;
float elapsed_time; // Time since prior key or mouse button, in seconds.
// It seems better to store the foreground window's title rather than its HWND since keystrokes
// might result in a window closing (being destroyed), in which case the displayed key history
// would not be able to display the title at the time the history is displayed, which would
// be undesirable.
// To save mem, could point this into a shared buffer instead, but if that buffer were to run
// out of space (perhaps due to the target window changing frequently), window logging would
// no longer be possible without adding complexity to the logging function. Seems best
// to keep it simple:
#define KEY_HISTORY_WINDOW_TITLE_SIZE 100
char target_window[KEY_HISTORY_WINDOW_TITLE_SIZE];
};
//-------------------------------------------
LRESULT CALLBACK LowLevelKeybdProc(int aCode, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK LowLevelMouseProc(int aCode, WPARAM wParam, LPARAM lParam);
LRESULT LowLevelCommon(const HHOOK aHook, int aCode, WPARAM wParam, LPARAM lParam, const vk_type aVK
, sc_type aSC, bool aKeyUp, ULONG_PTR aExtraInfo, DWORD aEventFlags);
#define HOTSTRING_INDEX_INVALID INT_MAX // Use a signed maximum rather than unsigned, in case indexes ever become signed.
#define SuppressThisKey SuppressThisKeyFunc(aHook, lParam, aVK, aSC, aKeyUp, pKeyHistoryCurr, hotkey_id_to_post)
LRESULT SuppressThisKeyFunc(const HHOOK aHook, LPARAM lParam, const vk_type aVK, const sc_type aSC
, bool aKeyUp, KeyHistoryItem *pKeyHistoryCurr, HotkeyIDType aHotkeyIDToPost
, WPARAM aHSwParamToPost = HOTSTRING_INDEX_INVALID, LPARAM aHSlParamToPost = 0);
#define AllowKeyToGoToSystem AllowIt(aHook, aCode, wParam, lParam, aVK, aSC, aKeyUp, pKeyHistoryCurr, hotkey_id_to_post, false)
#define AllowKeyToGoToSystemButDisguiseWinAlt AllowIt(aHook, aCode, wParam, lParam, aVK, aSC, aKeyUp, pKeyHistoryCurr, hotkey_id_to_post, true)
LRESULT AllowIt(const HHOOK aHook, int aCode, WPARAM wParam, LPARAM lParam, const vk_type aVK, const sc_type aSC
, bool aKeyUp, KeyHistoryItem *pKeyHistoryCurr, HotkeyIDType aHotkeyIDToPost, bool aDisguiseWinAlt);
bool CollectInput(KBDLLHOOKSTRUCT &aEvent, const vk_type aVK, const sc_type aSC, bool aKeyUp, bool aIsIgnored
, WPARAM &aHotstringWparamToPost, LPARAM &aHotstringLparamToPost);
void UpdateKeybdState(KBDLLHOOKSTRUCT &aEvent, const vk_type aVK, const sc_type aSC, bool aKeyUp, bool aIsSuppressed);
bool KeybdEventIsPhysical(DWORD aEventFlags, const vk_type aVK, bool aKeyUp);
bool DualStateNumpadKeyIsDown();
bool IsDualStateNumpadKey(const vk_type aVK, const sc_type aSC);
void ChangeHookState(Hotkey *aHK[], int aHK_count, HookType aWhichHook, HookType aWhichHookAlways);
void AddRemoveHooks(HookType aHooksToBeActive, bool aChangeIsTemporary = false);
bool SystemHasAnotherKeybdHook();
bool SystemHasAnotherMouseHook();
DWORD WINAPI HookThreadProc(LPVOID aUnused);
void ResetHook(bool aAllModifiersUp = false, HookType aWhichHook = (HOOK_KEYBD | HOOK_MOUSE)
, bool aResetKVKandKSC = false);
HookType GetActiveHooks();
void FreeHookMem();
void ResetKeyTypeState(key_type &key);
void GetHookStatus(char *aBuf, int aBufSize);
#endif