1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-05-02 10:32:07 -04:00

Compare commits

..

2 Commits

Author SHA1 Message Date
bbedward
6e6416c8ba blur: fix dankbar auto-hide blur, fix synchronization in popouts and
modals
2026-03-30 10:40:52 -04:00
bbedward
a0b2debd7e blur: add blur support with ext-bg-effect 2026-03-30 09:33:26 -04:00
35 changed files with 199 additions and 456 deletions

View File

@@ -1,40 +0,0 @@
package main
import (
"fmt"
"os"
"github.com/AvengeMedia/DankMaterialShell/core/internal/blur"
"github.com/spf13/cobra"
)
var blurCmd = &cobra.Command{
Use: "blur",
Short: "Background blur utilities",
}
var blurCheckCmd = &cobra.Command{
Use: "check",
Short: "Check if the compositor supports background blur (ext-background-effect-v1)",
Args: cobra.NoArgs,
Run: runBlurCheck,
}
func init() {
blurCmd.AddCommand(blurCheckCmd)
}
func runBlurCheck(cmd *cobra.Command, args []string) {
supported, err := blur.ProbeSupport()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
switch supported {
case true:
fmt.Println("supported")
default:
fmt.Println("unsupported")
}
}

View File

@@ -525,6 +525,5 @@ func getCommonCommands() []*cobra.Command {
configCmd, configCmd,
dlCmd, dlCmd,
randrCmd, randrCmd,
blurCmd,
} }
} }

View File

@@ -1,35 +0,0 @@
package blur
import (
wlhelpers "github.com/AvengeMedia/DankMaterialShell/core/internal/wayland/client"
client "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
)
const extBackgroundEffectInterface = "ext_background_effect_manager_v1"
func ProbeSupport() (bool, error) {
display, err := client.Connect("")
if err != nil {
return false, err
}
defer display.Context().Close()
registry, err := display.GetRegistry()
if err != nil {
return false, err
}
found := false
registry.SetGlobalHandler(func(e client.RegistryGlobalEvent) {
switch e.Interface {
case extBackgroundEffectInterface:
found = true
}
})
if err := wlhelpers.Roundtrip(display, display.Context()); err != nil {
return false, err
}
return found, nil
}

View File

@@ -137,7 +137,7 @@ bind = SUPER, bracketright, layoutmsg, preselect r
# === Sizing & Layout === # === Sizing & Layout ===
bind = SUPER, R, layoutmsg, togglesplit bind = SUPER, R, layoutmsg, togglesplit
bind = SUPER CTRL, F, resizeactive, exact 100% 100% bind = SUPER CTRL, F, resizeactive, exact 100%
# === Move/resize windows with mainMod + LMB/RMB and dragging === # === Move/resize windows with mainMod + LMB/RMB and dragging ===
bindmd = SUPER, mouse:272, Move window, movewindow bindmd = SUPER, mouse:272, Move window, movewindow

View File

@@ -94,7 +94,6 @@ windowrule = tile on, match:class ^(gnome-control-center)$
windowrule = tile on, match:class ^(pavucontrol)$ windowrule = tile on, match:class ^(pavucontrol)$
windowrule = tile on, match:class ^(nm-connection-editor)$ windowrule = tile on, match:class ^(nm-connection-editor)$
windowrule = float on, match:class ^(org\.gnome\.Calculator)$
windowrule = float on, match:class ^(gnome-calculator)$ windowrule = float on, match:class ^(gnome-calculator)$
windowrule = float on, match:class ^(galculator)$ windowrule = float on, match:class ^(galculator)$
windowrule = float on, match:class ^(blueman-manager)$ windowrule = float on, match:class ^(blueman-manager)$

View File

@@ -444,21 +444,20 @@ func GetFocusedMonitor() string {
type outputInfo struct { type outputInfo struct {
x, y int32 x, y int32
scale float64
transform int32 transform int32
} }
func getAllOutputInfos() map[string]*outputInfo { func getOutputInfo(outputName string) (*outputInfo, bool) {
display, err := client.Connect("") display, err := client.Connect("")
if err != nil { if err != nil {
return nil return nil, false
} }
ctx := display.Context() ctx := display.Context()
defer ctx.Close() defer ctx.Close()
registry, err := display.GetRegistry() registry, err := display.GetRegistry()
if err != nil { if err != nil {
return nil return nil, false
} }
var outputManager *wlr_output_management.ZwlrOutputManagerV1 var outputManager *wlr_output_management.ZwlrOutputManagerV1
@@ -477,17 +476,16 @@ func getAllOutputInfos() map[string]*outputInfo {
}) })
if err := wlhelpers.Roundtrip(display, ctx); err != nil { if err := wlhelpers.Roundtrip(display, ctx); err != nil {
return nil return nil, false
} }
if outputManager == nil { if outputManager == nil {
return nil return nil, false
} }
type headState struct { type headState struct {
name string name string
x, y int32 x, y int32
scale float64
transform int32 transform int32
} }
heads := make(map[*wlr_output_management.ZwlrOutputHeadV1]*headState) heads := make(map[*wlr_output_management.ZwlrOutputHeadV1]*headState)
@@ -503,9 +501,6 @@ func getAllOutputInfos() map[string]*outputInfo {
state.x = pe.X state.x = pe.X
state.y = pe.Y state.y = pe.Y
}) })
e.Head.SetScaleHandler(func(se wlr_output_management.ZwlrOutputHeadV1ScaleEvent) {
state.scale = se.Scale
})
e.Head.SetTransformHandler(func(te wlr_output_management.ZwlrOutputHeadV1TransformEvent) { e.Head.SetTransformHandler(func(te wlr_output_management.ZwlrOutputHeadV1TransformEvent) {
state.transform = te.Transform state.transform = te.Transform
}) })
@@ -516,32 +511,21 @@ func getAllOutputInfos() map[string]*outputInfo {
for !done { for !done {
if err := ctx.Dispatch(); err != nil { if err := ctx.Dispatch(); err != nil {
return nil return nil, false
} }
} }
result := make(map[string]*outputInfo, len(heads))
for _, state := range heads { for _, state := range heads {
if state.name == "" { if state.name == outputName {
continue return &outputInfo{
} x: state.x,
result[state.name] = &outputInfo{ y: state.y,
x: state.x, transform: state.transform,
y: state.y, }, true
scale: state.scale,
transform: state.transform,
} }
} }
return result
}
func getOutputInfo(outputName string) (*outputInfo, bool) { return nil, false
infos := getAllOutputInfos()
if infos == nil {
return nil, false
}
info, ok := infos[outputName]
return info, ok
} }
func getDWLActiveWindow() (*WindowGeometry, error) { func getDWLActiveWindow() (*WindowGeometry, error) {

View File

@@ -2,7 +2,6 @@ package screenshot
import ( import (
"fmt" "fmt"
"math"
"sync" "sync"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log" "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
@@ -305,20 +304,22 @@ func (s *Screenshoter) captureAllScreens() (*CaptureResult, error) {
if len(outputs) == 0 { if len(outputs) == 0 {
return nil, fmt.Errorf("no outputs available") return nil, fmt.Errorf("no outputs available")
} }
if len(outputs) == 1 { if len(outputs) == 1 {
return s.captureWholeOutput(outputs[0]) return s.captureWholeOutput(outputs[0])
} }
wlrInfos := getAllOutputInfos() // Capture all outputs first to get actual buffer sizes
type capturedOutput struct {
type pendingOutput struct { output *WaylandOutput
result *CaptureResult result *CaptureResult
logX float64 physX int
logY float64 physY int
scale float64
} }
var pending []pendingOutput captured := make([]capturedOutput, 0, len(outputs))
maxScale := 1.0
var minX, minY, maxX, maxY int
first := true
for _, output := range outputs { for _, output := range outputs {
result, err := s.captureWholeOutput(output) result, err := s.captureWholeOutput(output)
@@ -327,74 +328,50 @@ func (s *Screenshoter) captureAllScreens() (*CaptureResult, error) {
continue continue
} }
logX, logY := float64(output.x), float64(output.y) outX, outY := output.x, output.y
scale := float64(output.scale) scale := float64(output.scale)
switch DetectCompositor() { switch DetectCompositor() {
case CompositorHyprland: case CompositorHyprland:
if hx, hy, _, _, ok := GetHyprlandMonitorGeometry(output.name); ok { if hx, hy, _, _, ok := GetHyprlandMonitorGeometry(output.name); ok {
logX, logY = float64(hx), float64(hy) outX, outY = hx, hy
} }
if hs := GetHyprlandMonitorScale(output.name); hs > 0 { if s := GetHyprlandMonitorScale(output.name); s > 0 {
scale = hs scale = s
} }
default: case CompositorDWL:
if wlrInfos != nil { if info, ok := getOutputInfo(output.name); ok {
if info, ok := wlrInfos[output.name]; ok { outX, outY = info.x, info.y
logX, logY = float64(info.x), float64(info.y)
if info.scale > 0 {
scale = info.scale
}
}
} }
} }
if scale <= 0 { if scale <= 0 {
scale = 1.0 scale = 1.0
} }
pending = append(pending, pendingOutput{result: result, logX: logX, logY: logY, scale: scale}) physX := int(float64(outX) * scale)
if scale > maxScale { physY := int(float64(outY) * scale)
maxScale = scale
}
}
if len(pending) == 0 { captured = append(captured, capturedOutput{
return nil, fmt.Errorf("failed to capture any outputs") output: output,
} result: result,
if len(pending) == 1 { physX: physX,
return pending[0].result, nil physY: physY,
} })
type layoutEntry struct { right := physX + result.Buffer.Width
result *CaptureResult bottom := physY + result.Buffer.Height
canvasX int
canvasY int
canvasW int
canvasH int
}
entries := make([]layoutEntry, len(pending))
var minX, minY, maxX, maxY int
for i, p := range pending { if first {
cx := int(math.Round(p.logX * maxScale)) minX, minY = physX, physY
cy := int(math.Round(p.logY * maxScale)) maxX, maxY = right, bottom
cw := int(math.Round(float64(p.result.Buffer.Width) * maxScale / p.scale)) first = false
ch := int(math.Round(float64(p.result.Buffer.Height) * maxScale / p.scale))
entries[i] = layoutEntry{result: p.result, canvasX: cx, canvasY: cy, canvasW: cw, canvasH: ch}
right := cx + cw
bottom := cy + ch
if i == 0 {
minX, minY, maxX, maxY = cx, cy, right, bottom
continue continue
} }
if cx < minX {
minX = cx if physX < minX {
minX = physX
} }
if cy < minY { if physY < minY {
minY = cy minY = physY
} }
if right > maxX { if right > maxX {
maxX = right maxX = right
@@ -404,26 +381,35 @@ func (s *Screenshoter) captureAllScreens() (*CaptureResult, error) {
} }
} }
if len(captured) == 0 {
return nil, fmt.Errorf("failed to capture any outputs")
}
if len(captured) == 1 {
return captured[0].result, nil
}
totalW := maxX - minX totalW := maxX - minX
totalH := maxY - minY totalH := maxY - minY
composite, err := CreateShmBuffer(totalW, totalH, totalW*4)
compositeStride := totalW * 4
composite, err := CreateShmBuffer(totalW, totalH, compositeStride)
if err != nil { if err != nil {
for _, e := range entries { for _, c := range captured {
e.result.Buffer.Close() c.result.Buffer.Close()
} }
return nil, fmt.Errorf("create composite buffer: %w", err) return nil, fmt.Errorf("create composite buffer: %w", err)
} }
composite.Clear() composite.Clear()
var format uint32 var format uint32
for _, e := range entries { for _, c := range captured {
if format == 0 { if format == 0 {
format = e.result.Format format = c.result.Format
} }
s.blitBufferScaled(composite, e.result.Buffer, s.blitBuffer(composite, c.result.Buffer, c.physX-minX, c.physY-minY, c.result.YInverted)
e.canvasX-minX, e.canvasY-minY, e.canvasW, e.canvasH, c.result.Buffer.Close()
e.result.YInverted)
e.result.Buffer.Close()
} }
return &CaptureResult{ return &CaptureResult{
@@ -433,44 +419,32 @@ func (s *Screenshoter) captureAllScreens() (*CaptureResult, error) {
}, nil }, nil
} }
func (s *Screenshoter) blitBufferScaled(dst, src *ShmBuffer, dstX, dstY, dstW, dstH int, yInverted bool) { func (s *Screenshoter) blitBuffer(dst, src *ShmBuffer, dstX, dstY int, yInverted bool) {
if dstW <= 0 || dstH <= 0 {
return
}
srcData := src.Data() srcData := src.Data()
dstData := dst.Data() dstData := dst.Data()
for dy := 0; dy < dstH; dy++ { for srcY := 0; srcY < src.Height; srcY++ {
canvasY := dstY + dy actualSrcY := srcY
if canvasY < 0 || canvasY >= dst.Height {
continue
}
srcY := dy * src.Height / dstH
if yInverted { if yInverted {
srcY = src.Height - 1 - srcY actualSrcY = src.Height - 1 - srcY
} }
if srcY < 0 || srcY >= src.Height {
dy := dstY + srcY
if dy < 0 || dy >= dst.Height {
continue continue
} }
srcRowOff := srcY * src.Stride srcRowOff := actualSrcY * src.Stride
dstRowOff := canvasY * dst.Stride dstRowOff := dy * dst.Stride
for dx := 0; dx < dstW; dx++ { for srcX := 0; srcX < src.Width; srcX++ {
canvasX := dstX + dx dx := dstX + srcX
if canvasX < 0 || canvasX >= dst.Width { if dx < 0 || dx >= dst.Width {
continue
}
srcX := dx * src.Width / dstW
if srcX >= src.Width {
continue continue
} }
si := srcRowOff + srcX*4 si := srcRowOff + srcX*4
di := dstRowOff + canvasX*4 di := dstRowOff + dx*4
if si+3 >= len(srcData) || di+3 >= len(dstData) { if si+3 >= len(srcData) || di+3 >= len(dstData) {
continue continue

View File

@@ -369,7 +369,9 @@ Item {
} }
function previous(): void { function previous(): void {
MprisController.previousOrRewind(); if (MprisController.activePlayer && MprisController.activePlayer.canGoPrevious) {
MprisController.activePlayer.previous();
}
} }
function next(): void { function next(): void {

View File

@@ -31,7 +31,7 @@ Item {
property real animationOffset: Theme.spacingL property real animationOffset: Theme.spacingL
property list<real> animationEnterCurve: Theme.expressiveCurves.expressiveDefaultSpatial property list<real> animationEnterCurve: Theme.expressiveCurves.expressiveDefaultSpatial
property list<real> animationExitCurve: Theme.expressiveCurves.emphasized property list<real> animationExitCurve: Theme.expressiveCurves.emphasized
property color backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) property color backgroundColor: Theme.surfaceContainer
property color borderColor: Theme.outlineMedium property color borderColor: Theme.outlineMedium
property real borderWidth: 0 property real borderWidth: 0
property real cornerRadius: Theme.cornerRadius property real cornerRadius: Theme.cornerRadius

View File

@@ -132,7 +132,7 @@ DankModal {
modalWidth: 680 modalWidth: 680
modalHeight: contentLoader.item ? contentLoader.item.implicitHeight + Theme.spacingM * 2 : 680 modalHeight: contentLoader.item ? contentLoader.item.implicitHeight + Theme.spacingM * 2 : 680
backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) backgroundColor: Theme.surfaceContainer
cornerRadius: Theme.cornerRadius cornerRadius: Theme.cornerRadius
borderColor: Theme.outlineMedium borderColor: Theme.outlineMedium
borderWidth: 1 borderWidth: 1

View File

@@ -311,7 +311,7 @@ FocusScope {
Item { Item {
anchors.fill: parent anchors.fill: parent
visible: !editMode && !(root.parentModal?.isClosing ?? false) visible: !editMode
Item { Item {
id: footerBar id: footerBar
@@ -737,6 +737,8 @@ FocusScope {
Item { Item {
width: parent.width width: parent.width
height: parent.height - searchField.height - categoryRow.height - fileFilterRow.height - actionPanel.height - Theme.spacingXS * ((categoryRow.visible ? 1 : 0) + (fileFilterRow.visible ? 1 : 0) + 2) height: parent.height - searchField.height - categoryRow.height - fileFilterRow.height - actionPanel.height - Theme.spacingXS * ((categoryRow.visible ? 1 : 0) + (fileFilterRow.visible ? 1 : 0) + 2)
opacity: root.parentModal?.isClosing ? 0 : 1
ResultsList { ResultsList {
id: resultsList id: resultsList
anchors.fill: parent anchors.fill: parent

View File

@@ -324,8 +324,6 @@ Item {
height: 24 height: 24
z: 100 z: 100
visible: { visible: {
if (BlurService.enabled)
return false;
if (mainListView.contentHeight <= mainListView.height) if (mainListView.contentHeight <= mainListView.height)
return false; return false;
var atBottom = mainListView.contentY >= mainListView.contentHeight - mainListView.height + mainListView.originY - 5; var atBottom = mainListView.contentY >= mainListView.contentHeight - mainListView.height + mainListView.originY - 5;
@@ -451,7 +449,7 @@ Item {
case "apps": case "apps":
return "apps"; return "apps";
default: default:
return "search_off"; return root.controller?.searchQuery?.length > 0 ? "search_off" : "search";
} }
} }
} }
@@ -487,9 +485,9 @@ Item {
case "plugins": case "plugins":
return hasQuery ? I18n.tr("No plugin results") : I18n.tr("Browse or search plugins"); return hasQuery ? I18n.tr("No plugin results") : I18n.tr("Browse or search plugins");
case "apps": case "apps":
return I18n.tr("No apps found"); return hasQuery ? I18n.tr("No apps found") : I18n.tr("Type to search apps");
default: default:
return I18n.tr("No results found"); return hasQuery ? I18n.tr("No results found") : I18n.tr("Type to search");
} }
} }
} }

View File

@@ -133,7 +133,7 @@ DankPopout {
QtObject { QtObject {
id: modalAdapter id: modalAdapter
property bool spotlightOpen: appDrawerPopout.shouldBeVisible property bool spotlightOpen: appDrawerPopout.shouldBeVisible
readonly property bool isClosing: !appDrawerPopout.shouldBeVisible property bool isClosing: false
function hide() { function hide() {
appDrawerPopout.close(); appDrawerPopout.close();

View File

@@ -34,7 +34,7 @@ PluginComponent {
id: detailRoot id: detailRoot
implicitHeight: detailColumn.implicitHeight + Theme.spacingM * 2 implicitHeight: detailColumn.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) color: Theme.surfaceContainerHigh
DankActionButton { DankActionButton {
anchors.top: parent.top anchors.top: parent.top
@@ -252,7 +252,7 @@ PluginComponent {
width: parent ? parent.width : 300 width: parent ? parent.width : 300
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.surfaceLight color: Theme.surfaceContainerHighest
border.width: 1 border.width: 1
border.color: Theme.outlineLight border.color: Theme.outlineLight
opacity: 1.0 opacity: 1.0

View File

@@ -33,7 +33,7 @@ Row {
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
background: Rectangle { background: Rectangle {
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) color: Theme.surfaceContainer
border.color: Theme.primarySelected border.color: Theme.primarySelected
border.width: 0 border.width: 0
radius: Theme.cornerRadius radius: Theme.cornerRadius

View File

@@ -207,9 +207,9 @@ Rectangle {
width: parent.width width: parent.width
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: deviceMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceLight color: deviceMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.color: modelData === AudioService.source ? Theme.primary : Theme.outlineLight border.color: modelData === AudioService.source ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: modelData === AudioService.source ? 2 : 1 border.width: 0
Row { Row {
anchors.left: parent.left anchors.left: parent.left

View File

@@ -218,9 +218,9 @@ Rectangle {
width: parent.width width: parent.width
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: deviceMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceLight color: deviceMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.color: modelData === AudioService.sink ? Theme.primary : Theme.outlineLight border.color: modelData === AudioService.sink ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: modelData === AudioService.sink ? 2 : 1 border.width: 0
DankRipple { DankRipple {
id: deviceRipple id: deviceRipple
@@ -397,9 +397,9 @@ Rectangle {
width: parent.width width: parent.width
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.surfaceLight color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.color: modelData === AudioService.sink ? Theme.primary : Theme.outlineLight border.color: modelData === AudioService.sink ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: modelData === AudioService.sink ? 2 : 1 border.width: 0
Row { Row {
anchors.left: parent.left anchors.left: parent.left

View File

@@ -129,9 +129,8 @@ Rectangle {
width: (parent.width - Theme.spacingM) / 2 width: (parent.width - Theme.spacingM) / 2
height: 64 height: 64
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.surfaceLight color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.color: Theme.outlineLight border.width: 0
border.width: 1
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
@@ -165,9 +164,8 @@ Rectangle {
width: (parent.width - Theme.spacingM) / 2 width: (parent.width - Theme.spacingM) / 2
height: 64 height: 64
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.surfaceLight color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.color: Theme.outlineLight border.width: 0
border.width: 1
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent

View File

@@ -153,7 +153,7 @@ Item {
width: 320 width: 320
height: contentColumn.implicitHeight + Theme.spacingL * 2 height: contentColumn.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) color: Theme.surfaceContainer
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 0 border.width: 0
opacity: modalVisible ? 1 : 0 opacity: modalVisible ? 1 : 0

View File

@@ -229,6 +229,7 @@ Rectangle {
width: parent.width width: parent.width
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
border.width: 0
Component.onCompleted: { Component.onCompleted: {
if (!isConnected) if (!isConnected)
@@ -242,8 +243,8 @@ Rectangle {
if (isConnecting) if (isConnecting)
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12); return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12);
if (deviceMouseArea.containsMouse) if (deviceMouseArea.containsMouse)
return Theme.primaryHoverLight; return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08);
return Theme.surfaceLight; return Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency);
} }
border.color: { border.color: {
@@ -251,9 +252,8 @@ Rectangle {
return Theme.warning; return Theme.warning;
if (isConnected) if (isConnected)
return Theme.primary; return Theme.primary;
return Theme.outlineLight; return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12);
} }
border.width: (isConnecting || isConnected) ? 2 : 1
Row { Row {
anchors.left: parent.left anchors.left: parent.left
@@ -490,9 +490,9 @@ Rectangle {
width: parent.width width: parent.width
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: availableMouseArea.containsMouse && isInteractive ? Theme.primaryHoverLight : Theme.surfaceLight color: availableMouseArea.containsMouse && isInteractive ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.color: Theme.outlineLight border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 1 border.width: 0
opacity: isInteractive ? 1 : 0.6 opacity: isInteractive ? 1 : 0.6
Row { Row {

View File

@@ -79,9 +79,9 @@ Rectangle {
width: parent.width width: parent.width
height: 80 height: 80
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.surfaceLight color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.color: modelData.mount === currentMountPath ? Theme.primary : Theme.outlineLight border.color: modelData.mount === currentMountPath ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: modelData.mount === currentMountPath ? 2 : 1 border.width: modelData.mount === currentMountPath ? 2 : 0
Row { Row {
anchors.left: parent.left anchors.left: parent.left

View File

@@ -308,9 +308,9 @@ Rectangle {
width: parent.width width: parent.width
height: wiredContentRow.implicitHeight + Theme.spacingM * 2 height: wiredContentRow.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: wiredNetworkMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceLight color: wiredNetworkMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.color: isActive ? Theme.primary : Theme.outlineLight border.color: Theme.primary
border.width: isActive ? 2 : 1 border.width: 0
Row { Row {
id: wiredContentRow id: wiredContentRow
@@ -565,9 +565,9 @@ Rectangle {
width: wifiContent.width width: wifiContent.width
height: wifiContentRow.implicitHeight + Theme.spacingM * 2 height: wifiContentRow.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: networkMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceLight color: networkMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.color: wifiDelegate.isConnected ? Theme.primary : Theme.outlineLight border.color: wifiDelegate.isConnected ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: wifiDelegate.isConnected ? 2 : 1 border.width: 0
Row { Row {
id: wifiContentRow id: wifiContentRow

View File

@@ -102,7 +102,7 @@ BasePill {
StyledTextMetrics { StyledTextMetrics {
id: cpuBaseline id: cpuBaseline
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText) font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
text: "100%" text: "88%"
} }
StyledTextMetrics { StyledTextMetrics {

View File

@@ -99,7 +99,7 @@ BasePill {
if (isMouseWheelY) { if (isMouseWheelY) {
if (deltaY > 0) { if (deltaY > 0) {
MprisController.previousOrRewind(); activePlayer.previous();
} else { } else {
activePlayer.next(); activePlayer.next();
} }
@@ -107,7 +107,7 @@ BasePill {
scrollAccumulatorY += deltaY; scrollAccumulatorY += deltaY;
if (Math.abs(scrollAccumulatorY) >= touchpadThreshold) { if (Math.abs(scrollAccumulatorY) >= touchpadThreshold) {
if (scrollAccumulatorY > 0) { if (scrollAccumulatorY > 0) {
MprisController.previousOrRewind(); activePlayer.previous();
} else { } else {
activePlayer.next(); activePlayer.next();
} }
@@ -214,7 +214,7 @@ BasePill {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
activePlayer.togglePlaying(); activePlayer.togglePlaying();
} else if (mouse.button === Qt.MiddleButton) { } else if (mouse.button === Qt.MiddleButton) {
MprisController.previousOrRewind(); activePlayer.previous();
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton) {
activePlayer.next(); activePlayer.next();
} }
@@ -370,7 +370,11 @@ BasePill {
anchors.fill: parent anchors.fill: parent
enabled: root.playerAvailable enabled: root.playerAvailable
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: MprisController.previousOrRewind() onClicked: {
if (activePlayer) {
activePlayer.previous();
}
}
} }
} }

View File

@@ -20,46 +20,6 @@ Item {
property var blurBarWindow: null property var blurBarWindow: null
property var hyprlandOverviewLoader: null property var hyprlandOverviewLoader: null
property var parentScreen: null property var parentScreen: null
readonly property real _leftMargin: {
if (isVertical)
return 0;
root.x;
if (!root.parent)
return 0;
const gap = root.mapToItem(null, 0, 0).x;
return (gap > 0 && gap < 30) ? gap + 5 : 0;
}
readonly property real _rightMargin: {
if (isVertical)
return 0;
root.x;
root.width;
if (!root.parent || !blurBarWindow)
return 0;
const gap = blurBarWindow.width - root.mapToItem(null, root.width, 0).x;
return (gap > 0 && gap < 30) ? gap + 5 : 0;
}
readonly property real _topMargin: {
if (!isVertical)
return 0;
root.y;
if (!root.parent)
return 0;
const gap = root.mapToItem(null, 0, 0).y;
return (gap > 0 && gap < 30) ? gap + 5 : 0;
}
readonly property real _bottomMargin: {
if (!isVertical)
return 0;
root.y;
root.height;
if (!root.parent || !blurBarWindow)
return 0;
const gap = blurBarWindow.height - root.mapToItem(null, 0, root.height).y;
return (gap > 0 && gap < 30) ? gap + 5 : 0;
}
property int _desktopEntriesUpdateTrigger: 0 property int _desktopEntriesUpdateTrigger: 0
readonly property var sortedToplevels: { readonly property var sortedToplevels: {
return CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, screenName); return CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, screenName);
@@ -579,60 +539,6 @@ Item {
}); });
} }
function switchToWorkspaceByModelData(data) {
if (!data)
return;
if (root.useExtWorkspace && (data.id || data.name)) {
ExtWorkspaceService.activateWorkspace(data.id || data.name, data.groupID || "");
return;
}
switch (CompositorService.compositor) {
case "niri":
if (data.idx !== undefined)
NiriService.switchToWorkspace(data.idx);
break;
case "hyprland":
if (data.id)
Hyprland.dispatch(`workspace ${data.id}`);
break;
case "dwl":
if (data.tag !== undefined)
DwlService.switchToTag(root.screenName, data.tag);
break;
case "sway":
case "scroll":
case "miracle":
if (data.num)
try {
I3.dispatch(`workspace number ${data.num}`);
} catch (_) {}
break;
}
}
function findClosestWorkspaceIndex(localX, localY) {
if (workspaceRepeater.count === 0)
return -1;
let closestIdx = -1;
let closestDist = Infinity;
for (let i = 0; i < workspaceRepeater.count; i++) {
const item = workspaceRepeater.itemAt(i);
if (!item)
continue;
const center = item.mapToItem(root, item.width / 2, item.height / 2);
const dist = isVertical ? Math.abs(localY - center.y) : Math.abs(localX - center.x);
if (dist < closestDist) {
closestDist = dist;
closestIdx = i;
}
}
return closestIdx;
}
function switchWorkspace(direction) { function switchWorkspace(direction) {
if (useExtWorkspace) { if (useExtWorkspace) {
const realWorkspaces = getRealWorkspaces(); const realWorkspaces = getRealWorkspaces();
@@ -846,15 +752,8 @@ Item {
} }
MouseArea { MouseArea {
id: edgeMouseArea anchors.fill: parent
z: -1 acceptedButtons: Qt.RightButton
x: -root._leftMargin
y: -root._topMargin
width: root.width + root._leftMargin + root._rightMargin
height: root.height + root._topMargin + root._bottomMargin
acceptedButtons: Qt.LeftButton | Qt.RightButton
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
property real touchpadAccumulator: 0 property real touchpadAccumulator: 0
property real mouseAccumulator: 0 property real mouseAccumulator: 0
@@ -867,20 +766,12 @@ Item {
} }
onClicked: mouse => { onClicked: mouse => {
const rootPos = edgeMouseArea.mapToItem(root, mouse.x, mouse.y); if (mouse.button === Qt.RightButton) {
switch (mouse.button) {
case Qt.RightButton:
if (CompositorService.isNiri) { if (CompositorService.isNiri) {
NiriService.toggleOverview(); NiriService.toggleOverview();
} else if (CompositorService.isHyprland && root.hyprlandOverviewLoader?.item) { } else if (CompositorService.isHyprland && root.hyprlandOverviewLoader?.item) {
root.hyprlandOverviewLoader.item.overviewOpen = !root.hyprlandOverviewLoader.item.overviewOpen; root.hyprlandOverviewLoader.item.overviewOpen = !root.hyprlandOverviewLoader.item.overviewOpen;
} }
break;
case Qt.LeftButton:
const idx = root.findClosestWorkspaceIndex(rootPos.x, rootPos.y);
if (idx >= 0)
root.switchToWorkspaceByModelData(root.workspaceList[idx]);
break;
} }
} }

View File

@@ -487,7 +487,17 @@ Item {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: MprisController.previousOrRewind() onClicked: {
if (!activePlayer) {
return;
}
if (activePlayer.position > 8 && activePlayer.canSeek) {
activePlayer.position = 0;
} else {
activePlayer.previous();
}
}
} }
} }
} }

View File

@@ -145,7 +145,14 @@ Card {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: MprisController.previousOrRewind() onClicked: {
if (!activePlayer) return
if (activePlayer.position > 8 && activePlayer.canSeek) {
activePlayer.position = 0
} else {
activePlayer.previous()
}
}
} }
} }

View File

@@ -1338,7 +1338,7 @@ Item {
enabled: MprisController.activePlayer?.canGoPrevious ?? false enabled: MprisController.activePlayer?.canGoPrevious ?? false
hoverEnabled: enabled hoverEnabled: enabled
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: MprisController.previousOrRewind() onClicked: MprisController.activePlayer?.previous()
} }
} }

View File

@@ -1,5 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts
import qs.Common import qs.Common
import qs.Widgets import qs.Widgets
import qs.Services import qs.Services
@@ -51,14 +52,15 @@ Column {
height: implicitHeight height: implicitHeight
spacing: Theme.spacingM spacing: Theme.spacingM
Row { RowLayout {
width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
DankIcon { DankIcon {
name: root.titleIcon name: root.titleIcon
size: Theme.iconSize size: Theme.iconSize
color: Theme.primary color: Theme.primary
anchors.verticalCenter: parent.verticalCenter Layout.alignment: Qt.AlignVCenter
} }
StyledText { StyledText {
@@ -66,7 +68,7 @@ Column {
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter Layout.alignment: Qt.AlignVCenter
} }
} }
@@ -979,26 +981,10 @@ Column {
Repeater { Repeater {
model: [ model: [
{ { label: I18n.tr("Percentage"), mode: 0, icon: "percent" },
label: I18n.tr("Percentage"), { label: I18n.tr("Total"), mode: 1, icon: "storage" },
mode: 0, { label: I18n.tr("Remaining"), mode: 2, icon: "hourglass_empty" },
icon: "percent" { label: I18n.tr("Remaining / Total"), mode: 3, icon: "pie_chart" }
},
{
label: I18n.tr("Total"),
mode: 1,
icon: "storage"
},
{
label: I18n.tr("Remaining"),
mode: 2,
icon: "hourglass_empty"
},
{
label: I18n.tr("Remaining / Total"),
mode: 3,
icon: "pie_chart"
}
] ]
delegate: Rectangle { delegate: Rectangle {
@@ -1330,7 +1316,20 @@ Column {
id: longestControlCenterLabelMetrics id: longestControlCenterLabelMetrics
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
text: { text: {
const labels = [I18n.tr("Network"), I18n.tr("VPN"), I18n.tr("Bluetooth"), I18n.tr("Audio"), I18n.tr("Volume"), I18n.tr("Microphone"), I18n.tr("Microphone Volume"), I18n.tr("Brightness"), I18n.tr("Brightness Value"), I18n.tr("Battery"), I18n.tr("Printer"), I18n.tr("Screen Sharing")]; const labels = [
I18n.tr("Network"),
I18n.tr("VPN"),
I18n.tr("Bluetooth"),
I18n.tr("Audio"),
I18n.tr("Volume"),
I18n.tr("Microphone"),
I18n.tr("Microphone Volume"),
I18n.tr("Brightness"),
I18n.tr("Brightness Value"),
I18n.tr("Battery"),
I18n.tr("Printer"),
I18n.tr("Screen Sharing")
];
let longest = ""; let longest = "";
for (let i = 0; i < labels.length; i++) { for (let i = 0; i < labels.length; i++) {
if (labels[i].length > longest.length) if (labels[i].length > longest.length)
@@ -1341,7 +1340,6 @@ Column {
} }
Repeater { Repeater {
id: groupRepeater
model: controlCenterContextMenu.controlCenterGroups model: controlCenterContextMenu.controlCenterGroups
delegate: Item { delegate: Item {
@@ -1571,6 +1569,8 @@ Column {
} }
} }
} }
id: groupRepeater
} }
} }
} }

View File

@@ -3,16 +3,13 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io
import Quickshell.Wayland // ! Import is needed despite what qmlls says import Quickshell.Wayland // ! Import is needed despite what qmlls says
import qs.Common import qs.Common
Singleton { Singleton {
id: root id: root
property bool quickshellSupported: false property bool available: false
property bool compositorSupported: false
property bool available: quickshellSupported && compositorSupported
readonly property bool enabled: available && (SettingsData.blurEnabled ?? false) readonly property bool enabled: available && (SettingsData.blurEnabled ?? false)
readonly property color borderColor: { readonly property color borderColor: {
@@ -75,27 +72,6 @@ Singleton {
region.destroy(); region.destroy();
} }
Process {
id: blurProbe
running: false
command: ["dms", "blur", "check"]
stdout: StdioCollector {
onStreamFinished: {
root.compositorSupported = text.trim() === "supported";
if (root.compositorSupported)
console.info("BlurService: Compositor supports ext-background-effect-v1");
else
console.info("BlurService: Compositor does not support ext-background-effect-v1");
}
}
onExited: exitCode => {
if (exitCode !== 0)
console.warn("BlurService: blur probe failed with code:", exitCode);
}
}
Component.onCompleted: { Component.onCompleted: {
try { try {
const test = Qt.createQmlObject(` const test = Qt.createQmlObject(`
@@ -103,9 +79,8 @@ Singleton {
Region { radius: 0 } Region { radius: 0 }
`, root, "BlurAvailabilityTest"); `, root, "BlurAvailabilityTest");
test.destroy(); test.destroy();
quickshellSupported = true; available = true;
console.info("BlurService: Quickshell blur support available"); console.info("BlurService: Initialized with blur support");
blurProbe.running = true;
} catch (e) { } catch (e) {
console.info("BlurService: BackgroundEffect not available - blur disabled. Requires a newer version of Quickshell."); console.info("BlurService: BackgroundEffect not available - blur disabled. Requires a newer version of Quickshell.");
} }

View File

@@ -10,20 +10,4 @@ Singleton {
readonly property list<MprisPlayer> availablePlayers: Mpris.players.values readonly property list<MprisPlayer> availablePlayers: Mpris.players.values
property MprisPlayer activePlayer: availablePlayers.find(p => p.isPlaying) ?? availablePlayers.find(p => p.canControl && p.canPlay) ?? null property MprisPlayer activePlayer: availablePlayers.find(p => p.isPlaying) ?? availablePlayers.find(p => p.canControl && p.canPlay) ?? null
Timer {
interval: 1000
running: root.activePlayer?.playbackState === MprisPlaybackState.Playing
repeat: true
onTriggered: root.activePlayer?.positionChanged()
}
function previousOrRewind(): void {
if (!activePlayer)
return;
if (activePlayer.position > 8 && activePlayer.canSeek)
activePlayer.position = 0;
else if (activePlayer.canGoPrevious)
activePlayer.previous();
}
} }

View File

@@ -89,7 +89,7 @@ Row {
width: Math.max(contentItem.implicitWidth + root.buttonPadding * 2, root.minButtonWidth) + (selected ? 4 : 0) width: Math.max(contentItem.implicitWidth + root.buttonPadding * 2, root.minButtonWidth) + (selected ? 4 : 0)
height: root.buttonHeight height: root.buttonHeight
color: selected ? Theme.buttonBg : Theme.withAlpha(Theme.surfaceVariant, Theme.popupTransparency) color: selected ? Theme.buttonBg : Theme.surfaceVariant
border.color: "transparent" border.color: "transparent"
border.width: 0 border.width: 0

View File

@@ -266,7 +266,7 @@ PanelWindow {
scale: shouldBeVisible ? 1 : 0.9 scale: shouldBeVisible ? 1 : 0.9
property bool childHovered: false property bool childHovered: false
readonly property real popupSurfaceAlpha: Theme.popupTransparency readonly property real popupSurfaceAlpha: SettingsData.popupTransparency
Rectangle { Rectangle {
id: background id: background
@@ -286,7 +286,7 @@ PanelWindow {
level: Theme.elevationLevel3 level: Theme.elevationLevel3
fallbackOffset: 6 fallbackOffset: 6
targetRadius: Theme.cornerRadius targetRadius: Theme.cornerRadius
targetColor: Theme.withAlpha(Theme.surfaceContainer, osdContainer.popupSurfaceAlpha) targetColor: Theme.surfaceContainer
borderColor: Theme.outlineMedium borderColor: Theme.outlineMedium
borderWidth: 1 borderWidth: 1
shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"

View File

@@ -576,6 +576,14 @@ Item {
} }
} }
Rectangle {
anchors.fill: parent
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
border.color: BlurService.enabled ? BlurService.borderColor : Theme.outlineMedium
border.width: BlurService.borderWidth
}
Loader { Loader {
id: contentLoader id: contentLoader
anchors.fill: parent anchors.fill: parent
@@ -583,21 +591,6 @@ Item {
asynchronous: false asynchronous: false
} }
} }
Rectangle {
width: parent.width
height: parent.height
x: contentWrapper.x
y: contentWrapper.y
opacity: contentWrapper.opacity
scale: contentWrapper.scale
visible: contentWrapper.visible
radius: Theme.cornerRadius
color: "transparent"
border.color: BlurService.enabled ? BlurService.borderColor : Theme.outlineMedium
border.width: BlurService.borderWidth
z: 100
}
} }
Item { Item {

View File

@@ -238,7 +238,7 @@ Rectangle {
width: fieldContent.width + Theme.spacingM * 2 width: fieldContent.width + Theme.spacingM * 2
height: 32 height: 32
radius: Theme.cornerRadius - 2 radius: Theme.cornerRadius - 2
color: Theme.surfaceLight color: Theme.surfaceContainerHigh
border.width: 1 border.width: 1
border.color: Theme.outlineLight border.color: Theme.outlineLight
@@ -272,9 +272,7 @@ Rectangle {
checked: configData ? (configData.autoconnect || false) : false checked: configData ? (configData.autoconnect || false) : false
visible: !VPNService.configLoading && configData !== null visible: !VPNService.configLoading && configData !== null
onToggled: checked => { onToggled: checked => {
VPNService.updateConfig(profile.uuid, { VPNService.updateConfig(profile.uuid, {autoconnect: checked});
autoconnect: checked
});
} }
} }