Add GNOME Shell extension showing sing-box status via the Clash API

This commit is contained in:
av
2026-07-18 15:25:26 +03:00
commit 629e031845
10 changed files with 893 additions and 0 deletions
+82
View File
@@ -0,0 +1,82 @@
import Adw from 'gi://Adw';
import Gtk from 'gi://Gtk';
import Gio from 'gi://Gio';
import { ExtensionPreferences, gettext as _ } from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';
export default class SingBoxStatusPreferences extends ExtensionPreferences {
fillPreferencesWindow(window) {
const settings = this.getSettings();
const page = new Adw.PreferencesPage({
title: _('General'),
icon_name: 'network-vpn-symbolic',
});
window.add(page);
// --- Connection ------------------------------------------------
const connGroup = new Adw.PreferencesGroup({
title: _('Clash API'),
description: _('Matches experimental_clash_api in your sing-box config.'),
});
page.add(connGroup);
connGroup.add(this._entryRow(settings, 'api-url', _('API URL'),
_('e.g. http://127.0.0.1:9090')));
connGroup.add(this._passwordRow(settings, 'api-secret', _('Secret'),
_('Bearer token; leave empty if none.')));
connGroup.add(this._entryRow(settings, 'dashboard-url', _('Dashboard URL'),
_('Opened by “Open dashboard”.')));
// --- Display ---------------------------------------------------
const dispGroup = new Adw.PreferencesGroup({ title: _('Display') });
page.add(dispGroup);
dispGroup.add(this._switchRow(settings, 'show-outbound', _('Show active outbound'),
_('Show the current outbound name in the top bar.')));
dispGroup.add(this._switchRow(settings, 'show-speed', _('Show speed'),
_('Show upload/download speed in the top bar.')));
dispGroup.add(this._entryRow(settings, 'selector-group', _('Panel selector group'),
_('Selector group shown in the panel. Empty = first one found.')));
const intervalRow = new Adw.SpinRow({
title: _('Refresh interval'),
subtitle: _('Seconds between Clash API polls.'),
adjustment: new Gtk.Adjustment({ lower: 1, upper: 30, step_increment: 1 }),
});
settings.bind('refresh-interval', intervalRow, 'value', Gio.SettingsBindFlags.DEFAULT);
dispGroup.add(intervalRow);
}
_entryRow(settings, key, title, subtitle) {
const row = new Adw.EntryRow({ title });
if (subtitle && row.set_subtitle) // EntryRow gained subtitle later; guard.
row.set_subtitle(subtitle);
row.text = settings.get_string(key);
row.connect('changed', () => settings.set_string(key, row.text));
settings.connect(`changed::${key}`, () => {
const v = settings.get_string(key);
if (row.text !== v)
row.text = v;
});
return row;
}
_passwordRow(settings, key, title, subtitle) {
const row = new Adw.PasswordEntryRow({ title });
row.text = settings.get_string(key);
row.connect('changed', () => settings.set_string(key, row.text));
settings.connect(`changed::${key}`, () => {
const v = settings.get_string(key);
if (row.text !== v)
row.text = v;
});
return row;
}
_switchRow(settings, key, title, subtitle) {
const row = new Adw.SwitchRow({ title, subtitle });
settings.bind(key, row, 'active', Gio.SettingsBindFlags.DEFAULT);
return row;
}
}