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,525 @@
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import qs.config
import qs.modules.functions
import qs.modules.components
import qs.services
Item {
id: root
property bool initialChatSelected: false
function appendMessage(sender, message) {
messageModel.append({
"sender": sender,
"message": message
});
scrollToBottom();
}
function updateChatsList(files) {
let existing = {
};
for (let i = 0; i < chatListModel.count; i++) existing[chatListModel.get(i).name] = true
for (let file of files) {
let name = file.trim();
if (!name.length)
continue;
if (name.endsWith(".txt"))
name = name.slice(0, -4);
if (!existing[name])
chatListModel.append({
"name": name
});
delete existing[name];
}
// remove chats that no longer exist
for (let name in existing) {
for (let i = 0; i < chatListModel.count; i++) {
if (chatListModel.get(i).name === name) {
chatListModel.remove(i);
break;
}
}
}
// ensure default exists
let hasDefault = false;
for (let i = 0; i < chatListModel.count; i++) if (chatListModel.get(i).name === "default") {
hasDefault = true;
}
if (!hasDefault) {
chatListModel.insert(0, {
"name": "default"
});
FileUtils.createFile(FileUtils.trimFileProtocol(Directories.config) + "/zenith/chats/default.txt");
}
}
function scrollToBottom() {
chatView.positionViewAtEnd();
}
function sendMessage() {
if (userInput.text === "" || Zenith.loading)
return ;
Zenith.pendingInput = userInput.text;
appendMessage("You", userInput.text);
userInput.text = "";
Zenith.loading = true;
Zenith.send();
}
function loadChatHistory(chatName) {
messageModel.clear();
Zenith.loadChat(chatName);
}
function selectDefaultChat() {
let defaultIndex = -1;
for (let i = 0; i < chatListModel.count; i++) {
if (chatListModel.get(i).name === "default") {
defaultIndex = i;
break;
}
}
if (defaultIndex !== -1) {
chatSelector.currentIndex = defaultIndex;
Zenith.currentChat = "default";
loadChatHistory("default");
} else if (chatListModel.count > 0) {
chatSelector.currentIndex = 0;
Zenith.currentChat = chatListModel.get(0).name;
loadChatHistory(Zenith.currentChat);
}
}
ListModel {
// { sender: "You" | "AI", message: string }
id: messageModel
}
ListModel {
id: chatListModel
}
ColumnLayout {
spacing: Metrics.spacing(8)
anchors.centerIn: parent
StyledText {
visible: !Config.runtime.misc.intelligence.enabled
text: "Intelligence is disabled!"
Layout.leftMargin: Metrics.margin(24)
font.pixelSize: Appearance.font.size.huge
}
StyledText {
visible: !Config.runtime.misc.intelligence.enabled
text: "Go to the settings to enable intelligence"
}
}
StyledRect {
anchors.topMargin: Metrics.margin(74)
radius: Metrics.radius("normal")
anchors.fill: parent
color: "transparent"
visible: Config.runtime.misc.intelligence.enabled
ColumnLayout {
anchors.fill: parent
anchors.margins: Metrics.margin(16)
spacing: Metrics.spacing(10)
RowLayout {
Layout.fillWidth: true
spacing: Metrics.spacing(10)
StyledDropDown {
id: chatSelector
Layout.fillWidth: true
model: chatListModel
textRole: "name"
Layout.preferredHeight: 40
onCurrentIndexChanged: {
if (!initialChatSelected)
return ;
if (currentIndex < 0 || currentIndex >= chatListModel.count)
return ;
let chatName = chatListModel.get(currentIndex).name;
Zenith.currentChat = chatName;
loadChatHistory(chatName);
}
}
StyledButton {
icon: "add"
Layout.preferredWidth: 40
onClicked: {
let name = "new-chat-" + chatListModel.count;
let path = FileUtils.trimFileProtocol(Directories.config) + "/zenith/chats/" + name + ".txt";
FileUtils.createFile(path, function(success) {
if (success) {
chatListModel.append({
"name": name
});
chatSelector.currentIndex = chatListModel.count - 1;
Zenith.currentChat = name;
messageModel.clear();
}
});
}
}
StyledButton {
icon: "edit"
Layout.preferredWidth: 40
enabled: chatSelector.currentIndex >= 0
onClicked: renameDialog.open()
}
StyledButton {
icon: "delete"
Layout.preferredWidth: 40
enabled: chatSelector.currentIndex >= 0 && chatSelector.currentText !== "default"
onClicked: {
let name = chatSelector.currentText;
let path = FileUtils.trimFileProtocol(Directories.config) + "/zenith/chats/" + name + ".txt";
FileUtils.removeFile(path, function(success) {
if (success) {
chatListModel.remove(chatSelector.currentIndex);
selectDefaultChat();
}
});
}
}
}
RowLayout {
Layout.fillWidth: true
spacing: Metrics.spacing(10)
StyledDropDown {
id: modelSelector
Layout.fillWidth: true
model: ["openai/gpt-4o","openai/gpt-4","openai/gpt-3.5-turbo","openai/gpt-4o-mini","anthropic/claude-3.5-sonnet","anthropic/claude-3-haiku","meta-llama/llama-3.3-70b-instruct:free","deepseek/deepseek-r1-0528:free","qwen/qwen3-coder:free"]
currentIndex: 0
Layout.preferredHeight: 40
onCurrentTextChanged: Zenith.currentModel = currentText
}
StyledButton {
icon: "fullscreen"
Layout.preferredWidth: 40
onClicked: {
Quickshell.execDetached(["nucleus", "ipc", "call", "intelligence", "openWindow"]);
Globals.visiblility.sidebarLeft = false;
}
}
}
StyledRect {
Layout.fillWidth: true
Layout.fillHeight: true
radius: Metrics.radius("normal")
color: Appearance.m3colors.m3surfaceContainerLow
ScrollView {
anchors.fill: parent
clip: true
ListView {
id: chatView
model: messageModel
spacing: Metrics.spacing(8)
anchors.fill: parent
anchors.margins: Metrics.margin(12)
clip: true
delegate: Item {
property bool isCodeBlock: message.split("\n").length > 2 && message.includes("import ") // simple heuristic
width: chatView.width
height: bubble.implicitHeight + 6
Component.onCompleted: {
chatView.forceLayout();
}
Row {
width: parent.width
spacing: Metrics.spacing(8)
Item {
width: sender === "AI" ? 0 : parent.width * 0.2
}
StyledRect {
id: bubble
radius: Metrics.radius("normal")
color: sender === "You" ? Appearance.m3colors.m3primaryContainer : Appearance.m3colors.m3surfaceContainerHigh
implicitWidth: Math.min(textItem.implicitWidth + 20, chatView.width * 0.8)
implicitHeight: textItem.implicitHeight
anchors.right: sender === "You" ? parent.right : undefined
anchors.left: sender === "AI" ? parent.left : undefined
anchors.topMargin: Metrics.margin(2)
TextEdit {
id: textItem
text: StringUtils.markdownToHtml(message)
wrapMode: TextEdit.Wrap
textFormat: TextEdit.RichText
readOnly: true // make it selectable but not editable
font.pixelSize: Metrics.fontSize(16)
anchors.leftMargin: Metrics.margin(12)
color: Appearance.syntaxHighlightingTheme
padding: Metrics.padding(8)
anchors.fill: parent
}
MouseArea {
id: ma
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: {
let p = Qt.createQmlObject('import Quickshell; import Quickshell.Io; Process { command: ["wl-copy", "' + message + '"] }', parent);
p.running = true;
}
}
}
Item {
width: sender === "You" ? 0 : parent.width * 0.2
}
}
}
}
}
}
StyledRect {
Layout.fillWidth: true
height: 50
radius: Metrics.radius("normal")
color: Appearance.m3colors.m3surfaceContainer
RowLayout {
anchors.fill: parent
anchors.margins: 6
spacing: 10
StyledTextField {
// Shift+Enter → insert newline
// Enter → send message
id: userInput
Layout.fillWidth: true
placeholderText: "Type your message..."
font.pixelSize: Metrics.fontSize(14)
padding: Metrics.padding(8)
Keys.onPressed: {
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
if (event.modifiers & Qt.ShiftModifier)
insert("\n");
else
sendMessage();
event.accepted = true;
}
}
}
StyledButton {
text: "Send"
enabled: userInput.text.trim().length > 0 && !Zenith.loading
opacity: enabled ? 1 : 0.5
onClicked: sendMessage()
}
}
}
}
Dialog {
id: renameDialog
title: "Rename Chat"
modal: true
visible: false
standardButtons: Dialog.NoButton
x: (root.width - 360) / 2 // center horizontally
y: (root.height - 160) / 2 // center vertically
width: 360
height: 200
ColumnLayout {
anchors.fill: parent
anchors.margins: Metrics.margin(16)
spacing: Metrics.spacing(12)
StyledText {
text: "Enter a new name for the chat"
font.pixelSize: Metrics.fontSize(18)
horizontalAlignment: Text.AlignHCenter
Layout.fillWidth: true
}
StyledTextField {
id: renameInput
Layout.fillWidth: true
placeholderText: "New name"
filled: false
highlight: false
text: chatSelector.currentText
font.pixelSize: Metrics.iconSize(16)
Layout.preferredHeight: 45
padding: Metrics.padding(8)
}
RowLayout {
Layout.fillWidth: true
spacing: Metrics.spacing(12)
Layout.alignment: Qt.AlignRight
StyledButton {
text: "Cancel"
Layout.preferredWidth: 80
onClicked: renameDialog.close()
}
StyledButton {
text: "Rename"
Layout.preferredWidth: 100
enabled: renameInput.text.trim().length > 0 && renameInput.text !== chatSelector.currentText
onClicked: {
let oldName = chatSelector.currentText;
let newName = renameInput.text.trim();
let oldPath = FileUtils.trimFileProtocol(Directories.config) + "/zenith/chats/" + oldName + ".txt";
let newPath = FileUtils.trimFileProtocol(Directories.config) + "/zenith/chats/" + newName + ".txt";
FileUtils.renameFile(oldPath, newPath, function(success) {
if (success) {
chatListModel.set(chatSelector.currentIndex, {
"name": newName
});
Zenith.currentChat = newName;
renameDialog.close();
}
});
}
}
}
}
background: StyledRect {
color: Appearance.m3colors.m3surfaceContainer
radius: Metrics.radius("normal")
border.color: Appearance.colors.colOutline
border.width: 1
}
header: StyledRect {
color: Appearance.m3colors.m3surfaceContainer
radius: Metrics.radius("normal")
border.color: Appearance.colors.colOutline
border.width: 1
}
}
StyledText {
text: "Thinking…"
visible: Zenith.loading
color: Appearance.colors.colSubtext
font.pixelSize: Metrics.fontSize(14)
anchors {
left: parent.left
bottom: parent.bottom
leftMargin: Metrics.margin(22)
bottomMargin: Metrics.margin(76)
}
}
}
Connections {
function onChatsListed(text) {
let lines = text.split(/\r?\n/);
updateChatsList(lines);
// only auto-select once
if (!initialChatSelected) {
selectDefaultChat();
initialChatSelected = true;
}
}
function onAiReply(text) {
appendMessage("AI", text.slice(5));
Zenith.loading = false;
}
function onChatLoaded(text) {
let lines = text.split(/\r?\n/);
let batch = [];
for (let l of lines) {
let line = l.trim();
if (!line.length)
continue;
let u = line.match(/^\[\d{4}-.*\] User: (.*)$/);
let a = line.match(/^\[\d{4}-.*\] AI: (.*)$/);
if (u)
batch.push({
"sender": "You",
"message": u[1]
});
else if (a)
batch.push({
"sender": "AI",
"message": a[1]
});
else if (batch.length)
batch[batch.length - 1].message += "\n" + line;
}
messageModel.clear();
for (let m of batch) messageModel.append(m)
scrollToBottom();
}
target: Zenith
}
}

View File

@@ -0,0 +1,90 @@
import qs.config
import qs.modules.components
import qs.modules.functions
import qs.services
import QtQuick
import Quickshell
import QtQuick.Layouts
import Quickshell.Wayland
import Quickshell.Io
import Quickshell.Hyprland
import QtQuick.Controls
import Qt5Compat.GraphicalEffects
PanelWindow {
id: sidebarLeft
WlrLayershell.namespace: "nucleus:sidebarLeft"
WlrLayershell.layer: WlrLayer.Top
visible: Config.initialized && Globals.visiblility.sidebarLeft && !Globals.visiblility.sidebarRight
color: "transparent"
exclusiveZone: 0
WlrLayershell.keyboardFocus: Compositor.require("hyprland") && Globals.visiblility.sidebarLeft
property real sidebarLeftWidth: 500
implicitWidth: Compositor.screenW
HyprlandFocusGrab {
id: grab
active: Compositor.require("hyprland")
windows: [sidebarLeft]
}
anchors {
top: true
left: (Config.runtime.bar.position === "top" || Config.runtime.bar.position === "bottom" || Config.runtime.bar.position === "left")
bottom: true
right: (Config.runtime.bar.position === "right")
}
margins {
top: Config.runtime.bar.margins
bottom: Config.runtime.bar.margins
left: Metrics.margin("small")
right: Metrics.margin("small")
}
MouseArea {
anchors.fill: parent
z: 0
onPressed: Globals.visiblility.sidebarLeft = false
}
StyledRect {
id: container
z: 1
color: Appearance.m3colors.m3background
radius: Metrics.radius("large")
width: sidebarLeft.sidebarLeftWidth
anchors {
top: parent.top
bottom: parent.bottom
left: parent.left
}
FocusScope {
focus: true
anchors.fill: parent
Keys.onPressed: {
if (event.key === Qt.Key_Escape) {
Globals.visiblility.sidebarLeft = false;
}
}
SidebarLeftContent {}
}
}
function togglesidebarLeft() {
Globals.visiblility.sidebarLeft = !Globals.visiblility.sidebarLeft;
}
IpcHandler {
target: "sidebarLeft"
function toggle() {
togglesidebarLeft();
}
}
}

View File

@@ -0,0 +1,142 @@
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import qs.config
import qs.modules.functions
import qs.modules.components
import qs.services
Item {
anchors.fill: parent
SwipeView {
id: view
anchors.fill: parent
// IntelligencePanel {}
SystemOverview {}
// WallpapersPage {}
}
Rectangle {
height: 2
width: parent.width - Metrics.margin("verylarge")
color: Appearance.m3colors.m3outlineVariant
opacity: 0.6
anchors {
top: view.top
topMargin: segmentedIndicator.height + Metrics.margin("verysmall")
horizontalCenter: view.horizontalCenter
}
}
Rectangle {
id: activeTabIndicator
height: 2
width: 96
radius: Metrics.radius(1)
color: Appearance.m3colors.m3primary
x: (segmentedIndicator.width / view.count) * view.currentIndex + (segmentedIndicator.width / view.count - width) / 2
anchors {
top: segmentedIndicator.bottom
topMargin: Metrics.margin(8)
}
Behavior on x {
NumberAnimation {
duration: Metrics.chronoDuration(220)
easing.type: Easing.OutCubic
}
}
}
Item {
id: segmentedIndicator
height: 56
width: parent.width
anchors {
top: parent.top
horizontalCenter: parent.horizontalCenter
}
Row {
anchors.fill: parent
spacing: 0
Repeater {
model: [
// {
// "icon": "neurology",
// "text": "Intelligence"
// },
{
"icon": "overview",
"text": "Overview"
},
// {
// "icon": "wallpaper",
// "text": "Wallpapers"
// }
]
Item {
width: segmentedIndicator.width / view.count
height: parent.height
MouseArea {
anchors.fill: parent
onClicked: view.currentIndex = index
}
// Icon (true center)
MaterialSymbol {
icon: modelData.icon
iconSize: Metrics.iconSize("huge")
color: view.currentIndex === index ? Appearance.m3colors.m3primary : Appearance.m3colors.m3onSurfaceVariant
anchors {
horizontalCenter: parent.horizontalCenter
top: parent.top
topMargin: Metrics.margin(12)
}
}
// Label (independent centering)
StyledText {
text: modelData.text
font.pixelSize: Metrics.fontSize("large")
font.weight: Font.Medium
color: view.currentIndex === index ? Appearance.m3colors.m3primary : Appearance.m3colors.m3onSurfaceVariant
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
bottomMargin: 0
}
}
}
}
}
}
}

View File

@@ -0,0 +1,486 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import qs.modules.functions
import qs.services
import qs.config
import qs.modules.components
Item {
id: root
implicitWidth: 300
implicitHeight: parent ? parent.height : 500
ColumnLayout {
anchors.topMargin: Metrics.margin(90)
anchors.fill: parent
anchors.margins: Metrics.margin("normal")
spacing: Metrics.margin("small")
// Header
RowLayout {
Layout.fillWidth: true
RowLayout {
spacing: Metrics.margin("normal")
StyledText {
text: SystemDetails.osIcon
font.family: Metrics.fontFamily("nerdIcons")
font.pixelSize: Metrics.fontSize(48)
color: Appearance.colors.colPrimary
}
ColumnLayout {
spacing: Metrics.spacing(2)
StyledText {
text: SystemDetails.osName
font.pixelSize: Metrics.fontSize("large")
color: Appearance.m3colors.m3onSurface
}
StyledText {
text: `${SystemDetails.username}@${SystemDetails.hostname}`
font.pixelSize: Metrics.fontSize("small")
color: Appearance.colors.colSubtext
}
}
}
Item {
Layout.fillWidth: true
}
ColumnLayout {
spacing: Metrics.spacing(2)
Layout.alignment: Qt.AlignRight
StyledText {
text: `qs ${SystemDetails.qsVersion}`
font.pixelSize: Metrics.fontSize("small")
color: Appearance.colors.colSubtext
}
StyledText {
text: `nucleus-shell v${Config.runtime.shell.version}`
font.pixelSize: Metrics.fontSize("smaller")
color: Appearance.colors.colSubtext
}
}
}
Rectangle {
Layout.fillWidth: true
implicitHeight: 56
radius: Metrics.radius("normal")
color: Appearance.colors.colLayer2
RowLayout {
anchors.fill: parent
anchors.margins: Metrics.margin("small")
StyledText {
text: "Uptime"
font.pixelSize: Metrics.fontSize("normal")
color: Appearance.colors.colPrimary
}
Item {
Layout.fillWidth: true
}
StyledText {
text: SystemDetails.uptime
font.pixelSize: Metrics.fontSize("small")
color: Appearance.m3colors.m3onSurface
}
}
}
Rectangle {
Layout.fillWidth: true
implicitHeight: 56
radius: Metrics.radius("normal")
color: Appearance.colors.colLayer2
RowLayout {
anchors.fill: parent
anchors.margins: Metrics.margin("small")
StyledText {
text: "Operating System"
font.pixelSize: Metrics.fontSize("normal")
color: Appearance.colors.colPrimary
}
Item {
Layout.fillWidth: true
}
StyledText {
text: SystemDetails.osName
font.pixelSize: Metrics.fontSize("small")
color: Appearance.m3colors.m3onSurface
elide: Text.ElideRight
}
}
}
Rectangle {
Layout.fillWidth: true
implicitHeight: 320
radius: Metrics.radius("large")
color: Appearance.colors.colLayer2
ColumnLayout {
anchors.fill: parent
anchors.margins: Metrics.margin("large")
spacing: Metrics.margin("normal")
ColumnLayout {
spacing: Metrics.spacing(6)
RowLayout {
StyledText {
text: "CPU Usage"
color: Appearance.colors.colSubtext
}
Item {
Layout.fillWidth: true
}
StyledText {
animate: false
text: SystemDetails.cpuLoad
color: Appearance.colors.colSubtext
}
}
Rectangle {
Layout.fillWidth: true
height: 10
radius: Metrics.radius(5)
color: Appearance.colors.colLayer1
Rectangle {
width: parent.width * SystemDetails.cpuPercent
height: parent.height
radius: Metrics.radius(5)
color: Appearance.colors.colPrimary
}
}
}
ColumnLayout {
spacing: Metrics.spacing(6)
RowLayout {
StyledText {
text: "Ram Usage"
color: Appearance.colors.colSubtext
}
Item {
Layout.fillWidth: true
}
StyledText {
animate: false
text: SystemDetails.ramUsage
color: Appearance.colors.colSubtext
}
}
Rectangle {
Layout.fillWidth: true
height: 10
radius: Metrics.radius(5)
color: Appearance.colors.colLayer1
Rectangle {
width: parent.width * SystemDetails.ramPercent
height: parent.height
radius: Metrics.radius(5)
color: Appearance.colors.colPrimary
}
}
}
ColumnLayout {
spacing: Metrics.spacing(6)
RowLayout {
StyledText {
text: "Disk Usage"
color: Appearance.colors.colSubtext
}
Item {
Layout.fillWidth: true
}
StyledText {
animate: false
text: SystemDetails.diskUsage
color: Appearance.colors.colSubtext
}
}
Rectangle {
Layout.fillWidth: true
height: 10
radius: Metrics.radius(5)
color: Appearance.colors.colLayer1
Rectangle {
width: parent.width * SystemDetails.diskPercent
height: parent.height
radius: Metrics.radius(5)
color: Appearance.colors.colPrimary
}
}
}
ColumnLayout {
spacing: Metrics.spacing(6)
RowLayout {
StyledText {
text: "Swap Usage"
color: Appearance.colors.colSubtext
}
Item {
Layout.fillWidth: true
}
StyledText {
text: SystemDetails.swapUsage
color: Appearance.colors.colSubtext
}
}
Rectangle {
Layout.fillWidth: true
height: 10
radius: Metrics.radius(5)
color: Appearance.colors.colLayer1
Rectangle {
width: parent.width * SystemDetails.swapPercent
height: parent.height
radius: Metrics.radius(5)
color: Appearance.colors.colPrimary
}
}
}
}
}
Rectangle {
Layout.fillWidth: true
implicitHeight: 72
radius: Metrics.radius("normal")
color: Appearance.colors.colLayer2
RowLayout {
anchors.fill: parent
anchors.margins: Metrics.margin("small")
spacing: Metrics.margin("large")
ColumnLayout {
spacing: Metrics.spacing(2)
StyledText {
text: "Kernel"
font.pixelSize: Metrics.fontSize("small")
color: Appearance.colors.colSubtext
}
StyledText {
text: SystemDetails.kernelVersion
font.pixelSize: Metrics.fontSize("small")
color: Appearance.m3colors.m3onSurface
}
}
Item {
Layout.fillWidth: true
}
ColumnLayout {
spacing: Metrics.spacing(2)
Layout.alignment: Qt.AlignRight
StyledText {
text: "Architecture"
font.pixelSize: Metrics.fontSize("small")
color: Appearance.colors.colSubtext
horizontalAlignment: Text.AlignRight
}
StyledText {
text: SystemDetails.architecture
font.pixelSize: Metrics.fontSize("small")
color: Appearance.m3colors.m3onSurface
horizontalAlignment: Text.AlignRight
}
}
}
}
Rectangle {
Layout.fillWidth: true
implicitHeight: 72
radius: Metrics.radius("normal")
color: Appearance.colors.colLayer2
visible: UPower.batteryPresent
RowLayout {
anchors.fill: parent
anchors.margins: Metrics.margin("small")
spacing: Metrics.margin("large")
ColumnLayout {
spacing: Metrics.spacing(2)
StyledText {
text: "Battery"
font.pixelSize: Metrics.fontSize("small")
color: Appearance.colors.colSubtext
}
StyledText {
text: `${Math.round(UPower.percentage)}%`
font.pixelSize: Metrics.fontSize("small")
color: Appearance.m3colors.m3onSurface
}
}
Item {
Layout.fillWidth: true
}
ColumnLayout {
spacing: Metrics.spacing(2)
Layout.alignment: Qt.AlignRight
StyledText {
text: "AC"
font.pixelSize: Metrics.fontSize("small")
color: Appearance.colors.colSubtext
horizontalAlignment: Text.AlignRight
}
StyledText {
text: UPower.acOnline ? "online" : "battery"
font.pixelSize: Metrics.fontSize("small")
color: Appearance.m3colors.m3onSurface
horizontalAlignment: Text.AlignRight
}
}
}
}
Rectangle {
Layout.fillWidth: true
implicitHeight: 56
radius: Metrics.radius("normal")
color: Appearance.colors.colLayer2
RowLayout {
anchors.fill: parent
anchors.margins: Metrics.margin("small")
StyledText {
text: "Running Processes"
font.pixelSize: Metrics.fontSize("normal")
color: Appearance.colors.colPrimary
}
Item {
Layout.fillWidth: true
}
StyledText {
text: SystemDetails.runningProcesses
font.pixelSize: Metrics.fontSize("small")
color: Appearance.m3colors.m3onSurface
}
}
}
Rectangle {
Layout.fillWidth: true
implicitHeight: 56
radius: Metrics.radius("normal")
color: Appearance.colors.colLayer2
RowLayout {
anchors.fill: parent
anchors.margins: Metrics.margin("small")
StyledText {
text: "Logged-in Users"
font.pixelSize: Metrics.fontSize("normal")
color: Appearance.colors.colPrimary
}
Item {
Layout.fillWidth: true
}
StyledText {
text: SystemDetails.loggedInUsers
font.pixelSize: Metrics.fontSize("small")
color: Appearance.m3colors.m3onSurface
}
}
}
Item {
Layout.fillHeight: true
}
}
}

View File

@@ -0,0 +1,114 @@
import Qt.labs.folderlistmodel
import Qt5Compat.GraphicalEffects
import QtCore
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import qs.modules.functions
import qs.services
import qs.config
import qs.modules.components
Item {
id: wallpapersPage
property string displayName: screen?.name ?? ""
FolderListModel {
id: wallpaperModel
folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation) + "/Wallpapers"
nameFilters: ["*.png", "*.jpg", "*.jpeg", "*.webp", "mp4", "mkv", "webm", "avi", "mov", "flv", "wmv", "m4v"]
showDirs: false
showDotAndDotDot: false
}
// EMPTY STATE
StyledText {
visible: wallpaperModel.count === 0
text: "Put some wallpapers in\n~/Pictures/Wallpapers"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: Appearance.m3colors.m3onSurfaceVariant
font.pixelSize: Appearance.font.size.large
anchors.centerIn: parent
}
// WALLPAPER LIST
ListView {
anchors.topMargin: 90
visible: wallpaperModel.count > 0
anchors.fill: parent
model: wallpaperModel
spacing: Appearance.margin.normal
clip: true
delegate: Item {
width: ListView.view.width
height: 240
StyledRect {
id: imgContainer
property bool activeWallpaper:
Config.runtime.monitors?.[wallpapersPage.displayName]?.wallpaper === fileUrl
anchors.fill: parent
anchors.leftMargin: 20
anchors.rightMargin: 20
radius: Appearance.rounding.normal
color: activeWallpaper
? Appearance.m3colors.m3secondaryContainer
: Appearance.m3colors.m3surfaceContainerLow
layer.enabled: true
Image {
id: wallImg
anchors.fill: parent
source: fileUrl
fillMode: Image.PreserveAspectCrop
asynchronous: true
cache: true
clip: true
}
StyledText {
anchors.centerIn: parent
text: "Unsupported / Corrupted Image"
visible: wallImg.status === Image.Error
}
MouseArea {
anchors.fill: parent
onClicked: {
Config.updateKey(
"monitors." + wallpapersPage.displayName + ".wallpaper",
fileUrl
);
if (Config.runtime.appearance.colors.autogenerated) {
Quickshell.execDetached([
"nucleus", "ipc", "call", "global", "regenColors"
]);
}
}
cursorShape: Qt.PointingHandCursor
}
layer.effect: OpacityMask {
maskSource: Rectangle {
width: imgContainer.width
height: imgContainer.height
radius: imgContainer.radius
}
}
}
}
}
}