Как в Delphi в обработчике `OnMouseMove` определить источник события относительный или абсолютный (мышь или планшет)?
У моего ПК есть 2 устройства ввода с которыми я работаю попеременно - мышь и графический планшет (Wacom). Мышь, как известно, генерирует относительные координаты (типа X +5 Y +1), а планшет - абсолютные (типа X 567 Y 876). Windows их берет и перед отдачей в приложение преобразует в абсолютные.
Как мне в обработчике события OnMouseMove определить от какого из устройств пришло событие и, соответственно, были ли порождены новые координаты относительным или абсолютным устройством ввода? А в идеале - увидеть "сырое" событие от устройства ввода.
Видел что есть флаги ssTouch ssPen в TShiftState добавляемые MouseOriginToShiftState (которая внутри вызывает GetMessageExtraInfo MSDN), но похоже, что они относятся к планшетным ПК, а не к отдельным планшетам как устройству ввода.
Также слышал что есть RAWMOUSE structure (winuser.h) и MOUSE_MOVE_RELATIVE / MOUSE_MOVE_ABSOLUTE (MSDN), но как им пользоваться пока не понятно.
Ответы (1 шт):
Зарегистрировать слушателя "сырых" событий (иначе они не приходят) и подписаться на получение событий:
procedure RegisterListener; const // https://docs.microsoft.com/en-us/windows-hardware/drivers/hid/hid-usages#usage-page HID_USAGE_PAGE_GENERIC = 1; HID_USAGE_PAGE_GAME = 5; HID_USAGE_PAGE_LED = 8; HID_USAGE_PAGE_BUTTON = 9; // https://docs.microsoft.com/en-us/windows-hardware/drivers/hid/hid-usages#usage-id HID_USAGE_GENERIC_MOUSE = 2; HID_USAGE_GENERIC_KEYBOARD = 6; var rid: tagRAWINPUTDEVICE; begin // To receive WM_INPUT messages, an application must first register the raw input devices using RegisterRawInputDevices. // By default, an application does not receive raw input. rid.usUsagePage := HID_USAGE_PAGE_GENERIC; rid.usUsage := HID_USAGE_GENERIC_MOUSE; rid.dwFlags := 0; rid.hwndTarget := 0; // If NULL it follows the keyboard focus RegisterRawInputDevices(@rid, 1, SizeOf(rid)); end; procedure TForm1.FormCreate(Sender: TObject); begin RegisterListener; Application.OnMessage := OnMessage; end;Теперь в обработчике событий можно получить "сырые" данные, из которых узнать детали ввода. Сам ввод тут лучше не брать, т.к. он не облагорожен (например, для планшета x/y координаты не спроецированы на монитор, а от 0 до 65535, также, если я верно понимаю документацию, не применены ускорения курсора и т.п.).
procedure TForm1.OnMessage(var aMsg: TMsg; var aHandled: Boolean); var dwSize: Cardinal; ri: tagRAWINPUT; begin if aMsg.message = WM_INPUT then begin GetRawInputData(HRAWINPUT(aMsg.lParam), RID_INPUT, nil, dwSize, SizeOf(RAWINPUTHEADER)); if dwSize = 0 then ShowMessage('Can not allocate memory'); if GetRawInputData(HRAWINPUT(aMsg.lParam), RID_INPUT, @ri, dwSize, SizeOf(RAWINPUTHEADER)) <> dwSize then ShowMessage('GetRawInputData doesn''t return correct size!'); // https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-rawinputdevicelist // https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-rawmouse if ri.header.dwType = RIM_TYPEMOUSE then begin if ri.mouse.usFlags and $1 = 0 then fLastInputMode := imRelative else fLastInputMode := imAbsolute; end; end; end;Теперь имея флаг
fLastInputModeможно в обработчикеPanel1MouseMoveменять логику в зависимости от типа:procedure TForm1.Panel1MouseMove(Sender: TObject; Shift: TShiftState; X,Y: Integer); var origin: TPoint; begin case fLastInputMode of imRelative: begin // Stick mouse cursor to start location, otherwise it stucks at screen border origin := TControl(Sender).ClientToScreen(Point(fOriginCursorX, fOriginCursorY)); SetCursorPos(origin.X, origin.Y) end; imAbsolute: begin // Update tablet cursor origin, otherwise its offset will grow exponentially fOriginCursorX := X; fOriginCursorY := Y; end; end;
Методы WinApi и описания структур данных легко найти на гитхабе, тут их не привожу. Если интересен тестовый проект - https://github.com/Kromster80/raw_input_test/