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",
|
"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-description": "Seconds for the fade-to-black animation before each action fires. Any mouse movement cancels the fade.",
|
||||||
"fade-duration-label": "Fade duration",
|
"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",
|
"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",
|
"screen-off-label": "Turn off screen",
|
||||||
"status-description": "Idle time as reported by the compositor.",
|
"status-description": "Idle time as reported by the compositor.",
|
||||||
"status-label": "Idle time",
|
"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-description": "Set to 0 to disable a stage. Timeouts are paused while Keep Awake is active.",
|
||||||
"timeouts-label": "Timeouts",
|
"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": {
|
"indicator": {
|
||||||
"default-value": "Default: {value}",
|
"default-value": "Default: {value}",
|
||||||
|
|||||||
@@ -932,7 +932,8 @@
|
|||||||
"widget": "NToggle",
|
"widget": "NToggle",
|
||||||
"tab": 13,
|
"tab": 13,
|
||||||
"tabLabel": "common.idle",
|
"tabLabel": "common.idle",
|
||||||
"subTab": null
|
"subTab": 0,
|
||||||
|
"subTabLabel": "panels.idle.tab-behavior"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"labelKey": "panels.idle.status-label",
|
"labelKey": "panels.idle.status-label",
|
||||||
@@ -940,7 +941,8 @@
|
|||||||
"widget": "NLabel",
|
"widget": "NLabel",
|
||||||
"tab": 13,
|
"tab": 13,
|
||||||
"tabLabel": "common.idle",
|
"tabLabel": "common.idle",
|
||||||
"subTab": null
|
"subTab": 0,
|
||||||
|
"subTabLabel": "panels.idle.tab-behavior"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"labelKey": "panels.idle.timeouts-label",
|
"labelKey": "panels.idle.timeouts-label",
|
||||||
@@ -948,7 +950,8 @@
|
|||||||
"widget": "NLabel",
|
"widget": "NLabel",
|
||||||
"tab": 13,
|
"tab": 13,
|
||||||
"tabLabel": "common.idle",
|
"tabLabel": "common.idle",
|
||||||
"subTab": null
|
"subTab": 0,
|
||||||
|
"subTabLabel": "panels.idle.tab-behavior"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"labelKey": "panels.idle.screen-off-label",
|
"labelKey": "panels.idle.screen-off-label",
|
||||||
@@ -956,7 +959,8 @@
|
|||||||
"widget": "NSpinBox",
|
"widget": "NSpinBox",
|
||||||
"tab": 13,
|
"tab": 13,
|
||||||
"tabLabel": "common.idle",
|
"tabLabel": "common.idle",
|
||||||
"subTab": null
|
"subTab": 0,
|
||||||
|
"subTabLabel": "panels.idle.tab-behavior"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"labelKey": "panels.idle.lock-label",
|
"labelKey": "panels.idle.lock-label",
|
||||||
@@ -964,7 +968,8 @@
|
|||||||
"widget": "NSpinBox",
|
"widget": "NSpinBox",
|
||||||
"tab": 13,
|
"tab": 13,
|
||||||
"tabLabel": "common.idle",
|
"tabLabel": "common.idle",
|
||||||
"subTab": null
|
"subTab": 0,
|
||||||
|
"subTabLabel": "panels.idle.tab-behavior"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"labelKey": "common.suspend",
|
"labelKey": "common.suspend",
|
||||||
@@ -972,7 +977,8 @@
|
|||||||
"widget": "NSpinBox",
|
"widget": "NSpinBox",
|
||||||
"tab": 13,
|
"tab": 13,
|
||||||
"tabLabel": "common.idle",
|
"tabLabel": "common.idle",
|
||||||
"subTab": null
|
"subTab": 0,
|
||||||
|
"subTabLabel": "panels.idle.tab-behavior"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"labelKey": "panels.idle.fade-duration-label",
|
"labelKey": "panels.idle.fade-duration-label",
|
||||||
@@ -980,7 +986,35 @@
|
|||||||
"widget": "NSpinBox",
|
"widget": "NSpinBox",
|
||||||
"tab": 13,
|
"tab": 13,
|
||||||
"tabLabel": "common.idle",
|
"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",
|
"labelKey": "panels.launcher.settings-clipboard-history-label",
|
||||||
|
|||||||
@@ -722,10 +722,11 @@ Singleton {
|
|||||||
// idle management
|
// idle management
|
||||||
property JsonObject idle: JsonObject {
|
property JsonObject idle: JsonObject {
|
||||||
property bool enabled: false
|
property bool enabled: false
|
||||||
property int screenOffTimeout: 0 // minutes, 0 = disabled
|
property int screenOffTimeout: 0 // seconds, 0 = disabled
|
||||||
property int lockTimeout: 0 // minutes, 0 = disabled
|
property int lockTimeout: 0 // seconds, 0 = disabled
|
||||||
property int suspendTimeout: 0 // minutes, 0 = disabled
|
property int suspendTimeout: 0 // seconds, 0 = disabled
|
||||||
property int fadeDuration: 5 // seconds of fade-to-black before action fires
|
property int fadeDuration: 5 // seconds of fade-to-black before action fires
|
||||||
|
property string customCommands: "[]" // JSON array of {timeout, command}
|
||||||
}
|
}
|
||||||
|
|
||||||
// desktop widgets
|
// 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.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import qs.Commons
|
import qs.Commons
|
||||||
import qs.Services.Power
|
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: root
|
id: root
|
||||||
spacing: Style.marginL
|
spacing: 0
|
||||||
|
|
||||||
// Master enable
|
NTabBar {
|
||||||
NToggle {
|
id: subTabBar
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
label: I18n.tr("panels.idle.enable-label")
|
Layout.bottomMargin: Style.marginM
|
||||||
description: I18n.tr("panels.idle.enable-description")
|
distributeEvenly: true
|
||||||
checked: Settings.data.idle.enabled
|
currentIndex: tabView.currentIndex
|
||||||
defaultValue: Settings.getDefaultValue("idle.enabled")
|
|
||||||
onToggled: checked => Settings.data.idle.enabled = checked
|
|
||||||
}
|
|
||||||
|
|
||||||
// Live idle status
|
NTabButton {
|
||||||
RowLayout {
|
text: I18n.tr("panels.idle.tab-behavior")
|
||||||
Layout.fillWidth: true
|
tabIndex: 0
|
||||||
visible: IdleService.nativeIdleMonitorAvailable
|
checked: subTabBar.currentIndex === 0
|
||||||
|
|
||||||
NLabel {
|
|
||||||
label: I18n.tr("panels.idle.status-label")
|
|
||||||
description: I18n.tr("panels.idle.status-description")
|
|
||||||
}
|
}
|
||||||
|
NTabButton {
|
||||||
Item {
|
text: I18n.tr("panels.idle.tab-custom")
|
||||||
Layout.fillWidth: true
|
tabIndex: 1
|
||||||
}
|
checked: subTabBar.currentIndex === 1
|
||||||
|
|
||||||
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 {
|
Item {
|
||||||
visible: !IdleService.nativeIdleMonitorAvailable
|
Layout.fillWidth: true
|
||||||
description: I18n.tr("panels.idle.unavailable")
|
Layout.preferredHeight: Style.marginL
|
||||||
}
|
}
|
||||||
|
|
||||||
NDivider {
|
NTabView {
|
||||||
Layout.fillWidth: true
|
id: tabView
|
||||||
}
|
currentIndex: subTabBar.currentIndex
|
||||||
|
|
||||||
// Timeout spinboxes (disabled when idle is off)
|
BehaviorSubTab {}
|
||||||
ColumnLayout {
|
CustomSubTab {}
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ ColumnLayout {
|
|||||||
id: root
|
id: root
|
||||||
spacing: Style.marginL
|
spacing: Style.marginL
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
enabled: Settings.data.notifications.enabled
|
||||||
|
|
||||||
NToggle {
|
NToggle {
|
||||||
label: I18n.tr("panels.notifications.duration-respect-expire-label")
|
label: I18n.tr("panels.notifications.duration-respect-expire-label")
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import qs.Widgets
|
|||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: root
|
id: root
|
||||||
spacing: Style.marginL
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
property var addMonitor
|
property var addMonitor
|
||||||
@@ -23,119 +23,124 @@ ColumnLayout {
|
|||||||
defaultValue: Settings.getDefaultValue("notifications.enabled")
|
defaultValue: Settings.getDefaultValue("notifications.enabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
NComboBox {
|
ColumnLayout {
|
||||||
label: I18n.tr("panels.notifications.settings-density-label")
|
spacing: Style.marginL
|
||||||
description: I18n.tr("panels.notifications.settings-density-description")
|
enabled: Settings.data.notifications.enabled
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
NToggle {
|
NComboBox {
|
||||||
label: I18n.tr("tooltips.do-not-disturb-enabled")
|
label: I18n.tr("panels.notifications.settings-density-label")
|
||||||
description: I18n.tr("panels.notifications.settings-do-not-disturb-description")
|
description: I18n.tr("panels.notifications.settings-density-description")
|
||||||
checked: NotificationService.doNotDisturb
|
model: [
|
||||||
onToggled: checked => NotificationService.doNotDisturb = checked
|
{
|
||||||
}
|
"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 {
|
NToggle {
|
||||||
label: I18n.tr("common.position")
|
label: I18n.tr("tooltips.do-not-disturb-enabled")
|
||||||
description: I18n.tr("panels.notifications.settings-location-description")
|
description: I18n.tr("panels.notifications.settings-do-not-disturb-description")
|
||||||
model: [
|
checked: NotificationService.doNotDisturb
|
||||||
{
|
onToggled: checked => NotificationService.doNotDisturb = checked
|
||||||
"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 {
|
NComboBox {
|
||||||
label: I18n.tr("panels.osd.always-on-top-label")
|
label: I18n.tr("common.position")
|
||||||
description: I18n.tr("panels.notifications.settings-always-on-top-description")
|
description: I18n.tr("panels.notifications.settings-location-description")
|
||||||
checked: Settings.data.notifications.overlayLayer
|
model: [
|
||||||
onToggled: checked => Settings.data.notifications.overlayLayer = checked
|
{
|
||||||
defaultValue: Settings.getDefaultValue("notifications.overlayLayer")
|
"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 {
|
NToggle {
|
||||||
Layout.fillWidth: true
|
label: I18n.tr("panels.osd.always-on-top-label")
|
||||||
label: I18n.tr("panels.osd.background-opacity-label")
|
description: I18n.tr("panels.notifications.settings-always-on-top-description")
|
||||||
description: I18n.tr("panels.notifications.settings-background-opacity-description")
|
checked: Settings.data.notifications.overlayLayer
|
||||||
from: 0
|
onToggled: checked => Settings.data.notifications.overlayLayer = checked
|
||||||
to: 1
|
defaultValue: Settings.getDefaultValue("notifications.overlayLayer")
|
||||||
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 {
|
NValueSlider {
|
||||||
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
|
Layout.fillWidth: true
|
||||||
label: modelData.name || I18n.tr("common.unknown")
|
label: I18n.tr("panels.osd.background-opacity-label")
|
||||||
description: {
|
description: I18n.tr("panels.notifications.settings-background-opacity-description")
|
||||||
const compositorScale = CompositorService.getDisplayScale(modelData.name);
|
from: 0
|
||||||
I18n.tr("system.monitor-description", {
|
to: 1
|
||||||
"model": modelData.model,
|
stepSize: 0.01
|
||||||
"width": modelData.width * compositorScale,
|
showReset: true
|
||||||
"height": modelData.height * compositorScale,
|
value: Settings.data.notifications.backgroundOpacity
|
||||||
"scale": compositorScale
|
onMoved: value => Settings.data.notifications.backgroundOpacity = value
|
||||||
});
|
text: Math.round(Settings.data.notifications.backgroundOpacity * 100) + "%"
|
||||||
}
|
defaultValue: Settings.getDefaultValue("notifications.backgroundOpacity")
|
||||||
checked: (Settings.data.notifications.monitors || []).indexOf(modelData.name) !== -1
|
}
|
||||||
onToggled: checked => {
|
|
||||||
if (checked) {
|
NDivider {
|
||||||
Settings.data.notifications.monitors = root.addMonitor(Settings.data.notifications.monitors, modelData.name);
|
Layout.fillWidth: true
|
||||||
} else {
|
}
|
||||||
Settings.data.notifications.monitors = root.removeMonitor(Settings.data.notifications.monitors, modelData.name);
|
|
||||||
|
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
|
id: root
|
||||||
spacing: Style.marginL
|
spacing: Style.marginL
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
enabled: Settings.data.notifications.enabled
|
||||||
|
|
||||||
NToggle {
|
NToggle {
|
||||||
label: I18n.tr("panels.notifications.history-clear-dismiss-label")
|
label: I18n.tr("panels.notifications.history-clear-dismiss-label")
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ ColumnLayout {
|
|||||||
id: root
|
id: root
|
||||||
spacing: Style.marginL
|
spacing: Style.marginL
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
enabled: Settings.data.notifications.enabled
|
||||||
|
|
||||||
signal openUnifiedPicker
|
signal openUnifiedPicker
|
||||||
signal openLowPicker
|
signal openLowPicker
|
||||||
|
|||||||
@@ -20,8 +20,7 @@ import qs.Services.UI
|
|||||||
* IdleMonitor instances are created with Qt.createQmlObject() so the shell
|
* IdleMonitor instances are created with Qt.createQmlObject() so the shell
|
||||||
* does not crash on compositors that lack the protocol.
|
* does not crash on compositors that lack the protocol.
|
||||||
*
|
*
|
||||||
* Timeouts come from Settings.data.idle (in minutes). 0 = disabled.
|
* Timeouts come from Settings.data.idle (in seconds). 0 = disabled.
|
||||||
* NOTE: IdleMonitor.timeout is in seconds.
|
|
||||||
*/
|
*/
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
@@ -41,6 +40,7 @@ Singleton {
|
|||||||
property var _lockMonitor: null
|
property var _lockMonitor: null
|
||||||
property var _suspendMonitor: null
|
property var _suspendMonitor: null
|
||||||
property var _heartbeatMonitor: null
|
property var _heartbeatMonitor: null
|
||||||
|
property var _customMonitors: ({})
|
||||||
|
|
||||||
// Signals for external listeners (plugins, modules)
|
// Signals for external listeners (plugins, modules)
|
||||||
signal screenOffRequested
|
signal screenOffRequested
|
||||||
@@ -130,16 +130,76 @@ Singleton {
|
|||||||
function onEnabledChanged() {
|
function onEnabledChanged() {
|
||||||
root._applyTimeouts();
|
root._applyTimeouts();
|
||||||
}
|
}
|
||||||
|
function onCustomCommandsChanged() {
|
||||||
|
root._applyCustomMonitors();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _applyTimeouts() {
|
function _applyTimeouts() {
|
||||||
const idle = Settings.data.idle;
|
const idle = Settings.data.idle;
|
||||||
const globalEnabled = idle.enabled;
|
const globalEnabled = idle.enabled;
|
||||||
|
|
||||||
_setMonitor("screenOff", globalEnabled ? idle.screenOffTimeout * 60 : 0);
|
_setMonitor("screenOff", globalEnabled ? idle.screenOffTimeout : 0);
|
||||||
_setMonitor("lock", globalEnabled ? idle.lockTimeout * 60 : 0);
|
_setMonitor("lock", globalEnabled ? idle.lockTimeout : 0);
|
||||||
_setMonitor("suspend", globalEnabled ? idle.suspendTimeout * 60 : 0);
|
_setMonitor("suspend", globalEnabled ? idle.suspendTimeout : 0);
|
||||||
_ensureHeartbeat();
|
_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) {
|
function _setMonitor(stage, timeoutSec) {
|
||||||
@@ -198,6 +258,7 @@ Singleton {
|
|||||||
const monitor = Qt.createQmlObject(qml, root, "IdleMonitor_heartbeat");
|
const monitor = Qt.createQmlObject(qml, root, "IdleMonitor_heartbeat");
|
||||||
monitor.isIdleChanged.connect(function () {
|
monitor.isIdleChanged.connect(function () {
|
||||||
if (monitor.isIdle) {
|
if (monitor.isIdle) {
|
||||||
|
root.idleSeconds = 1;
|
||||||
idleCounter.start();
|
idleCounter.start();
|
||||||
} else {
|
} else {
|
||||||
idleCounter.stop();
|
idleCounter.stop();
|
||||||
|
|||||||
Reference in New Issue
Block a user