-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathhotkey.h
366 lines (307 loc) · 17.2 KB
/
hotkey.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
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
/*
AutoHotkey
Copyright 2003-2008 Chris Mallett ([email protected])
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 hotkey_h
#define hotkey_h
#include "keyboard_mouse.h"
#include "script.h" // For which label (and in turn which line) in the script to jump to.
EXTERN_SCRIPT; // For g_script.
// Due to control/alt/shift modifiers, quite a lot of hotkey combinations are possible, so support any
// conceivable use. Note: Increasing this value will increase the memory required (i.e. any arrays
// that use this value):
#define MAX_HOTKEYS 700
// Note: 0xBFFF is the largest ID that can be used with RegisterHotkey().
// But further limit this to 0x3FFF (16,383) so that the two highest order bits
// are reserved for our other uses:
#define HOTKEY_FUTURE_USE 0x8000 // Formerly HOTKEY_NO_SUPPRESS but no longer needed for that.
#define HOTKEY_KEY_UP 0x4000
#define HOTKEY_ID_MASK 0x3FFF
#define HOTKEY_ID_INVALID HOTKEY_ID_MASK
#define HOTKEY_ID_ALT_TAB 0x3FFE
#define HOTKEY_ID_ALT_TAB_SHIFT 0x3FFD
#define HOTKEY_ID_ALT_TAB_MENU 0x3FFC
#define HOTKEY_ID_ALT_TAB_AND_MENU 0x3FFB
#define HOTKEY_ID_ALT_TAB_MENU_DISMISS 0x3FFA
#define HOTKEY_ID_MAX 0x3FF9 // 16377 hotkeys
#define HOTKEY_ID_ON 0x01 // This and the next 2 are used only for convenience by ConvertAltTab().
#define HOTKEY_ID_OFF 0x02
#define HOTKEY_ID_TOGGLE 0x03
#define IS_ALT_TAB(id) (id > HOTKEY_ID_MAX && id < HOTKEY_ID_INVALID)
#define COMPOSITE_DELIMITER " & "
#define COMPOSITE_DELIMITER_LENGTH 3
// Smallest workable size: to save mem in some large arrays that use this:
typedef USHORT HotkeyIDType; // This is relied upon to be unsigned; e.g. many places omit a check for ID < 0.
typedef HotkeyIDType HookActionType;
typedef UCHAR HotkeyTypeType;
enum HotkeyTypeEnum {HK_NORMAL, HK_KEYBD_HOOK, HK_MOUSE_HOOK, HK_BOTH_HOOKS, HK_JOYSTICK};
// If above numbers are ever changed/reshuffled, update the macros below.
#define HK_TYPE_CAN_BECOME_KEYBD_HOOK(type) (type == HK_NORMAL)
#define HK_TYPE_IS_HOOK(type) (type > HK_NORMAL && type < HK_JOYSTICK)
typedef UCHAR HotCriterionType;
enum HotCriterionEnum {HOT_NO_CRITERION, HOT_IF_ACTIVE, HOT_IF_NOT_ACTIVE, HOT_IF_EXIST, HOT_IF_NOT_EXIST}; // HOT_NO_CRITERION must be zero.
HWND HotCriterionAllowsFiring(HotCriterionType aHotCriterion, char *aWinTitle, char *aWinText); // Used by hotkeys and hotstrings.
ResultType SetGlobalHotTitleText(char *aWinTitle, char *aWinText);
struct HotkeyCriterion
{
char *mHotWinTitle, *mHotWinText;
HotkeyCriterion *mNextCriterion;
};
struct HotkeyVariant
{
Label *mJumpToLabel;
DWORD mRunAgainTime;
char *mHotWinTitle, *mHotWinText;
HotkeyVariant *mNextVariant;
int mPriority;
// Keep members that are less than 32-bit adjacent to each other to conserve memory in with the default
// 4-byte alignment:
HotCriterionType mHotCriterion;
UCHAR mExistingThreads, mMaxThreads;
bool mNoSuppress; // v1.0.44: This became a per-variant attribute because it's more useful/flexible that way.
bool mMaxThreadsBuffer;
bool mRunAgainAfterFinished;
bool mEnabled; // Whether this variant has been disabled via the Hotkey command.
};
class Hotkey
{
private:
// These are done as static, rather than having an outer-class to contain all the hotkeys, because
// the hotkey ID is used as the array index for performance reasons. Having an outer class implies
// the potential future use of more than one set of hotkeys, which could still be implemented
// within static data and methods to retain the indexing/performance method:
static HookType sWhichHookNeeded;
static HookType sWhichHookAlways;
static DWORD sTimePrev;
static DWORD sTimeNow;
static HotkeyIDType sNextID;
bool Enable(HotkeyVariant &aVariant) // Returns true if the variant needed to be disabled, in which case caller should generally call ManifestAllHotkeysHotstringsHooks().
{
if (aVariant.mEnabled) // Added for v1.0.23 to greatly improve performance when hotkey is already in the right state.
return false; // Indicate that it's already enabled.
aVariant.mEnabled = true;
return true;
}
bool Disable(HotkeyVariant &aVariant) // Returns true if the variant needed to be disabled, in which case caller should generally call ManifestAllHotkeysHotstringsHooks().
{
if (!aVariant.mEnabled) // Added for v1.0.23 to greatly improve performance when hotkey is already in the right state.
return false; // Indicate that it's already disabled.
aVariant.mEnabled = false;
aVariant.mRunAgainAfterFinished = false; // ManifestAllHotkeysHotstringsHooks() won't do this unless the entire hotkey is disabled/unregistered.
return true;
}
bool EnableParent() // Returns true if the hotkey needed to be disabled, in which case caller should generally call ManifestAllHotkeysHotstringsHooks().
{
if (mParentEnabled)
return false; // Indicate that it's already enabled.
mParentEnabled = true;
return true;
}
bool DisableParent() // Returns true if the hotkey needed to be disabled, in which case caller should generally call ManifestAllHotkeysHotstringsHooks().
{
if (!mParentEnabled)
return false; // Indicate that it's already disabled.
mParentEnabled = false;
return true;
}
ResultType Register();
ResultType Unregister();
void *operator new(size_t aBytes) {return SimpleHeap::Malloc(aBytes);}
void *operator new[](size_t aBytes) {return SimpleHeap::Malloc(aBytes);}
void operator delete(void *aPtr) {SimpleHeap::Delete(aPtr);} // Deletes aPtr if it was the most recently allocated.
void operator delete[](void *aPtr) {SimpleHeap::Delete(aPtr);}
// For now, constructor & destructor are private so that only static methods can create new
// objects. This allow proper tracking of which OS hotkey IDs have been used.
Hotkey(HotkeyIDType aID, Label *aJumpToLabel, HookActionType aHookAction, char *aName, bool aSuffixHasTilde, bool aUseErrorLevel);
~Hotkey() {if (mIsRegistered) Unregister();}
public:
static Hotkey *shk[MAX_HOTKEYS];
HotkeyIDType mID; // Must be unique for each hotkey of a given thread.
HookActionType mHookAction;
char *mName; // Points to the label name for static hotkeys, or a dynamically-allocated string for dynamic hotkeys.
sc_type mSC; // Scan code. All vk's have a scan code, but not vice versa.
sc_type mModifierSC; // If mModifierVK is zero, this scan code, if non-zero, will be used as the modifier.
mod_type mModifiers; // MOD_ALT, MOD_CONTROL, MOD_SHIFT, MOD_WIN, or some additive or bitwise-or combination of these.
modLR_type mModifiersLR; // Left-right centric versions of the above.
modLR_type mModifiersConsolidatedLR; // The combination of mModifierVK, mModifierSC, mModifiersLR, modifiers
// Keep single-byte attributes adjacent to each other to conserve memory within byte-aligned class/struct:
vk_type mVK; // virtual-key code, e.g. VK_TAB, VK_LWIN, VK_LMENU, VK_APPS, VK_F10. If zero, use sc below.
vk_type mModifierVK; // Any other virtual key that must be pressed down in order to activate "vk" itself.
HotkeyTypeType mType;
#define NO_SUPPRESS_PREFIX 0x01 // Bitwise: Bit #1
#define AT_LEAST_ONE_VARIANT_HAS_TILDE 0x02 // Bitwise: Bit #2
#define AT_LEAST_ONE_VARIANT_LACKS_TILDE 0x04 // Bitwise: Bit #3
#define NO_SUPPRESS_NEXT_UP_EVENT 0x08 // Bitwise: Bit #4
#define NO_SUPPRESS_SUFFIX_VARIES (AT_LEAST_ONE_VARIANT_HAS_TILDE | AT_LEAST_ONE_VARIANT_LACKS_TILDE) // i.e. a hotkey that has variants of both types.
#define NO_SUPPRESS_STATES NO_SUPPRESS_NEXT_UP_EVENT // This is a bitwise union (currently only one item) of those of the above that represent a the key's dynamically changing state as the user types.
UCHAR mNoSuppress; // Uses the flags above. Normally 0, but can be overridden by using the hotkey tilde (~) prefix).
bool mKeybdHookMandatory;
bool mAllowExtraModifiers; // False if the hotkey should not fire when extraneous modifiers are held down.
bool mKeyUp; // A hotkey that should fire on key-up.
bool mVK_WasSpecifiedByNumber; // A hotkey defined by something like "VK24::" or "Hotkey, VK24, ..."
bool mUnregisterDuringThread; // Win9x: Whether this hotkey should be unregistered during its own subroutine (to prevent its own Send commmand from firing itself). Seems okay to apply this to all variants.
bool mIsRegistered; // Whether this hotkey has been successfully registered.
bool mParentEnabled; // When true, the individual variants' mEnabled flags matter. When false, the entire hotkey is disabled.
bool mConstructedOK;
HotkeyVariant *mFirstVariant, *mLastVariant; // v1.0.42: Linked list of variant hotkeys created via #IfWin directives.
// Make sHotkeyCount an alias for sNextID. Make it const to enforce modifying the value in only one way:
static const HotkeyIDType &sHotkeyCount;
static bool sJoystickHasHotkeys[MAX_JOYSTICKS];
static DWORD sJoyHotkeyCount;
static void AllDestructAndExit(int exit_code);
#define HOTKEY_EL_BADLABEL "1" // Set as strings so that SetFormat doesn't affect their appearance (for use with "If ErrorLevel in 5,6").
#define HOTKEY_EL_INVALID_KEYNAME "2"
#define HOTKEY_EL_UNSUPPORTED_PREFIX "3"
#define HOTKEY_EL_ALTTAB "4"
#define HOTKEY_EL_NOTEXIST "5"
#define HOTKEY_EL_NOTEXISTVARIANT "6"
#define HOTKEY_EL_WIN9X "50"
#define HOTKEY_EL_NOREG "51"
#define HOTKEY_EL_MAXCOUNT "98" // 98 allows room for other ErrorLevels to be added in between.
#define HOTKEY_EL_MEM "99"
static ResultType Dynamic(char *aHotkeyName, char *aLabelName, char *aOptions, Label *aJumpToLabel);
static Hotkey *AddHotkey(Label *aJumpToLabel, HookActionType aHookAction, char *aName, bool aSuffixHasTilde, bool aUseErrorLevel);
HotkeyVariant *FindVariant();
HotkeyVariant *AddVariant(Label *aJumpToLabel, bool aSuffixHasTilde);
static bool PrefixHasNoEnabledSuffixes(int aVKorSC, bool aIsSC);
HotkeyVariant *CriterionAllowsFiring(HWND *aFoundHWND = NULL);
static HotkeyVariant *CriterionAllowsFiring(HotkeyIDType aHotkeyID, HWND &aFoundHWND);
static bool CriterionFiringIsCertain(HotkeyIDType &aHotkeyIDwithFlags, bool aKeyUp, UCHAR &aNoSuppress
, bool &aFireWithNoSuppress, char *aSingleChar);
static void TriggerJoyHotkeys(int aJoystickID, DWORD aButtonsNewlyDown);
void Perform(HotkeyVariant &aVariant);
static void ManifestAllHotkeysHotstringsHooks();
static void RequireHook(HookType aWhichHook) {sWhichHookAlways |= aWhichHook;}
static ResultType TextInterpret(char *aName, Hotkey *aThisHotkey, bool aUseErrorLevel);
struct HotkeyProperties // Struct used by TextToModifiers() and its callers.
{
mod_type modifiers;
modLR_type modifiersLR;
char prefix_text[32]; // Has to be large enough to hold the largest key name in g_key_to_vk,
char suffix_text[32]; // which is probably "Browser_Favorites" (17).
bool suffix_has_tilde; // As opposed to "prefix has tilde".
bool has_asterisk;
bool is_key_up;
};
static char *TextToModifiers(char *aText, Hotkey *aThisHotkey, HotkeyProperties *aProperties = NULL);
static ResultType TextToKey(char *aText, char *aHotkeyName, bool aIsModifier, Hotkey *aThisHotkey, bool aUseErrorLevel);
static void InstallKeybdHook();
static void InstallMouseHook();
bool PerformIsAllowed(HotkeyVariant &aVariant)
{
// For now, attempts to launch another simultaneous instance of this subroutine
// are ignored if MaxThreadsPerHotkey (for this particular hotkey) has been reached.
// In the future, it might be better to have this user-configurable, i.e. to devise
// some way for the hotkeys to be kept queued up so that they take effect only when
// the number of currently active threads drops below the max. But doing such
// might make "infinite key loops" harder to catch because the rate of incoming hotkeys
// would be slowed down to prevent the subroutines from running concurrently:
return aVariant.mExistingThreads < aVariant.mMaxThreads
|| (ACT_IS_ALWAYS_ALLOWED(aVariant.mJumpToLabel->mJumpToLine->mActionType));
}
bool IsExemptFromSuspend() // A hotkey is considered exempt if even one of its variants is exempt.
{
// It's the caller's responsibility to check vp->mEnabled; that isn't done here.
if (mHookAction) // An alt-tab hotkey (which overrides all its variants) is never exempt.
return false;
for (HotkeyVariant *vp = mFirstVariant; vp; vp = vp->mNextVariant)
if (vp->mJumpToLabel->IsExemptFromSuspend()) // If it has no label, it's never exempt.
return true; // Even a single exempt variant makes the entire hotkey exempt.
// Otherwise, since the above didn't find any exempt variants, the entire hotkey is non-exempt:
return false;
}
bool IsCompletelyDisabled()
{
if (mHookAction) // Alt tab hotkeys are disabled completely if and only if the parent is disabled.
return !mParentEnabled;
for (HotkeyVariant *vp = mFirstVariant; vp; vp = vp->mNextVariant)
if (vp->mEnabled)
return false;
return true;
}
static void RunAgainAfterFinished(HotkeyVariant &aVariant)
{
if (aVariant.mMaxThreadsBuffer)
aVariant.mRunAgainAfterFinished = true;
aVariant.mRunAgainTime = GetTickCount();
// Above: The time this event was buffered, to make sure it doesn't get too old.
}
static void ResetRunAgainAfterFinished() // For all hotkeys and all variants of each.
{
HotkeyVariant *vp;
for (int i = 0; i < sHotkeyCount; ++i)
for (vp = shk[i]->mFirstVariant; vp; vp = vp->mNextVariant)
vp->mRunAgainAfterFinished = false;
}
static HookActionType ConvertAltTab(char *aBuf, bool aAllowOnOff)
{
if (!aBuf || !*aBuf) return 0;
if (!stricmp(aBuf, "AltTab")) return HOTKEY_ID_ALT_TAB;
if (!stricmp(aBuf, "ShiftAltTab")) return HOTKEY_ID_ALT_TAB_SHIFT;
if (!stricmp(aBuf, "AltTabMenu")) return HOTKEY_ID_ALT_TAB_MENU;
if (!stricmp(aBuf, "AltTabAndMenu")) return HOTKEY_ID_ALT_TAB_AND_MENU;
if (!stricmp(aBuf, "AltTabMenuDismiss")) return HOTKEY_ID_ALT_TAB_MENU_DISMISS;
if (aAllowOnOff)
{
if (!stricmp(aBuf, "On")) return HOTKEY_ID_ON;
if (!stricmp(aBuf, "Off")) return HOTKEY_ID_OFF;
if (!stricmp(aBuf, "Toggle")) return HOTKEY_ID_TOGGLE;
}
return 0;
}
static Hotkey *FindHotkeyByTrueNature(char *aName, bool &aSuffixHasTilde);
static Hotkey *FindHotkeyContainingModLR(modLR_type aModifiersLR); //, HotkeyIDType hotkey_id_to_omit);
//static Hotkey *FindHotkeyWithThisModifier(vk_type aVK, sc_type aSC);
//static Hotkey *FindHotkeyBySC(sc2_type aSC2, mod_type aModifiers, modLR_type aModifiersLR);
static char *ListHotkeys(char *aBuf, int aBufSize);
char *ToText(char *aBuf, int aBufSize, bool aAppendNewline);
};
///////////////////////////////////////////////////////////////////////////////////
#define MAX_HOTSTRING_LENGTH 40 // Hard to imagine a need for more than this, and most are only a few chars long.
#define MAX_HOTSTRING_LENGTH_STR "40" // Keep in sync with the above.
#define HOTSTRING_BLOCK_SIZE 1024
typedef UINT HotstringIDType;
enum CaseConformModes {CASE_CONFORM_NONE, CASE_CONFORM_ALL_CAPS, CASE_CONFORM_FIRST_CAP};
class Hotstring
{
public:
static Hotstring **shs; // An array to be allocated on first use (performs better than linked list).
static HotstringIDType sHotstringCount;
static HotstringIDType sHotstringCountMax;
static bool mAtLeastOneEnabled; // v1.0.44.08: For performance, such as avoiding calling ToAsciiEx() in the hook.
Label *mJumpToLabel;
char *mString, *mReplacement, *mHotWinTitle, *mHotWinText;
int mPriority, mKeyDelay;
// Keep members that are smaller than 32-bit adjacent with each other to conserve memory (due to 4-byte alignment).
SendModes mSendMode;
HotCriterionType mHotCriterion;
UCHAR mStringLength;
bool mSuspended;
UCHAR mExistingThreads, mMaxThreads;
bool mCaseSensitive, mConformToCase, mDoBackspace, mOmitEndChar, mSendRaw, mEndCharRequired
, mDetectWhenInsideWord, mDoReset, mConstructedOK;
static void SuspendAll(bool aSuspend);
ResultType Perform();
void DoReplace(LPARAM alParam);
static ResultType AddHotstring(Label *aJumpToLabel, char *aOptions, char *aHotstring, char *aReplacement
, bool aHasContinuationSection);
static void ParseOptions(char *aOptions, int &aPriority, int &aKeyDelay, SendModes &aSendMode
, bool &aCaseSensitive, bool &aConformToCase, bool &aDoBackspace, bool &aOmitEndChar, bool &aSendRaw
, bool &aEndCharRequired, bool &aDetectWhenInsideWord, bool &aDoReset);
// Constructor & destructor:
Hotstring(Label *aJumpToLabel, char *aOptions, char *aHotstring, char *aReplacement, bool aHasContinuationSection);
~Hotstring() {} // Note that mReplacement is sometimes malloc'd, sometimes from SimpleHeap, and sometimes the empty string.
void *operator new(size_t aBytes) {return SimpleHeap::Malloc(aBytes);}
void *operator new[](size_t aBytes) {return SimpleHeap::Malloc(aBytes);}
void operator delete(void *aPtr) {SimpleHeap::Delete(aPtr);} // Deletes aPtr if it was the most recently allocated.
void operator delete[](void *aPtr) {SimpleHeap::Delete(aPtr);}
};
#endif