监控在计算机w/Python和xorg服务器上花费的时间



我正在尝试制作一个脚本,该脚本将帮助我跟踪在计算机上花费的时间。这个脚本应该跟踪我何时开始、停止,以及我在每个";任务";。经过一番搜索,我发现了一个名为xdotool的终端实用程序,当它像这样运行时,它将返回当前关注的窗口及其标题:xdotool getwindowfocus getwindowna me。例如当聚焦于该窗口时,它返回:

linux - Monitering time spent on computer w/ Python and xorg-server - Stack Overflow — Firefox Developer Edition

这正是我想要的。我的第一个想法是检测聚焦窗口何时更改,然后获得发生这种情况的时间,但我找不到任何结果,所以我采用了每隔5秒运行一次这个命令的while循环,但这很麻烦,我非常喜欢聚焦更改方法,但这是我目前的代码:

#!/usr/bin/env python3
from subprocess import run
from time import time, sleep
log = []
prevwindow = ""
while True:
    currentwindow = run(['xdotool', 'getwindowfocus', 'getwindowname'], 
                                               capture_output=True, text=True).stdout
    if currentwindow != prevwindow:
        for entry in log:
            if currentwindow in entry:
                pass # Calculate time spent
        print(f"{time()}:t{currentwindow}")
        log.append((time(), currentwindow))
    prevwindow = currentwindow
    sleep(5)

我在Arch linux上使用dwm,如果这很重要的话

请参阅此要点。只需将日志记录机制放入handle_change函数中,它就可以工作,就像在Arch Linux-dwm系统上测试的那样。

出于存档目的,我在这里包含了代码。所有的功劳都归于GitHub上的Stephan Sokolow(sskolow(。

from contextlib import contextmanager
from typing import Any, Dict, Optional, Tuple, Union  # noqa
from Xlib import X
from Xlib.display import Display
from Xlib.error import XError
from Xlib.xobject.drawable import Window
from Xlib.protocol.rq import Event
# Connect to the X server and get the root window
disp = Display()
root = disp.screen().root
# Prepare the property names we use so they can be fed into X11 APIs
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}  # type: Dict[str, Any]

@contextmanager
def window_obj(win_id: Optional[int]) -> Window:
    """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 XError:
            pass
    yield window_obj

def get_active_window() -> Tuple[Optional[int], bool]:
    """Return a (window_obj, focus_has_changed) tuple for the active window."""
    response = root.get_full_property(NET_ACTIVE_WINDOW,
                                      X.AnyPropertyType)
    if not response:
        return None, False
    win_id = response.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=X.NoEventMask)
        last_seen['xid'] = win_id
        with window_obj(win_id) as new_win:
            if new_win:
                new_win.change_attributes(event_mask=X.PropertyChangeMask)
    return win_id, focus_changed

def _get_window_name_inner(win_obj: Window) -> str:
    """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  # type: Union[str, bytes]
                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: Optional[int]) -> Tuple[Optional[str], bool]:
    """Look up the window name for a given X11 window ID"""
    if not win_id:
        last_seen['title'] = None
        return last_seen['title'], True
    title_changed = False
    with window_obj(win_id) as wobj:
        if wobj:
            try:
                win_title = _get_window_name_inner(wobj)
            except XError:
                pass
            else:
                title_changed = (win_title != last_seen['title'])
                last_seen['title'] = win_title
    return last_seen['title'], title_changed

def handle_xevent(event: Event):
    """Handler for X events which ignores anything but focus/title change"""
    if event.type != X.PropertyNotify:
        return
    changed = False
    if event.atom == NET_ACTIVE_WINDOW:
        if get_active_window()[1]:
            get_window_name(last_seen['xid'])  # Rely on the side-effects
            changed = True
    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: dict):
    """Replace this with whatever you want to actually do"""
    print(new_state)
if __name__ == '__main__':
    # Listen for _NET_ACTIVE_WINDOW changes
    root.change_attributes(event_mask=X.PropertyChangeMask)
    # Prime last_seen with whatever window was active when we started this
    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())

最新更新