Files
dotfiles/gnome/.local/share/gnome-shell/extensions/snapshell@unbl.ink/prefs.js

201 lines
6.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Adw from "gi://Adw";
import Gio from "gi://Gio";
import GLib from "gi://GLib";
import Gtk from "gi://Gtk";
import Soup from "gi://Soup";
import { ExtensionPreferences } from "resource:///org/gnome/shell/extensions/prefs.js";
function jsonRpc(method, params = {}) {
return JSON.stringify({ jsonrpc: "2.0", id: 1, method, params });
}
async function postJson(session, url, bodyStr) {
const msg = Soup.Message.new("POST", url);
msg.request_headers.append("Content-Type", "application/json");
msg.set_request_body_from_bytes(
"application/json",
new GLib.Bytes(bodyStr),
);
const bytes = await session.send_and_read_async(
msg,
GLib.PRIORITY_DEFAULT,
null,
);
const text = new TextDecoder().decode(bytes.get_data());
return JSON.parse(text);
}
function connectedCount(group) {
return (group?.clients ?? []).filter((c) => c?.connected).length;
}
function groupLabel(group) {
const base =
group?.name && group.name.trim()
? group.name.trim()
: (group?.stream_id ?? "Group");
const connected = connectedCount(group);
const total = group?.clients?.length ?? 0;
return `${base} (${connected}/${total})`;
}
export default class SnapshellPreferences extends ExtensionPreferences {
fillPreferencesWindow(window) {
const settings = this.getSettings(
"org.gnome.shell.extensions.snapshell",
);
const session = new Soup.Session();
// --- UI models ---
const groupNames = new Gtk.StringList();
let groupIds = []; // parallel array: index -> group id
// --- Page ---
const page = new Adw.PreferencesPage({
title: "Snapshell",
icon_name: "audio-volume-high-symbolic",
});
window.add(page);
// --- Group: Connection ---
const connectionGroup = new Adw.PreferencesGroup({
title: "Connection",
description: "Configure Snapserver JSON-RPC endpoint.",
});
page.add(connectionGroup);
const urlRow = new Adw.EntryRow({
title: "Server URL",
text: settings.get_string("server-url"),
});
urlRow.set_tooltip_text("Example: https://snap.unbl.ink/jsonrpc");
connectionGroup.add(urlRow);
urlRow.connect("notify::text", () => {
settings.set_string("server-url", urlRow.text.trim());
});
// --- Group: Target Group ---
const targetGroup = new Adw.PreferencesGroup({
title: "Target group",
description:
"Choose which Snapcast group the Quick Settings slider controls.",
});
page.add(targetGroup);
const groupRow = new Adw.ComboRow({
title: "Snapcast group",
model: groupNames,
});
targetGroup.add(groupRow);
const refreshRow = new Adw.ActionRow({
title: "Refresh groups",
subtitle: "Fetch groups from Server.GetStatus",
});
const refreshBtn = new Gtk.Button({ label: "Refresh" });
refreshRow.add_suffix(refreshBtn);
refreshRow.activatable_widget = refreshBtn;
targetGroup.add(refreshRow);
// --- Group: Behavior ---
const behaviorGroup = new Adw.PreferencesGroup({
title: "Behavior",
});
page.add(behaviorGroup);
const pollRow = new Adw.SpinRow({
title: "Poll interval (seconds)",
adjustment: new Gtk.Adjustment({
lower: 2,
upper: 60,
step_increment: 1,
page_increment: 5,
value: settings.get_uint("poll-seconds") || 5,
}),
});
behaviorGroup.add(pollRow);
pollRow.connect("notify::value", () => {
settings.set_uint("poll-seconds", pollRow.value);
});
// --- Helpers to load + sync groups ---
const setStatus = (text) => {
refreshRow.subtitle = text;
};
const clearGroups = () => {
// Remove all items from StringList
while (groupNames.get_n_items() > 0) groupNames.remove(0);
groupIds = [];
};
const syncComboSelectionFromSettings = () => {
const selectedId = settings.get_string("group-id");
const idx = groupIds.indexOf(selectedId);
groupRow.selected = idx >= 0 ? idx : Gtk.INVALID_LIST_POSITION;
};
const fetchGroups = async () => {
const url = settings.get_string("server-url").trim();
if (!url) {
clearGroups();
setStatus("Set Server URL first.");
return;
}
setStatus("Fetching…");
try {
const resp = await postJson(
session,
url,
jsonRpc("Server.GetStatus", {}),
);
const groups = resp?.result?.server?.groups ?? [];
clearGroups();
// Populate list
for (const g of groups) {
groupNames.append(groupLabel(g));
groupIds.push(g.id);
}
setStatus(`Loaded ${groups.length} group(s).`);
syncComboSelectionFromSettings();
} catch (e) {
clearGroups();
setStatus(`Failed: ${e?.message ?? e}`);
}
};
// When user picks a row, store its group id
groupRow.connect("notify::selected", () => {
const idx = groupRow.selected;
if (idx === Gtk.INVALID_LIST_POSITION) return;
const id = groupIds[idx];
if (id) settings.set_string("group-id", id);
});
// Refresh button
refreshBtn.connect("clicked", () => {
fetchGroups();
});
// If URL changes, offer fresh fetch (dont auto-spam)
urlRow.connect("notify::text", () => {
setStatus("URL changed. Click Refresh.");
clearGroups();
syncComboSelectionFromSettings();
});
// Initial load
fetchGroups();
}
}