Something similar to _kbhit()?

I’m looking for a predicate (or a way to implement it) for emulating the behavior of the C function _kbhit. This function returns a nonzero value if a key has been pressed (corresponding to the key code) and otherwise, it returns 0. Reading the docs, I did not find it and thought that the following code might implement it:

go :-
  current_input(Input),
  character_count(Input, C),
  loop(Input, C).
  
loop(Input, C) :-
  repeat,
  character_count(Input, C0),
  (C == C0
   -> % Do not wait for user input, do some stuff
      % and repeat
      fail
   ;  with_tty_raw(get_single_char(_Code)),
      % Do some other stuff with _Code
      % and loop again
      character_count(Input, C1),
      loop(Input, C1) 
  ).

But maybe I’m missing something because the character count increases even when there is no user input in the default current input stream (keyboard).
This was tested in the swipl-win console of SWI-Prolog 8.5.11 running on Windows 10.

Hmm. _kbhit() is a Windows console function. with_tty_raw/1 runs the terminal raw, as does get_single_char/1, so using the two doesn’t make sense. It is intended for with_tty_raw(Goal) and simply use get_code/2, etc. such that we do not need to switch all the time between raw and cooked mode and (thus) avoid timing issues. Also in raw mode it blocks for input though. The difference is that it does not echo and doesn’t wait for an entire line but returns the character when it was typed.

On Unix systems, combining with_tty_raw/1 with wait_for_input/3 would work to check whether input is pending without blocking.

On swipl-win I don’t see many options. What could work is to have two threads, where one thread merely reads the console and puts the characters in a message queue and the other gets them from there.

If that doesn’t work, I guess something needs to be added to the swipl-win code or maybe we can add something based on the existing functionality on stream timeouts. I.e., set the timeout on user_input to 0, use with_tty_raw/1 and get_code/2 which will then give the code if one is available or a timeout if not.

This works as expected on Linux:

go :-
    set_stream(user_input, timeout(0.001)),
    with_tty_raw(loop).

loop :-
    (   catch(get_code(X), error(timeout_error(_,_),_), fail)
    ->  writeln(X)
    ;   writeln('No key'),
        sleep(0.1)
    ),
    loop.

This works in the Windows console. Note that the actual application runs in a background thread as it seems not to work to make the reading thread the background thread.

go :-
    message_queue_create(Q),
    thread_create(loop(Q), _),
    with_tty_raw(read_console(Q)).

read_console(Q) :-
    get_code(C),
    (   C == -1
    ->  true
    ;   thread_send_message(Q, code(C)),
        read_console(Q)
    ).

loop(Q) :-
    (   thread_get_message(Q, code(C), [timeout(0)])
    ->  writeln(C)
    ;   writeln('No key'),
        sleep(0.1)
    ),
    loop(Q).
1 Like

Many thanks, Jan. That’s an awesome workaround for the Windows case. For the record, this was motivated for showing different uses of Prolog, including minimal interactive videogames, such as Lunar Lander, where key presses were needed to activate commands, such as the thrust (altitude and remaining fuel can be shown as changing numbers in the console).

I think that the Linux approach is very appropriate, so that something compact such as:

?- catch(call_with_time_limit(0.1, get_single_char(X)), _, true).
true.

?- 

would be pretty useful in the Windows case; in particular, when the application needs both interactively reading single codes and reading normal input (such as with read/1). Currently, this call is blocked in Windows until a key is pressed, which I understand is related to your comment (“as it seems not to work to make the reading thread the background thread.”):

?- catch(call_with_time_limit(0.1, get_single_char(X)), _, true).
|: 

But this is not correct. If you fail to process the character before the user types the next, this is read in cooked mode. That is why you need the with_tty_raw/1. I pushed a patch because, although this works, it raises the wrong exception.

I had a look at the Windows version, but implementing this for swipl-win.exe is far from trivial. I saw that the low-level code actually defines kbhit(), so may be that is a solution. Would be nice if there was a way to do this that is portable though.

Here is yet another non-Windows version, probably the most elegant one.

t :-
    with_tty_raw(loop).

loop :-
    wait_for_input([user_input], Ready, 1),
    (   Ready == []
    ->  writeln('No key')
    ;   get_code(X),
        writeln(X)
    ),
    loop.
1 Like

Many thanks for your explanations and help. I wonder whether compiling a foreign predicate (say kbhit/1), place it in a DLL and use use_foreign_library/1 in the application would be a way in Windows, or maybe this approach would suffer the same blocking issue.

Provided you link this dll against plterm.dll which defines the swipl-win.exe version of kbhit(), it probably works. Note that most likely this function has never been tested. It surely does not block though. It simply tests that the input queue is not empty.

1 Like

Finally I was able to generate a DLL implementing the required behavior of kbhit/1. A direct reference to the _kbhit() was enough. The source code, the compilation call and an example are listed at the end of this message. However, safety checks might need to be added (and most likely, other things are not needed). Many thanks again, Jan.

File kbhit.c:

/***********************************************************************
 * Reading the pressed key code (if any) without waiting
 * Return the key code if a key was pressed or 0 otherwise
 */

#include <SWI-Prolog.h>
#include <SWI-Stream.h>
#include <conio.h>
#include <stdio.h>

/***********************************************************************/


/***********************************************************************
 * Declarations
 ***********************************************************************/

/***********************************************************************
 * Public functions
 ***********************************************************************/

foreign_t pl_kbhit(term_t tCode) {
  intptr_t nCode;
  
  if ( _kbhit() ) 
  { nCode = _getch();
  } else {
    nCode = 0;
  };

	if (PL_unify_integer(tCode, nCode)) {
  	PL_succeed;
	} else {
		PL_fail;
  };
}

File install.c:

/***********************************************************************
 * Foreign library installer
 */

#include <SWI-Prolog.h>

/***********************************************************************/


/***********************************************************************
 * Declarations
 ***********************************************************************/

/* Foreign predicate definitions */
extern foreign_t pl_kbhit(term_t tCode);


/***********************************************************************
 * Public functions
 ***********************************************************************/

/** install
 *
 *    Registers foreign predicates in SWI-Prolog system. This function
 *    is automatically called when load_foreign_library/1 is used.
 */
install_t install() {
	PL_register_foreign("kbhit", 1, pl_kbhit, 0);
};

Call to compile (most flags were inherited from another compilation), where the environment variables %SWIPL% and %GNUWIN64% point to the installation directory of SWI-Prolog and the gcc compiler, respectively:

swipl-ld -I"%SWIPL%\include" -I"%GNUWIN64%\include" -Wno-incompatible-pointer-types -Wno-implicit-function-declaration -Wno-unused-result -Wno-int-to-pointer-cast -Wno-pointer-to-int-cast -shared -fPIC -pl swipl -o extern.dll kbhit.c install.c

Example:

1 ?- load_foreign_library(extern).
true.

2 ?- repeat, kbhit(X), writeln(X), sleep(0.2), X==32, !.
0
0
0
0
32
X = 32.

3 ?- 
1 Like

Correction: this seems to work with swipl.exe, but not with swipl-win.exe. I’ll dig into this. Sorry for the spam.

Even if it only works with swipl.exe and not swipl-win.exe currently I would not consider it spam. It is not often that we see a complete C level code exmaple posted, especially something so small and practical.

1 Like

As I said, kbhit() (no underscore) is part of plterm.dll that implements the window for swipl-win.exe. So, the same code should work, except that you must remove the underscore and link to plterm.dll (and libswipl.dll). As nothing from Windows is used, you can omit all the Windows dlls.

Alternatively you can write a dll that uses the dynamic lookup functions to first check that plterm.dll is around and use kbhit() from there or else lookup _kbhit() from Windows. That would need to happen to make this part of the Prolog core. That would be one way out: add win_kbhit/0. On non-Windows we can use wait_for_input/3 and then we can write a little library on top of this to support portable gaming-style keyboard interaction.

1 Like