How to Detect Key Down Events?

I want to detect key presses while my main program loop runs. Say I’m computing loads of data and printing that in the REPL. I want to be able to detect any keys I press. For example, if the key ‘p’ is pressed, all the computation pauses. However, I don’t JUST wish to pause. Maybe I run a function or anything. Basically, I want to be able to detect key-down events.
How can I achieve that?
And of course I’m not talking about readline() since that requires you to press ENTER. Also, that prevents the rest of the program from running (Tell me if that can work with an @async loop. I couldn’t get it to work)
I don’t want to use 3rd party packages like GTK or Makie. Just simple Key event detection!

I found a similar question on stackoverflow.

Based on that you could do something like this

using REPL

# Run listener as separate task using channels, put keypresses in channel for main loop
function key_listener(c::Channel)
    t = REPL.TerminalMenus.terminal
    while true
        REPL.Terminals.raw!(t, true) || error("unable to switch to raw mode")
        keypress = Char(REPL.TerminalMenus.readkey(t.in_stream))
        REPL.Terminals.raw!(t, false) || error("unable to switch back from raw mode")
        put!(c, keypress)
    end
end

function main()
    channel = Channel(key_listener, 10) # Start task, 10 is buffer size for channel
    stop = false
    while !stop
        println("Doing some long calculation")
        sleep(1)
        while !isempty(channel) # Process all keypresses
            c = take!(channel)
            if c == 'q'
                println("quitting")
                stop = true
                close(channel)
                break
            else
                println("$c is not a recognized command")
            end
        end
    end
end

# Run listener as separate task using channels, put keypresses in channel for main loop
function key_listener(c::Channel)
    t = REPL.TerminalMenus.terminal
    while true
        REPL.Terminals.raw!(t, true) || error("unable to switch to raw mode")
        keypress = Char(REPL.TerminalMenus.readkey(t.in_stream))
        REPL.Terminals.raw!(t, false) || error("unable to switch back from raw mode")
        put!(c, keypress)
    end
end

function main()
    channel = Channel(key_listener, 10) # Start task, 10 is buffer size for channel
    stop = false
    flag = true
    while !stop
        if flag
            println("working")
            sleep(0.5)
        end
    
        while !isempty(channel) # Process all keypresses
            c = take!(channel)
            if c == 'q'
                println("quitting")
                stop = true
                close(channel)
                break
            elseif c == 'p'
                flag = false
            elseif c == 'c'
                flag = true
            else
                println("$c is not a recognized command")
            end
        end
    end
end

Somehow adding a flag in the main loop causes a problem. Things work just as I wanted. But I was hoping ‘p’ to pause, ‘c’ to continue. But somehow, ‘p’ freezes the entire thing. No key press seems to work. Can you please help me out with this?

Hmm, would guess it is something with task scheduling there, that the key_listener task does not get to run enough.

I think the easiest way is to just add a short sleep in the case the flag is off, it seems to do it for me at least.

        if flag
            println("working")
            sleep(0.5)
        else
            sleep(0.1) # Makes sure we give some cpu time to the key_listener
        end

It also seems like the Channel constructor can take a spawn kwarg which should (may?) give it its own thread. Though this didn’t seem to fix it for me at least.
https://docs.julialang.org/en/v1/base/parallel/#Base.Channel-Tuple{Function}

1 Like

Thanks, man, I was finally able to create this simple clock object. Now I can finally move on with my project :slight_smile:

I figured… yield() does the job as well