put shell in subdir, and add nix package

This commit is contained in:
kossLAN 2025-06-17 12:50:08 -04:00
parent c45c04e9ac
commit f41ea4b1cb
Signed by: kossLAN
SSH key fingerprint: SHA256:bdV0x+wdQHGJ6LgmstH3KV8OpWY+OOFmJcPcB0wQPV8
100 changed files with 57 additions and 126 deletions

View file

@ -0,0 +1,218 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import Quickshell.Services.Notifications
import "../widgets/" as Widgets
import ".."
import "../.."
Item {
id: root
required property var notification
signal expired(Notification notification)
signal closed(Notification notification)
width: parent.width
height: Math.min(row.implicitHeight + 30, 400)
Rectangle {
id: container
radius: 10
color: ShellSettings.colors["surface_container"]
anchors.fill: parent
Item {
id: timerController
property int totalDuration: 5000
property int remainingTime: totalDuration
property bool isRunning: false
property real lastTime: 0
Timer {
id: internalTimer
interval: 16
repeat: true
running: timerController.isRunning
onTriggered: {
var currentTime = Date.now();
if (timerController.lastTime > 0) {
var delta = currentTime - timerController.lastTime;
timerController.remainingTime -= delta;
if (timerController.remainingTime <= 0) {
timerController.isRunning = false;
root.expired(root.notification);
}
}
timerController.lastTime = currentTime;
}
}
function start() {
if (!isRunning) {
lastTime = Date.now();
isRunning = true;
}
}
function pause() {
isRunning = false;
lastTime = 0;
}
Component.onCompleted: {
start();
}
}
MouseArea {
id: notificationArea
hoverEnabled: true
anchors.fill: parent
onContainsMouseChanged: {
progressAnimation.paused = containsMouse;
if (containsMouse) {
timerController.pause();
} else {
timerController.start();
}
}
}
RowLayout {
id: row
spacing: 5
anchors {
fill: parent
margins: 15
}
ColumnLayout {
Layout.fillWidth: true
RowLayout {
id: topRow
spacing: 10
Layout.fillWidth: true
IconImage {
visible: root.notification.appIcon != ""
source: Quickshell.iconPath(root.notification.appIcon)
implicitSize: 24
}
RowLayout {
Layout.fillWidth: true
Text {
id: appName
text: root.notification.appName
color: ShellSettings.colors["inverse_surface"]
font.pointSize: 11
font.bold: true
elide: Text.ElideRight
maximumLineCount: 1
Layout.preferredWidth: implicitWidth
Layout.maximumWidth: topRow.width * 0.3
}
Widgets.Separator {}
Text {
id: summaryText
text: root.notification.summary
color: ShellSettings.colors["inverse_surface"]
font.pointSize: 11
elide: Text.ElideRight
maximumLineCount: 1
Layout.fillWidth: true
}
}
Item {
id: closeButton
width: 24
height: 24
Layout.alignment: Qt.AlignTop
Canvas {
id: progressCircle
anchors.fill: parent
antialiasing: true
property real progress: 1.0
onProgressChanged: requestPaint()
onPaint: {
var ctx = getContext("2d");
ctx.reset();
var centerX = width / 2;
var centerY = height / 2;
var radius = Math.min(width, height) / 2 - 2;
ctx.beginPath();
ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + 2 * Math.PI * progress);
ctx.strokeStyle = ShellSettings.colors["primary"];
ctx.lineWidth = 2;
ctx.stroke();
}
}
NumberAnimation {
id: progressAnimation
target: progressCircle
property: "progress"
from: 1.0
to: 0.0
duration: 5000
running: true
easing.type: Easing.Linear
}
Rectangle {
id: closeButtonBg
anchors.centerIn: parent
width: 16
height: 16
color: "#FF474D"
radius: 10
visible: closeButtonArea.containsMouse
}
MouseArea {
id: closeButtonArea
hoverEnabled: true
anchors.fill: parent
onPressed: root.closed(root.notification)
}
IconImage {
source: "image://icon/window-close"
implicitSize: 16
anchors.centerIn: parent
}
}
}
ColumnLayout {
Layout.fillWidth: true
Text {
id: bodyText
text: root.notification.body
color: ShellSettings.colors["inverse_surface"]
font.pointSize: 11
wrapMode: Text.Wrap
elide: Text.ElideRight
maximumLineCount: 10
Layout.fillWidth: true
}
}
}
}
}
}

View file

@ -0,0 +1,98 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import ".."
Scope {
id: root
Connections {
target: Notifications.notificationServer
function onNotification(notification) {
notificationLoader.item.visible = true;
}
}
LazyLoader {
id: notificationLoader
loading: true
PanelWindow {
id: notificationWindow
property var visibleCount: {
let count = 0;
for (let i = 0; i < toastList.count; i++) {
let item = toastList.itemAt(i);
if (item && item.visible) {
count++;
}
}
return count;
}
onVisibleCountChanged: visible = visibleCount != 0
color: "transparent"
implicitWidth: 525
visible: false
exclusionMode: ExclusionMode.Normal
mask: Region {
item: notifLayout
}
anchors {
top: true
bottom: true
right: true
}
Text {
text: "length: " + notificationWindow.visibleCount
}
ColumnLayout {
id: notifLayout
spacing: 15
anchors {
top: parent.top
left: parent.left
right: parent.right
margins: 5
}
Repeater {
id: toastList
model: ScriptModel {
values: Notifications.notificationServer.trackedNotifications.values.concat()
}
delegate: ActiveToast {
id: toast
required property var modelData
notification: modelData
Connections {
target: toast
function onExpired(notification) {
toast.visible = false;
}
function onClosed(notification) {
notification.dismiss();
}
}
}
}
}
}
}
}

View file

@ -0,0 +1,278 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Qt5Compat.GraphicalEffects
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Widgets
import "../widgets" as Widgets
import ".."
Singleton {
PersistentProperties {
id: persist
property bool notificationsOpen: false
}
IpcHandler {
id: ipc
target: "notifications"
function open(): void {
persist.notificationsOpen = true;
}
function close(): void {
persist.notificationsOpen = false;
}
function toggle(): void {
persist.notificationsOpen = !persist.notificationsOpen;
}
}
LazyLoader {
id: loader
activeAsync: persist.notificationsOpen
PanelWindow {
id: notificationPanel
color: "red"
implicitWidth: 500
exclusionMode: ExclusionMode.Ignore
// WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
anchors {
top: true
right: true
bottom: true
}
ColumnLayout {
spacing: 10
anchors {
fill: parent
margins: 10
}
Text {
text: "Notifications: " + toastList.count
Layout.fillWidth: true
}
ListView {
id: toastList
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
spacing: 5
model: ScriptModel {
values: {
const notifications = Notifications.notificationServer.trackedNotifications.values.concat();
const groupedByApp = notifications.reduce((groups, notification) => {
const appName = notification.appName;
if (!groups[appName]) {
groups[appName] = {
appName: appName,
summaryGroups: {}
};
}
const summary = notification.summary;
const image = notification.image;
if (!groups[appName].summaryGroups[summary]) {
groups[appName].summaryGroups[summary] = {
summary: summary,
image: image,
notifications: []
};
}
groups[appName].summaryGroups[summary].notifications.push(notification);
return groups;
}, {});
return Object.values(groupedByApp).map(appGroup => {
return {
appName: appGroup.appName,
summaryGroups: Object.values(appGroup.summaryGroups)
};
});
}
}
delegate: Item {
id: toastWrapper
required property var modelData
width: ListView.view.width
height: toastContent.height
Item {
id: toastContent
width: parent.width
height: contentColumn.implicitHeight
anchors.centerIn: parent
ColumnLayout {
id: contentColumn
spacing: 2
anchors {
fill: parent
margins: 0
}
// Notification content
Repeater {
model: toastWrapper.modelData.summaryGroups
delegate: Rectangle {
id: summaryGroup
required property var modelData
required property int index
Layout.fillWidth: true
Layout.preferredHeight: groupContent.implicitHeight + 24
color: ShellSettings.colors["surface_container"]
antialiasing: true
topLeftRadius: index === 0 ? 25 : 5
topRightRadius: index === 0 ? 25 : 5
bottomLeftRadius: index === (toastWrapper.modelData.summaryGroups.length - 1) ? 25 : 5
bottomRightRadius: index === (toastWrapper.modelData.summaryGroups.length - 1) ? 25 : 5
ColumnLayout {
id: groupContent
spacing: 8
anchors {
fill: parent
margins: 12
}
RowLayout {
spacing: 12
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
ColumnLayout {
Layout.alignment: Qt.AlignTop
spacing: 0
Item {
id: imageContainer
Layout.preferredWidth: 36
Layout.preferredHeight: 36
visible: summaryGroup.modelData.image != ""
antialiasing: true
Image {
id: notificationImage
anchors.fill: parent
source: summaryGroup.modelData.image
fillMode: Image.PreserveAspectCrop
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: notificationImage.width
height: notificationImage.height
radius: notificationImage.width / 2
antialiasing: true
}
}
}
}
}
// Content column
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
spacing: 8
// Header row
RowLayout {
Layout.fillWidth: true
spacing: 8
Text {
text: summaryGroup.modelData.summary
font.pixelSize: 16
font.weight: Font.Medium
color: ShellSettings.colors["on_surface"]
wrapMode: Text.WordWrap
maximumLineCount: 2
elide: Text.ElideRight
}
Widgets.Separator {}
Text {
text: "now"
font.pixelSize: 14
color: ShellSettings.colors["on_surface_variant"]
Layout.alignment: Qt.AlignVCenter
}
}
// Notification bodies
ColumnLayout {
Layout.fillWidth: true
spacing: 2
Repeater {
model: summaryGroup.modelData.notifications
delegate: ColumnLayout {
id: bodyDelegate
required property var modelData
required property int index
Layout.fillWidth: true
spacing: 0
Text {
Layout.fillWidth: true
text: bodyDelegate.modelData.body
font.pixelSize: 14
color: ShellSettings.colors["on_surface_variant"]
wrapMode: Text.WordWrap
maximumLineCount: 4
elide: Text.ElideRight
lineHeight: 1.3
visible: bodyDelegate.modelData.body != ""
}
}
}
}
}
}
}
}
}
}
}
}
}
}
// HyprlanFocusGrab {
// id: grab
// windows: [notificationPanel]
// onCleared: {
// ipc.hide();
// }
// }
}
}
function init() {
}
}

View file

@ -0,0 +1,23 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Services.Notifications
Singleton {
property alias notificationServer: notifServer
NotificationServer {
id: notifServer
actionsSupported: true
persistenceSupported: true
}
Connections {
target: notifServer
function onNotification(notification) {
notification.tracked = true;
}
}
}