Skip to content

Commit 199f77d

Browse files
systemUptime@KopfdesDaemons: Version 2.0.0 (#1738)
* use local date and time format * use St.Icon, refactor setup layout function * use file_get_contents, add more comments * use 24H clock cinnamon setting * simplify icon initialization * scale with system font size, change desklet entry point, cleanup settings, rename settings, update icon init * update icon size, add settings header * translation * version number * fix freezing of the settings window when reloading the desklet * async file reading * no unnecessary redrawing in settings callbacks * fix cut-of labels by loading values before layout rendering * split value fetching and label update logic * "show icon" setting * translation * update version order in `CHANGELOG.md`
1 parent fefdc02 commit 199f77d

15 files changed

Lines changed: 507 additions & 319 deletions

File tree

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Changelog
2+
3+
## [2.0.0] - 06.02.2026
4+
5+
- New features:
6+
- Scale desklet with system font size
7+
- Use the "Use 24H clock" cinnamon setting
8+
- Use local date and time format
9+
- Add "Show icon" setting
10+
- Updated screenshot
11+
- Refactoring:
12+
- Use `St.Icon` for the clock icon
13+
- Use `file_get_contents()` to get starttime and uptime
14+
- Added more comments
15+
- Updated icon initialization
16+
- Added settings cleanup
17+
- Changed desklet entry point to `on_desklet_added_to_desktop`
18+
- Renamed settings names
19+
- Split value fetching and label update logic
20+
- No unnecessary layout rerendering on style setting change
21+
22+
## [1.0.1] - 31.07.2025
23+
24+
- Update starttime in mainloop
25+
- Updated screenshot
26+
27+
## [1.0.0] - 26.11.2024
28+
29+
- Initial release

systemUptime@KopfDesDaemons/files/systemUptime@KopfDesDaemons/desklet.js

Lines changed: 175 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,174 +1,252 @@
11
const Desklet = imports.ui.desklet;
2-
const Lang = imports.lang;
32
const St = imports.gi.St;
43
const Mainloop = imports.mainloop;
54
const GLib = imports.gi.GLib;
65
const Settings = imports.ui.settings;
6+
const Gio = imports.gi.Gio;
77
const Gettext = imports.gettext;
8-
const Clutter = imports.gi.Clutter;
9-
const GdkPixbuf = imports.gi.GdkPixbuf;
10-
const Cogl = imports.gi.Cogl;
8+
const ByteArray = imports.byteArray;
119

1210
const UUID = "systemUptime@KopfDesDaemons";
1311

14-
Gettext.bindtextdomain(UUID, GLib.get_home_dir() + "/.local/share/locale");
12+
Gettext.bindtextdomain(UUID, GLib.get_user_data_dir() + "/locale");
1513

1614
function _(str) {
1715
return Gettext.dgettext(UUID, str);
1816
}
1917

18+
function getFileContents(path) {
19+
return new Promise((resolve, reject) => {
20+
const file = Gio.File.new_for_path(path);
21+
file.load_contents_async(null, (obj, res) => {
22+
try {
23+
const [success, contents] = obj.load_contents_finish(res);
24+
if (success) {
25+
resolve(contents);
26+
} else {
27+
reject(new Error(`Could not read ${path}`));
28+
}
29+
} catch (e) {
30+
reject(e);
31+
}
32+
});
33+
});
34+
}
35+
2036
class MyDesklet extends Desklet.Desklet {
2137
constructor(metadata, deskletId) {
2238
super(metadata, deskletId);
39+
this.setHeader(_("System Uptime"));
2340

24-
this.settings = new Settings.DeskletSettings(this, metadata["uuid"], deskletId);
41+
this._refreshTimeoutId = null;
42+
this._isReloading = false;
43+
this._startTimeLabel = null;
44+
this._startupValue = null;
45+
this._uptimeLabel = null;
46+
this._uptimeValue = null;
47+
this._clockIcon = null;
48+
this._iconBox = null;
49+
this._currentUptimeText = "";
50+
this._currentStartupText = "";
51+
52+
// Listen for changes in the system clock format (12h/24h)
53+
this._desktop_settings = new Gio.Settings({ schema_id: "org.cinnamon.desktop.interface" });
54+
this._clockSettingsId = this._desktop_settings.connect("changed::clock-use-24h", () => this._updateValues());
55+
56+
// Default settings values
57+
this.scaleSize = 1;
58+
this.labelColor = "rgb(51, 209, 122)";
59+
this.showStartDate = false;
60+
this.showUptimeInDays = false;
61+
this.hideDecorations = true;
62+
this.showIcon = true;
2563

2664
// Bind settings properties
27-
this.settings.bindProperty(Settings.BindingDirection.IN, "fontSize", "fontSize", this.onSettingsChanged.bind(this));
28-
this.settings.bindProperty(Settings.BindingDirection.IN, "colorLabel", "colorLabel", this.onSettingsChanged.bind(this));
29-
this.settings.bindProperty(Settings.BindingDirection.IN, "showStartDate", "showStartDate", this.onSettingsChanged.bind(this));
30-
this.settings.bindProperty(Settings.BindingDirection.IN, "showUptimeInDays", "showUptimeInDays", this.onSettingsChanged.bind(this));
31-
this.settings.bindProperty(Settings.BindingDirection.IN, "hideDecorations", "hideDecorations", this.updateDecoration.bind(this));
65+
this.settings = new Settings.DeskletSettings(this, metadata["uuid"], deskletId);
66+
this.settings.bindProperty(Settings.BindingDirection.IN, "scale-size", "scaleSize", this._setStyles);
67+
this.settings.bindProperty(Settings.BindingDirection.IN, "label-color", "labelColor", this._setStyles);
68+
this.settings.bindProperty(Settings.BindingDirection.IN, "show-start-date", "showStartDate", this._updateValues);
69+
this.settings.bindProperty(Settings.BindingDirection.IN, "show-uptime-in-days", "showUptimeInDays", this._updateValues);
70+
this.settings.bindProperty(Settings.BindingDirection.IN, "hide-decorations", "hideDecorations", this._onDecorationChanged);
71+
this.settings.bindProperty(Settings.BindingDirection.IN, "show-icon", "showIcon", this._onShowIconChanged);
72+
}
73+
74+
async on_desklet_added_to_desktop() {
75+
this._onDecorationChanged();
76+
await this._updateValues();
77+
this._setupLayout();
78+
this._setStyles();
79+
this._setRefreshTimeout();
80+
}
3281

33-
this.fontSize = this.settings.getValue("fontSize") || 20;
34-
this.colorLabel = this.settings.getValue("colorLabel") || "rgb(51, 209, 122)";
35-
this._timeout = null;
82+
on_desklet_removed() {
83+
if (this._refreshTimeoutId) {
84+
Mainloop.source_remove(this._refreshTimeoutId);
85+
this._refreshTimeoutId = null;
86+
}
87+
if (this._clockSettingsId) {
88+
this._desktop_settings.disconnect(this._clockSettingsId);
89+
this._clockSettingsId = 0;
90+
}
91+
if (this.settings && !this._isReloading) {
92+
this.settings.finalize();
93+
}
94+
}
3695

37-
this.setHeader(_("System Uptime"));
38-
this.updateDecoration();
39-
this.setupLayout();
40-
this.updateValues();
96+
on_desklet_reloaded() {
97+
this._isReloading = true;
4198
}
4299

43-
setupLayout() {
100+
_setupLayout() {
44101
// Create labels for uptime
45-
this.uptimeLabel = this.createLabel(_("Uptime:") + " ", this.colorLabel);
46-
this.uptimeValue = this.createLabel(_("Loading..."));
102+
this._uptimeLabel = new St.Label({ text: _("Uptime:") + " " });
103+
this._uptimeValue = new St.Label({ text: this._currentUptimeText });
47104

48-
const uptimeRow = this.createRow([this.uptimeLabel, this.uptimeValue]);
105+
const uptimeRow = new St.BoxLayout();
106+
uptimeRow.add_child(this._uptimeLabel);
107+
uptimeRow.add_child(this._uptimeValue);
49108

50109
// Create labels for startup time
51-
this.startTimeLabel = this.createLabel(_("Start time:") + " ", this.colorLabel);
52-
this.startupValue = this.createLabel(_("Loading..."));
110+
this._startTimeLabel = new St.Label({ text: _("Start time:") + " " });
111+
this._startupValue = new St.Label({ text: this._currentStartupText });
53112

54-
const startupRow = this.createRow([this.startTimeLabel, this.startupValue]);
113+
const startupRow = new St.BoxLayout();
114+
startupRow.add_child(this._startTimeLabel);
115+
startupRow.add_child(this._startupValue);
55116

56117
// Combine all into the main container
57-
const contentBox = new St.BoxLayout({ vertical: true });
58-
contentBox.set_style("margin-left: 0.5em;");
59-
contentBox.add_child(startupRow);
60-
contentBox.add_child(uptimeRow);
61-
62-
this.container = new St.BoxLayout();
63-
this.container.add_child(contentBox);
64-
65-
Mainloop.idle_add(() => {
66-
const computedHeight = contentBox.get_height();
118+
const labelBox = new St.BoxLayout({ vertical: true, y_align: St.Align.MIDDLE });
119+
labelBox.set_style("margin-left: 0.5em;");
120+
labelBox.add_child(startupRow);
121+
labelBox.add_child(uptimeRow);
122+
123+
const container = new St.BoxLayout({ y_align: St.Align.MIDDLE });
124+
125+
if (this.showIcon) {
126+
this._clockIcon = new St.Icon({
127+
gicon: Gio.icon_new_for_string(`${this.metadata.path}/icons/clock.svg`),
128+
icon_size: 5 * 16 * this.scaleSize,
129+
});
130+
this._iconBox = new St.Bin({ child: this._clockIcon });
131+
container.add_child(this._iconBox);
132+
}
67133

68-
const clockIcon = this.getImageAtScale(`${this.metadata.path}/clock.svg`, computedHeight, computedHeight);
134+
container.add_child(labelBox);
69135

70-
this.container.insert_child_below(clockIcon, contentBox);
71-
clockIcon.queue_relayout();
136+
this.setContent(container);
137+
}
72138

73-
return false;
74-
});
139+
_setStyles() {
140+
const valueStyle = `font-size: ${1.5 * this.scaleSize}em;`;
141+
const labelStyle = `${valueStyle} color: ${this.labelColor};`;
142+
const iconStyle = `width: ${3 * this.scaleSize}em; height: ${3 * this.scaleSize}em;`;
75143

76-
this.setContent(this.container);
77-
}
144+
// Uptime
145+
this._uptimeLabel.set_style(labelStyle);
146+
this._startTimeLabel.set_style(labelStyle);
78147

79-
createLabel(text, color = "inherit") {
80-
return new St.Label({
81-
text,
82-
y_align: St.Align.START,
83-
style: `font-size: ${this.fontSize}px; color: ${color};`,
84-
});
85-
}
148+
// Values
149+
this._uptimeValue.set_style(valueStyle);
150+
this._startupValue.set_style(valueStyle);
86151

87-
createRow(children) {
88-
const row = new St.BoxLayout();
89-
children.forEach(child => row.add_child(child));
90-
return row;
152+
// Icon
153+
this._clockIcon.set_style(iconStyle);
154+
this._iconBox.set_style(iconStyle);
91155
}
92156

93-
updateUptime() {
94-
let uptimeInSeconds = 0;
157+
async _fetchUptimeText() {
95158
try {
96-
const [result, out] = GLib.spawn_command_line_sync("awk '{print $1}' /proc/uptime");
97-
if (!result || !out) throw new Error("Could not get system uptime.");
98-
uptimeInSeconds = parseFloat(out.toString().trim());
159+
// Read uptime in seconds from /proc/uptime
160+
const contents = await getFileContents("/proc/uptime");
161+
const uptimeInSeconds = parseFloat(ByteArray.toString(contents).split(" ")[0]);
99162

100163
if (this.showUptimeInDays) {
164+
// Convert uptime to days, hours, and minutes
101165
const days = Math.floor(uptimeInSeconds / 86400);
102166
const hours = Math.floor((uptimeInSeconds % 86400) / 3600);
103167
const minutes = Math.floor(((uptimeInSeconds % 86400) % 3600) / 60);
104168

105-
this.uptimeValue.set_text(`${days} ${_("days")} ${hours} ${_("hrs")} ${minutes} ${_("min")}`);
169+
return `${days} ${_("days")} ${hours} ${_("hrs")} ${minutes} ${_("min")}`;
106170
} else {
171+
// Hours can be more than 24
107172
const hours = Math.floor(uptimeInSeconds / 3600);
108173
const minutes = Math.floor((uptimeInSeconds % 3600) / 60);
109174

110-
this.uptimeValue.set_text(`${hours} ${_("hours")} ${minutes} ${_("minutes")}`);
175+
return `${hours} ${_("hours")} ${minutes} ${_("minutes")}`;
111176
}
112177
} catch (error) {
113-
this.uptimeValue.set_text("Error");
114-
global.logError(`${UUID}: ${error.message}`);
178+
global.logError(`${UUID} Error: ${error.message}`);
179+
return _("Error");
115180
}
116181
}
117182

118-
getStartupTime() {
183+
async _fetchStartupTimeText() {
119184
try {
120-
const [result, out] = GLib.spawn_command_line_sync("uptime -s");
121-
if (!result || !out) throw new Error("Could not get system startup time.");
185+
// Read startup time from /proc/stat (btime)
186+
const contents = await getFileContents("/proc/stat");
187+
188+
// Search for the line starting with "btime " and parse the timestamp
189+
const lines = ByteArray.toString(contents).split("\n");
190+
let btime = 0;
191+
for (const line of lines) {
192+
if (line.startsWith("btime ")) {
193+
btime = parseInt(line.split(/\s+/)[1]);
194+
break;
195+
}
196+
}
197+
if (!btime) throw new Error("Could not get system startup time.");
122198

123-
const dateTime = out.toString().split(" ");
124-
const date = dateTime[0].split("-");
199+
const dateTime = GLib.DateTime.new_from_unix_local(btime);
200+
const use24h = this._desktop_settings.get_boolean("clock-use-24h");
201+
const timeFormat = use24h ? "%H:%M" : "%-l:%M %p";
125202

126203
if (this.showStartDate) {
127-
this.startupValue.set_text(date[2] + "." + date[1] + "." + date[0] + ", " + dateTime[1].trim());
204+
// Show date and time
205+
return dateTime.format("%x") + ", " + dateTime.format(timeFormat);
128206
} else {
129-
this.startupValue.set_text(dateTime[1].trim());
207+
// Show only time
208+
return dateTime.format(timeFormat);
130209
}
131210
} catch (error) {
132-
this.startupValue.set_text("Error");
133211
global.logError(`${UUID}: ${error.message}`);
212+
return _("Error");
213+
}
214+
}
215+
216+
async _updateUptime() {
217+
this._currentUptimeText = await this._fetchUptimeText();
218+
if (this._uptimeValue) {
219+
this._uptimeValue.set_text(this._currentUptimeText);
134220
}
135221
}
136222

137-
updateValues() {
138-
this.updateUptime();
139-
this.getStartupTime();
140-
if (this._timeout) Mainloop.source_remove(this._timeout);
141-
this._timeout = Mainloop.timeout_add_seconds(60, () => this.updateValues());
223+
async _updateStartupTime() {
224+
this._currentStartupText = await this._fetchStartupTimeText();
225+
if (this._startupValue) {
226+
this._startupValue.set_text(this._currentStartupText);
227+
}
142228
}
143229

144-
onSettingsChanged() {
145-
this.setupLayout();
146-
this.updateValues();
230+
async _updateValues() {
231+
await this._updateStartupTime();
232+
await this._updateUptime();
147233
}
148234

149-
updateDecoration() {
150-
this.metadata["prevent-decorations"] = this.hideDecorations;
151-
this._updateDecoration();
235+
_setRefreshTimeout() {
236+
this._refreshTimeoutId = Mainloop.timeout_add_seconds(60, () => {
237+
this._updateValues();
238+
return true;
239+
});
152240
}
153241

154-
on_desklet_removed() {
155-
if (this._timeout) Mainloop.source_remove(this._timeout);
242+
_onDecorationChanged() {
243+
this.metadata["prevent-decorations"] = this.hideDecorations;
244+
this._updateDecoration();
156245
}
157246

158-
getImageAtScale(imageFileName, width, height) {
159-
const pixBuf = GdkPixbuf.Pixbuf.new_from_file_at_size(imageFileName, width, height);
160-
const image = new Clutter.Image();
161-
image.set_data(
162-
pixBuf.get_pixels(),
163-
pixBuf.get_has_alpha() ? Cogl.PixelFormat.RGBA_8888 : Cogl.PixelFormat.RGBA_888,
164-
width,
165-
height,
166-
pixBuf.get_rowstride()
167-
);
168-
169-
const actor = new Clutter.Actor({ width, height });
170-
actor.set_content(image);
171-
return actor;
247+
_onShowIconChanged() {
248+
this._setupLayout();
249+
this._setStyles();
172250
}
173251
}
174252

systemUptime@KopfDesDaemons/files/systemUptime@KopfDesDaemons/clock.svg renamed to systemUptime@KopfDesDaemons/files/systemUptime@KopfDesDaemons/icons/clock.svg

File renamed without changes.

systemUptime@KopfDesDaemons/files/systemUptime@KopfDesDaemons/metadata.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"uuid": "systemUptime@KopfDesDaemons",
33
"name": "System Uptime",
44
"description": "Displays the current system uptime.",
5-
"version": "1.0.1",
5+
"version": "2.0.0",
66
"max-instances": "50",
77
"author": "KopfdesDaemons"
88
}

0 commit comments

Comments
 (0)