文章

动手修复Auto Screen Brightness

动手修复Auto Screen Brightness

背景

Auto Screen Brightness是一个比较小众的GNOME扩展,可以在电源线接入与否时自动调节屏幕亮度。这个扩展对我来说相当重要:由于在学校生活,夜间会断电,外出时也经常没有可以接入的电源。自动降低亮度的功能可以节省电量,延长电池续航时间。从认识这个插件以来我都依靠它自动调节亮度,极大提升了GNOME的使用体验。

GNOME 49于2025年9月发布。从这以后,这个插件就无法启动了。这是GNOME 49对API修改导致的。10月之后,由于没有找到合适的替代品,我都手动调整亮度,失去了自动调节的便利性。确实有别的用户也发现了这个问题(https://github.com/popov895/auto-screen-brightness/issues/6),然而作者没有回复,也没有别人进行修复。于是,我决定自己动手进行修复。这次开发将大大加深我对GNOME,DBus等技术的理解。

1
2
3
4
5
6
7
8
Extension auto-screen-brightness@popov895.ukr.net: TypeError: Invalid type [object Null]                                        
   Stack trace:
     _newNodeInfo@resource:///org/gnome/gjs/modules/core/overrides/Gio.js:280:11
     _wrapFunction/klass[method]@resource:///org/gnome/gjs/modules/core/overrides/Gio.js:312:25
     _newInterfaceInfo@resource:///org/gnome/gjs/modules/core/overrides/Gio.js:284:37
     _makeProxyWrapper@resource:///org/gnome/gjs/modules/core/overrides/Gio.js:235:33
     @file:///home/davidx/.local/share/gnome-shell/extensions/auto-screen-brightness@popov895.ukr.net/extension.js:10:39
     @resource:///org/gnome/shell/ui/init.js:21:20

问题介绍

从错误堆栈可以看出,问题出现在 Gio.DBusProxy.makeProxyWrapper() 调用时,传入了 [object Null]。旧版代码使用了 imports.misc.fileUtilsloadInterfaceXML() 函数来加载 D-Bus 接口定义,但这个函数在 GNOME 49 中已经被移除。

代码分析

旧版实现的问题

旧版代码存在以下几个问题:

  1. 使用了已废弃的 loadInterfaceXML():这个函数在 GNOME 49 中不再可用,导致返回 null
  2. 使用旧的导入语法imports.giExtensionUtils 等已被新的 ES6 模块系统取代
  3. 依赖 BrightnessProxy:旧版直接操作 org.gnome.SettingsDaemon.Power.Screen D-Bus 接口,这种方式不够稳定

新版的改进

新版代码进行了以下重要改进:

  1. 使用 ES6 模块语法
    1
    2
    
    import Gio from "gi://Gio";
    import { Extension } from "resource:///org/gnome/shell/extensions/extension.js";
    
  2. 内联 D-Bus 接口定义
    1
    2
    3
    4
    5
    6
    
    const PowerManagerInterface = `
    <node>
      <interface name="org.freedesktop.UPower">
        <property name="OnBattery" type="b" access="read"/>
      </interface>
    </node>`;
    

    直接在代码中定义接口,不再依赖外部加载函数

  3. 使用 Main.brightnessManager
    1
    2
    3
    4
    5
    
    const brightnessManager = Main.brightnessManager;
    const scales = brightnessManager.scales;
    for (const scale of scales) {
        scale.value = targetBrightness;
    }
    

    这是 GNOME Shell 的官方 API,更加稳定可靠

  4. 移除了 BrightnessProxy:不再需要单独监听亮度变化,简化了代码逻辑

修复过程

1. 更新模块导入系统

首先将旧的 imports 语法替换为 ES6 的 import

1
2
3
4
5
6
7
// 旧版
const { Gio } = imports.gi;
const ExtensionUtils = imports.misc.extensionUtils;

// 新版
import Gio from "gi://Gio";
import { Extension } from "resource:///org/gnome/shell/extensions/extension.js";

2. 内联 D-Bus 接口定义

移除对 loadInterfaceXML() 的依赖,直接在代码中定义:

1
2
3
4
5
6
7
8
const PowerManagerInterface = `
<node>
  <interface name="org.freedesktop.UPower">
    <property name="OnBattery" type="b" access="read"/>
  </interface>
</node>`;

const PowerManagerProxy = Gio.DBusProxy.makeProxyWrapper(PowerManagerInterface);

3. 改用官方 BrightnessManager API

这是最关键的修改。旧版通过 D-Bus 直接操作 org.gnome.SettingsDaemon.Power.Screen,新版改用 GNOME Shell 内置的 Main.brightnessManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
_updateScreenBrightness() {
    if (this._powerManagerProxy.OnBattery === null) {
        return;
    }

    const brightnessManager = Main.brightnessManager;
    if (!brightnessManager) {
        logError("BrightnessManager not available");
        return;
    }

    // 将 0-100 转换为 0.0-1.0
    const targetBrightness = this._powerManagerProxy.OnBattery
        ? this._preferences.brightnessOnBattery / 100.0
        : this._preferences.brightnessOnAc / 100.0;

    // 为所有显示器设置亮度
    const scales = brightnessManager.scales;
    for (const scale of scales) {
        scale.value = targetBrightness;
    }
}

4. 简化扩展类结构

新版使用标准的 ES6 类继承:

1
2
3
4
export default class AutoScreenBrightnessNGExtension extends Extension {
    enable() { ... }
    disable() { ... }
}

这样就不再需要 init() 函数返回实例了。

GNOME扩展安装的坑点

正常来说GNOME扩展可以很轻松的安装:

  1. 安装GNOME Shell集成浏览器插件
  2. 安装本地连接器,连接器将浏览器和GNOME Shell连接起来。在Arch Linux上这是gnome-browser-connector包,在Ubuntu上是chrome-gnome-shell
  3. GNOME Shell Extensions上查找并安装扩展

当然,也可以用命令行安装:

1
gnome-extensions install <扩展包路径>

然而,第二种方法的致命问题是安装后使用gnome-extensions enable <扩展名>启用扩展时会提示扩展不存在。如果之前有这个扩展且安装时使用--force覆盖安装,enable再disable后还是之前的那个版本而不是新的版本。这是因为使用浏览器安装时会自动重新加载所有扩展,然而使用命令行安装时不会,这也导致调试时经常出现扩展没有按预期工作然而代码实际上没有问题的情况。每次使用命令行安装后需要注销并重新登录才能让GNOME Shell运行新版的扩展。

总结

通过这次修复,我学习并理解了:

  1. GNOME Shell 扩展的现代化开发:从旧的 imports 系统迁移到 ES6 模块
  2. D-Bus 通信:如何正确定义和使用 D-Bus 接口
  3. GNOME Shell 内部 APIMain.brightnessManager 的使用方法
  4. 向下兼容性处理:如何应对 API 变更

修复后的扩展已经可以在 GNOME 49+ 上正常工作。

项目地址:auto-screen-brightness

本文由作者按照 CC BY 4.0 进行授权