 
This program creates an On Screen Keyboard with which you can provide 'keyboard' input using the mouse. As it stands it is of limited practical usefulness, but the code could be enhanced and customised to provide additional facilities such as supporting cursor and function keys. UK and US layouts are provided as standard, but other layouts could easily be added.
| Download OSKBBC.BBC | Run OSKBBC.EXE | 
|---|
      REM On Screen Keyboard written in BBC BASIC for Windows
      REM (C) Richard T. Russell, http://www.rtrussell.co.uk/
      REM!Window 750,250,client,xpstyle,hidden
      Version$ = "1.30"
      REM Ensure an error message is visible even if window is hidden:
      ON ERROR SYS "MessageBox", @hwnd%, REPORT$, 0, 0 : QUIT
      REM Install required libraries:
      INSTALL @lib$+"WINLIB2A"
      INSTALL @lib$+"WINLIB5"
      REM!WC Windows Constants (automatically inserted by WinConsts utility):
      BM_SETSTATE = &F3
      BS_DEFPUSHBUTTON = &1
      BS_MULTILINE = &2000
      CF_INITTOLOGFONTSTRUCT = &40
      CF_SCREENFONTS = &1
      CSIDL_APPDATA = &1A
      ES_NUMBER = &2000
      GWL_EXSTYLE = -20
      GWL_STYLE = -16
      HWND_NOTOPMOST = -2
      HWND_TOPMOST = -1
      IDCANCEL = 2
      IDOK = 1
      MB_ICONINFORMATION = &40
      MF_CHECKED = &8
      MF_POPUP = &10
      MF_UNCHECKED = &0
      SWP_NOMOVE = &2
      SWP_NOSIZE = &1
      SWP_NOZORDER = &4
      SW_SHOW = 5
      WM_SETFONT = &30
      WS_DISABLED = &8000000
      WS_EX_NOACTIVATE = &8000000
      WS_GROUP = &20000
      WS_MAXIMIZEBOX = &10000
      WS_THICKFRAME = &40000
      REM Disable the Escape key:
      *ESC OFF
      REM Declare the required global structures and arrays:
      DIM size{cx%,cy%}, rect{l%,t%,r%,b%}
      DIM lf{Height%, Width%, Escapement%, Orientation%, \
      \      Weight%, Italic&, Underline&, StrikeOut&, \
      \      CharSet&, OutPrecision&, ClipPrecision&, \
      \      Quality&, PitchAndFamily&, FaceName&(30)}
      DIM ch$(4,13), w(4,13), h(4,13), hw%(4,13)
      REM Set the default font:
      lf.FaceName&() = "Arial"
      lf.Height% = 26
      lf.Weight% = 600
      REM Read the settings:
      IniFile$ = FNspecialfolder(CSIDL_APPDATA)+"oskbbc.ini"
      SYS "GetPrivateProfileInt", "settings", "ontop", 1, IniFile$ TO AlwaysOnTop%
      SYS "GetPrivateProfileInt", "settings", "delay", 50, IniFile$ TO AutoRepeatDelay%
      SYS "GetPrivateProfileInt", "settings", "speed", 10, IniFile$ TO AutoRepeatSpeed%
      SYS "GetPrivateProfileInt", "keyboard", "layout", 0, IniFile$ TO Layout%
      SYS "GetPrivateProfileStruct", "settings", "font", lf{}, DIM(lf{}), IniFile$
      REM Create the fonts and determine the key size:
      SYS "CreateFontIndirect", lf{} TO hFontLarge%
      height% = lf.Height%
      lf.Height% *= 3/4
      SYS "CreateFontIndirect", lf{} TO hFontSmall%
      lf.Height% = height%
      SYS "SelectObject", @memhdc%, hFontLarge% TO oldfont%
      SYS "DeleteObject", oldfont%
      SYS "GetTextExtentPoint32", @memhdc%, "X", 1, size{}
      KeySize% = size.cy% * 2
      REM Create the menus:
      SYS "CreatePopupMenu" TO hFile%
      SYS "AppendMenu", hFile%, 0, 11, "E&xit"
      SYS "CreatePopupMenu" TO hLayout%
      SYS "AppendMenu", hLayout%, 0, 20, "U&K"
      SYS "AppendMenu", hLayout%, 0, 21, "U&S"
      SYS "CreatePopupMenu" TO hSettings%
      SYS "AppendMenu", hSettings%, 0, 30, "&Always on top"
      SYS "AppendMenu", hSettings%, 0, 31, "&Typematic..."
      SYS "AppendMenu", hSettings%, 0, 32, "&Font and size..."
      SYS "CreatePopupMenu" TO hHelp%
      SYS "AppendMenu", hHelp%, 0, 41, "&About On Screen Keyboard..."
      SYS "CreateMenu" TO hMenu%
      SYS "AppendMenu", hMenu%, MF_POPUP, hFile%, "&File"
      SYS "AppendMenu", hMenu%, MF_POPUP, hLayout%, "&Layout"
      SYS "AppendMenu", hMenu%, MF_POPUP, hSettings%, "&Settings"
      SYS "AppendMenu", hMenu%, MF_POPUP, hHelp%, "&Help"
      SYS "SetMenu", @hwnd%, hMenu%
      SYS "DrawMenuBar", @hwnd%
      SYS "CheckMenuItem", hLayout%, 20+Layout%, MF_CHECKED
      REM Handle click events:
      Click% = 0
      ON SYS Click% = @wparam% : RETURN
      REM Set the window title:
      SYS "SetWindowText", @hwnd%, "On Screen Keyboard - " + \
      \   "Left Click: lowercase, Right Click: uppercase, Middle Click: control code"
      REM Inactivate the window (we don't want keyboard input to come here!):
      SYS "GetWindowLong", @hwnd%, GWL_EXSTYLE TO exstyle%
      SYS "SetWindowLong", @hwnd%, GWL_EXSTYLE, exstyle% OR WS_EX_NOACTIVATE
      REM Disable resizing or maximizing:
      SYS "GetWindowLong", @hwnd%, GWL_STYLE TO style%
      style% AND= NOT (WS_MAXIMIZEBOX OR WS_THICKFRAME)
      SYS "SetWindowLong", @hwnd%, GWL_STYLE, style%
      REM Initialise the window to the required size:
      rect.r% = 15*KeySize%
      rect.b% = 5*KeySize%
      SYS "AdjustWindowRect", rect{}, style%, 1
      SYS "SetWindowPos", @hwnd%, 0,0,0, rect.r%-rect.l%, rect.b%-rect.t%, \
      \                   SWP_NOMOVE OR SWP_NOZORDER
      REM Display the window and set it topmost if requested:
      SYS "ShowWindow", @hwnd%, SW_SHOW
      IF AlwaysOnTop% THEN
        SYS "SetWindowPos", @hwnd%, HWND_TOPMOST, 0, 0, 0, 0, \
        \                                   SWP_NOMOVE OR SWP_NOSIZE
        SYS "CheckMenuItem", hSettings%, 30, MF_CHECKED
      ENDIF
      REM Set background colour:
      COLOUR 128+12
      VDU 26
      CLS
      REM Create the template for the Typematic dialogue box:
      Tdlg% = FN_newdialog("Typematic settings", 64, 51, 146, 60, 8, 500)
      PROC_pushbutton(Tdlg%, "OK", IDOK, 9, 40, 56, 14, WS_GROUP OR BS_DEFPUSHBUTTON)
      PROC_pushbutton(Tdlg%, "Cancel", IDCANCEL, 80, 40, 56, 14, WS_GROUP)
      PROC_static(Tdlg%, "Repeat delay (milliseconds):", 100, 10, 7, 96, 16, 0)
      PROC_static(Tdlg%, "Repeat rate (chars/second):", 101, 10, 24, 96, 16, 0)
      PROC_editbox(Tdlg%, "", 102, 106, 5, 29, 12, ES_NUMBER)
      PROC_editbox(Tdlg%, "", 103, 106, 22, 29, 12, ES_NUMBER)
      REM Set required keyboard layout:
      CASE Layout% OF
        WHEN 0: REM UK
          ch$() = "¬`","!1","""2","£3","$4","%5","^6","&7","*8","(9",")0","_-","+=","Backspace", \
          \       "Tab","Q","W","E","R","T","Y","U","I","O","P","{[","}]","Enter", \
          \       "Caps","A","S","D","F","G","H","J","K","L",":;","@'","~#","", \
          \       "Shift","|\","Z","X","C","V","B","N","M","<,",">.","?/","Shift","", \
          \       "Control","Alt","Space","Alt Gr","Control"
          REM relative widths:
          w() = 1,1,1,1,1,1,1,1,1,1,1,1,1,2.0, \
          \   1.5,1,1,1,1,1,1,1,1,1,1,1.15,1.15,1.2, \
          \   1.8,1,1,1,1,1,1,1,1,1,1,1,1,1.0, \
          \   1.3,1,1,1,1,1,1,1,1,1,1,1,2.7,0, \
          \   2.0,2.0,7.0,2.0,2.0
          REM relative heights:
          h() = 1 : h(1,13) = 2
        WHEN 1: REM US
          ch$() = "~`","!1","@2","#3","$4","%5","^6","&7","*8","(9",")0","_-","+=","Backspace", \
          \       "Tab","Q","W","E","R","T","Y","U","I","O","P","{[","}]","|\", \
          \       "Caps","A","S","D","F","G","H","J","K","L",":;","""'","Enter", "", \
          \       "Shift","","Z","X","C","V","B","N","M","<,",">.","?/","Shift","", \
          \       "Control","Alt","Space","Alt Gr","Control"
          REM relative widths:
          w() = 1,1,1,1,1,1,1,1,1,1,1,1,1,2.0, \
          \   1.5,1,1,1,1,1,1,1,1,1,1,1.15,1.15,1.2, \
          \   1.8,1,1,1,1,1,1,1,1,1,1,1,2.2,0, \
          \   2.3,0,1,1,1,1,1,1,1,1,1,1,2.7,0, \
          \   2.0,2.0,7.0,2.0,2.0
          REM relative heights:
          h() = 1
      ENDCASE
      REM Draw the 'keyboard':
      Y = 0
      FOR R% = 0 TO DIM(ch$(),1)
        X = 0
        FOR C% = 0 TO DIM(ch$(),2)
          ch$ = ch$(R%,C%)
          IF ch$ <> "" THEN
            IF LEN(ch$) = 2 ch$ = LEFT$(ch$) + CHR$(13) + RIGHT$(ch$)
            IF LEFT$(ch$,1) = "&" ch$ = "&" + ch$ : REM 'escape' the & symbol
            style% = BS_MULTILINE
            CASE ch$ OF
              WHEN "Shift","Control","Alt","Caps", "Alt Gr": style% OR= WS_DISABLED
            ENDCASE
            hw%(R%,C%) = FN_button(ch$, X, Y, KeySize%*w(R%,C%), KeySize%*h(R%,C%), \
            \                      0, style%)
            IF LEN(ch$) = 1 THEN
              SYS "SendMessage", hw%(R%,C%), WM_SETFONT, hFontLarge%, 1
            ELSE
              SYS "SendMessage", hw%(R%,C%), WM_SETFONT, hFontSmall%, 1
            ENDIF
          ENDIF
          X += KeySize% * w(R%,C%)
        NEXT C%
        Y += KeySize%
      NEXT R%
      REM Main loop:
      Restart% = FALSE
      timeout% = AutoRepeatDelay%
      REPEAT
        WAIT 0
        REM Check for mouse clicks:
        MOUSE X%,Y%,B%
        IF B% THEN
          PROCclick(B%,timeout%)
          timeout% = AutoRepeatSpeed%
        ELSE
          timeout% = AutoRepeatDelay%
        ENDIF
        REM Deactivate window if mouse over keyboard region:
        IF X% > 0 IF (X%/2) < @vdu.tr% IF Y% > 0 IF (Y%/2) < @vdu.tb% THEN
          SYS "GetWindowLong", @hwnd%, GWL_EXSTYLE TO exstyle%
          IF (exstyle% AND WS_EX_NOACTIVATE) = 0 THEN
            SYS "SetWindowLong", @hwnd%, GWL_EXSTYLE, exstyle% OR WS_EX_NOACTIVATE
            SYS "ShowWindow", @hwnd%, SW_SHOW
          ENDIF
        ENDIF
        REM Process menu selections:
        click% = 0
        SWAP click%,Click%
        CASE click% OF
          WHEN 11: QUIT
          WHEN 20,21,22,23: PROClayout(click%)
          WHEN 30: PROContop
          WHEN 31: PROCtypematic
          WHEN 32: PROCchoosefont
          WHEN 41: PROCabout
        ENDCASE
        REM Restart if necessary:
        IF Restart% THEN PROCcleanup : RUN
      UNTIL FALSE
      END
      REM Process mouse clicks:
      DEF PROCclick(B%,T%)
      LOCAL pt{},C%,R%,X%,Y%,hw%,exstyle%,ch$
      PRIVATE oldhw%
      DIM pt{x%,y%}
      TIME = 0
      REM Find position of mouse, in Windows coordinates:
      SYS "GetCursorPos", pt{}
      REM Find which window (if any) the mouse is over:
      SYS "WindowFromPoint", pt.x%, pt.y% TO hw%
      REM If different key from last time, 'unpress' the old one:
      IF oldhw% IF hw% <> oldhw% SYS "SendMessage", oldhw%, BM_SETSTATE, 0, 0
      REM Activate window if clicked on title or menu bar:
      IF hw% = @hwnd% THEN
        SYS "GetWindowLong", @hwnd%, GWL_EXSTYLE TO exstyle%
        SYS "SetWindowLong", @hwnd%, GWL_EXSTYLE, exstyle% AND NOT WS_EX_NOACTIVATE
        SYS "SetForegroundWindow", @hwnd%
      ENDIF
      REM Check whether the user clicked one of our 'keys':
      FOR R% = 0 TO DIM(hw%(),1)
        FOR C% = 0 TO DIM(hw%(),2)
          IF hw% = hw%(R%,C%) THEN
            SYS "SendMessage", hw%, BM_SETSTATE, 1, 0
            ch$ = ch$(R%,C%)
            IF LEN(ch$) = 1 THEN
              ch$ += CHR$(ASC(ch$)+32) : REM Add lower-case character
            ENDIF
            CASE ch$ OF
              WHEN "Space":     ch$ = " "
              WHEN "Backspace": ch$ = CHR$(8)
              WHEN "Tab":       ch$ = CHR$(9)
              WHEN "Enter":     ch$ = CHR$(13)
            ENDCASE
            IF B% AND 4 PROCfake(ASC(RIGHT$(ch$))) : REM Left click = 'normal'
            IF B% AND 1 PROCfake(ASC(ch$))         : REM Right click = 'shift'
            IF B% AND 2 PROCfake(ASC(ch$) AND 31)  : REM Middle click = "ctrl'
            REM Wait for mouse button to be released, or auto-repeat timeout:
            REPEAT
              WAIT 0
              MOUSE X%,Y%,B%
            UNTIL B% = 0 OR TIME > T%
            IF B% = 0 SYS "SendMessage", hw%, BM_SETSTATE, 0, 0 : REM 'unpress' key
            oldhw% = hw%
            ENDPROC
          ENDIF
        NEXT
      NEXT R%
      oldhw% = 0
      ENDPROC
      REM Change typematic settings:
      DEF PROCtypematic
      LOCAL click%,temp%
      PROC_showdialog(Tdlg%)
      SYS "SetDlgItemInt", !Tdlg%, 102, 10*AutoRepeatDelay%
      SYS "SetDlgItemInt", !Tdlg%, 103, 100/AutoRepeatSpeed%
      REPEAT
        WAIT 1
        click% = 0
        SWAP click%,Click%
      UNTIL click% = IDOK OR click% = IDCANCEL OR !Tdlg% = 0
      IF click% = 1 THEN
        SYS "GetDlgItemInt", !Tdlg%, 102 TO temp%
        AutoRepeatDelay% = temp%/10
        SYS "GetDlgItemInt", !Tdlg%, 103 TO temp%
        AutoRepeatSpeed% = 100/temp%
        SYS "WritePrivateProfileString", "settings", "delay", STR$(AutoRepeatDelay%), IniFile$
        SYS "WritePrivateProfileString", "settings", "speed", STR$(AutoRepeatSpeed%), IniFile$
      ENDIF
      PROC_closedialog(Tdlg%)
      ENDPROC
      REM Set font:
      DEF PROCchoosefont
      LOCAL cf{}, result%
      DIM cf{lStructSize%, hwndOwner%, hdc%, lpLogFont%, \
      \      iPointSize%, flags%, rgbColors%, lCustData%, \
      \      lpfnHook%, lpTemplateName%, hInstance%, lpszStyle%, \
      \      nFontType{l&,h&}, pad{l&,h&}, nSizeMin%, nSizeMax%}
      cf.lStructSize% = DIM(cf{})
      cf.hwndOwner% = @hwnd%
      cf.lpLogFont% = lf{}
      cf.flags% = CF_SCREENFONTS OR CF_INITTOLOGFONTSTRUCT
      SYS "ChooseFont", cf{} TO result%
      IF result% THEN
        SYS "WritePrivateProfileStruct", "settings", "font", lf{}, DIM(lf{}), IniFile$
        Restart% = TRUE
      ENDIF
      ENDPROC
      REM Toggle 'always on top' setting:
      DEF PROContop
      AlwaysOnTop% = -(AlwaysOnTop% == 0)
      IF AlwaysOnTop% THEN
        SYS "SetWindowPos", @hwnd%, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE OR SWP_NOSIZE
        SYS "CheckMenuItem", hSettings%, 30, MF_CHECKED
      ELSE
        SYS "SetWindowPos", @hwnd%, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE OR SWP_NOSIZE
        SYS "CheckMenuItem", hSettings%, 30, MF_UNCHECKED
      ENDIF
      SYS "WritePrivateProfileString", "settings", "ontop", STR$(AlwaysOnTop%), IniFile$
      ENDPROC
      REM Set keyboard layout:
      DEF PROClayout(id%)
      SYS "CheckMenuItem", hLayout%, 20+Layout%, MF_UNCHECKED
      Layout% = id%-20
      SYS "CheckMenuItem", hLayout%, 20+Layout%, MF_CHECKED
      SYS "WritePrivateProfileString", "keyboard", "layout", STR$(Layout%), IniFile$
      Restart% = TRUE
      ENDPROC
      REM About box:
      DEF PROCabout
      SYS "MessageBox", @hwnd%, "On Screen Keyboard version " + Version$ + CHR$13 + \
      \   "written in BBC BASIC for Windows" + CHR$13 +  \
      \   "by Richard Russell, October 2010" + CHR$13 + \
      \   "see http://www.rtrussell.co.uk/", "OSKBBC", MB_ICONINFORMATION
      ENDPROC
      REM Delete GDI and User objects:
      DEF PROCcleanup
      ON SYS OFF
      FOR R% = 0 TO DIM(hw%(),1)
        FOR C% = 0 TO DIM(hw%(),2)
          IF hw%(R%,C%) PROC_closewindow(hw%(R%,C%))
        NEXT
      NEXT R%
      SYS "DeleteObject", hFontLarge%
      SYS "DeleteObject", hFontSmall%
      SYS "DestroyMenu", hMenu%
      SYS "DestroyMenu", hFile%
      SYS "DestroyMenu", hLayout%
      SYS "DestroyMenu", hSettings%
      SYS "DestroyMenu", hHelp%
      ENDPROC
      REM From http://bb4w.wikispaces.com/Faking+keyboard+input
      DEF PROCfake(C%) : LOCAL V%
      SYS "VkKeyScan", C% TO V%
      IF V% AND &100 SYS "keybd_event", 16, 0, 0, 0
      IF V% AND &200 SYS "keybd_event", 17, 0, 0, 0
      IF V% AND &400 SYS "keybd_event", 18, 0, 0, 0
      SYS "keybd_event", V% AND &FF, 0, 0, 0
      SYS "keybd_event", V% AND &FF, 0, 2, 0
      IF V% AND &400 SYS "keybd_event", 18, 0, 2, 0
      IF V% AND &200 SYS "keybd_event", 17, 0, 2, 0
      IF V% AND &100 SYS "keybd_event", 16, 0, 2, 0
      ENDPROC
      REM From https://www.bbcbasic.co.uk/bbcwin/manual/bbcwine.html#specialfolders
      DEF FNspecialfolder(id%)
      LOCAL ppidl%, folder%, malloc%
      DIM folder% LOCAL 255
      SYS "SHGetSpecialFolderLocation", @hwnd%, id%, ^ppidl%
      SYS "SHGetPathFromIDList", ppidl%, folder%
      SYS "SHGetMalloc", ^malloc%
      SYS !(!malloc%+20), malloc%, ppidl% : REM. IMalloc::Free
      = $$folder% + "\"
 
