1

I'm playing around with ANSI code (ESC sequence) using Swift as a console app. Sending an ESC command is trivial, such as setting text color. However, reading a response value from an ESC command is challenging. Here's a simple test program:

print("Get cursor position:", "\u{1b}[6n", terminator: "")
let s = readLine()!
print(s)

The program sends <ESC>[6n to get the current cursor position and the console would return <ESC>[<line>;<column>R string. Here are the problems:

  1. readLine() keeps waiting for input until user press Return or Enter key. I thought it will automatically stop reading once the input buffer gets empty.
  2. readLine() strangely doesn't seem to read the response value from the console although it's clearly printed on the screen. What's is happening?
  3. The response value is printed on the console. I'd like to have it silently, like the way print() prints the ESC command. Is there a way to temporarily redirect standard input into a variable?

System:
• MacOS Mojave
• XCode 10
• Swift 4.2
• run on Terminal app

I've been looking at GitHub and Google to find some answers, but I got no luck. So, would anyone here give me a hint where to start solving this problem? Thank you.

Regards,

~Bee

Thomas Dickey
  • 51,086
  • 7
  • 70
  • 105
Bee Jay
  • 183
  • 2
  • 10

1 Answers1

3

A terminal is by default in "canonical input processing" mode, which means that a read request will not return until an entire line has been typed and processed. The tcsetattr() function is used to disable both the input processing and echo, see for example

and the tcsetattr(3) and termios(4) manual pages.

Here is a simple example in Swift, inspired by the C code in the above Q&As, and also by Xcode Swift Command Line Tool reads 1 char from keyboard without echo or need to press return:

#if os(Linux)
import Glibc
#else
import Darwin
#endif

// Write escape sequence:
write(STDOUT_FILENO, "\u{1b}[6n", 4)

// Save terminal attributes:
var oldt = termios()
tcgetattr(STDIN_FILENO, &oldt)

// Disable canonical input processing and echo:
var newt = oldt
newt.c_lflag &= ~tcflag_t(ICANON)
newt.c_lflag &= ~tcflag_t(ECHO)
tcsetattr(STDIN_FILENO, TCSANOW, &newt)

// Read response:
var response: [UInt8] = []
var c: UInt8 = 0
repeat {
    read(STDIN_FILENO, &c, 1)
    response.append(c)
} while c != UInt8(ascii: "R")

// Restore original terminal attributes:
tcsetattr(STDIN_FILENO, TCSANOW, &oldt)

print(response) // [27, 91, 52, 59, 49, 82] == ESC[4;1R

Note that this works only when run in a Terminal window, not within Xcode.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382