1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-05-07 21:12:08 -04:00

Compare commits

...

19 Commits

Author SHA1 Message Date
bbedward
0b88055742 clipboard: fix reliability of modal/popout 2026-04-06 10:30:39 -04:00
bbedward
2b0826e397 core: migrate to dms-shell arch package 2026-04-06 10:09:55 -04:00
bbedward
7db04c9660 i18n: use comments instead of context, sync 2026-04-06 09:41:45 -04:00
Al- Amin
14d1e1d985 fix: Add match rule for new version of Gnome Calculator app ID (#2157) 2026-04-06 09:11:26 -04:00
Dimariqe
903ab1e61d fix: add TrayRecoveryService with bidirectional SNI dedup (Go server) (#2137)
* fix: add TrayRecoveryService with bidirectional SNI dedup (Go server)

Add TrayRecoveryService manager that re-registers lost tray icons after
resume from suspend via native DBus scanning in the Go server.

The service resolves every registered SNI item (both well-known names and
:1.xxx connection IDs) to a canonical connection ID, building a unified
registeredConnIDs set before either scan section runs. This prevents
duplicates in both directions:

- If an app registered via well-known name, the connection-ID section
  skips its :1.xxx entry.
- If an app registered via connection ID, the well-known-name section
  skips its well-known name (checked through registeredConnIDs).
- After successfully registering via well-known name, registeredConnIDs
  is updated immediately so the connection-ID section won't probe the
  same app in the same run.

A startup scan (3 s delay) covers the common case where the DMS process
is killed during suspend and restarted by systemd (Type=dbus), so the
loginctl PrepareForSleep watcher alone is not sufficient. The startup
scan is harmless on a normal boot — it finds all items already registered
and exits early.

Go port of quickshell commit 1470aa3.

* fix: 'interface{}' can be replaced by 'any'

* TrayRecoveryService: Remove objPath parameter from registerSNI
2026-04-06 09:03:36 -04:00
Walid Salah
5982655539 Make focused app widget only show focused app on the current screen (#2152) 2026-04-05 10:53:18 -04:00
Aaron Tulino
1021a210cf Change power profile by scrolling battery (#2142)
Scrolling up "increases" the profile (Power Saver -> Balanced -> Performance). Supports touchpad.
2026-04-03 12:04:00 -04:00
bbedward
e34edb15bb i18n: sync 2026-04-03 11:56:00 -04:00
dev
61ee5f4336 Update CpuMonitor.qml to reserve enough widget space for the widest number "100" instead of "88" (#2135)
My bar kept shifting around by a few pixels every time I hit 100% cpu usage
2026-04-02 15:26:56 -04:00
Phil Jackson
ce2a92ec27 feat: rewind to track start on previous when past 8 seconds (#2136)
* feat: rewind to track start on previous when past 8 seconds

Adds MprisController.previousOrRewind() which rewinds the current track
to position 0 if more than 8 seconds in (with canSeek check), and falls
back to previous() otherwise — matching traditional media player behaviour.

All previous() call sites across Media.qml, MediaPlayerTab.qml,
MediaOverviewCard.qml, LockScreenContent.qml and DMSShellIPC.qml
are updated to use the new shared function.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: poll position in MprisController for previousOrRewind accuracy

Without a polling timer, activePlayer.position is never updated in
contexts that don't display a seekbar (e.g. the DankBar widget), causing
the position > 8 check in previousOrRewind() to always see 0 and fall
through to previous() instead of rewinding.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 15:26:36 -04:00
Al- Amin
66ce79b9bf fix:update resizeactive binding to include height to make it work (#2126) 2026-04-01 09:19:01 -04:00
Al- Amin
30dd640314 fix:add window rule for the new version of Gnome Calculator (#2125) 2026-04-01 09:18:56 -04:00
bbedward
28f9aabcd9 screenshot: fix scaling of global coordinate space when using all
screens
2026-03-31 15:13:10 -04:00
bbedward
3d9bd73336 launcher: some polishes for blur 2026-03-31 11:04:18 -04:00
bbedward
3497d5f523 blur: stylize control center for blur mode 2026-03-31 09:42:08 -04:00
bbedward
8ef1d95e65 popout: fix inconsistent transparency 2026-03-31 09:06:48 -04:00
bbedward
e9aeb9ac60 blur: add probe to check compositor for ext-bg-effect 2026-03-30 15:18:44 -04:00
bbedward
fb02f7294d workspace: fix mouse area to edges
fixes #2108
2026-03-30 15:16:17 -04:00
bbedward
f15d49d80a blur: add blur support with ext-bg-effect 2026-03-30 11:52:35 -04:00
92 changed files with 23011 additions and 24281 deletions

View File

@@ -0,0 +1,40 @@
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

@@ -53,6 +53,7 @@ var (
clipCopyPasteOnce bool
clipCopyType string
clipCopyDownload bool
clipCopyCacheFile string
clipJSONOutput bool
)
@@ -191,6 +192,8 @@ func init() {
clipCopyCmd.Flags().BoolVarP(&clipCopyPasteOnce, "paste-once", "o", false, "Exit after first paste")
clipCopyCmd.Flags().StringVarP(&clipCopyType, "type", "t", "text/plain;charset=utf-8", "MIME type")
clipCopyCmd.Flags().BoolVarP(&clipCopyDownload, "download", "d", false, "Download URL as image and copy as file")
clipCopyCmd.Flags().StringVar(&clipCopyCacheFile, "cache-file", "", "")
clipCopyCmd.Flags().MarkHidden("cache-file")
clipWatchCmd.Flags().BoolVar(&clipJSONOutput, "json", false, "Output as JSON")
clipHistoryCmd.Flags().BoolVar(&clipJSONOutput, "json", false, "Output as JSON")
@@ -221,6 +224,13 @@ func init() {
}
func runClipCopy(cmd *cobra.Command, args []string) {
if clipCopyCacheFile != "" {
if err := clipboard.ServeCacheFile(clipCopyCacheFile, clipCopyType, clipCopyPasteOnce); err != nil {
log.Fatalf("serve cache file: %v", err)
}
return
}
var data []byte
copyFromStdin := false

View File

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

View File

@@ -109,16 +109,41 @@ func updateArchLinux() error {
}
var packageName string
if isArchPackageInstalled("dms-shell-bin") {
packageName = "dms-shell-bin"
var isAUR bool
if isArchPackageInstalled("dms-shell") {
packageName = "dms-shell"
} else if isArchPackageInstalled("dms-shell-git") {
packageName = "dms-shell-git"
isAUR = true
} else if isArchPackageInstalled("dms-shell-bin") {
packageName = "dms-shell-bin"
isAUR = true
} else {
fmt.Println("Info: Neither dms-shell-bin nor dms-shell-git package found.")
fmt.Println("Info: No dms-shell package found.")
fmt.Println("Info: Falling back to git-based update method...")
return updateOtherDistros()
}
if !isAUR {
fmt.Printf("This will update %s using pacman.\n", packageName)
if !confirmUpdate() {
return errdefs.ErrUpdateCancelled
}
fmt.Printf("\nRunning: sudo pacman -S %s\n", packageName)
cmd := exec.Command("sudo", "pacman", "-S", "--noconfirm", packageName)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Printf("Error: Failed to update using pacman: %v\n", err)
return err
}
fmt.Println("dms successfully updated")
return nil
}
var helper string
var updateCmd *exec.Cmd

View File

@@ -0,0 +1,35 @@
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

@@ -1,7 +1,6 @@
package clipboard
import (
"bytes"
"fmt"
"io"
"os"
@@ -14,7 +13,7 @@ import (
)
func Copy(data []byte, mimeType string) error {
return CopyReader(bytes.NewReader(data), mimeType, false, false)
return copyForkCached(data, mimeType, false)
}
func CopyOpts(data []byte, mimeType string, foreground, pasteOnce bool) error {
@@ -34,7 +33,7 @@ func CopyOpts(data []byte, mimeType string, foreground, pasteOnce bool) error {
return nil
}, mimeType, pasteOnce)
}
return CopyReader(bytes.NewReader(data), mimeType, foreground, pasteOnce)
return copyForkCached(data, mimeType, pasteOnce)
}
func CopyReader(data io.Reader, mimeType string, foreground, pasteOnce bool) error {
@@ -44,6 +43,53 @@ func CopyReader(data io.Reader, mimeType string, foreground, pasteOnce bool) err
return copyServeReader(data, mimeType, pasteOnce)
}
func copyForkCached(data []byte, mimeType string, pasteOnce bool) error {
cacheFile, err := createClipboardCacheFile()
if err != nil {
return fmt.Errorf("create cache file: %w", err)
}
cachePath := cacheFile.Name()
if _, err := cacheFile.Write(data); err != nil {
cacheFile.Close()
os.Remove(cachePath)
return fmt.Errorf("write cache file: %w", err)
}
if err := cacheFile.Close(); err != nil {
os.Remove(cachePath)
return fmt.Errorf("close cache file: %w", err)
}
args := []string{os.Args[0], "cl", "copy", "--foreground", "--cache-file", cachePath}
if pasteOnce {
args = append(args, "--paste-once")
}
args = append(args, "--type", mimeType)
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdin = nil
cmd.Stderr = nil
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
cmd.Env = append(os.Environ(), "DMS_CLIP_FORKED=1")
stdout, err := cmd.StdoutPipe()
if err != nil {
os.Remove(cachePath)
return fmt.Errorf("stdout pipe: %w", err)
}
if err := cmd.Start(); err != nil {
os.Remove(cachePath)
return fmt.Errorf("start: %w", err)
}
var buf [1]byte
if _, err := stdout.Read(buf[:]); err != nil {
return fmt.Errorf("waiting for clipboard ready: %w", err)
}
return nil
}
func copyFork(data io.Reader, mimeType string, pasteOnce bool) error {
args := []string{os.Args[0], "cl", "copy", "--foreground"}
if pasteOnce {
@@ -99,6 +145,19 @@ func signalReady() {
os.Stdout.Write([]byte{1})
}
func ServeCacheFile(path, mimeType string, pasteOnce bool) error {
defer os.Remove(path)
return copyServeWithWriter(func(writer io.Writer) error {
f, err := os.Open(path)
if err != nil {
return fmt.Errorf("open cache file: %w", err)
}
defer f.Close()
_, err = io.Copy(writer, f)
return err
}, mimeType, pasteOnce)
}
func copyServeReader(data io.Reader, mimeType string, pasteOnce bool) error {
cachedData, err := createClipboardCacheFile()
if err != nil {

View File

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

View File

@@ -94,6 +94,7 @@ windowrule = tile on, match:class ^(gnome-control-center)$
windowrule = tile on, match:class ^(pavucontrol)$
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 ^(galculator)$
windowrule = float on, match:class ^(blueman-manager)$

View File

@@ -224,6 +224,7 @@ window-rule {
open-floating false
}
window-rule {
match app-id=r#"^org\.gnome\.Calculator$"#
match app-id=r#"^gnome-calculator$"#
match app-id=r#"^galculator$"#
match app-id=r#"^blueman-manager$"#

View File

@@ -242,11 +242,7 @@ func (a *ArchDistribution) getDMSMapping(variant deps.PackageVariant) PackageMap
return PackageMapping{Name: "dms-shell-git", Repository: RepoTypeAUR}
}
if a.packageInstalled("dms-shell-bin") {
return PackageMapping{Name: "dms-shell-bin", Repository: RepoTypeAUR}
}
return PackageMapping{Name: "dms-shell-bin", Repository: RepoTypeAUR}
return PackageMapping{Name: "dms-shell", Repository: RepoTypeSystem}
}
func (a *ArchDistribution) detectXwaylandSatellite() deps.Dependency {
@@ -540,7 +536,7 @@ func (a *ArchDistribution) reorderAURPackages(packages []string) []string {
var dmsShell []string
for _, pkg := range packages {
if pkg == "dms-shell-git" || pkg == "dms-shell-bin" {
if pkg == "dms-shell-git" {
dmsShell = append(dmsShell, pkg)
} else {
isDep := false
@@ -621,7 +617,7 @@ func (a *ArchDistribution) installSingleAURPackageInternal(ctx context.Context,
}
}
if pkg == "dms-shell-git" || pkg == "dms-shell-bin" {
if pkg == "dms-shell-git" {
srcinfoPath := filepath.Join(packageDir, ".SRCINFO")
depsToRemove := []string{
"depends = quickshell",
@@ -644,15 +640,7 @@ func (a *ArchDistribution) installSingleAURPackageInternal(ctx context.Context,
}
srcinfoPath = filepath.Join(packageDir, ".SRCINFO")
if pkg == "dms-shell-bin" {
progressChan <- InstallProgressMsg{
Phase: PhaseAURPackages,
Progress: startProgress + 0.35*(endProgress-startProgress),
Step: fmt.Sprintf("Skipping dependency installation for %s (manually managed)...", pkg),
IsComplete: false,
LogOutput: fmt.Sprintf("Dependencies for %s are installed separately", pkg),
}
} else {
{
progressChan <- InstallProgressMsg{
Phase: PhaseAURPackages,
Progress: startProgress + 0.3*(endProgress-startProgress),
@@ -739,42 +727,9 @@ func (a *ArchDistribution) installSingleAURPackageInternal(ctx context.Context,
CommandInfo: "sudo pacman -U built-package",
}
// Find .pkg.tar* files - for split packages, install the base and any installed compositor variants
var files []string
if pkg == "dms-shell-git" || pkg == "dms-shell-bin" {
// For DMS split packages, install base package
pattern := filepath.Join(packageDir, fmt.Sprintf("%s-%s*.pkg.tar*", pkg, "*"))
matches, err := filepath.Glob(pattern)
if err == nil {
for _, match := range matches {
basename := filepath.Base(match)
// Always include base package
if !strings.Contains(basename, "hyprland") && !strings.Contains(basename, "niri") {
files = append(files, match)
}
}
}
// Also update compositor-specific packages if they're installed
if strings.HasSuffix(pkg, "-git") {
if a.packageInstalled("dms-shell-hyprland-git") {
hyprlandPattern := filepath.Join(packageDir, "dms-shell-hyprland-git-*.pkg.tar*")
if hyprlandMatches, err := filepath.Glob(hyprlandPattern); err == nil && len(hyprlandMatches) > 0 {
files = append(files, hyprlandMatches[0])
}
}
if a.packageInstalled("dms-shell-niri-git") {
niriPattern := filepath.Join(packageDir, "dms-shell-niri-git-*.pkg.tar*")
if niriMatches, err := filepath.Glob(niriPattern); err == nil && len(niriMatches) > 0 {
files = append(files, niriMatches[0])
}
}
}
} else {
// For other packages, install all built packages
matches, _ := filepath.Glob(filepath.Join(packageDir, "*.pkg.tar*"))
files = matches
}
matches, _ := filepath.Glob(filepath.Join(packageDir, "*.pkg.tar*"))
files = matches
if len(files) == 0 {
return fmt.Errorf("no package files found after building %s", pkg)

View File

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

View File

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

View File

@@ -31,6 +31,7 @@ import (
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/network"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/thememode"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/trayrecovery"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wayland"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wlcontext"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wlroutput"
@@ -72,6 +73,7 @@ var clipboardManager *clipboard.Manager
var dbusManager *serverDbus.Manager
var wlContext *wlcontext.SharedContext
var themeModeManager *thememode.Manager
var trayRecoveryManager *trayrecovery.Manager
var locationManager *location.Manager
var geoClientInstance geolocation.Client
@@ -394,6 +396,18 @@ func InitializeThemeModeManager() error {
return nil
}
func InitializeTrayRecoveryManager() error {
manager, err := trayrecovery.NewManager()
if err != nil {
return err
}
trayRecoveryManager = manager
log.Info("TrayRecovery manager initialized")
return nil
}
func InitializeLocationManager(geoClient geolocation.Client) error {
manager, err := location.NewManager(geoClient)
if err != nil {
@@ -1325,6 +1339,9 @@ func cleanupManagers() {
if themeModeManager != nil {
themeModeManager.Close()
}
if trayRecoveryManager != nil {
trayRecoveryManager.Close()
}
if wlContext != nil {
wlContext.Close()
}
@@ -1610,6 +1627,18 @@ func Start(printDocs bool) error {
}()
}
go func() {
<-loginctlReady
if loginctlManager == nil {
return
}
if err := InitializeTrayRecoveryManager(); err != nil {
log.Warnf("TrayRecovery manager unavailable: %v", err)
} else {
trayRecoveryManager.WatchLoginctl(loginctlManager)
}
}()
go func() {
geoClient := geolocation.NewClient()
geoClientInstance = geoClient

View File

@@ -0,0 +1,93 @@
package trayrecovery
import (
"fmt"
"sync"
"time"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/loginctl"
"github.com/godbus/dbus/v5"
)
const resumeDelay = 3 * time.Second
type Manager struct {
conn *dbus.Conn
stopChan chan struct{}
wg sync.WaitGroup
}
func NewManager() (*Manager, error) {
conn, err := dbus.ConnectSessionBus()
if err != nil {
return nil, fmt.Errorf("failed to connect to session bus: %w", err)
}
m := &Manager{
conn: conn,
stopChan: make(chan struct{}),
}
// Run a startup scan after a delay — covers the case where the process
// was killed during suspend and restarted by systemd (Type=dbus).
// The fresh process never sees the PrepareForSleep true→false transition,
// so the loginctl watcher alone is not enough.
go m.scheduleRecovery()
return m, nil
}
// WatchLoginctl subscribes to loginctl session state changes and triggers
// tray recovery after resume from suspend (PrepareForSleep false transition).
// This handles the case where the process survives suspend.
func (m *Manager) WatchLoginctl(lm *loginctl.Manager) {
ch := lm.Subscribe("tray-recovery")
m.wg.Add(1)
go func() {
defer m.wg.Done()
defer lm.Unsubscribe("tray-recovery")
wasSleeping := false
for {
select {
case <-m.stopChan:
return
case state, ok := <-ch:
if !ok {
return
}
if state.PreparingForSleep {
wasSleeping = true
continue
}
if wasSleeping {
wasSleeping = false
go m.scheduleRecovery()
}
}
}
}()
}
func (m *Manager) scheduleRecovery() {
select {
case <-time.After(resumeDelay):
m.recoverTrayItems()
case <-m.stopChan:
}
}
func (m *Manager) Close() {
select {
case <-m.stopChan:
return
default:
close(m.stopChan)
}
m.wg.Wait()
if m.conn != nil {
m.conn.Close()
}
log.Info("TrayRecovery manager closed")
}

View File

@@ -0,0 +1,262 @@
package trayrecovery
import (
"context"
"strings"
"sync"
"time"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
"github.com/godbus/dbus/v5"
)
const (
sniWatcherDest = "org.kde.StatusNotifierWatcher"
sniWatcherPath = "/StatusNotifierWatcher"
sniWatcherIface = "org.kde.StatusNotifierWatcher"
sniItemIface = "org.kde.StatusNotifierItem"
dbusIface = "org.freedesktop.DBus"
propsIface = "org.freedesktop.DBus.Properties"
probeTimeout = 300 * time.Millisecond
connProbeTimeout = 150 * time.Millisecond
batchSize = 30
)
var excludedPrefixes = []string{
"org.freedesktop.",
"org.gnome.",
"org.kde.StatusNotifier",
"com.canonical.AppMenu",
"org.mpris.",
"org.pipewire.",
"org.pulseaudio",
"fi.epitaph",
"quickshell",
"org.kde.quickshell",
}
func (m *Manager) recoverTrayItems() {
registeredItems := m.getRegisteredItems()
allNames := m.getDBusNames()
if allNames == nil {
return
}
registeredConnIDs := m.buildRegisteredConnIDs(registeredItems)
count := len(registeredItems)
log.Infof("TrayRecoveryService: scanning DBus for unregistered SNI items (%d already registered)...", count)
m.scanWellKnownNames(allNames, registeredItems, registeredConnIDs)
m.scanConnectionIDs(allNames, registeredItems, registeredConnIDs)
}
func (m *Manager) getRegisteredItems() []string {
obj := m.conn.Object(sniWatcherDest, sniWatcherPath)
variant, err := obj.GetProperty(sniWatcherIface + ".RegisteredStatusNotifierItems")
if err != nil {
log.Warnf("TrayRecoveryService: failed to get registered items: %v", err)
return nil
}
switch v := variant.Value().(type) {
case []string:
return v
case []any:
items := make([]string, 0, len(v))
for _, elem := range v {
if s, ok := elem.(string); ok {
items = append(items, s)
}
}
return items
}
return nil
}
func (m *Manager) getDBusNames() []string {
var names []string
err := m.conn.BusObject().Call(dbusIface+".ListNames", 0).Store(&names)
if err != nil {
log.Warnf("TrayRecoveryService: failed to list bus names: %v", err)
return nil
}
return names
}
func (m *Manager) getNameOwner(name string) string {
var owner string
err := m.conn.BusObject().Call(dbusIface+".GetNameOwner", 0, name).Store(&owner)
if err != nil {
return ""
}
return owner
}
// buildRegisteredConnIDs resolves every registered SNI item (well-known name
// or :1.xxx connection ID) to a canonical connection ID. This prevents
// duplicates in both directions.
func (m *Manager) buildRegisteredConnIDs(registeredItems []string) map[string]bool {
connIDs := make(map[string]bool, len(registeredItems))
for _, item := range registeredItems {
name := extractName(item)
if strings.HasPrefix(name, ":1.") {
connIDs[name] = true
} else {
owner := m.getNameOwner(name)
if owner != "" {
connIDs[owner] = true
}
}
}
return connIDs
}
// scanWellKnownNames probes well-known names (e.g. DinoX, nm-applet) for
// unregistered SNI items and re-registers them.
func (m *Manager) scanWellKnownNames(allNames []string, registeredItems []string, registeredConnIDs map[string]bool) {
registeredRaw := strings.Join(registeredItems, "\n")
for _, name := range allNames {
if strings.HasPrefix(name, ":") {
continue
}
if strings.Contains(registeredRaw, name) {
continue
}
// Skip if this name's connection ID is already in the registered set
// (handles the case where the app registered via connection ID instead)
connForName := m.getNameOwner(name)
if connForName != "" && registeredConnIDs[connForName] {
continue
}
if isExcludedName(name) {
continue
}
short := shortName(name)
objectPaths := []string{
"/StatusNotifierItem",
"/org/ayatana/NotificationItem/" + short,
}
for _, objPath := range objectPaths {
if m.probeSNI(name, objPath, probeTimeout) {
m.registerSNI(name)
// Update set so the connection-ID section won't double-register this app
if connForName != "" {
registeredConnIDs[connForName] = true
}
break
}
}
}
}
// scanConnectionIDs probes all :1.xxx connections in parallel for unregistered
// SNI items (e.g. Vesktop, Electron apps). Most non-SNI connections return an
// error instantly, so this is fast.
func (m *Manager) scanConnectionIDs(allNames []string, registeredItems []string, registeredConnIDs map[string]bool) {
registeredRaw := strings.Join(registeredItems, "\n")
registeredLower := strings.ToLower(registeredRaw)
var wg sync.WaitGroup
sem := make(chan struct{}, batchSize)
for _, name := range allNames {
if !strings.HasPrefix(name, ":1.") {
continue
}
if registeredConnIDs[name] {
continue
}
sem <- struct{}{}
wg.Add(1)
go func(conn string) {
defer wg.Done()
defer func() { <-sem }()
sniID := m.getSNIId(conn, connProbeTimeout)
if sniID == "" {
return
}
// Skip if an item with the same Id is already registered (case-insensitive)
if strings.Contains(registeredLower, strings.ToLower(sniID)) {
return
}
m.registerSNI(conn)
log.Infof("TrayRecovery: re-registered %s (Id: %s)", conn, sniID)
}(name)
}
wg.Wait()
}
func (m *Manager) probeSNI(dest, path string, timeout time.Duration) bool {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
obj := m.conn.Object(dest, dbus.ObjectPath(path))
var props map[string]dbus.Variant
err := obj.CallWithContext(ctx, propsIface+".GetAll", 0, sniItemIface).Store(&props)
if err != nil {
return false
}
_, hasID := props["Id"]
return hasID
}
func (m *Manager) getSNIId(dest string, timeout time.Duration) string {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
obj := m.conn.Object(dest, "/StatusNotifierItem")
var variant dbus.Variant
err := obj.CallWithContext(ctx, propsIface+".Get", 0, sniItemIface, "Id").Store(&variant)
if err != nil {
return ""
}
id, _ := variant.Value().(string)
return id
}
func (m *Manager) registerSNI(name string) {
obj := m.conn.Object(sniWatcherDest, sniWatcherPath)
call := obj.Call(sniWatcherIface+".RegisterStatusNotifierItem", 0, name)
if call.Err != nil {
log.Warnf("TrayRecovery: failed to register %s: %v", name, call.Err)
return
}
log.Infof("TrayRecovery: re-registered %s", name)
}
func extractName(item string) string {
if idx := strings.IndexByte(item, '/'); idx != -1 {
return item[:idx]
}
return item
}
func shortName(name string) string {
parts := strings.Split(name, ".")
if len(parts) > 0 {
return parts[len(parts)-1]
}
return name
}
func isExcludedName(name string) bool {
for _, prefix := range excludedPrefixes {
if strings.HasPrefix(name, prefix) {
return true
}
}
return false
}

View File

@@ -139,7 +139,7 @@ func dmsPackageName(distroID string, dependencies []deps.Dependency) string {
if isGit {
return "dms-shell-git"
}
return "dms-shell-bin"
return "dms-shell"
case distros.FamilyFedora, distros.FamilyUbuntu, distros.FamilyDebian, distros.FamilySUSE:
if isGit {
return "dms-git"

View File

@@ -186,6 +186,14 @@ Singleton {
onPopoutElevationEnabledChanged: saveSettings()
property bool barElevationEnabled: true
onBarElevationEnabledChanged: saveSettings()
property bool blurEnabled: false
onBlurEnabledChanged: saveSettings()
property string blurBorderColor: "outline"
onBlurBorderColorChanged: saveSettings()
property string blurBorderCustomColor: "#ffffff"
onBlurBorderCustomColorChanged: saveSettings()
property real blurBorderOpacity: 1.0
onBlurBorderOpacityChanged: saveSettings()
property string wallpaperFillMode: "Fill"
property bool blurredWallpaperLayer: false
property bool blurWallpaperOnOverview: false

View File

@@ -58,6 +58,10 @@ var SPEC = {
modalElevationEnabled: { def: true },
popoutElevationEnabled: { def: true },
barElevationEnabled: { def: true },
blurEnabled: { def: false },
blurBorderColor: { def: "outline" },
blurBorderCustomColor: { def: "#ffffff" },
blurBorderOpacity: { def: 1.0, coerce: percentToUnit },
wallpaperFillMode: { def: "Fill" },
blurredWallpaperLayer: { def: false },
blurWallpaperOnOverview: { def: false },

View File

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

View File

@@ -122,7 +122,7 @@ Item {
}
StyledText {
text: I18n.tr("No recent clipboard entries found")
text: clipboardContent.modal.clipboardAvailable ? I18n.tr("No recent clipboard entries found") : I18n.tr("Connecting to clipboard service…")
anchors.centerIn: parent
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
@@ -181,7 +181,7 @@ Item {
}
StyledText {
text: I18n.tr("No saved clipboard entries")
text: clipboardContent.modal.clipboardAvailable ? I18n.tr("No saved clipboard entries") : I18n.tr("Connecting to clipboard service…")
anchors.centerIn: parent
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText

View File

@@ -60,15 +60,12 @@ DankModal {
}
function show() {
if (!clipboardAvailable) {
ToastService.showError(I18n.tr("Clipboard service not available"));
return;
}
open();
activeImageLoads = 0;
shouldHaveFocus = true;
ClipboardService.reset();
ClipboardService.refresh();
if (clipboardAvailable)
ClipboardService.refresh();
keyboardController.reset();
Qt.callLater(function () {

View File

@@ -50,14 +50,11 @@ DankPopout {
}
function show() {
if (!clipboardAvailable) {
ToastService.showError(I18n.tr("Clipboard service not available"));
return;
}
open();
activeImageLoads = 0;
ClipboardService.reset();
ClipboardService.refresh();
if (clipboardAvailable)
ClipboardService.refresh();
keyboardController.reset();
Qt.callLater(function () {
@@ -122,10 +119,10 @@ DankPopout {
onBackgroundClicked: hide()
onShouldBeVisibleChanged: {
if (!shouldBeVisible) {
if (!shouldBeVisible)
return;
}
ClipboardService.refresh();
if (clipboardAvailable)
ClipboardService.refresh();
keyboardController.reset();
Qt.callLater(function () {
if (contentLoader.item?.searchField) {

View File

@@ -3,6 +3,7 @@ import Quickshell
import Quickshell.Wayland
import qs.Common
import qs.Services
import qs.Widgets
Item {
id: root
@@ -30,7 +31,7 @@ Item {
property real animationOffset: Theme.spacingL
property list<real> animationEnterCurve: Theme.expressiveCurves.expressiveDefaultSpatial
property list<real> animationExitCurve: Theme.expressiveCurves.emphasized
property color backgroundColor: Theme.surfaceContainer
property color backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
property color borderColor: Theme.outlineMedium
property real borderWidth: 0
property real cornerRadius: Theme.cornerRadius
@@ -59,11 +60,25 @@ Item {
function open() {
closeTimer.stop();
const focusedScreen = CompositorService.getFocusedScreen();
const screenChanged = focusedScreen && contentWindow.screen !== focusedScreen;
if (focusedScreen) {
if (screenChanged)
contentWindow.visible = false;
contentWindow.screen = focusedScreen;
if (!useSingleWindow)
if (!useSingleWindow) {
if (screenChanged)
clickCatcher.visible = false;
clickCatcher.screen = focusedScreen;
}
}
if (screenChanged) {
Qt.callLater(() => root._finishOpen());
} else {
_finishOpen();
}
}
function _finishOpen() {
ModalManager.openModal(root);
shouldBeVisible = true;
if (!useSingleWindow)
@@ -215,6 +230,16 @@ Item {
visible: false
color: "transparent"
WindowBlur {
targetWindow: contentWindow
readonly property real s: Math.min(1, modalContainer.scaleValue)
blurX: modalContainer.x + modalContainer.width * (1 - s) * 0.5 + Theme.snap(modalContainer.animX, root.dpr)
blurY: modalContainer.y + modalContainer.height * (1 - s) * 0.5 + Theme.snap(modalContainer.animY, root.dpr)
blurWidth: (shouldBeVisible && animatedContent.opacity > 0) ? modalContainer.width * s : 0
blurHeight: (shouldBeVisible && animatedContent.opacity > 0) ? modalContainer.height * s : 0
blurRadius: root.cornerRadius
}
WlrLayershell.namespace: root.layerNamespace
WlrLayershell.layer: {
if (root.useOverlayLayer)
@@ -393,6 +418,15 @@ Item {
shadowEnabled: root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
}
Rectangle {
anchors.fill: parent
radius: root.cornerRadius
color: "transparent"
border.color: BlurService.borderColor
border.width: BlurService.borderWidth
z: 100
}
FocusScope {
anchors.fill: parent
focus: root.shouldBeVisible

View File

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

View File

@@ -4,6 +4,7 @@ import Quickshell.Wayland
import Quickshell.Hyprland
import qs.Common
import qs.Services
import qs.Widgets
Item {
id: root
@@ -134,40 +135,47 @@ Item {
}
}
function show() {
closeCleanupTimer.stop();
function _finishShow(query, mode) {
spotlightOpen = true;
isClosing = false;
openedFromOverview = false;
var focusedScreen = CompositorService.getFocusedScreen();
if (focusedScreen)
launcherWindow.screen = focusedScreen;
spotlightOpen = true;
keyboardActive = true;
ModalManager.openModal(root);
if (useHyprlandFocusGrab)
focusGrab.active = true;
_ensureContentLoadedAndInitialize("", "");
_ensureContentLoadedAndInitialize(query || "", mode || "");
}
function show() {
closeCleanupTimer.stop();
var focusedScreen = CompositorService.getFocusedScreen();
if (focusedScreen && launcherWindow.screen !== focusedScreen) {
spotlightOpen = false;
isClosing = false;
launcherWindow.screen = focusedScreen;
Qt.callLater(() => root._finishShow("", ""));
return;
}
_finishShow("", "");
}
function showWithQuery(query) {
closeCleanupTimer.stop();
isClosing = false;
openedFromOverview = false;
var focusedScreen = CompositorService.getFocusedScreen();
if (focusedScreen)
if (focusedScreen && launcherWindow.screen !== focusedScreen) {
spotlightOpen = false;
isClosing = false;
launcherWindow.screen = focusedScreen;
Qt.callLater(() => root._finishShow(query, ""));
return;
}
spotlightOpen = true;
keyboardActive = true;
ModalManager.openModal(root);
if (useHyprlandFocusGrab)
focusGrab.active = true;
_ensureContentLoadedAndInitialize(query, "");
_finishShow(query, "");
}
function hide() {
@@ -191,14 +199,20 @@ Item {
function showWithMode(mode) {
closeCleanupTimer.stop();
var focusedScreen = CompositorService.getFocusedScreen();
if (focusedScreen && launcherWindow.screen !== focusedScreen) {
spotlightOpen = false;
isClosing = false;
launcherWindow.screen = focusedScreen;
Qt.callLater(() => root._finishShow("", mode));
return;
}
spotlightOpen = true;
isClosing = false;
openedFromOverview = false;
var focusedScreen = CompositorService.getFocusedScreen();
if (focusedScreen)
launcherWindow.screen = focusedScreen;
spotlightOpen = true;
keyboardActive = true;
ModalManager.openModal(root);
if (useHyprlandFocusGrab)
@@ -295,6 +309,16 @@ Item {
color: "transparent"
exclusionMode: ExclusionMode.Ignore
WindowBlur {
targetWindow: launcherWindow
readonly property real s: Math.min(1, modalContainer.scale)
blurX: root.modalX + root.modalWidth * (1 - s) * 0.5
blurY: root.modalY + root.modalHeight * (1 - s) * 0.5
blurWidth: (contentVisible && modalContainer.opacity > 0) ? root.modalWidth * s : 0
blurHeight: (contentVisible && modalContainer.opacity > 0) ? root.modalHeight * s : 0
blurRadius: root.cornerRadius
}
WlrLayershell.namespace: "dms:spotlight"
WlrLayershell.layer: {
switch (Quickshell.env("DMS_MODAL_LAYER")) {
@@ -428,6 +452,14 @@ Item {
event.accepted = true;
}
}
Rectangle {
anchors.fill: parent
radius: root.cornerRadius
color: "transparent"
border.color: BlurService.borderColor
border.width: BlurService.borderWidth
}
}
}
}

View File

@@ -311,7 +311,7 @@ FocusScope {
Item {
anchors.fill: parent
visible: !editMode
visible: !editMode && !(root.parentModal?.isClosing ?? false)
Item {
id: footerBar
@@ -737,8 +737,6 @@ FocusScope {
Item {
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)
opacity: root.parentModal?.isClosing ? 0 : 1
ResultsList {
id: resultsList
anchors.fill: parent

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -79,9 +79,9 @@ Rectangle {
width: parent.width
height: 80
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
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 : 0
color: Theme.surfaceLight
border.color: modelData.mount === currentMountPath ? Theme.primary : Theme.outlineLight
border.width: modelData.mount === currentMountPath ? 2 : 1
Row {
anchors.left: parent.left

View File

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

View File

@@ -14,6 +14,7 @@ Item {
property real barThickness: 48
property real barSpacing: 4
property var barConfig: null
property var blurBarWindow: null
property bool overrideAxisLayout: false
property bool forceVerticalLayout: false
@@ -357,6 +358,7 @@ Item {
barThickness: root.barThickness
barSpacing: root.barSpacing
barConfig: root.barConfig
blurBarWindow: root.blurBarWindow
isFirst: index === 0
isLast: index === centerRepeater.count - 1
sectionSpacing: parent.itemSpacing

View File

@@ -14,6 +14,8 @@ Item {
required property var rootWindow
required property var barConfig
readonly property var blurBarWindow: barWindow
property var leftWidgetsModel
property var centerWidgetsModel
property var rightWidgetsModel
@@ -408,6 +410,12 @@ Item {
value: topBarContent.barConfig
restoreMode: Binding.RestoreNone
}
Binding {
target: hLeftSection
property: "blurBarWindow"
value: topBarContent.blurBarWindow
restoreMode: Binding.RestoreNone
}
RightSection {
id: hRightSection
@@ -434,6 +442,12 @@ Item {
value: topBarContent.barConfig
restoreMode: Binding.RestoreNone
}
Binding {
target: hRightSection
property: "blurBarWindow"
value: topBarContent.blurBarWindow
restoreMode: Binding.RestoreNone
}
CenterSection {
id: hCenterSection
@@ -460,6 +474,12 @@ Item {
value: topBarContent.barConfig
restoreMode: Binding.RestoreNone
}
Binding {
target: hCenterSection
property: "blurBarWindow"
value: topBarContent.blurBarWindow
restoreMode: Binding.RestoreNone
}
}
Item {
@@ -493,6 +513,12 @@ Item {
value: topBarContent.barConfig
restoreMode: Binding.RestoreNone
}
Binding {
target: vLeftSection
property: "blurBarWindow"
value: topBarContent.blurBarWindow
restoreMode: Binding.RestoreNone
}
CenterSection {
id: vCenterSection
@@ -520,6 +546,12 @@ Item {
value: topBarContent.barConfig
restoreMode: Binding.RestoreNone
}
Binding {
target: vCenterSection
property: "blurBarWindow"
value: topBarContent.blurBarWindow
restoreMode: Binding.RestoreNone
}
RightSection {
id: vRightSection
@@ -548,6 +580,12 @@ Item {
value: topBarContent.barConfig
restoreMode: Binding.RestoreNone
}
Binding {
target: vRightSection
property: "blurBarWindow"
value: topBarContent.blurBarWindow
restoreMode: Binding.RestoreNone
}
}
}

View File

@@ -97,6 +97,112 @@ PanelWindow {
}
}
property var blurRegion: null
property var _blurWidgetItems: []
function registerBlurWidget(item) {
if (_blurWidgetItems.indexOf(item) >= 0)
return;
_blurWidgetItems = _blurWidgetItems.concat([item]);
_blurRebuildTimer.restart();
}
function unregisterBlurWidget(item) {
const idx = _blurWidgetItems.indexOf(item);
if (idx < 0)
return;
const arr = _blurWidgetItems.slice();
arr.splice(idx, 1);
_blurWidgetItems = arr;
_blurRebuildTimer.restart();
}
Timer {
id: _blurRebuildTimer
interval: 1
onTriggered: barBlur.rebuild()
}
Item {
id: barBlur
visible: false
readonly property bool barHasTransparency: barWindow._backgroundAlpha > 0 && barWindow._backgroundAlpha < 1
function rebuild() {
teardown();
if (!BlurService.enabled || !BlurService.available)
return;
const widgets = barWindow._blurWidgetItems.filter(w => w && w.visible && w.width > 0 && w.height > 0);
const hasBar = barHasTransparency;
if (!hasBar && widgets.length === 0)
return;
const cr = Theme.cornerRadius;
let qml = 'import QtQuick; import Quickshell; Region {';
for (let i = 0; i < widgets.length; i++) {
qml += ` property Item w${i}; Region { item: w${i}; radius: ${cr} }`;
}
qml += '}';
try {
const region = Qt.createQmlObject(qml, barWindow, "BarBlurRegion");
if (hasBar) {
region.x = Qt.binding(() => topBarMouseArea.x + barUnitInset.x + topBarSlide.x);
region.y = Qt.binding(() => topBarMouseArea.y + barUnitInset.y + topBarSlide.y);
region.width = Qt.binding(() => barUnitInset.width);
region.height = Qt.binding(() => barUnitInset.height);
region.radius = Qt.binding(() => barBackground.rt);
}
for (let i = 0; i < widgets.length; i++) {
region[`w${i}`] = widgets[i];
}
barWindow.BackgroundEffect.blurRegion = region;
barWindow.blurRegion = region;
} catch (e) {
console.warn("BarBlur: Failed to create blur region:", e);
}
}
function teardown() {
if (!barWindow.blurRegion)
return;
try {
barWindow.BackgroundEffect.blurRegion = null;
} catch (e) {}
barWindow.blurRegion.destroy();
barWindow.blurRegion = null;
}
onBarHasTransparencyChanged: _blurRebuildTimer.restart()
Connections {
target: BlurService
function onEnabledChanged() {
barBlur.rebuild();
}
}
Connections {
target: topBarSlide
function onXChanged() {
if (barWindow.blurRegion)
barWindow.blurRegion.changed();
}
function onYChanged() {
if (barWindow.blurRegion)
barWindow.blurRegion.changed();
}
}
Component.onCompleted: rebuild()
Component.onDestruction: teardown()
}
WlrLayershell.layer: dBarLayer
WlrLayershell.namespace: "dms:bar"
@@ -711,7 +817,8 @@ PanelWindow {
onHasActivePopoutChanged: evaluateReveal()
function updateActivePopoutState() {
if (!barWindow.screen) return;
if (!barWindow.screen)
return;
const screenName = barWindow.screen.name;
const activePopout = PopoutManager.currentPopoutsByScreen[screenName];
const activeTrayMenu = TrayMenuManager.activeTrayMenus[screenName];

View File

@@ -13,6 +13,7 @@ Item {
property real barThickness: 48
property real barSpacing: 4
property var barConfig: null
property var blurBarWindow: null
property bool overrideAxisLayout: false
property bool forceVerticalLayout: false
@@ -59,6 +60,7 @@ Item {
barThickness: root.barThickness
barSpacing: root.barSpacing
barConfig: root.barConfig
blurBarWindow: root.blurBarWindow
isFirst: index === 0
isLast: index === rowRepeater.count - 1
sectionSpacing: parent.rowSpacing
@@ -103,6 +105,7 @@ Item {
barThickness: root.barThickness
barSpacing: root.barSpacing
barConfig: root.barConfig
blurBarWindow: root.blurBarWindow
isFirst: index === 0
isLast: index === columnRepeater.count - 1
sectionSpacing: parent.columnSpacing

View File

@@ -13,6 +13,7 @@ Item {
property real barThickness: 48
property real barSpacing: 4
property var barConfig: null
property var blurBarWindow: null
property bool overrideAxisLayout: false
property bool forceVerticalLayout: false
@@ -61,6 +62,7 @@ Item {
barThickness: root.barThickness
barSpacing: root.barSpacing
barConfig: root.barConfig
blurBarWindow: root.blurBarWindow
isFirst: index === 0
isLast: index === rowRepeater.count - 1
sectionSpacing: parent.rowSpacing
@@ -105,6 +107,7 @@ Item {
barThickness: root.barThickness
barSpacing: root.barSpacing
barConfig: root.barConfig
blurBarWindow: root.blurBarWindow
isFirst: index === 0
isLast: index === columnRepeater.count - 1
sectionSpacing: parent.columnSpacing

View File

@@ -16,6 +16,7 @@ Loader {
property real barThickness: 48
property real barSpacing: 4
property var barConfig: null
property var blurBarWindow: null
property bool isFirst: false
property bool isLast: false
property real sectionSpacing: 0
@@ -92,6 +93,14 @@ Loader {
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "blurBarWindow" in root.item
property: "blurBarWindow"
value: root.blurBarWindow
restoreMode: Binding.RestoreNone
}
Binding {
target: root.item
when: root.item && "axis" in root.item

View File

@@ -630,7 +630,7 @@ BasePill {
if (appItem.isFocused && colorizeEnabled) {
return mouseArea.containsMouse ? Theme.withAlpha(Qt.lighter(appItem.activeOverlayColor, 1.3), 0.4) : Theme.withAlpha(appItem.activeOverlayColor, 0.3);
}
return mouseArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent";
return mouseArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent";
}
border.width: dragHandler.dragging ? 2 : 0

View File

@@ -1,4 +1,5 @@
import QtQuick
import Quickshell.Services.UPower
import qs.Common
import qs.Modules.Plugins
import qs.Services
@@ -10,6 +11,8 @@ BasePill {
property bool batteryPopupVisible: false
property var popoutTarget: null
property real touchpadAccumulator: 0
readonly property int barPosition: {
switch (axis?.edge) {
case "top":
@@ -119,5 +122,44 @@ BasePill {
battery.triggerRipple(this, mouse.x, mouse.y);
toggleBatteryPopup();
}
onWheel: wheel => {
var delta = wheel.angleDelta.y;
if (delta === 0)
return;
// Check if this is a touchpad
if (delta !== 120 && delta !== -120) {
touchpadAccumulator += delta;
console.info("Acc: "+touchpadAccumulator);
if (Math.abs(touchpadAccumulator) < 500)
return;
delta = touchpadAccumulator;
touchpadAccumulator = 0;
}
console.info("Trigger! Delta: "+delta)
// This is after the other delta checks so it only shows on valid Y scroll
if (typeof PowerProfiles === "undefined") {
ToastService.showError("power-profiles-daemon not available");
return;
}
// Get list of profiles, and current index
const profiles = [PowerProfile.PowerSaver, PowerProfile.Balanced].concat(PowerProfiles.hasPerformanceProfile ? [PowerProfile.Performance] : []);
var index = profiles.findIndex(profile => PowerProfiles.profile === profile);
// Step once based on mouse wheel direction
if (delta > 0) index += 1;
else index -= 1;
// Already at end of list, can't go further
if (index < 0 || index >= profiles.length) return;
// Set new profile
PowerProfiles.profile = profiles[index];
if (PowerProfiles.profile !== profiles[index]) {
ToastService.showError("Failed to set power profile");
}
}
}
}

View File

@@ -3,6 +3,7 @@ import Quickshell
import Quickshell.Wayland
import qs.Common
import qs.Modules.Plugins
import qs.Services
import qs.Widgets
BasePill {
@@ -93,6 +94,15 @@ BasePill {
PanelWindow {
id: contextMenuWindow
WindowBlur {
targetWindow: contextMenuWindow
blurX: menuContainer.x
blurY: menuContainer.y
blurWidth: contextMenuWindow.visible ? menuContainer.width : 0
blurHeight: contextMenuWindow.visible ? menuContainer.height : 0
blurRadius: Theme.cornerRadius
}
WlrLayershell.namespace: "dms:clipboard-context-menu"
property bool isVertical: false
@@ -187,8 +197,8 @@ BasePill {
height: Math.max(64, menuColumn.implicitHeight + Theme.spacingS * 2)
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
radius: Theme.cornerRadius
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1
border.color: BlurService.enabled ? BlurService.borderColor : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: BlurService.enabled ? BlurService.borderWidth : 1
opacity: contextMenuWindow.visible ? 1 : 0
visible: opacity > 0
@@ -224,7 +234,7 @@ BasePill {
width: parent.width
height: 30
radius: Theme.cornerRadius
color: clearAllArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent"
color: clearAllArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
Row {
anchors.fill: parent
@@ -264,7 +274,7 @@ BasePill {
width: parent.width
height: 30
radius: Theme.cornerRadius
color: savedItemsArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent"
color: savedItemsArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
Row {
anchors.fill: parent

View File

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

View File

@@ -17,7 +17,7 @@ BasePill {
property int availableWidth: 400
readonly property int maxNormalWidth: 456
readonly property int maxCompactWidth: 288
readonly property Toplevel activeWindow: ToplevelManager.activeToplevel
property Toplevel activeWindow: null
property var activeDesktopEntry: null
property bool isHovered: mouseArea.containsMouse
property bool isAutoHideBar: false
@@ -38,10 +38,44 @@ BasePill {
return 0;
}
function updateActiveWindow() {
const active = ToplevelManager.activeToplevel;
if (!active) {
// Only clear if our tracked window is no longer alive
if (activeWindow) {
const alive = ToplevelManager.toplevels?.values;
if (alive && !Array.from(alive).some(t => t === activeWindow))
activeWindow = null;
}
return;
}
if (!parentScreen || CompositorService.filterCurrentDisplay([active], parentScreen?.name)?.length > 0) {
activeWindow = active;
}
// else: active window is on a different screen so keep the previous value
}
Component.onCompleted: {
updateActiveWindow();
updateDesktopEntry();
}
Connections {
target: ToplevelManager
function onActiveToplevelChanged() {
root.updateActiveWindow();
}
}
Connections {
target: CompositorService
function onToplevelsChanged() {
root.updateActiveWindow();
}
}
Connections {
target: DesktopEntries
function onApplicationsChanged() {

View File

@@ -99,7 +99,7 @@ BasePill {
if (isMouseWheelY) {
if (deltaY > 0) {
activePlayer.previous();
MprisController.previousOrRewind();
} else {
activePlayer.next();
}
@@ -107,7 +107,7 @@ BasePill {
scrollAccumulatorY += deltaY;
if (Math.abs(scrollAccumulatorY) >= touchpadThreshold) {
if (scrollAccumulatorY > 0) {
activePlayer.previous();
MprisController.previousOrRewind();
} else {
activePlayer.next();
}
@@ -214,7 +214,7 @@ BasePill {
if (mouse.button === Qt.LeftButton) {
activePlayer.togglePlaying();
} else if (mouse.button === Qt.MiddleButton) {
activePlayer.previous();
MprisController.previousOrRewind();
} else if (mouse.button === Qt.RightButton) {
activePlayer.next();
}
@@ -354,7 +354,7 @@ BasePill {
height: 20
radius: 10
anchors.verticalCenter: parent.verticalCenter
color: prevArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent"
color: prevArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
visible: root.playerAvailable
opacity: (activePlayer && activePlayer.canGoPrevious) ? 1 : 0.3
@@ -370,11 +370,7 @@ BasePill {
anchors.fill: parent
enabled: root.playerAvailable
cursorShape: Qt.PointingHandCursor
onClicked: {
if (activePlayer) {
activePlayer.previous();
}
}
onClicked: MprisController.previousOrRewind()
}
}
@@ -411,7 +407,7 @@ BasePill {
height: 20
radius: 10
anchors.verticalCenter: parent.verticalCenter
color: nextArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent"
color: nextArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
visible: playerAvailable
opacity: (activePlayer && activePlayer.canGoNext) ? 1 : 0.3

View File

@@ -285,7 +285,7 @@ BasePill {
width: parent.width
height: 30
radius: Theme.cornerRadius
color: tabArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent"
color: tabArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
Row {
anchors.fill: parent
@@ -327,7 +327,7 @@ BasePill {
width: parent.width
height: 30
radius: Theme.cornerRadius
color: newNoteArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent"
color: newNoteArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
Row {
anchors.fill: parent

View File

@@ -273,7 +273,7 @@ BasePill {
if (isFocused) {
return mouseArea.containsMouse ? Theme.primarySelected : Theme.withAlpha(Theme.primary, 0.2);
}
return mouseArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent";
return mouseArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent";
}
// App icon
@@ -528,7 +528,7 @@ BasePill {
if (isFocused) {
return mouseArea.containsMouse ? Theme.primarySelected : Theme.withAlpha(Theme.primary, 0.2);
}
return mouseArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent";
return mouseArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent";
}
IconImage {
@@ -738,6 +738,15 @@ BasePill {
sourceComponent: PanelWindow {
id: contextMenuWindow
WindowBlur {
targetWindow: contextMenuWindow
blurX: contextMenuRect.x
blurY: contextMenuRect.y
blurWidth: contextMenuWindow.isVisible ? contextMenuRect.width : 0
blurHeight: contextMenuWindow.isVisible ? contextMenuRect.height : 0
blurRadius: Theme.cornerRadius
}
property var currentWindow: null
property bool isVisible: false
property point anchorPos: Qt.point(0, 0)
@@ -830,6 +839,7 @@ BasePill {
}
Rectangle {
id: contextMenuRect
x: {
if (contextMenuWindow.isVertical) {
if (contextMenuWindow.edge === "left") {
@@ -858,13 +868,13 @@ BasePill {
height: 32
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
radius: Theme.cornerRadius
border.width: 1
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: BlurService.enabled ? BlurService.borderWidth : 1
border.color: BlurService.enabled ? BlurService.borderColor : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
Rectangle {
anchors.fill: parent
radius: parent.radius
color: closeMouseArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent"
color: closeMouseArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
}
StyledText {

View File

@@ -287,7 +287,7 @@ BasePill {
height: root.trayItemSize
anchors.centerIn: parent
radius: Theme.cornerRadius
color: trayItemArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent"
color: trayItemArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
border.width: dragHandler.dragging ? 2 : 0
border.color: Theme.primary
opacity: dragHandler.dragging ? 0.8 : 1.0
@@ -425,7 +425,7 @@ BasePill {
height: root.trayItemSize
anchors.centerIn: parent
radius: Theme.cornerRadius
color: caretArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent"
color: caretArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
DankIcon {
anchors.centerIn: parent
@@ -547,7 +547,7 @@ BasePill {
height: root.trayItemSize
anchors.centerIn: parent
radius: Theme.cornerRadius
color: trayItemArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent"
color: trayItemArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
border.width: dragHandler.dragging ? 2 : 0
border.color: Theme.primary
opacity: dragHandler.dragging ? 0.8 : 1.0
@@ -685,7 +685,7 @@ BasePill {
height: root.trayItemSize
anchors.centerIn: parent
radius: Theme.cornerRadius
color: caretAreaVert.containsMouse ? Theme.widgetBaseHoverColor : "transparent"
color: caretAreaVert.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
DankIcon {
anchors.centerIn: parent
@@ -723,6 +723,16 @@ BasePill {
PanelWindow {
id: overflowMenu
WindowBlur {
targetWindow: overflowMenu
blurX: menuContainer.x
blurY: menuContainer.y
blurWidth: root.menuOpen ? menuContainer.width : 0
blurHeight: root.menuOpen ? menuContainer.height : 0
blurRadius: Theme.cornerRadius
}
visible: root.menuOpen
screen: root.parentScreen
WlrLayershell.layer: WlrLayershell.Top
@@ -990,6 +1000,15 @@ BasePill {
layer.samples: 4
}
Rectangle {
anchors.fill: parent
color: "transparent"
radius: Theme.cornerRadius
border.color: BlurService.borderColor
border.width: BlurService.borderWidth
z: 100
}
Grid {
id: menuGrid
anchors.centerIn: parent
@@ -1030,7 +1049,7 @@ BasePill {
width: root.trayItemSize + 4
height: root.trayItemSize + 4
radius: Theme.cornerRadius
color: itemArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.withAlpha(Theme.surfaceContainer, 0)
color: itemArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : Theme.withAlpha(Theme.surfaceContainer, 0)
IconImage {
id: menuIconImg
@@ -1191,6 +1210,15 @@ BasePill {
PanelWindow {
id: menuWindow
WindowBlur {
targetWindow: menuWindow
blurX: trayMenuContainer.x
blurY: trayMenuContainer.y
blurWidth: menuRoot.showMenu ? trayMenuContainer.width : 0
blurHeight: menuRoot.showMenu ? trayMenuContainer.height : 0
blurRadius: Theme.cornerRadius
}
WlrLayershell.namespace: "dms:tray-menu-window"
visible: menuRoot.showMenu && (menuRoot.trayItem?.hasMenu ?? false)
WlrLayershell.layer: WlrLayershell.Top
@@ -1302,7 +1330,7 @@ BasePill {
onClicked: mouse => {
const clickX = mouse.x + menuWindow.maskX;
const clickY = mouse.y + menuWindow.maskY;
const outsideContent = clickX < menuContainer.x || clickX > menuContainer.x + menuContainer.width || clickY < menuContainer.y || clickY > menuContainer.y + menuContainer.height;
const outsideContent = clickX < trayMenuContainer.x || clickX > trayMenuContainer.x + trayMenuContainer.width || clickY < trayMenuContainer.y || clickY > trayMenuContainer.y + trayMenuContainer.height;
if (!outsideContent)
return;
@@ -1360,7 +1388,7 @@ BasePill {
}
Item {
id: menuContainer
id: trayMenuContainer
readonly property real rawWidth: Math.min(500, Math.max(250, menuColumn.implicitWidth + Theme.spacingS * 2))
readonly property real rawHeight: Math.max(40, menuColumn.implicitHeight + Theme.spacingS * 2)
@@ -1438,6 +1466,15 @@ BasePill {
layer.textureMirroring: ShaderEffectSource.MirrorVertically
}
Rectangle {
anchors.fill: parent
color: "transparent"
radius: Theme.cornerRadius
border.color: BlurService.borderColor
border.width: BlurService.borderWidth
z: 100
}
QsMenuAnchor {
id: submenuHydrator
anchor.window: menuWindow
@@ -1470,7 +1507,7 @@ BasePill {
width: parent.width
height: 28
radius: Theme.cornerRadius
color: visibilityToggleArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.withAlpha(Theme.surfaceContainer, 0)
color: visibilityToggleArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : Theme.withAlpha(Theme.surfaceContainer, 0)
StyledText {
anchors.left: parent.left
@@ -1523,7 +1560,7 @@ BasePill {
width: parent.width
height: 28
radius: Theme.cornerRadius
color: backArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.withAlpha(Theme.surfaceContainer, 0)
color: backArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : Theme.withAlpha(Theme.surfaceContainer, 0)
Row {
anchors.left: parent.left
@@ -1574,7 +1611,7 @@ BasePill {
color: {
if (menuEntry?.isSeparator)
return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2);
return itemArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.withAlpha(Theme.surfaceContainer, 0);
return itemArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : Theme.withAlpha(Theme.surfaceContainer, 0);
}
MouseArea {

View File

@@ -17,8 +17,49 @@ Item {
property real widgetHeight: 30
property real barThickness: 48
property var barConfig: null
property var blurBarWindow: null
property var hyprlandOverviewLoader: 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
readonly property var sortedToplevels: {
return CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, screenName);
@@ -538,6 +579,60 @@ 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) {
if (useExtWorkspace) {
const realWorkspaces = getRealWorkspaces();
@@ -751,8 +846,15 @@ Item {
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton
id: edgeMouseArea
z: -1
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 mouseAccumulator: 0
@@ -765,12 +867,20 @@ Item {
}
onClicked: mouse => {
if (mouse.button === Qt.RightButton) {
const rootPos = edgeMouseArea.mapToItem(root, mouse.x, mouse.y);
switch (mouse.button) {
case Qt.RightButton:
if (CompositorService.isNiri) {
NiriService.toggleOverview();
} else if (CompositorService.isHyprland && root.hyprlandOverviewLoader?.item) {
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;
}
}
@@ -1845,5 +1955,27 @@ Item {
if (useExtWorkspace && !DMSService.activeSubscriptions.includes("extworkspace")) {
DMSService.addSubscription("extworkspace");
}
_updateBlurRegistration();
}
property bool _blurRegistered: false
readonly property bool _shouldBlur: BlurService.enabled && blurBarWindow && blurBarWindow.registerBlurWidget && !(barConfig?.noBackground ?? false) && root.visible && root.width > 0
on_ShouldBlurChanged: _updateBlurRegistration()
function _updateBlurRegistration() {
if (_shouldBlur && !_blurRegistered) {
blurBarWindow.registerBlurWidget(visualBackground);
_blurRegistered = true;
} else if (!_shouldBlur && _blurRegistered) {
if (blurBarWindow && blurBarWindow.unregisterBlurWidget)
blurBarWindow.unregisterBlurWidget(visualBackground);
_blurRegistered = false;
}
}
Component.onDestruction: {
if (_blurRegistered && blurBarWindow && blurBarWindow.unregisterBlurWidget)
blurBarWindow.unregisterBlurWidget(visualBackground);
}
}

View File

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

View File

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

View File

@@ -17,6 +17,15 @@ Variants {
delegate: PanelWindow {
id: dock
WindowBlur {
targetWindow: dock
blurX: dockBackground.x + dockContainer.x + dockMouseArea.x + dockCore.x + dockSlide.x
blurY: dockBackground.y + dockContainer.y + dockMouseArea.y + dockCore.y + dockSlide.y
blurWidth: dock.hasApps && dock.reveal ? dockBackground.width : 0
blurHeight: dock.hasApps && dock.reveal ? dockBackground.height : 0
blurRadius: Theme.cornerRadius
}
WlrLayershell.namespace: "dms:dock"
readonly property bool isVertical: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right
@@ -562,6 +571,15 @@ Variants {
color: Theme.withAlpha(Theme.surfaceContainer, backgroundTransparency)
radius: Theme.cornerRadius
}
Rectangle {
anchors.fill: parent
color: "transparent"
radius: Theme.cornerRadius
border.color: BlurService.borderColor
border.width: BlurService.borderWidth
z: 100
}
}
Shape {

View File

@@ -9,6 +9,15 @@ import qs.Widgets
PanelWindow {
id: root
WindowBlur {
targetWindow: root
blurX: menuContainer.x
blurY: menuContainer.y
blurWidth: root.visible ? menuContainer.width : 0
blurHeight: root.visible ? menuContainer.height : 0
blurRadius: Theme.cornerRadius
}
WlrLayershell.namespace: "dms:dock-context-menu"
property var appData: null
@@ -168,8 +177,8 @@ PanelWindow {
height: menuColumn.implicitHeight + Theme.spacingS * 2
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
radius: Theme.cornerRadius
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1
border.color: BlurService.enabled ? BlurService.borderColor : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: BlurService.enabled ? BlurService.borderWidth : 1
opacity: root.visible ? 1 : 0
visible: opacity > 0

View File

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

View File

@@ -1,6 +1,5 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import Quickshell
import Quickshell.Wayland
import Quickshell.Services.Notifications
@@ -11,6 +10,15 @@ import qs.Widgets
PanelWindow {
id: win
WindowBlur {
targetWindow: win
blurX: content.x + content.cardInset + swipeTx.x + tx.x
blurY: content.y + content.cardInset + swipeTx.y + tx.y
blurWidth: !win._finalized ? Math.max(0, content.width - content.cardInset * 2) : 0
blurHeight: !win._finalized ? Math.max(0, content.height - content.cardInset * 2) : 0
blurRadius: Theme.cornerRadius
}
WlrLayershell.namespace: "dms:notification-popup"
required property var notificationData
@@ -436,6 +444,16 @@ PanelWindow {
}
}
Rectangle {
anchors.fill: parent
anchors.margins: content.cardInset
radius: Theme.cornerRadius
color: "transparent"
border.color: BlurService.borderColor
border.width: BlurService.borderWidth
z: 100
}
Item {
id: backgroundContainer
anchors.fill: parent

View File

@@ -14,6 +14,7 @@ Item {
property real barThickness: 48
property real barSpacing: 4
property var barConfig: null
property var blurBarWindow: null
property alias content: contentLoader.sourceComponent
property bool isVerticalOrientation: axis?.isVertical ?? false
property bool isFirst: false
@@ -106,7 +107,7 @@ Item {
const rawTransparency = (root.barConfig && root.barConfig.widgetTransparency !== undefined) ? root.barConfig.widgetTransparency : 1.0;
const isHovered = root.enableBackgroundHover && (mouseArea.containsMouse || (root.isHovered || false));
const transparency = isHovered ? Math.max(0.3, rawTransparency) : rawTransparency;
const baseColor = isHovered ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor;
const baseColor = isHovered ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : Theme.widgetBaseBackgroundColor;
if (Theme.widgetBackgroundHasAlpha) {
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * transparency);
@@ -169,4 +170,26 @@ Item {
root.wheel(wheelEvent);
}
}
property bool _blurRegistered: false
readonly property bool _shouldBlur: BlurService.enabled && blurBarWindow && blurBarWindow.registerBlurWidget && !(barConfig?.noBackground ?? false) && root.visible && root.width > 0
on_ShouldBlurChanged: _updateBlurRegistration()
function _updateBlurRegistration() {
if (_shouldBlur && !_blurRegistered) {
blurBarWindow.registerBlurWidget(visualContent);
_blurRegistered = true;
} else if (!_shouldBlur && _blurRegistered) {
if (blurBarWindow && blurBarWindow.unregisterBlurWidget)
blurBarWindow.unregisterBlurWidget(visualContent);
_blurRegistered = false;
}
}
Component.onCompleted: _updateBlurRegistration()
Component.onDestruction: {
if (_blurRegistered && blurBarWindow && blurBarWindow.unregisterBlurWidget)
blurBarWindow.unregisterBlurWidget(visualContent);
}
}

View File

@@ -14,6 +14,7 @@ Item {
property real barThickness: 48
property real barSpacing: 4
property var barConfig: null
property var blurBarWindow: null
property string pluginId: ""
property var pluginService: null
@@ -182,6 +183,7 @@ Item {
barThickness: root.barThickness
barSpacing: root.barSpacing
barConfig: root.barConfig
blurBarWindow: root.blurBarWindow
content: root.horizontalBarPill
states: State {
@@ -241,6 +243,7 @@ Item {
barThickness: root.barThickness
barSpacing: root.barSpacing
barConfig: root.barConfig
blurBarWindow: root.blurBarWindow
content: root.verticalBarPill
isVerticalOrientation: true

View File

@@ -2,6 +2,7 @@ import QtQuick
import QtQuick.Controls
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
Popup {
@@ -186,8 +187,8 @@ Popup {
contentItem: Rectangle {
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
radius: Theme.cornerRadius
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1
border.color: BlurService.enabled ? BlurService.borderColor : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: BlurService.enabled ? BlurService.borderWidth : 1
Item {
id: keyboardHandler

View File

@@ -125,6 +125,15 @@ Item {
return Theme.warning;
}
function openBlurBorderColorPicker() {
PopoutService.colorPickerModal.selectedColor = SettingsData.blurBorderCustomColor ?? "#ffffff";
PopoutService.colorPickerModal.pickerTitle = I18n.tr("Blur Border Color");
PopoutService.colorPickerModal.onColorSelectedCallback = function (color) {
SettingsData.set("blurBorderCustomColor", color.toString());
};
PopoutService.colorPickerModal.open();
}
function openM3ShadowColorPicker() {
PopoutService.colorPickerModal.selectedColor = SettingsData.m3ElevationCustomColor ?? "#000000";
PopoutService.colorPickerModal.pickerTitle = I18n.tr("Shadow Color");
@@ -1816,6 +1825,77 @@ Item {
}
}
SettingsCard {
tab: "theme"
tags: ["blur", "background", "transparency", "glass", "frosted"]
title: I18n.tr("Background Blur")
settingKey: "blurEnabled"
iconName: "blur_on"
SettingsToggleRow {
tab: "theme"
tags: ["blur", "background", "transparency", "glass", "frosted"]
settingKey: "blurEnabled"
text: I18n.tr("Background Blur")
description: BlurService.available ? I18n.tr("Blur the background behind bars, popouts, modals, and notifications. Requires compositor support and configuration.") : I18n.tr("Requires a newer version of Quickshell")
checked: SettingsData.blurEnabled ?? false
enabled: BlurService.available
onToggled: checked => SettingsData.set("blurEnabled", checked)
}
SettingsDropdownRow {
tab: "theme"
tags: ["blur", "border", "outline", "edge"]
settingKey: "blurBorderColor"
text: I18n.tr("Blur Border Color")
description: I18n.tr("Border color around blurred surfaces")
visible: SettingsData.blurEnabled
options: [I18n.tr("Outline", "blur border color"), I18n.tr("Primary", "blur border color"), I18n.tr("Secondary", "blur border color"), I18n.tr("Text Color", "blur border color"), I18n.tr("Custom", "blur border color")]
currentValue: {
switch (SettingsData.blurBorderColor) {
case "primary":
return I18n.tr("Primary", "blur border color");
case "secondary":
return I18n.tr("Secondary", "blur border color");
case "surfaceText":
return I18n.tr("Text Color", "blur border color");
case "custom":
return I18n.tr("Custom", "blur border color");
default:
return I18n.tr("Outline", "blur border color");
}
}
onValueChanged: value => {
if (value === I18n.tr("Primary", "blur border color")) {
SettingsData.set("blurBorderColor", "primary");
} else if (value === I18n.tr("Secondary", "blur border color")) {
SettingsData.set("blurBorderColor", "secondary");
} else if (value === I18n.tr("Text Color", "blur border color")) {
SettingsData.set("blurBorderColor", "surfaceText");
} else if (value === I18n.tr("Custom", "blur border color")) {
SettingsData.set("blurBorderColor", "custom");
openBlurBorderColorPicker();
} else {
SettingsData.set("blurBorderColor", "outline");
}
}
}
SettingsSliderRow {
tab: "theme"
tags: ["blur", "border", "opacity"]
settingKey: "blurBorderOpacity"
text: I18n.tr("Blur Border Opacity")
visible: SettingsData.blurEnabled
value: Math.round((SettingsData.blurBorderOpacity ?? 1.0) * 100)
minimum: 0
maximum: 100
unit: "%"
defaultValue: 100
onSliderValueChanged: newValue => SettingsData.set("blurBorderOpacity", newValue / 100)
}
}
SettingsCard {
tab: "theme"
tags: ["niri", "layout", "gaps", "radius", "window", "border"]
@@ -2602,7 +2682,6 @@ Item {
onToggled: checked => SettingsData.set("matugenTemplateNeovim", checked)
}
SettingsDropdownRow {
text: I18n.tr("Dark mode base")
tab: "theme"

View File

@@ -1,6 +1,5 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Common
import qs.Widgets
import qs.Services
@@ -52,15 +51,14 @@ Column {
height: implicitHeight
spacing: Theme.spacingM
RowLayout {
width: parent.width
Row {
spacing: Theme.spacingM
DankIcon {
name: root.titleIcon
size: Theme.iconSize
color: Theme.primary
Layout.alignment: Qt.AlignVCenter
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
@@ -68,7 +66,7 @@ Column {
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
Layout.alignment: Qt.AlignVCenter
anchors.verticalCenter: parent.verticalCenter
}
}
@@ -981,10 +979,26 @@ Column {
Repeater {
model: [
{ label: I18n.tr("Percentage"), mode: 0, icon: "percent" },
{ 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" }
{
label: I18n.tr("Percentage"),
mode: 0,
icon: "percent"
},
{
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 {
@@ -1316,20 +1330,7 @@ Column {
id: longestControlCenterLabelMetrics
font.pixelSize: Theme.fontSizeSmall
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 = "";
for (let i = 0; i < labels.length; i++) {
if (labels[i].length > longest.length)
@@ -1340,6 +1341,7 @@ Column {
}
Repeater {
id: groupRepeater
model: controlCenterContextMenu.controlCenterGroups
delegate: Item {
@@ -1569,8 +1571,6 @@ Column {
}
}
}
id: groupRepeater
}
}
}

View File

@@ -0,0 +1,113 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Wayland // ! Import is needed despite what qmlls says
import qs.Common
Singleton {
id: root
property bool quickshellSupported: false
property bool compositorSupported: false
property bool available: quickshellSupported && compositorSupported
readonly property bool enabled: available && (SettingsData.blurEnabled ?? false)
readonly property color borderColor: {
if (!enabled)
return "transparent";
const opacity = SettingsData.blurBorderOpacity ?? 0.5;
switch (SettingsData.blurBorderColor ?? "outline") {
case "primary":
return Theme.withAlpha(Theme.primary, opacity);
case "secondary":
return Theme.withAlpha(Theme.secondary, opacity);
case "surfaceText":
return Theme.withAlpha(Theme.surfaceText, opacity);
case "custom":
return Theme.withAlpha(SettingsData.blurBorderCustomColor ?? "#ffffff", opacity);
default:
return Theme.withAlpha(Theme.outline, opacity);
}
}
readonly property int borderWidth: enabled ? 1 : 0
function hoverColor(baseColor, hoverAlpha) {
if (!enabled)
return baseColor;
return Theme.withAlpha(baseColor, hoverAlpha ?? 0.15);
}
function createBlurRegion(targetWindow) {
if (!available)
return null;
try {
const region = Qt.createQmlObject(`
import Quickshell
Region {}
`, targetWindow, "BlurRegion");
targetWindow.BackgroundEffect.blurRegion = region;
return region;
} catch (e) {
console.warn("BlurService: Failed to create blur region:", e);
return null;
}
}
function reapplyBlurRegion(targetWindow, region) {
if (!region || !available)
return;
try {
targetWindow.BackgroundEffect.blurRegion = region;
region.changed();
} catch (e) {}
}
function destroyBlurRegion(targetWindow, region) {
if (!region)
return;
try {
targetWindow.BackgroundEffect.blurRegion = null;
} catch (e) {}
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: {
try {
const test = Qt.createQmlObject(`
import Quickshell
Region { radius: 0 }
`, root, "BlurAvailabilityTest");
test.destroy();
quickshellSupported = true;
console.info("BlurService: Quickshell blur support available");
blurProbe.running = true;
} catch (e) {
console.info("BlurService: BackgroundEffect not available - blur disabled. Requires a newer version of Quickshell.");
}
}
}

View File

@@ -255,6 +255,12 @@ Singleton {
return pinnedEntries.some(pinnedEntry => pinnedEntry.hash === entryHash);
}
onClipboardAvailableChanged: {
if (!clipboardAvailable || refCount <= 0)
return;
refresh();
}
Connections {
target: DMSService
enabled: root.refCount > 0

View File

@@ -10,4 +10,20 @@ Singleton {
readonly property list<MprisPlayer> availablePlayers: Mpris.players.values
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)
height: root.buttonHeight
color: selected ? Theme.buttonBg : Theme.surfaceVariant
color: selected ? Theme.buttonBg : Theme.withAlpha(Theme.surfaceVariant, Theme.popupTransparency)
border.color: "transparent"
border.width: 0

View File

@@ -107,6 +107,16 @@ PanelWindow {
}
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
WindowBlur {
targetWindow: root
blurX: shadowBuffer
blurY: shadowBuffer
blurWidth: shouldBeVisible ? alignedWidth : 0
blurHeight: shouldBeVisible ? alignedHeight : 0
blurRadius: Theme.cornerRadius
}
color: "transparent"
readonly property real dpr: CompositorService.getScreenScale(screen)
@@ -256,15 +266,15 @@ PanelWindow {
scale: shouldBeVisible ? 1 : 0.9
property bool childHovered: false
readonly property real popupSurfaceAlpha: SettingsData.popupTransparency
readonly property real popupSurfaceAlpha: Theme.popupTransparency
Rectangle {
id: background
anchors.fill: parent
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainer, osdContainer.popupSurfaceAlpha)
border.color: Theme.outlineMedium
border.width: 1
border.color: BlurService.enabled ? BlurService.borderColor : Theme.outlineMedium
border.width: BlurService.enabled ? BlurService.borderWidth : 1
z: -1
}
@@ -276,7 +286,7 @@ PanelWindow {
level: Theme.elevationLevel3
fallbackOffset: 6
targetRadius: Theme.cornerRadius
targetColor: Theme.surfaceContainer
targetColor: Theme.withAlpha(Theme.surfaceContainer, osdContainer.popupSurfaceAlpha)
borderColor: Theme.outlineMedium
borderWidth: 1
shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"

View File

@@ -398,6 +398,17 @@ Item {
visible: false
color: "transparent"
WindowBlur {
id: popoutBlur
targetWindow: contentWindow
readonly property real s: Math.min(1, contentContainer.scaleValue)
blurX: contentContainer.x + contentContainer.width * (1 - s) * 0.5 + Theme.snap(contentContainer.animX, root.dpr)
blurY: contentContainer.y + contentContainer.height * (1 - s) * 0.5 + Theme.snap(contentContainer.animY, root.dpr)
blurWidth: (shouldBeVisible && contentWrapper.opacity > 0) ? contentContainer.width * s : 0
blurHeight: (shouldBeVisible && contentWrapper.opacity > 0) ? contentContainer.height * s : 0
blurRadius: Theme.cornerRadius
}
WlrLayershell.namespace: root.layerNamespace
WlrLayershell.layer: {
switch (Quickshell.env("DMS_POPOUT_LAYER")) {
@@ -565,14 +576,6 @@ Item {
}
}
Rectangle {
anchors.fill: parent
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
border.color: Theme.outlineMedium
border.width: 0
}
Loader {
id: contentLoader
anchors.fill: parent
@@ -580,6 +583,21 @@ Item {
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 {

View File

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

View File

@@ -0,0 +1,65 @@
import QtQuick
import qs.Services
Item {
id: root
visible: false
required property var targetWindow
property var blurItem: null
property real blurX: 0
property real blurY: 0
property real blurWidth: 0
property real blurHeight: 0
property real blurRadius: 0
property var _region: null
function _apply() {
if (!BlurService.enabled || !targetWindow) {
_cleanup();
return;
}
if (!_region)
_region = BlurService.createBlurRegion(targetWindow);
if (!_region)
return;
_region.item = Qt.binding(() => root.blurItem);
_region.x = Qt.binding(() => root.blurX);
_region.y = Qt.binding(() => root.blurY);
_region.width = Qt.binding(() => root.blurWidth);
_region.height = Qt.binding(() => root.blurHeight);
_region.radius = Qt.binding(() => root.blurRadius);
}
function _cleanup() {
if (!_region)
return;
BlurService.destroyBlurRegion(targetWindow, _region);
_region = null;
}
Connections {
target: BlurService
function onEnabledChanged() {
root._apply();
}
}
Connections {
target: root.targetWindow
function onVisibleChanged() {
if (root.targetWindow && root.targetWindow.visible) {
root._region = null;
root._apply();
}
}
}
Component.onCompleted: _apply()
Component.onDestruction: _cleanup()
}

File diff suppressed because it is too large Load Diff

View File

@@ -86,13 +86,13 @@ def create_poeditor_json(translations):
references.append(ref)
contexts = sorted(data['contexts']) if data['contexts'] else []
context_str = " | ".join(contexts) if contexts else term
comment = " | ".join(contexts) if contexts else ""
entry = {
"term": term,
"context": context_str,
"context": term,
"reference": ", ".join(references),
"comment": ""
"comment": comment
}
poeditor_data.append(entry)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -2112,6 +2112,26 @@
],
"icon": "history"
},
{
"section": "rememberLastQuery",
"label": "Remember Last Query",
"tabIndex": 9,
"category": "Launcher",
"keywords": [
"autofill",
"drawer",
"last",
"launcher",
"menu",
"opened",
"query",
"remember",
"remembered",
"search",
"start"
],
"description": "Autofill last remembered query when opened"
},
{
"section": "searchAppActions",
"label": "Search App Actions",
@@ -2378,6 +2398,47 @@
],
"icon": "schedule"
},
{
"section": "blurEnabled",
"label": "Background Blur",
"tabIndex": 10,
"category": "Theme & Colors",
"keywords": [
"alert",
"alerts",
"appearance",
"background",
"bars",
"behind",
"blur",
"colors",
"compositor",
"config",
"configuration",
"configure",
"frosted",
"glass",
"look",
"modals",
"notif",
"notifications",
"notifs",
"panel",
"popouts",
"requires",
"scheme",
"setup",
"statusbar",
"style",
"support",
"taskbar",
"theme",
"topbar",
"transparency"
],
"icon": "blur_on",
"description": "Blur the background behind bars, popouts, modals, and notifications. Requires compositor support and configuration."
},
{
"section": "barElevationEnabled",
"label": "Bar Shadows",
@@ -2405,6 +2466,49 @@
],
"description": "Shadow elevation on bars and panels"
},
{
"section": "blurBorderColor",
"label": "Blur Border Color",
"tabIndex": 10,
"category": "Theme & Colors",
"keywords": [
"appearance",
"around",
"blur",
"blurred",
"border",
"color",
"colors",
"colour",
"edge",
"hue",
"look",
"outline",
"scheme",
"style",
"surfaces",
"theme",
"tint"
],
"description": "Border color around blurred surfaces"
},
{
"section": "blurBorderOpacity",
"label": "Blur Border Opacity",
"tabIndex": 10,
"category": "Theme & Colors",
"keywords": [
"appearance",
"blur",
"border",
"colors",
"look",
"opacity",
"scheme",
"style",
"theme"
]
},
{
"section": "niriLayoutBorderSize",
"label": "Border Size",
@@ -4384,27 +4488,6 @@
],
"description": "Automatically lock the screen when DMS starts"
},
{
"section": "lockBeforeSuspend",
"label": "Lock before suspend",
"tabIndex": 11,
"category": "Lock Screen",
"keywords": [
"automatic",
"automatically",
"before",
"lock",
"login",
"password",
"prepares",
"screen",
"security",
"sleep",
"suspend",
"system"
],
"description": "Automatically lock the screen when the system prepares to suspend"
},
{
"section": "lockScreenNotificationMode",
"label": "Notification Display",
@@ -6407,6 +6490,27 @@
"icon": "schedule",
"description": "Gradually fade the screen before locking with a configurable grace period"
},
{
"section": "lockBeforeSuspend",
"label": "Lock before suspend",
"tabIndex": 21,
"category": "Power & Sleep",
"keywords": [
"automatically",
"before",
"energy",
"lock",
"power",
"prepares",
"screen",
"security",
"shutdown",
"sleep",
"suspend",
"system"
],
"description": "Automatically lock the screen when the system prepares to suspend"
},
{
"section": "fadeToLockGracePeriod",
"label": "Lock fade grace period",

View File

@@ -1182,6 +1182,13 @@
"reference": "",
"comment": ""
},
{
"term": "Applying authentication changes…",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Apps",
"translation": "",
@@ -1378,6 +1385,34 @@
"reference": "",
"comment": ""
},
{
"term": "Authentication changes applied.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Authentication changes apply automatically.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Authentication changes apply automatically. Fingerprint-only login may not unlock Keyring.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Authentication changes need sudo. Opening terminal so you can use password or fingerprint.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Authentication error - try again",
"translation": "",
@@ -1518,6 +1553,13 @@
"reference": "",
"comment": ""
},
{
"term": "Autofill last remembered query when opened",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Automatic Color Mode",
"translation": "",
@@ -1672,6 +1714,13 @@
"reference": "",
"comment": ""
},
{
"term": "Background Blur",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Background Opacity",
"translation": "",
@@ -1679,6 +1728,13 @@
"reference": "",
"comment": ""
},
{
"term": "Background authentication sync failed. Trying terminal mode.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Background image",
"translation": "",
@@ -1875,6 +1931,20 @@
"reference": "",
"comment": ""
},
{
"term": "Blur Border Color",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Blur Border Opacity",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Blur Wallpaper Layer",
"translation": "",
@@ -1889,6 +1959,13 @@
"reference": "",
"comment": ""
},
{
"term": "Blur the background behind bars, popouts, modals, and notifications. Requires compositor support and configuration.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Blur wallpaper when niri overview is open",
"translation": "",
@@ -1938,6 +2015,13 @@
"reference": "",
"comment": ""
},
{
"term": "Border color around blurred surfaces",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Border with BG",
"translation": "",
@@ -2289,7 +2373,7 @@
"comment": ""
},
{
"term": "Check sync status on demand. Sync copies your theme, settings, PAM config, and wallpaper to the login screen in one step. Must run Sync to apply changes.",
"term": "Check sync status on demand. Sync copies your theme, settings, and wallpaper configuration to the login screen. Authentication changes apply automatically.",
"translation": "",
"context": "",
"reference": "",
@@ -3544,7 +3628,7 @@
{
"term": "Custom",
"translation": "",
"context": "shadow color option | theme category option",
"context": "blur border color | shadow color option | theme category option",
"reference": "",
"comment": ""
},
@@ -3717,7 +3801,7 @@
"comment": ""
},
{
"term": "DMS greeter needs: greetd, dms-greeter. Fingerprint: fprintd, pam_fprintd. Security keys: pam_u2f. Add your user to the greeter group. Sync checks sudo first and opens a terminal when interactive authentication is required.",
"term": "DMS greeter needs: greetd, dms-greeter. Fingerprint: fprintd, pam_fprintd. Security keys: pam_u2f. Add your user to the greeter group. Authentication changes apply automatically and may open a terminal when sudo authentication is required.",
"translation": "",
"context": "",
"reference": "",
@@ -4788,7 +4872,7 @@
"comment": ""
},
{
"term": "Enable fingerprint or security key for DMS Greeter. Run Sync to apply and configure PAM.",
"term": "Enable fingerprint or security key for DMS Greeter. Authentication changes apply automatically.",
"translation": "",
"context": "",
"reference": "",
@@ -4836,6 +4920,13 @@
"reference": "",
"comment": ""
},
{
"term": "Enabled, but no prints are enrolled yet. Authentication changes apply automatically once you enroll fingerprints.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Enabled, but no prints are enrolled yet. Enroll fingerprints and run Sync.",
"translation": "",
@@ -4844,7 +4935,7 @@
"comment": ""
},
{
"term": "Enabled, but no prints are enrolled yet. Enroll fingerprints to use it.",
"term": "Enabled, but no registered security key was found yet. Authentication changes apply automatically once your key is registered or your U2F config is updated.",
"translation": "",
"context": "",
"reference": "",
@@ -4857,13 +4948,6 @@
"reference": "",
"comment": ""
},
{
"term": "Enabled, but no registered security key was found yet. Register a key or update your U2F config.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Enabled, but security-key availability could not be confirmed.",
"translation": "",
@@ -5809,6 +5893,27 @@
"reference": "",
"comment": ""
},
{
"term": "Fingerprint error",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Fingerprint error: %1",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Fingerprint not recognized (%1/%2). Please try again or use password.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Fingerprint reader detected, but no prints are enrolled yet. You can enable this now and enroll later.",
"translation": "",
@@ -7139,6 +7244,13 @@
"reference": "",
"comment": ""
},
{
"term": "Incorrect password - try again",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Indicator Style",
"translation": "",
@@ -7202,6 +7314,13 @@
"reference": "",
"comment": ""
},
{
"term": "Insert your security key...",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Install",
"translation": "",
@@ -7930,6 +8049,13 @@
"reference": "",
"comment": ""
},
{
"term": "Lock screen authentication changes apply automatically and may open a terminal when sudo authentication is required.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Locked",
"translation": "",
@@ -8315,6 +8441,13 @@
"reference": "",
"comment": ""
},
{
"term": "Maximum fingerprint attempts reached. Please use password.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Maximum number of clipboard entries to keep",
"translation": "",
@@ -10131,7 +10264,7 @@
{
"term": "Outline",
"translation": "",
"context": "outline color",
"context": "blur border color | outline color",
"reference": "",
"comment": ""
},
@@ -11062,7 +11195,7 @@
{
"term": "Primary",
"translation": "",
"context": "button color option | color option | primary color | shadow color option | tile color option",
"context": "blur border color | button color option | color option | primary color | shadow color option | tile color option",
"reference": "",
"comment": ""
},
@@ -11479,6 +11612,13 @@
"reference": "",
"comment": ""
},
{
"term": "Remember Last Query",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Remember last session",
"translation": "",
@@ -11605,6 +11745,13 @@
"reference": "",
"comment": ""
},
{
"term": "Requires a newer version of Quickshell",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Requires night mode support",
"translation": "",
@@ -11850,20 +11997,6 @@
"reference": "",
"comment": ""
},
{
"term": "Run Sync to apply.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Run Sync to apply. Fingerprint-only login may not unlock GNOME Keyring.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Run User Templates",
"translation": "",
@@ -12259,7 +12392,7 @@
{
"term": "Secondary",
"translation": "",
"context": "button color option | color option | secondary color | tile color option",
"context": "blur border color | button color option | color option | secondary color | tile color option",
"reference": "",
"comment": ""
},
@@ -14055,6 +14188,13 @@
"reference": "",
"comment": ""
},
{
"term": "Terminal fallback failed. Install a supported terminal emulator or run 'dms auth sync' manually.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Terminal fallback failed. Install one of the supported terminal emulators or run 'dms greeter sync' manually.",
"translation": "",
@@ -14062,6 +14202,13 @@
"reference": "",
"comment": ""
},
{
"term": "Terminal fallback opened. Complete authentication setup there; it will close automatically when done.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Terminal fallback opened. Complete sync there; it will close automatically when done.",
"translation": "",
@@ -14076,6 +14223,13 @@
"reference": "",
"comment": ""
},
{
"term": "Terminal opened. Complete authentication setup there; it will close automatically when done.",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Terminal opened. Complete sync authentication there; it will close automatically when done.",
"translation": "",
@@ -14128,7 +14282,7 @@
{
"term": "Text Color",
"translation": "",
"context": "shadow color option",
"context": "blur border color | shadow color option",
"reference": "",
"comment": ""
},
@@ -14489,6 +14643,13 @@
"reference": "",
"comment": ""
},
{
"term": "Too many attempts - locked out",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Too many failed attempts - account may be locked",
"translation": "",
@@ -14573,6 +14734,13 @@
"reference": "",
"comment": ""
},
{
"term": "Touch your security key...",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Transform",
"translation": "",
@@ -14678,20 +14846,6 @@
"reference": "",
"comment": ""
},
{
"term": "Type to search",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Type to search apps",
"translation": "",
"context": "",
"reference": "",
"comment": ""
},
{
"term": "Type to search files",
"translation": "",