私の端末がGUIにフォーカスがあるかどうかをシェルスクリプトでどのように検出できますか?

私の端末がGUIにフォーカスがあるかどうかをシェルスクリプトでどのように検出できますか?

問題/目標説明

理想的にはいいねシェルスクリプトでウィンドウにフォーカスがあるかどうかを検出する方法です。 「良い」とは、最も少ないステップを必要とし、最良の方法を意味します。いいえタイトルに基づいて自分のウィンドウを見つけるには、開いているすべてのウィンドウを盲目的に調べる必要があります。

目的は、さまざまなスクリプトで通知を制御することです。したがって、私はすべてのスクリプトで動作できる一般的なソリューションを探しています。

これまで私が思いついたのは、次のようにロータリーで陳腐です。

  1. 私のタイトルを一意または機械的に関連付けられているものに設定します(私のモデルではPTSパスまたはより安定したUUIDです)。このタイトルが何かによって上書きされないことを切に願っています。

  2. 開いているすべてのウィンドウのリストをタイトル別に取得します。

  3. リストを繰り返してタイトル要素と一致させ、私のウィンドウを識別します。 (他のウィンドウに同じタイトル要素があるとエラーが発生する可能性があります。)

  4. ウィンドウにフォーカスがあることを確認してください。

注目すべき点は、するいいえこれを実装したいと思い、最後の手段としてのみ使用したいと思います。だから私がここで求めているのはこれはありません。

妥協

この解決策は明らかに不都合なので、より良い方法があるかどうか疑問に思います。私はポータブルでエレガントで完璧なものを好むが、妥協が必要かもしれないことを知っている。渡すより良いもの私の言葉は次のいずれかを意味します。

  1. 端末エミュレータ自体でスクリプトが現在存在するウィンドウを検出できるようにする環境変数を設定するなど、特定の端末エミュレータでのみ動作するソリューションです。

  2. タイトル設定は必要ありませんが、代わりにそのウィンドウに接続されているシェルスクリプトでアクセスして検出できるウィンドウの状態に他の見えないマーカーを使用するソリューションです。

  3. 親プロセスラダーを再利用して親端末エミュレータPIDを見つけてそこで作業します(プロセスツリーを再利用して起動スクリプトの親プロセスを検出するソリューションは、スクリプトがローカルで実行されている場合にのみ機能するため、このソリューションは不完全なソリューションです。それでもいい)

状況

私が好む解決策がどのような条件で実行されるべきかについての質問を受けました。答えは次のとおりです。多いほど良い。しかし、少なくとも私は役に立つことを望んでいます。

  1. ローカルで実行されるシングルタップ端末セッション(基本シナリオ)。

  2. tmuxのような端末マルチプレクサで。 (他の端末マルチプレクサ間の移植性が好まれますが、実際には必須ではありません。)

私が本当に高く評価する追加機能(重要度順)は次のとおりです。

  1. TelnetとSSHを介してリモートで接続する機能。

  2. マルチタブ端末セッションで開いているタブを区別する機能。


一般化する

私が欲しいいいね私のシェルスクリプトが接続されているターミナルエミュレータウィンドウを見つけてフォーカスがあるかどうかを検出する方法です。

私は次のメカニズムを理解しました。どのように開いたウィンドウを繰り返し、フォーカスがあるかどうか、タイトルが何であるかを検出する方法を繰り返します。私は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を使用してソリューションを作成しました。ここRedisにiTermのデータを保存するPythonデーモン。ブリシ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リポジトリにある最新バージョンのコードを見るには、リンクをクリックしてください。ただし、コードで不要な項目をクリーンアップする必要があります。

関連情報