Basically there are two things you have to do in order to reach your goal:
You have to disable any buffering in the terminal, so that your program gets the pressed keys at once (and you don't have to confirm with ENTER).
hSetBuffering stdin NoBuffering
Then you have to ensure the character isn't echoed back to the terminal.
hSetEcho stdin False
Please note that just before your program exits it has to restore the previous settings in order to allow further usage of the terminal.
Putting everything together the code will look like this:
import System.IO
import Control.Exception (bracket)
withHiddenTerminalInput :: IO a -> IO a
withHiddenTerminalInput io = bracket
(do prevBuff <- hGetBuffering stdin
prevEcho <- hGetEcho stdin
hSetBuffering stdin NoBuffering
hSetEcho stdin False
return (prevBuff, prevEcho)
)
(\(prevBuff, prevEcho) -> do
hSetBuffering stdin prevBuff
hSetEcho stdin prevEcho
)
(const io)