jtdaugherty / brick

A declarative Unix terminal UI library written in Haskell
Other
1.6k stars 164 forks source link

Strange suspend and halt behavior on MacOS, when using /dev/tty as inputFd #364

Open danchoi opened 2 years ago

danchoi commented 2 years ago

This github project demonstrates an issue I've discovered with suspending and halting in Brick.

I modified Brick's demo program brick-suspend-resume-demo to use the /dev/tty handle directly as the configured inputFd:

-- void $ defaultMain theApp initialState
ttyHandle <- openFile "/dev/tty" ReadMode
ttyFd <- IO.handleToFd ttyHandle
defConfig <- V.standardIOConfig
let builder = V.mkVty $ defConfig {
                V.inputFd = Just ttyFd
              }
initialVty <- builder
void $ customMain initialVty builder Nothing theApp initialState

The reason I wanted to do this was so I can use data piped from STDIN into a Brick program. Because the inputFd is assigned to stdin by default, I need it to be assigned explicitly to /dev/tty in order to make the terminal UI interactive.

If you compile and run my demo executable in the Ubuntu Terminal (tested on Ubuntu 20.04), pressing SPACE once suspends and pressing ESC once quits -- just like the original demo.

If you run the executable in the MacOS Terminal (macOS Montery, Apple M1 Pro), pressing SPACE or ESC once isn't sufficient. You need to press another key afterward to make the suspend or quit command register. Is this bug, or is there something wrong with my Haskell code?

jtdaugherty commented 2 years ago

As I look at the snippet you provided above, I wonder if this will work as intended if you only ever open /dev/tty once. The way that code is written, you hold on to a handle to the terminal while Vty uses it. I don’t recall off hand what it’s going to do to the input descriptor, but my hunch is that it would be best to open /dev/tty each time a new Vty handle is built rather than once as is done above. Since Vty might change input buffering and other behaviors, it may make assumptions about the file handle it is given. Since Vty and Brick are going to evaluate the builder action each time the terminal needs to be controlled again, the code above will result in your ttyFd getting re-used across Vty builds, and that might cause problems.

So my suggestion is to take the two lines starting with ttyHandle <- … and move them into builder and see if that helps. If not, we can dig in further.

jtdaugherty commented 2 years ago

@danchoi I wanted to check in on this again and see if you have had any time to try out my suggestion above.

danchoi commented 2 years ago

@jtdaugherty sorry for the delay. Thank you for your suggestion, but when trying it the issue I reported still happens. Here is the modified code:

main = do
    -- void $ defaultMain theApp initialState
    defConfig <- V.standardIOConfig
    let builder = do
                    ttyHandle <- openFile "/dev/tty" ReadMode
                    ttyFd <- IO.handleToFd ttyHandle
                    V.mkVty $ defConfig {
                      V.inputFd = Just ttyFd
                    }
    initialVty <- builder
    void $ customMain initialVty builder Nothing theApp initialState
jtdaugherty commented 2 years ago

Thanks! I'm doing some investigation. You can check out my minimal demo program here:

https://github.com/jtdaugherty/brick-suspend-issue/blob/main/Main2.hs

I think that the program shows that it isn't a Brick or Vty problem (although once I understand the problem, then we'll see where the solution belongs; maybe in Vty). That demo program is the essence of what is going on inside of Vty when you are having the problem that you reported in this ticket. That demo program behaves the same for its stdin and tty arguments on Linux (analogous to your report), but on MacOS the behavior changes in such a way that it results in the issue you're seeing. I'll let you know when I find out more about what is going on.

danchoi commented 2 years ago

Thank you very much @jtdaugherty for investigating this.

jtdaugherty commented 2 years ago

There's now this related GHC ticket: https://gitlab.haskell.org/ghc/ghc/-/issues/21503