Writing a GNOME Extension
By Prithvi Vishak
Created June 16th, 2021
Unfortunately, there is very little documentation regarding how to write extensions for the GNOME desktop environment. The GNOME libraries seem to be fairly well documented, but it can be exhausting to read through it all. Often, people, including me when I wrote my first extension, just got code snippets from other peoples' extensions. This article aims to document how I built mine.
What It Should Do
The extension is called MacBook Battery Percentage Corrector. It exists because MacBooks (Intel-based ones, at least) have a custom SMC, or System Management Controller. According to Wikipedia, it is supposed to be an equivalent to a PMU, or Power Management Unit, on regular PC laptops.
PMUs report a number of different metrics.
~$ upower -i /org/freedesktop/UPower/devices/battery_BAT1
native-path: BAT1
vendor: सेब Inc.
model: A1A4492
serial: 2345
power supply: yes
updated: Thu 17 Jun 2021 01:48:50 PM IST (32 seconds ago)
has history: yes
has statistics: yes
battery
present: yes
rechargeable: yes
state: fully-charged
warning-level: none
energy: 56.66 Wh
energy-empty: 0 Wh
energy-full: 57.33 Wh
energy-full-design: 71.1 Wh
energy-rate: 0.00591272 W
voltage: 12.728 V
percentage: 98%
capacity: 80.6329%
technology: lithium-ion
icon-name: 'battery-full-charged-symbolic'
Your desktop gets these metrics through a power subsystem such as UPower and displays it on your desktop. The thing with Apple's SMC, though, is that the metrics it reports don't seem to follow the convention that PC laptop PMUs do.
On MacBooks, Linux seems to read battery percentage as the percentage of capacity by design (energy-full-design
above). Batteries degrade, or lose some capacity, with every charge-discharge cycle.
This means that the more a battery is used, the smaller its maximum capacity becomes. Normally, operating systems consider battery degradation while reporting percentage.
That doesn't seem to be the case with Linux on a MacBook.
Long story short, as indicated by their motto, Apple has a pathological need to do their own nonsense and not follow standards. That's probably the reason Linux considers the wrong values while reporting battery percentage.
How It Should Work
These battery metrics can be found in the directory /sys/class/power_supply/BAT0
. We need to do the following:
- Read the
charge_now
andcharge_full
files - Calculate percentage from these numbers
- Replace the indicator in the top-right corner with what we calculated
Code
A good place to start would be GNOME's official extension guide. Extensions follow certain guidelines. Other useful documentation is listed on the world's most useful website. Here's a look at my code:
const Panel = imports.ui.main.panel;
const { GObject } = imports.gi;
const GLib = imports.gi.GLib;
const BaseIndicator = imports.ui.status.power.Indicator;
let aggregateMenu = Panel.statusArea['aggregateMenu'];
let origIndicator;
let newIndicator;
const BatPath = "/sys/class/power_supply/BAT0"
function init() {}
function enable() {
origIndicator = aggregateMenu._power;
newIndicator = new Indicator();
aggregateMenu._indicators.replace_child(origIndicator, newIndicator);
}
function disable() {
aggregateMenu._indicators.replace_child(newIndicator, origIndicator);
}
function readFile(filepath) {
return String(GLib.file_get_contents(filepath)[1]).replace("\n", "");
}
var Indicator = GObject.registerClass(
class Indicator extends BaseIndicator {
_getCorrectPercentage() {
let charge_full = parseInt(readFile(BatPath + "/charge_full"));
let charge_now = parseInt(readFile(BatPath + "/charge_now"));
let percentage = Math.trunc((charge_now/charge_full) * 100);
return percentage + "%";
}
_sync() {
super._sync();
this._percentageLabel.text = this._getCorrectPercentage();
}
}
);
All right. Let's go through this real quick. At the top there are some imports and global variables. GLib
and GObject
are some GNOME libraries. Everything else is pretty self-explanatory.
function init()
is a function required by GNOME in extension.js
. My extension doesn't need anything to be done when GNOME Shell starts, so I've left it empty.
At the end of the code, we see a class definition. It inherits from GNOME's battery power indicator base class. The function readFile
, well, reads the content of a file and returns it as a string.
The _getCorrectPercentage
function reads the battery's current charge and full charge values from the files mentioned above, and returns the percentage formatted as a string.
The _sync
function runs every time battery power status is updated. It sets the text of the percentage label next to the battery icon to the value _getCorrectPercentage
calculates. This overwrites the incorrect value.
enable
runs when the extension is enabled. origIndicator = aggregateMenu._power;
stores the default indicator in a variable, so that it can be restored if the extension is disabled. It then creates a new instance of our custom battery status indicator, and puts that in the old one's place.
disable
runs when, well, the extension is disabled. It simply replaces our custom indicator with the original we stored away.