Repository: PowerToys
Issue: keyboard manager add a function to select the input device
Labels: Idea-Enhancement,Product-Keyboard Shortcut Manager

### Description of the new feature / enhancement

Can the keyboard manager add a function to select the input device, such as the ability to change the keyboard to a different mapping when the keyboard is connected to an external tablet or other keyboards. For example, different input devices can be distinguished by identifying different USB or Bluetooth device identifiers.

### Scenario when this would be used?

When two keyboard devices and tablet or a Bluetooth keyboard are connected

### Supporting information

some code i think helpful was found
          // #include "stdafx.h"
          #include <stdio.h>
          #include <tchar.h>
          #include <windows.h>
          
          //声明自定义消息处理函数
          LRESULT CALLBACK WindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam);
          HWND m_hWnd;
          
          
          
          //注册原始输入设备
          void RegKeyboardRawInput(HWND hWnd)
          {
	          RAWINPUTDEVICE rawInputDevice[1];
	          rawInputDevice[0].usUsagePage = 0x01;//设备类
	          rawInputDevice[0].usUsage = 0x06;//设备类内的具体设备
	          rawInputDevice[0].dwFlags = RIDEV_INPUTSINK;//意味着即使窗口失去焦点位置，仍然会一直接收输入消息
	          rawInputDevice[0].hwndTarget = hWnd;
	          if (RegisterRawInputDevices(rawInputDevice, 1, sizeof(rawInputDevice[0])) == FALSE)
	          {
		          printf("RegisterRawInputDevices failed");
	          }
          }
          
          int _tmain(int argc, _TCHAR* argv[])
          {
	          //创建一个隐藏窗口
	          HINSTANCE hInst;
	          hInst = GetModuleHandle(NULL);	//获取一个应用程序或动态链接库的模块句柄
	          WNDCLASSEX wcx;
	          wcx.cbSize = sizeof(wcx);
	          wcx.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC | CS_NOCLOSE;//窗口的风格
	          wcx.lpfnWndProc = WindowProc;     //采用自定义消息处理函数
	          wcx.cbClsExtra = 0;                // no extra class memory
	          wcx.cbWndExtra = 0;                // no extra window memory
	          wcx.hInstance = hInst;         //当前应用程序的实例句柄
	          wcx.hIcon = LoadIcon(NULL,IDI_APPLICATION); //图标风格
	          wcx.hCursor = LoadCursor(NULL,IDC_ARROW);  //鼠标风格
	          wcx.hbrBackground = (HBRUSH)WHITE_BRUSH; //背景色
	          wcx.lpszMenuName =NULL;  //菜单名
	          wcx.lpszClassName = _T("ITSMYOSDCLASS"); //窗口类的名称
	          wcx.hIconSm = NULL;
          
	          if (!RegisterClassEx(&wcx))
	          {
		          printf("RegisterClassEx failed");
		          return 1;
	          }
          
	          //窗口在屏幕上的显示位置
	          int OSDleft = GetSystemMetrics(SM_CXSCREEN) / 2 - 300;
	          int OSDTop = GetSystemMetrics(SM_CYSCREEN) / 2;
          
	          m_hWnd = CreateWindowEx(
		          WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_TRANSPARENT | WS_EX_NOACTIVATE,//窗口的扩展风格
		          wcx.lpszClassName,//上面注册的类名lpszClassName，要完全一致
		          NULL,
		          WS_VISIBLE | WS_POPUP,//窗口的风格
		          OSDleft,//窗口相对于父级的X坐标
		          OSDTop,//窗口相对于父级的Y坐标
		          300,//窗口的宽度
		          300,//窗口的高度
		          (HWND)NULL,//没有父窗口，为(HWND)NULL//GetDesktopWindow(),
		          (HMENU)NULL,//没有菜单，为NULL
		          hInst,//当前应用程序的实例句柄
		          (LPVOID)NULL);	//没有附加数据，为NULL
          
	          if (!m_hWnd)
	          {
		          printf("CreateWindowEx failed");
		          return 1;
	          }
	          RegKeyboardRawInput(m_hWnd);
	          //消息循环
	          MSG msg;
	          while (GetMessage(&msg, (HWND)NULL, 0, 0))
	          {
		          TranslateMessage(&msg);
		          DispatchMessage(&msg);
	          }
          
	          return 0;
          }
          //LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
          // 自定义消息处理函数的实现
          LRESULT CALLBACK WindowProc(_In_  HWND hwnd,_In_  UINT uMsg,_In_  WPARAM wParam,_In_  LPARAM lParam)
          {
	          switch (uMsg)
	          {
	          case WM_COMMAND:
		          break;
	          case WM_PAINT:
		          break;
	          case WM_DESTROY:
		          PostQuitMessage(0);
		          break;
	          case WM_INPUT:
	          {
		          UINT dwSize = 0;
		          GetRawInputData((HRAWINPUT)lParam, (UINT)RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER));//取数据,第一次调用函数将获取需要的字节大小
		          LPBYTE lpbBuffer = new BYTE[dwSize];//分配指定的缓冲区大小
		          GetRawInputData((HRAWINPUT)lParam, (UINT)RID_INPUT, (LPVOID)lpbBuffer, (PUINT)&dwSize, (UINT)sizeof(RAWINPUTHEADER));//第二次调用获取原始输入数据，读入lpbBuffer
          
		          RAWINPUT * raw = (RAWINPUT *)lpbBuffer;
		          if (raw->header.dwType == RIM_TYPEKEYBOARD)//这里可以截获所有键盘信息,如需区分不同的键盘输入信息,可以通过设备句柄判断。
		          {
			          if (raw->data.keyboard.Message == WM_KEYUP)
			          {
				          //读取键盘名称
				          UINT bufferSize;//为键盘设备名准备缓冲区大小
				          GetRawInputDeviceInfo(raw->header.hDevice, RIDI_DEVICENAME, NULL, &bufferSize);
				          WCHAR* RawDevName = new WCHAR[bufferSize];
				          GetRawInputDeviceInfo(raw->header.hDevice, RIDI_DEVICENAME, RawDevName, &bufferSize);//将设备名读入缓冲区RawDevName
				          printf("dev=%s\n", RawDevName);
				          delete[] RawDevName;
				          //获取键盘输入
				          char keytext[10] = { 0 };
				          BYTE state[256] = { 0 };
				          ToAscii(raw->data.keyboard.VKey, raw->data.keyboard.MakeCode, state, (LPWORD)keytext, 0);//通过虚拟键盘码得到名字
				          printf("key=%s\n", keytext);
				          printf("Kbd: make = % 04x Flags : % 04x Reserved : % 04x ExtraInformation : % 08x, msg = % 04x VK = % 04x \n",
					          raw->data.keyboard.MakeCode,
					          raw->data.keyboard.Flags,
					          raw->data.keyboard.Reserved,
					          raw->data.keyboard.ExtraInformation,
					          raw->data.keyboard.Message,
					          raw->data.keyboard.VKey);
			          }
		          }
		          delete[] lpbBuffer;
		          return 0;
	          }
	          default:
		          return DefWindowProc(hwnd, uMsg, wParam, lParam);
	          }
	          return 0;
          }
------------------------------------------------------------------------------------------------------------------
          #include <Windows.h>
          #include <iostream>
          
          std::string get_time()
          {
	          SYSTEMTIME sys;
	          GetLocalTime(&sys);
	          char time[255] = { 0 };
	          sprintf_s(time, "[%4d/%02d/%02d %02d:%02d:%02d]", sys.wYear, sys.wMonth, sys.wDay, sys.wHour, sys.wMinute, sys.wSecond);
          
	          return std::string(time);
          }
          
          LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
          {
	          /*
	          typedef struct tagKBDLLHOOKSTRUCT {
	          DWORD     vkCode;		// 按键代号
	          DWORD     scanCode;		// 硬件扫描代号，同 vkCode 也可以作为按键的代号。
	          DWORD     flags;		// 事件类型，一般按键按下为 0 抬起为 128。
	          DWORD     time;			// 消息时间戳
	          ULONG_PTR dwExtraInfo;	// 消息附加信息，一般为 0。
	          }KBDLLHOOKSTRUCT,*LPKBDLLHOOKSTRUCT,*PKBDLLHOOKSTRUCT;
	          */
	          KBDLLHOOKSTRUCT* ks = (KBDLLHOOKSTRUCT*)lParam;		// 包含低级键盘输入事件信息
	          char buffer[255];
	          DWORD code = ks->vkCode;
	          std::string t = get_time();
	          char state[20];
          
	          if (wParam == WM_KEYDOWN)
	          {
		          strcpy_s(state, "按下");
	          }
	          else if (wParam == WM_KEYUP)
	          {
		          strcpy_s(state, "抬起");
	          }
          
	          sprintf_s(buffer, "[键盘]%s 键代码:%d %s", t.c_str(), code, state);
              if (code==65)keybd_event(0x42,0,(ks->flags&128)/64,0);
	          std::cout << buffer << std::endl;
          
	          //return 1;	// 拦截消息
	          return CallNextHookEx(NULL, nCode, wParam, lParam);
          }
          
          LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
          {
	          /*
	          typedef struct tagMOUSEHOOKSTRUCT {
		          POINT   pt;					// Point数据
		          HWND    hwnd;				// 接收鼠标消息的窗体的句柄
		          UINT    wHitTestCode;		// 指定点击测试值
		          ULONG_PTR dwExtraInfo;		// 指定和该消息相关联的附加信息。
	          } MOUSEHOOKSTRUCT, FAR* LPMOUSEHOOKSTRUCT, * PMOUSEHOOKSTRUCT;
	          */
          
	          MOUSEHOOKSTRUCT* ms = (MOUSEHOOKSTRUCT*)lParam;
	          POINT pt = ms->pt;
	          std::string time = get_time();
	          char buffer[1024];
	          char state[20] = "未知";
          
	          if (wParam == WM_LBUTTONDOWN)
	          {
		          strcpy_s(state, "左键按下");
	          }
	          else if (wParam == WM_LBUTTONUP)
	          {
		          strcpy_s(state, "左键抬起");
	          }
	          else if (wParam == WM_RBUTTONDOWN)
	          {
		          strcpy_s(state, "右键按下");
	          }
	          else if (wParam == WM_RBUTTONUP)
	          {
		          strcpy_s(state, "右键抬起");
	          }
	          else if (wParam == WM_MOUSEMOVE)
	          {
		          strcpy_s(state, "移动");
	          }
          
	          sprintf_s(buffer, "[鼠标]%s 键代码:x:%d y:%d %s", time.c_str(), pt.x, pt.y, state);
	          std::cout << buffer << std::endl;
          
	          //return 1;	// 拦截消息
	          return CallNextHookEx(NULL, nCode, wParam, lParam);
          
          }
          
          int main()
          {
	          HINSTANCE hM = GetModuleHandle(NULL), hK = GetModuleHandle(NULL);
	          HHOOK g_Hook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, hK, 0);
	          HHOOK g_Hook2 = SetWindowsHookEx(WH_MOUSE_LL, MouseProc, hM, 0);
          
	          // 消息循环
	          MSG msg;
	          while (GetMessage(&msg, NULL, 0, 0))
	          {
		          TranslateMessage(&msg);
		          DispatchMessage(&msg);
	          }
          
	          UnhookWindowsHookEx(g_Hook);
          
	          return 0;
          }
···