Search code examples
c++clinuxncurses

Ctrl Z in child process breaks ncurses' reset_prog_mode


I'm writing an ncurses app that displays the files in a directory, and takes the file chosen by the user and opens in in vim, the problem is that when I hit ctrl z to temporarily close vim, after resuming and closing vim, my program is messed up in quite a few ways.

  1. The cursor becomes visible and calling curs_set(0) to hide it again does nothing
  2. If I don't call redrawwin(stdscr) (refresh does nothing), the screen buffer just keeps the terminal screen from before I ran fg.
  3. After closing the program, ncurses doesn't restore the original shell state and the state my app closed in stays on the screen except for the shell prompt.

Runable example here https://pastebin.com/tEdLkaPP Running on Linux 6.5.2 with ncurses version 6.4.2

Part of code that forks and opens vim with execl

                def_prog_mode(); endwin();

                pid_t pid = fork();
                if (pid == -1) {
                    perror("fork");
                } else if (pid == 0) {
                    execl("vim", "vim", <file>, NULL);
                } else {
                    int child_return = 1;
                    waitpid(-1, &child_return, WUNTRACED);
                }

                reset_prog_mode();

                redrawwin(stdscr);

//              Redraws the window

Code that deletes ncurses windows and calls endwin()

exit:
    delwin(win);
    delwin(bottom_win);
    delwin(win_border);
    delwin(bottom_border);
    endwin();
    return 0;

I've tried calling def_prog_mode() when the child process gets stopped with SIGSTOP, and then calling reset_prog_mode() when it gets SIGCONT, but that doesn't change anything.

Code for handing the signals

                    int child_return = 1;

                    while(!WIFEXITED(child_return))
                    {
                        waitpid(-1, &child_return, WUNTRACED | WCONTINUED);

                        if(WIFSTOPPED(child_return))
                            def_prog_mode();
                        else if (WIFCONTINUED(child_return))
                            reset_prog_mode();
                    }


Solution

  • After looking at https://stackoverflow.com/a/4891565/4769313 from Sir Jo Black's answer, I realized that if I handled SIGTSTP in a custom signal handler, I would need to run kill(getpid(), SIGSTOP) to actually get the process to stop. So with the custom handlers below, everything seems to work as intended. One caveat is that you have to register the signal handlers after initscr to or ncurses may overwrite your signal handlers.

        void sig_tstp(__attribute__((unused))int signal)
        {
            endwin();
            kill(getpid(), SIGSTOP);
        }
        
        void sig_cont(__attribute__((unused))int signal)
        {
            reset_prog_mode();
        }
    
        int main()
        {
            initscr();
    
            signal(SIGTSTP, sig_tstp);
            signal(SIGCONT, sig_cont);
    //      ...
        }
    

    The forking code no longer needed to handle the child's process until the child terminated, so now it looks like this

    
            def_prog_mode(); endwin();
    
            pid_t pid = fork();
            if (pid == -1) {
                perror("fork");
            } else if (pid == 0) {
                execl(program, program, buf, NULL);
            } else {
                int child_return = 1;
                waitpid(-1, &child_return, 0);
            }
    
            reset_prog_mode();