Fix notification and other small fixes
This commit is contained in:
parent
fb68300746
commit
69d84752f3
10 changed files with 366 additions and 338 deletions
|
|
@ -296,9 +296,9 @@ PanelWithOverlay {
|
||||||
const searchTerm = query.slice(5).trim();
|
const searchTerm = query.slice(5).trim();
|
||||||
|
|
||||||
clipboardHistory.forEach(function(clip, index) {
|
clipboardHistory.forEach(function(clip, index) {
|
||||||
let searchContent = clip.type === 'image' ?
|
let searchContent = clip.type === 'image' ?
|
||||||
clip.mimeType :
|
clip.mimeType :
|
||||||
clip.content || clip; // Support both new object format and old string format
|
clip.content || clip; // Support both new object format and old string format
|
||||||
|
|
||||||
if (!searchTerm || searchContent.toLowerCase().includes(searchTerm)) {
|
if (!searchTerm || searchContent.toLowerCase().includes(searchTerm)) {
|
||||||
let entry;
|
let entry;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ IpcHandler {
|
||||||
property var appLauncherPanel
|
property var appLauncherPanel
|
||||||
property var lockScreen
|
property var lockScreen
|
||||||
property IdleInhibitor idleInhibitor
|
property IdleInhibitor idleInhibitor
|
||||||
property var notificationPopupVariants
|
property var notificationPopup
|
||||||
|
|
||||||
target: "globalIPC"
|
target: "globalIPC"
|
||||||
|
|
||||||
|
|
@ -17,18 +17,11 @@ IpcHandler {
|
||||||
|
|
||||||
function toggleNotificationPopup(): void {
|
function toggleNotificationPopup(): void {
|
||||||
console.log("[IPC] NotificationPopup toggle() called")
|
console.log("[IPC] NotificationPopup toggle() called")
|
||||||
|
// Use the global toggle function from the notification manager
|
||||||
if (notificationPopupVariants) {
|
notificationPopup.togglePopup();
|
||||||
for (let i = 0; i < notificationPopupVariants.count; i++) {
|
|
||||||
let popup = notificationPopupVariants.objectAt(i);
|
|
||||||
if (popup) {
|
|
||||||
popup.togglePopup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Toggle Applauncher visibility
|
||||||
function toggleLauncher(): void {
|
function toggleLauncher(): void {
|
||||||
if (!appLauncherPanel) {
|
if (!appLauncherPanel) {
|
||||||
console.warn("AppLauncherIpcHandler: appLauncherPanel not set!");
|
console.warn("AppLauncherIpcHandler: appLauncherPanel not set!");
|
||||||
|
|
@ -42,7 +35,7 @@ IpcHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Toggle LockScreen
|
||||||
function toggleLock(): void {
|
function toggleLock(): void {
|
||||||
if (!lockScreen) {
|
if (!lockScreen) {
|
||||||
console.warn("LockScreenIpcHandler: lockScreen not set!");
|
console.warn("LockScreenIpcHandler: lockScreen not set!");
|
||||||
|
|
@ -51,4 +44,4 @@ IpcHandler {
|
||||||
console.log("[IPC] LockScreen show() called");
|
console.log("[IPC] LockScreen show() called");
|
||||||
lockScreen.locked = true;
|
lockScreen.locked = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -81,7 +81,7 @@ Singleton {
|
||||||
// Monitor/Display Settings
|
// Monitor/Display Settings
|
||||||
property var barMonitors: [] // Array of monitor names to show the bar on
|
property var barMonitors: [] // Array of monitor names to show the bar on
|
||||||
property var dockMonitors: [] // Array of monitor names to show the dock on
|
property var dockMonitors: [] // Array of monitor names to show the dock on
|
||||||
property var notificationMonitors: [] // Array of monitor names to show notifications on
|
property var notificationMonitors: [] // Array of monitor names to show notifications on, "*" means all monitors
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,5 +90,7 @@ Singleton {
|
||||||
function onRandomWallpaperChanged() { WallpaperManager.toggleRandomWallpaper() }
|
function onRandomWallpaperChanged() { WallpaperManager.toggleRandomWallpaper() }
|
||||||
function onWallpaperIntervalChanged() { WallpaperManager.restartRandomWallpaperTimer() }
|
function onWallpaperIntervalChanged() { WallpaperManager.restartRandomWallpaperTimer() }
|
||||||
function onWallpaperFolderChanged() { WallpaperManager.loadWallpapers() }
|
function onWallpaperFolderChanged() { WallpaperManager.loadWallpapers() }
|
||||||
|
function onNotificationMonitorsChanged() {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,313 +4,356 @@ import Quickshell
|
||||||
import Quickshell.Widgets
|
import Quickshell.Widgets
|
||||||
import qs.Settings
|
import qs.Settings
|
||||||
|
|
||||||
PanelWindow {
|
// Main container that manages multiple notification popups for different monitors
|
||||||
id: window
|
Item {
|
||||||
implicitWidth: 350
|
id: notificationManager
|
||||||
implicitHeight: notificationColumn.implicitHeight
|
anchors.fill: parent
|
||||||
color: "transparent"
|
|
||||||
visible: notificationsVisible && notificationModel.count > 0
|
|
||||||
screen: (typeof modelData !== 'undefined' ? modelData : Quickshell.primaryScreen)
|
|
||||||
focusable: false
|
|
||||||
|
|
||||||
property bool barVisible: true
|
// Get list of available monitors/screens
|
||||||
|
property var monitors: Quickshell.screens || []
|
||||||
|
|
||||||
|
// Global visibility state for all notification popups
|
||||||
property bool notificationsVisible: true
|
property bool notificationsVisible: true
|
||||||
|
|
||||||
anchors.top: true
|
|
||||||
anchors.right: true
|
|
||||||
margins.top: 6
|
|
||||||
margins.right: 6
|
|
||||||
|
|
||||||
ListModel {
|
|
||||||
id: notificationModel
|
|
||||||
}
|
|
||||||
|
|
||||||
property int maxVisible: 5
|
|
||||||
property int spacing: 5
|
|
||||||
|
|
||||||
function togglePopup(): void {
|
function togglePopup(): void {
|
||||||
console.log("[NotificationPopup] Current state: " + notificationsVisible);
|
console.log("[NotificationManager] Current state: " + notificationsVisible);
|
||||||
notificationsVisible = !notificationsVisible;
|
notificationsVisible = !notificationsVisible;
|
||||||
console.log("[NotificationPopup] New state: " + notificationsVisible);
|
console.log("[NotificationManager] New state: " + notificationsVisible);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addNotification(notification) {
|
// Create a notification popup for each monitor
|
||||||
notificationModel.insert(0, {
|
Repeater {
|
||||||
id: notification.id,
|
model: notificationManager.monitors
|
||||||
appName: notification.appName || "Notification",
|
delegate: Item {
|
||||||
summary: notification.summary || "",
|
id: delegateItem
|
||||||
body: notification.body || "",
|
|
||||||
urgency: notification.urgency || 0,
|
// Make addNotification accessible from the Item level
|
||||||
rawNotification: notification,
|
function addNotification(notification) {
|
||||||
appeared: false,
|
if (panelWindow) {
|
||||||
dismissed: false
|
panelWindow.addNotification(notification);
|
||||||
});
|
}
|
||||||
|
|
||||||
while (notificationModel.count > maxVisible) {
|
|
||||||
notificationModel.remove(notificationModel.count - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function dismissNotificationById(id) {
|
|
||||||
for (var i = 0; i < notificationModel.count; i++) {
|
|
||||||
if (notificationModel.get(i).id === id) {
|
|
||||||
dismissNotificationByIndex(i);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
PanelWindow {
|
||||||
|
id: panelWindow
|
||||||
|
implicitWidth: 350
|
||||||
|
implicitHeight: Math.max(notificationColumn.height, 0)
|
||||||
|
color: "transparent"
|
||||||
|
visible: notificationManager.notificationsVisible && notificationModel.count > 0 && shouldShowOnThisMonitor
|
||||||
|
screen: modelData
|
||||||
|
focusable: false
|
||||||
|
|
||||||
function dismissNotificationByIndex(index) {
|
property bool barVisible: true
|
||||||
if (index >= 0 && index < notificationModel.count) {
|
property bool notificationsVisible: notificationManager.notificationsVisible
|
||||||
var notif = notificationModel.get(index);
|
|
||||||
if (!notif.dismissed) {
|
// Check if this monitor should show notifications - make it reactive to settings changes
|
||||||
notificationModel.set(index, {
|
property bool shouldShowOnThisMonitor: {
|
||||||
id: notif.id,
|
let notificationMonitors = Settings.settings.notificationMonitors || [];
|
||||||
appName: notif.appName,
|
let currentScreenName = modelData ? modelData.name : "";
|
||||||
summary: notif.summary,
|
return notificationMonitors.includes("*") ||
|
||||||
body: notif.body,
|
notificationMonitors.includes(currentScreenName);
|
||||||
rawNotification: notif.rawNotification,
|
}
|
||||||
appeared: notif.appeared,
|
|
||||||
dismissed: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
// Watch for changes in notification monitors setting
|
||||||
id: notificationColumn
|
Connections {
|
||||||
anchors.right: parent.right
|
target: Settings.settings
|
||||||
spacing: window.spacing
|
function onNotificationMonitorsChanged() {
|
||||||
width: parent.width
|
// Settings changed, visibility will update automatically
|
||||||
clip: false
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Repeater {
|
anchors.top: true
|
||||||
id: notificationRepeater
|
anchors.right: true
|
||||||
model: notificationModel
|
margins.top: 6
|
||||||
|
margins.right: 6
|
||||||
|
|
||||||
delegate: Rectangle {
|
ListModel {
|
||||||
id: notificationDelegate
|
id: notificationModel
|
||||||
width: parent.width
|
}
|
||||||
color: Theme.backgroundPrimary
|
|
||||||
radius: 20
|
|
||||||
border.color: model.urgency == 2 ? Theme.warning : Theme.outline
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
property bool appeared: model.appeared
|
property int maxVisible: 5
|
||||||
property bool dismissed: model.dismissed
|
property int spacing: 5
|
||||||
property var rawNotification: model.rawNotification
|
|
||||||
|
|
||||||
x: appeared ? 0 : width
|
function addNotification(notification) {
|
||||||
opacity: dismissed ? 0 : 1
|
notificationModel.insert(0, {
|
||||||
height: dismissed ? 0 : contentRow.height + 20
|
id: notification.id,
|
||||||
|
appName: notification.appName || "Notification",
|
||||||
|
summary: notification.summary || "",
|
||||||
|
body: notification.body || "",
|
||||||
|
urgency: notification.urgency || 0,
|
||||||
|
rawNotification: notification,
|
||||||
|
appeared: false,
|
||||||
|
dismissed: false
|
||||||
|
});
|
||||||
|
|
||||||
Row {
|
while (notificationModel.count > maxVisible) {
|
||||||
id: contentRow
|
notificationModel.remove(notificationModel.count - 1);
|
||||||
anchors.centerIn: parent
|
}
|
||||||
spacing: 10
|
}
|
||||||
width: parent.width - 20
|
|
||||||
|
|
||||||
// Circular Icon container with border
|
function dismissNotificationById(id) {
|
||||||
Rectangle {
|
for (var i = 0; i < notificationModel.count; i++) {
|
||||||
id: iconBackground
|
if (notificationModel.get(i).id === id) {
|
||||||
width: 36
|
dismissNotificationByIndex(i);
|
||||||
height: 36
|
break;
|
||||||
radius: width / 2
|
|
||||||
color: Theme.accentPrimary
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
border.color: Qt.darker(Theme.accentPrimary, 1.2)
|
|
||||||
border.width: 1.5
|
|
||||||
|
|
||||||
// Priority order for notification icons: image > appIcon > icon
|
|
||||||
property var iconSources: [rawNotification?.image || "", rawNotification?.appIcon || "", rawNotification?.icon || ""]
|
|
||||||
|
|
||||||
// Load notification icon with fallback handling
|
|
||||||
IconImage {
|
|
||||||
id: iconImage
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 4
|
|
||||||
asynchronous: true
|
|
||||||
backer.fillMode: Image.PreserveAspectFit
|
|
||||||
source: {
|
|
||||||
// Try each icon source in priority order
|
|
||||||
for (var i = 0; i < iconBackground.iconSources.length; i++) {
|
|
||||||
var icon = iconBackground.iconSources[i];
|
|
||||||
if (!icon)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Handle special path format from some notifications
|
|
||||||
if (icon.includes("?path=")) {
|
|
||||||
const [name, path] = icon.split("?path=");
|
|
||||||
const fileName = name.substring(name.lastIndexOf("/") + 1);
|
|
||||||
return `file://${path}/${fileName}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle absolute file paths
|
|
||||||
if (icon.startsWith('/')) {
|
|
||||||
return "file://" + icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
return icon;
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
visible: status === Image.Ready && source.toString() !== ""
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Fallback: show first letter of app name when no icon available
|
function dismissNotificationByIndex(index) {
|
||||||
Text {
|
if (index >= 0 && index < notificationModel.count) {
|
||||||
anchors.centerIn: parent
|
var notif = notificationModel.get(index);
|
||||||
visible: !iconImage.visible
|
if (!notif.dismissed) {
|
||||||
text: model.appName ? model.appName.charAt(0).toUpperCase() : "?"
|
notificationModel.set(index, {
|
||||||
font.family: Theme.fontFamily
|
id: notif.id,
|
||||||
font.pixelSize: Theme.fontSizeBody
|
appName: notif.appName,
|
||||||
font.bold: true
|
summary: notif.summary,
|
||||||
|
body: notif.body,
|
||||||
|
rawNotification: notif.rawNotification,
|
||||||
|
appeared: notif.appeared,
|
||||||
|
dismissed: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: notificationColumn
|
||||||
|
anchors.right: parent.right
|
||||||
|
spacing: window.spacing
|
||||||
|
width: parent.width
|
||||||
|
clip: false
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
id: notificationRepeater
|
||||||
|
model: notificationModel
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
id: notificationDelegate
|
||||||
|
width: parent.width
|
||||||
color: Theme.backgroundPrimary
|
color: Theme.backgroundPrimary
|
||||||
}
|
radius: 20
|
||||||
}
|
border.color: model.urgency == 2 ? Theme.warning : Theme.outline
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
Column {
|
property bool appeared: model.appeared
|
||||||
width: contentRow.width - iconBackground.width - 10
|
property bool dismissed: model.dismissed
|
||||||
spacing: 5
|
property var rawNotification: model.rawNotification
|
||||||
|
|
||||||
Text {
|
x: appeared ? 0 : width
|
||||||
text: model.appName
|
opacity: dismissed ? 0 : 1
|
||||||
width: parent.width
|
height: dismissed ? 0 : contentRow.height + 20
|
||||||
color: Theme.textPrimary
|
|
||||||
font.family: Theme.fontFamily
|
|
||||||
font.bold: true
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
Text {
|
|
||||||
text: model.summary
|
|
||||||
width: parent.width
|
|
||||||
color: "#eeeeee"
|
|
||||||
font.family: Theme.fontFamily
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
visible: text !== ""
|
|
||||||
}
|
|
||||||
Text {
|
|
||||||
text: model.body
|
|
||||||
width: parent.width
|
|
||||||
color: "#cccccc"
|
|
||||||
font.family: Theme.fontFamily
|
|
||||||
font.pixelSize: Theme.fontSizeCaption
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
visible: text !== ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
Row {
|
||||||
interval: 4000
|
id: contentRow
|
||||||
running: !dismissed
|
anchors.centerIn: parent
|
||||||
repeat: false
|
spacing: 10
|
||||||
onTriggered: {
|
width: parent.width - 20
|
||||||
dismissAnimation.start();
|
|
||||||
if (rawNotification)
|
|
||||||
rawNotification.expire();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
// Circular Icon container with border
|
||||||
anchors.fill: parent
|
Rectangle {
|
||||||
onClicked: {
|
id: iconBackground
|
||||||
dismissAnimation.start();
|
width: 36
|
||||||
if (rawNotification)
|
height: 36
|
||||||
rawNotification.dismiss();
|
radius: width / 2
|
||||||
}
|
color: Theme.accentPrimary
|
||||||
}
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
border.color: Qt.darker(Theme.accentPrimary, 1.2)
|
||||||
|
border.width: 1.5
|
||||||
|
|
||||||
ParallelAnimation {
|
// Priority order for notification icons: image > appIcon > icon
|
||||||
id: dismissAnimation
|
property var iconSources: [rawNotification?.image || "", rawNotification?.appIcon || "", rawNotification?.icon || ""]
|
||||||
NumberAnimation {
|
|
||||||
target: notificationDelegate
|
// Load notification icon with fallback handling
|
||||||
property: "opacity"
|
IconImage {
|
||||||
to: 0
|
id: iconImage
|
||||||
duration: 150
|
anchors.fill: parent
|
||||||
}
|
anchors.margins: 4
|
||||||
NumberAnimation {
|
asynchronous: true
|
||||||
target: notificationDelegate
|
backer.fillMode: Image.PreserveAspectFit
|
||||||
property: "height"
|
source: {
|
||||||
to: 0
|
// Try each icon source in priority order
|
||||||
duration: 150
|
for (var i = 0; i < iconBackground.iconSources.length; i++) {
|
||||||
}
|
var icon = iconBackground.iconSources[i];
|
||||||
NumberAnimation {
|
if (!icon)
|
||||||
target: notificationDelegate
|
continue;
|
||||||
property: "x"
|
|
||||||
to: width
|
// Handle special path format from some notifications
|
||||||
duration: 150
|
if (icon.includes("?path=")) {
|
||||||
easing.type: Easing.InQuad
|
const [name, path] = icon.split("?path=");
|
||||||
}
|
const fileName = name.substring(name.lastIndexOf("/") + 1);
|
||||||
onFinished: {
|
return `file://${path}/${fileName}`;
|
||||||
for (let i = 0; i < notificationModel.count; i++) {
|
}
|
||||||
if (notificationModel.get(i).id === notificationDelegate.id) {
|
|
||||||
notificationModel.remove(i);
|
// Handle absolute file paths
|
||||||
break;
|
if (icon.startsWith('/')) {
|
||||||
|
return "file://" + icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
visible: status === Image.Ready && source.toString() !== ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: show first letter of app name when no icon available
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
visible: !iconImage.visible
|
||||||
|
text: model.appName ? model.appName.charAt(0).toUpperCase() : "?"
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontSizeBody
|
||||||
|
font.bold: true
|
||||||
|
color: Theme.backgroundPrimary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: contentRow.width - iconBackground.width - 10
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: model.appName
|
||||||
|
width: parent.width
|
||||||
|
color: Theme.textPrimary
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.bold: true
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
text: model.summary
|
||||||
|
width: parent.width
|
||||||
|
color: "#eeeeee"
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
visible: text !== ""
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
text: model.body
|
||||||
|
width: parent.width
|
||||||
|
color: "#cccccc"
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontSizeCaption
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
visible: text !== ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
interval: 4000
|
||||||
|
running: !dismissed
|
||||||
|
repeat: false
|
||||||
|
onTriggered: {
|
||||||
|
dismissAnimation.start();
|
||||||
|
if (rawNotification)
|
||||||
|
rawNotification.expire();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: {
|
||||||
|
dismissAnimation.start();
|
||||||
|
if (rawNotification)
|
||||||
|
rawNotification.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ParallelAnimation {
|
||||||
|
id: dismissAnimation
|
||||||
|
NumberAnimation {
|
||||||
|
target: notificationDelegate
|
||||||
|
property: "opacity"
|
||||||
|
to: 0
|
||||||
|
duration: 150
|
||||||
|
}
|
||||||
|
NumberAnimation {
|
||||||
|
target: notificationDelegate
|
||||||
|
property: "height"
|
||||||
|
to: 0
|
||||||
|
duration: 150
|
||||||
|
}
|
||||||
|
NumberAnimation {
|
||||||
|
target: notificationDelegate
|
||||||
|
property: "x"
|
||||||
|
to: width
|
||||||
|
duration: 150
|
||||||
|
easing.type: Easing.InQuad
|
||||||
|
}
|
||||||
|
onFinished: {
|
||||||
|
for (let i = 0; i < notificationModel.count; i++) {
|
||||||
|
if (notificationModel.get(i).id === notificationDelegate.id) {
|
||||||
|
notificationModel.remove(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ParallelAnimation {
|
||||||
|
id: appearAnimation
|
||||||
|
NumberAnimation {
|
||||||
|
target: notificationDelegate
|
||||||
|
property: "opacity"
|
||||||
|
to: 1
|
||||||
|
duration: 150
|
||||||
|
}
|
||||||
|
NumberAnimation {
|
||||||
|
target: notificationDelegate
|
||||||
|
property: "height"
|
||||||
|
to: contentRow.height + 20
|
||||||
|
duration: 150
|
||||||
|
}
|
||||||
|
NumberAnimation {
|
||||||
|
target: notificationDelegate
|
||||||
|
property: "x"
|
||||||
|
to: 0
|
||||||
|
duration: 150
|
||||||
|
easing.type: Easing.OutQuad
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
if (!appeared) {
|
||||||
|
opacity = 0;
|
||||||
|
height = 0;
|
||||||
|
x = width;
|
||||||
|
appearAnimation.start();
|
||||||
|
for (let i = 0; i < notificationModel.count; i++) {
|
||||||
|
if (notificationModel.get(i).id === notificationDelegate.id) {
|
||||||
|
var oldItem = notificationModel.get(i);
|
||||||
|
notificationModel.set(i, {
|
||||||
|
id: oldItem.id,
|
||||||
|
appName: oldItem.appName,
|
||||||
|
summary: oldItem.summary,
|
||||||
|
body: oldItem.body,
|
||||||
|
rawNotification: oldItem.rawNotification,
|
||||||
|
appeared: true,
|
||||||
|
read: oldItem.read,
|
||||||
|
dismissed: oldItem.dismissed
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ParallelAnimation {
|
Connections {
|
||||||
id: appearAnimation
|
target: Quickshell
|
||||||
NumberAnimation {
|
function onScreensChanged() {
|
||||||
target: notificationDelegate
|
if (panelWindow.screen) {
|
||||||
property: "opacity"
|
x = panelWindow.screen.width - panelWindow.width - 20;
|
||||||
to: 1
|
|
||||||
duration: 150
|
|
||||||
}
|
|
||||||
NumberAnimation {
|
|
||||||
target: notificationDelegate
|
|
||||||
property: "height"
|
|
||||||
to: contentRow.height + 20
|
|
||||||
duration: 150
|
|
||||||
}
|
|
||||||
NumberAnimation {
|
|
||||||
target: notificationDelegate
|
|
||||||
property: "x"
|
|
||||||
to: 0
|
|
||||||
duration: 150
|
|
||||||
easing.type: Easing.OutQuad
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
if (!appeared) {
|
|
||||||
opacity = 0;
|
|
||||||
height = 0;
|
|
||||||
x = width;
|
|
||||||
appearAnimation.start();
|
|
||||||
for (let i = 0; i < notificationModel.count; i++) {
|
|
||||||
if (notificationModel.get(i).id === notificationDelegate.id) {
|
|
||||||
var oldItem = notificationModel.get(i);
|
|
||||||
notificationModel.set(i, {
|
|
||||||
id: oldItem.id,
|
|
||||||
appName: oldItem.appName,
|
|
||||||
summary: oldItem.summary,
|
|
||||||
body: oldItem.body,
|
|
||||||
rawNotification: oldItem.rawNotification,
|
|
||||||
appeared: true,
|
|
||||||
read: oldItem.read,
|
|
||||||
dismissed: oldItem.dismissed
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: Quickshell
|
|
||||||
function onScreensChanged() {
|
|
||||||
if (window.screen) {
|
|
||||||
x = window.screen.width - width - 20;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -255,7 +255,7 @@ Item {
|
||||||
font.pixelSize: 14
|
font.pixelSize: 14
|
||||||
color: Theme.textSecondary
|
color: Theme.textSecondary
|
||||||
Layout.alignment: Qt.AlignCenter
|
Layout.alignment: Qt.AlignCenter
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 24
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,17 @@ ColumnLayout {
|
||||||
|
|
||||||
// Get list of available monitors/screens
|
// Get list of available monitors/screens
|
||||||
property var monitors: Quickshell.screens || []
|
property var monitors: Quickshell.screens || []
|
||||||
|
|
||||||
|
// Sorted monitors by name
|
||||||
|
property var sortedMonitors: {
|
||||||
|
let sorted = [...monitors];
|
||||||
|
sorted.sort((a, b) => {
|
||||||
|
let nameA = a.name || "Unknown";
|
||||||
|
let nameB = b.name || "Unknown";
|
||||||
|
return nameA.localeCompare(nameB);
|
||||||
|
});
|
||||||
|
return sorted;
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
@ -68,7 +79,7 @@ ColumnLayout {
|
||||||
spacing: 8
|
spacing: 8
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: root.monitors
|
model: root.sortedMonitors
|
||||||
delegate: Rectangle {
|
delegate: Rectangle {
|
||||||
id: barCheckbox
|
id: barCheckbox
|
||||||
property bool isChecked: false
|
property bool isChecked: false
|
||||||
|
|
@ -171,7 +182,7 @@ ColumnLayout {
|
||||||
spacing: 8
|
spacing: 8
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: root.monitors
|
model: root.sortedMonitors
|
||||||
delegate: Rectangle {
|
delegate: Rectangle {
|
||||||
id: dockCheckbox
|
id: dockCheckbox
|
||||||
property bool isChecked: false
|
property bool isChecked: false
|
||||||
|
|
@ -277,7 +288,7 @@ ColumnLayout {
|
||||||
spacing: 8
|
spacing: 8
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: root.monitors
|
model: root.sortedMonitors
|
||||||
delegate: Rectangle {
|
delegate: Rectangle {
|
||||||
id: notificationCheckbox
|
id: notificationCheckbox
|
||||||
property bool isChecked: false
|
property bool isChecked: false
|
||||||
|
|
|
||||||
|
|
@ -107,15 +107,11 @@ ColumnLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: 16
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
spacing: 4
|
spacing: 4
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 58
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "User Interface"
|
text: "User Interface"
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ ColumnLayout {
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
spacing: 16
|
spacing: 16
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 58
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "Bluetooth"
|
text: "Bluetooth"
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,7 @@ ColumnLayout {
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
spacing: 4
|
spacing: 4
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 58
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "Weather"
|
text: "Weather"
|
||||||
|
|
|
||||||
53
shell.qml
53
shell.qml
|
|
@ -22,17 +22,18 @@ Scope {
|
||||||
property var notificationHistoryWin: notificationHistoryWin
|
property var notificationHistoryWin: notificationHistoryWin
|
||||||
property bool pendingReload: false
|
property bool pendingReload: false
|
||||||
|
|
||||||
// Round volume to nearest 5% increment for consistent control
|
// Helper function to round value to nearest step
|
||||||
function roundToStep(value, step) {
|
function roundToStep(value, step) {
|
||||||
return Math.round(value / step) * step;
|
return Math.round(value / step) * step;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Current audio volume (0-100), synced with system
|
// Volume property reflecting current audio volume in 0-100
|
||||||
|
// Will be kept in sync dynamically below
|
||||||
property int volume: (defaultAudioSink && defaultAudioSink.audio && !defaultAudioSink.audio.muted)
|
property int volume: (defaultAudioSink && defaultAudioSink.audio && !defaultAudioSink.audio.muted)
|
||||||
? Math.round(defaultAudioSink.audio.volume * 100)
|
? Math.round(defaultAudioSink.audio.volume * 100)
|
||||||
: 0
|
: 0
|
||||||
|
|
||||||
// Update volume with 5-step increments and apply to audio sink
|
// Function to update volume with clamping, stepping, and applying to audio sink
|
||||||
function updateVolume(vol) {
|
function updateVolume(vol) {
|
||||||
var clamped = Math.max(0, Math.min(100, vol));
|
var clamped = Math.max(0, Math.min(100, vol));
|
||||||
var stepped = roundToStep(clamped, 5);
|
var stepped = roundToStep(clamped, 5);
|
||||||
|
|
@ -52,13 +53,8 @@ Scope {
|
||||||
property var notificationHistoryWin: notificationHistoryWin
|
property var notificationHistoryWin: notificationHistoryWin
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create dock for each monitor (respects dockMonitors setting)
|
Dock {
|
||||||
Variants {
|
id: dock
|
||||||
model: Quickshell.screens
|
|
||||||
|
|
||||||
Dock {
|
|
||||||
property var modelData
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Applauncher {
|
Applauncher {
|
||||||
|
|
@ -83,17 +79,16 @@ Scope {
|
||||||
NotificationServer {
|
NotificationServer {
|
||||||
id: notificationServer
|
id: notificationServer
|
||||||
onNotification: function (notification) {
|
onNotification: function (notification) {
|
||||||
console.log("Notification received:", notification.appName);
|
|
||||||
notification.tracked = true;
|
notification.tracked = true;
|
||||||
|
if (notificationPopup.notificationsVisible) {
|
||||||
// Distribute notification to all visible notification popups
|
// Add notification to all popup instances
|
||||||
for (let i = 0; i < notificationPopupVariants.count; i++) {
|
for (let i = 0; i < notificationPopup.children.length; i++) {
|
||||||
let popup = notificationPopupVariants.objectAt(i);
|
let child = notificationPopup.children[i];
|
||||||
if (popup && popup.notificationsVisible) {
|
if (child.addNotification) {
|
||||||
popup.addNotification(notification);
|
child.addNotification(notification);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notificationHistoryWin) {
|
if (notificationHistoryWin) {
|
||||||
notificationHistoryWin.addToHistory({
|
notificationHistoryWin.addToHistory({
|
||||||
id: notification.id,
|
id: notification.id,
|
||||||
|
|
@ -107,19 +102,8 @@ Scope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create notification popups for each selected monitor
|
NotificationPopup {
|
||||||
Variants {
|
id: notificationPopup
|
||||||
id: notificationPopupVariants
|
|
||||||
model: Quickshell.screens
|
|
||||||
|
|
||||||
NotificationPopup {
|
|
||||||
property var modelData
|
|
||||||
barVisible: bar.visible
|
|
||||||
screen: modelData
|
|
||||||
visible: notificationsVisible && notificationModel.count > 0 &&
|
|
||||||
(Settings.settings.notificationMonitors.includes(modelData.name) ||
|
|
||||||
(Settings.settings.notificationMonitors.length === 0)) // Show on all if none selected
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NotificationHistory {
|
NotificationHistory {
|
||||||
|
|
@ -137,7 +121,7 @@ Scope {
|
||||||
appLauncherPanel: appLauncherPanel
|
appLauncherPanel: appLauncherPanel
|
||||||
lockScreen: lockScreen
|
lockScreen: lockScreen
|
||||||
idleInhibitor: idleInhibitor
|
idleInhibitor: idleInhibitor
|
||||||
notificationPopupVariants: notificationPopupVariants
|
notificationPopup: notificationPopup
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
|
|
@ -154,12 +138,11 @@ Scope {
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: reloadTimer
|
id: reloadTimer
|
||||||
interval: 500
|
interval: 500 // ms
|
||||||
repeat: false
|
repeat: false
|
||||||
onTriggered: Quickshell.reload(true)
|
onTriggered: Quickshell.reload(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle screen configuration changes (delay reload if locked)
|
|
||||||
Connections {
|
Connections {
|
||||||
target: Quickshell
|
target: Quickshell
|
||||||
function onScreensChanged() {
|
function onScreensChanged() {
|
||||||
|
|
@ -191,4 +174,4 @@ Scope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue