Halfsie One-handed keyboard

Developed or developing a new product in PureBasic? Tell the world about it.
missile69
User
User
Posts: 25
Joined: Mon Feb 21, 2011 12:15 pm

Halfsie One-handed keyboard

Post by missile69 »

Simple program to let you type with using just your left hand. Uses the space bar as the modifier. Pressing Space+5 prints 6, Space+4 prints 7, Space+3 prints 8 etc. I find it useful when using Photoshop or Illustrator. I can keep my right hand on the mouse, while still being able to use the common keyboard shortcuts like 'i' and 'p' for eyedropper and pen too respectively.

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