Skip to content

Commit 5e4b317

Browse files
temperature@india - Updating the Thermometer Desklet (#1765)
* Updating the Thermometer Desklet * fixing issues * schema set to traditional style and fixed scaling of desklet
1 parent 903d1f8 commit 5e4b317

6 files changed

Lines changed: 269 additions & 907 deletions

File tree

temperature@india/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11

22
## Thermometer
33

4+
> [!IMPORTANT]
5+
> This Desklet uses the lm-sensors package, please install it to use this desklet
6+
47
A simple desklet to show the temperature like traditional thermometer.
58

69
pictures are taken from pixabay.com, Visual concept is taken from battery desklet by schorschii, Any suggestion is welcome.

temperature@india/files/temperature@india/desklet.js

Lines changed: 201 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -2,141 +2,216 @@ const Desklet = imports.ui.desklet;
22
const St = imports.gi.St;
33
const GLib = imports.gi.GLib;
44
const Util = imports.misc.util;
5-
const Cinnamon = imports.gi.Cinnamon;
65
const Lang = imports.lang;
76
const Mainloop = imports.mainloop;
8-
const Main = imports.ui.main;
97
const Clutter = imports.gi.Clutter;
10-
const GdkPixbuf = imports.gi.GdkPixbuf;
11-
const Cogl = imports.gi.Cogl;
12-
const Gio = imports.gi.Gio;
13-
14-
const DESKLET_ROOT = imports.ui.deskletManager.deskletMeta["temperature@india"].path;
8+
const Cairo = imports.gi.cairo;
9+
const Settings = imports.ui.settings;
1510

11+
const UUID = "temperature@india";
1612

1713
function MyDesklet(metadata, desklet_id) {
18-
this._init(metadata, desklet_id);
19-
}
20-
21-
function main(metadata, desklet_id) {
22-
return new MyDesklet(metadata, desklet_id);
23-
}
24-
25-
function getImageAtScale(imageFileName, width, height, width2 = 0, height2 = 0) {
26-
if (width2 == 0 || height2 == 0) {
27-
width2 = width;
28-
height2 = height;
29-
}
30-
31-
let pixBuf = GdkPixbuf.Pixbuf.new_from_file_at_size(imageFileName, width, height);
32-
let image = new Clutter.Image();
33-
image.set_data(
34-
pixBuf.get_pixels(),
35-
pixBuf.get_has_alpha() ? Cogl.PixelFormat.RGBA_8888 : Cogl.PixelFormat.RGBA_888,
36-
width, height,
37-
pixBuf.get_rowstride()
38-
);
39-
40-
let actor = new Clutter.Actor({width: width2, height: height2});
41-
actor.set_content(image);
42-
43-
return actor;
14+
this._init(metadata, desklet_id);
4415
}
4516

46-
47-
const Thermal = function() {
48-
this._init.apply(this, arguments);
49-
};
50-
51-
Thermal.prototype = {
52-
_init: function(sensorFile) {
53-
this.file = sensorFile;
54-
this.degrees = 0;
55-
this.info = "N/A";
56-
57-
},
58-
59-
refresh: function() {
60-
if(GLib.file_test(this.file, 1<<4)){
61-
let t_str = Cinnamon.get_file_contents_utf8_sync(this.file).split("\n")[0];
62-
this.degrees = parseInt(t_str) / 1000;
63-
this.info = this.degrees + "°C";
17+
MyDesklet.prototype = {
18+
__proto__: Desklet.Desklet.prototype,
19+
20+
_init: function(metadata, desklet_id) {
21+
Desklet.Desklet.prototype._init.call(this, metadata);
22+
23+
this.settings = new Settings.DeskletSettings(this, this.metadata["uuid"], desklet_id);
24+
this.settings.bindProperty(Settings.BindingDirection.IN, "text-color", "text_color", this.on_setting_changed, null);
25+
this.settings.bindProperty(Settings.BindingDirection.IN, "text-size", "text_size", this.on_setting_changed, null);
26+
this.settings.bindProperty(Settings.BindingDirection.IN, "scale-size", "scale_size", this.on_setting_changed, null);
27+
this.settings.bindProperty(Settings.BindingDirection.IN, "update-interval", "update_interval", this.on_setting_changed, null);
28+
29+
this.sensorsData = [];
30+
this.sensorsPath = '/usr/bin/sensors';
31+
32+
let desklet_path = GLib.get_user_data_dir() + "/cinnamon/desklets/" + this.metadata["uuid"];
33+
this.img_path = desklet_path + "/img/thermometer.svg";
34+
this.mercury_img_path = desklet_path + "/img/mercury.svg";
35+
36+
this.mainContainer = new St.Widget({
37+
style: `background-image: url('${this.img_path}');
38+
background-size: 50px 500px;
39+
background-repeat: no-repeat;
40+
background-position: 0px 0px;
41+
background-color: transparent;`,
42+
layout_manager: new Clutter.BinLayout()
43+
});
44+
45+
// Create the Mercury Widget
46+
this.mercuryWidget = new St.Widget({
47+
style: `background-image: url('${this.mercury_img_path}');
48+
background-size: 3px 500px;
49+
background-position: 0px 0px;
50+
background-repeat: no-repeat;`
51+
});
52+
53+
this.setContent(this.mainContainer);
54+
55+
Util.spawn_async(['which', 'sensors'], (ret) => {
56+
if (ret && ret.toString().trim() !== "") {
57+
this.sensorsPath = ret.toString().split('\n', 1)[0].trim();
58+
this.updateTemperature();
59+
}
60+
});
61+
},
62+
63+
on_setting_changed: function() {
64+
this.updateTemperature();
65+
},
66+
67+
updateTemperature: function() {
68+
if (this.sensorsPath) {
69+
Util.spawn_async([this.sensorsPath], (output) => {
70+
if (output) {
71+
this._parseAllSensors(output.toString());
72+
}
73+
});
74+
}
75+
76+
if (this.timeoutId) Mainloop.source_remove(this.timeoutId);
77+
this.timeoutId = Mainloop.timeout_add_seconds(
78+
this.update_interval || 2,
79+
() => this.updateTemperature()
80+
);
81+
},
82+
83+
_parseAllSensors: function(text) {
84+
let regex = /([^:\n]+):\s+\+?(\d+\.\d+)°C/g;
85+
let match;
86+
let newData = [];
87+
while ((match = regex.exec(text)) !== null) {
88+
let label = match[1].trim();
89+
let icon = "🌡️";
90+
if (label.toLowerCase().includes("core")) icon = "⚡";
91+
else if (label.toLowerCase().includes("gpu")) icon = "🔥";
92+
93+
newData.push({
94+
label: label,
95+
icon: icon,
96+
temp: Math.round(parseFloat(match[2]))
97+
});
98+
}
99+
this.sensorsData = newData;
100+
this.refreshUI();
101+
},
102+
103+
refreshUI: function() {
104+
this.mainContainer.destroy_all_children();
105+
106+
let scale = this.scale_size || 1.0;
107+
let maxHeight = 400 * scale;
108+
let width = 250 * scale;
109+
let fontSize = this.text_size || 14;
110+
let fontColor = this.text_color || 'white';
111+
let upperOffset = 30;
112+
let startY = 30;
113+
let scaleX = 50 * scale;
114+
115+
this.mainContainer.set_size(width, maxHeight+100);
116+
117+
// Draw Scale Markings (0 to 100)
118+
for (let i = 0; i <= 100; i += 10) {
119+
let yPos = upperOffset + (maxHeight * (100 - i)/100);
120+
let markLabel = new St.Label({
121+
text: i.toString(),
122+
style: `font-size: ${10 * scale}px; color: rgba(0,0,0,1); font-weight: bold;`
123+
});
124+
this.mainContainer.add_actor(markLabel);
125+
markLabel.set_position(30, yPos);
126+
}
127+
128+
// Draw Sensor Pointers
129+
for (let sensor of this.sensorsData) {
130+
let row = new St.BoxLayout({ vertical: false, style: "align-items: center;" });
131+
let percentage = Math.min(Math.max(sensor.temp, 0), 100) / 100;
132+
let yPos = startY + maxHeight*(1-percentage);
133+
134+
let arrowCanvas = new Clutter.Canvas();
135+
arrowCanvas.set_size(30 * scale, 15 * scale);
136+
arrowCanvas.connect('draw', (canvas, cr, w, h) => {
137+
this._drawVibrantPointer(cr, w, h, sensor.temp, scale);
138+
});
139+
let arrowActor = new Clutter.Actor({ width: 30 * scale, height: 15 * scale });
140+
arrowActor.set_content(arrowCanvas);
141+
arrowCanvas.invalidate();
142+
143+
let contentLabel = new St.Label({
144+
text: `${sensor.icon} ${sensor.temp}° ${sensor.label}`,
145+
style: `font-size: ${fontSize}px; color: ${fontColor}; font-weight: bold;`
146+
});
147+
contentLabel.set_size(300,20);
148+
//arrowActor.set_size(30,15);
149+
row.add_actor(arrowActor);
150+
row.add_actor(contentLabel);
151+
152+
row.set_position(scaleX, yPos);
153+
this.mainContainer.add_actor(row);
64154
}
65-
else
66-
global.logError("error reading: " + this.file);
155+
// Calculate Tctl Mercury Logic
156+
let tctlSensor = this.sensorsData.find(s => s.label === "Tctl") || { temp: 0 };
157+
let mercuryPercentage = Math.min(Math.max(tctlSensor.temp, 0), 100) / 100;
158+
159+
let mercuryWidth = 3 * scale;
160+
//let mercuryX = 32 * scale; // Tweak this to move left/right into the tube
161+
let mercuryFillHeight = mercuryPercentage * maxHeight;
162+
let mercuryY = upperOffset + maxHeight - mercuryFillHeight;
163+
164+
let thermo_height = 500 * scale;
165+
let thermo_width = 50 * scale;
166+
this.mainContainer.set_size(thermo_width, thermo_height);
167+
this.mainContainer.set_style(`
168+
background-image: url('${this.img_path}');
169+
background-size: ${thermo_width}px ${thermo_height}px;
170+
background-repeat: no-repeat;
171+
background-position: bottom;
172+
`);
173+
174+
// Setup Mercury Widget
175+
this.mercuryWidget.set_size(mercuryWidth, mercuryFillHeight);
176+
this.mercuryWidget.set_position(23*scale, mercuryY);
177+
this.mercuryWidget.set_style(`
178+
background-image: url('${this.mercury_img_path}');
179+
background-size: ${mercuryWidth}px ${mercuryFillHeight}px;
180+
background-position: bottom;
181+
background-repeat: no-repeat;
182+
`);
183+
184+
// Insert at index 0 to ensure it is the BOTTOM layer
185+
this.mainContainer.insert_child_at_index(this.mercuryWidget, 0);
186+
187+
188+
},
189+
190+
_drawVibrantPointer: function(cr, width, height, temp, scale) {
191+
cr.save();
192+
cr.setOperator(Cairo.Operator.CLEAR);
193+
cr.paint();
194+
cr.restore();
195+
196+
let h = height;
197+
if (temp < 45) cr.setSourceRGBA(0, 0.8, 1, 1);
198+
else if (temp < 75) cr.setSourceRGBA(1, 0.8, 0, 1);
199+
else cr.setSourceRGBA(1, 0.1, 0, 1);
200+
201+
cr.setLineWidth(2 * scale);
202+
cr.moveTo(10 * scale, h/2 - 7 * scale);
203+
cr.lineTo(0, h/2);
204+
cr.lineTo(10 * scale, h/2 + 7 * scale);
205+
cr.stroke();
206+
207+
cr.setSourceRGBA(1, 1, 1, 0.4);
208+
cr.setLineWidth(1 * scale);
209+
cr.moveTo(0, h/2);
210+
cr.lineTo(width, h/2);
211+
cr.stroke();
67212
}
68-
}
69-
70-
71-
MyDesklet.prototype = {
72-
__proto__: Desklet.Desklet.prototype,
73-
74-
_init: function(metadata, desklet_id) {
75-
Desklet.Desklet.prototype._init.call(this, metadata);
76-
this.thermal = new Thermal("/sys/devices/virtual/thermal/thermal_zone0/temp");
77-
this.setupUI();
78-
},
79-
80-
setupUI: function() {
81-
82-
// load images and set initial sizes
83-
this.refreshDesklet(true);
84-
85-
// set root element
86-
this.setContent(this.battery);
87-
this.setHeader("Thermometer");
88-
// start update cycle
89-
this.update();
90-
},
91-
92-
update: function() {
93-
this.thermal.refresh();
94-
this.refreshDesklet();
95-
// update again in two seconds
96-
this.timeout = Mainloop.timeout_add_seconds(1, Lang.bind(this, this.update));
97-
},
98-
99-
refreshDesklet: function(forceRefresh = false) {
100-
101-
// calc new sizes based on scale factor
102-
let batteryWidth = 50;
103-
let batteryHeight = 500;
104-
let segmentHeight = 360;
105-
let segmentWidth = 3;
106-
let segmentHeightCalc = segmentHeight * (20+parseInt(this.thermal.info))/120 ;
107-
108-
// set images
109-
let bar_img = "mercury.svg";
110-
this.bg_img = "thermometer.svg";
111-
112-
// create battery background
113-
this.battery = getImageAtScale(DESKLET_ROOT + "/img/" + this.bg_img, batteryWidth, batteryHeight);
114-
115-
// create segment = variable width bar (indicates capacity)
116-
this.segment = getImageAtScale(DESKLET_ROOT + "/img/" + bar_img, segmentWidth, segmentHeight, segmentWidth, segmentHeightCalc);
117-
this.segment.set_position(23,40+segmentHeight - segmentHeightCalc);
118-
119-
// container for subelements (icon, label)
120-
this.container = new St.Group();
121-
122-
123-
// label for percent string
124-
this.labelText = new St.Label({style_class:"text"});
125-
this.labelText.set_text(parseInt(this.thermal.info)+"°C");
126-
this.labelText.style = "font-size:10px;"
127-
this.labelText.set_position(10,510);
128-
129-
// add actor
130-
this.battery.remove_all_children();
131-
this.battery.add_actor(this.segment);
132-
this.battery.add_actor(this.labelText);
133-
this.setContent(this.battery);
134-
135-
136-
},
137-
213+
};
138214

139-
on_desklet_removed: function() {
140-
Mainloop.source_remove(this.timeout);
141-
}
215+
function main(metadata, desklet_id) {
216+
return new MyDesklet(metadata, desklet_id);
142217
}

0 commit comments

Comments
 (0)