Open jaki opened 4 years ago
Thanks for letting me know!
I have had a preliminary look and here are my findings.
dua
uses tui
, which exhibits the behaviour described here both for the termion and crossterm backends. All the following findings have been run with cargo run --example termion_demo -- --tick-rate 200
.Ctrl+Z
, which is due to the terminal being in raw mode, thus key presses don't show and are entirely handled by the application. Otherwise interrupts are generated by the owning shell which would see Ctrl+Z
before the application does.kill -STOP
, the terminal remains in raw mode. I am able to continue it with kill -CONT
or fg
in the shell and it does display, even though that will leave the user with control over the terminal to some extend. As such, the application will not respond to any input, even though input will not display in the terminal either as it is still in raw mode. However, now Ctrl+C
or Ctrl+Z
now work as they are effectively intercepted and translated into signals by the owning shell. I did not find a way to restore input to the application.Knowing how obtaining input works, I do wonder what else has to happen to make this work. I have a feeling it's something the application has to be aware of, and possibly rebind standard input somehow.
When trying the above with dua
, continuing it would not display anything. That is expected, as it only redraws when it receives user input, which we know can't be obtained anymore. However, now one can kill it with Ctrl+C, leaving the terminal in a dirty state as destructors didn't run.
Next I will continue checking what happens when doing this in termion
and crossterm
respectively.
I think I know what happens. After interruption, the input thread crashes and for some reason doesn't bring down the program with it (which it really should at least). Thus it leaves the application unresponsive.
That should easily be fixable, let me see….
Even though it's trivial to ignore interrupts, it appears that's not actually happening in case of dua. Previously the input thread would gracefully shutdown, and leave the main loop input loop with a closed channel which should unblock and stop naturally.
Further diving revealed that applications do indeed have to implement support for that themselves
As of now, I am none the wiser though on how to fix that, and the question remains why connectivity to the terminal is lost entirely.
Ok, here it comes: The terminal is loosing it's RAW mode, which is why now one can actually see all input. Thus pressing enter dispatches all input to the application, which will happily draw then as well.
Thus the solution should be to set a handler for SIGCONT and set the terminal back into raw mode.
Any thoughts on this would be appreciated - it puzzles me that this is not done automatically. It appears like tig
deals with this specifically thanks to using curses
as backend.
Putting this to the test, if I run cargo run --example curses_demo --no-default-features --features="curses" -- --tick-rate 200
in the tui-rs
repository, stopping it via separate signal actually leaves the terminal in a decent state, and fg
brings back the application in raw mode. Thus curses
must have been installing enough signal handlers to make that happen.
As dua
uses tui
, it would be possible to compile it against curses
, a backend supported by tui
.
It seems like ideally, this kind of functionality would be provided by the termion
and crossterm
backends respectively, ideally by a callback to separately install certain signal handlers to save and restore terminal attributes at the right time.
I will sleep on that and see if support for that can be implemented in termion
or crossterm
respectively to match curses
capabilities.
This article for the first time in my life explains how all this is actually working. Very interesting, even after only having read the first 10% or so. From what I could gather, the shell as session leader should be the one to restore the previous terminal attributes (which includes raw mode) shortly before it puts an application into the foreground. This, however, doesn't seem to happen, and curses handling this specifically (at least so it seems) might be a good example for this being some sort of special case nonetheless.
Thanks for all the effort you put into this! That article was helpful for me, too. I read up to
But since the editor is now a background job, the TTY device will not allow it.
then tested it out on vim
and got the same result. If I kill -STOP <pid>
on vim
rather than using Ctrl+Z, I get in a bad state after fg
. A minor difference is that resizing the screen (which should send SIGWINCH) causes it to be redrawn, but the text input seems to have no affect to the editor.
Then, I think that kill -STOP <pid>
is not something to worry about and, instead, it's about handling Ctrl+Z + fg.
I could just reproduce this behaviour with the tui
curses
backend, too. Curses and Vim probably implement something like this:
Now the user should be able to interact with a pristine terminal.
Once CONT is received, the application restores the previously current terminal settings and uninstalls the CONT signal handler and potentially reinstalls the SIGTSTP handler to get ready for the next background request.
If the application wanted to, it could always install a CONT handler to be able to always restore the correct terminal settings, even in case of STOP having been received earlier.
Now is the question how to handle it. My first intuition is to put that into crosstermion
at first as PoC and see if that can trickle down into the actual backends, namely crossterm
or termion
, or possibly it's very own crate as only one cross-platform implementation should ever be needed.
The sig crate could probably be used to send signals to a process using libc, which I suspect lacks windows support. There seem to be plenty of issues it might be more feasible to implement this as no-op on windows at first.
Additionally, I have created an issue over at termion, pointing to the corresponding crossterm
issue which probably has the higher chances of yielding an outcome.
I want to implement this for helix editor as well https://github.com/helix-editor/helix but seemed like we need to use low-level library for this directly using signal-hook.
In interactive
dua i
, Ctrl+Z doesn't work. It does nothing. If I stop the process usingkill -STOP <pid>
on a separate terminal, it does stop. If Ifg
it on that terminal, it starts again, but the graphical interface is not there. Whatever I type seems to have no affect, and the text I type just shows. It even doesn't recognize Ctrl+C (in most cases). So Ikill -KILL <pid>
, and all the keys I inputted are spit out on that terminal.Ideally, I would like Ctrl+Z to stop the process like many other programs.