记一次 macOS 前台窗口被“无形”抢焦点的排查:凶手是 lghub_agent
这几天我的 Mac 出现了一个非常烦人的问题:正在某个最前面的窗口里打字,突然就不输入了,细看一下发现是窗口的焦点被抢走了。更诡异的是,屏幕左上角菜单栏的应用名称并没有切换,看起来就像前台 App 没变,但焦点被什么东西偷走了。重启能缓解一阵子,然后它又会回来。经历了几次之后,终于不想再重启了,下决心排查出元凶。
这几天我的 Mac 出现了一个非常烦人的问题:正在某个最前面的窗口里打字,突然就不输入了,细看一下发现是窗口的焦点被抢走了。更诡异的是,屏幕左上角菜单栏的应用名称并没有切换,看起来就像前台 App 没变,但焦点被什么东西偷走了。
重启能缓解一阵子,然后它又会回来。经历了几次之后,终于不想再重启了,下决心排查出元凶。
我的目标很简单:当抢焦点的行为再发生时,我要知道当下谁是 active app。核心思路是,写个小脚本记录“当前激活进程”,等它抢焦点时看日志
macOS 本身就能告诉你“当前激活的应用是谁”,从 NSWorkspace.sharedWorkspace().activeApplication() 就能拿到:
NSApplicationNameNSApplicationPathNSApplicationProcessIdentifier
我需要做的就是:每 200ms 轮询一次,一旦发生变化就打印一行。
工具选择:用 uv 创建一个临时环境,不污染我的 Python
我本机的 Python 不是系统自带的 Python 3,而是通过 uv 安装的。uv 是当下最先进的 python 版本和 virtualenv 管理器,如果你还没有用过,强烈推荐你使用。
我们可以用它的“脚本内声明依赖”的能力:
- 把依赖写在脚本头部
uv run xxx.py直接跑(读取魔法格式的依赖声明注释,自动装依赖)
这个场景非常合适:我不想为了一个排查脚本在全局环境装一堆东西。
脚本:focus 监控器
我创建了如下内容的 find_focus_stealer.py:
# /// script
# requires-python = ">=3.10,<3.11"
# dependencies = [
# "pyobjc-framework-Cocoa",
# ]
# ///
from AppKit import NSWorkspace
from datetime import datetime
from time import sleep
last = None
while True:
app = NSWorkspace.sharedWorkspace().activeApplication() or {}
name = app.get("NSApplicationName")
pid = app.get("NSApplicationProcessIdentifier")
path = app.get("NSApplicationPath")
cur = (name, pid, path)
if cur != last:
last = cur
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print(f"{ts}: {name} pid={pid} path={path}")
sleep(0.2)
然后运行:
uv run find_focus_stealer.py
抓现行:日志里出现了 lghub_agent.app
脚本跑着,我就照常用电脑。等问题再次出现,我第一时间看终端输出的最后几行。
结果非常清晰,出现了 lghub_agent.app:Logitech G HUB 的后台 Agent 在抢焦点。
查了一下,发现Reddit上也有其他网友反馈 G hub 抢焦点的问题。目前官方也在调查,还没有解决方案。因此目前看,只能出现问题之后先到 activity monitor 里面把 g hub agent 先 kill 掉。如果后续再出现,就只能卸载 G hub了。