记一次 macOS 前台窗口被“无形”抢焦点的排查:凶手是 lghub_agent

这几天我的 Mac 出现了一个非常烦人的问题:正在某个最前面的窗口里打字,突然就不输入了,细看一下发现是窗口的焦点被抢走了。更诡异的是,屏幕左上角菜单栏的应用名称并没有切换,看起来就像前台 App 没变,但焦点被什么东西偷走了。重启能缓解一阵子,然后它又会回来。经历了几次之后,终于不想再重启了,下决心排查出元凶。

记一次 macOS 前台窗口被“无形”抢焦点的排查:凶手是 lghub_agent
Photo by Royce Fonseca / Unsplash

这几天我的 Mac 出现了一个非常烦人的问题:正在某个最前面的窗口里打字,突然就不输入了,细看一下发现是窗口的焦点被抢走了。更诡异的是,屏幕左上角菜单栏的应用名称并没有切换,看起来就像前台 App 没变,但焦点被什么东西偷走了。

重启能缓解一阵子,然后它又会回来。经历了几次之后,终于不想再重启了,下决心排查出元凶。

我的目标很简单:当抢焦点的行为再发生时,我要知道当下谁是 active app。核心思路是,写个小脚本记录“当前激活进程”,等它抢焦点时看日志

macOS 本身就能告诉你“当前激活的应用是谁”,从 NSWorkspace.sharedWorkspace().activeApplication() 就能拿到:

  • NSApplicationName
  • NSApplicationPath
  • NSApplicationProcessIdentifier

我需要做的就是:每 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.appLogitech G HUB 的后台 Agent 在抢焦点

查了一下,发现Reddit上也有其他网友反馈 G hub 抢焦点的问题。目前官方也在调查,还没有解决方案。因此目前看,只能出现问题之后先到 activity monitor 里面把 g hub agent 先 kill 掉。如果后续再出现,就只能卸载 G hub了。