4

I have a keyboard with these arrow (less/greater than) characters as alternate keys on Y and X.

Keyboard

Let's focus on the key X for this example.

By default the alternate character > is triggered with AltGr + X of course.

But I would like to trigger it by simply long pressing X, to speed things up and having no need for a second hand or finger.

So far I have the following, which I got from other posts:

$x::
    KeyWait, x, T0.2

    if (ErrorLevel)
        Send > ;long

    else {
        KeyWait, x, D T0.2

        if (ErrorLevel)
            Send x ;single
    }

    KeyWait, x
return

This basically works, but has one major flaw: A normal single key press takes now too much time to write the normal X character.

For instance, if you write fast a word like "exchange", you end up with something like "echxange", because the X takes too much time to be sent.

So how can this script be modified to fix that problem? My idea would be to send a normal X and to abord this whole script, once a {X Up} is registered. So after {X Up} he will no longer wait.

Or any other idea?

Thanks.

DanielM
  • 189
  • 8

4 Answers4

3

Here is a solution that calculates the duration of the keypress, the downside is you always have to release the key in order to get the required input. This means you can't hold down x to type xxxxxxxx.

$x::
    startTime := A_TickCount ;record the time the key was pressed
    KeyWait, x, U ;wait for the key to be released
    keypressDuration := A_TickCount-startTime ;calculate the duration the key was pressed down
    if (keypressDuration > 200) ;if the key was pressed down for more than 200ms send >
    {
        Send > 
    }
    else ;if the key was pressed down for less than 200ms send x
    {
         Send x 
    }

return
Yane
  • 722
  • 6
  • 15
  • Nice solution. Occasionally, depending on how fast one types, there is still the possibility to get something like, "echxange". – EJE May 06 '19 at 11:47
  • Maybe using [`Sendinput`](https://autohotkey.com/docs/commands/Send.htm#SendInputDetail) can help. From the documentation "...it is also more reliable because there is less opportunity for some other window to pop up unexpectedly and intercept the keystrokes. Reliability is further improved by the fact that anything the user types during a SendInput is postponed until afterward..." – Yane May 06 '19 at 12:08
  • My script header has `SendMode , Input`, but I also tried using `SendInput` as you suggested and I can still get "echxange". Actually, one time it gave me this "ech>ange", which seems impossible. Note that I have to type uncomfortably fast to get this, so it may prove to be the best solution. – EJE May 06 '19 at 12:15
  • Marked it solved. Thanks. However, I'd like to remap the hash chracter #. But I am having problems since this is a modifier key or something. Made a research but couldn't find anything helpful. Short press: # Long press: ' Any idea? – DanielM May 07 '19 at 00:52
  • Check [https://autohotkey.com/docs/KeyList.htm#SpecialKeys](https://autohotkey.com/docs/KeyList.htm#SpecialKeys) – Yane May 07 '19 at 07:47
  • nice, but if you press something else before 200 ms it will come before `x`. Try e.g. pressing `xc` very fast, and you will get `cx`. – Mikhail V May 17 '19 at 23:46
  • @Yane couldn't find what I was looking for at the link you provided. Again: What I need is # at short press and ' (apostrophe) at long press. Could you post an example into the comments on how to use these characters? That would be helpful. Thanks! – DanielM May 19 '19 at 04:56
  • First you need to find the scan code of your key by looking at KeyHistory, then you need to replace `x` in your script with that code – Yane May 19 '19 at 08:28
  • @Yane By looking at KeyHistory I got the info that the scan code of # is 03F. At the last Send of your script I add it like this: {03F}. But if I put the code either way with brackets or without at the "KeyWait" line, I get the error message "...Parameter #1 invalid." What's the format for using the scan code in the script? – DanielM May 21 '19 at 04:14
2

I took the answer provided by Yane, and improved it slightly.

The difference: Yane's answer will send the key when you release the key. My example will send the long-hold key after a set amount of time. This way you know when you've hold the key long enough.

$x::
    timeHasElapsed := 0
    SetTimer, SendAngleBracketRight, -200 ;if the key was pressed down for more than 200ms send > (negative value to make the timer run only once)
    KeyWait, x, U ;wait for the key to be released
    if (timeHasElapsed == 0) ;if the timer didn't go off disable the timer and send x
    {
        SetTimer, SendAngleBracketRight, OFF
        SendInput, x
    }
return

SendAngleBracketRight:
    SendInput, >
    timeHasElapsed := 1
return
Animiles
  • 77
  • 7
1

The code below seems to work well for me, but note that there is the possibility (with some unfortunate timing) that it could erroneously replace an "x" if you're typing many x's in a row, resulting in something like this, "xxxx>xxx". I increased the sleep time to 350ms to make that scenario less likely, but it's something to be aware of and should be changed to suit whatever you need.

~x::
Sleep , 350
BlockInput , On
Send , % GetKeyState( "x" , P ) ? "{backspace}>" : ""
KeyWait , x , U
BlockInput , Off
Return

Note that since it sends a backspace, you may get unintended results if using it in a non-typing environment, such as in a browser where it may go back to the previous page if you can't type text. This can be mitigated if needed by filtering out those specific scenarios with WinActive or something similar.

EJE
  • 1,693
  • 2
  • 6
  • 17
  • This is really not bad. The problems you described can be avoided in my case. However, I encounter another problem from time to time. If you want to get the > character, but you press it *too* long, then some Xs are sent. Of course, with a little practice you can get used to the correct timing. But isn't there a possibility to completely avoid sending the X after a long press, even if it is pressed longer by accident than it is supposed to be pressed? Thanks. – DanielM May 04 '19 at 03:38
  • @DanielM I added `KeyWait`. It seems to have fixed that issue. – EJE May 04 '19 at 06:10
  • I am still getting xxx... when I press a bit too long. – DanielM May 04 '19 at 22:09
  • @DanielM Hmmm, I'm not able to get repeated xxx... when holding (I held for a full 5s and just got ">"). Are you certain you've tried the updated code above? I wouldn't expect that it would have any effect, but is your AHK version up-to-date? – EJE May 06 '19 at 11:50
  • Yes, I double checked that it's the correct code which is executed. I have no other script running. I don't really know how to properly check the Autohotkey version. But right click, properties on Autohotkey.exe shows: File version: 1.1.28.2 – DanielM May 06 '19 at 17:59
  • It appears that the current version is 1.1.30.3. (I was using 1.1.30.1) https://www.autohotkey.com/docs/AHKL_ChangeLog.htm I suppose it is possible that it might behave differently in the new version. – EJE May 06 '19 at 18:02
  • Just updated to the newest version and still getting xxx. In fact what I get when holding is >xxxxxx.... – DanielM May 06 '19 at 23:17
  • Unfortunately, since I can't replicate the problem, I can't address it, at least not with this solution. I suspect that it may be related to my keyboard or keyboard driver. Another option would involve the keyboard hook and playing back any keys that may have been pressed during the waiting period to determine if X was held. – EJE May 07 '19 at 00:18
  • It's alright. The answer from Yane solved it for me. Thanks anyway! Maybe you have a solution for my # problem? See latest comment on Yanes answer. – DanielM May 07 '19 at 00:53
-1

Quite simple, but it'll need 2 separate scripts.
First run this script to block the repetition (I am almost sure it is what you want).

SendMode Input 

$x::
    send {x}
    keywait, x
    send {x up}
return

Then run this second script, which does the logic :

SendMode Input 

dt := 30            ;  milliseconds time resolution
timeout := 1500 ;  timeout

loop 
{
    sleep %dt%                      ; sleep to reduce CPU usage
    p := getkeystate("x", "P")          ; getkeystate
    if (p = 1) {
        timer += dt
    }
    if (p = 0) {
        timer := 0
    }
    ; tooltip pressed:  %p%  timer: %timer%
    if (timer > timeout) {
        send {backspace}
        send {>}
        timer := 0              ; reset timer
    }
}

It will input x normally, and > after 1.5 second, see timeout variable. The trick is that in the latter case it removes last x so you get only >.

Mikhail V
  • 1,264
  • 13
  • 21