我想知道是否可以在 Ubuntu 中监控应用程序的打开/关闭/最小化事件。我最初的目标是监控我打开电报查看消息的次数。
PS:我正在使用 Ubuntu 20.04LTS(X11)。
答案1
只是为了好玩,因为你在 X 上
使用 python(或其他一些语言),我们可以使用来自屏幕和窗口关注窗口的创建和/或状态变化。这包括最大化和最小化窗口。
这正是以下脚本所做的。随后,它会维护一个日志文件,如果您创建、最小化或取消最小化特定 WM_CLASS(以及应用程序)的窗口,该文件将更新。您可以通过打开终端、键入xprop WM_CLASS
+ Return,然后单击窗口主题(“telegramdesktop”表示电报或类似名称)来找到目标应用程序的 WMCLASS。
请注意,我让脚本在每次(日志记录)会话后重置日志文件,否则日志文件会随着时间的推移变得巨大。
日志文件里有什么?
日志文件 ( ~/.windowlog.txt
) 将记录给定 WM_Class 的窗口的创建、关闭和状态更改。每次窗口取消最小化时,计数器都会加一,因此在一天结束时,您可以看到以下活动:
found window: Telegram
state changed: visual(1)
state changed: minimized
state changed: visual(2)
state changed: minimized
state changed: visual(3)
window closed:Telegram
new window:Telegram
state changed: visual(4)
state changed: minimized
state changed: visual(5)
笔记脚本是为应用程序编写的,就像您问题中的一样。为了保持更详细的记录每个窗口,处理数据需要更复杂的编码。
剧本
#!/usr/bin/env python3
import gi
gi.require_version("Gtk", "3.0")
gi.require_version("Wnck", "3.0")
from gi.repository import Gtk, Wnck
import os
import sys
class WatchWindow:
def __init__(self, wmclass):
self.visual = None
self.count_visual = 0
self.wnck_scr = Wnck.Screen.get_default()
self.wmclass = wmclass
self.logpath = os.environ["HOME"] + "/.windowlog.txt"
self.run_watching()
Gtk.main()
def write_to_log(self, newline):
with open(self.logpath, "a+") as logfile:
logfile.write(newline + "\n")
def readable_state(self, minimized):
n = ""
if not minimized:
self.count_visual = self.count_visual + 1
n = "(" + str(self.count_visual) + ")"
return ["minimized", "visual"][[True, False].index(minimized)] + n
def logstate(self, window, *args):
old_state = self.visual
new_state = window.is_minimized()
# only register if minimized state really changed
if old_state != new_state:
self.visual = new_state
message = "state changed: " + self.readable_state(self.visual) # log
print(message)
self.write_to_log(message)
def log_new(self, screen, window):
if window.get_class_group_name().lower() == self.wmclass:
message = "new window:" + window.get_name() # log new
print(message)
self.write_to_log(message)
self.watch_window(window)
self.logstate(window)
def log_closed(self, screen, window):
if window.get_class_group_name().lower() == self.wmclass:
name = window.get_name()
self.visual = None
print("window closed:", name) # log closed
def watch_window(self, window, firstcall=False):
if window.get_class_group_name().lower() == self.wmclass:
if firstcall:
message = "found window:" + window.get_name()
print(message) # log please
self.write_to_log("found window: " + window.get_name())
self.logstate(window)
window.connect("state_changed", self.logstate)
def run_watching(self):
try:
os.remove(self.logpath)
except FileNotFoundError:
pass
self.wnck_scr.force_update()
for w in self.wnck_scr.get_windows():
self.watch_window(w, True)
self.wnck_scr.connect("window-opened", self.log_new)
self.wnck_scr.connect("window-closed", self.log_closed)
args = sys.argv[1:]
if not args:
print("Insufficient arguments! We need a wm_class to watch...")
else:
WatchWindow(args[0])
设置
将脚本复制到一个空文件中,另存
windowlogger.py
为使其可执行测试-在终端窗口中运行它,使用 WM_CLASS 作为参数运行它(我想
telegramdesktop
),所以:/path/to/windowlogger telegramdesktop
查看终端中的输出是否正常,查看日志文件内部
~/.windowlog.txt
是否一切正常。如果您愿意,可以将其添加到您的启动应用程序中。
注意:
可能您需要添加一个或多个库,请检查终端输出。
日志窗口是否处于活动状态?
从评论中,我了解到您认为窗口只有在活动窗口时才“已使用”。
在这种情况下,我们可以使脚本变得更简单,因为我们仅有的需要查看active_window_changed
信号。如果我们还记录时间使用情况(每次使用/总使用时间),您可以清楚地了解您花了多少时间盯着(任何)电报窗口。日志文件如下所示:
start_time: woensdag, oktober 06 2021, 11:32:53
window activated (1)
window hidden or closed, was active: 0:00:04 total: 0:00:04
window activated (2)
window hidden or closed, was active: 0:00:06 total: 0:00:10
window activated (3)
window hidden or closed, was active: 0:00:12 total: 0:00:22
window activated (4)
window hidden or closed, was active: 0:00:07 total: 0:00:29
在这种情况下,脚本:
#!/usr/bin/env python3
import gi
gi.require_version("Gtk", "3.0")
gi.require_version("Wnck", "3.0")
from gi.repository import Gtk, Wnck
import os
import sys
import time
import datetime
class WatchWindow:
def __init__(self, wmclass):
self.visual = False
self.count_visual = 1
self.wnck_scr = Wnck.Screen.get_default()
self.wmclass = wmclass
self.logpath = os.environ["HOME"] + "/.windowlog.txt"
self.total_time = 0
self.last_time = time.time()
self.run_watching()
Gtk.main()
def write_to_log(self, newline):
with open(self.logpath, "a+") as logfile:
logfile.write(newline + "\n")
def get_readable_time(self, elapsed):
return str(datetime.timedelta(seconds=elapsed))
def log_active(self, *args):
try:
# active_class can be None, e.g. on startup
active_class = self.wnck_scr.get_active_window().get_class_group_name()
except AttributeError:
active_class = ""
newvisual = active_class.lower() == self.wmclass.lower()
oldvisual = self.visual
currtime = time.time()
if newvisual != oldvisual:
if newvisual:
self.last_time = currtime
message = "window activated (" + str(self.count_visual) + ")"
self.count_visual = self.count_visual + 1
else:
winactive_time = currtime - self.last_time
self.last_time = currtime
self.total_time = self.total_time + winactive_time
message = "window hidden or closed, was active: " + \
self.get_readable_time(round(winactive_time)) +\
"\t" + "total: " +\
self.get_readable_time(round(self.total_time))
self.write_to_log(message)
self.visual = newvisual
def run_watching(self):
try:
os.remove(self.logpath)
except FileNotFoundError:
pass
time_stamp_message = "start_time: " + time.strftime(" %A, %B %d %Y, %H:%M:%S")
self.write_to_log(time_stamp_message)
self.wnck_scr.force_update()
self.wnck_scr.connect("active-window-changed", self.log_active)
self.log_active()
args = sys.argv[1:]
if not args:
print("Insufficient arguments! We need a wm_class to watch...")
else:
WatchWindow(args[0])
设置相同。