Microsoft Surface Pro 3 pen barrel button customization
Posted: 12 May 2015, 08:20
Microsoft provides no way to customize the two barrel buttons on the Surface Pro 3 pen. Many users have been searching for a way, and the person behind http://radialmenu.weebly.com/ did find a way, but the customization is embedded opaquely within a larger project. I have been search to find a more direct mechanism, suitable for use in AutoHotkey scripts, and here is the result.
A bit of background first. The pen has two buttons on the barrel. If the upper button is pressed and the screen is tapped, AutoHotkey sees a right-mouse-button click, and no special code is required to isolate this. If neither button is pressed, AutoHotkey sees a left-mouse-button click, and again no special code is required. The problem is that if the bottom button is pressed and the screen is tapped, then AutoHotkey sees again a normal left-mouse-button click, identical to the case when the bottom button is not pressed. This script intends to isolate these three cases so that they may be easily customized.
The script is interested only in trappng taps of the pen on the screen. I do not use inking, and do not know how the script interacts with inking. I don't exploit hovering, and so have not really considered hovering. But the script does allow the state of the buttons to be detected when the screen is tapped with the pen.
The sample script below is illustrative. It isolates plain taps and taps with concurrent CTRL, ALT, and CTRL+ALT keyboard button presses. Only as many combinations as are useful need be supplied; additional combinations, using SHIFT and WIN and using the left and right variants of all of these, may be defined if useful. In my own work I fully populate the code with the eight combinations of CTRL, ALT, and SHIFT.
The code works by intercepting and analyzing WM_INPUT messages sent by the pen as it does its work. When the pen is tapped on the screen, a WM_INPUT message is sent, and this is seen by AutoHotkey before the subsequent LButton or RButton event. The WM_INPUT message provides information to isolate the state of the buttons. This state is copied into a global variable, and this global variable can be tested when the subsequent LButton or RButton event occurs. This script uses the HID funnctions at http://www.autohotkey.com/board/topic/3 ... functions/ and its hidfuncs.ahk must be included by a standard include mechanism (I put that file in the standard library folder and the script is written to fetch it from there). The HID testing functions at that thread are interesting to try and explore.
The code works on my Surface Pro 3, but has not been tested by others (yet). Likely there will be glitches. One anomaloy noted so far is that Windows may present the right-click-menu on some flavors of the upper-button-pressed events (those involving CTRL) in spite of the fact that AutoHotkey has trapped these events. But for me at least I finally can override the barrel buttons on the mouse.
Other users may have more devices. They may present WM_INPUT messages that I do not know about and that the code below may not simply ignore. On my system, the value for AnyFlag in the code below is always 67 for the surface pen and always zero for anything else, so I simply compare to zero to distinguish the two cases. I can find no documentation about this (binary) flag for values higher than 8 (IIRC). If additional devices are present on your system and things act strangely, consider investigating this flag.
A final remark. I use this code primarily to turn bottom-button-pressed pen taps (of all flavors of CTRL, ALT, and SHIFT) into equivalently-modified middle-button mouse clicks. That way, the pen with no buttons sends left mouse clicks, with the bottom button it sends middle mouse clicks, and with the top button it sens right mosue clicks. Lexicos and Nextron at http://ahkscript.org/boards/viewtopic.php?f=5&t=7462 helped me find a very succinct way to write this remapping and make it work. If you are overriding other hotkeys in your script with the hotkeys detected by the above script, you should read that other thread to see the discussion on SendLevel.
[edit] changed typo in a comment; added remark about AnyFlag
A bit of background first. The pen has two buttons on the barrel. If the upper button is pressed and the screen is tapped, AutoHotkey sees a right-mouse-button click, and no special code is required to isolate this. If neither button is pressed, AutoHotkey sees a left-mouse-button click, and again no special code is required. The problem is that if the bottom button is pressed and the screen is tapped, then AutoHotkey sees again a normal left-mouse-button click, identical to the case when the bottom button is not pressed. This script intends to isolate these three cases so that they may be easily customized.
The script is interested only in trappng taps of the pen on the screen. I do not use inking, and do not know how the script interacts with inking. I don't exploit hovering, and so have not really considered hovering. But the script does allow the state of the buttons to be detected when the screen is tapped with the pen.
The sample script below is illustrative. It isolates plain taps and taps with concurrent CTRL, ALT, and CTRL+ALT keyboard button presses. Only as many combinations as are useful need be supplied; additional combinations, using SHIFT and WIN and using the left and right variants of all of these, may be defined if useful. In my own work I fully populate the code with the eight combinations of CTRL, ALT, and SHIFT.
The code works by intercepting and analyzing WM_INPUT messages sent by the pen as it does its work. When the pen is tapped on the screen, a WM_INPUT message is sent, and this is seen by AutoHotkey before the subsequent LButton or RButton event. The WM_INPUT message provides information to isolate the state of the buttons. This state is copied into a global variable, and this global variable can be tested when the subsequent LButton or RButton event occurs. This script uses the HID funnctions at http://www.autohotkey.com/board/topic/3 ... functions/ and its hidfuncs.ahk must be included by a standard include mechanism (I put that file in the standard library folder and the script is written to fetch it from there). The HID testing functions at that thread are interesting to try and explore.
The code works on my Surface Pro 3, but has not been tested by others (yet). Likely there will be glitches. One anomaloy noted so far is that Windows may present the right-click-menu on some flavors of the upper-button-pressed events (those involving CTRL) in spite of the fact that AutoHotkey has trapped these events. But for me at least I finally can override the barrel buttons on the mouse.
Other users may have more devices. They may present WM_INPUT messages that I do not know about and that the code below may not simply ignore. On my system, the value for AnyFlag in the code below is always 67 for the surface pen and always zero for anything else, so I simply compare to zero to distinguish the two cases. I can find no documentation about this (binary) flag for values higher than 8 (IIRC). If additional devices are present on your system and things act strangely, consider investigating this flag.
Code: Select all
#SingleInstance Force
#UseHook
SurfacePenBegin()
Return
; trap pen taps with the top button pressed
#If (SurfacePenState = "tap down with upper side button pressed (seen in AutoHotkey as RButton event)")
^!RButton::OutputDebug CTRL+ALT Top Side Button
^RButton::OutputDebug CTRL Top Side Button
!RButton::OutputDebug ALT Top Side Button
RButton::OutputDebug plain Top Side Button
#If (SurfacePenState = "tap up with upper side button pressed (seen in AutoHotkey as RButton up event)")
^!RButton::OutputDebug CTRL+ALT Top Side Button Up
^RButton::OutputDebug CTRL Top Side Button Up
!RButton::OutputDebug ALT Top Side Button Up
RButton Up::OutputDebug plain Top Side Button Up
#If
; trap pen taps with the bottom button pressed
#If (SurfacePenState = "tap down with bottom side button pressed (seen in AutoHotkey as LButton event)")
^!LButton::OutputDebug CTRL+ALT Bottom Side Button
^LButton::OutputDebug CTRL Bottom Side Button
!LButton::OutputDebug ALT Bottom Side Button
LButton::OutputDebug plain Bottom Side Button
#If (SurfacePenState = "tap up with bottom side button pressed (seen in AutoHotkey as LButton up event)")
^!LButton::OutputDebug CTRL+ALT Bottom Side Button Up
^LButton::OutputDebug CTRL Bottom Side Button Up
!LButton::OutputDebug ALT Bottom Side Button Up
LButton Up::OutputDebug plain Bottom Side Button Up
#If
; trap pen taps with no buttons pressed
#If (SurfacePenState = "tap down with no side button pressed (seen in AutoHotkey as LButton event)")
^!LButton::OutputDebug CTRL+ALT Neither Button
^LButton::OutputDebug CTRL Neither Button
!LButton::OutputDebug ALT Neither Button
LButton::OutputDebug plain Neither Button
#If (SurfacePenState = "tap up with no side button pressed (seen in AutoHotkey as LButton up event)")
^!LButton::OutputDebug CTRL+ALT Neither Button Up
^LButton::OutputDebug CTRL Neither Button Up
!LButton::OutputDebug ALT Neither Button Up
LButton Up::OutputDebug plain Neither Button Up
#If
; initialize to trap input messages
SurfacePenBegin( )
{
Global SurfacePenState
SurfacePenState := ""
AHKHID_Register(1, 2, A_ScriptHwnd, RIDEV_INPUTSINK := 0x00000100)
OnMessage(WM_INPUT := 0xFF, "SurfacePenEvent")
}
; trap wm_input messages and analyze them
SurfacePenEvent( wParam, lParam, msg, hwnd )
{
Global SurfacePenState
Critical
AnyType := AHKHID_GetInputInfo(lParam, II_DEVTYPE := 0)
AnyFlag := AHKHID_GetInputInfo(lParam, II_MSE_FLAGS := (08 + A_PtrSize * 2) | 0x0100)
AnyButt := AHKHID_GetInputInfo(lParam, II_MSE_BUTTONFLAGS := ((12 + A_PtrSize * 2) | 0x0100))
AnyData := AHKHID_GetInputInfo(lParam, II_MSE_EXTRAINFO := 28 + A_PtrSize * 2)
SurfacePenState := ""
If (AnyType = 0 And AnyFlag <> 0 And AnyButt <> 0)
{
If (AnyButt = 1 And (AnyData & 3) = 2)
SurfacePenState := "tap down with no side button pressed (seen in AutoHotkey as LButton event)"
If (AnyButt = 2 And (AnyData & 3) = 2)
SurfacePenState := "tap up with no side button pressed (seen in AutoHotkey as LButton up event)"
If (AnyButt = 1 And (AnyData & 3) = 3)
SurfacePenState := "tap down with bottom side button pressed (seen in AutoHotkey as LButton event)"
If (AnyButt = 2 And (AnyData & 3) = 3)
SurfacePenState := "tap up with bottom side button pressed (seen in AutoHotkey as LButton up event)"
If (AnyButt = 4)
SurfacePenState := "tap down with upper side button pressed (seen in AutoHotkey as RButton event)"
If (AnyButt = 8)
SurfacePenState := "tap up with upper side button pressed (seen in AutoHotkey as RButton up event)"
OutputDebug, SurfacePenEvent [%SurfacePenState%]
}
}
#Include <hidfuncs>
[edit] changed typo in a comment; added remark about AnyFlag