Currently I am trying to listen to user input from the command line in my swift application.
I am aware of the readLine()
method but it does not really fit my needs. I want to listen for data being inserted on the command line. Like when a user is pressing the ‘up key’ inside the terminal.
Something like what can be done in Node.js:
stdin.on( 'data', function( key ){
if (key === '\u0003' ) {
process.exit();
} // write the key to stdout all normal like
process.stdout.write( key );
});
I tried searching but I couldn’t find an equivalent to this in Swift. I thought maybe something with ‘Inputstream’ but didn’t a find a appropriate solution either.
If someone could give me some hints on how to do something like this in Swift I would highly appreciate it.
Normally standard input buffers everything until a newline is entered, that's why a typical standard input is read by lines:
while let line = readLine() {
print(line)
}
(press CTRL+D to send EOF, that is end the input)
To really read every character separately, you need to enter raw mode and that means use the low level terminal functions:
// see https://stackoverflow.com/a/24335355/669586
func initStruct<S>() -> S {
let struct_pointer = UnsafeMutablePointer<S>.allocate(capacity: 1)
let struct_memory = struct_pointer.pointee
struct_pointer.deallocate()
return struct_memory
}
func enableRawMode(fileHandle: FileHandle) -> termios {
var raw: termios = initStruct()
tcgetattr(fileHandle.fileDescriptor, &raw)
let original = raw
raw.c_lflag &= ~(UInt(ECHO | ICANON))
tcsetattr(fileHandle.fileDescriptor, TCSAFLUSH, &raw);
return original
}
func restoreRawMode(fileHandle: FileHandle, originalTerm: termios) {
var term = originalTerm
tcsetattr(fileHandle.fileDescriptor, TCSAFLUSH, &term);
}
let stdIn = FileHandle.standardInput
let originalTerm = enableRawMode(fileHandle: stdIn)
var char: UInt8 = 0
while read(stdIn.fileDescriptor, &char, 1) == 1 {
if char == 0x04 { // detect EOF (Ctrl+D)
break
}
print(char)
}
// It would be also nice to disable raw input when exiting the app.
restoreRawMode(fileHandle: stdIn, originalTerm: originalTerm)
Reference https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html
You probably want FileHandle.standardInput
.
Something like:
let file = FileHandle.standardInput
while true {
let data = file.availableData
print("\(String(bytes: data, encoding: .utf8))")
}
will echo out input the way I think you want it. Standard disclaimers about being careful with input and that this is probably a dangerous activity, sanitise your inputs and so on.
I'm not exactly sure how you'd go about matching specific control and arrow keys, but this is a start.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With