add noctalia and fuzzel

This commit is contained in:
2026-05-23 21:20:58 +02:00
parent 364801f1a3
commit 9a3eceb3ab
599 changed files with 204318 additions and 0 deletions
@@ -0,0 +1,199 @@
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
}
IdleCommandEditPopup {
id: editPopup
parent: Overlay.overlay
}
function openEdit(actionName, cmdVal, resumeCmdVal, onSaveCmd, onSaveResume) {
editPopup.editIndex = -1;
editPopup.showCommand = true;
editPopup.showTimeout = false;
editPopup.titleText = I18n.tr("common.edit") + " " + actionName;
editPopup.timeoutValue = 0;
editPopup.commandValue = cmdVal;
editPopup.resumeCommandValue = resumeCmdVal;
try {
editPopup.saved.disconnect(editPopup._savedSlot);
} catch (e) {}
editPopup._savedSlot = function (timeout, cmd, resumeCmd, name) {
onSaveCmd(cmd);
onSaveResume(resumeCmd);
};
editPopup.saved.connect(editPopup._savedSlot);
editPopup.open();
}
// Timeout spinboxes and resume commands
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")
}
DefaultActionRow {
actionName: I18n.tr("panels.idle.screen-off-label")
actionDescription: I18n.tr("panels.idle.screen-off-description")
timeoutValue: Settings.data.idle.screenOffTimeout
defaultValue: Settings.getDefaultValue("idle.screenOffTimeout")
command: Settings.data.idle.screenOffCommand
resumeCommand: Settings.data.idle.resumeScreenOffCommand
onActionTimeoutChanged: val => Settings.data.idle.screenOffTimeout = val
onActionCommandChanged: cmd => {
Settings.data.idle.screenOffCommand = cmd;
Settings.saveImmediate();
}
onActionResumeCommandChanged: cmd => {
Settings.data.idle.resumeScreenOffCommand = cmd;
Settings.saveImmediate();
}
}
DefaultActionRow {
actionName: I18n.tr("panels.idle.lock-label")
actionDescription: I18n.tr("panels.idle.lock-description")
timeoutValue: Settings.data.idle.lockTimeout
defaultValue: Settings.getDefaultValue("idle.lockTimeout")
command: Settings.data.idle.lockCommand
resumeCommand: Settings.data.idle.resumeLockCommand
onActionTimeoutChanged: val => Settings.data.idle.lockTimeout = val
onActionCommandChanged: cmd => {
Settings.data.idle.lockCommand = cmd;
Settings.saveImmediate();
}
onActionResumeCommandChanged: cmd => {
Settings.data.idle.resumeLockCommand = cmd;
Settings.saveImmediate();
}
}
DefaultActionRow {
actionName: I18n.tr("common.suspend")
actionDescription: I18n.tr("panels.idle.suspend-description")
timeoutValue: Settings.data.idle.suspendTimeout
defaultValue: Settings.getDefaultValue("idle.suspendTimeout")
command: Settings.data.idle.suspendCommand
resumeCommand: Settings.data.idle.resumeSuspendCommand
onActionTimeoutChanged: val => Settings.data.idle.suspendTimeout = val
onActionCommandChanged: cmd => {
Settings.data.idle.suspendCommand = cmd;
Settings.saveImmediate();
}
onActionResumeCommandChanged: cmd => {
Settings.data.idle.resumeSuspendCommand = cmd;
Settings.saveImmediate();
}
}
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
}
}
component DefaultActionRow: RowLayout {
id: rowRoot
Layout.fillWidth: true
spacing: Style.marginM
property string actionName
property string actionDescription
property alias timeoutValue: spinBox.value
property int defaultValue
property string command
property string resumeCommand
signal actionTimeoutChanged(int newValue)
signal actionCommandChanged(string newCmd)
signal actionResumeCommandChanged(string newCmd)
NSpinBox {
id: spinBox
Layout.fillWidth: true
label: rowRoot.actionName
description: rowRoot.actionDescription
from: 0
to: 86400
suffix: "s"
defaultValue: rowRoot.defaultValue
onValueChanged: rowRoot.actionTimeoutChanged(value)
}
NIconButton {
Layout.alignment: Qt.AlignVCenter
icon: "settings"
tooltipText: I18n.tr("common.edit")
onClicked: root.openEdit(rowRoot.actionName, rowRoot.command, rowRoot.resumeCommand, rowRoot.actionCommandChanged, rowRoot.actionResumeCommandChanged)
}
}
}
@@ -0,0 +1,163 @@
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({
"name": String(entries[i].name || ""),
"timeout": parseInt(entries[i].timeout) || 60,
"command": String(entries[i].command || ""),
"resumeCommand": String(entries[i].resumeCommand || "")
});
}
}
function _saveFromModel() {
_saving = true;
var arr = [];
for (var i = 0; i < entriesModel.count; i++) {
var item = entriesModel.get(i);
arr.push({
"name": item.name,
"timeout": item.timeout,
"command": item.command,
"resumeCommand": item.resumeCommand
});
}
Settings.data.idle.customCommands = JSON.stringify(arr);
_saving = false;
}
function _removeEntry(index) {
entriesModel.remove(index, 1);
_saveFromModel();
}
Component.onCompleted: Qt.callLater(_loadToModel)
Connections {
target: Settings.data.idle
function onCustomCommandsChanged() {
root._loadToModel();
}
}
// Shared Edit Popup
IdleCommandEditPopup {
id: editPopup
parent: Overlay.overlay
}
function openEdit(index, nameVal, timeoutVal, cmdVal, resumeCmdVal) {
editPopup.editIndex = index;
editPopup.nameValue = nameVal;
editPopup.timeoutValue = timeoutVal;
editPopup.commandValue = cmdVal;
editPopup.resumeCommandValue = resumeCmdVal;
editPopup.showName = true;
try {
editPopup.saved.disconnect(editPopup._savedSlot);
} catch (e) {}
editPopup._savedSlot = function (timeout, cmd, resumeCmd, name) {
if (index >= 0 && index < entriesModel.count) {
entriesModel.setProperty(index, "name", name);
entriesModel.setProperty(index, "timeout", timeout);
entriesModel.setProperty(index, "command", cmd);
entriesModel.setProperty(index, "resumeCommand", resumeCmd);
} else {
entriesModel.append({
"name": name,
"timeout": timeout,
"command": cmd,
"resumeCommand": resumeCmd
});
}
root._saveFromModel();
};
editPopup.saved.connect(editPopup._savedSlot);
editPopup.open();
}
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: RowLayout {
id: entryDelegate
required property int index
required property string name
required property int timeout
required property string command
required property string resumeCommand
spacing: Style.marginM
Layout.fillWidth: true
NLabel {
Layout.fillWidth: true
label: entryDelegate.name || I18n.tr("panels.idle.custom-entry-unnamed")
description: I18n.trp("common.second", entryDelegate.timeout)
labelColor: (entryDelegate.command || entryDelegate.resumeCommand) ? Color.mPrimary : Color.mOnSurface
}
NIconButton {
icon: "settings"
tooltipText: I18n.tr("common.edit")
onClicked: root.openEdit(entryDelegate.index, entryDelegate.name, entryDelegate.timeout, entryDelegate.command, entryDelegate.resumeCommand)
}
NIconButton {
icon: "trash"
tooltipText: I18n.tr("panels.idle.custom-entry-delete")
onClicked: root._removeEntry(entryDelegate.index)
}
}
}
NButton {
text: I18n.tr("panels.idle.custom-add")
icon: "add"
enabled: Settings.data.idle.enabled
onClicked: {
root.openEdit(-1, "", 60, "", "");
}
}
}
@@ -0,0 +1,138 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Widgets
Popup {
id: root
modal: true
closePolicy: Popup.NoAutoClose
dim: true
anchors.centerIn: parent
width: Math.min(600 * Style.uiScaleRatio, parent.width * 0.9)
height: Math.min(contentLayout.implicitHeight + padding * 2, parent.height * 0.9)
padding: Style.marginL
property int editIndex: -1
property int timeoutValue: 60
property string commandValue: ""
property string resumeCommandValue: ""
property string nameValue: ""
property bool showCommand: true
property bool showTimeout: true
property bool showName: false
property string titleText: root.editIndex >= 0 ? I18n.tr("panels.idle.custom-entry-edit") : I18n.tr("panels.idle.custom-entry-new")
signal saved(int timeout, string command, string resumeCommand, string name)
property var _savedSlot: null
background: Rectangle {
color: Color.mSurface
radius: Style.radiusL
border.color: Color.mOutline
border.width: Style.borderS
}
onOpened: {
nameInput.text = nameValue;
timeoutSpinBox.value = timeoutValue;
commandInput.text = commandValue;
resumeCommandInput.text = resumeCommandValue;
if (showName) {
nameInput.forceActiveFocus();
} else {
timeoutSpinBox.forceActiveFocus();
}
}
contentItem: ColumnLayout {
id: contentLayout
spacing: Style.marginL
// Header
RowLayout {
Layout.fillWidth: true
NText {
text: root.titleText
font.weight: Style.fontWeightBold
pointSize: Style.fontSizeL
Layout.fillWidth: true
}
NIconButton {
icon: "close"
onClicked: root.close()
}
}
// Input Area
ColumnLayout {
Layout.fillWidth: true
spacing: Style.marginM
NTextInput {
id: nameInput
visible: root.showName
Layout.fillWidth: true
label: I18n.tr("panels.idle.custom-entry-name")
placeholderText: I18n.tr("panels.idle.custom-entry-name-placeholder")
}
NSpinBox {
id: timeoutSpinBox
visible: root.showTimeout
Layout.fillWidth: true
label: I18n.tr("panels.idle.custom-entry-timeout")
from: 0
to: 86400
suffix: "s"
}
NTextInput {
id: commandInput
visible: root.showCommand
Layout.fillWidth: true
label: I18n.tr("panels.idle.custom-entry-command")
placeholderText: "notify-send \"Idle\""
fontFamily: Settings.data.ui.fontFixed
}
NTextInput {
id: resumeCommandInput
Layout.fillWidth: true
label: I18n.tr("panels.idle.resume-command-label")
placeholderText: "notify-send \"Welcome back!\""
fontFamily: Settings.data.ui.fontFixed
}
}
// Action Buttons
RowLayout {
Layout.fillWidth: true
spacing: Style.marginM
Item {
Layout.fillWidth: true
} // Spacer
NButton {
text: I18n.tr("common.cancel")
outlined: true
onClicked: root.close()
}
NButton {
text: I18n.tr("common.save")
icon: "check"
backgroundColor: Color.mPrimary
textColor: Color.mOnPrimary
onClicked: {
root.saved(timeoutSpinBox.value, commandInput.text, resumeCommandInput.text, nameInput.text);
root.close();
}
}
}
}
}
@@ -0,0 +1,42 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Widgets
ColumnLayout {
id: root
spacing: 0
NTabBar {
id: subTabBar
Layout.fillWidth: true
Layout.bottomMargin: Style.marginM
distributeEvenly: true
currentIndex: tabView.currentIndex
NTabButton {
text: I18n.tr("panels.idle.tab-behavior")
tabIndex: 0
checked: subTabBar.currentIndex === 0
}
NTabButton {
text: I18n.tr("panels.idle.tab-custom")
tabIndex: 1
checked: subTabBar.currentIndex === 1
}
}
Item {
Layout.fillWidth: true
Layout.preferredHeight: Style.marginL
}
NTabView {
id: tabView
currentIndex: subTabBar.currentIndex
BehaviorSubTab {}
CustomSubTab {}
}
}