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
42 changes: 42 additions & 0 deletions cpugpuram-viewer@lyk4s5/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# CPU/GPU/RAM Viewer

A minimalist, text-based system resource monitor for the Cinnamon Desktop Environment.

This applet displays real-time usage percentages for CPU, GPU, and RAM directly on your panel. It is designed to be lightweight, unobtrusive, and dynamic.

## Features

* **Minimalist Design:** Text-only display (e.g., `CPU %15 • GPU %30 • RAM %40`).
* **Dynamic Resizing:** The applet grows or shrinks based on the text length (no fixed width).
* **Auto-Detect GPU:**
* **NVIDIA:** Uses `nvidia-smi` automatically if installed.
* **AMD:** Reads directly from system files (`/sys/class/drm/...`).
* **Intel/No GPU:** Automatically hides the GPU section if no dedicated GPU is detected.
* **One-Click Monitor:** Left-clicking the applet opens `gnome-system-monitor` directly to the **Resources** tab.
* **Native Behavior:** Fully supports right-click context menus and panel edit mode (drag & drop).

## Requirements

* **Cinnamon Desktop Environment**
* `gnome-system-monitor` (For the click action)
* `nvidia-smi` (Optional: Only required for NVIDIA GPU monitoring)

## Installation

1. Download or clone this repository.
2. Copy the folder to your local Cinnamon applets directory:
```bash
cp -r cpugpuram-viewer@lyk4s5 ~/.local/share/cinnamon/applets/
```
3. Right-click on your Cinnamon panel -> **Applets**.
4. Find **CPU/GPU/RAM Viewer** in the *Manage* tab and add it to your panel.

## Usage

* **Left Click:** Opens System Monitor (Resources tab).
* **Right Click:** Opens the standard applet context menu (About, Remove, etc.).

## License

1. Distributed under the MIT License.
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the README, there's inconsistent indentation and formatting in the numbered list. Line 41 uses a tab character for indentation while other list items use consistent spacing. This should be standardized to use consistent indentation (either spaces or tabs, but not mixed).

Suggested change
1. Distributed under the MIT License.
1. Distributed under the MIT License.

Copilot uses AI. Check for mistakes.

157 changes: 157 additions & 0 deletions cpugpuram-viewer@lyk4s5/files/cpugpuram-viewer@lyk4s5/applet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
const Applet = imports.ui.applet;
const GLib = imports.gi.GLib;
const Util = imports.misc.util;
const Mainloop = imports.mainloop;

class CinnamonApplet extends Applet.TextApplet {
constructor(metadata, orientation, panel_height, instance_id) {
super(orientation, panel_height, instance_id);


Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's trailing whitespace on this empty line that should be removed for code cleanliness.

Suggested change

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The metadata parameter is passed to the constructor but never used. While it's part of the standard Cinnamon applet signature, consider either using it (e.g., for setting the icon path like in rssdock@lyk4s5) or documenting why it's not needed. If the applet grows and needs to access metadata.path or other properties, they won't be available without storing the metadata parameter.

Suggested change
// Store metadata for potential future use (e.g., icon paths, configuration).
this._metadata = metadata;

Copilot uses AI. Check for mistakes.
this.setAllowedLayout(Applet.AllowedLayout.BOTH);


this.set_applet_label("...");
this.set_applet_tooltip("Click to open System Monitor.");


this.prevIdle = 0;
this.prevTotal = 0;


this.gpuProvider = this.detectGpuProvider();


Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's trailing whitespace on this empty line that should be removed for code cleanliness.

Suggested change

Copilot uses AI. Check for mistakes.
this.updateLoop();
}


on_applet_clicked(event) {
Util.spawnCommandLine("gnome-system-monitor -r");
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The applet launches gnome-system-monitor with the -r flag, but this flag may not be recognized by all versions of gnome-system-monitor. Some versions may not support this flag or it might have been deprecated. Consider checking if the program exists before launching it, and handle potential errors gracefully. Look at cpu-monitor-text@gnemonix/files/cpu-monitor-text@gnemonix/applet.js:105-116 for a more robust approach using Cinnamon.AppSystem to launch the system monitor.

Copilot uses AI. Check for mistakes.
}

detectGpuProvider() {
// 1. NVIDIA
let nvidiaPath = GLib.find_program_in_path("nvidia-smi");
if (nvidiaPath) {
return "nvidia";
}

// 2. AMD
if (GLib.file_test("/sys/class/drm/card0/device/gpu_busy_percent", GLib.FileTest.EXISTS)) {
return "amd_card0";
}
if (GLib.file_test("/sys/class/drm/card1/device/gpu_busy_percent", GLib.FileTest.EXISTS)) {
return "amd_card1";
}

return null;
}

updateLoop() {
this.updateMetrics();
// 2 saniyede bir güncelle
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is in Turkish ("2 saniyede bir güncelle" means "update every 2 seconds"). All code comments should be in English for consistency with the rest of the codebase and to ensure maintainability for international contributors.

Suggested change
// 2 saniyede bir güncelle
// Update every 2 seconds

Copilot uses AI. Check for mistakes.
Mainloop.timeout_add_seconds(2, () => {
this.updateLoop();
});
Comment on lines +51 to +56
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The updateLoop recursively calls itself, creating a new function context each time. While this works, it's less efficient than using a repeating timeout pattern. Consider changing line 54 to return true at the end of updateMetrics(), and modify updateLoop to use: this._updateLoopID = Mainloop.timeout_add_seconds(2, Lang.bind(this, this.updateMetrics)); This approach is more memory-efficient and is the pattern used in other applets like cpu-monitor-text@gnemonix.

Copilot uses AI. Check for mistakes.
}
Comment on lines +51 to +57
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The updateLoop method creates an infinite recursion pattern that doesn't properly manage the timeout ID. This can lead to memory leaks because there's no way to clean up the timeout when the applet is removed from the panel. The timeout ID should be stored (e.g., this._updateLoopID) and removed in an on_applet_removed_from_panel callback using Mainloop.source_remove(). Look at cpu-monitor-text@gnemonix/files/cpu-monitor-text@gnemonix/applet.js:64-66 for an example of proper cleanup.

Copilot uses AI. Check for mistakes.

updateMetrics() {
try {
let parts = [];

// 1. CPU
let cpuUsage = this.getCpuUsage();
parts.push(`CPU %${cpuUsage}`);

// 2. GPU (if there)
if (this.gpuProvider) {
let gpuUsage = this.getGpuUsage();
if (gpuUsage !== null) {
parts.push(`GPU %${gpuUsage}`);
}
}

// 3. RAM
let ramUsage = this.getRamUsage();
parts.push(`RAM %${ramUsage}`);


this.set_applet_label(parts.join(" • "));

} catch (e) {
global.logError(e);
this.set_applet_label("Hata");
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message label "Hata" is in Turkish. Error messages should be in English for consistency with the rest of the codebase and to ensure accessibility for international users. Consider changing this to "Error" or using a more descriptive error message.

Suggested change
this.set_applet_label("Hata");
this.set_applet_label("Error");

Copilot uses AI. Check for mistakes.
}
}

getGpuUsage() {
try {
if (this.gpuProvider === "nvidia") {
let [res, out] = GLib.spawn_command_line_sync("nvidia-smi --query-gpu=utilization.gpu --format=csv,noheader,nounits");
if (res) return out.toString().trim();
}
Comment on lines +88 to +93
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getGpuUsage method spawns a synchronous command (nvidia-smi) which can block the UI thread if the command takes time to execute. While this is typically fast, consider that on some systems or under heavy load, this could cause UI stuttering. The current implementation is acceptable for a 2-second update interval, but if users want faster updates, this could become problematic. Consider documenting this limitation or using async command execution if performance becomes an issue.

Copilot uses AI. Check for mistakes.
else if (this.gpuProvider.startsWith("amd")) {
let cardPath = this.gpuProvider === "amd_card0" ?
"/sys/class/drm/card0/device/gpu_busy_percent" :
"/sys/class/drm/card1/device/gpu_busy_percent";

let [res, out] = GLib.file_get_contents(cardPath);
if (res) return out.toString().trim();
}
} catch (e) { return null; }
return null;
}

getCpuUsage() {
try {
let [res, fileContent] = GLib.file_get_contents('/proc/stat');
if (!res) return 0;

let lines = fileContent.toString().split('\n');
let cpuLine = lines[0].split(/\s+/);

let idle = parseInt(cpuLine[4]) + parseInt(cpuLine[5]);
let total = 0;
for (let i = 1; i < cpuLine.length; i++) {
let val = parseInt(cpuLine[i]);
if (!isNaN(val)) total += val;
}

let diffIdle = idle - this.prevIdle;
let diffTotal = total - this.prevTotal;
let usage = 0;

if (diffTotal > 0) {
usage = Math.round(100 * (diffTotal - diffIdle) / diffTotal);
}

this.prevIdle = idle;
this.prevTotal = total;

return usage;
} catch(e) { return 0; }
Comment on lines +106 to +133
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getCpuUsage function will return 0 on the first call because prevIdle and prevTotal are initialized to 0. When diffTotal is calculated on the first run, it will be non-zero, but the usage calculation will be based on the delta from 0, which may give an inaccurate initial reading. Consider either initializing these values properly in the constructor by reading /proc/stat once, or handling the first call specially to avoid showing misleading data.

Copilot uses AI. Check for mistakes.
}

getRamUsage() {
try {
let [res, fileContent] = GLib.file_get_contents('/proc/meminfo');
if (!res) return 0;

let contentStr = fileContent.toString();
let totalMatch = contentStr.match(/MemTotal:\s+(\d+)/);
let availableMatch = contentStr.match(/MemAvailable:\s+(\d+)/);

if (totalMatch && availableMatch) {
let total = parseInt(totalMatch[1]);
let available = parseInt(availableMatch[1]);
return Math.round(((total - available) / total) * 100);
}
} catch(e) { return 0; }
return 0;
}
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing cleanup callback for applet removal. The applet should implement on_applet_removed_from_panel to properly clean up the update loop timeout and prevent memory leaks when the applet is removed from the panel. This is a standard pattern in Cinnamon applets as seen in cpu-monitor-text@gnemonix/files/cpu-monitor-text@gnemonix/applet.js:63-68.

Suggested change
}
}
on_applet_removed_from_panel() {
if (this._updateLoopTimeoutId) {
Mainloop.source_remove(this._updateLoopTimeoutId);
this._updateLoopTimeoutId = null;
}
}

Copilot uses AI. Check for mistakes.
}

function main(metadata, orientation, panel_height, instance_id) {
return new CinnamonApplet(metadata, orientation, panel_height, instance_id);
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"uuid": "cpugpuram-viewer@lyk4s5",
"name": "CPU/GPU/RAM Viewer",
"description": "Minimalist system monitor for CPU, GPU, and RAM usage.",
"version": "1.0"
}
Comment on lines +5 to +6
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The metadata.json file is missing several standard fields that are present in other applets in this repository. At minimum, it should include a "description" field. Other applets typically also include fields like "max-instances" (for applets that support multiple instances) and "icon" (though this is optional if icon.png exists). See cpu-monitor-text@gnemonix/files/cpu-monitor-text@gnemonix/metadata.json as an example.

Suggested change
"version": "1.0"
}
"version": "1.0",
"max-instances": -1,
"icon": "icon"
}

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's trailing whitespace at the end of this line. This should be removed for code cleanliness and consistency.

Suggested change
}
}

Copilot uses AI. Check for mistakes.
5 changes: 5 additions & 0 deletions cpugpuram-viewer@lyk4s5/info.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file starts with an unnecessary blank line. This should be removed for consistency with the format seen in other applet info.json files in the repository.

Suggested change

Copilot uses AI. Check for mistakes.
{
"author": "lyk4s5",
"name": "CPU/GPU/RAM Viewer"
}
Binary file added cpugpuram-viewer@lyk4s5/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 8 additions & 2 deletions rssdock@lyk4s5/files/rssdock@lyk4s5/applet.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ MyApplet.prototype = {
this._tickerLoop();
this._setWidth();

// refresh every X seconds
this._refreshLoop = Mainloop.timeout_add_seconds(this.update_interval * 60, Lang.bind(this, this._updateFeed));


Comment on lines +42 to +43
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The removal of the initial refresh loop setup in _init can cause a problem if _updateFeed encounters an error or is slow to complete. The refresh loop is now only set up at the end of _updateFeed (line 136), but if _updateFeed is called from _init (line 38) and takes a long time or fails partway through, there will be no scheduled refresh until it completes successfully. Consider keeping the initial setup or ensuring that _updateFeed always schedules the next refresh even on errors.

Copilot uses AI. Check for mistakes.
},

_setupSettings: function () {
Expand Down Expand Up @@ -80,11 +80,13 @@ MyApplet.prototype = {

_updateFeed: function () {
// Collect news from all sources
if (this._refreshLoop) Mainloop.source_remove(this._refreshLoop);
this._allNews = [];
let sources = this.news_sources || [];
if (!sources.length) {
this._tickerText = _("No RSS source configured");
this._buildMenu();
this._refreshLoop = Mainloop.timeout_add_seconds(60, Lang.bind(this, this._updateFeed));
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hardcoded 60-second refresh interval for the "no sources configured" case is inconsistent with the dynamic interval calculation at line 135. If a user has set a custom update_interval (e.g., 5 minutes), and then removes all sources, the applet will suddenly switch to checking every minute. This could be confusing behavior. Consider using the same interval calculation here: Math.max(1, this.update_interval) * 60, or at least using this.update_interval * 60 if it's guaranteed to be >= 1.

Suggested change
this._refreshLoop = Mainloop.timeout_add_seconds(60, Lang.bind(this, this._updateFeed));
this._refreshLoop = Mainloop.timeout_add_seconds(Math.max(1, this.update_interval) * 60, Lang.bind(this, this._updateFeed));

Copilot uses AI. Check for mistakes.
return;
}

Expand Down Expand Up @@ -114,6 +116,7 @@ MyApplet.prototype = {
link: link,
date: dateObj,
source: source.label || "RSS"

Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's trailing whitespace on this line that should be removed for code cleanliness.

Suggested change

Copilot uses AI. Check for mistakes.
});
});
} catch (e) {
Expand All @@ -128,6 +131,9 @@ MyApplet.prototype = {
}
});
});
Comment on lines 81 to 133
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When _updateFeed is called asynchronously (e.g., when settings change via bindProperty or from a timeout callback), multiple instances of the async fetch operations could overlap. The code now removes the previous timeout at the start (line 83), but if _updateFeed is called before the previous async operations complete, there could be multiple concurrent fetch operations running. This could lead to race conditions where the _allNews array is overwritten or _buildMenu is called multiple times. Consider adding a flag to prevent concurrent updates or ensuring only one update can run at a time.

Copilot uses AI. Check for mistakes.
// refresh every X seconds
let interval = Math.max(1, this.update_interval) * 60;
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's inconsistent indentation here. The line uses spaces for indentation while the rest of the code appears to use consistent spacing. This should match the indentation style of the surrounding code.

Suggested change
let interval = Math.max(1, this.update_interval) * 60;
let interval = Math.max(1, this.update_interval) * 60;

Copilot uses AI. Check for mistakes.
Comment on lines +134 to +135
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Math.max(1, this.update_interval) ensures a minimum interval of 1 minute, which is good defensive programming. However, the original code at line 42-43 that was removed didn't have this protection. Consider documenting this change in the commit message or adding a comment explaining why the minimum is necessary to prevent excessive API calls or resource usage.

Suggested change
// refresh every X seconds
let interval = Math.max(1, this.update_interval) * 60;
// refresh every X seconds; enforce a minimum of 1 minute between updates
// to avoid excessive polling / API calls even if update_interval is misconfigured
let interval = Math.max(1, this.update_interval) * 60;

Copilot uses AI. Check for mistakes.
this._refreshLoop = Mainloop.timeout_add_seconds(interval, Lang.bind(this, this._updateFeed));
Comment on lines 83 to +136
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR is titled "CPU/GPU/RAM Viewer new applet" and described as adding "A minimalist and high-performance applet to monitor CPU, GPU, and RAM usage". However, this PR also includes unrelated changes to the rssdock@lyk4s5 applet (RSS feed refresh loop modifications). These changes should either be in a separate PR or the PR description should mention both changes. This makes it harder to review and understand the scope of changes.

Copilot uses AI. Check for mistakes.
Comment on lines 83 to +136
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The added code removes any existing refresh loop at the start of _updateFeed. However, this removal happens before checking if there are any news sources configured. If _updateFeed is called when there are no sources, it schedules a new refresh loop (line 89) but the old one was already removed (line 83). This is correct behavior, but consider that if _updateFeed fails or throws an exception before line 136, there will be no refresh loop scheduled. Add error handling to ensure the refresh loop is always rescheduled.

Copilot uses AI. Check for mistakes.
},

_buildMenu: function () {
Expand Down