mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2026-08-15 18:43:59 +00:00
Merge pull request #644 from lonerOrz/feat/control-center-custom-button
feat: Implement Control Center custom button basic framework
This commit is contained in:
@@ -857,7 +857,61 @@
|
||||
"label": "Widgets für Kurzbefehle"
|
||||
},
|
||||
"sectionLeft": "Links",
|
||||
"sectionRight": "Rechts"
|
||||
"sectionRight": "Rechts",
|
||||
"custom-button": {
|
||||
"icon": {
|
||||
"label": "Symbol",
|
||||
"description": "Wählen Sie ein Symbol aus der Bibliothek."
|
||||
},
|
||||
"browse": "Durchsuchen",
|
||||
"command": {
|
||||
"label": "Befehl",
|
||||
"description": "Der Befehl, der ausgeführt werden soll, wenn die Schaltfläche geklickt wird."
|
||||
},
|
||||
"tooltip": {
|
||||
"label": "Tooltip",
|
||||
"description": "Der Tooltip, der angezeigt wird, wenn Sie mit der Maus über die Schaltfläche fahren."
|
||||
},
|
||||
"on-state-icon": {
|
||||
"label": "Symbol im Aktiv-Zustand",
|
||||
"description": "Das Symbol für die Schaltfläche, wenn sie sich im 'aktiv' Zustand befindet."
|
||||
},
|
||||
"on-clicked": {
|
||||
"label": "Linksklick-Befehl",
|
||||
"description": "Befehl, der ausgeführt wird, wenn die Schaltfläche links geklickt wird."
|
||||
},
|
||||
"on-right-clicked": {
|
||||
"label": "Rechtsklick-Befehl",
|
||||
"description": "Befehl, der ausgeführt wird, wenn die Schaltfläche rechts geklickt wird."
|
||||
},
|
||||
"on-middle-clicked": {
|
||||
"label": "Mittelklick-Befehl",
|
||||
"description": "Befehl, der ausgeführt wird, wenn die Schaltfläche mit der mittleren Maustaste geklickt wird."
|
||||
},
|
||||
"on-state-command": {
|
||||
"label": "Befehl für Aktiv-Zustand-Prüfung",
|
||||
"description": "Befehl, der ausgeführt wird, um zu prüfen, ob sich die Schaltfläche im 'aktiv' Zustand befinden soll. Gibt 0 für aktiv, ungleich 0 für inaktiv zurück."
|
||||
},
|
||||
"general-tooltip-text": {
|
||||
"label": "Allgemeiner Tooltip-Text",
|
||||
"description": "Allgemeine Beschreibung für den Tooltip der Schaltfläche."
|
||||
},
|
||||
"enable-on-state-logic": {
|
||||
"label": "Aktiv-Zustand-Logik aktivieren",
|
||||
"description": "Aktiviert ein zweites Symbol und 'aktiv' Zustand basierend auf einem Prüfbefehl."
|
||||
},
|
||||
"state-checks": {
|
||||
"label": "Zustandsprüfungen",
|
||||
"command": "Befehl, der für diese Zustandsprüfung ausgeführt werden soll",
|
||||
"browse-icon": "Durchsuchen",
|
||||
"remove": "Entfernen",
|
||||
"add": "Zustandsprüfung hinzufügen"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"cancel": "Abbrechen",
|
||||
"apply": "Anwenden"
|
||||
}
|
||||
}
|
||||
},
|
||||
"user-interface": {
|
||||
|
||||
@@ -857,7 +857,61 @@
|
||||
"description": "Configure and manage the shortcuts widgets."
|
||||
},
|
||||
"sectionLeft": "Left",
|
||||
"sectionRight": "Right"
|
||||
"sectionRight": "Right",
|
||||
"custom-button": {
|
||||
"icon": {
|
||||
"label": "Icon",
|
||||
"description": "Select an icon from the library."
|
||||
},
|
||||
"browse": "Browse",
|
||||
"command": {
|
||||
"label": "Command",
|
||||
"description": "The command to execute when the button is clicked."
|
||||
},
|
||||
"tooltip": {
|
||||
"label": "Tooltip",
|
||||
"description": "The tooltip to show when hovering over the button."
|
||||
},
|
||||
"on-state-icon": {
|
||||
"label": "On State Icon",
|
||||
"description": "The icon for the button when it's in the 'on' state."
|
||||
},
|
||||
"on-clicked": {
|
||||
"label": "Left Click Command",
|
||||
"description": "Command to execute when the button is left-clicked."
|
||||
},
|
||||
"on-right-clicked": {
|
||||
"label": "Right Click Command",
|
||||
"description": "Command to execute when the button is right-clicked."
|
||||
},
|
||||
"on-middle-clicked": {
|
||||
"label": "Middle Click Command",
|
||||
"description": "Command to execute when the button is middle-clicked."
|
||||
},
|
||||
"on-state-command": {
|
||||
"label": "On State Check Command",
|
||||
"description": "Command to execute to check if the button should be in the 'on' state. Returns 0 for on, non-zero for off."
|
||||
},
|
||||
"general-tooltip-text": {
|
||||
"label": "General Tooltip Text",
|
||||
"description": "General description for the button's tooltip."
|
||||
},
|
||||
"enable-on-state-logic": {
|
||||
"label": "Enable On-State Logic",
|
||||
"description": "Enable a second icon and 'hot' state based on a check command."
|
||||
},
|
||||
"state-checks": {
|
||||
"label": "State Checks",
|
||||
"command": "Command to execute for this state check",
|
||||
"browse-icon": "Browse",
|
||||
"remove": "Remove",
|
||||
"add": "Add State Check"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"cancel": "Cancel",
|
||||
"apply": "Apply"
|
||||
}
|
||||
}
|
||||
},
|
||||
"user-interface": {
|
||||
@@ -1471,7 +1525,8 @@
|
||||
"search": "Search...",
|
||||
"select": "Select",
|
||||
"cancel": "Cancel",
|
||||
"test": "Test"
|
||||
"test": "Test",
|
||||
"enter-tooltip": "Enter tooltip"
|
||||
},
|
||||
"options": {
|
||||
"colors": {
|
||||
|
||||
@@ -857,7 +857,61 @@
|
||||
"label": "Widgets de accesos directos"
|
||||
},
|
||||
"sectionLeft": "Izquierda",
|
||||
"sectionRight": "Derecha"
|
||||
"sectionRight": "Derecha",
|
||||
"custom-button": {
|
||||
"icon": {
|
||||
"label": "Icono",
|
||||
"description": "Selecciona un icono de la biblioteca."
|
||||
},
|
||||
"browse": "Explorar",
|
||||
"command": {
|
||||
"label": "Comando",
|
||||
"description": "El comando a ejecutar cuando se hace clic en el botón."
|
||||
},
|
||||
"tooltip": {
|
||||
"label": "Tooltip",
|
||||
"description": "El tooltip a mostrar al pasar el cursor sobre el botón."
|
||||
},
|
||||
"on-state-icon": {
|
||||
"label": "Icono de estado activado",
|
||||
"description": "El icono para el botón cuando está en el estado 'encendido'."
|
||||
},
|
||||
"on-clicked": {
|
||||
"label": "Comando de clic izquierdo",
|
||||
"description": "Comando a ejecutar cuando se hace clic izquierdo en el botón."
|
||||
},
|
||||
"on-right-clicked": {
|
||||
"label": "Comando de clic derecho",
|
||||
"description": "Comando a ejecutar cuando se hace clic derecho en el botón."
|
||||
},
|
||||
"on-middle-clicked": {
|
||||
"label": "Comando de clic central",
|
||||
"description": "Comando a ejecutar cuando se hace clic central en el botón."
|
||||
},
|
||||
"on-state-command": {
|
||||
"label": "Comando de comprobación de estado activado",
|
||||
"description": "Comando a ejecutar para comprobar si el botón debe estar en el estado 'encendido'. Devuelve 0 para encendido, no cero para apagado."
|
||||
},
|
||||
"general-tooltip-text": {
|
||||
"label": "Texto de tooltip general",
|
||||
"description": "Descripción general para el tooltip del botón."
|
||||
},
|
||||
"enable-on-state-logic": {
|
||||
"label": "Habilitar lógica de estado activado",
|
||||
"description": "Habilita un segundo icono y un estado 'caliente' basado en un comando de comprobación."
|
||||
},
|
||||
"state-checks": {
|
||||
"label": "Comprobaciones de estado",
|
||||
"command": "Comando a ejecutar para esta comprobación de estado",
|
||||
"browse-icon": "Explorar",
|
||||
"remove": "Eliminar",
|
||||
"add": "Añadir comprobación de estado"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"cancel": "Cancelar",
|
||||
"apply": "Aplicar"
|
||||
}
|
||||
}
|
||||
},
|
||||
"user-interface": {
|
||||
|
||||
@@ -857,7 +857,61 @@
|
||||
"label": "Widgets de raccourcis"
|
||||
},
|
||||
"sectionLeft": "Gauche",
|
||||
"sectionRight": "Droite"
|
||||
"sectionRight": "Droite",
|
||||
"custom-button": {
|
||||
"icon": {
|
||||
"label": "Icône",
|
||||
"description": "Sélectionnez une icône dans la bibliothèque."
|
||||
},
|
||||
"browse": "Parcourir",
|
||||
"command": {
|
||||
"label": "Commande",
|
||||
"description": "La commande à exécuter lorsque le bouton est cliqué."
|
||||
},
|
||||
"tooltip": {
|
||||
"label": "Info-bulle",
|
||||
"description": "L'info-bulle à afficher lors du survol du bouton."
|
||||
},
|
||||
"on-state-icon": {
|
||||
"label": "Icône d'état activé",
|
||||
"description": "L'icône du bouton lorsqu'il est dans l'état 'activé'."
|
||||
},
|
||||
"on-clicked": {
|
||||
"label": "Commande de clic gauche",
|
||||
"description": "Commande à exécuter lorsque le bouton est cliqué avec le bouton gauche."
|
||||
},
|
||||
"on-right-clicked": {
|
||||
"label": "Commande de clic droit",
|
||||
"description": "Commande à exécuter lorsque le bouton est cliqué avec le bouton droit."
|
||||
},
|
||||
"on-middle-clicked": {
|
||||
"label": "Commande de clic central",
|
||||
"description": "Commande à exécuter lorsque le bouton est cliqué avec le bouton du milieu."
|
||||
},
|
||||
"on-state-command": {
|
||||
"label": "Commande de vérification de l'état activé",
|
||||
"description": "Commande à exécuter pour vérifier si le bouton doit être dans l'état 'activé'. Retourne 0 pour activé, non-zéro pour désactivé."
|
||||
},
|
||||
"general-tooltip-text": {
|
||||
"label": "Texte de l'info-bulle générale",
|
||||
"description": "Description générale de l'info-bulle du bouton."
|
||||
},
|
||||
"enable-on-state-logic": {
|
||||
"label": "Activer la logique d'état activé",
|
||||
"description": "Active une deuxième icône et un état 'chaud' basés sur une commande de vérification."
|
||||
},
|
||||
"state-checks": {
|
||||
"label": "Vérifications d'état",
|
||||
"command": "Commande à exécuter pour cette vérification d'état",
|
||||
"browse-icon": "Parcourir",
|
||||
"remove": "Supprimer",
|
||||
"add": "Ajouter une vérification d'état"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"cancel": "Annuler",
|
||||
"apply": "Appliquer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"user-interface": {
|
||||
|
||||
@@ -857,7 +857,61 @@
|
||||
"label": "Widgets de atalhos"
|
||||
},
|
||||
"sectionLeft": "Esquerda",
|
||||
"sectionRight": "Direito/Certo/À direita"
|
||||
"sectionRight": "Direito/Certo/À direita",
|
||||
"custom-button": {
|
||||
"icon": {
|
||||
"label": "Ícone",
|
||||
"description": "Selecione um ícone da biblioteca."
|
||||
},
|
||||
"browse": "Procurar",
|
||||
"command": {
|
||||
"label": "Comando",
|
||||
"description": "O comando a ser executado quando o botão é clicado."
|
||||
},
|
||||
"tooltip": {
|
||||
"label": "Dica de ferramenta",
|
||||
"description": "A dica de ferramenta a ser exibida ao passar o mouse sobre o botão."
|
||||
},
|
||||
"on-state-icon": {
|
||||
"label": "Ícone de estado ativado",
|
||||
"description": "O ícone para o botão quando ele está no estado 'ligado'."
|
||||
},
|
||||
"on-clicked": {
|
||||
"label": "Comando de clique esquerdo",
|
||||
"description": "Comando a ser executado quando o botão é clicado com o botão esquerdo."
|
||||
},
|
||||
"on-right-clicked": {
|
||||
"label": "Comando de clique direito",
|
||||
"description": "Comando a ser executado quando o botão é clicado com o botão direito."
|
||||
},
|
||||
"on-middle-clicked": {
|
||||
"label": "Comando de clique do meio",
|
||||
"description": "Comando a ser executado quando o botão é clicado com o botão do meio."
|
||||
},
|
||||
"on-state-command": {
|
||||
"label": "Comando de verificação de estado ativado",
|
||||
"description": "Comando a ser executado para verificar se o botão deve estar no estado 'ligado'. Retorna 0 para ligado, diferente de zero para desligado."
|
||||
},
|
||||
"general-tooltip-text": {
|
||||
"label": "Texto geral da dica de ferramenta",
|
||||
"description": "Descrição geral para a dica de ferramenta do botão."
|
||||
},
|
||||
"enable-on-state-logic": {
|
||||
"label": "Ativar lógica de estado ativado",
|
||||
"description": "Ativa um segundo ícone e um estado 'quente' com base em um comando de verificação."
|
||||
},
|
||||
"state-checks": {
|
||||
"label": "Verificações de estado",
|
||||
"command": "Comando a ser executado para esta verificação de estado",
|
||||
"browse-icon": "Procurar",
|
||||
"remove": "Remover",
|
||||
"add": "Adicionar verificação de estado"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"cancel": "Cancelar",
|
||||
"apply": "Aplicar"
|
||||
}
|
||||
}
|
||||
},
|
||||
"user-interface": {
|
||||
|
||||
@@ -857,7 +857,61 @@
|
||||
"description": "Налаштуйте та керуйте віджетами швидкого доступу."
|
||||
},
|
||||
"sectionLeft": "Лівий",
|
||||
"sectionRight": "Правий"
|
||||
"sectionRight": "Правий",
|
||||
"custom-button": {
|
||||
"icon": {
|
||||
"label": "Іконка",
|
||||
"description": "Виберіть іконку з бібліотеки."
|
||||
},
|
||||
"browse": "Огляд",
|
||||
"command": {
|
||||
"label": "Команда",
|
||||
"description": "Команда, яка буде виконана при натисканні кнопки."
|
||||
},
|
||||
"tooltip": {
|
||||
"label": "Підказка",
|
||||
"description": "Підказка, яка буде відображатися при наведенні на кнопку."
|
||||
},
|
||||
"on-state-icon": {
|
||||
"label": "Іконка увімкненого стану",
|
||||
"description": "Іконка для кнопки, коли вона у стані 'увімкнено'."
|
||||
},
|
||||
"on-clicked": {
|
||||
"label": "Команда лівого кліку",
|
||||
"description": "Команда, яка буде виконана при натисканні лівої кнопки миші."
|
||||
},
|
||||
"on-right-clicked": {
|
||||
"label": "Команда правого кліку",
|
||||
"description": "Команда, яка буде виконана при натисканні правої кнопки миші."
|
||||
},
|
||||
"on-middle-clicked": {
|
||||
"label": "Команда середнього кліку",
|
||||
"description": "Команда, яка буде виконана при натисканні середньої кнопки миші."
|
||||
},
|
||||
"on-state-command": {
|
||||
"label": "Команда перевірки увімкненого стану",
|
||||
"description": "Команда для перевірки, чи повинна кнопка бути у стані 'увімкнено'. Повертає 0 для увімкненого стану, ненульове значення для вимкненого."
|
||||
},
|
||||
"general-tooltip-text": {
|
||||
"label": "Загальний текст підказки",
|
||||
"description": "Загальний опис для підказки кнопки."
|
||||
},
|
||||
"enable-on-state-logic": {
|
||||
"label": "Увімкнути логіку увімкненого стану",
|
||||
"description": "Увімкнути другу іконку та 'гарячий' стан на основі команди перевірки."
|
||||
},
|
||||
"state-checks": {
|
||||
"label": "Перевірки стану",
|
||||
"command": "Команда для виконання для цієї перевірки стану",
|
||||
"browse-icon": "Огляд",
|
||||
"remove": "Видалити",
|
||||
"add": "Додати перевірку стану"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"cancel": "Скасувати",
|
||||
"apply": "Застосувати"
|
||||
}
|
||||
}
|
||||
},
|
||||
"user-interface": {
|
||||
|
||||
@@ -857,7 +857,61 @@
|
||||
"label": "快捷方式小部件"
|
||||
},
|
||||
"sectionLeft": "左",
|
||||
"sectionRight": "右"
|
||||
"sectionRight": "右",
|
||||
"custom-button": {
|
||||
"icon": {
|
||||
"label": "图标",
|
||||
"description": "从库中选择一个图标。"
|
||||
},
|
||||
"browse": "浏览",
|
||||
"command": {
|
||||
"label": "命令",
|
||||
"description": "单击按钮时执行的命令。"
|
||||
},
|
||||
"tooltip": {
|
||||
"label": "工具提示",
|
||||
"description": "悬停在按钮上时显示的工具提示。"
|
||||
},
|
||||
"on-state-icon": {
|
||||
"label": "开启状态图标",
|
||||
"description": "按钮处于“开启”状态时的图标。"
|
||||
},
|
||||
"on-clicked": {
|
||||
"label": "左键单击命令",
|
||||
"description": "左键单击按钮时执行的命令。"
|
||||
},
|
||||
"on-right-clicked": {
|
||||
"label": "右键单击命令",
|
||||
"description": "右键单击按钮时执行的命令。"
|
||||
},
|
||||
"on-middle-clicked": {
|
||||
"label": "中键单击命令",
|
||||
"description": "中键单击按钮时执行的命令。"
|
||||
},
|
||||
"on-state-command": {
|
||||
"label": "开启状态检查命令",
|
||||
"description": "执行以检查按钮是否应处于“开启”状态的命令。返回 0 表示开启,非零表示关闭。"
|
||||
},
|
||||
"general-tooltip-text": {
|
||||
"label": "常规工具提示文本",
|
||||
"description": "按钮工具提示的常规说明。"
|
||||
},
|
||||
"enable-on-state-logic": {
|
||||
"label": "启用开启状态逻辑",
|
||||
"description": "根据检查命令启用第二个图标和“高亮”状态。"
|
||||
},
|
||||
"state-checks": {
|
||||
"label": "状态检查",
|
||||
"command": "要为此状态检查执行的命令",
|
||||
"browse-icon": "浏览",
|
||||
"remove": "移除",
|
||||
"add": "添加状态检查"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
"cancel": "取消",
|
||||
"apply": "应用"
|
||||
}
|
||||
}
|
||||
},
|
||||
"user-interface": {
|
||||
|
||||
@@ -38,7 +38,8 @@ RowLayout {
|
||||
"widgetId": modelData.id,
|
||||
"section": "quickSettings",
|
||||
"sectionWidgetIndex": index,
|
||||
"sectionWidgetsCount": Settings.data.controlCenter.shortcuts.left.length
|
||||
"sectionWidgetsCount": Settings.data.controlCenter.shortcuts.left.length,
|
||||
"widgetSettings": modelData
|
||||
}
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
@@ -76,7 +77,8 @@ RowLayout {
|
||||
"widgetId": modelData.id,
|
||||
"section": "quickSettings",
|
||||
"sectionWidgetIndex": index,
|
||||
"sectionWidgetsCount": Settings.data.controlCenter.shortcuts.right.length
|
||||
"sectionWidgetsCount": Settings.data.controlCenter.shortcuts.right.length,
|
||||
"widgetSettings": modelData
|
||||
}
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
|
||||
property string widgetId: "CustomButton"
|
||||
property var widgetSettings: null
|
||||
|
||||
property string onClickedCommand: ""
|
||||
property string onRightClickedCommand: ""
|
||||
property string onMiddleClickedCommand: ""
|
||||
property string stateChecksJson: "[]" // Store state checks as JSON string
|
||||
|
||||
property string generalTooltipText: "Custom Button"
|
||||
property bool enableOnStateLogic: false
|
||||
|
||||
property string _currentIcon: "heart" // Default icon
|
||||
property bool _isHot: false
|
||||
property var _parsedStateChecks: [] // Local array for parsed state checks
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
|
||||
function _updatePropertiesFromSettings() {
|
||||
if (!widgetSettings) {
|
||||
return
|
||||
}
|
||||
|
||||
onClickedCommand = widgetSettings.onClicked || ""
|
||||
onRightClickedCommand = widgetSettings.onRightClicked || ""
|
||||
onMiddleClickedCommand = widgetSettings.onMiddleClicked || ""
|
||||
stateChecksJson = widgetSettings.stateChecksJson || "[]" // Populate from widgetSettings
|
||||
try {
|
||||
_parsedStateChecks = JSON.parse(stateChecksJson)
|
||||
} catch (e) {
|
||||
console.error("CustomButton: Failed to parse stateChecksJson:", e.message)
|
||||
_parsedStateChecks = []
|
||||
}
|
||||
generalTooltipText = widgetSettings.generalTooltipText || "Custom Button"
|
||||
enableOnStateLogic = widgetSettings.enableOnStateLogic || false
|
||||
|
||||
updateState()
|
||||
}
|
||||
|
||||
function onWidgetSettingsChanged() {
|
||||
if (widgetSettings) {
|
||||
_updatePropertiesFromSettings()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property int _currentStateCheckIndex: -1
|
||||
property string _activeStateIcon: ""
|
||||
|
||||
Process {
|
||||
id: stateCheckProcessExecutor
|
||||
running: false
|
||||
command: _currentStateCheckIndex !== -1 && _parsedStateChecks.length > _currentStateCheckIndex ? ["sh", "-c", _parsedStateChecks[_currentStateCheckIndex].command] : []
|
||||
onExited: function(exitCode, stdout, stderr) {
|
||||
var currentCheckItem = _parsedStateChecks[_currentStateCheckIndex]
|
||||
var currentCommand = currentCheckItem.command
|
||||
if (exitCode === 0) {
|
||||
// Command succeeded, this is the active state
|
||||
_isHot = true
|
||||
_activeStateIcon = currentCheckItem.icon || widgetSettings.icon || "heart"
|
||||
} else {
|
||||
// Command failed, try next one
|
||||
_currentStateCheckIndex++
|
||||
_checkNextState()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: stateUpdateTimer
|
||||
interval: 200
|
||||
running: false
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (enableOnStateLogic && _parsedStateChecks.length > 0) {
|
||||
_currentStateCheckIndex = 0
|
||||
_checkNextState()
|
||||
} else {
|
||||
_isHot = false
|
||||
_activeStateIcon = widgetSettings.icon || "heart"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _checkNextState() {
|
||||
if (_currentStateCheckIndex < _parsedStateChecks.length) {
|
||||
var currentCheckItem = _parsedStateChecks[_currentStateCheckIndex]
|
||||
if (currentCheckItem && currentCheckItem.command) {
|
||||
stateCheckProcessExecutor.running = true
|
||||
} else {
|
||||
_currentStateCheckIndex++
|
||||
_checkNextState()
|
||||
}
|
||||
} else {
|
||||
// All checks failed
|
||||
_isHot = false
|
||||
_activeStateIcon = widgetSettings.icon || "heart"
|
||||
}
|
||||
}
|
||||
|
||||
function updateState() {
|
||||
if (!enableOnStateLogic || _parsedStateChecks.length === 0) {
|
||||
_isHot = false;
|
||||
_activeStateIcon = widgetSettings.icon || "heart";
|
||||
return;
|
||||
}
|
||||
stateUpdateTimer.restart();
|
||||
}
|
||||
function _buildTooltipText() {
|
||||
let tooltip = generalTooltipText
|
||||
if (onClickedCommand) {
|
||||
tooltip += `\nLeft click: ${onClickedCommand}`
|
||||
}
|
||||
if (onRightClickedCommand) {
|
||||
tooltip += `\nRight click: ${onRightClickedCommand}`
|
||||
}
|
||||
if (onMiddleClickedCommand) {
|
||||
tooltip += `\nMiddle click: ${onMiddleClickedCommand}`
|
||||
}
|
||||
|
||||
return tooltip
|
||||
}
|
||||
|
||||
implicitWidth: button.implicitWidth
|
||||
implicitHeight: button.implicitHeight
|
||||
|
||||
NIconButtonHot {
|
||||
id: button
|
||||
icon: _activeStateIcon
|
||||
hot: _isHot
|
||||
tooltipText: _buildTooltipText()
|
||||
onClicked: {
|
||||
if (onClickedCommand) {
|
||||
Quickshell.execDetached(["sh", "-c", onClickedCommand])
|
||||
updateState()
|
||||
}
|
||||
}
|
||||
onRightClicked: {
|
||||
if (onRightClickedCommand) {
|
||||
Quickshell.execDetached(["sh", "-c", onRightClickedCommand])
|
||||
updateState()
|
||||
}
|
||||
}
|
||||
onMiddleClicked: {
|
||||
if (onMiddleClickedCommand) {
|
||||
Quickshell.execDetached(["sh", "-c", onMiddleClickedCommand])
|
||||
updateState()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Services
|
||||
|
||||
// Widget Settings Dialog Component
|
||||
Popup {
|
||||
id: root
|
||||
|
||||
property int widgetIndex: -1
|
||||
property var widgetData: null
|
||||
property string widgetId: ""
|
||||
property string sectionId: ""
|
||||
|
||||
signal updateWidgetSettings(string section, int index, var settings)
|
||||
|
||||
width: Math.max(content.implicitWidth + padding * 2, 500)
|
||||
height: content.implicitHeight + padding * 2
|
||||
padding: Style.marginXL
|
||||
modal: true
|
||||
anchors.centerIn: parent
|
||||
|
||||
onOpened: {
|
||||
PanelService.willOpenPopup(root)
|
||||
if (widgetData && widgetId) {
|
||||
loadWidgetSettings()
|
||||
}
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
PanelService.willClosePopup(root)
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Color.mSurface
|
||||
radius: Style.radiusL
|
||||
border.color: Color.mPrimary
|
||||
border.width: Style.borderM
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
id: content
|
||||
width: parent.width
|
||||
spacing: Style.marginM
|
||||
|
||||
// Title
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: I18n.tr("system.widget-settings-title", { "widget": root.widgetId })
|
||||
pointSize: Style.fontSizeL
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mPrimary
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
tooltipText: I18n.tr("tooltips.close")
|
||||
onClicked: root.close()
|
||||
}
|
||||
}
|
||||
|
||||
// Separator
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
color: Color.mOutline
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: settingsLoader
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Action buttons
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginM
|
||||
spacing: Style.marginM
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("settings.control-center.shortcuts.dialog.cancel", "Cancel")
|
||||
outlined: true
|
||||
onClicked: root.close()
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("settings.control-center.shortcuts.dialog.apply", "Apply")
|
||||
icon: "check"
|
||||
onClicked: {
|
||||
if (settingsLoader.item && settingsLoader.item.saveSettings) {
|
||||
var newSettings = settingsLoader.item.saveSettings()
|
||||
root.updateWidgetSettings(root.sectionId, root.widgetIndex, newSettings)
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function loadWidgetSettings() {
|
||||
const widgetSettingsMap = {
|
||||
"CustomButton": "WidgetSettings/CustomButtonSettings.qml"
|
||||
}
|
||||
|
||||
const source = widgetSettingsMap[widgetId]
|
||||
if (source) {
|
||||
settingsLoader.setSource(source, {
|
||||
"widgetData": widgetData,
|
||||
"widgetMetadata": ControlCenterWidgetRegistry.widgetMetadata[widgetId]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQml.Models // Import ListModel
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
QtObject {
|
||||
id: _settings
|
||||
|
||||
property string icon: (widgetData && widgetData.icon !== undefined) ? widgetData.icon : widgetMetadata.icon
|
||||
property string onClicked: (widgetData && widgetData.onClicked !== undefined) ? widgetData.onClicked : widgetMetadata.onClicked
|
||||
property string onRightClicked: (widgetData && widgetData.onRightClicked !== undefined) ? widgetData.onRightClicked : widgetMetadata.onRightClicked
|
||||
property string onMiddleClicked: (widgetData && widgetData.onMiddleClicked !== undefined) ? widgetData.onMiddleClicked : widgetMetadata.onMiddleClicked
|
||||
property ListModel _stateChecksListModel: ListModel {}
|
||||
property string stateChecksJson: "[]"
|
||||
property string generalTooltipText: (widgetData && widgetData.generalTooltipText !== undefined) ? widgetData.generalTooltipText : widgetMetadata.generalTooltipText
|
||||
property bool enableOnStateLogic: (widgetData && widgetData.enableOnStateLogic !== undefined) ? widgetData.enableOnStateLogic : widgetMetadata.enableOnStateLogic
|
||||
|
||||
Component.onCompleted: {
|
||||
stateChecksJson = (widgetData && widgetData.stateChecksJson !== undefined) ? widgetData.stateChecksJson : widgetMetadata.stateChecksJson || "[]"
|
||||
try {
|
||||
var initialChecks = JSON.parse(stateChecksJson)
|
||||
if (initialChecks && Array.isArray(initialChecks)) {
|
||||
for (var i = 0; i < initialChecks.length; i++) {
|
||||
var item = initialChecks[i]
|
||||
if (item && typeof item === "object") {
|
||||
_settings._stateChecksListModel.append({
|
||||
command: item.command || "",
|
||||
icon: item.icon || ""
|
||||
})
|
||||
} else {
|
||||
console.warn("⚠️ Invalid stateChecks entry at index " + i + ":", item)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("CustomButtonSettings: Failed to parse stateChecksJson:", e.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function saveSettings() {
|
||||
var savedStateChecksArray = []
|
||||
for (var i = 0; i < _settings._stateChecksListModel.count; i++) {
|
||||
savedStateChecksArray.push(_settings._stateChecksListModel.get(i))
|
||||
}
|
||||
_settings.stateChecksJson = JSON.stringify(savedStateChecksArray)
|
||||
|
||||
return {
|
||||
id: widgetData.id,
|
||||
icon: _settings.icon,
|
||||
onClicked: _settings.onClicked,
|
||||
onRightClicked: _settings.onRightClicked,
|
||||
onMiddleClicked: _settings.onMiddleClicked,
|
||||
stateChecksJson: _settings.stateChecksJson,
|
||||
generalTooltipText: _settings.generalTooltipText,
|
||||
enableOnStateLogic: _settings.enableOnStateLogic
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style?.marginM ?? 8
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("settings.control-center.shortcuts.custom-button.icon.label")
|
||||
description: I18n.tr("settings.control-center.shortcuts.custom-button.icon.description")
|
||||
}
|
||||
|
||||
NIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
icon: _settings.icon || widgetMetadata.icon
|
||||
pointSize: Style?.fontSizeXL ?? 24
|
||||
visible: (_settings.icon || widgetMetadata.icon) !== ""
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("settings.control-center.shortcuts.custom-button.browse")
|
||||
onClicked: iconPicker.open()
|
||||
}
|
||||
}
|
||||
|
||||
NIconPicker {
|
||||
id: iconPicker
|
||||
initialIcon: _settings.icon
|
||||
onIconSelected: function (iconName) {
|
||||
_settings.icon = iconName
|
||||
}
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.control-center.shortcuts.custom-button.general-tooltip-text.label")
|
||||
description: I18n.tr("settings.control-center.shortcuts.custom-button.general-tooltip-text.description")
|
||||
placeholderText: I18n.tr("placeholders.enter-tooltip")
|
||||
text: _settings.generalTooltipText
|
||||
onTextChanged: _settings.generalTooltipText = text
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.control-center.shortcuts.custom-button.on-clicked.label")
|
||||
description: I18n.tr("settings.control-center.shortcuts.custom-button.on-clicked.description")
|
||||
placeholderText: I18n.tr("placeholders.enter-command")
|
||||
text: _settings.onClicked
|
||||
onTextChanged: _settings.onClicked = text
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.control-center.shortcuts.custom-button.on-right-clicked.label")
|
||||
description: I18n.tr("settings.control-center.shortcuts.custom-button.on-right-clicked.description")
|
||||
placeholderText: I18n.tr("placeholders.enter-command")
|
||||
text: _settings.onRightClicked
|
||||
onTextChanged: _settings.onRightClicked = text
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.control-center.shortcuts.custom-button.on-middle-clicked.label")
|
||||
description: I18n.tr("settings.control-center.shortcuts.custom-button.on-middle-clicked.description")
|
||||
placeholderText: I18n.tr("placeholders.enter-command")
|
||||
text: _settings.onMiddleClicked
|
||||
onTextChanged: _settings.onMiddleClicked = text
|
||||
}
|
||||
|
||||
NDivider {}
|
||||
|
||||
NToggle {
|
||||
id: enableOnStateLogicToggle
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("settings.control-center.shortcuts.custom-button.enable-on-state-logic.label")
|
||||
description: I18n.tr("settings.control-center.shortcuts.custom-button.enable-on-state-logic.description")
|
||||
checked: _settings.enableOnStateLogic
|
||||
onToggled: checked => _settings.enableOnStateLogic = checked
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
visible: _settings.enableOnStateLogic
|
||||
spacing: (Style?.marginM ?? 8) * 2
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("settings.control-center.shortcuts.custom-button.state-checks.label")
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: _settings._stateChecksListModel
|
||||
delegate: Item {
|
||||
property int currentIndex: index
|
||||
|
||||
implicitHeight: contentRow.implicitHeight + ((divider.visible) ? divider.height : 0)
|
||||
Layout.fillWidth: true
|
||||
|
||||
RowLayout {
|
||||
id: contentRow
|
||||
anchors.fill: parent
|
||||
spacing: Style?.marginM ?? 8
|
||||
|
||||
NTextInput {
|
||||
Layout.fillWidth: true
|
||||
placeholderText: I18n.tr("settings.control-center.shortcuts.custom-button.state-checks.command")
|
||||
text: model.command
|
||||
onEditingFinished: _settings._stateChecksListModel.set(currentIndex, { "command": text, "icon": model.icon })
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
spacing: Style?.marginS ?? 4
|
||||
|
||||
NIcon {
|
||||
icon: model.icon
|
||||
pointSize: Style?.fontSizeL ?? 20
|
||||
visible: model.icon !== undefined && model.icon !== ""
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "folder"
|
||||
tooltipText: I18n.tr("settings.control-center.shortcuts.custom-button.state-checks.browse-icon")
|
||||
baseSize: Style?.buttonSizeS ?? 24
|
||||
onClicked: iconPickerDelegate.open()
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
tooltipText: I18n.tr("settings.control-center.shortcuts.custom-button.state-checks.remove")
|
||||
baseSize: Style?.buttonSizeS ?? 24
|
||||
colorBorder: Qt.alpha(Color.mOutline, Style.opacityLight)
|
||||
colorBg: Color.mError
|
||||
colorFg: Color.mOnError
|
||||
colorBgHover: Qt.alpha(Color.mError, Style.opacityMedium)
|
||||
colorFgHover: Color.mOnError
|
||||
onClicked: _settings._stateChecksListModel.remove(currentIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NIconPicker {
|
||||
id: iconPickerDelegate
|
||||
initialIcon: model.icon
|
||||
onIconSelected: function (iconName) {
|
||||
_settings._stateChecksListModel.set(currentIndex, { "command": model.command, "icon": iconName })
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
id: divider
|
||||
anchors.bottom: parent.bottom
|
||||
visible: index < _settings._stateChecksListModel.count - 1 // Only show divider if not the last item
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style?.marginM ?? 8
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("settings.control-center.shortcuts.custom-button.state-checks.add")
|
||||
onClicked: _settings._stateChecksListModel.append({ command: "", icon: "" })
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {}
|
||||
}
|
||||
@@ -68,12 +68,13 @@ ColumnLayout {
|
||||
Component.onCompleted: {
|
||||
// Fill out availableWidgets ListModel
|
||||
availableWidgets.clear()
|
||||
ControlCenterWidgetRegistry.getAvailableWidgets().forEach(entry => {
|
||||
availableWidgets.append({
|
||||
"key": entry,
|
||||
"name": entry
|
||||
})
|
||||
})
|
||||
var sortedEntries = ControlCenterWidgetRegistry.getAvailableWidgets().slice().sort()
|
||||
sortedEntries.forEach(entry => {
|
||||
availableWidgets.append({
|
||||
"key": entry,
|
||||
"name": entry
|
||||
})
|
||||
})
|
||||
// Starts empty
|
||||
cardsModel = []
|
||||
|
||||
@@ -226,7 +227,7 @@ ColumnLayout {
|
||||
NSectionEditor {
|
||||
sectionName: I18n.tr("settings.control-center.shortcuts.sectionLeft")
|
||||
sectionId: "left"
|
||||
settingsDialogComponent: ""
|
||||
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Settings/ControlCenter/ControlCenterWidgetSettingsDialog.qml")
|
||||
maxWidgets: 5
|
||||
widgetRegistry: ControlCenterWidgetRegistry
|
||||
widgetModel: Settings.data.controlCenter.shortcuts["left"]
|
||||
@@ -245,7 +246,7 @@ ColumnLayout {
|
||||
NSectionEditor {
|
||||
sectionName: I18n.tr("settings.control-center.shortcuts.sectionRight")
|
||||
sectionId: "right"
|
||||
settingsDialogComponent: ""
|
||||
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Settings/ControlCenter/ControlCenterWidgetSettingsDialog.qml")
|
||||
maxWidgets: 5
|
||||
widgetRegistry: ControlCenterWidgetRegistry
|
||||
widgetModel: Settings.data.controlCenter.shortcuts["right"]
|
||||
@@ -329,8 +330,12 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
function _updateWidgetSettingsInSection(section, index, settings) {
|
||||
// Update the widget settings in the Settings data
|
||||
Settings.data.controlCenter.shortcuts[section][index] = settings
|
||||
// Create a new array to trigger QML's change detection for persistence.
|
||||
// This is crucial for Settings.data to detect the change and persist it.
|
||||
var newSectionArray = Settings.data.controlCenter.shortcuts[section].slice()
|
||||
newSectionArray[index] = settings
|
||||
Settings.data.controlCenter.shortcuts[section] = newSectionArray
|
||||
Settings.saveImmediate()
|
||||
}
|
||||
|
||||
// Base list model for all combo boxes
|
||||
|
||||
@@ -17,10 +17,22 @@ Singleton {
|
||||
"PowerProfile": powerProfileComponent,
|
||||
"ScreenRecorder": screenRecorderComponent,
|
||||
"WiFi": wiFiComponent,
|
||||
"WallpaperSelector": wallpaperSelectorComponent
|
||||
"WallpaperSelector": wallpaperSelectorComponent,
|
||||
"CustomButton": customButtonComponent
|
||||
})
|
||||
|
||||
property var widgetMetadata: ({})
|
||||
property var widgetMetadata: ({
|
||||
"CustomButton": {
|
||||
"allowUserSettings": true,
|
||||
"icon": "heart",
|
||||
"onClicked": "",
|
||||
"onRightClicked": "",
|
||||
"onMiddleClicked": "",
|
||||
"stateChecks": [],
|
||||
"generalTooltipText": "Custom Button",
|
||||
"enableOnStateLogic": false
|
||||
}
|
||||
})
|
||||
|
||||
// Component definitions - these are loaded once at startup
|
||||
property Component bluetoothComponent: Component {
|
||||
@@ -47,6 +59,9 @@ Singleton {
|
||||
property Component wallpaperSelectorComponent: Component {
|
||||
WallpaperSelector {}
|
||||
}
|
||||
property Component customButtonComponent: Component {
|
||||
CustomButton {}
|
||||
}
|
||||
|
||||
function init() {
|
||||
Logger.i("ControlCenterWidgetRegistry", "Service started")
|
||||
|
||||
Reference in New Issue
Block a user