6

I would like to detect whether there is input on stdin in a short time window, and continue execution either way, with the outcome stored in a Bool. (My real goal is to implement a pause button on a simulation that runs in the terminal. A second keypress should unpause the program, and it should continue executing.) I have tried to use poll_fd but it does not work on stdin:

julia> FileWatching.poll_fd(stdin, readable=true)
ERROR: MethodError: no method matching poll_fd(::Base.TTY; readable=true)

Is there a way that will work on julia? I have found a solution that works in python, and I have considered using this via PyCall, but I am looking for

  1. a cleaner, pure-julia way; and
  2. a way that does not fight or potentially interfere with julia's use of libuv.
Jim Garrison
  • 4,199
  • 4
  • 25
  • 39

2 Answers2

7
bytesavailable(stdin)

Here is a sample usage. Note that if you capture the keyboard you also need to handle Ctrl+C yourself (in this example only the first byte of chunk is checked).

If you want to run it fully asynchronously put @async in front of the while loop. However if there will be no more code in this case this program will just exit.

import REPL
term = REPL.Terminals.TTYTerminal("xterm",stdin,stdout,stderr)
REPL.Terminals.raw!(term,true)
Base.start_reading(stdin)

while (true)
    sleep(1)
    bb = bytesavailable(stdin)
    if bb > 0
        data = read(stdin, bb)
        if data[1] == UInt(3)
            println("Ctrl+C - exiting")
            exit()
        end
        println("Got $bb bytes: $(string(data))")
    end
end
Przemyslaw Szufel
  • 40,002
  • 3
  • 32
  • 62
  • `bytesavailable(stdin)` returns `0` even after multiple keys have been pressed. I'm trying to figure out now if there is a way to disable libuv's input buffer. – Jim Garrison Mar 31 '20 at 17:28
  • Thanks. `bytesavailable(stdin)` now returns the number of bytes available. But this came at a cost of Ctrl+C no longer interrupting the process. Also, I am trying to figure out how to read/clear synchronously once bytes are available (`read(stdin)` did not work). – Jim Garrison Mar 31 '20 at 18:05
  • 1
    I wrote the full code for you and updated the answer but if you want to add another functionality please ask a separate question on StackOverflow – Przemyslaw Szufel Mar 31 '20 at 22:05
  • With julia 1.5 now out, I believe the Ctrl+C interrupt can now be enabled more succinctly by using `Base.exit_on_sigint` https://github.com/JuliaLang/julia/pull/29411/files – Jim Garrison Aug 03 '20 at 18:09
0

Following @Przemyslaw Szufel's response, here is a full solution that allows a keypress to pause/unpause the iteration of a loop:

import REPL
term = REPL.Terminals.TTYTerminal("xterm",stdin,stdout,stderr)
REPL.Terminals.raw!(term,true)
Base.start_reading(stdin)

function read_and_handle_control_c()
    b = bytesavailable(stdin)
    @assert b > 0
    data = read(stdin, b)
    if data[1] == UInt(3)
        println("Ctrl+C - exiting")
        exit()
    end
    nothing
end

function check_for_and_handle_pause()
    if bytesavailable(stdin) > 0
        read_and_handle_control_c()
        while bytesavailable(stdin) == 0
            sleep(0.1)
        end
        read_and_handle_control_c()
    end
    nothing
end

while true
    # [do stuff]
    sleep(0.05)
    check_for_and_handle_pause()
end

This is somewhat suboptimal in that it requires the process to wake up regularly even when paused, but it achieves my goal nevertheless.

Jim Garrison
  • 4,199
  • 4
  • 25
  • 39