Fork the desktop off Omarchy as a self-contained system
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import qs.Ui
|
||||
|
||||
// The shared gauge-cluster overlay (SpeedTestOverlay) dressed for the
|
||||
// internet speed test: download and upload dials in Mbps, titled with the
|
||||
// connection under test.
|
||||
//
|
||||
// Standalone panel plugin: summoning it starts a fresh run, dismissing it
|
||||
// stops the traffic, so the download workers never keep saturating the link
|
||||
// behind a closed overlay. The payload may carry the connection's display
|
||||
// name -- {"connection": "MyWifi"} -- and the panel looks it up itself via
|
||||
// blob-network-status when the caller doesn't know it.
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var shell: null
|
||||
property var manifest: null
|
||||
|
||||
property bool opened: false
|
||||
property string connectionName: ""
|
||||
|
||||
property bool running: false
|
||||
property bool expectedStop: false
|
||||
property bool pendingRun: false
|
||||
property string phase: "" // "down" | "up" | ""
|
||||
property string stderrText: ""
|
||||
property string downloadMbps: ""
|
||||
property string uploadMbps: ""
|
||||
property string error: ""
|
||||
|
||||
readonly property real downloadValue: toMbps(downloadMbps)
|
||||
readonly property real uploadValue: toMbps(uploadMbps)
|
||||
|
||||
function toMbps(raw) {
|
||||
var value = parseFloat(raw)
|
||||
return isFinite(value) && value > 0 ? value : 0
|
||||
}
|
||||
|
||||
function open(payloadJson) {
|
||||
var payload = {}
|
||||
try { payload = JSON.parse(payloadJson || "{}") || {} } catch (e) {}
|
||||
if (payload.connection !== undefined) root.connectionName = String(payload.connection)
|
||||
else refreshConnectionName()
|
||||
root.opened = true
|
||||
runSpeedTest()
|
||||
}
|
||||
|
||||
function close() {
|
||||
root.opened = false
|
||||
root.pendingRun = false
|
||||
phaseTimer.stop()
|
||||
// Clear the phase before killing the process: onExited advances to the
|
||||
// upload phase when it still reads "down".
|
||||
root.phase = ""
|
||||
root.running = false
|
||||
if (speedTestProc.running) {
|
||||
root.expectedStop = true
|
||||
speedTestProc.running = false
|
||||
}
|
||||
}
|
||||
|
||||
function dismiss() {
|
||||
if (root.shell && typeof root.shell.hide === "function")
|
||||
root.shell.hide((root.manifest && root.manifest.id) || "blob.speedtest")
|
||||
else close()
|
||||
}
|
||||
|
||||
function refreshConnectionName() {
|
||||
root.connectionName = ""
|
||||
statusProc.running = false
|
||||
statusProc.running = true
|
||||
}
|
||||
|
||||
function updateSpeedTestLine(line) {
|
||||
var value = parseFloat(line)
|
||||
if (!isFinite(value) || value < 0) return
|
||||
|
||||
if (phase === "down") downloadMbps = String(value)
|
||||
else if (phase === "up") uploadMbps = String(value)
|
||||
}
|
||||
|
||||
function runSpeedTest() {
|
||||
if (speedTestProc.running) {
|
||||
// A dismissal's SIGTERM is still in flight; Process.running stays true
|
||||
// until the child exits, so queue the fresh run for onExited.
|
||||
if (expectedStop) pendingRun = true
|
||||
return
|
||||
}
|
||||
error = ""
|
||||
downloadMbps = ""
|
||||
uploadMbps = ""
|
||||
running = true
|
||||
startPhase("down")
|
||||
}
|
||||
|
||||
function startPhase(nextPhase) {
|
||||
expectedStop = false
|
||||
phase = nextPhase
|
||||
stderrText = ""
|
||||
speedTestProc.command = ["blob-network-speedtest", nextPhase]
|
||||
speedTestProc.running = true
|
||||
phaseTimer.restart()
|
||||
}
|
||||
|
||||
function stopPhase() {
|
||||
phaseTimer.stop()
|
||||
if (speedTestProc.running) {
|
||||
expectedStop = true
|
||||
speedTestProc.running = false
|
||||
return
|
||||
}
|
||||
finishPhase()
|
||||
}
|
||||
|
||||
function finishPhase() {
|
||||
if (phase === "down") {
|
||||
startPhase("up")
|
||||
return
|
||||
}
|
||||
|
||||
phase = ""
|
||||
running = false
|
||||
expectedStop = false
|
||||
}
|
||||
|
||||
Process {
|
||||
id: speedTestProc
|
||||
stdout: SplitParser { onRead: function(line) { root.updateSpeedTestLine(line) } }
|
||||
// Exit and stream-finished have no guaranteed order: when a failed exit
|
||||
// beat the collector and published the generic message, replace it with
|
||||
// the specific one once it lands.
|
||||
stderr: StdioCollector {
|
||||
waitForEnd: true
|
||||
onStreamFinished: {
|
||||
root.stderrText = String(text || "").trim()
|
||||
if (root.error !== "" && root.stderrText !== "") root.error = root.stderrText
|
||||
}
|
||||
}
|
||||
onExited: function(exitCode) {
|
||||
phaseTimer.stop()
|
||||
|
||||
if (root.pendingRun) {
|
||||
root.pendingRun = false
|
||||
root.expectedStop = false
|
||||
if (root.opened) Qt.callLater(root.runSpeedTest)
|
||||
return
|
||||
}
|
||||
|
||||
if (!root.expectedStop && exitCode !== 0) {
|
||||
root.error = root.stderrText || "Speed test failed"
|
||||
root.phase = ""
|
||||
root.running = false
|
||||
return
|
||||
}
|
||||
|
||||
root.expectedStop = false
|
||||
root.finishPhase()
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: phaseTimer
|
||||
interval: 5000
|
||||
repeat: false
|
||||
onTriggered: root.stopPhase()
|
||||
}
|
||||
|
||||
// Names the connection under test when the summoner didn't. First tab
|
||||
// field is the kind, second the SSID (wifi) or device (ethernet).
|
||||
Process {
|
||||
id: statusProc
|
||||
command: ["blob-network-status"]
|
||||
stdout: StdioCollector {
|
||||
waitForEnd: true
|
||||
onStreamFinished: {
|
||||
var fields = String(text || "").trim().split("\t")
|
||||
if (fields[0] === "wifi") root.connectionName = fields[1] || "Wi-Fi"
|
||||
else if (fields[0] === "ethernet") root.connectionName = "Ethernet"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SpeedTestOverlay {
|
||||
fontFamily: Style.font.family
|
||||
layerNamespace: "blob-network-speedtest"
|
||||
title: root.connectionName
|
||||
leftLabel: "DOWNLOAD"
|
||||
rightLabel: "UPLOAD"
|
||||
runAgainTooltip: "Measure again via fast.com"
|
||||
running: root.running
|
||||
leftValue: root.downloadValue
|
||||
rightValue: root.uploadValue
|
||||
leftLive: root.running && root.phase === "down"
|
||||
rightLive: root.running && root.phase === "up"
|
||||
error: root.error
|
||||
open: root.opened
|
||||
onCloseRequested: root.dismiss()
|
||||
onRunAgainRequested: root.runSpeedTest()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user