diff --git a/extension.js b/extension.js
index 6f14df7..2e5e594 100644
--- a/extension.js
+++ b/extension.js
@@ -30,9 +30,9 @@ var VitalsMenuButton = GObject.registerClass({
this._settings = extensionObject.getSettings();
this._sensorIcons = {
- 'temperature' : { 'icon': 'temperature-symbolic.svg' },
+ 'temperature' : { 'icon': 'temperature-symbolic.svg', 'icon-c': 'water-droplet-symbolic.svg' },
'voltage' : { 'icon': 'voltage-symbolic.svg' },
- 'fan' : { 'icon': 'fan-symbolic.svg' },
+ 'fan' : { 'icon': 'fan-symbolic.svg', 'icon-pump': 'pump-symbolic.svg' },
'memory' : { 'icon': 'memory-symbolic.svg' },
'processor' : { 'icon': 'cpu-symbolic.svg' },
'system' : { 'icon': 'system-symbolic.svg' },
@@ -255,6 +255,7 @@ var VitalsMenuButton = GObject.registerClass({
}
_createHotItem(key, value) {
+ this._lastHotSensorKey = key;
let icon = this._defaultIcon(key);
this._hotIcons[key] = icon;
this._menuLayout.add_child(icon)
@@ -439,6 +440,9 @@ var VitalsMenuButton = GObject.registerClass({
let split = sensor.type.split('-');
let type = split[0];
let icon = (split.length == 2)?'icon-' + split[1]:'icon';
+ // Custom: Use special icon for AIO Pump or Coolant Temp
+ if (type === 'fan' && key.includes('aio')) { icon = 'icon-pump' }
+ if (type === 'temperature' && key.includes('coolant')) { icon = 'icon-c' }
let gicon = Gio.icon_new_for_string(this._sensorIconPath(type, icon));
let item = new MenuItem.MenuItem(gicon, key, sensor.label, sensor.value, this._hotLabels[key]);
@@ -520,6 +524,10 @@ var VitalsMenuButton = GObject.registerClass({
// If the sensor is a numbered gpu, use the gpu icon. Otherwise use whatever icon associated with the sensor name.
let sensorKey = sensor;
if(sensor.startsWith('gpu')) sensorKey = 'gpu';
+ // Custom: Use water droplet for coolant temp
+ // The key for your sensor will be like "_temperature_ac_vision_coolant_temp_"
+ if (sensorKey === 'temperature' && icon === 'icon' && this._lastHotSensorKey && this._lastHotSensorKey.toLowerCase().includes('coolant')) { icon = 'icon-c'}
+ if (sensorKey === 'fan' && icon === 'icon' && this._lastHotSensorKey && this._lastHotSensorKey.toLowerCase().includes('aio')) { icon = 'icon-pump'}
const iconPathPrefixIndex = this._settings.get_int('icon-style');
return this._extensionObject.path + this._sensorsIconPathPrefix[iconPathPrefixIndex] + this._sensorIcons[sensorKey][icon];
@@ -646,6 +654,24 @@ var VitalsMenuButton = GObject.registerClass({
super.destroy();
}
+
+ _getMenuGroupIconName(type) {
+ if (type === 'fan') {
+ for (let key in this._sensorMenuItems) {
+ if (key.startsWith('_fan_') && key.includes('aio')) {
+ return 'icon-pump';
+ }
+ }
+ }
+ if (type === 'temperature') {
+ for (let key in this._sensorMenuItems) {
+ if (key.startsWith('_temperature_') && key.includes('coolant')) {
+ return 'icon-c';
+ }
+ }
+ }
+ return 'icon';
+ }
});
export default class VitalsExtension extends Extension {
diff --git a/icons/gnome/pump-symbolic.svg b/icons/gnome/pump-symbolic.svg
new file mode 100644
index 0000000..a64d0d1
--- /dev/null
+++ b/icons/gnome/pump-symbolic.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/gnome/water-droplet-symbolic.svg b/icons/gnome/water-droplet-symbolic.svg
new file mode 100644
index 0000000..1253383
--- /dev/null
+++ b/icons/gnome/water-droplet-symbolic.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/sensors.js b/sensors.js
index f310161..e40775e 100644
--- a/sensors.js
+++ b/sensors.js
@@ -25,6 +25,8 @@
*/
import GObject from 'gi://GObject';
+import Gio from 'gi://Gio';
+import GLib from 'gi://GLib';
import * as SubProcessModule from './helpers/subprocess.js';
import * as FileModule from './helpers/file.js';
import { gettext as _ } from 'resource:///org/gnome/shell/extensions/extension.js';
@@ -120,6 +122,12 @@ export const Sensors = GObject.registerClass({
for (let label in this._tempVoltFanSensors[type]) {
let sensor = this._tempVoltFanSensors[type][label];
+ // Special handling for Aquacomputer Vision
+ if (sensor.aquacomputer) {
+ this._readAquacomputerSensor(callback, label, sensor, type);
+ continue;
+ }
+
new FileModule.File(sensor['path']).read().then(value => {
this._returnValue(callback, label, value, type, sensor['format']);
}).catch(err => {
@@ -128,6 +136,48 @@ export const Sensors = GObject.registerClass({
}
}
+ _readAquacomputerSensor(callback, label, sensor, type) {
+ try {
+ let file = Gio.File.new_for_path(sensor['path']);
+ file.read_async(GLib.PRIORITY_DEFAULT, null, (obj, res) => {
+ try {
+ let stream = obj.read_finish(res);
+ let input = new Gio.DataInputStream({ base_stream: stream });
+
+ input.read_bytes_async(
+ 64,
+ GLib.PRIORITY_DEFAULT,
+ null,
+ (input, res2) => {
+ try {
+ let bytes = input.read_bytes_finish(res2);
+ let arr = new Uint8Array(bytes.get_data());
+
+ if (arr.length < 64 || arr[0] !== 0x01) {
+ this._returnValue(callback, label, 'disabled', type, sensor['format']);
+ return;
+ }
+
+ // Read coolant temperature - correct offset and encoding
+ const offset = 0x37; // Offset 55 (0x37) contains correct temperature data
+ let raw = (arr[offset] << 8) | arr[offset + 1]; // Big-endian encoding
+ let temp = (raw / 100) * 1000; // millidegrees
+
+ this._returnValue(callback, label, temp, type, sensor['format']);
+ } catch (e) {
+ this._returnValue(callback, label, 'disabled', type, sensor['format']);
+ }
+ }
+ );
+ } catch (e) {
+ this._returnValue(callback, label, 'disabled', type, sensor['format']);
+ }
+ });
+ } catch (e) {
+ this._returnValue(callback, label, 'disabled', type, sensor['format']);
+ }
+ }
+
_queryMemory(callback) {
// check memory info
new FileModule.File('/proc/meminfo').read().then(lines => {
@@ -801,6 +851,62 @@ export const Sensors = GObject.registerClass({
// Launch nvidia-smi subprocess if nvidia querying is enabled
this._reconfigureNvidiaSmiProcess();
this._discoverGpuDrm();
+
+ // Aquacomputer Next Vision
+ const VENDOR_ID = '0c70';
+ const PRODUCT_ID = 'f00c';
+
+ let base = '/sys/class/hidraw/';
+ let dir = Gio.File.new_for_path(base);
+ try {
+ let enumerator = dir.enumerate_children(
+ 'standard::*',
+ Gio.FileQueryInfoFlags.NONE,
+ null
+ );
+ let info;
+ while ((info = enumerator.next_file(null)) !== null) {
+ let name = info.get_name(); // e.g. hidraw7
+ let deviceSymlink = `${base}${name}/device`;
+ let realDevicePath = GLib.file_read_link(deviceSymlink);
+ // realDevicePath will be something like:
+ // ../../devices/.../0003:0C70:F00C.0008
+ let parts = realDevicePath.split('/');
+ let devDir = parts[parts.length - 1];
+ // devDir is like "0003:0C70:F00C.0008"
+ let match = devDir.match(
+ /^[0-9A-Fa-f]+:([0-9A-Fa-f]{4}):([0-9A-Fa-f]{4})\./
+ );
+ if (match) {
+ let vid = match[1].toLowerCase();
+ let pid = match[2].toLowerCase();
+ if (vid === VENDOR_ID && pid === PRODUCT_ID) {
+ // Check if AC Vision sensor already exists to prevent re-adding
+ if (!('Coolant Temp' in this._tempVoltFanSensors['temperature'])) {
+ this._addTempVoltFan(
+ null, // Don't call callback during discovery
+ {
+ type: 'temperature',
+ format: 'temp',
+ input: `/dev/${name}`,
+ aquacomputer: true,
+ },
+ 'AC Vision',
+ 'Coolant Temp',
+ '',
+ 0 // placeholder value, will be read during query
+ );
+ }
+ break;
+ }
+ } else {
+ }
+ }
+ enumerator.close(null);
+ } catch (e) {
+ // If the hidraw device is not available, we just ignore it.
+ // This can happen if the device is not connected or if the user does not have permission to access it.
+ }
}
_discoverGpuDrm() {
@@ -955,6 +1061,12 @@ export const Sensors = GObject.registerClass({
if (label == 'iwlwifi_1 temp1') label = 'Wireless Adapter';
if (label == 'Package id 0') label = 'Processor 0';
if (label == 'Package id 1') label = 'Processor 1';
+ if (label == 'nct6799 fan1') label = 'VRM HeatSink Fan';
+ if (label == 'nct6799 fan2') label = 'Radiator Fan(s)';
+ if (label == 'nct6799 fan6') label = 'Chipset/NVMe Fan';
+ if (label == 'nct6799 fan7') label = 'AIO Pump';
+ if (label == 'nct6799 SYSTIN') label = 'Motherboard Temp';
+ if (label == 'nct6799 CPUTIN') label = 'CPU Socket Temp';
label = label.replace('Package id', 'CPU');
let types = [ 'temperature', 'voltage', 'fan' ];
@@ -974,12 +1086,15 @@ export const Sensors = GObject.registerClass({
}
}
+ if (!obj['aquacomputer'] && callback) {
// update screen on initial build to prevent delay on update
this._returnValue(callback, label, value, obj['type'], obj['format']);
+ }
this._tempVoltFanSensors[obj['type']][label] = {
'format': obj['format'],
- 'path': obj['input']
+ 'path': obj['input'],
+ 'aquacomputer': obj['aquacomputer'] || false,
};
}