mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-05-12 23:32:50 -04:00
Compare commits
2 Commits
e86227f05f
..
anims
| Author | SHA1 | Date | |
|---|---|---|---|
| 62bf9c6efe | |||
| 61a77bd186 |
@@ -20,7 +20,7 @@ jobs:
|
|||||||
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
token: ${{ steps.app_token.outputs.token }}
|
token: ${{ steps.app_token.outputs.token }}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install flatpak
|
- name: Install flatpak
|
||||||
run: sudo apt update && sudo apt install -y 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
|
run: sudo flatpak install -y org.freedesktop.Platform/x86_64/24.08 app.zen_browser.zen
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v6
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: ./core/go.mod
|
go-version-file: ./core/go.mod
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install flatpak
|
- name: Install flatpak
|
||||||
run: sudo apt update && sudo apt install -y 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
|
run: sudo flatpak install -y org.freedesktop.Platform/x86_64/24.08 app.zen_browser.zen
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v6
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: core/go.mod
|
go-version-file: core/go.mod
|
||||||
|
|
||||||
|
|||||||
@@ -32,13 +32,13 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ inputs.tag }}
|
ref: ${{ inputs.tag }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v6
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: ./core/go.mod
|
go-version-file: ./core/go.mod
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload artifacts (${{ matrix.arch }})
|
- name: Upload artifacts (${{ matrix.arch }})
|
||||||
if: matrix.arch == 'arm64'
|
if: matrix.arch == 'arm64'
|
||||||
uses: actions/upload-artifact@v5
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: core-assets-${{ matrix.arch }}
|
name: core-assets-${{ matrix.arch }}
|
||||||
path: |
|
path: |
|
||||||
@@ -120,7 +120,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload artifacts with completions
|
- name: Upload artifacts with completions
|
||||||
if: matrix.arch == 'amd64'
|
if: matrix.arch == 'amd64'
|
||||||
uses: actions/upload-artifact@v5
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: core-assets-${{ matrix.arch }}
|
name: core-assets-${{ matrix.arch }}
|
||||||
path: |
|
path: |
|
||||||
@@ -147,7 +147,7 @@ jobs:
|
|||||||
# private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
# private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
# - name: Checkout
|
# - name: Checkout
|
||||||
# uses: actions/checkout@v6
|
# uses: actions/checkout@v4
|
||||||
# with:
|
# with:
|
||||||
# token: ${{ steps.app_token.outputs.token }}
|
# token: ${{ steps.app_token.outputs.token }}
|
||||||
# fetch-depth: 0
|
# fetch-depth: 0
|
||||||
@@ -181,7 +181,7 @@ jobs:
|
|||||||
TAG: ${{ inputs.tag }}
|
TAG: ${{ inputs.tag }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ inputs.tag }}
|
ref: ${{ inputs.tag }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
@@ -192,12 +192,12 @@ jobs:
|
|||||||
git checkout ${TAG}
|
git checkout ${TAG}
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v6
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: ./core/go.mod
|
go-version-file: ./core/go.mod
|
||||||
|
|
||||||
- name: Download core artifacts
|
- name: Download core artifacts
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
pattern: core-assets-*
|
pattern: core-assets-*
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Determine version
|
- name: Determine version
|
||||||
id: version
|
id: version
|
||||||
@@ -134,7 +134,7 @@ jobs:
|
|||||||
rpm -qpi "$SRPM"
|
rpm -qpi "$SRPM"
|
||||||
|
|
||||||
- name: Upload SRPM artifact
|
- name: Upload SRPM artifact
|
||||||
uses: actions/upload-artifact@v5
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.package }}-stable-srpm-${{ steps.version.outputs.version }}
|
name: ${{ matrix.package }}-stable-srpm-${{ steps.version.outputs.version }}
|
||||||
path: ${{ steps.build.outputs.srpm_path }}
|
path: ${{ steps.build.outputs.srpm_path }}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -195,13 +195,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Wait before OBS upload
|
|
||||||
run: sleep 3
|
|
||||||
|
|
||||||
- name: Determine packages to update
|
- name: Determine packages to update
|
||||||
id: packages
|
id: packages
|
||||||
run: |
|
run: |
|
||||||
@@ -347,7 +344,7 @@ jobs:
|
|||||||
done
|
done
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v6
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: ./core/go.mod
|
go-version-file: ./core/go.mod
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -157,12 +157,12 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v6
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: ./core/go.mod
|
go-version-file: ./core/go.mod
|
||||||
cache: false
|
cache: false
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
token: ${{ steps.app_token.outputs.token }}
|
token: ${{ steps.app_token.outputs.token }}
|
||||||
|
|||||||
+1
-3
@@ -86,9 +86,7 @@ touch .qmlls.ini
|
|||||||
|
|
||||||
4. Restart dms to generate the `.qmlls.ini` file
|
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`.
|
5. Make your changes, test, and open a pull request.
|
||||||
|
|
||||||
6. Make your changes, test, and open a pull request.
|
|
||||||
|
|
||||||
### I18n/Localization
|
### I18n/Localization
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ SHELL_INSTALL_DIR=$(DATA_DIR)/quickshell/dms
|
|||||||
ASSETS_DIR=assets
|
ASSETS_DIR=assets
|
||||||
APPLICATIONS_DIR=$(DATA_DIR)/applications
|
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
|
all: build
|
||||||
|
|
||||||
@@ -32,9 +32,6 @@ clean:
|
|||||||
@$(MAKE) -C $(CORE_DIR) clean
|
@$(MAKE) -C $(CORE_DIR) clean
|
||||||
@echo "Clean complete"
|
@echo "Clean complete"
|
||||||
|
|
||||||
lint-qml:
|
|
||||||
@./quickshell/scripts/qmllint-entrypoints.sh
|
|
||||||
|
|
||||||
# Installation targets
|
# Installation targets
|
||||||
install-bin:
|
install-bin:
|
||||||
@echo "Installing $(BINARY_NAME) to $(INSTALL_DIR)..."
|
@echo "Installing $(BINARY_NAME) to $(INSTALL_DIR)..."
|
||||||
@@ -133,7 +130,6 @@ help:
|
|||||||
@echo " all (default) - Build the DMS binary"
|
@echo " all (default) - Build the DMS binary"
|
||||||
@echo " build - Same as 'all'"
|
@echo " build - Same as 'all'"
|
||||||
@echo " clean - Clean build artifacts"
|
@echo " clean - Clean build artifacts"
|
||||||
@echo " lint-qml - Run qmllint on shell entrypoints using the Quickshell tooling VFS"
|
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Install:"
|
@echo "Install:"
|
||||||
@echo " install - Build and install everything (requires sudo)"
|
@echo " install - Build and install everything (requires sudo)"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/golangci/golangci-lint
|
- repo: https://github.com/golangci/golangci-lint
|
||||||
rev: v2.9.0
|
rev: v2.10.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: golangci-lint-fmt
|
- id: golangci-lint-fmt
|
||||||
require_serial: true
|
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) {
|
func runClipCopy(cmd *cobra.Command, args []string) {
|
||||||
var data []byte
|
var data []byte
|
||||||
copyFromStdin := false
|
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case len(args) > 0:
|
case len(args) > 0:
|
||||||
data = []byte(args[0])
|
data = []byte(args[0])
|
||||||
case clipCopyDownload || clipCopyType == "__multi__":
|
default:
|
||||||
var err error
|
var err error
|
||||||
data, err = io.ReadAll(os.Stdin)
|
data, err = io.ReadAll(os.Stdin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("read stdin: %v", err)
|
log.Fatalf("read stdin: %v", err)
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
copyFromStdin = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if clipCopyDownload {
|
if clipCopyDownload {
|
||||||
@@ -260,13 +257,6 @@ func runClipCopy(cmd *cobra.Command, args []string) {
|
|||||||
return
|
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 {
|
if err := clipboard.CopyOpts(data, clipCopyType, clipCopyForeground, clipCopyPasteOnce); err != nil {
|
||||||
log.Fatalf("copy: %v", err)
|
log.Fatalf("copy: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
+115
-1069
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,6 @@ var setupCmd = &cobra.Command{
|
|||||||
Use: "setup",
|
Use: "setup",
|
||||||
Short: "Deploy DMS configurations",
|
Short: "Deploy DMS configurations",
|
||||||
Long: "Deploy compositor and terminal configurations with interactive prompts",
|
Long: "Deploy compositor and terminal configurations with interactive prompts",
|
||||||
PersistentPreRunE: requireMutableSystemCommand,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
if err := runSetup(); err != nil {
|
if err := runSetup(); err != nil {
|
||||||
log.Fatalf("Error during setup: %v", err)
|
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)
|
|
||||||
}
|
|
||||||
+10
-1
@@ -16,10 +16,19 @@ func init() {
|
|||||||
runCmd.Flags().Bool("session", false, "Session managed (like as a systemd unit)")
|
runCmd.Flags().Bool("session", false, "Session managed (like as a systemd unit)")
|
||||||
runCmd.Flags().MarkHidden("daemon-child")
|
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)
|
setupCmd.AddCommand(setupBindsCmd, setupLayoutCmd, setupColorsCmd, setupAlttabCmd, setupOutputsCmd, setupCursorCmd, setupWindowrulesCmd)
|
||||||
|
|
||||||
|
// Add subcommands to update
|
||||||
updateCmd.AddCommand(updateCheckCmd)
|
updateCmd.AddCommand(updateCheckCmd)
|
||||||
|
|
||||||
|
// Add subcommands to plugins
|
||||||
pluginsCmd.AddCommand(pluginsBrowseCmd, pluginsListCmd, pluginsInstallCmd, pluginsUninstallCmd, pluginsUpdateCmd)
|
pluginsCmd.AddCommand(pluginsBrowseCmd, pluginsListCmd, pluginsInstallCmd, pluginsUninstallCmd, pluginsUpdateCmd)
|
||||||
|
|
||||||
|
// Add common commands to root
|
||||||
rootCmd.AddCommand(getCommonCommands()...)
|
rootCmd.AddCommand(getCommonCommands()...)
|
||||||
|
|
||||||
rootCmd.AddCommand(updateCmd)
|
rootCmd.AddCommand(updateCmd)
|
||||||
|
|||||||
@@ -11,20 +11,29 @@ import (
|
|||||||
var Version = "dev"
|
var Version = "dev"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
// Add flags
|
||||||
runCmd.Flags().BoolP("daemon", "d", false, "Run in daemon mode")
|
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("daemon-child", false, "Internal flag for daemon child process")
|
||||||
runCmd.Flags().Bool("session", false, "Session managed (like as a systemd unit)")
|
runCmd.Flags().Bool("session", false, "Session managed (like as a systemd unit)")
|
||||||
runCmd.Flags().MarkHidden("daemon-child")
|
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)
|
setupCmd.AddCommand(setupBindsCmd, setupLayoutCmd, setupColorsCmd, setupAlttabCmd, setupOutputsCmd, setupCursorCmd, setupWindowrulesCmd)
|
||||||
|
|
||||||
|
// Add subcommands to plugins
|
||||||
pluginsCmd.AddCommand(pluginsBrowseCmd, pluginsListCmd, pluginsInstallCmd, pluginsUninstallCmd, pluginsUpdateCmd)
|
pluginsCmd.AddCommand(pluginsBrowseCmd, pluginsListCmd, pluginsInstallCmd, pluginsUninstallCmd, pluginsUpdateCmd)
|
||||||
|
|
||||||
|
// Add common commands to root
|
||||||
rootCmd.AddCommand(getCommonCommands()...)
|
rootCmd.AddCommand(getCommonCommands()...)
|
||||||
|
|
||||||
rootCmd.SetHelpTemplate(getHelpTemplate())
|
rootCmd.SetHelpTemplate(getHelpTemplate())
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
// Block root
|
||||||
if os.Geteuid() == 0 {
|
if os.Geteuid() == 0 {
|
||||||
log.Fatal("This program should not be run as root. Exiting.")
|
log.Fatal("This program should not be run as root. Exiting.")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,14 @@ import (
|
|||||||
"strings"
|
"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 {
|
func isArchPackageInstalled(packageName string) bool {
|
||||||
cmd := exec.Command("pacman", "-Q", packageName)
|
cmd := exec.Command("pacman", "-Q", packageName)
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
package clipboard
|
package clipboard
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/ext_data_control"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/ext_data_control"
|
||||||
@@ -14,37 +12,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Copy(data []byte, mimeType string) error {
|
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 {
|
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 {
|
if !foreground {
|
||||||
return copyFork(data, mimeType, pasteOnce)
|
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"}
|
args := []string{os.Args[0], "cl", "copy", "--foreground"}
|
||||||
if pasteOnce {
|
if pasteOnce {
|
||||||
args = append(args, "--paste-once")
|
args = append(args, "--paste-once")
|
||||||
@@ -52,15 +30,11 @@ func copyFork(data io.Reader, mimeType string, pasteOnce bool) error {
|
|||||||
args = append(args, "--type", mimeType)
|
args = append(args, "--type", mimeType)
|
||||||
|
|
||||||
cmd := exec.Command(args[0], args[1:]...)
|
cmd := exec.Command(args[0], args[1:]...)
|
||||||
|
cmd.Stdin = nil
|
||||||
cmd.Stdout = nil
|
cmd.Stdout = nil
|
||||||
cmd.Stderr = nil
|
cmd.Stderr = nil
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
||||||
|
|
||||||
if stdinSource, ok := data.(*os.File); ok {
|
|
||||||
cmd.Stdin = stdinSource
|
|
||||||
return cmd.Start()
|
|
||||||
}
|
|
||||||
|
|
||||||
stdin, err := cmd.StdinPipe()
|
stdin, err := cmd.StdinPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("stdin pipe: %w", err)
|
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)
|
return fmt.Errorf("start: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := io.Copy(stdin, data); err != nil {
|
if _, err := stdin.Write(data); err != nil {
|
||||||
stdin.Close()
|
stdin.Close()
|
||||||
return fmt.Errorf("write stdin: %w", err)
|
return fmt.Errorf("write stdin: %w", err)
|
||||||
}
|
}
|
||||||
if err := stdin.Close(); err != nil {
|
stdin.Close()
|
||||||
return fmt.Errorf("close stdin: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyServeReader(data io.Reader, mimeType string, pasteOnce bool) error {
|
func copyServe(data []byte, 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 {
|
|
||||||
display, err := wlclient.Connect("")
|
display, err := wlclient.Connect("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("wayland connect: %w", err)
|
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{})
|
cancelled := make(chan struct{})
|
||||||
pasted := make(chan struct{}, 1)
|
pasted := make(chan struct{}, 1)
|
||||||
sendErr := make(chan error, 1)
|
|
||||||
|
|
||||||
source.SetSendHandler(func(e ext_data_control.ExtDataControlSourceV1SendEvent) {
|
source.SetSendHandler(func(e ext_data_control.ExtDataControlSourceV1SendEvent) {
|
||||||
defer syscall.Close(e.Fd)
|
defer syscall.Close(e.Fd)
|
||||||
file := os.NewFile(uintptr(e.Fd), "pipe")
|
file := os.NewFile(uintptr(e.Fd), "pipe")
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
if err := writeTo(file); err != nil {
|
file.Write(data)
|
||||||
select {
|
|
||||||
case sendErr <- err:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
select {
|
select {
|
||||||
case pasted <- struct{}{}:
|
case pasted <- struct{}{}:
|
||||||
default:
|
default:
|
||||||
@@ -247,8 +165,6 @@ func copyServeWithWriter(writeTo func(io.Writer) error, mimeType string, pasteOn
|
|||||||
select {
|
select {
|
||||||
case <-cancelled:
|
case <-cancelled:
|
||||||
return nil
|
return nil
|
||||||
case err := <-sendErr:
|
|
||||||
return err
|
|
||||||
case <-pasted:
|
case <-pasted:
|
||||||
if pasteOnce {
|
if pasteOnce {
|
||||||
return nil
|
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, ", ")))
|
a.log(fmt.Sprintf("Installing AUR packages manually: %s", strings.Join(packages, ", ")))
|
||||||
|
|
||||||
hasNiri := false
|
hasNiri := false
|
||||||
|
hasQuickshell := false
|
||||||
for _, pkg := range packages {
|
for _, pkg := range packages {
|
||||||
if pkg == "niri-git" {
|
if pkg == "niri-git" {
|
||||||
hasNiri = true
|
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
|
// 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)
|
return fmt.Errorf("failed to remove optdepends from .SRCINFO for %s: %w", pkg, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
srcinfoPath = filepath.Join(packageDir, ".SRCINFO")
|
// Skip dependency installation for dms-shell-git and dms-shell-bin
|
||||||
if pkg == "dms-shell-bin" {
|
// since we manually manage those dependencies
|
||||||
progressChan <- InstallProgressMsg{
|
if pkg != "dms-shell-git" && pkg != "dms-shell-bin" {
|
||||||
Phase: PhaseAURPackages,
|
// Pre-install dependencies from .SRCINFO
|
||||||
Progress: startProgress + 0.35*(endProgress-startProgress),
|
|
||||||
Step: fmt.Sprintf("Skipping dependency installation for %s (manually managed)...", pkg),
|
|
||||||
IsComplete: false,
|
|
||||||
LogOutput: fmt.Sprintf("Dependencies for %s are installed separately", pkg),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
progressChan <- InstallProgressMsg{
|
progressChan <- InstallProgressMsg{
|
||||||
Phase: PhaseAURPackages,
|
Phase: PhaseAURPackages,
|
||||||
Progress: startProgress + 0.3*(endProgress-startProgress),
|
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",
|
CommandInfo: "Installing package dependencies and makedepends",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install dependencies from .SRCINFO
|
// Install dependencies and makedepends explicitly
|
||||||
depFilter := ""
|
srcinfoPath = filepath.Join(packageDir, ".SRCINFO")
|
||||||
if pkg == "dms-shell-git" {
|
|
||||||
depFilter = ` | sed -E 's/[[:space:]]*(quickshell|dgop)[[:space:]]*/ /g' | tr -s ' '`
|
|
||||||
}
|
|
||||||
|
|
||||||
depsCmd := exec.CommandContext(ctx, "bash", "-c",
|
depsCmd := exec.CommandContext(ctx, "bash", "-c",
|
||||||
fmt.Sprintf(`
|
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
|
if [ ! -z "$deps" ] && [ "$deps" != " " ]; then
|
||||||
echo '%s' | sudo -S pacman -S --needed --noconfirm $deps
|
echo '%s' | sudo -S pacman -S --needed --noconfirm $deps
|
||||||
fi
|
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 {
|
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)
|
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 {
|
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)
|
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{
|
progressChan <- InstallProgressMsg{
|
||||||
@@ -656,7 +677,7 @@ func (a *ArchDistribution) installSingleAURPackage(ctx context.Context, pkg, sud
|
|||||||
|
|
||||||
buildCmd := exec.CommandContext(ctx, "makepkg", "--noconfirm")
|
buildCmd := exec.CommandContext(ctx, "makepkg", "--noconfirm")
|
||||||
buildCmd.Dir = packageDir
|
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 {
|
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)
|
return fmt.Errorf("failed to build %s: %w", pkg, err)
|
||||||
|
|||||||
@@ -92,25 +92,9 @@ func (d *DebianDistribution) detectDMSGreeter() deps.Dependency {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *DebianDistribution) packageInstalled(pkg string) bool {
|
func (d *DebianDistribution) packageInstalled(pkg string) bool {
|
||||||
return debianPackageInstalledPrecisely(pkg)
|
cmd := exec.Command("dpkg", "-l", pkg)
|
||||||
}
|
err := cmd.Run()
|
||||||
|
return err == nil
|
||||||
func debianPackageInstalledPrecisely(pkg string) bool {
|
|
||||||
cmd := exec.Command("dpkg-query", "-W", "-f=${db:Status-Status}", pkg)
|
|
||||||
output, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(string(output)) == "installed"
|
|
||||||
}
|
|
||||||
|
|
||||||
func containsString(values []string, target string) bool {
|
|
||||||
for _, value := range values {
|
|
||||||
if value == target {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DebianDistribution) GetPackageMapping(wm deps.WindowManager) map[string]PackageMapping {
|
func (d *DebianDistribution) GetPackageMapping(wm deps.WindowManager) map[string]PackageMapping {
|
||||||
@@ -210,12 +194,12 @@ func (d *DebianDistribution) InstallPrerequisites(ctx context.Context, sudoPassw
|
|||||||
Step: "Installing development dependencies...",
|
Step: "Installing development dependencies...",
|
||||||
IsComplete: false,
|
IsComplete: false,
|
||||||
NeedsSudo: true,
|
NeedsSudo: true,
|
||||||
CommandInfo: "sudo apt-get install -y curl wget git cmake ninja-build pkg-config gnupg libxcb-cursor-dev libglib2.0-dev libpolkit-agent-1-dev",
|
CommandInfo: "sudo apt-get install -y curl wget git cmake ninja-build pkg-config libxcb-cursor-dev libglib2.0-dev libpolkit-agent-1-dev",
|
||||||
LogOutput: "Installing additional development tools",
|
LogOutput: "Installing additional development tools",
|
||||||
}
|
}
|
||||||
|
|
||||||
devToolsCmd := ExecSudoCommand(ctx, sudoPassword,
|
devToolsCmd := ExecSudoCommand(ctx, sudoPassword,
|
||||||
"DEBIAN_FRONTEND=noninteractive apt-get install -y curl wget git cmake ninja-build pkg-config gnupg libxcb-cursor-dev libglib2.0-dev libpolkit-agent-1-dev libjpeg-dev libpugixml-dev")
|
"DEBIAN_FRONTEND=noninteractive apt-get install -y curl wget git cmake ninja-build pkg-config libxcb-cursor-dev libglib2.0-dev libpolkit-agent-1-dev libjpeg-dev libpugixml-dev")
|
||||||
if err := d.runWithProgress(devToolsCmd, progressChan, PhasePrerequisites, 0.10, 0.12); err != nil {
|
if err := d.runWithProgress(devToolsCmd, progressChan, PhasePrerequisites, 0.10, 0.12); err != nil {
|
||||||
return fmt.Errorf("failed to install development tools: %w", err)
|
return fmt.Errorf("failed to install development tools: %w", err)
|
||||||
}
|
}
|
||||||
@@ -395,14 +379,6 @@ func (d *DebianDistribution) extractPackageNames(packages []PackageMapping) []st
|
|||||||
return names
|
return names
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DebianDistribution) aptInstallArgs(packages []string, minimal bool) []string {
|
|
||||||
args := []string{"DEBIAN_FRONTEND=noninteractive", "apt-get", "install", "-y"}
|
|
||||||
if minimal {
|
|
||||||
args = append(args, "--no-install-recommends")
|
|
||||||
}
|
|
||||||
return append(args, packages...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DebianDistribution) enableOBSRepos(ctx context.Context, obsPkgs []PackageMapping, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
func (d *DebianDistribution) enableOBSRepos(ctx context.Context, obsPkgs []PackageMapping, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||||
enabledRepos := make(map[string]bool)
|
enabledRepos := make(map[string]bool)
|
||||||
|
|
||||||
@@ -506,30 +482,12 @@ func (d *DebianDistribution) installAPTPackages(ctx context.Context, packages []
|
|||||||
|
|
||||||
d.log(fmt.Sprintf("Installing APT packages: %s", strings.Join(packages, ", ")))
|
d.log(fmt.Sprintf("Installing APT packages: %s", strings.Join(packages, ", ")))
|
||||||
|
|
||||||
groups := orderedMinimalInstallGroups(packages)
|
args := []string{"DEBIAN_FRONTEND=noninteractive", "apt-get", "install", "-y"}
|
||||||
totalGroups := len(groups)
|
args = append(args, packages...)
|
||||||
|
|
||||||
groupIndex := 0
|
|
||||||
installGroup := func(groupPackages []string, minimal bool) error {
|
|
||||||
if len(groupPackages) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
groupIndex++
|
|
||||||
startProgress := 0.40
|
|
||||||
endProgress := 0.60
|
|
||||||
if totalGroups > 1 {
|
|
||||||
if groupIndex == 1 {
|
|
||||||
endProgress = 0.50
|
|
||||||
} else {
|
|
||||||
startProgress = 0.50
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
args := d.aptInstallArgs(groupPackages, minimal)
|
|
||||||
progressChan <- InstallProgressMsg{
|
progressChan <- InstallProgressMsg{
|
||||||
Phase: PhaseSystemPackages,
|
Phase: PhaseSystemPackages,
|
||||||
Progress: startProgress,
|
Progress: 0.40,
|
||||||
Step: "Installing system packages...",
|
Step: "Installing system packages...",
|
||||||
IsComplete: false,
|
IsComplete: false,
|
||||||
NeedsSudo: true,
|
NeedsSudo: true,
|
||||||
@@ -537,15 +495,7 @@ func (d *DebianDistribution) installAPTPackages(ctx context.Context, packages []
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
||||||
return d.runWithProgress(cmd, progressChan, PhaseSystemPackages, startProgress, endProgress)
|
return d.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.40, 0.60)
|
||||||
}
|
|
||||||
|
|
||||||
for _, group := range groups {
|
|
||||||
if err := installGroup(group.packages, group.minimal); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DebianDistribution) installBuildDependencies(ctx context.Context, manualPkgs []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
func (d *DebianDistribution) installBuildDependencies(ctx context.Context, manualPkgs []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||||
|
|||||||
@@ -484,7 +484,28 @@ func (f *FedoraDistribution) installDNFPackages(ctx context.Context, packages []
|
|||||||
|
|
||||||
f.log(fmt.Sprintf("Installing DNF packages: %s", strings.Join(packages, ", ")))
|
f.log(fmt.Sprintf("Installing DNF packages: %s", strings.Join(packages, ", ")))
|
||||||
|
|
||||||
return f.installDNFGroups(ctx, packages, sudoPassword, progressChan, PhaseSystemPackages, "Installing system packages...", 0.40, 0.60)
|
args := []string{"dnf", "install", "-y"}
|
||||||
|
|
||||||
|
for _, pkg := range packages {
|
||||||
|
if pkg == "niri" || pkg == "niri-git" {
|
||||||
|
args = append(args, "--setopt=install_weak_deps=False")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
args = append(args, packages...)
|
||||||
|
|
||||||
|
progressChan <- InstallProgressMsg{
|
||||||
|
Phase: PhaseSystemPackages,
|
||||||
|
Progress: 0.40,
|
||||||
|
Step: "Installing system packages...",
|
||||||
|
IsComplete: false,
|
||||||
|
NeedsSudo: true,
|
||||||
|
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
||||||
|
return f.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.40, 0.60)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FedoraDistribution) installCOPRPackages(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
func (f *FedoraDistribution) installCOPRPackages(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||||
@@ -494,57 +515,26 @@ func (f *FedoraDistribution) installCOPRPackages(ctx context.Context, packages [
|
|||||||
|
|
||||||
f.log(fmt.Sprintf("Installing COPR packages: %s", strings.Join(packages, ", ")))
|
f.log(fmt.Sprintf("Installing COPR packages: %s", strings.Join(packages, ", ")))
|
||||||
|
|
||||||
return f.installDNFGroups(ctx, packages, sudoPassword, progressChan, PhaseAURPackages, "Installing COPR packages...", 0.70, 0.85)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FedoraDistribution) dnfInstallArgs(packages []string, minimal bool) []string {
|
|
||||||
args := []string{"dnf", "install", "-y"}
|
args := []string{"dnf", "install", "-y"}
|
||||||
if minimal {
|
|
||||||
|
for _, pkg := range packages {
|
||||||
|
if pkg == "niri" || pkg == "niri-git" {
|
||||||
args = append(args, "--setopt=install_weak_deps=False")
|
args = append(args, "--setopt=install_weak_deps=False")
|
||||||
}
|
break
|
||||||
return append(args, packages...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FedoraDistribution) installDNFGroups(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg, phase InstallPhase, step string, startProgress float64, endProgress float64) error {
|
|
||||||
groups := orderedMinimalInstallGroups(packages)
|
|
||||||
totalGroups := len(groups)
|
|
||||||
|
|
||||||
groupIndex := 0
|
|
||||||
installGroup := func(groupPackages []string, minimal bool) error {
|
|
||||||
if len(groupPackages) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
groupIndex++
|
|
||||||
groupStart := startProgress
|
|
||||||
groupEnd := endProgress
|
|
||||||
if totalGroups > 1 {
|
|
||||||
midpoint := startProgress + ((endProgress - startProgress) / 2)
|
|
||||||
if groupIndex == 1 {
|
|
||||||
groupEnd = midpoint
|
|
||||||
} else {
|
|
||||||
groupStart = midpoint
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
args := f.dnfInstallArgs(groupPackages, minimal)
|
args = append(args, packages...)
|
||||||
|
|
||||||
progressChan <- InstallProgressMsg{
|
progressChan <- InstallProgressMsg{
|
||||||
Phase: phase,
|
Phase: PhaseAURPackages,
|
||||||
Progress: groupStart,
|
Progress: 0.70,
|
||||||
Step: step,
|
Step: "Installing COPR packages...",
|
||||||
IsComplete: false,
|
IsComplete: false,
|
||||||
NeedsSudo: true,
|
NeedsSudo: true,
|
||||||
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
||||||
return f.runWithProgress(cmd, progressChan, phase, groupStart, groupEnd)
|
return f.runWithProgress(cmd, progressChan, PhaseAURPackages, 0.70, 0.85)
|
||||||
}
|
|
||||||
|
|
||||||
for _, group := range groups {
|
|
||||||
if err := installGroup(group.packages, group.minimal); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
package distros
|
|
||||||
|
|
||||||
type minimalInstallGroup struct {
|
|
||||||
packages []string
|
|
||||||
minimal bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func shouldPreferMinimalInstall(pkg string) bool {
|
|
||||||
switch pkg {
|
|
||||||
case "niri", "niri-git":
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitMinimalInstallPackages(packages []string) (normal []string, minimal []string) {
|
|
||||||
for _, pkg := range packages {
|
|
||||||
if shouldPreferMinimalInstall(pkg) {
|
|
||||||
minimal = append(minimal, pkg)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
normal = append(normal, pkg)
|
|
||||||
}
|
|
||||||
return normal, minimal
|
|
||||||
}
|
|
||||||
|
|
||||||
func orderedMinimalInstallGroups(packages []string) []minimalInstallGroup {
|
|
||||||
normal, minimal := splitMinimalInstallPackages(packages)
|
|
||||||
groups := make([]minimalInstallGroup, 0, 2)
|
|
||||||
if len(minimal) > 0 {
|
|
||||||
groups = append(groups, minimalInstallGroup{
|
|
||||||
packages: minimal,
|
|
||||||
minimal: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if len(normal) > 0 {
|
|
||||||
groups = append(groups, minimalInstallGroup{
|
|
||||||
packages: normal,
|
|
||||||
minimal: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return groups
|
|
||||||
}
|
|
||||||
@@ -29,8 +29,6 @@ type OpenSUSEDistribution struct {
|
|||||||
config DistroConfig
|
config DistroConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
const openSUSENiriWaylandServerPackage = "libwayland-server0"
|
|
||||||
|
|
||||||
func NewOpenSUSEDistribution(config DistroConfig, logChan chan<- string) *OpenSUSEDistribution {
|
func NewOpenSUSEDistribution(config DistroConfig, logChan chan<- string) *OpenSUSEDistribution {
|
||||||
base := NewBaseDistribution(logChan)
|
base := NewBaseDistribution(logChan)
|
||||||
return &OpenSUSEDistribution{
|
return &OpenSUSEDistribution{
|
||||||
@@ -201,7 +199,35 @@ func (o *OpenSUSEDistribution) detectAccountsService() deps.Dependency {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o *OpenSUSEDistribution) getPrerequisites() []string {
|
func (o *OpenSUSEDistribution) getPrerequisites() []string {
|
||||||
return []string{}
|
return []string{
|
||||||
|
"make",
|
||||||
|
"unzip",
|
||||||
|
"gcc",
|
||||||
|
"gcc-c++",
|
||||||
|
"cmake",
|
||||||
|
"ninja",
|
||||||
|
"pkgconf-pkg-config",
|
||||||
|
"git",
|
||||||
|
"qt6-base-devel",
|
||||||
|
"qt6-declarative-devel",
|
||||||
|
"qt6-declarative-private-devel",
|
||||||
|
"qt6-shadertools",
|
||||||
|
"qt6-shadertools-devel",
|
||||||
|
"qt6-wayland-devel",
|
||||||
|
"qt6-waylandclient-private-devel",
|
||||||
|
"spirv-tools-devel",
|
||||||
|
"cli11-devel",
|
||||||
|
"wayland-protocols-devel",
|
||||||
|
"libgbm-devel",
|
||||||
|
"libdrm-devel",
|
||||||
|
"pipewire-devel",
|
||||||
|
"jemalloc-devel",
|
||||||
|
"wayland-utils",
|
||||||
|
"Mesa-libGLESv3-devel",
|
||||||
|
"pam-devel",
|
||||||
|
"glib2-devel",
|
||||||
|
"polkit-devel",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OpenSUSEDistribution) InstallPrerequisites(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
func (o *OpenSUSEDistribution) InstallPrerequisites(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||||
@@ -271,10 +297,6 @@ func (o *OpenSUSEDistribution) InstallPackages(ctx context.Context, dependencies
|
|||||||
LogOutput: "Starting prerequisite check...",
|
LogOutput: "Starting prerequisite check...",
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := o.disableInstallMediaRepos(ctx, sudoPassword, progressChan); err != nil {
|
|
||||||
return fmt.Errorf("failed to disable install media repositories: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := o.InstallPrerequisites(ctx, sudoPassword, progressChan); err != nil {
|
if err := o.InstallPrerequisites(ctx, sudoPassword, progressChan); err != nil {
|
||||||
return fmt.Errorf("failed to install prerequisites: %w", err)
|
return fmt.Errorf("failed to install prerequisites: %w", err)
|
||||||
}
|
}
|
||||||
@@ -305,7 +327,7 @@ func (o *OpenSUSEDistribution) InstallPackages(ctx context.Context, dependencies
|
|||||||
NeedsSudo: true,
|
NeedsSudo: true,
|
||||||
LogOutput: fmt.Sprintf("Installing system packages: %s", strings.Join(systemPkgs, ", ")),
|
LogOutput: fmt.Sprintf("Installing system packages: %s", strings.Join(systemPkgs, ", ")),
|
||||||
}
|
}
|
||||||
if err := o.installZypperPackages(ctx, systemPkgs, sudoPassword, progressChan, PhaseSystemPackages, "Installing system packages...", 0.40, 0.60); err != nil {
|
if err := o.installZypperPackages(ctx, systemPkgs, sudoPassword, progressChan); err != nil {
|
||||||
return fmt.Errorf("failed to install zypper packages: %w", err)
|
return fmt.Errorf("failed to install zypper packages: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -320,7 +342,7 @@ func (o *OpenSUSEDistribution) InstallPackages(ctx context.Context, dependencies
|
|||||||
IsComplete: false,
|
IsComplete: false,
|
||||||
LogOutput: fmt.Sprintf("Installing OBS packages: %s", strings.Join(obsPkgNames, ", ")),
|
LogOutput: fmt.Sprintf("Installing OBS packages: %s", strings.Join(obsPkgNames, ", ")),
|
||||||
}
|
}
|
||||||
if err := o.installZypperPackages(ctx, obsPkgNames, sudoPassword, progressChan, PhaseAURPackages, "Installing OBS packages...", 0.70, 0.85); err != nil {
|
if err := o.installZypperPackages(ctx, obsPkgNames, sudoPassword, progressChan); err != nil {
|
||||||
return fmt.Errorf("failed to install OBS packages: %w", err)
|
return fmt.Errorf("failed to install OBS packages: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -410,32 +432,9 @@ func (o *OpenSUSEDistribution) categorizePackages(dependencies []deps.Dependency
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
systemPkgs = o.appendMissingSystemPackages(systemPkgs, openSUSENiriRuntimePackages(wm, disabledFlags))
|
|
||||||
|
|
||||||
return systemPkgs, obsPkgs, manualPkgs, variantMap
|
return systemPkgs, obsPkgs, manualPkgs, variantMap
|
||||||
}
|
}
|
||||||
|
|
||||||
func openSUSENiriRuntimePackages(wm deps.WindowManager, disabledFlags map[string]bool) []string {
|
|
||||||
if wm != deps.WindowManagerNiri || disabledFlags["niri"] {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return []string{openSUSENiriWaylandServerPackage}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenSUSEDistribution) appendMissingSystemPackages(systemPkgs []string, extraPkgs []string) []string {
|
|
||||||
for _, pkg := range extraPkgs {
|
|
||||||
if containsString(systemPkgs, pkg) || o.packageInstalled(pkg) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
o.log(fmt.Sprintf("Adding openSUSE runtime package: %s", pkg))
|
|
||||||
systemPkgs = append(systemPkgs, pkg)
|
|
||||||
}
|
|
||||||
|
|
||||||
return systemPkgs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenSUSEDistribution) extractPackageNames(packages []PackageMapping) []string {
|
func (o *OpenSUSEDistribution) extractPackageNames(packages []PackageMapping) []string {
|
||||||
names := make([]string, len(packages))
|
names := make([]string, len(packages))
|
||||||
for i, pkg := range packages {
|
for i, pkg := range packages {
|
||||||
@@ -515,146 +514,27 @@ func (o *OpenSUSEDistribution) enableOBSRepos(ctx context.Context, obsPkgs []Pac
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isOpenSUSEInstallMediaURI(uri string) bool {
|
func (o *OpenSUSEDistribution) installZypperPackages(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||||
normalizedURI := strings.ToLower(strings.TrimSpace(uri))
|
|
||||||
|
|
||||||
return strings.HasPrefix(normalizedURI, "cd:/") ||
|
|
||||||
strings.HasPrefix(normalizedURI, "dvd:/") ||
|
|
||||||
strings.HasPrefix(normalizedURI, "hd:/") ||
|
|
||||||
strings.HasPrefix(normalizedURI, "iso:/")
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseZypperInstallMediaAliases(output string) []string {
|
|
||||||
var aliases []string
|
|
||||||
|
|
||||||
for _, line := range strings.Split(output, "\n") {
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
if line == "" || !strings.Contains(line, "|") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
parts := strings.Split(line, "|")
|
|
||||||
if len(parts) < 7 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range parts {
|
|
||||||
parts[i] = strings.TrimSpace(parts[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
alias := parts[1]
|
|
||||||
enabled := strings.ToLower(parts[3])
|
|
||||||
uri := parts[len(parts)-1]
|
|
||||||
|
|
||||||
if alias == "" || strings.EqualFold(alias, "alias") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if enabled != "" && enabled != "yes" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !isOpenSUSEInstallMediaURI(uri) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
aliases = append(aliases, alias)
|
|
||||||
}
|
|
||||||
|
|
||||||
return aliases
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenSUSEDistribution) disableInstallMediaRepos(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
|
||||||
listCmd := exec.CommandContext(ctx, "zypper", "repos", "-u")
|
|
||||||
output, err := listCmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
o.log(fmt.Sprintf("Warning: failed to list zypper repositories: %s", strings.TrimSpace(string(output))))
|
|
||||||
return fmt.Errorf("failed to list zypper repositories: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
aliases := parseZypperInstallMediaAliases(string(output))
|
|
||||||
if len(aliases) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
o.log(fmt.Sprintf("Disabling install media repositories: %s", strings.Join(aliases, ", ")))
|
|
||||||
progressChan <- InstallProgressMsg{
|
|
||||||
Phase: PhasePrerequisites,
|
|
||||||
Progress: 0.055,
|
|
||||||
Step: "Disabling install media repositories...",
|
|
||||||
IsComplete: false,
|
|
||||||
NeedsSudo: true,
|
|
||||||
CommandInfo: fmt.Sprintf("sudo zypper modifyrepo -d %s", strings.Join(aliases, " ")),
|
|
||||||
LogOutput: fmt.Sprintf("Disabling install media repositories: %s", strings.Join(aliases, ", ")),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, alias := range aliases {
|
|
||||||
cmd := ExecSudoCommand(ctx, sudoPassword, fmt.Sprintf("zypper modifyrepo -d '%s'", escapeSingleQuotes(alias)))
|
|
||||||
repoOutput, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
o.log(fmt.Sprintf("Failed to disable install media repo %s: %s", alias, strings.TrimSpace(string(repoOutput))))
|
|
||||||
return fmt.Errorf("failed to disable install media repo %s: %w", alias, err)
|
|
||||||
}
|
|
||||||
o.log(fmt.Sprintf("Disabled install media repo %s: %s", alias, strings.TrimSpace(string(repoOutput))))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenSUSEDistribution) zypperInstallArgs(packages []string, minimal bool) []string {
|
|
||||||
args := []string{"zypper", "install", "-y"}
|
|
||||||
if minimal {
|
|
||||||
args = append(args, "--no-recommends")
|
|
||||||
}
|
|
||||||
return append(args, packages...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenSUSEDistribution) installZypperPackages(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg, phase InstallPhase, step string, startProgress float64, endProgress float64) error {
|
|
||||||
if len(packages) == 0 {
|
if len(packages) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
o.log(fmt.Sprintf("Installing zypper packages: %s", strings.Join(packages, ", ")))
|
o.log(fmt.Sprintf("Installing zypper packages: %s", strings.Join(packages, ", ")))
|
||||||
|
|
||||||
groups := orderedMinimalInstallGroups(packages)
|
args := []string{"zypper", "install", "-y"}
|
||||||
totalGroups := len(groups)
|
args = append(args, packages...)
|
||||||
|
|
||||||
groupIndex := 0
|
|
||||||
installGroup := func(groupPackages []string, minimal bool) error {
|
|
||||||
if len(groupPackages) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
groupIndex++
|
|
||||||
groupStart := startProgress
|
|
||||||
groupEnd := endProgress
|
|
||||||
if totalGroups > 1 {
|
|
||||||
midpoint := startProgress + ((endProgress - startProgress) / 2)
|
|
||||||
if groupIndex == 1 {
|
|
||||||
groupEnd = midpoint
|
|
||||||
} else {
|
|
||||||
groupStart = midpoint
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
args := o.zypperInstallArgs(groupPackages, minimal)
|
|
||||||
progressChan <- InstallProgressMsg{
|
progressChan <- InstallProgressMsg{
|
||||||
Phase: phase,
|
Phase: PhaseSystemPackages,
|
||||||
Progress: groupStart,
|
Progress: 0.40,
|
||||||
Step: step,
|
Step: "Installing system packages...",
|
||||||
IsComplete: false,
|
IsComplete: false,
|
||||||
NeedsSudo: true,
|
NeedsSudo: true,
|
||||||
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
||||||
return o.runWithProgress(cmd, progressChan, phase, groupStart, groupEnd)
|
return o.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.40, 0.60)
|
||||||
}
|
|
||||||
|
|
||||||
for _, group := range groups {
|
|
||||||
if err := installGroup(group.packages, group.minimal); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OpenSUSEDistribution) installQuickshell(ctx context.Context, variant deps.PackageVariant, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
func (o *OpenSUSEDistribution) installQuickshell(ctx context.Context, variant deps.PackageVariant, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||||
|
|||||||
@@ -100,7 +100,9 @@ func (u *UbuntuDistribution) detectDMSGreeter() deps.Dependency {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *UbuntuDistribution) packageInstalled(pkg string) bool {
|
func (u *UbuntuDistribution) packageInstalled(pkg string) bool {
|
||||||
return debianPackageInstalledPrecisely(pkg)
|
cmd := exec.Command("dpkg", "-l", pkg)
|
||||||
|
err := cmd.Run()
|
||||||
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UbuntuDistribution) GetPackageMapping(wm deps.WindowManager) map[string]PackageMapping {
|
func (u *UbuntuDistribution) GetPackageMapping(wm deps.WindowManager) map[string]PackageMapping {
|
||||||
@@ -452,7 +454,21 @@ func (u *UbuntuDistribution) installAPTPackages(ctx context.Context, packages []
|
|||||||
}
|
}
|
||||||
|
|
||||||
u.log(fmt.Sprintf("Installing APT packages: %s", strings.Join(packages, ", ")))
|
u.log(fmt.Sprintf("Installing APT packages: %s", strings.Join(packages, ", ")))
|
||||||
return u.installAPTGroups(ctx, packages, sudoPassword, progressChan, PhaseSystemPackages, "Installing system packages...", 0.40, 0.60)
|
|
||||||
|
args := []string{"apt-get", "install", "-y"}
|
||||||
|
args = append(args, packages...)
|
||||||
|
|
||||||
|
progressChan <- InstallProgressMsg{
|
||||||
|
Phase: PhaseSystemPackages,
|
||||||
|
Progress: 0.40,
|
||||||
|
Step: "Installing system packages...",
|
||||||
|
IsComplete: false,
|
||||||
|
NeedsSudo: true,
|
||||||
|
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
||||||
|
return u.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.40, 0.60)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UbuntuDistribution) installPPAPackages(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
func (u *UbuntuDistribution) installPPAPackages(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||||
@@ -461,59 +477,21 @@ func (u *UbuntuDistribution) installPPAPackages(ctx context.Context, packages []
|
|||||||
}
|
}
|
||||||
|
|
||||||
u.log(fmt.Sprintf("Installing PPA packages: %s", strings.Join(packages, ", ")))
|
u.log(fmt.Sprintf("Installing PPA packages: %s", strings.Join(packages, ", ")))
|
||||||
return u.installAPTGroups(ctx, packages, sudoPassword, progressChan, PhaseAURPackages, "Installing PPA packages...", 0.70, 0.85)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *UbuntuDistribution) aptInstallArgs(packages []string, minimal bool) []string {
|
args := []string{"apt-get", "install", "-y"}
|
||||||
args := []string{"DEBIAN_FRONTEND=noninteractive", "apt-get", "install", "-y"}
|
args = append(args, packages...)
|
||||||
if minimal {
|
|
||||||
args = append(args, "--no-install-recommends")
|
|
||||||
}
|
|
||||||
return append(args, packages...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *UbuntuDistribution) installAPTGroups(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg, phase InstallPhase, step string, startProgress float64, endProgress float64) error {
|
|
||||||
groups := orderedMinimalInstallGroups(packages)
|
|
||||||
totalGroups := len(groups)
|
|
||||||
|
|
||||||
groupIndex := 0
|
|
||||||
installGroup := func(groupPackages []string, minimal bool) error {
|
|
||||||
if len(groupPackages) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
groupIndex++
|
|
||||||
groupStart := startProgress
|
|
||||||
groupEnd := endProgress
|
|
||||||
if totalGroups > 1 {
|
|
||||||
midpoint := startProgress + ((endProgress - startProgress) / 2)
|
|
||||||
if groupIndex == 1 {
|
|
||||||
groupEnd = midpoint
|
|
||||||
} else {
|
|
||||||
groupStart = midpoint
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
args := u.aptInstallArgs(groupPackages, minimal)
|
|
||||||
progressChan <- InstallProgressMsg{
|
progressChan <- InstallProgressMsg{
|
||||||
Phase: phase,
|
Phase: PhaseAURPackages,
|
||||||
Progress: groupStart,
|
Progress: 0.70,
|
||||||
Step: step,
|
Step: "Installing PPA packages...",
|
||||||
IsComplete: false,
|
IsComplete: false,
|
||||||
NeedsSudo: true,
|
NeedsSudo: true,
|
||||||
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
||||||
return u.runWithProgress(cmd, progressChan, phase, groupStart, groupEnd)
|
return u.runWithProgress(cmd, progressChan, PhaseAURPackages, 0.70, 0.85)
|
||||||
}
|
|
||||||
|
|
||||||
for _, group := range groups {
|
|
||||||
if err := installGroup(group.packages, group.minimal); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UbuntuDistribution) installBuildDependencies(ctx context.Context, manualPkgs []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
func (u *UbuntuDistribution) installBuildDependencies(ctx context.Context, manualPkgs []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||||
|
|||||||
@@ -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"),
|
|
||||||
}
|
|
||||||
+107
-1139
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: "kcolorscheme", ConfigFile: "kcolorscheme.toml", RunUnconditionally: true},
|
||||||
{ID: "vscode", Kind: TemplateKindVSCode},
|
{ID: "vscode", Kind: TemplateKindVSCode},
|
||||||
{ID: "emacs", Commands: []string{"emacs"}, ConfigFile: "emacs.toml", Kind: TemplateKindEmacs},
|
{ID: "emacs", Commands: []string{"emacs"}, ConfigFile: "emacs.toml", Kind: TemplateKindEmacs},
|
||||||
{ID: "zed", Commands: []string{"zed"}, ConfigFile: "zed.toml"},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ColorMode) GTKTheme() string {
|
func (c *ColorMode) GTKTheme() string {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package network
|
package network
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -29,13 +28,7 @@ func TestDetectResult_HasNetworkdField(t *testing.T) {
|
|||||||
|
|
||||||
func TestDetectNetworkStack_Integration(t *testing.T) {
|
func TestDetectNetworkStack_Integration(t *testing.T) {
|
||||||
result, err := DetectNetworkStack()
|
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)
|
assert.NoError(t, err)
|
||||||
if assert.NotNil(t, result) {
|
assert.NotNil(t, result)
|
||||||
assert.NotEmpty(t, result.ChosenReason)
|
assert.NotEmpty(t, result.ChosenReason)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ Architecture: any
|
|||||||
Depends: ${misc:Depends},
|
Depends: ${misc:Depends},
|
||||||
greetd,
|
greetd,
|
||||||
quickshell-git | quickshell
|
quickshell-git | quickshell
|
||||||
Suggests: niri | hyprland | sway
|
Recommends: niri | hyprland | sway
|
||||||
Description: DankMaterialShell greeter for greetd
|
Description: DankMaterialShell greeter for greetd
|
||||||
DankMaterialShell greeter for greetd login manager. A modern, Material Design 3
|
DankMaterialShell greeter for greetd login manager. A modern, Material Design 3
|
||||||
inspired greeter interface built with Quickshell for Wayland compositors.
|
inspired greeter interface built with Quickshell for Wayland compositors.
|
||||||
|
|||||||
@@ -102,19 +102,6 @@ if [[ ! -d "distro/debian" ]]; then
|
|||||||
echo "Error: Run this script from the repository root"
|
echo "Error: Run this script from the repository root"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
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:
|
# Parameters:
|
||||||
# $1 = PROJECT
|
# $1 = PROJECT
|
||||||
# $2 = PACKAGE
|
# $2 = PACKAGE
|
||||||
@@ -322,23 +309,8 @@ mkdir -p "$OBS_BASE"
|
|||||||
if [[ ! -d "$OBS_BASE/$OBS_PROJECT/$PACKAGE" ]]; then
|
if [[ ! -d "$OBS_BASE/$OBS_PROJECT/$PACKAGE" ]]; then
|
||||||
echo "Checking out $OBS_PROJECT/$PACKAGE..."
|
echo "Checking out $OBS_PROJECT/$PACKAGE..."
|
||||||
cd "$OBS_BASE"
|
cd "$OBS_BASE"
|
||||||
CHECKOUT_OK=false
|
osc co "$OBS_PROJECT/$PACKAGE"
|
||||||
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
|
|
||||||
cd "$REPO_ROOT"
|
cd "$REPO_ROOT"
|
||||||
if [[ "$CHECKOUT_OK" != "true" ]]; then
|
|
||||||
echo "Error: Checkout failed after 3 attempts"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
WORK_DIR="$OBS_BASE/$OBS_PROJECT/$PACKAGE"
|
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)
|
# Update working copy to latest revision (without expanding service files to avoid revision conflicts)
|
||||||
echo "==> Updating working copy"
|
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"
|
echo "Error: Failed to update working copy"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@@ -1173,7 +1145,7 @@ if ! osc status 2>/dev/null | grep -qE '^[MAD]|^[?]'; then
|
|||||||
else
|
else
|
||||||
echo "==> Committing to OBS"
|
echo "==> Committing to OBS"
|
||||||
set +e
|
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]}
|
COMMIT_EXIT=${PIPESTATUS[0]}
|
||||||
set -e
|
set -e
|
||||||
if [[ $COMMIT_EXIT -ne 0 ]]; then
|
if [[ $COMMIT_EXIT -ne 0 ]]; then
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ Architecture: any
|
|||||||
Depends: ${misc:Depends},
|
Depends: ${misc:Depends},
|
||||||
greetd,
|
greetd,
|
||||||
quickshell-git | quickshell
|
quickshell-git | quickshell
|
||||||
Suggests: niri | hyprland | sway
|
Recommends: niri | hyprland | sway
|
||||||
Description: DankMaterialShell greeter for greetd
|
Description: DankMaterialShell greeter for greetd
|
||||||
DankMaterialShell greeter for greetd login manager. A modern, Material Design 3
|
DankMaterialShell greeter for greetd login manager. A modern, Material Design 3
|
||||||
inspired greeter interface built with Quickshell for Wayland compositors.
|
inspired greeter interface built with Quickshell for Wayland compositors.
|
||||||
|
|||||||
@@ -72,14 +72,9 @@
|
|||||||
"${cleanVersion}${dateSuffix}${revSuffix}";
|
"${cleanVersion}${dateSuffix}${revSuffix}";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
dms-shell = pkgs.lib.makeOverridable (
|
dms-shell = pkgs.buildGoModule (
|
||||||
{
|
|
||||||
extraQtPackages ? [ ],
|
|
||||||
}:
|
|
||||||
pkgs.buildGoModule (
|
|
||||||
let
|
let
|
||||||
rootSrc = ./.;
|
rootSrc = ./.;
|
||||||
qtPackages = (qmlPkgs pkgs) ++ extraQtPackages;
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
inherit version;
|
inherit version;
|
||||||
@@ -115,8 +110,8 @@
|
|||||||
|
|
||||||
wrapProgram $out/bin/dms \
|
wrapProgram $out/bin/dms \
|
||||||
--add-flags "-c $out/share/quickshell/dms" \
|
--add-flags "-c $out/share/quickshell/dms" \
|
||||||
--prefix "NIXPKGS_QT6_QML_IMPORT_PATH" ":" "${mkQmlImportPath pkgs qtPackages}" \
|
--prefix "NIXPKGS_QT6_QML_IMPORT_PATH" ":" "${mkQmlImportPath pkgs (qmlPkgs pkgs)}" \
|
||||||
--prefix "QT_PLUGIN_PATH" ":" "${mkQtPluginPath pkgs qtPackages}"
|
--prefix "QT_PLUGIN_PATH" ":" "${mkQtPluginPath pkgs (qmlPkgs pkgs)}"
|
||||||
|
|
||||||
install -Dm644 ${rootSrc}/assets/systemd/dms.service \
|
install -Dm644 ${rootSrc}/assets/systemd/dms.service \
|
||||||
$out/lib/systemd/user/dms.service
|
$out/lib/systemd/user/dms.service
|
||||||
@@ -146,8 +141,7 @@
|
|||||||
platforms = pkgs.lib.platforms.linux;
|
platforms = pkgs.lib.platforms.linux;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
) { };
|
|
||||||
|
|
||||||
quickshell = quickshell.packages.${system}.default;
|
quickshell = quickshell.packages.${system}.default;
|
||||||
|
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ qs -v -p shell.qml # Verbose debugging
|
|||||||
|
|
||||||
# Code formatting and linting
|
# Code formatting and linting
|
||||||
qmlfmt -t 4 -i 4 -b 250 -w /path/to/file.qml # Format QML (don't use qmlformat)
|
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
|
./qmlformat-all.sh # Format all QML files
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -783,7 +783,7 @@ When modifying the shell:
|
|||||||
|
|
||||||
**QML Frontend:**
|
**QML Frontend:**
|
||||||
1. **Test changes**: `qs -p .` (automatic reload on file changes)
|
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)
|
3. **Performance**: Ensure animations remain smooth (60 FPS target)
|
||||||
4. **Theming**: Use `Theme.propertyName` for Material Design 3 consistency
|
4. **Theming**: Use `Theme.propertyName` for Material Design 3 consistency
|
||||||
|
|
||||||
|
|||||||
@@ -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 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 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]
|
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]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1245,7 +1245,7 @@ Singleton {
|
|||||||
id: greeterSessionFile
|
id: greeterSessionFile
|
||||||
|
|
||||||
path: {
|
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";
|
return greetCfgDir + "/session.json";
|
||||||
}
|
}
|
||||||
preload: isGreeterMode
|
preload: isGreeterMode
|
||||||
|
|||||||
@@ -37,6 +37,18 @@ Singleton {
|
|||||||
Custom
|
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 {
|
enum SuspendBehavior {
|
||||||
Suspend,
|
Suspend,
|
||||||
Hibernate,
|
Hibernate,
|
||||||
@@ -166,6 +178,12 @@ Singleton {
|
|||||||
property int modalCustomAnimationDuration: 150
|
property int modalCustomAnimationDuration: 150
|
||||||
property bool enableRippleEffects: true
|
property bool enableRippleEffects: true
|
||||||
onEnableRippleEffectsChanged: saveSettings()
|
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
|
property bool m3ElevationEnabled: true
|
||||||
onM3ElevationEnabledChanged: saveSettings()
|
onM3ElevationEnabledChanged: saveSettings()
|
||||||
property int m3ElevationIntensity: 12
|
property int m3ElevationIntensity: 12
|
||||||
@@ -280,7 +298,6 @@ Singleton {
|
|||||||
property bool showOccupiedWorkspacesOnly: false
|
property bool showOccupiedWorkspacesOnly: false
|
||||||
property bool reverseScrolling: false
|
property bool reverseScrolling: false
|
||||||
property bool dwlShowAllTags: false
|
property bool dwlShowAllTags: false
|
||||||
property bool workspaceActiveAppHighlightEnabled: false
|
|
||||||
property string workspaceColorMode: "default"
|
property string workspaceColorMode: "default"
|
||||||
property string workspaceOccupiedColorMode: "none"
|
property string workspaceOccupiedColorMode: "none"
|
||||||
property string workspaceUnfocusedColorMode: "default"
|
property string workspaceUnfocusedColorMode: "default"
|
||||||
@@ -314,17 +331,6 @@ Singleton {
|
|||||||
property string centeringMode: "index"
|
property string centeringMode: "index"
|
||||||
property string clockDateFormat: ""
|
property string clockDateFormat: ""
|
||||||
property string lockDateFormat: ""
|
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 int mediaSize: 1
|
||||||
|
|
||||||
property string appLauncherViewMode: "list"
|
property string appLauncherViewMode: "list"
|
||||||
@@ -468,20 +474,18 @@ Singleton {
|
|||||||
property bool matugenTemplateGhostty: true
|
property bool matugenTemplateGhostty: true
|
||||||
property bool matugenTemplateKitty: true
|
property bool matugenTemplateKitty: true
|
||||||
property bool matugenTemplateFoot: true
|
property bool matugenTemplateFoot: true
|
||||||
property bool matugenTemplateNeovim: false
|
property bool matugenTemplateNeovim: true
|
||||||
property bool matugenTemplateAlacritty: true
|
property bool matugenTemplateAlacritty: true
|
||||||
property bool matugenTemplateWezterm: true
|
property bool matugenTemplateWezterm: true
|
||||||
property bool matugenTemplateDgop: true
|
property bool matugenTemplateDgop: true
|
||||||
property bool matugenTemplateKcolorscheme: true
|
property bool matugenTemplateKcolorscheme: true
|
||||||
property bool matugenTemplateVscode: true
|
property bool matugenTemplateVscode: true
|
||||||
property bool matugenTemplateEmacs: true
|
property bool matugenTemplateEmacs: true
|
||||||
property bool matugenTemplateZed: true
|
|
||||||
|
|
||||||
property bool showDock: false
|
property bool showDock: false
|
||||||
property bool dockAutoHide: false
|
property bool dockAutoHide: false
|
||||||
property bool dockSmartAutoHide: false
|
property bool dockSmartAutoHide: false
|
||||||
property bool dockGroupByApp: false
|
property bool dockGroupByApp: false
|
||||||
property bool dockRestoreSpecialWorkspaceOnClick: false
|
|
||||||
property bool dockOpenOnOverview: false
|
property bool dockOpenOnOverview: false
|
||||||
property int dockPosition: SettingsData.Position.Bottom
|
property int dockPosition: SettingsData.Position.Bottom
|
||||||
property real dockSpacing: 4
|
property real dockSpacing: 4
|
||||||
@@ -527,23 +531,9 @@ Singleton {
|
|||||||
property bool enableFprint: false
|
property bool enableFprint: false
|
||||||
property int maxFprintTries: 15
|
property int maxFprintTries: 15
|
||||||
property bool fprintdAvailable: false
|
property bool fprintdAvailable: false
|
||||||
property bool lockFingerprintCanEnable: false
|
|
||||||
property bool lockFingerprintReady: false
|
|
||||||
property string lockFingerprintReason: "probe_failed"
|
|
||||||
property bool greeterFingerprintCanEnable: false
|
|
||||||
property bool greeterFingerprintReady: false
|
|
||||||
property string greeterFingerprintReason: "probe_failed"
|
|
||||||
property string greeterFingerprintSource: "none"
|
|
||||||
property bool enableU2f: false
|
property bool enableU2f: false
|
||||||
property string u2fMode: "or"
|
property string u2fMode: "or"
|
||||||
property bool u2fAvailable: false
|
property bool u2fAvailable: false
|
||||||
property bool lockU2fCanEnable: false
|
|
||||||
property bool lockU2fReady: false
|
|
||||||
property string lockU2fReason: "probe_failed"
|
|
||||||
property bool greeterU2fCanEnable: false
|
|
||||||
property bool greeterU2fReady: false
|
|
||||||
property string greeterU2fReason: "probe_failed"
|
|
||||||
property string greeterU2fSource: "none"
|
|
||||||
property string lockScreenActiveMonitor: "all"
|
property string lockScreenActiveMonitor: "all"
|
||||||
property string lockScreenInactiveColor: "#000000"
|
property string lockScreenInactiveColor: "#000000"
|
||||||
property int lockScreenNotificationMode: 0
|
property int lockScreenNotificationMode: 0
|
||||||
@@ -566,7 +556,6 @@ Singleton {
|
|||||||
property bool notificationHistorySaveNormal: true
|
property bool notificationHistorySaveNormal: true
|
||||||
property bool notificationHistorySaveCritical: true
|
property bool notificationHistorySaveCritical: true
|
||||||
property var notificationRules: []
|
property var notificationRules: []
|
||||||
property bool notificationFocusedMonitor: false
|
|
||||||
|
|
||||||
property bool osdAlwaysShowValue: false
|
property bool osdAlwaysShowValue: false
|
||||||
property int osdPosition: SettingsData.Position.BottomCenter
|
property int osdPosition: SettingsData.Position.BottomCenter
|
||||||
@@ -1030,19 +1019,13 @@ Singleton {
|
|||||||
signal widgetDataChanged
|
signal widgetDataChanged
|
||||||
signal workspaceIconsUpdated
|
signal workspaceIconsUpdated
|
||||||
|
|
||||||
function refreshAuthAvailability() {
|
|
||||||
if (isGreeterMode)
|
|
||||||
return;
|
|
||||||
Processes.settingsRoot = root;
|
|
||||||
Processes.detectAuthCapabilities();
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (!isGreeterMode) {
|
if (!isGreeterMode) {
|
||||||
Processes.settingsRoot = root;
|
Processes.settingsRoot = root;
|
||||||
loadSettings();
|
loadSettings();
|
||||||
initializeListModels();
|
initializeListModels();
|
||||||
refreshAuthAvailability();
|
Processes.detectFprintd();
|
||||||
|
Processes.detectU2f();
|
||||||
Processes.checkPluginSettings();
|
Processes.checkPluginSettings();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1190,7 +1173,7 @@ Singleton {
|
|||||||
"updateCompositorLayout": updateCompositorLayout,
|
"updateCompositorLayout": updateCompositorLayout,
|
||||||
"applyStoredIconTheme": applyStoredIconTheme,
|
"applyStoredIconTheme": applyStoredIconTheme,
|
||||||
"updateBarConfigs": updateBarConfigs,
|
"updateBarConfigs": updateBarConfigs,
|
||||||
"updateCompositorCursor": updateCompositorCursor
|
"updateCompositorCursor": updateCompositorCursor,
|
||||||
})
|
})
|
||||||
|
|
||||||
function set(key, value) {
|
function set(key, value) {
|
||||||
@@ -1282,47 +1265,10 @@ Singleton {
|
|||||||
return JSON.stringify(Store.toJson(root), null, 2);
|
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() {
|
function loadPluginSettings() {
|
||||||
try {
|
_pluginSettingsLoading = true;
|
||||||
parsePluginSettings(pluginSettingsFile.text());
|
parsePluginSettings(pluginSettingsFile.text());
|
||||||
} catch (e) {
|
_pluginSettingsLoading = false;
|
||||||
const msg = e.message || String(e);
|
|
||||||
if (!_isMissingPluginSettingsError(e))
|
|
||||||
console.warn("SettingsData: Failed to load plugin_settings.json. Error:", msg);
|
|
||||||
_resetPluginSettings();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function parsePluginSettings(content) {
|
function parsePluginSettings(content) {
|
||||||
@@ -2758,7 +2704,6 @@ Singleton {
|
|||||||
blockLoading: true
|
blockLoading: true
|
||||||
blockWrites: true
|
blockWrites: true
|
||||||
atomicWrites: true
|
atomicWrites: true
|
||||||
printErrors: false
|
|
||||||
watchChanges: !isGreeterMode
|
watchChanges: !isGreeterMode
|
||||||
onLoaded: {
|
onLoaded: {
|
||||||
if (!isGreeterMode) {
|
if (!isGreeterMode) {
|
||||||
@@ -2767,10 +2712,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
onLoadFailed: error => {
|
onLoadFailed: error => {
|
||||||
if (!isGreeterMode) {
|
if (!isGreeterMode) {
|
||||||
const msg = String(error || "");
|
pluginSettings = {};
|
||||||
if (!_isMissingPluginSettingsError(error))
|
|
||||||
console.warn("SettingsData: Failed to load plugin_settings.json. Error:", msg);
|
|
||||||
_resetPluginSettings();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -960,6 +960,24 @@ Singleton {
|
|||||||
"expressiveEffects": [0.34, 0.8, 0.34, 1, 1, 1]
|
"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: {
|
readonly property var animationPresetDurations: {
|
||||||
"none": 0,
|
"none": 0,
|
||||||
"short": 250,
|
"short": 250,
|
||||||
@@ -1084,7 +1102,7 @@ Singleton {
|
|||||||
|
|
||||||
property string fontFamily: {
|
property string fontFamily: {
|
||||||
if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") {
|
if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") {
|
||||||
return GreetdSettings.getEffectiveFontFamily();
|
return GreetdSettings.fontFamily;
|
||||||
}
|
}
|
||||||
return typeof SettingsData !== "undefined" ? SettingsData.fontFamily : "Inter Variable";
|
return typeof SettingsData !== "undefined" ? SettingsData.fontFamily : "Inter Variable";
|
||||||
}
|
}
|
||||||
@@ -1551,7 +1569,7 @@ Singleton {
|
|||||||
if (typeof SettingsData !== "undefined") {
|
if (typeof SettingsData !== "undefined") {
|
||||||
const skipTemplates = [];
|
const skipTemplates = [];
|
||||||
if (!SettingsData.runDmsMatugenTemplates) {
|
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 {
|
} else {
|
||||||
if (!SettingsData.matugenTemplateGtk)
|
if (!SettingsData.matugenTemplateGtk)
|
||||||
skipTemplates.push("gtk");
|
skipTemplates.push("gtk");
|
||||||
@@ -1595,8 +1613,6 @@ Singleton {
|
|||||||
skipTemplates.push("vscode");
|
skipTemplates.push("vscode");
|
||||||
if (!SettingsData.matugenTemplateEmacs)
|
if (!SettingsData.matugenTemplateEmacs)
|
||||||
skipTemplates.push("emacs");
|
skipTemplates.push("emacs");
|
||||||
if (!SettingsData.matugenTemplateZed)
|
|
||||||
skipTemplates.push("zed");
|
|
||||||
}
|
}
|
||||||
if (skipTemplates.length > 0) {
|
if (skipTemplates.length > 0) {
|
||||||
args.push("--skip-templates", skipTemplates.join(","));
|
args.push("--skip-templates", skipTemplates.join(","));
|
||||||
@@ -1989,7 +2005,7 @@ Singleton {
|
|||||||
FileView {
|
FileView {
|
||||||
id: dynamicColorsFileView
|
id: dynamicColorsFileView
|
||||||
path: {
|
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";
|
const colorsPath = SessionData.isGreeterMode ? greetCfgDir + "/colors.json" : stateDir + "/dms-colors.json";
|
||||||
return colorsPath;
|
return colorsPath;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,352 +10,22 @@ Singleton {
|
|||||||
|
|
||||||
property var settingsRoot: null
|
property var settingsRoot: null
|
||||||
|
|
||||||
property string greetdPamText: ""
|
|
||||||
property string systemAuthPamText: ""
|
|
||||||
property string commonAuthPamText: ""
|
|
||||||
property string passwordAuthPamText: ""
|
|
||||||
property string systemLoginPamText: ""
|
|
||||||
property string systemLocalLoginPamText: ""
|
|
||||||
property string commonAuthPcPamText: ""
|
|
||||||
property string loginPamText: ""
|
|
||||||
property string dankshellU2fPamText: ""
|
|
||||||
property string u2fKeysText: ""
|
|
||||||
|
|
||||||
property string fingerprintProbeOutput: ""
|
|
||||||
property int fingerprintProbeExitCode: 0
|
|
||||||
property bool fingerprintProbeStreamFinished: false
|
|
||||||
property bool fingerprintProbeExited: false
|
|
||||||
property string fingerprintProbeState: "probe_failed"
|
|
||||||
|
|
||||||
property string pamSupportProbeOutput: ""
|
|
||||||
property bool pamSupportProbeStreamFinished: false
|
|
||||||
property bool pamSupportProbeExited: false
|
|
||||||
property int pamSupportProbeExitCode: 0
|
|
||||||
property bool pamFprintSupportDetected: false
|
|
||||||
property bool pamU2fSupportDetected: false
|
|
||||||
|
|
||||||
readonly property string homeDir: Quickshell.env("HOME") || ""
|
|
||||||
readonly property string u2fKeysPath: homeDir ? homeDir + "/.config/Yubico/u2f_keys" : ""
|
|
||||||
readonly property bool homeU2fKeysDetected: u2fKeysPath !== "" && u2fKeysWatcher.loaded && u2fKeysText.trim() !== ""
|
|
||||||
readonly property bool lockU2fCustomConfigDetected: pamModuleEnabled(dankshellU2fPamText, "pam_u2f")
|
|
||||||
readonly property bool greeterPamHasFprint: greeterPamStackHasModule("pam_fprintd")
|
|
||||||
readonly property bool greeterPamHasU2f: greeterPamStackHasModule("pam_u2f")
|
|
||||||
|
|
||||||
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() {
|
function detectQtTools() {
|
||||||
qtToolsDetectionProcess.running = true;
|
qtToolsDetectionProcess.running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function detectAuthCapabilities() {
|
|
||||||
if (!settingsRoot)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (forcedFprintAvailable === null) {
|
|
||||||
fingerprintProbeOutput = "";
|
|
||||||
fingerprintProbeStreamFinished = false;
|
|
||||||
fingerprintProbeExited = false;
|
|
||||||
fingerprintProbeProcess.running = true;
|
|
||||||
} else {
|
|
||||||
fingerprintProbeState = forcedFprintAvailable ? "ready" : "probe_failed";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (forcedFprintAvailable === null || forcedU2fAvailable === null) {
|
|
||||||
pamFprintSupportDetected = false;
|
|
||||||
pamU2fSupportDetected = false;
|
|
||||||
pamSupportProbeOutput = "";
|
|
||||||
pamSupportProbeStreamFinished = false;
|
|
||||||
pamSupportProbeExited = false;
|
|
||||||
pamSupportDetectionProcess.running = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
recomputeAuthCapabilities();
|
|
||||||
}
|
|
||||||
|
|
||||||
function detectFprintd() {
|
function detectFprintd() {
|
||||||
detectAuthCapabilities();
|
fprintdDetectionProcess.running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function detectU2f() {
|
function detectU2f() {
|
||||||
detectAuthCapabilities();
|
u2fDetectionProcess.running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkPluginSettings() {
|
function checkPluginSettings() {
|
||||||
pluginSettingsCheckProcess.running = true;
|
pluginSettingsCheckProcess.running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 pamTextIncludesFile(pamText, filename) {
|
|
||||||
if (!pamText || !filename)
|
|
||||||
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(filename) && (line.includes("include") || line.includes("substack") || line.startsWith("@include")))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function greeterPamStackHasModule(moduleName) {
|
|
||||||
if (pamModuleEnabled(greetdPamText, moduleName))
|
|
||||||
return true;
|
|
||||||
const includedPamStacks = [
|
|
||||||
["system-auth", systemAuthPamText],
|
|
||||||
["common-auth", commonAuthPamText],
|
|
||||||
["password-auth", passwordAuthPamText],
|
|
||||||
["system-login", systemLoginPamText],
|
|
||||||
["system-local-login", systemLocalLoginPamText],
|
|
||||||
["common-auth-pc", commonAuthPcPamText],
|
|
||||||
["login", loginPamText]
|
|
||||||
];
|
|
||||||
for (let i = 0; i < includedPamStacks.length; i++) {
|
|
||||||
const stack = includedPamStacks[i];
|
|
||||||
if (pamTextIncludesFile(greetdPamText, stack[0]) && pamModuleEnabled(stack[1], moduleName))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasEnrolledFingerprintOutput(output) {
|
|
||||||
const lower = (output || "").toLowerCase();
|
|
||||||
if (lower.includes("has fingers enrolled") || lower.includes("has fingerprints enrolled"))
|
|
||||||
return true;
|
|
||||||
const lines = lower.split(/\r?\n/);
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
|
||||||
const trimmed = lines[i].trim();
|
|
||||||
if (trimmed.startsWith("finger:"))
|
|
||||||
return true;
|
|
||||||
if (trimmed.startsWith("- ") && trimmed.includes("finger"))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasMissingFingerprintEnrollmentOutput(output) {
|
|
||||||
const lower = (output || "").toLowerCase();
|
|
||||||
return lower.includes("no fingers enrolled")
|
|
||||||
|| lower.includes("no fingerprints enrolled")
|
|
||||||
|| lower.includes("no prints enrolled");
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasMissingFingerprintReaderOutput(output) {
|
|
||||||
const lower = (output || "").toLowerCase();
|
|
||||||
return lower.includes("no devices available")
|
|
||||||
|| lower.includes("no device available")
|
|
||||||
|| lower.includes("no devices found")
|
|
||||||
|| lower.includes("list_devices failed")
|
|
||||||
|| lower.includes("no device");
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseFingerprintProbe(exitCode, output) {
|
|
||||||
if (hasEnrolledFingerprintOutput(output))
|
|
||||||
return "ready";
|
|
||||||
if (hasMissingFingerprintEnrollmentOutput(output))
|
|
||||||
return "missing_enrollment";
|
|
||||||
if (hasMissingFingerprintReaderOutput(output))
|
|
||||||
return "missing_reader";
|
|
||||||
if (exitCode === 0)
|
|
||||||
return "missing_enrollment";
|
|
||||||
if (exitCode === 127 || (output || "").includes("__missing_command__"))
|
|
||||||
return "probe_failed";
|
|
||||||
return pamFprintSupportDetected ? "probe_failed" : "missing_pam_support";
|
|
||||||
}
|
|
||||||
|
|
||||||
function setLockFingerprintCapability(canEnable, ready, reason) {
|
|
||||||
settingsRoot.lockFingerprintCanEnable = canEnable;
|
|
||||||
settingsRoot.lockFingerprintReady = ready;
|
|
||||||
settingsRoot.lockFingerprintReason = reason;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setLockU2fCapability(canEnable, ready, reason) {
|
|
||||||
settingsRoot.lockU2fCanEnable = canEnable;
|
|
||||||
settingsRoot.lockU2fReady = ready;
|
|
||||||
settingsRoot.lockU2fReason = reason;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setGreeterFingerprintCapability(canEnable, ready, reason, source) {
|
|
||||||
settingsRoot.greeterFingerprintCanEnable = canEnable;
|
|
||||||
settingsRoot.greeterFingerprintReady = ready;
|
|
||||||
settingsRoot.greeterFingerprintReason = reason;
|
|
||||||
settingsRoot.greeterFingerprintSource = source;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setGreeterU2fCapability(canEnable, ready, reason, source) {
|
|
||||||
settingsRoot.greeterU2fCanEnable = canEnable;
|
|
||||||
settingsRoot.greeterU2fReady = ready;
|
|
||||||
settingsRoot.greeterU2fReason = reason;
|
|
||||||
settingsRoot.greeterU2fSource = source;
|
|
||||||
}
|
|
||||||
|
|
||||||
function recomputeFingerprintCapabilities() {
|
|
||||||
if (forcedFprintAvailable !== null) {
|
|
||||||
const reason = forcedFprintAvailable ? "ready" : "probe_failed";
|
|
||||||
const source = forcedFprintAvailable ? "dms" : "none";
|
|
||||||
setLockFingerprintCapability(forcedFprintAvailable, forcedFprintAvailable, reason);
|
|
||||||
setGreeterFingerprintCapability(forcedFprintAvailable, forcedFprintAvailable, reason, source);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const state = fingerprintProbeState;
|
|
||||||
|
|
||||||
switch (state) {
|
|
||||||
case "ready":
|
|
||||||
setLockFingerprintCapability(true, true, "ready");
|
|
||||||
break;
|
|
||||||
case "missing_enrollment":
|
|
||||||
setLockFingerprintCapability(true, false, "missing_enrollment");
|
|
||||||
break;
|
|
||||||
case "missing_reader":
|
|
||||||
setLockFingerprintCapability(false, false, "missing_reader");
|
|
||||||
break;
|
|
||||||
case "missing_pam_support":
|
|
||||||
setLockFingerprintCapability(false, false, "missing_pam_support");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
setLockFingerprintCapability(false, false, "probe_failed");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (greeterPamHasFprint) {
|
|
||||||
switch (state) {
|
|
||||||
case "ready":
|
|
||||||
setGreeterFingerprintCapability(true, true, "configured_externally", "pam");
|
|
||||||
break;
|
|
||||||
case "missing_enrollment":
|
|
||||||
setGreeterFingerprintCapability(true, false, "missing_enrollment", "pam");
|
|
||||||
break;
|
|
||||||
case "missing_reader":
|
|
||||||
setGreeterFingerprintCapability(false, false, "missing_reader", "pam");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
setGreeterFingerprintCapability(true, false, "probe_failed", "pam");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (state) {
|
|
||||||
case "ready":
|
|
||||||
setGreeterFingerprintCapability(true, true, "ready", "dms");
|
|
||||||
break;
|
|
||||||
case "missing_enrollment":
|
|
||||||
setGreeterFingerprintCapability(true, false, "missing_enrollment", "dms");
|
|
||||||
break;
|
|
||||||
case "missing_reader":
|
|
||||||
setGreeterFingerprintCapability(false, false, "missing_reader", "none");
|
|
||||||
break;
|
|
||||||
case "missing_pam_support":
|
|
||||||
setGreeterFingerprintCapability(false, false, "missing_pam_support", "none");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
setGreeterFingerprintCapability(false, false, "probe_failed", "none");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function recomputeU2fCapabilities() {
|
|
||||||
if (forcedU2fAvailable !== null) {
|
|
||||||
const reason = forcedU2fAvailable ? "ready" : "probe_failed";
|
|
||||||
const source = forcedU2fAvailable ? "dms" : "none";
|
|
||||||
setLockU2fCapability(forcedU2fAvailable, forcedU2fAvailable, reason);
|
|
||||||
setGreeterU2fCapability(forcedU2fAvailable, forcedU2fAvailable, reason, source);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lockReady = lockU2fCustomConfigDetected || homeU2fKeysDetected;
|
|
||||||
const lockCanEnable = lockReady || pamU2fSupportDetected;
|
|
||||||
const lockReason = lockReady ? "ready" : (lockCanEnable ? "missing_key_registration" : "missing_pam_support");
|
|
||||||
setLockU2fCapability(lockCanEnable, lockReady, lockReason);
|
|
||||||
|
|
||||||
if (greeterPamHasU2f) {
|
|
||||||
setGreeterU2fCapability(true, true, "configured_externally", "pam");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const greeterReady = homeU2fKeysDetected;
|
|
||||||
const greeterCanEnable = greeterReady || pamU2fSupportDetected;
|
|
||||||
const greeterReason = greeterReady ? "ready" : (greeterCanEnable ? "missing_key_registration" : "missing_pam_support");
|
|
||||||
setGreeterU2fCapability(greeterCanEnable, greeterReady, greeterReason, greeterCanEnable ? "dms" : "none");
|
|
||||||
}
|
|
||||||
|
|
||||||
function recomputeAuthCapabilities() {
|
|
||||||
if (!settingsRoot)
|
|
||||||
return;
|
|
||||||
recomputeFingerprintCapabilities();
|
|
||||||
recomputeU2fCapabilities();
|
|
||||||
settingsRoot.fprintdAvailable = settingsRoot.lockFingerprintReady || settingsRoot.greeterFingerprintReady;
|
|
||||||
settingsRoot.u2fAvailable = settingsRoot.lockU2fReady || settingsRoot.greeterU2fReady;
|
|
||||||
}
|
|
||||||
|
|
||||||
function finalizeFingerprintProbe() {
|
|
||||||
if (!fingerprintProbeStreamFinished || !fingerprintProbeExited)
|
|
||||||
return;
|
|
||||||
fingerprintProbeState = parseFingerprintProbe(fingerprintProbeExitCode, fingerprintProbeOutput);
|
|
||||||
recomputeAuthCapabilities();
|
|
||||||
}
|
|
||||||
|
|
||||||
function finalizePamSupportProbe() {
|
|
||||||
if (!pamSupportProbeStreamFinished || !pamSupportProbeExited)
|
|
||||||
return;
|
|
||||||
|
|
||||||
pamFprintSupportDetected = false;
|
|
||||||
pamU2fSupportDetected = false;
|
|
||||||
|
|
||||||
const lines = (pamSupportProbeOutput || "").trim().split(/\r?\n/);
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
|
||||||
const parts = lines[i].split(":");
|
|
||||||
if (parts.length !== 2)
|
|
||||||
continue;
|
|
||||||
if (parts[0] === "pam_fprintd.so")
|
|
||||||
pamFprintSupportDetected = parts[1] === "true";
|
|
||||||
else if (parts[0] === "pam_u2f.so")
|
|
||||||
pamU2fSupportDetected = parts[1] === "true";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (forcedFprintAvailable === null && fingerprintProbeState === "missing_pam_support")
|
|
||||||
fingerprintProbeState = parseFingerprintProbe(fingerprintProbeExitCode, fingerprintProbeOutput);
|
|
||||||
|
|
||||||
recomputeAuthCapabilities();
|
|
||||||
}
|
|
||||||
|
|
||||||
property var qtToolsDetectionProcess: Process {
|
property var qtToolsDetectionProcess: Process {
|
||||||
command: ["sh", "-c", "echo -n 'qt5ct:'; command -v qt5ct >/dev/null && echo 'true' || echo 'false'; echo -n 'qt6ct:'; command -v qt6ct >/dev/null && echo 'true' || echo 'false'; echo -n 'gtk:'; (command -v gsettings >/dev/null || command -v dconf >/dev/null) && echo 'true' || echo 'false'"]
|
command: ["sh", "-c", "echo -n 'qt5ct:'; command -v qt5ct >/dev/null && echo 'true' || echo 'false'; echo -n 'qt6ct:'; command -v qt6ct >/dev/null && echo 'true' || echo 'false'; echo -n 'gtk:'; (command -v gsettings >/dev/null || command -v dconf >/dev/null) && echo 'true' || echo 'false'"]
|
||||||
running: false
|
running: false
|
||||||
@@ -365,15 +35,15 @@ Singleton {
|
|||||||
if (!settingsRoot)
|
if (!settingsRoot)
|
||||||
return;
|
return;
|
||||||
if (text && text.trim()) {
|
if (text && text.trim()) {
|
||||||
const lines = text.trim().split("\n");
|
var lines = text.trim().split('\n');
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
const line = lines[i];
|
var line = lines[i];
|
||||||
if (line.startsWith("qt5ct:")) {
|
if (line.startsWith('qt5ct:')) {
|
||||||
settingsRoot.qt5ctAvailable = line.split(":")[1] === "true";
|
settingsRoot.qt5ctAvailable = line.split(':')[1] === 'true';
|
||||||
} else if (line.startsWith("qt6ct:")) {
|
} else if (line.startsWith('qt6ct:')) {
|
||||||
settingsRoot.qt6ctAvailable = line.split(":")[1] === "true";
|
settingsRoot.qt6ctAvailable = line.split(':')[1] === 'true';
|
||||||
} else if (line.startsWith("gtk:")) {
|
} else if (line.startsWith('gtk:')) {
|
||||||
settingsRoot.gtkAvailable = line.split(":")[1] === "true";
|
settingsRoot.gtkAvailable = line.split(':')[1] === 'true';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -381,181 +51,23 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
property var fingerprintProbeProcess: Process {
|
property var fprintdDetectionProcess: Process {
|
||||||
command: ["sh", "-c", "if command -v fprintd-list >/dev/null 2>&1; then fprintd-list \"${USER:-$(id -un)}\" 2>&1; else printf '__missing_command__\\n'; exit 127; fi"]
|
command: ["sh", "-c", "command -v fprintd-list >/dev/null 2>&1"]
|
||||||
running: false
|
running: false
|
||||||
|
|
||||||
stdout: StdioCollector {
|
|
||||||
onStreamFinished: {
|
|
||||||
root.fingerprintProbeOutput = text || "";
|
|
||||||
root.fingerprintProbeStreamFinished = true;
|
|
||||||
root.finalizeFingerprintProbe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onExited: function (exitCode) {
|
onExited: function (exitCode) {
|
||||||
root.fingerprintProbeExitCode = exitCode;
|
if (!settingsRoot)
|
||||||
root.fingerprintProbeExited = true;
|
return;
|
||||||
root.finalizeFingerprintProbe();
|
settingsRoot.fprintdAvailable = (exitCode === 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
property var pamSupportDetectionProcess: Process {
|
property var u2fDetectionProcess: Process {
|
||||||
command: ["sh", "-c", "for module in pam_fprintd.so pam_u2f.so; do found=false; for dir in /usr/lib64/security /usr/lib/security /lib/security /lib/x86_64-linux-gnu/security /usr/lib/x86_64-linux-gnu/security /usr/lib/aarch64-linux-gnu/security /run/current-system/sw/lib/security; do if [ -f \"$dir/$module\" ]; then found=true; break; fi; done; printf '%s:%s\\n' \"$module\" \"$found\"; done"]
|
command: ["sh", "-c", "(test -f /usr/lib/security/pam_u2f.so || test -f /usr/lib64/security/pam_u2f.so) && (test -f /etc/pam.d/dankshell-u2f || test -f \"$HOME/.config/Yubico/u2f_keys\")"]
|
||||||
running: false
|
running: false
|
||||||
|
|
||||||
stdout: StdioCollector {
|
|
||||||
onStreamFinished: {
|
|
||||||
root.pamSupportProbeOutput = text || "";
|
|
||||||
root.pamSupportProbeStreamFinished = true;
|
|
||||||
root.finalizePamSupportProbe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onExited: function (exitCode) {
|
onExited: function (exitCode) {
|
||||||
root.pamSupportProbeExitCode = exitCode;
|
if (!settingsRoot)
|
||||||
root.pamSupportProbeExited = true;
|
return;
|
||||||
root.finalizePamSupportProbe();
|
settingsRoot.u2fAvailable = (exitCode === 0);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileView {
|
|
||||||
id: greetdPamWatcher
|
|
||||||
path: "/etc/pam.d/greetd"
|
|
||||||
printErrors: false
|
|
||||||
onLoaded: {
|
|
||||||
root.greetdPamText = text();
|
|
||||||
root.recomputeAuthCapabilities();
|
|
||||||
}
|
|
||||||
onLoadFailed: {
|
|
||||||
root.greetdPamText = "";
|
|
||||||
root.recomputeAuthCapabilities();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileView {
|
|
||||||
id: systemAuthPamWatcher
|
|
||||||
path: "/etc/pam.d/system-auth"
|
|
||||||
printErrors: false
|
|
||||||
onLoaded: {
|
|
||||||
root.systemAuthPamText = text();
|
|
||||||
root.recomputeAuthCapabilities();
|
|
||||||
}
|
|
||||||
onLoadFailed: {
|
|
||||||
root.systemAuthPamText = "";
|
|
||||||
root.recomputeAuthCapabilities();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileView {
|
|
||||||
id: commonAuthPamWatcher
|
|
||||||
path: "/etc/pam.d/common-auth"
|
|
||||||
printErrors: false
|
|
||||||
onLoaded: {
|
|
||||||
root.commonAuthPamText = text();
|
|
||||||
root.recomputeAuthCapabilities();
|
|
||||||
}
|
|
||||||
onLoadFailed: {
|
|
||||||
root.commonAuthPamText = "";
|
|
||||||
root.recomputeAuthCapabilities();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileView {
|
|
||||||
id: passwordAuthPamWatcher
|
|
||||||
path: "/etc/pam.d/password-auth"
|
|
||||||
printErrors: false
|
|
||||||
onLoaded: {
|
|
||||||
root.passwordAuthPamText = text();
|
|
||||||
root.recomputeAuthCapabilities();
|
|
||||||
}
|
|
||||||
onLoadFailed: {
|
|
||||||
root.passwordAuthPamText = "";
|
|
||||||
root.recomputeAuthCapabilities();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileView {
|
|
||||||
id: systemLoginPamWatcher
|
|
||||||
path: "/etc/pam.d/system-login"
|
|
||||||
printErrors: false
|
|
||||||
onLoaded: {
|
|
||||||
root.systemLoginPamText = text();
|
|
||||||
root.recomputeAuthCapabilities();
|
|
||||||
}
|
|
||||||
onLoadFailed: {
|
|
||||||
root.systemLoginPamText = "";
|
|
||||||
root.recomputeAuthCapabilities();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileView {
|
|
||||||
id: systemLocalLoginPamWatcher
|
|
||||||
path: "/etc/pam.d/system-local-login"
|
|
||||||
printErrors: false
|
|
||||||
onLoaded: {
|
|
||||||
root.systemLocalLoginPamText = text();
|
|
||||||
root.recomputeAuthCapabilities();
|
|
||||||
}
|
|
||||||
onLoadFailed: {
|
|
||||||
root.systemLocalLoginPamText = "";
|
|
||||||
root.recomputeAuthCapabilities();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileView {
|
|
||||||
id: commonAuthPcPamWatcher
|
|
||||||
path: "/etc/pam.d/common-auth-pc"
|
|
||||||
printErrors: false
|
|
||||||
onLoaded: {
|
|
||||||
root.commonAuthPcPamText = text();
|
|
||||||
root.recomputeAuthCapabilities();
|
|
||||||
}
|
|
||||||
onLoadFailed: {
|
|
||||||
root.commonAuthPcPamText = "";
|
|
||||||
root.recomputeAuthCapabilities();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileView {
|
|
||||||
id: loginPamWatcher
|
|
||||||
path: "/etc/pam.d/login"
|
|
||||||
printErrors: false
|
|
||||||
onLoaded: {
|
|
||||||
root.loginPamText = text();
|
|
||||||
root.recomputeAuthCapabilities();
|
|
||||||
}
|
|
||||||
onLoadFailed: {
|
|
||||||
root.loginPamText = "";
|
|
||||||
root.recomputeAuthCapabilities();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileView {
|
|
||||||
id: dankshellU2fPamWatcher
|
|
||||||
path: "/etc/pam.d/dankshell-u2f"
|
|
||||||
printErrors: false
|
|
||||||
onLoaded: {
|
|
||||||
root.dankshellU2fPamText = text();
|
|
||||||
root.recomputeAuthCapabilities();
|
|
||||||
}
|
|
||||||
onLoadFailed: {
|
|
||||||
root.dankshellU2fPamText = "";
|
|
||||||
root.recomputeAuthCapabilities();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileView {
|
|
||||||
id: u2fKeysWatcher
|
|
||||||
path: root.u2fKeysPath
|
|
||||||
printErrors: false
|
|
||||||
onLoaded: {
|
|
||||||
root.u2fKeysText = text();
|
|
||||||
root.recomputeAuthCapabilities();
|
|
||||||
}
|
|
||||||
onLoadFailed: {
|
|
||||||
root.u2fKeysText = "";
|
|
||||||
root.recomputeAuthCapabilities();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ var SPEC = {
|
|||||||
modalAnimationSpeed: { def: 1 },
|
modalAnimationSpeed: { def: 1 },
|
||||||
modalCustomAnimationDuration: { def: 150 },
|
modalCustomAnimationDuration: { def: 150 },
|
||||||
enableRippleEffects: { def: true },
|
enableRippleEffects: { def: true },
|
||||||
|
animationVariant: { def: 0 },
|
||||||
|
motionEffect: { def: 0 },
|
||||||
|
directionalAnimationMode: { def: 0 },
|
||||||
m3ElevationEnabled: { def: true },
|
m3ElevationEnabled: { def: true },
|
||||||
m3ElevationIntensity: { def: 12 },
|
m3ElevationIntensity: { def: 12 },
|
||||||
m3ElevationOpacity: { def: 30 },
|
m3ElevationOpacity: { def: 30 },
|
||||||
@@ -123,7 +126,6 @@ var SPEC = {
|
|||||||
showOccupiedWorkspacesOnly: { def: false },
|
showOccupiedWorkspacesOnly: { def: false },
|
||||||
reverseScrolling: { def: false },
|
reverseScrolling: { def: false },
|
||||||
dwlShowAllTags: { def: false },
|
dwlShowAllTags: { def: false },
|
||||||
workspaceActiveAppHighlightEnabled: { def: false },
|
|
||||||
workspaceColorMode: { def: "default" },
|
workspaceColorMode: { def: "default" },
|
||||||
workspaceOccupiedColorMode: { def: "none" },
|
workspaceOccupiedColorMode: { def: "none" },
|
||||||
workspaceUnfocusedColorMode: { def: "default" },
|
workspaceUnfocusedColorMode: { def: "default" },
|
||||||
@@ -165,17 +167,6 @@ var SPEC = {
|
|||||||
centeringMode: { def: "index" },
|
centeringMode: { def: "index" },
|
||||||
clockDateFormat: { def: "" },
|
clockDateFormat: { def: "" },
|
||||||
lockDateFormat: { 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 },
|
mediaSize: { def: 1 },
|
||||||
|
|
||||||
appLauncherViewMode: { def: "list" },
|
appLauncherViewMode: { def: "list" },
|
||||||
@@ -284,19 +275,17 @@ var SPEC = {
|
|||||||
matugenTemplateKitty: { def: true },
|
matugenTemplateKitty: { def: true },
|
||||||
matugenTemplateFoot: { def: true },
|
matugenTemplateFoot: { def: true },
|
||||||
matugenTemplateAlacritty: { def: true },
|
matugenTemplateAlacritty: { def: true },
|
||||||
matugenTemplateNeovim: { def: false },
|
matugenTemplateNeovim: { def: true },
|
||||||
matugenTemplateWezterm: { def: true },
|
matugenTemplateWezterm: { def: true },
|
||||||
matugenTemplateDgop: { def: true },
|
matugenTemplateDgop: { def: true },
|
||||||
matugenTemplateKcolorscheme: { def: true },
|
matugenTemplateKcolorscheme: { def: true },
|
||||||
matugenTemplateVscode: { def: true },
|
matugenTemplateVscode: { def: true },
|
||||||
matugenTemplateEmacs: { def: true },
|
matugenTemplateEmacs: { def: true },
|
||||||
matugenTemplateZed: { def: true },
|
|
||||||
|
|
||||||
showDock: { def: false },
|
showDock: { def: false },
|
||||||
dockAutoHide: { def: false },
|
dockAutoHide: { def: false },
|
||||||
dockSmartAutoHide: { def: false },
|
dockSmartAutoHide: { def: false },
|
||||||
dockGroupByApp: { def: false },
|
dockGroupByApp: { def: false },
|
||||||
dockRestoreSpecialWorkspaceOnClick: { def: false },
|
|
||||||
dockOpenOnOverview: { def: false },
|
dockOpenOnOverview: { def: false },
|
||||||
dockPosition: { def: 1 },
|
dockPosition: { def: 1 },
|
||||||
dockSpacing: { def: 4 },
|
dockSpacing: { def: 4 },
|
||||||
@@ -341,23 +330,9 @@ var SPEC = {
|
|||||||
enableFprint: { def: false },
|
enableFprint: { def: false },
|
||||||
maxFprintTries: { def: 15 },
|
maxFprintTries: { def: 15 },
|
||||||
fprintdAvailable: { def: false, persist: false },
|
fprintdAvailable: { def: false, persist: false },
|
||||||
lockFingerprintCanEnable: { def: false, persist: false },
|
|
||||||
lockFingerprintReady: { def: false, persist: false },
|
|
||||||
lockFingerprintReason: { def: "probe_failed", persist: false },
|
|
||||||
greeterFingerprintCanEnable: { def: false, persist: false },
|
|
||||||
greeterFingerprintReady: { def: false, persist: false },
|
|
||||||
greeterFingerprintReason: { def: "probe_failed", persist: false },
|
|
||||||
greeterFingerprintSource: { def: "none", persist: false },
|
|
||||||
enableU2f: { def: false },
|
enableU2f: { def: false },
|
||||||
u2fMode: { def: "or" },
|
u2fMode: { def: "or" },
|
||||||
u2fAvailable: { def: false, persist: false },
|
u2fAvailable: { def: false, persist: false },
|
||||||
lockU2fCanEnable: { def: false, persist: false },
|
|
||||||
lockU2fReady: { def: false, persist: false },
|
|
||||||
lockU2fReason: { def: "probe_failed", persist: false },
|
|
||||||
greeterU2fCanEnable: { def: false, persist: false },
|
|
||||||
greeterU2fReady: { def: false, persist: false },
|
|
||||||
greeterU2fReason: { def: "probe_failed", persist: false },
|
|
||||||
greeterU2fSource: { def: "none", persist: false },
|
|
||||||
lockScreenActiveMonitor: { def: "all" },
|
lockScreenActiveMonitor: { def: "all" },
|
||||||
lockScreenInactiveColor: { def: "#000000" },
|
lockScreenInactiveColor: { def: "#000000" },
|
||||||
lockScreenNotificationMode: { def: 0 },
|
lockScreenNotificationMode: { def: 0 },
|
||||||
@@ -380,7 +355,6 @@ var SPEC = {
|
|||||||
notificationHistorySaveNormal: { def: true },
|
notificationHistorySaveNormal: { def: true },
|
||||||
notificationHistorySaveCritical: { def: true },
|
notificationHistorySaveCritical: { def: true },
|
||||||
notificationRules: { def: [] },
|
notificationRules: { def: [] },
|
||||||
notificationFocusedMonitor: { def: false },
|
|
||||||
|
|
||||||
osdAlwaysShowValue: { def: false },
|
osdAlwaysShowValue: { def: false },
|
||||||
osdPosition: { def: 5 },
|
osdPosition: { def: 5 },
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import Quickshell.Services.Greetd
|
||||||
|
import qs.Common
|
||||||
import qs.Modules.Greetd
|
import qs.Modules.Greetd
|
||||||
|
|
||||||
Scope {
|
Scope {
|
||||||
|
|||||||
@@ -313,7 +313,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Variants {
|
Variants {
|
||||||
model: SettingsData.notificationFocusedMonitor ? Quickshell.screens : SettingsData.getFilteredScreens("notifications")
|
model: SettingsData.getFilteredScreens("notifications")
|
||||||
|
|
||||||
delegate: NotificationPopupManager {
|
delegate: NotificationPopupManager {
|
||||||
modelData: item
|
modelData: item
|
||||||
|
|||||||
@@ -26,10 +26,10 @@ Item {
|
|||||||
property bool closeOnBackgroundClick: true
|
property bool closeOnBackgroundClick: true
|
||||||
property string animationType: "scale"
|
property string animationType: "scale"
|
||||||
property int animationDuration: Theme.modalAnimationDuration
|
property int animationDuration: Theme.modalAnimationDuration
|
||||||
property real animationScaleCollapsed: 0.96
|
property real animationScaleCollapsed: Theme.effectScaleCollapsed
|
||||||
property real animationOffset: Theme.spacingL
|
property real animationOffset: Theme.effectAnimOffset
|
||||||
property list<real> animationEnterCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
property list<real> animationEnterCurve: Theme.variantModalEnterCurve
|
||||||
property list<real> animationExitCurve: Theme.expressiveCurves.emphasized
|
property list<real> animationExitCurve: Theme.variantModalExitCurve
|
||||||
property color backgroundColor: Theme.surfaceContainer
|
property color backgroundColor: Theme.surfaceContainer
|
||||||
property color borderColor: Theme.outlineMedium
|
property color borderColor: Theme.outlineMedium
|
||||||
property real borderWidth: 0
|
property real borderWidth: 0
|
||||||
@@ -44,11 +44,13 @@ Item {
|
|||||||
property bool keepPopoutsOpen: false
|
property bool keepPopoutsOpen: false
|
||||||
property var customKeyboardFocus: null
|
property var customKeyboardFocus: null
|
||||||
property bool useOverlayLayer: false
|
property bool useOverlayLayer: false
|
||||||
|
property real frozenMotionOffsetX: 0
|
||||||
|
property real frozenMotionOffsetY: 0
|
||||||
readonly property alias contentWindow: contentWindow
|
readonly property alias contentWindow: contentWindow
|
||||||
readonly property alias clickCatcher: clickCatcher
|
readonly property alias clickCatcher: clickCatcher
|
||||||
readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab
|
readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab
|
||||||
readonly property bool useBackground: showBackground && SettingsData.modalDarkenBackground
|
readonly property bool useBackground: showBackground && SettingsData.modalDarkenBackground
|
||||||
readonly property bool useSingleWindow: CompositorService.isHyprland || useBackground
|
readonly property bool useSingleWindow: CompositorService.isHyprland
|
||||||
|
|
||||||
signal opened
|
signal opened
|
||||||
signal dialogClosed
|
signal dialogClosed
|
||||||
@@ -58,19 +60,34 @@ Item {
|
|||||||
|
|
||||||
function open() {
|
function open() {
|
||||||
closeTimer.stop();
|
closeTimer.stop();
|
||||||
|
animationsEnabled = false;
|
||||||
|
frozenMotionOffsetX = modalContainer ? modalContainer.offsetX : 0;
|
||||||
|
frozenMotionOffsetY = modalContainer ? modalContainer.offsetY : animationOffset;
|
||||||
|
|
||||||
const focusedScreen = CompositorService.getFocusedScreen();
|
const focusedScreen = CompositorService.getFocusedScreen();
|
||||||
if (focusedScreen) {
|
if (focusedScreen) {
|
||||||
contentWindow.screen = focusedScreen;
|
contentWindow.screen = focusedScreen;
|
||||||
if (!useSingleWindow)
|
if (!useSingleWindow)
|
||||||
clickCatcher.screen = focusedScreen;
|
clickCatcher.screen = focusedScreen;
|
||||||
}
|
}
|
||||||
ModalManager.openModal(root);
|
|
||||||
shouldBeVisible = true;
|
if (Theme.isDirectionalEffect || root.useBackground) {
|
||||||
if (!useSingleWindow)
|
if (!useSingleWindow)
|
||||||
clickCatcher.visible = true;
|
clickCatcher.visible = true;
|
||||||
contentWindow.visible = true;
|
contentWindow.visible = true;
|
||||||
|
}
|
||||||
|
ModalManager.openModal(root);
|
||||||
|
|
||||||
|
Qt.callLater(() => {
|
||||||
|
animationsEnabled = true;
|
||||||
|
shouldBeVisible = true;
|
||||||
|
if (!useSingleWindow && !clickCatcher.visible)
|
||||||
|
clickCatcher.visible = true;
|
||||||
|
if (!contentWindow.visible)
|
||||||
|
contentWindow.visible = true;
|
||||||
shouldHaveFocus = false;
|
shouldHaveFocus = false;
|
||||||
Qt.callLater(() => shouldHaveFocus = Qt.binding(() => shouldBeVisible));
|
Qt.callLater(() => shouldHaveFocus = Qt.binding(() => shouldBeVisible));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
@@ -131,7 +148,7 @@ Item {
|
|||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: closeTimer
|
id: closeTimer
|
||||||
interval: animationDuration + 50
|
interval: Theme.variantCloseInterval(animationDuration)
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (shouldBeVisible)
|
if (shouldBeVisible)
|
||||||
return;
|
return;
|
||||||
@@ -145,7 +162,17 @@ Item {
|
|||||||
readonly property var shadowLevel: Theme.elevationLevel3
|
readonly property var shadowLevel: Theme.elevationLevel3
|
||||||
readonly property real shadowFallbackOffset: 6
|
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 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 shadowBuffer: Theme.snap(shadowRenderPadding + shadowMotionPadding, dpr)
|
||||||
readonly property real alignedWidth: Theme.px(modalWidth, dpr)
|
readonly property real alignedWidth: Theme.px(modalWidth, dpr)
|
||||||
readonly property real alignedHeight: Theme.px(modalHeight, dpr)
|
readonly property real alignedHeight: Theme.px(modalHeight, dpr)
|
||||||
@@ -205,9 +232,26 @@ Item {
|
|||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
enabled: root.closeOnBackgroundClick && root.shouldBeVisible
|
enabled: !root.useSingleWindow && root.closeOnBackgroundClick && root.shouldBeVisible
|
||||||
onClicked: root.backgroundClicked()
|
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 {
|
PanelWindow {
|
||||||
@@ -250,9 +294,12 @@ Item {
|
|||||||
bottom: root.useSingleWindow
|
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 {
|
WlrLayershell.margins {
|
||||||
left: root.useSingleWindow ? 0 : Math.max(0, Theme.snap(root.alignedX - shadowBuffer, dpr))
|
left: actualMarginLeft
|
||||||
top: root.useSingleWindow ? 0 : Math.max(0, Theme.snap(root.alignedY - shadowBuffer, dpr))
|
top: actualMarginTop
|
||||||
right: 0
|
right: 0
|
||||||
bottom: 0
|
bottom: 0
|
||||||
}
|
}
|
||||||
@@ -282,13 +329,14 @@ Item {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
z: -1
|
z: -1
|
||||||
color: "black"
|
color: "black"
|
||||||
opacity: root.useBackground ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
|
opacity: (root.useSingleWindow && root.useBackground) ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
|
||||||
visible: root.useBackground
|
visible: opacity > 0
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
enabled: root.animationsEnabled
|
enabled: root.animationsEnabled && !Theme.isDirectionalEffect
|
||||||
DankAnim {
|
NumberAnimation {
|
||||||
duration: root.animationDuration
|
duration: Math.round(Theme.variantDuration(root.animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -296,8 +344,8 @@ Item {
|
|||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: modalContainer
|
id: modalContainer
|
||||||
x: root.useSingleWindow ? root.alignedX : shadowBuffer
|
x: (root.useSingleWindow ? root.alignedX : (root.alignedX - contentWindow.actualMarginLeft)) + Theme.snap(animX, root.dpr)
|
||||||
y: root.useSingleWindow ? root.alignedY : shadowBuffer
|
y: (root.useSingleWindow ? root.alignedY : (root.alignedY - contentWindow.actualMarginTop)) + Theme.snap(animY, root.dpr)
|
||||||
|
|
||||||
width: root.alignedWidth
|
width: root.alignedWidth
|
||||||
height: root.alignedHeight
|
height: root.alignedHeight
|
||||||
@@ -313,45 +361,117 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
readonly property bool slide: root.animationType === "slide"
|
readonly property bool slide: root.animationType === "slide"
|
||||||
readonly property real offsetX: slide ? 15 : 0
|
readonly property bool directionalEffect: Theme.isDirectionalEffect
|
||||||
readonly property real offsetY: slide ? -30 : root.animationOffset
|
readonly property bool depthEffect: Theme.isDepthEffect
|
||||||
|
readonly property real directionalTravel: Math.max(root.animationOffset, Math.max(root.alignedWidth, root.alignedHeight) * 0.8)
|
||||||
property real animX: 0
|
readonly property real depthTravel: Math.max(root.animationOffset * 0.8, 36)
|
||||||
property real animY: 0
|
readonly property real customAnchorX: root.alignedX + root.alignedWidth * 0.5
|
||||||
property real scaleValue: root.animationScaleCollapsed
|
readonly property real customAnchorY: root.alignedY + root.alignedHeight * 0.5
|
||||||
|
readonly property real customDistLeft: customAnchorX
|
||||||
onOffsetXChanged: animX = Theme.snap(root.shouldBeVisible ? 0 : offsetX, root.dpr)
|
readonly property real customDistRight: root.screenWidth - customAnchorX
|
||||||
onOffsetYChanged: animY = Theme.snap(root.shouldBeVisible ? 0 : offsetY, root.dpr)
|
readonly property real customDistTop: customAnchorY
|
||||||
|
readonly property real customDistBottom: root.screenHeight - customAnchorY
|
||||||
Connections {
|
readonly property real offsetX: {
|
||||||
target: root
|
if (typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode === 2 && Theme.isDirectionalEffect)
|
||||||
function onShouldBeVisibleChanged() {
|
return 0;
|
||||||
modalContainer.animX = Theme.snap(root.shouldBeVisible ? 0 : modalContainer.offsetX, root.dpr);
|
if (slide && !directionalEffect && !depthEffect)
|
||||||
modalContainer.animY = Theme.snap(root.shouldBeVisible ? 0 : modalContainer.offsetY, root.dpr);
|
return 15;
|
||||||
modalContainer.scaleValue = root.shouldBeVisible ? 1.0 : root.animationScaleCollapsed;
|
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 {
|
Behavior on animX {
|
||||||
enabled: root.animationsEnabled
|
enabled: root.animationsEnabled
|
||||||
DankAnim {
|
NumberAnimation {
|
||||||
duration: root.animationDuration
|
duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible)
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on animY {
|
Behavior on animY {
|
||||||
enabled: root.animationsEnabled
|
enabled: root.animationsEnabled
|
||||||
DankAnim {
|
NumberAnimation {
|
||||||
duration: root.animationDuration
|
duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible)
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on scaleValue {
|
Behavior on scaleValue {
|
||||||
enabled: root.animationsEnabled
|
enabled: root.animationsEnabled
|
||||||
DankAnim {
|
NumberAnimation {
|
||||||
duration: root.animationDuration
|
duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible)
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -367,15 +487,14 @@ Item {
|
|||||||
id: animatedContent
|
id: animatedContent
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
clip: false
|
clip: false
|
||||||
opacity: root.shouldBeVisible ? 1 : 0
|
opacity: Theme.isDirectionalEffect ? 1 : (root.shouldBeVisible ? 1 : 0)
|
||||||
scale: modalContainer.scaleValue
|
scale: modalContainer.scaleValue
|
||||||
x: Theme.snap(modalContainer.animX, root.dpr) + (parent.width - width) * (1 - modalContainer.scaleValue) * 0.5
|
transformOrigin: Item.Center
|
||||||
y: Theme.snap(modalContainer.animY, root.dpr) + (parent.height - height) * (1 - modalContainer.scaleValue) * 0.5
|
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
enabled: root.animationsEnabled
|
enabled: root.animationsEnabled && !Theme.isDirectionalEffect
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: animationDuration
|
duration: Math.round(Theme.variantDuration(animationDuration, root.shouldBeVisible) * Theme.variantOpacityDurationScale)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1006,7 +1006,9 @@ Item {
|
|||||||
_applyHighlights(newSections, searchQuery);
|
_applyHighlights(newSections, searchQuery);
|
||||||
flatModel = Scorer.flattenSections(newSections);
|
flatModel = Scorer.flattenSections(newSections);
|
||||||
sections = newSections;
|
sections = newSections;
|
||||||
|
if (selectedFlatIndex >= flatModel.length) {
|
||||||
selectedFlatIndex = getFirstItemIndex();
|
selectedFlatIndex = getFirstItemIndex();
|
||||||
|
}
|
||||||
updateSelectedItem();
|
updateSelectedItem();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ Item {
|
|||||||
property bool spotlightOpen: false
|
property bool spotlightOpen: false
|
||||||
property bool keyboardActive: false
|
property bool keyboardActive: false
|
||||||
property bool contentVisible: false
|
property bool contentVisible: false
|
||||||
|
readonly property bool launcherMotionVisible: Theme.isDirectionalEffect ? spotlightOpen : _motionActive
|
||||||
property var spotlightContent: launcherContentLoader.item
|
property var spotlightContent: launcherContentLoader.item
|
||||||
property bool openedFromOverview: false
|
property bool openedFromOverview: false
|
||||||
property bool isClosing: false
|
property bool isClosing: false
|
||||||
@@ -23,8 +24,14 @@ Item {
|
|||||||
property string _pendingMode: ""
|
property string _pendingMode: ""
|
||||||
readonly property bool unloadContentOnClose: SettingsData.dankLauncherV2UnloadOnClose
|
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 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 screenWidth: effectiveScreen?.width ?? 1920
|
||||||
readonly property real screenHeight: effectiveScreen?.height ?? 1080
|
readonly property real screenHeight: effectiveScreen?.height ?? 1080
|
||||||
readonly property real dpr: effectiveScreen ? CompositorService.getScreenScale(effectiveScreen) : 1
|
readonly property real dpr: effectiveScreen ? CompositorService.getScreenScale(effectiveScreen) : 1
|
||||||
@@ -78,6 +85,34 @@ Item {
|
|||||||
}
|
}
|
||||||
readonly property int borderWidth: SettingsData.dankLauncherV2BorderEnabled ? SettingsData.dankLauncherV2BorderThickness : 0
|
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
|
signal dialogClosed
|
||||||
|
|
||||||
function _ensureContentLoadedAndInitialize(query, mode) {
|
function _ensureContentLoadedAndInitialize(query, mode) {
|
||||||
@@ -97,7 +132,8 @@ Item {
|
|||||||
if (!spotlightContent)
|
if (!spotlightContent)
|
||||||
return;
|
return;
|
||||||
contentVisible = true;
|
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) {
|
if (spotlightContent.searchField) {
|
||||||
spotlightContent.searchField.text = query;
|
spotlightContent.searchField.text = query;
|
||||||
@@ -130,40 +166,59 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function show() {
|
function _openCommon(query, mode) {
|
||||||
closeCleanupTimer.stop();
|
closeCleanupTimer.stop();
|
||||||
isClosing = false;
|
isClosing = false;
|
||||||
openedFromOverview = false;
|
openedFromOverview = false;
|
||||||
|
|
||||||
var focusedScreen = CompositorService.getFocusedScreen();
|
// Disable animations so the snap is instant
|
||||||
if (focusedScreen)
|
animationsEnabled = false;
|
||||||
launcherWindow.screen = focusedScreen;
|
|
||||||
|
|
||||||
spotlightOpen = true;
|
// Freeze the collapsed offsets (they depend on height which could change)
|
||||||
keyboardActive = true;
|
_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);
|
ModalManager.openModal(root);
|
||||||
|
spotlightOpen = true;
|
||||||
|
backgroundWindow.visible = true;
|
||||||
|
contentWindow.visible = true;
|
||||||
if (useHyprlandFocusGrab)
|
if (useHyprlandFocusGrab)
|
||||||
focusGrab.active = true;
|
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) {
|
function showWithQuery(query) {
|
||||||
closeCleanupTimer.stop();
|
_openCommon(query, "");
|
||||||
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, "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function hide() {
|
function hide() {
|
||||||
@@ -171,13 +226,17 @@ Item {
|
|||||||
return;
|
return;
|
||||||
openedFromOverview = false;
|
openedFromOverview = false;
|
||||||
isClosing = true;
|
isClosing = true;
|
||||||
|
// For directional effects, defer contentVisible=false so content stays rendered during exit slide
|
||||||
|
if (!Theme.isDirectionalEffect)
|
||||||
contentVisible = false;
|
contentVisible = false;
|
||||||
|
|
||||||
|
// Trigger exit animation — Behaviors will animate motionX/Y to frozen collapsed position
|
||||||
|
_motionActive = false;
|
||||||
|
|
||||||
keyboardActive = false;
|
keyboardActive = false;
|
||||||
spotlightOpen = false;
|
spotlightOpen = false;
|
||||||
focusGrab.active = false;
|
focusGrab.active = false;
|
||||||
ModalManager.closeModal(root);
|
ModalManager.closeModal(root);
|
||||||
|
|
||||||
closeCleanupTimer.start();
|
closeCleanupTimer.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,21 +245,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function showWithMode(mode) {
|
function showWithMode(mode) {
|
||||||
closeCleanupTimer.stop();
|
_openCommon("", mode);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleWithMode(mode) {
|
function toggleWithMode(mode) {
|
||||||
@@ -221,10 +266,13 @@ Item {
|
|||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: closeCleanupTimer
|
id: closeCleanupTimer
|
||||||
interval: Theme.modalAnimationDuration + 50
|
interval: Theme.variantCloseInterval(Theme.modalAnimationDuration)
|
||||||
repeat: false
|
repeat: false
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
isClosing = false;
|
isClosing = false;
|
||||||
|
contentVisible = false;
|
||||||
|
contentWindow.visible = false;
|
||||||
|
backgroundWindow.visible = false;
|
||||||
if (root.unloadContentOnClose)
|
if (root.unloadContentOnClose)
|
||||||
launcherContentLoader.active = false;
|
launcherContentLoader.active = false;
|
||||||
dialogClosed();
|
dialogClosed();
|
||||||
@@ -242,7 +290,7 @@ Item {
|
|||||||
|
|
||||||
HyprlandFocusGrab {
|
HyprlandFocusGrab {
|
||||||
id: focusGrab
|
id: focusGrab
|
||||||
windows: [launcherWindow]
|
windows: [contentWindow]
|
||||||
active: false
|
active: false
|
||||||
|
|
||||||
onCleared: {
|
onCleared: {
|
||||||
@@ -267,7 +315,7 @@ Item {
|
|||||||
if (Quickshell.screens.length === 0)
|
if (Quickshell.screens.length === 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const screen = launcherWindow.screen;
|
const screen = contentWindow.screen;
|
||||||
const screenName = screen?.name;
|
const screenName = screen?.name;
|
||||||
|
|
||||||
let needsReset = !screen || !screenName;
|
let needsReset = !screen || !screenName;
|
||||||
@@ -289,35 +337,31 @@ Item {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
root._windowEnabled = false;
|
root._windowEnabled = false;
|
||||||
launcherWindow.screen = newScreen;
|
backgroundWindow.screen = newScreen;
|
||||||
|
contentWindow.screen = newScreen;
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
root._windowEnabled = true;
|
root._windowEnabled = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Background window: fullscreen, handles darkening + click-to-dismiss ──
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: launcherWindow
|
id: backgroundWindow
|
||||||
visible: root._windowEnabled && (spotlightOpen || isClosing)
|
visible: false
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
exclusionMode: ExclusionMode.Ignore
|
|
||||||
|
|
||||||
WlrLayershell.namespace: "dms:spotlight"
|
WlrLayershell.namespace: "dms:spotlight:bg"
|
||||||
WlrLayershell.layer: {
|
WlrLayershell.layer: WlrLayershell.Top
|
||||||
switch (Quickshell.env("DMS_MODAL_LAYER")) {
|
WlrLayershell.exclusiveZone: -1
|
||||||
case "bottom":
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||||
console.error("DankModal: 'bottom' layer is not valid for modals. Defaulting to 'top' layer.");
|
|
||||||
return WlrLayershell.Top;
|
WlrLayershell.margins {
|
||||||
case "background":
|
top: contentContainer.dockTop ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 0 ? Theme.px(42, root.dpr) : 0)
|
||||||
console.error("DankModal: 'background' layer is not valid for modals. Defaulting to 'top' layer.");
|
bottom: contentContainer.dockBottom ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 1 ? Theme.px(42, root.dpr) : 0)
|
||||||
return WlrLayershell.Top;
|
left: contentContainer.dockLeft ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 2 ? Theme.px(42, root.dpr) : 0)
|
||||||
case "overlay":
|
right: contentContainer.dockRight ? contentContainer.dockThickness : (typeof SettingsData !== "undefined" && SettingsData.barPosition === 3 ? Theme.px(42, root.dpr) : 0)
|
||||||
return WlrLayershell.Overlay;
|
|
||||||
default:
|
|
||||||
return WlrLayershell.Top;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
WlrLayershell.keyboardFocus: keyboardActive ? (root.useHyprlandFocusGrab ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive) : WlrKeyboardFocus.None
|
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
top: true
|
top: true
|
||||||
@@ -327,11 +371,11 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mask: Region {
|
mask: Region {
|
||||||
item: spotlightOpen ? fullScreenMask : null
|
item: (spotlightOpen || isClosing) ? bgFullScreenMask : null
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: fullScreenMask
|
id: bgFullScreenMask
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,13 +383,14 @@ Item {
|
|||||||
id: backgroundDarken
|
id: backgroundDarken
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: "black"
|
color: "black"
|
||||||
opacity: contentVisible && SettingsData.modalDarkenBackground ? 0.5 : 0
|
opacity: launcherMotionVisible && SettingsData.modalDarkenBackground ? 0.5 : 0
|
||||||
visible: contentVisible || opacity > 0
|
visible: launcherMotionVisible || opacity > 0
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
|
enabled: root.animationsEnabled && !Theme.isDirectionalEffect
|
||||||
DankAnim {
|
DankAnim {
|
||||||
duration: Theme.modalAnimationDuration
|
duration: Math.round(Theme.variantDuration(Theme.modalAnimationDuration, launcherMotionVisible) * Theme.variantOpacityDurationScale)
|
||||||
easing.bezierCurve: contentVisible ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
|
easing.bezierCurve: launcherMotionVisible ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -353,49 +398,179 @@ Item {
|
|||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
enabled: spotlightOpen
|
enabled: spotlightOpen
|
||||||
onClicked: mouse => {
|
onClicked: root.hide()
|
||||||
var contentX = modalContainer.x;
|
|
||||||
var contentY = modalContainer.y;
|
|
||||||
var contentW = modalContainer.width;
|
|
||||||
var contentH = modalContainer.height;
|
|
||||||
|
|
||||||
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: 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 {
|
Item {
|
||||||
id: modalContainer
|
id: directionalClipMask
|
||||||
x: root.modalX
|
readonly property bool shouldClip: Theme.isDirectionalEffect && typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode > 0
|
||||||
y: root.modalY
|
readonly property real clipOversize: 2000
|
||||||
width: root.modalWidth
|
|
||||||
height: root.modalHeight
|
|
||||||
visible: contentVisible || opacity > 0
|
|
||||||
|
|
||||||
opacity: contentVisible ? 1 : 0
|
clip: shouldClip
|
||||||
scale: contentVisible ? 1 : 0.96
|
|
||||||
transformOrigin: Item.Center
|
|
||||||
|
|
||||||
Behavior on opacity {
|
x: shouldClip ? (contentContainer.dockRight ? -clipOversize : (contentContainer.dockLeft ? contentContainer.dockThickness - root._ccX : -clipOversize)) : 0
|
||||||
DankAnim {
|
y: shouldClip ? (contentContainer.dockBottom ? -clipOversize : (contentContainer.dockTop ? contentContainer.dockThickness - root._ccY : -clipOversize)) : 0
|
||||||
duration: Theme.modalAnimationDuration
|
|
||||||
easing.bezierCurve: contentVisible ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on scale {
|
width: shouldClip ? parent.width + clipOversize + (contentContainer.dockRight ? (root.screenWidth - contentContainer.dockThickness - root._ccX - parent.width) : (contentContainer.dockLeft ? clipOversize : clipOversize)) : parent.width
|
||||||
DankAnim {
|
height: shouldClip ? parent.height + clipOversize + (contentContainer.dockBottom ? (root.screenHeight - contentContainer.dockThickness - root._ccY - parent.height) : (contentContainer.dockTop ? clipOversize : clipOversize)) : parent.height
|
||||||
duration: Theme.modalAnimationDuration
|
|
||||||
easing.bezierCurve: contentVisible ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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 {
|
ElevationShadow {
|
||||||
id: launcherShadowLayer
|
id: launcherShadowLayer
|
||||||
anchors.fill: parent
|
width: parent.width
|
||||||
level: Theme.elevationLevel3
|
height: parent.height
|
||||||
fallbackOffset: 6
|
opacity: contentWrapper.opacity
|
||||||
|
scale: contentWrapper.scale
|
||||||
|
x: contentWrapper.x
|
||||||
|
y: contentWrapper.y
|
||||||
|
level: root.shadowLevel
|
||||||
|
fallbackOffset: root.shadowFallbackOffset
|
||||||
targetColor: root.backgroundColor
|
targetColor: root.backgroundColor
|
||||||
borderColor: root.borderColor
|
borderColor: root.borderColor
|
||||||
borderWidth: root.borderWidth
|
borderWidth: root.borderWidth
|
||||||
@@ -403,6 +578,25 @@ Item {
|
|||||||
shadowEnabled: Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
|
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 {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
onPressed: mouse => mouse.accepted = true
|
onPressed: mouse => mouse.accepted = true
|
||||||
@@ -435,6 +629,9 @@ Item {
|
|||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} // contentWrapper
|
||||||
}
|
} // aligner
|
||||||
|
} // directionalClipMask
|
||||||
|
} // contentContainer
|
||||||
|
} // PanelWindow
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ FocusScope {
|
|||||||
|
|
||||||
Controller {
|
Controller {
|
||||||
id: controller
|
id: controller
|
||||||
active: root.parentModal?.spotlightOpen ?? true
|
active: root.parentModal ? (root.parentModal.spotlightOpen || root.parentModal.isClosing) : true
|
||||||
viewModeContext: root.viewModeContext
|
viewModeContext: root.viewModeContext
|
||||||
|
|
||||||
onItemExecuted: {
|
onItemExecuted: {
|
||||||
@@ -462,7 +462,7 @@ FocusScope {
|
|||||||
showClearButton: true
|
showClearButton: true
|
||||||
textColor: Theme.surfaceText
|
textColor: Theme.surfaceText
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
enabled: root.parentModal ? root.parentModal.spotlightOpen : true
|
enabled: root.parentModal ? (root.parentModal.spotlightOpen || root.parentModal.isClosing) : true
|
||||||
placeholderText: ""
|
placeholderText: ""
|
||||||
ignoreUpDownKeys: true
|
ignoreUpDownKeys: true
|
||||||
ignoreTabKeys: true
|
ignoreTabKeys: true
|
||||||
@@ -548,7 +548,6 @@ FocusScope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
@@ -697,7 +696,13 @@ FocusScope {
|
|||||||
Item {
|
Item {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: parent.height - searchField.height - categoryRow.height - fileFilterRow.height - actionPanel.height - Theme.spacingXS * ((categoryRow.visible ? 1 : 0) + (fileFilterRow.visible ? 1 : 0) + 2)
|
height: parent.height - searchField.height - categoryRow.height - fileFilterRow.height - actionPanel.height - Theme.spacingXS * ((categoryRow.visible ? 1 : 0) + (fileFilterRow.visible ? 1 : 0) + 2)
|
||||||
opacity: root.parentModal?.isClosing ? 0 : 1
|
opacity: {
|
||||||
|
if (!root.parentModal)
|
||||||
|
return 1;
|
||||||
|
if (Theme.isDirectionalEffect && root.parentModal.isClosing)
|
||||||
|
return 1;
|
||||||
|
return root.parentModal.isClosing ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
ResultsList {
|
ResultsList {
|
||||||
id: resultsList
|
id: resultsList
|
||||||
|
|||||||
@@ -468,7 +468,7 @@ Item {
|
|||||||
switch (mode) {
|
switch (mode) {
|
||||||
case "files":
|
case "files":
|
||||||
if (!DSearchService.dsearchAvailable)
|
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)
|
if (!hasQuery)
|
||||||
return I18n.tr("Type to search files");
|
return I18n.tr("Type to search files");
|
||||||
if (root.controller.searchQuery.length < 2)
|
if (root.controller.searchQuery.length < 2)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
@@ -12,45 +11,8 @@ FloatingWindow {
|
|||||||
property string passwordInput: ""
|
property string passwordInput: ""
|
||||||
property var currentFlow: PolkitService.agent?.flow
|
property var currentFlow: PolkitService.agent?.flow
|
||||||
property bool isLoading: false
|
property bool isLoading: false
|
||||||
property bool awaitingFprintForPassword: false
|
|
||||||
readonly property int inputFieldHeight: Theme.fontSizeMedium + Theme.spacingL * 2
|
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() {
|
function focusPasswordField() {
|
||||||
passwordField.forceActiveFocus();
|
passwordField.forceActiveFocus();
|
||||||
}
|
}
|
||||||
@@ -58,7 +20,6 @@ FloatingWindow {
|
|||||||
function show() {
|
function show() {
|
||||||
passwordInput = "";
|
passwordInput = "";
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
awaitingFprintForPassword = false;
|
|
||||||
visible = true;
|
visible = true;
|
||||||
Qt.callLater(focusPasswordField);
|
Qt.callLater(focusPasswordField);
|
||||||
}
|
}
|
||||||
@@ -67,27 +28,17 @@ FloatingWindow {
|
|||||||
visible = false;
|
visible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _commitSubmit() {
|
|
||||||
isLoading = true;
|
|
||||||
awaitingFprintForPassword = false;
|
|
||||||
currentFlow.submit(passwordInput);
|
|
||||||
passwordInput = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function submitAuth() {
|
function submitAuth() {
|
||||||
if (!currentFlow || isLoading)
|
if (!currentFlow || isLoading)
|
||||||
return;
|
return;
|
||||||
if (!currentFlow.isResponseRequired) {
|
isLoading = true;
|
||||||
awaitingFprintForPassword = true;
|
currentFlow.submit(passwordInput);
|
||||||
return;
|
passwordInput = "";
|
||||||
}
|
|
||||||
_commitSubmit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function cancelAuth() {
|
function cancelAuth() {
|
||||||
if (isLoading)
|
if (isLoading)
|
||||||
return;
|
return;
|
||||||
awaitingFprintForPassword = false;
|
|
||||||
if (currentFlow) {
|
if (currentFlow) {
|
||||||
currentFlow.cancelAuthenticationRequest();
|
currentFlow.cancelAuthenticationRequest();
|
||||||
return;
|
return;
|
||||||
@@ -109,7 +60,6 @@ FloatingWindow {
|
|||||||
}
|
}
|
||||||
passwordInput = "";
|
passwordInput = "";
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
awaitingFprintForPassword = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
@@ -133,11 +83,6 @@ FloatingWindow {
|
|||||||
function onIsResponseRequiredChanged() {
|
function onIsResponseRequiredChanged() {
|
||||||
if (!currentFlow.isResponseRequired)
|
if (!currentFlow.isResponseRequired)
|
||||||
return;
|
return;
|
||||||
if (awaitingFprintForPassword && passwordInput !== "") {
|
|
||||||
_commitSubmit();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
awaitingFprintForPassword = false;
|
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
passwordInput = "";
|
passwordInput = "";
|
||||||
passwordField.forceActiveFocus();
|
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 {
|
FocusScope {
|
||||||
id: contentFocusScope
|
id: contentFocusScope
|
||||||
|
|
||||||
@@ -295,31 +205,37 @@ FloatingWindow {
|
|||||||
visible: text !== ""
|
visible: text !== ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: inputFieldHeight
|
||||||
|
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
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
enabled: !isLoading
|
||||||
|
onClicked: passwordField.forceActiveFocus()
|
||||||
|
}
|
||||||
|
|
||||||
DankTextField {
|
DankTextField {
|
||||||
id: passwordField
|
id: passwordField
|
||||||
|
|
||||||
width: parent.width
|
anchors.fill: parent
|
||||||
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
|
|
||||||
opacity: isLoading ? 0.5 : 1
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
textColor: Theme.surfaceText
|
textColor: Theme.surfaceText
|
||||||
text: passwordInput
|
text: passwordInput
|
||||||
showPasswordToggle: !(currentFlow?.responseVisible ?? false)
|
showPasswordToggle: !(currentFlow?.responseVisible ?? false)
|
||||||
echoMode: (currentFlow?.responseVisible ?? false) || passwordVisible ? TextInput.Normal : TextInput.Password
|
echoMode: (currentFlow?.responseVisible ?? false) || passwordVisible ? TextInput.Normal : TextInput.Password
|
||||||
placeholderText: ""
|
placeholderText: ""
|
||||||
|
backgroundColor: "transparent"
|
||||||
enabled: !isLoading
|
enabled: !isLoading
|
||||||
onTextEdited: passwordInput = text
|
onTextEdited: passwordInput = text
|
||||||
onAccepted: submitAuth()
|
onAccepted: submitAuth()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: I18n.tr("Authentication failed, please try again")
|
text: I18n.tr("Authentication failed, please try again")
|
||||||
|
|||||||
@@ -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 {
|
Loader {
|
||||||
id: pluginsLoader
|
id: pluginsLoader
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|||||||
@@ -287,12 +287,6 @@ Rectangle {
|
|||||||
"icon": "lock",
|
"icon": "lock",
|
||||||
"tabIndex": 11
|
"tabIndex": 11
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "greeter",
|
|
||||||
"text": I18n.tr("Greeter"),
|
|
||||||
"icon": "login",
|
|
||||||
"tabIndex": 31
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "power_sleep",
|
"id": "power_sleep",
|
||||||
"text": I18n.tr("Power & Sleep"),
|
"text": I18n.tr("Power & Sleep"),
|
||||||
|
|||||||
@@ -8,9 +8,6 @@ DankPopout {
|
|||||||
|
|
||||||
layerNamespace: "dms:app-launcher"
|
layerNamespace: "dms:app-launcher"
|
||||||
|
|
||||||
readonly property real screenWidth: screen?.width ?? 1920
|
|
||||||
readonly property real screenHeight: screen?.height ?? 1080
|
|
||||||
|
|
||||||
property string _pendingMode: ""
|
property string _pendingMode: ""
|
||||||
property string _pendingQuery: ""
|
property string _pendingQuery: ""
|
||||||
|
|
||||||
@@ -44,35 +41,8 @@ DankPopout {
|
|||||||
openWithQuery(query);
|
openWithQuery(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property int _baseWidth: {
|
popupWidth: 560
|
||||||
switch (SettingsData.dankLauncherV2Size) {
|
popupHeight: 640
|
||||||
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)
|
|
||||||
|
|
||||||
triggerWidth: 40
|
triggerWidth: 40
|
||||||
positioning: ""
|
positioning: ""
|
||||||
contentHandlesKeys: contentLoader.item?.launcherContent?.editMode ?? false
|
contentHandlesKeys: contentLoader.item?.launcherContent?.editMode ?? false
|
||||||
@@ -136,7 +106,7 @@ DankPopout {
|
|||||||
QtObject {
|
QtObject {
|
||||||
id: modalAdapter
|
id: modalAdapter
|
||||||
property bool spotlightOpen: appDrawerPopout.shouldBeVisible
|
property bool spotlightOpen: appDrawerPopout.shouldBeVisible
|
||||||
property bool isClosing: false
|
property bool isClosing: appDrawerPopout.isClosing
|
||||||
|
|
||||||
function hide() {
|
function hide() {
|
||||||
appDrawerPopout.close();
|
appDrawerPopout.close();
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ DankPopout {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
layerNamespace: "dms:control-center"
|
layerNamespace: "dms:control-center"
|
||||||
fullHeightSurface: true
|
fullHeightSurface: false
|
||||||
|
|
||||||
property string expandedSection: ""
|
property string expandedSection: ""
|
||||||
property var triggerScreen: null
|
property var triggerScreen: null
|
||||||
@@ -126,9 +126,11 @@ DankPopout {
|
|||||||
z: 5000
|
z: 5000
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
|
enabled: !Theme.isDirectionalEffect
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: 200
|
duration: Theme.shortDuration
|
||||||
easing.type: Easing.OutCubic
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: root.shouldBeVisible ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1117,6 +1117,7 @@ Item {
|
|||||||
if (!notificationCenterLoader.item) {
|
if (!notificationCenterLoader.item) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
notificationCenterLoader.item.triggerScreen = barWindow.screen;
|
||||||
const effectiveBarConfig = topBarContent.barConfig;
|
const effectiveBarConfig = topBarContent.barConfig;
|
||||||
const barPosition = barWindow.axis?.edge === "left" ? 2 : (barWindow.axis?.edge === "right" ? 3 : (barWindow.axis?.edge === "top" ? 0 : 1));
|
const barPosition = barWindow.axis?.edge === "left" ? 2 : (barWindow.axis?.edge === "right" ? 3 : (barWindow.axis?.edge === "top" ? 0 : 1));
|
||||||
if (notificationCenterLoader.item.setBarContext) {
|
if (notificationCenterLoader.item.setBarContext) {
|
||||||
|
|||||||
@@ -390,11 +390,10 @@ BasePill {
|
|||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
}
|
}
|
||||||
|
|
||||||
NumericText {
|
StyledText {
|
||||||
id: audioPercentV
|
id: audioPercentV
|
||||||
visible: root.showAudioPercent
|
visible: root.showAudioPercent
|
||||||
text: Math.round((AudioService.sink?.audio?.volume ?? 0) * 100) + "%"
|
text: Math.round((AudioService.sink?.audio?.volume ?? 0) * 100) + "%"
|
||||||
reserveText: "100%"
|
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
color: Theme.widgetTextColor
|
color: Theme.widgetTextColor
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
@@ -417,11 +416,10 @@ BasePill {
|
|||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
}
|
}
|
||||||
|
|
||||||
NumericText {
|
StyledText {
|
||||||
id: micPercentV
|
id: micPercentV
|
||||||
visible: root.showMicPercent
|
visible: root.showMicPercent
|
||||||
text: Math.round((AudioService.source?.audio?.volume ?? 0) * 100) + "%"
|
text: Math.round((AudioService.source?.audio?.volume ?? 0) * 100) + "%"
|
||||||
reserveText: "100%"
|
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
color: Theme.widgetTextColor
|
color: Theme.widgetTextColor
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
@@ -444,11 +442,10 @@ BasePill {
|
|||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
}
|
}
|
||||||
|
|
||||||
NumericText {
|
StyledText {
|
||||||
id: brightnessPercentV
|
id: brightnessPercentV
|
||||||
visible: root.showBrightnessPercent
|
visible: root.showBrightnessPercent
|
||||||
text: Math.round(getBrightness() * 100) + "%"
|
text: Math.round(getBrightness() * 100) + "%"
|
||||||
reserveText: "100%"
|
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
color: Theme.widgetTextColor
|
color: Theme.widgetTextColor
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
@@ -539,8 +536,7 @@ BasePill {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: audioIcon.implicitWidth + (root.showAudioPercent ? audioPercent.reservedWidth : 0) + 4
|
width: audioIcon.implicitWidth + (root.showAudioPercent ? audioPercent.implicitWidth : 0) + 4
|
||||||
implicitWidth: width
|
|
||||||
height: root.widgetThickness - root.horizontalPadding * 2
|
height: root.widgetThickness - root.horizontalPadding * 2
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
@@ -556,23 +552,20 @@ BasePill {
|
|||||||
anchors.leftMargin: 2
|
anchors.leftMargin: 2
|
||||||
}
|
}
|
||||||
|
|
||||||
NumericText {
|
StyledText {
|
||||||
id: audioPercent
|
id: audioPercent
|
||||||
visible: root.showAudioPercent
|
visible: root.showAudioPercent
|
||||||
text: Math.round((AudioService.sink?.audio?.volume ?? 0) * 100) + "%"
|
text: Math.round((AudioService.sink?.audio?.volume ?? 0) * 100) + "%"
|
||||||
reserveText: "100%"
|
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
color: Theme.widgetTextColor
|
color: Theme.widgetTextColor
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.left: audioIcon.right
|
anchors.left: audioIcon.right
|
||||||
anchors.leftMargin: 2
|
anchors.leftMargin: 2
|
||||||
width: reservedWidth
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: micIcon.implicitWidth + (root.showMicPercent ? micPercent.reservedWidth : 0) + 4
|
width: micIcon.implicitWidth + (root.showMicPercent ? micPercent.implicitWidth : 0) + 4
|
||||||
implicitWidth: width
|
|
||||||
height: root.widgetThickness - root.horizontalPadding * 2
|
height: root.widgetThickness - root.horizontalPadding * 2
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
@@ -588,22 +581,20 @@ BasePill {
|
|||||||
anchors.leftMargin: 2
|
anchors.leftMargin: 2
|
||||||
}
|
}
|
||||||
|
|
||||||
NumericText {
|
StyledText {
|
||||||
id: micPercent
|
id: micPercent
|
||||||
visible: root.showMicPercent
|
visible: root.showMicPercent
|
||||||
text: Math.round((AudioService.source?.audio?.volume ?? 0) * 100) + "%"
|
text: Math.round((AudioService.source?.audio?.volume ?? 0) * 100) + "%"
|
||||||
reserveText: "100%"
|
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
color: Theme.widgetTextColor
|
color: Theme.widgetTextColor
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.left: micIcon.right
|
anchors.left: micIcon.right
|
||||||
anchors.leftMargin: 2
|
anchors.leftMargin: 2
|
||||||
width: reservedWidth
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
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
|
height: root.widgetThickness - root.horizontalPadding * 2
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
@@ -619,17 +610,15 @@ BasePill {
|
|||||||
anchors.leftMargin: 2
|
anchors.leftMargin: 2
|
||||||
}
|
}
|
||||||
|
|
||||||
NumericText {
|
StyledText {
|
||||||
id: brightnessPercent
|
id: brightnessPercent
|
||||||
visible: root.showBrightnessPercent
|
visible: root.showBrightnessPercent
|
||||||
text: Math.round(getBrightness() * 100) + "%"
|
text: Math.round(getBrightness() * 100) + "%"
|
||||||
reserveText: "100%"
|
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
color: Theme.widgetTextColor
|
color: Theme.widgetTextColor
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.left: brightnessIcon.right
|
anchors.left: brightnessIcon.right
|
||||||
anchors.leftMargin: 2
|
anchors.leftMargin: 2
|
||||||
width: reservedWidth
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -211,17 +211,16 @@ BasePill {
|
|||||||
text: {
|
text: {
|
||||||
const title = activeWindow && activeWindow.title ? activeWindow.title : "";
|
const title = activeWindow && activeWindow.title ? activeWindow.title : "";
|
||||||
const appName = appText.text;
|
const appName = appText.text;
|
||||||
|
|
||||||
if (compactMode && title === appName) {
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!title || !appName) {
|
if (!title || !appName) {
|
||||||
return title;
|
return title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (title.endsWith(" - " + appName)) {
|
||||||
|
return title.substring(0, title.length - (" - " + appName).length);
|
||||||
|
}
|
||||||
|
|
||||||
if (title.endsWith(appName)) {
|
if (title.endsWith(appName)) {
|
||||||
return title.substring(0, title.length - appName.length).replace(/ (-|—) $/, "");
|
return title.substring(0, title.length - appName.length).replace(/ - $/, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
return title;
|
return title;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
|
import QtQuick.Effects
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Hyprland
|
import Quickshell.Hyprland
|
||||||
import Quickshell.Services.SystemTray
|
import Quickshell.Services.SystemTray
|
||||||
@@ -161,23 +162,6 @@ BasePill {
|
|||||||
return 0;
|
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 bool menuOpen: false
|
||||||
property var currentTrayMenu: null
|
property var currentTrayMenu: null
|
||||||
|
|
||||||
@@ -956,6 +940,13 @@ BasePill {
|
|||||||
}
|
}
|
||||||
})(), overflowMenu.dpr)
|
})(), 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
|
opacity: root.menuOpen ? 1 : 0
|
||||||
scale: root.menuOpen ? 1 : 0.85
|
scale: root.menuOpen ? 1 : 0.85
|
||||||
|
|
||||||
@@ -976,14 +967,19 @@ BasePill {
|
|||||||
ElevationShadow {
|
ElevationShadow {
|
||||||
id: bgShadowLayer
|
id: bgShadowLayer
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
level: Theme.elevationLevel3
|
level: menuContainer.elev
|
||||||
direction: root.effectiveShadowDirection
|
fallbackOffset: 4
|
||||||
fallbackOffset: 6
|
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)
|
targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||||
targetRadius: Theme.cornerRadius
|
targetRadius: Theme.cornerRadius
|
||||||
sourceRect.antialiasing: true
|
sourceRect.antialiasing: true
|
||||||
sourceRect.smooth: true
|
sourceRect.smooth: true
|
||||||
shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled
|
shadowEnabled: Theme.elevationEnabled
|
||||||
layer.smooth: true
|
layer.smooth: true
|
||||||
layer.textureSize: Qt.size(Math.round(width * overflowMenu.dpr * 2), Math.round(height * overflowMenu.dpr * 2))
|
layer.textureSize: Qt.size(Math.round(width * overflowMenu.dpr * 2), Math.round(height * overflowMenu.dpr * 2))
|
||||||
layer.textureMirroring: ShaderEffectSource.MirrorVertically
|
layer.textureMirroring: ShaderEffectSource.MirrorVertically
|
||||||
@@ -1406,6 +1402,13 @@ BasePill {
|
|||||||
}
|
}
|
||||||
})(), menuWindow.dpr)
|
})(), 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
|
opacity: menuRoot.showMenu ? 1 : 0
|
||||||
scale: menuRoot.showMenu ? 1 : 0.85
|
scale: menuRoot.showMenu ? 1 : 0.85
|
||||||
|
|
||||||
@@ -1426,13 +1429,18 @@ BasePill {
|
|||||||
ElevationShadow {
|
ElevationShadow {
|
||||||
id: menuBgShadowLayer
|
id: menuBgShadowLayer
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
level: Theme.elevationLevel3
|
level: menuContainer.elev
|
||||||
direction: root.effectiveShadowDirection
|
fallbackOffset: 4
|
||||||
fallbackOffset: 6
|
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)
|
targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||||
targetRadius: Theme.cornerRadius
|
targetRadius: Theme.cornerRadius
|
||||||
sourceRect.antialiasing: true
|
sourceRect.antialiasing: true
|
||||||
shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled
|
shadowEnabled: Theme.elevationEnabled
|
||||||
layer.smooth: true
|
layer.smooth: true
|
||||||
layer.textureSize: Qt.size(Math.round(width * menuWindow.dpr), Math.round(height * menuWindow.dpr))
|
layer.textureSize: Qt.size(Math.round(width * menuWindow.dpr), Math.round(height * menuWindow.dpr))
|
||||||
layer.textureMirroring: ShaderEffectSource.MirrorVertically
|
layer.textureMirroring: ShaderEffectSource.MirrorVertically
|
||||||
|
|||||||
@@ -1470,10 +1470,6 @@ Item {
|
|||||||
delegate: Item {
|
delegate: Item {
|
||||||
width: root.appIconSize
|
width: root.appIconSize
|
||||||
height: 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 {
|
IconImage {
|
||||||
id: rowAppIcon
|
id: rowAppIcon
|
||||||
@@ -1489,14 +1485,14 @@ Item {
|
|||||||
color: Theme.surfaceContainer
|
color: Theme.surfaceContainer
|
||||||
radius: Theme.cornerRadius * (root.appIconSize / 40)
|
radius: Theme.cornerRadius * (root.appIconSize / 40)
|
||||||
border.width: 1
|
border.width: 1
|
||||||
border.color: appBorderColor
|
border.color: Theme.primarySelected
|
||||||
opacity: appOpacity
|
opacity: (modelData.active || isActive) ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: (modelData.fallbackText || "?").charAt(0).toUpperCase()
|
text: (modelData.fallbackText || "?").charAt(0).toUpperCase()
|
||||||
font.pixelSize: parent.width * 0.45
|
font.pixelSize: parent.width * 0.45
|
||||||
color: appGlyphColor
|
color: Theme.primary
|
||||||
font.weight: Font.Bold
|
font.weight: Font.Bold
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1507,14 +1503,14 @@ Item {
|
|||||||
color: Theme.surfaceContainer
|
color: Theme.surfaceContainer
|
||||||
radius: Theme.cornerRadius * (root.appIconSize / 40)
|
radius: Theme.cornerRadius * (root.appIconSize / 40)
|
||||||
border.width: 1
|
border.width: 1
|
||||||
border.color: appBorderColor
|
border.color: Theme.primarySelected
|
||||||
opacity: appOpacity
|
opacity: (modelData.active || isActive) ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
size: parent.width * 0.7
|
size: parent.width * 0.7
|
||||||
name: "sports_esports"
|
name: "sports_esports"
|
||||||
color: appGlyphColor
|
color: Theme.primary
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1527,12 +1523,11 @@ Item {
|
|||||||
layer.effect: MultiEffect {
|
layer.effect: MultiEffect {
|
||||||
saturation: 0
|
saturation: 0
|
||||||
colorization: 1
|
colorization: 1
|
||||||
colorizationColor: appHighlightActive ? focusedBorderColor : (isActive ? quickshellIconActiveColor : quickshellIconInactiveColor)
|
colorizationColor: isActive ? quickshellIconActiveColor : quickshellIconInactiveColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IconImage {
|
IconImage {
|
||||||
id: rowSteamIcon
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
source: modelData.icon
|
source: modelData.icon
|
||||||
opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
|
opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
|
||||||
@@ -1543,21 +1538,11 @@ Item {
|
|||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
size: root.appIconSize
|
size: root.appIconSize
|
||||||
name: "sports_esports"
|
name: "sports_esports"
|
||||||
color: appHighlightActive ? focusedBorderColor : Theme.widgetTextColor
|
color: Theme.widgetTextColor
|
||||||
opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
|
opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
|
||||||
visible: modelData.isSteamApp && !modelData.icon
|
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 {
|
MouseArea {
|
||||||
id: rowAppMouseArea
|
id: rowAppMouseArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
@@ -1639,10 +1624,6 @@ Item {
|
|||||||
delegate: Item {
|
delegate: Item {
|
||||||
width: root.appIconSize
|
width: root.appIconSize
|
||||||
height: 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 {
|
IconImage {
|
||||||
id: colAppIcon
|
id: colAppIcon
|
||||||
@@ -1658,14 +1639,14 @@ Item {
|
|||||||
color: Theme.surfaceContainer
|
color: Theme.surfaceContainer
|
||||||
radius: Theme.cornerRadius * (root.appIconSize / 40)
|
radius: Theme.cornerRadius * (root.appIconSize / 40)
|
||||||
border.width: 1
|
border.width: 1
|
||||||
border.color: appBorderColor
|
border.color: Theme.primarySelected
|
||||||
opacity: appOpacity
|
opacity: (modelData.active || isActive) ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: (modelData.fallbackText || "?").charAt(0).toUpperCase()
|
text: (modelData.fallbackText || "?").charAt(0).toUpperCase()
|
||||||
font.pixelSize: parent.width * 0.45
|
font.pixelSize: parent.width * 0.45
|
||||||
color: appGlyphColor
|
color: Theme.primary
|
||||||
font.weight: Font.Bold
|
font.weight: Font.Bold
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1676,14 +1657,14 @@ Item {
|
|||||||
color: Theme.surfaceContainer
|
color: Theme.surfaceContainer
|
||||||
radius: Theme.cornerRadius * (root.appIconSize / 40)
|
radius: Theme.cornerRadius * (root.appIconSize / 40)
|
||||||
border.width: 1
|
border.width: 1
|
||||||
border.color: appBorderColor
|
border.color: Theme.primarySelected
|
||||||
opacity: appOpacity
|
opacity: (modelData.active || isActive) ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
size: parent.width * 0.7
|
size: parent.width * 0.7
|
||||||
name: "sports_esports"
|
name: "sports_esports"
|
||||||
color: appGlyphColor
|
color: Theme.primary
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1696,12 +1677,11 @@ Item {
|
|||||||
layer.effect: MultiEffect {
|
layer.effect: MultiEffect {
|
||||||
saturation: 0
|
saturation: 0
|
||||||
colorization: 1
|
colorization: 1
|
||||||
colorizationColor: appHighlightActive ? focusedBorderColor : (isActive ? quickshellIconActiveColor : quickshellIconInactiveColor)
|
colorizationColor: isActive ? quickshellIconActiveColor : quickshellIconInactiveColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IconImage {
|
IconImage {
|
||||||
id: colSteamIcon
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
source: modelData.icon
|
source: modelData.icon
|
||||||
opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
|
opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
|
||||||
@@ -1712,21 +1692,11 @@ Item {
|
|||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
size: root.appIconSize
|
size: root.appIconSize
|
||||||
name: "sports_esports"
|
name: "sports_esports"
|
||||||
color: appHighlightActive ? focusedBorderColor : Theme.widgetTextColor
|
color: Theme.widgetTextColor
|
||||||
opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
|
opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
|
||||||
visible: modelData.isSteamApp && !modelData.icon
|
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 {
|
MouseArea {
|
||||||
id: colAppMouseArea
|
id: colAppMouseArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ DankPopout {
|
|||||||
popupHeight: contentLoader.item ? contentLoader.item.implicitHeight : 500
|
popupHeight: contentLoader.item ? contentLoader.item.implicitHeight : 500
|
||||||
triggerWidth: 80
|
triggerWidth: 80
|
||||||
screen: triggerScreen
|
screen: triggerScreen
|
||||||
shouldBeVisible: dashVisible
|
|
||||||
|
|
||||||
property bool __focusArmed: false
|
property bool __focusArmed: false
|
||||||
property bool __contentReady: false
|
property bool __contentReady: false
|
||||||
|
|||||||
@@ -44,6 +44,43 @@ Item {
|
|||||||
|
|
||||||
property int __volumeHoverCount: 0
|
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() {
|
function volumeAreaEntered() {
|
||||||
__volumeHoverCount++;
|
__volumeHoverCount++;
|
||||||
panelEntered();
|
panelEntered();
|
||||||
@@ -62,30 +99,47 @@ Item {
|
|||||||
visible: dropdownType === 1 && volumeAvailable
|
visible: dropdownType === 1 && volumeAvailable
|
||||||
width: 60
|
width: 60
|
||||||
height: 180
|
height: 180
|
||||||
x: isRightEdge ? anchorPos.x : anchorPos.x - width
|
x: (isRightEdge ? anchorPos.x : anchorPos.x - width) + panelMotionX(width, dropdownType === 1)
|
||||||
y: anchorPos.y - height / 2
|
y: anchorPos.y - height / 2 + panelMotionY(1, height, dropdownType === 1)
|
||||||
radius: Theme.cornerRadius * 2
|
radius: Theme.cornerRadius * 2
|
||||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95)
|
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.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
||||||
border.width: 1
|
border.width: 1
|
||||||
|
|
||||||
opacity: dropdownType === 1 ? 1 : 0
|
opacity: Theme.isDirectionalEffect ? 1 : (dropdownType === 1 ? 1 : 0)
|
||||||
scale: dropdownType === 1 ? 1 : 0.96
|
scale: Theme.isDirectionalEffect ? 1 : (dropdownType === 1 ? 1 : Theme.effectScaleCollapsed)
|
||||||
transformOrigin: isRightEdge ? Item.Left : Item.Right
|
transformOrigin: isRightEdge ? Item.Left : Item.Right
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
NumberAnimation {
|
enabled: !Theme.isDirectionalEffect
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
DankAnim {
|
||||||
easing.type: Easing.BezierSpline
|
duration: Math.round(Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, dropdownType === 1) * Theme.variantOpacityDurationScale)
|
||||||
easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
easing.bezierCurve: dropdownType === 1 ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on scale {
|
Behavior on scale {
|
||||||
|
enabled: !Theme.isDirectionalEffect
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, dropdownType === 1)
|
||||||
easing.type: Easing.BezierSpline
|
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 {
|
Rectangle {
|
||||||
id: audioDevicesPanel
|
id: audioDevicesPanel
|
||||||
visible: dropdownType === 2
|
visible: dropdownType === 2 && activePlayer !== null
|
||||||
width: 280
|
width: 280
|
||||||
height: Math.max(200, Math.min(280, availableDevices.length * 50 + 100))
|
height: Math.max(200, Math.min(280, availableDevices.length * 50 + 100))
|
||||||
x: isRightEdge ? anchorPos.x : anchorPos.x - width
|
x: (isRightEdge ? anchorPos.x : anchorPos.x - width) + panelMotionX(width, dropdownType === 2)
|
||||||
y: anchorPos.y - height / 2
|
y: anchorPos.y - height / 2 + panelMotionY(2, height, dropdownType === 2)
|
||||||
radius: Theme.cornerRadius * 2
|
radius: Theme.cornerRadius * 2
|
||||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.98)
|
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.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.6)
|
||||||
border.width: 2
|
border.width: 2
|
||||||
|
|
||||||
opacity: dropdownType === 2 ? 1 : 0
|
opacity: Theme.isDirectionalEffect ? 1 : (dropdownType === 2 ? 1 : 0)
|
||||||
scale: dropdownType === 2 ? 1 : 0.96
|
scale: Theme.isDirectionalEffect ? 1 : (dropdownType === 2 ? 1 : Theme.effectScaleCollapsed)
|
||||||
transformOrigin: isRightEdge ? Item.Left : Item.Right
|
transformOrigin: isRightEdge ? Item.Left : Item.Right
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
NumberAnimation {
|
enabled: !Theme.isDirectionalEffect
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
DankAnim {
|
||||||
easing.type: Easing.BezierSpline
|
duration: Math.round(Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, dropdownType === 2) * Theme.variantOpacityDurationScale)
|
||||||
easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
easing.bezierCurve: dropdownType === 2 ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on scale {
|
Behavior on scale {
|
||||||
|
enabled: !Theme.isDirectionalEffect
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, dropdownType === 2)
|
||||||
easing.type: Easing.BezierSpline
|
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
|
visible: dropdownType === 3
|
||||||
width: 240
|
width: 240
|
||||||
height: Math.max(180, Math.min(240, (allPlayers?.length || 0) * 50 + 80))
|
height: Math.max(180, Math.min(240, (allPlayers?.length || 0) * 50 + 80))
|
||||||
x: isRightEdge ? anchorPos.x : anchorPos.x - width
|
x: (isRightEdge ? anchorPos.x : anchorPos.x - width) + panelMotionX(width, dropdownType === 3)
|
||||||
y: anchorPos.y - height / 2
|
y: anchorPos.y - height / 2 + panelMotionY(3, height, dropdownType === 3)
|
||||||
radius: Theme.cornerRadius * 2
|
radius: Theme.cornerRadius * 2
|
||||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.98)
|
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.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.6)
|
||||||
border.width: 2
|
border.width: 2
|
||||||
|
|
||||||
opacity: dropdownType === 3 ? 1 : 0
|
opacity: Theme.isDirectionalEffect ? 1 : (dropdownType === 3 ? 1 : 0)
|
||||||
scale: dropdownType === 3 ? 1 : 0.96
|
scale: Theme.isDirectionalEffect ? 1 : (dropdownType === 3 ? 1 : Theme.effectScaleCollapsed)
|
||||||
transformOrigin: isRightEdge ? Item.Left : Item.Right
|
transformOrigin: isRightEdge ? Item.Left : Item.Right
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
NumberAnimation {
|
enabled: !Theme.isDirectionalEffect
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
DankAnim {
|
||||||
easing.type: Easing.BezierSpline
|
duration: Math.round(Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, dropdownType === 3) * Theme.variantOpacityDurationScale)
|
||||||
easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
easing.bezierCurve: dropdownType === 3 ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on scale {
|
Behavior on scale {
|
||||||
|
enabled: !Theme.isDirectionalEffect
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, dropdownType === 3)
|
||||||
easing.type: Easing.BezierSpline
|
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
|
||||||
import QtQuick.Effects
|
import QtQuick.Effects
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Hyprland
|
|
||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
import Quickshell.Widgets
|
import Quickshell.Widgets
|
||||||
import qs.Common
|
import qs.Common
|
||||||
@@ -134,40 +133,6 @@ Item {
|
|||||||
function getGroupedToplevels() {
|
function getGroupedToplevels() {
|
||||||
return appData?.allWindows?.map(w => w.toplevel).filter(t => t !== null) || [];
|
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: {
|
onIsHoveredChanged: {
|
||||||
if (mouseArea.pressed || dragging)
|
if (mouseArea.pressed || dragging)
|
||||||
return;
|
return;
|
||||||
@@ -311,11 +276,8 @@ Item {
|
|||||||
break;
|
break;
|
||||||
case "window":
|
case "window":
|
||||||
const windowToplevel = getToplevelObject();
|
const windowToplevel = getToplevelObject();
|
||||||
if (windowToplevel) {
|
if (windowToplevel)
|
||||||
if (restoreSpecialWorkspaceWindow(windowToplevel))
|
|
||||||
return;
|
|
||||||
windowToplevel.activate();
|
windowToplevel.activate();
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case "grouped":
|
case "grouped":
|
||||||
if (appData.windowCount === 0) {
|
if (appData.windowCount === 0) {
|
||||||
@@ -338,11 +300,8 @@ Item {
|
|||||||
SessionService.launchDesktopEntry(groupedEntry);
|
SessionService.launchDesktopEntry(groupedEntry);
|
||||||
} else if (appData.windowCount === 1) {
|
} else if (appData.windowCount === 1) {
|
||||||
const groupedToplevel = getToplevelObject();
|
const groupedToplevel = getToplevelObject();
|
||||||
if (groupedToplevel) {
|
if (groupedToplevel)
|
||||||
if (restoreSpecialWorkspaceWindow(groupedToplevel))
|
|
||||||
return;
|
|
||||||
groupedToplevel.activate();
|
groupedToplevel.activate();
|
||||||
}
|
|
||||||
} else if (contextMenu) {
|
} else if (contextMenu) {
|
||||||
const shouldHidePin = appData.appId === "org.quickshell";
|
const shouldHidePin = appData.appId === "org.quickshell";
|
||||||
contextMenu.showForButton(root, appData, root.height + 25, shouldHidePin, cachedDesktopEntry, parentDockScreen, dockApps);
|
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 QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import "GreetdEnv.js" as GreetdEnv
|
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
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 sessionConfigPath: greetCfgDir + "/session.json"
|
||||||
readonly property string memoryFile: greetCfgDir + "/.local/state/memory.json"
|
readonly property string memoryFile: greetCfgDir + "/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)
|
|
||||||
|
|
||||||
property string lastSessionId: ""
|
property string lastSessionId: ""
|
||||||
property string lastSuccessfulUser: ""
|
property string lastSuccessfulUser: ""
|
||||||
@@ -52,44 +49,26 @@ Singleton {
|
|||||||
if (!content || !content.trim())
|
if (!content || !content.trim())
|
||||||
return;
|
return;
|
||||||
const memory = JSON.parse(content);
|
const memory = JSON.parse(content);
|
||||||
lastSessionId = rememberLastSession ? (memory.lastSessionId || "") : "";
|
lastSessionId = memory.lastSessionId || "";
|
||||||
lastSuccessfulUser = rememberLastUser ? (memory.lastSuccessfulUser || "") : "";
|
lastSuccessfulUser = memory.lastSuccessfulUser || "";
|
||||||
if (!rememberLastSession || !rememberLastUser)
|
|
||||||
saveMemory();
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("Failed to parse greetd memory:", e);
|
console.warn("Failed to parse greetd memory:", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveMemory() {
|
function saveMemory() {
|
||||||
let memory = {};
|
memoryFileView.setText(JSON.stringify({
|
||||||
if (rememberLastSession && lastSessionId)
|
"lastSessionId": lastSessionId,
|
||||||
memory.lastSessionId = lastSessionId;
|
"lastSuccessfulUser": lastSuccessfulUser
|
||||||
if (rememberLastUser && lastSuccessfulUser)
|
}, null, 2));
|
||||||
memory.lastSuccessfulUser = lastSuccessfulUser;
|
|
||||||
memoryFileView.setText(JSON.stringify(memory, null, 2));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setLastSessionId(id) {
|
function setLastSessionId(id) {
|
||||||
if (!rememberLastSession) {
|
|
||||||
if (lastSessionId !== "") {
|
|
||||||
lastSessionId = "";
|
|
||||||
saveMemory();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
lastSessionId = id || "";
|
lastSessionId = id || "";
|
||||||
saveMemory();
|
saveMemory();
|
||||||
}
|
}
|
||||||
|
|
||||||
function setLastSuccessfulUser(username) {
|
function setLastSuccessfulUser(username) {
|
||||||
if (!rememberLastUser) {
|
|
||||||
if (lastSuccessfulUser !== "") {
|
|
||||||
lastSuccessfulUser = "";
|
|
||||||
saveMemory();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
lastSuccessfulUser = username || "";
|
lastSuccessfulUser = username || "";
|
||||||
saveMemory();
|
saveMemory();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,22 +5,15 @@ import QtQuick
|
|||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import "GreetdEnv.js" as GreetdEnv
|
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property string configPath: {
|
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";
|
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 string currentThemeName: "purple"
|
||||||
property bool settingsLoaded: false
|
property bool settingsLoaded: false
|
||||||
property string customThemeFile: ""
|
property string customThemeFile: ""
|
||||||
@@ -28,12 +21,6 @@ Singleton {
|
|||||||
property bool use24HourClock: true
|
property bool use24HourClock: true
|
||||||
property bool showSeconds: false
|
property bool showSeconds: false
|
||||||
property bool padHours12Hour: 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 useFahrenheit: false
|
||||||
property bool nightModeEnabled: false
|
property bool nightModeEnabled: false
|
||||||
property string weatherLocation: "New York, NY"
|
property string weatherLocation: "New York, NY"
|
||||||
@@ -54,11 +41,6 @@ Singleton {
|
|||||||
property string lockDateFormat: ""
|
property string lockDateFormat: ""
|
||||||
property bool lockScreenShowPowerActions: true
|
property bool lockScreenShowPowerActions: true
|
||||||
property bool lockScreenShowProfileImage: 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 bool powerActionConfirm: true
|
||||||
property real powerActionHoldDuration: 0.5
|
property real powerActionHoldDuration: 0.5
|
||||||
property var powerMenuActions: ["reboot", "logout", "poweroff", "lock", "suspend", "restart"]
|
property var powerMenuActions: ["reboot", "logout", "poweroff", "lock", "suspend", "restart"]
|
||||||
@@ -70,26 +52,14 @@ Singleton {
|
|||||||
|
|
||||||
function parseSettings(content) {
|
function parseSettings(content) {
|
||||||
try {
|
try {
|
||||||
let settings = {};
|
|
||||||
if (content && content.trim()) {
|
if (content && content.trim()) {
|
||||||
settings = JSON.parse(content);
|
const settings = JSON.parse(content);
|
||||||
}
|
|
||||||
|
|
||||||
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";
|
currentThemeName = settings.currentThemeName !== undefined ? settings.currentThemeName : "purple";
|
||||||
customThemeFile = settings.customThemeFile !== undefined ? settings.customThemeFile : "";
|
customThemeFile = settings.customThemeFile !== undefined ? settings.customThemeFile : "";
|
||||||
matugenScheme = settings.matugenScheme !== undefined ? settings.matugenScheme : "scheme-tonal-spot";
|
matugenScheme = settings.matugenScheme !== undefined ? settings.matugenScheme : "scheme-tonal-spot";
|
||||||
use24HourClock = settings.use24HourClock !== undefined ? settings.use24HourClock : true;
|
use24HourClock = settings.use24HourClock !== undefined ? settings.use24HourClock : true;
|
||||||
showSeconds = settings.showSeconds !== undefined ? settings.showSeconds : false;
|
showSeconds = settings.showSeconds !== undefined ? settings.showSeconds : false;
|
||||||
padHours12Hour = settings.padHours12Hour !== undefined ? settings.padHours12Hour : 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;
|
useFahrenheit = settings.useFahrenheit !== undefined ? settings.useFahrenheit : false;
|
||||||
nightModeEnabled = settings.nightModeEnabled !== undefined ? settings.nightModeEnabled : false;
|
nightModeEnabled = settings.nightModeEnabled !== undefined ? settings.nightModeEnabled : false;
|
||||||
weatherLocation = settings.weatherLocation !== undefined ? settings.weatherLocation : "New York, NY";
|
weatherLocation = settings.weatherLocation !== undefined ? settings.weatherLocation : "New York, NY";
|
||||||
@@ -110,19 +80,6 @@ Singleton {
|
|||||||
lockDateFormat = settings.lockDateFormat !== undefined ? settings.lockDateFormat : "";
|
lockDateFormat = settings.lockDateFormat !== undefined ? settings.lockDateFormat : "";
|
||||||
lockScreenShowPowerActions = settings.lockScreenShowPowerActions !== undefined ? settings.lockScreenShowPowerActions : true;
|
lockScreenShowPowerActions = settings.lockScreenShowPowerActions !== undefined ? settings.lockScreenShowPowerActions : true;
|
||||||
lockScreenShowProfileImage = settings.lockScreenShowProfileImage !== undefined ? settings.lockScreenShowProfileImage : 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;
|
powerActionConfirm = settings.powerActionConfirm !== undefined ? settings.powerActionConfirm : true;
|
||||||
powerActionHoldDuration = settings.powerActionHoldDuration !== undefined ? settings.powerActionHoldDuration : 0.5;
|
powerActionHoldDuration = settings.powerActionHoldDuration !== undefined ? settings.powerActionHoldDuration : 0.5;
|
||||||
powerMenuActions = settings.powerMenuActions !== undefined ? settings.powerMenuActions : ["reboot", "logout", "poweroff", "lock", "suspend", "restart"];
|
powerMenuActions = settings.powerMenuActions !== undefined ? settings.powerMenuActions : ["reboot", "logout", "poweroff", "lock", "suspend", "restart"];
|
||||||
@@ -131,6 +88,7 @@ Singleton {
|
|||||||
screenPreferences = settings.screenPreferences !== undefined ? settings.screenPreferences : ({});
|
screenPreferences = settings.screenPreferences !== undefined ? settings.screenPreferences : ({});
|
||||||
animationSpeed = settings.animationSpeed !== undefined ? settings.animationSpeed : 2;
|
animationSpeed = settings.animationSpeed !== undefined ? settings.animationSpeed : 2;
|
||||||
wallpaperFillMode = settings.wallpaperFillMode !== undefined ? settings.wallpaperFillMode : "Fill";
|
wallpaperFillMode = settings.wallpaperFillMode !== undefined ? settings.wallpaperFillMode : "Fill";
|
||||||
|
settingsLoaded = true;
|
||||||
|
|
||||||
if (typeof Theme !== "undefined") {
|
if (typeof Theme !== "undefined") {
|
||||||
if (currentThemeName === "custom" && customThemeFile) {
|
if (currentThemeName === "custom" && customThemeFile) {
|
||||||
@@ -138,35 +96,22 @@ Singleton {
|
|||||||
}
|
}
|
||||||
Theme.applyGreeterTheme(currentThemeName);
|
Theme.applyGreeterTheme(currentThemeName);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("Failed to parse greetd settings:", e);
|
console.warn("Failed to parse greetd settings:", e);
|
||||||
} finally {
|
|
||||||
settingsLoaded = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEffectiveTimeFormat() {
|
function getEffectiveTimeFormat() {
|
||||||
const use24 = greeterUse24HourClock;
|
if (use24HourClock)
|
||||||
const secs = greeterShowSeconds;
|
return showSeconds ? "hh:mm:ss" : "hh:mm";
|
||||||
const pad = greeterPadHours12Hour;
|
if (padHours12Hour)
|
||||||
if (use24)
|
return showSeconds ? "hh:mm:ss AP" : "hh:mm AP";
|
||||||
return secs ? "hh:mm:ss" : "hh:mm";
|
return showSeconds ? "h:mm:ss AP" : "h:mm AP";
|
||||||
if (pad)
|
|
||||||
return secs ? "hh:mm:ss AP" : "hh:mm AP";
|
|
||||||
return secs ? "h:mm:ss AP" : "h:mm AP";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEffectiveLockDateFormat() {
|
function getEffectiveLockDateFormat() {
|
||||||
const fmt = (greeterLockDateFormat !== undefined && greeterLockDateFormat !== "") ? greeterLockDateFormat : lockDateFormat;
|
return lockDateFormat && lockDateFormat.length > 0 ? lockDateFormat : Locale.LongFormat;
|
||||||
return fmt && fmt.length > 0 ? fmt : Locale.LongFormat;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getEffectiveWallpaperFillMode() {
|
|
||||||
return (greeterWallpaperFillMode && greeterWallpaperFillMode !== "") ? greeterWallpaperFillMode : wallpaperFillMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getEffectiveFontFamily() {
|
|
||||||
return (greeterFontFamily && greeterFontFamily !== "") ? greeterFontFamily : fontFamily;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFilteredScreens(componentId) {
|
function getFilteredScreens(componentId) {
|
||||||
@@ -188,9 +133,5 @@ Singleton {
|
|||||||
onLoaded: {
|
onLoaded: {
|
||||||
parseSettings(settingsFile.text());
|
parseSettings(settingsFile.text());
|
||||||
}
|
}
|
||||||
onLoadFailed: error => {
|
|
||||||
console.warn("Failed to load greetd settings:", error);
|
|
||||||
root.parseSettings("");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,35 +31,6 @@ Item {
|
|||||||
signal launchRequested
|
signal launchRequested
|
||||||
|
|
||||||
property bool weatherInitialized: false
|
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: 30000
|
|
||||||
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 systemLoginPamText: ""
|
|
||||||
property string systemLocalLoginPamText: ""
|
|
||||||
property string commonAuthPcPamText: ""
|
|
||||||
property string loginPamText: ""
|
|
||||||
property string faillockConfigText: ""
|
|
||||||
property bool greeterWallpaperOverrideExists: false
|
|
||||||
property string externalAuthAutoStartedForUser: ""
|
|
||||||
property int passwordSessionTransitionRetryCount: 0
|
|
||||||
property int maxPasswordSessionTransitionRetries: 2
|
|
||||||
readonly property bool greeterPamHasFprint: greeterPamStackHasModule("pam_fprintd")
|
|
||||||
readonly property bool greeterPamHasU2f: greeterPamStackHasModule("pam_u2f")
|
|
||||||
readonly property bool greeterExternalAuthAvailable: (greeterPamHasFprint && GreetdSettings.greeterEnableFprint) || (greeterPamHasU2f && GreetdSettings.greeterEnableU2f)
|
|
||||||
readonly property bool greeterPamHasExternalAuth: greeterPamHasFprint || greeterPamHasU2f
|
|
||||||
|
|
||||||
function initWeatherService() {
|
function initWeatherService() {
|
||||||
if (weatherInitialized)
|
if (weatherInitialized)
|
||||||
@@ -73,357 +44,16 @@ Item {
|
|||||||
WeatherService.forceRefresh();
|
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 pamTextIncludesFile(pamText, filename) {
|
|
||||||
if (!pamText || !filename)
|
|
||||||
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(filename) && (line.includes("include") || line.includes("substack") || line.startsWith("@include")))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function greeterPamStackHasModule(moduleName) {
|
|
||||||
if (pamModuleEnabled(greetdPamText, moduleName))
|
|
||||||
return true;
|
|
||||||
const includedPamStacks = [
|
|
||||||
["system-auth", systemAuthPamText],
|
|
||||||
["common-auth", commonAuthPamText],
|
|
||||||
["password-auth", passwordAuthPamText],
|
|
||||||
["system-login", systemLoginPamText],
|
|
||||||
["system-local-login", systemLocalLoginPamText],
|
|
||||||
["common-auth-pc", commonAuthPcPamText],
|
|
||||||
["login", loginPamText]
|
|
||||||
];
|
|
||||||
for (let i = 0; i < includedPamStacks.length; i++) {
|
|
||||||
const stack = includedPamStacks[i];
|
|
||||||
if (pamTextIncludesFile(greetdPamText, stack[0]) && pamModuleEnabled(stack[1], 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, systemLoginPamText, systemLocalLoginPamText, commonAuthPcPamText, loginPamText];
|
|
||||||
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 {
|
Connections {
|
||||||
target: GreetdSettings
|
target: GreetdSettings
|
||||||
function onSettingsLoadedChanged() {
|
function onSettingsLoadedChanged() {
|
||||||
if (GreetdSettings.settingsLoaded) {
|
if (GreetdSettings.settingsLoaded)
|
||||||
initWeatherService();
|
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();
|
|
||||||
root.maybeAutoStartExternalAuth();
|
|
||||||
}
|
|
||||||
onLoadFailed: {
|
|
||||||
root.systemAuthPamText = "";
|
|
||||||
root.refreshPasswordAttemptPolicyHint();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileView {
|
|
||||||
id: commonAuthPamWatcher
|
|
||||||
path: "/etc/pam.d/common-auth"
|
|
||||||
printErrors: false
|
|
||||||
onLoaded: {
|
|
||||||
root.commonAuthPamText = text();
|
|
||||||
root.refreshPasswordAttemptPolicyHint();
|
|
||||||
root.maybeAutoStartExternalAuth();
|
|
||||||
}
|
|
||||||
onLoadFailed: {
|
|
||||||
root.commonAuthPamText = "";
|
|
||||||
root.refreshPasswordAttemptPolicyHint();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileView {
|
|
||||||
id: passwordAuthPamWatcher
|
|
||||||
path: "/etc/pam.d/password-auth"
|
|
||||||
printErrors: false
|
|
||||||
onLoaded: {
|
|
||||||
root.passwordAuthPamText = text();
|
|
||||||
root.refreshPasswordAttemptPolicyHint();
|
|
||||||
root.maybeAutoStartExternalAuth();
|
|
||||||
}
|
|
||||||
onLoadFailed: {
|
|
||||||
root.passwordAuthPamText = "";
|
|
||||||
root.refreshPasswordAttemptPolicyHint();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileView {
|
|
||||||
id: systemLoginPamWatcher
|
|
||||||
path: "/etc/pam.d/system-login"
|
|
||||||
printErrors: false
|
|
||||||
onLoaded: {
|
|
||||||
root.systemLoginPamText = text();
|
|
||||||
root.refreshPasswordAttemptPolicyHint();
|
|
||||||
root.maybeAutoStartExternalAuth();
|
|
||||||
}
|
|
||||||
onLoadFailed: {
|
|
||||||
root.systemLoginPamText = "";
|
|
||||||
root.refreshPasswordAttemptPolicyHint();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileView {
|
|
||||||
id: systemLocalLoginPamWatcher
|
|
||||||
path: "/etc/pam.d/system-local-login"
|
|
||||||
printErrors: false
|
|
||||||
onLoaded: {
|
|
||||||
root.systemLocalLoginPamText = text();
|
|
||||||
root.refreshPasswordAttemptPolicyHint();
|
|
||||||
root.maybeAutoStartExternalAuth();
|
|
||||||
}
|
|
||||||
onLoadFailed: {
|
|
||||||
root.systemLocalLoginPamText = "";
|
|
||||||
root.refreshPasswordAttemptPolicyHint();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileView {
|
|
||||||
id: commonAuthPcPamWatcher
|
|
||||||
path: "/etc/pam.d/common-auth-pc"
|
|
||||||
printErrors: false
|
|
||||||
onLoaded: {
|
|
||||||
root.commonAuthPcPamText = text();
|
|
||||||
root.refreshPasswordAttemptPolicyHint();
|
|
||||||
root.maybeAutoStartExternalAuth();
|
|
||||||
}
|
|
||||||
onLoadFailed: {
|
|
||||||
root.commonAuthPcPamText = "";
|
|
||||||
root.refreshPasswordAttemptPolicyHint();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileView {
|
|
||||||
id: loginPamWatcher
|
|
||||||
path: "/etc/pam.d/login"
|
|
||||||
printErrors: false
|
|
||||||
onLoaded: {
|
|
||||||
root.loginPamText = text();
|
|
||||||
root.refreshPasswordAttemptPolicyHint();
|
|
||||||
root.maybeAutoStartExternalAuth();
|
|
||||||
}
|
|
||||||
onLoadFailed: {
|
|
||||||
root.loginPamText = "";
|
|
||||||
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: {
|
Component.onCompleted: {
|
||||||
initWeatherService();
|
initWeatherService();
|
||||||
refreshPasswordAttemptPolicyHint();
|
|
||||||
|
|
||||||
if (isPrimaryScreen)
|
if (isPrimaryScreen)
|
||||||
applyLastSuccessfulUser();
|
applyLastSuccessfulUser();
|
||||||
@@ -433,131 +63,15 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function applyLastSuccessfulUser() {
|
function applyLastSuccessfulUser() {
|
||||||
if (!GreetdSettings.settingsLoaded || !GreetdSettings.rememberLastUser)
|
|
||||||
return;
|
|
||||||
const lastUser = GreetdMemory.lastSuccessfulUser;
|
const lastUser = GreetdMemory.lastSuccessfulUser;
|
||||||
if (lastUser && !GreeterState.showPasswordInput && !GreeterState.username) {
|
if (lastUser && !GreeterState.showPasswordInput && !GreeterState.username) {
|
||||||
GreeterState.username = lastUser;
|
GreeterState.username = lastUser;
|
||||||
GreeterState.usernameInput = lastUser;
|
GreeterState.usernameInput = lastUser;
|
||||||
GreeterState.showPasswordInput = true;
|
GreeterState.showPasswordInput = true;
|
||||||
PortalService.getGreeterUserProfileImage(lastUser);
|
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: {
|
Component.onDestruction: {
|
||||||
if (weatherInitialized)
|
if (weatherInitialized)
|
||||||
WeatherService.removeRef();
|
WeatherService.removeRef();
|
||||||
@@ -629,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 {
|
DankBackdrop {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
screenName: root.screenName
|
screenName: root.screenName
|
||||||
visible: {
|
visible: {
|
||||||
if (GreetdSettings.greeterWallpaperPath !== "" && root.greeterWallpaperOverrideExists)
|
|
||||||
return false;
|
|
||||||
var _ = SessionData.perMonitorWallpaper;
|
var _ = SessionData.perMonitorWallpaper;
|
||||||
var __ = SessionData.monitorWallpapers;
|
var __ = SessionData.monitorWallpapers;
|
||||||
var currentWallpaper = SessionData.getMonitorWallpaper(screenName);
|
var currentWallpaper = SessionData.getMonitorWallpaper(screenName);
|
||||||
@@ -674,14 +159,12 @@ Item {
|
|||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
source: {
|
source: {
|
||||||
if (GreetdSettings.greeterWallpaperPath !== "" && root.greeterWallpaperOverrideExists)
|
|
||||||
return encodeFileUrl(GreetdSettings.greeterWallpaperOverridePath);
|
|
||||||
var _ = SessionData.perMonitorWallpaper;
|
var _ = SessionData.perMonitorWallpaper;
|
||||||
var __ = SessionData.monitorWallpapers;
|
var __ = SessionData.monitorWallpapers;
|
||||||
var currentWallpaper = SessionData.getMonitorWallpaper(screenName);
|
var currentWallpaper = SessionData.getMonitorWallpaper(screenName);
|
||||||
return (currentWallpaper && !currentWallpaper.startsWith("#")) ? encodeFileUrl(currentWallpaper) : "";
|
return (currentWallpaper && !currentWallpaper.startsWith("#")) ? encodeFileUrl(currentWallpaper) : "";
|
||||||
}
|
}
|
||||||
fillMode: Theme.getFillMode(GreetdSettings.getEffectiveWallpaperFillMode())
|
fillMode: Theme.getFillMode(GreetdSettings.wallpaperFillMode)
|
||||||
smooth: true
|
smooth: true
|
||||||
asynchronous: false
|
asynchronous: false
|
||||||
cache: true
|
cache: true
|
||||||
@@ -844,7 +327,10 @@ Item {
|
|||||||
anchors.top: clockContainer.bottom
|
anchors.top: clockContainer.bottom
|
||||||
anchors.topMargin: 4
|
anchors.topMargin: 4
|
||||||
text: {
|
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
|
font.pixelSize: Theme.fontSizeXLarge
|
||||||
color: "white"
|
color: "white"
|
||||||
@@ -913,9 +399,6 @@ Item {
|
|||||||
if (GreeterState.showPasswordInput && revealButton.visible) {
|
if (GreeterState.showPasswordInput && revealButton.visible) {
|
||||||
margin += revealButton.width;
|
margin += revealButton.width;
|
||||||
}
|
}
|
||||||
if (externalAuthButton.visible) {
|
|
||||||
margin += externalAuthButton.width;
|
|
||||||
}
|
|
||||||
if (virtualKeyboardButton.visible) {
|
if (virtualKeyboardButton.visible) {
|
||||||
margin += virtualKeyboardButton.width;
|
margin += virtualKeyboardButton.width;
|
||||||
}
|
}
|
||||||
@@ -932,18 +415,21 @@ Item {
|
|||||||
return;
|
return;
|
||||||
if (GreeterState.showPasswordInput) {
|
if (GreeterState.showPasswordInput) {
|
||||||
GreeterState.passwordBuffer = text;
|
GreeterState.passwordBuffer = text;
|
||||||
if (!text || text.length === 0)
|
|
||||||
root.passwordSubmitRequested = false;
|
|
||||||
} else {
|
} else {
|
||||||
GreeterState.usernameInput = text;
|
GreeterState.usernameInput = text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onAccepted: {
|
onAccepted: {
|
||||||
if (GreeterState.showPasswordInput) {
|
if (GreeterState.showPasswordInput) {
|
||||||
root.startAuthSession(true);
|
if (Greetd.state === GreetdState.Inactive && GreeterState.username) {
|
||||||
|
Greetd.createSession(GreeterState.username);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (text.trim()) {
|
if (text.trim()) {
|
||||||
root.submitUsername(text);
|
GreeterState.username = text.trim();
|
||||||
|
GreeterState.showPasswordInput = true;
|
||||||
|
PortalService.getGreeterUserProfileImage(GreeterState.username);
|
||||||
|
GreeterState.passwordBuffer = "";
|
||||||
syncingFromState = true;
|
syncingFromState = true;
|
||||||
text = "";
|
text = "";
|
||||||
syncingFromState = false;
|
syncingFromState = false;
|
||||||
@@ -975,14 +461,14 @@ Item {
|
|||||||
|
|
||||||
anchors.left: lockIcon.right
|
anchors.left: lockIcon.right
|
||||||
anchors.leftMargin: Theme.spacingM
|
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.rightMargin: 2
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
text: {
|
text: {
|
||||||
if (GreeterState.unlocking) {
|
if (GreeterState.unlocking) {
|
||||||
return "Logging in...";
|
return "Logging in...";
|
||||||
}
|
}
|
||||||
if (Greetd.state !== GreetdState.Inactive && !awaitingExternalAuth && !pendingPasswordResponse) {
|
if (Greetd.state !== GreetdState.Inactive) {
|
||||||
return "Authenticating...";
|
return "Authenticating...";
|
||||||
}
|
}
|
||||||
if (GreeterState.showPasswordInput) {
|
if (GreeterState.showPasswordInput) {
|
||||||
@@ -990,7 +476,7 @@ Item {
|
|||||||
}
|
}
|
||||||
return "Username...";
|
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
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
opacity: (GreeterState.showPasswordInput ? GreeterState.passwordBuffer.length === 0 : GreeterState.usernameInput.length === 0) ? 1 : 0
|
opacity: (GreeterState.showPasswordInput ? GreeterState.passwordBuffer.length === 0 : GreeterState.usernameInput.length === 0) ? 1 : 0
|
||||||
|
|
||||||
@@ -1012,7 +498,7 @@ Item {
|
|||||||
StyledText {
|
StyledText {
|
||||||
anchors.left: lockIcon.right
|
anchors.left: lockIcon.right
|
||||||
anchors.leftMargin: Theme.spacingM
|
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.rightMargin: 2
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
text: {
|
text: {
|
||||||
@@ -1042,27 +528,15 @@ Item {
|
|||||||
DankActionButton {
|
DankActionButton {
|
||||||
id: revealButton
|
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.rightMargin: 0
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
iconName: parent.showPassword ? "visibility_off" : "visibility"
|
iconName: parent.showPassword ? "visibility_off" : "visibility"
|
||||||
buttonSize: 32
|
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
|
enabled: visible
|
||||||
onClicked: parent.showPassword = !parent.showPassword
|
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 {
|
DankActionButton {
|
||||||
id: virtualKeyboardButton
|
id: virtualKeyboardButton
|
||||||
|
|
||||||
@@ -1071,7 +545,7 @@ Item {
|
|||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
iconName: "keyboard"
|
iconName: "keyboard"
|
||||||
buttonSize: 32
|
buttonSize: 32
|
||||||
visible: (Greetd.state === GreetdState.Inactive || awaitingExternalAuth || pendingPasswordResponse) && !GreeterState.unlocking
|
visible: Greetd.state === GreetdState.Inactive && !GreeterState.unlocking
|
||||||
enabled: visible
|
enabled: visible
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (keyboard_controller.isKeyboardActive) {
|
if (keyboard_controller.isKeyboardActive) {
|
||||||
@@ -1090,14 +564,19 @@ Item {
|
|||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
iconName: "keyboard_return"
|
iconName: "keyboard_return"
|
||||||
buttonSize: 36
|
buttonSize: 36
|
||||||
visible: (Greetd.state === GreetdState.Inactive || awaitingExternalAuth || pendingPasswordResponse) && !GreeterState.unlocking
|
visible: Greetd.state === GreetdState.Inactive && !GreeterState.unlocking
|
||||||
enabled: true
|
enabled: true
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (GreeterState.showPasswordInput) {
|
if (GreeterState.showPasswordInput) {
|
||||||
root.startAuthSession(true);
|
if (GreeterState.username) {
|
||||||
|
Greetd.createSession(GreeterState.username);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (inputField.text.trim()) {
|
if (inputField.text.trim()) {
|
||||||
root.submitUsername(inputField.text);
|
GreeterState.username = inputField.text.trim();
|
||||||
|
GreeterState.showPasswordInput = true;
|
||||||
|
PortalService.getGreeterUserProfileImage(GreeterState.username);
|
||||||
|
GreeterState.passwordBuffer = "";
|
||||||
inputField.text = "";
|
inputField.text = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1122,16 +601,20 @@ Item {
|
|||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: 38
|
Layout.preferredHeight: 20
|
||||||
Layout.topMargin: -Theme.spacingS
|
Layout.topMargin: -Theme.spacingS
|
||||||
Layout.bottomMargin: -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
|
color: Theme.error
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
wrapMode: Text.WordWrap
|
opacity: GreeterState.pamState !== "" ? 1 : 0
|
||||||
maximumLineCount: 2
|
|
||||||
opacity: root.authFeedbackMessage !== "" ? 1 : 0
|
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
@@ -1184,7 +667,6 @@ Item {
|
|||||||
enabled: !GreeterState.unlocking && Greetd.state === GreetdState.Inactive && GreeterState.showPasswordInput
|
enabled: !GreeterState.unlocking && Greetd.state === GreetdState.Inactive && GreeterState.showPasswordInput
|
||||||
onClicked: {
|
onClicked: {
|
||||||
GreeterState.reset();
|
GreeterState.reset();
|
||||||
root.externalAuthAutoStartedForUser = "";
|
|
||||||
inputField.text = "";
|
inputField.text = "";
|
||||||
PortalService.profileImage = "";
|
PortalService.profileImage = "";
|
||||||
}
|
}
|
||||||
@@ -1547,11 +1029,9 @@ Item {
|
|||||||
return;
|
return;
|
||||||
if (!GreetdMemory.memoryReady)
|
if (!GreetdMemory.memoryReady)
|
||||||
return;
|
return;
|
||||||
if (!GreetdSettings.settingsLoaded)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const savedSession = GreetdSettings.rememberLastSession ? GreetdMemory.lastSessionId : "";
|
const savedSession = GreetdMemory.lastSessionId;
|
||||||
if (savedSession && GreetdSettings.rememberLastSession) {
|
if (savedSession) {
|
||||||
for (var i = 0; i < GreeterState.sessionPaths.length; i++) {
|
for (var i = 0; i < GreeterState.sessionPaths.length; i++) {
|
||||||
if (GreeterState.sessionPaths[i] === savedSession) {
|
if (GreeterState.sessionPaths[i] === savedSession) {
|
||||||
GreeterState.currentSessionIndex = i;
|
GreeterState.currentSessionIndex = i;
|
||||||
@@ -1684,151 +1164,44 @@ Item {
|
|||||||
|
|
||||||
function onAuthMessage(message, error, responseRequired, echoResponse) {
|
function onAuthMessage(message, error, responseRequired, echoResponse) {
|
||||||
if (responseRequired) {
|
if (responseRequired) {
|
||||||
cancelingExternalAuthForPassword = false;
|
Greetd.respond(GreeterState.passwordBuffer);
|
||||||
passwordSessionTransitionRetryCount = 0;
|
GreeterState.passwordBuffer = "";
|
||||||
awaitingExternalAuth = false;
|
inputField.text = "";
|
||||||
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();
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
pendingPasswordResponse = false;
|
if (!error)
|
||||||
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("");
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onReadyToLaunch() {
|
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 sessionCmd = GreeterState.selectedSession || GreeterState.sessionExecs[GreeterState.currentSessionIndex];
|
||||||
const sessionPath = GreeterState.selectedSessionPath || GreeterState.sessionPaths[GreeterState.currentSessionIndex];
|
const sessionPath = GreeterState.selectedSessionPath || GreeterState.sessionPaths[GreeterState.currentSessionIndex];
|
||||||
if (!sessionCmd) {
|
if (!sessionCmd) {
|
||||||
GreeterState.pamState = "error";
|
GreeterState.pamState = "error";
|
||||||
authFeedbackMessage = currentAuthMessage();
|
|
||||||
placeholderDelay.restart();
|
placeholderDelay.restart();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
GreeterState.unlocking = true;
|
GreeterState.unlocking = true;
|
||||||
launchTimeout.restart();
|
launchTimeout.restart();
|
||||||
if (GreetdSettings.rememberLastSession) {
|
|
||||||
GreetdMemory.setLastSessionId(sessionPath);
|
GreetdMemory.setLastSessionId(sessionPath);
|
||||||
} else if (GreetdMemory.lastSessionId) {
|
|
||||||
GreetdMemory.setLastSessionId("");
|
|
||||||
}
|
|
||||||
if (GreetdSettings.rememberLastUser) {
|
|
||||||
GreetdMemory.setLastSuccessfulUser(GreeterState.username);
|
GreetdMemory.setLastSuccessfulUser(GreeterState.username);
|
||||||
} else if (GreetdMemory.lastSuccessfulUser) {
|
Greetd.launch(sessionCmd.split(" "), ["XDG_SESSION_TYPE=wayland"]);
|
||||||
GreetdMemory.setLastSuccessfulUser("");
|
|
||||||
}
|
|
||||||
pendingLaunchCommand = sessionCmd;
|
|
||||||
pendingLaunchEnv = ["XDG_SESSION_TYPE=wayland"];
|
|
||||||
memoryFlushTimer.restart();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onAuthFailure(message) {
|
function onAuthFailure(message) {
|
||||||
awaitingExternalAuth = false;
|
|
||||||
pendingPasswordResponse = false;
|
|
||||||
resetPasswordSessionTransition(true);
|
|
||||||
authTimeout.interval = defaultAuthTimeoutMs;
|
|
||||||
authTimeout.stop();
|
|
||||||
launchTimeout.stop();
|
launchTimeout.stop();
|
||||||
GreeterState.unlocking = false;
|
GreeterState.unlocking = false;
|
||||||
if (isLikelyLockoutMessage(message)) {
|
|
||||||
GreeterState.pamState = "max";
|
|
||||||
} else {
|
|
||||||
GreeterState.pamState = "fail";
|
GreeterState.pamState = "fail";
|
||||||
passwordFailureCount = passwordFailureCount + 1;
|
|
||||||
}
|
|
||||||
authFeedbackMessage = currentAuthMessage();
|
|
||||||
GreeterState.passwordBuffer = "";
|
GreeterState.passwordBuffer = "";
|
||||||
inputField.text = "";
|
inputField.text = "";
|
||||||
placeholderDelay.restart();
|
placeholderDelay.restart();
|
||||||
Greetd.cancelSession();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onError(error) {
|
function onError(error) {
|
||||||
awaitingExternalAuth = false;
|
|
||||||
pendingPasswordResponse = false;
|
|
||||||
resetPasswordSessionTransition(true);
|
|
||||||
authTimeout.interval = defaultAuthTimeoutMs;
|
|
||||||
authTimeout.stop();
|
|
||||||
launchTimeout.stop();
|
launchTimeout.stop();
|
||||||
GreeterState.unlocking = false;
|
GreeterState.unlocking = false;
|
||||||
GreeterState.pamState = "error";
|
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 = "";
|
GreeterState.passwordBuffer = "";
|
||||||
inputField.text = "";
|
inputField.text = "";
|
||||||
placeholderDelay.restart();
|
placeholderDelay.restart();
|
||||||
@@ -1842,11 +1215,8 @@ Item {
|
|||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (!GreeterState.unlocking)
|
if (!GreeterState.unlocking)
|
||||||
return;
|
return;
|
||||||
pendingPasswordResponse = false;
|
|
||||||
resetPasswordSessionTransition(true);
|
|
||||||
GreeterState.unlocking = false;
|
GreeterState.unlocking = false;
|
||||||
GreeterState.pamState = "error";
|
GreeterState.pamState = "error";
|
||||||
authFeedbackMessage = currentAuthMessage();
|
|
||||||
placeholderDelay.restart();
|
placeholderDelay.restart();
|
||||||
Greetd.cancelSession();
|
Greetd.cancelSession();
|
||||||
}
|
}
|
||||||
@@ -1855,7 +1225,7 @@ Item {
|
|||||||
Timer {
|
Timer {
|
||||||
id: placeholderDelay
|
id: placeholderDelay
|
||||||
interval: 4000
|
interval: 4000
|
||||||
onTriggered: clearAuthFeedback()
|
onTriggered: GreeterState.pamState = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
LockPowerMenu {
|
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.
|
- **Multiple compositors**: Supports niri, Hyprland, Sway, or mangowc.
|
||||||
- **Custom PAM**: Supports custom PAM configuration in `/etc/pam.d/greetd`
|
- **Custom PAM**: Supports custom PAM configuration in `/etc/pam.d/greetd`
|
||||||
- **Session Memory**: Remembers last selected session and user
|
- **Session Memory**: Remembers last selected session and user
|
||||||
- Can be disabled via `settings.json` keys: `greeterRememberLastSession` and `greeterRememberLastUser`
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -213,7 +212,6 @@ dms-greeter --command hyprland
|
|||||||
dms-greeter --command sway
|
dms-greeter --command sway
|
||||||
dms-greeter --command mangowc
|
dms-greeter --command mangowc
|
||||||
dms-greeter --command niri -C /path/to/custom-niri.kdl
|
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`:
|
Configure greetd to use it in `/etc/greetd/config.toml`:
|
||||||
|
|||||||
@@ -6,9 +6,6 @@ COMPOSITOR=""
|
|||||||
COMPOSITOR_CONFIG=""
|
COMPOSITOR_CONFIG=""
|
||||||
DMS_PATH="dms-greeter"
|
DMS_PATH="dms-greeter"
|
||||||
CACHE_DIR="/var/cache/dms-greeter"
|
CACHE_DIR="/var/cache/dms-greeter"
|
||||||
REMEMBER_LAST_SESSION=""
|
|
||||||
REMEMBER_LAST_USER=""
|
|
||||||
DEBUG_MODE=0
|
|
||||||
|
|
||||||
show_help() {
|
show_help() {
|
||||||
cat << EOF
|
cat << EOF
|
||||||
@@ -25,15 +22,6 @@ Options:
|
|||||||
(default: dms-greeter)
|
(default: dms-greeter)
|
||||||
--cache-dir PATH Cache directory for greeter data
|
--cache-dir PATH Cache directory for greeter data
|
||||||
(default: /var/cache/dms-greeter)
|
(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
|
-h, --help Show this help message
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
@@ -42,7 +30,6 @@ Examples:
|
|||||||
dms-greeter --command sway -p /home/user/.config/quickshell/custom-dms
|
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 scroll -p /home/user/.config/quickshell/custom-dms
|
||||||
dms-greeter --command niri --cache-dir /tmp/dmsgreeter
|
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 mango
|
||||||
dms-greeter --command labwc
|
dms-greeter --command labwc
|
||||||
EOF
|
EOF
|
||||||
@@ -56,41 +43,6 @@ require_command() {
|
|||||||
fi
|
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
|
while [[ $# -gt 0 ]]; do
|
||||||
case $1 in
|
case $1 in
|
||||||
--command)
|
--command)
|
||||||
@@ -109,26 +61,6 @@ while [[ $# -gt 0 ]]; do
|
|||||||
CACHE_DIR="$2"
|
CACHE_DIR="$2"
|
||||||
shift 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)
|
-h|--help)
|
||||||
show_help
|
show_help
|
||||||
exit 0
|
exit 0
|
||||||
@@ -179,58 +111,9 @@ export QT_QPA_PLATFORM=wayland
|
|||||||
export QT_WAYLAND_DISABLE_WINDOWDECORATION=1
|
export QT_WAYLAND_DISABLE_WINDOWDECORATION=1
|
||||||
export EGL_PLATFORM=gbm
|
export EGL_PLATFORM=gbm
|
||||||
export DMS_RUN_GREETER=1
|
export DMS_RUN_GREETER=1
|
||||||
|
|
||||||
ensure_cache_tree() {
|
|
||||||
local base="$1"
|
|
||||||
mkdir -p "$base/.local/state" "$base/.local/state/wireplumber" "$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"
|
export DMS_GREET_CFG_DIR="$CACHE_DIR"
|
||||||
|
|
||||||
if [[ -n "$REMEMBER_LAST_SESSION" ]]; then
|
mkdir -p "$CACHE_DIR"
|
||||||
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
|
|
||||||
|
|
||||||
if command -v qs >/dev/null 2>&1; then
|
if command -v qs >/dev/null 2>&1; then
|
||||||
QS_BIN="qs"
|
QS_BIN="qs"
|
||||||
@@ -247,9 +130,7 @@ if [[ "$DMS_PATH" == /* ]]; then
|
|||||||
else
|
else
|
||||||
RESOLVED_PATH=$(locate_dms_config "$DMS_PATH")
|
RESOLVED_PATH=$(locate_dms_config "$DMS_PATH")
|
||||||
if [[ $? -eq 0 && -n "$RESOLVED_PATH" ]]; then
|
if [[ $? -eq 0 && -n "$RESOLVED_PATH" ]]; then
|
||||||
if [[ "$DEBUG_MODE" == "1" ]]; then
|
|
||||||
echo "Located DMS config at: $RESOLVED_PATH" >&2
|
echo "Located DMS config at: $RESOLVED_PATH" >&2
|
||||||
fi
|
|
||||||
QS_CMD="$QS_BIN -p $RESOLVED_PATH"
|
QS_CMD="$QS_BIN -p $RESOLVED_PATH"
|
||||||
else
|
else
|
||||||
echo "Error: Could not find DMS config '$DMS_PATH' (shell.qml) in any valid config path" >&2
|
echo "Error: Could not find DMS config '$DMS_PATH' (shell.qml) in any valid config path" >&2
|
||||||
@@ -270,10 +151,6 @@ hotkey-overlay {
|
|||||||
|
|
||||||
environment {
|
environment {
|
||||||
DMS_RUN_GREETER "1"
|
DMS_RUN_GREETER "1"
|
||||||
HOME "$CACHE_DIR"
|
|
||||||
XDG_STATE_HOME "$CACHE_DIR/.local/state"
|
|
||||||
XDG_DATA_HOME "$CACHE_DIR/.local/share"
|
|
||||||
XDG_CACHE_HOME "$CACHE_DIR/.cache"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
debug {
|
debug {
|
||||||
@@ -315,7 +192,7 @@ NIRI_EOF
|
|||||||
spawn-at-startup "sh" "-c" "$QS_CMD; niri msg action quit --skip-confirmation"
|
spawn-at-startup "sh" "-c" "$QS_CMD; niri msg action quit --skip-confirmation"
|
||||||
NIRI_EOF
|
NIRI_EOF
|
||||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||||
exec_compositor "niri" niri -c "$COMPOSITOR_CONFIG"
|
exec niri -c "$COMPOSITOR_CONFIG"
|
||||||
;;
|
;;
|
||||||
|
|
||||||
hyprland)
|
hyprland)
|
||||||
@@ -327,10 +204,6 @@ NIRI_EOF
|
|||||||
TEMP_CONFIG=$(mktemp)
|
TEMP_CONFIG=$(mktemp)
|
||||||
cat > "$TEMP_CONFIG" << HYPRLAND_EOF
|
cat > "$TEMP_CONFIG" << HYPRLAND_EOF
|
||||||
env = DMS_RUN_GREETER,1
|
env = DMS_RUN_GREETER,1
|
||||||
env = HOME,$CACHE_DIR
|
|
||||||
env = XDG_STATE_HOME,$CACHE_DIR/.local/state
|
|
||||||
env = XDG_DATA_HOME,$CACHE_DIR/.local/share
|
|
||||||
env = XDG_CACHE_HOME,$CACHE_DIR/.cache
|
|
||||||
|
|
||||||
misc {
|
misc {
|
||||||
disable_hyprland_logo = true
|
disable_hyprland_logo = true
|
||||||
@@ -344,18 +217,14 @@ HYPRLAND_EOF
|
|||||||
cat "$COMPOSITOR_CONFIG" > "$TEMP_CONFIG"
|
cat "$COMPOSITOR_CONFIG" > "$TEMP_CONFIG"
|
||||||
cat >> "$TEMP_CONFIG" << HYPRLAND_EOF
|
cat >> "$TEMP_CONFIG" << HYPRLAND_EOF
|
||||||
|
|
||||||
env = HOME,$CACHE_DIR
|
|
||||||
env = XDG_STATE_HOME,$CACHE_DIR/.local/state
|
|
||||||
env = XDG_DATA_HOME,$CACHE_DIR/.local/share
|
|
||||||
env = XDG_CACHE_HOME,$CACHE_DIR/.cache
|
|
||||||
exec-once = sh -c "$QS_CMD; hyprctl dispatch exit"
|
exec-once = sh -c "$QS_CMD; hyprctl dispatch exit"
|
||||||
HYPRLAND_EOF
|
HYPRLAND_EOF
|
||||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||||
fi
|
fi
|
||||||
if command -v start-hyprland >/dev/null 2>&1; then
|
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
|
else
|
||||||
exec_compositor "hyprland" Hyprland -c "$COMPOSITOR_CONFIG"
|
exec Hyprland -c "$COMPOSITOR_CONFIG"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
|
|
||||||
@@ -364,7 +233,6 @@ HYPRLAND_EOF
|
|||||||
if [[ -z "$COMPOSITOR_CONFIG" ]]; then
|
if [[ -z "$COMPOSITOR_CONFIG" ]]; then
|
||||||
TEMP_CONFIG=$(mktemp)
|
TEMP_CONFIG=$(mktemp)
|
||||||
cat > "$TEMP_CONFIG" << SWAY_EOF
|
cat > "$TEMP_CONFIG" << SWAY_EOF
|
||||||
exec --no-startup-id dbus-update-activation-environment --systemd HOME XDG_STATE_HOME XDG_DATA_HOME XDG_CACHE_HOME
|
|
||||||
exec "$QS_CMD; swaymsg exit"
|
exec "$QS_CMD; swaymsg exit"
|
||||||
SWAY_EOF
|
SWAY_EOF
|
||||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||||
@@ -373,12 +241,11 @@ SWAY_EOF
|
|||||||
cat "$COMPOSITOR_CONFIG" > "$TEMP_CONFIG"
|
cat "$COMPOSITOR_CONFIG" > "$TEMP_CONFIG"
|
||||||
cat >> "$TEMP_CONFIG" << SWAY_EOF
|
cat >> "$TEMP_CONFIG" << SWAY_EOF
|
||||||
|
|
||||||
exec --no-startup-id dbus-update-activation-environment --systemd HOME XDG_STATE_HOME XDG_DATA_HOME XDG_CACHE_HOME
|
|
||||||
exec "$QS_CMD; swaymsg exit"
|
exec "$QS_CMD; swaymsg exit"
|
||||||
SWAY_EOF
|
SWAY_EOF
|
||||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||||
fi
|
fi
|
||||||
exec_compositor "sway" sway --unsupported-gpu -c "$COMPOSITOR_CONFIG"
|
exec sway --unsupported-gpu -c "$COMPOSITOR_CONFIG"
|
||||||
;;
|
;;
|
||||||
|
|
||||||
scroll)
|
scroll)
|
||||||
@@ -386,7 +253,6 @@ SWAY_EOF
|
|||||||
if [[ -z "$COMPOSITOR_CONFIG" ]]; then
|
if [[ -z "$COMPOSITOR_CONFIG" ]]; then
|
||||||
TEMP_CONFIG=$(mktemp)
|
TEMP_CONFIG=$(mktemp)
|
||||||
cat > "$TEMP_CONFIG" << SCROLL_EOF
|
cat > "$TEMP_CONFIG" << SCROLL_EOF
|
||||||
exec --no-startup-id dbus-update-activation-environment --systemd HOME XDG_STATE_HOME XDG_DATA_HOME XDG_CACHE_HOME
|
|
||||||
exec "$QS_CMD; scrollmsg exit"
|
exec "$QS_CMD; scrollmsg exit"
|
||||||
SCROLL_EOF
|
SCROLL_EOF
|
||||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||||
@@ -395,12 +261,11 @@ SCROLL_EOF
|
|||||||
cat "$COMPOSITOR_CONFIG" > "$TEMP_CONFIG"
|
cat "$COMPOSITOR_CONFIG" > "$TEMP_CONFIG"
|
||||||
cat >> "$TEMP_CONFIG" << SCROLL_EOF
|
cat >> "$TEMP_CONFIG" << SCROLL_EOF
|
||||||
|
|
||||||
exec --no-startup-id dbus-update-activation-environment --systemd HOME XDG_STATE_HOME XDG_DATA_HOME XDG_CACHE_HOME
|
|
||||||
exec "$QS_CMD; scrollmsg exit"
|
exec "$QS_CMD; scrollmsg exit"
|
||||||
SCROLL_EOF
|
SCROLL_EOF
|
||||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||||
fi
|
fi
|
||||||
exec_compositor "scroll" scroll -c "$COMPOSITOR_CONFIG"
|
exec scroll -c "$COMPOSITOR_CONFIG"
|
||||||
;;
|
;;
|
||||||
|
|
||||||
miracle|miracle-wm)
|
miracle|miracle-wm)
|
||||||
@@ -420,24 +285,24 @@ exec "$QS_CMD; miraclemsg exit"
|
|||||||
MIRACLE_EOF
|
MIRACLE_EOF
|
||||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||||
fi
|
fi
|
||||||
exec_compositor "miracle" miracle-wm -c "$COMPOSITOR_CONFIG"
|
exec miracle-wm -c "$COMPOSITOR_CONFIG"
|
||||||
;;
|
;;
|
||||||
|
|
||||||
labwc)
|
labwc)
|
||||||
require_command "labwc"
|
require_command "labwc"
|
||||||
if [[ -n "$COMPOSITOR_CONFIG" ]]; then
|
if [[ -n "$COMPOSITOR_CONFIG" ]]; then
|
||||||
exec_compositor "labwc" labwc --config "$COMPOSITOR_CONFIG" --session "$QS_CMD"
|
exec labwc --config "$COMPOSITOR_CONFIG" --session "$QS_CMD"
|
||||||
else
|
else
|
||||||
exec_compositor "labwc" labwc --session "$QS_CMD"
|
exec labwc --session "$QS_CMD"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
|
|
||||||
mango|mangowc)
|
mango|mangowc)
|
||||||
require_command "mango"
|
require_command "mango"
|
||||||
if [[ -n "$COMPOSITOR_CONFIG" ]]; then
|
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
|
else
|
||||||
exec_compositor "mango" mango -s "$QS_CMD && mmsg -d quit"
|
exec mango -s "$QS_CMD && mmsg -d quit"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
|
|
||||||
|
|||||||
@@ -755,7 +755,7 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
onAccepted: {
|
onAccepted: {
|
||||||
if (!demoMode && !root.unlocking && !pam.passwd.active && !pam.u2fPending) {
|
if (!demoMode && !pam.passwd.active && !pam.u2fPending) {
|
||||||
pam.passwd.start();
|
pam.passwd.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -764,11 +764,6 @@ Item {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root.unlocking) {
|
|
||||||
event.accepted = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.key === Qt.Key_Escape) {
|
if (event.key === Qt.Key_Escape) {
|
||||||
if (pam.u2fPending) {
|
if (pam.u2fPending) {
|
||||||
pam.cancelU2fPending();
|
pam.cancelU2fPending();
|
||||||
@@ -1022,7 +1017,7 @@ Item {
|
|||||||
visible: (demoMode || (!pam.passwd.active && !root.unlocking && !pam.u2fPending))
|
visible: (demoMode || (!pam.passwd.active && !root.unlocking && !pam.u2fPending))
|
||||||
enabled: !demoMode
|
enabled: !demoMode
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (!demoMode && !root.unlocking && !pam.u2fPending) {
|
if (!demoMode && !pam.u2fPending) {
|
||||||
pam.passwd.start();
|
pam.passwd.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1631,7 +1626,6 @@ Item {
|
|||||||
onStateChanged: {
|
onStateChanged: {
|
||||||
root.pamState = state;
|
root.pamState = state;
|
||||||
if (state !== "") {
|
if (state !== "") {
|
||||||
root.unlocking = false;
|
|
||||||
placeholderDelay.restart();
|
placeholderDelay.restart();
|
||||||
passwordField.text = "";
|
passwordField.text = "";
|
||||||
root.passwordBuffer = "";
|
root.passwordBuffer = "";
|
||||||
@@ -1647,15 +1641,6 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: pam
|
|
||||||
|
|
||||||
function onUnlockInProgressChanged() {
|
|
||||||
if (!pam.unlockInProgress && root.unlocking)
|
|
||||||
root.unlocking = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Binding {
|
Binding {
|
||||||
target: pam
|
target: pam
|
||||||
property: "buffer"
|
property: "buffer"
|
||||||
|
|||||||
@@ -25,29 +25,6 @@ Scope {
|
|||||||
signal flashMsg
|
signal flashMsg
|
||||||
signal unlockRequested
|
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 {
|
function completeUnlock(): void {
|
||||||
if (!unlockInProgress) {
|
if (!unlockInProgress) {
|
||||||
unlockInProgress = true;
|
unlockInProgress = true;
|
||||||
@@ -59,7 +36,6 @@ Scope {
|
|||||||
u2fPendingTimeout.running = false;
|
u2fPendingTimeout.running = false;
|
||||||
u2fPending = false;
|
u2fPending = false;
|
||||||
u2fState = "";
|
u2fState = "";
|
||||||
unlockRequestTimeout.restart();
|
|
||||||
unlockRequested();
|
unlockRequested();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,13 +66,6 @@ Scope {
|
|||||||
printErrors: false
|
printErrors: false
|
||||||
}
|
}
|
||||||
|
|
||||||
FileView {
|
|
||||||
id: loginConfigWatcher
|
|
||||||
|
|
||||||
path: "/etc/pam.d/login"
|
|
||||||
printErrors: false
|
|
||||||
}
|
|
||||||
|
|
||||||
FileView {
|
FileView {
|
||||||
id: u2fConfigWatcher
|
id: u2fConfigWatcher
|
||||||
|
|
||||||
@@ -108,7 +77,7 @@ Scope {
|
|||||||
id: passwd
|
id: passwd
|
||||||
|
|
||||||
config: dankshellConfigWatcher.loaded ? "dankshell" : "login"
|
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: {
|
onMessageChanged: {
|
||||||
if (message.startsWith("The account is locked"))
|
if (message.startsWith("The account is locked"))
|
||||||
@@ -133,13 +102,6 @@ Scope {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
unlockRequestTimeout.running = false;
|
|
||||||
root.unlockInProgress = false;
|
|
||||||
root.u2fPending = false;
|
|
||||||
root.u2fState = "";
|
|
||||||
u2fPendingTimeout.running = false;
|
|
||||||
u2f.abort();
|
|
||||||
|
|
||||||
if (res === PamResult.Error)
|
if (res === PamResult.Error)
|
||||||
root.state = "error";
|
root.state = "error";
|
||||||
else if (res === PamResult.MaxTries)
|
else if (res === PamResult.MaxTries)
|
||||||
@@ -152,22 +114,10 @@ Scope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: passwd
|
|
||||||
|
|
||||||
function onActiveChanged() {
|
|
||||||
if (passwd.active) {
|
|
||||||
passwdActiveTimeout.restart();
|
|
||||||
} else {
|
|
||||||
passwdActiveTimeout.running = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PamContext {
|
PamContext {
|
||||||
id: fprint
|
id: fprint
|
||||||
|
|
||||||
property bool available: SettingsData.lockFingerprintReady
|
property bool available
|
||||||
property int tries
|
property int tries
|
||||||
property int errorTries
|
property int errorTries
|
||||||
|
|
||||||
@@ -223,7 +173,7 @@ Scope {
|
|||||||
PamContext {
|
PamContext {
|
||||||
id: u2f
|
id: u2f
|
||||||
|
|
||||||
property bool available: SettingsData.lockU2fReady
|
property bool available
|
||||||
|
|
||||||
function checkAvail(): void {
|
function checkAvail(): void {
|
||||||
if (!available || !SettingsData.enableU2f || !root.lockSecured) {
|
if (!available || !SettingsData.enableU2f || !root.lockSecured) {
|
||||||
@@ -252,7 +202,7 @@ Scope {
|
|||||||
configDirectory: u2fConfigWatcher.loaded ? "/etc/pam.d" : Quickshell.shellDir + "/assets/pam"
|
configDirectory: u2fConfigWatcher.loaded ? "/etc/pam.d" : Quickshell.shellDir + "/assets/pam"
|
||||||
|
|
||||||
onMessageChanged: {
|
onMessageChanged: {
|
||||||
if (message !== "")
|
if (message.toLowerCase().includes("touch"))
|
||||||
root.u2fState = "waiting";
|
root.u2fState = "waiting";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,6 +238,26 @@ Scope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: availProc
|
||||||
|
|
||||||
|
command: ["sh", "-c", "fprintd-list $USER"]
|
||||||
|
onExited: code => {
|
||||||
|
fprint.available = code === 0;
|
||||||
|
fprint.checkAvail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: u2fAvailProc
|
||||||
|
|
||||||
|
command: ["sh", "-c", "(test -f /usr/lib/security/pam_u2f.so || test -f /usr/lib64/security/pam_u2f.so) && (test -f /etc/pam.d/dankshell-u2f || test -f \"$HOME/.config/Yubico/u2f_keys\")"]
|
||||||
|
onExited: code => {
|
||||||
|
u2f.available = code === 0;
|
||||||
|
u2f.checkAvail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: errorRetry
|
id: errorRetry
|
||||||
|
|
||||||
@@ -309,26 +279,6 @@ Scope {
|
|||||||
onTriggered: root.cancelU2fPending()
|
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 {
|
Timer {
|
||||||
id: stateReset
|
id: stateReset
|
||||||
|
|
||||||
@@ -351,17 +301,24 @@ Scope {
|
|||||||
|
|
||||||
onLockSecuredChanged: {
|
onLockSecuredChanged: {
|
||||||
if (lockSecured) {
|
if (lockSecured) {
|
||||||
SettingsData.refreshAuthAvailability();
|
availProc.running = true;
|
||||||
|
u2fAvailProc.running = true;
|
||||||
root.state = "";
|
root.state = "";
|
||||||
root.fprintState = "";
|
root.fprintState = "";
|
||||||
root.u2fState = "";
|
root.u2fState = "";
|
||||||
root.u2fPending = false;
|
root.u2fPending = false;
|
||||||
root.lockMessage = "";
|
root.lockMessage = "";
|
||||||
root.resetAuthFlows();
|
root.unlockInProgress = false;
|
||||||
fprint.checkAvail();
|
|
||||||
u2f.checkAvail();
|
|
||||||
} else {
|
} 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,24 +329,15 @@ Scope {
|
|||||||
fprint.checkAvail();
|
fprint.checkAvail();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onLockFingerprintReadyChanged(): void {
|
|
||||||
fprint.checkAvail();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onEnableU2fChanged(): void {
|
function onEnableU2fChanged(): void {
|
||||||
u2f.checkAvail();
|
u2f.checkAvail();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onLockU2fReadyChanged(): void {
|
|
||||||
u2f.checkAvail();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onU2fModeChanged(): void {
|
function onU2fModeChanged(): void {
|
||||||
if (root.lockSecured) {
|
if (root.lockSecured) {
|
||||||
u2f.abort();
|
u2f.abort();
|
||||||
u2fErrorRetry.running = false;
|
u2fErrorRetry.running = false;
|
||||||
u2fPendingTimeout.running = false;
|
u2fPendingTimeout.running = false;
|
||||||
unlockRequestTimeout.running = false;
|
|
||||||
root.u2fPending = false;
|
root.u2fPending = false;
|
||||||
root.u2fState = "";
|
root.u2fState = "";
|
||||||
u2f.checkAvail();
|
u2f.checkAvail();
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ DankPopout {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
layerNamespace: "dms:notification-center-popout"
|
layerNamespace: "dms:notification-center-popout"
|
||||||
fullHeightSurface: true
|
fullHeightSurface: false
|
||||||
|
|
||||||
property bool notificationHistoryVisible: false
|
property bool notificationHistoryVisible: false
|
||||||
property var triggerScreen: null
|
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
|
popupHeight: stablePopupHeight
|
||||||
positioning: ""
|
positioning: ""
|
||||||
animationScaleCollapsed: 0.94
|
|
||||||
animationOffset: 0
|
|
||||||
suspendShadowWhileResizing: false
|
suspendShadowWhileResizing: false
|
||||||
|
|
||||||
screen: triggerScreen
|
screen: triggerScreen
|
||||||
|
|||||||
@@ -24,6 +24,29 @@ PanelWindow {
|
|||||||
property real _lastReportedAlignedHeight: -1
|
property real _lastReportedAlignedHeight: -1
|
||||||
property real _storedTopMargin: 0
|
property real _storedTopMargin: 0
|
||||||
property real _storedBottomMargin: 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")
|
readonly property string clearText: I18n.tr("Dismiss")
|
||||||
property bool descriptionExpanded: false
|
property bool descriptionExpanded: false
|
||||||
readonly property bool hasExpandableBody: (notificationData?.htmlBody || "").replace(/<[^>]*>/g, "").trim().length > 0
|
readonly property bool hasExpandableBody: (notificationData?.htmlBody || "").replace(/<[^>]*>/g, "").trim().length > 0
|
||||||
@@ -137,9 +160,9 @@ PanelWindow {
|
|||||||
enabled: !exiting && !_isDestroying
|
enabled: !exiting && !_isDestroying
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
id: implicitHeightAnim
|
id: implicitHeightAnim
|
||||||
duration: descriptionExpanded ? Theme.notificationExpandDuration : Theme.notificationCollapseDuration
|
duration: Theme.variantDuration(descriptionExpanded ? Theme.notificationExpandDuration : Theme.notificationCollapseDuration, descriptionExpanded)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: Theme.expressiveCurves.emphasized
|
easing.bezierCurve: descriptionExpanded ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -911,9 +934,9 @@ PanelWindow {
|
|||||||
if (isCenterPosition)
|
if (isCenterPosition)
|
||||||
return 0;
|
return 0;
|
||||||
const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom;
|
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"
|
property: isCenterPosition ? "y" : "x"
|
||||||
from: {
|
from: {
|
||||||
if (isTopCenter)
|
if (isTopCenter)
|
||||||
return -Anims.slidePx;
|
return -entryTravel;
|
||||||
if (isBottomCenter)
|
if (isBottomCenter)
|
||||||
return Anims.slidePx;
|
return entryTravel;
|
||||||
const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom;
|
const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom;
|
||||||
return isLeft ? -Anims.slidePx : Anims.slidePx;
|
return isLeft ? -entryTravel : entryTravel;
|
||||||
}
|
}
|
||||||
to: 0
|
to: 0
|
||||||
duration: Theme.notificationEnterDuration
|
duration: Theme.variantDuration(Theme.notificationEnterDuration, true)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: isCenterPosition ? Theme.expressiveCurves.standardDecel : Theme.expressiveCurves.emphasizedDecel
|
easing.bezierCurve: Theme.variantPopoutEnterCurve
|
||||||
onStopped: {
|
onStopped: {
|
||||||
if (!win.exiting && !win._isDestroying) {
|
if (!win.exiting && !win._isDestroying) {
|
||||||
if (isCenterPosition) {
|
if (isCenterPosition) {
|
||||||
@@ -959,35 +982,35 @@ PanelWindow {
|
|||||||
from: 0
|
from: 0
|
||||||
to: {
|
to: {
|
||||||
if (isTopCenter)
|
if (isTopCenter)
|
||||||
return -Anims.slidePx;
|
return -exitTravel;
|
||||||
if (isBottomCenter)
|
if (isBottomCenter)
|
||||||
return Anims.slidePx;
|
return exitTravel;
|
||||||
const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom;
|
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.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedAccel
|
easing.bezierCurve: Theme.variantPopoutExitCurve
|
||||||
}
|
}
|
||||||
|
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
target: content
|
target: content
|
||||||
property: "opacity"
|
property: "opacity"
|
||||||
from: 1
|
from: 1
|
||||||
to: 0
|
to: Theme.isDirectionalEffect ? 1 : 0
|
||||||
duration: Theme.notificationExitDuration
|
duration: Theme.variantDuration(Theme.notificationExitDuration, false)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: Theme.expressiveCurves.standardAccel
|
easing.bezierCurve: Theme.variantPopoutExitCurve
|
||||||
}
|
}
|
||||||
|
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
target: content
|
target: content
|
||||||
property: "scale"
|
property: "scale"
|
||||||
from: 1
|
from: 1
|
||||||
to: 0.98
|
to: Theme.isDirectionalEffect ? 1 : Theme.effectScaleCollapsed
|
||||||
duration: Theme.notificationExitDuration
|
duration: Theme.variantDuration(Theme.notificationExitDuration, false)
|
||||||
easing.type: Easing.BezierSpline
|
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;
|
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) {
|
function _sync(newWrappers) {
|
||||||
for (const p of popupWindows.slice()) {
|
for (const p of popupWindows.slice()) {
|
||||||
if (!_isValidWindow(p) || p.exiting)
|
if (!_isValidWindow(p) || p.exiting)
|
||||||
@@ -125,7 +118,7 @@ QtObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const w of newWrappers) {
|
for (const w of newWrappers) {
|
||||||
if (w && !_hasWindowFor(w) && _isFocusedScreen())
|
if (w && !_hasWindowFor(w))
|
||||||
_insertAtTop(w);
|
_insertAtTop(w);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -909,9 +909,6 @@ Singleton {
|
|||||||
case "dwl":
|
case "dwl":
|
||||||
DwlService.generateOutputsConfig(outputsData);
|
DwlService.generateOutputsConfig(outputsData);
|
||||||
break;
|
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 {
|
DankToggle {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
text: I18n.tr("Show on Last Display")
|
text: I18n.tr("Show on Last Display")
|
||||||
|
|||||||
@@ -160,16 +160,6 @@ Item {
|
|||||||
onToggled: checked => SettingsData.set("dockGroupByApp", checked)
|
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 {
|
SettingsButtonGroupRow {
|
||||||
settingKey: "dockIndicatorStyle"
|
settingKey: "dockIndicatorStyle"
|
||||||
tags: ["dock", "indicator", "style", "circle", "line"]
|
tags: ["dock", "indicator", "style", "circle", "line"]
|
||||||
|
|||||||
@@ -1,784 +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.greeterFingerprintCanEnable || SettingsData.greeterEnableFprint
|
|
||||||
readonly property bool greeterU2fToggleAvailable: SettingsData.greeterU2fCanEnable || SettingsData.greeterEnableU2f
|
|
||||||
|
|
||||||
function greeterFingerprintDescription() {
|
|
||||||
const source = SettingsData.greeterFingerprintSource;
|
|
||||||
const reason = SettingsData.greeterFingerprintReason;
|
|
||||||
|
|
||||||
if (source === "pam") {
|
|
||||||
switch (reason) {
|
|
||||||
case "configured_externally":
|
|
||||||
return SettingsData.greeterEnableFprint ? I18n.tr("Enabled. PAM already provides fingerprint auth.") : I18n.tr("PAM already provides fingerprint auth. Enable this to show it at login.");
|
|
||||||
case "missing_enrollment":
|
|
||||||
return SettingsData.greeterEnableFprint ? I18n.tr("Enabled. PAM provides fingerprint auth, but no prints are enrolled yet.") : I18n.tr("PAM provides fingerprint auth, but no prints are enrolled yet.");
|
|
||||||
case "missing_reader":
|
|
||||||
return I18n.tr("PAM provides fingerprint auth, but no reader was detected.");
|
|
||||||
default:
|
|
||||||
return I18n.tr("PAM provides fingerprint auth, but availability could not be confirmed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (reason) {
|
|
||||||
case "ready":
|
|
||||||
return SettingsData.greeterEnableFprint ? I18n.tr("Run Sync to apply. Fingerprint-only login may not unlock GNOME Keyring.") : I18n.tr("Only affects DMS-managed PAM. If greetd already includes pam_fprintd, fingerprint stays enabled.");
|
|
||||||
case "missing_enrollment":
|
|
||||||
if (SettingsData.greeterEnableFprint)
|
|
||||||
return I18n.tr("Enabled, but no prints are enrolled yet. Enroll fingerprints and run Sync.");
|
|
||||||
return I18n.tr("Fingerprint reader detected, but no prints are enrolled yet. You can enable this now and run Sync later.");
|
|
||||||
case "missing_reader":
|
|
||||||
return SettingsData.greeterEnableFprint ? I18n.tr("Enabled, but no fingerprint reader was detected.") : I18n.tr("No fingerprint reader detected.");
|
|
||||||
case "missing_pam_support":
|
|
||||||
return I18n.tr("Not available — install fprintd and pam_fprintd, or configure greetd PAM.");
|
|
||||||
default:
|
|
||||||
return SettingsData.greeterEnableFprint ? I18n.tr("Enabled, but fingerprint availability could not be confirmed.") : I18n.tr("Fingerprint availability could not be confirmed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function greeterU2fDescription() {
|
|
||||||
const source = SettingsData.greeterU2fSource;
|
|
||||||
const reason = SettingsData.greeterU2fReason;
|
|
||||||
|
|
||||||
if (source === "pam") {
|
|
||||||
return SettingsData.greeterEnableU2f ? I18n.tr("Enabled. PAM already provides security-key auth.") : I18n.tr("PAM already provides security-key auth. Enable this to show it at login.");
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (reason) {
|
|
||||||
case "ready":
|
|
||||||
return SettingsData.greeterEnableU2f ? I18n.tr("Run Sync to apply.") : I18n.tr("Available.");
|
|
||||||
case "missing_key_registration":
|
|
||||||
if (SettingsData.greeterEnableU2f)
|
|
||||||
return I18n.tr("Enabled, but no registered security key was found yet. Register a key and run Sync.");
|
|
||||||
return I18n.tr("Security-key support was detected, but no registered key was found yet. You can enable this now and register one later.");
|
|
||||||
case "missing_pam_support":
|
|
||||||
return I18n.tr("Not available — install or configure pam_u2f, or configure greetd PAM.");
|
|
||||||
default:
|
|
||||||
return SettingsData.greeterEnableU2f ? I18n.tr("Enabled, but security-key availability could not be confirmed.") : I18n.tr("Security-key availability could not be confirmed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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: root.greeterFingerprintDescription()
|
|
||||||
descriptionColor: (SettingsData.greeterFingerprintReason === "ready" || SettingsData.greeterFingerprintReason === "configured_externally") ? 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: root.greeterU2fDescription()
|
|
||||||
descriptionColor: (SettingsData.greeterU2fReason === "ready" || SettingsData.greeterU2fReason === "configured_externally") ? 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,51 +9,6 @@ import qs.Modules.Settings.Widgets
|
|||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property bool lockFprintToggleAvailable: SettingsData.lockFingerprintCanEnable || SettingsData.enableFprint
|
|
||||||
readonly property bool lockU2fToggleAvailable: SettingsData.lockU2fCanEnable || SettingsData.enableU2f
|
|
||||||
|
|
||||||
function lockFingerprintDescription() {
|
|
||||||
switch (SettingsData.lockFingerprintReason) {
|
|
||||||
case "ready":
|
|
||||||
return I18n.tr("Use fingerprint authentication for the lock screen.");
|
|
||||||
case "missing_enrollment":
|
|
||||||
if (SettingsData.enableFprint)
|
|
||||||
return I18n.tr("Enabled, but no prints are enrolled yet. Enroll fingerprints to use it.");
|
|
||||||
return I18n.tr("Fingerprint reader detected, but no prints are enrolled yet. You can enable this now and enroll later.");
|
|
||||||
case "missing_reader":
|
|
||||||
return SettingsData.enableFprint ? I18n.tr("Enabled, but no fingerprint reader was detected.") : I18n.tr("No fingerprint reader detected.");
|
|
||||||
case "missing_pam_support":
|
|
||||||
return I18n.tr("Not available — install fprintd and pam_fprintd.");
|
|
||||||
default:
|
|
||||||
return SettingsData.enableFprint ? I18n.tr("Enabled, but fingerprint availability could not be confirmed.") : I18n.tr("Fingerprint availability could not be confirmed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function lockU2fDescription() {
|
|
||||||
switch (SettingsData.lockU2fReason) {
|
|
||||||
case "ready":
|
|
||||||
return I18n.tr("Use a security key for lock screen authentication.", "lock screen U2F security key setting");
|
|
||||||
case "missing_key_registration":
|
|
||||||
if (SettingsData.enableU2f)
|
|
||||||
return I18n.tr("Enabled, but no registered security key was found yet. Register a key or update your U2F config.");
|
|
||||||
return I18n.tr("Security-key support was detected, but no registered key was found yet. You can enable this now and register one later.");
|
|
||||||
case "missing_pam_support":
|
|
||||||
return I18n.tr("Not available — install or configure pam_u2f.");
|
|
||||||
default:
|
|
||||||
return SettingsData.enableU2f ? I18n.tr("Enabled, but security-key availability could not be confirmed.") : I18n.tr("Security-key availability could not be confirmed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function refreshAuthDetection() {
|
|
||||||
SettingsData.refreshAuthAvailability();
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: refreshAuthDetection()
|
|
||||||
onVisibleChanged: {
|
|
||||||
if (visible)
|
|
||||||
refreshAuthDetection();
|
|
||||||
}
|
|
||||||
|
|
||||||
FileBrowserModal {
|
FileBrowserModal {
|
||||||
id: videoBrowserModal
|
id: videoBrowserModal
|
||||||
browserTitle: I18n.tr("Select Video or Folder")
|
browserTitle: I18n.tr("Select Video or Folder")
|
||||||
@@ -217,10 +172,10 @@ Item {
|
|||||||
settingKey: "enableFprint"
|
settingKey: "enableFprint"
|
||||||
tags: ["lock", "screen", "fingerprint", "authentication", "biometric", "fprint"]
|
tags: ["lock", "screen", "fingerprint", "authentication", "biometric", "fprint"]
|
||||||
text: I18n.tr("Enable fingerprint authentication")
|
text: I18n.tr("Enable fingerprint authentication")
|
||||||
description: root.lockFingerprintDescription()
|
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.lockFingerprintReason === "ready" ? Theme.surfaceVariantText : Theme.warning
|
descriptionColor: SettingsData.fprintdAvailable ? Theme.surfaceVariantText : Theme.warning
|
||||||
checked: SettingsData.enableFprint
|
checked: SettingsData.enableFprint
|
||||||
enabled: root.lockFprintToggleAvailable
|
enabled: SettingsData.fprintdAvailable
|
||||||
onToggled: checked => SettingsData.set("enableFprint", checked)
|
onToggled: checked => SettingsData.set("enableFprint", checked)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,10 +183,10 @@ Item {
|
|||||||
settingKey: "enableU2f"
|
settingKey: "enableU2f"
|
||||||
tags: ["lock", "screen", "u2f", "yubikey", "security", "key", "fido", "authentication", "hardware"]
|
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")
|
text: I18n.tr("Enable security key authentication", "Enable FIDO2/U2F hardware security key for lock screen")
|
||||||
description: root.lockU2fDescription()
|
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.lockU2fReason === "ready" ? Theme.surfaceVariantText : Theme.warning
|
descriptionColor: SettingsData.u2fAvailable ? Theme.surfaceVariantText : Theme.warning
|
||||||
checked: SettingsData.enableU2f
|
checked: SettingsData.enableU2f
|
||||||
enabled: root.lockU2fToggleAvailable
|
enabled: SettingsData.u2fAvailable
|
||||||
onToggled: checked => SettingsData.set("enableU2f", checked)
|
onToggled: checked => SettingsData.set("enableU2f", checked)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,7 +195,7 @@ Item {
|
|||||||
tags: ["lock", "screen", "u2f", "yubikey", "security", "key", "mode", "factor", "second"]
|
tags: ["lock", "screen", "u2f", "yubikey", "security", "key", "mode", "factor", "second"]
|
||||||
text: I18n.tr("Security key mode", "lock screen U2F security key mode setting")
|
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")
|
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")]
|
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")
|
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 => {
|
onValueChanged: value => {
|
||||||
@@ -290,7 +245,7 @@ Item {
|
|||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: I18n.tr("Path to a video file or folder containing videos")
|
text: I18n.tr("Path to a video file or folder containing videos")
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeXSmall
|
||||||
color: Theme.outlineVariant
|
color: Theme.outlineVariant
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
|||||||
@@ -1935,6 +1935,11 @@ Item {
|
|||||||
label: I18n.tr("Auth Type"),
|
label: I18n.tr("Auth Type"),
|
||||||
value: data["connection-type"]
|
value: data["connection-type"]
|
||||||
});
|
});
|
||||||
|
fields.push({
|
||||||
|
label: I18n.tr("Autoconnect"),
|
||||||
|
value: configData.autoconnect ? I18n.tr("Yes") : I18n.tr("No")
|
||||||
|
});
|
||||||
|
|
||||||
return fields;
|
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 {
|
Item {
|
||||||
width: 1
|
width: 1
|
||||||
height: Theme.spacingXS
|
height: Theme.spacingXS
|
||||||
|
|||||||
@@ -20,10 +20,7 @@ Item {
|
|||||||
var out = [];
|
var out = [];
|
||||||
for (var i = 0; i < rules.length; i++) {
|
for (var i = 0; i < rules.length; i++) {
|
||||||
if ((rules[i].action || "").toString().toLowerCase() === "mute")
|
if ((rules[i].action || "").toString().toLowerCase() === "mute")
|
||||||
out.push({
|
out.push({ rule: rules[i], index: i });
|
||||||
rule: rules[i],
|
|
||||||
index: i
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
@@ -291,15 +288,6 @@ Item {
|
|||||||
onToggled: checked => SettingsData.set("notificationPopupPrivacyMode", checked)
|
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 {
|
Item {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: notificationAnimationColumn.implicitHeight + Theme.spacingM * 2
|
height: notificationAnimationColumn.implicitHeight + Theme.spacingM * 2
|
||||||
@@ -352,7 +340,6 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsSliderRow {
|
SettingsSliderRow {
|
||||||
id: animationDurationSlider
|
|
||||||
settingKey: "notificationCustomAnimationDuration"
|
settingKey: "notificationCustomAnimationDuration"
|
||||||
tags: ["notification", "animation", "duration", "custom", "speed"]
|
tags: ["notification", "animation", "duration", "custom", "speed"]
|
||||||
text: I18n.tr("Duration")
|
text: I18n.tr("Duration")
|
||||||
@@ -368,13 +355,6 @@ Item {
|
|||||||
}
|
}
|
||||||
SettingsData.set("notificationCustomAnimationDuration", newValue);
|
SettingsData.set("notificationCustomAnimationDuration", newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: Theme
|
|
||||||
function onNotificationAnimationBaseDurationChanged() {
|
|
||||||
animationDurationSlider.value = Theme.notificationAnimationBaseDuration;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -408,8 +408,6 @@ FloatingWindow {
|
|||||||
}
|
}
|
||||||
clip: true
|
clip: true
|
||||||
visible: !root.isLoading
|
visible: !root.isLoading
|
||||||
add: null
|
|
||||||
displaced: null
|
|
||||||
|
|
||||||
ScrollBar.vertical: DankScrollbar {
|
ScrollBar.vertical: DankScrollbar {
|
||||||
id: browserScrollbar
|
id: browserScrollbar
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import QtCore
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Effects
|
import QtQuick.Effects
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals.FileBrowser
|
import qs.Modals.FileBrowser
|
||||||
import qs.Services
|
import qs.Services
|
||||||
@@ -750,24 +751,6 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("No themes installed. Browse themes to install from the registry.", "no registry themes installed hint")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
width: parent.width
|
|
||||||
visible: themeColorsTab.installedRegistryThemes.length === 0
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
DankButton {
|
|
||||||
text: I18n.tr("Browse Themes", "browse themes button")
|
|
||||||
iconName: "store"
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
onClicked: showThemeBrowser()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: variantSelector
|
id: variantSelector
|
||||||
width: parent.width
|
width: parent.width
|
||||||
@@ -775,9 +758,7 @@ Item {
|
|||||||
visible: activeThemeId !== "" && activeThemeVariants !== null && (isMultiVariant || (activeThemeVariants.options && activeThemeVariants.options.length > 0))
|
visible: activeThemeId !== "" && activeThemeVariants !== null && (isMultiVariant || (activeThemeVariants.options && activeThemeVariants.options.length > 0))
|
||||||
|
|
||||||
property string activeThemeId: {
|
property string activeThemeId: {
|
||||||
switch (Theme.currentThemeCategory) {
|
if (Theme.currentThemeCategory !== "registry" || Theme.currentTheme !== "custom")
|
||||||
case "registry":
|
|
||||||
if (Theme.currentTheme !== "custom")
|
|
||||||
return "";
|
return "";
|
||||||
for (var i = 0; i < themeColorsTab.installedRegistryThemes.length; i++) {
|
for (var i = 0; i < themeColorsTab.installedRegistryThemes.length; i++) {
|
||||||
var t = themeColorsTab.installedRegistryThemes[i];
|
var t = themeColorsTab.installedRegistryThemes[i];
|
||||||
@@ -785,28 +766,16 @@ Item {
|
|||||||
return t.id;
|
return t.id;
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
case "custom":
|
|
||||||
return Theme.currentThemeId || "";
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
property var activeThemeVariants: {
|
property var activeThemeVariants: {
|
||||||
if (!activeThemeId)
|
if (!activeThemeId)
|
||||||
return null;
|
return null;
|
||||||
switch (Theme.currentThemeCategory) {
|
|
||||||
case "registry":
|
|
||||||
for (var i = 0; i < themeColorsTab.installedRegistryThemes.length; i++) {
|
for (var i = 0; i < themeColorsTab.installedRegistryThemes.length; i++) {
|
||||||
var t = themeColorsTab.installedRegistryThemes[i];
|
var t = themeColorsTab.installedRegistryThemes[i];
|
||||||
if (t.id === activeThemeId && t.hasVariants)
|
if (t.id === activeThemeId && t.hasVariants)
|
||||||
return t.variants;
|
return t.variants;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
case "custom":
|
|
||||||
return Theme.currentThemeVariants || null;
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
property bool isMultiVariant: activeThemeVariants?.type === "multi"
|
property bool isMultiVariant: activeThemeVariants?.type === "multi"
|
||||||
property string colorMode: Theme.isLightMode ? "light" : "dark"
|
property string colorMode: Theme.isLightMode ? "light" : "dark"
|
||||||
@@ -923,7 +892,7 @@ Item {
|
|||||||
width: accentColorsGrid.dotSize
|
width: accentColorsGrid.dotSize
|
||||||
height: accentColorsGrid.dotSize
|
height: accentColorsGrid.dotSize
|
||||||
radius: width / 2
|
radius: width / 2
|
||||||
color: modelData.color || modelData[variantSelector.selectedFlavor]?.primary || Theme.primary
|
color: modelData.color || Theme.primary
|
||||||
border.color: Theme.outline
|
border.color: Theme.outline
|
||||||
border.width: isSelected ? 2 : 1
|
border.width: isSelected ? 2 : 1
|
||||||
scale: isSelected ? 1.1 : 1
|
scale: isSelected ? 1.1 : 1
|
||||||
@@ -1009,6 +978,24 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("No themes installed. Browse themes to install from the registry.", "no registry themes installed hint")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
width: parent.width
|
||||||
|
visible: themeColorsTab.installedRegistryThemes.length === 0
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
DankButton {
|
||||||
|
text: I18n.tr("Browse Themes", "browse themes button")
|
||||||
|
iconName: "store"
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
onClicked: showThemeBrowser()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2651,18 +2638,6 @@ Item {
|
|||||||
checked: SettingsData.matugenTemplateEmacs
|
checked: SettingsData.matugenTemplateEmacs
|
||||||
onToggled: checked => SettingsData.set("matugenTemplateEmacs", checked)
|
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 {
|
Rectangle {
|
||||||
|
|||||||
@@ -55,6 +55,180 @@ Item {
|
|||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
spacing: Theme.spacingXL
|
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 {
|
SettingsCard {
|
||||||
tab: "typography"
|
tab: "typography"
|
||||||
tags: ["font", "family", "text", "typography"]
|
tags: ["font", "family", "text", "typography"]
|
||||||
|
|||||||
@@ -125,16 +125,6 @@ Item {
|
|||||||
onToggled: checked => SettingsData.set("groupWorkspaceApps", checked)
|
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 {
|
SettingsToggleRow {
|
||||||
settingKey: "workspaceFollowFocus"
|
settingKey: "workspaceFollowFocus"
|
||||||
tags: ["workspace", "focus", "follow", "monitor"]
|
tags: ["workspace", "focus", "follow", "monitor"]
|
||||||
|
|||||||
@@ -121,9 +121,9 @@ Scope {
|
|||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewScope.overviewOpen)
|
||||||
easing.type: Easing.BezierSpline
|
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
|
id: scaleTransform
|
||||||
origin.x: contentContainer.width / 2
|
origin.x: contentContainer.width / 2
|
||||||
origin.y: contentContainer.height / 2
|
origin.y: contentContainer.height / 2
|
||||||
xScale: overviewScope.overviewOpen ? 1 : 0.96
|
xScale: overviewScope.overviewOpen ? 1 : Theme.effectScaleCollapsed
|
||||||
yScale: overviewScope.overviewOpen ? 1 : 0.96
|
yScale: overviewScope.overviewOpen ? 1 : Theme.effectScaleCollapsed
|
||||||
|
|
||||||
Behavior on xScale {
|
Behavior on xScale {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewScope.overviewOpen)
|
||||||
easing.type: Easing.BezierSpline
|
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 {
|
Behavior on yScale {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewScope.overviewOpen)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: overviewScope.overviewOpen ? Theme.expressiveCurves.expressiveDefaultSpatial : Theme.expressiveCurves.emphasized
|
easing.bezierCurve: overviewScope.overviewOpen ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Translate {
|
Translate {
|
||||||
id: motionTransform
|
id: motionTransform
|
||||||
x: 0
|
x: {
|
||||||
y: overviewScope.overviewOpen ? 0 : Theme.spacingL
|
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 {
|
Behavior on y {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewScope.overviewOpen)
|
||||||
easing.type: Easing.BezierSpline
|
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 {
|
Behavior on opacity {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewScope.overviewOpen)
|
||||||
easing.type: Easing.BezierSpline
|
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 {
|
Item {
|
||||||
id: spotlightContainer
|
id: spotlightContainer
|
||||||
x: Theme.snap((parent.width - width) / 2, overlayWindow.dpr)
|
readonly property bool directionalEffect: Theme.isDirectionalEffect
|
||||||
y: Theme.snap((parent.height - height) / 2, overlayWindow.dpr)
|
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: {
|
readonly property int baseWidth: {
|
||||||
switch (SettingsData.dankLauncherV2Size) {
|
switch (SettingsData.dankLauncherV2Size) {
|
||||||
@@ -234,8 +244,8 @@ Scope {
|
|||||||
|
|
||||||
readonly property bool animatingOut: niriOverviewScope.isClosing && overlayWindow.isSpotlightScreen
|
readonly property bool animatingOut: niriOverviewScope.isClosing && overlayWindow.isSpotlightScreen
|
||||||
|
|
||||||
scale: overlayWindow.shouldShowSpotlight ? 1.0 : 0.96
|
scale: Theme.isDirectionalEffect ? 1 : (overlayWindow.shouldShowSpotlight ? 1.0 : Theme.effectScaleCollapsed)
|
||||||
opacity: overlayWindow.shouldShowSpotlight ? 1 : 0
|
opacity: Theme.isDirectionalEffect ? 1 : (overlayWindow.shouldShowSpotlight ? 1 : 0)
|
||||||
visible: overlayWindow.shouldShowSpotlight || animatingOut
|
visible: overlayWindow.shouldShowSpotlight || animatingOut
|
||||||
enabled: overlayWindow.shouldShowSpotlight
|
enabled: overlayWindow.shouldShowSpotlight
|
||||||
|
|
||||||
@@ -245,10 +255,11 @@ Scope {
|
|||||||
|
|
||||||
Behavior on scale {
|
Behavior on scale {
|
||||||
id: scaleAnimation
|
id: scaleAnimation
|
||||||
|
enabled: !Theme.isDirectionalEffect
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.fast
|
duration: Theme.variantDuration(Theme.expressiveDurations.fast, overlayWindow.shouldShowSpotlight)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: spotlightContainer.visible ? Theme.expressiveCurves.expressiveFastSpatial : Theme.expressiveCurves.standardAccel
|
easing.bezierCurve: spotlightContainer.visible ? Theme.variantModalEnterCurve : Theme.variantModalExitCurve
|
||||||
onRunningChanged: {
|
onRunningChanged: {
|
||||||
if (running || !spotlightContainer.animatingOut)
|
if (running || !spotlightContainer.animatingOut)
|
||||||
return;
|
return;
|
||||||
@@ -258,10 +269,27 @@ Scope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
|
enabled: !Theme.isDirectionalEffect
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.fast
|
duration: Theme.variantDuration(Theme.expressiveDurations.fast, overlayWindow.shouldShowSpotlight)
|
||||||
easing.type: Easing.BezierSpline
|
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 {
|
Behavior on x {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewOpen)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
|
easing.bezierCurve: Theme.variantModalEnterCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Behavior on y {
|
Behavior on y {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewOpen)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
|
easing.bezierCurve: Theme.variantModalEnterCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Behavior on width {
|
Behavior on width {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewOpen)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
|
easing.bezierCurve: Theme.variantModalEnterCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Behavior on height {
|
Behavior on height {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewOpen)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
|
easing.bezierCurve: Theme.variantModalEnterCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,16 +124,16 @@ Item {
|
|||||||
|
|
||||||
Behavior on width {
|
Behavior on width {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewOpen)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
|
easing.bezierCurve: Theme.variantModalEnterCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Behavior on height {
|
Behavior on height {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewOpen)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: Theme.expressiveCurves.emphasizedDecel
|
easing.bezierCurve: Theme.variantModalEnterCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,9 +27,7 @@ quickshell -p quickshell/
|
|||||||
**Code formatting:**
|
**Code formatting:**
|
||||||
```bash
|
```bash
|
||||||
qmlfmt -t 4 -i 4 -b 250 -w path/to/file.qml
|
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/`)
|
qmllint **/*.qml
|
||||||
# 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`.
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Components
|
## Components
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ pragma ComponentBehavior: Bound
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import qs.Common
|
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|||||||
@@ -93,9 +93,9 @@ Singleton {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
monitorOffMonitor = Qt.createQmlObject(qmlString, root, "IdleService.MonitorOffMonitor");
|
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.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 () {
|
monitorOffMonitor.isIdleChanged.connect(function () {
|
||||||
if (monitorOffMonitor.isIdle) {
|
if (monitorOffMonitor.isIdle) {
|
||||||
if (SettingsData.fadeToDpmsEnabled) {
|
if (SettingsData.fadeToDpmsEnabled) {
|
||||||
@@ -112,9 +112,9 @@ Singleton {
|
|||||||
});
|
});
|
||||||
|
|
||||||
lockMonitor = Qt.createQmlObject(qmlString, root, "IdleService.LockMonitor");
|
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.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 () {
|
lockMonitor.isIdleChanged.connect(function () {
|
||||||
if (lockMonitor.isIdle) {
|
if (lockMonitor.isIdle) {
|
||||||
if (SettingsData.fadeToLockEnabled) {
|
if (SettingsData.fadeToLockEnabled) {
|
||||||
@@ -130,9 +130,9 @@ Singleton {
|
|||||||
});
|
});
|
||||||
|
|
||||||
suspendMonitor = Qt.createQmlObject(qmlString, root, "IdleService.SuspendMonitor");
|
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.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 () {
|
suspendMonitor.isIdleChanged.connect(function () {
|
||||||
if (suspendMonitor.isIdle) {
|
if (suspendMonitor.isIdle) {
|
||||||
root.requestSuspend();
|
root.requestSuspend();
|
||||||
|
|||||||
@@ -142,7 +142,6 @@ Singleton {
|
|||||||
}
|
}
|
||||||
ToastService.showInfo(I18n.tr("VPN configuration updated"));
|
ToastService.showInfo(I18n.tr("VPN configuration updated"));
|
||||||
DMSNetworkService.refreshVpnProfiles();
|
DMSNetworkService.refreshVpnProfiles();
|
||||||
getConfig(uuid);
|
|
||||||
configUpdated();
|
configUpdated();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (process) {
|
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.targetScreenName = screenName;
|
||||||
process.currentWallpaper = currentWallpaper;
|
process.currentWallpaper = currentWallpaper;
|
||||||
process.goToPrevious = false;
|
process.goToPrevious = false;
|
||||||
@@ -272,7 +272,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Use global process for fallback
|
// 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.targetScreenName = screenName || "";
|
||||||
cyclingProcess.currentWallpaper = currentWallpaper;
|
cyclingProcess.currentWallpaper = currentWallpaper;
|
||||||
cyclingProcess.running = true;
|
cyclingProcess.running = true;
|
||||||
@@ -296,7 +296,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (process) {
|
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.targetScreenName = screenName;
|
||||||
process.currentWallpaper = currentWallpaper;
|
process.currentWallpaper = currentWallpaper;
|
||||||
process.goToPrevious = true;
|
process.goToPrevious = true;
|
||||||
@@ -304,7 +304,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Use global process for fallback
|
// 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.targetScreenName = screenName || "";
|
||||||
prevCyclingProcess.currentWallpaper = currentWallpaper;
|
prevCyclingProcess.currentWallpaper = currentWallpaper;
|
||||||
prevCyclingProcess.running = true;
|
prevCyclingProcess.running = true;
|
||||||
|
|||||||
@@ -18,191 +18,197 @@ Singleton {
|
|||||||
target: DMSService
|
target: DMSService
|
||||||
|
|
||||||
function onCapabilitiesReceived() {
|
function onCapabilitiesReceived() {
|
||||||
checkCapabilities();
|
checkCapabilities()
|
||||||
}
|
}
|
||||||
|
|
||||||
function onConnectionStateChanged() {
|
function onConnectionStateChanged() {
|
||||||
if (DMSService.isConnected) {
|
if (DMSService.isConnected) {
|
||||||
checkCapabilities();
|
checkCapabilities()
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
wlrOutputAvailable = false;
|
wlrOutputAvailable = false
|
||||||
}
|
}
|
||||||
|
|
||||||
function onWlrOutputStateUpdate(data) {
|
function onWlrOutputStateUpdate(data) {
|
||||||
if (!wlrOutputAvailable) {
|
if (!wlrOutputAvailable) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
handleStateUpdate(data);
|
handleStateUpdate(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (!DMSService.dmsAvailable) {
|
if (!DMSService.dmsAvailable) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
checkCapabilities();
|
checkCapabilities()
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkCapabilities() {
|
function checkCapabilities() {
|
||||||
if (!DMSService.capabilities || !Array.isArray(DMSService.capabilities)) {
|
if (!DMSService.capabilities || !Array.isArray(DMSService.capabilities)) {
|
||||||
wlrOutputAvailable = false;
|
wlrOutputAvailable = false
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasWlrOutput = DMSService.capabilities.includes("wlroutput");
|
const hasWlrOutput = DMSService.capabilities.includes("wlroutput")
|
||||||
if (hasWlrOutput && !wlrOutputAvailable) {
|
if (hasWlrOutput && !wlrOutputAvailable) {
|
||||||
wlrOutputAvailable = true;
|
wlrOutputAvailable = true
|
||||||
console.info("WlrOutputService: wlr-output-management capability detected");
|
console.info("WlrOutputService: wlr-output-management capability detected")
|
||||||
requestState();
|
requestState()
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasWlrOutput) {
|
if (!hasWlrOutput) {
|
||||||
wlrOutputAvailable = false;
|
wlrOutputAvailable = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function requestState() {
|
function requestState() {
|
||||||
if (!DMSService.isConnected || !wlrOutputAvailable) {
|
if (!DMSService.isConnected || !wlrOutputAvailable) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
DMSService.sendRequest("wlroutput.getState", null, response => {
|
DMSService.sendRequest("wlroutput.getState", null, response => {
|
||||||
if (!response.result) {
|
if (!response.result) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
handleStateUpdate(response.result);
|
handleStateUpdate(response.result)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleStateUpdate(state) {
|
function handleStateUpdate(state) {
|
||||||
outputs = state.outputs || [];
|
outputs = state.outputs || []
|
||||||
serial = state.serial || 0;
|
serial = state.serial || 0
|
||||||
|
|
||||||
if (outputs.length === 0) {
|
if (outputs.length === 0) {
|
||||||
console.warn("WlrOutputService: Received empty outputs list");
|
console.warn("WlrOutputService: Received empty outputs list")
|
||||||
} else {
|
} 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) => {
|
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) {
|
function getOutput(name) {
|
||||||
for (const output of outputs) {
|
for (const output of outputs) {
|
||||||
if (output.name === name) {
|
if (output.name === name) {
|
||||||
return output;
|
return output
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEnabledOutputs() {
|
function getEnabledOutputs() {
|
||||||
return outputs.filter(output => output.enabled);
|
return outputs.filter(output => output.enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyConfiguration(heads, callback) {
|
function applyConfiguration(heads, callback) {
|
||||||
if (!DMSService.isConnected || !wlrOutputAvailable) {
|
if (!DMSService.isConnected || !wlrOutputAvailable) {
|
||||||
if (callback) {
|
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) => {
|
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", {
|
DMSService.sendRequest("wlroutput.applyConfiguration", {
|
||||||
"heads": heads
|
"heads": heads
|
||||||
}, response => {
|
}, response => {
|
||||||
const success = !response.error;
|
const success = !response.error
|
||||||
const message = response.error || response.result?.message || "";
|
const message = response.error || response.result?.message || ""
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
console.warn("WlrOutputService: applyConfiguration error:", response.error);
|
console.warn("WlrOutputService: applyConfiguration error:", response.error)
|
||||||
} else {
|
} else {
|
||||||
console.log("WlrOutputService: Configuration applied successfully");
|
console.log("WlrOutputService: Configuration applied successfully")
|
||||||
}
|
}
|
||||||
|
|
||||||
configurationApplied(success, message);
|
configurationApplied(success, message)
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(success, message);
|
callback(success, message)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function testConfiguration(heads, callback) {
|
function testConfiguration(heads, callback) {
|
||||||
if (!DMSService.isConnected || !wlrOutputAvailable) {
|
if (!DMSService.isConnected || !wlrOutputAvailable) {
|
||||||
if (callback) {
|
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", {
|
DMSService.sendRequest("wlroutput.testConfiguration", {
|
||||||
"heads": heads
|
"heads": heads
|
||||||
}, response => {
|
}, response => {
|
||||||
const success = !response.error;
|
const success = !response.error
|
||||||
const message = response.error || response.result?.message || "";
|
const message = response.error || response.result?.message || ""
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
console.warn("WlrOutputService: testConfiguration error:", response.error);
|
console.warn("WlrOutputService: testConfiguration error:", response.error)
|
||||||
} else {
|
} else {
|
||||||
console.log("WlrOutputService: Configuration test passed");
|
console.log("WlrOutputService: Configuration test passed")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(success, message);
|
callback(success, message)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function setOutputEnabled(outputName, enabled, callback) {
|
function setOutputEnabled(outputName, enabled, callback) {
|
||||||
const output = getOutput(outputName);
|
const output = getOutput(outputName)
|
||||||
if (!output) {
|
if (!output) {
|
||||||
console.warn("WlrOutputService: Output not found:", outputName);
|
console.warn("WlrOutputService: Output not found:", outputName)
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(false, "Output not found");
|
callback(false, "Output not found")
|
||||||
}
|
}
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const heads = [
|
const heads = [{
|
||||||
{
|
|
||||||
"name": outputName,
|
"name": outputName,
|
||||||
"enabled": enabled
|
"enabled": enabled
|
||||||
}
|
}]
|
||||||
];
|
|
||||||
|
|
||||||
if (enabled && output.currentMode) {
|
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) {
|
function setOutputMode(outputName, modeId, callback) {
|
||||||
const heads = [
|
const heads = [{
|
||||||
{
|
|
||||||
"name": outputName,
|
"name": outputName,
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"modeId": modeId
|
"modeId": modeId
|
||||||
}
|
}]
|
||||||
];
|
|
||||||
|
|
||||||
applyConfiguration(heads, callback);
|
applyConfiguration(heads, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setOutputCustomMode(outputName, width, height, refresh, callback) {
|
function setOutputCustomMode(outputName, width, height, refresh, callback) {
|
||||||
const heads = [
|
const heads = [{
|
||||||
{
|
|
||||||
"name": outputName,
|
"name": outputName,
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"customMode": {
|
"customMode": {
|
||||||
@@ -210,137 +216,60 @@ Singleton {
|
|||||||
"height": height,
|
"height": height,
|
||||||
"refresh": refresh
|
"refresh": refresh
|
||||||
}
|
}
|
||||||
}
|
}]
|
||||||
];
|
|
||||||
|
|
||||||
applyConfiguration(heads, callback);
|
applyConfiguration(heads, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setOutputPosition(outputName, x, y, callback) {
|
function setOutputPosition(outputName, x, y, callback) {
|
||||||
const heads = [
|
const heads = [{
|
||||||
{
|
|
||||||
"name": outputName,
|
"name": outputName,
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"position": {
|
"position": {
|
||||||
"x": x,
|
"x": x,
|
||||||
"y": y
|
"y": y
|
||||||
}
|
}
|
||||||
}
|
}]
|
||||||
];
|
|
||||||
|
|
||||||
applyConfiguration(heads, callback);
|
applyConfiguration(heads, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setOutputScale(outputName, scale, callback) {
|
function setOutputScale(outputName, scale, callback) {
|
||||||
const heads = [
|
const heads = [{
|
||||||
{
|
|
||||||
"name": outputName,
|
"name": outputName,
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"scale": scale
|
"scale": scale
|
||||||
}
|
}]
|
||||||
];
|
|
||||||
|
|
||||||
applyConfiguration(heads, callback);
|
applyConfiguration(heads, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setOutputTransform(outputName, transform, callback) {
|
function setOutputTransform(outputName, transform, callback) {
|
||||||
const heads = [
|
const heads = [{
|
||||||
{
|
|
||||||
"name": outputName,
|
"name": outputName,
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"transform": transform
|
"transform": transform
|
||||||
}
|
}]
|
||||||
];
|
|
||||||
|
|
||||||
applyConfiguration(heads, callback);
|
applyConfiguration(heads, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setOutputAdaptiveSync(outputName, state, callback) {
|
function setOutputAdaptiveSync(outputName, state, callback) {
|
||||||
const heads = [
|
const heads = [{
|
||||||
{
|
|
||||||
"name": outputName,
|
"name": outputName,
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"adaptiveSync": state
|
"adaptiveSync": state
|
||||||
}
|
}]
|
||||||
];
|
|
||||||
|
|
||||||
applyConfiguration(heads, callback);
|
applyConfiguration(heads, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function configureOutput(config, callback) {
|
function configureOutput(config, callback) {
|
||||||
const heads = [config];
|
const heads = [config]
|
||||||
applyConfiguration(heads, callback);
|
applyConfiguration(heads, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
function configureMultipleOutputs(configs, callback) {
|
function configureMultipleOutputs(configs, callback) {
|
||||||
applyConfiguration(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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,12 +121,7 @@ Item {
|
|||||||
dropdownMenu.close();
|
dropdownMenu.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dropdownMenu.open();
|
dropdownMenu.open();
|
||||||
|
|
||||||
let currentIndex = root.options.indexOf(root.currentValue);
|
|
||||||
listView.positionViewAtIndex(currentIndex, ListView.Beginning);
|
|
||||||
|
|
||||||
const pos = dropdown.mapToItem(Overlay.overlay, 0, 0);
|
const pos = dropdown.mapToItem(Overlay.overlay, 0, 0);
|
||||||
const popupW = dropdownMenu.width;
|
const popupW = dropdownMenu.width;
|
||||||
const popupH = dropdownMenu.height;
|
const popupH = dropdownMenu.height;
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ Item {
|
|||||||
property string triggerSection: ""
|
property string triggerSection: ""
|
||||||
property string positioning: "center"
|
property string positioning: "center"
|
||||||
property int animationDuration: Theme.popoutAnimationDuration
|
property int animationDuration: Theme.popoutAnimationDuration
|
||||||
property real animationScaleCollapsed: 0.96
|
property real animationScaleCollapsed: Theme.effectScaleCollapsed
|
||||||
property real animationOffset: Theme.spacingL
|
property real animationOffset: Theme.effectAnimOffset
|
||||||
property list<real> animationEnterCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
property list<real> animationEnterCurve: Theme.variantPopoutEnterCurve
|
||||||
property list<real> animationExitCurve: Theme.expressiveCurves.emphasized
|
property list<real> animationExitCurve: Theme.variantPopoutExitCurve
|
||||||
property bool suspendShadowWhileResizing: false
|
property bool suspendShadowWhileResizing: false
|
||||||
property bool shouldBeVisible: false
|
property bool shouldBeVisible: false
|
||||||
property var customKeyboardFocus: null
|
property var customKeyboardFocus: null
|
||||||
@@ -74,6 +74,7 @@ Item {
|
|||||||
signal backgroundClicked
|
signal backgroundClicked
|
||||||
|
|
||||||
property var _lastOpenedScreen: null
|
property var _lastOpenedScreen: null
|
||||||
|
property bool isClosing: false
|
||||||
|
|
||||||
property int effectiveBarPosition: 0
|
property int effectiveBarPosition: 0
|
||||||
property real effectiveBarBottomGap: 0
|
property real effectiveBarBottomGap: 0
|
||||||
@@ -156,10 +157,14 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property bool animationsEnabled: true
|
||||||
|
|
||||||
function open() {
|
function open() {
|
||||||
if (!screen)
|
if (!screen)
|
||||||
return;
|
return;
|
||||||
closeTimer.stop();
|
closeTimer.stop();
|
||||||
|
isClosing = false;
|
||||||
|
animationsEnabled = false;
|
||||||
|
|
||||||
// Snapshot mask geometry
|
// Snapshot mask geometry
|
||||||
_frozenMaskX = maskX;
|
_frozenMaskX = maskX;
|
||||||
@@ -174,12 +179,22 @@ Item {
|
|||||||
}
|
}
|
||||||
_lastOpenedScreen = screen;
|
_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) {
|
if (useBackgroundWindow) {
|
||||||
_surfaceMarginLeft = alignedX - shadowBuffer;
|
_surfaceMarginLeft = alignedX - shadowBuffer;
|
||||||
_surfaceW = alignedWidth + shadowBuffer * 2;
|
_surfaceW = alignedWidth + shadowBuffer * 2;
|
||||||
|
backgroundWindow.visible = true;
|
||||||
}
|
}
|
||||||
|
contentWindow.visible = true;
|
||||||
|
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
|
animationsEnabled = true;
|
||||||
|
shouldBeVisible = true;
|
||||||
if (shouldBeVisible && screen) {
|
if (shouldBeVisible && screen) {
|
||||||
if (useBackgroundWindow)
|
if (useBackgroundWindow)
|
||||||
backgroundWindow.visible = true;
|
backgroundWindow.visible = true;
|
||||||
@@ -191,6 +206,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
|
isClosing = true;
|
||||||
shouldBeVisible = false;
|
shouldBeVisible = false;
|
||||||
_primeContent = false;
|
_primeContent = false;
|
||||||
PopoutManager.popoutChanged();
|
PopoutManager.popoutChanged();
|
||||||
@@ -222,9 +238,10 @@ Item {
|
|||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: closeTimer
|
id: closeTimer
|
||||||
interval: animationDuration
|
interval: Theme.variantCloseInterval(animationDuration)
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (!shouldBeVisible) {
|
if (!shouldBeVisible) {
|
||||||
|
isClosing = false;
|
||||||
contentWindow.visible = false;
|
contentWindow.visible = false;
|
||||||
if (useBackgroundWindow)
|
if (useBackgroundWindow)
|
||||||
backgroundWindow.visible = false;
|
backgroundWindow.visible = false;
|
||||||
@@ -241,7 +258,16 @@ Item {
|
|||||||
readonly property var shadowLevel: Theme.elevationLevel3
|
readonly property var shadowLevel: Theme.elevationLevel3
|
||||||
readonly property real shadowFallbackOffset: 6
|
readonly property real shadowFallbackOffset: 6
|
||||||
readonly property real shadowRenderPadding: (Theme.elevationEnabled && SettingsData.popoutElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, effectiveShadowDirection, shadowFallbackOffset, 8, 16) : 0
|
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 shadowBuffer: Theme.snap(shadowRenderPadding + shadowMotionPadding, dpr)
|
||||||
readonly property real alignedWidth: Theme.px(popupWidth, dpr)
|
readonly property real alignedWidth: Theme.px(popupWidth, dpr)
|
||||||
readonly property real alignedHeight: Theme.px(popupHeight, dpr)
|
readonly property real alignedHeight: Theme.px(popupHeight, dpr)
|
||||||
@@ -353,6 +379,10 @@ Item {
|
|||||||
|
|
||||||
mask: Region {
|
mask: Region {
|
||||||
item: maskRect
|
item: maskRect
|
||||||
|
Region {
|
||||||
|
item: contentExclusionRect
|
||||||
|
intersection: Intersection.Subtract
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -361,26 +391,70 @@ Item {
|
|||||||
color: "transparent"
|
color: "transparent"
|
||||||
x: root._frozenMaskX
|
x: root._frozenMaskX
|
||||||
y: root._frozenMaskY
|
y: root._frozenMaskY
|
||||||
width: (shouldBeVisible && backgroundInteractive) ? root._frozenMaskWidth : 0
|
width: (backgroundWindow.visible && backgroundInteractive) ? root._frozenMaskWidth : 0
|
||||||
height: (shouldBeVisible && backgroundInteractive) ? root._frozenMaskHeight : 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
|
x: root._frozenMaskX
|
||||||
y: root._frozenMaskY
|
y: root._frozenMaskY
|
||||||
width: root._frozenMaskWidth
|
width: root._frozenMaskWidth
|
||||||
height: root._frozenMaskHeight
|
height: root._frozenMaskHeight
|
||||||
hoverEnabled: false
|
enabled: root.shouldBeVisible && root.backgroundInteractive
|
||||||
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;
|
|
||||||
|
|
||||||
if (!outsideContent)
|
readonly property real contentLeft: Math.max(0, root.alignedX - x)
|
||||||
return;
|
readonly property real contentTop: Math.max(0, root.alignedY - y)
|
||||||
backgroundClicked();
|
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
|
readonly property bool _fullHeight: useBackgroundWindow && root.fullHeightSurface
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
left: true
|
left: true
|
||||||
top: true
|
top: true
|
||||||
@@ -483,12 +556,70 @@ Item {
|
|||||||
readonly property bool barBottom: effectiveBarPosition === SettingsData.Position.Bottom
|
readonly property bool barBottom: effectiveBarPosition === SettingsData.Position.Bottom
|
||||||
readonly property bool barLeft: effectiveBarPosition === SettingsData.Position.Left
|
readonly property bool barLeft: effectiveBarPosition === SettingsData.Position.Left
|
||||||
readonly property bool barRight: effectiveBarPosition === SettingsData.Position.Right
|
readonly property bool barRight: effectiveBarPosition === SettingsData.Position.Right
|
||||||
readonly property real offsetX: barLeft ? root.animationOffset : (barRight ? -root.animationOffset : 0)
|
readonly property bool directionalEffect: Theme.isDirectionalEffect
|
||||||
readonly property real offsetY: barBottom ? -root.animationOffset : (barTop ? root.animationOffset : 0)
|
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 animX: 0
|
||||||
property real animY: 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)
|
onOffsetXChanged: animX = Theme.snap(root.shouldBeVisible ? 0 : offsetX, root.dpr)
|
||||||
onOffsetYChanged: animY = Theme.snap(root.shouldBeVisible ? 0 : offsetY, root.dpr)
|
onOffsetYChanged: animY = Theme.snap(root.shouldBeVisible ? 0 : offsetY, root.dpr)
|
||||||
@@ -498,34 +629,72 @@ Item {
|
|||||||
function onShouldBeVisibleChanged() {
|
function onShouldBeVisibleChanged() {
|
||||||
contentContainer.animX = Theme.snap(root.shouldBeVisible ? 0 : contentContainer.offsetX, root.dpr);
|
contentContainer.animX = Theme.snap(root.shouldBeVisible ? 0 : contentContainer.offsetX, root.dpr);
|
||||||
contentContainer.animY = Theme.snap(root.shouldBeVisible ? 0 : contentContainer.offsetY, 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 {
|
Behavior on animX {
|
||||||
|
enabled: root.animationsEnabled
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: root.animationDuration
|
duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on animY {
|
Behavior on animY {
|
||||||
|
enabled: root.animationsEnabled
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: root.animationDuration
|
duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on scaleValue {
|
Behavior on scaleValue {
|
||||||
|
enabled: root.animationsEnabled
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: root.animationDuration
|
duration: Theme.variantDuration(root.animationDuration, root.shouldBeVisible)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: directionalClipMask
|
||||||
|
|
||||||
|
readonly property bool shouldClip: typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode > 0 && Theme.isDirectionalEffect
|
||||||
|
readonly property real clipOversize: 1000
|
||||||
|
|
||||||
|
clip: shouldClip
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
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 {
|
ElevationShadow {
|
||||||
id: shadowSource
|
id: shadowSource
|
||||||
width: parent.width
|
width: parent.width
|
||||||
@@ -544,22 +713,23 @@ Item {
|
|||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: contentWrapper
|
id: contentWrapper
|
||||||
anchors.centerIn: parent
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: parent.height
|
height: parent.height
|
||||||
opacity: shouldBeVisible ? 1 : 0
|
opacity: Theme.isDirectionalEffect ? 1 : (shouldBeVisible ? 1 : 0)
|
||||||
visible: opacity > 0
|
visible: opacity > 0
|
||||||
scale: contentContainer.scaleValue
|
|
||||||
x: Theme.snap(contentContainer.animX + (parent.width - width) * (1 - contentContainer.scaleValue) * 0.5, root.dpr)
|
scale: aligner.isRollOut ? 1.0 : contentContainer.scaleValue
|
||||||
y: Theme.snap(contentContainer.animY + (parent.height - height) * (1 - contentContainer.scaleValue) * 0.5, root.dpr)
|
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.enabled: contentWrapper.opacity < 1
|
||||||
layer.smooth: false
|
layer.smooth: false
|
||||||
layer.textureSize: root.dpr > 1 ? Qt.size(Math.ceil(width * root.dpr), Math.ceil(height * root.dpr)) : Qt.size(0, 0)
|
layer.textureSize: root.dpr > 1 ? Qt.size(Math.ceil(width * root.dpr), Math.ceil(height * root.dpr)) : Qt.size(0, 0)
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
|
enabled: !Theme.isDirectionalEffect
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: animationDuration
|
duration: Math.round(Theme.variantDuration(animationDuration, shouldBeVisible) * Theme.variantOpacityDurationScale)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||||
}
|
}
|
||||||
@@ -579,8 +749,11 @@ Item {
|
|||||||
active: root._primeContent || shouldBeVisible || contentWindow.visible
|
active: root._primeContent || shouldBeVisible || contentWindow.visible
|
||||||
asynchronous: false
|
asynchronous: false
|
||||||
}
|
}
|
||||||
}
|
} // closes contentWrapper
|
||||||
}
|
} // closes unrollCounteract
|
||||||
|
} // closes aligner
|
||||||
|
} // closes directionalClipMask
|
||||||
|
} // closes contentContainer
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: focusHelper
|
id: focusHelper
|
||||||
|
|||||||
@@ -239,7 +239,7 @@ Item {
|
|||||||
StyledRect {
|
StyledRect {
|
||||||
id: valueTooltip
|
id: valueTooltip
|
||||||
|
|
||||||
width: tooltipText.reservedWidth + Theme.spacingS * 2
|
width: tooltipText.contentWidth + Theme.spacingS * 2
|
||||||
height: tooltipText.contentHeight + Theme.spacingXS * 2
|
height: tooltipText.contentHeight + Theme.spacingXS * 2
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: Theme.surfaceContainer
|
color: Theme.surfaceContainer
|
||||||
@@ -251,22 +251,10 @@ Item {
|
|||||||
visible: slider.alwaysShowValue ? slider.showValue : ((sliderMouseArea.containsMouse && slider.showValue) || (slider.isDragging && slider.showValue))
|
visible: slider.alwaysShowValue ? slider.showValue : ((sliderMouseArea.containsMouse && slider.showValue) || (slider.isDragging && slider.showValue))
|
||||||
opacity: visible ? 1 : 0
|
opacity: visible ? 1 : 0
|
||||||
|
|
||||||
NumericText {
|
StyledText {
|
||||||
id: tooltipText
|
id: tooltipText
|
||||||
|
|
||||||
text: (slider.valueOverride >= 0 ? Math.round(slider.valueOverride) : slider.value) + slider.unit
|
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
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: Theme.surfaceText
|
color: Theme.surfaceText
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
|
|||||||
@@ -99,12 +99,10 @@ Item {
|
|||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
|
|
||||||
// M3 disabled track: on surface 12% opacity
|
color: (checked && enabled) ? Theme.primary : Theme.surfaceVariantAlpha
|
||||||
color: !toggle.enabled ? Qt.alpha(Theme.surfaceText, 0.12) : (toggle.checked ? Theme.primary : Theme.surfaceVariantAlpha)
|
opacity: toggling ? 0.6 : (enabled ? 1 : 0.4)
|
||||||
opacity: toggle.toggling ? 0.6 : 1
|
|
||||||
|
|
||||||
// M3 disabled unchecked border: on surface 12% opacity
|
border.color: (!checked || !enabled) ? Theme.outline : "transparent"
|
||||||
border.color: toggle.checked ? "transparent" : (!toggle.enabled ? Qt.alpha(Theme.surfaceText, 0.12) : Theme.outline)
|
|
||||||
|
|
||||||
readonly property int pad: Math.round((height - thumb.width) / 2)
|
readonly property int pad: Math.round((height - thumb.width) / 2)
|
||||||
readonly property int edgeLeft: pad
|
readonly property int edgeLeft: pad
|
||||||
@@ -113,18 +111,16 @@ Item {
|
|||||||
StyledRect {
|
StyledRect {
|
||||||
id: thumb
|
id: thumb
|
||||||
|
|
||||||
width: toggle.checked ? insetCircle : insetCircle - 4
|
width: (checked && enabled) ? insetCircle : insetCircle - 4
|
||||||
height: toggle.checked ? insetCircle : insetCircle - 4
|
height: (checked && enabled) ? insetCircle : insetCircle - 4
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
// M3 disabled thumb:
|
color: (checked && enabled) ? Theme.surface : Theme.outline
|
||||||
// checked = solid surface | unchecked = on surface 38%
|
border.color: (checked && enabled) ? Theme.outline : Theme.outline
|
||||||
color: !toggle.enabled ? (toggle.checked ? Theme.surface : Qt.alpha(Theme.surfaceText, 0.38)) : (toggle.checked ? Theme.surface : Theme.outline)
|
border.width: (checked && enabled) ? 1 : 2
|
||||||
border.color: !toggle.enabled ? (toggle.checked ? "transparent" : Qt.alpha(Theme.surfaceText, 0.38)) : Theme.outline
|
|
||||||
border.width: (toggle.checked && toggle.enabled) ? 1 : 2
|
|
||||||
|
|
||||||
x: toggle.checked ? toggleTrack.edgeRight : toggleTrack.edgeLeft
|
x: (checked && enabled) ? toggleTrack.edgeRight : toggleTrack.edgeLeft
|
||||||
|
|
||||||
Behavior on x {
|
Behavior on x {
|
||||||
SequentialAnimation {
|
SequentialAnimation {
|
||||||
@@ -162,11 +158,10 @@ Item {
|
|||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
name: "check"
|
name: "check"
|
||||||
size: 20
|
size: 20
|
||||||
// M3 disabled icon: on surface 38%
|
color: Theme.surfaceText
|
||||||
color: toggle.enabled ? Theme.surfaceText : Qt.alpha(Theme.surfaceText, 0.38)
|
|
||||||
filled: true
|
filled: true
|
||||||
opacity: toggle.checked ? 1 : 0
|
opacity: checked && enabled ? 1 : 0
|
||||||
scale: toggle.checked ? 1 : 0.6
|
scale: checked && enabled ? 1 : 0.6
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
|
|||||||
@@ -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"),
|
"label": I18n.tr("Auth Type"),
|
||||||
"value": data["connection-type"]
|
"value": data["connection-type"]
|
||||||
});
|
});
|
||||||
|
fields.push({
|
||||||
|
"key": "auto",
|
||||||
|
"label": I18n.tr("Autoconnect"),
|
||||||
|
"value": configData.autoconnect ? I18n.tr("Yes") : I18n.tr("No")
|
||||||
|
});
|
||||||
return fields;
|
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 {
|
Item {
|
||||||
width: 1
|
width: 1
|
||||||
height: Theme.spacingXS
|
height: Theme.spacingXS
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user