mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-05-05 12:02:06 -04:00
Compare commits
42 Commits
088ed806ae
...
hotfix-1.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bcf41ed5ca | ||
|
|
5033bdc630 | ||
|
|
b8bfaf9a26 | ||
|
|
da45714c54 | ||
|
|
0c2d00b79c | ||
|
|
c10b42f599 | ||
|
|
4c617cf022 | ||
|
|
e75b95b854 | ||
|
|
6b15670918 | ||
|
|
c52b9e19a1 | ||
|
|
7a3444bd30 | ||
|
|
a733d760e4 | ||
|
|
1b33079e39 | ||
|
|
1cf0dd1031 | ||
|
|
8d49a5cbfc | ||
|
|
f5928b09d3 | ||
|
|
38373aa5f2 | ||
|
|
665680e15e | ||
|
|
210607cfbc | ||
|
|
69fca14611 | ||
|
|
10a235e686 | ||
|
|
253cc7f8a3 | ||
|
|
a63ad99684 | ||
|
|
c44c032879 | ||
|
|
dc1ce55971 | ||
|
|
9f65882a12 | ||
|
|
97bf83cce6 | ||
|
|
96bf0162d6 | ||
|
|
73b833731a | ||
|
|
84522aeaad | ||
|
|
faf1a277d2 | ||
|
|
60515736e6 | ||
|
|
1715e2eab7 | ||
|
|
4e14cf5cce | ||
|
|
a644c93b1b | ||
|
|
f9428a1009 | ||
|
|
b4b51785e5 | ||
|
|
0a97df6d49 | ||
|
|
352ba77677 | ||
|
|
d320035d97 | ||
|
|
8d262a9555 | ||
|
|
9bfa8310d2 |
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")
|
||||
}
|
||||
}
|
||||
@@ -236,6 +236,7 @@ func runBrightnessSet(cmd *cobra.Command, args []string) {
|
||||
defer ddc.Close()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
if err := ddc.SetBrightnessWithExponent(deviceID, percent, exponential, exponent, nil); err == nil {
|
||||
ddc.WaitPending()
|
||||
fmt.Printf("Set %s to %d%%\n", deviceID, percent)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -525,5 +525,6 @@ func getCommonCommands() []*cobra.Command {
|
||||
configCmd,
|
||||
dlCmd,
|
||||
randrCmd,
|
||||
blurCmd,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/blur"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/clipboard"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/config"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
|
||||
@@ -82,7 +83,7 @@ func (ds *DoctorStatus) OKCount() int {
|
||||
}
|
||||
|
||||
var (
|
||||
quickshellVersionRegex = regexp.MustCompile(`quickshell (\d+\.\d+\.\d+)`)
|
||||
quickshellVersionRegex = regexp.MustCompile(`(?i)quickshell (\d+\.\d+\.\d+)`)
|
||||
hyprlandVersionRegex = regexp.MustCompile(`v?(\d+\.\d+\.\d+)`)
|
||||
niriVersionRegex = regexp.MustCompile(`niri (\d+\.\d+)`)
|
||||
swayVersionRegex = regexp.MustCompile(`sway version (\d+\.\d+)`)
|
||||
@@ -90,6 +91,7 @@ var (
|
||||
wayfireVersionRegex = regexp.MustCompile(`wayfire (\d+\.\d+)`)
|
||||
labwcVersionRegex = regexp.MustCompile(`labwc (\d+\.\d+\.\d+)`)
|
||||
mangowcVersionRegex = regexp.MustCompile(`mango (\d+\.\d+\.\d+)`)
|
||||
miracleVersionRegex = regexp.MustCompile(`miracle-wm v?(\d+\.\d+\.\d+)`)
|
||||
)
|
||||
|
||||
var doctorCmd = &cobra.Command{
|
||||
@@ -468,6 +470,7 @@ func checkWindowManagers() []checkResult {
|
||||
{"Wayfire", "wayfire", "--version", wayfireVersionRegex, []string{"wayfire"}},
|
||||
{"labwc", "labwc", "--version", labwcVersionRegex, []string{"labwc"}},
|
||||
{"mangowc", "mango", "-v", mangowcVersionRegex, []string{"mango"}},
|
||||
{"Miracle WM", "miracle-wm", "--version", miracleVersionRegex, []string{"miracle-wm"}},
|
||||
}
|
||||
|
||||
var results []checkResult
|
||||
@@ -500,7 +503,7 @@ func checkWindowManagers() []checkResult {
|
||||
results = append(results, checkResult{
|
||||
catCompositor, "Compositor", statusError,
|
||||
"No supported Wayland compositor found",
|
||||
"Install Hyprland, niri, Sway, River, or Wayfire",
|
||||
"Install Hyprland, niri, Sway, River, Wayfire, or miracle-wm",
|
||||
doctorDocsURL + "#compositor-checks",
|
||||
})
|
||||
}
|
||||
@@ -509,9 +512,24 @@ func checkWindowManagers() []checkResult {
|
||||
results = append(results, checkResult{catCompositor, "Active", statusInfo, wm, "", doctorDocsURL + "#compositor"})
|
||||
}
|
||||
|
||||
results = append(results, checkCompositorBlurSupport())
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func checkCompositorBlurSupport() checkResult {
|
||||
supported, err := blur.ProbeSupport()
|
||||
if err != nil {
|
||||
return checkResult{catCompositor, "Background Blur", statusInfo, "Unable to verify", err.Error(), doctorDocsURL + "#compositor-checks"}
|
||||
}
|
||||
|
||||
if supported {
|
||||
return checkResult{catCompositor, "Background Blur", statusOK, "Supported", "Compositor supports ext-background-effect-v1", doctorDocsURL + "#compositor-checks"}
|
||||
}
|
||||
|
||||
return checkResult{catCompositor, "Background Blur", statusWarn, "Unsupported", "Compositor does not support ext-background-effect-v1", doctorDocsURL + "#compositor-checks"}
|
||||
}
|
||||
|
||||
func getVersionFromCommand(cmd, arg string, regex *regexp.Regexp) string {
|
||||
output, err := exec.Command(cmd, arg).CombinedOutput()
|
||||
if err != nil && len(output) == 0 {
|
||||
@@ -535,6 +553,8 @@ func detectRunningWM() string {
|
||||
return "Hyprland"
|
||||
case os.Getenv("NIRI_SOCKET") != "":
|
||||
return "niri"
|
||||
case os.Getenv("MIRACLESOCK") != "":
|
||||
return "Miracle WM"
|
||||
case os.Getenv("XDG_CURRENT_DESKTOP") != "":
|
||||
return os.Getenv("XDG_CURRENT_DESKTOP")
|
||||
}
|
||||
@@ -553,6 +573,7 @@ func checkQuickshellFeatures() ([]checkResult, bool) {
|
||||
qmlContent := `
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
|
||||
ShellRoot {
|
||||
id: root
|
||||
@@ -561,6 +582,7 @@ ShellRoot {
|
||||
property bool idleMonitorAvailable: false
|
||||
property bool idleInhibitorAvailable: false
|
||||
property bool shortcutInhibitorAvailable: false
|
||||
property bool backgroundBlurAvailable: false
|
||||
|
||||
Timer {
|
||||
interval: 50
|
||||
@@ -578,16 +600,18 @@ ShellRoot {
|
||||
|
||||
try {
|
||||
var testItem = Qt.createQmlObject(
|
||||
'import Quickshell.Wayland; import QtQuick; QtObject { ' +
|
||||
'import Quickshell; import Quickshell.Wayland; import QtQuick; QtObject { ' +
|
||||
'readonly property bool hasIdleMonitor: typeof IdleMonitor !== "undefined"; ' +
|
||||
'readonly property bool hasIdleInhibitor: typeof IdleInhibitor !== "undefined"; ' +
|
||||
'readonly property bool hasShortcutInhibitor: typeof ShortcutInhibitor !== "undefined" ' +
|
||||
'readonly property bool hasShortcutInhibitor: typeof ShortcutInhibitor !== "undefined"; ' +
|
||||
'readonly property bool hasBackgroundBlur: typeof BackgroundEffect !== "undefined" ' +
|
||||
'}',
|
||||
root
|
||||
)
|
||||
root.idleMonitorAvailable = testItem.hasIdleMonitor
|
||||
root.idleInhibitorAvailable = testItem.hasIdleInhibitor
|
||||
root.shortcutInhibitorAvailable = testItem.hasShortcutInhibitor
|
||||
root.backgroundBlurAvailable = testItem.hasBackgroundBlur
|
||||
testItem.destroy()
|
||||
} catch (e) {}
|
||||
|
||||
@@ -596,6 +620,8 @@ ShellRoot {
|
||||
console.warn(root.idleInhibitorAvailable ? "FEATURE:IdleInhibitor:OK" : "FEATURE:IdleInhibitor:UNAVAILABLE")
|
||||
console.warn(root.shortcutInhibitorAvailable ? "FEATURE:ShortcutInhibitor:OK" : "FEATURE:ShortcutInhibitor:UNAVAILABLE")
|
||||
|
||||
console.warn(root.backgroundBlurAvailable ? "FEATURE:BackgroundBlur:OK" : "FEATURE:BackgroundBlur:UNAVAILABLE")
|
||||
|
||||
Quickshell.execDetached(["kill", "-TERM", String(Quickshell.processId)])
|
||||
}
|
||||
}
|
||||
@@ -616,6 +642,7 @@ ShellRoot {
|
||||
{"IdleMonitor", "Idle detection"},
|
||||
{"IdleInhibitor", "Prevent idle/sleep"},
|
||||
{"ShortcutInhibitor", "Allow shortcut management (niri)"},
|
||||
{"BackgroundBlur", "Background blur API support in Quickshell"},
|
||||
}
|
||||
|
||||
var results []checkResult
|
||||
@@ -820,10 +847,14 @@ func checkOptionalDependencies() []checkResult {
|
||||
results = append(results, checkImageFormatPlugins()...)
|
||||
|
||||
terminals := []string{"ghostty", "kitty", "alacritty", "foot", "wezterm"}
|
||||
if idx := slices.IndexFunc(terminals, utils.CommandExists); idx >= 0 {
|
||||
results = append(results, checkResult{catOptionalFeatures, "Terminal", statusOK, terminals[idx], "", optionalFeaturesURL})
|
||||
terminals = slices.DeleteFunc(terminals, func(t string) bool {
|
||||
return !utils.CommandExists(t)
|
||||
})
|
||||
|
||||
if len(terminals) > 0 {
|
||||
results = append(results, checkResult{catOptionalFeatures, "Terminal", statusOK, strings.Join(terminals, ", "), "", optionalFeaturesURL})
|
||||
} else {
|
||||
results = append(results, checkResult{catOptionalFeatures, "Terminal", statusWarn, "None found", "Install ghostty, kitty, or alacritty", optionalFeaturesURL})
|
||||
results = append(results, checkResult{catOptionalFeatures, "Terminal", statusWarn, "None found", "Install ghostty, kitty, foot or alacritty", optionalFeaturesURL})
|
||||
}
|
||||
|
||||
networkResult, err := network.DetectNetworkStack()
|
||||
|
||||
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
|
||||
}
|
||||
@@ -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$"#
|
||||
|
||||
@@ -324,6 +324,13 @@ func (a *ArchDistribution) InstallPackages(ctx context.Context, dependencies []d
|
||||
|
||||
systemPkgs, aurPkgs, manualPkgs, variantMap := a.categorizePackages(dependencies, wm, reinstallFlags, disabledFlags)
|
||||
|
||||
if slices.Contains(aurPkgs, "quickshell-git") && slices.Contains(systemPkgs, "dms-shell") {
|
||||
if err := a.preinstallQuickshellGit(ctx, sudoPassword, progressChan); err != nil {
|
||||
return fmt.Errorf("failed to preinstall quickshell-git: %w", err)
|
||||
}
|
||||
aurPkgs = slices.DeleteFunc(aurPkgs, func(p string) bool { return p == "quickshell-git" })
|
||||
}
|
||||
|
||||
// Phase 3: System Packages
|
||||
if len(systemPkgs) > 0 {
|
||||
progressChan <- InstallProgressMsg{
|
||||
@@ -441,6 +448,37 @@ func (a *ArchDistribution) categorizePackages(dependencies []deps.Dependency, wm
|
||||
return systemPkgs, aurPkgs, manualPkgs, variantMap
|
||||
}
|
||||
|
||||
func (a *ArchDistribution) preinstallQuickshellGit(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||
if a.packageInstalled("quickshell-git") {
|
||||
return nil
|
||||
}
|
||||
|
||||
if a.packageInstalled("quickshell") {
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseAURPackages,
|
||||
Progress: 0.15,
|
||||
Step: "Removing stable quickshell...",
|
||||
IsComplete: false,
|
||||
NeedsSudo: true,
|
||||
CommandInfo: "sudo pacman -Rdd --noconfirm quickshell",
|
||||
LogOutput: "Removing stable quickshell so quickshell-git can be installed",
|
||||
}
|
||||
cmd := ExecSudoCommand(ctx, sudoPassword, "pacman -Rdd --noconfirm quickshell")
|
||||
if err := a.runWithProgress(cmd, progressChan, PhaseAURPackages, 0.15, 0.18); err != nil {
|
||||
return fmt.Errorf("failed to remove stable quickshell: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseAURPackages,
|
||||
Progress: 0.18,
|
||||
Step: "Building quickshell-git before system packages...",
|
||||
IsComplete: false,
|
||||
CommandInfo: "Installing quickshell-git ahead of dms-shell to avoid conflict",
|
||||
}
|
||||
return a.installSingleAURPackage(ctx, "quickshell-git", sudoPassword, progressChan, 0.18, 0.32)
|
||||
}
|
||||
|
||||
func (a *ArchDistribution) installSystemPackages(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||
if len(packages) == 0 {
|
||||
return nil
|
||||
@@ -449,6 +487,9 @@ func (a *ArchDistribution) installSystemPackages(ctx context.Context, packages [
|
||||
a.log(fmt.Sprintf("Installing system packages: %s", strings.Join(packages, ", ")))
|
||||
|
||||
args := []string{"pacman", "-S", "--needed", "--noconfirm"}
|
||||
if slices.Contains(packages, "dms-shell") {
|
||||
args = append(args, "--assume-installed", "dms-shell-compositor=1")
|
||||
}
|
||||
args = append(args, packages...)
|
||||
|
||||
progressChan <- InstallProgressMsg{
|
||||
|
||||
@@ -91,9 +91,25 @@ func (d *DebianDistribution) detectDMSGreeter() deps.Dependency {
|
||||
}
|
||||
|
||||
func (d *DebianDistribution) packageInstalled(pkg string) bool {
|
||||
cmd := exec.Command("dpkg", "-l", pkg)
|
||||
err := cmd.Run()
|
||||
return err == nil
|
||||
return debianPackageInstalledPrecisely(pkg)
|
||||
}
|
||||
|
||||
func debianPackageInstalledPrecisely(pkg string) bool {
|
||||
cmd := exec.Command("dpkg-query", "-W", "-f=${db:Status-Status}", pkg)
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return strings.TrimSpace(string(output)) == "installed"
|
||||
}
|
||||
|
||||
func containsString(values []string, target string) bool {
|
||||
for _, value := range values {
|
||||
if value == target {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func debianRepoArchitecture(arch string) string {
|
||||
@@ -204,12 +220,12 @@ func (d *DebianDistribution) InstallPrerequisites(ctx context.Context, sudoPassw
|
||||
Step: "Installing development dependencies...",
|
||||
IsComplete: false,
|
||||
NeedsSudo: true,
|
||||
CommandInfo: "sudo apt-get install -y curl wget git cmake ninja-build pkg-config libxcb-cursor-dev libglib2.0-dev libpolkit-agent-1-dev",
|
||||
CommandInfo: "sudo apt-get install -y curl wget git cmake ninja-build pkg-config gnupg libxcb-cursor-dev libglib2.0-dev libpolkit-agent-1-dev",
|
||||
LogOutput: "Installing additional development tools",
|
||||
}
|
||||
|
||||
devToolsCmd := ExecSudoCommand(ctx, sudoPassword,
|
||||
"DEBIAN_FRONTEND=noninteractive apt-get install -y curl wget git cmake ninja-build pkg-config libxcb-cursor-dev libglib2.0-dev libpolkit-agent-1-dev libjpeg-dev libpugixml-dev")
|
||||
"DEBIAN_FRONTEND=noninteractive apt-get install -y curl wget git cmake ninja-build pkg-config gnupg libxcb-cursor-dev libglib2.0-dev libpolkit-agent-1-dev libjpeg-dev libpugixml-dev")
|
||||
if err := d.runWithProgress(devToolsCmd, progressChan, PhasePrerequisites, 0.10, 0.12); err != nil {
|
||||
return fmt.Errorf("failed to install development tools: %w", err)
|
||||
}
|
||||
@@ -389,6 +405,14 @@ func (d *DebianDistribution) extractPackageNames(packages []PackageMapping) []st
|
||||
return names
|
||||
}
|
||||
|
||||
func (d *DebianDistribution) aptInstallArgs(packages []string, minimal bool) []string {
|
||||
args := []string{"DEBIAN_FRONTEND=noninteractive", "apt-get", "install", "-y"}
|
||||
if minimal {
|
||||
args = append(args, "--no-install-recommends")
|
||||
}
|
||||
return append(args, packages...)
|
||||
}
|
||||
|
||||
func (d *DebianDistribution) enableOBSRepos(ctx context.Context, obsPkgs []PackageMapping, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||
enabledRepos := make(map[string]bool)
|
||||
|
||||
@@ -492,20 +516,46 @@ func (d *DebianDistribution) installAPTPackages(ctx context.Context, packages []
|
||||
|
||||
d.log(fmt.Sprintf("Installing APT packages: %s", strings.Join(packages, ", ")))
|
||||
|
||||
args := []string{"DEBIAN_FRONTEND=noninteractive", "apt-get", "install", "-y"}
|
||||
args = append(args, packages...)
|
||||
groups := orderedMinimalInstallGroups(packages)
|
||||
totalGroups := len(groups)
|
||||
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseSystemPackages,
|
||||
Progress: 0.40,
|
||||
Step: "Installing system packages...",
|
||||
IsComplete: false,
|
||||
NeedsSudo: true,
|
||||
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
||||
groupIndex := 0
|
||||
installGroup := func(groupPackages []string, minimal bool) error {
|
||||
if len(groupPackages) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
groupIndex++
|
||||
startProgress := 0.40
|
||||
endProgress := 0.60
|
||||
if totalGroups > 1 {
|
||||
if groupIndex == 1 {
|
||||
endProgress = 0.50
|
||||
} else {
|
||||
startProgress = 0.50
|
||||
}
|
||||
}
|
||||
|
||||
args := d.aptInstallArgs(groupPackages, minimal)
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseSystemPackages,
|
||||
Progress: startProgress,
|
||||
Step: "Installing system packages...",
|
||||
IsComplete: false,
|
||||
NeedsSudo: true,
|
||||
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
||||
}
|
||||
|
||||
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
||||
return d.runWithProgress(cmd, progressChan, PhaseSystemPackages, startProgress, endProgress)
|
||||
}
|
||||
|
||||
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
||||
return d.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.40, 0.60)
|
||||
for _, group := range groups {
|
||||
if err := installGroup(group.packages, group.minimal); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DebianDistribution) installBuildDependencies(ctx context.Context, manualPkgs []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||
|
||||
@@ -484,28 +484,7 @@ func (f *FedoraDistribution) installDNFPackages(ctx context.Context, packages []
|
||||
|
||||
f.log(fmt.Sprintf("Installing DNF packages: %s", strings.Join(packages, ", ")))
|
||||
|
||||
args := []string{"dnf", "install", "-y"}
|
||||
|
||||
for _, pkg := range packages {
|
||||
if pkg == "niri" || pkg == "niri-git" {
|
||||
args = append(args, "--setopt=install_weak_deps=False")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
args = append(args, packages...)
|
||||
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseSystemPackages,
|
||||
Progress: 0.40,
|
||||
Step: "Installing system packages...",
|
||||
IsComplete: false,
|
||||
NeedsSudo: true,
|
||||
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
||||
}
|
||||
|
||||
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
||||
return f.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.40, 0.60)
|
||||
return f.installDNFGroups(ctx, packages, sudoPassword, progressChan, PhaseSystemPackages, "Installing system packages...", 0.40, 0.60)
|
||||
}
|
||||
|
||||
func (f *FedoraDistribution) installCOPRPackages(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||
@@ -515,26 +494,57 @@ func (f *FedoraDistribution) installCOPRPackages(ctx context.Context, packages [
|
||||
|
||||
f.log(fmt.Sprintf("Installing COPR packages: %s", strings.Join(packages, ", ")))
|
||||
|
||||
args := []string{"dnf", "install", "-y"}
|
||||
return f.installDNFGroups(ctx, packages, sudoPassword, progressChan, PhaseAURPackages, "Installing COPR packages...", 0.70, 0.85)
|
||||
}
|
||||
|
||||
for _, pkg := range packages {
|
||||
if pkg == "niri" || pkg == "niri-git" {
|
||||
args = append(args, "--setopt=install_weak_deps=False")
|
||||
break
|
||||
func (f *FedoraDistribution) dnfInstallArgs(packages []string, minimal bool) []string {
|
||||
args := []string{"dnf", "install", "-y"}
|
||||
if minimal {
|
||||
args = append(args, "--setopt=install_weak_deps=False")
|
||||
}
|
||||
return append(args, packages...)
|
||||
}
|
||||
|
||||
func (f *FedoraDistribution) installDNFGroups(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg, phase InstallPhase, step string, startProgress float64, endProgress float64) error {
|
||||
groups := orderedMinimalInstallGroups(packages)
|
||||
totalGroups := len(groups)
|
||||
|
||||
groupIndex := 0
|
||||
installGroup := func(groupPackages []string, minimal bool) error {
|
||||
if len(groupPackages) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
groupIndex++
|
||||
groupStart := startProgress
|
||||
groupEnd := endProgress
|
||||
if totalGroups > 1 {
|
||||
midpoint := startProgress + ((endProgress - startProgress) / 2)
|
||||
if groupIndex == 1 {
|
||||
groupEnd = midpoint
|
||||
} else {
|
||||
groupStart = midpoint
|
||||
}
|
||||
}
|
||||
|
||||
args := f.dnfInstallArgs(groupPackages, minimal)
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: phase,
|
||||
Progress: groupStart,
|
||||
Step: step,
|
||||
IsComplete: false,
|
||||
NeedsSudo: true,
|
||||
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
||||
}
|
||||
|
||||
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
||||
return f.runWithProgress(cmd, progressChan, phase, groupStart, groupEnd)
|
||||
}
|
||||
|
||||
for _, group := range groups {
|
||||
if err := installGroup(group.packages, group.minimal); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
args = append(args, packages...)
|
||||
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseAURPackages,
|
||||
Progress: 0.70,
|
||||
Step: "Installing COPR packages...",
|
||||
IsComplete: false,
|
||||
NeedsSudo: true,
|
||||
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
||||
}
|
||||
|
||||
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
||||
return f.runWithProgress(cmd, progressChan, PhaseAURPackages, 0.70, 0.85)
|
||||
return nil
|
||||
}
|
||||
|
||||
44
core/internal/distros/minimal_install.go
Normal file
44
core/internal/distros/minimal_install.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package distros
|
||||
|
||||
type minimalInstallGroup struct {
|
||||
packages []string
|
||||
minimal bool
|
||||
}
|
||||
|
||||
func shouldPreferMinimalInstall(pkg string) bool {
|
||||
switch pkg {
|
||||
case "niri", "niri-git":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func splitMinimalInstallPackages(packages []string) (normal []string, minimal []string) {
|
||||
for _, pkg := range packages {
|
||||
if shouldPreferMinimalInstall(pkg) {
|
||||
minimal = append(minimal, pkg)
|
||||
continue
|
||||
}
|
||||
normal = append(normal, pkg)
|
||||
}
|
||||
return normal, minimal
|
||||
}
|
||||
|
||||
func orderedMinimalInstallGroups(packages []string) []minimalInstallGroup {
|
||||
normal, minimal := splitMinimalInstallPackages(packages)
|
||||
groups := make([]minimalInstallGroup, 0, 2)
|
||||
if len(minimal) > 0 {
|
||||
groups = append(groups, minimalInstallGroup{
|
||||
packages: minimal,
|
||||
minimal: true,
|
||||
})
|
||||
}
|
||||
if len(normal) > 0 {
|
||||
groups = append(groups, minimalInstallGroup{
|
||||
packages: normal,
|
||||
minimal: false,
|
||||
})
|
||||
}
|
||||
return groups
|
||||
}
|
||||
@@ -29,6 +29,8 @@ type OpenSUSEDistribution struct {
|
||||
config DistroConfig
|
||||
}
|
||||
|
||||
const openSUSENiriWaylandServerPackage = "libwayland-server0"
|
||||
|
||||
func NewOpenSUSEDistribution(config DistroConfig, logChan chan<- string) *OpenSUSEDistribution {
|
||||
base := NewBaseDistribution(logChan)
|
||||
return &OpenSUSEDistribution{
|
||||
@@ -199,35 +201,7 @@ func (o *OpenSUSEDistribution) detectAccountsService() deps.Dependency {
|
||||
}
|
||||
|
||||
func (o *OpenSUSEDistribution) getPrerequisites() []string {
|
||||
return []string{
|
||||
"make",
|
||||
"unzip",
|
||||
"gcc",
|
||||
"gcc-c++",
|
||||
"cmake",
|
||||
"ninja",
|
||||
"pkgconf-pkg-config",
|
||||
"git",
|
||||
"qt6-base-devel",
|
||||
"qt6-declarative-devel",
|
||||
"qt6-declarative-private-devel",
|
||||
"qt6-shadertools",
|
||||
"qt6-shadertools-devel",
|
||||
"qt6-wayland-devel",
|
||||
"qt6-waylandclient-private-devel",
|
||||
"spirv-tools-devel",
|
||||
"cli11-devel",
|
||||
"wayland-protocols-devel",
|
||||
"libgbm-devel",
|
||||
"libdrm-devel",
|
||||
"pipewire-devel",
|
||||
"jemalloc-devel",
|
||||
"wayland-utils",
|
||||
"Mesa-libGLESv3-devel",
|
||||
"pam-devel",
|
||||
"glib2-devel",
|
||||
"polkit-devel",
|
||||
}
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (o *OpenSUSEDistribution) InstallPrerequisites(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||
@@ -297,6 +271,10 @@ func (o *OpenSUSEDistribution) InstallPackages(ctx context.Context, dependencies
|
||||
LogOutput: "Starting prerequisite check...",
|
||||
}
|
||||
|
||||
if err := o.disableInstallMediaRepos(ctx, sudoPassword, progressChan); err != nil {
|
||||
return fmt.Errorf("failed to disable install media repositories: %w", err)
|
||||
}
|
||||
|
||||
if err := o.InstallPrerequisites(ctx, sudoPassword, progressChan); err != nil {
|
||||
return fmt.Errorf("failed to install prerequisites: %w", err)
|
||||
}
|
||||
@@ -327,7 +305,7 @@ func (o *OpenSUSEDistribution) InstallPackages(ctx context.Context, dependencies
|
||||
NeedsSudo: true,
|
||||
LogOutput: fmt.Sprintf("Installing system packages: %s", strings.Join(systemPkgs, ", ")),
|
||||
}
|
||||
if err := o.installZypperPackages(ctx, systemPkgs, sudoPassword, progressChan); err != nil {
|
||||
if err := o.installZypperPackages(ctx, systemPkgs, sudoPassword, progressChan, PhaseSystemPackages, "Installing system packages...", 0.40, 0.60); err != nil {
|
||||
return fmt.Errorf("failed to install zypper packages: %w", err)
|
||||
}
|
||||
}
|
||||
@@ -342,7 +320,7 @@ func (o *OpenSUSEDistribution) InstallPackages(ctx context.Context, dependencies
|
||||
IsComplete: false,
|
||||
LogOutput: fmt.Sprintf("Installing OBS packages: %s", strings.Join(obsPkgNames, ", ")),
|
||||
}
|
||||
if err := o.installZypperPackages(ctx, obsPkgNames, sudoPassword, progressChan); err != nil {
|
||||
if err := o.installZypperPackages(ctx, obsPkgNames, sudoPassword, progressChan, PhaseAURPackages, "Installing OBS packages...", 0.70, 0.85); err != nil {
|
||||
return fmt.Errorf("failed to install OBS packages: %w", err)
|
||||
}
|
||||
}
|
||||
@@ -432,9 +410,32 @@ func (o *OpenSUSEDistribution) categorizePackages(dependencies []deps.Dependency
|
||||
}
|
||||
}
|
||||
|
||||
systemPkgs = o.appendMissingSystemPackages(systemPkgs, openSUSENiriRuntimePackages(wm, disabledFlags))
|
||||
|
||||
return systemPkgs, obsPkgs, manualPkgs, variantMap
|
||||
}
|
||||
|
||||
func openSUSENiriRuntimePackages(wm deps.WindowManager, disabledFlags map[string]bool) []string {
|
||||
if wm != deps.WindowManagerNiri || disabledFlags["niri"] {
|
||||
return nil
|
||||
}
|
||||
|
||||
return []string{openSUSENiriWaylandServerPackage}
|
||||
}
|
||||
|
||||
func (o *OpenSUSEDistribution) appendMissingSystemPackages(systemPkgs []string, extraPkgs []string) []string {
|
||||
for _, pkg := range extraPkgs {
|
||||
if containsString(systemPkgs, pkg) || o.packageInstalled(pkg) {
|
||||
continue
|
||||
}
|
||||
|
||||
o.log(fmt.Sprintf("Adding openSUSE runtime package: %s", pkg))
|
||||
systemPkgs = append(systemPkgs, pkg)
|
||||
}
|
||||
|
||||
return systemPkgs
|
||||
}
|
||||
|
||||
func (o *OpenSUSEDistribution) extractPackageNames(packages []PackageMapping) []string {
|
||||
names := make([]string, len(packages))
|
||||
for i, pkg := range packages {
|
||||
@@ -514,27 +515,146 @@ func (o *OpenSUSEDistribution) enableOBSRepos(ctx context.Context, obsPkgs []Pac
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OpenSUSEDistribution) installZypperPackages(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||
func isOpenSUSEInstallMediaURI(uri string) bool {
|
||||
normalizedURI := strings.ToLower(strings.TrimSpace(uri))
|
||||
|
||||
return strings.HasPrefix(normalizedURI, "cd:/") ||
|
||||
strings.HasPrefix(normalizedURI, "dvd:/") ||
|
||||
strings.HasPrefix(normalizedURI, "hd:/") ||
|
||||
strings.HasPrefix(normalizedURI, "iso:/")
|
||||
}
|
||||
|
||||
func parseZypperInstallMediaAliases(output string) []string {
|
||||
var aliases []string
|
||||
|
||||
for _, line := range strings.Split(output, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || !strings.Contains(line, "|") {
|
||||
continue
|
||||
}
|
||||
|
||||
parts := strings.Split(line, "|")
|
||||
if len(parts) < 7 {
|
||||
continue
|
||||
}
|
||||
|
||||
for i := range parts {
|
||||
parts[i] = strings.TrimSpace(parts[i])
|
||||
}
|
||||
|
||||
alias := parts[1]
|
||||
enabled := strings.ToLower(parts[3])
|
||||
uri := parts[len(parts)-1]
|
||||
|
||||
if alias == "" || strings.EqualFold(alias, "alias") {
|
||||
continue
|
||||
}
|
||||
if enabled != "" && enabled != "yes" {
|
||||
continue
|
||||
}
|
||||
if !isOpenSUSEInstallMediaURI(uri) {
|
||||
continue
|
||||
}
|
||||
|
||||
aliases = append(aliases, alias)
|
||||
}
|
||||
|
||||
return aliases
|
||||
}
|
||||
|
||||
func (o *OpenSUSEDistribution) disableInstallMediaRepos(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||
listCmd := exec.CommandContext(ctx, "zypper", "repos", "-u")
|
||||
output, err := listCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
o.log(fmt.Sprintf("Warning: failed to list zypper repositories: %s", strings.TrimSpace(string(output))))
|
||||
return fmt.Errorf("failed to list zypper repositories: %w", err)
|
||||
}
|
||||
|
||||
aliases := parseZypperInstallMediaAliases(string(output))
|
||||
if len(aliases) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
o.log(fmt.Sprintf("Disabling install media repositories: %s", strings.Join(aliases, ", ")))
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhasePrerequisites,
|
||||
Progress: 0.055,
|
||||
Step: "Disabling install media repositories...",
|
||||
IsComplete: false,
|
||||
NeedsSudo: true,
|
||||
CommandInfo: fmt.Sprintf("sudo zypper modifyrepo -d %s", strings.Join(aliases, " ")),
|
||||
LogOutput: fmt.Sprintf("Disabling install media repositories: %s", strings.Join(aliases, ", ")),
|
||||
}
|
||||
|
||||
for _, alias := range aliases {
|
||||
cmd := ExecSudoCommand(ctx, sudoPassword, fmt.Sprintf("zypper modifyrepo -d '%s'", escapeSingleQuotes(alias)))
|
||||
repoOutput, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
o.log(fmt.Sprintf("Failed to disable install media repo %s: %s", alias, strings.TrimSpace(string(repoOutput))))
|
||||
return fmt.Errorf("failed to disable install media repo %s: %w", alias, err)
|
||||
}
|
||||
o.log(fmt.Sprintf("Disabled install media repo %s: %s", alias, strings.TrimSpace(string(repoOutput))))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OpenSUSEDistribution) zypperInstallArgs(packages []string, minimal bool) []string {
|
||||
args := []string{"zypper", "install", "-y"}
|
||||
if minimal {
|
||||
args = append(args, "--no-recommends")
|
||||
}
|
||||
return append(args, packages...)
|
||||
}
|
||||
|
||||
func (o *OpenSUSEDistribution) installZypperPackages(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg, phase InstallPhase, step string, startProgress float64, endProgress float64) error {
|
||||
if len(packages) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
o.log(fmt.Sprintf("Installing zypper packages: %s", strings.Join(packages, ", ")))
|
||||
|
||||
args := []string{"zypper", "install", "-y"}
|
||||
args = append(args, packages...)
|
||||
groups := orderedMinimalInstallGroups(packages)
|
||||
totalGroups := len(groups)
|
||||
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseSystemPackages,
|
||||
Progress: 0.40,
|
||||
Step: "Installing system packages...",
|
||||
IsComplete: false,
|
||||
NeedsSudo: true,
|
||||
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
||||
groupIndex := 0
|
||||
installGroup := func(groupPackages []string, minimal bool) error {
|
||||
if len(groupPackages) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
groupIndex++
|
||||
groupStart := startProgress
|
||||
groupEnd := endProgress
|
||||
if totalGroups > 1 {
|
||||
midpoint := startProgress + ((endProgress - startProgress) / 2)
|
||||
if groupIndex == 1 {
|
||||
groupEnd = midpoint
|
||||
} else {
|
||||
groupStart = midpoint
|
||||
}
|
||||
}
|
||||
|
||||
args := o.zypperInstallArgs(groupPackages, minimal)
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: phase,
|
||||
Progress: groupStart,
|
||||
Step: step,
|
||||
IsComplete: false,
|
||||
NeedsSudo: true,
|
||||
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
||||
}
|
||||
|
||||
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
||||
return o.runWithProgress(cmd, progressChan, phase, groupStart, groupEnd)
|
||||
}
|
||||
|
||||
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
||||
return o.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.40, 0.60)
|
||||
for _, group := range groups {
|
||||
if err := installGroup(group.packages, group.minimal); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OpenSUSEDistribution) installQuickshell(ctx context.Context, variant deps.PackageVariant, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||
|
||||
@@ -100,9 +100,7 @@ func (u *UbuntuDistribution) detectDMSGreeter() deps.Dependency {
|
||||
}
|
||||
|
||||
func (u *UbuntuDistribution) packageInstalled(pkg string) bool {
|
||||
cmd := exec.Command("dpkg", "-l", pkg)
|
||||
err := cmd.Run()
|
||||
return err == nil
|
||||
return debianPackageInstalledPrecisely(pkg)
|
||||
}
|
||||
|
||||
func (u *UbuntuDistribution) GetPackageMapping(wm deps.WindowManager) map[string]PackageMapping {
|
||||
@@ -454,21 +452,7 @@ func (u *UbuntuDistribution) installAPTPackages(ctx context.Context, packages []
|
||||
}
|
||||
|
||||
u.log(fmt.Sprintf("Installing APT packages: %s", strings.Join(packages, ", ")))
|
||||
|
||||
args := []string{"apt-get", "install", "-y"}
|
||||
args = append(args, packages...)
|
||||
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseSystemPackages,
|
||||
Progress: 0.40,
|
||||
Step: "Installing system packages...",
|
||||
IsComplete: false,
|
||||
NeedsSudo: true,
|
||||
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
||||
}
|
||||
|
||||
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
||||
return u.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.40, 0.60)
|
||||
return u.installAPTGroups(ctx, packages, sudoPassword, progressChan, PhaseSystemPackages, "Installing system packages...", 0.40, 0.60)
|
||||
}
|
||||
|
||||
func (u *UbuntuDistribution) installPPAPackages(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||
@@ -477,21 +461,59 @@ func (u *UbuntuDistribution) installPPAPackages(ctx context.Context, packages []
|
||||
}
|
||||
|
||||
u.log(fmt.Sprintf("Installing PPA packages: %s", strings.Join(packages, ", ")))
|
||||
return u.installAPTGroups(ctx, packages, sudoPassword, progressChan, PhaseAURPackages, "Installing PPA packages...", 0.70, 0.85)
|
||||
}
|
||||
|
||||
args := []string{"apt-get", "install", "-y"}
|
||||
args = append(args, packages...)
|
||||
func (u *UbuntuDistribution) aptInstallArgs(packages []string, minimal bool) []string {
|
||||
args := []string{"DEBIAN_FRONTEND=noninteractive", "apt-get", "install", "-y"}
|
||||
if minimal {
|
||||
args = append(args, "--no-install-recommends")
|
||||
}
|
||||
return append(args, packages...)
|
||||
}
|
||||
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseAURPackages,
|
||||
Progress: 0.70,
|
||||
Step: "Installing PPA packages...",
|
||||
IsComplete: false,
|
||||
NeedsSudo: true,
|
||||
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
||||
func (u *UbuntuDistribution) installAPTGroups(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg, phase InstallPhase, step string, startProgress float64, endProgress float64) error {
|
||||
groups := orderedMinimalInstallGroups(packages)
|
||||
totalGroups := len(groups)
|
||||
|
||||
groupIndex := 0
|
||||
installGroup := func(groupPackages []string, minimal bool) error {
|
||||
if len(groupPackages) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
groupIndex++
|
||||
groupStart := startProgress
|
||||
groupEnd := endProgress
|
||||
if totalGroups > 1 {
|
||||
midpoint := startProgress + ((endProgress - startProgress) / 2)
|
||||
if groupIndex == 1 {
|
||||
groupEnd = midpoint
|
||||
} else {
|
||||
groupStart = midpoint
|
||||
}
|
||||
}
|
||||
|
||||
args := u.aptInstallArgs(groupPackages, minimal)
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: phase,
|
||||
Progress: groupStart,
|
||||
Step: step,
|
||||
IsComplete: false,
|
||||
NeedsSudo: true,
|
||||
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
||||
}
|
||||
|
||||
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
||||
return u.runWithProgress(cmd, progressChan, phase, groupStart, groupEnd)
|
||||
}
|
||||
|
||||
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
||||
return u.runWithProgress(cmd, progressChan, PhaseAURPackages, 0.70, 0.85)
|
||||
for _, group := range groups {
|
||||
if err := installGroup(group.packages, group.minimal); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UbuntuDistribution) installBuildDependencies(ctx context.Context, manualPkgs []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
|
||||
"github.com/sblinch/kdl-go"
|
||||
"github.com/sblinch/kdl-go/document"
|
||||
)
|
||||
|
||||
@@ -292,7 +291,7 @@ func (n *NiriProvider) loadOverrideBinds() (map[string]*overrideBind, error) {
|
||||
parser := NewNiriParser(filepath.Dir(overridePath))
|
||||
parser.currentSource = overridePath
|
||||
|
||||
doc, err := kdl.Parse(strings.NewReader(string(data)))
|
||||
doc, err := parseKDL(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -50,6 +50,103 @@ type NiriParser struct {
|
||||
conflictingConfigs map[string]*NiriKeyBinding
|
||||
}
|
||||
|
||||
func parseKDL(data []byte) (*document.Document, error) {
|
||||
return kdl.Parse(strings.NewReader(normalizeKDLBraces(string(data))))
|
||||
}
|
||||
|
||||
func normalizeKDLBraces(input string) string {
|
||||
var sb strings.Builder
|
||||
sb.Grow(len(input))
|
||||
|
||||
var prev byte
|
||||
n := len(input)
|
||||
for i := 0; i < n; {
|
||||
c := input[i]
|
||||
|
||||
switch {
|
||||
case c == '"':
|
||||
end := findStringEnd(input, i)
|
||||
sb.WriteString(input[i:end])
|
||||
prev = '"'
|
||||
i = end
|
||||
case c == '/' && i+1 < n && input[i+1] == '/':
|
||||
end := findLineCommentEnd(input, i)
|
||||
sb.WriteString(input[i:end])
|
||||
prev = '\n'
|
||||
i = end
|
||||
case c == '/' && i+1 < n && input[i+1] == '*':
|
||||
end := findBlockCommentEnd(input, i)
|
||||
sb.WriteString(input[i:end])
|
||||
prev = '/'
|
||||
i = end
|
||||
case c == '{' && prev != 0 && !isBraceAdjacentSpace(prev):
|
||||
sb.WriteByte(' ')
|
||||
sb.WriteByte(c)
|
||||
prev = c
|
||||
i++
|
||||
default:
|
||||
sb.WriteByte(c)
|
||||
prev = c
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func findStringEnd(s string, start int) int {
|
||||
n := len(s)
|
||||
for i := start + 1; i < n; {
|
||||
switch s[i] {
|
||||
case '\\':
|
||||
i += 2
|
||||
case '"':
|
||||
return i + 1
|
||||
default:
|
||||
i++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func findLineCommentEnd(s string, start int) int {
|
||||
for i := start + 2; i < len(s); i++ {
|
||||
if s[i] == '\n' {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func findBlockCommentEnd(s string, start int) int {
|
||||
n := len(s)
|
||||
depth := 1
|
||||
for i := start + 2; i < n && depth > 0; {
|
||||
switch {
|
||||
case i+1 < n && s[i] == '/' && s[i+1] == '*':
|
||||
depth++
|
||||
i += 2
|
||||
case i+1 < n && s[i] == '*' && s[i+1] == '/':
|
||||
depth--
|
||||
i += 2
|
||||
if depth == 0 {
|
||||
return i
|
||||
}
|
||||
default:
|
||||
i++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func isBraceAdjacentSpace(b byte) bool {
|
||||
switch b {
|
||||
case ' ', '\t', '\n', '\r', '{':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func NewNiriParser(configDir string) *NiriParser {
|
||||
return &NiriParser{
|
||||
configDir: configDir,
|
||||
@@ -91,7 +188,7 @@ func (p *NiriParser) parseDMSBindsDirectly(dmsBindsPath string, section *NiriSec
|
||||
return
|
||||
}
|
||||
|
||||
doc, err := kdl.Parse(strings.NewReader(string(data)))
|
||||
doc, err := parseKDL(data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -159,7 +256,7 @@ func (p *NiriParser) parseFile(filePath, sectionName string) (*NiriSection, erro
|
||||
return nil, fmt.Errorf("failed to read %s: %w", absPath, err)
|
||||
}
|
||||
|
||||
doc, err := kdl.Parse(strings.NewReader(string(data)))
|
||||
doc, err := parseKDL(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse KDL in %s: %w", absPath, err)
|
||||
}
|
||||
|
||||
@@ -3,9 +3,74 @@ package providers
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNiriParse_NoSpaceBeforeBrace(t *testing.T) {
|
||||
config := `recent-windows {
|
||||
binds {
|
||||
Alt+Tab { next-window scope="output"; }
|
||||
Alt+Shift+Tab { previous-window scope="output"; }
|
||||
Alt+grave { next-window filter="app-id"; }
|
||||
Alt+Shift+grave { previous-window filter="app-id"; }
|
||||
Alt+Escape { next-window scope="all"; }
|
||||
Alt+Shift+Escape{ previous-window scope="all"; }
|
||||
}
|
||||
}
|
||||
`
|
||||
tmpDir := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(tmpDir, "config.kdl"), []byte(config), 0o644); err != nil {
|
||||
t.Fatalf("Failed to write test config: %v", err)
|
||||
}
|
||||
|
||||
result, err := ParseNiriKeys(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseNiriKeys failed on valid niri config: %v", err)
|
||||
}
|
||||
|
||||
var found *NiriKeyBinding
|
||||
for i := range result.Section.Keybinds {
|
||||
kb := &result.Section.Keybinds[i]
|
||||
if kb.Key == "Escape" && slices.Contains(kb.Mods, "Alt") && slices.Contains(kb.Mods, "Shift") {
|
||||
found = kb
|
||||
break
|
||||
}
|
||||
}
|
||||
if found == nil {
|
||||
t.Fatal("Alt+Shift+Escape bind missing — '{' without preceding space was not handled")
|
||||
}
|
||||
if found.Action != "previous-window" {
|
||||
t.Errorf("Action = %q, want %q", found.Action, "previous-window")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeKDLBraces(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{"already spaced", "node { child }\n", "node { child }\n"},
|
||||
{"missing space", "node{ child }\n", "node { child }\n"},
|
||||
{"niri keybind", "Alt+Shift+Escape{ previous-window; }", "Alt+Shift+Escape { previous-window; }"},
|
||||
{"brace inside string", `node "a{b" { child }`, `node "a{b" { child }`},
|
||||
{"brace in line comment", "// foo{bar\nnode { }", "// foo{bar\nnode { }"},
|
||||
{"brace in block comment", "/* foo{bar */ node{ }", "/* foo{bar */ node { }"},
|
||||
{"escaped quote in string", `node "a\"b{c" { }`, `node "a\"b{c" { }`},
|
||||
{"leading brace", "{ child }", "{ child }"},
|
||||
{"nested missing space", "a{b{ c }}", "a {b { c }}"},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := normalizeKDLBraces(tc.in)
|
||||
if got != tc.out {
|
||||
t.Errorf("normalizeKDLBraces(%q) = %q, want %q", tc.in, got, tc.out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNiriParseKeyCombo(t *testing.T) {
|
||||
tests := []struct {
|
||||
combo string
|
||||
|
||||
@@ -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"
|
||||
@@ -298,22 +299,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)
|
||||
@@ -322,50 +321,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
|
||||
@@ -375,35 +398,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{
|
||||
@@ -413,32 +427,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
|
||||
|
||||
@@ -215,31 +215,34 @@ func (b *DDCBackend) SetBrightnessWithExponent(id string, value int, exponential
|
||||
callback: callback,
|
||||
}
|
||||
|
||||
if timer, exists := b.debounceTimers[id]; exists {
|
||||
timer.Reset(200 * time.Millisecond)
|
||||
} else {
|
||||
b.debounceTimers[id] = time.AfterFunc(200*time.Millisecond, func() {
|
||||
b.debounceMutex.Lock()
|
||||
pending, exists := b.debouncePending[id]
|
||||
if exists {
|
||||
delete(b.debouncePending, id)
|
||||
}
|
||||
b.debounceMutex.Unlock()
|
||||
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
|
||||
err := b.setBrightnessImmediateWithExponent(id, pending.percent)
|
||||
if err != nil {
|
||||
log.Debugf("Failed to set brightness for %s: %v", id, err)
|
||||
}
|
||||
|
||||
if pending.callback != nil {
|
||||
pending.callback()
|
||||
}
|
||||
})
|
||||
if existing, exists := b.debounceTimers[id]; exists {
|
||||
if existing.Stop() {
|
||||
b.debounceWg.Done()
|
||||
}
|
||||
}
|
||||
|
||||
b.debounceWg.Add(1)
|
||||
b.debounceTimers[id] = time.AfterFunc(200*time.Millisecond, func() {
|
||||
defer b.debounceWg.Done()
|
||||
|
||||
b.debounceMutex.Lock()
|
||||
pending, hasPending := b.debouncePending[id]
|
||||
delete(b.debouncePending, id)
|
||||
delete(b.debounceTimers, id)
|
||||
b.debounceMutex.Unlock()
|
||||
|
||||
if !hasPending {
|
||||
return
|
||||
}
|
||||
|
||||
if err := b.setBrightnessImmediateWithExponent(id, pending.percent); err != nil {
|
||||
log.Debugf("Failed to set brightness for %s: %v", id, err)
|
||||
}
|
||||
|
||||
if pending.callback != nil {
|
||||
pending.callback()
|
||||
}
|
||||
})
|
||||
b.debounceMutex.Unlock()
|
||||
|
||||
return nil
|
||||
@@ -490,5 +493,19 @@ func (b *DDCBackend) valueToPercent(value int, max int, exponential bool) int {
|
||||
return percent
|
||||
}
|
||||
|
||||
func (b *DDCBackend) WaitPending() {
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
b.debounceWg.Wait()
|
||||
close(done)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(5 * time.Second):
|
||||
log.Debug("WaitPending timed out waiting for DDC writes")
|
||||
}
|
||||
}
|
||||
|
||||
func (b *DDCBackend) Close() {
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@ type DDCBackend struct {
|
||||
debounceMutex sync.Mutex
|
||||
debounceTimers map[string]*time.Timer
|
||||
debouncePending map[string]ddcPendingSet
|
||||
debounceWg sync.WaitGroup
|
||||
}
|
||||
|
||||
type ddcPendingSet struct {
|
||||
|
||||
@@ -212,9 +212,10 @@ func (m *Manager) setupDataDeviceSync() {
|
||||
}
|
||||
|
||||
var offer any
|
||||
if e.Id != nil {
|
||||
switch {
|
||||
case e.Id != nil:
|
||||
offer = e.Id
|
||||
} else if e.OfferId != 0 {
|
||||
case e.OfferId != 0:
|
||||
m.offerMutex.RLock()
|
||||
offer = m.offerRegistry[e.OfferId]
|
||||
m.offerMutex.RUnlock()
|
||||
@@ -224,10 +225,6 @@ func (m *Manager) setupDataDeviceSync() {
|
||||
wasOwner := m.isOwner
|
||||
m.ownerLock.Unlock()
|
||||
|
||||
if offer == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if wasOwner {
|
||||
return
|
||||
}
|
||||
@@ -236,9 +233,11 @@ func (m *Manager) setupDataDeviceSync() {
|
||||
m.currentOffer = offer
|
||||
|
||||
if prevOffer != nil && prevOffer != offer {
|
||||
m.offerMutex.Lock()
|
||||
delete(m.offerMimeTypes, prevOffer)
|
||||
m.offerMutex.Unlock()
|
||||
m.releaseOffer(prevOffer)
|
||||
}
|
||||
|
||||
if offer == nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.offerMutex.RLock()
|
||||
@@ -292,6 +291,33 @@ func (m *Manager) setupDataDeviceSync() {
|
||||
log.Info("Data device setup complete")
|
||||
}
|
||||
|
||||
func (m *Manager) releaseOffer(offer any) {
|
||||
if offer == nil {
|
||||
return
|
||||
}
|
||||
typedOffer, ok := offer.(*ext_data_control.ExtDataControlOfferV1)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
m.offerMutex.Lock()
|
||||
delete(m.offerMimeTypes, offer)
|
||||
delete(m.offerRegistry, typedOffer.ID())
|
||||
m.offerMutex.Unlock()
|
||||
typedOffer.Destroy()
|
||||
}
|
||||
|
||||
func (m *Manager) releaseCurrentSource() {
|
||||
if m.currentSource == nil {
|
||||
return
|
||||
}
|
||||
source, ok := m.currentSource.(*ext_data_control.ExtDataControlSourceV1)
|
||||
m.currentSource = nil
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
source.Destroy()
|
||||
}
|
||||
|
||||
func (m *Manager) readAndStore(r *os.File, mimeType string) {
|
||||
defer r.Close()
|
||||
|
||||
@@ -395,7 +421,7 @@ func (m *Manager) deduplicateInTx(b *bolt.Bucket, hash uint64) error {
|
||||
if extractHash(v) != hash {
|
||||
continue
|
||||
}
|
||||
entry, err := decodeEntry(v)
|
||||
entry, err := decodeEntryMeta(v)
|
||||
if err == nil && entry.Pinned {
|
||||
continue
|
||||
}
|
||||
@@ -413,7 +439,7 @@ func (m *Manager) trimLengthInTx(b *bolt.Bucket) error {
|
||||
c := b.Cursor()
|
||||
var count int
|
||||
for k, v := c.Last(); k != nil; k, v = c.Prev() {
|
||||
entry, err := decodeEntry(v)
|
||||
entry, err := decodeEntryMeta(v)
|
||||
if err == nil && entry.Pinned {
|
||||
continue
|
||||
}
|
||||
@@ -456,6 +482,14 @@ func encodeEntry(e Entry) ([]byte, error) {
|
||||
}
|
||||
|
||||
func decodeEntry(data []byte) (Entry, error) {
|
||||
return decodeEntryFields(data, true)
|
||||
}
|
||||
|
||||
func decodeEntryMeta(data []byte) (Entry, error) {
|
||||
return decodeEntryFields(data, false)
|
||||
}
|
||||
|
||||
func decodeEntryFields(data []byte, withData bool) (Entry, error) {
|
||||
buf := bytes.NewReader(data)
|
||||
var e Entry
|
||||
|
||||
@@ -463,8 +497,15 @@ func decodeEntry(data []byte) (Entry, error) {
|
||||
|
||||
var dataLen uint32
|
||||
binary.Read(buf, binary.BigEndian, &dataLen)
|
||||
e.Data = make([]byte, dataLen)
|
||||
buf.Read(e.Data)
|
||||
switch {
|
||||
case withData:
|
||||
e.Data = make([]byte, dataLen)
|
||||
buf.Read(e.Data)
|
||||
default:
|
||||
if _, err := buf.Seek(int64(dataLen), io.SeekCurrent); err != nil {
|
||||
return e, err
|
||||
}
|
||||
}
|
||||
|
||||
var mimeLen uint32
|
||||
binary.Read(buf, binary.BigEndian, &mimeLen)
|
||||
@@ -668,14 +709,9 @@ func sizeStr(size int) string {
|
||||
func (m *Manager) updateState() {
|
||||
history := m.GetHistory()
|
||||
|
||||
for i := range history {
|
||||
history[i].Data = nil
|
||||
}
|
||||
|
||||
var current *Entry
|
||||
if len(history) > 0 {
|
||||
c := history[0]
|
||||
c.Data = nil
|
||||
current = &c
|
||||
}
|
||||
|
||||
@@ -750,7 +786,7 @@ func (m *Manager) GetHistory() []Entry {
|
||||
c := b.Cursor()
|
||||
|
||||
for k, v := c.Last(); k != nil; k, v = c.Prev() {
|
||||
entry, err := decodeEntry(v)
|
||||
entry, err := decodeEntryMeta(v)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -935,7 +971,7 @@ func (m *Manager) ClearHistory() {
|
||||
var toDelete [][]byte
|
||||
c := b.Cursor()
|
||||
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||
entry, err := decodeEntry(v)
|
||||
entry, err := decodeEntryMeta(v)
|
||||
if err != nil || !entry.Pinned {
|
||||
toDelete = append(toDelete, k)
|
||||
}
|
||||
@@ -958,7 +994,7 @@ func (m *Manager) ClearHistory() {
|
||||
if b != nil {
|
||||
c := b.Cursor()
|
||||
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||
entry, _ := decodeEntry(v)
|
||||
entry, _ := decodeEntryMeta(v)
|
||||
if entry.Pinned {
|
||||
pinnedCount++
|
||||
}
|
||||
@@ -1066,6 +1102,7 @@ func (m *Manager) SetClipboard(data []byte, mimeType string) error {
|
||||
m.ownerLock.Unlock()
|
||||
})
|
||||
|
||||
m.releaseCurrentSource()
|
||||
m.currentSource = source
|
||||
m.sourceMutex.Lock()
|
||||
m.sourceMimeTypes = []string{mimeType}
|
||||
@@ -1145,9 +1182,11 @@ func (m *Manager) Close() {
|
||||
m.subscribers = make(map[string]chan State)
|
||||
m.subMutex.Unlock()
|
||||
|
||||
if m.currentSource != nil {
|
||||
source := m.currentSource.(*ext_data_control.ExtDataControlSourceV1)
|
||||
source.Destroy()
|
||||
m.releaseCurrentSource()
|
||||
|
||||
if m.currentOffer != nil {
|
||||
m.releaseOffer(m.currentOffer)
|
||||
m.currentOffer = nil
|
||||
}
|
||||
|
||||
if m.dataDevice != nil {
|
||||
@@ -1191,11 +1230,10 @@ func (m *Manager) clearOldEntries(days int) error {
|
||||
var toDelete [][]byte
|
||||
c := b.Cursor()
|
||||
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||
entry, err := decodeEntry(v)
|
||||
entry, err := decodeEntryMeta(v)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
// Skip pinned entries
|
||||
if entry.Pinned {
|
||||
continue
|
||||
}
|
||||
@@ -1310,7 +1348,7 @@ func (m *Manager) Search(params SearchParams) SearchResult {
|
||||
|
||||
c := b.Cursor()
|
||||
for k, v := c.Last(); k != nil; k, v = c.Prev() {
|
||||
entry, err := decodeEntry(v)
|
||||
entry, err := decodeEntryMeta(v)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -1335,7 +1373,6 @@ func (m *Manager) Search(params SearchParams) SearchResult {
|
||||
continue
|
||||
}
|
||||
|
||||
entry.Data = nil
|
||||
all = append(all, entry)
|
||||
}
|
||||
return nil
|
||||
@@ -1510,7 +1547,7 @@ func (m *Manager) PinEntry(id uint64) error {
|
||||
}
|
||||
c := b.Cursor()
|
||||
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||
entry, err := decodeEntry(v)
|
||||
entry, err := decodeEntryMeta(v)
|
||||
if err != nil || !entry.Pinned {
|
||||
continue
|
||||
}
|
||||
@@ -1528,7 +1565,6 @@ func (m *Manager) PinEntry(id uint64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check pinned count
|
||||
cfg := m.getConfig()
|
||||
pinnedCount := 0
|
||||
if err := m.db.View(func(tx *bolt.Tx) error {
|
||||
@@ -1538,7 +1574,7 @@ func (m *Manager) PinEntry(id uint64) error {
|
||||
}
|
||||
c := b.Cursor()
|
||||
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||
entry, err := decodeEntry(v)
|
||||
entry, err := decodeEntryMeta(v)
|
||||
if err == nil && entry.Pinned {
|
||||
pinnedCount++
|
||||
}
|
||||
@@ -1629,12 +1665,11 @@ func (m *Manager) GetPinnedEntries() []Entry {
|
||||
|
||||
c := b.Cursor()
|
||||
for k, v := c.Last(); k != nil; k, v = c.Prev() {
|
||||
entry, err := decodeEntry(v)
|
||||
entry, err := decodeEntryMeta(v)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if entry.Pinned {
|
||||
entry.Data = nil
|
||||
pinned = append(pinned, entry)
|
||||
}
|
||||
}
|
||||
@@ -1660,7 +1695,7 @@ func (m *Manager) GetPinnedCount() int {
|
||||
|
||||
c := b.Cursor()
|
||||
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||
entry, err := decodeEntry(v)
|
||||
entry, err := decodeEntryMeta(v)
|
||||
if err == nil && entry.Pinned {
|
||||
count++
|
||||
}
|
||||
@@ -1779,6 +1814,7 @@ func (m *Manager) CopyFile(filePath string) error {
|
||||
m.ownerLock.Unlock()
|
||||
})
|
||||
|
||||
m.releaseCurrentSource()
|
||||
m.currentSource = source
|
||||
|
||||
m.ownerLock.Lock()
|
||||
|
||||
@@ -158,18 +158,26 @@ func (b *NetworkManagerBackend) GetWiFiNetworkDetails(ssid string) (*NetworkInfo
|
||||
|
||||
channel := frequencyToChannel(freq)
|
||||
|
||||
isConnected := ssid == currentSSID && bssid == currentBSSID
|
||||
rate := maxBitrate / 1000
|
||||
if isConnected {
|
||||
if devBitrate, err := w.GetPropertyBitrate(); err == nil && devBitrate > 0 {
|
||||
rate = devBitrate / 1000
|
||||
}
|
||||
}
|
||||
|
||||
network := WiFiNetwork{
|
||||
SSID: ssid,
|
||||
BSSID: bssid,
|
||||
Signal: strength,
|
||||
Secured: secured,
|
||||
Enterprise: enterprise,
|
||||
Connected: ssid == currentSSID && bssid == currentBSSID,
|
||||
Connected: isConnected,
|
||||
Saved: savedSSIDs[ssid],
|
||||
Autoconnect: autoconnectMap[ssid],
|
||||
Frequency: freq,
|
||||
Mode: modeStr,
|
||||
Rate: maxBitrate / 1000,
|
||||
Rate: rate,
|
||||
Channel: channel,
|
||||
}
|
||||
|
||||
@@ -455,19 +463,27 @@ func (b *NetworkManagerBackend) updateWiFiNetworks() ([]WiFiNetwork, error) {
|
||||
|
||||
channel := frequencyToChannel(freq)
|
||||
|
||||
isConnected := ssid == currentSSID
|
||||
rate := maxBitrate / 1000
|
||||
if isConnected {
|
||||
if devBitrate, err := w.GetPropertyBitrate(); err == nil && devBitrate > 0 {
|
||||
rate = devBitrate / 1000
|
||||
}
|
||||
}
|
||||
|
||||
network := WiFiNetwork{
|
||||
SSID: ssid,
|
||||
BSSID: bssid,
|
||||
Signal: strength,
|
||||
Secured: secured,
|
||||
Enterprise: enterprise,
|
||||
Connected: ssid == currentSSID,
|
||||
Connected: isConnected,
|
||||
Saved: savedSSIDs[ssid],
|
||||
Autoconnect: autoconnectMap[ssid],
|
||||
Hidden: hiddenSSIDs[ssid],
|
||||
Frequency: freq,
|
||||
Mode: modeStr,
|
||||
Rate: maxBitrate / 1000,
|
||||
Rate: rate,
|
||||
Channel: channel,
|
||||
}
|
||||
|
||||
@@ -1003,19 +1019,27 @@ func (b *NetworkManagerBackend) updateAllWiFiDevices() {
|
||||
|
||||
channel := frequencyToChannel(freq)
|
||||
|
||||
isConnected := connected && apSSID == ssid
|
||||
rate := maxBitrate / 1000
|
||||
if isConnected {
|
||||
if devBitrate, err := devInfo.wireless.GetPropertyBitrate(); err == nil && devBitrate > 0 {
|
||||
rate = devBitrate / 1000
|
||||
}
|
||||
}
|
||||
|
||||
network := WiFiNetwork{
|
||||
SSID: apSSID,
|
||||
BSSID: apBSSID,
|
||||
Signal: strength,
|
||||
Secured: secured,
|
||||
Enterprise: enterprise,
|
||||
Connected: connected && apSSID == ssid,
|
||||
Connected: isConnected,
|
||||
Saved: savedSSIDs[apSSID],
|
||||
Autoconnect: autoconnectMap[apSSID],
|
||||
Hidden: hiddenSSIDs[apSSID],
|
||||
Frequency: freq,
|
||||
Mode: modeStr,
|
||||
Rate: maxBitrate / 1000,
|
||||
Rate: rate,
|
||||
Channel: channel,
|
||||
Device: name,
|
||||
}
|
||||
|
||||
10
flake.lock
generated
10
flake.lock
generated
@@ -23,16 +23,16 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1766725085,
|
||||
"narHash": "sha256-O2aMFdDUYJazFrlwL7aSIHbUSEm3ADVZjmf41uBJfHs=",
|
||||
"lastModified": 1776854048,
|
||||
"narHash": "sha256-lLbV66V3RMNp1l8/UelmR4YzoJ5ONtgvEtiUMJATH/o=",
|
||||
"ref": "refs/heads/master",
|
||||
"rev": "41828c4180fb921df7992a5405f5ff05d2ac2fff",
|
||||
"revCount": 715,
|
||||
"rev": "783c953987dc56ff0601abe6845ed96f1d00495a",
|
||||
"revCount": 806,
|
||||
"type": "git",
|
||||
"url": "https://git.outfoxxed.me/quickshell/quickshell"
|
||||
},
|
||||
"original": {
|
||||
"rev": "41828c4180fb921df7992a5405f5ff05d2ac2fff",
|
||||
"rev": "783c953987dc56ff0601abe6845ed96f1d00495a",
|
||||
"type": "git",
|
||||
"url": "https://git.outfoxxed.me/quickshell/quickshell"
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||
quickshell = {
|
||||
url = "git+https://git.outfoxxed.me/quickshell/quickshell?rev=41828c4180fb921df7992a5405f5ff05d2ac2fff";
|
||||
url = "git+https://git.outfoxxed.me/quickshell/quickshell?rev=783c953987dc56ff0601abe6845ed96f1d00495a";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
@@ -158,10 +158,16 @@ const NIRI_ACTIONS = {
|
||||
{ id: "focus-monitor-right", label: "Focus Monitor Right" },
|
||||
{ id: "focus-monitor-down", label: "Focus Monitor Down" },
|
||||
{ id: "focus-monitor-up", label: "Focus Monitor Up" },
|
||||
{ id: "move-column-to-monitor-left", label: "Move to Monitor Left" },
|
||||
{ id: "move-column-to-monitor-right", label: "Move to Monitor Right" },
|
||||
{ id: "move-column-to-monitor-down", label: "Move to Monitor Down" },
|
||||
{ id: "move-column-to-monitor-up", label: "Move to Monitor Up" }
|
||||
{ id: "move-column-to-monitor-left", label: "Move Column to Monitor Left" },
|
||||
{ id: "move-column-to-monitor-right", label: "Move Column to Monitor Right" },
|
||||
{ id: "move-column-to-monitor-down", label: "Move Column to Monitor Down" },
|
||||
{ id: "move-column-to-monitor-up", label: "Move Column to Monitor Up" },
|
||||
{ id: "move-workspace-to-monitor-left", label: "Move Workspace to Monitor Left" },
|
||||
{ id: "move-workspace-to-monitor-right", label: "Move Workspace to Monitor Right" },
|
||||
{ id: "move-workspace-to-monitor-down", label: "Move Workspace to Monitor Down" },
|
||||
{ id: "move-workspace-to-monitor-up", label: "Move Workspace to Monitor Up" },
|
||||
{ id: "move-workspace-to-monitor-next", label: "Move Workspace to Next Monitor" },
|
||||
{ id: "move-workspace-to-monitor-previous", label: "Move Workspace to Previous Monitor" }
|
||||
],
|
||||
"Screenshot": [
|
||||
{ id: "screenshot", label: "Screenshot (Interactive)" },
|
||||
|
||||
@@ -23,7 +23,9 @@ Singleton {
|
||||
}
|
||||
|
||||
function expandTilde(path: string): string {
|
||||
return strip(path.replace("~", stringify(root.home)));
|
||||
if (!path.startsWith("~"))
|
||||
return path;
|
||||
return strip(root.home) + path.substring(1);
|
||||
}
|
||||
|
||||
function shortenHome(path: string): string {
|
||||
|
||||
@@ -338,8 +338,8 @@ Singleton {
|
||||
|
||||
function setLightMode(lightMode) {
|
||||
isSwitchingMode = true;
|
||||
syncWallpaperForCurrentMode(lightMode);
|
||||
isLightMode = lightMode;
|
||||
syncWallpaperForCurrentMode();
|
||||
saveSettings();
|
||||
Qt.callLater(() => {
|
||||
isSwitchingMode = false;
|
||||
@@ -1091,15 +1091,16 @@ Singleton {
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function syncWallpaperForCurrentMode() {
|
||||
function syncWallpaperForCurrentMode(mode) {
|
||||
if (!perModeWallpaper)
|
||||
return;
|
||||
var light = (mode !== undefined) ? mode : isLightMode;
|
||||
if (perMonitorWallpaper) {
|
||||
monitorWallpapers = isLightMode ? Object.assign({}, monitorWallpapersLight) : Object.assign({}, monitorWallpapersDark);
|
||||
monitorWallpapers = light ? Object.assign({}, monitorWallpapersLight) : Object.assign({}, monitorWallpapersDark);
|
||||
return;
|
||||
}
|
||||
|
||||
wallpaperPath = isLightMode ? wallpaperPathLight : wallpaperPathDark;
|
||||
wallpaperPath = light ? wallpaperPathLight : wallpaperPathDark;
|
||||
}
|
||||
|
||||
function _findMonitorValue(map, screenName) {
|
||||
|
||||
@@ -165,6 +165,18 @@ Singleton {
|
||||
property int modalCustomAnimationDuration: 150
|
||||
property bool enableRippleEffects: true
|
||||
onEnableRippleEffectsChanged: saveSettings()
|
||||
property bool blurEnabled: false
|
||||
onBlurEnabledChanged: saveSettings()
|
||||
property bool blurForegroundLayers: true
|
||||
onBlurForegroundLayersChanged: saveSettings()
|
||||
property real blurLayerOutlineOpacity: 0.12
|
||||
onBlurLayerOutlineOpacityChanged: saveSettings()
|
||||
property string blurBorderColor: "outline"
|
||||
onBlurBorderColorChanged: saveSettings()
|
||||
property string blurBorderCustomColor: "#ffffff"
|
||||
onBlurBorderCustomColorChanged: saveSettings()
|
||||
property real blurBorderOpacity: 0.35
|
||||
onBlurBorderOpacityChanged: saveSettings()
|
||||
property string wallpaperFillMode: "Fill"
|
||||
property bool blurredWallpaperLayer: false
|
||||
property bool blurWallpaperOnOverview: false
|
||||
@@ -182,6 +194,9 @@ Singleton {
|
||||
property int selectedGpuIndex: 0
|
||||
property var enabledGpuPciIds: []
|
||||
property bool showSystemTray: true
|
||||
property string systemTrayIconTintMode: "none"
|
||||
property int systemTrayIconTintSaturation: 50
|
||||
property int systemTrayIconTintStrength: 135
|
||||
property bool showClock: true
|
||||
property bool showNotificationButton: true
|
||||
property bool showBattery: true
|
||||
@@ -1284,9 +1299,7 @@ Singleton {
|
||||
return true;
|
||||
|
||||
const msg = String(error || "").toLowerCase();
|
||||
return msg.indexOf("file does not exist") !== -1
|
||||
|| msg.indexOf("no such file") !== -1
|
||||
|| msg.indexOf("enoent") !== -1;
|
||||
return msg.indexOf("file does not exist") !== -1 || msg.indexOf("no such file") !== -1 || msg.indexOf("enoent") !== -1;
|
||||
}
|
||||
|
||||
function loadPluginSettings() {
|
||||
|
||||
@@ -346,12 +346,11 @@ Singleton {
|
||||
function onLoginctlEvent(event) {
|
||||
if (!SessionData.themeModeAutoEnabled)
|
||||
return;
|
||||
if (event.event === "unlock" || event.event === "resume") {
|
||||
if (!themeAutoBackendAvailable()) {
|
||||
root.evaluateThemeMode();
|
||||
return;
|
||||
}
|
||||
DMSService.sendRequest("theme.auto.trigger", {});
|
||||
if (typeof SettingsData !== "undefined" && SettingsData.loginctlLockIntegration)
|
||||
return;
|
||||
const eventType = String(event?.type || event?.event || "").toLowerCase();
|
||||
if (eventType === "unlock") {
|
||||
root.triggerThemeAutomationRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,6 +413,27 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SessionService
|
||||
enabled: typeof SessionService !== "undefined" && typeof SessionData !== "undefined" && SessionData.themeModeAutoEnabled
|
||||
|
||||
function onSessionUnlocked() {
|
||||
root.triggerThemeAutomationRefresh();
|
||||
}
|
||||
|
||||
function onSessionResumed() {
|
||||
root.triggerThemeAutomationRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
function triggerThemeAutomationRefresh() {
|
||||
if (!themeAutoBackendAvailable()) {
|
||||
root.evaluateThemeMode();
|
||||
return;
|
||||
}
|
||||
DMSService.sendRequest("theme.auto.trigger", {});
|
||||
}
|
||||
|
||||
function applyGreeterTheme(themeName) {
|
||||
switchTheme(themeName, false, false);
|
||||
if (themeName === dynamic && dynamicColorsFileView.path) {
|
||||
@@ -541,8 +561,8 @@ Singleton {
|
||||
property color success: currentThemeData.success || "#4CAF50"
|
||||
|
||||
property color primaryHover: Qt.rgba(primary.r, primary.g, primary.b, 0.12)
|
||||
property color primaryHoverLight: Qt.rgba(primary.r, primary.g, primary.b, 0.08)
|
||||
property color primaryPressed: Qt.rgba(primary.r, primary.g, primary.b, 0.16)
|
||||
property color primaryHoverLight: Qt.rgba(primary.r, primary.g, primary.b, transparentBlurLayers ? 0.12 : 0.08)
|
||||
property color primaryPressed: Qt.rgba(primary.r, primary.g, primary.b, transparentBlurLayers ? 0.24 : 0.16)
|
||||
property color primarySelected: Qt.rgba(primary.r, primary.g, primary.b, 0.3)
|
||||
property color primaryBackground: Qt.rgba(primary.r, primary.g, primary.b, 0.04)
|
||||
|
||||
@@ -551,17 +571,28 @@ Singleton {
|
||||
property color surfaceHover: Qt.rgba(surfaceVariant.r, surfaceVariant.g, surfaceVariant.b, 0.08)
|
||||
property color surfacePressed: Qt.rgba(surfaceVariant.r, surfaceVariant.g, surfaceVariant.b, 0.12)
|
||||
property color surfaceSelected: Qt.rgba(surfaceVariant.r, surfaceVariant.g, surfaceVariant.b, 0.15)
|
||||
property color surfaceLight: Qt.rgba(surfaceVariant.r, surfaceVariant.g, surfaceVariant.b, 0.1)
|
||||
property color surfaceLight: Qt.rgba(surfaceVariant.r, surfaceVariant.g, surfaceVariant.b, transparentBlurLayers ? 0.3 : 0.1)
|
||||
property color surfaceVariantAlpha: Qt.rgba(surfaceVariant.r, surfaceVariant.g, surfaceVariant.b, 0.2)
|
||||
|
||||
readonly property bool blurForegroundLayers: BlurService.enabled && (typeof SettingsData === "undefined" || (SettingsData.blurForegroundLayers ?? true))
|
||||
readonly property bool transparentBlurLayers: BlurService.enabled && !blurForegroundLayers
|
||||
readonly property color readableSurface: withAlpha(surfaceContainer, popupTransparency)
|
||||
readonly property color readableSurfaceHigh: withAlpha(surfaceContainerHigh, popupTransparency)
|
||||
readonly property color floatingSurface: transparentBlurLayers ? "transparent" : readableSurface
|
||||
readonly property color floatingSurfaceHigh: transparentBlurLayers ? "transparent" : readableSurfaceHigh
|
||||
readonly property color nestedSurface: floatingSurfaceHigh
|
||||
readonly property real blurLayerOutlineOpacity: Math.max(0, Math.min(1, typeof SettingsData === "undefined" ? 0.12 : (SettingsData.blurLayerOutlineOpacity ?? 0.12)))
|
||||
readonly property real layerOutlineOpacity: BlurService.enabled ? blurLayerOutlineOpacity : 0.08
|
||||
readonly property int layerOutlineWidth: BlurService.enabled && layerOutlineOpacity > 0 ? 1 : 0
|
||||
property color surfaceTextHover: Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.08)
|
||||
property color surfaceTextAlpha: Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.3)
|
||||
property color surfaceTextLight: Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.06)
|
||||
property color surfaceTextMedium: Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.7)
|
||||
|
||||
property color outlineButton: Qt.rgba(outline.r, outline.g, outline.b, 0.5)
|
||||
property color outlineLight: Qt.rgba(outline.r, outline.g, outline.b, 0.05)
|
||||
property color outlineMedium: Qt.rgba(outline.r, outline.g, outline.b, 0.08)
|
||||
property color outlineStrong: Qt.rgba(outline.r, outline.g, outline.b, 0.12)
|
||||
property color outlineLight: Qt.rgba(outline.r, outline.g, outline.b, BlurService.enabled ? Math.min(1, layerOutlineOpacity * 0.625) : 0.05)
|
||||
property color outlineMedium: Qt.rgba(outline.r, outline.g, outline.b, layerOutlineOpacity)
|
||||
property color outlineStrong: Qt.rgba(outline.r, outline.g, outline.b, BlurService.enabled ? Math.min(1, layerOutlineOpacity * 1.5) : 0.12)
|
||||
|
||||
property color errorHover: Qt.rgba(error.r, error.g, error.b, 0.12)
|
||||
property color errorPressed: Qt.rgba(error.r, error.g, error.b, 0.16)
|
||||
@@ -579,6 +610,12 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
readonly property color ccTileInactiveBg: transparentBlurLayers ? withAlpha(surfaceContainerHigh, 0.16) : (blurForegroundLayers ? withAlpha(surfaceContainerHigh, Math.min(popupTransparency, 0.24)) : withAlpha(surfaceContainer, popupTransparency))
|
||||
readonly property color ccPillInactiveBg: transparentBlurLayers ? withAlpha(surfaceContainerHigh, 0.08) : nestedSurface
|
||||
readonly property color ccPillInactiveHoverBg: transparentBlurLayers ? withAlpha(primary, 0.10) : primaryPressed
|
||||
readonly property color ccSliderTrackColor: transparentBlurLayers ? surfaceText : surfaceContainerHigh
|
||||
readonly property real ccSliderTrackOpacity: transparentBlurLayers ? 0.18 : popupTransparency
|
||||
|
||||
readonly property color ccTileActiveText: {
|
||||
switch (SettingsData.controlCenterTileColorMode) {
|
||||
case "primaryContainer":
|
||||
@@ -1100,7 +1137,7 @@ Singleton {
|
||||
}
|
||||
|
||||
function loadCustomThemeFromFile(filePath) {
|
||||
customThemeFileView.path = filePath;
|
||||
customThemeFileView.path = Paths.expandTilde(filePath);
|
||||
}
|
||||
|
||||
function reloadCustomThemeVariant() {
|
||||
@@ -1749,6 +1786,7 @@ Singleton {
|
||||
|
||||
FileView {
|
||||
id: customThemeFileView
|
||||
blockLoading: false
|
||||
watchChanges: currentTheme === "custom"
|
||||
|
||||
function parseAndLoadTheme() {
|
||||
|
||||
@@ -46,6 +46,12 @@ var SPEC = {
|
||||
modalAnimationSpeed: { def: 1 },
|
||||
modalCustomAnimationDuration: { def: 150 },
|
||||
enableRippleEffects: { def: true },
|
||||
blurEnabled: { def: false },
|
||||
blurForegroundLayers: { def: true },
|
||||
blurLayerOutlineOpacity: { def: 0.12, coerce: percentToUnit },
|
||||
blurBorderColor: { def: "outline" },
|
||||
blurBorderCustomColor: { def: "#ffffff" },
|
||||
blurBorderOpacity: { def: 0.35, coerce: percentToUnit },
|
||||
wallpaperFillMode: { def: "Fill" },
|
||||
blurredWallpaperLayer: { def: false },
|
||||
blurWallpaperOnOverview: { def: false },
|
||||
@@ -63,6 +69,9 @@ var SPEC = {
|
||||
selectedGpuIndex: { def: 0 },
|
||||
enabledGpuPciIds: { def: [] },
|
||||
showSystemTray: { def: true },
|
||||
systemTrayIconTintMode: { def: "none" },
|
||||
systemTrayIconTintSaturation: { def: 50 },
|
||||
systemTrayIconTintStrength: { def: 135 },
|
||||
showClock: { def: true },
|
||||
showNotificationButton: { def: true },
|
||||
showBattery: { def: true },
|
||||
|
||||
@@ -27,6 +27,15 @@ import qs.Services
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property bool osdSurfacesLoaded: true
|
||||
property int pendingOsdResumeReloads: 0
|
||||
|
||||
function recreateOsdSurfaces() {
|
||||
OSDManager.currentOSDsByScreen = ({});
|
||||
osdSurfacesLoaded = false;
|
||||
osdSurfaceReloadTimer.restart();
|
||||
}
|
||||
|
||||
Instantiator {
|
||||
id: daemonPluginInstantiator
|
||||
asynchronous: true
|
||||
@@ -221,6 +230,33 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: osdResumeRecreateTimer
|
||||
interval: 400
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
root.recreateOsdSurfaces();
|
||||
|
||||
if (root.pendingOsdResumeReloads > 1) {
|
||||
root.pendingOsdResumeReloads--;
|
||||
interval = 1400;
|
||||
restart();
|
||||
return;
|
||||
}
|
||||
|
||||
root.pendingOsdResumeReloads = 0;
|
||||
interval = 400;
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: osdSurfaceReloadTimer
|
||||
interval: 120
|
||||
repeat: false
|
||||
onTriggered: root.osdSurfacesLoaded = true
|
||||
}
|
||||
|
||||
|
||||
Component.onCompleted: {
|
||||
dockRecreateDebounce.start();
|
||||
// Force PolkitService singleton to initialize
|
||||
@@ -716,6 +752,16 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SessionService
|
||||
|
||||
function onSessionResumed() {
|
||||
root.pendingOsdResumeReloads = 2;
|
||||
osdResumeRecreateTimer.interval = 400;
|
||||
osdResumeRecreateTimer.restart();
|
||||
}
|
||||
}
|
||||
|
||||
DankColorPickerModal {
|
||||
id: colorPickerModal
|
||||
|
||||
@@ -890,51 +936,85 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Variants {
|
||||
model: SettingsData.getFilteredScreens("osd")
|
||||
Loader {
|
||||
id: osdSurfacesLoader
|
||||
active: root.osdSurfacesLoaded
|
||||
asynchronous: false
|
||||
|
||||
delegate: VolumeOSD {
|
||||
modelData: item
|
||||
}
|
||||
}
|
||||
sourceComponent: Component {
|
||||
Item {
|
||||
Variants {
|
||||
model: SettingsData.getFilteredScreens("osd")
|
||||
|
||||
Variants {
|
||||
model: SettingsData.getFilteredScreens("osd")
|
||||
delegate: VolumeOSD {
|
||||
modelData: item
|
||||
}
|
||||
}
|
||||
|
||||
delegate: MediaVolumeOSD {
|
||||
modelData: item
|
||||
}
|
||||
}
|
||||
Variants {
|
||||
model: SettingsData.getFilteredScreens("osd")
|
||||
|
||||
Variants {
|
||||
model: SettingsData.getFilteredScreens("osd")
|
||||
delegate: MediaVolumeOSD {
|
||||
modelData: item
|
||||
}
|
||||
}
|
||||
|
||||
delegate: MediaPlaybackOSD {
|
||||
modelData: item
|
||||
}
|
||||
}
|
||||
Variants {
|
||||
model: SettingsData.getFilteredScreens("osd")
|
||||
|
||||
Variants {
|
||||
model: SettingsData.getFilteredScreens("osd")
|
||||
delegate: MediaPlaybackOSD {
|
||||
modelData: item
|
||||
}
|
||||
}
|
||||
|
||||
delegate: MicMuteOSD {
|
||||
modelData: item
|
||||
}
|
||||
}
|
||||
Variants {
|
||||
model: SettingsData.getFilteredScreens("osd")
|
||||
|
||||
Variants {
|
||||
model: SettingsData.getFilteredScreens("osd")
|
||||
delegate: MicMuteOSD {
|
||||
modelData: item
|
||||
}
|
||||
}
|
||||
|
||||
delegate: BrightnessOSD {
|
||||
modelData: item
|
||||
}
|
||||
}
|
||||
Variants {
|
||||
model: SettingsData.getFilteredScreens("osd")
|
||||
|
||||
Variants {
|
||||
model: SettingsData.getFilteredScreens("osd")
|
||||
delegate: BrightnessOSD {
|
||||
modelData: item
|
||||
}
|
||||
}
|
||||
|
||||
delegate: IdleInhibitorOSD {
|
||||
modelData: item
|
||||
Variants {
|
||||
model: SettingsData.getFilteredScreens("osd")
|
||||
|
||||
delegate: IdleInhibitorOSD {
|
||||
modelData: item
|
||||
}
|
||||
}
|
||||
|
||||
Variants {
|
||||
model: SettingsData.osdPowerProfileEnabled ? SettingsData.getFilteredScreens("osd") : []
|
||||
|
||||
delegate: PowerProfileOSD {
|
||||
modelData: item
|
||||
}
|
||||
}
|
||||
|
||||
Variants {
|
||||
model: SettingsData.getFilteredScreens("osd")
|
||||
|
||||
delegate: CapsLockOSD {
|
||||
modelData: item
|
||||
}
|
||||
}
|
||||
|
||||
Variants {
|
||||
model: SettingsData.getFilteredScreens("osd")
|
||||
|
||||
delegate: AudioOutputOSD {
|
||||
modelData: item
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -944,30 +1024,6 @@ Item {
|
||||
source: "Services/PowerProfileWatcher.qml"
|
||||
}
|
||||
|
||||
Variants {
|
||||
model: SettingsData.osdPowerProfileEnabled ? SettingsData.getFilteredScreens("osd") : []
|
||||
|
||||
delegate: PowerProfileOSD {
|
||||
modelData: item
|
||||
}
|
||||
}
|
||||
|
||||
Variants {
|
||||
model: SettingsData.getFilteredScreens("osd")
|
||||
|
||||
delegate: CapsLockOSD {
|
||||
modelData: item
|
||||
}
|
||||
}
|
||||
|
||||
Variants {
|
||||
model: SettingsData.getFilteredScreens("osd")
|
||||
|
||||
delegate: AudioOutputOSD {
|
||||
modelData: item
|
||||
}
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
id: hyprlandOverviewLoader
|
||||
active: CompositorService.isHyprland
|
||||
|
||||
@@ -3,6 +3,7 @@ import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -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)
|
||||
@@ -211,6 +226,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)
|
||||
@@ -385,6 +410,15 @@ Item {
|
||||
radius: root.cornerRadius
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -4,6 +4,7 @@ import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -124,40 +125,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() {
|
||||
@@ -181,14 +189,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)
|
||||
@@ -284,6 +298,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")) {
|
||||
@@ -413,6 +437,14 @@ Item {
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: root.cornerRadius
|
||||
color: "transparent"
|
||||
border.color: BlurService.borderColor
|
||||
border.width: BlurService.borderWidth
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +270,7 @@ FocusScope {
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
visible: !editMode
|
||||
visible: !editMode && !(root.parentModal?.isClosing ?? false)
|
||||
|
||||
Item {
|
||||
id: footerBar
|
||||
@@ -551,7 +551,6 @@ FocusScope {
|
||||
Item {
|
||||
width: parent.width
|
||||
height: parent.height - searchField.height - categoryRow.height - actionPanel.height - Theme.spacingXS * (categoryRow.visible ? 3 : 2)
|
||||
opacity: root.parentModal?.isClosing ? 0 : 1
|
||||
|
||||
ResultsList {
|
||||
id: resultsList
|
||||
|
||||
@@ -372,7 +372,7 @@ Popup {
|
||||
anchors.fill: parent
|
||||
implicitWidth: Math.max(180, menuColumn.implicitWidth + Theme.spacingS * 2)
|
||||
implicitHeight: menuColumn.implicitHeight + Theme.spacingS * 2
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
color: Theme.floatingSurface
|
||||
radius: Theme.cornerRadius
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
|
||||
@@ -58,9 +58,9 @@ Item {
|
||||
item: items[i],
|
||||
flatIndex: flatIdx,
|
||||
sectionId: sectionId,
|
||||
height: 52
|
||||
height: 56
|
||||
});
|
||||
cumY += 52;
|
||||
cumY += 56;
|
||||
}
|
||||
} else {
|
||||
var cols = root.controller?.getGridColumns(sectionId) ?? root.gridColumns;
|
||||
@@ -190,124 +190,135 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
DankListView {
|
||||
id: mainListView
|
||||
Item {
|
||||
id: listClip
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: BlurService.enabled && stickyHeader.visible ? 32 : 0
|
||||
clip: true
|
||||
scrollBarTopMargin: (root.controller?.sections?.length > 0) ? 32 : 0
|
||||
|
||||
model: ScriptModel {
|
||||
values: root._visualRows
|
||||
objectProp: "_rowId"
|
||||
}
|
||||
DankListView {
|
||||
id: mainListView
|
||||
y: -listClip.anchors.topMargin
|
||||
width: parent.width
|
||||
height: parent.height + listClip.anchors.topMargin
|
||||
clip: true
|
||||
scrollBarTopMargin: (root.controller?.sections?.length > 0) ? 32 : 0
|
||||
|
||||
add: null
|
||||
remove: null
|
||||
displaced: null
|
||||
move: null
|
||||
|
||||
delegate: Item {
|
||||
id: delegateRoot
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: mainListView.width
|
||||
height: modelData?.height ?? 52
|
||||
|
||||
SectionHeader {
|
||||
anchors.fill: parent
|
||||
visible: delegateRoot.modelData?.type === "header"
|
||||
section: delegateRoot.modelData?.section ?? null
|
||||
controller: root.controller
|
||||
viewMode: {
|
||||
var vt = root.controller?.viewModeVersion ?? 0;
|
||||
void (vt);
|
||||
return root.controller?.getSectionViewMode(delegateRoot.modelData?.sectionId ?? "") ?? "list";
|
||||
}
|
||||
canChangeViewMode: {
|
||||
var vt = root.controller?.viewModeVersion ?? 0;
|
||||
void (vt);
|
||||
return root.controller?.canChangeSectionViewMode(delegateRoot.modelData?.sectionId ?? "") ?? false;
|
||||
}
|
||||
canCollapse: root.controller?.canCollapseSection(delegateRoot.modelData?.sectionId ?? "") ?? false
|
||||
model: ScriptModel {
|
||||
values: root._visualRows
|
||||
objectProp: "_rowId"
|
||||
}
|
||||
|
||||
ResultItem {
|
||||
anchors.fill: parent
|
||||
visible: delegateRoot.modelData?.type === "list_item"
|
||||
item: delegateRoot.modelData?.type === "list_item" ? (delegateRoot.modelData?.item ?? null) : null
|
||||
isSelected: delegateRoot.modelData?.type === "list_item" && (delegateRoot.modelData?.flatIndex ?? -1) === root.controller?.selectedFlatIndex
|
||||
controller: root.controller
|
||||
flatIndex: delegateRoot.modelData?.type === "list_item" ? (delegateRoot.modelData?.flatIndex ?? -1) : -1
|
||||
add: null
|
||||
remove: null
|
||||
displaced: null
|
||||
move: null
|
||||
|
||||
onClicked: {
|
||||
if (root.controller && delegateRoot.modelData?.item) {
|
||||
root.controller.executeItem(delegateRoot.modelData.item);
|
||||
delegate: Item {
|
||||
id: delegateRoot
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: mainListView.width
|
||||
height: modelData?.height ?? 52
|
||||
|
||||
SectionHeader {
|
||||
anchors.fill: parent
|
||||
visible: delegateRoot.modelData?.type === "header"
|
||||
section: delegateRoot.modelData?.section ?? null
|
||||
controller: root.controller
|
||||
viewMode: {
|
||||
var vt = root.controller?.viewModeVersion ?? 0;
|
||||
void (vt);
|
||||
return root.controller?.getSectionViewMode(delegateRoot.modelData?.sectionId ?? "") ?? "list";
|
||||
}
|
||||
canChangeViewMode: {
|
||||
var vt = root.controller?.viewModeVersion ?? 0;
|
||||
void (vt);
|
||||
return root.controller?.canChangeSectionViewMode(delegateRoot.modelData?.sectionId ?? "") ?? false;
|
||||
}
|
||||
canCollapse: root.controller?.canCollapseSection(delegateRoot.modelData?.sectionId ?? "") ?? false
|
||||
}
|
||||
|
||||
ResultItem {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 2
|
||||
anchors.bottomMargin: 2
|
||||
visible: delegateRoot.modelData?.type === "list_item"
|
||||
item: delegateRoot.modelData?.type === "list_item" ? (delegateRoot.modelData?.item ?? null) : null
|
||||
isSelected: delegateRoot.modelData?.type === "list_item" && (delegateRoot.modelData?.flatIndex ?? -1) === root.controller?.selectedFlatIndex
|
||||
controller: root.controller
|
||||
flatIndex: delegateRoot.modelData?.type === "list_item" ? (delegateRoot.modelData?.flatIndex ?? -1) : -1
|
||||
|
||||
onClicked: {
|
||||
if (root.controller && delegateRoot.modelData?.item) {
|
||||
root.controller.executeItem(delegateRoot.modelData.item);
|
||||
}
|
||||
}
|
||||
|
||||
onRightClicked: (mouseX, mouseY) => {
|
||||
root.itemRightClicked(delegateRoot.modelData?.flatIndex ?? -1, delegateRoot.modelData?.item ?? null, mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
|
||||
onRightClicked: (mouseX, mouseY) => {
|
||||
root.itemRightClicked(delegateRoot.modelData?.flatIndex ?? -1, delegateRoot.modelData?.item ?? null, mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
Row {
|
||||
id: gridRowContent
|
||||
anchors.fill: parent
|
||||
visible: delegateRoot.modelData?.type === "grid_row"
|
||||
|
||||
Row {
|
||||
id: gridRowContent
|
||||
anchors.fill: parent
|
||||
visible: delegateRoot.modelData?.type === "grid_row"
|
||||
Repeater {
|
||||
model: delegateRoot.modelData?.type === "grid_row" ? (delegateRoot.modelData?.items ?? []) : []
|
||||
|
||||
Repeater {
|
||||
model: delegateRoot.modelData?.type === "grid_row" ? (delegateRoot.modelData?.items ?? []) : []
|
||||
Item {
|
||||
id: gridCellDelegate
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
Item {
|
||||
id: gridCellDelegate
|
||||
required property var modelData
|
||||
required property int index
|
||||
readonly property real cellWidth: delegateRoot.modelData?.viewMode === "tile" ? Math.floor(delegateRoot.width / 3) : Math.floor(delegateRoot.width / (delegateRoot.modelData?.cols ?? root.gridColumns))
|
||||
|
||||
readonly property real cellWidth: delegateRoot.modelData?.viewMode === "tile" ? Math.floor(delegateRoot.width / 3) : Math.floor(delegateRoot.width / (delegateRoot.modelData?.cols ?? root.gridColumns))
|
||||
width: cellWidth
|
||||
height: delegateRoot.height
|
||||
|
||||
width: cellWidth
|
||||
height: delegateRoot.height
|
||||
GridItem {
|
||||
width: parent.width - 4
|
||||
height: parent.height - 4
|
||||
anchors.centerIn: parent
|
||||
visible: delegateRoot.modelData?.viewMode === "grid"
|
||||
item: gridCellDelegate.modelData?.item ?? null
|
||||
isSelected: (gridCellDelegate.modelData?.flatIndex ?? -1) === root.controller?.selectedFlatIndex
|
||||
controller: root.controller
|
||||
flatIndex: gridCellDelegate.modelData?.flatIndex ?? -1
|
||||
|
||||
GridItem {
|
||||
width: parent.width - 4
|
||||
height: parent.height - 4
|
||||
anchors.centerIn: parent
|
||||
visible: delegateRoot.modelData?.viewMode === "grid"
|
||||
item: gridCellDelegate.modelData?.item ?? null
|
||||
isSelected: (gridCellDelegate.modelData?.flatIndex ?? -1) === root.controller?.selectedFlatIndex
|
||||
controller: root.controller
|
||||
flatIndex: gridCellDelegate.modelData?.flatIndex ?? -1
|
||||
onClicked: {
|
||||
if (root.controller && gridCellDelegate.modelData?.item) {
|
||||
root.controller.executeItem(gridCellDelegate.modelData.item);
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
if (root.controller && gridCellDelegate.modelData?.item) {
|
||||
root.controller.executeItem(gridCellDelegate.modelData.item);
|
||||
onRightClicked: (mouseX, mouseY) => {
|
||||
root.itemRightClicked(gridCellDelegate.modelData?.flatIndex ?? -1, gridCellDelegate.modelData?.item ?? null, mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
|
||||
onRightClicked: (mouseX, mouseY) => {
|
||||
root.itemRightClicked(gridCellDelegate.modelData?.flatIndex ?? -1, gridCellDelegate.modelData?.item ?? null, mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
TileItem {
|
||||
width: parent.width - 4
|
||||
height: parent.height - 4
|
||||
anchors.centerIn: parent
|
||||
visible: delegateRoot.modelData?.viewMode === "tile"
|
||||
item: gridCellDelegate.modelData?.item ?? null
|
||||
isSelected: (gridCellDelegate.modelData?.flatIndex ?? -1) === root.controller?.selectedFlatIndex
|
||||
controller: root.controller
|
||||
flatIndex: gridCellDelegate.modelData?.flatIndex ?? -1
|
||||
|
||||
TileItem {
|
||||
width: parent.width - 4
|
||||
height: parent.height - 4
|
||||
anchors.centerIn: parent
|
||||
visible: delegateRoot.modelData?.viewMode === "tile"
|
||||
item: gridCellDelegate.modelData?.item ?? null
|
||||
isSelected: (gridCellDelegate.modelData?.flatIndex ?? -1) === root.controller?.selectedFlatIndex
|
||||
controller: root.controller
|
||||
flatIndex: gridCellDelegate.modelData?.flatIndex ?? -1
|
||||
|
||||
onClicked: {
|
||||
if (root.controller && gridCellDelegate.modelData?.item) {
|
||||
root.controller.executeItem(gridCellDelegate.modelData.item);
|
||||
onClicked: {
|
||||
if (root.controller && gridCellDelegate.modelData?.item) {
|
||||
root.controller.executeItem(gridCellDelegate.modelData.item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onRightClicked: (mouseX, mouseY) => {
|
||||
root.itemRightClicked(gridCellDelegate.modelData?.flatIndex ?? -1, gridCellDelegate.modelData?.item ?? null, mouseX, mouseY);
|
||||
onRightClicked: (mouseX, mouseY) => {
|
||||
root.itemRightClicked(gridCellDelegate.modelData?.flatIndex ?? -1, gridCellDelegate.modelData?.item ?? null, mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -324,6 +335,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;
|
||||
@@ -363,7 +376,7 @@ Item {
|
||||
anchors.top: parent.top
|
||||
height: 32
|
||||
z: 101
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
color: Theme.floatingSurface
|
||||
visible: stickyHeaderSection !== null
|
||||
|
||||
readonly property int versionTrigger: root.controller?.viewModeVersion ?? 0
|
||||
@@ -441,7 +454,7 @@ Item {
|
||||
case "apps":
|
||||
return "apps";
|
||||
default:
|
||||
return root.controller?.searchQuery?.length > 0 ? "search_off" : "search";
|
||||
return "search_off";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -469,9 +482,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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ Item {
|
||||
id: listComponent
|
||||
|
||||
Column {
|
||||
spacing: 2
|
||||
spacing: 4
|
||||
width: contentLoader.width
|
||||
|
||||
Repeater {
|
||||
|
||||
@@ -136,7 +136,7 @@ DankPopout {
|
||||
QtObject {
|
||||
id: modalAdapter
|
||||
property bool spotlightOpen: appDrawerPopout.shouldBeVisible
|
||||
property bool isClosing: false
|
||||
readonly property bool isClosing: !appDrawerPopout.shouldBeVisible
|
||||
|
||||
function hide() {
|
||||
appDrawerPopout.close();
|
||||
|
||||
@@ -58,7 +58,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
property bool enabled: isInstance ? (instanceData?.enabled ?? true) : SettingsData.desktopClockEnabled
|
||||
enabled: isInstance ? (instanceData?.enabled ?? true) : SettingsData.desktopClockEnabled
|
||||
property real transparency: isInstance ? (cfg.transparency ?? 0.8) : SettingsData.desktopClockTransparency
|
||||
property string colorMode: isInstance ? (cfg.colorMode ?? "primary") : SettingsData.desktopClockColorMode
|
||||
property color customColor: isInstance ? (cfg.customColor ?? "#ffffff") : SettingsData.desktopClockCustomColor
|
||||
|
||||
@@ -37,7 +37,7 @@ Item {
|
||||
readonly property var cfg: instanceData?.config ?? null
|
||||
readonly property bool isInstance: instanceId !== "" && cfg !== null
|
||||
|
||||
property bool enabled: isInstance ? (instanceData?.enabled ?? true) : SettingsData.systemMonitorEnabled
|
||||
enabled: isInstance ? (instanceData?.enabled ?? true) : SettingsData.systemMonitorEnabled
|
||||
property bool showHeader: isInstance ? (cfg.showHeader ?? true) : SettingsData.systemMonitorShowHeader
|
||||
property real transparency: isInstance ? (cfg.transparency ?? 0.8) : SettingsData.systemMonitorTransparency
|
||||
property string colorMode: isInstance ? (cfg.colorMode ?? "primary") : SettingsData.systemMonitorColorMode
|
||||
|
||||
@@ -34,7 +34,9 @@ PluginComponent {
|
||||
id: detailRoot
|
||||
implicitHeight: detailColumn.implicitHeight + Theme.spacingM * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
color: Theme.nestedSurface
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: Theme.layerOutlineWidth
|
||||
|
||||
DankActionButton {
|
||||
anchors.top: parent.top
|
||||
@@ -252,7 +254,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
|
||||
|
||||
@@ -12,7 +12,6 @@ Rectangle {
|
||||
property string text: ""
|
||||
property string secondaryText: ""
|
||||
property bool isActive: false
|
||||
property bool enabled: true
|
||||
property int widgetIndex: 0
|
||||
property var widgetData: null
|
||||
property bool editMode: false
|
||||
@@ -28,12 +27,12 @@ Rectangle {
|
||||
}
|
||||
|
||||
readonly property color _tileBgActive: Theme.ccTileActiveBg
|
||||
readonly property color _tileBgInactive: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
readonly property color _tileBgInactive: Theme.ccPillInactiveBg
|
||||
readonly property color _tileRingActive: Theme.ccTileRing
|
||||
|
||||
color: isActive ? _tileBgActive : _tileBgInactive
|
||||
border.color: isActive ? _tileRingActive : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: isActive ? 1 : 1
|
||||
border.color: isActive ? _tileRingActive : Theme.outlineMedium
|
||||
border.width: isActive ? 1 : Theme.layerOutlineWidth
|
||||
opacity: enabled ? 1.0 : 0.6
|
||||
|
||||
function hoverTint(base) {
|
||||
|
||||
@@ -260,7 +260,7 @@ Column {
|
||||
}
|
||||
case "audioOutput":
|
||||
{
|
||||
if (!AudioService.sink)
|
||||
if (!AudioService.sink?.audio)
|
||||
return "volume_off";
|
||||
let volume = AudioService.sink.audio.volume;
|
||||
let muted = AudioService.sink.audio.muted;
|
||||
@@ -276,7 +276,7 @@ Column {
|
||||
}
|
||||
case "audioInput":
|
||||
{
|
||||
if (!AudioService.source)
|
||||
if (!AudioService.source?.audio)
|
||||
return "mic_off";
|
||||
let muted = AudioService.source.audio.muted;
|
||||
return muted ? "mic_off" : "mic";
|
||||
@@ -369,7 +369,7 @@ Column {
|
||||
}
|
||||
case "audioOutput":
|
||||
{
|
||||
if (!AudioService.sink)
|
||||
if (!AudioService.sink?.audio)
|
||||
return I18n.tr("Select device", "audio status");
|
||||
if (AudioService.sink.audio.muted)
|
||||
return I18n.tr("Muted", "audio status");
|
||||
@@ -380,7 +380,7 @@ Column {
|
||||
}
|
||||
case "audioInput":
|
||||
{
|
||||
if (!AudioService.source)
|
||||
if (!AudioService.source?.audio)
|
||||
return I18n.tr("Select device", "audio status");
|
||||
if (AudioService.source.audio.muted)
|
||||
return I18n.tr("Muted", "audio status");
|
||||
@@ -412,9 +412,9 @@ Column {
|
||||
case "bluetooth":
|
||||
return !!(BluetoothService.available && BluetoothService.adapter && BluetoothService.adapter.enabled);
|
||||
case "audioOutput":
|
||||
return !!(AudioService.sink && !AudioService.sink.audio.muted);
|
||||
return !!(AudioService.sink?.audio && !AudioService.sink.audio.muted);
|
||||
case "audioInput":
|
||||
return !!(AudioService.source && !AudioService.source.audio.muted);
|
||||
return !!(AudioService.source?.audio && !AudioService.source.audio.muted);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -507,7 +507,8 @@ Column {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: 14
|
||||
property color sliderTrackColor: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
sliderTrackColor: Theme.ccSliderTrackColor
|
||||
sliderTrackOpacity: Theme.ccSliderTrackOpacity
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -529,7 +530,8 @@ Column {
|
||||
instanceId: widgetData.instanceId || ""
|
||||
screenName: root.screenName
|
||||
parentScreen: root.parentScreen
|
||||
property color sliderTrackColor: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
sliderTrackColor: Theme.ccSliderTrackColor
|
||||
sliderTrackOpacity: Theme.ccSliderTrackOpacity
|
||||
|
||||
onIconClicked: {
|
||||
if (!root.editMode && DisplayService.devices && DisplayService.devices.length > 1) {
|
||||
@@ -552,7 +554,8 @@ Column {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: 14
|
||||
property color sliderTrackColor: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
sliderTrackColor: Theme.ccSliderTrackColor
|
||||
sliderTrackOpacity: Theme.ccSliderTrackOpacity
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
@@ -10,7 +11,11 @@ Row {
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
property var availableWidgets: []
|
||||
property Item popoutContent: null
|
||||
property var popupScreen: null
|
||||
property real popoutX: 0
|
||||
property real popoutY: 0
|
||||
property real popoutWidth: 0
|
||||
property real popoutHeight: 0
|
||||
|
||||
signal addWidget(string widgetId)
|
||||
signal resetToDefault
|
||||
@@ -19,121 +24,190 @@ Row {
|
||||
height: 48
|
||||
spacing: Theme.spacingS
|
||||
|
||||
onAddWidget: addWidgetPopup.close()
|
||||
function openWidgetLibrary() {
|
||||
if (popupScreen)
|
||||
addWidgetWindow.screen = popupScreen;
|
||||
addWidgetWindow.visible = true;
|
||||
}
|
||||
|
||||
Popup {
|
||||
id: addWidgetPopup
|
||||
parent: popoutContent
|
||||
x: parent ? Math.round((parent.width - width) / 2) : 0
|
||||
y: parent ? Math.round((parent.height - height) / 2) : 0
|
||||
width: 400
|
||||
height: 300
|
||||
modal: false
|
||||
focus: true
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
function closeWidgetLibrary() {
|
||||
addWidgetWindow.visible = false;
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.surfaceContainer
|
||||
border.color: Theme.primarySelected
|
||||
border.width: 0
|
||||
radius: Theme.cornerRadius
|
||||
onAddWidget: closeWidgetLibrary()
|
||||
onVisibleChanged: {
|
||||
if (!visible)
|
||||
closeWidgetLibrary();
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
id: addWidgetWindow
|
||||
|
||||
screen: root.popupScreen
|
||||
visible: false
|
||||
color: "transparent"
|
||||
|
||||
WlrLayershell.namespace: "dms:control-center-widget-library"
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: visible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
contentItem: Item {
|
||||
readonly property bool blurActive: Theme.blurForegroundLayers || Theme.transparentBlurLayers
|
||||
readonly property real surfaceAlpha: blurActive ? Math.min(Theme.popupTransparency, Theme.transparentBlurLayers ? 0.24 : 0.72) : Theme.popupTransparency
|
||||
readonly property real rowAlpha: blurActive ? Math.min(Theme.popupTransparency, Theme.transparentBlurLayers ? 0.10 : 0.52) : Theme.popupTransparency
|
||||
readonly property int panelWidth: 400
|
||||
readonly property int panelHeight: 300
|
||||
|
||||
WindowBlur {
|
||||
targetWindow: addWidgetWindow
|
||||
blurX: widgetLibraryPanel.x
|
||||
blurY: widgetLibraryPanel.y
|
||||
blurWidth: addWidgetWindow.visible ? widgetLibraryPanel.width : 0
|
||||
blurHeight: addWidgetWindow.visible ? widgetLibraryPanel.height : 0
|
||||
blurRadius: Theme.cornerRadius
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onClicked: root.closeWidgetLibrary()
|
||||
}
|
||||
|
||||
Row {
|
||||
id: headerRow
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
spacing: Theme.spacingM
|
||||
FocusScope {
|
||||
anchors.fill: parent
|
||||
focus: addWidgetWindow.visible
|
||||
|
||||
DankIcon {
|
||||
name: "add_circle"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
Keys.onEscapePressed: event => {
|
||||
root.closeWidgetLibrary();
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
Typography {
|
||||
text: I18n.tr("Add Widget")
|
||||
style: Typography.Style.Subtitle
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
Rectangle {
|
||||
id: widgetLibraryPanel
|
||||
|
||||
width: addWidgetWindow.panelWidth
|
||||
height: addWidgetWindow.panelHeight
|
||||
x: Math.round((root.popoutWidth > 0 ? root.popoutX + (root.popoutWidth - width) / 2 : (addWidgetWindow.width - width) / 2))
|
||||
y: Math.round((root.popoutHeight > 0 ? root.popoutY + (root.popoutHeight - height) / 2 : (addWidgetWindow.height - height) / 2))
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, addWidgetWindow.surfaceAlpha)
|
||||
border.color: addWidgetWindow.blurActive ? Theme.outlineMedium : Theme.primarySelected
|
||||
border.width: addWidgetWindow.blurActive ? Theme.layerOutlineWidth : 0
|
||||
antialiasing: true
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onClicked: mouse => mouse.accepted = true
|
||||
}
|
||||
|
||||
DankListView {
|
||||
anchors.top: headerRow.bottom
|
||||
anchors.topMargin: Theme.spacingM
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
spacing: Theme.spacingS
|
||||
clip: true
|
||||
model: root.availableWidgets
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
|
||||
delegate: Rectangle {
|
||||
width: 400 - Theme.spacingL * 2
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: widgetMouseArea.containsMouse ? Theme.primaryHover : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
border.width: 0
|
||||
Row {
|
||||
id: headerRow
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: modelData.icon
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
width: 400 - Theme.spacingL * 2 - Theme.iconSize - Theme.spacingM * 3 - Theme.iconSize
|
||||
|
||||
Typography {
|
||||
text: modelData.text
|
||||
style: Typography.Style.Body
|
||||
color: Theme.surfaceText
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
Typography {
|
||||
text: modelData.description
|
||||
style: Typography.Style.Caption
|
||||
color: Theme.outline
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: "add"
|
||||
size: Theme.iconSize - 4
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
DankIcon {
|
||||
name: "add_circle"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: widgetMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.addWidget(modelData.id);
|
||||
Typography {
|
||||
text: I18n.tr("Add Widget")
|
||||
style: Typography.Style.Subtitle
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
DankListView {
|
||||
id: widgetList
|
||||
|
||||
anchors.top: headerRow.bottom
|
||||
anchors.topMargin: Theme.spacingM
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
spacing: Theme.spacingS
|
||||
clip: true
|
||||
model: root.availableWidgets
|
||||
|
||||
delegate: Rectangle {
|
||||
width: widgetList.width
|
||||
height: 50
|
||||
radius: Theme.cornerRadius
|
||||
color: widgetMouseArea.containsMouse ? Theme.withAlpha(Theme.primary, addWidgetWindow.blurActive ? 0.12 : 0.08) : Theme.withAlpha(Theme.surfaceContainerHigh, addWidgetWindow.rowAlpha)
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: Theme.layerOutlineWidth
|
||||
antialiasing: true
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: modelData.icon
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
width: parent.width - Theme.iconSize * 2 - Theme.spacingM * 3
|
||||
|
||||
Typography {
|
||||
text: modelData.text
|
||||
style: Typography.Style.Body
|
||||
color: Theme.surfaceText
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
Typography {
|
||||
text: modelData.description
|
||||
style: Typography.Style.Caption
|
||||
color: Theme.outline
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: "add"
|
||||
size: Theme.iconSize - 4
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: widgetMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.addWidget(modelData.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -171,7 +245,7 @@ Row {
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: addWidgetPopup.open()
|
||||
onClicked: root.openWidgetLibrary()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,9 +21,9 @@ Rectangle {
|
||||
|
||||
implicitHeight: 70
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 0
|
||||
color: Theme.nestedSurface
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: Theme.layerOutlineWidth
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
|
||||
@@ -41,7 +41,7 @@ DankPopout {
|
||||
}
|
||||
}
|
||||
|
||||
readonly property color _containerBg: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
readonly property color _containerBg: Theme.nestedSurface
|
||||
|
||||
function openWithSection(section) {
|
||||
StateUtils.openWithSection(root, section);
|
||||
@@ -210,7 +210,11 @@ DankPopout {
|
||||
EditControls {
|
||||
width: parent.width
|
||||
visible: editMode
|
||||
popoutContent: controlContent
|
||||
popupScreen: root.screen
|
||||
popoutX: root.alignedX
|
||||
popoutY: root.alignedY
|
||||
popoutWidth: root.alignedWidth
|
||||
popoutHeight: root.alignedHeight
|
||||
availableWidgets: {
|
||||
if (!editMode)
|
||||
return [];
|
||||
|
||||
@@ -18,9 +18,9 @@ Rectangle {
|
||||
|
||||
implicitHeight: headerRow.height + (hasInputVolumeSliderInCC ? 0 : volumeSlider.height) + audioContent.height + Theme.spacingM
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 0
|
||||
color: Theme.nestedSurface
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: Theme.layerOutlineWidth
|
||||
|
||||
Row {
|
||||
id: headerRow
|
||||
@@ -123,6 +123,8 @@ Rectangle {
|
||||
unit: "%"
|
||||
valueOverride: actualVolumePercent
|
||||
thumbOutlineColor: Theme.surfaceVariant
|
||||
trackColor: Theme.ccSliderTrackColor
|
||||
trackOpacity: Theme.ccSliderTrackOpacity
|
||||
|
||||
onSliderValueChanged: function (newValue) {
|
||||
if (AudioService.source && AudioService.source.audio) {
|
||||
@@ -207,9 +209,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
|
||||
@@ -351,8 +353,8 @@ Rectangle {
|
||||
deviceRipple.trigger(mapped.x, mapped.y);
|
||||
}
|
||||
onClicked: {
|
||||
if (modelData) {
|
||||
Pipewire.preferredDefaultAudioSource = modelData;
|
||||
if (modelData && modelData.name) {
|
||||
AudioService.setDefaultSourceByName(modelData.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ Rectangle {
|
||||
|
||||
implicitHeight: headerRow.height + (!hasVolumeSliderInCC ? volumeSlider.height : 0) + audioContent.height + Theme.spacingM
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 0
|
||||
color: Theme.nestedSurface
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: Theme.layerOutlineWidth
|
||||
|
||||
Row {
|
||||
id: headerRow
|
||||
@@ -132,6 +132,8 @@ Rectangle {
|
||||
unit: "%"
|
||||
valueOverride: actualVolumePercent
|
||||
thumbOutlineColor: Theme.surfaceVariant
|
||||
trackColor: Theme.ccSliderTrackColor
|
||||
trackOpacity: Theme.ccSliderTrackOpacity
|
||||
|
||||
onSliderValueChanged: function (newValue) {
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
@@ -218,9 +220,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
|
||||
@@ -355,8 +357,8 @@ Rectangle {
|
||||
deviceRipple.trigger(mapped.x, mapped.y);
|
||||
}
|
||||
onClicked: {
|
||||
if (modelData) {
|
||||
Pipewire.preferredDefaultAudioSink = modelData;
|
||||
if (modelData && modelData.name) {
|
||||
AudioService.setDefaultSinkByName(modelData.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -397,9 +399,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
|
||||
@@ -448,6 +450,7 @@ Rectangle {
|
||||
Item {
|
||||
id: appVolumeRow
|
||||
property color sliderTrackColor: "transparent"
|
||||
property real sliderTrackOpacity: Theme.ccSliderTrackOpacity
|
||||
anchors.centerIn: parent
|
||||
|
||||
height: 40
|
||||
@@ -519,7 +522,8 @@ Rectangle {
|
||||
unit: "%"
|
||||
valueOverride: actualVolumePercent
|
||||
thumbOutlineColor: Theme.surfaceContainer
|
||||
trackColor: appVolumeRow.sliderTrackColor.a > 0 ? appVolumeRow.sliderTrackColor : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
trackColor: appVolumeRow.sliderTrackColor.a > 0 ? appVolumeRow.sliderTrackColor : Theme.ccSliderTrackColor
|
||||
trackOpacity: appVolumeRow.sliderTrackOpacity
|
||||
|
||||
onSliderValueChanged: function (newValue) {
|
||||
if (modelData) {
|
||||
|
||||
@@ -12,9 +12,9 @@ Rectangle {
|
||||
|
||||
implicitHeight: contentColumn.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 0
|
||||
color: Theme.nestedSurface
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: Theme.layerOutlineWidth
|
||||
|
||||
function isActiveProfile(profile) {
|
||||
if (typeof PowerProfiles === "undefined") {
|
||||
@@ -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,9 +153,9 @@ Item {
|
||||
width: 320
|
||||
height: contentColumn.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainer
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 0
|
||||
color: Theme.floatingSurface
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: Theme.layerOutlineWidth
|
||||
opacity: modalVisible ? 1 : 0
|
||||
scale: modalVisible ? 1 : 0.9
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@ Rectangle {
|
||||
return headerRow.height + bluetoothContent.height + Theme.spacingM;
|
||||
}
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 0
|
||||
color: Theme.nestedSurface
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: Theme.layerOutlineWidth
|
||||
|
||||
property var bluetoothCodecModalRef: null
|
||||
property var devicesBeingPaired: new Set()
|
||||
@@ -115,7 +115,7 @@ Rectangle {
|
||||
height: 36
|
||||
radius: 18
|
||||
color: scanMouseArea.containsMouse && adapterEnabled ? Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) : "transparent"
|
||||
border.color: adapterEnabled ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.color: adapterEnabled ? Theme.primary : Theme.outlineStrong
|
||||
border.width: 0
|
||||
visible: adapterEnabled
|
||||
|
||||
@@ -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
|
||||
@@ -434,7 +434,7 @@ Rectangle {
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
color: Theme.outlineStrong
|
||||
visible: pairedRepeater.count > 0 && availableRepeater.count > 0
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -609,7 +609,7 @@ Rectangle {
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
radius: Theme.cornerRadius
|
||||
border.width: 0
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.color: Theme.outlineStrong
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
|
||||
@@ -106,9 +106,9 @@ Rectangle {
|
||||
return brightnessContent.height + Theme.spacingM;
|
||||
}
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 0
|
||||
color: Theme.nestedSurface
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: Theme.layerOutlineWidth
|
||||
|
||||
DankFlickable {
|
||||
id: brightnessContent
|
||||
|
||||
@@ -16,9 +16,9 @@ Rectangle {
|
||||
|
||||
implicitHeight: diskContent.height + Theme.spacingM
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 0
|
||||
color: Theme.nestedSurface
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: Theme.layerOutlineWidth
|
||||
|
||||
Component.onCompleted: {
|
||||
DgopService.addRef(["diskmounts"]);
|
||||
@@ -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
|
||||
|
||||
@@ -22,9 +22,9 @@ Rectangle {
|
||||
return headerRow.height + wifiOffContent.height + Theme.spacingM;
|
||||
}
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 0
|
||||
color: Theme.nestedSurface
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: Theme.layerOutlineWidth
|
||||
|
||||
Component.onCompleted: {
|
||||
NetworkService.addRef();
|
||||
@@ -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
|
||||
|
||||
@@ -11,6 +11,7 @@ Row {
|
||||
|
||||
property var defaultSink: AudioService.sink
|
||||
property color sliderTrackColor: "transparent"
|
||||
property real sliderTrackOpacity: Theme.ccSliderTrackOpacity
|
||||
|
||||
height: 40
|
||||
spacing: 0
|
||||
@@ -35,7 +36,7 @@ Row {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: mouse => iconRipple.trigger(mouse.x, mouse.y)
|
||||
onClicked: {
|
||||
if (defaultSink) {
|
||||
if (defaultSink?.audio) {
|
||||
SessionData.suppressOSDTemporarily();
|
||||
defaultSink.audio.muted = !defaultSink.audio.muted;
|
||||
}
|
||||
@@ -45,7 +46,7 @@ Row {
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: {
|
||||
if (!defaultSink)
|
||||
if (!defaultSink?.audio)
|
||||
return "volume_off";
|
||||
|
||||
let volume = defaultSink.audio.volume;
|
||||
@@ -62,28 +63,29 @@ Row {
|
||||
return "volume_up";
|
||||
}
|
||||
size: Theme.iconSize
|
||||
color: defaultSink && !defaultSink.audio.muted && defaultSink.audio.volume > 0 ? Theme.primary : Theme.surfaceText
|
||||
color: defaultSink?.audio && !defaultSink.audio.muted && defaultSink.audio.volume > 0 ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
}
|
||||
|
||||
DankSlider {
|
||||
id: volumeSlider
|
||||
|
||||
readonly property real actualVolumePercent: defaultSink ? Math.round(defaultSink.audio.volume * 100) : 0
|
||||
readonly property real actualVolumePercent: defaultSink?.audio ? Math.round(defaultSink.audio.volume * 100) : 0
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - (Theme.iconSize + Theme.spacingS * 2)
|
||||
enabled: defaultSink !== null
|
||||
enabled: defaultSink?.audio != null
|
||||
minimum: 0
|
||||
maximum: AudioService.sinkMaxVolume
|
||||
showValue: true
|
||||
unit: "%"
|
||||
valueOverride: actualVolumePercent
|
||||
thumbOutlineColor: Theme.surfaceContainer
|
||||
trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Theme.ccSliderTrackColor
|
||||
trackOpacity: root.sliderTrackOpacity
|
||||
|
||||
onSliderValueChanged: function (newValue) {
|
||||
if (defaultSink) {
|
||||
if (defaultSink?.audio) {
|
||||
SessionData.suppressOSDTemporarily();
|
||||
defaultSink.audio.volume = newValue / 100.0;
|
||||
if (newValue > 0 && defaultSink.audio.muted) {
|
||||
@@ -97,7 +99,7 @@ Row {
|
||||
Binding {
|
||||
target: volumeSlider
|
||||
property: "value"
|
||||
value: defaultSink ? Math.min(AudioService.sinkMaxVolume, Math.round(defaultSink.audio.volume * 100)) : 0
|
||||
value: defaultSink?.audio ? Math.min(AudioService.sinkMaxVolume, Math.round(defaultSink.audio.volume * 100)) : 0
|
||||
when: !volumeSlider.isDragging
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ Row {
|
||||
property string instanceId: ""
|
||||
property string screenName: ""
|
||||
property var parentScreen: null
|
||||
property color sliderTrackColor: "transparent"
|
||||
property real sliderTrackOpacity: Theme.ccSliderTrackOpacity
|
||||
|
||||
signal iconClicked
|
||||
|
||||
@@ -184,7 +186,8 @@ Row {
|
||||
}
|
||||
}
|
||||
thumbOutlineColor: Theme.surfaceContainer
|
||||
trackColor: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Theme.ccSliderTrackColor
|
||||
trackOpacity: root.sliderTrackOpacity
|
||||
|
||||
Binding on value {
|
||||
value: root.targetBrightness
|
||||
|
||||
@@ -14,16 +14,15 @@ Rectangle {
|
||||
property real value: 0.0
|
||||
property real maximumValue: 1.0
|
||||
property real minimumValue: 0.0
|
||||
property bool enabled: true
|
||||
|
||||
signal sliderValueChanged(real value)
|
||||
|
||||
width: parent ? parent.width : 200
|
||||
height: 60
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 0
|
||||
color: Theme.nestedSurface
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: Theme.layerOutlineWidth
|
||||
opacity: enabled ? 1.0 : 0.6
|
||||
|
||||
Row {
|
||||
@@ -65,6 +64,8 @@ Rectangle {
|
||||
minimum: Math.round(root.minimumValue * 100)
|
||||
maximum: Math.round(root.maximumValue * 100)
|
||||
value: Math.round(root.value * 100)
|
||||
trackColor: Theme.ccSliderTrackColor
|
||||
trackOpacity: Theme.ccSliderTrackOpacity
|
||||
onSliderValueChanged: root.sliderValueChanged(newValue / 100.0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,23 +29,21 @@ Rectangle {
|
||||
return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor);
|
||||
}
|
||||
|
||||
readonly property color _containerBg: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
readonly property color _containerBg: Theme.ccPillInactiveBg
|
||||
|
||||
color: {
|
||||
const baseColor = bodyMouse.containsMouse ? Theme.primaryPressed : _containerBg;
|
||||
const baseColor = bodyMouse.containsMouse ? Theme.ccPillInactiveHoverBg : _containerBg;
|
||||
return baseColor;
|
||||
}
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.10)
|
||||
border.width: 0
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: Theme.layerOutlineWidth
|
||||
antialiasing: true
|
||||
|
||||
readonly property color _labelPrimary: Theme.surfaceText
|
||||
readonly property color _labelSecondary: Theme.surfaceVariantText
|
||||
readonly property color _tileBgActive: Theme.ccTileActiveBg
|
||||
readonly property color _tileBgInactive: {
|
||||
const transparency = Theme.popupTransparency;
|
||||
const surface = Theme.surfaceContainer || Qt.rgba(0.1, 0.1, 0.1, 1);
|
||||
return Qt.rgba(surface.r, surface.g, surface.b, transparency);
|
||||
return Theme.ccTileInactiveBg;
|
||||
}
|
||||
readonly property color _tileRingActive: Theme.ccTileRing
|
||||
readonly property color _tileRingInactive: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.18)
|
||||
@@ -92,8 +90,8 @@ Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
radius: _tileRadius
|
||||
color: isActive ? _tileBgActive : _tileBgInactive
|
||||
border.color: isActive ? _tileRingActive : "transparent"
|
||||
border.width: isActive ? 1 : 0
|
||||
border.color: isActive ? _tileRingActive : Theme.outlineMedium
|
||||
border.width: isActive ? 1 : Theme.layerOutlineWidth
|
||||
antialiasing: true
|
||||
|
||||
Rectangle {
|
||||
|
||||
@@ -11,6 +11,7 @@ Row {
|
||||
|
||||
property var defaultSource: AudioService.source
|
||||
property color sliderTrackColor: "transparent"
|
||||
property real sliderTrackOpacity: Theme.ccSliderTrackOpacity
|
||||
|
||||
height: 40
|
||||
spacing: 0
|
||||
@@ -35,7 +36,7 @@ Row {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: mouse => iconRipple.trigger(mouse.x, mouse.y)
|
||||
onClicked: {
|
||||
if (defaultSource) {
|
||||
if (defaultSource?.audio) {
|
||||
SessionData.suppressOSDTemporarily();
|
||||
defaultSource.audio.muted = !defaultSource.audio.muted;
|
||||
}
|
||||
@@ -45,7 +46,7 @@ Row {
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: {
|
||||
if (!defaultSource)
|
||||
if (!defaultSource?.audio)
|
||||
return "mic_off";
|
||||
|
||||
let volume = defaultSource.audio.volume;
|
||||
@@ -56,26 +57,27 @@ Row {
|
||||
return "mic";
|
||||
}
|
||||
size: Theme.iconSize
|
||||
color: defaultSource && !defaultSource.audio.muted && defaultSource.audio.volume > 0 ? Theme.primary : Theme.surfaceText
|
||||
color: defaultSource?.audio && !defaultSource.audio.muted && defaultSource.audio.volume > 0 ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
}
|
||||
|
||||
DankSlider {
|
||||
readonly property real actualVolumePercent: defaultSource ? Math.round(defaultSource.audio.volume * 100) : 0
|
||||
readonly property real actualVolumePercent: defaultSource?.audio ? Math.round(defaultSource.audio.volume * 100) : 0
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - (Theme.iconSize + Theme.spacingS * 2)
|
||||
enabled: defaultSource !== null
|
||||
enabled: defaultSource?.audio != null
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
value: defaultSource ? Math.min(100, Math.round(defaultSource.audio.volume * 100)) : 0
|
||||
value: defaultSource?.audio ? Math.min(100, Math.round(defaultSource.audio.volume * 100)) : 0
|
||||
showValue: true
|
||||
unit: "%"
|
||||
valueOverride: actualVolumePercent
|
||||
thumbOutlineColor: Theme.surfaceContainer
|
||||
trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
trackColor: root.sliderTrackColor.a > 0 ? root.sliderTrackColor : Theme.ccSliderTrackColor
|
||||
trackOpacity: root.sliderTrackOpacity
|
||||
onSliderValueChanged: function (newValue) {
|
||||
if (defaultSource) {
|
||||
if (defaultSource?.audio) {
|
||||
SessionData.suppressOSDTemporarily();
|
||||
defaultSource.audio.volume = newValue / 100.0;
|
||||
if (newValue > 0 && defaultSource.audio.muted) {
|
||||
|
||||
@@ -10,7 +10,7 @@ Rectangle {
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
property bool isActive: BatteryService.batteryAvailable && (BatteryService.isCharging || BatteryService.isPluggedIn)
|
||||
property bool enabled: BatteryService.batteryAvailable
|
||||
enabled: BatteryService.batteryAvailable
|
||||
|
||||
signal clicked
|
||||
|
||||
@@ -28,7 +28,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
readonly property color _tileBgActive: Theme.ccTileActiveBg
|
||||
readonly property color _tileBgInactive: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
readonly property color _tileBgInactive: Theme.ccPillInactiveBg
|
||||
readonly property color _tileRingActive: Theme.ccTileRing
|
||||
readonly property color _tileIconActive: Theme.ccTileActiveText
|
||||
readonly property color _tileIconInactive: Theme.ccTileInactiveIcon
|
||||
@@ -36,11 +36,11 @@ Rectangle {
|
||||
color: {
|
||||
if (isActive)
|
||||
return _tileBgActive;
|
||||
const baseColor = mouseArea.containsMouse ? Theme.primaryPressed : _tileBgInactive;
|
||||
const baseColor = mouseArea.containsMouse ? Theme.ccPillInactiveHoverBg : _tileBgInactive;
|
||||
return baseColor;
|
||||
}
|
||||
border.color: isActive ? _tileRingActive : "transparent"
|
||||
border.width: isActive ? 1 : 0
|
||||
border.color: isActive ? _tileRingActive : Theme.outlineMedium
|
||||
border.width: isActive ? 1 : Theme.layerOutlineWidth
|
||||
antialiasing: true
|
||||
opacity: enabled ? 1.0 : 0.6
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ Rectangle {
|
||||
return parseFloat(selectedMount.percent.replace("%", "")) || 0;
|
||||
}
|
||||
|
||||
property bool enabled: DgopService.dgopAvailable
|
||||
enabled: DgopService.dgopAvailable
|
||||
|
||||
signal clicked
|
||||
|
||||
@@ -38,11 +38,11 @@ Rectangle {
|
||||
return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor);
|
||||
}
|
||||
|
||||
readonly property color _tileBg: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
readonly property color _tileBg: Theme.ccPillInactiveBg
|
||||
|
||||
color: mouseArea.containsMouse ? Theme.primaryPressed : _tileBg
|
||||
border.color: "transparent"
|
||||
border.width: 0
|
||||
color: mouseArea.containsMouse ? Theme.ccPillInactiveHoverBg : _tileBg
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: Theme.layerOutlineWidth
|
||||
antialiasing: true
|
||||
opacity: enabled ? 1.0 : 0.6
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ Rectangle {
|
||||
|
||||
property string iconName: ""
|
||||
property bool isActive: false
|
||||
property bool enabled: true
|
||||
property real iconRotation: 0
|
||||
|
||||
signal clicked
|
||||
@@ -27,7 +26,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
readonly property color _tileBgActive: Theme.ccTileActiveBg
|
||||
readonly property color _tileBgInactive: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
readonly property color _tileBgInactive: Theme.ccPillInactiveBg
|
||||
readonly property color _tileRingActive: Theme.ccTileRing
|
||||
readonly property color _tileIconActive: Theme.ccTileActiveText
|
||||
readonly property color _tileIconInactive: Theme.ccTileInactiveIcon
|
||||
@@ -35,11 +34,11 @@ Rectangle {
|
||||
color: {
|
||||
if (isActive)
|
||||
return _tileBgActive;
|
||||
const baseColor = mouseArea.containsMouse ? Theme.primaryPressed : _tileBgInactive;
|
||||
const baseColor = mouseArea.containsMouse ? Theme.ccPillInactiveHoverBg : _tileBgInactive;
|
||||
return baseColor;
|
||||
}
|
||||
border.color: isActive ? _tileRingActive : "transparent"
|
||||
border.width: isActive ? 1 : 0
|
||||
border.color: isActive ? _tileRingActive : Theme.outlineMedium
|
||||
border.width: isActive ? 1 : Theme.layerOutlineWidth
|
||||
antialiasing: true
|
||||
opacity: enabled ? 1.0 : 0.6
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ Rectangle {
|
||||
property string iconName: ""
|
||||
property string text: ""
|
||||
property bool isActive: false
|
||||
property bool enabled: true
|
||||
property string secondaryText: ""
|
||||
property real iconRotation: 0
|
||||
|
||||
@@ -27,17 +26,17 @@ Rectangle {
|
||||
}
|
||||
|
||||
readonly property color _tileBgActive: Theme.ccTileActiveBg
|
||||
readonly property color _tileBgInactive: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
readonly property color _tileBgInactive: Theme.ccPillInactiveBg
|
||||
readonly property color _tileRingActive: Theme.ccTileRing
|
||||
|
||||
color: {
|
||||
if (isActive)
|
||||
return _tileBgActive;
|
||||
const baseColor = mouseArea.containsMouse ? Theme.primaryPressed : _tileBgInactive;
|
||||
const baseColor = mouseArea.containsMouse ? Theme.ccPillInactiveHoverBg : _tileBgInactive;
|
||||
return baseColor;
|
||||
}
|
||||
border.color: isActive ? _tileRingActive : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 0
|
||||
border.color: isActive ? _tileRingActive : Theme.outlineMedium
|
||||
border.width: isActive ? 1 : Theme.layerOutlineWidth
|
||||
opacity: enabled ? 1.0 : 0.6
|
||||
|
||||
function hoverTint(base) {
|
||||
@@ -45,7 +44,7 @@ Rectangle {
|
||||
return Theme.isLightMode ? Qt.darker(base, factor) : Qt.lighter(base, factor);
|
||||
}
|
||||
|
||||
readonly property color _containerBg: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
readonly property color _containerBg: Theme.ccPillInactiveBg
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -54,7 +54,7 @@ Item {
|
||||
}
|
||||
|
||||
readonly property real shadowIntensity: barConfig?.shadowIntensity ?? 0
|
||||
readonly property bool shadowEnabled: shadowIntensity > 0
|
||||
readonly property bool shadowEnabled: !BlurService.enabled && shadowIntensity > 0
|
||||
readonly property int blurMax: 64
|
||||
readonly property real shadowBlurPx: shadowIntensity * 0.2
|
||||
readonly property real shadowBlur: Math.max(0, Math.min(1, shadowBlurPx / blurMax))
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -672,7 +778,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
|
||||
|
||||
@@ -260,7 +260,7 @@ DankPopout {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
height: 64
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
color: Theme.nestedSurface
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
@@ -295,7 +295,7 @@ DankPopout {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
height: 64
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
color: Theme.nestedSurface
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
@@ -346,7 +346,7 @@ DankPopout {
|
||||
width: parent.width
|
||||
height: batteryColumn.implicitHeight + Theme.spacingM * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
color: Theme.nestedSurface
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
@@ -416,7 +416,7 @@ DankPopout {
|
||||
width: (parent.width - Theme.spacingS * 2) / 3
|
||||
height: 48
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
color: Theme.nestedSurface
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
@@ -453,7 +453,7 @@ DankPopout {
|
||||
width: (parent.width - Theme.spacingS * 2) / 3
|
||||
height: 48
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
color: Theme.nestedSurface
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
@@ -482,7 +482,7 @@ DankPopout {
|
||||
width: (parent.width - Theme.spacingS * 2) / 3
|
||||
height: 48
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
color: Theme.nestedSurface
|
||||
border.width: 0
|
||||
|
||||
Column {
|
||||
|
||||
@@ -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
|
||||
@@ -83,6 +84,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
|
||||
|
||||
@@ -110,7 +110,7 @@ PanelWindow {
|
||||
|
||||
width: Math.min(400, Math.max(180, menuColumn.implicitWidth + Theme.spacingS * 2))
|
||||
height: Math.max(60, menuColumn.implicitHeight + Theme.spacingS * 2)
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
color: Theme.floatingSurface
|
||||
radius: Theme.cornerRadius
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,62 @@ BasePill {
|
||||
return 0;
|
||||
}
|
||||
|
||||
function updateActiveWindow() {
|
||||
const active = ToplevelManager.activeToplevel;
|
||||
|
||||
if (!active) {
|
||||
if (activeWindow) {
|
||||
if (CompositorService.isNiri) {
|
||||
if (NiriService.currentOutput === (parentScreen?.name ?? ""))
|
||||
activeWindow = null;
|
||||
} else {
|
||||
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 if (activeWindow) {
|
||||
const alive = ToplevelManager.toplevels?.values;
|
||||
if (alive && !Array.from(alive).some(t => t === activeWindow))
|
||||
activeWindow = null;
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
updateActiveWindow();
|
||||
updateDesktopEntry();
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: ToplevelManager
|
||||
function onActiveToplevelChanged() {
|
||||
if (!CompositorService.isNiri)
|
||||
root.updateActiveWindow();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: CompositorService
|
||||
function onToplevelsChanged() {
|
||||
root.updateActiveWindow();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: CompositorService.isNiri ? NiriService : null
|
||||
function onWindowsChanged() {
|
||||
root.updateActiveWindow();
|
||||
}
|
||||
function onCurrentOutputChanged() {
|
||||
root.updateActiveWindow();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: DesktopEntries
|
||||
function onApplicationsChanged() {
|
||||
@@ -73,21 +125,17 @@ BasePill {
|
||||
}
|
||||
readonly property bool hasWindowsOnCurrentWorkspace: {
|
||||
if (CompositorService.isNiri) {
|
||||
let currentWorkspaceId = null;
|
||||
for (var i = 0; i < NiriService.allWorkspaces.length; i++) {
|
||||
const ws = NiriService.allWorkspaces[i];
|
||||
if (ws.is_focused) {
|
||||
currentWorkspaceId = ws.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!currentWorkspaceId) {
|
||||
if (!activeWindow || !(activeWindow.title || activeWindow.appId))
|
||||
return false;
|
||||
}
|
||||
|
||||
const workspaceWindows = NiriService.windows.filter(w => w.workspace_id === currentWorkspaceId);
|
||||
return workspaceWindows.length > 0 && activeWindow && (activeWindow.title || activeWindow.appId);
|
||||
if (NiriService.currentOutput !== (parentScreen?.name ?? ""))
|
||||
return true;
|
||||
const focusedWin = NiriService.windows.find(w => w.is_focused);
|
||||
if (!focusedWin)
|
||||
return false;
|
||||
const screenWsIds = new Set(
|
||||
NiriService.allWorkspaces.filter(ws => ws.output === parentScreen.name).map(ws => ws.id)
|
||||
);
|
||||
return screenWsIds.has(focusedWin.workspace_id);
|
||||
}
|
||||
|
||||
if (CompositorService.isHyprland) {
|
||||
|
||||
@@ -352,7 +352,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
|
||||
|
||||
@@ -409,7 +409,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 {
|
||||
|
||||
@@ -79,6 +79,66 @@ BasePill {
|
||||
item: item
|
||||
}))
|
||||
readonly property var hiddenBarItems: allSortedTrayItems.filter(item => SessionData.isHiddenTrayId(root.getTrayItemKey(item)))
|
||||
readonly property string trayIconTintMode: {
|
||||
const configuredMode = SettingsData.systemTrayIconTintMode || "none";
|
||||
switch (configuredMode) {
|
||||
case "monochrome":
|
||||
case "primary":
|
||||
case "secondary":
|
||||
return configuredMode;
|
||||
default:
|
||||
return "none";
|
||||
}
|
||||
}
|
||||
readonly property bool trayIconTintEnabled: trayIconTintMode !== "none"
|
||||
readonly property real trayIconTintSaturationAmount: {
|
||||
const raw = SettingsData.systemTrayIconTintSaturation;
|
||||
const value = (raw === undefined || raw === null) ? 50 : raw;
|
||||
return Math.max(0, Math.min(100, value)) / 100;
|
||||
}
|
||||
readonly property real trayIconTintStrengthAmount: {
|
||||
const raw = SettingsData.systemTrayIconTintStrength;
|
||||
const value = (raw === undefined || raw === null) ? 135 : raw;
|
||||
return Math.max(0, Math.min(200, value)) / 100;
|
||||
}
|
||||
readonly property real trayIconSaturation: {
|
||||
switch (trayIconTintMode) {
|
||||
case "monochrome":
|
||||
return -1;
|
||||
case "primary":
|
||||
case "secondary":
|
||||
return -root.trayIconTintSaturationAmount;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
readonly property real trayIconColorization: {
|
||||
switch (trayIconTintMode) {
|
||||
case "primary":
|
||||
case "secondary":
|
||||
return root.trayIconTintStrengthAmount;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
readonly property color trayIconTintColor: {
|
||||
switch (trayIconTintMode) {
|
||||
case "primary":
|
||||
return Theme.primary;
|
||||
case "secondary":
|
||||
return Theme.secondary;
|
||||
default:
|
||||
return Theme.surfaceText;
|
||||
}
|
||||
}
|
||||
|
||||
readonly property bool reverseInlineHorizontal: !useOverflowPopup && !isVerticalOrientation && section === "right"
|
||||
readonly property bool reverseInlineVertical: !useOverflowPopup && isVerticalOrientation && section === "right"
|
||||
readonly property var displayedMainBarItems: reverseInlineHorizontal ? [...mainBarItems].reverse() : mainBarItems
|
||||
readonly property var displayedInlineExpandedItems: (reverseInlineHorizontal ? [...hiddenBarItems].reverse() : hiddenBarItems).map(item => ({
|
||||
key: getTrayItemKey(item),
|
||||
item: item
|
||||
}))
|
||||
|
||||
function moveTrayItemInFullOrder(visibleFromIndex, visibleToIndex) {
|
||||
if (visibleFromIndex === visibleToIndex || visibleFromIndex < 0 || visibleToIndex < 0)
|
||||
@@ -271,7 +331,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
|
||||
@@ -290,6 +350,12 @@ BasePill {
|
||||
smooth: true
|
||||
mipmap: true
|
||||
visible: status === Image.Ready
|
||||
layer.enabled: root.trayIconTintEnabled
|
||||
layer.effect: MultiEffect {
|
||||
saturation: root.trayIconSaturation
|
||||
colorization: root.trayIconColorization
|
||||
colorizationColor: root.trayIconTintColor
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
@@ -409,7 +475,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
|
||||
@@ -435,6 +501,313 @@ BasePill {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: root.displayedInlineExpandedItems
|
||||
objectProp: "key"
|
||||
}
|
||||
|
||||
delegate: inlineExpandedTrayItemDelegate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: inlineExpandedTrayItemDelegate
|
||||
|
||||
Item {
|
||||
property var trayItem: modelData.item
|
||||
property string itemKey: modelData.key
|
||||
property string iconSource: root.trayIconSourceFor(trayItem)
|
||||
|
||||
width: root.isVerticalOrientation ? root.barThickness : (root.inlineExpanded ? root.trayItemSize : 0)
|
||||
height: root.isVerticalOrientation ? (root.inlineExpanded ? root.trayItemSize : 0) : root.barThickness
|
||||
visible: width > 0 || height > 0
|
||||
|
||||
Behavior on width {
|
||||
enabled: !root.isVerticalOrientation
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on height {
|
||||
enabled: root.isVerticalOrientation
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: inlineVisualContent
|
||||
width: root.trayItemSize
|
||||
height: root.trayItemSize
|
||||
x: root.isVerticalOrientation ? Math.round((parent.width - width) / 2) : (root.reverseInlineHorizontal ? parent.width - width : 0)
|
||||
y: root.isVerticalOrientation ? (root.reverseInlineVertical ? parent.height - height : 0) : Math.round((parent.height - height) / 2)
|
||||
radius: Theme.cornerRadius
|
||||
color: inlineTrayItemArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
|
||||
opacity: root.inlineExpanded ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
IconImage {
|
||||
id: inlineIconImg
|
||||
anchors.centerIn: parent
|
||||
width: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||
height: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||
source: iconSource
|
||||
asynchronous: true
|
||||
smooth: true
|
||||
mipmap: true
|
||||
visible: status === Image.Ready
|
||||
layer.enabled: root.trayIconTintEnabled
|
||||
layer.effect: MultiEffect {
|
||||
saturation: root.trayIconSaturation
|
||||
colorization: root.trayIconColorization
|
||||
colorizationColor: root.trayIconTintColor
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
visible: !inlineIconImg.visible
|
||||
text: {
|
||||
const itemId = trayItem?.id || "";
|
||||
if (!itemId)
|
||||
return "?";
|
||||
return itemId.charAt(0).toUpperCase();
|
||||
}
|
||||
font.pixelSize: 10
|
||||
color: Theme.widgetTextColor
|
||||
}
|
||||
|
||||
DankRipple {
|
||||
id: inlineItemRipple
|
||||
cornerRadius: Theme.cornerRadius
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: inlineTrayItemArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: root.inlineExpanded
|
||||
|
||||
onPressed: mouse => {
|
||||
const pos = mapToItem(inlineVisualContent, mouse.x, mouse.y);
|
||||
inlineItemRipple.trigger(pos.x, pos.y);
|
||||
}
|
||||
|
||||
onClicked: mouse => {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
root.activateInlineTrayItem(trayItem, inlineVisualContent);
|
||||
return;
|
||||
}
|
||||
if (mouse.button !== Qt.RightButton)
|
||||
return;
|
||||
root.openInlineTrayContextMenu(trayItem, inlineTrayItemArea, mouse, inlineVisualContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: verticalMainTrayItemDelegate
|
||||
|
||||
Item {
|
||||
property var trayItem: modelData.item
|
||||
property string itemKey: modelData.key
|
||||
property string iconSource: root.trayIconSourceFor(trayItem)
|
||||
|
||||
width: root.barThickness
|
||||
height: root.trayItemSize
|
||||
z: dragHandler.dragging ? 100 : 0
|
||||
|
||||
property real shiftOffset: {
|
||||
if (root.draggedIndex < 0)
|
||||
return 0;
|
||||
if (index === root.draggedIndex)
|
||||
return 0;
|
||||
const dragIdx = root.draggedIndex;
|
||||
const dropIdx = root.dropTargetIndex;
|
||||
const shiftAmount = root.trayItemSize;
|
||||
if (dropIdx < 0)
|
||||
return 0;
|
||||
if (dragIdx < dropIdx && index > dragIdx && index <= dropIdx)
|
||||
return -shiftAmount;
|
||||
if (dragIdx > dropIdx && index >= dropIdx && index < dragIdx)
|
||||
return shiftAmount;
|
||||
return 0;
|
||||
}
|
||||
|
||||
transform: Translate {
|
||||
y: shiftOffset
|
||||
Behavior on y {
|
||||
enabled: !root.suppressShiftAnimation
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: dragHandler
|
||||
anchors.fill: parent
|
||||
property bool dragging: false
|
||||
property point dragStartPos: Qt.point(0, 0)
|
||||
property real dragAxisOffset: 0
|
||||
property bool longPressing: false
|
||||
|
||||
Timer {
|
||||
id: longPressTimer
|
||||
interval: 400
|
||||
repeat: false
|
||||
onTriggered: dragHandler.longPressing = true
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: visualContent
|
||||
width: root.trayItemSize
|
||||
height: root.trayItemSize
|
||||
anchors.centerIn: parent
|
||||
radius: Theme.cornerRadius
|
||||
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
|
||||
|
||||
transform: Translate {
|
||||
y: dragHandler.dragging ? dragHandler.dragAxisOffset : 0
|
||||
}
|
||||
|
||||
IconImage {
|
||||
id: iconImg
|
||||
anchors.centerIn: parent
|
||||
width: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||
height: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||
source: iconSource
|
||||
asynchronous: true
|
||||
smooth: true
|
||||
mipmap: true
|
||||
visible: status === Image.Ready
|
||||
layer.enabled: root.trayIconTintEnabled
|
||||
layer.effect: MultiEffect {
|
||||
saturation: root.trayIconSaturation
|
||||
colorization: root.trayIconColorization
|
||||
colorizationColor: root.trayIconTintColor
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
visible: !iconImg.visible
|
||||
text: {
|
||||
const itemId = trayItem?.id || "";
|
||||
if (!itemId)
|
||||
return "?";
|
||||
return itemId.charAt(0).toUpperCase();
|
||||
}
|
||||
font.pixelSize: 10
|
||||
color: Theme.widgetTextColor
|
||||
}
|
||||
|
||||
DankRipple {
|
||||
id: itemRipple
|
||||
cornerRadius: Theme.cornerRadius
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: trayItemArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
cursorShape: dragHandler.longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor
|
||||
|
||||
onPressed: mouse => {
|
||||
const pos = mapToItem(visualContent, mouse.x, mouse.y);
|
||||
itemRipple.trigger(pos.x, pos.y);
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
dragHandler.dragStartPos = Qt.point(mouse.x, mouse.y);
|
||||
longPressTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: mouse => {
|
||||
longPressTimer.stop();
|
||||
const wasDragging = dragHandler.dragging;
|
||||
const didReorder = wasDragging && root.dropTargetIndex >= 0 && root.dropTargetIndex !== root.draggedIndex;
|
||||
|
||||
if (didReorder) {
|
||||
root.suppressShiftAnimation = true;
|
||||
root.moveTrayItemInFullOrder(root.draggedIndex, root.dropTargetIndex);
|
||||
Qt.callLater(() => root.suppressShiftAnimation = false);
|
||||
}
|
||||
|
||||
dragHandler.longPressing = false;
|
||||
dragHandler.dragging = false;
|
||||
dragHandler.dragAxisOffset = 0;
|
||||
root.draggedIndex = -1;
|
||||
root.dropTargetIndex = -1;
|
||||
|
||||
if (wasDragging || mouse.button !== Qt.LeftButton)
|
||||
return;
|
||||
|
||||
if (!trayItem)
|
||||
return;
|
||||
if (!trayItem.onlyMenu) {
|
||||
trayItem.activate();
|
||||
return;
|
||||
}
|
||||
if (!trayItem.hasMenu)
|
||||
return;
|
||||
if (root.useOverflowPopup)
|
||||
root.menuOpen = false;
|
||||
root.showForTrayItem(trayItem, visualContent, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
|
||||
}
|
||||
|
||||
onPositionChanged: mouse => {
|
||||
if (dragHandler.longPressing && !dragHandler.dragging) {
|
||||
const distance = Math.abs(mouse.y - dragHandler.dragStartPos.y);
|
||||
if (distance > 5) {
|
||||
dragHandler.dragging = true;
|
||||
root.draggedIndex = index;
|
||||
root.dropTargetIndex = root.draggedIndex;
|
||||
}
|
||||
}
|
||||
if (!dragHandler.dragging)
|
||||
return;
|
||||
|
||||
const axisOffset = mouse.y - dragHandler.dragStartPos.y;
|
||||
dragHandler.dragAxisOffset = axisOffset;
|
||||
const itemSize = root.trayItemSize;
|
||||
const slotOffset = Math.round(axisOffset / itemSize);
|
||||
const newTargetIndex = Math.max(0, Math.min(root.mainBarItems.length - 1, index + slotOffset));
|
||||
if (newTargetIndex !== root.dropTargetIndex) {
|
||||
root.dropTargetIndex = newTargetIndex;
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: mouse => {
|
||||
if (dragHandler.dragging)
|
||||
return;
|
||||
if (mouse.button !== Qt.RightButton)
|
||||
return;
|
||||
root.openInlineTrayContextMenu(trayItem, trayItemArea, mouse, visualContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -531,7 +904,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
|
||||
@@ -550,6 +923,10 @@ BasePill {
|
||||
smooth: true
|
||||
mipmap: true
|
||||
visible: status === Image.Ready
|
||||
layer.enabled: root.trayIconsMonochrome && visible
|
||||
layer.effect: MultiEffect {
|
||||
saturation: -1
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
@@ -669,7 +1046,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
|
||||
@@ -707,6 +1084,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
|
||||
@@ -966,7 +1353,7 @@ BasePill {
|
||||
Item {
|
||||
id: bgShadowLayer
|
||||
anchors.fill: parent
|
||||
layer.enabled: true
|
||||
layer.enabled: !BlurService.enabled
|
||||
layer.smooth: true
|
||||
layer.textureSize: Qt.size(Math.round(width * overflowMenu.dpr * 2), Math.round(height * overflowMenu.dpr * 2))
|
||||
layer.textureMirroring: ShaderEffectSource.MirrorVertically
|
||||
@@ -996,6 +1383,15 @@ BasePill {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -1036,7 +1432,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
|
||||
@@ -1048,6 +1444,12 @@ BasePill {
|
||||
smooth: true
|
||||
mipmap: true
|
||||
visible: status === Image.Ready
|
||||
layer.enabled: root.trayIconTintEnabled
|
||||
layer.effect: MultiEffect {
|
||||
saturation: root.trayIconSaturation
|
||||
colorization: root.trayIconColorization
|
||||
colorizationColor: root.trayIconTintColor
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
@@ -1197,6 +1599,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
|
||||
@@ -1308,7 +1719,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;
|
||||
@@ -1366,7 +1777,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,7 +1849,7 @@ BasePill {
|
||||
Item {
|
||||
id: menuBgShadowLayer
|
||||
anchors.fill: parent
|
||||
layer.enabled: true
|
||||
layer.enabled: !BlurService.enabled
|
||||
layer.smooth: true
|
||||
layer.textureSize: Qt.size(Math.round(width * menuWindow.dpr), Math.round(height * menuWindow.dpr))
|
||||
layer.textureMirroring: ShaderEffectSource.MirrorVertically
|
||||
@@ -1466,6 +1877,15 @@ BasePill {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -1498,7 +1918,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
|
||||
@@ -1551,7 +1971,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
|
||||
@@ -1602,7 +2022,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1815,5 +1925,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ DankPopout {
|
||||
isRightEdge: root.__dropdownRightEdge
|
||||
activePlayer: root.__dropdownPlayer
|
||||
allPlayers: root.__dropdownPlayers
|
||||
targetWindow: root.backgroundWindow
|
||||
onCloseRequested: root.__hideDropdowns()
|
||||
onPanelEntered: root.__stopCloseTimer()
|
||||
onPanelExited: root.__startCloseTimer()
|
||||
|
||||
@@ -16,6 +16,7 @@ Item {
|
||||
property var allPlayers: []
|
||||
property point anchorPos: Qt.point(0, 0)
|
||||
property bool isRightEdge: false
|
||||
property var targetWindow: null
|
||||
|
||||
property bool __isChromeBrowser: {
|
||||
if (!activePlayer?.identity)
|
||||
@@ -25,7 +26,7 @@ Item {
|
||||
}
|
||||
property bool usePlayerVolume: activePlayer && activePlayer.volumeSupported && !__isChromeBrowser
|
||||
property real currentVolume: usePlayerVolume ? activePlayer.volume : (AudioService.sink?.audio?.volume ?? 0)
|
||||
property bool volumeAvailable: (activePlayer && activePlayer.volumeSupported && !__isChromeBrowser) || (AudioService.sink && AudioService.sink.audio)
|
||||
property bool volumeAvailable: !!((activePlayer && activePlayer.volumeSupported && !__isChromeBrowser) || (AudioService.sink && AudioService.sink.audio))
|
||||
property var availableDevices: {
|
||||
const hidden = SessionData.hiddenOutputDeviceNames ?? [];
|
||||
return Pipewire.nodes.values.filter(node => {
|
||||
@@ -57,6 +58,30 @@ Item {
|
||||
});
|
||||
}
|
||||
|
||||
readonly property Item __activePanel: {
|
||||
switch (dropdownType) {
|
||||
case 1:
|
||||
return volumePanel;
|
||||
case 2:
|
||||
return audioDevicesPanel;
|
||||
case 3:
|
||||
return playersPanel;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
WindowBlur {
|
||||
targetWindow: root.targetWindow
|
||||
readonly property bool active: root.__activePanel !== null && root.__activePanel.visible && root.__activePanel.opacity > 0
|
||||
readonly property real s: root.__activePanel ? Math.min(1, root.__activePanel.scale) : 1
|
||||
blurX: root.__activePanel ? root.__activePanel.x + root.__activePanel.width * (1 - s) * 0.5 : 0
|
||||
blurY: root.__activePanel ? root.__activePanel.y + root.__activePanel.height * (1 - s) * 0.5 : 0
|
||||
blurWidth: active ? root.__activePanel.width * s : 0
|
||||
blurHeight: active ? root.__activePanel.height * s : 0
|
||||
blurRadius: Theme.cornerRadius * 2
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: volumePanel
|
||||
visible: dropdownType === 1 && volumeAvailable
|
||||
@@ -65,8 +90,8 @@ Item {
|
||||
x: isRightEdge ? anchorPos.x : anchorPos.x - width
|
||||
y: anchorPos.y - height / 2
|
||||
radius: Theme.cornerRadius * 2
|
||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
||||
color: Theme.floatingSurface
|
||||
border.color: Theme.outlineStrong
|
||||
border.width: 1
|
||||
|
||||
opacity: dropdownType === 1 ? 1 : 0
|
||||
@@ -89,7 +114,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
layer.enabled: true
|
||||
layer.enabled: !BlurService.enabled
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowHorizontalOffset: 0
|
||||
@@ -123,23 +148,26 @@ Item {
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
anchors.centerIn: parent
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
color: Theme.withAlpha(Theme.outline, Theme.popupTransparency)
|
||||
radius: Theme.cornerRadius
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
readonly property real ratio: volumeAvailable ? Math.min(1.0, currentVolume) : 0
|
||||
readonly property real thumbHeight: 4
|
||||
width: parent.width
|
||||
height: volumeAvailable ? (Math.min(1.0, currentVolume) * parent.height) : 0
|
||||
height: Math.max(0, ratio * (parent.height - thumbHeight) - 3)
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: Theme.primary
|
||||
bottomLeftRadius: Theme.cornerRadius
|
||||
bottomRightRadius: Theme.cornerRadius
|
||||
radius: Theme.cornerRadius
|
||||
topLeftRadius: 0
|
||||
topRightRadius: 0
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width + 8
|
||||
height: 8
|
||||
height: 4
|
||||
radius: Theme.cornerRadius
|
||||
y: {
|
||||
const ratio = volumeAvailable ? Math.min(1.0, currentVolume) : 0;
|
||||
@@ -148,8 +176,7 @@ Item {
|
||||
}
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: Theme.primary
|
||||
border.width: 3
|
||||
border.color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 1.0)
|
||||
border.width: 0
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -199,8 +226,8 @@ Item {
|
||||
x: isRightEdge ? anchorPos.x : anchorPos.x - width
|
||||
y: anchorPos.y - height / 2
|
||||
radius: Theme.cornerRadius * 2
|
||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.98)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.6)
|
||||
color: Theme.floatingSurface
|
||||
border.color: Theme.outlineStrong
|
||||
border.width: 2
|
||||
|
||||
opacity: dropdownType === 2 ? 1 : 0
|
||||
@@ -223,7 +250,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
layer.enabled: true
|
||||
layer.enabled: !BlurService.enabled
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowHorizontalOffset: 0
|
||||
@@ -267,7 +294,7 @@ Item {
|
||||
width: parent.width
|
||||
height: 48
|
||||
radius: Theme.cornerRadius
|
||||
color: deviceMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
color: deviceMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.nestedSurface
|
||||
border.color: modelData === AudioService.sink ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
border.width: modelData === AudioService.sink ? 2 : 1
|
||||
|
||||
@@ -328,8 +355,8 @@ Item {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (modelData) {
|
||||
Pipewire.preferredDefaultAudioSink = modelData;
|
||||
if (modelData && modelData.name) {
|
||||
AudioService.setDefaultSinkByName(modelData.name);
|
||||
root.deviceSelected(modelData);
|
||||
}
|
||||
}
|
||||
@@ -349,8 +376,8 @@ Item {
|
||||
x: isRightEdge ? anchorPos.x : anchorPos.x - width
|
||||
y: anchorPos.y - height / 2
|
||||
radius: Theme.cornerRadius * 2
|
||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.98)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.6)
|
||||
color: Theme.floatingSurface
|
||||
border.color: Theme.outlineStrong
|
||||
border.width: 2
|
||||
|
||||
opacity: dropdownType === 3 ? 1 : 0
|
||||
@@ -373,7 +400,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
layer.enabled: true
|
||||
layer.enabled: !BlurService.enabled
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowHorizontalOffset: 0
|
||||
@@ -417,7 +444,7 @@ Item {
|
||||
width: parent.width
|
||||
height: 48
|
||||
radius: Theme.cornerRadius
|
||||
color: playerMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
color: playerMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.nestedSurface
|
||||
border.color: modelData === activePlayer ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
border.width: modelData === activePlayer ? 2 : 1
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import QtQuick
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Services.Mpris
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
@@ -57,7 +56,7 @@ Item {
|
||||
const id = activePlayer.identity.toLowerCase();
|
||||
return id.includes("chrome") || id.includes("chromium");
|
||||
}
|
||||
readonly property bool volumeAvailable: (activePlayer && activePlayer.volumeSupported && !__isChromeBrowser) || (AudioService.sink && AudioService.sink.audio)
|
||||
readonly property bool volumeAvailable: !!((activePlayer && activePlayer.volumeSupported && !__isChromeBrowser) || (AudioService.sink && AudioService.sink.audio))
|
||||
readonly property bool usePlayerVolume: activePlayer && activePlayer.volumeSupported && !__isChromeBrowser
|
||||
readonly property real currentVolume: usePlayerVolume ? activePlayer.volume : (AudioService.sink?.audio?.volume ?? 0)
|
||||
|
||||
@@ -638,7 +637,7 @@ Item {
|
||||
x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM
|
||||
y: 185
|
||||
color: playerSelectorArea.containsMouse || playersExpanded ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) : "transparent"
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
||||
border.color: Theme.outlineStrong
|
||||
border.width: 1
|
||||
z: 100
|
||||
visible: (allPlayers?.length || 0) >= 1
|
||||
@@ -681,7 +680,7 @@ Item {
|
||||
x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM
|
||||
y: 130
|
||||
color: volumeButtonArea.containsMouse && volumeAvailable || volumeExpanded ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) : "transparent"
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, volumeAvailable ? 0.3 : 0.15)
|
||||
border.color: volumeAvailable ? Theme.outlineStrong : Theme.outlineMedium
|
||||
border.width: 1
|
||||
z: 101
|
||||
enabled: volumeAvailable
|
||||
@@ -758,7 +757,7 @@ Item {
|
||||
x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM
|
||||
y: 240
|
||||
color: audioDevicesArea.containsMouse || devicesExpanded ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) : "transparent"
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
||||
border.color: Theme.outlineStrong
|
||||
border.width: 1
|
||||
z: 100
|
||||
|
||||
|
||||
@@ -83,8 +83,8 @@ Rectangle {
|
||||
}
|
||||
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.05)
|
||||
color: Theme.nestedSurface
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: 1
|
||||
|
||||
Column {
|
||||
@@ -351,7 +351,7 @@ Rectangle {
|
||||
} else if (eventMouseArea.containsMouse) {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.06);
|
||||
}
|
||||
return Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency);
|
||||
return Theme.nestedSurface;
|
||||
}
|
||||
border.color: {
|
||||
if (modelData.url && eventMouseArea.containsMouse) {
|
||||
@@ -359,9 +359,9 @@ Rectangle {
|
||||
} else if (eventMouseArea.containsMouse) {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.15);
|
||||
}
|
||||
return "transparent";
|
||||
return Theme.outlineMedium;
|
||||
}
|
||||
border.width: 1
|
||||
border.width: eventMouseArea.containsMouse ? 1 : Theme.layerOutlineWidth
|
||||
|
||||
Rectangle {
|
||||
width: 3
|
||||
|
||||
@@ -10,8 +10,8 @@ Rectangle {
|
||||
property int pad: Theme.spacingM
|
||||
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
color: Theme.nestedSurface
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: 1
|
||||
|
||||
default property alias content: contentItem.data
|
||||
|
||||
@@ -103,7 +103,7 @@ Rectangle {
|
||||
}
|
||||
]
|
||||
|
||||
color: isCurrent ? Theme.withAlpha(Theme.primary, 0.1) : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
color: isCurrent ? Theme.withAlpha(Theme.primary, 0.1) : Theme.nestedSurface
|
||||
border.color: isCurrent ? Theme.withAlpha(Theme.primary, 0.3) : "transparent"
|
||||
border.width: isCurrent ? 1 : 0
|
||||
|
||||
|
||||
@@ -213,8 +213,8 @@ Item {
|
||||
width: parent.width
|
||||
height: heroContent.height + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
border.color: Theme.withAlpha(Theme.outline, 0.08)
|
||||
color: Theme.nestedSurface
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: 1
|
||||
|
||||
Column {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -35,14 +35,14 @@ Rectangle {
|
||||
color: {
|
||||
if (isSelected && keyboardNavigationActive)
|
||||
return Theme.primaryPressed;
|
||||
return Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency);
|
||||
return Theme.floatingSurfaceHigh;
|
||||
}
|
||||
border.color: {
|
||||
if (isSelected && keyboardNavigationActive)
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.5);
|
||||
if (historyItem.urgency === 2)
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3);
|
||||
return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.05);
|
||||
return Theme.outlineMedium;
|
||||
}
|
||||
border.width: {
|
||||
if (isSelected && keyboardNavigationActive)
|
||||
@@ -122,12 +122,10 @@ Rectangle {
|
||||
return "";
|
||||
const appIcon = historyItem.appIcon;
|
||||
if (!appIcon)
|
||||
return iconFromImage ? Paths.resolveIconUrl(iconFromImage) : "";
|
||||
return "";
|
||||
if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://") || appIcon.includes("/"))
|
||||
return appIcon;
|
||||
if (appIcon.startsWith("material:") || appIcon.startsWith("svg:") || appIcon.startsWith("unicode:") || appIcon.startsWith("image:"))
|
||||
return "";
|
||||
return Paths.resolveIconPath(appIcon);
|
||||
return "";
|
||||
}
|
||||
|
||||
hasImage: hasNotificationImage
|
||||
|
||||
@@ -71,7 +71,7 @@ Rectangle {
|
||||
if (keyboardNavigationActive && expanded && selectedNotificationIndex >= 0) {
|
||||
return Theme.primaryHoverLight;
|
||||
}
|
||||
return Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency);
|
||||
return Theme.floatingSurfaceHigh;
|
||||
}
|
||||
border.color: {
|
||||
if (isGroupSelected && keyboardNavigationActive) {
|
||||
@@ -83,7 +83,7 @@ Rectangle {
|
||||
if (notificationGroup?.latestNotification?.urgency === NotificationUrgency.Critical) {
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3);
|
||||
}
|
||||
return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.05);
|
||||
return Theme.outlineMedium;
|
||||
}
|
||||
border.width: {
|
||||
if (isGroupSelected && keyboardNavigationActive) {
|
||||
@@ -169,12 +169,10 @@ Rectangle {
|
||||
return "";
|
||||
const appIcon = notificationGroup?.latestNotification?.appIcon;
|
||||
if (!appIcon)
|
||||
return iconFromImage ? Paths.resolveIconUrl(iconFromImage) : "";
|
||||
return "";
|
||||
if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://") || appIcon.includes("/"))
|
||||
return appIcon;
|
||||
if (appIcon.startsWith("material:") || appIcon.startsWith("svg:") || appIcon.startsWith("unicode:") || appIcon.startsWith("image:"))
|
||||
return "";
|
||||
return Paths.resolveIconPath(appIcon);
|
||||
return "";
|
||||
}
|
||||
|
||||
hasImage: hasNotificationImage
|
||||
@@ -454,8 +452,8 @@ Rectangle {
|
||||
return expandedBaseHeight;
|
||||
}
|
||||
radius: Theme.cornerRadius
|
||||
color: isSelected ? Theme.primaryPressed : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
border.color: isSelected ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.4) : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.05)
|
||||
color: isSelected ? Theme.primaryPressed : Theme.nestedSurface
|
||||
border.color: isSelected ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.4) : Theme.outlineMedium
|
||||
border.width: 1
|
||||
|
||||
Behavior on border.color {
|
||||
@@ -503,12 +501,10 @@ Rectangle {
|
||||
return "";
|
||||
const appIcon = modelData?.appIcon;
|
||||
if (!appIcon)
|
||||
return iconFromImage ? Paths.resolveIconUrl(iconFromImage) : "";
|
||||
return "";
|
||||
if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://") || appIcon.includes("/"))
|
||||
return appIcon;
|
||||
if (appIcon.startsWith("material:") || appIcon.startsWith("svg:") || appIcon.startsWith("unicode:") || appIcon.startsWith("image:"))
|
||||
return "";
|
||||
return Paths.resolveIconPath(appIcon);
|
||||
return "";
|
||||
}
|
||||
|
||||
fallbackIcon: {
|
||||
|
||||
@@ -99,7 +99,9 @@ Item {
|
||||
height: Theme.iconSize + Theme.spacingS
|
||||
radius: Theme.cornerRadius
|
||||
visible: root.currentTab === 0 ? NotificationService.notifications.length > 0 : NotificationService.historyList.length > 0
|
||||
color: clearArea.containsMouse ? Theme.primaryHoverLight : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
color: clearArea.containsMouse ? Theme.primaryHoverLight : Theme.nestedSurface
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: Theme.layerOutlineWidth
|
||||
|
||||
Row {
|
||||
id: clearButtonContent
|
||||
|
||||
@@ -14,8 +14,8 @@ Rectangle {
|
||||
visible: expanded
|
||||
clip: true
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.3)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.1)
|
||||
color: Theme.nestedSurface
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: 1
|
||||
|
||||
Behavior on height {
|
||||
|
||||
@@ -11,6 +11,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
|
||||
@@ -304,6 +313,8 @@ PanelWindow {
|
||||
}
|
||||
}
|
||||
|
||||
readonly property real cardInset: Theme.snap(4, win.dpr)
|
||||
|
||||
property real swipeOffset: 0
|
||||
readonly property real dismissThreshold: isCenterPosition ? height * 0.4 : width * 0.35
|
||||
readonly property real swipeFadeStartRatio: 0.75
|
||||
@@ -314,8 +325,9 @@ PanelWindow {
|
||||
property bool swipeDismissing: false
|
||||
|
||||
readonly property real radiusForShadow: Theme.cornerRadius
|
||||
property real shadowBlurPx: SettingsData.notificationPopupShadowEnabled ? ((2 + radiusForShadow * 0.2) * (cardHoverHandler.hovered ? 1.2 : 1)) : 0
|
||||
property real shadowSpreadPx: SettingsData.notificationPopupShadowEnabled ? (radiusForShadow * (cardHoverHandler.hovered ? 0.06 : 0)) : 0
|
||||
readonly property bool shadowsAllowed: SettingsData.notificationPopupShadowEnabled && !BlurService.enabled
|
||||
property real shadowBlurPx: shadowsAllowed ? ((2 + radiusForShadow * 0.2) * (cardHoverHandler.hovered ? 1.2 : 1)) : 0
|
||||
property real shadowSpreadPx: shadowsAllowed ? (radiusForShadow * (cardHoverHandler.hovered ? 0.06 : 0)) : 0
|
||||
property real shadowBaseAlpha: 0.35
|
||||
readonly property real popupSurfaceAlpha: SettingsData.popupTransparency
|
||||
readonly property real effectiveShadowAlpha: Math.max(0, Math.min(1, shadowBaseAlpha * popupSurfaceAlpha))
|
||||
@@ -338,7 +350,7 @@ PanelWindow {
|
||||
id: bgShadowLayer
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.snap(4, win.dpr)
|
||||
layer.enabled: !win._isDestroying && win.screenValid
|
||||
layer.enabled: !win._isDestroying && win.screenValid && content.shadowsAllowed
|
||||
layer.smooth: false
|
||||
layer.textureSize: Qt.size(Math.round(width * win.dpr), Math.round(height * win.dpr))
|
||||
layer.textureMirroring: ShaderEffectSource.MirrorVertically
|
||||
@@ -396,6 +408,17 @@ PanelWindow {
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: content.cardInset
|
||||
radius: Theme.cornerRadius
|
||||
antialiasing: true
|
||||
color: "transparent"
|
||||
border.color: BlurService.enabled ? BlurService.borderColor : Theme.outlineMedium
|
||||
border.width: BlurService.enabled ? BlurService.borderWidth : 1
|
||||
z: 100
|
||||
}
|
||||
|
||||
Item {
|
||||
id: backgroundContainer
|
||||
anchors.fill: parent
|
||||
@@ -479,12 +502,10 @@ PanelWindow {
|
||||
return "";
|
||||
const appIcon = notificationData.appIcon;
|
||||
if (!appIcon)
|
||||
return iconFromImage ? Paths.resolveIconUrl(iconFromImage) : "";
|
||||
return "";
|
||||
if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://") || appIcon.includes("/"))
|
||||
return appIcon;
|
||||
if (appIcon.startsWith("material:") || appIcon.startsWith("svg:") || appIcon.startsWith("unicode:") || appIcon.startsWith("image:"))
|
||||
return "";
|
||||
return Paths.resolveIconPath(appIcon);
|
||||
return "";
|
||||
}
|
||||
|
||||
hasImage: hasNotificationImage
|
||||
|
||||
@@ -95,7 +95,7 @@ DankOSD {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
minimum: 0
|
||||
maximum: AudioService.sinkMaxVolume
|
||||
enabled: AudioService.sink?.audio
|
||||
enabled: AudioService.sink?.audio ?? false
|
||||
showValue: true
|
||||
unit: "%"
|
||||
thumbOutlineColor: Theme.surfaceContainer
|
||||
@@ -207,7 +207,7 @@ DankOSD {
|
||||
id: vertSliderArea
|
||||
anchors.fill: parent
|
||||
anchors.margins: -12
|
||||
enabled: AudioService.sink?.audio
|
||||
enabled: AudioService.sink?.audio ?? false
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 80
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
color: Theme.nestedSurface
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
@@ -110,7 +110,7 @@ Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
color: Theme.nestedSurface
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -163,7 +163,7 @@ Item {
|
||||
property color extraInfoColor: Theme.surfaceVariantText
|
||||
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
color: Theme.nestedSurface
|
||||
border.color: Theme.outlineLight
|
||||
border.width: 1
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user