Skip to content
遇见王斌 edited this page May 19, 2024 · 8 revisions

snack 前世今生

大多数商业化软件产品一般会通过实现 GUI(Graphical User Interface) 或者 TUI(Text-based User Interface/Textual User Interface/Terminal User Interface) 来降低软件的使用难度

1 简介

1.1 newt

newt 是由 RedHat 开发,主要用在 RatHat 的 Linux 发行版本 (RHEL, Fedora 和 CentOS) 的安装程序项目 Anaconda 中。 NEWT 的全称是:Not Erik’s Windowing Toolkit ,它基于 S-Lang 库实现。

因为 newt 是用于 Linux 发行版本的安装程序中,因而它的运行空间非常有限,需要保证程序尽可能小,所以在设计时选择使用 C 语言进行开发,也尽量不支持多余特性。

于是在设计之初就不支持事件驱动。在支持事件驱动的窗口库中,有一个事件循环监听事件发生,根据发生的事件展示不同的窗口。

在 newt 中,窗口创建和销毁基于栈模式,新创建的窗口位于之前的窗口之上,且只能展示最上层窗口,当最上层窗口销毁之后才能展示下边的窗口。

这也就是说所有窗口都为模态窗口 ( model windows )。这种特性限制了 newt 库本身只适合用于完成顺序的程序流程,一个典型的场景就是程序的安装向导。

SHELL 程序往往也是顺序的流程,从 SHELL 程序转换为 newt 窗口程序完全不涉及程序流程的改变,实现非常简单。

示例:

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <newt.h>

void show_message_window(void) {
    char message[] = "This is a pretty long message. It will be displayed "
                     "in a newt textbox, and illustrates how to construct "
                     "a textbox from arbitrary text which may not have "
                     "very good line breaks.\n\n"
                     "Notice how literal \\n characters are respected, and "
                     "may be used to force line breaks and blank lines.";
    newtComponent text, button, form;

    newtCls();
    text = newtTextboxReflowed(1, 1, message, 30, 5, 5, 0);
    button = newtButton(12, newtTextboxGetNumLines(text) + 2, "Exit");
    newtCenteredWindow(37, newtTextboxGetNumLines(text) + 7, "MESSAGE");
    form = newtForm(NULL, NULL, 0);
    newtFormAddComponents(form, text, button, NULL);
    newtRunForm(form);

    newtFormDestroy(form);
    newtPopWindow();
}

void show_login_failed_window(void) {
    newtComponent label, button, form;

    newtCls();
    newtCenteredWindow(60, 6, "NOTE");
    label = newtLabel(10, 2, "Login failed!");

    button = newtCompactButton(30, 4, "Exit");

    form = newtForm(NULL, NULL, 0);
    newtFormAddComponents(form, label, button, NULL);
    newtRunForm(form);

    newtFormDestroy(form);
    newtPopWindow();
}

int main(void) {
    int cols, rows;

    newtInit();

    do {
        newtCls();
        newtPushHelpLine(NULL);

        newtGetScreenSize(&cols, &rows);
        newtOpenWindow(1, 1, cols - 2, rows - 4, "LOGIN");

        newtComponent form, btn_ok, btn_cancel, label_username, label_password,
                      entry_username, entry_password, result;
        const char *username, *password;

        label_username = newtLabel(10, 3, "Username:");
        entry_username = newtEntry(20, 3, "root", 20, &username,
                                   NEWT_FLAG_SCROLL);
        label_password = newtLabel(10, 5, "Password:");
        entry_password = newtEntry(20, 5, "", 20, &password,
                                   NEWT_FLAG_PASSWORD | NEWT_FLAG_SCROLL);

        result = newtLabel(10, 10, "");

        btn_ok = newtButton(10, 7, "OK");
        btn_cancel = newtButton(20, 7, "Cancel");

        form = newtForm(NULL, NULL, 0);

        newtFormAddComponents(form, label_username, entry_username,
                              label_password, entry_password,
                              btn_ok, btn_cancel, result,
                              NULL);
        struct newtExitStruct exit_status;
        newtFormRun(form, &exit_status);

        if (exit_status.reason == NEWT_EXIT_COMPONENT) {
            if (exit_status.u.co == btn_ok) {
                if ((strcmp(username, "root") == 0)
                    && (strcmp(password, "123456") == 0))
                {
                    show_message_window();
                    break;
                } else {
                    show_login_failed_window();
                }
            } else if (exit_status.u.co == btn_cancel) {
                break;
            }
        }

        newtRefresh();
        newtFormDestroy(form);
        newtPopWindow();
        newtPopHelpLine();

    } while (1);

    newtFinished();
    return 0;
}

编译

gcc -o newtdemo newtdemo.c -lnewt

1.2 python snack 库

官方 newt 库中还提供了 Python 封装库,名称为 snack 。具体用法和 C 库类似,官方也提供了两个示例:

2 snack

2.1 环境

Centos 6.X 以上可以直接使用

如果是想在 Centos 低版本上使用,需要做以下步骤:(因为需要升级 glibc 所以不建议操作)

升级 glibc 到 2.4 以上(有风险)

2.2 在 screen 模式下发生段错误

screen 模式,无法对 snack 类重新实例化导致。

2.3 项目中 tools/install_lib.sh 说明

执行此脚本时会检测是否有libnewt.so.0.52libslang.so.2 文件,如果没有则进行拷贝,拷贝的库为 centos 6.5 系统的库文件

备注: Centos6.x 及以上系统时无需操作

3 本仓库依赖的 snack 版本

使用的 Centos7.6 下的 snack(2014 年)

$ cd /usr/lib64/python2.7/site-packages
$ ll | grep snack 
-rwxr-xr-x 1 root root  40904 Jun 10  2014 _snackmodule.so
-rw-r--r-- 1 root root  30957 Jun 10  2014 snack.py

在 centos6.X 上也是可以运行的