206 lines
6.6 KiB
JavaScript
206 lines
6.6 KiB
JavaScript
import Adw from "gi://Adw";
|
|
import GLib from "gi://GLib";
|
|
import Gtk from "gi://Gtk";
|
|
import Soup from "gi://Soup";
|
|
|
|
import { ExtensionPreferences } from "resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js";
|
|
|
|
export default class SnapshellPreferences extends ExtensionPreferences {
|
|
fillPreferencesWindow(window) {
|
|
const settings = this.getSettings();
|
|
|
|
const session = new Soup.Session({
|
|
timeout: 5,
|
|
idle_timeout: 5,
|
|
user_agent: "Snapshell/1",
|
|
});
|
|
|
|
const groupNames = new Gtk.StringList();
|
|
let groupIds = [];
|
|
|
|
function jsonRpc(method, params) {
|
|
return JSON.stringify({
|
|
jsonrpc: "2.0",
|
|
id: 1,
|
|
method: method,
|
|
params: params || {},
|
|
});
|
|
}
|
|
|
|
function postJson(url, bodyStr) {
|
|
return new Promise((resolve, reject) => {
|
|
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),
|
|
);
|
|
|
|
session.send_and_read_async(
|
|
msg,
|
|
GLib.PRIORITY_DEFAULT,
|
|
null,
|
|
(sess, res) => {
|
|
try {
|
|
const bytes = sess.send_and_read_finish(res);
|
|
const text = new TextDecoder().decode(
|
|
bytes.get_data(),
|
|
);
|
|
resolve(JSON.parse(text));
|
|
} catch (e) {
|
|
reject(e);
|
|
}
|
|
},
|
|
);
|
|
});
|
|
}
|
|
|
|
function labelForGroup(g) {
|
|
const clients = g && g.clients ? g.clients : [];
|
|
let connected = 0;
|
|
for (const c of clients) {
|
|
if (c && c.connected) connected++;
|
|
}
|
|
const total = clients.length;
|
|
|
|
let base = "Group";
|
|
if (g && g.name && String(g.name).trim().length > 0)
|
|
base = String(g.name).trim();
|
|
else if (g && g.stream_id) base = String(g.stream_id);
|
|
|
|
return `${base} (${connected}/${total})`;
|
|
}
|
|
|
|
function clearGroups() {
|
|
while (groupNames.get_n_items() > 0) groupNames.remove(0);
|
|
groupIds = [];
|
|
}
|
|
|
|
function syncSelectionFromSettings(groupRow) {
|
|
const selectedId = settings.get_string("group-id");
|
|
const idx = groupIds.indexOf(selectedId);
|
|
groupRow.selected = idx >= 0 ? idx : Gtk.INVALID_LIST_POSITION;
|
|
}
|
|
|
|
async function fetchGroups(statusRow, groupRow) {
|
|
const url = settings.get_string("server-url").trim();
|
|
if (!url) {
|
|
clearGroups();
|
|
statusRow.subtitle = "Set Server URL first.";
|
|
return;
|
|
}
|
|
|
|
statusRow.subtitle = "Fetching…";
|
|
try {
|
|
const resp = await postJson(
|
|
url,
|
|
jsonRpc("Server.GetStatus", {}),
|
|
);
|
|
const groups =
|
|
resp &&
|
|
resp.result &&
|
|
resp.result.server &&
|
|
resp.result.server.groups
|
|
? resp.result.server.groups
|
|
: [];
|
|
|
|
clearGroups();
|
|
for (const g of groups) {
|
|
groupNames.append(labelForGroup(g));
|
|
groupIds.push(g.id);
|
|
}
|
|
|
|
statusRow.subtitle = `Loaded ${groups.length} group(s).`;
|
|
syncSelectionFromSettings(groupRow);
|
|
} catch (e) {
|
|
clearGroups();
|
|
statusRow.subtitle = `Failed: ${e && e.message ? e.message : e}`;
|
|
}
|
|
}
|
|
|
|
// ----- UI -----
|
|
const page = new Adw.PreferencesPage({
|
|
title: "Snapshell",
|
|
icon_name: "audio-volume-high-symbolic",
|
|
});
|
|
window.add(page);
|
|
|
|
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"),
|
|
});
|
|
connectionGroup.add(urlRow);
|
|
|
|
urlRow.connect("notify::text", () => {
|
|
settings.set_string("server-url", urlRow.text.trim());
|
|
});
|
|
|
|
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 statusRow = new Adw.ActionRow({
|
|
title: "Refresh groups",
|
|
subtitle: "Fetch groups from Server.GetStatus",
|
|
});
|
|
const refreshBtn = new Gtk.Button({ label: "Refresh" });
|
|
statusRow.add_suffix(refreshBtn);
|
|
statusRow.activatable_widget = refreshBtn;
|
|
targetGroup.add(statusRow);
|
|
|
|
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);
|
|
});
|
|
|
|
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);
|
|
});
|
|
|
|
refreshBtn.connect("clicked", () => {
|
|
fetchGroups(statusRow, groupRow);
|
|
});
|
|
|
|
urlRow.connect("notify::text", () => {
|
|
statusRow.subtitle = "URL changed. Click Refresh.";
|
|
clearGroups();
|
|
syncSelectionFromSettings(groupRow);
|
|
});
|
|
|
|
// Initial load
|
|
fetchGroups(statusRow, groupRow);
|
|
}
|
|
}
|