GUI 终端仿真器中如何实现光标闪烁?

GUI 终端仿真器中如何实现光标闪烁?

自计算早期以来,闪烁是一种常见的做法,尤其是对于光标而言。

但是,当我运行strace检查它们的系统调用时,终端模拟器konsole和 shellbash都不会注册任何类型的计时器(通过timer_settime())或间隔计时器(通过setitimer())。同时,这些程序无法使用自旋锁来等待一定时间。

真实终端能够做到这一点,因为它们的控制器可以理解闪烁转义控制序列。但图形监视器显然无法做到这些事情。

那么这些程序如何让文本闪烁,尤其是在图形环境中?文本也可以在非 X 图形终端中闪烁(就像按Ctrl+Alt+F2)。

闪烁的终端光标是如何发明的?这个问题展示了它们被发明的原因,以及真实终端如何实现它们的技术细节。

答案1

您的问题混淆了两个概念:闪烁光标和闪烁文本。

GNOME Terminal (VTE) 很长一段时间以来都支持闪烁光标,我在 5 年前添加了闪烁文本支持。让我分享三个关于闪烁文本支持的有趣细节,希望您会喜欢它们:)

这不是您问题的答案(我已经单独发布了实际答案),这只是一个额外的横向评论。


一个有趣的方面是:到底什么时候闪烁文本?

文本闪烁速度应该与光标闪烁速度相关联,还是不同的属性?我们以同样的速度前进,劫持了这个选项。 IIRCkonsole对两者使用不同的频率。

带有闪烁文本的多个终端是否应该同时闪烁?我们的结论是,是的,他们应该这样做,否则可能会非常烦人(就好像闪烁的文本一开始并不烦人一样)。何时打开或“关闭”文本与系统时钟相关。

每次按键后,闪烁光标始终会以完全“打开”状态重新启动。我们应该保持这种行为吗?结论是,是的,保持这种行为。

光标和文本是否应该同步闪烁?希望很容易看出我们不能同时拥有一切,必须放宽其中一个可能的标准。所以不,这两者不同步。

这不是回答这些问题的唯一可能的方法,但却是我们发现最有意义的方法。


\e[5m为了实现闪烁文本支持,何时看到相应的或\e[25m转义序列并不重要。滚动条可能设置为其非默认位置,显示回滚内容。显然,对于每个单元格,我们需要跟踪所有颜色和装饰属性,包括是否闪烁该单元格。重要的是当前视口内是否存在这样的单元格。

VTE有时需要重新绘制整个屏幕,但是有时只重新绘制屏幕的一部分(内容发生变化的地方)。

标准之一是:如果视口中没有闪烁的文本(在绝大多数情况下),我们不应该每隔 0.6 秒左右唤醒终端而不执行任何操作(或更糟糕:重新绘制相同的内容)。这对于节省能源、延长笔记本电脑电池寿命等非常重要。

很长一段时间以来,我一直在考虑周期性计时器。每当绘制闪烁文本时,都会启动一个周期性计时器,每次闪烁状态发生变化时该计时器都会启动。然后我们重新绘制屏幕(最好只绘制相关部分)。

但是:我们如何知道何时停止这个周期性计时器?很长一段时间我都没有找到一个干净、简单的答案。

然后便士掉了下来。我们不要考虑周期性计时器。我们只安装一个一次性计时器,因为当前屏幕上闪烁的文本需要更改其状态。如果视口中仍有闪烁的文本,则重新绘制将安装下一个一次性计时器,依此类推,直到那里的内容不再闪烁。这种坚如磐石且超级简单的解决方案的代价是最后一次额外的不必要的重新绘制,没有人关心(除非您调试或跟踪,并poll()在存在闪烁文本的情况下研究这些调用)。


许多人希望看到闪烁的文本(如果这就是应用程序发出的内容)。许多人讨厌这个功能,并且不想看到闪烁的文本。据推测,与光标仅在聚焦终端中闪烁类似,许多人希望仅在聚焦终端中看到闪烁的文本。

然而,GNOME Terminal 还提供了第四个不寻常的选项:仅在未聚焦的窗口中闪烁文本。这是我的想法,思考过程是:眨眼大概是用来将你的注意力吸引到真正意想不到和令人震惊的事情上。在您的注意力集中的情况下,这是没有必要的,并且可能会分散您的注意力(例如,如果您正在尝试解决您收到警报的问题)。然而,您的周边视觉对眨眼等变化更加敏感,而不仅仅是打印一次静态警报。

我们不会在我们的软件中进行分析,不会发送统计数据(这会引起愤怒),所以我没有关于这四个选项受欢迎程度的信息,特别是如果有人发现这个“仅在不集中注意力时”选项有用。

答案2

我已经strace了解了gnome-terminal-server,这就是GNOME Terminal的实际流程。

当其他空闲时,只是闪烁光标,它驻留在一个poll(..., 598)或类似的内核调用中,即poll()超时时间略短于 0.6 秒。 (GNOME 完整闪烁周期的默认值为 1.2 秒,因此每个“开”或“关”状态为 0.6 秒。这会因上次执行的实际工作量而缩短,例如重新绘制光标区域。)

poll()会一直等到给定的文件描述符之一上有活动,或者计时器到期,或者信号到达。

poll()不是“手动”实现的,而是 VTE(GNOME 终端和其他一些终端背后的终端仿真小部件)在 GLib 的主循环中注册事件处理程序,而 GLib 负责实际的底层实现。由 GLib 决定使用什么方法,例如它可以使用select、 或epollwith timerfd,或者大概还有其他选择。

我在'ing时看到几乎相同的poll()调用,并且我怀疑大多数其他终端模拟器也做了类似的事情。stracekonsole

在终端(Bash 或其他)上运行的程序仅输出要求闪烁的适当转义码,但并不参与实际执行此操作。打印转义符的程序可能早已消失,而闪烁的文本在终端上仍然可见。

相关内容