diff --git a/source/hook.cpp b/source/hook.cpp index 5291e581c..a09d4ed88 100644 --- a/source/hook.cpp +++ b/source/hook.cpp @@ -762,6 +762,8 @@ LRESULT LowLevelCommon(const HHOOK aHook, int aCode, WPARAM wParam, LPARAM lPara GetModifierLRState(true); } + bool fire_with_no_suppress = false; // Set default. + /////////////////////////////////////////////////////////////////////////////////////// // CASE #1 of 4: PREFIX key has been pressed down. But use it in this capacity only if // no other prefix is already in effect or if this key isn't a suffix. Update: Or if @@ -780,9 +782,10 @@ LRESULT LowLevelCommon(const HHOOK aHook, int aCode, WPARAM wParam, LPARAM lPara // introduce a new hotkey modifier such as an "up2" keyword that makes any key into a prefix // key even if it never acts as a prefix for other keys, which in turn has the benefit of firing // on key-up, but only if the no other key was pressed while the user was holding it down. + bool suppress_this_prefix = false;// !(this_key.no_suppress & NO_SUPPRESS_PREFIX); // Set default. bool has_no_enabled_suffixes; if ( !(has_no_enabled_suffixes = (this_key.used_as_prefix == PREFIX_ACTUAL) - && Hotkey::PrefixHasNoEnabledSuffixes(sc_takes_precedence ? aSC : aVK, sc_takes_precedence)) ) + && Hotkey::PrefixHasNoEnabledSuffixes(sc_takes_precedence ? aSC : aVK, sc_takes_precedence, suppress_this_prefix)) ) { // This check is necessary in cases such as the following, in which the "A" key continues // to repeat because pressing a mouse button (unlike pressing a keyboard key) does not @@ -838,24 +841,55 @@ LRESULT LowLevelCommon(const HHOOK aHook, int aCode, WPARAM wParam, LPARAM lPara if (this_key.as_modifiersLR) // This will always be false if our caller is the mouse hook. // Hotkeys are not defined to modify themselves, so look for a match accordingly. modifiersLRnew &= ~this_key.as_modifiersLR; - // For this case to be checked, there must be at least one modifier key currently down (other - // than this key itself if it's a modifier), because if there isn't and this prefix is also - // a suffix, its suffix action should only fire on key-up (i.e. not here, but later on). - // UPDATE: In v1.0.41, an exception to the above is when a prefix is disabled via - // has_no_enabled_suffixes, in which case it seems desirable for most uses to have its - // suffix action fire on key-down rather than key-up. - // UPDATE: Another exception was added so that the no-suppress prefix allows the key to function - // as if the custom combination wasn't defined. For example, ~x & y:: allows x:: to retain its - // normal behaviour, firing the subroutine on key-down and blocking the keystroke. This is more - // useful and intuitive/consistent than the old behaviour, which was to fire the suffix hotkey - // on key-up even though the key-down wasn't suppressed (unless either of the first two conditions - // below were met). - if (modifiersLRnew || has_no_enabled_suffixes || (this_key.no_suppress & NO_SUPPRESS_PREFIX)) + + // This prefix key's hotkey needs to be checked even if it will ultimately fire only on release. + // If suppress_this_prefix == false, this prefix key's key-down hotkey should fire immediately. + // If suppress_this_prefix == true, its final value can only be confirmed by verifying whether + // this prefix key's hotkey has the no-suppress prefix (which should cause the hotkey to fire + // immediately and not be suppressed). + // This prefix key's hotkey should also be fired immediately if there are any modifiers down. + // Check hook type too in case a script ever explicitly specifies scan code zero as a hotkey: + hotkey_id_with_flags = (aHook == g_KeybdHook && sc_takes_precedence) + ? Kscm(modifiersLRnew, aSC) : Kvkm(modifiersLRnew, aVK); + hotkey_id_temp = hotkey_id_with_flags & HOTKEY_ID_MASK; + if (IS_ALT_TAB(hotkey_id_temp)) + hotkey_id_with_flags = HOTKEY_ID_INVALID; // Let it be rediscovered when the key is released. + else { - // Check hook type too in case a script every explicitly specifies scan code zero as a hotkey: - hotkey_id_with_flags = (aHook == g_KeybdHook && sc_takes_precedence) - ? Kscm(modifiersLRnew, aSC) : Kvkm(modifiersLRnew, aVK); - if (hotkey_id_with_flags & HOTKEY_KEY_UP) // And it's okay even if it's is HOTKEY_ID_INVALID. + if (suppress_this_prefix && !modifiersLRnew) // So far, it looks like the prefix should be suppressed. + { + UCHAR unused_no_suppress; // Leave this_key.no_suppress unchanged in case !firing_is_certain. + firing_is_certain = Hotkey::CriterionFiringIsCertain(hotkey_id_with_flags, aKeyUp, aExtraInfo, unused_no_suppress, fire_with_no_suppress, NULL); + if (firing_is_certain && fire_with_no_suppress) + suppress_this_prefix = false; // Just to avoid making it fire on release below. + else // Hotkey is ineligible to fire or lacks the no-suppress prefix. + { + // Resetting the ID is necessary to avoid the following cases: + // 1) A key-down hotkey which isn't eligible to fire prevents the prefix key from being suppressed. + // 2) A key-down hotkey which isn't eligible to fire causes its key-up counterpart to fire even if + // the prefix key was used to activate a custom combo. + // 3) A key-down hotkey without ~ fires immediately instead of on release. + // 4) A key-up hotkey without ~ fires even if the prefix key was used to activate a custom combo. + if (hotkey_id_with_flags < Hotkey::sHotkeyCount && hotkey_up[hotkey_id_with_flags] != HOTKEY_ID_INVALID) + { + // This key-down hotkey has a key-up counterpart. + fire_with_no_suppress = false; // Reset for the call below. + auto firing_up = Hotkey::CriterionFiringIsCertain(hotkey_up[hotkey_id_with_flags], aKeyUp, aExtraInfo, unused_no_suppress, fire_with_no_suppress, NULL); + if ( !(firing_up && fire_with_no_suppress) ) // Both key-down and key-up are either ineligible or lack the no-suppress prefix. + hotkey_id_with_flags = HOTKEY_ID_INVALID; // See comments above about resetting the ID. + else if (firing_is_certain) // Both key-down and key-up are eligible, but key-down should be suppressed. + fire_with_no_suppress = false; // For backward-compatibility, suppress the key-down but leave hotkey_id_with_flags set so it fires immediately. + else // Key-down is not eligible, but key-up is. + { + firing_is_certain = firing_up; + hotkey_id_with_flags = hotkey_up[hotkey_id_with_flags]; + } + } + else + hotkey_id_with_flags = HOTKEY_ID_INVALID; // See comments above about resetting the ID. + } + } + if (hotkey_id_with_flags & HOTKEY_ID_INVALID) // A key-up hotkey or HOTKEY_ID_INVALID. { // Queue it for later, which is done here rather than upon release of the key so that // the user can release the key's modifiers before releasing the key itself, which @@ -865,9 +899,8 @@ LRESULT LowLevelCommon(const HHOOK aHook, int aCode, WPARAM wParam, LPARAM lPara this_key.hotkey_to_fire_upon_release = hotkey_id_with_flags; hotkey_id_with_flags = HOTKEY_ID_INVALID; } - else // hotkey_id_with_flags is either HOTKEY_ID_INVALID or a valid key-down hotkey. + else // hotkey_id_with_flags is a valid key-down hotkey. { - hotkey_id_temp = hotkey_id_with_flags & HOTKEY_ID_MASK; if (hotkey_id_temp < Hotkey::sHotkeyCount) this_key.hotkey_to_fire_upon_release = hotkey_up[hotkey_id_temp]; // Might assign HOTKEY_ID_INVALID. // Since this prefix key is being used in its capacity as a suffix instead, @@ -886,25 +919,18 @@ LRESULT LowLevelCommon(const HHOOK aHook, int aCode, WPARAM wParam, LPARAM lPara if (hotkey_id_with_flags == HOTKEY_ID_INVALID) { if (has_no_enabled_suffixes) - { - this_key.no_suppress |= NO_SUPPRESS_NEXT_UP_EVENT; // Since the "down" is non-suppressed, so should the "up". pKeyHistoryCurr->event_type = _T('#'); // '#' to indicate this prefix key is disabled due to #IfWin criterion. - } // In this case, a key-down event can't trigger a suffix, so return immediately. // If our caller is the mouse hook, both of the following will always be false: // this_key.as_modifiersLR // this_toggle_key_can_be_toggled - if (this_key.as_modifiersLR || (this_key.no_suppress & NO_SUPPRESS_PREFIX) - || this_toggle_key_can_be_toggled || has_no_enabled_suffixes) + if (this_key.as_modifiersLR || !suppress_this_prefix || this_toggle_key_can_be_toggled) + { + this_key.no_suppress |= NO_SUPPRESS_NEXT_UP_EVENT; return AllowKeyToGoToSystem; + } // Mark this key as having been suppressed. This currently doesn't have any known effect // since the change to tilde (~) handling in v1.0.95 (commit 161162b8), but may in future. - // Search for "SEND_NOSUPPRESS_PREFIX_KEY_ON_RELEASE" for related comments. - //#define SEND_NOSUPPRESS_PREFIX_KEY_ON_RELEASE - // Without this next assignment, the following issues occur if the above line is uncommented: - // 1) ~prefixkey:: allows just a key-up to pass through, without first sending a key-down as - // originally intended. - // 2) #if false .. ~prefixkey:: causes the key-up to pass through when it should be suppressed. this_key.hotkey_down_was_suppressed = true; return SuppressThisKey; } @@ -1069,7 +1095,7 @@ LRESULT LowLevelCommon(const HHOOK aHook, int aCode, WPARAM wParam, LPARAM lPara if (this_key.as_modifiersLR) // Always false if our caller is the mouse hook. return AllowKeyToGoToSystem; // Win/Alt will be disguised if needed. // Otherwise: - return (this_key.no_suppress & NO_SUPPRESS_PREFIX) ? AllowKeyToGoToSystem : SuppressThisKey; + return SuppressThisKey; } // v1.0.41: This spot cannot be reached when a disabled prefix key's up-action fires on @@ -1088,8 +1114,7 @@ LRESULT LowLevelCommon(const HHOOK aHook, int aCode, WPARAM wParam, LPARAM lPara // If our caller is the mouse hook, both of the following will always be false: // this_key.as_modifiersLR // this_toggle_key_can_be_toggled - return (this_key.as_modifiersLR || (this_key.no_suppress & NO_SUPPRESS_PREFIX) - // The order on this line important; it relies on short-circuit boolean: + return (this_key.as_modifiersLR || this_toggle_key_can_be_toggled) ? AllowKeyToGoToSystem : SuppressThisKey; // Since the above didn't return, this key is both a prefix and a suffix, but @@ -1120,7 +1145,6 @@ LRESULT LowLevelCommon(const HHOOK aHook, int aCode, WPARAM wParam, LPARAM lPara // it fell through from CASE #3 or #2 above). This case can also happen if it fell through from // case #1 (i.e. it already determined the value of hotkey_id_with_flags). //////////////////////////////////////////////////////////////////////////////////////////////////// - bool fire_with_no_suppress = false; // Set default. if (pPrefixKey && (!aKeyUp || this_key.used_as_key_up) && hotkey_id_with_flags == HOTKEY_ID_INVALID) // Helps performance by avoiding all the below checking. { @@ -1350,8 +1374,6 @@ LRESULT LowLevelCommon(const HHOOK aHook, int aCode, WPARAM wParam, LPARAM lPara // Fix for v1.0.28: If the ID isn't an alt-tab type, don't consider it to be valid. // Someone pointed out that pressing Alt-Tab and then pressing ESC while still holding // down ALT fired the ~Esc hotkey even when it should just dismiss the alt-tab menu. - // Note: Both of the below checks must be done because the high-order bits of the - // hotkey_id_with_flags might be set to indicate no-suppress, etc: hotkey_id_temp = hotkey_id_with_flags & HOTKEY_ID_MASK; if (!IS_ALT_TAB(hotkey_id_temp)) hotkey_id_with_flags = HOTKEY_ID_INVALID; // Since it's not an Alt-tab action, don't fire this hotkey. @@ -1440,9 +1462,7 @@ LRESULT LowLevelCommon(const HHOOK aHook, int aCode, WPARAM wParam, LPARAM lPara // toggled, just allow this up-event to go through because the // previous down-event for it (in its role as a prefix) would not // have been suppressed: - // NO_SUPPRESS_PREFIX can occur if it fell through from Case #3 but the right - // modifier keys aren't down to have triggered a key-up hotkey: - return (this_key.as_modifiersLR || (this_key.no_suppress & NO_SUPPRESS_PREFIX) + return (this_key.as_modifiersLR // The following line was added for v1.0.37.02 to take into account key-up hotkeys, // the release of which should never be suppressed if it didn't actually fire the // up-hotkey (due to the wrong modifiers being down): @@ -1871,56 +1891,11 @@ LRESULT LowLevelCommon(const HHOOK aHook, int aCode, WPARAM wParam, LPARAM lPara if (aHook == g_MouseHook) return AllowKeyToGoToSystem; // Otherwise, our caller is the keyboard hook. - - // The following section is currently disabled because it hasn't been working as intended - // for quite some time, and doesn't seem to be what users expect. It also contains some - // contradictions; for instance, explicit key-up hotkeys such as the one in the example - // were excluded, apparently by design (since v1.0.36.02). Explicit key-up hotkeys which - // are turned on after the key is pressed were erroneously included, but this has been - // fixed in the code below. Implicit key-up hotkeys (which act on key-up because the key - // is used as a prefix key) did not work because this_key.hotkey_down_was_suppressed was - // not set when the prefix key was suppressed -- and later because it was not suppressed - // at all due to a change in v1.0.95 (commit 161162b8). - #ifdef SEND_NOSUPPRESS_PREFIX_KEY_ON_RELEASE - // Since this hotkey is firing on key-up but the user specified not to suppress its native - // function, send a down event to make up for the fact that the original down event was - // suppressed (since key-up hotkeys' down events are always suppressed because they - // are also prefix keys by definition). UPDATE: Now that it is possible for a prefix key - // to be non-suppressed, this is done only if the prior down event wasn't suppressed. - // Note that for a pair of hotkeys such as: - // *capslock::Send {Ctrl Down} - // *~capslock up:: Send {Ctrl Up} ; Specify tilde to allow caps lock to be toggled upon release. - // ... the following key history is produced (see note): - //14 03A h d 3.46 Caps Lock - //A2 01D i d 0.00 Ctrl - //14 03A h u 0.10 Caps Lock - //14 03A i d 0.00 Caps Lock <<< This actually came before the prior due to re-entrancy. - //A2 01D i u 0.00 Ctrl - // Can't use this_toggle_key_can_be_toggled in this case. Relies on short-circuit boolean order: - bool suppress_to_prevent_toggle = this_key.pForceToggle && *this_key.pForceToggle != NEUTRAL; - // The following isn't checked as part of the above because this_key.was_just_used would - // never be true with hotkeys such as the Capslock pair shown above. That's because - // Capslock isn't a prefix in that case, it's just a suffix. Even if it were a prefix, it would - // never reach this point in the execution because places higher above return early if the value of - // this_key.was_just_used is AS_PREFIX/AS_PREFIX_FOR_HOTKEY. - // Used as either a prefix for a hotkey or just a plain modifier for another key. - // ... && (*this_key.pForceToggle != NEUTRAL || this_key.was_just_used); - if (this_key.hotkey_down_was_suppressed // Down was suppressed. - && !(hotkey_id_with_flags & HOTKEY_KEY_UP) // v1.0.36.02: Prevents a hotkey such as "~5 up::" from generating double characters, regardless of whether it's paired with a "~5::" hotkey. - && !suppress_to_prevent_toggle) // Mouse vs. keybd hook was already checked higher above. - KeyEvent(KEYDOWN, aVK, aSC); // Substitute this to make up for the suppression (a check higher above has already determined that no_supress==true). - // Now allow the up-event to go through. The DOWN should always wind up taking effect - // before the UP because the above should already have "finished" by now, since - // it resulted in a recursive call to this function (using our hook-thread - // rather than our main thread or some other thread): - return suppress_to_prevent_toggle ? SuppressThisKey : AllowKeyToGoToSystem; - #else // Although it seems more sensible to suppress the key-up if the key-down was suppressed, // it probably does no harm to let the key-up pass through, and in this case, it's exactly // what the script is asking to happen (by prefixing the key-up hotkey with '~'). // this_key.pForceToggle isn't checked because AllowIt() handles that. return AllowKeyToGoToSystem; - #endif } // No suppression. } else // Key Down @@ -3770,16 +3745,6 @@ void ChangeHookState(Hotkey *aHK[], int aHK_count, HookType aWhichHook, HookType pThisKey->first_hotkey = hk.mID; continue; } - #ifndef SEND_NOSUPPRESS_PREFIX_KEY_ON_RELEASE // Search for this symbol for details. - else - { - // If this hotkey is a lone key with ~ prefix such as "~a::", the following ensures that - // the ~ prefix is respected even if the key is also used as a prefix in a custom combo, - // such as "a & b::". This is consistent with the behaviour of "~a & b::". - if (!hk.mModifiersConsolidatedLR && (hk.mNoSuppress & AT_LEAST_ONE_VARIANT_HAS_TILDE)) - pThisKey->no_suppress |= NO_SUPPRESS_PREFIX; - } - #endif // At this point, since the above didn't "continue", this hotkey is one without a ModifierVK/SC. // Put it into a temporary array, which will be later sorted: diff --git a/source/hook.h b/source/hook.h index d616997cc..59f53eb40 100644 --- a/source/hook.h +++ b/source/hook.h @@ -130,7 +130,7 @@ struct key_type 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. + UCHAR no_suppress; // Contains bitwise flags such as NO_SUPPRESS_NEXT_UP_EVENT. 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). diff --git a/source/hotkey.cpp b/source/hotkey.cpp index a3102c1d4..45cb0c5c2 100644 --- a/source/hotkey.cpp +++ b/source/hotkey.cpp @@ -522,12 +522,13 @@ void Hotkey::AllDestructAndExit(int aExitCode) -bool Hotkey::PrefixHasNoEnabledSuffixes(int aVKorSC, bool aIsSC) +bool Hotkey::PrefixHasNoEnabledSuffixes(int aVKorSC, bool aIsSC, bool &aSuppress) // aVKorSC contains the virtual key or scan code of the specified prefix key (it's a scan code if aIsSC is true). -// Returns true if this prefix key has no suffixes that can possibly. Each such suffix is prevented from +// Returns true if this prefix key has no suffixes that can possibly fire. Each such suffix is prevented from // firing by one or more of the following: // 1) Hotkey is completely disabled via IsCompletelyDisabled(). // 2) Hotkey has criterion and those criterion do not allow the hotkey to fire. +// Caller is expected to set aSuppress to a default of false. { // v1.0.44: Added aAsModifier so that a pair of hotkeys such as: // LControl::tooltip LControl @@ -536,6 +537,8 @@ bool Hotkey::PrefixHasNoEnabledSuffixes(int aVKorSC, bool aIsSC) // down because it is considered a prefix key for the <^c hotkey . modLR_type aAsModifier = KeyToModifiersLR(aIsSC ? 0 : aVKorSC, aIsSC ? aVKorSC : 0, NULL); + bool has_enabled_suffix = false; + for (int i = 0; i < sHotkeyCount; ++i) { Hotkey &hk = *shk[i]; @@ -549,9 +552,14 @@ bool Hotkey::PrefixHasNoEnabledSuffixes(int aVKorSC, bool aIsSC) // alt-tab hotkeys have no subroutine capable of making them exempt. So g_IsSuspended is checked // for alt-tab hotkeys here; and for other types of hotkeys, it's checked further below. continue; - else // This alt-tab hotkey is currently active. + //else // This alt-tab hotkey is currently active. + if ((hk.mNoSuppress & NO_SUPPRESS_PREFIX) || aSuppress) return false; // Since any stored mHotCriterion are ignored for alt-tab hotkeys, no further checking is needed. + has_enabled_suffix = true; + continue; // Still need to check other hotkeys for NO_SUPPRESS_PREFIX. } + if (has_enabled_suffix && !(hk.mNoSuppress & NO_SUPPRESS_PREFIX)) + continue; // No need to evaluate this hotkey's variants. // Otherwise, find out if any of its variants is eligible to fire. If so, immediately return // false because even one eligible hotkey means this prefix is enabled. for (HotkeyVariant *vp = hk.mFirstVariant; vp; vp = vp->mNextVariant) @@ -560,10 +568,19 @@ bool Hotkey::PrefixHasNoEnabledSuffixes(int aVKorSC, bool aIsSC) if ( vp->mEnabled // This particular variant within its parent hotkey is enabled. && (!g_IsSuspended || vp->mJumpToLabel->IsExemptFromSuspend()) // This variant isn't suspended... && (!vp->mHotCriterion || HotCriterionAllowsFiring(vp->mHotCriterion, hk.mName)) ) // ... and its criteria allow it to fire. - return false; // At least one of this prefix's suffixes is eligible for firing. + { + if ((vp->mNoSuppress & NO_SUPPRESS_PREFIX) || aSuppress) + return false; // At least one of this prefix's suffixes is eligible for firing. + has_enabled_suffix = true; + if (!(hk.mNoSuppress & NO_SUPPRESS_PREFIX)) + break; // None of this hotkey's variants have NO_SUPPRESS_PREFIX. + // Keep checking to ensure no other enabled variants have NO_SUPPRESS_PREFIX. + } } - // Since above didn't return, no hotkeys were found for this prefix that are capable of firing. - return true; + // Since above didn't return, either no hotkeys were found for this prefix that are capable of firing, + // or no variants were found with the NO_SUPPRESS_PREFIX flag. + aSuppress = has_enabled_suffix; + return !has_enabled_suffix; } @@ -1019,8 +1036,9 @@ ResultType Hotkey::Dynamic(LPTSTR aHotkeyName, LPTSTR aLabelName, LPTSTR aOption // both can be zero/NULL only when the caller is updating an existing hotkey to have new options // (i.e. it's retaining its current label). - bool suffix_has_tilde, hook_is_mandatory; - Hotkey *hk = FindHotkeyByTrueNature(aHotkeyName, suffix_has_tilde, hook_is_mandatory); // NULL if not found. + UCHAR no_suppress; + bool hook_is_mandatory; + Hotkey *hk = FindHotkeyByTrueNature(aHotkeyName, no_suppress, hook_is_mandatory); // NULL if not found. HotkeyVariant *variant = hk ? hk->FindVariant() : NULL; bool update_all_hotkeys = false; // This method avoids multiple calls to ManifestAllHotkeysHotstringsHooks() (which is high-overhead). bool variant_was_just_created = false; @@ -1055,12 +1073,12 @@ ResultType Hotkey::Dynamic(LPTSTR aHotkeyName, LPTSTR aLabelName, LPTSTR aOption if (!hk) // No existing hotkey of this name, so create a new hotkey. { if (hook_action) // COMMAND (create hotkey): Hotkey, Name, AltTabAction - hk = AddHotkey(NULL, hook_action, aHotkeyName, suffix_has_tilde, use_errorlevel); + hk = AddHotkey(NULL, hook_action, aHotkeyName, no_suppress, use_errorlevel); else // COMMAND (create hotkey): Hotkey, Name, LabelName [, Options] { if (!aJumpToLabel) // Caller is trying to set new aOptions for a nonexistent hotkey. RETURN_HOTKEY_ERROR(HOTKEY_EL_NOTEXIST, ERR_NONEXISTENT_HOTKEY, aHotkeyName); - hk = AddHotkey(aJumpToLabel, 0, aHotkeyName, suffix_has_tilde, use_errorlevel); + hk = AddHotkey(aJumpToLabel, 0, aHotkeyName, no_suppress, use_errorlevel); } if (!hk) return use_errorlevel ? OK : FAIL; // AddHotkey() already displayed the error (or set ErrorLevel). @@ -1128,7 +1146,7 @@ ResultType Hotkey::Dynamic(LPTSTR aHotkeyName, LPTSTR aLabelName, LPTSTR aOption } else // No existing variant matching current #IfWin criteria, so create a new variant. { - if ( !(variant = hk->AddVariant(aJumpToLabel, suffix_has_tilde)) ) // Out of memory. + if ( !(variant = hk->AddVariant(aJumpToLabel, no_suppress)) ) // Out of memory. RETURN_HOTKEY_ERROR(HOTKEY_EL_MEM, ERR_OUTOFMEM, aHotkeyName); variant_was_just_created = true; update_all_hotkeys = true; @@ -1147,13 +1165,15 @@ ResultType Hotkey::Dynamic(LPTSTR aHotkeyName, LPTSTR aLabelName, LPTSTR aOption // v1.1.15: Allow the ~tilde prefix to be added/removed from an existing hotkey variant. // v1.1.19: Apply this change even if aJumpToLabel is omitted. This is redundant if // variant_was_just_created, but checking that condition seems counter-productive. - if (variant->mNoSuppress = suffix_has_tilde) - hk->mNoSuppress |= AT_LEAST_ONE_VARIANT_HAS_TILDE; - else + variant->mNoSuppress = no_suppress; + // hk->mNoSuppress might be inaccurate if a no-suppress flag was just removed from this variant, + // but that just means a slight reduction in efficiency if tilde is removed from all variants. + hk->mNoSuppress |= no_suppress; // Apply both AT_LEAST_ONE_VARIANT_HAS_TILDE and NO_SUPPRESS_PREFIX, if present. + if (!(no_suppress & AT_LEAST_ONE_VARIANT_HAS_TILDE)) hk->mNoSuppress |= AT_LEAST_ONE_VARIANT_LACKS_TILDE; // v1.1.19: Allow the $UseHook prefix to be added to an existing hotkey. - if (!hk->mKeybdHookMandatory && (hook_is_mandatory || suffix_has_tilde)) + if (!hk->mKeybdHookMandatory && (hook_is_mandatory || no_suppress)) { // Require the hook for all variants of this hotkey if any variant requires it. // This seems more intuitive than the old behaviour, which required $ or #UseHook @@ -1267,7 +1287,7 @@ ResultType Hotkey::Dynamic(LPTSTR aHotkeyName, LPTSTR aLabelName, LPTSTR aOption -Hotkey *Hotkey::AddHotkey(IObject *aJumpToLabel, HookActionType aHookAction, LPTSTR aName, bool aSuffixHasTilde, bool aUseErrorLevel) +Hotkey *Hotkey::AddHotkey(IObject *aJumpToLabel, HookActionType aHookAction, LPTSTR aName, UCHAR aNoSuppress, bool aUseErrorLevel) // Caller provides aJumpToLabel rather than a Line* because at the time a hotkey or hotstring // is created, the label's destination line is not yet known. So the label is used a placeholder. // Caller must ensure that either aJumpToLabel or aName is not NULL. @@ -1278,7 +1298,7 @@ Hotkey *Hotkey::AddHotkey(IObject *aJumpToLabel, HookActionType aHookAction, LPT // The caller is responsible for calling ManifestAllHotkeysHotstringsHooks(), if appropriate. { if ( (shkMax <= sNextID && !HookAdjustMaxHotkeys(shk, shkMax, shkMax ? shkMax * 2 : INITIAL_MAX_HOTKEYS)) // Allocate or expand shk if needed. - || !(shk[sNextID] = new Hotkey(sNextID, aJumpToLabel, aHookAction, aName, aSuffixHasTilde, aUseErrorLevel)) ) + || !(shk[sNextID] = new Hotkey(sNextID, aJumpToLabel, aHookAction, aName, aNoSuppress, aUseErrorLevel)) ) { if (aUseErrorLevel) g_ErrorLevel->Assign(HOTKEY_EL_MEM); @@ -1297,7 +1317,7 @@ Hotkey *Hotkey::AddHotkey(IObject *aJumpToLabel, HookActionType aHookAction, LPT Hotkey::Hotkey(HotkeyIDType aID, IObject *aJumpToLabel, HookActionType aHookAction, LPTSTR aName - , bool aSuffixHasTilde, bool aUseErrorLevel) + , UCHAR aNoSuppress, bool aUseErrorLevel) // Constructor. // Caller provides aJumpToLabel rather than a Line* because at the time a hotkey or hotstring // is created, the label's destination line is not yet known. So the label is used a placeholder. @@ -1500,7 +1520,7 @@ Hotkey::Hotkey(HotkeyIDType aID, IObject *aJumpToLabel, HookActionType aHookActi if (HK_TYPE_CAN_BECOME_KEYBD_HOOK(mType)) if ( (mModifiersLR || aHookAction || mKeyUp || mModifierVK || mModifierSC) // mSC is handled higher above. - || (g_ForceKeybdHook || mAllowExtraModifiers // mNoSuppress&NO_SUPPRESS_PREFIX has already been handled elsewhere. Other bits in mNoSuppress must be checked later because they can change by any variants added after *this* one. + || (g_ForceKeybdHook || mAllowExtraModifiers // mNoSuppress must be checked later because it can be changed by any variants added after *this* one. || (mVK && !mVK_WasSpecifiedByNumber && vk_to_sc(mVK, true))) ) // Its mVK corresponds to two scan codes (such as "ENTER"). mKeybdHookMandatory = true; // v1.0.38.02: The check of mVK_WasSpecifiedByNumber above was added so that an explicit VK hotkey such @@ -1528,7 +1548,7 @@ Hotkey::Hotkey(HotkeyIDType aID, IObject *aJumpToLabel, HookActionType aHookActi // To avoid memory leak, this is done only when it is certain the hotkey will be created: if ( !(mName = aName ? SimpleHeap::Malloc(aName) : hotkey_name) - || !(AddVariant(aJumpToLabel, aSuffixHasTilde)) ) // Too rare to worry about freeing the other if only one fails. + || !(AddVariant(aJumpToLabel, aNoSuppress)) ) // Too rare to worry about freeing the other if only one fails. { if (aUseErrorLevel) g_ErrorLevel->Assign(HOTKEY_EL_MEM); @@ -1560,7 +1580,7 @@ HotkeyVariant *Hotkey::FindVariant() -HotkeyVariant *Hotkey::AddVariant(IObject *aJumpToLabel, bool aSuffixHasTilde) +HotkeyVariant *Hotkey::AddVariant(IObject *aJumpToLabel, UCHAR aNoSuppress) // Returns NULL upon out-of-memory; otherwise, the address of the new variant. // Even if aJumpToLabel is NULL, a non-NULL mJumpToLabel will be stored in each variant so that // NULL doesn't have to be constantly checked during script runtime. @@ -1590,10 +1610,10 @@ HotkeyVariant *Hotkey::AddVariant(IObject *aJumpToLabel, bool aSuffixHasTilde) // A non-zero InputLevel only works when using the hook mKeybdHookMandatory = true; } - if (aSuffixHasTilde) + v.mNoSuppress = aNoSuppress; + mNoSuppress |= aNoSuppress; // Apply both AT_LEAST_ONE_VARIANT_HAS_TILDE and NO_SUPPRESS_PREFIX, if present. + if (aNoSuppress & AT_LEAST_ONE_VARIANT_HAS_TILDE) { - v.mNoSuppress = true; // Override the false value set by ZeroMemory above. - mNoSuppress |= AT_LEAST_ONE_VARIANT_HAS_TILDE; // For simplicity, make the hook mandatory for any hotkey that has at least one non-suppressed variant. // Otherwise, ManifestAllHotkeysHotstringsHooks() would have to do a loop to check if any // non-suppressed variants are actually enabled & non-suspended to decide if the hook is actually needed @@ -1637,14 +1657,7 @@ ResultType Hotkey::TextInterpret(LPTSTR aName, Hotkey *aThisHotkey, bool aUseErr if (!term2) return TextToKey(TextToModifiers(term1, aThisHotkey), aName, false, aThisHotkey, aUseErrorLevel); if (*term1 == '~') - { - if (aThisHotkey) - { - aThisHotkey->mNoSuppress |= NO_SUPPRESS_PREFIX; - aThisHotkey->mKeybdHookMandatory = true; - } - term1 = omit_leading_whitespace(term1 + 1); - } + ++term1; // Some other stage handles this modifier, so just ignore it here. LPTSTR end_of_term1 = omit_trailing_whitespace(term1, term2) + 1; // Temporarily terminate the string so that the 2nd term is hidden: TCHAR ctemp = *end_of_term1; @@ -1825,11 +1838,12 @@ LPTSTR Hotkey::TextToModifiers(LPTSTR aText, Hotkey *aThisHotkey, HotkeyProperti if (temp = _tcsstr(aProperties->prefix_text, COMPOSITE_DELIMITER)) // Check again in case it tried to overflow. omit_trailing_whitespace(aProperties->prefix_text, temp)[1] = '\0'; // Truncate prefix_text so that the suffix text is omitted. composite = omit_leading_whitespace(composite + COMPOSITE_DELIMITER_LENGTH); - if (aProperties->suffix_has_tilde = (*composite == '~')) // Override any value of suffix_has_tilde set higher above. + aProperties->prefix_has_tilde = aProperties->suffix_has_tilde; + if (aProperties->suffix_has_tilde = (*composite == '~')) // Override any value of no_suppress set higher above. ++composite; // For simplicity, no skipping of leading whitespace between tilde and the suffix key name. tcslcpy(aProperties->suffix_text, composite, _countof(aProperties->suffix_text)); // Protect against overflow case script ultra-long (and thus invalid) key name. } - else // A normal (non-composite) hotkey, so suffix_has_tilde was already set properly (higher above). + else // A normal (non-composite) hotkey, so no_suppress was already set properly (higher above). tcslcpy(aProperties->suffix_text, omit_leading_whitespace(marker), _countof(aProperties->suffix_text)); // Protect against overflow case script ultra-long (and thus invalid) key name. if (temp = tcscasestr(aProperties->suffix_text, _T(" Up"))) // Should be reliable detection method because leading spaces have been omitted and it's unlikely a legitimate key name will ever contain a space followed by "Up". { @@ -2078,7 +2092,7 @@ void Hotkey::InstallMouseHook() -Hotkey *Hotkey::FindHotkeyByTrueNature(LPTSTR aName, bool &aSuffixHasTilde, bool &aHookIsMandatory) +Hotkey *Hotkey::FindHotkeyByTrueNature(LPTSTR aName, UCHAR &aNoSuppress, bool &aHookIsMandatory) // Returns the address of the hotkey if found, NULL otherwise. // In v1.0.42, it tries harder to find a match so that the order of modifier symbols doesn't affect the true nature of a hotkey. // For example, ^!c should be the same as !^c, primarily because RegisterHotkey() and the hook would consider them the same. @@ -2094,9 +2108,10 @@ Hotkey *Hotkey::FindHotkeyByTrueNature(LPTSTR aName, bool &aSuffixHasTilde, bool { HotkeyProperties prop_candidate, prop_existing; TextToModifiers(aName, NULL, &prop_candidate); - aSuffixHasTilde = prop_candidate.suffix_has_tilde; // Set for caller. + aNoSuppress = (prop_candidate.prefix_has_tilde ? NO_SUPPRESS_PREFIX : 0) // Set for caller. + | (prop_candidate.suffix_has_tilde ? AT_LEAST_ONE_VARIANT_HAS_TILDE : 0); aHookIsMandatory = prop_candidate.hook_is_mandatory; // Set for caller. - // Both suffix_has_tilde and a hypothetical prefix_has_tilde are ignored during dupe-checking below. + // Both suffix_has_tilde and prefix_has_tilde are ignored during dupe-checking below. // See comments inside the loop for details. for (int i = 0; i < sHotkeyCount; ++i) @@ -2118,9 +2133,9 @@ Hotkey *Hotkey::FindHotkeyByTrueNature(LPTSTR aName, bool &aSuffixHasTilde, bool // ID slot within the VK/SC hook arrays). The advantages of allowing tilde to be a per-variant attribute // seem substantial, namely to have some variant/siblings pass-through while others do not. && prop_existing.has_asterisk == prop_candidate.has_asterisk - // v1.0.43.05: Use stricmp not lstrcmpi so that the higher ANSI letters because an uppercase - // high ANSI letter isn't necessarily produced by holding down the shift key and pressing the - // lowercase letter. In addition, it preserves backward compatibility and may improve flexibility. + // v1.0.43.05: Use stricmp not lstrcmpi because an uppercase high ANSI letter isn't necessarily + // produced by holding down the shift key and pressing the lowercase letter. In addition, it + // preserves backward compatibility and may improve flexibility. && !_tcsicmp(prop_existing.prefix_text, prop_candidate.prefix_text) && !_tcsicmp(prop_existing.suffix_text, prop_candidate.suffix_text) ) return shk[i]; // Match found. diff --git a/source/hotkey.h b/source/hotkey.h index 549bd6c73..60a467a91 100644 --- a/source/hotkey.h +++ b/source/hotkey.h @@ -90,7 +90,7 @@ struct HotkeyVariant USHORT mIndex; UCHAR mExistingThreads, mMaxThreads; SendLevelType mInputLevel; - bool mNoSuppress; // v1.0.44: This became a per-variant attribute because it's more useful/flexible that way. + UCHAR 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. @@ -154,7 +154,7 @@ class Hotkey // 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, IObject *aJumpToLabel, HookActionType aHookAction, LPTSTR aName, bool aSuffixHasTilde, bool aUseErrorLevel); + Hotkey(HotkeyIDType aID, IObject *aJumpToLabel, HookActionType aHookAction, LPTSTR aName, UCHAR aNoSuppress, bool aUseErrorLevel); ~Hotkey() {if (mIsRegistered) Unregister();} public: @@ -215,12 +215,11 @@ class Hotkey #define HOTKEY_EL_MEM _T("99") static ResultType Dynamic(LPTSTR aHotkeyName, LPTSTR aLabelName, LPTSTR aOptions, IObject *aJumpToLabel, Var *aJumpToLabelVar); - static Hotkey *AddHotkey(IObject *aJumpToLabel, HookActionType aHookAction, LPTSTR aName, bool aSuffixHasTilde, bool aUseErrorLevel); + static Hotkey *AddHotkey(IObject *aJumpToLabel, HookActionType aHookAction, LPTSTR aName, UCHAR aNoSuppress, bool aUseErrorLevel); HotkeyVariant *FindVariant(); - HotkeyVariant *AddVariant(IObject *aJumpToLabel, bool aSuffixHasTilde); - static bool PrefixHasNoEnabledSuffixes(int aVKorSC, bool aIsSC); + HotkeyVariant *AddVariant(IObject *aJumpToLabel, UCHAR aNoSuppress); + static bool PrefixHasNoEnabledSuffixes(int aVKorSC, bool aIsSC, bool &aSuppress); HotkeyVariant *CriterionAllowsFiring(HWND *aFoundHWND = NULL, ULONG_PTR aExtraInfo = 0, LPTSTR aSingleChar = NULL); - static HotkeyVariant *CriterionAllowsFiring(HotkeyIDType aHotkeyID, HWND &aFoundHWND); static HotkeyVariant *CriterionFiringIsCertain(HotkeyIDType &aHotkeyIDwithFlags, bool aKeyUp, ULONG_PTR aExtraInfo , UCHAR &aNoSuppress, bool &aFireWithNoSuppress, LPTSTR aSingleChar); static modLR_type HotkeyRequiresModLR(HotkeyIDType aHotkeyIDwithoutflags, modLR_type aModLR); @@ -237,6 +236,7 @@ class Hotkey modLR_type modifiersLR; TCHAR prefix_text[32]; // Has to be large enough to hold the largest key name in g_key_to_vk, TCHAR suffix_text[32]; // which is probably "Browser_Favorites" (17). + bool prefix_has_tilde; bool suffix_has_tilde; // As opposed to "prefix has tilde". bool has_asterisk; bool is_key_up; @@ -317,7 +317,7 @@ class Hotkey return 0; } - static Hotkey *FindHotkeyByTrueNature(LPTSTR aName, bool &aSuffixHasTilde, bool &aHookIsMandatory); + static Hotkey *FindHotkeyByTrueNature(LPTSTR aName, UCHAR &aNoSuppress, bool &aHookIsMandatory); 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); diff --git a/source/script.cpp b/source/script.cpp index 2b0ea0989..b671a638e 100644 --- a/source/script.cpp +++ b/source/script.cpp @@ -1574,7 +1574,8 @@ ResultType Script::LoadIncludedFile(TextStream *fp) Hotkey *hk; LineNumberType pending_buf_line_number, saved_line_number; HookActionType hook_action; - bool is_label, suffix_has_tilde, hook_is_mandatory, in_comment_section, hotstring_options_all_valid, hotstring_execute; + bool is_label, hook_is_mandatory, in_comment_section, hotstring_options_all_valid, hotstring_execute; + UCHAR no_suppress; ResultType hotkey_validity; // For the remap mechanism, e.g. a::b @@ -2617,9 +2618,9 @@ ResultType Script::LoadIncludedFile(TextStream *fp) return FAIL; mLastLine->mAttribute = ATTR_LINE_CAN_BE_UNREACHABLE; } - if (hk = Hotkey::FindHotkeyByTrueNature(buf, suffix_has_tilde, hook_is_mandatory)) // Parent hotkey found. Add a child/variant hotkey for it. + if (hk = Hotkey::FindHotkeyByTrueNature(buf, no_suppress, hook_is_mandatory)) // Parent hotkey found. Add a child/variant hotkey for it. { - if (hook_action) // suffix_has_tilde has always been ignored for these types (alt-tab hotkeys). + if (hook_action) // no_suppress has always been ignored for these types (alt-tab hotkeys). { // Hotkey::Dynamic() contains logic and comments similar to this, so maintain them together. // An attempt to add an alt-tab variant to an existing hotkey. This might have @@ -2631,12 +2632,12 @@ ResultType Script::LoadIncludedFile(TextStream *fp) else { // Detect duplicate hotkey variants to help spot bugs in scripts. - if (hk->FindVariant()) // See if there's already a variant matching the current criteria (suffix_has_tilde does not make variants distinct form each other because it would require firing two hotkey IDs in response to pressing one hotkey, which currently isn't in the design). + if (hk->FindVariant()) // See if there's already a variant matching the current criteria (no_suppress does not make variants distinct form each other because it would require firing two hotkey IDs in response to pressing one hotkey, which currently isn't in the design). { mCurrLine = NULL; // Prevents showing unhelpful vicinity lines. return ScriptError(_T("Duplicate hotkey."), buf); } - if (!hk->AddVariant(mLastLabel, suffix_has_tilde)) + if (!hk->AddVariant(mLastLabel, no_suppress)) return ScriptError(ERR_OUTOFMEM, buf); if (hook_is_mandatory || g_ForceKeybdHook) { @@ -2648,7 +2649,7 @@ ResultType Script::LoadIncludedFile(TextStream *fp) } } else // No parent hotkey yet, so create it. - if ( !(hk = Hotkey::AddHotkey(mLastLabel, hook_action, mLastLabel->mName, suffix_has_tilde, false)) ) + if ( !(hk = Hotkey::AddHotkey(mLastLabel, hook_action, mLastLabel->mName, no_suppress, false)) ) { if (hotkey_validity != CONDITION_TRUE) return FAIL; // It already displayed the error.