Skip to content

Instantly share code, notes, and snippets.

@nomand
Forked from TheZoc/AppleWKRemapper.ahk
Last active December 2, 2021 02:05
Show Gist options
  • Star 17 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save nomand/9bb9fac582e2191685804c2ab96e9916 to your computer and use it in GitHub Desktop.
Save nomand/9bb9fac582e2191685804c2ab96e9916 to your computer and use it in GitHub Desktop.
AutoHotKey script that allows Apple wireless keyboard work in Windows 10 with some layout changes. See header for details and credits to original authors and modders.
;===========================================================================================================================================
; Additional Modifications by nomand
;==========================================================================================================================================
;
; - fixed Media_Next functinality
; - moved Eject key screenshots to active window screenshot shortcut (Ctrl+F3)
; - restored Lctrl <> Fn swap
; - restored page up/down, home/end functinality
; - F12 Insert remap kept disabled
; - cleaned up code indentations and comments
;
; ISSUES:
; Windows interrupts lock up remapped keys (windows dialogue popup will interrupt AHK when crl, fn, eject etc. are held)
; Page Up/Page Down/to Start/to End text selections while holding Shift don't work
; Some applications (Ableton Live) don't respect AHK at all and talk to the keyboard only through apple designated keys (wtf)
; Alt and Command keys need to be remapped to put the "Windows" key where it should be
; Any remapping related to Ctrl and Fn swap is only done on the left of the spacebar
; Ctrl + Shift key combinations don't work with remapped keys (Fn + Shift) (for example unclose last tab with Ctrl + Shift + T)
; A bunch more bugs most likely
;
; - Previous header follows:
;
; Apple Wireless Keyboard Eject Key and Function kKey remapper for Windows.
; You can get the latest version of this file at:
; https://gist.github.com/TheZoc/a913642e9bedf62ba3ef
;
;==========================================================================================================================================
; Please, if you make a significant change, fix or would like to improve this script,
; I'd really appreciate if you can contact me so we can merge both works :)
;==========================================================================================================================================
;
; This is a havily modified version of the script:
; https://gist.github.com/anonymous/4294405
;
; This is a "hack" of the original script with was updated to support 64 bits Windows.
; With a file compare tool, it's easy to update the original script, if you prefer it's remaps.
;
; Changes from the original script:
; - Windows x64 support.
; - Eject key changed to take screenshots to <user>/Pictures/Screenshots
; - Added a "bootcampWindows" config variable, so users in bootcamp can disable some remappings (System volume changes) - untested.
; - Disabled fn key + arrow key remapping
; - Disabled F12 to Insert Key remapping
; - Disabled swapping of the fn key and left Ctrl key.
;
;==========================================================================================================================================
; Original script header follows
;==========================================================================================================================================
;
; AutoHotkey Version: 1.x
; Language.........: English
; Platform.........: NT/XP/Vista
; Author...........: mrBTK
;
; Script Function..: Make Apple Wireless Keyboard useful in MS Windows:
; - EJECT = Delete with repeat deleting on long pressing. Shift-DEL and other combinations works too.
; - Swap FN & left Control
; - FN-functions оn F3-F12 keys and arrow keys (use new-FN)
; - FN+EJECT = drive eject
; - F12 = INSERT (on your need, it simple to delete this feature: find in script "switch F12 to Insert" and delete the command)
; - AWK POWER button toggle script suspending. Place in script directory two any sound files "on.wav" & "off.wav" for notification
;
; Based on.........: DLLCall: Support for Human Interface devices: some keymapping code
; By...............: Micha
; URL..............: http://www.autohotkey.com/forum/viewtopic.php?t=6367
; Based on.........: HID/Extended input devices (MS Natural Keyboard 4000 etc.): HID operations code
; By...............: Shaun
; URL..............: http://www.autohotkey.com/board/topic/36304-hidextended-input-devices-ms-natural-keyboard-4000-etc/
;
; Use..............: Spread the word! Just be sure to credit the writer of the original config
; and the authors of this file.
;
#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases.
#MaxHotkeysPerInterval 1000
#SingleInstance force ; Replace any previous instance
DetectHiddenWindows, on
OnMessage(0x00FF, "InputMessage")
SendMode Input ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir% ; Ensures a consistent starting directory.
;#NoTrayIcon
; Set screen title, to set the HWND
Gui, Show, x0 y0 h0 w0, AppleWKHelper
HWND := WinExist("AppleWKHelper")
hidMessage := 0
isSuspend := 0
; Variable for the modifier key
fnPressed := 0
fnPrevState := 0
ejPressed := 0
ejPrevState := 0
pwrPressed := 0
pwrPrevState := 0
; Variable for Fn <> Lctrl
lctrlPressed := 0
lctrlPrevState := 0
; set this to 0 if you're not running windows on Bootcamp (aka: Normal PC)
bootcampWindows := 0
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; handle HID input, set global vars, call modKeysProcessing
; List all of the "Raw Input" devices available for use and allow
; capture of output
;
; 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
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
DoCapture := 0
;;get count of HID devices
Res := DllCall("GetRawInputDeviceList", "Ptr", 0, "UInt*", Count, UInt, SizeofRawInputDeviceList)
VarSetCapacity(RawInputList, SizeofRawInputDeviceList * Count)
;;get list of HID devices
Res := DllCall("GetRawInputDeviceList", "Ptr", &RawInputList, "UInt*", Count, "UInt", SizeofRawInputDeviceList)
rimHIDregistered := 0
Loop %Count% ;for all HID devices
{
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"
; get HID device name length
Res := DllCall("GetRawInputDeviceInfo", "Ptr", Handle, "UInt", RIDI_DEVICENAME, "Ptr", 0, "UInt *", nLength)
VarSetCapacity(Name, (nLength + 1) * 2)
; get HID device name
Res := DllCall("GetRawInputDeviceInfo", "Ptr", Handle, "UInt", RIDI_DEVICENAME, "Str", Name, "UInt*", nLength)
; get HID device info
Res := DllCall("GetRawInputDeviceInfo", "Ptr", Handle, "UInt", RIDI_DEVICEINFO, "Ptr", 0, "UInt *", iLength)
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)
; 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
; AWK modifier buttons is separate TYPEHID (rather keyboard standard buttons)
if (Type = RIM_TYPEHID)
{
Vendor := NumGet(Info, 4 * 2, "UShort")
Product := NumGet(Info, 4 * 3, "UShort")
Version := NumGet(Info, 4 * 4, "UShort")
UsagePage := NumGet(Info, (4 * 5), "UShort")
Usage := NumGet(Info, (4 * 5) + 2, "UShort")
}
VarSetCapacity(RawDevice, SizeofRawInputDevice, 0)
NumPut(RIDEV_INPUTSINK, RawDevice, 4)
NumPut(HWND, RawDevice, 8)
if (Type = RIM_TYPEHID && Vendor = 1452 && rimHIDregistered = 0) ; AWK Vendor number
{
rimHIDregistered := 1
NumPut(UsagePage, RawDevice, 0, "UShort")
NumPut(Usage, RawDevice, 2, "UShort")
; Register AWK modifier buttons HID
Res := DllCall("RegisterRawInputDevices", "UInt", &RawDevice, UInt, 1, UInt, SizeofRawInputDevice)
if (Res = 0)
{
MsgBox, Failed to register for AWK device!
ExitApp
}
}
}
Count := 1
InputMessage(wParam, lParam, msg, hwnd)
{
global hidMessage
global isSuspend
global RIM_TYPEMOUSE, RIM_TYPEKEYBOARD, RIM_TYPEHID
global RID_INPUT
; get HID input
Res := DllCall("GetRawInputData", "UInt", lParam, "UInt", RID_INPUT, "Ptr", 0, "UInt *", Size, "UInt", 8 + A_PtrSize * 2)
VarSetCapacity(Buffer, Size)
Res := DllCall("GetRawInputData", "UInt", lParam, "UInt", RID_INPUT, "Ptr", &Buffer, "UInt *", Size, "UInt", 8 + A_PtrSize * 2)
Type := NumGet(Buffer, 0, "UInt")
if (Type = RIM_TYPEHID)
{
SizeHid := NumGet(Buffer, (8+A_PtrSize*2), "UInt")
InputCount := NumGet(Buffer, (12+A_PtrSize*2), "UInt")
Loop %InputCount% {
Addr := &Buffer + (16+A_PtrSize*2) + ((A_Index - 1) * SizeHid)
hidMessage := Mem2Hex(Addr, SizeHid)
ProcessHIDData(wParam, lParam)
}
}
return
}
Mem2Hex( pointer, len )
{
multiply := 0x100
Hex := 0
Loop, %len%
{
Hex := Hex * multiply
Hex := Hex + *Pointer+0
Pointer ++
}
Return Hex
}
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; set global vars for further handling
ProcessHIDData(wParam, lParam)
{
global hidMessage
global isSuspend
global fnPressed
global fnPrevState
global ejPressed
global ejPrevState
global pwrPressed
global pwrPrevState
SetTimer, SendDelete, Off
; Filter bit 5 (Fn key)
Transform, FnValue, BitAnd, 0xFF10, hidMessage
if (FnValue = 0x1110) ; Fn is pressed
{
fnPrevState := fnPressed
fnPressed := 1
}
else ; Fn is released
{
fnPrevState := fnPressed
fnPressed := 0
}
; Filter bit 4 (Eject key)
Transform, FnValue, BitAnd, 0xFF08, hidMessage
if (FnValue = 0x1108) ; Eject is pressed
{
ejPrevState := ejPressed
ejPressed := 1
}
else ; Eject is Released
{
ejPrevState := ejPressed
ejPressed := 0
}
; Filter bit 1 fnd 2 (Power key)
Transform, FnValue, BitAnd, 0xFF03, hidMessage
if (FnValue = 0x1303) ; Power is pressed
{
pwrPrevState := 0
pwrPressed := 1
chkSuspend() ; Toggle Suspend of this script.
}
if (fnValue = 0x1302) ; Power is released
{
pwrPrevState := 1
pwrPressed := 0
; no anything doing оn power button release
}
if (isSuspend = 0)
modKeysProcessing()
}
modKeysProcessing() ; handle keypressing for modifier keys
{
global fnPressed
global ejPressed
global ejPrevState
global fnPrevState
global lctrlPressed
global lctrlPrevState
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Eject = delete with delay and repeat
if(lctrlPressed = 0) ; eject only pressed
{
if(ejPressed = 1 and ejPrevState = 0)
{
if(GetKeyState("Shift") or GetKeyState("Alt") or GetKeyState("Control"))
{
SendInput {Blind}{Delete} ; Edit::Cut and other w|o repeating
}
else if(fnPressed = 1)
{
SendInput {Ctrl}{Delete} ; ctrl - del
}
else
{ ; No modifiers = Del with repeating
SendInput {Delete}
SetTimer, SendDelete, -700 ; Delay for start repeating
}
}
}
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; fn = rCtrl
if (ejPressed = 0) ; fn only pressed
{
if (fnPressed = 1 and fnPrevState = 0) ; fn down
SendInput {rCtrl Down}
if (fnPressed = 0 and fnPrevState = 1) ; fn up
SendInput {rCtrl Up}
}
}
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Send Delete keystroke repeatedly while Eject still pressed
SendDelete:
; repeating del while Eject still down
if (ejPressed = 1)
{
SendInput {delete}
SetTimer, SendDelete, -40
}
Return
chkSuspend()
{
global fnPressed
global fnPrevState
global ejPressed
global ejPrevState
global lctrlPressed
global lctrlPrevState
global pwrPressed
global pwrPrevState
global isSuspend
if (isSuspend = 0)
{
isSuspend := 1
fnPressed := 0
fnPrevState := 0
ejPressed := 0
ejPrevState := 0
pwrPressed := 0
pwrPrevState := 0
lctrlPressed := 0
lctrlPrevState := 0
Suspend , On
SetTimer, SendDelete, Off
SendInput {rCtrl Up}
TrayTip, AWK Helper, Suspended, 1, 1
Soundplay , off.wav
}
else
{
isSuspend := 0
Suspend , Off
TrayTip, AWK Helper, Restored, 1, 1
Soundplay , on.wav
}
}
; switch F12 to Insert
;*F12::sendInput {Blind}{Insert}
; lctrl = fn
; get up and down Lcontrol, sets global variables
$*lControl up::LCtrlUp()
LCtrlUp()
{
global lctrlPressed
global lctrlPrevState
lctrlPrevState := 1
lctrlPressed := 0
SendInput {F24 up} ; previously {LCtrl up}
}
Return
$*lControl::LCtrlDn()
LCtrlDn()
{
global lctrlPressed
global lctrlPrevState
lctrlPrevState := 0
lctrlPressed := 1
SetTimer, SendDelete, Off
SendInput {F24 down} ; previously {LCtrl down}
}
Return
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Fn modifier: PrintScreen, Task Manager
; lctrl uses as fn
; fn+alt F3 = PrtScr for Active Window
$!F3::hotkeyAltF3()
hotkeyAltF3()
{
global ejPressed
global fnPressed
if(fnPressed = 1 and ejPressed = 0)
{
SendInput !{PrintScreen} ; print screen to clipboard
SendInput #{PrintScreen} ; print screen to <user>/Pictures/Screenshots
}
else
SendInput !{F3}
}
Return
; fn+F3 = PrtScr
$F3::hotkeyF3()
hotkeyF3()
{
global ejPressed
global fnPressed
if(fnPressed = 1 and ejPressed = 0)
SendInput {PrintScreen}
else
SendInput {F3}
}
Return
; fn+F4 = Run TM
$F4::hotkeyF4()
hotkeyF4()
{
global ejPressed
global fnPressed
if(fnPressed = 1 and ejPressed = 0)
SendInput +^{Esc}
else
SendInput {F4}
}
Return
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Fn modifier: Audio hotkeys, specified for WMP
; WinMPlayer: Previous
$F7::hotkeyF7()
hotkeyF7()
{
global ejPressed
global fnPressed
if(fnPressed = 1 and ejPressed = 0)
SendInput {Media_Prev}
else
SendInput {F7}
}
Return
; WinMPlayer: Pause/Unpause
$F8::hotkeyF8()
hotkeyF8()
{
global ejPressed
global fnPressed
if(fnPressed = 1 and ejPressed = 0)
SendInput {Media_Play_Pause}
else
SendInput {F8}
}
Return
; WinMPlayer: Next
$F9::hotkeyF9()
hotkeyF9()
{
global ejPressed
global fnPressed
if(fnPressed = 1 and ejPressed = 0)
SendInput {Media_Next}
else
SendInput {F9}
}
Return
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Fn modifier: Audio hotkeys, system volume
if (bootcampWindows = 0)
{
; System volume: Mute/Unmute
$F10::hotkeyF10()
hotkeyF10() {
global ejPressed
global fnPressed
if(fnPressed = 1 and ejPressed = 0)
SendInput {Volume_Mute} ; Mute/unmute the master volume.
else
SendInput {F10}
}
Return
; System volume: Volume Down
$F11::hotkeyF11()
hotkeyF11()
{
global ejPressed
global fnPressed
if(fnPressed = 1 and ejPressed = 0)
SendInput {Volume_Down} ; Lower the master volume by 1 interval (typically 5%)
else
SendInput {F11}
}
Return
; System volume: Volume Down
$F12::hotkeyF12()
hotkeyF12()
{
global ejPressed
global fnPressed
if(fnPressed = 1 and ejPressed = 0)
SendInput {Volume_Up} ; Raise the master volume by 1 interval (typically 5%).
else
SendInput {F12}
}
Return
}
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Fn modifier: Arrow keys
; Page Up
$UP::hotkeyPgUp()
hotkeyPgUp()
{
global ejPressed
global fnPressed
if(fnPressed = 1 and ejPressed = 0)
{
if (GetKeyState("Shift"))
SendInput {rCtrl Up}+{PgUp}
else
SendInput {rCtrl Up}{PgUp}
} else
SendInput {UP}
}
Return
; Page Down
$Down::hotkeyPgDn()
hotkeyPgDn()
{
global fnPressed
global ejPressed
global ejPrevState
global fnPrevState
global fnPressed
if(fnPressed = 1 and ejPressed = 0)
SendInput {rCtrl Up}{PgDn}
else
SendInput {Down}
}
Return
; Home
$Left::hotkeyHome()
hotkeyHome()
{
global ejPressed
global fnPressed
if(fnPressed = 1 and ejPressed = 0)
SendInput {rCtrl Up}{Home}
else
SendInput {Left}
}
Return
; End
$Right::hotkeyEnd()
hotkeyEnd()
{
global ejPressed
global fnPressed
if(fnPressed = 1 and ejPressed = 0)
SendInput {rCtrl Up}{End}
else
SendInput {Right}
}
Return
@arjpeg
Copy link

arjpeg commented Aug 22, 2020

Any way can disable the Random window?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment