3

Haskell has a bug in Windows that seems fixed with WinIO in GHC9: getChar ignores NoBuffering mode - it does not evaluate until Enter is pressed.

Somebody suggested a workaround via a foreign call:

{-# LANGUAGE ForeignFunctionInterface #-}
import Data.Char
import Foreign.C.Types
getHiddenChar = fmap (chr.fromEnum) c_getch
foreign import ccall unsafe "conio.h getch"
  c_getch :: IO CInt

It kind of works, but the problem is that it seems to block the output to console until some key is pressed, and I am concurrently reading key presses and writing to console from different threads.

Could you please help find a way (e.g. write a foreign function call - I do not know much about it, unfortunately) to read characters/keys from console, without buffering and echo, and without blocking output to console, in GHC 8.8.x (or at least 8.10.x), on Windows?

Thank you!

(Ideally I need a cross-platform way, but I can do it via conditional compilation, so if it only works on Windows it is ok. Above foreign call is not cross-platform already).

esp
  • 7,314
  • 6
  • 49
  • 79

2 Answers2

1

I've found the library that does it: https://hackage.haskell.org/package/terminal

It does not block while waiting for key presses, you can still print from another thread.

It also solves Windows terminal utf8 problem.

esp
  • 7,314
  • 6
  • 49
  • 79
1

The essential problem with that particular FFI call is the unsafe. That makes the getch happen in the calling (system) thread. As a rule, foreign calls that may take some time should be marked safe. Even that is a bit problematic, because exceptions (e.g., from the user hitting Ctrl-C) are masked.

The true solution for such FFI calls is to mark them interruptible and then deal with interruption properly. You do this by checking whether the call was successful (based on its return value). If it wasn't, then you'd check errno for EINTR and try again in that case, as for any foreign call of that sort. But for an interruptible one, if you get EINTR you should deliver asynchronous exceptions using allowInterrupt before retrying.

Caveat: the above approach only works for foreign calls that are (mostly) making interruptible system calls, and that terminate with a failure status and set EINTR when interrupted. A foreign function performing an expensive mathematical computation, for example, is not generally interruptible at all.

Caution: mixing Handle-based I/O with raw FFI I/O on the same file descriptor sounds like a recipe for trouble.

dfeuer
  • 48,079
  • 5
  • 63
  • 167
  • > mixing Handle-based I/O with raw FFI I/O on the same file descriptor sounds like a recipe for trouble. Even if only one thread does writing, only via handle, and another thread does reading, only via FFI? – esp Apr 06 '21 at 11:29
  • 1
    @esp, that I couldn't say for sure. Might be fine, but I'm not an expert in that aspect. – dfeuer Apr 06 '21 at 14:23