让常用程序使用备用屏幕

让常用程序使用备用屏幕

这已经困扰我很多年了。尽管我使用现代终端仿真器并且大多数 CLI 程序(例如lessvim)都能够充分使用备用屏幕(IE在启动时输入它并在退出时保留它),有些人不愿意这样做。流氓程序包括topscreendialog

当它们启动时,这些程序会清除窗口的内容(IE最后 N 行,其中 N 是终端窗口的高度)。当他们退出时,一些 ( top) 保留其最后的状态可见,而另一些 ( screen) 再次清除屏幕。在所有情况下,回滚缓冲区的最后 N 行都已被覆盖。

随着时间的推移,我在各种机器上以及各种终端模拟器中检查了这一点,包括xcfce4-terminalurxvtscreen(使用 正确启用了其备用屏幕:altscreen on)。因此,我不认为这是我的终端的问题,我宁愿相信这是这些程序的内置行为(至少未打补丁,因为它们分布在 Archlinux 中)。

所以我的问题是:

  1. 为什么这些程序会有这样的行为?我想这有充分的理由吗?
  2. 我该如何解决这个问题?目前,我正在使用如下所示的愚蠢包装脚本,但也许有一种更干净的方法?

    # save this as: ~/bin/top
    
    if [ -t 1 ]; then  # only use alt screen if output is a terminal
        tput smcup  # toggle alt screen on
        /usr/bin/top "$@"
        tput rmcup  # toggle alt screen off
    else
        /usr/bin/top "$@"
    fi
    

答案1

我们可以编写一个简短的 C 程序,而不是尝试使用 shell 脚本换行,它允许我们在程序停止时切换回正常屏幕:

#define _GNU_SOURCE 1

#include <stdbool.h>
#include <stdio.h>


#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

/* Quick hack; todo: use terminfo instead */
#include <stdlib.h>
static void enter_alt_screen(void)
{
    system("tput smcup");
}
static void leave_alt_screen(void)
{
    system("tput rmcup");
}

int main(int argc, char **argv)
{
    if (argc < 2) {
        fprintf(stderr, "Usage: %s command args...", argv[0]);
        return 1;
    }

    if (!isatty(fileno(stdout))) {
        /* not a terminal; act normally */
        execvp(argv[1], argv+1);
    }

    enter_alt_screen();
    const pid_t child = fork();
    switch (child) {
    case -1:
        leave_alt_screen();
        perror("fork");
        return 1;
    case 0:
        /* child */
        execvp(argv[1], argv+1);
        leave_alt_screen();
        perror("exec");
        return 1;
    }

    int status;
    while (waitpid(child, &status, WCONTINUED) == child) {
        if (WIFSTOPPED(status)) {
            leave_alt_screen();
        } else if (WIFCONTINUED(status)) {
            enter_alt_screen();
        } else if (WIFSIGNALED(status)) {
            leave_alt_screen();
            signal(WTERMSIG(status), signal(SIGTERM, SIG_DFL));
            raise(WTERMSIG(status));
        } else if (WIFEXITED(status)) {
            leave_alt_screen();
            return WEXITSTATUS(status);
        }
    }
    return 1;
}

相关内容