ウィンドウタイトルの変更について通知を受ける


9

...ポーリングなし。

現在フォーカスされているウィンドウがいつ変更されたかを検出して、システムのカスタムGUIを更新できるようにしたいと考えています。

興味のあるポイント:

  • リアルタイムの通知。ラグが0.2秒の場合は問題ありません。ラグが1秒の場合は問題あり、ラグが5秒の場合はまったく受け入れられません。
  • リソースの使いやすさ:このため、ポーリングは避けたいです。xdotool getactivewindow getwindownameたとえば、0.5秒ごとに実行することは非常に問題なく動作しますが、2つのプロセスを1秒間起動することは私のシステムにとってそれほど友好的ですか?

ではbspwmbspc subscribeウィンドウのフォーカスが変わるたびに、いくつかの(非常に)基本的な統計を含む行を表示するwhich を使用できます。このアプローチは最初は良いように見えますが、これを聞いてもウィンドウタイトルが自動的に変更されることは検出されません(たとえば、Webブラウザーでタブを変更すると、この方法に気付かれなくなります)。

では、Linuxで0.5秒ごとに新しいプロセスを生成することは問題ありません。そうでない場合、どうすればより適切に処理できますか?

私の頭に浮かぶのは、ウィンドウマネージャーの動作をエミュレートすることです。しかし、「ウィンドウの作成」、「タイトルの変更要求」などのイベントのフックを、作業中のウィンドウマネージャーから独立して作成できますか、それともウィンドウマネージャー自体になる必要がありますか?これにはrootが必要ですか?

(私の頭に浮かんだもう1つのことは、xdotoolのコードを見て、興味のあるものだけをエミュレートして、ボイラープレートを生成するすべてのプロセスを回避できるようにすることですが、それでもポーリングされます。)

回答:


4

フォーカスを変更するアプローチをKwin 4.xで確実に機能させることはできませんでしたが、最新のウィンドウマネージャーは、_NET_ACTIVE_WINDOW変更をリッスンできるルートウィンドウのプロパティを維持しています。

これがまさにPythonの実装です:

#!/usr/bin/python
from contextlib import contextmanager
import Xlib
import Xlib.display

disp = Xlib.display.Display()
root = disp.screen().root

NET_ACTIVE_WINDOW = disp.intern_atom('_NET_ACTIVE_WINDOW')
NET_WM_NAME = disp.intern_atom('_NET_WM_NAME')  # UTF-8
WM_NAME = disp.intern_atom('WM_NAME')           # Legacy encoding

last_seen = { 'xid': None, 'title': None }

@contextmanager
def window_obj(win_id):
    """Simplify dealing with BadWindow (make it either valid or None)"""
    window_obj = None
    if win_id:
        try:
            window_obj = disp.create_resource_object('window', win_id)
        except Xlib.error.XError:
            pass
    yield window_obj

def get_active_window():
    win_id = root.get_full_property(NET_ACTIVE_WINDOW,
                                       Xlib.X.AnyPropertyType).value[0]

    focus_changed = (win_id != last_seen['xid'])
    if focus_changed:
        with window_obj(last_seen['xid']) as old_win:
            if old_win:
                old_win.change_attributes(event_mask=Xlib.X.NoEventMask)

        last_seen['xid'] = win_id
        with window_obj(win_id) as new_win:
            if new_win:
                new_win.change_attributes(event_mask=Xlib.X.PropertyChangeMask)

    return win_id, focus_changed

def _get_window_name_inner(win_obj):
    """Simplify dealing with _NET_WM_NAME (UTF-8) vs. WM_NAME (legacy)"""
    for atom in (NET_WM_NAME, WM_NAME):
        try:
            window_name = win_obj.get_full_property(atom, 0)
        except UnicodeDecodeError:  # Apparently a Debian distro package bug
            title = "<could not decode characters>"
        else:
            if window_name:
                win_name = window_name.value
                if isinstance(win_name, bytes):
                    # Apparently COMPOUND_TEXT is so arcane that this is how
                    # tools like xprop deal with receiving it these days
                    win_name = win_name.decode('latin1', 'replace')
                return win_name
            else:
                title = "<unnamed window>"

    return "{} (XID: {})".format(title, win_obj.id)

def get_window_name(win_id):
    if not win_id:
        last_seen['title'] = "<no window id>"
        return last_seen['title']

    title_changed = False
    with window_obj(win_id) as wobj:
        if wobj:
            win_title = _get_window_name_inner(wobj)
            title_changed = (win_title != last_seen['title'])
            last_seen['title'] = win_title

    return last_seen['title'], title_changed

def handle_xevent(event):
    if event.type != Xlib.X.PropertyNotify:
        return

    changed = False
    if event.atom == NET_ACTIVE_WINDOW:
        if get_active_window()[1]:
            changed = changed or get_window_name(last_seen['xid'])[1]
    elif event.atom in (NET_WM_NAME, WM_NAME):
        changed = changed or get_window_name(last_seen['xid'])[1]

    if changed:
        handle_change(last_seen)

def handle_change(new_state):
    """Replace this with whatever you want to actually do"""
    print(new_state)

if __name__ == '__main__':
    root.change_attributes(event_mask=Xlib.X.PropertyChangeMask)

    get_window_name(get_active_window()[0])
    handle_change(last_seen)

    while True:  # next_event() sleeps until we get an event
        handle_xevent(disp.next_event())

私が誰かのために例として書いたより完全にコメントされたバージョンは、この要点にあります。

更新:これで、_NET_WM_NAME要求された内容を正確に実行するための後半(のリッスン)も示されます。

アップデート#2: ...そして3番目の部分:WM_NAMExtermのようなものが設定されていない場合のフォールバック_NET_WM_NAME。前者は呼ばコーディングレガシー文字を使用することになっている間(後者はUTF-8でエンコードされ化合物のテキストを誰がどのようにして仕事に知っているようだないので、あなたがプログラムは、彼らがそこに持っているとバイトのどんな流れ投げ得るが、xprop ちょうど仮定を ISO-8859-1になります。)


おかげで、それは明らかによりクリーンなアプローチです。私はこの特性を知りませんでした。
rr- 2017年

@ rr-ウォッチングのデモンストレーションも行う_NET_WM_NAMEように更新したので、私のコードは、あなたが要求したものに対する概念の証明を提供します。
ssokolow 2017年

6

まあ、@ Basileのコメントのおかげで、私は多くのことを学び、次の実用的なサンプルを思い付きました:

#!/usr/bin/python3
import Xlib
import Xlib.display

disp = Xlib.display.Display()
root = disp.screen().root

NET_WM_NAME = disp.intern_atom('_NET_WM_NAME')
NET_ACTIVE_WINDOW = disp.intern_atom('_NET_ACTIVE_WINDOW')

root.change_attributes(event_mask=Xlib.X.FocusChangeMask)
while True:
    try:
        window_id = root.get_full_property(NET_ACTIVE_WINDOW, Xlib.X.AnyPropertyType).value[0]
        window = disp.create_resource_object('window', window_id)
        window.change_attributes(event_mask=Xlib.X.PropertyChangeMask)
        window_name = window.get_full_property(NET_WM_NAME, 0).value
    except Xlib.error.XError:
        window_name = None
    print(window_name)
    event = disp.next_event()

xdotool単純に実行するのではなく、Xによって生成されたイベントに同期してリッスンします。これは、まさに私が求めていたものです。


xmonadウィンドウマネージャーを使用している場合は、XMonad.Hooks.EwmhDesktopsを構成に含める必要があります
Vasiliy Kevroletin 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.