====== Using AutoHotkey to make a second mouse into a multimedia control ====== Using a rather fancy AutoHotKey recipe, we can use an extra mouse hooked to the system to act as a multimedia report (or whatever we want). This is a bit tricky, since Windows treats all mice as the same by default. To get around this, we use the [[http://msdn.microsoft.com/en-us/library/windows/desktop/ms645536(v=vs.85).aspx|Microsoft API for raw input]]. This was inspired by (and somewhat copied from) [[http://www.autohotkey.com/board/topic/44468-eithermouse-0581-not-just-for-leftys-anymore/|EitherMouse]] and [[http://www.autohotkey.com/board/topic/26967-using-multiple-mice-to-control-one-computer/|this guy's forum post]]. Note that this does NOT use Michael Simon's [[http://www.autohotkey.net/~Micha/HIDsupport/Autohotkey.html|AutoHotKey HID support]] package. To use, install the code below in AutoHotKey. Press Ctrl+Win+M then wiggle/click the "target mouse" to find out its name. Plug the name into the 'target_mouse' line. You can then change what each button does by editing the Send commands in WM_INPUT. The code works by: - Hooking all mouse events into WM_INPUT via RegisterRawInputDevices API. - Using the GetRawInputData to get the call detail, and GetRawInputDeviceInfo to find the name of the mouse responsible. - If the target mouse sent the event, then we (1) squelch the next mouse event that would be caught by the normal Windows event system, and (2) send another key sequence instead. - Mouse squelching is done by hooking all the button events using normal AutoHotKey syntax, then forwarding the event if and only if squelch_mouse is off. If squelch_mouse is on, it is turned off by the event, so subsequent clicks WILL be received (e.g. from the regular mouse). This supresses the button events -- to supress the cursor movement, I suggest masking tape. ; === Using AutoHotkey to make a second mouse into a multimedia control === ; ; Using a rather fancy AutoHotKey recipe, we can use an extra mouse hooked to the system ; to act as a multimedia report (or whatever we want). This is a bit tricky, since ; Windows treats all mice as the same by default. To get around this, we use the ; Microsoft API for raw input. ; ; This was inspired by (and somewhat copied from) EitherMouse and this guy's forum post. ; Note that this does NOT use Michael Simon's AutoHotKey HID support package. ; ; To use, install the code below in AutoHotKey. Press Ctrl+Win+M then wiggle/click the ; "target mouse" to find out its name. Plug the name into the 'target_mouse' line. You ; can then change what each button does by editing the Send commands in WM_INPUT. ; ; The code works by: ; ; 1. Hooking all mouse events into WM_INPUT via RegisterRawInputDevices API. ; 2. Using the GetRawInputData to get the call detail, and GetRawInputDeviceInfo to find the ; name of the mouse responsible. ; 3. If the target mouse sent the event, then we (1) squelch the next mouse event that would ; be caught by the normal Windows event system, and (2) send another key sequence instead. ; 4. Mouse squelching is done by hooking all the button events using normal AutoHotKey syntax, ; then forwarding the event if and only if squelch_mouse is off. If squelch_mouse is on, ; it is turned off by the event, so subsequent clicks WILL be received (e.g. from the ; regular mouse). ; ; This supresses the button events -- to supress the cursor movement, I suggest masking tape. ; RegisterRawInputDevice(1, 2, A_ScriptHWnd) ; Listen to all mouse events target_mouse := "\\?\HID#VID_046D&PID_C52F&MI_00#8&569199f&0&0000#{378de44c-56ef-11d1-bc8c-00a0c91405dd}" ; Wrapper for the RegisterRawInputDevices API call RegisterRawInputDevice(UsagePage, Usage, hwnd) { VarSetCapacity(dev, 12, 0) NumPut(UsagePage, dev, 0, "UShort") NumPut(Usage, dev, 2, "UShort") NumPut(0x100, dev, 4) ; dwFlags = RIDEV_INPUTSINK (don't require foreground) NumPut(hwnd, dev, 8) if DllCall("RegisterRawInputDevices" , "uint", &dev ; pRawInputDevices (pointer to an array of RAWINPUTDEVICE) , "uint", 1 ; uiNumDevices , "uint", 12) ; cbSize (size of a RAWINPUTDEVICE structure) OnMessage(0xFF, "WM_INPUT") } ; Ctrl+Win+M -- find out the target mouse's name for pasting at the top of this script. #^m:: global report_mouse MsgBox, % "Click OK, then click or wiggle the mouse you wish to capture to get it's name." report_mouse:=1 squelch_mouse:=0 ; Silently eat the next mouse event (used so our target mouse doesn't act like an actual mouse, while the normal mice are unaffected) ; API hook for raw device events WM_INPUT(wParam, lParam) { Critical Global target_mouse, squelch_mouse ; Get required buffer size. -- http://msdn.microsoft.com/en-us/library/windows/desktop/ms645596(v=vs.85).aspx DllCall("GetRawInputData", "uint", lParam, "uint", 0x10000003, "uint", 0, "uint*", size, "uint", 16) VarSetCapacity(raw, size, 0) ; Get raw input data from handle (lParam) -- http://msdn.microsoft.com/en-us/library/windows/desktop/ms645596(v=vs.85).aspx ret := DllCall("GetRawInputData", "uint", lParam, "uint", 0x10000003, "uint", &raw, "uint*", size, "uint", 16, "int") if (ErrorLevel or ret = -1) return type := NumGet(raw, 0) ; -- http://msdn.microsoft.com/en-us/library/windows/desktop/ms645571(v=vs.85).aspx ; http://msdn.microsoft.com/en-us/library/windows/desktop/ms645581(v=vs.85).aspx if (type != 0) ; RIM_TYPEMOUSE -- ignore non-mouse events (though we shouldn't get any) return hDevice := NumGet(raw, 8) ; -- http://msdn.microsoft.com/en-us/library/windows/desktop/ms645571(v=vs.85).aspx name := get_mouse_name(hDevice) ; returns NULL if from a simulated source if (name and report_mouse) { MsgBox, Paste the following at the top of the script to set the target mouse:`n`ntarget_mouse="%name%"`n`nYou can press Ctrl+C to copy this message. report_mouse := 0 return } is_target := name = target_mouse ;usFlags := NumGet(raw,16, "USHORT") usButtonFlags := NumGet(raw,16+4, "USHORT") usButtonData := NumGet(raw,16+6, "SHORT") ;FileAppend, % is_target " " usButtonFlags " " usButtonData " " name "`n", mouse-log.txt ;if (is_target) ;FileAppend, % is_target " " usButtonFlags " " usButtonData " " name "`n", mouse-log.txt ; LMB usButtonFlags=1,2 ; RMB usButtonFlags=4,8 ; MMB usButtonFlags=16,32 ; Wheel usButtonFlags=1024 -- down usButtonData<0, up usButtonData>0 ; Tilt usButtonFlags=2048 -- left usButtonData<0, right usButtonData>0 -- repeats typematically if (is_target) { if (usButtonFlags & 1 or usButtonFlags & 4 or usButtonFlags & 16) { ; LMB/MMB/RMB down squelch_mouse := 1 ; ignore the next mouse even, since it will be this event, which comes from the target mouse } if (usButtonFlags & 2) { ; LMB up squelch_mouse := 1 Send {Media_Play_Pause} } if (usButtonFlags & 8) { ; RMB up squelch_mouse := 1 ; NOTHING -- this is the button my dog ate } if (usButtonFlags & 32) { ; MMB up squelch_mouse := 1 Send {Media_Stop}#q ; Stop *AND* toggle shuffle } if (usButtonFlags & 1024 && usButtonData<0) { ; Wheel down squelch_mouse := 1 Send {Volume_Down} } if (usButtonFlags & 1024 && usButtonData>0) { ; Wheel up squelch_mouse := 1 Send {Volume_up} } if (usButtonFlags & 2048 && usButtonData<0) { ; Wheel left squelch_mouse := 1 Send {Media_Prev} } if (usButtonFlags & 2048 && usButtonData>0) { ; Wheel right squelch_mouse := 1 Send {Media_Next} } } } $LButton:: if (squelch_mouse) squelch_mouse:=0 else Send {LButton down} return $LButton up:: if (squelch_mouse) squelch_mouse:=0 else Send {LButton up} return $RButton:: if (squelch_mouse) squelch_mouse:=0 else Send {RButton down} return $RButton up:: if (squelch_mouse) squelch_mouse:=0 else Send {RButton up} return $MButton:: if (squelch_mouse) squelch_mouse:=0 else Send {MButton down} return $MButton up:: if (squelch_mouse) squelch_mouse:=0 else Send {MButton up} return $WheelDown:: if (squelch_mouse) squelch_mouse:=0 else Send {WheelDown} return $WheelUp:: if (squelch_mouse) squelch_mouse:=0 else Send {WheelUp} return $WheelLeft:: if (squelch_mouse) squelch_mouse:=0 else Send {WheelLeft} return $WheelRight:: if (squelch_mouse) squelch_mouse:=0 else Send {WheelRight} return get_mouse_name(h) { DllCall("GetRawInputDeviceInfo",Int,h,UInt,0x20000007,Int,0,"UInt*",l) VarSetCapacity(Name,l*2+2) DllCall("GetRawInputDeviceInfo",Int,h,UInt,0x20000007,Str,Name,"UInt*",l) Return Name } ; ===== END aux mouse multimedia control =====