CatLab Studio
article thumbnail

0. AutoHotInterception 란?


AutoHotInterception 는 간단히 말하면 흔히 오토핫키의 하드웨어 입력 라이브러리로 알려진 Class DD와 같이 마우스 입력, 키보드 입력 등을 소프트웨어 신호가 아닌 하드웨어 신호로 전송할 수 있게 해주는 라이브러리입니다.

은행과 같이 보안 프로그램으로 인해 오토핫키의 기본 입력 명령어 ( Send, SendInput, MouseClick 등 ) 가 입력되지 않는 경우에 유용하게 사용이 가능합니다.

 

이 외에도 오토핫키의 핫키처럼 원하는 장치 ( 키보드, 마우스 등 )에 대한 입력에 반응할 수 있고, 똑같은 키 입력이라 하더라도 장치마다 각기 다른 행동을 시킬 수도 있습니다.

 

우선, Class DD와 가장 큰 차이점은 오픈소스라는 점입니다. 그렇기 때문에 실행 시 서버인증을 거치는 Class DD와 달리 자유롭게 사용이 가능하고, 추후 서버가 닫혀 사용이 불가능한 일도 발생하지 않습니다.

 

이번 글에서는 이 라이브러리에 대해서 정리하고, 실습하면서 공부해보겠습니다.

 

1. 설치하기


AutoHotInterception는  C#기반의 Dll로, 오토핫키의 CLR 라이브러리로 Dll를 불러와 사용이 가능합니다.

 

1. Interception 드라이버 설치하기

 

우선, 아래 홈페이지에 접속해서 "Interception.zip" 파일을 다운로드해줍니다.

https://github.com/oblitum/Interception/releases/tag/v1.0.1

 

Release v1.0.1 · oblitum/Interception

Minor fixes in API code Sample binaries added to release package Fix uninstall issue "error deleting "

github.com

다운로드가 완료됐다면 압축을 풀고,


1. cmd를 관리자 권한으로 실행한다.
2. "command line installer" 폴더로 이동한다.
( 예시 : cd C:\Users\Desktop\Interception\command line installer )
3. install-interception.exe /install
4. 재부팅

다음과 같이 Interception 드라이버를 설치해줍니다.

 

 

2. AutoHotInterception 다운받기

 

위의 드라이버를 설치 완료했다면, 다음 링크에서 "AutoHotInterception.zip" 파일을 다운로드해줍니다.

https://github.com/evilC/AutoHotInterception/releases

 

Releases · evilC/AutoHotInterception

An AutoHotkey wrapper for the Interception driver. Contribute to evilC/AutoHotInterception development by creating an account on GitHub.

github.com

다운로드가 완료되면 위의 1번에서 다운로드한 폴더(Interception)에 들어가서 library\x64, x86 폴더 안의 interception.dll 을 AutoHotInterception\Lib\x64, x86 폴더 안으로 이동시켜줍니다.

+추가로, DLL를 다운로드할 때 종종 차단돼서 정상 동작을 하지 않는 경우가 있으니, 포함된 Unblocker.ps1 파일을 관리자 권한으로 실행하라 합니다.

 

다음과 같은 설치 과정이 끝난다면, AutoHotInterception폴더 안 필수 기본 구조는 다음과 같습니다

AutoHotInterception

│  Monitor.ahk

─Lib

     │  AutoHotInterception.ahk

     │  AutoHotInterception.dll
     │  CLR.ahk
     │  Unblocker.ps1

     │  
     ├─x64 interception.dll    
     └─x86 interception.dll

 

메인 스크립트 ( 실제 사용자가 코드를 입력하는 스크립트 ) 와 Lib폴더를 같은 위치에 위치시켜주고,

메인 스크립트에서는 

#include Lib\AutoHotInterception.ahk 를 스크립트 상단에 선언해주면 됩니다.

 

이후, ahk 형식의 스크립트를 exe 형식으로 컴파일해서 배포할 때는,

모든 필수 dll 파일들이 exe 파일에 압축되기 때문에 배포 시 단독으로 exe 파일 하나만을 배포하시면 됩니다.

( 내장 변수 A_PtrSize를 이용해 bit를 확인하고, A_IsCompiled시 FileInstall을 통해 exe파일 내에 dll를 포함하는 방식 )

2. 사용하기


앞선 설치 과정이 끝났다면, 이번엔 실사용 함수를 몇 가지 설명하고 사용해보려 합니다.

사용하기 앞서, 기본적인 환경설정은 Lib폴더를 메인스크립트와 같은 위치에 두고 메인스크립트에는

 

#Persistent
#include Lib\AutoHotInterception.ahk
global AHI := new AutoHotInterception() ; 전역 AutoHotInterception Class 객체 선언

 

위 세 가지를 입력해줍니다.

이제 AutoHotInterception를 사용할 준비가 모두 끝났습니다.

 

1. ID 란?

사용하기 앞서, ID 라는것에 대해서 짚고 넘어가겠습니다

AutoHotInterception은 고유 ID을 통해 하드웨어 키보드 혹은 마우스를 특정합니다.

예를 들어 사용자의 키 입력을 받을 때도 해당 입력이 어떤 키보드에서 왔는지, 마우스 클릭이 어떤 마우스로부터 왔는지를 ID를 통해 특정합니다.

 

만약 하나의 컴퓨터에 A 키보드와 B 키보드가 연결되어있다면 A 키보드는 ID 1을 부여하고 B 키보드에는 ID 2를 부여해서 A 키보드에서 엔터키를 눌렀다면 "ID 1번에서 엔터키를 눌렀다"라고 특정할 수 있는 거죠.

 

이러한 ID는 1부터 20까지의 숫자를 지니며, 

1 ~ 10 번은 키보드, 11 ~ 20 번은 마우스입니다.

 

ID는 또한 기기를 뽑고 다시 연결할 때 변경될 수 있으며 이로 인한 문제점이 하나 있습니다.

더보기

문제점 :

기기를 뽑고 다시 연결할 때마다 고유 ID가 1씩 증가하는데, 키보드의 경우 ID가 10을 넘거나 마우스의 경우는 20을 넘게 되면 해당 장치는 컴퓨터를 재부팅하기 전까진 사용이 불가능하게 됩니다.

이는 Interception 드라이버의 고유 Issue라 수정이 힘드니 알아만 두시면 좋습니다.

하지만 사용에 지장이 가는 문제점은 아니니 넘어가셔도 좋습니다.

 

ID는 AutoHotInterception에서 상당히 핵심적인 요소입니다. 이런 ID를 알아내는 방법은 대충 세 가지로 나뉩니다

 

첫 번째는 장치의 VID (Vendor ID : 공급사 ID)와 PID (Product ID : 제품 ID)로 알아내는 방법.

두 번째는 장치의 핸들 Handle로 알아내는 방법입니다.

세 번째는 모든 장치의 정보를 가져오는 방법입니다.

 

1) VID / PID 로부터 ID 가져오기

KeybdID := AHI.GetKeyboardId(<VID>, <PID> [,<instance = 1>] )
MouseID := AHI.GetMouseId(<VID>, <PID> [,<instance = 1>] )

2) Handle 로부터 ID 가져오기

KeybdID := AHI.GetKeyboardIdFromHandle(<handle> [,<instance = 1>] )
MouseID := AHI.GetMouseIdFromHandle(<handle> [,<instance = 1>] )

 

그렇다면, 기기의 VID, PID, 그리고 Handle과 같은 정보는 어떻게 가져오는지 의문점이 하나 생깁니다.

이 역시 현재 컴퓨터에 연결돼 있는 기기들의 ID, VID, PID, Handle을 가져올 수 있는 함수를 제공합니다.

 

바로 GetDeviceList() 함수입니다. 해당 함수는 모든 기기들의 정보를 받아와 배열 - 연관배열 2차원 배열로 저장합니다.

연관배열의 Key는 5가지 ( Id isMouse Vid Pid Handle )가 있습니다.

 

예시를 들어 보여드리겠습니다.

 

3) 모든 기기의 정보 가져오기 ( ID, VID, PID, Handle . . . )

Devicelists := AHI.GetDeviceList()
Devices := ""

for i, d in Devicelists
{
	DeviceInfos := ""
	for k, v in d
		DeviceInfos .= k " : " ((k = "PID" || k = "VID") ? FHex(v, 4) : v) "`n"
	
	Devices .= (Devices ? "`n`n" : "") DeviceInfos
}
MsgBox,% "디바이스 정보들 : `n`n" Devices
return


FHex(int, pad=0) { ; Function by [VxE]. Formats an integer (decimals are truncated) as hex.
	Static hx := "0123456789ABCDEF"
	If !( 0 < int |= 0 )
		Return !int ? "0x0" : "-" FHex( -int, pad )

	s := 1 + Floor( Ln( int ) / Ln( 16 ) )
	h := SubStr( "0x0000000000000000", 1, pad := pad < s ? s + 2 : pad < 16 ? pad + 2 : 18 )
	u := A_IsUnicode = 1

	Loop % s
		NumPut( *( &hx + ( ( int & 15 ) << u ) ), h, pad - A_Index << u, "UChar" ), int >>= 4

	Return h
}

 

Devicelists 변수에 GetDeiveList() 함수를 이용해 배열 - 연관배열 2차원 배열로 담고, 이를 for문을 이용해 한눈에 보이게 해 주었습니다.

2차원배열이 다소 보기에 생소할 수 있으니, 오토핫키의 래퍼런스 객체 Object 부분을 한번 살펴보시면 좋습니다.

[ 참조 : autohotkeykr.sourceforge.net/docs/Objects.htm ]

 

VID 와 PID의 경우는 Hex로 표현되기 때문에 오토핫키 포럼 VxE님의 함수를 인용해 Hex값으로 표현해주었습니다

 

 

2. 입력 감지

AutoHotInterception는 오토핫키의 핫키처럼 사용자의 키 입력을 감지해서 원하는 명령어를 실행시키거나, Class DD처럼 하드웨어 신호로 키를 입력시킬 수 있습니다.

 

이번에는 Interception의 키 입력 감지 부분에 대해 다뤄보려고 합니다.

만약 입력 감지가 아닌 하드웨어 키 입력에 대해서만 알고 넘어가고 싶으시다면 3번 부터 보셔도 괜찮습니다.

하지만 1번 ID 부분은 꼭 한번 읽어보고 넘어가시는 걸 추천드립니다

 

AutoHotInterception에서는 Subscription 모드와 Context 모드 이렇게 두 가지 입력 감지 모드를 지원합니다.

좀 더 이해가 쉽게 번역을 해서 작성하려고 했으나 마땅한 번역명이 생각나지 않아서 원문 그대로 언급을 하겠습니다

 

Context 모드

우선, Context 모드는 오토핫키의 문맥 감지 핫키의 장점을 가져온 모드라고 할 수 있습니다

[ 참조 : autohotkeykr.sourceforge.net/docs/Hotkeys.htm#Context ]

 

대략적인 사용방법을 살펴보면, 위에서 설명한 Interception의 고유 ID로부터 "Context Manager" 를 생성해서 해당 ID가 .IsActive 속성을 지닐 때 설정한 핫키를 동작시키는 방식입니다.

 

한마디로, A 키보드와 B 키보드가 있을 때 A 키보드에서 키를 눌렀을 때만 핫키를 발동시키고 B 키보드에서 똑같은 키를 눌러도 핫키를 발동시키지 않을 수 있다는 말입니다.

 

예제 파일에 포함된 Context Example.ahk를 예제로 설명해보겠습니다.

#SingleInstance force
#Persistent
#include Lib\AutoHotInterception.ahk

AHI := new AutoHotInterception()
id1 := AHI.GetKeyboardId(0x1C4F, 0x0002, 1)

cm1 := AHI.CreateContextManager(id1)
return

#if cm1.IsActive
::aaa::JACKPOT
1:: 
	ToolTip % "KEY DOWN EVENT @ " A_TickCount
	return
	
1 up::
	ToolTip % "KEY UP EVENT @ " A_TickCount
	return
#if

^Esc::
	ExitApp

해당 코드를 설명하면, VID가 0x1C4F이고 PID가 0x0002인 키보드로부터 ID를 받아 온 다음 cm1이라는 Context Manager를 생성하고, 해당 키보드가 작동할 때 ( .IsActive ) #if 문 아래의 핫키를 촉발시킨다는 뜻입니다.

 

만약 위에서 생성한 키보드가 아닌 다른 키보드로 해당 핫키들을 누른다 해도 설정해둔 핫 스트링이나 핫키는 작동하지 않게 됩니다.

 

한 가지 특징으로는 해당 모드에서는 키보드 입력이나 마우스 클릭은 모두 감지할 수 있으나 마우스 움직임은 감지하지 못한다는 특징이 있습니다.

 

● Subscription 모드

Subscription 모드는 오토핫키의 모든 핫키 시스템을 우회하고 모든 형태의 입력을 지원합니다.

또한 해당 모드는 상단의 Context 모드와 같이 사용이 가능한데, 같이 사용할 경우 Subscription 모드가 우선시 됩니다.

 

즉, 간단히 말하면 Subscription 모드가 Context 모드보다 보다 높은 권한을 가지고 Context 모드를 재정의(Override) 합니다. 상당히 강력한 모드라 볼 수 있죠

Subscription 모드는 SetState(true | false) 함수를 통해 전체 Subscription을 끄거나 킬 수 있습니다.

 

Subscription 모드에서는 간단하게 4가지 함수로 나눠서 어떻게 사용하는지 알아보겠습니다.

 

  1. 특정 키보드에서 원하는 키의 입력을 감지하기
  2. 특정 키보드의 모든 키 입력을 감지하기
  3. 특정 마우스의 원하는 버튼의 입력을 감지하기
  4. 특정 마우스의 모든 입력을 감지하기

1. 특정 키보드에서 원하는 키의 입력을 감지하기

SubscribeKey(<deviceId>, <scanCode>, <block>, <callback>, <concurrent>)
< deviceid >  입력을 감지할 키보드의 ID
< scancode >  감지할 키의 스캔코드
< block >  고유 키의 기능 차단 여부 ( true : 기존 키 입력 차단 / false : 기존 키 기능 사용 )
< callback >  CallBack 함수
< concurrent >  신규 쓰레드 생성 여부

 

UnsubscribeKey(<deviceId>, <scanCode>)
< deviceid >  감지를 해지할 키보드의 ID
< scancode >  감지를 해지할 키의 스캔코드

우선, 해당 함수를 보면 총 5가지의 인자를 받는 걸 볼 수 있습니다.

 

첫 번째 인자는 DevideID로 감지할 키보드의 고유 ID 입니다

 

두 번째 인자는 감지할 키의 스캔 코드로, 오토핫키의 GetKeySC() 함수로 손쉽게 알아낼 수 있습니다.

 

세 번째 인자는 고유 키의 기능 차단여부로, 오토핫키의 핫키 수식 키 "~" 의 여부와 비슷하다고 볼 수 있습니다.

만약 "F1" 키를 감지하는데 해당 인자를 true로 둔다면, 기존 F1키의 기능은 작동하지 않습니다.

하지만 해당 인자를 false로 둔다면 기존 키의 기능 또한 작동하고 callback 또한 작동합니다.

 

네 번째 인자는 CallBack 함수로, 앞서 설정한 키보드의 특정 키가 눌렸을 경우 호출되는 함수입니다.

CallBack함수는 키의 State ( 눌렸으면 1 아니면 0 )를 인자로 받습니다.

 

다섯 번째 인자는 신규 쓰레드의 생성 여부로, 기본적으로 false로 설정되어있습니다.

만약 이를 true로 설정하면 매번 입력마다 새로운 쓰레드가 생성됩니다. 기본은 순차적으로 쓰레드를 진행하게 됩니다.

 

예를 들어, AHI.SubscribeKey(keyboardId, GetKeySC("F1"), true, Func("KeyEvent")) 와 같은 명령어는

keyboardId 라는ID를 가진 키보드의 "F1"키가 눌리면 "KeyEvent" 라는 함수를 키의 상태를 인자로 넘기면서 호출한다.

라는 의미를 지니고 있습니다.

 

만약, KeyEvent의 함수를 다음과 같이 작성하면 F1 키가 눌릴 때마다 ToolTip으로 키의 상태를 표시해줍니다.

KeyEvent(state){
	ToolTip % "F1 키의 상태 : " state
}

이 외의 3가지 함수 모두 크게 다르지 않기 때문에 원리만 제대로 이해하고 넘어간다면 다른 함수들 또한 모두 손쉽게 응용이 가능할 거라 생각됩니다

 

나머지 함수들은 함수 원형과 대략적인 차이점을 서술하고 넘어가도록 하겠습니다. 모두 직접 실습으로 하나하나 해본다면 보다 쉽게 이해가 될 겁니다

 

혹은 포함된 예제들의 VID와 PID를 수정해서 직접 실행해보는 것도 좋은 방법입니다

 

 

2. 특정 키보드에서 모든 키의 입력을 감지하기

SubscribeKeyboard(<deviceId>, <block>, <callback>, <concurrent>)

 

1번 함수와의 차이점은 키를 감지할 키를 지정하지 않기 때문에 해당 키보드의 입력을 모두 감지하고, CallBack함수가 전달받는 인자가 입력된 키의 SC 스캔 코드와 상태라는 점입니다.

( 첫 번째 인자는 입력된 키의 스캔 코드, 두 번째 인자는 해당 키의 상태 )

 

 

3. 특정 마우스의 원하는 버튼의 입력을 감지하기

SubscribeMouseButton(<deviceId>, <button>, <block>, <callback>, <concurrent>)

UnsubscribeMouseButton(<deviceId>, <button>)

 

마우스 또한 키보드와 크게 다르지 않습니다.

한 가지 차이점은, 키보드는 입력된 키의 스캔 코드로 구별하는 반면 마우스는 Button을 숫자로 입력받는다는 점입니다.

< button >
0: 왼쪽 버튼
1: 오른쪽 버튼
2: 가운데 버튼
3: 사이드 버튼1
4: 사이드 버튼2 
5: 마우스 휠 ( 세로 )
6: 마우스 휠 ( 가로 )

이 외의 CallBack함수가 키의 상태를 인자로 전달받는 점 또한 위의 키보드 함수와 다르지 않지만, 마우스의 휠을 올리거나 내릴 때의 키의 상태가 추가됐습니다. ( 1 : 마우스 휠 올림 / -1 : 마우스 휠 내림 )  

 

 

4. 특정 마우스의 모든 입력을 감지하기

SubscribeMouseButtons(<deviceId>, <block>, <callback>, <concurrent>)

 

이 함수 역시 3번과 크게 다르지 않고, 2번과 같이 CallBack함수가 키의 상태뿐만 아니라 눌린 키가 어떤 키인지.

즉, < button >을 첫 번째 인자로 전달받고 키의 상태를 두 번째 인자로 전달받습니다

 

 

 

3. 하드웨어 입력

위에서는 AutoHotInterception의 ID개념, 입력 감지 등을 다뤘습니다.

그런데 오토핫키를 접한 지 얼마 안 되셨거나, 처음 접하시는 분들은 다소 생소하고 어렵게 다가올 수 있는 내용들입니다.

 

그래서 하드웨어 입력 부분은 초보자 분들도 보다 알기 쉽고 빠르게 사용을 할 수 있도록 간단하게 작성해보려 합니다

 

하드웨어 입력 함수는 간략하게 딱 3가지로 나누겠습니다.

 

  1. 키보드 키 입력
  2. 마우스 이동
  3. 마우스 클릭

 

1. 키보드 키 입력

AHI.SendKeyEvent(<keyboardId>, <scanCode>, <state>)

 

함수의 인자들은 앞서 설명한 내용들을 보셨다면 충분히 어떤 뜻인지 알 수 있을 겁니다

간단하게 예시 코드를 들겠습니다

AHI.SendKeyEvent(4, GetKeySC("F1"), 1) ; 키보드 ID "4"의 "F1" 키를 누른다 (1)
AHI.SendKeyEvent(4, GetKeySC("F1"), 0) ; 키보드 ID "4"의 "F1" 키를 뗀다 (0)

 

2. 마우스 이동

AHI.MoveCursor(<x>, <y> [, <coordMode>, <mouseId>])

 

함수명 그대로 마우스 커서의 위치를 원하는 X Y 좌표로 옮깁니다.

Coordmode는 오토핫키의 Coordmode와 같다고 보시면 됩니다.

[ 참조 : autohotkeykr.sourceforge.net/docs/commands/CoordMode.htm ]

 

이 역시 간단하게 예시 코드를 보여드리겠습니다.

AHI.MoveCursor(300, 200)           ; 마우스 커서를 x300 y200으로 옮긴다
AHI.MoveCursor(100, 200, "Window") ; 마우스 커서를 활성창에 상대좌표로 x100 y200으로 옮긴다
AHI.MoveCursor(300, 200,, 11)      ; 마우스 커서를 "ID 11"을 지닌 마우스 신호로 옮긴다

 

3. 마우스 클릭

AHI.SendMouseButtonEvent(<mouseId>, <button>, <state>)

 

원하는 ID의 마우스로 원하는 버튼을 누르거나 뗍니다.

AHI.SendMouseButtonEvent(11, 0, 1) ; "ID 11"를 가진 마우스의 신호로 왼쪽 버튼(0)을 누른다(1)
AHI.SendMouseButtonEvent(11, 4, 1) ; "ID 11"를 가진 마우스의 신호로 사이드 버튼2(4)를 누른다(1)
AHI.SendMouseButtonEvent(11, 5, 1) ; "ID 11"를 가진 마우스의 신호로 마우스휠(5)을 올린다(1)

< button >
0: 왼쪽 버튼
1: 오른쪽 버튼
2: 가운데 버튼
3: 사이드 버튼1
4: 사이드 버튼2 
5: 마우스 휠 ( 세로 )
6: 마우스 휠 ( 가로 )

< state >
1: 누름
0: 뗌
1: 마우스 휠 올림
-1: 마우스 휠 내림

 

Etc. 절대좌표 이동 / 클릭 ( 보셔도 좋고 안 보셔도 좋습니다 )

AHI.SendMouseMoveAbsolute(<mouseId>, <x>, <y>)

AHI.SendMouseButtonEventAbsolute(<mouseId>, <button>, <state>, <x>, <y>)

 

AHI의 마우스 좌표는 상대 좌표(Relative)와 절대좌표(Absolute)로 나눌 수 있습니다.

 

상대좌표는 흔히 익숙한 "px(pixel)"단위를 사용하지 않고 "Mickeys"를 사용하는데, 이를 사용하려면 다소 어려울 수 있으니 절대좌표를 이용하겠습니다.

 

절대좌표 또한 단순히 바로 500, 500 px으로 이동하라 입력하면 원하는 위치로 이동할 수 없습니다.

 

이는 화면 좌표를 0부터 65535로 나눠서 계산하기 때문인데, 화면의 너비와 길이만큼 65535로 나눠서 좌표값을 계산한다면 기존의 오토핫키 MouseMove 명령어처럼 손쉽게 사용이 가능합니다.

 

간단하게 예를 들겠습니다.

Mov_X := 500      ; 목표 이동 X값
Mov_Y := 500      ; 목표 이동 Y값
Send_X := 65535 / A_ScreenWidth * Mov_X      ; 목표 X값을 0~65535 비율에 맞춰 계산
Send_Y := 65535 / A_ScreenHeight * Mov_Y     ; 목표 Y값을 0~65535 비율에 맞춰 계산

AHI.SendMouseMoveAbsolute(11, Send_X, Send_Y) 
; "ID 11"를 지닌 마우스의 신호로 목표 좌표로 이동한다

AHI.SendMouseButtonEventAbsolute(11, 0, 1, Send_X, Send_Y) 
; "ID 11"를 지닌 마우스의 신호로 왼쪽버튼(0)을 Send_X Send_Y 좌표에서 누른다(1)

 

 

3. 마무리


처음에는 최대한 간단한 사용방법만 추려서 작성하려고 마음먹었는데 작성하다 보니 조금 길어지게 됐네요

상당히 좋은 드라이버/라이브러리이니 길더라도 천천히 보시면서 한번 사용해보시면 좋을 것 같습니다

 

나중에는 웹 통신 쪽 API에 대해서 다뤄보려 하는데 좋은 아이디어 있으시면 언제든 댓글 달아주세요

긴 글 봐주셔서 감사합니다

 

 

 

 

 

 

 

profile

CatLab Studio

@CatLab

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!