mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-05-02 18:42:06 -04:00
Compare commits
52 Commits
v1.4.3
...
997011e008
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
997011e008 | ||
|
|
2504396435 | ||
|
|
d206723b36 | ||
|
|
a0ec3d59b8 | ||
|
|
17ef08aa58 | ||
|
|
57279d1c53 | ||
|
|
8b003ac9cd | ||
|
|
0ea10b0ad2 | ||
|
|
2db4c9daa0 | ||
|
|
363964e90b | ||
|
|
a7b49eba70 | ||
|
|
4ae334f60f | ||
|
|
86c0064ff9 | ||
|
|
5a6b52f07f | ||
|
|
5aaa56853f | ||
|
|
35913c22f5 | ||
|
|
d7b560573c | ||
|
|
02a274ebe2 | ||
|
|
fc7b61c20b | ||
|
|
5880043f56 | ||
|
|
fee3b7f2a7 | ||
|
|
c0b0339fca | ||
|
|
26c1e62204 | ||
|
|
7b2d4dbe30 | ||
|
|
78c5d46c6b | ||
|
|
3fb85df504 | ||
|
|
227dd24726 | ||
|
|
ae6a656899 | ||
|
|
a4055e0f01 | ||
|
|
6d98c229ef | ||
|
|
71d93ad85e | ||
|
|
4ec21fcd3d | ||
|
|
0a2fe03fee | ||
|
|
4f4745609b | ||
|
|
a69cd515fb | ||
|
|
06c4b97a6b | ||
|
|
a6cf71a190 | ||
|
|
21750156dc | ||
|
|
f9b737f543 | ||
|
|
246b59f3b9 | ||
|
|
dcda81ea64 | ||
|
|
9909b665cd | ||
|
|
4bcd786be3 | ||
|
|
64c9222000 | ||
|
|
12acf2dd51 | ||
|
|
fea97b4aad | ||
|
|
c6d398eeac | ||
|
|
7a74be83d7 | ||
|
|
67a6427418 | ||
|
|
18b20d3225 | ||
|
|
8a76885fb6 | ||
|
|
69b1e61ab7 |
6
.github/workflows/update-vendor-hash.yml
vendored
6
.github/workflows/update-vendor-hash.yml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
echo "Build succeeded, no hash update needed"
|
||||
exit 0
|
||||
fi
|
||||
new_hash=$(echo "$output" | grep -oP "got:\s+\K\S+" | head -n1)
|
||||
new_hash=$(echo "$output" | grep -oP "got:\s+\K\S+" | head -n1 || true)
|
||||
[ -n "$new_hash" ] || { echo "Could not extract new vendorHash"; echo "$output"; exit 1; }
|
||||
current_hash=$(grep -oP 'vendorHash = "\K[^"]+' flake.nix)
|
||||
[ "$current_hash" = "$new_hash" ] && { echo "vendorHash already up to date"; exit 0; }
|
||||
@@ -59,8 +59,8 @@ jobs:
|
||||
git config user.email "dms-ci[bot]@users.noreply.github.com"
|
||||
git add flake.nix
|
||||
git commit -m "nix: update vendorHash for go.mod changes" || exit 0
|
||||
git pull --rebase origin master
|
||||
git push https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git HEAD:master
|
||||
git pull --rebase origin ${{ github.ref_name }}
|
||||
git push https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git HEAD:${{ github.ref_name }}
|
||||
else
|
||||
echo "No changes to flake.nix"
|
||||
fi
|
||||
|
||||
@@ -222,16 +222,19 @@ func init() {
|
||||
|
||||
func runClipCopy(cmd *cobra.Command, args []string) {
|
||||
var data []byte
|
||||
copyFromStdin := false
|
||||
|
||||
switch {
|
||||
case len(args) > 0:
|
||||
data = []byte(args[0])
|
||||
default:
|
||||
case clipCopyDownload || clipCopyType == "__multi__":
|
||||
var err error
|
||||
data, err = io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
log.Fatalf("read stdin: %v", err)
|
||||
}
|
||||
default:
|
||||
copyFromStdin = true
|
||||
}
|
||||
|
||||
if clipCopyDownload {
|
||||
@@ -257,6 +260,13 @@ func runClipCopy(cmd *cobra.Command, args []string) {
|
||||
return
|
||||
}
|
||||
|
||||
if copyFromStdin {
|
||||
if err := clipboard.CopyReader(os.Stdin, clipCopyType, clipCopyForeground, clipCopyPasteOnce); err != nil {
|
||||
log.Fatalf("copy: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := clipboard.CopyOpts(data, clipCopyType, clipCopyForeground, clipCopyPasteOnce); err != nil {
|
||||
log.Fatalf("copy: %v", err)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package clipboard
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/ext_data_control"
|
||||
@@ -12,17 +14,37 @@ import (
|
||||
)
|
||||
|
||||
func Copy(data []byte, mimeType string) error {
|
||||
return CopyOpts(data, mimeType, false, false)
|
||||
return CopyReader(bytes.NewReader(data), mimeType, false, false)
|
||||
}
|
||||
|
||||
func CopyOpts(data []byte, mimeType string, foreground, pasteOnce bool) error {
|
||||
if foreground {
|
||||
return copyServeWithWriter(func(writer io.Writer) error {
|
||||
total := 0
|
||||
for total < len(data) {
|
||||
n, err := writer.Write(data[total:])
|
||||
total += n
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if total != len(data) {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
return nil
|
||||
}, mimeType, pasteOnce)
|
||||
}
|
||||
return CopyReader(bytes.NewReader(data), mimeType, foreground, pasteOnce)
|
||||
}
|
||||
|
||||
func CopyReader(data io.Reader, mimeType string, foreground, pasteOnce bool) error {
|
||||
if !foreground {
|
||||
return copyFork(data, mimeType, pasteOnce)
|
||||
}
|
||||
return copyServe(data, mimeType, pasteOnce)
|
||||
return copyServeReader(data, mimeType, pasteOnce)
|
||||
}
|
||||
|
||||
func copyFork(data []byte, mimeType string, pasteOnce bool) error {
|
||||
func copyFork(data io.Reader, mimeType string, pasteOnce bool) error {
|
||||
args := []string{os.Args[0], "cl", "copy", "--foreground"}
|
||||
if pasteOnce {
|
||||
args = append(args, "--paste-once")
|
||||
@@ -30,11 +52,15 @@ func copyFork(data []byte, mimeType string, pasteOnce bool) error {
|
||||
args = append(args, "--type", mimeType)
|
||||
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Stdin = nil
|
||||
cmd.Stdout = nil
|
||||
cmd.Stderr = nil
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
||||
|
||||
if stdinSource, ok := data.(*os.File); ok {
|
||||
cmd.Stdin = stdinSource
|
||||
return cmd.Start()
|
||||
}
|
||||
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("stdin pipe: %w", err)
|
||||
@@ -44,16 +70,66 @@ func copyFork(data []byte, mimeType string, pasteOnce bool) error {
|
||||
return fmt.Errorf("start: %w", err)
|
||||
}
|
||||
|
||||
if _, err := stdin.Write(data); err != nil {
|
||||
if _, err := io.Copy(stdin, data); err != nil {
|
||||
stdin.Close()
|
||||
return fmt.Errorf("write stdin: %w", err)
|
||||
}
|
||||
stdin.Close()
|
||||
if err := stdin.Close(); err != nil {
|
||||
return fmt.Errorf("close stdin: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyServe(data []byte, mimeType string, pasteOnce bool) error {
|
||||
func copyServeReader(data io.Reader, mimeType string, pasteOnce bool) error {
|
||||
cachedData, err := createClipboardCacheFile()
|
||||
if err != nil {
|
||||
return fmt.Errorf("create clipboard cache file: %w", err)
|
||||
}
|
||||
defer os.Remove(cachedData.Name())
|
||||
|
||||
if _, err := io.Copy(cachedData, data); err != nil {
|
||||
return fmt.Errorf("cache clipboard data: %w", err)
|
||||
}
|
||||
if err := cachedData.Close(); err != nil {
|
||||
return fmt.Errorf("close temp cache file: %w", err)
|
||||
}
|
||||
|
||||
return copyServeWithWriter(func(writer io.Writer) error {
|
||||
cachedFile, err := os.Open(cachedData.Name())
|
||||
if err != nil {
|
||||
return fmt.Errorf("open temp cache file: %w", err)
|
||||
}
|
||||
defer cachedFile.Close()
|
||||
|
||||
if _, err := io.Copy(writer, cachedFile); err != nil {
|
||||
return fmt.Errorf("write clipboard data: %w", err)
|
||||
}
|
||||
return nil
|
||||
}, mimeType, pasteOnce)
|
||||
}
|
||||
|
||||
func createClipboardCacheFile() (*os.File, error) {
|
||||
preferredDirs := []string{}
|
||||
|
||||
if cacheDir, err := os.UserCacheDir(); err == nil {
|
||||
preferredDirs = append(preferredDirs, filepath.Join(cacheDir, "dms", "clipboard"))
|
||||
}
|
||||
preferredDirs = append(preferredDirs, "/var/tmp/dms/clipboard")
|
||||
|
||||
for _, dir := range preferredDirs {
|
||||
if err := os.MkdirAll(dir, 0o700); err != nil {
|
||||
continue
|
||||
}
|
||||
cachedData, err := os.CreateTemp(dir, "dms-clipboard-*")
|
||||
if err == nil {
|
||||
return cachedData, nil
|
||||
}
|
||||
}
|
||||
return os.CreateTemp("", "dms-clipboard-*")
|
||||
}
|
||||
|
||||
func copyServeWithWriter(writeTo func(io.Writer) error, mimeType string, pasteOnce bool) error {
|
||||
display, err := wlclient.Connect("")
|
||||
if err != nil {
|
||||
return fmt.Errorf("wayland connect: %w", err)
|
||||
@@ -139,12 +215,18 @@ func copyServe(data []byte, mimeType string, pasteOnce bool) error {
|
||||
|
||||
cancelled := make(chan struct{})
|
||||
pasted := make(chan struct{}, 1)
|
||||
sendErr := make(chan error, 1)
|
||||
|
||||
source.SetSendHandler(func(e ext_data_control.ExtDataControlSourceV1SendEvent) {
|
||||
defer syscall.Close(e.Fd)
|
||||
file := os.NewFile(uintptr(e.Fd), "pipe")
|
||||
defer file.Close()
|
||||
file.Write(data)
|
||||
if err := writeTo(file); err != nil {
|
||||
select {
|
||||
case sendErr <- err:
|
||||
default:
|
||||
}
|
||||
}
|
||||
select {
|
||||
case pasted <- struct{}{}:
|
||||
default:
|
||||
@@ -165,6 +247,8 @@ func copyServe(data []byte, mimeType string, pasteOnce bool) error {
|
||||
select {
|
||||
case <-cancelled:
|
||||
return nil
|
||||
case err := <-sendErr:
|
||||
return err
|
||||
case <-pasted:
|
||||
if pasteOnce {
|
||||
return nil
|
||||
|
||||
@@ -440,29 +440,10 @@ func (a *ArchDistribution) installAURPackages(ctx context.Context, packages []st
|
||||
a.log(fmt.Sprintf("Installing AUR packages manually: %s", strings.Join(packages, ", ")))
|
||||
|
||||
hasNiri := false
|
||||
hasQuickshell := false
|
||||
for _, pkg := range packages {
|
||||
if pkg == "niri-git" {
|
||||
hasNiri = true
|
||||
}
|
||||
if pkg == "quickshell" || pkg == "quickshell-git" {
|
||||
hasQuickshell = true
|
||||
}
|
||||
}
|
||||
|
||||
// If quickshell is in the list, always reinstall google-breakpad first
|
||||
if hasQuickshell {
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseAURPackages,
|
||||
Progress: 0.63,
|
||||
Step: "Reinstalling google-breakpad for quickshell...",
|
||||
IsComplete: false,
|
||||
CommandInfo: "Reinstalling prerequisite AUR package for quickshell",
|
||||
}
|
||||
|
||||
if err := a.installSingleAURPackage(ctx, "google-breakpad", sudoPassword, progressChan, 0.63, 0.65); err != nil {
|
||||
return fmt.Errorf("failed to reinstall google-breakpad prerequisite for quickshell: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// If niri is in the list, install makepkg-git-lfs-proto first if not already installed
|
||||
@@ -616,10 +597,16 @@ func (a *ArchDistribution) installSingleAURPackage(ctx context.Context, pkg, sud
|
||||
return fmt.Errorf("failed to remove optdepends from .SRCINFO for %s: %w", pkg, err)
|
||||
}
|
||||
|
||||
// Skip dependency installation for dms-shell-git and dms-shell-bin
|
||||
// since we manually manage those dependencies
|
||||
if pkg != "dms-shell-git" && pkg != "dms-shell-bin" {
|
||||
// Pre-install dependencies from .SRCINFO
|
||||
srcinfoPath = filepath.Join(packageDir, ".SRCINFO")
|
||||
if pkg == "dms-shell-bin" {
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseAURPackages,
|
||||
Progress: startProgress + 0.35*(endProgress-startProgress),
|
||||
Step: fmt.Sprintf("Skipping dependency installation for %s (manually managed)...", pkg),
|
||||
IsComplete: false,
|
||||
LogOutput: fmt.Sprintf("Dependencies for %s are installed separately", pkg),
|
||||
}
|
||||
} else {
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseAURPackages,
|
||||
Progress: startProgress + 0.3*(endProgress-startProgress),
|
||||
@@ -628,19 +615,19 @@ func (a *ArchDistribution) installSingleAURPackage(ctx context.Context, pkg, sud
|
||||
CommandInfo: "Installing package dependencies and makedepends",
|
||||
}
|
||||
|
||||
// Install dependencies and makedepends explicitly
|
||||
srcinfoPath = filepath.Join(packageDir, ".SRCINFO")
|
||||
// Install dependencies from .SRCINFO
|
||||
depFilter := ""
|
||||
if pkg == "dms-shell-git" {
|
||||
depFilter = ` | sed -E 's/[[:space:]]*(quickshell|dgop)[[:space:]]*/ /g' | tr -s ' '`
|
||||
}
|
||||
|
||||
depsCmd := exec.CommandContext(ctx, "bash", "-c",
|
||||
fmt.Sprintf(`
|
||||
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
|
||||
deps=$(grep "depends = " "%s" | grep -v "makedepends" | sed 's/.*depends = //' | tr '\n' ' ' %s | sed 's/[[:space:]]*$//')
|
||||
if [ ! -z "$deps" ] && [ "$deps" != " " ]; then
|
||||
echo '%s' | sudo -S pacman -S --needed --noconfirm $deps
|
||||
fi
|
||||
`, srcinfoPath, pkg, sudoPassword))
|
||||
`, srcinfoPath, depFilter, sudoPassword))
|
||||
|
||||
if err := a.runWithProgress(depsCmd, progressChan, PhaseAURPackages, startProgress+0.3*(endProgress-startProgress), startProgress+0.35*(endProgress-startProgress)); err != nil {
|
||||
return fmt.Errorf("FAILED to install runtime dependencies for %s: %w", pkg, err)
|
||||
@@ -657,14 +644,6 @@ func (a *ArchDistribution) installSingleAURPackage(ctx context.Context, pkg, sud
|
||||
if err := a.runWithProgress(makedepsCmd, progressChan, PhaseAURPackages, startProgress+0.35*(endProgress-startProgress), startProgress+0.4*(endProgress-startProgress)); err != nil {
|
||||
return fmt.Errorf("FAILED to install make dependencies for %s: %w", pkg, err)
|
||||
}
|
||||
} else {
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseAURPackages,
|
||||
Progress: startProgress + 0.35*(endProgress-startProgress),
|
||||
Step: fmt.Sprintf("Skipping dependency installation for %s (manually managed)...", pkg),
|
||||
IsComplete: false,
|
||||
LogOutput: fmt.Sprintf("Dependencies for %s are installed separately", pkg),
|
||||
}
|
||||
}
|
||||
|
||||
progressChan <- InstallProgressMsg{
|
||||
@@ -677,7 +656,7 @@ func (a *ArchDistribution) installSingleAURPackage(ctx context.Context, pkg, sud
|
||||
|
||||
buildCmd := exec.CommandContext(ctx, "makepkg", "--noconfirm")
|
||||
buildCmd.Dir = packageDir
|
||||
buildCmd.Env = append(os.Environ(), "PKGEXT=.pkg.tar") // Disable compression for speed
|
||||
buildCmd.Env = append(os.Environ(), "PKGEXT=.pkg.tar")
|
||||
|
||||
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)
|
||||
|
||||
@@ -71,6 +71,7 @@ var templateRegistry = []TemplateDef{
|
||||
{ID: "kcolorscheme", ConfigFile: "kcolorscheme.toml", RunUnconditionally: true},
|
||||
{ID: "vscode", Kind: TemplateKindVSCode},
|
||||
{ID: "emacs", Commands: []string{"emacs"}, ConfigFile: "emacs.toml", Kind: TemplateKindEmacs},
|
||||
{ID: "zed", Commands: []string{"zed", "zeditor", "zedit"}, ConfigFile: "zed.toml"},
|
||||
}
|
||||
|
||||
func (c *ColorMode) GTKTheme() string {
|
||||
|
||||
@@ -5,5 +5,6 @@ const (
|
||||
dbusPath = "/org/freedesktop/login1"
|
||||
dbusManagerInterface = "org.freedesktop.login1.Manager"
|
||||
dbusSessionInterface = "org.freedesktop.login1.Session"
|
||||
dbusUserInterface = "org.freedesktop.login1.User"
|
||||
dbusPropsInterface = "org.freedesktop.DBus.Properties"
|
||||
)
|
||||
|
||||
@@ -17,15 +17,8 @@ func NewManager() (*Manager, error) {
|
||||
return nil, fmt.Errorf("failed to connect to system bus: %w", err)
|
||||
}
|
||||
|
||||
sessionID := os.Getenv("XDG_SESSION_ID")
|
||||
if sessionID == "" {
|
||||
sessionID = "self"
|
||||
}
|
||||
|
||||
m := &Manager{
|
||||
state: &SessionState{
|
||||
SessionID: sessionID,
|
||||
},
|
||||
state: &SessionState{},
|
||||
stateMutex: sync.RWMutex{},
|
||||
|
||||
stopChan: make(chan struct{}),
|
||||
@@ -60,12 +53,13 @@ func (m *Manager) initialize() error {
|
||||
|
||||
m.initializeFallbackDelay()
|
||||
|
||||
sessionPath, err := m.getSession(m.state.SessionID)
|
||||
sessionID, sessionPath, err := m.discoverSession()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get session path: %w", err)
|
||||
}
|
||||
|
||||
m.stateMutex.Lock()
|
||||
m.state.SessionID = sessionID
|
||||
m.state.SessionPath = string(sessionPath)
|
||||
m.sessionPath = sessionPath
|
||||
m.stateMutex.Unlock()
|
||||
@@ -79,6 +73,41 @@ func (m *Manager) initialize() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) discoverSession() (string, dbus.ObjectPath, error) {
|
||||
// 1. Explicit XDG_SESSION_ID
|
||||
if id := os.Getenv("XDG_SESSION_ID"); id != "" {
|
||||
if path, err := m.getSession(id); err == nil {
|
||||
fmt.Fprintf(os.Stderr, "loginctl: using XDG_SESSION_ID=%s\n", id)
|
||||
return id, path, nil
|
||||
}
|
||||
}
|
||||
|
||||
// 2. PID-based lookup (works when caller is inside a session cgroup)
|
||||
if id, path, err := m.getSessionByPID(uint32(os.Getpid())); err == nil {
|
||||
fmt.Fprintf(os.Stderr, "loginctl: found session %s via PID\n", id)
|
||||
return id, path, nil
|
||||
}
|
||||
|
||||
// 3. User's primary display session (handles UWSM and similar)
|
||||
if id, path, err := m.getUserDisplaySession(); err == nil {
|
||||
fmt.Fprintf(os.Stderr, "loginctl: found session %s via User.Display\n", id)
|
||||
return id, path, nil
|
||||
}
|
||||
|
||||
// 4. Score all sessions for current UID
|
||||
if id, path, err := m.findBestSession(); err == nil {
|
||||
fmt.Fprintf(os.Stderr, "loginctl: found session %s via ListSessions scoring\n", id)
|
||||
return id, path, nil
|
||||
}
|
||||
|
||||
// 5. Last resort: "self"
|
||||
path, err := m.getSession("self")
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("%w", err)
|
||||
}
|
||||
return "self", path, nil
|
||||
}
|
||||
|
||||
func (m *Manager) getSession(id string) (dbus.ObjectPath, error) {
|
||||
var out dbus.ObjectPath
|
||||
err := m.managerObj.Call(dbusManagerInterface+".GetSession", 0, id).Store(&out)
|
||||
@@ -88,6 +117,166 @@ func (m *Manager) getSession(id string) (dbus.ObjectPath, error) {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (m *Manager) getSessionByPID(pid uint32) (string, dbus.ObjectPath, error) {
|
||||
var path dbus.ObjectPath
|
||||
if err := m.managerObj.Call(dbusManagerInterface+".GetSessionByPID", 0, pid).Store(&path); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
sessionObj := m.conn.Object(dbusDest, path)
|
||||
var id dbus.Variant
|
||||
if err := sessionObj.Call(dbusPropsInterface+".Get", 0, dbusSessionInterface, "Id").Store(&id); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return id.Value().(string), path, nil
|
||||
}
|
||||
|
||||
func (m *Manager) getUserDisplaySession() (string, dbus.ObjectPath, error) {
|
||||
uid := uint32(os.Getuid())
|
||||
|
||||
var userPath dbus.ObjectPath
|
||||
if err := m.managerObj.Call(dbusManagerInterface+".GetUser", 0, uid).Store(&userPath); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
userObj := m.conn.Object(dbusDest, userPath)
|
||||
var display dbus.Variant
|
||||
if err := userObj.Call(dbusPropsInterface+".Get", 0, dbusUserInterface, "Display").Store(&display); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
pair, ok := display.Value().([]any)
|
||||
if !ok || len(pair) < 2 {
|
||||
return "", "", fmt.Errorf("unexpected Display format")
|
||||
}
|
||||
|
||||
sessionID, _ := pair[0].(string)
|
||||
sessionPath, _ := pair[1].(dbus.ObjectPath)
|
||||
if sessionID == "" || sessionPath == "" {
|
||||
return "", "", fmt.Errorf("empty Display session")
|
||||
}
|
||||
|
||||
return sessionID, sessionPath, nil
|
||||
}
|
||||
|
||||
type sessionCandidate struct {
|
||||
id string
|
||||
path dbus.ObjectPath
|
||||
}
|
||||
|
||||
func (m *Manager) findBestSession() (string, dbus.ObjectPath, error) {
|
||||
// ListSessions returns a(susso): [][]any where each entry is [id, uid, name, seat, path]
|
||||
var raw [][]any
|
||||
if err := m.managerObj.Call(dbusManagerInterface+".ListSessions", 0).Store(&raw); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
uid := uint32(os.Getuid())
|
||||
var candidates []sessionCandidate
|
||||
for _, entry := range raw {
|
||||
if len(entry) < 5 {
|
||||
continue
|
||||
}
|
||||
entryUID, _ := entry[1].(uint32)
|
||||
if entryUID != uid {
|
||||
continue
|
||||
}
|
||||
id, _ := entry[0].(string)
|
||||
path, _ := entry[4].(dbus.ObjectPath)
|
||||
if id != "" && path != "" {
|
||||
candidates = append(candidates, sessionCandidate{id: id, path: path})
|
||||
}
|
||||
}
|
||||
if len(candidates) == 0 {
|
||||
return "", "", fmt.Errorf("no sessions for uid %d", uid)
|
||||
}
|
||||
|
||||
bestScore := -1
|
||||
var best sessionCandidate
|
||||
for _, c := range candidates {
|
||||
score := m.scoreSession(c.path)
|
||||
if score > bestScore {
|
||||
bestScore = score
|
||||
best = c
|
||||
}
|
||||
}
|
||||
if bestScore < 0 {
|
||||
return "", "", fmt.Errorf("no viable session found")
|
||||
}
|
||||
return best.id, best.path, nil
|
||||
}
|
||||
|
||||
func (m *Manager) scoreSession(path dbus.ObjectPath) int {
|
||||
obj := m.conn.Object(dbusDest, path)
|
||||
var props map[string]dbus.Variant
|
||||
if err := obj.Call(dbusPropsInterface+".GetAll", 0, dbusSessionInterface).Store(&props); err != nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
getStr := func(key string) string {
|
||||
if v, ok := props[key]; ok {
|
||||
if s, ok := v.Value().(string); ok {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
getBool := func(key string) bool {
|
||||
if v, ok := props[key]; ok {
|
||||
if b, ok := v.Value().(bool); ok {
|
||||
return b
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
getUint32 := func(key string) uint32 {
|
||||
if v, ok := props[key]; ok {
|
||||
if u, ok := v.Value().(uint32); ok {
|
||||
return u
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
class := getStr("Class")
|
||||
if class != "user" {
|
||||
return -1
|
||||
}
|
||||
if getBool("Remote") {
|
||||
return -1
|
||||
}
|
||||
|
||||
score := 0
|
||||
|
||||
if getBool("Active") {
|
||||
score += 100
|
||||
}
|
||||
|
||||
switch getStr("Type") {
|
||||
case "wayland", "x11":
|
||||
score += 80
|
||||
case "tty":
|
||||
score += 10
|
||||
}
|
||||
|
||||
if v, ok := props["Seat"]; ok {
|
||||
if seatArr, ok := v.Value().([]any); ok && len(seatArr) >= 1 {
|
||||
if seat, ok := seatArr[0].(string); ok && seat != "" {
|
||||
score += 40
|
||||
if seat == "seat0" {
|
||||
score += 10
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if getUint32("VTNr") > 0 {
|
||||
score += 20
|
||||
}
|
||||
|
||||
return score
|
||||
}
|
||||
|
||||
func (m *Manager) refreshSessionBinding() error {
|
||||
if m.managerObj == nil || m.conn == nil {
|
||||
return fmt.Errorf("manager not fully initialized")
|
||||
|
||||
@@ -2,10 +2,10 @@ package wlcontext
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"golang.org/x/sys/unix"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"time"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
@@ -123,6 +123,9 @@ func (sc *SharedContext) eventDispatcher() {
|
||||
{Fd: int32(sc.wakeR), Events: unix.POLLIN},
|
||||
}
|
||||
|
||||
consecutiveErrors := 0
|
||||
const maxConsecutiveErrors = 20
|
||||
|
||||
for {
|
||||
sc.drainCmdQueue()
|
||||
|
||||
@@ -153,9 +156,19 @@ func (sc *SharedContext) eventDispatcher() {
|
||||
}
|
||||
|
||||
if err := ctx.Dispatch(); err != nil && !os.IsTimeout(err) {
|
||||
log.Errorf("Wayland connection error: %v", err)
|
||||
return
|
||||
consecutiveErrors++
|
||||
log.Warnf("Wayland connection error (%d/%d): %v", consecutiveErrors, maxConsecutiveErrors, err)
|
||||
|
||||
if consecutiveErrors >= maxConsecutiveErrors {
|
||||
log.Errorf("Fatal: Wayland connection unrecoverable after %d attempts. Exiting dispatcher.", maxConsecutiveErrors)
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond * time.Duration(consecutiveErrors))
|
||||
continue
|
||||
}
|
||||
|
||||
consecutiveErrors = 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
139
flake.nix
139
flake.nix
@@ -17,6 +17,25 @@
|
||||
...
|
||||
}:
|
||||
let
|
||||
goModVersion =
|
||||
let
|
||||
content = builtins.readFile ./core/go.mod;
|
||||
lines = builtins.filter builtins.isString (builtins.split "\n" content);
|
||||
goLines = builtins.filter (l: builtins.match "go [0-9]+\\..*" l != null) lines;
|
||||
matched =
|
||||
if goLines != [ ] then builtins.match "go ([0-9]+)\\.([0-9]+).*" (builtins.head goLines) else null;
|
||||
in
|
||||
if matched != null then
|
||||
{
|
||||
major = builtins.elemAt matched 0;
|
||||
minor = builtins.elemAt matched 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
major = "1";
|
||||
minor = "25";
|
||||
};
|
||||
goForPkgs = pkgs: pkgs.${"go_${goModVersion.major}_${goModVersion.minor}"};
|
||||
forEachSystem =
|
||||
fn:
|
||||
nixpkgs.lib.genAttrs [ "aarch64-darwin" "aarch64-linux" "x86_64-darwin" "x86_64-linux" ] (
|
||||
@@ -72,76 +91,82 @@
|
||||
"${cleanVersion}${dateSuffix}${revSuffix}";
|
||||
in
|
||||
{
|
||||
dms-shell = pkgs.buildGoModule (
|
||||
let
|
||||
rootSrc = ./.;
|
||||
in
|
||||
dms-shell = pkgs.lib.makeOverridable (
|
||||
{
|
||||
inherit version;
|
||||
pname = "dms-shell";
|
||||
src = ./core;
|
||||
vendorHash = "sha256-dEk7IOd6aQwaxZruxQclN7TGMyb8EJOl6NBWRsoZ9HQ=";
|
||||
extraQtPackages ? [ ],
|
||||
}:
|
||||
(pkgs.buildGoModule.override { go = goForPkgs pkgs; }) (
|
||||
let
|
||||
rootSrc = ./.;
|
||||
qtPackages = (qmlPkgs pkgs) ++ extraQtPackages;
|
||||
in
|
||||
{
|
||||
inherit version;
|
||||
pname = "dms-shell";
|
||||
src = ./core;
|
||||
vendorHash = "sha256-cVUJXgzYMRSM0od1xzDVkMTdxHu3OIQX2bQ8AJbGQ1Q=";
|
||||
|
||||
subPackages = [ "cmd/dms" ];
|
||||
subPackages = [ "cmd/dms" ];
|
||||
|
||||
ldflags = [
|
||||
"-s"
|
||||
"-w"
|
||||
"-X 'main.Version=${version}'"
|
||||
];
|
||||
ldflags = [
|
||||
"-s"
|
||||
"-w"
|
||||
"-X 'main.Version=${version}'"
|
||||
];
|
||||
|
||||
nativeBuildInputs = with pkgs; [
|
||||
installShellFiles
|
||||
makeWrapper
|
||||
];
|
||||
nativeBuildInputs = with pkgs; [
|
||||
installShellFiles
|
||||
makeWrapper
|
||||
];
|
||||
|
||||
postInstall = ''
|
||||
mkdir -p $out/share/quickshell/dms
|
||||
cp -r ${rootSrc}/quickshell/. $out/share/quickshell/dms/
|
||||
postInstall = ''
|
||||
mkdir -p $out/share/quickshell/dms
|
||||
cp -r ${rootSrc}/quickshell/. $out/share/quickshell/dms/
|
||||
|
||||
chmod u+w $out/share/quickshell/dms/VERSION
|
||||
echo "${version}" > $out/share/quickshell/dms/VERSION
|
||||
chmod u+w $out/share/quickshell/dms/VERSION
|
||||
echo "${version}" > $out/share/quickshell/dms/VERSION
|
||||
|
||||
# Install desktop file and icon
|
||||
install -D ${rootSrc}/assets/dms-open.desktop \
|
||||
$out/share/applications/dms-open.desktop
|
||||
install -D ${rootSrc}/core/assets/danklogo.svg \
|
||||
$out/share/hicolor/scalable/apps/danklogo.svg
|
||||
# Install desktop file and icon
|
||||
install -D ${rootSrc}/assets/dms-open.desktop \
|
||||
$out/share/applications/dms-open.desktop
|
||||
install -D ${rootSrc}/core/assets/danklogo.svg \
|
||||
$out/share/hicolor/scalable/apps/danklogo.svg
|
||||
|
||||
wrapProgram $out/bin/dms \
|
||||
--add-flags "-c $out/share/quickshell/dms" \
|
||||
--prefix "NIXPKGS_QT6_QML_IMPORT_PATH" ":" "${mkQmlImportPath pkgs (qmlPkgs pkgs)}" \
|
||||
--prefix "QT_PLUGIN_PATH" ":" "${mkQtPluginPath pkgs (qmlPkgs pkgs)}"
|
||||
wrapProgram $out/bin/dms \
|
||||
--add-flags "-c $out/share/quickshell/dms" \
|
||||
--prefix "NIXPKGS_QT6_QML_IMPORT_PATH" ":" "${mkQmlImportPath pkgs qtPackages}" \
|
||||
--prefix "QT_PLUGIN_PATH" ":" "${mkQtPluginPath pkgs qtPackages}"
|
||||
|
||||
install -Dm644 ${rootSrc}/assets/systemd/dms.service \
|
||||
$out/lib/systemd/user/dms.service
|
||||
install -Dm644 ${rootSrc}/assets/systemd/dms.service \
|
||||
$out/lib/systemd/user/dms.service
|
||||
|
||||
substituteInPlace $out/lib/systemd/user/dms.service \
|
||||
--replace-fail /usr/bin/dms $out/bin/dms \
|
||||
--replace-fail /usr/bin/pkill ${pkgs.procps}/bin/pkill
|
||||
substituteInPlace $out/lib/systemd/user/dms.service \
|
||||
--replace-fail /usr/bin/dms $out/bin/dms \
|
||||
--replace-fail /usr/bin/pkill ${pkgs.procps}/bin/pkill
|
||||
|
||||
substituteInPlace $out/share/quickshell/dms/Modules/Greetd/assets/dms-greeter \
|
||||
--replace-fail /bin/bash ${pkgs.bashInteractive}/bin/bash
|
||||
substituteInPlace $out/share/quickshell/dms/Modules/Greetd/assets/dms-greeter \
|
||||
--replace-fail /bin/bash ${pkgs.bashInteractive}/bin/bash
|
||||
|
||||
substituteInPlace $out/share/quickshell/dms/assets/pam/fprint \
|
||||
--replace-fail pam_fprintd.so ${pkgs.fprintd}/lib/security/pam_fprintd.so
|
||||
substituteInPlace $out/share/quickshell/dms/assets/pam/fprint \
|
||||
--replace-fail pam_fprintd.so ${pkgs.fprintd}/lib/security/pam_fprintd.so
|
||||
|
||||
installShellCompletion --cmd dms \
|
||||
--bash <($out/bin/dms completion bash) \
|
||||
--fish <($out/bin/dms completion fish) \
|
||||
--zsh <($out/bin/dms completion zsh)
|
||||
'';
|
||||
installShellCompletion --cmd dms \
|
||||
--bash <($out/bin/dms completion bash) \
|
||||
--fish <($out/bin/dms completion fish) \
|
||||
--zsh <($out/bin/dms completion zsh)
|
||||
'';
|
||||
|
||||
meta = {
|
||||
description = "Desktop shell for wayland compositors built with Quickshell & GO";
|
||||
homepage = "https://danklinux.com";
|
||||
changelog = "https://github.com/AvengeMedia/DankMaterialShell/releases/tag/v${version}";
|
||||
license = pkgs.lib.licenses.mit;
|
||||
mainProgram = "dms";
|
||||
platforms = pkgs.lib.platforms.linux;
|
||||
};
|
||||
}
|
||||
);
|
||||
meta = {
|
||||
description = "Desktop shell for wayland compositors built with Quickshell & GO";
|
||||
homepage = "https://danklinux.com";
|
||||
changelog = "https://github.com/AvengeMedia/DankMaterialShell/releases/tag/v${version}";
|
||||
license = pkgs.lib.licenses.mit;
|
||||
mainProgram = "dms";
|
||||
platforms = pkgs.lib.platforms.linux;
|
||||
};
|
||||
}
|
||||
)
|
||||
) { };
|
||||
|
||||
quickshell = quickshell.packages.${system}.default;
|
||||
|
||||
@@ -181,7 +206,7 @@
|
||||
buildInputs =
|
||||
with pkgs;
|
||||
[
|
||||
go_1_25
|
||||
(goForPkgs pkgs)
|
||||
gopls
|
||||
delve
|
||||
go-tools
|
||||
|
||||
@@ -71,15 +71,40 @@ Singleton {
|
||||
return appId;
|
||||
}
|
||||
|
||||
function resolveIconPath(iconName: string): string {
|
||||
if (!iconName) return "";
|
||||
const moddedId = moddedAppId(iconName);
|
||||
if (moddedId !== iconName) {
|
||||
if (moddedId.startsWith("~") || moddedId.startsWith("/"))
|
||||
return toFileUrl(expandTilde(moddedId));
|
||||
if (moddedId.startsWith("file://"))
|
||||
return moddedId;
|
||||
return Quickshell.iconPath(moddedId, true);
|
||||
}
|
||||
return Quickshell.iconPath(iconName, true) || DesktopService.resolveIconPath(iconName);
|
||||
}
|
||||
|
||||
function resolveIconUrl(iconName: string): string {
|
||||
if (!iconName) return "";
|
||||
const moddedId = moddedAppId(iconName);
|
||||
if (moddedId !== iconName) {
|
||||
if (moddedId.startsWith("~") || moddedId.startsWith("/"))
|
||||
return toFileUrl(expandTilde(moddedId));
|
||||
if (moddedId.startsWith("file://"))
|
||||
return moddedId;
|
||||
return "image://icon/" + moddedId;
|
||||
}
|
||||
return "image://icon/" + iconName;
|
||||
}
|
||||
|
||||
function getAppIcon(appId: string, desktopEntry: var): string {
|
||||
if (appId === "org.quickshell") {
|
||||
return Qt.resolvedUrl("../assets/danklogo.svg");
|
||||
}
|
||||
|
||||
const moddedId = moddedAppId(appId);
|
||||
if (moddedId !== appId) {
|
||||
return Quickshell.iconPath(moddedId, true);
|
||||
}
|
||||
if (moddedId !== appId)
|
||||
return resolveIconPath(appId);
|
||||
|
||||
if (desktopEntry && desktopEntry.icon) {
|
||||
return Quickshell.iconPath(desktopEntry.icon, true);
|
||||
|
||||
@@ -575,14 +575,7 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
if (!newSettings[identifier]) {
|
||||
newSettings[identifier] = {
|
||||
"enabled": false,
|
||||
"mode": "interval",
|
||||
"interval": 300,
|
||||
"time": "06:00"
|
||||
};
|
||||
}
|
||||
newSettings[identifier] = getMonitorCyclingSettings(screenName);
|
||||
newSettings[identifier].enabled = enabled;
|
||||
monitorCyclingSettings = newSettings;
|
||||
saveSettings();
|
||||
@@ -613,14 +606,7 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
if (!newSettings[identifier]) {
|
||||
newSettings[identifier] = {
|
||||
"enabled": false,
|
||||
"mode": "interval",
|
||||
"interval": 300,
|
||||
"time": "06:00"
|
||||
};
|
||||
}
|
||||
newSettings[identifier] = getMonitorCyclingSettings(screenName);
|
||||
newSettings[identifier].mode = mode;
|
||||
monitorCyclingSettings = newSettings;
|
||||
saveSettings();
|
||||
@@ -651,14 +637,7 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
if (!newSettings[identifier]) {
|
||||
newSettings[identifier] = {
|
||||
"enabled": false,
|
||||
"mode": "interval",
|
||||
"interval": 300,
|
||||
"time": "06:00"
|
||||
};
|
||||
}
|
||||
newSettings[identifier] = getMonitorCyclingSettings(screenName);
|
||||
newSettings[identifier].interval = interval;
|
||||
monitorCyclingSettings = newSettings;
|
||||
saveSettings();
|
||||
@@ -689,14 +668,7 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
if (!newSettings[identifier]) {
|
||||
newSettings[identifier] = {
|
||||
"enabled": false,
|
||||
"mode": "interval",
|
||||
"interval": 300,
|
||||
"time": "06:00"
|
||||
};
|
||||
}
|
||||
newSettings[identifier] = getMonitorCyclingSettings(screenName);
|
||||
newSettings[identifier].time = time;
|
||||
monitorCyclingSettings = newSettings;
|
||||
saveSettings();
|
||||
@@ -1205,7 +1177,7 @@ Singleton {
|
||||
"time": "06:00"
|
||||
};
|
||||
var value = _findMonitorValue(monitorCyclingSettings, screenName);
|
||||
return value !== undefined ? value : defaults;
|
||||
return Object.assign({}, defaults, value !== undefined ? value : {});
|
||||
}
|
||||
|
||||
FileView {
|
||||
|
||||
@@ -437,13 +437,14 @@ Singleton {
|
||||
property bool matugenTemplateGhostty: true
|
||||
property bool matugenTemplateKitty: true
|
||||
property bool matugenTemplateFoot: true
|
||||
property bool matugenTemplateNeovim: true
|
||||
property bool matugenTemplateNeovim: false
|
||||
property bool matugenTemplateAlacritty: true
|
||||
property bool matugenTemplateWezterm: true
|
||||
property bool matugenTemplateDgop: true
|
||||
property bool matugenTemplateKcolorscheme: true
|
||||
property bool matugenTemplateVscode: true
|
||||
property bool matugenTemplateEmacs: true
|
||||
property bool matugenTemplateZed: true
|
||||
|
||||
property bool showDock: false
|
||||
property bool dockAutoHide: false
|
||||
@@ -1221,10 +1222,47 @@ Singleton {
|
||||
return JSON.stringify(Store.toJson(root), null, 2);
|
||||
}
|
||||
|
||||
function _resetPluginSettings() {
|
||||
_pluginParseError = false;
|
||||
pluginSettings = {};
|
||||
}
|
||||
|
||||
function _pluginSettingsErrorCode(error) {
|
||||
if (typeof error === "number")
|
||||
return error;
|
||||
if (error && typeof error === "object") {
|
||||
if (typeof error.code === "number")
|
||||
return error.code;
|
||||
if (typeof error.errno === "number")
|
||||
return error.errno;
|
||||
}
|
||||
|
||||
const msg = String(error || "").trim();
|
||||
if (/^\d+$/.test(msg))
|
||||
return Number(msg);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
function _isMissingPluginSettingsError(error) {
|
||||
if (_pluginSettingsErrorCode(error) === 2)
|
||||
return true;
|
||||
|
||||
const msg = String(error || "").toLowerCase();
|
||||
return msg.indexOf("file does not exist") !== -1
|
||||
|| msg.indexOf("no such file") !== -1
|
||||
|| msg.indexOf("enoent") !== -1;
|
||||
}
|
||||
|
||||
function loadPluginSettings() {
|
||||
_pluginSettingsLoading = true;
|
||||
parsePluginSettings(pluginSettingsFile.text());
|
||||
_pluginSettingsLoading = false;
|
||||
try {
|
||||
parsePluginSettings(pluginSettingsFile.text());
|
||||
} catch (e) {
|
||||
const msg = e.message || String(e);
|
||||
if (!_isMissingPluginSettingsError(e))
|
||||
console.warn("SettingsData: Failed to load plugin_settings.json. Error:", msg);
|
||||
_resetPluginSettings();
|
||||
}
|
||||
}
|
||||
|
||||
function parsePluginSettings(content) {
|
||||
@@ -2660,6 +2698,7 @@ Singleton {
|
||||
blockLoading: true
|
||||
blockWrites: true
|
||||
atomicWrites: true
|
||||
printErrors: false
|
||||
watchChanges: !isGreeterMode
|
||||
onLoaded: {
|
||||
if (!isGreeterMode) {
|
||||
@@ -2668,7 +2707,10 @@ Singleton {
|
||||
}
|
||||
onLoadFailed: error => {
|
||||
if (!isGreeterMode) {
|
||||
pluginSettings = {};
|
||||
const msg = String(error || "");
|
||||
if (!_isMissingPluginSettingsError(error))
|
||||
console.warn("SettingsData: Failed to load plugin_settings.json. Error:", msg);
|
||||
_resetPluginSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1325,7 +1325,7 @@ Singleton {
|
||||
if (typeof SettingsData !== "undefined") {
|
||||
const skipTemplates = [];
|
||||
if (!SettingsData.runDmsMatugenTemplates) {
|
||||
skipTemplates.push("gtk", "nvim", "niri", "qt5ct", "qt6ct", "firefox", "pywalfox", "zenbrowser", "vesktop", "equibop", "ghostty", "kitty", "foot", "alacritty", "wezterm", "dgop", "kcolorscheme", "vscode", "emacs");
|
||||
skipTemplates.push("gtk", "nvim", "niri", "qt5ct", "qt6ct", "firefox", "pywalfox", "zenbrowser", "vesktop", "equibop", "ghostty", "kitty", "foot", "alacritty", "wezterm", "dgop", "kcolorscheme", "vscode", "emacs", "zed");
|
||||
} else {
|
||||
if (!SettingsData.matugenTemplateGtk)
|
||||
skipTemplates.push("gtk");
|
||||
@@ -1369,6 +1369,8 @@ Singleton {
|
||||
skipTemplates.push("vscode");
|
||||
if (!SettingsData.matugenTemplateEmacs)
|
||||
skipTemplates.push("emacs");
|
||||
if (!SettingsData.matugenTemplateZed)
|
||||
skipTemplates.push("zed");
|
||||
}
|
||||
if (skipTemplates.length > 0) {
|
||||
args.push("--skip-templates", skipTemplates.join(","));
|
||||
|
||||
@@ -262,12 +262,13 @@ var SPEC = {
|
||||
matugenTemplateKitty: { def: true },
|
||||
matugenTemplateFoot: { def: true },
|
||||
matugenTemplateAlacritty: { def: true },
|
||||
matugenTemplateNeovim: { def: true },
|
||||
matugenTemplateNeovim: { def: false },
|
||||
matugenTemplateWezterm: { def: true },
|
||||
matugenTemplateDgop: { def: true },
|
||||
matugenTemplateKcolorscheme: { def: true },
|
||||
matugenTemplateVscode: { def: true },
|
||||
matugenTemplateEmacs: { def: true },
|
||||
matugenTemplateZed: { def: true },
|
||||
|
||||
showDock: { def: false },
|
||||
dockAutoHide: { def: false },
|
||||
|
||||
@@ -154,18 +154,18 @@ Item {
|
||||
|
||||
property string _barLayoutStateJson: {
|
||||
const configs = SettingsData.barConfigs;
|
||||
const mapped = configs.map(c => ({
|
||||
const mapped = configs.map((c, i) => ({
|
||||
id: c.id,
|
||||
position: c.position,
|
||||
autoHide: c.autoHide,
|
||||
visible: c.visible
|
||||
visible: c.visible,
|
||||
_origIndex: i
|
||||
})).sort((a, b) => {
|
||||
const aVertical = a.position === SettingsData.Position.Left || a.position === SettingsData.Position.Right;
|
||||
const bVertical = b.position === SettingsData.Position.Left || b.position === SettingsData.Position.Right;
|
||||
if (aVertical !== bVertical) {
|
||||
if (aVertical !== bVertical)
|
||||
return aVertical - bVertical;
|
||||
}
|
||||
return String(a.id).localeCompare(String(b.id));
|
||||
return a._origIndex - b._origIndex;
|
||||
});
|
||||
return JSON.stringify(mapped);
|
||||
}
|
||||
|
||||
@@ -21,11 +21,37 @@ Item {
|
||||
required property var workspaceRenameModalLoader
|
||||
required property var windowRuleModalLoader
|
||||
|
||||
function getFirstBar() {
|
||||
function getPreferredBar(refPropertyName) {
|
||||
if (!root.dankBarRepeater || root.dankBarRepeater.count === 0)
|
||||
return null;
|
||||
const firstLoader = root.dankBarRepeater.itemAt(0);
|
||||
return firstLoader ? firstLoader.item : null;
|
||||
|
||||
const focusedScreenName = BarWidgetService.getFocusedScreenName();
|
||||
|
||||
const loaders = Array.from({
|
||||
length: root.dankBarRepeater.count
|
||||
}, (_, i) => root.dankBarRepeater.itemAt(i));
|
||||
|
||||
let currentBar = null;
|
||||
|
||||
for (const loader of loaders) {
|
||||
const instances = loader?.item?.barVariants?.instances || [];
|
||||
for (const bar of instances) {
|
||||
if (!bar)
|
||||
continue;
|
||||
|
||||
const onFocusedScreen = focusedScreenName && bar.modelData?.name === focusedScreenName;
|
||||
const hasRef = !refPropertyName || !!bar[refPropertyName];
|
||||
|
||||
if (hasRef) {
|
||||
currentBar = bar;
|
||||
|
||||
if (onFocusedScreen)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return currentBar;
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
@@ -97,9 +123,9 @@ Item {
|
||||
|
||||
IpcHandler {
|
||||
function open(): string {
|
||||
const bar = root.getFirstBar();
|
||||
const bar = root.getPreferredBar("controlCenterButtonRef");
|
||||
if (bar) {
|
||||
bar.triggerControlCenterOnFocusedScreen();
|
||||
bar.triggerControlCenter();
|
||||
return "CONTROL_CENTER_OPEN_SUCCESS";
|
||||
}
|
||||
return "CONTROL_CENTER_OPEN_FAILED";
|
||||
@@ -114,9 +140,14 @@ Item {
|
||||
}
|
||||
|
||||
function toggle(): string {
|
||||
const bar = root.getFirstBar();
|
||||
if (root.controlCenterLoader.item?.shouldBeVisible) {
|
||||
root.controlCenterLoader.item.close();
|
||||
return "CONTROL_CENTER_TOGGLE_SUCCESS";
|
||||
}
|
||||
|
||||
const bar = root.getPreferredBar("controlCenterButtonRef");
|
||||
if (bar) {
|
||||
bar.triggerControlCenterOnFocusedScreen();
|
||||
bar.triggerControlCenter();
|
||||
return "CONTROL_CENTER_TOGGLE_SUCCESS";
|
||||
}
|
||||
return "CONTROL_CENTER_TOGGLE_FAILED";
|
||||
@@ -131,27 +162,37 @@ Item {
|
||||
|
||||
IpcHandler {
|
||||
function open(tab: string): string {
|
||||
root.dankDashPopoutLoader.active = true;
|
||||
if (root.dankDashPopoutLoader.item) {
|
||||
switch (tab.toLowerCase()) {
|
||||
case "media":
|
||||
root.dankDashPopoutLoader.item.currentTabIndex = 1;
|
||||
break;
|
||||
case "wallpaper":
|
||||
root.dankDashPopoutLoader.item.currentTabIndex = 2;
|
||||
break;
|
||||
case "weather":
|
||||
root.dankDashPopoutLoader.item.currentTabIndex = SettingsData.weatherEnabled ? 3 : 0;
|
||||
break;
|
||||
default:
|
||||
root.dankDashPopoutLoader.item.currentTabIndex = 0;
|
||||
break;
|
||||
}
|
||||
root.dankDashPopoutLoader.item.setTriggerPosition(Screen.width / 2, Theme.barHeight + Theme.spacingS, 100, "center", Screen);
|
||||
root.dankDashPopoutLoader.item.dashVisible = true;
|
||||
return "DASH_OPEN_SUCCESS";
|
||||
const bar = root.getPreferredBar("clockButtonRef");
|
||||
if (!bar)
|
||||
return "DASH_OPEN_FAILED";
|
||||
|
||||
const dash = root.dankDashPopoutLoader.item;
|
||||
const onSameScreen = dash && dash.shouldBeVisible && dash.triggerScreen?.name === bar.screen?.name;
|
||||
|
||||
if (!onSameScreen) {
|
||||
bar.triggerWallpaperBrowser();
|
||||
}
|
||||
return "DASH_OPEN_FAILED";
|
||||
|
||||
if (!root.dankDashPopoutLoader.item)
|
||||
return "DASH_OPEN_FAILED";
|
||||
|
||||
switch (tab.toLowerCase()) {
|
||||
case "media":
|
||||
root.dankDashPopoutLoader.item.currentTabIndex = 1;
|
||||
break;
|
||||
case "wallpaper":
|
||||
root.dankDashPopoutLoader.item.currentTabIndex = 2;
|
||||
break;
|
||||
case "weather":
|
||||
root.dankDashPopoutLoader.item.currentTabIndex = SettingsData.weatherEnabled ? 3 : 0;
|
||||
break;
|
||||
default:
|
||||
root.dankDashPopoutLoader.item.currentTabIndex = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
root.dankDashPopoutLoader.item.dashVisible = true;
|
||||
return "DASH_OPEN_SUCCESS";
|
||||
}
|
||||
|
||||
function close(): string {
|
||||
@@ -163,8 +204,14 @@ Item {
|
||||
}
|
||||
|
||||
function toggle(tab: string): string {
|
||||
const bar = root.getFirstBar();
|
||||
if (bar && bar.triggerWallpaperBrowserOnFocusedScreen()) {
|
||||
if (root.dankDashPopoutLoader.item?.dashVisible) {
|
||||
root.dankDashPopoutLoader.item.dashVisible = false;
|
||||
return "DASH_TOGGLE_SUCCESS";
|
||||
}
|
||||
|
||||
const bar = root.getPreferredBar("clockButtonRef");
|
||||
if (bar) {
|
||||
bar.triggerWallpaperBrowser();
|
||||
if (root.dankDashPopoutLoader.item) {
|
||||
switch (tab.toLowerCase()) {
|
||||
case "media":
|
||||
@@ -521,8 +568,9 @@ Item {
|
||||
|
||||
IpcHandler {
|
||||
function wallpaper(): string {
|
||||
const bar = root.getFirstBar();
|
||||
if (bar && bar.triggerWallpaperBrowserOnFocusedScreen()) {
|
||||
const bar = root.getPreferredBar("clockButtonRef");
|
||||
if (bar) {
|
||||
bar.triggerWallpaperBrowser();
|
||||
return "SUCCESS: Toggled wallpaper browser";
|
||||
}
|
||||
return "ERROR: Failed to toggle wallpaper browser";
|
||||
|
||||
@@ -875,9 +875,7 @@ Item {
|
||||
_applyHighlights(newSections, searchQuery);
|
||||
flatModel = Scorer.flattenSections(newSections);
|
||||
sections = newSections;
|
||||
if (selectedFlatIndex >= flatModel.length) {
|
||||
selectedFlatIndex = getFirstItemIndex();
|
||||
}
|
||||
selectedFlatIndex = getFirstItemIndex();
|
||||
updateSelectedItem();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -643,7 +643,7 @@ FocusScope {
|
||||
Image {
|
||||
width: 40
|
||||
height: 40
|
||||
source: editingApp?.icon ? "image://icon/" + editingApp.icon : "image://icon/application-x-executable"
|
||||
source: Paths.resolveIconUrl(editingApp?.icon || "application-x-executable")
|
||||
sourceSize.width: 40
|
||||
sourceSize.height: 40
|
||||
fillMode: Image.PreserveAspectFit
|
||||
|
||||
@@ -460,7 +460,7 @@ Item {
|
||||
switch (mode) {
|
||||
case "files":
|
||||
if (!DSearchService.dsearchAvailable)
|
||||
return I18n.tr("File search requires dsearch\nInstall from github.com/morelazers/dsearch");
|
||||
return I18n.tr("File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch");
|
||||
if (!hasQuery)
|
||||
return I18n.tr("Type to search files");
|
||||
if (root.controller.searchQuery.length < 2)
|
||||
|
||||
@@ -8,6 +8,9 @@ DankPopout {
|
||||
|
||||
layerNamespace: "dms:app-launcher"
|
||||
|
||||
readonly property real screenWidth: screen?.width ?? 1920
|
||||
readonly property real screenHeight: screen?.height ?? 1080
|
||||
|
||||
property string _pendingMode: ""
|
||||
property string _pendingQuery: ""
|
||||
|
||||
@@ -41,8 +44,35 @@ DankPopout {
|
||||
openWithQuery(query);
|
||||
}
|
||||
|
||||
popupWidth: 560
|
||||
popupHeight: 640
|
||||
readonly property int _baseWidth: {
|
||||
switch (SettingsData.dankLauncherV2Size) {
|
||||
case "micro":
|
||||
return 500;
|
||||
case "medium":
|
||||
return 720;
|
||||
case "large":
|
||||
return 860;
|
||||
default:
|
||||
return 620;
|
||||
}
|
||||
}
|
||||
|
||||
readonly property int _baseHeight: {
|
||||
switch (SettingsData.dankLauncherV2Size) {
|
||||
case "micro":
|
||||
return 480;
|
||||
case "medium":
|
||||
return 720;
|
||||
case "large":
|
||||
return 860;
|
||||
default:
|
||||
return 600;
|
||||
}
|
||||
}
|
||||
|
||||
popupWidth: Math.min(_baseWidth, screenWidth - 100)
|
||||
popupHeight: Math.min(_baseHeight, screenHeight - 100)
|
||||
|
||||
triggerWidth: 40
|
||||
positioning: ""
|
||||
contentHandlesKeys: contentLoader.item?.launcherContent?.editMode ?? false
|
||||
|
||||
@@ -87,10 +87,6 @@ Variants {
|
||||
Component.onCompleted: {
|
||||
if (typeof blurWallpaperWindow.updatesEnabled !== "undefined")
|
||||
blurWallpaperWindow.updatesEnabled = Qt.binding(() => root.effectActive || root._renderSettling || currentWallpaper.status === Image.Loading || nextWallpaper.status === Image.Loading);
|
||||
|
||||
if (!source) {
|
||||
root._renderSettling = false;
|
||||
}
|
||||
isInitialized = true;
|
||||
}
|
||||
|
||||
@@ -113,7 +109,7 @@ Variants {
|
||||
|
||||
Timer {
|
||||
id: renderSettleTimer
|
||||
interval: 100
|
||||
interval: 1000
|
||||
onTriggered: root._renderSettling = false
|
||||
}
|
||||
|
||||
|
||||
@@ -552,8 +552,9 @@ PanelWindow {
|
||||
readonly property var _leftSection: topBarContent ? (barWindow.isVertical ? topBarContent.vLeftSection : topBarContent.hLeftSection) : null
|
||||
readonly property var _centerSection: topBarContent ? (barWindow.isVertical ? topBarContent.vCenterSection : topBarContent.hCenterSection) : null
|
||||
readonly property var _rightSection: topBarContent ? (barWindow.isVertical ? topBarContent.vRightSection : topBarContent.hRightSection) : null
|
||||
readonly property real _revealProgress: topBarSlide.x + topBarSlide.y
|
||||
|
||||
function sectionRect(section, isCenter) {
|
||||
function sectionRect(section, isCenter, _dep) {
|
||||
if (!section)
|
||||
return {
|
||||
"x": 0,
|
||||
@@ -582,7 +583,7 @@ PanelWindow {
|
||||
item: clickThroughEnabled ? null : inputMask
|
||||
|
||||
Region {
|
||||
readonly property var r: barWindow.clickThroughEnabled ? barWindow.sectionRect(barWindow._leftSection, false) : {
|
||||
readonly property var r: barWindow.clickThroughEnabled ? barWindow.sectionRect(barWindow._leftSection, false, barWindow._revealProgress) : {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 0,
|
||||
@@ -595,7 +596,7 @@ PanelWindow {
|
||||
}
|
||||
|
||||
Region {
|
||||
readonly property var r: barWindow.clickThroughEnabled ? barWindow.sectionRect(barWindow._centerSection, true) : {
|
||||
readonly property var r: barWindow.clickThroughEnabled ? barWindow.sectionRect(barWindow._centerSection, true, barWindow._revealProgress) : {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 0,
|
||||
@@ -608,7 +609,7 @@ PanelWindow {
|
||||
}
|
||||
|
||||
Region {
|
||||
readonly property var r: barWindow.clickThroughEnabled ? barWindow.sectionRect(barWindow._rightSection, false) : {
|
||||
readonly property var r: barWindow.clickThroughEnabled ? barWindow.sectionRect(barWindow._rightSection, false, barWindow._revealProgress) : {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 0,
|
||||
@@ -619,6 +620,14 @@ PanelWindow {
|
||||
width: r.w
|
||||
height: r.h
|
||||
}
|
||||
|
||||
Region {
|
||||
readonly property bool active: barWindow.clickThroughEnabled && !inputMask.showing
|
||||
x: active ? inputMask.x : 0
|
||||
y: active ? inputMask.y : 0
|
||||
width: active ? inputMask.width : 0
|
||||
height: active ? inputMask.height : 0
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
@@ -631,7 +640,7 @@ PanelWindow {
|
||||
|
||||
Timer {
|
||||
id: revealHold
|
||||
interval: barConfig?.autoHideDelay ?? 250
|
||||
interval: barWindow.clickThroughEnabled ? Math.max((barConfig?.autoHideDelay ?? 250) * 6, 1500) : (barConfig?.autoHideDelay ?? 250)
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (!topBarMouseArea.containsMouse && !topBarCore.hasActivePopout) {
|
||||
@@ -689,7 +698,6 @@ PanelWindow {
|
||||
Connections {
|
||||
function onBarConfigChanged() {
|
||||
topBarCore.autoHide = barConfig?.autoHide ?? false;
|
||||
revealHold.interval = barConfig?.autoHideDelay ?? 250;
|
||||
}
|
||||
|
||||
target: rootWindow
|
||||
|
||||
@@ -273,7 +273,7 @@ PanelWindow {
|
||||
|
||||
IconImage {
|
||||
anchors.fill: parent
|
||||
source: modelData.icon ? Quickshell.iconPath(modelData.icon, true) : ""
|
||||
source: modelData.icon ? Paths.resolveIconPath(modelData.icon) : ""
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
visible: status === Image.Ready
|
||||
|
||||
@@ -41,6 +41,12 @@ BasePill {
|
||||
return `${id}::${tooltipTitle}`;
|
||||
}
|
||||
|
||||
// ! TODO - replace with either native dbus client (like plugins use) or just a DMS cli or something
|
||||
function callContextMenuFallback(trayItemId, globalX, globalY) {
|
||||
const script = ['ITEMS=$(dbus-send --session --print-reply --dest=org.kde.StatusNotifierWatcher /StatusNotifierWatcher org.freedesktop.DBus.Properties.Get string:org.kde.StatusNotifierWatcher string:RegisteredStatusNotifierItems 2>/dev/null)', 'while IFS= read -r line; do', ' line="${line#*\\\"}"', ' line="${line%\\\"*}"', ' [ -z "$line" ] && continue', ' BUS="${line%%/*}"', ' OBJ="/${line#*/}"', ' ID=$(dbus-send --session --print-reply --dest="$BUS" "$OBJ" org.freedesktop.DBus.Properties.Get string:org.kde.StatusNotifierItem string:Id 2>/dev/null | grep -oP "(?<=\\\")(.*?)(?=\\\")" | tail -1)', ' if [ "$ID" = "$1" ]; then', ' dbus-send --session --type=method_call --dest="$BUS" "$OBJ" org.kde.StatusNotifierItem.ContextMenu int32:"$2" int32:"$3"', ' exit 0', ' fi', 'done <<< "$ITEMS"',].join("\n");
|
||||
Quickshell.execDetached(["bash", "-c", script, "_", trayItemId, String(globalX), String(globalY)]);
|
||||
}
|
||||
|
||||
property int _trayOrderTrigger: 0
|
||||
|
||||
Connections {
|
||||
@@ -380,8 +386,11 @@ BasePill {
|
||||
return;
|
||||
if (mouse.button !== Qt.RightButton)
|
||||
return;
|
||||
if (!delegateRoot.trayItem?.hasMenu)
|
||||
if (!delegateRoot.trayItem?.hasMenu) {
|
||||
const gp = trayItemArea.mapToGlobal(mouse.x, mouse.y);
|
||||
root.callContextMenuFallback(delegateRoot.trayItem.id, Math.round(gp.x), Math.round(gp.y));
|
||||
return;
|
||||
}
|
||||
root.menuOpen = false;
|
||||
root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
|
||||
}
|
||||
@@ -637,8 +646,11 @@ BasePill {
|
||||
return;
|
||||
if (mouse.button !== Qt.RightButton)
|
||||
return;
|
||||
if (!delegateRoot.trayItem?.hasMenu)
|
||||
if (!delegateRoot.trayItem?.hasMenu) {
|
||||
const gp = trayItemArea.mapToGlobal(mouse.x, mouse.y);
|
||||
root.callContextMenuFallback(delegateRoot.trayItem.id, Math.round(gp.x), Math.round(gp.y));
|
||||
return;
|
||||
}
|
||||
root.menuOpen = false;
|
||||
root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
|
||||
}
|
||||
@@ -1065,9 +1077,11 @@ BasePill {
|
||||
root.menuOpen = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!trayItem.hasMenu)
|
||||
if (!trayItem.hasMenu) {
|
||||
const gp = itemArea.mapToGlobal(mouse.x, mouse.y);
|
||||
root.callContextMenuFallback(trayItem.id, Math.round(gp.x), Math.round(gp.y));
|
||||
return;
|
||||
}
|
||||
root.showForTrayItem(trayItem, menuContainer, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,9 +447,8 @@ Variants {
|
||||
|
||||
height: {
|
||||
if (dock.isVertical) {
|
||||
if (!dock.reveal)
|
||||
return Math.min(Math.max(dockBackground.height + 64, 200), screenHeight * 0.5);
|
||||
return Math.min(dockBackground.height + 8 + dock.borderThickness, maxDockHeight);
|
||||
// Keep the taller hit area regardless of the reveal state to prevent shrinking loop
|
||||
return Math.min(Math.max(dockBackground.height + 64, 200), screenHeight * 0.5);
|
||||
}
|
||||
return dock.reveal ? px(dock.effectiveBarHeight + SettingsData.dockSpacing + SettingsData.dockBottomGap + SettingsData.dockMargin) : 1;
|
||||
}
|
||||
@@ -457,8 +456,7 @@ Variants {
|
||||
if (dock.isVertical) {
|
||||
return dock.reveal ? px(dock.effectiveBarHeight + SettingsData.dockSpacing + SettingsData.dockBottomGap + SettingsData.dockMargin) : 1;
|
||||
}
|
||||
if (!dock.reveal)
|
||||
return Math.min(Math.max(dockBackground.width + 64, 200), screenWidth * 0.5);
|
||||
// Keep the wider hit area regardless of the reveal state to prevent shrinking loop
|
||||
return Math.min(dockBackground.width + 8 + dock.borderThickness, maxDockWidth);
|
||||
}
|
||||
anchors {
|
||||
|
||||
@@ -329,7 +329,7 @@ PanelWindow {
|
||||
|
||||
IconImage {
|
||||
anchors.fill: parent
|
||||
source: modelData.icon ? Quickshell.iconPath(modelData.icon, true) : ""
|
||||
source: modelData.icon ? Paths.resolveIconPath(modelData.icon) : ""
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
visible: status === Image.Ready
|
||||
|
||||
@@ -41,6 +41,11 @@ Singleton {
|
||||
property string lockDateFormat: ""
|
||||
property bool lockScreenShowPowerActions: true
|
||||
property bool lockScreenShowProfileImage: true
|
||||
property bool powerActionConfirm: true
|
||||
property real powerActionHoldDuration: 0.5
|
||||
property var powerMenuActions: ["reboot", "logout", "poweroff", "lock", "suspend", "restart"]
|
||||
property string powerMenuDefaultAction: "logout"
|
||||
property bool powerMenuGridLayout: false
|
||||
property var screenPreferences: ({})
|
||||
property int animationSpeed: 2
|
||||
property string wallpaperFillMode: "Fill"
|
||||
@@ -75,6 +80,11 @@ Singleton {
|
||||
lockDateFormat = settings.lockDateFormat !== undefined ? settings.lockDateFormat : "";
|
||||
lockScreenShowPowerActions = settings.lockScreenShowPowerActions !== undefined ? settings.lockScreenShowPowerActions : true;
|
||||
lockScreenShowProfileImage = settings.lockScreenShowProfileImage !== undefined ? settings.lockScreenShowProfileImage : true;
|
||||
powerActionConfirm = settings.powerActionConfirm !== undefined ? settings.powerActionConfirm : true;
|
||||
powerActionHoldDuration = settings.powerActionHoldDuration !== undefined ? settings.powerActionHoldDuration : 0.5;
|
||||
powerMenuActions = settings.powerMenuActions !== undefined ? settings.powerMenuActions : ["reboot", "logout", "poweroff", "lock", "suspend", "restart"];
|
||||
powerMenuDefaultAction = settings.powerMenuDefaultAction !== undefined ? settings.powerMenuDefaultAction : "logout";
|
||||
powerMenuGridLayout = settings.powerMenuGridLayout !== undefined ? settings.powerMenuGridLayout : false;
|
||||
screenPreferences = settings.screenPreferences !== undefined ? settings.screenPreferences : ({});
|
||||
animationSpeed = settings.animationSpeed !== undefined ? settings.animationSpeed : 2;
|
||||
wallpaperFillMode = settings.wallpaperFillMode !== undefined ? settings.wallpaperFillMode : "Fill";
|
||||
|
||||
@@ -1231,6 +1231,12 @@ Item {
|
||||
LockPowerMenu {
|
||||
id: powerMenu
|
||||
showLogout: false
|
||||
powerActionConfirmOverride: GreetdSettings.powerActionConfirm
|
||||
powerActionHoldDurationOverride: GreetdSettings.powerActionHoldDuration
|
||||
powerMenuActionsOverride: GreetdSettings.powerMenuActions
|
||||
powerMenuDefaultActionOverride: GreetdSettings.powerMenuDefaultAction
|
||||
powerMenuGridLayoutOverride: GreetdSettings.powerMenuGridLayout
|
||||
requiredActions: ["poweroff"]
|
||||
onClosed: {
|
||||
if (isPrimaryScreen && inputField && inputField.forceActiveFocus) {
|
||||
Qt.callLater(() => inputField.forceActiveFocus());
|
||||
|
||||
@@ -24,13 +24,20 @@ Rectangle {
|
||||
property real holdProgress: 0
|
||||
property bool showHoldHint: false
|
||||
|
||||
readonly property bool needsConfirmation: SettingsData.powerActionConfirm
|
||||
readonly property int holdDurationMs: SettingsData.powerActionHoldDuration * 1000
|
||||
property var powerActionConfirmOverride: undefined
|
||||
property var powerActionHoldDurationOverride: undefined
|
||||
property var powerMenuActionsOverride: undefined
|
||||
property var powerMenuDefaultActionOverride: undefined
|
||||
property var powerMenuGridLayoutOverride: undefined
|
||||
property var requiredActions: []
|
||||
|
||||
readonly property bool needsConfirmation: powerActionConfirmOverride !== undefined ? powerActionConfirmOverride : SettingsData.powerActionConfirm
|
||||
readonly property int holdDurationMs: (powerActionHoldDurationOverride !== undefined ? powerActionHoldDurationOverride : SettingsData.powerActionHoldDuration) * 1000
|
||||
|
||||
signal closed
|
||||
|
||||
function updateVisibleActions() {
|
||||
const allActions = (typeof SettingsData !== "undefined" && SettingsData.powerMenuActions) ? SettingsData.powerMenuActions : ["logout", "suspend", "hibernate", "reboot", "poweroff"];
|
||||
const allActions = powerMenuActionsOverride !== undefined ? powerMenuActionsOverride : ((typeof SettingsData !== "undefined" && SettingsData.powerMenuActions) ? SettingsData.powerMenuActions : ["logout", "suspend", "hibernate", "reboot", "poweroff"]);
|
||||
const hibernateSupported = (typeof SessionService !== "undefined" && SessionService.hibernateSupported) || false;
|
||||
let filtered = allActions.filter(action => {
|
||||
if (action === "hibernate" && !hibernateSupported)
|
||||
@@ -44,9 +51,14 @@ Rectangle {
|
||||
return true;
|
||||
});
|
||||
|
||||
for (const action of requiredActions) {
|
||||
if (!filtered.includes(action))
|
||||
filtered.push(action);
|
||||
}
|
||||
|
||||
visibleActions = filtered;
|
||||
|
||||
useGridLayout = (typeof SettingsData !== "undefined" && SettingsData.powerMenuGridLayout !== undefined) ? SettingsData.powerMenuGridLayout : false;
|
||||
useGridLayout = powerMenuGridLayoutOverride !== undefined ? powerMenuGridLayoutOverride : ((typeof SettingsData !== "undefined" && SettingsData.powerMenuGridLayout !== undefined) ? SettingsData.powerMenuGridLayout : false);
|
||||
if (!useGridLayout)
|
||||
return;
|
||||
const count = visibleActions.length;
|
||||
@@ -73,7 +85,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
function getDefaultActionIndex() {
|
||||
const defaultAction = (typeof SettingsData !== "undefined" && SettingsData.powerMenuDefaultAction) ? SettingsData.powerMenuDefaultAction : "suspend";
|
||||
const defaultAction = powerMenuDefaultActionOverride !== undefined ? powerMenuDefaultActionOverride : ((typeof SettingsData !== "undefined" && SettingsData.powerMenuDefaultAction) ? SettingsData.powerMenuDefaultAction : "suspend");
|
||||
const index = visibleActions.indexOf(defaultAction);
|
||||
return index >= 0 ? index : 0;
|
||||
}
|
||||
@@ -780,8 +792,9 @@ Rectangle {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
readonly property real totalMs: SettingsData.powerActionHoldDuration * 1000
|
||||
readonly property real totalMs: root.holdDurationMs
|
||||
readonly property int remainingMs: Math.ceil(totalMs * (1 - root.holdProgress))
|
||||
readonly property real durationSec: root.holdDurationMs / 1000
|
||||
text: {
|
||||
if (root.showHoldHint)
|
||||
return I18n.tr("Hold longer to confirm");
|
||||
@@ -792,7 +805,7 @@ Rectangle {
|
||||
}
|
||||
if (totalMs < 1000)
|
||||
return I18n.tr("Hold to confirm (%1 ms)").arg(totalMs);
|
||||
return I18n.tr("Hold to confirm (%1s)").arg(SettingsData.powerActionHoldDuration);
|
||||
return I18n.tr("Hold to confirm (%1s)").arg(durationSec);
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: root.showHoldHint ? Theme.warning : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
|
||||
|
||||
@@ -122,12 +122,12 @@ Rectangle {
|
||||
return "";
|
||||
const appIcon = historyItem.appIcon;
|
||||
if (!appIcon)
|
||||
return iconFromImage ? "image://icon/" + iconFromImage : "";
|
||||
return iconFromImage ? Paths.resolveIconUrl(iconFromImage) : "";
|
||||
if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://") || appIcon.includes("/"))
|
||||
return appIcon;
|
||||
if (appIcon.startsWith("material:") || appIcon.startsWith("svg:") || appIcon.startsWith("unicode:") || appIcon.startsWith("image:"))
|
||||
return "";
|
||||
return Quickshell.iconPath(appIcon, true);
|
||||
return Paths.resolveIconPath(appIcon);
|
||||
}
|
||||
|
||||
hasImage: hasNotificationImage
|
||||
|
||||
@@ -169,12 +169,12 @@ Rectangle {
|
||||
return "";
|
||||
const appIcon = notificationGroup?.latestNotification?.appIcon;
|
||||
if (!appIcon)
|
||||
return iconFromImage ? "image://icon/" + iconFromImage : "";
|
||||
return iconFromImage ? Paths.resolveIconUrl(iconFromImage) : "";
|
||||
if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://") || appIcon.includes("/"))
|
||||
return appIcon;
|
||||
if (appIcon.startsWith("material:") || appIcon.startsWith("svg:") || appIcon.startsWith("unicode:") || appIcon.startsWith("image:"))
|
||||
return "";
|
||||
return Quickshell.iconPath(appIcon, true);
|
||||
return Paths.resolveIconPath(appIcon);
|
||||
}
|
||||
|
||||
hasImage: hasNotificationImage
|
||||
@@ -503,12 +503,12 @@ Rectangle {
|
||||
return "";
|
||||
const appIcon = modelData?.appIcon;
|
||||
if (!appIcon)
|
||||
return iconFromImage ? "image://icon/" + iconFromImage : "";
|
||||
return iconFromImage ? Paths.resolveIconUrl(iconFromImage) : "";
|
||||
if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://") || appIcon.includes("/"))
|
||||
return appIcon;
|
||||
if (appIcon.startsWith("material:") || appIcon.startsWith("svg:") || appIcon.startsWith("unicode:") || appIcon.startsWith("image:"))
|
||||
return "";
|
||||
return Quickshell.iconPath(appIcon, true);
|
||||
return Paths.resolveIconPath(appIcon);
|
||||
}
|
||||
|
||||
fallbackIcon: {
|
||||
|
||||
@@ -479,12 +479,12 @@ PanelWindow {
|
||||
return "";
|
||||
const appIcon = notificationData.appIcon;
|
||||
if (!appIcon)
|
||||
return iconFromImage ? "image://icon/" + iconFromImage : "";
|
||||
return iconFromImage ? Paths.resolveIconUrl(iconFromImage) : "";
|
||||
if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://") || appIcon.includes("/"))
|
||||
return appIcon;
|
||||
if (appIcon.startsWith("material:") || appIcon.startsWith("svg:") || appIcon.startsWith("unicode:") || appIcon.startsWith("image:"))
|
||||
return "";
|
||||
return Quickshell.iconPath(appIcon, true);
|
||||
return Paths.resolveIconPath(appIcon);
|
||||
}
|
||||
|
||||
hasImage: hasNotificationImage
|
||||
|
||||
@@ -27,11 +27,11 @@ DankOSD {
|
||||
let icon = "music_note";
|
||||
switch (player.playbackState) {
|
||||
case MprisPlaybackState.Playing:
|
||||
icon = "play_arrow";
|
||||
icon = "pause";
|
||||
break;
|
||||
case MprisPlaybackState.Paused:
|
||||
case MprisPlaybackState.Stopped:
|
||||
icon = "pause";
|
||||
icon = "play_arrow";
|
||||
break;
|
||||
}
|
||||
if (icon === _displayIcon)
|
||||
|
||||
@@ -351,6 +351,7 @@ Item {
|
||||
Loader {
|
||||
id: contentLoader
|
||||
anchors.fill: parent
|
||||
active: root.widgetEnabled && root.activeComponent !== null
|
||||
sourceComponent: root.activeComponent
|
||||
|
||||
function reloadComponent() {
|
||||
|
||||
@@ -897,7 +897,7 @@ Item {
|
||||
Image {
|
||||
width: 24
|
||||
height: 24
|
||||
source: modelData.icon ? "image://icon/" + modelData.icon : "image://icon/application-x-executable"
|
||||
source: Paths.resolveIconUrl(modelData.icon || "application-x-executable")
|
||||
sourceSize.width: 24
|
||||
sourceSize.height: 24
|
||||
fillMode: Image.PreserveAspectFit
|
||||
@@ -1008,7 +1008,7 @@ Item {
|
||||
Image {
|
||||
width: 24
|
||||
height: 24
|
||||
source: modelData.icon ? "image://icon/" + modelData.icon : "image://icon/application-x-executable"
|
||||
source: Paths.resolveIconUrl(modelData.icon || "application-x-executable")
|
||||
sourceSize.width: 24
|
||||
sourceSize.height: 24
|
||||
fillMode: Image.PreserveAspectFit
|
||||
@@ -1154,7 +1154,7 @@ Item {
|
||||
Image {
|
||||
width: 24
|
||||
height: 24
|
||||
source: modelData.icon ? "image://icon/" + modelData.icon : "image://icon/application-x-executable"
|
||||
source: Paths.resolveIconUrl(modelData.icon || "application-x-executable")
|
||||
sourceSize.width: 24
|
||||
sourceSize.height: 24
|
||||
fillMode: Image.PreserveAspectFit
|
||||
|
||||
@@ -20,7 +20,10 @@ Item {
|
||||
var out = [];
|
||||
for (var i = 0; i < rules.length; i++) {
|
||||
if ((rules[i].action || "").toString().toLowerCase() === "mute")
|
||||
out.push({ rule: rules[i], index: i });
|
||||
out.push({
|
||||
rule: rules[i],
|
||||
index: i
|
||||
});
|
||||
}
|
||||
return out;
|
||||
}
|
||||
@@ -340,6 +343,7 @@ Item {
|
||||
}
|
||||
|
||||
SettingsSliderRow {
|
||||
id: animationDurationSlider
|
||||
settingKey: "notificationCustomAnimationDuration"
|
||||
tags: ["notification", "animation", "duration", "custom", "speed"]
|
||||
text: I18n.tr("Duration")
|
||||
@@ -355,6 +359,13 @@ Item {
|
||||
}
|
||||
SettingsData.set("notificationCustomAnimationDuration", newValue);
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Theme
|
||||
function onNotificationAnimationBaseDurationChanged() {
|
||||
animationDurationSlider.value = Theme.notificationAnimationBaseDuration;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,6 +408,8 @@ FloatingWindow {
|
||||
}
|
||||
clip: true
|
||||
visible: !root.isLoading
|
||||
add: null
|
||||
displaced: null
|
||||
|
||||
ScrollBar.vertical: DankScrollbar {
|
||||
id: browserScrollbar
|
||||
|
||||
@@ -2,7 +2,6 @@ import QtCore
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Modals.FileBrowser
|
||||
import qs.Services
|
||||
@@ -742,234 +741,6 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: variantSelector
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
visible: activeThemeId !== "" && activeThemeVariants !== null && (isMultiVariant || (activeThemeVariants.options && activeThemeVariants.options.length > 0))
|
||||
|
||||
property string activeThemeId: {
|
||||
if (Theme.currentThemeCategory !== "registry" || Theme.currentTheme !== "custom")
|
||||
return "";
|
||||
for (var i = 0; i < themeColorsTab.installedRegistryThemes.length; i++) {
|
||||
var t = themeColorsTab.installedRegistryThemes[i];
|
||||
if (SettingsData.customThemeFile && SettingsData.customThemeFile.endsWith((t.sourceDir || t.id) + "/theme.json"))
|
||||
return t.id;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
property var activeThemeVariants: {
|
||||
if (!activeThemeId)
|
||||
return null;
|
||||
for (var i = 0; i < themeColorsTab.installedRegistryThemes.length; i++) {
|
||||
var t = themeColorsTab.installedRegistryThemes[i];
|
||||
if (t.id === activeThemeId && t.hasVariants)
|
||||
return t.variants;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
property bool isMultiVariant: activeThemeVariants?.type === "multi"
|
||||
property string colorMode: Theme.isLightMode ? "light" : "dark"
|
||||
property var multiDefaults: {
|
||||
if (!isMultiVariant || !activeThemeVariants?.defaults)
|
||||
return {};
|
||||
return activeThemeVariants.defaults[colorMode] || activeThemeVariants.defaults.dark || {};
|
||||
}
|
||||
property var storedMulti: activeThemeId ? SettingsData.getRegistryThemeMultiVariant(activeThemeId, multiDefaults, colorMode) : multiDefaults
|
||||
property string selectedFlavor: {
|
||||
var sf = storedMulti.flavor || multiDefaults.flavor || "";
|
||||
for (var i = 0; i < flavorOptions.length; i++) {
|
||||
if (flavorOptions[i].id === sf)
|
||||
return sf;
|
||||
}
|
||||
if (flavorOptions.length > 0)
|
||||
return flavorOptions[0].id;
|
||||
return sf;
|
||||
}
|
||||
property string selectedAccent: storedMulti.accent || multiDefaults.accent || ""
|
||||
property var flavorOptions: {
|
||||
if (!isMultiVariant || !activeThemeVariants?.flavors)
|
||||
return [];
|
||||
return activeThemeVariants.flavors.filter(f => {
|
||||
if (f.mode)
|
||||
return f.mode === colorMode || f.mode === "both";
|
||||
return !!f[colorMode];
|
||||
});
|
||||
}
|
||||
property var flavorNames: flavorOptions.map(f => f.name)
|
||||
property int flavorIndex: {
|
||||
for (var i = 0; i < flavorOptions.length; i++) {
|
||||
if (flavorOptions[i].id === selectedFlavor)
|
||||
return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
property string selectedVariant: activeThemeId ? SettingsData.getRegistryThemeVariant(activeThemeId, activeThemeVariants?.default || "") : ""
|
||||
property var variantNames: {
|
||||
if (!activeThemeVariants?.options)
|
||||
return [];
|
||||
return activeThemeVariants.options.map(v => v.name);
|
||||
}
|
||||
property int selectedIndex: {
|
||||
if (!activeThemeVariants?.options || !selectedVariant)
|
||||
return 0;
|
||||
for (var i = 0; i < activeThemeVariants.options.length; i++) {
|
||||
if (activeThemeVariants.options[i].id === selectedVariant)
|
||||
return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: flavorButtonGroup.implicitHeight
|
||||
clip: true
|
||||
visible: variantSelector.isMultiVariant && variantSelector.flavorOptions.length > 1
|
||||
|
||||
DankButtonGroup {
|
||||
id: flavorButtonGroup
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
property int _count: variantSelector.flavorNames.length
|
||||
property real _maxPerItem: _count > 1 ? (parent.width - (_count - 1) * spacing) / _count : parent.width
|
||||
buttonPadding: _maxPerItem < 55 ? Theme.spacingXS : (_maxPerItem < 75 ? Theme.spacingS : Theme.spacingL)
|
||||
minButtonWidth: Math.min(_maxPerItem < 55 ? 28 : (_maxPerItem < 75 ? 44 : 64), Math.max(28, Math.floor(_maxPerItem)))
|
||||
textSize: _maxPerItem < 55 ? Theme.fontSizeSmall - 2 : (_maxPerItem < 75 ? Theme.fontSizeSmall : Theme.fontSizeMedium)
|
||||
checkEnabled: _maxPerItem >= 55
|
||||
property int pendingIndex: -1
|
||||
model: variantSelector.flavorNames
|
||||
currentIndex: pendingIndex >= 0 ? pendingIndex : variantSelector.flavorIndex
|
||||
selectionMode: "single"
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (!selected)
|
||||
return;
|
||||
pendingIndex = index;
|
||||
}
|
||||
onAnimationCompleted: {
|
||||
if (pendingIndex < 0 || pendingIndex >= variantSelector.flavorOptions.length)
|
||||
return;
|
||||
const flavorId = variantSelector.flavorOptions[pendingIndex]?.id;
|
||||
const idx = pendingIndex;
|
||||
pendingIndex = -1;
|
||||
if (!flavorId || flavorId === variantSelector.selectedFlavor)
|
||||
return;
|
||||
Theme.screenTransition();
|
||||
SettingsData.setRegistryThemeMultiVariant(variantSelector.activeThemeId, flavorId, variantSelector.selectedAccent, variantSelector.colorMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: accentColorsGrid.implicitHeight
|
||||
visible: variantSelector.isMultiVariant && variantSelector.activeThemeVariants?.accents?.length > 0
|
||||
|
||||
Grid {
|
||||
id: accentColorsGrid
|
||||
property int accentCount: variantSelector.activeThemeVariants?.accents?.length ?? 0
|
||||
property int dotSize: parent.width < 300 ? 28 : 32
|
||||
columns: accentCount > 0 ? Math.ceil(accentCount / 2) : 1
|
||||
rowSpacing: Theme.spacingS
|
||||
columnSpacing: Theme.spacingS
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
Repeater {
|
||||
model: variantSelector.activeThemeVariants?.accents || []
|
||||
|
||||
Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
property string accentId: modelData.id
|
||||
property bool isSelected: accentId === variantSelector.selectedAccent
|
||||
width: accentColorsGrid.dotSize
|
||||
height: accentColorsGrid.dotSize
|
||||
radius: width / 2
|
||||
color: modelData.color || Theme.primary
|
||||
border.color: Theme.outline
|
||||
border.width: isSelected ? 2 : 1
|
||||
scale: isSelected ? 1.1 : 1
|
||||
|
||||
Rectangle {
|
||||
width: accentNameText.contentWidth + Theme.spacingS * 2
|
||||
height: accentNameText.contentHeight + Theme.spacingXS * 2
|
||||
color: Theme.surfaceContainer
|
||||
radius: Theme.cornerRadius
|
||||
anchors.bottom: parent.top
|
||||
anchors.bottomMargin: Theme.spacingXS
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: accentMouseArea.containsMouse
|
||||
|
||||
StyledText {
|
||||
id: accentNameText
|
||||
text: modelData.name
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: accentMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (parent.isSelected)
|
||||
return;
|
||||
Theme.screenTransition();
|
||||
SettingsData.setRegistryThemeMultiVariant(variantSelector.activeThemeId, variantSelector.selectedFlavor, parent.accentId, variantSelector.colorMode);
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: variantButtonGroup.implicitHeight
|
||||
clip: true
|
||||
visible: !variantSelector.isMultiVariant && variantSelector.variantNames.length > 0
|
||||
|
||||
DankButtonGroup {
|
||||
id: variantButtonGroup
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
property int _count: variantSelector.variantNames.length
|
||||
property real _maxPerItem: _count > 1 ? (parent.width - (_count - 1) * spacing) / _count : parent.width
|
||||
buttonPadding: _maxPerItem < 55 ? Theme.spacingXS : (_maxPerItem < 75 ? Theme.spacingS : Theme.spacingL)
|
||||
minButtonWidth: Math.min(_maxPerItem < 55 ? 28 : (_maxPerItem < 75 ? 44 : 64), Math.max(28, Math.floor(_maxPerItem)))
|
||||
textSize: _maxPerItem < 55 ? Theme.fontSizeSmall - 2 : (_maxPerItem < 75 ? Theme.fontSizeSmall : Theme.fontSizeMedium)
|
||||
checkEnabled: _maxPerItem >= 55
|
||||
property int pendingIndex: -1
|
||||
model: variantSelector.variantNames
|
||||
currentIndex: pendingIndex >= 0 ? pendingIndex : variantSelector.selectedIndex
|
||||
selectionMode: "single"
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (!selected)
|
||||
return;
|
||||
pendingIndex = index;
|
||||
}
|
||||
onAnimationCompleted: {
|
||||
if (pendingIndex < 0 || !variantSelector.activeThemeVariants?.options)
|
||||
return;
|
||||
const variantId = variantSelector.activeThemeVariants.options[pendingIndex]?.id;
|
||||
const idx = pendingIndex;
|
||||
pendingIndex = -1;
|
||||
if (!variantId || variantId === variantSelector.selectedVariant)
|
||||
return;
|
||||
Theme.screenTransition();
|
||||
SettingsData.setRegistryThemeVariant(variantSelector.activeThemeId, variantId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("No themes installed. Browse themes to install from the registry.", "no registry themes installed hint")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
@@ -987,6 +758,248 @@ Item {
|
||||
onClicked: showThemeBrowser()
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: variantSelector
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
visible: activeThemeId !== "" && activeThemeVariants !== null && (isMultiVariant || (activeThemeVariants.options && activeThemeVariants.options.length > 0))
|
||||
|
||||
property string activeThemeId: {
|
||||
switch (Theme.currentThemeCategory) {
|
||||
case "registry":
|
||||
if (Theme.currentTheme !== "custom")
|
||||
return "";
|
||||
for (var i = 0; i < themeColorsTab.installedRegistryThemes.length; i++) {
|
||||
var t = themeColorsTab.installedRegistryThemes[i];
|
||||
if (SettingsData.customThemeFile && SettingsData.customThemeFile.endsWith((t.sourceDir || t.id) + "/theme.json"))
|
||||
return t.id;
|
||||
}
|
||||
return "";
|
||||
case "custom":
|
||||
return Theme.currentThemeId || "";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
property var activeThemeVariants: {
|
||||
if (!activeThemeId)
|
||||
return null;
|
||||
switch (Theme.currentThemeCategory) {
|
||||
case "registry":
|
||||
for (var i = 0; i < themeColorsTab.installedRegistryThemes.length; i++) {
|
||||
var t = themeColorsTab.installedRegistryThemes[i];
|
||||
if (t.id === activeThemeId && t.hasVariants)
|
||||
return t.variants;
|
||||
}
|
||||
return null;
|
||||
case "custom":
|
||||
return Theme.currentThemeVariants || null;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
property bool isMultiVariant: activeThemeVariants?.type === "multi"
|
||||
property string colorMode: Theme.isLightMode ? "light" : "dark"
|
||||
property var multiDefaults: {
|
||||
if (!isMultiVariant || !activeThemeVariants?.defaults)
|
||||
return {};
|
||||
return activeThemeVariants.defaults[colorMode] || activeThemeVariants.defaults.dark || {};
|
||||
}
|
||||
property var storedMulti: activeThemeId ? SettingsData.getRegistryThemeMultiVariant(activeThemeId, multiDefaults, colorMode) : multiDefaults
|
||||
property string selectedFlavor: {
|
||||
var sf = storedMulti.flavor || multiDefaults.flavor || "";
|
||||
for (var i = 0; i < flavorOptions.length; i++) {
|
||||
if (flavorOptions[i].id === sf)
|
||||
return sf;
|
||||
}
|
||||
if (flavorOptions.length > 0)
|
||||
return flavorOptions[0].id;
|
||||
return sf;
|
||||
}
|
||||
property string selectedAccent: storedMulti.accent || multiDefaults.accent || ""
|
||||
property var flavorOptions: {
|
||||
if (!isMultiVariant || !activeThemeVariants?.flavors)
|
||||
return [];
|
||||
return activeThemeVariants.flavors.filter(f => {
|
||||
if (f.mode)
|
||||
return f.mode === colorMode || f.mode === "both";
|
||||
return !!f[colorMode];
|
||||
});
|
||||
}
|
||||
property var flavorNames: flavorOptions.map(f => f.name)
|
||||
property int flavorIndex: {
|
||||
for (var i = 0; i < flavorOptions.length; i++) {
|
||||
if (flavorOptions[i].id === selectedFlavor)
|
||||
return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
property string selectedVariant: activeThemeId ? SettingsData.getRegistryThemeVariant(activeThemeId, activeThemeVariants?.default || "") : ""
|
||||
property var variantNames: {
|
||||
if (!activeThemeVariants?.options)
|
||||
return [];
|
||||
return activeThemeVariants.options.map(v => v.name);
|
||||
}
|
||||
property int selectedIndex: {
|
||||
if (!activeThemeVariants?.options || !selectedVariant)
|
||||
return 0;
|
||||
for (var i = 0; i < activeThemeVariants.options.length; i++) {
|
||||
if (activeThemeVariants.options[i].id === selectedVariant)
|
||||
return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: flavorButtonGroup.implicitHeight
|
||||
clip: true
|
||||
visible: variantSelector.isMultiVariant && variantSelector.flavorOptions.length > 1
|
||||
|
||||
DankButtonGroup {
|
||||
id: flavorButtonGroup
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
property int _count: variantSelector.flavorNames.length
|
||||
property real _maxPerItem: _count > 1 ? (parent.width - (_count - 1) * spacing) / _count : parent.width
|
||||
buttonPadding: _maxPerItem < 55 ? Theme.spacingXS : (_maxPerItem < 75 ? Theme.spacingS : Theme.spacingL)
|
||||
minButtonWidth: Math.min(_maxPerItem < 55 ? 28 : (_maxPerItem < 75 ? 44 : 64), Math.max(28, Math.floor(_maxPerItem)))
|
||||
textSize: _maxPerItem < 55 ? Theme.fontSizeSmall - 2 : (_maxPerItem < 75 ? Theme.fontSizeSmall : Theme.fontSizeMedium)
|
||||
checkEnabled: _maxPerItem >= 55
|
||||
property int pendingIndex: -1
|
||||
model: variantSelector.flavorNames
|
||||
currentIndex: pendingIndex >= 0 ? pendingIndex : variantSelector.flavorIndex
|
||||
selectionMode: "single"
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (!selected)
|
||||
return;
|
||||
pendingIndex = index;
|
||||
}
|
||||
onAnimationCompleted: {
|
||||
if (pendingIndex < 0 || pendingIndex >= variantSelector.flavorOptions.length)
|
||||
return;
|
||||
const flavorId = variantSelector.flavorOptions[pendingIndex]?.id;
|
||||
const idx = pendingIndex;
|
||||
pendingIndex = -1;
|
||||
if (!flavorId || flavorId === variantSelector.selectedFlavor)
|
||||
return;
|
||||
Theme.screenTransition();
|
||||
SettingsData.setRegistryThemeMultiVariant(variantSelector.activeThemeId, flavorId, variantSelector.selectedAccent, variantSelector.colorMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: accentColorsGrid.implicitHeight
|
||||
visible: variantSelector.isMultiVariant && variantSelector.activeThemeVariants?.accents?.length > 0
|
||||
|
||||
Grid {
|
||||
id: accentColorsGrid
|
||||
property int accentCount: variantSelector.activeThemeVariants?.accents?.length ?? 0
|
||||
property int dotSize: parent.width < 300 ? 28 : 32
|
||||
columns: accentCount > 0 ? Math.ceil(accentCount / 2) : 1
|
||||
rowSpacing: Theme.spacingS
|
||||
columnSpacing: Theme.spacingS
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
Repeater {
|
||||
model: variantSelector.activeThemeVariants?.accents || []
|
||||
|
||||
Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
property string accentId: modelData.id
|
||||
property bool isSelected: accentId === variantSelector.selectedAccent
|
||||
width: accentColorsGrid.dotSize
|
||||
height: accentColorsGrid.dotSize
|
||||
radius: width / 2
|
||||
color: modelData.color || modelData[variantSelector.selectedFlavor]?.primary || Theme.primary
|
||||
border.color: Theme.outline
|
||||
border.width: isSelected ? 2 : 1
|
||||
scale: isSelected ? 1.1 : 1
|
||||
|
||||
Rectangle {
|
||||
width: accentNameText.contentWidth + Theme.spacingS * 2
|
||||
height: accentNameText.contentHeight + Theme.spacingXS * 2
|
||||
color: Theme.surfaceContainer
|
||||
radius: Theme.cornerRadius
|
||||
anchors.bottom: parent.top
|
||||
anchors.bottomMargin: Theme.spacingXS
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: accentMouseArea.containsMouse
|
||||
|
||||
StyledText {
|
||||
id: accentNameText
|
||||
text: modelData.name
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: accentMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (parent.isSelected)
|
||||
return;
|
||||
Theme.screenTransition();
|
||||
SettingsData.setRegistryThemeMultiVariant(variantSelector.activeThemeId, variantSelector.selectedFlavor, parent.accentId, variantSelector.colorMode);
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: variantButtonGroup.implicitHeight
|
||||
clip: true
|
||||
visible: !variantSelector.isMultiVariant && variantSelector.variantNames.length > 0
|
||||
|
||||
DankButtonGroup {
|
||||
id: variantButtonGroup
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
property int _count: variantSelector.variantNames.length
|
||||
property real _maxPerItem: _count > 1 ? (parent.width - (_count - 1) * spacing) / _count : parent.width
|
||||
buttonPadding: _maxPerItem < 55 ? Theme.spacingXS : (_maxPerItem < 75 ? Theme.spacingS : Theme.spacingL)
|
||||
minButtonWidth: Math.min(_maxPerItem < 55 ? 28 : (_maxPerItem < 75 ? 44 : 64), Math.max(28, Math.floor(_maxPerItem)))
|
||||
textSize: _maxPerItem < 55 ? Theme.fontSizeSmall - 2 : (_maxPerItem < 75 ? Theme.fontSizeSmall : Theme.fontSizeMedium)
|
||||
checkEnabled: _maxPerItem >= 55
|
||||
property int pendingIndex: -1
|
||||
model: variantSelector.variantNames
|
||||
currentIndex: pendingIndex >= 0 ? pendingIndex : variantSelector.selectedIndex
|
||||
selectionMode: "single"
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (!selected)
|
||||
return;
|
||||
pendingIndex = index;
|
||||
}
|
||||
onAnimationCompleted: {
|
||||
if (pendingIndex < 0 || !variantSelector.activeThemeVariants?.options)
|
||||
return;
|
||||
const variantId = variantSelector.activeThemeVariants.options[pendingIndex]?.id;
|
||||
const idx = pendingIndex;
|
||||
pendingIndex = -1;
|
||||
if (!variantId || variantId === variantSelector.selectedVariant)
|
||||
return;
|
||||
Theme.screenTransition();
|
||||
SettingsData.setRegistryThemeVariant(variantSelector.activeThemeId, variantId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2416,6 +2429,18 @@ Item {
|
||||
checked: SettingsData.matugenTemplateEmacs
|
||||
onToggled: checked => SettingsData.set("matugenTemplateEmacs", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
tab: "theme"
|
||||
tags: ["matugen", "zed", "template"]
|
||||
settingKey: "matugenTemplateZed"
|
||||
text: "Zed"
|
||||
description: getTemplateDescription("zed", "")
|
||||
descriptionColor: getTemplateDescriptionColor("zed")
|
||||
visible: SettingsData.runDmsMatugenTemplates
|
||||
checked: SettingsData.matugenTemplateZed
|
||||
onToggled: checked => SettingsData.set("matugenTemplateZed", checked)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
|
||||
@@ -135,7 +135,7 @@ Variants {
|
||||
|
||||
Timer {
|
||||
id: renderSettleTimer
|
||||
interval: 100
|
||||
interval: 1000
|
||||
onTriggered: root._renderSettling = false
|
||||
}
|
||||
|
||||
|
||||
@@ -56,8 +56,8 @@ Singleton {
|
||||
}
|
||||
readonly property bool isCharging: batteryAvailable && batteries.some(b => b.state === UPowerDeviceState.Charging)
|
||||
|
||||
// Is the system plugged in (none of the batteries are discharging or empty)
|
||||
readonly property bool isPluggedIn: batteryAvailable && batteries.every(b => b.state !== UPowerDeviceState.Discharging)
|
||||
// Is the system plugged in (Is not running on battery)
|
||||
readonly property bool isPluggedIn: !UPower.onBattery
|
||||
readonly property bool isLowBattery: batteryAvailable && batteryLevel <= 20
|
||||
|
||||
onIsPluggedInChanged: {
|
||||
|
||||
@@ -4,6 +4,7 @@ pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
@@ -4,6 +4,7 @@ pragma ComponentBehavior: Bound
|
||||
import QtCore
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import qs.Common
|
||||
|
||||
Singleton {
|
||||
|
||||
@@ -93,9 +93,9 @@ Singleton {
|
||||
`;
|
||||
|
||||
monitorOffMonitor = Qt.createQmlObject(qmlString, root, "IdleService.MonitorOffMonitor");
|
||||
monitorOffMonitor.enabled = Qt.binding(() => root._enableGate && root.enabled && root.idleMonitorAvailable && root.monitorTimeout > 0);
|
||||
monitorOffMonitor.timeout = Qt.binding(() => root.monitorTimeout > 0 ? root.monitorTimeout : 86400);
|
||||
monitorOffMonitor.respectInhibitors = Qt.binding(() => root.respectInhibitors);
|
||||
monitorOffMonitor.timeout = Qt.binding(() => root.monitorTimeout);
|
||||
monitorOffMonitor.enabled = Qt.binding(() => root._enableGate && root.enabled && root.idleMonitorAvailable && root.monitorTimeout > 0);
|
||||
monitorOffMonitor.isIdleChanged.connect(function () {
|
||||
if (monitorOffMonitor.isIdle) {
|
||||
if (SettingsData.fadeToDpmsEnabled) {
|
||||
@@ -112,9 +112,9 @@ Singleton {
|
||||
});
|
||||
|
||||
lockMonitor = Qt.createQmlObject(qmlString, root, "IdleService.LockMonitor");
|
||||
lockMonitor.enabled = Qt.binding(() => root._enableGate && root.enabled && root.idleMonitorAvailable && root.lockTimeout > 0);
|
||||
lockMonitor.timeout = Qt.binding(() => root.lockTimeout > 0 ? root.lockTimeout : 86400);
|
||||
lockMonitor.respectInhibitors = Qt.binding(() => root.respectInhibitors);
|
||||
lockMonitor.timeout = Qt.binding(() => root.lockTimeout);
|
||||
lockMonitor.enabled = Qt.binding(() => root._enableGate && root.enabled && root.idleMonitorAvailable && root.lockTimeout > 0);
|
||||
lockMonitor.isIdleChanged.connect(function () {
|
||||
if (lockMonitor.isIdle) {
|
||||
if (SettingsData.fadeToLockEnabled) {
|
||||
@@ -130,9 +130,9 @@ Singleton {
|
||||
});
|
||||
|
||||
suspendMonitor = Qt.createQmlObject(qmlString, root, "IdleService.SuspendMonitor");
|
||||
suspendMonitor.enabled = Qt.binding(() => root._enableGate && root.enabled && root.idleMonitorAvailable && root.suspendTimeout > 0);
|
||||
suspendMonitor.timeout = Qt.binding(() => root.suspendTimeout > 0 ? root.suspendTimeout : 86400);
|
||||
suspendMonitor.respectInhibitors = Qt.binding(() => root.respectInhibitors);
|
||||
suspendMonitor.timeout = Qt.binding(() => root.suspendTimeout);
|
||||
suspendMonitor.enabled = Qt.binding(() => root._enableGate && root.enabled && root.idleMonitorAvailable && root.suspendTimeout > 0);
|
||||
suspendMonitor.isIdleChanged.connect(function () {
|
||||
if (suspendMonitor.isIdle) {
|
||||
root.requestSuspend();
|
||||
|
||||
@@ -19,6 +19,7 @@ Singleton {
|
||||
readonly property string historyFile: Paths.strip(Paths.cache) + "/notification_history.json"
|
||||
readonly property string imageCacheDir: Paths.strip(Paths.cache) + "/notification_images"
|
||||
property bool historyLoaded: false
|
||||
property int historyEntryCounter: 0
|
||||
|
||||
property list<NotifWrapper> notificationQueue: []
|
||||
property list<NotifWrapper> visibleNotifications: []
|
||||
@@ -73,6 +74,12 @@ Singleton {
|
||||
onTriggered: root.performSaveHistory()
|
||||
}
|
||||
|
||||
function _makeHistoryEntryId(sourceId, timestamp) {
|
||||
historyEntryCounter += 1;
|
||||
const safeSource = sourceId && sourceId !== "" ? sourceId : "notification";
|
||||
return safeSource + "_" + (timestamp || Date.now()) + "_" + historyEntryCounter;
|
||||
}
|
||||
|
||||
function getImageCachePath(wrapper) {
|
||||
const ts = wrapper.time ? wrapper.time.getTime() : Date.now();
|
||||
const id = wrapper.notification?.id?.toString() || "0";
|
||||
@@ -80,12 +87,13 @@ Singleton {
|
||||
}
|
||||
|
||||
function updateHistoryImage(wrapperId, imagePath) {
|
||||
const idx = historyList.findIndex(n => n.id === wrapperId);
|
||||
const idx = historyList.findIndex(n => n.sourceNotificationId === wrapperId || n.id === wrapperId);
|
||||
if (idx < 0)
|
||||
return;
|
||||
const item = historyList[idx];
|
||||
const updated = {
|
||||
id: item.id,
|
||||
sourceNotificationId: item.sourceNotificationId || item.id,
|
||||
summary: item.summary,
|
||||
body: item.body,
|
||||
htmlBody: item.htmlBody,
|
||||
@@ -113,8 +121,11 @@ Singleton {
|
||||
} else if (imageUrl && !imageUrl.startsWith("image://qsimage/")) {
|
||||
persistableImage = imageUrl;
|
||||
}
|
||||
const sourceNotificationId = wrapper.notification?.id?.toString() || "";
|
||||
const timestamp = wrapper.time.getTime();
|
||||
const data = {
|
||||
id: wrapper.notification?.id?.toString() || Date.now().toString(),
|
||||
id: _makeHistoryEntryId(sourceNotificationId, timestamp),
|
||||
sourceNotificationId: sourceNotificationId,
|
||||
summary: wrapper.summary || "",
|
||||
body: wrapper.body || "",
|
||||
htmlBody: wrapper.htmlBody || wrapper.body || "",
|
||||
@@ -122,7 +133,7 @@ Singleton {
|
||||
appIcon: wrapper.appIcon || "",
|
||||
image: persistableImage,
|
||||
urgency: urg,
|
||||
timestamp: wrapper.time.getTime(),
|
||||
timestamp: timestamp,
|
||||
desktopEntry: wrapper.desktopEntry || ""
|
||||
};
|
||||
let newList = [data, ...historyList];
|
||||
@@ -152,6 +163,8 @@ Singleton {
|
||||
const now = Date.now();
|
||||
const maxAgeMs = maxAgeDays > 0 ? maxAgeDays * 24 * 60 * 60 * 1000 : 0;
|
||||
const loaded = [];
|
||||
const seenIds = {};
|
||||
let needsRewrite = false;
|
||||
|
||||
for (const item of historyAdapter.notifications || []) {
|
||||
if (maxAgeMs > 0 && (now - item.timestamp) > maxAgeMs)
|
||||
@@ -162,8 +175,18 @@ Singleton {
|
||||
if (htmlBody) {
|
||||
htmlBody = htmlBody.replace(/<img\b[^>]*>/gi, "");
|
||||
}
|
||||
const sourceNotificationId = (item.sourceNotificationId || item.id || "").toString();
|
||||
let historyId = (item.id || "").toString();
|
||||
if (!historyId || seenIds[historyId]) {
|
||||
historyId = _makeHistoryEntryId(sourceNotificationId, item.timestamp || now);
|
||||
needsRewrite = true;
|
||||
}
|
||||
if (!item.sourceNotificationId)
|
||||
needsRewrite = true;
|
||||
seenIds[historyId] = true;
|
||||
loaded.push({
|
||||
id: item.id || "",
|
||||
id: historyId,
|
||||
sourceNotificationId: sourceNotificationId,
|
||||
summary: item.summary || "",
|
||||
body: body,
|
||||
htmlBody: htmlBody,
|
||||
@@ -177,7 +200,7 @@ Singleton {
|
||||
}
|
||||
historyList = loaded;
|
||||
historyLoaded = true;
|
||||
if (maxAgeMs > 0 && loaded.length !== (historyAdapter.notifications || []).length)
|
||||
if ((maxAgeMs > 0 && loaded.length !== (historyAdapter.notifications || []).length) || needsRewrite)
|
||||
saveHistory();
|
||||
} catch (e) {
|
||||
console.warn("NotificationService: load history failed:", e);
|
||||
|
||||
@@ -44,24 +44,26 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
readonly property var archBasedPMSettings: {
|
||||
"listUpdatesSettings": {
|
||||
"params": ["-Qu"],
|
||||
"correctExitCodes": [0, 1] // Exit code 0 = updates available, 1 = no updates
|
||||
},
|
||||
"upgradeSettings": {
|
||||
"params": ["-Syu"],
|
||||
"requiresSudo": false
|
||||
},
|
||||
"parserSettings": {
|
||||
"lineRegex": /^(\S+)\s+([^\s]+)\s+->\s+([^\s]+)$/,
|
||||
"entryProducer": function (match) {
|
||||
return {
|
||||
"name": match[1],
|
||||
"currentVersion": match[2],
|
||||
"newVersion": match[3],
|
||||
"description": `${match[1]} ${match[2]} → ${match[3]}`
|
||||
};
|
||||
readonly property var archBasedPMSettings: function(requiresSudo) {
|
||||
return {
|
||||
"listUpdatesSettings": {
|
||||
"params": ["-Qu"],
|
||||
"correctExitCodes": [0, 1] // Exit code 0 = updates available, 1 = no updates
|
||||
},
|
||||
"upgradeSettings": {
|
||||
"params": ["-Syu"],
|
||||
"requiresSudo": requiresSudo
|
||||
},
|
||||
"parserSettings": {
|
||||
"lineRegex": /^(\S+)\s+([^\s]+)\s+->\s+([^\s]+)$/,
|
||||
"entryProducer": function (match) {
|
||||
return {
|
||||
"name": match[1],
|
||||
"currentVersion": match[2],
|
||||
"newVersion": match[3],
|
||||
"description": `${match[1]} ${match[2]} → ${match[3]}`
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,8 +94,9 @@ Singleton {
|
||||
"checkupdates": archBasedUCSettings
|
||||
}
|
||||
readonly property var packageManagerParams: {
|
||||
"yay": archBasedPMSettings,
|
||||
"paru": archBasedPMSettings,
|
||||
"yay": archBasedPMSettings(false),
|
||||
"paru": archBasedPMSettings(false),
|
||||
"pacman": archBasedPMSettings(true),
|
||||
"dnf": fedoraBasedPMSettings
|
||||
}
|
||||
readonly property list<string> supportedDistributions: ["arch", "artix", "cachyos", "manjaro", "endeavouros", "fedora"]
|
||||
@@ -182,7 +185,7 @@ Singleton {
|
||||
|
||||
Process {
|
||||
id: pkgManagerDetection
|
||||
command: ["sh", "-c", "which paru || which yay || which dnf"]
|
||||
command: ["sh", "-c", "which paru || which yay || which pacman || which dnf"]
|
||||
|
||||
onExited: exitCode => {
|
||||
if (exitCode === 0) {
|
||||
@@ -259,7 +262,7 @@ Singleton {
|
||||
const terminal = Quickshell.env("TERMINAL") || "xterm";
|
||||
|
||||
if (SettingsData.updaterUseCustomCommand && SettingsData.updaterCustomCommand.length > 0) {
|
||||
const updateCommand = `${SettingsData.updaterCustomCommand} && echo "Updates complete! Press Enter to close..." && read`;
|
||||
const updateCommand = `${SettingsData.updaterCustomCommand} && echo -n "Updates complete! " ; echo "Press Enter to close..." && read`;
|
||||
const termClass = SettingsData.updaterTerminalAdditionalParams;
|
||||
|
||||
var finalCommand = [terminal];
|
||||
@@ -274,7 +277,7 @@ Singleton {
|
||||
} else {
|
||||
const params = packageManagerParams[pkgManager].upgradeSettings.params.join(" ");
|
||||
const sudo = packageManagerParams[pkgManager].upgradeSettings.requiresSudo ? "sudo" : "";
|
||||
const updateCommand = `${sudo} ${pkgManager} ${params} && echo "Updates complete! Press Enter to close..." && read`;
|
||||
const updateCommand = `${sudo} ${pkgManager} ${params} && echo -n "Updates complete! " ; echo "Press Enter to close..." && read`;
|
||||
|
||||
updater.command = [terminal, "-e", "sh", "-c", updateCommand];
|
||||
}
|
||||
|
||||
@@ -264,7 +264,7 @@ Singleton {
|
||||
}
|
||||
|
||||
if (process) {
|
||||
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.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.targetScreenName = screenName;
|
||||
process.currentWallpaper = currentWallpaper;
|
||||
process.goToPrevious = false;
|
||||
@@ -272,7 +272,7 @@ Singleton {
|
||||
}
|
||||
} else {
|
||||
// Use global process for fallback
|
||||
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.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.targetScreenName = screenName || "";
|
||||
cyclingProcess.currentWallpaper = currentWallpaper;
|
||||
cyclingProcess.running = true;
|
||||
@@ -296,7 +296,7 @@ Singleton {
|
||||
}
|
||||
|
||||
if (process) {
|
||||
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.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.targetScreenName = screenName;
|
||||
process.currentWallpaper = currentWallpaper;
|
||||
process.goToPrevious = true;
|
||||
@@ -304,7 +304,7 @@ Singleton {
|
||||
}
|
||||
} else {
|
||||
// Use global process for fallback
|
||||
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.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.targetScreenName = screenName || "";
|
||||
prevCyclingProcess.currentWallpaper = currentWallpaper;
|
||||
prevCyclingProcess.running = true;
|
||||
|
||||
@@ -1 +1 @@
|
||||
v1.4.3
|
||||
v1.4.4
|
||||
|
||||
@@ -49,7 +49,7 @@ Item {
|
||||
readonly property string iconPath: {
|
||||
if (hasSpecialPrefix || !iconValue)
|
||||
return "";
|
||||
return Quickshell.iconPath(iconValue, true) || DesktopService.resolveIconPath(iconValue);
|
||||
return Paths.resolveIconPath(iconValue);
|
||||
}
|
||||
|
||||
visible: iconValue !== undefined && iconValue !== ""
|
||||
|
||||
@@ -289,7 +289,7 @@ Item {
|
||||
visible: false
|
||||
color: "transparent"
|
||||
Component.onCompleted: {
|
||||
if (typeof updatesEnabled !== "undefined")
|
||||
if (typeof updatesEnabled !== "undefined" && !root.overlayContent)
|
||||
updatesEnabled = false;
|
||||
}
|
||||
|
||||
|
||||
3
quickshell/matugen/configs/zed.toml
Normal file
3
quickshell/matugen/configs/zed.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[templates.dmszed]
|
||||
input_path = 'SHELL_DIR/matugen/templates/dank-zed.json'
|
||||
output_path = 'CONFIG_DIR/zed/themes/dank-zed-theme.json'
|
||||
1523
quickshell/matugen/templates/dank-zed.json
Normal file
1523
quickshell/matugen/templates/dank-zed.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
[colors]
|
||||
[colors-dark]
|
||||
foreground={{colors.on_surface.default.hex_stripped}}
|
||||
background={{colors.background.default.hex_stripped}}
|
||||
selection-foreground={{colors.on_surface.default.hex_stripped}}
|
||||
|
||||
@@ -92,3 +92,21 @@ toolbar .toolbarbutton-1 {
|
||||
#zen-appcontent-navbar-container {
|
||||
background-color: {{colors.background.default.hex}} !important;
|
||||
}
|
||||
|
||||
#PanelUI-menu-button .toolbarbutton-icon,
|
||||
#downloads-button .toolbarbutton-icon,
|
||||
#unified-extensions-button .toolbarbutton-icon {
|
||||
fill: {{colors.primary.default.hex}} !important;
|
||||
color: {{colors.primary.default.hex}} !important;
|
||||
}
|
||||
|
||||
#PanelUI-menu-button .toolbarbutton-badge-stack,
|
||||
#downloads-button .toolbarbutton-badge-stack,
|
||||
#unified-extensions-button .toolbarbutton-badge-stack {
|
||||
fill: {{colors.primary.default.hex}} !important;
|
||||
color: {{colors.primary.default.hex}} !important;
|
||||
}
|
||||
|
||||
toolbar .toolbarbutton-1 > .toolbarbutton-icon {
|
||||
fill: {{colors.primary.default.hex}} !important;
|
||||
}
|
||||
|
||||
@@ -4452,8 +4452,8 @@
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "File search requires dsearch\nInstall from github.com/morelazers/dsearch",
|
||||
"context": "File search requires dsearch\nInstall from github.com/morelazers/dsearch",
|
||||
"term": "File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch",
|
||||
"context": "File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch",
|
||||
"reference": "Modals/DankLauncherV2/ResultsList.qml:471",
|
||||
"comment": ""
|
||||
},
|
||||
|
||||
@@ -2023,11 +2023,11 @@
|
||||
"File Information": {
|
||||
"File Information": "Información del archivo"
|
||||
},
|
||||
"File search requires dsearch\nInstall from github.com/morelazers/dsearch": {
|
||||
"File search requires dsearch\nInstall from github.com/morelazers/dsearch": ""
|
||||
"File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch": {
|
||||
"File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch": ""
|
||||
},
|
||||
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": {
|
||||
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": ""
|
||||
"File search requires dsearch\\nInstall from github.com/AvengeMedia/danksearch": {
|
||||
"File search requires dsearch\\nInstall from github.com/AvengeMedia/danksearch": ""
|
||||
},
|
||||
"Files": {
|
||||
"Files": "Archivos"
|
||||
|
||||
@@ -2023,11 +2023,11 @@
|
||||
"File Information": {
|
||||
"File Information": "اطلاعات فایل"
|
||||
},
|
||||
"File search requires dsearch\nInstall from github.com/morelazers/dsearch": {
|
||||
"File search requires dsearch\nInstall from github.com/morelazers/dsearch": "جستجوی فایل به dsearch نیاز دارد\nاز github.com/morelazers/dsearch نصب کنید"
|
||||
"File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch": {
|
||||
"File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch": "جستجوی فایل به dsearch نیاز دارد\nاز github.com/AvengeMedia/danksearch نصب کنید"
|
||||
},
|
||||
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": {
|
||||
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": "جستجوی نیاز به dsearch دارد\\nاز github.com/morelazers/dsearch نصب کنید"
|
||||
"File search requires dsearch\\nInstall from github.com/AvengeMedia/danksearch": {
|
||||
"File search requires dsearch\\nInstall from github.com/AvengeMedia/danksearch": "جستجوی نیاز به dsearch دارد\\nاز github.com/AvengeMedia/danksearch نصب کنید"
|
||||
},
|
||||
"Files": {
|
||||
"Files": "فایلها"
|
||||
|
||||
@@ -2023,11 +2023,11 @@
|
||||
"File Information": {
|
||||
"File Information": "Informations sur le fichier"
|
||||
},
|
||||
"File search requires dsearch\nInstall from github.com/morelazers/dsearch": {
|
||||
"File search requires dsearch\nInstall from github.com/morelazers/dsearch": "La recherche de fichiers nécessite dsearch\nInstallez-le depuis github.com/morelazers/dsearch"
|
||||
"File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch": {
|
||||
"File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch": "La recherche de fichiers nécessite dsearch\nInstallez-le depuis github.com/AvengeMedia/danksearch"
|
||||
},
|
||||
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": {
|
||||
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": "La recherche de fichiers nécessite dsearch\\nInstallez-le depuis github.com/morelazers/dsearch"
|
||||
"File search requires dsearch\\nInstall from github.com/AvengeMedia/danksearch": {
|
||||
"File search requires dsearch\\nInstall from github.com/AvengeMedia/danksearch": "La recherche de fichiers nécessite dsearch\\nInstallez-le depuis github.com/AvengeMedia/danksearch"
|
||||
},
|
||||
"Files": {
|
||||
"Files": "Fichiers"
|
||||
|
||||
@@ -2023,11 +2023,11 @@
|
||||
"File Information": {
|
||||
"File Information": "פרטי קובץ"
|
||||
},
|
||||
"File search requires dsearch\nInstall from github.com/morelazers/dsearch": {
|
||||
"File search requires dsearch\nInstall from github.com/morelazers/dsearch": "חיפוש קבצים הכלי dsearch נדרש כדי לבצע חיפוש של קבצים.\nהתקן/י אותו מ: github.com/morelazers/dsearch"
|
||||
"File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch": {
|
||||
"File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch": "חיפוש קבצים הכלי dsearch נדרש כדי לבצע חיפוש של קבצים.\nהתקן/י אותו מ: github.com/AvengeMedia/danksearch"
|
||||
},
|
||||
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": {
|
||||
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": "חיפוש קבצים הכלי dsearch נדרש כדי לבצע חיפוש של קבצים.\\nהתקן/י אותו מ: github.com/morelazers/dsearch"
|
||||
"File search requires dsearch\\nInstall from github.com/AvengeMedia/danksearch": {
|
||||
"File search requires dsearch\\nInstall from github.com/AvengeMedia/danksearch": "חיפוש קבצים הכלי dsearch נדרש כדי לבצע חיפוש של קבצים.\\nהתקן/י אותו מ: github.com/AvengeMedia/danksearch"
|
||||
},
|
||||
"Files": {
|
||||
"Files": "קבצים"
|
||||
|
||||
@@ -2023,11 +2023,11 @@
|
||||
"File Information": {
|
||||
"File Information": "Fájlinformáció"
|
||||
},
|
||||
"File search requires dsearch\nInstall from github.com/morelazers/dsearch": {
|
||||
"File search requires dsearch\nInstall from github.com/morelazers/dsearch": "A fájlkereséshez dsearch szükséges\nTelepítsd innen: github.com/morelazers/dsearch"
|
||||
"File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch": {
|
||||
"File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch": "A fájlkereséshez dsearch szükséges\nTelepítsd innen: github.com/AvengeMedia/danksearch"
|
||||
},
|
||||
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": {
|
||||
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": "A fájlkereséshez dsearch szükséges\\nTelepítsd innen: github.com/morelazers/dsearch"
|
||||
"File search requires dsearch\\nInstall from github.com/AvengeMedia/danksearch": {
|
||||
"File search requires dsearch\\nInstall from github.com/AvengeMedia/danksearch": "A fájlkereséshez dsearch szükséges\\nTelepítsd innen: github.com/AvengeMedia/danksearch"
|
||||
},
|
||||
"Files": {
|
||||
"Files": "Fájlok"
|
||||
|
||||
@@ -2023,11 +2023,11 @@
|
||||
"File Information": {
|
||||
"File Information": "Informazioni File"
|
||||
},
|
||||
"File search requires dsearch\nInstall from github.com/morelazers/dsearch": {
|
||||
"File search requires dsearch\nInstall from github.com/morelazers/dsearch": "La ricerca file richiede dsearch\\nInstalla da github.com/morelazers/dsearch"
|
||||
"File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch": {
|
||||
"File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch": "La ricerca file richiede dsearch\\nInstalla da github.com/AvengeMedia/danksearch"
|
||||
},
|
||||
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": {
|
||||
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": "La ricerca file richiede dsearch\\nInstalla da github.com/morelazers/dsearch"
|
||||
"File search requires dsearch\\nInstall from github.com/AvengeMedia/danksearch": {
|
||||
"File search requires dsearch\\nInstall from github.com/AvengeMedia/danksearch": "La ricerca file richiede dsearch\\nInstalla da github.com/AvengeMedia/danksearch"
|
||||
},
|
||||
"Files": {
|
||||
"Files": "File"
|
||||
|
||||
@@ -2023,11 +2023,11 @@
|
||||
"File Information": {
|
||||
"File Information": "ファイル情報"
|
||||
},
|
||||
"File search requires dsearch\nInstall from github.com/morelazers/dsearch": {
|
||||
"File search requires dsearch\nInstall from github.com/morelazers/dsearch": ""
|
||||
"File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch": {
|
||||
"File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch": ""
|
||||
},
|
||||
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": {
|
||||
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": ""
|
||||
"File search requires dsearch\\nInstall from github.com/AvengeMedia/danksearch": {
|
||||
"File search requires dsearch\\nInstall from github.com/AvengeMedia/danksearch": ""
|
||||
},
|
||||
"Files": {
|
||||
"Files": ""
|
||||
|
||||
@@ -2023,11 +2023,11 @@
|
||||
"File Information": {
|
||||
"File Information": "Bestandsinformatie"
|
||||
},
|
||||
"File search requires dsearch\nInstall from github.com/morelazers/dsearch": {
|
||||
"File search requires dsearch\nInstall from github.com/morelazers/dsearch": "Bestandszoeken vereist dsearch\nInstalleer via github.com/morelazers/dsearch"
|
||||
"File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch": {
|
||||
"File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch": "Bestandszoeken vereist dsearch\nInstalleer via github.com/AvengeMedia/danksearch"
|
||||
},
|
||||
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": {
|
||||
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": "Bestandszoeken vereist dsearch\\nInstalleer via github.com/morelazers/dsearch"
|
||||
"File search requires dsearch\\nInstall from github.com/AvengeMedia/danksearch": {
|
||||
"File search requires dsearch\\nInstall from github.com/AvengeMedia/danksearch": "Bestandszoeken vereist dsearch\\nInstalleer via github.com/AvengeMedia/danksearch"
|
||||
},
|
||||
"Files": {
|
||||
"Files": "Bestanden"
|
||||
|
||||
@@ -2023,11 +2023,11 @@
|
||||
"File Information": {
|
||||
"File Information": "Informacje"
|
||||
},
|
||||
"File search requires dsearch\nInstall from github.com/morelazers/dsearch": {
|
||||
"File search requires dsearch\nInstall from github.com/morelazers/dsearch": ""
|
||||
"File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch": {
|
||||
"File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch": ""
|
||||
},
|
||||
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": {
|
||||
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": ""
|
||||
"File search requires dsearch\\nInstall from github.com/AvengeMedia/danksearch": {
|
||||
"File search requires dsearch\\nInstall from github.com/AvengeMedia/danksearch": ""
|
||||
},
|
||||
"Files": {
|
||||
"Files": "Akta"
|
||||
|
||||
@@ -2023,11 +2023,11 @@
|
||||
"File Information": {
|
||||
"File Information": "Informação do Arquivo"
|
||||
},
|
||||
"File search requires dsearch\nInstall from github.com/morelazers/dsearch": {
|
||||
"File search requires dsearch\nInstall from github.com/morelazers/dsearch": "Pesquisa de arquivo requer dsearch\nInstalar de github.com/morelazers/dsearch"
|
||||
"File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch": {
|
||||
"File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch": "Pesquisa de arquivo requer dsearch\nInstalar de github.com/AvengeMedia/danksearch"
|
||||
},
|
||||
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": {
|
||||
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": "A pesquisa de arquivo requer dsearch\\nInstale em github.com/morelazers/dsearch"
|
||||
"File search requires dsearch\\nInstall from github.com/AvengeMedia/danksearch": {
|
||||
"File search requires dsearch\\nInstall from github.com/AvengeMedia/danksearch": "A pesquisa de arquivo requer dsearch\\nInstale em github.com/AvengeMedia/danksearch"
|
||||
},
|
||||
"Files": {
|
||||
"Files": "Arquivos"
|
||||
|
||||
@@ -2023,11 +2023,11 @@
|
||||
"File Information": {
|
||||
"File Information": "Dosya Bilgisi"
|
||||
},
|
||||
"File search requires dsearch\nInstall from github.com/morelazers/dsearch": {
|
||||
"File search requires dsearch\nInstall from github.com/morelazers/dsearch": ""
|
||||
"File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch": {
|
||||
"File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch": ""
|
||||
},
|
||||
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": {
|
||||
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": ""
|
||||
"File search requires dsearch\\nInstall from github.com/AvengeMedia/danksearch": {
|
||||
"File search requires dsearch\\nInstall from github.com/AvengeMedia/danksearch": ""
|
||||
},
|
||||
"Files": {
|
||||
"Files": "Dosyalar"
|
||||
|
||||
@@ -2023,11 +2023,11 @@
|
||||
"File Information": {
|
||||
"File Information": "文件信息"
|
||||
},
|
||||
"File search requires dsearch\nInstall from github.com/morelazers/dsearch": {
|
||||
"File search requires dsearch\nInstall from github.com/morelazers/dsearch": ""
|
||||
"File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch": {
|
||||
"File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch": ""
|
||||
},
|
||||
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": {
|
||||
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": "文件搜索需要dsearch\\n请从github.com/morelazers/dsearch进行安装"
|
||||
"File search requires dsearch\\nInstall from github.com/AvengeMedia/danksearch": {
|
||||
"File search requires dsearch\\nInstall from github.com/AvengeMedia/danksearch": "文件搜索需要dsearch\\n请从github.com/AvengeMedia/danksearch进行安装"
|
||||
},
|
||||
"Files": {
|
||||
"Files": "文件"
|
||||
|
||||
@@ -2023,11 +2023,11 @@
|
||||
"File Information": {
|
||||
"File Information": "檔案資訊"
|
||||
},
|
||||
"File search requires dsearch\nInstall from github.com/morelazers/dsearch": {
|
||||
"File search requires dsearch\nInstall from github.com/morelazers/dsearch": ""
|
||||
"File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch": {
|
||||
"File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch": ""
|
||||
},
|
||||
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": {
|
||||
"File search requires dsearch\\nInstall from github.com/morelazers/dsearch": "檔案搜尋需要 dsearch\\n請從 github.com/morelazers/dsearch 安裝"
|
||||
"File search requires dsearch\\nInstall from github.com/AvengeMedia/danksearch": {
|
||||
"File search requires dsearch\\nInstall from github.com/AvengeMedia/danksearch": "檔案搜尋需要 dsearch\\n請從 github.com/AvengeMedia/danksearch 安裝"
|
||||
},
|
||||
"Files": {
|
||||
"Files": "檔案"
|
||||
|
||||
@@ -2484,6 +2484,23 @@
|
||||
"theme"
|
||||
]
|
||||
},
|
||||
{
|
||||
"section": "matugenTemplateZed",
|
||||
"label": "Zed",
|
||||
"tabIndex": 10,
|
||||
"category": "Theme & Colors",
|
||||
"keywords": [
|
||||
"appearance",
|
||||
"colors",
|
||||
"look",
|
||||
"matugen",
|
||||
"scheme",
|
||||
"style",
|
||||
"template",
|
||||
"theme",
|
||||
"zed"
|
||||
]
|
||||
},
|
||||
{
|
||||
"section": "matugenTemplateFirefox",
|
||||
"label": "Firefox",
|
||||
|
||||
@@ -5194,7 +5194,7 @@
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "File search requires dsearch\nInstall from github.com/morelazers/dsearch",
|
||||
"term": "File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
|
||||
Reference in New Issue
Block a user