mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-05-07 21:12:08 -04:00
Compare commits
19 Commits
blur
...
0b88055742
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b88055742 | ||
|
|
2b0826e397 | ||
|
|
7db04c9660 | ||
|
|
14d1e1d985 | ||
|
|
903ab1e61d | ||
|
|
5982655539 | ||
|
|
1021a210cf | ||
|
|
e34edb15bb | ||
|
|
61ee5f4336 | ||
|
|
ce2a92ec27 | ||
|
|
66ce79b9bf | ||
|
|
30dd640314 | ||
|
|
28f9aabcd9 | ||
|
|
3d9bd73336 | ||
|
|
3497d5f523 | ||
|
|
8ef1d95e65 | ||
|
|
e9aeb9ac60 | ||
|
|
fb02f7294d | ||
|
|
f15d49d80a |
40
core/cmd/dms/commands_blur.go
Normal file
40
core/cmd/dms/commands_blur.go
Normal 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")
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -525,5 +525,6 @@ func getCommonCommands() []*cobra.Command {
|
||||
configCmd,
|
||||
dlCmd,
|
||||
randrCmd,
|
||||
blurCmd,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
35
core/internal/blur/probe.go
Normal file
35
core/internal/blur/probe.go
Normal 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
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)$
|
||||
|
||||
@@ -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$"#
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
93
core/internal/server/trayrecovery/manager.go
Normal file
93
core/internal/server/trayrecovery/manager.go
Normal 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")
|
||||
}
|
||||
262
core/internal/server/trayrecovery/recovery.go
Normal file
262
core/internal/server/trayrecovery/recovery.go
Normal 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
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -369,9 +369,7 @@ Item {
|
||||
}
|
||||
|
||||
function previous(): void {
|
||||
if (MprisController.activePlayer && MprisController.activePlayer.canGoPrevious) {
|
||||
MprisController.activePlayer.previous();
|
||||
}
|
||||
MprisController.previousOrRewind();
|
||||
}
|
||||
|
||||
function next(): void {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
113
quickshell/Services/BlurService.qml
Normal file
113
quickshell/Services/BlurService.qml
Normal 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
65
quickshell/Widgets/WindowBlur.qml
Normal file
65
quickshell/Widgets/WindowBlur.qml
Normal 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
@@ -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
@@ -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",
|
||||
|
||||
@@ -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": "",
|
||||
|
||||
Reference in New Issue
Block a user