Shortcuts you invent, rules you write, gestures you own - all still just data
Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
@@ -35,6 +35,12 @@ Singleton {
|
||||
// connection name -> the helper's connection shape. See detailsFor().
|
||||
property var details: ({})
|
||||
|
||||
// Every profile NetworkManager holds, in range or not. See the `saved`
|
||||
// verb: this is the list that is otherwise invisible until you are standing
|
||||
// next to the network you wanted to tidy up.
|
||||
property var savedConnections: []
|
||||
property bool savedScanned: false
|
||||
|
||||
property var hotspot: ({})
|
||||
property string proxyMode: "none"
|
||||
property string proxyHost: ""
|
||||
@@ -54,7 +60,7 @@ Singleton {
|
||||
|
||||
// Guards read the Process objects directly; a derived binding is stale
|
||||
// inside the handler that changes it. See DefaultApps.qml.
|
||||
readonly property bool busy: mutation.running || enterprise.running || detailsQuery.running
|
||||
readonly property bool busy: mutation.running || passwordJoin.running || detailsQuery.running
|
||||
|
||||
// Connections whose details have been asked for, in order, so a burst of
|
||||
// requests becomes one query at a time rather than one Process each.
|
||||
@@ -131,6 +137,18 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
function absorbSaved(text: string): void {
|
||||
try {
|
||||
const parsed = JSON.parse(text);
|
||||
root.savedConnections = Array.isArray(parsed.connections) ? parsed.connections : [];
|
||||
root.savedScanned = true;
|
||||
if (String(parsed.error ?? "") !== "")
|
||||
root.lastError = String(parsed.error);
|
||||
} catch (error) {
|
||||
root.lastError = "Could not read the saved networks.";
|
||||
}
|
||||
}
|
||||
|
||||
function absorbHotspot(text: string): void {
|
||||
try {
|
||||
const parsed = JSON.parse(text);
|
||||
@@ -185,6 +203,7 @@ Singleton {
|
||||
function absorb(shape: string, subject: string, text: string): void {
|
||||
switch (shape) {
|
||||
case "connection": root.absorbDetails(subject, text); break;
|
||||
case "saved": root.absorbSaved(text); break;
|
||||
case "hotspot": root.absorbHotspot(text); break;
|
||||
case "proxy": root.absorbProxy(text); break;
|
||||
case "airplane": root.absorbAirplane(text); break;
|
||||
@@ -206,6 +225,10 @@ Singleton {
|
||||
|
||||
function forget(connection: string): void {
|
||||
root.run("connection", connection, ["forget", connection]);
|
||||
// The saved list is the one surface that shows profiles nobody is
|
||||
// standing next to, so a forget nobody re-reads leaves a row for a
|
||||
// profile that no longer exists. The timer waits for the mutation.
|
||||
root.refreshSavedSoon();
|
||||
}
|
||||
|
||||
function setAutoconnect(connection: string, enabled: bool): void {
|
||||
@@ -218,6 +241,36 @@ Singleton {
|
||||
["set-mac-random", connection, enabled ? "true" : "false"]);
|
||||
}
|
||||
|
||||
// "yes", "no" or "auto". Three states rather than two, because "automatic"
|
||||
// is NetworkManager guessing from what the network said and "no" is a claim
|
||||
// — see the helper's set_metered.
|
||||
function setMetered(connection: string, mode: string): void {
|
||||
root.run("connection", connection, ["set-metered", connection, String(mode)]);
|
||||
}
|
||||
|
||||
// ---- static addressing
|
||||
//
|
||||
// family is "4" or "6" — the helper's own spelling, so nothing has to
|
||||
// translate between two vocabularies on the way down.
|
||||
|
||||
function setIpAuto(connection: string, family: string): void {
|
||||
root.run("connection", connection, ["set-ip", connection, String(family), "auto"]);
|
||||
}
|
||||
|
||||
// Every field on every call, including the empty ones: the helper writes
|
||||
// the whole stack at once so that switching modes cannot leave half the old
|
||||
// configuration behind. `dns` is the comma-separated list as typed.
|
||||
function setIpManual(connection: string, family: string, address: string,
|
||||
gateway: string, dns: string): void {
|
||||
root.run("connection", connection,
|
||||
["set-ip", connection, String(family), "manual",
|
||||
String(address), String(gateway), String(dns)]);
|
||||
}
|
||||
|
||||
// ---- saved profiles
|
||||
|
||||
function refreshSaved(): void { root.run("saved", "", ["saved"]); }
|
||||
|
||||
// ---- VPN
|
||||
|
||||
function importVpn(path: string): void {
|
||||
@@ -273,16 +326,34 @@ Singleton {
|
||||
// above: only this one ever opens stdin.
|
||||
function joinEnterprise(ssid: string, profile: string, identity: string,
|
||||
password: string, caCert: string): void {
|
||||
if (enterprise.running)
|
||||
if (passwordJoin.running)
|
||||
return;
|
||||
root.lastError = "";
|
||||
root.pendingPassword = password;
|
||||
enterprise.subject = ssid;
|
||||
enterprise.command = String(caCert ?? "") !== ""
|
||||
passwordJoin.subject = ssid;
|
||||
passwordJoin.command = String(caCert ?? "") !== ""
|
||||
? [root.helperPath, "join-enterprise", ssid, profile, identity, caCert]
|
||||
: [root.helperPath, "join-enterprise", ssid, profile, identity];
|
||||
enterprise.stdinEnabled = true;
|
||||
enterprise.running = true;
|
||||
passwordJoin.stdinEnabled = true;
|
||||
passwordJoin.running = true;
|
||||
}
|
||||
|
||||
// ---- hidden Wi-Fi
|
||||
//
|
||||
// Same passphrase path as the enterprise join, for the same reason: a
|
||||
// passphrase in argv is published to every process on this machine through
|
||||
// /proc. An open hidden network still goes down this path and writes an
|
||||
// empty line, so the helper's read returns rather than waiting forever.
|
||||
function joinHidden(ssid: string, profile: string, security: string,
|
||||
password: string): void {
|
||||
if (passwordJoin.running)
|
||||
return;
|
||||
root.lastError = "";
|
||||
root.pendingPassword = password;
|
||||
passwordJoin.subject = profile;
|
||||
passwordJoin.command = [root.helperPath, "join-hidden", ssid, profile, security];
|
||||
passwordJoin.stdinEnabled = true;
|
||||
passwordJoin.running = true;
|
||||
}
|
||||
|
||||
// Everything that is not per-connection, in one call: what a page asks for
|
||||
@@ -291,6 +362,7 @@ Singleton {
|
||||
root.refreshProxy();
|
||||
root.refreshAirplaneSoon();
|
||||
root.refreshHotspotSoon();
|
||||
root.refreshSavedSoon();
|
||||
for (const connection in root.details)
|
||||
root.requestDetails(connection);
|
||||
}
|
||||
@@ -299,6 +371,7 @@ Singleton {
|
||||
// over it rather than dropped by its running guard.
|
||||
function refreshAirplaneSoon(): void { airplaneSoon.restart(); }
|
||||
function refreshHotspotSoon(): void { hotspotSoon.restart(); }
|
||||
function refreshSavedSoon(): void { savedSoon.restart(); }
|
||||
|
||||
onActiveChanged: if (root.active) root.refresh()
|
||||
|
||||
@@ -324,6 +397,17 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: savedSoon
|
||||
interval: 400
|
||||
onTriggered: {
|
||||
if (mutation.running)
|
||||
savedSoon.restart();
|
||||
else
|
||||
root.refreshSaved();
|
||||
}
|
||||
}
|
||||
|
||||
// The next queued details read, one tick after the last one exits. Draining
|
||||
// from inside onExited would look at a `running` that has not gone false
|
||||
// yet, and the queue would stall on its own guard.
|
||||
@@ -369,24 +453,29 @@ Singleton {
|
||||
}
|
||||
|
||||
Process {
|
||||
id: enterprise
|
||||
id: passwordJoin
|
||||
|
||||
property string subject: ""
|
||||
|
||||
onStarted: {
|
||||
enterprise.write(root.pendingPassword + "\n");
|
||||
passwordJoin.write(root.pendingPassword + "\n");
|
||||
// Held for as long as it takes to hand over, and no longer.
|
||||
root.pendingPassword = "";
|
||||
// Closing stdin is what lets the helper's read return; without it
|
||||
// the join waits forever for a line that is already sent.
|
||||
enterprise.stdinEnabled = false;
|
||||
passwordJoin.stdinEnabled = false;
|
||||
}
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: root.absorbDetails(enterprise.subject, this.text)
|
||||
onStreamFinished: root.absorbDetails(passwordJoin.subject, this.text)
|
||||
}
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: if (this.text.trim() !== "") root.lastError = this.text.trim()
|
||||
}
|
||||
onExited: root.pendingPassword = ""
|
||||
onExited: {
|
||||
root.pendingPassword = "";
|
||||
// A join makes a profile, so the saved list is out of date the
|
||||
// moment this returns.
|
||||
root.refreshSavedSoon();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user