- hypr/apps.lua - hypr/autostart.lua - hypr/envs.lua - hypr/hyprland.lua - hypr/hyprsunset.conf - hypr/input.lua - hypr/looknfeel.lua - hypr/omasettings.lua - hypr/xdph.conf - omarchy/branding/about.txt - omarchy/branding/screensaver.txt - omarchy/extensions/omarchy-menu.jsonc - omarchy/hooks/battery-low.d/play-warning-sound.sample - omarchy/hooks/font-set.d/show-font-notification.sample - omarchy/hooks/post-boot.d/weather.sample - omarchy/hooks/post-update.d/install-voxtype.hook - omarchy/hooks/post-update.d/setup-agent.hook - omarchy/hooks/post-update.d/setup-fingerprint.hook - omarchy/hooks/post-update.d/show-update-notification.sample - omarchy/hooks/pre-refresh-pacman.d/add-custom-repo.sample - omarchy/hooks/theme-set.d/show-theme-notification.sample - omarchy/shell.json - omarchy/shell.toml - omarchy/theme.name - omarchy/themes/azure-glow/README.md - omarchy/themes/azure-glow/alacritty.toml - omarchy/themes/azure-glow/btop.theme - omarchy/themes/azure-glow/hyprland.conf - omarchy/themes/azure-glow/hyprlock.conf - omarchy/themes/azure-glow/icons.theme - … 269 more
342 lines
11 KiB
QML
342 lines
11 KiB
QML
pragma ComponentBehavior: Bound
|
|
|
|
import QtQuick
|
|
import qs.Commons
|
|
import qs.Ui
|
|
import "format.js" as Fmt
|
|
|
|
// The default tab: is it me or is it them, in one glance.
|
|
Column {
|
|
id: tab
|
|
|
|
required property var panel
|
|
readonly property var live: panel.live
|
|
|
|
spacing: Style.space(12)
|
|
|
|
readonly property bool outage: live
|
|
&& (live.state === "wan-down" || live.state === "local-down")
|
|
|
|
// Same shape the bar shows, so the two agree at a glance.
|
|
function elapsed(since) {
|
|
if (!since) return ""
|
|
var s = Math.max(0, Math.round(Date.now() / 1000 - since))
|
|
var m = Math.floor(s / 60)
|
|
return m > 0 ? m + "m" + (s % 60) + "s" : s + "s"
|
|
}
|
|
|
|
// Only on a captive network, so it costs no height the rest of the time.
|
|
// It goes first because it is the one thing worth reading here: without
|
|
// it, every number below is the portal answering rather than the
|
|
// connection, and the panel would be blaming the router for a sign-in
|
|
// page.
|
|
Text {
|
|
textFormat: Text.PlainText
|
|
visible: tab.live && tab.live.state === "captive"
|
|
height: visible ? implicitHeight : 0
|
|
width: parent.width
|
|
wrapMode: Text.WordWrap
|
|
text: "This network wants you to sign in. Something here is answering "
|
|
+ "for the internet, so treat the numbers below as the sign-in page, "
|
|
+ "not your connection."
|
|
color: tab.panel.warnTone
|
|
font.family: tab.panel.fontFamily
|
|
font.pixelSize: Style.font.caption
|
|
}
|
|
|
|
Text {
|
|
textFormat: Text.PlainText
|
|
text: "PATH"
|
|
color: tab.panel.dim
|
|
font.family: tab.panel.fontFamily
|
|
font.pixelSize: Style.font.caption
|
|
font.letterSpacing: 1
|
|
}
|
|
|
|
PathChain {
|
|
width: parent.width
|
|
live: tab.live
|
|
panel: tab.panel
|
|
anchor: tab.panel.setting("internetAnchor", "1.1.1.1")
|
|
textColor: tab.panel.fg
|
|
dimColor: tab.panel.dim
|
|
}
|
|
|
|
// Lag summary line, Orb vocabulary: best / typical / worst.
|
|
Item {
|
|
width: parent.width
|
|
height: lagLabel.implicitHeight
|
|
|
|
Text {
|
|
id: lagLabel
|
|
textFormat: Text.PlainText
|
|
text: "LAG"
|
|
color: tab.panel.dim
|
|
font.family: tab.panel.fontFamily
|
|
font.pixelSize: Style.font.caption
|
|
font.letterSpacing: 1
|
|
}
|
|
Text {
|
|
textFormat: Text.PlainText
|
|
anchors.right: parent.right
|
|
text: {
|
|
var l = tab.live
|
|
if (!l || !l.lag) return "--"
|
|
// The band is null when the whole window was lost. `lag.now` is
|
|
// still 1500 there because Responsiveness needs an anchor to land
|
|
// on, but 1500 is not a round trip and printing it three times
|
|
// said the link was replying slowly when it was not replying.
|
|
if (l.lag.typical === null || l.lag.typical === undefined)
|
|
return tab.outage ? "no reply" : "--"
|
|
var best = l.lag.best !== null ? Math.round(l.lag.best) : "--"
|
|
var worst = l.lag.worst !== null ? Math.round(l.lag.worst) : "--"
|
|
return "best " + best + " · typical " + Math.round(l.lag.typical)
|
|
+ " ms · worst " + worst
|
|
}
|
|
color: tab.panel.fg
|
|
font.family: tab.panel.fontFamily
|
|
font.pixelSize: Style.font.bodySmall
|
|
}
|
|
}
|
|
|
|
PanelSeparator { width: parent.width }
|
|
|
|
// The three pillars.
|
|
Row {
|
|
width: parent.width
|
|
spacing: Style.space(14)
|
|
|
|
readonly property real cell: (width - Style.space(14) * 2) / 3
|
|
readonly property var scores: tab.live && tab.live.scores ? tab.live.scores : {}
|
|
|
|
ScorePillar {
|
|
width: parent.cell
|
|
label: "RESPONSIVENESS"
|
|
value: parent.scores.responsiveness !== undefined ? parent.scores.responsiveness : null
|
|
note: {
|
|
var l = tab.live
|
|
if (!l || !l.lag) return "no data yet"
|
|
if (l.lag.typical === null || l.lag.typical === undefined)
|
|
return tab.outage ? "nothing is answering" : "no data yet"
|
|
return "lag " + Math.round(l.lag.typical) + " ms typical"
|
|
}
|
|
textColor: tab.panel.fg
|
|
dimColor: tab.panel.dim
|
|
}
|
|
ScorePillar {
|
|
width: parent.cell
|
|
label: "RELIABILITY"
|
|
value: parent.scores.reliability !== undefined ? parent.scores.reliability : null
|
|
// A 24-hour score barely moves in the first minute of an outage, so
|
|
// the number stays 100 and is honest. Green is not: it reads as
|
|
// reassurance next to a dead link. The caption carries the live fact
|
|
// instead of restating the window, and it is short enough to fit —
|
|
// the previous wording truncated mid-word in this column.
|
|
toneOverride: tab.outage ? tab.panel.warnTone : null
|
|
note: {
|
|
var l = tab.live
|
|
if (!l) return ""
|
|
if (l.state === "captive") return "not signed in yet"
|
|
if (tab.outage) {
|
|
var d = tab.elapsed(l.down_since)
|
|
return d ? "down " + d : "outage now"
|
|
}
|
|
return "last 24 h"
|
|
}
|
|
textColor: tab.panel.fg
|
|
dimColor: tab.panel.dim
|
|
}
|
|
ScorePillar {
|
|
width: parent.cell
|
|
label: "SPEED"
|
|
value: parent.scores.speed !== undefined ? parent.scores.speed : null
|
|
// A figure the index is ignoring must not shout in red as though it
|
|
// were the verdict — it is being reported, not counted.
|
|
toneOverride: {
|
|
var c = tab.live ? tab.live.speed_ctx : null
|
|
return c && c.scored === false ? tab.panel.dim : null
|
|
}
|
|
note: {
|
|
// Say why it is blank, or the pause reads as a fault. This is the
|
|
// whole point of detecting the hotspot: the checks are ~14 MB each
|
|
// and hourly, which is data the user did not offer.
|
|
var m = tab.live ? tab.live.metered : null
|
|
var ctx = tab.live ? tab.live.speed_ctx : null
|
|
var held = m && m.care
|
|
if (!ctx || ctx.last_down === null || ctx.last_down === undefined)
|
|
return held ? "checks paused on " + m.label : "no content check yet"
|
|
var mbps = Math.round(ctx.last_down) + " Mbps"
|
|
// No content check runs while the line is down, so this figure is
|
|
// from before it. Saying "measured" would imply it is current.
|
|
if (tab.outage) return mbps + " before the drop"
|
|
if (held) return mbps + " · checks paused"
|
|
if (ctx.basis === "plan")
|
|
return mbps + " vs " + Math.round(ctx.plan_down) + " plan"
|
|
// Not counted, and why. One check is the arrival check on a link
|
|
// that may still have been settling; a peak test that read far
|
|
// higher has already disproved this figure.
|
|
// Kept inside the column: "63 Mbps · usually 384" is the widest
|
|
// caption that fits here, so anything longer elides mid-word. The
|
|
// dim number already says it is not counted; this says why.
|
|
if (ctx.scored === false) {
|
|
if (ctx.peak_down)
|
|
return mbps + " · test: " + Math.round(ctx.peak_down)
|
|
return mbps + " · unconfirmed"
|
|
}
|
|
if (ctx.baseline_down && ctx.last_down < ctx.baseline_down * 0.6)
|
|
return mbps + " · usually " + Math.round(ctx.baseline_down)
|
|
return mbps + " measured"
|
|
}
|
|
textColor: tab.panel.fg
|
|
dimColor: tab.panel.dim
|
|
}
|
|
}
|
|
|
|
PanelSeparator { width: parent.width }
|
|
|
|
// 30-minute latency chart from recent.json.
|
|
Item {
|
|
width: parent.width
|
|
height: chartLabel.implicitHeight
|
|
|
|
Text {
|
|
id: chartLabel
|
|
textFormat: Text.PlainText
|
|
text: "LATENCY · LAST 30 MIN"
|
|
color: tab.panel.dim
|
|
font.family: tab.panel.fontFamily
|
|
font.pixelSize: Style.font.caption
|
|
font.letterSpacing: 1
|
|
}
|
|
Text {
|
|
textFormat: Text.PlainText
|
|
anchors.right: parent.right
|
|
text: {
|
|
var l = tab.live
|
|
if (!l || !l.total || l.total.p50 === null) return ""
|
|
return "p50 " + Math.round(l.total.p50) + " ms · p95 "
|
|
+ Math.round(l.total.p95) + " ms · jitter "
|
|
+ (l.total.jitter !== null ? l.total.jitter.toFixed(1) : "--") + " ms"
|
|
}
|
|
color: tab.panel.dim
|
|
font.family: tab.panel.fontFamily
|
|
font.pixelSize: Style.font.caption
|
|
}
|
|
}
|
|
|
|
LegChart {
|
|
width: parent.width
|
|
height: Style.space(96)
|
|
points: tab.panel.recentPoints
|
|
wanColor: Color.accent
|
|
localColor: tab.panel.dim
|
|
showScale: true
|
|
fontFamily: tab.panel.fontFamily
|
|
}
|
|
|
|
Row {
|
|
spacing: Style.space(16)
|
|
|
|
component LegendEntry: Row {
|
|
property color tint: "white"
|
|
property string label: ""
|
|
property bool tick: false
|
|
spacing: Style.space(6)
|
|
Rectangle {
|
|
width: parent.tick ? 2 : Style.space(10)
|
|
height: parent.tick ? Style.space(8) : 2
|
|
color: parent.tint
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
Text {
|
|
textFormat: Text.PlainText
|
|
text: parent.label
|
|
color: tab.panel.dim
|
|
font.family: tab.panel.fontFamily
|
|
font.pixelSize: Style.font.caption
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
}
|
|
|
|
LegendEntry { tint: Color.accent; label: "wan leg" }
|
|
LegendEntry { tint: tab.panel.dim; label: "local leg" }
|
|
LegendEntry { tint: Color.urgent; label: "packet loss"; tick: true }
|
|
}
|
|
|
|
PanelSeparator { width: parent.width }
|
|
|
|
// Speed strip + the run button.
|
|
Item {
|
|
width: parent.width
|
|
height: speedCol.implicitHeight
|
|
|
|
Column {
|
|
id: speedCol
|
|
spacing: Style.space(4)
|
|
|
|
Text {
|
|
textFormat: Text.PlainText
|
|
text: "THROUGHPUT NOW"
|
|
color: tab.panel.dim
|
|
font.family: tab.panel.fontFamily
|
|
font.pixelSize: Style.font.caption
|
|
font.letterSpacing: 1
|
|
}
|
|
Row {
|
|
spacing: Style.space(14)
|
|
|
|
Text {
|
|
textFormat: Text.PlainText
|
|
text: {
|
|
var r = tab.live && tab.live.rates ? tab.live.rates.rx_bps : null
|
|
return " " + Fmt.rate(r)
|
|
}
|
|
color: Color.accent
|
|
font.family: tab.panel.fontFamily
|
|
font.pixelSize: Style.font.subtitle
|
|
}
|
|
Text {
|
|
textFormat: Text.PlainText
|
|
text: {
|
|
var r = tab.live && tab.live.rates ? tab.live.rates.tx_bps : null
|
|
return " " + Fmt.rate(r)
|
|
}
|
|
color: tab.panel.warnTone
|
|
font.family: tab.panel.fontFamily
|
|
font.pixelSize: Style.font.subtitle
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
width: runLabel.implicitWidth + Style.space(24)
|
|
height: Style.space(28)
|
|
color: runHover.hovered
|
|
? Style.hoverFillFor(tab.panel.fg, Color.accent)
|
|
: Style.normalFillFor(tab.panel.fg, Color.accent)
|
|
border.width: Style.normalBorderWidth
|
|
border.color: Style.normalBorderFor(tab.panel.fg, Color.accent)
|
|
|
|
Text {
|
|
id: runLabel
|
|
textFormat: Text.PlainText
|
|
anchors.centerIn: parent
|
|
text: tab.live && tab.live.peak_running ? " Testing…"
|
|
: tab.panel.peakArmed ? " Uses data · press again"
|
|
: " Run test"
|
|
color: tab.panel.fg
|
|
font.family: tab.panel.fontFamily
|
|
font.pixelSize: Style.font.bodySmall
|
|
}
|
|
HoverHandler { id: runHover }
|
|
TapHandler {
|
|
enabled: !(tab.live && tab.live.peak_running)
|
|
onTapped: tab.panel.runPeakTest()
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|