1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-05 21:15:38 -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
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.Io
import qs.Services
import qs.Common
Singleton {
@@ -13,7 +14,7 @@ Singleton {
readonly property string _homeUrl: StandardPaths.writableLocation(StandardPaths.HomeLocation)
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"
property bool matugenAvailable: false
property string matugenJson: ""
@@ -141,6 +142,7 @@ Singleton {
root.matugenJson = out;
root.matugenColors = JSON.parse(out);
root.colorsUpdated();
generateAppConfigs();
ToastService.clearWallpaperError();
} catch (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 real osLogoBrightness: 0.5
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() {
parseSettings(settingsFile.text());
@@ -77,6 +82,11 @@ Singleton {
osLogoColorOverride = settings.osLogoColorOverride !== undefined ? settings.osLogoColorOverride : "";
osLogoBrightness = settings.osLogoBrightness !== undefined ? settings.osLogoBrightness : 0.5;
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();
detectAvailableIconThemes();
updateGtkIconTheme(iconTheme);
@@ -118,7 +128,12 @@ Singleton {
"useOSLogo": useOSLogo,
"osLogoColorOverride": osLogoColorOverride,
"osLogoBrightness": osLogoBrightness,
"osLogoContrast": osLogoContrast
"osLogoContrast": osLogoContrast,
"wallpaperPath": wallpaperPath,
"wallpaperDirectory": wallpaperDirectory,
"wallpaperDynamicTheming": wallpaperDynamicTheming,
"wallpaperLastPath": wallpaperLastPath,
"profileLastPath": profileLastPath
}, null, 2));
}
@@ -418,6 +433,43 @@ gtk-application-prefer-dark-theme=true`;
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()
onShowSystemResourcesChanged: {
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");
}
// 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
spacing: Theme.spacingM
Text {
StyledText {
text: "Clear All History?"
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
@@ -151,7 +151,7 @@ DankModal {
anchors.horizontalCenter: parent.horizontalCenter
}
Text {
StyledText {
text: "This will permanently delete all clipboard history."
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
@@ -171,7 +171,7 @@ DankModal {
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)
Text {
StyledText {
text: "Cancel"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
@@ -196,7 +196,7 @@ DankModal {
radius: Theme.cornerRadius
color: confirmClearButton.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.9) : Theme.error
Text {
StyledText {
text: "Clear All"
font.pixelSize: Theme.fontSizeMedium
color: Theme.primaryText
@@ -353,7 +353,7 @@ DankModal {
anchors.verticalCenter: parent.verticalCenter
}
Text {
StyledText {
text: `Clipboard History (${totalCount})`
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
@@ -439,7 +439,7 @@ DankModal {
model: filteredClipboardModel
spacing: Theme.spacingXS
Text {
StyledText {
text: "No clipboard entries found"
anchors.centerIn: parent
font.pixelSize: Theme.fontSizeMedium
@@ -473,7 +473,7 @@ DankModal {
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2)
anchors.verticalCenter: parent.verticalCenter
Text {
StyledText {
anchors.centerIn: parent
text: entryIndex.toString()
font.pixelSize: Theme.fontSizeSmall
@@ -509,7 +509,7 @@ DankModal {
width: parent.width - Theme.iconSize - Theme.spacingM
spacing: Theme.spacingXS
Text {
StyledText {
text: {
switch (entryType) {
case "image":
@@ -527,7 +527,7 @@ DankModal {
elide: Text.ElideRight
}
Text {
StyledText {
id: contentText
text: entryPreview

View File

@@ -61,14 +61,14 @@ DankModal {
width: parent.width - 40
spacing: Theme.spacingXS
Text {
StyledText {
text: "Network Information"
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
}
Text {
StyledText {
text: "Details for \"" + networkSSID + "\""
font.pixelSize: Theme.fontSizeMedium
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.width: 1
Text {
StyledText {
id: detailsText
anchors.fill: parent
@@ -143,7 +143,7 @@ DankModal {
radius: Theme.cornerRadius
color: closeArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
Text {
StyledText {
id: closeText
anchors.centerIn: parent

View File

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

View File

@@ -85,7 +85,7 @@ DankModal {
height: 40
spacing: Theme.spacingM
Text {
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: "System Monitor"
font.pixelSize: Theme.fontSizeLarge + 4
@@ -98,7 +98,7 @@ DankModal {
height: 1
}
Text {
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: ProcessMonitorService.processes.length + " processes"
font.pixelSize: Theme.fontSizeMedium
@@ -163,7 +163,7 @@ DankModal {
}
Text {
StyledText {
text: modelData
font.pixelSize: Theme.fontSizeLarge
font.weight: currentTab === index ? Font.Bold : Font.Medium
@@ -294,29 +294,8 @@ DankModal {
SystemTab {}
}
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.Controls
import qs.Common
import qs.Widgets
import qs.Modules.Settings
import qs.Widgets
DankModal {
id: settingsModal
@@ -51,7 +51,7 @@ DankModal {
anchors.verticalCenter: parent.verticalCenter
}
Text {
StyledText {
text: "Settings"
font.pixelSize: Theme.fontSizeXLarge
color: Theme.surfaceText
@@ -103,6 +103,16 @@ DankModal {
}
// Wallpaper Settings
SettingsSection {
title: "Wallpaper"
iconName: "wallpaper"
content: WallpaperTab {
}
}
// Clock Settings
SettingsSection {
title: "Clock & Time"

View File

@@ -4,9 +4,9 @@ import QtQuick.Effects
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Modules.AppDrawer
import qs.Services
import qs.Widgets
import qs.Modules.AppDrawer
DankModal {
id: spotlightModal
@@ -34,9 +34,6 @@ DankModal {
show();
}
// DankModal configuration
visible: spotlightOpen
width: 550
@@ -47,18 +44,15 @@ DankModal {
borderColor: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
borderWidth: 1
enableShadow: true
onVisibleChanged: {
console.log("SpotlightModal visibility changed to:", visible);
if (visible && !spotlightOpen) {
if (visible && !spotlightOpen)
show();
}
}
onBackgroundClicked: {
spotlightOpen = false;
}
Component.onCompleted: {
console.log("SpotlightModal: Component.onCompleted called - component loaded successfully!");
}
@@ -66,21 +60,41 @@ DankModal {
// App launcher logic
AppLauncher {
id: appLauncher
viewMode: Prefs.spotlightModalViewMode
gridColumns: 4
onAppLaunched: hide()
onViewModeSelected: function(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 {
Item {
anchors.fill: parent
focus: true
// Handle keyboard shortcuts
Keys.onPressed: function(event) {
if (event.key === Qt.Key_Escape) {
@@ -108,227 +122,213 @@ DankModal {
}
}
Column {
anchors.fill: parent
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
Column {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingM
DankTextField {
id: searchField
width: parent.width - 80 - Theme.spacingM // Leave space for view toggle buttons
height: 56
cornerRadius: Theme.cornerRadiusLarge
backgroundColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.7)
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;
}
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;
}
// Category selector
CategorySelector {
width: parent.width
categories: appLauncher.categories
selectedCategory: appLauncher.selectedCategory
compact: false
visible: appLauncher.categories.length > 1 || appLauncher.model.count > 0
onCategorySelected: (category) => {
return appLauncher.setCategory(category);
}
}
// View mode toggle buttons next to search bar
// Search field with view toggle buttons
Row {
spacing: Theme.spacingXS
visible: appLauncher.model.count > 0
anchors.verticalCenter: parent.verticalCenter
width: parent.width
spacing: Theme.spacingM
// 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
DankTextField {
id: searchField
DankIcon {
anchors.centerIn: parent
name: "view_list"
size: 18
color: appLauncher.viewMode === "list" ? Theme.primary : Theme.surfaceText
width: parent.width - 80 - Theme.spacingM // Leave space for view toggle buttons
height: 56
cornerRadius: Theme.cornerRadiusLarge
backgroundColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.7)
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 {
id: listViewArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
appLauncher.setViewMode("list");
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;
}
}
}
// 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");
Connections {
function onOpened() {
searchField.forceActiveFocus();
}
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
spacing: Theme.spacingXS
Text {
StyledText {
text: "Connect to Wi-Fi"
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
}
Text {
StyledText {
text: "Enter password for \"" + wifiPasswordSSID + "\""
font.pixelSize: Theme.fontSizeMedium
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
@@ -168,7 +168,7 @@ DankModal {
}
Text {
StyledText {
text: "Show password"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
@@ -195,7 +195,7 @@ DankModal {
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 1
Text {
StyledText {
id: cancelText
anchors.centerIn: parent
@@ -227,7 +227,7 @@ DankModal {
enabled: passwordInput.text.length > 0
opacity: enabled ? 1 : 0.5
Text {
StyledText {
id: connectText
anchors.centerIn: parent

View File

@@ -1,5 +1,6 @@
import QtQuick
import QtQuick.Effects
import Quickshell
import qs.Common
import qs.Widgets
@@ -12,11 +13,11 @@ Column {
width: parent.width
spacing: Theme.spacingM
Text {
StyledText {
text: "Profile Image"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
}
// Profile Image Preview with circular crop
@@ -33,34 +34,32 @@ Column {
width: 54
height: 54
// This rectangle provides the themed ring via its border.
Rectangle {
anchors.fill: parent
radius: width / 2
color: "transparent"
border.color: Theme.primary
border.width: 1 // The ring is 1px thick.
border.width: 1
visible: parent.hasImage
}
// Hidden Image loader. Its only purpose is to load the texture.
Image {
id: avatarImageSource
source: {
if (profileImageInput.text === "")
if (Prefs.profileImage === "")
return "";
if (profileImageInput.text.startsWith("/"))
return "file://" + profileImageInput.text;
if (Prefs.profileImage.startsWith("/"))
return "file://" + Prefs.profileImage;
return profileImageInput.text;
return Prefs.profileImage;
}
smooth: true
asynchronous: true
mipmap: true
cache: true
visible: false // This item is never shown directly.
visible: false
}
MultiEffect {
@@ -92,7 +91,6 @@ Column {
}
// Fallback for when there is no image.
Rectangle {
anchors.fill: parent
radius: width / 2
@@ -108,60 +106,136 @@ Column {
}
// Error icon for when the image fails to load.
DankIcon {
anchors.centerIn: parent
name: "warning"
size: Theme.iconSize + 8
color: Theme.primaryText
visible: profileImageInput.text !== "" && avatarImageSource.status === Image.Error
visible: Prefs.profileImage !== "" && avatarImageSource.status === Image.Error
}
}
// Input field
Column {
width: parent.width - 80 - Theme.spacingM
width: parent.width - 54 - Theme.spacingM
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
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 {
text: "Local filesystem path or URL to an image file."
StyledText {
text: Prefs.profileImage ? Prefs.profileImage : ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
elide: Text.ElideMiddle
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
Rectangle {
id: cpuMonitor
id: root
property bool showPercentage: true
property bool showIcon: true
property var toggleProcessList
width: 55
height: 30
@@ -24,7 +25,8 @@ Rectangle {
cursorShape: Qt.PointingHandCursor
onClicked: {
ProcessMonitorService.setSortBy("cpu");
processListPopout.toggle();
if (root.toggleProcessList)
root.toggleProcessList();
}
}
@@ -32,9 +34,8 @@ Rectangle {
anchors.centerIn: parent
spacing: 3
// CPU icon
DankIcon {
name: "memory" // Material Design memory icon (swapped from RAM widget)
name: "memory"
size: Theme.iconSize - 8
color: {
if (SystemMonitorService.cpuUsage > 80)
@@ -48,7 +49,6 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter
}
// Percentage text
Text {
text: (SystemMonitorService.cpuUsage || 0).toFixed(0) + "%"
font.pixelSize: Theme.fontSizeSmall

View File

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

View File

@@ -14,15 +14,11 @@ import qs.Services
import qs.Widgets
PanelWindow {
// Proxy objects for external connections
id: root
property var modelData
property string screenName: modelData.name
// Transparency property for the top bar background
property real backgroundTransparency: Prefs.topBarTransparency
// Notification properties
readonly property int notificationCount: NotificationService.notifications.length
screen: modelData
@@ -55,7 +51,6 @@ PanelWindow {
right: true
}
// Floating panel container with margins
Item {
anchors.fill: parent
anchors.margins: 2
@@ -132,9 +127,10 @@ PanelWindow {
LauncherButton {
anchors.verticalCenter: parent.verticalCenter
isActive: appDrawerPopout.isVisible
isActive: appDrawerPopout ? appDrawerPopout.isVisible : false
onClicked: {
appDrawerPopout.toggle();
if (appDrawerPopout)
appDrawerPopout.toggle();
}
}
@@ -238,15 +234,16 @@ PanelWindow {
}
// System Monitor Widgets
CpuMonitor {
anchors.verticalCenter: parent.verticalCenter
visible: Prefs.showSystemResources
toggleProcessList: () => processListPopout.toggle()
}
RamMonitor {
anchors.verticalCenter: parent.verticalCenter
visible: Prefs.showSystemResources
toggleProcessList: () => processListPopout.toggle()
}
NotificationCenterButton {
@@ -258,7 +255,6 @@ PanelWindow {
}
}
// Battery Widget
Battery {
anchors.verticalCenter: parent.verticalCenter
batteryPopupVisible: batteryPopout.batteryPopupVisible
@@ -268,8 +264,6 @@ PanelWindow {
}
ControlCenterButton {
// Bluetooth devices are automatically updated via signals
anchors.verticalCenter: parent.verticalCenter
isActive: controlCenterPopout.controlCenterVisible
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 |
| ddcutil (or brightnessctl) | Allows controlling brightness of monitors | No Brightness |
| 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
# 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)
```
# 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
3. Install DankMaterialShell
```
mkdir -p ~/.config/quickshell
git clone https://github.com/bbedward/DankMaterialShell.git ~/.config/quickshell/DankMaterialShell
```
5. Enable
4. Enable
```
qs -c DankMaterialShell
@@ -102,7 +71,9 @@ qs -c DankMaterialShell ipc call <target> <function>
| spotlight | toggle | Toggle spotlight (app launcher) |
| clipboard | toggle | Toggle clipboard history view |
| 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.)

View File

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

View File

@@ -1,18 +1,48 @@
import QtQuick
import qs.Common
Text {
StyledText {
id: icon
property alias name: icon.text
property alias size: icon.font.pixelSize
property alias color: icon.color
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.pixelSize: Theme.iconSize
font.weight: filled ? Font.Medium : Font.Normal
font.pixelSize: Appearance.fontSize.normal
font.weight: weight
color: Theme.surfaceText
verticalAlignment: Text.AlignVCenter
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
// Value display
Text {
StyledText {
text: slider.value + slider.unit
font.pixelSize: Theme.fontSizeMedium
color: slider.enabled ? Theme.surfaceText : Theme.surfaceVariantText
@@ -49,7 +49,7 @@ Item {
}
// Slider track
Rectangle {
StyledRect {
id: sliderTrack
property int leftIconWidth: slider.leftIcon.length > 0 ? Theme.iconSize : 0
@@ -62,7 +62,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter
// Fill
Rectangle {
StyledRect {
id: sliderFill
width: parent.width * ((slider.value - slider.minimum) / (slider.maximum - slider.minimum))
@@ -72,8 +72,8 @@ Item {
Behavior on width {
NumberAnimation {
duration: 150
easing.type: Easing.OutCubic
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
@@ -81,7 +81,7 @@ Item {
}
// Draggable handle
Rectangle {
StyledRect {
id: sliderHandle
width: 18
@@ -95,7 +95,7 @@ Item {
scale: sliderMouseArea.containsMouse || sliderMouseArea.pressed ? 1.2 : 1
// Handle glow effect when active
Rectangle {
StyledRect {
anchors.centerIn: parent
width: parent.width + 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.width: 2
visible: sliderMouseArea.containsMouse && slider.enabled
Behavior on opacity {
NumberAnimation {
duration: 150
}
}
}
Behavior on scale {
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 qs.Common
import qs.Widgets
Item {
id: toggle
@@ -16,13 +17,25 @@ Item {
width: text ? parent.width : 48
height: text ? 60 : 24
Rectangle {
StyledRect {
id: background
anchors.fill: parent
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
StateLayer {
visible: toggle.text
stateColor: Theme.primary
cornerRadius: parent.radius
onClicked: {
toggle.checked = !toggle.checked;
toggle.clicked();
toggle.toggled(toggle.checked);
}
}
}
Row {
@@ -40,16 +53,15 @@ Item {
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXS
Text {
StyledText {
text: toggle.text
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.pixelSize: Appearance.fontSize.normal
font.weight: Font.Medium
}
Text {
StyledText {
text: toggle.description
font.pixelSize: Theme.fontSizeSmall
font.pixelSize: Appearance.fontSize.small
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: Math.min(implicitWidth, toggle.width - 120)
@@ -60,7 +72,7 @@ Item {
}
Rectangle {
StyledRect {
id: toggleTrack
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)
opacity: toggle.toggling ? 0.6 : 1
Rectangle {
StyledRect {
id: toggleHandle
width: 20
@@ -82,7 +94,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter
x: toggle.checked ? parent.width - width - 2 : 2
Rectangle {
StyledRect {
anchors.centerIn: parent
width: parent.width + 2
height: parent.height + 2
@@ -95,28 +107,26 @@ Item {
Behavior on x {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.emphasizedEasing
duration: Appearance.anim.durations.normal
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.anim.curves.emphasizedDecel
}
}
}
}
MouseArea {
id: toggleArea
anchors.fill: toggle.text ? toggle : toggleTrack
hoverEnabled: true
cursorShape: toggle.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: toggle.enabled
onClicked: {
toggle.checked = !toggle.checked;
toggle.clicked();
toggle.toggled(toggle.checked);
StateLayer {
disabled: !toggle.enabled
stateColor: Theme.primary
cornerRadius: parent.radius
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
import Quickshell
import Quickshell.Io
import qs.Modules
import qs.Modules.TopBar
import qs.Modules.AppDrawer
import qs.Modules.CentcomCenter
import qs.Modules.ControlCenter
import qs.Modules.Settings
import qs.Modules.TopBar
import qs.Modules.ProcessList
import qs.Modules.ControlCenter.Network
import qs.Modals
@@ -14,6 +15,8 @@ import qs.Modals
ShellRoot {
id: root
WallpaperBackground {}
// Multi-monitor support using Variants
Variants {
model: Quickshell.screens
@@ -21,14 +24,13 @@ ShellRoot {
delegate: TopBar {
modelData: item
}
}
// Global popup windows
CentcomPopout {
id: centcomPopout
}
SystemTrayContextMenu {
id: systemTrayContextMenu
}
@@ -79,7 +81,6 @@ ShellRoot {
id: settingsModal
}
// Application and clipboard components
AppDrawerPopout {
id: appDrawerPopout
@@ -89,12 +90,39 @@ ShellRoot {
id: spotlightModal
}
ProcessListModal {
id: processListModal
LazyLoader {
id: processListModalLoader
active: false
ProcessListModal {
id: processListModal
}
}
IpcHandler {
function open() {
processListModalLoader.active = true;
if (processListModalLoader.item) {
processListModalLoader.item.show();
}
return "PROCESSLIST_OPEN_SUCCESS";
}
ClipboardHistoryModal {
id: clipboardHistoryModalPopup
function close() {
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 {