如何通过 shell 脚本检测我的终端是否在 GUI 中具有焦点?

如何通过 shell 脚本检测我的终端是否在 GUI 中具有焦点?

问题/目标描述

理想情况下,我想要一个好的从 shell 脚本检测窗口是否具有焦点的方法。我所说的“好”方式是指需要最少步骤并且最好是的某种方式不是需要盲目地筛选每个打开的窗口,根据标题找到我的窗口。

目的是控制许多不同脚本中的通知——所以我只是在寻找一个可以适用于任何和所有脚本的通用解决方案。

到目前为止,我想出的办法是迂回和老套的——如下:

  1. 将我的标题设置为独特或机械相关的内容(在我的模型中,它是我的 PTS 路径,或者更可靠的是 UUID)。迫切希望这个标题不会被什么东西覆盖。

  2. 按标题获取所有打开的窗口的列表。

  3. 迭代列表以通过将其与标题元素匹配来识别我的窗口。 (请注意,如果另一个窗口碰巧具有相同的标题元素,则可能会出现错误。)

  4. 检测该窗口是否具有焦点。

应该指出的是,我不是想要实施这一点,并且只会作为最后的手段。所以我在这里要求的是不是这个的东西

妥协

这个解决方案显然很糟糕,所以我想知道是否有任何更好的方法。我更喜欢便携、优雅和完美的东西,但我认识到可能需要妥协。经过更好的我的意思是以下任何一项:

  1. 仅适用于特定终端仿真器的解决方案,例如通过让终端仿真器本身设置一个环境变量,允许脚本检测它所在的窗口。

  2. 一种不需要设置标题的解决方案,而是在窗口状态下使用一些其他不可见标记,该标记可通过附加到所述窗口的 shell 脚本进行访问和检测。

  3. 重复使用父进程阶梯来查找父终端仿真器 PID,并从那里开始工作(请注意,通过重复使用进程树来检测启动脚本的父进程的解决方案仅在脚本在本地运行时才有效,所以这个解决方案不完整,但仍然很好!)

状况

我收到关于我的首选解决方案应该在什么条件下运行的问题,答案是越多越好。但至少,我想要一些有用的东西:

  1. 在本机运行的单选项卡终端会话中(默认场景)。

  2. 在像 tmux 这样的终端多路复用器中。 (不同终端多路复用器之间的可移植性是首选,但实际上不是必需的。)

我非常欣赏的额外功能(按重要性排序)包括:

  1. 能够通过 telnet 和 SSH 进行远程连接。

  2. 能够区分在多选项卡终端会话中打开了哪个选项卡。


概括

我想要一个好的查找我的 shell 脚本附加到哪个终端仿真器窗口的方法,以便我可以检测它是否具有焦点。

请注意,我已经了解了以下机制如何迭代打开的窗口,以及如何检测它们是否具有焦点以及它们具有什么标题。我知道xdotool和的存在xprop,并且这个问题与这些工具的基本机制无关(除非有一些隐藏的黑魔法功能,我不知道它回避了我当前解决方案的内在黑客性。)

我不想这样做的原因是因为它太可怕了。还有其他解决方案可以完成同样的事情吗?

答案1

有一个焦点输入/焦点输出模式。启用:

echo -ne '\e[?1004h'

禁用:

echo -ne '\e[?1004l'

在每个焦点事件上,您都会从输入流接收\e[I(in) 或(out)。\e[O

当您启用此模式时,GNOME 终端(和其他基于 VTE 的终端)也会报告当前状态。也就是说,您可以启用然后立即禁用它来查询一次值。

您可以结合read超时,或指定读取 3 个字符来获取响应。但请注意,它会受到竞争条件的影响,例如,如果您提前输入了某些字符。

答案2

if [ "$(xdotool getwindowfocus)" -eq "$WINDOWID" ]; then
   echo I have the focus
fi

如果 screen/tmux 是从其他地方启动并且只是附加在当前窗口中,则这在 screen/tmux 内不起作用。

答案3

我使用适用于 macOS 的 iTerm Python API 编写了一个解决方案。这里是Python守护进程,它将来自iTerm的数据存储在redis中(需要布里什pip install -U brish:) :

#!/usr/bin/env python3

import AppKit
bundle = "com.googlecode.iterm2"
if not AppKit.NSRunningApplication.runningApplicationsWithBundleIdentifier_(bundle):
    AppKit.NSWorkspace.sharedWorkspace().launchApplication_("iTerm")

import os
from brish import z, zp
os.environ["ITERM2_COOKIE"] = z("""osascript -e 'tell application "iTerm2" to request cookie' """).outrs

import asyncio
import iterm2

async def main(connection):
    app = await iterm2.async_get_app(connection)
    async with iterm2.FocusMonitor(connection) as monitor:
        while True:
            update = await monitor.async_get_next_update()
            window = app.current_terminal_window
            if (update.active_session_changed or update.selected_tab_changed or update.window_changed) and window.current_tab:
                if update.window_changed:
                    zp('redis-cli set iterm_focus {update.window_changed.event.name} 2>&1')
                zp('redis-cli set iterm_active_session {window.current_tab.active_session_id} 2>&1')


iterm2.run_forever(main)

这里是外壳包装:

function iterm-session-active() {
    redis-cli --raw get iterm_active_session
}

function iterm-session-my() {
    if [[ "$ITERM_SESSION_ID" =~ '[^:]*:(.*)' ]] ; then
        ec "$match[1]"
    else
        return 1
    fi
}

function iterm-session-is-active() {
    [[ "$(iterm-session-active)" == "$(iterm-session-my)" ]]
}

function iterm-focus-get() {
    redis-cli --raw get iterm_focus
}

function iterm-focus-is() {
    [[ "$(iterm-focus-get)" == TERMINAL_WINDOW_BECAME_KEY ]]
}

单击链接可查看我的 git 存储库中代码的最新版本。不过,您需要从代码中清理一些不必要的内容。

相关内容