Key Capture
For historical reasons, I/O operations in Fortran are record-based (even if some limited stream-based I/O is possible in modern language versions). Therefore, no intrinsic Fortran routine exists to capture single key-stroke events. The best way seems to be to call external C routines, which works on most Unix-like operating systems. For non-blocking keyboard input, one may interface the C procedures listed on Rosetta Code.
stty
On Unix, the command-line tool
stty(1)
is used to configure the terminal device interface. The cbreak mode
lets us read single characters from terminal. We could either write interfaces
to ioctl(2)
to modify the device parameters, or, just execute stty
through the
Fortran intrinsic routine execute_command_line()
.
For both cases, the C function getchar(3) in libc returns the next input character from the input stream. We simply define an ISO C binding interface to the C function in order to read a single character in Fortran.
! getch.f90
program main
use, intrinsic :: iso_c_binding, only: c_int
implicit none
interface
! int getchar(void)
function c_getchar() bind(c, name='getchar')
import :: c_int
implicit none
integer(kind=c_int) :: c_getchar
end function c_getchar
end interface
integer :: ch
! Enable cbreak mode.
call execute_command_line('stty -echo cbreak </dev/tty >/dev/tty 2>&1')
print '("Press <q> to quit.")'
do
ch = c_getchar()
print '("Key pressed: ", i0)', ch
if (ch == ichar('q')) exit
end do
! Disable cbreak mode.
call execute_command_line('stty echo -cbreak </dev/tty >/dev/tty 2>&1')
end program main
Compile and run the example program with:
$ gfortran12 -o getch getch.f90
$ ./getch
Press <q> to quit.
Key pressed: 70
termios
The termios(4)
API lets us control terminal I/O on Unix. We have to define a C routine
setmode()
to disable echo and line editing mode, and another C
routine nextchar()
to read a single input character:
/* term.c */
#include <stdio.h>
#include <termios.h>
void setmode(int *mode)
{
static struct termios termattr, saveattr;
if (*mode != 0)
{
tcgetattr(0, &termattr);
saveattr = termattr;
termattr.c_lflag &= ~(ICANON | ECHO);
termattr.c_cc[VMIN] = 1;
termattr.c_cc[VTIME] = 0;
tcsetattr(0, TCSADRAIN, &termattr);
}
else
{
tcsetattr(0, TCSADRAIN, &saveattr);
}
}
void nextchar(int *nextch)
{
*nextch = getchar();
}
In order to access these routines from Fortran, ISO C binding interfaces have to be implemented. The following example prints the code of each pressed key.
! key.f90
program main
use, intrinsic :: iso_c_binding
implicit none
interface
subroutine c_setmode(mode) bind(c, name='setmode')
import :: c_int
implicit none
integer(kind=c_int) :: mode
end subroutine c_set_mode
subroutine c_nextchar(nextch) bind(c, name='nextchar')
import :: c_int
implicit none
integer(kind=c_int) :: nextch
end subroutine c_next_char
end interface
integer :: key
! Enable single key capture.
call c_setmode(1)
print '("Press <q> to quit.")'
do
! Read single character.
call c_nextchar(key)
print '("Key pressed: ", i0)', key
if (key == ichar('q')) exit
end do
! Revert to default.
call c_setmode(0)
end program main
Compile the example with GCC:
$ gcc12 -c term.c
$ gfortran12 -o key key.f90 term.o
Or, instead, using Clang/Flang:
$ clang -c term.c
$ flang -o key key.f90 term.o
The code of each pressed key will be printed to screen until the user hits
q
:
$ ./key
Press <q> to quit.
Key pressed: 70
References
- C. Page: Reading single keystrokes from Fortran, 1994
< Inter-Process Communication | [Index] | Date and Time > |