Skip to content

Commit e85577b

Browse files
committed
feature: Implemented Aquacomputer Next Vision coolant temperature reading as well as custom icons for the water temp and aio pump
1 parent f183191 commit e85577b

4 files changed

Lines changed: 149 additions & 3 deletions

File tree

extension.js

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ var VitalsMenuButton = GObject.registerClass({
3030
this._settings = extensionObject.getSettings();
3131

3232
this._sensorIcons = {
33-
'temperature' : { 'icon': 'temperature-symbolic.svg' },
33+
'temperature' : { 'icon': 'temperature-symbolic.svg', 'icon-c': 'water-droplet-symbolic.svg' },
3434
'voltage' : { 'icon': 'voltage-symbolic.svg' },
35-
'fan' : { 'icon': 'fan-symbolic.svg' },
35+
'fan' : { 'icon': 'fan-symbolic.svg', 'icon-pump': 'pump-symbolic.svg' },
3636
'memory' : { 'icon': 'memory-symbolic.svg' },
3737
'processor' : { 'icon': 'cpu-symbolic.svg' },
3838
'system' : { 'icon': 'system-symbolic.svg' },
@@ -255,6 +255,7 @@ var VitalsMenuButton = GObject.registerClass({
255255
}
256256

257257
_createHotItem(key, value) {
258+
this._lastHotSensorKey = key;
258259
let icon = this._defaultIcon(key);
259260
this._hotIcons[key] = icon;
260261
this._menuLayout.add_child(icon)
@@ -439,6 +440,9 @@ var VitalsMenuButton = GObject.registerClass({
439440
let split = sensor.type.split('-');
440441
let type = split[0];
441442
let icon = (split.length == 2)?'icon-' + split[1]:'icon';
443+
// Custom: Use special icon for AIO Pump or Coolant Temp
444+
if (type === 'fan' && key.includes('aio')) { icon = 'icon-pump' }
445+
if (type === 'temperature' && key.includes('coolant')) { icon = 'icon-c' }
442446
let gicon = Gio.icon_new_for_string(this._sensorIconPath(type, icon));
443447

444448
let item = new MenuItem.MenuItem(gicon, key, sensor.label, sensor.value, this._hotLabels[key]);
@@ -520,6 +524,10 @@ var VitalsMenuButton = GObject.registerClass({
520524
// If the sensor is a numbered gpu, use the gpu icon. Otherwise use whatever icon associated with the sensor name.
521525
let sensorKey = sensor;
522526
if(sensor.startsWith('gpu')) sensorKey = 'gpu';
527+
// Custom: Use water droplet for coolant temp
528+
// The key for your sensor will be like "_temperature_ac_vision_coolant_temp_"
529+
if (sensorKey === 'temperature' && icon === 'icon' && this._lastHotSensorKey && this._lastHotSensorKey.toLowerCase().includes('coolant')) { icon = 'icon-c'}
530+
if (sensorKey === 'fan' && icon === 'icon' && this._lastHotSensorKey && this._lastHotSensorKey.toLowerCase().includes('aio')) { icon = 'icon-pump'}
523531

524532
const iconPathPrefixIndex = this._settings.get_int('icon-style');
525533
return this._extensionObject.path + this._sensorsIconPathPrefix[iconPathPrefixIndex] + this._sensorIcons[sensorKey][icon];
@@ -646,6 +654,24 @@ var VitalsMenuButton = GObject.registerClass({
646654

647655
super.destroy();
648656
}
657+
658+
_getMenuGroupIconName(type) {
659+
if (type === 'fan') {
660+
for (let key in this._sensorMenuItems) {
661+
if (key.startsWith('_fan_') && key.includes('aio')) {
662+
return 'icon-pump';
663+
}
664+
}
665+
}
666+
if (type === 'temperature') {
667+
for (let key in this._sensorMenuItems) {
668+
if (key.startsWith('_temperature_') && key.includes('coolant')) {
669+
return 'icon-c';
670+
}
671+
}
672+
}
673+
return 'icon';
674+
}
649675
});
650676

651677
export default class VitalsExtension extends Extension {

icons/gnome/pump-symbolic.svg

Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 4 additions & 0 deletions
Loading

sensors.js

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
*/
2626

2727
import GObject from 'gi://GObject';
28+
import Gio from 'gi://Gio';
29+
import GLib from 'gi://GLib';
2830
import * as SubProcessModule from './helpers/subprocess.js';
2931
import * as FileModule from './helpers/file.js';
3032
import { gettext as _ } from 'resource:///org/gnome/shell/extensions/extension.js';
@@ -120,6 +122,12 @@ export const Sensors = GObject.registerClass({
120122
for (let label in this._tempVoltFanSensors[type]) {
121123
let sensor = this._tempVoltFanSensors[type][label];
122124

125+
// Special handling for Aquacomputer Vision
126+
if (sensor.aquacomputer) {
127+
this._readAquacomputerSensor(callback, label, sensor, type);
128+
continue;
129+
}
130+
123131
new FileModule.File(sensor['path']).read().then(value => {
124132
this._returnValue(callback, label, value, type, sensor['format']);
125133
}).catch(err => {
@@ -128,6 +136,48 @@ export const Sensors = GObject.registerClass({
128136
}
129137
}
130138

139+
_readAquacomputerSensor(callback, label, sensor, type) {
140+
try {
141+
let file = Gio.File.new_for_path(sensor['path']);
142+
file.read_async(GLib.PRIORITY_DEFAULT, null, (obj, res) => {
143+
try {
144+
let stream = obj.read_finish(res);
145+
let input = new Gio.DataInputStream({ base_stream: stream });
146+
147+
input.read_bytes_async(
148+
64,
149+
GLib.PRIORITY_DEFAULT,
150+
null,
151+
(input, res2) => {
152+
try {
153+
let bytes = input.read_bytes_finish(res2);
154+
let arr = new Uint8Array(bytes.get_data());
155+
156+
if (arr.length < 64 || arr[0] !== 0x01) {
157+
this._returnValue(callback, label, 'disabled', type, sensor['format']);
158+
return;
159+
}
160+
161+
// Read coolant temperature
162+
const offset = 0x0037 + 1;
163+
let raw = arr[offset] | (arr[offset + 1] << 8);
164+
let temp = (raw / 100) * 1000; // millidegrees
165+
166+
this._returnValue(callback, label, temp, type, sensor['format']);
167+
} catch (e) {
168+
this._returnValue(callback, label, 'disabled', type, sensor['format']);
169+
}
170+
}
171+
);
172+
} catch (e) {
173+
this._returnValue(callback, label, 'disabled', type, sensor['format']);
174+
}
175+
});
176+
} catch (e) {
177+
this._returnValue(callback, label, 'disabled', type, sensor['format']);
178+
}
179+
}
180+
131181
_queryMemory(callback) {
132182
// check memory info
133183
new FileModule.File('/proc/meminfo').read().then(lines => {
@@ -801,6 +851,62 @@ export const Sensors = GObject.registerClass({
801851
// Launch nvidia-smi subprocess if nvidia querying is enabled
802852
this._reconfigureNvidiaSmiProcess();
803853
this._discoverGpuDrm();
854+
855+
// Aquacomputer Next Vision
856+
const VENDOR_ID = '0c70';
857+
const PRODUCT_ID = 'f00c';
858+
859+
let base = '/sys/class/hidraw/';
860+
let dir = Gio.File.new_for_path(base);
861+
try {
862+
let enumerator = dir.enumerate_children(
863+
'standard::*',
864+
Gio.FileQueryInfoFlags.NONE,
865+
null
866+
);
867+
let info;
868+
while ((info = enumerator.next_file(null)) !== null) {
869+
let name = info.get_name(); // e.g. hidraw7
870+
let deviceSymlink = `${base}${name}/device`;
871+
let realDevicePath = GLib.file_read_link(deviceSymlink);
872+
// realDevicePath will be something like:
873+
// ../../devices/.../0003:0C70:F00C.0008
874+
let parts = realDevicePath.split('/');
875+
let devDir = parts[parts.length - 1];
876+
// devDir is like "0003:0C70:F00C.0008"
877+
let match = devDir.match(
878+
/^[0-9A-Fa-f]+:([0-9A-Fa-f]{4}):([0-9A-Fa-f]{4})\./
879+
);
880+
if (match) {
881+
let vid = match[1].toLowerCase();
882+
let pid = match[2].toLowerCase();
883+
if (vid === VENDOR_ID && pid === PRODUCT_ID) {
884+
// Check if AC Vision sensor already exists to prevent re-adding
885+
if (!('Coolant Temp' in this._tempVoltFanSensors['temperature'])) {
886+
this._addTempVoltFan(
887+
null, // Don't call callback during discovery
888+
{
889+
type: 'temperature',
890+
format: 'temp',
891+
input: `/dev/${name}`,
892+
aquacomputer: true,
893+
},
894+
'AC Vision',
895+
'Coolant Temp',
896+
'',
897+
0 // placeholder value, will be read during query
898+
);
899+
}
900+
break;
901+
}
902+
} else {
903+
}
904+
}
905+
enumerator.close(null);
906+
} catch (e) {
907+
// If the hidraw device is not available, we just ignore it.
908+
// This can happen if the device is not connected or if the user does not have permission to access it.
909+
}
804910
}
805911

806912
_discoverGpuDrm() {
@@ -955,6 +1061,12 @@ export const Sensors = GObject.registerClass({
9551061
if (label == 'iwlwifi_1 temp1') label = 'Wireless Adapter';
9561062
if (label == 'Package id 0') label = 'Processor 0';
9571063
if (label == 'Package id 1') label = 'Processor 1';
1064+
if (label == 'nct6799 fan1') label = 'VRM HeatSink Fan';
1065+
if (label == 'nct6799 fan2') label = 'Radiator Fan(s)';
1066+
if (label == 'nct6799 fan6') label = 'Chipset/NVMe Fan';
1067+
if (label == 'nct6799 fan7') label = 'AIO Pump';
1068+
if (label == 'nct6799 SYSTIN') label = 'Motherboard Temp';
1069+
if (label == 'nct6799 CPUTIN') label = 'CPU Socket Temp';
9581070
label = label.replace('Package id', 'CPU');
9591071

9601072
let types = [ 'temperature', 'voltage', 'fan' ];
@@ -974,12 +1086,15 @@ export const Sensors = GObject.registerClass({
9741086
}
9751087
}
9761088

1089+
if (!obj['aquacomputer'] && callback) {
9771090
// update screen on initial build to prevent delay on update
9781091
this._returnValue(callback, label, value, obj['type'], obj['format']);
1092+
}
9791093

9801094
this._tempVoltFanSensors[obj['type']][label] = {
9811095
'format': obj['format'],
982-
'path': obj['input']
1096+
'path': obj['input'],
1097+
'aquacomputer': obj['aquacomputer'] || false,
9831098
};
9841099
}
9851100

0 commit comments

Comments
 (0)