Movwin:我的(未发布)TUI 框架

Hacker News Top 工具

摘要

一篇博文,详细介绍了作者个人未发布的、基于 ncurses 的 Python TUI 框架 'movwin',重点突出了其对 Unicode 的支持和性能表现,并因担心 AI 公司爬取代码而决定将其保密。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/06/01 13:42

# movwin:我的(未发布)TUI框架 来源:https://movq.de/blog/postings/2026-05-29/0/POSTING-en.html 博客 (https://movq.de/blog/)\-git (https://movq.de/git/)\-桌面 (https://movq.de/desktop/)\-联系 (https://movq.de/contact.html) --- 2026-05-29 很长一段时间以来,编写带某种 GUI(或 TUI)的程序对我来说一直不太满意。库来了又走,趋势来了又走。你不得不不断跟随上游的新决策并调整代码。有时你完全不认同上游的决定,然后就得寻找新的框架。这有点令人疲惫。我的项目经常维持 5 年、10 年甚至更久,而在这段时间里**会有很多**变化发生。 于是,在 2025 年 12 月底的上一届 Advent of Code 之后,我决定开始自己做一个 TUI 框架。这不是一个容易的决定,因为我知道这将耗费大量工作。我寻找过替代方案,但找不到任何我喜欢的——或者足够快的。最近性能似乎真的变差了,有些框架光是初始化就需要两秒钟。 这篇博文只是对 movwin 当前状态的一次简单巡览,因为我决定暂时不发布这些代码。目前情况不太好:我发布的任何东西都会被"AI"公司抓取,然后他们拿去卖,完全无视代码附带的许可协议。我对此无法接受。 ## 基础 它是一个 Python 库。我不再是 Python 的最大粉丝,但它确实有它的优势,主要是**庞大**的标准库,让我能轻松做很多事情。 movwin——代表"movq's windows and widgets"——构建在 ncurses 之上。ncurses 在终端兼容性方面承担了繁重的工作。movwin 没有使用的是 ncurses 的子窗口或 pads。相反,ncurses 充当一种(智能的)帧缓冲,我可以向其绘制内容,同时它也是键盘和鼠标输入的来源。 一个主要目标是"可接受"的 Unicode 支持。movwin 不太可能支持从右到左之类的功能,但我**绝对不**想在某处放一个 emoji 就导致整个布局爆炸。换句话说,movwin 必须知道一个 Unicode 序列在终端中可能占用多少个单元格。这里的大问题是这取决于终端,所以永远无法完美。 只有一个依赖(除了 Python 3.14 本身):wcwidth (https://pypi.org/project/wcwidth/) 及其 `wcswidth()` 函数。这用于测量文本的"外观尺寸":例如,"♀️" 占两个单元格宽度。 从很早开始,movwin 就有了"Window"和"Window Manager"的概念。我在制作整个东西时脑海中浮现的是古老的 DOS TUI: tc.png (https://movq.de/blog/postings/2026-05-29/0/tc.png) pcdoshelp.png (https://movq.de/blog/postings/2026-05-29/0/pcdoshelp.png) pcdosshell.png (https://movq.de/blog/postings/2026-05-29/0/pcdosshell.png) 要是能真正重现这种体验就好了,在某种程度上我也做到了,但 UNIX 终端中的鼠标支持并不太好。在我的测试中,没有终端默认报告鼠标**移动**事件(只有"按下/释放"),我不得不调整 terminfo 文件。更糟糕的是,在大的终端窗口中,有些鼠标事件根本不会触发。因此,movwin 中的鼠标支持目前相当有限。也许我甚至会完全移除它,因为,嗯,用键盘驱动其实也是好事。不过对于移动窗口或控制滚动区域之类的事情,鼠标支持还是很不错的。 另一个主要目标是"可接受"的性能,意思是:"在我那台 10 年旧的、装有 Celeron CPU 的小 Intel NUC 上,启动时间大约 200-300 毫秒是可以接受的,但不应超过这个数。" Python 的启动时间确实是个问题,因为 `import` 超级慢。我不得不在一些地方做出牺牲,比如不使用 `dataclasses`。在这台 NUC 上,光是 import 的时间就足以致命: ``` $ time python -c 'exit(0)' real 0m0.061s user 0m0.048s sys 0m0.011s $ time python -c 'from dataclasses import dataclass' real 0m0.151s user 0m0.115s sys 0m0.027s ``` ## 应用程序 这一切的起因是一个叫做 `tracktivity` 的小程序:我用它来追踪活动和事件,比如咖啡因摄入或天气事件。它操作 CSV 文件: ``` rfc3339datetime,Food[coffee;blacktea;greentea],Comment 2026-05-29T14:04:00+00:00,coffee,at home 2026-05-29T14:04:43+00:00,blacktea,just some dummy entry :-) 2026-05-29T14:04:51+00:00,blacktea,more tea ``` `Food` 列被配置为有几个选项可以选,`Comment` 列是自由文本。`tracktivity` 基于该文件构建一个 UI 表单,看起来像这样: tracktivity.png (https://movq.de/blog/postings/2026-05-29/0/tracktivity.png) 它没什么花哨,而且一些控件类型仍然缺失,例如还没有合适的表格或列表控件。 `tracktivity` 是一个简单的单窗口全屏程序,但它仍然使用 `Window` 类(像所有 movwin 程序应该做的那样),所以如果我愿意,我可以调整大小并移动这个窗口(点击观看视频): tracktivity-moveresize.mp4 (https://movq.de/blog/postings/2026-05-29/0/tracktivity-moveresize.mp4) "弹出窗口"也被实现为新的窗口。movwin 自带一些内置弹出窗口,比如"是/否框"、"输入框"或"消息框"。 `bine` 是另一个使用 movwin 的程序:它是一个基础的十六进制编辑器。我真正想要的是一个**简单**的十六进制编辑器,性能良好,并且底部有一个小信息面板(点击观看视频): bine.mp4 (https://movq.de/blog/postings/2026-05-29/0/bine.mp4) 比如,"光标下的字节,当解释为有符号 8 位整数时,其值是多少"或同样的事情对于 2、4、8 字节,有符号和无符号。或者尝试将字节(从光标位置开始)解释为 UTF-8。 这是我滚动菜单以显示更多可用功能(点击观看视频): bine-menus.mp4 (https://movq.de/blog/postings/2026-05-29/0/bine-menus.mp4) 还有两个演示,第一个展示"编辑二进制"功能,它使用了一个带有应用定义控件的自定义弹出窗口,第二个视频展示了撤销/重做(点击观看视频): bine-binary.mp4 (https://movq.de/blog/postings/2026-05-29/0/bine-binary.mp4) bine-undoredo.mp4 (https://movq.de/blog/postings/2026-05-29/0/bine-undoredo.mp4) `bine` 大量使用 `mmap()`,用 Python 编写并不影响性能。正如你在第一个视频中看到的,在一个 2GB 的文件中搜索 ASCII 字符串也许只用了一秒。对我来说这已经足够好了,而且我没有花任何时间试图优化它。Python 的 `bytes.find()` (https://docs.python.org/3/library/stdtypes.html#bytes.find) 操作在 mmap 对象上,它完成了所有繁重的工作。 `bine` 也能很好地利用窗口系统:你可以在一个新窗口中打开另一个文件,而第一个窗口不受影响。当打开多个文件时,`WindowManager` 类会应用平铺布局(或者你可以切换到"将焦点窗口置于全屏模式"——或者如果你愿意,也可以自由移动它们): bine-multiwin.png (https://movq.de/blog/postings/2026-05-29/0/bine-multiwin.png) bine-multiwin-chaos.png (https://movq.de/blog/postings/2026-05-29/0/bine-multiwin-chaos.png) 最后,我最近制作了一个非常简单的计时程序: watwar2.png (https://movq.de/blog/postings/2026-05-29/0/watwar2.png) 它有一个开始/停止按钮,并显示一个简单的"记录"列表:在那个截图中,我从 4:59 开始工作到 10:08,然后休息,之后在 12:21 又开始工作。到我截图时,总计为 9 小时 28 分钟。 截图中看不到的是,这个工具还会将 9:28 显示在连接到 Arduino 的一个小七段数码管上: arduino.webp (https://movq.de/blog/postings/2026-05-29/0/arduino.webp) 我在工作中用这个东西来注意我已经工作了几个小时。:-) 当一天结束时,我可以将这些数据上传到我们基于云的时间追踪服务(这就是"传输"菜单的作用)。 顺便提一下:在 Linux 上,可以使用 timerfd (https://docs.python.org/3/library/os.html#os.timerfd_create) 来实现定时器事件。movwin 的主循环基于 `select()`,你可以注册任意文件描述符。timerfd 以固定间隔触发,所以这允许 UI 和 Arduino 显示定期更新。 ## 更多好东西 ### 主题 movwin 自带两个内置主题: bine-bluegray.png (https://movq.de/blog/postings/2026-05-29/0/bine-bluegray.png) bine-amber.png (https://movq.de/blog/postings/2026-05-29/0/bine-amber.png) "琥珀色"主题让我充满怀旧感,因为它让我想起我第一台琥珀色 CRT 显示器上的 DOS 程序。 如果你愿意,可以把颜色定义放在 `~/.config/movwin/colors.json` 中,然后创建自己的主题,但这显然会有些棘手,因为应用程序可能也定义了自己的颜色(你**可以**在该文件中覆盖它们的颜色)。 默认情况下,movwin 根据月份选择当前主题。如果你在北半球,在十二月左右的月份会看到"琥珀色"主题,其他月份是更亮的"蓝灰色"主题。自动选择可以切换到"南半球",或者你也可以直接设置 `$MOVWIN_THEME` 来选择某个主题。 我最近在别处写道: > 在过去的几周里,我越来越欣赏**菜单**的概念。它们提供了功能易于发现的能力。无需阅读手册。它们按类别组织。例如,我在查找程序的全局设置时不会去打开"格式"菜单。由于"加速键",它们可以用键盘驱动。而且当一个菜单项有直接快捷键(如 `Ctrl+U`)时,它会直接在菜单中显示出来。(当然,并非所有菜单系统都实现了所有这些。)我们曾经认为菜单是理所当然的,因为那是 GUI 程序的常规操作。但感觉它们在某处丢失了……尤其是在(UNIX)终端程序和网站上。还有智能手机上。 我特别喜欢 movwin 中热键的工作方式: ```python menu_tree = MenuRoot( [ MenuSub( '&File', children=[ MenuItem( '&Quit', cb_quit, hotkey='KEY_F3', ), ], ), MenuSub( '&Edit', children=[ MenuItem( 'Edit &raw file', cb_edit_raw, hotkey='KEY_F5', arg=state, ), ], ), ... ], ) ``` 所以当你定义菜单结构时,你可以直接在那里指定菜单项的热键。我喜欢这一点,因为它是自文档的。不需要专门的 F3 处理函数,也不需要将 F3 放到帮助页面中,因为菜单已经告诉你这个键是干什么的。(我可以在 `bine` 中更多地利用这一点。) 至于加速键:在上面的例子中,`Alt+f, q` 会调用 `cb_quit()`,`Alt+e, r` 会调用 `cb_edit_raw()`。 菜单可以任意嵌套,尽管我还没有实际需要用到这一点: menu-nesting.png (https://movq.de/blog/postings/2026-05-29/0/menu-nesting.png) 不过菜单缺少鼠标支持。目前,我懒得实现它。而且正如我说的,也许我无论如何都会移除鼠标支持。 而且菜单不会在下面的项目上投下阴影。同样,我现在也懒得做。:-) ### 编辑框和 Unicode 编辑框对 Unicode 的理解足够到位,使得水平滚动不会出现故障(点击观看视频): edit-unicode.mp4 (https://movq.de/blog/postings/2026-05-29/0/edit-unicode.mp4) 当两只单元格宽的企鹅从屏幕左侧离开时,滚动并不完美,但这里主要的是控件不会将企鹅绘制到分配的区域之外。 (这种裁剪适用于所有文本,不仅仅是编辑框。不过编辑框有点特殊,因为它们实现了自己的水平滚动。) ## 接下来往哪里去? 首先,我对目前的状态非常满意。代码中还有一些待办事项,但它已经相当可用了。拥有一个能做我所需所想、并且不会在下一个版本中意外改变的框架,感觉真好。ncurses 超级稳定,它不会做奇怪的事情。wcwidth 如果必要的话我可以 fork。Python 本身也相对稳定,而且我**认为**他们从艰难的 2 到 3 过渡中学到了教训,短期内不会再这样做?不确定。不管怎样,movwin 感觉像是一个在 5 年、10 年、15 年后仍然能工作——而不会太折腾的东西。 还有一些"更大"的事情我想实现,比如合适的列表视图,也许还有树形视图,以及最重要的文件选择对话框。 然后……我们拭目以待。

相似文章

别再做终端界面了

Hacker News Top

作者主张用原生用户界面替代终端界面,并展示了几个借助AI以极少量代码创建的应用程序。

别再做 TUI 了

Simon Willison's Blog

Thomas Ptacek 主张构建原生图形用户界面而非文本用户界面,他指出编码代理已经降低了开发成本。Simon Willison 基于自己使用 vibe-coded macOS 应用的经验支持这一观点。

@jakevin7: 发表个暴论,TUI 会逐渐式微甚至被淘汰。 我已经很久没有用 claude code 了。基本都是用 slock。 对于临时任务,现在用的更多的是 codex desktop,偶尔用 claude desktop。 让我开始重新思考 TU…

X AI KOLs Following

文章讨论了TUI(终端用户界面)在AI编程工具中逐渐式微的趋势,作者认为随着模型能力增强,TUI将被CLI+server架构或Web UI取代,并分享了从Claude Code转向Slock、Codex Desktop等工具的个人体验。