Skip to content

Instantly share code, notes, and snippets.

@reclaimed
Last active February 6, 2018 10:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save reclaimed/00759d46decaef040bd1e1b86fe319cf to your computer and use it in GitHub Desktop.
Save reclaimed/00759d46decaef040bd1e1b86fe319cf to your computer and use it in GitHub Desktop.
/*
Short:
a helper for Microsoft Natural Keyboard 7000 and 4000:
- map the zoom rocker to up/down arrows
- make the favorite keys work without the IntelliPoint
- make the calculator keys work without IntelliPoint
- show screen indicators for Caps/Scroll/Num Locks
Long:
the Microsoft Natural Keyboard 7000 (and 4000) has four extra keys in the middle of the alphanumeric
zone and almost a dozen across the whole keyboard:
- zoom up
- zoom down
- browser back
- browser forward
- a constellation of six Favourites keys on the top
- a group of three calculator-related keys
i find a huge flaw in their designer's flow of thoughts, i mean, an ergonomic keyboard is supposed
to make typing easier, and at the same time they spend valuable keys in the home zone
for some useless functionality. but anyway.
most of the extra keys don't generate regular scancodes like a normal keyboard do. instead, they throwing
HID codes of mysterious nature, what makes remapping difficult.
this script remaps the useless functions with useful ones:
R E M A P P E D A C T I O N S
key plain alt ctrl shift
zoom up arrow up pgup ctrl-up shift-up
zoom down arrow down pgdn ctrl-down shift-down
browser back arrow left home ctrl-left shift-left
browser fw arrow right end ctrl-right shift-right
= =
( (
) )
Fav* Ctrl-Alt-Shift-F6
Fav1 Ctrl-Alt-Shift-F5
Fav2 Ctrl-Alt-Shift-F4
Fav3 Ctrl-Alt-Shift-F3
Fav4 Ctrl-Alt-Shift-F2
Fav5 Ctrl-Alt-Shift-F1
this script is standing on the shoulders of giants:
; ===========================================================
; https://autohotkey.com/board/topic/83394-ahk-just-stopped-working-all-of-a-sudden/
; ===========================================================
; AutoHotkey "Raw Input" listing and capture
; You can get the latest version of this file at:
; https://gist.github.com/TheZoc/4bf163aa9a9922b21fbf
;
; There may be more than one 'raw' device per device actually attached
; to the system. This is because these devices generally represent
; "HID Collections", and there may be more than one HID collection per
; USB device. For example, the Natural Keyboard 4000 supports a normal
; keyboard HID collection, plus an additional HID collection that can
; be used for the zoom slider and other important buttons
; ===========================================================
; https://autohotkey.com/board/topic/100990-another-on-screen-caps-lock-indicator/
*/
; come on guys yes globals are evil just move along
global LockIndicators := True
global DebugConsole := False
global ArrowsDisable := True ; good for getting rid of the habit
global ZoomRepeatEnabled := False
/*
the script sometimes loses the keypresses and autorepeat won't work well.
i think it's the OnMessage mechanism that fails to catch the HID messages on
time, but idk how to make it work the right way. opinions are welcome.
*/
; Replace any previous instance
#SingleInstance force
OnMessage(0x00FF, "InputMessage")
SizeofRawInputDeviceList := A_PtrSize * 2
SizeofRawInputDevice := 8 + A_PtrSize
RIM_TYPEMOUSE := 0
RIM_TYPEKEYBOARD := 1
RIM_TYPEHID := 2
RIDI_DEVICENAME := 0x20000007
RIDI_DEVICEINFO := 0x2000000b
RIDEV_INPUTSINK := 0x00000100
RID_INPUT := 0x10000003
; again, don't be THAT boring
global ZoomPressed := 0
global ZoomTime := 0
global ZoomDelay := 0
; todo: look for states, not just toggle everything off
if (LockIndicators) {
SetCapsLockState, Off
SetNumLockState, Off
SetScrollLockState, Off
}
xposnum := A_ScreenWidth - 60
yposnum := A_ScreenHeight - 70
xposscrl := A_ScreenWidth - 120
yposscrl := A_ScreenHeight - 70
xposcaps := A_ScreenWidth - 180
yposcaps := A_ScreenHeight - 70
if (ZoomRepeatEnabled)
SetTimer, ZoomKeyAutorepeat, 50, -10
if (DebugConsole) {
Gui, Add, Edit, HScroll w460 h300 vInfoOut -Wrap ReadOnly HwndInfoHwnd
Gui, Add, Edit, HScroll w460 h300 vEditOut -Wrap ReadOnly HwndEditHwnd
Gui, Show, , HIDList
}
DetectHiddenWindows, on
HWND := WinExist("ahk_class AutoHotkey ahk_pid " DllCall("GetCurrentProcessId"))
;HWND := WinExist("HIDList")
Res := DllCall("GetRawInputDeviceList", "Ptr", 0, "UInt*", Count, UInt, SizeofRawInputDeviceList)
If (Res = -1) {
InfoOutput("[1]GetRawInputDeviceList call failed.`r`n")
}
InfoOutput("There are " . Count . " raw input devices`r`n`r`n")
VarSetCapacity(RawInputList, SizeofRawInputDeviceList * Count)
Res := DllCall("GetRawInputDeviceList", "Ptr", &RawInputList, "UInt*", Count, "UInt", SizeofRawInputDeviceList)
If (Res = -1) {
InfoOutput("[2]GetRawInputDeviceList call failed.`r`n")
}
InfoOutput(RawInputList)
MouseRegistered := 0
KeyboardRegistered := 0
Loop %Count% {
Handle := NumGet(RawInputList, (A_Index - 1) * SizeofRawInputDeviceList, "UInt")
Type := NumGet(RawInputList, ((A_Index - 1) * SizeofRawInputDeviceList) + A_PtrSize, "UInt")
if (Type = RIM_TYPEMOUSE)
TypeName := "RIM_TYPEMOUSE"
else if (Type = RIM_TYPEKEYBOARD)
TypeName := "RIM_TYPEKEYBOARD"
else if (Type = RIM_TYPEHID)
TypeName := "RIM_TYPEHID"
else
TypeName := "RIM_OTHER"
InfoOutput("`r`nDevice " . A_Index . ":`r`n`tHandle " . Handle . "`r`n`tType " . Type . " (" . TypeName . ") `r`n")
Res := DllCall("GetRawInputDeviceInfo", "Ptr", Handle, "UInt", RIDI_DEVICENAME, "Ptr", 0, "UInt *", nLength)
If (Res = -1) {
InfoOutput("[1]GetRawInputDeviceInfo call failed.`r`n")
}
VarSetCapacity(Name, (nLength + 1) * 2)
Res := DllCall("GetRawInputDeviceInfo", "Ptr", Handle, "UInt", RIDI_DEVICENAME, "Str", Name, "UInt*", nLength)
If (Res = -1) {
InfoOutput("[2]GetRawInputDeviceInfo call failed.`r`n")
}
InfoOutput("`tName " . Name . "`r`n")
Res := DllCall("GetRawInputDeviceInfo", "Ptr", Handle, "UInt", RIDI_DEVICEINFO, "Ptr", 0, "UInt *", iLength)
If (Res = -1) {
InfoOutput("[3]GetRawInputDeviceInfo call failed.`r`n")
}
VarSetCapacity(Info, iLength)
NumPut(iLength, Info, 0, "UInt") ;Put length in struct RIDI_DEVICEINFO
Res := DllCall("GetRawInputDeviceInfo", "Ptr", Handle, "UInt", RIDI_DEVICEINFO, "UInt", &Info, "UInt *", iLength)
If (Res = -1) {
InfoOutput("[4]GetRawInputDeviceInfo call failed.`r`n")
}
if (Type = RIM_TYPEMOUSE)
InfoOutput("`tButtons " . NumGet(Info, 4 * 3) . "`r`n`tSample rate " . NumGet(Info, 4 * 4) . "`r`n")
else if (Type = RIM_TYPEKEYBOARD)
InfoOutput("`tMode " . NumGet(Info, 4 * 4) . "`r`n`tFunction keys " . NumGet(Info, 4 * 5) . "`r`n")
else if (Type = RIM_TYPEHID)
{
InfoOutput("`tVendor " . NumGet(Info, 4 * 2) . " Product " . NumGet(Info, 4 * 3) . " Version " . NumGet(Info, 4 * 4) . " `r`n")
UsagePage := NumGet(Info, (4 * 5), "UShort")
Usage := NumGet(Info, (4 * 5) + 2, "UShort")
InfoOutput("`tUsage " . Usage . " Usage Page " . UsagePage . "`r`n")
}
; Keyboards are always Usage 6, Usage Page 1, Mice are Usage 2, Usage Page 1,
; HID devices specify their top level collection in the info block
VarSetCapacity(RawDevice, SizeofRawInputDevice, 0)
NumPut(RIDEV_INPUTSINK, RawDevice, 4)
NumPut(HWND, RawDevice, 8)
DoRegister := 0
if (Type = RIM_TYPEMOUSE && MouseRegistered = 0)
{
DoRegister := 1
; Mice are Usage 2, Usage Page 1
NumPut(1, RawDevice, 0, "UShort")
NumPut(2, RawDevice, 2, "UShort")
MouseRegistered := 1
}
else if (Type = RIM_TYPEKEYBOARD && KeyboardRegistered = 0)
{
DoRegister := 1
; Keyboards are always Usage 6, Usage Page 1
NumPut(1, RawDevice, 0, "UShort")
NumPut(6, RawDevice, 2, "UShort")
KeyboardRegistered := 1
}
else if (Type = RIM_TYPEHID)
{
DoRegister := 1
NumPut(UsagePage, RawDevice, 0, "UShort")
NumPut(Usage, RawDevice, 2, "UShort")
}
if (DoRegister)
{
Res := DllCall("RegisterRawInputDevices", "Ptr", &RawDevice, "UInt", 1, "UInt", 8 + A_PtrSize)
if (Res = 0)
InfoOutput("`t>>> Failed to register for this device! <<<`r`n`r`n")
else
InfoOutput("`tRegistered for this device`r`n")
}
}
Count := 1
InputMessage(wParam, lParam, msg, hwnd)
{
;global DoCapture
global RIM_TYPEMOUSE, RIM_TYPEKEYBOARD, RIM_TYPEHID
global RID_INPUT
Res := DllCall("GetRawInputData", "UInt", lParam, "UInt", RID_INPUT, "Ptr", 0, "UInt *", Size, "UInt", 8 + A_PtrSize * 2)
If (Res = -1) {
InfoOutput("[1]GetRawInputData call failed.`r`n")
}
VarSetCapacity(Buffer, Size)
Res := DllCall("GetRawInputData", "UInt", lParam, "UInt", RID_INPUT, "Ptr", &Buffer, "UInt *", Size, "UInt", 8 + A_PtrSize * 2)
If (Res = -1) {
InfoOutput("[2]GetRawInputData call failed.`r`n")
}
; AppendOutput(Mem2Hex(&Buffer, Size))
Type := NumGet(Buffer, 0 * 4, "UInt")
Size := NumGet(Buffer, 1 * 4, "UInt")
Handle := NumGet(Buffer, 2 * 4, "UInt")
;AppendOutput("Got Input with " . Res . " size " . Size . " Type " . Type . " Handle " . Handle . "`r`n")
if (Type = RIM_TYPEMOUSE)
{
/*LastX := NumGet(Buffer, (20+A_PtrSize*2), "Int")
LastY := NumGet(Buffer, (24+A_PtrSize*2), "Int")
AppendOutput("`r`nHND " . Handle . " MOUSE LastX " . LastX . " LastY " . LastY . "`r`n")
*/
}
else if (Type = RIM_TYPEKEYBOARD)
{
/*ScanCode := NumGet(Buffer, (8+A_PtrSize*2), "UShort")
VKey := NumGet(Buffer, (14+A_PtrSize*2), "UShort")
Message := NumGet(Buffer, (16+A_PtrSize*2))
AppendOutput("HND " . Handle . " KBD ScanCode " . ScanCode . " VKey " . VKey . " Msg " . Message . "`r`n")
*/
}
else if (Type = RIM_TYPEHID)
{
SizeHid := NumGet(Buffer, (8+A_PtrSize*2), "UInt")
InputCount := NumGet(Buffer, (12+A_PtrSize*2), "UInt")
;AppendOutput("HND " . Handle . " HID Size " . SizeHid . " Count " . InputCount . " Ptr " . &Buffer)
Loop %InputCount% {
Addr := &Buffer + (16+A_PtrSize*2) + ((A_Index - 1) * SizeHid)
BAddr := &Buffer
;MsgBox, BAddr %BAddr% Addr %Addr%
;AppendOutput(" - Input " . Mem2Hex(Addr, SizeHid) . "`r`n")
InputHex := Mem2Hex(Addr, SizeHid)
AppendOutput(InputHex . " " . zoom_up . zoom_pgup . zoom_down . zoom_pgdn . "`r`n")
if IsLabel(InputHex) {
SetTimer ZoomKeyAutorepeat, off ; supposed to prevent losing messages
;gosub ResetFlags
gosub %InputHex%
SetTimer ZoomKeyAutorepeat, on
}
}
}
else
{
;AppendOutput("HND " . Handle . " Unknown Type " . Type . "`r`n")
}
return
}
return
Exit
;GuiClose:
;ExitApp
Mem2Hex( pointer, len )
{
A_FI := A_FormatInteger
SetFormat, Integer, Hex
Loop, %len% {
Hex := *Pointer+0
StringReplace, Hex, Hex, 0x, 0x0
StringRight Hex, Hex, 2
hexDump := hexDump . hex
Pointer ++
}
SetFormat, Integer, %A_FI%
StringUpper, hexDump, hexDump
Return hexDump
}
InfoOutput(Text)
{
if (DebugConsole!=True)
return
global InfoHwnd
GuiControlGet, InfoOut
NewText := InfoOut . Text
GuiControl, , InfoOut, %NewText%
return
}
AppendOutput(Text)
{
if (DebugConsole!=True)
return
global EditHwnd
GuiControlGet, EditOut
NewText := EditOut . Text
GuiControl, , EditOut, %NewText%
; WM_VSCROLL (0x115), SB_BOTTOM (7)
;MsgBox, %EditHwnd%
SendMessage, 0x115, 0x0000007, 0, , ahk_id %EditHwnd%
return
}
; ===========================================================================
ZoomKeypress(id) {
if (ZoomRepeatEnabled) {
ZoomKeyRegister(id)
} else {
ZoomKeySend(id)
}
return
}
/*
first it was separated into two blocks: ZoomKeyDown and ZoomKeyUp
to simulate the proper autorepeat. but it had no real advantages and
the algorithm was simplified that way.
*/
ZoomKeySend(id) {
if (id==10) ; zoom up
Send {Up}
if (id==11) ; ctrl zoom up
Send ^{Up}
if (id==12) ; shift zoom up
Send +{Up}
if (id==14) ; alt zoom up
Send {PgUp}
if (id==20) ; zoom down
Send {Down}
if (id==21) ; ctrl zoom down
Send ^{Down}
if (id==22) ; shift zoom down
Send +{Down}
if (id==24) ; alt zoom down
Send {PgDn}
return
}
ZoomKeyRegister(id) {
if (id==ZoomPressed)
return
ZoomPressed := id
ZoomTime := A_TickCount
ZoomDelay := 0
return
}
ZoomKeyAutorepeat() {
if (ZoomPressed==0)
return
if ((A_TickCount - ZoomTime)<=ZoomDelay)
return
/*
fsm:
1. send keypress
2. wait for 250ms
3. send keypress
4. wait for 30ms
5. goto 3
why not the Sleep function? it seemngly freezes the OnMessage.
*/
ZoomKeySend(ZoomPressed)
if (ZoomDelay==0)
ZoomDelay := 250
else if (ZoomDelay==250)
ZoomDelay :=30
return
}
; indicators
~*NumLock::
if (LockIndicators!=True)
return
Sleep, 10
if GetKeyState("NumLock", "T") {
Progress,1: B1 X%xposnum% Y%yposnum% W58 H28 ZH0 FS11 WS900 CTFF0000, NUM
} else {
Progress,1: off
}
return
~*ScrollLock::
if (LockIndicators!=True)
return
Sleep, 10
if GetKeyState("ScrollLock", "T") {
Progress,2: B1 X%xposscrl% Y%yposscrl% W58 H28 ZH0 FS11 WS900 CTFF0000, SCR
} else {
Progress,2: off
}
return
~*CapsLock::
if (LockIndicators!=True)
return
Sleep, 10
if GetKeyState("CapsLock", "T") {
Progress,3: B1 X%xposcaps% Y%yposcaps% W58 H28 ZH0 FS11 WS900 CTFF0000, CAPS
} else {
Progress,3: off
}
return
; keys Browser Forward and Browser Back send both HID codes and Scancodes at the same time.
; it looks like that: you press "back" and AHK sends the "left arrow" but your browser
; or IDE or whatever performs "browser back" operation as well, making the key completely
; useless.
; i didn't find a way to suppress scancodes and decided to remap the browser_back/forward keys instead.
; pros: no unwanted behaviour; hasslefree autorepeat functions
; cons: you can't use the "browser back" function from, say, your Remote Control
; hid labels are commented out. if you need it, look below
;----------------------------------------------------------------
Browser_Back::
Send {Left}
return
+Browser_Back:: ; shifted
Send +{Left}
return
^Browser_Back:: ; ctrl
Send ^{Left}
return
; there is a * wildcard for modifiers, but i plan to extend functionality in future
!Browser_Back:: ; alt
#Browser_Back:: ; win
Send {Home}
return
;----------------------------------------------------------------
Browser_Forward::
Send {Right}
return
+Browser_Forward:: ; shifted
Send +{Right}
return
^Browser_Forward:: ; ctrl
Send ^{Right}
return
!Browser_Forward:: ; alt
#Browser_Forward:: ; win
Send {End}
return
; Something was Released ----------------------------------------------------------------
0100000000010000: ; 4000: release
0100000000010001: ; 4000: lctrl+release
0100000000010010: ; 4000: rctrl+release
0100000000010002: ; 4000: lshift+release
0100000000010020: ; 4000: rshift+release
0100000000010004: ; 4000: lalt+release
0100000000010040: ; 4000: ralt+release
0100000000000000: ; 7000: release
0100000000000001: ; 7000: lctrl+release
0100000000000010: ; 7000: rctrl+release
0100000000000002: ; 7000: lshift+release
0100000000000020: ; 7000: rshift+release
0100000000000004: ; 7000: lalt+release
0100000000000040: ; 7000: ralt+release
ZoomKeypress(0)
return
;zoom up ----------------------------------------------------------------
012D020000010000: ; 4000: zup
012D020000000000: ; 7000: zup
ZoomKeypress(10)
return
012D020000010001: ; 4000: lctrl+zup
012D020000010010: ; 4000: rctrl+zup
012D020000000001: ; 7000: left ctrl
012D020000000010: ; 7000: right ctrl
ZoomKeypress(11)
return
012D020000010002: ; 4000: lshift+zup
012D020000010020: ; 4000: rshift+zup
012D020000000002: ; 7000: lshift+zup
012D020000000020: ; 7000: rshift+zup
ZoomKeypress(12)
return
012D020000010004: ; 4000: lalt+zup
012D020000010040: ; 4000: ralt+zup
012D020000000004: ; 7000: left alt
012D020000000040: ; 7000: right alt
ZoomKeypress(14)
return
;zoom down ----------------------------------------------------------------
012E020000010000: ; 4000: zdown
012E020000000000: ; 7000: zdown
ZoomKeypress(20)
return
012E020000010001: ; 4000: lctrl+zdown
012E020000010010: ; 4000: rctll+zdown
012E020000000001: ; 7000: lctrl+zdown
012E020000000010: ; 7000: rctll+zdown
ZoomKeypress(21)
012E020000010002: ; 4000: lshift+zdown
012E020000010020: ; 4000: rshift+zdown
012E020000000002: ; 7000: lshift+zdown
012E020000000020: ; 7000: rshift+zdown
ZoomKeypress(22)
return
012E020000010004: ; 4000: lalt+zdown
012E020000010040: ; 4000: ralt+zdown
012E020000000004: ; 7000: lalt+zdown
012E020000000040: ; 7000: ralt+zdown
ZoomKeypress(24)
return
/*
;back ----------------------------------------------------------------
0124020000000000: ; 7000
Send {Left}
return
0124020000000001: ; 7000
0124020000000010:
0124020000000002:
0124020000000020:
0124020000000004:
0124020000000040:
Send {Home}
return
;forward ----------------------------------------------------------------
0125020000000000: ; 7000
Send {Right}
return
0125020000000001: ; 7000
0125020000000010:
0125020000000002:
0125020000000020:
0125020000000004:
0125020000000040:
Send {End}
return
*/
; My Favorites ----------------------------------------------------------------
0182010000000000: ; 7000
Send ^+!{F6}
return
; Favorites 1 ----------------------------------------------------------------
0100000000050000: ; 4000
0100000000040000: ; 7000
Send ^+!{F5}
return
; Favorites 2 ----------------------------------------------------------------
0100000000090000: ; 4000
0100000000080000: ; 7000
Send ^+!{F4}
return
; Favorites 3 ----------------------------------------------------------------
0100000000110000: ; 4000
0100000000100000: ; 7000
Send ^+!{F3}
return
; Favorites 4 ----------------------------------------------------------------
0100000000210000: ; 4000
0100000000200000: ; 7000
Send ^+!{F2}
return
; Favorites 5 ----------------------------------------------------------------
0100000000410000: ; 4000
0100000000400000: ; 7000
Send ^+!{F1}
return
;numpad( ----------------------------------------------------------------
010000B600010000: ; 4000
010000B600000000: ; 7000
Send (
return
;numpad) ----------------------------------------------------------------
010000B700010000: ; 4000
010000B700000000: ; 7000
Send )
return
;numpad= ----------------------------------------------------------------
0100006700010000: ; 4000
0100006700000000: ; 7000
Send `=
return
; disable arrows to help yourself to get rid of the carpal inducing habit ----------------------------------------------------------------
ArrowReminder(){
if (ArrowsDisable==False)
return True ; well it looks weird but
ToolTip Arrows are Disabled
; some annoying music to create pavlov's reflex in user
; https://autohotkey.com/board/topic/15483-ringtone-melody-using-soundbeep/
; http://forum.script-coding.com/viewtopic.php?id=9579
; https://www.reddit.com/r/AutoHotkey/comments/65ab8v/making_music_with_soundbeep/
SoundBeep 524, 400
SoundBeep 416, 400
SoundBeep 393, 400
SoundBeep 350, 400
SoundBeep 312, 200
SoundBeep 393, 200
SoundBeep 294, 200
SoundBeep 312, 200
SoundBeep 262, 400
SoundBeep 393, 200
SoundBeep 416, 200
SoundBeep 393, 400
SoundBeep 312, 200
SoundBeep 294, 200
SoundBeep 262, 400
SoundBeep 247, 200
SoundBeep 262, 200
SoundBeep 294, 600
SoundBeep 247, 200
SoundBeep 197, 400
SoundBeep 393, 200
SoundBeep 416, 200
SoundBeep 393, 400
SoundBeep 371, 200
SoundBeep 393, 200
SoundBeep 588, 400
SoundBeep 623, 200
SoundBeep 588, 200
SoundBeep 524, 1200
SoundBeep 524, 200
SoundBeep 494, 200
SoundBeep 588, 400
SoundBeep 524, 200
SoundBeep 416, 200
SoundBeep 350, 400
SoundBeep 416, 200
SoundBeep 524, 200
SoundBeep 393, 600
SoundBeep 312, 200
SoundBeep 262, 400
SoundBeep 393, 200
SoundBeep 416, 200
SoundBeep 393, 400
SoundBeep 247, 200
SoundBeep 294, 200
SoundBeep 393, 400
SoundBeep 294, 200
SoundBeep 312, 200
SoundBeep 262, 1200
ToolTip
return False
}
Up::
if (ArrowReminder())
Send {Up}
return
Down::
if (ArrowReminder())
Send {Down}
return
Left::
if (ArrowReminder())
Send {Left}
return
Right::
if (ArrowReminder())
Send {Right}
return
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment