C
C#2mo ago
Hark Dand

[SOLVED] .NET runtime control over tty input and output

I am currently doing some experiments writing a console application in C#. I am trying to get a noncanonical/raw tty while using C#. Since ttys are not a C# specific concept, I followed the approach one would normally take in C: utilising termios to disable some tty attributes and to apply them. I am doing this by using some platform invocations to the libc library. Setting the attributes works as expected; no errors and all the attributes I expect to be deactivated are deactivated. Here comes the issue, however: using termios I intentionally disabled the option the let the tty process inputs such as CTRL+C. I would expect, then, that pressing CTRL+C when running the binary does not stop the application; unfortunately, it does. I also disabled - again, via termios - that input is only read line-wise. It should, instead, be read character-wise. However, input only gets read when I enter a newline character. I have tried this exact same configuration in C, where it works flawlessly. My question then is: What additional pre-processing does the .NET runtime add to a console application and its standard I/O? I am well aware that I can configure some of the behaviour I described earlier by setting properties on the Console class. However, that does not give me the control I want over the tty.
6 Replies
boiled goose
boiled goose2mo ago
guys in allow-unsafe-block would know more but i don't think c# (or rather the clr) does really that much processing of system shortcuts like ctrl-c, i may be wrong but i believe it's more likely they are intercepted from the operating system (or an helper process) and then translated into signals/events
Hark Dand
Hark Dand2mo ago
I would agree with this if it weren't for the fact that the same approach (calling the same functions from termios) works as expected when using C. Additionally, setting Console.TreatControlCAsInput to true does disable CTRL+C. That leads me to the assumption that this would not be something the OS is responsible for but rather the .NET runtime
boiled goose
boiled goose2mo ago
TreatControlCAsInput calls api SetConsoleMode on windows (but you are using linux? then it calls tcsetattr)
reflectronic
reflectronic2mo ago
@Zor might know, he has been working on a library that does something similar i think?
Hark Dand
Hark Dand2mo ago
I have this minimal example:
public class NonCanonicalTerminalExample
{
private const int STDIN_FILENO = 0;
private const int TCSAFLUSH = 2;

[DllImport("libc")]
public static extern int tcgetattr(int fd, out Termios termios);

[DllImport("libc")]
public static extern void cfmakeraw(ref Termios termios);

[DllImport("libc")]
public static extern int tcsetattr(int fd, int optional_actions, ref Termios termios);

public static void Main(string[] args)
{
var termios = new Termios();
tcgetattr(STDIN_FILENO, out termios);
cfmakeraw(ref termios);
tcsetattr(STDIN_FILENO, TCSAFLUSH, ref termios);

Console.ReadKey(true);
}
}
public class NonCanonicalTerminalExample
{
private const int STDIN_FILENO = 0;
private const int TCSAFLUSH = 2;

[DllImport("libc")]
public static extern int tcgetattr(int fd, out Termios termios);

[DllImport("libc")]
public static extern void cfmakeraw(ref Termios termios);

[DllImport("libc")]
public static extern int tcsetattr(int fd, int optional_actions, ref Termios termios);

public static void Main(string[] args)
{
var termios = new Termios();
tcgetattr(STDIN_FILENO, out termios);
cfmakeraw(ref termios);
tcsetattr(STDIN_FILENO, TCSAFLUSH, ref termios);

Console.ReadKey(true);
}
}
I use cfmakeraw here for ease of use; It disables the ISIG flag which is responsible for processing the CTRL+C keybind. However, this still cancels on CTRL+C, which it should not. Printing the c_lflag also correctly shows me 101000110000 Turns out: The code does work. I just had a major flaw my approach of testing this... Changing the Console.Readkey(true); to a while(true) { Console.Readkey(true); } works a lot better. CTRL+C, of course, is a keystroke, and will end the running program... not because the interrupt gets processed, but because the program ends after a single key press... Summary: .NET seems to behave correctly when using termios. .NET does not seem to add any preprocessing to the I/O of a console application if configured correctly.
alexrp
alexrp2mo ago
as long as you don't touch System.Console or System.Diagnostics.Process, .net will not mess with the terminal state obligatory plug: https://github.com/vezel-dev/cathode this library will make it fairly straightforward to work with the terminal in "raw" mode. it deals with all the platform-specific stuff and various edge cases. and also contains helpers for emitting various control sequences