mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-05-06 20:42:07 -04:00
Compare commits
2 Commits
e78ba77def
...
anims
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62bf9c6efe | ||
|
|
61a77bd186 |
2
.github/workflows/dms-stable.yml
vendored
2
.github/workflows/dms-stable.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ steps.app_token.outputs.token }}
|
||||
|
||||
4
.github/workflows/go-ci.yml
vendored
4
.github/workflows/go-ci.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install flatpak
|
||||
run: sudo apt update && sudo apt install -y flatpak
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
run: sudo flatpak install -y org.freedesktop.Platform/x86_64/24.08 app.zen_browser.zen
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: ./core/go.mod
|
||||
|
||||
|
||||
2
.github/workflows/nix-pr-check.yml
vendored
2
.github/workflows/nix-pr-check.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
4
.github/workflows/prek.yml
vendored
4
.github/workflows/prek.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install flatpak
|
||||
run: sudo apt update && sudo apt install -y flatpak
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
run: sudo flatpak install -y org.freedesktop.Platform/x86_64/24.08 app.zen_browser.zen
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: core/go.mod
|
||||
|
||||
|
||||
16
.github/workflows/release.yml
vendored
16
.github/workflows/release.yml
vendored
@@ -32,13 +32,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: ./core/go.mod
|
||||
|
||||
@@ -106,7 +106,7 @@ jobs:
|
||||
|
||||
- name: Upload artifacts (${{ matrix.arch }})
|
||||
if: matrix.arch == 'arm64'
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: core-assets-${{ matrix.arch }}
|
||||
path: |
|
||||
@@ -120,7 +120,7 @@ jobs:
|
||||
|
||||
- name: Upload artifacts with completions
|
||||
if: matrix.arch == 'amd64'
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: core-assets-${{ matrix.arch }}
|
||||
path: |
|
||||
@@ -147,7 +147,7 @@ jobs:
|
||||
# private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||
|
||||
# - name: Checkout
|
||||
# uses: actions/checkout@v6
|
||||
# uses: actions/checkout@v4
|
||||
# with:
|
||||
# token: ${{ steps.app_token.outputs.token }}
|
||||
# fetch-depth: 0
|
||||
@@ -181,7 +181,7 @@ jobs:
|
||||
TAG: ${{ inputs.tag }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
fetch-depth: 0
|
||||
@@ -192,12 +192,12 @@ jobs:
|
||||
git checkout ${TAG}
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: ./core/go.mod
|
||||
|
||||
- name: Download core artifacts
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: core-assets-*
|
||||
merge-multiple: true
|
||||
|
||||
4
.github/workflows/run-copr.yml
vendored
4
.github/workflows/run-copr.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Determine version
|
||||
id: version
|
||||
@@ -134,7 +134,7 @@ jobs:
|
||||
rpm -qpi "$SRPM"
|
||||
|
||||
- name: Upload SRPM artifact
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.package }}-stable-srpm-${{ steps.version.outputs.version }}
|
||||
path: ${{ steps.build.outputs.srpm_path }}
|
||||
|
||||
9
.github/workflows/run-obs.yml
vendored
9
.github/workflows/run-obs.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -195,13 +195,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Wait before OBS upload
|
||||
run: sleep 3
|
||||
|
||||
- name: Determine packages to update
|
||||
id: packages
|
||||
run: |
|
||||
@@ -347,7 +344,7 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: ./core/go.mod
|
||||
|
||||
|
||||
6
.github/workflows/run-ppa.yml
vendored
6
.github/workflows/run-ppa.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -157,12 +157,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: ./core/go.mod
|
||||
cache: false
|
||||
|
||||
2
.github/workflows/update-vendor-hash.yml
vendored
2
.github/workflows/update-vendor-hash.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ steps.app_token.outputs.token }}
|
||||
|
||||
@@ -86,9 +86,7 @@ touch .qmlls.ini
|
||||
|
||||
4. Restart dms to generate the `.qmlls.ini` file
|
||||
|
||||
5. Run `make lint-qml` from the repo root to lint QML entrypoints (requires the `.qmlls.ini` generated above). The script needs the **Qt 6** `qmllint`; it checks `qmllint6`, Fedora's `qmllint-qt6`, `/usr/lib/qt6/bin/qmllint`, then `qmllint` in `PATH`. If your Qt 6 binary lives elsewhere, set `QMLLINT=/path/to/qmllint`.
|
||||
|
||||
6. Make your changes, test, and open a pull request.
|
||||
5. Make your changes, test, and open a pull request.
|
||||
|
||||
### I18n/Localization
|
||||
|
||||
|
||||
6
Makefile
6
Makefile
@@ -18,7 +18,7 @@ SHELL_INSTALL_DIR=$(DATA_DIR)/quickshell/dms
|
||||
ASSETS_DIR=assets
|
||||
APPLICATIONS_DIR=$(DATA_DIR)/applications
|
||||
|
||||
.PHONY: all build clean lint-qml install install-bin install-shell install-completions install-systemd install-icon install-desktop uninstall uninstall-bin uninstall-shell uninstall-completions uninstall-systemd uninstall-icon uninstall-desktop help
|
||||
.PHONY: all build clean install install-bin install-shell install-completions install-systemd install-icon install-desktop uninstall uninstall-bin uninstall-shell uninstall-completions uninstall-systemd uninstall-icon uninstall-desktop help
|
||||
|
||||
all: build
|
||||
|
||||
@@ -32,9 +32,6 @@ clean:
|
||||
@$(MAKE) -C $(CORE_DIR) clean
|
||||
@echo "Clean complete"
|
||||
|
||||
lint-qml:
|
||||
@./quickshell/scripts/qmllint-entrypoints.sh
|
||||
|
||||
# Installation targets
|
||||
install-bin:
|
||||
@echo "Installing $(BINARY_NAME) to $(INSTALL_DIR)..."
|
||||
@@ -133,7 +130,6 @@ help:
|
||||
@echo " all (default) - Build the DMS binary"
|
||||
@echo " build - Same as 'all'"
|
||||
@echo " clean - Clean build artifacts"
|
||||
@echo " lint-qml - Run qmllint on shell entrypoints using the Quickshell tooling VFS"
|
||||
@echo ""
|
||||
@echo "Install:"
|
||||
@echo " install - Build and install everything (requires sudo)"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/golangci/golangci-lint
|
||||
rev: v2.9.0
|
||||
rev: v2.10.1
|
||||
hooks:
|
||||
- id: golangci-lint-fmt
|
||||
require_serial: true
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"policy_version": 1,
|
||||
"blocked_commands": [
|
||||
"greeter install",
|
||||
"greeter enable",
|
||||
"greeter sync",
|
||||
"greeter uninstall",
|
||||
"setup"
|
||||
],
|
||||
"message": "This command is disabled on immutable/image-based systems. Use your distro-native workflow for system-level changes."
|
||||
}
|
||||
@@ -222,19 +222,16 @@ func init() {
|
||||
|
||||
func runClipCopy(cmd *cobra.Command, args []string) {
|
||||
var data []byte
|
||||
copyFromStdin := false
|
||||
|
||||
switch {
|
||||
case len(args) > 0:
|
||||
data = []byte(args[0])
|
||||
case clipCopyDownload || clipCopyType == "__multi__":
|
||||
default:
|
||||
var err error
|
||||
data, err = io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
log.Fatalf("read stdin: %v", err)
|
||||
}
|
||||
default:
|
||||
copyFromStdin = true
|
||||
}
|
||||
|
||||
if clipCopyDownload {
|
||||
@@ -260,13 +257,6 @@ func runClipCopy(cmd *cobra.Command, args []string) {
|
||||
return
|
||||
}
|
||||
|
||||
if copyFromStdin {
|
||||
if err := clipboard.CopyReader(os.Stdin, clipCopyType, clipCopyForeground, clipCopyPasteOnce); err != nil {
|
||||
log.Fatalf("copy: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := clipboard.CopyOpts(data, clipCopyType, clipCopyForeground, clipCopyPasteOnce); err != nil {
|
||||
log.Fatalf("copy: %v", err)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,10 +16,9 @@ import (
|
||||
)
|
||||
|
||||
var setupCmd = &cobra.Command{
|
||||
Use: "setup",
|
||||
Short: "Deploy DMS configurations",
|
||||
Long: "Deploy compositor and terminal configurations with interactive prompts",
|
||||
PersistentPreRunE: requireMutableSystemCommand,
|
||||
Use: "setup",
|
||||
Short: "Deploy DMS configurations",
|
||||
Long: "Deploy compositor and terminal configurations with interactive prompts",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := runSetup(); err != nil {
|
||||
log.Fatalf("Error during setup: %v", err)
|
||||
|
||||
@@ -1,271 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
cliPolicyPackagedPath = "/usr/share/dms/cli-policy.json"
|
||||
cliPolicyAdminPath = "/etc/dms/cli-policy.json"
|
||||
)
|
||||
|
||||
var (
|
||||
immutablePolicyOnce sync.Once
|
||||
immutablePolicy immutableCommandPolicy
|
||||
immutablePolicyErr error
|
||||
)
|
||||
|
||||
//go:embed assets/cli-policy.default.json
|
||||
var defaultCLIPolicyJSON []byte
|
||||
|
||||
type immutableCommandPolicy struct {
|
||||
ImmutableSystem bool
|
||||
ImmutableReason string
|
||||
BlockedCommands []string
|
||||
Message string
|
||||
}
|
||||
|
||||
type cliPolicyFile struct {
|
||||
PolicyVersion int `json:"policy_version"`
|
||||
ImmutableSystem *bool `json:"immutable_system"`
|
||||
BlockedCommands *[]string `json:"blocked_commands"`
|
||||
Message *string `json:"message"`
|
||||
}
|
||||
|
||||
func normalizeCommandSpec(raw string) string {
|
||||
normalized := strings.ToLower(strings.TrimSpace(raw))
|
||||
normalized = strings.TrimPrefix(normalized, "dms ")
|
||||
return strings.Join(strings.Fields(normalized), " ")
|
||||
}
|
||||
|
||||
func normalizeBlockedCommands(raw []string) []string {
|
||||
normalized := make([]string, 0, len(raw))
|
||||
seen := make(map[string]bool)
|
||||
|
||||
for _, cmd := range raw {
|
||||
spec := normalizeCommandSpec(cmd)
|
||||
if spec == "" || seen[spec] {
|
||||
continue
|
||||
}
|
||||
seen[spec] = true
|
||||
normalized = append(normalized, spec)
|
||||
}
|
||||
|
||||
return normalized
|
||||
}
|
||||
|
||||
func commandBlockedByPolicy(commandPath string, blocked []string) bool {
|
||||
normalizedPath := normalizeCommandSpec(commandPath)
|
||||
if normalizedPath == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, entry := range blocked {
|
||||
spec := normalizeCommandSpec(entry)
|
||||
if spec == "" {
|
||||
continue
|
||||
}
|
||||
if normalizedPath == spec || strings.HasPrefix(normalizedPath, spec+" ") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func loadPolicyFile(path string) (*cliPolicyFile, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("failed to read %s: %w", path, err)
|
||||
}
|
||||
|
||||
var policy cliPolicyFile
|
||||
if err := json.Unmarshal(data, &policy); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse %s: %w", path, err)
|
||||
}
|
||||
|
||||
return &policy, nil
|
||||
}
|
||||
|
||||
func mergePolicyFile(base *immutableCommandPolicy, path string) error {
|
||||
policyFile, err := loadPolicyFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if policyFile == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if policyFile.ImmutableSystem != nil {
|
||||
base.ImmutableSystem = *policyFile.ImmutableSystem
|
||||
}
|
||||
if policyFile.BlockedCommands != nil {
|
||||
base.BlockedCommands = normalizeBlockedCommands(*policyFile.BlockedCommands)
|
||||
}
|
||||
if policyFile.Message != nil {
|
||||
msg := strings.TrimSpace(*policyFile.Message)
|
||||
if msg != "" {
|
||||
base.Message = msg
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readOSReleaseMap(path string) map[string]string {
|
||||
values := make(map[string]string)
|
||||
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return values
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(line, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
key := strings.ToUpper(strings.TrimSpace(parts[0]))
|
||||
value := strings.Trim(strings.TrimSpace(parts[1]), "\"")
|
||||
values[key] = strings.ToLower(value)
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
|
||||
func hasAnyToken(text string, tokens ...string) bool {
|
||||
if text == "" {
|
||||
return false
|
||||
}
|
||||
for _, token := range tokens {
|
||||
if strings.Contains(text, token) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func detectImmutableSystem() (bool, string) {
|
||||
if _, err := os.Stat("/run/ostree-booted"); err == nil {
|
||||
return true, "/run/ostree-booted is present"
|
||||
}
|
||||
|
||||
osRelease := readOSReleaseMap("/etc/os-release")
|
||||
if len(osRelease) == 0 {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
id := osRelease["ID"]
|
||||
idLike := osRelease["ID_LIKE"]
|
||||
variantID := osRelease["VARIANT_ID"]
|
||||
name := osRelease["NAME"]
|
||||
prettyName := osRelease["PRETTY_NAME"]
|
||||
|
||||
immutableIDs := map[string]bool{
|
||||
"bluefin": true,
|
||||
"bazzite": true,
|
||||
"silverblue": true,
|
||||
"kinoite": true,
|
||||
"sericea": true,
|
||||
"onyx": true,
|
||||
"aurora": true,
|
||||
"fedora-iot": true,
|
||||
"fedora-coreos": true,
|
||||
}
|
||||
if immutableIDs[id] {
|
||||
return true, "os-release ID=" + id
|
||||
}
|
||||
|
||||
markers := []string{"silverblue", "kinoite", "sericea", "onyx", "bazzite", "bluefin", "aurora", "ostree", "atomic"}
|
||||
if hasAnyToken(variantID, markers...) {
|
||||
return true, "os-release VARIANT_ID=" + variantID
|
||||
}
|
||||
if hasAnyToken(idLike, "ostree", "rpm-ostree") {
|
||||
return true, "os-release ID_LIKE=" + idLike
|
||||
}
|
||||
if hasAnyToken(name, markers...) || hasAnyToken(prettyName, markers...) {
|
||||
return true, "os-release identifies an atomic/ostree variant"
|
||||
}
|
||||
|
||||
return false, ""
|
||||
}
|
||||
|
||||
func getImmutablePolicy() (*immutableCommandPolicy, error) {
|
||||
immutablePolicyOnce.Do(func() {
|
||||
detectedImmutable, reason := detectImmutableSystem()
|
||||
immutablePolicy = immutableCommandPolicy{
|
||||
ImmutableSystem: detectedImmutable,
|
||||
ImmutableReason: reason,
|
||||
BlockedCommands: []string{"greeter install", "greeter enable", "greeter sync", "setup"},
|
||||
Message: "This command is disabled on immutable/image-based systems. Use your distro-native workflow for system-level changes.",
|
||||
}
|
||||
|
||||
var defaultPolicy cliPolicyFile
|
||||
if err := json.Unmarshal(defaultCLIPolicyJSON, &defaultPolicy); err != nil {
|
||||
immutablePolicyErr = fmt.Errorf("failed to parse embedded default CLI policy: %w", err)
|
||||
return
|
||||
}
|
||||
if defaultPolicy.BlockedCommands != nil {
|
||||
immutablePolicy.BlockedCommands = normalizeBlockedCommands(*defaultPolicy.BlockedCommands)
|
||||
}
|
||||
if defaultPolicy.Message != nil {
|
||||
msg := strings.TrimSpace(*defaultPolicy.Message)
|
||||
if msg != "" {
|
||||
immutablePolicy.Message = msg
|
||||
}
|
||||
}
|
||||
|
||||
if err := mergePolicyFile(&immutablePolicy, cliPolicyPackagedPath); err != nil {
|
||||
immutablePolicyErr = err
|
||||
return
|
||||
}
|
||||
if err := mergePolicyFile(&immutablePolicy, cliPolicyAdminPath); err != nil {
|
||||
immutablePolicyErr = err
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
if immutablePolicyErr != nil {
|
||||
return nil, immutablePolicyErr
|
||||
}
|
||||
return &immutablePolicy, nil
|
||||
}
|
||||
|
||||
func requireMutableSystemCommand(cmd *cobra.Command, _ []string) error {
|
||||
policy, err := getImmutablePolicy()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !policy.ImmutableSystem {
|
||||
return nil
|
||||
}
|
||||
|
||||
commandPath := normalizeCommandSpec(cmd.CommandPath())
|
||||
if !commandBlockedByPolicy(commandPath, policy.BlockedCommands) {
|
||||
return nil
|
||||
}
|
||||
|
||||
reason := ""
|
||||
if policy.ImmutableReason != "" {
|
||||
reason = "Detected immutable system: " + policy.ImmutableReason + "\n"
|
||||
}
|
||||
|
||||
return fmt.Errorf("%s%s\nCommand: dms %s\nPolicy files:\n %s\n %s", reason, policy.Message, commandPath, cliPolicyPackagedPath, cliPolicyAdminPath)
|
||||
}
|
||||
@@ -16,10 +16,19 @@ func init() {
|
||||
runCmd.Flags().Bool("session", false, "Session managed (like as a systemd unit)")
|
||||
runCmd.Flags().MarkHidden("daemon-child")
|
||||
|
||||
greeterCmd.AddCommand(greeterInstallCmd, greeterSyncCmd, greeterEnableCmd, greeterStatusCmd, greeterUninstallCmd)
|
||||
// Add subcommands to greeter
|
||||
greeterCmd.AddCommand(greeterInstallCmd, greeterSyncCmd, greeterEnableCmd, greeterStatusCmd)
|
||||
|
||||
// Add subcommands to setup
|
||||
setupCmd.AddCommand(setupBindsCmd, setupLayoutCmd, setupColorsCmd, setupAlttabCmd, setupOutputsCmd, setupCursorCmd, setupWindowrulesCmd)
|
||||
|
||||
// Add subcommands to update
|
||||
updateCmd.AddCommand(updateCheckCmd)
|
||||
|
||||
// Add subcommands to plugins
|
||||
pluginsCmd.AddCommand(pluginsBrowseCmd, pluginsListCmd, pluginsInstallCmd, pluginsUninstallCmd, pluginsUpdateCmd)
|
||||
|
||||
// Add common commands to root
|
||||
rootCmd.AddCommand(getCommonCommands()...)
|
||||
|
||||
rootCmd.AddCommand(updateCmd)
|
||||
|
||||
@@ -11,20 +11,29 @@ import (
|
||||
var Version = "dev"
|
||||
|
||||
func init() {
|
||||
// Add flags
|
||||
runCmd.Flags().BoolP("daemon", "d", false, "Run in daemon mode")
|
||||
runCmd.Flags().Bool("daemon-child", false, "Internal flag for daemon child process")
|
||||
runCmd.Flags().Bool("session", false, "Session managed (like as a systemd unit)")
|
||||
runCmd.Flags().MarkHidden("daemon-child")
|
||||
|
||||
greeterCmd.AddCommand(greeterInstallCmd, greeterSyncCmd, greeterEnableCmd, greeterStatusCmd, greeterUninstallCmd)
|
||||
// Add subcommands to greeter
|
||||
greeterCmd.AddCommand(greeterInstallCmd, greeterSyncCmd, greeterEnableCmd, greeterStatusCmd)
|
||||
|
||||
// Add subcommands to setup
|
||||
setupCmd.AddCommand(setupBindsCmd, setupLayoutCmd, setupColorsCmd, setupAlttabCmd, setupOutputsCmd, setupCursorCmd, setupWindowrulesCmd)
|
||||
|
||||
// Add subcommands to plugins
|
||||
pluginsCmd.AddCommand(pluginsBrowseCmd, pluginsListCmd, pluginsInstallCmd, pluginsUninstallCmd, pluginsUpdateCmd)
|
||||
|
||||
// Add common commands to root
|
||||
rootCmd.AddCommand(getCommonCommands()...)
|
||||
|
||||
rootCmd.SetHelpTemplate(getHelpTemplate())
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Block root
|
||||
if os.Geteuid() == 0 {
|
||||
log.Fatal("This program should not be run as root. Exiting.")
|
||||
}
|
||||
|
||||
@@ -7,6 +7,14 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func findCommandPath(cmd string) (string, error) {
|
||||
path, err := exec.LookPath(cmd)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("command '%s' not found in PATH", cmd)
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func isArchPackageInstalled(packageName string) bool {
|
||||
cmd := exec.Command("pacman", "-Q", packageName)
|
||||
err := cmd.Run()
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
package clipboard
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/ext_data_control"
|
||||
@@ -14,37 +12,17 @@ import (
|
||||
)
|
||||
|
||||
func Copy(data []byte, mimeType string) error {
|
||||
return CopyReader(bytes.NewReader(data), mimeType, false, false)
|
||||
return CopyOpts(data, mimeType, false, false)
|
||||
}
|
||||
|
||||
func CopyOpts(data []byte, mimeType string, foreground, pasteOnce bool) error {
|
||||
if foreground {
|
||||
return copyServeWithWriter(func(writer io.Writer) error {
|
||||
total := 0
|
||||
for total < len(data) {
|
||||
n, err := writer.Write(data[total:])
|
||||
total += n
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if total != len(data) {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
return nil
|
||||
}, mimeType, pasteOnce)
|
||||
}
|
||||
return CopyReader(bytes.NewReader(data), mimeType, foreground, pasteOnce)
|
||||
}
|
||||
|
||||
func CopyReader(data io.Reader, mimeType string, foreground, pasteOnce bool) error {
|
||||
if !foreground {
|
||||
return copyFork(data, mimeType, pasteOnce)
|
||||
}
|
||||
return copyServeReader(data, mimeType, pasteOnce)
|
||||
return copyServe(data, mimeType, pasteOnce)
|
||||
}
|
||||
|
||||
func copyFork(data io.Reader, mimeType string, pasteOnce bool) error {
|
||||
func copyFork(data []byte, mimeType string, pasteOnce bool) error {
|
||||
args := []string{os.Args[0], "cl", "copy", "--foreground"}
|
||||
if pasteOnce {
|
||||
args = append(args, "--paste-once")
|
||||
@@ -52,15 +30,11 @@ func copyFork(data io.Reader, mimeType string, pasteOnce bool) error {
|
||||
args = append(args, "--type", mimeType)
|
||||
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Stdin = nil
|
||||
cmd.Stdout = nil
|
||||
cmd.Stderr = nil
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
||||
|
||||
if stdinSource, ok := data.(*os.File); ok {
|
||||
cmd.Stdin = stdinSource
|
||||
return cmd.Start()
|
||||
}
|
||||
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("stdin pipe: %w", err)
|
||||
@@ -70,66 +44,16 @@ func copyFork(data io.Reader, mimeType string, pasteOnce bool) error {
|
||||
return fmt.Errorf("start: %w", err)
|
||||
}
|
||||
|
||||
if _, err := io.Copy(stdin, data); err != nil {
|
||||
if _, err := stdin.Write(data); err != nil {
|
||||
stdin.Close()
|
||||
return fmt.Errorf("write stdin: %w", err)
|
||||
}
|
||||
if err := stdin.Close(); err != nil {
|
||||
return fmt.Errorf("close stdin: %w", err)
|
||||
}
|
||||
stdin.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyServeReader(data io.Reader, mimeType string, pasteOnce bool) error {
|
||||
cachedData, err := createClipboardCacheFile()
|
||||
if err != nil {
|
||||
return fmt.Errorf("create clipboard cache file: %w", err)
|
||||
}
|
||||
defer os.Remove(cachedData.Name())
|
||||
|
||||
if _, err := io.Copy(cachedData, data); err != nil {
|
||||
return fmt.Errorf("cache clipboard data: %w", err)
|
||||
}
|
||||
if err := cachedData.Close(); err != nil {
|
||||
return fmt.Errorf("close temp cache file: %w", err)
|
||||
}
|
||||
|
||||
return copyServeWithWriter(func(writer io.Writer) error {
|
||||
cachedFile, err := os.Open(cachedData.Name())
|
||||
if err != nil {
|
||||
return fmt.Errorf("open temp cache file: %w", err)
|
||||
}
|
||||
defer cachedFile.Close()
|
||||
|
||||
if _, err := io.Copy(writer, cachedFile); err != nil {
|
||||
return fmt.Errorf("write clipboard data: %w", err)
|
||||
}
|
||||
return nil
|
||||
}, mimeType, pasteOnce)
|
||||
}
|
||||
|
||||
func createClipboardCacheFile() (*os.File, error) {
|
||||
preferredDirs := []string{}
|
||||
|
||||
if cacheDir, err := os.UserCacheDir(); err == nil {
|
||||
preferredDirs = append(preferredDirs, filepath.Join(cacheDir, "dms", "clipboard"))
|
||||
}
|
||||
preferredDirs = append(preferredDirs, "/var/tmp/dms/clipboard")
|
||||
|
||||
for _, dir := range preferredDirs {
|
||||
if err := os.MkdirAll(dir, 0o700); err != nil {
|
||||
continue
|
||||
}
|
||||
cachedData, err := os.CreateTemp(dir, "dms-clipboard-*")
|
||||
if err == nil {
|
||||
return cachedData, nil
|
||||
}
|
||||
}
|
||||
return os.CreateTemp("", "dms-clipboard-*")
|
||||
}
|
||||
|
||||
func copyServeWithWriter(writeTo func(io.Writer) error, mimeType string, pasteOnce bool) error {
|
||||
func copyServe(data []byte, mimeType string, pasteOnce bool) error {
|
||||
display, err := wlclient.Connect("")
|
||||
if err != nil {
|
||||
return fmt.Errorf("wayland connect: %w", err)
|
||||
@@ -215,18 +139,12 @@ func copyServeWithWriter(writeTo func(io.Writer) error, mimeType string, pasteOn
|
||||
|
||||
cancelled := make(chan struct{})
|
||||
pasted := make(chan struct{}, 1)
|
||||
sendErr := make(chan error, 1)
|
||||
|
||||
source.SetSendHandler(func(e ext_data_control.ExtDataControlSourceV1SendEvent) {
|
||||
defer syscall.Close(e.Fd)
|
||||
file := os.NewFile(uintptr(e.Fd), "pipe")
|
||||
defer file.Close()
|
||||
if err := writeTo(file); err != nil {
|
||||
select {
|
||||
case sendErr <- err:
|
||||
default:
|
||||
}
|
||||
}
|
||||
file.Write(data)
|
||||
select {
|
||||
case pasted <- struct{}{}:
|
||||
default:
|
||||
@@ -247,8 +165,6 @@ func copyServeWithWriter(writeTo func(io.Writer) error, mimeType string, pasteOn
|
||||
select {
|
||||
case <-cancelled:
|
||||
return nil
|
||||
case err := <-sendErr:
|
||||
return err
|
||||
case <-pasted:
|
||||
if pasteOnce {
|
||||
return nil
|
||||
|
||||
@@ -440,10 +440,29 @@ func (a *ArchDistribution) installAURPackages(ctx context.Context, packages []st
|
||||
a.log(fmt.Sprintf("Installing AUR packages manually: %s", strings.Join(packages, ", ")))
|
||||
|
||||
hasNiri := false
|
||||
hasQuickshell := false
|
||||
for _, pkg := range packages {
|
||||
if pkg == "niri-git" {
|
||||
hasNiri = true
|
||||
}
|
||||
if pkg == "quickshell" || pkg == "quickshell-git" {
|
||||
hasQuickshell = true
|
||||
}
|
||||
}
|
||||
|
||||
// If quickshell is in the list, always reinstall google-breakpad first
|
||||
if hasQuickshell {
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseAURPackages,
|
||||
Progress: 0.63,
|
||||
Step: "Reinstalling google-breakpad for quickshell...",
|
||||
IsComplete: false,
|
||||
CommandInfo: "Reinstalling prerequisite AUR package for quickshell",
|
||||
}
|
||||
|
||||
if err := a.installSingleAURPackage(ctx, "google-breakpad", sudoPassword, progressChan, 0.63, 0.65); err != nil {
|
||||
return fmt.Errorf("failed to reinstall google-breakpad prerequisite for quickshell: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// If niri is in the list, install makepkg-git-lfs-proto first if not already installed
|
||||
@@ -597,16 +616,10 @@ func (a *ArchDistribution) installSingleAURPackage(ctx context.Context, pkg, sud
|
||||
return fmt.Errorf("failed to remove optdepends from .SRCINFO for %s: %w", pkg, err)
|
||||
}
|
||||
|
||||
srcinfoPath = filepath.Join(packageDir, ".SRCINFO")
|
||||
if pkg == "dms-shell-bin" {
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseAURPackages,
|
||||
Progress: startProgress + 0.35*(endProgress-startProgress),
|
||||
Step: fmt.Sprintf("Skipping dependency installation for %s (manually managed)...", pkg),
|
||||
IsComplete: false,
|
||||
LogOutput: fmt.Sprintf("Dependencies for %s are installed separately", pkg),
|
||||
}
|
||||
} else {
|
||||
// Skip dependency installation for dms-shell-git and dms-shell-bin
|
||||
// since we manually manage those dependencies
|
||||
if pkg != "dms-shell-git" && pkg != "dms-shell-bin" {
|
||||
// Pre-install dependencies from .SRCINFO
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseAURPackages,
|
||||
Progress: startProgress + 0.3*(endProgress-startProgress),
|
||||
@@ -615,19 +628,19 @@ func (a *ArchDistribution) installSingleAURPackage(ctx context.Context, pkg, sud
|
||||
CommandInfo: "Installing package dependencies and makedepends",
|
||||
}
|
||||
|
||||
// Install dependencies from .SRCINFO
|
||||
depFilter := ""
|
||||
if pkg == "dms-shell-git" {
|
||||
depFilter = ` | sed -E 's/[[:space:]]*(quickshell|dgop)[[:space:]]*/ /g' | tr -s ' '`
|
||||
}
|
||||
// Install dependencies and makedepends explicitly
|
||||
srcinfoPath = filepath.Join(packageDir, ".SRCINFO")
|
||||
|
||||
depsCmd := exec.CommandContext(ctx, "bash", "-c",
|
||||
fmt.Sprintf(`
|
||||
deps=$(grep "depends = " "%s" | grep -v "makedepends" | sed 's/.*depends = //' | tr '\n' ' ' %s | sed 's/[[:space:]]*$//')
|
||||
deps=$(grep "depends = " "%s" | grep -v "makedepends" | sed 's/.*depends = //' | tr '\n' ' ' | sed 's/[[:space:]]*$//')
|
||||
if [[ "%s" == *"quickshell"* ]]; then
|
||||
deps=$(echo "$deps" | sed 's/google-breakpad//g' | sed 's/ / /g' | sed 's/^ *//g' | sed 's/ *$//g')
|
||||
fi
|
||||
if [ ! -z "$deps" ] && [ "$deps" != " " ]; then
|
||||
echo '%s' | sudo -S pacman -S --needed --noconfirm $deps
|
||||
fi
|
||||
`, srcinfoPath, depFilter, sudoPassword))
|
||||
`, srcinfoPath, pkg, sudoPassword))
|
||||
|
||||
if err := a.runWithProgress(depsCmd, progressChan, PhaseAURPackages, startProgress+0.3*(endProgress-startProgress), startProgress+0.35*(endProgress-startProgress)); err != nil {
|
||||
return fmt.Errorf("FAILED to install runtime dependencies for %s: %w", pkg, err)
|
||||
@@ -644,6 +657,14 @@ func (a *ArchDistribution) installSingleAURPackage(ctx context.Context, pkg, sud
|
||||
if err := a.runWithProgress(makedepsCmd, progressChan, PhaseAURPackages, startProgress+0.35*(endProgress-startProgress), startProgress+0.4*(endProgress-startProgress)); err != nil {
|
||||
return fmt.Errorf("FAILED to install make dependencies for %s: %w", pkg, err)
|
||||
}
|
||||
} else {
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseAURPackages,
|
||||
Progress: startProgress + 0.35*(endProgress-startProgress),
|
||||
Step: fmt.Sprintf("Skipping dependency installation for %s (manually managed)...", pkg),
|
||||
IsComplete: false,
|
||||
LogOutput: fmt.Sprintf("Dependencies for %s are installed separately", pkg),
|
||||
}
|
||||
}
|
||||
|
||||
progressChan <- InstallProgressMsg{
|
||||
@@ -656,7 +677,7 @@ func (a *ArchDistribution) installSingleAURPackage(ctx context.Context, pkg, sud
|
||||
|
||||
buildCmd := exec.CommandContext(ctx, "makepkg", "--noconfirm")
|
||||
buildCmd.Dir = packageDir
|
||||
buildCmd.Env = append(os.Environ(), "PKGEXT=.pkg.tar")
|
||||
buildCmd.Env = append(os.Environ(), "PKGEXT=.pkg.tar") // Disable compression for speed
|
||||
|
||||
if err := a.runWithProgress(buildCmd, progressChan, PhaseAURPackages, startProgress+0.4*(endProgress-startProgress), startProgress+0.7*(endProgress-startProgress)); err != nil {
|
||||
return fmt.Errorf("failed to build %s: %w", pkg, err)
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
# AppArmor profile for dms-greeter
|
||||
#
|
||||
# Managed by DMS — regenerated on every `dms greeter install` / `dms greeter sync`.
|
||||
# Manual edits will be overwritten on next sync.
|
||||
#
|
||||
# Mode: complain (denials are logged, nothing is blocked)
|
||||
# To switch to enforce after validating with `aa-logprof`:
|
||||
# sudo aa-enforce /etc/apparmor.d/usr.bin.dms-greeter
|
||||
#
|
||||
#include <tunables/global>
|
||||
|
||||
profile dms-greeter /usr/bin/dms-greeter flags=(complain) {
|
||||
#include <abstractions/base>
|
||||
#include <abstractions/bash>
|
||||
|
||||
# The launcher script itself
|
||||
/usr/bin/dms-greeter r,
|
||||
|
||||
# Cache directory — created by dms greeter sync/enable with greeter:greeter ownership
|
||||
/var/cache/dms-greeter/ rw,
|
||||
/var/cache/dms-greeter/** rwlk,
|
||||
|
||||
# DMS config — packaged path
|
||||
/usr/share/quickshell/dms-greeter/ r,
|
||||
/usr/share/quickshell/dms-greeter/** r,
|
||||
/usr/share/quickshell/ r,
|
||||
/usr/share/quickshell/** r,
|
||||
|
||||
# DMS config — system and user overrides
|
||||
/etc/dms/ r,
|
||||
/etc/dms/** r,
|
||||
/usr/share/dms/ r,
|
||||
/usr/share/dms/** r,
|
||||
/home/*/.config/quickshell/ r,
|
||||
/home/*/.config/quickshell/** r,
|
||||
/root/.config/quickshell/ r,
|
||||
/root/.config/quickshell/** r,
|
||||
|
||||
# greetd / PAM — read-only for session setup
|
||||
/etc/greetd/ r,
|
||||
/etc/greetd/** r,
|
||||
/etc/pam.d/ r,
|
||||
/etc/pam.d/** r,
|
||||
/usr/lib/pam.d/ r,
|
||||
/usr/lib/pam.d/** r,
|
||||
|
||||
# Compositor binaries — run unconfined so each compositor uses its own profile
|
||||
/usr/bin/niri Ux,
|
||||
/usr/bin/hyprland Ux,
|
||||
/usr/bin/Hyprland Ux,
|
||||
/usr/bin/sway Ux,
|
||||
/usr/bin/labwc Ux,
|
||||
/usr/bin/scroll Ux,
|
||||
/usr/bin/miracle-wm Ux,
|
||||
/usr/bin/mango Ux,
|
||||
|
||||
# Quickshell — run unconfined (has its own compositor profile on some distros)
|
||||
/usr/bin/qs Ux,
|
||||
/usr/bin/quickshell Ux,
|
||||
|
||||
# Wayland / XDG runtime (pipewire, wireplumber, wayland socket)
|
||||
/run/user/[0-9]*/ rw,
|
||||
/run/user/[0-9]*/** rw,
|
||||
|
||||
# DRM / GPU devices (required for Wayland compositor startup)
|
||||
/dev/dri/ r,
|
||||
/dev/dri/* rw,
|
||||
/dev/udmabuf rw,
|
||||
|
||||
# Input devices
|
||||
/dev/input/ r,
|
||||
/dev/input/* r,
|
||||
|
||||
# Systemd journal / logging
|
||||
/run/systemd/journal/socket rw,
|
||||
/dev/log rw,
|
||||
|
||||
# Shell helper binaries invoked by the launcher script
|
||||
/usr/bin/env ix,
|
||||
/usr/bin/mkdir ix,
|
||||
/usr/bin/cat ix,
|
||||
/usr/bin/grep ix,
|
||||
/usr/bin/dirname ix,
|
||||
/usr/bin/basename ix,
|
||||
/usr/bin/command ix,
|
||||
/bin/env ix,
|
||||
/bin/mkdir ix,
|
||||
|
||||
# Signal management (compositor lifecycle)
|
||||
signal (send, receive) set=("term", "int", "hup", "kill"),
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -71,7 +71,6 @@ var templateRegistry = []TemplateDef{
|
||||
{ID: "kcolorscheme", ConfigFile: "kcolorscheme.toml", RunUnconditionally: true},
|
||||
{ID: "vscode", Kind: TemplateKindVSCode},
|
||||
{ID: "emacs", Commands: []string{"emacs"}, ConfigFile: "emacs.toml", Kind: TemplateKindEmacs},
|
||||
{ID: "zed", Commands: []string{"zed"}, ConfigFile: "zed.toml"},
|
||||
}
|
||||
|
||||
func (c *ColorMode) GTKTheme() string {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -29,13 +28,7 @@ func TestDetectResult_HasNetworkdField(t *testing.T) {
|
||||
|
||||
func TestDetectNetworkStack_Integration(t *testing.T) {
|
||||
result, err := DetectNetworkStack()
|
||||
|
||||
if err != nil && strings.Contains(err.Error(), "connect system bus") {
|
||||
t.Skipf("system D-Bus unavailable: %v", err)
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
if assert.NotNil(t, result) {
|
||||
assert.NotEmpty(t, result.ChosenReason)
|
||||
}
|
||||
assert.NotNil(t, result)
|
||||
assert.NotEmpty(t, result.ChosenReason)
|
||||
}
|
||||
|
||||
@@ -102,19 +102,6 @@ if [[ ! -d "distro/debian" ]]; then
|
||||
echo "Error: Run this script from the repository root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Retry wrapper for osc commands (mitigates SSL "Connection reset by peer" from api.opensuse.org)
|
||||
osc_retry() {
|
||||
local max=3 attempt=1
|
||||
while true; do
|
||||
if osc "$@"; then return 0; fi
|
||||
((attempt >= max)) && return 1
|
||||
echo "Retrying in $((5*attempt))s (attempt $attempt/$max)..."
|
||||
sleep $((5*attempt))
|
||||
((attempt++))
|
||||
done
|
||||
}
|
||||
|
||||
# Parameters:
|
||||
# $1 = PROJECT
|
||||
# $2 = PACKAGE
|
||||
@@ -322,23 +309,8 @@ mkdir -p "$OBS_BASE"
|
||||
if [[ ! -d "$OBS_BASE/$OBS_PROJECT/$PACKAGE" ]]; then
|
||||
echo "Checking out $OBS_PROJECT/$PACKAGE..."
|
||||
cd "$OBS_BASE"
|
||||
CHECKOUT_OK=false
|
||||
for attempt in 1 2 3; do
|
||||
if osc co "$OBS_PROJECT/$PACKAGE"; then
|
||||
CHECKOUT_OK=true
|
||||
break
|
||||
fi
|
||||
if [[ $attempt -lt 3 ]]; then
|
||||
echo "Checkout failed (attempt $attempt/3). Removing partial copy and retrying in $((5*attempt))s..."
|
||||
rm -rf "${OBS_BASE:?}/${OBS_PROJECT:?}"
|
||||
sleep $((5*attempt))
|
||||
fi
|
||||
done
|
||||
osc co "$OBS_PROJECT/$PACKAGE"
|
||||
cd "$REPO_ROOT"
|
||||
if [[ "$CHECKOUT_OK" != "true" ]]; then
|
||||
echo "Error: Checkout failed after 3 attempts"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
WORK_DIR="$OBS_BASE/$OBS_PROJECT/$PACKAGE"
|
||||
@@ -1092,7 +1064,7 @@ fi
|
||||
|
||||
# Update working copy to latest revision (without expanding service files to avoid revision conflicts)
|
||||
echo "==> Updating working copy"
|
||||
if ! osc_retry up 2>/dev/null; then
|
||||
if ! osc up 2>/dev/null; then
|
||||
echo "Error: Failed to update working copy"
|
||||
exit 1
|
||||
fi
|
||||
@@ -1173,7 +1145,7 @@ if ! osc status 2>/dev/null | grep -qE '^[MAD]|^[?]'; then
|
||||
else
|
||||
echo "==> Committing to OBS"
|
||||
set +e
|
||||
osc_retry commit --skip-local-service-run -m "$MESSAGE" 2>&1 | grep -v "Git SCM package" | grep -v "apiurl\|project\|_ObsPrj\|_manifest\|git-obs"
|
||||
osc commit --skip-local-service-run -m "$MESSAGE" 2>&1 | grep -v "Git SCM package" | grep -v "apiurl\|project\|_ObsPrj\|_manifest\|git-obs"
|
||||
COMMIT_EXIT=${PIPESTATUS[0]}
|
||||
set -e
|
||||
if [[ $COMMIT_EXIT -ne 0 ]]; then
|
||||
|
||||
118
flake.nix
118
flake.nix
@@ -72,82 +72,76 @@
|
||||
"${cleanVersion}${dateSuffix}${revSuffix}";
|
||||
in
|
||||
{
|
||||
dms-shell = pkgs.lib.makeOverridable (
|
||||
dms-shell = pkgs.buildGoModule (
|
||||
let
|
||||
rootSrc = ./.;
|
||||
in
|
||||
{
|
||||
extraQtPackages ? [ ],
|
||||
}:
|
||||
pkgs.buildGoModule (
|
||||
let
|
||||
rootSrc = ./.;
|
||||
qtPackages = (qmlPkgs pkgs) ++ extraQtPackages;
|
||||
in
|
||||
{
|
||||
inherit version;
|
||||
pname = "dms-shell";
|
||||
src = ./core;
|
||||
vendorHash = "sha256-dEk7IOd6aQwaxZruxQclN7TGMyb8EJOl6NBWRsoZ9HQ=";
|
||||
inherit version;
|
||||
pname = "dms-shell";
|
||||
src = ./core;
|
||||
vendorHash = "sha256-dEk7IOd6aQwaxZruxQclN7TGMyb8EJOl6NBWRsoZ9HQ=";
|
||||
|
||||
subPackages = [ "cmd/dms" ];
|
||||
subPackages = [ "cmd/dms" ];
|
||||
|
||||
ldflags = [
|
||||
"-s"
|
||||
"-w"
|
||||
"-X 'main.Version=${version}'"
|
||||
];
|
||||
ldflags = [
|
||||
"-s"
|
||||
"-w"
|
||||
"-X 'main.Version=${version}'"
|
||||
];
|
||||
|
||||
nativeBuildInputs = with pkgs; [
|
||||
installShellFiles
|
||||
makeWrapper
|
||||
];
|
||||
nativeBuildInputs = with pkgs; [
|
||||
installShellFiles
|
||||
makeWrapper
|
||||
];
|
||||
|
||||
postInstall = ''
|
||||
mkdir -p $out/share/quickshell/dms
|
||||
cp -r ${rootSrc}/quickshell/. $out/share/quickshell/dms/
|
||||
postInstall = ''
|
||||
mkdir -p $out/share/quickshell/dms
|
||||
cp -r ${rootSrc}/quickshell/. $out/share/quickshell/dms/
|
||||
|
||||
chmod u+w $out/share/quickshell/dms/VERSION
|
||||
echo "${version}" > $out/share/quickshell/dms/VERSION
|
||||
chmod u+w $out/share/quickshell/dms/VERSION
|
||||
echo "${version}" > $out/share/quickshell/dms/VERSION
|
||||
|
||||
# Install desktop file and icon
|
||||
install -D ${rootSrc}/assets/dms-open.desktop \
|
||||
$out/share/applications/dms-open.desktop
|
||||
install -D ${rootSrc}/core/assets/danklogo.svg \
|
||||
$out/share/hicolor/scalable/apps/danklogo.svg
|
||||
# Install desktop file and icon
|
||||
install -D ${rootSrc}/assets/dms-open.desktop \
|
||||
$out/share/applications/dms-open.desktop
|
||||
install -D ${rootSrc}/core/assets/danklogo.svg \
|
||||
$out/share/hicolor/scalable/apps/danklogo.svg
|
||||
|
||||
wrapProgram $out/bin/dms \
|
||||
--add-flags "-c $out/share/quickshell/dms" \
|
||||
--prefix "NIXPKGS_QT6_QML_IMPORT_PATH" ":" "${mkQmlImportPath pkgs qtPackages}" \
|
||||
--prefix "QT_PLUGIN_PATH" ":" "${mkQtPluginPath pkgs qtPackages}"
|
||||
wrapProgram $out/bin/dms \
|
||||
--add-flags "-c $out/share/quickshell/dms" \
|
||||
--prefix "NIXPKGS_QT6_QML_IMPORT_PATH" ":" "${mkQmlImportPath pkgs (qmlPkgs pkgs)}" \
|
||||
--prefix "QT_PLUGIN_PATH" ":" "${mkQtPluginPath pkgs (qmlPkgs pkgs)}"
|
||||
|
||||
install -Dm644 ${rootSrc}/assets/systemd/dms.service \
|
||||
$out/lib/systemd/user/dms.service
|
||||
install -Dm644 ${rootSrc}/assets/systemd/dms.service \
|
||||
$out/lib/systemd/user/dms.service
|
||||
|
||||
substituteInPlace $out/lib/systemd/user/dms.service \
|
||||
--replace-fail /usr/bin/dms $out/bin/dms \
|
||||
--replace-fail /usr/bin/pkill ${pkgs.procps}/bin/pkill
|
||||
substituteInPlace $out/lib/systemd/user/dms.service \
|
||||
--replace-fail /usr/bin/dms $out/bin/dms \
|
||||
--replace-fail /usr/bin/pkill ${pkgs.procps}/bin/pkill
|
||||
|
||||
substituteInPlace $out/share/quickshell/dms/Modules/Greetd/assets/dms-greeter \
|
||||
--replace-fail /bin/bash ${pkgs.bashInteractive}/bin/bash
|
||||
substituteInPlace $out/share/quickshell/dms/Modules/Greetd/assets/dms-greeter \
|
||||
--replace-fail /bin/bash ${pkgs.bashInteractive}/bin/bash
|
||||
|
||||
substituteInPlace $out/share/quickshell/dms/assets/pam/fprint \
|
||||
--replace-fail pam_fprintd.so ${pkgs.fprintd}/lib/security/pam_fprintd.so
|
||||
substituteInPlace $out/share/quickshell/dms/assets/pam/fprint \
|
||||
--replace-fail pam_fprintd.so ${pkgs.fprintd}/lib/security/pam_fprintd.so
|
||||
|
||||
installShellCompletion --cmd dms \
|
||||
--bash <($out/bin/dms completion bash) \
|
||||
--fish <($out/bin/dms completion fish) \
|
||||
--zsh <($out/bin/dms completion zsh)
|
||||
'';
|
||||
installShellCompletion --cmd dms \
|
||||
--bash <($out/bin/dms completion bash) \
|
||||
--fish <($out/bin/dms completion fish) \
|
||||
--zsh <($out/bin/dms completion zsh)
|
||||
'';
|
||||
|
||||
meta = {
|
||||
description = "Desktop shell for wayland compositors built with Quickshell & GO";
|
||||
homepage = "https://danklinux.com";
|
||||
changelog = "https://github.com/AvengeMedia/DankMaterialShell/releases/tag/v${version}";
|
||||
license = pkgs.lib.licenses.mit;
|
||||
mainProgram = "dms";
|
||||
platforms = pkgs.lib.platforms.linux;
|
||||
};
|
||||
}
|
||||
)
|
||||
) { };
|
||||
meta = {
|
||||
description = "Desktop shell for wayland compositors built with Quickshell & GO";
|
||||
homepage = "https://danklinux.com";
|
||||
changelog = "https://github.com/AvengeMedia/DankMaterialShell/releases/tag/v${version}";
|
||||
license = pkgs.lib.licenses.mit;
|
||||
mainProgram = "dms";
|
||||
platforms = pkgs.lib.platforms.linux;
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
quickshell = quickshell.packages.${system}.default;
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ qs -v -p shell.qml # Verbose debugging
|
||||
|
||||
# Code formatting and linting
|
||||
qmlfmt -t 4 -i 4 -b 250 -w /path/to/file.qml # Format QML (don't use qmlformat)
|
||||
make -C .. lint-qml # From quickshell/, call the repo-root lint target; requires the generated .qmlls.ini VFS from `qs -p .`
|
||||
qmllint **/*.qml # Lint all QML files
|
||||
./qmlformat-all.sh # Format all QML files
|
||||
```
|
||||
|
||||
@@ -783,7 +783,7 @@ When modifying the shell:
|
||||
|
||||
**QML Frontend:**
|
||||
1. **Test changes**: `qs -p .` (automatic reload on file changes)
|
||||
2. **Code quality**: Run `./qmlformat-all.sh` or `qmlformat -i **/*.qml`, then from repo root run `make lint-qml` after Quickshell has generated the local `.qmlls.ini` VFS with `qs -p .`
|
||||
2. **Code quality**: Run `./qmlformat-all.sh` or `qmlformat -i **/*.qml` and `qmllint **/*.qml`
|
||||
3. **Performance**: Ensure animations remain smooth (60 FPS target)
|
||||
4. **Theming**: Use `Theme.propertyName` for Material Design 3 consistency
|
||||
|
||||
|
||||
174
quickshell/Common/AnimVariants.qml
Normal file
174
quickshell/Common/AnimVariants.qml
Normal file
@@ -0,0 +1,174 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
|
||||
// AnimVariants — Central tuning for animation and Motion Effects variants
|
||||
// (Material/Fluent/Dynamic) (Standard/Directional/Depth)
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property list<real> variantEnterCurve: {
|
||||
if (typeof SettingsData === "undefined")
|
||||
return Anims.expressiveDefaultSpatial;
|
||||
switch (SettingsData.animationVariant) {
|
||||
case 1:
|
||||
return Anims.standardDecel;
|
||||
case 2:
|
||||
return Anims.expressiveFastSpatial;
|
||||
default:
|
||||
return Anims.expressiveDefaultSpatial;
|
||||
}
|
||||
}
|
||||
|
||||
readonly property list<real> variantExitCurve: {
|
||||
if (typeof SettingsData === "undefined")
|
||||
return Anims.emphasized;
|
||||
switch (SettingsData.animationVariant) {
|
||||
case 1:
|
||||
return Anims.standard;
|
||||
case 2:
|
||||
return Anims.emphasized;
|
||||
default:
|
||||
return Anims.emphasized;
|
||||
}
|
||||
}
|
||||
|
||||
// Modal-specific entry curve
|
||||
readonly property list<real> variantModalEnterCurve: {
|
||||
if (typeof SettingsData === "undefined")
|
||||
return Anims.expressiveDefaultSpatial;
|
||||
if (isDirectionalEffect) {
|
||||
if (SettingsData.animationVariant === 1)
|
||||
return Anims.standardDecel;
|
||||
if (SettingsData.animationVariant === 2)
|
||||
return Anims.expressiveFastSpatial;
|
||||
}
|
||||
return variantEnterCurve;
|
||||
}
|
||||
|
||||
readonly property list<real> variantModalExitCurve: {
|
||||
if (typeof SettingsData === "undefined")
|
||||
return Anims.emphasized;
|
||||
if (isDirectionalEffect) {
|
||||
if (SettingsData.animationVariant === 1)
|
||||
return Anims.emphasizedAccel;
|
||||
if (SettingsData.animationVariant === 2)
|
||||
return Anims.emphasizedAccel;
|
||||
}
|
||||
return variantExitCurve;
|
||||
}
|
||||
|
||||
// Popout-specific entry curve
|
||||
readonly property list<real> variantPopoutEnterCurve: {
|
||||
if (typeof SettingsData === "undefined")
|
||||
return Anims.expressiveDefaultSpatial;
|
||||
if (isDirectionalEffect) {
|
||||
if (SettingsData.animationVariant === 1)
|
||||
return Anims.standardDecel;
|
||||
if (SettingsData.animationVariant === 2)
|
||||
return Anims.expressiveFastSpatial;
|
||||
return Anims.standardDecel;
|
||||
}
|
||||
return variantEnterCurve;
|
||||
}
|
||||
|
||||
readonly property list<real> variantPopoutExitCurve: {
|
||||
if (typeof SettingsData === "undefined")
|
||||
return Anims.emphasized;
|
||||
if (isDirectionalEffect) {
|
||||
if (SettingsData.animationVariant === 1)
|
||||
return Anims.emphasizedAccel;
|
||||
if (SettingsData.animationVariant === 2)
|
||||
return Anims.emphasizedAccel;
|
||||
}
|
||||
return variantExitCurve;
|
||||
}
|
||||
|
||||
readonly property real variantEnterDurationFactor: {
|
||||
if (typeof SettingsData === "undefined")
|
||||
return 1.0;
|
||||
switch (SettingsData.animationVariant) {
|
||||
case 1:
|
||||
return 0.9;
|
||||
case 2:
|
||||
return 1.08;
|
||||
default:
|
||||
return 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
readonly property real variantExitDurationFactor: {
|
||||
if (typeof SettingsData === "undefined")
|
||||
return 1.0;
|
||||
switch (SettingsData.animationVariant) {
|
||||
case 1:
|
||||
return 0.85;
|
||||
case 2:
|
||||
return 0.92;
|
||||
default:
|
||||
return 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
// Fluent: opacity at ~55% of duration; Material/Dynamic: 1:1 with position
|
||||
readonly property real variantOpacityDurationScale: {
|
||||
if (typeof SettingsData === "undefined")
|
||||
return 1.0;
|
||||
return SettingsData.animationVariant === 1 ? 0.55 : 1.0;
|
||||
}
|
||||
|
||||
function variantDuration(baseDuration, entering) {
|
||||
const factor = entering ? variantEnterDurationFactor : variantExitDurationFactor;
|
||||
return Math.max(0, Math.round(baseDuration * factor));
|
||||
}
|
||||
|
||||
function variantExitCleanupPadding() {
|
||||
if (typeof SettingsData === "undefined")
|
||||
return 50;
|
||||
switch (SettingsData.motionEffect) {
|
||||
case 1:
|
||||
return 8;
|
||||
case 2:
|
||||
return 24;
|
||||
default:
|
||||
return 50;
|
||||
}
|
||||
}
|
||||
|
||||
function variantCloseInterval(baseDuration) {
|
||||
return variantDuration(baseDuration, false) + variantExitCleanupPadding();
|
||||
}
|
||||
|
||||
readonly property bool isDirectionalEffect: typeof SettingsData !== "undefined" && SettingsData.motionEffect === 1
|
||||
readonly property bool isDepthEffect: typeof SettingsData !== "undefined" && SettingsData.motionEffect === 2
|
||||
|
||||
readonly property real effectScaleCollapsed: {
|
||||
if (typeof SettingsData === "undefined")
|
||||
return 0.96;
|
||||
switch (SettingsData.motionEffect) {
|
||||
case 1:
|
||||
return 1.0;
|
||||
case 2:
|
||||
return 0.88;
|
||||
default:
|
||||
return 0.96;
|
||||
}
|
||||
}
|
||||
|
||||
readonly property real effectAnimOffset: {
|
||||
if (typeof SettingsData === "undefined")
|
||||
return 16;
|
||||
switch (SettingsData.motionEffect) {
|
||||
case 1:
|
||||
return 144;
|
||||
case 2:
|
||||
return 56;
|
||||
default:
|
||||
return 16;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,4 +22,9 @@ Singleton {
|
||||
readonly property var standard: [0.20, 0.00, 0.00, 1.00, 1.00, 1.00]
|
||||
readonly property var standardDecel: [0.00, 0.00, 0.00, 1.00, 1.00, 1.00]
|
||||
readonly property var standardAccel: [0.30, 0.00, 1.00, 1.00, 1.00, 1.00]
|
||||
|
||||
// Used by AnimVariants for variant/effect logic
|
||||
readonly property var expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1, 1, 1]
|
||||
readonly property var expressiveFastSpatial: [0.34, 1.5, 0.2, 1.0, 1.0, 1.0]
|
||||
readonly property var expressiveEffects: [0.34, 0.8, 0.34, 1, 1, 1]
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ Singleton {
|
||||
property bool _hasUnsavedChanges: false
|
||||
property var _loadedSessionSnapshot: null
|
||||
readonly property var _hooks: ({
|
||||
"updateLocale": updateLocale
|
||||
})
|
||||
"updateLocale": updateLocale
|
||||
})
|
||||
readonly property string _stateUrl: StandardPaths.writableLocation(StandardPaths.GenericStateLocation)
|
||||
readonly property string _stateDir: Paths.strip(_stateUrl)
|
||||
|
||||
@@ -1245,7 +1245,7 @@ Singleton {
|
||||
id: greeterSessionFile
|
||||
|
||||
path: {
|
||||
const greetCfgDir = Quickshell.env("DMS_GREET_CFG_DIR") || "/var/cache/dms-greeter";
|
||||
const greetCfgDir = Quickshell.env("DMS_GREET_CFG_DIR") || "/etc/greetd/.dms";
|
||||
return greetCfgDir + "/session.json";
|
||||
}
|
||||
preload: isGreeterMode
|
||||
|
||||
@@ -37,6 +37,18 @@ Singleton {
|
||||
Custom
|
||||
}
|
||||
|
||||
enum AnimationVariant {
|
||||
Material,
|
||||
Fluent,
|
||||
Dynamic
|
||||
}
|
||||
|
||||
enum AnimationEffect {
|
||||
Standard, // 0 — M3: scale-in, rises from below
|
||||
Directional, // 1 — pure large slide, no scale
|
||||
Depth // 2 — medium slide with deep depth scale pop
|
||||
}
|
||||
|
||||
enum SuspendBehavior {
|
||||
Suspend,
|
||||
Hibernate,
|
||||
@@ -166,6 +178,12 @@ Singleton {
|
||||
property int modalCustomAnimationDuration: 150
|
||||
property bool enableRippleEffects: true
|
||||
onEnableRippleEffectsChanged: saveSettings()
|
||||
property int animationVariant: SettingsData.AnimationVariant.Material
|
||||
onAnimationVariantChanged: saveSettings()
|
||||
property int motionEffect: SettingsData.AnimationEffect.Standard
|
||||
onMotionEffectChanged: saveSettings()
|
||||
property int directionalAnimationMode: 0
|
||||
onDirectionalAnimationModeChanged: saveSettings()
|
||||
property bool m3ElevationEnabled: true
|
||||
onM3ElevationEnabledChanged: saveSettings()
|
||||
property int m3ElevationIntensity: 12
|
||||
@@ -280,7 +298,6 @@ Singleton {
|
||||
property bool showOccupiedWorkspacesOnly: false
|
||||
property bool reverseScrolling: false
|
||||
property bool dwlShowAllTags: false
|
||||
property bool workspaceActiveAppHighlightEnabled: false
|
||||
property string workspaceColorMode: "default"
|
||||
property string workspaceOccupiedColorMode: "none"
|
||||
property string workspaceUnfocusedColorMode: "default"
|
||||
@@ -314,17 +331,6 @@ Singleton {
|
||||
property string centeringMode: "index"
|
||||
property string clockDateFormat: ""
|
||||
property string lockDateFormat: ""
|
||||
property bool greeterRememberLastSession: true
|
||||
property bool greeterRememberLastUser: true
|
||||
property bool greeterEnableFprint: false
|
||||
property bool greeterEnableU2f: false
|
||||
property string greeterWallpaperPath: ""
|
||||
property bool greeterUse24HourClock: true
|
||||
property bool greeterShowSeconds: false
|
||||
property bool greeterPadHours12Hour: false
|
||||
property string greeterLockDateFormat: ""
|
||||
property string greeterFontFamily: ""
|
||||
property string greeterWallpaperFillMode: ""
|
||||
property int mediaSize: 1
|
||||
|
||||
property string appLauncherViewMode: "list"
|
||||
@@ -468,20 +474,18 @@ Singleton {
|
||||
property bool matugenTemplateGhostty: true
|
||||
property bool matugenTemplateKitty: true
|
||||
property bool matugenTemplateFoot: true
|
||||
property bool matugenTemplateNeovim: false
|
||||
property bool matugenTemplateNeovim: true
|
||||
property bool matugenTemplateAlacritty: true
|
||||
property bool matugenTemplateWezterm: true
|
||||
property bool matugenTemplateDgop: true
|
||||
property bool matugenTemplateKcolorscheme: true
|
||||
property bool matugenTemplateVscode: true
|
||||
property bool matugenTemplateEmacs: true
|
||||
property bool matugenTemplateZed: true
|
||||
|
||||
property bool showDock: false
|
||||
property bool dockAutoHide: false
|
||||
property bool dockSmartAutoHide: false
|
||||
property bool dockGroupByApp: false
|
||||
property bool dockRestoreSpecialWorkspaceOnClick: false
|
||||
property bool dockOpenOnOverview: false
|
||||
property int dockPosition: SettingsData.Position.Bottom
|
||||
property real dockSpacing: 4
|
||||
@@ -552,7 +556,6 @@ Singleton {
|
||||
property bool notificationHistorySaveNormal: true
|
||||
property bool notificationHistorySaveCritical: true
|
||||
property var notificationRules: []
|
||||
property bool notificationFocusedMonitor: false
|
||||
|
||||
property bool osdAlwaysShowValue: false
|
||||
property int osdPosition: SettingsData.Position.BottomCenter
|
||||
@@ -1016,20 +1019,13 @@ Singleton {
|
||||
signal widgetDataChanged
|
||||
signal workspaceIconsUpdated
|
||||
|
||||
function refreshAuthAvailability() {
|
||||
if (isGreeterMode)
|
||||
return;
|
||||
Processes.settingsRoot = root;
|
||||
Processes.detectFprintd();
|
||||
Processes.detectU2f();
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (!isGreeterMode) {
|
||||
Processes.settingsRoot = root;
|
||||
loadSettings();
|
||||
initializeListModels();
|
||||
refreshAuthAvailability();
|
||||
Processes.detectFprintd();
|
||||
Processes.detectU2f();
|
||||
Processes.checkPluginSettings();
|
||||
}
|
||||
}
|
||||
@@ -1177,7 +1173,7 @@ Singleton {
|
||||
"updateCompositorLayout": updateCompositorLayout,
|
||||
"applyStoredIconTheme": applyStoredIconTheme,
|
||||
"updateBarConfigs": updateBarConfigs,
|
||||
"updateCompositorCursor": updateCompositorCursor
|
||||
"updateCompositorCursor": updateCompositorCursor,
|
||||
})
|
||||
|
||||
function set(key, value) {
|
||||
@@ -1269,47 +1265,10 @@ Singleton {
|
||||
return JSON.stringify(Store.toJson(root), null, 2);
|
||||
}
|
||||
|
||||
function _resetPluginSettings() {
|
||||
_pluginParseError = false;
|
||||
pluginSettings = {};
|
||||
}
|
||||
|
||||
function _pluginSettingsErrorCode(error) {
|
||||
if (typeof error === "number")
|
||||
return error;
|
||||
if (error && typeof error === "object") {
|
||||
if (typeof error.code === "number")
|
||||
return error.code;
|
||||
if (typeof error.errno === "number")
|
||||
return error.errno;
|
||||
}
|
||||
|
||||
const msg = String(error || "").trim();
|
||||
if (/^\d+$/.test(msg))
|
||||
return Number(msg);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
function _isMissingPluginSettingsError(error) {
|
||||
if (_pluginSettingsErrorCode(error) === 2)
|
||||
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;
|
||||
}
|
||||
|
||||
function loadPluginSettings() {
|
||||
try {
|
||||
parsePluginSettings(pluginSettingsFile.text());
|
||||
} catch (e) {
|
||||
const msg = e.message || String(e);
|
||||
if (!_isMissingPluginSettingsError(e))
|
||||
console.warn("SettingsData: Failed to load plugin_settings.json. Error:", msg);
|
||||
_resetPluginSettings();
|
||||
}
|
||||
_pluginSettingsLoading = true;
|
||||
parsePluginSettings(pluginSettingsFile.text());
|
||||
_pluginSettingsLoading = false;
|
||||
}
|
||||
|
||||
function parsePluginSettings(content) {
|
||||
@@ -2745,7 +2704,6 @@ Singleton {
|
||||
blockLoading: true
|
||||
blockWrites: true
|
||||
atomicWrites: true
|
||||
printErrors: false
|
||||
watchChanges: !isGreeterMode
|
||||
onLoaded: {
|
||||
if (!isGreeterMode) {
|
||||
@@ -2754,10 +2712,7 @@ Singleton {
|
||||
}
|
||||
onLoadFailed: error => {
|
||||
if (!isGreeterMode) {
|
||||
const msg = String(error || "");
|
||||
if (!_isMissingPluginSettingsError(error))
|
||||
console.warn("SettingsData: Failed to load plugin_settings.json. Error:", msg);
|
||||
_resetPluginSettings();
|
||||
pluginSettings = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -960,6 +960,24 @@ Singleton {
|
||||
"expressiveEffects": [0.34, 0.8, 0.34, 1, 1, 1]
|
||||
}
|
||||
|
||||
// Delegates to AnimVariants.qml for curves, timing, scale, and offsets.
|
||||
readonly property list<real> variantEnterCurve: AnimVariants.variantEnterCurve
|
||||
readonly property list<real> variantExitCurve: AnimVariants.variantExitCurve
|
||||
readonly property list<real> variantModalEnterCurve: AnimVariants.variantModalEnterCurve
|
||||
readonly property list<real> variantModalExitCurve: AnimVariants.variantModalExitCurve
|
||||
readonly property list<real> variantPopoutEnterCurve: AnimVariants.variantPopoutEnterCurve
|
||||
readonly property list<real> variantPopoutExitCurve: AnimVariants.variantPopoutExitCurve
|
||||
readonly property real variantEnterDurationFactor: AnimVariants.variantEnterDurationFactor
|
||||
readonly property real variantExitDurationFactor: AnimVariants.variantExitDurationFactor
|
||||
readonly property real variantOpacityDurationScale: AnimVariants.variantOpacityDurationScale
|
||||
readonly property bool isDirectionalEffect: AnimVariants.isDirectionalEffect
|
||||
readonly property bool isDepthEffect: AnimVariants.isDepthEffect
|
||||
readonly property real effectScaleCollapsed: AnimVariants.effectScaleCollapsed
|
||||
readonly property real effectAnimOffset: AnimVariants.effectAnimOffset
|
||||
function variantDuration(baseDuration, entering) { return AnimVariants.variantDuration(baseDuration, entering); }
|
||||
function variantExitCleanupPadding() { return AnimVariants.variantExitCleanupPadding(); }
|
||||
function variantCloseInterval(baseDuration) { return AnimVariants.variantCloseInterval(baseDuration); }
|
||||
|
||||
readonly property var animationPresetDurations: {
|
||||
"none": 0,
|
||||
"short": 250,
|
||||
@@ -1084,7 +1102,7 @@ Singleton {
|
||||
|
||||
property string fontFamily: {
|
||||
if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") {
|
||||
return GreetdSettings.getEffectiveFontFamily();
|
||||
return GreetdSettings.fontFamily;
|
||||
}
|
||||
return typeof SettingsData !== "undefined" ? SettingsData.fontFamily : "Inter Variable";
|
||||
}
|
||||
@@ -1551,7 +1569,7 @@ Singleton {
|
||||
if (typeof SettingsData !== "undefined") {
|
||||
const skipTemplates = [];
|
||||
if (!SettingsData.runDmsMatugenTemplates) {
|
||||
skipTemplates.push("gtk", "nvim", "niri", "qt5ct", "qt6ct", "firefox", "pywalfox", "zenbrowser", "vesktop", "equibop", "ghostty", "kitty", "foot", "alacritty", "wezterm", "dgop", "kcolorscheme", "vscode", "emacs", "zed");
|
||||
skipTemplates.push("gtk", "nvim", "niri", "qt5ct", "qt6ct", "firefox", "pywalfox", "zenbrowser", "vesktop", "equibop", "ghostty", "kitty", "foot", "alacritty", "wezterm", "dgop", "kcolorscheme", "vscode", "emacs");
|
||||
} else {
|
||||
if (!SettingsData.matugenTemplateGtk)
|
||||
skipTemplates.push("gtk");
|
||||
@@ -1595,8 +1613,6 @@ Singleton {
|
||||
skipTemplates.push("vscode");
|
||||
if (!SettingsData.matugenTemplateEmacs)
|
||||
skipTemplates.push("emacs");
|
||||
if (!SettingsData.matugenTemplateZed)
|
||||
skipTemplates.push("zed");
|
||||
}
|
||||
if (skipTemplates.length > 0) {
|
||||
args.push("--skip-templates", skipTemplates.join(","));
|
||||
@@ -1989,7 +2005,7 @@ Singleton {
|
||||
FileView {
|
||||
id: dynamicColorsFileView
|
||||
path: {
|
||||
const greetCfgDir = Quickshell.env("DMS_GREET_CFG_DIR") || "/var/cache/dms-greeter";
|
||||
const greetCfgDir = Quickshell.env("DMS_GREET_CFG_DIR") || "/etc/greetd/.dms";
|
||||
const colorsPath = SessionData.isGreeterMode ? greetCfgDir + "/colors.json" : stateDir + "/dms-colors.json";
|
||||
return colorsPath;
|
||||
}
|
||||
|
||||
@@ -10,39 +10,15 @@ Singleton {
|
||||
|
||||
property var settingsRoot: null
|
||||
|
||||
function envFlag(name) {
|
||||
const value = (Quickshell.env(name) || "").trim().toLowerCase();
|
||||
if (value === "1" || value === "true" || value === "yes" || value === "on")
|
||||
return true;
|
||||
if (value === "0" || value === "false" || value === "no" || value === "off")
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
readonly property var forcedFprintAvailable: envFlag("DMS_FORCE_FPRINT_AVAILABLE")
|
||||
readonly property var forcedU2fAvailable: envFlag("DMS_FORCE_U2F_AVAILABLE")
|
||||
|
||||
function detectQtTools() {
|
||||
qtToolsDetectionProcess.running = true;
|
||||
}
|
||||
|
||||
function detectFprintd() {
|
||||
if (!settingsRoot)
|
||||
return;
|
||||
if (forcedFprintAvailable !== null) {
|
||||
settingsRoot.fprintdAvailable = forcedFprintAvailable;
|
||||
return;
|
||||
}
|
||||
fprintdDetectionProcess.running = true;
|
||||
}
|
||||
|
||||
function detectU2f() {
|
||||
if (!settingsRoot)
|
||||
return;
|
||||
if (forcedU2fAvailable !== null) {
|
||||
settingsRoot.u2fAvailable = forcedU2fAvailable;
|
||||
return;
|
||||
}
|
||||
u2fDetectionProcess.running = true;
|
||||
}
|
||||
|
||||
@@ -76,7 +52,7 @@ Singleton {
|
||||
}
|
||||
|
||||
property var fprintdDetectionProcess: Process {
|
||||
command: ["sh", "-c", "command -v fprintd-list >/dev/null 2>&1 && fprintd-list \"${USER:-$(id -un)}\" >/dev/null 2>&1"]
|
||||
command: ["sh", "-c", "command -v fprintd-list >/dev/null 2>&1"]
|
||||
running: false
|
||||
onExited: function (exitCode) {
|
||||
if (!settingsRoot)
|
||||
|
||||
@@ -47,6 +47,9 @@ var SPEC = {
|
||||
modalAnimationSpeed: { def: 1 },
|
||||
modalCustomAnimationDuration: { def: 150 },
|
||||
enableRippleEffects: { def: true },
|
||||
animationVariant: { def: 0 },
|
||||
motionEffect: { def: 0 },
|
||||
directionalAnimationMode: { def: 0 },
|
||||
m3ElevationEnabled: { def: true },
|
||||
m3ElevationIntensity: { def: 12 },
|
||||
m3ElevationOpacity: { def: 30 },
|
||||
@@ -123,7 +126,6 @@ var SPEC = {
|
||||
showOccupiedWorkspacesOnly: { def: false },
|
||||
reverseScrolling: { def: false },
|
||||
dwlShowAllTags: { def: false },
|
||||
workspaceActiveAppHighlightEnabled: { def: false },
|
||||
workspaceColorMode: { def: "default" },
|
||||
workspaceOccupiedColorMode: { def: "none" },
|
||||
workspaceUnfocusedColorMode: { def: "default" },
|
||||
@@ -165,17 +167,6 @@ var SPEC = {
|
||||
centeringMode: { def: "index" },
|
||||
clockDateFormat: { def: "" },
|
||||
lockDateFormat: { def: "" },
|
||||
greeterRememberLastSession: { def: true },
|
||||
greeterRememberLastUser: { def: true },
|
||||
greeterEnableFprint: { def: false },
|
||||
greeterEnableU2f: { def: false },
|
||||
greeterWallpaperPath: { def: "" },
|
||||
greeterUse24HourClock: { def: true },
|
||||
greeterShowSeconds: { def: false },
|
||||
greeterPadHours12Hour: { def: false },
|
||||
greeterLockDateFormat: { def: "" },
|
||||
greeterFontFamily: { def: "" },
|
||||
greeterWallpaperFillMode: { def: "" },
|
||||
mediaSize: { def: 1 },
|
||||
|
||||
appLauncherViewMode: { def: "list" },
|
||||
@@ -284,19 +275,17 @@ var SPEC = {
|
||||
matugenTemplateKitty: { def: true },
|
||||
matugenTemplateFoot: { def: true },
|
||||
matugenTemplateAlacritty: { def: true },
|
||||
matugenTemplateNeovim: { def: false },
|
||||
matugenTemplateNeovim: { def: true },
|
||||
matugenTemplateWezterm: { def: true },
|
||||
matugenTemplateDgop: { def: true },
|
||||
matugenTemplateKcolorscheme: { def: true },
|
||||
matugenTemplateVscode: { def: true },
|
||||
matugenTemplateEmacs: { def: true },
|
||||
matugenTemplateZed: { def: true },
|
||||
|
||||
showDock: { def: false },
|
||||
dockAutoHide: { def: false },
|
||||
dockSmartAutoHide: { def: false },
|
||||
dockGroupByApp: { def: false },
|
||||
dockRestoreSpecialWorkspaceOnClick: { def: false },
|
||||
dockOpenOnOverview: { def: false },
|
||||
dockPosition: { def: 1 },
|
||||
dockSpacing: { def: 4 },
|
||||
@@ -366,7 +355,6 @@ var SPEC = {
|
||||
notificationHistorySaveNormal: { def: true },
|
||||
notificationHistorySaveCritical: { def: true },
|
||||
notificationRules: { def: [] },
|
||||
notificationFocusedMonitor: { def: false },
|
||||
|
||||
osdAlwaysShowValue: { def: false },
|
||||
osdPosition: { def: 5 },
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Services.Greetd
|
||||
import qs.Common
|
||||
import qs.Modules.Greetd
|
||||
|
||||
Scope {
|
||||
|
||||
@@ -313,7 +313,7 @@ Item {
|
||||
}
|
||||
|
||||
Variants {
|
||||
model: SettingsData.notificationFocusedMonitor ? Quickshell.screens : SettingsData.getFilteredScreens("notifications")
|
||||
model: SettingsData.getFilteredScreens("notifications")
|
||||
|
||||
delegate: NotificationPopupManager {
|
||||
modelData: item
|
||||
|
||||
@@ -26,10 +26,10 @@ Item {
|
||||
property bool closeOnBackgroundClick: true
|
||||
property string animationType: "scale"
|
||||
property int animationDuration: Theme.modalAnimationDuration
|
||||
property real animationScaleCollapsed: 0.96
|
||||
property real animationOffset: Theme.spacingL
|
||||
property list<real> animationEnterCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
||||
property list<real> animationExitCurve: Theme.expressiveCurves.emphasized
|
||||
property real animationScaleCollapsed: Theme.effectScaleCollapsed
|
||||
property real animationOffset: Theme.effectAnimOffset
|
||||
property list<real> animationEnterCurve: Theme.variantModalEnterCurve
|
||||
property list<real> animationExitCurve: Theme.variantModalExitCurve
|
||||
property color backgroundColor: Theme.surfaceContainer
|
||||
property color borderColor: Theme.outlineMedium
|
||||
property real borderWidth: 0
|
||||
@@ -44,11 +44,13 @@ Item {
|
||||
property bool keepPopoutsOpen: false
|
||||
property var customKeyboardFocus: null
|
||||
property bool useOverlayLayer: false
|
||||
property real frozenMotionOffsetX: 0
|
||||
property real frozenMotionOffsetY: 0
|
||||
readonly property alias contentWindow: contentWindow
|
||||
readonly property alias clickCatcher: clickCatcher
|
||||
readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab
|
||||
readonly property bool useBackground: showBackground && SettingsData.modalDarkenBackground
|
||||
readonly property bool useSingleWindow: CompositorService.isHyprland || useBackground
|
||||
readonly property bool useSingleWindow: CompositorService.isHyprland
|
||||
|
||||
signal opened
|
||||
signal dialogClosed
|
||||
@@ -58,19 +60,34 @@ Item {
|
||||
|
||||
function open() {
|
||||
closeTimer.stop();
|
||||
animationsEnabled = false;
|
||||
frozenMotionOffsetX = modalContainer ? modalContainer.offsetX : 0;
|
||||
frozenMotionOffsetY = modalContainer ? modalContainer.offsetY : animationOffset;
|
||||
|
||||
const focusedScreen = CompositorService.getFocusedScreen();
|
||||
if (focusedScreen) {
|
||||
contentWindow.screen = focusedScreen;
|
||||
if (!useSingleWindow)
|
||||
clickCatcher.screen = focusedScreen;
|
||||
}
|
||||
|
||||
if (Theme.isDirectionalEffect || root.useBackground) {
|
||||
if (!useSingleWindow)
|
||||
clickCatcher.visible = true;
|
||||
contentWindow.visible = true;
|
||||
}
|
||||
ModalManager.openModal(root);
|
||||
shouldBeVisible = true;
|
||||
if (!useSingleWindow)
|
||||
clickCatcher.visible = true;
|
||||
contentWindow.visible = true;
|
||||
shouldHaveFocus = false;
|
||||
Qt.callLater(() => shouldHaveFocus = Qt.binding(() => shouldBeVisible));
|
||||
|
||||
Qt.callLater(() => {
|
||||
animationsEnabled = true;
|
||||
shouldBeVisible = true;
|
||||
if (!useSingleWindow && !clickCatcher.visible)
|
||||
clickCatcher.visible = true;
|
||||
if (!contentWindow.visible)
|
||||
contentWindow.visible = true;
|
||||
shouldHaveFocus = false;
|
||||
Qt.callLater(() => shouldHaveFocus = Qt.binding(() => shouldBeVisible));
|
||||
});
|
||||
}
|
||||
|
||||
function close() {
|
||||
@@ -131,7 +148,7 @@ Item {
|
||||
|
||||
Timer {
|
||||
id: closeTimer
|
||||
interval: animationDuration + 50
|
||||
interval: Theme.variantCloseInterval(animationDuration)
|
||||
onTriggered: {
|
||||
if (shouldBeVisible)
|
||||
return;
|
||||
@@ -145,7 +162,17 @@ Item {
|
||||
readonly property var shadowLevel: Theme.elevationLevel3
|
||||
readonly property real shadowFallbackOffset: 6
|
||||
readonly property real shadowRenderPadding: (root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, Theme.elevationLightDirection, shadowFallbackOffset, 8, 16) : 0
|
||||
readonly property real shadowMotionPadding: animationType === "slide" ? 30 : Math.max(0, animationOffset)
|
||||
readonly property real shadowMotionPadding: {
|
||||
if (typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode > 0 && Theme.isDirectionalEffect)
|
||||
return 0; // Wayland native overlap mask
|
||||
if (animationType === "slide")
|
||||
return 30;
|
||||
if (Theme.isDirectionalEffect)
|
||||
return Math.max(Math.max(0, animationOffset), Math.max(alignedWidth, alignedHeight) * 0.9);
|
||||
if (Theme.isDepthEffect)
|
||||
return Math.max(Math.max(0, animationOffset), Math.max(alignedWidth, alignedHeight) * 0.35);
|
||||
return Math.max(0, animationOffset);
|
||||
}
|
||||
readonly property real shadowBuffer: Theme.snap(shadowRenderPadding + shadowMotionPadding, dpr)
|
||||
readonly property real alignedWidth: Theme.px(modalWidth, dpr)
|
||||
readonly property real alignedHeight: Theme.px(modalHeight, dpr)
|
||||
@@ -205,9 +232,26 @@ Item {
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: root.closeOnBackgroundClick && root.shouldBeVisible
|
||||
enabled: !root.useSingleWindow && root.closeOnBackgroundClick && root.shouldBeVisible
|
||||
onClicked: root.backgroundClicked()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
z: -1
|
||||
color: "black"
|
||||
opacity: (!root.useSingleWindow && root.useBackground) ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
|
||||
visible: opacity > 0
|
||||
|
||||
Behavior on opacity {
|
||||
enabled: root.animationsEnabled && !Theme.isDirectionalEffect
|
||||
NumberAnimation {
|
||||
duration: Math.round(Theme.variantDuration(root.animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
@@ -250,9 +294,12 @@ Item {
|
||||
bottom: root.useSingleWindow
|
||||
}
|
||||
|
||||
readonly property real actualMarginLeft: root.useSingleWindow ? 0 : Math.max(0, Theme.snap(root.alignedX - shadowBuffer, dpr))
|
||||
readonly property real actualMarginTop: root.useSingleWindow ? 0 : Math.max(0, Theme.snap(root.alignedY - shadowBuffer, dpr))
|
||||
|
||||
WlrLayershell.margins {
|
||||
left: root.useSingleWindow ? 0 : Math.max(0, Theme.snap(root.alignedX - shadowBuffer, dpr))
|
||||
top: root.useSingleWindow ? 0 : Math.max(0, Theme.snap(root.alignedY - shadowBuffer, dpr))
|
||||
left: actualMarginLeft
|
||||
top: actualMarginTop
|
||||
right: 0
|
||||
bottom: 0
|
||||
}
|
||||
@@ -282,13 +329,14 @@ Item {
|
||||
anchors.fill: parent
|
||||
z: -1
|
||||
color: "black"
|
||||
opacity: root.useBackground ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
|
||||
visible: root.useBackground
|
||||
opacity: (root.useSingleWindow && root.useBackground) ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
|
||||
visible: opacity > 0
|
||||
|
||||
Behavior on opacity {
|
||||
enabled: root.animationsEnabled
|
||||
DankAnim {
|
||||
duration: root.animationDuration
|
||||
enabled: root.animationsEnabled && !Theme.isDirectionalEffect
|
||||
NumberAnimation {
|
||||
duration: Math.round(Theme.variantDuration(root.animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
}
|
||||
@@ -296,8 +344,8 @@ Item {
|
||||
|
||||
Item {
|
||||
id: modalContainer
|
||||
x: root.useSingleWindow ? root.alignedX : shadowBuffer
|
||||
y: root.useSingleWindow ? root.alignedY : shadowBuffer
|
||||
x: (root.useSingleWindow ? root.alignedX : (root.alignedX - contentWindow.actualMarginLeft)) + Theme.snap(animX, root.dpr)
|
||||
y: (root.useSingleWindow ? root.alignedY : (root.alignedY - contentWindow.actualMarginTop)) + Theme.snap(animY, root.dpr)
|
||||
|
||||
width: root.alignedWidth
|
||||
height: root.alignedHeight
|
||||
@@ -313,45 +361,117 @@ Item {
|
||||
}
|
||||
|
||||
readonly property bool slide: root.animationType === "slide"
|
||||
readonly property real offsetX: slide ? 15 : 0
|
||||
readonly property real offsetY: slide ? -30 : root.animationOffset
|
||||
|
||||
property real animX: 0
|
||||
property real animY: 0
|
||||
property real scaleValue: root.animationScaleCollapsed
|
||||
|
||||
onOffsetXChanged: animX = Theme.snap(root.shouldBeVisible ? 0 : offsetX, root.dpr)
|
||||
onOffsetYChanged: animY = Theme.snap(root.shouldBeVisible ? 0 : offsetY, root.dpr)
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onShouldBeVisibleChanged() {
|
||||
modalContainer.animX = Theme.snap(root.shouldBeVisible ? 0 : modalContainer.offsetX, root.dpr);
|
||||
modalContainer.animY = Theme.snap(root.shouldBeVisible ? 0 : modalContainer.offsetY, root.dpr);
|
||||
modalContainer.scaleValue = root.shouldBeVisible ? 1.0 : root.animationScaleCollapsed;
|
||||
readonly property bool directionalEffect: Theme.isDirectionalEffect
|
||||
readonly property bool depthEffect: Theme.isDepthEffect
|
||||
readonly property real directionalTravel: Math.max(root.animationOffset, Math.max(root.alignedWidth, root.alignedHeight) * 0.8)
|
||||
readonly property real depthTravel: Math.max(root.animationOffset * 0.8, 36)
|
||||
readonly property real customAnchorX: root.alignedX + root.alignedWidth * 0.5
|
||||
readonly property real customAnchorY: root.alignedY + root.alignedHeight * 0.5
|
||||
readonly property real customDistLeft: customAnchorX
|
||||
readonly property real customDistRight: root.screenWidth - customAnchorX
|
||||
readonly property real customDistTop: customAnchorY
|
||||
readonly property real customDistBottom: root.screenHeight - customAnchorY
|
||||
readonly property real offsetX: {
|
||||
if (typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2 && Theme.isDirectionalEffect)
|
||||
return 0;
|
||||
if (slide && !directionalEffect && !depthEffect)
|
||||
return 15;
|
||||
if (directionalEffect) {
|
||||
switch (root.positioning) {
|
||||
case "top-right":
|
||||
return 0;
|
||||
case "custom":
|
||||
if (customDistLeft <= customDistRight && customDistLeft <= customDistTop && customDistLeft <= customDistBottom)
|
||||
return -directionalTravel;
|
||||
if (customDistRight <= customDistTop && customDistRight <= customDistBottom)
|
||||
return directionalTravel;
|
||||
return 0;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (depthEffect) {
|
||||
switch (root.positioning) {
|
||||
case "top-right":
|
||||
return 0;
|
||||
case "custom":
|
||||
if (customDistLeft <= customDistRight && customDistLeft <= customDistTop && customDistLeft <= customDistBottom)
|
||||
return -depthTravel;
|
||||
if (customDistRight <= customDistTop && customDistRight <= customDistBottom)
|
||||
return depthTravel;
|
||||
return 0;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
readonly property real offsetY: {
|
||||
if (typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2 && Theme.isDirectionalEffect)
|
||||
return 0;
|
||||
if (slide && !directionalEffect && !depthEffect)
|
||||
return -30;
|
||||
if (directionalEffect) {
|
||||
switch (root.positioning) {
|
||||
case "top-right":
|
||||
return -Math.max(directionalTravel * 0.65, 96);
|
||||
case "custom":
|
||||
if (customDistTop <= customDistBottom && customDistTop <= customDistLeft && customDistTop <= customDistRight)
|
||||
return -directionalTravel;
|
||||
if (customDistBottom <= customDistLeft && customDistBottom <= customDistRight)
|
||||
return directionalTravel;
|
||||
return 0;
|
||||
default:
|
||||
// Default to sliding down from top when centered
|
||||
return -Math.max(directionalTravel, root.screenHeight * 0.24);
|
||||
}
|
||||
}
|
||||
if (depthEffect) {
|
||||
switch (root.positioning) {
|
||||
case "top-right":
|
||||
return -depthTravel * 0.75;
|
||||
case "custom":
|
||||
if (customDistTop <= customDistBottom && customDistTop <= customDistLeft && customDistTop <= customDistRight)
|
||||
return -depthTravel;
|
||||
if (customDistBottom <= customDistLeft && customDistBottom <= customDistRight)
|
||||
return depthTravel;
|
||||
return depthTravel * 0.45;
|
||||
default:
|
||||
return -depthTravel;
|
||||
}
|
||||
}
|
||||
return root.animationOffset;
|
||||
}
|
||||
|
||||
property real animX: root.shouldBeVisible ? 0 : root.frozenMotionOffsetX
|
||||
property real animY: root.shouldBeVisible ? 0 : root.frozenMotionOffsetY
|
||||
|
||||
readonly property real computedScaleCollapsed: (typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2 && Theme.isDirectionalEffect) ? 0.0 : root.animationScaleCollapsed
|
||||
property real scaleValue: root.shouldBeVisible ? 1.0 : computedScaleCollapsed
|
||||
|
||||
Behavior on animX {
|
||||
enabled: root.animationsEnabled
|
||||
DankAnim {
|
||||
duration: root.animationDuration
|
||||
NumberAnimation {
|
||||
duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on animY {
|
||||
enabled: root.animationsEnabled
|
||||
DankAnim {
|
||||
duration: root.animationDuration
|
||||
NumberAnimation {
|
||||
duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scaleValue {
|
||||
enabled: root.animationsEnabled
|
||||
DankAnim {
|
||||
duration: root.animationDuration
|
||||
NumberAnimation {
|
||||
duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
}
|
||||
@@ -367,15 +487,14 @@ Item {
|
||||
id: animatedContent
|
||||
anchors.fill: parent
|
||||
clip: false
|
||||
opacity: root.shouldBeVisible ? 1 : 0
|
||||
opacity: Theme.isDirectionalEffect ? 1 : (root.shouldBeVisible ? 1 : 0)
|
||||
scale: modalContainer.scaleValue
|
||||
x: Theme.snap(modalContainer.animX, root.dpr) + (parent.width - width) * (1 - modalContainer.scaleValue) * 0.5
|
||||
y: Theme.snap(modalContainer.animY, root.dpr) + (parent.height - height) * (1 - modalContainer.scaleValue) * 0.5
|
||||
transformOrigin: Item.Center
|
||||
|
||||
Behavior on opacity {
|
||||
enabled: root.animationsEnabled
|
||||
enabled: root.animationsEnabled && !Theme.isDirectionalEffect
|
||||
NumberAnimation {
|
||||
duration: animationDuration
|
||||
duration: Math.round(Theme.variantDuration(animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
|
||||
@@ -1006,7 +1006,9 @@ Item {
|
||||
_applyHighlights(newSections, searchQuery);
|
||||
flatModel = Scorer.flattenSections(newSections);
|
||||
sections = newSections;
|
||||
selectedFlatIndex = getFirstItemIndex();
|
||||
if (selectedFlatIndex >= flatModel.length) {
|
||||
selectedFlatIndex = getFirstItemIndex();
|
||||
}
|
||||
updateSelectedItem();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ Item {
|
||||
property bool spotlightOpen: false
|
||||
property bool keyboardActive: false
|
||||
property bool contentVisible: false
|
||||
readonly property bool launcherMotionVisible: Theme.isDirectionalEffect ? spotlightOpen : _motionActive
|
||||
property var spotlightContent: launcherContentLoader.item
|
||||
property bool openedFromOverview: false
|
||||
property bool isClosing: false
|
||||
@@ -23,8 +24,14 @@ Item {
|
||||
property string _pendingMode: ""
|
||||
readonly property bool unloadContentOnClose: SettingsData.dankLauncherV2UnloadOnClose
|
||||
|
||||
// Animation state — matches DankPopout/DankModal pattern
|
||||
property bool animationsEnabled: true
|
||||
property bool _motionActive: false
|
||||
property real _frozenMotionX: 0
|
||||
property real _frozenMotionY: 0
|
||||
|
||||
readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab
|
||||
readonly property var effectiveScreen: launcherWindow.screen
|
||||
readonly property var effectiveScreen: contentWindow.screen
|
||||
readonly property real screenWidth: effectiveScreen?.width ?? 1920
|
||||
readonly property real screenHeight: effectiveScreen?.height ?? 1080
|
||||
readonly property real dpr: effectiveScreen ? CompositorService.getScreenScale(effectiveScreen) : 1
|
||||
@@ -78,6 +85,34 @@ Item {
|
||||
}
|
||||
readonly property int borderWidth: SettingsData.dankLauncherV2BorderEnabled ? SettingsData.dankLauncherV2BorderThickness : 0
|
||||
|
||||
// Shadow padding for the content window (render padding only, no motion padding)
|
||||
readonly property var shadowLevel: Theme.elevationLevel3
|
||||
readonly property real shadowFallbackOffset: 6
|
||||
readonly property real shadowRenderPadding: (Theme.elevationEnabled && SettingsData.modalElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, Theme.elevationLightDirection, shadowFallbackOffset, 8, 16) : 0
|
||||
readonly property real shadowPad: Theme.snap(shadowRenderPadding, dpr)
|
||||
readonly property real alignedWidth: Theme.px(modalWidth, dpr)
|
||||
readonly property real alignedHeight: Theme.px(modalHeight, dpr)
|
||||
readonly property real alignedX: Theme.snap(modalX, dpr)
|
||||
readonly property real alignedY: Theme.snap(modalY, dpr)
|
||||
|
||||
// For directional/depth: window extends from screen top (content slides within)
|
||||
// For standard: small window tightly around the modal + shadow padding
|
||||
readonly property bool _needsExtendedWindow: Theme.isDirectionalEffect || Theme.isDepthEffect
|
||||
// Content window geometry
|
||||
readonly property real _cwMarginLeft: Theme.snap(alignedX - shadowPad, dpr)
|
||||
readonly property real _cwMarginTop: _needsExtendedWindow ? 0 : Theme.snap(alignedY - shadowPad, dpr)
|
||||
readonly property real _cwWidth: alignedWidth + shadowPad * 2
|
||||
readonly property real _cwHeight: {
|
||||
if (Theme.isDirectionalEffect)
|
||||
return screenHeight + shadowPad;
|
||||
if (Theme.isDepthEffect)
|
||||
return alignedY + alignedHeight + shadowPad;
|
||||
return alignedHeight + shadowPad * 2;
|
||||
}
|
||||
// Where the content container sits inside the content window
|
||||
readonly property real _ccX: shadowPad
|
||||
readonly property real _ccY: _needsExtendedWindow ? alignedY : shadowPad
|
||||
|
||||
signal dialogClosed
|
||||
|
||||
function _ensureContentLoadedAndInitialize(query, mode) {
|
||||
@@ -97,7 +132,8 @@ Item {
|
||||
if (!spotlightContent)
|
||||
return;
|
||||
contentVisible = true;
|
||||
spotlightContent.searchField.forceActiveFocus();
|
||||
// NOTE: forceActiveFocus() is deliberately NOT called here.
|
||||
// It is deferred to after animation starts to avoid compositor IPC stalls.
|
||||
|
||||
if (spotlightContent.searchField) {
|
||||
spotlightContent.searchField.text = query;
|
||||
@@ -130,40 +166,59 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
function show() {
|
||||
function _openCommon(query, mode) {
|
||||
closeCleanupTimer.stop();
|
||||
isClosing = false;
|
||||
openedFromOverview = false;
|
||||
|
||||
var focusedScreen = CompositorService.getFocusedScreen();
|
||||
if (focusedScreen)
|
||||
launcherWindow.screen = focusedScreen;
|
||||
// Disable animations so the snap is instant
|
||||
animationsEnabled = false;
|
||||
|
||||
spotlightOpen = true;
|
||||
keyboardActive = true;
|
||||
// Freeze the collapsed offsets (they depend on height which could change)
|
||||
_frozenMotionX = contentContainer ? contentContainer.collapsedMotionX : 0;
|
||||
_frozenMotionY = contentContainer ? contentContainer.collapsedMotionY : (Theme.isDirectionalEffect ? Math.max(root.screenHeight - root._ccY + root.shadowPad, Theme.effectAnimOffset * 1.1) : -Theme.effectAnimOffset);
|
||||
|
||||
var focusedScreen = CompositorService.getFocusedScreen();
|
||||
if (focusedScreen) {
|
||||
backgroundWindow.screen = focusedScreen;
|
||||
contentWindow.screen = focusedScreen;
|
||||
}
|
||||
|
||||
// _motionActive = false ensures motionX/Y snap to frozen collapsed position
|
||||
_motionActive = false;
|
||||
|
||||
// Make windows visible but do NOT request keyboard focus yet
|
||||
ModalManager.openModal(root);
|
||||
spotlightOpen = true;
|
||||
backgroundWindow.visible = true;
|
||||
contentWindow.visible = true;
|
||||
if (useHyprlandFocusGrab)
|
||||
focusGrab.active = true;
|
||||
|
||||
_ensureContentLoadedAndInitialize("", "");
|
||||
// Load content and initialize (but no forceActiveFocus — that's deferred)
|
||||
_ensureContentLoadedAndInitialize(query || "", mode || "");
|
||||
|
||||
// Frame 1: enable animations and trigger enter motion
|
||||
Qt.callLater(() => {
|
||||
root.animationsEnabled = true;
|
||||
root._motionActive = true;
|
||||
|
||||
// Frame 2: request keyboard focus + activate search field
|
||||
// Double-deferred to avoid compositor IPC competing with animation frames
|
||||
Qt.callLater(() => {
|
||||
root.keyboardActive = true;
|
||||
if (root.spotlightContent && root.spotlightContent.searchField)
|
||||
root.spotlightContent.searchField.forceActiveFocus();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function show() {
|
||||
_openCommon("", "");
|
||||
}
|
||||
|
||||
function showWithQuery(query) {
|
||||
closeCleanupTimer.stop();
|
||||
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(query, "");
|
||||
_openCommon(query, "");
|
||||
}
|
||||
|
||||
function hide() {
|
||||
@@ -171,13 +226,17 @@ Item {
|
||||
return;
|
||||
openedFromOverview = false;
|
||||
isClosing = true;
|
||||
contentVisible = false;
|
||||
// For directional effects, defer contentVisible=false so content stays rendered during exit slide
|
||||
if (!Theme.isDirectionalEffect)
|
||||
contentVisible = false;
|
||||
|
||||
// Trigger exit animation — Behaviors will animate motionX/Y to frozen collapsed position
|
||||
_motionActive = false;
|
||||
|
||||
keyboardActive = false;
|
||||
spotlightOpen = false;
|
||||
focusGrab.active = false;
|
||||
ModalManager.closeModal(root);
|
||||
|
||||
closeCleanupTimer.start();
|
||||
}
|
||||
|
||||
@@ -186,21 +245,7 @@ Item {
|
||||
}
|
||||
|
||||
function showWithMode(mode) {
|
||||
closeCleanupTimer.stop();
|
||||
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("", mode);
|
||||
_openCommon("", mode);
|
||||
}
|
||||
|
||||
function toggleWithMode(mode) {
|
||||
@@ -221,10 +266,13 @@ Item {
|
||||
|
||||
Timer {
|
||||
id: closeCleanupTimer
|
||||
interval: Theme.modalAnimationDuration + 50
|
||||
interval: Theme.variantCloseInterval(Theme.modalAnimationDuration)
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
isClosing = false;
|
||||
contentVisible = false;
|
||||
contentWindow.visible = false;
|
||||
backgroundWindow.visible = false;
|
||||
if (root.unloadContentOnClose)
|
||||
launcherContentLoader.active = false;
|
||||
dialogClosed();
|
||||
@@ -242,7 +290,7 @@ Item {
|
||||
|
||||
HyprlandFocusGrab {
|
||||
id: focusGrab
|
||||
windows: [launcherWindow]
|
||||
windows: [contentWindow]
|
||||
active: false
|
||||
|
||||
onCleared: {
|
||||
@@ -267,7 +315,7 @@ Item {
|
||||
if (Quickshell.screens.length === 0)
|
||||
return;
|
||||
|
||||
const screen = launcherWindow.screen;
|
||||
const screen = contentWindow.screen;
|
||||
const screenName = screen?.name;
|
||||
|
||||
let needsReset = !screen || !screenName;
|
||||
@@ -289,35 +337,31 @@ Item {
|
||||
return;
|
||||
|
||||
root._windowEnabled = false;
|
||||
launcherWindow.screen = newScreen;
|
||||
backgroundWindow.screen = newScreen;
|
||||
contentWindow.screen = newScreen;
|
||||
Qt.callLater(() => {
|
||||
root._windowEnabled = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ── Background window: fullscreen, handles darkening + click-to-dismiss ──
|
||||
PanelWindow {
|
||||
id: launcherWindow
|
||||
visible: root._windowEnabled && (spotlightOpen || isClosing)
|
||||
id: backgroundWindow
|
||||
visible: false
|
||||
color: "transparent"
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
|
||||
WlrLayershell.namespace: "dms:spotlight"
|
||||
WlrLayershell.layer: {
|
||||
switch (Quickshell.env("DMS_MODAL_LAYER")) {
|
||||
case "bottom":
|
||||
console.error("DankModal: 'bottom' layer is not valid for modals. Defaulting to 'top' layer.");
|
||||
return WlrLayershell.Top;
|
||||
case "background":
|
||||
console.error("DankModal: 'background' layer is not valid for modals. Defaulting to 'top' layer.");
|
||||
return WlrLayershell.Top;
|
||||
case "overlay":
|
||||
return WlrLayershell.Overlay;
|
||||
default:
|
||||
return WlrLayershell.Top;
|
||||
}
|
||||
WlrLayershell.namespace: "dms:spotlight:bg"
|
||||
WlrLayershell.layer: WlrLayershell.Top
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
|
||||
WlrLayershell.margins {
|
||||
top: contentContainer.dockTop ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 0 ? Theme.px(42, root.dpr) : 0)
|
||||
bottom: contentContainer.dockBottom ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 1 ? Theme.px(42, root.dpr) : 0)
|
||||
left: contentContainer.dockLeft ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 2 ? Theme.px(42, root.dpr) : 0)
|
||||
right: contentContainer.dockRight ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 3 ? Theme.px(42, root.dpr) : 0)
|
||||
}
|
||||
WlrLayershell.keyboardFocus: keyboardActive ? (root.useHyprlandFocusGrab ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive) : WlrKeyboardFocus.None
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
@@ -327,11 +371,11 @@ Item {
|
||||
}
|
||||
|
||||
mask: Region {
|
||||
item: spotlightOpen ? fullScreenMask : null
|
||||
item: (spotlightOpen || isClosing) ? bgFullScreenMask : null
|
||||
}
|
||||
|
||||
Item {
|
||||
id: fullScreenMask
|
||||
id: bgFullScreenMask
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
@@ -339,13 +383,14 @@ Item {
|
||||
id: backgroundDarken
|
||||
anchors.fill: parent
|
||||
color: "black"
|
||||
opacity: contentVisible && SettingsData.modalDarkenBackground ? 0.5 : 0
|
||||
visible: contentVisible || opacity > 0
|
||||
opacity: launcherMotionVisible && SettingsData.modalDarkenBackground ? 0.5 : 0
|
||||
visible: launcherMotionVisible || opacity > 0
|
||||
|
||||
Behavior on opacity {
|
||||
enabled: root.animationsEnabled && !Theme.isDirectionalEffect
|
||||
DankAnim {
|
||||
duration: Theme.modalAnimationDuration
|
||||
easing.bezierCurve: contentVisible ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
|
||||
duration: Math.round(Theme.variantDuration(Theme.modalAnimationDuration, launcherMotionVisible) * Theme.variantOpacityDurationScale)
|
||||
easing.bezierCurve: launcherMotionVisible ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -353,88 +398,240 @@ Item {
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: spotlightOpen
|
||||
onClicked: mouse => {
|
||||
var contentX = modalContainer.x;
|
||||
var contentY = modalContainer.y;
|
||||
var contentW = modalContainer.width;
|
||||
var contentH = modalContainer.height;
|
||||
onClicked: root.hide()
|
||||
}
|
||||
}
|
||||
|
||||
if (mouse.x < contentX || mouse.x > contentX + contentW || mouse.y < contentY || mouse.y > contentY + contentH) {
|
||||
root.hide();
|
||||
}
|
||||
// ── Content window: SMALL, positioned with margins — only renders the modal area ──
|
||||
PanelWindow {
|
||||
id: contentWindow
|
||||
visible: false
|
||||
color: "transparent"
|
||||
|
||||
WlrLayershell.namespace: "dms:spotlight"
|
||||
WlrLayershell.layer: {
|
||||
switch (Quickshell.env("DMS_MODAL_LAYER")) {
|
||||
case "bottom":
|
||||
console.error("DankLauncherV2Modal: 'bottom' layer is not valid for modals. Defaulting to 'top' layer.");
|
||||
return WlrLayershell.Top;
|
||||
case "background":
|
||||
console.error("DankLauncherV2Modal: 'background' layer is not valid for modals. Defaulting to 'top' layer.");
|
||||
return WlrLayershell.Top;
|
||||
case "overlay":
|
||||
return WlrLayershell.Overlay;
|
||||
default:
|
||||
return WlrLayershell.Top;
|
||||
}
|
||||
}
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: keyboardActive ? (root.useHyprlandFocusGrab ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive) : WlrKeyboardFocus.None
|
||||
|
||||
anchors {
|
||||
left: true
|
||||
top: true
|
||||
}
|
||||
|
||||
WlrLayershell.margins {
|
||||
left: root._cwMarginLeft
|
||||
top: root._cwMarginTop
|
||||
}
|
||||
|
||||
implicitWidth: root._cwWidth
|
||||
implicitHeight: root._cwHeight
|
||||
|
||||
mask: Region {
|
||||
item: contentInputMask
|
||||
}
|
||||
|
||||
Item {
|
||||
id: modalContainer
|
||||
x: root.modalX
|
||||
y: root.modalY
|
||||
width: root.modalWidth
|
||||
height: root.modalHeight
|
||||
visible: contentVisible || opacity > 0
|
||||
|
||||
opacity: contentVisible ? 1 : 0
|
||||
scale: contentVisible ? 1 : 0.96
|
||||
transformOrigin: Item.Center
|
||||
|
||||
Behavior on opacity {
|
||||
DankAnim {
|
||||
duration: Theme.modalAnimationDuration
|
||||
easing.bezierCurve: contentVisible ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
DankAnim {
|
||||
duration: Theme.modalAnimationDuration
|
||||
easing.bezierCurve: contentVisible ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
|
||||
}
|
||||
}
|
||||
|
||||
ElevationShadow {
|
||||
id: launcherShadowLayer
|
||||
anchors.fill: parent
|
||||
level: Theme.elevationLevel3
|
||||
fallbackOffset: 6
|
||||
targetColor: root.backgroundColor
|
||||
borderColor: root.borderColor
|
||||
borderWidth: root.borderWidth
|
||||
targetRadius: root.cornerRadius
|
||||
shadowEnabled: Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onPressed: mouse => mouse.accepted = true
|
||||
}
|
||||
|
||||
FocusScope {
|
||||
anchors.fill: parent
|
||||
focus: keyboardActive
|
||||
|
||||
Loader {
|
||||
id: launcherContentLoader
|
||||
anchors.fill: parent
|
||||
active: !root.unloadContentOnClose || root.spotlightOpen || root.isClosing || root.contentVisible || root._pendingInitialize
|
||||
asynchronous: false
|
||||
sourceComponent: LauncherContent {
|
||||
focus: true
|
||||
parentModal: root
|
||||
}
|
||||
|
||||
onLoaded: {
|
||||
if (root._pendingInitialize) {
|
||||
root._initializeAndShow(root._pendingQuery, root._pendingMode);
|
||||
root._pendingInitialize = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onEscapePressed: event => {
|
||||
root.hide();
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
id: contentInputMask
|
||||
visible: false
|
||||
x: contentContainer.x + contentWrapper.x
|
||||
y: contentContainer.y + contentWrapper.y
|
||||
width: root.alignedWidth
|
||||
height: root.alignedHeight
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: contentContainer
|
||||
|
||||
// For directional/depth: contentContainer is at alignedY from window top (window starts at screen top)
|
||||
// For standard: contentContainer is at shadowPad from window top (window starts near modal)
|
||||
x: root._ccX
|
||||
y: root._ccY
|
||||
width: root.alignedWidth
|
||||
height: root.alignedHeight
|
||||
|
||||
readonly property int dockEdge: typeof SettingsData !== "undefined" ? SettingsData.dockPosition : 1
|
||||
readonly property bool dockTop: dockEdge === 0
|
||||
readonly property bool dockBottom: dockEdge === 1
|
||||
readonly property bool dockLeft: dockEdge === 2
|
||||
readonly property bool dockRight: dockEdge === 3
|
||||
|
||||
readonly property real dockThickness: typeof SettingsData !== "undefined" && SettingsData.showDock ? Theme.px(SettingsData.dockIconSize + (SettingsData.dockMargin * 2) + SettingsData.dockSpacing + 8, root.dpr) : Theme.px(60, root.dpr)
|
||||
|
||||
readonly property bool directionalEffect: Theme.isDirectionalEffect
|
||||
readonly property bool depthEffect: Theme.isDepthEffect
|
||||
readonly property real collapsedMotionX: {
|
||||
if (directionalEffect) {
|
||||
if (dockLeft)
|
||||
return -(root._ccX + root.alignedWidth + Theme.effectAnimOffset);
|
||||
if (dockRight)
|
||||
return root.screenWidth - root._ccX + Theme.effectAnimOffset;
|
||||
}
|
||||
if (depthEffect)
|
||||
return Theme.effectAnimOffset * 0.25;
|
||||
return 0;
|
||||
}
|
||||
readonly property real collapsedMotionY: {
|
||||
if (directionalEffect) {
|
||||
if (dockTop)
|
||||
return -(root._ccY + root.alignedHeight + Theme.effectAnimOffset);
|
||||
if (dockBottom)
|
||||
return root.screenHeight - root._ccY + root.shadowPad + Theme.effectAnimOffset;
|
||||
return 0;
|
||||
}
|
||||
if (depthEffect)
|
||||
return -Math.max(Theme.effectAnimOffset * 0.85, 34);
|
||||
return -Math.max((root.shadowPad || 0) + Theme.effectAnimOffset, 40);
|
||||
}
|
||||
|
||||
// animX/animY are Behavior-animated — DankPopout pattern
|
||||
property real animX: 0
|
||||
property real animY: 0
|
||||
property real scaleValue: Theme.isDirectionalEffect && typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2 ? Theme.effectScaleCollapsed : (Theme.isDirectionalEffect ? 1 : Theme.effectScaleCollapsed)
|
||||
|
||||
Component.onCompleted: {
|
||||
animX = Theme.snap(root._motionActive ? 0 : collapsedMotionX, root.dpr);
|
||||
animY = Theme.snap(root._motionActive ? 0 : collapsedMotionY, root.dpr);
|
||||
scaleValue = root._motionActive ? 1.0 : (Theme.isDirectionalEffect && typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2 ? Theme.effectScaleCollapsed : (Theme.isDirectionalEffect ? 1 : Theme.effectScaleCollapsed));
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function on_MotionActiveChanged() {
|
||||
contentContainer.animX = Theme.snap(root._motionActive ? 0 : root._frozenMotionX, root.dpr);
|
||||
contentContainer.animY = Theme.snap(root._motionActive ? 0 : root._frozenMotionY, root.dpr);
|
||||
contentContainer.scaleValue = root._motionActive ? 1.0 : (Theme.isDirectionalEffect && typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2 ? Theme.effectScaleCollapsed : (Theme.isDirectionalEffect ? 1 : Theme.effectScaleCollapsed));
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on animX {
|
||||
enabled: root.animationsEnabled
|
||||
DankAnim {
|
||||
duration: Theme.variantDuration(Theme.modalAnimationDuration, root._motionActive)
|
||||
easing.bezierCurve: root._motionActive ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on animY {
|
||||
enabled: root.animationsEnabled
|
||||
DankAnim {
|
||||
duration: Theme.variantDuration(Theme.modalAnimationDuration, root._motionActive)
|
||||
easing.bezierCurve: root._motionActive ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scaleValue {
|
||||
enabled: root.animationsEnabled && (!Theme.isDirectionalEffect || (typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2))
|
||||
DankAnim {
|
||||
duration: Theme.variantDuration(Theme.modalAnimationDuration, root._motionActive)
|
||||
easing.bezierCurve: root._motionActive ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: directionalClipMask
|
||||
readonly property bool shouldClip: Theme.isDirectionalEffect && typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode > 0
|
||||
readonly property real clipOversize: 2000
|
||||
|
||||
clip: shouldClip
|
||||
|
||||
x: shouldClip ? (contentContainer.dockRight ? -clipOversize : (contentContainer.dockLeft ? contentContainer.dockThickness - root._ccX : -clipOversize)) : 0
|
||||
y: shouldClip ? (contentContainer.dockBottom ? -clipOversize : (contentContainer.dockTop ? contentContainer.dockThickness - root._ccY : -clipOversize)) : 0
|
||||
|
||||
width: shouldClip ? parent.width + clipOversize + (contentContainer.dockRight ? (root.screenWidth - contentContainer.dockThickness - root._ccX - parent.width) : (contentContainer.dockLeft ? clipOversize : clipOversize)) : parent.width
|
||||
height: shouldClip ? parent.height + clipOversize + (contentContainer.dockBottom ? (root.screenHeight - contentContainer.dockThickness - root._ccY - parent.height) : (contentContainer.dockTop ? clipOversize : clipOversize)) : parent.height
|
||||
|
||||
Item {
|
||||
id: aligner
|
||||
x: directionalClipMask.x !== 0 ? -directionalClipMask.x : 0
|
||||
y: directionalClipMask.y !== 0 ? -directionalClipMask.y : 0
|
||||
width: contentContainer.width
|
||||
height: contentContainer.height
|
||||
|
||||
// Shadow mirrors contentWrapper position/scale/opacity
|
||||
ElevationShadow {
|
||||
id: launcherShadowLayer
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
opacity: contentWrapper.opacity
|
||||
scale: contentWrapper.scale
|
||||
x: contentWrapper.x
|
||||
y: contentWrapper.y
|
||||
level: root.shadowLevel
|
||||
fallbackOffset: root.shadowFallbackOffset
|
||||
targetColor: root.backgroundColor
|
||||
borderColor: root.borderColor
|
||||
borderWidth: root.borderWidth
|
||||
targetRadius: root.cornerRadius
|
||||
shadowEnabled: Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
|
||||
}
|
||||
|
||||
// contentWrapper moves inside static contentContainer — DankPopout pattern
|
||||
Item {
|
||||
id: contentWrapper
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
opacity: Theme.isDirectionalEffect ? 1 : (launcherMotionVisible ? 1 : 0)
|
||||
visible: opacity > 0
|
||||
scale: contentContainer.scaleValue
|
||||
x: Theme.snap(contentContainer.animX + (parent.width - width) * (1 - contentContainer.scaleValue) * 0.5, root.dpr)
|
||||
y: Theme.snap(contentContainer.animY + (parent.height - height) * (1 - contentContainer.scaleValue) * 0.5, root.dpr)
|
||||
|
||||
Behavior on opacity {
|
||||
enabled: root.animationsEnabled && !Theme.isDirectionalEffect
|
||||
DankAnim {
|
||||
duration: Math.round(Theme.variantDuration(Theme.modalAnimationDuration, launcherMotionVisible) * Theme.variantOpacityDurationScale)
|
||||
easing.bezierCurve: launcherMotionVisible ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onPressed: mouse => mouse.accepted = true
|
||||
}
|
||||
|
||||
FocusScope {
|
||||
anchors.fill: parent
|
||||
focus: keyboardActive
|
||||
|
||||
Loader {
|
||||
id: launcherContentLoader
|
||||
anchors.fill: parent
|
||||
active: !root.unloadContentOnClose || root.spotlightOpen || root.isClosing || root.contentVisible || root._pendingInitialize
|
||||
asynchronous: false
|
||||
sourceComponent: LauncherContent {
|
||||
focus: true
|
||||
parentModal: root
|
||||
}
|
||||
|
||||
onLoaded: {
|
||||
if (root._pendingInitialize) {
|
||||
root._initializeAndShow(root._pendingQuery, root._pendingMode);
|
||||
root._pendingInitialize = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onEscapePressed: event => {
|
||||
root.hide();
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
} // contentWrapper
|
||||
} // aligner
|
||||
} // directionalClipMask
|
||||
} // contentContainer
|
||||
} // PanelWindow
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ FocusScope {
|
||||
|
||||
Controller {
|
||||
id: controller
|
||||
active: root.parentModal?.spotlightOpen ?? true
|
||||
active: root.parentModal ? (root.parentModal.spotlightOpen || root.parentModal.isClosing) : true
|
||||
viewModeContext: root.viewModeContext
|
||||
|
||||
onItemExecuted: {
|
||||
@@ -462,7 +462,7 @@ FocusScope {
|
||||
showClearButton: true
|
||||
textColor: Theme.surfaceText
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
enabled: root.parentModal ? root.parentModal.spotlightOpen : true
|
||||
enabled: root.parentModal ? (root.parentModal.spotlightOpen || root.parentModal.isClosing) : true
|
||||
placeholderText: ""
|
||||
ignoreUpDownKeys: true
|
||||
ignoreTabKeys: true
|
||||
@@ -548,7 +548,6 @@ FocusScope {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
@@ -697,7 +696,13 @@ FocusScope {
|
||||
Item {
|
||||
width: parent.width
|
||||
height: parent.height - searchField.height - categoryRow.height - fileFilterRow.height - actionPanel.height - Theme.spacingXS * ((categoryRow.visible ? 1 : 0) + (fileFilterRow.visible ? 1 : 0) + 2)
|
||||
opacity: root.parentModal?.isClosing ? 0 : 1
|
||||
opacity: {
|
||||
if (!root.parentModal)
|
||||
return 1;
|
||||
if (Theme.isDirectionalEffect && root.parentModal.isClosing)
|
||||
return 1;
|
||||
return root.parentModal.isClosing ? 0 : 1;
|
||||
}
|
||||
|
||||
ResultsList {
|
||||
id: resultsList
|
||||
|
||||
@@ -468,7 +468,7 @@ Item {
|
||||
switch (mode) {
|
||||
case "files":
|
||||
if (!DSearchService.dsearchAvailable)
|
||||
return I18n.tr("File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch");
|
||||
return I18n.tr("File search requires dsearch\nInstall from github.com/morelazers/dsearch");
|
||||
if (!hasQuery)
|
||||
return I18n.tr("Type to search files");
|
||||
if (root.controller.searchQuery.length < 2)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
@@ -12,45 +11,8 @@ FloatingWindow {
|
||||
property string passwordInput: ""
|
||||
property var currentFlow: PolkitService.agent?.flow
|
||||
property bool isLoading: false
|
||||
property bool awaitingFprintForPassword: false
|
||||
readonly property int inputFieldHeight: Theme.fontSizeMedium + Theme.spacingL * 2
|
||||
|
||||
property string polkitEtcPamText: ""
|
||||
property string polkitLibPamText: ""
|
||||
property string systemAuthPamText: ""
|
||||
property string commonAuthPamText: ""
|
||||
property string passwordAuthPamText: ""
|
||||
readonly property bool polkitPamHasFprint: {
|
||||
const polkitText = polkitEtcPamText !== "" ? polkitEtcPamText : polkitLibPamText;
|
||||
if (!polkitText)
|
||||
return false;
|
||||
return pamModuleEnabled(polkitText, "pam_fprintd") || (polkitText.includes("system-auth") && pamModuleEnabled(systemAuthPamText, "pam_fprintd")) || (polkitText.includes("common-auth") && pamModuleEnabled(commonAuthPamText, "pam_fprintd")) || (polkitText.includes("password-auth") && pamModuleEnabled(passwordAuthPamText, "pam_fprintd"));
|
||||
}
|
||||
|
||||
function stripPamComment(line) {
|
||||
if (!line)
|
||||
return "";
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith("#"))
|
||||
return "";
|
||||
const hashIdx = trimmed.indexOf("#");
|
||||
if (hashIdx >= 0)
|
||||
return trimmed.substring(0, hashIdx).trim();
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
function pamModuleEnabled(pamText, moduleName) {
|
||||
if (!pamText || !moduleName)
|
||||
return false;
|
||||
const lines = pamText.split(/\r?\n/);
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = stripPamComment(lines[i]);
|
||||
if (line && line.includes(moduleName))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function focusPasswordField() {
|
||||
passwordField.forceActiveFocus();
|
||||
}
|
||||
@@ -58,7 +20,6 @@ FloatingWindow {
|
||||
function show() {
|
||||
passwordInput = "";
|
||||
isLoading = false;
|
||||
awaitingFprintForPassword = false;
|
||||
visible = true;
|
||||
Qt.callLater(focusPasswordField);
|
||||
}
|
||||
@@ -67,27 +28,17 @@ FloatingWindow {
|
||||
visible = false;
|
||||
}
|
||||
|
||||
function _commitSubmit() {
|
||||
isLoading = true;
|
||||
awaitingFprintForPassword = false;
|
||||
currentFlow.submit(passwordInput);
|
||||
passwordInput = "";
|
||||
}
|
||||
|
||||
function submitAuth() {
|
||||
if (!currentFlow || isLoading)
|
||||
return;
|
||||
if (!currentFlow.isResponseRequired) {
|
||||
awaitingFprintForPassword = true;
|
||||
return;
|
||||
}
|
||||
_commitSubmit();
|
||||
isLoading = true;
|
||||
currentFlow.submit(passwordInput);
|
||||
passwordInput = "";
|
||||
}
|
||||
|
||||
function cancelAuth() {
|
||||
if (isLoading)
|
||||
return;
|
||||
awaitingFprintForPassword = false;
|
||||
if (currentFlow) {
|
||||
currentFlow.cancelAuthenticationRequest();
|
||||
return;
|
||||
@@ -109,7 +60,6 @@ FloatingWindow {
|
||||
}
|
||||
passwordInput = "";
|
||||
isLoading = false;
|
||||
awaitingFprintForPassword = false;
|
||||
}
|
||||
|
||||
Connections {
|
||||
@@ -133,11 +83,6 @@ FloatingWindow {
|
||||
function onIsResponseRequiredChanged() {
|
||||
if (!currentFlow.isResponseRequired)
|
||||
return;
|
||||
if (awaitingFprintForPassword && passwordInput !== "") {
|
||||
_commitSubmit();
|
||||
return;
|
||||
}
|
||||
awaitingFprintForPassword = false;
|
||||
isLoading = false;
|
||||
passwordInput = "";
|
||||
passwordField.forceActiveFocus();
|
||||
@@ -156,41 +101,6 @@ FloatingWindow {
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
path: "/etc/pam.d/polkit-1"
|
||||
printErrors: false
|
||||
onLoaded: root.polkitEtcPamText = text()
|
||||
onLoadFailed: root.polkitEtcPamText = ""
|
||||
}
|
||||
|
||||
FileView {
|
||||
path: "/usr/lib/pam.d/polkit-1"
|
||||
printErrors: false
|
||||
onLoaded: root.polkitLibPamText = text()
|
||||
onLoadFailed: root.polkitLibPamText = ""
|
||||
}
|
||||
|
||||
FileView {
|
||||
path: "/etc/pam.d/system-auth"
|
||||
printErrors: false
|
||||
onLoaded: root.systemAuthPamText = text()
|
||||
onLoadFailed: root.systemAuthPamText = ""
|
||||
}
|
||||
|
||||
FileView {
|
||||
path: "/etc/pam.d/common-auth"
|
||||
printErrors: false
|
||||
onLoaded: root.commonAuthPamText = text()
|
||||
onLoadFailed: root.commonAuthPamText = ""
|
||||
}
|
||||
|
||||
FileView {
|
||||
path: "/etc/pam.d/password-auth"
|
||||
printErrors: false
|
||||
onLoaded: root.passwordAuthPamText = text()
|
||||
onLoadFailed: root.passwordAuthPamText = ""
|
||||
}
|
||||
|
||||
FocusScope {
|
||||
id: contentFocusScope
|
||||
|
||||
@@ -295,30 +205,36 @@ FloatingWindow {
|
||||
visible: text !== ""
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: passwordField
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: inputFieldHeight
|
||||
backgroundColor: Theme.surfaceHover
|
||||
normalBorderColor: Theme.outlineStrong
|
||||
focusedBorderColor: Theme.primary
|
||||
borderWidth: 1
|
||||
focusedBorderWidth: 2
|
||||
leftIconName: polkitPamHasFprint ? "fingerprint" : ""
|
||||
leftIconSize: 20
|
||||
leftIconColor: Theme.primary
|
||||
leftIconFocusedColor: Theme.primary
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceHover
|
||||
border.color: passwordField.activeFocus ? Theme.primary : Theme.outlineStrong
|
||||
border.width: passwordField.activeFocus ? 2 : 1
|
||||
opacity: isLoading ? 0.5 : 1
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
textColor: Theme.surfaceText
|
||||
text: passwordInput
|
||||
showPasswordToggle: !(currentFlow?.responseVisible ?? false)
|
||||
echoMode: (currentFlow?.responseVisible ?? false) || passwordVisible ? TextInput.Normal : TextInput.Password
|
||||
placeholderText: ""
|
||||
enabled: !isLoading
|
||||
onTextEdited: passwordInput = text
|
||||
onAccepted: submitAuth()
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: !isLoading
|
||||
onClicked: passwordField.forceActiveFocus()
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: passwordField
|
||||
|
||||
anchors.fill: parent
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
textColor: Theme.surfaceText
|
||||
text: passwordInput
|
||||
showPasswordToggle: !(currentFlow?.responseVisible ?? false)
|
||||
echoMode: (currentFlow?.responseVisible ?? false) || passwordVisible ? TextInput.Normal : TextInput.Password
|
||||
placeholderText: ""
|
||||
backgroundColor: "transparent"
|
||||
enabled: !isLoading
|
||||
onTextEdited: passwordInput = text
|
||||
onAccepted: submitAuth()
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
|
||||
@@ -241,21 +241,6 @@ FocusScope {
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: greeterLoader
|
||||
anchors.fill: parent
|
||||
active: root.currentIndex === 31
|
||||
visible: active
|
||||
focus: active
|
||||
|
||||
sourceComponent: GreeterTab {}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item)
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: pluginsLoader
|
||||
anchors.fill: parent
|
||||
@@ -485,7 +470,7 @@ FocusScope {
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item)
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,7 +485,7 @@ FocusScope {
|
||||
|
||||
onActiveChanged: {
|
||||
if (active && item)
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
Qt.callLater(() => item.forceActiveFocus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,12 +287,6 @@ Rectangle {
|
||||
"icon": "lock",
|
||||
"tabIndex": 11
|
||||
},
|
||||
{
|
||||
"id": "greeter",
|
||||
"text": I18n.tr("Greeter"),
|
||||
"icon": "login",
|
||||
"tabIndex": 31
|
||||
},
|
||||
{
|
||||
"id": "power_sleep",
|
||||
"text": I18n.tr("Power & Sleep"),
|
||||
|
||||
@@ -8,9 +8,6 @@ DankPopout {
|
||||
|
||||
layerNamespace: "dms:app-launcher"
|
||||
|
||||
readonly property real screenWidth: screen?.width ?? 1920
|
||||
readonly property real screenHeight: screen?.height ?? 1080
|
||||
|
||||
property string _pendingMode: ""
|
||||
property string _pendingQuery: ""
|
||||
|
||||
@@ -44,35 +41,8 @@ DankPopout {
|
||||
openWithQuery(query);
|
||||
}
|
||||
|
||||
readonly property int _baseWidth: {
|
||||
switch (SettingsData.dankLauncherV2Size) {
|
||||
case "micro":
|
||||
return 500;
|
||||
case "medium":
|
||||
return 720;
|
||||
case "large":
|
||||
return 860;
|
||||
default:
|
||||
return 620;
|
||||
}
|
||||
}
|
||||
|
||||
readonly property int _baseHeight: {
|
||||
switch (SettingsData.dankLauncherV2Size) {
|
||||
case "micro":
|
||||
return 480;
|
||||
case "medium":
|
||||
return 720;
|
||||
case "large":
|
||||
return 860;
|
||||
default:
|
||||
return 600;
|
||||
}
|
||||
}
|
||||
|
||||
popupWidth: Math.min(_baseWidth, screenWidth - 100)
|
||||
popupHeight: Math.min(_baseHeight, screenHeight - 100)
|
||||
|
||||
popupWidth: 560
|
||||
popupHeight: 640
|
||||
triggerWidth: 40
|
||||
positioning: ""
|
||||
contentHandlesKeys: contentLoader.item?.launcherContent?.editMode ?? false
|
||||
@@ -136,7 +106,7 @@ DankPopout {
|
||||
QtObject {
|
||||
id: modalAdapter
|
||||
property bool spotlightOpen: appDrawerPopout.shouldBeVisible
|
||||
property bool isClosing: false
|
||||
property bool isClosing: appDrawerPopout.isClosing
|
||||
|
||||
function hide() {
|
||||
appDrawerPopout.close();
|
||||
|
||||
@@ -12,7 +12,7 @@ DankPopout {
|
||||
id: root
|
||||
|
||||
layerNamespace: "dms:control-center"
|
||||
fullHeightSurface: true
|
||||
fullHeightSurface: false
|
||||
|
||||
property string expandedSection: ""
|
||||
property var triggerScreen: null
|
||||
@@ -126,9 +126,11 @@ DankPopout {
|
||||
z: 5000
|
||||
|
||||
Behavior on opacity {
|
||||
enabled: !Theme.isDirectionalEffect
|
||||
NumberAnimation {
|
||||
duration: 200
|
||||
easing.type: Easing.OutCubic
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1117,6 +1117,7 @@ Item {
|
||||
if (!notificationCenterLoader.item) {
|
||||
return;
|
||||
}
|
||||
notificationCenterLoader.item.triggerScreen = barWindow.screen;
|
||||
const effectiveBarConfig = topBarContent.barConfig;
|
||||
const barPosition = barWindow.axis?.edge === "left" ? 2 : (barWindow.axis?.edge === "right" ? 3 : (barWindow.axis?.edge === "top" ? 0 : 1));
|
||||
if (notificationCenterLoader.item.setBarContext) {
|
||||
|
||||
@@ -390,11 +390,10 @@ BasePill {
|
||||
anchors.top: parent.top
|
||||
}
|
||||
|
||||
NumericText {
|
||||
StyledText {
|
||||
id: audioPercentV
|
||||
visible: root.showAudioPercent
|
||||
text: Math.round((AudioService.sink?.audio?.volume ?? 0) * 100) + "%"
|
||||
reserveText: "100%"
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||
color: Theme.widgetTextColor
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
@@ -417,11 +416,10 @@ BasePill {
|
||||
anchors.top: parent.top
|
||||
}
|
||||
|
||||
NumericText {
|
||||
StyledText {
|
||||
id: micPercentV
|
||||
visible: root.showMicPercent
|
||||
text: Math.round((AudioService.source?.audio?.volume ?? 0) * 100) + "%"
|
||||
reserveText: "100%"
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||
color: Theme.widgetTextColor
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
@@ -444,11 +442,10 @@ BasePill {
|
||||
anchors.top: parent.top
|
||||
}
|
||||
|
||||
NumericText {
|
||||
StyledText {
|
||||
id: brightnessPercentV
|
||||
visible: root.showBrightnessPercent
|
||||
text: Math.round(getBrightness() * 100) + "%"
|
||||
reserveText: "100%"
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||
color: Theme.widgetTextColor
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
@@ -539,8 +536,7 @@ BasePill {
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: audioIcon.implicitWidth + (root.showAudioPercent ? audioPercent.reservedWidth : 0) + 4
|
||||
implicitWidth: width
|
||||
width: audioIcon.implicitWidth + (root.showAudioPercent ? audioPercent.implicitWidth : 0) + 4
|
||||
height: root.widgetThickness - root.horizontalPadding * 2
|
||||
color: "transparent"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -556,23 +552,20 @@ BasePill {
|
||||
anchors.leftMargin: 2
|
||||
}
|
||||
|
||||
NumericText {
|
||||
StyledText {
|
||||
id: audioPercent
|
||||
visible: root.showAudioPercent
|
||||
text: Math.round((AudioService.sink?.audio?.volume ?? 0) * 100) + "%"
|
||||
reserveText: "100%"
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||
color: Theme.widgetTextColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: audioIcon.right
|
||||
anchors.leftMargin: 2
|
||||
width: reservedWidth
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: micIcon.implicitWidth + (root.showMicPercent ? micPercent.reservedWidth : 0) + 4
|
||||
implicitWidth: width
|
||||
width: micIcon.implicitWidth + (root.showMicPercent ? micPercent.implicitWidth : 0) + 4
|
||||
height: root.widgetThickness - root.horizontalPadding * 2
|
||||
color: "transparent"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -588,22 +581,20 @@ BasePill {
|
||||
anchors.leftMargin: 2
|
||||
}
|
||||
|
||||
NumericText {
|
||||
StyledText {
|
||||
id: micPercent
|
||||
visible: root.showMicPercent
|
||||
text: Math.round((AudioService.source?.audio?.volume ?? 0) * 100) + "%"
|
||||
reserveText: "100%"
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||
color: Theme.widgetTextColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: micIcon.right
|
||||
anchors.leftMargin: 2
|
||||
width: reservedWidth
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: brightnessIcon.implicitWidth + (root.showBrightnessPercent ? brightnessPercent.reservedWidth : 0) + 4
|
||||
width: brightnessIcon.implicitWidth + (root.showBrightnessPercent ? brightnessPercent.implicitWidth : 0) + 4
|
||||
height: root.widgetThickness - root.horizontalPadding * 2
|
||||
color: "transparent"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -619,17 +610,15 @@ BasePill {
|
||||
anchors.leftMargin: 2
|
||||
}
|
||||
|
||||
NumericText {
|
||||
StyledText {
|
||||
id: brightnessPercent
|
||||
visible: root.showBrightnessPercent
|
||||
text: Math.round(getBrightness() * 100) + "%"
|
||||
reserveText: "100%"
|
||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||
color: Theme.widgetTextColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: brightnessIcon.right
|
||||
anchors.leftMargin: 2
|
||||
width: reservedWidth
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -211,17 +211,16 @@ BasePill {
|
||||
text: {
|
||||
const title = activeWindow && activeWindow.title ? activeWindow.title : "";
|
||||
const appName = appText.text;
|
||||
|
||||
if (compactMode && title === appName) {
|
||||
return title;
|
||||
}
|
||||
|
||||
if (!title || !appName) {
|
||||
return title;
|
||||
}
|
||||
|
||||
if (title.endsWith(" - " + appName)) {
|
||||
return title.substring(0, title.length - (" - " + appName).length);
|
||||
}
|
||||
|
||||
if (title.endsWith(appName)) {
|
||||
return title.substring(0, title.length - appName.length).replace(/ (-|—) $/, "");
|
||||
return title.substring(0, title.length - appName.length).replace(/ - $/, "");
|
||||
}
|
||||
|
||||
return title;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Services.SystemTray
|
||||
@@ -161,23 +162,6 @@ BasePill {
|
||||
return 0;
|
||||
}
|
||||
|
||||
readonly property string autoBarShadowDirection: {
|
||||
const edge = root.axis?.edge;
|
||||
switch (edge) {
|
||||
case "top":
|
||||
return "top";
|
||||
case "bottom":
|
||||
return "bottom";
|
||||
case "left":
|
||||
return "left";
|
||||
case "right":
|
||||
return "right";
|
||||
default:
|
||||
return "bottom";
|
||||
}
|
||||
}
|
||||
readonly property string effectiveShadowDirection: Theme.elevationLightDirection === "autoBar" ? autoBarShadowDirection : Theme.elevationLightDirection
|
||||
|
||||
property bool menuOpen: false
|
||||
property var currentTrayMenu: null
|
||||
|
||||
@@ -956,6 +940,13 @@ BasePill {
|
||||
}
|
||||
})(), overflowMenu.dpr)
|
||||
|
||||
readonly property var elev: Theme.elevationLevel2
|
||||
property real shadowBlurPx: elev && elev.blurPx !== undefined ? elev.blurPx : 8
|
||||
property real shadowSpreadPx: elev && elev.spreadPx !== undefined ? elev.spreadPx : 0
|
||||
property real shadowBaseAlpha: elev && elev.alpha !== undefined ? elev.alpha : 0.25
|
||||
readonly property real popupSurfaceAlpha: Theme.popupTransparency
|
||||
readonly property real effectiveShadowAlpha: Math.max(0, Math.min(1, shadowBaseAlpha * popupSurfaceAlpha))
|
||||
|
||||
opacity: root.menuOpen ? 1 : 0
|
||||
scale: root.menuOpen ? 1 : 0.85
|
||||
|
||||
@@ -976,14 +967,19 @@ BasePill {
|
||||
ElevationShadow {
|
||||
id: bgShadowLayer
|
||||
anchors.fill: parent
|
||||
level: Theme.elevationLevel3
|
||||
direction: root.effectiveShadowDirection
|
||||
fallbackOffset: 6
|
||||
level: menuContainer.elev
|
||||
fallbackOffset: 4
|
||||
shadowBlurPx: menuContainer.shadowBlurPx
|
||||
shadowSpreadPx: menuContainer.shadowSpreadPx
|
||||
shadowColor: {
|
||||
const baseColor = Theme.isLightMode ? Qt.rgba(0, 0, 0, 1) : Theme.surfaceContainerHighest;
|
||||
return Theme.withAlpha(baseColor, menuContainer.effectiveShadowAlpha);
|
||||
}
|
||||
targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
targetRadius: Theme.cornerRadius
|
||||
sourceRect.antialiasing: true
|
||||
sourceRect.smooth: true
|
||||
shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled
|
||||
shadowEnabled: Theme.elevationEnabled
|
||||
layer.smooth: true
|
||||
layer.textureSize: Qt.size(Math.round(width * overflowMenu.dpr * 2), Math.round(height * overflowMenu.dpr * 2))
|
||||
layer.textureMirroring: ShaderEffectSource.MirrorVertically
|
||||
@@ -1406,6 +1402,13 @@ BasePill {
|
||||
}
|
||||
})(), menuWindow.dpr)
|
||||
|
||||
readonly property var elev: Theme.elevationLevel2
|
||||
property real shadowBlurPx: elev && elev.blurPx !== undefined ? elev.blurPx : 8
|
||||
property real shadowSpreadPx: elev && elev.spreadPx !== undefined ? elev.spreadPx : 0
|
||||
property real shadowBaseAlpha: elev && elev.alpha !== undefined ? elev.alpha : 0.25
|
||||
readonly property real popupSurfaceAlpha: Theme.popupTransparency
|
||||
readonly property real effectiveShadowAlpha: Math.max(0, Math.min(1, shadowBaseAlpha * popupSurfaceAlpha))
|
||||
|
||||
opacity: menuRoot.showMenu ? 1 : 0
|
||||
scale: menuRoot.showMenu ? 1 : 0.85
|
||||
|
||||
@@ -1426,13 +1429,18 @@ BasePill {
|
||||
ElevationShadow {
|
||||
id: menuBgShadowLayer
|
||||
anchors.fill: parent
|
||||
level: Theme.elevationLevel3
|
||||
direction: root.effectiveShadowDirection
|
||||
fallbackOffset: 6
|
||||
level: menuContainer.elev
|
||||
fallbackOffset: 4
|
||||
shadowBlurPx: menuContainer.shadowBlurPx
|
||||
shadowSpreadPx: menuContainer.shadowSpreadPx
|
||||
shadowColor: {
|
||||
const baseColor = Theme.isLightMode ? Qt.rgba(0, 0, 0, 1) : Theme.surfaceContainerHighest;
|
||||
return Theme.withAlpha(baseColor, menuContainer.effectiveShadowAlpha);
|
||||
}
|
||||
targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
targetRadius: Theme.cornerRadius
|
||||
sourceRect.antialiasing: true
|
||||
shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled
|
||||
shadowEnabled: Theme.elevationEnabled
|
||||
layer.smooth: true
|
||||
layer.textureSize: Qt.size(Math.round(width * menuWindow.dpr), Math.round(height * menuWindow.dpr))
|
||||
layer.textureMirroring: ShaderEffectSource.MirrorVertically
|
||||
|
||||
@@ -1470,10 +1470,6 @@ Item {
|
||||
delegate: Item {
|
||||
width: root.appIconSize
|
||||
height: root.appIconSize
|
||||
readonly property bool appHighlightActive: SettingsData.workspaceActiveAppHighlightEnabled && modelData.active
|
||||
readonly property color appBorderColor: appHighlightActive ? focusedBorderColor : Theme.primarySelected
|
||||
readonly property color appGlyphColor: appHighlightActive ? focusedBorderColor : Theme.primary
|
||||
readonly property real appOpacity: (modelData.active || isActive) ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
|
||||
|
||||
IconImage {
|
||||
id: rowAppIcon
|
||||
@@ -1489,14 +1485,14 @@ Item {
|
||||
color: Theme.surfaceContainer
|
||||
radius: Theme.cornerRadius * (root.appIconSize / 40)
|
||||
border.width: 1
|
||||
border.color: appBorderColor
|
||||
opacity: appOpacity
|
||||
border.color: Theme.primarySelected
|
||||
opacity: (modelData.active || isActive) ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: (modelData.fallbackText || "?").charAt(0).toUpperCase()
|
||||
font.pixelSize: parent.width * 0.45
|
||||
color: appGlyphColor
|
||||
color: Theme.primary
|
||||
font.weight: Font.Bold
|
||||
}
|
||||
}
|
||||
@@ -1507,14 +1503,14 @@ Item {
|
||||
color: Theme.surfaceContainer
|
||||
radius: Theme.cornerRadius * (root.appIconSize / 40)
|
||||
border.width: 1
|
||||
border.color: appBorderColor
|
||||
opacity: appOpacity
|
||||
border.color: Theme.primarySelected
|
||||
opacity: (modelData.active || isActive) ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
size: parent.width * 0.7
|
||||
name: "sports_esports"
|
||||
color: appGlyphColor
|
||||
color: Theme.primary
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1527,12 +1523,11 @@ Item {
|
||||
layer.effect: MultiEffect {
|
||||
saturation: 0
|
||||
colorization: 1
|
||||
colorizationColor: appHighlightActive ? focusedBorderColor : (isActive ? quickshellIconActiveColor : quickshellIconInactiveColor)
|
||||
colorizationColor: isActive ? quickshellIconActiveColor : quickshellIconInactiveColor
|
||||
}
|
||||
}
|
||||
|
||||
IconImage {
|
||||
id: rowSteamIcon
|
||||
anchors.fill: parent
|
||||
source: modelData.icon
|
||||
opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
|
||||
@@ -1543,21 +1538,11 @@ Item {
|
||||
anchors.centerIn: parent
|
||||
size: root.appIconSize
|
||||
name: "sports_esports"
|
||||
color: appHighlightActive ? focusedBorderColor : Theme.widgetTextColor
|
||||
color: Theme.widgetTextColor
|
||||
opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
|
||||
visible: modelData.isSteamApp && !modelData.icon
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: (rowAppIcon.visible || rowSteamIcon.visible || modelData.isQuickshell) && appHighlightActive
|
||||
color: "transparent"
|
||||
radius: Theme.cornerRadius * (root.appIconSize / 40)
|
||||
border.width: 1
|
||||
border.color: focusedBorderColor
|
||||
z: 1
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: rowAppMouseArea
|
||||
anchors.fill: parent
|
||||
@@ -1639,10 +1624,6 @@ Item {
|
||||
delegate: Item {
|
||||
width: root.appIconSize
|
||||
height: root.appIconSize
|
||||
readonly property bool appHighlightActive: SettingsData.workspaceActiveAppHighlightEnabled && modelData.active
|
||||
readonly property color appBorderColor: appHighlightActive ? focusedBorderColor : Theme.primarySelected
|
||||
readonly property color appGlyphColor: appHighlightActive ? focusedBorderColor : Theme.primary
|
||||
readonly property real appOpacity: (modelData.active || isActive) ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
|
||||
|
||||
IconImage {
|
||||
id: colAppIcon
|
||||
@@ -1658,14 +1639,14 @@ Item {
|
||||
color: Theme.surfaceContainer
|
||||
radius: Theme.cornerRadius * (root.appIconSize / 40)
|
||||
border.width: 1
|
||||
border.color: appBorderColor
|
||||
opacity: appOpacity
|
||||
border.color: Theme.primarySelected
|
||||
opacity: (modelData.active || isActive) ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: (modelData.fallbackText || "?").charAt(0).toUpperCase()
|
||||
font.pixelSize: parent.width * 0.45
|
||||
color: appGlyphColor
|
||||
color: Theme.primary
|
||||
font.weight: Font.Bold
|
||||
}
|
||||
}
|
||||
@@ -1676,14 +1657,14 @@ Item {
|
||||
color: Theme.surfaceContainer
|
||||
radius: Theme.cornerRadius * (root.appIconSize / 40)
|
||||
border.width: 1
|
||||
border.color: appBorderColor
|
||||
opacity: appOpacity
|
||||
border.color: Theme.primarySelected
|
||||
opacity: (modelData.active || isActive) ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
size: parent.width * 0.7
|
||||
name: "sports_esports"
|
||||
color: appGlyphColor
|
||||
color: Theme.primary
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1696,12 +1677,11 @@ Item {
|
||||
layer.effect: MultiEffect {
|
||||
saturation: 0
|
||||
colorization: 1
|
||||
colorizationColor: appHighlightActive ? focusedBorderColor : (isActive ? quickshellIconActiveColor : quickshellIconInactiveColor)
|
||||
colorizationColor: isActive ? quickshellIconActiveColor : quickshellIconInactiveColor
|
||||
}
|
||||
}
|
||||
|
||||
IconImage {
|
||||
id: colSteamIcon
|
||||
anchors.fill: parent
|
||||
source: modelData.icon
|
||||
opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
|
||||
@@ -1712,21 +1692,11 @@ Item {
|
||||
anchors.centerIn: parent
|
||||
size: root.appIconSize
|
||||
name: "sports_esports"
|
||||
color: appHighlightActive ? focusedBorderColor : Theme.widgetTextColor
|
||||
color: Theme.widgetTextColor
|
||||
opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
|
||||
visible: modelData.isSteamApp && !modelData.icon
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: (colAppIcon.visible || colSteamIcon.visible || modelData.isQuickshell) && appHighlightActive
|
||||
color: "transparent"
|
||||
radius: Theme.cornerRadius * (root.appIconSize / 40)
|
||||
border.width: 1
|
||||
border.color: focusedBorderColor
|
||||
z: 1
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: colAppMouseArea
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -16,7 +16,6 @@ DankPopout {
|
||||
popupHeight: contentLoader.item ? contentLoader.item.implicitHeight : 500
|
||||
triggerWidth: 80
|
||||
screen: triggerScreen
|
||||
shouldBeVisible: dashVisible
|
||||
|
||||
property bool __focusArmed: false
|
||||
property bool __contentReady: false
|
||||
|
||||
@@ -44,6 +44,43 @@ Item {
|
||||
|
||||
property int __volumeHoverCount: 0
|
||||
|
||||
readonly property bool directionalEffect: Theme.isDirectionalEffect
|
||||
readonly property bool depthEffect: Theme.isDepthEffect
|
||||
|
||||
function panelMotionX(panelWidth, active) {
|
||||
if (active)
|
||||
return 0;
|
||||
if (directionalEffect) {
|
||||
const travel = Math.max(Theme.effectAnimOffset, panelWidth * 0.85);
|
||||
return isRightEdge ? -travel : travel;
|
||||
}
|
||||
if (depthEffect) {
|
||||
const travel = Math.max(Theme.effectAnimOffset * 0.7, panelWidth * 0.32);
|
||||
return isRightEdge ? -travel : travel;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function panelMotionY(panelType, panelHeight, active) {
|
||||
if (active)
|
||||
return 0;
|
||||
if (directionalEffect) {
|
||||
if (panelType === 2)
|
||||
return panelHeight * 0.08;
|
||||
if (panelType === 3)
|
||||
return -panelHeight * 0.08;
|
||||
return 0;
|
||||
}
|
||||
if (depthEffect) {
|
||||
if (panelType === 2)
|
||||
return panelHeight * 0.04;
|
||||
if (panelType === 3)
|
||||
return -panelHeight * 0.04;
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function volumeAreaEntered() {
|
||||
__volumeHoverCount++;
|
||||
panelEntered();
|
||||
@@ -62,30 +99,47 @@ Item {
|
||||
visible: dropdownType === 1 && volumeAvailable
|
||||
width: 60
|
||||
height: 180
|
||||
x: isRightEdge ? anchorPos.x : anchorPos.x - width
|
||||
y: anchorPos.y - height / 2
|
||||
x: (isRightEdge ? anchorPos.x : anchorPos.x - width) + panelMotionX(width, dropdownType === 1)
|
||||
y: anchorPos.y - height / 2 + panelMotionY(1, height, dropdownType === 1)
|
||||
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)
|
||||
border.width: 1
|
||||
|
||||
opacity: dropdownType === 1 ? 1 : 0
|
||||
scale: dropdownType === 1 ? 1 : 0.96
|
||||
opacity: Theme.isDirectionalEffect ? 1 : (dropdownType === 1 ? 1 : 0)
|
||||
scale: Theme.isDirectionalEffect ? 1 : (dropdownType === 1 ? 1 : Theme.effectScaleCollapsed)
|
||||
transformOrigin: isRightEdge ? Item.Left : Item.Right
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
||||
enabled: !Theme.isDirectionalEffect
|
||||
DankAnim {
|
||||
duration: Math.round(Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, dropdownType === 1) * Theme.variantOpacityDurationScale)
|
||||
easing.bezierCurve: dropdownType === 1 ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
enabled: !Theme.isDirectionalEffect
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, dropdownType === 1)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
||||
easing.bezierCurve: dropdownType === 1 ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, dropdownType === 1)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: dropdownType === 1 ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on y {
|
||||
NumberAnimation {
|
||||
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, dropdownType === 1)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: dropdownType === 1 ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,33 +251,50 @@ Item {
|
||||
|
||||
Rectangle {
|
||||
id: audioDevicesPanel
|
||||
visible: dropdownType === 2
|
||||
visible: dropdownType === 2 && activePlayer !== null
|
||||
width: 280
|
||||
height: Math.max(200, Math.min(280, availableDevices.length * 50 + 100))
|
||||
x: isRightEdge ? anchorPos.x : anchorPos.x - width
|
||||
y: anchorPos.y - height / 2
|
||||
x: (isRightEdge ? anchorPos.x : anchorPos.x - width) + panelMotionX(width, dropdownType === 2)
|
||||
y: anchorPos.y - height / 2 + panelMotionY(2, height, dropdownType === 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)
|
||||
border.width: 2
|
||||
|
||||
opacity: dropdownType === 2 ? 1 : 0
|
||||
scale: dropdownType === 2 ? 1 : 0.96
|
||||
opacity: Theme.isDirectionalEffect ? 1 : (dropdownType === 2 ? 1 : 0)
|
||||
scale: Theme.isDirectionalEffect ? 1 : (dropdownType === 2 ? 1 : Theme.effectScaleCollapsed)
|
||||
transformOrigin: isRightEdge ? Item.Left : Item.Right
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
||||
enabled: !Theme.isDirectionalEffect
|
||||
DankAnim {
|
||||
duration: Math.round(Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, dropdownType === 2) * Theme.variantOpacityDurationScale)
|
||||
easing.bezierCurve: dropdownType === 2 ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
enabled: !Theme.isDirectionalEffect
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, dropdownType === 2)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
||||
easing.bezierCurve: dropdownType === 2 ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, dropdownType === 2)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: dropdownType === 2 ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on y {
|
||||
NumberAnimation {
|
||||
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, dropdownType === 2)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: dropdownType === 2 ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,30 +425,47 @@ Item {
|
||||
visible: dropdownType === 3
|
||||
width: 240
|
||||
height: Math.max(180, Math.min(240, (allPlayers?.length || 0) * 50 + 80))
|
||||
x: isRightEdge ? anchorPos.x : anchorPos.x - width
|
||||
y: anchorPos.y - height / 2
|
||||
x: (isRightEdge ? anchorPos.x : anchorPos.x - width) + panelMotionX(width, dropdownType === 3)
|
||||
y: anchorPos.y - height / 2 + panelMotionY(3, height, dropdownType === 3)
|
||||
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)
|
||||
border.width: 2
|
||||
|
||||
opacity: dropdownType === 3 ? 1 : 0
|
||||
scale: dropdownType === 3 ? 1 : 0.96
|
||||
opacity: Theme.isDirectionalEffect ? 1 : (dropdownType === 3 ? 1 : 0)
|
||||
scale: Theme.isDirectionalEffect ? 1 : (dropdownType === 3 ? 1 : Theme.effectScaleCollapsed)
|
||||
transformOrigin: isRightEdge ? Item.Left : Item.Right
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
||||
enabled: !Theme.isDirectionalEffect
|
||||
DankAnim {
|
||||
duration: Math.round(Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, dropdownType === 3) * Theme.variantOpacityDurationScale)
|
||||
easing.bezierCurve: dropdownType === 3 ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
enabled: !Theme.isDirectionalEffect
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, dropdownType === 3)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
||||
easing.bezierCurve: dropdownType === 3 ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, dropdownType === 3)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: dropdownType === 3 ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on y {
|
||||
NumberAnimation {
|
||||
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, dropdownType === 3)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: dropdownType === 3 ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
@@ -134,40 +133,6 @@ Item {
|
||||
function getGroupedToplevels() {
|
||||
return appData?.allWindows?.map(w => w.toplevel).filter(t => t !== null) || [];
|
||||
}
|
||||
|
||||
function getHyprToplevelForWayland(waylandToplevel) {
|
||||
if (!waylandToplevel || !CompositorService.isHyprland || !Hyprland.toplevels)
|
||||
return null;
|
||||
const hyprToplevels = Array.from(Hyprland.toplevels.values);
|
||||
for (let i = 0; i < hyprToplevels.length; i++) {
|
||||
if (hyprToplevels[i].wayland === waylandToplevel)
|
||||
return hyprToplevels[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getSpecialWorkspaceName(waylandToplevel) {
|
||||
const hyprToplevel = getHyprToplevelForWayland(waylandToplevel);
|
||||
if (!hyprToplevel)
|
||||
return "";
|
||||
const wsName = String(hyprToplevel.lastIpcObject?.workspace?.name || hyprToplevel.workspace?.name || "");
|
||||
if (!wsName.startsWith("special:"))
|
||||
return "";
|
||||
return wsName.slice("special:".length);
|
||||
}
|
||||
|
||||
function restoreSpecialWorkspaceWindow(waylandToplevel) {
|
||||
if (!SettingsData.dockRestoreSpecialWorkspaceOnClick || !CompositorService.isHyprland || !waylandToplevel)
|
||||
return false;
|
||||
|
||||
const specialName = getSpecialWorkspaceName(waylandToplevel);
|
||||
if (!specialName)
|
||||
return false;
|
||||
|
||||
Hyprland.dispatch("togglespecialworkspace " + specialName);
|
||||
Qt.callLater(() => waylandToplevel.activate());
|
||||
return true;
|
||||
}
|
||||
onIsHoveredChanged: {
|
||||
if (mouseArea.pressed || dragging)
|
||||
return;
|
||||
@@ -311,11 +276,8 @@ Item {
|
||||
break;
|
||||
case "window":
|
||||
const windowToplevel = getToplevelObject();
|
||||
if (windowToplevel) {
|
||||
if (restoreSpecialWorkspaceWindow(windowToplevel))
|
||||
return;
|
||||
if (windowToplevel)
|
||||
windowToplevel.activate();
|
||||
}
|
||||
break;
|
||||
case "grouped":
|
||||
if (appData.windowCount === 0) {
|
||||
@@ -338,11 +300,8 @@ Item {
|
||||
SessionService.launchDesktopEntry(groupedEntry);
|
||||
} else if (appData.windowCount === 1) {
|
||||
const groupedToplevel = getToplevelObject();
|
||||
if (groupedToplevel) {
|
||||
if (restoreSpecialWorkspaceWindow(groupedToplevel))
|
||||
return;
|
||||
if (groupedToplevel)
|
||||
groupedToplevel.activate();
|
||||
}
|
||||
} else if (contextMenu) {
|
||||
const shouldHidePin = appData.appId === "org.quickshell";
|
||||
contextMenu.showForButton(root, appData, root.height + 25, shouldHidePin, cachedDesktopEntry, parentDockScreen, dockApps);
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
.pragma library
|
||||
|
||||
function readBoolOverride(envReader, names, fallbackValue) {
|
||||
for (let i = 0; i < names.length; i++) {
|
||||
const name = names[i];
|
||||
const raw = envReader(name);
|
||||
if (raw === undefined || raw === null || raw === "")
|
||||
continue;
|
||||
|
||||
const normalized = String(raw).trim().toLowerCase();
|
||||
if (normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on")
|
||||
return true;
|
||||
if (normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off")
|
||||
return false;
|
||||
|
||||
console.warn("Invalid boolean override for", name + ":", raw, "- trying next override/fallback");
|
||||
}
|
||||
|
||||
return fallbackValue;
|
||||
}
|
||||
@@ -4,16 +4,13 @@ pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import "GreetdEnv.js" as GreetdEnv
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property string greetCfgDir: Quickshell.env("DMS_GREET_CFG_DIR") || "/var/cache/dms-greeter"
|
||||
readonly property string greetCfgDir: Quickshell.env("DMS_GREET_CFG_DIR") || "/etc/greetd/.dms"
|
||||
readonly property string sessionConfigPath: greetCfgDir + "/session.json"
|
||||
readonly property string memoryFile: greetCfgDir + "/.local/state/memory.json"
|
||||
readonly property bool rememberLastSession: GreetdEnv.readBoolOverride(Quickshell.env, ["DMS_GREET_REMEMBER_LAST_SESSION", "DMS_SAVE_SESSION"], true)
|
||||
readonly property bool rememberLastUser: GreetdEnv.readBoolOverride(Quickshell.env, ["DMS_GREET_REMEMBER_LAST_USER", "DMS_SAVE_USERNAME"], true)
|
||||
readonly property string memoryFile: greetCfgDir + "/memory.json"
|
||||
|
||||
property string lastSessionId: ""
|
||||
property string lastSuccessfulUser: ""
|
||||
@@ -52,44 +49,26 @@ Singleton {
|
||||
if (!content || !content.trim())
|
||||
return;
|
||||
const memory = JSON.parse(content);
|
||||
lastSessionId = rememberLastSession ? (memory.lastSessionId || "") : "";
|
||||
lastSuccessfulUser = rememberLastUser ? (memory.lastSuccessfulUser || "") : "";
|
||||
if (!rememberLastSession || !rememberLastUser)
|
||||
saveMemory();
|
||||
lastSessionId = memory.lastSessionId || "";
|
||||
lastSuccessfulUser = memory.lastSuccessfulUser || "";
|
||||
} catch (e) {
|
||||
console.warn("Failed to parse greetd memory:", e);
|
||||
}
|
||||
}
|
||||
|
||||
function saveMemory() {
|
||||
let memory = {};
|
||||
if (rememberLastSession && lastSessionId)
|
||||
memory.lastSessionId = lastSessionId;
|
||||
if (rememberLastUser && lastSuccessfulUser)
|
||||
memory.lastSuccessfulUser = lastSuccessfulUser;
|
||||
memoryFileView.setText(JSON.stringify(memory, null, 2));
|
||||
memoryFileView.setText(JSON.stringify({
|
||||
"lastSessionId": lastSessionId,
|
||||
"lastSuccessfulUser": lastSuccessfulUser
|
||||
}, null, 2));
|
||||
}
|
||||
|
||||
function setLastSessionId(id) {
|
||||
if (!rememberLastSession) {
|
||||
if (lastSessionId !== "") {
|
||||
lastSessionId = "";
|
||||
saveMemory();
|
||||
}
|
||||
return;
|
||||
}
|
||||
lastSessionId = id || "";
|
||||
saveMemory();
|
||||
}
|
||||
|
||||
function setLastSuccessfulUser(username) {
|
||||
if (!rememberLastUser) {
|
||||
if (lastSuccessfulUser !== "") {
|
||||
lastSuccessfulUser = "";
|
||||
saveMemory();
|
||||
}
|
||||
return;
|
||||
}
|
||||
lastSuccessfulUser = username || "";
|
||||
saveMemory();
|
||||
}
|
||||
|
||||
@@ -5,22 +5,15 @@ import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import "GreetdEnv.js" as GreetdEnv
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property string configPath: {
|
||||
const greetCfgDir = Quickshell.env("DMS_GREET_CFG_DIR") || "/var/cache/dms-greeter";
|
||||
const greetCfgDir = Quickshell.env("DMS_GREET_CFG_DIR") || "/etc/greetd/.dms";
|
||||
return greetCfgDir + "/settings.json";
|
||||
}
|
||||
|
||||
readonly property string _greeterCacheDir: {
|
||||
const i = root.configPath.lastIndexOf("/");
|
||||
return i >= 0 ? root.configPath.substring(0, i) : "";
|
||||
}
|
||||
readonly property string greeterWallpaperOverridePath: root._greeterCacheDir ? (root._greeterCacheDir + "/greeter_wallpaper_override.jpg") : ""
|
||||
|
||||
property string currentThemeName: "purple"
|
||||
property bool settingsLoaded: false
|
||||
property string customThemeFile: ""
|
||||
@@ -28,12 +21,6 @@ Singleton {
|
||||
property bool use24HourClock: true
|
||||
property bool showSeconds: false
|
||||
property bool padHours12Hour: false
|
||||
property bool greeterUse24HourClock: true
|
||||
property bool greeterShowSeconds: false
|
||||
property bool greeterPadHours12Hour: false
|
||||
property string greeterLockDateFormat: ""
|
||||
property string greeterFontFamily: ""
|
||||
property string greeterWallpaperFillMode: ""
|
||||
property bool useFahrenheit: false
|
||||
property bool nightModeEnabled: false
|
||||
property string weatherLocation: "New York, NY"
|
||||
@@ -54,11 +41,6 @@ Singleton {
|
||||
property string lockDateFormat: ""
|
||||
property bool lockScreenShowPowerActions: true
|
||||
property bool lockScreenShowProfileImage: true
|
||||
property bool rememberLastSession: true
|
||||
property bool rememberLastUser: true
|
||||
property bool greeterEnableFprint: false
|
||||
property bool greeterEnableU2f: false
|
||||
property string greeterWallpaperPath: ""
|
||||
property bool powerActionConfirm: true
|
||||
property real powerActionHoldDuration: 0.5
|
||||
property var powerMenuActions: ["reboot", "logout", "poweroff", "lock", "suspend", "restart"]
|
||||
@@ -70,103 +52,66 @@ Singleton {
|
||||
|
||||
function parseSettings(content) {
|
||||
try {
|
||||
let settings = {};
|
||||
if (content && content.trim()) {
|
||||
settings = JSON.parse(content);
|
||||
}
|
||||
const settings = JSON.parse(content);
|
||||
currentThemeName = settings.currentThemeName !== undefined ? settings.currentThemeName : "purple";
|
||||
customThemeFile = settings.customThemeFile !== undefined ? settings.customThemeFile : "";
|
||||
matugenScheme = settings.matugenScheme !== undefined ? settings.matugenScheme : "scheme-tonal-spot";
|
||||
use24HourClock = settings.use24HourClock !== undefined ? settings.use24HourClock : true;
|
||||
showSeconds = settings.showSeconds !== undefined ? settings.showSeconds : false;
|
||||
padHours12Hour = settings.padHours12Hour !== undefined ? settings.padHours12Hour : false;
|
||||
useFahrenheit = settings.useFahrenheit !== undefined ? settings.useFahrenheit : false;
|
||||
nightModeEnabled = settings.nightModeEnabled !== undefined ? settings.nightModeEnabled : false;
|
||||
weatherLocation = settings.weatherLocation !== undefined ? settings.weatherLocation : "New York, NY";
|
||||
weatherCoordinates = settings.weatherCoordinates !== undefined ? settings.weatherCoordinates : "40.7128,-74.0060";
|
||||
useAutoLocation = settings.useAutoLocation !== undefined ? settings.useAutoLocation : false;
|
||||
weatherEnabled = settings.weatherEnabled !== undefined ? settings.weatherEnabled : true;
|
||||
iconTheme = settings.iconTheme !== undefined ? settings.iconTheme : "System Default";
|
||||
useOSLogo = settings.useOSLogo !== undefined ? settings.useOSLogo : false;
|
||||
osLogoColorOverride = settings.osLogoColorOverride !== undefined ? settings.osLogoColorOverride : "";
|
||||
osLogoBrightness = settings.osLogoBrightness !== undefined ? settings.osLogoBrightness : 0.5;
|
||||
osLogoContrast = settings.osLogoContrast !== undefined ? settings.osLogoContrast : 1;
|
||||
fontFamily = settings.fontFamily !== undefined ? settings.fontFamily : Theme.defaultFontFamily;
|
||||
monoFontFamily = settings.monoFontFamily !== undefined ? settings.monoFontFamily : Theme.defaultMonoFontFamily;
|
||||
fontWeight = settings.fontWeight !== undefined ? settings.fontWeight : Font.Normal;
|
||||
fontScale = settings.fontScale !== undefined ? settings.fontScale : 1.0;
|
||||
cornerRadius = settings.cornerRadius !== undefined ? settings.cornerRadius : 12;
|
||||
widgetBackgroundColor = settings.widgetBackgroundColor !== undefined ? settings.widgetBackgroundColor : "sch";
|
||||
lockDateFormat = settings.lockDateFormat !== undefined ? settings.lockDateFormat : "";
|
||||
lockScreenShowPowerActions = settings.lockScreenShowPowerActions !== undefined ? settings.lockScreenShowPowerActions : true;
|
||||
lockScreenShowProfileImage = settings.lockScreenShowProfileImage !== undefined ? settings.lockScreenShowProfileImage : true;
|
||||
powerActionConfirm = settings.powerActionConfirm !== undefined ? settings.powerActionConfirm : true;
|
||||
powerActionHoldDuration = settings.powerActionHoldDuration !== undefined ? settings.powerActionHoldDuration : 0.5;
|
||||
powerMenuActions = settings.powerMenuActions !== undefined ? settings.powerMenuActions : ["reboot", "logout", "poweroff", "lock", "suspend", "restart"];
|
||||
powerMenuDefaultAction = settings.powerMenuDefaultAction !== undefined ? settings.powerMenuDefaultAction : "logout";
|
||||
powerMenuGridLayout = settings.powerMenuGridLayout !== undefined ? settings.powerMenuGridLayout : false;
|
||||
screenPreferences = settings.screenPreferences !== undefined ? settings.screenPreferences : ({});
|
||||
animationSpeed = settings.animationSpeed !== undefined ? settings.animationSpeed : 2;
|
||||
wallpaperFillMode = settings.wallpaperFillMode !== undefined ? settings.wallpaperFillMode : "Fill";
|
||||
settingsLoaded = true;
|
||||
|
||||
const envRememberLastSession = GreetdEnv.readBoolOverride(Quickshell.env, ["DMS_GREET_REMEMBER_LAST_SESSION", "DMS_SAVE_SESSION"], undefined);
|
||||
const envRememberLastUser = GreetdEnv.readBoolOverride(Quickshell.env, ["DMS_GREET_REMEMBER_LAST_USER", "DMS_SAVE_USERNAME"], undefined);
|
||||
|
||||
currentThemeName = settings.currentThemeName !== undefined ? settings.currentThemeName : "purple";
|
||||
customThemeFile = settings.customThemeFile !== undefined ? settings.customThemeFile : "";
|
||||
matugenScheme = settings.matugenScheme !== undefined ? settings.matugenScheme : "scheme-tonal-spot";
|
||||
use24HourClock = settings.use24HourClock !== undefined ? settings.use24HourClock : true;
|
||||
showSeconds = settings.showSeconds !== undefined ? settings.showSeconds : false;
|
||||
padHours12Hour = settings.padHours12Hour !== undefined ? settings.padHours12Hour : false;
|
||||
greeterUse24HourClock = settings.greeterUse24HourClock !== undefined ? settings.greeterUse24HourClock : use24HourClock;
|
||||
greeterShowSeconds = settings.greeterShowSeconds !== undefined ? settings.greeterShowSeconds : showSeconds;
|
||||
greeterPadHours12Hour = settings.greeterPadHours12Hour !== undefined ? settings.greeterPadHours12Hour : padHours12Hour;
|
||||
greeterLockDateFormat = settings.greeterLockDateFormat !== undefined ? settings.greeterLockDateFormat : "";
|
||||
greeterFontFamily = settings.greeterFontFamily !== undefined ? settings.greeterFontFamily : "";
|
||||
greeterWallpaperFillMode = settings.greeterWallpaperFillMode !== undefined ? settings.greeterWallpaperFillMode : "";
|
||||
useFahrenheit = settings.useFahrenheit !== undefined ? settings.useFahrenheit : false;
|
||||
nightModeEnabled = settings.nightModeEnabled !== undefined ? settings.nightModeEnabled : false;
|
||||
weatherLocation = settings.weatherLocation !== undefined ? settings.weatherLocation : "New York, NY";
|
||||
weatherCoordinates = settings.weatherCoordinates !== undefined ? settings.weatherCoordinates : "40.7128,-74.0060";
|
||||
useAutoLocation = settings.useAutoLocation !== undefined ? settings.useAutoLocation : false;
|
||||
weatherEnabled = settings.weatherEnabled !== undefined ? settings.weatherEnabled : true;
|
||||
iconTheme = settings.iconTheme !== undefined ? settings.iconTheme : "System Default";
|
||||
useOSLogo = settings.useOSLogo !== undefined ? settings.useOSLogo : false;
|
||||
osLogoColorOverride = settings.osLogoColorOverride !== undefined ? settings.osLogoColorOverride : "";
|
||||
osLogoBrightness = settings.osLogoBrightness !== undefined ? settings.osLogoBrightness : 0.5;
|
||||
osLogoContrast = settings.osLogoContrast !== undefined ? settings.osLogoContrast : 1;
|
||||
fontFamily = settings.fontFamily !== undefined ? settings.fontFamily : Theme.defaultFontFamily;
|
||||
monoFontFamily = settings.monoFontFamily !== undefined ? settings.monoFontFamily : Theme.defaultMonoFontFamily;
|
||||
fontWeight = settings.fontWeight !== undefined ? settings.fontWeight : Font.Normal;
|
||||
fontScale = settings.fontScale !== undefined ? settings.fontScale : 1.0;
|
||||
cornerRadius = settings.cornerRadius !== undefined ? settings.cornerRadius : 12;
|
||||
widgetBackgroundColor = settings.widgetBackgroundColor !== undefined ? settings.widgetBackgroundColor : "sch";
|
||||
lockDateFormat = settings.lockDateFormat !== undefined ? settings.lockDateFormat : "";
|
||||
lockScreenShowPowerActions = settings.lockScreenShowPowerActions !== undefined ? settings.lockScreenShowPowerActions : true;
|
||||
lockScreenShowProfileImage = settings.lockScreenShowProfileImage !== undefined ? settings.lockScreenShowProfileImage : true;
|
||||
if (envRememberLastSession !== undefined) {
|
||||
rememberLastSession = envRememberLastSession;
|
||||
} else {
|
||||
rememberLastSession = settings.greeterRememberLastSession !== undefined ? settings.greeterRememberLastSession : settings.rememberLastSession !== undefined ? settings.rememberLastSession : true;
|
||||
}
|
||||
if (envRememberLastUser !== undefined) {
|
||||
rememberLastUser = envRememberLastUser;
|
||||
} else {
|
||||
rememberLastUser = settings.greeterRememberLastUser !== undefined ? settings.greeterRememberLastUser : settings.rememberLastUser !== undefined ? settings.rememberLastUser : true;
|
||||
}
|
||||
greeterEnableFprint = settings.greeterEnableFprint !== undefined ? settings.greeterEnableFprint : false;
|
||||
greeterEnableU2f = settings.greeterEnableU2f !== undefined ? settings.greeterEnableU2f : false;
|
||||
greeterWallpaperPath = settings.greeterWallpaperPath !== undefined ? settings.greeterWallpaperPath : "";
|
||||
powerActionConfirm = settings.powerActionConfirm !== undefined ? settings.powerActionConfirm : true;
|
||||
powerActionHoldDuration = settings.powerActionHoldDuration !== undefined ? settings.powerActionHoldDuration : 0.5;
|
||||
powerMenuActions = settings.powerMenuActions !== undefined ? settings.powerMenuActions : ["reboot", "logout", "poweroff", "lock", "suspend", "restart"];
|
||||
powerMenuDefaultAction = settings.powerMenuDefaultAction !== undefined ? settings.powerMenuDefaultAction : "logout";
|
||||
powerMenuGridLayout = settings.powerMenuGridLayout !== undefined ? settings.powerMenuGridLayout : false;
|
||||
screenPreferences = settings.screenPreferences !== undefined ? settings.screenPreferences : ({});
|
||||
animationSpeed = settings.animationSpeed !== undefined ? settings.animationSpeed : 2;
|
||||
wallpaperFillMode = settings.wallpaperFillMode !== undefined ? settings.wallpaperFillMode : "Fill";
|
||||
|
||||
if (typeof Theme !== "undefined") {
|
||||
if (currentThemeName === "custom" && customThemeFile) {
|
||||
Theme.loadCustomThemeFromFile(customThemeFile);
|
||||
if (typeof Theme !== "undefined") {
|
||||
if (currentThemeName === "custom" && customThemeFile) {
|
||||
Theme.loadCustomThemeFromFile(customThemeFile);
|
||||
}
|
||||
Theme.applyGreeterTheme(currentThemeName);
|
||||
}
|
||||
Theme.applyGreeterTheme(currentThemeName);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Failed to parse greetd settings:", e);
|
||||
} finally {
|
||||
settingsLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
function getEffectiveTimeFormat() {
|
||||
const use24 = greeterUse24HourClock;
|
||||
const secs = greeterShowSeconds;
|
||||
const pad = greeterPadHours12Hour;
|
||||
if (use24)
|
||||
return secs ? "hh:mm:ss" : "hh:mm";
|
||||
if (pad)
|
||||
return secs ? "hh:mm:ss AP" : "hh:mm AP";
|
||||
return secs ? "h:mm:ss AP" : "h:mm AP";
|
||||
if (use24HourClock)
|
||||
return showSeconds ? "hh:mm:ss" : "hh:mm";
|
||||
if (padHours12Hour)
|
||||
return showSeconds ? "hh:mm:ss AP" : "hh:mm AP";
|
||||
return showSeconds ? "h:mm:ss AP" : "h:mm AP";
|
||||
}
|
||||
|
||||
function getEffectiveLockDateFormat() {
|
||||
const fmt = (greeterLockDateFormat !== undefined && greeterLockDateFormat !== "") ? greeterLockDateFormat : lockDateFormat;
|
||||
return fmt && fmt.length > 0 ? fmt : Locale.LongFormat;
|
||||
}
|
||||
|
||||
function getEffectiveWallpaperFillMode() {
|
||||
return (greeterWallpaperFillMode && greeterWallpaperFillMode !== "") ? greeterWallpaperFillMode : wallpaperFillMode;
|
||||
}
|
||||
|
||||
function getEffectiveFontFamily() {
|
||||
return (greeterFontFamily && greeterFontFamily !== "") ? greeterFontFamily : fontFamily;
|
||||
return lockDateFormat && lockDateFormat.length > 0 ? lockDateFormat : Locale.LongFormat;
|
||||
}
|
||||
|
||||
function getFilteredScreens(componentId) {
|
||||
@@ -188,9 +133,5 @@ Singleton {
|
||||
onLoaded: {
|
||||
parseSettings(settingsFile.text());
|
||||
}
|
||||
onLoadFailed: error => {
|
||||
console.warn("Failed to load greetd settings:", error);
|
||||
root.parseSettings("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,31 +31,6 @@ Item {
|
||||
signal launchRequested
|
||||
|
||||
property bool weatherInitialized: false
|
||||
property bool awaitingExternalAuth: false
|
||||
property bool pendingPasswordResponse: false
|
||||
property bool passwordSubmitRequested: false
|
||||
property bool cancelingExternalAuthForPassword: false
|
||||
property int defaultAuthTimeoutMs: 10000
|
||||
property int externalAuthTimeoutMs: 36000
|
||||
property int memoryFlushDelayMs: 120
|
||||
property string pendingLaunchCommand: ""
|
||||
property var pendingLaunchEnv: []
|
||||
property int passwordFailureCount: 0
|
||||
property int passwordAttemptLimitHint: 0
|
||||
property string authFeedbackMessage: ""
|
||||
property string greetdPamText: ""
|
||||
property string systemAuthPamText: ""
|
||||
property string commonAuthPamText: ""
|
||||
property string passwordAuthPamText: ""
|
||||
property string faillockConfigText: ""
|
||||
property bool greeterWallpaperOverrideExists: false
|
||||
property string externalAuthAutoStartedForUser: ""
|
||||
property int passwordSessionTransitionRetryCount: 0
|
||||
property int maxPasswordSessionTransitionRetries: 2
|
||||
readonly property bool greeterPamHasFprint: pamModuleEnabled(greetdPamText, "pam_fprintd") || (greetdPamText.includes("system-auth") && pamModuleEnabled(systemAuthPamText, "pam_fprintd")) || (greetdPamText.includes("common-auth") && pamModuleEnabled(commonAuthPamText, "pam_fprintd")) || (greetdPamText.includes("password-auth") && pamModuleEnabled(passwordAuthPamText, "pam_fprintd"))
|
||||
readonly property bool greeterPamHasU2f: pamModuleEnabled(greetdPamText, "pam_u2f") || (greetdPamText.includes("system-auth") && pamModuleEnabled(systemAuthPamText, "pam_u2f")) || (greetdPamText.includes("common-auth") && pamModuleEnabled(commonAuthPamText, "pam_u2f")) || (greetdPamText.includes("password-auth") && pamModuleEnabled(passwordAuthPamText, "pam_u2f"))
|
||||
readonly property bool greeterExternalAuthAvailable: (greeterPamHasFprint && GreetdSettings.greeterEnableFprint) || (greeterPamHasU2f && GreetdSettings.greeterEnableU2f)
|
||||
readonly property bool greeterPamHasExternalAuth: greeterPamHasFprint || greeterPamHasU2f
|
||||
|
||||
function initWeatherService() {
|
||||
if (weatherInitialized)
|
||||
@@ -69,260 +44,16 @@ Item {
|
||||
WeatherService.forceRefresh();
|
||||
}
|
||||
|
||||
function stripPamComment(line) {
|
||||
if (!line)
|
||||
return "";
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith("#"))
|
||||
return "";
|
||||
const hashIdx = trimmed.indexOf("#");
|
||||
if (hashIdx >= 0)
|
||||
return trimmed.substring(0, hashIdx).trim();
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
function pamModuleEnabled(pamText, moduleName) {
|
||||
if (!pamText || !moduleName)
|
||||
return false;
|
||||
const lines = pamText.split(/\r?\n/);
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = stripPamComment(lines[i]);
|
||||
if (!line)
|
||||
continue;
|
||||
if (line.includes(moduleName))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function usesPamLockoutPolicy(pamText) {
|
||||
if (!pamText)
|
||||
return false;
|
||||
const lines = pamText.split(/\r?\n/);
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = stripPamComment(lines[i]);
|
||||
if (!line)
|
||||
continue;
|
||||
if (line.includes("pam_faillock.so") || line.includes("pam_tally2.so") || line.includes("pam_tally.so"))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function parsePamLineDenyValue(pamText) {
|
||||
if (!pamText)
|
||||
return -1;
|
||||
const lines = pamText.split(/\r?\n/);
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = stripPamComment(lines[i]);
|
||||
if (!line)
|
||||
continue;
|
||||
if (!line.includes("pam_faillock.so") && !line.includes("pam_tally2.so") && !line.includes("pam_tally.so"))
|
||||
continue;
|
||||
const denyMatch = line.match(/\bdeny\s*=\s*(\d+)\b/i);
|
||||
if (!denyMatch)
|
||||
continue;
|
||||
const parsed = parseInt(denyMatch[1], 10);
|
||||
if (!isNaN(parsed))
|
||||
return parsed;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
function parseFaillockDenyValue(configText) {
|
||||
if (!configText)
|
||||
return -1;
|
||||
const lines = configText.split(/\r?\n/);
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = stripPamComment(lines[i]);
|
||||
if (!line)
|
||||
continue;
|
||||
const denyMatch = line.match(/^deny\s*=\s*(\d+)\s*$/i);
|
||||
if (!denyMatch)
|
||||
continue;
|
||||
const parsed = parseInt(denyMatch[1], 10);
|
||||
if (!isNaN(parsed))
|
||||
return parsed;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
function refreshPasswordAttemptPolicyHint() {
|
||||
const pamSources = [greetdPamText, systemAuthPamText, commonAuthPamText, passwordAuthPamText];
|
||||
let lockoutConfigured = false;
|
||||
let denyFromPam = -1;
|
||||
for (let i = 0; i < pamSources.length; i++) {
|
||||
const source = pamSources[i];
|
||||
if (!source)
|
||||
continue;
|
||||
if (usesPamLockoutPolicy(source))
|
||||
lockoutConfigured = true;
|
||||
const denyValue = parsePamLineDenyValue(source);
|
||||
if (denyValue >= 0 && (denyFromPam < 0 || denyValue < denyFromPam))
|
||||
denyFromPam = denyValue;
|
||||
}
|
||||
|
||||
if (!lockoutConfigured) {
|
||||
passwordAttemptLimitHint = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const denyFromConfig = parseFaillockDenyValue(faillockConfigText);
|
||||
if (denyFromConfig >= 0) {
|
||||
passwordAttemptLimitHint = denyFromConfig;
|
||||
return;
|
||||
}
|
||||
|
||||
if (denyFromPam >= 0) {
|
||||
passwordAttemptLimitHint = denyFromPam;
|
||||
return;
|
||||
}
|
||||
|
||||
// pam_faillock default deny value when no explicit config is set.
|
||||
passwordAttemptLimitHint = 3;
|
||||
}
|
||||
|
||||
function isLikelyLockoutMessage(message) {
|
||||
const lower = (message || "").toLowerCase();
|
||||
return lower.includes("account is locked") || lower.includes("too many") || lower.includes("maximum number of") || lower.includes("auth_err");
|
||||
}
|
||||
|
||||
function currentAuthMessage() {
|
||||
if (GreeterState.pamState === "error")
|
||||
return "Authentication error - try again";
|
||||
if (GreeterState.pamState === "max")
|
||||
return "Too many failed attempts - account may be locked";
|
||||
if (GreeterState.pamState === "fail") {
|
||||
if (passwordAttemptLimitHint > 0) {
|
||||
const attempt = Math.max(1, Math.min(passwordFailureCount, passwordAttemptLimitHint));
|
||||
const remaining = Math.max(passwordAttemptLimitHint - attempt, 0);
|
||||
if (remaining > 0) {
|
||||
return "Incorrect password - attempt " + attempt + " of " + passwordAttemptLimitHint + " (lockout may follow)";
|
||||
}
|
||||
return "Incorrect password - next failures may trigger account lockout";
|
||||
}
|
||||
return "Incorrect password";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function clearAuthFeedback() {
|
||||
GreeterState.pamState = "";
|
||||
authFeedbackMessage = "";
|
||||
}
|
||||
|
||||
function resetPasswordSessionTransition(clearSubmitRequest) {
|
||||
cancelingExternalAuthForPassword = false;
|
||||
passwordSessionTransitionRetryCount = 0;
|
||||
if (clearSubmitRequest)
|
||||
passwordSubmitRequested = false;
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: GreetdSettings
|
||||
function onSettingsLoadedChanged() {
|
||||
if (GreetdSettings.settingsLoaded) {
|
||||
if (GreetdSettings.settingsLoaded)
|
||||
initWeatherService();
|
||||
if (isPrimaryScreen) {
|
||||
applyLastSuccessfulUser();
|
||||
finalizeSessionSelection();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onRememberLastUserChanged() {
|
||||
if (!isPrimaryScreen)
|
||||
return;
|
||||
if (!GreetdSettings.rememberLastUser && GreetdMemory.lastSuccessfulUser) {
|
||||
GreetdMemory.setLastSuccessfulUser("");
|
||||
}
|
||||
applyLastSuccessfulUser();
|
||||
}
|
||||
|
||||
function onRememberLastSessionChanged() {
|
||||
if (!isPrimaryScreen)
|
||||
return;
|
||||
if (!GreetdSettings.rememberLastSession && GreetdMemory.lastSessionId) {
|
||||
GreetdMemory.setLastSessionId("");
|
||||
}
|
||||
finalizeSessionSelection();
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: greetdPamWatcher
|
||||
path: "/etc/pam.d/greetd"
|
||||
printErrors: false
|
||||
onLoaded: {
|
||||
root.greetdPamText = text();
|
||||
root.refreshPasswordAttemptPolicyHint();
|
||||
root.maybeAutoStartExternalAuth();
|
||||
}
|
||||
onLoadFailed: {
|
||||
root.greetdPamText = "";
|
||||
root.refreshPasswordAttemptPolicyHint();
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: systemAuthPamWatcher
|
||||
path: "/etc/pam.d/system-auth"
|
||||
printErrors: false
|
||||
onLoaded: {
|
||||
root.systemAuthPamText = text();
|
||||
root.refreshPasswordAttemptPolicyHint();
|
||||
}
|
||||
onLoadFailed: {
|
||||
root.systemAuthPamText = "";
|
||||
root.refreshPasswordAttemptPolicyHint();
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: commonAuthPamWatcher
|
||||
path: "/etc/pam.d/common-auth"
|
||||
printErrors: false
|
||||
onLoaded: {
|
||||
root.commonAuthPamText = text();
|
||||
root.refreshPasswordAttemptPolicyHint();
|
||||
}
|
||||
onLoadFailed: {
|
||||
root.commonAuthPamText = "";
|
||||
root.refreshPasswordAttemptPolicyHint();
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: passwordAuthPamWatcher
|
||||
path: "/etc/pam.d/password-auth"
|
||||
printErrors: false
|
||||
onLoaded: {
|
||||
root.passwordAuthPamText = text();
|
||||
root.refreshPasswordAttemptPolicyHint();
|
||||
}
|
||||
onLoadFailed: {
|
||||
root.passwordAuthPamText = "";
|
||||
root.refreshPasswordAttemptPolicyHint();
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: faillockConfigWatcher
|
||||
path: "/etc/security/faillock.conf"
|
||||
printErrors: false
|
||||
onLoaded: {
|
||||
root.faillockConfigText = text();
|
||||
root.refreshPasswordAttemptPolicyHint();
|
||||
}
|
||||
onLoadFailed: {
|
||||
root.faillockConfigText = "";
|
||||
root.refreshPasswordAttemptPolicyHint();
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
initWeatherService();
|
||||
refreshPasswordAttemptPolicyHint();
|
||||
|
||||
if (isPrimaryScreen)
|
||||
applyLastSuccessfulUser();
|
||||
@@ -332,131 +63,15 @@ Item {
|
||||
}
|
||||
|
||||
function applyLastSuccessfulUser() {
|
||||
if (!GreetdSettings.settingsLoaded || !GreetdSettings.rememberLastUser)
|
||||
return;
|
||||
const lastUser = GreetdMemory.lastSuccessfulUser;
|
||||
if (lastUser && !GreeterState.showPasswordInput && !GreeterState.username) {
|
||||
GreeterState.username = lastUser;
|
||||
GreeterState.usernameInput = lastUser;
|
||||
GreeterState.showPasswordInput = true;
|
||||
PortalService.getGreeterUserProfileImage(lastUser);
|
||||
maybeAutoStartExternalAuth();
|
||||
}
|
||||
}
|
||||
|
||||
function submitUsername(rawValue) {
|
||||
const user = (rawValue || "").trim();
|
||||
if (!user)
|
||||
return;
|
||||
if (GreeterState.username !== user) {
|
||||
passwordFailureCount = 0;
|
||||
clearAuthFeedback();
|
||||
externalAuthAutoStartedForUser = "";
|
||||
}
|
||||
GreeterState.username = user;
|
||||
GreeterState.showPasswordInput = true;
|
||||
PortalService.getGreeterUserProfileImage(user);
|
||||
GreeterState.passwordBuffer = "";
|
||||
pendingPasswordResponse = false;
|
||||
resetPasswordSessionTransition(true);
|
||||
maybeAutoStartExternalAuth();
|
||||
}
|
||||
|
||||
function submitBufferedPassword() {
|
||||
pendingPasswordResponse = false;
|
||||
resetPasswordSessionTransition(true);
|
||||
awaitingExternalAuth = false;
|
||||
authTimeout.interval = defaultAuthTimeoutMs;
|
||||
authTimeout.restart();
|
||||
// Some PAM stacks expect an explicit empty response to advance U2F/fprint or fail normally.
|
||||
Greetd.respond(GreeterState.passwordBuffer || "");
|
||||
GreeterState.passwordBuffer = "";
|
||||
inputField.text = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
function requestPasswordSessionTransition() {
|
||||
const hasPasswordBuffer = GreeterState.passwordBuffer && GreeterState.passwordBuffer.length > 0;
|
||||
if (!passwordSubmitRequested && !hasPasswordBuffer)
|
||||
return;
|
||||
if (cancelingExternalAuthForPassword)
|
||||
return;
|
||||
if (passwordSessionTransitionRetryCount >= maxPasswordSessionTransitionRetries) {
|
||||
pendingPasswordResponse = false;
|
||||
awaitingExternalAuth = false;
|
||||
authTimeout.interval = defaultAuthTimeoutMs;
|
||||
authTimeout.stop();
|
||||
resetPasswordSessionTransition(true);
|
||||
GreeterState.pamState = "error";
|
||||
authFeedbackMessage = currentAuthMessage();
|
||||
placeholderDelay.restart();
|
||||
Greetd.cancelSession();
|
||||
return;
|
||||
}
|
||||
cancelingExternalAuthForPassword = true;
|
||||
passwordSessionTransitionRetryCount = passwordSessionTransitionRetryCount + 1;
|
||||
awaitingExternalAuth = false;
|
||||
pendingPasswordResponse = false;
|
||||
authTimeout.interval = defaultAuthTimeoutMs;
|
||||
authTimeout.stop();
|
||||
Greetd.cancelSession();
|
||||
}
|
||||
|
||||
function startAuthSession(submitPassword) {
|
||||
submitPassword = submitPassword === true;
|
||||
if (!GreeterState.showPasswordInput || !GreeterState.username)
|
||||
return;
|
||||
if (GreeterState.unlocking)
|
||||
return;
|
||||
const hasPasswordBuffer = GreeterState.passwordBuffer && GreeterState.passwordBuffer.length > 0;
|
||||
if (Greetd.state !== GreetdState.Inactive) {
|
||||
if (pendingPasswordResponse && submitPassword)
|
||||
submitBufferedPassword();
|
||||
else if (submitPassword)
|
||||
passwordSubmitRequested = true;
|
||||
return;
|
||||
}
|
||||
if (cancelingExternalAuthForPassword) {
|
||||
if (submitPassword)
|
||||
passwordSubmitRequested = true;
|
||||
return;
|
||||
}
|
||||
if (!submitPassword && !hasPasswordBuffer && !root.greeterExternalAuthAvailable)
|
||||
return;
|
||||
pendingPasswordResponse = false;
|
||||
passwordSubmitRequested = submitPassword;
|
||||
awaitingExternalAuth = !submitPassword && !hasPasswordBuffer && root.greeterExternalAuthAvailable;
|
||||
// Included PAM stacks (system-auth/common-auth/password-auth) may still run
|
||||
// biometric/U2F modules before password even when DMS toggles are off.
|
||||
const waitingOnPamExternalBeforePassword = submitPassword && root.greeterPamHasExternalAuth;
|
||||
authTimeout.interval = (awaitingExternalAuth || waitingOnPamExternalBeforePassword) ? externalAuthTimeoutMs : defaultAuthTimeoutMs;
|
||||
authTimeout.restart();
|
||||
Greetd.createSession(GreeterState.username);
|
||||
}
|
||||
|
||||
function maybeAutoStartExternalAuth() {
|
||||
if (!GreeterState.showPasswordInput || !GreeterState.username)
|
||||
return;
|
||||
if (!root.greeterExternalAuthAvailable)
|
||||
return;
|
||||
if (GreeterState.unlocking || Greetd.state !== GreetdState.Inactive)
|
||||
return;
|
||||
if (passwordSubmitRequested || cancelingExternalAuthForPassword)
|
||||
return;
|
||||
if (GreeterState.passwordBuffer && GreeterState.passwordBuffer.length > 0)
|
||||
return;
|
||||
if (externalAuthAutoStartedForUser === GreeterState.username)
|
||||
return;
|
||||
|
||||
externalAuthAutoStartedForUser = GreeterState.username;
|
||||
startAuthSession(false);
|
||||
}
|
||||
|
||||
function isExternalAuthPrompt(message, responseRequired) {
|
||||
// Non-response PAM messages commonly represent waiting states (fprint/U2F/token touch).
|
||||
return !responseRequired;
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
if (weatherInitialized)
|
||||
WeatherService.removeRef();
|
||||
@@ -528,39 +143,10 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: greeterWallpaperOverrideFile
|
||||
path: GreetdSettings.greeterWallpaperOverridePath
|
||||
printErrors: false
|
||||
watchChanges: true
|
||||
onLoaded: root.greeterWallpaperOverrideExists = true
|
||||
onLoadFailed: root.greeterWallpaperOverrideExists = false
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: GreetdSettings
|
||||
function onGreeterWallpaperOverridePathChanged() {
|
||||
if (!GreetdSettings.greeterWallpaperOverridePath) {
|
||||
root.greeterWallpaperOverrideExists = false;
|
||||
return;
|
||||
}
|
||||
greeterWallpaperOverrideFile.reload();
|
||||
}
|
||||
function onGreeterWallpaperPathChanged() {
|
||||
if (!GreetdSettings.greeterWallpaperPath) {
|
||||
root.greeterWallpaperOverrideExists = false;
|
||||
return;
|
||||
}
|
||||
greeterWallpaperOverrideFile.reload();
|
||||
}
|
||||
}
|
||||
|
||||
DankBackdrop {
|
||||
anchors.fill: parent
|
||||
screenName: root.screenName
|
||||
visible: {
|
||||
if (GreetdSettings.greeterWallpaperPath !== "" && root.greeterWallpaperOverrideExists)
|
||||
return false;
|
||||
var _ = SessionData.perMonitorWallpaper;
|
||||
var __ = SessionData.monitorWallpapers;
|
||||
var currentWallpaper = SessionData.getMonitorWallpaper(screenName);
|
||||
@@ -573,14 +159,12 @@ Item {
|
||||
|
||||
anchors.fill: parent
|
||||
source: {
|
||||
if (GreetdSettings.greeterWallpaperPath !== "" && root.greeterWallpaperOverrideExists)
|
||||
return encodeFileUrl(GreetdSettings.greeterWallpaperOverridePath);
|
||||
var _ = SessionData.perMonitorWallpaper;
|
||||
var __ = SessionData.monitorWallpapers;
|
||||
var currentWallpaper = SessionData.getMonitorWallpaper(screenName);
|
||||
return (currentWallpaper && !currentWallpaper.startsWith("#")) ? encodeFileUrl(currentWallpaper) : "";
|
||||
}
|
||||
fillMode: Theme.getFillMode(GreetdSettings.getEffectiveWallpaperFillMode())
|
||||
fillMode: Theme.getFillMode(GreetdSettings.wallpaperFillMode)
|
||||
smooth: true
|
||||
asynchronous: false
|
||||
cache: true
|
||||
@@ -743,7 +327,10 @@ Item {
|
||||
anchors.top: clockContainer.bottom
|
||||
anchors.topMargin: 4
|
||||
text: {
|
||||
return systemClock.date.toLocaleDateString(I18n.locale(), GreetdSettings.getEffectiveLockDateFormat());
|
||||
if (GreetdSettings.lockDateFormat && GreetdSettings.lockDateFormat.length > 0) {
|
||||
return systemClock.date.toLocaleDateString(I18n.locale(), GreetdSettings.lockDateFormat);
|
||||
}
|
||||
return systemClock.date.toLocaleDateString(I18n.locale(), Locale.LongFormat);
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeXLarge
|
||||
color: "white"
|
||||
@@ -812,9 +399,6 @@ Item {
|
||||
if (GreeterState.showPasswordInput && revealButton.visible) {
|
||||
margin += revealButton.width;
|
||||
}
|
||||
if (externalAuthButton.visible) {
|
||||
margin += externalAuthButton.width;
|
||||
}
|
||||
if (virtualKeyboardButton.visible) {
|
||||
margin += virtualKeyboardButton.width;
|
||||
}
|
||||
@@ -831,18 +415,21 @@ Item {
|
||||
return;
|
||||
if (GreeterState.showPasswordInput) {
|
||||
GreeterState.passwordBuffer = text;
|
||||
if (!text || text.length === 0)
|
||||
root.passwordSubmitRequested = false;
|
||||
} else {
|
||||
GreeterState.usernameInput = text;
|
||||
}
|
||||
}
|
||||
onAccepted: {
|
||||
if (GreeterState.showPasswordInput) {
|
||||
root.startAuthSession(true);
|
||||
if (Greetd.state === GreetdState.Inactive && GreeterState.username) {
|
||||
Greetd.createSession(GreeterState.username);
|
||||
}
|
||||
} else {
|
||||
if (text.trim()) {
|
||||
root.submitUsername(text);
|
||||
GreeterState.username = text.trim();
|
||||
GreeterState.showPasswordInput = true;
|
||||
PortalService.getGreeterUserProfileImage(GreeterState.username);
|
||||
GreeterState.passwordBuffer = "";
|
||||
syncingFromState = true;
|
||||
text = "";
|
||||
syncingFromState = false;
|
||||
@@ -874,14 +461,14 @@ Item {
|
||||
|
||||
anchors.left: lockIcon.right
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.right: (GreeterState.showPasswordInput && revealButton.visible ? revealButton.left : (externalAuthButton.visible ? externalAuthButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : parent.right))))
|
||||
anchors.right: (GreeterState.showPasswordInput && revealButton.visible ? revealButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : parent.right)))
|
||||
anchors.rightMargin: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: {
|
||||
if (GreeterState.unlocking) {
|
||||
return "Logging in...";
|
||||
}
|
||||
if (Greetd.state !== GreetdState.Inactive && !awaitingExternalAuth && !pendingPasswordResponse) {
|
||||
if (Greetd.state !== GreetdState.Inactive) {
|
||||
return "Authenticating...";
|
||||
}
|
||||
if (GreeterState.showPasswordInput) {
|
||||
@@ -889,7 +476,7 @@ Item {
|
||||
}
|
||||
return "Username...";
|
||||
}
|
||||
color: (GreeterState.unlocking || (Greetd.state !== GreetdState.Inactive && !awaitingExternalAuth && !pendingPasswordResponse)) ? Theme.primary : Theme.outline
|
||||
color: GreeterState.unlocking ? Theme.primary : (Greetd.state !== GreetdState.Inactive ? Theme.primary : Theme.outline)
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
opacity: (GreeterState.showPasswordInput ? GreeterState.passwordBuffer.length === 0 : GreeterState.usernameInput.length === 0) ? 1 : 0
|
||||
|
||||
@@ -911,7 +498,7 @@ Item {
|
||||
StyledText {
|
||||
anchors.left: lockIcon.right
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.right: (GreeterState.showPasswordInput && revealButton.visible ? revealButton.left : (externalAuthButton.visible ? externalAuthButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : parent.right))))
|
||||
anchors.right: (GreeterState.showPasswordInput && revealButton.visible ? revealButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : parent.right)))
|
||||
anchors.rightMargin: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: {
|
||||
@@ -941,27 +528,15 @@ Item {
|
||||
DankActionButton {
|
||||
id: revealButton
|
||||
|
||||
anchors.right: externalAuthButton.visible ? externalAuthButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : parent.right))
|
||||
anchors.right: virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : parent.right)
|
||||
anchors.rightMargin: 0
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconName: parent.showPassword ? "visibility_off" : "visibility"
|
||||
buttonSize: 32
|
||||
visible: GreeterState.showPasswordInput && GreeterState.passwordBuffer.length > 0 && (Greetd.state === GreetdState.Inactive || awaitingExternalAuth || pendingPasswordResponse) && !GreeterState.unlocking
|
||||
visible: GreeterState.showPasswordInput && GreeterState.passwordBuffer.length > 0 && Greetd.state === GreetdState.Inactive && !GreeterState.unlocking
|
||||
enabled: visible
|
||||
onClicked: parent.showPassword = !parent.showPassword
|
||||
}
|
||||
DankActionButton {
|
||||
id: externalAuthButton
|
||||
|
||||
anchors.right: virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : parent.right)
|
||||
anchors.rightMargin: 0
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconName: root.greeterPamHasFprint ? "fingerprint" : "key"
|
||||
buttonSize: 32
|
||||
visible: GreeterState.showPasswordInput && root.greeterExternalAuthAvailable && GreeterState.passwordBuffer.length === 0 && (Greetd.state === GreetdState.Inactive || awaitingExternalAuth || pendingPasswordResponse) && !GreeterState.unlocking
|
||||
enabled: visible
|
||||
onClicked: root.startAuthSession(false)
|
||||
}
|
||||
DankActionButton {
|
||||
id: virtualKeyboardButton
|
||||
|
||||
@@ -970,7 +545,7 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconName: "keyboard"
|
||||
buttonSize: 32
|
||||
visible: (Greetd.state === GreetdState.Inactive || awaitingExternalAuth || pendingPasswordResponse) && !GreeterState.unlocking
|
||||
visible: Greetd.state === GreetdState.Inactive && !GreeterState.unlocking
|
||||
enabled: visible
|
||||
onClicked: {
|
||||
if (keyboard_controller.isKeyboardActive) {
|
||||
@@ -989,14 +564,19 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconName: "keyboard_return"
|
||||
buttonSize: 36
|
||||
visible: (Greetd.state === GreetdState.Inactive || awaitingExternalAuth || pendingPasswordResponse) && !GreeterState.unlocking
|
||||
visible: Greetd.state === GreetdState.Inactive && !GreeterState.unlocking
|
||||
enabled: true
|
||||
onClicked: {
|
||||
if (GreeterState.showPasswordInput) {
|
||||
root.startAuthSession(true);
|
||||
if (GreeterState.username) {
|
||||
Greetd.createSession(GreeterState.username);
|
||||
}
|
||||
} else {
|
||||
if (inputField.text.trim()) {
|
||||
root.submitUsername(inputField.text);
|
||||
GreeterState.username = inputField.text.trim();
|
||||
GreeterState.showPasswordInput = true;
|
||||
PortalService.getGreeterUserProfileImage(GreeterState.username);
|
||||
GreeterState.passwordBuffer = "";
|
||||
inputField.text = "";
|
||||
}
|
||||
}
|
||||
@@ -1021,16 +601,20 @@ Item {
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 38
|
||||
Layout.preferredHeight: 20
|
||||
Layout.topMargin: -Theme.spacingS
|
||||
Layout.bottomMargin: -Theme.spacingS
|
||||
text: root.authFeedbackMessage
|
||||
text: {
|
||||
if (GreeterState.pamState === "error")
|
||||
return "Authentication error - try again";
|
||||
if (GreeterState.pamState === "fail")
|
||||
return "Incorrect password";
|
||||
return "";
|
||||
}
|
||||
color: Theme.error
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WordWrap
|
||||
maximumLineCount: 2
|
||||
opacity: root.authFeedbackMessage !== "" ? 1 : 0
|
||||
opacity: GreeterState.pamState !== "" ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
@@ -1083,7 +667,6 @@ Item {
|
||||
enabled: !GreeterState.unlocking && Greetd.state === GreetdState.Inactive && GreeterState.showPasswordInput
|
||||
onClicked: {
|
||||
GreeterState.reset();
|
||||
root.externalAuthAutoStartedForUser = "";
|
||||
inputField.text = "";
|
||||
PortalService.profileImage = "";
|
||||
}
|
||||
@@ -1446,11 +1029,9 @@ Item {
|
||||
return;
|
||||
if (!GreetdMemory.memoryReady)
|
||||
return;
|
||||
if (!GreetdSettings.settingsLoaded)
|
||||
return;
|
||||
|
||||
const savedSession = GreetdSettings.rememberLastSession ? GreetdMemory.lastSessionId : "";
|
||||
if (savedSession && GreetdSettings.rememberLastSession) {
|
||||
const savedSession = GreetdMemory.lastSessionId;
|
||||
if (savedSession) {
|
||||
for (var i = 0; i < GreeterState.sessionPaths.length; i++) {
|
||||
if (GreeterState.sessionPaths[i] === savedSession) {
|
||||
GreeterState.currentSessionIndex = i;
|
||||
@@ -1583,151 +1164,44 @@ Item {
|
||||
|
||||
function onAuthMessage(message, error, responseRequired, echoResponse) {
|
||||
if (responseRequired) {
|
||||
cancelingExternalAuthForPassword = false;
|
||||
passwordSessionTransitionRetryCount = 0;
|
||||
awaitingExternalAuth = false;
|
||||
pendingPasswordResponse = true;
|
||||
const hasPasswordBuffer = GreeterState.passwordBuffer && GreeterState.passwordBuffer.length > 0;
|
||||
if (!passwordSubmitRequested && hasPasswordBuffer)
|
||||
passwordSubmitRequested = true;
|
||||
if (passwordSubmitRequested && !root.submitBufferedPassword())
|
||||
passwordSubmitRequested = false;
|
||||
if (passwordSubmitRequested || hasPasswordBuffer) {
|
||||
authTimeout.interval = defaultAuthTimeoutMs;
|
||||
authTimeout.restart();
|
||||
} else {
|
||||
authTimeout.stop();
|
||||
}
|
||||
Greetd.respond(GreeterState.passwordBuffer);
|
||||
GreeterState.passwordBuffer = "";
|
||||
inputField.text = "";
|
||||
return;
|
||||
}
|
||||
pendingPasswordResponse = false;
|
||||
const externalPrompt = root.isExternalAuthPrompt(message, responseRequired);
|
||||
if (!passwordSubmitRequested)
|
||||
awaitingExternalAuth = root.greeterExternalAuthAvailable && externalPrompt;
|
||||
if (awaitingExternalAuth || (passwordSubmitRequested && externalPrompt && root.greeterPamHasExternalAuth))
|
||||
authTimeout.interval = externalAuthTimeoutMs;
|
||||
else
|
||||
authTimeout.interval = defaultAuthTimeoutMs;
|
||||
authTimeout.restart();
|
||||
Greetd.respond("");
|
||||
}
|
||||
|
||||
function onStateChanged() {
|
||||
if (Greetd.state === GreetdState.Inactive) {
|
||||
const resumePasswordSubmit = cancelingExternalAuthForPassword && passwordSubmitRequested;
|
||||
awaitingExternalAuth = false;
|
||||
pendingPasswordResponse = false;
|
||||
cancelingExternalAuthForPassword = false;
|
||||
authTimeout.interval = defaultAuthTimeoutMs;
|
||||
authTimeout.stop();
|
||||
if (resumePasswordSubmit) {
|
||||
Qt.callLater(function() {
|
||||
root.startAuthSession(true);
|
||||
});
|
||||
return;
|
||||
}
|
||||
resetPasswordSessionTransition(true);
|
||||
}
|
||||
if (!error)
|
||||
Greetd.respond("");
|
||||
}
|
||||
|
||||
function onReadyToLaunch() {
|
||||
awaitingExternalAuth = false;
|
||||
pendingPasswordResponse = false;
|
||||
resetPasswordSessionTransition(true);
|
||||
authTimeout.interval = defaultAuthTimeoutMs;
|
||||
authTimeout.stop();
|
||||
passwordFailureCount = 0;
|
||||
clearAuthFeedback();
|
||||
const sessionCmd = GreeterState.selectedSession || GreeterState.sessionExecs[GreeterState.currentSessionIndex];
|
||||
const sessionPath = GreeterState.selectedSessionPath || GreeterState.sessionPaths[GreeterState.currentSessionIndex];
|
||||
if (!sessionCmd) {
|
||||
GreeterState.pamState = "error";
|
||||
authFeedbackMessage = currentAuthMessage();
|
||||
placeholderDelay.restart();
|
||||
return;
|
||||
}
|
||||
|
||||
GreeterState.unlocking = true;
|
||||
launchTimeout.restart();
|
||||
if (GreetdSettings.rememberLastSession) {
|
||||
GreetdMemory.setLastSessionId(sessionPath);
|
||||
} else if (GreetdMemory.lastSessionId) {
|
||||
GreetdMemory.setLastSessionId("");
|
||||
}
|
||||
if (GreetdSettings.rememberLastUser) {
|
||||
GreetdMemory.setLastSuccessfulUser(GreeterState.username);
|
||||
} else if (GreetdMemory.lastSuccessfulUser) {
|
||||
GreetdMemory.setLastSuccessfulUser("");
|
||||
}
|
||||
pendingLaunchCommand = sessionCmd;
|
||||
pendingLaunchEnv = ["XDG_SESSION_TYPE=wayland"];
|
||||
memoryFlushTimer.restart();
|
||||
GreetdMemory.setLastSessionId(sessionPath);
|
||||
GreetdMemory.setLastSuccessfulUser(GreeterState.username);
|
||||
Greetd.launch(sessionCmd.split(" "), ["XDG_SESSION_TYPE=wayland"]);
|
||||
}
|
||||
|
||||
function onAuthFailure(message) {
|
||||
awaitingExternalAuth = false;
|
||||
pendingPasswordResponse = false;
|
||||
resetPasswordSessionTransition(true);
|
||||
authTimeout.interval = defaultAuthTimeoutMs;
|
||||
authTimeout.stop();
|
||||
launchTimeout.stop();
|
||||
GreeterState.unlocking = false;
|
||||
if (isLikelyLockoutMessage(message)) {
|
||||
GreeterState.pamState = "max";
|
||||
} else {
|
||||
GreeterState.pamState = "fail";
|
||||
passwordFailureCount = passwordFailureCount + 1;
|
||||
}
|
||||
authFeedbackMessage = currentAuthMessage();
|
||||
GreeterState.pamState = "fail";
|
||||
GreeterState.passwordBuffer = "";
|
||||
inputField.text = "";
|
||||
placeholderDelay.restart();
|
||||
Greetd.cancelSession();
|
||||
}
|
||||
|
||||
function onError(error) {
|
||||
awaitingExternalAuth = false;
|
||||
pendingPasswordResponse = false;
|
||||
resetPasswordSessionTransition(true);
|
||||
authTimeout.interval = defaultAuthTimeoutMs;
|
||||
authTimeout.stop();
|
||||
launchTimeout.stop();
|
||||
GreeterState.unlocking = false;
|
||||
GreeterState.pamState = "error";
|
||||
authFeedbackMessage = currentAuthMessage();
|
||||
GreeterState.passwordBuffer = "";
|
||||
inputField.text = "";
|
||||
placeholderDelay.restart();
|
||||
Greetd.cancelSession();
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: memoryFlushTimer
|
||||
interval: memoryFlushDelayMs
|
||||
onTriggered: {
|
||||
if (!pendingLaunchCommand)
|
||||
return;
|
||||
const sessionCommand = pendingLaunchCommand;
|
||||
const launchEnv = pendingLaunchEnv;
|
||||
pendingLaunchCommand = "";
|
||||
pendingLaunchEnv = [];
|
||||
Greetd.launch(sessionCommand.split(" "), launchEnv);
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: authTimeout
|
||||
interval: defaultAuthTimeoutMs
|
||||
onTriggered: {
|
||||
if (GreeterState.unlocking || Greetd.state === GreetdState.Inactive)
|
||||
return;
|
||||
awaitingExternalAuth = false;
|
||||
pendingPasswordResponse = false;
|
||||
resetPasswordSessionTransition(true);
|
||||
authTimeout.interval = defaultAuthTimeoutMs;
|
||||
GreeterState.pamState = "error";
|
||||
authFeedbackMessage = currentAuthMessage();
|
||||
GreeterState.passwordBuffer = "";
|
||||
inputField.text = "";
|
||||
placeholderDelay.restart();
|
||||
@@ -1741,11 +1215,8 @@ Item {
|
||||
onTriggered: {
|
||||
if (!GreeterState.unlocking)
|
||||
return;
|
||||
pendingPasswordResponse = false;
|
||||
resetPasswordSessionTransition(true);
|
||||
GreeterState.unlocking = false;
|
||||
GreeterState.pamState = "error";
|
||||
authFeedbackMessage = currentAuthMessage();
|
||||
placeholderDelay.restart();
|
||||
Greetd.cancelSession();
|
||||
}
|
||||
@@ -1754,7 +1225,7 @@ Item {
|
||||
Timer {
|
||||
id: placeholderDelay
|
||||
interval: 4000
|
||||
onTriggered: clearAuthFeedback()
|
||||
onTriggered: GreeterState.pamState = ""
|
||||
}
|
||||
|
||||
LockPowerMenu {
|
||||
|
||||
@@ -9,7 +9,6 @@ A greeter for [greetd](https://github.com/kennylevinsen/greetd) that follows the
|
||||
- **Multiple compositors**: Supports niri, Hyprland, Sway, or mangowc.
|
||||
- **Custom PAM**: Supports custom PAM configuration in `/etc/pam.d/greetd`
|
||||
- **Session Memory**: Remembers last selected session and user
|
||||
- Can be disabled via `settings.json` keys: `greeterRememberLastSession` and `greeterRememberLastUser`
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -213,7 +212,6 @@ dms-greeter --command hyprland
|
||||
dms-greeter --command sway
|
||||
dms-greeter --command mangowc
|
||||
dms-greeter --command niri -C /path/to/custom-niri.kdl
|
||||
dms-greeter --command niri --remember-last-user false --remember-last-session false
|
||||
```
|
||||
|
||||
Configure greetd to use it in `/etc/greetd/config.toml`:
|
||||
|
||||
@@ -6,9 +6,6 @@ COMPOSITOR=""
|
||||
COMPOSITOR_CONFIG=""
|
||||
DMS_PATH="dms-greeter"
|
||||
CACHE_DIR="/var/cache/dms-greeter"
|
||||
REMEMBER_LAST_SESSION=""
|
||||
REMEMBER_LAST_USER=""
|
||||
DEBUG_MODE=0
|
||||
|
||||
show_help() {
|
||||
cat << EOF
|
||||
@@ -25,15 +22,6 @@ Options:
|
||||
(default: dms-greeter)
|
||||
--cache-dir PATH Cache directory for greeter data
|
||||
(default: /var/cache/dms-greeter)
|
||||
--remember-last-session BOOL
|
||||
Persist selected session to greeter memory
|
||||
(BOOL: true/false, default: from settings.json)
|
||||
--remember-last-user BOOL
|
||||
Persist last successful username to greeter memory
|
||||
(BOOL: true/false, default: from settings.json)
|
||||
--no-save-session Alias for --remember-last-session false
|
||||
--no-save-username Alias for --remember-last-user false
|
||||
--debug Enable verbose startup logging to stderr
|
||||
-h, --help Show this help message
|
||||
|
||||
Examples:
|
||||
@@ -42,7 +30,6 @@ Examples:
|
||||
dms-greeter --command sway -p /home/user/.config/quickshell/custom-dms
|
||||
dms-greeter --command scroll -p /home/user/.config/quickshell/custom-dms
|
||||
dms-greeter --command niri --cache-dir /tmp/dmsgreeter
|
||||
dms-greeter --command niri --no-save-session --no-save-username
|
||||
dms-greeter --command mango
|
||||
dms-greeter --command labwc
|
||||
EOF
|
||||
@@ -56,41 +43,6 @@ require_command() {
|
||||
fi
|
||||
}
|
||||
|
||||
normalize_bool_flag() {
|
||||
local flag_name="$1"
|
||||
local value="$2"
|
||||
local normalized="${value,,}"
|
||||
|
||||
case "$normalized" in
|
||||
1|true|yes|on)
|
||||
echo "1"
|
||||
;;
|
||||
0|false|no|off)
|
||||
echo "0"
|
||||
;;
|
||||
*)
|
||||
echo "Error: $flag_name must be true/false (or 1/0, yes/no, on/off)" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
exec_compositor() {
|
||||
local log_tag="$1"
|
||||
shift
|
||||
|
||||
if [[ "$DEBUG_MODE" == "1" ]]; then
|
||||
exec "$@"
|
||||
fi
|
||||
|
||||
if command -v systemd-cat >/dev/null 2>&1; then
|
||||
exec "$@" > >(systemd-cat -t "dms-greeter/$log_tag" -p info) 2>&1
|
||||
fi
|
||||
|
||||
local log_file="$CACHE_DIR/$log_tag.log"
|
||||
exec "$@" >> "$log_file" 2>&1
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--command)
|
||||
@@ -109,26 +61,6 @@ while [[ $# -gt 0 ]]; do
|
||||
CACHE_DIR="$2"
|
||||
shift 2
|
||||
;;
|
||||
--remember-last-session)
|
||||
REMEMBER_LAST_SESSION="$2"
|
||||
shift 2
|
||||
;;
|
||||
--remember-last-user)
|
||||
REMEMBER_LAST_USER="$2"
|
||||
shift 2
|
||||
;;
|
||||
--no-save-session)
|
||||
REMEMBER_LAST_SESSION="0"
|
||||
shift
|
||||
;;
|
||||
--no-save-username)
|
||||
REMEMBER_LAST_USER="0"
|
||||
shift
|
||||
;;
|
||||
--debug)
|
||||
DEBUG_MODE=1
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
show_help
|
||||
exit 0
|
||||
@@ -179,58 +111,9 @@ export QT_QPA_PLATFORM=wayland
|
||||
export QT_WAYLAND_DISABLE_WINDOWDECORATION=1
|
||||
export EGL_PLATFORM=gbm
|
||||
export DMS_RUN_GREETER=1
|
||||
|
||||
ensure_cache_tree() {
|
||||
local base="$1"
|
||||
mkdir -p "$base/.local/state" "$base/.local/share" "$base/.cache"
|
||||
}
|
||||
|
||||
if ! ensure_cache_tree "$CACHE_DIR" 2>/dev/null; then
|
||||
FALLBACK_CACHE_DIR="/tmp/dms-greeter-${UID:-$(id -u)}"
|
||||
echo "Warning: cache directory '$CACHE_DIR' is not writable; falling back to '$FALLBACK_CACHE_DIR'" >&2
|
||||
CACHE_DIR="$FALLBACK_CACHE_DIR"
|
||||
if ! ensure_cache_tree "$CACHE_DIR"; then
|
||||
echo "Error: failed to initialize fallback cache directory '$CACHE_DIR'" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
export DMS_GREET_CFG_DIR="$CACHE_DIR"
|
||||
|
||||
if [[ -n "$REMEMBER_LAST_SESSION" ]]; then
|
||||
DMS_GREET_REMEMBER_LAST_SESSION=$(normalize_bool_flag "--remember-last-session" "$REMEMBER_LAST_SESSION")
|
||||
export DMS_GREET_REMEMBER_LAST_SESSION
|
||||
if [[ "$DMS_GREET_REMEMBER_LAST_SESSION" == "1" ]]; then
|
||||
DMS_SAVE_SESSION=true
|
||||
else
|
||||
DMS_SAVE_SESSION=false
|
||||
fi
|
||||
export DMS_SAVE_SESSION
|
||||
fi
|
||||
|
||||
if [[ -n "$REMEMBER_LAST_USER" ]]; then
|
||||
DMS_GREET_REMEMBER_LAST_USER=$(normalize_bool_flag "--remember-last-user" "$REMEMBER_LAST_USER")
|
||||
export DMS_GREET_REMEMBER_LAST_USER
|
||||
if [[ "$DMS_GREET_REMEMBER_LAST_USER" == "1" ]]; then
|
||||
DMS_SAVE_USERNAME=true
|
||||
else
|
||||
DMS_SAVE_USERNAME=false
|
||||
fi
|
||||
export DMS_SAVE_USERNAME
|
||||
fi
|
||||
|
||||
export HOME="$CACHE_DIR"
|
||||
export XDG_STATE_HOME="$CACHE_DIR/.local/state"
|
||||
export XDG_DATA_HOME="$CACHE_DIR/.local/share"
|
||||
export XDG_CACHE_HOME="$CACHE_DIR/.cache"
|
||||
|
||||
# Keep greeter VT clean by default; callers can override via env or --debug.
|
||||
if [[ -z "${RUST_LOG:-}" ]]; then
|
||||
export RUST_LOG=warn
|
||||
fi
|
||||
if [[ -z "${NIRI_LOG:-}" ]]; then
|
||||
export NIRI_LOG=warn
|
||||
fi
|
||||
mkdir -p "$CACHE_DIR"
|
||||
|
||||
if command -v qs >/dev/null 2>&1; then
|
||||
QS_BIN="qs"
|
||||
@@ -247,9 +130,7 @@ if [[ "$DMS_PATH" == /* ]]; then
|
||||
else
|
||||
RESOLVED_PATH=$(locate_dms_config "$DMS_PATH")
|
||||
if [[ $? -eq 0 && -n "$RESOLVED_PATH" ]]; then
|
||||
if [[ "$DEBUG_MODE" == "1" ]]; then
|
||||
echo "Located DMS config at: $RESOLVED_PATH" >&2
|
||||
fi
|
||||
echo "Located DMS config at: $RESOLVED_PATH" >&2
|
||||
QS_CMD="$QS_BIN -p $RESOLVED_PATH"
|
||||
else
|
||||
echo "Error: Could not find DMS config '$DMS_PATH' (shell.qml) in any valid config path" >&2
|
||||
@@ -311,7 +192,7 @@ NIRI_EOF
|
||||
spawn-at-startup "sh" "-c" "$QS_CMD; niri msg action quit --skip-confirmation"
|
||||
NIRI_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
exec_compositor "niri" niri -c "$COMPOSITOR_CONFIG"
|
||||
exec niri -c "$COMPOSITOR_CONFIG"
|
||||
;;
|
||||
|
||||
hyprland)
|
||||
@@ -341,9 +222,9 @@ HYPRLAND_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
fi
|
||||
if command -v start-hyprland >/dev/null 2>&1; then
|
||||
exec_compositor "hyprland" start-hyprland -- --config "$COMPOSITOR_CONFIG"
|
||||
exec start-hyprland -- --config "$COMPOSITOR_CONFIG"
|
||||
else
|
||||
exec_compositor "hyprland" Hyprland -c "$COMPOSITOR_CONFIG"
|
||||
exec Hyprland -c "$COMPOSITOR_CONFIG"
|
||||
fi
|
||||
;;
|
||||
|
||||
@@ -364,7 +245,7 @@ exec "$QS_CMD; swaymsg exit"
|
||||
SWAY_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
fi
|
||||
exec_compositor "sway" sway --unsupported-gpu -c "$COMPOSITOR_CONFIG"
|
||||
exec sway --unsupported-gpu -c "$COMPOSITOR_CONFIG"
|
||||
;;
|
||||
|
||||
scroll)
|
||||
@@ -384,7 +265,7 @@ exec "$QS_CMD; scrollmsg exit"
|
||||
SCROLL_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
fi
|
||||
exec_compositor "scroll" scroll -c "$COMPOSITOR_CONFIG"
|
||||
exec scroll -c "$COMPOSITOR_CONFIG"
|
||||
;;
|
||||
|
||||
miracle|miracle-wm)
|
||||
@@ -404,24 +285,24 @@ exec "$QS_CMD; miraclemsg exit"
|
||||
MIRACLE_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
fi
|
||||
exec_compositor "miracle" miracle-wm -c "$COMPOSITOR_CONFIG"
|
||||
exec miracle-wm -c "$COMPOSITOR_CONFIG"
|
||||
;;
|
||||
|
||||
labwc)
|
||||
require_command "labwc"
|
||||
if [[ -n "$COMPOSITOR_CONFIG" ]]; then
|
||||
exec_compositor "labwc" labwc --config "$COMPOSITOR_CONFIG" --session "$QS_CMD"
|
||||
exec labwc --config "$COMPOSITOR_CONFIG" --session "$QS_CMD"
|
||||
else
|
||||
exec_compositor "labwc" labwc --session "$QS_CMD"
|
||||
exec labwc --session "$QS_CMD"
|
||||
fi
|
||||
;;
|
||||
|
||||
mango|mangowc)
|
||||
require_command "mango"
|
||||
if [[ -n "$COMPOSITOR_CONFIG" ]]; then
|
||||
exec_compositor "mango" mango -c "$COMPOSITOR_CONFIG" -s "$QS_CMD && mmsg -d quit"
|
||||
exec mango -c "$COMPOSITOR_CONFIG" -s "$QS_CMD && mmsg -d quit"
|
||||
else
|
||||
exec_compositor "mango" mango -s "$QS_CMD && mmsg -d quit"
|
||||
exec mango -s "$QS_CMD && mmsg -d quit"
|
||||
fi
|
||||
;;
|
||||
|
||||
|
||||
@@ -755,7 +755,7 @@ Item {
|
||||
}
|
||||
}
|
||||
onAccepted: {
|
||||
if (!demoMode && !root.unlocking && !pam.passwd.active && !pam.u2fPending) {
|
||||
if (!demoMode && !pam.passwd.active && !pam.u2fPending) {
|
||||
pam.passwd.start();
|
||||
}
|
||||
}
|
||||
@@ -764,11 +764,6 @@ Item {
|
||||
return;
|
||||
}
|
||||
|
||||
if (root.unlocking) {
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
if (pam.u2fPending) {
|
||||
pam.cancelU2fPending();
|
||||
@@ -1022,7 +1017,7 @@ Item {
|
||||
visible: (demoMode || (!pam.passwd.active && !root.unlocking && !pam.u2fPending))
|
||||
enabled: !demoMode
|
||||
onClicked: {
|
||||
if (!demoMode && !root.unlocking && !pam.u2fPending) {
|
||||
if (!demoMode && !pam.u2fPending) {
|
||||
pam.passwd.start();
|
||||
}
|
||||
}
|
||||
@@ -1631,7 +1626,6 @@ Item {
|
||||
onStateChanged: {
|
||||
root.pamState = state;
|
||||
if (state !== "") {
|
||||
root.unlocking = false;
|
||||
placeholderDelay.restart();
|
||||
passwordField.text = "";
|
||||
root.passwordBuffer = "";
|
||||
@@ -1647,15 +1641,6 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: pam
|
||||
|
||||
function onUnlockInProgressChanged() {
|
||||
if (!pam.unlockInProgress && root.unlocking)
|
||||
root.unlocking = false;
|
||||
}
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: pam
|
||||
property: "buffer"
|
||||
|
||||
@@ -25,29 +25,6 @@ Scope {
|
||||
signal flashMsg
|
||||
signal unlockRequested
|
||||
|
||||
function resetAuthFlows(): void {
|
||||
passwd.abort();
|
||||
fprint.abort();
|
||||
u2f.abort();
|
||||
errorRetry.running = false;
|
||||
u2fErrorRetry.running = false;
|
||||
u2fPendingTimeout.running = false;
|
||||
passwdActiveTimeout.running = false;
|
||||
unlockRequestTimeout.running = false;
|
||||
u2fPending = false;
|
||||
u2fState = "";
|
||||
unlockInProgress = false;
|
||||
}
|
||||
|
||||
function recoverFromAuthStall(newState: string): void {
|
||||
resetAuthFlows();
|
||||
state = newState;
|
||||
flashMsg();
|
||||
stateReset.restart();
|
||||
fprint.checkAvail();
|
||||
u2f.checkAvail();
|
||||
}
|
||||
|
||||
function completeUnlock(): void {
|
||||
if (!unlockInProgress) {
|
||||
unlockInProgress = true;
|
||||
@@ -59,7 +36,6 @@ Scope {
|
||||
u2fPendingTimeout.running = false;
|
||||
u2fPending = false;
|
||||
u2fState = "";
|
||||
unlockRequestTimeout.restart();
|
||||
unlockRequested();
|
||||
}
|
||||
}
|
||||
@@ -90,13 +66,6 @@ Scope {
|
||||
printErrors: false
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: loginConfigWatcher
|
||||
|
||||
path: "/etc/pam.d/login"
|
||||
printErrors: false
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: u2fConfigWatcher
|
||||
|
||||
@@ -108,7 +77,7 @@ Scope {
|
||||
id: passwd
|
||||
|
||||
config: dankshellConfigWatcher.loaded ? "dankshell" : "login"
|
||||
configDirectory: dankshellConfigWatcher.loaded || loginConfigWatcher.loaded ? "/etc/pam.d" : Quickshell.shellDir + "/assets/pam"
|
||||
configDirectory: dankshellConfigWatcher.loaded ? "/etc/pam.d" : Quickshell.shellDir + "/assets/pam"
|
||||
|
||||
onMessageChanged: {
|
||||
if (message.startsWith("The account is locked"))
|
||||
@@ -133,13 +102,6 @@ Scope {
|
||||
return;
|
||||
}
|
||||
|
||||
unlockRequestTimeout.running = false;
|
||||
root.unlockInProgress = false;
|
||||
root.u2fPending = false;
|
||||
root.u2fState = "";
|
||||
u2fPendingTimeout.running = false;
|
||||
u2f.abort();
|
||||
|
||||
if (res === PamResult.Error)
|
||||
root.state = "error";
|
||||
else if (res === PamResult.MaxTries)
|
||||
@@ -152,18 +114,6 @@ Scope {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: passwd
|
||||
|
||||
function onActiveChanged() {
|
||||
if (passwd.active) {
|
||||
passwdActiveTimeout.restart();
|
||||
} else {
|
||||
passwdActiveTimeout.running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PamContext {
|
||||
id: fprint
|
||||
|
||||
@@ -291,7 +241,7 @@ Scope {
|
||||
Process {
|
||||
id: availProc
|
||||
|
||||
command: ["sh", "-c", "fprintd-list \"${USER:-$(id -un)}\""]
|
||||
command: ["sh", "-c", "fprintd-list $USER"]
|
||||
onExited: code => {
|
||||
fprint.available = code === 0;
|
||||
fprint.checkAvail();
|
||||
@@ -329,26 +279,6 @@ Scope {
|
||||
onTriggered: root.cancelU2fPending()
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: passwdActiveTimeout
|
||||
|
||||
interval: 15000
|
||||
onTriggered: {
|
||||
if (passwd.active)
|
||||
root.recoverFromAuthStall("error");
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: unlockRequestTimeout
|
||||
|
||||
interval: 8000
|
||||
onTriggered: {
|
||||
if (root.unlockInProgress)
|
||||
root.recoverFromAuthStall("error");
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: stateReset
|
||||
|
||||
@@ -378,9 +308,17 @@ Scope {
|
||||
root.u2fState = "";
|
||||
root.u2fPending = false;
|
||||
root.lockMessage = "";
|
||||
root.resetAuthFlows();
|
||||
root.unlockInProgress = false;
|
||||
} else {
|
||||
root.resetAuthFlows();
|
||||
fprint.abort();
|
||||
passwd.abort();
|
||||
u2f.abort();
|
||||
errorRetry.running = false;
|
||||
u2fErrorRetry.running = false;
|
||||
u2fPendingTimeout.running = false;
|
||||
root.u2fPending = false;
|
||||
root.u2fState = "";
|
||||
root.unlockInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,7 +338,6 @@ Scope {
|
||||
u2f.abort();
|
||||
u2fErrorRetry.running = false;
|
||||
u2fPendingTimeout.running = false;
|
||||
unlockRequestTimeout.running = false;
|
||||
root.u2fPending = false;
|
||||
root.u2fState = "";
|
||||
u2f.checkAvail();
|
||||
|
||||
@@ -7,7 +7,7 @@ DankPopout {
|
||||
id: root
|
||||
|
||||
layerNamespace: "dms:notification-center-popout"
|
||||
fullHeightSurface: true
|
||||
fullHeightSurface: false
|
||||
|
||||
property bool notificationHistoryVisible: false
|
||||
property var triggerScreen: null
|
||||
@@ -39,11 +39,9 @@ DankPopout {
|
||||
}
|
||||
}
|
||||
|
||||
popupWidth: triggerScreen ? Math.min(500, Math.max(380, triggerScreen.width - 48)) : 400
|
||||
popupWidth: 400
|
||||
popupHeight: stablePopupHeight
|
||||
positioning: ""
|
||||
animationScaleCollapsed: 0.94
|
||||
animationOffset: 0
|
||||
suspendShadowWhileResizing: false
|
||||
|
||||
screen: triggerScreen
|
||||
|
||||
@@ -24,6 +24,29 @@ PanelWindow {
|
||||
property real _lastReportedAlignedHeight: -1
|
||||
property real _storedTopMargin: 0
|
||||
property real _storedBottomMargin: 0
|
||||
readonly property bool directionalEffect: Theme.isDirectionalEffect
|
||||
readonly property bool depthEffect: Theme.isDepthEffect
|
||||
readonly property real entryTravel: {
|
||||
const base = Math.abs(Theme.effectAnimOffset);
|
||||
if (directionalEffect) {
|
||||
if (isCenterPosition)
|
||||
return Math.max(base, Math.round(content.height * 1.1));
|
||||
return Math.max(base, Math.round(content.width * 0.95));
|
||||
}
|
||||
if (depthEffect)
|
||||
return Math.max(base, 44);
|
||||
return base;
|
||||
}
|
||||
readonly property real exitTravel: {
|
||||
if (directionalEffect) {
|
||||
if (isCenterPosition)
|
||||
return content.height + entryTravel;
|
||||
return content.width + entryTravel;
|
||||
}
|
||||
if (depthEffect)
|
||||
return Math.round(entryTravel * 1.35);
|
||||
return Anims.slidePx;
|
||||
}
|
||||
readonly property string clearText: I18n.tr("Dismiss")
|
||||
property bool descriptionExpanded: false
|
||||
readonly property bool hasExpandableBody: (notificationData?.htmlBody || "").replace(/<[^>]*>/g, "").trim().length > 0
|
||||
@@ -137,9 +160,9 @@ PanelWindow {
|
||||
enabled: !exiting && !_isDestroying
|
||||
NumberAnimation {
|
||||
id: implicitHeightAnim
|
||||
duration: descriptionExpanded ? Theme.notificationExpandDuration : Theme.notificationCollapseDuration
|
||||
duration: Theme.variantDuration(descriptionExpanded ? Theme.notificationExpandDuration : Theme.notificationCollapseDuration, descriptionExpanded)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.emphasized
|
||||
easing.bezierCurve: descriptionExpanded ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
@@ -911,9 +934,9 @@ PanelWindow {
|
||||
if (isCenterPosition)
|
||||
return 0;
|
||||
const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom;
|
||||
return isLeft ? -Anims.slidePx : Anims.slidePx;
|
||||
return isLeft ? -entryTravel : entryTravel;
|
||||
}
|
||||
y: isTopCenter ? -Anims.slidePx : isBottomCenter ? Anims.slidePx : 0
|
||||
y: isTopCenter ? -entryTravel : isBottomCenter ? entryTravel : 0
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -925,16 +948,16 @@ PanelWindow {
|
||||
property: isCenterPosition ? "y" : "x"
|
||||
from: {
|
||||
if (isTopCenter)
|
||||
return -Anims.slidePx;
|
||||
return -entryTravel;
|
||||
if (isBottomCenter)
|
||||
return Anims.slidePx;
|
||||
return entryTravel;
|
||||
const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom;
|
||||
return isLeft ? -Anims.slidePx : Anims.slidePx;
|
||||
return isLeft ? -entryTravel : entryTravel;
|
||||
}
|
||||
to: 0
|
||||
duration: Theme.notificationEnterDuration
|
||||
duration: Theme.variantDuration(Theme.notificationEnterDuration, true)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: isCenterPosition ? Theme.expressiveCurves.standardDecel : Theme.expressiveCurves.emphasizedDecel
|
||||
easing.bezierCurve: Theme.variantPopoutEnterCurve
|
||||
onStopped: {
|
||||
if (!win.exiting && !win._isDestroying) {
|
||||
if (isCenterPosition) {
|
||||
@@ -959,35 +982,35 @@ PanelWindow {
|
||||
from: 0
|
||||
to: {
|
||||
if (isTopCenter)
|
||||
return -Anims.slidePx;
|
||||
return -exitTravel;
|
||||
if (isBottomCenter)
|
||||
return Anims.slidePx;
|
||||
return exitTravel;
|
||||
const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom;
|
||||
return isLeft ? -Anims.slidePx : Anims.slidePx;
|
||||
return isLeft ? -exitTravel : exitTravel;
|
||||
}
|
||||
duration: Theme.notificationExitDuration
|
||||
duration: Theme.variantDuration(Theme.notificationExitDuration, false)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedAccel
|
||||
easing.bezierCurve: Theme.variantPopoutExitCurve
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: content
|
||||
property: "opacity"
|
||||
from: 1
|
||||
to: 0
|
||||
duration: Theme.notificationExitDuration
|
||||
to: Theme.isDirectionalEffect ? 1 : 0
|
||||
duration: Theme.variantDuration(Theme.notificationExitDuration, false)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.standardAccel
|
||||
easing.bezierCurve: Theme.variantPopoutExitCurve
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: content
|
||||
property: "scale"
|
||||
from: 1
|
||||
to: 0.98
|
||||
duration: Theme.notificationExitDuration
|
||||
to: Theme.isDirectionalEffect ? 1 : Theme.effectScaleCollapsed
|
||||
duration: Theme.variantDuration(Theme.notificationExitDuration, false)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedAccel
|
||||
easing.bezierCurve: Theme.variantPopoutExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -108,13 +108,6 @@ QtObject {
|
||||
return p && p.status !== Component.Null && !p._isDestroying && p.hasValidData;
|
||||
}
|
||||
|
||||
function _isFocusedScreen() {
|
||||
if (!SettingsData.notificationFocusedMonitor)
|
||||
return true;
|
||||
const focused = CompositorService.getFocusedScreen();
|
||||
return focused && manager.modelData && focused.name === manager.modelData.name;
|
||||
}
|
||||
|
||||
function _sync(newWrappers) {
|
||||
for (const p of popupWindows.slice()) {
|
||||
if (!_isValidWindow(p) || p.exiting)
|
||||
@@ -125,7 +118,7 @@ QtObject {
|
||||
}
|
||||
}
|
||||
for (const w of newWrappers) {
|
||||
if (w && !_hasWindowFor(w) && _isFocusedScreen())
|
||||
if (w && !_hasWindowFor(w))
|
||||
_insertAtTop(w);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -909,9 +909,6 @@ Singleton {
|
||||
case "dwl":
|
||||
DwlService.generateOutputsConfig(outputsData);
|
||||
break;
|
||||
default:
|
||||
WlrOutputService.applyOutputsConfig(outputsData, outputs);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -417,15 +417,6 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Focused monitor only")
|
||||
description: I18n.tr("Show notifications only on the currently focused monitor")
|
||||
visible: parent.componentId === "notifications"
|
||||
checked: SettingsData.notificationFocusedMonitor
|
||||
onToggled: checked => SettingsData.set("notificationFocusedMonitor", checked)
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Show on Last Display")
|
||||
|
||||
@@ -160,16 +160,6 @@ Item {
|
||||
onToggled: checked => SettingsData.set("dockGroupByApp", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "dockRestoreSpecialWorkspaceOnClick"
|
||||
tags: ["dock", "hyprland", "special", "workspace", "restore"]
|
||||
text: I18n.tr("Restore Special Workspace Windows")
|
||||
description: I18n.tr("When clicking a dock window in a Hyprland special workspace, bring that special workspace back before focusing the window")
|
||||
checked: SettingsData.dockRestoreSpecialWorkspaceOnClick
|
||||
visible: CompositorService.isHyprland
|
||||
onToggled: checked => SettingsData.set("dockRestoreSpecialWorkspaceOnClick", checked)
|
||||
}
|
||||
|
||||
SettingsButtonGroupRow {
|
||||
settingKey: "dockIndicatorStyle"
|
||||
tags: ["dock", "indicator", "style", "circle", "line"]
|
||||
|
||||
@@ -1,743 +0,0 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Modals.Common
|
||||
import qs.Modals.FileBrowser
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.Settings.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
readonly property bool greeterFprintToggleAvailable: SettingsData.fprintdAvailable || SettingsData.greeterEnableFprint
|
||||
readonly property bool greeterU2fToggleAvailable: SettingsData.u2fAvailable || SettingsData.greeterEnableU2f
|
||||
|
||||
function refreshAuthDetection() {
|
||||
SettingsData.refreshAuthAvailability();
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible)
|
||||
refreshAuthDetection();
|
||||
}
|
||||
|
||||
ConfirmModal {
|
||||
id: greeterActionConfirm
|
||||
}
|
||||
|
||||
FileBrowserModal {
|
||||
id: greeterWallpaperBrowserModal
|
||||
browserTitle: I18n.tr("Select greeter background image")
|
||||
browserIcon: "wallpaper"
|
||||
browserType: "wallpaper"
|
||||
showHiddenFiles: true
|
||||
fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp", "*.jxl", "*.avif", "*.heif"]
|
||||
onFileSelected: path => {
|
||||
SettingsData.set("greeterWallpaperPath", path);
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
property string greeterStatusText: ""
|
||||
property bool greeterStatusRunning: false
|
||||
property bool greeterSyncRunning: false
|
||||
property bool greeterInstallActionRunning: false
|
||||
property string greeterStatusStdout: ""
|
||||
property string greeterStatusStderr: ""
|
||||
property string greeterSyncStdout: ""
|
||||
property string greeterSyncStderr: ""
|
||||
property string greeterSudoProbeStderr: ""
|
||||
property string greeterTerminalFallbackStderr: ""
|
||||
property bool greeterTerminalFallbackFromPrecheck: false
|
||||
property var cachedFontFamilies: []
|
||||
property bool fontsEnumerated: false
|
||||
property bool greeterBinaryExists: false
|
||||
property bool greeterEnabled: false
|
||||
readonly property bool greeterInstalled: greeterBinaryExists || greeterEnabled
|
||||
|
||||
readonly property string greeterActionLabel: {
|
||||
if (!root.greeterInstalled)
|
||||
return I18n.tr("Install");
|
||||
if (!root.greeterEnabled)
|
||||
return I18n.tr("Activate");
|
||||
return I18n.tr("Uninstall");
|
||||
}
|
||||
readonly property string greeterActionIcon: {
|
||||
if (!root.greeterInstalled)
|
||||
return "download";
|
||||
if (!root.greeterEnabled)
|
||||
return "login";
|
||||
return "delete";
|
||||
}
|
||||
readonly property var greeterActionCommand: {
|
||||
if (!root.greeterInstalled)
|
||||
return ["dms", "greeter", "install", "--terminal"];
|
||||
if (!root.greeterEnabled)
|
||||
return ["dms", "greeter", "enable", "--terminal"];
|
||||
return ["dms", "greeter", "uninstall", "--terminal", "--yes"];
|
||||
}
|
||||
property string greeterPendingAction: ""
|
||||
|
||||
function checkGreeterInstallState() {
|
||||
greetdEnabledCheckProcess.running = true;
|
||||
greeterBinaryCheckProcess.running = true;
|
||||
}
|
||||
|
||||
function runGreeterStatus() {
|
||||
greeterStatusText = "";
|
||||
greeterStatusStdout = "";
|
||||
greeterStatusStderr = "";
|
||||
greeterStatusRunning = true;
|
||||
greeterStatusProcess.running = true;
|
||||
}
|
||||
|
||||
function runGreeterInstallAction() {
|
||||
root.greeterPendingAction = !root.greeterInstalled ? "install" : !root.greeterEnabled ? "activate" : "uninstall";
|
||||
greeterStatusText = I18n.tr("Opening terminal: ") + root.greeterActionLabel + "…";
|
||||
greeterInstallActionRunning = true;
|
||||
greeterInstallActionProcess.running = true;
|
||||
}
|
||||
|
||||
function promptGreeterActionConfirm() {
|
||||
var title, message, confirmText;
|
||||
if (!root.greeterInstalled) {
|
||||
title = I18n.tr("Install Greeter", "greeter action confirmation");
|
||||
message = I18n.tr("Install the DMS greeter? A terminal will open for sudo authentication.");
|
||||
confirmText = I18n.tr("Install");
|
||||
} else if (!root.greeterEnabled) {
|
||||
title = I18n.tr("Activate Greeter", "greeter action confirmation");
|
||||
message = I18n.tr("Activate the DMS greeter? A terminal will open for sudo authentication. Run Sync after activation to apply your settings.");
|
||||
confirmText = I18n.tr("Activate");
|
||||
} else {
|
||||
title = I18n.tr("Uninstall Greeter", "greeter action confirmation");
|
||||
message = I18n.tr("Uninstall the DMS greeter? This will remove configuration and restore your previous display manager. A terminal will open for sudo authentication.");
|
||||
confirmText = I18n.tr("Uninstall");
|
||||
}
|
||||
greeterActionConfirm.showWithOptions({
|
||||
"title": title,
|
||||
"message": message,
|
||||
"confirmText": confirmText,
|
||||
"cancelText": I18n.tr("Cancel"),
|
||||
"confirmColor": Theme.primary,
|
||||
"onConfirm": () => root.runGreeterInstallAction(),
|
||||
"onCancel": () => {}
|
||||
});
|
||||
}
|
||||
|
||||
function runGreeterSync() {
|
||||
greeterSyncStdout = "";
|
||||
greeterSyncStderr = "";
|
||||
greeterSudoProbeStderr = "";
|
||||
greeterTerminalFallbackStderr = "";
|
||||
greeterTerminalFallbackFromPrecheck = false;
|
||||
greeterStatusText = I18n.tr("Checking whether sudo authentication is needed…");
|
||||
greeterSyncRunning = true;
|
||||
greeterSudoProbeProcess.running = true;
|
||||
}
|
||||
|
||||
function launchGreeterSyncTerminalFallback(fromPrecheck, statusText) {
|
||||
greeterTerminalFallbackFromPrecheck = fromPrecheck;
|
||||
if (statusText && statusText !== "")
|
||||
greeterStatusText = statusText;
|
||||
greeterTerminalFallbackStderr = "";
|
||||
greeterTerminalFallbackProcess.running = true;
|
||||
}
|
||||
|
||||
function enumerateFonts() {
|
||||
if (fontsEnumerated)
|
||||
return;
|
||||
var fonts = [];
|
||||
var availableFonts = Qt.fontFamilies();
|
||||
for (var i = 0; i < availableFonts.length; i++) {
|
||||
var fontName = availableFonts[i];
|
||||
if (fontName.startsWith("."))
|
||||
continue;
|
||||
fonts.push(fontName);
|
||||
}
|
||||
fonts.sort();
|
||||
fonts.unshift("Default");
|
||||
cachedFontFamilies = fonts;
|
||||
fontsEnumerated = true;
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
refreshAuthDetection();
|
||||
Qt.callLater(enumerateFonts);
|
||||
Qt.callLater(checkGreeterInstallState);
|
||||
}
|
||||
|
||||
Process {
|
||||
id: greetdEnabledCheckProcess
|
||||
command: ["systemctl", "is-enabled", "greetd"]
|
||||
running: false
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: root.greeterEnabled = text.trim() === "enabled"
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: greeterBinaryCheckProcess
|
||||
command: ["sh", "-c", "test -f /usr/bin/dms-greeter || test -f /usr/local/bin/dms-greeter"]
|
||||
running: false
|
||||
|
||||
onExited: exitCode => {
|
||||
root.greeterBinaryExists = (exitCode === 0);
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: greeterStatusProcess
|
||||
command: ["dms", "greeter", "status"]
|
||||
running: false
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
root.greeterStatusStdout = text || "";
|
||||
}
|
||||
}
|
||||
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: root.greeterStatusStderr = text || ""
|
||||
}
|
||||
|
||||
onExited: exitCode => {
|
||||
root.greeterStatusRunning = false;
|
||||
const out = (root.greeterStatusStdout || "").trim();
|
||||
const err = (root.greeterStatusStderr || "").trim();
|
||||
if (exitCode === 0) {
|
||||
root.greeterStatusText = out !== "" ? out : I18n.tr("No status output.");
|
||||
if (err !== "")
|
||||
root.greeterStatusText = root.greeterStatusText + "\n\nstderr:\n" + err;
|
||||
return;
|
||||
}
|
||||
var failure = I18n.tr("Failed to run 'dms greeter status'. Ensure DMS is installed and dms is in PATH.", "greeter status error") + " (exit " + exitCode + ")";
|
||||
if (out !== "")
|
||||
failure = failure + "\n\n" + out;
|
||||
if (err !== "")
|
||||
failure = failure + "\n\nstderr:\n" + err;
|
||||
root.greeterStatusText = failure;
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: greeterSyncProcess
|
||||
command: ["dms", "greeter", "sync", "--yes"]
|
||||
running: false
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: root.greeterSyncStdout = text || ""
|
||||
}
|
||||
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: root.greeterSyncStderr = text || ""
|
||||
}
|
||||
|
||||
onExited: exitCode => {
|
||||
root.greeterSyncRunning = false;
|
||||
const out = (root.greeterSyncStdout || "").trim();
|
||||
const err = (root.greeterSyncStderr || "").trim();
|
||||
if (exitCode === 0) {
|
||||
var success = I18n.tr("Sync completed successfully.");
|
||||
if (out !== "")
|
||||
success = success + "\n\n" + out;
|
||||
if (err !== "")
|
||||
success = success + "\n\nstderr:\n" + err;
|
||||
root.greeterStatusText = success;
|
||||
} else {
|
||||
var failure = I18n.tr("Sync failed in background mode. Trying terminal mode so you can authenticate interactively.") + " (exit " + exitCode + ")";
|
||||
if (out !== "")
|
||||
failure = failure + "\n\n" + out;
|
||||
if (err !== "")
|
||||
failure = failure + "\n\nstderr:\n" + err;
|
||||
root.greeterStatusText = failure;
|
||||
root.launchGreeterSyncTerminalFallback(false, "");
|
||||
}
|
||||
root.checkGreeterInstallState();
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: greeterSudoProbeProcess
|
||||
command: ["sudo", "-n", "true"]
|
||||
running: false
|
||||
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: root.greeterSudoProbeStderr = text || ""
|
||||
}
|
||||
|
||||
onExited: exitCode => {
|
||||
const err = (root.greeterSudoProbeStderr || "").trim();
|
||||
if (exitCode === 0) {
|
||||
root.greeterStatusText = I18n.tr("Running greeter sync…");
|
||||
greeterSyncProcess.running = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var authNeeded = I18n.tr("Sync needs sudo authentication. Opening terminal so you can use password or fingerprint.");
|
||||
if (err !== "")
|
||||
authNeeded = authNeeded + "\n\n" + err;
|
||||
root.launchGreeterSyncTerminalFallback(true, authNeeded);
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: greeterTerminalFallbackProcess
|
||||
command: ["dms", "greeter", "sync", "--terminal", "--yes"]
|
||||
running: false
|
||||
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: root.greeterTerminalFallbackStderr = text || ""
|
||||
}
|
||||
|
||||
onExited: exitCode => {
|
||||
root.greeterSyncRunning = false;
|
||||
if (exitCode === 0) {
|
||||
var launched = root.greeterTerminalFallbackFromPrecheck ? I18n.tr("Terminal opened. Complete sync authentication there; it will close automatically when done.") : I18n.tr("Terminal fallback opened. Complete sync there; it will close automatically when done.");
|
||||
root.greeterStatusText = root.greeterStatusText ? root.greeterStatusText + "\n\n" + launched : launched;
|
||||
return;
|
||||
}
|
||||
var fallback = I18n.tr("Terminal fallback failed. Install one of the supported terminal emulators or run 'dms greeter sync' manually.") + " (exit " + exitCode + ")";
|
||||
const err = (root.greeterTerminalFallbackStderr || "").trim();
|
||||
if (err !== "")
|
||||
fallback = fallback + "\n\nstderr:\n" + err;
|
||||
root.greeterStatusText = root.greeterStatusText ? root.greeterStatusText + "\n\n" + fallback : fallback;
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: greeterInstallActionProcess
|
||||
command: root.greeterActionCommand
|
||||
running: false
|
||||
|
||||
onExited: exitCode => {
|
||||
root.greeterInstallActionRunning = false;
|
||||
const pending = root.greeterPendingAction;
|
||||
root.greeterPendingAction = "";
|
||||
if (exitCode === 0) {
|
||||
if (pending === "install")
|
||||
root.greeterStatusText = I18n.tr("Install complete. Greeter has been installed.");
|
||||
else if (pending === "activate")
|
||||
root.greeterStatusText = I18n.tr("Greeter activated. greetd is now enabled.");
|
||||
else
|
||||
root.greeterStatusText = I18n.tr("Uninstall complete. Greeter has been removed.");
|
||||
} else {
|
||||
root.greeterStatusText = I18n.tr("Action failed or terminal was closed.") + " (exit " + exitCode + ")";
|
||||
}
|
||||
root.checkGreeterInstallState();
|
||||
}
|
||||
}
|
||||
|
||||
readonly property var _lockDateFormatPresets: [
|
||||
{
|
||||
format: "",
|
||||
label: I18n.tr("System Default", "date format option")
|
||||
},
|
||||
{
|
||||
format: "ddd d",
|
||||
label: I18n.tr("Day Date", "date format option")
|
||||
},
|
||||
{
|
||||
format: "ddd MMM d",
|
||||
label: I18n.tr("Day Month Date", "date format option")
|
||||
},
|
||||
{
|
||||
format: "MMM d",
|
||||
label: I18n.tr("Month Date", "date format option")
|
||||
},
|
||||
{
|
||||
format: "M/d",
|
||||
label: I18n.tr("Numeric (M/D)", "date format option")
|
||||
},
|
||||
{
|
||||
format: "d/M",
|
||||
label: I18n.tr("Numeric (D/M)", "date format option")
|
||||
},
|
||||
{
|
||||
format: "ddd d MMM yyyy",
|
||||
label: I18n.tr("Full with Year", "date format option")
|
||||
},
|
||||
{
|
||||
format: "yyyy-MM-dd",
|
||||
label: I18n.tr("ISO Date", "date format option")
|
||||
},
|
||||
{
|
||||
format: "dddd, MMMM d",
|
||||
label: I18n.tr("Full Day & Month", "date format option")
|
||||
}
|
||||
]
|
||||
readonly property var _wallpaperFillModes: ["Stretch", "Fit", "Fill", "Tile", "TileVertically", "TileHorizontally", "Pad"]
|
||||
|
||||
DankFlickable {
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
contentHeight: mainColumn.height + Theme.spacingXL
|
||||
contentWidth: width
|
||||
|
||||
Column {
|
||||
id: mainColumn
|
||||
topPadding: 4
|
||||
width: Math.min(550, parent.width - Theme.spacingL * 2)
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Theme.spacingXL
|
||||
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
iconName: "info"
|
||||
title: I18n.tr("Greeter Status")
|
||||
settingKey: "greeterStatus"
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Check sync status on demand. Sync copies your theme, settings, PAM config, and wallpaper to the login screen in one step. Must run Sync to apply changes.")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
width: parent.width
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 1
|
||||
height: Theme.spacingS
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: Math.min(180, statusTextArea.implicitHeight + Theme.spacingM * 2)
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHighest
|
||||
|
||||
StyledText {
|
||||
id: statusTextArea
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
text: root.greeterStatusRunning ? I18n.tr("Checking…", "greeter status loading") : (root.greeterStatusText || I18n.tr("Click Refresh to check status.", "greeter status placeholder"))
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.family: "monospace"
|
||||
color: root.greeterStatusRunning ? Theme.surfaceVariantText : Theme.surfaceText
|
||||
wrapMode: Text.Wrap
|
||||
verticalAlignment: Text.AlignTop
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 1
|
||||
height: Theme.spacingM
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankButton {
|
||||
text: root.greeterActionLabel
|
||||
iconName: root.greeterActionIcon
|
||||
horizontalPadding: Theme.spacingL
|
||||
onClicked: root.promptGreeterActionConfirm()
|
||||
enabled: !root.greeterInstallActionRunning && !root.greeterSyncRunning
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
DankButton {
|
||||
text: I18n.tr("Refresh")
|
||||
iconName: "refresh"
|
||||
horizontalPadding: Theme.spacingL
|
||||
onClicked: root.runGreeterStatus()
|
||||
enabled: !root.greeterStatusRunning
|
||||
}
|
||||
|
||||
DankButton {
|
||||
text: I18n.tr("Sync")
|
||||
iconName: "sync"
|
||||
horizontalPadding: Theme.spacingL
|
||||
onClicked: root.runGreeterSync()
|
||||
enabled: root.greeterInstalled && !root.greeterSyncRunning && !root.greeterInstallActionRunning
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
iconName: "fingerprint"
|
||||
title: I18n.tr("Login Authentication")
|
||||
settingKey: "greeterAuth"
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Enable fingerprint or security key for DMS Greeter. Run Sync to apply and configure PAM.")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
width: parent.width
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "greeterEnableFprint"
|
||||
tags: ["greeter", "fingerprint", "fprintd", "login", "auth"]
|
||||
text: I18n.tr("Enable fingerprint at login")
|
||||
description: {
|
||||
if (!SettingsData.fprintdAvailable) {
|
||||
if (SettingsData.greeterEnableFprint)
|
||||
return I18n.tr("Enabled in settings, but fingerprint availability could not yet be confirmed. Re-open after enrolling fingerprints or reconnecting the reader.");
|
||||
return I18n.tr("Not available — install fprintd and enroll fingerprints.");
|
||||
}
|
||||
return SettingsData.greeterEnableFprint ? I18n.tr("Run Sync to apply. Fingerprint-only login may not unlock GNOME Keyring.") : I18n.tr("Only off for DMS-managed PAM lines. If greetd includes system-auth/common-auth/password-auth with pam_fprintd, fingerprint still stays enabled.");
|
||||
}
|
||||
descriptionColor: SettingsData.fprintdAvailable ? Theme.surfaceVariantText : Theme.warning
|
||||
checked: SettingsData.greeterEnableFprint
|
||||
enabled: root.greeterFprintToggleAvailable
|
||||
onToggled: checked => SettingsData.set("greeterEnableFprint", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "greeterEnableU2f"
|
||||
tags: ["greeter", "u2f", "security", "key", "login", "auth"]
|
||||
text: I18n.tr("Enable security key at login")
|
||||
description: {
|
||||
if (!SettingsData.u2fAvailable) {
|
||||
if (SettingsData.greeterEnableU2f)
|
||||
return I18n.tr("Enabled in settings, but security key availability could not yet be confirmed. Re-open after enrolling keys or updating pam_u2f.");
|
||||
return I18n.tr("Not available — install pam_u2f and enroll keys.");
|
||||
}
|
||||
return SettingsData.greeterEnableU2f ? I18n.tr("Run Sync to apply.") : I18n.tr("Disabled.");
|
||||
}
|
||||
descriptionColor: SettingsData.u2fAvailable ? Theme.surfaceVariantText : Theme.warning
|
||||
checked: SettingsData.greeterEnableU2f
|
||||
enabled: root.greeterU2fToggleAvailable
|
||||
onToggled: checked => SettingsData.set("greeterEnableU2f", checked)
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
iconName: "palette"
|
||||
title: I18n.tr("Greeter Appearance")
|
||||
settingKey: "greeterAppearance"
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Font")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
topPadding: Theme.spacingM
|
||||
}
|
||||
|
||||
SettingsDropdownRow {
|
||||
settingKey: "greeterFontFamily"
|
||||
tags: ["greeter", "font", "typography"]
|
||||
text: I18n.tr("Greeter font")
|
||||
description: I18n.tr("Font used on the login screen")
|
||||
options: root.fontsEnumerated ? root.cachedFontFamilies : ["Default"]
|
||||
currentValue: (!SettingsData.greeterFontFamily || SettingsData.greeterFontFamily === "" || SettingsData.greeterFontFamily === Theme.defaultFontFamily) ? "Default" : (SettingsData.greeterFontFamily || "Default")
|
||||
enableFuzzySearch: true
|
||||
popupWidthOffset: 100
|
||||
maxPopupHeight: 400
|
||||
onValueChanged: value => {
|
||||
if (value === "Default")
|
||||
SettingsData.set("greeterFontFamily", "");
|
||||
else
|
||||
SettingsData.set("greeterFontFamily", value);
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Time format")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
topPadding: Theme.spacingM
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "greeterUse24Hour"
|
||||
tags: ["greeter", "time", "24hour"]
|
||||
text: I18n.tr("24-hour clock")
|
||||
description: I18n.tr("Greeter only — does not affect main clock")
|
||||
checked: SettingsData.greeterUse24HourClock
|
||||
onToggled: checked => SettingsData.set("greeterUse24HourClock", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "greeterShowSeconds"
|
||||
tags: ["greeter", "time", "seconds"]
|
||||
text: I18n.tr("Show seconds")
|
||||
checked: SettingsData.greeterShowSeconds
|
||||
onToggled: checked => SettingsData.set("greeterShowSeconds", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "greeterPadHours"
|
||||
tags: ["greeter", "time", "12hour"]
|
||||
text: I18n.tr("Pad hours (02:00 vs 2:00)")
|
||||
visible: !SettingsData.greeterUse24HourClock
|
||||
checked: SettingsData.greeterPadHours12Hour
|
||||
onToggled: checked => SettingsData.set("greeterPadHours12Hour", checked)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Date format on greeter")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
topPadding: Theme.spacingM
|
||||
}
|
||||
|
||||
SettingsDropdownRow {
|
||||
settingKey: "greeterLockDateFormat"
|
||||
tags: ["greeter", "date", "format"]
|
||||
text: I18n.tr("Date format")
|
||||
description: I18n.tr("Greeter only — format for the date on the login screen")
|
||||
options: root._lockDateFormatPresets.map(p => p.label)
|
||||
currentValue: {
|
||||
var current = (SettingsData.greeterLockDateFormat !== undefined && SettingsData.greeterLockDateFormat !== "") ? SettingsData.greeterLockDateFormat : SettingsData.lockDateFormat || "";
|
||||
var match = root._lockDateFormatPresets.find(p => p.format === current);
|
||||
return match ? match.label : (current ? I18n.tr("Custom: ") + current : root._lockDateFormatPresets[0].label);
|
||||
}
|
||||
onValueChanged: value => {
|
||||
var preset = root._lockDateFormatPresets.find(p => p.label === value);
|
||||
SettingsData.set("greeterLockDateFormat", preset ? preset.format : "");
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Background")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
topPadding: Theme.spacingM
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Use a custom image for the login screen, or leave empty to use your desktop wallpaper.")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
width: parent.width
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankTextField {
|
||||
id: greeterWallpaperPathField
|
||||
width: parent.width - browseGreeterWallpaperButton.width - Theme.spacingS
|
||||
placeholderText: I18n.tr("Use desktop wallpaper")
|
||||
text: SettingsData.greeterWallpaperPath
|
||||
backgroundColor: Theme.surfaceContainerHighest
|
||||
onTextChanged: {
|
||||
if (text !== SettingsData.greeterWallpaperPath)
|
||||
SettingsData.set("greeterWallpaperPath", text);
|
||||
}
|
||||
}
|
||||
|
||||
DankButton {
|
||||
id: browseGreeterWallpaperButton
|
||||
text: I18n.tr("Browse")
|
||||
horizontalPadding: Theme.spacingL
|
||||
onClicked: greeterWallpaperBrowserModal.open()
|
||||
}
|
||||
}
|
||||
|
||||
SettingsDropdownRow {
|
||||
settingKey: "greeterWallpaperFillMode"
|
||||
tags: ["greeter", "wallpaper", "background", "fill"]
|
||||
text: I18n.tr("Wallpaper fill mode")
|
||||
description: I18n.tr("How the background image is scaled")
|
||||
options: root._wallpaperFillModes.map(m => I18n.tr(m, "wallpaper fill mode"))
|
||||
currentValue: {
|
||||
var mode = (SettingsData.greeterWallpaperFillMode && SettingsData.greeterWallpaperFillMode !== "") ? SettingsData.greeterWallpaperFillMode : (SettingsData.wallpaperFillMode || "Fill");
|
||||
var idx = root._wallpaperFillModes.indexOf(mode);
|
||||
return idx >= 0 ? I18n.tr(root._wallpaperFillModes[idx], "wallpaper fill mode") : I18n.tr("Fill", "wallpaper fill mode");
|
||||
}
|
||||
onValueChanged: value => {
|
||||
var idx = root._wallpaperFillModes.map(m => I18n.tr(m, "wallpaper fill mode")).indexOf(value);
|
||||
if (idx >= 0)
|
||||
SettingsData.set("greeterWallpaperFillMode", root._wallpaperFillModes[idx]);
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Layout and module positions on the greeter are synced from your shell (e.g. bar config). Run Sync to apply.")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
width: parent.width
|
||||
wrapMode: Text.Wrap
|
||||
topPadding: Theme.spacingS
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
iconName: "history"
|
||||
title: I18n.tr("Greeter Behavior")
|
||||
settingKey: "greeterBehavior"
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Convenience options for the login screen. Sync to apply.")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
width: parent.width
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "greeterRememberLastSession"
|
||||
tags: ["greeter", "session", "remember", "login"]
|
||||
text: I18n.tr("Remember last session")
|
||||
description: I18n.tr("Pre-select the last used session on the greeter")
|
||||
checked: SettingsData.greeterRememberLastSession
|
||||
onToggled: checked => SettingsData.set("greeterRememberLastSession", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "greeterRememberLastUser"
|
||||
tags: ["greeter", "user", "remember", "login", "username"]
|
||||
text: I18n.tr("Remember last user")
|
||||
description: I18n.tr("Pre-fill the last successful username on the greeter")
|
||||
checked: SettingsData.greeterRememberLastUser
|
||||
onToggled: checked => SettingsData.set("greeterRememberLastUser", checked)
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
iconName: "extension"
|
||||
title: I18n.tr("Dependencies & documentation")
|
||||
settingKey: "greeterDeps"
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("DMS greeter needs: greetd, dms-greeter. Fingerprint: fprintd, pam_fprintd. Security keys: pam_u2f. Add your user to the greeter group. Sync checks sudo first and opens a terminal when interactive authentication is required.")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
width: parent.width
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Installation and PAM setup: see the ") + "<a href=\"https://danklinux.com/docs/dankgreeter/installation\" style=\"text-decoration:none; color:" + Theme.primary + ";\">DankGreeter docs</a> " + I18n.tr("or run ") + "'dms greeter install'."
|
||||
textFormat: Text.RichText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
linkColor: Theme.primary
|
||||
width: parent.width
|
||||
wrapMode: Text.Wrap
|
||||
onLinkActivated: url => Qt.openUrlExternally(url)
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
acceptedButtons: Qt.NoButton
|
||||
propagateComposedEvents: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,19 +9,6 @@ import qs.Modules.Settings.Widgets
|
||||
Item {
|
||||
id: root
|
||||
|
||||
readonly property bool lockFprintToggleAvailable: SettingsData.fprintdAvailable || SettingsData.enableFprint
|
||||
readonly property bool lockU2fToggleAvailable: SettingsData.u2fAvailable || SettingsData.enableU2f
|
||||
|
||||
function refreshAuthDetection() {
|
||||
SettingsData.refreshAuthAvailability();
|
||||
}
|
||||
|
||||
Component.onCompleted: refreshAuthDetection()
|
||||
onVisibleChanged: {
|
||||
if (visible)
|
||||
refreshAuthDetection();
|
||||
}
|
||||
|
||||
FileBrowserModal {
|
||||
id: videoBrowserModal
|
||||
browserTitle: I18n.tr("Select Video or Folder")
|
||||
@@ -185,16 +172,10 @@ Item {
|
||||
settingKey: "enableFprint"
|
||||
tags: ["lock", "screen", "fingerprint", "authentication", "biometric", "fprint"]
|
||||
text: I18n.tr("Enable fingerprint authentication")
|
||||
description: {
|
||||
if (SettingsData.fprintdAvailable)
|
||||
return I18n.tr("Use fingerprint reader for lock screen authentication (requires enrolled fingerprints)");
|
||||
if (SettingsData.enableFprint)
|
||||
return I18n.tr("Enabled in settings, but fingerprint availability could not yet be confirmed. Re-open after enrolling fingerprints or reconnecting the reader.");
|
||||
return I18n.tr("Not available — install fprintd and enroll fingerprints.");
|
||||
}
|
||||
description: SettingsData.fprintdAvailable ? I18n.tr("Use fingerprint reader for lock screen authentication (requires enrolled fingerprints)") : I18n.tr("Not enrolled", "fingerprint not detected status")
|
||||
descriptionColor: SettingsData.fprintdAvailable ? Theme.surfaceVariantText : Theme.warning
|
||||
checked: SettingsData.enableFprint
|
||||
enabled: root.lockFprintToggleAvailable
|
||||
enabled: SettingsData.fprintdAvailable
|
||||
onToggled: checked => SettingsData.set("enableFprint", checked)
|
||||
}
|
||||
|
||||
@@ -202,16 +183,10 @@ Item {
|
||||
settingKey: "enableU2f"
|
||||
tags: ["lock", "screen", "u2f", "yubikey", "security", "key", "fido", "authentication", "hardware"]
|
||||
text: I18n.tr("Enable security key authentication", "Enable FIDO2/U2F hardware security key for lock screen")
|
||||
description: {
|
||||
if (SettingsData.u2fAvailable)
|
||||
return I18n.tr("Use a FIDO2/U2F security key (e.g. YubiKey) for lock screen authentication (requires enrolled keys)", "lock screen U2F security key setting");
|
||||
if (SettingsData.enableU2f)
|
||||
return I18n.tr("Enabled in settings, but security key availability could not yet be confirmed. Re-open after enrolling keys or updating pam_u2f.");
|
||||
return I18n.tr("Not available — install pam_u2f and enroll keys.");
|
||||
}
|
||||
description: SettingsData.u2fAvailable ? I18n.tr("Use a FIDO2/U2F security key (e.g. YubiKey) for lock screen authentication (requires enrolled keys)", "lock screen U2F security key setting") : I18n.tr("Not enrolled", "security key not detected status")
|
||||
descriptionColor: SettingsData.u2fAvailable ? Theme.surfaceVariantText : Theme.warning
|
||||
checked: SettingsData.enableU2f
|
||||
enabled: root.lockU2fToggleAvailable
|
||||
enabled: SettingsData.u2fAvailable
|
||||
onToggled: checked => SettingsData.set("enableU2f", checked)
|
||||
}
|
||||
|
||||
@@ -220,7 +195,7 @@ Item {
|
||||
tags: ["lock", "screen", "u2f", "yubikey", "security", "key", "mode", "factor", "second"]
|
||||
text: I18n.tr("Security key mode", "lock screen U2F security key mode setting")
|
||||
description: I18n.tr("'Alternative' lets the key unlock on its own. 'Second factor' requires password or fingerprint first, then the key.", "lock screen U2F security key mode setting")
|
||||
visible: SettingsData.enableU2f
|
||||
visible: SettingsData.u2fAvailable && SettingsData.enableU2f
|
||||
options: [I18n.tr("Alternative (OR)", "U2F mode option: key works as standalone unlock method"), I18n.tr("Second Factor (AND)", "U2F mode option: key required after password or fingerprint")]
|
||||
currentValue: SettingsData.u2fMode === "and" ? I18n.tr("Second Factor (AND)", "U2F mode option: key required after password or fingerprint") : I18n.tr("Alternative (OR)", "U2F mode option: key works as standalone unlock method")
|
||||
onValueChanged: value => {
|
||||
@@ -270,7 +245,7 @@ Item {
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Path to a video file or folder containing videos")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.pixelSize: Theme.fontSizeXSmall
|
||||
color: Theme.outlineVariant
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
|
||||
@@ -1935,6 +1935,11 @@ Item {
|
||||
label: I18n.tr("Auth Type"),
|
||||
value: data["connection-type"]
|
||||
});
|
||||
fields.push({
|
||||
label: I18n.tr("Autoconnect"),
|
||||
value: configData.autoconnect ? I18n.tr("Yes") : I18n.tr("No")
|
||||
});
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
@@ -1973,16 +1978,6 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Autoconnect")
|
||||
checked: configData ? (configData.autoconnect || false) : false
|
||||
visible: !VPNService.configLoading && configData !== null
|
||||
onToggled: checked => {
|
||||
VPNService.updateConfig(modelData.uuid, {autoconnect: checked});
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 1
|
||||
height: Theme.spacingXS
|
||||
|
||||
@@ -288,15 +288,6 @@ Item {
|
||||
onToggled: checked => SettingsData.set("notificationPopupPrivacyMode", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "notificationFocusedMonitor"
|
||||
tags: ["notification", "popup", "focused", "monitor", "display", "screen", "active"]
|
||||
text: I18n.tr("Focused Monitor Only")
|
||||
description: I18n.tr("Show notification popups only on the currently focused monitor")
|
||||
checked: SettingsData.notificationFocusedMonitor
|
||||
onToggled: checked => SettingsData.set("notificationFocusedMonitor", checked)
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: notificationAnimationColumn.implicitHeight + Theme.spacingM * 2
|
||||
|
||||
@@ -408,8 +408,6 @@ FloatingWindow {
|
||||
}
|
||||
clip: true
|
||||
visible: !root.isLoading
|
||||
add: null
|
||||
displaced: null
|
||||
|
||||
ScrollBar.vertical: DankScrollbar {
|
||||
id: browserScrollbar
|
||||
|
||||
@@ -2638,18 +2638,6 @@ Item {
|
||||
checked: SettingsData.matugenTemplateEmacs
|
||||
onToggled: checked => SettingsData.set("matugenTemplateEmacs", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
tab: "theme"
|
||||
tags: ["matugen", "zed", "template"]
|
||||
settingKey: "matugenTemplateZed"
|
||||
text: "Zed"
|
||||
description: getTemplateDescription("zed", "")
|
||||
descriptionColor: getTemplateDescriptionColor("zed")
|
||||
visible: SettingsData.runDmsMatugenTemplates
|
||||
checked: SettingsData.matugenTemplateZed
|
||||
onToggled: checked => SettingsData.set("matugenTemplateZed", checked)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
|
||||
@@ -55,6 +55,180 @@ Item {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Theme.spacingXL
|
||||
|
||||
SettingsCard {
|
||||
tab: "typography"
|
||||
tags: ["animation", "variant", "style", "slide", "fluent", "dynamic", "motion"]
|
||||
title: I18n.tr("Animation Style")
|
||||
settingKey: "animationVariant"
|
||||
iconName: "auto_awesome_motion"
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: animVariantGroup.implicitHeight
|
||||
clip: true
|
||||
|
||||
DankButtonGroup {
|
||||
id: animVariantGroup
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
buttonPadding: parent.width < 480 ? Theme.spacingS : Theme.spacingL
|
||||
minButtonWidth: parent.width < 480 ? 64 : 96
|
||||
textSize: parent.width < 480 ? Theme.fontSizeSmall : Theme.fontSizeMedium
|
||||
model: [I18n.tr("Material"), I18n.tr("Fluent"), I18n.tr("Dynamic")]
|
||||
selectionMode: "single"
|
||||
currentIndex: SettingsData.animationVariant
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (!selected)
|
||||
return;
|
||||
SettingsData.set("animationVariant", index);
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onAnimationVariantChanged() {
|
||||
animVariantGroup.currentIndex = SettingsData.animationVariant;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Theme.outline
|
||||
opacity: 0.15
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: variantDescription.implicitHeight + Theme.spacingS * 2
|
||||
|
||||
StyledText {
|
||||
id: variantDescription
|
||||
x: Theme.spacingM
|
||||
y: Theme.spacingS
|
||||
width: parent.width - Theme.spacingM * 2
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
text: {
|
||||
switch (SettingsData.animationVariant) {
|
||||
case 1:
|
||||
return I18n.tr("Fluent: Smooth cubic deceleration in, quick snap out — clean, elegant curves.");
|
||||
case 2:
|
||||
return I18n.tr("Dynamic: Spring bezier with overshoot — entry briefly exceeds its target then settles. Expressive and alive.");
|
||||
default:
|
||||
return I18n.tr("Material: Material Design 3 Expressive bezier curves. The DMS default feel.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
tab: "typography"
|
||||
tags: ["animation", "motion", "effect", "slide", "directional", "depth", "spring", "physics"]
|
||||
title: I18n.tr("Motion Effects")
|
||||
settingKey: "motionEffect"
|
||||
iconName: "motion_photos_on"
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: motionEffectGroup.implicitHeight
|
||||
clip: true
|
||||
|
||||
DankButtonGroup {
|
||||
id: motionEffectGroup
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
buttonPadding: parent.width < 480 ? Theme.spacingS : Theme.spacingL
|
||||
minButtonWidth: parent.width < 480 ? 64 : 96
|
||||
textSize: parent.width < 480 ? Theme.fontSizeSmall : Theme.fontSizeMedium
|
||||
model: [I18n.tr("Standard"), I18n.tr("Directional"), I18n.tr("Depth")]
|
||||
selectionMode: "single"
|
||||
currentIndex: SettingsData.motionEffect
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (!selected)
|
||||
return;
|
||||
SettingsData.set("motionEffect", index);
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onMotionEffectChanged() {
|
||||
motionEffectGroup.currentIndex = SettingsData.motionEffect;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Theme.outline
|
||||
opacity: 0.15
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: motionEffectDescription.implicitHeight + Theme.spacingS * 2
|
||||
|
||||
StyledText {
|
||||
id: motionEffectDescription
|
||||
x: Theme.spacingM
|
||||
y: Theme.spacingS
|
||||
width: parent.width - Theme.spacingM * 2
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
text: {
|
||||
switch (SettingsData.motionEffect) {
|
||||
case 1:
|
||||
return I18n.tr("Directional: Panels glide in from a larger distance at full size — no scale change, pure clean motion.");
|
||||
case 2:
|
||||
return I18n.tr("Depth: Panels scale up from small as they slide in — a dramatic pop-forward depth effect.");
|
||||
default:
|
||||
return I18n.tr("Standard: Classic Material Design 3 — panels rise from below with a subtle scale. The DMS default.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Theme.outline
|
||||
opacity: 0.15
|
||||
visible: SettingsData.motionEffect === 1
|
||||
}
|
||||
|
||||
SettingsDropdownRow {
|
||||
visible: SettingsData.motionEffect === 1
|
||||
tab: "typography"
|
||||
tags: ["animation", "directional", "behavior", "overlap", "sticky", "roll"]
|
||||
settingKey: "directionalAnimationMode"
|
||||
text: I18n.tr("Directional Behavior")
|
||||
description: I18n.tr("How the popout emerges from the DankBar")
|
||||
options: [I18n.tr("Overlap"), I18n.tr("Slide"), I18n.tr("Roll")]
|
||||
currentValue: {
|
||||
switch (SettingsData.directionalAnimationMode) {
|
||||
case 1:
|
||||
return I18n.tr("Slide");
|
||||
case 2:
|
||||
return I18n.tr("Roll");
|
||||
default:
|
||||
return I18n.tr("Overlap");
|
||||
}
|
||||
}
|
||||
onValueChanged: value => {
|
||||
if (value === I18n.tr("Slide"))
|
||||
SettingsData.set("directionalAnimationMode", 1);
|
||||
else if (value === I18n.tr("Roll"))
|
||||
SettingsData.set("directionalAnimationMode", 2);
|
||||
else
|
||||
SettingsData.set("directionalAnimationMode", 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
tab: "typography"
|
||||
tags: ["font", "family", "text", "typography"]
|
||||
|
||||
@@ -125,16 +125,6 @@ Item {
|
||||
onToggled: checked => SettingsData.set("groupWorkspaceApps", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "workspaceActiveAppHighlightEnabled"
|
||||
tags: ["workspace", "apps", "icons", "highlight", "active", "focused"]
|
||||
text: I18n.tr("Highlight Active Workspace App")
|
||||
description: I18n.tr("Highlight the currently focused app inside workspace indicators")
|
||||
checked: SettingsData.workspaceActiveAppHighlightEnabled
|
||||
visible: SettingsData.showWorkspaceApps
|
||||
onToggled: checked => SettingsData.set("workspaceActiveAppHighlightEnabled", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "workspaceFollowFocus"
|
||||
tags: ["workspace", "focus", "follow", "monitor"]
|
||||
|
||||
@@ -121,9 +121,9 @@ Scope {
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewScope.overviewOpen)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
|
||||
easing.bezierCurve: overviewScope.overviewOpen ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,45 +154,69 @@ Scope {
|
||||
id: scaleTransform
|
||||
origin.x: contentContainer.width / 2
|
||||
origin.y: contentContainer.height / 2
|
||||
xScale: overviewScope.overviewOpen ? 1 : 0.96
|
||||
yScale: overviewScope.overviewOpen ? 1 : 0.96
|
||||
xScale: overviewScope.overviewOpen ? 1 : Theme.effectScaleCollapsed
|
||||
yScale: overviewScope.overviewOpen ? 1 : Theme.effectScaleCollapsed
|
||||
|
||||
Behavior on xScale {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewScope.overviewOpen)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
|
||||
easing.bezierCurve: overviewScope.overviewOpen ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on yScale {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewScope.overviewOpen)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
|
||||
easing.bezierCurve: overviewScope.overviewOpen ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Translate {
|
||||
id: motionTransform
|
||||
x: 0
|
||||
y: overviewScope.overviewOpen ? 0 : Theme.spacingL
|
||||
x: {
|
||||
if (overviewScope.overviewOpen)
|
||||
return 0;
|
||||
if (Theme.isDirectionalEffect)
|
||||
return 0;
|
||||
if (Theme.isDepthEffect)
|
||||
return Theme.effectAnimOffset * 0.25;
|
||||
return 0;
|
||||
}
|
||||
y: {
|
||||
if (overviewScope.overviewOpen)
|
||||
return 0;
|
||||
if (Theme.isDirectionalEffect)
|
||||
return -Math.max(contentContainer.height * 0.8, Theme.effectAnimOffset * 1.1);
|
||||
if (Theme.isDepthEffect)
|
||||
return Math.max(Theme.effectAnimOffset * 0.85, 28);
|
||||
return Theme.effectAnimOffset;
|
||||
}
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewScope.overviewOpen)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: overviewScope.overviewOpen ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on y {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewScope.overviewOpen)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
|
||||
easing.bezierCurve: overviewScope.overviewOpen ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewScope.overviewOpen)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
|
||||
easing.bezierCurve: overviewScope.overviewOpen ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -202,8 +202,18 @@ Scope {
|
||||
|
||||
Item {
|
||||
id: spotlightContainer
|
||||
x: Theme.snap((parent.width - width) / 2, overlayWindow.dpr)
|
||||
y: Theme.snap((parent.height - height) / 2, overlayWindow.dpr)
|
||||
readonly property bool directionalEffect: Theme.isDirectionalEffect
|
||||
readonly property bool depthEffect: Theme.isDepthEffect
|
||||
readonly property real collapsedMotionX: depthEffect ? Theme.effectAnimOffset * 0.25 : 0
|
||||
readonly property real collapsedMotionY: {
|
||||
if (directionalEffect)
|
||||
return Math.max(height * 0.85, Theme.effectAnimOffset * 1.1);
|
||||
if (depthEffect)
|
||||
return Math.max(Theme.effectAnimOffset * 0.8, 30);
|
||||
return 0;
|
||||
}
|
||||
x: Theme.snap((parent.width - width) / 2 + (overlayWindow.shouldShowSpotlight ? 0 : collapsedMotionX), overlayWindow.dpr)
|
||||
y: Theme.snap((parent.height - height) / 2 + (overlayWindow.shouldShowSpotlight ? 0 : collapsedMotionY), overlayWindow.dpr)
|
||||
|
||||
readonly property int baseWidth: {
|
||||
switch (SettingsData.dankLauncherV2Size) {
|
||||
@@ -234,8 +244,8 @@ Scope {
|
||||
|
||||
readonly property bool animatingOut: niriOverviewScope.isClosing && overlayWindow.isSpotlightScreen
|
||||
|
||||
scale: overlayWindow.shouldShowSpotlight ? 1.0 : 0.96
|
||||
opacity: overlayWindow.shouldShowSpotlight ? 1 : 0
|
||||
scale: Theme.isDirectionalEffect ? 1 : (overlayWindow.shouldShowSpotlight ? 1.0 : Theme.effectScaleCollapsed)
|
||||
opacity: Theme.isDirectionalEffect ? 1 : (overlayWindow.shouldShowSpotlight ? 1 : 0)
|
||||
visible: overlayWindow.shouldShowSpotlight || animatingOut
|
||||
enabled: overlayWindow.shouldShowSpotlight
|
||||
|
||||
@@ -245,10 +255,11 @@ Scope {
|
||||
|
||||
Behavior on scale {
|
||||
id: scaleAnimation
|
||||
enabled: !Theme.isDirectionalEffect
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.fast
|
||||
duration: Theme.variantDuration(Theme.expressiveDurations.fast, overlayWindow.shouldShowSpotlight)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: spotlightContainer.visible ? Theme.expressiveCurves.expressiveFastSpatial : Theme.expressiveCurves.standardAccel
|
||||
easing.bezierCurve: spotlightContainer.visible ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||
onRunningChanged: {
|
||||
if (running || !spotlightContainer.animatingOut)
|
||||
return;
|
||||
@@ -258,10 +269,27 @@ Scope {
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
enabled: !Theme.isDirectionalEffect
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.fast
|
||||
duration: Theme.variantDuration(Theme.expressiveDurations.fast, overlayWindow.shouldShowSpotlight)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: spotlightContainer.visible ? Theme.expressiveCurves.expressiveFastSpatial : Theme.expressiveCurves.standardAccel
|
||||
easing.bezierCurve: spotlightContainer.visible ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: Theme.variantDuration(Theme.expressiveDurations.fast, overlayWindow.shouldShowSpotlight)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: spotlightContainer.visible ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on y {
|
||||
NumberAnimation {
|
||||
duration: Theme.variantDuration(Theme.expressiveDurations.fast, overlayWindow.shouldShowSpotlight)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: spotlightContainer.visible ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,30 +62,30 @@ Item {
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewOpen)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
|
||||
easing.bezierCurve: Theme.variantModalEnterCurve
|
||||
}
|
||||
}
|
||||
Behavior on y {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewOpen)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
|
||||
easing.bezierCurve: Theme.variantModalEnterCurve
|
||||
}
|
||||
}
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewOpen)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
|
||||
easing.bezierCurve: Theme.variantModalEnterCurve
|
||||
}
|
||||
}
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewOpen)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
|
||||
easing.bezierCurve: Theme.variantModalEnterCurve
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,16 +124,16 @@ Item {
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewOpen)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
|
||||
easing.bezierCurve: Theme.variantModalEnterCurve
|
||||
}
|
||||
}
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewOpen)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
|
||||
easing.bezierCurve: Theme.variantModalEnterCurve
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,9 +27,7 @@ quickshell -p quickshell/
|
||||
**Code formatting:**
|
||||
```bash
|
||||
qmlfmt -t 4 -i 4 -b 250 -w path/to/file.qml
|
||||
make lint-qml # Run from repo root; requires quickshell/.qmlls.ini (generated by `qs -p quickshell/`)
|
||||
# Uses Qt 6 qmllint. Override path with QMLLINT=/path/to/qmllint if needed.
|
||||
# Auto-detects `qmllint6`, Fedora's `qmllint-qt6`, `/usr/lib/qt6/bin/qmllint`, then `qmllint`.
|
||||
qmllint **/*.qml
|
||||
```
|
||||
|
||||
## Components
|
||||
|
||||
@@ -4,7 +4,6 @@ pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
@@ -93,9 +93,9 @@ Singleton {
|
||||
`;
|
||||
|
||||
monitorOffMonitor = Qt.createQmlObject(qmlString, root, "IdleService.MonitorOffMonitor");
|
||||
monitorOffMonitor.timeout = Qt.binding(() => root.monitorTimeout > 0 ? root.monitorTimeout : 86400);
|
||||
monitorOffMonitor.respectInhibitors = Qt.binding(() => root.respectInhibitors);
|
||||
monitorOffMonitor.enabled = Qt.binding(() => root._enableGate && root.enabled && root.idleMonitorAvailable && root.monitorTimeout > 0);
|
||||
monitorOffMonitor.respectInhibitors = Qt.binding(() => root.respectInhibitors);
|
||||
monitorOffMonitor.timeout = Qt.binding(() => root.monitorTimeout);
|
||||
monitorOffMonitor.isIdleChanged.connect(function () {
|
||||
if (monitorOffMonitor.isIdle) {
|
||||
if (SettingsData.fadeToDpmsEnabled) {
|
||||
@@ -112,9 +112,9 @@ Singleton {
|
||||
});
|
||||
|
||||
lockMonitor = Qt.createQmlObject(qmlString, root, "IdleService.LockMonitor");
|
||||
lockMonitor.timeout = Qt.binding(() => root.lockTimeout > 0 ? root.lockTimeout : 86400);
|
||||
lockMonitor.respectInhibitors = Qt.binding(() => root.respectInhibitors);
|
||||
lockMonitor.enabled = Qt.binding(() => root._enableGate && root.enabled && root.idleMonitorAvailable && root.lockTimeout > 0);
|
||||
lockMonitor.respectInhibitors = Qt.binding(() => root.respectInhibitors);
|
||||
lockMonitor.timeout = Qt.binding(() => root.lockTimeout);
|
||||
lockMonitor.isIdleChanged.connect(function () {
|
||||
if (lockMonitor.isIdle) {
|
||||
if (SettingsData.fadeToLockEnabled) {
|
||||
@@ -130,9 +130,9 @@ Singleton {
|
||||
});
|
||||
|
||||
suspendMonitor = Qt.createQmlObject(qmlString, root, "IdleService.SuspendMonitor");
|
||||
suspendMonitor.timeout = Qt.binding(() => root.suspendTimeout > 0 ? root.suspendTimeout : 86400);
|
||||
suspendMonitor.respectInhibitors = Qt.binding(() => root.respectInhibitors);
|
||||
suspendMonitor.enabled = Qt.binding(() => root._enableGate && root.enabled && root.idleMonitorAvailable && root.suspendTimeout > 0);
|
||||
suspendMonitor.respectInhibitors = Qt.binding(() => root.respectInhibitors);
|
||||
suspendMonitor.timeout = Qt.binding(() => root.suspendTimeout);
|
||||
suspendMonitor.isIdleChanged.connect(function () {
|
||||
if (suspendMonitor.isIdle) {
|
||||
root.requestSuspend();
|
||||
|
||||
@@ -142,7 +142,6 @@ Singleton {
|
||||
}
|
||||
ToastService.showInfo(I18n.tr("VPN configuration updated"));
|
||||
DMSNetworkService.refreshVpnProfiles();
|
||||
getConfig(uuid);
|
||||
configUpdated();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -264,7 +264,7 @@ Singleton {
|
||||
}
|
||||
|
||||
if (process) {
|
||||
process.command = ["sh", "-c", `find -L "${wallpaperDir}" -maxdepth 1 -type f \\( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.bmp" -o -iname "*.gif" -o -iname "*.webp" -o -iname "*.jxl" -o -iname "*.avif" -o -iname "*.heif" -o -iname "*.exr" \\) 2>/dev/null | sort`];
|
||||
process.command = ["sh", "-c", `find "${wallpaperDir}" -maxdepth 1 -type f \\( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.bmp" -o -iname "*.gif" -o -iname "*.webp" -o -iname "*.jxl" -o -iname "*.avif" -o -iname "*.heif" -o -iname "*.exr" \\) 2>/dev/null | sort`];
|
||||
process.targetScreenName = screenName;
|
||||
process.currentWallpaper = currentWallpaper;
|
||||
process.goToPrevious = false;
|
||||
@@ -272,7 +272,7 @@ Singleton {
|
||||
}
|
||||
} else {
|
||||
// Use global process for fallback
|
||||
cyclingProcess.command = ["sh", "-c", `find -L "${wallpaperDir}" -maxdepth 1 -type f \\( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.bmp" -o -iname "*.gif" -o -iname "*.webp" -o -iname "*.jxl" -o -iname "*.avif" -o -iname "*.heif" -o -iname "*.exr" \\) 2>/dev/null | sort`];
|
||||
cyclingProcess.command = ["sh", "-c", `find "${wallpaperDir}" -maxdepth 1 -type f \\( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.bmp" -o -iname "*.gif" -o -iname "*.webp" -o -iname "*.jxl" -o -iname "*.avif" -o -iname "*.heif" -o -iname "*.exr" \\) 2>/dev/null | sort`];
|
||||
cyclingProcess.targetScreenName = screenName || "";
|
||||
cyclingProcess.currentWallpaper = currentWallpaper;
|
||||
cyclingProcess.running = true;
|
||||
@@ -296,7 +296,7 @@ Singleton {
|
||||
}
|
||||
|
||||
if (process) {
|
||||
process.command = ["sh", "-c", `find -L "${wallpaperDir}" -maxdepth 1 -type f \\( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.bmp" -o -iname "*.gif" -o -iname "*.webp" -o -iname "*.jxl" -o -iname "*.avif" -o -iname "*.heif" -o -iname "*.exr" \\) 2>/dev/null | sort`];
|
||||
process.command = ["sh", "-c", `find "${wallpaperDir}" -maxdepth 1 -type f \\( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.bmp" -o -iname "*.gif" -o -iname "*.webp" -o -iname "*.jxl" -o -iname "*.avif" -o -iname "*.heif" -o -iname "*.exr" \\) 2>/dev/null | sort`];
|
||||
process.targetScreenName = screenName;
|
||||
process.currentWallpaper = currentWallpaper;
|
||||
process.goToPrevious = true;
|
||||
@@ -304,7 +304,7 @@ Singleton {
|
||||
}
|
||||
} else {
|
||||
// Use global process for fallback
|
||||
prevCyclingProcess.command = ["sh", "-c", `find -L "${wallpaperDir}" -maxdepth 1 -type f \\( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.bmp" -o -iname "*.gif" -o -iname "*.webp" -o -iname "*.jxl" -o -iname "*.avif" -o -iname "*.heif" -o -iname "*.exr" \\) 2>/dev/null | sort`];
|
||||
prevCyclingProcess.command = ["sh", "-c", `find "${wallpaperDir}" -maxdepth 1 -type f \\( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.bmp" -o -iname "*.gif" -o -iname "*.webp" -o -iname "*.jxl" -o -iname "*.avif" -o -iname "*.heif" -o -iname "*.exr" \\) 2>/dev/null | sort`];
|
||||
prevCyclingProcess.targetScreenName = screenName || "";
|
||||
prevCyclingProcess.currentWallpaper = currentWallpaper;
|
||||
prevCyclingProcess.running = true;
|
||||
|
||||
@@ -18,329 +18,258 @@ Singleton {
|
||||
target: DMSService
|
||||
|
||||
function onCapabilitiesReceived() {
|
||||
checkCapabilities();
|
||||
checkCapabilities()
|
||||
}
|
||||
|
||||
function onConnectionStateChanged() {
|
||||
if (DMSService.isConnected) {
|
||||
checkCapabilities();
|
||||
return;
|
||||
checkCapabilities()
|
||||
return
|
||||
}
|
||||
wlrOutputAvailable = false;
|
||||
wlrOutputAvailable = false
|
||||
}
|
||||
|
||||
function onWlrOutputStateUpdate(data) {
|
||||
if (!wlrOutputAvailable) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
handleStateUpdate(data);
|
||||
handleStateUpdate(data)
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (!DMSService.dmsAvailable) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
checkCapabilities();
|
||||
checkCapabilities()
|
||||
}
|
||||
|
||||
function checkCapabilities() {
|
||||
if (!DMSService.capabilities || !Array.isArray(DMSService.capabilities)) {
|
||||
wlrOutputAvailable = false;
|
||||
return;
|
||||
wlrOutputAvailable = false
|
||||
return
|
||||
}
|
||||
|
||||
const hasWlrOutput = DMSService.capabilities.includes("wlroutput");
|
||||
const hasWlrOutput = DMSService.capabilities.includes("wlroutput")
|
||||
if (hasWlrOutput && !wlrOutputAvailable) {
|
||||
wlrOutputAvailable = true;
|
||||
console.info("WlrOutputService: wlr-output-management capability detected");
|
||||
requestState();
|
||||
return;
|
||||
wlrOutputAvailable = true
|
||||
console.info("WlrOutputService: wlr-output-management capability detected")
|
||||
requestState()
|
||||
return
|
||||
}
|
||||
|
||||
if (!hasWlrOutput) {
|
||||
wlrOutputAvailable = false;
|
||||
wlrOutputAvailable = false
|
||||
}
|
||||
}
|
||||
|
||||
function requestState() {
|
||||
if (!DMSService.isConnected || !wlrOutputAvailable) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
DMSService.sendRequest("wlroutput.getState", null, response => {
|
||||
if (!response.result) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
handleStateUpdate(response.result);
|
||||
});
|
||||
handleStateUpdate(response.result)
|
||||
})
|
||||
}
|
||||
|
||||
function handleStateUpdate(state) {
|
||||
outputs = state.outputs || [];
|
||||
serial = state.serial || 0;
|
||||
outputs = state.outputs || []
|
||||
serial = state.serial || 0
|
||||
|
||||
if (outputs.length === 0) {
|
||||
console.warn("WlrOutputService: Received empty outputs list");
|
||||
console.warn("WlrOutputService: Received empty outputs list")
|
||||
} else {
|
||||
console.log("WlrOutputService: Updated with", outputs.length, "outputs, serial:", serial);
|
||||
console.log("WlrOutputService: Updated with", outputs.length, "outputs, serial:", serial)
|
||||
outputs.forEach((output, index) => {
|
||||
console.log("WlrOutputService: Output", index, "-", output.name, "enabled:", output.enabled, "mode:", output.currentMode ? output.currentMode.width + "x" + output.currentMode.height + "@" + (output.currentMode.refresh / 1000) + "Hz" : "none");
|
||||
});
|
||||
console.log("WlrOutputService: Output", index, "-", output.name,
|
||||
"enabled:", output.enabled,
|
||||
"mode:", output.currentMode ?
|
||||
output.currentMode.width + "x" + output.currentMode.height + "@" +
|
||||
(output.currentMode.refresh / 1000) + "Hz" : "none")
|
||||
})
|
||||
}
|
||||
stateChanged();
|
||||
stateChanged()
|
||||
}
|
||||
|
||||
function getOutput(name) {
|
||||
for (const output of outputs) {
|
||||
if (output.name === name) {
|
||||
return output;
|
||||
return output
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
function getEnabledOutputs() {
|
||||
return outputs.filter(output => output.enabled);
|
||||
return outputs.filter(output => output.enabled)
|
||||
}
|
||||
|
||||
function applyConfiguration(heads, callback) {
|
||||
if (!DMSService.isConnected || !wlrOutputAvailable) {
|
||||
if (callback) {
|
||||
callback(false, "Not connected");
|
||||
callback(false, "Not connected")
|
||||
}
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
console.log("WlrOutputService: Applying configuration for", heads.length, "outputs");
|
||||
console.log("WlrOutputService: Applying configuration for", heads.length, "outputs")
|
||||
heads.forEach((head, index) => {
|
||||
console.log("WlrOutputService: Head", index, "- name:", head.name, "enabled:", head.enabled, "modeId:", head.modeId, "customMode:", JSON.stringify(head.customMode), "position:", JSON.stringify(head.position), "scale:", head.scale, "transform:", head.transform, "adaptiveSync:", head.adaptiveSync);
|
||||
});
|
||||
console.log("WlrOutputService: Head", index, "- name:", head.name,
|
||||
"enabled:", head.enabled,
|
||||
"modeId:", head.modeId,
|
||||
"customMode:", JSON.stringify(head.customMode),
|
||||
"position:", JSON.stringify(head.position),
|
||||
"scale:", head.scale,
|
||||
"transform:", head.transform,
|
||||
"adaptiveSync:", head.adaptiveSync)
|
||||
})
|
||||
|
||||
DMSService.sendRequest("wlroutput.applyConfiguration", {
|
||||
"heads": heads
|
||||
}, response => {
|
||||
const success = !response.error;
|
||||
const message = response.error || response.result?.message || "";
|
||||
const success = !response.error
|
||||
const message = response.error || response.result?.message || ""
|
||||
|
||||
if (response.error) {
|
||||
console.warn("WlrOutputService: applyConfiguration error:", response.error);
|
||||
console.warn("WlrOutputService: applyConfiguration error:", response.error)
|
||||
} else {
|
||||
console.log("WlrOutputService: Configuration applied successfully");
|
||||
console.log("WlrOutputService: Configuration applied successfully")
|
||||
}
|
||||
|
||||
configurationApplied(success, message);
|
||||
configurationApplied(success, message)
|
||||
if (callback) {
|
||||
callback(success, message);
|
||||
callback(success, message)
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function testConfiguration(heads, callback) {
|
||||
if (!DMSService.isConnected || !wlrOutputAvailable) {
|
||||
if (callback) {
|
||||
callback(false, "Not connected");
|
||||
callback(false, "Not connected")
|
||||
}
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
console.log("WlrOutputService: Testing configuration for", heads.length, "outputs");
|
||||
console.log("WlrOutputService: Testing configuration for", heads.length, "outputs")
|
||||
|
||||
DMSService.sendRequest("wlroutput.testConfiguration", {
|
||||
"heads": heads
|
||||
}, response => {
|
||||
const success = !response.error;
|
||||
const message = response.error || response.result?.message || "";
|
||||
const success = !response.error
|
||||
const message = response.error || response.result?.message || ""
|
||||
|
||||
if (response.error) {
|
||||
console.warn("WlrOutputService: testConfiguration error:", response.error);
|
||||
console.warn("WlrOutputService: testConfiguration error:", response.error)
|
||||
} else {
|
||||
console.log("WlrOutputService: Configuration test passed");
|
||||
console.log("WlrOutputService: Configuration test passed")
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(success, message);
|
||||
callback(success, message)
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function setOutputEnabled(outputName, enabled, callback) {
|
||||
const output = getOutput(outputName);
|
||||
const output = getOutput(outputName)
|
||||
if (!output) {
|
||||
console.warn("WlrOutputService: Output not found:", outputName);
|
||||
console.warn("WlrOutputService: Output not found:", outputName)
|
||||
if (callback) {
|
||||
callback(false, "Output not found");
|
||||
callback(false, "Output not found")
|
||||
}
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
const heads = [
|
||||
{
|
||||
"name": outputName,
|
||||
"enabled": enabled
|
||||
}
|
||||
];
|
||||
const heads = [{
|
||||
"name": outputName,
|
||||
"enabled": enabled
|
||||
}]
|
||||
|
||||
if (enabled && output.currentMode) {
|
||||
heads[0].modeId = output.currentMode.id;
|
||||
heads[0].modeId = output.currentMode.id
|
||||
}
|
||||
|
||||
applyConfiguration(heads, callback);
|
||||
applyConfiguration(heads, callback)
|
||||
}
|
||||
|
||||
function setOutputMode(outputName, modeId, callback) {
|
||||
const heads = [
|
||||
{
|
||||
"name": outputName,
|
||||
"enabled": true,
|
||||
"modeId": modeId
|
||||
}
|
||||
];
|
||||
const heads = [{
|
||||
"name": outputName,
|
||||
"enabled": true,
|
||||
"modeId": modeId
|
||||
}]
|
||||
|
||||
applyConfiguration(heads, callback);
|
||||
applyConfiguration(heads, callback)
|
||||
}
|
||||
|
||||
function setOutputCustomMode(outputName, width, height, refresh, callback) {
|
||||
const heads = [
|
||||
{
|
||||
"name": outputName,
|
||||
"enabled": true,
|
||||
"customMode": {
|
||||
"width": width,
|
||||
"height": height,
|
||||
"refresh": refresh
|
||||
}
|
||||
const heads = [{
|
||||
"name": outputName,
|
||||
"enabled": true,
|
||||
"customMode": {
|
||||
"width": width,
|
||||
"height": height,
|
||||
"refresh": refresh
|
||||
}
|
||||
];
|
||||
}]
|
||||
|
||||
applyConfiguration(heads, callback);
|
||||
applyConfiguration(heads, callback)
|
||||
}
|
||||
|
||||
function setOutputPosition(outputName, x, y, callback) {
|
||||
const heads = [
|
||||
{
|
||||
"name": outputName,
|
||||
"enabled": true,
|
||||
"position": {
|
||||
"x": x,
|
||||
"y": y
|
||||
}
|
||||
const heads = [{
|
||||
"name": outputName,
|
||||
"enabled": true,
|
||||
"position": {
|
||||
"x": x,
|
||||
"y": y
|
||||
}
|
||||
];
|
||||
}]
|
||||
|
||||
applyConfiguration(heads, callback);
|
||||
applyConfiguration(heads, callback)
|
||||
}
|
||||
|
||||
function setOutputScale(outputName, scale, callback) {
|
||||
const heads = [
|
||||
{
|
||||
"name": outputName,
|
||||
"enabled": true,
|
||||
"scale": scale
|
||||
}
|
||||
];
|
||||
const heads = [{
|
||||
"name": outputName,
|
||||
"enabled": true,
|
||||
"scale": scale
|
||||
}]
|
||||
|
||||
applyConfiguration(heads, callback);
|
||||
applyConfiguration(heads, callback)
|
||||
}
|
||||
|
||||
function setOutputTransform(outputName, transform, callback) {
|
||||
const heads = [
|
||||
{
|
||||
"name": outputName,
|
||||
"enabled": true,
|
||||
"transform": transform
|
||||
}
|
||||
];
|
||||
const heads = [{
|
||||
"name": outputName,
|
||||
"enabled": true,
|
||||
"transform": transform
|
||||
}]
|
||||
|
||||
applyConfiguration(heads, callback);
|
||||
applyConfiguration(heads, callback)
|
||||
}
|
||||
|
||||
function setOutputAdaptiveSync(outputName, state, callback) {
|
||||
const heads = [
|
||||
{
|
||||
"name": outputName,
|
||||
"enabled": true,
|
||||
"adaptiveSync": state
|
||||
}
|
||||
];
|
||||
const heads = [{
|
||||
"name": outputName,
|
||||
"enabled": true,
|
||||
"adaptiveSync": state
|
||||
}]
|
||||
|
||||
applyConfiguration(heads, callback);
|
||||
applyConfiguration(heads, callback)
|
||||
}
|
||||
|
||||
function configureOutput(config, callback) {
|
||||
const heads = [config];
|
||||
applyConfiguration(heads, callback);
|
||||
const heads = [config]
|
||||
applyConfiguration(heads, callback)
|
||||
}
|
||||
|
||||
function configureMultipleOutputs(configs, callback) {
|
||||
applyConfiguration(configs, callback);
|
||||
}
|
||||
|
||||
// High-level apply matching the generateOutputsConfig() pattern used by
|
||||
// NiriService, HyprlandService and DwlService. Instead of writing a
|
||||
// config file, the changes are applied directly via the
|
||||
// wlr-output-management protocol.
|
||||
function applyOutputsConfig(outputsData, connectedOutputs) {
|
||||
if (!wlrOutputAvailable)
|
||||
return;
|
||||
const heads = [];
|
||||
for (const name in outputsData) {
|
||||
if (!connectedOutputs[name])
|
||||
continue;
|
||||
const output = outputsData[name];
|
||||
const mode = (output.modes && output.current_mode >= 0) ? output.modes[output.current_mode] : null;
|
||||
const enabled = !!mode;
|
||||
const head = {
|
||||
"name": name,
|
||||
"enabled": enabled
|
||||
};
|
||||
|
||||
if (enabled) {
|
||||
if (mode.id !== undefined)
|
||||
head.modeId = mode.id;
|
||||
else
|
||||
head.customMode = {
|
||||
"width": mode.width,
|
||||
"height": mode.height,
|
||||
"refresh": mode.refresh_rate
|
||||
};
|
||||
|
||||
if (output.logical) {
|
||||
head.position = {
|
||||
"x": output.logical.x ?? 0,
|
||||
"y": output.logical.y ?? 0
|
||||
};
|
||||
head.scale = output.logical.scale ?? 1.0;
|
||||
head.transform = transformFromName(output.logical.transform);
|
||||
}
|
||||
}
|
||||
heads.push(head);
|
||||
}
|
||||
|
||||
if (heads.length > 0)
|
||||
applyConfiguration(heads);
|
||||
}
|
||||
|
||||
function transformFromName(name) {
|
||||
switch (name) {
|
||||
case "Normal":
|
||||
return 0;
|
||||
case "90":
|
||||
return 1;
|
||||
case "180":
|
||||
return 2;
|
||||
case "270":
|
||||
return 3;
|
||||
case "Flipped":
|
||||
return 4;
|
||||
case "Flipped90":
|
||||
return 5;
|
||||
case "Flipped180":
|
||||
return 6;
|
||||
case "Flipped270":
|
||||
return 7;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
applyConfiguration(configs, callback)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,12 +121,7 @@ Item {
|
||||
dropdownMenu.close();
|
||||
return;
|
||||
}
|
||||
|
||||
dropdownMenu.open();
|
||||
|
||||
let currentIndex = root.options.indexOf(root.currentValue);
|
||||
listView.positionViewAtIndex(currentIndex, ListView.Beginning);
|
||||
|
||||
const pos = dropdown.mapToItem(Overlay.overlay, 0, 0);
|
||||
const popupW = dropdownMenu.width;
|
||||
const popupH = dropdownMenu.height;
|
||||
|
||||
@@ -20,10 +20,10 @@ Item {
|
||||
property string triggerSection: ""
|
||||
property string positioning: "center"
|
||||
property int animationDuration: Theme.popoutAnimationDuration
|
||||
property real animationScaleCollapsed: 0.96
|
||||
property real animationOffset: Theme.spacingL
|
||||
property list<real> animationEnterCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
||||
property list<real> animationExitCurve: Theme.expressiveCurves.emphasized
|
||||
property real animationScaleCollapsed: Theme.effectScaleCollapsed
|
||||
property real animationOffset: Theme.effectAnimOffset
|
||||
property list<real> animationEnterCurve: Theme.variantPopoutEnterCurve
|
||||
property list<real> animationExitCurve: Theme.variantPopoutExitCurve
|
||||
property bool suspendShadowWhileResizing: false
|
||||
property bool shouldBeVisible: false
|
||||
property var customKeyboardFocus: null
|
||||
@@ -74,6 +74,7 @@ Item {
|
||||
signal backgroundClicked
|
||||
|
||||
property var _lastOpenedScreen: null
|
||||
property bool isClosing: false
|
||||
|
||||
property int effectiveBarPosition: 0
|
||||
property real effectiveBarBottomGap: 0
|
||||
@@ -156,10 +157,14 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
property bool animationsEnabled: true
|
||||
|
||||
function open() {
|
||||
if (!screen)
|
||||
return;
|
||||
closeTimer.stop();
|
||||
isClosing = false;
|
||||
animationsEnabled = false;
|
||||
|
||||
// Snapshot mask geometry
|
||||
_frozenMaskX = maskX;
|
||||
@@ -174,12 +179,22 @@ Item {
|
||||
}
|
||||
_lastOpenedScreen = screen;
|
||||
|
||||
shouldBeVisible = true;
|
||||
if (contentContainer) {
|
||||
contentContainer.animX = Theme.snap(contentContainer.offsetX, root.dpr);
|
||||
contentContainer.animY = Theme.snap(contentContainer.offsetY, root.dpr);
|
||||
contentContainer.scaleValue = root.animationScaleCollapsed;
|
||||
}
|
||||
|
||||
if (useBackgroundWindow) {
|
||||
_surfaceMarginLeft = alignedX - shadowBuffer;
|
||||
_surfaceW = alignedWidth + shadowBuffer * 2;
|
||||
backgroundWindow.visible = true;
|
||||
}
|
||||
contentWindow.visible = true;
|
||||
|
||||
Qt.callLater(() => {
|
||||
animationsEnabled = true;
|
||||
shouldBeVisible = true;
|
||||
if (shouldBeVisible && screen) {
|
||||
if (useBackgroundWindow)
|
||||
backgroundWindow.visible = true;
|
||||
@@ -191,6 +206,7 @@ Item {
|
||||
}
|
||||
|
||||
function close() {
|
||||
isClosing = true;
|
||||
shouldBeVisible = false;
|
||||
_primeContent = false;
|
||||
PopoutManager.popoutChanged();
|
||||
@@ -222,9 +238,10 @@ Item {
|
||||
|
||||
Timer {
|
||||
id: closeTimer
|
||||
interval: animationDuration
|
||||
interval: Theme.variantCloseInterval(animationDuration)
|
||||
onTriggered: {
|
||||
if (!shouldBeVisible) {
|
||||
isClosing = false;
|
||||
contentWindow.visible = false;
|
||||
if (useBackgroundWindow)
|
||||
backgroundWindow.visible = false;
|
||||
@@ -241,7 +258,16 @@ Item {
|
||||
readonly property var shadowLevel: Theme.elevationLevel3
|
||||
readonly property real shadowFallbackOffset: 6
|
||||
readonly property real shadowRenderPadding: (Theme.elevationEnabled && SettingsData.popoutElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, effectiveShadowDirection, shadowFallbackOffset, 8, 16) : 0
|
||||
readonly property real shadowMotionPadding: Math.max(0, animationOffset)
|
||||
readonly property real shadowMotionPadding: {
|
||||
if (Theme.isDirectionalEffect) {
|
||||
if (typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode !== 0)
|
||||
return 16; // Slide Behind and Roll Out do not add animationOffset, enabling strict Wayland clipping.
|
||||
return Math.max(0, animationOffset) + 16;
|
||||
}
|
||||
if (Theme.isDepthEffect)
|
||||
return Math.max(0, animationOffset) + 8;
|
||||
return Math.max(0, animationOffset);
|
||||
}
|
||||
readonly property real shadowBuffer: Theme.snap(shadowRenderPadding + shadowMotionPadding, dpr)
|
||||
readonly property real alignedWidth: Theme.px(popupWidth, dpr)
|
||||
readonly property real alignedHeight: Theme.px(popupHeight, dpr)
|
||||
@@ -353,6 +379,10 @@ Item {
|
||||
|
||||
mask: Region {
|
||||
item: maskRect
|
||||
Region {
|
||||
item: contentExclusionRect
|
||||
intersection: Intersection.Subtract
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -361,26 +391,70 @@ Item {
|
||||
color: "transparent"
|
||||
x: root._frozenMaskX
|
||||
y: root._frozenMaskY
|
||||
width: (shouldBeVisible && backgroundInteractive) ? root._frozenMaskWidth : 0
|
||||
height: (shouldBeVisible && backgroundInteractive) ? root._frozenMaskHeight : 0
|
||||
width: (backgroundWindow.visible && backgroundInteractive) ? root._frozenMaskWidth : 0
|
||||
height: (backgroundWindow.visible && backgroundInteractive) ? root._frozenMaskHeight : 0
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
Item {
|
||||
id: contentExclusionRect
|
||||
visible: false
|
||||
x: root.alignedX
|
||||
y: root.alignedY
|
||||
width: root.alignedWidth
|
||||
height: root.alignedHeight
|
||||
}
|
||||
|
||||
Item {
|
||||
id: outsideClickCatcher
|
||||
x: root._frozenMaskX
|
||||
y: root._frozenMaskY
|
||||
width: root._frozenMaskWidth
|
||||
height: root._frozenMaskHeight
|
||||
hoverEnabled: false
|
||||
enabled: shouldBeVisible && backgroundInteractive
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onClicked: mouse => {
|
||||
const clickX = mouse.x + root._frozenMaskX;
|
||||
const clickY = mouse.y + root._frozenMaskY;
|
||||
const outsideContent = clickX < root.alignedX || clickX > root.alignedX + root.alignedWidth || clickY < root.alignedY || clickY > root.alignedY + root.alignedHeight;
|
||||
enabled: root.shouldBeVisible && root.backgroundInteractive
|
||||
|
||||
if (!outsideContent)
|
||||
return;
|
||||
backgroundClicked();
|
||||
readonly property real contentLeft: Math.max(0, root.alignedX - x)
|
||||
readonly property real contentTop: Math.max(0, root.alignedY - y)
|
||||
readonly property real contentRight: Math.min(width, contentLeft + root.alignedWidth)
|
||||
readonly property real contentBottom: Math.min(height, contentTop + root.alignedHeight)
|
||||
|
||||
MouseArea {
|
||||
x: 0
|
||||
y: 0
|
||||
width: outsideClickCatcher.width
|
||||
height: Math.max(0, outsideClickCatcher.contentTop)
|
||||
enabled: parent.enabled
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onClicked: root.backgroundClicked()
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
x: 0
|
||||
y: outsideClickCatcher.contentBottom
|
||||
width: outsideClickCatcher.width
|
||||
height: Math.max(0, outsideClickCatcher.height - outsideClickCatcher.contentBottom)
|
||||
enabled: parent.enabled
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onClicked: root.backgroundClicked()
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
x: 0
|
||||
y: outsideClickCatcher.contentTop
|
||||
width: Math.max(0, outsideClickCatcher.contentLeft)
|
||||
height: Math.max(0, outsideClickCatcher.contentBottom - outsideClickCatcher.contentTop)
|
||||
enabled: parent.enabled
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onClicked: root.backgroundClicked()
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
x: outsideClickCatcher.contentRight
|
||||
y: outsideClickCatcher.contentTop
|
||||
width: Math.max(0, outsideClickCatcher.width - outsideClickCatcher.contentRight)
|
||||
height: Math.max(0, outsideClickCatcher.contentBottom - outsideClickCatcher.contentTop)
|
||||
enabled: parent.enabled
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onClicked: root.backgroundClicked()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,7 +499,6 @@ Item {
|
||||
}
|
||||
|
||||
readonly property bool _fullHeight: useBackgroundWindow && root.fullHeightSurface
|
||||
|
||||
anchors {
|
||||
left: true
|
||||
top: true
|
||||
@@ -483,12 +556,70 @@ Item {
|
||||
readonly property bool barBottom: effectiveBarPosition === SettingsData.Position.Bottom
|
||||
readonly property bool barLeft: effectiveBarPosition === SettingsData.Position.Left
|
||||
readonly property bool barRight: effectiveBarPosition === SettingsData.Position.Right
|
||||
readonly property real offsetX: barLeft ? root.animationOffset : (barRight ? -root.animationOffset : 0)
|
||||
readonly property real offsetY: barBottom ? -root.animationOffset : (barTop ? root.animationOffset : 0)
|
||||
readonly property bool directionalEffect: Theme.isDirectionalEffect
|
||||
readonly property bool depthEffect: Theme.isDepthEffect
|
||||
readonly property real directionalTravelX: Math.max(root.animationOffset, root.alignedWidth + Theme.spacingL)
|
||||
readonly property real directionalTravelY: Math.max(root.animationOffset, root.alignedHeight + Theme.spacingL)
|
||||
readonly property real depthTravel: Math.max(root.animationOffset * 0.7, 28)
|
||||
readonly property real sectionTilt: (triggerSection === "left" ? -1 : (triggerSection === "right" ? 1 : 0))
|
||||
readonly property real offsetX: {
|
||||
if (directionalEffect) {
|
||||
if (typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2)
|
||||
return 0;
|
||||
if (barLeft)
|
||||
return -directionalTravelX;
|
||||
if (barRight)
|
||||
return directionalTravelX;
|
||||
if (barTop || barBottom)
|
||||
return 0;
|
||||
return sectionTilt * directionalTravelX * 0.2;
|
||||
}
|
||||
if (depthEffect) {
|
||||
if (barLeft)
|
||||
return -depthTravel;
|
||||
if (barRight)
|
||||
return depthTravel;
|
||||
if (barTop || barBottom)
|
||||
return 0;
|
||||
return sectionTilt * depthTravel * 0.2;
|
||||
}
|
||||
return barLeft ? root.animationOffset : (barRight ? -root.animationOffset : 0);
|
||||
}
|
||||
readonly property real offsetY: {
|
||||
if (directionalEffect) {
|
||||
if (typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2)
|
||||
return 0;
|
||||
if (barBottom)
|
||||
return directionalTravelY;
|
||||
if (barTop)
|
||||
return -directionalTravelY;
|
||||
if (barLeft || barRight)
|
||||
return 0;
|
||||
return directionalTravelY;
|
||||
}
|
||||
if (depthEffect) {
|
||||
if (barBottom)
|
||||
return depthTravel;
|
||||
if (barTop)
|
||||
return -depthTravel;
|
||||
if (barLeft || barRight)
|
||||
return 0;
|
||||
return depthTravel;
|
||||
}
|
||||
return barBottom ? -root.animationOffset : (barTop ? root.animationOffset : 0);
|
||||
}
|
||||
|
||||
property real animX: 0
|
||||
property real animY: 0
|
||||
property real scaleValue: root.animationScaleCollapsed
|
||||
|
||||
readonly property real computedScaleCollapsed: (typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2 && Theme.isDirectionalEffect) ? 0.0 : root.animationScaleCollapsed
|
||||
property real scaleValue: computedScaleCollapsed
|
||||
|
||||
Component.onCompleted: {
|
||||
animX = Theme.snap(root.shouldBeVisible ? 0 : offsetX, root.dpr);
|
||||
animY = Theme.snap(root.shouldBeVisible ? 0 : offsetY, root.dpr);
|
||||
scaleValue = root.shouldBeVisible ? 1.0 : computedScaleCollapsed;
|
||||
}
|
||||
|
||||
onOffsetXChanged: animX = Theme.snap(root.shouldBeVisible ? 0 : offsetX, root.dpr)
|
||||
onOffsetYChanged: animY = Theme.snap(root.shouldBeVisible ? 0 : offsetY, root.dpr)
|
||||
@@ -498,89 +629,131 @@ Item {
|
||||
function onShouldBeVisibleChanged() {
|
||||
contentContainer.animX = Theme.snap(root.shouldBeVisible ? 0 : contentContainer.offsetX, root.dpr);
|
||||
contentContainer.animY = Theme.snap(root.shouldBeVisible ? 0 : contentContainer.offsetY, root.dpr);
|
||||
contentContainer.scaleValue = root.shouldBeVisible ? 1.0 : root.animationScaleCollapsed;
|
||||
contentContainer.scaleValue = root.shouldBeVisible ? 1.0 : contentContainer.computedScaleCollapsed;
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on animX {
|
||||
enabled: root.animationsEnabled
|
||||
NumberAnimation {
|
||||
duration: root.animationDuration
|
||||
duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on animY {
|
||||
enabled: root.animationsEnabled
|
||||
NumberAnimation {
|
||||
duration: root.animationDuration
|
||||
duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scaleValue {
|
||||
enabled: root.animationsEnabled
|
||||
NumberAnimation {
|
||||
duration: root.animationDuration
|
||||
duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
ElevationShadow {
|
||||
id: shadowSource
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
opacity: contentWrapper.opacity
|
||||
scale: contentWrapper.scale
|
||||
x: contentWrapper.x
|
||||
y: contentWrapper.y
|
||||
level: root.shadowLevel
|
||||
direction: root.effectiveShadowDirection
|
||||
fallbackOffset: root.shadowFallbackOffset
|
||||
targetRadius: Theme.cornerRadius
|
||||
targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" && !(root.suspendShadowWhileResizing && root._resizeActive)
|
||||
}
|
||||
|
||||
Item {
|
||||
id: contentWrapper
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
opacity: shouldBeVisible ? 1 : 0
|
||||
visible: opacity > 0
|
||||
scale: contentContainer.scaleValue
|
||||
x: Theme.snap(contentContainer.animX + (parent.width - width) * (1 - contentContainer.scaleValue) * 0.5, root.dpr)
|
||||
y: Theme.snap(contentContainer.animY + (parent.height - height) * (1 - contentContainer.scaleValue) * 0.5, root.dpr)
|
||||
id: directionalClipMask
|
||||
|
||||
layer.enabled: contentWrapper.opacity < 1
|
||||
layer.smooth: false
|
||||
layer.textureSize: root.dpr > 1 ? Qt.size(Math.ceil(width * root.dpr), Math.ceil(height * root.dpr)) : Qt.size(0, 0)
|
||||
readonly property bool shouldClip: typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode > 0 && Theme.isDirectionalEffect
|
||||
readonly property real clipOversize: 1000
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: animationDuration
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
}
|
||||
clip: shouldClip
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: 0
|
||||
}
|
||||
// Bound the clipping strictly to the bar side, allowing massive overflow on the other 3 sides for shadows
|
||||
x: shouldClip ? (contentContainer.barRight ? -clipOversize : (contentContainer.barLeft ? 0 : -clipOversize)) : 0
|
||||
y: shouldClip ? (contentContainer.barBottom ? -clipOversize : (contentContainer.barTop ? 0 : -clipOversize)) : 0
|
||||
|
||||
Loader {
|
||||
id: contentLoader
|
||||
anchors.fill: parent
|
||||
active: root._primeContent || shouldBeVisible || contentWindow.visible
|
||||
asynchronous: false
|
||||
}
|
||||
}
|
||||
}
|
||||
width: shouldClip ? parent.width + clipOversize + (contentContainer.barLeft || contentContainer.barRight ? 0 : clipOversize) : parent.width
|
||||
height: shouldClip ? parent.height + clipOversize + (contentContainer.barTop || contentContainer.barBottom ? 0 : clipOversize) : parent.height
|
||||
|
||||
Item {
|
||||
id: aligner
|
||||
readonly property real baseWidth: contentContainer.width
|
||||
readonly property real baseHeight: contentContainer.height
|
||||
readonly property bool isRollOut: typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2 && Theme.isDirectionalEffect
|
||||
|
||||
x: (directionalClipMask.x !== 0 ? -directionalClipMask.x : 0) + (isRollOut && contentContainer.barRight ? baseWidth * (1 - contentContainer.scaleValue) : 0)
|
||||
y: (directionalClipMask.y !== 0 ? -directionalClipMask.y : 0) + (isRollOut && contentContainer.barBottom ? baseHeight * (1 - contentContainer.scaleValue) : 0)
|
||||
width: isRollOut && (contentContainer.barLeft || contentContainer.barRight) ? Math.max(0, baseWidth * contentContainer.scaleValue) : baseWidth
|
||||
height: isRollOut && (contentContainer.barTop || contentContainer.barBottom) ? Math.max(0, baseHeight * contentContainer.scaleValue) : baseHeight
|
||||
|
||||
clip: isRollOut
|
||||
|
||||
Item {
|
||||
id: unrollCounteract
|
||||
x: aligner.isRollOut && contentContainer.barRight ? -(aligner.baseWidth * (1 - contentContainer.scaleValue)) : 0
|
||||
y: aligner.isRollOut && contentContainer.barBottom ? -(aligner.baseHeight * (1 - contentContainer.scaleValue)) : 0
|
||||
width: aligner.baseWidth
|
||||
height: aligner.baseHeight
|
||||
|
||||
ElevationShadow {
|
||||
id: shadowSource
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
opacity: contentWrapper.opacity
|
||||
scale: contentWrapper.scale
|
||||
x: contentWrapper.x
|
||||
y: contentWrapper.y
|
||||
level: root.shadowLevel
|
||||
direction: root.effectiveShadowDirection
|
||||
fallbackOffset: root.shadowFallbackOffset
|
||||
targetRadius: Theme.cornerRadius
|
||||
targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" && !(root.suspendShadowWhileResizing && root._resizeActive)
|
||||
}
|
||||
|
||||
Item {
|
||||
id: contentWrapper
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
opacity: Theme.isDirectionalEffect ? 1 : (shouldBeVisible ? 1 : 0)
|
||||
visible: opacity > 0
|
||||
|
||||
scale: aligner.isRollOut ? 1.0 : contentContainer.scaleValue
|
||||
x: Theme.snap(contentContainer.animX + (parent.width - width) * (1 - scale) * 0.5, root.dpr)
|
||||
y: Theme.snap(contentContainer.animY + (parent.height - height) * (1 - scale) * 0.5, root.dpr)
|
||||
|
||||
layer.enabled: contentWrapper.opacity < 1
|
||||
layer.smooth: false
|
||||
layer.textureSize: root.dpr > 1 ? Qt.size(Math.ceil(width * root.dpr), Math.ceil(height * root.dpr)) : Qt.size(0, 0)
|
||||
|
||||
Behavior on opacity {
|
||||
enabled: !Theme.isDirectionalEffect
|
||||
NumberAnimation {
|
||||
duration: Math.round(Theme.variantDuration(animationDuration, shouldBeVisible) * Theme.variantOpacityDurationScale)
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: 0
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: contentLoader
|
||||
anchors.fill: parent
|
||||
active: root._primeContent || shouldBeVisible || contentWindow.visible
|
||||
asynchronous: false
|
||||
}
|
||||
} // closes contentWrapper
|
||||
} // closes unrollCounteract
|
||||
} // closes aligner
|
||||
} // closes directionalClipMask
|
||||
} // closes contentContainer
|
||||
|
||||
Item {
|
||||
id: focusHelper
|
||||
|
||||
@@ -239,7 +239,7 @@ Item {
|
||||
StyledRect {
|
||||
id: valueTooltip
|
||||
|
||||
width: tooltipText.reservedWidth + Theme.spacingS * 2
|
||||
width: tooltipText.contentWidth + Theme.spacingS * 2
|
||||
height: tooltipText.contentHeight + Theme.spacingXS * 2
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainer
|
||||
@@ -251,22 +251,10 @@ Item {
|
||||
visible: slider.alwaysShowValue ? slider.showValue : ((sliderMouseArea.containsMouse && slider.showValue) || (slider.isDragging && slider.showValue))
|
||||
opacity: visible ? 1 : 0
|
||||
|
||||
NumericText {
|
||||
StyledText {
|
||||
id: tooltipText
|
||||
|
||||
text: (slider.valueOverride >= 0 ? Math.round(slider.valueOverride) : slider.value) + slider.unit
|
||||
reserveText: {
|
||||
let widest = "";
|
||||
const samples = [slider.minimum, slider.maximum];
|
||||
if (slider.valueOverride >= 0)
|
||||
samples.push(slider.valueOverride);
|
||||
for (let i = 0; i < samples.length; i++) {
|
||||
const candidate = Math.round(samples[i]) + slider.unit;
|
||||
if (candidate.length > widest.length)
|
||||
widest = candidate;
|
||||
}
|
||||
return widest;
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
|
||||
StyledText {
|
||||
id: root
|
||||
|
||||
property string reserveText: ""
|
||||
readonly property real reservedWidth: reserveText !== "" ? Math.max(contentWidth, reserveMetrics.width) : contentWidth
|
||||
|
||||
isMonospace: true
|
||||
wrapMode: Text.NoWrap
|
||||
|
||||
StyledTextMetrics {
|
||||
id: reserveMetrics
|
||||
isMonospace: root.isMonospace
|
||||
font.pixelSize: root.font.pixelSize
|
||||
font.family: root.font.family
|
||||
font.weight: root.font.weight
|
||||
font.hintingPreference: root.font.hintingPreference
|
||||
text: root.reserveText
|
||||
}
|
||||
}
|
||||
@@ -75,6 +75,11 @@ Rectangle {
|
||||
"label": I18n.tr("Auth Type"),
|
||||
"value": data["connection-type"]
|
||||
});
|
||||
fields.push({
|
||||
"key": "auto",
|
||||
"label": I18n.tr("Autoconnect"),
|
||||
"value": configData.autoconnect ? I18n.tr("Yes") : I18n.tr("No")
|
||||
});
|
||||
return fields;
|
||||
}
|
||||
|
||||
@@ -266,16 +271,6 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Autoconnect")
|
||||
checked: configData ? (configData.autoconnect || false) : false
|
||||
visible: !VPNService.configLoading && configData !== null
|
||||
onToggled: checked => {
|
||||
VPNService.updateConfig(profile.uuid, {autoconnect: checked});
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 1
|
||||
height: Theme.spacingXS
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#%PAM-1.0
|
||||
|
||||
auth required pam_fprintd.so max-tries=1 timeout=5
|
||||
auth required pam_fprintd.so max-tries=1
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
[templates.dmszed]
|
||||
input_path = "SHELL_DIR/matugen/templates/dank-zed.json"
|
||||
output_path = "CONFIG_DIR/zed/themes/dank-zed-theme.json"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
[colors-dark]
|
||||
[colors]
|
||||
foreground={{colors.on_surface.default.hex_stripped}}
|
||||
background={{colors.background.default.hex_stripped}}
|
||||
selection-foreground={{colors.on_surface.default.hex_stripped}}
|
||||
|
||||
@@ -26,8 +26,7 @@ LANGUAGES = {
|
||||
"hu": "hu.json",
|
||||
"fa": "fa.json",
|
||||
"fr": "fr.json",
|
||||
"nl": "nl.json",
|
||||
"ru": "ru.json"
|
||||
"nl": "nl.json"
|
||||
}
|
||||
|
||||
def error(msg):
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
script_dir="$(
|
||||
CDPATH=''
|
||||
cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd
|
||||
)"
|
||||
repo_root="$(
|
||||
CDPATH=''
|
||||
cd -- "${script_dir}/../.." && pwd
|
||||
)"
|
||||
quickshell_dir="${repo_root}/quickshell"
|
||||
qmlls_config="${quickshell_dir}/.qmlls.ini"
|
||||
|
||||
# Resolve qmllint: honour QMLLINT, then try common Qt 6 binary names and
|
||||
# install paths, and finally bare qmllint. We need the Qt 6 build (>= 6.x)
|
||||
# because older Qt 5 qmllint doesn't understand --ignore-settings / -W.
|
||||
resolve_qmllint() {
|
||||
if [[ -n "${QMLLINT:-}" ]]; then
|
||||
printf '%s\n' "${QMLLINT}"
|
||||
return
|
||||
fi
|
||||
local candidate
|
||||
for candidate in qmllint6 qmllint-qt6 /usr/lib/qt6/bin/qmllint qmllint; do
|
||||
if command -v -- "${candidate}" >/dev/null 2>&1; then
|
||||
printf '%s\n' "${candidate}"
|
||||
return
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
if ! qmllint_bin="$(resolve_qmllint)"; then
|
||||
printf 'error: qmllint (Qt 6) not found in PATH (override with QMLLINT=/path/to/qmllint)\n' >&2
|
||||
exit 127
|
||||
fi
|
||||
|
||||
print_broken_qmlls_link() {
|
||||
local target=""
|
||||
target="$(readlink -- "${qmlls_config}" 2>/dev/null || true)"
|
||||
printf 'error: %s is a broken symlink. lint-qml requires a live Quickshell tooling VFS.\n' "${qmlls_config}" >&2
|
||||
if [[ -n "${target}" ]]; then
|
||||
printf 'Broken target: %s\n' "${target}" >&2
|
||||
fi
|
||||
print_vfs_recovery
|
||||
}
|
||||
|
||||
trim_ini_value() {
|
||||
local value="$1"
|
||||
value="${value#\"}"
|
||||
value="${value%\"}"
|
||||
printf '%s\n' "${value}"
|
||||
}
|
||||
|
||||
read_ini_value() {
|
||||
local key="$1"
|
||||
local file="$2"
|
||||
local raw
|
||||
|
||||
raw="$(sed -n "s/^${key}=//p" "${file}" | head -n 1)"
|
||||
if [[ -z "${raw}" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
trim_ini_value "${raw}"
|
||||
}
|
||||
|
||||
print_vfs_recovery() {
|
||||
printf 'Generate it by starting the local shell config once, for example:\n' >&2
|
||||
printf ' dms -c %q run\n' "${quickshell_dir}" >&2
|
||||
printf ' qs -p %q\n' "${quickshell_dir}" >&2
|
||||
}
|
||||
|
||||
if [[ -L "${qmlls_config}" && ! -e "${qmlls_config}" ]]; then
|
||||
print_broken_qmlls_link
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -e "${qmlls_config}" ]]; then
|
||||
printf 'error: %s is missing. lint-qml requires the Quickshell tooling VFS.\n' "${qmlls_config}" >&2
|
||||
print_vfs_recovery
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! build_dir="$(read_ini_value "buildDir" "${qmlls_config}")"; then
|
||||
printf 'error: %s does not contain a buildDir entry.\n' "${qmlls_config}" >&2
|
||||
print_vfs_recovery
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! import_paths_raw="$(read_ini_value "importPaths" "${qmlls_config}")"; then
|
||||
printf 'error: %s does not contain an importPaths entry.\n' "${qmlls_config}" >&2
|
||||
print_vfs_recovery
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -d "${build_dir}" || ! -f "${build_dir}/qs/qmldir" ]]; then
|
||||
printf 'error: Quickshell tooling VFS is missing or stale: %s\n' "${build_dir}" >&2
|
||||
print_vfs_recovery
|
||||
exit 1
|
||||
fi
|
||||
|
||||
targets=(
|
||||
"${quickshell_dir}/shell.qml"
|
||||
"${quickshell_dir}/DMSShell.qml"
|
||||
"${quickshell_dir}/DMSGreeter.qml"
|
||||
)
|
||||
|
||||
qmllint_args=(
|
||||
--ignore-settings
|
||||
-W 0
|
||||
-I "${build_dir}"
|
||||
)
|
||||
|
||||
IFS=':' read -r -a import_paths <<< "${import_paths_raw}"
|
||||
for path in "${import_paths[@]}"; do
|
||||
if [[ -n "${path}" ]]; then
|
||||
qmllint_args+=(-I "${path}")
|
||||
fi
|
||||
done
|
||||
|
||||
if ! output="$("${qmllint_bin}" "${qmllint_args[@]}" "${targets[@]}" 2>&1)"; then
|
||||
printf '%s\n' "${output}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n "${output}" ]]; then
|
||||
printf '%s\n' "${output}"
|
||||
fi
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user