mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
idle: added support for custom commands
This commit is contained in:
@@ -1193,16 +1193,24 @@
|
||||
"enable-label": "Enable idle management",
|
||||
"fade-duration-description": "Seconds for the fade-to-black animation before each action fires. Any mouse movement cancels the fade.",
|
||||
"fade-duration-label": "Fade duration",
|
||||
"lock-description": "Minutes of inactivity before the lock screen activates.",
|
||||
"lock-description": "Seconds of inactivity before the lock screen activates.",
|
||||
"lock-label": "Lock screen",
|
||||
"screen-off-description": "Minutes of inactivity before monitors are turned off.",
|
||||
"screen-off-description": "Seconds of inactivity before monitors are turned off.",
|
||||
"screen-off-label": "Turn off screen",
|
||||
"status-description": "Idle time as reported by the compositor.",
|
||||
"status-label": "Idle time",
|
||||
"suspend-description": "Minutes of inactivity before the system suspends.",
|
||||
"suspend-description": "Seconds of inactivity before the system suspends.",
|
||||
"timeouts-description": "Set to 0 to disable a stage. Timeouts are paused while Keep Awake is active.",
|
||||
"timeouts-label": "Timeouts",
|
||||
"unavailable": "Native idle monitoring is not available on this compositor."
|
||||
"unavailable": "Native idle monitoring is not available on this compositor.",
|
||||
"tab-behavior": "Behavior",
|
||||
"tab-custom": "Custom",
|
||||
"custom-label": "Custom idle commands",
|
||||
"custom-description": "Run a shell command after a period of inactivity.",
|
||||
"custom-add": "Add command",
|
||||
"custom-entry-timeout": "Inactivity time",
|
||||
"custom-entry-command": "Command",
|
||||
"custom-entry-delete": "Delete"
|
||||
},
|
||||
"indicator": {
|
||||
"default-value": "Default: {value}",
|
||||
|
||||
@@ -932,7 +932,8 @@
|
||||
"widget": "NToggle",
|
||||
"tab": 13,
|
||||
"tabLabel": "common.idle",
|
||||
"subTab": null
|
||||
"subTab": 0,
|
||||
"subTabLabel": "panels.idle.tab-behavior"
|
||||
},
|
||||
{
|
||||
"labelKey": "panels.idle.status-label",
|
||||
@@ -940,7 +941,8 @@
|
||||
"widget": "NLabel",
|
||||
"tab": 13,
|
||||
"tabLabel": "common.idle",
|
||||
"subTab": null
|
||||
"subTab": 0,
|
||||
"subTabLabel": "panels.idle.tab-behavior"
|
||||
},
|
||||
{
|
||||
"labelKey": "panels.idle.timeouts-label",
|
||||
@@ -948,7 +950,8 @@
|
||||
"widget": "NLabel",
|
||||
"tab": 13,
|
||||
"tabLabel": "common.idle",
|
||||
"subTab": null
|
||||
"subTab": 0,
|
||||
"subTabLabel": "panels.idle.tab-behavior"
|
||||
},
|
||||
{
|
||||
"labelKey": "panels.idle.screen-off-label",
|
||||
@@ -956,7 +959,8 @@
|
||||
"widget": "NSpinBox",
|
||||
"tab": 13,
|
||||
"tabLabel": "common.idle",
|
||||
"subTab": null
|
||||
"subTab": 0,
|
||||
"subTabLabel": "panels.idle.tab-behavior"
|
||||
},
|
||||
{
|
||||
"labelKey": "panels.idle.lock-label",
|
||||
@@ -964,7 +968,8 @@
|
||||
"widget": "NSpinBox",
|
||||
"tab": 13,
|
||||
"tabLabel": "common.idle",
|
||||
"subTab": null
|
||||
"subTab": 0,
|
||||
"subTabLabel": "panels.idle.tab-behavior"
|
||||
},
|
||||
{
|
||||
"labelKey": "common.suspend",
|
||||
@@ -972,7 +977,8 @@
|
||||
"widget": "NSpinBox",
|
||||
"tab": 13,
|
||||
"tabLabel": "common.idle",
|
||||
"subTab": null
|
||||
"subTab": 0,
|
||||
"subTabLabel": "panels.idle.tab-behavior"
|
||||
},
|
||||
{
|
||||
"labelKey": "panels.idle.fade-duration-label",
|
||||
@@ -980,7 +986,35 @@
|
||||
"widget": "NSpinBox",
|
||||
"tab": 13,
|
||||
"tabLabel": "common.idle",
|
||||
"subTab": null
|
||||
"subTab": 0,
|
||||
"subTabLabel": "panels.idle.tab-behavior"
|
||||
},
|
||||
{
|
||||
"labelKey": "panels.idle.custom-label",
|
||||
"descriptionKey": "panels.idle.custom-description",
|
||||
"widget": "NLabel",
|
||||
"tab": 13,
|
||||
"tabLabel": "common.idle",
|
||||
"subTab": 1,
|
||||
"subTabLabel": "panels.idle.tab-custom"
|
||||
},
|
||||
{
|
||||
"labelKey": "panels.idle.custom-entry-timeout",
|
||||
"descriptionKey": null,
|
||||
"widget": "NSpinBox",
|
||||
"tab": 13,
|
||||
"tabLabel": "common.idle",
|
||||
"subTab": 1,
|
||||
"subTabLabel": "panels.idle.tab-custom"
|
||||
},
|
||||
{
|
||||
"labelKey": "panels.idle.custom-entry-command",
|
||||
"descriptionKey": null,
|
||||
"widget": "NTextInput",
|
||||
"tab": 13,
|
||||
"tabLabel": "common.idle",
|
||||
"subTab": 1,
|
||||
"subTabLabel": "panels.idle.tab-custom"
|
||||
},
|
||||
{
|
||||
"labelKey": "panels.launcher.settings-clipboard-history-label",
|
||||
|
||||
@@ -722,10 +722,11 @@ Singleton {
|
||||
// idle management
|
||||
property JsonObject idle: JsonObject {
|
||||
property bool enabled: false
|
||||
property int screenOffTimeout: 0 // minutes, 0 = disabled
|
||||
property int lockTimeout: 0 // minutes, 0 = disabled
|
||||
property int suspendTimeout: 0 // minutes, 0 = disabled
|
||||
property int screenOffTimeout: 0 // seconds, 0 = disabled
|
||||
property int lockTimeout: 0 // seconds, 0 = disabled
|
||||
property int suspendTimeout: 0 // seconds, 0 = disabled
|
||||
property int fadeDuration: 5 // seconds of fade-to-black before action fires
|
||||
property string customCommands: "[]" // JSON array of {timeout, command}
|
||||
}
|
||||
|
||||
// desktop widgets
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.Power
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
|
||||
// Master enable
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.idle.enable-label")
|
||||
description: I18n.tr("panels.idle.enable-description")
|
||||
checked: Settings.data.idle.enabled
|
||||
defaultValue: Settings.getDefaultValue("idle.enabled")
|
||||
onToggled: checked => Settings.data.idle.enabled = checked
|
||||
}
|
||||
|
||||
// Live idle status
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
enabled: Settings.data.idle.enabled
|
||||
visible: IdleService.nativeIdleMonitorAvailable
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("panels.idle.status-label")
|
||||
description: I18n.tr("panels.idle.status-description")
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NText {
|
||||
Layout.alignment: Qt.AlignBottom | Qt.AlignRight
|
||||
text: IdleService.idleSeconds > 0 ? I18n.trp("common.second", IdleService.idleSeconds) : I18n.tr("common.active")
|
||||
family: Settings.data.ui.fontFixed
|
||||
pointSize: Style.fontSizeM
|
||||
color: IdleService.idleSeconds > 0 ? Color.mPrimary : Color.mOnSurfaceVariant
|
||||
}
|
||||
}
|
||||
|
||||
NLabel {
|
||||
visible: !IdleService.nativeIdleMonitorAvailable
|
||||
description: I18n.tr("panels.idle.unavailable")
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Timeout spinboxes (disabled when idle is off)
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginL
|
||||
enabled: Settings.data.idle.enabled
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("panels.idle.timeouts-label")
|
||||
description: I18n.tr("panels.idle.timeouts-description")
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
label: I18n.tr("panels.idle.screen-off-label")
|
||||
description: I18n.tr("panels.idle.screen-off-description")
|
||||
from: 0
|
||||
to: 86400
|
||||
suffix: "s"
|
||||
value: Settings.data.idle.screenOffTimeout
|
||||
defaultValue: 0
|
||||
onValueChanged: Settings.data.idle.screenOffTimeout = value
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
label: I18n.tr("panels.idle.lock-label")
|
||||
description: I18n.tr("panels.idle.lock-description")
|
||||
from: 0
|
||||
to: 86400
|
||||
suffix: "s"
|
||||
value: Settings.data.idle.lockTimeout
|
||||
defaultValue: 0
|
||||
onValueChanged: Settings.data.idle.lockTimeout = value
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
label: I18n.tr("common.suspend")
|
||||
description: I18n.tr("panels.idle.suspend-description")
|
||||
from: 0
|
||||
to: 86400
|
||||
suffix: "s"
|
||||
value: Settings.data.idle.suspendTimeout
|
||||
defaultValue: 0
|
||||
onValueChanged: Settings.data.idle.suspendTimeout = value
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
label: I18n.tr("panels.idle.fade-duration-label")
|
||||
description: I18n.tr("panels.idle.fade-duration-description")
|
||||
from: 1
|
||||
to: 60
|
||||
suffix: "s"
|
||||
value: Settings.data.idle.fadeDuration
|
||||
defaultValue: Settings.getDefaultValue("idle.fadeDuration")
|
||||
onValueChanged: Settings.data.idle.fadeDuration = value
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.Power
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
enabled: Settings.data.idle.enabled
|
||||
|
||||
property bool _saving: false
|
||||
|
||||
ListModel {
|
||||
id: entriesModel
|
||||
}
|
||||
|
||||
function _loadToModel() {
|
||||
if (_saving)
|
||||
return;
|
||||
entriesModel.clear();
|
||||
var entries = [];
|
||||
try {
|
||||
entries = JSON.parse(Settings.data.idle.customCommands);
|
||||
} catch (e) {
|
||||
Logger.w("CustomSubTab", "Failed to parse customCommands:", e);
|
||||
}
|
||||
for (var i = 0; i < entries.length; i++) {
|
||||
entriesModel.append({
|
||||
"timeout": parseInt(entries[i].timeout) || 60,
|
||||
"command": String(entries[i].command || "")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function _saveFromModel() {
|
||||
_saving = true;
|
||||
var arr = [];
|
||||
for (var i = 0; i < entriesModel.count; i++) {
|
||||
var item = entriesModel.get(i);
|
||||
arr.push({
|
||||
"timeout": item.timeout,
|
||||
"command": item.command
|
||||
});
|
||||
}
|
||||
Settings.data.idle.customCommands = JSON.stringify(arr);
|
||||
_saving = false;
|
||||
}
|
||||
|
||||
Component.onCompleted: _loadToModel()
|
||||
|
||||
Connections {
|
||||
target: Settings.data.idle
|
||||
function onCustomCommandsChanged() {
|
||||
root._loadToModel();
|
||||
}
|
||||
}
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("panels.idle.custom-label")
|
||||
description: I18n.tr("panels.idle.custom-description")
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginS
|
||||
Layout.bottomMargin: Style.marginS
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: entriesModel
|
||||
|
||||
delegate: ColumnLayout {
|
||||
id: entryDelegate
|
||||
required property int index
|
||||
required property int timeout
|
||||
required property string command
|
||||
|
||||
spacing: Style.marginM
|
||||
Layout.fillWidth: true
|
||||
|
||||
property bool _initialized: false
|
||||
|
||||
Component.onCompleted: {
|
||||
commandInput.text = entryDelegate.command;
|
||||
_initialized = false;
|
||||
timeoutSpinBox.value = entryDelegate.timeout;
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NSpinBox {
|
||||
id: timeoutSpinBox
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.idle.custom-entry-timeout")
|
||||
from: 1
|
||||
to: 86400
|
||||
suffix: "s"
|
||||
onValueChanged: {
|
||||
if (entryDelegate._initialized && !root._saving) {
|
||||
entriesModel.setProperty(entryDelegate.index, "timeout", value);
|
||||
root._saveFromModel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "trash"
|
||||
tooltipText: I18n.tr("panels.idle.custom-entry-delete")
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
onClicked: {
|
||||
entriesModel.remove(entryDelegate.index, 1);
|
||||
root._saveFromModel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: commandInput
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.idle.custom-entry-command")
|
||||
placeholderText: "notify-send \"Idle\""
|
||||
fontFamily: Settings.data.ui.fontFixed
|
||||
onTextChanged: {
|
||||
if (entryDelegate._initialized && !root._saving) {
|
||||
entriesModel.setProperty(entryDelegate.index, "command", text);
|
||||
root._saveFromModel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginS
|
||||
Layout.bottomMargin: Style.marginS
|
||||
visible: entryDelegate.index < entriesModel.count - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("panels.idle.custom-add")
|
||||
icon: "add"
|
||||
enabled: Settings.data.idle.enabled
|
||||
onClicked: {
|
||||
entriesModel.append({
|
||||
"timeout": 60,
|
||||
"command": ""
|
||||
});
|
||||
root._saveFromModel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,108 +2,41 @@ import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.Power
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
spacing: 0
|
||||
|
||||
// Master enable
|
||||
NToggle {
|
||||
NTabBar {
|
||||
id: subTabBar
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.idle.enable-label")
|
||||
description: I18n.tr("panels.idle.enable-description")
|
||||
checked: Settings.data.idle.enabled
|
||||
defaultValue: Settings.getDefaultValue("idle.enabled")
|
||||
onToggled: checked => Settings.data.idle.enabled = checked
|
||||
}
|
||||
Layout.bottomMargin: Style.marginM
|
||||
distributeEvenly: true
|
||||
currentIndex: tabView.currentIndex
|
||||
|
||||
// Live idle status
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
visible: IdleService.nativeIdleMonitorAvailable
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("panels.idle.status-label")
|
||||
description: I18n.tr("panels.idle.status-description")
|
||||
NTabButton {
|
||||
text: I18n.tr("panels.idle.tab-behavior")
|
||||
tabIndex: 0
|
||||
checked: subTabBar.currentIndex === 0
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NText {
|
||||
Layout.alignment: Qt.AlignBottom | Qt.AlignRight
|
||||
text: IdleService.idleSeconds > 0 ? I18n.trp("common.second", IdleService.idleSeconds) : I18n.tr("common.active")
|
||||
family: Settings.data.ui.fontFixed
|
||||
pointSize: Style.fontSizeM
|
||||
color: IdleService.idleSeconds > 0 ? Color.mPrimary : Color.mOnSurfaceVariant
|
||||
NTabButton {
|
||||
text: I18n.tr("panels.idle.tab-custom")
|
||||
tabIndex: 1
|
||||
checked: subTabBar.currentIndex === 1
|
||||
}
|
||||
}
|
||||
|
||||
NLabel {
|
||||
visible: !IdleService.nativeIdleMonitorAvailable
|
||||
description: I18n.tr("panels.idle.unavailable")
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Style.marginL
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
NTabView {
|
||||
id: tabView
|
||||
currentIndex: subTabBar.currentIndex
|
||||
|
||||
// Timeout spinboxes (disabled when idle is off)
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginL
|
||||
enabled: Settings.data.idle.enabled
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("panels.idle.timeouts-label")
|
||||
description: I18n.tr("panels.idle.timeouts-description")
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
label: I18n.tr("panels.idle.screen-off-label")
|
||||
description: I18n.tr("panels.idle.screen-off-description")
|
||||
from: 0
|
||||
to: 999
|
||||
value: Settings.data.idle.screenOffTimeout
|
||||
defaultValue: 0
|
||||
onValueChanged: Settings.data.idle.screenOffTimeout = value
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
label: I18n.tr("panels.idle.lock-label")
|
||||
description: I18n.tr("panels.idle.lock-description")
|
||||
from: 0
|
||||
to: 999
|
||||
value: Settings.data.idle.lockTimeout
|
||||
defaultValue: 0
|
||||
onValueChanged: Settings.data.idle.lockTimeout = value
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
label: I18n.tr("common.suspend")
|
||||
description: I18n.tr("panels.idle.suspend-description")
|
||||
from: 0
|
||||
to: 999
|
||||
value: Settings.data.idle.suspendTimeout
|
||||
defaultValue: 0
|
||||
onValueChanged: Settings.data.idle.suspendTimeout = value
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
label: I18n.tr("panels.idle.fade-duration-label")
|
||||
description: I18n.tr("panels.idle.fade-duration-description")
|
||||
from: 1
|
||||
to: 60
|
||||
value: Settings.data.idle.fadeDuration
|
||||
defaultValue: Settings.getDefaultValue("idle.fadeDuration")
|
||||
onValueChanged: Settings.data.idle.fadeDuration = value
|
||||
}
|
||||
BehaviorSubTab {}
|
||||
CustomSubTab {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
enabled: Settings.data.notifications.enabled
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.notifications.duration-respect-expire-label")
|
||||
|
||||
@@ -9,7 +9,7 @@ import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
property var addMonitor
|
||||
@@ -23,119 +23,124 @@ ColumnLayout {
|
||||
defaultValue: Settings.getDefaultValue("notifications.enabled")
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("panels.notifications.settings-density-label")
|
||||
description: I18n.tr("panels.notifications.settings-density-description")
|
||||
model: [
|
||||
{
|
||||
"key": "default",
|
||||
"name": I18n.tr("options.notification-density.default")
|
||||
},
|
||||
{
|
||||
"key": "compact",
|
||||
"name": I18n.tr("options.notification-density.compact")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.notifications.density || "default"
|
||||
onSelected: key => Settings.data.notifications.density = key
|
||||
defaultValue: Settings.getDefaultValue("notifications.density")
|
||||
}
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL
|
||||
enabled: Settings.data.notifications.enabled
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("tooltips.do-not-disturb-enabled")
|
||||
description: I18n.tr("panels.notifications.settings-do-not-disturb-description")
|
||||
checked: NotificationService.doNotDisturb
|
||||
onToggled: checked => NotificationService.doNotDisturb = checked
|
||||
}
|
||||
NComboBox {
|
||||
label: I18n.tr("panels.notifications.settings-density-label")
|
||||
description: I18n.tr("panels.notifications.settings-density-description")
|
||||
model: [
|
||||
{
|
||||
"key": "default",
|
||||
"name": I18n.tr("options.notification-density.default")
|
||||
},
|
||||
{
|
||||
"key": "compact",
|
||||
"name": I18n.tr("options.notification-density.compact")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.notifications.density || "default"
|
||||
onSelected: key => Settings.data.notifications.density = key
|
||||
defaultValue: Settings.getDefaultValue("notifications.density")
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("common.position")
|
||||
description: I18n.tr("panels.notifications.settings-location-description")
|
||||
model: [
|
||||
{
|
||||
"key": "top",
|
||||
"name": I18n.tr("positions.top-center")
|
||||
},
|
||||
{
|
||||
"key": "top_left",
|
||||
"name": I18n.tr("positions.top-left")
|
||||
},
|
||||
{
|
||||
"key": "top_right",
|
||||
"name": I18n.tr("positions.top-right")
|
||||
},
|
||||
{
|
||||
"key": "bottom",
|
||||
"name": I18n.tr("positions.bottom-center")
|
||||
},
|
||||
{
|
||||
"key": "bottom_left",
|
||||
"name": I18n.tr("positions.bottom-left")
|
||||
},
|
||||
{
|
||||
"key": "bottom_right",
|
||||
"name": I18n.tr("positions.bottom-right")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.notifications.location || "top_right"
|
||||
onSelected: key => Settings.data.notifications.location = key
|
||||
defaultValue: Settings.getDefaultValue("notifications.location")
|
||||
}
|
||||
NToggle {
|
||||
label: I18n.tr("tooltips.do-not-disturb-enabled")
|
||||
description: I18n.tr("panels.notifications.settings-do-not-disturb-description")
|
||||
checked: NotificationService.doNotDisturb
|
||||
onToggled: checked => NotificationService.doNotDisturb = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.osd.always-on-top-label")
|
||||
description: I18n.tr("panels.notifications.settings-always-on-top-description")
|
||||
checked: Settings.data.notifications.overlayLayer
|
||||
onToggled: checked => Settings.data.notifications.overlayLayer = checked
|
||||
defaultValue: Settings.getDefaultValue("notifications.overlayLayer")
|
||||
}
|
||||
NComboBox {
|
||||
label: I18n.tr("common.position")
|
||||
description: I18n.tr("panels.notifications.settings-location-description")
|
||||
model: [
|
||||
{
|
||||
"key": "top",
|
||||
"name": I18n.tr("positions.top-center")
|
||||
},
|
||||
{
|
||||
"key": "top_left",
|
||||
"name": I18n.tr("positions.top-left")
|
||||
},
|
||||
{
|
||||
"key": "top_right",
|
||||
"name": I18n.tr("positions.top-right")
|
||||
},
|
||||
{
|
||||
"key": "bottom",
|
||||
"name": I18n.tr("positions.bottom-center")
|
||||
},
|
||||
{
|
||||
"key": "bottom_left",
|
||||
"name": I18n.tr("positions.bottom-left")
|
||||
},
|
||||
{
|
||||
"key": "bottom_right",
|
||||
"name": I18n.tr("positions.bottom-right")
|
||||
}
|
||||
]
|
||||
currentKey: Settings.data.notifications.location || "top_right"
|
||||
onSelected: key => Settings.data.notifications.location = key
|
||||
defaultValue: Settings.getDefaultValue("notifications.location")
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("panels.osd.background-opacity-label")
|
||||
description: I18n.tr("panels.notifications.settings-background-opacity-description")
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
showReset: true
|
||||
value: Settings.data.notifications.backgroundOpacity
|
||||
onMoved: value => Settings.data.notifications.backgroundOpacity = value
|
||||
text: Math.round(Settings.data.notifications.backgroundOpacity * 100) + "%"
|
||||
defaultValue: Settings.getDefaultValue("notifications.backgroundOpacity")
|
||||
}
|
||||
NToggle {
|
||||
label: I18n.tr("panels.osd.always-on-top-label")
|
||||
description: I18n.tr("panels.notifications.settings-always-on-top-description")
|
||||
checked: Settings.data.notifications.overlayLayer
|
||||
onToggled: checked => Settings.data.notifications.overlayLayer = checked
|
||||
defaultValue: Settings.getDefaultValue("notifications.overlayLayer")
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("panels.notifications.monitors-desc")
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Quickshell.screens || []
|
||||
delegate: NCheckbox {
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: modelData.name || I18n.tr("common.unknown")
|
||||
description: {
|
||||
const compositorScale = CompositorService.getDisplayScale(modelData.name);
|
||||
I18n.tr("system.monitor-description", {
|
||||
"model": modelData.model,
|
||||
"width": modelData.width * compositorScale,
|
||||
"height": modelData.height * compositorScale,
|
||||
"scale": compositorScale
|
||||
});
|
||||
}
|
||||
checked: (Settings.data.notifications.monitors || []).indexOf(modelData.name) !== -1
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
Settings.data.notifications.monitors = root.addMonitor(Settings.data.notifications.monitors, modelData.name);
|
||||
} else {
|
||||
Settings.data.notifications.monitors = root.removeMonitor(Settings.data.notifications.monitors, modelData.name);
|
||||
label: I18n.tr("panels.osd.background-opacity-label")
|
||||
description: I18n.tr("panels.notifications.settings-background-opacity-description")
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
showReset: true
|
||||
value: Settings.data.notifications.backgroundOpacity
|
||||
onMoved: value => Settings.data.notifications.backgroundOpacity = value
|
||||
text: Math.round(Settings.data.notifications.backgroundOpacity * 100) + "%"
|
||||
defaultValue: Settings.getDefaultValue("notifications.backgroundOpacity")
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("panels.notifications.monitors-desc")
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Quickshell.screens || []
|
||||
delegate: NCheckbox {
|
||||
Layout.fillWidth: true
|
||||
label: modelData.name || I18n.tr("common.unknown")
|
||||
description: {
|
||||
const compositorScale = CompositorService.getDisplayScale(modelData.name);
|
||||
I18n.tr("system.monitor-description", {
|
||||
"model": modelData.model,
|
||||
"width": modelData.width * compositorScale,
|
||||
"height": modelData.height * compositorScale,
|
||||
"scale": compositorScale
|
||||
});
|
||||
}
|
||||
checked: (Settings.data.notifications.monitors || []).indexOf(modelData.name) !== -1
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
Settings.data.notifications.monitors = root.addMonitor(Settings.data.notifications.monitors, modelData.name);
|
||||
} else {
|
||||
Settings.data.notifications.monitors = root.removeMonitor(Settings.data.notifications.monitors, modelData.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
enabled: Settings.data.notifications.enabled
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("panels.notifications.history-clear-dismiss-label")
|
||||
|
||||
@@ -10,6 +10,7 @@ ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
Layout.fillWidth: true
|
||||
enabled: Settings.data.notifications.enabled
|
||||
|
||||
signal openUnifiedPicker
|
||||
signal openLowPicker
|
||||
|
||||
@@ -20,8 +20,7 @@ import qs.Services.UI
|
||||
* IdleMonitor instances are created with Qt.createQmlObject() so the shell
|
||||
* does not crash on compositors that lack the protocol.
|
||||
*
|
||||
* Timeouts come from Settings.data.idle (in minutes). 0 = disabled.
|
||||
* NOTE: IdleMonitor.timeout is in seconds.
|
||||
* Timeouts come from Settings.data.idle (in seconds). 0 = disabled.
|
||||
*/
|
||||
Singleton {
|
||||
id: root
|
||||
@@ -41,6 +40,7 @@ Singleton {
|
||||
property var _lockMonitor: null
|
||||
property var _suspendMonitor: null
|
||||
property var _heartbeatMonitor: null
|
||||
property var _customMonitors: ({})
|
||||
|
||||
// Signals for external listeners (plugins, modules)
|
||||
signal screenOffRequested
|
||||
@@ -130,16 +130,76 @@ Singleton {
|
||||
function onEnabledChanged() {
|
||||
root._applyTimeouts();
|
||||
}
|
||||
function onCustomCommandsChanged() {
|
||||
root._applyCustomMonitors();
|
||||
}
|
||||
}
|
||||
|
||||
function _applyTimeouts() {
|
||||
const idle = Settings.data.idle;
|
||||
const globalEnabled = idle.enabled;
|
||||
|
||||
_setMonitor("screenOff", globalEnabled ? idle.screenOffTimeout * 60 : 0);
|
||||
_setMonitor("lock", globalEnabled ? idle.lockTimeout * 60 : 0);
|
||||
_setMonitor("suspend", globalEnabled ? idle.suspendTimeout * 60 : 0);
|
||||
_setMonitor("screenOff", globalEnabled ? idle.screenOffTimeout : 0);
|
||||
_setMonitor("lock", globalEnabled ? idle.lockTimeout : 0);
|
||||
_setMonitor("suspend", globalEnabled ? idle.suspendTimeout : 0);
|
||||
_ensureHeartbeat();
|
||||
_applyCustomMonitors();
|
||||
}
|
||||
|
||||
function _applyCustomMonitors() {
|
||||
// Destroy all existing custom monitors
|
||||
for (var key in _customMonitors) {
|
||||
if (_customMonitors[key]) {
|
||||
_customMonitors[key].destroy();
|
||||
}
|
||||
}
|
||||
root._customMonitors = {};
|
||||
|
||||
const idle = Settings.data.idle;
|
||||
if (!idle.enabled)
|
||||
return;
|
||||
|
||||
var entries = [];
|
||||
try {
|
||||
entries = JSON.parse(idle.customCommands);
|
||||
} catch (e) {
|
||||
Logger.w("IdleService", "Failed to parse customCommands:", e);
|
||||
return;
|
||||
}
|
||||
|
||||
var newMonitors = {};
|
||||
for (var i = 0; i < entries.length; i++) {
|
||||
const entry = entries[i];
|
||||
const timeoutSec = parseInt(entry.timeout);
|
||||
const cmd = entry.command;
|
||||
if (!cmd || timeoutSec <= 0)
|
||||
continue;
|
||||
try {
|
||||
const qml = `
|
||||
import Quickshell.Wayland
|
||||
IdleMonitor { timeout: ${timeoutSec} }
|
||||
`;
|
||||
|
||||
const monitor = Qt.createQmlObject(qml, root, "IdleMonitor_custom_" + i);
|
||||
const capturedCmd = cmd;
|
||||
monitor.isIdleChanged.connect(function () {
|
||||
if (monitor.isIdle) {
|
||||
root._executeCustomCommand(capturedCmd);
|
||||
}
|
||||
});
|
||||
newMonitors[i] = monitor;
|
||||
root._monitorsCreated = true;
|
||||
Logger.i("IdleService", "Custom monitor " + i + " created, timeout", timeoutSec, "s");
|
||||
} catch (e) {
|
||||
Logger.w("IdleService", "Failed to create custom monitor " + i + ":", e);
|
||||
}
|
||||
}
|
||||
root._customMonitors = newMonitors;
|
||||
}
|
||||
|
||||
function _executeCustomCommand(cmd) {
|
||||
Logger.i("IdleService", "Executing custom command:", cmd);
|
||||
Quickshell.execDetached(["sh", "-c", cmd]);
|
||||
}
|
||||
|
||||
function _setMonitor(stage, timeoutSec) {
|
||||
@@ -198,6 +258,7 @@ Singleton {
|
||||
const monitor = Qt.createQmlObject(qml, root, "IdleMonitor_heartbeat");
|
||||
monitor.isIdleChanged.connect(function () {
|
||||
if (monitor.isIdle) {
|
||||
root.idleSeconds = 1;
|
||||
idleCounter.start();
|
||||
} else {
|
||||
idleCounter.stop();
|
||||
|
||||
Reference in New Issue
Block a user