Skip to content

Add System Monitor Cinnamon applet#8301

Open
MainPoser wants to merge 8 commits intolinuxmint:masterfrom
MainPoser:master
Open

Add System Monitor Cinnamon applet#8301
MainPoser wants to merge 8 commits intolinuxmint:masterfrom
MainPoser:master

Conversation

@MainPoser
Copy link

This PR adds a new applet: System Monitor Cinnamon.

A small tool to display CPU usage, memory usage, and real-time network speed in the Cinnamon desktop panel.

Features:

  1. Real-time monitoring of CPU, RAM, and net speed.

  2. cat or horse run with cpu

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a new Cinnamon desktop applet called "System Monitor Cinnamon" that displays real-time system metrics (CPU usage, memory usage, and network speed) in the panel with animated icons. The applet is inspired by RunCat365 and features customizable cat or horse animation icons that run faster as CPU usage increases.

Changes:

  • Complete implementation of a system monitoring applet with CPU, RAM, and network speed tracking
  • Animated icon system with cat and horse runners that respond to CPU load
  • Comprehensive internationalization support with 50+ language translations
  • Settings interface for customizing refresh rate, network display, icon theme, and animation type

Reviewed changes

Copilot reviewed 52 out of 94 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
applet.js Main applet implementation with system monitoring logic and animation rendering
metadata.json Applet metadata defining UUID, name, version, and description
settings-schema.json Configuration schema for user-customizable options
info.json Author information for Cinnamon Spices
stylesheet.css Empty CSS file for potential styling
icon.png Applet icon displayed in the Cinnamon settings
screenshot.png Screenshot for applet showcase
po/* (50+ files) Translation files for internationalization support
icons/runners/* SVG animation frames for cat and horse icons in black and white variants
runners/* Duplicate icon directory (appears redundant)
README.md / README.en.md Documentation in Chinese and English

Comment on lines +190 to +191
totalDownload += parseInt(data[1]);
totalUpload += parseInt(data[9]);
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.

Potential integer overflow risk in network data parsing. The parseInt() calls on lines 190-191 could overflow for very large byte counts on systems with high network usage over extended uptime. Consider using parseInt with base 10 explicitly and potentially handling large numbers with BigInt for systems with high network throughput.

Copilot uses AI. Check for mistakes.
Comment on lines +69 to +85
_data_loop() {
this._stats.cpu = this._get_cpu_usage();
this._stats.mem = this._get_mem_usage();

// 5. 根据配置逻辑显示
let label = ` CPU: ${this._stats.cpu}% | RAM: ${this._stats.mem}%`;

if (this._settings.show_network) {
let net = this._get_net_speed();
label += ` | ${net}`;
}

this.set_applet_label(label);

// 使用绑定的变量 this._settings.refresh_interval
this._render.dataTimeoutId = Mainloop.timeout_add(this._settings.refresh_interval * 1000, () => this._data_loop());
return false;
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 timeout callbacks in _data_loop() and _anim_loop() return false, which prevents them from repeating automatically. However, new timeouts are manually scheduled at the end of each iteration. This creates a potential issue: if an exception occurs before reaching the timeout_add call, the loop will stop permanently. Consider using a try-catch block to ensure the loop continues even if errors occur, or return true to auto-repeat and only reschedule when settings change.

Copilot uses AI. Check for mistakes.
this._render.frame = (this._render.frame + 1) % 5;
this._settings.them = this._get_them();

let iconPath = `${this._render.path}/icons/runners/${this._settings.runner}/${this._settings.them}/${this._settings.runner}_${this._render.frame}.svg`;
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.

Duplicate icon files exist in two separate directory structures: files/.../icons/runners/ and files/.../runners/. This redundancy wastes storage space and creates maintenance overhead. The code references the 'icons/runners' path (line 101), so the duplicate 'runners' directory appears to be unnecessary and should be removed.

Copilot uses AI. Check for mistakes.
Comment on lines +44 to +45
├── metada.json # Applet metadata
├── setting-schema.json # Configuration options definition
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 README.en.md file contains the same typos as README.md. The file names "metada.json" should be "metadata.json" and "setting-schema.json" should be "settings-schema.json" to match the actual files in the project.

Suggested change
├── metada.json # Applet metadata
├── setting-schema.json # Configuration options definition
├── metadata.json # Applet metadata
├── settings-schema.json # Configuration options definition

Copilot uses AI. Check for mistakes.
Comment on lines +99 to +101
this._settings.them = this._get_them();

let iconPath = `${this._render.path}/icons/runners/${this._settings.runner}/${this._settings.them}/${this._settings.runner}_${this._render.frame}.svg`;
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 variable name "them" appears to be a typo. It should be "theme" to properly represent the icon theme color scheme (black/white). This typo occurs in line 99 where it's assigned and line 101 where it's used in the path construction.

Suggested change
this._settings.them = this._get_them();
let iconPath = `${this._render.path}/icons/runners/${this._settings.runner}/${this._settings.them}/${this._settings.runner}_${this._render.frame}.svg`;
this._settings.theme = this._get_them();
let iconPath = `${this._render.path}/icons/runners/${this._settings.runner}/${this._settings.theme}/${this._settings.runner}_${this._render.frame}.svg`;

Copilot uses AI. Check for mistakes.
/**
* 核心: 决定当前应该使用哪个文件夹
*/
_get_them() {
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 method name "_get_them()" contains a typo and should be "_get_theme()" for consistency with standard naming conventions and to accurately represent its purpose of determining the theme color.

Copilot uses AI. Check for mistakes.
Comment on lines +46 to +47
├── metada.json # 小部件元数据
├── setting-schema.json # 配置选项定义
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 README.md file references "metada.json" and "setting-schema.json" in the project structure section, but the actual files are named "metadata.json" and "settings-schema.json" (note the missing 't' in metadata and the plural 's' in settings). This documentation should be corrected to match the actual file names.

Suggested change
├── metada.json # 小部件元数据
├── setting-schema.json # 配置选项定义
├── metadata.json # 小部件元数据
├── settings-schema.json # 配置选项定义

Copilot uses AI. Check for mistakes.
安装后,小部件将自动在面板上显示系统监控信息。默认显示格式为:

```
CPU: 45% | MEM: 2.1G/8G | ↑ 1.2MB/s ↓ 500KB/s
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 network speed display format in the README shows "↑ 1.2MB/s ↓ 500KB/s" but the code (line 222) outputs "↓ ${down} KB/s ↑ ${up} KB/s" (download first, then upload). The order and unit case (MB vs KB) differ from the documentation. This should be clarified or made consistent.

Copilot uses AI. Check for mistakes.
Comment on lines +167 to +170
let content = GLib.file_get_contents("/proc/meminfo")[1].toString();
let total = parseInt(content.match(/MemTotal:\s+(\d+)/)[1]);
let available = parseInt(content.match(/MemAvailable:\s+(\d+)/)[1]);
return Math.round(((total - available) / total) * 100);
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.

Error handling in _get_mem_usage() is incomplete. If /proc/meminfo reading fails or the regex doesn't match, the function will throw an unhandled exception and crash the applet. Consider adding try-catch error handling similar to what's done in _get_cpu_usage() and _get_net_raw(), returning a fallback value (e.g., 0) on error.

Suggested change
let content = GLib.file_get_contents("/proc/meminfo")[1].toString();
let total = parseInt(content.match(/MemTotal:\s+(\d+)/)[1]);
let available = parseInt(content.match(/MemAvailable:\s+(\d+)/)[1]);
return Math.round(((total - available) / total) * 100);
try {
let content = GLib.file_get_contents("/proc/meminfo")[1].toString();
let totalMatch = content.match(/MemTotal:\s+(\d+)/);
let availableMatch = content.match(/MemAvailable:\s+(\d+)/);
if (!totalMatch || !availableMatch) {
throw new Error("Unexpected /proc/meminfo format");
}
let total = parseInt(totalMatch[1]);
let available = parseInt(availableMatch[1]);
if (!isFinite(total) || total <= 0 || !isFinite(available)) {
throw new Error("Failed to parse memory values");
}
return Math.round(((total - available) / total) * 100);
} catch (e) {
global.logError("Failed to get memory usage: " + e);
return 0;
}

Copilot uses AI. Check for mistakes.
*/
_get_net_raw() {
try {
let content = GLib.file_get_contents("/proc/net/dev")[1].toString();
Copy link
Member

@mtwebster mtwebster Feb 19, 2026

Choose a reason for hiding this comment

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

Do not use synchronous i/o functions. Use Gio.File.read_async/read_finish

@mtwebster
Copy link
Member

mtwebster commented Feb 19, 2026

Your black/white icons seem unnecessary. Make a single set of symbolic icons. They'll use whatever color the theme dictates as an appropriate color for them. This is how themes work.

I object to your entire 'animation' premise here - you're sharing a single thread with everything else in the UI. It's useless overhead.


let iconPath = `${this._render.path}/icons/runners/${this._settings.runner}/${this._settings.them}/${this._settings.runner}_${this._render.frame}.svg`;

if (GLib.file_test(iconPath, GLib.FileTest.EXISTS)) {
Copy link
Member

Choose a reason for hiding this comment

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

GLib.file_test() can block the thread, don't use it.

_get_cpu_usage() {
try {
// 1. 读取 /proc/stat
let content = GLib.file_get_contents("/proc/stat")[1].toString();
Copy link
Member

Choose a reason for hiding this comment

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

Bad synchronous io.

@github-actions
Copy link
Contributor

Best-practices scanner

This is a regex-based check for API usage that can pose security, performance or
maintainability issues, or that may already be provided by Cinnamon. Having code flagged
by it doesn't automatically disqualify a pull request.

This check is not perfect will not replace a normal review.


Found 4 potential issue(s):

⚠️ sync_file_get_contents

system-monitor-cinnamon@MainPoser/files/system-monitor-cinnamon@MainPoser/applet.js:118

let content = GLib.file_get_contents("/proc/stat")[1].toString();

Synchronous file_get_contents() blocks the main loop.
Use Gio.File.load_contents_async() instead.

system-monitor-cinnamon@MainPoser/files/system-monitor-cinnamon@MainPoser/applet.js:167

let content = GLib.file_get_contents("/proc/meminfo")[1].toString();

Synchronous file_get_contents() blocks the main loop.
Use Gio.File.load_contents_async() instead.

system-monitor-cinnamon@MainPoser/files/system-monitor-cinnamon@MainPoser/applet.js:178

let content = GLib.file_get_contents("/proc/net/dev")[1].toString();

Synchronous file_get_contents() blocks the main loop.
Use Gio.File.load_contents_async() instead.

⚠️ sync_file_test

system-monitor-cinnamon@MainPoser/files/system-monitor-cinnamon@MainPoser/applet.js:103

if (GLib.file_test(iconPath, GLib.FileTest.EXISTS)) {

file_test() is a synchronous stat call that can block on slow/network filesystems.
Prefer attempting the operation and handling a Gio.IOErrorEnum.NOT_FOUND error instead.


Automated pattern check.

@github-actions
Copy link
Contributor

Best-practices scanner

This is a regex-based check for API usage that can pose security, performance or
maintainability issues, or that may already be provided by Cinnamon. Having code flagged
by it doesn't automatically disqualify a pull request.

This check is not perfect will not replace a normal review.


Found 5 potential issue(s):

⚠️ sync_file_get_contents

system-monitor-cinnamon@MainPoser/files/system-monitor-cinnamon@MainPoser/applet.js:154

let content = GLib.file_get_contents("/proc/stat")[1].toString();

Synchronous file_get_contents() blocks the main loop.
Use Gio.File.load_contents_async() instead.

system-monitor-cinnamon@MainPoser/files/system-monitor-cinnamon@MainPoser/applet.js:203

let content = GLib.file_get_contents("/proc/meminfo")[1].toString();

Synchronous file_get_contents() blocks the main loop.
Use Gio.File.load_contents_async() instead.

system-monitor-cinnamon@MainPoser/files/system-monitor-cinnamon@MainPoser/applet.js:214

let content = GLib.file_get_contents("/proc/net/dev")[1].toString();

Synchronous file_get_contents() blocks the main loop.
Use Gio.File.load_contents_async() instead.

⚠️ sync_file_test

system-monitor-cinnamon@MainPoser/files/system-monitor-cinnamon@MainPoser/applet.js:120

if (GLib.file_test(iconPath, GLib.FileTest.EXISTS)) {

file_test() is a synchronous stat call that can block on slow/network filesystems.
Prefer attempting the operation and handling a Gio.IOErrorEnum.NOT_FOUND error instead.

system-monitor-cinnamon@MainPoser/files/system-monitor-cinnamon@MainPoser/applet.js:137

if (GLib.file_test(iconPath, GLib.FileTest.EXISTS)) {

file_test() is a synchronous stat call that can block on slow/network filesystems.
Prefer attempting the operation and handling a Gio.IOErrorEnum.NOT_FOUND error instead.


Automated pattern check.

@github-actions
Copy link
Contributor

Best-practices scanner

This is a regex-based check for API usage that can pose security, performance or
maintainability issues, or that may already be provided by Cinnamon. Having code flagged
by it doesn't automatically disqualify a pull request.

This check is not perfect will not replace a normal review.


Found 5 potential issue(s):

⚠️ sync_file_get_contents

system-monitor-cinnamon@MainPoser/files/system-monitor-cinnamon@MainPoser/applet.js:154

let content = GLib.file_get_contents("/proc/stat")[1].toString();

Synchronous file_get_contents() blocks the main loop.
Use Gio.File.load_contents_async() instead.

system-monitor-cinnamon@MainPoser/files/system-monitor-cinnamon@MainPoser/applet.js:203

let content = GLib.file_get_contents("/proc/meminfo")[1].toString();

Synchronous file_get_contents() blocks the main loop.
Use Gio.File.load_contents_async() instead.

system-monitor-cinnamon@MainPoser/files/system-monitor-cinnamon@MainPoser/applet.js:214

let content = GLib.file_get_contents("/proc/net/dev")[1].toString();

Synchronous file_get_contents() blocks the main loop.
Use Gio.File.load_contents_async() instead.

⚠️ sync_file_test

system-monitor-cinnamon@MainPoser/files/system-monitor-cinnamon@MainPoser/applet.js:120

if (GLib.file_test(iconPath, GLib.FileTest.EXISTS)) {

file_test() is a synchronous stat call that can block on slow/network filesystems.
Prefer attempting the operation and handling a Gio.IOErrorEnum.NOT_FOUND error instead.

system-monitor-cinnamon@MainPoser/files/system-monitor-cinnamon@MainPoser/applet.js:137

if (GLib.file_test(iconPath, GLib.FileTest.EXISTS)) {

file_test() is a synchronous stat call that can block on slow/network filesystems.
Prefer attempting the operation and handling a Gio.IOErrorEnum.NOT_FOUND error instead.


Automated pattern check.

@github-actions
Copy link
Contributor

Best-practices scanner

This is a regex-based check for API usage that can pose security, performance or
maintainability issues, or that may already be provided by Cinnamon. Having code flagged
by it doesn't automatically disqualify a pull request.

This check is not perfect will not replace a normal review.


Found 5 potential issue(s):

⚠️ sync_file_get_contents

system-monitor-cinnamon@MainPoser/files/system-monitor-cinnamon@MainPoser/applet.js:154

let content = GLib.file_get_contents("/proc/stat")[1].toString();

Synchronous file_get_contents() blocks the main loop.
Use Gio.File.load_contents_async() instead.

system-monitor-cinnamon@MainPoser/files/system-monitor-cinnamon@MainPoser/applet.js:203

let content = GLib.file_get_contents("/proc/meminfo")[1].toString();

Synchronous file_get_contents() blocks the main loop.
Use Gio.File.load_contents_async() instead.

system-monitor-cinnamon@MainPoser/files/system-monitor-cinnamon@MainPoser/applet.js:214

let content = GLib.file_get_contents("/proc/net/dev")[1].toString();

Synchronous file_get_contents() blocks the main loop.
Use Gio.File.load_contents_async() instead.

⚠️ sync_file_test

system-monitor-cinnamon@MainPoser/files/system-monitor-cinnamon@MainPoser/applet.js:120

if (GLib.file_test(iconPath, GLib.FileTest.EXISTS)) {

file_test() is a synchronous stat call that can block on slow/network filesystems.
Prefer attempting the operation and handling a Gio.IOErrorEnum.NOT_FOUND error instead.

system-monitor-cinnamon@MainPoser/files/system-monitor-cinnamon@MainPoser/applet.js:137

if (GLib.file_test(iconPath, GLib.FileTest.EXISTS)) {

file_test() is a synchronous stat call that can block on slow/network filesystems.
Prefer attempting the operation and handling a Gio.IOErrorEnum.NOT_FOUND error instead.


Automated pattern check.

@github-actions
Copy link
Contributor

Best-practices scanner

This is a regex-based check for API usage that can pose security, performance or
maintainability issues, or that may already be provided by Cinnamon. Having code flagged
by it doesn't automatically disqualify a pull request.

This check is not perfect will not replace a normal review.


Found 5 potential issue(s):

⚠️ sync_file_get_contents

system-monitor-cinnamon@MainPoser/files/system-monitor-cinnamon@MainPoser/applet.js:154

let content = GLib.file_get_contents("/proc/stat")[1].toString();

Synchronous file_get_contents() blocks the main loop.
Use Gio.File.load_contents_async() instead.

system-monitor-cinnamon@MainPoser/files/system-monitor-cinnamon@MainPoser/applet.js:203

let content = GLib.file_get_contents("/proc/meminfo")[1].toString();

Synchronous file_get_contents() blocks the main loop.
Use Gio.File.load_contents_async() instead.

system-monitor-cinnamon@MainPoser/files/system-monitor-cinnamon@MainPoser/applet.js:214

let content = GLib.file_get_contents("/proc/net/dev")[1].toString();

Synchronous file_get_contents() blocks the main loop.
Use Gio.File.load_contents_async() instead.

⚠️ sync_file_test

system-monitor-cinnamon@MainPoser/files/system-monitor-cinnamon@MainPoser/applet.js:120

if (GLib.file_test(iconPath, GLib.FileTest.EXISTS)) {

file_test() is a synchronous stat call that can block on slow/network filesystems.
Prefer attempting the operation and handling a Gio.IOErrorEnum.NOT_FOUND error instead.

system-monitor-cinnamon@MainPoser/files/system-monitor-cinnamon@MainPoser/applet.js:137

if (GLib.file_test(iconPath, GLib.FileTest.EXISTS)) {

file_test() is a synchronous stat call that can block on slow/network filesystems.
Prefer attempting the operation and handling a Gio.IOErrorEnum.NOT_FOUND error instead.


Automated pattern check.

@github-actions
Copy link
Contributor

Best-practices scanner

This is a regex-based check for API usage that can pose security, performance or
maintainability issues, or that may already be provided by Cinnamon. Having code flagged
by it doesn't automatically disqualify a pull request.

This check is not perfect will not replace a normal review.


Found 5 potential issue(s):

⚠️ sync_file_get_contents

system-monitor-cinnamon@MainPoser/files/system-monitor-cinnamon@MainPoser/applet.js:154

let content = GLib.file_get_contents("/proc/stat")[1].toString();

Synchronous file_get_contents() blocks the main loop.
Use Gio.File.load_contents_async() instead.

system-monitor-cinnamon@MainPoser/files/system-monitor-cinnamon@MainPoser/applet.js:204

let content = GLib.file_get_contents("/proc/meminfo")[1].toString();

Synchronous file_get_contents() blocks the main loop.
Use Gio.File.load_contents_async() instead.

system-monitor-cinnamon@MainPoser/files/system-monitor-cinnamon@MainPoser/applet.js:227

let content = GLib.file_get_contents("/proc/net/dev")[1].toString();

Synchronous file_get_contents() blocks the main loop.
Use Gio.File.load_contents_async() instead.

⚠️ sync_file_test

system-monitor-cinnamon@MainPoser/files/system-monitor-cinnamon@MainPoser/applet.js:120

if (GLib.file_test(iconPath, GLib.FileTest.EXISTS)) {

file_test() is a synchronous stat call that can block on slow/network filesystems.
Prefer attempting the operation and handling a Gio.IOErrorEnum.NOT_FOUND error instead.

system-monitor-cinnamon@MainPoser/files/system-monitor-cinnamon@MainPoser/applet.js:137

if (GLib.file_test(iconPath, GLib.FileTest.EXISTS)) {

file_test() is a synchronous stat call that can block on slow/network filesystems.
Prefer attempting the operation and handling a Gio.IOErrorEnum.NOT_FOUND error instead.


Automated pattern check.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants