C# - SendInput Win32 API를 이용한 가상 키보드/마우스
이 글과 관련된 영상을 유튜브로 제공하고 있습니다. ^^
닷넷 C# 프로그램 실습 #1 가상 키보드/마우스 (5분 영상)
; https://youtu.be/BDrLENsHbB0
그동안 가상 키보드/마우스 관련해 이미 솔루션을 충분히 다뤄봤는데요,
Raspberry Pi Zero(OTG)를 다른 컴퓨터에 연결해 가상 마우스 + 키보드로 쓰는 방법 (두 번째 이야기)
; https://www.sysnet.pe.kr/2/0/11363
USB/IP PROJECT를 이용해 C#으로 USB Keyboard + Mouse 가상 장치 만들기
; https://www.sysnet.pe.kr/2/0/12229
그러고 보니, SendInput을 이용한 방법은 한 번도 소개한 적이 없었습니다.
SendInput
; https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendinput
하지만, 이미 다른 글에서 충분히 잘 다루고 있기 때문에,
Sending Keystrokes to Other Apps with Windows API and C#
; https://dzone.com/articles/sending-keys-other-apps
inputsimulator/WindowsInput/
; https://github.com/michaelnoonan/inputsimulator/tree/master/WindowsInput
자세한 설명은 생략하고 다음의 소스 코드만 실어 놓겠습니다. ^^
using System;
using System.Runtime.InteropServices;
using System.Threading;
namespace ConsoleApp1
{
// Sending Keystrokes to Other Apps with Windows API and C#
// ; https://dzone.com/articles/sending-keys-other-apps
// inputsimulator/WindowsInput/
// ; https://github.com/michaelnoonan/inputsimulator/tree/master/WindowsInput
[StructLayout(LayoutKind.Sequential)]
internal struct MOUSEINPUT
{
public int X;
public int Y;
public uint MouseData;
public uint Flags;
public uint Time;
public IntPtr ExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
internal struct KEYBDINPUT
{
public ushort Vk;
public ushort Scan;
public uint Flags;
public uint Time;
public IntPtr ExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HARDWAREINPUT
{
public uint Msg;
public ushort ParamL;
public ushort ParamH;
}
[StructLayout(LayoutKind.Explicit)]
internal struct MOUSEKEYBDHARDWAREINPUT
{
[FieldOffset(0)]
public HARDWAREINPUT Hardware;
[FieldOffset(0)]
public KEYBDINPUT Keyboard;
[FieldOffset(0)]
public MOUSEINPUT Mouse;
}
[StructLayout(LayoutKind.Sequential)]
internal struct INPUT
{
public uint Type;
public MOUSEKEYBDHARDWAREINPUT Data;
}
class Program
{
[DllImport("user32.dll", SetLastError = true)]
private static extern uint SendInput(uint numberOfInputs, INPUT[] inputs, int sizeOfInputStructure);
public static void SendKeyPress(KeyCode keyCode)
{
INPUT input = new INPUT
{
Type = 1
};
input.Data.Keyboard = new KEYBDINPUT()
{
Vk = (ushort)keyCode,
Scan = 0,
Flags = 0,
Time = 0,
ExtraInfo = IntPtr.Zero,
};
INPUT input2 = new INPUT
{
Type = 1
};
input2.Data.Keyboard = new KEYBDINPUT()
{
Vk = (ushort)keyCode,
Scan = 0,
Flags = 2,
Time = 0,
ExtraInfo = IntPtr.Zero
};
INPUT[] inputs = new INPUT[] { input, input2 };
if (SendInput(2, inputs, Marshal.SizeOf(typeof(INPUT))) == 0)
throw new Exception();
}
/// <summary>
/// Send a key down and hold it down until sendkeyup method is called
/// </summary>
/// <param name="keyCode"></param>
public static void SendKeyDown(KeyCode keyCode)
{
INPUT input = new INPUT
{
Type = 1
};
input.Data.Keyboard = new KEYBDINPUT();
input.Data.Keyboard.Vk = (ushort)keyCode;
input.Data.Keyboard.Scan = 0;
input.Data.Keyboard.Flags = 0;
input.Data.Keyboard.Time = 0;
input.Data.Keyboard.ExtraInfo = IntPtr.Zero;
INPUT[] inputs = new INPUT[] { input };
if (SendInput(1, inputs, Marshal.SizeOf(typeof(INPUT))) == 0)
{
throw new Exception();
}
}
/// <summary>
/// Release a key that is being hold down
/// </summary>
/// <param name="keyCode"></param>
public static void SendKeyUp(KeyCode keyCode)
{
INPUT input = new INPUT
{
Type = 1
};
input.Data.Keyboard = new KEYBDINPUT();
input.Data.Keyboard.Vk = (ushort)keyCode;
input.Data.Keyboard.Scan = 0;
input.Data.Keyboard.Flags = 2;
input.Data.Keyboard.Time = 0;
input.Data.Keyboard.ExtraInfo = IntPtr.Zero;
INPUT[] inputs = new INPUT[] { input };
if (SendInput(1, inputs, Marshal.SizeOf(typeof(INPUT))) == 0)
throw new Exception();
}
public enum KeyCode : ushort
{
#region Media
/// <summary>
/// Next track if a song is playing
/// </summary>
MEDIA_NEXT_TRACK = 0xb0,
/// <summary>
/// Play pause
/// </summary>
MEDIA_PLAY_PAUSE = 0xb3,
/// <summary>
/// Previous track
/// </summary>
MEDIA_PREV_TRACK = 0xb1,
/// <summary>
/// Stop
/// </summary>
MEDIA_STOP = 0xb2,
#endregion
#region math
/// <summary>Key "+"</summary>
ADD = 0x6b,
/// <summary>
/// "*" key
/// </summary>
MULTIPLY = 0x6a,
/// <summary>
/// "/" key
/// </summary>
DIVIDE = 0x6f,
/// <summary>
/// Subtract key "-"
/// </summary>
SUBTRACT = 0x6d,
#endregion
#region Browser
/// <summary>
/// Go Back
/// </summary>
BROWSER_BACK = 0xa6,
/// <summary>
/// Favorites
/// </summary>
BROWSER_FAVORITES = 0xab,
/// <summary>
/// Forward
/// </summary>
BROWSER_FORWARD = 0xa7,
/// <summary>
/// Home
/// </summary>
BROWSER_HOME = 0xac,
/// <summary>
/// Refresh
/// </summary>
BROWSER_REFRESH = 0xa8,
/// <summary>
/// browser search
/// </summary>
BROWSER_SEARCH = 170,
/// <summary>
/// Stop
/// </summary>
BROWSER_STOP = 0xa9,
#endregion
#region Numpad numbers
/// <summary>
///
/// </summary>
NUMPAD0 = 0x60,
/// <summary>
///
/// </summary>
NUMPAD1 = 0x61,
/// <summary>
///
/// </summary>
NUMPAD2 = 0x62,
/// <summary>
///
/// </summary>
NUMPAD3 = 0x63,
/// <summary>
///
/// </summary>
NUMPAD4 = 100,
/// <summary>
///
/// </summary>
NUMPAD5 = 0x65,
/// <summary>
///
/// </summary>
NUMPAD6 = 0x66,
/// <summary>
///
/// </summary>
NUMPAD7 = 0x67,
/// <summary>
///
/// </summary>
NUMPAD8 = 0x68,
/// <summary>
///
/// </summary>
NUMPAD9 = 0x69,
#endregion
#region Fkeys
/// <summary>
/// F1
/// </summary>
F1 = 0x70,
/// <summary>
/// F10
/// </summary>
F10 = 0x79,
/// <summary>
///
/// </summary>
F11 = 0x7a,
/// <summary>
///
/// </summary>
F12 = 0x7b,
/// <summary>
///
/// </summary>
F13 = 0x7c,
/// <summary>
///
/// </summary>
F14 = 0x7d,
/// <summary>
///
/// </summary>
F15 = 0x7e,
/// <summary>
///
/// </summary>
F16 = 0x7f,
/// <summary>
///
/// </summary>
F17 = 0x80,
/// <summary>
///
/// </summary>
F18 = 0x81,
/// <summary>
///
/// </summary>
F19 = 130,
/// <summary>
///
/// </summary>
F2 = 0x71,
/// <summary>
///
/// </summary>
F20 = 0x83,
/// <summary>
///
/// </summary>
F21 = 0x84,
/// <summary>
///
/// </summary>
F22 = 0x85,
/// <summary>
///
/// </summary>
F23 = 0x86,
/// <summary>
///
/// </summary>
F24 = 0x87,
/// <summary>
///
/// </summary>
F3 = 0x72,
/// <summary>
///
/// </summary>
F4 = 0x73,
/// <summary>
///
/// </summary>
F5 = 0x74,
/// <summary>
///
/// </summary>
F6 = 0x75,
/// <summary>
///
/// </summary>
F7 = 0x76,
/// <summary>
///
/// </summary>
F8 = 0x77,
/// <summary>
///
/// </summary>
F9 = 120,
#endregion
#region Other
/// <summary>
///
/// </summary>
OEM_1 = 0xba,
/// <summary>
///
/// </summary>
OEM_102 = 0xe2,
/// <summary>
///
/// </summary>
OEM_2 = 0xbf,
/// <summary>
///
/// </summary>
OEM_3 = 0xc0,
/// <summary>
///
/// </summary>
OEM_4 = 0xdb,
/// <summary>
///
/// </summary>
OEM_5 = 220,
/// <summary>
///
/// </summary>
OEM_6 = 0xdd,
/// <summary>
///
/// </summary>
OEM_7 = 0xde,
/// <summary>
///
/// </summary>
OEM_8 = 0xdf,
/// <summary>
///
/// </summary>
OEM_CLEAR = 0xfe,
/// <summary>
///
/// </summary>
OEM_COMMA = 0xbc,
/// <summary>
///
/// </summary>
OEM_MINUS = 0xbd,
/// <summary>
///
/// </summary>
OEM_PERIOD = 190,
/// <summary>
///
/// </summary>
OEM_PLUS = 0xbb,
#endregion
#region KEYS
/// <summary>
///
/// </summary>
KEY_0 = 0x30,
/// <summary>
///
/// </summary>
KEY_1 = 0x31,
/// <summary>
///
/// </summary>
KEY_2 = 50,
/// <summary>
///
/// </summary>
KEY_3 = 0x33,
/// <summary>
///
/// </summary>
KEY_4 = 0x34,
/// <summary>
///
/// </summary>
KEY_5 = 0x35,
/// <summary>
///
/// </summary>
KEY_6 = 0x36,
/// <summary>
///
/// </summary>
KEY_7 = 0x37,
/// <summary>
///
/// </summary>
KEY_8 = 0x38,
/// <summary>
///
/// </summary>
KEY_9 = 0x39,
/// <summary>
///
/// </summary>
KEY_A = 0x41,
/// <summary>
///
/// </summary>
KEY_B = 0x42,
/// <summary>
///
/// </summary>
KEY_C = 0x43,
/// <summary>
///
/// </summary>
KEY_D = 0x44,
/// <summary>
///
/// </summary>
KEY_E = 0x45,
/// <summary>
///
/// </summary>
KEY_F = 70,
/// <summary>
///
/// </summary>
KEY_G = 0x47,
/// <summary>
///
/// </summary>
KEY_H = 0x48,
/// <summary>
///
/// </summary>
KEY_I = 0x49,
/// <summary>
///
/// </summary>
KEY_J = 0x4a,
/// <summary>
///
/// </summary>
KEY_K = 0x4b,
/// <summary>
///
/// </summary>
KEY_L = 0x4c,
/// <summary>
///
/// </summary>
KEY_M = 0x4d,
/// <summary>
///
/// </summary>
KEY_N = 0x4e,
/// <summary>
///
/// </summary>
KEY_O = 0x4f,
/// <summary>
///
/// </summary>
KEY_P = 80,
/// <summary>
///
/// </summary>
KEY_Q = 0x51,
/// <summary>
///
/// </summary>
KEY_R = 0x52,
/// <summary>
///
/// </summary>
KEY_S = 0x53,
/// <summary>
///
/// </summary>
KEY_T = 0x54,
/// <summary>
///
/// </summary>
KEY_U = 0x55,
/// <summary>
///
/// </summary>
KEY_V = 0x56,
/// <summary>
///
/// </summary>
KEY_W = 0x57,
/// <summary>
///
/// </summary>
KEY_X = 0x58,
/// <summary>
///
/// </summary>
KEY_Y = 0x59,
/// <summary>
///
/// </summary>
KEY_Z = 90,
#endregion
#region volume
/// <summary>
/// Decrese volume
/// </summary>
VOLUME_DOWN = 0xae,
/// <summary>
/// Mute volume
/// </summary>
VOLUME_MUTE = 0xad,
/// <summary>
/// Increase volue
/// </summary>
VOLUME_UP = 0xaf,
#endregion
/// <summary>
/// Take snapshot of the screen and place it on the clipboard
/// </summary>
SNAPSHOT = 0x2c,
/// <summary>Send right click from keyboard "key that is 2 keys to the right of space bar"</summary>
RightClick = 0x5d,
/// <summary>
/// Go Back or delete
/// </summary>
BACKSPACE = 8,
/// <summary>
/// Control + Break "When debuging if you step into an infinite loop this will stop debug"
/// </summary>
CANCEL = 3,
/// <summary>
/// Caps lock key to send cappital letters
/// </summary>
CAPS_LOCK = 20,
/// <summary>
/// Ctlr key
/// </summary>
CONTROL = 0x11,
/// <summary>
/// Alt key
/// </summary>
ALT = 18,
/// <summary>
/// "." key
/// </summary>
DECIMAL = 110,
/// <summary>
/// Delete Key
/// </summary>
DELETE = 0x2e,
/// <summary>
/// Arrow down key
/// </summary>
DOWN = 40,
/// <summary>
/// End key
/// </summary>
END = 0x23,
/// <summary>
/// Escape key
/// </summary>
ESC = 0x1b,
/// <summary>
/// Home key
/// </summary>
HOME = 0x24,
/// <summary>
/// Insert key
/// </summary>
INSERT = 0x2d,
/// <summary>
/// Open my computer
/// </summary>
LAUNCH_APP1 = 0xb6,
/// <summary>
/// Open calculator
/// </summary>
LAUNCH_APP2 = 0xb7,
/// <summary>
/// Open default email in my case outlook
/// </summary>
LAUNCH_MAIL = 180,
/// <summary>
/// Opend default media player (itunes, winmediaplayer, etc)
/// </summary>
LAUNCH_MEDIA_SELECT = 0xb5,
/// <summary>
/// Left control
/// </summary>
LCONTROL = 0xa2,
/// <summary>
/// Left arrow
/// </summary>
LEFT = 0x25,
/// <summary>
/// Left shift
/// </summary>
LSHIFT = 160,
/// <summary>
/// left windows key
/// </summary>
LWIN = 0x5b,
/// <summary>
/// Next "page down"
/// </summary>
PAGEDOWN = 0x22,
/// <summary>
/// Num lock to enable typing numbers
/// </summary>
NUMLOCK = 0x90,
/// <summary>
/// Page up key
/// </summary>
PAGE_UP = 0x21,
/// <summary>
/// Right control
/// </summary>
RCONTROL = 0xa3,
/// <summary>
/// Return key
/// </summary>
ENTER = 13,
/// <summary>
/// Right arrow key
/// </summary>
RIGHT = 0x27,
/// <summary>
/// Right shift
/// </summary>
RSHIFT = 0xa1,
/// <summary>
/// Right windows key
/// </summary>
RWIN = 0x5c,
/// <summary>
/// Shift key
/// </summary>
SHIFT = 0x10,
/// <summary>
/// Space back key
/// </summary>
SPACE_BAR = 0x20,
/// <summary>
/// Tab key
/// </summary>
TAB = 9,
/// <summary>
/// Up arrow key
/// </summary>
UP = 0x26,
}
}
}
저렇게 SendInput 관련 코드를 작성했으면 이제 키보드 입력 전달을 다음과 같이 할 수 있습니다.
static void Main(string[] args)
{
Thread.Sleep(5 * 1000);
SendKeyPress(KeyCode.KEY_Z);
}
(
첨부 파일은 이 글의 예제 코드를 포함합니다.)
사실 보안 도구와 잘 협업하고 있는 게임과 같은 특수 사례만 아니라면 SendInput API만으로도 대부분의 키보드/마우스 입력을 전달하는데 별문제가 없습니다.
파이썬 키보드 마우스 제어하기 (pyautogui)
; https://blog.naver.com/cjinnnn/223048792430
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]