StdIn for Console App

My dilemna is probably simple, but I am in an endless (mental) loop trying strategies that have not worked. I am sure there is a ‘best/better’ way.

Writing my first console app (a basic TCP server using ServerSocket). I want to input keystrokes from the Terminal for commands (quit, listen, stop listening, write out stats to Terminal etc). I whipped up a simple parsing routine called ‘processInput ( s as string)’ which pulls from StdIn and breaks the strings down into discrete actions that my server responds to. Here is the basics of my App.Run event code:

    // instantiate server, set up root directory, networkInterface, port, start listening, etc)
    App.running = true                       
    Do
       App.processInput (Input)              'parsing method fed by StdIn
       App.DoEvents(100)
    Loop until not App.running
    return 0

When I run this, no Socket events are fired. The server is dead silent. My surprise was that I copy/pasted the exact server code/classes from a Desktop app where it was working perfectly. After a number of hours and trial/error, I found that when I hit the Enter key in the Terminal, all the socket events fired.

Great…calling StdIn obviously blocks the loop awaiting the Enter key before returning the input stream. Simple solution. I just commented out the processInput method. Now the server is alive and all socket events fire as they did in the Desktop version. But I can’t communicate with the Console App via the Terminal for even basic things like sending a Quit message.

Next, I tried to spin off a TCPSocket to listen to StdIn (as per the Language Ref) thinking this would work asynchronously…

     //prior to Do...Loop
    App.keyboardListener as TCPSocket 
    keyboardListener = StdIn      
    AddHandler keyboardListener.DataAvailable, AddressOf processInput
    keyboardListener.connect
    keyboardListener.listen

I updated the input param for my parsing routine (to TCPSocket). It compiles and runs, but does nothing in response to keystrokes in the Terminal.

So, I am looking for a way to poll StdIn without blocking the event loop in Console app. I expect this is quite simple…but… Best way???

Many thanks in advance

To run sockets, you need to call App.DoEvents in a loop.
The loop needs to run often, like 10 times a second and you should not do anything that causes waiting there, e.g. wait for a keypress without the loop running.

First, consider using ServerSocket for your TCP connections so you can maintain multiple connections. But that’s besides the point.

The problem in your code is the Input function that waits for the user to enter something + return before continuing. Instead, you will have to simulate that using StdIn.ReadAll.

Here is the basic idea: on each pass, append the contents of ReadAll to a string (or array). When you detect an EOL, clear that buffer and pass it into your Process function.

We did exactly this in our server console app and used @Jeremy_Cowgar’s OptionParser to process command like they were command line switches. (It’s an open-source project, but I don’t have a link handy.) So “quit when you can” is “-q”, and “quit now!” is -Q, for example. We can also use long switches like “–status-report”.

Here is the challenge: you have to implement your own interpreter if you want to make it truly useful. For example, hitting backspace will enter the control code for that and, if you don’t handle it in code, you will be processing an unusable string. If your commands are short, this might not be an issue.

1 Like

Kem:

Many thanks. Worked great, and is simple. For future readers, here is how I implemented your suggestion. There might be more elegant code, but this certainly polls the keyboard, does not block the Run Loop, and my socket events are fired as expected.

BTW here is the link to Jeremy’s Xojo OptionsParser on GitHub

    App.keyBuffer as string                    'new app property

    Do 
      App.DoEvents (100)
      keyBuffer = keyBuffer + stdin.readall      'readAll does not block
      If keyBuffer.IndexOf (EndOfLine) >=0 Then processInput  (keyBuffer)   
    Loop Until Not running


   processInput (input as string)
   var command as string = input.replace(EndofLine, "")
   //....parse the command
   keyBuffer = ""             'flush buffer
1 Like