repositories / dotfiles
dotfiles
bugabingas dorkfiles
owned by admin
quickshell/nuguland/Osd.qml
Rawimport QtQuick
Item {
id: root
property bool visibleState: false
property bool expired: true
property string currentEvent: ""
property string currentValue: ""
property string currentRole: "info"
property int restartCount: 0
readonly property int lifetimeMs: 1200
readonly property var supportedEvents: ["volume", "microphone", "brightness", "media", "workspace", "screenshot", "color", "error"]
function roleFor(event, value) {
const name = String(event);
const text = String(value);
if (name === "error")
return "error";
if (name === "microphone" && (text === "muted" || text === "active"))
return "warning";
if (name === "volume" || name === "brightness")
return "ui.accent";
return "info";
}
function isNumeric(value) {
const text = String(value).replace("%", "").trim();
return text.length > 0 && isFinite(Number(text));
}
// 0..1 for the meter-style events (volume/brightness/microphone) when the
// value is a real number; -1 otherwise (mute toggles, media, color, error)
// so the bar is hidden rather than drawn empty.
function normalizedValue(event, value) {
const name = String(event);
if (name !== "volume" && name !== "brightness" && name !== "microphone")
return -1;
if (!isNumeric(value))
return -1;
return Math.max(0, Math.min(1, Number(String(value).replace("%", "")) / 100));
}
// freedesktop icon name for the event; resolved to a path at render time
// via Quickshell.iconPath(). All names verified present in the Adwaita theme.
function iconName(event, value) {
const name = String(event);
const text = String(value);
const muted = text === "muted" || (isNumeric(text) && Number(text.replace("%", "")) <= 0);
if (name === "volume")
return muted ? "audio-volume-muted" : (Number(text.replace("%", "")) < 34 ? "audio-volume-low" : (Number(text.replace("%", "")) < 67 ? "audio-volume-medium" : "audio-volume-high"));
if (name === "microphone")
return muted ? "microphone-sensitivity-muted" : "microphone-sensitivity-high";
if (name === "brightness")
return "display-brightness-symbolic";
if (name === "media")
return text.indexOf("pause") >= 0 ? "media-playback-pause" : "media-playback-start";
if (name === "screenshot")
return "applets-screenshooter";
if (name === "color")
return "color-picker";
if (name === "error")
return "dialog-error";
return "dialog-information";
}
function displayLabel(event) {
const name = String(event);
const labels = {
"volume": "Volume",
"microphone": "Microphone",
"brightness": "Brightness",
"media": "Media",
"screenshot": "Screenshot",
"color": "Color",
"error": "Error"
};
return labels[name] || (name.length > 0 ? name : "OSD");
}
// right-aligned glance value: "72%" for meters, a word for toggles, the
// raw payload (media event / colour hex) otherwise. Errors render their
// message in the body line instead, so the corner stays empty.
function displayValue(event, value) {
const name = String(event);
const text = String(value);
const normalized = normalizedValue(name, text);
if (normalized >= 0)
return `${Math.round(normalized * 100)}%`;
if (text === "muted")
return "muted";
if (text === "unmuted")
return "on";
if (name === "error")
return "";
return text;
}
function iconRole(event, value) {
const name = String(event);
const text = String(value);
if (name === "volume")
return Number(text) <= 0 ? "audio-muted" : "audio-volume";
if (name === "microphone")
return text === "muted" ? "microphone-muted" : "microphone-active";
if (name === "brightness")
return "brightness";
if (name === "screenshot")
return "screenshot";
if (name === "color")
return "color-picker";
if (name === "error")
return "danger";
return name.length > 0 ? name : "info";
}
function numericText(event, value) {
const normalized = normalizedValue(event, value);
return normalized >= 0 ? `${Math.round(normalized * 100)}` : "";
}
function valueIndicator(event, value) {
const name = String(event);
const text = String(value);
const normalized = normalizedValue(name, text);
const hasProgress = normalized >= 0;
return {
"source": "Nuguland OsdValueIndicator.qml plus Nuguland OSD sliders",
"layout": "icon-left-value-right",
"iconRole": iconRole(name, text),
"iconName": iconName(name, text),
"displayLabel": displayLabel(name),
"displayValue": displayValue(name, text),
"value": text,
"normalizedValue": hasProgress ? normalized : null,
"progressBar": {
"visible": hasProgress,
"from": 0,
"to": 1,
"value": hasProgress ? normalized : 0,
"role": currentRole
},
"numericText": {
"text": numericText(name, text),
"odometerMotion": hasProgress,
"hardSeparator": hasProgress ? "%" : ""
},
"slideFromEdge": {
"edge": "right",
"hiddenOffset": "width+sidebar-gap",
"visibleOffset": 0
},
"wheelActions": name === "volume" ? ["volume-up", "volume-down"] : name === "brightness" ? ["brightness-up", "brightness-down"] : [],
"protectionMessage": {
"visible": name === "error",
"role": "error",
"text": name === "error" ? text : ""
},
"hardSeparator": hasProgress ? "%" : ""
};
}
function stateObject() {
return {
"visible": visibleState,
"expired": expired,
"event": currentEvent,
"value": currentValue,
"role": currentRole,
"iconName": iconName(currentEvent, currentValue),
"displayLabel": displayLabel(currentEvent),
"displayValue": displayValue(currentEvent, currentValue),
"lifetimeMs": lifetimeMs,
"restartCount": restartCount,
"supportedEvents": supportedEvents,
"surface": "masked-overlay",
"blocksInput": false,
"reservesSpace": false,
"valueIndicator": valueIndicator(currentEvent, currentValue)
};
}
function stateJson() {
return JSON.stringify(stateObject());
}
function trigger(event, value) {
const name = supportedEvents.indexOf(String(event)) >= 0 ? String(event) : "error";
currentEvent = name;
currentValue = String(value);
currentRole = roleFor(name, currentValue);
visibleState = true;
expired = false;
restartCount += 1;
expiryTimer.restart();
return stateJson();
}
function expire() {
expiryTimer.stop();
visibleState = false;
expired = true;
return stateJson();
}
Timer {
id: expiryTimer
interval: root.lifetimeMs
repeat: false
onTriggered: root.expire()
}
}