diff --git a/.github/README.md b/.github/README.md index 2020ab82b..de4c9c790 100644 --- a/.github/README.md +++ b/.github/README.md @@ -28,6 +28,7 @@
Installation (illogical-impulse Quickshell) + - _If you're new to Linux and decide to use Hyprland, you're in for a tough ride._ - Just run `bash <(curl -s https://ii.clsty.link/get)` - Or, clone this repo and run `./setup install` - See [document](https://ii.clsty.link/en/ii-qs/01setup/) for details. diff --git a/.github/workflows/dist-update-notification.yml b/.github/workflows/dist-update-notification.yml index 341a906e8..f9e971e12 100644 --- a/.github/workflows/dist-update-notification.yml +++ b/.github/workflows/dist-update-notification.yml @@ -2,8 +2,11 @@ name: Comment on Discussion When sdata/dist-arch/ Changes on: push: + branches: + - main paths: - - 'sdata/dist-arch/**' + - "sdata/dist-arch/**" + - "!sdata/dist-arch/README.md" # workflow_dispatch: jobs: diff --git a/dots-extra/fontsets/ar/conf.d b/dots-extra/fontsets/ar/fonts.conf similarity index 100% rename from dots-extra/fontsets/ar/conf.d rename to dots-extra/fontsets/ar/fonts.conf diff --git a/dots-extra/vianix/hypridle.conf b/dots-extra/vianix/hypridle.conf index 6f1858607..11abc7e4e 100644 --- a/dots-extra/vianix/hypridle.conf +++ b/dots-extra/vianix/hypridle.conf @@ -1,4 +1,4 @@ -$lock_cmd = swaylock +$lock_cmd = swaylock -c 000000 # $lock_cmd = pidof hyprlock || hyprlock $suspend_cmd = systemctl suspend || loginctl suspend diff --git a/dots/.config/fontconfig/conf.d b/dots/.config/fontconfig/fonts.conf similarity index 100% rename from dots/.config/fontconfig/conf.d rename to dots/.config/fontconfig/fonts.conf diff --git a/dots/.config/hypr/hyprland/keybinds.conf b/dots/.config/hypr/hyprland/keybinds.conf index b77166eb2..294854d67 100644 --- a/dots/.config/hypr/hyprland/keybinds.conf +++ b/dots/.config/hypr/hyprland/keybinds.conf @@ -33,7 +33,7 @@ bindd = Super, N, Toggle right sidebar, global, quickshell:sidebarRightToggle # bindd = Super, Slash, Toggle cheatsheet, global, quickshell:cheatsheetToggle # Toggle cheatsheet bindd = Super, K, Toggle on-screen keyboard, global, quickshell:oskToggle # Toggle on-screen keyboard bindd = Super, M, Toggle media controls, global, quickshell:mediaControlsToggle # Toggle media controls -bind = Super, G, global, quickshell:crosshairToggle # Toggle crosshair +bind = Super, G, global, quickshell:overlayToggle # Toggle overlay bindd = Ctrl+Alt, Delete, Toggle session menu, global, quickshell:sessionToggle # Toggle session menu bindd = Super, J, Toggle bar, global, quickshell:barToggle # Toggle bar bind = Ctrl+Alt, Delete, exec, qs -c $qsConfig ipc call TEST_ALIVE || pkill wlogout || wlogout -p layer-shell # [hidden] Session menu (fallback) diff --git a/dots/.config/hypr/hyprland/rules.conf b/dots/.config/hypr/hyprland/rules.conf index 7eb985c94..01ac1056d 100644 --- a/dots/.config/hypr/hyprland/rules.conf +++ b/dots/.config/hypr/hyprland/rules.conf @@ -134,11 +134,11 @@ layerrule = blur, quickshell:.* layerrule = ignorealpha 0.79, quickshell:.* layerrule = animation slide, quickshell:bar layerrule = animation slide bottom, quickshell:cheatsheet -layerrule = noanim, quickshell:crosshair layerrule = animation slide bottom, quickshell:dock layerrule = animation popin 120%, quickshell:screenCorners layerrule = noanim, quickshell:lockWindowPusher layerrule = animation fade, quickshell:notificationPopup +layerrule = noanim, quickshell:overlay layerrule = noanim, quickshell:overview layerrule = animation slide bottom, quickshell:osk layerrule = noanim, quickshell:polkit diff --git a/dots/.config/quickshell/ii/GlobalStates.qml b/dots/.config/quickshell/ii/GlobalStates.qml index 5cee09d96..972495c64 100644 --- a/dots/.config/quickshell/ii/GlobalStates.qml +++ b/dots/.config/quickshell/ii/GlobalStates.qml @@ -17,6 +17,7 @@ Singleton { property bool osdBrightnessOpen: false property bool osdVolumeOpen: false property bool oskOpen: false + property bool overlayOpen: false property bool overviewOpen: false property bool regionSelectorOpen: false property bool screenLocked: false diff --git a/dots/.config/quickshell/ii/modules/background/Background.qml b/dots/.config/quickshell/ii/modules/background/Background.qml index f36cf3590..7057da1cd 100644 --- a/dots/.config/quickshell/ii/modules/background/Background.qml +++ b/dots/.config/quickshell/ii/modules/background/Background.qml @@ -4,6 +4,7 @@ import qs import qs.services import qs.modules.common import qs.modules.common.widgets +import qs.modules.common.widgets.widgetCanvas import qs.modules.common.functions as CF import QtQuick import QtQuick.Layouts @@ -13,18 +14,12 @@ import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland -import qs.modules.background.cookieClock +import qs.modules.background.widgets +import qs.modules.background.widgets.clock +import qs.modules.background.widgets.weather Variants { id: root - readonly property bool fixedClockPosition: Config.options.background.clock.fixedPosition - readonly property real fixedClockX: Config.options.background.clock.x - readonly property real fixedClockY: Config.options.background.clock.y - readonly property real clockSizePadding: 20 - readonly property real screenSizePadding: 50 - readonly property string clockStyle: Config.options.background.clock.style - readonly property bool showCookieQuote: Config.options.background.showQuote && Config.options.background.quote !== "" && !GlobalStates.screenLocked && Config.options.background.clock.style === "cookie" - readonly property real clockParallaxFactor: Config.options.background.parallax.clockFactor // 0 = full parallax, 1 = no parallax model: Quickshell.screens PanelWindow { @@ -46,9 +41,9 @@ Variants { property bool wallpaperIsVideo: Config.options.background.wallpaperPath.endsWith(".mp4") || Config.options.background.wallpaperPath.endsWith(".webm") || Config.options.background.wallpaperPath.endsWith(".mkv") || Config.options.background.wallpaperPath.endsWith(".avi") || Config.options.background.wallpaperPath.endsWith(".mov") property string wallpaperPath: wallpaperIsVideo ? Config.options.background.thumbnailPath : Config.options.background.wallpaperPath property bool wallpaperSafetyTriggered: { - const enabled = Config.options.workSafety.enable.wallpaper - const sensitiveWallpaper = (CF.StringUtils.stringListContainsSubstring(wallpaperPath.toLowerCase(), Config.options.workSafety.triggerCondition.fileKeywords)) - const sensitiveNetwork = (CF.StringUtils.stringListContainsSubstring(Network.networkName.toLowerCase(), Config.options.workSafety.triggerCondition.networkNameKeywords)) + const enabled = Config.options.workSafety.enable.wallpaper; + const sensitiveWallpaper = (CF.StringUtils.stringListContainsSubstring(wallpaperPath.toLowerCase(), Config.options.workSafety.triggerCondition.fileKeywords)); + const sensitiveNetwork = (CF.StringUtils.stringListContainsSubstring(Network.networkName.toLowerCase(), Config.options.workSafety.triggerCondition.networkNameKeywords)); return enabled && sensitiveWallpaper && sensitiveNetwork; } property real wallpaperToScreenRatio: Math.min(wallpaperWidth / screen.width, wallpaperHeight / screen.height) @@ -59,18 +54,6 @@ Variants { property real movableXSpace: ((wallpaperWidth / wallpaperToScreenRatio * effectiveWallpaperScale) - screen.width) / 2 property real movableYSpace: ((wallpaperHeight / wallpaperToScreenRatio * effectiveWallpaperScale) - screen.height) / 2 readonly property bool verticalParallax: (Config.options.background.parallax.autoVertical && wallpaperHeight > wallpaperWidth) || Config.options.background.parallax.vertical - // Position - property real clockX: (modelData.width / 2) - property real clockY: (modelData.height / 2) - property var textHorizontalAlignment: { - if ((Config.options.lock.centerClock && GlobalStates.screenLocked) || wallpaperSafetyTriggered) - return Text.AlignHCenter; - if (clockX < screen.width / 3) - return Text.AlignLeft; - if (clockX > screen.width * 2 / 3) - return Text.AlignRight; - return Text.AlignHCenter; - } // Colors property bool shouldBlur: (GlobalStates.screenLocked && Config.options.lock.blur.enable) property color dominantColor: Appearance.colors.colPrimary // Default, to be changed @@ -97,8 +80,9 @@ Variants { right: true } color: { - if (!bgRoot.wallpaperSafetyTriggered || bgRoot.wallpaperIsVideo) return "transparent"; - return CF.ColorUtils.mix(Appearance.colors.colLayer0, Appearance.colors.colPrimary, 0.75) + if (!bgRoot.wallpaperSafetyTriggered || bgRoot.wallpaperIsVideo) + return "transparent"; + return CF.ColorUtils.mix(Appearance.colors.colLayer0, Appearance.colors.colPrimary, 0.75); } Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) @@ -134,53 +118,15 @@ Variants { // Oversized = can be zoomed for parallax, yay bgRoot.effectiveWallpaperScale = Math.min(bgRoot.preferredWallpaperScale, width / screenWidth, height / screenHeight); } - - bgRoot.updateClockPosition(); } } } - // Clock positioning - function updateClockPosition() { - // Somehow all this manual setting is needed to make the proc correctly use the new values - leastBusyRegionProc.path = bgRoot.wallpaperPath; - leastBusyRegionProc.contentWidth = clockLoader.implicitWidth + root.clockSizePadding * 2; - leastBusyRegionProc.contentHeight = clockLoader.implicitHeight + root.clockSizePadding * 2; - leastBusyRegionProc.horizontalPadding = bgRoot.movableXSpace + root.screenSizePadding * 2; - leastBusyRegionProc.verticalPadding = bgRoot.movableYSpace + root.screenSizePadding * 2; - leastBusyRegionProc.running = false; - leastBusyRegionProc.running = true; - } - Process { - id: leastBusyRegionProc - property string path: bgRoot.wallpaperPath - property int contentWidth: 300 - property int contentHeight: 300 - property int horizontalPadding: bgRoot.movableXSpace - property int verticalPadding: bgRoot.movableYSpace - command: [Quickshell.shellPath("scripts/images/least-busy-region-venv.sh"), "--screen-width", Math.round(bgRoot.screen.width / bgRoot.effectiveWallpaperScale), "--screen-height", Math.round(bgRoot.screen.height / bgRoot.effectiveWallpaperScale), "--width", contentWidth, "--height", contentHeight, "--horizontal-padding", horizontalPadding, "--vertical-padding", verticalPadding, path - // "--visual-output", - ,] - stdout: StdioCollector { - id: leastBusyRegionOutputCollector - onStreamFinished: { - const output = leastBusyRegionOutputCollector.text; - // console.log("[Background] Least busy region output:", output) - if (output.length === 0) - return; - const parsedContent = JSON.parse(output); - bgRoot.clockX = parsedContent.center_x * bgRoot.effectiveWallpaperScale; - bgRoot.clockY = parsedContent.center_y * bgRoot.effectiveWallpaperScale; - bgRoot.dominantColor = parsedContent.dominant_color || Appearance.colors.colPrimary; - } - } - } - - // Wallpaper Item { anchors.fill: parent clip: true + // Wallpaper StyledImage { id: wallpaper visible: opacity > 0 && !blurLoader.active @@ -261,25 +207,23 @@ Variants { } } - // The clock - Loader { - id: clockLoader - scale: Config.options.background.clock.scale - active: Config.options.background.clock.show + WidgetCanvas { + id: widgetCanvas anchors { left: wallpaper.left + right: wallpaper.right top: wallpaper.top - horizontalCenter: undefined - verticalCenter: undefined + bottom: wallpaper.bottom + readonly property real parallaxFactor: Config.options.background.parallax.widgetsFactor leftMargin: { - const clockXOnWallpaper = bgRoot.movableXSpace + ((root.fixedClockPosition ? root.fixedClockX : bgRoot.clockX * bgRoot.effectiveWallpaperScale) - implicitWidth / 2) - const extraMove = (wallpaper.effectiveValueX * 2 * bgRoot.movableXSpace) * (root.clockParallaxFactor - 1); - return clockXOnWallpaper - extraMove; + const xOnWallpaper = bgRoot.movableXSpace; + const extraMove = (wallpaper.effectiveValueX * 2 * bgRoot.movableXSpace) * (parallaxFactor - 1); + return xOnWallpaper - extraMove; } topMargin: { - const clockYOnWallpaper = bgRoot.movableYSpace + ((root.fixedClockPosition ? root.fixedClockY : bgRoot.clockY * bgRoot.effectiveWallpaperScale) - implicitHeight / 2) - const extraMove = (wallpaper.effectiveValueY * 2 * bgRoot.movableYSpace) * (root.clockParallaxFactor - 1); - return clockYOnWallpaper - extraMove; + const yOnWallpaper = bgRoot.movableYSpace; + const extraMove = (wallpaper.effectiveValueY * 2 * bgRoot.movableYSpace) * (parallaxFactor - 1); + return yOnWallpaper - extraMove; } Behavior on leftMargin { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) @@ -288,193 +232,65 @@ Variants { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } } + width: wallpaper.width + height: wallpaper.height states: State { name: "centered" - when: (GlobalStates.screenLocked && Config.options.lock.centerClock) || bgRoot.wallpaperSafetyTriggered + when: GlobalStates.screenLocked || bgRoot.wallpaperSafetyTriggered + PropertyChanges { + target: widgetCanvas + width: parent.width + height: parent.height + } AnchorChanges { - target: clockLoader + target: widgetCanvas anchors { left: undefined right: undefined top: undefined - verticalCenter: parent.verticalCenter - horizontalCenter: parent.horizontalCenter + bottom: undefined + // horizontalCenter: parent.horizontalCenter + // verticalCenter: parent.verticalCenter } } } transitions: Transition { + PropertyAnimation { + properties: "width,height" + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve + } AnchorAnimation { duration: Appearance.animation.elementMove.duration easing.type: Appearance.animation.elementMove.type easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } - sourceComponent: Column { - Loader { - id: digitalClockLoader - visible: root.clockStyle === "digital" - active: visible - sourceComponent: ColumnLayout { - id: clockColumn - spacing: 6 - ClockText { - font.pixelSize: 90 - text: DateTime.time - } - ClockText { - Layout.topMargin: -5 - text: DateTime.date - } - StyledText { - // Somehow gets fucked up if made a ClockText??? - visible: Config.options.background.showQuote && Config.options.background.quote.length > 0 - Layout.fillWidth: true - horizontalAlignment: bgRoot.textHorizontalAlignment - font { - pixelSize: Appearance.font.pixelSize.normal - weight: 350 - } - color: bgRoot.colText - style: Text.Raised - styleColor: Appearance.colors.colShadow - text: Config.options.background.quote - } - } + FadeLoader { + shown: Config.options.background.widgets.weather.enable + sourceComponent: WeatherWidget { + screenWidth: bgRoot.screen.width + screenHeight: bgRoot.screen.height + scaledScreenWidth: bgRoot.screen.width / bgRoot.effectiveWallpaperScale + scaledScreenHeight: bgRoot.screen.height / bgRoot.effectiveWallpaperScale + wallpaperScale: bgRoot.effectiveWallpaperScale } - - Loader { - id: cookieClockLoader - visible: root.clockStyle === "cookie" - active: visible - sourceComponent: CookieClock {} - } - - Loader { - id: cookieQuoteLoader - visible: root.showCookieQuote - active: visible - sourceComponent: CookieQuote {} - anchors.horizontalCenter: cookieClockLoader.horizontalCenter - } - } - Item { - anchors { - top: clockLoader.bottom - topMargin: 8 - horizontalCenter: (bgRoot.textHorizontalAlignment === Text.AlignHCenter || root.clockStyle === "cookie") ? clockLoader.horizontalCenter : undefined - left: (bgRoot.textHorizontalAlignment === Text.AlignLeft) ? clockLoader.left : undefined - right: (bgRoot.textHorizontalAlignment === Text.AlignRight) ? clockLoader.right : undefined - leftMargin: -26 - rightMargin: -26 - } - implicitWidth: statusTextBg.implicitWidth - implicitHeight: statusTextBg.implicitHeight - - StyledRectangularShadow { - target: statusTextBg - visible: statusTextBg.visible && root.clockStyle === "cookie" - opacity: statusTextBg.opacity - } - - Rectangle { - id: statusTextBg - anchors.centerIn: parent - clip: true - opacity: (safetyStatusText.shown || lockStatusText.shown) ? 1 : 0 - visible: opacity > 0 - implicitHeight: statusTextRow.implicitHeight + 5 * 2 - implicitWidth: statusTextRow.implicitWidth + 5 * 2 - radius: Appearance.rounding.small - color: CF.ColorUtils.transparentize(Appearance.colors.colSecondaryContainer, root.clockStyle === "cookie" ? 0 : 1) - - Behavior on implicitWidth { - animation: Appearance.animation.elementResize.numberAnimation.createObject(this) - } - Behavior on implicitHeight { - animation: Appearance.animation.elementResize.numberAnimation.createObject(this) - } - Behavior on opacity { - animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) - } - - RowLayout { - id: statusTextRow - anchors.centerIn: parent - spacing: 14 - Item { - Layout.fillWidth: bgRoot.textHorizontalAlignment !== Text.AlignLeft - implicitWidth: 1 - } - ClockStatusText { - id: safetyStatusText - shown: bgRoot.wallpaperSafetyTriggered - statusIcon: "hide_image" - statusText: Translation.tr("Wallpaper safety enforced") - } - ClockStatusText { - id: lockStatusText - shown: GlobalStates.screenLocked && Config.options.lock.showLockedText - statusIcon: "lock" - statusText: Translation.tr("Locked") - } - Item { - Layout.fillWidth: bgRoot.textHorizontalAlignment !== Text.AlignRight - implicitWidth: 1 - } - } + FadeLoader { + shown: Config.options.background.widgets.clock.enable + sourceComponent: ClockWidget { + screenWidth: bgRoot.screen.width + screenHeight: bgRoot.screen.height + scaledScreenWidth: bgRoot.screen.width / bgRoot.effectiveWallpaperScale + scaledScreenHeight: bgRoot.screen.height / bgRoot.effectiveWallpaperScale + wallpaperScale: bgRoot.effectiveWallpaperScale + wallpaperSafetyTriggered: bgRoot.wallpaperSafetyTriggered } } } } } - - // ComponentsCookieClock {} - component ClockText: StyledText { - Layout.fillWidth: true - horizontalAlignment: bgRoot.textHorizontalAlignment - font { - family: Appearance.font.family.expressive - pixelSize: 20 - weight: Font.DemiBold - } - color: bgRoot.colText - style: Text.Raised - styleColor: Appearance.colors.colShadow - animateChange: Config.options.background.clock.digital.animateChange - } - component ClockStatusText: Row { - id: statusTextRow - property alias statusIcon: statusIconWidget.text - property alias statusText: statusTextWidget.text - property bool shown: true - property color textColor: root.clockStyle === "cookie" ? Appearance.colors.colOnSecondaryContainer : bgRoot.colText - opacity: shown ? 1 : 0 - visible: opacity > 0 - Behavior on opacity { - animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) - } - spacing: 4 - MaterialSymbol { - id: statusIconWidget - anchors.verticalCenter: statusTextRow.verticalCenter - iconSize: Appearance.font.pixelSize.huge - color: statusTextRow.textColor - style: Text.Raised - styleColor: Appearance.colors.colShadow - } - ClockText { - id: statusTextWidget - color: statusTextRow.textColor - anchors.verticalCenter: statusTextRow.verticalCenter - font { - pixelSize: Appearance.font.pixelSize.large - weight: Font.Normal - } - style: Text.Raised - styleColor: Appearance.colors.colShadow - } - } } diff --git a/dots/.config/quickshell/ii/modules/background/widgets/AbstractBackgroundWidget.qml b/dots/.config/quickshell/ii/modules/background/widgets/AbstractBackgroundWidget.qml new file mode 100644 index 000000000..b24c33aaa --- /dev/null +++ b/dots/.config/quickshell/ii/modules/background/widgets/AbstractBackgroundWidget.qml @@ -0,0 +1,101 @@ +import QtQuick +import Quickshell +import Quickshell.Io +import qs +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets.widgetCanvas + +AbstractWidget { + id: root + + required property string configEntryName + required property int screenWidth + required property int screenHeight + required property int scaledScreenWidth + required property int scaledScreenHeight + required property real wallpaperScale + property bool visibleWhenLocked: false + property var configEntry: Config.options.background.widgets[configEntryName] + property string placementStrategy: configEntry.placementStrategy + property real targetX: Math.max(0, Math.min(configEntry.x, scaledScreenWidth - width)) + property real targetY : Math.max(0, Math.min(configEntry.y, scaledScreenHeight - height)) + x: targetX + y: targetY + visible: opacity > 0 + opacity: (GlobalStates.screenLocked && !visibleWhenLocked) ? 0 : 1 + Behavior on opacity { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + scale: (draggable && containsPress) ? 1.05 : 1 + Behavior on scale { + animation: Appearance.animation.elementResize.numberAnimation.createObject(this) + } + + draggable: placementStrategy === "free" + onReleased: { + root.targetX = root.x; + root.targetY = root.y; + configEntry.x = root.targetX; + configEntry.y = root.targetY; + } + + property bool needsColText: false + property color dominantColor: Appearance.colors.colPrimary + property bool dominantColorIsDark: dominantColor.hslLightness < 0.5 + property color colText: { + const onNormalBackground = (GlobalStates.screenLocked && Config.options.lock.blur.enable) + const adaptiveColor = ColorUtils.colorWithLightness(Appearance.colors.colPrimary, (dominantColorIsDark ? 0.8 : 0.12)) + return onNormalBackground ? Appearance.colors.colOnLayer0 : adaptiveColor; + } + + property bool wallpaperIsVideo: Config.options.background.wallpaperPath.endsWith(".mp4") || Config.options.background.wallpaperPath.endsWith(".webm") || Config.options.background.wallpaperPath.endsWith(".mkv") || Config.options.background.wallpaperPath.endsWith(".avi") || Config.options.background.wallpaperPath.endsWith(".mov") + property string wallpaperPath: wallpaperIsVideo ? Config.options.background.thumbnailPath : Config.options.background.wallpaperPath + + onWallpaperPathChanged: refreshPlacementIfNeeded() + onPlacementStrategyChanged: refreshPlacementIfNeeded() + Connections { + target: Config + function onReadyChanged() { refreshPlacementIfNeeded() } + } + function refreshPlacementIfNeeded() { + if (!Config.ready || (root.placementStrategy === "free" && root.needsColText)) return; + leastBusyRegionProc.wallpaperPath = root.wallpaperPath; + leastBusyRegionProc.running = false; + leastBusyRegionProc.running = true; + } + Process { + id: leastBusyRegionProc + property string wallpaperPath: root.wallpaperPath + // TODO: make these less arbitrary + property int contentWidth: 300 + property int contentHeight: 300 + property int horizontalPadding: 200 + property int verticalPadding: 200 + command: [Quickshell.shellPath("scripts/images/least-busy-region-venv.sh") // Comments to force the formatter to break lines + , "--screen-width", Math.round(root.scaledScreenWidth) // + , "--screen-height", Math.round(root.scaledScreenHeight) // + , "--width", contentWidth // + , "--height", contentHeight // + , "--horizontal-padding", horizontalPadding // + , "--vertical-padding", verticalPadding // + , wallpaperPath // + , ...(root.placementStrategy === "mostBusy" ? ["--busiest"] : []) + // "--visual-output", + ] + stdout: StdioCollector { + id: leastBusyRegionOutputCollector + onStreamFinished: { + const output = leastBusyRegionOutputCollector.text; + // console.log("[Background] Least busy region output:", output) + if (output.length === 0) return; + const parsedContent = JSON.parse(output); + root.dominantColor = parsedContent.dominant_color || Appearance.colors.colPrimary; + if (root.placementStrategy === "free") return; + root.targetX = parsedContent.center_x * root.wallpaperScale - root.width / 2; + root.targetY = parsedContent.center_y * root.wallpaperScale - root.height / 2; + } + } + } +} + diff --git a/dots/.config/quickshell/ii/modules/background/widgets/clock/ClockWidget.qml b/dots/.config/quickshell/ii/modules/background/widgets/clock/ClockWidget.qml new file mode 100644 index 000000000..84cc7e01b --- /dev/null +++ b/dots/.config/quickshell/ii/modules/background/widgets/clock/ClockWidget.qml @@ -0,0 +1,195 @@ +import QtQuick +import QtQuick.Layouts +import qs +import qs.services +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.modules.common.widgets.widgetCanvas +import qs.modules.background.widgets + +AbstractBackgroundWidget { + id: root + + configEntryName: "clock" + + implicitHeight: contentColumn.implicitHeight + implicitWidth: contentColumn.implicitWidth + + property string clockStyle: Config.options.background.widgets.clock.style + property bool forceCenter: (GlobalStates.screenLocked && Config.options.lock.centerClock) + property bool wallpaperSafetyTriggered: false + needsColText: clockStyle === "digital" + x: forceCenter ? ((root.screenWidth - root.width) / 2) : targetX + y: forceCenter ? ((root.screenHeight - root.height) / 2) : targetY + visibleWhenLocked: true + + property var textHorizontalAlignment: { + if (root.forceCenter) + return Text.AlignHCenter; + if (root.x < root.scaledScreenWidth / 3) + return Text.AlignLeft; + if (root.x > root.scaledScreenWidth * 2 / 3) + return Text.AlignRight; + return Text.AlignHCenter; + } + + Column { + id: contentColumn + anchors.centerIn: parent + spacing: 6 + + FadeLoader { + id: cookieClockLoader + anchors.horizontalCenter: parent.horizontalCenter + shown: root.clockStyle === "cookie" + sourceComponent: Column { + CookieClock { + anchors.horizontalCenter: parent.horizontalCenter + } + FadeLoader { + anchors.horizontalCenter: parent.horizontalCenter + shown: Config.options.background.widgets.clock.quote.enable && Config.options.background.widgets.clock.quote.text !== "" + sourceComponent: CookieQuote {} + } + } + } + + FadeLoader { + id: digitalClockLoader + anchors.horizontalCenter: parent.horizontalCenter + shown: root.clockStyle === "digital" + sourceComponent: ColumnLayout { + id: clockColumn + spacing: 6 + + ClockText { + font.pixelSize: 90 + text: DateTime.time + } + ClockText { + Layout.topMargin: -5 + text: DateTime.date + } + StyledText { + // Somehow gets fucked up if made a ClockText??? + visible: Config.options.background.widgets.clock.quote.enable && Config.options.background.widgets.clock.quote.text.length > 0 + Layout.fillWidth: true + horizontalAlignment: root.textHorizontalAlignment + font { + pixelSize: Appearance.font.pixelSize.normal + weight: 350 + } + color: root.colText + style: Text.Raised + styleColor: Appearance.colors.colShadow + text: Config.options.background.widgets.clock.quote.text + } + } + } + Item { + id: statusText + anchors.horizontalCenter: parent.horizontalCenter + implicitHeight: statusTextBg.implicitHeight + implicitWidth: statusTextBg.implicitWidth + StyledRectangularShadow { + target: statusTextBg + visible: statusTextBg.visible && root.clockStyle === "cookie" + opacity: statusTextBg.opacity + } + Rectangle { + id: statusTextBg + anchors.centerIn: parent + clip: true + opacity: (safetyStatusText.shown || lockStatusText.shown) ? 1 : 0 + visible: opacity > 0 + implicitHeight: statusTextRow.implicitHeight + 5 * 2 + implicitWidth: statusTextRow.implicitWidth + 5 * 2 + radius: Appearance.rounding.small + color: ColorUtils.transparentize(Appearance.colors.colSecondaryContainer, root.clockStyle === "cookie" ? 0 : 1) + + Behavior on implicitWidth { + animation: Appearance.animation.elementResize.numberAnimation.createObject(this) + } + Behavior on implicitHeight { + animation: Appearance.animation.elementResize.numberAnimation.createObject(this) + } + Behavior on opacity { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + + RowLayout { + id: statusTextRow + anchors.centerIn: parent + spacing: 14 + Item { + Layout.fillWidth: root.textHorizontalAlignment !== Text.AlignLeft + implicitWidth: 1 + } + ClockStatusText { + id: safetyStatusText + shown: root.wallpaperSafetyTriggered + statusIcon: "hide_image" + statusText: Translation.tr("Wallpaper safety enforced") + } + ClockStatusText { + id: lockStatusText + shown: GlobalStates.screenLocked && Config.options.lock.showLockedText + statusIcon: "lock" + statusText: Translation.tr("Locked") + } + Item { + Layout.fillWidth: root.textHorizontalAlignment !== Text.AlignRight + implicitWidth: 1 + } + } + } + } + } + + component ClockText: StyledText { + Layout.fillWidth: true + horizontalAlignment: root.textHorizontalAlignment + font { + family: Appearance.font.family.expressive + pixelSize: 20 + weight: Font.DemiBold + } + color: root.colText + style: Text.Raised + styleColor: Appearance.colors.colShadow + animateChange: Config.options.background.widgets.clock.digital.animateChange + } + component ClockStatusText: Row { + id: statusTextRow + property alias statusIcon: statusIconWidget.text + property alias statusText: statusTextWidget.text + property bool shown: true + property color textColor: root.clockStyle === "cookie" ? Appearance.colors.colOnSecondaryContainer : root.colText + opacity: shown ? 1 : 0 + visible: opacity > 0 + Behavior on opacity { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + spacing: 4 + MaterialSymbol { + id: statusIconWidget + anchors.verticalCenter: statusTextRow.verticalCenter + iconSize: Appearance.font.pixelSize.huge + color: statusTextRow.textColor + style: Text.Raised + styleColor: Appearance.colors.colShadow + } + ClockText { + id: statusTextWidget + color: statusTextRow.textColor + anchors.verticalCenter: statusTextRow.verticalCenter + font { + pixelSize: Appearance.font.pixelSize.large + weight: Font.Normal + } + style: Text.Raised + styleColor: Appearance.colors.colShadow + } + } +} diff --git a/dots/.config/quickshell/ii/modules/background/cookieClock/CookieClock.qml b/dots/.config/quickshell/ii/modules/background/widgets/clock/CookieClock.qml similarity index 72% rename from dots/.config/quickshell/ii/modules/background/cookieClock/CookieClock.qml rename to dots/.config/quickshell/ii/modules/background/widgets/clock/CookieClock.qml index a730535ec..1284e4299 100644 --- a/dots/.config/quickshell/ii/modules/background/cookieClock/CookieClock.qml +++ b/dots/.config/quickshell/ii/modules/background/widgets/clock/CookieClock.qml @@ -9,13 +9,13 @@ import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell.Io -import qs.modules.background.cookieClock.dateIndicator -import qs.modules.background.cookieClock.minuteMarks +import qs.modules.background.widgets.clock.dateIndicator +import qs.modules.background.widgets.clock.minuteMarks Item { id: root - readonly property string clockStyle: Config.options.background.clock.style + readonly property string clockStyle: Config.options.background.widgets.clock.style property real implicitSize: 230 @@ -36,16 +36,16 @@ Item { implicitHeight: implicitSize function applyStyle(sides, dialStyle, hourHandStyle, minuteHandStyle, secondHandStyle, dateStyle) { - Config.options.background.clock.cookie.sides = sides - Config.options.background.clock.cookie.dialNumberStyle = dialStyle - Config.options.background.clock.cookie.hourHandStyle = hourHandStyle - Config.options.background.clock.cookie.minuteHandStyle = minuteHandStyle - Config.options.background.clock.cookie.secondHandStyle = secondHandStyle - Config.options.background.clock.cookie.dateStyle = dateStyle + Config.options.background.widgets.clock.cookie.sides = sides + Config.options.background.widgets.clock.cookie.dialNumberStyle = dialStyle + Config.options.background.widgets.clock.cookie.hourHandStyle = hourHandStyle + Config.options.background.widgets.clock.cookie.minuteHandStyle = minuteHandStyle + Config.options.background.widgets.clock.cookie.secondHandStyle = secondHandStyle + Config.options.background.widgets.clock.cookie.dateStyle = dateStyle } function setClockPreset(category) { - if (!Config.options.background.clock.cookie.aiStyling) return; + if (!Config.options.background.widgets.clock.cookie.aiStyling) return; if (category === "") return; print("[Cookie clock] Setting clock preset for category: " + category) // "abstract", "anime", "city", "minimalist", "landscape", "plants", "person", "space" @@ -83,17 +83,12 @@ Item { } } - property bool useSineCookie: Config.options.background.clock.cookie.useSineCookie - DropShadow { - source: useSineCookie ? sineCookieLoader : roundedPolygonCookieLoader - anchors.fill: source - radius: 8 - samples: radius * 2 + 1 - color: root.colShadow - transparentBorder: true + property bool useSineCookie: Config.options.background.widgets.clock.cookie.useSineCookie + StyledDropShadow { + target: useSineCookie ? sineCookieLoader : roundedPolygonCookieLoader RotationAnimation on rotation { - running: Config.options.background.clock.cookie.constantlyRotate + running: Config.options.background.widgets.clock.cookie.constantlyRotate duration: 30000 easing.type: Easing.Linear loops: Animation.Infinite @@ -108,7 +103,7 @@ Item { active: useSineCookie sourceComponent: SineCookie { implicitSize: root.implicitSize - sides: Config.options.background.clock.cookie.sides + sides: Config.options.background.widgets.clock.cookie.sides color: root.colBackground } } @@ -119,7 +114,7 @@ Item { active: !useSineCookie sourceComponent: MaterialCookie { implicitSize: root.implicitSize - sides: Config.options.background.clock.cookie.sides + sides: Config.options.background.widgets.clock.cookie.sides color: root.colBackground } } @@ -134,7 +129,7 @@ Item { FadeLoader { id: hourMarksLoader anchors.centerIn: parent - shown: Config.options.background.clock.cookie.hourMarks + shown: Config.options.background.widgets.clock.cookie.hourMarks sourceComponent: HourMarks { implicitSize: 135 * (1.75 - 0.75 * hourMarksLoader.opacity) color: root.colOnBackground @@ -146,7 +141,7 @@ Item { FadeLoader { id: timeColumnLoader anchors.centerIn: parent - shown: Config.options.background.clock.cookie.timeIndicators + shown: Config.options.background.widgets.clock.cookie.timeIndicators scale: 1.4 - 0.4 * timeColumnLoader.shown Behavior on scale { animation: Appearance.animation.elementResize.numberAnimation.createObject(this) @@ -161,11 +156,11 @@ Item { FadeLoader { anchors.fill: parent z: 1 - shown: Config.options.background.clock.cookie.minuteHandStyle !== "hide" + shown: Config.options.background.widgets.clock.cookie.minuteHandStyle !== "hide" sourceComponent: MinuteHand { anchors.fill: parent clockMinute: root.clockMinute - style: Config.options.background.clock.cookie.minuteHandStyle + style: Config.options.background.widgets.clock.cookie.minuteHandStyle color: root.colMinuteHand } } @@ -174,11 +169,11 @@ Item { FadeLoader { anchors.fill: parent z: item?.style === "hollow" ? 0 : 2 - shown: Config.options.background.clock.cookie.hourHandStyle !== "hide" + shown: Config.options.background.widgets.clock.cookie.hourHandStyle !== "hide" sourceComponent: HourHand { clockHour: root.clockHour clockMinute: root.clockMinute - style: Config.options.background.clock.cookie.hourHandStyle + style: Config.options.background.widgets.clock.cookie.hourHandStyle color: root.colHourHand } } @@ -186,13 +181,13 @@ Item { // Second hand FadeLoader { id: secondHandLoader - z: (Config.options.background.clock.cookie.secondHandStyle === "line") ? 2 : 3 - shown: Config.options.time.secondPrecision && Config.options.background.clock.cookie.secondHandStyle !== "hide" + z: (Config.options.background.widgets.clock.cookie.secondHandStyle === "line") ? 2 : 3 + shown: Config.options.time.secondPrecision && Config.options.background.widgets.clock.cookie.secondHandStyle !== "hide" anchors.fill: parent sourceComponent: SecondHand { id: secondHand clockSecond: root.clockSecond - style: Config.options.background.clock.cookie.secondHandStyle + style: Config.options.background.widgets.clock.cookie.secondHandStyle color: root.colSecondHand } } @@ -201,9 +196,9 @@ Item { FadeLoader { z: 4 anchors.centerIn: parent - shown: Config.options.background.clock.cookie.minuteHandStyle !== "bold" + shown: Config.options.background.widgets.clock.cookie.minuteHandStyle !== "bold" sourceComponent: Rectangle { - color: Config.options.background.clock.cookie.minuteHandStyle === "medium" ? root.colBackground : root.colMinuteHand + color: Config.options.background.widgets.clock.cookie.minuteHandStyle === "medium" ? root.colBackground : root.colMinuteHand implicitWidth: 6 implicitHeight: implicitWidth radius: width / 2 @@ -213,11 +208,11 @@ Item { // Date FadeLoader { anchors.fill: parent - shown: Config.options.background.clock.cookie.dateStyle !== "hide" + shown: Config.options.background.widgets.clock.cookie.dateStyle !== "hide" sourceComponent: DateIndicator { color: root.colBackgroundInfo - style: Config.options.background.clock.cookie.dateStyle + style: Config.options.background.widgets.clock.cookie.dateStyle } } } diff --git a/dots/.config/quickshell/ii/modules/background/cookieClock/CookieQuote.qml b/dots/.config/quickshell/ii/modules/background/widgets/clock/CookieQuote.qml similarity index 94% rename from dots/.config/quickshell/ii/modules/background/cookieClock/CookieQuote.qml rename to dots/.config/quickshell/ii/modules/background/widgets/clock/CookieQuote.qml index 45b726fa3..19567aeb4 100644 --- a/dots/.config/quickshell/ii/modules/background/cookieClock/CookieQuote.qml +++ b/dots/.config/quickshell/ii/modules/background/widgets/clock/CookieQuote.qml @@ -7,7 +7,7 @@ import Qt5Compat.GraphicalEffects Item { id: root - readonly property string quoteText: Config.options.background.quote + readonly property string quoteText: Config.options.background.widgets.clock.quote.text implicitWidth: quoteBox.implicitWidth implicitHeight: quoteBox.implicitHeight @@ -47,7 +47,7 @@ Item { StyledText { id: quoteStyledText horizontalAlignment: Text.AlignLeft - text: Config.options.background.quote + text: Config.options.background.widgets.clock.quote.text color: Appearance.colors.colOnSecondaryContainer font { family: Appearance.font.family.reading diff --git a/dots/.config/quickshell/ii/modules/background/cookieClock/HourHand.qml b/dots/.config/quickshell/ii/modules/background/widgets/clock/HourHand.qml similarity index 100% rename from dots/.config/quickshell/ii/modules/background/cookieClock/HourHand.qml rename to dots/.config/quickshell/ii/modules/background/widgets/clock/HourHand.qml diff --git a/dots/.config/quickshell/ii/modules/background/cookieClock/HourMarks.qml b/dots/.config/quickshell/ii/modules/background/widgets/clock/HourMarks.qml similarity index 100% rename from dots/.config/quickshell/ii/modules/background/cookieClock/HourMarks.qml rename to dots/.config/quickshell/ii/modules/background/widgets/clock/HourMarks.qml diff --git a/dots/.config/quickshell/ii/modules/background/cookieClock/MinuteHand.qml b/dots/.config/quickshell/ii/modules/background/widgets/clock/MinuteHand.qml similarity index 100% rename from dots/.config/quickshell/ii/modules/background/cookieClock/MinuteHand.qml rename to dots/.config/quickshell/ii/modules/background/widgets/clock/MinuteHand.qml diff --git a/dots/.config/quickshell/ii/modules/background/cookieClock/SecondHand.qml b/dots/.config/quickshell/ii/modules/background/widgets/clock/SecondHand.qml similarity index 94% rename from dots/.config/quickshell/ii/modules/background/cookieClock/SecondHand.qml rename to dots/.config/quickshell/ii/modules/background/widgets/clock/SecondHand.qml index 93ca1cf48..2c436a292 100644 --- a/dots/.config/quickshell/ii/modules/background/cookieClock/SecondHand.qml +++ b/dots/.config/quickshell/ii/modules/background/widgets/clock/SecondHand.qml @@ -18,7 +18,7 @@ Item { rotation: (360 / 60 * clockSecond) + 90 Behavior on rotation { - enabled: Config.options.background.clock.cookie.constantlyRotate // Animating every second is expensive... + enabled: Config.options.background.widgets.clock.cookie.constantlyRotate // Animating every second is expensive... animation: RotationAnimation { direction: RotationAnimation.Clockwise duration: 1000 // 1 second diff --git a/dots/.config/quickshell/ii/modules/background/cookieClock/TimeColumn.qml b/dots/.config/quickshell/ii/modules/background/widgets/clock/TimeColumn.qml similarity index 86% rename from dots/.config/quickshell/ii/modules/background/cookieClock/TimeColumn.qml rename to dots/.config/quickshell/ii/modules/background/widgets/clock/TimeColumn.qml index e2d00c23c..6b73c851a 100644 --- a/dots/.config/quickshell/ii/modules/background/cookieClock/TimeColumn.qml +++ b/dots/.config/quickshell/ii/modules/background/widgets/clock/TimeColumn.qml @@ -8,10 +8,10 @@ import QtQuick Column { id: root property list clockNumbers: DateTime.time.split(/[: ]/) - property bool isEnabled: Config.options.background.clock.cookie.timeIndicators + property bool isEnabled: Config.options.background.widgets.clock.cookie.timeIndicators property color color: Appearance.colors.colOnSecondaryContainer - property bool hourMarksEnabled: Config.options.background.clock.cookie.hourMarks + property bool hourMarksEnabled: Config.options.background.widgets.clock.cookie.hourMarks spacing: -16 Repeater { diff --git a/dots/.config/quickshell/ii/modules/background/cookieClock/dateIndicator/BubbleDate.qml b/dots/.config/quickshell/ii/modules/background/widgets/clock/dateIndicator/BubbleDate.qml similarity index 100% rename from dots/.config/quickshell/ii/modules/background/cookieClock/dateIndicator/BubbleDate.qml rename to dots/.config/quickshell/ii/modules/background/widgets/clock/dateIndicator/BubbleDate.qml diff --git a/dots/.config/quickshell/ii/modules/background/cookieClock/dateIndicator/DateIndicator.qml b/dots/.config/quickshell/ii/modules/background/widgets/clock/dateIndicator/DateIndicator.qml similarity index 96% rename from dots/.config/quickshell/ii/modules/background/cookieClock/dateIndicator/DateIndicator.qml rename to dots/.config/quickshell/ii/modules/background/widgets/clock/dateIndicator/DateIndicator.qml index 66b1852ee..820a96dea 100644 --- a/dots/.config/quickshell/ii/modules/background/cookieClock/dateIndicator/DateIndicator.qml +++ b/dots/.config/quickshell/ii/modules/background/widgets/clock/dateIndicator/DateIndicator.qml @@ -14,7 +14,7 @@ Item { // Rotating date FadeLoader { anchors.fill: parent - shown: Config.options.background.clock.cookie.dateStyle === "border" + shown: Config.options.background.widgets.clock.cookie.dateStyle === "border" sourceComponent: RotatingDate { color: root.color } diff --git a/dots/.config/quickshell/ii/modules/background/cookieClock/dateIndicator/RectangleDate.qml b/dots/.config/quickshell/ii/modules/background/widgets/clock/dateIndicator/RectangleDate.qml similarity index 92% rename from dots/.config/quickshell/ii/modules/background/cookieClock/dateIndicator/RectangleDate.qml rename to dots/.config/quickshell/ii/modules/background/widgets/clock/dateIndicator/RectangleDate.qml index d8ea9ab2e..0c09db194 100644 --- a/dots/.config/quickshell/ii/modules/background/cookieClock/dateIndicator/RectangleDate.qml +++ b/dots/.config/quickshell/ii/modules/background/widgets/clock/dateIndicator/RectangleDate.qml @@ -6,7 +6,7 @@ import QtQuick Rectangle { id: rect - readonly property string dialStyle: Config.options.background.clock.cookie.dialNumberStyle + readonly property string dialStyle: Config.options.background.widgets.clock.cookie.dialNumberStyle StyledText { anchors.centerIn: parent diff --git a/dots/.config/quickshell/ii/modules/background/cookieClock/dateIndicator/RotatingDate.qml b/dots/.config/quickshell/ii/modules/background/widgets/clock/dateIndicator/RotatingDate.qml similarity index 90% rename from dots/.config/quickshell/ii/modules/background/cookieClock/dateIndicator/RotatingDate.qml rename to dots/.config/quickshell/ii/modules/background/widgets/clock/dateIndicator/RotatingDate.qml index ec25c024f..3c8354d74 100644 --- a/dots/.config/quickshell/ii/modules/background/cookieClock/dateIndicator/RotatingDate.qml +++ b/dots/.config/quickshell/ii/modules/background/widgets/clock/dateIndicator/RotatingDate.qml @@ -8,14 +8,14 @@ import QtQuick Item { id: root - property string style: Config.options.background.clock.cookie.dateStyle + property string style: Config.options.background.widgets.clock.cookie.dateStyle property color color: Appearance.colors.colOnSecondaryContainer property real angleStep: 12 * Math.PI / 180 property string dateText: Qt.locale().toString(DateTime.clock.date, "ddd dd") readonly property int clockSecond: DateTime.clock.seconds - readonly property string dialStyle: Config.options.background.clock.cookie.dialNumberStyle - readonly property bool timeIndicators: Config.options.background.clock.cookie.timeIndicators + readonly property string dialStyle: Config.options.background.widgets.clock.cookie.dialNumberStyle + readonly property bool timeIndicators: Config.options.background.widgets.clock.cookie.timeIndicators property real radius: style === "border" ? 90 : 0 Behavior on radius { diff --git a/dots/.config/quickshell/ii/modules/background/cookieClock/minuteMarks/BigHourNumbers.qml b/dots/.config/quickshell/ii/modules/background/widgets/clock/minuteMarks/BigHourNumbers.qml similarity index 100% rename from dots/.config/quickshell/ii/modules/background/cookieClock/minuteMarks/BigHourNumbers.qml rename to dots/.config/quickshell/ii/modules/background/widgets/clock/minuteMarks/BigHourNumbers.qml diff --git a/dots/.config/quickshell/ii/modules/background/cookieClock/minuteMarks/Dots.qml b/dots/.config/quickshell/ii/modules/background/widgets/clock/minuteMarks/Dots.qml similarity index 100% rename from dots/.config/quickshell/ii/modules/background/cookieClock/minuteMarks/Dots.qml rename to dots/.config/quickshell/ii/modules/background/widgets/clock/minuteMarks/Dots.qml diff --git a/dots/.config/quickshell/ii/modules/background/cookieClock/minuteMarks/Lines.qml b/dots/.config/quickshell/ii/modules/background/widgets/clock/minuteMarks/Lines.qml similarity index 100% rename from dots/.config/quickshell/ii/modules/background/cookieClock/minuteMarks/Lines.qml rename to dots/.config/quickshell/ii/modules/background/widgets/clock/minuteMarks/Lines.qml diff --git a/dots/.config/quickshell/ii/modules/background/cookieClock/minuteMarks/MinuteMarks.qml b/dots/.config/quickshell/ii/modules/background/widgets/clock/minuteMarks/MinuteMarks.qml similarity index 84% rename from dots/.config/quickshell/ii/modules/background/cookieClock/minuteMarks/MinuteMarks.qml rename to dots/.config/quickshell/ii/modules/background/widgets/clock/minuteMarks/MinuteMarks.qml index 07cf0dc5e..02d8f2582 100644 --- a/dots/.config/quickshell/ii/modules/background/cookieClock/minuteMarks/MinuteMarks.qml +++ b/dots/.config/quickshell/ii/modules/background/widgets/clock/minuteMarks/MinuteMarks.qml @@ -8,8 +8,8 @@ Item { id: root property color color: Appearance.colors.colOnSecondaryContainer - property string style: Config.options.background.clock.cookie.dialNumberStyle // "dots", "numbers", "full", "hide" - property string dateStyle : Config.options.background.clock.cookie.dateStyle + property string style: Config.options.background.widgets.clock.cookie.dialNumberStyle // "dots", "numbers", "full", "hide" + property string dateStyle : Config.options.background.widgets.clock.cookie.dateStyle // 12 Dots FadeLoader { diff --git a/dots/.config/quickshell/ii/modules/background/widgets/weather/WeatherWidget.qml b/dots/.config/quickshell/ii/modules/background/widgets/weather/WeatherWidget.qml new file mode 100644 index 000000000..322772cbe --- /dev/null +++ b/dots/.config/quickshell/ii/modules/background/widgets/weather/WeatherWidget.qml @@ -0,0 +1,58 @@ +import QtQuick +import qs +import qs.services +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.modules.common.widgets.widgetCanvas +import qs.modules.background.widgets + +AbstractBackgroundWidget { + id: root + + configEntryName: "weather" + + implicitHeight: backgroundShape.implicitHeight + implicitWidth: backgroundShape.implicitWidth + + StyledDropShadow { + target: backgroundShape + } + + MaterialShape { + id: backgroundShape + anchors.fill: parent + shape: MaterialShape.Shape.Pill + color: Appearance.colors.colPrimaryContainer + implicitSize: 200 + + StyledText { + font { + pixelSize: 80 + family: Appearance.font.family.expressive + weight: Font.Medium + } + color: Appearance.colors.colPrimary + text: Weather.data?.temp.substring(0,Weather.data?.temp.length - 1) ?? "--°" + anchors { + right: parent.right + top: parent.top + rightMargin: 16 + topMargin: 20 + } + } + + MaterialSymbol { + iconSize: 80 + color: Appearance.colors.colOnPrimaryContainer + text: Icons.getWeatherIcon(Weather.data.wCode) ?? "cloud" + anchors { + left: parent.left + bottom: parent.bottom + + leftMargin: 16 + bottomMargin: 20 + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/bar/BatteryPopup.qml b/dots/.config/quickshell/ii/modules/bar/BatteryPopup.qml index 5bc07d18c..26eda569e 100644 --- a/dots/.config/quickshell/ii/modules/bar/BatteryPopup.qml +++ b/dots/.config/quickshell/ii/modules/bar/BatteryPopup.qml @@ -13,121 +13,60 @@ StyledPopup { spacing: 4 // Header - Row { - id: header - spacing: 5 - - MaterialSymbol { - anchors.verticalCenter: parent.verticalCenter - fill: 0 - font.weight: Font.Medium - text: "battery_android_full" - iconSize: Appearance.font.pixelSize.large - color: Appearance.colors.colOnSurfaceVariant - } - - StyledText { - anchors.verticalCenter: parent.verticalCenter - text: "Battery" - font { - weight: Font.Medium - pixelSize: Appearance.font.pixelSize.normal - } - color: Appearance.colors.colOnSurfaceVariant - } + StyledPopupHeaderRow { + icon: "battery_android_full" + label: Translation.tr("Battery") } - // This row is hidden when the battery is full. - RowLayout { - spacing: 5 - Layout.fillWidth: true - property bool rowVisible: { + StyledPopupValueRow { + visible: { let timeValue = Battery.isCharging ? Battery.timeToFull : Battery.timeToEmpty; let power = Battery.energyRate; return !(Battery.chargeState == 4 || timeValue <= 0 || power <= 0.01); } - visible: rowVisible - opacity: rowVisible ? 1 : 0 - Behavior on opacity { - NumberAnimation { - duration: 500 + icon: "schedule" + label: Battery.isCharging ? Translation.tr("Time to full:") : Translation.tr("Time to empty:") + value: { + function formatTime(seconds) { + var h = Math.floor(seconds / 3600); + var m = Math.floor((seconds % 3600) / 60); + if (h > 0) + return `${h}h, ${m}m`; + else + return `${m}m`; + } + if (Battery.isCharging) + return formatTime(Battery.timeToFull); + else + return formatTime(Battery.timeToEmpty); + } + } + + StyledPopupValueRow { + visible: !(Battery.chargeState != 4 && Battery.energyRate == 0) + icon: "bolt" + label: { + if (Battery.chargeState == 4) { + return Translation.tr("Fully charged"); + } else if (Battery.chargeState == 1) { + return Translation.tr("Charging:"); + } else { + return Translation.tr("Discharging:"); } } - - MaterialSymbol { - text: "schedule" - color: Appearance.colors.colOnSurfaceVariant - iconSize: Appearance.font.pixelSize.large - } - StyledText { - text: Battery.isCharging ? Translation.tr("Time to full:") : Translation.tr("Time to empty:") - color: Appearance.colors.colOnSurfaceVariant - } - StyledText { - Layout.fillWidth: true - horizontalAlignment: Text.AlignRight - color: Appearance.colors.colOnSurfaceVariant - text: { - function formatTime(seconds) { - var h = Math.floor(seconds / 3600); - var m = Math.floor((seconds % 3600) / 60); - if (h > 0) - return `${h}h, ${m}m`; - else - return `${m}m`; - } - if (Battery.isCharging) - return formatTime(Battery.timeToFull); - else - return formatTime(Battery.timeToEmpty); + value: { + if (Battery.chargeState == 4) { + return ""; + } else { + return `${Battery.energyRate.toFixed(2)}W`; } } } - RowLayout { - spacing: 5 - Layout.fillWidth: true - - property bool rowVisible: !(Battery.chargeState != 4 && Battery.energyRate == 0) - visible: rowVisible - opacity: rowVisible ? 1 : 0 - Behavior on opacity { - NumberAnimation { - duration: 500 - } - } - - MaterialSymbol { - text: "bolt" - color: Appearance.colors.colOnSurfaceVariant - iconSize: Appearance.font.pixelSize.large - } - - StyledText { - text: { - if (Battery.chargeState == 4) { - return Translation.tr("Fully charged"); - } else if (Battery.chargeState == 1) { - return Translation.tr("Charging:"); - } else { - return Translation.tr("Discharging:"); - } - } - color: Appearance.colors.colOnSurfaceVariant - } - - StyledText { - Layout.fillWidth: true - horizontalAlignment: Text.AlignRight - color: Appearance.colors.colOnSurfaceVariant - text: { - if (Battery.chargeState == 4) { - return ""; - } else { - return `${Battery.energyRate.toFixed(2)}W`; - } - } - } + StyledPopupValueRow { + icon: "heart_check" + label: Translation.tr("Health:") + value: `${(Battery.health).toFixed(1)}%` } } } diff --git a/dots/.config/quickshell/ii/modules/bar/ClockWidget.qml b/dots/.config/quickshell/ii/modules/bar/ClockWidget.qml index 4d6fb61f2..895ad9be0 100644 --- a/dots/.config/quickshell/ii/modules/bar/ClockWidget.qml +++ b/dots/.config/quickshell/ii/modules/bar/ClockWidget.qml @@ -43,7 +43,7 @@ Item { hoverEnabled: true acceptedButtons: Qt.NoButton - ClockWidgetTooltip { + ClockWidgetPopup { hoverTarget: mouseArea } } diff --git a/dots/.config/quickshell/ii/modules/bar/ClockWidgetPopup.qml b/dots/.config/quickshell/ii/modules/bar/ClockWidgetPopup.qml new file mode 100644 index 000000000..c23edb7b4 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/bar/ClockWidgetPopup.qml @@ -0,0 +1,70 @@ +import qs.modules.common +import qs.modules.common.widgets +import qs.services +import QtQuick +import QtQuick.Layouts + +StyledPopup { + id: root + property string formattedDate: Qt.locale().toString(DateTime.clock.date, "dddd, MMMM dd, yyyy") + property string formattedTime: DateTime.time + property string formattedUptime: DateTime.uptime + property string todosSection: getUpcomingTodos() + + function getUpcomingTodos() { + const unfinishedTodos = Todo.list.filter(function (item) { + return !item.done; + }); + if (unfinishedTodos.length === 0) { + return Translation.tr("No pending tasks"); + } + + // Limit to first 5 todos to keep popup manageable + const limitedTodos = unfinishedTodos.slice(0, 5); + let todoText = limitedTodos.map(function (item, index) { + return ` ${index + 1}. ${item.content}`; + }).join('\n'); + + if (unfinishedTodos.length > 5) { + todoText += `\n ${Translation.tr("... and %1 more").arg(unfinishedTodos.length - 5)}`; + } + + return todoText; + } + + ColumnLayout { + id: columnLayout + anchors.centerIn: parent + spacing: 4 + + StyledPopupHeaderRow { + icon: "calendar_month" + label: root.formattedDate + } + + StyledPopupValueRow { + icon: "timelapse" + label: Translation.tr("System uptime:") + value: root.formattedUptime + } + + // Tasks + Column { + spacing: 0 + Layout.fillWidth: true + + StyledPopupValueRow { + icon: "checklist" + label: Translation.tr("To Do:") + value: "" + } + + StyledText { + horizontalAlignment: Text.AlignLeft + wrapMode: Text.Wrap + color: Appearance.colors.colOnSurfaceVariant + text: root.todosSection + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/bar/ClockWidgetTooltip.qml b/dots/.config/quickshell/ii/modules/bar/ClockWidgetTooltip.qml deleted file mode 100644 index 734d89b22..000000000 --- a/dots/.config/quickshell/ii/modules/bar/ClockWidgetTooltip.qml +++ /dev/null @@ -1,110 +0,0 @@ -import qs.modules.common -import qs.modules.common.widgets -import qs.services -import QtQuick -import QtQuick.Layouts - -StyledPopup { - id: root - property string formattedDate: Qt.locale().toString(DateTime.clock.date, "dddd, MMMM dd, yyyy") - property string formattedTime: DateTime.time - property string formattedUptime: DateTime.uptime - property string todosSection: getUpcomingTodos() - - function getUpcomingTodos() { - const unfinishedTodos = Todo.list.filter(function (item) { - return !item.done; - }); - if (unfinishedTodos.length === 0) { - return Translation.tr("No pending tasks"); - } - - // Limit to first 5 todos to keep popup manageable - const limitedTodos = unfinishedTodos.slice(0, 5); - let todoText = limitedTodos.map(function (item, index) { - return `${index + 1}. ${item.content}`; - }).join('\n'); - - if (unfinishedTodos.length > 5) { - todoText += `\n${Translation.tr("... and %1 more").arg(unfinishedTodos.length - 5)}`; - } - - return todoText; - } - - ColumnLayout { - id: columnLayout - anchors.centerIn: parent - spacing: 4 - - // Date + Time row - Row { - spacing: 5 - - MaterialSymbol { - anchors.verticalCenter: parent.verticalCenter - fill: 0 - font.weight: Font.Medium - text: "calendar_month" - iconSize: Appearance.font.pixelSize.large - color: Appearance.colors.colOnSurfaceVariant - } - StyledText { - anchors.verticalCenter: parent.verticalCenter - horizontalAlignment: Text.AlignLeft - color: Appearance.colors.colOnSurfaceVariant - text: `${root.formattedDate}` - font.weight: Font.Medium - } - } - - // Uptime row - RowLayout { - spacing: 5 - Layout.fillWidth: true - MaterialSymbol { - text: "timelapse" - color: Appearance.colors.colOnSurfaceVariant - font.pixelSize: Appearance.font.pixelSize.large - } - StyledText { - text: Translation.tr("System uptime:") - color: Appearance.colors.colOnSurfaceVariant - } - StyledText { - Layout.fillWidth: true - horizontalAlignment: Text.AlignRight - color: Appearance.colors.colOnSurfaceVariant - text: root.formattedUptime - } - } - - // Tasks - Column { - spacing: 0 - Layout.fillWidth: true - - Row { - spacing: 4 - MaterialSymbol { - anchors.verticalCenter: parent.verticalCenter - text: "checklist" - color: Appearance.colors.colOnSurfaceVariant - font.pixelSize: Appearance.font.pixelSize.large - } - StyledText { - anchors.verticalCenter: parent.verticalCenter - text: Translation.tr("To Do:") - color: Appearance.colors.colOnSurfaceVariant - } - } - - StyledText { - horizontalAlignment: Text.AlignLeft - wrapMode: Text.Wrap - color: Appearance.colors.colOnSurfaceVariant - text: root.todosSection - } - } - } -} diff --git a/dots/.config/quickshell/ii/modules/bar/ResourcesPopup.qml b/dots/.config/quickshell/ii/modules/bar/ResourcesPopup.qml index 1cac240d8..40ed72756 100644 --- a/dots/.config/quickshell/ii/modules/bar/ResourcesPopup.qml +++ b/dots/.config/quickshell/ii/modules/bar/ResourcesPopup.qml @@ -12,57 +12,6 @@ StyledPopup { return (kb / (1024 * 1024)).toFixed(1) + " GB"; } - component ResourceItem: RowLayout { - id: resourceItem - required property string icon - required property string label - required property string value - spacing: 4 - - MaterialSymbol { - text: resourceItem.icon - color: Appearance.colors.colOnSurfaceVariant - iconSize: Appearance.font.pixelSize.large - } - StyledText { - text: resourceItem.label - color: Appearance.colors.colOnSurfaceVariant - } - StyledText { - Layout.fillWidth: true - horizontalAlignment: Text.AlignRight - visible: resourceItem.value !== "" - color: Appearance.colors.colOnSurfaceVariant - text: resourceItem.value - } - } - - component ResourceHeaderItem: Row { - id: headerItem - required property var icon - required property var label - spacing: 5 - - MaterialSymbol { - anchors.verticalCenter: parent.verticalCenter - fill: 0 - font.weight: Font.Medium - text: headerItem.icon - iconSize: Appearance.font.pixelSize.large - color: Appearance.colors.colOnSurfaceVariant - } - - StyledText { - anchors.verticalCenter: parent.verticalCenter - text: headerItem.label - font { - weight: Font.Medium - pixelSize: Appearance.font.pixelSize.normal - } - color: Appearance.colors.colOnSurfaceVariant - } - } - Row { anchors.centerIn: parent spacing: 12 @@ -71,23 +20,23 @@ StyledPopup { anchors.top: parent.top spacing: 8 - ResourceHeaderItem { + StyledPopupHeaderRow { icon: "memory" label: "RAM" } Column { spacing: 4 - ResourceItem { + StyledPopupValueRow { icon: "clock_loader_60" label: Translation.tr("Used:") value: root.formatKB(ResourceUsage.memoryUsed) } - ResourceItem { + StyledPopupValueRow { icon: "check_circle" label: Translation.tr("Free:") value: root.formatKB(ResourceUsage.memoryFree) } - ResourceItem { + StyledPopupValueRow { icon: "empty_dashboard" label: Translation.tr("Total:") value: root.formatKB(ResourceUsage.memoryTotal) @@ -100,23 +49,23 @@ StyledPopup { anchors.top: parent.top spacing: 8 - ResourceHeaderItem { + StyledPopupHeaderRow { icon: "swap_horiz" label: "Swap" } Column { spacing: 4 - ResourceItem { + StyledPopupValueRow { icon: "clock_loader_60" label: Translation.tr("Used:") value: root.formatKB(ResourceUsage.swapUsed) } - ResourceItem { + StyledPopupValueRow { icon: "check_circle" label: Translation.tr("Free:") value: root.formatKB(ResourceUsage.swapFree) } - ResourceItem { + StyledPopupValueRow { icon: "empty_dashboard" label: Translation.tr("Total:") value: root.formatKB(ResourceUsage.swapTotal) @@ -128,16 +77,16 @@ StyledPopup { anchors.top: parent.top spacing: 8 - ResourceHeaderItem { + StyledPopupHeaderRow { icon: "planner_review" label: "CPU" } Column { spacing: 4 - ResourceItem { + StyledPopupValueRow { icon: "bolt" label: Translation.tr("Load:") - value: (ResourceUsage.cpuUsage > 0.8 ? Translation.tr("High") : ResourceUsage.cpuUsage > 0.4 ? Translation.tr("Medium") : Translation.tr("Low")) + ` (${Math.round(ResourceUsage.cpuUsage * 100)}%)` + value: `${Math.round(ResourceUsage.cpuUsage * 100)}%` } } } diff --git a/dots/.config/quickshell/ii/modules/bar/StyledPopupHeaderRow.qml b/dots/.config/quickshell/ii/modules/bar/StyledPopupHeaderRow.qml new file mode 100644 index 000000000..f5c7ba445 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/bar/StyledPopupHeaderRow.qml @@ -0,0 +1,30 @@ +import QtQuick +import QtQuick.Layouts +import qs.modules.common +import qs.modules.common.widgets + +Row { + id: root + required property var icon + required property var label + spacing: 5 + + MaterialSymbol { + anchors.verticalCenter: parent.verticalCenter + fill: 0 + font.weight: Font.DemiBold + text: root.icon + iconSize: Appearance.font.pixelSize.large + color: Appearance.colors.colOnSurfaceVariant + } + + StyledText { + anchors.verticalCenter: parent.verticalCenter + text: root.label + font { + weight: Font.DemiBold + pixelSize: Appearance.font.pixelSize.normal + } + color: Appearance.colors.colOnSurfaceVariant + } +} \ No newline at end of file diff --git a/dots/.config/quickshell/ii/modules/bar/StyledPopupValueRow.qml b/dots/.config/quickshell/ii/modules/bar/StyledPopupValueRow.qml new file mode 100644 index 000000000..de8ac579e --- /dev/null +++ b/dots/.config/quickshell/ii/modules/bar/StyledPopupValueRow.qml @@ -0,0 +1,29 @@ +import QtQuick +import QtQuick.Layouts +import qs.modules.common +import qs.modules.common.widgets + +RowLayout { + id: root + required property string icon + required property string label + required property string value + spacing: 4 + + MaterialSymbol { + text: root.icon + color: Appearance.colors.colOnSurfaceVariant + iconSize: Appearance.font.pixelSize.large + } + StyledText { + text: root.label + color: Appearance.colors.colOnSurfaceVariant + } + StyledText { + Layout.fillWidth: true + horizontalAlignment: Text.AlignRight + visible: root.value !== "" + color: Appearance.colors.colOnSurfaceVariant + text: root.value + } +} diff --git a/dots/.config/quickshell/ii/modules/bar/SysTray.qml b/dots/.config/quickshell/ii/modules/bar/SysTray.qml index 5489eb2f3..0233b2405 100644 --- a/dots/.config/quickshell/ii/modules/bar/SysTray.qml +++ b/dots/.config/quickshell/ii/modules/bar/SysTray.qml @@ -104,7 +104,6 @@ Item { id: overflowPopup hoverTarget: trayOverflowButton active: root.trayOverflowOpen && root.unpinnedItems.length > 0 - popupBackgroundMargin: 300 // This should be plenty... makes sure tooltips don't get cutoff (easily) GridLayout { id: trayOverflowLayout diff --git a/dots/.config/quickshell/ii/modules/bar/UtilButtons.qml b/dots/.config/quickshell/ii/modules/bar/UtilButtons.qml index cc3eb64a2..2f7197b58 100644 --- a/dots/.config/quickshell/ii/modules/bar/UtilButtons.qml +++ b/dots/.config/quickshell/ii/modules/bar/UtilButtons.qml @@ -25,7 +25,7 @@ Item { visible: Config.options.bar.utilButtons.showScreenSnip sourceComponent: CircleUtilButton { Layout.alignment: Qt.AlignVCenter - onClicked: Hyprland.dispatch("global quickshell:regionScreenshot") + onClicked: Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "region", "screenshot"]); MaterialSymbol { horizontalAlignment: Qt.AlignHCenter fill: 1 diff --git a/dots/.config/quickshell/ii/modules/bar/weather/WeatherBar.qml b/dots/.config/quickshell/ii/modules/bar/weather/WeatherBar.qml index 66923834d..1fcde7176 100644 --- a/dots/.config/quickshell/ii/modules/bar/weather/WeatherBar.qml +++ b/dots/.config/quickshell/ii/modules/bar/weather/WeatherBar.qml @@ -30,7 +30,7 @@ MouseArea { MaterialSymbol { fill: 0 - text: WeatherIcons.codeToName[Weather.data.wCode] ?? "cloud" + text: Icons.getWeatherIcon(Weather.data.wCode) ?? "cloud" iconSize: Appearance.font.pixelSize.large color: Appearance.colors.colOnLayer1 Layout.alignment: Qt.AlignVCenter diff --git a/dots/.config/quickshell/ii/modules/bar/weather/WeatherIcons.qml b/dots/.config/quickshell/ii/modules/bar/weather/WeatherIcons.qml deleted file mode 100644 index bd74d4e17..000000000 --- a/dots/.config/quickshell/ii/modules/bar/weather/WeatherIcons.qml +++ /dev/null @@ -1,59 +0,0 @@ -pragma Singleton - -import Quickshell - -Singleton { - // credits: calestia - // this snippet is taken from - // https://github.com/caelestia-dots/shell - readonly property var codeToName: ({ - "113": "clear_day", - "116": "partly_cloudy_day", - "119": "cloud", - "122": "cloud", - "143": "foggy", - "176": "rainy", - "179": "rainy", - "182": "rainy", - "185": "rainy", - "200": "thunderstorm", - "227": "cloudy_snowing", - "230": "snowing_heavy", - "248": "foggy", - "260": "foggy", - "263": "rainy", - "266": "rainy", - "281": "rainy", - "284": "rainy", - "293": "rainy", - "296": "rainy", - "299": "rainy", - "302": "weather_hail", - "305": "rainy", - "308": "weather_hail", - "311": "rainy", - "314": "rainy", - "317": "rainy", - "320": "cloudy_snowing", - "323": "cloudy_snowing", - "326": "cloudy_snowing", - "329": "snowing_heavy", - "332": "snowing_heavy", - "335": "snowing", - "338": "snowing_heavy", - "350": "rainy", - "353": "rainy", - "356": "rainy", - "359": "weather_hail", - "362": "rainy", - "365": "rainy", - "368": "cloudy_snowing", - "371": "snowing", - "374": "rainy", - "377": "rainy", - "386": "thunderstorm", - "389": "thunderstorm", - "392": "thunderstorm", - "395": "snowing" - }) -} diff --git a/dots/.config/quickshell/ii/modules/common/Config.qml b/dots/.config/quickshell/ii/modules/common/Config.qml index 4df736237..8e5d588ec 100644 --- a/dots/.config/quickshell/ii/modules/common/Config.qml +++ b/dots/.config/quickshell/ii/modules/common/Config.qml @@ -145,36 +145,44 @@ Singleton { } property JsonObject background: JsonObject { - property JsonObject clock: JsonObject { - property bool fixedPosition: false - property real x: -500 - property real y: -500 - property bool show: true - property string style: "cookie" // Options: "cookie", "digital" - property real scale: 1 - property JsonObject cookie: JsonObject { - property bool aiStyling: false - property int sides: 14 - property string dialNumberStyle: "full" // Options: "dots" , "numbers", "full" , "none" - property string hourHandStyle: "fill" // Options: "classic", "fill", "hollow", "hide" - property string minuteHandStyle: "medium" // Options "classic", "thin", "medium", "bold", "hide" - property string secondHandStyle: "dot" // Options: "dot", "line", "classic", "hide" - property string dateStyle: "bubble" // Options: "border", "rect", "bubble" , "hide" - property bool timeIndicators: true - property bool hourMarks: false - property bool dateInClock: true - property bool constantlyRotate: false - property bool useSineCookie: false + property JsonObject widgets: JsonObject { + property JsonObject clock: JsonObject { + property bool enable: true + property string placementStrategy: "leastBusy" // "free", "leastBusy", "mostBusy" + property real x: 100 + property real y: 100 + property string style: "cookie" // Options: "cookie", "digital" + property JsonObject cookie: JsonObject { + property bool aiStyling: false + property int sides: 14 + property string dialNumberStyle: "full" // Options: "dots" , "numbers", "full" , "none" + property string hourHandStyle: "fill" // Options: "classic", "fill", "hollow", "hide" + property string minuteHandStyle: "medium" // Options "classic", "thin", "medium", "bold", "hide" + property string secondHandStyle: "dot" // Options: "dot", "line", "classic", "hide" + property string dateStyle: "bubble" // Options: "border", "rect", "bubble" , "hide" + property bool timeIndicators: true + property bool hourMarks: false + property bool dateInClock: true + property bool constantlyRotate: false + property bool useSineCookie: false + } + property JsonObject digital: JsonObject { + property bool animateChange: true + } + property JsonObject quote: JsonObject { + property bool enable: false + property string text: "" + } } - property JsonObject digital: JsonObject { - property bool animateChange: true + property JsonObject weather: JsonObject { + property bool enable: false + property string placementStrategy: "free" // "free", "leastBusy", "mostBusy" + property real x: 400 + property real y: 100 } - } property string wallpaperPath: "" property string thumbnailPath: "" - property string quote: "" - property bool showQuote: false property bool hideWhenFullscreen: true property JsonObject parallax: JsonObject { property bool vertical: false @@ -182,7 +190,7 @@ Singleton { property bool enableWorkspace: true property real workspaceZoom: 1.07 // Relative to your screen, not wallpaper size property bool enableSidebar: true - property real clockFactor: 1.2 + property real widgetsFactor: 1.2 } } @@ -318,7 +326,7 @@ Singleton { property bool useHyprlock: false property bool launchOnStartup: false property JsonObject blur: JsonObject { - property bool enable: false + property bool enable: true property real radius: 100 property real extraZoom: 1.1 } @@ -353,6 +361,11 @@ Singleton { property bool pinnedOnStartup: false } + property JsonObject overlay: JsonObject { + property bool openingZoomAnimation: true + property bool darkenScreen: true + } + property JsonObject overview: JsonObject { property bool enable: true property real scale: 0.18 // Relative to screen size @@ -369,6 +382,7 @@ Singleton { property bool showLabel: false property real opacity: 0.3 property real contentRegionOpacity: 0.8 + property int selectionPadding: 5 } property JsonObject rect: JsonObject { property bool showAimLines: true @@ -461,6 +475,14 @@ Singleton { } } + property JsonObject screenRecord: JsonObject { + property string savePath: Directories.videos.replace("file://","") // strip "file://" + } + + property JsonObject screenSnip: JsonObject { + property string savePath: "" // only copy to clipboard when empty + } + property JsonObject sounds: JsonObject { property bool battery: false property bool pomodoro: false diff --git a/dots/.config/quickshell/ii/modules/common/Icons.qml b/dots/.config/quickshell/ii/modules/common/Icons.qml index 454aea11e..9858d2963 100644 --- a/dots/.config/quickshell/ii/modules/common/Icons.qml +++ b/dots/.config/quickshell/ii/modules/common/Icons.qml @@ -20,4 +20,63 @@ Singleton { return "keyboard"; return "bluetooth"; } + + readonly property var weatherIconMap: ({ + "113": "clear_day", + "116": "partly_cloudy_day", + "119": "cloud", + "122": "cloud", + "143": "foggy", + "176": "rainy", + "179": "rainy", + "182": "rainy", + "185": "rainy", + "200": "thunderstorm", + "227": "cloudy_snowing", + "230": "snowing_heavy", + "248": "foggy", + "260": "foggy", + "263": "rainy", + "266": "rainy", + "281": "rainy", + "284": "rainy", + "293": "rainy", + "296": "rainy", + "299": "rainy", + "302": "weather_hail", + "305": "rainy", + "308": "weather_hail", + "311": "rainy", + "314": "rainy", + "317": "rainy", + "320": "cloudy_snowing", + "323": "cloudy_snowing", + "326": "cloudy_snowing", + "329": "snowing_heavy", + "332": "snowing_heavy", + "335": "snowing", + "338": "snowing_heavy", + "350": "rainy", + "353": "rainy", + "356": "rainy", + "359": "weather_hail", + "362": "rainy", + "365": "rainy", + "368": "cloudy_snowing", + "371": "snowing", + "374": "rainy", + "377": "rainy", + "386": "thunderstorm", + "389": "thunderstorm", + "392": "thunderstorm", + "395": "snowing" + }) + + + function getWeatherIcon(code) { + const key = String(code) + if (weatherIconMap.hasOwnProperty(key)) { + return weatherIconMap[key] + } + } } diff --git a/dots/.config/quickshell/ii/modules/common/Persistent.qml b/dots/.config/quickshell/ii/modules/common/Persistent.qml index a705de098..5335deb9d 100644 --- a/dots/.config/quickshell/ii/modules/common/Persistent.qml +++ b/dots/.config/quickshell/ii/modules/common/Persistent.qml @@ -79,6 +79,28 @@ Singleton { property bool inhibit: false } + property JsonObject overlay: JsonObject { + property list open: ["crosshair"] + property JsonObject crosshair: JsonObject { + property bool pinned: false + property bool clickthrough: true + property real x: 835 + property real y: 490 + } + property JsonObject recorder: JsonObject { + property bool pinned: false + property bool clickthrough: false + property real x: 100 + property real y: 130 + } + property JsonObject volumeMixer: JsonObject { + property bool pinned: false + property bool clickthrough: false + property real x: 100 + property real y: 320 + } + } + property JsonObject timer: JsonObject { property JsonObject pomodoro: JsonObject { property bool running: false diff --git a/dots/.config/quickshell/ii/modules/common/widgets/StyledDropShadow.qml b/dots/.config/quickshell/ii/modules/common/widgets/StyledDropShadow.qml new file mode 100644 index 000000000..5daca8915 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/widgets/StyledDropShadow.qml @@ -0,0 +1,13 @@ +import QtQuick +import Qt5Compat.GraphicalEffects +import qs.modules.common + +DropShadow { + required property var target + source: target + anchors.fill: source + radius: 8 + samples: radius * 2 + 1 + color: Appearance.colors.colShadow + transparentBorder: true +} diff --git a/dots/.config/quickshell/ii/modules/common/widgets/widgetCanvas/AbstractOverlayWidget.qml b/dots/.config/quickshell/ii/modules/common/widgets/widgetCanvas/AbstractOverlayWidget.qml new file mode 100644 index 000000000..10ce0f5cb --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/widgets/widgetCanvas/AbstractOverlayWidget.qml @@ -0,0 +1,13 @@ +import QtQuick +import Quickshell +import qs.modules.common + +/* + * Abstract widgets for an overlay. Doesn't contain any visuals. + */ +AbstractWidget { + id: root + + property bool pinned: false // Whether to stay visible when the overlay is dismissed + property bool clickthrough: true // When pinned, whether to allow clicks go through +} diff --git a/dots/.config/quickshell/ii/modules/common/widgets/widgetCanvas/AbstractWidget.qml b/dots/.config/quickshell/ii/modules/common/widgets/widgetCanvas/AbstractWidget.qml new file mode 100644 index 000000000..20b0c27c1 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/widgets/widgetCanvas/AbstractWidget.qml @@ -0,0 +1,26 @@ +import QtQuick +import Quickshell +import qs.modules.common + +/* + * Widget to be placed on a WidgetCanvas + */ +MouseArea { + id: root + + property bool draggable: true + drag.target: draggable ? root : undefined + cursorShape: (draggable && containsPress) ? Qt.ClosedHandCursor : draggable ? Qt.OpenHandCursor : Qt.ArrowCursor + + function center() { + root.x = (root.parent.width - root.width) / 2 + root.y = (root.parent.height - root.height) / 2 + } + + Behavior on x { + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + } + Behavior on y { + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + } +} diff --git a/dots/.config/quickshell/ii/modules/common/widgets/widgetCanvas/WidgetCanvas.qml b/dots/.config/quickshell/ii/modules/common/widgets/widgetCanvas/WidgetCanvas.qml new file mode 100644 index 000000000..d348ffae4 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/widgets/widgetCanvas/WidgetCanvas.qml @@ -0,0 +1,7 @@ +import QtQuick + +MouseArea { + id: root + + // uh this is stupid turns out we don't need anything here +} diff --git a/dots/.config/quickshell/ii/modules/crosshair/Crosshair.qml b/dots/.config/quickshell/ii/modules/crosshair/Crosshair.qml deleted file mode 100644 index f47902132..000000000 --- a/dots/.config/quickshell/ii/modules/crosshair/Crosshair.qml +++ /dev/null @@ -1,57 +0,0 @@ -import qs -import qs.modules.common -import qs.modules.common.widgets -import qs.services -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import Quickshell -import Quickshell.Io -import Quickshell.Wayland -import Quickshell.Hyprland - -Scope { - id: root - - Loader { - id: crosshairLoader - active: GlobalStates.crosshairOpen - sourceComponent: PanelWindow { - id: crosshairWindow - exclusionMode: ExclusionMode.Ignore - WlrLayershell.namespace: "quickshell:crosshair" - WlrLayershell.layer: WlrLayer.Overlay - visible: true - color: "transparent" - - mask: Region { // Crosshair should not block mouse input - item: null - } - - implicitWidth: crosshairContent.implicitWidth - implicitHeight: crosshairContent.implicitHeight - - CrosshairContent { - id: crosshairContent - anchors.centerIn: parent - } - } - } - - IpcHandler { - target: "crosshair" - - function toggle(): void { - GlobalStates.crosshairOpen = !GlobalStates.crosshairOpen; - } - } - - GlobalShortcut { - name: "crosshairToggle" - description: "Toggles crosshair on press" - - onPressed: { - GlobalStates.crosshairOpen = !GlobalStates.crosshairOpen; - } - } -} diff --git a/dots/.config/quickshell/ii/modules/lock/Lock.qml b/dots/.config/quickshell/ii/modules/lock/Lock.qml index e5557425b..4266d03f7 100644 --- a/dots/.config/quickshell/ii/modules/lock/Lock.qml +++ b/dots/.config/quickshell/ii/modules/lock/Lock.qml @@ -128,11 +128,19 @@ Scope { } } + function lock() { + if (Config.options.lock.useHyprlock) { + Quickshell.execDetached(["bash", "-c", "pidof hyprlock || hyprlock"]); + return; + } + GlobalStates.screenLocked = true; + } + IpcHandler { target: "lock" function activate(): void { - GlobalStates.screenLocked = true; + root.lock(); } function focus(): void { lockContext.shouldReFocus(); @@ -144,11 +152,7 @@ Scope { description: "Locks the screen" onPressed: { - if (Config.options.lock.useHyprlock) { - Quickshell.execDetached(["bash", "-c", "pidof hyprlock || hyprlock"]); - return; - } - GlobalStates.screenLocked = true; + root.lock() } } @@ -165,7 +169,7 @@ Scope { function initIfReady() { if (!Config.ready || !Persistent.ready) return; if (Config.options.lock.launchOnStartup && Persistent.isNewHyprlandInstance) { - Hyprland.dispatch("global quickshell:lock") + root.lock(); } else { KeyringStorage.fetchKeyringData(); } diff --git a/dots/.config/quickshell/ii/modules/lock/LockContext.qml b/dots/.config/quickshell/ii/modules/lock/LockContext.qml index 17893bb18..b242cdc05 100644 --- a/dots/.config/quickshell/ii/modules/lock/LockContext.qml +++ b/dots/.config/quickshell/ii/modules/lock/LockContext.qml @@ -87,7 +87,7 @@ Scope { } onExited: (exitCode, exitStatus) => { if (exitCode !== 0) { - console.warn("fprintd-list command exited with error:", exitCode, exitStatus); + // console.warn("[LockContext] fprintd-list command exited with error:", exitCode, exitStatus); root.fingerprintsConfigured = false; } } diff --git a/dots/.config/quickshell/ii/modules/overlay/Overlay.qml b/dots/.config/quickshell/ii/modules/overlay/Overlay.qml new file mode 100644 index 000000000..5054b9d1c --- /dev/null +++ b/dots/.config/quickshell/ii/modules/overlay/Overlay.qml @@ -0,0 +1,69 @@ +import qs +import qs.modules.common +import qs.modules.common.widgets +import qs.services +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import Quickshell.Hyprland + +Scope { + id: root + + property Component regionComponent: Component { + Region {} + } + + Loader { + id: overlayLoader + active: GlobalStates.overlayOpen || OverlayContext.hasPinnedWidgets + sourceComponent: PanelWindow { + id: overlayWindow + exclusionMode: ExclusionMode.Ignore + WlrLayershell.namespace: "quickshell:overlay" + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand + visible: true + color: "transparent" + + mask: Region { + item: GlobalStates.overlayOpen ? overlayContent : null + regions: OverlayContext.clickableWidgets.map((widget) => regionComponent.createObject(this, { + item: widget + })); + } + + anchors { + top: true + bottom: true + left: true + right: true + } + + OverlayContent { + id: overlayContent + anchors.fill: parent + } + } + } + + IpcHandler { + target: "overlay" + + function toggle(): void { + GlobalStates.overlayOpen = !GlobalStates.overlayOpen; + } + } + + GlobalShortcut { + name: "overlayToggle" + description: "Toggles overlay on press" + + onPressed: { + GlobalStates.overlayOpen = !GlobalStates.overlayOpen; + } + } +} diff --git a/dots/.config/quickshell/ii/modules/overlay/OverlayContent.qml b/dots/.config/quickshell/ii/modules/overlay/OverlayContent.qml new file mode 100644 index 000000000..838267b80 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/overlay/OverlayContent.qml @@ -0,0 +1,67 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Widgets +import qs +import qs.services +import qs.modules.common +import qs.modules.common.widgets +import qs.modules.common.widgets.widgetCanvas + +import qs.modules.overlay.crosshair + +Item { + id: root + readonly property bool usePasswordChars: !PolkitService.flow?.responseVisible ?? true + + Keys.onPressed: (event) => { // Esc to close + if (event.key === Qt.Key_Escape) { + GlobalStates.overlayOpen = false; + } + } + + property real initScale: Config.options.overlay.openingZoomAnimation ? 1.08 : 1.000001 + scale: initScale + Component.onCompleted: { + scale = 1 + } + Behavior on scale { + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) + } + + Rectangle { + id: bg + anchors.fill: parent + color: Appearance.colors.colScrim + visible: Config.options.overlay.darkenScreen && opacity > 0 + opacity: (GlobalStates.overlayOpen && root.scale !== initScale) ? 1 : 0 + Behavior on opacity { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + } + + WidgetCanvas { + anchors.fill: parent + onClicked: GlobalStates.overlayOpen = false + + OverlayTaskbar { + anchors { + horizontalCenter: parent.horizontalCenter + top: parent.top + topMargin: 50 + } + } + + Repeater { + model: ScriptModel { + values: Persistent.states.overlay.open.map(identifier => { + return OverlayContext.availableWidgets.find(w => w.identifier === identifier); + }) + objectProp: "identifier" + } + delegate: OverlayWidgetDelegateChooser { + + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/overlay/OverlayContext.qml b/dots/.config/quickshell/ii/modules/overlay/OverlayContext.qml new file mode 100644 index 000000000..22746c4de --- /dev/null +++ b/dots/.config/quickshell/ii/modules/overlay/OverlayContext.qml @@ -0,0 +1,38 @@ +pragma Singleton +pragma ComponentBehavior: Bound +import Quickshell + +Singleton { + id: root + + readonly property list availableWidgets: [ + { identifier: "crosshair", materialSymbol: "point_scan" }, + { identifier: "volumeMixer", materialSymbol: "volume_up" }, + { identifier: "recorder", materialSymbol: "screen_record" }, + ] + + readonly property bool hasPinnedWidgets: root.pinnedWidgetIdentifiers.length > 0 + + property list pinnedWidgetIdentifiers: [] + property list clickableWidgets: [] + + function pin(identifier: string, pin = true) { + if (pin) { + if (!root.pinnedWidgetIdentifiers.includes(identifier)) { + root.pinnedWidgetIdentifiers.push(identifier) + } + } else { + root.pinnedWidgetIdentifiers = root.pinnedWidgetIdentifiers.filter(id => id !== identifier) + } + } + + function registerClickableWidget(widget: var, clickable = true) { + if (clickable) { + if (!root.clickableWidgets.includes(widget)) { + root.clickableWidgets.push(widget) + } + } else { + root.clickableWidgets = root.clickableWidgets.filter(w => w !== widget) + } + } +} diff --git a/dots/.config/quickshell/ii/modules/overlay/OverlayTaskbar.qml b/dots/.config/quickshell/ii/modules/overlay/OverlayTaskbar.qml new file mode 100644 index 000000000..65bb2a27e --- /dev/null +++ b/dots/.config/quickshell/ii/modules/overlay/OverlayTaskbar.qml @@ -0,0 +1,113 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs +import qs.services +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.modules.common.widgets.widgetCanvas + +Rectangle { + id: root + + property real padding: 8 + + opacity: GlobalStates.overlayOpen ? 1 : 0 + implicitWidth: contentRow.implicitWidth + (padding * 2) + implicitHeight: contentRow.implicitHeight + (padding * 2) + color: Appearance.m3colors.m3surfaceContainer + radius: Appearance.rounding.large + border.color: Appearance.colors.colOutlineVariant + border.width: 1 + + Behavior on opacity { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + + RowLayout { + id: contentRow + anchors { + fill: parent + margins: root.padding + } + spacing: 6 + + Row { + spacing: 4 + Repeater { + model: ScriptModel { + values: OverlayContext.availableWidgets + } + delegate: WidgetButton { + required property var modelData + identifier: modelData.identifier + materialSymbol: modelData.materialSymbol + } + } + } + + Separator {} + + TimeWidget {} + } + + component Separator: Rectangle { + implicitWidth: 1 + color: Appearance.colors.colOutlineVariant + Layout.fillHeight: true + Layout.topMargin: 10 + Layout.bottomMargin: 10 + } + + component TimeWidget: StyledText { + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: 8 + Layout.rightMargin: 6 + + text: DateTime.time + font { + family: Appearance.font.family.numbers + variableAxes: Appearance.font.variableAxes.numbers + pixelSize: 22 + } + } + + component WidgetButton: RippleButton { + id: widgetButton + required property string identifier + required property string materialSymbol + + Layout.alignment: Qt.AlignVCenter + + toggled: Persistent.states.overlay.open.includes(identifier) + onClicked: { + if (widgetButton.toggled) { + Persistent.states.overlay.open = Persistent.states.overlay.open.filter(type => type !== identifier); + } else { + Persistent.states.overlay.open.push(identifier); + } + } + implicitWidth: implicitHeight + + colBackgroundToggled: Appearance.colors.colSecondaryContainer + colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover + colRippleToggled: Appearance.colors.colSecondaryContainerActive + + buttonRadius: root.radius - (root.height - height) / 2 + + contentItem: Item { + anchors.centerIn: parent + implicitWidth: 32 + implicitHeight: 32 + MaterialSymbol { + id: iconWidget + anchors.centerIn: parent + iconSize: 24 + text: widgetButton.materialSymbol + color: widgetButton.toggled ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnSurfaceVariant + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/overlay/OverlayWidgetDelegateChooser.qml b/dots/.config/quickshell/ii/modules/overlay/OverlayWidgetDelegateChooser.qml new file mode 100644 index 000000000..39df62ac3 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/overlay/OverlayWidgetDelegateChooser.qml @@ -0,0 +1,20 @@ +pragma ComponentBehavior: Bound +import qs.services +import qs.modules.common +import qs.modules.common.widgets +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Bluetooth +import qs.modules.overlay.crosshair +import qs.modules.overlay.volumeMixer +import qs.modules.overlay.recorder + +DelegateChooser { + id: root + role: "identifier" + + DelegateChoice { roleValue: "crosshair"; Crosshair {} } + DelegateChoice { roleValue: "volumeMixer"; VolumeMixer {} } + DelegateChoice { roleValue: "recorder"; Recorder {} } +} diff --git a/dots/.config/quickshell/ii/modules/overlay/StyledOverlayWidget.qml b/dots/.config/quickshell/ii/modules/overlay/StyledOverlayWidget.qml new file mode 100644 index 000000000..5e6a0ce3f --- /dev/null +++ b/dots/.config/quickshell/ii/modules/overlay/StyledOverlayWidget.qml @@ -0,0 +1,230 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Layouts +import Quickshell +import Qt5Compat.GraphicalEffects +import qs +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.modules.common.widgets.widgetCanvas + +/* + * To make an overlay widget: + * 1. Create a modules/overlay//.qml, using this as the base class and declare your widget content as contentItem + * 2. Add an entry to OverlayContext.availableWidgets with identifier= + * 3. Add an entry in Persistent.states.overlay. with x, y, pinned, clickthrough properties set to reasonable defaults + * 4. Add an entry in OverlayWidgetDelegateChooser with roleValue= and Declare your widget in there + * Use existing entries as reference. + */ +AbstractOverlayWidget { + id: root + + required property Item contentItem + + required property var modelData + readonly property string identifier: modelData.identifier + readonly property string materialSymbol: modelData.materialSymbol ?? "widgets" + property string title: identifier.replace(/([A-Z])/g, " $1").replace(/^./, function(str){ return str.toUpperCase(); }) + property var persistentStateEntry: Persistent.states.overlay[identifier] + property real radius: Appearance.rounding.windowRounding + property real minWidth: 250 + + draggable: GlobalStates.overlayOpen + x: Math.round(persistentStateEntry.x) // Round or it'll be blurry + y: Math.round(persistentStateEntry.y) // Round or it'll be blurry + pinned: persistentStateEntry.pinned + clickthrough: persistentStateEntry.clickthrough + drag { + minimumX: 0 + minimumY: 0 + maximumX: root.parent.width - root.width + maximumY: root.parent.height - root.height + } + + // Guarded states & registration funcs + property bool open: Persistent.states.overlay.open + property bool actuallyPinned: pinned && open + property bool actuallyClickable: !clickthrough && actuallyPinned && open + onActuallyPinnedChanged: reportPinnedState(); + onActuallyClickableChanged: reportClickableState(); + function reportPinnedState() { + OverlayContext.pin(identifier, actuallyPinned); + } + function reportClickableState() { + OverlayContext.registerClickableWidget(contentItem, actuallyClickable); + } + + // Self-registeration with OverlayContext + Component.onCompleted: { + reportPinnedState(); + reportClickableState(); + } + + // Hooks + onReleased: savePosition(); + + function close() { + Persistent.states.overlay.open = Persistent.states.overlay.open.filter(type => type !== root.identifier); + } + + function togglePinned() { + persistentStateEntry.pinned = !persistentStateEntry.pinned; + } + + function toggleClickthrough() { + persistentStateEntry.clickthrough = !persistentStateEntry.clickthrough; + } + + function savePosition(xPos = root.x, yPos = root.y) { + persistentStateEntry.x = xPos; + persistentStateEntry.y = yPos; + } + + function center() { + const targetX = (root.parent.width - contentColumn.width) / 2 + const targetY = (root.parent.height - contentItem.height) / 2 - titleBar.implicitHeight + root.x = targetX + root.y = targetY + root.savePosition(targetX, targetY) + } + + visible: GlobalStates.overlayOpen || actuallyPinned + implicitWidth: Math.max(contentColumn.implicitWidth, minWidth) + implicitHeight: contentColumn.implicitHeight + + Rectangle { + id: border + anchors.fill: parent + color: "transparent" + radius: root.radius + border.color: ColorUtils.transparentize(Appearance.colors.colOutlineVariant, GlobalStates.overlayOpen ? 0 : 1) + border.width: 1 + + layer.enabled: GlobalStates.overlayOpen + layer.effect: OpacityMask { + maskSource: Rectangle { + width: border.width + height: border.height + radius: root.radius + } + } + + Column { + id: contentColumn + z: -1 + anchors.fill: parent + + // Title bar + Rectangle { + id: titleBar + opacity: GlobalStates.overlayOpen ? 1 : 0 + anchors { + left: parent.left + right: parent.right + } + property real padding: 2 + implicitWidth: titleBarRow.implicitWidth + padding * 2 + implicitHeight: titleBarRow.implicitHeight + padding * 2 + color: Appearance.m3colors.m3surfaceContainer + border.color: Appearance.colors.colOutlineVariant + border.width: 1 + + RowLayout { + id: titleBarRow + anchors { + fill: parent + margins: titleBar.padding + leftMargin: titleBar.padding + 8 + } + spacing: 0 + + MaterialSymbol { + text: root.materialSymbol + iconSize: 20 + Layout.alignment: Qt.AlignVCenter + Layout.rightMargin: 4 + } + + StyledText { + text: root.title + Layout.fillWidth: true + elide: Text.ElideRight + } + + TitlebarButton { + materialSymbol: "recenter" + onClicked: root.center() + StyledToolTip { + text: "Center" + } + } + + TitlebarButton { + materialSymbol: "mouse" + toggled: !root.clickthrough + onClicked: root.toggleClickthrough() + StyledToolTip { + text: "Clickable when pinned" + } + } + + TitlebarButton { + materialSymbol: "keep" + toggled: root.pinned + onClicked: root.togglePinned() + StyledToolTip { + text: "Pin" + } + } + + TitlebarButton { + materialSymbol: "close" + onClicked: root.close() + StyledToolTip { + text: "Close" + } + } + } + } + + // Content + Item { + id: contentContainer + anchors.horizontalCenter: parent.horizontalCenter + implicitWidth: root.contentItem.implicitWidth + implicitHeight: root.contentItem.implicitHeight + children: [root.contentItem] + } + } + } + + + component TitlebarButton: RippleButton { + id: titlebarButton + required property string materialSymbol + buttonRadius: height / 2 + implicitHeight: contentItem.implicitHeight + implicitWidth: implicitHeight + padding: 0 + + colBackgroundToggled: Appearance.colors.colSecondaryContainer + colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover + colRippleToggled: Appearance.colors.colSecondaryContainerActive + + contentItem: Item { + anchors.centerIn: parent + implicitWidth: 30 + implicitHeight: 30 + + MaterialSymbol { + id: iconWidget + anchors.centerIn: parent + iconSize: 20 + text: titlebarButton.materialSymbol + fill: titlebarButton.toggled + color: titlebarButton.toggled ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnSurface + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/overlay/crosshair/Crosshair.qml b/dots/.config/quickshell/ii/modules/overlay/crosshair/Crosshair.qml new file mode 100644 index 000000000..abb1cbf26 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/overlay/crosshair/Crosshair.qml @@ -0,0 +1,9 @@ +import QtQuick +import Quickshell +import qs.modules.common +import qs.modules.overlay + +StyledOverlayWidget { + id: root + contentItem: CrosshairContent {} +} diff --git a/dots/.config/quickshell/ii/modules/crosshair/CrosshairContent.qml b/dots/.config/quickshell/ii/modules/overlay/crosshair/CrosshairContent.qml similarity index 100% rename from dots/.config/quickshell/ii/modules/crosshair/CrosshairContent.qml rename to dots/.config/quickshell/ii/modules/overlay/crosshair/CrosshairContent.qml diff --git a/dots/.config/quickshell/ii/modules/overlay/recorder/Recorder.qml b/dots/.config/quickshell/ii/modules/overlay/recorder/Recorder.qml new file mode 100644 index 000000000..d32adedea --- /dev/null +++ b/dots/.config/quickshell/ii/modules/overlay/recorder/Recorder.qml @@ -0,0 +1,122 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Hyprland +import qs +import qs.modules.common +import qs.modules.common.widgets +import qs.modules.overlay + +StyledOverlayWidget { + id: root + + contentItem: Rectangle { + id: contentItem + anchors.centerIn: parent + color: Appearance.m3colors.m3surfaceContainer + property real padding: 8 + implicitHeight: contentColumn.implicitHeight + padding * 2 + implicitWidth: 350 + ColumnLayout { + id: contentColumn + anchors { + fill: parent + margins: parent.padding + } + spacing: 10 + + Row { + Layout.alignment: Qt.AlignHCenter + spacing: 10 + + BigRecorderButton { + materialSymbol: "screenshot_region" + name: "Screenshot region" + onClicked: { + GlobalStates.overlayOpen = false; + Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "region", "screenshot"]); + } + } + + BigRecorderButton { + materialSymbol: "photo_camera" + name: "Screenshot" + onClicked: { + GlobalStates.overlayOpen = false; + Quickshell.execDetached(["bash", "-c", "grim - | wl-copy"]); + } + } + + BigRecorderButton { + materialSymbol: "screen_record" + name: "Record region" + onClicked: { + GlobalStates.overlayOpen = false; + Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "region", "recordWithSound"]); + } + } + + BigRecorderButton { + materialSymbol: "capture" + name: "Record screen" + onClicked: { + GlobalStates.overlayOpen = false; + Quickshell.execDetached([Directories.recordScriptPath, "--fullscreen", "--sound"]); + } + } + } + + RippleButton { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: false + buttonRadius: height / 2 + colBackground: Appearance.colors.colLayer3 + colBackgroundHover: Appearance.colors.colLayer3Hover + colRipple: Appearance.colors.colLayer3Active + onClicked: { + GlobalStates.overlayOpen = false; + Qt.openUrlExternally(Directories.videos); + } + contentItem: Row { + anchors.centerIn: parent + spacing: 6 + MaterialSymbol { + anchors.verticalCenter: parent.verticalCenter + text: "animated_images" + iconSize: 20 + } + StyledText { + anchors.verticalCenter: parent.verticalCenter + text: qsTr("Open recordings folder") + } + } + } + } + } + + component BigRecorderButton: RippleButton { + id: bigButton + required property string materialSymbol + required property string name + implicitHeight: 66 + implicitWidth: 66 + buttonRadius: height / 2 + + colBackground: Appearance.colors.colLayer3 + colBackgroundHover: Appearance.colors.colLayer3Hover + colRipple: Appearance.colors.colLayer3Active + + contentItem: MaterialSymbol { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + text: bigButton.materialSymbol + iconSize: 28 + } + + StyledToolTip { + text: bigButton.name + } + } +} diff --git a/dots/.config/quickshell/ii/modules/overlay/volumeMixer/VolumeMixer.qml b/dots/.config/quickshell/ii/modules/overlay/volumeMixer/VolumeMixer.qml new file mode 100644 index 000000000..14aa53006 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/overlay/volumeMixer/VolumeMixer.qml @@ -0,0 +1,24 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs.modules.common +import qs.modules.overlay +import qs.modules.sidebarRight.volumeMixer + +StyledOverlayWidget { + id: root + contentItem: Rectangle { + anchors.centerIn: parent + color: Appearance.m3colors.m3surfaceContainer + property real padding: 16 + implicitHeight: 600 + implicitWidth: 350 + + VolumeDialogContent { + anchors.fill: parent + anchors.margins: parent.padding + isSink: true + } + + } +} diff --git a/dots/.config/quickshell/ii/modules/overview/SearchBar.qml b/dots/.config/quickshell/ii/modules/overview/SearchBar.qml index a0abd54c8..5cd7b9ac5 100644 --- a/dots/.config/quickshell/ii/modules/overview/SearchBar.qml +++ b/dots/.config/quickshell/ii/modules/overview/SearchBar.qml @@ -99,7 +99,7 @@ RowLayout { Layout.bottomMargin: 4 onClicked: { GlobalStates.overviewOpen = false; - Hyprland.dispatch("global quickshell:regionSearch") + Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "region", "search"]); } text: "image_search" StyledToolTip { diff --git a/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml b/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml index a0853cc89..a52471ba6 100644 --- a/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml +++ b/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml @@ -33,6 +33,10 @@ PanelWindow { property var selectionMode: RegionSelection.SelectionMode.RectCorners signal dismiss() + property string saveScreenshotDir: Config.options.screenSnip.savePath !== "" + ? Config.options.screenSnip.savePath + : "" + property string screenshotDir: Directories.screenshotTemp property string imageSearchEngineBaseUrl: Config.options.search.imageSearch.imageSearchEngineBaseUrl property string fileUploadApiEndpoint: "https://uguu.se/upload" @@ -121,10 +125,11 @@ PanelWindow { return (root.targetedRegionX >= 0 && root.targetedRegionY >= 0) } function setRegionToTargeted() { - root.regionX = root.targetedRegionX; - root.regionY = root.targetedRegionY; - root.regionWidth = root.targetedRegionWidth; - root.regionHeight = root.targetedRegionHeight; + const padding = Config.options.regionSelector.targetRegions.selectionPadding; // Make borders not cut off n stuff + root.regionX = root.targetedRegionX - padding; + root.regionY = root.targetedRegionY - padding; + root.regionWidth = root.targetedRegionWidth + padding * 2; + root.regionHeight = root.targetedRegionHeight + padding * 2; } function updateTargetedRegion(x, y) { @@ -258,7 +263,23 @@ PanelWindow { } switch (root.action) { case RegionSelection.SnipAction.Copy: - snipProc.command = ["bash", "-c", `${cropToStdout} | wl-copy && ${cleanup}`] + if (saveScreenshotDir === "") { + // not saving the screenshot, just copy to clipboard + snipProc.command = ["bash", "-c", `${cropToStdout} | wl-copy && ${cleanup}`] + break; + } + + const savePathBase = root.saveScreenshotDir + + snipProc.command = [ + "bash", "-c", + `mkdir -p '${StringUtils.shellSingleQuoteEscape(savePathBase)}' && \ + saveFileName="screenshot-$(date '+%Y-%m-%d_%H.%M.%S').png" && \ + savePath="${savePathBase}/$saveFileName" && \ + ${cropToStdout} | tee >(wl-copy) > "$savePath" && \ + ${cleanup}` + ] + break; case RegionSelection.SnipAction.Edit: snipProc.command = ["bash", "-c", `${cropToStdout} | swappy -f - && ${cleanup}`] diff --git a/dots/.config/quickshell/ii/modules/settings/BackgroundConfig.qml b/dots/.config/quickshell/ii/modules/settings/BackgroundConfig.qml new file mode 100644 index 000000000..3b388583f --- /dev/null +++ b/dots/.config/quickshell/ii/modules/settings/BackgroundConfig.qml @@ -0,0 +1,471 @@ +import QtQuick +import QtQuick.Layouts +import qs.services +import qs.modules.common +import qs.modules.common.widgets + +ContentPage { + forceWidth: true + + ContentSection { + icon: "sync_alt" + title: Translation.tr("Parallax") + + ConfigSwitch { + buttonIcon: "unfold_more_double" + text: Translation.tr("Vertical") + checked: Config.options.background.parallax.vertical + onCheckedChanged: { + Config.options.background.parallax.vertical = checked; + } + } + + ConfigRow { + uniform: true + ConfigSwitch { + buttonIcon: "counter_1" + text: Translation.tr("Depends on workspace") + checked: Config.options.background.parallax.enableWorkspace + onCheckedChanged: { + Config.options.background.parallax.enableWorkspace = checked; + } + } + ConfigSwitch { + buttonIcon: "side_navigation" + text: Translation.tr("Depends on sidebars") + checked: Config.options.background.parallax.enableSidebar + onCheckedChanged: { + Config.options.background.parallax.enableSidebar = checked; + } + } + } + ConfigSpinBox { + icon: "loupe" + text: Translation.tr("Preferred wallpaper zoom (%)") + value: Config.options.background.parallax.workspaceZoom * 100 + from: 100 + to: 150 + stepSize: 1 + onValueChanged: { + Config.options.background.parallax.workspaceZoom = value / 100; + } + } + } + + ContentSection { + icon: "clock_loader_40" + title: Translation.tr("Widget: Clock") + + ConfigRow { + Layout.fillWidth: true + + ConfigSwitch { + Layout.fillWidth: false + buttonIcon: "check" + text: Translation.tr("Enable") + checked: Config.options.background.widgets.clock.enable + onCheckedChanged: { + Config.options.background.widgets.clock.enable = checked; + } + } + Item { + Layout.fillWidth: true + } + ConfigSelectionArray { + Layout.fillWidth: false + currentValue: Config.options.background.widgets.clock.placementStrategy + onSelected: newValue => { + Config.options.background.widgets.clock.placementStrategy = newValue; + } + options: [ + { + displayName: Translation.tr("Draggable"), + icon: "drag_pan", + value: "free" + }, + { + displayName: Translation.tr("Least busy"), + icon: "category", + value: "leastBusy" + }, + { + displayName: Translation.tr("Most busy"), + icon: "shapes", + value: "mostBusy" + }, + ] + } + } + + ContentSubsection { + title: Translation.tr("Clock style") + ConfigSelectionArray { + currentValue: Config.options.background.widgets.clock.style + onSelected: newValue => { + Config.options.background.widgets.clock.style = newValue; + } + options: [ + { + displayName: Translation.tr("Digital"), + icon: "timer_10", + value: "digital" + }, + { + displayName: Translation.tr("Cookie"), + icon: "cookie", + value: "cookie" + } + ] + } + } + + ContentSubsection { + visible: Config.options.background.widgets.clock.style === "digital" + title: Translation.tr("Digital clock settings") + + ConfigSwitch { + buttonIcon: "animation" + text: Translation.tr("Animate time change") + checked: Config.options.background.widgets.clock.digital.animateChange + onCheckedChanged: { + Config.options.background.widgets.clock.digital.animateChange = checked; + } + } + } + + ContentSubsection { + visible: Config.options.background.widgets.clock.style === "cookie" + title: Translation.tr("Cookie clock settings") + + ConfigSwitch { + buttonIcon: "wand_stars" + text: Translation.tr("Auto styling with Gemini") + checked: Config.options.background.widgets.clock.cookie.aiStyling + onCheckedChanged: { + Config.options.background.widgets.clock.cookie.aiStyling = checked; + } + StyledToolTip { + text: Translation.tr("Uses Gemini to categorize the wallpaper then picks a preset based on it.\nYou'll need to set Gemini API key on the left sidebar first.\nImages are downscaled for performance, but just to be safe,\ndo not select wallpapers with sensitive information.") + } + } + + ConfigSwitch { + buttonIcon: "airwave" + text: Translation.tr("Use old sine wave cookie implementation") + checked: Config.options.background.widgets.clock.cookie.useSineCookie + onCheckedChanged: { + Config.options.background.widgets.clock.cookie.useSineCookie = checked; + } + StyledToolTip { + text: "Looks a bit softer and more consistent with different number of sides,\nbut has less impressive morphing" + } + } + + ConfigSpinBox { + icon: "add_triangle" + text: Translation.tr("Sides") + value: Config.options.background.widgets.clock.cookie.sides + from: 0 + to: 40 + stepSize: 1 + onValueChanged: { + Config.options.background.widgets.clock.cookie.sides = value; + } + } + + ConfigSwitch { + buttonIcon: "autoplay" + text: Translation.tr("Constantly rotate") + checked: Config.options.background.widgets.clock.cookie.constantlyRotate + onCheckedChanged: { + Config.options.background.widgets.clock.cookie.constantlyRotate = checked; + } + StyledToolTip { + text: "Makes the clock always rotate. This is extremely expensive\n(expect 50% usage on Intel UHD Graphics) and thus impractical." + } + } + + ConfigRow { + + ConfigSwitch { + enabled: Config.options.background.widgets.clock.style === "cookie" && Config.options.background.widgets.clock.cookie.dialNumberStyle === "dots" || Config.options.background.widgets.clock.cookie.dialNumberStyle === "full" + buttonIcon: "brightness_7" + text: Translation.tr("Hour marks") + checked: Config.options.background.widgets.clock.cookie.hourMarks + onEnabledChanged: { + checked = Config.options.background.widgets.clock.cookie.hourMarks; + } + onCheckedChanged: { + Config.options.background.widgets.clock.cookie.hourMarks = checked; + } + StyledToolTip { + text: "Can only be turned on using the 'Dots' or 'Full' dial style for aesthetic reasons" + } + } + + ConfigSwitch { + enabled: Config.options.background.widgets.clock.style === "cookie" && Config.options.background.widgets.clock.cookie.dialNumberStyle !== "numbers" + buttonIcon: "timer_10" + text: Translation.tr("Digits in the middle") + checked: Config.options.background.widgets.clock.cookie.timeIndicators + onEnabledChanged: { + checked = Config.options.background.widgets.clock.cookie.timeIndicators; + } + onCheckedChanged: { + Config.options.background.widgets.clock.cookie.timeIndicators = checked; + } + StyledToolTip { + text: "Can't be turned on when using 'Numbers' dial style for aesthetic reasons" + } + } + } + } + + ContentSubsection { + visible: Config.options.background.widgets.clock.style === "cookie" + title: Translation.tr("Dial style") + ConfigSelectionArray { + currentValue: Config.options.background.widgets.clock.cookie.dialNumberStyle + onSelected: newValue => { + Config.options.background.widgets.clock.cookie.dialNumberStyle = newValue; + if (newValue !== "dots" && newValue !== "full") { + Config.options.background.widgets.clock.cookie.hourMarks = false; + } + if (newValue === "numbers") { + Config.options.background.widgets.clock.cookie.timeIndicators = false; + } + } + options: [ + { + displayName: "", + icon: "block", + value: "none" + }, + { + displayName: Translation.tr("Dots"), + icon: "graph_6", + value: "dots" + }, + { + displayName: Translation.tr("Full"), + icon: "history_toggle_off", + value: "full" + }, + { + displayName: Translation.tr("Numbers"), + icon: "counter_1", + value: "numbers" + } + ] + } + } + + ContentSubsection { + visible: Config.options.background.widgets.clock.style === "cookie" + title: Translation.tr("Hour hand") + ConfigSelectionArray { + currentValue: Config.options.background.widgets.clock.cookie.hourHandStyle + onSelected: newValue => { + Config.options.background.widgets.clock.cookie.hourHandStyle = newValue; + } + options: [ + { + displayName: "", + icon: "block", + value: "hide" + }, + { + displayName: Translation.tr("Classic"), + icon: "radio", + value: "classic" + }, + { + displayName: Translation.tr("Hollow"), + icon: "circle", + value: "hollow" + }, + { + displayName: Translation.tr("Fill"), + icon: "eraser_size_5", + value: "fill" + }, + ] + } + } + + ContentSubsection { + visible: Config.options.background.widgets.clock.style === "cookie" + title: Translation.tr("Minute hand") + + ConfigSelectionArray { + currentValue: Config.options.background.widgets.clock.cookie.minuteHandStyle + onSelected: newValue => { + Config.options.background.widgets.clock.cookie.minuteHandStyle = newValue; + } + options: [ + { + displayName: "", + icon: "block", + value: "hide" + }, + { + displayName: Translation.tr("Classic"), + icon: "radio", + value: "classic" + }, + { + displayName: Translation.tr("Thin"), + icon: "line_end", + value: "thin" + }, + { + displayName: Translation.tr("Medium"), + icon: "eraser_size_2", + value: "medium" + }, + { + displayName: Translation.tr("Bold"), + icon: "eraser_size_4", + value: "bold" + }, + ] + } + } + + ContentSubsection { + visible: Config.options.background.widgets.clock.style === "cookie" + title: Translation.tr("Second hand") + + ConfigSelectionArray { + currentValue: Config.options.background.widgets.clock.cookie.secondHandStyle + onSelected: newValue => { + Config.options.background.widgets.clock.cookie.secondHandStyle = newValue; + } + options: [ + { + displayName: "", + icon: "block", + value: "hide" + }, + { + displayName: Translation.tr("Classic"), + icon: "radio", + value: "classic" + }, + { + displayName: Translation.tr("Line"), + icon: "line_end", + value: "line" + }, + { + displayName: Translation.tr("Dot"), + icon: "adjust", + value: "dot" + }, + ] + } + } + + ContentSubsection { + visible: Config.options.background.widgets.clock.style === "cookie" + title: Translation.tr("Date style") + + ConfigSelectionArray { + currentValue: Config.options.background.widgets.clock.cookie.dateStyle + onSelected: newValue => { + Config.options.background.widgets.clock.cookie.dateStyle = newValue; + } + options: [ + { + displayName: "", + icon: "block", + value: "hide" + }, + { + displayName: Translation.tr("Bubble"), + icon: "bubble_chart", + value: "bubble" + }, + { + displayName: Translation.tr("Border"), + icon: "rotate_right", + value: "border" + }, + { + displayName: Translation.tr("Rect"), + icon: "rectangle", + value: "rect" + } + ] + } + } + + ContentSubsection { + title: Translation.tr("Quote") + + ConfigSwitch { + buttonIcon: "check" + text: Translation.tr("Enable") + checked: Config.options.background.widgets.clock.quote.enable + onCheckedChanged: { + Config.options.background.widgets.clock.quote.enable = checked; + } + } + MaterialTextArea { + Layout.fillWidth: true + placeholderText: Translation.tr("Quote") + text: Config.options.background.widgets.clock.quote.text + wrapMode: TextEdit.Wrap + onTextChanged: { + Config.options.background.widgets.clock.quote.text = text; + } + } + } + } + + ContentSection { + icon: "weather_mix" + title: Translation.tr("Widget: Weather") + + ConfigRow { + Layout.fillWidth: true + + ConfigSwitch { + Layout.fillWidth: false + buttonIcon: "check" + text: Translation.tr("Enable") + checked: Config.options.background.widgets.weather.enable + onCheckedChanged: { + Config.options.background.widgets.weather.enable = checked; + } + } + Item { + Layout.fillWidth: true + } + ConfigSelectionArray { + Layout.fillWidth: false + currentValue: Config.options.background.widgets.weather.placementStrategy + onSelected: newValue => { + Config.options.background.widgets.weather.placementStrategy = newValue; + } + options: [ + { + displayName: Translation.tr("Draggable"), + icon: "drag_pan", + value: "free" + }, + { + displayName: Translation.tr("Least busy"), + icon: "category", + value: "leastBusy" + }, + { + displayName: Translation.tr("Most busy"), + icon: "shapes", + value: "mostBusy" + }, + ] + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/settings/BarConfig.qml b/dots/.config/quickshell/ii/modules/settings/BarConfig.qml index 2ad39ceca..a53d30644 100644 --- a/dots/.config/quickshell/ii/modules/settings/BarConfig.qml +++ b/dots/.config/quickshell/ii/modules/settings/BarConfig.qml @@ -318,17 +318,17 @@ ContentPage { { displayName: Translation.tr("Normal"), icon: "timer_10", - value: '["1","2","3","4","5","6","7","8","9","10"]' + value: '[]' }, { - displayName: Translation.tr("Japanese"), + displayName: Translation.tr("Han chars"), icon: "square_dot", - value: '["一","二","三","四","五","六","七","八","九","十"]' + value: '["一","二","三","四","五","六","七","八","九","十","十一","十二","十三","十四","十五","十六","十七","十八","十九","二十"]' }, { displayName: Translation.tr("Roman"), icon: "account_balance", - value: '["I","II","III","IV","V","VI","VII","VIII","IX","X"]' + value: '["I","II","III","IV","V","VI","VII","VIII","IX","X","XI","XII","XIII","XIV","XV","XVI","XVII","XVIII","XIX","XX"]' } ] } diff --git a/dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml b/dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml index 89e202e0c..c26cce455 100644 --- a/dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml +++ b/dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml @@ -7,440 +7,6 @@ import qs.modules.common.widgets ContentPage { forceWidth: true - ContentSection { - icon: "wallpaper" - title: Translation.tr("Background") - - ConfigSwitch { - buttonIcon: "nest_clock_farsight_analog" - text: Translation.tr("Show clock") - checked: Config.options.background.clock.show - onCheckedChanged: { - Config.options.background.clock.show = checked; - } - } - - - ConfigSpinBox { - icon: "loupe" - text: Translation.tr("Scale (%)") - value: Config.options.background.clock.scale * 100 - from: 1 - to: 200 - stepSize: 2 - onValueChanged: { - Config.options.background.clock.scale = value / 100; - } - } - - ContentSubsection { - title: Translation.tr("Clock style") - ConfigSelectionArray { - currentValue: Config.options.background.clock.style - onSelected: newValue => { - Config.options.background.clock.style = newValue; - } - options: [ - { - displayName: Translation.tr("Simple digital"), - icon: "timer_10", - value: "digital" - }, - { - displayName: Translation.tr("Material cookie"), - icon: "cookie", - value: "cookie" - } - ] - } - } - - ContentSubsection { - visible: Config.options.background.clock.style === "digital" - title: Translation.tr("Digital clock settings") - - ConfigSwitch { - buttonIcon: "animation" - text: Translation.tr("Animate time change") - checked: Config.options.background.clock.digital.animateChange - onCheckedChanged: { - Config.options.background.clock.digital.animateChange = checked; - } - } - } - - ContentSubsection { - visible: Config.options.background.clock.style === "cookie" - title: Translation.tr("Cookie clock settings") - - ConfigSwitch { - buttonIcon: "wand_stars" - text: Translation.tr("Auto styling with Gemini") - checked: Config.options.background.clock.cookie.aiStyling - onCheckedChanged: { - Config.options.background.clock.cookie.aiStyling = checked; - } - StyledToolTip { - text: Translation.tr("Uses Gemini to categorize the wallpaper then picks a preset based on it.\nYou'll need to set Gemini API key on the left sidebar first.\nImages are downscaled for performance, but just to be safe,\ndo not select wallpapers with sensitive information.") - } - } - - ConfigSwitch { - buttonIcon: "airwave" - text: Translation.tr("Use old sine wave cookie implementation") - checked: Config.options.background.clock.cookie.useSineCookie - onCheckedChanged: { - Config.options.background.clock.cookie.useSineCookie = checked; - } - StyledToolTip { - text: "Looks a bit softer and more consistent with different number of sides,\nbut has less impressive morphing" - } - } - - ConfigSpinBox { - icon: "add_triangle" - text: Translation.tr("Sides") - value: Config.options.background.clock.cookie.sides - from: 0 - to: 40 - stepSize: 1 - onValueChanged: { - Config.options.background.clock.cookie.sides = value; - } - } - - ConfigSwitch { - buttonIcon: "autoplay" - text: Translation.tr("Constantly rotate") - checked: Config.options.background.clock.cookie.constantlyRotate - onCheckedChanged: { - Config.options.background.clock.cookie.constantlyRotate = checked; - } - StyledToolTip { - text: "Makes the clock always rotate. This is extremely expensive\n(expect 50% usage on Intel UHD Graphics) and thus impractical." - } - } - - ConfigRow { - - ConfigSwitch { - enabled: Config.options.background.clock.style === "cookie" && Config.options.background.clock.cookie.dialNumberStyle === "dots" || Config.options.background.clock.cookie.dialNumberStyle === "full" - buttonIcon: "brightness_7" - text: Translation.tr("Hour marks") - checked: Config.options.background.clock.cookie.hourMarks - onEnabledChanged: { - checked = Config.options.background.clock.cookie.hourMarks; - } - onCheckedChanged: { - Config.options.background.clock.cookie.hourMarks = checked; - } - StyledToolTip { - text: "Can only be turned on using the 'Dots' or 'Full' dial style for aesthetic reasons" - } - } - - ConfigSwitch { - enabled: Config.options.background.clock.style === "cookie" && Config.options.background.clock.cookie.dialNumberStyle !== "numbers" - buttonIcon: "timer_10" - text: Translation.tr("Digits in the middle") - checked: Config.options.background.clock.cookie.timeIndicators - onEnabledChanged: { - checked = Config.options.background.clock.cookie.timeIndicators; - } - onCheckedChanged: { - Config.options.background.clock.cookie.timeIndicators = checked; - } - StyledToolTip { - text: "Can't be turned on when using 'Numbers' dial style for aesthetic reasons" - } - } - } - } - - ContentSubsection { - visible: Config.options.background.clock.style === "cookie" - title: Translation.tr("Dial style") - ConfigSelectionArray { - currentValue: Config.options.background.clock.cookie.dialNumberStyle - onSelected: newValue => { - Config.options.background.clock.cookie.dialNumberStyle = newValue; - if (newValue !== "dots" && newValue !== "full") { - Config.options.background.clock.cookie.hourMarks = false; - } - if (newValue === "numbers") { - Config.options.background.clock.cookie.timeIndicators = false; - } - } - options: [ - { - displayName: "", - icon: "block", - value: "none" - }, - { - displayName: Translation.tr("Dots"), - icon: "graph_6", - value: "dots" - }, - { - displayName: Translation.tr("Full"), - icon: "history_toggle_off", - value: "full" - }, - { - displayName: Translation.tr("Numbers"), - icon: "counter_1", - value: "numbers" - } - ] - } - } - - ContentSubsection { - visible: Config.options.background.clock.style === "cookie" - title: Translation.tr("Hour hand") - ConfigSelectionArray { - currentValue: Config.options.background.clock.cookie.hourHandStyle - onSelected: newValue => { - Config.options.background.clock.cookie.hourHandStyle = newValue; - } - options: [ - { - displayName: "", - icon: "block", - value: "hide" - }, - { - displayName: Translation.tr("Classic"), - icon: "radio", - value: "classic" - }, - { - displayName: Translation.tr("Hollow"), - icon: "circle", - value: "hollow" - }, - { - displayName: Translation.tr("Fill"), - icon: "eraser_size_5", - value: "fill" - }, - ] - } - } - - ContentSubsection { - visible: Config.options.background.clock.style === "cookie" - title: Translation.tr("Minute hand") - - ConfigSelectionArray { - currentValue: Config.options.background.clock.cookie.minuteHandStyle - onSelected: newValue => { - Config.options.background.clock.cookie.minuteHandStyle = newValue; - } - options: [ - { - displayName: "", - icon: "block", - value: "hide" - }, - { - displayName: Translation.tr("Classic"), - icon: "radio", - value: "classic" - }, - { - displayName: Translation.tr("Thin"), - icon: "line_end", - value: "thin" - }, - { - displayName: Translation.tr("Medium"), - icon: "eraser_size_2", - value: "medium" - }, - { - displayName: Translation.tr("Bold"), - icon: "eraser_size_4", - value: "bold" - }, - ] - } - } - - ContentSubsection { - visible: Config.options.background.clock.style === "cookie" - title: Translation.tr("Second hand") - - ConfigSelectionArray { - currentValue: Config.options.background.clock.cookie.secondHandStyle - onSelected: newValue => { - Config.options.background.clock.cookie.secondHandStyle = newValue; - } - options: [ - { - displayName: "", - icon: "block", - value: "hide" - }, - { - displayName: Translation.tr("Classic"), - icon: "radio", - value: "classic" - }, - { - displayName: Translation.tr("Line"), - icon: "line_end", - value: "line" - }, - { - displayName: Translation.tr("Dot"), - icon: "adjust", - value: "dot" - }, - ] - } - } - - ContentSubsection { - visible: Config.options.background.clock.style === "cookie" - title: Translation.tr("Date style") - - ConfigSelectionArray { - currentValue: Config.options.background.clock.cookie.dateStyle - onSelected: newValue => { - Config.options.background.clock.cookie.dateStyle = newValue; - } - options: [ - { - displayName: "", - icon: "block", - value: "hide" - }, - { - displayName: Translation.tr("Bubble"), - icon: "bubble_chart", - value: "bubble" - }, - { - displayName: Translation.tr("Border"), - icon: "rotate_right", - value: "border" - }, - { - displayName: Translation.tr("Rect"), - icon: "rectangle", - value: "rect" - } - ] - } - } - - ContentSubsection { - title: Translation.tr("Quote settings") - ConfigSwitch { - buttonIcon: "format_quote" - text: Translation.tr("Show quote") - checked: Config.options.background.showQuote - onCheckedChanged: { - Config.options.background.showQuote = checked; - } - } - MaterialTextArea { - Layout.fillWidth: true - placeholderText: Translation.tr("Quote") - text: Config.options.background.quote - wrapMode: TextEdit.Wrap - onTextChanged: { - Config.options.background.quote = text; - } - } - } - - ContentSubsection { - title: Translation.tr("Wallpaper parallax") - - ConfigSwitch { - buttonIcon: "unfold_more_double" - text: Translation.tr("Vertical") - checked: Config.options.background.parallax.vertical - onCheckedChanged: { - Config.options.background.parallax.vertical = checked; - } - } - - ConfigRow { - uniform: true - ConfigSwitch { - buttonIcon: "counter_1" - text: Translation.tr("Depends on workspace") - checked: Config.options.background.parallax.enableWorkspace - onCheckedChanged: { - Config.options.background.parallax.enableWorkspace = checked; - } - } - ConfigSwitch { - buttonIcon: "side_navigation" - text: Translation.tr("Depends on sidebars") - checked: Config.options.background.parallax.enableSidebar - onCheckedChanged: { - Config.options.background.parallax.enableSidebar = checked; - } - } - } - ConfigSpinBox { - icon: "loupe" - text: Translation.tr("Preferred wallpaper zoom (%)") - value: Config.options.background.parallax.workspaceZoom * 100 - from: 100 - to: 150 - stepSize: 1 - onValueChanged: { - Config.options.background.parallax.workspaceZoom = value / 100; - } - } - } - } - - ContentSection { - icon: "point_scan" - title: Translation.tr("Crosshair overlay") - - MaterialTextArea { - Layout.fillWidth: true - placeholderText: Translation.tr("Crosshair code (in Valorant's format)") - text: Config.options.crosshair.code - wrapMode: TextEdit.Wrap - onTextChanged: { - Config.options.crosshair.code = text; - } - } - - RowLayout { - StyledText { - Layout.leftMargin: 10 - color: Appearance.colors.colSubtext - font.pixelSize: Appearance.font.pixelSize.smallie - text: Translation.tr("Press Super+G to toggle appearance") - } - Item { - Layout.fillWidth: true - } - RippleButtonWithIcon { - id: editorButton - buttonRadius: Appearance.rounding.full - materialIcon: "open_in_new" - mainText: Translation.tr("Open editor") - onClicked: { - Qt.openUrlExternally(`https://www.vcrdb.net/builder?c=${Config.options.crosshair.code}`); - } - StyledToolTip { - text: "www.vcrdb.net" - } - } - } - } - ContentSection { icon: "call_to_action" title: Translation.tr("Dock") @@ -609,6 +175,67 @@ ContentPage { } } + ContentSection { + icon: "select_window" + title: Translation.tr("Overlay: General") + + ConfigSwitch { + buttonIcon: "high_density" + text: Translation.tr("Enable opening zoom animation") + checked: Config.options.overlay.openingZoomAnimation + onCheckedChanged: { + Config.options.overlay.openingZoomAnimation = checked; + } + } + ConfigSwitch { + buttonIcon: "texture" + text: Translation.tr("Darken screen") + checked: Config.options.overlay.darkenScreen + onCheckedChanged: { + Config.options.overlay.darkenScreen = checked; + } + } + } + + ContentSection { + icon: "point_scan" + title: Translation.tr("Overlay: Crosshair") + + MaterialTextArea { + Layout.fillWidth: true + placeholderText: Translation.tr("Crosshair code (in Valorant's format)") + text: Config.options.crosshair.code + wrapMode: TextEdit.Wrap + onTextChanged: { + Config.options.crosshair.code = text; + } + } + + RowLayout { + StyledText { + Layout.leftMargin: 10 + color: Appearance.colors.colSubtext + font.pixelSize: Appearance.font.pixelSize.smallie + text: Translation.tr("Press Super+G to open the overlay and pin the crosshair") + } + Item { + Layout.fillWidth: true + } + RippleButtonWithIcon { + id: editorButton + buttonRadius: Appearance.rounding.full + materialIcon: "open_in_new" + mainText: Translation.tr("Open editor") + onClicked: { + Qt.openUrlExternally(`https://www.vcrdb.net/builder?c=${Config.options.crosshair.code}`); + } + StyledToolTip { + text: "www.vcrdb.net" + } + } + } + } + ContentSection { icon: "screenshot_frame_2" title: Translation.tr("Region selector (screen snipping/Google Lens)") diff --git a/dots/.config/quickshell/ii/modules/settings/ServicesConfig.qml b/dots/.config/quickshell/ii/modules/settings/ServicesConfig.qml index c9ff0c16b..f5931bb1b 100644 --- a/dots/.config/quickshell/ii/modules/settings/ServicesConfig.qml +++ b/dots/.config/quickshell/ii/modules/settings/ServicesConfig.qml @@ -85,6 +85,31 @@ ContentPage { } + ContentSection { + icon: "file_open" + title: Translation.tr("Save paths") + + MaterialTextArea { + Layout.fillWidth: true + placeholderText: Translation.tr("Video Recording Path") + text: Config.options.screenRecord.savePath + wrapMode: TextEdit.Wrap + onTextChanged: { + Config.options.screenRecord.savePath = text; + } + } + + MaterialTextArea { + Layout.fillWidth: true + placeholderText: Translation.tr("Screenshot Path (leave empty to just copy)") + text: Config.options.screenSnip.savePath + wrapMode: TextEdit.Wrap + onTextChanged: { + Config.options.screenSnip.savePath = text; + } + } + } + ContentSection { icon: "search" title: Translation.tr("Search") diff --git a/dots/.config/quickshell/ii/modules/sidebarLeft/aiChat/AiMessage.qml b/dots/.config/quickshell/ii/modules/sidebarLeft/aiChat/AiMessage.qml index 9ede16e33..74d5c5f6a 100644 --- a/dots/.config/quickshell/ii/modules/sidebarLeft/aiChat/AiMessage.qml +++ b/dots/.config/quickshell/ii/modules/sidebarLeft/aiChat/AiMessage.qml @@ -329,7 +329,7 @@ Rectangle { segmentContent: thisBlock.content messageData: root.messageData done: root.messageData?.done ?? false - forceDisableChunkSplitting: root.messageData.content.includes("```") + forceDisableChunkSplitting: root.messageData?.content.includes("```") ?? true } } } } diff --git a/dots/.config/quickshell/ii/modules/sidebarLeft/aiChat/MessageTextBlock.qml b/dots/.config/quickshell/ii/modules/sidebarLeft/aiChat/MessageTextBlock.qml index 2c471d3f8..de508e169 100644 --- a/dots/.config/quickshell/ii/modules/sidebarLeft/aiChat/MessageTextBlock.qml +++ b/dots/.config/quickshell/ii/modules/sidebarLeft/aiChat/MessageTextBlock.qml @@ -159,7 +159,7 @@ ColumnLayout { selectedTextColor: Appearance.m3colors.m3onSecondaryContainer selectionColor: Appearance.colors.colSecondaryContainer wrapMode: TextEdit.Wrap - color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1 + color: root.messageData?.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1 textFormat: renderMarkdown ? TextEdit.MarkdownText : TextEdit.PlainText text: modelData diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidScreenSnipToggle.qml b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidScreenSnipToggle.qml index c4b4a5479..875022249 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidScreenSnipToggle.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidScreenSnipToggle.qml @@ -23,7 +23,7 @@ AndroidQuickToggleButton { interval: 300 repeat: false onTriggered: { - Hyprland.dispatch("global quickshell:regionScreenshot") + Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "region", "screenshot"]); } } diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/volumeMixer/VolumeDialog.qml b/dots/.config/quickshell/ii/modules/sidebarRight/volumeMixer/VolumeDialog.qml index 58a7af71a..004440b25 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/volumeMixer/VolumeDialog.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/volumeMixer/VolumeDialog.qml @@ -11,87 +11,14 @@ import Quickshell.Services.Pipewire WindowDialog { id: root property bool isSink: true - function correctType(node) { - return (node.isSink === root.isSink) && node.audio - } - readonly property list appPwNodes: Pipewire.nodes.values.filter((node) => { // Should be list but it breaks ScriptModel - return root.correctType(node) && node.isStream - }) - readonly property bool hasApps: appPwNodes.length > 0 backgroundHeight: 700 WindowDialogTitle { text: root.isSink ? Translation.tr("Audio output") : Translation.tr("Audio input") } - WindowDialogSectionHeader { - visible: root.hasApps - text: Translation.tr("Applications") - } - - WindowDialogSeparator { - visible: root.hasApps - Layout.topMargin: -22 - Layout.leftMargin: 0 - Layout.rightMargin: 0 - } - - DialogSectionListView { - visible: root.hasApps - Layout.fillHeight: true - - model: ScriptModel { - values: root.appPwNodes - } - delegate: VolumeMixerEntry { - anchors { - left: parent?.left - right: parent?.right - } - required property var modelData - node: modelData - } - } - - WindowDialogSectionHeader { - text: Translation.tr("Devices") - } - - WindowDialogSeparator { - Layout.topMargin: -22 - Layout.leftMargin: 0 - Layout.rightMargin: 0 - } - - DialogSectionListView { - Layout.fillHeight: !root.hasApps - Layout.preferredHeight: 180 - - model: ScriptModel { - values: Pipewire.nodes.values.filter(node => { - return root.correctType(node) && !node.isStream - }) - } - delegate: StyledRadioButton { - id: radioButton - required property var modelData - anchors { - left: parent?.left - right: parent?.right - } - - description: modelData.description - checked: modelData.id === (root.isSink ? Pipewire.preferredDefaultAudioSink?.id : Pipewire.preferredDefaultAudioSource?.id) - - onCheckedChanged: { - if (!checked) return; - if (root.isSink) { - Pipewire.preferredDefaultAudioSink = modelData - } else { - Pipewire.preferredDefaultAudioSource = modelData - } - } - } + VolumeDialogContent { + isSink: root.isSink } WindowDialogSeparator { @@ -117,20 +44,4 @@ WindowDialog { onClicked: root.dismiss() } } - - component DialogSectionListView: StyledListView { - Layout.fillWidth: true - Layout.topMargin: -22 - Layout.bottomMargin: -16 - Layout.leftMargin: -Appearance.rounding.large - Layout.rightMargin: -Appearance.rounding.large - topMargin: 12 - bottomMargin: 12 - leftMargin: 20 - rightMargin: 20 - - clip: true - spacing: 4 - animateAppearance: false - } } diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/volumeMixer/VolumeDialogContent.qml b/dots/.config/quickshell/ii/modules/sidebarRight/volumeMixer/VolumeDialogContent.qml new file mode 100644 index 000000000..56f524968 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/sidebarRight/volumeMixer/VolumeDialogContent.qml @@ -0,0 +1,107 @@ +import qs +import qs.services +import qs.modules.common +import qs.modules.common.widgets +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Services.Pipewire + +ColumnLayout { + id: root + required property bool isSink + function correctType(node) { + return (node.isSink === root.isSink) && node.audio + } + readonly property list appPwNodes: Pipewire.nodes.values.filter((node) => { // Should be list but it breaks ScriptModel + return root.correctType(node) && node.isStream + }) + readonly property bool hasApps: appPwNodes.length > 0 + spacing: 16 + + WindowDialogSectionHeader { + visible: root.hasApps + text: Translation.tr("Applications") + } + + WindowDialogSeparator { + visible: root.hasApps + Layout.topMargin: -22 + Layout.leftMargin: 0 + Layout.rightMargin: 0 + } + + DialogSectionListView { + visible: root.hasApps + Layout.fillHeight: true + + model: ScriptModel { + values: root.appPwNodes + } + delegate: VolumeMixerEntry { + anchors { + left: parent?.left + right: parent?.right + } + required property var modelData + node: modelData + } + } + + WindowDialogSectionHeader { + text: Translation.tr("Devices") + } + + WindowDialogSeparator { + Layout.topMargin: -22 + Layout.leftMargin: 0 + Layout.rightMargin: 0 + } + + DialogSectionListView { + Layout.fillHeight: !root.hasApps + Layout.preferredHeight: 180 + + model: ScriptModel { + values: Pipewire.nodes.values.filter(node => { + return root.correctType(node) && !node.isStream + }) + } + delegate: StyledRadioButton { + id: radioButton + required property var modelData + anchors { + left: parent?.left + right: parent?.right + } + + description: modelData.description + checked: modelData.id === (root.isSink ? Pipewire.preferredDefaultAudioSink?.id : Pipewire.preferredDefaultAudioSource?.id) + + onCheckedChanged: { + if (!checked) return; + if (root.isSink) { + Pipewire.preferredDefaultAudioSink = modelData + } else { + Pipewire.preferredDefaultAudioSource = modelData + } + } + } + } + + component DialogSectionListView: StyledListView { + Layout.fillWidth: true + Layout.topMargin: -22 + Layout.bottomMargin: -16 + Layout.leftMargin: -Appearance.rounding.large + Layout.rightMargin: -Appearance.rounding.large + topMargin: 12 + bottomMargin: 12 + leftMargin: 20 + rightMargin: 20 + + clip: true + spacing: 4 + animateAppearance: false + } +} diff --git a/dots/.config/quickshell/ii/modules/verticalBar/VerticalClockWidget.qml b/dots/.config/quickshell/ii/modules/verticalBar/VerticalClockWidget.qml index 391d2e78c..921f63603 100644 --- a/dots/.config/quickshell/ii/modules/verticalBar/VerticalClockWidget.qml +++ b/dots/.config/quickshell/ii/modules/verticalBar/VerticalClockWidget.qml @@ -36,7 +36,7 @@ Item { hoverEnabled: true acceptedButtons: Qt.NoButton - Bar.ClockWidgetTooltip { + Bar.ClockWidgetPopup { hoverTarget: mouseArea } } diff --git a/dots/.config/quickshell/ii/modules/verticalBar/VerticalMedia.qml b/dots/.config/quickshell/ii/modules/verticalBar/VerticalMedia.qml index 7a512564a..677941d64 100644 --- a/dots/.config/quickshell/ii/modules/verticalBar/VerticalMedia.qml +++ b/dots/.config/quickshell/ii/modules/verticalBar/VerticalMedia.qml @@ -74,30 +74,13 @@ MouseArea { anchors.centerIn: parent spacing: 4 - Row { - spacing: 4 - - MaterialSymbol { - anchors.verticalCenter: parent.verticalCenter - fill: 0 - font.weight: Font.Medium - text: "music_note" - iconSize: Appearance.font.pixelSize.large - color: Appearance.colors.colOnSurfaceVariant - } - - StyledText { - anchors.verticalCenter: parent.verticalCenter - text: "Media" - font { - weight: Font.Medium - pixelSize: Appearance.font.pixelSize.normal - } - color: Appearance.colors.colOnSurfaceVariant - } + Bar.StyledPopupHeaderRow { + icon: "music_note" + label: Translation.tr("Media") } StyledText { + color: Appearance.colors.colOnSurfaceVariant text: `${cleanedTitle}${activePlayer?.trackArtist ? '\n' + activePlayer.trackArtist : ''}` } } diff --git a/dots/.config/quickshell/ii/scripts/images/least_busy_region.py b/dots/.config/quickshell/ii/scripts/images/least_busy_region.py index 1d64033c6..b9e1c8963 100755 --- a/dots/.config/quickshell/ii/scripts/images/least_busy_region.py +++ b/dots/.config/quickshell/ii/scripts/images/least_busy_region.py @@ -18,7 +18,7 @@ def center_crop(img, target_w, target_h): y2 = y1 + target_h return img[y1:y2, x1:x2] -def find_least_busy_region(image_path, region_width=300, region_height=200, screen_width=None, screen_height=None, verbose=False, stride=2, screen_mode="fill", horizontal_padding=50, vertical_padding=50): +def find_least_busy_region(image_path, region_width=300, region_height=200, screen_width=None, screen_height=None, verbose=False, stride=2, screen_mode="fill", horizontal_padding=50, vertical_padding=50, busiest=False): img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) if img is None: raise FileNotFoundError(f"Image not found: {image_path}") @@ -77,7 +77,9 @@ def find_least_busy_region(image_path, region_width=300, region_height=200, scre total += ii[y1-1, x1-1] return total min_var = None + max_var = None min_coords = (horizontal_padding, vertical_padding) + max_coords = (horizontal_padding, vertical_padding) area = region_width * region_height x_start = horizontal_padding y_start = vertical_padding @@ -100,7 +102,13 @@ def find_least_busy_region(image_path, region_width=300, region_height=200, scre if (min_var is None) or (var < min_var): min_var = var min_coords = (x, y) - return min_coords, min_var + if (max_var is None) or (var > max_var): + max_var = var + max_coords = (x, y) + if busiest: + return max_coords, max_var + else: + return min_coords, min_var def find_largest_region(image_path, screen_width=None, screen_height=None, verbose=False, stride=2, screen_mode="fill", threshold=100.0, aspect_ratio=1.0, horizontal_padding=50, vertical_padding=50): img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) @@ -313,6 +321,7 @@ def main(): parser.add_argument("--aspect-ratio", type=float, default=1.78, help="Aspect ratio (width/height) for largest region mode") parser.add_argument("--horizontal-padding", "-hp", type=int, default=50, help="Minimum horizontal distance from region to image edge") parser.add_argument("--vertical-padding", "-vp", type=int, default=50, help="Minimum vertical distance from region to image edge") + parser.add_argument("--busiest", action="store_true", help="Find the busiest region instead of the least busy") args = parser.parse_args() if args.largest_region: @@ -363,7 +372,8 @@ def main(): stride=args.stride, screen_mode=args.screen_mode, horizontal_padding=args.horizontal_padding, - vertical_padding=args.vertical_padding + vertical_padding=args.vertical_padding, + busiest=args.busiest ) if args.visual_output: draw_region(args.image_path, coords, region_width=args.width, region_height=args.height, screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode) diff --git a/dots/.config/quickshell/ii/scripts/videos/record.sh b/dots/.config/quickshell/ii/scripts/videos/record.sh index 794bcf1ba..bf0ab504d 100755 --- a/dots/.config/quickshell/ii/scripts/videos/record.sh +++ b/dots/.config/quickshell/ii/scripts/videos/record.sh @@ -1,5 +1,18 @@ #!/usr/bin/env bash +CONFIG_FILE="$HOME/.config/illogical-impulse/config.json" +JSON_PATH=".screenRecord.savePath" + +CUSTOM_PATH=$(jq -r "$JSON_PATH" "$CONFIG_FILE" 2>/dev/null) + +RECORDING_DIR="" + +if [[ -n "$CUSTOM_PATH" ]]; then + RECORDING_DIR="$CUSTOM_PATH" +else + RECORDING_DIR="$HOME/Videos" # Use default path +fi + getdate() { date '+%Y-%m-%d_%H.%M.%S' } @@ -10,12 +23,8 @@ getactivemonitor() { hyprctl monitors -j | jq -r '.[] | select(.focused == true) | .name' } -xdgvideo="$(xdg-user-dir VIDEOS)" -if [[ $xdgvideo = "$HOME" ]]; then - unset xdgvideo -fi -mkdir -p "${xdgvideo:-$HOME/Videos}" -cd "${xdgvideo:-$HOME/Videos}" || exit +mkdir -p "$RECORDING_DIR" +cd "$RECORDING_DIR" || exit # parse --region without modifying $@ so other flags like --fullscreen still work ARGS=("$@") @@ -66,4 +75,4 @@ else wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$region" fi fi -fi +fi \ No newline at end of file diff --git a/dots/.config/quickshell/ii/services/Battery.qml b/dots/.config/quickshell/ii/services/Battery.qml index b07bd5305..e2a285f22 100644 --- a/dots/.config/quickshell/ii/services/Battery.qml +++ b/dots/.config/quickshell/ii/services/Battery.qml @@ -31,6 +31,25 @@ Singleton { property real timeToEmpty: UPower.displayDevice.timeToEmpty property real timeToFull: UPower.displayDevice.timeToFull + property real health: (function() { + const devList = UPower.devices.values; + for (let i = 0; i < devList.length; ++i) { + const dev = devList[i]; + if (dev.isLaptopBattery && dev.healthSupported) { + const health = dev.healthPercentage; + if (health === 0) { + return 0.01; + } else if (health < 1) { + return health * 100; + } else { + return health; + } + } + } + return 0; + })() + + onIsLowAndNotChargingChanged: { if (!root.available || !isLowAndNotCharging) return; Quickshell.execDetached([ @@ -38,7 +57,8 @@ Singleton { Translation.tr("Low battery"), Translation.tr("Consider plugging in your device"), "-u", "critical", - "-a", "Shell" + "-a", "Shell", + "--hint=int:transient:1", ]) if (root.soundEnabled) Audio.playSystemSound("dialog-warning"); @@ -51,7 +71,8 @@ Singleton { Translation.tr("Critically low battery"), Translation.tr("Please charge!\nAutomatic suspend triggers at %1%").arg(Config.options.battery.suspend), "-u", "critical", - "-a", "Shell" + "-a", "Shell", + "--hint=int:transient:1", ]); if (root.soundEnabled) Audio.playSystemSound("suspend-error"); @@ -69,7 +90,8 @@ Singleton { "notify-send", Translation.tr("Battery full"), Translation.tr("Please unplug the charger"), - "-a", "Shell" + "-a", "Shell", + "--hint=int:transient:1", ]); if (root.soundEnabled) Audio.playSystemSound("complete"); diff --git a/dots/.config/quickshell/ii/services/KeyringStorage.qml b/dots/.config/quickshell/ii/services/KeyringStorage.qml index 1e8562cac..ae49496d2 100644 --- a/dots/.config/quickshell/ii/services/KeyringStorage.qml +++ b/dots/.config/quickshell/ii/services/KeyringStorage.qml @@ -108,7 +108,7 @@ Singleton { } } onExited: (exitCode, exitStatus) => { - console.log("[KeyringStorage] Keyring data fetch process exited with code:", exitCode); + // console.log("[KeyringStorage] Keyring data fetch process exited with code:", exitCode); if (exitCode === 1) { console.error("[KeyringStorage] Entry not found, initializing."); root.keyringData = {}; diff --git a/dots/.config/quickshell/ii/services/Notifications.qml b/dots/.config/quickshell/ii/services/Notifications.qml index 37d72f5b3..702da0f8a 100644 --- a/dots/.config/quickshell/ii/services/Notifications.qml +++ b/dots/.config/quickshell/ii/services/Notifications.qml @@ -25,6 +25,7 @@ Singleton { "text": action.text, })) ?? [] property bool popup: false + property bool isTransient: notification?.hints.transient ?? false property string appIcon: notification?.appIcon ?? "" property string appName: notification?.appName ?? "" property string body: notification?.body ?? "" @@ -63,7 +64,11 @@ Singleton { interval: 7000 running: true onTriggered: () => { - root.timeoutNotification(notificationId); + const index = root.list.findIndex((notif) => notif.notificationId === notificationId); + const notifObject = root.list[index]; + print("[Notifications] Notification timer triggered for ID: " + notificationId + ", transient: " + notifObject?.isTransient); + if (notifObject.isTransient) root.discardNotification(notificationId); + else root.timeoutNotification(notificationId); destroy() } } diff --git a/dots/.config/quickshell/ii/settings.qml b/dots/.config/quickshell/ii/settings.qml index a836c06a1..65c64c164 100644 --- a/dots/.config/quickshell/ii/settings.qml +++ b/dots/.config/quickshell/ii/settings.qml @@ -31,7 +31,6 @@ ApplicationWindow { { name: Translation.tr("General"), icon: "browse", - iconRotation: 180, component: "modules/settings/GeneralConfig.qml" }, { @@ -40,6 +39,11 @@ ApplicationWindow { iconRotation: 180, component: "modules/settings/BarConfig.qml" }, + { + name: Translation.tr("Background"), + icon: "texture", + component: "modules/settings/BackgroundConfig.qml" + }, { name: Translation.tr("Interface"), icon: "bottom_app_bar", diff --git a/dots/.config/quickshell/ii/shell.qml b/dots/.config/quickshell/ii/shell.qml index d50f6640f..209597fcf 100644 --- a/dots/.config/quickshell/ii/shell.qml +++ b/dots/.config/quickshell/ii/shell.qml @@ -11,7 +11,6 @@ import qs.modules.common import qs.modules.background import qs.modules.bar import qs.modules.cheatsheet -import qs.modules.crosshair import qs.modules.dock import qs.modules.lock import qs.modules.mediaControls @@ -25,6 +24,7 @@ import qs.modules.screenCorners import qs.modules.sessionScreen import qs.modules.sidebarLeft import qs.modules.sidebarRight +import qs.modules.overlay import qs.modules.verticalBar import qs.modules.wallpaperSelector @@ -39,7 +39,6 @@ ShellRoot { property bool enableBar: true property bool enableBackground: true property bool enableCheatsheet: true - property bool enableCrosshair: true property bool enableDock: true property bool enableLock: true property bool enableMediaControls: true @@ -47,6 +46,7 @@ ShellRoot { property bool enablePolkit: true property bool enableOnScreenDisplay: true property bool enableOnScreenKeyboard: true + property bool enableOverlay: true property bool enableOverview: true property bool enableRegionSelector: true property bool enableReloadPopup: true @@ -70,13 +70,13 @@ ShellRoot { LazyLoader { active: enableBar && Config.ready && !Config.options.bar.vertical; component: Bar {} } LazyLoader { active: enableBackground; component: Background {} } LazyLoader { active: enableCheatsheet; component: Cheatsheet {} } - LazyLoader { active: enableCrosshair; component: Crosshair {} } LazyLoader { active: enableDock && Config.options.dock.enable; component: Dock {} } LazyLoader { active: enableLock; component: Lock {} } LazyLoader { active: enableMediaControls; component: MediaControls {} } LazyLoader { active: enableNotificationPopup; component: NotificationPopup {} } LazyLoader { active: enableOnScreenDisplay; component: OnScreenDisplay {} } LazyLoader { active: enableOnScreenKeyboard; component: OnScreenKeyboard {} } + LazyLoader { active: enableOverlay; component: Overlay {} } LazyLoader { active: enableOverview; component: Overview {} } LazyLoader { active: enablePolkit; component: Polkit {} } LazyLoader { active: enableRegionSelector; component: RegionSelector {} } diff --git a/sdata/dist-nix/README.md b/sdata/dist-nix/README.md index 5febf4698..cc1c8dd07 100644 --- a/sdata/dist-nix/README.md +++ b/sdata/dist-nix/README.md @@ -3,14 +3,23 @@ - See also [Install scripts | illogical-impulse](https://ii.clsty.link/en/dev/inst-script/) - See also [#1061](https://github.com/end-4/dots-hyprland/issues/1061) -**NOTE: The sdata/dist-nix is not for NixOS but every distro, using Nix and home-manager.** +**NOTE: The `dist-nix` is not for NixOS but every distro, using Nix and home-manager.** +As we all know Nix and Home-manager has two major functionalities: +- Handling dependencies (i.e. package installation) +- Handling dotfiles + +They are discussed in following sections. + +# Handling dependencies +## Status +Partially works. See [Discussion #2382](https://github.com/end-4/dots-hyprland/discussions/2382). ## plan Note that this script must be idempotent. TODO: -- [ ] Fix all TODOs inside `dist-nix`. -- [ ] Warn user if inode-limited filesystem (typically ext4) is used. +- [ ] Fix all TODOs inside `dist-nix`. ([search online](https://github.com/search?q=repo%3Aend-4%2Fdots-hyprland+path%3A%2F%5Esdata%5C%2Fdist-nix%5C%2F%2F+TODO&type=code)) +- [ ] Since Nix uses a large number of inodes, need to warn user if inode-limited filesystem (typically ext4) is used. - [ ] Deal with error when running `systemctl --user enable ydotool --now`: ```plain Failed to connect to user scope bus via local transport: $DBUS_SESSION_BUS_ADDRESS and $XDG_RUNTIME_DIR not defined (consider using --machine=@.host --user to connect to bus of other user) @@ -23,9 +32,48 @@ On non-NixOS distros, programs using PAM (typically screen locker) will not work - One problem is that Debian(-based) distros use modified version of PAM which supports `@include` directive in `/etc/pam.d` config files but the PAM from Nix does not support it, see [this comment](https://github.com/NixOS/nixpkgs/issues/128523#issuecomment-1086106614). - Another problem is the location of a suid helper binary that is necessary, see [this comment](https://github.com/end-4/dots-hyprland/issues/1061#issuecomment-3403195230). -The problem could be solved by using the system-provided libpam instead. +As [commented](https://github.com/end-4/dots-hyprland/issues/1061#issuecomment-3403195230) by @Cu3PO42 , both the problem could be solved by using the system-provided libpam instead. -See also https://github.com/caelestia-dots/shell/issues/668 +See also [caelestia-dots/shell#668](https://github.com/caelestia-dots/shell/issues/668). ### NixGL On non-NixOS distros, packages installed via home-manager have problem accessing GPU, especially Hyprland because it requires GPU acceleration to launch. `nixGL` should be used to address the problem. + +# Handling dot files +## Status +Paused, until some suitable method has been confirmed to meet the requirements below. +## Requirements +About handling the dotfiles, i.e. `dots/`, if we are doing this using Nix then the following requirements must be fulfilled. + +**1. Allow modifications over the existing dotfiles.** + +Current state of `./setup install`: +- After finishing running `./setup install`, users can modify any dotfiles in a traditional way, and if they run `./setup install` again to update then they need to skip the steps which overwrite the targets that they have modified and later sync the upgrade manually for such targets by themselves. + - For Hyprland, specially we have a `custom` folder along with `~/.config/hypr/hyprland.conf` which will only get overwritten the first time but not the later times running `./setup install`. +- This works but is not elegant. An experimental solution is using yaml config to store the selected behavior for each target, see [#2137](https://github.com/end-4/dots-hyprland/issues/2137). + +If we use Nix to handle dotfiles, then it must be at least better than the current state described above, mainly in terms of convenience and automation. + +**2. Allow choosing targets.** + +This is similar to the above. For example user may want to use their own `~/.config/foot` instead of the files under `dots/.config/foot` entirely. + +**3. Easy developing dotfiles or at least not worse than current state.** + +About the current state: +- @clsty: "If I were the one who develops the dotfiles, I will make changes to the local Git repo `dots-hyprland` and rerun `./setup install-files -f` to apply the changes to observe the outcome." +- @end-4 (who develops the dots; see [comment](https://github.com/end-4/dots-hyprland/pull/2278#issuecomment-3454929577)): "I modify my local copy of stuff, copy the relevant parts over, optionally selectively pick changes then commit. It's.... the most obvious way but I guess not necessarily the cleanest" + +If we use Nix to handle dotfiles, then it must be at least better than the current state described above, mainly in terms of convenience and automation. + +**4. Others** + +Find out a good method to avoid what @end-4 [mentioned](https://github.com/end-4/dots-hyprland/issues/1061#issuecomment-2954725029): + +> About home-manager, from my limited understanding of and experience with it, any change to the config files require a rebuild right? If this is indeed the case, switching entirely to this is not okay. Having to wait 20 seconds for each change is absurd. + +Some information may help, e.g. @darsh032 [commented](https://github.com/end-4/dots-hyprland/issues/1061#issuecomment-3336839862): + +> I mean thats not really needed you can use mkOutOfStoreSymlink or use hjem-impure to change the configs without rebuilding + +And also the "hmrice" [mentioned](https://github.com/end-4/dots-hyprland/issues/1061#issuecomment-3353345504) by @Markus328 , and the `flake.nix` (for quickshell only) [mentioned](https://github.com/end-4/dots-hyprland/issues/1061#issuecomment-3354387126) by @darsh032 . diff --git a/sdata/dist-nix/home-manager/home.nix b/sdata/dist-nix/home-manager/home.nix index e8f96cde2..b6ab39cbe 100644 --- a/sdata/dist-nix/home-manager/home.nix +++ b/sdata/dist-nix/home-manager/home.nix @@ -18,8 +18,9 @@ # "org.freedesktop.impl.portal.ScreenCast" = [ "gnome" ]; #}; }; - # The following seems to generate ~/.config/fontconfig conflicting with the one under dots/ - #fonts.fontconfig.enable = true; + # Note: The following generate files under ~/.config/fontconfig/conf.d/ + # fontconfig may rely on this to properly find fonts installed via Nix. + fonts.fontconfig.enable = true; wayland.windowManager.hyprland = { ## Make sure home-manager not generate ~/.config/hypr/hyprland.conf