multikey/inkey/getkey variant for fbgfx screens on Windows

Post your FreeBASIC tips and tricks here. Please don’t post your code without including an explanation.
Zippy
Posts: 1295
Joined: Feb 10, 2006 18:05

multikey/inkey/getkey variant for fbgfx screens on Windows

Postby Zippy » Feb 07, 2010 22:28

I started down this path again (I've tried several times in the past and failed) because of this thread:

http://www.freebasic.net/forum/viewtopic.php?t=14986

The OP wants to use the key sequence alt+3 with an fbgfx screen. I don't know why but it's never been possible to use alt+ any alphanumeric key in fbgfx windows. I think someone(s) tried to address this in the past but gave up (and now is not the time to be making radical changes to effect this).

There are additional limitations with multikey, inkey, and getkey in fbgfx (not a complete list):

Multikey requires that you test for one key per invocation. This makes it diff (for example) to test for multiple keys like ctrl+s. You also must use it in a loop if testing for more than one key. Testing for repeating keys is problematic.

Inkey can't accept alt+functionkey (at least there's no BEEP). Something like ctrl+s returns a single character. Testing for repeating keys is problematic.

Getkey gets.. a single key and blocks until it.. gets it. And removes that key from the keyboard buffer (sometimes useful..).

What I want, what I'm headed towards, is a method that is more flexible in fbgfx windows. I want to test for [almost] any key combination. I don't want to test for repeating keys. I want to be able to block while waiting for a key/keys, or not block at will. I don't want to disable multikey, inkey, or getkey. I think I've mostly succeeded with this code (needs more thorough testing).

What this won't work with:

1. fb versions prior to 0.20b (might but I won't address issues with earlier versions)
2. -lang fblite or -lang qb, requires -lang fb
3. Windows versions <Win2k (might but I won't address..)
4. a non-Windows OS (including DOS). Nor do I believe that it'll work in Wine.

Here's some code, Notes after (as time permits):

Need this saved as multikey-alt.bi:

Code: Select all

'multikey-alt.bi
'MultiKey alternative?, Windows fbgfx only
' probably requires windows versions Win2k+
' requires -lang fb and fb versions 0.20+
' compile -s gui
' tested with fb 0.21b and Vista
'
'============================================== config
#include once "windows.bi"
#include once "fbgfx.bi"
#ifndef MAPVK_VSC_TO_VK
    #define MAPVK_VSC_TO_VK 1 'for later use with MapVirtualKey()
#endif
'
type tagKeyHookStruct
   as ushort repeat
   as ubyte scancode
   as ubyte extended : 1
   as ubyte reserved : 4
   as ubyte alt      : 1
   as ubyte prevkey  : 1
   as ubyte state    : 1
end type
'
type tagKeyTypeStruct
    as ubyte  state     'if = 1 then at least one of the next 3 states are set
    as ubyte  alt       'alt key was pressed
    as ubyte  shift     'shift key
    as ubyte  ctrl      'ctrl key
    as ubyte  scancode  '..scancode of key pressed..
    as ubyte  fill1     'filler1
    as ushort fill2     'filler2
end type
'
dim shared as tagKeyTypeStruct keytypestruct
dim shared as HHOOK KBHandle
dim shared as HANDLE myKeyEvent
'
declare function KBProc1(_
                    byval code as integer, _
                    byval wparam as integer,_
                    byval lparam as integer) as integer
'
myKeyEvent=CreateEvent(_
            NULL,_
            FALSE,_  'FALSE = auto-reset
            FALSE,_  'TRUE  = initially signaled
            "ZippyKeyEvent")
'
'============================================== end config
function KBProc1(_
            byval code as integer, _
            byval wparam as integer,_
            byval lparam as integer) as integer
'         
    static as tagKeyHookStruct Ptr Hook
    static as integer altkey,scode,shiftkey,ctrlkey
    '
    if (Code = HC_ACTION) then
    '                 
        Hook=cast(tagKeyHookStruct Ptr,@lParam)
        scode=Hook->scancode
        altkey=Hook->alt
        ' 
        shiftkey=bit(GetKeyState(VK_SHIFT),15)
        ctrlkey=bit(GetKeyState(VK_CONTROL),15)
        '
        if Hook->state=1 then
            if scode<>&h38 then
                '
                clear(@keytypestruct,0,sizeof(keytypestruct))
                '
                if scode<>&h1D and scode<>&h2A then
                    if altkey then
                        keytypestruct.state=1
                        keytypestruct.alt=1
                    end if
                    if shiftkey then
                        keytypestruct.state=1
                        keytypestruct.shift=1               
                    end if
                    if ctrlkey then
                        keytypestruct.state=1
                        keytypestruct.ctrl=1
                    end if
                    keytypestruct.scancode=scode
                    SetEvent(myKeyEvent)
                end if
            end if
        end if
        '
        if altkey then return 1
    '
    end if
    '   
    KBProc1=CallNextHookEx(KBHandle,code,wparam,lparam)
'
end function

Note [tag]KeyTypeStruct in code above. This is what is visible to you in your code. Each completed key event returns the state of alt|shift|ctrl and the scancode of the "loose" key.

Test1:

Code: Select all

'multikey-alt-test1.bas test wait in loop, test input
'MultiKey alternative?, Windows fbgfx only
' probably requires windows versions Win2k+
' requires -lang fb and fb versions 0.20+
' compile -s gui
' tested with fb 0.21b and Vista
'
#include once "multikey-alt.bi"
'
dim as integer res
dim as HWND hwnd
'
Screen 11 'requires fbGFX screen, not just console
'
ScreenControl FB.GET_WINDOW_HANDLE,cast(integer,hwnd)
'
if hwnd<=0 then
    beep:beep
    print "Requires gfx window, sleeping to Exit"
    sleep
    end
end if
'
'set our keyboard handler for this thread
KBHandle=SetWindowsHookEx(_
                    WH_KEYBOARD,_
                    cast(HOOKPROC,@KBProc1),_
                    NULL,_
                    GetWindowThreadProcessId(hwnd,NULL))
'
if KBHandle=0 then
    beep:beep
    print "Failed to intialize keyhandler, sleeping to Exit"
    sleep
    end
end if
'
'example usage.. test loop
print "Try alt|shift|ctrl with other keys, <Esc> to continue":print
dim as string k
while k<>chr(27)' <Esc> to exit test loop
    '
    k=inkey 'test for program closure below..
    '
    res=WaitForSingleObject(myKeyEvent,100)
    if res=WAIT_OBJECT_0 then
        '
        with keytypestruct
        '   
            'show keys pressed..
            if .state then
                if .alt   then print "alt + ";
                if .shift then print "shift + ";
                if .ctrl  then print "ctrl + ";
            end if
            print " ScanCode: ";hex(.scancode)
            '
            'test for alt+3
            if .scancode=&h4 _
                andAlso .alt _
                andAlso not .ctrl  _
                andAlso not .shift _
            then print "alt+3 was pressed"
        '
        end with
        '
    end if
    '
    'can we still trap window closure?
    if k=chr(255) & "k" then beep:print "Window closure attempt"
    '
wend
while inkey<>"":wend 'clear the keyboard buffer, like multikey
'
'test input for anomalies..
dim as string teststring
dim as double testdouble
print
input "Test string input: ";teststring
if teststring<>"" then
    print "Input was: ";teststring
else
    print "Input was <blank>?"
end if
print
input "Test numeric input: ";testdouble
if testdouble<>0 then
    print "Input was: ";testdouble
else
    print "Input was <blank>?"
end if
'end test
'
print
print "Sleeping to Exit.. press a key"
sleep
'
UnhookWindowsHookEx(KBHandle)
'
'done...


Test2:

Code: Select all

'multikey-alt-test2.bas  test wait forever
'MultiKey alternative?, Windows fbgfx only
' probably requires windows versions Win2k+
' requires -lang fb and fb versions 0.20+
' compile -s gui
' tested with fb 0.21b and Vista
'
#include once "multikey-alt.bi"
'
dim as integer res
dim as HWND hwnd
'
Screen 11 'requires fbGFX screen, not just console
'
ScreenControl FB.GET_WINDOW_HANDLE,cast(integer,hwnd)
'
if hwnd<=0 then
    beep:beep
    print "Requires gfx window, sleeping to Exit"
    sleep
    end
end if
'
'set our keyboard handler for this thread
KBHandle=SetWindowsHookEx(_
                    WH_KEYBOARD,_
                    cast(HOOKPROC,@KBProc1),_
                    NULL,_
                    GetWindowThreadProcessId(hwnd,NULL))
'
if KBHandle=0 then
    beep:beep
    print "Failed to intialize keyhandler, sleeping to Exit"
    sleep
    end
end if
'
'example usage.. wait forever
print "Try alt|shift|ctrl with other keys":print
res=WaitForSingleObject(myKeyEvent,INFINITE)
if res=WAIT_OBJECT_0 then
    '
    with keytypestruct
    '   
        'show keys pressed..
        if .state then
            if .alt   then print "alt + ";
            if .shift then print "shift + ";
            if .ctrl  then print "ctrl + ";
        end if
        print " ScanCode: ";hex(.scancode)
        '
        'test for alt+3
        if .scancode=&h4 _
            andAlso .alt _
            andAlso not .ctrl  _
            andAlso not .shift _
        then print "alt+3 was pressed"
    '
    end with
    '
end if
while inkey<>"":wend 'clear the keyboard buffer, like multikey
'
print
print "Sleeping to Exit.. press a key"
sleep
'
UnhookWindowsHookEx(KBHandle)
'
'done...


Test3:

Code: Select all

'multikey-alt-test3.bas  test wait 5 seconds
'MultiKey alternative?, Windows fbgfx only
' probably requires windows versions Win2k+
' requires -lang fb and fb versions 0.20+
' compile -s gui
' tested with fb 0.21b and Vista
'
#include once "multikey-alt.bi"
'
dim as integer res
dim as HWND hwnd
'
Screen 11 'requires fbGFX screen, not just console
'
ScreenControl FB.GET_WINDOW_HANDLE,cast(integer,hwnd)
'
if hwnd<=0 then
    beep:beep
    print "Requires gfx window, sleeping to Exit"
    sleep
    end
end if
'
'set our keyboard handler for this thread
KBHandle=SetWindowsHookEx(_
                    WH_KEYBOARD,_
                    cast(HOOKPROC,@KBProc1),_
                    NULL,_
                    GetWindowThreadProcessId(hwnd,NULL))
'
if KBHandle=0 then
    beep:beep
    print "Failed to intialize keyhandler, sleeping to Exit"
    sleep
    end
end if
'
'example usage.. wait 5 seconds
print "Try alt|shift|ctrl with other keys"
print " 5 second timeout"
print
res=WaitForSingleObject(myKeyEvent,5000)
if res=WAIT_OBJECT_0 then
    '
    with keytypestruct
    '   
        'show keys pressed..
        if .state then
            if .alt   then print "alt + ";
            if .shift then print "shift + ";
            if .ctrl  then print "ctrl + ";
        end if
        print " ScanCode: ";hex(.scancode)
        '
        'test for alt+3
        if .scancode=&h4 _
            andAlso .alt _
            andAlso not .ctrl  _
            andAlso not .shift _
        then print "alt+3 was pressed"
    '
    end with
    '
else
    if res=WAIT_TIMEOUT   then print "Timed-out waiting for input"
end if
while inkey<>"":wend 'clear the keyboard buffer, like multikey
'
print
print "Sleeping to Exit.. press a key"
sleep
'
UnhookWindowsHookEx(KBHandle)
'
'done...


Test4:

Code: Select all

'multikey-alt-test4.bas  test wait in thread
'MultiKey alternative?, Windows fbgfx only
' probably requires windows versions Win2k+
' requires -lang fb and fb versions 0.20+
' compile -s gui
' tested with fb 0.21b and Vista
'
#include once "multikey-alt.bi"
'
dim as integer res
dim as HWND hwnd
dim as any ptr thptr
declare sub testThread()
'
Screen 11 'requires fbGFX screen, not just console
'
ScreenControl FB.GET_WINDOW_HANDLE,cast(integer,hwnd)
'
if hwnd<=0 then
    beep:beep
    print "Requires gfx window, sleeping to Exit"
    sleep
    end
end if
'
'set our keyboard handler for this thread
KBHandle=SetWindowsHookEx(_
                    WH_KEYBOARD,_
                    cast(HOOKPROC,@KBProc1),_
                    NULL,_
                    GetWindowThreadProcessId(hwnd,NULL))
'
if KBHandle=0 then
    beep:beep
    print "Failed to intialize keyhandler, sleeping to Exit"
    sleep
    end
end if
'
'example usage.. wait in thread
thptr=threadCreate(cast(any ptr,@testThread))
'
dim as integer c
while c<30
    print c,time
    sleep 1000
    c+=1
wend
'
print
print "Sleeping to Exit.. press a key"
sleep
'
UnhookWindowsHookEx(KBHandle)
'
'done...
sub testThread()
    dim as integer res
    print "Entered thread:"
    print "Try alt|shift|ctrl with other keys":print
    res=WaitForSingleObject(myKeyEvent,INFINITE)
    if res=WAIT_OBJECT_0 then
        '
        print "Thread: ";
        '
        with keytypestruct
        '   
            'show keys pressed..
            if .state then
                if .alt   then print "alt + ";
                if .shift then print "shift + ";
                if .ctrl  then print "ctrl + ";
            end if
            print " ScanCode: ";hex(.scancode)
            '
            'test for alt+3
            if .scancode=&h4 _
                andAlso .alt _
                andAlso not .ctrl  _
                andAlso not .shift _
            then print "alt+3 was pressed"
        '
        end with
        '
    end if
    while inkey<>"":wend 'clear the keyboard buffer, like multikey
    print "Exited thread.."
end sub


Known issues:

1. The alt key is eaten. This means that you can't test for an alt+ key combination by other means (i.e., inkey). But this doesn't really work anyway, and that's the problem.
2. Entering alt+ 3 digits from numeric keypad will fail.
3. Changing screens after the keyboard hook is installed (Set) will stop the hook and probably cause all sorts of strange things to happen.
4. That's it. May be problem with rightshift key detection.

The upside is that you can with this code detect any combinaton of alt+shift+ctrl+key, or just a key (except you can't test just for alt or shift or ctrl). The key hook function only reports when the last key is UP and you will not encounter repeating keys (this ain't for games..).
agamemnus
Posts: 1842
Joined: Jun 02, 2005 4:48

Postby agamemnus » Feb 08, 2010 19:21

Multikey requires that you test for one key per invocation. This makes it diff (for example) to test for multiple keys like ctrl+s. You also must use it in a loop if testing for more than one key. Testing for repeating keys is problematic.


Really? I was under the impression that you could do if multikey(ctrl_code) and multikey(s_code) and there wouldn't be any problems with this?
Zippy
Posts: 1295
Joined: Feb 10, 2006 18:05

Postby Zippy » Feb 08, 2010 20:01

agamemnus wrote:<snip>
Really? I was under the impression that you could do if multikey(ctrl_code) and multikey(s_code) and there wouldn't be any problems with this?


You are correct, you can, but there are still issues.

Try this with ctrl+s and alt+s, then try holding either combination down..

Code: Select all

'test multikey alt/ctrl+s
'you have to ctrl+c to end this
#include once "fbgfx.bi"
    Using fb
   
while (1)

if multikey(SC_ALT) and multikey(SC_S) then ? "alt+s"
if multikey(SC_CONTROL) and multikey(SC_S) then ? "ctrl+s"

sleep 100

wend

I get 2 of every alt+s combo, 1 of ctrl+s with single press/release. You can more or less control this behavior by clearing the keyboard buffer in the loop. Then you need to adjust the loop timing delay, tricky. Keys can get "lost". Repeats can be problematic.
agamemnus
Posts: 1842
Joined: Jun 02, 2005 4:48

Postby agamemnus » Feb 11, 2010 21:58

I see. I haven't looked at your code in depth, but would you say that this problem is caused by the fact that you can't specify exactly when multikey collects key press data from the buffer?

Return to “Tips and Tricks”

Who is online

Users browsing this forum: No registered users and 2 guests