1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-24 21:42:51 -05:00

meta: integrate wallpaper, FileBrowser, StateLayer

- A lot of this is implements patterns implemented by soramannew's
  caelestia-shell
This commit is contained in:
bbedward
2025-07-23 23:20:11 -04:00
parent a0735db7a4
commit ee2cbd708d
33 changed files with 1494 additions and 915 deletions

7
.gitignore vendored
View File

@@ -56,4 +56,9 @@ UNUSED
.qmlls.ini .qmlls.ini
CLAUDE-activeContext.md CLAUDE-activeContext.md
CLAUDE-temp.md CLAUDE-temp.md
# Auto-generated theme files
*.generated.*
niri-colors.generated.kdl
ghostty-colors.generated.conf

65
Common/Appearance.qml Normal file
View File

@@ -0,0 +1,65 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
Singleton {
id: root
readonly property Rounding rounding: Rounding {}
readonly property Spacing spacing: Spacing {}
readonly property FontSize fontSize: FontSize {}
readonly property Anim anim: Anim {}
component Rounding: QtObject {
readonly property int small: 8
readonly property int normal: 12
readonly property int large: 16
readonly property int extraLarge: 24
readonly property int full: 1000
}
component Spacing: QtObject {
readonly property int small: 4
readonly property int normal: 8
readonly property int large: 12
readonly property int extraLarge: 16
readonly property int huge: 24
}
component FontSize: QtObject {
readonly property int small: 12
readonly property int normal: 14
readonly property int large: 16
readonly property int extraLarge: 20
readonly property int huge: 24
}
component AnimCurves: QtObject {
readonly property list<real> standard: [0.2, 0, 0, 1, 1, 1]
readonly property list<real> standardAccel: [0.3, 0, 1, 1, 1, 1]
readonly property list<real> standardDecel: [0, 0, 0, 1, 1, 1]
readonly property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1]
readonly property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1]
readonly property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1]
readonly property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.9, 1, 1]
readonly property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1, 1, 1]
readonly property list<real> expressiveEffects: [0.34, 0.8, 0.34, 1, 1, 1]
}
component AnimDurations: QtObject {
readonly property int quick: 150
readonly property int normal: 300
readonly property int slow: 500
readonly property int extraSlow: 1000
readonly property int expressiveFastSpatial: 350
readonly property int expressiveDefaultSpatial: 500
readonly property int expressiveEffects: 200
}
component Anim: QtObject {
readonly property AnimCurves curves: AnimCurves {}
readonly property AnimDurations durations: AnimDurations {}
}
}

View File

@@ -6,6 +6,7 @@ import QtQuick
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import qs.Services import qs.Services
import qs.Common
Singleton { Singleton {
@@ -13,7 +14,7 @@ Singleton {
readonly property string _homeUrl: StandardPaths.writableLocation(StandardPaths.HomeLocation) readonly property string _homeUrl: StandardPaths.writableLocation(StandardPaths.HomeLocation)
readonly property string homeDir: _homeUrl.startsWith("file://") ? _homeUrl.substring(7) : _homeUrl readonly property string homeDir: _homeUrl.startsWith("file://") ? _homeUrl.substring(7) : _homeUrl
readonly property string wallpaperPath: homeDir + "/quickshell/current_wallpaper" readonly property string wallpaperPath: Prefs.wallpaperPath || homeDir + "/quickshell/current_wallpaper"
readonly property string notifyPath: homeDir + "/quickshell/wallpaper_changed" readonly property string notifyPath: homeDir + "/quickshell/wallpaper_changed"
property bool matugenAvailable: false property bool matugenAvailable: false
property string matugenJson: "" property string matugenJson: ""
@@ -141,6 +142,7 @@ Singleton {
root.matugenJson = out; root.matugenJson = out;
root.matugenColors = JSON.parse(out); root.matugenColors = JSON.parse(out);
root.colorsUpdated(); root.colorsUpdated();
generateAppConfigs();
ToastService.clearWallpaperError(); ToastService.clearWallpaperError();
} catch (e) { } catch (e) {
console.error("JSON parse failed:", e); console.error("JSON parse failed:", e);
@@ -156,4 +158,113 @@ Singleton {
} }
function generateAppConfigs() {
if (!matugenColors || !matugenColors.colors) {
console.warn("No matugen colors available for app config generation");
return;
}
generateNiriConfig();
generateGhosttyConfig();
}
function generateNiriConfig() {
var dark = matugenColors.colors.dark;
if (!dark) return;
var bg = dark.background || "#1a1c1e";
var primary = dark.primary || "#42a5f5";
var secondary = dark.secondary || "#8ab4f8";
var inverse = dark.inverse_primary || "#6200ea";
var content = `// AUTO-GENERATED on ${new Date().toISOString()}
layout {
border {
active-color "${primary}"
inactive-color "${secondary}"
}
focus-ring {
active-color "${inverse}"
}
background-color "${bg}"
}`;
niriConfigWriter.command = ["bash", "-c", `echo '${content}' > niri-colors.generated.kdl`];
niriConfigWriter.running = true;
}
function generateGhosttyConfig() {
var dark = matugenColors.colors.dark;
var light = matugenColors.colors.light;
if (!dark || !light) return;
var bg = dark.background || "#1a1c1e";
var fg = dark.on_background || "#e3e8ef";
var primary = dark.primary || "#42a5f5";
var secondary = dark.secondary || "#8ab4f8";
var tertiary = dark.tertiary || "#bb86fc";
var tertiary_ctr = dark.tertiary_container || "#3700b3";
var error = dark.error || "#cf6679";
var inverse = dark.inverse_primary || "#6200ea";
var bg_b = light.background || "#fef7ff";
var fg_b = light.on_background || "#1d1b20";
var primary_b = light.primary || "#1976d2";
var secondary_b = light.secondary || "#1565c0";
var tertiary_b = light.tertiary || "#7b1fa2";
var tertiary_ctr_b = light.tertiary_container || "#e1bee7";
var error_b = light.error || "#b00020";
var inverse_b = light.inverse_primary || "#bb86fc";
var content = `# AUTO-GENERATED on ${new Date().toISOString()}
background = ${bg}
foreground = ${fg}
cursor-color = ${inverse}
selection-background = ${secondary}
selection-foreground = #ffffff
palette = 0=${bg}
palette = 1=${error}
palette = 2=${tertiary}
palette = 3=${secondary}
palette = 4=${primary}
palette = 5=${tertiary_ctr}
palette = 6=${inverse}
palette = 7=${fg}
palette = 8=${bg_b}
palette = 9=${error_b}
palette = 10=${tertiary_b}
palette = 11=${secondary_b}
palette = 12=${primary_b}
palette = 13=${tertiary_ctr_b}
palette = 14=${inverse_b}
palette = 15=${fg_b}`;
ghosttyConfigWriter.command = ["bash", "-c", `echo '${content}' > ghostty-colors.generated.conf`];
ghosttyConfigWriter.running = true;
}
Process {
id: niriConfigWriter
running: false
onExited: (exitCode) => {
if (exitCode === 0) {
console.log("Generated niri-colors.generated.kdl");
} else {
console.warn("Failed to generate niri config, exit code:", exitCode);
}
}
}
Process {
id: ghosttyConfigWriter
running: false
onExited: (exitCode) => {
if (exitCode === 0) {
console.log("Generated ghostty-colors.generated.conf");
} else {
console.warn("Failed to generate ghostty config, exit code:", exitCode);
}
}
}
} }

View File

@@ -40,6 +40,11 @@ Singleton {
property string osLogoColorOverride: "" property string osLogoColorOverride: ""
property real osLogoBrightness: 0.5 property real osLogoBrightness: 0.5
property real osLogoContrast: 1.0 property real osLogoContrast: 1.0
property string wallpaperPath: ""
property string wallpaperDirectory: StandardPaths.writableLocation(StandardPaths.PicturesLocation) + "/Wallpapers"
property bool wallpaperDynamicTheming: true
property string wallpaperLastPath: ""
property string profileLastPath: ""
function loadSettings() { function loadSettings() {
parseSettings(settingsFile.text()); parseSettings(settingsFile.text());
@@ -77,6 +82,11 @@ Singleton {
osLogoColorOverride = settings.osLogoColorOverride !== undefined ? settings.osLogoColorOverride : ""; osLogoColorOverride = settings.osLogoColorOverride !== undefined ? settings.osLogoColorOverride : "";
osLogoBrightness = settings.osLogoBrightness !== undefined ? settings.osLogoBrightness : 0.5; osLogoBrightness = settings.osLogoBrightness !== undefined ? settings.osLogoBrightness : 0.5;
osLogoContrast = settings.osLogoContrast !== undefined ? settings.osLogoContrast : 1.0; osLogoContrast = settings.osLogoContrast !== undefined ? settings.osLogoContrast : 1.0;
wallpaperPath = settings.wallpaperPath !== undefined ? settings.wallpaperPath : "";
wallpaperDirectory = settings.wallpaperDirectory !== undefined ? settings.wallpaperDirectory : StandardPaths.writableLocation(StandardPaths.PicturesLocation) + "/Wallpapers";
wallpaperDynamicTheming = settings.wallpaperDynamicTheming !== undefined ? settings.wallpaperDynamicTheming : true;
wallpaperLastPath = settings.wallpaperLastPath !== undefined ? settings.wallpaperLastPath : "";
profileLastPath = settings.profileLastPath !== undefined ? settings.profileLastPath : "";
applyStoredTheme(); applyStoredTheme();
detectAvailableIconThemes(); detectAvailableIconThemes();
updateGtkIconTheme(iconTheme); updateGtkIconTheme(iconTheme);
@@ -118,7 +128,12 @@ Singleton {
"useOSLogo": useOSLogo, "useOSLogo": useOSLogo,
"osLogoColorOverride": osLogoColorOverride, "osLogoColorOverride": osLogoColorOverride,
"osLogoBrightness": osLogoBrightness, "osLogoBrightness": osLogoBrightness,
"osLogoContrast": osLogoContrast "osLogoContrast": osLogoContrast,
"wallpaperPath": wallpaperPath,
"wallpaperDirectory": wallpaperDirectory,
"wallpaperDynamicTheming": wallpaperDynamicTheming,
"wallpaperLastPath": wallpaperLastPath,
"profileLastPath": profileLastPath
}, null, 2)); }, null, 2));
} }
@@ -418,6 +433,43 @@ gtk-application-prefer-dark-theme=true`;
saveSettings(); saveSettings();
} }
function setWallpaperPath(path) {
wallpaperPath = path;
saveSettings();
// Trigger dynamic theming if enabled
if (wallpaperDynamicTheming && path && typeof Theme !== "undefined") {
console.log("Wallpaper changed, triggering dynamic theme extraction");
Theme.switchTheme(themeIndex, true, true);
}
}
function setWallpaperDirectory(directory) {
wallpaperDirectory = directory;
saveSettings();
}
function setWallpaperDynamicTheming(enabled) {
wallpaperDynamicTheming = enabled;
saveSettings();
// If enabled and we have a wallpaper, trigger dynamic theming
if (enabled && wallpaperPath && typeof Theme !== "undefined") {
console.log("Dynamic theming enabled, triggering theme extraction");
Theme.switchTheme(themeIndex, true, true);
}
}
function setWallpaper(imagePath) {
wallpaperPath = imagePath;
saveSettings();
// Trigger color extraction if dynamic theming is enabled
if (wallpaperDynamicTheming && typeof Colors !== "undefined") {
Colors.extractColors();
}
}
Component.onCompleted: loadSettings() Component.onCompleted: loadSettings()
onShowSystemResourcesChanged: { onShowSystemResourcesChanged: {
if (typeof SystemMonitorService !== 'undefined') if (typeof SystemMonitorService !== 'undefined')
@@ -535,4 +587,5 @@ gtk-application-prefer-dark-theme=true`;
} }
} }
} }

View File

@@ -602,19 +602,5 @@ Singleton {
console.log("Theme initialized, waiting for Prefs to load settings and apply theme"); console.log("Theme initialized, waiting for Prefs to load settings and apply theme");
} }
// Wallpaper IPC handler
IpcHandler {
function refresh() {
console.log("Wallpaper IPC: refresh() called");
// Trigger color extraction if using dynamic theme
if (typeof Theme !== "undefined" && Theme.isDynamicTheme) {
console.log("Triggering color extraction due to wallpaper IPC");
Colors.extractColors();
}
return "WALLPAPER_REFRESH_SUCCESS";
}
target: "wallpaper"
}
} }

View File

@@ -143,7 +143,7 @@ DankModal {
width: parent.width - Theme.spacingM * 2 width: parent.width - Theme.spacingM * 2
spacing: Theme.spacingM spacing: Theme.spacingM
Text { StyledText {
text: "Clear All History?" text: "Clear All History?"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
@@ -151,7 +151,7 @@ DankModal {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
Text { StyledText {
text: "This will permanently delete all clipboard history." text: "This will permanently delete all clipboard history."
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
@@ -171,7 +171,7 @@ DankModal {
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: cancelClearButton.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3) color: cancelClearButton.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
Text { StyledText {
text: "Cancel" text: "Cancel"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
@@ -196,7 +196,7 @@ DankModal {
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: confirmClearButton.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.9) : Theme.error color: confirmClearButton.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.9) : Theme.error
Text { StyledText {
text: "Clear All" text: "Clear All"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.primaryText color: Theme.primaryText
@@ -353,7 +353,7 @@ DankModal {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Text { StyledText {
text: `Clipboard History (${totalCount})` text: `Clipboard History (${totalCount})`
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
@@ -439,7 +439,7 @@ DankModal {
model: filteredClipboardModel model: filteredClipboardModel
spacing: Theme.spacingXS spacing: Theme.spacingXS
Text { StyledText {
text: "No clipboard entries found" text: "No clipboard entries found"
anchors.centerIn: parent anchors.centerIn: parent
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -473,7 +473,7 @@ DankModal {
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
Text { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
text: entryIndex.toString() text: entryIndex.toString()
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -509,7 +509,7 @@ DankModal {
width: parent.width - Theme.iconSize - Theme.spacingM width: parent.width - Theme.iconSize - Theme.spacingM
spacing: Theme.spacingXS spacing: Theme.spacingXS
Text { StyledText {
text: { text: {
switch (entryType) { switch (entryType) {
case "image": case "image":
@@ -527,7 +527,7 @@ DankModal {
elide: Text.ElideRight elide: Text.ElideRight
} }
Text { StyledText {
id: contentText id: contentText
text: entryPreview text: entryPreview

View File

@@ -61,14 +61,14 @@ DankModal {
width: parent.width - 40 width: parent.width - 40
spacing: Theme.spacingXS spacing: Theme.spacingXS
Text { StyledText {
text: "Network Information" text: "Network Information"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
Text { StyledText {
text: "Details for \"" + networkSSID + "\"" text: "Details for \"" + networkSSID + "\""
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
@@ -112,7 +112,7 @@ DankModal {
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 1 border.width: 1
Text { StyledText {
id: detailsText id: detailsText
anchors.fill: parent anchors.fill: parent
@@ -143,7 +143,7 @@ DankModal {
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: closeArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary color: closeArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
Text { StyledText {
id: closeText id: closeText
anchors.centerIn: parent anchors.centerIn: parent

View File

@@ -66,7 +66,7 @@ DankModal {
spacing: Theme.spacingM spacing: Theme.spacingM
// Title // Title
Text { StyledText {
text: powerConfirmTitle text: powerConfirmTitle
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: { color: {
@@ -85,7 +85,7 @@ DankModal {
} }
// Message // Message
Text { StyledText {
text: powerConfirmMessage text: powerConfirmMessage
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
@@ -110,7 +110,7 @@ DankModal {
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: cancelButton.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3) color: cancelButton.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
Text { StyledText {
text: "Cancel" text: "Cancel"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
@@ -152,7 +152,7 @@ DankModal {
return confirmButton.containsMouse ? Qt.rgba(baseColor.r, baseColor.g, baseColor.b, 0.9) : baseColor; return confirmButton.containsMouse ? Qt.rgba(baseColor.r, baseColor.g, baseColor.b, 0.9) : baseColor;
} }
Text { StyledText {
text: "Confirm" text: "Confirm"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.primaryText color: Theme.primaryText

View File

@@ -85,7 +85,7 @@ DankModal {
height: 40 height: 40
spacing: Theme.spacingM spacing: Theme.spacingM
Text { StyledText {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: "System Monitor" text: "System Monitor"
font.pixelSize: Theme.fontSizeLarge + 4 font.pixelSize: Theme.fontSizeLarge + 4
@@ -98,7 +98,7 @@ DankModal {
height: 1 height: 1
} }
Text { StyledText {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: ProcessMonitorService.processes.length + " processes" text: ProcessMonitorService.processes.length + " processes"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
@@ -163,7 +163,7 @@ DankModal {
} }
Text { StyledText {
text: modelData text: modelData
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
font.weight: currentTab === index ? Font.Bold : Font.Medium font.weight: currentTab === index ? Font.Bold : Font.Medium
@@ -294,29 +294,8 @@ DankModal {
SystemTab {} SystemTab {}
} }
ProcessContextMenu { ProcessContextMenu {
id: processContextMenu id: processContextMenu
} }
IpcHandler {
function open() {
processListModal.show();
return "PROCESSLIST_OPEN_SUCCESS";
}
function close() {
processListModal.hide();
return "PROCESSLIST_CLOSE_SUCCESS";
}
function toggle() {
processListModal.toggle();
return "PROCESSLIST_TOGGLE_SUCCESS";
}
target: "processlist"
}
} }

View File

@@ -1,8 +1,8 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import qs.Common import qs.Common
import qs.Widgets
import qs.Modules.Settings import qs.Modules.Settings
import qs.Widgets
DankModal { DankModal {
id: settingsModal id: settingsModal
@@ -51,7 +51,7 @@ DankModal {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Text { StyledText {
text: "Settings" text: "Settings"
font.pixelSize: Theme.fontSizeXLarge font.pixelSize: Theme.fontSizeXLarge
color: Theme.surfaceText color: Theme.surfaceText
@@ -103,6 +103,16 @@ DankModal {
} }
// Wallpaper Settings
SettingsSection {
title: "Wallpaper"
iconName: "wallpaper"
content: WallpaperTab {
}
}
// Clock Settings // Clock Settings
SettingsSection { SettingsSection {
title: "Clock & Time" title: "Clock & Time"

View File

@@ -4,9 +4,9 @@ import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import qs.Common import qs.Common
import qs.Modules.AppDrawer
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
import qs.Modules.AppDrawer
DankModal { DankModal {
id: spotlightModal id: spotlightModal
@@ -34,9 +34,6 @@ DankModal {
show(); show();
} }
// DankModal configuration // DankModal configuration
visible: spotlightOpen visible: spotlightOpen
width: 550 width: 550
@@ -47,18 +44,15 @@ DankModal {
borderColor: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) borderColor: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
borderWidth: 1 borderWidth: 1
enableShadow: true enableShadow: true
onVisibleChanged: { onVisibleChanged: {
console.log("SpotlightModal visibility changed to:", visible); console.log("SpotlightModal visibility changed to:", visible);
if (visible && !spotlightOpen) { if (visible && !spotlightOpen)
show(); show();
}
} }
onBackgroundClicked: { onBackgroundClicked: {
spotlightOpen = false; spotlightOpen = false;
} }
Component.onCompleted: { Component.onCompleted: {
console.log("SpotlightModal: Component.onCompleted called - component loaded successfully!"); console.log("SpotlightModal: Component.onCompleted called - component loaded successfully!");
} }
@@ -66,21 +60,41 @@ DankModal {
// App launcher logic // App launcher logic
AppLauncher { AppLauncher {
id: appLauncher id: appLauncher
viewMode: Prefs.spotlightModalViewMode viewMode: Prefs.spotlightModalViewMode
gridColumns: 4 gridColumns: 4
onAppLaunched: hide() onAppLaunched: hide()
onViewModeSelected: function(mode) { onViewModeSelected: function(mode) {
Prefs.setSpotlightModalViewMode(mode); Prefs.setSpotlightModalViewMode(mode);
} }
} }
IpcHandler {
function open() {
console.log("SpotlightModal: IPC open() called");
spotlightModal.show();
return "SPOTLIGHT_OPEN_SUCCESS";
}
function close() {
console.log("SpotlightModal: IPC close() called");
spotlightModal.hide();
return "SPOTLIGHT_CLOSE_SUCCESS";
}
function toggle() {
console.log("SpotlightModal: IPC toggle() called");
spotlightModal.toggle();
return "SPOTLIGHT_TOGGLE_SUCCESS";
}
target: "spotlight"
}
content: Component { content: Component {
Item { Item {
anchors.fill: parent anchors.fill: parent
focus: true focus: true
// Handle keyboard shortcuts // Handle keyboard shortcuts
Keys.onPressed: function(event) { Keys.onPressed: function(event) {
if (event.key === Qt.Key_Escape) { if (event.key === Qt.Key_Escape) {
@@ -108,227 +122,213 @@ DankModal {
} }
} }
Column { Column {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingM anchors.margins: Theme.spacingM
spacing: Theme.spacingM
// Category selector
CategorySelector {
width: parent.width
categories: appLauncher.categories
selectedCategory: appLauncher.selectedCategory
compact: false
visible: appLauncher.categories.length > 1 || appLauncher.model.count > 0
onCategorySelected: appLauncher.setCategory(category)
}
// Search field with view toggle buttons
Row {
width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
DankTextField { // Category selector
id: searchField CategorySelector {
width: parent.width
width: parent.width - 80 - Theme.spacingM // Leave space for view toggle buttons categories: appLauncher.categories
height: 56 selectedCategory: appLauncher.selectedCategory
cornerRadius: Theme.cornerRadiusLarge compact: false
backgroundColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.7) visible: appLauncher.categories.length > 1 || appLauncher.model.count > 0
normalBorderColor: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) onCategorySelected: (category) => {
focusedBorderColor: Theme.primary return appLauncher.setCategory(category);
leftIconName: "search"
leftIconSize: Theme.iconSize
leftIconColor: Theme.surfaceVariantText
leftIconFocusedColor: Theme.primary
showClearButton: true
textColor: Theme.surfaceText
font.pixelSize: Theme.fontSizeLarge
enabled: spotlightOpen
placeholderText: "Search applications..."
text: appLauncher.searchQuery
onTextEdited: {
appLauncher.searchQuery = text;
}
Connections {
target: spotlightModal
function onOpened() {
searchField.forceActiveFocus();
}
function onDialogClosed() {
searchField.clearFocus();
}
}
Keys.onPressed: (event) => {
if (event.key === Qt.Key_Escape) {
hide();
event.accepted = true;
} else if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && appLauncher.searchQuery.length > 0) {
// Launch first app when typing in search field
if (appLauncher.model.count > 0) {
appLauncher.launchApp(appLauncher.model.get(0));
}
event.accepted = true;
} else if (event.key === Qt.Key_Down || event.key === Qt.Key_Up ||
(event.key === Qt.Key_Left && appLauncher.viewMode === "grid") ||
(event.key === Qt.Key_Right && appLauncher.viewMode === "grid") ||
((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && appLauncher.searchQuery.length === 0)) {
// Pass navigation keys and enter (when not searching) to main handler
event.accepted = false;
}
} }
} }
// View mode toggle buttons next to search bar // Search field with view toggle buttons
Row { Row {
spacing: Theme.spacingXS width: parent.width
visible: appLauncher.model.count > 0 spacing: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
// List view button DankTextField {
Rectangle { id: searchField
width: 36
height: 36
radius: Theme.cornerRadiusLarge
color: appLauncher.viewMode === "list" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : listViewArea.containsMouse ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08) : "transparent"
border.color: appLauncher.viewMode === "list" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) : "transparent"
border.width: 1
DankIcon { width: parent.width - 80 - Theme.spacingM // Leave space for view toggle buttons
anchors.centerIn: parent height: 56
name: "view_list" cornerRadius: Theme.cornerRadiusLarge
size: 18 backgroundColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.7)
color: appLauncher.viewMode === "list" ? Theme.primary : Theme.surfaceText normalBorderColor: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
focusedBorderColor: Theme.primary
leftIconName: "search"
leftIconSize: Theme.iconSize
leftIconColor: Theme.surfaceVariantText
leftIconFocusedColor: Theme.primary
showClearButton: true
textColor: Theme.surfaceText
font.pixelSize: Theme.fontSizeLarge
enabled: spotlightOpen
placeholderText: "Search applications..."
text: appLauncher.searchQuery
onTextEdited: {
appLauncher.searchQuery = text;
} }
Keys.onPressed: (event) => {
if (event.key === Qt.Key_Escape) {
hide();
event.accepted = true;
} else if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && appLauncher.searchQuery.length > 0) {
// Launch first app when typing in search field
if (appLauncher.model.count > 0)
appLauncher.launchApp(appLauncher.model.get(0));
MouseArea { event.accepted = true;
id: listViewArea } else if (event.key === Qt.Key_Down || event.key === Qt.Key_Up || (event.key === Qt.Key_Left && appLauncher.viewMode === "grid") || (event.key === Qt.Key_Right && appLauncher.viewMode === "grid") || ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && appLauncher.searchQuery.length === 0)) {
// Pass navigation keys and enter (when not searching) to main handler
anchors.fill: parent event.accepted = false;
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
appLauncher.setViewMode("list");
} }
} }
}
// Grid view button Connections {
Rectangle { function onOpened() {
width: 36 searchField.forceActiveFocus();
height: 36
radius: Theme.cornerRadiusLarge
color: appLauncher.viewMode === "grid" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : gridViewArea.containsMouse ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08) : "transparent"
border.color: appLauncher.viewMode === "grid" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) : "transparent"
border.width: 1
DankIcon {
anchors.centerIn: parent
name: "grid_view"
size: 18
color: appLauncher.viewMode === "grid" ? Theme.primary : Theme.surfaceText
}
MouseArea {
id: gridViewArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
appLauncher.setViewMode("grid");
} }
function onDialogClosed() {
searchField.clearFocus();
}
target: spotlightModal
}
}
// View mode toggle buttons next to search bar
Row {
spacing: Theme.spacingXS
visible: appLauncher.model.count > 0
anchors.verticalCenter: parent.verticalCenter
// List view button
Rectangle {
width: 36
height: 36
radius: Theme.cornerRadiusLarge
color: appLauncher.viewMode === "list" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : listViewArea.containsMouse ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08) : "transparent"
border.color: appLauncher.viewMode === "list" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) : "transparent"
border.width: 1
DankIcon {
anchors.centerIn: parent
name: "view_list"
size: 18
color: appLauncher.viewMode === "list" ? Theme.primary : Theme.surfaceText
}
MouseArea {
id: listViewArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
appLauncher.setViewMode("list");
}
}
}
// Grid view button
Rectangle {
width: 36
height: 36
radius: Theme.cornerRadiusLarge
color: appLauncher.viewMode === "grid" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : gridViewArea.containsMouse ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08) : "transparent"
border.color: appLauncher.viewMode === "grid" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) : "transparent"
border.width: 1
DankIcon {
anchors.centerIn: parent
name: "grid_view"
size: 18
color: appLauncher.viewMode === "grid" ? Theme.primary : Theme.surfaceText
}
MouseArea {
id: gridViewArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
appLauncher.setViewMode("grid");
}
}
}
}
}
// Results container
Rectangle {
id: resultsContainer
width: parent.width
height: parent.height - y // Use remaining space
color: "transparent"
// List view
DankListView {
id: resultsList
anchors.fill: parent
visible: appLauncher.viewMode === "list"
model: appLauncher.model
currentIndex: appLauncher.selectedIndex
itemHeight: 60
iconSize: 40
showDescription: true
hoverUpdatesSelection: false
keyboardNavigationActive: appLauncher.keyboardNavigationActive
onItemClicked: function(index, modelData) {
appLauncher.launchApp(modelData);
}
onItemHovered: function(index) {
appLauncher.selectedIndex = index;
}
onKeyboardNavigationReset: {
appLauncher.keyboardNavigationActive = false;
} }
} }
// Grid view
DankGridView {
id: resultsGrid
anchors.fill: parent
visible: appLauncher.viewMode === "grid"
model: appLauncher.model
columns: 4
adaptiveColumns: false
minCellWidth: 120
maxCellWidth: 160
iconSizeRatio: 0.55
maxIconSize: 48
currentIndex: appLauncher.selectedIndex
hoverUpdatesSelection: false
keyboardNavigationActive: appLauncher.keyboardNavigationActive
onItemClicked: function(index, modelData) {
appLauncher.launchApp(modelData);
}
onItemHovered: function(index) {
appLauncher.selectedIndex = index;
}
onKeyboardNavigationReset: {
appLauncher.keyboardNavigationActive = false;
}
}
} }
} }
// Results container
Rectangle {
id: resultsContainer
width: parent.width
height: parent.height - y // Use remaining space
color: "transparent"
// List view
DankListView {
id: resultsList
anchors.fill: parent
visible: appLauncher.viewMode === "list"
model: appLauncher.model
currentIndex: appLauncher.selectedIndex
itemHeight: 60
iconSize: 40
showDescription: true
hoverUpdatesSelection: false
keyboardNavigationActive: appLauncher.keyboardNavigationActive
onItemClicked: function(index, modelData) {
appLauncher.launchApp(modelData);
}
onItemHovered: function(index) {
appLauncher.selectedIndex = index;
}
onKeyboardNavigationReset: {
appLauncher.keyboardNavigationActive = false;
}
}
// Grid view
DankGridView {
id: resultsGrid
anchors.fill: parent
visible: appLauncher.viewMode === "grid"
model: appLauncher.model
columns: 4
adaptiveColumns: false
minCellWidth: 120
maxCellWidth: 160
iconSizeRatio: 0.55
maxIconSize: 48
currentIndex: appLauncher.selectedIndex
hoverUpdatesSelection: false
keyboardNavigationActive: appLauncher.keyboardNavigationActive
onItemClicked: function(index, modelData) {
appLauncher.launchApp(modelData);
}
onItemHovered: function(index) {
appLauncher.selectedIndex = index;
}
onKeyboardNavigationReset: {
appLauncher.keyboardNavigationActive = false;
}
}
}
}
} }
} }
IpcHandler { }
function open() {
console.log("SpotlightModal: IPC open() called");
spotlightModal.show();
return "SPOTLIGHT_OPEN_SUCCESS";
}
function close() {
console.log("SpotlightModal: IPC close() called");
spotlightModal.hide();
return "SPOTLIGHT_CLOSE_SUCCESS";
}
function toggle() {
console.log("SpotlightModal: IPC toggle() called");
spotlightModal.toggle();
return "SPOTLIGHT_TOGGLE_SUCCESS";
}
target: "spotlight"
}
}

View File

@@ -56,14 +56,14 @@ DankModal {
width: parent.width - 40 width: parent.width - 40
spacing: Theme.spacingXS spacing: Theme.spacingXS
Text { StyledText {
text: "Connect to Wi-Fi" text: "Connect to Wi-Fi"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
Text { StyledText {
text: "Enter password for \"" + wifiPasswordSSID + "\"" text: "Enter password for \"" + wifiPasswordSSID + "\""
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
@@ -168,7 +168,7 @@ DankModal {
} }
Text { StyledText {
text: "Show password" text: "Show password"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
@@ -195,7 +195,7 @@ DankModal {
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 1 border.width: 1
Text { StyledText {
id: cancelText id: cancelText
anchors.centerIn: parent anchors.centerIn: parent
@@ -227,7 +227,7 @@ DankModal {
enabled: passwordInput.text.length > 0 enabled: passwordInput.text.length > 0
opacity: enabled ? 1 : 0.5 opacity: enabled ? 1 : 0.5
Text { StyledText {
id: connectText id: connectText
anchors.centerIn: parent anchors.centerIn: parent

View File

@@ -1,5 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Effects import QtQuick.Effects
import Quickshell
import qs.Common import qs.Common
import qs.Widgets import qs.Widgets
@@ -12,11 +13,11 @@ Column {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
Text { StyledText {
text: "Profile Image" text: "Profile Image"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText
} }
// Profile Image Preview with circular crop // Profile Image Preview with circular crop
@@ -33,34 +34,32 @@ Column {
width: 54 width: 54
height: 54 height: 54
// This rectangle provides the themed ring via its border.
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
radius: width / 2 radius: width / 2
color: "transparent" color: "transparent"
border.color: Theme.primary border.color: Theme.primary
border.width: 1 // The ring is 1px thick. border.width: 1
visible: parent.hasImage visible: parent.hasImage
} }
// Hidden Image loader. Its only purpose is to load the texture.
Image { Image {
id: avatarImageSource id: avatarImageSource
source: { source: {
if (profileImageInput.text === "") if (Prefs.profileImage === "")
return ""; return "";
if (profileImageInput.text.startsWith("/")) if (Prefs.profileImage.startsWith("/"))
return "file://" + profileImageInput.text; return "file://" + Prefs.profileImage;
return profileImageInput.text; return Prefs.profileImage;
} }
smooth: true smooth: true
asynchronous: true asynchronous: true
mipmap: true mipmap: true
cache: true cache: true
visible: false // This item is never shown directly. visible: false
} }
MultiEffect { MultiEffect {
@@ -92,7 +91,6 @@ Column {
} }
// Fallback for when there is no image.
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
radius: width / 2 radius: width / 2
@@ -108,60 +106,136 @@ Column {
} }
// Error icon for when the image fails to load.
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
name: "warning" name: "warning"
size: Theme.iconSize + 8 size: Theme.iconSize + 8
color: Theme.primaryText color: Theme.primaryText
visible: profileImageInput.text !== "" && avatarImageSource.status === Image.Error visible: Prefs.profileImage !== "" && avatarImageSource.status === Image.Error
} }
} }
// Input field
Column { Column {
width: parent.width - 80 - Theme.spacingM width: parent.width - 54 - Theme.spacingM
spacing: Theme.spacingS spacing: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
Rectangle { StyledText {
text: Prefs.profileImage ? Prefs.profileImage.split('/').pop() : "No profile image selected"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
elide: Text.ElideMiddle
width: parent.width width: parent.width
height: 48
radius: Theme.cornerRadius
color: Theme.surfaceVariant
border.color: profileImageInput.activeFocus ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
border.width: profileImageInput.activeFocus ? 2 : 1
DankTextField {
id: profileImageInput
anchors.fill: parent
textColor: Theme.surfaceText
font.pixelSize: Theme.fontSizeMedium
text: Prefs.profileImage
placeholderText: "Enter image path or URL..."
backgroundColor: "transparent"
normalBorderColor: "transparent"
focusedBorderColor: "transparent"
onEditingFinished: {
Prefs.setProfileImage(text);
}
}
} }
Text { StyledText {
text: "Local filesystem path or URL to an image file." text: Prefs.profileImage ? Prefs.profileImage : ""
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
wrapMode: Text.WordWrap elide: Text.ElideMiddle
width: parent.width width: parent.width
visible: Prefs.profileImage !== ""
} }
} }
} }
Row {
width: parent.width
spacing: Theme.spacingS
StyledRect {
width: 100
height: 32
radius: Theme.cornerRadius
color: Theme.primary
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: "folder_open"
size: Theme.iconSizeSmall
color: Theme.primaryText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "Browse..."
color: Theme.primaryText
font.pixelSize: Theme.fontSizeSmall
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
profileBrowserLoader.active = true;
profileBrowser.visible = true;
}
}
}
StyledRect {
width: 80
height: 32
radius: Theme.cornerRadius
color: Theme.surfaceVariant
opacity: Prefs.profileImage !== "" ? 1.0 : 0.5
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: "clear"
size: Theme.iconSizeSmall
color: Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "Clear"
color: Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
anchors.fill: parent
enabled: Prefs.profileImage !== ""
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: {
Prefs.setProfileImage("");
}
}
}
}
} }
LazyLoader {
id: profileBrowserLoader
active: false
FileBrowser {
id: profileBrowser
browserTitle: "Select Profile Image"
browserIcon: "person"
browserType: "profile"
fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"]
onFileSelected: (path) => {
Prefs.setProfileImage(path);
visible = false;
}
}
}
property alias profileBrowser: profileBrowserLoader.item
} }

View File

@@ -0,0 +1,235 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import Quickshell
import qs.Common
import qs.Widgets
Column {
id: wallpaperTab
width: parent.width
spacing: Theme.spacingM
// Current Wallpaper Section
Column {
width: parent.width
spacing: Theme.spacingS
StyledText {
text: "Current Wallpaper"
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
}
Row {
width: parent.width
spacing: Theme.spacingM
// Wallpaper Preview
StyledRect {
width: 120
height: 67
radius: Theme.cornerRadius
color: Theme.surfaceVariant
border.color: Theme.outline
border.width: 1
Image {
anchors.fill: parent
anchors.margins: 1
source: Prefs.wallpaperPath ? "file://" + Prefs.wallpaperPath : ""
fillMode: Image.PreserveAspectCrop
visible: Prefs.wallpaperPath !== ""
layer.enabled: true
layer.effect: MultiEffect {
maskEnabled: true
maskSource: mask
maskThresholdMin: 0.5
maskSpreadAtMin: 1.0
}
}
Rectangle {
id: mask
anchors.fill: parent
anchors.margins: 1
radius: Theme.cornerRadius - 1
color: "black"
visible: false
layer.enabled: true
}
DankIcon {
anchors.centerIn: parent
name: "image"
size: Theme.iconSizeLarge
color: Theme.surfaceVariantText
visible: Prefs.wallpaperPath === ""
}
}
Column {
width: parent.width - 120 - Theme.spacingM
spacing: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: Prefs.wallpaperPath ? Prefs.wallpaperPath.split('/').pop() : "No wallpaper selected"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
elide: Text.ElideMiddle
width: parent.width
}
StyledText {
text: Prefs.wallpaperPath ? Prefs.wallpaperPath : ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
elide: Text.ElideMiddle
width: parent.width
visible: Prefs.wallpaperPath !== ""
}
}
}
Row {
width: parent.width
spacing: Theme.spacingS
StyledRect {
width: 100
height: 32
radius: Theme.cornerRadius
color: Theme.primary
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: "folder_open"
size: Theme.iconSizeSmall
color: Theme.primaryText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "Browse..."
color: Theme.primaryText
font.pixelSize: Theme.fontSizeSmall
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
wallpaperBrowserLoader.active = true;
wallpaperBrowser.visible = true;
}
}
}
StyledRect {
width: 80
height: 32
radius: Theme.cornerRadius
color: Theme.surfaceVariant
opacity: Prefs.wallpaperPath !== "" ? 1.0 : 0.5
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: "clear"
size: Theme.iconSizeSmall
color: Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "Clear"
color: Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
anchors.fill: parent
enabled: Prefs.wallpaperPath !== ""
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: {
Prefs.setWallpaperPath("");
}
}
}
}
}
// Dynamic Theming Section
Column {
width: parent.width
spacing: Theme.spacingS
Row {
width: parent.width
spacing: Theme.spacingM
Column {
width: parent.width - toggle.width - Theme.spacingM
spacing: Theme.spacingXS
StyledText {
text: "Dynamic Theming"
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
}
StyledText {
text: "Automatically extract colors from wallpaper"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
}
}
DankToggle {
id: toggle
anchors.verticalCenter: parent.verticalCenter
checked: Prefs.wallpaperDynamicTheming
onCheckedChanged: {
if (activeFocus) {
Prefs.setWallpaperDynamicTheming(checked);
}
}
}
}
}
LazyLoader {
id: wallpaperBrowserLoader
active: false
FileBrowser {
id: wallpaperBrowser
browserTitle: "Select Wallpaper"
browserIcon: "wallpaper"
browserType: "wallpaper"
fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"]
onFileSelected: (path) => {
Prefs.setWallpaperPath(path);
visible = false;
}
}
}
property alias wallpaperBrowser: wallpaperBrowserLoader.item
}

View File

@@ -6,10 +6,11 @@ import qs.Services
import qs.Widgets import qs.Widgets
Rectangle { Rectangle {
id: cpuMonitor id: root
property bool showPercentage: true property bool showPercentage: true
property bool showIcon: true property bool showIcon: true
property var toggleProcessList
width: 55 width: 55
height: 30 height: 30
@@ -24,7 +25,8 @@ Rectangle {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
ProcessMonitorService.setSortBy("cpu"); ProcessMonitorService.setSortBy("cpu");
processListPopout.toggle(); if (root.toggleProcessList)
root.toggleProcessList();
} }
} }
@@ -32,9 +34,8 @@ Rectangle {
anchors.centerIn: parent anchors.centerIn: parent
spacing: 3 spacing: 3
// CPU icon
DankIcon { DankIcon {
name: "memory" // Material Design memory icon (swapped from RAM widget) name: "memory"
size: Theme.iconSize - 8 size: Theme.iconSize - 8
color: { color: {
if (SystemMonitorService.cpuUsage > 80) if (SystemMonitorService.cpuUsage > 80)
@@ -48,7 +49,6 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
// Percentage text
Text { Text {
text: (SystemMonitorService.cpuUsage || 0).toFixed(0) + "%" text: (SystemMonitorService.cpuUsage || 0).toFixed(0) + "%"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall

View File

@@ -6,10 +6,11 @@ import qs.Services
import qs.Widgets import qs.Widgets
Rectangle { Rectangle {
id: ramMonitor id: root
property bool showPercentage: true property bool showPercentage: true
property bool showIcon: true property bool showIcon: true
property var toggleProcessList
width: 55 width: 55
height: 30 height: 30
@@ -24,7 +25,8 @@ Rectangle {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
ProcessMonitorService.setSortBy("memory"); ProcessMonitorService.setSortBy("memory");
processListPopout.toggle(); if (root.toggleProcessList)
root.toggleProcessList();
} }
} }
@@ -32,9 +34,8 @@ Rectangle {
anchors.centerIn: parent anchors.centerIn: parent
spacing: 3 spacing: 3
// RAM icon
DankIcon { DankIcon {
name: "developer_board" // Material Design CPU/processor icon (swapped from CPU widget) name: "developer_board"
size: Theme.iconSize - 8 size: Theme.iconSize - 8
color: { color: {
if (SystemMonitorService.memoryUsage > 90) if (SystemMonitorService.memoryUsage > 90)
@@ -48,7 +49,6 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
// Percentage text
Text { Text {
text: (SystemMonitorService.memoryUsage || 0).toFixed(0) + "%" text: (SystemMonitorService.memoryUsage || 0).toFixed(0) + "%"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall

View File

@@ -14,15 +14,11 @@ import qs.Services
import qs.Widgets import qs.Widgets
PanelWindow { PanelWindow {
// Proxy objects for external connections
id: root id: root
property var modelData property var modelData
property string screenName: modelData.name property string screenName: modelData.name
// Transparency property for the top bar background
property real backgroundTransparency: Prefs.topBarTransparency property real backgroundTransparency: Prefs.topBarTransparency
// Notification properties
readonly property int notificationCount: NotificationService.notifications.length readonly property int notificationCount: NotificationService.notifications.length
screen: modelData screen: modelData
@@ -55,7 +51,6 @@ PanelWindow {
right: true right: true
} }
// Floating panel container with margins
Item { Item {
anchors.fill: parent anchors.fill: parent
anchors.margins: 2 anchors.margins: 2
@@ -132,9 +127,10 @@ PanelWindow {
LauncherButton { LauncherButton {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
isActive: appDrawerPopout.isVisible isActive: appDrawerPopout ? appDrawerPopout.isVisible : false
onClicked: { onClicked: {
appDrawerPopout.toggle(); if (appDrawerPopout)
appDrawerPopout.toggle();
} }
} }
@@ -238,15 +234,16 @@ PanelWindow {
} }
// System Monitor Widgets
CpuMonitor { CpuMonitor {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: Prefs.showSystemResources visible: Prefs.showSystemResources
toggleProcessList: () => processListPopout.toggle()
} }
RamMonitor { RamMonitor {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: Prefs.showSystemResources visible: Prefs.showSystemResources
toggleProcessList: () => processListPopout.toggle()
} }
NotificationCenterButton { NotificationCenterButton {
@@ -258,7 +255,6 @@ PanelWindow {
} }
} }
// Battery Widget
Battery { Battery {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
batteryPopupVisible: batteryPopout.batteryPopupVisible batteryPopupVisible: batteryPopout.batteryPopupVisible
@@ -268,8 +264,6 @@ PanelWindow {
} }
ControlCenterButton { ControlCenterButton {
// Bluetooth devices are automatically updated via signals
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
isActive: controlCenterPopout.controlCenterVisible isActive: controlCenterPopout.controlCenterVisible
onClicked: { onClicked: {

View File

@@ -0,0 +1,63 @@
import QtQuick
import Quickshell
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common
import qs.Widgets
Variants {
model: Quickshell.screens
PanelWindow {
id: wallpaperWindow
property var modelData
screen: modelData
WlrLayershell.layer: WlrLayer.Background
WlrLayershell.exclusionMode: ExclusionMode.Ignore
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
anchors.top: true
anchors.bottom: true
anchors.left: true
anchors.right: true
visible: true
color: "transparent"
Image {
id: wallpaperImage
anchors.fill: parent
source: Prefs.wallpaperPath ? "file://" + Prefs.wallpaperPath : ""
fillMode: Image.PreserveAspectCrop
visible: Prefs.wallpaperPath !== ""
smooth: true
cache: true
// Smooth transition when wallpaper changes
Behavior on opacity {
NumberAnimation {
duration: Appearance.anim.durations.normal
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.anim.curves.standard
}
}
onStatusChanged: {
if (status === Image.Error) {
console.warn("Failed to load wallpaper:", source);
}
}
}
// Fallback background color when no wallpaper is set
StyledRect {
anchors.fill: parent
color: Theme.surface
visible: !wallpaperImage.visible
}
}
}

View File

@@ -30,53 +30,22 @@ paru -S quickshell-git
| matugen | Allows dynamic themes based on wallpaper | Just can choose from preconfigured themes instead of dynamic colors | | matugen | Allows dynamic themes based on wallpaper | Just can choose from preconfigured themes instead of dynamic colors |
| ddcutil (or brightnessctl) | Allows controlling brightness of monitors | No Brightness | | ddcutil (or brightnessctl) | Allows controlling brightness of monitors | No Brightness |
| wl-clipboard | Unlocks copy functionality of certain elements, such as process PIDs | No copy | | wl-clipboard | Unlocks copy functionality of certain elements, such as process PIDs | No copy |
| swaybg | Wallpaper | Just one wallpaper solution, others will work just not with `set-wallpaper.sh` below |
```bash ```bash
# Arch # Arch
paru -S ttf-material-symbols-variable-git matugen cliphist cava wl-clipboard ddcutil swaybg paru -S ttf-material-symbols-variable-git matugen cliphist cava wl-clipboard ddcutil
``` ```
**Note on networking:** **Note on networking:** This shell requires NetworkManager for WiFi functionality.
3. Configure SwayBG (If Desired) 3. Install DankMaterialShell
```
# Install wallpaper script
sudo cp ./scripts/set-wallpaper.sh /usr/local/bin/set-wallpaper.sh
sudo chmod +x /usr/local/bin/set-wallpaper.sh
# Arch
pacman -S swaybg
# Create service
echo '[Unit]
PartOf=graphical-session.target
After=graphical-session.target
Requisite=graphical-session.target
[Service]
ExecStart=/usr/bin/swaybg -m fill -i "%h/quickshell/current_wallpaper"
Restart=on-failure
[Install]
WantedBy=graphical-session.target' > ~/.config/systemd/user/swaybg.service
# Set a wallpaper
set-wallpaper.sh /path/to/image.jpg
# Enable service
systemctl --user enable --now swaybg.service
```
4. Install DankMaterialShell
``` ```
mkdir -p ~/.config/quickshell mkdir -p ~/.config/quickshell
git clone https://github.com/bbedward/DankMaterialShell.git ~/.config/quickshell/DankMaterialShell git clone https://github.com/bbedward/DankMaterialShell.git ~/.config/quickshell/DankMaterialShell
``` ```
5. Enable 4. Enable
``` ```
qs -c DankMaterialShell qs -c DankMaterialShell
@@ -102,7 +71,9 @@ qs -c DankMaterialShell ipc call <target> <function>
| spotlight | toggle | Toggle spotlight (app launcher) | | spotlight | toggle | Toggle spotlight (app launcher) |
| clipboard | toggle | Toggle clipboard history view | | clipboard | toggle | Toggle clipboard history view |
| processlist | toggle | Toggle process list (task manager) | | processlist | toggle | Toggle process list (task manager) |
| wallpaper | refresh | Refresh theme (refreshes theme after wallpaper change) | | wallpaper | set \<path\> | Set wallpaper to image path and refresh theme (if auto theme enabled) |
| wallpaper | get | Get current wallpaper path |
| wallpaper | list | List available wallpapers |
## (Optional) Setup Calendar events (Google, Microsoft, other Caldev, etc.) ## (Optional) Setup Calendar events (Google, Microsoft, other Caldev, etc.)

View File

@@ -2,7 +2,7 @@ import QtQuick
import qs.Common import qs.Common
import qs.Widgets import qs.Widgets
Rectangle { StyledRect {
id: root id: root
property string iconName: "" property string iconName: ""
@@ -18,7 +18,7 @@ Rectangle {
width: buttonSize width: buttonSize
height: buttonSize height: buttonSize
radius: circular ? buttonSize / 2 : Theme.cornerRadius radius: circular ? buttonSize / 2 : Theme.cornerRadius
color: mouseArea.containsMouse ? hoverColor : backgroundColor color: backgroundColor
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
@@ -27,21 +27,10 @@ Rectangle {
color: root.iconColor color: root.iconColor
} }
MouseArea { StateLayer {
id: mouseArea stateColor: Theme.primary
cornerRadius: root.radius
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.clicked() onClicked: root.clicked()
} }
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
} }

View File

@@ -1,18 +1,48 @@
import QtQuick import QtQuick
import qs.Common import qs.Common
Text { StyledText {
id: icon id: icon
property alias name: icon.text property alias name: icon.text
property alias size: icon.font.pixelSize property alias size: icon.font.pixelSize
property alias color: icon.color property alias color: icon.color
property bool filled: false property bool filled: false
property real fill: filled ? 1 : 0
property int grade: Theme.isLightMode ? 0 : -25
property int weight: filled ? 500 : 400
font.family: "Material Symbols Rounded" font.family: "Material Symbols Rounded"
font.pixelSize: Theme.iconSize font.pixelSize: Appearance.fontSize.normal
font.weight: filled ? Font.Medium : Font.Normal font.weight: weight
color: Theme.surfaceText color: Theme.surfaceText
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
// Material Icons variable font axes support
font.variableAxes: ({
"FILL": fill.toFixed(1),
"GRAD": grade,
"opsz": 24,
"wght": weight
})
// Smooth transitions for variable axes
Behavior on fill {
NumberAnimation {
duration: Appearance.anim.durations.quick
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.anim.curves.standard
}
}
Behavior on weight {
NumberAnimation {
duration: Appearance.anim.durations.quick
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.anim.curves.standard
}
}
} }

View File

@@ -25,7 +25,7 @@ Item {
spacing: Theme.spacingM spacing: Theme.spacingM
// Value display // Value display
Text { StyledText {
text: slider.value + slider.unit text: slider.value + slider.unit
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: slider.enabled ? Theme.surfaceText : Theme.surfaceVariantText color: slider.enabled ? Theme.surfaceText : Theme.surfaceVariantText
@@ -49,7 +49,7 @@ Item {
} }
// Slider track // Slider track
Rectangle { StyledRect {
id: sliderTrack id: sliderTrack
property int leftIconWidth: slider.leftIcon.length > 0 ? Theme.iconSize : 0 property int leftIconWidth: slider.leftIcon.length > 0 ? Theme.iconSize : 0
@@ -62,7 +62,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
// Fill // Fill
Rectangle { StyledRect {
id: sliderFill id: sliderFill
width: parent.width * ((slider.value - slider.minimum) / (slider.maximum - slider.minimum)) width: parent.width * ((slider.value - slider.minimum) / (slider.maximum - slider.minimum))
@@ -72,8 +72,8 @@ Item {
Behavior on width { Behavior on width {
NumberAnimation { NumberAnimation {
duration: 150 duration: Theme.shortDuration
easing.type: Easing.OutCubic easing.type: Theme.standardEasing
} }
} }
@@ -81,7 +81,7 @@ Item {
} }
// Draggable handle // Draggable handle
Rectangle { StyledRect {
id: sliderHandle id: sliderHandle
width: 18 width: 18
@@ -95,7 +95,7 @@ Item {
scale: sliderMouseArea.containsMouse || sliderMouseArea.pressed ? 1.2 : 1 scale: sliderMouseArea.containsMouse || sliderMouseArea.pressed ? 1.2 : 1
// Handle glow effect when active // Handle glow effect when active
Rectangle { StyledRect {
anchors.centerIn: parent anchors.centerIn: parent
width: parent.width + 4 width: parent.width + 4
height: parent.height + 4 height: parent.height + 4
@@ -104,19 +104,12 @@ Item {
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
border.width: 2 border.width: 2
visible: sliderMouseArea.containsMouse && slider.enabled visible: sliderMouseArea.containsMouse && slider.enabled
Behavior on opacity {
NumberAnimation {
duration: 150
}
}
} }
Behavior on scale { Behavior on scale {
NumberAnimation { NumberAnimation {
duration: 150 duration: Theme.shortDuration
easing.type: Theme.standardEasing
} }
} }

View File

@@ -1,38 +0,0 @@
import QtQuick
import QtQuick.Effects
import Quickshell
import Quickshell.Io
import Quickshell.Widgets
import qs.Common
IconImage {
id: root
property string colorOverride: ""
property real brightnessOverride: 0.5
property real contrastOverride: 1
smooth: true
asynchronous: true
layer.enabled: colorOverride !== ""
Process {
running: true
command: ["sh", "-c", ". /etc/os-release && echo $LOGO"]
stdout: StdioCollector {
onStreamFinished: () => {
root.source = Quickshell.iconPath(this.text.trim());
}
}
}
layer.effect: MultiEffect {
colorization: 1
colorizationColor: colorOverride
brightness: brightnessOverride
contrast: contrastOverride
}
}

View File

@@ -1,5 +1,6 @@
import QtQuick import QtQuick
import qs.Common import qs.Common
import qs.Widgets
Item { Item {
id: toggle id: toggle
@@ -16,13 +17,25 @@ Item {
width: text ? parent.width : 48 width: text ? parent.width : 48
height: text ? 60 : 24 height: text ? 60 : 24
Rectangle { StyledRect {
id: background id: background
anchors.fill: parent anchors.fill: parent
radius: toggle.text ? Theme.cornerRadius : 0 radius: toggle.text ? Theme.cornerRadius : 0
color: toggle.text ? (toggleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)) : "transparent" color: toggle.text ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08) : "transparent"
visible: toggle.text visible: toggle.text
StateLayer {
visible: toggle.text
stateColor: Theme.primary
cornerRadius: parent.radius
onClicked: {
toggle.checked = !toggle.checked;
toggle.clicked();
toggle.toggled(toggle.checked);
}
}
} }
Row { Row {
@@ -40,16 +53,15 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXS spacing: Theme.spacingXS
Text { StyledText {
text: toggle.text text: toggle.text
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Appearance.fontSize.normal
color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
Text { StyledText {
text: toggle.description text: toggle.description
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Appearance.fontSize.small
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
width: Math.min(implicitWidth, toggle.width - 120) width: Math.min(implicitWidth, toggle.width - 120)
@@ -60,7 +72,7 @@ Item {
} }
Rectangle { StyledRect {
id: toggleTrack id: toggleTrack
width: toggle.text ? 48 : parent.width width: toggle.text ? 48 : parent.width
@@ -72,7 +84,7 @@ Item {
color: toggle.checked ? Theme.primary : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3) color: toggle.checked ? Theme.primary : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
opacity: toggle.toggling ? 0.6 : 1 opacity: toggle.toggling ? 0.6 : 1
Rectangle { StyledRect {
id: toggleHandle id: toggleHandle
width: 20 width: 20
@@ -82,7 +94,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
x: toggle.checked ? parent.width - width - 2 : 2 x: toggle.checked ? parent.width - width - 2 : 2
Rectangle { StyledRect {
anchors.centerIn: parent anchors.centerIn: parent
width: parent.width + 2 width: parent.width + 2
height: parent.height + 2 height: parent.height + 2
@@ -95,28 +107,26 @@ Item {
Behavior on x { Behavior on x {
NumberAnimation { NumberAnimation {
duration: Theme.shortDuration duration: Appearance.anim.durations.normal
easing.type: Theme.emphasizedEasing easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.anim.curves.emphasizedDecel
} }
} }
} }
} StateLayer {
disabled: !toggle.enabled
MouseArea { stateColor: Theme.primary
id: toggleArea cornerRadius: parent.radius
onClicked: {
anchors.fill: toggle.text ? toggle : toggleTrack toggle.checked = !toggle.checked;
hoverEnabled: true toggle.clicked();
cursorShape: toggle.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor toggle.toggled(toggle.checked);
enabled: toggle.enabled }
onClicked: {
toggle.checked = !toggle.checked;
toggle.clicked();
toggle.toggled(toggle.checked);
} }
} }
} }

281
Widgets/FileBrowser.qml Normal file
View File

@@ -0,0 +1,281 @@
import QtQuick
import QtQuick.Controls
import QtCore
import Qt.labs.folderlistmodel
import Quickshell.Io
import qs.Common
import qs.Widgets
import qs.Modals
DankModal {
id: fileBrowser
signal fileSelected(string path)
property string homeDir: StandardPaths.writableLocation(StandardPaths.HomeLocation)
property string currentPath: ""
property var fileExtensions: ["*.*"]
property string browserTitle: "Select File"
property string browserIcon: "folder_open"
property string browserType: "generic" // "wallpaper" or "profile" for last path memory
FolderListModel {
id: folderModel
showDirsFirst: true
showDotAndDotDot: false
showHidden: false
nameFilters: fileExtensions
showFiles: true
showDirs: true
folder: currentPath ? "file://" + currentPath : "file://" + homeDir
}
function getLastPath() {
var lastPath = "";
if (browserType === "wallpaper") {
lastPath = Prefs.wallpaperLastPath;
} else if (browserType === "profile") {
lastPath = Prefs.profileLastPath;
}
// Check if last path exists, otherwise use home
if (lastPath && lastPath !== "") {
// TODO: Could add directory existence check here
return lastPath;
}
return homeDir;
}
function saveLastPath(path) {
if (browserType === "wallpaper") {
Prefs.wallpaperLastPath = path;
} else if (browserType === "profile") {
Prefs.profileLastPath = path;
}
Prefs.saveSettings();
}
Component.onCompleted: {
currentPath = getLastPath();
}
width: 800
height: 600
keyboardFocus: "ondemand"
enableShadow: true
visible: false
onBackgroundClicked: visible = false
onVisibleChanged: {
if (visible) {
// Use last path or home directory when opening
var startPath = getLastPath();
console.log("Opening file browser, setting path to:", startPath);
currentPath = startPath;
}
}
onCurrentPathChanged: {
console.log("Current path changed to:", currentPath);
console.log("Model count:", folderModel.count);
// Log first few files to debug
for (var i = 0; i < Math.min(3, folderModel.count); i++) {
console.log("File", i, ":", folderModel.get(i, "fileName"));
}
}
function navigateUp() {
var path = currentPath;
// Don't go above home directory
if (path === homeDir) {
console.log("Already at home directory, can't go up");
return;
}
var lastSlash = path.lastIndexOf('/');
if (lastSlash > 0) {
var newPath = path.substring(0, lastSlash);
// Make sure we don't go above home (check if newPath starts with homeDir)
if (newPath.startsWith(homeDir)) {
console.log("Navigating up from", path, "to", newPath);
currentPath = newPath;
saveLastPath(newPath);
} else {
console.log("Would go above home directory, stopping at", homeDir);
currentPath = homeDir;
saveLastPath(homeDir);
}
}
}
function navigateTo(path) {
currentPath = path;
saveLastPath(path); // Save the path when navigating
}
content: Component {
Column {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingS
// Header
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: browserIcon
size: Theme.iconSizeLarge
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: browserTitle
font.pixelSize: Theme.fontSizeXLarge
color: Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
Item {
width: parent.width - 200
height: 1
}
// Close button
DankActionButton {
iconName: "close"
iconSize: Theme.iconSizeSmall
iconColor: Theme.surfaceText
onClicked: fileBrowser.visible = false
}
}
// Current path display and navigation
Row {
width: parent.width
spacing: Theme.spacingS
StyledRect {
width: 32
height: 32
radius: Theme.cornerRadius
color: mouseArea.containsMouse && currentPath !== homeDir ? Theme.surfaceVariant : "transparent"
opacity: currentPath !== homeDir ? 1.0 : 0.0
DankIcon {
anchors.centerIn: parent
name: "arrow_back"
size: Theme.iconSizeSmall
color: Theme.surfaceText
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: currentPath !== homeDir
cursorShape: currentPath !== homeDir ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: currentPath !== homeDir
onClicked: navigateUp()
}
}
StyledText {
text: "Current folder: " + fileBrowser.currentPath
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: parent.width - 40 - Theme.spacingS
elide: Text.ElideMiddle
anchors.verticalCenter: parent.verticalCenter
maximumLineCount: 1
wrapMode: Text.NoWrap
}
}
// File grid
ScrollView {
width: parent.width
height: parent.height - 80
clip: true
GridView {
id: fileGrid
cellWidth: 150
cellHeight: 130
model: folderModel
delegate: StyledRect {
width: 140
height: 120
radius: Theme.cornerRadius
color: mouseArea.containsMouse ? Theme.surfaceVariant : "transparent"
border.color: Theme.outline
border.width: mouseArea.containsMouse ? 1 : 0
Column {
anchors.centerIn: parent
spacing: Theme.spacingXS
// Image preview or folder icon
Item {
width: 80
height: 60
anchors.horizontalCenter: parent.horizontalCenter
Image {
anchors.fill: parent
source: !model.fileIsDir ? model.fileURL : ""
fillMode: Image.PreserveAspectCrop
visible: !model.fileIsDir
asynchronous: true
}
DankIcon {
anchors.centerIn: parent
name: model.fileIsDir ? "folder" : "description"
size: Theme.iconSizeLarge
color: Theme.primary
visible: model.fileIsDir
}
}
// File name
StyledText {
text: model.fileName || ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
width: 120
elide: Text.ElideMiddle
horizontalAlignment: Text.AlignHCenter
anchors.horizontalCenter: parent.horizontalCenter
maximumLineCount: 2
wrapMode: Text.WordWrap
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (model.fileIsDir) {
navigateTo(model.filePath);
} else {
fileSelected(model.filePath);
}
}
}
}
}
}
}
}
}

View File

@@ -1,56 +0,0 @@
import QtQuick
import qs.Common
Item {
id: root
required property Item target
property int direction: Anims.direction.fadeOnly
function show() { _apply(true) }
function hide() { _apply(false) }
function _apply(showing) {
const off = Anims.slidePx
let fromX = 0
let toX = 0
switch(direction) {
case Anims.direction.fromLeft: fromX = -off; toX = 0; break
case Anims.direction.fromRight: fromX = off; toX = 0; break
default: fromX = 0; toX = 0;
}
if (showing) {
target.x = fromX
target.opacity = 0
target.visible = true
animX.from = fromX; animX.to = toX
animO.from = 0; animO.to = 1
} else {
animX.from = target.x; animX.to = (direction === Anims.direction.fromLeft ? -off :
direction === Anims.direction.fromRight ? off : 0)
animO.from = target.opacity; animO.to = 0
}
seq.restart()
}
SequentialAnimation {
id: seq
ParallelAnimation {
NumberAnimation {
id: animX
target: root.target
property: "x"
duration: Anims.durMed
easing.type: Easing.OutCubic
}
NumberAnimation {
id: animO
target: root.target
property: "opacity"
duration: Anims.durShort
}
}
ScriptAction { script: if (root.target.opacity === 0) root.target.visible = false }
}
}

98
Widgets/StateLayer.qml Normal file
View File

@@ -0,0 +1,98 @@
import QtQuick
import qs.Common
import qs.Widgets
MouseArea {
id: root
property bool disabled: false
property color stateColor: Theme.surfaceText
property real cornerRadius: parent?.radius ?? Appearance.rounding.normal
anchors.fill: parent
cursorShape: disabled ? undefined : Qt.PointingHandCursor
hoverEnabled: true
onPressed: event => {
if (disabled) return;
rippleAnimation.x = event.x;
rippleAnimation.y = event.y;
const dist = (ox, oy) => ox * ox + oy * oy;
rippleAnimation.radius = Math.sqrt(Math.max(
dist(event.x, event.y),
dist(event.x, height - event.y),
dist(width - event.x, event.y),
dist(width - event.x, height - event.y)
));
rippleAnimation.restart();
}
Rectangle {
id: hoverLayer
anchors.fill: parent
radius: root.cornerRadius
color: Qt.rgba(root.stateColor.r, root.stateColor.g, root.stateColor.b,
root.disabled ? 0 :
root.pressed ? 0.12 :
root.containsMouse ? 0.08 : 0)
Rectangle {
id: ripple
radius: width / 2
color: root.stateColor
opacity: 0
transform: Translate {
x: -ripple.width / 2
y: -ripple.height / 2
}
}
// Clip ripple to container bounds
clip: true
}
SequentialAnimation {
id: rippleAnimation
property real x
property real y
property real radius
PropertyAction {
target: ripple
property: "x"
value: rippleAnimation.x
}
PropertyAction {
target: ripple
property: "y"
value: rippleAnimation.y
}
PropertyAction {
target: ripple
property: "opacity"
value: 0.12
}
NumberAnimation {
target: ripple
properties: "width,height"
from: 0
to: rippleAnimation.radius * 2
duration: Appearance.anim.durations.normal
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.anim.curves.standardDecel
}
NumberAnimation {
target: ripple
property: "opacity"
to: 0
duration: Appearance.anim.durations.normal
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.anim.curves.standard
}
}
}

37
Widgets/StyledRect.qml Normal file
View File

@@ -0,0 +1,37 @@
import QtQuick
import qs.Common
Rectangle {
id: root
color: "transparent"
radius: Appearance.rounding.normal
Behavior on color {
ColorAnimation {
duration: Appearance.anim.durations.normal
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.anim.curves.standard
}
}
Behavior on radius {
NumberAnimation {
duration: Appearance.anim.durations.normal
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.anim.curves.standard
}
}
Behavior on opacity {
NumberAnimation {
duration: Appearance.anim.durations.normal
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.anim.curves.standard
}
}
}

35
Widgets/StyledText.qml Normal file
View File

@@ -0,0 +1,35 @@
import QtQuick
import qs.Common
Text {
id: root
color: Theme.surfaceText
font.pixelSize: Appearance.fontSize.normal
wrapMode: Text.WordWrap
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
// Font rendering improvements for crisp text
renderType: Text.NativeRendering
textFormat: Text.PlainText
antialiasing: true
Behavior on color {
ColorAnimation {
duration: Appearance.anim.durations.normal
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.anim.curves.standard
}
}
Behavior on opacity {
NumberAnimation {
duration: Appearance.anim.durations.normal
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.anim.curves.standard
}
}
}

View File

@@ -1,55 +0,0 @@
# Dynamic Theme Setup
This setup adds wallpaper-aware "Auto" theme support to your Quickshell + Niri environment.
## Prerequisites
Install the required tools:
```bash
# Required for Material-You palette generation
# Or paru -S matugen-bin on arch
cargo install matugen
# Required for JSON processing (usually pre-installed)
sudo pacman -S jq # Arch Linux
# or: sudo apt install jq # Ubuntu/Debian
# Background setters (choose one)
sudo pacman -S swaybg # Simple and reliable
```
## Setup
1. **Initial wallpaper setup:**
```bash
# Set your initial wallpaper
sudo cp ./set-wallpaper.sh /usr/local/bin
sudo chmod +x /usr/local/bin/set-wallpaper.sh
set-wallpaper.sh /path/to/your/wallpaper.jpg
```
2. **Enable Niri color integration (optional):**
Niri doesn't have a good way to just set colors, you have to edit your main `~/.config/niri/config.kdl`
The script generates suggestions in `~/quickshell/generated_niri_colors.kdl` you can manually configure in Niri.
3. **Enable Auto theme:**
Open Control Center → Theme Picker → Click the gradient "Auto" button
4. **Configure swaybg systemd unit**
```
[Unit]
PartOf=graphical-session.target
After=graphical-session.target
Requisite=graphical-session.target
[Service]
ExecStart=/usr/bin/swaybg -m fill -i "%h/quickshell/current_wallpaper"
Restart=on-failure
```
```bash
systemctl enable --user --now swaybg
```

View File

@@ -1,233 +0,0 @@
#!/bin/bash
# Diagnostic script for Qt/QML environment differences
echo "==== Qt Version ===="
qmake --version 2>/dev/null || qtpaths --qt-version 2>/dev/null || echo "qmake/qtpaths not found"
echo "\n==== Qt Platform Theme ===="
echo "QT_QPA_PLATFORMTHEME: $QT_QPA_PLATFORMTHEME"
echo "\n==== Qt Scale/Font DPI ===="
echo "QT_SCALE_FACTOR: $QT_SCALE_FACTOR"
echo "QT_FONT_DPI: $QT_FONT_DPI"
echo "GDK_SCALE: $GDK_SCALE"
echo "GDK_DPI_SCALE: $GDK_DPI_SCALE"
if command -v xrdb >/dev/null; then
echo "\n==== X11 DPI (xrdb) ===="
xrdb -query | grep dpi
fi
echo "\n==== Icon Font Availability (for cross-distro compatibility) ===="
echo "Checking icon fonts used by Quickshell Icon component..."
# Check Material Design Icons
echo -n "Material Symbols Rounded: "
if fc-list | grep -q "Material Symbols Rounded"; then
echo "✓ FOUND"
MATERIAL_SYMBOLS_FOUND=1
else
echo "✗ NOT FOUND"
MATERIAL_SYMBOLS_FOUND=0
fi
echo -n "Material Icons Round: "
if fc-list | grep -q "Material Icons Round"; then
echo "✓ FOUND"
MATERIAL_ICONS_FOUND=1
else
echo "✗ NOT FOUND"
MATERIAL_ICONS_FOUND=0
fi
# Check FontAwesome 6
echo -n "Font Awesome 6 Free: "
if fc-list | grep -q "Font Awesome 6 Free"; then
echo "✓ FOUND"
FONTAWESOME_FOUND=1
else
echo "✗ NOT FOUND"
FONTAWESOME_FOUND=0
fi
# Check JetBrains Mono Nerd Font
echo -n "JetBrainsMono Nerd Font: "
if fc-list | grep -q "JetBrainsMono Nerd Font"; then
echo "✓ FOUND"
JETBRAINS_NERD_FOUND=1
else
echo -n "✗ NOT FOUND, checking JetBrains Mono: "
if fc-list | grep -q "JetBrains Mono"; then
echo "✓ FOUND (fallback available)"
JETBRAINS_FALLBACK_FOUND=1
else
echo "✗ NOT FOUND"
JETBRAINS_FALLBACK_FOUND=0
fi
fi
echo "\n==== Icon System Recommendation ===="
if [ $MATERIAL_SYMBOLS_FOUND -eq 1 ]; then
echo "✓ OPTIMAL: Material Symbols Rounded found - best icon experience"
elif [ $MATERIAL_ICONS_FOUND -eq 1 ]; then
echo "✓ GOOD: Material Icons Round found - good icon experience"
elif [ $FONTAWESOME_FOUND -eq 1 ]; then
echo "⚠ FAIR: FontAwesome 6 found - acceptable icon experience"
elif [ $JETBRAINS_NERD_FOUND -eq 1 ] || [ $JETBRAINS_FALLBACK_FOUND -eq 1 ]; then
echo "⚠ BASIC: JetBrains Mono found - basic icon experience"
else
echo "⚠ FALLBACK: No icon fonts found - will use emoji fallback"
fi
echo "\n==== Font Installation Recommendations ===="
if [ $MATERIAL_SYMBOLS_FOUND -eq 0 ] && [ $MATERIAL_ICONS_FOUND -eq 0 ]; then
echo "📦 Install Material Design Icons for best experience:"
echo " • Ubuntu/Debian: sudo apt install fonts-material-design-icons-iconfont"
echo " • Fedora: sudo dnf install google-material-design-icons-fonts"
echo " • Arch: sudo pacman -S ttf-material-design-icons"
echo " • Or download from: https://fonts.google.com/icons"
fi
if [ $FONTAWESOME_FOUND -eq 0 ]; then
echo "📦 Install FontAwesome 6 for broader compatibility:"
echo " • Ubuntu/Debian: sudo apt install fonts-font-awesome"
echo " • Fedora: sudo dnf install fontawesome-fonts"
echo " • Arch: sudo pacman -S ttf-font-awesome"
fi
if [ "${JETBRAINS_NERD_FOUND:-0}" -eq 0 ]; then
echo "📦 Install JetBrains Mono Nerd Font for developer icons:"
echo " • Download from: https://github.com/ryanoasis/nerd-fonts/releases"
echo " • Or install via package manager if available"
fi
echo "\n==== Quickshell Icon Component Test ===="
if command -v qs >/dev/null 2>&1; then
echo "Testing Icon component fallback system..."
# Create a temporary test QML file
cat > /tmp/icon_test.qml << 'EOF'
import QtQuick
import "../Common"
Item {
Component.onCompleted: {
var icon = Qt.createQmlObject('import QtQuick; import "../Common"; Icon { name: "battery"; level: 75; charging: false; available: true }', parent)
console.log("Icon system detected:", icon.iconSystem)
console.log("Font family:", icon.font.family)
console.log("Battery icon:", icon.text)
Qt.quit()
}
}
EOF
# Test if we can run the icon test
if [ -f "../Common/Icon.qml" ]; then
echo "Running Icon component test..."
timeout 5s qs -c /tmp/icon_test.qml 2>&1 | grep -E "(Icon system|Font family|Battery icon)" || echo "Icon test failed or timed out"
else
echo "Icon.qml not found - make sure you're running from the quickshell directory"
fi
rm -f /tmp/icon_test.qml
else
echo "Quickshell (qs) not found - cannot test Icon component"
fi
echo "\n==== All Available Fonts ===="
fc-list : family | sort | uniq | grep -E 'Material|Sans|Serif|Mono|Noto|DejaVu|Roboto|Symbols|Awesome|Nerd' || echo "fc-list not found or no relevant fonts"
echo "\n==== Qt Plugins ===="
QT_DEBUG_PLUGINS=1 qtpaths --plugin-dir 2>&1 | head -20 || echo "qtpaths not found or no plugin info"
echo "\n==== QML Import Paths ===="
qtpaths --qml-imports 2>/dev/null || echo "qtpaths not found"
echo "\n==== System Info ===="
uname -a
cat /etc/os-release
echo "\n==== Graphics Drivers ===="
lspci | grep -i vga || echo "lspci not found"
echo "\n==== Wayland/X11 Session ===="
echo "XDG_SESSION_TYPE: ${XDG_SESSION_TYPE:-not set}"
echo "WAYLAND_DISPLAY: ${WAYLAND_DISPLAY:-not set}"
echo "DISPLAY: ${DISPLAY:-not set}"
if [ "$XDG_SESSION_TYPE" = "wayland" ]; then
echo "✓ Running on Wayland"
else
echo "✓ Running on X11"
fi
echo "\n==== Qt Environment Variables ===="
echo "QT_QPA_PLATFORM: ${QT_QPA_PLATFORM:-not set}"
echo "QT_WAYLAND_DECORATION: ${QT_WAYLAND_DECORATION:-not set}"
echo "QT_AUTO_SCREEN_SCALE_FACTOR: ${QT_AUTO_SCREEN_SCALE_FACTOR:-not set}"
echo "QT_ENABLE_HIGHDPI_SCALING: ${QT_ENABLE_HIGHDPI_SCALING:-not set}"
echo "\n==== Cross-Distro Compatibility Issues ===="
echo "Checking for common cross-distro problems..."
# Check for common Qt issues
if [ -z "$QT_QPA_PLATFORMTHEME" ]; then
echo "⚠ QT_QPA_PLATFORMTHEME not set - may cause theme inconsistencies"
fi
# Check for font rendering issues
if [ -z "$FONTCONFIG_PATH" ]; then
echo " FONTCONFIG_PATH not set - using system defaults"
fi
# Check for missing libraries that might cause QML issues
echo -n "Checking for essential libraries: "
MISSING_LIBS=""
for lib in libQt6Core.so.6 libQt6Gui.so.6 libQt6Qml.so.6 libQt6Quick.so.6; do
if ! ldconfig -p | grep -q "$lib"; then
MISSING_LIBS="$MISSING_LIBS $lib"
fi
done
if [ -z "$MISSING_LIBS" ]; then
echo "✓ All essential Qt6 libraries found"
else
echo "⚠ Missing libraries:$MISSING_LIBS"
echo " Install Qt6 development packages for your distro"
fi
echo "\n==== Notification System Check ===="
echo "Checking for common notification issues..."
# Check if notification daemon is running
if pgrep -x "mako" > /dev/null; then
echo "✓ Mako notification daemon running"
elif pgrep -x "dunst" > /dev/null; then
echo "✓ Dunst notification daemon running"
elif pgrep -x "swaync" > /dev/null; then
echo "✓ SwayNC notification daemon running"
else
echo "⚠ No common notification daemon detected"
fi
# Check D-Bus notification service
if busctl --user status org.freedesktop.Notifications >/dev/null 2>&1; then
echo "✓ D-Bus notification service available"
else
echo "⚠ D-Bus notification service not available"
fi
# Check for notification image format issues
echo " Common notification warnings to expect:"
echo " - 'Unable to parse pixmap as rowstride is incorrect' - Discord/Telegram images"
echo " - This is a known issue with some applications sending malformed image data"
echo " - Does not affect notification functionality, only image display"
echo "\n==== Diagnostic Summary ===="
echo "Run this script on different distros to compare environments."
echo "Save output with: ./qt_env_diagnostics.sh > my_system_info.txt"
echo "Share with developers for troubleshooting cross-distro issues."
echo ""
echo "If you see pixmap rowstride warnings, this is normal for some applications."
echo "The notification system will fall back to app icons or default icons."
# End of diagnostics

View File

@@ -1,86 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
img=$1
# Convert to absolute path to fix symlink issues
img=$(realpath "$img")
QS_DIR="$HOME/quickshell"
mkdir -p "$QS_DIR"
LINK="$QS_DIR/current_wallpaper"
ln -sf -- "$img" "$LINK"
# Kill existing swaybg processes before starting new one
pkill -f "swaybg.*$LINK" 2>/dev/null || true
swaybg -m fill -i "$LINK" & disown
json="$(matugen image "$img" --json hex)"
get() { jq -r "$1" <<<"$json"; }
bg=$(get '.colors.dark.background')
fg=$(get '.colors.dark.on_background')
primary=$(get '.colors.dark.primary')
secondary=$(get '.colors.dark.secondary')
tertiary=$(get '.colors.dark.tertiary')
tertiary_ctr=$(get '.colors.dark.tertiary_container')
error=$(get '.colors.dark.error')
inverse=$(get '.colors.dark.inverse_primary')
bg_b=$(get '.colors.light.background')
fg_b=$(get '.colors.light.on_background')
primary_b=$(get '.colors.light.primary')
secondary_b=$(get '.colors.light.secondary')
tertiary_b=$(get '.colors.light.tertiary')
tertiary_ctr_b=$(get '.colors.light.tertiary_container')
error_b=$(get '.colors.light.error')
inverse_b=$(get '.colors.light.inverse_primary')
cat >"$QS_DIR/generated_niri_colors.kdl" <<EOF
// AUTO-GENERATED on $(date)
layout {
border {
active-color "$primary"
inactive-color "$secondary"
}
focus-ring {
active-color "$inverse"
}
background-color "$bg"
}
EOF
echo "→ Niri colours: $QS_DIR/generated_niri_colors.kdl"
cat >"$QS_DIR/generated_ghostty_colors.conf" <<EOF
# AUTO-GENERATED on $(date)
background = $bg
foreground = $fg
cursor-color = $inverse
selection-background = $secondary
selection-foreground = #ffffff
palette = 0=$bg
palette = 1=$error
palette = 2=$tertiary
palette = 3=$secondary
palette = 4=$primary
palette = 5=$tertiary_ctr
palette = 6=$inverse
palette = 7=$fg
palette = 8=$bg_b
palette = 9=$error_b
palette = 10=$tertiary_b
palette = 11=$secondary_b
palette = 12=$primary_b
palette = 13=$tertiary_ctr_b
palette = 14=$inverse_b
palette = 15=$fg_b
EOF
echo "→ Ghostty theme: $QS_DIR/generated_ghostty_colors.conf"
echo " (use in ghostty: theme = $QS_DIR/generated_ghostty_colors.conf )"
niri msg action do-screen-transition --delay-ms 100
# Notify running shell about wallpaper change via IPC
qs -c "DankMaterialShell" ipc call wallpaper refresh 2>/dev/null && echo "→ Shell notified via IPC" || echo "→ Shell not running or IPC failed"

View File

@@ -1,12 +1,13 @@
//@ pragma UseQApplication //@ pragma UseQApplication
import Quickshell import Quickshell
import Quickshell.Io
import qs.Modules import qs.Modules
import qs.Modules.TopBar
import qs.Modules.AppDrawer import qs.Modules.AppDrawer
import qs.Modules.CentcomCenter import qs.Modules.CentcomCenter
import qs.Modules.ControlCenter import qs.Modules.ControlCenter
import qs.Modules.Settings import qs.Modules.Settings
import qs.Modules.TopBar
import qs.Modules.ProcessList import qs.Modules.ProcessList
import qs.Modules.ControlCenter.Network import qs.Modules.ControlCenter.Network
import qs.Modals import qs.Modals
@@ -14,6 +15,8 @@ import qs.Modals
ShellRoot { ShellRoot {
id: root id: root
WallpaperBackground {}
// Multi-monitor support using Variants // Multi-monitor support using Variants
Variants { Variants {
model: Quickshell.screens model: Quickshell.screens
@@ -21,14 +24,13 @@ ShellRoot {
delegate: TopBar { delegate: TopBar {
modelData: item modelData: item
} }
} }
// Global popup windows
CentcomPopout { CentcomPopout {
id: centcomPopout id: centcomPopout
} }
SystemTrayContextMenu { SystemTrayContextMenu {
id: systemTrayContextMenu id: systemTrayContextMenu
} }
@@ -79,7 +81,6 @@ ShellRoot {
id: settingsModal id: settingsModal
} }
// Application and clipboard components // Application and clipboard components
AppDrawerPopout { AppDrawerPopout {
id: appDrawerPopout id: appDrawerPopout
@@ -89,12 +90,39 @@ ShellRoot {
id: spotlightModal id: spotlightModal
} }
ProcessListModal { LazyLoader {
id: processListModal id: processListModalLoader
active: false
ProcessListModal {
id: processListModal
}
} }
IpcHandler {
function open() {
processListModalLoader.active = true;
if (processListModalLoader.item) {
processListModalLoader.item.show();
}
return "PROCESSLIST_OPEN_SUCCESS";
}
ClipboardHistoryModal { function close() {
id: clipboardHistoryModalPopup if (processListModalLoader.item) {
processListModalLoader.item.hide();
}
return "PROCESSLIST_CLOSE_SUCCESS";
}
function toggle() {
processListModalLoader.active = true;
if (processListModalLoader.item) {
processListModalLoader.item.toggle();
}
return "PROCESSLIST_TOGGLE_SUCCESS";
}
target: "processlist"
} }
Toast { Toast {