quickshell and hyprland additions

This commit is contained in:
2026-03-15 13:56:00 +02:00
parent c9c27d1554
commit 1ad06b82a6
509 changed files with 68371 additions and 19 deletions

View File

@@ -0,0 +1,115 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import qs.config
import qs.modules.components
import qs.config
import qs.services
Item {
id: root
Layout.fillWidth: true
Layout.fillHeight: true
property int logoOffset: -30
Column {
anchors.centerIn: parent
width: 460
spacing: Metrics.spacing(12)
Item {
width: parent.width
height: Metrics.fontSize(200)
StyledText {
text: SystemDetails.osIcon
anchors.centerIn: parent
x: root.logoOffset
font.pixelSize: Metrics.fontSize(200)
}
}
StyledText {
text: "Nucleus Shell"
width: parent.width
horizontalAlignment: Text.AlignHCenter
font.family: "Outfit ExtraBold"
font.pixelSize: Metrics.fontSize(26)
}
StyledText {
text: "A shell built to get things done."
width: parent.width
wrapMode: Text.Wrap
horizontalAlignment: Text.AlignHCenter
font.pixelSize: Metrics.fontSize(14)
}
Row {
anchors.horizontalCenter: parent.horizontalCenter
spacing: Metrics.spacing(10)
StyledButton {
text: "View on GitHub"
icon: "code"
secondary: true
onClicked: Qt.openUrlExternally("https://github.com/xZepyx/nucleus-shell")
}
StyledButton {
text: "Report Issue"
icon: "bug_report"
secondary: true
onClicked: Qt.openUrlExternally("https://github.com/xZepyx/nucleus-shell/issues")
}
}
}
StyledText {
text: "Nucleus-Shell v" + Config.runtime.shell.version
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: Metrics.margin(24)
font.pixelSize: Metrics.fontSize(12)
}
StyledRect {
width: 52
height: 52
radius: Appearance.rounding.small
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: Metrics.margin(24)
StyledText {
text: "↻"
anchors.centerIn: parent
font.pixelSize: Metrics.fontSize(22)
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onClicked: {
Globals.states.settingsOpen = false
Quickshell.execDetached(["notify-send", "Updating Nucleus Shell"])
Quickshell.execDetached([
"kitty",
"--hold",
"bash",
"-c",
Directories.scriptsPath + "/system/update.sh"
])
}
}
}
}

View File

@@ -0,0 +1,364 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Widgets
import qs.config
import qs.modules.components
import qs.services
import qs.plugins
ContentMenu {
title: "Appearance"
description: "Adjust how the desktop looks like."
ContentCard {
ColumnLayout {
Layout.fillWidth: true
spacing: Metrics.spacing(16)
ColumnLayout {
spacing: Metrics.spacing(4)
StyledText {
text: "Select Theme"
font.pixelSize: Metrics.fontSize(16)
}
StyledText {
text: "Choose between dark or light mode."
font.pixelSize: Metrics.fontSize(12)
color: "#888888"
}
}
RowLayout {
Layout.leftMargin: Metrics.margin(15)
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
spacing: Metrics.spacing(16)
StyledButton {
Layout.preferredHeight: 300
Layout.preferredWidth: 460
Layout.maximumHeight: 400
Layout.maximumWidth: 500
icon: "dark_mode"
iconSize: Metrics.iconSize(64)
checked: Config.runtime.appearance.theme === "dark"
hoverEnabled: true
onClicked: {
if (!Config.runtime.appearance.colors.autogenerated) {
const scheme = Config.runtime.appearance.colors.scheme
const file = Theme.map[scheme]?.dark
if (!file) {
Theme.notifyMissingVariant(scheme, "dark")
return
}
Config.updateKey("appearance.theme", "dark")
Quickshell.execDetached([
"nucleus", "theme", "switch", file
])
} else {
Config.updateKey("appearance.theme", "dark")
Quickshell.execDetached([
"nucleus", "ipc", "call", "global", "regenColors"
])
}
}
}
StyledButton {
Layout.preferredHeight: 300
Layout.preferredWidth: 460
Layout.maximumHeight: 400
Layout.maximumWidth: 500
icon: "light_mode"
iconSize: Metrics.iconSize(64)
checked: Config.runtime.appearance.theme === "light"
hoverEnabled: true
onClicked: {
if (!Config.runtime.appearance.colors.autogenerated) {
const scheme = Config.runtime.appearance.colors.scheme
const file = Theme.map[scheme]?.light
if (!file) {
Theme.notifyMissingVariant(scheme, "light")
return
}
Config.updateKey("appearance.theme", "light")
Quickshell.execDetached([
"nucleus", "theme", "switch", file
])
} else {
Config.updateKey("appearance.theme", "light")
Quickshell.execDetached([
"nucleus", "ipc", "call", "global", "regenColors"
])
}
}
}
}
Item {
width: Metrics.spacing(30)
}
}
}
ContentCard {
RowLayout {
opacity: autogeneratedColorsSelector.enabled ? 1 : 0.8
ColumnLayout {
StyledText {
text: "Color Generation Schemes:"
font.pixelSize: Metrics.fontSize(16)
}
StyledText {
text: "Choose the scheme for autogenerated color generation."
font.pixelSize: Metrics.fontSize(12)
}
}
Item { Layout.fillWidth: true }
StyledDropDown {
id: autogeneratedColorsSelector
label: "Color Scheme"
model: [
"scheme-content",
"scheme-expressive",
"scheme-fidelity",
"scheme-fruit-salad",
"scheme-monochrome",
"scheme-neutral",
"scheme-rainbow",
"scheme-tonal-spot"
]
currentIndex: model.indexOf(Config.runtime.appearance.colors.matugenScheme)
onSelectedIndexChanged: (index) => {
if (!Config.runtime.appearance.colors.autogenerated)
return
const selectedScheme = model[index]
Config.updateKey("appearance.colors.matugenScheme", selectedScheme)
Quickshell.execDetached([
"nucleus", "ipc", "call", "global", "regenColors"
])
}
enabled: Config.runtime.appearance.colors.autogenerated
}
}
RowLayout {
opacity: predefinedThemeSelector.enabled ? 1 : 0.8
ColumnLayout {
StyledText {
font.pixelSize: Metrics.fontSize(16)
text: "Predefined/Custom Themes:"
}
StyledText {
font.pixelSize: Metrics.fontSize(12)
text: "Choose a pre-defined theme for your interface."
}
}
Item { Layout.fillWidth: true }
StyledDropDown {
id: predefinedThemeSelector
label: "Theme"
model: Object.keys(Theme.map)
currentIndex: model.indexOf(Config.runtime.appearance.colors.scheme)
onSelectedIndexChanged: (index) => {
if (Config.runtime.appearance.colors.autogenerated)
return
const selectedTheme = model[index]
const variant = Config.runtime.appearance.theme
const file = Theme.map[selectedTheme][variant]
if (!file) return
Config.updateKey("appearance.colors.scheme", selectedTheme)
Quickshell.execDetached([
"nucleus", "theme", "switch", file
])
}
enabled: !Config.runtime.appearance.colors.autogenerated
}
}
}
ContentCard {
StyledSwitchOption {
title: "Tint Icons"
description: "Either tint icons across the shell or keep them colorized."
prefField: "appearance.tintIcons"
}
StyledSwitchOption {
title: "Use Autogenerated Themes"
description: "Use autogenerated themes."
prefField: "appearance.colors.autogenerated"
}
StyledSwitchOption {
title: "Use User Defined Themes"
description: "Enabling this will also run the default `config.toml` in `~/.config/matugen` dir."
prefField: "appearance.colors.runMatugenUserWide"
}
}
ContentCard {
StyledText {
text: "Clock"
font.pixelSize: Metrics.fontSize(20)
font.bold: true
}
StyledSwitchOption {
title: "Show Clock"
description: "Whether to show or disable the clock on the background."
prefField: "appearance.background.clock.enabled"
}
StyledSwitchOption {
title: "Analog Variant"
description: "Whether to use analog clock or not."
prefField: "appearance.background.clock.isAnalog"
}
StyledSwitchOption {
title: "Rotate Clock Polygon"
description: "Rotate the shape polygon of the analog clock."
prefField: "appearance.background.clock.rotatePolygonBg"
enabled: Config.runtime.appearance.background.clock.isAnalog
opacity: enabled ? 1 : 0.8
}
NumberStepper {
label: "Rotation Duration"
description: "Adjust the duration in which the clock rotates 360* (Seconds)."
prefField: "appearance.background.clock.rotationDuration"
minimum: 1
maximum: 40
step: 1
}
RowLayout {
id: shapeSelector
ColumnLayout {
StyledText {
text: "Analog Clock Shape"
font.pixelSize: Metrics.fontSize(16)
}
StyledText {
text: "Choose the analog clock's shape."
font.pixelSize: Metrics.fontSize(12)
}
}
Item { Layout.fillWidth: true }
StyledDropDown {
label: "Shape Type"
model: ["Cookie 7 Sided", "Cookie 9 Sided", "Cookie 12 Sided", "Pixelated Circle", "Circle"]
currentIndex: Config.runtime.appearance.background.clock.shape
onSelectedIndexChanged: (index) => {
Config.updateKey(
"appearance.background.clock.shape",
index
)
}
}
}
}
ContentCard {
StyledText {
text: "Rounding"
font.pixelSize: Metrics.fontSize(20)
font.bold: true
}
NumberStepper {
label: "Factor"
description: "Adjust the rounding factor."
prefField: "appearance.rounding.factor"
minimum: 0
maximum: 1
step: 0.1
}
}
ContentCard {
StyledText {
text: "Font"
font.pixelSize: Metrics.fontSize(20)
font.bold: true
}
NumberStepper {
label: "Scale"
description: "Adjust the font scale."
prefField: "appearance.font.scale"
minimum: 0.1
maximum: 2
step: 0.1
}
}
ContentCard {
StyledText {
text: "Transparency"
font.pixelSize: Metrics.fontSize(20)
font.bold: true
}
StyledSwitchOption {
title: "Enabled"
description: "Whether to enable or disable transparency."
prefField: "appearance.transparency.enabled"
}
NumberStepper {
label: "Factor"
description: "Adjust the alpha value for transparency."
prefField: "appearance.transparency.alpha"
minimum: 0.1
maximum: 1
step: 0.1
}
}
ContentCard {
StyledText {
text: "Animations"
font.pixelSize: Metrics.fontSize(20)
font.bold: true
}
StyledSwitchOption {
title: "Enabled"
description: "Whether to enable or disable animations (applies everywhere in the shell)."
prefField: "appearance.animations.enabled"
}
NumberStepper {
label: "Duration Scale"
description: "Adjust the duration scale of the animations."
prefField: "appearance.animations.durationScale"
minimum: 0.1
maximum: 1
step: 0.1
}
}
}

View File

@@ -0,0 +1,355 @@
import QtQuick
import QtQuick.Layouts
import Quickshell.Io
import qs.modules.functions
import qs.config
import qs.modules.components
import qs.services
ContentMenu {
title: "Sound"
description: "Volume and audio devices"
ContentCard {
ColumnLayout {
Layout.fillWidth: true
spacing: Metrics.spacing(20)
RowLayout {
Layout.fillWidth: true
spacing: Metrics.spacing(16)
Rectangle {
Layout.preferredWidth: 40
Layout.preferredHeight: 40
radius: Metrics.radius("large")
color: Appearance.m3colors.m3primaryContainer
MaterialSymbol {
anchors.centerIn: parent
icon: "volume_up"
color: Appearance.m3colors.m3onPrimaryContainer
iconSize: Metrics.iconSize(24)
}
}
ColumnLayout {
Layout.fillWidth: true
StyledText {
text: "Output"
font.pixelSize: Metrics.fontSize(16)
font.family: Metrics.fontFamily("Outfit Medium")
color: Appearance.m3colors.m3onSurface
}
StyledText {
text: Volume.defaultSink.description
font.pixelSize: Metrics.fontSize(13)
color: Appearance.m3colors.m3onSurfaceVariant
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: Appearance.m3colors.m3outlineVariant
opacity: 0.4
}
ColumnLayout {
Layout.fillWidth: true
spacing: Metrics.spacing(12)
RowLayout {
Layout.fillWidth: true
StyledText {
text: "Volume"
font.pixelSize: Metrics.fontSize(14)
font.family: Metrics.fontFamily("Outfit Medium")
color: Appearance.m3colors.m3onSurface
}
Item { Layout.fillWidth: true }
StyledText {
animate: false
text: Math.round(Volume.defaultSink.audio.volume * 100) + "%"
font.pixelSize: Metrics.fontSize(14)
font.family: Metrics.fontFamily("Outfit SemiBold")
color: Appearance.m3colors.m3primary
}
}
RowLayout {
Layout.fillWidth: true
spacing: Metrics.spacing(16)
MaterialSymbol {
icon: Volume.defaultSink.audio.muted ? "volume_off"
: Volume.defaultSink.audio.volume < 0.33 ? "volume_mute"
: Volume.defaultSink.audio.volume < 0.66 ? "volume_down"
: "volume_up"
color: Appearance.m3colors.m3onSurfaceVariant
iconSize: Metrics.iconSize(24)
}
StyledSlider {
id: outputVolumeSlider
Layout.fillWidth: true
value: Volume.defaultSink.audio.volume * 100
onValueChanged: Volume.setVolume(value / 100)
}
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: Metrics.spacing(8)
StyledText {
text: "Device"
font.pixelSize: Metrics.fontSize(14)
font.family: Metrics.fontFamily("Outfit Medium")
color: Appearance.m3colors.m3onSurface
}
StyledDropDown {
Layout.fillWidth: true
label: "Output device"
model: Volume.sinks.map(d => d.description)
currentIndex: {
for (let i = 0; i < Volume.sinks.length; i++)
if (Volume.sinks[i].name === Volume.defaultSink.name) return i
return -1
}
onSelectedIndexChanged: index => {
if (index >= 0 && index < Volume.sinks.length)
Volume.setDefaultSink(Volume.sinks[index])
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 56
radius: Metrics.radius("small")
color: Appearance.m3colors.m3surfaceContainerHigh
RowLayout {
anchors.fill: parent
anchors.leftMargin: Metrics.margin(16)
anchors.rightMargin: Metrics.margin(16)
spacing: Metrics.spacing(12)
MaterialSymbol {
icon: Volume.defaultSink.audio.muted ? "volume_off" : "volume_up"
color: Volume.defaultSink.audio.muted ? Appearance.m3colors.m3error : Appearance.m3colors.m3onSurfaceVariant
iconSize: Metrics.iconSize(24)
}
StyledText {
Layout.fillWidth: true
text: "Mute output"
font.pixelSize: Metrics.fontSize(14)
color: Appearance.m3colors.m3onSurface
}
StyledSwitch {
checked: Volume.defaultSink.audio.muted
onToggled: Volume.toggleMuted(Volume.defaultSink)
}
}
}
}
}
ContentCard {
ColumnLayout {
Layout.fillWidth: true
spacing: Metrics.spacing(20)
RowLayout {
Layout.fillWidth: true
spacing: Metrics.spacing(16)
Rectangle {
Layout.preferredWidth: 40
Layout.preferredHeight: 40
radius: Metrics.radius("large")
color: Appearance.m3colors.m3secondaryContainer
MaterialSymbol {
anchors.centerIn: parent
icon: "mic"
color: Appearance.m3colors.m3onSecondaryContainer
iconSize: Metrics.iconSize(24)
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: Metrics.spacing(2)
StyledText {
text: "Input"
font.pixelSize: Metrics.fontSize(16)
font.family: Metrics.fontFamily("Outfit Medium")
color: Appearance.m3colors.m3onSurface
}
StyledText {
visible: Volume.sources.length > 0
text: Volume.defaultSource.description
font.pixelSize: Metrics.fontSize(13)
color: Appearance.m3colors.m3onSurfaceVariant
}
}
}
Rectangle {
visible: Volume.sources.length === 0
Layout.fillWidth: true
Layout.preferredHeight: 120
radius: Metrics.radius("small")
color: Appearance.m3colors.m3surfaceContainerHigh
ColumnLayout {
anchors.centerIn: parent
spacing: Metrics.spacing(8)
MaterialSymbol {
icon: "mic_off"
iconSize: Metrics.iconSize(48)
color: ColorUtils.transparentize(Appearance.m3colors.m3onSurface, 0.3)
Layout.alignment: Qt.AlignHCenter
}
StyledText {
text: "No input devices"
font.pixelSize: Metrics.fontSize(14)
font.family: Metrics.fontFamily("Outfit Medium")
color: Appearance.m3colors.m3onSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
}
}
Rectangle {
visible: Volume.sources.length > 0
Layout.fillWidth: true
Layout.preferredHeight: 1
color: Appearance.m3colors.m3outlineVariant
opacity: 0.4
}
ColumnLayout {
visible: Volume.sources.length > 0
Layout.fillWidth: true
spacing: Metrics.spacing(12)
RowLayout {
Layout.fillWidth: true
StyledText {
text: "Volume"
font.pixelSize: Metrics.fontSize(14)
font.family: Metrics.fontFamily("Outfit Medium")
color: Appearance.m3colors.m3onSurface
}
Item { Layout.fillWidth: true }
StyledText {
animate: false
text: Math.round(Volume.defaultSource.audio.volume * 100) + "%"
font.pixelSize: Metrics.fontSize(14)
font.family: Metrics.fontFamily("Outfit SemiBold")
color: Appearance.m3colors.m3primary
}
}
RowLayout {
Layout.fillWidth: true
spacing: Metrics.spacing(16)
MaterialSymbol {
icon: Volume.defaultSource.audio.muted ? "mic_off" : "mic"
color: Appearance.m3colors.m3onSurfaceVariant
iconSize: Metrics.iconSize(24)
}
StyledSlider {
id: inputVolumeSlider
Layout.fillWidth: true
value: Volume.defaultSource.audio.volume * 100
onValueChanged: Volume.setSourceVolume(value / 100)
}
}
}
ColumnLayout {
visible: Volume.sources.length > 0
Layout.fillWidth: true
spacing: Metrics.spacing(8)
StyledText {
text: "Device"
font.pixelSize: Metrics.fontSize(14)
font.family: Metrics.fontFamily("Outfit Medium")
color: Appearance.m3colors.m3onSurface
}
StyledDropDown {
Layout.fillWidth: true
label: "Input device"
model: Volume.sources.map(d => d.description)
currentIndex: {
for (let i = 0; i < Volume.sources.length; i++)
if (Volume.sources[i].name === Volume.defaultSource.name) return i
return -1
}
onSelectedIndexChanged: index => {
if (index >= 0 && index < Volume.sources.length)
Volume.setDefaultSource(Volume.sources[index])
}
}
}
Rectangle {
visible: Volume.sources.length > 0
Layout.fillWidth: true
Layout.preferredHeight: 56
radius: Metrics.radius("small")
color: Appearance.m3colors.m3surfaceContainerHigh
RowLayout {
anchors.fill: parent
anchors.leftMargin: Metrics.margin(16)
anchors.rightMargin: Metrics.margin(16)
spacing: Metrics.spacing(12)
MaterialSymbol {
icon: Volume.defaultSource.audio.muted ? "mic_off" : "mic"
color: Volume.defaultSource.audio.muted ? Appearance.m3colors.m3error : Appearance.m3colors.m3onSurfaceVariant
iconSize: Metrics.iconSize(24)
}
StyledText {
Layout.fillWidth: true
text: "Mute input"
font.pixelSize: Metrics.fontSize(14)
color: Appearance.m3colors.m3onSurface
}
StyledSwitch {
checked: Volume.defaultSource.audio.muted
onToggled: Volume.toggleMuted(Volume.defaultSource)
}
}
}
}
}
}

View File

@@ -0,0 +1,300 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import QtQuick.Controls
import Quickshell.Widgets
import qs.config
import qs.modules.components
import qs.services
ContentMenu {
property string barKey: "bar"
title: "Bar"
description: "Adjust the bar's look."
ContentCard {
id: monitorSelectorCard
StyledText {
text: "Monitor Bar Configuration"
font.pixelSize: Metrics.fontSize(20)
font.bold: true
}
StyledText {
text: (Config.runtime.monitors?.[monitorSelector.model[monitorSelector.currentIndex]]?.bar)
? "This monitor has its own bar configuration."
: "This monitor currently uses the global bar."
wrapMode: Text.WordWrap
}
RowLayout {
spacing: Metrics.spacing("normal")
StyledDropDown {
id: monitorSelector
Layout.preferredWidth: 220
model: Xrandr.monitors.map(m => m.name)
currentIndex: 0
onCurrentIndexChanged: monitorSelectorCard.updateMonitorProperties()
}
Item { Layout.fillWidth: true }
StyledButton {
id: createButton
icon: "add"
text: "Override Bar: (" + monitorSelector.model[monitorSelector.currentIndex] + ")"
Layout.preferredWidth: 280
onClicked: {
const monitorName = monitorSelector.model[monitorSelector.currentIndex]
if (!monitorName) return
if (!Config.runtime.monitors) Config.runtime.monitors = {}
if (!Config.runtime.monitors[monitorName])
Config.runtime.monitors[monitorName] = {}
const defaultBar = {
density: 50,
enabled: true,
floating: false,
gothCorners: true,
margins: 16,
merged: false,
modules: {
height: 34,
paddingColor: "#1f1f1f",
radius: 17,
statusIcons: {
bluetoothStatusEnabled: true,
enabled: true,
networkStatusEnabled: true
},
systemUsage: {
cpuStatsEnabled: true,
enabled: true,
memoryStatsEnabled: true,
tempStatsEnabled: true
},
workspaces: {
enabled: true,
showAppIcons: true,
showJapaneseNumbers: false,
workspaceIndicators: 8
}
},
position: "top",
radius: 23
}
Config.updateKey("monitors." + monitorName + ".bar", defaultBar)
monitorSelectorCard.updateMonitorProperties()
}
}
StyledButton {
id: deleteButton
icon: "delete"
text: "Use Global Bar: (" + monitorSelector.model[monitorSelector.currentIndex] + ")"
secondary: true
Layout.preferredWidth: 280
onClicked: {
const monitorName = monitorSelector.model[monitorSelector.currentIndex]
if (!monitorName) return
Config.updateKey("monitors." + monitorName + ".bar", undefined)
monitorSelectorCard.updateMonitorProperties()
}
}
}
function updateMonitorProperties() {
const monitorName = monitorSelector.model[monitorSelector.currentIndex]
const monitorBar = Config.runtime.monitors?.[monitorName]?.bar
barKey = monitorBar ? "monitors." + monitorName + ".bar" : "bar"
createButton.enabled = !monitorBar
deleteButton.enabled = !!monitorBar
monitorSelector.model = Xrandr.monitors.map(m => m.name)
monitorSelector.currentIndex = Xrandr.monitors.findIndex(m => m.name === monitorName)
}
}
ContentCard {
StyledText {
text: "Bar"
font.pixelSize: Metrics.fontSize(20)
font.bold: true
}
ColumnLayout {
StyledText {
text: "Position"
font.pixelSize: Metrics.fontSize(16)
}
RowLayout {
spacing: Metrics.spacing(8)
Repeater {
model: ["Top", "Bottom", "Left", "Right"]
delegate: StyledButton {
property string pos: modelData.toLowerCase()
text: modelData
Layout.fillWidth: true
checked: ConfigResolver.bar(monitorSelector.model[monitorSelector.currentIndex]).position === pos
topLeftRadius: Metrics.radius("normal")
topRightRadius: Metrics.radius("normal")
bottomLeftRadius: Metrics.radius("normal")
bottomRightRadius: Metrics.radius("normal")
onClicked: Config.updateKey(barKey + ".position", pos)
}
}
}
}
StyledSwitchOption {
title: "Enabled"
description: "Toggle the bar visibility on/off"
prefField: barKey + ".enabled"
}
StyledSwitchOption {
title: "Floating Bar"
description: "Make the bar float above other windows instead of being part of the desktop"
prefField: barKey + ".floating"
}
StyledSwitchOption {
title: "Goth Corners"
description: "Apply gothic-style corner cutouts to the bar"
prefField: barKey + ".gothCorners"
}
StyledSwitchOption {
title: "Merged Layout"
description: "Merge all modules into a single continuous layout"
prefField: barKey + ".merged"
}
}
ContentCard {
StyledText {
text: "Bar Rounding & Size"
font.pixelSize: Metrics.fontSize(20)
font.bold: true
}
NumberStepper {
label: "Bar Density"
prefField: barKey + ".density"
description: "Modify the bar's density"
minimum: 40
maximum: 128
}
NumberStepper {
label: "Bar Radius"
prefField: barKey + ".radius"
description: "Modify the bar's radius"
minimum: 10
maximum: 128
}
NumberStepper {
label: "Module Container Radius"
prefField: barKey + ".modules.radius"
description: "Modify the bar's module.radius"
minimum: 10
maximum: 128
}
NumberStepper {
label: "Module Height"
prefField: barKey + ".modules.height"
description: "Modify the bar's module.height"
minimum: 10
maximum: 128
}
NumberStepper {
label: "Workspace Indicators"
prefField: barKey + ".modules.workspaces.workspaceIndicators"
description: "Adjust how many workspace indicators to show."
minimum: 1
maximum: 10
}
}
ContentCard {
StyledText {
text: "Bar Modules"
font.pixelSize: Metrics.fontSize(20)
font.bold: true
}
StyledText {
text: "Workspaces"
font.pixelSize: Metrics.fontSize(18)
font.bold: true
}
StyledSwitchOption {
title: "Enabled"
description: "Show workspace indicator module"
prefField: barKey + ".modules.workspaces.enabled"
}
StyledSwitchOption {
title: "Show App Icons"
description: "Display application icons in workspace indicators"
prefField: barKey + ".modules.workspaces.showAppIcons"
enabled: !barKey.modules.workspaces.showJapaneseNumbers && Compositor.require("hyprland")
opacity: !barKey.modules.workspaces.showJapaneseNumbers && Compositor.require("hyprland") ? 1 : 0.8
}
StyledSwitchOption {
title: "Show Japanese Numbers"
description: "Use Japanese-style numbers instead of standard numerals"
prefField: barKey + ".modules.workspaces.showJapaneseNumbers"
enabled: !barKey.modules.workspaces.showAppIcons
opacity: !barKey.modules.workspaces.showAppIcons ? 1 : 0.8
}
StyledText {
text: "Status Icons"
font.pixelSize: Metrics.fontSize(18)
font.bold: true
}
StyledSwitchOption {
title: "Enabled"
description: "Show status icons module (wifi, bluetooth)"
prefField: barKey + ".modules.statusIcons.enabled"
}
StyledSwitchOption {
title: "Show Wifi Status"
description: "Display wifi connection status and signal strength"
prefField: barKey + ".modules.statusIcons.networkStatusEnabled"
}
StyledSwitchOption {
title: "Show Bluetooth Status"
description: "Display bluetooth connection status"
prefField: barKey + ".modules.statusIcons.bluetoothStatusEnabled"
}
StyledText {
text: "System Stats"
font.pixelSize: Metrics.fontSize(18)
font.bold: true
}
StyledSwitchOption {
title: "Enabled"
description: "Show system resource monitoring module"
prefField: barKey + ".modules.systemUsage.enabled"
}
StyledSwitchOption {
title: "Show Cpu Usage Stats"
description: "Display CPU usage percentage and load"
prefField: barKey + ".modules.systemUsage.cpuStatsEnabled"
}
StyledSwitchOption {
title: "Show Memory Usage Stats"
description: "Display RAM usage and available memory"
prefField: barKey + ".modules.systemUsage.memoryStatsEnabled"
}
StyledSwitchOption {
title: "Show Cpu Temperature Stats"
description: "Display CPU temperature readings"
prefField: barKey + ".modules.systemUsage.tempStatsEnabled"
}
}
}

View File

@@ -0,0 +1,187 @@
import QtQuick
import QtQuick.Layouts
import qs.config
import qs.modules.components
import qs.modules.functions
import qs.services
import Quickshell.Bluetooth as QsBluetooth
ContentMenu {
title: "Bluetooth"
description: "Manage Bluetooth devices and connections."
ContentCard {
ContentRowCard {
cardSpacing: Metrics.spacing(0)
verticalPadding: Bluetooth.defaultAdapter.enabled ? Metrics.padding(10) : Metrics.padding(0)
cardMargin: Metrics.margin(0)
StyledText {
text: powerSwitch.checked ? "Power: On" : "Power: Off"
font.pixelSize: Metrics.fontSize(16)
font.bold: true
}
Item { Layout.fillWidth: true }
StyledSwitch {
id: powerSwitch
checked: Bluetooth.defaultAdapter?.enabled
onToggled: Bluetooth.defaultAdapter.enabled = checked
}
}
ContentRowCard {
visible: Bluetooth.defaultAdapter.enabled
cardSpacing: Metrics.spacing(0)
verticalPadding: Metrics.padding(10)
cardMargin: Metrics.margin(0)
ColumnLayout {
spacing: Metrics.spacing(2)
StyledText {
text: "Discoverable"
font.pixelSize: Metrics.fontSize(16)
}
StyledText {
text: "Allow other devices to find this computer."
font.pixelSize: Metrics.fontSize(12)
color: ColorUtils.transparentize(Appearance.m3colors.m3onSurface, 0.6)
}
}
Item { Layout.fillWidth: true }
StyledSwitch {
checked: Bluetooth.defaultAdapter?.discoverable
onToggled: Bluetooth.defaultAdapter.discoverable = checked
}
}
ContentRowCard {
visible: Bluetooth.defaultAdapter.enabled
cardSpacing: Metrics.spacing(0)
verticalPadding: Metrics.padding(0)
cardMargin: Metrics.margin(0)
ColumnLayout {
spacing: Metrics.spacing(2)
StyledText {
text: "Scanning"
font.pixelSize: Metrics.fontSize(16)
}
StyledText {
text: "Search for nearby Bluetooth devices."
font.pixelSize: Metrics.fontSize(12)
color: ColorUtils.transparentize(Appearance.m3colors.m3onSurface, 0.6)
}
}
Item { Layout.fillWidth: true }
StyledSwitch {
checked: Bluetooth.defaultAdapter?.discovering
onToggled: Bluetooth.defaultAdapter.discovering = checked
}
}
}
ContentCard {
visible: connectedDevices.count > 0
StyledText {
text: "Connected Devices"
font.pixelSize: Metrics.fontSize(18)
font.bold: true
}
Repeater {
id: connectedDevices
model: Bluetooth.devices.filter(d => d.connected)
delegate: BluetoothDeviceCard {
device: modelData
statusText: modelData.batteryAvailable
? "Connected, " + Math.floor(modelData.battery * 100) + "% left"
: "Connected"
showDisconnect: true
showRemove: true
usePrimary: true
}
}
}
ContentCard {
visible: Bluetooth.defaultAdapter?.enabled
StyledText {
text: "Paired Devices"
font.pixelSize: Metrics.fontSize(18)
font.bold: true
}
Item {
visible: pairedDevices.count === 0
width: parent.width
height: Metrics.spacing(40)
StyledText {
anchors.left: parent.left
text: "No paired devices"
font.pixelSize: Metrics.fontSize(14)
color: ColorUtils.transparentize(Appearance.m3colors.m3onSurface, 0.6)
}
}
Repeater {
id: pairedDevices
model: Bluetooth.devices.filter(d => !d.connected && d.paired)
delegate: BluetoothDeviceCard {
device: modelData
statusText: "Not connected"
showConnect: true
showRemove: true
}
}
}
ContentCard {
visible: Bluetooth.defaultAdapter?.enabled
StyledText {
text: "Available Devices"
font.pixelSize: Metrics.fontSize(18)
font.bold: true
}
Item {
visible: discoveredDevices.count === 0 && !Bluetooth.defaultAdapter.discovering
width: parent.width
height: Metrics.spacing(40)
StyledText {
Layout.alignment: Qt.AlignHCenter
text: "No new devices found"
font.pixelSize: Metrics.fontSize(14)
color: ColorUtils.transparentize(Appearance.m3colors.m3onSurface, 0.6)
}
}
Repeater {
id: discoveredDevices
model: Bluetooth.devices.filter(d => !d.paired && !d.connected)
delegate: BluetoothDeviceCard {
device: modelData
statusText: "Discovered"
showConnect: true
showPair: true
}
}
}
}

View File

@@ -0,0 +1,90 @@
import QtQuick
import QtQuick.Layouts
import qs.modules.components
import qs.config
import qs.modules.functions
import Quickshell.Bluetooth as QsBluetooth
ContentRowCard {
id: deviceRow
property var device
property string statusText: ""
property bool usePrimary: false
property bool showConnect: false
property bool showDisconnect: false
property bool showPair: false
property bool showRemove: false
cardMargin: Metrics.margin(0)
cardSpacing: Metrics.spacing(10)
verticalPadding: Metrics.padding(0)
opacity: device.state === QsBluetooth.BluetoothDeviceState.Connecting ||
device.state === QsBluetooth.BluetoothDeviceState.Disconnecting ? 0.6 : 1
function mapBluetoothIcon(dbusIcon, name) {
console.log(dbusIcon, " / ", name)
const iconMap = {
"audio-headset": "headset",
"audio-headphones": "headphones",
"input-keyboard": "keyboard",
"input-mouse": "mouse",
"input-gaming": "sports_esports",
"phone": "phone_android",
"computer": "computer",
"printer": "print",
"camera": "photo_camera",
"unknown": "bluetooth"
}
return iconMap[dbusIcon] || "bluetooth"
}
MaterialSymbol {
icon: mapBluetoothIcon(device.icon, device.name)
font.pixelSize: Metrics.fontSize(32)
}
ColumnLayout {
Layout.alignment: Qt.AlignVCenter
spacing: Metrics.spacing(0)
StyledText {
text: device.name || device.address
font.pixelSize: Metrics.fontSize(16)
font.bold: true
}
StyledText {
text: statusText
font.pixelSize: Metrics.fontSize(12)
color: usePrimary
? Appearance.m3colors.m3primary
: ColorUtils.transparentize(Appearance.m3colors.m3onSurface, 0.6)
}
}
Item { Layout.fillWidth: true }
StyledButton {
visible: showConnect
icon: "link"
onClicked: device.connect()
}
StyledButton {
visible: showDisconnect
icon: "link_off"
onClicked: device.disconnect()
}
StyledButton {
visible: showPair
icon: "add"
onClicked: device.pair()
}
StyledButton {
visible: showRemove
icon: "delete"
onClicked: Bluetooth.removeDevice(device)
}
}

View File

@@ -0,0 +1,79 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Widgets
import qs.config
import qs.modules.components
import qs.services
ContentMenu {
title: "Launcher"
description: "Adjust launcher's settings."
ContentCard {
StyledText {
text: "Filters & Search"
font.pixelSize: Metrics.fontSize(20)
font.bold: true
}
StyledSwitchOption {
title: "Fuzzy Search"
description: "Enable or disable fuzzy search."
prefField: "launcher.fuzzySearchEnabled"
}
RowLayout {
id: webEngineSelector
property string title: "Web Search Engine"
property string description: "Choose the web search engine for web searches."
property string prefField: ''
ColumnLayout {
StyledText {
text: webEngineSelector.title
font.pixelSize: Metrics.fontSize(16)
}
StyledText {
text: webEngineSelector.description
font.pixelSize: Metrics.fontSize(12)
}
}
Item {
Layout.fillWidth: true
}
StyledDropDown {
label: "Engine"
model: ["Google", "Brave", "DuckDuckGo", "Bing"]
// Set the initial index based on the lowercase value in Config
currentIndex: {
switch (Config.runtime.launcher.webSearchEngine.toLowerCase()) {
case "google":
return 0;
case "brave":
return 1;
case "duckduckgo":
return 2;
case "bing":
return 3;
default:
return 0;
}
}
onSelectedIndexChanged: (index) => {
// Update Config with lowercase version of selected model
Config.updateKey("launcher.webSearchEngine", model[index].toLowerCase());
}
}
}
}
}

View File

@@ -0,0 +1,109 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Widgets
import qs.config
import qs.modules.components
import qs.services
ContentMenu {
title: "Miscellaneous"
description: "Configure misc settings."
ContentCard {
StyledText {
text: "Versions"
font.pixelSize: Metrics.fontSize(20)
font.bold: true
}
RowLayout {
id: releaseChannelSelector
property string title: "Release Channel"
property string description: "Choose the release channel for updates."
property string prefField: ''
ColumnLayout {
StyledText {
text: releaseChannelSelector.title
font.pixelSize: Metrics.fontSize(16)
}
StyledText {
text: releaseChannelSelector.description
font.pixelSize: Metrics.fontSize(12)
}
}
Item {
Layout.fillWidth: true
}
StyledDropDown {
label: "Type"
model: ["Stable", "Edge (indev)"]
currentIndex: Config.runtime.shell.releaseChannel === "edge" ? 1 : 0
onSelectedIndexChanged: (index) => {
Config.updateKey("shell.releaseChannel", index === 1 ? "edge" : "stable");
UpdateNotifier.notified = false;
}
}
}
}
ContentCard {
StyledText {
text: "Intelligence"
font.pixelSize: Metrics.fontSize(20)
font.bold: true
}
StyledSwitchOption {
title: "Enabled"
description: "Enable or disable intelligence."
prefField: "misc.intelligence.enabled"
}
}
ContentCard {
StyledText {
text: "Intelligence Bearer/API"
font.pixelSize: Metrics.fontSize(20)
font.bold: true
}
StyledTextField {
id: apiKeyTextField
clip: true
horizontalAlignment: Text.AlignLeft
placeholderText: Config.runtime.misc.intelligence.apiKey !== "" ? Config.runtime.misc.intelligence.apiKey : "Bearer Key"
Layout.fillWidth: true
Keys.onPressed: (event) => {
if (event.key === Qt.Key_S && (event.modifiers & Qt.ControlModifier)) {
event.accepted = true;
Config.updateKey("misc.intelligence.apiKey", apiKeyTextField.text);
Quickshell.execDetached(["notify-send", "Saved Bearer/API Key"])
}
}
font.pixelSize: Metrics.fontSize(16)
}
Item {
width: 20
}
InfoCard {
title: "How to save the api key"
description: "In order to save the api key press Ctrl+S and it will save the api key to the config."
}
}
}

View File

@@ -0,0 +1,136 @@
import QtQuick
import QtQuick.Layouts
import qs.config
import qs.modules.components
import qs.services
import qs.modules.functions
Item {
id: networkRow
property var connection
property bool isActive: false
property bool showConnect: false
property bool showDisconnect: false
property bool showPasswordField: false
property string password: ""
width: parent.width
implicitHeight: mainLayout.implicitHeight
function signalIcon(strength, secure) {
if (!connection) return "network_wifi";
if (connection.type === "ethernet") return "settings_ethernet";
if (strength >= 75) return "network_wifi";
if (strength >= 50) return "network_wifi_3_bar";
if (strength >= 25) return "network_wifi_2_bar";
if (strength > 0) return "network_wifi_1_bar";
return "network_wifi_1_bar";
}
ColumnLayout {
id: mainLayout
anchors.fill: parent
spacing: Metrics.spacing(10)
RowLayout {
spacing: Metrics.spacing(10)
// Signal icon with lock overlay
Item {
width: Metrics.spacing(32)
height: Metrics.spacing(32)
MaterialSymbol {
anchors.fill: parent
icon: connection ? signalIcon(connection.strength, connection.isSecure) : "network_wifi"
font.pixelSize: Metrics.fontSize(32)
}
// Lock overlay (anchors are safe because Item is not layout-managed)
MaterialSymbol {
icon: "lock"
visible: connection && connection.type === "wifi" && connection.isSecure
font.pixelSize: Metrics.fontSize(12)
anchors.right: parent.right
anchors.bottom: parent.bottom
}
}
ColumnLayout {
Layout.alignment: Qt.AlignVCenter
spacing: Metrics.spacing(0)
StyledText {
text: connection ? connection.name : ""
font.pixelSize: Metrics.fontSize(16)
font.bold: true
}
StyledText {
text: connection ? (
isActive ? "Connected" :
connection.type === "ethernet" ? connection.device || "Ethernet" :
connection.isSecure ? "Secured" : "Open"
) : ""
font.pixelSize: Metrics.fontSize(12)
color: isActive
? Appearance.m3colors.m3primary
: ColorUtils.transparentize(Appearance.m3colors.m3onSurface, 0.4)
}
}
Item { Layout.fillWidth: true }
StyledButton {
visible: showConnect && !showPasswordField
icon: "link"
onClicked: {
if (!connection) return;
if (connection.type === "ethernet") Network.connect(connection, "")
else if (connection.isSecure) showPasswordField = true
else Network.connect(connection, "")
}
}
StyledButton {
visible: showDisconnect && !showPasswordField
icon: "link_off"
onClicked: Network.disconnect()
}
}
// Password row
RowLayout {
visible: showPasswordField && connection && connection.type === "wifi"
spacing: Metrics.spacing(10)
StyledTextField {
padding: Metrics.padding(10)
Layout.fillWidth: true
placeholderText: "Enter password"
echoMode: parent.showPassword ? TextInput.Normal : TextInput.Password
onTextChanged: networkRow.password = text
onAccepted: {
if (!connection) return;
Network.connect(connection, networkRow.password)
showPasswordField = false
}
}
StyledButton {
property bool showPassword: false
icon: parent.showPassword ? "visibility" : "visibility_off"
onClicked: parent.showPassword = !parent.showPassword
}
StyledButton {
icon: "link"
onClicked: {
if (!connection) return;
Network.connect(connection, networkRow.password)
showPasswordField = false
}
}
}
}
}

View File

@@ -0,0 +1,172 @@
import QtQuick
import QtQuick.Layouts
import qs.modules.functions
import qs.config
import qs.modules.components
import qs.services
ContentMenu {
title: "Network"
description: "Manage network connections."
ContentCard {
ContentRowCard {
cardSpacing: Metrics.spacing(0)
verticalPadding: Network.wifiEnabled ? Metrics.padding(10) : Metrics.padding(0)
cardMargin: Metrics.margin(0)
StyledText {
text: powerSwitch.checked ? "Wi-Fi: On" : "Wi-Fi: Off"
font.pixelSize: Metrics.fontSize(16)
font.bold: true
}
Item { Layout.fillWidth: true }
StyledSwitch {
id: powerSwitch
checked: Network.wifiEnabled
onToggled: Network.enableWifi(checked)
}
}
ContentRowCard {
visible: Network.wifiEnabled
cardSpacing: Metrics.spacing(0)
verticalPadding: Metrics.padding(10)
cardMargin: Metrics.margin(0)
ColumnLayout {
spacing: Metrics.spacing(2)
StyledText {
text: "Scanning"
font.pixelSize: Metrics.fontSize(16)
}
StyledText {
text: "Search for nearby Wi-Fi networks."
font.pixelSize: Metrics.fontSize(12)
color: ColorUtils.transparentize(
Appearance.m3colors.m3onSurface, 0.4
)
}
}
Item { Layout.fillWidth: true }
StyledSwitch {
checked: Network.scanning
onToggled: {
if (checked)
Network.rescan()
}
}
}
}
InfoCard {
visible: Network.message !== "" && Network.message !== "ok"
icon: "error"
backgroundColor: Appearance.m3colors.m3error
contentColor: Appearance.m3colors.m3onError
title: "Failed to connect to " + Network.lastNetworkAttempt
description: Network.message
}
ContentCard {
visible: Network.active !== null
StyledText {
text: "Active Connection"
font.pixelSize: Metrics.fontSize(18)
font.bold: true
}
NetworkCard {
connection: Network.active
isActive: true
showDisconnect: Network.active?.type === "wifi"
}
}
ContentCard {
visible: Network.connections.filter(c => c.type === "ethernet").length > 0
StyledText {
text: "Ethernet"
font.pixelSize: Metrics.fontSize(18)
font.bold: true
}
Repeater {
model: Network.connections.filter(c => c.type === "ethernet" && !c.active)
delegate: NetworkCard {
connection: modelData
showConnect: true
}
}
}
ContentCard {
visible: Network.wifiEnabled
StyledText {
text: "Available Wi-Fi Networks"
font.pixelSize: Metrics.fontSize(18)
font.bold: true
}
Item {
visible: Network.connections.filter(c => c.type === "wifi").length === 0 && !Network.scanning
width: parent.width
height: Metrics.spacing(40)
StyledText {
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
text: "No networks found"
font.pixelSize: Metrics.fontSize(14)
color: ColorUtils.transparentize(Appearance.m3colors.m3onSurface, 0.4)
}
}
Repeater {
model: Network.connections.filter(c => c.type === "wifi" && !c.active)
delegate: NetworkCard {
connection: modelData
showConnect: true
}
}
}
ContentCard {
visible: Network.savedNetworks.length > 0
StyledText {
text: "Remembered Networks"
font.pixelSize: Metrics.fontSize(18)
font.bold: true
}
Item {
visible: Network.savedNetworks.length === 0
width: parent.width
height: Metrics.spacing(40)
StyledText {
anchors.left: parent.left
text: "No remembered networks"
font.pixelSize: Metrics.fontSize(14)
color: Appearance.colors.colSubtext
}
}
Repeater {
model: Network.connections.filter(c => c.type === "wifi" && c.saved && !c.active)
delegate: NetworkCard {
connection: modelData
showConnect: false
showDisconnect: false
}
}
}
}

View File

@@ -0,0 +1,215 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Widgets
import qs.config
import qs.modules.components
import qs.services
import qs.modules.functions
ContentMenu {
title: "Notifications & Overlays"
description: "Adjust notification and overlay settings."
function indexFromPosition(pos, model) {
pos = pos.toLowerCase()
for (let i = 0; i < model.length; i++) {
if (model[i].toLowerCase().replace(" ", "-") === pos)
return i
}
return 0
}
ContentCard {
StyledText {
text: "Notifications"
font.pixelSize: Metrics.fontSize(20)
font.bold: true
}
StyledSwitchOption {
title: "Enabled"
description: "Enable or disable built-in notification daemon."
prefField: "notifications.enabled"
}
StyledSwitchOption {
title: "Do not disturb enabled"
description: "Enable or disable dnd."
prefField: "notifications.doNotDisturb"
}
RowLayout {
ColumnLayout {
StyledText {
text: "Notification Position"
font.pixelSize: Metrics.fontSize(16)
}
StyledText {
text: "Select where notification will be shown."
font.pixelSize: Metrics.fontSize(12)
}
}
Item { Layout.fillWidth: true }
StyledDropDown {
id: notificationDropdown
label: "Position"
property var positions: ["Top Left", "Top Right", "Top"]
model: positions
currentIndex:
indexFromPosition(
Config.runtime.notifications.position,
positions
)
onSelectedIndexChanged: function(index) {
Config.updateKey(
"notifications.position",
positions[index].toLowerCase().replace(" ", "-")
)
}
}
}
RowLayout {
ColumnLayout {
StyledText {
text: "Test Notifications"
font.pixelSize: Metrics.fontSize(16)
}
StyledText {
text: "Run a test notification."
font.pixelSize: Metrics.fontSize(12)
}
}
Item { Layout.fillWidth: true }
StyledButton {
text: "Test"
icon: "chat"
onClicked:
Quickshell.execDetached([
"notify-send",
"Quickshell",
"This is a test notification"
])
}
}
}
ContentCard {
StyledText {
text: "Overlays / OSDs"
font.pixelSize: Metrics.fontSize(20)
font.bold: true
}
StyledSwitchOption {
title: "Enabled"
description: "Enable or disable built-in osd daemon."
prefField: "overlays.enabled"
}
StyledSwitchOption {
title: "Volume OSD enabled"
description: "Enable or disable volume osd."
prefField: "overlays.volumeOverlayEnabled"
}
StyledSwitchOption {
title: "Brightness OSD enabled"
description: "Enable or disable brightness osd."
prefField: "overlays.brightnessOverlayEnabled"
}
RowLayout {
ColumnLayout {
StyledText {
text: "Brightness OSD Position"
font.pixelSize: Metrics.fontSize(16)
}
StyledText {
text: "Choose where brightness OSD is shown."
font.pixelSize: Metrics.fontSize(12)
}
}
Item { Layout.fillWidth: true }
StyledDropDown {
property var positions:
["Top Left","Top Right","Bottom Left","Bottom Right","Top","Bottom"]
model: positions
currentIndex:
indexFromPosition(
Config.runtime.overlays.brightnessOverlayPosition,
positions
)
onSelectedIndexChanged: function(index) {
Config.updateKey(
"overlays.brightnessOverlayPosition",
positions[index].toLowerCase().replace(" ", "-")
)
}
}
}
RowLayout {
ColumnLayout {
StyledText {
text: "Volume OSD Position"
font.pixelSize: Metrics.fontSize(16)
}
StyledText {
text: "Choose where volume OSD is shown."
font.pixelSize: Metrics.fontSize(12)
}
}
Item { Layout.fillWidth: true }
StyledDropDown {
property var positions:
["Top Left","Top Right","Bottom Left","Bottom Right","Top","Bottom"]
model: positions
currentIndex:
indexFromPosition(
Config.runtime.overlays.volumeOverlayPosition,
positions
)
onSelectedIndexChanged: function(index) {
Config.updateKey(
"overlays.volumeOverlayPosition",
positions[index].toLowerCase().replace(" ", "-")
)
}
}
}
}
}

View File

@@ -0,0 +1,51 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Widgets
import qs.config
import qs.modules.components
import qs.plugins
ContentMenu {
title: "Plugins"
description: "Modify and Customize Installed Plugins."
ContentCard {
Layout.fillWidth: true
Layout.preferredHeight: implicitHeight
color: "transparent"
GridLayout {
id: grid
columns: 1
Layout.fillWidth: true
columnSpacing: Metrics.spacing(16)
rowSpacing: Metrics.spacing(16)
StyledText {
text: "Plugins not found!"
font.pixelSize: Metrics.fontSize(20)
font.bold: true
visible: PluginLoader.plugins.length === 0
Layout.alignment: Qt.AlignHCenter
}
Repeater {
model: PluginLoader.plugins
delegate: ContentCard {
Layout.fillWidth: true
Loader {
Layout.fillWidth: true
asynchronous: true
source: Qt.resolvedUrl(
Directories.shellConfig + "/plugins/" + modelData + "/Settings.qml"
)
}
}
}
}
}
}

View File

@@ -0,0 +1,201 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Window
import Quickshell
import Quickshell.Io
import Quickshell.Widgets
import qs.config
import qs.modules.functions
import qs.modules.components
import qs.services
Scope {
property var settingsWindow: null
IpcHandler {
function open(menu: string) {
Globals.states.settingsOpen = true;
if (menu !== "" && settingsWindow !== null) {
for (var i = 0; i < settingsWindow.menuModel.length; i++) {
var item = settingsWindow.menuModel[i];
if (!item.header && item.label.toLowerCase() === menu.toLowerCase()) {
settingsWindow.selectedIndex = item.page;
break;
}
}
}
}
target: "settings"
}
LazyLoader {
active: Globals.states.settingsOpen
Window {
id: root
width: 1280
height: 720
visible: true
title: "Nucleus - Settings"
color: Appearance.m3colors.m3background
onClosing: Globals.states.settingsOpen = false
Component.onCompleted: settingsWindow = root
property int selectedIndex: 0
property bool sidebarCollapsed: false
property var menuModel: [
{ "header": true, "label": "System" },
{ "icon": "bluetooth", "label": "Bluetooth", "page": 0 },
{ "icon": "network_wifi", "label": "Network", "page": 1 },
{ "icon": "volume_up", "label": "Audio", "page": 2 },
{ "icon": "instant_mix", "label": "Appearance", "page": 3 },
{ "header": true, "label": "Customization" },
{ "icon": "toolbar", "label": "Bar", "page": 4 },
{ "icon": "wallpaper", "label": "Wallpapers", "page": 5 },
{ "icon": "apps", "label": "Launcher", "page": 6 },
{ "icon": "chat", "label": "Notifications", "page": 7 },
{ "icon": "extension", "label": "Plugins", "page": 8 },
{ "icon": "apps", "label": "Store", "page": 9 },
{ "icon": "build", "label": "Miscellaneous", "page": 10 },
{ "header": true, "label": "About" },
{ "icon": "info", "label": "About", "page": 11 }
]
Item {
anchors.fill: parent
Rectangle {
id: sidebarBG
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
width: root.sidebarCollapsed ? 80 : 350
color: Appearance.m3colors.m3surfaceContainerLow
ColumnLayout {
anchors.fill: parent
anchors.margins: Metrics.margin(40)
spacing: Metrics.spacing(5)
RowLayout {
Layout.fillWidth: true
StyledText {
Layout.fillWidth: true
text: "Settings"
font.family: "Outfit ExtraBold"
font.pixelSize: Metrics.fontSize(28)
visible: !root.sidebarCollapsed
}
StyledButton {
Layout.preferredHeight: 40
icon: root.sidebarCollapsed ? "chevron_right" : "chevron_left"
secondary: true
onClicked: root.sidebarCollapsed = !root.sidebarCollapsed
}
}
ListView {
id: sidebarList
Layout.fillWidth: true
Layout.fillHeight: true
model: root.menuModel
spacing: Metrics.spacing(5)
clip: true
delegate: Item {
width: sidebarList.width
height: modelData.header ? (root.sidebarCollapsed ? 0 : 30) : 42
visible: !modelData.header || !root.sidebarCollapsed
// header
Item {
width: parent.width
height: parent.height
StyledText {
y: (parent.height - height) * 0.5
x: 10
text: modelData.label
font.pixelSize: Metrics.fontSize(14)
font.bold: true
opacity: modelData.header ? 1 : 0
}
}
Rectangle {
anchors.fill: parent
visible: !modelData.header
radius: Appearance.rounding.large
color: root.selectedIndex === modelData.page
? Appearance.m3colors.m3primary
: "transparent"
RowLayout {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 10
spacing: 10
MaterialSymbol {
visible: !modelData.header
icon: modelData.icon ? modelData.icon : ""
iconSize: Metrics.iconSize(24)
}
StyledText {
text: modelData.label
visible: !root.sidebarCollapsed
}
}
}
MouseArea {
anchors.fill: parent
enabled: modelData.page !== undefined
onClicked: {
root.selectedIndex = modelData.page
settingsStack.currentIndex = modelData.page
}
}
}
}
}
Behavior on width {
NumberAnimation { duration: 180; easing.type: Easing.InOutCubic }
}
}
StackLayout {
id: settingsStack
anchors.left: sidebarBG.right
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
currentIndex: root.selectedIndex
BluetoothConfig { Layout.fillWidth: true; Layout.fillHeight: true }
NetworkConfig { Layout.fillWidth: true; Layout.fillHeight: true }
AudioConfig { Layout.fillWidth: true; Layout.fillHeight: true }
AppearanceConfig { Layout.fillWidth: true; Layout.fillHeight: true }
BarConfig { Layout.fillWidth: true; Layout.fillHeight: true }
WallpaperConfig { Layout.fillWidth: true; Layout.fillHeight: true }
LauncherConfig { Layout.fillWidth: true; Layout.fillHeight: true }
NotificationConfig { Layout.fillWidth: true; Layout.fillHeight: true }
Plugins { Layout.fillWidth: true; Layout.fillHeight: true }
Store { Layout.fillWidth: true; Layout.fillHeight: true }
MiscConfig { Layout.fillWidth: true; Layout.fillHeight: true }
About { Layout.fillWidth: true; Layout.fillHeight: true }
}
}
}
}
}

View File

@@ -0,0 +1,104 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Widgets
import qs.config
import qs.modules.components
import qs.plugins
ContentMenu {
title: "Store"
description: "Manage plugins and other stuff for the shell."
ContentCard {
Layout.fillWidth: true
GridLayout {
columns: 1
Layout.fillWidth: true
columnSpacing: Metrics.spacing(16)
rowSpacing: Metrics.spacing(16)
Repeater {
model: PluginParser.model
delegate: StyledRect {
Layout.preferredHeight: 90
Layout.fillWidth: true
radius: Metrics.radius("small")
color: Appearance.m3colors.m3surfaceContainer
RowLayout {
anchors.fill: parent
anchors.margins: Metrics.margin("normal")
spacing: Metrics.spacing(12)
Column {
Layout.fillWidth: true
spacing: Metrics.spacing(2)
StyledText {
font.pixelSize: Metrics.fontSize("large")
text: name
}
RowLayout {
spacing: Metrics.spacing(6)
StyledText {
font.pixelSize: Metrics.fontSize("small")
text: author
color: Appearance.colors.colSubtext
}
StyledText {
font.pixelSize: Metrics.fontSize("small")
text: "| Requires Nucleus " + requires_nucleus
color: Appearance.colors.colSubtext
}
}
StyledText {
font.pixelSize: Metrics.fontSize("normal")
text: description
color: Appearance.colors.colSubtext
}
}
RowLayout {
spacing: Metrics.spacing(8)
StyledButton {
icon: "download"
text: "Install"
visible: !installed
secondary: true
Layout.preferredWidth: 140
onClicked: PluginParser.install(id)
}
StyledButton {
icon: "update"
text: "Update"
visible: installed
secondary: true
Layout.preferredWidth: 140
onClicked: PluginParser.update(id)
}
StyledButton {
icon: "delete"
text: "Remove"
visible: installed
secondary: true
Layout.preferredWidth: 140
onClicked: PluginParser.uninstall(id)
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,294 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Widgets
import qs.config
import qs.modules.components
import qs.services
ContentMenu {
property string displayName: root.screen?.name ?? ""
property var intervalOptions: [{
"value": 5,
"label": "5 minutes"
}, {
"value": 15,
"label": "15 minutes"
}, {
"value": 30,
"label": "30 minutes"
}, {
"value": 60,
"label": "1 hour"
}, {
"value": 120,
"label": "2 hours"
}, {
"value": 360,
"label": "6 hours"
}]
function getIntervalIndex(minutes) {
for (let i = 0; i < intervalOptions.length; i++) {
if (intervalOptions[i].value === minutes)
return i;
}
return 0;
}
title: "Wallpaper"
description: "Manage your wallpapers"
ContentCard {
ClippingRectangle {
id: wpContainer
Layout.alignment: Qt.AlignHCenter
width: root.screen.width / 2
height: width * root.screen.height / root.screen.width
radius: Metrics.radius("unsharpenmore")
color: Appearance.m3colors.m3surfaceContainer
StyledText {
text: "Current Wallpaper:"
font.pixelSize: Metrics.fontSize("big")
font.bold: true
}
ClippingRectangle {
id: wpPreview
Layout.alignment: Qt.AlignHCenter | Qt.AlignCenter
anchors.fill: parent
radius: Metrics.radius("unsharpenmore")
color: Appearance.m3colors.m3paddingContainer
layer.enabled: true
StyledText {
opacity: !Config.runtime.appearance.background.enabled ? 1 : 0
font.pixelSize: Metrics.fontSize("title")
text: "Wallpaper Manager Disabled"
anchors.centerIn: parent
Behavior on opacity {
enabled: Config.runtime.appearance.animations.enabled
Anim { }
}
}
Image {
opacity: Config.runtime.appearance.background.enabled ? 1 : 0
anchors.fill: parent
source: previewImg + "?t=" + Date.now()
property string previewImg: {
const displays = Config.runtime.monitors
const fallback = Config.runtime.appearance.background.defaultPath
if (!displays)
return fallback
const monitor = displays?.[displayName]
return monitor?.wallpaper ?? fallback
}
fillMode: Image.PreserveAspectCrop
cache: true
Behavior on opacity {
enabled: Config.runtime.appearance.animations.enabled
Anim { }
}
}
}
}
StyledButton {
icon: "wallpaper"
text: "Change Wallpaper"
Layout.fillWidth: true
onClicked: {
Quickshell.execDetached(["nucleus", "ipc", "call", "background", "change"]);
}
}
StyledSwitchOption {
title: "Enabled"
description: "Enabled or disable built-in wallpaper daemon."
prefField: "appearance.background.enabled"
}
}
ContentCard {
StyledText {
text: "Parallax Effect"
font.pixelSize: Metrics.fontSize("big")
font.bold: true
}
StyledSwitchOption {
title: "Enabled"
description: "Enabled or disable wallpaper parallax effect."
prefField: "appearance.background.parallax.enabled"
}
StyledSwitchOption {
title: "Enabled for Sidebar Left"
description: "Show parralax effect when sidebarLeft is opened."
prefField: "appearance.background.parallax.enableSidebarLeft"
}
StyledSwitchOption {
title: "Enabled for Sidebar Right"
description: "Show parralax effect when sidebarRight is opened."
prefField: "appearance.background.parallax.enableSidebarRight"
}
NumberStepper {
label: "Zoom Amount"
description: "Adjust the zoom of the parallax effect."
prefField: "appearance.background.parallax.zoom"
step: 0.1
minimum: 1.10
maximum: 2
}
}
ContentCard {
StyledText {
text: "Wallpaper Slideshow"
font.pixelSize: Metrics.fontSize("big")
font.bold: true
}
StyledSwitchOption {
title: "Enable Slideshow"
description: "Automatically rotate wallpapers from a folder."
prefField: "appearance.background.slideshow.enabled"
}
StyledSwitchOption {
title: "Include Subfolders"
description: "Also search for wallpapers in subfolders."
prefField: "appearance.background.slideshow.includeSubfolders"
}
ColumnLayout {
Layout.fillWidth: true
spacing: Metrics.spacing(8)
RowLayout {
Layout.fillWidth: true
spacing: Metrics.spacing(12)
ColumnLayout {
Layout.fillWidth: true
spacing: Metrics.spacing(4)
StyledText {
text: "Wallpaper Folder"
font.pixelSize: Metrics.fontSize("normal")
}
StyledText {
text: Config.runtime.appearance.background.slideshow.folder || "No folder selected"
font.pixelSize: Metrics.fontSize("small")
color: Appearance.m3colors.m3onSurfaceVariant
elide: Text.ElideMiddle
Layout.fillWidth: true
}
}
StyledButton {
icon: "folder_open"
text: "Browse"
onClicked: folderPickerProc.running = true
}
}
}
RowLayout {
id: skipWallpaper
property string title: "Skip To Next Wallpaper"
property string description: "Skip to the next wallpaper in the wallpaper directory."
property string prefField: ''
ColumnLayout {
StyledText {
text: skipWallpaper.title
font.pixelSize: Metrics.fontSize("normal")
}
StyledText {
text: skipWallpaper.description
font.pixelSize: Metrics.fontSize("small")
}
}
Item { Layout.fillWidth: true }
StyledButton {
icon: "skip_next"
text: "Skip Next"
enabled: WallpaperSlideshow.wallpapers.length > 0
onClicked: {
Quickshell.execDetached(["nucleus", "ipc", "call", "background", "next"]);
}
}
}
RowLayout {
Layout.fillWidth: true
spacing: Metrics.spacing(12)
ColumnLayout {
Layout.fillWidth: true
spacing: Metrics.spacing(4)
StyledText {
text: "Change Interval"
font.pixelSize: Metrics.fontSize("normal")
}
StyledText {
text: "How often to change the wallpaper."
font.pixelSize: Metrics.fontSize("small")
color: Appearance.m3colors.m3onSurfaceVariant
}
}
Item { Layout.fillWidth: true }
StyledDropDown {
label: "Interval"
model: intervalOptions.map((opt) => {
return opt.label;
})
currentIndex: getIntervalIndex(Config.runtime.appearance.background.slideshow.interval)
onSelectedIndexChanged: (index) => {
Config.updateKey("appearance.background.slideshow.interval", intervalOptions[index].value);
}
}
}
}
Process {
id: folderPickerProc
command: ["bash", Directories.scriptsPath + "/interface/selectfolder.sh", Config.runtime.appearance.background.slideshow.folder || Directories.pictures]
stdout: StdioCollector {
onStreamFinished: {
const out = text.trim();
if (out !== "null" && out.length > 0)
Config.updateKey("appearance.background.slideshow.folder", out);
}
}
}
component Anim: NumberAnimation {
duration: Metrics.chronoDuration(400)
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animation.curves.standard
}
}