Add a no-animations mode, on by default in VMs (#13550)

A machine without a GPU, like most VMs, renders through llvmpipe on the
CPU, where every animated frame and every translucent window costs. In a
VM, opening and closing a terminal took ~5s of CPU; a panel ~3.4s.

omarchy toggle animations (also under Toggle > Animations) places a Hyprland
flag that turns off animations, blur and shadows and makes windows opaque.
The shell follows Hyprland's animations:enabled, rereading it on every
config reload: its one-shot animations run for Style.duration(ms), which
is then 0, and its spinners, pulses and title marquee hold still. A new
install in a VM starts with the flag in place.

Measured in the ISO test VM (llvmpipe), CPU per interaction:
terminal open+close 5000ms -> 481ms, workspace switch 1670ms -> 409ms,
audio panel 3446ms -> 796ms, volume OSD 2324ms -> 584ms, menu 2028ms ->
1241ms.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
David Heinemeier HanssonandClaude Opus 5.5 authored and GitHub committed 2026-09-28 15:46:05 +02:00
1 parent 97a9fce54f
commit b18ab4952b
41 files changed
+201 -79

No files matched your search

+3 -3
View File
@@ -788,7 +788,7 @@ Panel {
color: meter.alarming ? root.urgent : root.foreground
Behavior on width {
NumberAnimation { duration: 160; easing.type: Easing.OutCubic }
NumberAnimation { duration: Style.duration(160); easing.type: Easing.OutCubic }
}
}
@@ -837,7 +837,7 @@ Panel {
color: dayRow.today ? root.foreground : root.alpha(root.foreground, 0.55)
Behavior on width {
NumberAnimation { duration: 160; easing.type: Easing.OutCubic }
NumberAnimation { duration: Style.duration(160); easing.type: Easing.OutCubic }
}
}
}
@@ -894,7 +894,7 @@ Panel {
color: root.alpha(root.foreground, 0.14)
Behavior on width {
NumberAnimation { duration: 160; easing.type: Easing.OutCubic }
NumberAnimation { duration: Style.duration(160); easing.type: Easing.OutCubic }
}
}
+1 -1
View File
@@ -258,7 +258,7 @@ Item {
property: "revealProgress"
from: 0
to: 1
duration: 420
duration: Style.duration(420)
easing.type: Easing.InOutCubic
onFinished: {
if (root.incomingBackground) {
+5 -5
View File
@@ -80,9 +80,9 @@ Item {
property color background: Color.bar.background
property color urgent: Color.bar.active
Behavior on barForeground { enabled: root.foregroundAnimationEnabled; ColorAnimation { duration: 420; easing.type: Easing.InOutCubic } }
Behavior on background { ColorAnimation { duration: 420; easing.type: Easing.InOutCubic } }
Behavior on urgent { ColorAnimation { duration: 420; easing.type: Easing.InOutCubic } }
Behavior on barForeground { enabled: root.foregroundAnimationEnabled; ColorAnimation { duration: Style.duration(420); easing.type: Easing.InOutCubic } }
Behavior on background { ColorAnimation { duration: Style.duration(420); easing.type: Easing.InOutCubic } }
Behavior on urgent { ColorAnimation { duration: Style.duration(420); easing.type: Easing.InOutCubic } }
property var tooltipTarget: null
property var pendingTooltipTarget: null
property string tooltipText: ""
@@ -1509,7 +1509,7 @@ Item {
opacity: root.barMoveCandidate === modelData ? (root.transparent ? 0.45 : 0.7) : 0
Behavior on opacity {
NumberAnimation { duration: 140; easing.type: Easing.OutCubic }
NumberAnimation { duration: Style.duration(140); easing.type: Easing.OutCubic }
}
}
}
@@ -1895,7 +1895,7 @@ Item {
z: 50
Behavior on opacity {
NumberAnimation { duration: 120; easing.type: Easing.OutCubic }
NumberAnimation { duration: Style.duration(120); easing.type: Easing.OutCubic }
}
}
+1 -1
View File
@@ -18,7 +18,7 @@ BarWidget {
implicitHeight: barSize
Behavior on implicitWidth {
NumberAnimation { duration: 180; easing.type: Easing.OutCubic }
NumberAnimation { duration: Style.duration(180); easing.type: Easing.OutCubic }
}
Item {
+1 -1
View File
@@ -216,7 +216,7 @@ BarWidget {
implicitHeight: root.vertical ? trayContent.implicitHeight : root.barSize
Behavior on revealProgress {
NumberAnimation { duration: root.animationDuration; easing.type: Easing.OutCubic }
NumberAnimation { duration: Style.duration(root.animationDuration); easing.type: Easing.OutCubic }
}
Loader {
@@ -207,7 +207,7 @@ BorderSurface {
visible: opacity > 0
opacity: root.hovered ? 1 : 0
Behavior on opacity { NumberAnimation { duration: 100 } }
Behavior on opacity { NumberAnimation { duration: Style.duration(100) } }
Text {
anchors.centerIn: parent
+1 -1
View File
@@ -178,7 +178,7 @@ Item {
Behavior on width {
enabled: root.opened
NumberAnimation { duration: 140; easing.type: Easing.OutCubic }
NumberAnimation { duration: Style.duration(140); easing.type: Easing.OutCubic }
}
}
}
+1 -1
View File
@@ -944,7 +944,7 @@ Panel {
height: parent.height
width: parent.width * Math.max(0, Math.min(1, inputPeakMonitor.peak))
color: root.bar.foreground
Behavior on width { NumberAnimation { duration: 70 } }
Behavior on width { NumberAnimation { duration: Style.duration(70) } }
}
}
}
+2 -2
View File
@@ -603,14 +603,14 @@ Panel {
id: phraseSwap
PropertyAnimation {
target: heroStatus; property: "opacity"
to: 0.0; duration: 180; easing.type: Easing.OutQuad
to: 0.0; duration: Style.duration(180); easing.type: Easing.OutQuad
}
ScriptAction {
script: root.phraseIndex = (root.phraseIndex + 1) % root.activePhrases.length
}
PropertyAnimation {
target: heroStatus; property: "opacity"
to: 1.0; duration: 260; easing.type: Easing.InQuad
to: 1.0; duration: Style.duration(260); easing.type: Easing.InQuad
}
}
+2 -2
View File
@@ -457,7 +457,7 @@ Panel {
radius: parent.radius
color: Style.selectedStateColor(root.contentForeground, Color.accent)
Behavior on width { NumberAnimation { duration: 160; easing.type: Easing.OutCubic } }
Behavior on width { NumberAnimation { duration: Style.duration(160); easing.type: Easing.OutCubic } }
}
}
}
@@ -515,7 +515,7 @@ Panel {
radius: parent.radius
color: Style.selectedStateColor(root.contentForeground, Color.accent)
Behavior on width { NumberAnimation { duration: 160; easing.type: Easing.OutCubic } }
Behavior on width { NumberAnimation { duration: Style.duration(160); easing.type: Easing.OutCubic } }
}
}
+2 -2
View File
@@ -369,14 +369,14 @@ Panel {
id: phraseSwap
PropertyAnimation {
target: hero; property: "metaOpacity"
to: 0.0; duration: 180; easing.type: Easing.OutQuad
to: 0.0; duration: Style.duration(180); easing.type: Easing.OutQuad
}
ScriptAction {
script: root.phraseIndex = (root.phraseIndex + 1) % root.activePhrases.length
}
PropertyAnimation {
target: hero; property: "metaOpacity"
to: 1.0; duration: 260; easing.type: Easing.InQuad
to: 1.0; duration: Style.duration(260); easing.type: Easing.InQuad
}
}
+5 -5
View File
@@ -79,13 +79,13 @@ Rectangle {
// The dragged row follows the pointer one-to-one; the others ease aside.
Behavior on y {
enabled: !row.dragged
NumberAnimation { duration: 130; easing.type: Easing.OutCubic }
NumberAnimation { duration: Style.duration(130); easing.type: Easing.OutCubic }
}
}
]
z: dragged ? 2 : 0
opacity: dragged ? 0.9 : 1
Behavior on opacity { NumberAnimation { duration: 120 } }
Behavior on opacity { NumberAnimation { duration: Style.duration(120) } }
HoverHandler { id: rowHover }
@@ -219,7 +219,7 @@ Rectangle {
spacing: Style.spacing.sm
opacity: parent.greeting ? 0 : 1
visible: opacity > 0
Behavior on opacity { NumberAnimation { duration: 110 } }
Behavior on opacity { NumberAnimation { duration: Style.duration(110) } }
Caption {
text: row.ready ? row.rowData.date : "\u2026"
@@ -242,7 +242,7 @@ Rectangle {
spacing: Style.space(5)
opacity: parent.greeting ? 1 : 0
visible: opacity > 0
Behavior on opacity { NumberAnimation { duration: 110 } }
Behavior on opacity { NumberAnimation { duration: Style.duration(110) } }
// Non-Latin scripts fall back per character through fontconfig.
Caption {
@@ -304,7 +304,7 @@ Rectangle {
// Shown on the keyboard's pick too, so Delete's target is plain.
opacity: row.removable && row.lit ? 1 : 0
visible: opacity > 0
Behavior on opacity { NumberAnimation { duration: 120 } }
Behavior on opacity { NumberAnimation { duration: Style.duration(120) } }
onClicked: row.panel.removeCityAt(row.index)
}
@@ -115,7 +115,7 @@ Item {
color: strip.foreground
}
Behavior on x { NumberAnimation { duration: 400; easing.type: Easing.OutCubic } }
Behavior on x { NumberAnimation { duration: Style.duration(400); easing.type: Easing.OutCubic } }
}
}
@@ -182,7 +182,7 @@ Item {
// Gone rather than faded, so a hidden arrow is also untappable.
opacity: strip.hovered && !arrow.covered ? 1 : 0
visible: opacity > 0
Behavior on opacity { NumberAnimation { duration: 160 } }
Behavior on opacity { NumberAnimation { duration: Style.duration(160) } }
// Sunrise sits a little high and sunset a little low: a cue that needs no reading.
transform: Translate { y: arrow.rising ? -Style.space(2) : Style.space(2) }
+2 -2
View File
@@ -338,9 +338,9 @@ Item {
ParallelAnimation {
id: flight
NumberAnimation { id: flightSpin; target: root; property: "spin"
duration: 800; easing.type: Easing.OutCubic }
duration: Style.duration(800); easing.type: Easing.OutCubic }
NumberAnimation { id: flightViewLat; target: root; property: "viewLat"
duration: 800; easing.type: Easing.OutCubic }
duration: Style.duration(800); easing.type: Easing.OutCubic }
}
// The throw: spin keeps going after the drag and eases to a stop.
+2 -2
View File
@@ -137,7 +137,7 @@ Column {
id: globeSpin
target: heroIcon
property: "spin"
duration: 1250
duration: Style.duration(1250)
easing.type: Easing.OutQuart
}
@@ -145,7 +145,7 @@ Column {
id: focusSpin
target: heroIcon
property: "spin"
duration: 700
duration: Style.duration(700)
easing.type: Easing.OutCubic
}
}
+4 -4
View File
@@ -631,7 +631,7 @@ Panel {
// Heavy on the way out, brisk on the way back.
Behavior on zoom {
NumberAnimation {
duration: root.zoomDuration
duration: Style.duration(root.zoomDuration)
easing.type: root.zoomEasing
}
}
@@ -649,14 +649,14 @@ Panel {
Behavior on moonDemo {
enabled: root.moonShowing
NumberAnimation { duration: 460; easing.type: Easing.InOutSine }
NumberAnimation { duration: Style.duration(460); easing.type: Easing.InOutSine }
}
NumberAnimation {
id: dropAnimation
target: root
property: "dragOffset"
duration: 150
duration: Style.duration(150)
easing.type: Easing.OutCubic
onFinished: root.commitRowDrag()
}
@@ -805,7 +805,7 @@ Panel {
id: scrollAnim
target: scroller
property: "contentY"
duration: 160
duration: Style.duration(160)
easing.type: Easing.OutCubic
}
+4 -4
View File
@@ -965,14 +965,14 @@ Panel {
id: connectionPhraseSwap
PropertyAnimation {
target: heroMeta; property: "opacity"
to: 0.0; duration: 180; easing.type: Easing.OutQuad
to: 0.0; duration: Style.duration(180); easing.type: Easing.OutQuad
}
ScriptAction {
script: root.connectionPhraseIndex = (root.connectionPhraseIndex + 1) % root.connectionPhrases.length
}
PropertyAnimation {
target: heroMeta; property: "opacity"
to: 1.0; duration: 260; easing.type: Easing.InQuad
to: 1.0; duration: Style.duration(260); easing.type: Easing.InQuad
}
}
@@ -1472,10 +1472,10 @@ Panel {
opacity: root.bandPillsVisible ? 1 : 0
Behavior on height {
NumberAnimation { duration: 140; easing.type: Easing.OutCubic }
NumberAnimation { duration: Style.duration(140); easing.type: Easing.OutCubic }
}
Behavior on opacity {
NumberAnimation { duration: 140; easing.type: Easing.OutCubic }
NumberAnimation { duration: Style.duration(140); easing.type: Easing.OutCubic }
}
Row {
+7 -7
View File
@@ -246,7 +246,7 @@ Panel {
id: phraseSwap
PropertyAnimation {
target: heroStatus; property: "opacity"
to: 0.0; duration: 180; easing.type: Easing.OutQuad
to: 0.0; duration: Style.duration(180); easing.type: Easing.OutQuad
}
ScriptAction {
script: {
@@ -256,7 +256,7 @@ Panel {
}
PropertyAnimation {
target: heroStatus; property: "opacity"
to: 1.0; duration: 260; easing.type: Easing.InQuad
to: 1.0; duration: Style.duration(260); easing.type: Easing.InQuad
}
}
@@ -333,7 +333,7 @@ Panel {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
Behavior on color { ColorAnimation { duration: 200 } }
Behavior on color { ColorAnimation { duration: Style.duration(200) } }
}
Column {
@@ -380,7 +380,7 @@ Panel {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
Behavior on color { ColorAnimation { duration: 200 } }
Behavior on color { ColorAnimation { duration: Style.duration(200) } }
}
}
@@ -405,12 +405,12 @@ Panel {
color: root.batteryFillColor
width: Math.max(barTrack.height, barTrack.width * root.batteryFraction)
Behavior on width { NumberAnimation { duration: 320; easing.type: Easing.OutCubic } }
Behavior on color { ColorAnimation { duration: 220 } }
Behavior on width { NumberAnimation { duration: Style.duration(320); easing.type: Easing.OutCubic } }
Behavior on color { ColorAnimation { duration: Style.duration(220) } }
// Subtle pulse while charging — visible signal that energy is flowing in.
SequentialAnimation on opacity {
running: root.charging && !root.fullyCharged && root.opened
running: root.charging && !root.fullyCharged && root.opened && !Style.reduceMotion
loops: Animation.Infinite
alwaysRunToEnd: true
NumberAnimation { from: 1.0; to: 0.55; duration: 950; easing.type: Easing.InOutSine }
+4 -4
View File
@@ -726,14 +726,14 @@ Panel {
id: phraseSwap
PropertyAnimation {
target: hero; property: "metaOpacity"
to: 0.0; duration: 180; easing.type: Easing.OutQuad
to: 0.0; duration: Style.duration(180); easing.type: Easing.OutQuad
}
ScriptAction {
script: root.phraseIndex = (root.phraseIndex + 1) % root.activePhrases.length
}
PropertyAnimation {
target: hero; property: "metaOpacity"
to: 1.0; duration: 260; easing.type: Easing.InQuad
to: 1.0; duration: Style.duration(260); easing.type: Easing.InQuad
}
}
@@ -834,7 +834,7 @@ Panel {
opacity: accountRow.switchingAccount ? 0.45 : 1.0
SequentialAnimation on opacity {
running: accountRow.switchingAccount
running: accountRow.switchingAccount && !Style.reduceMotion
NumberAnimation { to: 1.0; duration: 420; easing.type: Easing.InOutQuad }
NumberAnimation { to: 0.45; duration: 420; easing.type: Easing.InOutQuad }
loops: Animation.Infinite
@@ -1150,7 +1150,7 @@ Panel {
anchors.verticalCenter: parent.verticalCenter
NumberAnimation on rotation {
running: exitNodeRow.settingExitNode
running: exitNodeRow.settingExitNode && !Style.reduceMotion
from: 0
to: 360
duration: 900
+1 -1
View File
@@ -657,7 +657,7 @@ Panel {
font.pixelSize: Style.font.bodySmall
RotationAnimator on rotation {
running: root.savingLocation
running: root.savingLocation && !Style.reduceMotion
from: 0; to: 360
duration: 800
loops: Animation.Infinite
+3 -3
View File
@@ -153,9 +153,9 @@ Item {
SequentialAnimation {
id: shakeAnimation
NumberAnimation { target: root; property: "shakeOffset"; to: -8; duration: 35; easing.type: Easing.OutQuad }
NumberAnimation { target: root; property: "shakeOffset"; to: 8; duration: 50; easing.type: Easing.InOutQuad }
NumberAnimation { target: root; property: "shakeOffset"; to: 0; duration: 55; easing.type: Easing.OutQuad }
NumberAnimation { target: root; property: "shakeOffset"; to: -8; duration: Style.duration(35); easing.type: Easing.OutQuad }
NumberAnimation { target: root; property: "shakeOffset"; to: 8; duration: Style.duration(50); easing.type: Easing.InOutQuad }
NumberAnimation { target: root; property: "shakeOffset"; to: 0; duration: Style.duration(55); easing.type: Easing.OutQuad }
}
FileView {
path: "/etc/pam.d/polkit-1"
+4 -2
View File
@@ -40,7 +40,7 @@ BarWidget {
font.pixelSize: Style.font.body
Behavior on color {
enabled: !root.bar || root.bar.foregroundAnimationEnabled
ColorAnimation { duration: 160 }
ColorAnimation { duration: Style.duration(160) }
}
}
@@ -65,7 +65,9 @@ BarWidget {
NumberAnimation on x {
id: scrollAnim
running: labelText.needsScroll && !root.popupOpen && !root.bar.vertical
running: labelText.needsScroll && !root.popupOpen && !root.bar.vertical && !Style.reduceMotion
// Stopped for reduced motion, the title reads from its start again.
onRunningChanged: if (!running && Style.reduceMotion) labelText.x = 0
loops: Animation.Infinite
duration: Math.max(6000, labelText.implicitWidth * 25)
from: scrollClip.width