折腾了两天的「自动挂起」:一个 inhibitor 如何让 Ubuntu 电源面板少一行

文章 · 标签 · 搜索 · 写一篇 · RSS · DSH · 植物诊断

Ubuntu 的「设置 → 电源」里应该有「自动挂起」这一项,可以分别设置用电池时和插电时空闲多久挂起。

我的机器上它整组消失了。从"是不是我改坏了"开始查,一路查到 systemd 的 inhibitor 机制,过程挺有意思,记一笔。

症状

打开 设置 → 电源 →「节电(P)」,只有:

屏幕变暗(D)      关
自动节电(S)      关
自动熄屏(B)      开     延时(E) 5 分钟

没有「自动挂起」那一组。GNOME 46 的面板源码里其实是有这一组的:

<AdwPreferencesGroup id="suspend_on_battery_group">
  <property name="title" translatable="yes">Automatic Suspend</property>
  <property name="visible">false</property>   <!-- 初始隐藏 -->
  ...

它是代码控制显隐的,条件只有一个:

self->can_suspend = can_power_action (self, connection, "CanSuspend");

排除法

既然条件是 CanSuspend,那就先确认它到底是什么。

1. 是不是硬件不支持? 不是。

cat /sys/power/state     # freeze mem     ← 只有 s2idle,没有 S3
cat /sys/power/disk      # [disabled]

固件确实只有 Modern Standby,但内核照样报告能挂起:

busctl call --system org.freedesktop.login1 /org/freedesktop/login1 \
  org.freedesktop.login1.Manager CanSuspend
# → s "yes"      ← root 调用是 yes

2. 是不是面板版本问题? 不是。把 gnome-control-center 降到装机时的 50.0-0ubuntu6,依然不显示。

3. 是不是 UPower 的锅? 不是。UPower 1.91 确实删掉了 CanSuspend 属性,但面板调的是 logind 的方法,不是 UPower 的属性。

4. 是不是 polkit 拦了? 不是。pkcheck 显示 polkit 里根本没注册这个动作。

5. 那它到底为什么返回 challenge? 普通用户调用:

busctl call --system org.freedesktop.login1 /org/freedesktop/login1 \
  org.freedesktop.login1.Manager CanSuspend
# → s "challenge"     ← 普通用户却拿到 challenge

root 是 yes、普通用户是 challenge,这个差异就是线索:说明有一个"当前是否被阻止"的运行时判断。

真凶

systemd 在新版本里,只要存在 sleep 类的 inhibitor,就把 CanSuspend 报成 challenge(意思是"可以做,但需要授权"),而不是 yes。面板只认 yes,于是整组隐藏。

而那个 inhibitor 是我自己加的 —— 为了防止机器半夜自己挂起(挂起就没法回微信了),我给隧道套了一层:

ExecStart=/usr/bin/systemd-inhibit --what=sleep:idle --why="DSH 隧道常驻,禁止挂起" --mode=block %h/.local/bin/dsh-tunnel.sh

一个 block 锁,把整个"能挂起"的能力锁死了。

修法

先把 inhibitor 拆掉:

ExecStart=%h/.local/bin/dsh-tunnel.sh

"不自动挂起"改由这两处保证(都不影响面板显示):

# gsettings:空闲后不挂起
gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-type 'nothing'
gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-battery-type 'nothing'
# logind:合盖/空闲也不挂起
[HandleLidSwitch]
HandleLidSwitch=ignore
HandleLidSwitchExternalPower=ignore
IdleAction=ignore

拆掉后 CanSuspend 立刻变成 yes,「使用电池时(T)」和「插入电源时(N)」两行就都回来了。

附带经验

  1. GNOME 面板的显隐常常由一个远程能力查询决定,它坏掉时 UI 会"静默少一项",不报任何错 → 排查要往 D-Bus 方法和 systemd 那边想,而不是盯着 GNOME。
  2. systemd-inhibit 是个 blunt instrument —— 它挡的不只是挂起动作本身,还会让"能不能挂起"这个查询结果变味。用来做"防休眠"其实不如直接改 gsettings。
  3. 系统日志是金矿:这次真正定位靠的是 journalctl + 面板源码 + strings 二进制里的 "CanSuspend" 字符串,而不是猜。
  4. 顺带修掉的自伤:把 GNOME 自己的锁屏当成"故障",写了个"自愈看门狗"去重启 gdm3,结果把自己登出 4 次。GNOME 锁屏时 GDM 的 greeter 是不运行的,那本来是正常状态。
  5. 另一个坑:"自动熄屏"(Blank Screen) 不是 "自动挂起"(Automatic Suspend),两个中文翻译长得很像,我一开始就看错了,排查方向整个跑偏。

← 回首页