Windows only and currently set up only for standard US keyboard layout but it's really easy to change for your needs or if you'd rather use your right hand instead. Just modify the Replacements() array below.
Code: Select all
; Program name: Halfsie Keyboard
; Author: missile69 - http://www.purebasic.fr/english/memberlist.php?mode=viewprofile&u=7421
; OS: Windows only. Tested only on Win 7
; License: http://www.gnu.org/licenses/gpl.html
;
; Description: Allows you to type with just your left hand. Most keys from the right half of your keyboard
; are mirrored onto the left keys. Use space key as the modifier key. For example, pressing
; SPACE+G would print H, SPACE+F prints J, etc.
;
; [1\0][2\9][3\8][4\7][5\6]
; [Tab\Backspace ][q\p][w\o][e\i][r\u][t\y]
; [ Caps\Return ][a\;][s\l][d\k][f\j][g\h]
; [ Shift ][z//][x/.][c/,][v/m][b/n]
; [ Ctrl ][ Win ][ Alt ][ Space ]
;
;** Please do not kill program through PB's IDE. Exit through tray icon menu to properly release keyboard hook **
EnableExplicit
Enumeration
#imgIcon
#mnuExit
EndEnumeration
; When you press the space key, this is the time the program waits for a second key press before allowing the
; space key's auto-repeat to keep sending spaces.
#Timeout = 1000 ;ms
Structure NOTIFYICONDATA_v6
cbSize.i
hWnd.i
uID.i
uFlags.i
uCallbackMessage.i
hIcon.i
szTip.s{128}
dwState.i
dwStateMask.l
szInfo.s{256}
StructureUnion
uTimeout.i
uVersion.i
EndStructureUnion
szInfoTitle.s{64}
dwInfoFlags.i
guidItem.GUID
EndStructure
Global Dim Replacements.i(90)
;{ Replacement array setup
Replacements(#VK_TAB) = #VK_BACK ; Backspace
Replacements(#VK_CAPITAL) = #VK_RETURN
Replacements(#VK_1) = #VK_0
Replacements(#VK_2) = #VK_9
Replacements(#VK_3) = #VK_8
Replacements(#VK_4) = #VK_7
Replacements(#VK_5) = #VK_6
Replacements(#VK_Q) = #VK_P
Replacements(#VK_W) = #VK_O
Replacements(#VK_E) = #VK_I
Replacements(#VK_R) = #VK_U
Replacements(#VK_T) = #VK_Y
Replacements(#VK_A) = #VK_OEM_1 ; Semicolon on Standard US Keyboards
Replacements(#VK_S) = #VK_L
Replacements(#VK_D) = #VK_K
Replacements(#VK_F) = #VK_J
Replacements(#VK_G) = #VK_H
Replacements(#VK_Z) = #VK_OEM_2 ; ?/ key on Standard US Keyboards
Replacements(#VK_X) = #VK_OEM_PERIOD
Replacements(#VK_C) = #VK_OEM_COMMA
Replacements(#VK_V) = #VK_M
Replacements(#VK_B) = #VK_N
;}
Procedure SendKey(vKey.i)
Dim KeyData.INPUT(1)
KeyData(0)\type = #INPUT_KEYBOARD
KeyData(0)\ki\wVk = vKey
KeyData(0)\ki\dwFlags = 0
KeyData(1)\type = #INPUT_KEYBOARD
KeyData(1)\ki\wVk = vKey
KeyData(1)\ki\dwFlags = #KEYEVENTF_KEYUP
SendInput_(1, @KeyData(0), SizeOf(INPUT))
;Delay(20)
SendInput_(1, @KeyData(1), SizeOf(INPUT))
EndProcedure
Procedure.i myKBLLCallback(nCode.i, wParam.i, *lParam.KBDLLHOOKSTRUCT)
Static SpaceDown.b, didReplace.b, mySpace.b, blockedPress.i
Define r.i, ellapsed.i, vKeyCode.i = *lParam\vkCode
If nCode < 0
;Must pass on message without processing
r = CallNextHookEx_(#Null, nCode, wParam, *lParam)
Else
;Debug wParam
If vKeyCode = #VK_SPACE
If wParam = #WM_KEYDOWN
;if space is down and it's been over #Timeout ms since we blocked the press
;then stop blocking the space message.
If SpaceDown And blockedPress
;Check how long it's been since we blocked the space keydown message
ellapsed = *lParam\time - blockedPress
If Sign(ellapsed) ;Positive number
;Debug "ellapsed: " + Str(ellapsed)
If ellapsed > #Timeout
;don't block the space
Goto DontBlockMessage
Else
Goto BlockMessage
EndIf
Else
If blockedPress - *lParam\time > #Timeout
;don't block the space
Goto DontBlockMessage
Else
Goto BlockMessage
EndIf
EndIf
Else
If mySpace = #False
;When space key's #wm_keydown message is received and it's not one we generated,
;block it.
SpaceDown = #True
blockedPress = *lParam\time
Goto BlockMessage
Else
;If this was a space message we inserted, don't process it.
;mySpace = #False
Goto DontBlockMessage
EndIf
EndIf
Else
;On #wm_keyup - If we didn't replace any characters while space was down (user just pressed
;and released space key), then send the space key that we blocked on #wm_keydown.
SpaceDown = #False
;Reset time since we blocked space key
If mySpace = #False
blockedPress = 0
If didReplace = #False
mySpace = #True
SendKey(#VK_SPACE)
Else
didReplace = #False
EndIf
;Block the #wm_keyup message since it's corresponding keydown message was blocked earlier.
Goto BlockMessage
Else
mySpace = #False
EndIf
EndIf
Else
; If space key is down and we receive another keypress, look for it in our replacements array
; and send the replacement character if found.
If SpaceDown
If vKeyCode < 91 And wParam = #WM_KEYDOWN
;Debug vKeyCode
If Replacements(vKeyCode)
;Debug "prevented vkey " + Chr(vKeyCode)
SendKey(Replacements(vKeyCode))
didReplace = #True
Goto BlockMessage
EndIf
EndIf
EndIf
EndIf
DontBlockMessage:
r = CallNextHookEx_(#Null, nCode, wParam, *lParam)
ProcedureReturn r
BlockMessage:
ProcedureReturn 1
EndIf
EndProcedure
Procedure ShowBalloonTip(winID.i, sysTrayIcon, title.s, text.s, timeout.i)
Protected r.i
Define IconData.NOTIFYICONDATA_v6
If OSVersion() = #PB_OS_Windows_2000
IconData\cbSize = #NOTIFYICONDATA_V2_SIZE
ElseIf OSVersion() > (#PB_OS_Windows_XP - 1)
IconData\cbSize = SizeOf(NOTIFYICONDATA_v6)
Else
Debug "WILL NOT WORK ON THIS OS"
EndIf
IconData\hWnd = winID
IconData\uID = sysTrayIcon
IconData\uFlags = #NIF_INFO
IconData\szInfoTitle = title
IconData\szInfo = text
IconData\uTimeout = timeout ;ms
IconData\dwInfoFlags = #NIIF_INFO
r = Shell_NotifyIcon_(#NIM_MODIFY, @IconData)
ProcedureReturn r
EndProcedure
If OpenWindow(0, 0, 0, 300, 200, "KB Hook", #PB_Window_ScreenCentered|#PB_Window_SystemMenu|#PB_Window_Invisible)
Define e.i, hook.i
CatchImage(#imgIcon, ?TrayIcon_start)
AddSysTrayIcon(0, WindowID(0), ImageID(#imgIcon))
SysTrayIconToolTip(0, "Halfsie Keyboard - Click for options")
If CreatePopupMenu(0)
MenuItem(1, "Exit")
;set hook
hook = SetWindowsHookEx_(#WH_KEYBOARD_LL, @myKBLLCallback(), GetModuleHandle_(#Null), #Null)
If hook
ShowBalloonTip(WindowID(0), 0, "Halfsie Keyboard", "Press SPACE plus any left-hand key to type a righ-hand character. Use SPACE + Tab for BACKSPACE", 5000)
Repeat
e = WaitWindowEvent()
If e = #PB_Event_SysTray
DisplayPopupMenu(0, WindowID(0))
ElseIf e = #PB_Event_Menu
e = #PB_Event_CloseWindow
EndIf
Until e = #PB_Event_CloseWindow
;remove hook
UnhookWindowsHookEx_(hook)
Else
Debug "Error: " + Str(GetLastError_())
EndIf
EndIf
EndIf
DataSection
TrayIcon_start:
; size : 1406 bytes
Data.q $1010000100010000,$0568000800010000,$0028000000160000,$0020000000100000,$0000000800010000
Data.q $0000000000000000,$0000000000000000,$1E1E000000000000,$233B00002137001E,$34570000243D0000
Data.q $3E6800003D670000,$4574000045730000,$4A7C000546730000,$4A4A004646460000,$528900686868004A
Data.q $59940000558E0000,$64A700005E9E0000,$69B0000069AF0000,$76C500006DB70000,$7DD000007ACC0000
Data.q $84D8000082D80000,$88E2000086E00008,$97F900008CEA0000,$9BFE000098FE0007,$959500089CFF0008
Data.q $9D9D009999990095,$A5A500A1A1A1009D,$AEAE00A9A9A900A5,$BBBB00B5B5B500AE,$DBDB00D6D6D600BB
Data.q $E1E100DDDDDD00DB,$000000E4E4E400E1,$500000212F000000,$9000004C70000037,$CF000079B0000063
Data.q $FF1100A6F000008F,$FF5100BEFF3100B4,$FF9100D3FF7100C8,$FFD100E5FFB100DC,$000000FFFFFF00F0
Data.q $5000000E2F000000,$9000002270000018,$CF000036B000002C,$FF11004AF0000040,$FF510071FF31005B
Data.q $FF91009DFF710087,$FFD100C9FFB100B2,$000000FFFFFF00DF,$500400002F020000,$9008000070060000
Data.q $CF0B0000B00A0000,$FF200000F00E0000,$FF5B0031FF3D0012,$FF980071FF790051,$FFD400B1FFB50091
Data.q $000000FFFFFF00D1,$502200002F140000,$903D000070300000,$CF590000B04C0000,$FF780000F0670000
Data.q $FF9C0031FF8A0011,$FFC00071FFAE0051,$FFE400B1FFD20091,$000000FFFFFF00D1,$504000002F260000
Data.q $90740000705A0000,$CFA90000B08E0000,$FFD10000F0C20000,$FFDE0031FFD80011,$FFE90071FFE30051
Data.q $FFF600B1FFEF0091,$000000FFFFFF00D1,$41500000262F0000,$749000005B700000,$A9CF00008EB00000
Data.q $D2FF0000C3F00000,$DDFF0031D8FF0011,$EAFF0071E4FF0051,$F6FF00B1F0FF0091,$000000FFFFFF00D1
Data.q $22500000142F0000,$3E90000030700000,$5BCF00004DB00000,$79FF000069F00000,$9DFF00318AFF0011
Data.q $C1FF0071AFFF0051,$E5FF00B1D2FF0091,$000000FFFFFF00D1,$04500000032F0000,$0990000006700000
Data.q $0CCF00000AB00000,$20FF00000EF00000,$5CFF00313EFF0012,$97FF00717AFF0051,$D4FF00B1B6FF0091
Data.q $000000FFFFFF00D1,$0050000E002F0000,$0090002100700017,$00CF003600B0002B,$11FF004900F00040
Data.q $51FF007031FF005A,$91FF009C71FF0086,$D1FF00C8B1FF00B2,$000000FFFFFF00DF,$00500020002F0000
Data.q $0090004C00700036,$00CF007800B00062,$11FF00A400F0008E,$51FF00BE31FF00B3,$91FF00D171FF00C7
Data.q $D1FF00E5B1FF00DC,$000000FFFFFF00F0,$004B002F002C0000,$0087007000690050,$00C400B000A50090
Data.q $11F000F000E100CF,$51F400FF31F200FF,$91F700FF71F600FF,$D1FB00FFB1F900FF,$000000FFFFFF00FF
Data.q $002D002F001B0000,$00520070003F0050,$007600B000630090,$119900F0008800CF,$51B400FF31A600FF
Data.q $91CF00FF71C200FF,$D1EB00FFB1DC00FF,$000000FFFFFF00FF,$000E002F00080000,$001B007000150050
Data.q $002600B000210090,$113E00F0002C00CF,$517100FF315800FF,$91A600FF718C00FF,$D1DA00FFB1BF00FF
Data.q $141900FFFFFF00FF,$1414141414141414,$191F141414141414,$1414141414141414,$041F141414141414
Data.q $3030303014141430,$0B1F140330303030,$2F2F2F1414142F28,$301F140C282F2F2F,$30301414142F2F2F
Data.q $301F14302F2F2F30,$2F1414142F2F2F2F,$301F14302F2F2F2F,$1414143F303F300B,$301F14302F0B303F
Data.q $14142F2F2F2F2F2F,$301F14302F2F2F14,$143F303F303F303F,$301F14302F221414,$2F2E2E2E2E2F2F2F
Data.q $0B1F14302F141414,$3030303030303030,$171F140B14141430,$1F1F1F1F15301616,$1F1F14141414141F
Data.q $1F1F1F1F1C301F1F,$1F1F141414191F1F,$1F1F1F1F301D1F1F,$1F1F1414191F1F1F,$1F1F1830181F1F1F
Data.q $212114191F1F1F1F,$211E302021212121,$00001A2021212121,$0000000000000000,$0000000000000000
Data.q $0000000000000000,$0000000000000000,$0000000000000000,$0000000000000000,$0000000000000000
Data.b $00,$00,$00,$00,$00,$00
TrayIcon_end:
EndDataSection