Bugfix: PanelWithOverlay would close when clicking in the background of the panel.
This commit is contained in:
parent
0b5f1cd9e5
commit
8c7f6e491d
6 changed files with 543 additions and 314 deletions
|
|
@ -7,13 +7,67 @@ import qs.Settings
|
||||||
|
|
||||||
PanelWithOverlay {
|
PanelWithOverlay {
|
||||||
id: ioSelector
|
id: ioSelector
|
||||||
signal panelClosed()
|
|
||||||
property int tabIndex: 0
|
property int tabIndex: 0
|
||||||
property Item anchorItem: null
|
property Item anchorItem: null
|
||||||
|
|
||||||
|
signal panelClosed()
|
||||||
|
|
||||||
|
function sinkNodes() {
|
||||||
|
let nodes = Pipewire.nodes && Pipewire.nodes.values ? Pipewire.nodes.values.filter(function(n) {
|
||||||
|
return n.isSink && n.audio && n.isStream === false;
|
||||||
|
}) : [];
|
||||||
|
if (Pipewire.defaultAudioSink)
|
||||||
|
nodes = nodes.slice().sort(function(a, b) {
|
||||||
|
if (a.id === Pipewire.defaultAudioSink.id)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (b.id === Pipewire.defaultAudioSink.id)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sourceNodes() {
|
||||||
|
let nodes = Pipewire.nodes && Pipewire.nodes.values ? Pipewire.nodes.values.filter(function(n) {
|
||||||
|
return !n.isSink && n.audio && n.isStream === false;
|
||||||
|
}) : [];
|
||||||
|
if (Pipewire.defaultAudioSource)
|
||||||
|
nodes = nodes.slice().sort(function(a, b) {
|
||||||
|
if (a.id === Pipewire.defaultAudioSource.id)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (b.id === Pipewire.defaultAudioSource.id)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
if (Pipewire.nodes && Pipewire.nodes.values) {
|
||||||
|
for (var i = 0; i < Pipewire.nodes.values.length; ++i) {
|
||||||
|
var n = Pipewire.nodes.values[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Component.onDestruction: {
|
||||||
|
}
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (!visible)
|
||||||
|
panelClosed();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Bind all Pipewire nodes so their properties are valid
|
// Bind all Pipewire nodes so their properties are valid
|
||||||
PwObjectTracker {
|
PwObjectTracker {
|
||||||
id: nodeTracker
|
id: nodeTracker
|
||||||
|
|
||||||
objects: Pipewire.nodes
|
objects: Pipewire.nodes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,6 +81,11 @@ PanelWithOverlay {
|
||||||
anchors.topMargin: 4
|
anchors.topMargin: 4
|
||||||
anchors.rightMargin: 4
|
anchors.rightMargin: 4
|
||||||
|
|
||||||
|
// Prevent closing when clicking in the panel bg
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 16
|
anchors.margins: 16
|
||||||
|
|
@ -40,45 +99,59 @@ PanelWithOverlay {
|
||||||
|
|
||||||
Tabs {
|
Tabs {
|
||||||
id: ioTabs
|
id: ioTabs
|
||||||
tabsModel: [
|
|
||||||
{ label: "Output", icon: "volume_up" },
|
tabsModel: [{
|
||||||
{ label: "Input", icon: "mic" }
|
"label": "Output",
|
||||||
]
|
"icon": "volume_up"
|
||||||
|
}, {
|
||||||
|
"label": "Input",
|
||||||
|
"icon": "mic"
|
||||||
|
}]
|
||||||
currentIndex: tabIndex
|
currentIndex: tabIndex
|
||||||
onTabChanged: {
|
onTabChanged: {
|
||||||
tabIndex = currentIndex;
|
tabIndex = currentIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add vertical space between tabs and entries
|
// Add vertical space between tabs and entries
|
||||||
Item { height: 36; Layout.fillWidth: true }
|
Item {
|
||||||
|
height: 36
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
// Output Devices
|
// Output Devices
|
||||||
Flickable {
|
Flickable {
|
||||||
id: sinkList
|
id: sinkList
|
||||||
|
|
||||||
visible: tabIndex === 0
|
visible: tabIndex === 0
|
||||||
contentHeight: sinkColumn.height
|
contentHeight: sinkColumn.height
|
||||||
clip: true
|
clip: true
|
||||||
interactive: contentHeight > height
|
interactive: contentHeight > height
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 220
|
height: 220
|
||||||
ScrollBar.vertical: ScrollBar {}
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: sinkColumn
|
id: sinkColumn
|
||||||
|
|
||||||
width: sinkList.width
|
width: sinkList.width
|
||||||
spacing: 6
|
spacing: 6
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: ioSelector.sinkNodes()
|
model: ioSelector.sinkNodes()
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 36
|
height: 36
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
radius: 6
|
radius: 6
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 6
|
anchors.margins: 6
|
||||||
spacing: 8
|
spacing: 8
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "volume_up"
|
text: "volume_up"
|
||||||
font.family: "Material Symbols Outlined"
|
font.family: "Material Symbols Outlined"
|
||||||
|
|
@ -86,10 +159,12 @@ PanelWithOverlay {
|
||||||
color: (Pipewire.defaultAudioSink && Pipewire.defaultAudioSink.id === modelData.id) ? Theme.accentPrimary : Theme.textPrimary
|
color: (Pipewire.defaultAudioSink && Pipewire.defaultAudioSink.id === modelData.id) ? Theme.accentPrimary : Theme.textPrimary
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: 1
|
spacing: 1
|
||||||
Layout.maximumWidth: sinkList.width - 120 // Reserve space for the Set button
|
Layout.maximumWidth: sinkList.width - 120 // Reserve space for the Set button
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: modelData.nickname || modelData.description || modelData.name
|
text: modelData.nickname || modelData.description || modelData.name
|
||||||
font.bold: true
|
font.bold: true
|
||||||
|
|
@ -99,6 +174,7 @@ PanelWithOverlay {
|
||||||
maximumLineCount: 1
|
maximumLineCount: 1
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: modelData.description !== modelData.nickname ? modelData.description : ""
|
text: modelData.description !== modelData.nickname ? modelData.description : ""
|
||||||
font.pixelSize: 10
|
font.pixelSize: 10
|
||||||
|
|
@ -107,15 +183,19 @@ PanelWithOverlay {
|
||||||
maximumLineCount: 1
|
maximumLineCount: 1
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
visible: Pipewire.preferredDefaultAudioSink !== modelData
|
visible: Pipewire.preferredDefaultAudioSink !== modelData
|
||||||
width: 60; height: 20
|
width: 60
|
||||||
|
height: 20
|
||||||
radius: 4
|
radius: 4
|
||||||
color: Theme.accentPrimary
|
color: Theme.accentPrimary
|
||||||
border.color: Theme.accentPrimary
|
border.color: Theme.accentPrimary
|
||||||
border.width: 1
|
border.width: 1
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: "Set"
|
text: "Set"
|
||||||
|
|
@ -123,12 +203,15 @@ PanelWithOverlay {
|
||||||
font.pixelSize: 10
|
font.pixelSize: 10
|
||||||
font.bold: true
|
font.bold: true
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: Pipewire.preferredDefaultAudioSink = modelData
|
onClicked: Pipewire.preferredDefaultAudioSink = modelData
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "(Current)"
|
text: "(Current)"
|
||||||
visible: Pipewire.defaultAudioSink && Pipewire.defaultAudioSink.id === modelData.id
|
visible: Pipewire.defaultAudioSink && Pipewire.defaultAudioSink.id === modelData.id
|
||||||
|
|
@ -136,37 +219,51 @@ PanelWithOverlay {
|
||||||
font.pixelSize: 10
|
font.pixelSize: 10
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ScrollBar.vertical: ScrollBar {
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Input Devices
|
// Input Devices
|
||||||
Flickable {
|
Flickable {
|
||||||
id: sourceList
|
id: sourceList
|
||||||
|
|
||||||
visible: tabIndex === 1
|
visible: tabIndex === 1
|
||||||
contentHeight: sourceColumn.height
|
contentHeight: sourceColumn.height
|
||||||
clip: true
|
clip: true
|
||||||
interactive: contentHeight > height
|
interactive: contentHeight > height
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 220
|
height: 220
|
||||||
ScrollBar.vertical: ScrollBar {}
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: sourceColumn
|
id: sourceColumn
|
||||||
|
|
||||||
width: sourceList.width
|
width: sourceList.width
|
||||||
spacing: 6
|
spacing: 6
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: ioSelector.sourceNodes()
|
model: ioSelector.sourceNodes()
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 36
|
height: 36
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
radius: 6
|
radius: 6
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 6
|
anchors.margins: 6
|
||||||
spacing: 8
|
spacing: 8
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "mic"
|
text: "mic"
|
||||||
font.family: "Material Symbols Outlined"
|
font.family: "Material Symbols Outlined"
|
||||||
|
|
@ -174,10 +271,12 @@ PanelWithOverlay {
|
||||||
color: (Pipewire.defaultAudioSource && Pipewire.defaultAudioSource.id === modelData.id) ? Theme.accentPrimary : Theme.textPrimary
|
color: (Pipewire.defaultAudioSource && Pipewire.defaultAudioSource.id === modelData.id) ? Theme.accentPrimary : Theme.textPrimary
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: 1
|
spacing: 1
|
||||||
Layout.maximumWidth: sourceList.width - 120 // Reserve space for the Set button
|
Layout.maximumWidth: sourceList.width - 120 // Reserve space for the Set button
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: modelData.nickname || modelData.description || modelData.name
|
text: modelData.nickname || modelData.description || modelData.name
|
||||||
font.bold: true
|
font.bold: true
|
||||||
|
|
@ -187,6 +286,7 @@ PanelWithOverlay {
|
||||||
maximumLineCount: 1
|
maximumLineCount: 1
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: modelData.description !== modelData.nickname ? modelData.description : ""
|
text: modelData.description !== modelData.nickname ? modelData.description : ""
|
||||||
font.pixelSize: 10
|
font.pixelSize: 10
|
||||||
|
|
@ -195,15 +295,19 @@ PanelWithOverlay {
|
||||||
maximumLineCount: 1
|
maximumLineCount: 1
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
visible: Pipewire.preferredDefaultAudioSource !== modelData
|
visible: Pipewire.preferredDefaultAudioSource !== modelData
|
||||||
width: 60; height: 20
|
width: 60
|
||||||
|
height: 20
|
||||||
radius: 4
|
radius: 4
|
||||||
color: Theme.accentPrimary
|
color: Theme.accentPrimary
|
||||||
border.color: Theme.accentPrimary
|
border.color: Theme.accentPrimary
|
||||||
border.width: 1
|
border.width: 1
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: "Set"
|
text: "Set"
|
||||||
|
|
@ -211,12 +315,15 @@ PanelWithOverlay {
|
||||||
font.pixelSize: 10
|
font.pixelSize: 10
|
||||||
font.bold: true
|
font.bold: true
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: Pipewire.preferredDefaultAudioSource = modelData
|
onClicked: Pipewire.preferredDefaultAudioSource = modelData
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "(Current)"
|
text: "(Current)"
|
||||||
visible: Pipewire.defaultAudioSource && Pipewire.defaultAudioSource.id === modelData.id
|
visible: Pipewire.defaultAudioSource && Pipewire.defaultAudioSource.id === modelData.id
|
||||||
|
|
@ -224,55 +331,25 @@ PanelWithOverlay {
|
||||||
font.pixelSize: 10
|
font.pixelSize: 10
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function sinkNodes() {
|
ScrollBar.vertical: ScrollBar {
|
||||||
let nodes = Pipewire.nodes && Pipewire.nodes.values
|
}
|
||||||
? Pipewire.nodes.values.filter(function(n) {
|
|
||||||
return n.isSink && n.audio && n.isStream === false;
|
|
||||||
})
|
|
||||||
: [];
|
|
||||||
if (Pipewire.defaultAudioSink) {
|
|
||||||
nodes = nodes.slice().sort(function(a, b) {
|
|
||||||
if (a.id === Pipewire.defaultAudioSink.id) return -1;
|
|
||||||
if (b.id === Pipewire.defaultAudioSink.id) return 1;
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return nodes;
|
|
||||||
}
|
|
||||||
function sourceNodes() {
|
|
||||||
let nodes = Pipewire.nodes && Pipewire.nodes.values
|
|
||||||
? Pipewire.nodes.values.filter(function(n) {
|
|
||||||
return !n.isSink && n.audio && n.isStream === false;
|
|
||||||
})
|
|
||||||
: [];
|
|
||||||
if (Pipewire.defaultAudioSource) {
|
|
||||||
nodes = nodes.slice().sort(function(a, b) {
|
|
||||||
if (a.id === Pipewire.defaultAudioSource.id) return -1;
|
|
||||||
if (b.id === Pipewire.defaultAudioSource.id) return 1;
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return nodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
if (Pipewire.nodes && Pipewire.nodes.values) {
|
|
||||||
for (var i = 0; i < Pipewire.nodes.values.length; ++i) {
|
|
||||||
var n = Pipewire.nodes.values[i];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: Pipewire
|
|
||||||
function onReadyChanged() {
|
function onReadyChanged() {
|
||||||
if (Pipewire.ready && Pipewire.nodes && Pipewire.nodes.values) {
|
if (Pipewire.ready && Pipewire.nodes && Pipewire.nodes.values) {
|
||||||
for (var i = 0; i < Pipewire.nodes.values.length; ++i) {
|
for (var i = 0; i < Pipewire.nodes.values.length; ++i) {
|
||||||
|
|
@ -280,15 +357,14 @@ PanelWithOverlay {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDefaultAudioSinkChanged() {
|
function onDefaultAudioSinkChanged() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDefaultAudioSourceChanged() {
|
function onDefaultAudioSourceChanged() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
target: Pipewire
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onDestruction: {
|
|
||||||
}
|
|
||||||
onVisibleChanged: {
|
|
||||||
if (!visible) panelClosed();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
|
import "../../Helpers/Holidays.js" as Holidays
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import Quickshell.Wayland
|
||||||
import qs.Components
|
import qs.Components
|
||||||
import qs.Settings
|
import qs.Settings
|
||||||
import Quickshell.Wayland
|
|
||||||
import "../../Helpers/Holidays.js" as Holidays
|
|
||||||
|
|
||||||
PanelWithOverlay {
|
PanelWithOverlay {
|
||||||
id: calendarOverlay
|
id: calendarOverlay
|
||||||
|
|
@ -22,6 +22,11 @@ PanelWithOverlay {
|
||||||
anchors.topMargin: 4
|
anchors.topMargin: 4
|
||||||
anchors.rightMargin: 4
|
anchors.rightMargin: 4
|
||||||
|
|
||||||
|
// Prevent closing when clicking in the panel bg
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 16
|
anchors.margins: 16
|
||||||
|
|
@ -60,13 +65,15 @@ PanelWithOverlay {
|
||||||
calendar.month = newDate.getMonth();
|
calendar.month = newDate.getMonth();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DayOfWeekRow {
|
DayOfWeekRow {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: 0
|
spacing: 0
|
||||||
Layout.leftMargin: 8 // Align with grid
|
Layout.leftMargin: 8 // Align with grid
|
||||||
Layout.rightMargin: 8
|
Layout.rightMargin: 8
|
||||||
|
|
||||||
delegate: Text {
|
delegate: Text {
|
||||||
text: shortName
|
text: shortName
|
||||||
color: Theme.textPrimary
|
color: Theme.textPrimary
|
||||||
|
|
@ -77,16 +84,11 @@ PanelWithOverlay {
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
width: 32
|
width: 32
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MonthGrid {
|
MonthGrid {
|
||||||
id: calendar
|
id: calendar
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.leftMargin: 8
|
|
||||||
Layout.rightMargin: 8
|
|
||||||
spacing: 0
|
|
||||||
month: Time.date.getMonth()
|
|
||||||
year: Time.date.getFullYear()
|
|
||||||
|
|
||||||
property var holidays: []
|
property var holidays: []
|
||||||
|
|
||||||
|
|
@ -96,12 +98,19 @@ PanelWithOverlay {
|
||||||
calendar.holidays = holidays;
|
calendar.holidays = holidays;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: 8
|
||||||
|
Layout.rightMargin: 8
|
||||||
|
spacing: 0
|
||||||
|
month: Time.date.getMonth()
|
||||||
|
year: Time.date.getFullYear()
|
||||||
onMonthChanged: updateHolidays()
|
onMonthChanged: updateHolidays()
|
||||||
onYearChanged: updateHolidays()
|
onYearChanged: updateHolidays()
|
||||||
Component.onCompleted: updateHolidays()
|
Component.onCompleted: updateHolidays()
|
||||||
|
|
||||||
// Optionally, update when the panel becomes visible
|
// Optionally, update when the panel becomes visible
|
||||||
Connections {
|
Connections {
|
||||||
target: calendarOverlay
|
|
||||||
function onVisibleChanged() {
|
function onVisibleChanged() {
|
||||||
if (calendarOverlay.visible) {
|
if (calendarOverlay.visible) {
|
||||||
calendar.month = Time.date.getMonth();
|
calendar.month = Time.date.getMonth();
|
||||||
|
|
@ -109,29 +118,35 @@ PanelWithOverlay {
|
||||||
calendar.updateHolidays();
|
calendar.updateHolidays();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
target: calendarOverlay
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate: Rectangle {
|
delegate: Rectangle {
|
||||||
width: 32
|
|
||||||
height: 32
|
|
||||||
radius: 8
|
|
||||||
property var holidayInfo: calendar.holidays.filter(function(h) {
|
property var holidayInfo: calendar.holidays.filter(function(h) {
|
||||||
var d = new Date(h.date);
|
var d = new Date(h.date);
|
||||||
return d.getDate() === model.day && d.getMonth() === model.month && d.getFullYear() === model.year;
|
return d.getDate() === model.day && d.getMonth() === model.month && d.getFullYear() === model.year;
|
||||||
})
|
})
|
||||||
property bool isHoliday: holidayInfo.length > 0
|
property bool isHoliday: holidayInfo.length > 0
|
||||||
|
|
||||||
|
width: 32
|
||||||
|
height: 32
|
||||||
|
radius: 8
|
||||||
color: {
|
color: {
|
||||||
if (model.today)
|
if (model.today)
|
||||||
return Theme.accentPrimary;
|
return Theme.accentPrimary;
|
||||||
|
|
||||||
if (mouseArea2.containsMouse)
|
if (mouseArea2.containsMouse)
|
||||||
return Theme.backgroundTertiary;
|
return Theme.backgroundTertiary;
|
||||||
|
|
||||||
return "transparent";
|
return "transparent";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Holiday dot indicator
|
// Holiday dot indicator
|
||||||
Rectangle {
|
Rectangle {
|
||||||
visible: isHoliday
|
visible: isHoliday
|
||||||
width: 4; height: 4
|
width: 4
|
||||||
|
height: 4
|
||||||
radius: 4
|
radius: 4
|
||||||
color: Theme.accentTertiary
|
color: Theme.accentTertiary
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
|
|
@ -145,7 +160,7 @@ PanelWithOverlay {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: model.day
|
text: model.day
|
||||||
color: model.today ? Theme.onAccent : Theme.textPrimary
|
color: model.today ? Theme.onAccent : Theme.textPrimary
|
||||||
opacity: model.month === calendar.month ? (mouseArea2.containsMouse ? 1.0 : 0.7) : 0.3
|
opacity: model.month === calendar.month ? (mouseArea2.containsMouse ? 1 : 0.7) : 0.3
|
||||||
font.pixelSize: 13
|
font.pixelSize: 13
|
||||||
font.family: Theme.fontFamily
|
font.family: Theme.fontFamily
|
||||||
font.bold: model.today ? true : false
|
font.bold: model.today ? true : false
|
||||||
|
|
@ -153,6 +168,7 @@ PanelWithOverlay {
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: mouseArea2
|
id: mouseArea2
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
onEntered: {
|
onEntered: {
|
||||||
|
|
@ -167,21 +183,28 @@ PanelWithOverlay {
|
||||||
onExited: holidayTooltip.tooltipVisible = false
|
onExited: holidayTooltip.tooltipVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: 150
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledTooltip {
|
StyledTooltip {
|
||||||
id: holidayTooltip
|
id: holidayTooltip
|
||||||
|
|
||||||
text: ""
|
text: ""
|
||||||
tooltipVisible: false
|
tooltipVisible: false
|
||||||
targetItem: null
|
targetItem: null
|
||||||
delay: 100
|
delay: 100
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: 150
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,61 +1,31 @@
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import qs.Settings
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import qs.Components
|
import qs.Components
|
||||||
|
import qs.Settings
|
||||||
|
|
||||||
PanelWithOverlay {
|
PanelWithOverlay {
|
||||||
id: notificationHistoryWin
|
id: notificationHistoryWin
|
||||||
|
|
||||||
property string historyFilePath: Settings.settingsDir + "notification_history.json"
|
property string historyFilePath: Settings.settingsDir + "notification_history.json"
|
||||||
property bool hasUnread: notificationHistoryWinRect.hasUnread && !notificationHistoryWinRect.visible
|
property bool hasUnread: notificationHistoryWinRect.hasUnread && !notificationHistoryWinRect.visible
|
||||||
function addToHistory(notification) { notificationHistoryWinRect.addToHistory(notification) }
|
|
||||||
|
function addToHistory(notification) {
|
||||||
|
notificationHistoryWinRect.addToHistory(notification);
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: notificationHistoryWinRect
|
id: notificationHistoryWinRect
|
||||||
implicitWidth: 400
|
|
||||||
property int maxPopupHeight: 800
|
property int maxPopupHeight: 800
|
||||||
property int minPopupHeight: 210
|
property int minPopupHeight: 210
|
||||||
property int contentHeight: headerRow.height + historyList.contentHeight + 56
|
property int contentHeight: headerRow.height + historyList.contentHeight + 56
|
||||||
implicitHeight: Math.max(Math.min(contentHeight, maxPopupHeight), minPopupHeight)
|
|
||||||
visible: parent.visible
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.topMargin: 4
|
|
||||||
anchors.rightMargin: 4
|
|
||||||
color: Theme.backgroundPrimary
|
|
||||||
radius: 20
|
|
||||||
|
|
||||||
property int maxHistory: 100
|
property int maxHistory: 100
|
||||||
property bool hasUnread: false
|
property bool hasUnread: false
|
||||||
|
|
||||||
signal unreadChanged(bool hasUnread)
|
signal unreadChanged(bool hasUnread)
|
||||||
|
|
||||||
ListModel {
|
|
||||||
id: historyModel
|
|
||||||
}
|
|
||||||
|
|
||||||
FileView {
|
|
||||||
id: historyFileView
|
|
||||||
path: notificationHistoryWin.historyFilePath
|
|
||||||
blockLoading: true
|
|
||||||
printErrors: true
|
|
||||||
watchChanges: true
|
|
||||||
|
|
||||||
JsonAdapter {
|
|
||||||
id: historyAdapter
|
|
||||||
property var notifications: []
|
|
||||||
}
|
|
||||||
|
|
||||||
onFileChanged: historyFileView.reload()
|
|
||||||
onLoaded: notificationHistoryWinRect.loadHistory()
|
|
||||||
onLoadFailed: function (error) {
|
|
||||||
historyAdapter.notifications = [];
|
|
||||||
historyFileView.writeAdapter();
|
|
||||||
}
|
|
||||||
Component.onCompleted: if (path)
|
|
||||||
reload()
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateHasUnread() {
|
function updateHasUnread() {
|
||||||
var unread = false;
|
var unread = false;
|
||||||
for (let i = 0; i < historyModel.count; ++i) {
|
for (let i = 0; i < historyModel.count; ++i) {
|
||||||
|
|
@ -80,9 +50,11 @@ PanelWithOverlay {
|
||||||
if (typeof n === 'object' && n !== null) {
|
if (typeof n === 'object' && n !== null) {
|
||||||
if (n.read === undefined)
|
if (n.read === undefined)
|
||||||
n.read = false;
|
n.read = false;
|
||||||
|
|
||||||
// Mark as read if window is open
|
// Mark as read if window is open
|
||||||
if (notificationHistoryWinRect.visible)
|
if (notificationHistoryWinRect.visible)
|
||||||
n.read = true;
|
n.read = true;
|
||||||
|
|
||||||
historyModel.append(n);
|
historyModel.append(n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -95,19 +67,19 @@ PanelWithOverlay {
|
||||||
const count = Math.min(historyModel.count, maxHistory);
|
const count = Math.min(historyModel.count, maxHistory);
|
||||||
for (let i = 0; i < count; ++i) {
|
for (let i = 0; i < count; ++i) {
|
||||||
let obj = historyModel.get(i);
|
let obj = historyModel.get(i);
|
||||||
if (typeof obj === 'object' && obj !== null) {
|
if (typeof obj === 'object' && obj !== null)
|
||||||
historyArray.push({
|
historyArray.push({
|
||||||
id: obj.id,
|
"id": obj.id,
|
||||||
appName: obj.appName,
|
"appName": obj.appName,
|
||||||
summary: obj.summary,
|
"summary": obj.summary,
|
||||||
body: obj.body,
|
"body": obj.body,
|
||||||
timestamp: obj.timestamp,
|
"timestamp": obj.timestamp,
|
||||||
read: obj.read === undefined ? false : obj.read
|
"read": obj.read === undefined ? false : obj.read
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
historyAdapter.notifications = historyArray;
|
historyAdapter.notifications = historyArray;
|
||||||
Qt.callLater(function () {
|
Qt.callLater(function() {
|
||||||
historyFileView.writeAdapter();
|
historyFileView.writeAdapter();
|
||||||
});
|
});
|
||||||
updateHasUnread();
|
updateHasUnread();
|
||||||
|
|
@ -116,12 +88,12 @@ PanelWithOverlay {
|
||||||
function addToHistory(notification) {
|
function addToHistory(notification) {
|
||||||
if (!notification.id)
|
if (!notification.id)
|
||||||
notification.id = Date.now();
|
notification.id = Date.now();
|
||||||
|
|
||||||
if (!notification.timestamp)
|
if (!notification.timestamp)
|
||||||
notification.timestamp = new Date().toISOString();
|
notification.timestamp = new Date().toISOString();
|
||||||
|
|
||||||
// Mark as read if window is open
|
// Mark as read if window is open
|
||||||
notification.read = visible;
|
notification.read = visible;
|
||||||
|
|
||||||
// Remove duplicate by id
|
// Remove duplicate by id
|
||||||
for (let i = 0; i < historyModel.count; ++i) {
|
for (let i = 0; i < historyModel.count; ++i) {
|
||||||
if (historyModel.get(i).id === notification.id) {
|
if (historyModel.get(i).id === notification.id) {
|
||||||
|
|
@ -129,11 +101,10 @@ PanelWithOverlay {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
historyModel.insert(0, notification);
|
historyModel.insert(0, notification);
|
||||||
|
|
||||||
if (historyModel.count > maxHistory)
|
if (historyModel.count > maxHistory)
|
||||||
historyModel.remove(maxHistory);
|
historyModel.remove(maxHistory);
|
||||||
|
|
||||||
saveHistory();
|
saveHistory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,6 +117,7 @@ PanelWithOverlay {
|
||||||
function formatTimestamp(ts) {
|
function formatTimestamp(ts) {
|
||||||
if (!ts)
|
if (!ts)
|
||||||
return "";
|
return "";
|
||||||
|
|
||||||
var date = typeof ts === "number" ? new Date(ts) : new Date(Date.parse(ts));
|
var date = typeof ts === "number" ? new Date(ts) : new Date(Date.parse(ts));
|
||||||
var y = date.getFullYear();
|
var y = date.getFullYear();
|
||||||
var m = (date.getMonth() + 1).toString().padStart(2, '0');
|
var m = (date.getMonth() + 1).toString().padStart(2, '0');
|
||||||
|
|
@ -155,6 +127,15 @@ PanelWithOverlay {
|
||||||
return `${y}-${m}-${d} ${h}:${min}`;
|
return `${y}-${m}-${d} ${h}:${min}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
implicitWidth: 400
|
||||||
|
implicitHeight: Math.max(Math.min(contentHeight, maxPopupHeight), minPopupHeight)
|
||||||
|
visible: parent.visible
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.topMargin: 4
|
||||||
|
anchors.rightMargin: 4
|
||||||
|
color: Theme.backgroundPrimary
|
||||||
|
radius: 20
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
// Mark all as read when popup is opened
|
// Mark all as read when popup is opened
|
||||||
|
|
@ -167,9 +148,46 @@ PanelWithOverlay {
|
||||||
}
|
}
|
||||||
if (changed)
|
if (changed)
|
||||||
saveHistory();
|
saveHistory();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prevent closing when clicking in the panel bg
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
ListModel {
|
||||||
|
id: historyModel
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: historyFileView
|
||||||
|
|
||||||
|
path: notificationHistoryWin.historyFilePath
|
||||||
|
blockLoading: true
|
||||||
|
printErrors: true
|
||||||
|
watchChanges: true
|
||||||
|
onFileChanged: historyFileView.reload()
|
||||||
|
onLoaded: notificationHistoryWinRect.loadHistory()
|
||||||
|
onLoadFailed: function(error) {
|
||||||
|
historyAdapter.notifications = [];
|
||||||
|
historyFileView.writeAdapter();
|
||||||
|
}
|
||||||
|
Component.onCompleted: {
|
||||||
|
if (path) {
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonAdapter {
|
||||||
|
id: historyAdapter
|
||||||
|
|
||||||
|
property var notifications: []
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: notificationHistoryWinRect.width
|
width: notificationHistoryWinRect.width
|
||||||
height: notificationHistoryWinRect.height
|
height: notificationHistoryWinRect.height
|
||||||
|
|
@ -184,6 +202,7 @@ PanelWithOverlay {
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
id: headerRow
|
id: headerRow
|
||||||
|
|
||||||
spacing: 4
|
spacing: 4
|
||||||
anchors.topMargin: 4
|
anchors.topMargin: 4
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
|
@ -193,6 +212,7 @@ PanelWithOverlay {
|
||||||
Layout.preferredHeight: 52
|
Layout.preferredHeight: 52
|
||||||
anchors.leftMargin: 16
|
anchors.leftMargin: 16
|
||||||
anchors.rightMargin: 16
|
anchors.rightMargin: 16
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "Notification History"
|
text: "Notification History"
|
||||||
font.pixelSize: 18
|
font.pixelSize: 18
|
||||||
|
|
@ -200,11 +220,14 @@ PanelWithOverlay {
|
||||||
color: Theme.textPrimary
|
color: Theme.textPrimary
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: clearAllButton
|
id: clearAllButton
|
||||||
|
|
||||||
width: 90
|
width: 90
|
||||||
height: 32
|
height: 32
|
||||||
radius: 16
|
radius: 16
|
||||||
|
|
@ -212,9 +235,11 @@ PanelWithOverlay {
|
||||||
border.color: Theme.accentPrimary
|
border.color: Theme.accentPrimary
|
||||||
border.width: 1
|
border.width: 1
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: 6
|
spacing: 6
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "delete_sweep"
|
text: "delete_sweep"
|
||||||
font.family: "Material Symbols Outlined"
|
font.family: "Material Symbols Outlined"
|
||||||
|
|
@ -222,6 +247,7 @@ PanelWithOverlay {
|
||||||
color: clearAllMouseArea.containsMouse ? Theme.onAccent : Theme.accentPrimary
|
color: clearAllMouseArea.containsMouse ? Theme.onAccent : Theme.accentPrimary
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "Clear"
|
text: "Clear"
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
|
@ -229,15 +255,20 @@ PanelWithOverlay {
|
||||||
font.bold: true
|
font.bold: true
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: clearAllMouseArea
|
id: clearAllMouseArea
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: notificationHistoryWinRect.clearHistory()
|
onClicked: notificationHistoryWinRect.clearHistory()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|
@ -261,29 +292,36 @@ PanelWithOverlay {
|
||||||
radius: 20
|
radius: 20
|
||||||
z: 0
|
z: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: listContainer
|
id: listContainer
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.topMargin: 12
|
anchors.topMargin: 12
|
||||||
anchors.bottomMargin: 12
|
anchors.bottomMargin: 12
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
id: historyList
|
id: historyList
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: Math.min(contentHeight, parent.height)
|
height: Math.min(contentHeight, parent.height)
|
||||||
spacing: 12
|
spacing: 12
|
||||||
model: historyModel.count > 0 ? historyModel : placeholderModel
|
model: historyModel.count > 0 ? historyModel : placeholderModel
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
delegate: Item {
|
delegate: Item {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: notificationCard.implicitHeight + 12
|
height: notificationCard.implicitHeight + 12
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: notificationCard
|
id: notificationCard
|
||||||
|
|
||||||
width: parent.width - 24
|
width: parent.width - 24
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
color: Theme.backgroundPrimary
|
color: Theme.backgroundPrimary
|
||||||
|
|
@ -292,16 +330,22 @@ PanelWithOverlay {
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.margins: 0
|
anchors.margins: 0
|
||||||
implicitHeight: contentColumn.implicitHeight + 20
|
implicitHeight: contentColumn.implicitHeight + 20
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: contentColumn
|
id: contentColumn
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 14
|
anchors.margins: 14
|
||||||
spacing: 6
|
spacing: 6
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
id: headerRow2
|
id: headerRow2
|
||||||
|
|
||||||
spacing: 8
|
spacing: 8
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: iconBackground
|
id: iconBackground
|
||||||
|
|
||||||
width: 28
|
width: 28
|
||||||
height: 28
|
height: 28
|
||||||
radius: 20
|
radius: 20
|
||||||
|
|
@ -309,6 +353,7 @@ PanelWithOverlay {
|
||||||
border.color: Qt.darker(Theme.accentPrimary, 1.2)
|
border.color: Qt.darker(Theme.accentPrimary, 1.2)
|
||||||
border.width: 1.2
|
border.width: 1.2
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: model.appName ? model.appName.charAt(0).toUpperCase() : "?"
|
text: model.appName ? model.appName.charAt(0).toUpperCase() : "?"
|
||||||
|
|
@ -317,11 +362,15 @@ PanelWithOverlay {
|
||||||
font.bold: true
|
font.bold: true
|
||||||
color: Theme.backgroundPrimary
|
color: Theme.backgroundPrimary
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: appInfoColumn
|
id: appInfoColumn
|
||||||
|
|
||||||
spacing: 0
|
spacing: 0
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: model.appName || "No Notifications"
|
text: model.appName || "No Notifications"
|
||||||
font.bold: true
|
font.bold: true
|
||||||
|
|
@ -330,6 +379,7 @@ PanelWithOverlay {
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
visible: !model.isPlaceholder
|
visible: !model.isPlaceholder
|
||||||
text: model.timestamp ? notificationHistoryWinRect.formatTimestamp(model.timestamp) : ""
|
text: model.timestamp ? notificationHistoryWinRect.formatTimestamp(model.timestamp) : ""
|
||||||
|
|
@ -338,11 +388,15 @@ PanelWithOverlay {
|
||||||
font.pixelSize: Theme.fontSizeCaption
|
font.pixelSize: Theme.fontSizeCaption
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: model.summary || (model.isPlaceholder ? "You're all caught up!" : "")
|
text: model.summary || (model.isPlaceholder ? "You're all caught up!" : "")
|
||||||
color: Theme.textSecondary
|
color: Theme.textSecondary
|
||||||
|
|
@ -351,6 +405,7 @@ PanelWithOverlay {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: model.body || (model.isPlaceholder ? "No notifications to show." : "")
|
text: model.body || (model.isPlaceholder ? "No notifications to show." : "")
|
||||||
color: Theme.textDisabled
|
color: Theme.textDisabled
|
||||||
|
|
@ -359,12 +414,19 @@ PanelWithOverlay {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|
@ -375,14 +437,20 @@ PanelWithOverlay {
|
||||||
|
|
||||||
ListModel {
|
ListModel {
|
||||||
id: placeholderModel
|
id: placeholderModel
|
||||||
|
|
||||||
ListElement {
|
ListElement {
|
||||||
appName: ""
|
appName: ""
|
||||||
summary: ""
|
summary: ""
|
||||||
body: ""
|
body: ""
|
||||||
isPlaceholder: true
|
isPlaceholder: true
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,11 @@ PanelWithOverlay {
|
||||||
// Center the settings window on screen
|
// Center the settings window on screen
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
// Prevent closing when clicking in the panel bg
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: background
|
id: background
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,23 +3,14 @@ import QtQuick.Layouts
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
|
import qs.Components
|
||||||
import qs.Settings
|
import qs.Settings
|
||||||
import qs.Widgets.SettingsWindow
|
import qs.Widgets.SettingsWindow
|
||||||
import qs.Components
|
|
||||||
|
|
||||||
PanelWithOverlay {
|
PanelWithOverlay {
|
||||||
id: sidebarPopup
|
id: sidebarPopup
|
||||||
property var shell: null
|
|
||||||
|
|
||||||
// Trigger initial weather loading when component is completed
|
property var shell: null
|
||||||
Component.onCompleted: {
|
|
||||||
// Load initial weather data after a short delay to ensure all components are ready
|
|
||||||
Qt.callLater(function() {
|
|
||||||
if (weather && weather.fetchCityWeather) {
|
|
||||||
weather.fetchCityWeather();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function showAt() {
|
function showAt() {
|
||||||
sidebarPopupRect.showAt();
|
sidebarPopupRect.showAt();
|
||||||
|
|
@ -37,18 +28,27 @@ PanelWithOverlay {
|
||||||
sidebarPopupRect.hidePopup();
|
sidebarPopupRect.hidePopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
// Trigger initial weather loading when component is completed
|
||||||
id: sidebarPopupRect
|
Component.onCompleted: {
|
||||||
implicitWidth: 500
|
// Load initial weather data after a short delay to ensure all components are ready
|
||||||
implicitHeight: 800
|
Qt.callLater(function() {
|
||||||
visible: parent.visible
|
if (weather && weather.fetchCityWeather)
|
||||||
color: "transparent"
|
weather.fetchCityWeather();
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.right: parent.right
|
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
// Access the shell's SettingsWindow instead of creating a new one
|
||||||
|
|
||||||
|
id: sidebarPopupRect
|
||||||
|
|
||||||
property real slideOffset: width
|
property real slideOffset: width
|
||||||
property bool isAnimating: false
|
property bool isAnimating: false
|
||||||
|
property int leftPadding: 20
|
||||||
|
property int bottomPadding: 20
|
||||||
|
// Recording properties
|
||||||
|
property bool isRecording: false
|
||||||
|
|
||||||
function showAt() {
|
function showAt() {
|
||||||
if (!sidebarPopup.visible) {
|
if (!sidebarPopup.visible) {
|
||||||
|
|
@ -59,24 +59,26 @@ PanelWithOverlay {
|
||||||
slideAnim.running = true;
|
slideAnim.running = true;
|
||||||
if (weather)
|
if (weather)
|
||||||
weather.startWeatherFetch();
|
weather.startWeatherFetch();
|
||||||
|
|
||||||
if (systemWidget)
|
if (systemWidget)
|
||||||
systemWidget.panelVisible = true;
|
systemWidget.panelVisible = true;
|
||||||
|
|
||||||
if (quickAccessWidget)
|
if (quickAccessWidget)
|
||||||
quickAccessWidget.panelVisible = true;
|
quickAccessWidget.panelVisible = true;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function hidePopup() {
|
function hidePopup() {
|
||||||
if (shell && shell.settingsWindow && shell.settingsWindow.visible) {
|
if (shell && shell.settingsWindow && shell.settingsWindow.visible)
|
||||||
shell.settingsWindow.visible = false;
|
shell.settingsWindow.visible = false;
|
||||||
}
|
|
||||||
|
|
||||||
if (wifiPanelLoader.active && wifiPanelLoader.item && wifiPanelLoader.item.visible) {
|
if (wifiPanelLoader.active && wifiPanelLoader.item && wifiPanelLoader.item.visible)
|
||||||
wifiPanelLoader.item.visible = false;
|
wifiPanelLoader.item.visible = false;
|
||||||
}
|
|
||||||
if (bluetoothPanelLoader.active && bluetoothPanelLoader.item && bluetoothPanelLoader.item.visible) {
|
if (bluetoothPanelLoader.active && bluetoothPanelLoader.item && bluetoothPanelLoader.item.visible)
|
||||||
bluetoothPanelLoader.item.visible = false;
|
bluetoothPanelLoader.item.visible = false;
|
||||||
}
|
|
||||||
if (sidebarPopup.visible) {
|
if (sidebarPopup.visible) {
|
||||||
slideAnim.from = 0;
|
slideAnim.from = 0;
|
||||||
slideAnim.to = width;
|
slideAnim.to = width;
|
||||||
|
|
@ -84,37 +86,87 @@ PanelWithOverlay {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start screen recording using Quickshell.execDetached
|
||||||
|
function startRecording() {
|
||||||
|
var currentDate = new Date();
|
||||||
|
var hours = String(currentDate.getHours()).padStart(2, '0');
|
||||||
|
var minutes = String(currentDate.getMinutes()).padStart(2, '0');
|
||||||
|
var day = String(currentDate.getDate()).padStart(2, '0');
|
||||||
|
var month = String(currentDate.getMonth() + 1).padStart(2, '0');
|
||||||
|
var year = currentDate.getFullYear();
|
||||||
|
var filename = hours + "-" + minutes + "-" + day + "-" + month + "-" + year + ".mp4";
|
||||||
|
var videoPath = Settings.settings.videoPath;
|
||||||
|
if (videoPath && !videoPath.endsWith("/"))
|
||||||
|
videoPath += "/";
|
||||||
|
|
||||||
|
var outputPath = videoPath + filename;
|
||||||
|
var command = "gpu-screen-recorder -w portal" + " -f " + Settings.settings.recordingFrameRate + " -a default_output" + " -k " + Settings.settings.recordingCodec + " -ac " + Settings.settings.audioCodec + " -q " + Settings.settings.recordingQuality + " -cursor " + (Settings.settings.showCursor ? "yes" : "no") + " -cr " + Settings.settings.colorRange + " -o " + outputPath;
|
||||||
|
Quickshell.execDetached(["sh", "-c", command]);
|
||||||
|
isRecording = true;
|
||||||
|
quickAccessWidget.isRecording = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop recording using Quickshell.execDetached
|
||||||
|
function stopRecording() {
|
||||||
|
Quickshell.execDetached(["sh", "-c", "pkill -SIGINT -f 'gpu-screen-recorder.*portal'"]);
|
||||||
|
// Optionally, force kill after a delay
|
||||||
|
var cleanupTimer = Qt.createQmlObject('import QtQuick; Timer { interval: 3000; running: true; repeat: false }', sidebarPopupRect);
|
||||||
|
cleanupTimer.triggered.connect(function() {
|
||||||
|
Quickshell.execDetached(["sh", "-c", "pkill -9 -f 'gpu-screen-recorder.*portal' 2>/dev/null || true"]);
|
||||||
|
cleanupTimer.destroy();
|
||||||
|
});
|
||||||
|
isRecording = false;
|
||||||
|
quickAccessWidget.isRecording = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
implicitWidth: 500
|
||||||
|
implicitHeight: 800
|
||||||
|
visible: parent.visible
|
||||||
|
color: "transparent"
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.right: parent.right
|
||||||
|
// Clean up processes on destruction
|
||||||
|
Component.onDestruction: {
|
||||||
|
if (isRecording)
|
||||||
|
stopRecording();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent closing when clicking in the panel bg
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
}
|
||||||
|
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
id: slideAnim
|
id: slideAnim
|
||||||
|
|
||||||
target: sidebarPopupRect
|
target: sidebarPopupRect
|
||||||
property: "slideOffset"
|
property: "slideOffset"
|
||||||
duration: 300
|
duration: 300
|
||||||
easing.type: Easing.OutCubic
|
easing.type: Easing.OutCubic
|
||||||
|
|
||||||
onStopped: {
|
onStopped: {
|
||||||
if (sidebarPopupRect.slideOffset === sidebarPopupRect.width) {
|
if (sidebarPopupRect.slideOffset === sidebarPopupRect.width) {
|
||||||
sidebarPopup.visible = false;
|
sidebarPopup.visible = false;
|
||||||
|
|
||||||
if (weather)
|
if (weather)
|
||||||
weather.stopWeatherFetch();
|
weather.stopWeatherFetch();
|
||||||
|
|
||||||
if (systemWidget)
|
if (systemWidget)
|
||||||
systemWidget.panelVisible = false;
|
systemWidget.panelVisible = false;
|
||||||
|
|
||||||
if (quickAccessWidget)
|
if (quickAccessWidget)
|
||||||
quickAccessWidget.panelVisible = false;
|
quickAccessWidget.panelVisible = false;
|
||||||
|
|
||||||
}
|
}
|
||||||
sidebarPopupRect.isAnimating = false;
|
sidebarPopupRect.isAnimating = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onStarted: {
|
onStarted: {
|
||||||
sidebarPopupRect.isAnimating = true;
|
sidebarPopupRect.isAnimating = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
property int leftPadding: 20
|
|
||||||
property int bottomPadding: 20
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: mainRectangle
|
id: mainRectangle
|
||||||
|
|
||||||
width: sidebarPopupRect.width - sidebarPopupRect.leftPadding
|
width: sidebarPopupRect.width - sidebarPopupRect.leftPadding
|
||||||
height: sidebarPopupRect.height - sidebarPopupRect.bottomPadding
|
height: sidebarPopupRect.height - sidebarPopupRect.bottomPadding
|
||||||
anchors.top: sidebarPopupRect.top
|
anchors.top: sidebarPopupRect.top
|
||||||
|
|
@ -126,68 +178,69 @@ PanelWithOverlay {
|
||||||
|
|
||||||
Behavior on x {
|
Behavior on x {
|
||||||
enabled: !sidebarPopupRect.isAnimating
|
enabled: !sidebarPopupRect.isAnimating
|
||||||
|
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: 300
|
duration: 300
|
||||||
easing.type: Easing.OutCubic
|
easing.type: Easing.OutCubic
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Access the shell's SettingsWindow instead of creating a new one
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// LazyLoader for WifiPanel
|
// LazyLoader for WifiPanel
|
||||||
LazyLoader {
|
LazyLoader {
|
||||||
id: wifiPanelLoader
|
id: wifiPanelLoader
|
||||||
|
|
||||||
loading: false
|
loading: false
|
||||||
component: WifiPanel {}
|
|
||||||
|
component: WifiPanel {
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LazyLoader for BluetoothPanel
|
// LazyLoader for BluetoothPanel
|
||||||
LazyLoader {
|
LazyLoader {
|
||||||
id: bluetoothPanelLoader
|
id: bluetoothPanelLoader
|
||||||
|
|
||||||
loading: false
|
loading: false
|
||||||
component: BluetoothPanel {}
|
|
||||||
|
component: BluetoothPanel {
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// SettingsIcon component
|
// SettingsIcon component
|
||||||
SettingsIcon {
|
SettingsIcon {
|
||||||
id: settingsModal
|
id: settingsModal
|
||||||
|
|
||||||
onWeatherRefreshRequested: {
|
onWeatherRefreshRequested: {
|
||||||
if (weather && weather.fetchCityWeather) {
|
if (weather && weather.fetchCityWeather)
|
||||||
weather.fetchCityWeather();
|
weather.fetchCityWeather();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
anchors.fill: mainRectangle
|
anchors.fill: mainRectangle
|
||||||
x: sidebarPopupRect.slideOffset
|
x: sidebarPopupRect.slideOffset
|
||||||
|
Keys.onEscapePressed: sidebarPopupRect.hidePopup()
|
||||||
Behavior on x {
|
|
||||||
enabled: !sidebarPopupRect.isAnimating
|
|
||||||
NumberAnimation {
|
|
||||||
duration: 300
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 20
|
anchors.margins: 20
|
||||||
spacing: 16
|
spacing: 16
|
||||||
|
|
||||||
System {
|
PowerMenu {
|
||||||
id: systemWidget
|
id: systemWidget
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
z: 3
|
z: 3
|
||||||
}
|
}
|
||||||
|
|
||||||
Weather {
|
Weather {
|
||||||
id: weather
|
id: weather
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
z: 2
|
z: 2
|
||||||
}
|
}
|
||||||
|
|
@ -204,8 +257,10 @@ PanelWithOverlay {
|
||||||
|
|
||||||
SystemMonitor {
|
SystemMonitor {
|
||||||
id: systemMonitor
|
id: systemMonitor
|
||||||
|
|
||||||
z: 2
|
z: 2
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Power profile, Wifi and Bluetooth row
|
// Power profile, Wifi and Bluetooth row
|
||||||
|
|
@ -236,6 +291,7 @@ PanelWithOverlay {
|
||||||
// Wifi button
|
// Wifi button
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: wifiButton
|
id: wifiButton
|
||||||
|
|
||||||
width: 36
|
width: 36
|
||||||
height: 36
|
height: 36
|
||||||
radius: 18
|
radius: 18
|
||||||
|
|
@ -255,16 +311,17 @@ PanelWithOverlay {
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: wifiButtonArea
|
id: wifiButtonArea
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (!wifiPanelLoader.active) {
|
if (!wifiPanelLoader.active)
|
||||||
wifiPanelLoader.loading = true;
|
wifiPanelLoader.loading = true;
|
||||||
}
|
|
||||||
if (wifiPanelLoader.item) {
|
if (wifiPanelLoader.item)
|
||||||
wifiPanelLoader.item.showAt();
|
wifiPanelLoader.item.showAt();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -273,11 +330,13 @@ PanelWithOverlay {
|
||||||
targetItem: wifiButtonArea
|
targetItem: wifiButtonArea
|
||||||
tooltipVisible: wifiButtonArea.containsMouse
|
tooltipVisible: wifiButtonArea.containsMouse
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bluetooth button
|
// Bluetooth button
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: bluetoothButton
|
id: bluetoothButton
|
||||||
|
|
||||||
width: 36
|
width: 36
|
||||||
height: 36
|
height: 36
|
||||||
radius: 18
|
radius: 18
|
||||||
|
|
@ -297,16 +356,17 @@ PanelWithOverlay {
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: bluetoothButtonArea
|
id: bluetoothButtonArea
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (!bluetoothPanelLoader.active) {
|
if (!bluetoothPanelLoader.active)
|
||||||
bluetoothPanelLoader.loading = true;
|
bluetoothPanelLoader.loading = true;
|
||||||
}
|
|
||||||
if (bluetoothPanelLoader.item) {
|
if (bluetoothPanelLoader.item)
|
||||||
bluetoothPanelLoader.item.showAt();
|
bluetoothPanelLoader.item.showAt();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -315,9 +375,13 @@ PanelWithOverlay {
|
||||||
targetItem: bluetoothButtonArea
|
targetItem: bluetoothButtonArea
|
||||||
tooltipVisible: bluetoothButtonArea.containsMouse
|
tooltipVisible: bluetoothButtonArea.containsMouse
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
|
@ -326,101 +390,60 @@ PanelWithOverlay {
|
||||||
|
|
||||||
// QuickAccess widget
|
// QuickAccess widget
|
||||||
QuickAccess {
|
QuickAccess {
|
||||||
|
// 6 is the wallpaper tab index
|
||||||
|
|
||||||
id: quickAccessWidget
|
id: quickAccessWidget
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
Layout.topMargin: -16
|
Layout.topMargin: -16
|
||||||
z: 2
|
z: 2
|
||||||
isRecording: sidebarPopupRect.isRecording
|
isRecording: sidebarPopupRect.isRecording
|
||||||
|
|
||||||
onRecordingRequested: {
|
onRecordingRequested: {
|
||||||
sidebarPopupRect.startRecording();
|
sidebarPopupRect.startRecording();
|
||||||
}
|
}
|
||||||
|
|
||||||
onStopRecordingRequested: {
|
onStopRecordingRequested: {
|
||||||
sidebarPopupRect.stopRecording();
|
sidebarPopupRect.stopRecording();
|
||||||
}
|
}
|
||||||
|
onRecordingStateMismatch: function(actualState) {
|
||||||
onRecordingStateMismatch: function (actualState) {
|
|
||||||
isRecording = actualState;
|
isRecording = actualState;
|
||||||
quickAccessWidget.isRecording = actualState;
|
quickAccessWidget.isRecording = actualState;
|
||||||
}
|
}
|
||||||
|
|
||||||
onSettingsRequested: {
|
onSettingsRequested: {
|
||||||
// Use the SettingsModal's openSettings function
|
// Use the SettingsModal's openSettings function
|
||||||
if (typeof settingsModal !== 'undefined' && settingsModal && settingsModal.openSettings) {
|
if (typeof settingsModal !== 'undefined' && settingsModal && settingsModal.openSettings)
|
||||||
settingsModal.openSettings();
|
settingsModal.openSettings();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
onWallpaperSelectorRequested: {
|
onWallpaperSelectorRequested: {
|
||||||
// Use the SettingsModal's openSettings function with wallpaper tab (index 6)
|
// Use the SettingsModal's openSettings function with wallpaper tab (index 6)
|
||||||
if (typeof settingsModal !== 'undefined' && settingsModal && settingsModal.openSettings) {
|
if (typeof settingsModal !== 'undefined' && settingsModal && settingsModal.openSettings)
|
||||||
settingsModal.openSettings(6); // 6 is the wallpaper tab index
|
settingsModal.openSettings(6);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Keys.onEscapePressed: sidebarPopupRect.hidePopup()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recording properties
|
Behavior on x {
|
||||||
property bool isRecording: false
|
enabled: !sidebarPopupRect.isAnimating
|
||||||
|
|
||||||
// Start screen recording using Quickshell.execDetached
|
NumberAnimation {
|
||||||
function startRecording() {
|
duration: 300
|
||||||
var currentDate = new Date();
|
easing.type: Easing.OutCubic
|
||||||
var hours = String(currentDate.getHours()).padStart(2, '0');
|
}
|
||||||
var minutes = String(currentDate.getMinutes()).padStart(2, '0');
|
|
||||||
var day = String(currentDate.getDate()).padStart(2, '0');
|
|
||||||
var month = String(currentDate.getMonth() + 1).padStart(2, '0');
|
|
||||||
var year = currentDate.getFullYear();
|
|
||||||
|
|
||||||
var filename = hours + "-" + minutes + "-" + day + "-" + month + "-" + year + ".mp4";
|
|
||||||
var videoPath = Settings.settings.videoPath;
|
|
||||||
if (videoPath && !videoPath.endsWith("/")) {
|
|
||||||
videoPath += "/";
|
|
||||||
}
|
}
|
||||||
var outputPath = videoPath + filename;
|
|
||||||
var command = "gpu-screen-recorder -w portal" +
|
|
||||||
" -f " + Settings.settings.recordingFrameRate +
|
|
||||||
" -a default_output" +
|
|
||||||
" -k " + Settings.settings.recordingCodec +
|
|
||||||
" -ac " + Settings.settings.audioCodec +
|
|
||||||
" -q " + Settings.settings.recordingQuality +
|
|
||||||
" -cursor " + (Settings.settings.showCursor ? "yes" : "no") +
|
|
||||||
" -cr " + Settings.settings.colorRange +
|
|
||||||
" -o " + outputPath;
|
|
||||||
Quickshell.execDetached(["sh", "-c", command]);
|
|
||||||
isRecording = true;
|
|
||||||
quickAccessWidget.isRecording = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop recording using Quickshell.execDetached
|
|
||||||
function stopRecording() {
|
|
||||||
Quickshell.execDetached(["sh", "-c", "pkill -SIGINT -f 'gpu-screen-recorder.*portal'"]);
|
|
||||||
// Optionally, force kill after a delay
|
|
||||||
var cleanupTimer = Qt.createQmlObject('import QtQuick; Timer { interval: 3000; running: true; repeat: false }', sidebarPopupRect);
|
|
||||||
cleanupTimer.triggered.connect(function () {
|
|
||||||
Quickshell.execDetached(["sh", "-c", "pkill -9 -f 'gpu-screen-recorder.*portal' 2>/dev/null || true"]);
|
|
||||||
cleanupTimer.destroy();
|
|
||||||
});
|
|
||||||
isRecording = false;
|
|
||||||
quickAccessWidget.isRecording = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up processes on destruction
|
|
||||||
Component.onDestruction: {
|
|
||||||
if (isRecording) {
|
|
||||||
stopRecording();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
active: Settings.settings.showCorners
|
active: Settings.settings.showCorners
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
sourceComponent: Item {
|
sourceComponent: Item {
|
||||||
Corners {
|
Corners {
|
||||||
id: sidebarCornerLeft
|
id: sidebarCornerLeft
|
||||||
|
|
||||||
position: "bottomright"
|
position: "bottomright"
|
||||||
size: 1.1
|
size: 1.1
|
||||||
fillColor: Theme.backgroundPrimary
|
fillColor: Theme.backgroundPrimary
|
||||||
|
|
@ -430,15 +453,19 @@ PanelWithOverlay {
|
||||||
|
|
||||||
Behavior on offsetX {
|
Behavior on offsetX {
|
||||||
enabled: !sidebarPopupRect.isAnimating
|
enabled: !sidebarPopupRect.isAnimating
|
||||||
|
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: 300
|
duration: 300
|
||||||
easing.type: Easing.OutCubic
|
easing.type: Easing.OutCubic
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Corners {
|
Corners {
|
||||||
id: sidebarCornerBottom
|
id: sidebarCornerBottom
|
||||||
|
|
||||||
position: "bottomright"
|
position: "bottomright"
|
||||||
size: 1.1
|
size: 1.1
|
||||||
fillColor: Theme.backgroundPrimary
|
fillColor: Theme.backgroundPrimary
|
||||||
|
|
@ -448,13 +475,20 @@ PanelWithOverlay {
|
||||||
|
|
||||||
Behavior on offsetX {
|
Behavior on offsetX {
|
||||||
enabled: !sidebarPopupRect.isAnimating
|
enabled: !sidebarPopupRect.isAnimating
|
||||||
|
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: 300
|
duration: 300
|
||||||
easing.type: Easing.OutCubic
|
easing.type: Easing.OutCubic
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,64 @@
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Effects
|
import QtQuick.Effects
|
||||||
|
import QtQuick.Layouts
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import Quickshell.Widgets
|
import Quickshell.Widgets
|
||||||
|
import qs.Components
|
||||||
|
import qs.Helpers
|
||||||
|
import qs.Services
|
||||||
import qs.Settings
|
import qs.Settings
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
import qs.Widgets.LockScreen
|
import qs.Widgets.LockScreen
|
||||||
import qs.Helpers
|
|
||||||
import qs.Services
|
|
||||||
import qs.Components
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: systemWidget
|
id: systemWidget
|
||||||
|
|
||||||
|
property string uptimeText: "--:--"
|
||||||
|
property bool panelVisible: false
|
||||||
|
|
||||||
|
function logout() {
|
||||||
|
if (WorkspaceManager.isNiri)
|
||||||
|
logoutProcessNiri.running = true;
|
||||||
|
else if (WorkspaceManager.isHyprland)
|
||||||
|
logoutProcessHyprland.running = true;
|
||||||
|
else
|
||||||
|
console.warn("No supported compositor detected for logout");
|
||||||
|
}
|
||||||
|
|
||||||
|
function suspend() {
|
||||||
|
suspendProcess.running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function shutdown() {
|
||||||
|
shutdownProcess.running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function reboot() {
|
||||||
|
rebootProcess.running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSystemInfo() {
|
||||||
|
uptimeProcess.running = true;
|
||||||
|
}
|
||||||
|
|
||||||
width: 440
|
width: 440
|
||||||
height: 80
|
height: 80
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
anchors.horizontalCenterOffset: -2
|
anchors.horizontalCenterOffset: -2
|
||||||
|
onPanelVisibleChanged: {
|
||||||
|
if (panelVisible)
|
||||||
|
updateSystemInfo();
|
||||||
|
|
||||||
|
}
|
||||||
|
Component.onCompleted: {
|
||||||
|
uptimeProcess.running = true;
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: card
|
id: card
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: Theme.surface
|
color: Theme.surface
|
||||||
radius: 18
|
radius: 18
|
||||||
|
|
@ -30,19 +68,16 @@ Rectangle {
|
||||||
anchors.margins: 18
|
anchors.margins: 18
|
||||||
spacing: 12
|
spacing: 12
|
||||||
|
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: 12
|
spacing: 12
|
||||||
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 48
|
width: 48
|
||||||
height: 48
|
height: 48
|
||||||
radius: 24
|
radius: 24
|
||||||
color: Theme.accentPrimary
|
color: Theme.accentPrimary
|
||||||
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
@ -52,9 +87,10 @@ Rectangle {
|
||||||
z: 2
|
z: 2
|
||||||
}
|
}
|
||||||
|
|
||||||
Avatar {}
|
Avatar {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
spacing: 4
|
spacing: 4
|
||||||
|
|
@ -74,16 +110,16 @@ Rectangle {
|
||||||
font.pixelSize: 12
|
font.pixelSize: 12
|
||||||
color: Theme.textSecondary
|
color: Theme.textSecondary
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: systemButton
|
id: systemButton
|
||||||
|
|
||||||
width: 32
|
width: 32
|
||||||
height: 32
|
height: 32
|
||||||
radius: 16
|
radius: 16
|
||||||
|
|
@ -101,6 +137,7 @@ Rectangle {
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: systemButtonArea
|
id: systemButtonArea
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
|
|
@ -108,24 +145,30 @@ Rectangle {
|
||||||
systemMenu.visible = !systemMenu.visible;
|
systemMenu.visible = !systemMenu.visible;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledTooltip {
|
StyledTooltip {
|
||||||
id: systemTooltip
|
id: systemTooltip
|
||||||
text: "System"
|
|
||||||
|
text: "Power Menu"
|
||||||
targetItem: systemButton
|
targetItem: systemButton
|
||||||
tooltipVisible: systemButtonArea.containsMouse
|
tooltipVisible: systemButtonArea.containsMouse
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PanelWithOverlay {
|
PanelWithOverlay {
|
||||||
id: systemMenu
|
id: systemMenu
|
||||||
|
|
||||||
anchors.top: systemButton.bottom
|
anchors.top: systemButton.bottom
|
||||||
anchors.right: systemButton.right
|
anchors.right: systemButton.right
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|
||||||
width: 160
|
width: 160
|
||||||
height: 220
|
height: 220
|
||||||
color: Theme.surface
|
color: Theme.surface
|
||||||
|
|
@ -136,17 +179,19 @@ Rectangle {
|
||||||
z: 9999
|
z: 9999
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
|
||||||
anchors.rightMargin: 32
|
anchors.rightMargin: 32
|
||||||
anchors.topMargin: systemButton.y + systemButton.height + 48
|
anchors.topMargin: systemButton.y + systemButton.height + 48
|
||||||
|
|
||||||
|
// Prevent closing when clicking in the panel bg
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 8
|
anchors.margins: 8
|
||||||
spacing: 4
|
spacing: 4
|
||||||
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: 36
|
Layout.preferredHeight: 36
|
||||||
|
|
@ -172,10 +217,12 @@ Rectangle {
|
||||||
color: lockButtonArea.containsMouse ? Theme.onAccent : Theme.textPrimary
|
color: lockButtonArea.containsMouse ? Theme.onAccent : Theme.textPrimary
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: lockButtonArea
|
id: lockButtonArea
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
@ -184,8 +231,8 @@ Rectangle {
|
||||||
systemMenu.visible = false;
|
systemMenu.visible = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
@ -211,10 +258,12 @@ Rectangle {
|
||||||
color: suspendButtonArea.containsMouse ? Theme.onAccent : Theme.textPrimary
|
color: suspendButtonArea.containsMouse ? Theme.onAccent : Theme.textPrimary
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: suspendButtonArea
|
id: suspendButtonArea
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
@ -223,8 +272,8 @@ Rectangle {
|
||||||
systemMenu.visible = false;
|
systemMenu.visible = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
@ -251,10 +300,12 @@ Rectangle {
|
||||||
color: rebootButtonArea.containsMouse ? Theme.onAccent : Theme.textPrimary
|
color: rebootButtonArea.containsMouse ? Theme.onAccent : Theme.textPrimary
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: rebootButtonArea
|
id: rebootButtonArea
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
@ -263,8 +314,8 @@ Rectangle {
|
||||||
systemMenu.visible = false;
|
systemMenu.visible = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
@ -290,10 +341,12 @@ Rectangle {
|
||||||
color: logoutButtonArea.containsMouse ? Theme.onAccent : Theme.textPrimary
|
color: logoutButtonArea.containsMouse ? Theme.onAccent : Theme.textPrimary
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: logoutButtonArea
|
id: logoutButtonArea
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
@ -302,8 +355,8 @@ Rectangle {
|
||||||
systemMenu.visible = false;
|
systemMenu.visible = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
@ -329,10 +382,12 @@ Rectangle {
|
||||||
color: shutdownButtonArea.containsMouse ? Theme.onAccent : Theme.textPrimary
|
color: shutdownButtonArea.containsMouse ? Theme.onAccent : Theme.textPrimary
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: shutdownButtonArea
|
id: shutdownButtonArea
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
@ -341,96 +396,72 @@ Rectangle {
|
||||||
systemMenu.visible = false;
|
systemMenu.visible = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
property string uptimeText: "--:--"
|
|
||||||
|
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: uptimeProcess
|
id: uptimeProcess
|
||||||
|
|
||||||
command: ["sh", "-c", "uptime | awk -F 'up ' '{print $2}' | awk -F ',' '{print $1}' | xargs"]
|
command: ["sh", "-c", "uptime | awk -F 'up ' '{print $2}' | awk -F ',' '{print $1}' | xargs"]
|
||||||
running: false
|
running: false
|
||||||
|
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
uptimeText = this.text.trim();
|
uptimeText = this.text.trim();
|
||||||
uptimeProcess.running = false;
|
uptimeProcess.running = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: shutdownProcess
|
id: shutdownProcess
|
||||||
|
|
||||||
command: ["shutdown", "-h", "now"]
|
command: ["shutdown", "-h", "now"]
|
||||||
running: false
|
running: false
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: rebootProcess
|
id: rebootProcess
|
||||||
|
|
||||||
command: ["reboot"]
|
command: ["reboot"]
|
||||||
running: false
|
running: false
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: suspendProcess
|
id: suspendProcess
|
||||||
|
|
||||||
command: ["systemctl", "suspend"]
|
command: ["systemctl", "suspend"]
|
||||||
running: false
|
running: false
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: logoutProcessNiri
|
id: logoutProcessNiri
|
||||||
|
|
||||||
command: ["niri", "msg", "action", "quit", "--skip-confirmation"]
|
command: ["niri", "msg", "action", "quit", "--skip-confirmation"]
|
||||||
running: false
|
running: false
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: logoutProcessHyprland
|
id: logoutProcessHyprland
|
||||||
|
|
||||||
command: ["hyprctl", "dispatch", "exit"]
|
command: ["hyprctl", "dispatch", "exit"]
|
||||||
running: false
|
running: false
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: logoutProcess
|
id: logoutProcess
|
||||||
|
|
||||||
command: ["loginctl", "terminate-user", Quickshell.env("USER")]
|
command: ["loginctl", "terminate-user", Quickshell.env("USER")]
|
||||||
running: false
|
running: false
|
||||||
}
|
}
|
||||||
|
|
||||||
function logout() {
|
|
||||||
if (WorkspaceManager.isNiri) {
|
|
||||||
logoutProcessNiri.running = true;
|
|
||||||
} else if (WorkspaceManager.isHyprland) {
|
|
||||||
logoutProcessHyprland.running = true;
|
|
||||||
} else {
|
|
||||||
|
|
||||||
console.warn("No supported compositor detected for logout");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function suspend() {
|
|
||||||
suspendProcess.running = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function shutdown() {
|
|
||||||
shutdownProcess.running = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function reboot() {
|
|
||||||
rebootProcess.running = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
property bool panelVisible: false
|
|
||||||
|
|
||||||
|
|
||||||
onPanelVisibleChanged: {
|
|
||||||
if (panelVisible) {
|
|
||||||
updateSystemInfo();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
interval: 60000
|
interval: 60000
|
||||||
repeat: true
|
repeat: true
|
||||||
|
|
@ -438,16 +469,8 @@ Rectangle {
|
||||||
onTriggered: updateSystemInfo()
|
onTriggered: updateSystemInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
uptimeProcess.running = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateSystemInfo() {
|
|
||||||
uptimeProcess.running = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
LockScreen {
|
LockScreen {
|
||||||
id: lockScreen
|
id: lockScreen
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue