MSDN.WhiteKnight - Stack Overflow answers
Ответ на "Получение всей информации о всех взаимодействиях с мышью"
Answer 827498
Для USB-мышей в Windows лог событий можно получить с помощью механизма Event Tracing. Этот механизм позволяет собирать события от определенных источников (провайдеров) и сохранять их в файл журнала событий (.etl). Этот файл потом можно открыть с помощью стандартной оснастки просмотра событий Windows или специализированного софта типа Network Monitor, либо прочитать программно с помощью того же API.
Отслеживать передаваемые данные USB-устройств позволяют провайдеры Microsoft-Windows-USB-USBPORT (USB 2.0, введен в Windows 7) и Microsoft-Windows-USB-UCX (USB 3.0, введен в Windows 8); USB-мышь отправляет данные по протоколу HID, приблизительную структуру ее пакета можно посмотреть здесь. Для работы с Event Tracing в .NET существует библиотека Microsoft.Diagnostics.Tracing.TraceEvent.
Для запуска сеансов отслеживания требуются права администратора, но для чтения сохраненных логов они не обязательны. Запуск/остановку сеансов отслеживания можно также производить не из кода, а с помощью утилиты командной строки logman. Большой плюс способа в том, что сеанс отслеживания управляется самой системой и работает независимо от программы, т.е. можно закрыть программу и продолжать отслеживание (параметр StopOnDispose = false), пока сеанс не будет явно остановлен (программно или через logman / системный монитор). При желании можно настроить и так, чтобы отслеживание начиналось сразу при загрузке системы. Недостаток в том, что данные по движению мыши приходят не в пикселях, а в аппаратных единицах, и преобразовать их в пиксельные координаты довольно сложно.
Пример кода отслеживания HID-пакетов для клавиатуры есть здесь. Модифицируем его, так чтобы можно было сохранять события в файл и добавим обработку мышиных пакетов, получаем что-то такое:
using System; using System.IO; using System.Linq; using System.Collections.Generic; using Microsoft.Diagnostics.Tracing; using Microsoft.Diagnostics.Tracing.Session; using System.Threading; namespace EtwHidlogger { public interface IPrint { void Print(string text); } public class KeyMap { public static string[] GetKey(int value) { switch (value) { case 0x00: return new string[1]; // unused case 0x04: return new[] { "a", "A" }; case 0x05: return new[] { "b", "B" }; case 0x06: return new[] { "c", "C" }; case 0x07: return new[] { "d", "D" }; case 0x08: return new[] { "e", "E" }; case 0x09: return new[] { "f", "F" }; case 0x0A: return new[] { "g", "G" }; case 0x0B: return new[] { "h", "H" }; case 0x0C: return new[] { "i", "I" }; case 0x0D: return new[] { "j", "J" }; case 0x0E: return new[] { "k", "K" }; case 0x0F: return new[] { "l", "L" }; case 0x10: return new[] { "m", "M" }; case 0x11: return new[] { "n", "N" }; case 0x12: return new[] { "o", "O" }; case 0x13: return new[] { "p", "P" }; case 0x14: return new[] { "q", "Q" }; case 0x15: return new[] { "r", "R" }; case 0x16: return new[] { "s", "S" }; case 0x17: return new[] { "t", "T" }; case 0x18: return new[] { "u", "U" }; case 0x19: return new[] { "v", "V" }; case 0x1A: return new[] { "w", "W" }; case 0x1B: return new[] { "x", "X" }; case 0x1C: return new[] { "y", "Y" }; case 0x1D: return new[] { "z", "Z" }; case 0x1E: return new[] { "1", "!" }; case 0x1F: return new[] { "2", "@" }; case 0x20: return new[] { "3", "#" }; case 0x21: return new[] { "4", "$" }; case 0x22: return new[] { "5", "%" }; case 0x23: return new[] { "6", "^" }; case 0x24: return new[] { "7", "&" }; case 0x25: return new[] { "8", "*" }; case 0x26: return new[] { "9", "(" }; case 0x27: return new[] { "0", ")" }; case 0x28: return new[] { "[RET]" }; case 0x29: return new[] { "[ESC]" }; case 0x2A: return new[] { "[DEL]" }; case 0x2B: return new[] { "[TAB]" }; case 0x2C: return new[] { "[SPACE]" }; case 0x2D: return new[] { "-", "_" }; case 0x2E: return new[] { "=", "+" }; case 0x2F: return new[] { "[", "{" }; case 0x30: return new[] { "]", "}" }; case 0x31: return new[] { "\\", "|" }; case 0x32: return new[] { "#", "~" }; //non-US case 0x33: return new[] { ";", ":" }; case 0x34: return new[] { "\'", "\"" }; case 0x35: return new[] { "`", "~" }; case 0x36: return new[] { ";", "<" }; case 0x37: return new[] { ".", ">" }; case 0x38: return new[] { "/", "?" }; case 0x39: return new[] { "[CAPS]" }; case 0x3A: return new[] { "[F1]" }; case 0x3B: return new[] { "[F2]" }; case 0x3C: return new[] { "[F3]" }; case 0x3D: return new[] { "[F4]" }; case 0x3E: return new[] { "[F5]" }; case 0x3F: return new[] { "[F6]" }; case 0x40: return new[] { "[F7]" }; case 0x41: return new[] { "[F8]" }; case 0x42: return new[] { "[F9]" }; case 0x43: return new[] { "[F10]" }; case 0x44: return new[] { "[F11]" }; case 0x45: return new[] { "[F12]" }; case 0x46: return new[] { "[PRT]" }; case 0x47: return new[] { "[SCL]" }; case 0x48: return new[] { "[PAU]" }; case 0x49: return new[] { "[INS]" }; case 0x4A: return new[] { "[HOME]" }; case 0x4B: return new[] { "[P-UP]" }; case 0x4C: return new[] { "[FWD]" }; case 0x4D: return new[] { "[END]" }; case 0x4E: return new[] { "[P-DN]" }; case 0x4F: return new[] { "[RT]" }; case 0x50: return new[] { "[LT]" }; case 0x51: return new[] { "[DN]" }; case 0x52: return new[] { "[UP]" }; case 0x53: return new[] { "[NUM]", "[CLR]" }; case 0x54: return new[] { "/" }; case 0x55: return new[] { "*" }; case 0x56: return new[] { "-" }; case 0x57: return new[] { "+" }; case 0x58: return new[] { "[ENTER]" }; case 0x59: return new[] { "1", "[END]" }; case 0x5A: return new[] { "2", "[DN]" }; case 0x5B: return new[] { "3", "[P-DN]" }; case 0x5C: return new[] { "4", "[LT]" }; case 0x5D: return new[] { "5" }; case 0x5E: return new[] { "6", "[RT]" }; case 0x5F: return new[] { "7", "[HOME]" }; case 0x60: return new[] { "8", "[UP]" }; case 0x61: return new[] { "9", "[P-UP]" }; case 0x62: return new[] { "0", "[INS]" }; case 0x63: return new[] { ".", "[DEL]" }; case 0x64: return new[] { "\\", "|" }; //non-US default: return null; } } } /*Represents a block of data transferred via USB*/ public class UsbData { public byte usbver; //usb version (2.0 or 3.0) public byte[] data; //transferred bytes public ulong hndl; //device handle public DateTime time; //timestamp public uint vid; //Vendor ID (2.0 only) public uint pid; //Product ID (2.0 only) public uint datalen; //amount of bytes transferred public UsbData(DateTime inputTime, ulong inputHndl, byte[] inputData) { time = inputTime; data = inputData; hndl = inputHndl; } } /*A class to manage Event tracing for Windows sessions*/ public class UsbEventSource { // Microsoft-Windows-USB-UCX (usb3.0) private static Guid UsbUcx = new Guid("36DA592D-E43A-4E28-AF6F-4BC57C5A11E8"); // Microsoft-Windows-USB-USBPORT (usb2.0) private static Guid UsbPort = new Guid("C88A4EF5-D048-4013-9408-E04B7DB2814A"); private static string sessionName = "UsbKeylog"; public static TraceEventSession session; public static IPrint Instance; //object to display things on GUI /// <summary> /// Starts new event tracing session /// </summary> /// <param name="newSessionName">Session name</param> /// <param name="filename">Path where to save event log file</param> public static void StartCapture(string newSessionName = null, string filename = "log.etl") { if (session != null) StopCapture(); if (newSessionName != null) sessionName = newSessionName; session = new TraceEventSession( sessionName, filename); session.EnableProvider(UsbUcx); session.EnableProvider(UsbPort); } /// <summary> /// Stops event tracing session /// </summary> public static void StopCapture() { if (session != null) { session.Stop(); session.Dispose(); session = null; } } private static Dictionary<string, string> _expose(object hidden) { string str = hidden.ToString(); char[] separators = { '{', '}', ',' }; char[] remove = { ' ', '"' }; string[] s = str.Split(separators, StringSplitOptions.RemoveEmptyEntries); Dictionary<string, string> item = s.ToDictionary(x => x.Split('=')[0].Trim(), x => x.Split('=')[1].Trim(remove)); return item; } //Gets item with certain name from event data private static object GetItem(TraceEvent eventData, string item) { object value = null; int pIndex = eventData.PayloadIndex(item); if (pIndex < 0) return value; try { value = eventData.PayloadValue(pIndex); } catch (ArgumentOutOfRangeException) { } return value; } //Parses usb event data private static UsbData GetData(TraceEvent eventData) { ulong hndl; object field; uint vid=0,pid=0; byte usbver = 0; //try to determine device handle and IDs field = GetItem(eventData, "fid_USBPORT_Device"); if (field != null) { Dictionary<string, string> deviceInfo = _expose(field); if (!ulong.TryParse(deviceInfo["DeviceHandle"], out hndl) && hndl <= 0) return null; vid = UInt32.Parse(deviceInfo["idVendor"]); pid = UInt32.Parse(deviceInfo["idProduct"]); } else { hndl = (ulong)GetItem(eventData, "fid_PipeHandle"); if (hndl <= 0) return null; } //try to get event parameters field = GetItem(eventData, "fid_USBPORT_URB_BULK_OR_INTERRUPT_TRANSFER"); //2.0 usbver = 2; if (field == null) { field = GetItem(eventData, "fid_UCX_URB_BULK_OR_INTERRUPT_TRANSFER"); //3.0 usbver = 3; } Dictionary<string, string> urb = _expose(field);//transform parameter string to dictionary //determine transferred data length int xferDataSize = 0; if (!int.TryParse(urb["fid_URB_TransferBufferLength"], out xferDataSize)) return null; if ((xferDataSize > 8) && (usbver == 2)) xferDataSize = 8; //USB 2.0 sometimes gives wrong size if (xferDataSize > 8) return null; //data is too large for mouse / keyboard byte[] data2=eventData.EventData(); byte[] xferData = new byte[xferDataSize]; Array.Copy(data2, eventData.EventDataLength - xferDataSize, xferData, 0, xferDataSize); bool HasNonZero = false; for (int i = 0; i < xferDataSize; i++) if (xferData[i] != 0) { HasNonZero = true; break; } if (HasNonZero == false) return null; //data is empty /* Construct UsbData object*/ UsbData data = new UsbData(eventData.TimeStamp, hndl, xferData); data.usbver = usbver; data.datalen = (uint)xferDataSize; data.vid = vid; data.pid = pid; return data; } //Method called on new event public static void EventCallback(TraceEvent eventData) { if (eventData.EventDataLength <= 0) return; UsbData usbdata = null; string output = ""; try { if (eventData.PayloadNames.Contains("fid_USBPORT_URB_BULK_OR_INTERRUPT_TRANSFER") || eventData.PayloadNames.Contains("fid_UCX_URB_BULK_OR_INTERRUPT_TRANSFER")) usbdata = GetData(eventData); if (usbdata == null) return; string idstr=""; if (usbdata.usbver == 2) { idstr = String.Format("VID_0x{0} PID_0x{1}", usbdata.vid.ToString("X4"), usbdata.pid.ToString("X4")); } //determine what device data comes from and actual length of data (for USB 2.0) bool IsMouse = false; uint len = usbdata.datalen; //(mouse data is 4 or 5 bytes, keyboard - 8 bytes) if (usbdata.datalen < 8) { IsMouse = true; len = usbdata.datalen; } else { if (usbdata.usbver == 2 && usbdata.data[0] == 0 && usbdata.data[1] == 0 && usbdata.data[2] == 4 && usbdata.data[3] == 0) { IsMouse = true; len = 4; } else if (usbdata.usbver == 2 && usbdata.data[0] == 5 && usbdata.data[1] == 0) { IsMouse = true; len = 5; } else IsMouse = false; } //Print data if (IsMouse == false && usbdata.data[1]==0) //second byte must be zero for keyboard { var arr = ParseKeys(usbdata.data); output = String.Format("{0} {1} Keyboard ", usbdata.time, idstr); if (arr != null) foreach (var s in arr) output += s + " "; } else { sbyte b; output = String.Format("{0} {1} Mouse ",usbdata.time,idstr); uint i_start = usbdata.datalen - len; uint index; bool action = false; for (uint i = i_start; i < usbdata.datalen; i++) { index = i - i_start; b = (sbyte)usbdata.data[i]; switch (index) { case 0: //first byte defines pressed buttons if ((usbdata.data[i] & 0x01) > 0) {output+= "Left button press ";action=true;} if ((usbdata.data[i] & 0x02) > 0) {output += "Right button press ";action=true;} if ((usbdata.data[i] & 0x04) > 0) {output += "Middle button press ";action=true;} if ((usbdata.data[i] & 0x08) > 0) {output += "Special button press ";action=true;} if ((usbdata.data[i] & 0xf0) > 0) { output += "Special button press ";action = true; } break; case 1: //second byte is x movement if (usbdata.data[i] != 0 || usbdata.data[i + 1] != 0) { output += "move: "; action = true; } if (usbdata.data[i] != 0) output += "dx=" + b.ToString() + " "; break; case 2: //third byte is y movement if (usbdata.data[i] != 0) output += "dy=" + b.ToString() + " "; break; case 3: //4th byte (if present) is wheel movement if (usbdata.data[i] != 0) { output += "Wheel Move: Delta=" + b.ToString() + " "; action = true; } break; } /*output += b.ToString() + " ";*/ } if (!action) output += "Button release"; } } catch (Exception ex) { output = "Error in callback: "+ex.GetType().ToString()+" "+ex.Message; } if(output!="")Instance.Print(output); //datastore.Add(usbdata); } private static string[] ParseKeys(byte[] bytes) { string[] result = new string[2]; result[0] = BitConverter.ToString(bytes).Replace("-", " "); // modifiers: // CTL = 1 // SFT = 2 // ALT = 4 byte modByte = bytes[0]; byte noneByte = bytes[1]; byte[] dataBytes = new byte[6]; Array.Copy(bytes, 2, dataBytes, 0, 6); string[] fullKeyStroke = new string[6]; // convert usageId -> usageName for (int i = 0; i < fullKeyStroke.Length; i++) { int usageId = dataBytes[i]; string[] key = KeyMap.GetKey(usageId); // skip unmapped data if (key == null) return null; // empty data (usageId == 0) if (key.SequenceEqual(new string[1])) { fullKeyStroke[i] = ""; continue; } // [SFT] if ((modByte & 0x22) != 0) fullKeyStroke[i] = key.Last(); else fullKeyStroke[i] = key.First(); } string parsed = ""; if ((modByte & 0x11) != 0) parsed += "[CTL] "; if ((modByte & 0x44) != 0) parsed += "[ALT] "; parsed += String.Join(" ", fullKeyStroke); result[1] = parsed.Trim(); return result; } } }
Пример использования:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Text; using System.Windows.Forms; using EtwHidlogger; using Microsoft.Diagnostics.Tracing; using Microsoft.Diagnostics.Tracing.Session; namespace HidLogger { public partial class Form1 : Form, IPrint { StringBuilder sb; public void Print(string text) { if (sb == null) sb = new StringBuilder(50000); if (text == null) return; this.Invoke((MethodInvoker)(() => { sb.AppendLine(text); })); } public Form1() { InitializeComponent(); UsbEventSource.Instance = this; } //запуск сеанса отслеживания private void bStartCapture_Click(object sender, EventArgs e) { string session = tbSessionName.Text; try { UsbEventSource.StartCapture(session, tbFile.Text); MessageBox.Show("Capture sesion is started"); } catch (Exception ex) { tbEvents.Text = ex.ToString(); } } //остановка сеанса отслеживания private void bStopCapture_Click(object sender, EventArgs e) { try { UsbEventSource.StopCapture(); MessageBox.Show("Capture sesion is stopped"); } catch (Exception ex) { tbEvents.Text = ex.ToString(); } } //чтение сохраненных событий из .etl-файла private void bViewEvents_Click(object sender, EventArgs e) { tbEvents.Text = ""; sb = new StringBuilder(50000); try { this.Cursor = Cursors.WaitCursor; ETWTraceEventSource src = new ETWTraceEventSource(tbFile.Text); src.Dynamic.All += UsbEventSource.EventCallback; src.Process(); string s; if (sb.Length <= 100000) s = sb.ToString(); else s = sb.ToString().Substring(0, 99999) + Environment.NewLine + "(some text trimmed)"; tbEvents.Text = s; } catch (Exception ex) { tbEvents.Text = ex.ToString(); } finally { this.Cursor = Cursors.Arrow; } } } }
Content is retrieved from StackExchange API.
Auto-generated by ruso-archive tools.