Listening to stdin in Swift
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
Objective C : How to keep listening to incoming data on stdin?
The solution is:
int main(int argc, char * argv[]) {
@autoreleasepool {
NSFileHandle *stdIn = [NSFileHandle fileHandleWithStandardInput];
[[NSNotificationCenter defaultCenter] addObserverForName:NSFileHandleDataAvailableNotification
object:stdIn
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note) {
NSError * stdinError = nil;
NSData * rawReqLen = [stdIn readDataUpToLength:4 error:&stdinError];
if(rawReqLen == nil || stdinError != nil) exit(1);
uint32_t reqLen;
[rawReqLen getBytes:&reqLen length:4];
reqLen = OSSwapLittleToHostInt32(reqLen);
NSData * req = [stdIn readDataUpToLength:reqLen error:&stdinError];
if(stdinError != nil) exit(1);
handleRequest(req);
[stdIn waitForDataInBackgroundAndNotify];
}];
[stdIn waitForDataInBackgroundAndNotify];
}
NSRunLoop *loop = [NSRunLoop currentRunLoop];
[loop acceptInputForMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
[loop run];
return 0;
}
Calling exit(1)
could also be replaced by a new call to [stdIn waitForDataInBackgroundAndNotify];
if you want to keep the port opened instead.
This handles incoming requests well, but breaks the response: writing to stdout does not work anymore, I might need to stop the run loop for each response and restart it when done. (not tested yet)
Reading stdio from Mac Application
NSFileHandle, a wrapper for file descriptor is the solution.You can use file handle objects to access data associated with files, sockets, pipes, and devices. For files, you can read, write, and seek within the file.
DispatchQueue.global(qos: .background).async {
let input = FileHandle.standardInput
var aStr : String!
while true {
//Read first 4 bytes to get message length details(As per NativeMessagingAPI protocol)
let lengthDetails = input.readData(ofLength:4)
let length = lengthDetails.withUnsafeBytes { (ptr: UnsafePointer<Int32>) -> Int32 in
return ptr.pointee
}
let data = input.readData(ofLength:Int(length))//input.availableData
if (data.count > 0) {
var aStr = String(data:data, encoding:String.Encoding.utf8)
if (aStr != nil) {
DispatchQueue.main.async {
//Handle data logic here
}
}
}
}
}
Getting data from the nstask - communicating with command line - objective c
Give an NSPipe
or an NSFileHandle
as the task's standardOutput
, and read from that.
NSTask * list = [[NSTask alloc] init];
[list setLaunchPath:@"/bin/ls"];
[list setCurrentDirectoryPath:@"/"];
NSPipe * out = [NSPipe pipe];
[list setStandardOutput:out];
[list launch];
[list waitUntilExit];
[list release];
NSFileHandle * read = [out fileHandleForReading];
NSData * dataRead = [read readDataToEndOfFile];
NSString * stringRead = [[[NSString alloc] initWithData:dataRead encoding:NSUTF8StringEncoding] autorelease];
NSLog(@"output: %@", stringRead);
Note that if you use a pipe, you have to worry about the pipe filling up. If you provide an NSFileHandle
instead, the task can output all it wants without you having to worry about losing any, but you also get the overhead of having to write the data out to disk.
swift main run / build from command line
the problem
the command command line specify sub-command but miss the executable product
$ swift run read-aliases
the solution
you must use the executable product before sub-command
swift run easy-aliaser read-aliases
steps to reproduce
[so-test]$ git clone https://github.com/CreaTorAleXander/easy-aliaser
Cloning into 'easy-aliaser'...
remote: Enumerating objects: 41, done.
remote: Counting objects: 100% (41/41), done.
remote: Compressing objects: 100% (32/32), done.
remote: Total 41 (delta 2), reused 38 (delta 1), pack-reused 0
Unpacking objects: 100% (41/41), done.
[so-test]$ cd easy-aliaser
#
# [easy-aliaser (main)]$ swift package generate-xcodeproj
# edited the wired filename in your code just to refer to an existing file
# run the project in XCode without problems
# back to the command line
#
[easy-aliaser (main)]$ swift run easy-aliaser read-aliases
Fetching https://github.com/apple/swift-argument-parser
Cloning https://github.com/apple/swift-argument-parser
Resolving https://github.com/apple/swift-argument-parser at 0.3.1
/Users/me/projects/so-test/easy-aliaser/Sources/easy-aliaser/main.swift:23:13: warning: result of call to 'readFile(path:)' is unused
readFile(path: "/Users/me/projects/so-test/65203567/myzshrc")
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[3/3] Linking easy-aliaser
These are your Aliases
who_listening='sudo lsof -nP -iTCP -sTCP:LISTEN'
du-docker="du -h ~/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/Docker.qcow2 && docker images"
du-openshift=" /Users/ronda/.docker/machine/machines/openshift/disk.vmdk && du -h ~/.docker/machine/machines/openshift/boot2docker.iso"
Related Topics
Notification in Swift Returning Userinfo in Dictionary
How to Add More Cases for Enum in Swift
Xcodebuild Commands Failed to Generate Ipa
Override UIgesturerecognizer Touchesbegan
Produce Sounds of Different Frequencies in Swift
Iterating Through an Array of Strings, Fetched from Mongodb
Navigationlink Inside .Searchable Does Not Work
Nscollectionview Selection Handling in Swift
Swift Short Syntax of Execution
Swift Cannot Invoke '*' with an Argument List of Type '(Int, Int)'
How to Print Http Request to Console
Wkwebview Won't Load (Nsviewcontroller, Os X)
Performseguewithidentifier Not Working If Called from Viewdidload
How to Put View on Top of All Other Views in Swiftui
How to Assign a Generic Function to a Variable
Control a Nstabviewcontroller from Parent View
What Are The Benefits of an Immutable Struct Over a Mutable One
Dynamic Dispatching Protocol Extension Doesn't Work Multiple Targets