Merge branch 'main' into keybinds-settings

This commit is contained in:
Madjid Taha
2025-11-06 12:30:05 +01:00
committed by GitHub
72 changed files with 2148 additions and 707 deletions
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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
@@ -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
@@ -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
}
}
}
@@ -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;
}
}
}
}
@@ -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
}
}
}
@@ -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
}
}
}
@@ -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
@@ -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
@@ -8,10 +8,10 @@ import QtQuick
Column {
id: root
property list<string> 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 {
@@ -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
}
@@ -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
@@ -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 {
@@ -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 {
@@ -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
}
}
}
}
@@ -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
@@ -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"
})
}
@@ -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
}
}
@@ -334,7 +342,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
}
@@ -369,6 +377,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
@@ -385,6 +398,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
@@ -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]
}
}
}
@@ -79,6 +79,22 @@ Singleton {
property bool inhibit: false
}
property JsonObject overlay: JsonObject {
property list<string> open: ["crosshair"]
property JsonObject crosshair: JsonObject {
property bool pinned: false
property bool clickthrough: true
property real x: 100
property real y: 100
}
property JsonObject volumeMixer: JsonObject {
property bool pinned: false
property bool clickthrough: false
property real x: 55
property real y: 188
}
}
property JsonObject timer: JsonObject {
property JsonObject pomodoro: JsonObject {
property bool running: false
@@ -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
}
@@ -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
}
@@ -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)
}
}
@@ -0,0 +1,7 @@
import QtQuick
MouseArea {
id: root
// uh this is stupid turns out we don't need anything here
}
@@ -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;
}
}
}
@@ -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;
}
}
@@ -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;
}
}
}
@@ -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 {
}
}
}
}
@@ -0,0 +1,37 @@
pragma Singleton
pragma ComponentBehavior: Bound
import Quickshell
Singleton {
id: root
readonly property list<var> availableWidgets: [
{ identifier: "crosshair", materialSymbol: "point_scan" },
{ identifier: "volumeMixer", materialSymbol: "volume_up" }
]
readonly property bool hasPinnedWidgets: root.pinnedWidgetIdentifiers.length > 0
property list<string> pinnedWidgetIdentifiers: []
property list<var> 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)
}
}
}
@@ -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
}
}
}
}
@@ -0,0 +1,18 @@
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
DelegateChooser {
id: root
role: "identifier"
DelegateChoice { roleValue: "crosshair"; Crosshair {} }
DelegateChoice { roleValue: "volumeMixer"; VolumeMixer {} }
}
@@ -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/<yourWidget>/<YourWidget>.qml, using this as the base class and declare your widget content as contentItem
* 2. Add an entry to OverlayContext.availableWidgets with identifier=<yourWidgetIdentifier>
* 3. Add an entry in Persistent.states.overlay.<yourWidgetIdentifier> with x, y, pinned, clickthrough properties set to reasonable defaults
* 4. Add an entry in OverlayWidgetDelegateChooser with roleValue=<yourWidgetIdentifier> 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
}
}
}
}
@@ -0,0 +1,9 @@
import QtQuick
import Quickshell
import qs.modules.common
import qs.modules.overlay
StyledOverlayWidget {
id: root
contentItem: CrosshairContent {}
}
@@ -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: 700
implicitWidth: 400
VolumeDialogContent {
anchors.fill: parent
anchors.margins: parent.padding
isSink: true
}
}
}
@@ -224,8 +224,8 @@ Item {
}
z: Drag.active ? root.windowDraggingZ : (root.windowZ + windowData?.floating)
Drag.hotSpot.x: targetWindowWidth / 2
Drag.hotSpot.y: targetWindowHeight / 2
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
MouseArea {
id: dragArea
anchors.fill: parent
@@ -121,10 +121,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) {
@@ -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"
},
]
}
}
}
}
@@ -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"),
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"]'
}
]
}
@@ -701,6 +701,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)")
@@ -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
} }
}
}
@@ -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
@@ -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<var> appPwNodes: Pipewire.nodes.values.filter((node) => { // Should be list<PwNode> 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
}
}
@@ -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<var> appPwNodes: Pipewire.nodes.values.filter((node) => { // Should be list<PwNode> 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
}
}
@@ -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)
@@ -38,7 +38,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 +52,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 +71,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");
@@ -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 = {};
@@ -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()
}
}
+5 -1
View File
@@ -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",
+3 -3
View File
@@ -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 {} }