Fork the desktop off Omarchy as a self-contained system

This commit is contained in:
2026-09-19 23:50:39 -04:00
parent 16a56f49a1
commit 9501f2bb4d
559 changed files with 43273 additions and 0 deletions
+206
View File
@@ -0,0 +1,206 @@
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import qs.Commons
import qs.Ui
import "OsdModel.js" as OsdModel
Item {
id: root
property bool opened: false
property string icon: ""
property string message: ""
property string iconKey: ""
property int value: 0
property int maxValue: 100
property bool hasProgress: true
property int duration: 1200
readonly property bool mediaOsd: iconKey.indexOf("media") === 0 || iconKey.indexOf("player") === 0
// The card is built out of measured columns instead of fixed widths, so it
// keeps exactly `pad` between border and content on every side whatever
// glyph or message it carries. Messages grow with their text up to
// `maxMessageWidth` and elide beyond it.
readonly property int pad: Style.space(16)
readonly property int gap: Style.space(16)
// A glyph next to a message reads airier than it measures: the icon outline
// and the letterforms both fall away from their ink extremes, so the space
// between them opens up well past the nominal gap. Text takes two thirds of
// it; the progress bar's hard edge keeps the full gap.
readonly property int messageGap: Math.round(root.gap * 2 / 3)
readonly property int barWidth: Style.space(142)
readonly property int maxMessageWidth: root.mediaOsd ? Style.space(325) : Style.space(190)
// Nerd Font glyphs draw well outside their monospace cell, so the icon
// column is measured by ink rather than by advance width. Progress OSDs pin
// it to the widest glyph the model can return, so the bar doesn't shift when
// volume crosses an icon threshold.
readonly property int iconInkWidth: Math.ceil(iconMetrics.tightBoundingRect.width)
readonly property int iconWidth: root.hasProgress
? Math.max(root.iconInkWidth, Math.ceil(widestIconMetrics.tightBoundingRect.width))
: root.iconInkWidth
// Same idea for the readout: it is as wide as the longest percentage so the
// digits don't jitter between 9% and 100%.
readonly property int valueWidth: Math.ceil(Math.max(valueMetrics.advanceWidth, messageMetrics.advanceWidth))
readonly property int messageWidth: Math.min(Math.ceil(messageMetrics.advanceWidth), root.maxMessageWidth)
readonly property int contentWidth: root.hasProgress
? root.iconWidth + root.gap + root.barWidth + root.gap + root.valueWidth
: (root.message === "" ? root.iconWidth : root.iconWidth + root.messageGap + root.messageWidth)
function iconFor(name, percent) {
return OsdModel.iconFor(name, percent)
}
function show(iconName, rawMessage, rawValue, rawMax, rawProgressText, rawDuration) {
var next = OsdModel.stateForShow(iconName, rawMessage, rawValue, rawMax, rawProgressText, rawDuration)
// Update before opening so a fresh OSD starts at its new value; only
// subsequent updates while it remains open animate the progress bar.
iconKey = next.iconKey
maxValue = next.maxValue
hasProgress = next.hasProgress
value = next.value
message = next.message
icon = next.icon
duration = next.duration
opened = true
if (duration > 0) hideTimer.restart()
else hideTimer.stop()
}
function open(payloadJson) {
try {
var p = JSON.parse(payloadJson || "{}")
show(p.icon || "", p.message || "", p.value === undefined ? "" : String(p.value), p.max === undefined ? "100" : String(p.max), p.progressText || "", p.duration === undefined ? "1200" : String(p.duration))
} catch (e) {}
}
function close() { opened = false }
Timer {
id: hideTimer
interval: root.duration
onTriggered: root.opened = false
}
TextMetrics {
id: messageMetrics
font.family: Style.font.family
font.bold: true
font.pixelSize: Style.font.title
text: root.message
}
TextMetrics {
id: valueMetrics
font: messageMetrics.font
text: "100%"
}
TextMetrics {
id: iconMetrics
font.family: Style.font.family
font.pixelSize: Style.font.displayLarge
text: root.icon
}
TextMetrics {
id: widestIconMetrics
font: iconMetrics.font
text: OsdModel.widestIcon
}
IpcHandler {
target: "osd"
function show(payloadJson: string): string {
root.open(payloadJson)
return "ok"
}
function close(): string { root.close(); return "ok" }
function state(): string { return root.opened ? "open" : "closed" }
function ping(): string { return "ok" }
}
PanelWindow {
id: panel
visible: root.opened
anchors { top: true; bottom: true; left: true; right: true }
color: "transparent"
WlrLayershell.namespace: "blob-osd"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
exclusionMode: ExclusionMode.Ignore
// Visual-only surface: keep the layer-shell input region empty so the OSD
// never blocks clicks to the desktop below it.
mask: Region {}
BorderSurface {
id: card
width: card.borderLeft + root.pad + root.contentWidth + root.pad + card.borderRight
height: card.borderTop + root.pad + Style.font.displayLarge + root.pad + card.borderBottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: Style.space(67)
color: Util.alpha(Color.background, 0.97)
borderSpec: Border.surfaceSpec("popups", "border", Color.popups.border, Math.max(1, Style.space(2)))
radius: Style.cornerRadius
opacity: root.opened ? 1 : 0
Row {
anchors.fill: parent
anchors.topMargin: card.borderTop + root.pad
anchors.rightMargin: card.borderRight + root.pad
anchors.bottomMargin: card.borderBottom + root.pad
anchors.leftMargin: card.borderLeft + root.pad
spacing: root.hasProgress ? root.gap : root.messageGap
Item {
width: root.iconWidth
height: parent.height
Text {
textFormat: Text.PlainText
// Sit the glyph's ink flush in the column, centered when the
// column is wider than this particular glyph.
x: Math.round((root.iconWidth - root.iconInkWidth) / 2 - iconMetrics.tightBoundingRect.x)
anchors.verticalCenter: parent.verticalCenter
text: root.icon
font: iconMetrics.font
color: Color.popups.text
}
}
Rectangle {
visible: root.hasProgress
width: root.barWidth
height: Math.max(Style.space(6), Style.spacing.sm)
anchors.verticalCenter: parent.verticalCenter
color: Util.alpha(Color.popups.text, 0.45)
Rectangle {
height: parent.height
width: parent.width * (root.hasProgress ? root.value / root.maxValue : 0)
color: Color.accent
Behavior on width {
enabled: root.opened
NumberAnimation { duration: 140; easing.type: Easing.OutCubic }
}
}
}
Text {
textFormat: Text.PlainText
visible: root.message !== ""
width: root.hasProgress ? root.valueWidth : root.messageWidth
// The readout hugs the card edge so a short percentage doesn't leave
// a hole in the padding; the slack lands in the gap after the bar.
horizontalAlignment: root.hasProgress ? Text.AlignRight : Text.AlignLeft
anchors.verticalCenter: parent.verticalCenter
text: root.message
font: messageMetrics.font
color: Color.popups.text
elide: Text.ElideRight
maximumLineCount: 1
}
}
}
}
}
+62
View File
@@ -0,0 +1,62 @@
function clamp(value, min, max) {
return Math.max(min, Math.min(max, value))
}
// The widest glyph `iconFor` can return. The progress OSD sizes its icon
// column to it so the bar keeps its place as the icon changes.
var widestIcon = ""
function iconFor(name, percent) {
var n = String(name || "").toLowerCase()
if (n === "volume-muted" || n === "volume-mute" || n === "muted" || n === "mute") return ""
if (n === "volume-low") return ""
if (n === "volume-medium") return ""
if (n === "volume-high" || n === "volume") return ""
if (n === "microphone-muted" || n === "microphone-off" || n === "mic-muted" || n === "mic-off") return "󰍭"
if (n === "microphone" || n === "mic") return "󰍬"
if (n === "keyboard") return "󰌌"
if (n === "brightness" || n === "display") return "󰍹"
if (n === "touchpad") return "󰟸"
if (n === "touch" || n === "touchscreen") return "󰝁"
if (n === "reboot" || n === "restart") return "󰜉"
if (n === "shutdown" || n === "power" || n === "poweroff") return "󰐥"
if (n === "logout" || n === "sign-out" || n === "leave") return "󰍃"
if (n === "media" || n === "player") return "󰝚"
if (n === "media-source" || n === "player-source") return "󰝚"
if (n === "media-play" || n === "player-play") return "󰐊"
if (n === "media-pause" || n === "player-pause") return "󰏤"
if (n === "media-next" || n === "player-next") return "󰒭"
if (n === "media-previous" || n === "player-previous") return "󰒮"
if (n.length > 0) return name
if (percent <= 0) return ""
if (percent <= 33) return ""
if (percent <= 66) return ""
return ""
}
function stateForShow(iconName, rawMessage, rawValue, rawMax, rawProgressText, rawDuration) {
var maxValue = Math.max(1, parseInt(rawMax || "100", 10))
var parsedValue = parseInt(rawValue || "0", 10)
var hasProgress = rawValue !== "" && !isNaN(parsedValue) && rawMessage === ""
var value = hasProgress ? clamp(parsedValue, 0, maxValue) : 0
var percent = hasProgress ? Math.round(value * 100 / maxValue) : -1
var parsedDuration = parseInt(rawDuration || "1200", 10)
return {
iconKey: String(iconName || "").toLowerCase(),
maxValue: maxValue,
hasProgress: hasProgress,
value: value,
message: String(rawMessage || (hasProgress ? (rawProgressText || percent + "%") : "")),
icon: iconFor(iconName, percent),
duration: isNaN(parsedDuration) ? 1200 : Math.max(0, parsedDuration)
}
}
if (typeof module !== "undefined") {
module.exports = {
widestIcon: widestIcon,
iconFor: iconFor,
stateForShow: stateForShow
}
}
+14
View File
@@ -0,0 +1,14 @@
{
"schemaVersion": 1,
"id": "blob.osd",
"name": "On-screen display",
"version": "1.0.0",
"description": "Quickshell volume, brightness, and status overlays.",
"kinds": [
"panel"
],
"keepLoaded": true,
"entryPoints": {
"panel": "Osd.qml"
}
}