Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions extension.js
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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]);
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions icons/gnome/pump-symbolic.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions icons/gnome/water-droplet-symbolic.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
117 changes: 116 additions & 1 deletion sensors.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 => {
Expand All @@ -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 => {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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' ];
Expand All @@ -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,
};
}

Expand Down