Attempt to catch arrow keystrokes

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Message
Author
penpen
Expert
Posts: 2009
Joined: 23 Jun 2013 06:15
Location: Germany

Re: Attempt to catch arrow keystrokes

#16 Post by penpen » 14 May 2014 05:12

Some time ago i had to catch some special keys in batch (F1 ..., num lock, ...), so I've written a C#.NET program, but it also is able to catch the arrow keys.
This is no batch solution:
It needs .NET (actually i don't know the minimum version), it reads keys instead of characters (for example try type in the '!' char),
and it is only tested under XP (getKeyState function: "SCROLL LOCK" state), but worked nicely for me:

Code: Select all

// // >nul 2> nul & @goto :main
/*
:main
   @echo off
   setlocal
   cls

   set "csc="

   pushd "%SystemRoot%\Microsoft.NET\Framework"
   for /f "tokens=* delims=" %%i in ('dir /b /o:n "v*"') do (
      dir /a-d /b "%%~fi\csc.exe" >nul 2>&1 && set "csc="%%~fi\csc.exe""
   )
   popd

   if defined csc (
      echo most recent C#.NET compiler located in:
      echo %csc%.
   ) else (
      echo C#.NET compiler not found.
      goto :eof
   )

   %csc% /nologo /optimize /warnaserror /nowin32manifest /debug- /target:exe /out:"%~dp0readKey.exe" "%~f0"
   goto :eof
*/


using System;
using System.Runtime.InteropServices;


class ReadKey {
   [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
   public static extern short GetKeyState(int keyCode);

   public static void Main () {
      ConsoleKeyInfo cki;
      Console.TreatControlCAsInput = true;

      cki = Console.ReadKey (true);

      if (Console.NumberLock) Console.Write ("NUM+");
      if (Console.CapsLock  ) Console.Write ("CAPS+");
      if ((((ushort)GetKeyState (0x91)) & 0xFFFF) != 0) Console.Write ("SCROLL+");

      if ((cki.Modifiers & ConsoleModifiers.Alt    ) != 0) Console.Write ("ALT+");
      if ((cki.Modifiers & ConsoleModifiers.Control) != 0) Console.Write ("CTRL+");
      if ((cki.Modifiers & ConsoleModifiers.Shift  ) != 0) Console.Write ("SHIFT+");

      Console.WriteLine (cki.Key.ToString ());
   }
}

penpen

aGerman
Expert
Posts: 4678
Joined: 22 Jan 2010 18:01
Location: Germany

Re: Attempt to catch arrow keystrokes

#17 Post by aGerman » 14 May 2014 17:16

Thanks for your suggestions guys. Always worth it to leave some 3rd party or embedded code here for other users who stumbled on this thread. I already knew that it needs only a few lines of code in other languages but I had a little hope we could achieve that using pure Batch as well. Nevermind, as the topic already says, it was an attempt...

Regards
aGerman

Aacini
Expert
Posts: 1914
Joined: 06 Dec 2011 22:15
Location: México City, México
Contact:

Re: Attempt to catch arrow keystrokes

#18 Post by Aacini » 14 May 2014 22:26

I discovered a very strange point about this matter. A very brief introduction first:

My getkey.exe auxiliary program may read normal and extended keys, and report the second ones via a negative value. For example:

Code: Select all

@echo off
set /P "=Press any key: " < NUL
getkey
echo Key read: %errorlevel%

Example of three runs with comments added at right side:

Code: Select all

C:\> test
Press any key: Key read: -72            <- UpArrow key pressed here

C:\> test
Press any key: Key read: -80            <- DownArrow key pressed here

C:\> test
Press any key: Key read: 65             <- Upper case "A" letter key pressed here

The extended keys are identified because the first byte is zero or 0xE0. This is the main part of getkey.asm program written in assembly language:

Code: Select all

read_char:
        call    crt__getch              ;EAX = character read
        test    eax, eax                ;is extended v1?
        jz      SHORT second_byte       ;yes: get the second byte
        cmp     eax, 0E0H               ;is extended v2?
        jne     SHORT quit              ;no: terminate
        ;
second_byte:
        call    crt__getch              ;get second byte of extended key
        neg     eax                     ;and return it as negative value
        ;
quit:
        ret                             ;return

Previous code is straightforward. You may download getkey.exe program from this site; look for program #3.

Now the strange part: when I read about the problems of the method that use a PAUSE command to discard the first byte of an extended key, I thought that the cause was the next command. For example, if the next command is SET /P, the arrow keys are active and allows to edit the input line, so it is possible that this combination avoids to successfully detect the second byte of an extended key. We need a method to correctly detect this byte after a PAUSE command, so I thought to use getkey.exe program just to do some tests to identify the second byte:

Code: Select all

@echo off
set /P "=Press any key: " < NUL
pause > NUL
getkey
echo Key read: %errorlevel%

This way, after press an extended key, PAUSE command take the first byte and getkey identify the next one, whichever it is. However, the result surprised me:

Code: Select all

C:\> test
Press any key: Key read: 32             <- UpArrow key pressed here and nothing happened,
                                           so I pressed a space (ASCII=32)

C:\> test
Press any key: Key read: -80            <- DownArrow key pressed here and immediately -80 appear!

In the first case, PAUSE discarded the first byte of UpArrow key, so getkey should got the second byte and should reported a positive 72, but it waits for a key instead! When I pressed a space, getkey took it and reported it correctly. This means that the second byte of UpArrow key was not passed as input to getkey.

In the second case I expected a similar behaviour, but when I pressed the DownArrow key, getkey correctly took it as an extended key! This means that the PAUSE command did NOT discarded its first byte, but it continued anyway after got a key. Which one? I can only guess that it was the byte with 72 that remained from previous test!

This means that the use of PAUSE command in combination with extended keys modify the natural stream of bytes from the keyboard, so this method can not be used for this purpose.

Antonio

penpen
Expert
Posts: 2009
Joined: 23 Jun 2013 06:15
Location: Germany

Re: Attempt to catch arrow keystrokes

#19 Post by penpen » 15 May 2014 05:47

This is the "CTRL+C" detection mechanism of the command shell (i've added the spaces and the "ignored:..." lines to see, what keys i've pressed):

Code: Select all

Z:\>@for /L %a in (1,1,10) do @test.bat
                ignored: -77
Press any key: Key read: -80
Press any key: Key read: -75
                ignored: -72

Press any key: Key read: -77
Press any key: Key read: -80
                ignored: -75
Press any key: Key read: -72

Press any key: Key read: -77
                ignored: -80
Press any key: Key read: -75
Press any key: Key read: -72

                ignored: -77
Press any key: Key read: -80
Press any key: Key read: -75

Now you could try "CTRL+C" as the first input (and then enter 'j'):

Code: Select all

Z:\>@for /L %a in (1,1,10) do @test.bat
Press any key: Batchvorgang abbrechen (J/N)? j

Another "CTRL+C" after two arrow down keys (the first is ignored):

Code: Select all

Z:\>@for /L %a in (1,1,10) do @test.bat
Press any key: Key read: -80
Press any key: Key read: 3
Press any key:

penpen

Aacini
Expert
Posts: 1914
Joined: 06 Dec 2011 22:15
Location: México City, México
Contact:

Re: Attempt to catch arrow keystrokes

#20 Post by Aacini » 15 May 2014 13:27

@penpen: Your results are consistent with my conclusion, that is:

Code: Select all

Z:\>@for /L %a in (1,1,10) do @test.bat
                ignored: -77    RightArrow: 0->Pause, 77: pending
Press any key: Key read: -80    DownArrow: 0+80 reported as -80
Press any key: Key read: -75    LeftArrow: pending77->Pause, 0+75 reported as -75
                ignored: -72    UpArrow: 0->Pause, 72: pending

Press any key: Key read: -77    RightArrow: 0+77 reported as -77
Press any key: Key read: -80    DownArrow: pending72->Pause, 0+80 reported as -80
                ignored: -75    LeftArrow: 0->Pause, 75: pending
Press any key: Key read: -72    UpArrow: 0+72 reported as -72

Press any key: Key read: -77    RightArrow: pending75->Pause, 0+77 reported as -77
                ignored: -80    DownArrow: 0->Pause, 80: pending
Press any key: Key read: -75    LeftArrow: 0+75 reported as -75
Press any key: Key read: -72    UpArrow: pending80->Pause, 0+72 reported as -72

                ignored: -77    RightArrow: 0->Pause, 77: pending
Press any key: Key read: -80    DownArrow: 0+80 reported as -80
Press any key: Key read: -75    LeftArrow: pending77->Pause, 0+75 reported as -75

Try "CTRL+C" as the first input:

Code: Select all

Z:\>@for /L %a in (1,1,10) do @test.bat
Press any key: Ctrl-C->Pause -> Cancel Batch file
Batchvorgang abbrechen (J/N)? j

A CTRL+C after two down arrow keys:

Code: Select all

Z:\>@for /L %a in (1,1,10) do @test.bat
                ignored: -80    DownArrow: 0->Pause, 80: pending
Press any key: Key read: -80    DownArrow: 0+80 reported as -80
Press any key: Key read: 3      Ctrl-C: pending80->Pause, Ctrl-C reported as 3
Press any key:


I am afraid I don't understand what you mean with "the CTRL+C detection mechanism of the command shell"...

Antonio

penpen
Expert
Posts: 2009
Joined: 23 Jun 2013 06:15
Location: Germany

Re: Attempt to catch arrow keystrokes

#21 Post by penpen » 15 May 2014 14:33

If a batch program doesn't expect any input, it regularly (not always) reads all pending data and searches for "CTRL+C", consuming pending data.
On success this is the result: "Terminate batch job (Y/N)" ... .

penpen

MicrosoftIsKillingMe
Posts: 55
Joined: 11 Dec 2017 09:08

Re: Attempt to catch arrow keystrokes

#22 Post by MicrosoftIsKillingMe » 24 Mar 2019 11:06

The thread may be ancient but the malady persists. Note, Control-Break (maybe Control C) is also victimized (at least observed in Windows 10 DOS).

If you can compile C, try this. Even tiny model :)

#include <conio.h>
int
main(void)
{
while (kbhit())
getch();
return 0;
}

and run eat-keys.com or eat-keys.exe atop .BATs. Let me know if that works, or even better if in the intervening years, someone has come up with something simpler.

It's pretty devastating when one uses the (some would say "lazy") approach of
echo Hit Control-C now or I am going to overwrite
pause
copy ...

and the pause is ignored because of an earlier arrow key. Or earlier Control-Break!!! Control-Break is supposed to prevent things, not give green lights !!

Post Reply