Integrate swaybg and swayosd into the new shell

This commit is contained in:
David Heinemeier Hansson
2026-05-16 20:34:09 +02:00
parent 83e7f41308
commit 1eab0cffc9
48 changed files with 665 additions and 353 deletions
@@ -0,0 +1,259 @@
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import QtQuick
Item {
id: root
property string omarchyPath: ""
property var shell: null
property var manifest: null
readonly property string home: Quickshell.env("HOME")
readonly property string currentBackgroundLink: home + "/.config/omarchy/current/background"
property string currentBackground: ""
property string displayedBackground: ""
property string incomingBackground: ""
property string oldBackground: ""
property bool finishingTransition: false
property int backgroundVersion: 0
property real revealProgress: 1
function imageUrl(path) {
if (!path) return ""
return "file://" + path
}
function refreshBackground() {
if (!readlinkProc.running) readlinkProc.running = true
}
function setBackground(path) {
path = String(path || "").trim()
if (!path || path === currentBackground) return
currentBackground = path
backgroundVersion += 1
if (!displayedBackground) {
displayedBackground = path
revealProgress = 1
return
}
revealAnimation.stop()
finishingTransition = false
oldBackground = displayedBackground
incomingBackground = path
revealProgress = 0
}
function openSelector() {
if (!bgSwitchProc.running) bgSwitchProc.running = true
}
Process {
id: bgSwitchProc
command: ["bash", "-lc", "background=$(omarchy-theme-bg-switcher); [[ -n $background ]] && omarchy-theme-bg-set \"$background\""]
onExited: root.refreshBackground()
}
Process {
id: readlinkProc
command: ["readlink", "-f", root.currentBackgroundLink]
stdout: StdioCollector {
onStreamFinished: root.setBackground(String(text || "").trim())
}
}
Timer {
interval: 100
running: true
repeat: true
onTriggered: root.refreshBackground()
}
NumberAnimation {
id: revealAnimation
target: root
property: "revealProgress"
from: 0
to: 1
duration: 420
easing.type: Easing.InOutCubic
onFinished: {
if (root.incomingBackground) {
root.displayedBackground = root.incomingBackground
root.finishingTransition = true
}
root.revealProgress = 1
}
}
Component.onCompleted: refreshBackground()
Variants {
model: Quickshell.screens
PanelWindow {
id: panel
required property var modelData
screen: modelData
visible: true
anchors { top: true; bottom: true; left: true; right: true }
color: "transparent"
property bool maskReady: false
WlrLayershell.namespace: "omarchy-background"
WlrLayershell.layer: WlrLayer.Background
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
exclusionMode: ExclusionMode.Ignore
Image {
id: base
anchors.fill: parent
source: root.imageUrl(root.displayedBackground)
fillMode: Image.PreserveAspectCrop
asynchronous: true
cache: false
onStatusChanged: {
if (status === Image.Ready && root.finishingTransition) {
root.incomingBackground = ""
root.oldBackground = ""
root.finishingTransition = false
}
}
}
Image {
id: incomingFrame
anchors.fill: parent
source: root.imageUrl(root.incomingBackground)
fillMode: Image.PreserveAspectCrop
asynchronous: true
cache: false
visible: root.incomingBackground !== "" && status === Image.Ready
opacity: root.revealProgress >= 1 ? 1 : 0.001
onStatusChanged: if (status === Image.Ready && root.incomingBackground) revealCanvas.prepareImage()
}
Image {
id: oldFrame
anchors.fill: parent
source: root.imageUrl(root.oldBackground)
fillMode: Image.PreserveAspectCrop
asynchronous: true
cache: false
visible: root.oldBackground !== "" && root.revealProgress < 1
onStatusChanged: if (status === Image.Ready && root.incomingBackground) revealCanvas.requestPaint()
}
Canvas {
id: revealCanvas
anchors.fill: parent
visible: root.incomingBackground !== "" && root.revealProgress < 1 && incomingFrame.status === Image.Ready && oldFrame.status === Image.Ready
opacity: panel.maskReady ? 1 : 0
renderTarget: Canvas.FramebufferObject
renderStrategy: Canvas.Immediate
readonly property real slant: -0.18
function prepareImage() {
var src = root.imageUrl(root.incomingBackground)
if (!src) return
if (isImageLoaded(src)) {
requestPaint()
} else if (!isImageLoading(src)) {
loadImage(src)
}
}
onImageLoaded: function(url) {
if (url === root.imageUrl(root.incomingBackground)) requestPaint()
}
onPaint: {
var ctx = getContext("2d")
ctx.reset()
ctx.clearRect(0, 0, width, height)
var src = root.imageUrl(root.incomingBackground)
if (!src || incomingFrame.status !== Image.Ready || root.revealProgress >= 1) return
if (!isImageLoaded(src)) {
prepareImage()
return
}
var iw = incomingFrame.sourceSize.width
var ih = incomingFrame.sourceSize.height
if (iw <= 0 || ih <= 0 || width <= 0 || height <= 0) return
var sx = 0
var sy = 0
var sw = iw
var sh = ih
if (iw / ih > width / height) {
sw = ih * width / height
sx = (iw - sw) / 2
} else {
sh = iw * height / width
sy = (ih - sh) / 2
}
var center = width / 2
var reach = width / 2 + Math.abs(slant) * height + 4
var spread = reach * root.revealProgress
var leftTop = center - spread
var leftBottom = center - spread + slant * height
var rightTop = center + spread
var rightBottom = center + spread + slant * height
ctx.save()
ctx.beginPath()
ctx.moveTo(center, 0)
ctx.lineTo(leftTop, 0)
ctx.lineTo(leftBottom, height)
ctx.lineTo(center, height)
ctx.closePath()
ctx.clip()
ctx.drawImage(src, sx, sy, sw, sh, 0, 0, width, height)
ctx.restore()
ctx.save()
ctx.beginPath()
ctx.moveTo(center, 0)
ctx.lineTo(rightTop, 0)
ctx.lineTo(rightBottom, height)
ctx.lineTo(center, height)
ctx.closePath()
ctx.clip()
ctx.drawImage(src, sx, sy, sw, sh, 0, 0, width, height)
ctx.restore()
if (!panel.maskReady && root.incomingBackground && root.revealProgress === 0) {
Qt.callLater(function() {
panel.maskReady = true
revealAnimation.restart()
})
}
}
Connections {
target: root
function onRevealProgressChanged() { revealCanvas.requestPaint() }
function onIncomingBackgroundChanged() {
panel.maskReady = false
revealCanvas.prepareImage()
}
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onDoubleClicked: root.openSelector()
}
}
}
}
@@ -0,0 +1,11 @@
{
"schemaVersion": 1,
"id": "omarchy.background",
"name": "Background",
"version": "1.0.0",
"author": "Omarchy",
"description": "Desktop background renderer with click handling and transitions",
"kinds": ["service"],
"activation": "startup",
"entryPoints": { "service": "Background.qml" }
}
@@ -0,0 +1,140 @@
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import qs.Commons
Item {
id: root
property string omarchyPath: ""
property var shell: null
property var manifest: null
property bool opened: false
property string icon: ""
property string message: ""
property int value: 0
property int maxValue: 100
property bool hasProgress: true
function clamp(v, min, max) { return Math.max(min, Math.min(max, v)) }
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 === "media" || n === "player") return "󰝚"
if (percent <= 0) return "󰝟"
if (percent <= 33) return ""
if (percent <= 66) return ""
return ""
}
function show(iconName, rawMessage, rawValue, rawMax, rawProgressText) {
maxValue = Math.max(1, parseInt(rawMax || "100", 10))
var parsed = parseInt(rawValue || "0", 10)
hasProgress = rawValue !== "" && !isNaN(parsed) && rawMessage === ""
value = hasProgress ? clamp(parsed, 0, maxValue) : 0
message = String(rawMessage || (hasProgress ? (rawProgressText || Math.round(value * 100 / maxValue) + "%") : ""))
icon = iconFor(iconName, hasProgress ? Math.round(value * 100 / maxValue) : -1)
opened = true
hideTimer.restart()
}
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 || "")
} catch (e) {}
}
function close() { opened = false }
Timer {
id: hideTimer
interval: 1200
onTriggered: root.opened = false
}
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: "omarchy-osd"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
exclusionMode: ExclusionMode.Ignore
Rectangle {
id: card
width: 269
height: 68
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 67
color: Color.alpha(Color.background, 0.97)
border.color: Color.foreground
border.width: 2
radius: 0
opacity: root.opened ? 1 : 0
Row {
anchors.fill: parent
anchors.leftMargin: 16
anchors.rightMargin: 16
spacing: 16
Text {
width: 28
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignHCenter
text: root.icon
font.family: "JetBrainsMono Nerd Font"
font.pixelSize: 27
color: Color.foreground
}
Rectangle {
visible: root.hasProgress
width: visible ? 142 : 0
height: 6
anchors.verticalCenter: parent.verticalCenter
color: Color.alpha(Color.foreground, 0.45)
Rectangle {
height: parent.height
width: parent.width * (root.hasProgress ? root.value / root.maxValue : 0)
color: Color.accent
}
}
Text {
width: root.hasProgress ? 41 : 190
anchors.verticalCenter: parent.verticalCenter
text: root.message
font.family: "JetBrainsMono Nerd Font"
font.bold: true
font.pixelSize: 14
color: Color.foreground
}
}
}
}
}
@@ -0,0 +1,11 @@
{
"schemaVersion": 1,
"id": "omarchy.osd",
"name": "On-screen display",
"version": "1.0.0",
"description": "Quickshell volume, brightness, and status overlays.",
"kinds": ["panel"],
"activation": "persistent",
"keepLoaded": true,
"entryPoints": { "panel": "Osd.qml" }
}