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,87 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Services.System
import qs.Services.UI
import qs.Widgets
ColumnLayout {
id: root
spacing: Style.marginL
Layout.fillWidth: true
Layout.fillHeight: true
ColumnLayout {
spacing: Style.marginL
Layout.fillWidth: true
NComboBox {
id: controlCenterPosition
label: I18n.tr("common.position")
description: I18n.tr("panels.control-center.position-description")
Layout.fillWidth: true
model: [
{
"key": "close_to_bar_button",
"name": I18n.tr("positions.close-to-bar")
},
{
"key": "center",
"name": I18n.tr("positions.center")
},
{
"key": "top_center",
"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_center",
"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.controlCenter.position
onSelected: function (key) {
Settings.data.controlCenter.position = key;
}
defaultValue: Settings.getDefaultValue("controlCenter.position")
}
NComboBox {
id: diskPathComboBox
Layout.fillWidth: true
label: I18n.tr("panels.control-center.system-monitor-disk-path-label")
description: I18n.tr("panels.control-center.system-monitor-disk-path-description")
model: {
const paths = Object.keys(SystemStatService.diskPercents).sort();
return paths.map(path => ({
key: path,
name: path
}));
}
currentKey: Settings.data.controlCenter.diskPath || "/"
onSelected: key => Settings.data.controlCenter.diskPath = key
defaultValue: Settings.getDefaultValue("controlCenter.diskPath") || "/"
}
}
Rectangle {
Layout.fillHeight: true
}
}
@@ -0,0 +1,79 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Services.System
import qs.Services.UI
import qs.Widgets
ColumnLayout {
id: root
spacing: Style.marginL
Layout.fillWidth: true
Layout.fillHeight: true
required property var cardsModel
required property list<var> cardsDefault
function saveCards() {
var toSave = [];
for (var i = 0; i < cardsModel.length; i++) {
toSave.push({
"id": cardsModel[i].id,
"enabled": cardsModel[i].enabled
});
}
Settings.data.controlCenter.cards = toSave;
}
NText {
text: I18n.tr("panels.control-center.cards-desc")
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
ColumnLayout {
spacing: Style.marginXXS
Layout.fillWidth: true
Connections {
target: Settings.data.location
function onWeatherEnabledChanged() {
// Auto-disable weather card when weather is disabled
var newModel = root.cardsModel.slice();
for (var i = 0; i < newModel.length; i++) {
if (newModel[i].id === "weather-card") {
newModel[i] = Object.assign({}, newModel[i], {
"enabled": Settings.data.location.weatherEnabled
});
root.cardsModel = newModel;
saveCards();
break;
}
}
}
}
NReorderCheckboxes {
Layout.fillWidth: true
model: root.cardsModel
disabledIds: Settings.data.location.weatherEnabled ? [] : ["weather-card"]
onItemToggled: function (index, enabled) {
var newModel = root.cardsModel.slice();
newModel[index] = Object.assign({}, newModel[index], {
"enabled": enabled
});
root.cardsModel = newModel;
saveCards();
}
onItemsReordered: function (fromIndex, toIndex) {
var newModel = root.cardsModel.slice();
var item = newModel.splice(fromIndex, 1)[0];
newModel.splice(toIndex, 0, item);
root.cardsModel = newModel;
saveCards();
}
}
}
}
@@ -0,0 +1,252 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Services.Noctalia
import qs.Services.System
import qs.Services.UI
import qs.Widgets
ColumnLayout {
id: root
spacing: 0
property list<var> cardsModel: []
property list<var> cardsDefault: [
{
"id": "profile-card",
"text": "Profile",
"enabled": true,
"required": true
},
{
"id": "shortcuts-card",
"text": "Shortcuts",
"enabled": true,
"required": false
},
{
"id": "audio-card",
"text": "Audio Sliders",
"enabled": true,
"required": false
},
{
"id": "brightness-card",
"text": "Brightness",
"enabled": false,
"required": false
},
{
"id": "weather-card",
"text": "Weather",
"enabled": true,
"required": false
},
{
"id": "media-sysmon-card",
"text": "Media and System Monitor",
"enabled": true,
"required": false
}
]
Component.onCompleted: {
// Fill out availableWidgets ListModel
availableWidgets.clear();
var sortedEntries = ControlCenterWidgetRegistry.getAvailableWidgets().slice().sort();
sortedEntries.forEach(entry => {
const isPlugin = ControlCenterWidgetRegistry.isPluginWidget(entry);
let displayName = entry;
let badges = [];
if (isPlugin) {
const pluginId = entry.replace("plugin:", "");
const manifest = PluginRegistry.getPluginManifest(pluginId);
if (manifest && manifest.name) {
displayName = manifest.name;
} else {
displayName = pluginId;
}
badges.push({
"icon": "plugin",
"color": Color.mSecondary
});
}
availableWidgets.append({
"key": entry,
"name": displayName,
"badges": badges
});
});
// Starts empty
cardsModel = [];
// Add the cards available in settings
for (var i = 0; i < Settings.data.controlCenter.cards.length; i++) {
const settingCard = Settings.data.controlCenter.cards[i];
for (var j = 0; j < cardsDefault.length; j++) {
if (settingCard.id === cardsDefault[j].id) {
var card = cardsDefault[j];
card.enabled = settingCard.enabled;
// Auto-disable weather card if weather is disabled
if (card.id === "weather-card" && !Settings.data.location.weatherEnabled) {
card.enabled = false;
}
cardsModel.push(card);
}
}
}
// Add any missing cards from default
for (var i = 0; i < cardsDefault.length; i++) {
var found = false;
for (var j = 0; j < cardsModel.length; j++) {
if (cardsModel[j].id === cardsDefault[i].id) {
found = true;
break;
}
}
if (!found) {
var card = cardsDefault[i];
// Auto-disable weather card if weather is disabled
if (card.id === "weather-card" && !Settings.data.location.weatherEnabled) {
card.enabled = false;
}
cardsModel.push(card);
}
}
}
NTabBar {
id: subTabBar
Layout.fillWidth: true
Layout.bottomMargin: Style.marginM
distributeEvenly: true
currentIndex: tabView.currentIndex
NTabButton {
text: I18n.tr("common.appearance")
tabIndex: 0
checked: subTabBar.currentIndex === 0
}
NTabButton {
text: I18n.tr("common.cards")
tabIndex: 1
checked: subTabBar.currentIndex === 1
}
NTabButton {
text: I18n.tr("common.shortcuts")
tabIndex: 2
checked: subTabBar.currentIndex === 2
}
}
Item {
Layout.fillWidth: true
Layout.preferredHeight: Style.marginL
}
NTabView {
id: tabView
currentIndex: subTabBar.currentIndex
Layout.fillWidth: true
Layout.fillHeight: true
AppearanceSubTab {}
CardsSubTab {
cardsModel: root.cardsModel
cardsDefault: root.cardsDefault
}
ShortcutsSubTab {
availableWidgets: availableWidgets
onAddWidgetToSection: (widgetId, section) => _addWidgetToSection(widgetId, section)
onRemoveWidgetFromSection: (section, index) => _removeWidgetFromSection(section, index)
onReorderWidgetInSection: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex)
onUpdateWidgetSettingsInSection: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings)
onMoveWidgetBetweenSections: (fromSection, index, toSection) => _moveWidgetBetweenSections(fromSection, index, toSection)
onOpenPluginSettingsRequested: manifest => pluginSettingsDialog.openPluginSettings(manifest)
}
}
// ---------------------------------
// Signal functions
// ---------------------------------
function _addWidgetToSection(widgetId, section) {
var newWidget = {
"id": widgetId
};
if (ControlCenterWidgetRegistry.widgetHasUserSettings(widgetId)) {
var metadata = ControlCenterWidgetRegistry.widgetMetadata[widgetId];
if (metadata) {
Object.keys(metadata).forEach(function (key) {
newWidget[key] = metadata[key];
});
}
}
Settings.data.controlCenter.shortcuts[section].push(newWidget);
}
function _removeWidgetFromSection(section, index) {
if (index >= 0 && index < Settings.data.controlCenter.shortcuts[section].length) {
var newArray = Settings.data.controlCenter.shortcuts[section].slice();
var removedWidgets = newArray.splice(index, 1);
Settings.data.controlCenter.shortcuts[section] = newArray;
}
}
function _reorderWidgetInSection(section, fromIndex, toIndex) {
if (fromIndex >= 0 && fromIndex < Settings.data.controlCenter.shortcuts[section].length && toIndex >= 0 && toIndex < Settings.data.controlCenter.shortcuts[section].length) {
// Create a new array to avoid modifying the original
var newArray = Settings.data.controlCenter.shortcuts[section].slice();
var item = newArray[fromIndex];
newArray.splice(fromIndex, 1);
newArray.splice(toIndex, 0, item);
Settings.data.controlCenter.shortcuts[section] = newArray;
}
}
function _moveWidgetBetweenSections(fromSection, index, toSection) {
// Get the widget from the source section
if (index >= 0 && index < Settings.data.controlCenter.shortcuts[fromSection].length) {
var widget = Settings.data.controlCenter.shortcuts[fromSection][index];
// Remove from source section
var sourceArray = Settings.data.controlCenter.shortcuts[fromSection].slice();
sourceArray.splice(index, 1);
Settings.data.controlCenter.shortcuts[fromSection] = sourceArray;
// Add to target section
var targetArray = Settings.data.controlCenter.shortcuts[toSection].slice();
targetArray.push(widget);
Settings.data.controlCenter.shortcuts[toSection] = targetArray;
}
}
function _updateWidgetSettingsInSection(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
ListModel {
id: availableWidgets
}
// Shared Plugin Settings Popup
NPluginSettingsPopup {
id: pluginSettingsDialog
parent: Overlay.overlay
showToastOnSave: false
}
}
@@ -0,0 +1,87 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Services.System
import qs.Services.UI
import qs.Widgets
ColumnLayout {
id: root
spacing: Style.marginL
Layout.fillWidth: true
Layout.fillHeight: true
required property var availableWidgets
signal addWidgetToSection(string widgetId, string section)
signal removeWidgetFromSection(string section, int index)
signal reorderWidgetInSection(string section, int fromIndex, int toIndex)
signal updateWidgetSettingsInSection(string section, int index, var settings)
signal moveWidgetBetweenSections(string fromSection, int index, string toSection)
signal openPluginSettingsRequested(var manifest)
function getSectionIcons() {
return {
"left": "arrow-bar-to-up",
"right": "arrow-bar-to-down"
};
}
// Widgets Management Section
ColumnLayout {
spacing: Style.marginXXS
Layout.fillWidth: true
// Sections
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.topMargin: Style.marginM
spacing: Style.marginM
// Left
NSectionEditor {
sectionName: I18n.tr("positions.left")
sectionId: "left"
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/ControlCenter/ControlCenterWidgetSettingsDialog.qml")
maxWidgets: Settings.data.controlCenter.shortcuts["right"].length > 5 ? 0 : (Settings.data.controlCenter.shortcuts["right"].length > 0 ? 5 : 10)
widgetRegistry: ControlCenterWidgetRegistry
widgetModel: Settings.data.controlCenter.shortcuts["left"]
sectionIcons: root.getSectionIcons()
availableWidgets: root.availableWidgets
availableSections: ["left", "right"]
onAddWidget: (widgetId, section) => root.addWidgetToSection(widgetId, section)
onRemoveWidget: (section, index) => root.removeWidgetFromSection(section, index)
onReorderWidget: (section, fromIndex, toIndex) => root.reorderWidgetInSection(section, fromIndex, toIndex)
onUpdateWidgetSettings: (section, index, settings) => root.updateWidgetSettingsInSection(section, index, settings)
onMoveWidget: (fromSection, index, toSection) => root.moveWidgetBetweenSections(fromSection, index, toSection)
onOpenPluginSettingsRequested: manifest => root.openPluginSettingsRequested(manifest)
}
// Right
NSectionEditor {
sectionName: I18n.tr("positions.right")
sectionId: "right"
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/ControlCenter/ControlCenterWidgetSettingsDialog.qml")
maxWidgets: Settings.data.controlCenter.shortcuts["left"].length > 5 ? 0 : (Settings.data.controlCenter.shortcuts["left"].length > 0 ? 5 : 10)
widgetRegistry: ControlCenterWidgetRegistry
widgetModel: Settings.data.controlCenter.shortcuts["right"]
sectionIcons: root.getSectionIcons()
availableWidgets: root.availableWidgets
availableSections: ["left", "right"]
onAddWidget: (widgetId, section) => root.addWidgetToSection(widgetId, section)
onRemoveWidget: (section, index) => root.removeWidgetFromSection(section, index)
onReorderWidget: (section, fromIndex, toIndex) => root.reorderWidgetInSection(section, fromIndex, toIndex)
onUpdateWidgetSettings: (section, index, settings) => root.updateWidgetSettingsInSection(section, index, settings)
onMoveWidget: (fromSection, index, toSection) => root.moveWidgetBetweenSections(fromSection, index, toSection)
onOpenPluginSettingsRequested: manifest => root.openPluginSettingsRequested(manifest)
}
}
}
Rectangle {
Layout.fillHeight: true
}
}