Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions simple-battery@suckatcoding.com/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# 1.0.0
* Initial release
56 changes: 56 additions & 0 deletions simple-battery@suckatcoding.com/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Simple Battery Desklet for Cinnamon

A minimalist, highly customizable battery desklet for the Cinnamon desktop environment. It provides essential power metrics at a glance without cluttering your workspace.

![Screenshot placeholder](screenshot.png)

*(Note: Upload a screenshot of your desklet to your repo and replace this link!)*

## ✨ Features

* **Minimalist Design:** A sleek horizontal progress bar and compact text.
* **Real-Time Status:** Shows the exact battery percentage and time remaining (or time until fully charged).
* **Session Tracking:** Calculates how much battery percentage you have consumed (or charged) since you turned on the PC.
* **System Uptime:** Displays how long your current session has been running.
* **Customizable Appearance:** Turn the semi-transparent background on/off and adjust the opacity directly via Cinnamon's desklet settings.
* **Multi-Language Support (i18n):** English and German are included by default. Easily expandable to other languages.

## 📦 Installation

### Manual Installation via Git
1. Open your terminal.
2. Clone this repository directly into your Cinnamon desklets folder:
```bash
git clone [https://github.com/suckatcoding-com/simple-battery-desklet.git](https://github.com/suckatcoding-com/simple-battery-desklet.git) ~/.local/share/cinnamon/desklets/simple-battery@local
```
3. Restart Cinnamon so it detects the new desklet. Press Alt + F2, type r, and press Enter.
4. Right-click your desktop, select Add Desklets, go to the Manage tab, and add "Simple Battery" to your desktop.

## ⚙️ Configuration

You can easily configure the desklet to match your desktop theme:

1. Right-click the desklet on your desktop.
2. Select Configure (the gear icon).
3. Toggle the background visibility or adjust the background opacity using the slider. Changes are applied instantly.

## 🌍 Translations (i18n)

This desklet supports Gettext for translations. The base language of the code is English.

Want to add your language?

1. Create a new .po file in the main directory (e.g., fr.po for French) and translate the msgid strings.
2. Create the locale folder for your language:
```bash
mkdir -p locale/fr/LC_MESSAGES
```
3. Compile the .po file into a .mo file using msgfmt:
```bash
msgfmt po/fr.po -o locale/fr/LC_MESSAGES/simple-battery@suckatcoding.com.mo
```
4. Restart Cinnamon.
5. Feel free to open a Pull Request to share your translation with everyone!

## 📜 License
This project is licensed under the Apache 2.0 License - see the LICENSE file for details.
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
const Desklet = imports.ui.desklet;
const St = imports.gi.St;
const Mainloop = imports.mainloop;
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const ByteArray = imports.byteArray;
const Settings = imports.ui.settings;
const Gettext = imports.gettext;

const UUID = "simple-battery@suckatcoding.com";
const DESKLET_DIR = GLib.get_user_data_dir() + "/cinnamon/desklets/" + UUID;
Gettext.bindtextdomain(UUID, DESKLET_DIR + "/locale");

function _(str) {
return Gettext.dgettext(UUID, str);
}

function SimpleBatteryDesklet(metadata, desklet_id) {
this._init(metadata, desklet_id);
}

SimpleBatteryDesklet.prototype = {
__proto__: Desklet.Desklet.prototype,

_init: function(metadata, desklet_id) {
Desklet.Desklet.prototype._init.call(this, metadata, desklet_id);

this.settings = new Settings.DeskletSettings(this, this.metadata["uuid"], desklet_id);
this.settings.bindProperty(Settings.BindingDirection.IN, "show-background", "show_background", this.on_setting_changed, null);
this.settings.bindProperty(Settings.BindingDirection.IN, "bg-opacity", "bg_opacity", this.on_setting_changed, null);

this.setupUI();
this.update();
},

setupUI: function() {
this.container = new St.BoxLayout({vertical: true, style_class: "battery-desklet"});

this.barContainer = new St.BoxLayout({vertical: false});
this.track = new St.BoxLayout({style_class: "battery-track"});
this.fill = new St.BoxLayout({style_class: "battery-fill"});
this.track.add_actor(this.fill);
this.pctLabel = new St.Label({style_class: "battery-pct-small", text: "--%"});
this.barContainer.add_actor(this.track);
this.barContainer.add_actor(this.pctLabel);

this.timeLabel = new St.Label({style_class: "battery-time", text: "..."});

this.divider = new St.BoxLayout({style_class: "battery-divider"});
this.uptimeLabel = new St.Label({style_class: "battery-stats", text: "..."});
this.usageLabel = new St.Label({style_class: "battery-stats", text: "..."});

this.container.add_actor(this.barContainer);
this.container.add_actor(this.timeLabel);
this.container.add_actor(this.divider);
this.container.add_actor(this.uptimeLabel);
this.container.add_actor(this.usageLabel);

this.setContent(this.container);
this.on_setting_changed();
},

on_setting_changed: function() {
if (this.show_background) {
let alpha = this.bg_opacity / 100;
this.container.set_style("background-color: rgba(0, 0, 0, " + alpha + ");");
} else {
this.container.set_style("background-color: transparent;");
}
},

update: function() {
let hasBatteryData = false;
try {
let procBat = Gio.Subprocess.new(
['bash', '-c', 'upower -i $(upower -e | grep -i bat | head -n 1)'],
Gio.SubprocessFlags.STDOUT_PIPE
);

procBat.communicate_utf8_async(null, null, (proc, res) => {
try {
let [ok, outBat, stderr] = proc.communicate_utf8_finish(res);
if (ok) {
hasBatteryData = this.parseAndUpdate(outBat || "");
}
} catch (e) {
this.timeLabel.set_text(_("Error reading data"));
}
this.scheduleNextUpdate(hasBatteryData);
});

} catch (e) {
this.timeLabel.set_text(_("Error reading data"));
this.scheduleNextUpdate(false);
}
},

scheduleNextUpdate: function(hasBatteryData) {
if (this.timeout) Mainloop.source_remove(this.timeout);
let nextUpdate = hasBatteryData ? 60 : 5;
this.timeout = Mainloop.timeout_add_seconds(nextUpdate, () => {
this.update();
return false;
});
},

parseAndUpdate: function(output) {
let matchPct = output.match(/percentage:\s+(\d+)%/);
let matchEmpty = output.match(/time to empty:\s+(.*)/);
let matchFull = output.match(/time to full:\s+(.*)/);
let matchState = output.match(/state:\s+(.*)/);

let pctNum = 0;
let hasBatteryData = false;

if (matchPct) {
pctNum = parseInt(matchPct[1]);
hasBatteryData = true;
this.pctLabel.set_text(matchPct[1] + "%");
let fillWidth = Math.round(150 * (pctNum / 100));
this.fill.set_style("width: " + fillWidth + "px;");

let state = matchState ? matchState[1] : "";
if (state === "discharging" && matchEmpty) {
this.timeLabel.set_text(matchEmpty[1] + " " + _("remaining"));
} else if (state === "charging" && matchFull) {
this.timeLabel.set_text(matchFull[1] + " " + _("until full"));
} else if (state === "fully-charged") {
this.timeLabel.set_text(_("Fully charged"));
} else {
this.timeLabel.set_text(_("AC Power"));
}
} else {
this.pctLabel.set_text("--%");
this.fill.set_style("width: 0px;");
this.timeLabel.set_text(_("Connecting..."));
}

if (hasBatteryData) {
let tmpPath = "/tmp/desklet_start_bat_" + UUID + ".txt";
let tmpFile = Gio.File.new_for_path(tmpPath);

tmpFile.load_contents_async(null, (file, res) => {
try {
let [success, contents] = file.load_contents_finish(res);
if (success) {
let contentStr = ByteArray.toString(contents).trim();
let startPct = contentStr !== "" ? parseInt(contentStr) : pctNum;
this.updateUsageUI(startPct, pctNum);
}
} catch (e) {
try {
GLib.file_set_contents(tmpPath, pctNum.toString());
} catch (err) {}
this.updateUsageUI(pctNum, pctNum);
}
});
} else {
this.usageLabel.set_text(_("Usage:") + " --");
}

let uptimeFile = Gio.File.new_for_path("/proc/uptime");
uptimeFile.load_contents_async(null, (file, res) => {
try {
let [success, contents] = file.load_contents_finish(res);
if (success) {
let outUptime = ByteArray.toString(contents);
let uptimeSeconds = parseFloat(outUptime.split(" ")[0]);
let hours = Math.floor(uptimeSeconds / 3600);
let minutes = Math.floor((uptimeSeconds % 3600) / 60);

if (hours > 0) {
this.uptimeLabel.set_text(_("Uptime:") + " " + hours + _("h") + " " + minutes + _("min"));
} else {
this.uptimeLabel.set_text(_("Uptime:") + " " + minutes + _("min"));
}
}
} catch (e) {}
});

return hasBatteryData;
},

updateUsageUI: function(startPct, currentPct) {
let diff = startPct - currentPct;
if (diff > 0) {
this.usageLabel.set_text(_("Usage:") + " " + diff + "% " + _("since start"));
} else if (diff < 0) {
this.usageLabel.set_text(_("Charged:") + " " + Math.abs(diff) + "% " + _("since start"));
} else {
this.usageLabel.set_text(_("Usage:") + " 0% " + _("since start"));
}
},

on_desklet_removed: function() {
if (this.timeout) {
Mainloop.source_remove(this.timeout);
}
}
};

function main(metadata, desklet_id) {
return new SimpleBatteryDesklet(metadata, desklet_id);
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"uuid": "simple-battery@suckatcoding.com",
"name": "Simple Battery",
"description": "Displays battery status, uptime, and usage in a sleek, minimalist progress bar.",
"prevent-decorations": true,
"version": "1.0.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# SIMPLE BATTERY
# This file is put in the public domain.
# Nick 'Milchreis' Müller, 2026
#
msgid ""
msgstr ""
"Project-Id-Version: simple-battery@suckatcoding.com 1.0.0\n"
"Report-Msgid-Bugs-To: https://github.com/linuxmint/cinnamon-spices-desklets/"
"issues\n"
"POT-Creation-Date: 2026-06-14 10:54+0200\n"
"PO-Revision-Date: 2026-06-14 11:00+0200\n"
"Last-Translator: Nick 'Milchreis' Müller\n"
"Language-Team: German\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

#. desklet.js:86 desklet.js:91
msgid "Error reading data"
msgstr "Fehler beim Auslesen"

#. desklet.js:115
msgid "remaining"
msgstr "verbleibend"

#. desklet.js:117
msgid "until full"
msgstr "bis voll"

#. desklet.js:119
msgid "Fully charged"
msgstr "Vollständig geladen"

#. desklet.js:121
msgid "AC Power"
msgstr "Netzbetrieb"

#. desklet.js:161 desklet.js:163
msgid "Uptime:"
msgstr "Laufzeit:"

#. desklet.js:161
msgid "h"
msgstr "Std"

#. desklet.js:161 desklet.js:163
msgid "min"
msgstr "Min"

#. desklet.js:174 desklet.js:178
msgid "Usage:"
msgstr "Verbrauch:"

#. desklet.js:174 desklet.js:176 desklet.js:178
msgid "since start"
msgstr "seit Start"

#. desklet.js:176
msgid "Charged:"
msgstr "Geladen:"

#. metadata.json->name
msgid "Simple Battery"
msgstr "Schlichter Akku"

#. metadata.json->description
msgid ""
"Displays battery status, uptime, and usage in a sleek, minimalist progress "
"bar."
msgstr ""
"Zeigt den Akkustand, die Laufzeit und den Verbrauch in einem schlichten, "
"minimalistischen Fortschrittsbalken an."

#. settings-schema.json->show-background->description
msgid "Show background"
msgstr "Hintergrund anzeigen"

#. settings-schema.json->bg-opacity->description
msgid "Background opacity (%)"
msgstr "Hintergrund-Deckkraft (%)"
Loading
Loading