Keyboard input monitoring

Hello all,

I have a “simple” problem, but I can’t seem to find a simple solution.

Basically what I want is to monitor key strokes for user input in an interactive application, with a couple requirements:

  1. it should not “hang” the application when the user is not providing an input
  2. it should not disrupt the terminal output while it’s running (I’m working on a Terminal application). For example the user input should not be displayed.

I’ve seen a few solutions like this and others on SO, and had a look at how REPL.TerminalMenus does it, but none of it seems to work yet.

Any help would be great!

1 Like

Perhaps gamezero.jl might be useful?

Maybe Observables.jl, used in Makie.jl: Observables & Interaction.

E.g. keyboard and mouse handling: Events.

1 Like

Thanks both, I’ll have a look.

I think I’ve found a solution to make it work without halting, using byteavailable before reading from stdin. Though for some reason this seems to only work with Base.start_reading(stdin), otherwise it returns 0.
This seems to work fine in a normal Terminal, though it’s not working in VSCode yet

Then perhaps it’s up to vscode extension and probably those issues are not of interest. Maybe you should metion more detail things what you doing, exactly you want.

You could run a get-input loop asynchronously from the main application task. E.g. the loop could read(stdin, Char) continually and put it into a Channel, which the main task could check periodically.

That’s fair.

I’m working on Term.jl adding interactive terminal visualizations.
Basically, you have a function that creates some output to the terminal which gets updated continuously, and I need to capture user input to modify what gets displayed.
For example in the example below:
ezgif.com-gif-maker(12)

I have a Pager display which shows some (random) text and the user can move up and down using the arrow keys. So I want to capture the user input to modify what gets displayed.

To do that , I start with:

        Base.start_reading(stdin)

        # prepare terminal 
        raw_mode_enabled = try
            REPL.Terminals.raw!(terminal, true)
            true
        catch err
            @warn "Unable to enter raw mode: " exception=(err, catch_backtrace())
            false
        end
        # hide the cursor
        raw_mode_enabled && print(terminal.out_stream, "\x1b[?25l")

and then in the code that handles updating the display I call this function

function keyboard_input(live::AbstractLiveDisplay)
    if bytesavailable(terminal.in_stream) > 0
        c = readkey(terminal.in_stream) |> Int
        return if Int(c) in keys(KEYs)
            out = key_press(live, KEYs[Int(c)])
            out isa Bool && return out
            return true
        else
            Char(c)
        end
    end
    return true
end

which uses readkey defined in REPL.

using bytesavailable I can avoid calling readkey if there’s no input so I don’t end up halting the program. In a normal terminal it all works correctly: the user input is not shown but can be captured to change the display.

In VSCode it doesn’t work at all… the user input is not detected, but rather it messes up the REPL - not sure how to get around that

2 Likes

In VSCode, it makes a difference whether you run code blocks from the editor or type commands directly into the repl. This seems to be due to the communication pipeline and still sometimes trips me up, things like Infiltrator should always be run from the repl. Does that make a difference for you here?

it does, thank you!

The terminal part from Gameoji from @c42f could be of inspiration too. I’ve highjacked some part of it for what I’m very sporadically (read: I would if I had more time) playing with in TUIseer. I’m trying to develop some sort of modal tui there.