diff --git a/.config/hypr/hyprland/execs.conf b/.config/hypr/hyprland/execs.conf index 32a25be4..d4945605 100644 --- a/.config/hypr/hyprland/execs.conf +++ b/.config/hypr/hyprland/execs.conf @@ -1,6 +1,6 @@ # Bar, wallpaper exec-once = swww-daemon --format xrgb --no-cache -exec-once = sleep 0.5; swww img "$(cat ~/.local/state/quickshell/user/generated/wallpaper.txt)" --transition-step 100 --transition-fps 120 --transition-type grow --transition-angle 30 --transition-duration 1 +exec-once = sleep 0.5; swww img "$(cat ~/.local/state/quickshell/user/generated/wallpaper/path.txt)" --transition-step 100 --transition-fps 120 --transition-type grow --transition-angle 30 --transition-duration 1 exec-once = /usr/lib/geoclue-2.0/demos/agent & gammastep exec-once = qs & diff --git a/.config/hypr/hyprland/rules.conf b/.config/hypr/hyprland/rules.conf index 19945f98..fe1f5da4 100644 --- a/.config/hypr/hyprland/rules.conf +++ b/.config/hypr/hyprland/rules.conf @@ -119,6 +119,8 @@ layerrule = animation slide bottom, quickshell:osk layerrule = blur, quickshell:session layerrule = noanim, quickshell:session layerrule = animation fade, quickshell:notificationPopup +layerrule = blur, quickshell:backgroundWidgets +layerrule = ignorealpha 0.05, quickshell:backgroundWidgets # layerrule = blurpopups, quickshell:.* # layerrule = blur, quickshell:.* diff --git a/.config/matugen/config.toml b/.config/matugen/config.toml index ef7bf763..66c17a37 100644 --- a/.config/matugen/config.toml +++ b/.config/matugen/config.toml @@ -45,4 +45,4 @@ post_hook = '~/.config/matugen/templates/kde/kde-material-you-colors-wrapper.sh' [templates.wallpaper] input_path = '~/.config/matugen/templates/wallpaper.txt' -output_path = '~/.local/state/quickshell/user/generated/wallpaper.txt' +output_path = '~/.local/state/quickshell/user/generated/wallpaper/path.txt' diff --git a/.config/quickshell/modules/backgroundWidgets/BackgroundWidgets.qml b/.config/quickshell/modules/backgroundWidgets/BackgroundWidgets.qml new file mode 100644 index 00000000..dea41e08 --- /dev/null +++ b/.config/quickshell/modules/backgroundWidgets/BackgroundWidgets.qml @@ -0,0 +1,134 @@ +import "root:/" +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/services" +import "root:/modules/common/functions/color_utils.js" as ColorUtils +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import Quickshell.Hyprland +import Quickshell.Services.UPower + +Scope { + id: root + property string filePath: `${Directories.state}/user/generated/wallpaper/least_busy_region.json` + property real centerX: 0 + property real centerY: 0 + property color dominantColor: Appearance.m3colors.m3primary + property bool dominantColorIsDark: dominantColor.hslLightness < 0.5 + property color colBackground: ColorUtils.transparentize(ColorUtils.mix(Appearance.m3colors.m3primary, Appearance.m3colors.m3secondaryContainer), 1) + property color colText: ColorUtils.colorWithLightness(Appearance.m3colors.m3primary, (root.dominantColorIsDark ? 0.8 : 0.12)) + + function updateWidgetPosition(fileContent) { + console.log("[BackgroundWidgets] Updating widget position with content:", fileContent) + const parsedContent = JSON.parse(fileContent) + root.centerX = parsedContent.center_x + root.centerY = parsedContent.center_y + root.dominantColor = parsedContent.dominant_color || Appearance.m3colors.m3primary + } + + Timer { + id: delayedFileRead + interval: ConfigOptions.hacks.arbitraryRaceConditionDelay + running: false + onTriggered: { + root.updateWidgetPosition(leastBusyRegionFileView.text()) + } + } + + FileView { + id: leastBusyRegionFileView + path: Qt.resolvedUrl(root.filePath) + watchChanges: true + onFileChanged: { + this.reload() + delayedFileRead.start() + } + onLoadedChanged: { + const fileContent = leastBusyRegionFileView.text() + root.updateWidgetPosition(fileContent) + } + } + + Variants { // For each monitor + model: Quickshell.screens + + Loader { + required property var modelData + active: !ToplevelManager.activeToplevel?.activated + sourceComponent: PanelWindow { // Window + id: windowRoot + screen: modelData + property var textHorizontalAlignment: root.centerX < windowRoot.width / 3 ? Text.AlignLeft : + (root.centerX > windowRoot.width * 2 / 3 ? Text.AlignRight : Text.AlignHCenter) + + WlrLayershell.layer: WlrLayer.Bottom + WlrLayershell.namespace: "quickshell:backgroundWidgets" + + anchors { + top: true + bottom:true + left: true + right: true + } + color: "transparent" + HyprlandWindow.visibleMask: Region { + item: widgetBackground + } + + Rectangle { + id: widgetBackground + property real verticalPadding: 20 + property real horizontalPadding: 30 + radius: 40 + color: root.colBackground + implicitHeight: columnLayout.implicitHeight + verticalPadding * 2 + implicitWidth: columnLayout.implicitWidth + horizontalPadding * 2 + anchors { + left: parent.left + top: parent.top + leftMargin: root.centerX - implicitWidth / 2 + topMargin: root.centerY - implicitHeight / 2 + Behavior on leftMargin { + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + } + Behavior on topMargin { + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + } + } + + ColumnLayout { + id: columnLayout + anchors.centerIn: parent + spacing: -5 + + StyledText { + Layout.fillWidth: true + horizontalAlignment: windowRoot.textHorizontalAlignment + font.pixelSize: 95 + color: root.colText + style: Text.Raised + styleColor: Appearance.colors.colShadow + text: DateTime.time + } + StyledText { + Layout.fillWidth: true + horizontalAlignment: windowRoot.textHorizontalAlignment + font.pixelSize: 25 + color: root.colText + style: Text.Raised + styleColor: Appearance.colors.colShadow + text: DateTime.date + } + } + } + + } + } + + } + +} diff --git a/.config/quickshell/modules/common/functions/color_utils.js b/.config/quickshell/modules/common/functions/color_utils.js index c0ccfda9..eb0fc0c2 100644 --- a/.config/quickshell/modules/common/functions/color_utils.js +++ b/.config/quickshell/modules/common/functions/color_utils.js @@ -39,6 +39,30 @@ function colorWithSaturationOf(color1, color2) { return Qt.hsva(hue, sat, val, alpha); } +/** + * Returns a color with the given lightness and the hue, saturation, and alpha of the input color (using HSL). + * + * @param {string} color - The base color (any Qt.color-compatible string). + * @param {number} lightness - The lightness value to use (0-1). + * @returns {Qt.rgba} The resulting color. + */ +function colorWithLightness(color, lightness) { + var c = Qt.color(color); + return Qt.hsla(c.hslHue, c.hslSaturation, lightness, c.a); +} + +/** + * Returns a color with the lightness of color2 and the hue, saturation, and alpha of color1 (using HSL). + * + * @param {string} color1 - The base color (any Qt.color-compatible string). + * @param {string} color2 - The color to take lightness from. + * @returns {Qt.rgba} The resulting color. + */ +function colorWithLightnessOf(color1, color2) { + var c2 = Qt.color(color2); + return colorWithLightness(color1, c2.hslLightness); +} + /** * Adapts color1 to the accent (hue and saturation) of color2 using HSL, keeping lightness and alpha from color1. * @@ -66,7 +90,7 @@ function adaptToAccent(color1, color2) { * @param {number} percentage - The mix ratio (0-1). 1 = all color1, 0 = all color2. * @returns {Qt.rgba} The resulting mixed color. */ -function mix(color1, color2, percentage) { +function mix(color1, color2, percentage = 0.5) { var c1 = Qt.color(color1); var c2 = Qt.color(color2); return Qt.rgba(percentage * c1.r + (1 - percentage) * c2.r, percentage * c1.g + (1 - percentage) * c2.g, percentage * c1.b + (1 - percentage) * c2.b, percentage * c1.a + (1 - percentage) * c2.a); diff --git a/.config/quickshell/scripts/switchwall.sh b/.config/quickshell/scripts/switchwall.sh index 838bb8b9..adc76201 100755 --- a/.config/quickshell/scripts/switchwall.sh +++ b/.config/quickshell/scripts/switchwall.sh @@ -7,6 +7,7 @@ CONFIG_DIR="$XDG_CONFIG_HOME/quickshell" CACHE_DIR="$XDG_CACHE_HOME/quickshell" STATE_DIR="$XDG_STATE_HOME/quickshell" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +MATUGEN_DIR="$XDG_CONFIG_HOME/matugen" terminalscheme="$XDG_CONFIG_HOME/quickshell/scripts/terminal/scheme-base.json" pre_process() { @@ -26,7 +27,19 @@ pre_process() { } post_process() { - true + local screen_width="$1" + local screen_height="$2" + local wallpaper_path="$3" + + # Determine the largest region on the wallpaper that's sufficiently un-busy to put widgets in + if [ ! -f "$MATUGEN_DIR/scripts/least_busy_region.py" ]; then + echo "Error: least_busy_region.py script not found in $MATUGEN_DIR/scripts/" + else + "$MATUGEN_DIR/scripts/least_busy_region.py" \ + --screen-width "$screen_width" --screen-height "$screen_height" \ + --width 300 --height 200 \ + "$wallpaper_path" > "$STATE_DIR"/user/generated/wallpaper/least_busy_region.json + fi } check_and_prompt_upscale() { @@ -219,7 +232,10 @@ switch() { "$SCRIPT_DIR"/applycolor.sh deactivate - post_process + # Pass screen width, height, and wallpaper path to post_process + min_width_desired="$(hyprctl monitors -j | jq '([.[].width] | max)' | xargs)" + min_height_desired="$(hyprctl monitors -j | jq '([.[].height] | max)' | xargs)" + post_process "$min_width_desired" "$min_height_desired" "$imgpath" } main() { @@ -273,4 +289,4 @@ main() { switch "$imgpath" "$mode_flag" "$type_flag" "$color_flag" "$color" } -main "$@" \ No newline at end of file +main "$@" diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index e6e5d566..5d14061b 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -3,6 +3,7 @@ //@ pragma Env QT_QUICK_CONTROLS_STYLE=Basic import "./modules/common/" +import "./modules/backgroundWidgets/" import "./modules/bar/" import "./modules/cheatsheet/" import "./modules/dock/" @@ -26,6 +27,7 @@ ShellRoot { // Enable/disable modules here. False = not loaded at all, so rest assured // no unnecessary stuff will take up memory if you decide to only use, say, the overview. property bool enableBar: true + property bool enableBackgroundWidgets: true property bool enableCheatsheet: true property bool enableDock: true property bool enableMediaControls: true @@ -49,19 +51,20 @@ ShellRoot { FirstRunExperience.load() } - Loader { active: enableBar; sourceComponent: Bar {} } - Loader { active: enableCheatsheet; sourceComponent: Cheatsheet {} } - Loader { active: (enableDock && ConfigOptions?.dock.enable); sourceComponent: Dock {} } - Loader { active: enableMediaControls; sourceComponent: MediaControls {} } - Loader { active: enableNotificationPopup; sourceComponent: NotificationPopup {} } - Loader { active: enableOnScreenDisplayBrightness; sourceComponent: OnScreenDisplayBrightness {} } - Loader { active: enableOnScreenDisplayVolume; sourceComponent: OnScreenDisplayVolume {} } - Loader { active: enableOnScreenKeyboard; sourceComponent: OnScreenKeyboard {} } - Loader { active: enableOverview; sourceComponent: Overview {} } - Loader { active: enableReloadPopup; sourceComponent: ReloadPopup {} } - Loader { active: enableScreenCorners; sourceComponent: ScreenCorners {} } - Loader { active: enableSession; sourceComponent: Session {} } - Loader { active: enableSidebarLeft; sourceComponent: SidebarLeft {} } - Loader { active: enableSidebarRight; sourceComponent: SidebarRight {} } + LazyLoader { active: enableBar; component: Bar {} } + LazyLoader { active: enableBackgroundWidgets; component: BackgroundWidgets {} } + LazyLoader { active: enableCheatsheet; component: Cheatsheet {} } + LazyLoader { active: (enableDock && ConfigOptions?.dock.enable); component: Dock {} } + LazyLoader { active: enableMediaControls; component: MediaControls {} } + LazyLoader { active: enableNotificationPopup; component: NotificationPopup {} } + LazyLoader { active: enableOnScreenDisplayBrightness; component: OnScreenDisplayBrightness {} } + LazyLoader { active: enableOnScreenDisplayVolume; component: OnScreenDisplayVolume {} } + LazyLoader { active: enableOnScreenKeyboard; component: OnScreenKeyboard {} } + LazyLoader { active: enableOverview; component: Overview {} } + LazyLoader { active: enableReloadPopup; component: ReloadPopup {} } + LazyLoader { active: enableScreenCorners; component: ScreenCorners {} } + LazyLoader { active: enableSession; component: Session {} } + LazyLoader { active: enableSidebarLeft; component: SidebarLeft {} } + LazyLoader { active: enableSidebarRight; component: SidebarRight {} } }