Attention Required: notification rules with effects for Omarchy
Rules watch every notification for words and apps and answer with an effect: nudge, flash, banner, airplane, confetti, blink, sound, focus, or a command. Silenced notifications that match a rule are let through. Settings popup behind a bar bell, rules kept in one JSON file.
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
|
||||
// A glow that pulses in from the edges of every screen. Click-through, takes
|
||||
// no keyboard focus, and reserves no space, so nothing under it moves.
|
||||
//
|
||||
// Options on the effect:
|
||||
// duration seconds the glow keeps pulsing, default 1
|
||||
// intensity 0..1, how strong the glow gets, default 0.9
|
||||
// speed pulses per second, default 3
|
||||
// thickness px of glow from each edge, default 64
|
||||
// color a theme role ("accent" | "urgent" | "foreground") or any CSS color
|
||||
Item {
|
||||
id: root
|
||||
width: 0
|
||||
height: 0
|
||||
visible: false
|
||||
|
||||
property color glow: Color.accent
|
||||
property int pulses: 3
|
||||
property int thickness: 64
|
||||
property int pulseMs: 333
|
||||
property real intensity: 0.9
|
||||
property real level: 0
|
||||
property bool active: false
|
||||
|
||||
function resolveColor(value) {
|
||||
var v = String(value || "").trim().toLowerCase()
|
||||
if (!v || v === "accent") return Color.accent
|
||||
if (v === "urgent") return Color.urgent
|
||||
if (v === "foreground" || v === "text") return Color.foreground
|
||||
if (v === "background") return Color.background
|
||||
var c = Qt.color(value)
|
||||
return c.valid === false ? Color.accent : c
|
||||
}
|
||||
|
||||
function number(value, fallback, min, max) {
|
||||
var n = Number(value)
|
||||
if (!isFinite(n)) return fallback
|
||||
return Math.max(min, Math.min(max, n))
|
||||
}
|
||||
|
||||
function trigger(opts) {
|
||||
opts = opts || {}
|
||||
glow = resolveColor(opts.color)
|
||||
var duration = number(opts.duration, 1, 0.1, 30)
|
||||
var speed = number(opts.speed, 3, 0.2, 20)
|
||||
intensity = number(opts.intensity, 0.9, 0.05, 1)
|
||||
thickness = Math.round(number(opts.thickness, 64, 4, 600))
|
||||
pulseMs = Math.round(1000 / speed)
|
||||
pulses = Math.max(1, Math.round(duration * speed))
|
||||
anim.stop()
|
||||
level = 0
|
||||
active = true
|
||||
anim.loops = pulses
|
||||
anim.start()
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: anim
|
||||
NumberAnimation { target: root; property: "level"; from: 0; to: 1; duration: root.pulseMs / 2; easing.type: Easing.OutQuad }
|
||||
NumberAnimation { target: root; property: "level"; from: 1; to: 0; duration: root.pulseMs / 2; easing.type: Easing.InQuad }
|
||||
onFinished: {
|
||||
root.level = 0
|
||||
root.active = false
|
||||
}
|
||||
}
|
||||
|
||||
Variants {
|
||||
model: Quickshell.screens
|
||||
|
||||
PanelWindow {
|
||||
id: window
|
||||
required property var modelData
|
||||
screen: modelData
|
||||
visible: root.active
|
||||
color: "transparent"
|
||||
anchors { top: true; bottom: true; left: true; right: true }
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
WlrLayershell.namespace: "attention-required"
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
mask: Region {}
|
||||
|
||||
readonly property color solid: Qt.rgba(root.glow.r, root.glow.g, root.glow.b, 0.9)
|
||||
readonly property color clear: Qt.rgba(root.glow.r, root.glow.g, root.glow.b, 0)
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
opacity: root.level * root.intensity
|
||||
|
||||
Rectangle {
|
||||
anchors { top: parent.top; left: parent.left; right: parent.right }
|
||||
height: root.thickness
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0; color: window.solid }
|
||||
GradientStop { position: 1; color: window.clear }
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
anchors { bottom: parent.bottom; left: parent.left; right: parent.right }
|
||||
height: root.thickness
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0; color: window.clear }
|
||||
GradientStop { position: 1; color: window.solid }
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
anchors { top: parent.top; bottom: parent.bottom; left: parent.left }
|
||||
width: root.thickness
|
||||
gradient: Gradient {
|
||||
orientation: Gradient.Horizontal
|
||||
GradientStop { position: 0; color: window.solid }
|
||||
GradientStop { position: 1; color: window.clear }
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
anchors { top: parent.top; bottom: parent.bottom; right: parent.right }
|
||||
width: root.thickness
|
||||
gradient: Gradient {
|
||||
orientation: Gradient.Horizontal
|
||||
GradientStop { position: 0; color: window.clear }
|
||||
GradientStop { position: 1; color: window.solid }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user