Byron / dua-cli

View disk space usage and delete unwanted data, fast.
https://lib.rs/crates/dua-cli
MIT License
4.19k stars 113 forks source link

support ctrl+z stop process #65

Open jaki opened 4 years ago

jaki commented 4 years ago

In interactive dua i, Ctrl+Z doesn't work. It does nothing. If I stop the process using kill -STOP <pid> on a separate terminal, it does stop. If I fg 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 I kill -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.

Byron commented 4 years ago

Thanks for letting me know!

I have had a preliminary look and here are my findings.

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.

Byron commented 4 years ago

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….

Byron commented 4 years ago

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.

Byron commented 4 years ago

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.

Byron commented 4 years ago

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.

jaki commented 4 years ago

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.

Byron commented 4 years ago

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.

Byron commented 4 years ago

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.

pickfire commented 3 years ago

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.