Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

method to detect key combinations


  • Please log in to reply
7 replies to this topic
shimanov
  • Members
  • 610 posts
  • Last active: Jul 18 2006 08:35 PM
  • Joined: 25 Sep 2005
This code demonstrates a simple method to detect a combination of depressed keys -- also mouse buttons -- and act thereupon. It uses the OS provided AttachThreadInput function to accomplish this with a minimal amount of code.

Refer to Virtual-Key Codes at MSDN for information necessary to extend the capabilities of this script.

note: present incarnation requires that all keys be depressed simultaneously

To test, open Notepad, simultaneously press and hold 'a', 'g', and 'i', then release.

#Persistent

SetBatchLines, -1
SetFormat, Integer, Hex
SetKeyDelay, -1

OnExit, HandleExit

tid_this := DllCall( "GetCurrentThreadId" )

VarSetCapacity( state, 256 )
SetTimer, timer_MonitorKeyboard, 10
return

HandleExit:
	DllCall( "AttachThreadInput", "uint", tid_this, "uint", tid_active_old, "int", false )
ExitApp

timer_MonitorKeyboard:
	tid_active := DllCall( "GetWindowThreadProcessId", "uint", WinActive( "A" ), "uint", 0 )
	if ( tid_active != tid_active_old )
	{
		DllCall( "AttachThreadInput", "uint", tid_this, "uint", tid_active_old, "int", false )
		DllCall( "AttachThreadInput", "uint", tid_this, "uint", tid_active, "int", true )
		
		tid_active_old := tid_active
	}
	
	DllCall( "GetKeyboardState", "uint", &state )
	
	state_text=
	loop, 254
		if ( *( &state+A_Index ) & 0x80 )
			state_text = %state_text%|%A_Index%

	; 0x41, 0x44, 0x53, etc. are Virtual-Key codes
	; must be listed in numerically increasing order
	if ( state_text = "|0x41|0x44|0x53" )										; ads
		Send, {Backspace 3}about super dogs
	if ( state_text = "|0x41|0x47|0x49" )										; agi
		Send, {Backspace 3}AHk is great!
	else if ( state_text = "|0x48|0x57" )										; hw
		Send, {Backspace 2}Hello, World!
	else if ( state_text = "|0x4A|0x4B|0x4C" )									; jkl
		Send, {Backspace 3}just kidding Latecia
	else if ( state_text = "|0x43|0x56|0x58|0x5A" )								; cvxz
		Send, {Backspace 4}zoology xylophone category vertical
return


Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004
I tried it in Notepad by holding down the three keys "ads" simultaneously. It's remarkably reliable, and due to its mouse capabilities and other benefits, perhaps it will be preferred over the Input command for some purposes.

Great demonstration!

shimanov
  • Members
  • 610 posts
  • Last active: Jul 18 2006 08:35 PM
  • Joined: 25 Sep 2005

three keys "ads" simultaneously


I would have tried "agi".

Great demonstration!


Thanks.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
This is a very elegant method, but there is a little problem with it: the need to scan most of the table of virtual key codes, that is, a loop of 254 iterations. It takes over 2 ms in my fast laptop. In a keyboard driver application, we have to keep track of the modifiers, too. If Win, Ctrl or Alt is pressed, one might want to leave the keys alone, but Shift-ed keys need to be remapped. There are equivalent key combinations (LShift, RShift), and the state of CapsLock has to be considered. These are not difficult, but as interpreted code, they slow down the whole script, consuming some 30% of the CPU time (2GHz Centrino) with the 10 ms timer. (I cannot even use the 1 ms TimerMessage method.)

Is there a DllCall, or any other faster way to return the indices of the table entries, which have one of their bit set (the negative ones)? In practice, we could ignore the cases where more than 6 or 8 entries are active. Writing a Dll for this scan looks like an overkill.

shimanov
  • Members
  • 610 posts
  • Last active: Jul 18 2006 08:35 PM
  • Joined: 25 Sep 2005

a loop of 254 iterations. It takes over 2 ms in my fast laptop.


Interesting. The loop completes processing in under a millisecond on my system (1.7 GHz Pentium M).

consuming some 30% of the CPU time (2GHz Centrino) with the 10 ms timer. (I cannot even use the 1 ms TimerMessage method.)


Running a timer with a 1 ms period does consume ~100% CPU time; however, the code functions correctly, and, since the process priority is left at normal, the system remains responsive.

It is possible to combine this method with that from WaitForAnyKey, to reduce CPU time consumption. Something like this:

note: With StartTimer( 1, 0, true ), remains reliable and reduces CPU consumption from ~100% to ~5%.

#InstallKeybdHook
...
Handle_TimerMessage()
{
	static	idle, repeat_once

	if ( idle > A_TimeIdlePhysical or repeat_once )
	{
		repeat_once := !repeat_once
		...
	}

	idle := A_TimeIdlePhysical
}

It is rare that any one solution will be useful or effective universally. The posted code demonstrates one method, which seems effective for the purpose stated. It will be necessary to conduct further research to adapt it for any another purpose.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
Since A_TimeIdlePhysical uses the tick counter, they change synchronously. This way we loose the high resolution of the TimerMessages. Also, after a keystroke is detected, we still need to scan the table and find a match in the replacement list, so there is some 3 ms blackout time. I don't say this method is not better than anything else I have seen so far, but scanning the table with non-interpreted code was still an important improvement.

jabobian
  • Members
  • 29 posts
  • Last active: Feb 03 2011 03:46 AM
  • Joined: 13 Apr 2010
#Persistent

SetBatchLines, -1
SetFormat, Integer, Hex
SetKeyDelay, -1

OnExit, HandleExit

tid_this := DllCall( "GetCurrentThreadId" )

VarSetCapacity( state, 256 )
SetTimer, timer_MonitorKeyboard, 10
return

HandleExit:
   DllCall( "AttachThreadInput", "uint", tid_this, "uint", tid_active_old, "int", false )
ExitApp

timer_MonitorKeyboard:
   tid_active := DllCall( "GetWindowThreadProcessId", "uint", WinActive( "A" ), "uint", 0 )
   if ( tid_active != tid_active_old )
   {
      DllCall( "AttachThreadInput", "uint", tid_this, "uint", tid_active_old, "int", false )
      DllCall( "AttachThreadInput", "uint", tid_this, "uint", tid_active, "int", true )
      
      tid_active_old := tid_active
   }
   
   DllCall( "GetKeyboardState", "uint", &state )
   
   state_text=
   loop, 26
      if ( *( &state+64+A_Index ) & 0x80 )
         {
           state_text := state_text . "|" . A_Index + 64
         }
         
   ; 0x41, 0x44, 0x53, etc. are Virtual-Key codes must be listed in numerically increasing order
   if ( state_text = "|0x45|0x52" )               ;ER                 
      {
        Send, {Backspace 2}u
        KeyWait e
        KeyWait r
      }
   else if ( state_text = "|0x45|0x52|0x57" )       ;ERW                       
      {
        Send, {Backspace 3}i
        KeyWait e
        KeyWait r
        KeyWait w
      }
   else if ( state_text = "|0x45|0x57" )               ;EW                
      {
        Send, {Backspace 2}o
        KeyWait e
        KeyWait w
      }
   if ( state_text = "|0x44|0x46" )                     ;DF         
      {
        Send, {Backspace 2}j
      }
   else if ( state_text = "|0x44|0x46|0x53" )                ;DFS              
      {
        Send, {Backspace 3}k
      }
   else if ( state_text = "|0x44|0x53" )                   ;DS           
      {
        Send, {Backspace 2}l   
      } 
   else if ( state_text = "|0x43|0x56" )                    ;CV          
      {
        Send, {Backspace 2}n
      }
   else if ( state_text = "|0x43|0x56|0x58" )                  ;CVX            
      {
        Send, {Backspace 3}m 
      }
   else if ( state_text = "|0x43|0x58" )                     ;CX         
      {
        Send, {Backspace 2}p
      }
   else if ( state_text = "|0x45|0x46" )                     ;EF         
      {
        Send, {Backspace 2}y
      }
   else if ( state_text = "|0x44|0x56" )                     ;DV         
      {
        Send, {Backspace 2}h 
      }
   
return

The problem with this approach is not with 254 loops, but with instead the nature of timer. I added a number of keywait to make it more robust.

But to avoide the annoying {backspace n} flickering, a lower level approach should be considered.

evilc
  • Members
  • 340 posts
  • Last active: Oct 27 2015 11:07 PM
  • Joined: 17 Nov 2005

I stumbled upon this thread recently, and was wondering if it could maybe be re-purposed...

 

I would like to try an improve upon my current efforts at getting the ultimate "Hotkey" GUI item (lets a user bind a desired hotkey to something).

I suppose you would need some way to translate the scancode into key names, but I wondered if there would be a way to do it without having to hard-code all the scan codes in a huge if block.

I found a post here that seems to be talking about decoding the name for each VK from the locale info, but it is a bit above me.

If anyone can help with the windows stuff, I would appreciate it.