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, }; }