I am new to Julia but I can’t seem to find out how to do this…
I want a function that allows an input of number of seconds to wait for a key to be pressed, if zero it returns the next key pressed in the buffer. Then it returns info as to what key was pressed, ASCII is ok but the Ins (Insert) key returns 27 + something else. The Esc (Escape) key returns just 27. The function keys don’t return ASCII but something else. This would be similar to inkey() in FoxPlus (if anybody knows what that is) which doesn’t work quite the way I want it to, either. The return can be 1-127 for ASCII and higher numbers for the rest of the keys. I am not looking for just the Shift, Ctrl, or Alt keys by themselves, but I want the up-arrow, down-arrow, Home, PgUp, etc. to return info as to which one was pressed. It would be nice if I could clear the type ahead buffer when I want to but I can’t find a command that does that. Maybe I just don’t know where to look.
This isn’t so much a language thing as it is an operating system / graphics framework sort of thing.
What sort of context do you want this to work/run in?
This is to be a terminal type interface, wasn’t even thinking of mouse interaction, using Linux as the server. I hadn’t even looked at a graphics interface. The programs i’m trying to convert are written in FoxPlus, totally text, no graphics.
I think I have a rough idea of what you’re talking about, I’m on my phone but can give a more full answer tomorrow. But here is my rough idea:
println("Please enter a key")
sleep(2)
if bytesavailable(stdin) > 0 # if there are any bytes, there has been terminal input
println("input:")
while bytesavailable(stdin) > 0
c = read(stdin, Char)
# different keys like home or arrow keys are mapped to different sequences of characters
print(escape_string(string(c)))
end
println()
else
println("No input")
end
This code likely doesn’t work as I haven’t tested it, but this is my rough idea.
In order to determine which key has been hit, you would need to test each key and see what character sequence it corresponds to.
Not sure it’s much help, I’ll try to revisit this and give a more thorough answer when I’m at my computer.
Yes, I just tried it out over an ssh connection and it’s not behaving like I thought. If this is still unsolved tomorrow I’ll try to provide a better answer.
It is certainly possible though; this is done in the package VimBindings.jl. As you say, the Escape key sends a special character, whereas (for example) an arrow key sends that same special character followed by a character sequence. In order to tell whether the key was just an Escape key press or another key, the terminal must wait to see if any new data comes in.
c = read(stdin, Char)
doesn’t like BackSpace
Right, that’s exactly why I talked about operating systems and graphical toolkits. Command line interfaces (regardless of the language) only get the text that the controlling terminal sends them. This can sometimes include some control chars like backspace, but it’s very configuration-dependent in the terminal itself. If you need to get the raw keypresses, you typically need to have a framework that talks more directly with the operating system, like a graphics framework or entire windowing system or web frontend or somesuch.
I’ve been able to sit down at my machine and figure this out, I think.
Perhaps surprisingly, this actually doesn’t relate to graphical toolkits (although it is somewhat related to operating systems and the way an OS terminal handles characters).
This is not the case; programs which are run at the terminal are able to access all bytes sent to stdin
by setting the terminal to “raw mode”, also called “non-canonical mode”. In this mode, the terminal sends input byte-by-byte to the program, with no buffering or processing of keys or control sequences. Even the Julia REPL runs in raw mode, and the REPL code processes and handles control sequences byte for byte.
Still, that doesn’t solve the ability to read the raw terminal input directly from stdin
. While julia code can call read(stdin, Char)
, it seems to only be able to do with line-by-line buffering, effectively in “canonical” mode (also called “cooked” mode). But the code below uses the utility function with_raw_tty
to set the input to raw mode, and read raw bytes directly from the terminal, adapted from the function getpass
in base/utils.jl
Just as a warning, this bypasses all of the processing and safety measures Julia offers, so Ctrl-c
does not work! You are reading directly from the terminal input. In this example, you can escape the loop with the Enter
key.
function raw_input_example()
# based on the function getpass in base/utils.jl#343
Base.with_raw_tty(stdin) do
while true
c = Base._getch() # get raw character. ONLY works in with_raw_tty
print("Byte: $c\r\n")
print("Char: " * escape_string(string(Char(c))) * "\r\n\n")
# use Enter to escape this loop
if c == 0xff || c == UInt8('\n') || c == UInt8('\r') || c == 0x04
break # EOF or return
end
end
end
end
And here is the output when I strike the following keys on my keyboard: a b c <Backspace> <Up Arrow> <Escape> <Enter>
, with annotated comments. Note that this successfully records the <Backspace>
key strike. Also note the full control sequence with the <Up Arrow>
key strike.
julia> raw_input_example()
Byte: 97
Char: a
Byte: 98
Char: b
Byte: 99
Char: c
Byte: 127 # Backspace
Char: \x7f
Byte: 27 # Up Arrow
Char: \e
Byte: 91
Char: [
Byte: 65
Char: A
Byte: 27 # Escape
Char: \e
Byte: 13 # Enter
Char: \r
Ah yes, I had forgotten about raw mode! That won’t get the fine-grained keydown/keyup events or the modifier keys by themselves as I understand it, though. Not sure if that’s a requirement here, but that’s why my head went to UI frameworks.
Ah yes definitely - not something you could write a more modern UI application with for sure. And you are right, no modifier key events, unlike something like an X11
application.
Based on the information you gave me last time I wrote a function that works like a little bit like inkey() in FoxPlus except it returns strings instead of numbers. If you call it with inkey(“”) it checks for a key pressed one time and returns with “” (empty string), if no key has been pressed. If a letter key (32-127) was pressed, it returns with that letter. If the tab key was pressed it returns with “Tab”, likewise for “Ctrl-A” - “Ctrl-Z” except for “BackSpace” and “Enter”, function keys are returned with “F1” to “F12”, likewise for “Ins” “Del” “Home” “End” “PgUp” “PgDn” “UpArrow” “DnArrow” “LeftArrow” “RightArrow” If called with inkey(“60”) it will only wait for a key for 60 seconds, if called with inkey(“0”) it will wait for ever. The text file is 192 lines long and using include(inkey_function.jl") allowed me to fix typos. Now I’m trying to figure out how to get it into REPL to try it out (I am new to julia)
How come c = read(stdin, Char) quit working? It didn’t used to require Enter… Even closed REPL an reloaded it to n avail… by the way, Caleb-Allen, your routine doesn’t differentiate between “A” and “F1” or “Home” (excuse me, it does work, I was only seeing the last byte) apparently bytesavailable doesn’t work. I have it sleep for a second and then try bytesavailable(stdin) > 0 and it fails
Using your info, Caleb, and the info at
I was able to write the function inkey() the way I want it to work. Somebody else might like this function, where do I put it? it’s 186 lines.
I’m glad to hear you got it working!
You can place the code directly in this thread and putting three tic marks (```) before and after it to indicate that it is a code snippet, that way it will be scrollable.
You could also upload it to https://gist.github.com/ if you have a github account, and get a sharable link.
function inkey(s)
#=
requires import REPL before using
This is for a single key stroke and returns what key it was
based on a terminal setting in PuTTY of Ctrl-H for BackSpace and
Linux function keys
s is a string
if null, checks bytesavailable one time
if parse(Int, s) == 0 , has no time out
otherwise, time out after parse(Int, s) seconds
key pressed is returned as:
Key Char
number return
------ ------
1 - 7 Ctrl-A to Ctrl-G
8 BackSpace (Ctrl-H)
9 Tab (Ctrl-I)
13 Enter (Ctrl-M)
11 - 26 Ctrl-K to Ctrl-Z
0x1b Esc
0x1b 0x5b 0x31 0x31 0x7e F1
0x1b 0x5b 0x31 0x32 0x7e F2
0x1b 0x5b 0x31 0x33 0x7e F3
0x1b 0x5b 0x31 0x34 0x7e F4
0x1b 0x5b 0x31 0x35 0x7e F5
0x1b 0x5b 0x31 0x37 0x7e F6
0x1b 0x5b 0x31 0x38 0x7e F7
0x1b 0x5b 0x31 0x39 0x7e F8
0x1b 0x5b 0x32 0x30 0x7e F9
0x1b 0x5b 0x32 0x31 0x7e F10
0x1b 0x5b 0x32 0x33 0x7e F11
0x1b 0x5b 0x32 0x34 0x7e F12
0x1b 0x5b 0x32 0x7e Ins
0x1b 0x5b 0x33 0x7e Del
0x1b 0x5b 0x31 0x7e Home
0x1b 0x5b 0x34 0x7e End
0x1b 0x5b 0x35 0x7e PgUp
0x1b 0x5b 0x36 0x7e PgDn
0x1b 0x5b 0x41 UpArrow
0x1b 0x5b 0x42 DnArrow
0x1b 0x5b 0x43 RightArrow
0x1b 0x5b 0x44 LeftArrow
=#
seconds = -1
term = REPL.Terminals.TTYTerminal("xterm",stdin,stdout,stderr)
REPL.Terminals.raw!(term,true)
Base.start_reading(stdin)
while true
sleep(.1)
ba = bytesavailable(stdin)
if ba > 0
str = read(stdin, ba)
if str[1] == 0x1b
# We have an Escape
if length(str) == 1
return "ESC"
end
if length(str) > 4
if str[2] == 0x5b && str[3] == 0x31 && str[4] == 0x31 &&
str[5] == 0x7e
return "F1"
end
if str[2] == 0x5b && str[3] == 0x31 && str[4] == 0x32 &&
str[5] == 0x7e
return "F2"
end
if str[2] == 0x5b && str[3] == 0x31 && str[4] == 0x33 &&
str[5] == 0x7e
return "F3"
end
if str[2] == 0x5b && str[3] == 0x31 && str[4] == 0x34 &&
str[5] == 0x7e
return "F4"
end
if str[2] == 0x5b && str[3] == 0x31 && str[4] == 0x35 &&
str[5] == 0x7e
return "F5"
end
if str[2] == 0x5b && str[3] == 0x31 && str[4] == 0x37 &&
str[5] == 0x7e
return "F6"
end
if str[2] == 0x5b && str[3] == 0x31 && str[4] == 0x38 &&
str[5] == 0x7e
return "F7"
end
if str[2] == 0x5b && str[3] == 0x31 && str[4] == 0x39 &&
str[5] == 0x7e
return "F8"
end
if str[2] == 0x5b && str[3] == 0x32 && str[4] == 0x30 &&
str[5] == 0x7e
return "F9"
end
if str[2] == 0x5b && str[3] == 0x32 && str[4] == 0x31 &&
str[5] == 0x7e
return "F10"
end
if str[2] == 0x5b && str[3] == 0x32 && str[4] == 0x33 &&
str[5] == 0x7e
return "F11"
end
if str[2] == 0x5b && str[3] == 0x32 && str[4] == 0x34 &&
str[5] == 0x7e
return "F12"
end
return ""
end # if length(str) > 4
if length(str) > 3
if str[2] == 0x5b && str[3] == 0x32 && str[4] == 0x7e
return "Ins"
end
if str[2] == 0x5b && str[3] == 0x33 && str[4] == 0x7e
return "Del"
end
if str[2] == 0x5b && str[3] == 0x31 && str[4] == 0x7e
return "Home"
end
if str[2] == 0x5b && str[3] == 0x34 && str[4] == 0x7e
return "End"
end
if str[2] == 0x5b && str[3] == 0x35 && str[4] == 0x7e
return "PgUp"
end
if str[2] == 0x5b && str[3] == 0x36 && str[4] == 0x7e
return "PgDn"
end
return ""
end # if length(str) > 3
if str[2] == 0x5b && sr[3] == 0x41
return "UpArrow"
end
if str[2] == 0x5b && sr[3] == 0x42
return "DnArrow"
end
if str[2] == 0x5b && sr[3] == 0x43
return "RightArrow"
end
if str[2] == 0x5b && sr[3] == 0x44
return "LeftArrow"
end
return ""
# We had an Escape
end # if str[1] == 0x1b
x = convert(Int, str[1])
if x == 8
return "BackSpace"
end
if x == 9
return "Tab"
end
if x == 13
return "Enter"
end
if x < 27
y = 64 + x
return "Ctrl-" * Char(y)
end
if x < 128 && x > 31
return String(str)
end
return ""
# We had a key pressed!
end # if ba > 0
if s == ""
# this is a one time test
return s
end
if s != "0"
# this means we have a timed wait
if seconds < 0
# first time
seconds = parse(Int, s)
else
seconds = seconds - 1
if seconds == 0
return ""
end
end
# else
# this means we wait until aa key is pressed
end
sleep(.9)
end # while true
end # function inkey(s)