mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-05-05 12:02:06 -04:00
Compare commits
155 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9798d78300 | ||
|
|
1f64bb8031 | ||
|
|
eea7d12c0b | ||
|
|
85173126f4 | ||
|
|
222187d8a6 | ||
|
|
bef3f65f63 | ||
|
|
bff83fe563 | ||
|
|
cbf00d133a | ||
|
|
347f06b758 | ||
|
|
9070903512 | ||
|
|
e9d030f6d8 | ||
|
|
fbf9e6d1b9 | ||
|
|
e803812344 | ||
|
|
9a64f2acf0 | ||
|
|
c647eafadc | ||
|
|
720ec07d13 | ||
|
|
4b4334e611 | ||
|
|
b69a96e80b | ||
|
|
1e6a73fd60 | ||
|
|
60b6280750 | ||
|
|
9e079f8a4b | ||
|
|
62c2e858ef | ||
|
|
78357d45bb | ||
|
|
3ff9564c9b | ||
|
|
b0989cecad | ||
|
|
47be6a1033 | ||
|
|
31b415b086 | ||
|
|
7156e1e299 | ||
|
|
c72c9bfb08 | ||
|
|
73c75fcc2c | ||
|
|
2ff42eba41 | ||
|
|
9f13465cd7 | ||
|
|
366a98e0cc | ||
|
|
31aeb8dc4b | ||
|
|
c4e7f3d62f | ||
|
|
a1d13f276a | ||
|
|
dbf132d633 | ||
|
|
59451890f1 | ||
|
|
e633c9e039 | ||
|
|
6c1fff2df1 | ||
|
|
3891d125d1 | ||
|
|
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 | ||
|
|
3f24cf37ca | ||
|
|
01218f34cb | ||
|
|
9da58d8296 | ||
|
|
af0038e634 | ||
|
|
05c312b9eb | ||
|
|
89d5c958c4 | ||
|
|
e4d86ad595 | ||
|
|
532b54a028 | ||
|
|
504d027c3f | ||
|
|
e8f95f4533 | ||
|
|
b83256c83a | ||
|
|
8e2cd21be8 | ||
|
|
c5413608da | ||
|
|
586bcad442 | ||
|
|
3b3d10f730 | ||
|
|
4834891b36 | ||
|
|
f60e65aecb | ||
|
|
01387b0123 | ||
|
|
1476658c23 | ||
|
|
7861c6e316 | ||
|
|
d2247d7b24 | ||
|
|
2ff78d4a02 | ||
|
|
785243ce5f | ||
|
|
0e1b868384 | ||
|
|
2b08e800e8 | ||
|
|
74e4f8ea1e | ||
|
|
9c58569b4c | ||
|
|
29de677e00 | ||
|
|
fae4944845 | ||
|
|
07a0ac4b7d | ||
|
|
b2d8f4d73b | ||
|
|
fe58c45233 | ||
|
|
3ea4e389eb | ||
|
|
7276f295fc | ||
|
|
93ed96a789 | ||
|
|
bea325e94c | ||
|
|
2f8f1c30ad | ||
|
|
f859a14173 | ||
|
|
153f39da48 | ||
|
|
e4accdd1c7 | ||
|
|
a2c89e0a8c | ||
|
|
e282831c2e | ||
|
|
5c5ff6195a | ||
|
|
c4bbf54679 | ||
|
|
98acafb4b8 | ||
|
|
da20681fc0 | ||
|
|
b38cb961b2 | ||
|
|
7a0bb07518 | ||
|
|
403e3e90a2 | ||
|
|
50b91f14b6 | ||
|
|
b3df47fce0 | ||
|
|
09bd65d746 | ||
|
|
020d56ab7f | ||
|
|
f3bee65da9 | ||
|
|
b14b0946e2 | ||
|
|
ca44205f1c | ||
|
|
2d39e8fd2a | ||
|
|
6d4df6e927 | ||
|
|
b8ab86e6c0 | ||
|
|
837329a6d8 | ||
|
|
8c6c2ffd23 | ||
|
|
ad3c8b6755 |
8
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
8
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -7,7 +7,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
## DankMaterialShell Bug Report
|
## DankMaterialShell Bug Report
|
||||||
Limit your report to one issue per submission unless closely related
|
Limit your report to one issue per submission unless similarly related
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
id: compositor
|
id: compositor
|
||||||
attributes:
|
attributes:
|
||||||
@@ -53,9 +53,9 @@ body:
|
|||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
id: original_installation_method_different
|
id: original_installation_method
|
||||||
attributes:
|
attributes:
|
||||||
label: Was your original Installation method different?
|
label: Was this your original Installation method?
|
||||||
options:
|
options:
|
||||||
- "Yes"
|
- "Yes"
|
||||||
- No (specify below)
|
- No (specify below)
|
||||||
@@ -73,7 +73,7 @@ body:
|
|||||||
id: dms_doctor
|
id: dms_doctor
|
||||||
attributes:
|
attributes:
|
||||||
label: dms doctor -vC
|
label: dms doctor -vC
|
||||||
description: Output of `dms doctor -vC` command — paste between the lines below to keep it collapsed in the issue
|
description: Output of `dms doctor -vC` command — paste between the details tags below to keep it collapsed in the issue
|
||||||
placeholder: Paste the output of `dms doctor -vC` here
|
placeholder: Paste the output of `dms doctor -vC` here
|
||||||
value: |
|
value: |
|
||||||
<details>
|
<details>
|
||||||
|
|||||||
215
.github/workflows/run-obs.yml
vendored
215
.github/workflows/run-obs.yml
vendored
@@ -10,6 +10,7 @@ on:
|
|||||||
options:
|
options:
|
||||||
- dms
|
- dms
|
||||||
- dms-git
|
- dms-git
|
||||||
|
- dms-greeter
|
||||||
- all
|
- all
|
||||||
default: "dms"
|
default: "dms"
|
||||||
rebuild_release:
|
rebuild_release:
|
||||||
@@ -72,12 +73,27 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Helper function to check dms-greeter stable tag
|
||||||
|
check_dms_greeter_stable() {
|
||||||
|
LATEST_TAG=$(curl -s https://api.github.com/repos/AvengeMedia/DankMaterialShell/releases/latest | grep '"tag_name"' | sed 's/.*"tag_name": "\([^"]*\)".*/\1/' || echo "")
|
||||||
|
local OBS_SPEC=$(curl -s -u "$OBS_USERNAME:$OBS_PASSWORD" "https://api.opensuse.org/source/home:AvengeMedia:danklinux/dms-greeter/dms-greeter.spec" 2>/dev/null || echo "")
|
||||||
|
local OBS_VERSION=$(echo "$OBS_SPEC" | grep "^Version:" | awk '{print $2}' | xargs | sed 's/^v//')
|
||||||
|
|
||||||
|
if [[ -n "$LATEST_TAG" && "$LATEST_TAG" == "v$OBS_VERSION" ]]; then
|
||||||
|
echo "📋 dms-greeter: Tag $LATEST_TAG already exists, skipping"
|
||||||
|
return 1 # No update needed
|
||||||
|
else
|
||||||
|
echo "📋 dms-greeter: New tag ${LATEST_TAG:-unknown} (OBS has ${OBS_VERSION:-none})"
|
||||||
|
return 0 # Update needed
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# Main logic
|
# Main logic
|
||||||
REBUILD="${{ github.event.inputs.rebuild_release }}"
|
REBUILD="${{ github.event.inputs.rebuild_release }}"
|
||||||
|
|
||||||
if [[ "${{ github.ref }}" =~ ^refs/tags/ ]]; then
|
if [[ "${{ github.ref }}" =~ ^refs/tags/ ]] && [[ -z "${{ github.event.inputs.package }}" ]]; then
|
||||||
# Tag selected or pushed - always update stable package
|
# Run from tag with no package specified - update both stable packages
|
||||||
echo "packages=dms" >> $GITHUB_OUTPUT
|
echo "packages=dms dms-greeter" >> $GITHUB_OUTPUT
|
||||||
VERSION="${GITHUB_REF#refs/tags/}"
|
VERSION="${GITHUB_REF#refs/tags/}"
|
||||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||||
@@ -112,6 +128,10 @@ jobs:
|
|||||||
echo "version=$LATEST_TAG" >> $GITHUB_OUTPUT
|
echo "version=$LATEST_TAG" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
if check_dms_greeter_stable; then
|
||||||
|
PACKAGES_TO_UPDATE+=("dms-greeter")
|
||||||
|
[[ -n "$LATEST_TAG" ]] && echo "version=$LATEST_TAG" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ ${#PACKAGES_TO_UPDATE[@]} -gt 0 ]]; then
|
if [[ ${#PACKAGES_TO_UPDATE[@]} -gt 0 ]]; then
|
||||||
echo "packages=${PACKAGES_TO_UPDATE[*]}" >> $GITHUB_OUTPUT
|
echo "packages=${PACKAGES_TO_UPDATE[*]}" >> $GITHUB_OUTPUT
|
||||||
@@ -144,6 +164,18 @@ jobs:
|
|||||||
echo "has_updates=false" >> $GITHUB_OUTPUT
|
echo "has_updates=false" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
elif [[ "$PKG" == "dms-greeter" ]]; then
|
||||||
|
if check_dms_greeter_stable; then
|
||||||
|
echo "packages=$PKG" >> $GITHUB_OUTPUT
|
||||||
|
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||||
|
if [[ -n "$LATEST_TAG" ]]; then
|
||||||
|
echo "version=$LATEST_TAG" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "packages=" >> $GITHUB_OUTPUT
|
||||||
|
echo "has_updates=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
else
|
else
|
||||||
# Unknown package - proceed anyway
|
# Unknown package - proceed anyway
|
||||||
echo "packages=$PKG" >> $GITHUB_OUTPUT
|
echo "packages=$PKG" >> $GITHUB_OUTPUT
|
||||||
@@ -171,15 +203,8 @@ jobs:
|
|||||||
- name: Determine packages to update
|
- name: Determine packages to update
|
||||||
id: packages
|
id: packages
|
||||||
run: |
|
run: |
|
||||||
# Check if GITHUB_REF points to a tag (works for both push events and workflow_dispatch with tag selected)
|
# Use check-updates outputs when available
|
||||||
if [[ "${{ github.ref }}" =~ ^refs/tags/ ]]; then
|
if [[ -n "${{ needs.check-updates.outputs.version }}" ]]; then
|
||||||
# Tag selected or pushed - use the tag from GITHUB_REF
|
|
||||||
echo "packages=dms" >> $GITHUB_OUTPUT
|
|
||||||
VERSION="${GITHUB_REF#refs/tags/}"
|
|
||||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
||||||
echo "Using tag from GITHUB_REF: $VERSION"
|
|
||||||
# Check if check-updates already determined a version (from auto-detection)
|
|
||||||
elif [[ -n "${{ needs.check-updates.outputs.version }}" ]]; then
|
|
||||||
# Use version from check-updates job
|
# Use version from check-updates job
|
||||||
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
|
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
|
||||||
echo "version=${{ needs.check-updates.outputs.version }}" >> $GITHUB_OUTPUT
|
echo "version=${{ needs.check-updates.outputs.version }}" >> $GITHUB_OUTPUT
|
||||||
@@ -191,42 +216,18 @@ jobs:
|
|||||||
elif [[ -n "${{ github.event.inputs.package }}" ]]; then
|
elif [[ -n "${{ github.event.inputs.package }}" ]]; then
|
||||||
# Manual workflow dispatch
|
# Manual workflow dispatch
|
||||||
|
|
||||||
# Determine version for dms stable
|
# Determine version for dms stable and dms-greeter using the API
|
||||||
if [[ "${{ github.event.inputs.package }}" == "dms" ]]; then
|
# GITHUB_REF is unreliable when "Use workflow from" a tag; API works from any ref
|
||||||
# Use github.ref if tag selected, otherwise auto-detect latest
|
if [[ "${{ github.event.inputs.package }}" == "dms" ]] || [[ "${{ github.event.inputs.package }}" == "dms-greeter" ]] || [[ "${{ github.event.inputs.package }}" == "all" ]]; then
|
||||||
if [[ "${{ github.ref }}" =~ ^refs/tags/ ]]; then
|
|
||||||
VERSION="${GITHUB_REF#refs/tags/}"
|
|
||||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
||||||
echo "Using tag from GITHUB_REF: $VERSION"
|
|
||||||
else
|
|
||||||
# Auto-detect latest release for dms
|
|
||||||
LATEST_TAG=$(curl -s https://api.github.com/repos/AvengeMedia/DankMaterialShell/releases/latest | grep '"tag_name"' | sed 's/.*"tag_name": "\([^"]*\)".*/\1/' || echo "")
|
LATEST_TAG=$(curl -s https://api.github.com/repos/AvengeMedia/DankMaterialShell/releases/latest | grep '"tag_name"' | sed 's/.*"tag_name": "\([^"]*\)".*/\1/' || echo "")
|
||||||
if [[ -n "$LATEST_TAG" ]]; then
|
if [[ -n "$LATEST_TAG" ]]; then
|
||||||
echo "version=$LATEST_TAG" >> $GITHUB_OUTPUT
|
echo "version=$LATEST_TAG" >> $GITHUB_OUTPUT
|
||||||
echo "Auto-detected latest release: $LATEST_TAG"
|
echo "Using latest release from API: $LATEST_TAG"
|
||||||
else
|
else
|
||||||
echo "ERROR: Could not auto-detect latest release"
|
echo "ERROR: Could not fetch latest release from API"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
elif [[ "${{ github.event.inputs.package }}" == "all" ]]; then
|
|
||||||
# Use github.ref if tag selected, otherwise auto-detect latest
|
|
||||||
if [[ "${{ github.ref }}" =~ ^refs/tags/ ]]; then
|
|
||||||
VERSION="${GITHUB_REF#refs/tags/}"
|
|
||||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
||||||
echo "Using tag from GITHUB_REF: $VERSION"
|
|
||||||
else
|
|
||||||
# Auto-detect latest release for "all"
|
|
||||||
LATEST_TAG=$(curl -s https://api.github.com/repos/AvengeMedia/DankMaterialShell/releases/latest | grep '"tag_name"' | sed 's/.*"tag_name": "\([^"]*\)".*/\1/' || echo "")
|
|
||||||
if [[ -n "$LATEST_TAG" ]]; then
|
|
||||||
echo "version=$LATEST_TAG" >> $GITHUB_OUTPUT
|
|
||||||
echo "Auto-detected latest release: $LATEST_TAG"
|
|
||||||
else
|
|
||||||
echo "ERROR: Could not auto-detect latest release"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Use filtered packages from check-updates when package="all" and no rebuild/tag specified
|
# Use filtered packages from check-updates when package="all" and no rebuild/tag specified
|
||||||
if [[ "${{ github.event.inputs.package }}" == "all" ]] && [[ -z "${{ github.event.inputs.rebuild_release }}" ]] && [[ ! "${{ github.ref }}" =~ ^refs/tags/ ]]; then
|
if [[ "${{ github.event.inputs.package }}" == "all" ]] && [[ -z "${{ github.event.inputs.rebuild_release }}" ]] && [[ ! "${{ github.ref }}" =~ ^refs/tags/ ]]; then
|
||||||
@@ -283,21 +284,20 @@ jobs:
|
|||||||
echo " -- Avenge Media <AvengeMedia.US@gmail.com> $CHANGELOG_DATE"
|
echo " -- Avenge Media <AvengeMedia.US@gmail.com> $CHANGELOG_DATE"
|
||||||
} > "distro/debian/dms-git/debian/changelog"
|
} > "distro/debian/dms-git/debian/changelog"
|
||||||
|
|
||||||
- name: Update dms stable version
|
- name: Update stable version (dms + dms-greeter)
|
||||||
if: steps.packages.outputs.version != ''
|
if: steps.packages.outputs.version != ''
|
||||||
run: |
|
run: |
|
||||||
VERSION="${{ steps.packages.outputs.version }}"
|
VERSION="${{ steps.packages.outputs.version }}"
|
||||||
VERSION_NO_V="${VERSION#v}"
|
VERSION_NO_V="${VERSION#v}"
|
||||||
|
PACKAGES="${{ steps.packages.outputs.packages }}"
|
||||||
echo "==> Updating packaging files to version: $VERSION_NO_V"
|
echo "==> Updating packaging files to version: $VERSION_NO_V"
|
||||||
|
|
||||||
# Update spec file
|
# Update dms spec and changelog when dms is in the upload list
|
||||||
|
if [[ "$PACKAGES" == *"dms"* ]]; then
|
||||||
sed -i "s/^Version:.*/Version: $VERSION_NO_V/" distro/opensuse/dms.spec
|
sed -i "s/^Version:.*/Version: $VERSION_NO_V/" distro/opensuse/dms.spec
|
||||||
|
|
||||||
# Verify the update
|
|
||||||
UPDATED_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1)
|
UPDATED_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1)
|
||||||
echo "✓ Spec file now shows Version: $UPDATED_VERSION"
|
echo "✓ dms spec now shows Version: $UPDATED_VERSION"
|
||||||
|
|
||||||
# Single changelog entry (full history on OBS website)
|
|
||||||
DATE_STR=$(date "+%a %b %d %Y")
|
DATE_STR=$(date "+%a %b %d %Y")
|
||||||
LOCAL_SPEC_HEAD=$(sed -n '1,/%changelog/{ /%changelog/d; p }' distro/opensuse/dms.spec)
|
LOCAL_SPEC_HEAD=$(sed -n '1,/%changelog/{ /%changelog/d; p }' distro/opensuse/dms.spec)
|
||||||
{
|
{
|
||||||
@@ -307,19 +307,6 @@ jobs:
|
|||||||
echo "- Update to stable $VERSION release"
|
echo "- Update to stable $VERSION release"
|
||||||
} > distro/opensuse/dms.spec
|
} > distro/opensuse/dms.spec
|
||||||
|
|
||||||
# Update Debian _service files (both tar_scm and download_url formats)
|
|
||||||
for service in distro/debian/*/_service; do
|
|
||||||
if [[ -f "$service" ]]; then
|
|
||||||
# Update tar_scm revision parameter (for dms-git)
|
|
||||||
sed -i "s|<param name=\"revision\">v[0-9.]*</param>|<param name=\"revision\">$VERSION</param>|" "$service"
|
|
||||||
|
|
||||||
# Update download_url paths (for dms stable)
|
|
||||||
sed -i "s|/v[0-9.]\+/|/$VERSION/|g" "$service"
|
|
||||||
sed -i "s|/tags/v[0-9.]\+\.tar\.gz|/tags/$VERSION.tar.gz|g" "$service"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Update Debian changelog for dms stable (single entry, history on OBS website)
|
|
||||||
if [[ -f "distro/debian/dms/debian/changelog" ]]; then
|
if [[ -f "distro/debian/dms/debian/changelog" ]]; then
|
||||||
CHANGELOG_DATE=$(date -R)
|
CHANGELOG_DATE=$(date -R)
|
||||||
{
|
{
|
||||||
@@ -329,8 +316,33 @@ jobs:
|
|||||||
echo ""
|
echo ""
|
||||||
echo " -- Avenge Media <AvengeMedia.US@gmail.com> $CHANGELOG_DATE"
|
echo " -- Avenge Media <AvengeMedia.US@gmail.com> $CHANGELOG_DATE"
|
||||||
} > "distro/debian/dms/debian/changelog"
|
} > "distro/debian/dms/debian/changelog"
|
||||||
echo "✓ Updated Debian changelog to ${VERSION_NO_V}db1"
|
echo "✓ Updated dms changelog to ${VERSION_NO_V}db1"
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update dms-greeter changelog when dms-greeter is in the upload list
|
||||||
|
if [[ "$PACKAGES" == *"dms-greeter"* ]] && [[ -f "distro/debian/dms-greeter/debian/changelog" ]]; then
|
||||||
|
CHANGELOG_DATE=$(date -R)
|
||||||
|
{
|
||||||
|
echo "dms-greeter (${VERSION_NO_V}db1) unstable; urgency=medium"
|
||||||
|
echo ""
|
||||||
|
echo " * Update to $VERSION stable release"
|
||||||
|
echo ""
|
||||||
|
echo " -- Avenge Media <AvengeMedia.US@gmail.com> $CHANGELOG_DATE"
|
||||||
|
} > "distro/debian/dms-greeter/debian/changelog"
|
||||||
|
echo "✓ Updated dms-greeter changelog to ${VERSION_NO_V}db1"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update Debian _service files for packages in upload list (download_url paths)
|
||||||
|
for service in distro/debian/*/_service; do
|
||||||
|
if [[ -f "$service" ]]; then
|
||||||
|
# Update tar_scm revision parameter (for dms-git)
|
||||||
|
sed -i "s|<param name=\"revision\">v[0-9.]*</param>|<param name=\"revision\">$VERSION</param>|" "$service"
|
||||||
|
# Update download_url paths (for dms, dms-greeter stable)
|
||||||
|
sed -i "s|/v[0-9.]\+/|/$VERSION/|g" "$service"
|
||||||
|
sed -i "s|/tags/v[0-9.]\+\.tar\.gz|/tags/$VERSION.tar.gz|g" "$service"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
@@ -354,6 +366,7 @@ jobs:
|
|||||||
chmod 600 ~/.config/osc/oscrc
|
chmod 600 ~/.config/osc/oscrc
|
||||||
|
|
||||||
- name: Upload to OBS
|
- name: Upload to OBS
|
||||||
|
id: upload
|
||||||
env:
|
env:
|
||||||
REBUILD_RELEASE: ${{ github.event.inputs.rebuild_release }}
|
REBUILD_RELEASE: ${{ github.event.inputs.rebuild_release }}
|
||||||
TAG_VERSION: ${{ steps.packages.outputs.version }}
|
TAG_VERSION: ${{ steps.packages.outputs.version }}
|
||||||
@@ -362,6 +375,8 @@ jobs:
|
|||||||
|
|
||||||
if [[ -z "$PACKAGES" ]]; then
|
if [[ -z "$PACKAGES" ]]; then
|
||||||
echo "✓ No packages need uploading. All up to date!"
|
echo "✓ No packages need uploading. All up to date!"
|
||||||
|
echo "uploaded_packages=" >> $GITHUB_OUTPUT
|
||||||
|
echo "skipped_packages=" >> $GITHUB_OUTPUT
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -371,6 +386,9 @@ jobs:
|
|||||||
echo "==> Version being uploaded: ${{ steps.packages.outputs.version }}"
|
echo "==> Version being uploaded: ${{ steps.packages.outputs.version }}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
UPLOADED_PACKAGES=()
|
||||||
|
SKIPPED_PACKAGES=()
|
||||||
|
|
||||||
# PACKAGES can be space-separated list (e.g., "dms-git dms" from "all" check)
|
# PACKAGES can be space-separated list (e.g., "dms-git dms" from "all" check)
|
||||||
# Loop through each package and upload
|
# Loop through each package and upload
|
||||||
for PKG in $PACKAGES; do
|
for PKG in $PACKAGES; do
|
||||||
@@ -382,13 +400,37 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
|
||||||
|
LOG_FILE=$(mktemp)
|
||||||
|
set +e
|
||||||
if [[ "$PKG" == "dms-git" ]]; then
|
if [[ "$PKG" == "dms-git" ]]; then
|
||||||
bash distro/scripts/obs-upload.sh dms-git "Automated git update"
|
bash distro/scripts/obs-upload.sh dms-git "Automated git update" >"$LOG_FILE" 2>&1
|
||||||
else
|
else
|
||||||
bash distro/scripts/obs-upload.sh "$PKG" "$MESSAGE"
|
bash distro/scripts/obs-upload.sh "$PKG" "$MESSAGE" >"$LOG_FILE" 2>&1
|
||||||
fi
|
fi
|
||||||
|
STATUS=$?
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cat "$LOG_FILE"
|
||||||
|
|
||||||
|
if [[ $STATUS -ne 0 ]]; then
|
||||||
|
rm -f "$LOG_FILE"
|
||||||
|
echo "❌ Upload failed for $PKG"
|
||||||
|
exit $STATUS
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -Eq "Exiting gracefully \(no changes needed\)|No changes needed for this package\. Exiting gracefully\." "$LOG_FILE"; then
|
||||||
|
echo "ℹ️ $PKG is already up to date. Skipped."
|
||||||
|
SKIPPED_PACKAGES+=("$PKG")
|
||||||
|
else
|
||||||
|
UPLOADED_PACKAGES+=("$PKG")
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "$LOG_FILE"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
echo "uploaded_packages=${UPLOADED_PACKAGES[*]}" >> $GITHUB_OUTPUT
|
||||||
|
echo "skipped_packages=${SKIPPED_PACKAGES[*]}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Summary
|
- name: Summary
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
@@ -402,20 +444,59 @@ jobs:
|
|||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "All packages are current. Run completed successfully." >> $GITHUB_STEP_SUMMARY
|
echo "All packages are current. Run completed successfully." >> $GITHUB_STEP_SUMMARY
|
||||||
else
|
else
|
||||||
echo "**Packages Uploaded:**" >> $GITHUB_STEP_SUMMARY
|
UPLOADED_PACKAGES="${{ steps.upload.outputs.uploaded_packages }}"
|
||||||
|
SKIPPED_PACKAGES="${{ steps.upload.outputs.skipped_packages }}"
|
||||||
|
TOTAL_COUNT=$(wc -w <<<"$PACKAGES" | tr -d ' ')
|
||||||
|
UPLOADED_COUNT=0
|
||||||
|
SKIPPED_COUNT=0
|
||||||
|
if [[ -n "$UPLOADED_PACKAGES" ]]; then
|
||||||
|
UPLOADED_COUNT=$(wc -w <<<"$UPLOADED_PACKAGES" | tr -d ' ')
|
||||||
|
fi
|
||||||
|
if [[ -n "$SKIPPED_PACKAGES" ]]; then
|
||||||
|
SKIPPED_COUNT=$(wc -w <<<"$SKIPPED_PACKAGES" | tr -d ' ')
|
||||||
|
fi
|
||||||
|
in_list() {
|
||||||
|
local item="$1"
|
||||||
|
local list="$2"
|
||||||
|
[[ " $list " == *" $item "* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "${{ job.status }}" == "success" ]]; then
|
||||||
|
echo "**Status:** ✅ Completed successfully" >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
echo "**Status:** ❌ Completed with errors" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "**Processed:** $TOTAL_COUNT package(s)" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "**Uploaded:** $UPLOADED_COUNT package(s)" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "**Skipped (up to date):** $SKIPPED_COUNT package(s)" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "**Packages:**" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
for PKG in $PACKAGES; do
|
for PKG in $PACKAGES; do
|
||||||
|
STATUS_ICON="✅"
|
||||||
|
STATUS_TEXT="uploaded"
|
||||||
|
if in_list "$PKG" "$SKIPPED_PACKAGES"; then
|
||||||
|
STATUS_ICON="ℹ️"
|
||||||
|
STATUS_TEXT="up to date (skipped)"
|
||||||
|
elif ! in_list "$PKG" "$UPLOADED_PACKAGES"; then
|
||||||
|
STATUS_ICON="❌"
|
||||||
|
STATUS_TEXT="failed"
|
||||||
|
fi
|
||||||
|
|
||||||
case "$PKG" in
|
case "$PKG" in
|
||||||
dms)
|
dms)
|
||||||
echo "- ✅ **dms** → [View builds](https://build.opensuse.org/package/show/home:AvengeMedia:dms/dms)" >> $GITHUB_STEP_SUMMARY
|
echo "- $STATUS_ICON **dms** ($STATUS_TEXT) → [View builds](https://build.opensuse.org/package/show/home:AvengeMedia:dms/dms)" >> $GITHUB_STEP_SUMMARY
|
||||||
;;
|
;;
|
||||||
dms-git)
|
dms-git)
|
||||||
echo "- ✅ **dms-git** → [View builds](https://build.opensuse.org/package/show/home:AvengeMedia:dms-git/dms-git)" >> $GITHUB_STEP_SUMMARY
|
echo "- $STATUS_ICON **dms-git** ($STATUS_TEXT) → [View builds](https://build.opensuse.org/package/show/home:AvengeMedia:dms-git/dms-git)" >> $GITHUB_STEP_SUMMARY
|
||||||
|
;;
|
||||||
|
dms-greeter)
|
||||||
|
echo "- $STATUS_ICON **dms-greeter** ($STATUS_TEXT) → [View builds](https://build.opensuse.org/package/show/home:AvengeMedia:danklinux/dms-greeter)" >> $GITHUB_STEP_SUMMARY
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
if [[ -n "${{ github.event.inputs.rebuild_release }}" ]]; then
|
if [[ -n "${{ github.event.inputs.rebuild_release }}" ]]; then
|
||||||
|
|||||||
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"
|
echo "Build succeeded, no hash update needed"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
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; }
|
[ -n "$new_hash" ] || { echo "Could not extract new vendorHash"; echo "$output"; exit 1; }
|
||||||
current_hash=$(grep -oP 'vendorHash = "\K[^"]+' flake.nix)
|
current_hash=$(grep -oP 'vendorHash = "\K[^"]+' flake.nix)
|
||||||
[ "$current_hash" = "$new_hash" ] && { echo "vendorHash already up to date"; exit 0; }
|
[ "$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 config user.email "dms-ci[bot]@users.noreply.github.com"
|
||||||
git add flake.nix
|
git add flake.nix
|
||||||
git commit -m "nix: update vendorHash for go.mod changes" || exit 0
|
git commit -m "nix: update vendorHash for go.mod changes" || exit 0
|
||||||
git pull --rebase origin master
|
git pull --rebase origin ${{ github.ref_name }}
|
||||||
git push https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git HEAD:master
|
git push https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git HEAD:${{ github.ref_name }}
|
||||||
else
|
else
|
||||||
echo "No changes to flake.nix"
|
echo "No changes to flake.nix"
|
||||||
fi
|
fi
|
||||||
|
|||||||
10
core/cmd/dms/assets/cli-policy.default.json
Normal file
10
core/cmd/dms/assets/cli-policy.default.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"policy_version": 1,
|
||||||
|
"blocked_commands": [
|
||||||
|
"greeter install",
|
||||||
|
"greeter enable",
|
||||||
|
"greeter uninstall",
|
||||||
|
"setup"
|
||||||
|
],
|
||||||
|
"message": "This command is disabled on immutable/image-based systems. Use your distro-native workflow for system-level changes."
|
||||||
|
}
|
||||||
@@ -222,16 +222,19 @@ func init() {
|
|||||||
|
|
||||||
func runClipCopy(cmd *cobra.Command, args []string) {
|
func runClipCopy(cmd *cobra.Command, args []string) {
|
||||||
var data []byte
|
var data []byte
|
||||||
|
copyFromStdin := false
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case len(args) > 0:
|
case len(args) > 0:
|
||||||
data = []byte(args[0])
|
data = []byte(args[0])
|
||||||
default:
|
case clipCopyDownload || clipCopyType == "__multi__":
|
||||||
var err error
|
var err error
|
||||||
data, err = io.ReadAll(os.Stdin)
|
data, err = io.ReadAll(os.Stdin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("read stdin: %v", err)
|
log.Fatalf("read stdin: %v", err)
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
copyFromStdin = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if clipCopyDownload {
|
if clipCopyDownload {
|
||||||
@@ -257,6 +260,13 @@ func runClipCopy(cmd *cobra.Command, args []string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if copyFromStdin {
|
||||||
|
if err := clipboard.CopyReader(os.Stdin, clipCopyType, clipCopyForeground, clipCopyPasteOnce); err != nil {
|
||||||
|
log.Fatalf("copy: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err := clipboard.CopyOpts(data, clipCopyType, clipCopyForeground, clipCopyPasteOnce); err != nil {
|
if err := clipboard.CopyOpts(data, clipCopyType, clipCopyForeground, clipCopyPasteOnce); err != nil {
|
||||||
log.Fatalf("copy: %v", err)
|
log.Fatalf("copy: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -525,5 +525,6 @@ func getCommonCommands() []*cobra.Command {
|
|||||||
doctorCmd,
|
doctorCmd,
|
||||||
configCmd,
|
configCmd,
|
||||||
dlCmd,
|
dlCmd,
|
||||||
|
randrCmd,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -652,16 +652,14 @@ func checkI2CAvailability() checkResult {
|
|||||||
func checkImageFormatPlugins() []checkResult {
|
func checkImageFormatPlugins() []checkResult {
|
||||||
url := doctorDocsURL + "#optional-features"
|
url := doctorDocsURL + "#optional-features"
|
||||||
|
|
||||||
pluginDir := findQtPluginDir()
|
pluginDirs := findQtPluginDirs()
|
||||||
if pluginDir == "" {
|
if len(pluginDirs) == 0 {
|
||||||
return []checkResult{
|
return []checkResult{
|
||||||
{catOptionalFeatures, "qt6-imageformats", statusInfo, "Cannot detect (plugin dir not found)", "WebP, TIFF, JP2 support", url},
|
{catOptionalFeatures, "qt6-imageformats", statusInfo, "Cannot detect (plugin dir not found)", "WebP, TIFF, JP2 support", url},
|
||||||
{catOptionalFeatures, "kimageformats", statusInfo, "Cannot detect (plugin dir not found)", "AVIF, HEIF, JXL support", url},
|
{catOptionalFeatures, "kimageformats", statusInfo, "Cannot detect (plugin dir not found)", "AVIF, HEIF, JXL support", url},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
imageFormatsDir := filepath.Join(pluginDir, "imageformats")
|
|
||||||
|
|
||||||
type pluginCheck struct {
|
type pluginCheck struct {
|
||||||
name string
|
name string
|
||||||
desc string
|
desc string
|
||||||
@@ -695,10 +693,19 @@ func checkImageFormatPlugins() []checkResult {
|
|||||||
var results []checkResult
|
var results []checkResult
|
||||||
for _, c := range checks {
|
for _, c := range checks {
|
||||||
var found []string
|
var found []string
|
||||||
|
var foundDirs []string
|
||||||
|
for _, pluginDir := range pluginDirs {
|
||||||
|
imageFormatsDir := filepath.Join(pluginDir, "imageformats")
|
||||||
for _, p := range c.plugins {
|
for _, p := range c.plugins {
|
||||||
if _, err := os.Stat(filepath.Join(imageFormatsDir, p.file)); err == nil {
|
if _, err := os.Stat(filepath.Join(imageFormatsDir, p.file)); err == nil {
|
||||||
|
if !slices.Contains(found, p.format) {
|
||||||
found = append(found, p.format)
|
found = append(found, p.format)
|
||||||
}
|
}
|
||||||
|
if !slices.Contains(foundDirs, imageFormatsDir) {
|
||||||
|
foundDirs = append(foundDirs, imageFormatsDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var result checkResult
|
var result checkResult
|
||||||
@@ -708,7 +715,7 @@ func checkImageFormatPlugins() []checkResult {
|
|||||||
default:
|
default:
|
||||||
details := ""
|
details := ""
|
||||||
if doctorVerbose {
|
if doctorVerbose {
|
||||||
details = fmt.Sprintf("Formats: %s (%s)", strings.Join(found, ", "), imageFormatsDir)
|
details = fmt.Sprintf("Formats: %s (%s)", strings.Join(found, ", "), strings.Join(foundDirs, ":"))
|
||||||
}
|
}
|
||||||
result = checkResult{catOptionalFeatures, c.name, statusOK, fmt.Sprintf("Installed (%d formats)", len(found)), details, url}
|
result = checkResult{catOptionalFeatures, c.name, statusOK, fmt.Sprintf("Installed (%d formats)", len(found)), details, url}
|
||||||
}
|
}
|
||||||
@@ -718,22 +725,28 @@ func checkImageFormatPlugins() []checkResult {
|
|||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
func findQtPluginDir() string {
|
func findQtPluginDirs() []string {
|
||||||
// Check QT_PLUGIN_PATH env var first (used by NixOS and custom setups)
|
var dirs []string
|
||||||
|
|
||||||
|
addDir := func(dir string) {
|
||||||
|
if dir != "" {
|
||||||
|
if _, err := os.Stat(filepath.Join(dir, "imageformats")); err == nil {
|
||||||
|
dirs = append(dirs, dir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check all paths in QT_PLUGIN_PATH env var (used by NixOS and custom setups)
|
||||||
if envPath := os.Getenv("QT_PLUGIN_PATH"); envPath != "" {
|
if envPath := os.Getenv("QT_PLUGIN_PATH"); envPath != "" {
|
||||||
for dir := range strings.SplitSeq(envPath, ":") {
|
for dir := range strings.SplitSeq(envPath, ":") {
|
||||||
if _, err := os.Stat(filepath.Join(dir, "imageformats")); err == nil {
|
addDir(dir)
|
||||||
return dir
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try qtpaths
|
// Try qtpaths
|
||||||
for _, cmd := range []string{"qtpaths6", "qtpaths"} {
|
for _, cmd := range []string{"qtpaths6", "qtpaths"} {
|
||||||
if output, err := exec.Command(cmd, "-query", "QT_INSTALL_PLUGINS").Output(); err == nil {
|
if output, err := exec.Command(cmd, "-query", "QT_INSTALL_PLUGINS").Output(); err == nil {
|
||||||
if dir := strings.TrimSpace(string(output)); dir != "" {
|
addDir(strings.TrimSpace(string(output)))
|
||||||
return dir
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -744,12 +757,10 @@ func findQtPluginDir() string {
|
|||||||
"/usr/lib/x86_64-linux-gnu/qt6/plugins",
|
"/usr/lib/x86_64-linux-gnu/qt6/plugins",
|
||||||
"/usr/lib/aarch64-linux-gnu/qt6/plugins",
|
"/usr/lib/aarch64-linux-gnu/qt6/plugins",
|
||||||
} {
|
} {
|
||||||
if _, err := os.Stat(filepath.Join(dir, "imageformats")); err == nil {
|
addDir(dir)
|
||||||
return dir
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ""
|
return dirs
|
||||||
}
|
}
|
||||||
|
|
||||||
func detectNetworkBackend(stackResult *network.DetectResult) string {
|
func detectNetworkBackend(stackResult *network.DetectResult) string {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,9 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
@@ -95,7 +97,11 @@ func buildMatugenOptions(cmd *cobra.Command) matugen.Options {
|
|||||||
|
|
||||||
func runMatugenGenerate(cmd *cobra.Command, args []string) {
|
func runMatugenGenerate(cmd *cobra.Command, args []string) {
|
||||||
opts := buildMatugenOptions(cmd)
|
opts := buildMatugenOptions(cmd)
|
||||||
if err := matugen.Run(opts); err != nil {
|
err := matugen.Run(opts)
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, matugen.ErrNoChanges):
|
||||||
|
os.Exit(2)
|
||||||
|
case err != nil:
|
||||||
log.Fatalf("Theme generation failed: %v", err)
|
log.Fatalf("Theme generation failed: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,7 +135,11 @@ func runMatugenQueue(cmd *cobra.Command, args []string) {
|
|||||||
if !wait {
|
if !wait {
|
||||||
if err := sendServerRequestFireAndForget(request); err != nil {
|
if err := sendServerRequestFireAndForget(request); err != nil {
|
||||||
log.Info("Server unavailable, running synchronously")
|
log.Info("Server unavailable, running synchronously")
|
||||||
if err := matugen.Run(opts); err != nil {
|
err := matugen.Run(opts)
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, matugen.ErrNoChanges):
|
||||||
|
os.Exit(2)
|
||||||
|
case err != nil:
|
||||||
log.Fatalf("Theme generation failed: %v", err)
|
log.Fatalf("Theme generation failed: %v", err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -146,11 +156,15 @@ func runMatugenQueue(cmd *cobra.Command, args []string) {
|
|||||||
resp, ok := tryServerRequest(request)
|
resp, ok := tryServerRequest(request)
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Info("Server unavailable, running synchronously")
|
log.Info("Server unavailable, running synchronously")
|
||||||
if err := matugen.Run(opts); err != nil {
|
err := matugen.Run(opts)
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, matugen.ErrNoChanges):
|
||||||
|
resultCh <- matugen.ErrNoChanges
|
||||||
|
case err != nil:
|
||||||
resultCh <- err
|
resultCh <- err
|
||||||
return
|
default:
|
||||||
}
|
|
||||||
resultCh <- nil
|
resultCh <- nil
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if resp.Error != "" {
|
if resp.Error != "" {
|
||||||
@@ -162,7 +176,10 @@ func runMatugenQueue(cmd *cobra.Command, args []string) {
|
|||||||
|
|
||||||
select {
|
select {
|
||||||
case err := <-resultCh:
|
case err := <-resultCh:
|
||||||
if err != nil {
|
switch {
|
||||||
|
case errors.Is(err, matugen.ErrNoChanges):
|
||||||
|
os.Exit(2)
|
||||||
|
case err != nil:
|
||||||
log.Fatalf("Theme generation failed: %v", err)
|
log.Fatalf("Theme generation failed: %v", err)
|
||||||
}
|
}
|
||||||
fmt.Println("Theme generation completed")
|
fmt.Println("Theme generation completed")
|
||||||
|
|||||||
58
core/cmd/dms/commands_randr.go
Normal file
58
core/cmd/dms/commands_randr.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var randrCmd = &cobra.Command{
|
||||||
|
Use: "randr",
|
||||||
|
Short: "Query output display information",
|
||||||
|
Long: "Query Wayland compositor for output names, scales, resolutions and refresh rates via zwlr-output-management",
|
||||||
|
Run: runRandr,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
randrCmd.Flags().Bool("json", false, "Output in JSON format")
|
||||||
|
}
|
||||||
|
|
||||||
|
type randrJSON struct {
|
||||||
|
Outputs []randrOutput `json:"outputs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func runRandr(cmd *cobra.Command, args []string) {
|
||||||
|
outputs, err := queryRandr()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonFlag, _ := cmd.Flags().GetBool("json")
|
||||||
|
|
||||||
|
if jsonFlag {
|
||||||
|
data, err := json.Marshal(randrJSON{Outputs: outputs})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to marshal JSON: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Println(string(data))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, out := range outputs {
|
||||||
|
if i > 0 {
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
status := "enabled"
|
||||||
|
if !out.Enabled {
|
||||||
|
status = "disabled"
|
||||||
|
}
|
||||||
|
fmt.Printf("%s (%s)\n", out.Name, status)
|
||||||
|
fmt.Printf(" Scale: %.4g\n", out.Scale)
|
||||||
|
fmt.Printf(" Resolution: %dx%d\n", out.Width, out.Height)
|
||||||
|
if out.Refresh > 0 {
|
||||||
|
fmt.Printf(" Refresh: %.2f Hz\n", float64(out.Refresh)/1000.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ var setupCmd = &cobra.Command{
|
|||||||
Use: "setup",
|
Use: "setup",
|
||||||
Short: "Deploy DMS configurations",
|
Short: "Deploy DMS configurations",
|
||||||
Long: "Deploy compositor and terminal configurations with interactive prompts",
|
Long: "Deploy compositor and terminal configurations with interactive prompts",
|
||||||
|
PersistentPreRunE: requireMutableSystemCommand,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
if err := runSetup(); err != nil {
|
if err := runSetup(); err != nil {
|
||||||
log.Fatalf("Error during setup: %v", err)
|
log.Fatalf("Error during setup: %v", err)
|
||||||
|
|||||||
271
core/cmd/dms/immutable_policy.go
Normal file
271
core/cmd/dms/immutable_policy.go
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
_ "embed"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
cliPolicyPackagedPath = "/usr/share/dms/cli-policy.json"
|
||||||
|
cliPolicyAdminPath = "/etc/dms/cli-policy.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
immutablePolicyOnce sync.Once
|
||||||
|
immutablePolicy immutableCommandPolicy
|
||||||
|
immutablePolicyErr error
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed assets/cli-policy.default.json
|
||||||
|
var defaultCLIPolicyJSON []byte
|
||||||
|
|
||||||
|
type immutableCommandPolicy struct {
|
||||||
|
ImmutableSystem bool
|
||||||
|
ImmutableReason string
|
||||||
|
BlockedCommands []string
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
type cliPolicyFile struct {
|
||||||
|
PolicyVersion int `json:"policy_version"`
|
||||||
|
ImmutableSystem *bool `json:"immutable_system"`
|
||||||
|
BlockedCommands *[]string `json:"blocked_commands"`
|
||||||
|
Message *string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeCommandSpec(raw string) string {
|
||||||
|
normalized := strings.ToLower(strings.TrimSpace(raw))
|
||||||
|
normalized = strings.TrimPrefix(normalized, "dms ")
|
||||||
|
return strings.Join(strings.Fields(normalized), " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeBlockedCommands(raw []string) []string {
|
||||||
|
normalized := make([]string, 0, len(raw))
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
|
||||||
|
for _, cmd := range raw {
|
||||||
|
spec := normalizeCommandSpec(cmd)
|
||||||
|
if spec == "" || seen[spec] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[spec] = true
|
||||||
|
normalized = append(normalized, spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized
|
||||||
|
}
|
||||||
|
|
||||||
|
func commandBlockedByPolicy(commandPath string, blocked []string) bool {
|
||||||
|
normalizedPath := normalizeCommandSpec(commandPath)
|
||||||
|
if normalizedPath == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range blocked {
|
||||||
|
spec := normalizeCommandSpec(entry)
|
||||||
|
if spec == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if normalizedPath == spec || strings.HasPrefix(normalizedPath, spec+" ") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadPolicyFile(path string) (*cliPolicyFile, error) {
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("failed to read %s: %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var policy cliPolicyFile
|
||||||
|
if err := json.Unmarshal(data, &policy); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse %s: %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &policy, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergePolicyFile(base *immutableCommandPolicy, path string) error {
|
||||||
|
policyFile, err := loadPolicyFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if policyFile == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if policyFile.ImmutableSystem != nil {
|
||||||
|
base.ImmutableSystem = *policyFile.ImmutableSystem
|
||||||
|
}
|
||||||
|
if policyFile.BlockedCommands != nil {
|
||||||
|
base.BlockedCommands = normalizeBlockedCommands(*policyFile.BlockedCommands)
|
||||||
|
}
|
||||||
|
if policyFile.Message != nil {
|
||||||
|
msg := strings.TrimSpace(*policyFile.Message)
|
||||||
|
if msg != "" {
|
||||||
|
base.Message = msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readOSReleaseMap(path string) map[string]string {
|
||||||
|
values := make(map[string]string)
|
||||||
|
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
if line == "" || strings.HasPrefix(line, "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts := strings.SplitN(line, "=", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key := strings.ToUpper(strings.TrimSpace(parts[0]))
|
||||||
|
value := strings.Trim(strings.TrimSpace(parts[1]), "\"")
|
||||||
|
values[key] = strings.ToLower(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasAnyToken(text string, tokens ...string) bool {
|
||||||
|
if text == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, token := range tokens {
|
||||||
|
if strings.Contains(text, token) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectImmutableSystem() (bool, string) {
|
||||||
|
if _, err := os.Stat("/run/ostree-booted"); err == nil {
|
||||||
|
return true, "/run/ostree-booted is present"
|
||||||
|
}
|
||||||
|
|
||||||
|
osRelease := readOSReleaseMap("/etc/os-release")
|
||||||
|
if len(osRelease) == 0 {
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
id := osRelease["ID"]
|
||||||
|
idLike := osRelease["ID_LIKE"]
|
||||||
|
variantID := osRelease["VARIANT_ID"]
|
||||||
|
name := osRelease["NAME"]
|
||||||
|
prettyName := osRelease["PRETTY_NAME"]
|
||||||
|
|
||||||
|
immutableIDs := map[string]bool{
|
||||||
|
"bluefin": true,
|
||||||
|
"bazzite": true,
|
||||||
|
"silverblue": true,
|
||||||
|
"kinoite": true,
|
||||||
|
"sericea": true,
|
||||||
|
"onyx": true,
|
||||||
|
"aurora": true,
|
||||||
|
"fedora-iot": true,
|
||||||
|
"fedora-coreos": true,
|
||||||
|
}
|
||||||
|
if immutableIDs[id] {
|
||||||
|
return true, "os-release ID=" + id
|
||||||
|
}
|
||||||
|
|
||||||
|
markers := []string{"silverblue", "kinoite", "sericea", "onyx", "bazzite", "bluefin", "aurora", "ostree", "atomic"}
|
||||||
|
if hasAnyToken(variantID, markers...) {
|
||||||
|
return true, "os-release VARIANT_ID=" + variantID
|
||||||
|
}
|
||||||
|
if hasAnyToken(idLike, "ostree", "rpm-ostree") {
|
||||||
|
return true, "os-release ID_LIKE=" + idLike
|
||||||
|
}
|
||||||
|
if hasAnyToken(name, markers...) || hasAnyToken(prettyName, markers...) {
|
||||||
|
return true, "os-release identifies an atomic/ostree variant"
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func getImmutablePolicy() (*immutableCommandPolicy, error) {
|
||||||
|
immutablePolicyOnce.Do(func() {
|
||||||
|
detectedImmutable, reason := detectImmutableSystem()
|
||||||
|
immutablePolicy = immutableCommandPolicy{
|
||||||
|
ImmutableSystem: detectedImmutable,
|
||||||
|
ImmutableReason: reason,
|
||||||
|
BlockedCommands: []string{"greeter install", "greeter enable", "setup"},
|
||||||
|
Message: "This command is disabled on immutable/image-based systems. Use your distro-native workflow for system-level changes.",
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultPolicy cliPolicyFile
|
||||||
|
if err := json.Unmarshal(defaultCLIPolicyJSON, &defaultPolicy); err != nil {
|
||||||
|
immutablePolicyErr = fmt.Errorf("failed to parse embedded default CLI policy: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if defaultPolicy.BlockedCommands != nil {
|
||||||
|
immutablePolicy.BlockedCommands = normalizeBlockedCommands(*defaultPolicy.BlockedCommands)
|
||||||
|
}
|
||||||
|
if defaultPolicy.Message != nil {
|
||||||
|
msg := strings.TrimSpace(*defaultPolicy.Message)
|
||||||
|
if msg != "" {
|
||||||
|
immutablePolicy.Message = msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := mergePolicyFile(&immutablePolicy, cliPolicyPackagedPath); err != nil {
|
||||||
|
immutablePolicyErr = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := mergePolicyFile(&immutablePolicy, cliPolicyAdminPath); err != nil {
|
||||||
|
immutablePolicyErr = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if immutablePolicyErr != nil {
|
||||||
|
return nil, immutablePolicyErr
|
||||||
|
}
|
||||||
|
return &immutablePolicy, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func requireMutableSystemCommand(cmd *cobra.Command, _ []string) error {
|
||||||
|
policy, err := getImmutablePolicy()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !policy.ImmutableSystem {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
commandPath := normalizeCommandSpec(cmd.CommandPath())
|
||||||
|
if !commandBlockedByPolicy(commandPath, policy.BlockedCommands) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
reason := ""
|
||||||
|
if policy.ImmutableReason != "" {
|
||||||
|
reason = "Detected immutable system: " + policy.ImmutableReason + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("%s%s\nCommand: dms %s\nPolicy files:\n %s\n %s", reason, policy.Message, commandPath, cliPolicyPackagedPath, cliPolicyAdminPath)
|
||||||
|
}
|
||||||
@@ -16,19 +16,10 @@ func init() {
|
|||||||
runCmd.Flags().Bool("session", false, "Session managed (like as a systemd unit)")
|
runCmd.Flags().Bool("session", false, "Session managed (like as a systemd unit)")
|
||||||
runCmd.Flags().MarkHidden("daemon-child")
|
runCmd.Flags().MarkHidden("daemon-child")
|
||||||
|
|
||||||
// Add subcommands to greeter
|
greeterCmd.AddCommand(greeterInstallCmd, greeterSyncCmd, greeterEnableCmd, greeterStatusCmd, greeterUninstallCmd)
|
||||||
greeterCmd.AddCommand(greeterInstallCmd, greeterSyncCmd, greeterEnableCmd, greeterStatusCmd)
|
|
||||||
|
|
||||||
// Add subcommands to setup
|
|
||||||
setupCmd.AddCommand(setupBindsCmd, setupLayoutCmd, setupColorsCmd, setupAlttabCmd, setupOutputsCmd, setupCursorCmd, setupWindowrulesCmd)
|
setupCmd.AddCommand(setupBindsCmd, setupLayoutCmd, setupColorsCmd, setupAlttabCmd, setupOutputsCmd, setupCursorCmd, setupWindowrulesCmd)
|
||||||
|
|
||||||
// Add subcommands to update
|
|
||||||
updateCmd.AddCommand(updateCheckCmd)
|
updateCmd.AddCommand(updateCheckCmd)
|
||||||
|
|
||||||
// Add subcommands to plugins
|
|
||||||
pluginsCmd.AddCommand(pluginsBrowseCmd, pluginsListCmd, pluginsInstallCmd, pluginsUninstallCmd, pluginsUpdateCmd)
|
pluginsCmd.AddCommand(pluginsBrowseCmd, pluginsListCmd, pluginsInstallCmd, pluginsUninstallCmd, pluginsUpdateCmd)
|
||||||
|
|
||||||
// Add common commands to root
|
|
||||||
rootCmd.AddCommand(getCommonCommands()...)
|
rootCmd.AddCommand(getCommonCommands()...)
|
||||||
|
|
||||||
rootCmd.AddCommand(updateCmd)
|
rootCmd.AddCommand(updateCmd)
|
||||||
@@ -37,7 +28,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if os.Geteuid() == 0 {
|
if os.Geteuid() == 0 && !isReadOnlyCommand(os.Args) {
|
||||||
log.Fatal("This program should not be run as root. Exiting.")
|
log.Fatal("This program should not be run as root. Exiting.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,30 +11,21 @@ import (
|
|||||||
var Version = "dev"
|
var Version = "dev"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Add flags
|
|
||||||
runCmd.Flags().BoolP("daemon", "d", false, "Run in daemon mode")
|
runCmd.Flags().BoolP("daemon", "d", false, "Run in daemon mode")
|
||||||
runCmd.Flags().Bool("daemon-child", false, "Internal flag for daemon child process")
|
runCmd.Flags().Bool("daemon-child", false, "Internal flag for daemon child process")
|
||||||
runCmd.Flags().Bool("session", false, "Session managed (like as a systemd unit)")
|
runCmd.Flags().Bool("session", false, "Session managed (like as a systemd unit)")
|
||||||
runCmd.Flags().MarkHidden("daemon-child")
|
runCmd.Flags().MarkHidden("daemon-child")
|
||||||
|
|
||||||
// Add subcommands to greeter
|
greeterCmd.AddCommand(greeterInstallCmd, greeterSyncCmd, greeterEnableCmd, greeterStatusCmd, greeterUninstallCmd)
|
||||||
greeterCmd.AddCommand(greeterSyncCmd, greeterEnableCmd, greeterStatusCmd)
|
|
||||||
|
|
||||||
// Add subcommands to setup
|
|
||||||
setupCmd.AddCommand(setupBindsCmd, setupLayoutCmd, setupColorsCmd, setupAlttabCmd, setupOutputsCmd, setupCursorCmd, setupWindowrulesCmd)
|
setupCmd.AddCommand(setupBindsCmd, setupLayoutCmd, setupColorsCmd, setupAlttabCmd, setupOutputsCmd, setupCursorCmd, setupWindowrulesCmd)
|
||||||
|
|
||||||
// Add subcommands to plugins
|
|
||||||
pluginsCmd.AddCommand(pluginsBrowseCmd, pluginsListCmd, pluginsInstallCmd, pluginsUninstallCmd, pluginsUpdateCmd)
|
pluginsCmd.AddCommand(pluginsBrowseCmd, pluginsListCmd, pluginsInstallCmd, pluginsUninstallCmd, pluginsUpdateCmd)
|
||||||
|
|
||||||
// Add common commands to root
|
|
||||||
rootCmd.AddCommand(getCommonCommands()...)
|
rootCmd.AddCommand(getCommonCommands()...)
|
||||||
|
|
||||||
rootCmd.SetHelpTemplate(getHelpTemplate())
|
rootCmd.SetHelpTemplate(getHelpTemplate())
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Block root
|
if os.Geteuid() == 0 && !isReadOnlyCommand(os.Args) {
|
||||||
if os.Geteuid() == 0 {
|
|
||||||
log.Fatal("This program should not be run as root. Exiting.")
|
log.Fatal("This program should not be run as root. Exiting.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
172
core/cmd/dms/randr_client.go
Normal file
172
core/cmd/dms/randr_client.go
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_output_management"
|
||||||
|
wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
type randrOutput struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Scale float64 `json:"scale"`
|
||||||
|
Width int32 `json:"width"`
|
||||||
|
Height int32 `json:"height"`
|
||||||
|
Refresh int32 `json:"refresh"`
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type randrHead struct {
|
||||||
|
name string
|
||||||
|
enabled bool
|
||||||
|
scale float64
|
||||||
|
currentModeID uint32
|
||||||
|
modeIDs []uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type randrMode struct {
|
||||||
|
width int32
|
||||||
|
height int32
|
||||||
|
refresh int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type randrClient struct {
|
||||||
|
display *wlclient.Display
|
||||||
|
ctx *wlclient.Context
|
||||||
|
manager *wlr_output_management.ZwlrOutputManagerV1
|
||||||
|
heads map[uint32]*randrHead
|
||||||
|
modes map[uint32]*randrMode
|
||||||
|
done bool
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryRandr() ([]randrOutput, error) {
|
||||||
|
display, err := wlclient.Connect("")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to connect to Wayland: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &randrClient{
|
||||||
|
display: display,
|
||||||
|
ctx: display.Context(),
|
||||||
|
heads: make(map[uint32]*randrHead),
|
||||||
|
modes: make(map[uint32]*randrMode),
|
||||||
|
}
|
||||||
|
defer c.ctx.Close()
|
||||||
|
|
||||||
|
registry, err := display.GetRegistry()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get registry: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) {
|
||||||
|
if e.Interface == wlr_output_management.ZwlrOutputManagerV1InterfaceName {
|
||||||
|
mgr := wlr_output_management.NewZwlrOutputManagerV1(c.ctx)
|
||||||
|
version := min(e.Version, 4)
|
||||||
|
|
||||||
|
mgr.SetHeadHandler(func(e wlr_output_management.ZwlrOutputManagerV1HeadEvent) {
|
||||||
|
c.handleHead(e)
|
||||||
|
})
|
||||||
|
|
||||||
|
mgr.SetDoneHandler(func(e wlr_output_management.ZwlrOutputManagerV1DoneEvent) {
|
||||||
|
c.done = true
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := registry.Bind(e.Name, e.Interface, version, mgr); err == nil {
|
||||||
|
c.manager = mgr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// First roundtrip: discover globals and bind manager
|
||||||
|
syncCallback, err := display.Sync()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to sync display: %w", err)
|
||||||
|
}
|
||||||
|
syncCallback.SetDoneHandler(func(e wlclient.CallbackDoneEvent) {
|
||||||
|
if c.manager == nil {
|
||||||
|
c.err = fmt.Errorf("zwlr_output_manager_v1 protocol not supported by compositor")
|
||||||
|
c.done = true
|
||||||
|
}
|
||||||
|
// Otherwise wait for manager's DoneHandler
|
||||||
|
})
|
||||||
|
|
||||||
|
for !c.done {
|
||||||
|
if err := c.ctx.Dispatch(); err != nil {
|
||||||
|
return nil, fmt.Errorf("dispatch error: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.err != nil {
|
||||||
|
return nil, c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.buildOutputs(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *randrClient) handleHead(e wlr_output_management.ZwlrOutputManagerV1HeadEvent) {
|
||||||
|
handle := e.Head
|
||||||
|
headID := handle.ID()
|
||||||
|
|
||||||
|
head := &randrHead{
|
||||||
|
modeIDs: make([]uint32, 0),
|
||||||
|
}
|
||||||
|
c.heads[headID] = head
|
||||||
|
|
||||||
|
handle.SetNameHandler(func(e wlr_output_management.ZwlrOutputHeadV1NameEvent) {
|
||||||
|
head.name = e.Name
|
||||||
|
})
|
||||||
|
|
||||||
|
handle.SetEnabledHandler(func(e wlr_output_management.ZwlrOutputHeadV1EnabledEvent) {
|
||||||
|
head.enabled = e.Enabled != 0
|
||||||
|
})
|
||||||
|
|
||||||
|
handle.SetScaleHandler(func(e wlr_output_management.ZwlrOutputHeadV1ScaleEvent) {
|
||||||
|
head.scale = e.Scale
|
||||||
|
})
|
||||||
|
|
||||||
|
handle.SetCurrentModeHandler(func(e wlr_output_management.ZwlrOutputHeadV1CurrentModeEvent) {
|
||||||
|
head.currentModeID = e.Mode.ID()
|
||||||
|
})
|
||||||
|
|
||||||
|
handle.SetModeHandler(func(e wlr_output_management.ZwlrOutputHeadV1ModeEvent) {
|
||||||
|
modeHandle := e.Mode
|
||||||
|
modeID := modeHandle.ID()
|
||||||
|
|
||||||
|
head.modeIDs = append(head.modeIDs, modeID)
|
||||||
|
|
||||||
|
mode := &randrMode{}
|
||||||
|
c.modes[modeID] = mode
|
||||||
|
|
||||||
|
modeHandle.SetSizeHandler(func(e wlr_output_management.ZwlrOutputModeV1SizeEvent) {
|
||||||
|
mode.width = e.Width
|
||||||
|
mode.height = e.Height
|
||||||
|
})
|
||||||
|
|
||||||
|
modeHandle.SetRefreshHandler(func(e wlr_output_management.ZwlrOutputModeV1RefreshEvent) {
|
||||||
|
mode.refresh = e.Refresh
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *randrClient) buildOutputs() []randrOutput {
|
||||||
|
outputs := make([]randrOutput, 0, len(c.heads))
|
||||||
|
|
||||||
|
for _, head := range c.heads {
|
||||||
|
out := randrOutput{
|
||||||
|
Name: head.name,
|
||||||
|
Scale: head.scale,
|
||||||
|
Enabled: head.enabled,
|
||||||
|
}
|
||||||
|
|
||||||
|
if mode, ok := c.modes[head.currentModeID]; ok {
|
||||||
|
out.Width = mode.width
|
||||||
|
out.Height = mode.height
|
||||||
|
out.Refresh = mode.refresh
|
||||||
|
}
|
||||||
|
|
||||||
|
outputs = append(outputs, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
return outputs
|
||||||
|
}
|
||||||
@@ -7,12 +7,20 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func findCommandPath(cmd string) (string, error) {
|
// isReadOnlyCommand returns true if the CLI args indicate a command that is
|
||||||
path, err := exec.LookPath(cmd)
|
// safe to run as root (e.g. shell completion, help).
|
||||||
if err != nil {
|
func isReadOnlyCommand(args []string) bool {
|
||||||
return "", fmt.Errorf("command '%s' not found in PATH", cmd)
|
for _, arg := range args[1:] {
|
||||||
|
if strings.HasPrefix(arg, "-") {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
return path, nil
|
switch arg {
|
||||||
|
case "completion", "help", "__complete":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func isArchPackageInstalled(packageName string) bool {
|
func isArchPackageInstalled(packageName string) bool {
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package clipboard
|
package clipboard
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/ext_data_control"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/ext_data_control"
|
||||||
@@ -12,17 +14,37 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Copy(data []byte, mimeType string) error {
|
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 {
|
func CopyOpts(data []byte, mimeType string, foreground, pasteOnce bool) error {
|
||||||
|
if foreground {
|
||||||
|
return copyServeWithWriter(func(writer io.Writer) error {
|
||||||
|
total := 0
|
||||||
|
for total < len(data) {
|
||||||
|
n, err := writer.Write(data[total:])
|
||||||
|
total += n
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if total != len(data) {
|
||||||
|
return io.ErrShortWrite
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}, mimeType, pasteOnce)
|
||||||
|
}
|
||||||
|
return CopyReader(bytes.NewReader(data), mimeType, foreground, pasteOnce)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CopyReader(data io.Reader, mimeType string, foreground, pasteOnce bool) error {
|
||||||
if !foreground {
|
if !foreground {
|
||||||
return copyFork(data, mimeType, pasteOnce)
|
return copyFork(data, mimeType, pasteOnce)
|
||||||
}
|
}
|
||||||
return 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"}
|
args := []string{os.Args[0], "cl", "copy", "--foreground"}
|
||||||
if pasteOnce {
|
if pasteOnce {
|
||||||
args = append(args, "--paste-once")
|
args = append(args, "--paste-once")
|
||||||
@@ -30,11 +52,15 @@ func copyFork(data []byte, mimeType string, pasteOnce bool) error {
|
|||||||
args = append(args, "--type", mimeType)
|
args = append(args, "--type", mimeType)
|
||||||
|
|
||||||
cmd := exec.Command(args[0], args[1:]...)
|
cmd := exec.Command(args[0], args[1:]...)
|
||||||
cmd.Stdin = nil
|
|
||||||
cmd.Stdout = nil
|
cmd.Stdout = nil
|
||||||
cmd.Stderr = nil
|
cmd.Stderr = nil
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
||||||
|
|
||||||
|
if stdinSource, ok := data.(*os.File); ok {
|
||||||
|
cmd.Stdin = stdinSource
|
||||||
|
return cmd.Start()
|
||||||
|
}
|
||||||
|
|
||||||
stdin, err := cmd.StdinPipe()
|
stdin, err := cmd.StdinPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("stdin pipe: %w", err)
|
return fmt.Errorf("stdin pipe: %w", err)
|
||||||
@@ -44,16 +70,66 @@ func copyFork(data []byte, mimeType string, pasteOnce bool) error {
|
|||||||
return fmt.Errorf("start: %w", err)
|
return fmt.Errorf("start: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := stdin.Write(data); err != nil {
|
if _, err := io.Copy(stdin, data); err != nil {
|
||||||
stdin.Close()
|
stdin.Close()
|
||||||
return fmt.Errorf("write stdin: %w", err)
|
return fmt.Errorf("write stdin: %w", err)
|
||||||
}
|
}
|
||||||
stdin.Close()
|
if err := stdin.Close(); err != nil {
|
||||||
|
return fmt.Errorf("close stdin: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
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("")
|
display, err := wlclient.Connect("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("wayland connect: %w", err)
|
return fmt.Errorf("wayland connect: %w", err)
|
||||||
@@ -139,12 +215,18 @@ func copyServe(data []byte, mimeType string, pasteOnce bool) error {
|
|||||||
|
|
||||||
cancelled := make(chan struct{})
|
cancelled := make(chan struct{})
|
||||||
pasted := make(chan struct{}, 1)
|
pasted := make(chan struct{}, 1)
|
||||||
|
sendErr := make(chan error, 1)
|
||||||
|
|
||||||
source.SetSendHandler(func(e ext_data_control.ExtDataControlSourceV1SendEvent) {
|
source.SetSendHandler(func(e ext_data_control.ExtDataControlSourceV1SendEvent) {
|
||||||
defer syscall.Close(e.Fd)
|
defer syscall.Close(e.Fd)
|
||||||
file := os.NewFile(uintptr(e.Fd), "pipe")
|
file := os.NewFile(uintptr(e.Fd), "pipe")
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
file.Write(data)
|
if err := writeTo(file); err != nil {
|
||||||
|
select {
|
||||||
|
case sendErr <- err:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
select {
|
select {
|
||||||
case pasted <- struct{}{}:
|
case pasted <- struct{}{}:
|
||||||
default:
|
default:
|
||||||
@@ -165,6 +247,8 @@ func copyServe(data []byte, mimeType string, pasteOnce bool) error {
|
|||||||
select {
|
select {
|
||||||
case <-cancelled:
|
case <-cancelled:
|
||||||
return nil
|
return nil
|
||||||
|
case err := <-sendErr:
|
||||||
|
return err
|
||||||
case <-pasted:
|
case <-pasted:
|
||||||
if pasteOnce {
|
if pasteOnce {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -252,6 +252,7 @@ window-rule {
|
|||||||
// Open dms windows as floating by default
|
// Open dms windows as floating by default
|
||||||
window-rule {
|
window-rule {
|
||||||
match app-id=r#"org.quickshell$"#
|
match app-id=r#"org.quickshell$"#
|
||||||
|
match app-id=r#"com.danklinux.dms$"#
|
||||||
open-floating true
|
open-floating true
|
||||||
}
|
}
|
||||||
debug {
|
debug {
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ func (a *ArchDistribution) DetectDependenciesWithTerminal(ctx context.Context, w
|
|||||||
dependencies = append(dependencies, a.detectGit())
|
dependencies = append(dependencies, a.detectGit())
|
||||||
dependencies = append(dependencies, a.detectWindowManager(wm))
|
dependencies = append(dependencies, a.detectWindowManager(wm))
|
||||||
dependencies = append(dependencies, a.detectQuickshell())
|
dependencies = append(dependencies, a.detectQuickshell())
|
||||||
|
dependencies = append(dependencies, a.detectDMSGreeter())
|
||||||
dependencies = append(dependencies, a.detectXDGPortal())
|
dependencies = append(dependencies, a.detectXDGPortal())
|
||||||
dependencies = append(dependencies, a.detectAccountsService())
|
dependencies = append(dependencies, a.detectAccountsService())
|
||||||
|
|
||||||
@@ -124,12 +125,52 @@ func (a *ArchDistribution) detectAccountsService() deps.Dependency {
|
|||||||
return a.detectPackage("accountsservice", "D-Bus interface for user account query and manipulation", a.packageInstalled("accountsservice"))
|
return a.detectPackage("accountsservice", "D-Bus interface for user account query and manipulation", a.packageInstalled("accountsservice"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *ArchDistribution) detectDMSGreeter() deps.Dependency {
|
||||||
|
return a.detectOptionalPackage("dms-greeter", "DankMaterialShell greetd greeter", a.packageInstalled("greetd-dms-greeter-git"))
|
||||||
|
}
|
||||||
|
|
||||||
func (a *ArchDistribution) packageInstalled(pkg string) bool {
|
func (a *ArchDistribution) packageInstalled(pkg string) bool {
|
||||||
cmd := exec.Command("pacman", "-Q", pkg)
|
cmd := exec.Command("pacman", "-Q", pkg)
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseSRCINFODeps reads a .SRCINFO file and returns runtime dep and makedep package
|
||||||
|
func parseSRCINFODeps(srcinfoPath string) (deps []string, makedeps []string, err error) {
|
||||||
|
data, err := os.ReadFile(srcinfoPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
for _, line := range strings.Split(string(data), "\n") {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
var pkg string
|
||||||
|
var target *[]string
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(line, "makedepends = "):
|
||||||
|
pkg = strings.TrimPrefix(line, "makedepends = ")
|
||||||
|
target = &makedeps
|
||||||
|
case strings.HasPrefix(line, "depends = "):
|
||||||
|
pkg = strings.TrimPrefix(line, "depends = ")
|
||||||
|
target = &deps
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Strip version constraint (>=, <=, >, <, =) and colon-descriptions
|
||||||
|
if idx := strings.IndexAny(pkg, "><:="); idx >= 0 {
|
||||||
|
pkg = pkg[:idx]
|
||||||
|
}
|
||||||
|
pkg = strings.TrimSpace(pkg)
|
||||||
|
if pkg != "" {
|
||||||
|
*target = append(*target, pkg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return deps, makedeps, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ArchDistribution) isInSystemRepo(pkg string) bool {
|
||||||
|
return exec.Command("pacman", "-Si", pkg).Run() == nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *ArchDistribution) GetPackageMapping(wm deps.WindowManager) map[string]PackageMapping {
|
func (a *ArchDistribution) GetPackageMapping(wm deps.WindowManager) map[string]PackageMapping {
|
||||||
return a.GetPackageMappingWithVariants(wm, make(map[string]deps.PackageVariant))
|
return a.GetPackageMappingWithVariants(wm, make(map[string]deps.PackageVariant))
|
||||||
}
|
}
|
||||||
@@ -139,6 +180,7 @@ func (a *ArchDistribution) GetPackageMappingWithVariants(wm deps.WindowManager,
|
|||||||
"dms (DankMaterialShell)": a.getDMSMapping(variants["dms (DankMaterialShell)"]),
|
"dms (DankMaterialShell)": a.getDMSMapping(variants["dms (DankMaterialShell)"]),
|
||||||
"git": {Name: "git", Repository: RepoTypeSystem},
|
"git": {Name: "git", Repository: RepoTypeSystem},
|
||||||
"quickshell": a.getQuickshellMapping(variants["quickshell"]),
|
"quickshell": a.getQuickshellMapping(variants["quickshell"]),
|
||||||
|
"dms-greeter": {Name: "greetd-dms-greeter-git", Repository: RepoTypeAUR},
|
||||||
"matugen": a.getMatugenMapping(variants["matugen"]),
|
"matugen": a.getMatugenMapping(variants["matugen"]),
|
||||||
"dgop": {Name: "dgop", Repository: RepoTypeSystem},
|
"dgop": {Name: "dgop", Repository: RepoTypeSystem},
|
||||||
"ghostty": {Name: "ghostty", Repository: RepoTypeSystem},
|
"ghostty": {Name: "ghostty", Repository: RepoTypeSystem},
|
||||||
@@ -434,29 +476,10 @@ func (a *ArchDistribution) installAURPackages(ctx context.Context, packages []st
|
|||||||
a.log(fmt.Sprintf("Installing AUR packages manually: %s", strings.Join(packages, ", ")))
|
a.log(fmt.Sprintf("Installing AUR packages manually: %s", strings.Join(packages, ", ")))
|
||||||
|
|
||||||
hasNiri := false
|
hasNiri := false
|
||||||
hasQuickshell := false
|
|
||||||
for _, pkg := range packages {
|
for _, pkg := range packages {
|
||||||
if pkg == "niri-git" {
|
if pkg == "niri-git" {
|
||||||
hasNiri = true
|
hasNiri = true
|
||||||
}
|
}
|
||||||
if pkg == "quickshell" || pkg == "quickshell-git" {
|
|
||||||
hasQuickshell = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If quickshell is in the list, always reinstall google-breakpad first
|
|
||||||
if hasQuickshell {
|
|
||||||
progressChan <- InstallProgressMsg{
|
|
||||||
Phase: PhaseAURPackages,
|
|
||||||
Progress: 0.63,
|
|
||||||
Step: "Reinstalling google-breakpad for quickshell...",
|
|
||||||
IsComplete: false,
|
|
||||||
CommandInfo: "Reinstalling prerequisite AUR package for quickshell",
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := a.installSingleAURPackage(ctx, "google-breakpad", sudoPassword, progressChan, 0.63, 0.65); err != nil {
|
|
||||||
return fmt.Errorf("failed to reinstall google-breakpad prerequisite for quickshell: %w", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If niri is in the list, install makepkg-git-lfs-proto first if not already installed
|
// If niri is in the list, install makepkg-git-lfs-proto first if not already installed
|
||||||
@@ -537,6 +560,16 @@ func (a *ArchDistribution) reorderAURPackages(packages []string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *ArchDistribution) installSingleAURPackage(ctx context.Context, pkg, sudoPassword string, progressChan chan<- InstallProgressMsg, startProgress, endProgress float64) error {
|
func (a *ArchDistribution) installSingleAURPackage(ctx context.Context, pkg, sudoPassword string, progressChan chan<- InstallProgressMsg, startProgress, endProgress float64) error {
|
||||||
|
return a.installSingleAURPackageInternal(ctx, pkg, sudoPassword, progressChan, startProgress, endProgress, make(map[string]bool))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ArchDistribution) installSingleAURPackageInternal(ctx context.Context, pkg, sudoPassword string, progressChan chan<- InstallProgressMsg, startProgress, endProgress float64, visited map[string]bool) error {
|
||||||
|
if visited[pkg] {
|
||||||
|
a.log(fmt.Sprintf("Skipping %s (already being installed, cycle detected)", pkg))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
visited[pkg] = true
|
||||||
|
|
||||||
homeDir, err := os.UserHomeDir()
|
homeDir, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get user home directory: %w", err)
|
return fmt.Errorf("failed to get user home directory: %w", err)
|
||||||
@@ -610,48 +643,8 @@ func (a *ArchDistribution) installSingleAURPackage(ctx context.Context, pkg, sud
|
|||||||
return fmt.Errorf("failed to remove optdepends from .SRCINFO for %s: %w", pkg, err)
|
return fmt.Errorf("failed to remove optdepends from .SRCINFO for %s: %w", pkg, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip dependency installation for dms-shell-git and dms-shell-bin
|
|
||||||
// since we manually manage those dependencies
|
|
||||||
if pkg != "dms-shell-git" && pkg != "dms-shell-bin" {
|
|
||||||
// Pre-install dependencies from .SRCINFO
|
|
||||||
progressChan <- InstallProgressMsg{
|
|
||||||
Phase: PhaseAURPackages,
|
|
||||||
Progress: startProgress + 0.3*(endProgress-startProgress),
|
|
||||||
Step: fmt.Sprintf("Installing dependencies for %s...", pkg),
|
|
||||||
IsComplete: false,
|
|
||||||
CommandInfo: "Installing package dependencies and makedepends",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Install dependencies and makedepends explicitly
|
|
||||||
srcinfoPath = filepath.Join(packageDir, ".SRCINFO")
|
srcinfoPath = filepath.Join(packageDir, ".SRCINFO")
|
||||||
|
if pkg == "dms-shell-bin" {
|
||||||
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
|
|
||||||
if [ ! -z "$deps" ] && [ "$deps" != " " ]; then
|
|
||||||
echo '%s' | sudo -S pacman -S --needed --noconfirm $deps
|
|
||||||
fi
|
|
||||||
`, srcinfoPath, pkg, sudoPassword))
|
|
||||||
|
|
||||||
if err := a.runWithProgress(depsCmd, progressChan, PhaseAURPackages, startProgress+0.3*(endProgress-startProgress), startProgress+0.35*(endProgress-startProgress)); err != nil {
|
|
||||||
return fmt.Errorf("FAILED to install runtime dependencies for %s: %w", pkg, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
makedepsCmd := exec.CommandContext(ctx, "bash", "-c",
|
|
||||||
fmt.Sprintf(`
|
|
||||||
makedeps=$(grep -E "^[[:space:]]*makedepends = " "%s" | sed 's/^[[:space:]]*makedepends = //' | tr '\n' ' ')
|
|
||||||
if [ ! -z "$makedeps" ]; then
|
|
||||||
echo '%s' | sudo -S pacman -S --needed --noconfirm $makedeps
|
|
||||||
fi
|
|
||||||
`, srcinfoPath, sudoPassword))
|
|
||||||
|
|
||||||
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{
|
progressChan <- InstallProgressMsg{
|
||||||
Phase: PhaseAURPackages,
|
Phase: PhaseAURPackages,
|
||||||
Progress: startProgress + 0.35*(endProgress-startProgress),
|
Progress: startProgress + 0.35*(endProgress-startProgress),
|
||||||
@@ -659,6 +652,66 @@ func (a *ArchDistribution) installSingleAURPackage(ctx context.Context, pkg, sud
|
|||||||
IsComplete: false,
|
IsComplete: false,
|
||||||
LogOutput: fmt.Sprintf("Dependencies for %s are installed separately", pkg),
|
LogOutput: fmt.Sprintf("Dependencies for %s are installed separately", pkg),
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
progressChan <- InstallProgressMsg{
|
||||||
|
Phase: PhaseAURPackages,
|
||||||
|
Progress: startProgress + 0.3*(endProgress-startProgress),
|
||||||
|
Step: fmt.Sprintf("Resolving dependencies for %s...", pkg),
|
||||||
|
IsComplete: false,
|
||||||
|
CommandInfo: "Classifying dependencies as system or AUR",
|
||||||
|
}
|
||||||
|
|
||||||
|
runtimeDeps, makeDeps, err := parseSRCINFODeps(srcinfoPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse .SRCINFO for %s: %w", pkg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
var systemPkgs []string
|
||||||
|
var aurPkgs []string
|
||||||
|
|
||||||
|
for _, dep := range append(runtimeDeps, makeDeps...) {
|
||||||
|
if seen[dep] || a.packageInstalled(dep) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[dep] = true
|
||||||
|
if a.isInSystemRepo(dep) {
|
||||||
|
systemPkgs = append(systemPkgs, dep)
|
||||||
|
} else {
|
||||||
|
aurPkgs = append(aurPkgs, dep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(systemPkgs) > 0 {
|
||||||
|
progressChan <- InstallProgressMsg{
|
||||||
|
Phase: PhaseAURPackages,
|
||||||
|
Progress: startProgress + 0.32*(endProgress-startProgress),
|
||||||
|
Step: fmt.Sprintf("Installing %d system dependencies for %s...", len(systemPkgs), pkg),
|
||||||
|
IsComplete: false,
|
||||||
|
CommandInfo: fmt.Sprintf("sudo pacman -S --needed --noconfirm %s", strings.Join(systemPkgs, " ")),
|
||||||
|
}
|
||||||
|
if err := a.installSystemPackages(ctx, systemPkgs, sudoPassword, progressChan); err != nil {
|
||||||
|
return fmt.Errorf("failed to install system dependencies for %s: %w", pkg, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, aurDep := range aurPkgs {
|
||||||
|
a.log(fmt.Sprintf("Dependency %s is AUR-only, building from source...", aurDep))
|
||||||
|
progressChan <- InstallProgressMsg{
|
||||||
|
Phase: PhaseAURPackages,
|
||||||
|
Progress: startProgress + 0.35*(endProgress-startProgress),
|
||||||
|
Step: fmt.Sprintf("Installing AUR dependency %s for %s...", aurDep, pkg),
|
||||||
|
IsComplete: false,
|
||||||
|
CommandInfo: fmt.Sprintf("Building AUR dependency: %s", aurDep),
|
||||||
|
}
|
||||||
|
if err := a.installSingleAURPackageInternal(ctx, aurDep, sudoPassword, progressChan,
|
||||||
|
startProgress+0.35*(endProgress-startProgress),
|
||||||
|
startProgress+0.39*(endProgress-startProgress),
|
||||||
|
visited,
|
||||||
|
); err != nil {
|
||||||
|
return fmt.Errorf("failed to install AUR dependency %s for %s: %w", aurDep, pkg, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
progressChan <- InstallProgressMsg{
|
progressChan <- InstallProgressMsg{
|
||||||
@@ -671,7 +724,7 @@ func (a *ArchDistribution) installSingleAURPackage(ctx context.Context, pkg, sud
|
|||||||
|
|
||||||
buildCmd := exec.CommandContext(ctx, "makepkg", "--noconfirm")
|
buildCmd := exec.CommandContext(ctx, "makepkg", "--noconfirm")
|
||||||
buildCmd.Dir = packageDir
|
buildCmd.Dir = packageDir
|
||||||
buildCmd.Env = append(os.Environ(), "PKGEXT=.pkg.tar") // 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 {
|
if err := a.runWithProgress(buildCmd, progressChan, PhaseAURPackages, startProgress+0.4*(endProgress-startProgress), startProgress+0.7*(endProgress-startProgress)); err != nil {
|
||||||
return fmt.Errorf("failed to build %s: %w", pkg, err)
|
return fmt.Errorf("failed to build %s: %w", pkg, err)
|
||||||
|
|||||||
@@ -102,6 +102,19 @@ func (b *BaseDistribution) detectPackage(name, description string, installed boo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *BaseDistribution) detectOptionalPackage(name, description string, installed bool) deps.Dependency {
|
||||||
|
status := deps.StatusMissing
|
||||||
|
if installed {
|
||||||
|
status = deps.StatusInstalled
|
||||||
|
}
|
||||||
|
return deps.Dependency{
|
||||||
|
Name: name,
|
||||||
|
Status: status,
|
||||||
|
Description: description,
|
||||||
|
Required: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (b *BaseDistribution) detectGit() deps.Dependency {
|
func (b *BaseDistribution) detectGit() deps.Dependency {
|
||||||
return b.detectCommand("git", "Version control system")
|
return b.detectCommand("git", "Version control system")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
||||||
@@ -61,6 +60,7 @@ func (d *DebianDistribution) DetectDependenciesWithTerminal(ctx context.Context,
|
|||||||
dependencies = append(dependencies, d.detectGit())
|
dependencies = append(dependencies, d.detectGit())
|
||||||
dependencies = append(dependencies, d.detectWindowManager(wm))
|
dependencies = append(dependencies, d.detectWindowManager(wm))
|
||||||
dependencies = append(dependencies, d.detectQuickshell())
|
dependencies = append(dependencies, d.detectQuickshell())
|
||||||
|
dependencies = append(dependencies, d.detectDMSGreeter())
|
||||||
dependencies = append(dependencies, d.detectXDGPortal())
|
dependencies = append(dependencies, d.detectXDGPortal())
|
||||||
dependencies = append(dependencies, d.detectAccountsService())
|
dependencies = append(dependencies, d.detectAccountsService())
|
||||||
|
|
||||||
@@ -86,12 +86,27 @@ func (d *DebianDistribution) detectAccountsService() deps.Dependency {
|
|||||||
return d.detectPackage("accountsservice", "D-Bus interface for user account query and manipulation", d.packageInstalled("accountsservice"))
|
return d.detectPackage("accountsservice", "D-Bus interface for user account query and manipulation", d.packageInstalled("accountsservice"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DebianDistribution) detectDMSGreeter() deps.Dependency {
|
||||||
|
return d.detectOptionalPackage("dms-greeter", "DankMaterialShell greetd greeter", d.packageInstalled("dms-greeter"))
|
||||||
|
}
|
||||||
|
|
||||||
func (d *DebianDistribution) packageInstalled(pkg string) bool {
|
func (d *DebianDistribution) packageInstalled(pkg string) bool {
|
||||||
cmd := exec.Command("dpkg", "-l", pkg)
|
cmd := exec.Command("dpkg", "-l", pkg)
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func debianRepoArchitecture(arch string) string {
|
||||||
|
switch arch {
|
||||||
|
case "amd64", "x86_64":
|
||||||
|
return "amd64"
|
||||||
|
case "arm64", "aarch64":
|
||||||
|
return "arm64"
|
||||||
|
default:
|
||||||
|
return arch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (d *DebianDistribution) GetPackageMapping(wm deps.WindowManager) map[string]PackageMapping {
|
func (d *DebianDistribution) GetPackageMapping(wm deps.WindowManager) map[string]PackageMapping {
|
||||||
return d.GetPackageMappingWithVariants(wm, make(map[string]deps.PackageVariant))
|
return d.GetPackageMappingWithVariants(wm, make(map[string]deps.PackageVariant))
|
||||||
}
|
}
|
||||||
@@ -108,6 +123,7 @@ func (d *DebianDistribution) GetPackageMappingWithVariants(wm deps.WindowManager
|
|||||||
// DMS packages from OBS with variant support
|
// DMS packages from OBS with variant support
|
||||||
"dms (DankMaterialShell)": d.getDmsMapping(variants["dms (DankMaterialShell)"]),
|
"dms (DankMaterialShell)": d.getDmsMapping(variants["dms (DankMaterialShell)"]),
|
||||||
"quickshell": d.getQuickshellMapping(variants["quickshell"]),
|
"quickshell": d.getQuickshellMapping(variants["quickshell"]),
|
||||||
|
"dms-greeter": {Name: "dms-greeter", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
|
||||||
"matugen": {Name: "matugen", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
|
"matugen": {Name: "matugen", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
|
||||||
"dgop": {Name: "dgop", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
|
"dgop": {Name: "dgop", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
|
||||||
"ghostty": {Name: "ghostty", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
|
"ghostty": {Name: "ghostty", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
|
||||||
@@ -430,7 +446,7 @@ func (d *DebianDistribution) enableOBSRepos(ctx context.Context, obsPkgs []Packa
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add repository
|
// Add repository
|
||||||
repoLine := fmt.Sprintf("deb [signed-by=%s arch=%s] %s/ /", keyringPath, runtime.GOARCH, baseURL)
|
repoLine := fmt.Sprintf("deb [signed-by=%s arch=%s] %s/ /", keyringPath, debianRepoArchitecture(osInfo.Architecture), baseURL)
|
||||||
|
|
||||||
progressChan <- InstallProgressMsg{
|
progressChan <- InstallProgressMsg{
|
||||||
Phase: PhaseSystemPackages,
|
Phase: PhaseSystemPackages,
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ func init() {
|
|||||||
Register("fedora", "#0B57A4", FamilyFedora, func(config DistroConfig, logChan chan<- string) Distribution {
|
Register("fedora", "#0B57A4", FamilyFedora, func(config DistroConfig, logChan chan<- string) Distribution {
|
||||||
return NewFedoraDistribution(config, logChan)
|
return NewFedoraDistribution(config, logChan)
|
||||||
})
|
})
|
||||||
|
Register("evernight", "#72B8DC", FamilyFedora, func(config DistroConfig, logChan chan<- string) Distribution {
|
||||||
|
return NewFedoraDistribution(config, logChan)
|
||||||
|
})
|
||||||
Register("nobara", "#0B57A4", FamilyFedora, func(config DistroConfig, logChan chan<- string) Distribution {
|
Register("nobara", "#0B57A4", FamilyFedora, func(config DistroConfig, logChan chan<- string) Distribution {
|
||||||
return NewFedoraDistribution(config, logChan)
|
return NewFedoraDistribution(config, logChan)
|
||||||
})
|
})
|
||||||
@@ -75,6 +78,7 @@ func (f *FedoraDistribution) DetectDependenciesWithTerminal(ctx context.Context,
|
|||||||
dependencies = append(dependencies, f.detectGit())
|
dependencies = append(dependencies, f.detectGit())
|
||||||
dependencies = append(dependencies, f.detectWindowManager(wm))
|
dependencies = append(dependencies, f.detectWindowManager(wm))
|
||||||
dependencies = append(dependencies, f.detectQuickshell())
|
dependencies = append(dependencies, f.detectQuickshell())
|
||||||
|
dependencies = append(dependencies, f.detectDMSGreeter())
|
||||||
dependencies = append(dependencies, f.detectXDGPortal())
|
dependencies = append(dependencies, f.detectXDGPortal())
|
||||||
dependencies = append(dependencies, f.detectAccountsService())
|
dependencies = append(dependencies, f.detectAccountsService())
|
||||||
|
|
||||||
@@ -120,6 +124,7 @@ func (f *FedoraDistribution) GetPackageMappingWithVariants(wm deps.WindowManager
|
|||||||
|
|
||||||
// COPR packages
|
// COPR packages
|
||||||
"quickshell": f.getQuickshellMapping(variants["quickshell"]),
|
"quickshell": f.getQuickshellMapping(variants["quickshell"]),
|
||||||
|
"dms-greeter": {Name: "dms-greeter", Repository: RepoTypeCOPR, RepoURL: "avengemedia/danklinux"},
|
||||||
"matugen": {Name: "matugen", Repository: RepoTypeCOPR, RepoURL: "avengemedia/danklinux"},
|
"matugen": {Name: "matugen", Repository: RepoTypeCOPR, RepoURL: "avengemedia/danklinux"},
|
||||||
"dms (DankMaterialShell)": f.getDmsMapping(variants["dms (DankMaterialShell)"]),
|
"dms (DankMaterialShell)": f.getDmsMapping(variants["dms (DankMaterialShell)"]),
|
||||||
"dgop": {Name: "dgop", Repository: RepoTypeCOPR, RepoURL: "avengemedia/danklinux"},
|
"dgop": {Name: "dgop", Repository: RepoTypeCOPR, RepoURL: "avengemedia/danklinux"},
|
||||||
@@ -191,6 +196,10 @@ func (f *FedoraDistribution) detectAccountsService() deps.Dependency {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *FedoraDistribution) detectDMSGreeter() deps.Dependency {
|
||||||
|
return f.detectOptionalPackage("dms-greeter", "DankMaterialShell greetd greeter", f.packageInstalled("dms-greeter"))
|
||||||
|
}
|
||||||
|
|
||||||
func (f *FedoraDistribution) getPrerequisites() []string {
|
func (f *FedoraDistribution) getPrerequisites() []string {
|
||||||
return []string{
|
return []string{
|
||||||
"dnf-plugins-core",
|
"dnf-plugins-core",
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ const (
|
|||||||
PhaseAURPackages
|
PhaseAURPackages
|
||||||
PhaseCursorTheme
|
PhaseCursorTheme
|
||||||
PhaseConfiguration
|
PhaseConfiguration
|
||||||
|
PhaseGreeterSetup
|
||||||
PhaseComplete
|
PhaseComplete
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ func (o *OpenSUSEDistribution) DetectDependenciesWithTerminal(ctx context.Contex
|
|||||||
dependencies = append(dependencies, o.detectGit())
|
dependencies = append(dependencies, o.detectGit())
|
||||||
dependencies = append(dependencies, o.detectWindowManager(wm))
|
dependencies = append(dependencies, o.detectWindowManager(wm))
|
||||||
dependencies = append(dependencies, o.detectQuickshell())
|
dependencies = append(dependencies, o.detectQuickshell())
|
||||||
|
dependencies = append(dependencies, o.detectDMSGreeter())
|
||||||
dependencies = append(dependencies, o.detectXDGPortal())
|
dependencies = append(dependencies, o.detectXDGPortal())
|
||||||
dependencies = append(dependencies, o.detectAccountsService())
|
dependencies = append(dependencies, o.detectAccountsService())
|
||||||
|
|
||||||
@@ -100,6 +101,10 @@ func (o *OpenSUSEDistribution) packageInstalled(pkg string) bool {
|
|||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *OpenSUSEDistribution) detectDMSGreeter() deps.Dependency {
|
||||||
|
return o.detectOptionalPackage("dms-greeter", "DankMaterialShell greetd greeter", o.packageInstalled("dms-greeter"))
|
||||||
|
}
|
||||||
|
|
||||||
func (o *OpenSUSEDistribution) GetPackageMapping(wm deps.WindowManager) map[string]PackageMapping {
|
func (o *OpenSUSEDistribution) GetPackageMapping(wm deps.WindowManager) map[string]PackageMapping {
|
||||||
return o.GetPackageMappingWithVariants(wm, make(map[string]deps.PackageVariant))
|
return o.GetPackageMappingWithVariants(wm, make(map[string]deps.PackageVariant))
|
||||||
}
|
}
|
||||||
@@ -116,6 +121,7 @@ func (o *OpenSUSEDistribution) GetPackageMappingWithVariants(wm deps.WindowManag
|
|||||||
// DMS packages from OBS
|
// DMS packages from OBS
|
||||||
"dms (DankMaterialShell)": o.getDmsMapping(variants["dms (DankMaterialShell)"]),
|
"dms (DankMaterialShell)": o.getDmsMapping(variants["dms (DankMaterialShell)"]),
|
||||||
"quickshell": o.getQuickshellMapping(variants["quickshell"]),
|
"quickshell": o.getQuickshellMapping(variants["quickshell"]),
|
||||||
|
"dms-greeter": {Name: "dms-greeter", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
|
||||||
"ghostty": {Name: "ghostty", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
|
"ghostty": {Name: "ghostty", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
|
||||||
"matugen": {Name: "matugen", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
|
"matugen": {Name: "matugen", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
|
||||||
"dgop": {Name: "dgop", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
|
"dgop": {Name: "dgop", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ func (u *UbuntuDistribution) DetectDependenciesWithTerminal(ctx context.Context,
|
|||||||
dependencies = append(dependencies, u.detectGit())
|
dependencies = append(dependencies, u.detectGit())
|
||||||
dependencies = append(dependencies, u.detectWindowManager(wm))
|
dependencies = append(dependencies, u.detectWindowManager(wm))
|
||||||
dependencies = append(dependencies, u.detectQuickshell())
|
dependencies = append(dependencies, u.detectQuickshell())
|
||||||
|
dependencies = append(dependencies, u.detectDMSGreeter())
|
||||||
dependencies = append(dependencies, u.detectXDGPortal())
|
dependencies = append(dependencies, u.detectXDGPortal())
|
||||||
dependencies = append(dependencies, u.detectAccountsService())
|
dependencies = append(dependencies, u.detectAccountsService())
|
||||||
|
|
||||||
@@ -94,6 +95,10 @@ func (u *UbuntuDistribution) detectAccountsService() deps.Dependency {
|
|||||||
return u.detectPackage("accountsservice", "D-Bus interface for user account query and manipulation", u.packageInstalled("accountsservice"))
|
return u.detectPackage("accountsservice", "D-Bus interface for user account query and manipulation", u.packageInstalled("accountsservice"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *UbuntuDistribution) detectDMSGreeter() deps.Dependency {
|
||||||
|
return u.detectOptionalPackage("dms-greeter", "DankMaterialShell greetd greeter", u.packageInstalled("dms-greeter"))
|
||||||
|
}
|
||||||
|
|
||||||
func (u *UbuntuDistribution) packageInstalled(pkg string) bool {
|
func (u *UbuntuDistribution) packageInstalled(pkg string) bool {
|
||||||
cmd := exec.Command("dpkg", "-l", pkg)
|
cmd := exec.Command("dpkg", "-l", pkg)
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
@@ -116,6 +121,7 @@ func (u *UbuntuDistribution) GetPackageMappingWithVariants(wm deps.WindowManager
|
|||||||
// DMS packages from PPAs
|
// DMS packages from PPAs
|
||||||
"dms (DankMaterialShell)": u.getDmsMapping(variants["dms (DankMaterialShell)"]),
|
"dms (DankMaterialShell)": u.getDmsMapping(variants["dms (DankMaterialShell)"]),
|
||||||
"quickshell": u.getQuickshellMapping(variants["quickshell"]),
|
"quickshell": u.getQuickshellMapping(variants["quickshell"]),
|
||||||
|
"dms-greeter": {Name: "dms-greeter", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"},
|
||||||
"matugen": {Name: "matugen", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"},
|
"matugen": {Name: "matugen", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"},
|
||||||
"dgop": {Name: "dgop", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"},
|
"dgop": {Name: "dgop", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"},
|
||||||
"ghostty": {Name: "ghostty", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"},
|
"ghostty": {Name: "ghostty", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"},
|
||||||
|
|||||||
91
core/internal/greeter/assets/apparmor/usr.bin.dms-greeter
Normal file
91
core/internal/greeter/assets/apparmor/usr.bin.dms-greeter
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
# AppArmor profile for dms-greeter
|
||||||
|
#
|
||||||
|
# Managed by DMS — regenerated on every `dms greeter install` / `dms greeter sync`.
|
||||||
|
# Manual edits will be overwritten on next sync.
|
||||||
|
#
|
||||||
|
# Mode: complain (denials are logged, nothing is blocked)
|
||||||
|
# To switch to enforce after validating with `aa-logprof`:
|
||||||
|
# sudo aa-enforce /etc/apparmor.d/usr.bin.dms-greeter
|
||||||
|
#
|
||||||
|
#include <tunables/global>
|
||||||
|
|
||||||
|
profile dms-greeter /usr/bin/dms-greeter flags=(complain) {
|
||||||
|
#include <abstractions/base>
|
||||||
|
#include <abstractions/bash>
|
||||||
|
|
||||||
|
# The launcher script itself
|
||||||
|
/usr/bin/dms-greeter r,
|
||||||
|
|
||||||
|
# Cache directory — created by dms greeter sync/enable with greeter:greeter ownership
|
||||||
|
/var/cache/dms-greeter/ rw,
|
||||||
|
/var/cache/dms-greeter/** rwlk,
|
||||||
|
|
||||||
|
# DMS config — packaged path
|
||||||
|
/usr/share/quickshell/dms-greeter/ r,
|
||||||
|
/usr/share/quickshell/dms-greeter/** r,
|
||||||
|
/usr/share/quickshell/ r,
|
||||||
|
/usr/share/quickshell/** r,
|
||||||
|
|
||||||
|
# DMS config — system and user overrides
|
||||||
|
/etc/dms/ r,
|
||||||
|
/etc/dms/** r,
|
||||||
|
/usr/share/dms/ r,
|
||||||
|
/usr/share/dms/** r,
|
||||||
|
/home/*/.config/quickshell/ r,
|
||||||
|
/home/*/.config/quickshell/** r,
|
||||||
|
/root/.config/quickshell/ r,
|
||||||
|
/root/.config/quickshell/** r,
|
||||||
|
|
||||||
|
# greetd / PAM — read-only for session setup
|
||||||
|
/etc/greetd/ r,
|
||||||
|
/etc/greetd/** r,
|
||||||
|
/etc/pam.d/ r,
|
||||||
|
/etc/pam.d/** r,
|
||||||
|
/usr/lib/pam.d/ r,
|
||||||
|
/usr/lib/pam.d/** r,
|
||||||
|
|
||||||
|
# Compositor binaries — run unconfined so each compositor uses its own profile
|
||||||
|
/usr/bin/niri Ux,
|
||||||
|
/usr/bin/hyprland Ux,
|
||||||
|
/usr/bin/Hyprland Ux,
|
||||||
|
/usr/bin/sway Ux,
|
||||||
|
/usr/bin/labwc Ux,
|
||||||
|
/usr/bin/scroll Ux,
|
||||||
|
/usr/bin/miracle-wm Ux,
|
||||||
|
/usr/bin/mango Ux,
|
||||||
|
|
||||||
|
# Quickshell — run unconfined (has its own compositor profile on some distros)
|
||||||
|
/usr/bin/qs Ux,
|
||||||
|
/usr/bin/quickshell Ux,
|
||||||
|
|
||||||
|
# Wayland / XDG runtime (pipewire, wireplumber, wayland socket)
|
||||||
|
/run/user/[0-9]*/ rw,
|
||||||
|
/run/user/[0-9]*/** rw,
|
||||||
|
|
||||||
|
# DRM / GPU devices (required for Wayland compositor startup)
|
||||||
|
/dev/dri/ r,
|
||||||
|
/dev/dri/* rw,
|
||||||
|
/dev/udmabuf rw,
|
||||||
|
|
||||||
|
# Input devices
|
||||||
|
/dev/input/ r,
|
||||||
|
/dev/input/* r,
|
||||||
|
|
||||||
|
# Systemd journal / logging
|
||||||
|
/run/systemd/journal/socket rw,
|
||||||
|
/dev/log rw,
|
||||||
|
|
||||||
|
# Shell helper binaries invoked by the launcher script
|
||||||
|
/usr/bin/env ix,
|
||||||
|
/usr/bin/mkdir ix,
|
||||||
|
/usr/bin/cat ix,
|
||||||
|
/usr/bin/grep ix,
|
||||||
|
/usr/bin/dirname ix,
|
||||||
|
/usr/bin/basename ix,
|
||||||
|
/usr/bin/command ix,
|
||||||
|
/bin/env ix,
|
||||||
|
/bin/mkdir ix,
|
||||||
|
|
||||||
|
# Signal management (compositor lifecycle)
|
||||||
|
signal (send, receive) set=("term", "int", "hup", "kill"),
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
98
core/internal/greeter/installer_test.go
Normal file
98
core/internal/greeter/installer_test.go
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package greeter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func writeTestJSON(t *testing.T, path string, content string) {
|
||||||
|
t.Helper()
|
||||||
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||||
|
t.Fatalf("failed to create parent dir for %s: %v", path, err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
||||||
|
t.Fatalf("failed to write %s: %v", path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveGreeterThemeSyncState(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
settingsJSON string
|
||||||
|
sessionJSON string
|
||||||
|
wantSourcePath string
|
||||||
|
wantResolvedWallpaper string
|
||||||
|
wantDynamicOverrideUsed bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "dynamic theme with greeter wallpaper override uses generated greeter colors",
|
||||||
|
settingsJSON: `{
|
||||||
|
"currentThemeName": "dynamic",
|
||||||
|
"greeterWallpaperPath": "Pictures/blue.jpg",
|
||||||
|
"matugenScheme": "scheme-tonal-spot",
|
||||||
|
"iconTheme": "Papirus"
|
||||||
|
}`,
|
||||||
|
sessionJSON: `{"isLightMode":true}`,
|
||||||
|
wantSourcePath: filepath.Join(".cache", "DankMaterialShell", "greeter-colors", "dms-colors.json"),
|
||||||
|
wantResolvedWallpaper: filepath.Join("Pictures", "blue.jpg"),
|
||||||
|
wantDynamicOverrideUsed: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dynamic theme without override uses desktop colors",
|
||||||
|
settingsJSON: `{
|
||||||
|
"currentThemeName": "dynamic",
|
||||||
|
"greeterWallpaperPath": ""
|
||||||
|
}`,
|
||||||
|
sessionJSON: `{"isLightMode":false}`,
|
||||||
|
wantSourcePath: filepath.Join(".cache", "DankMaterialShell", "dms-colors.json"),
|
||||||
|
wantResolvedWallpaper: "",
|
||||||
|
wantDynamicOverrideUsed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-dynamic theme keeps desktop colors even with override wallpaper",
|
||||||
|
settingsJSON: `{
|
||||||
|
"currentThemeName": "purple",
|
||||||
|
"greeterWallpaperPath": "/tmp/blue.jpg"
|
||||||
|
}`,
|
||||||
|
sessionJSON: `{"isLightMode":false}`,
|
||||||
|
wantSourcePath: filepath.Join(".cache", "DankMaterialShell", "dms-colors.json"),
|
||||||
|
wantResolvedWallpaper: "/tmp/blue.jpg",
|
||||||
|
wantDynamicOverrideUsed: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
homeDir := t.TempDir()
|
||||||
|
writeTestJSON(t, filepath.Join(homeDir, ".config", "DankMaterialShell", "settings.json"), tt.settingsJSON)
|
||||||
|
writeTestJSON(t, filepath.Join(homeDir, ".local", "state", "DankMaterialShell", "session.json"), tt.sessionJSON)
|
||||||
|
|
||||||
|
state, err := resolveGreeterThemeSyncState(homeDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("resolveGreeterThemeSyncState returned error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := state.effectiveColorsSource(homeDir); got != filepath.Join(homeDir, tt.wantSourcePath) {
|
||||||
|
t.Fatalf("effectiveColorsSource = %q, want %q", got, filepath.Join(homeDir, tt.wantSourcePath))
|
||||||
|
}
|
||||||
|
|
||||||
|
wantResolvedWallpaper := tt.wantResolvedWallpaper
|
||||||
|
if wantResolvedWallpaper != "" && !filepath.IsAbs(wantResolvedWallpaper) {
|
||||||
|
wantResolvedWallpaper = filepath.Join(homeDir, wantResolvedWallpaper)
|
||||||
|
}
|
||||||
|
if state.ResolvedGreeterWallpaperPath != wantResolvedWallpaper {
|
||||||
|
t.Fatalf("ResolvedGreeterWallpaperPath = %q, want %q", state.ResolvedGreeterWallpaperPath, wantResolvedWallpaper)
|
||||||
|
}
|
||||||
|
|
||||||
|
if state.UsesDynamicWallpaperOverride != tt.wantDynamicOverrideUsed {
|
||||||
|
t.Fatalf("UsesDynamicWallpaperOverride = %v, want %v", state.UsesDynamicWallpaperOverride, tt.wantDynamicOverrideUsed)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
package matugen
|
package matugen
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
@@ -19,6 +21,8 @@ import (
|
|||||||
"github.com/lucasb-eyer/go-colorful"
|
"github.com/lucasb-eyer/go-colorful"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ErrNoChanges = errors.New("no color changes")
|
||||||
|
|
||||||
type ColorMode string
|
type ColorMode string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -54,7 +58,7 @@ var templateRegistry = []TemplateDef{
|
|||||||
{ID: "qt6ct", Commands: []string{"qt6ct"}, ConfigFile: "qt6ct.toml"},
|
{ID: "qt6ct", Commands: []string{"qt6ct"}, ConfigFile: "qt6ct.toml"},
|
||||||
{ID: "firefox", Commands: []string{"firefox"}, ConfigFile: "firefox.toml"},
|
{ID: "firefox", Commands: []string{"firefox"}, ConfigFile: "firefox.toml"},
|
||||||
{ID: "pywalfox", Commands: []string{"pywalfox"}, ConfigFile: "pywalfox.toml"},
|
{ID: "pywalfox", Commands: []string{"pywalfox"}, ConfigFile: "pywalfox.toml"},
|
||||||
{ID: "zenbrowser", Commands: []string{"zen", "zen-browser"}, Flatpaks: []string{"app.zen_browser.zen"}, ConfigFile: "zenbrowser.toml"},
|
{ID: "zenbrowser", Commands: []string{"zen", "zen-browser", "zen-beta", "zen-twilight"}, Flatpaks: []string{"app.zen_browser.zen"}, ConfigFile: "zenbrowser.toml"},
|
||||||
{ID: "vesktop", Commands: []string{"vesktop"}, Flatpaks: []string{"dev.vencord.Vesktop"}, ConfigFile: "vesktop.toml"},
|
{ID: "vesktop", Commands: []string{"vesktop"}, Flatpaks: []string{"dev.vencord.Vesktop"}, ConfigFile: "vesktop.toml"},
|
||||||
{ID: "equibop", Commands: []string{"equibop"}, ConfigFile: "equibop.toml"},
|
{ID: "equibop", Commands: []string{"equibop"}, ConfigFile: "equibop.toml"},
|
||||||
{ID: "ghostty", Commands: []string{"ghostty"}, ConfigFile: "ghostty.toml", Kind: TemplateKindTerminal},
|
{ID: "ghostty", Commands: []string{"ghostty"}, ConfigFile: "ghostty.toml", Kind: TemplateKindTerminal},
|
||||||
@@ -67,6 +71,7 @@ var templateRegistry = []TemplateDef{
|
|||||||
{ID: "kcolorscheme", ConfigFile: "kcolorscheme.toml", RunUnconditionally: true},
|
{ID: "kcolorscheme", ConfigFile: "kcolorscheme.toml", RunUnconditionally: true},
|
||||||
{ID: "vscode", Kind: TemplateKindVSCode},
|
{ID: "vscode", Kind: TemplateKindVSCode},
|
||||||
{ID: "emacs", Commands: []string{"emacs"}, ConfigFile: "emacs.toml", Kind: TemplateKindEmacs},
|
{ID: "emacs", Commands: []string{"emacs"}, ConfigFile: "emacs.toml", Kind: TemplateKindEmacs},
|
||||||
|
{ID: "zed", Commands: []string{"zed", "zeditor", "zedit"}, ConfigFile: "zed.toml"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ColorMode) GTKTheme() string {
|
func (c *ColorMode) GTKTheme() string {
|
||||||
@@ -95,6 +100,7 @@ type Options struct {
|
|||||||
IconTheme string
|
IconTheme string
|
||||||
MatugenType string
|
MatugenType string
|
||||||
RunUserTemplates bool
|
RunUserTemplates bool
|
||||||
|
ColorsOnly bool
|
||||||
StockColors string
|
StockColors string
|
||||||
SyncModeWithPortal bool
|
SyncModeWithPortal bool
|
||||||
TerminalsAlwaysDark bool
|
TerminalsAlwaysDark bool
|
||||||
@@ -160,8 +166,14 @@ func Run(opts Options) error {
|
|||||||
|
|
||||||
log.Infof("Building theme: %s %s (%s)", opts.Kind, opts.Value, opts.Mode)
|
log.Infof("Building theme: %s %s (%s)", opts.Kind, opts.Value, opts.Mode)
|
||||||
|
|
||||||
if err := buildOnce(&opts); err != nil {
|
changed, buildErr := buildOnce(&opts)
|
||||||
return err
|
if buildErr != nil {
|
||||||
|
return buildErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if !changed {
|
||||||
|
log.Info("No color changes detected, skipping refresh")
|
||||||
|
return ErrNoChanges
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.SyncModeWithPortal {
|
if opts.SyncModeWithPortal {
|
||||||
@@ -172,25 +184,27 @@ func Run(opts Options) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildOnce(opts *Options) error {
|
func buildOnce(opts *Options) (bool, error) {
|
||||||
cfgFile, err := os.CreateTemp("", "matugen-config-*.toml")
|
cfgFile, err := os.CreateTemp("", "matugen-config-*.toml")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create temp config: %w", err)
|
return false, fmt.Errorf("failed to create temp config: %w", err)
|
||||||
}
|
}
|
||||||
defer os.Remove(cfgFile.Name())
|
defer os.Remove(cfgFile.Name())
|
||||||
defer cfgFile.Close()
|
defer cfgFile.Close()
|
||||||
|
|
||||||
tmpDir, err := os.MkdirTemp("", "matugen-templates-*")
|
tmpDir, err := os.MkdirTemp("", "matugen-templates-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create temp dir: %w", err)
|
return false, fmt.Errorf("failed to create temp dir: %w", err)
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(tmpDir)
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
if err := buildMergedConfig(opts, cfgFile, tmpDir); err != nil {
|
if err := buildMergedConfig(opts, cfgFile, tmpDir); err != nil {
|
||||||
return fmt.Errorf("failed to build config: %w", err)
|
return false, fmt.Errorf("failed to build config: %w", err)
|
||||||
}
|
}
|
||||||
cfgFile.Close()
|
cfgFile.Close()
|
||||||
|
|
||||||
|
oldColors, _ := os.ReadFile(opts.ColorsOutput())
|
||||||
|
|
||||||
var primaryDark, primaryLight, surface string
|
var primaryDark, primaryLight, surface string
|
||||||
var dank16JSON string
|
var dank16JSON string
|
||||||
var importArgs []string
|
var importArgs []string
|
||||||
@@ -202,7 +216,7 @@ func buildOnce(opts *Options) error {
|
|||||||
surface = extractNestedColor(opts.StockColors, "surface", "dark")
|
surface = extractNestedColor(opts.StockColors, "surface", "dark")
|
||||||
|
|
||||||
if primaryDark == "" {
|
if primaryDark == "" {
|
||||||
return fmt.Errorf("failed to extract primary dark from stock colors")
|
return false, fmt.Errorf("failed to extract primary dark from stock colors")
|
||||||
}
|
}
|
||||||
if primaryLight == "" {
|
if primaryLight == "" {
|
||||||
primaryLight = primaryDark
|
primaryLight = primaryDark
|
||||||
@@ -216,14 +230,14 @@ func buildOnce(opts *Options) error {
|
|||||||
args := []string{"color", "hex", primaryDark, "-m", string(opts.Mode), "-t", opts.MatugenType, "-c", cfgFile.Name()}
|
args := []string{"color", "hex", primaryDark, "-m", string(opts.Mode), "-t", opts.MatugenType, "-c", cfgFile.Name()}
|
||||||
args = append(args, importArgs...)
|
args = append(args, importArgs...)
|
||||||
if err := runMatugen(args); err != nil {
|
if err := runMatugen(args); err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Infof("Using dynamic theme from %s: %s", opts.Kind, opts.Value)
|
log.Infof("Using dynamic theme from %s: %s", opts.Kind, opts.Value)
|
||||||
|
|
||||||
matJSON, err := runMatugenDryRun(opts)
|
matJSON, err := runMatugenDryRun(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("matugen dry-run failed: %w", err)
|
return false, fmt.Errorf("matugen dry-run failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
primaryDark = extractMatugenColor(matJSON, "primary", "dark")
|
primaryDark = extractMatugenColor(matJSON, "primary", "dark")
|
||||||
@@ -231,7 +245,7 @@ func buildOnce(opts *Options) error {
|
|||||||
surface = extractMatugenColor(matJSON, "surface", "dark")
|
surface = extractMatugenColor(matJSON, "surface", "dark")
|
||||||
|
|
||||||
if primaryDark == "" {
|
if primaryDark == "" {
|
||||||
return fmt.Errorf("failed to extract primary color")
|
return false, fmt.Errorf("failed to extract primary color")
|
||||||
}
|
}
|
||||||
if primaryLight == "" {
|
if primaryLight == "" {
|
||||||
primaryLight = primaryDark
|
primaryLight = primaryDark
|
||||||
@@ -252,10 +266,19 @@ func buildOnce(opts *Options) error {
|
|||||||
args = append(args, "-m", string(opts.Mode), "-t", opts.MatugenType, "-c", cfgFile.Name())
|
args = append(args, "-m", string(opts.Mode), "-t", opts.MatugenType, "-c", cfgFile.Name())
|
||||||
args = append(args, importArgs...)
|
args = append(args, importArgs...)
|
||||||
if err := runMatugen(args); err != nil {
|
if err := runMatugen(args); err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newColors, _ := os.ReadFile(opts.ColorsOutput())
|
||||||
|
if bytes.Equal(oldColors, newColors) && len(oldColors) > 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.ColorsOnly {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
if isDMSGTKActive(opts.ConfigDir) {
|
if isDMSGTKActive(opts.ConfigDir) {
|
||||||
switch opts.Mode {
|
switch opts.Mode {
|
||||||
case ColorModeLight:
|
case ColorModeLight:
|
||||||
@@ -273,7 +296,7 @@ func buildOnce(opts *Options) error {
|
|||||||
|
|
||||||
signalTerminals(opts)
|
signalTerminals(opts)
|
||||||
|
|
||||||
return nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildMergedConfig(opts *Options, cfgFile *os.File, tmpDir string) error {
|
func buildMergedConfig(opts *Options, cfgFile *os.File, tmpDir string) error {
|
||||||
@@ -313,6 +336,10 @@ output_path = '%s'
|
|||||||
|
|
||||||
`, opts.ShellDir, opts.ColorsOutput())
|
`, opts.ShellDir, opts.ColorsOutput())
|
||||||
|
|
||||||
|
if opts.ColorsOnly {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
homeDir, _ := os.UserHomeDir()
|
homeDir, _ := os.UserHomeDir()
|
||||||
for _, tmpl := range templateRegistry {
|
for _, tmpl := range templateRegistry {
|
||||||
if opts.ShouldSkipTemplate(tmpl.ID) {
|
if opts.ShouldSkipTemplate(tmpl.ID) {
|
||||||
@@ -579,10 +606,10 @@ func detectMatugenVersionLocked() (matugenFlags, error) {
|
|||||||
matugenVersionOK = true
|
matugenVersionOK = true
|
||||||
|
|
||||||
if matugenSupportsCOE {
|
if matugenSupportsCOE {
|
||||||
log.Infof("Matugen %s supports --continue-on-error", versionStr)
|
log.Debugf("Matugen %s detected: continue-on-error support enabled", versionStr)
|
||||||
}
|
}
|
||||||
if matugenIsV4 {
|
if matugenIsV4 {
|
||||||
log.Infof("Matugen %s: using v4 flags", versionStr)
|
log.Debugf("Matugen %s detected: using v4 compatibility flags", versionStr)
|
||||||
}
|
}
|
||||||
return matugenFlags{matugenSupportsCOE, matugenIsV4}, nil
|
return matugenFlags{matugenSupportsCOE, matugenIsV4}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package matugen
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
mocks_utils "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/utils"
|
mocks_utils "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/utils"
|
||||||
@@ -392,3 +393,51 @@ func TestSubstituteVars(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuildMergedConfigColorsOnly(t *testing.T) {
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
|
||||||
|
shellDir := filepath.Join(tempDir, "shell")
|
||||||
|
configsDir := filepath.Join(shellDir, "matugen", "configs")
|
||||||
|
if err := os.MkdirAll(configsDir, 0o755); err != nil {
|
||||||
|
t.Fatalf("failed to create configs dir: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
baseConfig := "[config]\ncustom_keywords = []\n"
|
||||||
|
if err := os.WriteFile(filepath.Join(configsDir, "base.toml"), []byte(baseConfig), 0o644); err != nil {
|
||||||
|
t.Fatalf("failed to write base config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfgFile, err := os.CreateTemp(tempDir, "merged-*.toml")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create temp config: %v", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(cfgFile.Name())
|
||||||
|
defer cfgFile.Close()
|
||||||
|
|
||||||
|
opts := &Options{
|
||||||
|
ShellDir: shellDir,
|
||||||
|
ConfigDir: filepath.Join(tempDir, "config"),
|
||||||
|
StateDir: filepath.Join(tempDir, "state"),
|
||||||
|
ColorsOnly: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := buildMergedConfig(opts, cfgFile, filepath.Join(tempDir, "templates")); err != nil {
|
||||||
|
t.Fatalf("buildMergedConfig failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cfgFile.Close(); err != nil {
|
||||||
|
t.Fatalf("failed to close merged config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := os.ReadFile(cfgFile.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read merged config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
content := string(output)
|
||||||
|
assert.Contains(t, content, "[templates.dank]")
|
||||||
|
assert.Contains(t, content, "output_path = '"+filepath.Join(opts.StateDir, "dms-colors.json")+"'")
|
||||||
|
assert.NotContains(t, content, "[templates.gtk]")
|
||||||
|
assert.False(t, strings.Contains(content, "output_path = 'CONFIG_DIR/"), "colors-only config should not emit app template outputs")
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package matugen
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
@@ -93,10 +94,13 @@ func (q *Queue) runWorker() {
|
|||||||
err := Run(job.Options)
|
err := Run(job.Options)
|
||||||
|
|
||||||
var result Result
|
var result Result
|
||||||
if err != nil {
|
switch {
|
||||||
result = Result{Success: false, Error: err}
|
case err == nil:
|
||||||
} else {
|
|
||||||
result = Result{Success: true}
|
result = Result{Success: true}
|
||||||
|
case errors.Is(err, ErrNoChanges):
|
||||||
|
result = Result{Success: true}
|
||||||
|
default:
|
||||||
|
result = Result{Success: false, Error: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
q.finishJob(result)
|
q.finishJob(result)
|
||||||
|
|||||||
@@ -311,6 +311,10 @@ func (m *Manager) handleDevicePropertiesChanged(path dbus.ObjectPath, changed ma
|
|||||||
select {
|
select {
|
||||||
case m.eventQueue <- func() {
|
case m.eventQueue <- func() {
|
||||||
time.Sleep(300 * time.Millisecond)
|
time.Sleep(300 * time.Millisecond)
|
||||||
|
log.Infof("[Bluetooth] Auto-trusting newly paired device: %s", devicePath)
|
||||||
|
if err := m.TrustDevice(devicePath, true); err != nil {
|
||||||
|
log.Warnf("[Bluetooth] Auto-trust failed: %v", err)
|
||||||
|
}
|
||||||
log.Infof("[Bluetooth] Auto-connecting newly paired device: %s", devicePath)
|
log.Infof("[Bluetooth] Auto-connecting newly paired device: %s", devicePath)
|
||||||
if err := m.ConnectDevice(devicePath); err != nil {
|
if err := m.ConnectDevice(devicePath); err != nil {
|
||||||
log.Warnf("[Bluetooth] Auto-connect failed: %v", err)
|
log.Warnf("[Bluetooth] Auto-connect failed: %v", err)
|
||||||
|
|||||||
@@ -5,5 +5,6 @@ const (
|
|||||||
dbusPath = "/org/freedesktop/login1"
|
dbusPath = "/org/freedesktop/login1"
|
||||||
dbusManagerInterface = "org.freedesktop.login1.Manager"
|
dbusManagerInterface = "org.freedesktop.login1.Manager"
|
||||||
dbusSessionInterface = "org.freedesktop.login1.Session"
|
dbusSessionInterface = "org.freedesktop.login1.Session"
|
||||||
|
dbusUserInterface = "org.freedesktop.login1.User"
|
||||||
dbusPropsInterface = "org.freedesktop.DBus.Properties"
|
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)
|
return nil, fmt.Errorf("failed to connect to system bus: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionID := os.Getenv("XDG_SESSION_ID")
|
|
||||||
if sessionID == "" {
|
|
||||||
sessionID = "self"
|
|
||||||
}
|
|
||||||
|
|
||||||
m := &Manager{
|
m := &Manager{
|
||||||
state: &SessionState{
|
state: &SessionState{},
|
||||||
SessionID: sessionID,
|
|
||||||
},
|
|
||||||
stateMutex: sync.RWMutex{},
|
stateMutex: sync.RWMutex{},
|
||||||
|
|
||||||
stopChan: make(chan struct{}),
|
stopChan: make(chan struct{}),
|
||||||
@@ -60,12 +53,13 @@ func (m *Manager) initialize() error {
|
|||||||
|
|
||||||
m.initializeFallbackDelay()
|
m.initializeFallbackDelay()
|
||||||
|
|
||||||
sessionPath, err := m.getSession(m.state.SessionID)
|
sessionID, sessionPath, err := m.discoverSession()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get session path: %w", err)
|
return fmt.Errorf("failed to get session path: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
m.stateMutex.Lock()
|
m.stateMutex.Lock()
|
||||||
|
m.state.SessionID = sessionID
|
||||||
m.state.SessionPath = string(sessionPath)
|
m.state.SessionPath = string(sessionPath)
|
||||||
m.sessionPath = sessionPath
|
m.sessionPath = sessionPath
|
||||||
m.stateMutex.Unlock()
|
m.stateMutex.Unlock()
|
||||||
@@ -79,6 +73,41 @@ func (m *Manager) initialize() error {
|
|||||||
return nil
|
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) {
|
func (m *Manager) getSession(id string) (dbus.ObjectPath, error) {
|
||||||
var out dbus.ObjectPath
|
var out dbus.ObjectPath
|
||||||
err := m.managerObj.Call(dbusManagerInterface+".GetSession", 0, id).Store(&out)
|
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
|
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 {
|
func (m *Manager) refreshSessionBinding() error {
|
||||||
if m.managerObj == nil || m.conn == nil {
|
if m.managerObj == nil || m.conn == nil {
|
||||||
return fmt.Errorf("manager not fully initialized")
|
return fmt.Errorf("manager not fully initialized")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package network
|
package network
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -28,7 +29,13 @@ func TestDetectResult_HasNetworkdField(t *testing.T) {
|
|||||||
|
|
||||||
func TestDetectNetworkStack_Integration(t *testing.T) {
|
func TestDetectNetworkStack_Integration(t *testing.T) {
|
||||||
result, err := DetectNetworkStack()
|
result, err := DetectNetworkStack()
|
||||||
|
|
||||||
|
if err != nil && strings.Contains(err.Error(), "connect system bus") {
|
||||||
|
t.Skipf("system D-Bus unavailable: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, result)
|
if assert.NotNil(t, result) {
|
||||||
assert.NotEmpty(t, result.ChosenReason)
|
assert.NotEmpty(t, result.ChosenReason)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1599,6 +1599,13 @@ func Start(printDocs bool) error {
|
|||||||
log.Warnf("Theme mode manager unavailable: %v", err)
|
log.Warnf("Theme mode manager unavailable: %v", err)
|
||||||
} else {
|
} else {
|
||||||
notifyCapabilityChange()
|
notifyCapabilityChange()
|
||||||
|
go func() {
|
||||||
|
<-loginctlReady
|
||||||
|
if loginctlManager == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
themeModeManager.WatchLoginctl(loginctlManager)
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
fatalErrChan := make(chan error, 1)
|
fatalErrChan := make(chan error, 1)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/loginctl"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wayland"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/wayland"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
|
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
|
||||||
)
|
)
|
||||||
@@ -187,6 +188,29 @@ func (m *Manager) Close() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) WatchLoginctl(lm *loginctl.Manager) {
|
||||||
|
ch := lm.Subscribe("thememode")
|
||||||
|
m.wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer m.wg.Done()
|
||||||
|
defer lm.Unsubscribe("thememode")
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-m.stopChan:
|
||||||
|
return
|
||||||
|
case state, ok := <-ch:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if state.PreparingForSleep {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m.TriggerUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) schedulerLoop() {
|
func (m *Manager) schedulerLoop() {
|
||||||
defer m.wg.Done()
|
defer m.wg.Done()
|
||||||
|
|
||||||
@@ -327,11 +351,13 @@ func statesEqual(a, b *State) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) computeSchedule(now time.Time, config Config) (bool, time.Time) {
|
func (m *Manager) computeSchedule(now time.Time, config Config) (bool, time.Time) {
|
||||||
if config.Mode == "location" {
|
switch config.Mode {
|
||||||
|
case "location":
|
||||||
return m.computeLocationSchedule(now, config)
|
return m.computeLocationSchedule(now, config)
|
||||||
}
|
default:
|
||||||
return computeTimeSchedule(now, config)
|
return computeTimeSchedule(now, config)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func computeTimeSchedule(now time.Time, config Config) (bool, time.Time) {
|
func computeTimeSchedule(now time.Time, config Config) (bool, time.Time) {
|
||||||
startMinutes := config.StartHour*60 + config.StartMinute
|
startMinutes := config.StartHour*60 + config.StartMinute
|
||||||
@@ -381,10 +407,10 @@ func (m *Manager) computeLocationSchedule(now time.Time, config Config) (bool, t
|
|||||||
}
|
}
|
||||||
|
|
||||||
times, cond := wayland.CalculateSunTimesWithTwilight(*lat, *lon, now, config.ElevationTwilight, config.ElevationDaylight)
|
times, cond := wayland.CalculateSunTimesWithTwilight(*lat, *lon, now, config.ElevationTwilight, config.ElevationDaylight)
|
||||||
if cond != wayland.SunNormal {
|
switch cond {
|
||||||
if cond == wayland.SunMidnightSun {
|
case wayland.SunMidnightSun:
|
||||||
return true, startOfNextDay(now)
|
return true, startOfNextDay(now)
|
||||||
}
|
case wayland.SunPolarNight:
|
||||||
return false, startOfNextDay(now)
|
return false, startOfNextDay(now)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,10 +423,10 @@ func (m *Manager) computeLocationSchedule(now time.Time, config Config) (bool, t
|
|||||||
|
|
||||||
nextDay := startOfNextDay(now)
|
nextDay := startOfNextDay(now)
|
||||||
nextTimes, nextCond := wayland.CalculateSunTimesWithTwilight(*lat, *lon, nextDay, config.ElevationTwilight, config.ElevationDaylight)
|
nextTimes, nextCond := wayland.CalculateSunTimesWithTwilight(*lat, *lon, nextDay, config.ElevationTwilight, config.ElevationDaylight)
|
||||||
if nextCond != wayland.SunNormal {
|
switch nextCond {
|
||||||
if nextCond == wayland.SunMidnightSun {
|
case wayland.SunMidnightSun:
|
||||||
return true, startOfNextDay(nextDay)
|
return true, startOfNextDay(nextDay)
|
||||||
}
|
case wayland.SunPolarNight:
|
||||||
return false, startOfNextDay(nextDay)
|
return false, startOfNextDay(nextDay)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,13 +439,7 @@ func startOfNextDay(t time.Time) time.Time {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func validateHourMinute(hour, minute int) bool {
|
func validateHourMinute(hour, minute int) bool {
|
||||||
if hour < 0 || hour > 23 {
|
return hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59
|
||||||
return false
|
|
||||||
}
|
|
||||||
if minute < 0 || minute > 59 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) ValidateSchedule(startHour, startMinute, endHour, endMinute int) error {
|
func (m *Manager) ValidateSchedule(startHour, startMinute, endHour, endMinute int) error {
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ package wlcontext
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
@@ -123,6 +123,9 @@ func (sc *SharedContext) eventDispatcher() {
|
|||||||
{Fd: int32(sc.wakeR), Events: unix.POLLIN},
|
{Fd: int32(sc.wakeR), Events: unix.POLLIN},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
consecutiveErrors := 0
|
||||||
|
const maxConsecutiveErrors = 20
|
||||||
|
|
||||||
for {
|
for {
|
||||||
sc.drainCmdQueue()
|
sc.drainCmdQueue()
|
||||||
|
|
||||||
@@ -153,9 +156,19 @@ func (sc *SharedContext) eventDispatcher() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := ctx.Dispatch(); err != nil && !os.IsTimeout(err) {
|
if err := ctx.Dispatch(); err != nil && !os.IsTimeout(err) {
|
||||||
log.Errorf("Wayland connection error: %v", err)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
time.Sleep(100 * time.Millisecond * time.Duration(consecutiveErrors))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
consecutiveErrors = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/greeter"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -80,19 +81,24 @@ func (m Model) viewDependencyReview() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
note := ""
|
||||||
|
if dep.Name == "dms-greeter" {
|
||||||
|
note = m.styles.Subtle.Render(" (selection replaces your current display manager)")
|
||||||
|
}
|
||||||
|
|
||||||
var line string
|
var line string
|
||||||
if i == m.selectedDep {
|
if i == m.selectedDep {
|
||||||
line = fmt.Sprintf("▶ %s%s%-25s %s", reinstallMarker, variantMarker, dep.Name, status)
|
line = fmt.Sprintf("▶ %s%s%-25s %s", reinstallMarker, variantMarker, dep.Name, status)
|
||||||
if dep.Version != "" {
|
if dep.Version != "" {
|
||||||
line += fmt.Sprintf(" (%s)", dep.Version)
|
line += fmt.Sprintf(" (%s)", dep.Version)
|
||||||
}
|
}
|
||||||
line = m.styles.SelectedOption.Render(line)
|
line = m.styles.SelectedOption.Render(line) + note
|
||||||
} else {
|
} else {
|
||||||
line = fmt.Sprintf(" %s%s%-25s %s", reinstallMarker, variantMarker, dep.Name, status)
|
line = fmt.Sprintf(" %s%s%-25s %s", reinstallMarker, variantMarker, dep.Name, status)
|
||||||
if dep.Version != "" {
|
if dep.Version != "" {
|
||||||
line += fmt.Sprintf(" (%s)", dep.Version)
|
line += fmt.Sprintf(" (%s)", dep.Version)
|
||||||
}
|
}
|
||||||
line = m.styles.Normal.Render(line)
|
line = m.styles.Normal.Render(line) + note
|
||||||
}
|
}
|
||||||
|
|
||||||
b.WriteString(line)
|
b.WriteString(line)
|
||||||
@@ -115,6 +121,13 @@ func (m Model) updateDetectingDepsState(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
m.state = StateError
|
m.state = StateError
|
||||||
} else {
|
} else {
|
||||||
m.dependencies = depsMsg.deps
|
m.dependencies = depsMsg.deps
|
||||||
|
// dms-greeter is opt-in skipped by default
|
||||||
|
for _, dep := range depsMsg.deps {
|
||||||
|
if dep.Name == "dms-greeter" {
|
||||||
|
m.disabledItems["dms-greeter"] = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
m.state = StateDependencyReview
|
m.state = StateDependencyReview
|
||||||
}
|
}
|
||||||
return m, m.listenForLogs()
|
return m, m.listenForLogs()
|
||||||
@@ -230,6 +243,41 @@ func (m Model) installPackages() tea.Cmd {
|
|||||||
// Convert installer messages to TUI messages
|
// Convert installer messages to TUI messages
|
||||||
go func() {
|
go func() {
|
||||||
for msg := range installerProgressChan {
|
for msg := range installerProgressChan {
|
||||||
|
// Run optional greeter setup
|
||||||
|
if msg.Phase == distros.PhaseComplete && msg.IsComplete && msg.Error == nil {
|
||||||
|
greeterSelected := false
|
||||||
|
for _, dep := range m.dependencies {
|
||||||
|
if dep.Name == "dms-greeter" && !m.disabledItems["dms-greeter"] {
|
||||||
|
greeterSelected = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if greeterSelected {
|
||||||
|
compositorName := "niri"
|
||||||
|
if m.selectedWM == 1 {
|
||||||
|
compositorName = "Hyprland"
|
||||||
|
}
|
||||||
|
m.packageProgressChan <- packageInstallProgressMsg{
|
||||||
|
progress: 0.92,
|
||||||
|
step: "Configuring DMS greeter...",
|
||||||
|
logOutput: "Starting automated greeter setup...",
|
||||||
|
}
|
||||||
|
greeterLogFunc := func(line string) {
|
||||||
|
m.packageProgressChan <- packageInstallProgressMsg{
|
||||||
|
progress: 0.94,
|
||||||
|
step: "Configuring DMS greeter...",
|
||||||
|
logOutput: line,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := greeter.AutoSetupGreeter(compositorName, m.sudoPassword, greeterLogFunc); err != nil {
|
||||||
|
m.packageProgressChan <- packageInstallProgressMsg{
|
||||||
|
progress: 0.96,
|
||||||
|
step: "Greeter setup warning",
|
||||||
|
logOutput: fmt.Sprintf("⚠ Greeter auto-setup warning (non-fatal): %v", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
tuiMsg := packageInstallProgressMsg{
|
tuiMsg := packageInstallProgressMsg{
|
||||||
progress: msg.Progress,
|
progress: msg.Progress,
|
||||||
step: msg.Step,
|
step: msg.Step,
|
||||||
|
|||||||
@@ -864,10 +864,12 @@ func (p *NiriWritableProvider) formatRule(rule windowrules.WindowRule) string {
|
|||||||
|
|
||||||
func formatSizeProperty(name, value string) string {
|
func formatSizeProperty(name, value string) string {
|
||||||
parts := strings.SplitN(value, " ", 2)
|
parts := strings.SplitN(value, " ", 2)
|
||||||
if len(parts) != 2 {
|
if len(parts) == 2 {
|
||||||
|
return fmt.Sprintf(" %s { %s %s; }", name, parts[0], parts[1])
|
||||||
|
}
|
||||||
|
// Bare number without type prefix — default to "fixed"
|
||||||
|
if _, err := strconv.Atoi(value); err == nil {
|
||||||
|
return fmt.Sprintf(" %s { fixed %s; }", name, value)
|
||||||
|
}
|
||||||
return fmt.Sprintf(" %s { }", name)
|
return fmt.Sprintf(" %s { }", name)
|
||||||
}
|
}
|
||||||
sizeType := parts[0]
|
|
||||||
sizeValue := parts[1]
|
|
||||||
return fmt.Sprintf(" %s { %s %s; }", name, sizeType, sizeValue)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ Depends: ${misc:Depends},
|
|||||||
qml6-module-qtquick-templates,
|
qml6-module-qtquick-templates,
|
||||||
qml6-module-qtquick-window,
|
qml6-module-qtquick-window,
|
||||||
qt6ct
|
qt6ct
|
||||||
|
Suggests: cups-pk-helper
|
||||||
Provides: dms
|
Provides: dms
|
||||||
Conflicts: dms
|
Conflicts: dms
|
||||||
Replaces: dms
|
Replaces: dms
|
||||||
|
|||||||
9
distro/debian/dms-greeter/_service
Normal file
9
distro/debian/dms-greeter/_service
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<services>
|
||||||
|
<!-- Download dms-qml source tarball from GitHub releases (greeter + quickshell content) -->
|
||||||
|
<service name="download_url">
|
||||||
|
<param name="protocol">https</param>
|
||||||
|
<param name="host">github.com</param>
|
||||||
|
<param name="path">/AvengeMedia/DankMaterialShell/releases/download/v1.4.2/dms-qml.tar.gz</param>
|
||||||
|
<param name="filename">dms-qml.tar.gz</param>
|
||||||
|
</service>
|
||||||
|
</services>
|
||||||
6
distro/debian/dms-greeter/debian/changelog
Normal file
6
distro/debian/dms-greeter/debian/changelog
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
dms-greeter (1.4.2db8) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Initial Debian OBS package
|
||||||
|
* Port from Ubuntu/Fedora packaging
|
||||||
|
|
||||||
|
-- Avenge Media <AvengeMedia.US@gmail.com> Sat, 21 Feb 2026 00:00:00 +0000
|
||||||
23
distro/debian/dms-greeter/debian/control
Normal file
23
distro/debian/dms-greeter/debian/control
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
Source: dms-greeter
|
||||||
|
Section: x11
|
||||||
|
Priority: optional
|
||||||
|
Maintainer: Avenge Media <AvengeMedia.US@gmail.com>
|
||||||
|
Build-Depends: debhelper-compat (= 13)
|
||||||
|
Standards-Version: 4.6.2
|
||||||
|
Homepage: https://github.com/AvengeMedia/DankMaterialShell
|
||||||
|
Vcs-Browser: https://github.com/AvengeMedia/DankMaterialShell
|
||||||
|
Vcs-Git: https://github.com/AvengeMedia/DankMaterialShell.git
|
||||||
|
|
||||||
|
Package: dms-greeter
|
||||||
|
Architecture: any
|
||||||
|
Depends: ${misc:Depends},
|
||||||
|
greetd,
|
||||||
|
quickshell-git | quickshell
|
||||||
|
Suggests: niri | hyprland | sway
|
||||||
|
Description: DankMaterialShell greeter for greetd
|
||||||
|
DankMaterialShell greeter for greetd login manager. A modern, Material Design 3
|
||||||
|
inspired greeter interface built with Quickshell for Wayland compositors.
|
||||||
|
.
|
||||||
|
Supports multiple compositors including Niri, Hyprland, and Sway with automatic
|
||||||
|
compositor detection and configuration. Features session selection, user
|
||||||
|
authentication, and dynamic theming.
|
||||||
27
distro/debian/dms-greeter/debian/copyright
Normal file
27
distro/debian/dms-greeter/debian/copyright
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||||
|
Upstream-Name: dms-greeter
|
||||||
|
Upstream-Contact: Avenge Media LLC <AvengeMedia.US@gmail.com>
|
||||||
|
Source: https://github.com/AvengeMedia/DankMaterialShell
|
||||||
|
|
||||||
|
Files: *
|
||||||
|
Copyright: 2026 Avenge Media LLC
|
||||||
|
License: MIT
|
||||||
|
|
||||||
|
License: MIT
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
.
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
.
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
108
distro/debian/dms-greeter/debian/postinst
Normal file
108
distro/debian/dms-greeter/debian/postinst
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
configure)
|
||||||
|
# Create greeter user/group if they don't exist
|
||||||
|
if ! getent group greeter >/dev/null; then
|
||||||
|
addgroup --system greeter
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! getent passwd greeter >/dev/null; then
|
||||||
|
adduser --system --ingroup greeter --home /var/lib/greeter \
|
||||||
|
--shell /bin/bash --gecos "System Greeter" greeter
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d /var/cache/dms-greeter ]; then
|
||||||
|
chown -R greeter:greeter /var/cache/dms-greeter 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d /var/lib/greeter ]; then
|
||||||
|
chown -R greeter:greeter /var/lib/greeter 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check and set graphical.target as default
|
||||||
|
CURRENT_TARGET=$(systemctl get-default 2>/dev/null || echo "unknown")
|
||||||
|
if [ "$CURRENT_TARGET" != "graphical.target" ]; then
|
||||||
|
systemctl set-default graphical.target >/dev/null 2>&1 || true
|
||||||
|
TARGET_STATUS="Set to graphical.target (was: $CURRENT_TARGET) ✓"
|
||||||
|
else
|
||||||
|
TARGET_STATUS="Already graphical.target ✓"
|
||||||
|
fi
|
||||||
|
|
||||||
|
GREETD_CONFIG="/etc/greetd/config.toml"
|
||||||
|
CONFIG_STATUS="Not modified (already configured)"
|
||||||
|
|
||||||
|
# Check if niri or hyprland exists
|
||||||
|
COMPOSITOR="niri"
|
||||||
|
if ! command -v niri >/dev/null 2>&1; then
|
||||||
|
if command -v Hyprland >/dev/null 2>&1; then
|
||||||
|
COMPOSITOR="hyprland"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If config doesn't exist, create a default one
|
||||||
|
if [ ! -f "$GREETD_CONFIG" ]; then
|
||||||
|
mkdir -p /etc/greetd
|
||||||
|
cat > "$GREETD_CONFIG" << 'GREETD_EOF'
|
||||||
|
[terminal]
|
||||||
|
vt = 1
|
||||||
|
|
||||||
|
[default_session]
|
||||||
|
user = "greeter"
|
||||||
|
command = "/usr/bin/dms-greeter --command COMPOSITOR_PLACEHOLDER"
|
||||||
|
GREETD_EOF
|
||||||
|
sed -i "s|COMPOSITOR_PLACEHOLDER|$COMPOSITOR|" "$GREETD_CONFIG"
|
||||||
|
CONFIG_STATUS="Created new config with $COMPOSITOR ✓"
|
||||||
|
elif ! grep -q "dms-greeter" "$GREETD_CONFIG"; then
|
||||||
|
# Backup existing config
|
||||||
|
BACKUP_FILE="${GREETD_CONFIG}.backup-$(date +%Y%m%d-%H%M%S)"
|
||||||
|
cp "$GREETD_CONFIG" "$BACKUP_FILE" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Update command in default_session section
|
||||||
|
sed -i "/^\[default_session\]/,/^\[/ s|^command =.*|command = \"/usr/bin/dms-greeter --command $COMPOSITOR\"|" "$GREETD_CONFIG"
|
||||||
|
sed -i '/^\[default_session\]/,/^\[/ s|^user =.*|user = "greeter"|' "$GREETD_CONFIG"
|
||||||
|
CONFIG_STATUS="Updated existing config (backed up) with $COMPOSITOR ✓"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Only show banner on initial install
|
||||||
|
if [ -z "$2" ]; then
|
||||||
|
cat << 'EOF'
|
||||||
|
|
||||||
|
=========================================================================
|
||||||
|
DMS Greeter Installation Complete!
|
||||||
|
=========================================================================
|
||||||
|
|
||||||
|
Status:
|
||||||
|
EOF
|
||||||
|
echo " ✓ Greetd config: $CONFIG_STATUS"
|
||||||
|
echo " ✓ Default target: $TARGET_STATUS"
|
||||||
|
cat << 'EOF'
|
||||||
|
✓ Greeter user: Created
|
||||||
|
✓ Greeter directories: /var/cache/dms-greeter, /var/lib/greeter
|
||||||
|
|
||||||
|
Next steps:
|
||||||
|
|
||||||
|
1. Enable the greeter:
|
||||||
|
dms greeter enable
|
||||||
|
(This will automatically disable conflicting display managers,
|
||||||
|
set graphical.target, and enable greetd)
|
||||||
|
|
||||||
|
2. Sync your theme with the greeter (optional):
|
||||||
|
dms greeter sync
|
||||||
|
|
||||||
|
3. Check your setup:
|
||||||
|
dms greeter status
|
||||||
|
|
||||||
|
Ready to test? Run: sudo systemctl start greetd
|
||||||
|
Documentation: https://danklinux.com/docs/dankgreeter/
|
||||||
|
=========================================================================
|
||||||
|
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
#DEBHELPER#
|
||||||
|
|
||||||
|
exit 0
|
||||||
14
distro/debian/dms-greeter/debian/postrm
Normal file
14
distro/debian/dms-greeter/debian/postrm
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
purge)
|
||||||
|
# Remove greeter cache directory on purge
|
||||||
|
rm -rf /var/cache/dms-greeter 2>/dev/null || true
|
||||||
|
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
#DEBHELPER#
|
||||||
|
|
||||||
|
exit 0
|
||||||
48
distro/debian/dms-greeter/debian/rules
Normal file
48
distro/debian/dms-greeter/debian/rules
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#!/usr/bin/make -f
|
||||||
|
|
||||||
|
export DH_VERBOSE = 1
|
||||||
|
|
||||||
|
DEB_VERSION := $(shell dpkg-parsechangelog -S Version)
|
||||||
|
UPSTREAM_VERSION := $(shell echo $(DEB_VERSION) | sed 's/-[^-]*$$//')
|
||||||
|
|
||||||
|
%:
|
||||||
|
dh $@
|
||||||
|
|
||||||
|
override_dh_auto_build:
|
||||||
|
: nothing to build, we use prebuilt tarball content
|
||||||
|
|
||||||
|
override_dh_auto_install:
|
||||||
|
# Same pattern as dms: upstream from combined tarball (native format)
|
||||||
|
# Build root is either . (we're inside dms-qml) or has dms-qml/ subdir
|
||||||
|
SOURCE_DIR=""; \
|
||||||
|
if [ -d dms-qml ]; then SOURCE_DIR="dms-qml"; \
|
||||||
|
elif [ -f Modules/Greetd/assets/dms-greeter ]; then SOURCE_DIR="."; \
|
||||||
|
fi; \
|
||||||
|
if [ -n "$$SOURCE_DIR" ]; then \
|
||||||
|
mkdir -p debian/dms-greeter/usr/share/quickshell/dms-greeter && \
|
||||||
|
( cd $$SOURCE_DIR && tar cf - --exclude=debian . ) | \
|
||||||
|
( cd debian/dms-greeter/usr/share/quickshell/dms-greeter && tar xf - ) && \
|
||||||
|
install -Dm755 $$SOURCE_DIR/Modules/Greetd/assets/dms-greeter \
|
||||||
|
debian/dms-greeter/usr/bin/dms-greeter && \
|
||||||
|
install -Dm644 $$SOURCE_DIR/Modules/Greetd/README.md \
|
||||||
|
debian/dms-greeter/usr/share/doc/dms-greeter/README.md && \
|
||||||
|
install -Dm644 $$SOURCE_DIR/LICENSE \
|
||||||
|
debian/dms-greeter/usr/share/doc/dms-greeter/LICENSE && \
|
||||||
|
install -Dpm0644 $$SOURCE_DIR/systemd/tmpfiles-dms-greeter.conf \
|
||||||
|
debian/dms-greeter/usr/lib/tmpfiles.d/dms-greeter.conf; \
|
||||||
|
else \
|
||||||
|
echo "ERROR: No upstream source (dms-qml or Modules/Greetd/assets/dms-greeter)!" && \
|
||||||
|
echo "Contents of current directory:" && ls -la && exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Remove build and development files
|
||||||
|
rm -rf debian/dms-greeter/usr/share/quickshell/dms-greeter/core
|
||||||
|
rm -rf debian/dms-greeter/usr/share/quickshell/dms-greeter/distro
|
||||||
|
rm -rf debian/dms-greeter/usr/share/quickshell/dms-greeter/.git*
|
||||||
|
rm -f debian/dms-greeter/usr/share/quickshell/dms-greeter/.gitignore
|
||||||
|
rm -rf debian/dms-greeter/usr/share/quickshell/dms-greeter/.github
|
||||||
|
|
||||||
|
override_dh_auto_clean:
|
||||||
|
rm -rf dms-qml
|
||||||
|
# When build root is dms-qml itself, we're inside it - nothing extra to remove
|
||||||
|
dh_auto_clean
|
||||||
1
distro/debian/dms-greeter/debian/source/format
Normal file
1
distro/debian/dms-greeter/debian/source/format
Normal file
@@ -0,0 +1 @@
|
|||||||
|
3.0 (native)
|
||||||
1
distro/debian/dms-greeter/debian/source/options
Normal file
1
distro/debian/dms-greeter/debian/source/options
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# OBS _service downloads dms-qml.tar.gz; no extra excludes needed
|
||||||
@@ -9,7 +9,7 @@ Vcs-Browser: https://github.com/AvengeMedia/DankMaterialShell
|
|||||||
Vcs-Git: https://github.com/AvengeMedia/DankMaterialShell.git
|
Vcs-Git: https://github.com/AvengeMedia/DankMaterialShell.git
|
||||||
|
|
||||||
Package: dms
|
Package: dms
|
||||||
Architecture: amd64
|
Architecture: amd64 arm64
|
||||||
Depends: ${misc:Depends},
|
Depends: ${misc:Depends},
|
||||||
quickshell | quickshell-git,
|
quickshell | quickshell-git,
|
||||||
accountsservice,
|
accountsservice,
|
||||||
@@ -28,6 +28,7 @@ Depends: ${misc:Depends},
|
|||||||
qml6-module-qtquick-templates,
|
qml6-module-qtquick-templates,
|
||||||
qml6-module-qtquick-window,
|
qml6-module-qtquick-window,
|
||||||
qt6ct
|
qt6ct
|
||||||
|
Suggests: cups-pk-helper
|
||||||
Conflicts: dms-git
|
Conflicts: dms-git
|
||||||
Replaces: dms-git
|
Replaces: dms-git
|
||||||
Description: DankMaterialShell - Modern Wayland Desktop Shell
|
Description: DankMaterialShell - Modern Wayland Desktop Shell
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
dms-distropkg-amd64.gz
|
dms-distropkg-amd64.gz
|
||||||
|
dms-distropkg-arm64.gz
|
||||||
dms-source.tar.gz
|
dms-source.tar.gz
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
# Include files that are normally excluded by .gitignore
|
# Include files that are normally excluded by .gitignore
|
||||||
# These are needed for the build process on Launchpad
|
# These are needed for the build process on Launchpad
|
||||||
tar-ignore = !dms-distropkg-amd64.gz
|
tar-ignore = !dms-distropkg-amd64.gz
|
||||||
|
tar-ignore = !dms-distropkg-arm64.gz
|
||||||
tar-ignore = !dms-source.tar.gz
|
tar-ignore = !dms-source.tar.gz
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ Recommends: quickshell-git
|
|||||||
# Recommended system packages
|
# Recommended system packages
|
||||||
Recommends: NetworkManager
|
Recommends: NetworkManager
|
||||||
Recommends: qt6-qtmultimedia
|
Recommends: qt6-qtmultimedia
|
||||||
|
Suggests: cups-pk-helper
|
||||||
Suggests: qt6ct
|
Suggests: qt6ct
|
||||||
|
|
||||||
%description
|
%description
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ Recommends: danksearch
|
|||||||
Recommends: matugen
|
Recommends: matugen
|
||||||
Recommends: NetworkManager
|
Recommends: NetworkManager
|
||||||
Recommends: qt6-qtmultimedia
|
Recommends: qt6-qtmultimedia
|
||||||
|
Suggests: cups-pk-helper
|
||||||
Suggests: qt6ct
|
Suggests: qt6ct
|
||||||
|
|
||||||
%description
|
%description
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ let
|
|||||||
lib.makeBinPath [
|
lib.makeBinPath [
|
||||||
cfg.quickshell.package
|
cfg.quickshell.package
|
||||||
compositorPackage
|
compositorPackage
|
||||||
|
pkgs.glib # provides gdbus, used by the fprintd hardware probe in GreeterContent.qml
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
${
|
${
|
||||||
@@ -179,7 +180,9 @@ in
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -f settings.json ]; then
|
if [ -f settings.json ]; then
|
||||||
if cp "$(${jq} -r '.customThemeFile' settings.json)" custom-theme.json; then
|
theme_file="$(${jq} -r '.customThemeFile // empty' settings.json)"
|
||||||
|
if [ -f "$theme_file" ] && [ -r "$theme_file" ]; then
|
||||||
|
cp "$theme_file" custom-theme.json
|
||||||
mv settings.json settings.orig.json
|
mv settings.json settings.orig.json
|
||||||
${jq} '.customThemeFile = "${cacheDir}/custom-theme.json"' settings.orig.json > settings.json
|
${jq} '.customThemeFile = "${cacheDir}/custom-theme.json"' settings.orig.json > settings.json
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ Recommends: matugen
|
|||||||
Recommends: quickshell-git
|
Recommends: quickshell-git
|
||||||
Recommends: NetworkManager
|
Recommends: NetworkManager
|
||||||
Recommends: qt6-qtmultimedia
|
Recommends: qt6-qtmultimedia
|
||||||
|
Suggests: cups-pk-helper
|
||||||
Suggests: qt6ct
|
Suggests: qt6ct
|
||||||
|
|
||||||
Provides: dms
|
Provides: dms
|
||||||
|
|||||||
322
distro/opensuse/dms-greeter.spec
Normal file
322
distro/opensuse/dms-greeter.spec
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
# Spec for DMS Greeter - OpenSUSE/OBS
|
||||||
|
|
||||||
|
%global debug_package %{nil}
|
||||||
|
%global version VERSION_PLACEHOLDER
|
||||||
|
%global pkg_summary DankMaterialShell greeter for greetd
|
||||||
|
|
||||||
|
Name: dms-greeter
|
||||||
|
Version: %{version}
|
||||||
|
Release: RELEASE_PLACEHOLDER%{?dist}
|
||||||
|
Summary: %{pkg_summary}
|
||||||
|
|
||||||
|
License: MIT
|
||||||
|
URL: https://github.com/AvengeMedia/DankMaterialShell
|
||||||
|
|
||||||
|
Source0: dms-qml.tar.gz
|
||||||
|
|
||||||
|
BuildRequires: gzip
|
||||||
|
BuildRequires: wget
|
||||||
|
BuildRequires: systemd-rpm-macros
|
||||||
|
|
||||||
|
Requires: greetd
|
||||||
|
Requires: (quickshell-git or quickshell)
|
||||||
|
Requires(post): /usr/sbin/useradd
|
||||||
|
Requires(post): /usr/sbin/groupadd
|
||||||
|
|
||||||
|
Recommends: policycoreutils-python-utils
|
||||||
|
Recommends: acl
|
||||||
|
Suggests: niri
|
||||||
|
Suggests: hyprland
|
||||||
|
Suggests: sway
|
||||||
|
|
||||||
|
%description
|
||||||
|
DankMaterialShell greeter for greetd login manager. A modern, Material Design 3
|
||||||
|
inspired greeter interface built with Quickshell for Wayland compositors.
|
||||||
|
|
||||||
|
Supports multiple compositors including Niri, Hyprland, and Sway with automatic
|
||||||
|
compositor detection and configuration. Features session selection, user
|
||||||
|
authentication, and dynamic theming.
|
||||||
|
|
||||||
|
%prep
|
||||||
|
%setup -q -c -n dms-qml
|
||||||
|
|
||||||
|
%build
|
||||||
|
|
||||||
|
%install
|
||||||
|
# Install greeter files to shared data location
|
||||||
|
install -dm755 %{buildroot}%{_datadir}/quickshell/dms-greeter
|
||||||
|
cp -r %{_builddir}/dms-qml/* %{buildroot}%{_datadir}/quickshell/dms-greeter/
|
||||||
|
|
||||||
|
install -Dm755 %{_builddir}/dms-qml/Modules/Greetd/assets/dms-greeter %{buildroot}%{_bindir}/dms-greeter
|
||||||
|
|
||||||
|
install -Dm644 %{_builddir}/dms-qml/Modules/Greetd/README.md %{buildroot}%{_docdir}/dms-greeter/README.md
|
||||||
|
|
||||||
|
install -Dpm0644 %{_builddir}/dms-qml/systemd/tmpfiles-dms-greeter.conf %{buildroot}%{_tmpfilesdir}/dms-greeter.conf
|
||||||
|
|
||||||
|
install -Dm644 %{_builddir}/dms-qml/LICENSE %{buildroot}%{_docdir}/dms-greeter/LICENSE
|
||||||
|
|
||||||
|
install -dm755 %{buildroot}%{_sharedstatedir}/greeter
|
||||||
|
|
||||||
|
# Remove build and development files
|
||||||
|
rm -rf %{buildroot}%{_datadir}/quickshell/dms-greeter/.git*
|
||||||
|
rm -f %{buildroot}%{_datadir}/quickshell/dms-greeter/.gitignore
|
||||||
|
rm -rf %{buildroot}%{_datadir}/quickshell/dms-greeter/.github
|
||||||
|
rm -rf %{buildroot}%{_datadir}/quickshell/dms-greeter/distro
|
||||||
|
|
||||||
|
%posttrans
|
||||||
|
if [ -d "%{_sysconfdir}/xdg/quickshell/dms-greeter" ]; then
|
||||||
|
rmdir "%{_sysconfdir}/xdg/quickshell/dms-greeter" 2>/dev/null || true
|
||||||
|
rmdir "%{_sysconfdir}/xdg/quickshell" 2>/dev/null || true
|
||||||
|
rmdir "%{_sysconfdir}/xdg" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
%files
|
||||||
|
%dir %{_docdir}/dms-greeter
|
||||||
|
%license %{_docdir}/dms-greeter/LICENSE
|
||||||
|
%doc %{_docdir}/dms-greeter/README.md
|
||||||
|
%{_bindir}/dms-greeter
|
||||||
|
%dir %{_datadir}/quickshell
|
||||||
|
%{_datadir}/quickshell/dms-greeter/
|
||||||
|
%{_tmpfilesdir}/%{name}.conf
|
||||||
|
|
||||||
|
%pre
|
||||||
|
# Create greeter user/group if they don't exist
|
||||||
|
getent group greeter >/dev/null || groupadd -r greeter
|
||||||
|
getent passwd greeter >/dev/null || \
|
||||||
|
useradd -r -g greeter -d %{_sharedstatedir}/greeter -s /bin/bash \
|
||||||
|
-c "System Greeter" greeter
|
||||||
|
exit 0
|
||||||
|
|
||||||
|
%post
|
||||||
|
# SELinux contexts (no-op on OpenSUSE - semanage/restorecon not present)
|
||||||
|
if [ -x /usr/sbin/semanage ] && [ -x /usr/sbin/restorecon ]; then
|
||||||
|
semanage fcontext -a -t bin_t '%{_bindir}/dms-greeter' >/dev/null 2>&1 || true
|
||||||
|
restorecon %{_bindir}/dms-greeter >/dev/null 2>&1 || true
|
||||||
|
semanage fcontext -a -t user_home_dir_t '%{_sharedstatedir}/greeter(/.*)?' >/dev/null 2>&1 || true
|
||||||
|
restorecon -R %{_sharedstatedir}/greeter >/dev/null 2>&1 || true
|
||||||
|
semanage fcontext -a -t cache_home_t '%{_localstatedir}/cache/dms-greeter(/.*)?' >/dev/null 2>&1 || true
|
||||||
|
restorecon -R %{_localstatedir}/cache/dms-greeter >/dev/null 2>&1 || true
|
||||||
|
semanage fcontext -a -t usr_t '%{_datadir}/quickshell/dms-greeter(/.*)?' >/dev/null 2>&1 || true
|
||||||
|
restorecon -R %{_datadir}/quickshell/dms-greeter >/dev/null 2>&1 || true
|
||||||
|
restorecon %{_sysconfdir}/pam.d/greetd >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Resolve greeter runtime account/group for distro differences
|
||||||
|
GREETER_USER="greeter"
|
||||||
|
for candidate in greeter greetd _greeter; do
|
||||||
|
if getent passwd "$candidate" >/dev/null 2>&1; then
|
||||||
|
GREETER_USER="$candidate"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
GREETER_GROUP="$GREETER_USER"
|
||||||
|
if ! getent group "$GREETER_GROUP" >/dev/null 2>&1; then
|
||||||
|
for candidate in greeter greetd _greeter; do
|
||||||
|
if getent group "$candidate" >/dev/null 2>&1; then
|
||||||
|
GREETER_GROUP="$candidate"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure proper ownership of greeter directories
|
||||||
|
chown -R "$GREETER_USER:$GREETER_GROUP" %{_localstatedir}/cache/dms-greeter 2>/dev/null || true
|
||||||
|
chown -R "$GREETER_USER:$GREETER_GROUP" %{_sharedstatedir}/greeter 2>/dev/null || true
|
||||||
|
|
||||||
|
# Verify PAM configuration
|
||||||
|
PAM_CONFIG="/etc/pam.d/greetd"
|
||||||
|
write_greetd_pam_config() {
|
||||||
|
# openSUSE and Debian families usually expose PAM stacks as common-*
|
||||||
|
if [ -f /etc/pam.d/common-auth ] && [ -f /etc/pam.d/common-account ] && [ -f /etc/pam.d/common-password ] && [ -f /etc/pam.d/common-session ]; then
|
||||||
|
cat > "$PAM_CONFIG" << 'PAM_EOF'
|
||||||
|
#%PAM-1.0
|
||||||
|
auth include common-auth
|
||||||
|
account required pam_nologin.so
|
||||||
|
account include common-account
|
||||||
|
password include common-password
|
||||||
|
session required pam_loginuid.so
|
||||||
|
session optional pam_keyinit.so force revoke
|
||||||
|
session include common-session
|
||||||
|
PAM_EOF
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fedora/RHEL style system-auth/postlogin stack
|
||||||
|
if [ -f /etc/pam.d/system-auth ]; then
|
||||||
|
if [ -f /etc/pam.d/postlogin ]; then
|
||||||
|
cat > "$PAM_CONFIG" << 'PAM_EOF'
|
||||||
|
#%PAM-1.0
|
||||||
|
auth substack system-auth
|
||||||
|
auth include postlogin
|
||||||
|
account required pam_nologin.so
|
||||||
|
account include system-auth
|
||||||
|
password include system-auth
|
||||||
|
session required pam_loginuid.so
|
||||||
|
session optional pam_keyinit.so force revoke
|
||||||
|
session include system-auth
|
||||||
|
session include postlogin
|
||||||
|
PAM_EOF
|
||||||
|
else
|
||||||
|
cat > "$PAM_CONFIG" << 'PAM_EOF'
|
||||||
|
#%PAM-1.0
|
||||||
|
auth include system-auth
|
||||||
|
account required pam_nologin.so
|
||||||
|
account include system-auth
|
||||||
|
password include system-auth
|
||||||
|
session required pam_loginuid.so
|
||||||
|
session optional pam_keyinit.so force revoke
|
||||||
|
session include system-auth
|
||||||
|
PAM_EOF
|
||||||
|
fi
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Last-resort conservative fallback
|
||||||
|
cat > "$PAM_CONFIG" << 'PAM_EOF'
|
||||||
|
#%PAM-1.0
|
||||||
|
auth required pam_unix.so nullok
|
||||||
|
account required pam_unix.so
|
||||||
|
password required pam_unix.so nullok sha512
|
||||||
|
session required pam_unix.so
|
||||||
|
PAM_EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ ! -f "$PAM_CONFIG" ]; then
|
||||||
|
write_greetd_pam_config
|
||||||
|
chmod 644 "$PAM_CONFIG"
|
||||||
|
[ "$1" -eq 1 ] && echo "Created PAM configuration for greetd"
|
||||||
|
else
|
||||||
|
NEEDS_PAM_UPDATE=0
|
||||||
|
if grep -q "common-auth" "$PAM_CONFIG"; then
|
||||||
|
if [ ! -f /etc/pam.d/common-auth ]; then
|
||||||
|
NEEDS_PAM_UPDATE=1
|
||||||
|
fi
|
||||||
|
elif grep -q "system-auth" "$PAM_CONFIG"; then
|
||||||
|
if [ ! -f /etc/pam.d/system-auth ]; then
|
||||||
|
NEEDS_PAM_UPDATE=1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
NEEDS_PAM_UPDATE=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$NEEDS_PAM_UPDATE" -eq 1 ]; then
|
||||||
|
cp "$PAM_CONFIG" "$PAM_CONFIG.backup-dms-greeter"
|
||||||
|
write_greetd_pam_config
|
||||||
|
chmod 644 "$PAM_CONFIG"
|
||||||
|
[ "$1" -eq 1 ] && echo "Updated PAM configuration (old config backed up to $PAM_CONFIG.backup-dms-greeter)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Auto-configure greetd config
|
||||||
|
GREETD_CONFIG="/etc/greetd/config.toml"
|
||||||
|
CONFIG_STATUS="Not modified (already configured)"
|
||||||
|
|
||||||
|
COMPOSITOR=""
|
||||||
|
for candidate in niri Hyprland sway; do
|
||||||
|
if command -v "$candidate" >/dev/null 2>&1; then
|
||||||
|
case "$candidate" in
|
||||||
|
Hyprland)
|
||||||
|
COMPOSITOR="hyprland"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
COMPOSITOR="$candidate"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ! -f "$GREETD_CONFIG" ]; then
|
||||||
|
mkdir -p /etc/greetd
|
||||||
|
if [ -n "$COMPOSITOR" ]; then
|
||||||
|
cat > "$GREETD_CONFIG" << 'GREETD_EOF'
|
||||||
|
[terminal]
|
||||||
|
vt = 1
|
||||||
|
|
||||||
|
[default_session]
|
||||||
|
user = "GREETER_USER_PLACEHOLDER"
|
||||||
|
command = "/usr/bin/dms-greeter --command COMPOSITOR_PLACEHOLDER"
|
||||||
|
GREETD_EOF
|
||||||
|
sed -i "s|GREETER_USER_PLACEHOLDER|$GREETER_USER|" "$GREETD_CONFIG"
|
||||||
|
sed -i "s|COMPOSITOR_PLACEHOLDER|$COMPOSITOR|" "$GREETD_CONFIG"
|
||||||
|
CONFIG_STATUS="Created new config with $COMPOSITOR ✓"
|
||||||
|
else
|
||||||
|
cat > "$GREETD_CONFIG" << 'GREETD_EOF'
|
||||||
|
[terminal]
|
||||||
|
vt = 1
|
||||||
|
|
||||||
|
[default_session]
|
||||||
|
user = "GREETER_USER_PLACEHOLDER"
|
||||||
|
command = "agreety --cmd /bin/login"
|
||||||
|
GREETD_EOF
|
||||||
|
sed -i "s|GREETER_USER_PLACEHOLDER|$GREETER_USER|" "$GREETD_CONFIG"
|
||||||
|
CONFIG_STATUS="Created safe fallback config (no supported compositor detected)"
|
||||||
|
fi
|
||||||
|
elif ! grep -q "dms-greeter" "$GREETD_CONFIG"; then
|
||||||
|
if [ -n "$COMPOSITOR" ]; then
|
||||||
|
BACKUP_FILE="${GREETD_CONFIG}.backup-$(date +%%Y%%m%%d-%%H%%M%%S)"
|
||||||
|
cp "$GREETD_CONFIG" "$BACKUP_FILE" 2>/dev/null || true
|
||||||
|
sed -i "/^\[default_session\]/,/^\[/ s|^command =.*|command = \"/usr/bin/dms-greeter --command $COMPOSITOR\"|" "$GREETD_CONFIG"
|
||||||
|
sed -i "/^\[default_session\]/,/^\[/ s|^user =.*|user = \"$GREETER_USER\"|" "$GREETD_CONFIG"
|
||||||
|
CONFIG_STATUS="Updated existing config (backed up) with $COMPOSITOR ✓"
|
||||||
|
else
|
||||||
|
CONFIG_STATUS="Skipped dms-greeter command update (no supported compositor detected)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set graphical.target as default
|
||||||
|
CURRENT_TARGET=$(systemctl get-default 2>/dev/null || echo "unknown")
|
||||||
|
if [ "$CURRENT_TARGET" != "graphical.target" ]; then
|
||||||
|
systemctl set-default graphical.target >/dev/null 2>&1 || true
|
||||||
|
TARGET_STATUS="Set to graphical.target (was: $CURRENT_TARGET) ✓"
|
||||||
|
else
|
||||||
|
TARGET_STATUS="Already graphical.target ✓"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$1" -eq 1 ]; then
|
||||||
|
cat << 'EOF'
|
||||||
|
|
||||||
|
=========================================================================
|
||||||
|
DMS Greeter Installation Complete!
|
||||||
|
=========================================================================
|
||||||
|
|
||||||
|
Status:
|
||||||
|
EOF
|
||||||
|
echo " ✓ Greetd config: $CONFIG_STATUS"
|
||||||
|
echo " ✓ Default target: $TARGET_STATUS"
|
||||||
|
cat << 'EOF'
|
||||||
|
✓ Greeter user: Created
|
||||||
|
✓ Greeter directories: /var/cache/dms-greeter, /var/lib/greeter
|
||||||
|
✓ SELinux contexts: Applied (if applicable)
|
||||||
|
|
||||||
|
Next steps:
|
||||||
|
|
||||||
|
1. Enable the greeter:
|
||||||
|
dms greeter enable
|
||||||
|
|
||||||
|
2. Sync your theme with the greeter (optional):
|
||||||
|
dms greeter sync
|
||||||
|
|
||||||
|
3. Check your setup:
|
||||||
|
dms greeter status
|
||||||
|
|
||||||
|
Ready to test? Run: sudo systemctl start greetd
|
||||||
|
Documentation: https://danklinux.com/docs/dankgreeter/
|
||||||
|
=========================================================================
|
||||||
|
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
%postun
|
||||||
|
if [ "$1" -eq 0 ] && [ -x /usr/sbin/semanage ]; then
|
||||||
|
semanage fcontext -d '%{_bindir}/dms-greeter' 2>/dev/null || true
|
||||||
|
semanage fcontext -d '%{_sharedstatedir}/greeter(/.*)?' 2>/dev/null || true
|
||||||
|
semanage fcontext -d '%{_localstatedir}/cache/dms-greeter(/.*)?' 2>/dev/null || true
|
||||||
|
semanage fcontext -d '%{_datadir}/quickshell/dms-greeter(/.*)?' 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
%changelog
|
||||||
|
* CHANGELOG_DATE_PLACEHOLDER AvengeMedia <contact@avengemedia.com> - VERSION_PLACEHOLDER-RELEASE_PLACEHOLDER
|
||||||
|
- Stable release VERSION_PLACEHOLDER
|
||||||
|
- Initial OpenSUSE/OBS port from Fedora
|
||||||
@@ -27,6 +27,7 @@ Recommends: danksearch
|
|||||||
Recommends: matugen
|
Recommends: matugen
|
||||||
Recommends: NetworkManager
|
Recommends: NetworkManager
|
||||||
Recommends: qt6-qtmultimedia
|
Recommends: qt6-qtmultimedia
|
||||||
|
Suggests: cups-pk-helper
|
||||||
Suggests: qt6ct
|
Suggests: qt6ct
|
||||||
|
|
||||||
%description
|
%description
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
OBS_BASE_PROJECT="home:AvengeMedia"
|
OBS_BASE_PROJECT="home:AvengeMedia"
|
||||||
OBS_BASE="$HOME/.cache/osc-checkouts"
|
OBS_BASE="$HOME/.cache/osc-checkouts"
|
||||||
|
|
||||||
ALL_PACKAGES=(dms dms-git)
|
ALL_PACKAGES=(dms dms-git dms-greeter)
|
||||||
|
|
||||||
REPOS=("Debian_13" "openSUSE_Tumbleweed" "16.0")
|
REPOS=("Debian_13" "openSUSE_Tumbleweed" "16.0")
|
||||||
ARCHES=("x86_64" "aarch64")
|
ARCHES=("x86_64" "aarch64")
|
||||||
@@ -41,6 +41,9 @@ for pkg in "${PACKAGES[@]}"; do
|
|||||||
dms-git)
|
dms-git)
|
||||||
PROJECT="$OBS_BASE_PROJECT:dms-git"
|
PROJECT="$OBS_BASE_PROJECT:dms-git"
|
||||||
;;
|
;;
|
||||||
|
dms-greeter)
|
||||||
|
PROJECT="$OBS_BASE_PROJECT:danklinux"
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Error: Unknown package '$pkg'"
|
echo "Error: Unknown package '$pkg'"
|
||||||
continue
|
continue
|
||||||
@@ -74,11 +77,15 @@ for pkg in "${PACKAGES[@]}"; do
|
|||||||
COLOR="\033[0;32m" # Green
|
COLOR="\033[0;32m" # Green
|
||||||
SYMBOL="✅"
|
SYMBOL="✅"
|
||||||
;;
|
;;
|
||||||
failed)
|
failed|broken|broken*)
|
||||||
COLOR="\033[0;31m" # Red
|
COLOR="\033[0;31m" # Red
|
||||||
SYMBOL="❌"
|
SYMBOL="❌"
|
||||||
FAILED_BUILDS+=("$repo $arch")
|
FAILED_BUILDS+=("$repo $arch")
|
||||||
;;
|
;;
|
||||||
|
blocked)
|
||||||
|
COLOR="\033[0;33m" # Yellow
|
||||||
|
SYMBOL="⏸️"
|
||||||
|
;;
|
||||||
unresolvable)
|
unresolvable)
|
||||||
COLOR="\033[0;33m" # Yellow
|
COLOR="\033[0;33m" # Yellow
|
||||||
SYMBOL="⚠️"
|
SYMBOL="⚠️"
|
||||||
|
|||||||
@@ -68,13 +68,14 @@ fi
|
|||||||
|
|
||||||
OBS_BASE_PROJECT="home:AvengeMedia"
|
OBS_BASE_PROJECT="home:AvengeMedia"
|
||||||
OBS_BASE="$HOME/.cache/osc-checkouts"
|
OBS_BASE="$HOME/.cache/osc-checkouts"
|
||||||
AVAILABLE_PACKAGES=(dms dms-git)
|
AVAILABLE_PACKAGES=(dms dms-git dms-greeter)
|
||||||
|
|
||||||
if [[ -z "$PACKAGE" ]]; then
|
if [[ -z "$PACKAGE" ]]; then
|
||||||
echo "Available packages:"
|
echo "Available packages:"
|
||||||
echo ""
|
echo ""
|
||||||
echo " 1. dms - Stable DMS"
|
echo " 1. dms - Stable DMS"
|
||||||
echo " 2. dms-git - Nightly DMS"
|
echo " 2. dms-git - Nightly DMS"
|
||||||
|
echo " 3. dms-greeter - DMS greeter for greetd"
|
||||||
echo " a. all"
|
echo " a. all"
|
||||||
echo ""
|
echo ""
|
||||||
read -r -p "Select package (1-${#AVAILABLE_PACKAGES[@]}, a): " selection
|
read -r -p "Select package (1-${#AVAILABLE_PACKAGES[@]}, a): " selection
|
||||||
@@ -140,8 +141,13 @@ check_obs_version_exists() {
|
|||||||
echo "⚠️ Version $VERSION already exists in OBS"
|
echo "⚠️ Version $VERSION already exists in OBS"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
else
|
||||||
|
# Empty/invalid response: expected on first upload (no spec on server yet), or actual API failure
|
||||||
|
if [[ -z "$OBS_SPEC" ]]; then
|
||||||
|
echo " No existing spec on OBS (first upload?) - proceeding"
|
||||||
else
|
else
|
||||||
echo "⚠️ Could not fetch OBS spec (API may be unavailable), proceeding anyway"
|
echo "⚠️ Could not fetch OBS spec (API may be unavailable), proceeding anyway"
|
||||||
|
fi
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
return 1
|
return 1
|
||||||
@@ -168,6 +174,22 @@ update_debian_dms_service() {
|
|||||||
sed -i "s|/releases/download/v[0-9][^\"]*/dms-distropkg-arm64\.gz|/releases/download/v${base_version}/dms-distropkg-arm64.gz|" "$service_path"
|
sed -i "s|/releases/download/v[0-9][^\"]*/dms-distropkg-arm64\.gz|/releases/download/v${base_version}/dms-distropkg-arm64.gz|" "$service_path"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update_debian_dms_greeter_service() {
|
||||||
|
local service_path="$1"
|
||||||
|
if [[ -z "$service_path" || ! -f "$service_path" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ -z "$CHANGELOG_VERSION" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
local base_version
|
||||||
|
base_version=$(echo "$CHANGELOG_VERSION" | sed -E 's/^([0-9]+(\.[0-9]+)*).*/\1/')
|
||||||
|
if [[ -z "$base_version" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sed -i "s|/releases/download/v[0-9][^\"]*/dms-qml\.tar\.gz|/releases/download/v${base_version}/dms-qml.tar.gz|" "$service_path"
|
||||||
|
}
|
||||||
|
|
||||||
update_opensuse_git_spec() {
|
update_opensuse_git_spec() {
|
||||||
local spec_path="$1"
|
local spec_path="$1"
|
||||||
if [[ -z "$spec_path" || ! -f "$spec_path" ]]; then
|
if [[ -z "$spec_path" || ! -f "$spec_path" ]]; then
|
||||||
@@ -248,6 +270,9 @@ dms)
|
|||||||
dms-git)
|
dms-git)
|
||||||
PROJECT="dms-git"
|
PROJECT="dms-git"
|
||||||
;;
|
;;
|
||||||
|
dms-greeter)
|
||||||
|
PROJECT="danklinux"
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Error: Unknown package '$PACKAGE'"
|
echo "Error: Unknown package '$PACKAGE'"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -329,9 +354,11 @@ if [[ -d "distro/debian/$PACKAGE/debian" ]]; then
|
|||||||
echo " - Applied rebuild suffix: $CHANGELOG_VERSION"
|
echo " - Applied rebuild suffix: $CHANGELOG_VERSION"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Keep Debian dms _service in sync with changelog version
|
# Keep Debian _service in sync with changelog version
|
||||||
if [[ "$PACKAGE" == "dms" ]] && [[ -f "distro/debian/$PACKAGE/_service" ]]; then
|
if [[ "$PACKAGE" == "dms" ]] && [[ -f "distro/debian/$PACKAGE/_service" ]]; then
|
||||||
update_debian_dms_service "distro/debian/$PACKAGE/_service"
|
update_debian_dms_service "distro/debian/$PACKAGE/_service"
|
||||||
|
elif [[ "$PACKAGE" == "dms-greeter" ]] && [[ -f "distro/debian/$PACKAGE/_service" ]]; then
|
||||||
|
update_debian_dms_greeter_service "distro/debian/$PACKAGE/_service"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if this version already exists in OBS
|
# Check if this version already exists in OBS
|
||||||
@@ -341,6 +368,12 @@ if [[ -d "distro/debian/$PACKAGE/debian" ]]; then
|
|||||||
if [[ "$PACKAGE" == *"-git" ]]; then
|
if [[ "$PACKAGE" == *"-git" ]]; then
|
||||||
echo "==> Error: This commit is already uploaded to OBS"
|
echo "==> Error: This commit is already uploaded to OBS"
|
||||||
echo " The same git commit ($(echo "$CHANGELOG_VERSION" | grep -oP '[a-f0-9]{8}' | tail -1)) already exists on OBS."
|
echo " The same git commit ($(echo "$CHANGELOG_VERSION" | grep -oP '[a-f0-9]{8}' | tail -1)) already exists on OBS."
|
||||||
|
if [[ -n "${GITHUB_ACTIONS:-}" ]] || [[ -n "${CI:-}" ]]; then
|
||||||
|
echo " CI run detected: skipping upload as a no-op (already up to date)."
|
||||||
|
echo " If you need to force rebuild this same commit, set REBUILD_RELEASE (e.g. 2, 3, ...)."
|
||||||
|
echo "✓ Exiting gracefully (no changes needed)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
echo " To rebuild the same commit, specify a rebuild number:"
|
echo " To rebuild the same commit, specify a rebuild number:"
|
||||||
echo " ./distro/scripts/obs-upload.sh $PACKAGE 2"
|
echo " ./distro/scripts/obs-upload.sh $PACKAGE 2"
|
||||||
echo " ./distro/scripts/obs-upload.sh $PACKAGE 3"
|
echo " ./distro/scripts/obs-upload.sh $PACKAGE 3"
|
||||||
@@ -379,6 +412,13 @@ if [[ "$UPLOAD_OPENSUSE" == true ]] && [[ -f "distro/opensuse/$PACKAGE.spec" ]];
|
|||||||
|
|
||||||
if [[ "$PACKAGE" == *"-git" ]] && [[ -n "$CHANGELOG_VERSION" ]]; then
|
if [[ "$PACKAGE" == *"-git" ]] && [[ -n "$CHANGELOG_VERSION" ]]; then
|
||||||
update_opensuse_git_spec "$WORK_DIR/$PACKAGE.spec"
|
update_opensuse_git_spec "$WORK_DIR/$PACKAGE.spec"
|
||||||
|
elif [[ "$PACKAGE" == "dms-greeter" ]] && [[ -n "$CHANGELOG_VERSION" ]]; then
|
||||||
|
DMS_GREETER_BASE_VERSION=$(echo "$CHANGELOG_VERSION" | sed -E 's/^([0-9]+(\.[0-9]+)*).*/\1/')
|
||||||
|
DMS_GREETER_RELEASE=$(echo "$CHANGELOG_VERSION" | sed -E 's/.*db([0-9]+)$/\1/' || echo "1")
|
||||||
|
CHANGELOG_DATE=$(date '+%a %b %d %Y')
|
||||||
|
sed -i "s/VERSION_PLACEHOLDER/${DMS_GREETER_BASE_VERSION}/g" "$WORK_DIR/$PACKAGE.spec"
|
||||||
|
sed -i "s/RELEASE_PLACEHOLDER/${DMS_GREETER_RELEASE}/g" "$WORK_DIR/$PACKAGE.spec"
|
||||||
|
sed -i "s/CHANGELOG_DATE_PLACEHOLDER/${CHANGELOG_DATE}/g" "$WORK_DIR/$PACKAGE.spec"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -f "$WORK_DIR/.osc/$PACKAGE.spec" ]]; then
|
if [[ -f "$WORK_DIR/.osc/$PACKAGE.spec" ]]; then
|
||||||
@@ -444,6 +484,24 @@ if [[ "$UPLOAD_OPENSUSE" == true ]] && [[ "$UPLOAD_DEBIAN" == false ]] && [[ -f
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# For dms-greeter: download dms-qml.tar.gz from _service URL
|
||||||
|
if [[ -z "${SOURCE_DIR:-}" ]] && [[ "$PACKAGE" == "dms-greeter" ]] && [[ -f "distro/debian/$PACKAGE/_service" ]]; then
|
||||||
|
DMS_GREETER_URL=$(grep -A 5 'name="download_url"' "distro/debian/$PACKAGE/_service" | grep "path" | sed 's/.*<param name="path">\(.*\)<\/param>.*/\1/' | head -1)
|
||||||
|
if [[ -n "$DMS_GREETER_URL" ]]; then
|
||||||
|
DMS_GREETER_FULL_URL="https://github.com${DMS_GREETER_URL}"
|
||||||
|
echo " Downloading dms-greeter source from: $DMS_GREETER_FULL_URL"
|
||||||
|
if wget -q -O "$TEMP_DIR/dms-qml.tar.gz" "$DMS_GREETER_FULL_URL" 2>/dev/null || \
|
||||||
|
curl -L -f -s -o "$TEMP_DIR/dms-qml.tar.gz" "$DMS_GREETER_FULL_URL" 2>/dev/null; then
|
||||||
|
cd "$TEMP_DIR"
|
||||||
|
tar -xzf dms-qml.tar.gz
|
||||||
|
if [[ -f "Modules/Greetd/assets/dms-greeter" ]]; then
|
||||||
|
SOURCE_DIR="$TEMP_DIR"
|
||||||
|
fi
|
||||||
|
cd "$REPO_ROOT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ -n "$SOURCE_DIR" && -d "$SOURCE_DIR" ]]; then
|
if [[ -n "$SOURCE_DIR" && -d "$SOURCE_DIR" ]]; then
|
||||||
SOURCE0=$(grep "^Source0:" "distro/opensuse/$PACKAGE.spec" | awk '{print $2}' | head -1)
|
SOURCE0=$(grep "^Source0:" "distro/opensuse/$PACKAGE.spec" | awk '{print $2}' | head -1)
|
||||||
|
|
||||||
@@ -452,6 +510,15 @@ if [[ "$UPLOAD_OPENSUSE" == true ]] && [[ "$UPLOAD_DEBIAN" == false ]] && [[ -f
|
|||||||
cd "$OBS_TARBALL_DIR"
|
cd "$OBS_TARBALL_DIR"
|
||||||
|
|
||||||
case "$PACKAGE" in
|
case "$PACKAGE" in
|
||||||
|
dms-greeter)
|
||||||
|
EXPECTED_DIR="dms-qml"
|
||||||
|
echo " Creating $SOURCE0 (directory: $EXPECTED_DIR)"
|
||||||
|
mkdir -p "$EXPECTED_DIR"
|
||||||
|
cp -a "$SOURCE_DIR"/. "$EXPECTED_DIR/"
|
||||||
|
tar -czf "$WORK_DIR/$SOURCE0" "$EXPECTED_DIR"
|
||||||
|
rm -rf "$EXPECTED_DIR"
|
||||||
|
echo " Created $SOURCE0 ($(stat -c%s "$WORK_DIR/$SOURCE0" 2>/dev/null || echo 0) bytes)"
|
||||||
|
;;
|
||||||
dms)
|
dms)
|
||||||
DMS_VERSION=$(grep "^Version:" "$REPO_ROOT/distro/opensuse/$PACKAGE.spec" | sed 's/^Version:[[:space:]]*//' | head -1)
|
DMS_VERSION=$(grep "^Version:" "$REPO_ROOT/distro/opensuse/$PACKAGE.spec" | sed 's/^Version:[[:space:]]*//' | head -1)
|
||||||
EXPECTED_DIR="DankMaterialShell-${DMS_VERSION}"
|
EXPECTED_DIR="DankMaterialShell-${DMS_VERSION}"
|
||||||
@@ -584,6 +651,17 @@ if [[ "$UPLOAD_DEBIAN" == true ]] && [[ -d "distro/debian/$PACKAGE/debian" ]]; t
|
|||||||
if [[ -z "$SOURCE_DIR" ]]; then
|
if [[ -z "$SOURCE_DIR" ]]; then
|
||||||
SOURCE_DIR=$(find . -maxdepth 1 -type d ! -name "." | head -1)
|
SOURCE_DIR=$(find . -maxdepth 1 -type d ! -name "." | head -1)
|
||||||
fi
|
fi
|
||||||
|
# dms-qml.tar.gz extracts flat (quickshell contents, no top-level dir)
|
||||||
|
# Create dms-qml wrapper so combined tarball has correct top-level dir (like dms)
|
||||||
|
if [[ "$PACKAGE" == "dms-greeter" ]] && [[ -f "Modules/Greetd/assets/dms-greeter" ]]; then
|
||||||
|
mkdir -p dms-qml
|
||||||
|
for f in *; do
|
||||||
|
if [[ -e "$f" && "$f" != "dms-qml" && "$f" != "source-archive" ]]; then
|
||||||
|
mv "$f" dms-qml/ 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SOURCE_DIR="dms-qml"
|
||||||
|
fi
|
||||||
if [[ -z "$SOURCE_DIR" || ! -d "$SOURCE_DIR" ]]; then
|
if [[ -z "$SOURCE_DIR" || ! -d "$SOURCE_DIR" ]]; then
|
||||||
echo "Error: Failed to extract source archive or find source directory"
|
echo "Error: Failed to extract source archive or find source directory"
|
||||||
echo "Contents of $TEMP_DIR:"
|
echo "Contents of $TEMP_DIR:"
|
||||||
@@ -660,6 +738,21 @@ if [[ "$UPLOAD_DEBIAN" == true ]] && [[ -d "distro/debian/$PACKAGE/debian" ]]; t
|
|||||||
cd "$OBS_TARBALL_DIR"
|
cd "$OBS_TARBALL_DIR"
|
||||||
|
|
||||||
case "$PACKAGE" in
|
case "$PACKAGE" in
|
||||||
|
dms-greeter)
|
||||||
|
EXPECTED_DIR="dms-qml"
|
||||||
|
echo " Creating $SOURCE0 (directory: $EXPECTED_DIR)"
|
||||||
|
mkdir -p "$EXPECTED_DIR"
|
||||||
|
cp -a "$SOURCE_DIR"/. "$EXPECTED_DIR/"
|
||||||
|
if [[ "$SOURCE0" == *.tar.xz ]]; then
|
||||||
|
tar --sort=name --mtime='2000-01-01 00:00:00' --owner=0 --group=0 -cJf "$WORK_DIR/$SOURCE0" "$EXPECTED_DIR"
|
||||||
|
elif [[ "$SOURCE0" == *.tar.bz2 ]]; then
|
||||||
|
tar --sort=name --mtime='2000-01-01 00:00:00' --owner=0 --group=0 -cjf "$WORK_DIR/$SOURCE0" "$EXPECTED_DIR"
|
||||||
|
else
|
||||||
|
tar --sort=name --mtime='2000-01-01 00:00:00' --owner=0 --group=0 -czf "$WORK_DIR/$SOURCE0" "$EXPECTED_DIR"
|
||||||
|
fi
|
||||||
|
rm -rf "$EXPECTED_DIR"
|
||||||
|
echo " Created $SOURCE0 ($(stat -c%s "$WORK_DIR/$SOURCE0" 2>/dev/null || echo 0) bytes)"
|
||||||
|
;;
|
||||||
dms)
|
dms)
|
||||||
DMS_VERSION=$(grep "^Version:" "$REPO_ROOT/distro/opensuse/$PACKAGE.spec" | sed 's/^Version:[[:space:]]*//' | head -1)
|
DMS_VERSION=$(grep "^Version:" "$REPO_ROOT/distro/opensuse/$PACKAGE.spec" | sed 's/^Version:[[:space:]]*//' | head -1)
|
||||||
EXPECTED_DIR="DankMaterialShell-${DMS_VERSION}"
|
EXPECTED_DIR="DankMaterialShell-${DMS_VERSION}"
|
||||||
@@ -709,10 +802,17 @@ if [[ "$UPLOAD_DEBIAN" == true ]] && [[ -d "distro/debian/$PACKAGE/debian" ]]; t
|
|||||||
echo " - OpenSUSE source tarballs created"
|
echo " - OpenSUSE source tarballs created"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Copy and update OpenSUSE spec file with the correct version (for -git packages)
|
# Copy and update OpenSUSE spec file with the correct version
|
||||||
cp "distro/opensuse/$PACKAGE.spec" "$WORK_DIR/"
|
cp "distro/opensuse/$PACKAGE.spec" "$WORK_DIR/"
|
||||||
if [[ "$PACKAGE" == *"-git" ]] && [[ -n "$CHANGELOG_VERSION" ]]; then
|
if [[ "$PACKAGE" == *"-git" ]] && [[ -n "$CHANGELOG_VERSION" ]]; then
|
||||||
update_opensuse_git_spec "$WORK_DIR/$PACKAGE.spec"
|
update_opensuse_git_spec "$WORK_DIR/$PACKAGE.spec"
|
||||||
|
elif [[ "$PACKAGE" == "dms-greeter" ]] && [[ -n "$CHANGELOG_VERSION" ]]; then
|
||||||
|
DMS_GREETER_BASE_VERSION=$(echo "$CHANGELOG_VERSION" | sed -E 's/^([0-9]+(\.[0-9]+)*).*/\1/')
|
||||||
|
DMS_GREETER_RELEASE=$(echo "$CHANGELOG_VERSION" | sed -E 's/.*db([0-9]+)$/\1/' || echo "1")
|
||||||
|
CHANGELOG_DATE=$(date '+%a %b %d %Y')
|
||||||
|
sed -i "s/VERSION_PLACEHOLDER/${DMS_GREETER_BASE_VERSION}/g" "$WORK_DIR/$PACKAGE.spec"
|
||||||
|
sed -i "s/RELEASE_PLACEHOLDER/${DMS_GREETER_RELEASE}/g" "$WORK_DIR/$PACKAGE.spec"
|
||||||
|
sed -i "s/CHANGELOG_DATE_PLACEHOLDER/${CHANGELOG_DATE}/g" "$WORK_DIR/$PACKAGE.spec"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -839,8 +939,48 @@ EOF
|
|||||||
echo " - Quilt format detected: creating debian.tar.gz"
|
echo " - Quilt format detected: creating debian.tar.gz"
|
||||||
tar -czf "$WORK_DIR/debian.tar.gz" -C "distro/debian/$PACKAGE" debian/
|
tar -czf "$WORK_DIR/debian.tar.gz" -C "distro/debian/$PACKAGE" debian/
|
||||||
|
|
||||||
|
# For dms-greeter: create orig tarball so Debian build gets upstream (OBS only passes .dsc Files to Debian)
|
||||||
|
DSC_FILES_DEBIAN=""
|
||||||
|
if [[ "$PACKAGE" == "dms-greeter" ]]; then
|
||||||
|
UPSTREAM_VER=$(echo "$VERSION" | sed 's/-[^-]*$//')
|
||||||
|
ORIG_TARBALL="${PACKAGE}_${UPSTREAM_VER}.orig.tar.gz"
|
||||||
|
ORIG_DIR="${PACKAGE}-${UPSTREAM_VER}"
|
||||||
|
|
||||||
|
if [[ -f "distro/debian/$PACKAGE/_service" ]] && grep -q "download_url" "distro/debian/$PACKAGE/_service"; then
|
||||||
|
DG_TEMP=$(mktemp -d)
|
||||||
|
DMS_GREETER_PATH=$(grep -A 5 'name="download_url"' "distro/debian/$PACKAGE/_service" | grep "path" | sed 's/.*<param name="path">\(.*\)<\/param>.*/\1/' | head -1)
|
||||||
|
if [[ -n "$DMS_GREETER_PATH" ]]; then
|
||||||
|
DG_URL="https://github.com${DMS_GREETER_PATH}"
|
||||||
|
echo " - Downloading dms-greeter source for orig tarball: $DG_URL"
|
||||||
|
if wget -q -O "$DG_TEMP/dms-qml.tar.gz" "$DG_URL" 2>/dev/null || curl -L -f -s -o "$DG_TEMP/dms-qml.tar.gz" "$DG_URL" 2>/dev/null; then
|
||||||
|
( cd "$DG_TEMP" && tar --no-same-owner -xzf dms-qml.tar.gz && mkdir -p "$ORIG_DIR" && \
|
||||||
|
for f in *; do [[ "$f" != "dms-qml.tar.gz" && "$f" != "$ORIG_DIR" ]] && mv "$f" "$ORIG_DIR/"; done )
|
||||||
|
if [[ -d "$DG_TEMP/$ORIG_DIR/Modules" ]] || [[ -f "$DG_TEMP/$ORIG_DIR/LICENSE" ]]; then
|
||||||
|
tar --sort=name --mtime='2000-01-01 00:00:00' --owner=0 --group=0 -czf "$WORK_DIR/$ORIG_TARBALL" -C "$DG_TEMP" "$ORIG_DIR"
|
||||||
|
ORIG_MD5=$(md5sum "$WORK_DIR/$ORIG_TARBALL" | cut -d' ' -f1)
|
||||||
|
ORIG_SIZE=$(stat -c%s "$WORK_DIR/$ORIG_TARBALL" 2>/dev/null || stat -f%z "$WORK_DIR/$ORIG_TARBALL" 2>/dev/null)
|
||||||
|
DSC_FILES_DEBIAN=" $ORIG_MD5 $ORIG_SIZE $ORIG_TARBALL
|
||||||
|
"
|
||||||
|
echo " - Created $ORIG_TARBALL for Debian orig"
|
||||||
|
fi
|
||||||
|
rm -rf "$DG_TEMP"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
DEBIAN_MD5=$(md5sum "$WORK_DIR/debian.tar.gz" | cut -d' ' -f1)
|
||||||
|
DEBIAN_SIZE=$(stat -c%s "$WORK_DIR/debian.tar.gz" 2>/dev/null || stat -f%z "$WORK_DIR/debian.tar.gz" 2>/dev/null)
|
||||||
|
|
||||||
echo " - Generating $PACKAGE.dsc for quilt format"
|
echo " - Generating $PACKAGE.dsc for quilt format"
|
||||||
cat >"$WORK_DIR/$PACKAGE.dsc" <<EOF
|
# debtransform: DEBTRANSFORM-TAR = orig (upstream), DEBTRANSFORM-FILES-TAR = debian archive
|
||||||
|
DEBTRANSFORM_EXTRA=""
|
||||||
|
if [[ -n "$DSC_FILES_DEBIAN" ]] && [[ -n "$ORIG_TARBALL" ]]; then
|
||||||
|
DEBTRANSFORM_EXTRA="DEBTRANSFORM-TAR: $ORIG_TARBALL
|
||||||
|
DEBTRANSFORM-FILES-TAR: debian.tar.gz
|
||||||
|
"
|
||||||
|
fi
|
||||||
|
cat >"$WORK_DIR/$PACKAGE.dsc" <<DSCEOF
|
||||||
Format: 3.0 (quilt)
|
Format: 3.0 (quilt)
|
||||||
Source: $PACKAGE
|
Source: $PACKAGE
|
||||||
Binary: $PACKAGE
|
Binary: $PACKAGE
|
||||||
@@ -848,10 +988,9 @@ Architecture: any
|
|||||||
Version: $VERSION
|
Version: $VERSION
|
||||||
Maintainer: Avenge Media <AvengeMedia.US@gmail.com>
|
Maintainer: Avenge Media <AvengeMedia.US@gmail.com>
|
||||||
Build-Depends: debhelper-compat (= 13), wget, gzip
|
Build-Depends: debhelper-compat (= 13), wget, gzip
|
||||||
DEBTRANSFORM-TAR: debian.tar.gz
|
${DEBTRANSFORM_EXTRA}Files:${DSC_FILES_DEBIAN}
|
||||||
Files:
|
$DEBIAN_MD5 $DEBIAN_SIZE debian.tar.gz
|
||||||
00000000000000000000000000000000 1 debian.tar.gz
|
DSCEOF
|
||||||
EOF
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -882,6 +1021,13 @@ if [[ -n "$OBS_FILES" ]]; then
|
|||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Keep current orig tarball for dms-greeter (Debian 3.0 quilt needs it)
|
||||||
|
UPSTREAM_VER_CLEAN=$(echo "$CHANGELOG_VERSION" | sed 's/-[^-]*$//' 2>/dev/null)
|
||||||
|
if [[ "$PACKAGE" == "dms-greeter" ]] && [[ "$old_file" == "${PACKAGE}_${UPSTREAM_VER_CLEAN}.orig.tar.gz" ]]; then
|
||||||
|
echo " - Keeping orig tarball: $old_file"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ "$old_file" == "${PACKAGE}-source.tar.gz" ]]; then
|
if [[ "$old_file" == "${PACKAGE}-source.tar.gz" ]]; then
|
||||||
echo " - Keeping source tarball: $old_file"
|
echo " - Keeping source tarball: $old_file"
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -28,8 +28,17 @@ override_dh_auto_build:
|
|||||||
# Launchpad build environment has no internet access
|
# Launchpad build environment has no internet access
|
||||||
test -d dms-git-repo || (echo "ERROR: dms-git-repo directory not found!" && exit 1)
|
test -d dms-git-repo || (echo "ERROR: dms-git-repo directory not found!" && exit 1)
|
||||||
|
|
||||||
# Patch go.mod to use Go 1.24 base version (Ubuntu has 1.24.4, project requires 1.24.6)
|
# Patch go.mod for Launchpad: align go directive w/latest Go toolchain
|
||||||
sed -i 's/^go 1\.24\.[0-9]*/go 1.24/' dms-git-repo/core/go.mod
|
GO_VERSION=$$(go env GOVERSION | sed -E 's/^go([0-9]+\.[0-9]+).*/\1/'); \
|
||||||
|
if [ -n "$$GO_VERSION" ]; then \
|
||||||
|
sed -E -i "s/^go 1\.[0-9]+(\.[0-9]+)?/go $$GO_VERSION/" dms-git-repo/core/go.mod; \
|
||||||
|
if [ -f dms-git-repo/core/vendor/modules.txt ]; then \
|
||||||
|
sed -E -i "s/^(## explicit; go )1\.[0-9]+(\.[0-9]+)?$$/\1$$GO_VERSION/" dms-git-repo/core/vendor/modules.txt; \
|
||||||
|
fi; \
|
||||||
|
else \
|
||||||
|
echo "Warning: Could not detect Go version, leaving go.mod go directive unchanged"; \
|
||||||
|
fi
|
||||||
|
sed -E -i '/^toolchain go[0-9.]+$$/d' dms-git-repo/core/go.mod
|
||||||
|
|
||||||
# Build dms-cli from source
|
# Build dms-cli from source
|
||||||
# Detect architecture
|
# Detect architecture
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ Architecture: any
|
|||||||
Depends: ${misc:Depends},
|
Depends: ${misc:Depends},
|
||||||
greetd,
|
greetd,
|
||||||
quickshell-git | quickshell
|
quickshell-git | quickshell
|
||||||
Recommends: niri | hyprland | sway
|
Suggests: niri | hyprland | sway
|
||||||
Description: DankMaterialShell greeter for greetd
|
Description: DankMaterialShell greeter for greetd
|
||||||
DankMaterialShell greeter for greetd login manager. A modern, Material Design 3
|
DankMaterialShell greeter for greetd login manager. A modern, Material Design 3
|
||||||
inspired greeter interface built with Quickshell for Wayland compositors.
|
inspired greeter interface built with Quickshell for Wayland compositors.
|
||||||
|
|||||||
6
flake.lock
generated
6
flake.lock
generated
@@ -2,11 +2,11 @@
|
|||||||
"nodes": {
|
"nodes": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1769018530,
|
"lastModified": 1771369470,
|
||||||
"narHash": "sha256-MJ27Cy2NtBEV5tsK+YraYr2g851f3Fl1LpNHDzDX15c=",
|
"narHash": "sha256-0NBlEBKkN3lufyvFegY4TYv5mCNHbi5OmBDrzihbBMQ=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "88d3861acdd3d2f0e361767018218e51810df8a1",
|
"rev": "0182a361324364ae3f436a63005877674cf45efb",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
35
flake.nix
35
flake.nix
@@ -17,6 +17,25 @@
|
|||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
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 =
|
forEachSystem =
|
||||||
fn:
|
fn:
|
||||||
nixpkgs.lib.genAttrs [ "aarch64-darwin" "aarch64-linux" "x86_64-darwin" "x86_64-linux" ] (
|
nixpkgs.lib.genAttrs [ "aarch64-darwin" "aarch64-linux" "x86_64-darwin" "x86_64-linux" ] (
|
||||||
@@ -72,9 +91,14 @@
|
|||||||
"${cleanVersion}${dateSuffix}${revSuffix}";
|
"${cleanVersion}${dateSuffix}${revSuffix}";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
dms-shell = pkgs.buildGoModule (
|
dms-shell = pkgs.lib.makeOverridable (
|
||||||
|
{
|
||||||
|
extraQtPackages ? [ ],
|
||||||
|
}:
|
||||||
|
(pkgs.buildGoModule.override { go = goForPkgs pkgs; }) (
|
||||||
let
|
let
|
||||||
rootSrc = ./.;
|
rootSrc = ./.;
|
||||||
|
qtPackages = (qmlPkgs pkgs) ++ extraQtPackages;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
inherit version;
|
inherit version;
|
||||||
@@ -110,8 +134,8 @@
|
|||||||
|
|
||||||
wrapProgram $out/bin/dms \
|
wrapProgram $out/bin/dms \
|
||||||
--add-flags "-c $out/share/quickshell/dms" \
|
--add-flags "-c $out/share/quickshell/dms" \
|
||||||
--prefix "NIXPKGS_QT6_QML_IMPORT_PATH" ":" "${mkQmlImportPath pkgs (qmlPkgs pkgs)}" \
|
--prefix "NIXPKGS_QT6_QML_IMPORT_PATH" ":" "${mkQmlImportPath pkgs qtPackages}" \
|
||||||
--prefix "QT_PLUGIN_PATH" ":" "${mkQtPluginPath pkgs (qmlPkgs pkgs)}"
|
--prefix "QT_PLUGIN_PATH" ":" "${mkQtPluginPath pkgs qtPackages}"
|
||||||
|
|
||||||
install -Dm644 ${rootSrc}/assets/systemd/dms.service \
|
install -Dm644 ${rootSrc}/assets/systemd/dms.service \
|
||||||
$out/lib/systemd/user/dms.service
|
$out/lib/systemd/user/dms.service
|
||||||
@@ -141,7 +165,8 @@
|
|||||||
platforms = pkgs.lib.platforms.linux;
|
platforms = pkgs.lib.platforms.linux;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
|
) { };
|
||||||
|
|
||||||
quickshell = quickshell.packages.${system}.default;
|
quickshell = quickshell.packages.${system}.default;
|
||||||
|
|
||||||
@@ -181,7 +206,7 @@
|
|||||||
buildInputs =
|
buildInputs =
|
||||||
with pkgs;
|
with pkgs;
|
||||||
[
|
[
|
||||||
go_1_25
|
(goForPkgs pkgs)
|
||||||
gopls
|
gopls
|
||||||
delve
|
delve
|
||||||
go-tools
|
go-tools
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
|
|||||||
|
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import QtCore
|
import QtCore
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
@@ -70,20 +71,50 @@ Singleton {
|
|||||||
return appId;
|
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 {
|
function getAppIcon(appId: string, desktopEntry: var): string {
|
||||||
if (appId === "org.quickshell") {
|
if (appId === "org.quickshell") {
|
||||||
return Qt.resolvedUrl("../assets/danklogo.svg");
|
return Qt.resolvedUrl("../assets/danklogo.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
const moddedId = moddedAppId(appId);
|
const moddedId = moddedAppId(appId);
|
||||||
if (moddedId !== appId) {
|
if (moddedId !== appId)
|
||||||
return Quickshell.iconPath(moddedId, true);
|
return resolveIconPath(appId);
|
||||||
}
|
|
||||||
|
|
||||||
if (desktopEntry && desktopEntry.icon) {
|
if (desktopEntry && desktopEntry.icon) {
|
||||||
return Quickshell.iconPath(desktopEntry.icon, true);
|
return Quickshell.iconPath(desktopEntry.icon, true);
|
||||||
}
|
}
|
||||||
return Quickshell.iconPath(appId, true);
|
|
||||||
|
const icon = Quickshell.iconPath(appId, true);
|
||||||
|
if (icon && icon !== "")
|
||||||
|
return icon;
|
||||||
|
|
||||||
|
return DesktopService.resolveIconPath(appId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAppName(appId: string, desktopEntry: var): string {
|
function getAppName(appId: string, desktopEntry: var): string {
|
||||||
|
|||||||
@@ -12,6 +12,27 @@ Singleton {
|
|||||||
signal popoutOpening
|
signal popoutOpening
|
||||||
signal popoutChanged
|
signal popoutChanged
|
||||||
|
|
||||||
|
function _closePopout(popout) {
|
||||||
|
switch (true) {
|
||||||
|
case popout.dashVisible !== undefined:
|
||||||
|
popout.dashVisible = false;
|
||||||
|
return;
|
||||||
|
case popout.notificationHistoryVisible !== undefined:
|
||||||
|
popout.notificationHistoryVisible = false;
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
popout.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _isStale(popout) {
|
||||||
|
try {
|
||||||
|
return !popout || !("shouldBeVisible" in popout);
|
||||||
|
} catch (e) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function showPopout(popout) {
|
function showPopout(popout) {
|
||||||
if (!popout || !popout.screen)
|
if (!popout || !popout.screen)
|
||||||
return;
|
return;
|
||||||
@@ -23,13 +44,11 @@ Singleton {
|
|||||||
const otherPopout = currentPopoutsByScreen[otherScreenName];
|
const otherPopout = currentPopoutsByScreen[otherScreenName];
|
||||||
if (!otherPopout || otherPopout === popout)
|
if (!otherPopout || otherPopout === popout)
|
||||||
continue;
|
continue;
|
||||||
if (otherPopout.dashVisible !== undefined) {
|
if (_isStale(otherPopout)) {
|
||||||
otherPopout.dashVisible = false;
|
currentPopoutsByScreen[otherScreenName] = null;
|
||||||
} else if (otherPopout.notificationHistoryVisible !== undefined) {
|
continue;
|
||||||
otherPopout.notificationHistoryVisible = false;
|
|
||||||
} else {
|
|
||||||
otherPopout.close();
|
|
||||||
}
|
}
|
||||||
|
_closePopout(otherPopout);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentPopoutsByScreen[screenName] = popout;
|
currentPopoutsByScreen[screenName] = popout;
|
||||||
@@ -51,15 +70,9 @@ Singleton {
|
|||||||
function closeAllPopouts() {
|
function closeAllPopouts() {
|
||||||
for (const screenName in currentPopoutsByScreen) {
|
for (const screenName in currentPopoutsByScreen) {
|
||||||
const popout = currentPopoutsByScreen[screenName];
|
const popout = currentPopoutsByScreen[screenName];
|
||||||
if (!popout)
|
if (!popout || _isStale(popout))
|
||||||
continue;
|
continue;
|
||||||
if (popout.dashVisible !== undefined) {
|
_closePopout(popout);
|
||||||
popout.dashVisible = false;
|
|
||||||
} else if (popout.notificationHistoryVisible !== undefined) {
|
|
||||||
popout.notificationHistoryVisible = false;
|
|
||||||
} else {
|
|
||||||
popout.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
currentPopoutsByScreen = {};
|
currentPopoutsByScreen = {};
|
||||||
}
|
}
|
||||||
@@ -90,6 +103,12 @@ Singleton {
|
|||||||
if (!otherPopout)
|
if (!otherPopout)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
if (_isStale(otherPopout)) {
|
||||||
|
currentPopoutsByScreen[otherScreenName] = null;
|
||||||
|
currentPopoutTriggers[otherScreenName] = null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (otherPopout === popout) {
|
if (otherPopout === popout) {
|
||||||
movedFromOtherScreen = true;
|
movedFromOtherScreen = true;
|
||||||
currentPopoutsByScreen[otherScreenName] = null;
|
currentPopoutsByScreen[otherScreenName] = null;
|
||||||
@@ -97,45 +116,26 @@ Singleton {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (otherPopout.dashVisible !== undefined) {
|
_closePopout(otherPopout);
|
||||||
otherPopout.dashVisible = false;
|
|
||||||
} else if (otherPopout.notificationHistoryVisible !== undefined) {
|
|
||||||
otherPopout.notificationHistoryVisible = false;
|
|
||||||
} else {
|
|
||||||
otherPopout.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentPopout && currentPopout !== popout) {
|
if (currentPopout && currentPopout !== popout) {
|
||||||
if (currentPopout.dashVisible !== undefined) {
|
if (_isStale(currentPopout)) {
|
||||||
currentPopout.dashVisible = false;
|
currentPopoutsByScreen[screenName] = null;
|
||||||
} else if (currentPopout.notificationHistoryVisible !== undefined) {
|
currentPopoutTriggers[screenName] = null;
|
||||||
currentPopout.notificationHistoryVisible = false;
|
|
||||||
} else {
|
} else {
|
||||||
currentPopout.close();
|
_closePopout(currentPopout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentPopout === popout && popout.shouldBeVisible && !movedFromOtherScreen) {
|
if (currentPopout === popout && popout.shouldBeVisible && !movedFromOtherScreen) {
|
||||||
if (triggerId !== undefined && currentPopoutTriggers[screenName] === triggerId) {
|
if (triggerId !== undefined && currentPopoutTriggers[screenName] === triggerId) {
|
||||||
if (popout.dashVisible !== undefined) {
|
_closePopout(popout);
|
||||||
popout.dashVisible = false;
|
|
||||||
} else if (popout.notificationHistoryVisible !== undefined) {
|
|
||||||
popout.notificationHistoryVisible = false;
|
|
||||||
} else {
|
|
||||||
popout.close();
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (triggerId === undefined) {
|
if (triggerId === undefined) {
|
||||||
if (popout.dashVisible !== undefined) {
|
_closePopout(popout);
|
||||||
popout.dashVisible = false;
|
|
||||||
} else if (popout.notificationHistoryVisible !== undefined) {
|
|
||||||
popout.notificationHistoryVisible = false;
|
|
||||||
} else {
|
|
||||||
popout.close();
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -126,6 +126,10 @@ Singleton {
|
|||||||
property var hiddenOutputDeviceNames: []
|
property var hiddenOutputDeviceNames: []
|
||||||
property var hiddenInputDeviceNames: []
|
property var hiddenInputDeviceNames: []
|
||||||
|
|
||||||
|
property string launcherLastMode: "all"
|
||||||
|
property string appDrawerLastMode: "apps"
|
||||||
|
property string niriOverviewLastMode: "apps"
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (!isGreeterMode) {
|
if (!isGreeterMode) {
|
||||||
loadSettings();
|
loadSettings();
|
||||||
@@ -571,14 +575,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!newSettings[identifier]) {
|
newSettings[identifier] = getMonitorCyclingSettings(screenName);
|
||||||
newSettings[identifier] = {
|
|
||||||
"enabled": false,
|
|
||||||
"mode": "interval",
|
|
||||||
"interval": 300,
|
|
||||||
"time": "06:00"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
newSettings[identifier].enabled = enabled;
|
newSettings[identifier].enabled = enabled;
|
||||||
monitorCyclingSettings = newSettings;
|
monitorCyclingSettings = newSettings;
|
||||||
saveSettings();
|
saveSettings();
|
||||||
@@ -609,14 +606,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!newSettings[identifier]) {
|
newSettings[identifier] = getMonitorCyclingSettings(screenName);
|
||||||
newSettings[identifier] = {
|
|
||||||
"enabled": false,
|
|
||||||
"mode": "interval",
|
|
||||||
"interval": 300,
|
|
||||||
"time": "06:00"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
newSettings[identifier].mode = mode;
|
newSettings[identifier].mode = mode;
|
||||||
monitorCyclingSettings = newSettings;
|
monitorCyclingSettings = newSettings;
|
||||||
saveSettings();
|
saveSettings();
|
||||||
@@ -647,14 +637,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!newSettings[identifier]) {
|
newSettings[identifier] = getMonitorCyclingSettings(screenName);
|
||||||
newSettings[identifier] = {
|
|
||||||
"enabled": false,
|
|
||||||
"mode": "interval",
|
|
||||||
"interval": 300,
|
|
||||||
"time": "06:00"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
newSettings[identifier].interval = interval;
|
newSettings[identifier].interval = interval;
|
||||||
monitorCyclingSettings = newSettings;
|
monitorCyclingSettings = newSettings;
|
||||||
saveSettings();
|
saveSettings();
|
||||||
@@ -685,14 +668,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!newSettings[identifier]) {
|
newSettings[identifier] = getMonitorCyclingSettings(screenName);
|
||||||
newSettings[identifier] = {
|
|
||||||
"enabled": false,
|
|
||||||
"mode": "interval",
|
|
||||||
"interval": 300,
|
|
||||||
"time": "06:00"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
newSettings[identifier].time = time;
|
newSettings[identifier].time = time;
|
||||||
monitorCyclingSettings = newSettings;
|
monitorCyclingSettings = newSettings;
|
||||||
saveSettings();
|
saveSettings();
|
||||||
@@ -1100,6 +1076,21 @@ Singleton {
|
|||||||
saveSettings();
|
saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setLauncherLastMode(mode) {
|
||||||
|
launcherLastMode = mode;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setAppDrawerLastMode(mode) {
|
||||||
|
appDrawerLastMode = mode;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setNiriOverviewLastMode(mode) {
|
||||||
|
niriOverviewLastMode = mode;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
function syncWallpaperForCurrentMode() {
|
function syncWallpaperForCurrentMode() {
|
||||||
if (!perModeWallpaper)
|
if (!perModeWallpaper)
|
||||||
return;
|
return;
|
||||||
@@ -1186,7 +1177,7 @@ Singleton {
|
|||||||
"time": "06:00"
|
"time": "06:00"
|
||||||
};
|
};
|
||||||
var value = _findMonitorValue(monitorCyclingSettings, screenName);
|
var value = _findMonitorValue(monitorCyclingSettings, screenName);
|
||||||
return value !== undefined ? value : defaults;
|
return Object.assign({}, defaults, value !== undefined ? value : {});
|
||||||
}
|
}
|
||||||
|
|
||||||
FileView {
|
FileView {
|
||||||
@@ -1213,7 +1204,7 @@ Singleton {
|
|||||||
id: greeterSessionFile
|
id: greeterSessionFile
|
||||||
|
|
||||||
path: {
|
path: {
|
||||||
const greetCfgDir = Quickshell.env("DMS_GREET_CFG_DIR") || "/etc/greetd/.dms";
|
const greetCfgDir = Quickshell.env("DMS_GREET_CFG_DIR") || "/var/cache/dms-greeter";
|
||||||
return greetCfgDir + "/session.json";
|
return greetCfgDir + "/session.json";
|
||||||
}
|
}
|
||||||
preload: isGreeterMode
|
preload: isGreeterMode
|
||||||
|
|||||||
@@ -294,6 +294,17 @@ Singleton {
|
|||||||
property string centeringMode: "index"
|
property string centeringMode: "index"
|
||||||
property string clockDateFormat: ""
|
property string clockDateFormat: ""
|
||||||
property string lockDateFormat: ""
|
property string lockDateFormat: ""
|
||||||
|
property bool greeterRememberLastSession: true
|
||||||
|
property bool greeterRememberLastUser: true
|
||||||
|
property bool greeterEnableFprint: false
|
||||||
|
property bool greeterEnableU2f: false
|
||||||
|
property string greeterWallpaperPath: ""
|
||||||
|
property bool greeterUse24HourClock: true
|
||||||
|
property bool greeterShowSeconds: false
|
||||||
|
property bool greeterPadHours12Hour: false
|
||||||
|
property string greeterLockDateFormat: ""
|
||||||
|
property string greeterFontFamily: ""
|
||||||
|
property string greeterWallpaperFillMode: ""
|
||||||
property int mediaSize: 1
|
property int mediaSize: 1
|
||||||
|
|
||||||
property string appLauncherViewMode: "list"
|
property string appLauncherViewMode: "list"
|
||||||
@@ -437,13 +448,14 @@ Singleton {
|
|||||||
property bool matugenTemplateGhostty: true
|
property bool matugenTemplateGhostty: true
|
||||||
property bool matugenTemplateKitty: true
|
property bool matugenTemplateKitty: true
|
||||||
property bool matugenTemplateFoot: true
|
property bool matugenTemplateFoot: true
|
||||||
property bool matugenTemplateNeovim: true
|
property bool matugenTemplateNeovim: false
|
||||||
property bool matugenTemplateAlacritty: true
|
property bool matugenTemplateAlacritty: true
|
||||||
property bool matugenTemplateWezterm: true
|
property bool matugenTemplateWezterm: true
|
||||||
property bool matugenTemplateDgop: true
|
property bool matugenTemplateDgop: true
|
||||||
property bool matugenTemplateKcolorscheme: true
|
property bool matugenTemplateKcolorscheme: true
|
||||||
property bool matugenTemplateVscode: true
|
property bool matugenTemplateVscode: true
|
||||||
property bool matugenTemplateEmacs: true
|
property bool matugenTemplateEmacs: true
|
||||||
|
property bool matugenTemplateZed: true
|
||||||
|
|
||||||
property bool showDock: false
|
property bool showDock: false
|
||||||
property bool dockAutoHide: false
|
property bool dockAutoHide: false
|
||||||
@@ -494,6 +506,23 @@ Singleton {
|
|||||||
property bool enableFprint: false
|
property bool enableFprint: false
|
||||||
property int maxFprintTries: 15
|
property int maxFprintTries: 15
|
||||||
property bool fprintdAvailable: false
|
property bool fprintdAvailable: false
|
||||||
|
property bool lockFingerprintCanEnable: false
|
||||||
|
property bool lockFingerprintReady: false
|
||||||
|
property string lockFingerprintReason: "probe_failed"
|
||||||
|
property bool greeterFingerprintCanEnable: false
|
||||||
|
property bool greeterFingerprintReady: false
|
||||||
|
property string greeterFingerprintReason: "probe_failed"
|
||||||
|
property string greeterFingerprintSource: "none"
|
||||||
|
property bool enableU2f: false
|
||||||
|
property string u2fMode: "or"
|
||||||
|
property bool u2fAvailable: false
|
||||||
|
property bool lockU2fCanEnable: false
|
||||||
|
property bool lockU2fReady: false
|
||||||
|
property string lockU2fReason: "probe_failed"
|
||||||
|
property bool greeterU2fCanEnable: false
|
||||||
|
property bool greeterU2fReady: false
|
||||||
|
property string greeterU2fReason: "probe_failed"
|
||||||
|
property string greeterU2fSource: "none"
|
||||||
property string lockScreenActiveMonitor: "all"
|
property string lockScreenActiveMonitor: "all"
|
||||||
property string lockScreenInactiveColor: "#000000"
|
property string lockScreenInactiveColor: "#000000"
|
||||||
property int lockScreenNotificationMode: 0
|
property int lockScreenNotificationMode: 0
|
||||||
@@ -518,7 +547,7 @@ Singleton {
|
|||||||
property int osdPosition: SettingsData.Position.BottomCenter
|
property int osdPosition: SettingsData.Position.BottomCenter
|
||||||
property bool osdVolumeEnabled: true
|
property bool osdVolumeEnabled: true
|
||||||
property bool osdMediaVolumeEnabled: true
|
property bool osdMediaVolumeEnabled: true
|
||||||
property bool osdMediaPlaybackEnabled: true
|
property bool osdMediaPlaybackEnabled: false
|
||||||
property bool osdBrightnessEnabled: true
|
property bool osdBrightnessEnabled: true
|
||||||
property bool osdIdleInhibitorEnabled: true
|
property bool osdIdleInhibitorEnabled: true
|
||||||
property bool osdMicMuteEnabled: true
|
property bool osdMicMuteEnabled: true
|
||||||
@@ -572,6 +601,10 @@ Singleton {
|
|||||||
"widgetTransparency": 1.0,
|
"widgetTransparency": 1.0,
|
||||||
"squareCorners": false,
|
"squareCorners": false,
|
||||||
"noBackground": false,
|
"noBackground": false,
|
||||||
|
"maximizeWidgetIcons": false,
|
||||||
|
"maximizeWidgetText": false,
|
||||||
|
"removeWidgetPadding": false,
|
||||||
|
"widgetPadding": 8,
|
||||||
"gothCornersEnabled": false,
|
"gothCornersEnabled": false,
|
||||||
"gothCornerRadiusOverride": false,
|
"gothCornerRadiusOverride": false,
|
||||||
"gothCornerRadiusValue": 12,
|
"gothCornerRadiusValue": 12,
|
||||||
@@ -584,6 +617,7 @@ Singleton {
|
|||||||
"widgetOutlineOpacity": 1.0,
|
"widgetOutlineOpacity": 1.0,
|
||||||
"widgetOutlineThickness": 1,
|
"widgetOutlineThickness": 1,
|
||||||
"fontScale": 1.0,
|
"fontScale": 1.0,
|
||||||
|
"iconScale": 1.0,
|
||||||
"autoHide": false,
|
"autoHide": false,
|
||||||
"autoHideDelay": 250,
|
"autoHideDelay": 250,
|
||||||
"showOnWindowsOpen": false,
|
"showOnWindowsOpen": false,
|
||||||
@@ -971,12 +1005,19 @@ Singleton {
|
|||||||
signal widgetDataChanged
|
signal widgetDataChanged
|
||||||
signal workspaceIconsUpdated
|
signal workspaceIconsUpdated
|
||||||
|
|
||||||
|
function refreshAuthAvailability() {
|
||||||
|
if (isGreeterMode)
|
||||||
|
return;
|
||||||
|
Processes.settingsRoot = root;
|
||||||
|
Processes.detectAuthCapabilities();
|
||||||
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (!isGreeterMode) {
|
if (!isGreeterMode) {
|
||||||
Processes.settingsRoot = root;
|
Processes.settingsRoot = root;
|
||||||
loadSettings();
|
loadSettings();
|
||||||
initializeListModels();
|
initializeListModels();
|
||||||
Processes.detectFprintd();
|
refreshAuthAvailability();
|
||||||
Processes.checkPluginSettings();
|
Processes.checkPluginSettings();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1026,6 +1067,7 @@ Singleton {
|
|||||||
elif command -v dconf >/dev/null 2>&1; then
|
elif command -v dconf >/dev/null 2>&1; then
|
||||||
dconf read /org/gnome/desktop/interface/icon-theme 2>/dev/null | sed "s/'//g"
|
dconf read /org/gnome/desktop/interface/icon-theme 2>/dev/null | sed "s/'//g"
|
||||||
fi`;
|
fi`;
|
||||||
|
|
||||||
Proc.runCommand("detectCosmicIconTheme", ["sh", "-c", detectScript], (output, exitCode) => {
|
Proc.runCommand("detectCosmicIconTheme", ["sh", "-c", detectScript], (output, exitCode) => {
|
||||||
if (exitCode !== 0)
|
if (exitCode !== 0)
|
||||||
return;
|
return;
|
||||||
@@ -1215,10 +1257,47 @@ Singleton {
|
|||||||
return JSON.stringify(Store.toJson(root), null, 2);
|
return JSON.stringify(Store.toJson(root), null, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _resetPluginSettings() {
|
||||||
|
_pluginParseError = false;
|
||||||
|
pluginSettings = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function _pluginSettingsErrorCode(error) {
|
||||||
|
if (typeof error === "number")
|
||||||
|
return error;
|
||||||
|
if (error && typeof error === "object") {
|
||||||
|
if (typeof error.code === "number")
|
||||||
|
return error.code;
|
||||||
|
if (typeof error.errno === "number")
|
||||||
|
return error.errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
const msg = String(error || "").trim();
|
||||||
|
if (/^\d+$/.test(msg))
|
||||||
|
return Number(msg);
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _isMissingPluginSettingsError(error) {
|
||||||
|
if (_pluginSettingsErrorCode(error) === 2)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
const msg = String(error || "").toLowerCase();
|
||||||
|
return msg.indexOf("file does not exist") !== -1
|
||||||
|
|| msg.indexOf("no such file") !== -1
|
||||||
|
|| msg.indexOf("enoent") !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
function loadPluginSettings() {
|
function loadPluginSettings() {
|
||||||
_pluginSettingsLoading = true;
|
try {
|
||||||
parsePluginSettings(pluginSettingsFile.text());
|
parsePluginSettings(pluginSettingsFile.text());
|
||||||
_pluginSettingsLoading = false;
|
} 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) {
|
function parsePluginSettings(content) {
|
||||||
@@ -2654,6 +2733,7 @@ Singleton {
|
|||||||
blockLoading: true
|
blockLoading: true
|
||||||
blockWrites: true
|
blockWrites: true
|
||||||
atomicWrites: true
|
atomicWrites: true
|
||||||
|
printErrors: false
|
||||||
watchChanges: !isGreeterMode
|
watchChanges: !isGreeterMode
|
||||||
onLoaded: {
|
onLoaded: {
|
||||||
if (!isGreeterMode) {
|
if (!isGreeterMode) {
|
||||||
@@ -2662,7 +2742,10 @@ Singleton {
|
|||||||
}
|
}
|
||||||
onLoadFailed: error => {
|
onLoadFailed: error => {
|
||||||
if (!isGreeterMode) {
|
if (!isGreeterMode) {
|
||||||
pluginSettings = {};
|
const msg = String(error || "");
|
||||||
|
if (!_isMissingPluginSettingsError(error))
|
||||||
|
console.warn("SettingsData: Failed to load plugin_settings.json. Error:", msg);
|
||||||
|
_resetPluginSettings();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -858,7 +858,7 @@ Singleton {
|
|||||||
|
|
||||||
property string fontFamily: {
|
property string fontFamily: {
|
||||||
if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") {
|
if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") {
|
||||||
return GreetdSettings.fontFamily;
|
return GreetdSettings.getEffectiveFontFamily();
|
||||||
}
|
}
|
||||||
return typeof SettingsData !== "undefined" ? SettingsData.fontFamily : "Inter Variable";
|
return typeof SettingsData !== "undefined" ? SettingsData.fontFamily : "Inter Variable";
|
||||||
}
|
}
|
||||||
@@ -961,7 +961,6 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isGreeterMode) {
|
if (!isGreeterMode) {
|
||||||
// Skip with matugen because, our script runner will do it.
|
|
||||||
if (!matugenAvailable) {
|
if (!matugenAvailable) {
|
||||||
PortalService.setLightMode(light);
|
PortalService.setLightMode(light);
|
||||||
}
|
}
|
||||||
@@ -1023,7 +1022,11 @@ Singleton {
|
|||||||
if (themeData.variants.type === "multi" && themeData.variants.flavors && themeData.variants.accents) {
|
if (themeData.variants.type === "multi" && themeData.variants.flavors && themeData.variants.accents) {
|
||||||
const defaults = themeData.variants.defaults || {};
|
const defaults = themeData.variants.defaults || {};
|
||||||
const modeDefaults = defaults[colorMode] || defaults.dark || {};
|
const modeDefaults = defaults[colorMode] || defaults.dark || {};
|
||||||
const stored = typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeMultiVariant(themeId, modeDefaults, colorMode) : modeDefaults;
|
const isGreeterMode = typeof SessionData !== "undefined" && SessionData.isGreeterMode;
|
||||||
|
const stored = isGreeterMode ?
|
||||||
|
(GreetdSettings.registryThemeVariants[themeId]?.[colorMode] || modeDefaults) :
|
||||||
|
(typeof SettingsData !== "undefined" ?
|
||||||
|
SettingsData.getRegistryThemeMultiVariant(themeId, modeDefaults, colorMode) : modeDefaults);
|
||||||
var flavorId = stored.flavor || modeDefaults.flavor || "";
|
var flavorId = stored.flavor || modeDefaults.flavor || "";
|
||||||
const accentId = stored.accent || modeDefaults.accent || "";
|
const accentId = stored.accent || modeDefaults.accent || "";
|
||||||
var flavor = findVariant(themeData.variants.flavors, flavorId);
|
var flavor = findVariant(themeData.variants.flavors, flavorId);
|
||||||
@@ -1049,7 +1052,10 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (themeData.variants.options && themeData.variants.options.length > 0) {
|
if (themeData.variants.options && themeData.variants.options.length > 0) {
|
||||||
const selectedVariantId = typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeVariant(themeId, themeData.variants.default) : themeData.variants.default;
|
const isGreeterMode = typeof SessionData !== "undefined" && SessionData.isGreeterMode;
|
||||||
|
const selectedVariantId = isGreeterMode
|
||||||
|
? (typeof GreetdSettings.registryThemeVariants[themeId] === "string" ? GreetdSettings.registryThemeVariants[themeId] : themeData.variants.default)
|
||||||
|
: (typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeVariant(themeId, themeData.variants.default) : themeData.variants.default);
|
||||||
const variant = findVariant(themeData.variants.options, selectedVariantId);
|
const variant = findVariant(themeData.variants.options, selectedVariantId);
|
||||||
if (variant) {
|
if (variant) {
|
||||||
const variantColors = variant[colorMode] || variant.dark || variant.light || {};
|
const variantColors = variant[colorMode] || variant.dark || variant.light || {};
|
||||||
@@ -1170,21 +1176,23 @@ Singleton {
|
|||||||
return (0.299 * c.r + 0.587 * c.g + 0.114 * c.b) < 0.5;
|
return (0.299 * c.r + 0.587 * c.g + 0.114 * c.b) < 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
function barIconSize(barThickness, offset, noBackground) {
|
function barIconSize(barThickness, offset, maximizeIcon, iconScale) {
|
||||||
const defaultOffset = offset !== undefined ? offset : -6;
|
const defaultOffset = offset !== undefined ? offset : -6;
|
||||||
const size = (noBackground ?? false) ? iconSizeLarge : iconSize;
|
const size = (maximizeIcon ?? false) ? iconSizeLarge : iconSize;
|
||||||
|
const s = iconScale !== undefined ? iconScale : 1.0;
|
||||||
|
|
||||||
return Math.round((barThickness / 48) * (size + defaultOffset));
|
return Math.round((barThickness / 48) * (size + defaultOffset) * s);
|
||||||
}
|
}
|
||||||
|
|
||||||
function barTextSize(barThickness, fontScale) {
|
function barTextSize(barThickness, fontScale, maximizeText) {
|
||||||
const scale = barThickness / 48;
|
const scale = barThickness / 48;
|
||||||
const dankBarScale = fontScale !== undefined ? fontScale : 1.0;
|
const dankBarScale = fontScale !== undefined ? fontScale : 1.0;
|
||||||
|
const maxScale = (maximizeText ?? false) ? 1.5 : 1.0;
|
||||||
if (scale <= 0.75)
|
if (scale <= 0.75)
|
||||||
return Math.round(fontSizeSmall * 0.9 * dankBarScale);
|
return Math.round(fontSizeSmall * 0.9 * dankBarScale * maxScale);
|
||||||
if (scale >= 1.25)
|
if (scale >= 1.25)
|
||||||
return Math.round(fontSizeMedium * dankBarScale);
|
return Math.round(fontSizeMedium * dankBarScale * maxScale);
|
||||||
return Math.round(fontSizeSmall * dankBarScale);
|
return Math.round(fontSizeSmall * dankBarScale * maxScale);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBatteryIcon(level, isCharging, batteryAvailable) {
|
function getBatteryIcon(level, isCharging, batteryAvailable) {
|
||||||
@@ -1324,7 +1332,7 @@ Singleton {
|
|||||||
if (typeof SettingsData !== "undefined") {
|
if (typeof SettingsData !== "undefined") {
|
||||||
const skipTemplates = [];
|
const skipTemplates = [];
|
||||||
if (!SettingsData.runDmsMatugenTemplates) {
|
if (!SettingsData.runDmsMatugenTemplates) {
|
||||||
skipTemplates.push("gtk", "nvim", "niri", "qt5ct", "qt6ct", "firefox", "pywalfox", "zenbrowser", "vesktop", "equibop", "ghostty", "kitty", "foot", "alacritty", "wezterm", "dgop", "kcolorscheme", "vscode", "emacs");
|
skipTemplates.push("gtk", "nvim", "niri", "qt5ct", "qt6ct", "firefox", "pywalfox", "zenbrowser", "vesktop", "equibop", "ghostty", "kitty", "foot", "alacritty", "wezterm", "dgop", "kcolorscheme", "vscode", "emacs", "zed");
|
||||||
} else {
|
} else {
|
||||||
if (!SettingsData.matugenTemplateGtk)
|
if (!SettingsData.matugenTemplateGtk)
|
||||||
skipTemplates.push("gtk");
|
skipTemplates.push("gtk");
|
||||||
@@ -1368,6 +1376,8 @@ Singleton {
|
|||||||
skipTemplates.push("vscode");
|
skipTemplates.push("vscode");
|
||||||
if (!SettingsData.matugenTemplateEmacs)
|
if (!SettingsData.matugenTemplateEmacs)
|
||||||
skipTemplates.push("emacs");
|
skipTemplates.push("emacs");
|
||||||
|
if (!SettingsData.matugenTemplateZed)
|
||||||
|
skipTemplates.push("zed");
|
||||||
}
|
}
|
||||||
if (skipTemplates.length > 0) {
|
if (skipTemplates.length > 0) {
|
||||||
args.push("--skip-templates", skipTemplates.join(","));
|
args.push("--skip-templates", skipTemplates.join(","));
|
||||||
@@ -1417,8 +1427,13 @@ Singleton {
|
|||||||
const defaults = customThemeRawData.variants.defaults || {};
|
const defaults = customThemeRawData.variants.defaults || {};
|
||||||
const darkDefaults = defaults.dark || {};
|
const darkDefaults = defaults.dark || {};
|
||||||
const lightDefaults = defaults.light || defaults.dark || {};
|
const lightDefaults = defaults.light || defaults.dark || {};
|
||||||
const storedDark = typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeMultiVariant(themeId, darkDefaults, "dark") : darkDefaults;
|
const isGreeterMode = typeof SessionData !== "undefined" && SessionData.isGreeterMode;
|
||||||
const storedLight = typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeMultiVariant(themeId, lightDefaults, "light") : lightDefaults;
|
const storedDark = isGreeterMode
|
||||||
|
? (GreetdSettings.registryThemeVariants[themeId]?.dark || darkDefaults)
|
||||||
|
: (typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeMultiVariant(themeId, darkDefaults, "dark") : darkDefaults);
|
||||||
|
const storedLight = isGreeterMode
|
||||||
|
? (GreetdSettings.registryThemeVariants[themeId]?.light || lightDefaults)
|
||||||
|
: (typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeMultiVariant(themeId, lightDefaults, "light") : lightDefaults);
|
||||||
const darkFlavorId = storedDark.flavor || darkDefaults.flavor || "";
|
const darkFlavorId = storedDark.flavor || darkDefaults.flavor || "";
|
||||||
const lightFlavorId = storedLight.flavor || lightDefaults.flavor || "";
|
const lightFlavorId = storedLight.flavor || lightDefaults.flavor || "";
|
||||||
const accentId = storedDark.accent || darkDefaults.accent || "";
|
const accentId = storedDark.accent || darkDefaults.accent || "";
|
||||||
@@ -1436,7 +1451,10 @@ Singleton {
|
|||||||
lightTheme = mergeColors(lightTheme, accent[lightFlavor.id] || {});
|
lightTheme = mergeColors(lightTheme, accent[lightFlavor.id] || {});
|
||||||
}
|
}
|
||||||
} else if (customThemeRawData.variants.options) {
|
} else if (customThemeRawData.variants.options) {
|
||||||
const selectedVariantId = typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeVariant(themeId, customThemeRawData.variants.default) : customThemeRawData.variants.default;
|
const isGreeterMode = typeof SessionData !== "undefined" && SessionData.isGreeterMode;
|
||||||
|
const selectedVariantId = isGreeterMode
|
||||||
|
? (typeof GreetdSettings.registryThemeVariants[themeId] === "string" ? GreetdSettings.registryThemeVariants[themeId] : customThemeRawData.variants.default)
|
||||||
|
: (typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeVariant(themeId, customThemeRawData.variants.default) : customThemeRawData.variants.default);
|
||||||
const variant = findVariant(customThemeRawData.variants.options, selectedVariantId);
|
const variant = findVariant(customThemeRawData.variants.options, selectedVariantId);
|
||||||
if (variant) {
|
if (variant) {
|
||||||
darkTheme = mergeColors(darkTheme, variant.dark || {});
|
darkTheme = mergeColors(darkTheme, variant.dark || {});
|
||||||
@@ -1760,10 +1778,11 @@ Singleton {
|
|||||||
FileView {
|
FileView {
|
||||||
id: dynamicColorsFileView
|
id: dynamicColorsFileView
|
||||||
path: {
|
path: {
|
||||||
const greetCfgDir = Quickshell.env("DMS_GREET_CFG_DIR") || "/etc/greetd/.dms";
|
const greetCfgDir = Quickshell.env("DMS_GREET_CFG_DIR") || "/var/cache/dms-greeter";
|
||||||
const colorsPath = SessionData.isGreeterMode ? greetCfgDir + "/colors.json" : stateDir + "/dms-colors.json";
|
const colorsPath = SessionData.isGreeterMode ? greetCfgDir + "/colors.json" : stateDir + "/dms-colors.json";
|
||||||
return colorsPath;
|
return colorsPath;
|
||||||
}
|
}
|
||||||
|
blockLoading: false
|
||||||
watchChanges: !SessionData.isGreeterMode
|
watchChanges: !SessionData.isGreeterMode
|
||||||
|
|
||||||
function parseAndLoadColors() {
|
function parseAndLoadColors() {
|
||||||
|
|||||||
@@ -10,18 +10,352 @@ Singleton {
|
|||||||
|
|
||||||
property var settingsRoot: null
|
property var settingsRoot: null
|
||||||
|
|
||||||
|
property string greetdPamText: ""
|
||||||
|
property string systemAuthPamText: ""
|
||||||
|
property string commonAuthPamText: ""
|
||||||
|
property string passwordAuthPamText: ""
|
||||||
|
property string systemLoginPamText: ""
|
||||||
|
property string systemLocalLoginPamText: ""
|
||||||
|
property string commonAuthPcPamText: ""
|
||||||
|
property string loginPamText: ""
|
||||||
|
property string dankshellU2fPamText: ""
|
||||||
|
property string u2fKeysText: ""
|
||||||
|
|
||||||
|
property string fingerprintProbeOutput: ""
|
||||||
|
property int fingerprintProbeExitCode: 0
|
||||||
|
property bool fingerprintProbeStreamFinished: false
|
||||||
|
property bool fingerprintProbeExited: false
|
||||||
|
property string fingerprintProbeState: "probe_failed"
|
||||||
|
|
||||||
|
property string pamSupportProbeOutput: ""
|
||||||
|
property bool pamSupportProbeStreamFinished: false
|
||||||
|
property bool pamSupportProbeExited: false
|
||||||
|
property int pamSupportProbeExitCode: 0
|
||||||
|
property bool pamFprintSupportDetected: false
|
||||||
|
property bool pamU2fSupportDetected: false
|
||||||
|
|
||||||
|
readonly property string homeDir: Quickshell.env("HOME") || ""
|
||||||
|
readonly property string u2fKeysPath: homeDir ? homeDir + "/.config/Yubico/u2f_keys" : ""
|
||||||
|
readonly property bool homeU2fKeysDetected: u2fKeysPath !== "" && u2fKeysWatcher.loaded && u2fKeysText.trim() !== ""
|
||||||
|
readonly property bool lockU2fCustomConfigDetected: pamModuleEnabled(dankshellU2fPamText, "pam_u2f")
|
||||||
|
readonly property bool greeterPamHasFprint: greeterPamStackHasModule("pam_fprintd")
|
||||||
|
readonly property bool greeterPamHasU2f: greeterPamStackHasModule("pam_u2f")
|
||||||
|
|
||||||
|
function envFlag(name) {
|
||||||
|
const value = (Quickshell.env(name) || "").trim().toLowerCase();
|
||||||
|
if (value === "1" || value === "true" || value === "yes" || value === "on")
|
||||||
|
return true;
|
||||||
|
if (value === "0" || value === "false" || value === "no" || value === "off")
|
||||||
|
return false;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property var forcedFprintAvailable: envFlag("DMS_FORCE_FPRINT_AVAILABLE")
|
||||||
|
readonly property var forcedU2fAvailable: envFlag("DMS_FORCE_U2F_AVAILABLE")
|
||||||
|
|
||||||
function detectQtTools() {
|
function detectQtTools() {
|
||||||
qtToolsDetectionProcess.running = true;
|
qtToolsDetectionProcess.running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function detectAuthCapabilities() {
|
||||||
|
if (!settingsRoot)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (forcedFprintAvailable === null) {
|
||||||
|
fingerprintProbeOutput = "";
|
||||||
|
fingerprintProbeStreamFinished = false;
|
||||||
|
fingerprintProbeExited = false;
|
||||||
|
fingerprintProbeProcess.running = true;
|
||||||
|
} else {
|
||||||
|
fingerprintProbeState = forcedFprintAvailable ? "ready" : "probe_failed";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (forcedFprintAvailable === null || forcedU2fAvailable === null) {
|
||||||
|
pamFprintSupportDetected = false;
|
||||||
|
pamU2fSupportDetected = false;
|
||||||
|
pamSupportProbeOutput = "";
|
||||||
|
pamSupportProbeStreamFinished = false;
|
||||||
|
pamSupportProbeExited = false;
|
||||||
|
pamSupportDetectionProcess.running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
recomputeAuthCapabilities();
|
||||||
|
}
|
||||||
|
|
||||||
function detectFprintd() {
|
function detectFprintd() {
|
||||||
fprintdDetectionProcess.running = true;
|
detectAuthCapabilities();
|
||||||
|
}
|
||||||
|
|
||||||
|
function detectU2f() {
|
||||||
|
detectAuthCapabilities();
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkPluginSettings() {
|
function checkPluginSettings() {
|
||||||
pluginSettingsCheckProcess.running = true;
|
pluginSettingsCheckProcess.running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function stripPamComment(line) {
|
||||||
|
if (!line)
|
||||||
|
return "";
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (!trimmed || trimmed.startsWith("#"))
|
||||||
|
return "";
|
||||||
|
const hashIdx = trimmed.indexOf("#");
|
||||||
|
if (hashIdx >= 0)
|
||||||
|
return trimmed.substring(0, hashIdx).trim();
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pamModuleEnabled(pamText, moduleName) {
|
||||||
|
if (!pamText || !moduleName)
|
||||||
|
return false;
|
||||||
|
const lines = pamText.split(/\r?\n/);
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const line = stripPamComment(lines[i]);
|
||||||
|
if (!line)
|
||||||
|
continue;
|
||||||
|
if (line.includes(moduleName))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pamTextIncludesFile(pamText, filename) {
|
||||||
|
if (!pamText || !filename)
|
||||||
|
return false;
|
||||||
|
const lines = pamText.split(/\r?\n/);
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const line = stripPamComment(lines[i]);
|
||||||
|
if (!line)
|
||||||
|
continue;
|
||||||
|
if (line.includes(filename) && (line.includes("include") || line.includes("substack") || line.startsWith("@include")))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function greeterPamStackHasModule(moduleName) {
|
||||||
|
if (pamModuleEnabled(greetdPamText, moduleName))
|
||||||
|
return true;
|
||||||
|
const includedPamStacks = [
|
||||||
|
["system-auth", systemAuthPamText],
|
||||||
|
["common-auth", commonAuthPamText],
|
||||||
|
["password-auth", passwordAuthPamText],
|
||||||
|
["system-login", systemLoginPamText],
|
||||||
|
["system-local-login", systemLocalLoginPamText],
|
||||||
|
["common-auth-pc", commonAuthPcPamText],
|
||||||
|
["login", loginPamText]
|
||||||
|
];
|
||||||
|
for (let i = 0; i < includedPamStacks.length; i++) {
|
||||||
|
const stack = includedPamStacks[i];
|
||||||
|
if (pamTextIncludesFile(greetdPamText, stack[0]) && pamModuleEnabled(stack[1], moduleName))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasEnrolledFingerprintOutput(output) {
|
||||||
|
const lower = (output || "").toLowerCase();
|
||||||
|
if (lower.includes("has fingers enrolled") || lower.includes("has fingerprints enrolled"))
|
||||||
|
return true;
|
||||||
|
const lines = lower.split(/\r?\n/);
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const trimmed = lines[i].trim();
|
||||||
|
if (trimmed.startsWith("finger:"))
|
||||||
|
return true;
|
||||||
|
if (trimmed.startsWith("- ") && trimmed.includes("finger"))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasMissingFingerprintEnrollmentOutput(output) {
|
||||||
|
const lower = (output || "").toLowerCase();
|
||||||
|
return lower.includes("no fingers enrolled")
|
||||||
|
|| lower.includes("no fingerprints enrolled")
|
||||||
|
|| lower.includes("no prints enrolled");
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasMissingFingerprintReaderOutput(output) {
|
||||||
|
const lower = (output || "").toLowerCase();
|
||||||
|
return lower.includes("no devices available")
|
||||||
|
|| lower.includes("no device available")
|
||||||
|
|| lower.includes("no devices found")
|
||||||
|
|| lower.includes("list_devices failed")
|
||||||
|
|| lower.includes("no device");
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseFingerprintProbe(exitCode, output) {
|
||||||
|
if (hasEnrolledFingerprintOutput(output))
|
||||||
|
return "ready";
|
||||||
|
if (hasMissingFingerprintEnrollmentOutput(output))
|
||||||
|
return "missing_enrollment";
|
||||||
|
if (hasMissingFingerprintReaderOutput(output))
|
||||||
|
return "missing_reader";
|
||||||
|
if (exitCode === 0)
|
||||||
|
return "missing_enrollment";
|
||||||
|
if (exitCode === 127 || (output || "").includes("__missing_command__"))
|
||||||
|
return "probe_failed";
|
||||||
|
return pamFprintSupportDetected ? "probe_failed" : "missing_pam_support";
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLockFingerprintCapability(canEnable, ready, reason) {
|
||||||
|
settingsRoot.lockFingerprintCanEnable = canEnable;
|
||||||
|
settingsRoot.lockFingerprintReady = ready;
|
||||||
|
settingsRoot.lockFingerprintReason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLockU2fCapability(canEnable, ready, reason) {
|
||||||
|
settingsRoot.lockU2fCanEnable = canEnable;
|
||||||
|
settingsRoot.lockU2fReady = ready;
|
||||||
|
settingsRoot.lockU2fReason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setGreeterFingerprintCapability(canEnable, ready, reason, source) {
|
||||||
|
settingsRoot.greeterFingerprintCanEnable = canEnable;
|
||||||
|
settingsRoot.greeterFingerprintReady = ready;
|
||||||
|
settingsRoot.greeterFingerprintReason = reason;
|
||||||
|
settingsRoot.greeterFingerprintSource = source;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setGreeterU2fCapability(canEnable, ready, reason, source) {
|
||||||
|
settingsRoot.greeterU2fCanEnable = canEnable;
|
||||||
|
settingsRoot.greeterU2fReady = ready;
|
||||||
|
settingsRoot.greeterU2fReason = reason;
|
||||||
|
settingsRoot.greeterU2fSource = source;
|
||||||
|
}
|
||||||
|
|
||||||
|
function recomputeFingerprintCapabilities() {
|
||||||
|
if (forcedFprintAvailable !== null) {
|
||||||
|
const reason = forcedFprintAvailable ? "ready" : "probe_failed";
|
||||||
|
const source = forcedFprintAvailable ? "dms" : "none";
|
||||||
|
setLockFingerprintCapability(forcedFprintAvailable, forcedFprintAvailable, reason);
|
||||||
|
setGreeterFingerprintCapability(forcedFprintAvailable, forcedFprintAvailable, reason, source);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = fingerprintProbeState;
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case "ready":
|
||||||
|
setLockFingerprintCapability(true, true, "ready");
|
||||||
|
break;
|
||||||
|
case "missing_enrollment":
|
||||||
|
setLockFingerprintCapability(true, false, "missing_enrollment");
|
||||||
|
break;
|
||||||
|
case "missing_reader":
|
||||||
|
setLockFingerprintCapability(false, false, "missing_reader");
|
||||||
|
break;
|
||||||
|
case "missing_pam_support":
|
||||||
|
setLockFingerprintCapability(false, false, "missing_pam_support");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
setLockFingerprintCapability(false, false, "probe_failed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (greeterPamHasFprint) {
|
||||||
|
switch (state) {
|
||||||
|
case "ready":
|
||||||
|
setGreeterFingerprintCapability(true, true, "configured_externally", "pam");
|
||||||
|
break;
|
||||||
|
case "missing_enrollment":
|
||||||
|
setGreeterFingerprintCapability(true, false, "missing_enrollment", "pam");
|
||||||
|
break;
|
||||||
|
case "missing_reader":
|
||||||
|
setGreeterFingerprintCapability(false, false, "missing_reader", "pam");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
setGreeterFingerprintCapability(true, false, "probe_failed", "pam");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case "ready":
|
||||||
|
setGreeterFingerprintCapability(true, true, "ready", "dms");
|
||||||
|
break;
|
||||||
|
case "missing_enrollment":
|
||||||
|
setGreeterFingerprintCapability(true, false, "missing_enrollment", "dms");
|
||||||
|
break;
|
||||||
|
case "missing_reader":
|
||||||
|
setGreeterFingerprintCapability(false, false, "missing_reader", "none");
|
||||||
|
break;
|
||||||
|
case "missing_pam_support":
|
||||||
|
setGreeterFingerprintCapability(false, false, "missing_pam_support", "none");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
setGreeterFingerprintCapability(false, false, "probe_failed", "none");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function recomputeU2fCapabilities() {
|
||||||
|
if (forcedU2fAvailable !== null) {
|
||||||
|
const reason = forcedU2fAvailable ? "ready" : "probe_failed";
|
||||||
|
const source = forcedU2fAvailable ? "dms" : "none";
|
||||||
|
setLockU2fCapability(forcedU2fAvailable, forcedU2fAvailable, reason);
|
||||||
|
setGreeterU2fCapability(forcedU2fAvailable, forcedU2fAvailable, reason, source);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lockReady = lockU2fCustomConfigDetected || homeU2fKeysDetected;
|
||||||
|
const lockCanEnable = lockReady || pamU2fSupportDetected;
|
||||||
|
const lockReason = lockReady ? "ready" : (lockCanEnable ? "missing_key_registration" : "missing_pam_support");
|
||||||
|
setLockU2fCapability(lockCanEnable, lockReady, lockReason);
|
||||||
|
|
||||||
|
if (greeterPamHasU2f) {
|
||||||
|
setGreeterU2fCapability(true, true, "configured_externally", "pam");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const greeterReady = homeU2fKeysDetected;
|
||||||
|
const greeterCanEnable = greeterReady || pamU2fSupportDetected;
|
||||||
|
const greeterReason = greeterReady ? "ready" : (greeterCanEnable ? "missing_key_registration" : "missing_pam_support");
|
||||||
|
setGreeterU2fCapability(greeterCanEnable, greeterReady, greeterReason, greeterCanEnable ? "dms" : "none");
|
||||||
|
}
|
||||||
|
|
||||||
|
function recomputeAuthCapabilities() {
|
||||||
|
if (!settingsRoot)
|
||||||
|
return;
|
||||||
|
recomputeFingerprintCapabilities();
|
||||||
|
recomputeU2fCapabilities();
|
||||||
|
settingsRoot.fprintdAvailable = settingsRoot.lockFingerprintReady || settingsRoot.greeterFingerprintReady;
|
||||||
|
settingsRoot.u2fAvailable = settingsRoot.lockU2fReady || settingsRoot.greeterU2fReady;
|
||||||
|
}
|
||||||
|
|
||||||
|
function finalizeFingerprintProbe() {
|
||||||
|
if (!fingerprintProbeStreamFinished || !fingerprintProbeExited)
|
||||||
|
return;
|
||||||
|
fingerprintProbeState = parseFingerprintProbe(fingerprintProbeExitCode, fingerprintProbeOutput);
|
||||||
|
recomputeAuthCapabilities();
|
||||||
|
}
|
||||||
|
|
||||||
|
function finalizePamSupportProbe() {
|
||||||
|
if (!pamSupportProbeStreamFinished || !pamSupportProbeExited)
|
||||||
|
return;
|
||||||
|
|
||||||
|
pamFprintSupportDetected = false;
|
||||||
|
pamU2fSupportDetected = false;
|
||||||
|
|
||||||
|
const lines = (pamSupportProbeOutput || "").trim().split(/\r?\n/);
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const parts = lines[i].split(":");
|
||||||
|
if (parts.length !== 2)
|
||||||
|
continue;
|
||||||
|
if (parts[0] === "pam_fprintd.so")
|
||||||
|
pamFprintSupportDetected = parts[1] === "true";
|
||||||
|
else if (parts[0] === "pam_u2f.so")
|
||||||
|
pamU2fSupportDetected = parts[1] === "true";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (forcedFprintAvailable === null && fingerprintProbeState === "missing_pam_support")
|
||||||
|
fingerprintProbeState = parseFingerprintProbe(fingerprintProbeExitCode, fingerprintProbeOutput);
|
||||||
|
|
||||||
|
recomputeAuthCapabilities();
|
||||||
|
}
|
||||||
|
|
||||||
property var qtToolsDetectionProcess: Process {
|
property var qtToolsDetectionProcess: Process {
|
||||||
command: ["sh", "-c", "echo -n 'qt5ct:'; command -v qt5ct >/dev/null && echo 'true' || echo 'false'; echo -n 'qt6ct:'; command -v qt6ct >/dev/null && echo 'true' || echo 'false'; echo -n 'gtk:'; (command -v gsettings >/dev/null || command -v dconf >/dev/null) && echo 'true' || echo 'false'"]
|
command: ["sh", "-c", "echo -n 'qt5ct:'; command -v qt5ct >/dev/null && echo 'true' || echo 'false'; echo -n 'qt6ct:'; command -v qt6ct >/dev/null && echo 'true' || echo 'false'; echo -n 'gtk:'; (command -v gsettings >/dev/null || command -v dconf >/dev/null) && echo 'true' || echo 'false'"]
|
||||||
running: false
|
running: false
|
||||||
@@ -31,15 +365,15 @@ Singleton {
|
|||||||
if (!settingsRoot)
|
if (!settingsRoot)
|
||||||
return;
|
return;
|
||||||
if (text && text.trim()) {
|
if (text && text.trim()) {
|
||||||
var lines = text.trim().split('\n');
|
const lines = text.trim().split("\n");
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
var line = lines[i];
|
const line = lines[i];
|
||||||
if (line.startsWith('qt5ct:')) {
|
if (line.startsWith("qt5ct:")) {
|
||||||
settingsRoot.qt5ctAvailable = line.split(':')[1] === 'true';
|
settingsRoot.qt5ctAvailable = line.split(":")[1] === "true";
|
||||||
} else if (line.startsWith('qt6ct:')) {
|
} else if (line.startsWith("qt6ct:")) {
|
||||||
settingsRoot.qt6ctAvailable = line.split(':')[1] === 'true';
|
settingsRoot.qt6ctAvailable = line.split(":")[1] === "true";
|
||||||
} else if (line.startsWith('gtk:')) {
|
} else if (line.startsWith("gtk:")) {
|
||||||
settingsRoot.gtkAvailable = line.split(':')[1] === 'true';
|
settingsRoot.gtkAvailable = line.split(":")[1] === "true";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,13 +381,181 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
property var fprintdDetectionProcess: Process {
|
property var fingerprintProbeProcess: Process {
|
||||||
command: ["sh", "-c", "command -v fprintd-list >/dev/null 2>&1"]
|
command: ["sh", "-c", "if command -v fprintd-list >/dev/null 2>&1; then fprintd-list \"${USER:-$(id -un)}\" 2>&1; else printf '__missing_command__\\n'; exit 127; fi"]
|
||||||
running: false
|
running: false
|
||||||
|
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
root.fingerprintProbeOutput = text || "";
|
||||||
|
root.fingerprintProbeStreamFinished = true;
|
||||||
|
root.finalizeFingerprintProbe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onExited: function (exitCode) {
|
onExited: function (exitCode) {
|
||||||
if (!settingsRoot)
|
root.fingerprintProbeExitCode = exitCode;
|
||||||
return;
|
root.fingerprintProbeExited = true;
|
||||||
settingsRoot.fprintdAvailable = (exitCode === 0);
|
root.finalizeFingerprintProbe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property var pamSupportDetectionProcess: Process {
|
||||||
|
command: ["sh", "-c", "for module in pam_fprintd.so pam_u2f.so; do found=false; for dir in /usr/lib64/security /usr/lib/security /lib/security /lib/x86_64-linux-gnu/security /usr/lib/x86_64-linux-gnu/security /usr/lib/aarch64-linux-gnu/security /run/current-system/sw/lib/security; do if [ -f \"$dir/$module\" ]; then found=true; break; fi; done; printf '%s:%s\\n' \"$module\" \"$found\"; done"]
|
||||||
|
running: false
|
||||||
|
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: {
|
||||||
|
root.pamSupportProbeOutput = text || "";
|
||||||
|
root.pamSupportProbeStreamFinished = true;
|
||||||
|
root.finalizePamSupportProbe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onExited: function (exitCode) {
|
||||||
|
root.pamSupportProbeExitCode = exitCode;
|
||||||
|
root.pamSupportProbeExited = true;
|
||||||
|
root.finalizePamSupportProbe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: greetdPamWatcher
|
||||||
|
path: "/etc/pam.d/greetd"
|
||||||
|
printErrors: false
|
||||||
|
onLoaded: {
|
||||||
|
root.greetdPamText = text();
|
||||||
|
root.recomputeAuthCapabilities();
|
||||||
|
}
|
||||||
|
onLoadFailed: {
|
||||||
|
root.greetdPamText = "";
|
||||||
|
root.recomputeAuthCapabilities();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: systemAuthPamWatcher
|
||||||
|
path: "/etc/pam.d/system-auth"
|
||||||
|
printErrors: false
|
||||||
|
onLoaded: {
|
||||||
|
root.systemAuthPamText = text();
|
||||||
|
root.recomputeAuthCapabilities();
|
||||||
|
}
|
||||||
|
onLoadFailed: {
|
||||||
|
root.systemAuthPamText = "";
|
||||||
|
root.recomputeAuthCapabilities();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: commonAuthPamWatcher
|
||||||
|
path: "/etc/pam.d/common-auth"
|
||||||
|
printErrors: false
|
||||||
|
onLoaded: {
|
||||||
|
root.commonAuthPamText = text();
|
||||||
|
root.recomputeAuthCapabilities();
|
||||||
|
}
|
||||||
|
onLoadFailed: {
|
||||||
|
root.commonAuthPamText = "";
|
||||||
|
root.recomputeAuthCapabilities();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: passwordAuthPamWatcher
|
||||||
|
path: "/etc/pam.d/password-auth"
|
||||||
|
printErrors: false
|
||||||
|
onLoaded: {
|
||||||
|
root.passwordAuthPamText = text();
|
||||||
|
root.recomputeAuthCapabilities();
|
||||||
|
}
|
||||||
|
onLoadFailed: {
|
||||||
|
root.passwordAuthPamText = "";
|
||||||
|
root.recomputeAuthCapabilities();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: systemLoginPamWatcher
|
||||||
|
path: "/etc/pam.d/system-login"
|
||||||
|
printErrors: false
|
||||||
|
onLoaded: {
|
||||||
|
root.systemLoginPamText = text();
|
||||||
|
root.recomputeAuthCapabilities();
|
||||||
|
}
|
||||||
|
onLoadFailed: {
|
||||||
|
root.systemLoginPamText = "";
|
||||||
|
root.recomputeAuthCapabilities();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: systemLocalLoginPamWatcher
|
||||||
|
path: "/etc/pam.d/system-local-login"
|
||||||
|
printErrors: false
|
||||||
|
onLoaded: {
|
||||||
|
root.systemLocalLoginPamText = text();
|
||||||
|
root.recomputeAuthCapabilities();
|
||||||
|
}
|
||||||
|
onLoadFailed: {
|
||||||
|
root.systemLocalLoginPamText = "";
|
||||||
|
root.recomputeAuthCapabilities();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: commonAuthPcPamWatcher
|
||||||
|
path: "/etc/pam.d/common-auth-pc"
|
||||||
|
printErrors: false
|
||||||
|
onLoaded: {
|
||||||
|
root.commonAuthPcPamText = text();
|
||||||
|
root.recomputeAuthCapabilities();
|
||||||
|
}
|
||||||
|
onLoadFailed: {
|
||||||
|
root.commonAuthPcPamText = "";
|
||||||
|
root.recomputeAuthCapabilities();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: loginPamWatcher
|
||||||
|
path: "/etc/pam.d/login"
|
||||||
|
printErrors: false
|
||||||
|
onLoaded: {
|
||||||
|
root.loginPamText = text();
|
||||||
|
root.recomputeAuthCapabilities();
|
||||||
|
}
|
||||||
|
onLoadFailed: {
|
||||||
|
root.loginPamText = "";
|
||||||
|
root.recomputeAuthCapabilities();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: dankshellU2fPamWatcher
|
||||||
|
path: "/etc/pam.d/dankshell-u2f"
|
||||||
|
printErrors: false
|
||||||
|
onLoaded: {
|
||||||
|
root.dankshellU2fPamText = text();
|
||||||
|
root.recomputeAuthCapabilities();
|
||||||
|
}
|
||||||
|
onLoadFailed: {
|
||||||
|
root.dankshellU2fPamText = "";
|
||||||
|
root.recomputeAuthCapabilities();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: u2fKeysWatcher
|
||||||
|
path: root.u2fKeysPath
|
||||||
|
printErrors: false
|
||||||
|
onLoaded: {
|
||||||
|
root.u2fKeysText = text();
|
||||||
|
root.recomputeAuthCapabilities();
|
||||||
|
}
|
||||||
|
onLoadFailed: {
|
||||||
|
root.u2fKeysText = "";
|
||||||
|
root.recomputeAuthCapabilities();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -77,7 +77,11 @@ var SPEC = {
|
|||||||
|
|
||||||
deviceMaxVolumes: { def: {} },
|
deviceMaxVolumes: { def: {} },
|
||||||
hiddenOutputDeviceNames: { def: [] },
|
hiddenOutputDeviceNames: { def: [] },
|
||||||
hiddenInputDeviceNames: { def: [] }
|
hiddenInputDeviceNames: { def: [] },
|
||||||
|
|
||||||
|
launcherLastMode: { def: "all" },
|
||||||
|
appDrawerLastMode: { def: "apps" },
|
||||||
|
niriOverviewLastMode: { def: "apps" }
|
||||||
};
|
};
|
||||||
|
|
||||||
function getValidKeys() {
|
function getValidKeys() {
|
||||||
|
|||||||
@@ -154,6 +154,17 @@ var SPEC = {
|
|||||||
centeringMode: { def: "index" },
|
centeringMode: { def: "index" },
|
||||||
clockDateFormat: { def: "" },
|
clockDateFormat: { def: "" },
|
||||||
lockDateFormat: { def: "" },
|
lockDateFormat: { def: "" },
|
||||||
|
greeterRememberLastSession: { def: true },
|
||||||
|
greeterRememberLastUser: { def: true },
|
||||||
|
greeterEnableFprint: { def: false },
|
||||||
|
greeterEnableU2f: { def: false },
|
||||||
|
greeterWallpaperPath: { def: "" },
|
||||||
|
greeterUse24HourClock: { def: true },
|
||||||
|
greeterShowSeconds: { def: false },
|
||||||
|
greeterPadHours12Hour: { def: false },
|
||||||
|
greeterLockDateFormat: { def: "" },
|
||||||
|
greeterFontFamily: { def: "" },
|
||||||
|
greeterWallpaperFillMode: { def: "" },
|
||||||
mediaSize: { def: 1 },
|
mediaSize: { def: 1 },
|
||||||
|
|
||||||
appLauncherViewMode: { def: "list" },
|
appLauncherViewMode: { def: "list" },
|
||||||
@@ -262,12 +273,13 @@ var SPEC = {
|
|||||||
matugenTemplateKitty: { def: true },
|
matugenTemplateKitty: { def: true },
|
||||||
matugenTemplateFoot: { def: true },
|
matugenTemplateFoot: { def: true },
|
||||||
matugenTemplateAlacritty: { def: true },
|
matugenTemplateAlacritty: { def: true },
|
||||||
matugenTemplateNeovim: { def: true },
|
matugenTemplateNeovim: { def: false },
|
||||||
matugenTemplateWezterm: { def: true },
|
matugenTemplateWezterm: { def: true },
|
||||||
matugenTemplateDgop: { def: true },
|
matugenTemplateDgop: { def: true },
|
||||||
matugenTemplateKcolorscheme: { def: true },
|
matugenTemplateKcolorscheme: { def: true },
|
||||||
matugenTemplateVscode: { def: true },
|
matugenTemplateVscode: { def: true },
|
||||||
matugenTemplateEmacs: { def: true },
|
matugenTemplateEmacs: { def: true },
|
||||||
|
matugenTemplateZed: { def: true },
|
||||||
|
|
||||||
showDock: { def: false },
|
showDock: { def: false },
|
||||||
dockAutoHide: { def: false },
|
dockAutoHide: { def: false },
|
||||||
@@ -317,6 +329,23 @@ var SPEC = {
|
|||||||
enableFprint: { def: false },
|
enableFprint: { def: false },
|
||||||
maxFprintTries: { def: 15 },
|
maxFprintTries: { def: 15 },
|
||||||
fprintdAvailable: { def: false, persist: false },
|
fprintdAvailable: { def: false, persist: false },
|
||||||
|
lockFingerprintCanEnable: { def: false, persist: false },
|
||||||
|
lockFingerprintReady: { def: false, persist: false },
|
||||||
|
lockFingerprintReason: { def: "probe_failed", persist: false },
|
||||||
|
greeterFingerprintCanEnable: { def: false, persist: false },
|
||||||
|
greeterFingerprintReady: { def: false, persist: false },
|
||||||
|
greeterFingerprintReason: { def: "probe_failed", persist: false },
|
||||||
|
greeterFingerprintSource: { def: "none", persist: false },
|
||||||
|
enableU2f: { def: false },
|
||||||
|
u2fMode: { def: "or" },
|
||||||
|
u2fAvailable: { def: false, persist: false },
|
||||||
|
lockU2fCanEnable: { def: false, persist: false },
|
||||||
|
lockU2fReady: { def: false, persist: false },
|
||||||
|
lockU2fReason: { def: "probe_failed", persist: false },
|
||||||
|
greeterU2fCanEnable: { def: false, persist: false },
|
||||||
|
greeterU2fReady: { def: false, persist: false },
|
||||||
|
greeterU2fReason: { def: "probe_failed", persist: false },
|
||||||
|
greeterU2fSource: { def: "none", persist: false },
|
||||||
lockScreenActiveMonitor: { def: "all" },
|
lockScreenActiveMonitor: { def: "all" },
|
||||||
lockScreenInactiveColor: { def: "#000000" },
|
lockScreenInactiveColor: { def: "#000000" },
|
||||||
lockScreenNotificationMode: { def: 0 },
|
lockScreenNotificationMode: { def: 0 },
|
||||||
@@ -341,7 +370,7 @@ var SPEC = {
|
|||||||
osdPosition: { def: 5 },
|
osdPosition: { def: 5 },
|
||||||
osdVolumeEnabled: { def: true },
|
osdVolumeEnabled: { def: true },
|
||||||
osdMediaVolumeEnabled: { def: true },
|
osdMediaVolumeEnabled: { def: true },
|
||||||
osdMediaPlaybackEnabled: { def: true },
|
osdMediaPlaybackEnabled: { def: false },
|
||||||
osdBrightnessEnabled: { def: true },
|
osdBrightnessEnabled: { def: true },
|
||||||
osdIdleInhibitorEnabled: { def: true },
|
osdIdleInhibitorEnabled: { def: true },
|
||||||
osdMicMuteEnabled: { def: true },
|
osdMicMuteEnabled: { def: true },
|
||||||
@@ -395,6 +424,10 @@ var SPEC = {
|
|||||||
widgetTransparency: 1.0,
|
widgetTransparency: 1.0,
|
||||||
squareCorners: false,
|
squareCorners: false,
|
||||||
noBackground: false,
|
noBackground: false,
|
||||||
|
maximizeWidgetIcons: false,
|
||||||
|
maximizeWidgetText: false,
|
||||||
|
removeWidgetPadding: false,
|
||||||
|
widgetPadding: 8,
|
||||||
gothCornersEnabled: false,
|
gothCornersEnabled: false,
|
||||||
gothCornerRadiusOverride: false,
|
gothCornerRadiusOverride: false,
|
||||||
gothCornerRadiusValue: 12,
|
gothCornerRadiusValue: 12,
|
||||||
@@ -407,6 +440,7 @@ var SPEC = {
|
|||||||
widgetOutlineOpacity: 1.0,
|
widgetOutlineOpacity: 1.0,
|
||||||
widgetOutlineThickness: 1,
|
widgetOutlineThickness: 1,
|
||||||
fontScale: 1.0,
|
fontScale: 1.0,
|
||||||
|
iconScale: 1.0,
|
||||||
autoHide: false,
|
autoHide: false,
|
||||||
autoHideDelay: 250,
|
autoHideDelay: 250,
|
||||||
showOnWindowsOpen: false,
|
showOnWindowsOpen: false,
|
||||||
|
|||||||
@@ -142,25 +142,45 @@ Item {
|
|||||||
fadeDpmsWindowLoader.item.cancelFade();
|
fadeDpmsWindowLoader.item.cancelFade();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onRequestMonitorOn() {
|
||||||
|
if (!fadeDpmsWindowLoader.item)
|
||||||
|
return;
|
||||||
|
fadeDpmsWindowLoader.item.cancelFade();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property string _barLayoutStateJson: {
|
||||||
|
const configs = SettingsData.barConfigs;
|
||||||
|
const mapped = configs.map((c, i) => ({
|
||||||
|
id: c.id,
|
||||||
|
position: c.position,
|
||||||
|
autoHide: c.autoHide,
|
||||||
|
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)
|
||||||
|
return aVertical - bVertical;
|
||||||
|
return a._origIndex - b._origIndex;
|
||||||
|
});
|
||||||
|
return JSON.stringify(mapped);
|
||||||
|
}
|
||||||
|
|
||||||
|
on_BarLayoutStateJsonChanged: {
|
||||||
|
if (typeof dockRecreateDebounce !== "undefined") {
|
||||||
|
dockRecreateDebounce.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
id: dankBarRepeater
|
id: dankBarRepeater
|
||||||
model: ScriptModel {
|
model: ScriptModel {
|
||||||
id: barRepeaterModel
|
id: barRepeaterModel
|
||||||
values: {
|
values: JSON.parse(root._barLayoutStateJson)
|
||||||
const configs = SettingsData.barConfigs;
|
|
||||||
return configs.map(c => ({
|
|
||||||
id: c.id,
|
|
||||||
position: c.position
|
|
||||||
})).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;
|
|
||||||
return aVertical - bVertical;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
property var hyprlandOverviewLoaderRef: hyprlandOverviewLoader
|
property var hyprlandOverviewLoaderRef: hyprlandOverviewLoader
|
||||||
@@ -207,13 +227,6 @@ Item {
|
|||||||
PolkitService.polkitAvailable;
|
PolkitService.polkitAvailable;
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: SettingsData
|
|
||||||
function onBarConfigsChanged() {
|
|
||||||
dockRecreateDebounce.restart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: dockLoader
|
id: dockLoader
|
||||||
active: root.dockEnabled
|
active: root.dockEnabled
|
||||||
@@ -265,6 +278,7 @@ Item {
|
|||||||
sourceComponent: Component {
|
sourceComponent: Component {
|
||||||
DankDashPopout {
|
DankDashPopout {
|
||||||
id: dankDashPopout
|
id: dankDashPopout
|
||||||
|
onPopoutClosed: PopoutService.unloadDankDash()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -284,8 +298,13 @@ Item {
|
|||||||
|
|
||||||
active: false
|
active: false
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
PopoutService.notificationCenterLoader = notificationCenterLoader;
|
||||||
|
}
|
||||||
|
|
||||||
NotificationCenterPopout {
|
NotificationCenterPopout {
|
||||||
id: notificationCenter
|
id: notificationCenter
|
||||||
|
onPopoutClosed: PopoutService.unloadNotificationCenter()
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
PopoutService.notificationCenterPopout = notificationCenter;
|
PopoutService.notificationCenterPopout = notificationCenter;
|
||||||
@@ -309,10 +328,15 @@ Item {
|
|||||||
property var modalRef: colorPickerModal
|
property var modalRef: colorPickerModal
|
||||||
property LazyLoader powerModalLoaderRef: powerMenuModalLoader
|
property LazyLoader powerModalLoaderRef: powerMenuModalLoader
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
PopoutService.controlCenterLoader = controlCenterLoader;
|
||||||
|
}
|
||||||
|
|
||||||
ControlCenterPopout {
|
ControlCenterPopout {
|
||||||
id: controlCenterPopout
|
id: controlCenterPopout
|
||||||
colorPickerModal: controlCenterLoader.modalRef
|
colorPickerModal: controlCenterLoader.modalRef
|
||||||
powerMenuModalLoader: controlCenterLoader.powerModalLoaderRef
|
powerMenuModalLoader: controlCenterLoader.powerModalLoaderRef
|
||||||
|
onPopoutClosed: PopoutService.unloadControlCenter()
|
||||||
|
|
||||||
onLockRequested: {
|
onLockRequested: {
|
||||||
lock.activate();
|
lock.activate();
|
||||||
@@ -420,8 +444,13 @@ Item {
|
|||||||
|
|
||||||
active: false
|
active: false
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
PopoutService.batteryPopoutLoader = batteryPopoutLoader;
|
||||||
|
}
|
||||||
|
|
||||||
BatteryPopout {
|
BatteryPopout {
|
||||||
id: batteryPopout
|
id: batteryPopout
|
||||||
|
onPopoutClosed: PopoutService.unloadBattery()
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
PopoutService.batteryPopout = batteryPopout;
|
PopoutService.batteryPopout = batteryPopout;
|
||||||
@@ -434,8 +463,13 @@ Item {
|
|||||||
|
|
||||||
active: false
|
active: false
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
PopoutService.layoutPopoutLoader = layoutPopoutLoader;
|
||||||
|
}
|
||||||
|
|
||||||
DWLLayoutPopout {
|
DWLLayoutPopout {
|
||||||
id: layoutPopout
|
id: layoutPopout
|
||||||
|
onPopoutClosed: PopoutService.unloadLayoutPopout()
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
PopoutService.layoutPopout = layoutPopout;
|
PopoutService.layoutPopout = layoutPopout;
|
||||||
@@ -448,8 +482,13 @@ Item {
|
|||||||
|
|
||||||
active: false
|
active: false
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
PopoutService.vpnPopoutLoader = vpnPopoutLoader;
|
||||||
|
}
|
||||||
|
|
||||||
VpnPopout {
|
VpnPopout {
|
||||||
id: vpnPopout
|
id: vpnPopout
|
||||||
|
onPopoutClosed: PopoutService.unloadVpn()
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
PopoutService.vpnPopout = vpnPopout;
|
PopoutService.vpnPopout = vpnPopout;
|
||||||
@@ -462,8 +501,13 @@ Item {
|
|||||||
|
|
||||||
active: false
|
active: false
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
PopoutService.processListPopoutLoader = processListPopoutLoader;
|
||||||
|
}
|
||||||
|
|
||||||
ProcessListPopout {
|
ProcessListPopout {
|
||||||
id: processListPopout
|
id: processListPopout
|
||||||
|
onPopoutClosed: PopoutService.unloadProcessListPopout()
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
PopoutService.processListPopout = processListPopout;
|
PopoutService.processListPopout = processListPopout;
|
||||||
@@ -506,8 +550,13 @@ Item {
|
|||||||
|
|
||||||
active: false
|
active: false
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
PopoutService.appDrawerLoader = appDrawerLoader;
|
||||||
|
}
|
||||||
|
|
||||||
AppDrawerPopout {
|
AppDrawerPopout {
|
||||||
id: appDrawerPopout
|
id: appDrawerPopout
|
||||||
|
onPopoutClosed: PopoutService.unloadAppDrawer()
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
PopoutService.appDrawerPopout = appDrawerPopout;
|
PopoutService.appDrawerPopout = appDrawerPopout;
|
||||||
@@ -539,8 +588,13 @@ Item {
|
|||||||
|
|
||||||
active: false
|
active: false
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
PopoutService.clipboardHistoryPopoutLoader = clipboardHistoryPopoutLoader;
|
||||||
|
}
|
||||||
|
|
||||||
ClipboardHistoryPopout {
|
ClipboardHistoryPopout {
|
||||||
id: clipboardHistoryPopout
|
id: clipboardHistoryPopout
|
||||||
|
onPopoutClosed: PopoutService.unloadClipboardHistoryPopout()
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
PopoutService.clipboardHistoryPopout = clipboardHistoryPopout;
|
PopoutService.clipboardHistoryPopout = clipboardHistoryPopout;
|
||||||
@@ -715,8 +769,13 @@ Item {
|
|||||||
|
|
||||||
active: false
|
active: false
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
PopoutService.systemUpdateLoader = systemUpdateLoader;
|
||||||
|
}
|
||||||
|
|
||||||
SystemUpdatePopout {
|
SystemUpdatePopout {
|
||||||
id: systemUpdatePopout
|
id: systemUpdatePopout
|
||||||
|
onPopoutClosed: PopoutService.unloadSystemUpdate()
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
PopoutService.systemUpdatePopout = systemUpdatePopout;
|
PopoutService.systemUpdatePopout = systemUpdatePopout;
|
||||||
@@ -739,9 +798,8 @@ Item {
|
|||||||
|
|
||||||
content: Component {
|
content: Component {
|
||||||
Notepad {
|
Notepad {
|
||||||
onHideRequested: {
|
slideout: notepadSlideout
|
||||||
notepadSlideout.hide();
|
onHideRequested: notepadSlideout.hide()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,11 +21,37 @@ Item {
|
|||||||
required property var workspaceRenameModalLoader
|
required property var workspaceRenameModalLoader
|
||||||
required property var windowRuleModalLoader
|
required property var windowRuleModalLoader
|
||||||
|
|
||||||
function getFirstBar() {
|
function getPreferredBar(refPropertyName) {
|
||||||
if (!root.dankBarRepeater || root.dankBarRepeater.count === 0)
|
if (!root.dankBarRepeater || root.dankBarRepeater.count === 0)
|
||||||
return null;
|
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 {
|
IpcHandler {
|
||||||
@@ -97,9 +123,9 @@ Item {
|
|||||||
|
|
||||||
IpcHandler {
|
IpcHandler {
|
||||||
function open(): string {
|
function open(): string {
|
||||||
const bar = root.getFirstBar();
|
const bar = root.getPreferredBar("controlCenterButtonRef");
|
||||||
if (bar) {
|
if (bar) {
|
||||||
bar.triggerControlCenterOnFocusedScreen();
|
bar.triggerControlCenter();
|
||||||
return "CONTROL_CENTER_OPEN_SUCCESS";
|
return "CONTROL_CENTER_OPEN_SUCCESS";
|
||||||
}
|
}
|
||||||
return "CONTROL_CENTER_OPEN_FAILED";
|
return "CONTROL_CENTER_OPEN_FAILED";
|
||||||
@@ -114,9 +140,14 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function toggle(): string {
|
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) {
|
if (bar) {
|
||||||
bar.triggerControlCenterOnFocusedScreen();
|
bar.triggerControlCenter();
|
||||||
return "CONTROL_CENTER_TOGGLE_SUCCESS";
|
return "CONTROL_CENTER_TOGGLE_SUCCESS";
|
||||||
}
|
}
|
||||||
return "CONTROL_CENTER_TOGGLE_FAILED";
|
return "CONTROL_CENTER_TOGGLE_FAILED";
|
||||||
@@ -131,8 +162,20 @@ Item {
|
|||||||
|
|
||||||
IpcHandler {
|
IpcHandler {
|
||||||
function open(tab: string): string {
|
function open(tab: string): string {
|
||||||
root.dankDashPopoutLoader.active = true;
|
const bar = root.getPreferredBar("clockButtonRef");
|
||||||
if (root.dankDashPopoutLoader.item) {
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!root.dankDashPopoutLoader.item)
|
||||||
|
return "DASH_OPEN_FAILED";
|
||||||
|
|
||||||
switch (tab.toLowerCase()) {
|
switch (tab.toLowerCase()) {
|
||||||
case "media":
|
case "media":
|
||||||
root.dankDashPopoutLoader.item.currentTabIndex = 1;
|
root.dankDashPopoutLoader.item.currentTabIndex = 1;
|
||||||
@@ -147,12 +190,10 @@ Item {
|
|||||||
root.dankDashPopoutLoader.item.currentTabIndex = 0;
|
root.dankDashPopoutLoader.item.currentTabIndex = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
root.dankDashPopoutLoader.item.setTriggerPosition(Screen.width / 2, Theme.barHeight + Theme.spacingS, 100, "center", Screen);
|
|
||||||
root.dankDashPopoutLoader.item.dashVisible = true;
|
root.dankDashPopoutLoader.item.dashVisible = true;
|
||||||
return "DASH_OPEN_SUCCESS";
|
return "DASH_OPEN_SUCCESS";
|
||||||
}
|
}
|
||||||
return "DASH_OPEN_FAILED";
|
|
||||||
}
|
|
||||||
|
|
||||||
function close(): string {
|
function close(): string {
|
||||||
if (root.dankDashPopoutLoader.item) {
|
if (root.dankDashPopoutLoader.item) {
|
||||||
@@ -163,8 +204,14 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function toggle(tab: string): string {
|
function toggle(tab: string): string {
|
||||||
const bar = root.getFirstBar();
|
if (root.dankDashPopoutLoader.item?.dashVisible) {
|
||||||
if (bar && bar.triggerWallpaperBrowserOnFocusedScreen()) {
|
root.dankDashPopoutLoader.item.dashVisible = false;
|
||||||
|
return "DASH_TOGGLE_SUCCESS";
|
||||||
|
}
|
||||||
|
|
||||||
|
const bar = root.getPreferredBar("clockButtonRef");
|
||||||
|
if (bar) {
|
||||||
|
bar.triggerWallpaperBrowser();
|
||||||
if (root.dankDashPopoutLoader.item) {
|
if (root.dankDashPopoutLoader.item) {
|
||||||
switch (tab.toLowerCase()) {
|
switch (tab.toLowerCase()) {
|
||||||
case "media":
|
case "media":
|
||||||
@@ -521,8 +568,9 @@ Item {
|
|||||||
|
|
||||||
IpcHandler {
|
IpcHandler {
|
||||||
function wallpaper(): string {
|
function wallpaper(): string {
|
||||||
const bar = root.getFirstBar();
|
const bar = root.getPreferredBar("clockButtonRef");
|
||||||
if (bar && bar.triggerWallpaperBrowserOnFocusedScreen()) {
|
if (bar) {
|
||||||
|
bar.triggerWallpaperBrowser();
|
||||||
return "SUCCESS: Toggled wallpaper browser";
|
return "SUCCESS: Toggled wallpaper browser";
|
||||||
}
|
}
|
||||||
return "ERROR: Failed to toggle wallpaper browser";
|
return "ERROR: Failed to toggle wallpaper browser";
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ Rectangle {
|
|||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
maximumLineCount: entryType === "long_text" ? 3 : 1
|
maximumLineCount: entryType === "long_text" ? 3 : 1
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
|
textFormat: Text.PlainText
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -676,8 +676,6 @@ Item {
|
|||||||
|
|
||||||
var apps = searchApps(searchQuery);
|
var apps = searchApps(searchQuery);
|
||||||
for (var i = 0; i < apps.length; i++) {
|
for (var i = 0; i < apps.length; i++) {
|
||||||
if (searchQuery)
|
|
||||||
apps[i]._preScored = 11000 - i;
|
|
||||||
allItems.push(apps[i]);
|
allItems.push(apps[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -685,14 +683,11 @@ Item {
|
|||||||
if (searchQuery && searchQuery.length >= 2) {
|
if (searchQuery && searchQuery.length >= 2) {
|
||||||
_pluginPhasePending = true;
|
_pluginPhasePending = true;
|
||||||
_phase1Items = allItems.slice();
|
_phase1Items = allItems.slice();
|
||||||
pluginPhaseTimer.restart();
|
|
||||||
if (allItems.length === 0) {
|
|
||||||
_pluginPhaseForceFirst = shouldResetSelection;
|
_pluginPhaseForceFirst = shouldResetSelection;
|
||||||
|
pluginPhaseTimer.restart();
|
||||||
isSearching = true;
|
isSearching = true;
|
||||||
searchCompleted();
|
searchCompleted();
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
_pluginPhaseForceFirst = false;
|
|
||||||
} else if (!searchQuery) {
|
} else if (!searchQuery) {
|
||||||
var emptyTriggerOrdered = getEmptyTriggerPluginsOrdered();
|
var emptyTriggerOrdered = getEmptyTriggerPluginsOrdered();
|
||||||
for (var i = 0; i < emptyTriggerOrdered.length; i++) {
|
for (var i = 0; i < emptyTriggerOrdered.length; i++) {
|
||||||
@@ -880,9 +875,7 @@ Item {
|
|||||||
_applyHighlights(newSections, searchQuery);
|
_applyHighlights(newSections, searchQuery);
|
||||||
flatModel = Scorer.flattenSections(newSections);
|
flatModel = Scorer.flattenSections(newSections);
|
||||||
sections = newSections;
|
sections = newSections;
|
||||||
if (selectedFlatIndex >= flatModel.length) {
|
|
||||||
selectedFlatIndex = getFirstItemIndex();
|
selectedFlatIndex = getFirstItemIndex();
|
||||||
}
|
|
||||||
updateSelectedItem();
|
updateSelectedItem();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ Item {
|
|||||||
property var spotlightContent: launcherContentLoader.item
|
property var spotlightContent: launcherContentLoader.item
|
||||||
property bool openedFromOverview: false
|
property bool openedFromOverview: false
|
||||||
property bool isClosing: false
|
property bool isClosing: false
|
||||||
property bool _windowEnabled: true
|
|
||||||
property bool _pendingInitialize: false
|
property bool _pendingInitialize: false
|
||||||
property string _pendingQuery: ""
|
property string _pendingQuery: ""
|
||||||
property string _pendingMode: ""
|
property string _pendingMode: ""
|
||||||
@@ -102,7 +101,7 @@ Item {
|
|||||||
spotlightContent.searchField.text = query;
|
spotlightContent.searchField.text = query;
|
||||||
}
|
}
|
||||||
if (spotlightContent.controller) {
|
if (spotlightContent.controller) {
|
||||||
var targetMode = mode || "all";
|
var targetMode = mode || SessionData.launcherLastMode || "all";
|
||||||
spotlightContent.controller.searchMode = targetMode;
|
spotlightContent.controller.searchMode = targetMode;
|
||||||
spotlightContent.controller.activePluginId = "";
|
spotlightContent.controller.activePluginId = "";
|
||||||
spotlightContent.controller.activePluginName = "";
|
spotlightContent.controller.activePluginName = "";
|
||||||
@@ -226,6 +225,15 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: spotlightContent?.controller ?? null
|
||||||
|
function onModeChanged(mode) {
|
||||||
|
if (spotlightContent.controller.autoSwitchedToFiles)
|
||||||
|
return;
|
||||||
|
SessionData.setLauncherLastMode(mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
HyprlandFocusGrab {
|
HyprlandFocusGrab {
|
||||||
id: focusGrab
|
id: focusGrab
|
||||||
windows: [launcherWindow]
|
windows: [launcherWindow]
|
||||||
@@ -253,38 +261,26 @@ Item {
|
|||||||
if (Quickshell.screens.length === 0)
|
if (Quickshell.screens.length === 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const screen = launcherWindow.screen;
|
const screenName = launcherWindow.screen?.name;
|
||||||
const screenName = screen?.name;
|
if (screenName) {
|
||||||
|
|
||||||
let needsReset = !screen || !screenName;
|
|
||||||
if (!needsReset) {
|
|
||||||
needsReset = true;
|
|
||||||
for (let i = 0; i < Quickshell.screens.length; i++) {
|
for (let i = 0; i < Quickshell.screens.length; i++) {
|
||||||
if (Quickshell.screens[i].name === screenName) {
|
if (Quickshell.screens[i].name === screenName)
|
||||||
needsReset = false;
|
return;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!needsReset)
|
if (spotlightOpen)
|
||||||
return;
|
hide();
|
||||||
|
|
||||||
const newScreen = CompositorService.getFocusedScreen() ?? Quickshell.screens[0];
|
const newScreen = CompositorService.getFocusedScreen() ?? Quickshell.screens[0];
|
||||||
if (!newScreen)
|
if (newScreen)
|
||||||
return;
|
|
||||||
|
|
||||||
root._windowEnabled = false;
|
|
||||||
launcherWindow.screen = newScreen;
|
launcherWindow.screen = newScreen;
|
||||||
Qt.callLater(() => {
|
|
||||||
root._windowEnabled = true;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: launcherWindow
|
id: launcherWindow
|
||||||
visible: root._windowEnabled && (!root.unloadContentOnClose || spotlightOpen || isClosing)
|
visible: spotlightOpen || isClosing
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
exclusionMode: ExclusionMode.Ignore
|
exclusionMode: ExclusionMode.Ignore
|
||||||
|
|
||||||
|
|||||||
@@ -643,7 +643,7 @@ FocusScope {
|
|||||||
Image {
|
Image {
|
||||||
width: 40
|
width: 40
|
||||||
height: 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.width: 40
|
||||||
sourceSize.height: 40
|
sourceSize.height: 40
|
||||||
fillMode: Image.PreserveAspectFit
|
fillMode: Image.PreserveAspectFit
|
||||||
|
|||||||
@@ -460,7 +460,7 @@ Item {
|
|||||||
switch (mode) {
|
switch (mode) {
|
||||||
case "files":
|
case "files":
|
||||||
if (!DSearchService.dsearchAvailable)
|
if (!DSearchService.dsearchAvailable)
|
||||||
return I18n.tr("File search requires dsearch\nInstall from github.com/morelazers/dsearch");
|
return I18n.tr("File search requires dsearch\nInstall from github.com/AvengeMedia/danksearch");
|
||||||
if (!hasQuery)
|
if (!hasQuery)
|
||||||
return I18n.tr("Type to search files");
|
return I18n.tr("Type to search files");
|
||||||
if (root.controller.searchQuery.length < 2)
|
if (root.controller.searchQuery.length < 2)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
const Weights = {
|
const Weights = {
|
||||||
exactMatch: 10000,
|
exactMatch: 10000,
|
||||||
prefixMatch: 5000,
|
prefixMatch: 5000,
|
||||||
wordBoundary: 1000,
|
wordBoundary: 3000,
|
||||||
substring: 500,
|
substring: 500,
|
||||||
fuzzy: 100,
|
fuzzy: 100,
|
||||||
frecency: 2000,
|
frecency: 2000,
|
||||||
@@ -99,8 +99,8 @@ function getTimeBucketWeight(daysSinceUsed) {
|
|||||||
function calculateTextScore(name, query) {
|
function calculateTextScore(name, query) {
|
||||||
if (name === query) return Weights.exactMatch
|
if (name === query) return Weights.exactMatch
|
||||||
if (name.startsWith(query)) return Weights.prefixMatch
|
if (name.startsWith(query)) return Weights.prefixMatch
|
||||||
if (name.includes(query)) return Weights.substring
|
|
||||||
if (hasWordBoundaryMatch(name, query)) return Weights.wordBoundary
|
if (hasWordBoundaryMatch(name, query)) return Weights.wordBoundary
|
||||||
|
if (name.includes(query)) return Weights.substring
|
||||||
|
|
||||||
if (query.length >= 3) {
|
if (query.length >= 3) {
|
||||||
var fs = fuzzyScore(name, query)
|
var fs = fuzzyScore(name, query)
|
||||||
@@ -140,7 +140,7 @@ function score(item, query, frecencyData) {
|
|||||||
|
|
||||||
if (textScore === 0) return 0
|
if (textScore === 0) return 0
|
||||||
|
|
||||||
var usageBonus = frecencyData ? Math.min(frecencyData.usageCount * 10, Weights.frecency) : 0
|
var usageBonus = frecencyData ? Math.min(frecencyData.usageCount * 50, Weights.frecency) : 0
|
||||||
|
|
||||||
return textScore + usageBonus + typeBonus
|
return textScore + usageBonus + typeBonus
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
@@ -11,8 +12,45 @@ FloatingWindow {
|
|||||||
property string passwordInput: ""
|
property string passwordInput: ""
|
||||||
property var currentFlow: PolkitService.agent?.flow
|
property var currentFlow: PolkitService.agent?.flow
|
||||||
property bool isLoading: false
|
property bool isLoading: false
|
||||||
|
property bool awaitingFprintForPassword: false
|
||||||
readonly property int inputFieldHeight: Theme.fontSizeMedium + Theme.spacingL * 2
|
readonly property int inputFieldHeight: Theme.fontSizeMedium + Theme.spacingL * 2
|
||||||
|
|
||||||
|
property string polkitEtcPamText: ""
|
||||||
|
property string polkitLibPamText: ""
|
||||||
|
property string systemAuthPamText: ""
|
||||||
|
property string commonAuthPamText: ""
|
||||||
|
property string passwordAuthPamText: ""
|
||||||
|
readonly property bool polkitPamHasFprint: {
|
||||||
|
const polkitText = polkitEtcPamText !== "" ? polkitEtcPamText : polkitLibPamText;
|
||||||
|
if (!polkitText)
|
||||||
|
return false;
|
||||||
|
return pamModuleEnabled(polkitText, "pam_fprintd") || (polkitText.includes("system-auth") && pamModuleEnabled(systemAuthPamText, "pam_fprintd")) || (polkitText.includes("common-auth") && pamModuleEnabled(commonAuthPamText, "pam_fprintd")) || (polkitText.includes("password-auth") && pamModuleEnabled(passwordAuthPamText, "pam_fprintd"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function stripPamComment(line) {
|
||||||
|
if (!line)
|
||||||
|
return "";
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (!trimmed || trimmed.startsWith("#"))
|
||||||
|
return "";
|
||||||
|
const hashIdx = trimmed.indexOf("#");
|
||||||
|
if (hashIdx >= 0)
|
||||||
|
return trimmed.substring(0, hashIdx).trim();
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pamModuleEnabled(pamText, moduleName) {
|
||||||
|
if (!pamText || !moduleName)
|
||||||
|
return false;
|
||||||
|
const lines = pamText.split(/\r?\n/);
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const line = stripPamComment(lines[i]);
|
||||||
|
if (line && line.includes(moduleName))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function focusPasswordField() {
|
function focusPasswordField() {
|
||||||
passwordField.forceActiveFocus();
|
passwordField.forceActiveFocus();
|
||||||
}
|
}
|
||||||
@@ -20,6 +58,7 @@ FloatingWindow {
|
|||||||
function show() {
|
function show() {
|
||||||
passwordInput = "";
|
passwordInput = "";
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
|
awaitingFprintForPassword = false;
|
||||||
visible = true;
|
visible = true;
|
||||||
Qt.callLater(focusPasswordField);
|
Qt.callLater(focusPasswordField);
|
||||||
}
|
}
|
||||||
@@ -28,17 +67,27 @@ FloatingWindow {
|
|||||||
visible = false;
|
visible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _commitSubmit() {
|
||||||
|
isLoading = true;
|
||||||
|
awaitingFprintForPassword = false;
|
||||||
|
currentFlow.submit(passwordInput);
|
||||||
|
passwordInput = "";
|
||||||
|
}
|
||||||
|
|
||||||
function submitAuth() {
|
function submitAuth() {
|
||||||
if (!currentFlow || isLoading)
|
if (!currentFlow || isLoading)
|
||||||
return;
|
return;
|
||||||
isLoading = true;
|
if (!currentFlow.isResponseRequired) {
|
||||||
currentFlow.submit(passwordInput);
|
awaitingFprintForPassword = true;
|
||||||
passwordInput = "";
|
return;
|
||||||
|
}
|
||||||
|
_commitSubmit();
|
||||||
}
|
}
|
||||||
|
|
||||||
function cancelAuth() {
|
function cancelAuth() {
|
||||||
if (isLoading)
|
if (isLoading)
|
||||||
return;
|
return;
|
||||||
|
awaitingFprintForPassword = false;
|
||||||
if (currentFlow) {
|
if (currentFlow) {
|
||||||
currentFlow.cancelAuthenticationRequest();
|
currentFlow.cancelAuthenticationRequest();
|
||||||
return;
|
return;
|
||||||
@@ -60,6 +109,7 @@ FloatingWindow {
|
|||||||
}
|
}
|
||||||
passwordInput = "";
|
passwordInput = "";
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
|
awaitingFprintForPassword = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
@@ -83,6 +133,11 @@ FloatingWindow {
|
|||||||
function onIsResponseRequiredChanged() {
|
function onIsResponseRequiredChanged() {
|
||||||
if (!currentFlow.isResponseRequired)
|
if (!currentFlow.isResponseRequired)
|
||||||
return;
|
return;
|
||||||
|
if (awaitingFprintForPassword && passwordInput !== "") {
|
||||||
|
_commitSubmit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
awaitingFprintForPassword = false;
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
passwordInput = "";
|
passwordInput = "";
|
||||||
passwordField.forceActiveFocus();
|
passwordField.forceActiveFocus();
|
||||||
@@ -101,6 +156,41 @@ FloatingWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
path: "/etc/pam.d/polkit-1"
|
||||||
|
printErrors: false
|
||||||
|
onLoaded: root.polkitEtcPamText = text()
|
||||||
|
onLoadFailed: root.polkitEtcPamText = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
path: "/usr/lib/pam.d/polkit-1"
|
||||||
|
printErrors: false
|
||||||
|
onLoaded: root.polkitLibPamText = text()
|
||||||
|
onLoadFailed: root.polkitLibPamText = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
path: "/etc/pam.d/system-auth"
|
||||||
|
printErrors: false
|
||||||
|
onLoaded: root.systemAuthPamText = text()
|
||||||
|
onLoadFailed: root.systemAuthPamText = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
path: "/etc/pam.d/common-auth"
|
||||||
|
printErrors: false
|
||||||
|
onLoaded: root.commonAuthPamText = text()
|
||||||
|
onLoadFailed: root.commonAuthPamText = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
path: "/etc/pam.d/password-auth"
|
||||||
|
printErrors: false
|
||||||
|
onLoaded: root.passwordAuthPamText = text()
|
||||||
|
onLoadFailed: root.passwordAuthPamText = ""
|
||||||
|
}
|
||||||
|
|
||||||
FocusScope {
|
FocusScope {
|
||||||
id: contentFocusScope
|
id: contentFocusScope
|
||||||
|
|
||||||
@@ -205,37 +295,31 @@ FloatingWindow {
|
|||||||
visible: text !== ""
|
visible: text !== ""
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: inputFieldHeight
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: Theme.surfaceHover
|
|
||||||
border.color: passwordField.activeFocus ? Theme.primary : Theme.outlineStrong
|
|
||||||
border.width: passwordField.activeFocus ? 2 : 1
|
|
||||||
opacity: isLoading ? 0.5 : 1
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
enabled: !isLoading
|
|
||||||
onClicked: passwordField.forceActiveFocus()
|
|
||||||
}
|
|
||||||
|
|
||||||
DankTextField {
|
DankTextField {
|
||||||
id: passwordField
|
id: passwordField
|
||||||
|
|
||||||
anchors.fill: parent
|
width: parent.width
|
||||||
|
height: inputFieldHeight
|
||||||
|
backgroundColor: Theme.surfaceHover
|
||||||
|
normalBorderColor: Theme.outlineStrong
|
||||||
|
focusedBorderColor: Theme.primary
|
||||||
|
borderWidth: 1
|
||||||
|
focusedBorderWidth: 2
|
||||||
|
leftIconName: polkitPamHasFprint ? "fingerprint" : ""
|
||||||
|
leftIconSize: 20
|
||||||
|
leftIconColor: Theme.primary
|
||||||
|
leftIconFocusedColor: Theme.primary
|
||||||
|
opacity: isLoading ? 0.5 : 1
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
textColor: Theme.surfaceText
|
textColor: Theme.surfaceText
|
||||||
text: passwordInput
|
text: passwordInput
|
||||||
showPasswordToggle: !(currentFlow?.responseVisible ?? false)
|
showPasswordToggle: !(currentFlow?.responseVisible ?? false)
|
||||||
echoMode: (currentFlow?.responseVisible ?? false) || passwordVisible ? TextInput.Normal : TextInput.Password
|
echoMode: (currentFlow?.responseVisible ?? false) || passwordVisible ? TextInput.Normal : TextInput.Password
|
||||||
placeholderText: ""
|
placeholderText: ""
|
||||||
backgroundColor: "transparent"
|
|
||||||
enabled: !isLoading
|
enabled: !isLoading
|
||||||
onTextEdited: passwordInput = text
|
onTextEdited: passwordInput = text
|
||||||
onAccepted: submitAuth()
|
onAccepted: submitAuth()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: I18n.tr("Authentication failed, please try again")
|
text: I18n.tr("Authentication failed, please try again")
|
||||||
|
|||||||
@@ -631,7 +631,7 @@ FloatingWindow {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
textColor: Theme.surfaceText
|
textColor: Theme.surfaceText
|
||||||
placeholderText: "fixed 800"
|
placeholderText: "800"
|
||||||
backgroundColor: "transparent"
|
backgroundColor: "transparent"
|
||||||
enabled: root.visible
|
enabled: root.visible
|
||||||
}
|
}
|
||||||
@@ -658,7 +658,7 @@ FloatingWindow {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
textColor: Theme.surfaceText
|
textColor: Theme.surfaceText
|
||||||
placeholderText: "fixed 600"
|
placeholderText: "600"
|
||||||
backgroundColor: "transparent"
|
backgroundColor: "transparent"
|
||||||
enabled: root.visible
|
enabled: root.visible
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ DankPopout {
|
|||||||
|
|
||||||
layerNamespace: "dms:app-launcher"
|
layerNamespace: "dms:app-launcher"
|
||||||
|
|
||||||
|
readonly property real screenWidth: screen?.width ?? 1920
|
||||||
|
readonly property real screenHeight: screen?.height ?? 1080
|
||||||
|
|
||||||
property string _pendingMode: ""
|
property string _pendingMode: ""
|
||||||
property string _pendingQuery: ""
|
property string _pendingQuery: ""
|
||||||
|
|
||||||
@@ -41,8 +44,35 @@ DankPopout {
|
|||||||
openWithQuery(query);
|
openWithQuery(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
popupWidth: 560
|
readonly property int _baseWidth: {
|
||||||
popupHeight: 640
|
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
|
triggerWidth: 40
|
||||||
positioning: ""
|
positioning: ""
|
||||||
contentHandlesKeys: contentLoader.item?.launcherContent?.editMode ?? false
|
contentHandlesKeys: contentLoader.item?.launcherContent?.editMode ?? false
|
||||||
@@ -61,7 +91,7 @@ DankPopout {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
const query = _pendingQuery;
|
const query = _pendingQuery;
|
||||||
const mode = _pendingMode || "apps";
|
const mode = _pendingMode || SessionData.appDrawerLastMode || "apps";
|
||||||
_pendingMode = "";
|
_pendingMode = "";
|
||||||
_pendingQuery = "";
|
_pendingQuery = "";
|
||||||
|
|
||||||
@@ -83,6 +113,15 @@ DankPopout {
|
|||||||
lc.actionPanel?.hide();
|
lc.actionPanel?.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: contentLoader.item?.launcherContent?.controller ?? null
|
||||||
|
function onModeChanged(mode) {
|
||||||
|
if (contentLoader.item.launcherContent.controller.autoSwitchedToFiles)
|
||||||
|
return;
|
||||||
|
SessionData.setAppDrawerLastMode(mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
content: Component {
|
content: Component {
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: contentContainer
|
id: contentContainer
|
||||||
|
|||||||
@@ -85,12 +85,8 @@ Variants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (!source) {
|
if (typeof blurWallpaperWindow.updatesEnabled !== "undefined")
|
||||||
isInitialized = true;
|
blurWallpaperWindow.updatesEnabled = Qt.binding(() => !root.source || root.effectActive || root._renderSettling || currentWallpaper.status === Image.Loading || nextWallpaper.status === Image.Loading);
|
||||||
return;
|
|
||||||
}
|
|
||||||
const formattedSource = source.startsWith("file://") ? source : encodeFileUrl(source);
|
|
||||||
setWallpaperImmediate(formattedSource);
|
|
||||||
isInitialized = true;
|
isInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,8 +94,25 @@ Variants {
|
|||||||
property real transitionProgress: 0
|
property real transitionProgress: 0
|
||||||
readonly property bool transitioning: transitionAnimation.running
|
readonly property bool transitioning: transitionAnimation.running
|
||||||
property bool effectActive: false
|
property bool effectActive: false
|
||||||
|
property bool _renderSettling: true
|
||||||
property bool useNextForEffect: false
|
property bool useNextForEffect: false
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: currentWallpaper
|
||||||
|
function onStatusChanged() {
|
||||||
|
if (currentWallpaper.status !== Image.Ready && currentWallpaper.status !== Image.Error)
|
||||||
|
return;
|
||||||
|
root._renderSettling = true;
|
||||||
|
renderSettleTimer.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: renderSettleTimer
|
||||||
|
interval: 1000
|
||||||
|
onTriggered: root._renderSettling = false
|
||||||
|
}
|
||||||
|
|
||||||
onSourceChanged: {
|
onSourceChanged: {
|
||||||
if (!source || source.startsWith("#")) {
|
if (!source || source.startsWith("#")) {
|
||||||
setWallpaperImmediate("");
|
setWallpaperImmediate("");
|
||||||
@@ -124,6 +137,8 @@ Variants {
|
|||||||
transitionAnimation.stop();
|
transitionAnimation.stop();
|
||||||
root.transitionProgress = 0.0;
|
root.transitionProgress = 0.0;
|
||||||
root.effectActive = false;
|
root.effectActive = false;
|
||||||
|
root._renderSettling = true;
|
||||||
|
renderSettleTimer.restart();
|
||||||
currentWallpaper.source = newSource;
|
currentWallpaper.source = newSource;
|
||||||
nextWallpaper.source = "";
|
nextWallpaper.source = "";
|
||||||
}
|
}
|
||||||
@@ -152,6 +167,8 @@ Variants {
|
|||||||
transitionAnimation.stop();
|
transitionAnimation.stop();
|
||||||
root.transitionProgress = 0;
|
root.transitionProgress = 0;
|
||||||
root.effectActive = false;
|
root.effectActive = false;
|
||||||
|
root._renderSettling = true;
|
||||||
|
renderSettleTimer.restart();
|
||||||
currentWallpaper.source = nextWallpaper.source;
|
currentWallpaper.source = nextWallpaper.source;
|
||||||
nextWallpaper.source = "";
|
nextWallpaper.source = "";
|
||||||
}
|
}
|
||||||
@@ -160,6 +177,9 @@ Variants {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
root._renderSettling = true;
|
||||||
|
renderSettleTimer.restart();
|
||||||
|
|
||||||
nextWallpaper.source = newPath;
|
nextWallpaper.source = newPath;
|
||||||
|
|
||||||
if (nextWallpaper.status === Image.Ready)
|
if (nextWallpaper.status === Image.Ready)
|
||||||
@@ -186,6 +206,7 @@ Variants {
|
|||||||
visible: false
|
visible: false
|
||||||
opacity: 1
|
opacity: 1
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
|
retainWhileLoading: true
|
||||||
smooth: true
|
smooth: true
|
||||||
cache: true
|
cache: true
|
||||||
sourceSize: Qt.size(root.textureWidth, root.textureHeight)
|
sourceSize: Qt.size(root.textureWidth, root.textureHeight)
|
||||||
@@ -198,6 +219,7 @@ Variants {
|
|||||||
visible: false
|
visible: false
|
||||||
opacity: 0
|
opacity: 0
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
|
retainWhileLoading: true
|
||||||
smooth: true
|
smooth: true
|
||||||
cache: true
|
cache: true
|
||||||
sourceSize: Qt.size(root.textureWidth, root.textureHeight)
|
sourceSize: Qt.size(root.textureWidth, root.textureHeight)
|
||||||
@@ -280,6 +302,8 @@ Variants {
|
|||||||
root.useNextForEffect = false;
|
root.useNextForEffect = false;
|
||||||
nextWallpaper.source = "";
|
nextWallpaper.source = "";
|
||||||
root.transitionProgress = 0.0;
|
root.transitionProgress = 0.0;
|
||||||
|
root._renderSettling = true;
|
||||||
|
renderSettleTimer.restart();
|
||||||
root.effectActive = false;
|
root.effectActive = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,16 @@ DankPopout {
|
|||||||
|
|
||||||
backgroundInteractive: !anyModalOpen
|
backgroundInteractive: !anyModalOpen
|
||||||
|
|
||||||
|
onCredentialsPromptOpenChanged: {
|
||||||
|
if (credentialsPromptOpen && shouldBeVisible)
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
onPolkitModalOpenChanged: {
|
||||||
|
if (polkitModalOpen && shouldBeVisible)
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
customKeyboardFocus: {
|
customKeyboardFocus: {
|
||||||
if (!shouldBeVisible)
|
if (!shouldBeVisible)
|
||||||
return WlrKeyboardFocus.None;
|
return WlrKeyboardFocus.None;
|
||||||
|
|||||||
@@ -234,16 +234,7 @@ Rectangle {
|
|||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: {
|
name: AudioService.sinkIcon(modelData)
|
||||||
if (modelData.name.includes("bluez"))
|
|
||||||
return "headset";
|
|
||||||
else if (modelData.name.includes("hdmi"))
|
|
||||||
return "tv";
|
|
||||||
else if (modelData.name.includes("usb"))
|
|
||||||
return "headset";
|
|
||||||
else
|
|
||||||
return "speaker";
|
|
||||||
}
|
|
||||||
size: Theme.iconSize - 4
|
size: Theme.iconSize - 4
|
||||||
color: modelData === AudioService.sink ? Theme.primary : Theme.surfaceText
|
color: modelData === AudioService.sink ? Theme.primary : Theme.surfaceText
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|||||||
@@ -669,6 +669,30 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
text: bluetoothContextMenu.currentDevice?.trusted ? I18n.tr("Untrust") : I18n.tr("Trust")
|
||||||
|
height: 32
|
||||||
|
|
||||||
|
contentItem: StyledText {
|
||||||
|
text: parent.text
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
leftPadding: Theme.spacingS
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: parent.hovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||||
|
radius: Theme.cornerRadius / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
onTriggered: {
|
||||||
|
if (!bluetoothContextMenu.hasDevice)
|
||||||
|
return;
|
||||||
|
bluetoothContextMenu.currentDevice.trusted = !bluetoothContextMenu.currentDevice.trusted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MenuItem {
|
MenuItem {
|
||||||
text: I18n.tr("Forget Device")
|
text: I18n.tr("Forget Device")
|
||||||
height: 32
|
height: 32
|
||||||
|
|||||||
@@ -21,12 +21,10 @@ Item {
|
|||||||
property alias centerWidgetsModel: centerWidgetsModel
|
property alias centerWidgetsModel: centerWidgetsModel
|
||||||
property alias rightWidgetsModel: rightWidgetsModel
|
property alias rightWidgetsModel: rightWidgetsModel
|
||||||
|
|
||||||
ScriptModel {
|
property string _leftWidgetsJson: {
|
||||||
id: leftWidgetsModel
|
|
||||||
values: {
|
|
||||||
root.barConfig;
|
root.barConfig;
|
||||||
const leftWidgets = root.barConfig?.leftWidgets || [];
|
const leftWidgets = root.barConfig?.leftWidgets || [];
|
||||||
return leftWidgets.map((w, index) => {
|
const mapped = leftWidgets.map((w, index) => {
|
||||||
if (typeof w === "string") {
|
if (typeof w === "string") {
|
||||||
return {
|
return {
|
||||||
widgetId: w,
|
widgetId: w,
|
||||||
@@ -41,53 +39,64 @@ Item {
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return JSON.stringify(mapped);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property string _centerWidgetsJson: {
|
||||||
|
root.barConfig;
|
||||||
|
const centerWidgets = root.barConfig?.centerWidgets || [];
|
||||||
|
const mapped = centerWidgets.map((w, index) => {
|
||||||
|
if (typeof w === "string") {
|
||||||
|
return {
|
||||||
|
widgetId: w,
|
||||||
|
id: w + "_" + index,
|
||||||
|
enabled: true
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const obj = Object.assign({}, w);
|
||||||
|
obj.widgetId = w.id || w.widgetId;
|
||||||
|
obj.id = (w.id || w.widgetId) + "_" + index;
|
||||||
|
obj.enabled = w.enabled !== false;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return JSON.stringify(mapped);
|
||||||
|
}
|
||||||
|
|
||||||
|
property string _rightWidgetsJson: {
|
||||||
|
root.barConfig;
|
||||||
|
const rightWidgets = root.barConfig?.rightWidgets || [];
|
||||||
|
const mapped = rightWidgets.map((w, index) => {
|
||||||
|
if (typeof w === "string") {
|
||||||
|
return {
|
||||||
|
widgetId: w,
|
||||||
|
id: w + "_" + index,
|
||||||
|
enabled: true
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const obj = Object.assign({}, w);
|
||||||
|
obj.widgetId = w.id || w.widgetId;
|
||||||
|
obj.id = (w.id || w.widgetId) + "_" + index;
|
||||||
|
obj.enabled = w.enabled !== false;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return JSON.stringify(mapped);
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptModel {
|
||||||
|
id: leftWidgetsModel
|
||||||
|
values: JSON.parse(root._leftWidgetsJson)
|
||||||
}
|
}
|
||||||
|
|
||||||
ScriptModel {
|
ScriptModel {
|
||||||
id: centerWidgetsModel
|
id: centerWidgetsModel
|
||||||
values: {
|
values: JSON.parse(root._centerWidgetsJson)
|
||||||
root.barConfig;
|
|
||||||
const centerWidgets = root.barConfig?.centerWidgets || [];
|
|
||||||
return centerWidgets.map((w, index) => {
|
|
||||||
if (typeof w === "string") {
|
|
||||||
return {
|
|
||||||
widgetId: w,
|
|
||||||
id: w + "_" + index,
|
|
||||||
enabled: true
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
const obj = Object.assign({}, w);
|
|
||||||
obj.widgetId = w.id || w.widgetId;
|
|
||||||
obj.id = (w.id || w.widgetId) + "_" + index;
|
|
||||||
obj.enabled = w.enabled !== false;
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ScriptModel {
|
ScriptModel {
|
||||||
id: rightWidgetsModel
|
id: rightWidgetsModel
|
||||||
values: {
|
values: JSON.parse(root._rightWidgetsJson)
|
||||||
root.barConfig;
|
|
||||||
const rightWidgets = root.barConfig?.rightWidgets || [];
|
|
||||||
return rightWidgets.map((w, index) => {
|
|
||||||
if (typeof w === "string") {
|
|
||||||
return {
|
|
||||||
widgetId: w,
|
|
||||||
id: w + "_" + index,
|
|
||||||
enabled: true
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
const obj = Object.assign({}, w);
|
|
||||||
obj.widgetId = w.id || w.widgetId;
|
|
||||||
obj.id = (w.id || w.widgetId) + "_" + index;
|
|
||||||
obj.enabled = w.enabled !== false;
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function triggerControlCenterOnFocusedScreen() {
|
function triggerControlCenterOnFocusedScreen() {
|
||||||
|
|||||||
@@ -561,10 +561,7 @@ Item {
|
|||||||
axis: barWindow.axis
|
axis: barWindow.axis
|
||||||
section: topBarContent.getWidgetSection(parent)
|
section: topBarContent.getWidgetSection(parent)
|
||||||
parentScreen: barWindow.screen
|
parentScreen: barWindow.screen
|
||||||
popoutTarget: {
|
popoutTarget: clipboardHistoryPopoutLoader.item ?? null
|
||||||
clipboardHistoryPopoutLoader.active = true;
|
|
||||||
return clipboardHistoryPopoutLoader.item;
|
|
||||||
}
|
|
||||||
|
|
||||||
function openClipboardPopout(initialTab) {
|
function openClipboardPopout(initialTab) {
|
||||||
clipboardHistoryPopoutLoader.active = true;
|
clipboardHistoryPopoutLoader.active = true;
|
||||||
@@ -759,10 +756,7 @@ Item {
|
|||||||
barThickness: barWindow.effectiveBarThickness
|
barThickness: barWindow.effectiveBarThickness
|
||||||
widgetThickness: barWindow.widgetThickness
|
widgetThickness: barWindow.widgetThickness
|
||||||
section: topBarContent.getWidgetSection(parent) || "center"
|
section: topBarContent.getWidgetSection(parent) || "center"
|
||||||
popoutTarget: {
|
popoutTarget: dankDashPopoutLoader.item ?? null
|
||||||
dankDashPopoutLoader.active = true;
|
|
||||||
return dankDashPopoutLoader.item;
|
|
||||||
}
|
|
||||||
parentScreen: barWindow.screen
|
parentScreen: barWindow.screen
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
@@ -826,10 +820,7 @@ Item {
|
|||||||
barThickness: barWindow.effectiveBarThickness
|
barThickness: barWindow.effectiveBarThickness
|
||||||
widgetThickness: barWindow.widgetThickness
|
widgetThickness: barWindow.widgetThickness
|
||||||
section: topBarContent.getWidgetSection(parent) || "center"
|
section: topBarContent.getWidgetSection(parent) || "center"
|
||||||
popoutTarget: {
|
popoutTarget: dankDashPopoutLoader.item ?? null
|
||||||
dankDashPopoutLoader.active = true;
|
|
||||||
return dankDashPopoutLoader.item;
|
|
||||||
}
|
|
||||||
parentScreen: barWindow.screen
|
parentScreen: barWindow.screen
|
||||||
onClicked: {
|
onClicked: {
|
||||||
dankDashPopoutLoader.active = true;
|
dankDashPopoutLoader.active = true;
|
||||||
@@ -881,10 +872,7 @@ Item {
|
|||||||
barThickness: barWindow.effectiveBarThickness
|
barThickness: barWindow.effectiveBarThickness
|
||||||
widgetThickness: barWindow.widgetThickness
|
widgetThickness: barWindow.widgetThickness
|
||||||
section: topBarContent.getWidgetSection(parent) || "center"
|
section: topBarContent.getWidgetSection(parent) || "center"
|
||||||
popoutTarget: {
|
popoutTarget: dankDashPopoutLoader.item ?? null
|
||||||
dankDashPopoutLoader.active = true;
|
|
||||||
return dankDashPopoutLoader.item;
|
|
||||||
}
|
|
||||||
parentScreen: barWindow.screen
|
parentScreen: barWindow.screen
|
||||||
onClicked: {
|
onClicked: {
|
||||||
dankDashPopoutLoader.active = true;
|
dankDashPopoutLoader.active = true;
|
||||||
@@ -968,10 +956,7 @@ Item {
|
|||||||
widgetThickness: barWindow.widgetThickness
|
widgetThickness: barWindow.widgetThickness
|
||||||
axis: barWindow.axis
|
axis: barWindow.axis
|
||||||
section: topBarContent.getWidgetSection(parent) || "right"
|
section: topBarContent.getWidgetSection(parent) || "right"
|
||||||
popoutTarget: {
|
popoutTarget: processListPopoutLoader.item ?? null
|
||||||
processListPopoutLoader.active = true;
|
|
||||||
return processListPopoutLoader.item;
|
|
||||||
}
|
|
||||||
parentScreen: barWindow.screen
|
parentScreen: barWindow.screen
|
||||||
widgetData: parent.widgetData
|
widgetData: parent.widgetData
|
||||||
onCpuClicked: {
|
onCpuClicked: {
|
||||||
@@ -1004,10 +989,7 @@ Item {
|
|||||||
widgetThickness: barWindow.widgetThickness
|
widgetThickness: barWindow.widgetThickness
|
||||||
axis: barWindow.axis
|
axis: barWindow.axis
|
||||||
section: topBarContent.getWidgetSection(parent) || "right"
|
section: topBarContent.getWidgetSection(parent) || "right"
|
||||||
popoutTarget: {
|
popoutTarget: processListPopoutLoader.item ?? null
|
||||||
processListPopoutLoader.active = true;
|
|
||||||
return processListPopoutLoader.item;
|
|
||||||
}
|
|
||||||
parentScreen: barWindow.screen
|
parentScreen: barWindow.screen
|
||||||
widgetData: parent.widgetData
|
widgetData: parent.widgetData
|
||||||
onRamClicked: {
|
onRamClicked: {
|
||||||
@@ -1054,10 +1036,7 @@ Item {
|
|||||||
widgetThickness: barWindow.widgetThickness
|
widgetThickness: barWindow.widgetThickness
|
||||||
axis: barWindow.axis
|
axis: barWindow.axis
|
||||||
section: topBarContent.getWidgetSection(parent) || "right"
|
section: topBarContent.getWidgetSection(parent) || "right"
|
||||||
popoutTarget: {
|
popoutTarget: processListPopoutLoader.item ?? null
|
||||||
processListPopoutLoader.active = true;
|
|
||||||
return processListPopoutLoader.item;
|
|
||||||
}
|
|
||||||
parentScreen: barWindow.screen
|
parentScreen: barWindow.screen
|
||||||
widgetData: parent.widgetData
|
widgetData: parent.widgetData
|
||||||
onCpuTempClicked: {
|
onCpuTempClicked: {
|
||||||
@@ -1090,10 +1069,7 @@ Item {
|
|||||||
widgetThickness: barWindow.widgetThickness
|
widgetThickness: barWindow.widgetThickness
|
||||||
axis: barWindow.axis
|
axis: barWindow.axis
|
||||||
section: topBarContent.getWidgetSection(parent) || "right"
|
section: topBarContent.getWidgetSection(parent) || "right"
|
||||||
popoutTarget: {
|
popoutTarget: processListPopoutLoader.item ?? null
|
||||||
processListPopoutLoader.active = true;
|
|
||||||
return processListPopoutLoader.item;
|
|
||||||
}
|
|
||||||
parentScreen: barWindow.screen
|
parentScreen: barWindow.screen
|
||||||
widgetData: parent.widgetData
|
widgetData: parent.widgetData
|
||||||
onGpuTempClicked: {
|
onGpuTempClicked: {
|
||||||
@@ -1134,10 +1110,7 @@ Item {
|
|||||||
barThickness: barWindow.effectiveBarThickness
|
barThickness: barWindow.effectiveBarThickness
|
||||||
axis: barWindow.axis
|
axis: barWindow.axis
|
||||||
section: topBarContent.getWidgetSection(parent) || "right"
|
section: topBarContent.getWidgetSection(parent) || "right"
|
||||||
popoutTarget: {
|
popoutTarget: notificationCenterLoader.item ?? null
|
||||||
notificationCenterLoader.active = true;
|
|
||||||
return notificationCenterLoader.item;
|
|
||||||
}
|
|
||||||
parentScreen: barWindow.screen
|
parentScreen: barWindow.screen
|
||||||
onClicked: {
|
onClicked: {
|
||||||
notificationCenterLoader.active = true;
|
notificationCenterLoader.active = true;
|
||||||
@@ -1172,10 +1145,7 @@ Item {
|
|||||||
section: topBarContent.getWidgetSection(parent) || "right"
|
section: topBarContent.getWidgetSection(parent) || "right"
|
||||||
barSpacing: barConfig?.spacing ?? 4
|
barSpacing: barConfig?.spacing ?? 4
|
||||||
barConfig: topBarContent.barConfig
|
barConfig: topBarContent.barConfig
|
||||||
popoutTarget: {
|
popoutTarget: batteryPopoutLoader.item ?? null
|
||||||
batteryPopoutLoader.active = true;
|
|
||||||
return batteryPopoutLoader.item;
|
|
||||||
}
|
|
||||||
parentScreen: barWindow.screen
|
parentScreen: barWindow.screen
|
||||||
onToggleBatteryPopup: {
|
onToggleBatteryPopup: {
|
||||||
batteryPopoutLoader.active = true;
|
batteryPopoutLoader.active = true;
|
||||||
@@ -1208,10 +1178,7 @@ Item {
|
|||||||
barThickness: barWindow.effectiveBarThickness
|
barThickness: barWindow.effectiveBarThickness
|
||||||
axis: barWindow.axis
|
axis: barWindow.axis
|
||||||
section: topBarContent.getWidgetSection(parent) || "center"
|
section: topBarContent.getWidgetSection(parent) || "center"
|
||||||
popoutTarget: {
|
popoutTarget: layoutPopoutLoader.item ?? null
|
||||||
layoutPopoutLoader.active = true;
|
|
||||||
return layoutPopoutLoader.item;
|
|
||||||
}
|
|
||||||
parentScreen: barWindow.screen
|
parentScreen: barWindow.screen
|
||||||
onToggleLayoutPopup: {
|
onToggleLayoutPopup: {
|
||||||
layoutPopoutLoader.active = true;
|
layoutPopoutLoader.active = true;
|
||||||
@@ -1244,10 +1211,7 @@ Item {
|
|||||||
barSpacing: barConfig?.spacing ?? 4
|
barSpacing: barConfig?.spacing ?? 4
|
||||||
barConfig: topBarContent.barConfig
|
barConfig: topBarContent.barConfig
|
||||||
isAutoHideBar: topBarContent.barConfig?.autoHide ?? false
|
isAutoHideBar: topBarContent.barConfig?.autoHide ?? false
|
||||||
popoutTarget: {
|
popoutTarget: vpnPopoutLoader.item ?? null
|
||||||
vpnPopoutLoader.active = true;
|
|
||||||
return vpnPopoutLoader.item;
|
|
||||||
}
|
|
||||||
parentScreen: barWindow.screen
|
parentScreen: barWindow.screen
|
||||||
onToggleVpnPopup: {
|
onToggleVpnPopup: {
|
||||||
vpnPopoutLoader.active = true;
|
vpnPopoutLoader.active = true;
|
||||||
@@ -1281,10 +1245,7 @@ Item {
|
|||||||
barThickness: barWindow.effectiveBarThickness
|
barThickness: barWindow.effectiveBarThickness
|
||||||
axis: barWindow.axis
|
axis: barWindow.axis
|
||||||
section: topBarContent.getWidgetSection(parent) || "right"
|
section: topBarContent.getWidgetSection(parent) || "right"
|
||||||
popoutTarget: {
|
popoutTarget: controlCenterLoader.item ?? null
|
||||||
controlCenterLoader.active = true;
|
|
||||||
return controlCenterLoader.item;
|
|
||||||
}
|
|
||||||
parentScreen: barWindow.screen
|
parentScreen: barWindow.screen
|
||||||
screenName: barWindow.screen?.name || ""
|
screenName: barWindow.screen?.name || ""
|
||||||
screenModel: barWindow.screen?.model || ""
|
screenModel: barWindow.screen?.model || ""
|
||||||
@@ -1434,10 +1395,7 @@ Item {
|
|||||||
barThickness: barWindow.effectiveBarThickness
|
barThickness: barWindow.effectiveBarThickness
|
||||||
axis: barWindow.axis
|
axis: barWindow.axis
|
||||||
section: topBarContent.getWidgetSection(parent) || "right"
|
section: topBarContent.getWidgetSection(parent) || "right"
|
||||||
popoutTarget: {
|
popoutTarget: systemUpdateLoader.item ?? null
|
||||||
systemUpdateLoader.active = true;
|
|
||||||
return systemUpdateLoader.item;
|
|
||||||
}
|
|
||||||
parentScreen: barWindow.screen
|
parentScreen: barWindow.screen
|
||||||
onClicked: {
|
onClicked: {
|
||||||
systemUpdateLoader.active = true;
|
systemUpdateLoader.active = true;
|
||||||
|
|||||||
@@ -48,10 +48,11 @@ PanelWindow {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let section = "center";
|
||||||
if (clockButtonRef && clockButtonRef.visualContent && dankDashPopoutLoader.item.setTriggerPosition) {
|
if (clockButtonRef && clockButtonRef.visualContent && dankDashPopoutLoader.item.setTriggerPosition) {
|
||||||
// Calculate barPosition from axis.edge
|
// Calculate barPosition from axis.edge
|
||||||
const barPosition = axis?.edge === "left" ? 2 : (axis?.edge === "right" ? 3 : (axis?.edge === "top" ? 0 : 1));
|
const barPosition = axis?.edge === "left" ? 2 : (axis?.edge === "right" ? 3 : (axis?.edge === "top" ? 0 : 1));
|
||||||
const section = clockButtonRef.section || "center";
|
section = clockButtonRef.section || "center";
|
||||||
|
|
||||||
let triggerPos, triggerWidth;
|
let triggerPos, triggerWidth;
|
||||||
if (section === "center") {
|
if (section === "center") {
|
||||||
@@ -130,38 +131,55 @@ PanelWindow {
|
|||||||
readonly property real _wingR: Math.max(0, wingtipsRadius)
|
readonly property real _wingR: Math.max(0, wingtipsRadius)
|
||||||
readonly property color _surfaceContainer: Theme.surfaceContainer
|
readonly property color _surfaceContainer: Theme.surfaceContainer
|
||||||
readonly property string _barId: barConfig?.id ?? "default"
|
readonly property string _barId: barConfig?.id ?? "default"
|
||||||
readonly property var _liveBarConfig: SettingsData.barConfigs.find(c => c.id === _barId) || barConfig
|
property real _backgroundAlpha: barConfig?.transparency ?? 1.0
|
||||||
readonly property real _backgroundAlpha: _liveBarConfig?.transparency ?? 1.0
|
|
||||||
readonly property color _bgColor: Theme.withAlpha(_surfaceContainer, _backgroundAlpha)
|
readonly property color _bgColor: Theme.withAlpha(_surfaceContainer, _backgroundAlpha)
|
||||||
|
|
||||||
|
function _updateBackgroundAlpha() {
|
||||||
|
const live = SettingsData.barConfigs.find(c => c.id === _barId);
|
||||||
|
_backgroundAlpha = (live ?? barConfig)?.transparency ?? 1.0;
|
||||||
|
}
|
||||||
readonly property real _dpr: CompositorService.getScreenScale(barWindow.screen)
|
readonly property real _dpr: CompositorService.getScreenScale(barWindow.screen)
|
||||||
|
|
||||||
property string screenName: modelData.name
|
property string screenName: modelData.name
|
||||||
|
|
||||||
readonly property bool hasMaximizedToplevel: {
|
property bool hasMaximizedToplevel: false
|
||||||
if (!(barConfig?.maximizeDetection ?? true))
|
property bool shouldHideForWindows: false
|
||||||
return false;
|
|
||||||
if (!CompositorService.isHyprland && !CompositorService.isNiri)
|
function _updateHasMaximizedToplevel() {
|
||||||
return false;
|
if (!(barConfig?.maximizeDetection ?? true)) {
|
||||||
|
hasMaximizedToplevel = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!CompositorService.isHyprland && !CompositorService.isNiri) {
|
||||||
|
hasMaximizedToplevel = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const filtered = CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, screenName);
|
const filtered = CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, screenName);
|
||||||
for (let i = 0; i < filtered.length; i++) {
|
for (let i = 0; i < filtered.length; i++) {
|
||||||
if (filtered[i]?.maximized)
|
if (filtered[i]?.maximized) {
|
||||||
return true;
|
hasMaximizedToplevel = true;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return false;
|
}
|
||||||
|
hasMaximizedToplevel = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property bool shouldHideForWindows: {
|
function _updateShouldHideForWindows() {
|
||||||
if (!(barConfig?.showOnWindowsOpen ?? false))
|
if (!(barConfig?.showOnWindowsOpen ?? false)) {
|
||||||
return false;
|
shouldHideForWindows = false;
|
||||||
if (!(barConfig?.autoHide ?? false))
|
return;
|
||||||
return false;
|
}
|
||||||
if (!CompositorService.isNiri && !CompositorService.isHyprland)
|
if (!(barConfig?.autoHide ?? false)) {
|
||||||
return false;
|
shouldHideForWindows = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!CompositorService.isNiri && !CompositorService.isHyprland) {
|
||||||
|
shouldHideForWindows = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (CompositorService.isNiri) {
|
if (CompositorService.isNiri) {
|
||||||
NiriService.windows;
|
|
||||||
|
|
||||||
let currentWorkspaceId = null;
|
let currentWorkspaceId = null;
|
||||||
for (let i = 0; i < NiriService.allWorkspaces.length; i++) {
|
for (let i = 0; i < NiriService.allWorkspaces.length; i++) {
|
||||||
const ws = NiriService.allWorkspaces[i];
|
const ws = NiriService.allWorkspaces[i];
|
||||||
@@ -171,8 +189,10 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentWorkspaceId === null)
|
if (currentWorkspaceId === null) {
|
||||||
return false;
|
shouldHideForWindows = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let hasTiled = false;
|
let hasTiled = false;
|
||||||
let hasFloatingTouchingBar = false;
|
let hasFloatingTouchingBar = false;
|
||||||
@@ -216,14 +236,12 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasTiled)
|
shouldHideForWindows = hasTiled || hasFloatingTouchingBar;
|
||||||
return true;
|
return;
|
||||||
|
|
||||||
return hasFloatingTouchingBar;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const filtered = CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, screenName);
|
const filtered = CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, screenName);
|
||||||
return filtered.length > 0;
|
shouldHideForWindows = filtered.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
property real effectiveSpacing: hasMaximizedToplevel ? 0 : (barConfig?.spacing ?? 4)
|
property real effectiveSpacing: hasMaximizedToplevel ? 0 : (barConfig?.spacing ?? 4)
|
||||||
@@ -257,7 +275,7 @@ PanelWindow {
|
|||||||
const onThisScreen = bc.screenPreferences.includes(screenName) || bc.screenPreferences.length === 0 || bc.screenPreferences.includes("all");
|
const onThisScreen = bc.screenPreferences.includes(screenName) || bc.screenPreferences.length === 0 || bc.screenPreferences.includes("all");
|
||||||
if (!onThisScreen)
|
if (!onThisScreen)
|
||||||
return false;
|
return false;
|
||||||
if (bc.showOnLastDisplay && screenName !== barWindow.screen.name)
|
if (bc.showOnLastDisplay && screenName !== barWindow.screenName)
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@@ -280,7 +298,7 @@ PanelWindow {
|
|||||||
const onThisScreen = bc.screenPreferences.includes(screenName) || bc.screenPreferences.length === 0 || bc.screenPreferences.includes("all");
|
const onThisScreen = bc.screenPreferences.includes(screenName) || bc.screenPreferences.length === 0 || bc.screenPreferences.includes("all");
|
||||||
if (!onThisScreen)
|
if (!onThisScreen)
|
||||||
return false;
|
return false;
|
||||||
if (bc.showOnLastDisplay && screenName !== barWindow.screen.name)
|
if (bc.showOnLastDisplay && screenName !== barWindow.screenName)
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@@ -304,7 +322,7 @@ PanelWindow {
|
|||||||
const onThisScreen = bc.screenPreferences.includes(screenName) || bc.screenPreferences.length === 0 || bc.screenPreferences.includes("all");
|
const onThisScreen = bc.screenPreferences.includes(screenName) || bc.screenPreferences.length === 0 || bc.screenPreferences.includes("all");
|
||||||
if (!onThisScreen)
|
if (!onThisScreen)
|
||||||
return false;
|
return false;
|
||||||
if (bc.showOnLastDisplay && screenName !== barWindow.screen.name)
|
if (bc.showOnLastDisplay && screenName !== barWindow.screenName)
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@@ -328,7 +346,7 @@ PanelWindow {
|
|||||||
const onThisScreen = bc.screenPreferences.includes(screenName) || bc.screenPreferences.length === 0 || bc.screenPreferences.includes("all");
|
const onThisScreen = bc.screenPreferences.includes(screenName) || bc.screenPreferences.length === 0 || bc.screenPreferences.includes("all");
|
||||||
if (!onThisScreen)
|
if (!onThisScreen)
|
||||||
return false;
|
return false;
|
||||||
if (bc.showOnLastDisplay && screenName !== barWindow.screen.name)
|
if (bc.showOnLastDisplay && screenName !== barWindow.screenName)
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@@ -355,6 +373,9 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateGpuTempConfig();
|
updateGpuTempConfig();
|
||||||
|
_updateBackgroundAlpha();
|
||||||
|
_updateHasMaximizedToplevel();
|
||||||
|
_updateShouldHideForWindows();
|
||||||
|
|
||||||
inhibitorInitTimer.start();
|
inhibitorInitTimer.start();
|
||||||
}
|
}
|
||||||
@@ -431,11 +452,37 @@ PanelWindow {
|
|||||||
Connections {
|
Connections {
|
||||||
function onBarConfigChanged() {
|
function onBarConfigChanged() {
|
||||||
barWindow.updateGpuTempConfig();
|
barWindow.updateGpuTempConfig();
|
||||||
|
barWindow._updateBackgroundAlpha();
|
||||||
|
barWindow._updateHasMaximizedToplevel();
|
||||||
|
barWindow._updateShouldHideForWindows();
|
||||||
}
|
}
|
||||||
|
|
||||||
target: rootWindow
|
target: rootWindow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: SettingsData
|
||||||
|
function onBarConfigsChanged() {
|
||||||
|
barWindow._updateBackgroundAlpha();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: CompositorService
|
||||||
|
function onToplevelsChanged() {
|
||||||
|
barWindow._updateHasMaximizedToplevel();
|
||||||
|
barWindow._updateShouldHideForWindows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: NiriService
|
||||||
|
function onAllWorkspacesChanged() {
|
||||||
|
barWindow._updateHasMaximizedToplevel();
|
||||||
|
barWindow._updateShouldHideForWindows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
function onNvidiaGpuTempEnabledChanged() {
|
function onNvidiaGpuTempEnabledChanged() {
|
||||||
barWindow.updateGpuTempConfig();
|
barWindow.updateGpuTempConfig();
|
||||||
@@ -505,8 +552,9 @@ PanelWindow {
|
|||||||
readonly property var _leftSection: topBarContent ? (barWindow.isVertical ? topBarContent.vLeftSection : topBarContent.hLeftSection) : null
|
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 _centerSection: topBarContent ? (barWindow.isVertical ? topBarContent.vCenterSection : topBarContent.hCenterSection) : null
|
||||||
readonly property var _rightSection: topBarContent ? (barWindow.isVertical ? topBarContent.vRightSection : topBarContent.hRightSection) : 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)
|
if (!section)
|
||||||
return {
|
return {
|
||||||
"x": 0,
|
"x": 0,
|
||||||
@@ -535,7 +583,7 @@ PanelWindow {
|
|||||||
item: clickThroughEnabled ? null : inputMask
|
item: clickThroughEnabled ? null : inputMask
|
||||||
|
|
||||||
Region {
|
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,
|
"x": 0,
|
||||||
"y": 0,
|
"y": 0,
|
||||||
"w": 0,
|
"w": 0,
|
||||||
@@ -548,7 +596,7 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Region {
|
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,
|
"x": 0,
|
||||||
"y": 0,
|
"y": 0,
|
||||||
"w": 0,
|
"w": 0,
|
||||||
@@ -561,7 +609,7 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Region {
|
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,
|
"x": 0,
|
||||||
"y": 0,
|
"y": 0,
|
||||||
"w": 0,
|
"w": 0,
|
||||||
@@ -572,6 +620,14 @@ PanelWindow {
|
|||||||
width: r.w
|
width: r.w
|
||||||
height: r.h
|
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 {
|
Item {
|
||||||
@@ -584,7 +640,7 @@ PanelWindow {
|
|||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: revealHold
|
id: revealHold
|
||||||
interval: barConfig?.autoHideDelay ?? 250
|
interval: barWindow.clickThroughEnabled ? Math.max((barConfig?.autoHideDelay ?? 250) * 6, 1500) : (barConfig?.autoHideDelay ?? 250)
|
||||||
repeat: false
|
repeat: false
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (!topBarMouseArea.containsMouse && !topBarCore.hasActivePopout) {
|
if (!topBarMouseArea.containsMouse && !topBarCore.hasActivePopout) {
|
||||||
@@ -616,6 +672,7 @@ PanelWindow {
|
|||||||
onHasActivePopoutChanged: evaluateReveal()
|
onHasActivePopoutChanged: evaluateReveal()
|
||||||
|
|
||||||
function updateActivePopoutState() {
|
function updateActivePopoutState() {
|
||||||
|
if (!barWindow.screen) return;
|
||||||
const screenName = barWindow.screen.name;
|
const screenName = barWindow.screen.name;
|
||||||
const activePopout = PopoutManager.currentPopoutsByScreen[screenName];
|
const activePopout = PopoutManager.currentPopoutsByScreen[screenName];
|
||||||
const activeTrayMenu = TrayMenuManager.activeTrayMenus[screenName];
|
const activeTrayMenu = TrayMenuManager.activeTrayMenus[screenName];
|
||||||
@@ -642,7 +699,6 @@ PanelWindow {
|
|||||||
Connections {
|
Connections {
|
||||||
function onBarConfigChanged() {
|
function onBarConfigChanged() {
|
||||||
topBarCore.autoHide = barConfig?.autoHide ?? false;
|
topBarCore.autoHide = barConfig?.autoHide ?? false;
|
||||||
revealHold.interval = barConfig?.autoHideDelay ?? 250;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
target: rootWindow
|
target: rootWindow
|
||||||
|
|||||||
@@ -400,7 +400,7 @@ BasePill {
|
|||||||
Component.onCompleted: updateModel()
|
Component.onCompleted: updateModel()
|
||||||
|
|
||||||
visible: dockItems.length > 0
|
visible: dockItems.length > 0
|
||||||
readonly property real iconCellSize: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground) + 6
|
readonly property real iconCellSize: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale) + 6
|
||||||
|
|
||||||
content: Component {
|
content: Component {
|
||||||
Item {
|
Item {
|
||||||
@@ -584,8 +584,7 @@ BasePill {
|
|||||||
|
|
||||||
property string tooltipText: {
|
property string tooltipText: {
|
||||||
root._desktopEntriesUpdateTrigger;
|
root._desktopEntriesUpdateTrigger;
|
||||||
const moddedId = Paths.moddedAppId(appId);
|
const desktopEntry = appId ? DesktopEntries.heuristicLookup(appId) : null;
|
||||||
const desktopEntry = moddedId ? DesktopEntries.heuristicLookup(moddedId) : null;
|
|
||||||
const appName = appId ? Paths.getAppName(appId, desktopEntry) : "Unknown";
|
const appName = appId ? Paths.getAppName(appId, desktopEntry) : "Unknown";
|
||||||
|
|
||||||
if (modelData.type === "grouped" && windowCount > 1) {
|
if (modelData.type === "grouped" && windowCount > 1) {
|
||||||
@@ -597,7 +596,7 @@ BasePill {
|
|||||||
readonly property bool enlargeEnabled: (widgetData?.appsDockEnlargeOnHover !== undefined ? widgetData.appsDockEnlargeOnHover : SettingsData.appsDockEnlargeOnHover)
|
readonly property bool enlargeEnabled: (widgetData?.appsDockEnlargeOnHover !== undefined ? widgetData.appsDockEnlargeOnHover : SettingsData.appsDockEnlargeOnHover)
|
||||||
readonly property real enlargeScale: enlargeEnabled && mouseArea.containsMouse ? (widgetData?.appsDockEnlargePercentage !== undefined ? widgetData.appsDockEnlargePercentage : SettingsData.appsDockEnlargePercentage) / 100.0 : 1.0
|
readonly property real enlargeScale: enlargeEnabled && mouseArea.containsMouse ? (widgetData?.appsDockEnlargePercentage !== undefined ? widgetData.appsDockEnlargePercentage : SettingsData.appsDockEnlargePercentage) / 100.0 : 1.0
|
||||||
readonly property real baseIconSizeMultiplier: (widgetData?.appsDockIconSizePercentage !== undefined ? widgetData.appsDockIconSizePercentage : SettingsData.appsDockIconSizePercentage) / 100.0
|
readonly property real baseIconSizeMultiplier: (widgetData?.appsDockIconSizePercentage !== undefined ? widgetData.appsDockIconSizePercentage : SettingsData.appsDockIconSizePercentage) / 100.0
|
||||||
readonly property real effectiveIconSize: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground) * baseIconSizeMultiplier
|
readonly property real effectiveIconSize: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale) * baseIconSizeMultiplier
|
||||||
|
|
||||||
readonly property color activeOverlayColor: {
|
readonly property color activeOverlayColor: {
|
||||||
switch (SettingsData.appsDockActiveColorMode) {
|
switch (SettingsData.appsDockActiveColorMode) {
|
||||||
@@ -690,9 +689,8 @@ BasePill {
|
|||||||
if (!appItem.appId)
|
if (!appItem.appId)
|
||||||
return "";
|
return "";
|
||||||
if (modelData.isCoreApp)
|
if (modelData.isCoreApp)
|
||||||
return ""; // Explicitly skip if core app to avoid flickering or wrong look ups
|
return "";
|
||||||
const moddedId = Paths.moddedAppId(appItem.appId);
|
const desktopEntry = DesktopEntries.heuristicLookup(appItem.appId);
|
||||||
const desktopEntry = DesktopEntries.heuristicLookup(moddedId);
|
|
||||||
return Paths.getAppIcon(appItem.appId, desktopEntry);
|
return Paths.getAppIcon(appItem.appId, desktopEntry);
|
||||||
}
|
}
|
||||||
smooth: true
|
smooth: true
|
||||||
@@ -749,8 +747,7 @@ BasePill {
|
|||||||
root._desktopEntriesUpdateTrigger;
|
root._desktopEntriesUpdateTrigger;
|
||||||
if (!appItem.appId)
|
if (!appItem.appId)
|
||||||
return "?";
|
return "?";
|
||||||
const moddedId = Paths.moddedAppId(appItem.appId);
|
const desktopEntry = DesktopEntries.heuristicLookup(appItem.appId);
|
||||||
const desktopEntry = DesktopEntries.heuristicLookup(moddedId);
|
|
||||||
const appName = Paths.getAppName(appItem.appId, desktopEntry);
|
const appName = Paths.getAppName(appItem.appId, desktopEntry);
|
||||||
return appName.charAt(0).toUpperCase();
|
return appName.charAt(0).toUpperCase();
|
||||||
}
|
}
|
||||||
@@ -795,7 +792,7 @@ BasePill {
|
|||||||
anchors.rightMargin: Theme.spacingS
|
anchors.rightMargin: Theme.spacingS
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
text: appItem.windowTitle || appItem.appId
|
text: appItem.windowTitle || appItem.appId
|
||||||
font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale)
|
font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale, barConfig?.maximizeWidgetText)
|
||||||
color: Theme.widgetTextColor
|
color: Theme.widgetTextColor
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
maximumLineCount: 1
|
maximumLineCount: 1
|
||||||
|
|||||||
@@ -273,7 +273,7 @@ PanelWindow {
|
|||||||
|
|
||||||
IconImage {
|
IconImage {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
source: modelData.icon ? Quickshell.iconPath(modelData.icon, true) : ""
|
source: modelData.icon ? Paths.resolveIconPath(modelData.icon) : ""
|
||||||
smooth: true
|
smooth: true
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
visible: status === Image.Ready
|
visible: status === Image.Ready
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ BasePill {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: BatteryService.getBatteryIcon()
|
name: BatteryService.getBatteryIcon()
|
||||||
size: Theme.barIconSize(battery.barThickness, undefined, battery.barConfig?.noBackground)
|
size: Theme.barIconSize(battery.barThickness, undefined, battery.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||||
color: {
|
color: {
|
||||||
if (!BatteryService.batteryAvailable) {
|
if (!BatteryService.batteryAvailable) {
|
||||||
return Theme.widgetIconColor;
|
return Theme.widgetIconColor;
|
||||||
@@ -63,7 +63,7 @@ BasePill {
|
|||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: BatteryService.batteryLevel.toString()
|
text: BatteryService.batteryLevel.toString()
|
||||||
font.pixelSize: Theme.barTextSize(battery.barThickness, battery.barConfig?.fontScale)
|
font.pixelSize: Theme.barTextSize(battery.barThickness, battery.barConfig?.fontScale, battery.barConfig?.maximizeWidgetText)
|
||||||
color: Theme.widgetTextColor
|
color: Theme.widgetTextColor
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
visible: BatteryService.batteryAvailable
|
visible: BatteryService.batteryAvailable
|
||||||
@@ -78,7 +78,7 @@ BasePill {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: BatteryService.getBatteryIcon()
|
name: BatteryService.getBatteryIcon()
|
||||||
size: Theme.barIconSize(battery.barThickness, -4, battery.barConfig?.noBackground)
|
size: Theme.barIconSize(battery.barThickness, -4, battery.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||||
color: {
|
color: {
|
||||||
if (!BatteryService.batteryAvailable) {
|
if (!BatteryService.batteryAvailable) {
|
||||||
return Theme.widgetIconColor;
|
return Theme.widgetIconColor;
|
||||||
@@ -99,7 +99,7 @@ BasePill {
|
|||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: `${BatteryService.batteryLevel}%`
|
text: `${BatteryService.batteryLevel}%`
|
||||||
font.pixelSize: Theme.barTextSize(battery.barThickness, battery.barConfig?.fontScale)
|
font.pixelSize: Theme.barTextSize(battery.barThickness, battery.barConfig?.fontScale, battery.barConfig?.maximizeWidgetText)
|
||||||
color: Theme.widgetTextColor
|
color: Theme.widgetTextColor
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: BatteryService.batteryAvailable
|
visible: BatteryService.batteryAvailable
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ BasePill {
|
|||||||
id: icon
|
id: icon
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
name: "shift_lock"
|
name: "shift_lock"
|
||||||
size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||||
color: Theme.primary
|
color: Theme.primary
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,6 @@ BasePill {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool isActive: false
|
property bool isActive: false
|
||||||
property var popoutTarget: null
|
|
||||||
property var parentScreen: null
|
|
||||||
property Item windowRoot: (Window.window ? Window.window.contentItem : null)
|
|
||||||
property bool isAutoHideBar: false
|
property bool isAutoHideBar: false
|
||||||
|
|
||||||
signal clipboardClicked
|
signal clipboardClicked
|
||||||
@@ -87,7 +84,7 @@ BasePill {
|
|||||||
id: icon
|
id: icon
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
name: "content_paste"
|
name: "content_paste"
|
||||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||||
color: Theme.widgetIconColor
|
color: Theme.widgetIconColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ BasePill {
|
|||||||
const display = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours;
|
const display = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours;
|
||||||
return String(display).padStart(2, '0').charAt(0);
|
return String(display).padStart(2, '0').charAt(0);
|
||||||
}
|
}
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
color: Theme.widgetTextColor
|
color: Theme.widgetTextColor
|
||||||
width: Math.round(font.pixelSize * 0.6)
|
width: Math.round(font.pixelSize * 0.6)
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
@@ -53,7 +53,7 @@ BasePill {
|
|||||||
const display = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours;
|
const display = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours;
|
||||||
return String(display).padStart(2, '0').charAt(1);
|
return String(display).padStart(2, '0').charAt(1);
|
||||||
}
|
}
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
color: Theme.widgetTextColor
|
color: Theme.widgetTextColor
|
||||||
width: Math.round(font.pixelSize * 0.6)
|
width: Math.round(font.pixelSize * 0.6)
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
@@ -67,7 +67,7 @@ BasePill {
|
|||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: String(systemClock?.date?.getMinutes()).padStart(2, '0').charAt(0)
|
text: String(systemClock?.date?.getMinutes()).padStart(2, '0').charAt(0)
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
color: Theme.widgetTextColor
|
color: Theme.widgetTextColor
|
||||||
width: Math.round(font.pixelSize * 0.6)
|
width: Math.round(font.pixelSize * 0.6)
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
@@ -76,7 +76,7 @@ BasePill {
|
|||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: String(systemClock?.date?.getMinutes()).padStart(2, '0').charAt(1)
|
text: String(systemClock?.date?.getMinutes()).padStart(2, '0').charAt(1)
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
color: Theme.widgetTextColor
|
color: Theme.widgetTextColor
|
||||||
width: Math.round(font.pixelSize * 0.6)
|
width: Math.round(font.pixelSize * 0.6)
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
@@ -91,7 +91,7 @@ BasePill {
|
|||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: String(systemClock?.date?.getSeconds()).padStart(2, '0').charAt(0)
|
text: String(systemClock?.date?.getSeconds()).padStart(2, '0').charAt(0)
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
color: Theme.widgetTextColor
|
color: Theme.widgetTextColor
|
||||||
width: Math.round(font.pixelSize * 0.6)
|
width: Math.round(font.pixelSize * 0.6)
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
@@ -100,7 +100,7 @@ BasePill {
|
|||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: String(systemClock?.date?.getSeconds()).padStart(2, '0').charAt(1)
|
text: String(systemClock?.date?.getSeconds()).padStart(2, '0').charAt(1)
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
color: Theme.widgetTextColor
|
color: Theme.widgetTextColor
|
||||||
width: Math.round(font.pixelSize * 0.6)
|
width: Math.round(font.pixelSize * 0.6)
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
@@ -135,7 +135,7 @@ BasePill {
|
|||||||
const value = dayFirst ? String(systemClock?.date?.getDate()).padStart(2, '0') : String(systemClock?.date?.getMonth() + 1).padStart(2, '0');
|
const value = dayFirst ? String(systemClock?.date?.getDate()).padStart(2, '0') : String(systemClock?.date?.getMonth() + 1).padStart(2, '0');
|
||||||
return value.charAt(0);
|
return value.charAt(0);
|
||||||
}
|
}
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
color: Theme.primary
|
color: Theme.primary
|
||||||
width: Math.round(font.pixelSize * 0.6)
|
width: Math.round(font.pixelSize * 0.6)
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
@@ -150,7 +150,7 @@ BasePill {
|
|||||||
const value = dayFirst ? String(systemClock?.date?.getDate()).padStart(2, '0') : String(systemClock?.date?.getMonth() + 1).padStart(2, '0');
|
const value = dayFirst ? String(systemClock?.date?.getDate()).padStart(2, '0') : String(systemClock?.date?.getMonth() + 1).padStart(2, '0');
|
||||||
return value.charAt(1);
|
return value.charAt(1);
|
||||||
}
|
}
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
color: Theme.primary
|
color: Theme.primary
|
||||||
width: Math.round(font.pixelSize * 0.6)
|
width: Math.round(font.pixelSize * 0.6)
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
@@ -171,7 +171,7 @@ BasePill {
|
|||||||
const value = dayFirst ? String(systemClock?.date?.getMonth() + 1).padStart(2, '0') : String(systemClock?.date?.getDate()).padStart(2, '0');
|
const value = dayFirst ? String(systemClock?.date?.getMonth() + 1).padStart(2, '0') : String(systemClock?.date?.getDate()).padStart(2, '0');
|
||||||
return value.charAt(0);
|
return value.charAt(0);
|
||||||
}
|
}
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
color: Theme.primary
|
color: Theme.primary
|
||||||
width: Math.round(font.pixelSize * 0.6)
|
width: Math.round(font.pixelSize * 0.6)
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
@@ -186,7 +186,7 @@ BasePill {
|
|||||||
const value = dayFirst ? String(systemClock?.date?.getMonth() + 1).padStart(2, '0') : String(systemClock?.date?.getDate()).padStart(2, '0');
|
const value = dayFirst ? String(systemClock?.date?.getMonth() + 1).padStart(2, '0') : String(systemClock?.date?.getDate()).padStart(2, '0');
|
||||||
return value.charAt(1);
|
return value.charAt(1);
|
||||||
}
|
}
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
color: Theme.primary
|
color: Theme.primary
|
||||||
width: Math.round(font.pixelSize * 0.6)
|
width: Math.round(font.pixelSize * 0.6)
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
@@ -201,7 +201,7 @@ BasePill {
|
|||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
property real fontSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
|
property real fontSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
property real digitWidth: fontSize * 0.6
|
property real digitWidth: fontSize * 0.6
|
||||||
|
|
||||||
property string hoursStr: {
|
property string hoursStr: {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ BasePill {
|
|||||||
id: icon
|
id: icon
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
name: "palette"
|
name: "palette"
|
||||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||||
color: root.isActive ? Theme.primary : Theme.surfaceText
|
color: root.isActive ? Theme.primary : Theme.surfaceText
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ BasePill {
|
|||||||
property real micAccumulator: 0
|
property real micAccumulator: 0
|
||||||
property real volumeAccumulator: 0
|
property real volumeAccumulator: 0
|
||||||
property real brightnessAccumulator: 0
|
property real brightnessAccumulator: 0
|
||||||
readonly property real vIconSize: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
readonly property real vIconSize: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||||
property var _hRow: null
|
property var _hRow: null
|
||||||
property var _vCol: null
|
property var _vCol: null
|
||||||
property var _hAudio: null
|
property var _hAudio: null
|
||||||
@@ -378,7 +378,7 @@ BasePill {
|
|||||||
|
|
||||||
Item {
|
Item {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: root.vIconSize + (root.showAudioPercent ? audioPercentV.implicitHeight + 2 : 0)
|
height: root.vIconSize + (audioPercentV.visible ? audioPercentV.implicitHeight + 2 : 0)
|
||||||
visible: root.showAudioIcon
|
visible: root.showAudioIcon
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
@@ -392,9 +392,9 @@ BasePill {
|
|||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
id: audioPercentV
|
id: audioPercentV
|
||||||
visible: root.showAudioPercent
|
visible: root.showAudioPercent && isFinite(AudioService.sink?.audio?.volume)
|
||||||
text: Math.round((AudioService.sink?.audio?.volume ?? 0) * 100) + "%"
|
text: Math.round((AudioService.sink?.audio?.volume ?? 0) * 100) + "%"
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
color: Theme.widgetTextColor
|
color: Theme.widgetTextColor
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.top: audioIconV.bottom
|
anchors.top: audioIconV.bottom
|
||||||
@@ -404,7 +404,7 @@ BasePill {
|
|||||||
|
|
||||||
Item {
|
Item {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: root.vIconSize + (root.showMicPercent ? micPercentV.implicitHeight + 2 : 0)
|
height: root.vIconSize + (micPercentV.visible ? micPercentV.implicitHeight + 2 : 0)
|
||||||
visible: root.showMicIcon
|
visible: root.showMicIcon
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
@@ -418,9 +418,9 @@ BasePill {
|
|||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
id: micPercentV
|
id: micPercentV
|
||||||
visible: root.showMicPercent
|
visible: root.showMicPercent && isFinite(AudioService.source?.audio?.volume)
|
||||||
text: Math.round((AudioService.source?.audio?.volume ?? 0) * 100) + "%"
|
text: Math.round((AudioService.source?.audio?.volume ?? 0) * 100) + "%"
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
color: Theme.widgetTextColor
|
color: Theme.widgetTextColor
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.top: micIconV.bottom
|
anchors.top: micIconV.bottom
|
||||||
@@ -430,7 +430,7 @@ BasePill {
|
|||||||
|
|
||||||
Item {
|
Item {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: root.vIconSize + (root.showBrightnessPercent ? brightnessPercentV.implicitHeight + 2 : 0)
|
height: root.vIconSize + (brightnessPercentV.visible ? brightnessPercentV.implicitHeight + 2 : 0)
|
||||||
visible: root.showBrightnessIcon && DisplayService.brightnessAvailable && root.hasPinnedBrightnessDevice()
|
visible: root.showBrightnessIcon && DisplayService.brightnessAvailable && root.hasPinnedBrightnessDevice()
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
@@ -444,9 +444,9 @@ BasePill {
|
|||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
id: brightnessPercentV
|
id: brightnessPercentV
|
||||||
visible: root.showBrightnessPercent
|
visible: root.showBrightnessPercent && isFinite(getBrightness())
|
||||||
text: Math.round(getBrightness() * 100) + "%"
|
text: Math.round(getBrightness() * 100) + "%"
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
color: Theme.widgetTextColor
|
color: Theme.widgetTextColor
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.top: brightnessIconV.bottom
|
anchors.top: brightnessIconV.bottom
|
||||||
@@ -502,7 +502,7 @@ BasePill {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: "screen_record"
|
name: "screen_record"
|
||||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||||
color: NiriService.hasActiveCast ? Theme.primary : Theme.surfaceText
|
color: NiriService.hasActiveCast ? Theme.primary : Theme.surfaceText
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: root.showScreenSharingIcon && NiriService.hasCasts
|
visible: root.showScreenSharingIcon && NiriService.hasCasts
|
||||||
@@ -511,7 +511,7 @@ BasePill {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
id: networkIcon
|
id: networkIcon
|
||||||
name: root.getNetworkIconName()
|
name: root.getNetworkIconName()
|
||||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||||
color: root.getNetworkIconColor()
|
color: root.getNetworkIconColor()
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: root.showNetworkIcon && NetworkService.networkAvailable
|
visible: root.showNetworkIcon && NetworkService.networkAvailable
|
||||||
@@ -520,7 +520,7 @@ BasePill {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
id: vpnIcon
|
id: vpnIcon
|
||||||
name: "vpn_lock"
|
name: "vpn_lock"
|
||||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||||
color: NetworkService.vpnConnected ? Theme.primary : Theme.surfaceText
|
color: NetworkService.vpnConnected ? Theme.primary : Theme.surfaceText
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: root.showVpnIcon && NetworkService.vpnAvailable && NetworkService.vpnConnected
|
visible: root.showVpnIcon && NetworkService.vpnAvailable && NetworkService.vpnConnected
|
||||||
@@ -529,7 +529,7 @@ BasePill {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
id: bluetoothIcon
|
id: bluetoothIcon
|
||||||
name: "bluetooth"
|
name: "bluetooth"
|
||||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||||
color: BluetoothService.connected ? Theme.primary : Theme.surfaceText
|
color: BluetoothService.connected ? Theme.primary : Theme.surfaceText
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
|
visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
|
||||||
@@ -545,7 +545,7 @@ BasePill {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
id: audioIcon
|
id: audioIcon
|
||||||
name: root.getVolumeIconName()
|
name: root.getVolumeIconName()
|
||||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||||
color: Theme.widgetIconColor
|
color: Theme.widgetIconColor
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
@@ -554,9 +554,9 @@ BasePill {
|
|||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
id: audioPercent
|
id: audioPercent
|
||||||
visible: root.showAudioPercent
|
visible: root.showAudioPercent && isFinite(AudioService.sink?.audio?.volume)
|
||||||
text: Math.round((AudioService.sink?.audio?.volume ?? 0) * 100) + "%"
|
text: Math.round((AudioService.sink?.audio?.volume ?? 0) * 100) + "%"
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
color: Theme.widgetTextColor
|
color: Theme.widgetTextColor
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.left: audioIcon.right
|
anchors.left: audioIcon.right
|
||||||
@@ -574,7 +574,7 @@ BasePill {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
id: micIcon
|
id: micIcon
|
||||||
name: root.getMicIconName()
|
name: root.getMicIconName()
|
||||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||||
color: root.getMicIconColor()
|
color: root.getMicIconColor()
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
@@ -583,9 +583,9 @@ BasePill {
|
|||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
id: micPercent
|
id: micPercent
|
||||||
visible: root.showMicPercent
|
visible: root.showMicPercent && isFinite(AudioService.source?.audio?.volume)
|
||||||
text: Math.round((AudioService.source?.audio?.volume ?? 0) * 100) + "%"
|
text: Math.round((AudioService.source?.audio?.volume ?? 0) * 100) + "%"
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
color: Theme.widgetTextColor
|
color: Theme.widgetTextColor
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.left: micIcon.right
|
anchors.left: micIcon.right
|
||||||
@@ -603,7 +603,7 @@ BasePill {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
id: brightnessIcon
|
id: brightnessIcon
|
||||||
name: root.getBrightnessIconName()
|
name: root.getBrightnessIconName()
|
||||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||||
color: Theme.widgetIconColor
|
color: Theme.widgetIconColor
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
@@ -612,9 +612,9 @@ BasePill {
|
|||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
id: brightnessPercent
|
id: brightnessPercent
|
||||||
visible: root.showBrightnessPercent
|
visible: root.showBrightnessPercent && isFinite(getBrightness())
|
||||||
text: Math.round(getBrightness() * 100) + "%"
|
text: Math.round(getBrightness() * 100) + "%"
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
color: Theme.widgetTextColor
|
color: Theme.widgetTextColor
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.left: brightnessIcon.right
|
anchors.left: brightnessIcon.right
|
||||||
@@ -625,7 +625,7 @@ BasePill {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
id: batteryIcon
|
id: batteryIcon
|
||||||
name: Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable)
|
name: Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable)
|
||||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||||
color: root.getBatteryIconColor()
|
color: root.getBatteryIconColor()
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: root.showBatteryIcon && BatteryService.batteryAvailable
|
visible: root.showBatteryIcon && BatteryService.batteryAvailable
|
||||||
@@ -634,7 +634,7 @@ BasePill {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
id: printerIcon
|
id: printerIcon
|
||||||
name: "print"
|
name: "print"
|
||||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||||
color: Theme.primary
|
color: Theme.primary
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: root.showPrinterIcon && CupsService.cupsAvailable && root.hasPrintJobs()
|
visible: root.showPrinterIcon && CupsService.cupsAvailable && root.hasPrintJobs()
|
||||||
@@ -642,7 +642,7 @@ BasePill {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: "settings"
|
name: "settings"
|
||||||
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.noBackground)
|
size: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||||
color: root.isActive ? Theme.primary : Theme.widgetIconColor
|
color: root.isActive ? Theme.primary : Theme.widgetIconColor
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: root.hasNoVisibleIcons()
|
visible: root.hasNoVisibleIcons()
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ BasePill {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: "memory"
|
name: "memory"
|
||||||
size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||||
color: {
|
color: {
|
||||||
if (DgopService.cpuUsage > 80) {
|
if (DgopService.cpuUsage > 80) {
|
||||||
return Theme.tempDanger;
|
return Theme.tempDanger;
|
||||||
@@ -59,7 +59,7 @@ BasePill {
|
|||||||
|
|
||||||
return DgopService.cpuUsage.toFixed(0);
|
return DgopService.cpuUsage.toFixed(0);
|
||||||
}
|
}
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
color: Theme.widgetTextColor
|
color: Theme.widgetTextColor
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
}
|
}
|
||||||
@@ -74,7 +74,7 @@ BasePill {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
id: cpuIcon
|
id: cpuIcon
|
||||||
name: "memory"
|
name: "memory"
|
||||||
size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.noBackground)
|
size: Theme.barIconSize(root.barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
||||||
color: {
|
color: {
|
||||||
if (DgopService.cpuUsage > 80) {
|
if (DgopService.cpuUsage > 80) {
|
||||||
return Theme.tempDanger;
|
return Theme.tempDanger;
|
||||||
@@ -93,7 +93,7 @@ BasePill {
|
|||||||
id: textBox
|
id: textBox
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
implicitWidth: root.minimumWidth ? Math.max(cpuBaseline.width, cpuText.paintedWidth) : cpuText.paintedWidth
|
implicitWidth: root.minimumWidth ? Math.max(cpuBaseline.width, cpuCurrent.width) : cpuCurrent.width
|
||||||
implicitHeight: cpuText.implicitHeight
|
implicitHeight: cpuText.implicitHeight
|
||||||
|
|
||||||
width: implicitWidth
|
width: implicitWidth
|
||||||
@@ -101,10 +101,16 @@ BasePill {
|
|||||||
|
|
||||||
StyledTextMetrics {
|
StyledTextMetrics {
|
||||||
id: cpuBaseline
|
id: cpuBaseline
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
text: "88%"
|
text: "88%"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StyledTextMetrics {
|
||||||
|
id: cpuCurrent
|
||||||
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
|
text: cpuText.text
|
||||||
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
id: cpuText
|
id: cpuText
|
||||||
text: {
|
text: {
|
||||||
@@ -114,7 +120,7 @@ BasePill {
|
|||||||
}
|
}
|
||||||
return v.toFixed(0) + "%";
|
return v.toFixed(0) + "%";
|
||||||
}
|
}
|
||||||
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
||||||
color: Theme.widgetTextColor
|
color: Theme.widgetTextColor
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user