Merge branch 'end-4:main' into main

This commit is contained in:
jwihardi
2025-09-30 19:35:49 -04:00
committed by GitHub
56 changed files with 762 additions and 528 deletions
+17
View File
@@ -27,6 +27,23 @@ Singleton {
property bool superReleaseMightTrigger: true
property bool workspaceShowNumbers: false
Connections {
target: Config
function onReadyChanged() {
if (Config.options.lock.launchOnStartup && Config.ready && Persistent.ready && Persistent.isNewHyprlandInstance) {
GlobalStates.screenLocked = true;
}
}
}
Connections {
target: Persistent
function onReadyChanged() {
if (Config.options.lock.launchOnStartup && Config.ready && Persistent.ready && Persistent.isNewHyprlandInstance) {
GlobalStates.screenLocked = true;
}
}
}
property real screenZoom: 1
onScreenZoomChanged: {
Quickshell.execDetached(["hyprctl", "keyword", "cursor:zoom_factor", root.screenZoom.toString()]);
@@ -175,16 +175,10 @@ Variants {
anchors.fill: parent
clip: true
Image {
StyledImage {
id: wallpaper
visible: opacity > 0 && !blurLoader.active
opacity: (status === Image.Ready && !bgRoot.wallpaperIsVideo) ? 1 : 0
Behavior on opacity {
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
cache: false
asynchronous: true
retainWhileLoading: true
smooth: false
// Range = groups that workspaces span on
property int chunkSize: Config?.options.bar.workspaces.shown ?? 10
@@ -300,9 +294,7 @@ Variants {
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
}
sourceComponent: ColumnLayout {
spacing: 8
sourceComponent: Column {
Loader {
id: digitalClockLoader
visible: root.clockStyle === "digital"
@@ -340,7 +332,6 @@ Variants {
Loader {
id: cookieClockLoader
Layout.alignment: Qt.AlignHCenter
visible: root.clockStyle === "cookie"
active: visible
sourceComponent: CookieClock {}
@@ -432,7 +423,7 @@ Variants {
styleColor: Appearance.colors.colShadow
animateChange: true
}
component ClockStatusText: RowLayout {
component ClockStatusText: Row {
id: statusTextRow
property alias statusIcon: statusIconWidget.text
property alias statusText: statusTextWidget.text
@@ -443,10 +434,10 @@ Variants {
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Layout.fillWidth: false
spacing: 4
MaterialSymbol {
id: statusIconWidget
Layout.fillWidth: false
anchors.verticalCenter: statusTextRow.verticalCenter
iconSize: Appearance.font.pixelSize.huge
color: statusTextRow.textColor
style: Text.Raised
@@ -454,8 +445,8 @@ Variants {
}
ClockText {
id: statusTextWidget
Layout.fillWidth: false
color: statusTextRow.textColor
anchors.verticalCenter: statusTextRow.verticalCenter
font {
family: Appearance.font.family.main
pixelSize: Appearance.font.pixelSize.large
@@ -97,7 +97,7 @@ Item { // Bar content region
}
}
RowLayout { // Middle section
Row { // Middle section
id: middleSection
anchors {
top: parent.top
@@ -108,8 +108,8 @@ Item { // Bar content region
BarGroup {
id: leftCenterGroup
Layout.preferredWidth: root.centerSideModuleWidth
Layout.fillHeight: false
anchors.verticalCenter: parent.verticalCenter
implicitWidth: root.centerSideModuleWidth
Resources {
alwaysShowAllResources: root.useShortenedForm === 2
@@ -128,6 +128,7 @@ Item { // Bar content region
BarGroup {
id: middleCenterGroup
anchors.verticalCenter: parent.verticalCenter
padding: workspacesWidget.widgetPadding
Workspaces {
@@ -153,9 +154,9 @@ Item { // Bar content region
MouseArea {
id: rightCenterGroup
implicitWidth: rightCenterGroupContent.implicitWidth
anchors.verticalCenter: parent.verticalCenter
implicitWidth: root.centerSideModuleWidth
implicitHeight: rightCenterGroupContent.implicitHeight
Layout.preferredWidth: root.centerSideModuleWidth
onPressed: {
GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen;
@@ -14,11 +14,12 @@ StyledPopup {
spacing: 4
// Header
RowLayout {
Row {
id: header
spacing: 5
MaterialSymbol {
anchors.verticalCenter: parent.verticalCenter
fill: 0
font.weight: Font.Medium
text: "battery_android_full"
@@ -27,6 +28,7 @@ StyledPopup {
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: "Battery"
font {
weight: Font.Medium
@@ -39,10 +39,11 @@ StyledPopup {
spacing: 4
// Date + Time row
RowLayout {
Row {
spacing: 5
MaterialSymbol {
anchors.verticalCenter: parent.verticalCenter
fill: 0
font.weight: Font.Medium
text: "calendar_month"
@@ -50,6 +51,7 @@ StyledPopup {
color: Appearance.colors.colOnSurfaceVariant
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
color: Appearance.colors.colOnSurfaceVariant
text: `${root.formattedDate}`
@@ -79,26 +81,26 @@ StyledPopup {
}
// Tasks
ColumnLayout {
Column {
spacing: 0
Layout.fillWidth: true
RowLayout {
Row {
spacing: 4
Layout.fillWidth: true
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 {
Layout.fillWidth: true
horizontalAlignment: Text.AlignLeft
wrapMode: Text.Wrap
color: Appearance.colors.colOnSurfaceVariant
@@ -6,10 +6,16 @@ import qs.modules.common.widgets
Loader {
id: root
property bool vertical: false
active: HyprlandXkb.layoutCodes.length > 1
visible: active
function abbreviateLayoutCode(fullCode) {
return fullCode.split(':').map(layout => {
const baseLayout = layout.split('-')[0];
return baseLayout.slice(0, 4);
}).join('\n');
}
sourceComponent: Item {
implicitWidth: root.vertical ? null : layoutCodeText.implicitWidth
implicitHeight: root.vertical ? layoutCodeText.implicitHeight : null
@@ -18,7 +24,7 @@ Loader {
id: layoutCodeText
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
text: HyprlandXkb.currentLayoutCode.split(":").join("\n")
text: abbreviateLayoutCode(HyprlandXkb.currentLayoutCode)
font.pixelSize: text.includes("\n") ? Appearance.font.pixelSize.smallie : Appearance.font.pixelSize.small
color: rightSidebarButton.colText
animateChange: true
@@ -20,7 +20,6 @@ StyledPopup {
required property string label
required property string value
spacing: 4
Layout.fillWidth: true
MaterialSymbol {
text: resourceItem.icon
@@ -40,13 +39,14 @@ StyledPopup {
}
}
component ResourceHeaderItem: RowLayout {
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
@@ -55,6 +55,7 @@ StyledPopup {
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: headerItem.label
font {
weight: Font.Medium
@@ -64,19 +65,20 @@ StyledPopup {
}
}
RowLayout {
Row {
anchors.centerIn: parent
spacing: 12
ColumnLayout {
Layout.alignment: Qt.AlignTop
Column {
anchors.top: parent.top
spacing: 8
ResourceHeaderItem {
icon: "memory"
label: "RAM"
}
ColumnLayout {
Column {
spacing: 4
ResourceItem {
icon: "clock_loader_60"
label: Translation.tr("Used:")
@@ -95,16 +97,17 @@ StyledPopup {
}
}
ColumnLayout {
Column {
visible: ResourceUsage.swapTotal > 0
Layout.alignment: Qt.AlignTop
anchors.top: parent.top
spacing: 8
ResourceHeaderItem {
icon: "swap_horiz"
label: "Swap"
}
ColumnLayout {
Column {
spacing: 4
ResourceItem {
icon: "clock_loader_60"
label: Translation.tr("Used:")
@@ -123,15 +126,16 @@ StyledPopup {
}
}
ColumnLayout {
Layout.alignment: Qt.AlignTop
Column {
anchors.top: parent.top
spacing: 8
ResourceHeaderItem {
icon: "planner_review"
label: "CPU"
}
ColumnLayout {
Column {
spacing: 4
ResourceItem {
icon: "bolt"
label: Translation.tr("Load:")
@@ -2,7 +2,6 @@ import qs
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
import QtQuick.Layouts
Revealer { // Scroll hint
id: root
@@ -11,10 +10,11 @@ Revealer { // Scroll hint
property string tooltipText: ""
MouseArea {
id: mouseArea
anchors.right: root.side === "left" ? parent.right : undefined
anchors.left: root.side === "right" ? parent.left : undefined
implicitWidth: contentColumnLayout.implicitWidth
implicitHeight: contentColumnLayout.implicitHeight
implicitWidth: contentColumn.implicitWidth
implicitHeight: contentColumn.implicitHeight
property bool hovered: false
hoverEnabled: true
@@ -22,32 +22,36 @@ Revealer { // Scroll hint
onExited: hovered = false
acceptedButtons: Qt.NoButton
// StyledToolTip {
// extraVisibleCondition: tooltipText.length > 0
// text: tooltipText
// }
property bool showHintTimedOut: false
onHoveredChanged: showHintTimedOut = false
Timer {
running: mouseArea.hovered
interval: 500
onTriggered: mouseArea.showHintTimedOut = true
}
ColumnLayout {
id: contentColumnLayout
anchors.centerIn: parent
PopupToolTip {
extraVisibleCondition: (tooltipText.length > 0 && mouseArea.showHintTimedOut)
text: tooltipText
}
Column {
id: contentColumn
anchors {
fill: parent
}
spacing: -5
MaterialSymbol {
Layout.leftMargin: 5
Layout.rightMargin: 5
text: "keyboard_arrow_up"
iconSize: 14
color: Appearance.colors.colSubtext
}
MaterialSymbol {
Layout.leftMargin: 5
Layout.rightMargin: 5
text: root.icon
iconSize: 14
color: Appearance.colors.colSubtext
}
MaterialSymbol {
Layout.leftMargin: 5
Layout.rightMargin: 5
text: "keyboard_arrow_down"
iconSize: 14
color: Appearance.colors.colSubtext
@@ -24,8 +24,8 @@ LazyLoader {
anchors.top: Config.options.bar.vertical || (!Config.options.bar.vertical && !Config.options.bar.bottom)
anchors.bottom: !Config.options.bar.vertical && Config.options.bar.bottom
implicitWidth: popupBackground.implicitWidth + Appearance.sizes.hyprlandGapsOut * 2 + root.popupBackgroundMargin
implicitHeight: popupBackground.implicitHeight + Appearance.sizes.hyprlandGapsOut * 2 + root.popupBackgroundMargin
implicitWidth: popupBackground.implicitWidth + Appearance.sizes.elevationMargin * 2 + root.popupBackgroundMargin
implicitHeight: popupBackground.implicitHeight + Appearance.sizes.elevationMargin * 2 + root.popupBackgroundMargin
mask: Region {
item: popupBackground
@@ -63,10 +63,10 @@ LazyLoader {
readonly property real margin: 10
anchors {
fill: parent
leftMargin: Appearance.sizes.hyprlandGapsOut + root.popupBackgroundMargin * (!popupWindow.anchors.left)
rightMargin: Appearance.sizes.hyprlandGapsOut + root.popupBackgroundMargin * (!popupWindow.anchors.right)
topMargin: Appearance.sizes.hyprlandGapsOut + root.popupBackgroundMargin * (!popupWindow.anchors.top)
bottomMargin: Appearance.sizes.hyprlandGapsOut + root.popupBackgroundMargin * (!popupWindow.anchors.bottom)
leftMargin: Appearance.sizes.elevationMargin + root.popupBackgroundMargin * (!popupWindow.anchors.left)
rightMargin: Appearance.sizes.elevationMargin + root.popupBackgroundMargin * (!popupWindow.anchors.right)
topMargin: Appearance.sizes.elevationMargin + root.popupBackgroundMargin * (!popupWindow.anchors.top)
bottomMargin: Appearance.sizes.elevationMargin + root.popupBackgroundMargin * (!popupWindow.anchors.bottom)
}
implicitWidth: root.contentItem.implicitWidth + margin * 2
implicitHeight: root.contentItem.implicitHeight + margin * 2
@@ -5,7 +5,6 @@ import qs.modules.common.widgets
import qs.modules.common.functions
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
@@ -19,7 +18,8 @@ Item {
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.QsWindow.window?.screen)
readonly property Toplevel activeWindow: ToplevelManager.activeToplevel
readonly property int workspaceGroup: Math.floor((monitor?.activeWorkspace?.id - 1) / Config.options.bar.workspaces.shown)
readonly property int workspacesShown: Config.options.bar.workspaces.shown
readonly property int workspaceGroup: Math.floor((monitor?.activeWorkspace?.id - 1) / root.workspacesShown)
property list<bool> workspaceOccupied: []
property int widgetPadding: 4
property int workspaceButtonWidth: 26
@@ -28,7 +28,7 @@ Item {
property real workspaceIconSizeShrinked: workspaceButtonWidth * 0.55
property real workspaceIconOpacityShrinked: 1
property real workspaceIconMarginShrinked: -4
property int workspaceIndexInGroup: (monitor?.activeWorkspace?.id - 1) % Config.options.bar.workspaces.shown
property int workspaceIndexInGroup: (monitor?.activeWorkspace?.id - 1) % root.workspacesShown
property bool showNumbers: false
Timer {
@@ -56,8 +56,8 @@ Item {
// Function to update workspaceOccupied
function updateWorkspaceOccupied() {
workspaceOccupied = Array.from({ length: Config.options.bar.workspaces.shown }, (_, i) => {
return Hyprland.workspaces.values.some(ws => ws.id === workspaceGroup * Config.options.bar.workspaces.shown + i + 1);
workspaceOccupied = Array.from({ length: root.workspacesShown }, (_, i) => {
return Hyprland.workspaces.values.some(ws => ws.id === workspaceGroup * root.workspacesShown + i + 1);
})
}
@@ -79,8 +79,8 @@ Item {
updateWorkspaceOccupied();
}
implicitWidth: root.vertical ? Appearance.sizes.verticalBarWidth : backgroundLayout.implicitWidth
implicitHeight: root.vertical ? backgroundLayout.implicitHeight : Appearance.sizes.barHeight
implicitWidth: root.vertical ? Appearance.sizes.verticalBarWidth : (root.workspaceButtonWidth * root.workspacesShown)
implicitHeight: root.vertical ? (root.workspaceButtonWidth * root.workspacesShown) : Appearance.sizes.barHeight
// Scroll to switch workspaces
WheelHandler {
@@ -104,24 +104,20 @@ Item {
}
// Workspaces - background
GridLayout {
id: backgroundLayout
Grid {
z: 1
anchors.fill: parent
implicitHeight: root.vertical ? root.workspaceButtonWidth : Appearance.sizes.barHeight
implicitWidth: root.vertical ? Appearance.sizes.verticalBarWidth : root.workspaceButtonWidth
anchors.centerIn: parent
rowSpacing: 0
columnSpacing: 0
columns: root.vertical ? 1 : -1
columns: root.vertical ? 1 : root.workspacesShown
rows: root.vertical ? root.workspacesShown : 1
Repeater {
model: Config.options.bar.workspaces.shown
model: root.workspacesShown
Rectangle {
z: 1
Layout.alignment: root.vertical ? Qt.AlignHCenter : Qt.AlignVCenter
implicitWidth: workspaceButtonWidth
implicitHeight: workspaceButtonWidth
radius: (width / 2)
@@ -195,26 +191,24 @@ Item {
}
// Workspaces - numbers
GridLayout {
id: rowLayoutNumbers
Grid {
z: 3
columns: vertical ? 1 : -1
columns: root.vertical ? 1 : root.workspacesShown
rows: root.vertical ? root.workspacesShown : 1
columnSpacing: 0
rowSpacing: 0
anchors.fill: parent
implicitHeight: vertical ? Appearance.sizes.verticalBarWidth : Appearance.sizes.barHeight
implicitWidth: vertical ? Appearance.sizes.verticalBarWidth : Appearance.sizes.verticalBarWidth
Repeater {
model: Config.options.bar.workspaces.shown
model: root.workspacesShown
Button {
id: button
property int workspaceValue: workspaceGroup * Config.options.bar.workspaces.shown + index + 1
Layout.fillHeight: !root.vertical
Layout.fillWidth: root.vertical
property int workspaceValue: workspaceGroup * root.workspacesShown + index + 1
implicitHeight: vertical ? Appearance.sizes.verticalBarWidth : Appearance.sizes.barHeight
implicitWidth: vertical ? Appearance.sizes.verticalBarWidth : Appearance.sizes.verticalBarWidth
onPressed: Hyprland.dispatch(`workspace ${workspaceValue}`)
width: vertical ? undefined : workspaceButtonWidth
height: vertical ? workspaceButtonWidth : undefined
@@ -1,3 +1,4 @@
pragma ComponentBehavior: Bound
import qs
import qs.services
import qs.modules.common
@@ -11,8 +12,9 @@ Item {
readonly property var keybinds: HyprlandKeybinds.keybinds
property real spacing: 20
property real titleSpacing: 7
implicitWidth: rowLayout.implicitWidth
implicitHeight: rowLayout.implicitHeight
property real padding: 4
implicitWidth: row.implicitWidth + padding * 2
implicitHeight: row.implicitHeight + padding * 2
property var keyBlacklist: ["Super_L"]
property var keySubstitutions: ({
@@ -28,43 +30,51 @@ Item {
// "Shift": "",
})
RowLayout { // Keybind columns
id: rowLayout
Row { // Keybind columns
id: row
spacing: root.spacing
Repeater {
model: keybinds.children
delegate: ColumnLayout { // Keybind sections
delegate: Column { // Keybind sections
spacing: root.spacing
required property var modelData
Layout.alignment: Qt.AlignTop
anchors.top: row.top
Repeater {
model: modelData.children
delegate: Item { // Section with real keybinds
id: keybindSection
required property var modelData
implicitWidth: sectionColumnLayout.implicitWidth
implicitHeight: sectionColumnLayout.implicitHeight
ColumnLayout {
id: sectionColumnLayout
implicitWidth: sectionColumn.implicitWidth
implicitHeight: sectionColumn.implicitHeight
Column {
id: sectionColumn
anchors.centerIn: parent
spacing: root.titleSpacing
StyledText {
id: sectionTitle
font.family: Appearance.font.family.title
font.pixelSize: Appearance.font.pixelSize.huge
color: Appearance.colors.colOnLayer0
text: modelData.name
text: keybindSection.modelData.name
}
GridLayout {
Grid {
id: keybindGrid
columns: 2
columnSpacing: 4
rowSpacing: 4
Repeater {
model: {
var result = [];
for (var i = 0; i < modelData.keybinds.length; i++) {
const keybind = modelData.keybinds[i];
for (var i = 0; i < keybindSection.modelData.keybinds.length; i++) {
const keybind = keybindSection.modelData.keybinds[i];
result.push({
"type": "keys",
"mods": keybind.mods,
@@ -89,7 +99,7 @@ Item {
Component {
id: keysComponent
RowLayout {
Row {
spacing: 4
Repeater {
model: modelData.mods
@@ -101,7 +111,6 @@ Item {
StyledText {
id: keybindPlus
visible: !keyBlacklist.includes(modelData.key) && modelData.mods.length > 0
Layout.alignment: Qt.AlignVCenter
text: "+"
}
KeyboardKey {
@@ -15,14 +15,14 @@ Item {
implicitWidth: mainLayout.implicitWidth
implicitHeight: mainLayout.implicitHeight
ColumnLayout {
Column {
id: mainLayout
spacing: root.spacing
Repeater { // Main table rows
model: root.elements
delegate: RowLayout { // Table cells
delegate: Row { // Table cells
id: tableRow
spacing: root.spacing
required property var modelData
@@ -47,7 +47,7 @@ Item {
Repeater { // Main table rows
model: root.series
delegate: RowLayout { // Table cells
delegate: Row { // Table cells
id: seriesTableRow
spacing: root.spacing
required property var modelData
@@ -1,4 +1,5 @@
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
import QtQuick
@@ -18,7 +19,7 @@ RippleButton {
topMargin: 4
leftMargin: 4
}
color: Appearance.colors.colLayer2
color: ColorUtils.transparentize(Appearance.colors.colLayer2)
radius: Appearance.rounding.full
implicitWidth: Math.max(20, elementNumber.implicitWidth)
implicitHeight: Math.max(20, elementNumber.implicitHeight)
@@ -26,13 +27,35 @@ RippleButton {
StyledText {
id: elementNumber
anchors.centerIn: parent
anchors.left: parent.left
color: Appearance.colors.colOnLayer2
text: root.element.number
font.pixelSize: Appearance.font.pixelSize.smallest
}
}
Rectangle {
anchors {
top: parent.top
right: parent.right
topMargin: 4
rightMargin: 4
}
color: ColorUtils.transparentize(Appearance.colors.colLayer2)
radius: Appearance.rounding.full
implicitWidth: Math.max(20, elementWeight.implicitWidth)
implicitHeight: Math.max(20, elementWeight.implicitHeight)
width: height
StyledText {
id: elementWeight
anchors.right: parent.right
color: Appearance.colors.colOnLayer2
text: root.element.weight
font.pixelSize: Appearance.font.pixelSize.smallest
}
}
StyledText {
id: elementSymbol
anchors.centerIn: parent
@@ -356,7 +356,7 @@ Singleton {
property real mediaControlsWidth: 440
property real mediaControlsHeight: 160
property real notificationPopupWidth: 410
property real osdWidth: 200
property real osdWidth: 180
property real searchWidthCollapsed: 260
property real searchWidth: 450
property real sidebarWidth: 460
@@ -260,6 +260,7 @@ Singleton {
}
property JsonObject lock: JsonObject {
property bool launchOnStartup: false
property JsonObject blur: JsonObject {
property bool enable: false
property real radius: 100
@@ -267,6 +268,7 @@ Singleton {
}
property bool centerClock: true
property bool showLockedText: true
property bool unlockKeyring: true
}
property JsonObject media: JsonObject {
@@ -11,6 +11,15 @@ Singleton {
property string fileName: "states.json"
property string filePath: `${root.fileDir}/${root.fileName}`
property bool ready: false
property string previousHyprlandInstanceSignature: ""
property bool isNewHyprlandInstance: previousHyprlandInstanceSignature !== states.hyprlandInstanceSignature
onReadyChanged: {
root.previousHyprlandInstanceSignature = root.states.hyprlandInstanceSignature
root.states.hyprlandInstanceSignature = Quickshell.env("HYPRLAND_INSTANCE_SIGNATURE") || ""
}
Timer {
id: fileReloadTimer
interval: 100
@@ -36,6 +45,7 @@ Singleton {
watchChanges: true
onFileChanged: fileReloadTimer.restart()
onAdapterUpdated: fileWriteTimer.restart()
onLoaded: root.ready = true
onLoadFailed: error => {
console.log("Failed to load persistent states file:", error);
if (error == FileViewError.FileNotFound) {
@@ -45,6 +55,9 @@ Singleton {
adapter: JsonAdapter {
id: persistentStatesJsonAdapter
property string hyprlandInstanceSignature: ""
property JsonObject ai: JsonObject {
property string model
property real temperature: 0.5
@@ -77,7 +77,7 @@ Rectangle {
}
}
Image {
StyledImage {
id: image
anchors.fill: parent
@@ -72,8 +72,8 @@ RippleButton {
Layout.bottomMargin: 5
Layout.fillWidth: true
value: 0.7
sperm: true
animateSperm: lightDarkButtonRoot.toggled
wavy: true
animateWave: lightDarkButtonRoot.toggled
highlightColor: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3primary : lightDarkButtonRoot.previewFg
trackColor: ColorUtils.mix(lightDarkButtonRoot.previewBg, lightDarkButtonRoot.previewFg, 0.5)
}
@@ -173,7 +173,7 @@ Item { // Notification item area
implicitHeight: summaryText.implicitHeight
StyledText {
id: summaryText
Layout.fillWidth: summaryTextMetrics.width >= summaryRow.width * root.summaryElideRatio
Layout.fillWidth: summaryTextMetrics.width >= summaryRow.implicitWidth * root.summaryElideRatio
visible: !root.onlyNotification
font.pixelSize: root.fontSize
color: Appearance.colors.colOnLayer3
@@ -1,12 +1,9 @@
import qs.services
pragma ComponentBehavior: Bound
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import Qt5Compat.GraphicalEffects
/**
* Material 3 progress bar. See https://m3.material.io/components/progress-indicators/overview
@@ -18,13 +15,13 @@ ProgressBar {
property real valueBarGap: 4
property color highlightColor: Appearance?.colors.colPrimary ?? "#685496"
property color trackColor: Appearance?.m3colors.m3secondaryContainer ?? "#F1D3F9"
property bool sperm: false // If true, the progress bar will have a wavy fill effect
property bool animateSperm: true
property real spermAmplitudeMultiplier: sperm ? 0.5 : 0
property real spermFrequency: 6
property real spermFps: 60
property bool wavy: false // If true, the progress bar will have a wavy fill effect
property bool animateWave: true
property real waveAmplitudeMultiplier: wavy ? 0.5 : 0
property real waveFrequency: 6
property real waveFps: 60
Behavior on spermAmplitudeMultiplier {
Behavior on waveAmplitudeMultiplier {
animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
}
@@ -38,64 +35,62 @@ ProgressBar {
}
contentItem: Item {
id: contentItem
anchors.fill: parent
Canvas {
id: wavyFill
Loader {
anchors {
left: parent.left
right: parent.right
verticalCenter: parent.verticalCenter
}
height: parent.height * 6
onPaint: {
var ctx = getContext("2d");
ctx.clearRect(0, 0, width, height);
var progress = root.visualPosition;
var fillWidth = progress * width;
var amplitude = parent.height * root.spermAmplitudeMultiplier;
var frequency = root.spermFrequency;
var phase = Date.now() / 400.0;
var centerY = height / 2;
ctx.strokeStyle = root.highlightColor;
ctx.lineWidth = parent.height;
ctx.lineCap = "round";
ctx.beginPath();
for (var x = ctx.lineWidth / 2; x <= fillWidth; x += 1) {
var waveY = centerY + amplitude * Math.sin(frequency * 2 * Math.PI * x / width + phase);
if (x === 0)
ctx.moveTo(x, waveY);
else
ctx.lineTo(x, waveY);
active: root.wavy
sourceComponent: WavyLine {
id: wavyFill
frequency: root.waveFrequency
color: root.highlightColor
amplitudeMultiplier: root.wavy ? 0.5 : 0
height: contentItem.height * 6
width: contentItem.width * root.visualPosition
lineWidth: contentItem.height
fullLength: root.width
Connections {
target: root
function onValueChanged() { wavyFill.requestPaint(); }
function onHighlightColorChanged() { wavyFill.requestPaint(); }
}
FrameAnimation {
running: root.animateWave
onTriggered: {
wavyFill.requestPaint()
}
}
ctx.stroke();
}
Connections {
target: root
function onValueChanged() { wavyFill.requestPaint(); }
function onHighlightColorChanged() { wavyFill.requestPaint(); }
}
Timer {
interval: 1000 / root.spermFps
running: root.animateSperm
repeat: root.sperm
onTriggered: wavyFill.requestPaint()
}
}
Loader {
active: !root.wavy
sourceComponent: Rectangle {
anchors.left: parent.left
width: contentItem.width * root.visualPosition
height: contentItem.height
radius: height / 2
color: root.highlightColor
}
}
Rectangle { // Right remaining part fill
anchors.right: parent.right
width: (1 - root.visualPosition) * parent.width - valueBarGap
height: parent.height
radius: Appearance?.rounding.full ?? 9999
radius: height / 2
color: root.trackColor
}
Rectangle { // Stop point
anchors.right: parent.right
width: valueBarGap
height: valueBarGap
radius: Appearance?.rounding.full ?? 9999
radius: height / 2
color: root.highlightColor
}
}
@@ -5,7 +5,7 @@ import qs.modules.common
RectangularShadow {
required property var target
anchors.fill: target
radius: 20
radius: target.radius
blur: 0.9 * Appearance.sizes.elevationMargin
offset: Qt.vector2d(0.0, 1.0)
spread: 1
@@ -1,3 +1,4 @@
pragma ComponentBehavior: Bound
import qs.modules.common
import qs.modules.common.widgets
import qs.services
@@ -17,6 +18,7 @@ Slider {
property list<real> stopIndicatorValues: [1]
enum Configuration {
Wavy = 4,
XS = 12,
S = 18,
M = 30,
@@ -28,10 +30,9 @@ Slider {
property real handleDefaultWidth: 3
property real handlePressedWidth: 1.5
property color highlightColor: Appearance.colors.colPrimary
property color trackColor: Appearance.colors.colSecondaryContainer
property color handleColor: Appearance.m3colors.m3onSecondaryContainer
property color handleColor: Appearance.colors.colPrimary
property color dotColor: Appearance.m3colors.m3onSecondaryContainer
property color dotColorHighlighted: Appearance.m3colors.m3onPrimary
property real unsharpenRadius: Appearance.rounding.unsharpen
@@ -39,15 +40,18 @@ Slider {
property real trackRadius: trackWidth >= StyledSlider.Configuration.XL ? 21
: trackWidth >= StyledSlider.Configuration.L ? 12
: trackWidth >= StyledSlider.Configuration.M ? 9
: 6
property real handleHeight: Math.max(33, trackWidth + 9)
: trackWidth >= StyledSlider.Configuration.S ? 6
: height / 2
property real handleHeight: (configuration === StyledSlider.Configuration.Wavy) ? 24 : Math.max(33, trackWidth + 9)
property real handleWidth: root.pressed ? handlePressedWidth : handleDefaultWidth
property real handleMargins: 4
onHandleMarginsChanged: {
console.log("Handle margins changed to", handleMargins);
}
property real trackDotSize: 3
property string tooltipContent: `${Math.round(value * 100)}%`
property bool wavy: configuration === StyledSlider.Configuration.Wavy // If true, the progress bar will have a wavy fill effect
property bool animateWave: true
property real waveAmplitudeMultiplier: wavy ? 0.5 : 0
property real waveFrequency: 6
property real waveFps: 60
leftPadding: handleMargins
rightPadding: handleMargins
@@ -93,18 +97,51 @@ Slider {
implicitHeight: trackWidth
// Fill left
Rectangle {
Loader {
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
}
width: root.handleMargins + (root.visualPosition * root.effectiveDraggingWidth) - (root.handleWidth / 2 + root.handleMargins)
height: trackWidth
color: root.highlightColor
topLeftRadius: root.trackRadius
bottomLeftRadius: root.trackRadius
topRightRadius: root.unsharpenRadius
bottomRightRadius: root.unsharpenRadius
height: root.trackWidth
active: !root.wavy
sourceComponent: Rectangle {
color: root.highlightColor
topLeftRadius: root.trackRadius
bottomLeftRadius: root.trackRadius
topRightRadius: root.unsharpenRadius
bottomRightRadius: root.unsharpenRadius
}
}
Loader {
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
}
width: root.handleMargins + (root.visualPosition * root.effectiveDraggingWidth) - (root.handleWidth / 2 + root.handleMargins)
height: root.height
active: root.wavy
sourceComponent: WavyLine {
id: wavyFill
frequency: root.waveFrequency
fullLength: root.width
color: root.highlightColor
amplitudeMultiplier: root.wavy ? 0.5 : 0
width: root.handleMargins + (root.visualPosition * root.effectiveDraggingWidth) - (root.handleWidth / 2 + root.handleMargins)
height: root.trackWidth
Connections {
target: root
function onValueChanged() { wavyFill.requestPaint(); }
function onHighlightColorChanged() { wavyFill.requestPaint(); }
}
FrameAnimation {
running: root.animateWave
onTriggered: {
wavyFill.requestPaint()
}
}
}
}
// Fill right
@@ -16,6 +16,7 @@ Item {
default property alias data: toolbarLayout.data
implicitWidth: background.implicitWidth
implicitHeight: background.implicitHeight
property alias radius: background.radius
StyledRectangularShadow {
target: background
@@ -23,11 +24,11 @@ Item {
Rectangle {
id: background
anchors.centerIn: parent
anchors.fill: parent
color: Appearance.m3colors.m3surfaceContainer // Needs to be opaque
implicitHeight: Math.max(toolbarLayout.implicitHeight + root.padding * 2, 56)
implicitWidth: toolbarLayout.implicitWidth + root.padding * 2
radius: Appearance.rounding.full
radius: height / 2
RowLayout {
id: toolbarLayout
@@ -0,0 +1,34 @@
import qs.modules.common
import QtQuick
Canvas {
id: root
property real amplitudeMultiplier: 0.5
property real frequency: 6
property color color: Appearance?.colors.colPrimary ?? "#685496"
property real lineWidth: 4
property real fullLength: width
onPaint: {
var ctx = getContext("2d");
ctx.clearRect(0, 0, width, height);
var amplitude = root.lineWidth * root.amplitudeMultiplier;
var frequency = root.frequency;
var phase = Date.now() / 400.0;
var centerY = height / 2;
ctx.strokeStyle = root.color;
ctx.lineWidth = root.lineWidth;
ctx.lineCap = "round";
ctx.beginPath();
for (var x = ctx.lineWidth / 2; x <= root.width - ctx.lineWidth / 2; x += 1) {
var waveY = centerY + amplitude * Math.sin(frequency * 2 * Math.PI * x / root.fullLength + phase);
if (x === 0)
ctx.moveTo(x, waveY);
else
ctx.lineTo(x, waveY);
}
ctx.stroke();
}
}
@@ -1,4 +1,5 @@
import qs
import qs.modules.common
import QtQuick
import Quickshell
import Quickshell.Services.Pam
@@ -55,6 +56,7 @@ Scope {
onCompleted: result => {
if (result == PamResult.Success) {
root.unlocked();
if (Config.options.lock.unlockKeyring) root.unlockKeyring();
} else {
root.showFailure = true;
GlobalStates.screenUnlockFailed = true;
@@ -64,4 +66,13 @@ Scope {
root.unlockInProgress = false;
}
}
function unlockKeyring() {
Quickshell.execDetached({
environment: ({
UNLOCK_PASSWORD: root.currentText
}),
command: ["bash", "-c", Quickshell.shellPath("scripts/keyring/unlock.sh")]
})
}
}
@@ -155,21 +155,21 @@ MouseArea {
opacity: root.toolbarOpacity
// Username
RowLayout {
Row {
spacing: 6
Layout.leftMargin: 8
Layout.fillHeight: true
MaterialSymbol {
id: userIcon
Layout.alignment: Qt.AlignVCenter
anchors.verticalCenter: parent.verticalCenter
fill: 1
text: "account_circle"
iconSize: Appearance.font.pixelSize.huge
color: Appearance.colors.colOnSurfaceVariant
}
StyledText {
Layout.alignment: Qt.AlignVCenter
anchors.verticalCenter: parent.verticalCenter
text: SystemInfo.username
color: Appearance.colors.colOnSurfaceVariant
}
@@ -184,18 +184,19 @@ MouseArea {
active: true
visible: active
sourceComponent: RowLayout {
sourceComponent: Row {
spacing: 8
MaterialSymbol {
id: keyboardIcon
Layout.alignment: Qt.AlignVCenter
anchors.verticalCenter: parent.verticalCenter
fill: 1
text: "keyboard_alt"
iconSize: Appearance.font.pixelSize.huge
color: Appearance.colors.colOnSurfaceVariant
}
Loader {
anchors.verticalCenter: parent.verticalCenter
sourceComponent: StyledText {
text: HyprlandXkb.currentLayoutCode
color: Appearance.colors.colOnSurfaceVariant
@@ -229,18 +230,18 @@ MouseArea {
scale: root.toolbarScale
opacity: root.toolbarOpacity
RowLayout {
Row {
visible: UPower.displayDevice.isLaptopBattery
spacing: 6
spacing: 4
Layout.fillHeight: true
Layout.leftMargin: 10
Layout.rightMargin: 10
MaterialSymbol {
id: boltIcon
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: -2
Layout.rightMargin: -2
anchors {
verticalCenter: parent.verticalCenter
}
fill: 1
text: Battery.isCharging ? "bolt" : "battery_android_full"
iconSize: Appearance.font.pixelSize.huge
@@ -248,7 +249,7 @@ MouseArea {
color: (Battery.isLow && !Battery.isCharging) ? Appearance.colors.colError : Appearance.colors.colOnSurfaceVariant
}
StyledText {
Layout.alignment: Qt.AlignVCenter
anchors.verticalCenter: parent.verticalCenter
text: Math.round(Battery.percentage * 100)
color: (Battery.isLow && !Battery.isCharging) ? Appearance.colors.colError : Appearance.colors.colOnSurfaceVariant
}
@@ -1,3 +1,4 @@
pragma ComponentBehavior: Bound
import qs.modules.common
import qs.modules.common.widgets
import qs.services
@@ -153,8 +154,8 @@ Scope {
required property MprisPlayer modelData
player: modelData
visualizerPoints: root.visualizerPoints
implicitWidth: widgetWidth
implicitHeight: widgetHeight
implicitWidth: root.widgetWidth
implicitHeight: root.widgetHeight
radius: root.popupRounding
}
}
@@ -1,3 +1,4 @@
pragma ComponentBehavior: Bound
import qs.modules.common
import qs.modules.common.models
import qs.modules.common.widgets
@@ -160,7 +161,7 @@ Item { // Player instance
}
}
Image { // Art image
StyledImage { // Art image
id: mediaArt
property int size: parent.height
anchors.fill: parent
@@ -169,7 +170,6 @@ Item { // Player instance
fillMode: Image.PreserveAspectCrop
cache: false
antialiasing: true
asynchronous: true
width: size
height: size
@@ -233,16 +233,41 @@ Item { // Player instance
Item {
id: progressBarContainer
Layout.fillWidth: true
implicitHeight: progressBar.implicitHeight
implicitHeight: Math.max(sliderLoader.implicitHeight, progressBarLoader.implicitHeight)
StyledProgressBar {
id: progressBar
Loader {
id: sliderLoader
anchors.fill: parent
highlightColor: blendedColors.colPrimary
trackColor: blendedColors.colSecondaryContainer
value: playerController.player?.position / playerController.player?.length
sperm: playerController.player?.isPlaying
active: playerController.player?.canSeek ?? false
sourceComponent: StyledSlider {
configuration: StyledSlider.Configuration.Wavy
highlightColor: blendedColors.colPrimary
trackColor: blendedColors.colSecondaryContainer
handleColor: blendedColors.colPrimary
value: playerController.player?.position / playerController.player?.length
onMoved: {
playerController.player.position = value * playerController.player.length;
}
}
}
Loader {
id: progressBarLoader
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
right: parent.right
}
active: !(playerController.player?.canSeek ?? false)
sourceComponent: StyledProgressBar {
wavy: playerController.player?.isPlaying
highlightColor: blendedColors.colPrimary
trackColor: blendedColors.colSecondaryContainer
value: playerController.player?.position / playerController.player?.length
}
}
}
TrackChangeButton {
iconName: "skip_next"
@@ -15,9 +15,21 @@ Scope {
property string protectionMessage: ""
property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name)
property string currentIndicator: "volume"
property var indicators: [
{
id: "volume",
sourceUrl: "indicators/VolumeIndicator.qml"
},
{
id: "brightness",
sourceUrl: "indicators/BrightnessIndicator.qml"
},
]
function triggerOsd() {
GlobalStates.osdVolumeOpen = true
osdTimeout.restart()
GlobalStates.osdVolumeOpen = true;
osdTimeout.restart();
}
Timer {
@@ -26,35 +38,44 @@ Scope {
repeat: false
running: false
onTriggered: {
GlobalStates.osdVolumeOpen = false
root.protectionMessage = ""
GlobalStates.osdVolumeOpen = false;
root.protectionMessage = "";
}
}
Connections {
target: Brightness
function onBrightnessChanged() {
GlobalStates.osdVolumeOpen = false
root.protectionMessage = "";
root.currentIndicator = "brightness";
root.triggerOsd();
}
}
Connections { // Listen to volume changes
Connections {
// Listen to volume changes
target: Audio.sink?.audio ?? null
function onVolumeChanged() {
if (!Audio.ready) return
root.triggerOsd()
if (!Audio.ready)
return;
root.currentIndicator = "volume";
root.triggerOsd();
}
function onMutedChanged() {
if (!Audio.ready) return
root.triggerOsd()
if (!Audio.ready)
return;
root.currentIndicator = "volume";
root.triggerOsd();
}
}
Connections { // Listen to protection triggers
Connections {
// Listen to protection triggers
target: Audio
function onSinkProtectionTriggered(reason) {
root.protectionMessage = reason;
root.triggerOsd()
root.currentIndicator = "volume";
root.triggerOsd();
}
}
@@ -69,7 +90,7 @@ Scope {
Connections {
target: root
function onFocusedScreenChanged() {
osdRoot.screen = root.focusedScreen
osdRoot.screen = root.focusedScreen;
}
}
@@ -97,10 +118,11 @@ Scope {
ColumnLayout {
id: columnLayout
anchors.horizontalCenter: parent.horizontalCenter
Item {
id: osdValuesWrapper
// Extra space for shadow
implicitHeight: contentColumnLayout.implicitHeight + Appearance.sizes.elevationMargin * 2
implicitHeight: contentColumnLayout.implicitHeight
implicitWidth: contentColumnLayout.implicitWidth
clip: true
@@ -110,30 +132,25 @@ Scope {
onEntered: GlobalStates.osdVolumeOpen = false
}
ColumnLayout {
Column {
id: contentColumnLayout
anchors {
top: parent.top
left: parent.left
right: parent.right
leftMargin: Appearance.sizes.elevationMargin
rightMargin: Appearance.sizes.elevationMargin
}
spacing: 0
OsdValueIndicator {
id: osdValues
Layout.fillWidth: true
value: Audio.sink?.audio.volume ?? 0
icon: Audio.sink?.audio.muted ? "volume_off" : "volume_up"
name: Translation.tr("Volume")
Loader {
id: osdIndicatorLoader
source: root.indicators.find(i => i.id === root.currentIndicator)?.sourceUrl
}
Item {
id: protectionMessageWrapper
anchors.horizontalCenter: parent.horizontalCenter
implicitHeight: protectionMessageBackground.implicitHeight
implicitWidth: protectionMessageBackground.implicitWidth
Layout.alignment: Qt.AlignHCenter
opacity: root.protectionMessage !== "" ? 1 : 0
StyledRectangularShadow {
@@ -174,26 +191,26 @@ Scope {
}
IpcHandler {
target: "osdVolume"
target: "osdVolume"
function trigger() {
root.triggerOsd()
function trigger() {
root.triggerOsd();
}
function hide() {
GlobalStates.osdVolumeOpen = false
GlobalStates.osdVolumeOpen = false;
}
function toggle() {
GlobalStates.osdVolumeOpen = !GlobalStates.osdVolumeOpen
GlobalStates.osdVolumeOpen = !GlobalStates.osdVolumeOpen;
}
}
}
GlobalShortcut {
name: "osdVolumeTrigger"
description: "Triggers volume OSD on press"
onPressed: {
root.triggerOsd()
root.triggerOsd();
}
}
GlobalShortcut {
@@ -201,8 +218,7 @@ Scope {
description: "Hides volume OSD on press"
onPressed: {
GlobalStates.osdVolumeOpen = false
GlobalStates.osdVolumeOpen = false;
}
}
}
@@ -1,157 +0,0 @@
import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
import Quickshell.Wayland
Scope {
id: root
property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name)
property var brightnessMonitor: Brightness.getMonitorForScreen(focusedScreen)
function triggerOsd() {
GlobalStates.osdBrightnessOpen = true
osdTimeout.restart()
}
Timer {
id: osdTimeout
interval: Config.options.osd.timeout
repeat: false
running: false
onTriggered: {
GlobalStates.osdBrightnessOpen = false
}
}
Connections {
target: Audio.sink?.audio ?? null
function onVolumeChanged() {
if (!Audio.ready) return
GlobalStates.osdBrightnessOpen = false
}
}
Connections {
target: Brightness
function onBrightnessChanged() {
if (!root.brightnessMonitor.ready) return
root.triggerOsd()
}
}
Loader {
id: osdLoader
active: GlobalStates.osdBrightnessOpen
sourceComponent: PanelWindow {
id: osdRoot
color: "transparent"
Connections {
target: root
function onFocusedScreenChanged() {
osdRoot.screen = root.focusedScreen
}
}
WlrLayershell.namespace: "quickshell:onScreenDisplay"
WlrLayershell.layer: WlrLayer.Overlay
anchors {
top: !Config.options.bar.bottom
bottom: Config.options.bar.bottom
}
mask: Region {
item: osdValuesWrapper
}
exclusionMode: ExclusionMode.Ignore
exclusiveZone: 0
margins {
top: Appearance.sizes.barHeight
bottom: Appearance.sizes.barHeight
}
implicitWidth: columnLayout.implicitWidth
implicitHeight: columnLayout.implicitHeight
visible: osdLoader.active
ColumnLayout {
id: columnLayout
anchors.horizontalCenter: parent.horizontalCenter
Item {
id: osdValuesWrapper
// Extra space for shadow
implicitHeight: osdValues.implicitHeight + Appearance.sizes.elevationMargin * 2
implicitWidth: osdValues.implicitWidth
clip: true
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: GlobalStates.osdBrightnessOpen = false
}
Behavior on implicitHeight {
NumberAnimation {
duration: Appearance.animation.menuDecel.duration
easing.type: Appearance.animation.menuDecel.type
}
}
OsdValueIndicator {
id: osdValues
anchors.fill: parent
anchors.margins: Appearance.sizes.elevationMargin
value: root.brightnessMonitor?.brightness ?? 50
icon: "light_mode"
rotateIcon: true
scaleIcon: true
name: Translation.tr("Brightness")
}
}
}
}
}
IpcHandler {
target: "osdBrightness"
function trigger() {
root.triggerOsd()
}
function hide() {
GlobalStates.osdBrightnessOpen = false
}
function toggle() {
GlobalStates.osdBrightnessOpen = !GlobalStates.osdBrightnessOpen
}
}
GlobalShortcut {
name: "osdBrightnessTrigger"
description: "Triggers brightness OSD on press"
onPressed: {
root.triggerOsd()
}
}
GlobalShortcut {
name: "osdBrightnessHide"
description: "Hides brightness OSD on press"
onPressed: {
GlobalStates.osdBrightnessOpen = false
}
}
}
@@ -1,13 +1,8 @@
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
// import Qt5Compat.GraphicalEffects
Item {
id: root
@@ -21,19 +16,23 @@ Item {
property real valueIndicatorLeftPadding: 10
property real valueIndicatorRightPadding: 20 // An icon is circle ish, a column isn't, hence the extra padding
Layout.margins: Appearance.sizes.elevationMargin
implicitWidth: Appearance.sizes.osdWidth
implicitHeight: valueIndicator.implicitHeight
implicitWidth: Appearance.sizes.osdWidth + 2 * Appearance.sizes.elevationMargin
implicitHeight: valueIndicator.implicitHeight + 2 * Appearance.sizes.elevationMargin
StyledRectangularShadow {
target: valueIndicator
}
WrapperRectangle {
Rectangle {
id: valueIndicator
anchors.fill: parent
anchors {
fill: parent
margins: Appearance.sizes.elevationMargin
}
radius: Appearance.rounding.full
color: Appearance.colors.colLayer0
implicitWidth: valueRow.implicitWidth
implicitHeight: valueRow.implicitHeight
RowLayout { // Icon on the left, stuff on the right
id: valueRow
@@ -48,6 +47,7 @@ Item {
Layout.leftMargin: valueIndicatorLeftPadding
Layout.topMargin: valueIndicatorVerticalPadding
Layout.bottomMargin: valueIndicatorVerticalPadding
MaterialSymbol { // Icon
anchors {
centerIn: parent
@@ -101,4 +101,4 @@ Item {
}
}
}
}
}
@@ -0,0 +1,18 @@
import qs
import qs.services
import QtQuick
import Quickshell
import Quickshell.Hyprland
import "../"
OsdValueIndicator {
id: root
property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name)
property var brightnessMonitor: Brightness.getMonitorForScreen(focusedScreen)
value: root.brightnessMonitor?.brightness ?? 50
icon: "light_mode"
rotateIcon: true
scaleIcon: true
name: Translation.tr("Brightness")
}
@@ -0,0 +1,11 @@
import qs
import qs.services
import QtQuick
import "../"
OsdValueIndicator {
id: osdValues
value: Audio.sink?.audio.volume ?? 0
icon: Audio.sink?.audio.muted ? "volume_off" : "volume_up"
name: Translation.tr("Volume")
}
@@ -63,7 +63,7 @@ Item {
border.width: 1
border.color: Appearance.colors.colLayer0Border
ColumnLayout { // Workspaces
Column { // Workspaces
id: workspaceColumnLayout
z: root.workspaceZ
@@ -71,7 +71,7 @@ Item {
spacing: workspaceSpacing
Repeater {
model: Config.options.overview.rows
delegate: RowLayout {
delegate: Row {
id: row
property int rowIndex: index
spacing: workspaceSpacing
@@ -148,27 +148,27 @@ Item {
model: ScriptModel {
values: {
// console.log(JSON.stringify(ToplevelManager.toplevels.values.map(t => t), null, 2))
return ToplevelManager.toplevels.values.filter((toplevel) => {
return [...ToplevelManager.toplevels.values.filter((toplevel) => {
const address = `0x${toplevel.HyprlandToplevel?.address}`
var win = windowByAddress[address]
const inWorkspaceGroup = (root.workspaceGroup * root.workspacesShown < win?.workspace?.id && win?.workspace?.id <= (root.workspaceGroup + 1) * root.workspacesShown)
return inWorkspaceGroup;
})
})].reverse()
}
}
delegate: OverviewWindow {
id: window
required property var modelData
property int monitorId: windowData?.monitor
property var monitor: HyprlandData.monitors[monitorId]
property var monitor: HyprlandData.monitors.find(m => m.id == monitorId)
property var address: `0x${modelData.HyprlandToplevel.address}`
windowData: windowByAddress[address]
toplevel: modelData
monitorData: HyprlandData.monitors[monitorId]
monitorData: this.monitor
scale: root.scale
availableWorkspaceWidth: root.workspaceImplicitWidth
availableWorkspaceHeight: root.workspaceImplicitHeight
widgetMonitorId: root.monitor.id
windowData: windowByAddress[address]
property bool atInitPosition: (initX == x && initY == y)
@@ -188,7 +188,7 @@ Item {
}
}
z: atInitPosition ? root.windowZ : root.windowDraggingZ
z: atInitPosition ? (root.windowZ + windowData?.floating) : root.windowDraggingZ
Drag.hotSpot.x: targetWindowWidth / 2
Drag.hotSpot.y: targetWindowHeight / 2
MouseArea {
@@ -218,8 +218,13 @@ Item {
updateWindowPosition.restart()
}
else {
window.x = window.initX
window.y = window.initY
if (!window.windowData.floating) {
updateWindowPosition.restart()
return
}
const percentageX = Math.round((window.x - xOffset) / root.workspaceImplicitWidth * 100)
const percentageY = Math.round((window.y - yOffset) / root.workspaceImplicitHeight * 100)
Hyprland.dispatch(`movewindowpixel exact ${percentageX}% ${percentageY}%, address:${window.windowData?.address}`)
}
}
onClicked: (event) => {
@@ -81,35 +81,29 @@ Item { // Window
border.width : 1
}
ColumnLayout {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.right: parent.right
spacing: Appearance.font.pixelSize.smaller * 0.5
Image {
id: windowIcon
anchors.centerIn: parent
property var iconSize: {
// console.log("-=-=-", root.toplevel.title, "-=-=-")
// console.log("Target window size:", targetWindowWidth, targetWindowHeight)
// console.log("Icon ratio:", root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio)
// console.log("Scale:", root.monitorData.scale)
// console.log("Final:", Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) / root.monitorData.scale)
return Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) / root.monitorData.scale;
}
// mipmap: true
Layout.alignment: Qt.AlignHCenter
source: root.iconPath
width: iconSize
height: iconSize
sourceSize: Qt.size(iconSize, iconSize)
Image {
id: windowIcon
property var iconSize: {
// console.log("-=-=-", root.toplevel.title, "-=-=-")
// console.log("Target window size:", targetWindowWidth, targetWindowHeight)
// console.log("Icon ratio:", root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio)
// console.log("Scale:", root.monitorData.scale)
// console.log("Final:", Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) / root.monitorData.scale)
return Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) / root.monitorData.scale;
}
// mipmap: true
Layout.alignment: Qt.AlignHCenter
source: root.iconPath
width: iconSize
height: iconSize
sourceSize: Qt.size(iconSize, iconSize)
Behavior on width {
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
Behavior on height {
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
Behavior on width {
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
Behavior on height {
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
}
}
@@ -42,7 +42,7 @@ Item { // Wrapper
{
action: "konachanwallpaper",
execute: () => {
Quickshell.execDetached([Quickshell.shellPath("scripts/colors/random_konachan_wall.sh")]);
Quickshell.execDetached([Quickshell.shellPath("scripts/colors/random/random_konachan_wall.sh")]);
}
},
{
@@ -81,6 +81,12 @@ Item { // Wrapper
GlobalStates.wallpaperSelectorOpen = true;
}
},
{
action: "wipeclipboard",
execute: () => {
Cliphist.wipe();
}
},
]
function focusFirstItem() {
@@ -167,6 +167,14 @@ ContentPage {
icon: "lock"
title: Translation.tr("Lock screen")
ConfigSwitch {
text: Translation.tr('Launch on startup')
checked: Config.options.lock.launchOnStartup
onCheckedChanged: {
Config.options.lock.launchOnStartup = checked;
}
}
ContentSubsection {
title: Translation.tr("Blurred style")
@@ -14,12 +14,13 @@ ContentPage {
forceWidth: true
Process {
id: konachanWallProc
id: randomWallProc
property string status: ""
command: ["bash", "-c", FileUtils.trimFileProtocol(`${Directories.scriptPath}/colors/random_konachan_wall.sh`)]
property string scriptPath: `${Directories.scriptPath}/colors/random/random_konachan_wall.sh`
command: ["bash", "-c", FileUtils.trimFileProtocol(randomWallProc.scriptPath)]
stdout: SplitParser {
onRead: data => {
konachanWallProc.status = data.trim();
randomWallProc.status = data.trim();
}
}
}
@@ -90,19 +91,35 @@ ContentPage {
ColumnLayout {
RippleButtonWithIcon {
id: rndWallBtn
enabled: !randomWallProc.running
visible: Config.options.policies.weeb === 1
Layout.fillWidth: true
buttonRadius: Appearance.rounding.small
materialIcon: "ifl"
mainText: konachanWallProc.running ? Translation.tr("Be patient...") : Translation.tr("Random: Konachan")
mainText: randomWallProc.running ? Translation.tr("Be patient...") : Translation.tr("Random: Konachan")
onClicked: {
konachanWallProc.running = true;
randomWallProc.scriptPath = `${Directories.scriptPath}/colors/random/random_konachan_wall.sh`;
randomWallProc.running = true;
}
StyledToolTip {
text: Translation.tr("Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers")
}
}
RippleButtonWithIcon {
enabled: !randomWallProc.running
visible: Config.options.policies.weeb === 1
Layout.fillWidth: true
buttonRadius: Appearance.rounding.small
materialIcon: "ifl"
mainText: randomWallProc.running ? Translation.tr("Be patient...") : Translation.tr("Random: osu! seasonal")
onClicked: {
randomWallProc.scriptPath = `${Directories.scriptPath}/colors/random/random_osu_wall.sh`;
randomWallProc.running = true;
}
StyledToolTip {
text: Translation.tr("Random osu! seasonal background\nImage is saved to ~/Pictures/Wallpapers")
}
}
RippleButtonWithIcon {
Layout.fillWidth: true
materialIcon: "wallpaper"
@@ -154,17 +171,6 @@ ContentPage {
dark: true
}
}
ConfigSwitch {
text: Translation.tr("Transparency")
checked: Config.options.appearance.transparency.enable
onCheckedChanged: {
Config.options.appearance.transparency.enable = checked;
}
StyledToolTip {
text: Translation.tr("Might look ass. Unsupported.")
}
}
}
}
@@ -213,6 +219,17 @@ ContentPage {
}
]
}
ConfigSwitch {
text: Translation.tr("Transparency")
checked: Config.options.appearance.transparency.enable
onCheckedChanged: {
Config.options.appearance.transparency.enable = checked;
}
StyledToolTip {
text: Translation.tr("Might look ass. Unsupported.")
}
}
}
ContentSection {
@@ -36,6 +36,9 @@ Item {
event.accepted = true
}
}
if ((event.modifiers & Qt.ControlModifier) && (event.modifiers & Qt.ShiftModifier) && event.key === Qt.Key_O) {
Ai.clearMessages();
}
}
property var allCommands: [
@@ -332,14 +335,11 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
mouseScrollFactor: Config.options.interactions.scrolling.mouseScrollFactor * 1.4
property int lastResponseLength: 0
property bool shouldAutoScroll: true
onContentYChanged: shouldAutoScroll = atYEnd
onContentHeightChanged: {
if (shouldAutoScroll) positionViewAtEnd();
if (atYEnd) positionViewAtEnd();
}
onCountChanged: { // Auto-scroll when new messages are added
if (shouldAutoScroll) positionViewAtEnd();
if (atYEnd) positionViewAtEnd();
}
add: null // Prevent function calls from being janky
@@ -647,13 +647,24 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
root.handleInput(inputText)
event.accepted = true
}
} else if ((event.modifiers & Qt.ControlModifier) && event.key === Qt.Key_V) { // Intercept Ctrl+V to handle image pasting
} else if ((event.modifiers & Qt.ControlModifier) && event.key === Qt.Key_V) { // Intercept Ctrl+V to handle image/file pasting
if (event.modifiers & Qt.ShiftModifier) { // Let Shift+Ctrl+V = plain paste
messageInputField.text += Quickshell.clipboardText
event.accepted = true;
return;
}
// Try image paste first
const currentClipboardEntry = Cliphist.entries[0]
const cleanCliphistEntry = StringUtils.cleanCliphistEntry(currentClipboardEntry)
if (/^\d+\t\[\[.*binary data.*\d+x\d+.*\]\]$/.test(currentClipboardEntry)) { // First entry = currently copied entry = image?
decodeImageAndAttachProc.handleEntry(currentClipboardEntry)
event.accepted = true;
return;
} else if (cleanCliphistEntry.startsWith("file://")) { // First entry = currently copied entry = image?
const fileName = decodeURIComponent(cleanCliphistEntry)
Ai.attachFile(fileName);
event.accepted = true;
return;
}
event.accepted = false; // No image, let text pasting proceed
} else if (event.key === Qt.Key_Escape) { // Esc to detach file
@@ -127,8 +127,12 @@ Scope { // Scope
sourceComponent: FloatingWindow {
id: detachedSidebarRoot
visible: GlobalStates.sidebarLeftOpen
property var contentParent: detachedSidebarBackground
visible: GlobalStates.sidebarLeftOpen
onVisibleChanged: {
if (!visible) GlobalStates.sidebarLeftOpen = false;
}
Rectangle {
id: detachedSidebarBackground
@@ -125,13 +125,12 @@ Rectangle {
sourceComponent: Item {
implicitHeight: root.imageHeight * root.scale
implicitWidth: imagePreview.implicitWidth
Image {
StyledImage {
id: imagePreview
anchors.fill: parent
source: Qt.resolvedUrl(root.filePath)
fillMode: Image.PreserveAspectFit
antialiasing: true
asynchronous: true
width: root.imageWidth * root.scale
height: root.imageHeight * root.scale
sourceSize.width: root.imageWidth * root.scale
@@ -58,7 +58,7 @@ Button {
contentItem: Item {
anchors.fill: parent
Image {
StyledImage {
id: imageObject
anchors.fill: parent
width: root.rowHeight * modelData.aspect_ratio
@@ -68,12 +68,6 @@ Button {
sourceSize.width: root.rowHeight * modelData.aspect_ratio
sourceSize.height: root.rowHeight
visible: opacity > 0
opacity: status === Image.Ready ? 1 : 0
Behavior on opacity {
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
@@ -56,7 +56,7 @@ Item {
StyledSlider {
id: slider
value: root.node.audio.volume
onValueChanged: root.node.audio.volume = value
onMoved: root.node.audio.volume = value
}
}
}
@@ -80,7 +80,7 @@ Item { // Bar content region
}
}
ColumnLayout { // Middle section
Column { // Middle section
id: middleSection
anchors.centerIn: parent
spacing: 4
@@ -156,8 +156,6 @@ Item { // Bar content region
Layout.fillWidth: true
Layout.fillHeight: false
}
}
}
@@ -70,12 +70,15 @@ MouseArea {
hoverTarget: root
active: GlobalStates.mediaControlsOpen ? false : root.containsMouse
ColumnLayout {
Column {
anchors.centerIn: parent
RowLayout {
spacing: 5
spacing: 4
Row {
spacing: 4
MaterialSymbol {
anchors.verticalCenter: parent.verticalCenter
fill: 0
font.weight: Font.Medium
text: "music_note"
@@ -84,6 +87,7 @@ MouseArea {
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: "Media"
font {
weight: Font.Medium
+46 -22
View File
@@ -15,6 +15,7 @@ import qs.modules.common.functions
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell
import Quickshell.Io
import Quickshell.Widgets
@@ -35,7 +36,11 @@ ShellRoot {
property color imageFillColor: "#33f1d1ff"
property color onBorderColor: "#ff000000"
property real standardRounding: 4
readonly property var windows: HyprlandData.windowList
readonly property var windows: [...HyprlandData.windowList].sort((a, b) => {
// Sort floating=true windows before others
if (a.floating === b.floating) return 0;
return a.floating ? -1 : 1;
})
readonly property var layers: HyprlandData.layers
readonly property real falsePositivePreventionRatio: 0.5
@@ -74,10 +79,10 @@ ShellRoot {
}
implicitWidth: regionInfoRow.implicitWidth + horizontalPadding * 2
implicitHeight: regionInfoRow.implicitHeight + verticalPadding * 2
RowLayout {
Row {
id: regionInfoRow
anchors.centerIn: parent
spacing: 8
spacing: 4
Loader {
id: regionIconLoader
@@ -133,7 +138,8 @@ ShellRoot {
})
readonly property list<var> layerRegions: {
const layersOfThisMonitor = root.layers[panelWindow.hyprlandMonitor.name]
const topLayers = layersOfThisMonitor.levels["2"]
const topLayers = layersOfThisMonitor?.levels["2"]
if (!topLayers) return [];
const nonBarTopLayers = topLayers
.filter(layer => !(layer.namespace.includes(":bar") || layer.namespace.includes(":verticalBar") || layer.namespace.includes(":dock")))
.map(layer => {
@@ -386,14 +392,24 @@ ShellRoot {
// Overlay to darken screen
Rectangle { // Base
id: overlayRect
z: 0
anchors.fill: parent
color: root.overlayColor
layer.enabled: true
id: darkenOverlay
z: 1
anchors {
left: parent.left
top: parent.top
leftMargin: panelWindow.regionX - darkenOverlay.border.width
topMargin: panelWindow.regionY - darkenOverlay.border.width
}
width: panelWindow.regionWidth + darkenOverlay.border.width * 2
height: panelWindow.regionHeight + darkenOverlay.border.width * 2
color: "transparent"
// border.color: root.selectionBorderColor
border.color: root.overlayColor
border.width: Math.max(panelWindow.width, panelWindow.height)
radius: root.standardRounding
}
Rectangle {
// TODO: Make this mask the base instead of just overlaying a border
id: selectionBorder
z: 1
anchors {
left: parent.left
@@ -406,11 +422,21 @@ ShellRoot {
color: "transparent"
border.color: root.selectionBorderColor
border.width: 2
radius: root.standardRounding
// radius: root.standardRounding
radius: 0 // TODO: figure out how to make the overlay thing work with rounding
}
StyledText {
anchors {
bottom: selectionBorder.bottom
right: selectionBorder.right
margins: 8
}
text: `${Math.round(panelWindow.regionWidth)} x ${Math.round(panelWindow.regionHeight)}`
}
// Instructions
Rectangle {
z: 9999
anchors {
top: parent.top
horizontalCenter: parent.horizontalCenter
@@ -430,21 +456,19 @@ ShellRoot {
implicitWidth: instructionsRow.implicitWidth + 10 * 2
implicitHeight: instructionsRow.implicitHeight + 5 * 2
RowLayout {
Row {
id: instructionsRow
anchors.centerIn: parent
Item {
Layout.fillHeight: true
implicitWidth: screenshotRegionIcon.implicitWidth
MaterialSymbol {
id: screenshotRegionIcon
anchors.centerIn: parent
iconSize: Appearance.font.pixelSize.larger
text: "screenshot_region"
color: root.genericContentForeground
}
spacing: 4
MaterialSymbol {
id: screenshotRegionIcon
// anchors.centerIn: parent
iconSize: Appearance.font.pixelSize.larger
text: "screenshot_region"
color: root.genericContentForeground
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: Translation.tr("Drag or click a region • LMB: Copy • RMB: Edit")
color: root.genericContentForeground
}
@@ -32,11 +32,11 @@ page=$((1 + RANDOM % 1000));
response=$(curl "https://konachan.net/post.json?tags=rating%3Asafe&limit=1&page=$page")
link=$(echo "$response" | jq '.[0].file_url' -r);
ext=$(echo "$link" | awk -F. '{print $NF}')
downloadPath="$PICTURES_DIR/Wallpapers/konachan_random_image.$ext"
downloadPath="$PICTURES_DIR/Wallpapers/random_wallpaper.$ext"
illogicalImpulseConfigPath="$HOME/.config/illogical-impulse/config.json"
currentWallpaperPath=$(jq -r '.background.wallpaperPath' $illogicalImpulseConfigPath)
if [ "$downloadPath" == "$currentWallpaperPath" ]; then
downloadPath="$PICTURES_DIR/Wallpapers/konachan_random_image-1.$ext"
downloadPath="$PICTURES_DIR/Wallpapers/random_wallpaper-1.$ext"
fi
curl "$link" -o "$downloadPath"
"$SCRIPT_DIR/switchwall.sh" --image "$downloadPath"
"$SCRIPT_DIR/../switchwall.sh" --image "$downloadPath"
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
get_pictures_dir() {
if command -v xdg-user-dir &> /dev/null; then
xdg-user-dir PICTURES
return
fi
local config_file="${XDG_CONFIG_HOME:-$HOME/.config}/user-dirs.dirs"
if [ -f "$config_file" ]; then
local pictures_path
pictures_path=$(source "$config_file" >/dev/null 2>&1; echo "$XDG_PICTURES_DIR")
echo "${pictures_path/#\$HOME/$HOME}"
return
fi
echo "$HOME/Pictures"
}
QUICKSHELL_CONFIG_NAME="ii"
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}"
XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}"
PICTURES_DIR=$(get_pictures_dir)
CONFIG_DIR="$XDG_CONFIG_HOME/quickshell/$QUICKSHELL_CONFIG_NAME"
CACHE_DIR="$XDG_CACHE_HOME/quickshell"
STATE_DIR="$XDG_STATE_HOME/quickshell"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
mkdir -p "$PICTURES_DIR/Wallpapers"
response=$(curl "https://osu.ppy.sh/api/v2/seasonal-backgrounds")
images=$(echo "$response" | jq '.backgrounds | length' -r);
randomIndex=$((RANDOM % images));
link=$(echo "$response" | jq ".backgrounds[$randomIndex].url" -r)
ext=$(echo "$link" | awk -F. '{print $NF}')
downloadPath="$PICTURES_DIR/Wallpapers/random_wallpaper.$ext"
illogicalImpulseConfigPath="$HOME/.config/illogical-impulse/config.json"
currentWallpaperPath=$(jq -r '.background.wallpaperPath' $illogicalImpulseConfigPath)
if [ "$downloadPath" == "$currentWallpaperPath" ]; then
downloadPath="$PICTURES_DIR/Wallpapers/random_wallpaper-1.$ext"
fi
curl "$link" -o "$downloadPath"
"$SCRIPT_DIR/../switchwall.sh" --image "$downloadPath"
+25
View File
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
# Based on https://unix.stackexchange.com/a/602935
# Skip if already unlocked
locked_state=$(busctl --user get-property org.freedesktop.secrets \
/org/freedesktop/secrets/collection/login \
org.freedesktop.Secret.Collection Locked)
if [[ "${locked_state}" == "b false" ]]; then
echo 'Keyring is already unlocked.' >&2
exit 1
fi
# Prompt for password if not provided
if [[ -z "${UNLOCK_PASSWORD}" ]]; then
echo -n 'Login password: ' >&2
read -s UNLOCK_PASSWORD || return
fi
# Unlock
killall -q -u "$(whoami)" gnome-keyring-daemon
eval $(echo -n "${UNLOCK_PASSWORD}" \
| gnome-keyring-daemon --daemonize --login \
| sed -e 's/^/export /')
unset UNLOCK_PASSWORD
echo '' >&2
@@ -74,8 +74,14 @@ Singleton {
id: monitor
required property ShellScreen screen
readonly property bool isDdc: root.ddcMonitors.some(m => m.model === screen.model)
readonly property string busNum: root.ddcMonitors.find(m => m.model === screen.model)?.busNum ?? ""
readonly property bool isDdc: {
const match = root.ddcMonitors.find(m => m.model === screen.model && !root.monitors.slice(0, root.monitors.indexOf(this)).some(mon => mon.busNum === m.busNum));
return !!match;
}
readonly property string busNum: {
const match = root.ddcMonitors.find(m => m.model === screen.model && !root.monitors.slice(0, root.monitors.indexOf(this)).some(mon => mon.busNum === m.busNum));
return match?.busNum ?? "";
}
property int rawMaxBrightness: 100
property real brightness
property bool ready: false
@@ -95,6 +95,18 @@ Singleton {
deleteProc.deleteEntry(entry);
}
Process {
id: wipeProc
command: [root.cliphistBinary, "wipe"]
onExited: (exitCode, exitStatus) => {
root.refresh();
}
}
function wipe() {
wipeProc.running = true;
}
Connections {
target: Quickshell
function onClipboardTextChanged() {
+22 -8
View File
@@ -12,7 +12,6 @@ import Quickshell.Io
*/
Singleton {
id: root
property var manualActive
property string from: Config.options?.light?.night?.from ?? "19:00"
property string to: Config.options?.light?.night?.to ?? "06:30"
property bool automatic: Config.options?.light?.night?.automatic && (Config?.ready ?? true)
@@ -29,6 +28,9 @@ Singleton {
property int clockHour: DateTime.clock.hours
property int clockMinute: DateTime.clock.minutes
property var manualActive
property int manualActiveHour
property int manualActiveMinute
onClockMinuteChanged: reEvaluate()
onAutomaticChanged: {
@@ -36,17 +38,26 @@ Singleton {
root.firstEvaluation = true;
reEvaluate();
}
function inBetween(t, from, to) {
if (from < to) {
return (t >= from && t <= to);
} else {
// Wrapped around midnight
return (t >= from || t <= to);
}
}
function reEvaluate() {
const t = clockHour * 60 + clockMinute;
const from = fromHour * 60 + fromMinute;
const to = toHour * 60 + toMinute;
const manualActive = manualActiveHour * 60 + manualActiveMinute;
if (from < to) {
root.shouldBeOn = t >= from && t <= to;
} else {
// Wrapped around midnight
root.shouldBeOn = t >= from || t <= to;
if (root.manualActive !== undefined && (inBetween(from, manualActive, t) || inBetween(to, manualActive, t))) {
root.manualActive = undefined;
}
root.shouldBeOn = inBetween(t, from, to);
if (firstEvaluation) {
firstEvaluation = false;
root.ensureState();
@@ -94,15 +105,18 @@ Singleton {
if (output.length == 0 || output.startsWith("Couldn't"))
root.active = false;
else
root.active = (output != "6500");
root.active = (output != "6500"); // 6500 is the default when off
// console.log("[Hyprsunset] Fetched state:", output, "->", root.active);
}
}
}
function toggle() {
if (root.manualActive === undefined)
if (root.manualActive === undefined) {
root.manualActive = root.active;
root.manualActiveHour = root.clockHour;
root.manualActiveMinute = root.clockMinute;
}
root.manualActive = !root.manualActive;
if (root.manualActive) {
+14 -2
View File
@@ -12,10 +12,22 @@ Singleton {
id: root
property alias inhibit: idleInhibitor.enabled
inhibit: Persistent.states.idle.inhibit
inhibit: false
Connections {
target: Persistent
function onReadyChanged() {
if (!Persistent.isNewHyprlandInstance) {
root.inhibit = Persistent.states.idle.inhibit
} else {
Persistent.states.idle.inhibit = root.inhibit
}
}
}
function toggleInhibit() {
Persistent.states.idle.inhibit = !Persistent.states.idle.inhibit
root.inhibit = !root.inhibit
Persistent.states.idle.inhibit = root.inhibit
}
IdleInhibitor {
+2 -4
View File
@@ -224,10 +224,8 @@ ApplicationWindow {
Connections {
target: root
function onCurrentPageChanged() {
if (pageLoader.sourceComponent !== root.pages[root.currentPage].component) {
switchAnim.complete();
switchAnim.start();
}
switchAnim.complete();
switchAnim.start();
}
}
+2 -4
View File
@@ -42,8 +42,7 @@ ShellRoot {
property bool enableLock: true
property bool enableMediaControls: true
property bool enableNotificationPopup: true
property bool enableOnScreenDisplayBrightness: true
property bool enableOnScreenDisplayVolume: true
property bool enableOnScreenDisplay: true
property bool enableOnScreenKeyboard: true
property bool enableOverview: true
property bool enableReloadPopup: true
@@ -72,8 +71,7 @@ ShellRoot {
LazyLoader { active: enableLock; component: Lock {} }
LazyLoader { active: enableMediaControls; component: MediaControls {} }
LazyLoader { active: enableNotificationPopup; component: NotificationPopup {} }
LazyLoader { active: enableOnScreenDisplayBrightness; component: OnScreenDisplayBrightness {} }
LazyLoader { active: enableOnScreenDisplayVolume; component: OnScreenDisplayVolume {} }
LazyLoader { active: enableOnScreenDisplay; component: OnScreenDisplay {} }
LazyLoader { active: enableOnScreenKeyboard; component: OnScreenKeyboard {} }
LazyLoader { active: enableOverview; component: Overview {} }
LazyLoader { active: enableReloadPopup; component: ReloadPopup {} }
+1 -1
View File
@@ -44,7 +44,7 @@ ApplicationWindow {
Process {
id: konachanWallProc
property string status: ""
command: ["bash", "-c", Quickshell.shellPath("scripts/colors/random_konachan_wall.sh")]
command: ["bash", "-c", Quickshell.shellPath("scripts/colors/random/random_konachan_wall.sh")]
stdout: SplitParser {
onRead: data => {
console.log(`Konachan wall proc output: ${data}`);