mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-05 21:15:38 -05:00
Compare commits
128 Commits
v0.6.1
...
9c887fbe63
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c887fbe63 | ||
|
|
4723bffcd2 | ||
|
|
9643de3ca0 | ||
|
|
3bf3a54916 | ||
|
|
bcffc8856a | ||
|
|
6b8c35c27b | ||
|
|
dd409b4d1c | ||
|
|
94a1aebe2b | ||
|
|
d3030c3ec6 | ||
|
|
0221021078 | ||
|
|
966021bfd4 | ||
|
|
f06e6e85d5 | ||
|
|
28ad641070 | ||
|
|
384c775f1a | ||
|
|
ce40c691e9 | ||
|
|
5b0c38b0ed | ||
|
|
734456785f | ||
|
|
4f24312432 | ||
|
|
d79b1ff3b4 | ||
|
|
bbe1c1f1e0 | ||
|
|
1978e67401 | ||
|
|
e129e4a2d0 | ||
|
|
f7f1bbbdd2 | ||
|
|
de8f2e6a68 | ||
|
|
85704e3947 | ||
|
|
4d661ff41d | ||
|
|
d7b39634e6 | ||
|
|
039c98b9e3 | ||
|
|
172c4bf0a9 | ||
|
|
1f2a1c5dec | ||
|
|
e5a6a00282 | ||
|
|
d8153f7611 | ||
|
|
8b6ae3f39b | ||
|
|
24537781b7 | ||
|
|
d2a29506aa | ||
|
|
adf51d5264 | ||
|
|
0864179085 | ||
|
|
8de77f283d | ||
|
|
004a014000 | ||
|
|
80f6eb94aa | ||
|
|
4035c9cc5f | ||
|
|
3a365f6807 | ||
|
|
9920a0a59f | ||
|
|
c17bb9e171 | ||
|
|
03073f6875 | ||
|
|
609caf6e5f | ||
|
|
411141ff88 | ||
|
|
3e472e18bd | ||
|
|
e5b6fbd12a | ||
|
|
c2787f1282 | ||
|
|
df940124b1 | ||
|
|
5288d042ca | ||
|
|
fa98a27c90 | ||
|
|
d341a5a60b | ||
|
|
7f15227de1 | ||
|
|
bb45240665 | ||
|
|
29f84aeab5 | ||
|
|
5a52edcad8 | ||
|
|
b078e23aa1 | ||
|
|
7fa87125b5 | ||
|
|
f618df46d8 | ||
|
|
ee03853901 | ||
|
|
6c4a9bcfb8 | ||
|
|
1bec20ecef | ||
|
|
08c9bf570d | ||
|
|
5e77a10a81 | ||
|
|
3bc6461e2a | ||
|
|
d3194e15e2 | ||
|
|
2db79ef202 | ||
|
|
b3c07edef6 | ||
|
|
b773fdca34 | ||
|
|
2e9f9f7b7e | ||
|
|
30cbfe729d | ||
|
|
b036da2446 | ||
|
|
c8a9fb1674 | ||
|
|
43bea80cad | ||
|
|
23538c0323 | ||
|
|
2ae911230d | ||
|
|
5ce1cb87ea | ||
|
|
2a37028b6a | ||
|
|
8130feb2a0 | ||
|
|
c49a875ec2 | ||
|
|
2a002304b9 | ||
|
|
d9522818ae | ||
|
|
800588e121 | ||
|
|
991c31ebdb | ||
|
|
48f77e1691 | ||
|
|
42de6fd074 | ||
|
|
62845b470c | ||
|
|
fd20986cf8 | ||
|
|
61369cde9e | ||
|
|
644384ce8b | ||
|
|
97c11a2482 | ||
|
|
1e7e1c2d78 | ||
|
|
1c7201fb04 | ||
|
|
61ec0c697a | ||
|
|
4b5fce1bfc | ||
|
|
6cc6e7c8e9 | ||
|
|
89298fce30 | ||
|
|
a3a27e07fa | ||
|
|
4f32376f22 | ||
|
|
58bf189941 | ||
|
|
bcfa508da5 | ||
|
|
c0ae3ef58b | ||
|
|
1e70d7b4c3 | ||
|
|
f8dc6ad2bc | ||
|
|
e22482988f | ||
|
|
4eb896629d | ||
|
|
b310e66275 | ||
|
|
b39da1bea7 | ||
|
|
fa575d0574 | ||
|
|
dfe2f3771b | ||
|
|
46caeb0445 | ||
|
|
59cc9c7006 | ||
|
|
12e91534eb | ||
|
|
d9da88ceb5 | ||
|
|
2dbfec0307 | ||
|
|
09cf8c9641 | ||
|
|
f1bed4d6a3 | ||
|
|
2ed6c33c83 | ||
|
|
7ad532ed17 | ||
|
|
92fe8c5b14 | ||
|
|
8e95572589 | ||
|
|
62da862a66 | ||
|
|
993e34f548 | ||
|
|
e39465aece | ||
|
|
8fd616b680 | ||
|
|
cc054b27de |
62
.github/workflows/release.yml
vendored
62
.github/workflows/release.yml
vendored
@@ -386,6 +386,68 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
trigger-obs-update:
|
||||
runs-on: ubuntu-latest
|
||||
needs: release
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install OSC
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y osc
|
||||
|
||||
mkdir -p ~/.config/osc
|
||||
cat > ~/.config/osc/oscrc << EOF
|
||||
[general]
|
||||
apiurl = https://api.opensuse.org
|
||||
|
||||
[https://api.opensuse.org]
|
||||
user = ${{ secrets.OBS_USERNAME }}
|
||||
pass = ${{ secrets.OBS_PASSWORD }}
|
||||
EOF
|
||||
chmod 600 ~/.config/osc/oscrc
|
||||
|
||||
- name: Update OBS packages
|
||||
run: |
|
||||
VERSION="${{ github.ref_name }}"
|
||||
cd distro
|
||||
bash scripts/obs-upload.sh dms "Update to $VERSION"
|
||||
|
||||
trigger-ppa-update:
|
||||
runs-on: ubuntu-latest
|
||||
needs: release
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
debhelper \
|
||||
devscripts \
|
||||
dput \
|
||||
lftp \
|
||||
build-essential \
|
||||
fakeroot \
|
||||
dpkg-dev
|
||||
|
||||
- name: Configure GPG
|
||||
env:
|
||||
GPG_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
run: |
|
||||
echo "$GPG_KEY" | gpg --import
|
||||
GPG_KEY_ID=$(gpg --list-secret-keys --keyid-format LONG | grep sec | awk '{print $2}' | cut -d'/' -f2)
|
||||
echo "DEBSIGN_KEYID=$GPG_KEY_ID" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload to PPA
|
||||
run: |
|
||||
VERSION="${{ github.ref_name }}"
|
||||
cd distro/ubuntu/ppa
|
||||
bash create-and-upload.sh ../dms dms questing
|
||||
|
||||
copr-build:
|
||||
runs-on: ubuntu-latest
|
||||
needs: release
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: DMS Copr Stable Release (Manual)
|
||||
name: DMS Copr Stable Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
243
.github/workflows/run-obs.yml
vendored
Normal file
243
.github/workflows/run-obs.yml
vendored
Normal file
@@ -0,0 +1,243 @@
|
||||
name: Update OBS Packages
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
package:
|
||||
description: 'Package to update (dms, dms-git, or all)'
|
||||
required: false
|
||||
default: 'all'
|
||||
rebuild_release:
|
||||
description: 'Release number for rebuilds (e.g., 2, 3, 4 to increment spec Release)'
|
||||
required: false
|
||||
default: ''
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
schedule:
|
||||
- cron: '0 */3 * * *' # Every 3 hours for dms-git builds
|
||||
|
||||
jobs:
|
||||
check-updates:
|
||||
name: Check for updates
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
outputs:
|
||||
has_updates: ${{ steps.check.outputs.has_updates }}
|
||||
packages: ${{ steps.check.outputs.packages }}
|
||||
version: ${{ steps.check.outputs.version }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install OSC
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y osc
|
||||
|
||||
mkdir -p ~/.config/osc
|
||||
cat > ~/.config/osc/oscrc << EOF
|
||||
[general]
|
||||
apiurl = https://api.opensuse.org
|
||||
|
||||
[https://api.opensuse.org]
|
||||
user = ${{ secrets.OBS_USERNAME }}
|
||||
pass = ${{ secrets.OBS_PASSWORD }}
|
||||
EOF
|
||||
chmod 600 ~/.config/osc/oscrc
|
||||
|
||||
- name: Check for updates
|
||||
id: check
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" =~ ^refs/tags/ ]]; then
|
||||
echo "packages=dms" >> $GITHUB_OUTPUT
|
||||
VERSION="${GITHUB_REF#refs/tags/}"
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||
echo "Triggered by tag: $VERSION (always update)"
|
||||
elif [[ "${{ github.event_name }}" == "schedule" ]]; then
|
||||
echo "packages=dms-git" >> $GITHUB_OUTPUT
|
||||
echo "Checking if dms-git source has changed..."
|
||||
|
||||
# Get latest commit hash from master branch
|
||||
LATEST_COMMIT=$(git rev-parse origin/master 2>/dev/null || git rev-parse master 2>/dev/null || echo "")
|
||||
|
||||
if [[ -z "$LATEST_COMMIT" ]]; then
|
||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||
echo "Could not determine git commit, proceeding with update"
|
||||
else
|
||||
# Check OBS for last uploaded commit
|
||||
OBS_BASE="$HOME/.cache/osc-checkouts"
|
||||
mkdir -p "$OBS_BASE"
|
||||
OBS_PROJECT="home:AvengeMedia:dms-git"
|
||||
|
||||
if [[ -d "$OBS_BASE/$OBS_PROJECT/dms-git" ]]; then
|
||||
cd "$OBS_BASE/$OBS_PROJECT/dms-git"
|
||||
osc up -q 2>/dev/null || true
|
||||
|
||||
# Check tarball age - if older than 3 hours, update needed
|
||||
if [[ -f "dms-git-source.tar.gz" ]]; then
|
||||
TARBALL_MTIME=$(stat -c%Y "dms-git-source.tar.gz" 2>/dev/null || echo "0")
|
||||
CURRENT_TIME=$(date +%s)
|
||||
AGE_SECONDS=$((CURRENT_TIME - TARBALL_MTIME))
|
||||
AGE_HOURS=$((AGE_SECONDS / 3600))
|
||||
|
||||
# If tarball is older than 3 hours, check for new commits
|
||||
if [[ $AGE_HOURS -ge 3 ]]; then
|
||||
# Check if there are new commits in the last 3 hours
|
||||
cd "${{ github.workspace }}"
|
||||
NEW_COMMITS=$(git log --since="3 hours ago" --oneline origin/master 2>/dev/null | wc -l)
|
||||
|
||||
if [[ $NEW_COMMITS -gt 0 ]]; then
|
||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||
echo "📋 New commits detected in last 3 hours, update needed"
|
||||
else
|
||||
echo "has_updates=false" >> $GITHUB_OUTPUT
|
||||
echo "📋 No new commits in last 3 hours, skipping update"
|
||||
fi
|
||||
else
|
||||
echo "has_updates=false" >> $GITHUB_OUTPUT
|
||||
echo "📋 Recent upload exists (< 3 hours), skipping update"
|
||||
fi
|
||||
else
|
||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||
echo "📋 No existing tarball in OBS, update needed"
|
||||
fi
|
||||
cd "${{ github.workspace }}"
|
||||
else
|
||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||
echo "📋 First upload to OBS, update needed"
|
||||
fi
|
||||
fi
|
||||
elif [[ -n "${{ github.event.inputs.package }}" ]]; then
|
||||
echo "packages=${{ github.event.inputs.package }}" >> $GITHUB_OUTPUT
|
||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||
echo "Manual trigger: ${{ github.event.inputs.package }}"
|
||||
else
|
||||
echo "packages=all" >> $GITHUB_OUTPUT
|
||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
update-obs:
|
||||
name: Upload to OBS
|
||||
needs: check-updates
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
needs.check-updates.outputs.has_updates == 'true'
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Determine packages to update
|
||||
id: packages
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" =~ ^refs/tags/ ]]; then
|
||||
echo "packages=dms" >> $GITHUB_OUTPUT
|
||||
VERSION="${GITHUB_REF#refs/tags/}"
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Triggered by tag: $VERSION"
|
||||
elif [[ "${{ github.event_name }}" == "schedule" ]]; then
|
||||
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
|
||||
echo "Triggered by schedule: updating git package"
|
||||
elif [[ -n "${{ github.event.inputs.package }}" ]]; then
|
||||
echo "packages=${{ github.event.inputs.package }}" >> $GITHUB_OUTPUT
|
||||
echo "Manual trigger: ${{ github.event.inputs.package }}"
|
||||
else
|
||||
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Update dms-git spec version
|
||||
if: contains(steps.packages.outputs.packages, 'dms-git') || steps.packages.outputs.packages == 'all'
|
||||
run: |
|
||||
# Get commit info for dms-git versioning
|
||||
COMMIT_HASH=$(git rev-parse --short=8 HEAD)
|
||||
COMMIT_COUNT=$(git rev-list --count HEAD)
|
||||
BASE_VERSION=$(grep -oP '^Version:\s+\K[0-9.]+' distro/opensuse/dms.spec | head -1 || echo "0.6.2")
|
||||
|
||||
NEW_VERSION="${BASE_VERSION}+git${COMMIT_COUNT}.${COMMIT_HASH}"
|
||||
echo "📦 Updating dms-git.spec to version: $NEW_VERSION"
|
||||
|
||||
# Update version in spec
|
||||
sed -i "s/^Version:.*/Version: $NEW_VERSION/" distro/opensuse/dms-git.spec
|
||||
|
||||
# Add changelog entry
|
||||
DATE_STR=$(date "+%a %b %d %Y")
|
||||
CHANGELOG_ENTRY="* $DATE_STR Avenge Media <AvengeMedia.US@gmail.com> - ${NEW_VERSION}-1\n- Git snapshot (commit $COMMIT_COUNT: $COMMIT_HASH)"
|
||||
sed -i "/%changelog/a\\$CHANGELOG_ENTRY" distro/opensuse/dms-git.spec
|
||||
|
||||
- name: Update dms stable version
|
||||
if: steps.packages.outputs.version != ''
|
||||
run: |
|
||||
VERSION="${{ steps.packages.outputs.version }}"
|
||||
VERSION_NO_V="${VERSION#v}"
|
||||
echo "Updating packaging to version $VERSION_NO_V"
|
||||
|
||||
# Update openSUSE dms spec (stable only)
|
||||
sed -i "s/^Version:.*/Version: $VERSION_NO_V/" distro/opensuse/dms.spec
|
||||
|
||||
# Update Debian _service files
|
||||
for service in distro/debian/*/_service; do
|
||||
if [[ -f "$service" ]]; then
|
||||
sed -i "s|<param name=\"revision\">v[0-9.]*</param>|<param name=\"revision\">$VERSION</param>|" "$service"
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.24'
|
||||
|
||||
- name: Install OSC
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y osc
|
||||
|
||||
mkdir -p ~/.config/osc
|
||||
cat > ~/.config/osc/oscrc << EOF
|
||||
[general]
|
||||
apiurl = https://api.opensuse.org
|
||||
|
||||
[https://api.opensuse.org]
|
||||
user = ${{ secrets.OBS_USERNAME }}
|
||||
pass = ${{ secrets.OBS_PASSWORD }}
|
||||
EOF
|
||||
chmod 600 ~/.config/osc/oscrc
|
||||
|
||||
- name: Upload to OBS
|
||||
env:
|
||||
FORCE_REBUILD: ${{ github.event_name == 'workflow_dispatch' && 'true' || '' }}
|
||||
REBUILD_RELEASE: ${{ github.event.inputs.rebuild_release }}
|
||||
run: |
|
||||
PACKAGES="${{ steps.packages.outputs.packages }}"
|
||||
MESSAGE="Automated update from GitHub Actions"
|
||||
|
||||
if [[ -n "${{ steps.packages.outputs.version }}" ]]; then
|
||||
MESSAGE="Update to ${{ steps.packages.outputs.version }}"
|
||||
fi
|
||||
|
||||
if [[ "$PACKAGES" == "all" ]]; then
|
||||
bash distro/scripts/obs-upload.sh dms "$MESSAGE"
|
||||
bash distro/scripts/obs-upload.sh dms-git "Automated git update"
|
||||
else
|
||||
bash distro/scripts/obs-upload.sh "$PACKAGES" "$MESSAGE"
|
||||
fi
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
echo "### OBS Package Update Complete" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Packages**: ${{ steps.packages.outputs.packages }}" >> $GITHUB_STEP_SUMMARY
|
||||
if [[ -n "${{ steps.packages.outputs.version }}" ]]; then
|
||||
echo "- **Version**: ${{ steps.packages.outputs.version }}" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
if [[ "${{ needs.check-updates.outputs.has_updates }}" == "false" ]]; then
|
||||
echo "- **Status**: Skipped (no changes detected)" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
echo "- **Project**: https://build.opensuse.org/project/show/home:AvengeMedia" >> $GITHUB_STEP_SUMMARY
|
||||
123
.github/workflows/run-ppa.yml
vendored
Normal file
123
.github/workflows/run-ppa.yml
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
name: Update PPA Packages
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
package:
|
||||
description: 'Package to upload (dms, dms-git, dms-greeter, or all)'
|
||||
required: false
|
||||
default: 'dms-git'
|
||||
rebuild_release:
|
||||
description: 'Release number for rebuilds (e.g., 2, 3, 4 for ppa2, ppa3, ppa4)'
|
||||
required: false
|
||||
default: ''
|
||||
schedule:
|
||||
- cron: '0 */3 * * *' # Every 3 hours for dms-git builds
|
||||
|
||||
jobs:
|
||||
upload-ppa:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.24'
|
||||
cache: false
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
debhelper \
|
||||
devscripts \
|
||||
dput \
|
||||
lftp \
|
||||
build-essential \
|
||||
fakeroot \
|
||||
dpkg-dev
|
||||
|
||||
- name: Configure GPG
|
||||
env:
|
||||
GPG_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
run: |
|
||||
echo "$GPG_KEY" | gpg --import
|
||||
GPG_KEY_ID=$(gpg --list-secret-keys --keyid-format LONG | grep sec | awk '{print $2}' | cut -d'/' -f2)
|
||||
echo "DEBSIGN_KEYID=$GPG_KEY_ID" >> $GITHUB_ENV
|
||||
|
||||
- name: Determine packages to upload
|
||||
id: packages
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "schedule" ]]; then
|
||||
echo "packages=dms-git" >> $GITHUB_OUTPUT
|
||||
echo "Triggered by schedule: uploading git package"
|
||||
elif [[ -n "${{ github.event.inputs.package }}" ]]; then
|
||||
echo "packages=${{ github.event.inputs.package }}" >> $GITHUB_OUTPUT
|
||||
echo "Manual trigger: ${{ github.event.inputs.package }}"
|
||||
else
|
||||
echo "packages=dms-git" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Upload to PPA
|
||||
env:
|
||||
REBUILD_RELEASE: ${{ github.event.inputs.rebuild_release }}
|
||||
run: |
|
||||
PACKAGES="${{ steps.packages.outputs.packages }}"
|
||||
|
||||
if [[ "$PACKAGES" == "all" ]]; then
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "Uploading dms to PPA..."
|
||||
if [ -n "$REBUILD_RELEASE" ]; then
|
||||
echo "🔄 Using rebuild release number: ppa$REBUILD_RELEASE"
|
||||
fi
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
bash distro/scripts/ppa-upload.sh "distro/ubuntu/dms" dms questing
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "Uploading dms-git to PPA..."
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
bash distro/scripts/ppa-upload.sh "distro/ubuntu/dms-git" dms-git questing
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "Uploading dms-greeter to PPA..."
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
bash distro/scripts/ppa-upload.sh "distro/ubuntu/dms-greeter" danklinux questing
|
||||
else
|
||||
PPA_NAME="$PACKAGES"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "Uploading $PACKAGES to PPA..."
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
bash distro/scripts/ppa-upload.sh "distro/ubuntu/$PACKAGES" "$PPA_NAME" questing
|
||||
fi
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
echo "### PPA Package Upload Complete" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Packages**: ${{ steps.packages.outputs.packages }}" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
PACKAGES="${{ steps.packages.outputs.packages }}"
|
||||
if [[ "$PACKAGES" == "all" ]]; then
|
||||
echo "- **PPA dms**: https://launchpad.net/~avengemedia/+archive/ubuntu/dms/+packages" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **PPA dms-git**: https://launchpad.net/~avengemedia/+archive/ubuntu/dms-git/+packages" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **PPA danklinux**: https://launchpad.net/~avengemedia/+archive/ubuntu/danklinux/+packages" >> $GITHUB_STEP_SUMMARY
|
||||
elif [[ "$PACKAGES" == "dms" ]]; then
|
||||
echo "- **PPA**: https://launchpad.net/~avengemedia/+archive/ubuntu/dms/+packages" >> $GITHUB_STEP_SUMMARY
|
||||
elif [[ "$PACKAGES" == "dms-git" ]]; then
|
||||
echo "- **PPA**: https://launchpad.net/~avengemedia/+archive/ubuntu/dms-git/+packages" >> $GITHUB_STEP_SUMMARY
|
||||
elif [[ "$PACKAGES" == "dms-greeter" ]]; then
|
||||
echo "- **PPA**: https://launchpad.net/~avengemedia/+archive/ubuntu/danklinux/+packages" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [[ -n "${{ steps.packages.outputs.version }}" ]]; then
|
||||
echo "- **Version**: ${{ steps.packages.outputs.version }}" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Builds will appear once Launchpad processes the uploads." >> $GITHUB_STEP_SUMMARY
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -136,3 +136,9 @@ go.work.sum
|
||||
# .vscode/
|
||||
|
||||
bin/
|
||||
|
||||
# Extracted source trees in Ubuntu package directories
|
||||
distro/ubuntu/*/dms-git-repo/
|
||||
distro/ubuntu/*/DankMaterialShell-*/
|
||||
distro/ubuntu/danklinux/*/dsearch-*/
|
||||
distro/ubuntu/danklinux/*/dgop-*/
|
||||
|
||||
@@ -2,28 +2,42 @@
|
||||
|
||||
Contributions are welcome and encouraged.
|
||||
|
||||
## Formatting
|
||||
To contribute fork this repository, make your changes, and open a pull request.
|
||||
|
||||
The preferred tool for formatting files is [qmlfmt](https://github.com/jesperhh/qmlfmt) (also available on aur as qmlfmt-git). It actually kinda sucks, but `qmlformat` doesn't work with null safe operators and ternarys and pragma statements and a bunch of other things that are supported.
|
||||
## VSCode Setup
|
||||
|
||||
We need some consistent style, so this at least gives the same formatter that Qt Creator uses.
|
||||
This is a monorepo, the easiest thing to do is to open an editor in either `quickshell`, `core`, or both depending on which part of the project you are working on.
|
||||
|
||||
You can configure it to format on save in vscode by configuring the "custom local formatters" extension then adding this to settings json.
|
||||
### QML (`quickshell` directory)
|
||||
|
||||
1. Install the [QML Extension](https://doc.qt.io/vscodeext/)
|
||||
2. Configure `ctrl+shift+p` -> user preferences (json) with qmlls path
|
||||
|
||||
```json
|
||||
"customLocalFormatters.formatters": [
|
||||
{
|
||||
"command": "sh -c \"qmlfmt -t 4 -i 4 -b 250 | sed 's/pragma ComponentBehavior$/pragma ComponentBehavior: Bound/g'\"",
|
||||
"languages": ["qml"]
|
||||
}
|
||||
],
|
||||
"[qml]": {
|
||||
"editor.defaultFormatter": "jkillian.custom-local-formatters",
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
{
|
||||
"qt-qml.doNotAskForQmllsDownload": true,
|
||||
"qt-qml.qmlls.customExePath": "/usr/lib/qt6/bin/qmlls"
|
||||
}
|
||||
```
|
||||
|
||||
Sometimes it just breaks code though. Like turning `"_\""` into `"_""`, so you may not want to do formatOnSave.
|
||||
3. Create empty `.qmlls.ini` file in `quickshell/` directory
|
||||
|
||||
```bash
|
||||
cd quickshell
|
||||
touch .qmlls.ini
|
||||
```
|
||||
|
||||
4. Restart dms to generate the `.qmlls.ini` file
|
||||
|
||||
5. Make your changes, test, and open a pull request.
|
||||
|
||||
### GO (`core` directory)
|
||||
|
||||
1. Install the [Go Extension](https://code.visualstudio.com/docs/languages/go)
|
||||
2. Ensure code is formatted with `make fmt`
|
||||
3. Add appropriate test coverage and ensure tests pass with `make test`
|
||||
4. Run `go mod tidy`
|
||||
5. Open pull request
|
||||
|
||||
## Pull request
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
[](https://github.com/AvengeMedia/DankMaterialShell/releases)
|
||||
[](https://aur.archlinux.org/packages/dms-shell-bin)
|
||||
[)](https://aur.archlinux.org/packages/dms-shell-git)
|
||||
[](https://ko-fi.com/avengemediallc)
|
||||
[](https://ko-fi.com/danklinux)
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ packages:
|
||||
outpkg: mocks_brightness
|
||||
interfaces:
|
||||
DBusConn:
|
||||
github.com/AvengeMedia/danklinux/internal/server/network:
|
||||
github.com/AvengeMedia/DankMaterialShell/core/internal/server/network:
|
||||
config:
|
||||
dir: "internal/mocks/network"
|
||||
outpkg: mocks_network
|
||||
@@ -46,3 +46,9 @@ packages:
|
||||
outpkg: mocks_evdev
|
||||
interfaces:
|
||||
EvdevDevice:
|
||||
github.com/AvengeMedia/DankMaterialShell/core/internal/version:
|
||||
config:
|
||||
dir: "internal/mocks/version"
|
||||
outpkg: mocks_version
|
||||
interfaces:
|
||||
VersionFetcher:
|
||||
|
||||
@@ -2,7 +2,6 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/dank16"
|
||||
@@ -26,7 +25,6 @@ func init() {
|
||||
dank16Cmd.Flags().Bool("alacritty", false, "Output in Alacritty terminal format")
|
||||
dank16Cmd.Flags().Bool("ghostty", false, "Output in Ghostty terminal format")
|
||||
dank16Cmd.Flags().Bool("wezterm", false, "Output in Wezterm terminal format")
|
||||
dank16Cmd.Flags().String("vscode-enrich", "", "Enrich existing VSCode theme file with terminal colors")
|
||||
dank16Cmd.Flags().String("background", "", "Custom background color")
|
||||
dank16Cmd.Flags().String("contrast", "dps", "Contrast algorithm: dps (Delta Phi Star, default) or wcag")
|
||||
}
|
||||
@@ -44,7 +42,6 @@ func runDank16(cmd *cobra.Command, args []string) {
|
||||
isAlacritty, _ := cmd.Flags().GetBool("alacritty")
|
||||
isGhostty, _ := cmd.Flags().GetBool("ghostty")
|
||||
isWezterm, _ := cmd.Flags().GetBool("wezterm")
|
||||
vscodeEnrich, _ := cmd.Flags().GetString("vscode-enrich")
|
||||
background, _ := cmd.Flags().GetString("background")
|
||||
contrastAlgo, _ := cmd.Flags().GetString("contrast")
|
||||
|
||||
@@ -65,18 +62,7 @@ func runDank16(cmd *cobra.Command, args []string) {
|
||||
|
||||
colors := dank16.GeneratePalette(primaryColor, opts)
|
||||
|
||||
if vscodeEnrich != "" {
|
||||
data, err := os.ReadFile(vscodeEnrich)
|
||||
if err != nil {
|
||||
log.Fatalf("Error reading file: %v", err)
|
||||
}
|
||||
|
||||
enriched, err := dank16.EnrichVSCodeTheme(data, colors)
|
||||
if err != nil {
|
||||
log.Fatalf("Error enriching theme: %v", err)
|
||||
}
|
||||
fmt.Println(string(enriched))
|
||||
} else if isJson {
|
||||
if isJson {
|
||||
fmt.Print(dank16.GenerateJSON(colors))
|
||||
} else if isKitty {
|
||||
fmt.Print(dank16.GenerateKittyTheme(colors))
|
||||
|
||||
@@ -217,6 +217,190 @@ func checkGroupExists(groupName string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func disableDisplayManager(dmName string) (bool, error) {
|
||||
state, err := getSystemdServiceState(dmName)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to check %s state: %w", dmName, err)
|
||||
}
|
||||
|
||||
if !state.Exists {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
fmt.Printf("\nChecking %s...\n", dmName)
|
||||
fmt.Printf(" Current state: enabled=%s\n", state.EnabledState)
|
||||
|
||||
actionTaken := false
|
||||
|
||||
if state.NeedsDisable {
|
||||
var disableCmd *exec.Cmd
|
||||
var actionVerb string
|
||||
|
||||
if state.EnabledState == "static" {
|
||||
fmt.Printf(" Masking %s (static service cannot be disabled)...\n", dmName)
|
||||
disableCmd = exec.Command("sudo", "systemctl", "mask", dmName)
|
||||
actionVerb = "masked"
|
||||
} else {
|
||||
fmt.Printf(" Disabling %s...\n", dmName)
|
||||
disableCmd = exec.Command("sudo", "systemctl", "disable", dmName)
|
||||
actionVerb = "disabled"
|
||||
}
|
||||
|
||||
disableCmd.Stdout = os.Stdout
|
||||
disableCmd.Stderr = os.Stderr
|
||||
if err := disableCmd.Run(); err != nil {
|
||||
return actionTaken, fmt.Errorf("failed to disable/mask %s: %w", dmName, err)
|
||||
}
|
||||
|
||||
enabledState, shouldDisable, verifyErr := checkSystemdServiceEnabled(dmName)
|
||||
if verifyErr != nil {
|
||||
fmt.Printf(" ⚠ Warning: Could not verify %s was %s: %v\n", dmName, actionVerb, verifyErr)
|
||||
} else if shouldDisable {
|
||||
return actionTaken, fmt.Errorf("%s is still in state '%s' after %s operation", dmName, enabledState, actionVerb)
|
||||
} else {
|
||||
fmt.Printf(" ✓ %s %s (now: %s)\n", strings.Title(actionVerb), dmName, enabledState)
|
||||
}
|
||||
|
||||
actionTaken = true
|
||||
} else {
|
||||
if state.EnabledState == "masked" || state.EnabledState == "masked-runtime" {
|
||||
fmt.Printf(" ✓ %s is already masked\n", dmName)
|
||||
} else {
|
||||
fmt.Printf(" ✓ %s is already disabled\n", dmName)
|
||||
}
|
||||
}
|
||||
|
||||
return actionTaken, nil
|
||||
}
|
||||
|
||||
func ensureGreetdEnabled() error {
|
||||
fmt.Println("\nChecking greetd service status...")
|
||||
|
||||
state, err := getSystemdServiceState("greetd")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check greetd state: %w", err)
|
||||
}
|
||||
|
||||
if !state.Exists {
|
||||
return fmt.Errorf("greetd service not found. Please install greetd first")
|
||||
}
|
||||
|
||||
fmt.Printf(" Current state: %s\n", state.EnabledState)
|
||||
|
||||
if state.EnabledState == "masked" || state.EnabledState == "masked-runtime" {
|
||||
fmt.Println(" Unmasking greetd...")
|
||||
unmaskCmd := exec.Command("sudo", "systemctl", "unmask", "greetd")
|
||||
unmaskCmd.Stdout = os.Stdout
|
||||
unmaskCmd.Stderr = os.Stderr
|
||||
if err := unmaskCmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to unmask greetd: %w", err)
|
||||
}
|
||||
fmt.Println(" ✓ Unmasked greetd")
|
||||
}
|
||||
|
||||
if state.EnabledState == "disabled" || state.EnabledState == "masked" || state.EnabledState == "masked-runtime" {
|
||||
fmt.Println(" Enabling greetd service...")
|
||||
enableCmd := exec.Command("sudo", "systemctl", "enable", "greetd")
|
||||
enableCmd.Stdout = os.Stdout
|
||||
enableCmd.Stderr = os.Stderr
|
||||
if err := enableCmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to enable greetd: %w", err)
|
||||
}
|
||||
fmt.Println(" ✓ Enabled greetd service")
|
||||
} else if state.EnabledState == "enabled" || state.EnabledState == "enabled-runtime" {
|
||||
fmt.Println(" ✓ greetd is already enabled")
|
||||
} else {
|
||||
fmt.Printf(" ℹ greetd is in state '%s' (should work, no action needed)\n", state.EnabledState)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensureGraphicalTarget() error {
|
||||
getDefaultCmd := exec.Command("systemctl", "get-default")
|
||||
currentTarget, err := getDefaultCmd.Output()
|
||||
if err != nil {
|
||||
fmt.Println("⚠ Warning: Could not detect current default systemd target")
|
||||
return nil
|
||||
}
|
||||
|
||||
currentTargetStr := strings.TrimSpace(string(currentTarget))
|
||||
if currentTargetStr != "graphical.target" {
|
||||
fmt.Printf("\nSetting graphical.target as default (current: %s)...\n", currentTargetStr)
|
||||
setDefaultCmd := exec.Command("sudo", "systemctl", "set-default", "graphical.target")
|
||||
setDefaultCmd.Stdout = os.Stdout
|
||||
setDefaultCmd.Stderr = os.Stderr
|
||||
if err := setDefaultCmd.Run(); err != nil {
|
||||
fmt.Println("⚠ Warning: Failed to set graphical.target as default")
|
||||
fmt.Println(" Greeter may not start on boot. Run manually:")
|
||||
fmt.Println(" sudo systemctl set-default graphical.target")
|
||||
return nil
|
||||
}
|
||||
fmt.Println("✓ Set graphical.target as default")
|
||||
} else {
|
||||
fmt.Println("✓ Default target already set to graphical.target")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleConflictingDisplayManagers() error {
|
||||
fmt.Println("\n=== Checking for Conflicting Display Managers ===")
|
||||
|
||||
conflictingDMs := []string{"gdm", "gdm3", "lightdm", "sddm", "lxdm", "xdm"}
|
||||
|
||||
disabledAny := false
|
||||
var errors []string
|
||||
|
||||
for _, dm := range conflictingDMs {
|
||||
actionTaken, err := disableDisplayManager(dm)
|
||||
if err != nil {
|
||||
errMsg := fmt.Sprintf("Failed to handle %s: %v", dm, err)
|
||||
errors = append(errors, errMsg)
|
||||
fmt.Printf(" ⚠⚠⚠ ERROR: %s\n", errMsg)
|
||||
continue
|
||||
}
|
||||
if actionTaken {
|
||||
disabledAny = true
|
||||
}
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
fmt.Println("\n╔════════════════════════════════════════════════════════════╗")
|
||||
fmt.Println("║ ⚠⚠⚠ ERRORS OCCURRED ⚠⚠⚠ ║")
|
||||
fmt.Println("╚════════════════════════════════════════════════════════════╝")
|
||||
fmt.Println("\nSome display managers could not be disabled:")
|
||||
for _, err := range errors {
|
||||
fmt.Printf(" ✗ %s\n", err)
|
||||
}
|
||||
fmt.Println("\nThis may prevent greetd from starting properly.")
|
||||
fmt.Println("You may need to manually disable them before greetd will work.")
|
||||
fmt.Println("\nManual commands to try:")
|
||||
for _, dm := range conflictingDMs {
|
||||
fmt.Printf(" sudo systemctl disable %s\n", dm)
|
||||
fmt.Printf(" sudo systemctl mask %s\n", dm)
|
||||
}
|
||||
fmt.Print("\nContinue with greeter enablement anyway? (Y/n): ")
|
||||
|
||||
var response string
|
||||
fmt.Scanln(&response)
|
||||
response = strings.ToLower(strings.TrimSpace(response))
|
||||
|
||||
if response == "n" || response == "no" {
|
||||
return fmt.Errorf("aborted due to display manager conflicts")
|
||||
}
|
||||
fmt.Println("\nContinuing despite errors...")
|
||||
}
|
||||
|
||||
if !disabledAny && len(errors) == 0 {
|
||||
fmt.Println("\n✓ No conflicting display managers found")
|
||||
} else if disabledAny && len(errors) == 0 {
|
||||
fmt.Println("\n✓ Successfully handled all conflicting display managers")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func enableGreeter() error {
|
||||
fmt.Println("=== DMS Greeter Enable ===")
|
||||
fmt.Println()
|
||||
@@ -232,8 +416,29 @@ func enableGreeter() error {
|
||||
}
|
||||
|
||||
configContent := string(data)
|
||||
if strings.Contains(configContent, "dms-greeter") {
|
||||
configAlreadyCorrect := strings.Contains(configContent, "dms-greeter")
|
||||
|
||||
if configAlreadyCorrect {
|
||||
fmt.Println("✓ Greeter is already configured with dms-greeter")
|
||||
|
||||
if err := ensureGraphicalTarget(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := handleConflictingDisplayManagers(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ensureGreetdEnabled(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("\n=== Enable Complete ===")
|
||||
fmt.Println("\nGreeter configuration verified and system state corrected.")
|
||||
fmt.Println("To start the greeter now, run:")
|
||||
fmt.Println(" sudo systemctl start greetd")
|
||||
fmt.Println("\nOr reboot to see the greeter at boot time.")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -322,11 +527,23 @@ func enableGreeter() error {
|
||||
}
|
||||
|
||||
fmt.Printf("✓ Updated greetd configuration to use %s\n", selectedCompositor)
|
||||
|
||||
if err := ensureGraphicalTarget(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := handleConflictingDisplayManagers(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ensureGreetdEnabled(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("\n=== Enable Complete ===")
|
||||
fmt.Println("\nTo start the greeter, run:")
|
||||
fmt.Println("\nTo start the greeter now, run:")
|
||||
fmt.Println(" sudo systemctl start greetd")
|
||||
fmt.Println("\nTo enable on boot, run:")
|
||||
fmt.Println(" sudo systemctl enable --now greetd")
|
||||
fmt.Println("\nOr reboot to see the greeter at boot time.")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -57,6 +57,11 @@ func getRuntimeDir() string {
|
||||
return os.TempDir()
|
||||
}
|
||||
|
||||
func hasSystemdRun() bool {
|
||||
_, err := exec.LookPath("systemd-run")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func getPIDFilePath() string {
|
||||
return filepath.Join(getRuntimeDir(), fmt.Sprintf("danklinux-%d.pid", os.Getpid()))
|
||||
}
|
||||
@@ -165,6 +170,10 @@ func runShellInteractive(session bool) {
|
||||
cmd.Env = append(cmd.Env, "QT_LOGGING_RULES="+qtRules)
|
||||
}
|
||||
|
||||
if isSessionManaged && hasSystemdRun() {
|
||||
cmd.Env = append(cmd.Env, "DMS_DEFAULT_LAUNCH_PREFIX=systemd-run --user --scope")
|
||||
}
|
||||
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err == nil && os.Getenv("DMS_DISABLE_HOT_RELOAD") == "" {
|
||||
if !strings.HasPrefix(configPath, homeDir) {
|
||||
@@ -387,6 +396,10 @@ func runShellDaemon(session bool) {
|
||||
cmd.Env = append(cmd.Env, "QT_LOGGING_RULES="+qtRules)
|
||||
}
|
||||
|
||||
if isSessionManaged && hasSystemdRun() {
|
||||
cmd.Env = append(cmd.Env, "DMS_DEFAULT_LAUNCH_PREFIX=systemd-run --user --scope")
|
||||
}
|
||||
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err == nil && os.Getenv("DMS_DISABLE_HOT_RELOAD") == "" {
|
||||
if !strings.HasPrefix(configPath, homeDir) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func commandExists(cmd string) bool {
|
||||
@@ -24,3 +25,68 @@ func isArchPackageInstalled(packageName string) bool {
|
||||
err := cmd.Run()
|
||||
return err == nil
|
||||
}
|
||||
|
||||
type systemdServiceState struct {
|
||||
Name string
|
||||
EnabledState string
|
||||
NeedsDisable bool
|
||||
Exists bool
|
||||
}
|
||||
|
||||
// checkSystemdServiceEnabled returns (state, should_disable, error) for a systemd service
|
||||
func checkSystemdServiceEnabled(serviceName string) (string, bool, error) {
|
||||
cmd := exec.Command("systemctl", "is-enabled", serviceName)
|
||||
output, err := cmd.Output()
|
||||
|
||||
stateStr := strings.TrimSpace(string(output))
|
||||
|
||||
if err != nil {
|
||||
knownStates := []string{"disabled", "masked", "masked-runtime", "not-found", "enabled", "enabled-runtime", "static", "indirect", "alias"}
|
||||
isKnownState := false
|
||||
for _, known := range knownStates {
|
||||
if stateStr == known {
|
||||
isKnownState = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !isKnownState {
|
||||
return stateStr, false, fmt.Errorf("systemctl is-enabled failed: %w (output: %s)", err, stateStr)
|
||||
}
|
||||
}
|
||||
|
||||
shouldDisable := false
|
||||
switch stateStr {
|
||||
case "enabled", "enabled-runtime", "static", "indirect", "alias":
|
||||
shouldDisable = true
|
||||
case "disabled", "masked", "masked-runtime", "not-found":
|
||||
shouldDisable = false
|
||||
default:
|
||||
shouldDisable = true
|
||||
}
|
||||
|
||||
return stateStr, shouldDisable, nil
|
||||
}
|
||||
|
||||
func getSystemdServiceState(serviceName string) (*systemdServiceState, error) {
|
||||
state := &systemdServiceState{
|
||||
Name: serviceName,
|
||||
Exists: false,
|
||||
}
|
||||
|
||||
enabledState, needsDisable, err := checkSystemdServiceEnabled(serviceName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to check enabled state: %w", err)
|
||||
}
|
||||
|
||||
state.EnabledState = enabledState
|
||||
state.NeedsDisable = needsDisable
|
||||
|
||||
if enabledState == "not-found" {
|
||||
state.Exists = false
|
||||
return state, nil
|
||||
}
|
||||
|
||||
state.Exists = true
|
||||
return state, nil
|
||||
}
|
||||
|
||||
@@ -125,6 +125,8 @@ windowrulev2 = noborder, class:^(kitty)$
|
||||
windowrulev2 = float, class:^(firefox)$, title:^(Picture-in-Picture)$
|
||||
windowrulev2 = float, class:^(zoom)$
|
||||
|
||||
# DMS windows floating by default
|
||||
windowrulev2 = float, class:^(org.quickshell)$
|
||||
windowrulev2 = opacity 0.9 0.9, floating:0, focus:0
|
||||
|
||||
layerrule = noanim, ^(quickshell)$
|
||||
|
||||
@@ -218,6 +218,11 @@ window-rule {
|
||||
geometry-corner-radius 12
|
||||
clip-to-geometry true
|
||||
}
|
||||
// Open dms windows as floating by default
|
||||
window-rule {
|
||||
match app-id=r#"org.quickshell$"#
|
||||
open-floating true
|
||||
}
|
||||
binds {
|
||||
// === System & Overview ===
|
||||
Mod+D { spawn "niri" "msg" "action" "toggle-overview"; }
|
||||
|
||||
@@ -15,6 +15,48 @@ type HSV struct {
|
||||
H, S, V float64
|
||||
}
|
||||
|
||||
type ColorInfo struct {
|
||||
Hex string `json:"hex"`
|
||||
HexStripped string `json:"hex_stripped"`
|
||||
R int `json:"r"`
|
||||
G int `json:"g"`
|
||||
B int `json:"b"`
|
||||
}
|
||||
|
||||
type Palette struct {
|
||||
Color0 ColorInfo `json:"color0"`
|
||||
Color1 ColorInfo `json:"color1"`
|
||||
Color2 ColorInfo `json:"color2"`
|
||||
Color3 ColorInfo `json:"color3"`
|
||||
Color4 ColorInfo `json:"color4"`
|
||||
Color5 ColorInfo `json:"color5"`
|
||||
Color6 ColorInfo `json:"color6"`
|
||||
Color7 ColorInfo `json:"color7"`
|
||||
Color8 ColorInfo `json:"color8"`
|
||||
Color9 ColorInfo `json:"color9"`
|
||||
Color10 ColorInfo `json:"color10"`
|
||||
Color11 ColorInfo `json:"color11"`
|
||||
Color12 ColorInfo `json:"color12"`
|
||||
Color13 ColorInfo `json:"color13"`
|
||||
Color14 ColorInfo `json:"color14"`
|
||||
Color15 ColorInfo `json:"color15"`
|
||||
}
|
||||
|
||||
func NewColorInfo(hex string) ColorInfo {
|
||||
rgb := HexToRGB(hex)
|
||||
stripped := hex
|
||||
if len(hex) > 0 && hex[0] == '#' {
|
||||
stripped = hex[1:]
|
||||
}
|
||||
return ColorInfo{
|
||||
Hex: hex,
|
||||
HexStripped: stripped,
|
||||
R: int(math.Round(rgb.R * 255)),
|
||||
G: int(math.Round(rgb.G * 255)),
|
||||
B: int(math.Round(rgb.B * 255)),
|
||||
}
|
||||
}
|
||||
|
||||
func HexToRGB(hex string) RGB {
|
||||
if hex[0] == '#' {
|
||||
hex = hex[1:]
|
||||
@@ -310,13 +352,13 @@ func DeriveContainer(primary string, isLight bool) string {
|
||||
return RGBToHex(HSVToRGB(HSV{H: hsv.H, S: containerS, V: containerV}))
|
||||
}
|
||||
|
||||
func GeneratePalette(primaryColor string, opts PaletteOptions) []string {
|
||||
func GeneratePalette(primaryColor string, opts PaletteOptions) Palette {
|
||||
baseColor := DeriveContainer(primaryColor, opts.IsLight)
|
||||
|
||||
rgb := HexToRGB(baseColor)
|
||||
hsv := RGBToHSV(rgb)
|
||||
|
||||
palette := make([]string, 0, 16)
|
||||
var palette Palette
|
||||
|
||||
var normalTextTarget, secondaryTarget float64
|
||||
if opts.UseDPS {
|
||||
@@ -335,7 +377,7 @@ func GeneratePalette(primaryColor string, opts PaletteOptions) []string {
|
||||
} else {
|
||||
bgColor = "#1a1a1a"
|
||||
}
|
||||
palette = append(palette, bgColor)
|
||||
palette.Color0 = NewColorInfo(bgColor)
|
||||
|
||||
hueShift := (hsv.H - 0.6) * 0.12
|
||||
satBoost := 1.15
|
||||
@@ -344,39 +386,39 @@ func GeneratePalette(primaryColor string, opts PaletteOptions) []string {
|
||||
var redColor string
|
||||
if opts.IsLight {
|
||||
redColor = RGBToHex(HSVToRGB(HSV{H: redH, S: math.Min(0.80*satBoost, 1.0), V: 0.55}))
|
||||
palette = append(palette, ensureContrastAuto(redColor, bgColor, normalTextTarget, opts))
|
||||
palette.Color1 = NewColorInfo(ensureContrastAuto(redColor, bgColor, normalTextTarget, opts))
|
||||
} else {
|
||||
redColor = RGBToHex(HSVToRGB(HSV{H: redH, S: math.Min(0.65*satBoost, 1.0), V: 0.80}))
|
||||
palette = append(palette, ensureContrastAuto(redColor, bgColor, normalTextTarget, opts))
|
||||
palette.Color1 = NewColorInfo(ensureContrastAuto(redColor, bgColor, normalTextTarget, opts))
|
||||
}
|
||||
|
||||
greenH := math.Mod(0.33+hueShift+1.0, 1.0)
|
||||
var greenColor string
|
||||
if opts.IsLight {
|
||||
greenColor = RGBToHex(HSVToRGB(HSV{H: greenH, S: math.Min(math.Max(hsv.S*0.9, 0.80)*satBoost, 1.0), V: 0.45}))
|
||||
palette = append(palette, ensureContrastAuto(greenColor, bgColor, normalTextTarget, opts))
|
||||
palette.Color2 = NewColorInfo(ensureContrastAuto(greenColor, bgColor, normalTextTarget, opts))
|
||||
} else {
|
||||
greenColor = RGBToHex(HSVToRGB(HSV{H: greenH, S: math.Min(0.42*satBoost, 1.0), V: 0.84}))
|
||||
palette = append(palette, ensureContrastAuto(greenColor, bgColor, normalTextTarget, opts))
|
||||
palette.Color2 = NewColorInfo(ensureContrastAuto(greenColor, bgColor, normalTextTarget, opts))
|
||||
}
|
||||
|
||||
yellowH := math.Mod(0.15+hueShift+1.0, 1.0)
|
||||
var yellowColor string
|
||||
if opts.IsLight {
|
||||
yellowColor = RGBToHex(HSVToRGB(HSV{H: yellowH, S: math.Min(0.75*satBoost, 1.0), V: 0.50}))
|
||||
palette = append(palette, ensureContrastAuto(yellowColor, bgColor, normalTextTarget, opts))
|
||||
palette.Color3 = NewColorInfo(ensureContrastAuto(yellowColor, bgColor, normalTextTarget, opts))
|
||||
} else {
|
||||
yellowColor = RGBToHex(HSVToRGB(HSV{H: yellowH, S: math.Min(0.38*satBoost, 1.0), V: 0.86}))
|
||||
palette = append(palette, ensureContrastAuto(yellowColor, bgColor, normalTextTarget, opts))
|
||||
palette.Color3 = NewColorInfo(ensureContrastAuto(yellowColor, bgColor, normalTextTarget, opts))
|
||||
}
|
||||
|
||||
var blueColor string
|
||||
if opts.IsLight {
|
||||
blueColor = RGBToHex(HSVToRGB(HSV{H: hsv.H, S: math.Max(hsv.S*0.9, 0.7), V: hsv.V * 1.1}))
|
||||
palette = append(palette, ensureContrastAuto(blueColor, bgColor, normalTextTarget, opts))
|
||||
palette.Color4 = NewColorInfo(ensureContrastAuto(blueColor, bgColor, normalTextTarget, opts))
|
||||
} else {
|
||||
blueColor = RGBToHex(HSVToRGB(HSV{H: hsv.H, S: math.Max(hsv.S*0.8, 0.6), V: math.Min(hsv.V*1.6, 1.0)}))
|
||||
palette = append(palette, ensureContrastAuto(blueColor, bgColor, normalTextTarget, opts))
|
||||
palette.Color4 = NewColorInfo(ensureContrastAuto(blueColor, bgColor, normalTextTarget, opts))
|
||||
}
|
||||
|
||||
magH := hsv.H - 0.03
|
||||
@@ -388,65 +430,64 @@ func GeneratePalette(primaryColor string, opts PaletteOptions) []string {
|
||||
hh := RGBToHSV(hr)
|
||||
if opts.IsLight {
|
||||
magColor = RGBToHex(HSVToRGB(HSV{H: hh.H, S: math.Max(hh.S*0.9, 0.7), V: hh.V * 0.85}))
|
||||
palette = append(palette, ensureContrastAuto(magColor, bgColor, normalTextTarget, opts))
|
||||
palette.Color5 = NewColorInfo(ensureContrastAuto(magColor, bgColor, normalTextTarget, opts))
|
||||
} else {
|
||||
magColor = RGBToHex(HSVToRGB(HSV{H: hh.H, S: hh.S * 0.8, V: hh.V * 0.75}))
|
||||
palette = append(palette, ensureContrastAuto(magColor, bgColor, normalTextTarget, opts))
|
||||
palette.Color5 = NewColorInfo(ensureContrastAuto(magColor, bgColor, normalTextTarget, opts))
|
||||
}
|
||||
|
||||
cyanH := hsv.H + 0.08
|
||||
if cyanH > 1.0 {
|
||||
cyanH -= 1.0
|
||||
}
|
||||
palette = append(palette, ensureContrastAuto(primaryColor, bgColor, normalTextTarget, opts))
|
||||
palette.Color6 = NewColorInfo(ensureContrastAuto(primaryColor, bgColor, normalTextTarget, opts))
|
||||
|
||||
if opts.IsLight {
|
||||
palette = append(palette, "#1a1a1a")
|
||||
palette = append(palette, "#2e2e2e")
|
||||
palette.Color7 = NewColorInfo("#1a1a1a")
|
||||
palette.Color8 = NewColorInfo("#2e2e2e")
|
||||
} else {
|
||||
palette = append(palette, "#abb2bf")
|
||||
palette = append(palette, "#5c6370")
|
||||
palette.Color7 = NewColorInfo("#abb2bf")
|
||||
palette.Color8 = NewColorInfo("#5c6370")
|
||||
}
|
||||
|
||||
if opts.IsLight {
|
||||
brightRed := RGBToHex(HSVToRGB(HSV{H: redH, S: math.Min(0.70*satBoost, 1.0), V: 0.65}))
|
||||
palette = append(palette, ensureContrastAuto(brightRed, bgColor, secondaryTarget, opts))
|
||||
palette.Color9 = NewColorInfo(ensureContrastAuto(brightRed, bgColor, secondaryTarget, opts))
|
||||
brightGreen := RGBToHex(HSVToRGB(HSV{H: greenH, S: math.Min(math.Max(hsv.S*0.85, 0.75)*satBoost, 1.0), V: 0.55}))
|
||||
palette = append(palette, ensureContrastAuto(brightGreen, bgColor, secondaryTarget, opts))
|
||||
palette.Color10 = NewColorInfo(ensureContrastAuto(brightGreen, bgColor, secondaryTarget, opts))
|
||||
brightYellow := RGBToHex(HSVToRGB(HSV{H: yellowH, S: math.Min(0.68*satBoost, 1.0), V: 0.60}))
|
||||
palette = append(palette, ensureContrastAuto(brightYellow, bgColor, secondaryTarget, opts))
|
||||
palette.Color11 = NewColorInfo(ensureContrastAuto(brightYellow, bgColor, secondaryTarget, opts))
|
||||
hr := HexToRGB(primaryColor)
|
||||
hh := RGBToHSV(hr)
|
||||
brightBlue := RGBToHex(HSVToRGB(HSV{H: hh.H, S: math.Min(hh.S*1.1, 1.0), V: math.Min(hh.V*1.2, 1.0)}))
|
||||
palette = append(palette, ensureContrastAuto(brightBlue, bgColor, secondaryTarget, opts))
|
||||
palette.Color12 = NewColorInfo(ensureContrastAuto(brightBlue, bgColor, secondaryTarget, opts))
|
||||
brightMag := RGBToHex(HSVToRGB(HSV{H: magH, S: math.Max(hsv.S*0.9, 0.75), V: math.Min(hsv.V*1.25, 1.0)}))
|
||||
palette = append(palette, ensureContrastAuto(brightMag, bgColor, secondaryTarget, opts))
|
||||
palette.Color13 = NewColorInfo(ensureContrastAuto(brightMag, bgColor, secondaryTarget, opts))
|
||||
brightCyan := RGBToHex(HSVToRGB(HSV{H: cyanH, S: math.Max(hsv.S*0.75, 0.65), V: math.Min(hsv.V*1.25, 1.0)}))
|
||||
palette = append(palette, ensureContrastAuto(brightCyan, bgColor, secondaryTarget, opts))
|
||||
palette.Color14 = NewColorInfo(ensureContrastAuto(brightCyan, bgColor, secondaryTarget, opts))
|
||||
} else {
|
||||
brightRed := RGBToHex(HSVToRGB(HSV{H: redH, S: math.Min(0.50*satBoost, 1.0), V: 0.88}))
|
||||
palette = append(palette, ensureContrastAuto(brightRed, bgColor, secondaryTarget, opts))
|
||||
palette.Color9 = NewColorInfo(ensureContrastAuto(brightRed, bgColor, secondaryTarget, opts))
|
||||
brightGreen := RGBToHex(HSVToRGB(HSV{H: greenH, S: math.Min(0.35*satBoost, 1.0), V: 0.88}))
|
||||
palette = append(palette, ensureContrastAuto(brightGreen, bgColor, secondaryTarget, opts))
|
||||
palette.Color10 = NewColorInfo(ensureContrastAuto(brightGreen, bgColor, secondaryTarget, opts))
|
||||
brightYellow := RGBToHex(HSVToRGB(HSV{H: yellowH, S: math.Min(0.30*satBoost, 1.0), V: 0.91}))
|
||||
palette = append(palette, ensureContrastAuto(brightYellow, bgColor, secondaryTarget, opts))
|
||||
// Make it way brighter for type names in dark mode
|
||||
palette.Color11 = NewColorInfo(ensureContrastAuto(brightYellow, bgColor, secondaryTarget, opts))
|
||||
brightBlue := retoneToL(primaryColor, 85.0)
|
||||
palette = append(palette, brightBlue)
|
||||
palette.Color12 = NewColorInfo(brightBlue)
|
||||
brightMag := RGBToHex(HSVToRGB(HSV{H: magH, S: math.Max(hsv.S*0.7, 0.6), V: math.Min(hsv.V*1.3, 0.9)}))
|
||||
palette = append(palette, ensureContrastAuto(brightMag, bgColor, secondaryTarget, opts))
|
||||
palette.Color13 = NewColorInfo(ensureContrastAuto(brightMag, bgColor, secondaryTarget, opts))
|
||||
brightCyanH := hsv.H + 0.02
|
||||
if brightCyanH > 1.0 {
|
||||
brightCyanH -= 1.0
|
||||
}
|
||||
brightCyan := RGBToHex(HSVToRGB(HSV{H: brightCyanH, S: math.Max(hsv.S*0.6, 0.5), V: math.Min(hsv.V*1.2, 0.85)}))
|
||||
palette = append(palette, ensureContrastAuto(brightCyan, bgColor, secondaryTarget, opts))
|
||||
palette.Color14 = NewColorInfo(ensureContrastAuto(brightCyan, bgColor, secondaryTarget, opts))
|
||||
}
|
||||
|
||||
if opts.IsLight {
|
||||
palette = append(palette, "#1a1a1a")
|
||||
palette.Color15 = NewColorInfo("#1a1a1a")
|
||||
} else {
|
||||
palette = append(palette, "#ffffff")
|
||||
palette.Color15 = NewColorInfo("#ffffff")
|
||||
}
|
||||
|
||||
return palette
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package dank16
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
@@ -346,106 +345,36 @@ func TestGeneratePalette(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := GeneratePalette(tt.base, tt.opts)
|
||||
|
||||
if len(result) != 16 {
|
||||
t.Errorf("GeneratePalette returned %d colors, expected 16", len(result))
|
||||
colors := []ColorInfo{
|
||||
result.Color0, result.Color1, result.Color2, result.Color3,
|
||||
result.Color4, result.Color5, result.Color6, result.Color7,
|
||||
result.Color8, result.Color9, result.Color10, result.Color11,
|
||||
result.Color12, result.Color13, result.Color14, result.Color15,
|
||||
}
|
||||
|
||||
for i, color := range result {
|
||||
if len(color) != 7 || color[0] != '#' {
|
||||
t.Errorf("Color at index %d (%s) is not a valid hex color", i, color)
|
||||
for i, color := range colors {
|
||||
if len(color.Hex) != 7 || color.Hex[0] != '#' {
|
||||
t.Errorf("Color at index %d (%s) is not a valid hex color", i, color.Hex)
|
||||
}
|
||||
}
|
||||
|
||||
if tt.opts.Background != "" && result[0] != tt.opts.Background {
|
||||
t.Errorf("Background color = %s, expected %s", result[0], tt.opts.Background)
|
||||
} else if !tt.opts.IsLight && tt.opts.Background == "" && result[0] != "#1a1a1a" {
|
||||
t.Errorf("Dark mode background = %s, expected #1a1a1a", result[0])
|
||||
} else if tt.opts.IsLight && tt.opts.Background == "" && result[0] != "#f8f8f8" {
|
||||
t.Errorf("Light mode background = %s, expected #f8f8f8", result[0])
|
||||
if tt.opts.Background != "" && result.Color0.Hex != tt.opts.Background {
|
||||
t.Errorf("Background color = %s, expected %s", result.Color0.Hex, tt.opts.Background)
|
||||
} else if !tt.opts.IsLight && tt.opts.Background == "" && result.Color0.Hex != "#1a1a1a" {
|
||||
t.Errorf("Dark mode background = %s, expected #1a1a1a", result.Color0.Hex)
|
||||
} else if tt.opts.IsLight && tt.opts.Background == "" && result.Color0.Hex != "#f8f8f8" {
|
||||
t.Errorf("Light mode background = %s, expected #f8f8f8", result.Color0.Hex)
|
||||
}
|
||||
|
||||
if tt.opts.IsLight && result[15] != "#1a1a1a" {
|
||||
t.Errorf("Light mode foreground = %s, expected #1a1a1a", result[15])
|
||||
} else if !tt.opts.IsLight && result[15] != "#ffffff" {
|
||||
t.Errorf("Dark mode foreground = %s, expected #ffffff", result[15])
|
||||
if tt.opts.IsLight && result.Color15.Hex != "#1a1a1a" {
|
||||
t.Errorf("Light mode foreground = %s, expected #1a1a1a", result.Color15.Hex)
|
||||
} else if !tt.opts.IsLight && result.Color15.Hex != "#ffffff" {
|
||||
t.Errorf("Dark mode foreground = %s, expected #ffffff", result.Color15.Hex)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnrichVSCodeTheme(t *testing.T) {
|
||||
colors := GeneratePalette("#625690", PaletteOptions{IsLight: false})
|
||||
|
||||
baseTheme := map[string]interface{}{
|
||||
"name": "Test Theme",
|
||||
"type": "dark",
|
||||
"colors": map[string]interface{}{
|
||||
"editor.background": "#000000",
|
||||
},
|
||||
}
|
||||
|
||||
themeJSON, err := json.Marshal(baseTheme)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal base theme: %v", err)
|
||||
}
|
||||
|
||||
result, err := EnrichVSCodeTheme(themeJSON, colors)
|
||||
if err != nil {
|
||||
t.Fatalf("EnrichVSCodeTheme failed: %v", err)
|
||||
}
|
||||
|
||||
var enriched map[string]interface{}
|
||||
if err := json.Unmarshal(result, &enriched); err != nil {
|
||||
t.Fatalf("Failed to unmarshal result: %v", err)
|
||||
}
|
||||
|
||||
colorsMap, ok := enriched["colors"].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatal("colors is not a map")
|
||||
}
|
||||
|
||||
terminalColors := []string{
|
||||
"terminal.ansiBlack",
|
||||
"terminal.ansiRed",
|
||||
"terminal.ansiGreen",
|
||||
"terminal.ansiYellow",
|
||||
"terminal.ansiBlue",
|
||||
"terminal.ansiMagenta",
|
||||
"terminal.ansiCyan",
|
||||
"terminal.ansiWhite",
|
||||
"terminal.ansiBrightBlack",
|
||||
"terminal.ansiBrightRed",
|
||||
"terminal.ansiBrightGreen",
|
||||
"terminal.ansiBrightYellow",
|
||||
"terminal.ansiBrightBlue",
|
||||
"terminal.ansiBrightMagenta",
|
||||
"terminal.ansiBrightCyan",
|
||||
"terminal.ansiBrightWhite",
|
||||
}
|
||||
|
||||
for i, key := range terminalColors {
|
||||
if val, ok := colorsMap[key]; !ok {
|
||||
t.Errorf("Missing terminal color: %s", key)
|
||||
} else if val != colors[i] {
|
||||
t.Errorf("%s = %s, expected %s", key, val, colors[i])
|
||||
}
|
||||
}
|
||||
|
||||
if colorsMap["editor.background"] != "#000000" {
|
||||
t.Error("Original theme colors should be preserved")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnrichVSCodeThemeInvalidJSON(t *testing.T) {
|
||||
colors := GeneratePalette("#625690", PaletteOptions{IsLight: false})
|
||||
invalidJSON := []byte("{invalid json")
|
||||
|
||||
_, err := EnrichVSCodeTheme(invalidJSON, colors)
|
||||
if err == nil {
|
||||
t.Error("Expected error for invalid JSON, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundTripConversion(t *testing.T) {
|
||||
testColors := []string{"#000000", "#ffffff", "#ff0000", "#00ff00", "#0000ff", "#625690", "#808080"}
|
||||
|
||||
@@ -635,23 +564,26 @@ func TestGeneratePaletteWithDPS(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := GeneratePalette(tt.base, tt.opts)
|
||||
|
||||
if len(result) != 16 {
|
||||
t.Errorf("GeneratePalette returned %d colors, expected 16", len(result))
|
||||
colors := []ColorInfo{
|
||||
result.Color0, result.Color1, result.Color2, result.Color3,
|
||||
result.Color4, result.Color5, result.Color6, result.Color7,
|
||||
result.Color8, result.Color9, result.Color10, result.Color11,
|
||||
result.Color12, result.Color13, result.Color14, result.Color15,
|
||||
}
|
||||
|
||||
for i, color := range result {
|
||||
if len(color) != 7 || color[0] != '#' {
|
||||
t.Errorf("Color at index %d (%s) is not a valid hex color", i, color)
|
||||
for i, color := range colors {
|
||||
if len(color.Hex) != 7 || color.Hex[0] != '#' {
|
||||
t.Errorf("Color at index %d (%s) is not a valid hex color", i, color.Hex)
|
||||
}
|
||||
}
|
||||
|
||||
bgColor := result[0]
|
||||
bgColor := result.Color0.Hex
|
||||
for i := 1; i < 8; i++ {
|
||||
lc := DeltaPhiStarContrast(result[i], bgColor, tt.opts.IsLight)
|
||||
lc := DeltaPhiStarContrast(colors[i].Hex, bgColor, tt.opts.IsLight)
|
||||
minLc := 30.0
|
||||
if lc < minLc && lc > 0 {
|
||||
t.Errorf("Color %d (%s) has insufficient DPS contrast %f with background %s (expected >= %f)",
|
||||
i, result[i], lc, bgColor, minLc)
|
||||
i, colors[i].Hex, lc, bgColor, minLc)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -708,17 +640,26 @@ func TestContrastAlgorithmComparison(t *testing.T) {
|
||||
paletteWCAG := GeneratePalette(base, optsWCAG)
|
||||
paletteDPS := GeneratePalette(base, optsDPS)
|
||||
|
||||
if len(paletteWCAG) != 16 || len(paletteDPS) != 16 {
|
||||
t.Fatal("Both palettes should have 16 colors")
|
||||
wcagColors := []ColorInfo{
|
||||
paletteWCAG.Color0, paletteWCAG.Color1, paletteWCAG.Color2, paletteWCAG.Color3,
|
||||
paletteWCAG.Color4, paletteWCAG.Color5, paletteWCAG.Color6, paletteWCAG.Color7,
|
||||
paletteWCAG.Color8, paletteWCAG.Color9, paletteWCAG.Color10, paletteWCAG.Color11,
|
||||
paletteWCAG.Color12, paletteWCAG.Color13, paletteWCAG.Color14, paletteWCAG.Color15,
|
||||
}
|
||||
dpsColors := []ColorInfo{
|
||||
paletteDPS.Color0, paletteDPS.Color1, paletteDPS.Color2, paletteDPS.Color3,
|
||||
paletteDPS.Color4, paletteDPS.Color5, paletteDPS.Color6, paletteDPS.Color7,
|
||||
paletteDPS.Color8, paletteDPS.Color9, paletteDPS.Color10, paletteDPS.Color11,
|
||||
paletteDPS.Color12, paletteDPS.Color13, paletteDPS.Color14, paletteDPS.Color15,
|
||||
}
|
||||
|
||||
if paletteWCAG[0] != paletteDPS[0] {
|
||||
t.Errorf("Background colors differ: WCAG=%s, DPS=%s", paletteWCAG[0], paletteDPS[0])
|
||||
if paletteWCAG.Color0.Hex != paletteDPS.Color0.Hex {
|
||||
t.Errorf("Background colors differ: WCAG=%s, DPS=%s", paletteWCAG.Color0.Hex, paletteDPS.Color0.Hex)
|
||||
}
|
||||
|
||||
differentCount := 0
|
||||
for i := 0; i < 16; i++ {
|
||||
if paletteWCAG[i] != paletteDPS[i] {
|
||||
if wcagColors[i].Hex != dpsColors[i].Hex {
|
||||
differentCount++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,135 +6,104 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GenerateJSON(colors []string) string {
|
||||
colorMap := make(map[string]string)
|
||||
|
||||
for i, color := range colors {
|
||||
colorMap[fmt.Sprintf("color%d", i)] = color
|
||||
}
|
||||
|
||||
marshalled, _ := json.Marshal(colorMap)
|
||||
|
||||
func GenerateJSON(p Palette) string {
|
||||
marshalled, _ := json.Marshal(p)
|
||||
return string(marshalled)
|
||||
}
|
||||
|
||||
func GenerateKittyTheme(colors []string) string {
|
||||
kittyColors := []struct {
|
||||
name string
|
||||
index int
|
||||
}{
|
||||
{"color0", 0},
|
||||
{"color1", 1},
|
||||
{"color2", 2},
|
||||
{"color3", 3},
|
||||
{"color4", 4},
|
||||
{"color5", 5},
|
||||
{"color6", 6},
|
||||
{"color7", 7},
|
||||
{"color8", 8},
|
||||
{"color9", 9},
|
||||
{"color10", 10},
|
||||
{"color11", 11},
|
||||
{"color12", 12},
|
||||
{"color13", 13},
|
||||
{"color14", 14},
|
||||
{"color15", 15},
|
||||
}
|
||||
|
||||
func GenerateKittyTheme(p Palette) string {
|
||||
var result strings.Builder
|
||||
for _, kc := range kittyColors {
|
||||
fmt.Fprintf(&result, "%s %s\n", kc.name, colors[kc.index])
|
||||
}
|
||||
fmt.Fprintf(&result, "color0 %s\n", p.Color0.Hex)
|
||||
fmt.Fprintf(&result, "color1 %s\n", p.Color1.Hex)
|
||||
fmt.Fprintf(&result, "color2 %s\n", p.Color2.Hex)
|
||||
fmt.Fprintf(&result, "color3 %s\n", p.Color3.Hex)
|
||||
fmt.Fprintf(&result, "color4 %s\n", p.Color4.Hex)
|
||||
fmt.Fprintf(&result, "color5 %s\n", p.Color5.Hex)
|
||||
fmt.Fprintf(&result, "color6 %s\n", p.Color6.Hex)
|
||||
fmt.Fprintf(&result, "color7 %s\n", p.Color7.Hex)
|
||||
fmt.Fprintf(&result, "color8 %s\n", p.Color8.Hex)
|
||||
fmt.Fprintf(&result, "color9 %s\n", p.Color9.Hex)
|
||||
fmt.Fprintf(&result, "color10 %s\n", p.Color10.Hex)
|
||||
fmt.Fprintf(&result, "color11 %s\n", p.Color11.Hex)
|
||||
fmt.Fprintf(&result, "color12 %s\n", p.Color12.Hex)
|
||||
fmt.Fprintf(&result, "color13 %s\n", p.Color13.Hex)
|
||||
fmt.Fprintf(&result, "color14 %s\n", p.Color14.Hex)
|
||||
fmt.Fprintf(&result, "color15 %s\n", p.Color15.Hex)
|
||||
return result.String()
|
||||
}
|
||||
|
||||
func GenerateFootTheme(colors []string) string {
|
||||
footColors := []struct {
|
||||
name string
|
||||
index int
|
||||
}{
|
||||
{"regular0", 0},
|
||||
{"regular1", 1},
|
||||
{"regular2", 2},
|
||||
{"regular3", 3},
|
||||
{"regular4", 4},
|
||||
{"regular5", 5},
|
||||
{"regular6", 6},
|
||||
{"regular7", 7},
|
||||
{"bright0", 8},
|
||||
{"bright1", 9},
|
||||
{"bright2", 10},
|
||||
{"bright3", 11},
|
||||
{"bright4", 12},
|
||||
{"bright5", 13},
|
||||
{"bright6", 14},
|
||||
{"bright7", 15},
|
||||
}
|
||||
|
||||
func GenerateFootTheme(p Palette) string {
|
||||
var result strings.Builder
|
||||
for _, fc := range footColors {
|
||||
fmt.Fprintf(&result, "%s=%s\n", fc.name, strings.TrimPrefix(colors[fc.index], "#"))
|
||||
}
|
||||
fmt.Fprintf(&result, "regular0=%s\n", p.Color0.HexStripped)
|
||||
fmt.Fprintf(&result, "regular1=%s\n", p.Color1.HexStripped)
|
||||
fmt.Fprintf(&result, "regular2=%s\n", p.Color2.HexStripped)
|
||||
fmt.Fprintf(&result, "regular3=%s\n", p.Color3.HexStripped)
|
||||
fmt.Fprintf(&result, "regular4=%s\n", p.Color4.HexStripped)
|
||||
fmt.Fprintf(&result, "regular5=%s\n", p.Color5.HexStripped)
|
||||
fmt.Fprintf(&result, "regular6=%s\n", p.Color6.HexStripped)
|
||||
fmt.Fprintf(&result, "regular7=%s\n", p.Color7.HexStripped)
|
||||
fmt.Fprintf(&result, "bright0=%s\n", p.Color8.HexStripped)
|
||||
fmt.Fprintf(&result, "bright1=%s\n", p.Color9.HexStripped)
|
||||
fmt.Fprintf(&result, "bright2=%s\n", p.Color10.HexStripped)
|
||||
fmt.Fprintf(&result, "bright3=%s\n", p.Color11.HexStripped)
|
||||
fmt.Fprintf(&result, "bright4=%s\n", p.Color12.HexStripped)
|
||||
fmt.Fprintf(&result, "bright5=%s\n", p.Color13.HexStripped)
|
||||
fmt.Fprintf(&result, "bright6=%s\n", p.Color14.HexStripped)
|
||||
fmt.Fprintf(&result, "bright7=%s\n", p.Color15.HexStripped)
|
||||
return result.String()
|
||||
}
|
||||
|
||||
func GenerateAlacrittyTheme(colors []string) string {
|
||||
alacrittyColors := []struct {
|
||||
section string
|
||||
name string
|
||||
index int
|
||||
}{
|
||||
{"normal", "black", 0},
|
||||
{"normal", "red", 1},
|
||||
{"normal", "green", 2},
|
||||
{"normal", "yellow", 3},
|
||||
{"normal", "blue", 4},
|
||||
{"normal", "magenta", 5},
|
||||
{"normal", "cyan", 6},
|
||||
{"normal", "white", 7},
|
||||
{"bright", "black", 8},
|
||||
{"bright", "red", 9},
|
||||
{"bright", "green", 10},
|
||||
{"bright", "yellow", 11},
|
||||
{"bright", "blue", 12},
|
||||
{"bright", "magenta", 13},
|
||||
{"bright", "cyan", 14},
|
||||
{"bright", "white", 15},
|
||||
}
|
||||
|
||||
func GenerateAlacrittyTheme(p Palette) string {
|
||||
var result strings.Builder
|
||||
currentSection := ""
|
||||
for _, ac := range alacrittyColors {
|
||||
if ac.section != currentSection {
|
||||
if currentSection != "" {
|
||||
result.WriteString("\n")
|
||||
}
|
||||
fmt.Fprintf(&result, "[colors.%s]\n", ac.section)
|
||||
currentSection = ac.section
|
||||
}
|
||||
fmt.Fprintf(&result, "%-7s = '%s'\n", ac.name, colors[ac.index])
|
||||
}
|
||||
result.WriteString("[colors.normal]\n")
|
||||
fmt.Fprintf(&result, "black = '%s'\n", p.Color0.Hex)
|
||||
fmt.Fprintf(&result, "red = '%s'\n", p.Color1.Hex)
|
||||
fmt.Fprintf(&result, "green = '%s'\n", p.Color2.Hex)
|
||||
fmt.Fprintf(&result, "yellow = '%s'\n", p.Color3.Hex)
|
||||
fmt.Fprintf(&result, "blue = '%s'\n", p.Color4.Hex)
|
||||
fmt.Fprintf(&result, "magenta = '%s'\n", p.Color5.Hex)
|
||||
fmt.Fprintf(&result, "cyan = '%s'\n", p.Color6.Hex)
|
||||
fmt.Fprintf(&result, "white = '%s'\n", p.Color7.Hex)
|
||||
result.WriteString("\n[colors.bright]\n")
|
||||
fmt.Fprintf(&result, "black = '%s'\n", p.Color8.Hex)
|
||||
fmt.Fprintf(&result, "red = '%s'\n", p.Color9.Hex)
|
||||
fmt.Fprintf(&result, "green = '%s'\n", p.Color10.Hex)
|
||||
fmt.Fprintf(&result, "yellow = '%s'\n", p.Color11.Hex)
|
||||
fmt.Fprintf(&result, "blue = '%s'\n", p.Color12.Hex)
|
||||
fmt.Fprintf(&result, "magenta = '%s'\n", p.Color13.Hex)
|
||||
fmt.Fprintf(&result, "cyan = '%s'\n", p.Color14.Hex)
|
||||
fmt.Fprintf(&result, "white = '%s'\n", p.Color15.Hex)
|
||||
return result.String()
|
||||
}
|
||||
|
||||
func GenerateGhosttyTheme(colors []string) string {
|
||||
func GenerateGhosttyTheme(p Palette) string {
|
||||
var result strings.Builder
|
||||
for i, color := range colors {
|
||||
fmt.Fprintf(&result, "palette = %d=%s\n", i, color)
|
||||
}
|
||||
fmt.Fprintf(&result, "palette = 0=%s\n", p.Color0.Hex)
|
||||
fmt.Fprintf(&result, "palette = 1=%s\n", p.Color1.Hex)
|
||||
fmt.Fprintf(&result, "palette = 2=%s\n", p.Color2.Hex)
|
||||
fmt.Fprintf(&result, "palette = 3=%s\n", p.Color3.Hex)
|
||||
fmt.Fprintf(&result, "palette = 4=%s\n", p.Color4.Hex)
|
||||
fmt.Fprintf(&result, "palette = 5=%s\n", p.Color5.Hex)
|
||||
fmt.Fprintf(&result, "palette = 6=%s\n", p.Color6.Hex)
|
||||
fmt.Fprintf(&result, "palette = 7=%s\n", p.Color7.Hex)
|
||||
fmt.Fprintf(&result, "palette = 8=%s\n", p.Color8.Hex)
|
||||
fmt.Fprintf(&result, "palette = 9=%s\n", p.Color9.Hex)
|
||||
fmt.Fprintf(&result, "palette = 10=%s\n", p.Color10.Hex)
|
||||
fmt.Fprintf(&result, "palette = 11=%s\n", p.Color11.Hex)
|
||||
fmt.Fprintf(&result, "palette = 12=%s\n", p.Color12.Hex)
|
||||
fmt.Fprintf(&result, "palette = 13=%s\n", p.Color13.Hex)
|
||||
fmt.Fprintf(&result, "palette = 14=%s\n", p.Color14.Hex)
|
||||
fmt.Fprintf(&result, "palette = 15=%s\n", p.Color15.Hex)
|
||||
return result.String()
|
||||
}
|
||||
|
||||
func GenerateWeztermTheme(colors []string) string {
|
||||
func GenerateWeztermTheme(p Palette) string {
|
||||
var result strings.Builder
|
||||
labels := []string{"ansi", "brights"}
|
||||
for j, label := range labels {
|
||||
start := j * 8
|
||||
colorSlice := make([]string, 8)
|
||||
for i, color := range colors[start : start+8] {
|
||||
colorSlice[i] = fmt.Sprintf("'%s'", color)
|
||||
}
|
||||
fmt.Fprintf(&result, "%s = [%s]\n", label, strings.Join(colorSlice, ", "))
|
||||
}
|
||||
fmt.Fprintf(&result, "ansi = ['%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s']\n",
|
||||
p.Color0.Hex, p.Color1.Hex, p.Color2.Hex, p.Color3.Hex,
|
||||
p.Color4.Hex, p.Color5.Hex, p.Color6.Hex, p.Color7.Hex)
|
||||
fmt.Fprintf(&result, "brights = ['%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s']\n",
|
||||
p.Color8.Hex, p.Color9.Hex, p.Color10.Hex, p.Color11.Hex,
|
||||
p.Color12.Hex, p.Color13.Hex, p.Color14.Hex, p.Color15.Hex)
|
||||
return result.String()
|
||||
}
|
||||
|
||||
@@ -1,250 +0,0 @@
|
||||
package dank16
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type VSCodeTheme struct {
|
||||
Schema string `json:"$schema"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Colors map[string]string `json:"colors"`
|
||||
TokenColors []VSCodeTokenColor `json:"tokenColors"`
|
||||
SemanticHighlighting bool `json:"semanticHighlighting"`
|
||||
SemanticTokenColors map[string]VSCodeTokenSetting `json:"semanticTokenColors"`
|
||||
}
|
||||
|
||||
type VSCodeTokenColor struct {
|
||||
Scope interface{} `json:"scope"`
|
||||
Settings VSCodeTokenSetting `json:"settings"`
|
||||
}
|
||||
|
||||
type VSCodeTokenSetting struct {
|
||||
Foreground string `json:"foreground,omitempty"`
|
||||
FontStyle string `json:"fontStyle,omitempty"`
|
||||
}
|
||||
|
||||
func updateTokenColor(tc interface{}, scopeToColor map[string]string) {
|
||||
tcMap, ok := tc.(map[string]interface{})
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
scopes, ok := tcMap["scope"].([]interface{})
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
settings, ok := tcMap["settings"].(map[string]interface{})
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
isYaml := hasScopeContaining(scopes, "yaml")
|
||||
|
||||
for _, scope := range scopes {
|
||||
scopeStr, ok := scope.(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if scopeStr == "string" && isYaml {
|
||||
continue
|
||||
}
|
||||
|
||||
if applyColorToScope(settings, scope, scopeToColor) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func applyColorToScope(settings map[string]interface{}, scope interface{}, scopeToColor map[string]string) bool {
|
||||
scopeStr, ok := scope.(string)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
newColor, exists := scopeToColor[scopeStr]
|
||||
if !exists {
|
||||
return false
|
||||
}
|
||||
|
||||
settings["foreground"] = newColor
|
||||
return true
|
||||
}
|
||||
|
||||
func hasScopeContaining(scopes []interface{}, substring string) bool {
|
||||
for _, scope := range scopes {
|
||||
scopeStr, ok := scope.(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
for i := 0; i <= len(scopeStr)-len(substring); i++ {
|
||||
if scopeStr[i:i+len(substring)] == substring {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func EnrichVSCodeTheme(themeData []byte, colors []string) ([]byte, error) {
|
||||
var theme map[string]interface{}
|
||||
if err := json.Unmarshal(themeData, &theme); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
colorsMap, ok := theme["colors"].(map[string]interface{})
|
||||
if !ok {
|
||||
colorsMap = make(map[string]interface{})
|
||||
theme["colors"] = colorsMap
|
||||
}
|
||||
|
||||
bg := colors[0]
|
||||
isLight := false
|
||||
if len(bg) == 7 && bg[0] == '#' {
|
||||
r, g, b := 0, 0, 0
|
||||
fmt.Sscanf(bg[1:], "%02x%02x%02x", &r, &g, &b)
|
||||
luminance := (0.299*float64(r) + 0.587*float64(g) + 0.114*float64(b)) / 255.0
|
||||
isLight = luminance > 0.5
|
||||
}
|
||||
|
||||
if isLight {
|
||||
theme["type"] = "light"
|
||||
} else {
|
||||
theme["type"] = "dark"
|
||||
}
|
||||
|
||||
colorsMap["terminal.ansiBlack"] = colors[0]
|
||||
colorsMap["terminal.ansiRed"] = colors[1]
|
||||
colorsMap["terminal.ansiGreen"] = colors[2]
|
||||
colorsMap["terminal.ansiYellow"] = colors[3]
|
||||
colorsMap["terminal.ansiBlue"] = colors[4]
|
||||
colorsMap["terminal.ansiMagenta"] = colors[5]
|
||||
colorsMap["terminal.ansiCyan"] = colors[6]
|
||||
colorsMap["terminal.ansiWhite"] = colors[7]
|
||||
colorsMap["terminal.ansiBrightBlack"] = colors[8]
|
||||
colorsMap["terminal.ansiBrightRed"] = colors[9]
|
||||
colorsMap["terminal.ansiBrightGreen"] = colors[10]
|
||||
colorsMap["terminal.ansiBrightYellow"] = colors[11]
|
||||
colorsMap["terminal.ansiBrightBlue"] = colors[12]
|
||||
colorsMap["terminal.ansiBrightMagenta"] = colors[13]
|
||||
colorsMap["terminal.ansiBrightCyan"] = colors[14]
|
||||
colorsMap["terminal.ansiBrightWhite"] = colors[15]
|
||||
|
||||
tokenColors, ok := theme["tokenColors"].([]interface{})
|
||||
if ok {
|
||||
scopeToColor := map[string]string{
|
||||
"comment": colors[8],
|
||||
"punctuation.definition.comment": colors[8],
|
||||
"keyword": colors[5],
|
||||
"storage.type": colors[13],
|
||||
"storage.modifier": colors[5],
|
||||
"variable": colors[15],
|
||||
"variable.parameter": colors[7],
|
||||
"meta.object-literal.key": colors[4],
|
||||
"meta.property.object": colors[4],
|
||||
"variable.other.property": colors[4],
|
||||
"constant.other.symbol": colors[12],
|
||||
"constant.numeric": colors[12],
|
||||
"constant.language": colors[12],
|
||||
"constant.character": colors[3],
|
||||
"entity.name.type": colors[12],
|
||||
"support.type": colors[13],
|
||||
"entity.name.class": colors[12],
|
||||
"entity.name.function": colors[2],
|
||||
"support.function": colors[2],
|
||||
"support.class": colors[15],
|
||||
"support.variable": colors[15],
|
||||
"variable.language": colors[12],
|
||||
"entity.name.tag.yaml": colors[12],
|
||||
"string.unquoted.plain.out.yaml": colors[15],
|
||||
"string.unquoted.yaml": colors[15],
|
||||
"string": colors[3],
|
||||
}
|
||||
|
||||
for i, tc := range tokenColors {
|
||||
updateTokenColor(tc, scopeToColor)
|
||||
tokenColors[i] = tc
|
||||
}
|
||||
|
||||
yamlRules := []VSCodeTokenColor{
|
||||
{
|
||||
Scope: "entity.name.tag.yaml",
|
||||
Settings: VSCodeTokenSetting{Foreground: colors[12]},
|
||||
},
|
||||
{
|
||||
Scope: []string{"string.unquoted.plain.out.yaml", "string.unquoted.yaml"},
|
||||
Settings: VSCodeTokenSetting{Foreground: colors[15]},
|
||||
},
|
||||
}
|
||||
|
||||
for _, rule := range yamlRules {
|
||||
tokenColors = append(tokenColors, rule)
|
||||
}
|
||||
|
||||
theme["tokenColors"] = tokenColors
|
||||
}
|
||||
|
||||
if semanticTokenColors, ok := theme["semanticTokenColors"].(map[string]interface{}); ok {
|
||||
updates := map[string]string{
|
||||
"variable": colors[15],
|
||||
"variable.readonly": colors[12],
|
||||
"property": colors[4],
|
||||
"function": colors[2],
|
||||
"method": colors[2],
|
||||
"type": colors[12],
|
||||
"class": colors[12],
|
||||
"typeParameter": colors[13],
|
||||
"enumMember": colors[12],
|
||||
"string": colors[3],
|
||||
"number": colors[12],
|
||||
"comment": colors[8],
|
||||
"keyword": colors[5],
|
||||
"operator": colors[15],
|
||||
"parameter": colors[7],
|
||||
"namespace": colors[15],
|
||||
}
|
||||
|
||||
for key, color := range updates {
|
||||
if existing, ok := semanticTokenColors[key].(map[string]interface{}); ok {
|
||||
existing["foreground"] = color
|
||||
} else {
|
||||
semanticTokenColors[key] = map[string]interface{}{
|
||||
"foreground": color,
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
semanticTokenColors := make(map[string]interface{})
|
||||
updates := map[string]string{
|
||||
"variable": colors[7],
|
||||
"variable.readonly": colors[12],
|
||||
"property": colors[4],
|
||||
"function": colors[2],
|
||||
"method": colors[2],
|
||||
"type": colors[12],
|
||||
"class": colors[12],
|
||||
"typeParameter": colors[13],
|
||||
"enumMember": colors[12],
|
||||
"string": colors[3],
|
||||
"number": colors[12],
|
||||
"comment": colors[8],
|
||||
"keyword": colors[5],
|
||||
"operator": colors[15],
|
||||
"parameter": colors[7],
|
||||
"namespace": colors[15],
|
||||
}
|
||||
|
||||
for key, color := range updates {
|
||||
semanticTokenColors[key] = map[string]interface{}{
|
||||
"foreground": color,
|
||||
}
|
||||
}
|
||||
theme["semanticTokenColors"] = semanticTokenColors
|
||||
}
|
||||
|
||||
return json.MarshalIndent(theme, "", " ")
|
||||
}
|
||||
@@ -37,6 +37,9 @@ func init() {
|
||||
Register("garuda", "#cba6f7", FamilyArch, func(config DistroConfig, logChan chan<- string) Distribution {
|
||||
return NewArchDistribution(config, logChan)
|
||||
})
|
||||
Register("artix", "#1793D1", FamilyArch, func(config DistroConfig, logChan chan<- string) Distribution {
|
||||
return NewArchDistribution(config, logChan)
|
||||
})
|
||||
}
|
||||
|
||||
type ArchDistribution struct {
|
||||
|
||||
@@ -19,10 +19,12 @@ func init() {
|
||||
Register("fedora-asahi-remix", "#0B57A4", FamilyFedora, func(config DistroConfig, logChan chan<- string) Distribution {
|
||||
return NewFedoraDistribution(config, logChan)
|
||||
})
|
||||
|
||||
Register("bluefin", "#0B57A4", FamilyFedora, func(config DistroConfig, logChan chan<- string) Distribution {
|
||||
return NewFedoraDistribution(config, logChan)
|
||||
})
|
||||
Register("ultramarine", "#00078b", FamilyFedora, func(config DistroConfig, logChan chan<- string) Distribution {
|
||||
return NewFedoraDistribution(config, logChan)
|
||||
})
|
||||
}
|
||||
|
||||
type FedoraDistribution struct {
|
||||
@@ -506,6 +508,14 @@ func (f *FedoraDistribution) installDNFPackages(ctx context.Context, packages []
|
||||
f.log(fmt.Sprintf("Installing DNF packages: %s", strings.Join(packages, ", ")))
|
||||
|
||||
args := []string{"dnf", "install", "-y"}
|
||||
|
||||
for _, pkg := range packages {
|
||||
if pkg == "niri" || pkg == "niri-git" {
|
||||
args = append(args, "--setopt=install_weak_deps=False")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
args = append(args, packages...)
|
||||
|
||||
progressChan <- InstallProgressMsg{
|
||||
|
||||
@@ -509,6 +509,52 @@ func (_c *MockBackend_DisconnectWiFi_Call) RunAndReturn(run func() error) *MockB
|
||||
return _c
|
||||
}
|
||||
|
||||
// DisconnectWiFiDevice provides a mock function with given fields: device
|
||||
func (_m *MockBackend) DisconnectWiFiDevice(device string) error {
|
||||
ret := _m.Called(device)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DisconnectWiFiDevice")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(device)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockBackend_DisconnectWiFiDevice_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DisconnectWiFiDevice'
|
||||
type MockBackend_DisconnectWiFiDevice_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// DisconnectWiFiDevice is a helper method to define mock.On call
|
||||
// - device string
|
||||
func (_e *MockBackend_Expecter) DisconnectWiFiDevice(device interface{}) *MockBackend_DisconnectWiFiDevice_Call {
|
||||
return &MockBackend_DisconnectWiFiDevice_Call{Call: _e.mock.On("DisconnectWiFiDevice", device)}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_DisconnectWiFiDevice_Call) Run(run func(device string)) *MockBackend_DisconnectWiFiDevice_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_DisconnectWiFiDevice_Call) Return(_a0 error) *MockBackend_DisconnectWiFiDevice_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_DisconnectWiFiDevice_Call) RunAndReturn(run func(string) error) *MockBackend_DisconnectWiFiDevice_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ForgetWiFiNetwork provides a mock function with given fields: ssid
|
||||
func (_m *MockBackend) ForgetWiFiNetwork(ssid string) error {
|
||||
ret := _m.Called(ssid)
|
||||
@@ -659,6 +705,53 @@ func (_c *MockBackend_GetPromptBroker_Call) RunAndReturn(run func() network.Prom
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetWiFiDevices provides a mock function with no fields
|
||||
func (_m *MockBackend) GetWiFiDevices() []network.WiFiDevice {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetWiFiDevices")
|
||||
}
|
||||
|
||||
var r0 []network.WiFiDevice
|
||||
if rf, ok := ret.Get(0).(func() []network.WiFiDevice); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]network.WiFiDevice)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockBackend_GetWiFiDevices_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetWiFiDevices'
|
||||
type MockBackend_GetWiFiDevices_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetWiFiDevices is a helper method to define mock.On call
|
||||
func (_e *MockBackend_Expecter) GetWiFiDevices() *MockBackend_GetWiFiDevices_Call {
|
||||
return &MockBackend_GetWiFiDevices_Call{Call: _e.mock.On("GetWiFiDevices")}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_GetWiFiDevices_Call) Run(run func()) *MockBackend_GetWiFiDevices_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_GetWiFiDevices_Call) Return(_a0 []network.WiFiDevice) *MockBackend_GetWiFiDevices_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_GetWiFiDevices_Call) RunAndReturn(run func() []network.WiFiDevice) *MockBackend_GetWiFiDevices_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetWiFiEnabled provides a mock function with no fields
|
||||
func (_m *MockBackend) GetWiFiEnabled() (bool, error) {
|
||||
ret := _m.Called()
|
||||
@@ -1091,6 +1184,52 @@ func (_c *MockBackend_ScanWiFi_Call) RunAndReturn(run func() error) *MockBackend
|
||||
return _c
|
||||
}
|
||||
|
||||
// ScanWiFiDevice provides a mock function with given fields: device
|
||||
func (_m *MockBackend) ScanWiFiDevice(device string) error {
|
||||
ret := _m.Called(device)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ScanWiFiDevice")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(device)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockBackend_ScanWiFiDevice_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ScanWiFiDevice'
|
||||
type MockBackend_ScanWiFiDevice_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ScanWiFiDevice is a helper method to define mock.On call
|
||||
// - device string
|
||||
func (_e *MockBackend_Expecter) ScanWiFiDevice(device interface{}) *MockBackend_ScanWiFiDevice_Call {
|
||||
return &MockBackend_ScanWiFiDevice_Call{Call: _e.mock.On("ScanWiFiDevice", device)}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_ScanWiFiDevice_Call) Run(run func(device string)) *MockBackend_ScanWiFiDevice_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_ScanWiFiDevice_Call) Return(_a0 error) *MockBackend_ScanWiFiDevice_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_ScanWiFiDevice_Call) RunAndReturn(run func(string) error) *MockBackend_ScanWiFiDevice_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetPromptBroker provides a mock function with given fields: broker
|
||||
func (_m *MockBackend) SetPromptBroker(broker network.PromptBroker) error {
|
||||
ret := _m.Called(broker)
|
||||
|
||||
144
core/internal/mocks/version/mock_VersionFetcher.go
Normal file
144
core/internal/mocks/version/mock_VersionFetcher.go
Normal file
@@ -0,0 +1,144 @@
|
||||
// Code generated by mockery v2.53.5. DO NOT EDIT.
|
||||
|
||||
package mocks_version
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
|
||||
// MockVersionFetcher is an autogenerated mock type for the VersionFetcher type
|
||||
type MockVersionFetcher struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type MockVersionFetcher_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockVersionFetcher) EXPECT() *MockVersionFetcher_Expecter {
|
||||
return &MockVersionFetcher_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// GetCurrentVersion provides a mock function with given fields: dmsPath
|
||||
func (_m *MockVersionFetcher) GetCurrentVersion(dmsPath string) (string, error) {
|
||||
ret := _m.Called(dmsPath)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetCurrentVersion")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string) (string, error)); ok {
|
||||
return rf(dmsPath)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string) string); ok {
|
||||
r0 = rf(dmsPath)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(dmsPath)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockVersionFetcher_GetCurrentVersion_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetCurrentVersion'
|
||||
type MockVersionFetcher_GetCurrentVersion_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetCurrentVersion is a helper method to define mock.On call
|
||||
// - dmsPath string
|
||||
func (_e *MockVersionFetcher_Expecter) GetCurrentVersion(dmsPath interface{}) *MockVersionFetcher_GetCurrentVersion_Call {
|
||||
return &MockVersionFetcher_GetCurrentVersion_Call{Call: _e.mock.On("GetCurrentVersion", dmsPath)}
|
||||
}
|
||||
|
||||
func (_c *MockVersionFetcher_GetCurrentVersion_Call) Run(run func(dmsPath string)) *MockVersionFetcher_GetCurrentVersion_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockVersionFetcher_GetCurrentVersion_Call) Return(_a0 string, _a1 error) *MockVersionFetcher_GetCurrentVersion_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockVersionFetcher_GetCurrentVersion_Call) RunAndReturn(run func(string) (string, error)) *MockVersionFetcher_GetCurrentVersion_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetLatestVersion provides a mock function with given fields: dmsPath
|
||||
func (_m *MockVersionFetcher) GetLatestVersion(dmsPath string) (string, error) {
|
||||
ret := _m.Called(dmsPath)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetLatestVersion")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string) (string, error)); ok {
|
||||
return rf(dmsPath)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string) string); ok {
|
||||
r0 = rf(dmsPath)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(dmsPath)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockVersionFetcher_GetLatestVersion_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLatestVersion'
|
||||
type MockVersionFetcher_GetLatestVersion_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetLatestVersion is a helper method to define mock.On call
|
||||
// - dmsPath string
|
||||
func (_e *MockVersionFetcher_Expecter) GetLatestVersion(dmsPath interface{}) *MockVersionFetcher_GetLatestVersion_Call {
|
||||
return &MockVersionFetcher_GetLatestVersion_Call{Call: _e.mock.On("GetLatestVersion", dmsPath)}
|
||||
}
|
||||
|
||||
func (_c *MockVersionFetcher_GetLatestVersion_Call) Run(run func(dmsPath string)) *MockVersionFetcher_GetLatestVersion_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockVersionFetcher_GetLatestVersion_Call) Return(_a0 string, _a1 error) *MockVersionFetcher_GetLatestVersion_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockVersionFetcher_GetLatestVersion_Call) RunAndReturn(run func(string) (string, error)) *MockVersionFetcher_GetLatestVersion_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockVersionFetcher creates a new instance of MockVersionFetcher. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockVersionFetcher(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockVersionFetcher {
|
||||
mock := &MockVersionFetcher{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
|
||||
@@ -125,8 +126,9 @@ func (a *SecretAgent) GetSecrets(
|
||||
connType, displayName, vpnSvc := readConnTypeAndName(conn)
|
||||
ssid := readSSID(conn)
|
||||
fields := fieldsNeeded(settingName, hints)
|
||||
vpnPasswordFlags := readVPNPasswordFlags(conn, settingName)
|
||||
|
||||
log.Infof("[SecretAgent] connType=%s, name=%s, vpnSvc=%s, fields=%v, flags=%d", connType, displayName, vpnSvc, fields, flags)
|
||||
log.Infof("[SecretAgent] connType=%s, name=%s, vpnSvc=%s, fields=%v, flags=%d, vpnPasswordFlags=%d", connType, displayName, vpnSvc, fields, flags, vpnPasswordFlags)
|
||||
|
||||
if a.backend != nil {
|
||||
a.backend.stateMutex.RLock()
|
||||
@@ -163,57 +165,70 @@ func (a *SecretAgent) GetSecrets(
|
||||
}
|
||||
|
||||
if len(fields) == 0 {
|
||||
// For VPN connections with no hints, we can't provide a proper UI.
|
||||
// Defer to other agents (like nm-applet or VPN-specific auth dialogs)
|
||||
// that can handle the VPN type properly (e.g., OpenConnect with SAML, etc.)
|
||||
if settingName == "vpn" {
|
||||
log.Infof("[SecretAgent] VPN with empty hints - deferring to other agents for %s", vpnSvc)
|
||||
return nil, dbus.NewError("org.freedesktop.NetworkManager.SecretAgent.Error.NoSecrets", nil)
|
||||
}
|
||||
if a.backend != nil {
|
||||
a.backend.stateMutex.RLock()
|
||||
isConnectingVPN := a.backend.state.IsConnectingVPN
|
||||
a.backend.stateMutex.RUnlock()
|
||||
|
||||
const (
|
||||
NM_SETTING_SECRET_FLAG_NONE = 0
|
||||
NM_SETTING_SECRET_FLAG_AGENT_OWNED = 1
|
||||
NM_SETTING_SECRET_FLAG_NOT_SAVED = 2
|
||||
NM_SETTING_SECRET_FLAG_NOT_REQUIRED = 4
|
||||
)
|
||||
if !isConnectingVPN {
|
||||
log.Infof("[SecretAgent] VPN with empty hints - deferring to other agents for %s", vpnSvc)
|
||||
return nil, dbus.NewError("org.freedesktop.NetworkManager.SecretAgent.Error.NoSecrets", nil)
|
||||
}
|
||||
|
||||
var passwordFlags uint32 = 0xFFFF
|
||||
switch settingName {
|
||||
case "802-11-wireless-security":
|
||||
if wifiSecSettings, ok := conn["802-11-wireless-security"]; ok {
|
||||
if flagsVariant, ok := wifiSecSettings["psk-flags"]; ok {
|
||||
if pwdFlags, ok := flagsVariant.Value().(uint32); ok {
|
||||
passwordFlags = pwdFlags
|
||||
}
|
||||
}
|
||||
}
|
||||
case "802-1x":
|
||||
if dot1xSettings, ok := conn["802-1x"]; ok {
|
||||
if flagsVariant, ok := dot1xSettings["password-flags"]; ok {
|
||||
if pwdFlags, ok := flagsVariant.Value().(uint32); ok {
|
||||
passwordFlags = pwdFlags
|
||||
}
|
||||
}
|
||||
log.Infof("[SecretAgent] VPN with empty hints but we're connecting - prompting for password")
|
||||
fields = []string{"password"}
|
||||
} else {
|
||||
log.Infof("[SecretAgent] VPN with empty hints - deferring to other agents for %s", vpnSvc)
|
||||
return nil, dbus.NewError("org.freedesktop.NetworkManager.SecretAgent.Error.NoSecrets", nil)
|
||||
}
|
||||
}
|
||||
|
||||
if passwordFlags == 0xFFFF {
|
||||
log.Warnf("[SecretAgent] Could not determine password-flags for empty hints - returning NoSecrets error")
|
||||
return nil, dbus.NewError("org.freedesktop.NetworkManager.SecretAgent.Error.NoSecrets", nil)
|
||||
} else if passwordFlags&NM_SETTING_SECRET_FLAG_NOT_REQUIRED != 0 {
|
||||
log.Infof("[SecretAgent] Secrets not required (flags=%d)", passwordFlags)
|
||||
out := nmSettingMap{}
|
||||
out[settingName] = nmVariantMap{}
|
||||
return out, nil
|
||||
} else if passwordFlags&NM_SETTING_SECRET_FLAG_AGENT_OWNED != 0 {
|
||||
log.Warnf("[SecretAgent] Secrets are agent-owned but we don't store secrets (flags=%d) - returning NoSecrets error", passwordFlags)
|
||||
return nil, dbus.NewError("org.freedesktop.NetworkManager.SecretAgent.Error.NoSecrets", nil)
|
||||
} else {
|
||||
log.Infof("[SecretAgent] No secrets needed, using system stored secrets (flags=%d)", passwordFlags)
|
||||
out := nmSettingMap{}
|
||||
out[settingName] = nmVariantMap{}
|
||||
return out, nil
|
||||
if len(fields) == 0 {
|
||||
const (
|
||||
NM_SETTING_SECRET_FLAG_NONE = 0
|
||||
NM_SETTING_SECRET_FLAG_AGENT_OWNED = 1
|
||||
NM_SETTING_SECRET_FLAG_NOT_SAVED = 2
|
||||
NM_SETTING_SECRET_FLAG_NOT_REQUIRED = 4
|
||||
)
|
||||
|
||||
var passwordFlags uint32 = 0xFFFF
|
||||
switch settingName {
|
||||
case "802-11-wireless-security":
|
||||
if wifiSecSettings, ok := conn["802-11-wireless-security"]; ok {
|
||||
if flagsVariant, ok := wifiSecSettings["psk-flags"]; ok {
|
||||
if pwdFlags, ok := flagsVariant.Value().(uint32); ok {
|
||||
passwordFlags = pwdFlags
|
||||
}
|
||||
}
|
||||
}
|
||||
case "802-1x":
|
||||
if dot1xSettings, ok := conn["802-1x"]; ok {
|
||||
if flagsVariant, ok := dot1xSettings["password-flags"]; ok {
|
||||
if pwdFlags, ok := flagsVariant.Value().(uint32); ok {
|
||||
passwordFlags = pwdFlags
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if passwordFlags == 0xFFFF {
|
||||
log.Warnf("[SecretAgent] Could not determine password-flags for empty hints - returning NoSecrets error")
|
||||
return nil, dbus.NewError("org.freedesktop.NetworkManager.SecretAgent.Error.NoSecrets", nil)
|
||||
} else if passwordFlags&NM_SETTING_SECRET_FLAG_NOT_REQUIRED != 0 {
|
||||
log.Infof("[SecretAgent] Secrets not required (flags=%d)", passwordFlags)
|
||||
out := nmSettingMap{}
|
||||
out[settingName] = nmVariantMap{}
|
||||
return out, nil
|
||||
} else if passwordFlags&NM_SETTING_SECRET_FLAG_AGENT_OWNED != 0 {
|
||||
log.Warnf("[SecretAgent] Secrets are agent-owned but we don't store secrets (flags=%d) - returning NoSecrets error", passwordFlags)
|
||||
return nil, dbus.NewError("org.freedesktop.NetworkManager.SecretAgent.Error.NoSecrets", nil)
|
||||
} else {
|
||||
log.Infof("[SecretAgent] No secrets needed, using system stored secrets (flags=%d)", passwordFlags)
|
||||
out := nmSettingMap{}
|
||||
out[settingName] = nmVariantMap{}
|
||||
return out, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,13 +358,11 @@ func (a *SecretAgent) GetSecrets(
|
||||
// Update settings based on type
|
||||
switch settingName {
|
||||
case "vpn":
|
||||
// Set password-flags=0 and add secrets to vpn section
|
||||
vpn, ok := existingSettings["vpn"]
|
||||
if !ok {
|
||||
vpn = make(map[string]dbus.Variant)
|
||||
}
|
||||
|
||||
// Get existing data map (vpn.data is string->string)
|
||||
var data map[string]string
|
||||
if dataVariant, ok := vpn["data"]; ok {
|
||||
if dm, ok := dataVariant.Value().(map[string]string); ok {
|
||||
@@ -364,11 +377,9 @@ func (a *SecretAgent) GetSecrets(
|
||||
data = make(map[string]string)
|
||||
}
|
||||
|
||||
// Update password-flags to 0 (system-stored)
|
||||
data["password-flags"] = "0"
|
||||
vpn["data"] = dbus.MakeVariant(data)
|
||||
|
||||
// Add secrets (vpn.secrets is string->string)
|
||||
secs := make(map[string]string)
|
||||
for k, v := range reply.Secrets {
|
||||
secs[k] = v
|
||||
@@ -379,14 +390,12 @@ func (a *SecretAgent) GetSecrets(
|
||||
log.Infof("[SecretAgent] Updated VPN settings: password-flags=0, secrets with %d fields", len(secs))
|
||||
|
||||
case "802-11-wireless-security":
|
||||
// Set psk-flags=0 for WiFi
|
||||
wifiSec, ok := existingSettings["802-11-wireless-security"]
|
||||
if !ok {
|
||||
wifiSec = make(map[string]dbus.Variant)
|
||||
}
|
||||
wifiSec["psk-flags"] = dbus.MakeVariant(uint32(0))
|
||||
|
||||
// Add PSK secret
|
||||
if psk, ok := reply.Secrets["psk"]; ok {
|
||||
wifiSec["psk"] = dbus.MakeVariant(psk)
|
||||
log.Infof("[SecretAgent] Updated WiFi settings: psk-flags=0")
|
||||
@@ -394,14 +403,12 @@ func (a *SecretAgent) GetSecrets(
|
||||
settings["802-11-wireless-security"] = wifiSec
|
||||
|
||||
case "802-1x":
|
||||
// Set password-flags=0 for 802.1x
|
||||
dot1x, ok := existingSettings["802-1x"]
|
||||
if !ok {
|
||||
dot1x = make(map[string]dbus.Variant)
|
||||
}
|
||||
dot1x["password-flags"] = dbus.MakeVariant(uint32(0))
|
||||
|
||||
// Add password secret
|
||||
if password, ok := reply.Secrets["password"]; ok {
|
||||
dot1x["password"] = dbus.MakeVariant(password)
|
||||
log.Infof("[SecretAgent] Updated 802.1x settings: password-flags=0")
|
||||
@@ -507,6 +514,39 @@ func fieldsNeeded(setting string, hints []string) []string {
|
||||
}
|
||||
}
|
||||
|
||||
func readVPNPasswordFlags(conn map[string]nmVariantMap, settingName string) uint32 {
|
||||
if settingName != "vpn" {
|
||||
return 0xFFFF
|
||||
}
|
||||
|
||||
vpnSettings, ok := conn["vpn"]
|
||||
if !ok {
|
||||
return 0xFFFF
|
||||
}
|
||||
|
||||
dataVariant, ok := vpnSettings["data"]
|
||||
if !ok {
|
||||
return 0xFFFF
|
||||
}
|
||||
|
||||
dataMap, ok := dataVariant.Value().(map[string]string)
|
||||
if !ok {
|
||||
return 0xFFFF
|
||||
}
|
||||
|
||||
flagsStr, ok := dataMap["password-flags"]
|
||||
if !ok {
|
||||
return 0xFFFF
|
||||
}
|
||||
|
||||
flags64, err := strconv.ParseUint(flagsStr, 10, 32)
|
||||
if err != nil {
|
||||
return 0xFFFF
|
||||
}
|
||||
|
||||
return uint32(flags64)
|
||||
}
|
||||
|
||||
func reasonFromFlags(flags uint32) string {
|
||||
const (
|
||||
NM_SECRET_AGENT_GET_SECRETS_FLAG_NONE = 0x0
|
||||
|
||||
@@ -8,10 +8,13 @@ type Backend interface {
|
||||
SetWiFiEnabled(enabled bool) error
|
||||
|
||||
ScanWiFi() error
|
||||
ScanWiFiDevice(device string) error
|
||||
GetWiFiNetworkDetails(ssid string) (*NetworkInfoResponse, error)
|
||||
GetWiFiDevices() []WiFiDevice
|
||||
|
||||
ConnectWiFi(req ConnectionRequest) error
|
||||
DisconnectWiFi() error
|
||||
DisconnectWiFiDevice(device string) error
|
||||
ForgetWiFiNetwork(ssid string) error
|
||||
SetWiFiAutoconnect(ssid string, autoconnect bool) error
|
||||
|
||||
@@ -54,11 +57,13 @@ type BackendState struct {
|
||||
WiFiBSSID string
|
||||
WiFiSignal uint8
|
||||
WiFiNetworks []WiFiNetwork
|
||||
WiFiDevices []WiFiDevice
|
||||
WiredConnections []WiredConnection
|
||||
VPNProfiles []VPNProfile
|
||||
VPNActive []VPNActive
|
||||
IsConnecting bool
|
||||
ConnectingSSID string
|
||||
ConnectingDevice string
|
||||
IsConnectingVPN bool
|
||||
ConnectingVPNUUID string
|
||||
LastError string
|
||||
|
||||
@@ -196,3 +196,15 @@ func (b *HybridIwdNetworkdBackend) CancelCredentials(token string) error {
|
||||
func (b *HybridIwdNetworkdBackend) SetWiFiAutoconnect(ssid string, autoconnect bool) error {
|
||||
return b.wifi.SetWiFiAutoconnect(ssid, autoconnect)
|
||||
}
|
||||
|
||||
func (b *HybridIwdNetworkdBackend) ScanWiFiDevice(device string) error {
|
||||
return b.wifi.ScanWiFiDevice(device)
|
||||
}
|
||||
|
||||
func (b *HybridIwdNetworkdBackend) DisconnectWiFiDevice(device string) error {
|
||||
return b.wifi.DisconnectWiFiDevice(device)
|
||||
}
|
||||
|
||||
func (b *HybridIwdNetworkdBackend) GetWiFiDevices() []WiFiDevice {
|
||||
return b.wifi.GetWiFiDevices()
|
||||
}
|
||||
|
||||
@@ -139,9 +139,13 @@ func (b *IWDBackend) discoverDevices() error {
|
||||
}
|
||||
|
||||
func (b *IWDBackend) GetCurrentState() (*BackendState, error) {
|
||||
b.stateMutex.RLock()
|
||||
defer b.stateMutex.RUnlock()
|
||||
|
||||
state := *b.state
|
||||
state.WiFiNetworks = append([]WiFiNetwork(nil), b.state.WiFiNetworks...)
|
||||
state.WiredConnections = append([]WiredConnection(nil), b.state.WiredConnections...)
|
||||
state.WiFiDevices = b.getWiFiDevicesLocked()
|
||||
|
||||
return &state, nil
|
||||
}
|
||||
|
||||
@@ -45,3 +45,38 @@ func (b *IWDBackend) DisconnectAllVPN() error {
|
||||
func (b *IWDBackend) ClearVPNCredentials(uuidOrName string) error {
|
||||
return fmt.Errorf("VPN not supported by iwd backend")
|
||||
}
|
||||
|
||||
func (b *IWDBackend) ScanWiFiDevice(device string) error {
|
||||
return b.ScanWiFi()
|
||||
}
|
||||
|
||||
func (b *IWDBackend) DisconnectWiFiDevice(device string) error {
|
||||
return b.DisconnectWiFi()
|
||||
}
|
||||
|
||||
func (b *IWDBackend) GetWiFiDevices() []WiFiDevice {
|
||||
b.stateMutex.RLock()
|
||||
defer b.stateMutex.RUnlock()
|
||||
return b.getWiFiDevicesLocked()
|
||||
}
|
||||
|
||||
func (b *IWDBackend) getWiFiDevicesLocked() []WiFiDevice {
|
||||
if b.state.WiFiDevice == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
stateStr := "disconnected"
|
||||
if b.state.WiFiConnected {
|
||||
stateStr = "connected"
|
||||
}
|
||||
|
||||
return []WiFiDevice{{
|
||||
Name: b.state.WiFiDevice,
|
||||
State: stateStr,
|
||||
Connected: b.state.WiFiConnected,
|
||||
SSID: b.state.WiFiSSID,
|
||||
Signal: b.state.WiFiSignal,
|
||||
IP: b.state.WiFiIP,
|
||||
Networks: b.state.WiFiNetworks,
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -57,3 +57,15 @@ func (b *SystemdNetworkdBackend) ClearVPNCredentials(uuidOrName string) error {
|
||||
func (b *SystemdNetworkdBackend) SetWiFiAutoconnect(ssid string, autoconnect bool) error {
|
||||
return fmt.Errorf("WiFi autoconnect not supported by networkd backend")
|
||||
}
|
||||
|
||||
func (b *SystemdNetworkdBackend) ScanWiFiDevice(device string) error {
|
||||
return fmt.Errorf("WiFi scan not supported by networkd backend")
|
||||
}
|
||||
|
||||
func (b *SystemdNetworkdBackend) DisconnectWiFiDevice(device string) error {
|
||||
return fmt.Errorf("WiFi disconnect not supported by networkd backend")
|
||||
}
|
||||
|
||||
func (b *SystemdNetworkdBackend) GetWiFiDevices() []WiFiDevice {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -30,12 +30,20 @@ const (
|
||||
NmDeviceStateReasonNewActivation = 60
|
||||
)
|
||||
|
||||
type wifiDeviceInfo struct {
|
||||
device gonetworkmanager.Device
|
||||
wireless gonetworkmanager.DeviceWireless
|
||||
name string
|
||||
hwAddress string
|
||||
}
|
||||
|
||||
type NetworkManagerBackend struct {
|
||||
nmConn interface{}
|
||||
ethernetDevice interface{}
|
||||
wifiDevice interface{}
|
||||
settings interface{}
|
||||
wifiDev interface{}
|
||||
wifiDevices map[string]*wifiDeviceInfo
|
||||
|
||||
dbusConn *dbus.Conn
|
||||
signals chan *dbus.Signal
|
||||
@@ -71,8 +79,9 @@ func NewNetworkManagerBackend(nmConn ...gonetworkmanager.NetworkManager) (*Netwo
|
||||
}
|
||||
|
||||
backend := &NetworkManagerBackend{
|
||||
nmConn: nm,
|
||||
stopChan: make(chan struct{}),
|
||||
nmConn: nm,
|
||||
stopChan: make(chan struct{}),
|
||||
wifiDevices: make(map[string]*wifiDeviceInfo),
|
||||
state: &BackendState{
|
||||
Backend: "networkmanager",
|
||||
},
|
||||
@@ -114,27 +123,48 @@ func (b *NetworkManagerBackend) Initialize() error {
|
||||
}
|
||||
|
||||
case gonetworkmanager.NmDeviceTypeWifi:
|
||||
b.wifiDevice = dev
|
||||
if w, err := gonetworkmanager.NewDeviceWireless(dev.GetPath()); err == nil {
|
||||
b.wifiDev = w
|
||||
}
|
||||
wifiEnabled, err := nm.GetPropertyWirelessEnabled()
|
||||
if err == nil {
|
||||
b.stateMutex.Lock()
|
||||
b.state.WiFiEnabled = wifiEnabled
|
||||
b.stateMutex.Unlock()
|
||||
}
|
||||
if err := b.updateWiFiState(); err != nil {
|
||||
iface, err := dev.GetPropertyInterface()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if wifiEnabled {
|
||||
if _, err := b.updateWiFiNetworks(); err != nil {
|
||||
log.Warnf("Failed to get initial networks: %v", err)
|
||||
}
|
||||
w, err := gonetworkmanager.NewDeviceWireless(dev.GetPath())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
hwAddr, _ := w.GetPropertyHwAddress()
|
||||
|
||||
b.wifiDevices[iface] = &wifiDeviceInfo{
|
||||
device: dev,
|
||||
wireless: w,
|
||||
name: iface,
|
||||
hwAddress: hwAddr,
|
||||
}
|
||||
|
||||
if b.wifiDevice == nil {
|
||||
b.wifiDevice = dev
|
||||
b.wifiDev = w
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wifiEnabled, err := nm.GetPropertyWirelessEnabled()
|
||||
if err == nil {
|
||||
b.stateMutex.Lock()
|
||||
b.state.WiFiEnabled = wifiEnabled
|
||||
b.stateMutex.Unlock()
|
||||
}
|
||||
|
||||
if err := b.updateWiFiState(); err != nil {
|
||||
log.Warnf("Failed to update WiFi state: %v", err)
|
||||
}
|
||||
|
||||
if wifiEnabled {
|
||||
if _, err := b.updateWiFiNetworks(); err != nil {
|
||||
log.Warnf("Failed to get initial networks: %v", err)
|
||||
}
|
||||
b.updateAllWiFiDevices()
|
||||
}
|
||||
|
||||
if err := b.updatePrimaryConnection(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -165,6 +195,7 @@ func (b *NetworkManagerBackend) GetCurrentState() (*BackendState, error) {
|
||||
|
||||
state := *b.state
|
||||
state.WiFiNetworks = append([]WiFiNetwork(nil), b.state.WiFiNetworks...)
|
||||
state.WiFiDevices = append([]WiFiDevice(nil), b.state.WiFiDevices...)
|
||||
state.WiredConnections = append([]WiredConnection(nil), b.state.WiredConnections...)
|
||||
state.VPNProfiles = append([]VPNProfile(nil), b.state.VPNProfiles...)
|
||||
state.VPNActive = append([]VPNActive(nil), b.state.VPNActive...)
|
||||
|
||||
@@ -235,7 +235,7 @@ func (b *NetworkManagerBackend) ConnectVPN(uuidOrName string, singleActive bool)
|
||||
}
|
||||
|
||||
nm := b.nmConn.(gonetworkmanager.NetworkManager)
|
||||
activeConn, err := nm.ActivateConnection(targetConn, nil, nil)
|
||||
_, err = nm.ActivateConnection(targetConn, nil, nil)
|
||||
if err != nil {
|
||||
b.stateMutex.Lock()
|
||||
b.state.IsConnectingVPN = false
|
||||
@@ -249,20 +249,6 @@ func (b *NetworkManagerBackend) ConnectVPN(uuidOrName string, singleActive bool)
|
||||
return fmt.Errorf("failed to activate VPN: %w", err)
|
||||
}
|
||||
|
||||
if activeConn != nil {
|
||||
state, _ := activeConn.GetPropertyState()
|
||||
if state == 2 {
|
||||
b.stateMutex.Lock()
|
||||
b.state.IsConnectingVPN = false
|
||||
b.state.ConnectingVPNUUID = ""
|
||||
b.stateMutex.Unlock()
|
||||
b.ListActiveVPN()
|
||||
if b.onStateChange != nil {
|
||||
b.onStateChange()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -197,21 +197,23 @@ func (b *NetworkManagerBackend) GetWiFiNetworkDetails(ssid string) (*NetworkInfo
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) ConnectWiFi(req ConnectionRequest) error {
|
||||
if b.wifiDevice == nil {
|
||||
return fmt.Errorf("no WiFi device available")
|
||||
devInfo, err := b.getWifiDeviceForConnection(req.Device)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.stateMutex.RLock()
|
||||
alreadyConnected := b.state.WiFiConnected && b.state.WiFiSSID == req.SSID
|
||||
b.stateMutex.RUnlock()
|
||||
|
||||
if alreadyConnected && !req.Interactive {
|
||||
if alreadyConnected && !req.Interactive && req.Device == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
b.stateMutex.Lock()
|
||||
b.state.IsConnecting = true
|
||||
b.state.ConnectingSSID = req.SSID
|
||||
b.state.ConnectingDevice = req.Device
|
||||
b.state.LastError = ""
|
||||
b.stateMutex.Unlock()
|
||||
|
||||
@@ -223,14 +225,13 @@ func (b *NetworkManagerBackend) ConnectWiFi(req ConnectionRequest) error {
|
||||
|
||||
existingConn, err := b.findConnection(req.SSID)
|
||||
if err == nil && existingConn != nil {
|
||||
dev := b.wifiDevice.(gonetworkmanager.Device)
|
||||
|
||||
_, err := nm.ActivateConnection(existingConn, dev, nil)
|
||||
_, err := nm.ActivateConnection(existingConn, devInfo.device, nil)
|
||||
if err != nil {
|
||||
log.Warnf("[ConnectWiFi] Failed to activate existing connection: %v", err)
|
||||
b.stateMutex.Lock()
|
||||
b.state.IsConnecting = false
|
||||
b.state.ConnectingSSID = ""
|
||||
b.state.ConnectingDevice = ""
|
||||
b.state.LastError = fmt.Sprintf("failed to activate connection: %v", err)
|
||||
b.stateMutex.Unlock()
|
||||
if b.onStateChange != nil {
|
||||
@@ -242,11 +243,12 @@ func (b *NetworkManagerBackend) ConnectWiFi(req ConnectionRequest) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := b.createAndConnectWiFi(req); err != nil {
|
||||
if err := b.createAndConnectWiFiOnDevice(req, devInfo); err != nil {
|
||||
log.Warnf("[ConnectWiFi] Failed to create and connect: %v", err)
|
||||
b.stateMutex.Lock()
|
||||
b.state.IsConnecting = false
|
||||
b.state.ConnectingSSID = ""
|
||||
b.state.ConnectingDevice = ""
|
||||
b.state.LastError = err.Error()
|
||||
b.stateMutex.Unlock()
|
||||
if b.onStateChange != nil {
|
||||
@@ -502,19 +504,17 @@ func (b *NetworkManagerBackend) findConnection(ssid string) (gonetworkmanager.Co
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) createAndConnectWiFi(req ConnectionRequest) error {
|
||||
if b.wifiDevice == nil {
|
||||
return fmt.Errorf("no WiFi device available")
|
||||
}
|
||||
|
||||
nm := b.nmConn.(gonetworkmanager.NetworkManager)
|
||||
dev := b.wifiDevice.(gonetworkmanager.Device)
|
||||
|
||||
if err := b.ensureWiFiDevice(); err != nil {
|
||||
devInfo, err := b.getWifiDeviceForConnection(req.Device)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wifiDev := b.wifiDev
|
||||
return b.createAndConnectWiFiOnDevice(req, devInfo)
|
||||
}
|
||||
|
||||
w := wifiDev.(gonetworkmanager.DeviceWireless)
|
||||
func (b *NetworkManagerBackend) createAndConnectWiFiOnDevice(req ConnectionRequest, devInfo *wifiDeviceInfo) error {
|
||||
nm := b.nmConn.(gonetworkmanager.NetworkManager)
|
||||
dev := devInfo.device
|
||||
w := devInfo.wireless
|
||||
apPaths, err := w.GetAccessPoints()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get access points: %w", err)
|
||||
@@ -716,3 +716,254 @@ func (b *NetworkManagerBackend) SetWiFiAutoconnect(ssid string, autoconnect bool
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) ScanWiFiDevice(device string) error {
|
||||
devInfo, ok := b.wifiDevices[device]
|
||||
if !ok {
|
||||
return fmt.Errorf("WiFi device not found: %s", device)
|
||||
}
|
||||
|
||||
b.stateMutex.RLock()
|
||||
enabled := b.state.WiFiEnabled
|
||||
b.stateMutex.RUnlock()
|
||||
|
||||
if !enabled {
|
||||
return fmt.Errorf("WiFi is disabled")
|
||||
}
|
||||
|
||||
if err := devInfo.wireless.RequestScan(); err != nil {
|
||||
return fmt.Errorf("scan request failed: %w", err)
|
||||
}
|
||||
|
||||
b.updateAllWiFiDevices()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) DisconnectWiFiDevice(device string) error {
|
||||
devInfo, ok := b.wifiDevices[device]
|
||||
if !ok {
|
||||
return fmt.Errorf("WiFi device not found: %s", device)
|
||||
}
|
||||
|
||||
if err := devInfo.device.Disconnect(); err != nil {
|
||||
return fmt.Errorf("failed to disconnect: %w", err)
|
||||
}
|
||||
|
||||
b.updateWiFiState()
|
||||
b.updateAllWiFiDevices()
|
||||
b.updatePrimaryConnection()
|
||||
|
||||
if b.onStateChange != nil {
|
||||
b.onStateChange()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) GetWiFiDevices() []WiFiDevice {
|
||||
b.stateMutex.RLock()
|
||||
defer b.stateMutex.RUnlock()
|
||||
return append([]WiFiDevice(nil), b.state.WiFiDevices...)
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) updateAllWiFiDevices() {
|
||||
s := b.settings
|
||||
if s == nil {
|
||||
var err error
|
||||
s, err = gonetworkmanager.NewSettings()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
b.settings = s
|
||||
}
|
||||
|
||||
settingsMgr := s.(gonetworkmanager.Settings)
|
||||
connections, err := settingsMgr.ListConnections()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
savedSSIDs := make(map[string]bool)
|
||||
autoconnectMap := make(map[string]bool)
|
||||
for _, conn := range connections {
|
||||
connSettings, err := conn.GetSettings()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
connMeta, ok := connSettings["connection"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
connType, ok := connMeta["type"].(string)
|
||||
if !ok || connType != "802-11-wireless" {
|
||||
continue
|
||||
}
|
||||
|
||||
wifiSettings, ok := connSettings["802-11-wireless"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
ssidBytes, ok := wifiSettings["ssid"].([]byte)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
ssid := string(ssidBytes)
|
||||
savedSSIDs[ssid] = true
|
||||
autoconnect := true
|
||||
if ac, ok := connMeta["autoconnect"].(bool); ok {
|
||||
autoconnect = ac
|
||||
}
|
||||
autoconnectMap[ssid] = autoconnect
|
||||
}
|
||||
|
||||
var devices []WiFiDevice
|
||||
|
||||
for name, devInfo := range b.wifiDevices {
|
||||
state, _ := devInfo.device.GetPropertyState()
|
||||
connected := state == gonetworkmanager.NmDeviceStateActivated
|
||||
|
||||
var ssid, bssid, ip string
|
||||
var signal uint8
|
||||
|
||||
if connected {
|
||||
if activeAP, err := devInfo.wireless.GetPropertyActiveAccessPoint(); err == nil && activeAP != nil && activeAP.GetPath() != "/" {
|
||||
ssid, _ = activeAP.GetPropertySSID()
|
||||
signal, _ = activeAP.GetPropertyStrength()
|
||||
bssid, _ = activeAP.GetPropertyHWAddress()
|
||||
}
|
||||
ip = b.getDeviceIP(devInfo.device)
|
||||
}
|
||||
|
||||
stateStr := "disconnected"
|
||||
switch state {
|
||||
case gonetworkmanager.NmDeviceStateActivated:
|
||||
stateStr = "connected"
|
||||
case gonetworkmanager.NmDeviceStateConfig, gonetworkmanager.NmDeviceStateIpConfig:
|
||||
stateStr = "connecting"
|
||||
case gonetworkmanager.NmDeviceStatePrepare:
|
||||
stateStr = "preparing"
|
||||
case gonetworkmanager.NmDeviceStateDeactivating:
|
||||
stateStr = "disconnecting"
|
||||
}
|
||||
|
||||
apPaths, err := devInfo.wireless.GetAccessPoints()
|
||||
var networks []WiFiNetwork
|
||||
if err == nil {
|
||||
seenSSIDs := make(map[string]*WiFiNetwork)
|
||||
for _, ap := range apPaths {
|
||||
apSSID, err := ap.GetPropertySSID()
|
||||
if err != nil || apSSID == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if existing, exists := seenSSIDs[apSSID]; exists {
|
||||
strength, _ := ap.GetPropertyStrength()
|
||||
if strength > existing.Signal {
|
||||
existing.Signal = strength
|
||||
freq, _ := ap.GetPropertyFrequency()
|
||||
existing.Frequency = freq
|
||||
apBSSID, _ := ap.GetPropertyHWAddress()
|
||||
existing.BSSID = apBSSID
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
strength, _ := ap.GetPropertyStrength()
|
||||
flags, _ := ap.GetPropertyFlags()
|
||||
wpaFlags, _ := ap.GetPropertyWPAFlags()
|
||||
rsnFlags, _ := ap.GetPropertyRSNFlags()
|
||||
freq, _ := ap.GetPropertyFrequency()
|
||||
maxBitrate, _ := ap.GetPropertyMaxBitrate()
|
||||
apBSSID, _ := ap.GetPropertyHWAddress()
|
||||
mode, _ := ap.GetPropertyMode()
|
||||
|
||||
secured := flags != uint32(gonetworkmanager.Nm80211APFlagsNone) ||
|
||||
wpaFlags != uint32(gonetworkmanager.Nm80211APSecNone) ||
|
||||
rsnFlags != uint32(gonetworkmanager.Nm80211APSecNone)
|
||||
|
||||
enterprise := (rsnFlags&uint32(gonetworkmanager.Nm80211APSecKeyMgmt8021X) != 0) ||
|
||||
(wpaFlags&uint32(gonetworkmanager.Nm80211APSecKeyMgmt8021X) != 0)
|
||||
|
||||
var modeStr string
|
||||
switch mode {
|
||||
case gonetworkmanager.Nm80211ModeAdhoc:
|
||||
modeStr = "adhoc"
|
||||
case gonetworkmanager.Nm80211ModeInfra:
|
||||
modeStr = "infrastructure"
|
||||
case gonetworkmanager.Nm80211ModeAp:
|
||||
modeStr = "ap"
|
||||
default:
|
||||
modeStr = "unknown"
|
||||
}
|
||||
|
||||
channel := frequencyToChannel(freq)
|
||||
|
||||
network := WiFiNetwork{
|
||||
SSID: apSSID,
|
||||
BSSID: apBSSID,
|
||||
Signal: strength,
|
||||
Secured: secured,
|
||||
Enterprise: enterprise,
|
||||
Connected: connected && apSSID == ssid,
|
||||
Saved: savedSSIDs[apSSID],
|
||||
Autoconnect: autoconnectMap[apSSID],
|
||||
Frequency: freq,
|
||||
Mode: modeStr,
|
||||
Rate: maxBitrate / 1000,
|
||||
Channel: channel,
|
||||
Device: name,
|
||||
}
|
||||
|
||||
seenSSIDs[apSSID] = &network
|
||||
networks = append(networks, network)
|
||||
}
|
||||
sortWiFiNetworks(networks)
|
||||
}
|
||||
|
||||
devices = append(devices, WiFiDevice{
|
||||
Name: name,
|
||||
HwAddress: devInfo.hwAddress,
|
||||
State: stateStr,
|
||||
Connected: connected,
|
||||
SSID: ssid,
|
||||
BSSID: bssid,
|
||||
Signal: signal,
|
||||
IP: ip,
|
||||
Networks: networks,
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(devices, func(i, j int) bool {
|
||||
return devices[i].Name < devices[j].Name
|
||||
})
|
||||
|
||||
b.stateMutex.Lock()
|
||||
b.state.WiFiDevices = devices
|
||||
b.stateMutex.Unlock()
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) getWifiDeviceForConnection(deviceName string) (*wifiDeviceInfo, error) {
|
||||
if deviceName != "" {
|
||||
devInfo, ok := b.wifiDevices[deviceName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("WiFi device not found: %s", deviceName)
|
||||
}
|
||||
return devInfo, nil
|
||||
}
|
||||
|
||||
if b.wifiDevice == nil {
|
||||
return nil, fmt.Errorf("no WiFi device available")
|
||||
}
|
||||
|
||||
dev := b.wifiDevice.(gonetworkmanager.Device)
|
||||
iface, _ := dev.GetPropertyInterface()
|
||||
if devInfo, ok := b.wifiDevices[iface]; ok {
|
||||
return devInfo, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no WiFi device available")
|
||||
}
|
||||
|
||||
@@ -101,10 +101,21 @@ func TestNetworkManagerBackend_ConnectWiFi_AlreadyConnected(t *testing.T) {
|
||||
|
||||
backend.wifiDevice = mockDeviceWireless
|
||||
backend.wifiDev = mockDeviceWireless
|
||||
backend.wifiDevices = map[string]*wifiDeviceInfo{
|
||||
"wlan0": {
|
||||
device: nil,
|
||||
wireless: mockDeviceWireless,
|
||||
name: "wlan0",
|
||||
hwAddress: "00:11:22:33:44:55",
|
||||
},
|
||||
}
|
||||
|
||||
mockDeviceWireless.EXPECT().GetPropertyInterface().Return("wlan0", nil)
|
||||
|
||||
backend.stateMutex.Lock()
|
||||
backend.state.WiFiConnected = true
|
||||
backend.state.WiFiSSID = "TestNetwork"
|
||||
backend.state.WiFiDevice = "wlan0"
|
||||
backend.stateMutex.Unlock()
|
||||
|
||||
req := ConnectionRequest{SSID: "TestNetwork", Password: "password"}
|
||||
|
||||
@@ -135,7 +135,14 @@ func handleGetState(conn net.Conn, req Request, manager *Manager) {
|
||||
}
|
||||
|
||||
func handleScanWiFi(conn net.Conn, req Request, manager *Manager) {
|
||||
if err := manager.ScanWiFi(); err != nil {
|
||||
device, _ := req.Params["device"].(string)
|
||||
var err error
|
||||
if device != "" {
|
||||
err = manager.ScanWiFiDevice(device)
|
||||
} else {
|
||||
err = manager.ScanWiFi()
|
||||
}
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
@@ -163,6 +170,9 @@ func handleConnectWiFi(conn net.Conn, req Request, manager *Manager) {
|
||||
if username, ok := req.Params["username"].(string); ok {
|
||||
connReq.Username = username
|
||||
}
|
||||
if device, ok := req.Params["device"].(string); ok {
|
||||
connReq.Device = device
|
||||
}
|
||||
|
||||
if interactive, ok := req.Params["interactive"].(bool); ok {
|
||||
connReq.Interactive = interactive
|
||||
@@ -170,7 +180,7 @@ func handleConnectWiFi(conn net.Conn, req Request, manager *Manager) {
|
||||
state := manager.GetState()
|
||||
alreadyConnected := state.WiFiConnected && state.WiFiSSID == ssid
|
||||
|
||||
if alreadyConnected {
|
||||
if alreadyConnected && connReq.Device == "" {
|
||||
connReq.Interactive = false
|
||||
} else {
|
||||
networkInfo, err := manager.GetNetworkInfo(ssid)
|
||||
@@ -200,7 +210,14 @@ func handleConnectWiFi(conn net.Conn, req Request, manager *Manager) {
|
||||
}
|
||||
|
||||
func handleDisconnectWiFi(conn net.Conn, req Request, manager *Manager) {
|
||||
if err := manager.DisconnectWiFi(); err != nil {
|
||||
device, _ := req.Params["device"].(string)
|
||||
var err error
|
||||
if device != "" {
|
||||
err = manager.DisconnectWiFiDevice(device)
|
||||
} else {
|
||||
err = manager.DisconnectWiFi()
|
||||
}
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -117,11 +117,13 @@ func (m *Manager) syncStateFromBackend() error {
|
||||
m.state.WiFiBSSID = backendState.WiFiBSSID
|
||||
m.state.WiFiSignal = backendState.WiFiSignal
|
||||
m.state.WiFiNetworks = backendState.WiFiNetworks
|
||||
m.state.WiFiDevices = backendState.WiFiDevices
|
||||
m.state.WiredConnections = backendState.WiredConnections
|
||||
m.state.VPNProfiles = backendState.VPNProfiles
|
||||
m.state.VPNActive = backendState.VPNActive
|
||||
m.state.IsConnecting = backendState.IsConnecting
|
||||
m.state.ConnectingSSID = backendState.ConnectingSSID
|
||||
m.state.ConnectingDevice = backendState.ConnectingDevice
|
||||
m.state.LastError = backendState.LastError
|
||||
m.stateMutex.Unlock()
|
||||
|
||||
@@ -151,6 +153,7 @@ func (m *Manager) snapshotState() NetworkState {
|
||||
defer m.stateMutex.RUnlock()
|
||||
s := *m.state
|
||||
s.WiFiNetworks = append([]WiFiNetwork(nil), m.state.WiFiNetworks...)
|
||||
s.WiFiDevices = append([]WiFiDevice(nil), m.state.WiFiDevices...)
|
||||
s.WiredConnections = append([]WiredConnection(nil), m.state.WiredConnections...)
|
||||
s.VPNProfiles = append([]VPNProfile(nil), m.state.VPNProfiles...)
|
||||
s.VPNActive = append([]VPNActive(nil), m.state.VPNActive...)
|
||||
@@ -204,6 +207,9 @@ func stateChangedMeaningfully(old, new *NetworkState) bool {
|
||||
if len(old.WiFiNetworks) != len(new.WiFiNetworks) {
|
||||
return true
|
||||
}
|
||||
if len(old.WiFiDevices) != len(new.WiFiDevices) {
|
||||
return true
|
||||
}
|
||||
if len(old.WiredConnections) != len(new.WiredConnections) {
|
||||
return true
|
||||
}
|
||||
@@ -505,3 +511,19 @@ func (m *Manager) ClearVPNCredentials(uuidOrName string) error {
|
||||
func (m *Manager) SetWiFiAutoconnect(ssid string, autoconnect bool) error {
|
||||
return m.backend.SetWiFiAutoconnect(ssid, autoconnect)
|
||||
}
|
||||
|
||||
func (m *Manager) GetWiFiDevices() []WiFiDevice {
|
||||
m.stateMutex.RLock()
|
||||
defer m.stateMutex.RUnlock()
|
||||
devices := make([]WiFiDevice, len(m.state.WiFiDevices))
|
||||
copy(devices, m.state.WiFiDevices)
|
||||
return devices
|
||||
}
|
||||
|
||||
func (m *Manager) ScanWiFiDevice(device string) error {
|
||||
return m.backend.ScanWiFiDevice(device)
|
||||
}
|
||||
|
||||
func (m *Manager) DisconnectWiFiDevice(device string) error {
|
||||
return m.backend.DisconnectWiFiDevice(device)
|
||||
}
|
||||
|
||||
@@ -37,6 +37,19 @@ type WiFiNetwork struct {
|
||||
Mode string `json:"mode"`
|
||||
Rate uint32 `json:"rate"`
|
||||
Channel uint32 `json:"channel"`
|
||||
Device string `json:"device,omitempty"`
|
||||
}
|
||||
|
||||
type WiFiDevice struct {
|
||||
Name string `json:"name"`
|
||||
HwAddress string `json:"hwAddress"`
|
||||
State string `json:"state"`
|
||||
Connected bool `json:"connected"`
|
||||
SSID string `json:"ssid,omitempty"`
|
||||
BSSID string `json:"bssid,omitempty"`
|
||||
Signal uint8 `json:"signal,omitempty"`
|
||||
IP string `json:"ip,omitempty"`
|
||||
Networks []WiFiNetwork `json:"networks"`
|
||||
}
|
||||
|
||||
type VPNProfile struct {
|
||||
@@ -76,11 +89,13 @@ type NetworkState struct {
|
||||
WiFiBSSID string `json:"wifiBSSID"`
|
||||
WiFiSignal uint8 `json:"wifiSignal"`
|
||||
WiFiNetworks []WiFiNetwork `json:"wifiNetworks"`
|
||||
WiFiDevices []WiFiDevice `json:"wifiDevices"`
|
||||
WiredConnections []WiredConnection `json:"wiredConnections"`
|
||||
VPNProfiles []VPNProfile `json:"vpnProfiles"`
|
||||
VPNActive []VPNActive `json:"vpnActive"`
|
||||
IsConnecting bool `json:"isConnecting"`
|
||||
ConnectingSSID string `json:"connectingSSID"`
|
||||
ConnectingDevice string `json:"connectingDevice,omitempty"`
|
||||
LastError string `json:"lastError"`
|
||||
}
|
||||
|
||||
@@ -91,6 +106,7 @@ type ConnectionRequest struct {
|
||||
AnonymousIdentity string `json:"anonymousIdentity,omitempty"`
|
||||
DomainSuffixMatch string `json:"domainSuffixMatch,omitempty"`
|
||||
Interactive bool `json:"interactive,omitempty"`
|
||||
Device string `json:"device,omitempty"`
|
||||
}
|
||||
|
||||
type WiredConnection struct {
|
||||
|
||||
@@ -31,7 +31,7 @@ import (
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
|
||||
)
|
||||
|
||||
const APIVersion = 19
|
||||
const APIVersion = 20
|
||||
|
||||
type Capabilities struct {
|
||||
Capabilities []string `json:"capabilities"`
|
||||
@@ -1071,10 +1071,10 @@ func Start(printDocs bool) error {
|
||||
log.Info(" plugins.search - Search plugins (params: query, category?, compositor?, capability?)")
|
||||
log.Info("Network:")
|
||||
log.Info(" network.getState - Get current network state")
|
||||
log.Info(" network.wifi.scan - Scan for WiFi networks")
|
||||
log.Info(" network.wifi.scan - Scan for WiFi networks (params: device?)")
|
||||
log.Info(" network.wifi.networks - Get WiFi network list")
|
||||
log.Info(" network.wifi.connect - Connect to WiFi (params: ssid, password?, username?)")
|
||||
log.Info(" network.wifi.disconnect - Disconnect WiFi")
|
||||
log.Info(" network.wifi.connect - Connect to WiFi (params: ssid, password?, username?, device?)")
|
||||
log.Info(" network.wifi.disconnect - Disconnect WiFi (params: device?)")
|
||||
log.Info(" network.wifi.forget - Forget network (params: ssid)")
|
||||
log.Info(" network.wifi.toggle - Toggle WiFi radio")
|
||||
log.Info(" network.wifi.enable - Enable WiFi")
|
||||
|
||||
@@ -607,41 +607,6 @@ func (m *Manager) transitionWorker() {
|
||||
|
||||
if finalTarget == targetTemp {
|
||||
log.Debugf("Transition complete: now at %dK", targetTemp)
|
||||
|
||||
m.configMutex.RLock()
|
||||
enabled := m.config.Enabled
|
||||
identityTemp := m.config.HighTemp
|
||||
m.configMutex.RUnlock()
|
||||
|
||||
if !enabled && targetTemp == identityTemp && m.controlsInitialized {
|
||||
m.post(func() {
|
||||
log.Info("Destroying gamma controls after transition to identity")
|
||||
m.outputs.Range(func(id uint32, out *outputState) bool {
|
||||
if out.gammaControl != nil {
|
||||
control := out.gammaControl.(*wlr_gamma_control.ZwlrGammaControlV1)
|
||||
control.Destroy()
|
||||
log.Debugf("Destroyed gamma control for output %d", id)
|
||||
}
|
||||
return true
|
||||
})
|
||||
m.outputs.Range(func(key uint32, value *outputState) bool {
|
||||
m.outputs.Delete(key)
|
||||
return true
|
||||
})
|
||||
m.controlsInitialized = false
|
||||
|
||||
m.transitionMutex.Lock()
|
||||
m.currentTemp = identityTemp
|
||||
m.targetTemp = identityTemp
|
||||
m.transitionMutex.Unlock()
|
||||
|
||||
if _, err := m.display.Sync(); err != nil {
|
||||
log.Warnf("Failed to sync Wayland display after destroying controls: %v", err)
|
||||
}
|
||||
|
||||
log.Info("All gamma controls destroyed")
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1262,46 +1227,33 @@ func (m *Manager) SetEnabled(enabled bool) {
|
||||
}
|
||||
} else {
|
||||
if m.controlsInitialized {
|
||||
m.configMutex.RLock()
|
||||
identityTemp := m.config.HighTemp
|
||||
m.configMutex.RUnlock()
|
||||
|
||||
m.transitionMutex.RLock()
|
||||
currentTemp := m.currentTemp
|
||||
m.transitionMutex.RUnlock()
|
||||
|
||||
if currentTemp == identityTemp {
|
||||
m.post(func() {
|
||||
log.Infof("Already at %dK, destroying gamma controls immediately", identityTemp)
|
||||
m.outputs.Range(func(id uint32, out *outputState) bool {
|
||||
if out.gammaControl != nil {
|
||||
control := out.gammaControl.(*wlr_gamma_control.ZwlrGammaControlV1)
|
||||
control.Destroy()
|
||||
log.Debugf("Destroyed gamma control for output %d", id)
|
||||
}
|
||||
return true
|
||||
})
|
||||
m.outputs.Range(func(key uint32, value *outputState) bool {
|
||||
m.outputs.Delete(key)
|
||||
return true
|
||||
})
|
||||
m.controlsInitialized = false
|
||||
|
||||
m.transitionMutex.Lock()
|
||||
m.currentTemp = identityTemp
|
||||
m.targetTemp = identityTemp
|
||||
m.transitionMutex.Unlock()
|
||||
|
||||
if _, err := m.display.Sync(); err != nil {
|
||||
log.Warnf("Failed to sync Wayland display after destroying controls: %v", err)
|
||||
m.post(func() {
|
||||
log.Info("Disabling gamma, destroying controls immediately")
|
||||
m.outputs.Range(func(id uint32, out *outputState) bool {
|
||||
if out.gammaControl != nil {
|
||||
control := out.gammaControl.(*wlr_gamma_control.ZwlrGammaControlV1)
|
||||
control.Destroy()
|
||||
log.Debugf("Destroyed gamma control for output %d", id)
|
||||
}
|
||||
|
||||
log.Info("All gamma controls destroyed")
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
log.Infof("Disabling: transitioning to %dK before destroying controls", identityTemp)
|
||||
m.startTransition(identityTemp)
|
||||
}
|
||||
m.outputs.Range(func(key uint32, value *outputState) bool {
|
||||
m.outputs.Delete(key)
|
||||
return true
|
||||
})
|
||||
m.controlsInitialized = false
|
||||
|
||||
m.configMutex.RLock()
|
||||
identityTemp := m.config.HighTemp
|
||||
m.configMutex.RUnlock()
|
||||
|
||||
m.transitionMutex.Lock()
|
||||
m.currentTemp = identityTemp
|
||||
m.targetTemp = identityTemp
|
||||
m.transitionMutex.Unlock()
|
||||
|
||||
log.Info("All gamma controls destroyed")
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,28 +18,27 @@ type VersionInfo struct {
|
||||
HasUpdate bool
|
||||
}
|
||||
|
||||
func GetCurrentDMSVersion() (string, error) {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get home directory: %w", err)
|
||||
}
|
||||
// VersionFetcher is an interface for fetching version information
|
||||
type VersionFetcher interface {
|
||||
GetCurrentVersion(dmsPath string) (string, error)
|
||||
GetLatestVersion(dmsPath string) (string, error)
|
||||
}
|
||||
|
||||
dmsPath := filepath.Join(homeDir, ".config", "quickshell", "dms")
|
||||
if _, err := os.Stat(dmsPath); os.IsNotExist(err) {
|
||||
return "", fmt.Errorf("DMS not installed")
|
||||
}
|
||||
|
||||
originalDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer os.Chdir(originalDir)
|
||||
|
||||
if err := os.Chdir(dmsPath); err != nil {
|
||||
return "", fmt.Errorf("failed to change to DMS directory: %w", err)
|
||||
}
|
||||
// DefaultVersionFetcher is the default implementation that uses git/curl
|
||||
type DefaultVersionFetcher struct{}
|
||||
|
||||
func (d *DefaultVersionFetcher) GetCurrentVersion(dmsPath string) (string, error) {
|
||||
if _, err := os.Stat(filepath.Join(dmsPath, ".git")); err == nil {
|
||||
originalDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer os.Chdir(originalDir)
|
||||
|
||||
if err := os.Chdir(dmsPath); err != nil {
|
||||
return "", fmt.Errorf("failed to change to DMS directory: %w", err)
|
||||
}
|
||||
|
||||
tagCmd := exec.Command("git", "describe", "--exact-match", "--tags", "HEAD")
|
||||
if tagOutput, err := tagCmd.Output(); err == nil {
|
||||
return strings.TrimSpace(string(tagOutput)), nil
|
||||
@@ -65,21 +64,14 @@ func GetCurrentDMSVersion() (string, error) {
|
||||
return "unknown", nil
|
||||
}
|
||||
|
||||
func GetLatestDMSVersion() (string, error) {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get home directory: %w", err)
|
||||
}
|
||||
|
||||
dmsPath := filepath.Join(homeDir, ".config", "quickshell", "dms")
|
||||
|
||||
originalDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer os.Chdir(originalDir)
|
||||
|
||||
func (d *DefaultVersionFetcher) GetLatestVersion(dmsPath string) (string, error) {
|
||||
if _, err := os.Stat(filepath.Join(dmsPath, ".git")); err == nil {
|
||||
originalDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer os.Chdir(originalDir)
|
||||
|
||||
if err := os.Chdir(dmsPath); err != nil {
|
||||
return "", fmt.Errorf("failed to change to DMS directory: %w", err)
|
||||
}
|
||||
@@ -154,13 +146,54 @@ func GetLatestDMSVersion() (string, error) {
|
||||
return result.TagName, nil
|
||||
}
|
||||
|
||||
// defaultFetcher is used by the public functions
|
||||
var defaultFetcher VersionFetcher = &DefaultVersionFetcher{}
|
||||
|
||||
func GetCurrentDMSVersion() (string, error) {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get home directory: %w", err)
|
||||
}
|
||||
|
||||
dmsPath := filepath.Join(homeDir, ".config", "quickshell", "dms")
|
||||
if _, err := os.Stat(dmsPath); os.IsNotExist(err) {
|
||||
return "", fmt.Errorf("DMS not installed")
|
||||
}
|
||||
|
||||
return defaultFetcher.GetCurrentVersion(dmsPath)
|
||||
}
|
||||
|
||||
func GetLatestDMSVersion() (string, error) {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get home directory: %w", err)
|
||||
}
|
||||
|
||||
dmsPath := filepath.Join(homeDir, ".config", "quickshell", "dms")
|
||||
return defaultFetcher.GetLatestVersion(dmsPath)
|
||||
}
|
||||
|
||||
func GetDMSVersionInfo() (*VersionInfo, error) {
|
||||
current, err := GetCurrentDMSVersion()
|
||||
return GetDMSVersionInfoWithFetcher(defaultFetcher)
|
||||
}
|
||||
|
||||
func GetDMSVersionInfoWithFetcher(fetcher VersionFetcher) (*VersionInfo, error) {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get home directory: %w", err)
|
||||
}
|
||||
|
||||
dmsPath := filepath.Join(homeDir, ".config", "quickshell", "dms")
|
||||
if _, err := os.Stat(dmsPath); os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("DMS not installed")
|
||||
}
|
||||
|
||||
current, err := fetcher.GetCurrentVersion(dmsPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
latest, err := GetLatestDMSVersion()
|
||||
latest, err := fetcher.GetLatestVersion(dmsPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get latest version: %w", err)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
mocks_version "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/version"
|
||||
)
|
||||
|
||||
func TestCompareVersions(t *testing.T) {
|
||||
@@ -33,36 +35,107 @@ func TestCompareVersions(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetDMSVersionInfo_Structure(t *testing.T) {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
t.Skip("Cannot get home directory")
|
||||
}
|
||||
// Create a temp directory with a fake DMS installation
|
||||
tempDir := t.TempDir()
|
||||
dmsPath := filepath.Join(tempDir, ".config", "quickshell", "dms")
|
||||
os.MkdirAll(dmsPath, 0755)
|
||||
|
||||
dmsPath := filepath.Join(homeDir, ".config", "quickshell", "dms")
|
||||
if _, err := os.Stat(dmsPath); os.IsNotExist(err) {
|
||||
t.Skip("DMS not installed, skipping version info test")
|
||||
}
|
||||
// Create a .git directory to simulate git installation
|
||||
os.MkdirAll(filepath.Join(dmsPath, ".git"), 0755)
|
||||
|
||||
info, err := GetDMSVersionInfo()
|
||||
originalHome := os.Getenv("HOME")
|
||||
defer os.Setenv("HOME", originalHome)
|
||||
os.Setenv("HOME", tempDir)
|
||||
|
||||
// Create mock fetcher
|
||||
mockFetcher := mocks_version.NewMockVersionFetcher(t)
|
||||
mockFetcher.EXPECT().GetCurrentVersion(dmsPath).Return("v0.1.0", nil)
|
||||
mockFetcher.EXPECT().GetLatestVersion(dmsPath).Return("v0.1.1", nil)
|
||||
|
||||
info, err := GetDMSVersionInfoWithFetcher(mockFetcher)
|
||||
if err != nil {
|
||||
t.Fatalf("GetDMSVersionInfo() failed: %v", err)
|
||||
t.Fatalf("GetDMSVersionInfoWithFetcher() failed: %v", err)
|
||||
}
|
||||
|
||||
if info == nil {
|
||||
t.Fatal("GetDMSVersionInfo() returned nil")
|
||||
t.Fatal("GetDMSVersionInfoWithFetcher() returned nil")
|
||||
}
|
||||
|
||||
if info.Current == "" {
|
||||
t.Error("Current version is empty")
|
||||
if info.Current != "v0.1.0" {
|
||||
t.Errorf("Current version = %s, expected v0.1.0", info.Current)
|
||||
}
|
||||
|
||||
if info.Latest == "" {
|
||||
t.Error("Latest version is empty")
|
||||
if info.Latest != "v0.1.1" {
|
||||
t.Errorf("Latest version = %s, expected v0.1.1", info.Latest)
|
||||
}
|
||||
|
||||
if !info.HasUpdate {
|
||||
t.Error("HasUpdate should be true when current != latest")
|
||||
}
|
||||
|
||||
if !info.IsTag {
|
||||
t.Error("IsTag should be true for v0.1.0")
|
||||
}
|
||||
|
||||
t.Logf("Current: %s, Latest: %s, HasUpdate: %v", info.Current, info.Latest, info.HasUpdate)
|
||||
}
|
||||
|
||||
func TestGetDMSVersionInfo_BranchVersion(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
dmsPath := filepath.Join(tempDir, ".config", "quickshell", "dms")
|
||||
os.MkdirAll(dmsPath, 0755)
|
||||
os.MkdirAll(filepath.Join(dmsPath, ".git"), 0755)
|
||||
|
||||
originalHome := os.Getenv("HOME")
|
||||
defer os.Setenv("HOME", originalHome)
|
||||
os.Setenv("HOME", tempDir)
|
||||
|
||||
mockFetcher := mocks_version.NewMockVersionFetcher(t)
|
||||
mockFetcher.EXPECT().GetCurrentVersion(dmsPath).Return("master@abc1234", nil)
|
||||
mockFetcher.EXPECT().GetLatestVersion(dmsPath).Return("master@def5678", nil)
|
||||
|
||||
info, err := GetDMSVersionInfoWithFetcher(mockFetcher)
|
||||
if err != nil {
|
||||
t.Fatalf("GetDMSVersionInfoWithFetcher() failed: %v", err)
|
||||
}
|
||||
|
||||
if !info.IsBranch {
|
||||
t.Error("IsBranch should be true for branch@commit format")
|
||||
}
|
||||
|
||||
if !info.IsGit {
|
||||
t.Error("IsGit should be true for branch@commit format")
|
||||
}
|
||||
|
||||
if !info.HasUpdate {
|
||||
t.Error("HasUpdate should be true when commits differ")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDMSVersionInfo_NoUpdate(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
dmsPath := filepath.Join(tempDir, ".config", "quickshell", "dms")
|
||||
os.MkdirAll(dmsPath, 0755)
|
||||
os.MkdirAll(filepath.Join(dmsPath, ".git"), 0755)
|
||||
|
||||
originalHome := os.Getenv("HOME")
|
||||
defer os.Setenv("HOME", originalHome)
|
||||
os.Setenv("HOME", tempDir)
|
||||
|
||||
mockFetcher := mocks_version.NewMockVersionFetcher(t)
|
||||
mockFetcher.EXPECT().GetCurrentVersion(dmsPath).Return("v0.1.0", nil)
|
||||
mockFetcher.EXPECT().GetLatestVersion(dmsPath).Return("v0.1.0", nil)
|
||||
|
||||
info, err := GetDMSVersionInfoWithFetcher(mockFetcher)
|
||||
if err != nil {
|
||||
t.Fatalf("GetDMSVersionInfoWithFetcher() failed: %v", err)
|
||||
}
|
||||
|
||||
if info.HasUpdate {
|
||||
t.Error("HasUpdate should be false when current == latest")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCurrentDMSVersion_NotInstalled(t *testing.T) {
|
||||
originalHome := os.Getenv("HOME")
|
||||
defer os.Setenv("HOME", originalHome)
|
||||
|
||||
25
distro/debian/dms-git/_service
Normal file
25
distro/debian/dms-git/_service
Normal file
@@ -0,0 +1,25 @@
|
||||
<services>
|
||||
<!-- Git source and vendoring -->
|
||||
<service name="tar_scm" mode="disabled">
|
||||
<param name="scm">git</param>
|
||||
<param name="url">https://github.com/AvengeMedia/DankMaterialShell.git</param>
|
||||
<param name="revision">master</param>
|
||||
<param name="filename">dms-git-source</param>
|
||||
</service>
|
||||
<service name="recompress" mode="disabled">
|
||||
<param name="file">*.tar</param>
|
||||
<param name="compression">gz</param>
|
||||
</service>
|
||||
<!-- Binary downloads kept for backwards compat - removed after successful source builds
|
||||
<service name="download_url">
|
||||
<param name="protocol">https</param>
|
||||
<param name="host">github.com</param>
|
||||
<param name="path">/AvengeMedia/DankMaterialShell/releases/latest/download/dms-distropkg-amd64.gz</param>
|
||||
</service>
|
||||
<service name="download_url">
|
||||
<param name="protocol">https</param>
|
||||
<param name="host">github.com</param>
|
||||
<param name="path">/AvengeMedia/DankMaterialShell/releases/latest/download/dms-distropkg-arm64.gz</param>
|
||||
</service>
|
||||
-->
|
||||
</services>
|
||||
12
distro/debian/dms-git/debian/changelog
Normal file
12
distro/debian/dms-git/debian/changelog
Normal file
@@ -0,0 +1,12 @@
|
||||
dms-git (0.6.2+git5) nightly; urgency=medium
|
||||
|
||||
* Fix debian/rules to use source at root level (native format)
|
||||
* Remove incorrect dms-git-source subdirectory references
|
||||
* Fix Build-Depends parsing path issue in obs-upload.sh auto-increment
|
||||
* Fix Build-Depends parsing in obs-upload.sh for proper dependency extraction
|
||||
* Build dms binary from source for true git version strings
|
||||
* Match Fedora COPR git build behavior
|
||||
* Now shows proper git version (e.g., v0.6.2-11-g12e91534)
|
||||
* Add golang-go and make as build dependencies
|
||||
|
||||
-- Avenge Media <AvengeMedia.US@gmail.com> Wed, 27 Nov 2025 04:15:00 -0500
|
||||
51
distro/debian/dms-git/debian/control
Normal file
51
distro/debian/dms-git/debian/control
Normal file
@@ -0,0 +1,51 @@
|
||||
Source: dms-git
|
||||
Section: x11
|
||||
Priority: optional
|
||||
Maintainer: Avenge Media <AvengeMedia.US@gmail.com>
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
golang-go | golang (>= 2:1.22~) | golang-any
|
||||
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-git
|
||||
Architecture: amd64 arm64
|
||||
Depends: ${misc:Depends},
|
||||
quickshell-git | quickshell,
|
||||
accountsservice,
|
||||
cava,
|
||||
cliphist,
|
||||
danksearch,
|
||||
dgop,
|
||||
matugen,
|
||||
qml6-module-qtcore,
|
||||
qml6-module-qtmultimedia,
|
||||
qml6-module-qtqml,
|
||||
qml6-module-qtquick,
|
||||
qml6-module-qtquick-controls,
|
||||
qml6-module-qtquick-dialogs,
|
||||
qml6-module-qtquick-effects,
|
||||
qml6-module-qtquick-layouts,
|
||||
qml6-module-qtquick-templates,
|
||||
qml6-module-qtquick-window,
|
||||
qt6ct,
|
||||
wl-clipboard
|
||||
Provides: dms
|
||||
Conflicts: dms
|
||||
Replaces: dms
|
||||
Description: DankMaterialShell - Modern Wayland Desktop Shell (git nightly)
|
||||
DMS (DankMaterialShell) is a feature-rich desktop shell built on
|
||||
Quickshell, providing a modern and customizable user interface for
|
||||
Wayland compositors like niri, hyprland, and sway.
|
||||
.
|
||||
This is the nightly/git version built from the latest master branch.
|
||||
.
|
||||
Features include:
|
||||
- Material Design inspired UI
|
||||
- Customizable themes and appearance
|
||||
- Built-in application launcher
|
||||
- System tray and notifications
|
||||
- Network and Bluetooth management
|
||||
- Audio controls
|
||||
- Systemd integration
|
||||
27
distro/debian/dms-git/debian/copyright
Normal file
27
distro/debian/dms-git/debian/copyright
Normal file
@@ -0,0 +1,27 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: dms
|
||||
Upstream-Contact: Avenge Media LLC <AvengeMedia.US@gmail.com>
|
||||
Source: https://github.com/AvengeMedia/DankMaterialShell
|
||||
|
||||
Files: *
|
||||
Copyright: 2025 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.
|
||||
1
distro/debian/dms-git/debian/files
Normal file
1
distro/debian/dms-git/debian/files
Normal file
@@ -0,0 +1 @@
|
||||
dms-git_0.6.0+git2061.5ddea836ppa1_source.buildinfo x11 optional
|
||||
77
distro/debian/dms-git/debian/rules
Executable file
77
distro/debian/dms-git/debian/rules
Executable file
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
export DH_VERBOSE = 1
|
||||
|
||||
# Version info from changelog
|
||||
DEB_VERSION := $(shell dpkg-parsechangelog -S Version)
|
||||
UPSTREAM_VERSION := $(shell echo $(DEB_VERSION) | sed 's/-[^-]*$$//')
|
||||
DEB_HOST_ARCH := $(shell dpkg-architecture -qDEB_HOST_ARCH)
|
||||
|
||||
# Go needs writable directories for cache
|
||||
export HOME := $(CURDIR)/debian/tmp-home
|
||||
export GOCACHE := $(CURDIR)/debian/tmp-home/go-cache
|
||||
export GOMODCACHE := $(CURDIR)/debian/tmp-home/go-mod
|
||||
|
||||
export GOTOOLCHAIN := local
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
override_dh_auto_build:
|
||||
# Create Go cache directories
|
||||
mkdir -p $(HOME) $(GOCACHE) $(GOMODCACHE)
|
||||
|
||||
# Verify core directory exists (native package format has source at root)
|
||||
test -d core || (echo "ERROR: core directory not found!" && exit 1)
|
||||
|
||||
# Patch go.mod to use Go 1.24 base version (Debian 13 has 1.23.x, may vary)
|
||||
sed -i 's/^go 1\.24\.[0-9]*/go 1.24/' core/go.mod
|
||||
|
||||
# Extract version info for embedding
|
||||
VERSION="$(UPSTREAM_VERSION)"
|
||||
COMMIT=$$(echo "$(UPSTREAM_VERSION)" | grep -oP '(?<=git)[0-9]+\.[a-f0-9]+' | cut -d. -f2 | head -c8 || echo "unknown")
|
||||
|
||||
# Build dms-cli from source using vendored dependencies
|
||||
# Architecture mapping: Debian amd64/arm64 -> Makefile amd64/arm64
|
||||
if [ "$(DEB_HOST_ARCH)" = "amd64" ]; then \
|
||||
MAKE_ARCH=amd64; \
|
||||
BINARY_NAME=dms-linux-amd64; \
|
||||
elif [ "$(DEB_HOST_ARCH)" = "arm64" ]; then \
|
||||
MAKE_ARCH=arm64; \
|
||||
BINARY_NAME=dms-linux-arm64; \
|
||||
else \
|
||||
echo "ERROR: Unsupported architecture: $(DEB_HOST_ARCH)" && exit 1; \
|
||||
fi; \
|
||||
echo "Building with VERSION=$$VERSION COMMIT=$$COMMIT ARCH=$$MAKE_ARCH"; \
|
||||
cd core && $(MAKE) GOFLAGS="-mod=vendor" dist ARCH=$$MAKE_ARCH VERSION="$$VERSION" COMMIT="$$COMMIT"
|
||||
|
||||
# Copy binary to expected location
|
||||
if [ "$(DEB_HOST_ARCH)" = "amd64" ]; then \
|
||||
cp core/bin/dms-linux-amd64 dms; \
|
||||
elif [ "$(DEB_HOST_ARCH)" = "arm64" ]; then \
|
||||
cp core/bin/dms-linux-arm64 dms; \
|
||||
fi
|
||||
chmod +x dms
|
||||
|
||||
override_dh_auto_install:
|
||||
install -Dm755 dms debian/dms-git/usr/bin/dms
|
||||
|
||||
mkdir -p debian/dms-git/usr/share/quickshell/dms debian/dms-git/usr/lib/systemd/user
|
||||
if [ -d quickshell ]; then \
|
||||
cp -r quickshell/* debian/dms-git/usr/share/quickshell/dms/; \
|
||||
install -Dm644 quickshell/assets/systemd/dms.service debian/dms-git/usr/lib/systemd/user/dms.service; \
|
||||
else \
|
||||
echo "ERROR: quickshell directory not found!" && \
|
||||
echo "Contents of current directory:" && ls -la && \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
rm -rf debian/dms-git/usr/share/quickshell/dms/core \
|
||||
debian/dms-git/usr/share/quickshell/dms/distro
|
||||
|
||||
override_dh_auto_clean:
|
||||
# Clean up build artifacts
|
||||
rm -f dms
|
||||
rm -rf core/bin
|
||||
rm -rf debian/tmp-home
|
||||
dh_auto_clean
|
||||
1
distro/debian/dms-git/debian/source/format
Normal file
1
distro/debian/dms-git/debian/source/format
Normal file
@@ -0,0 +1 @@
|
||||
3.0 (native)
|
||||
1
distro/debian/dms-git/debian/source/include-binaries
Normal file
1
distro/debian/dms-git/debian/source/include-binaries
Normal file
@@ -0,0 +1 @@
|
||||
# dms-cli is built from source
|
||||
4
distro/debian/dms-git/debian/source/options
Normal file
4
distro/debian/dms-git/debian/source/options
Normal file
@@ -0,0 +1,4 @@
|
||||
# Include files that are normally excluded by .gitignore
|
||||
# These are needed for the build process on Launchpad
|
||||
tar-ignore = !dms-distropkg-amd64.gz
|
||||
tar-ignore = !dms-git-repo
|
||||
21
distro/debian/dms/_service
Normal file
21
distro/debian/dms/_service
Normal file
@@ -0,0 +1,21 @@
|
||||
<services>
|
||||
<!-- Download source tarball from GitHub releases -->
|
||||
<service name="download_url">
|
||||
<param name="protocol">https</param>
|
||||
<param name="host">github.com</param>
|
||||
<param name="path">/AvengeMedia/DankMaterialShell/archive/refs/tags/v0.6.2.tar.gz</param>
|
||||
<param name="filename">dms-source.tar.gz</param>
|
||||
</service>
|
||||
<!-- Download amd64 binary -->
|
||||
<service name="download_url">
|
||||
<param name="protocol">https</param>
|
||||
<param name="host">github.com</param>
|
||||
<param name="path">/AvengeMedia/DankMaterialShell/releases/download/v0.6.2/dms-distropkg-amd64.gz</param>
|
||||
</service>
|
||||
<!-- Download arm64 binary -->
|
||||
<service name="download_url">
|
||||
<param name="protocol">https</param>
|
||||
<param name="host">github.com</param>
|
||||
<param name="path">/AvengeMedia/DankMaterialShell/releases/download/v0.6.2/dms-distropkg-arm64.gz</param>
|
||||
</service>
|
||||
</services>
|
||||
7
distro/debian/dms/debian/changelog
Normal file
7
distro/debian/dms/debian/changelog
Normal file
@@ -0,0 +1,7 @@
|
||||
dms (0.6.2) stable; urgency=medium
|
||||
|
||||
* Update to v0.6.2 release
|
||||
* Fix binary download paths for OBS builds
|
||||
* Native format: removed revisions
|
||||
|
||||
-- Avenge Media <AvengeMedia.US@gmail.com> Tue, 19 Nov 2025 10:00:00 -0500
|
||||
47
distro/debian/dms/debian/control
Normal file
47
distro/debian/dms/debian/control
Normal file
@@ -0,0 +1,47 @@
|
||||
Source: dms
|
||||
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
|
||||
Architecture: amd64
|
||||
Depends: ${misc:Depends},
|
||||
quickshell-git | quickshell,
|
||||
accountsservice,
|
||||
cava,
|
||||
cliphist,
|
||||
danksearch,
|
||||
dgop,
|
||||
matugen,
|
||||
qml6-module-qtcore,
|
||||
qml6-module-qtmultimedia,
|
||||
qml6-module-qtqml,
|
||||
qml6-module-qtquick,
|
||||
qml6-module-qtquick-controls,
|
||||
qml6-module-qtquick-dialogs,
|
||||
qml6-module-qtquick-effects,
|
||||
qml6-module-qtquick-layouts,
|
||||
qml6-module-qtquick-templates,
|
||||
qml6-module-qtquick-window,
|
||||
qt6ct,
|
||||
wl-clipboard
|
||||
Conflicts: dms-git
|
||||
Replaces: dms-git
|
||||
Description: DankMaterialShell - Modern Wayland Desktop Shell
|
||||
DMS (DankMaterialShell) is a feature-rich desktop shell built on
|
||||
Quickshell, providing a modern and customizable user interface for
|
||||
Wayland compositors like niri, hyprland, and sway.
|
||||
.
|
||||
Features include:
|
||||
- Material Design inspired UI
|
||||
- Customizable themes and appearance
|
||||
- Built-in application launcher
|
||||
- System tray and notifications
|
||||
- Network and Bluetooth management
|
||||
- Audio controls
|
||||
- Systemd integration
|
||||
27
distro/debian/dms/debian/copyright
Normal file
27
distro/debian/dms/debian/copyright
Normal file
@@ -0,0 +1,27 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: dms
|
||||
Upstream-Contact: Avenge Media LLC <AvengeMedia.US@gmail.com>
|
||||
Source: https://github.com/AvengeMedia/DankMaterialShell
|
||||
|
||||
Files: *
|
||||
Copyright: 2025 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.
|
||||
1
distro/debian/dms/debian/files
Normal file
1
distro/debian/dms/debian/files
Normal file
@@ -0,0 +1 @@
|
||||
dms_0.6.0ppa2_source.buildinfo x11 optional
|
||||
71
distro/debian/dms/debian/rules
Executable file
71
distro/debian/dms/debian/rules
Executable file
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
DEB_VERSION := $(shell dpkg-parsechangelog -S Version)
|
||||
UPSTREAM_VERSION := $(shell echo $(DEB_VERSION) | sed 's/-[^-]*$$//')
|
||||
DEB_HOST_ARCH := $(shell dpkg-architecture -qDEB_HOST_ARCH)
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
override_dh_auto_build:
|
||||
if [ "$(DEB_HOST_ARCH)" = "amd64" ]; then \
|
||||
if [ -f dms-distropkg-amd64.gz ]; then \
|
||||
gunzip -c dms-distropkg-amd64.gz > dms; \
|
||||
elif [ -f ../SOURCES/dms-distropkg-amd64.gz ]; then \
|
||||
gunzip -c ../SOURCES/dms-distropkg-amd64.gz > dms; \
|
||||
elif [ -f ../../SOURCES/dms-distropkg-amd64.gz ]; then \
|
||||
gunzip -c ../../SOURCES/dms-distropkg-amd64.gz > dms; \
|
||||
else \
|
||||
echo "ERROR: dms-distropkg-amd64.gz not found!" && exit 1; \
|
||||
fi \
|
||||
elif [ "$(DEB_HOST_ARCH)" = "arm64" ]; then \
|
||||
if [ -f dms-distropkg-arm64.gz ]; then \
|
||||
gunzip -c dms-distropkg-arm64.gz > dms; \
|
||||
elif [ -f ../SOURCES/dms-distropkg-arm64.gz ]; then \
|
||||
gunzip -c ../SOURCES/dms-distropkg-arm64.gz > dms; \
|
||||
elif [ -f ../../SOURCES/dms-distropkg-arm64.gz ]; then \
|
||||
gunzip -c ../../SOURCES/dms-distropkg-arm64.gz > dms; \
|
||||
else \
|
||||
echo "ERROR: dms-distropkg-arm64.gz not found!" && exit 1; \
|
||||
fi \
|
||||
else \
|
||||
echo "Unsupported architecture: $(DEB_HOST_ARCH)" && exit 1; \
|
||||
fi
|
||||
chmod +x dms
|
||||
|
||||
if [ ! -d DankMaterialShell-$(UPSTREAM_VERSION) ]; then \
|
||||
if [ -f ../SOURCES/dms-source.tar.gz ]; then \
|
||||
tar -xzf ../SOURCES/dms-source.tar.gz; \
|
||||
elif [ -f dms-source.tar.gz ]; then \
|
||||
tar -xzf dms-source.tar.gz; \
|
||||
fi; \
|
||||
if [ ! -d DankMaterialShell-$(UPSTREAM_VERSION) ] && [ -d DankMaterialShell-0.6.2 ]; then \
|
||||
mv DankMaterialShell-0.6.2 DankMaterialShell-$(UPSTREAM_VERSION); \
|
||||
fi; \
|
||||
fi
|
||||
|
||||
|
||||
override_dh_auto_install:
|
||||
install -Dm755 dms debian/dms/usr/bin/dms
|
||||
|
||||
mkdir -p debian/dms/usr/share/quickshell/dms debian/dms/usr/lib/systemd/user
|
||||
# Handle directory name mismatch again for install step if needed
|
||||
if [ ! -d DankMaterialShell-$(UPSTREAM_VERSION) ] && [ -d DankMaterialShell-0.6.2 ]; then \
|
||||
mv DankMaterialShell-0.6.2 DankMaterialShell-$(UPSTREAM_VERSION); \
|
||||
fi
|
||||
if [ -d DankMaterialShell-$(UPSTREAM_VERSION) ]; then \
|
||||
cp -r DankMaterialShell-$(UPSTREAM_VERSION)/quickshell/* debian/dms/usr/share/quickshell/dms/; \
|
||||
install -Dm644 DankMaterialShell-$(UPSTREAM_VERSION)/quickshell/assets/systemd/dms.service debian/dms/usr/lib/systemd/user/dms.service; \
|
||||
else \
|
||||
echo "ERROR: DankMaterialShell-$(UPSTREAM_VERSION) directory not found!" && \
|
||||
echo "Contents of current directory:" && ls -la && \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
rm -rf debian/dms/usr/share/quickshell/dms/core \
|
||||
debian/dms/usr/share/quickshell/dms/distro
|
||||
|
||||
override_dh_auto_clean:
|
||||
rm -f dms
|
||||
rm -rf DankMaterialShell-$(UPSTREAM_VERSION)
|
||||
dh_auto_clean
|
||||
1
distro/debian/dms/debian/source/format
Normal file
1
distro/debian/dms/debian/source/format
Normal file
@@ -0,0 +1 @@
|
||||
3.0 (native)
|
||||
2
distro/debian/dms/debian/source/include-binaries
Normal file
2
distro/debian/dms/debian/source/include-binaries
Normal file
@@ -0,0 +1,2 @@
|
||||
dms-distropkg-amd64.gz
|
||||
dms-source.tar.gz
|
||||
4
distro/debian/dms/debian/source/options
Normal file
4
distro/debian/dms/debian/source/options
Normal file
@@ -0,0 +1,4 @@
|
||||
# Include files that are normally excluded by .gitignore
|
||||
# These are needed for the build process on Launchpad
|
||||
tar-ignore = !dms-distropkg-amd64.gz
|
||||
tar-ignore = !dms-source.tar.gz
|
||||
@@ -25,6 +25,7 @@ Requires(post): /usr/sbin/useradd
|
||||
Requires(post): /usr/sbin/groupadd
|
||||
|
||||
Recommends: policycoreutils-python-utils
|
||||
Recommends: setfacl
|
||||
Suggests: niri
|
||||
Suggests: hyprland
|
||||
Suggests: sway
|
||||
@@ -210,6 +211,15 @@ elif ! grep -q "dms-greeter" "$GREETD_CONFIG"; then
|
||||
CONFIG_STATUS="Updated existing config (backed up) with $COMPOSITOR ✓"
|
||||
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
|
||||
|
||||
# Only show banner on initial install
|
||||
if [ "$1" -eq 1 ]; then
|
||||
cat << 'EOF'
|
||||
@@ -219,31 +229,30 @@ cat << 'EOF'
|
||||
=========================================================================
|
||||
|
||||
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
|
||||
EOF
|
||||
echo " ✓ Greetd config: $CONFIG_STATUS"
|
||||
cat << 'EOF'
|
||||
|
||||
Next steps:
|
||||
|
||||
1. Disable any existing display managers (IMPORTANT):
|
||||
sudo systemctl disable gdm sddm lightdm
|
||||
1. Enable the greeter:
|
||||
dms greeter enable
|
||||
(This will automatically disable gdm, sddm, or lightdm display managers,
|
||||
set graphical.target, and enable greetd)
|
||||
|
||||
2. Enable greetd service:
|
||||
sudo systemctl enable greetd
|
||||
|
||||
3. (Optional) Sync your theme with the greeter:
|
||||
If you have DankMaterialShell (DMS) installed, you can sync with:
|
||||
2. Sync your theme with the greeter (optional):
|
||||
If you have DankMaterialShell (DMS) installed, you can sync theming with:
|
||||
dms greeter sync
|
||||
|
||||
Check sync status: dms greeter status
|
||||
Then logout/login to see your wallpaper on the greeter!
|
||||
3. Check your setup:
|
||||
dms greeter status
|
||||
|
||||
Ready to test? Reboot or run: sudo systemctl start greetd
|
||||
Documentation: /usr/share/doc/dms-greeter/README.md
|
||||
Website: https://danklinux.com/docs/dankgreeter/
|
||||
Ready to test? Run: sudo systemctl start greetd or simply log out/reboot your system
|
||||
Documentation: https://danklinux.com/docs/dankgreeter/
|
||||
=========================================================================
|
||||
|
||||
EOF
|
||||
|
||||
@@ -18,7 +18,7 @@ in {
|
||||
|
||||
systemd.user.services.dms = lib.mkIf cfg.systemd.enable {
|
||||
description = "DankMaterialShell";
|
||||
path = [cfg.quickshell.package];
|
||||
path = lib.mkForce [];
|
||||
|
||||
partOf = ["graphical-session.target"];
|
||||
after = ["graphical-session.target"];
|
||||
|
||||
25
distro/opensuse/_service
Normal file
25
distro/opensuse/_service
Normal file
@@ -0,0 +1,25 @@
|
||||
<services>
|
||||
<!-- Git source and vendoring -->
|
||||
<service name="tar_scm" mode="disabled">
|
||||
<param name="scm">git</param>
|
||||
<param name="url">https://github.com/AvengeMedia/DankMaterialShell.git</param>
|
||||
<param name="revision">master</param>
|
||||
<param name="filename">dms-git-source</param>
|
||||
</service>
|
||||
<service name="recompress" mode="disabled">
|
||||
<param name="file">*.tar</param>
|
||||
<param name="compression">gz</param>
|
||||
</service>
|
||||
<!-- Binary downloads removed - building from source
|
||||
<service name="download_url">
|
||||
<param name="protocol">https</param>
|
||||
<param name="host">github.com</param>
|
||||
<param name="path">/AvengeMedia/DankMaterialShell/releases/latest/download/dms-distropkg-amd64.gz</param>
|
||||
</service>
|
||||
<service name="download_url">
|
||||
<param name="protocol">https</param>
|
||||
<param name="host">github.com</param>
|
||||
<param name="path">/AvengeMedia/DankMaterialShell/releases/latest/download/dms-distropkg-arm64.gz</param>
|
||||
</service>
|
||||
-->
|
||||
</services>
|
||||
134
distro/opensuse/dms-git.spec
Normal file
134
distro/opensuse/dms-git.spec
Normal file
@@ -0,0 +1,134 @@
|
||||
%global debug_package %{nil}
|
||||
|
||||
Name: dms-git
|
||||
Version: 0.6.2+git2147.03073f68
|
||||
Release: 5%{?dist}
|
||||
Epoch: 1
|
||||
Summary: DankMaterialShell - Material 3 inspired shell (git nightly)
|
||||
|
||||
License: MIT
|
||||
URL: https://github.com/AvengeMedia/DankMaterialShell
|
||||
Source0: dms-git-source.tar.gz
|
||||
|
||||
BuildRequires: golang >= 1.22
|
||||
BuildRequires: golang-packaging
|
||||
BuildRequires: git-core
|
||||
BuildRequires: systemd-rpm-macros
|
||||
|
||||
Requires: (quickshell-git or quickshell)
|
||||
Requires: accountsservice
|
||||
Requires: dgop
|
||||
|
||||
Recommends: cava
|
||||
Recommends: cliphist
|
||||
Recommends: danksearch
|
||||
Recommends: matugen
|
||||
Recommends: quickshell-git
|
||||
Recommends: wl-clipboard
|
||||
|
||||
Recommends: NetworkManager
|
||||
Recommends: qt6-qtmultimedia
|
||||
Suggests: qt6ct
|
||||
|
||||
Provides: dms
|
||||
Conflicts: dms
|
||||
Obsoletes: dms
|
||||
|
||||
%description
|
||||
DankMaterialShell (DMS) is a modern Wayland desktop shell built with Quickshell
|
||||
and optimized for niri, Hyprland, Sway, and other wlroots compositors.
|
||||
|
||||
This git version tracks the master branch and includes the latest features
|
||||
and fixes. Includes pre-built dms CLI binary and QML shell files.
|
||||
|
||||
%prep
|
||||
%setup -q -n dms-git-source
|
||||
|
||||
# Verify vendored Go dependencies exist (vendored by obs-upload.sh before packaging)
|
||||
# OBS build environment has no network access
|
||||
test -d core/vendor || (echo "ERROR: Go vendor directory missing!" && exit 1)
|
||||
|
||||
%build
|
||||
# Create Go cache directories (OBS build env may have restricted HOME)
|
||||
export HOME=%{_builddir}/go-home
|
||||
export GOCACHE=%{_builddir}/go-cache
|
||||
export GOMODCACHE=%{_builddir}/go-mod
|
||||
mkdir -p $HOME $GOCACHE $GOMODCACHE
|
||||
|
||||
# OBS has no network access, so use local toolchain only
|
||||
export GOTOOLCHAIN=local
|
||||
|
||||
# Patch go.mod to use base Go version (e.g., go 1.24 instead of go 1.24.6)
|
||||
sed -i 's/^go 1\.24\.[0-9]*/go 1.24/' core/go.mod
|
||||
|
||||
# Extract version info for embedding in binary
|
||||
VERSION="%{version}"
|
||||
COMMIT=$(echo "%{version}" | grep -oP '(?<=git)[0-9]+\.[a-f0-9]+' | cut -d. -f2 | head -c8 || echo "unknown")
|
||||
|
||||
# Build dms-cli from source using vendored dependencies
|
||||
# Architecture mapping: RPM x86_64/aarch64 -> Makefile amd64/arm64
|
||||
cd core
|
||||
%ifarch x86_64
|
||||
make GOFLAGS="-mod=vendor" dist ARCH=amd64 VERSION="$VERSION" COMMIT="$COMMIT"
|
||||
mv bin/dms-linux-amd64 ../dms
|
||||
%endif
|
||||
%ifarch aarch64
|
||||
make GOFLAGS="-mod=vendor" dist ARCH=arm64 VERSION="$VERSION" COMMIT="$COMMIT"
|
||||
mv bin/dms-linux-arm64 ../dms
|
||||
%endif
|
||||
cd ..
|
||||
chmod +x dms
|
||||
|
||||
%install
|
||||
install -Dm755 dms %{buildroot}%{_bindir}/dms
|
||||
|
||||
install -d %{buildroot}%{_datadir}/bash-completion/completions
|
||||
install -d %{buildroot}%{_datadir}/zsh/site-functions
|
||||
install -d %{buildroot}%{_datadir}/fish/vendor_completions.d
|
||||
./dms completion bash > %{buildroot}%{_datadir}/bash-completion/completions/dms || :
|
||||
./dms completion zsh > %{buildroot}%{_datadir}/zsh/site-functions/_dms || :
|
||||
./dms completion fish > %{buildroot}%{_datadir}/fish/vendor_completions.d/dms.fish || :
|
||||
|
||||
install -Dm644 quickshell/assets/systemd/dms.service %{buildroot}%{_userunitdir}/dms.service
|
||||
|
||||
install -dm755 %{buildroot}%{_datadir}/quickshell/dms
|
||||
cp -r quickshell/* %{buildroot}%{_datadir}/quickshell/dms/
|
||||
|
||||
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.git*
|
||||
rm -f %{buildroot}%{_datadir}/quickshell/dms/.gitignore
|
||||
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.github
|
||||
rm -rf %{buildroot}%{_datadir}/quickshell/dms/distro
|
||||
rm -rf %{buildroot}%{_datadir}/quickshell/dms/core
|
||||
|
||||
%posttrans
|
||||
if [ -d "%{_sysconfdir}/xdg/quickshell/dms" ]; then
|
||||
rmdir "%{_sysconfdir}/xdg/quickshell/dms" 2>/dev/null || true
|
||||
rmdir "%{_sysconfdir}/xdg/quickshell" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if [ "$1" -ge 2 ]; then
|
||||
pkill -USR1 -x dms >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
%files
|
||||
%license LICENSE
|
||||
%doc CONTRIBUTING.md
|
||||
%doc quickshell/README.md
|
||||
%{_bindir}/dms
|
||||
%dir %{_datadir}/fish
|
||||
%dir %{_datadir}/fish/vendor_completions.d
|
||||
%{_datadir}/fish/vendor_completions.d/dms.fish
|
||||
%dir %{_datadir}/zsh
|
||||
%dir %{_datadir}/zsh/site-functions
|
||||
%{_datadir}/zsh/site-functions/_dms
|
||||
%{_datadir}/bash-completion/completions/dms
|
||||
%dir %{_datadir}/quickshell
|
||||
%{_datadir}/quickshell/dms/
|
||||
%{_userunitdir}/dms.service
|
||||
|
||||
%changelog
|
||||
* Tue Nov 25 2025 Avenge Media <AvengeMedia.US@gmail.com> - 0.6.2+git2147.03073f68-1
|
||||
- Git snapshot (commit 2147: 03073f68)
|
||||
* Fri Nov 22 2025 AvengeMedia <maintainer@avengemedia.com> - 0.6.2+git-5
|
||||
- Git nightly build from master branch
|
||||
- Multi-arch support (x86_64, aarch64)
|
||||
107
distro/opensuse/dms.spec
Normal file
107
distro/opensuse/dms.spec
Normal file
@@ -0,0 +1,107 @@
|
||||
# Spec for DMS for OpenSUSE/OBS
|
||||
|
||||
%global debug_package %{nil}
|
||||
|
||||
Name: dms
|
||||
Version: 0.6.2
|
||||
Release: 1%{?dist}
|
||||
Summary: DankMaterialShell - Material 3 inspired shell for Wayland compositors
|
||||
|
||||
License: MIT
|
||||
URL: https://github.com/AvengeMedia/DankMaterialShell
|
||||
Source0: dms-source.tar.gz
|
||||
Source1: dms-distropkg-amd64.gz
|
||||
Source2: dms-distropkg-arm64.gz
|
||||
|
||||
BuildRequires: gzip
|
||||
BuildRequires: systemd-rpm-macros
|
||||
|
||||
# Core requirements
|
||||
Requires: (quickshell-git or quickshell)
|
||||
Requires: accountsservice
|
||||
Requires: dgop
|
||||
|
||||
# Core utilities (Highly recommended for DMS functionality)
|
||||
Recommends: cava
|
||||
Recommends: cliphist
|
||||
Recommends: danksearch
|
||||
Recommends: matugen
|
||||
Recommends: NetworkManager
|
||||
Recommends: qt6-qtmultimedia
|
||||
Recommends: wl-clipboard
|
||||
Suggests: qt6ct
|
||||
|
||||
%description
|
||||
DankMaterialShell (DMS) is a modern Wayland desktop shell built with Quickshell
|
||||
and optimized for niri, Hyprland, Sway, and other wlroots compositors. Features
|
||||
notifications, app launcher, wallpaper customization, and plugin system.
|
||||
|
||||
Includes auto-theming for GTK/Qt apps with matugen, 20+ customizable widgets,
|
||||
process monitoring, notification center, clipboard history, dock, control center,
|
||||
lock screen, and comprehensive plugin system.
|
||||
|
||||
%prep
|
||||
%setup -q -n DankMaterialShell-%{version}
|
||||
|
||||
%ifarch x86_64
|
||||
gunzip -c %{SOURCE1} > dms
|
||||
%endif
|
||||
%ifarch aarch64
|
||||
gunzip -c %{SOURCE2} > dms
|
||||
%endif
|
||||
chmod +x dms
|
||||
|
||||
%build
|
||||
|
||||
%install
|
||||
install -Dm755 dms %{buildroot}%{_bindir}/dms
|
||||
|
||||
install -d %{buildroot}%{_datadir}/bash-completion/completions
|
||||
install -d %{buildroot}%{_datadir}/zsh/site-functions
|
||||
install -d %{buildroot}%{_datadir}/fish/vendor_completions.d
|
||||
./dms completion bash > %{buildroot}%{_datadir}/bash-completion/completions/dms || :
|
||||
./dms completion zsh > %{buildroot}%{_datadir}/zsh/site-functions/_dms || :
|
||||
./dms completion fish > %{buildroot}%{_datadir}/fish/vendor_completions.d/dms.fish || :
|
||||
|
||||
install -Dm644 quickshell/assets/systemd/dms.service %{buildroot}%{_userunitdir}/dms.service
|
||||
|
||||
install -dm755 %{buildroot}%{_datadir}/quickshell/dms
|
||||
cp -r quickshell/* %{buildroot}%{_datadir}/quickshell/dms/
|
||||
|
||||
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.git*
|
||||
rm -f %{buildroot}%{_datadir}/quickshell/dms/.gitignore
|
||||
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.github
|
||||
rm -rf %{buildroot}%{_datadir}/quickshell/dms/distro
|
||||
rm -rf %{buildroot}%{_datadir}/quickshell/dms/core
|
||||
|
||||
%posttrans
|
||||
if [ -d "%{_sysconfdir}/xdg/quickshell/dms" ]; then
|
||||
rmdir "%{_sysconfdir}/xdg/quickshell/dms" 2>/dev/null || true
|
||||
rmdir "%{_sysconfdir}/xdg/quickshell" 2>/dev/null || true
|
||||
rmdir "%{_sysconfdir}/xdg" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if [ "$1" -ge 2 ]; then
|
||||
pkill -USR1 -x dms >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
%files
|
||||
%license LICENSE
|
||||
%doc CONTRIBUTING.md
|
||||
%doc quickshell/README.md
|
||||
%{_bindir}/dms
|
||||
%dir %{_datadir}/fish
|
||||
%dir %{_datadir}/fish/vendor_completions.d
|
||||
%{_datadir}/fish/vendor_completions.d/dms.fish
|
||||
%dir %{_datadir}/zsh
|
||||
%dir %{_datadir}/zsh/site-functions
|
||||
%{_datadir}/zsh/site-functions/_dms
|
||||
%{_datadir}/bash-completion/completions/dms
|
||||
%dir %{_datadir}/quickshell
|
||||
%{_datadir}/quickshell/dms/
|
||||
%{_userunitdir}/dms.service
|
||||
|
||||
%changelog
|
||||
* Fri Nov 22 2025 AvengeMedia <maintainer@avengemedia.com> - 0.6.2-1
|
||||
- Stable release build with pre-built binaries
|
||||
- Multi-arch support (x86_64, aarch64)
|
||||
106
distro/scripts/obs-status.sh
Executable file
106
distro/scripts/obs-status.sh
Executable file
@@ -0,0 +1,106 @@
|
||||
#!/bin/bash
|
||||
# Unified OBS status checker for dms packages
|
||||
# Checks all platforms (Debian, OpenSUSE) and architectures (x86_64, aarch64)
|
||||
# Only pulls logs if build failed
|
||||
# Usage: ./distro/scripts/obs-status.sh [package-name]
|
||||
#
|
||||
# Examples:
|
||||
# ./distro/scripts/obs-status.sh # Check all packages
|
||||
# ./distro/scripts/obs-status.sh dms # Check specific package
|
||||
|
||||
OBS_BASE_PROJECT="home:AvengeMedia"
|
||||
OBS_BASE="$HOME/.cache/osc-checkouts"
|
||||
|
||||
ALL_PACKAGES=(dms dms-git)
|
||||
|
||||
REPOS=("Debian_13" "openSUSE_Tumbleweed" "16.0")
|
||||
ARCHES=("x86_64" "aarch64")
|
||||
|
||||
if [[ -n "$1" ]]; then
|
||||
PACKAGES=("$1")
|
||||
else
|
||||
PACKAGES=("${ALL_PACKAGES[@]}")
|
||||
fi
|
||||
|
||||
cd "$OBS_BASE"
|
||||
|
||||
for pkg in "${PACKAGES[@]}"; do
|
||||
case "$pkg" in
|
||||
dms)
|
||||
PROJECT="$OBS_BASE_PROJECT:dms"
|
||||
;;
|
||||
dms-git)
|
||||
PROJECT="$OBS_BASE_PROJECT:dms-git"
|
||||
;;
|
||||
*)
|
||||
echo "Error: Unknown package '$pkg'"
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "=========================================="
|
||||
echo "=== $pkg ==="
|
||||
echo "=========================================="
|
||||
|
||||
# Checkout if needed
|
||||
if [[ ! -d "$PROJECT/$pkg" ]]; then
|
||||
osc co "$PROJECT/$pkg" 2>&1 | tail -1
|
||||
fi
|
||||
|
||||
cd "$PROJECT/$pkg"
|
||||
|
||||
ALL_RESULTS=$(osc results 2>&1)
|
||||
|
||||
# Check each repository and architecture
|
||||
FAILED_BUILDS=()
|
||||
for repo in "${REPOS[@]}"; do
|
||||
for arch in "${ARCHES[@]}"; do
|
||||
STATUS=$(echo "$ALL_RESULTS" | grep "$repo.*$arch" | awk '{print $NF}' | head -1)
|
||||
|
||||
if [[ -n "$STATUS" ]]; then
|
||||
# Color code status
|
||||
case "$STATUS" in
|
||||
succeeded)
|
||||
COLOR="\033[0;32m" # Green
|
||||
SYMBOL="✅"
|
||||
;;
|
||||
failed)
|
||||
COLOR="\033[0;31m" # Red
|
||||
SYMBOL="❌"
|
||||
FAILED_BUILDS+=("$repo $arch")
|
||||
;;
|
||||
unresolvable)
|
||||
COLOR="\033[0;33m" # Yellow
|
||||
SYMBOL="⚠️"
|
||||
;;
|
||||
*)
|
||||
COLOR="\033[0;37m" # White
|
||||
SYMBOL="⏳"
|
||||
;;
|
||||
esac
|
||||
echo -e " $SYMBOL $repo $arch: ${COLOR}$STATUS\033[0m"
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
# Pull logs for failed builds
|
||||
if [[ ${#FAILED_BUILDS[@]} -gt 0 ]]; then
|
||||
echo ""
|
||||
echo " 📋 Fetching logs for failed builds..."
|
||||
for build in "${FAILED_BUILDS[@]}"; do
|
||||
read -r repo arch <<< "$build"
|
||||
echo ""
|
||||
echo " ────────────────────────────────────────────"
|
||||
echo " Build log: $repo $arch"
|
||||
echo " ────────────────────────────────────────────"
|
||||
osc remotebuildlog "$PROJECT" "$pkg" "$repo" "$arch" 2>&1 | tail -100
|
||||
done
|
||||
fi
|
||||
|
||||
echo ""
|
||||
cd - > /dev/null
|
||||
done
|
||||
|
||||
echo "=========================================="
|
||||
echo "Status check complete!"
|
||||
|
||||
917
distro/scripts/obs-upload.sh
Executable file
917
distro/scripts/obs-upload.sh
Executable file
@@ -0,0 +1,917 @@
|
||||
#!/bin/bash
|
||||
# Unified OBS upload script for dms packages
|
||||
# Handles Debian and OpenSUSE builds for both x86_64 and aarch64
|
||||
# Usage: ./distro/scripts/obs-upload.sh [distro] <package-name> [commit-message]
|
||||
#
|
||||
# Examples:
|
||||
# ./distro/scripts/obs-upload.sh dms "Update to v0.6.2"
|
||||
# ./distro/scripts/obs-upload.sh debian dms
|
||||
# ./distro/scripts/obs-upload.sh opensuse dms-git
|
||||
|
||||
set -e
|
||||
|
||||
UPLOAD_DEBIAN=true
|
||||
UPLOAD_OPENSUSE=true
|
||||
PACKAGE=""
|
||||
MESSAGE=""
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
debian)
|
||||
UPLOAD_DEBIAN=true
|
||||
UPLOAD_OPENSUSE=false
|
||||
;;
|
||||
opensuse)
|
||||
UPLOAD_DEBIAN=false
|
||||
UPLOAD_OPENSUSE=true
|
||||
;;
|
||||
*)
|
||||
if [[ -z "$PACKAGE" ]]; then
|
||||
PACKAGE="$arg"
|
||||
elif [[ -z "$MESSAGE" ]]; then
|
||||
MESSAGE="$arg"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
OBS_BASE_PROJECT="home:AvengeMedia"
|
||||
OBS_BASE="$HOME/.cache/osc-checkouts"
|
||||
AVAILABLE_PACKAGES=(dms dms-git)
|
||||
|
||||
if [[ -z "$PACKAGE" ]]; then
|
||||
echo "Available packages:"
|
||||
echo ""
|
||||
echo " 1. dms - Stable DMS"
|
||||
echo " 2. dms-git - Nightly DMS"
|
||||
echo " a. all"
|
||||
echo ""
|
||||
read -p "Select package (1-${#AVAILABLE_PACKAGES[@]}, a): " selection
|
||||
|
||||
if [[ "$selection" == "a" ]] || [[ "$selection" == "all" ]]; then
|
||||
PACKAGE="all"
|
||||
elif [[ "$selection" =~ ^[0-9]+$ ]] && [[ "$selection" -ge 1 ]] && [[ "$selection" -le ${#AVAILABLE_PACKAGES[@]} ]]; then
|
||||
PACKAGE="${AVAILABLE_PACKAGES[$((selection-1))]}"
|
||||
else
|
||||
echo "Error: Invalid selection"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
if [[ -z "$MESSAGE" ]]; then
|
||||
MESSAGE="Update packaging"
|
||||
fi
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
if [[ ! -d "distro/debian" ]]; then
|
||||
echo "Error: Run this script from the repository root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Handle "all" option
|
||||
if [[ "$PACKAGE" == "all" ]]; then
|
||||
echo "==> Uploading all packages"
|
||||
DISTRO_ARG=""
|
||||
if [[ "$UPLOAD_DEBIAN" == true && "$UPLOAD_OPENSUSE" == false ]]; then
|
||||
DISTRO_ARG="debian"
|
||||
elif [[ "$UPLOAD_DEBIAN" == false && "$UPLOAD_OPENSUSE" == true ]]; then
|
||||
DISTRO_ARG="opensuse"
|
||||
fi
|
||||
echo ""
|
||||
FAILED=()
|
||||
for pkg in "${AVAILABLE_PACKAGES[@]}"; do
|
||||
if [[ -d "distro/debian/$pkg" ]]; then
|
||||
echo "=========================================="
|
||||
echo "Uploading $pkg..."
|
||||
echo "=========================================="
|
||||
if [[ -n "$DISTRO_ARG" ]]; then
|
||||
if bash "$0" "$DISTRO_ARG" "$pkg" "$MESSAGE"; then
|
||||
echo "✅ $pkg uploaded successfully"
|
||||
else
|
||||
echo "❌ $pkg failed to upload"
|
||||
FAILED+=("$pkg")
|
||||
fi
|
||||
else
|
||||
if bash "$0" "$pkg" "$MESSAGE"; then
|
||||
echo "✅ $pkg uploaded successfully"
|
||||
else
|
||||
echo "❌ $pkg failed to upload"
|
||||
FAILED+=("$pkg")
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
else
|
||||
echo "⚠️ Skipping $pkg (not found in distro/debian/)"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#FAILED[@]} -eq 0 ]]; then
|
||||
echo "✅ All packages uploaded successfully!"
|
||||
exit 0
|
||||
else
|
||||
echo "❌ Some packages failed: ${FAILED[*]}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if package exists
|
||||
if [[ ! -d "distro/debian/$PACKAGE" ]]; then
|
||||
echo "Error: Package '$PACKAGE' not found in distro/debian/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$PACKAGE" in
|
||||
dms)
|
||||
PROJECT="dms"
|
||||
;;
|
||||
dms-git)
|
||||
PROJECT="dms-git"
|
||||
;;
|
||||
*)
|
||||
echo "Error: Unknown package '$PACKAGE'"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
OBS_PROJECT="${OBS_BASE_PROJECT}:${PROJECT}"
|
||||
|
||||
echo "==> Target: $OBS_PROJECT / $PACKAGE"
|
||||
if [[ "$UPLOAD_DEBIAN" == true && "$UPLOAD_OPENSUSE" == true ]]; then
|
||||
echo "==> Distributions: Debian + OpenSUSE"
|
||||
elif [[ "$UPLOAD_DEBIAN" == true ]]; then
|
||||
echo "==> Distribution: Debian only"
|
||||
elif [[ "$UPLOAD_OPENSUSE" == true ]]; then
|
||||
echo "==> Distribution: OpenSUSE only"
|
||||
fi
|
||||
|
||||
mkdir -p "$OBS_BASE"
|
||||
|
||||
if [[ ! -d "$OBS_BASE/$OBS_PROJECT/$PACKAGE" ]]; then
|
||||
echo "Checking out $OBS_PROJECT/$PACKAGE..."
|
||||
cd "$OBS_BASE"
|
||||
osc co "$OBS_PROJECT/$PACKAGE"
|
||||
cd "$REPO_ROOT"
|
||||
fi
|
||||
|
||||
WORK_DIR="$OBS_BASE/$OBS_PROJECT/$PACKAGE"
|
||||
|
||||
echo "==> Preparing $PACKAGE for OBS upload"
|
||||
|
||||
find "$WORK_DIR" -maxdepth 1 -type f \( -name "*.tar.gz" -o -name "*.tar.xz" -o -name "*.tar.bz2" -o -name "*.tar" -o -name "*.spec" -o -name "_service" -o -name "*.dsc" \) -delete 2>/dev/null || true
|
||||
|
||||
if [[ -f "distro/debian/$PACKAGE/_service" ]]; then
|
||||
echo " - Copying _service (for binary downloads)"
|
||||
cp "distro/debian/$PACKAGE/_service" "$WORK_DIR/"
|
||||
fi
|
||||
|
||||
CHANGELOG_VERSION=""
|
||||
if [[ -d "distro/debian/$PACKAGE/debian" ]]; then
|
||||
CHANGELOG_VERSION=$(grep -m1 "^$PACKAGE" "distro/debian/$PACKAGE/debian/changelog" 2>/dev/null | sed 's/.*(\([^)]*\)).*/\1/' || echo "")
|
||||
if [[ -n "$CHANGELOG_VERSION" ]] && [[ "$CHANGELOG_VERSION" == *"-"* ]]; then
|
||||
SOURCE_FORMAT_CHECK=$(cat "distro/debian/$PACKAGE/debian/source/format" 2>/dev/null || echo "3.0 (quilt)")
|
||||
if [[ "$SOURCE_FORMAT_CHECK" == *"native"* ]]; then
|
||||
CHANGELOG_VERSION=$(echo "$CHANGELOG_VERSION" | sed 's/-[0-9]*$//')
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$UPLOAD_OPENSUSE" == true ]] && [[ -f "distro/opensuse/$PACKAGE.spec" ]]; then
|
||||
echo " - Copying $PACKAGE.spec for OpenSUSE"
|
||||
cp "distro/opensuse/$PACKAGE.spec" "$WORK_DIR/"
|
||||
|
||||
if [[ -f "$WORK_DIR/.osc/$PACKAGE.spec" ]]; then
|
||||
NEW_VERSION=$(grep "^Version:" "$WORK_DIR/$PACKAGE.spec" | awk '{print $2}' | head -1)
|
||||
NEW_RELEASE=$(grep "^Release:" "$WORK_DIR/$PACKAGE.spec" | sed 's/^Release:[[:space:]]*//' | sed 's/%{?dist}//' | head -1)
|
||||
OLD_VERSION=$(grep "^Version:" "$WORK_DIR/.osc/$PACKAGE.spec" | awk '{print $2}' | head -1)
|
||||
OLD_RELEASE=$(grep "^Release:" "$WORK_DIR/.osc/$PACKAGE.spec" | sed 's/^Release:[[:space:]]*//' | sed 's/%{?dist}//' | head -1)
|
||||
|
||||
if [[ "$NEW_VERSION" == "$OLD_VERSION" ]]; then
|
||||
if [[ "$OLD_RELEASE" =~ ^([0-9]+) ]]; then
|
||||
BASE_RELEASE="${BASH_REMATCH[1]}"
|
||||
NEXT_RELEASE=$((BASE_RELEASE + 1))
|
||||
echo " - Detected rebuild of same version $NEW_VERSION (release $OLD_RELEASE -> $NEXT_RELEASE)"
|
||||
sed -i "s/^Release:[[:space:]]*${NEW_RELEASE}%{?dist}/Release: ${NEXT_RELEASE}%{?dist}/" "$WORK_DIR/$PACKAGE.spec"
|
||||
fi
|
||||
else
|
||||
echo " - New version detected: $OLD_VERSION -> $NEW_VERSION (keeping release $NEW_RELEASE)"
|
||||
fi
|
||||
else
|
||||
echo " - First upload to OBS (no previous spec found)"
|
||||
fi
|
||||
elif [[ "$UPLOAD_OPENSUSE" == true ]]; then
|
||||
echo " - Warning: OpenSUSE spec file not found, skipping OpenSUSE upload"
|
||||
fi
|
||||
|
||||
if [[ "$UPLOAD_OPENSUSE" == true ]] && [[ "$UPLOAD_DEBIAN" == false ]] && [[ -f "distro/opensuse/$PACKAGE.spec" ]]; then
|
||||
echo " - OpenSUSE-only upload: creating source tarball"
|
||||
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
trap "rm -rf $TEMP_DIR" EXIT
|
||||
|
||||
if [[ -f "distro/debian/$PACKAGE/_service" ]] && grep -q "tar_scm" "distro/debian/$PACKAGE/_service"; then
|
||||
GIT_URL=$(grep -A 5 'name="tar_scm"' "distro/debian/$PACKAGE/_service" | grep "url" | sed 's/.*<param name="url">\(.*\)<\/param>.*/\1/')
|
||||
GIT_REVISION=$(grep -A 5 'name="tar_scm"' "distro/debian/$PACKAGE/_service" | grep "revision" | sed 's/.*<param name="revision">\(.*\)<\/param>.*/\1/')
|
||||
|
||||
if [[ -n "$GIT_URL" ]]; then
|
||||
echo " Cloning git source from: $GIT_URL (revision: ${GIT_REVISION:-master})"
|
||||
SOURCE_DIR="$TEMP_DIR/dms-git-source"
|
||||
if git clone --depth 1 --branch "${GIT_REVISION:-master}" "$GIT_URL" "$SOURCE_DIR" 2>/dev/null || \
|
||||
git clone --depth 1 "$GIT_URL" "$SOURCE_DIR" 2>/dev/null; then
|
||||
cd "$SOURCE_DIR"
|
||||
if [[ -n "$GIT_REVISION" ]]; then
|
||||
git checkout "$GIT_REVISION" 2>/dev/null || true
|
||||
fi
|
||||
rm -rf .git
|
||||
SOURCE_DIR=$(pwd)
|
||||
cd "$REPO_ROOT"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n "$SOURCE_DIR" && -d "$SOURCE_DIR" ]]; then
|
||||
SOURCE0=$(grep "^Source0:" "distro/opensuse/$PACKAGE.spec" | awk '{print $2}' | head -1)
|
||||
|
||||
if [[ -n "$SOURCE0" ]]; then
|
||||
OBS_TARBALL_DIR=$(mktemp -d -t obs-tarball-XXXXXX)
|
||||
cd "$OBS_TARBALL_DIR"
|
||||
|
||||
case "$PACKAGE" in
|
||||
dms)
|
||||
DMS_VERSION=$(grep "^Version:" "$REPO_ROOT/distro/opensuse/$PACKAGE.spec" | sed 's/^Version:[[:space:]]*//' | head -1)
|
||||
EXPECTED_DIR="DankMaterialShell-${DMS_VERSION}"
|
||||
;;
|
||||
dms-git)
|
||||
EXPECTED_DIR="dms-git-source"
|
||||
;;
|
||||
*)
|
||||
EXPECTED_DIR=$(basename "$SOURCE_DIR")
|
||||
;;
|
||||
esac
|
||||
|
||||
echo " Creating $SOURCE0 (directory: $EXPECTED_DIR)"
|
||||
cp -r "$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)"
|
||||
|
||||
cd "$REPO_ROOT"
|
||||
rm -rf "$OBS_TARBALL_DIR"
|
||||
fi
|
||||
else
|
||||
echo " - Warning: Could not obtain source for OpenSUSE tarball"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Generate .dsc file and handle source format (for Debian only)
|
||||
if [[ "$UPLOAD_DEBIAN" == true ]] && [[ -d "distro/debian/$PACKAGE/debian" ]]; then
|
||||
# Use CHANGELOG_VERSION already set above, or get it if not set
|
||||
if [[ -z "$CHANGELOG_VERSION" ]]; then
|
||||
CHANGELOG_VERSION=$(grep -m1 "^$PACKAGE" distro/debian/$PACKAGE/debian/changelog 2>/dev/null | sed 's/.*(\([^)]*\)).*/\1/' || echo "0.1.11")
|
||||
fi
|
||||
|
||||
# Determine source format
|
||||
SOURCE_FORMAT=$(cat "distro/debian/$PACKAGE/debian/source/format" 2>/dev/null || echo "3.0 (quilt)")
|
||||
|
||||
# For native format, remove any Debian revision (-N) from version
|
||||
# Native format cannot have revisions, so strip them if present
|
||||
if [[ "$SOURCE_FORMAT" == *"native"* ]] && [[ "$CHANGELOG_VERSION" == *"-"* ]]; then
|
||||
# Remove Debian revision (everything from - onwards)
|
||||
CHANGELOG_VERSION=$(echo "$CHANGELOG_VERSION" | sed 's/-[0-9]*$//')
|
||||
echo " Warning: Removed Debian revision from version for native format: $CHANGELOG_VERSION"
|
||||
fi
|
||||
|
||||
if [[ "$SOURCE_FORMAT" == *"native"* ]]; then
|
||||
echo " - Native format detected: creating combined tarball"
|
||||
|
||||
VERSION="$CHANGELOG_VERSION"
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
trap "rm -rf $TEMP_DIR" EXIT
|
||||
COMBINED_TARBALL="${PACKAGE}_${VERSION}.tar.gz"
|
||||
SOURCE_DIR=""
|
||||
|
||||
if [[ -f "distro/debian/$PACKAGE/_service" ]]; then
|
||||
if grep -q "tar_scm" "distro/debian/$PACKAGE/_service"; then
|
||||
GIT_URL=$(grep -A 5 'name="tar_scm"' "distro/debian/$PACKAGE/_service" | grep "url" | sed 's/.*<param name="url">\(.*\)<\/param>.*/\1/')
|
||||
GIT_REVISION=$(grep -A 5 'name="tar_scm"' "distro/debian/$PACKAGE/_service" | grep "revision" | sed 's/.*<param name="revision">\(.*\)<\/param>.*/\1/')
|
||||
|
||||
if [[ -n "$GIT_URL" ]]; then
|
||||
echo " Cloning git source from: $GIT_URL (revision: ${GIT_REVISION:-master})"
|
||||
SOURCE_DIR="$TEMP_DIR/dms-git-source"
|
||||
if git clone --depth 1 --branch "${GIT_REVISION:-master}" "$GIT_URL" "$SOURCE_DIR" 2>/dev/null || \
|
||||
git clone --depth 1 "$GIT_URL" "$SOURCE_DIR" 2>/dev/null; then
|
||||
cd "$SOURCE_DIR"
|
||||
if [[ -n "$GIT_REVISION" ]]; then
|
||||
git checkout "$GIT_REVISION" 2>/dev/null || true
|
||||
fi
|
||||
rm -rf .git
|
||||
SOURCE_DIR=$(pwd)
|
||||
cd "$REPO_ROOT"
|
||||
else
|
||||
echo "Error: Failed to clone git repository"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
elif grep -q "download_url" "distro/debian/$PACKAGE/_service" && [[ "$PACKAGE" != "dms-git" ]]; then
|
||||
ALL_PATHS=$(grep -A 5 '<service name="download_url">' "distro/debian/$PACKAGE/_service" | \
|
||||
grep '<param name="path">' | \
|
||||
sed 's/.*<param name="path">\(.*\)<\/param>.*/\1/')
|
||||
|
||||
SOURCE_PATH=""
|
||||
for path in $ALL_PATHS; do
|
||||
if echo "$path" | grep -qE "(source|archive|\.tar\.(gz|xz|bz2))" && \
|
||||
! echo "$path" | grep -qE "(distropkg|binary)"; then
|
||||
SOURCE_PATH="$path"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -z "$SOURCE_PATH" ]]; then
|
||||
for path in $ALL_PATHS; do
|
||||
if echo "$path" | grep -qE "\.tar\.(gz|xz|bz2)$"; then
|
||||
SOURCE_PATH="$path"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ -n "$SOURCE_PATH" ]]; then
|
||||
SOURCE_BLOCK=$(awk -v target="$SOURCE_PATH" '
|
||||
/<service name="download_url">/ { in_block=1; block="" }
|
||||
in_block { block=block"\n"$0 }
|
||||
/<\/service>/ {
|
||||
if (in_block && block ~ target) {
|
||||
print block
|
||||
exit
|
||||
}
|
||||
in_block=0
|
||||
}
|
||||
' "distro/debian/$PACKAGE/_service")
|
||||
|
||||
URL_PROTOCOL=$(echo "$SOURCE_BLOCK" | grep "protocol" | sed 's/.*<param name="protocol">\(.*\)<\/param>.*/\1/' | head -1)
|
||||
URL_HOST=$(echo "$SOURCE_BLOCK" | grep "host" | sed 's/.*<param name="host">\(.*\)<\/param>.*/\1/' | head -1)
|
||||
URL_PATH="$SOURCE_PATH"
|
||||
fi
|
||||
|
||||
if [[ -n "$URL_PROTOCOL" && -n "$URL_HOST" && -n "$URL_PATH" ]]; then
|
||||
SOURCE_URL="${URL_PROTOCOL}://${URL_HOST}${URL_PATH}"
|
||||
echo " Downloading source from: $SOURCE_URL"
|
||||
|
||||
if wget -q -O "$TEMP_DIR/source-archive" "$SOURCE_URL" 2>/dev/null || \
|
||||
curl -L -f -s -o "$TEMP_DIR/source-archive" "$SOURCE_URL" 2>/dev/null; then
|
||||
cd "$TEMP_DIR"
|
||||
if [[ "$SOURCE_URL" == *.tar.xz ]]; then
|
||||
tar -xJf source-archive
|
||||
elif [[ "$SOURCE_URL" == *.tar.gz ]] || [[ "$SOURCE_URL" == *.tgz ]]; then
|
||||
tar -xzf source-archive
|
||||
fi
|
||||
SOURCE_DIR=$(find . -maxdepth 1 -type d -name "DankMaterialShell-*" | head -1)
|
||||
if [[ -z "$SOURCE_DIR" ]]; then
|
||||
SOURCE_DIR=$(find . -maxdepth 1 -type d ! -name "." | head -1)
|
||||
fi
|
||||
if [[ -z "$SOURCE_DIR" || ! -d "$SOURCE_DIR" ]]; then
|
||||
echo "Error: Failed to extract source archive or find source directory"
|
||||
echo "Contents of $TEMP_DIR:"
|
||||
ls -la "$TEMP_DIR"
|
||||
cd "$REPO_ROOT"
|
||||
exit 1
|
||||
fi
|
||||
SOURCE_DIR=$(cd "$SOURCE_DIR" && pwd)
|
||||
cd "$REPO_ROOT"
|
||||
else
|
||||
echo "Error: Failed to download source from $SOURCE_URL"
|
||||
echo "Tried both wget and curl. Please check the URL and network connectivity."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "$SOURCE_DIR" || ! -d "$SOURCE_DIR" ]]; then
|
||||
echo "Error: Could not determine or obtain source for $PACKAGE"
|
||||
echo "SOURCE_DIR: $SOURCE_DIR"
|
||||
if [[ -d "$TEMP_DIR" ]]; then
|
||||
echo "Contents of temp directory:"
|
||||
ls -la "$TEMP_DIR"
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo " Found source directory: $SOURCE_DIR"
|
||||
|
||||
# Vendor Go dependencies for dms-git
|
||||
if [[ "$PACKAGE" == "dms-git" ]] && [[ -d "$SOURCE_DIR/core" ]]; then
|
||||
echo " - Vendoring Go dependencies for offline OBS build..."
|
||||
cd "$SOURCE_DIR/core"
|
||||
|
||||
if ! command -v go &> /dev/null; then
|
||||
echo "ERROR: Go not found. Install Go to vendor dependencies."
|
||||
echo " Install: sudo apt-get install golang-go (Debian/Ubuntu)"
|
||||
echo " or: sudo dnf install golang (Fedora)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Vendor dependencies
|
||||
go mod vendor
|
||||
if [ ! -d "vendor" ]; then
|
||||
echo "ERROR: Failed to vendor Go dependencies"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VENDOR_SIZE=$(du -sh vendor | cut -f1)
|
||||
echo " ✓ Go dependencies vendored ($VENDOR_SIZE)"
|
||||
cd "$REPO_ROOT"
|
||||
fi
|
||||
|
||||
# Create OpenSUSE-compatible source tarballs BEFORE adding debian/ directory
|
||||
if [[ "$UPLOAD_OPENSUSE" == true ]] && [[ -f "distro/opensuse/$PACKAGE.spec" ]]; then
|
||||
echo " - Creating OpenSUSE-compatible source tarballs"
|
||||
|
||||
SOURCE0=$(grep "^Source0:" "distro/opensuse/$PACKAGE.spec" | awk '{print $2}' | head -1)
|
||||
if [[ -z "$SOURCE0" && "$PACKAGE" == "dms-git" ]]; then
|
||||
SOURCE0="dms-git-source.tar.gz"
|
||||
fi
|
||||
|
||||
if [[ -n "$SOURCE0" ]]; then
|
||||
OBS_TARBALL_DIR=$(mktemp -d -t obs-tarball-XXXXXX)
|
||||
cd "$OBS_TARBALL_DIR"
|
||||
|
||||
case "$PACKAGE" in
|
||||
dms)
|
||||
if [[ -n "$CHANGELOG_VERSION" ]]; then
|
||||
DMS_VERSION="$CHANGELOG_VERSION"
|
||||
else
|
||||
DMS_VERSION=$(grep "^Version:" "$REPO_ROOT/distro/opensuse/$PACKAGE.spec" | sed 's/^Version:[[:space:]]*//' | head -1)
|
||||
fi
|
||||
EXPECTED_DIR="DankMaterialShell-${DMS_VERSION}"
|
||||
echo " Creating $SOURCE0 (directory: $EXPECTED_DIR)"
|
||||
cp -r "$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-git)
|
||||
EXPECTED_DIR="dms-git-source"
|
||||
echo " Creating $SOURCE0 (directory: $EXPECTED_DIR)"
|
||||
cp -r "$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)"
|
||||
;;
|
||||
*)
|
||||
DIR_NAME=$(basename "$SOURCE_DIR")
|
||||
echo " Creating $SOURCE0 (directory: $DIR_NAME)"
|
||||
cp -r "$SOURCE_DIR" "$DIR_NAME"
|
||||
if [[ "$SOURCE0" == *.tar.xz ]]; then
|
||||
tar --sort=name --mtime='2000-01-01 00:00:00' -cJf "$WORK_DIR/$SOURCE0" "$DIR_NAME"
|
||||
elif [[ "$SOURCE0" == *.tar.bz2 ]]; then
|
||||
tar --sort=name --mtime='2000-01-01 00:00:00' -cjf "$WORK_DIR/$SOURCE0" "$DIR_NAME"
|
||||
else
|
||||
tar --sort=name --mtime='2000-01-01 00:00:00' -czf "$WORK_DIR/$SOURCE0" "$DIR_NAME"
|
||||
fi
|
||||
rm -rf "$DIR_NAME"
|
||||
echo " Created $SOURCE0 ($(stat -c%s "$WORK_DIR/$SOURCE0" 2>/dev/null || echo 0) bytes)"
|
||||
;;
|
||||
esac
|
||||
cd "$REPO_ROOT"
|
||||
rm -rf "$OBS_TARBALL_DIR"
|
||||
echo " - OpenSUSE source tarballs created"
|
||||
fi
|
||||
|
||||
cp "distro/opensuse/$PACKAGE.spec" "$WORK_DIR/"
|
||||
fi
|
||||
|
||||
if [[ "$UPLOAD_DEBIAN" == true ]]; then
|
||||
echo " Copying debian/ directory into source"
|
||||
cp -r "distro/debian/$PACKAGE/debian" "$SOURCE_DIR/"
|
||||
|
||||
# For dms, rename directory to match what debian/rules expects
|
||||
# debian/rules uses UPSTREAM_VERSION which is the full version from changelog
|
||||
if [[ "$PACKAGE" == "dms" ]]; then
|
||||
CHANGELOG_IN_SOURCE="$SOURCE_DIR/debian/changelog"
|
||||
if [[ -f "$CHANGELOG_IN_SOURCE" ]]; then
|
||||
ACTUAL_VERSION=$(grep -m1 "^$PACKAGE" "$CHANGELOG_IN_SOURCE" 2>/dev/null | sed 's/.*(\([^)]*\)).*/\1/' || echo "$VERSION")
|
||||
CURRENT_DIR=$(basename "$SOURCE_DIR")
|
||||
EXPECTED_DIR="DankMaterialShell-${ACTUAL_VERSION}"
|
||||
if [[ "$CURRENT_DIR" != "$EXPECTED_DIR" ]]; then
|
||||
echo " Renaming directory from $CURRENT_DIR to $EXPECTED_DIR to match debian/rules"
|
||||
cd "$(dirname "$SOURCE_DIR")"
|
||||
mv "$CURRENT_DIR" "$EXPECTED_DIR"
|
||||
SOURCE_DIR="$(pwd)/$EXPECTED_DIR"
|
||||
cd "$REPO_ROOT"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
rm -f "$WORK_DIR/$COMBINED_TARBALL"
|
||||
|
||||
echo " Creating combined tarball: $COMBINED_TARBALL"
|
||||
cd "$(dirname "$SOURCE_DIR")"
|
||||
TARBALL_BASE=$(basename "$SOURCE_DIR")
|
||||
tar --sort=name --mtime='2000-01-01 00:00:00' --owner=0 --group=0 -czf "$WORK_DIR/$COMBINED_TARBALL" "$TARBALL_BASE"
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
if [[ "$PACKAGE" == "dms" ]]; then
|
||||
TARBALL_DIR=$(tar -tzf "$WORK_DIR/$COMBINED_TARBALL" 2>/dev/null | head -1 | cut -d'/' -f1)
|
||||
EXPECTED_TARBALL_DIR="DankMaterialShell-${VERSION}"
|
||||
if [[ "$TARBALL_DIR" != "$EXPECTED_TARBALL_DIR" ]]; then
|
||||
echo " Warning: Tarball directory name mismatch: $TARBALL_DIR != $EXPECTED_TARBALL_DIR"
|
||||
echo " This may cause build failures. Recreating tarball..."
|
||||
cd "$(dirname "$SOURCE_DIR")"
|
||||
rm -f "$WORK_DIR/$COMBINED_TARBALL"
|
||||
tar --sort=name --mtime='2000-01-01 00:00:00' --owner=0 --group=0 -czf "$WORK_DIR/$COMBINED_TARBALL" "$TARBALL_BASE"
|
||||
cd "$REPO_ROOT"
|
||||
fi
|
||||
fi
|
||||
|
||||
TARBALL_SIZE=$(stat -c%s "$WORK_DIR/$COMBINED_TARBALL" 2>/dev/null || stat -f%z "$WORK_DIR/$COMBINED_TARBALL" 2>/dev/null)
|
||||
TARBALL_MD5=$(md5sum "$WORK_DIR/$COMBINED_TARBALL" | cut -d' ' -f1)
|
||||
|
||||
# Extract Build-Depends from debian/control using awk for proper multi-line parsing
|
||||
if [[ -f "$REPO_ROOT/distro/debian/$PACKAGE/debian/control" ]]; then
|
||||
BUILD_DEPS=$(awk '
|
||||
/^Build-Depends:/ {
|
||||
in_build_deps=1;
|
||||
sub(/^Build-Depends:[[:space:]]*/, "");
|
||||
printf "%s", $0;
|
||||
next;
|
||||
}
|
||||
in_build_deps && /^[[:space:]]/ {
|
||||
sub(/^[[:space:]]+/, " ");
|
||||
printf "%s", $0;
|
||||
next;
|
||||
}
|
||||
in_build_deps { exit; }
|
||||
' "$REPO_ROOT/distro/debian/$PACKAGE/debian/control" | sed 's/[[:space:]]\+/ /g; s/^[[:space:]]*//; s/[[:space:]]*$//')
|
||||
|
||||
# If extraction failed or is empty, use default fallback
|
||||
if [[ -z "$BUILD_DEPS" ]]; then
|
||||
BUILD_DEPS="debhelper-compat (= 13)"
|
||||
fi
|
||||
else
|
||||
BUILD_DEPS="debhelper-compat (= 13)"
|
||||
fi
|
||||
|
||||
cat > "$WORK_DIR/$PACKAGE.dsc" << EOF
|
||||
Format: 3.0 (native)
|
||||
Source: $PACKAGE
|
||||
Binary: $PACKAGE
|
||||
Architecture: any
|
||||
Version: $VERSION
|
||||
Maintainer: Avenge Media <AvengeMedia.US@gmail.com>
|
||||
Build-Depends: $BUILD_DEPS
|
||||
Files:
|
||||
$TARBALL_MD5 $TARBALL_SIZE $COMBINED_TARBALL
|
||||
EOF
|
||||
|
||||
echo " - Generated $PACKAGE.dsc for native format"
|
||||
fi
|
||||
else
|
||||
if [[ "$UPLOAD_DEBIAN" == true ]]; then
|
||||
if [[ "$CHANGELOG_VERSION" == *"-"* ]]; then
|
||||
VERSION="$CHANGELOG_VERSION"
|
||||
else
|
||||
VERSION="${CHANGELOG_VERSION}-1"
|
||||
fi
|
||||
|
||||
echo " - Quilt format detected: creating debian.tar.gz"
|
||||
tar -czf "$WORK_DIR/debian.tar.gz" -C "distro/debian/$PACKAGE" debian/
|
||||
|
||||
echo " - Generating $PACKAGE.dsc for quilt format"
|
||||
cat > "$WORK_DIR/$PACKAGE.dsc" << EOF
|
||||
Format: 3.0 (quilt)
|
||||
Source: $PACKAGE
|
||||
Binary: $PACKAGE
|
||||
Architecture: any
|
||||
Version: $VERSION
|
||||
Maintainer: Avenge Media <AvengeMedia.US@gmail.com>
|
||||
Build-Depends: debhelper-compat (= 13), wget, gzip
|
||||
DEBTRANSFORM-TAR: debian.tar.gz
|
||||
Files:
|
||||
00000000000000000000000000000000 1 debian.tar.gz
|
||||
EOF
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
cd "$WORK_DIR"
|
||||
|
||||
echo "==> Updating working copy"
|
||||
if ! osc up; then
|
||||
echo "Error: Failed to update working copy"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Only auto-increment on manual runs (REBUILD_RELEASE set or not in CI), not automated workflows
|
||||
OLD_DSC_FILE=""
|
||||
if [[ -f "$WORK_DIR/$PACKAGE.dsc" ]]; then
|
||||
OLD_DSC_FILE="$WORK_DIR/$PACKAGE.dsc"
|
||||
elif [[ -f "$WORK_DIR/.osc/sources/$PACKAGE.dsc" ]]; then
|
||||
OLD_DSC_FILE="$WORK_DIR/.osc/sources/$PACKAGE.dsc"
|
||||
fi
|
||||
|
||||
if [[ "$UPLOAD_DEBIAN" == true ]] && [[ "$SOURCE_FORMAT" == *"native"* ]] && [[ -n "$OLD_DSC_FILE" ]]; then
|
||||
OLD_DSC_VERSION=$(grep "^Version:" "$OLD_DSC_FILE" 2>/dev/null | awk '{print $2}' | head -1)
|
||||
|
||||
IS_MANUAL=false
|
||||
if [[ -n "${REBUILD_RELEASE:-}" ]]; then
|
||||
IS_MANUAL=true
|
||||
echo "==> Manual rebuild detected (REBUILD_RELEASE=$REBUILD_RELEASE)"
|
||||
elif [[ -n "${FORCE_REBUILD:-}" ]] && [[ "${FORCE_REBUILD}" == "true" ]]; then
|
||||
IS_MANUAL=true
|
||||
echo "==> Manual workflow trigger detected (FORCE_REBUILD=true)"
|
||||
elif [[ -z "${GITHUB_ACTIONS:-}" ]] && [[ -z "${CI:-}" ]]; then
|
||||
IS_MANUAL=true
|
||||
echo "==> Local/manual run detected (not in CI)"
|
||||
fi
|
||||
|
||||
if [[ -n "$OLD_DSC_VERSION" ]] && [[ "$OLD_DSC_VERSION" == "$CHANGELOG_VERSION" ]] && [[ "$IS_MANUAL" == true ]]; then
|
||||
echo "==> Detected rebuild of same version $CHANGELOG_VERSION, incrementing version"
|
||||
|
||||
if [[ "$CHANGELOG_VERSION" =~ ^([0-9.]+)\+git$ ]]; then
|
||||
BASE_VERSION="${BASH_REMATCH[1]}"
|
||||
NEW_VERSION="${BASE_VERSION}+git1"
|
||||
echo " Incrementing git number: $CHANGELOG_VERSION -> $NEW_VERSION"
|
||||
elif [[ "$CHANGELOG_VERSION" =~ ^([0-9.]+)\+git([0-9]+)$ ]]; then
|
||||
BASE_VERSION="${BASH_REMATCH[1]}"
|
||||
GIT_NUM="${BASH_REMATCH[2]}"
|
||||
NEW_GIT_NUM=$((GIT_NUM + 1))
|
||||
NEW_VERSION="${BASE_VERSION}+git${NEW_GIT_NUM}"
|
||||
echo " Incrementing git number: $CHANGELOG_VERSION -> $NEW_VERSION"
|
||||
elif [[ "$CHANGELOG_VERSION" =~ ^([0-9.]+)ppa([0-9]+)$ ]]; then
|
||||
BASE_VERSION="${BASH_REMATCH[1]}"
|
||||
PPA_NUM="${BASH_REMATCH[2]}"
|
||||
NEW_PPA_NUM=$((PPA_NUM + 1))
|
||||
NEW_VERSION="${BASE_VERSION}ppa${NEW_PPA_NUM}"
|
||||
echo " Incrementing PPA number: $CHANGELOG_VERSION -> $NEW_VERSION"
|
||||
elif [[ "$CHANGELOG_VERSION" =~ ^([0-9.]+)\+git([0-9]+)(\.[a-f0-9]+)?(ppa([0-9]+))?$ ]]; then
|
||||
BASE_VERSION="${BASH_REMATCH[1]}"
|
||||
GIT_NUM="${BASH_REMATCH[2]}"
|
||||
GIT_HASH="${BASH_REMATCH[3]}"
|
||||
PPA_NUM="${BASH_REMATCH[5]}"
|
||||
if [[ -n "$PPA_NUM" ]]; then
|
||||
NEW_PPA_NUM=$((PPA_NUM + 1))
|
||||
NEW_VERSION="${BASE_VERSION}+git${GIT_NUM}${GIT_HASH}ppa${NEW_PPA_NUM}"
|
||||
echo " Incrementing PPA number: $CHANGELOG_VERSION -> $NEW_VERSION"
|
||||
else
|
||||
NEW_VERSION="${BASE_VERSION}+git${GIT_NUM}${GIT_HASH}ppa1"
|
||||
echo " Adding PPA number: $CHANGELOG_VERSION -> $NEW_VERSION"
|
||||
fi
|
||||
elif [[ "$CHANGELOG_VERSION" =~ ^([0-9.]+)(-([0-9]+))?$ ]]; then
|
||||
BASE_VERSION="${BASH_REMATCH[1]}"
|
||||
NEW_VERSION="${BASE_VERSION}ppa1"
|
||||
echo " Warning: Native format cannot have Debian revision, converting to PPA format: $CHANGELOG_VERSION -> $NEW_VERSION"
|
||||
else
|
||||
NEW_VERSION="${CHANGELOG_VERSION}ppa1"
|
||||
echo " Warning: Could not parse version format, appending ppa1: $CHANGELOG_VERSION -> $NEW_VERSION"
|
||||
fi
|
||||
|
||||
if [[ -z "$SOURCE_DIR" ]] || [[ ! -d "$SOURCE_DIR" ]] || [[ ! -d "$SOURCE_DIR/debian" ]]; then
|
||||
echo " Error: Source directory with debian/ not found for version increment"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SOURCE_CHANGELOG="$SOURCE_DIR/debian/changelog"
|
||||
if [[ ! -f "$SOURCE_CHANGELOG" ]]; then
|
||||
echo " Error: Changelog not found in source directory: $SOURCE_CHANGELOG"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
REPO_CHANGELOG="$REPO_ROOT/distro/debian/$PACKAGE/debian/changelog"
|
||||
TEMP_CHANGELOG=$(mktemp)
|
||||
{
|
||||
echo "$PACKAGE ($NEW_VERSION) unstable; urgency=medium"
|
||||
echo ""
|
||||
echo " * Rebuild to fix repository metadata issues"
|
||||
echo ""
|
||||
echo " -- Avenge Media <AvengeMedia.US@gmail.com> $(date -R)"
|
||||
echo ""
|
||||
if [[ -f "$REPO_CHANGELOG" ]]; then
|
||||
OLD_ENTRY_START=$(grep -n "^$PACKAGE (" "$REPO_CHANGELOG" | sed -n '2p' | cut -d: -f1)
|
||||
if [[ -n "$OLD_ENTRY_START" ]]; then
|
||||
tail -n +$OLD_ENTRY_START "$REPO_CHANGELOG"
|
||||
fi
|
||||
fi
|
||||
} > "$TEMP_CHANGELOG"
|
||||
cp "$TEMP_CHANGELOG" "$SOURCE_CHANGELOG"
|
||||
rm -f "$TEMP_CHANGELOG"
|
||||
|
||||
CHANGELOG_VERSION="$NEW_VERSION"
|
||||
VERSION="$NEW_VERSION"
|
||||
COMBINED_TARBALL="${PACKAGE}_${VERSION}.tar.gz"
|
||||
|
||||
for old_tarball in "${PACKAGE}"_*.tar.gz; do
|
||||
if [[ -f "$old_tarball" ]] && [[ "$old_tarball" != "${PACKAGE}_${NEW_VERSION}.tar.gz" ]]; then
|
||||
echo " Removing old tarball from OBS: $old_tarball"
|
||||
osc rm -f "$old_tarball" 2>/dev/null || rm -f "$old_tarball"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$PACKAGE" == "dms" ]] && [[ -f "$WORK_DIR/dms-source.tar.gz" ]]; then
|
||||
echo " Recreating dms-source.tar.gz with new directory name for incremented version"
|
||||
EXPECTED_SOURCE_DIR="DankMaterialShell-${NEW_VERSION}"
|
||||
TEMP_SOURCE_DIR=$(mktemp -d)
|
||||
cd "$TEMP_SOURCE_DIR"
|
||||
tar -xzf "$WORK_DIR/dms-source.tar.gz" 2>/dev/null || tar -xJf "$WORK_DIR/dms-source.tar.gz" 2>/dev/null || tar -xjf "$WORK_DIR/dms-source.tar.gz" 2>/dev/null
|
||||
EXTRACTED=$(find . -maxdepth 1 -type d -name "DankMaterialShell-*" | head -1)
|
||||
if [[ -n "$EXTRACTED" ]] && [[ "$EXTRACTED" != "./$EXPECTED_SOURCE_DIR" ]]; then
|
||||
echo " Renaming $EXTRACTED to $EXPECTED_SOURCE_DIR"
|
||||
mv "$EXTRACTED" "$EXPECTED_SOURCE_DIR"
|
||||
rm -f "$WORK_DIR/dms-source.tar.gz"
|
||||
tar --sort=name --mtime='2000-01-01 00:00:00' --owner=0 --group=0 -czf "$WORK_DIR/dms-source.tar.gz" "$EXPECTED_SOURCE_DIR"
|
||||
ROOT_DIR=$(tar -tf "$WORK_DIR/dms-source.tar.gz" | head -1 | cut -d/ -f1)
|
||||
if [[ "$ROOT_DIR" != "$EXPECTED_SOURCE_DIR" ]]; then
|
||||
echo " Error: Recreated tarball has wrong root directory: $ROOT_DIR (expected $EXPECTED_SOURCE_DIR)"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
cd "$REPO_ROOT"
|
||||
rm -rf "$TEMP_SOURCE_DIR"
|
||||
fi
|
||||
|
||||
echo " Recreating tarball with new version: $COMBINED_TARBALL"
|
||||
if [[ -n "$SOURCE_DIR" ]] && [[ -d "$SOURCE_DIR" ]] && [[ -d "$SOURCE_DIR/debian" ]]; then
|
||||
if [[ "$PACKAGE" == "dms" ]]; then
|
||||
cd "$(dirname "$SOURCE_DIR")"
|
||||
CURRENT_DIR=$(basename "$SOURCE_DIR")
|
||||
EXPECTED_DIR="DankMaterialShell-${NEW_VERSION}"
|
||||
if [[ "$CURRENT_DIR" != "$EXPECTED_DIR" ]]; then
|
||||
echo " Renaming directory from $CURRENT_DIR to $EXPECTED_DIR to match debian/rules"
|
||||
if [[ -d "$CURRENT_DIR" ]]; then
|
||||
mv "$CURRENT_DIR" "$EXPECTED_DIR"
|
||||
SOURCE_DIR="$(pwd)/$EXPECTED_DIR"
|
||||
else
|
||||
echo " Warning: Source directory $CURRENT_DIR not found, extracting from existing tarball"
|
||||
OLD_TARBALL=$(ls "${PACKAGE}"_*.tar.gz 2>/dev/null | head -1)
|
||||
if [[ -f "$OLD_TARBALL" ]]; then
|
||||
EXTRACT_DIR=$(mktemp -d)
|
||||
cd "$EXTRACT_DIR"
|
||||
tar -xzf "$WORK_DIR/$OLD_TARBALL"
|
||||
EXTRACTED_DIR=$(find . -maxdepth 1 -type d -name "DankMaterialShell-*" | head -1)
|
||||
if [[ -n "$EXTRACTED_DIR" ]] && [[ "$EXTRACTED_DIR" != "./$EXPECTED_DIR" ]]; then
|
||||
mv "$EXTRACTED_DIR" "$EXPECTED_DIR"
|
||||
if [[ -f "$EXPECTED_DIR/debian/changelog" ]]; then
|
||||
ACTUAL_VER=$(grep -m1 "^$PACKAGE" "$EXPECTED_DIR/debian/changelog" 2>/dev/null | sed 's/.*(\([^)]*\)).*/\1/')
|
||||
if [[ "$ACTUAL_VER" != "$NEW_VERSION" ]]; then
|
||||
echo " Updating changelog version in extracted directory"
|
||||
REPO_CHANGELOG="$REPO_ROOT/distro/debian/$PACKAGE/debian/changelog"
|
||||
TEMP_CHANGELOG=$(mktemp)
|
||||
{
|
||||
echo "$PACKAGE ($NEW_VERSION) unstable; urgency=medium"
|
||||
echo ""
|
||||
echo " * Rebuild to fix repository metadata issues"
|
||||
echo ""
|
||||
echo " -- Avenge Media <AvengeMedia.US@gmail.com> $(date -R)"
|
||||
echo ""
|
||||
if [[ -f "$REPO_CHANGELOG" ]]; then
|
||||
OLD_ENTRY_START=$(grep -n "^$PACKAGE (" "$REPO_CHANGELOG" | sed -n '2p' | cut -d: -f1)
|
||||
if [[ -n "$OLD_ENTRY_START" ]]; then
|
||||
tail -n +$OLD_ENTRY_START "$REPO_CHANGELOG"
|
||||
fi
|
||||
fi
|
||||
} > "$TEMP_CHANGELOG"
|
||||
cp "$TEMP_CHANGELOG" "$EXPECTED_DIR/debian/changelog"
|
||||
rm -f "$TEMP_CHANGELOG"
|
||||
fi
|
||||
fi
|
||||
SOURCE_DIR="$(pwd)/$EXPECTED_DIR"
|
||||
cd "$REPO_ROOT"
|
||||
else
|
||||
echo " Error: Could not extract or find source directory"
|
||||
rm -rf "$EXTRACT_DIR"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo " Error: No existing tarball found to extract"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
cd "$(dirname "$SOURCE_DIR")"
|
||||
TARBALL_BASE=$(basename "$SOURCE_DIR")
|
||||
tar --sort=name --mtime='2000-01-01 00:00:00' --owner=0 --group=0 -czf "$WORK_DIR/$COMBINED_TARBALL" "$TARBALL_BASE"
|
||||
cd "$WORK_DIR"
|
||||
|
||||
TARBALL_SIZE=$(stat -c%s "$WORK_DIR/$COMBINED_TARBALL" 2>/dev/null || stat -f%z "$WORK_DIR/$COMBINED_TARBALL" 2>/dev/null)
|
||||
TARBALL_MD5=$(md5sum "$WORK_DIR/$COMBINED_TARBALL" | cut -d' ' -f1)
|
||||
|
||||
# Extract Build-Depends from debian/control using awk for proper multi-line parsing
|
||||
if [[ -f "$REPO_ROOT/distro/debian/$PACKAGE/debian/control" ]]; then
|
||||
BUILD_DEPS=$(awk '
|
||||
/^Build-Depends:/ {
|
||||
in_build_deps=1;
|
||||
sub(/^Build-Depends:[[:space:]]*/, "");
|
||||
printf "%s", $0;
|
||||
next;
|
||||
}
|
||||
in_build_deps && /^[[:space:]]/ {
|
||||
sub(/^[[:space:]]+/, " ");
|
||||
printf "%s", $0;
|
||||
next;
|
||||
}
|
||||
in_build_deps { exit; }
|
||||
' "$REPO_ROOT/distro/debian/$PACKAGE/debian/control" | sed 's/[[:space:]]\+/ /g; s/^[[:space:]]*//; s/[[:space:]]*$//')
|
||||
|
||||
# If extraction failed or is empty, use default fallback
|
||||
if [[ -z "$BUILD_DEPS" ]]; then
|
||||
BUILD_DEPS="debhelper-compat (= 13)"
|
||||
fi
|
||||
else
|
||||
BUILD_DEPS="debhelper-compat (= 13)"
|
||||
fi
|
||||
|
||||
cat > "$WORK_DIR/$PACKAGE.dsc" << EOF
|
||||
Format: 3.0 (native)
|
||||
Source: $PACKAGE
|
||||
Binary: $PACKAGE
|
||||
Architecture: any
|
||||
Version: $VERSION
|
||||
Maintainer: Avenge Media <AvengeMedia.US@gmail.com>
|
||||
Build-Depends: $BUILD_DEPS
|
||||
Files:
|
||||
$TARBALL_MD5 $TARBALL_SIZE $COMBINED_TARBALL
|
||||
EOF
|
||||
echo " - Updated changelog and recreated tarball with version $NEW_VERSION"
|
||||
else
|
||||
echo " Error: Source directory not found, cannot recreate tarball"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
find . -maxdepth 1 -type f \( -name "*.dsc" -o -name "*.spec" \) -exec grep -l "^<<<<<<< " {} \; 2>/dev/null | while read -r conflicted_file; do
|
||||
echo " Removing conflicted text file: $conflicted_file"
|
||||
rm -f "$conflicted_file"
|
||||
done
|
||||
|
||||
echo "==> Staging changes"
|
||||
echo "Files to upload:"
|
||||
if [[ "$UPLOAD_DEBIAN" == true ]] && [[ "$UPLOAD_OPENSUSE" == true ]]; then
|
||||
ls -lh *.tar.gz *.tar.xz *.tar *.spec *.dsc _service 2>/dev/null | awk '{print " " $9 " (" $5 ")"}'
|
||||
elif [[ "$UPLOAD_DEBIAN" == true ]]; then
|
||||
ls -lh *.tar.gz *.dsc _service 2>/dev/null | awk '{print " " $9 " (" $5 ")"}'
|
||||
elif [[ "$UPLOAD_OPENSUSE" == true ]]; then
|
||||
ls -lh *.tar.gz *.tar.xz *.tar *.spec _service 2>/dev/null | awk '{print " " $9 " (" $5 ")"}'
|
||||
fi
|
||||
echo ""
|
||||
|
||||
osc addremove 2>&1 | grep -v "Git SCM package" || true
|
||||
|
||||
SOURCE_TARBALL="${PACKAGE}-source.tar.gz"
|
||||
if [[ -f "$SOURCE_TARBALL" ]]; then
|
||||
echo "==> Ensuring $SOURCE_TARBALL is tracked by OBS"
|
||||
osc add "$SOURCE_TARBALL" 2>&1 | grep -v "already added\|already tracked\|Git SCM package" || true
|
||||
elif [[ -f "$WORK_DIR/$SOURCE_TARBALL" ]]; then
|
||||
echo "==> Copying $SOURCE_TARBALL from WORK_DIR and adding to OBS"
|
||||
cp "$WORK_DIR/$SOURCE_TARBALL" "$SOURCE_TARBALL"
|
||||
osc add "$SOURCE_TARBALL" 2>&1 | grep -v "already added\|already tracked\|Git SCM package" || true
|
||||
fi
|
||||
ADDREMOVE_EXIT=${PIPESTATUS[0]}
|
||||
if [[ $ADDREMOVE_EXIT -ne 0 ]] && [[ $ADDREMOVE_EXIT -ne 1 ]]; then
|
||||
echo "Warning: osc addremove returned exit code $ADDREMOVE_EXIT"
|
||||
fi
|
||||
|
||||
if osc status | grep -q '^C'; then
|
||||
echo "==> Resolving conflicts"
|
||||
osc status | grep '^C' | awk '{print $2}' | xargs -r osc resolved
|
||||
fi
|
||||
|
||||
if ! osc status 2>/dev/null | grep -qE '^[MAD]|^[?]'; then
|
||||
echo "==> No changes to commit (package already up to date)"
|
||||
else
|
||||
echo "==> Committing to OBS"
|
||||
set +e
|
||||
osc commit -m "$MESSAGE" 2>&1 | grep -v "Git SCM package" | grep -v "apiurl\|project\|_ObsPrj\|_manifest\|git-obs"
|
||||
COMMIT_EXIT=${PIPESTATUS[0]}
|
||||
set -e
|
||||
if [[ $COMMIT_EXIT -ne 0 ]]; then
|
||||
echo "Error: Upload failed with exit code $COMMIT_EXIT"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
osc results
|
||||
|
||||
echo ""
|
||||
echo "✅ Upload complete!"
|
||||
cd "$WORK_DIR"
|
||||
osc results 2>&1 | head -10
|
||||
cd "$REPO_ROOT"
|
||||
echo ""
|
||||
echo "Check build status with:"
|
||||
echo " ./distro/scripts/obs-status.sh $PACKAGE"
|
||||
590
distro/scripts/ppa-build.sh
Executable file
590
distro/scripts/ppa-build.sh
Executable file
@@ -0,0 +1,590 @@
|
||||
#!/bin/bash
|
||||
# Generic source package builder for DMS PPA packages
|
||||
# Usage: ./create-source.sh <package-dir> [ubuntu-series]
|
||||
#
|
||||
# Example:
|
||||
# ./create-source.sh ../dms questing
|
||||
# ./create-source.sh ../dms-git questing
|
||||
|
||||
set -e
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
if [ $# -lt 1 ]; then
|
||||
error "Usage: $0 <package-dir> [ubuntu-series]"
|
||||
echo
|
||||
echo "Arguments:"
|
||||
echo " package-dir : Path to package directory (e.g., ../dms)"
|
||||
echo " ubuntu-series : Ubuntu series (optional, default: noble)"
|
||||
echo " Options: noble, jammy, oracular, mantic"
|
||||
echo
|
||||
echo "Examples:"
|
||||
echo " $0 ../dms questing"
|
||||
echo " $0 ../dms-git questing"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PACKAGE_DIR="$1"
|
||||
UBUNTU_SERIES="${2:-noble}"
|
||||
|
||||
# Validate package directory
|
||||
if [ ! -d "$PACKAGE_DIR" ]; then
|
||||
error "Package directory not found: $PACKAGE_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$PACKAGE_DIR/debian" ]; then
|
||||
error "No debian/ directory found in $PACKAGE_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get absolute path
|
||||
PACKAGE_DIR=$(cd "$PACKAGE_DIR" && pwd)
|
||||
PACKAGE_NAME=$(basename "$PACKAGE_DIR")
|
||||
|
||||
info "Building source package for: $PACKAGE_NAME"
|
||||
info "Package directory: $PACKAGE_DIR"
|
||||
info "Target Ubuntu series: $UBUNTU_SERIES"
|
||||
|
||||
# Check for required files
|
||||
REQUIRED_FILES=(
|
||||
"debian/control"
|
||||
"debian/rules"
|
||||
"debian/changelog"
|
||||
"debian/copyright"
|
||||
"debian/source/format"
|
||||
)
|
||||
|
||||
for file in "${REQUIRED_FILES[@]}"; do
|
||||
if [ ! -f "$PACKAGE_DIR/$file" ]; then
|
||||
error "Required file missing: $file"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Verify GPG key is set up
|
||||
info "Checking GPG key setup..."
|
||||
if ! gpg --list-secret-keys &> /dev/null; then
|
||||
error "No GPG secret keys found. Please set up GPG first!"
|
||||
error "See GPG_SETUP.md for instructions"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
success "GPG key found"
|
||||
|
||||
# Check if debuild is installed
|
||||
if ! command -v debuild &> /dev/null; then
|
||||
error "debuild not found. Install devscripts:"
|
||||
error " sudo dnf install devscripts"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract package info from changelog
|
||||
cd "$PACKAGE_DIR"
|
||||
CHANGELOG_VERSION=$(dpkg-parsechangelog -S Version)
|
||||
SOURCE_NAME=$(dpkg-parsechangelog -S Source)
|
||||
|
||||
info "Source package: $SOURCE_NAME"
|
||||
info "Version: $CHANGELOG_VERSION"
|
||||
|
||||
# Check if version targets correct Ubuntu series
|
||||
CHANGELOG_SERIES=$(dpkg-parsechangelog -S Distribution)
|
||||
if [ "$CHANGELOG_SERIES" != "$UBUNTU_SERIES" ] && [ "$CHANGELOG_SERIES" != "UNRELEASED" ]; then
|
||||
warn "Changelog targets '$CHANGELOG_SERIES' but building for '$UBUNTU_SERIES'"
|
||||
warn "Consider updating changelog with: dch -r '' -D $UBUNTU_SERIES"
|
||||
fi
|
||||
|
||||
# Detect package type and update version automatically
|
||||
cd "$PACKAGE_DIR"
|
||||
|
||||
# Function to get latest tag from GitHub
|
||||
get_latest_tag() {
|
||||
local repo="$1"
|
||||
# Try GitHub API first (faster)
|
||||
if command -v curl &> /dev/null; then
|
||||
LATEST_TAG=$(curl -s "https://api.github.com/repos/$repo/releases/latest" 2>/dev/null | grep '"tag_name":' | sed 's/.*"tag_name": "\(.*\)".*/\1/' | head -1)
|
||||
if [ -n "$LATEST_TAG" ]; then
|
||||
echo "$LATEST_TAG" | sed 's/^v//'
|
||||
return
|
||||
fi
|
||||
fi
|
||||
# Fallback: clone and get latest tag
|
||||
TEMP_REPO=$(mktemp -d)
|
||||
if git clone --depth=1 --quiet "https://github.com/$repo.git" "$TEMP_REPO" 2>/dev/null; then
|
||||
LATEST_TAG=$(cd "$TEMP_REPO" && git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo "")
|
||||
rm -rf "$TEMP_REPO"
|
||||
echo "$LATEST_TAG"
|
||||
fi
|
||||
}
|
||||
|
||||
# Detect if package is git-based
|
||||
IS_GIT_PACKAGE=false
|
||||
GIT_REPO=""
|
||||
SOURCE_DIR=""
|
||||
|
||||
# Check package name for -git suffix
|
||||
if [[ "$PACKAGE_NAME" == *"-git" ]]; then
|
||||
IS_GIT_PACKAGE=true
|
||||
fi
|
||||
|
||||
# Check rules file for git clone patterns and extract repo
|
||||
if grep -q "git clone" debian/rules 2>/dev/null; then
|
||||
IS_GIT_PACKAGE=true
|
||||
# Extract GitHub repo URL from rules
|
||||
GIT_URL=$(grep -o "git clone.*https://github.com/[^/]*/[^/]*\.git" debian/rules 2>/dev/null | head -1 | sed 's/.*github\.com\///' | sed 's/\.git.*//' || echo "")
|
||||
if [ -n "$GIT_URL" ]; then
|
||||
GIT_REPO="$GIT_URL"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Special handling for known packages
|
||||
case "$PACKAGE_NAME" in
|
||||
dms-git)
|
||||
IS_GIT_PACKAGE=true
|
||||
GIT_REPO="AvengeMedia/DankMaterialShell"
|
||||
SOURCE_DIR="dms-git-repo"
|
||||
;;
|
||||
dms)
|
||||
GIT_REPO="AvengeMedia/DankMaterialShell"
|
||||
info "Downloading pre-built binaries and source for dms..."
|
||||
# Get version from changelog (remove ppa suffix for both quilt and native formats)
|
||||
# Native: 0.5.2ppa1 -> 0.5.2, Quilt: 0.5.2-1ppa1 -> 0.5.2
|
||||
VERSION=$(dpkg-parsechangelog -S Version | sed 's/-[^-]*$//' | sed 's/ppa[0-9]*$//')
|
||||
|
||||
# Download amd64 binary (will be included in source package)
|
||||
if [ ! -f "dms-distropkg-amd64.gz" ]; then
|
||||
info "Downloading dms binary for amd64..."
|
||||
if wget -O dms-distropkg-amd64.gz "https://github.com/AvengeMedia/DankMaterialShell/releases/download/v${VERSION}/dms-distropkg-amd64.gz"; then
|
||||
success "amd64 binary downloaded"
|
||||
else
|
||||
error "Failed to download dms-distropkg-amd64.gz"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Download source tarball for QML files
|
||||
if [ ! -f "dms-source.tar.gz" ]; then
|
||||
info "Downloading dms source for QML files..."
|
||||
if wget -O dms-source.tar.gz "https://github.com/AvengeMedia/DankMaterialShell/archive/refs/tags/v${VERSION}.tar.gz"; then
|
||||
success "source tarball downloaded"
|
||||
else
|
||||
error "Failed to download dms-source.tar.gz"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
dms-greeter)
|
||||
GIT_REPO="AvengeMedia/DankMaterialShell"
|
||||
info "Downloading source for dms-greeter..."
|
||||
VERSION=$(dpkg-parsechangelog -S Version | sed 's/-[^-]*$//' | sed 's/ppa[0-9]*$//')
|
||||
|
||||
if [ ! -f "dms-greeter-source.tar.gz" ]; then
|
||||
info "Downloading dms-greeter source..."
|
||||
if wget -O dms-greeter-source.tar.gz "https://github.com/AvengeMedia/DankMaterialShell/archive/refs/tags/v${VERSION}.tar.gz"; then
|
||||
success "source tarball downloaded"
|
||||
else
|
||||
error "Failed to download dms-greeter-source.tar.gz"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
danksearch)
|
||||
# danksearch uses pre-built binary from releases
|
||||
GIT_REPO="AvengeMedia/danksearch"
|
||||
;;
|
||||
dgop)
|
||||
# dgop uses pre-built binary from releases
|
||||
GIT_REPO="AvengeMedia/dgop"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Handle git packages
|
||||
if [ "$IS_GIT_PACKAGE" = true ] && [ -n "$GIT_REPO" ]; then
|
||||
info "Detected git package: $PACKAGE_NAME"
|
||||
|
||||
# Determine source directory name
|
||||
if [ -z "$SOURCE_DIR" ]; then
|
||||
# Default: use package name without -git suffix + -source or -repo
|
||||
BASE_NAME=$(echo "$PACKAGE_NAME" | sed 's/-git$//')
|
||||
if [ -d "${BASE_NAME}-source" ] 2>/dev/null; then
|
||||
SOURCE_DIR="${BASE_NAME}-source"
|
||||
elif [ -d "${BASE_NAME}-repo" ] 2>/dev/null; then
|
||||
SOURCE_DIR="${BASE_NAME}-repo"
|
||||
elif [ -d "$BASE_NAME" ] 2>/dev/null; then
|
||||
SOURCE_DIR="$BASE_NAME"
|
||||
else
|
||||
SOURCE_DIR="${BASE_NAME}-source"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Always clone fresh source to get latest commit info
|
||||
info "Cloning $GIT_REPO from GitHub (getting latest commit info)..."
|
||||
TEMP_CLONE=$(mktemp -d)
|
||||
if git clone "https://github.com/$GIT_REPO.git" "$TEMP_CLONE"; then
|
||||
# Get git commit info from fresh clone
|
||||
GIT_COMMIT_HASH=$(cd "$TEMP_CLONE" && git rev-parse --short HEAD)
|
||||
GIT_COMMIT_COUNT=$(cd "$TEMP_CLONE" && git rev-list --count HEAD)
|
||||
|
||||
# Get upstream version from latest git tag (e.g., 0.2.1)
|
||||
# Sort all tags by version and get the latest one (not just the one reachable from HEAD)
|
||||
UPSTREAM_VERSION=$(cd "$TEMP_CLONE" && git tag -l "v*" | sed 's/^v//' | sort -V | tail -1)
|
||||
if [ -z "$UPSTREAM_VERSION" ]; then
|
||||
# Fallback: try without v prefix
|
||||
UPSTREAM_VERSION=$(cd "$TEMP_CLONE" && git tag -l | grep -E '^[0-9]+\.[0-9]+\.[0-9]+' | sort -V | tail -1)
|
||||
fi
|
||||
if [ -z "$UPSTREAM_VERSION" ]; then
|
||||
# Last resort: use git describe
|
||||
UPSTREAM_VERSION=$(cd "$TEMP_CLONE" && git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo "0.0.1")
|
||||
fi
|
||||
|
||||
# Verify we got valid commit info
|
||||
if [ -z "$GIT_COMMIT_COUNT" ] || [ "$GIT_COMMIT_COUNT" = "0" ]; then
|
||||
error "Failed to get commit count from $GIT_REPO"
|
||||
rm -rf "$TEMP_CLONE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$GIT_COMMIT_HASH" ]; then
|
||||
error "Failed to get commit hash from $GIT_REPO"
|
||||
rm -rf "$TEMP_CLONE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
success "Got commit info: $GIT_COMMIT_COUNT ($GIT_COMMIT_HASH), upstream: $UPSTREAM_VERSION"
|
||||
|
||||
# Update changelog with git commit info
|
||||
info "Updating changelog with git commit info..."
|
||||
# Format: 0.2.1+git705.fdbb86appa1
|
||||
# Check if we're rebuilding the same commit (increment PPA number if so)
|
||||
BASE_VERSION="${UPSTREAM_VERSION}+git${GIT_COMMIT_COUNT}.${GIT_COMMIT_HASH}"
|
||||
CURRENT_VERSION=$(dpkg-parsechangelog -S Version 2>/dev/null || echo "")
|
||||
PPA_NUM=1
|
||||
|
||||
# If current version matches the base version, increment PPA number
|
||||
# Escape special regex characters in BASE_VERSION for pattern matching
|
||||
ESCAPED_BASE=$(echo "$BASE_VERSION" | sed 's/\./\\./g' | sed 's/+/\\+/g')
|
||||
if [[ "$CURRENT_VERSION" =~ ^${ESCAPED_BASE}ppa([0-9]+)$ ]]; then
|
||||
PPA_NUM=$((BASH_REMATCH[1] + 1))
|
||||
info "Detected rebuild of same commit (current: $CURRENT_VERSION), incrementing PPA number to $PPA_NUM"
|
||||
else
|
||||
info "New commit or first build, using PPA number $PPA_NUM"
|
||||
fi
|
||||
|
||||
NEW_VERSION="${BASE_VERSION}ppa${PPA_NUM}"
|
||||
|
||||
# Use sed to update changelog (non-interactive, faster)
|
||||
# Get current changelog content - find the next package header line (starts with package name)
|
||||
# Skip the first entry entirely by finding the second occurrence of the package name at start of line
|
||||
OLD_ENTRY_START=$(grep -n "^${SOURCE_NAME} (" debian/changelog | sed -n '2p' | cut -d: -f1)
|
||||
if [ -n "$OLD_ENTRY_START" ]; then
|
||||
# Found second entry, use everything from there
|
||||
CHANGELOG_CONTENT=$(tail -n +$OLD_ENTRY_START debian/changelog)
|
||||
else
|
||||
# No second entry found, changelog will only have new entry
|
||||
CHANGELOG_CONTENT=""
|
||||
fi
|
||||
|
||||
# Create new changelog entry with proper format
|
||||
CHANGELOG_ENTRY="${SOURCE_NAME} (${NEW_VERSION}) ${UBUNTU_SERIES}; urgency=medium
|
||||
|
||||
* Git snapshot (commit ${GIT_COMMIT_COUNT}: ${GIT_COMMIT_HASH})
|
||||
|
||||
-- Avenge Media <AvengeMedia.US@gmail.com> $(date -R)"
|
||||
|
||||
# Write new changelog (new entry, blank line, then old entries)
|
||||
echo "$CHANGELOG_ENTRY" > debian/changelog
|
||||
if [ -n "$CHANGELOG_CONTENT" ]; then
|
||||
echo "" >> debian/changelog
|
||||
echo "$CHANGELOG_CONTENT" >> debian/changelog
|
||||
fi
|
||||
success "Version updated to $NEW_VERSION"
|
||||
|
||||
# Now clone to source directory (without .git for inclusion in package)
|
||||
rm -rf "$SOURCE_DIR"
|
||||
cp -r "$TEMP_CLONE" "$SOURCE_DIR"
|
||||
|
||||
# Save version info for dms-git build process
|
||||
if [ "$PACKAGE_NAME" = "dms-git" ]; then
|
||||
info "Saving version info to .dms-version for build process..."
|
||||
echo "VERSION=${UPSTREAM_VERSION}+git${GIT_COMMIT_COUNT}.${GIT_COMMIT_HASH}" > "$SOURCE_DIR/.dms-version"
|
||||
echo "COMMIT=${GIT_COMMIT_HASH}" >> "$SOURCE_DIR/.dms-version"
|
||||
success "Version info saved: ${UPSTREAM_VERSION}+git${GIT_COMMIT_COUNT}.${GIT_COMMIT_HASH}"
|
||||
|
||||
# Vendor Go dependencies (Launchpad has no internet access)
|
||||
info "Vendoring Go dependencies for offline build..."
|
||||
cd "$SOURCE_DIR/core"
|
||||
|
||||
# Create vendor directory with all dependencies
|
||||
go mod vendor
|
||||
|
||||
if [ ! -d "vendor" ]; then
|
||||
error "Failed to vendor Go dependencies"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
success "Go dependencies vendored successfully"
|
||||
cd "$PACKAGE_DIR"
|
||||
fi
|
||||
|
||||
rm -rf "$SOURCE_DIR/.git"
|
||||
rm -rf "$TEMP_CLONE"
|
||||
|
||||
# Vendor Rust dependencies for packages that need it
|
||||
if false; then
|
||||
# No current packages need Rust vendoring
|
||||
if [ -f "$SOURCE_DIR/Cargo.toml" ]; then
|
||||
info "Vendoring Rust dependencies (Launchpad has no internet access)..."
|
||||
cd "$SOURCE_DIR"
|
||||
|
||||
# Clean up any existing vendor directory and .orig files
|
||||
# (prevents cargo from including .orig files in checksums)
|
||||
rm -rf vendor .cargo
|
||||
find . -type f -name "*.orig" -exec rm -f {} + || true
|
||||
|
||||
# Download all dependencies (crates.io + git repos) to vendor/
|
||||
# cargo vendor outputs the config to stderr, capture it
|
||||
mkdir -p .cargo
|
||||
cargo vendor 2>&1 | awk '
|
||||
/^\[source\.crates-io\]/ { printing=1 }
|
||||
printing { print }
|
||||
/^directory = "vendor"$/ { exit }
|
||||
' > .cargo/config.toml
|
||||
|
||||
# Verify vendor directory was created
|
||||
if [ ! -d "vendor" ]; then
|
||||
error "Failed to vendor dependencies"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify config was created
|
||||
if [ ! -s .cargo/config.toml ]; then
|
||||
error "Failed to create cargo config"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# CRITICAL: Remove ALL .orig files from vendor directory
|
||||
# These break cargo checksums when dh_clean tries to use them
|
||||
info "Cleaning .orig files from vendor directory..."
|
||||
find vendor -type f -name "*.orig" -exec rm -fv {} + || true
|
||||
find vendor -type f -name "*.rej" -exec rm -fv {} + || true
|
||||
|
||||
# Verify no .orig files remain
|
||||
ORIG_COUNT=$(find vendor -type f -name "*.orig" | wc -l)
|
||||
if [ "$ORIG_COUNT" -gt 0 ]; then
|
||||
warn "Found $ORIG_COUNT .orig files still in vendor directory"
|
||||
fi
|
||||
|
||||
success "Rust dependencies vendored (including git dependencies)"
|
||||
cd "$PACKAGE_DIR"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
success "Source prepared for packaging"
|
||||
else
|
||||
error "Failed to clone $GIT_REPO"
|
||||
rm -rf "$TEMP_CLONE"
|
||||
exit 1
|
||||
fi
|
||||
# Handle stable packages - get latest tag
|
||||
elif [ -n "$GIT_REPO" ]; then
|
||||
info "Detected stable package: $PACKAGE_NAME"
|
||||
info "Fetching latest tag from $GIT_REPO..."
|
||||
|
||||
LATEST_TAG=$(get_latest_tag "$GIT_REPO")
|
||||
if [ -n "$LATEST_TAG" ]; then
|
||||
# Check source format - native packages can't use dashes
|
||||
SOURCE_FORMAT=$(cat debian/source/format 2>/dev/null | head -1 || echo "3.0 (quilt)")
|
||||
|
||||
# Get current version to check if we need to increment PPA number
|
||||
CURRENT_VERSION=$(dpkg-parsechangelog -S Version 2>/dev/null || echo "")
|
||||
PPA_NUM=1
|
||||
|
||||
if [[ "$SOURCE_FORMAT" == *"native"* ]]; then
|
||||
# Native format: 0.2.1ppa1 (no dash, no revision)
|
||||
BASE_VERSION="${LATEST_TAG}"
|
||||
# Check if we're rebuilding the same version (increment PPA number if so)
|
||||
if [[ "$CURRENT_VERSION" =~ ^${LATEST_TAG}ppa([0-9]+)$ ]]; then
|
||||
PPA_NUM=$((BASH_REMATCH[1] + 1))
|
||||
info "Detected rebuild of same version (current: $CURRENT_VERSION), incrementing PPA number to $PPA_NUM"
|
||||
else
|
||||
info "New version or first build, using PPA number $PPA_NUM"
|
||||
fi
|
||||
NEW_VERSION="${BASE_VERSION}ppa${PPA_NUM}"
|
||||
else
|
||||
# Quilt format: 0.2.1-1ppa1 (with revision)
|
||||
BASE_VERSION="${LATEST_TAG}-1"
|
||||
# Check if we're rebuilding the same version (increment PPA number if so)
|
||||
ESCAPED_BASE=$(echo "$BASE_VERSION" | sed 's/\./\\./g' | sed 's/-/\\-/g')
|
||||
if [[ "$CURRENT_VERSION" =~ ^${ESCAPED_BASE}ppa([0-9]+)$ ]]; then
|
||||
PPA_NUM=$((BASH_REMATCH[1] + 1))
|
||||
info "Detected rebuild of same version (current: $CURRENT_VERSION), incrementing PPA number to $PPA_NUM"
|
||||
else
|
||||
info "New version or first build, using PPA number $PPA_NUM"
|
||||
fi
|
||||
NEW_VERSION="${BASE_VERSION}ppa${PPA_NUM}"
|
||||
fi
|
||||
|
||||
# Check if version needs updating (either new version or PPA number changed)
|
||||
if [ "$CURRENT_VERSION" != "$NEW_VERSION" ]; then
|
||||
if [ "$PPA_NUM" -gt 1 ]; then
|
||||
info "Updating changelog for rebuild (PPA number incremented to $PPA_NUM)"
|
||||
else
|
||||
info "Updating changelog to latest tag: $LATEST_TAG"
|
||||
fi
|
||||
# Use sed to update changelog (non-interactive)
|
||||
# Get current changelog content - find the next package header line
|
||||
OLD_ENTRY_START=$(grep -n "^${SOURCE_NAME} (" debian/changelog | sed -n '2p' | cut -d: -f1)
|
||||
if [ -n "$OLD_ENTRY_START" ]; then
|
||||
CHANGELOG_CONTENT=$(tail -n +$OLD_ENTRY_START debian/changelog)
|
||||
else
|
||||
CHANGELOG_CONTENT=""
|
||||
fi
|
||||
|
||||
# Create appropriate changelog message
|
||||
if [ "$PPA_NUM" -gt 1 ]; then
|
||||
CHANGELOG_MSG="Rebuild for packaging fixes (ppa${PPA_NUM})"
|
||||
else
|
||||
CHANGELOG_MSG="Upstream release ${LATEST_TAG}"
|
||||
fi
|
||||
|
||||
CHANGELOG_ENTRY="${SOURCE_NAME} (${NEW_VERSION}) ${UBUNTU_SERIES}; urgency=medium
|
||||
|
||||
* ${CHANGELOG_MSG}
|
||||
|
||||
-- Avenge Media <AvengeMedia.US@gmail.com> $(date -R)"
|
||||
echo "$CHANGELOG_ENTRY" > debian/changelog
|
||||
if [ -n "$CHANGELOG_CONTENT" ]; then
|
||||
echo "" >> debian/changelog
|
||||
echo "$CHANGELOG_CONTENT" >> debian/changelog
|
||||
fi
|
||||
success "Version updated to $NEW_VERSION"
|
||||
else
|
||||
info "Version already at latest tag: $LATEST_TAG"
|
||||
fi
|
||||
else
|
||||
warn "Could not determine latest tag for $GIT_REPO, using existing version"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Handle packages that need pre-built binaries downloaded
|
||||
cd "$PACKAGE_DIR"
|
||||
case "$PACKAGE_NAME" in
|
||||
danksearch)
|
||||
info "Downloading pre-built binaries for danksearch..."
|
||||
# Get version from changelog (remove ppa suffix for both quilt and native formats)
|
||||
# Native: 0.5.2ppa1 -> 0.5.2, Quilt: 0.5.2-1ppa1 -> 0.5.2
|
||||
VERSION=$(dpkg-parsechangelog -S Version | sed 's/-[^-]*$//' | sed 's/ppa[0-9]*$//')
|
||||
|
||||
# Download both amd64 and arm64 binaries (will be included in source package)
|
||||
# Launchpad can't download during build, so we include both architectures
|
||||
if [ ! -f "dsearch-amd64" ]; then
|
||||
info "Downloading dsearch binary for amd64..."
|
||||
if wget -O dsearch-amd64.gz "https://github.com/AvengeMedia/danksearch/releases/download/v${VERSION}/dsearch-linux-amd64.gz"; then
|
||||
gunzip dsearch-amd64.gz
|
||||
chmod +x dsearch-amd64
|
||||
success "amd64 binary downloaded"
|
||||
else
|
||||
error "Failed to download dsearch-amd64.gz"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -f "dsearch-arm64" ]; then
|
||||
info "Downloading dsearch binary for arm64..."
|
||||
if wget -O dsearch-arm64.gz "https://github.com/AvengeMedia/danksearch/releases/download/v${VERSION}/dsearch-linux-arm64.gz"; then
|
||||
gunzip dsearch-arm64.gz
|
||||
chmod +x dsearch-arm64
|
||||
success "arm64 binary downloaded"
|
||||
else
|
||||
error "Failed to download dsearch-arm64.gz"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
dgop)
|
||||
# dgop binary should already be committed in the repo
|
||||
if [ ! -f "dgop" ]; then
|
||||
warn "dgop binary not found - should be committed to repo"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
cd - > /dev/null
|
||||
|
||||
# Check if this version already exists on PPA (only in CI environment)
|
||||
if command -v rmadison >/dev/null 2>&1; then
|
||||
info "Checking if version already exists on PPA..."
|
||||
PPA_VERSION_CHECK=$(rmadison -u ppa:avengemedia/dms "$PACKAGE_NAME" 2>/dev/null | grep "$VERSION" || true)
|
||||
if [ -n "$PPA_VERSION_CHECK" ]; then
|
||||
warn "Version $VERSION already exists on PPA:"
|
||||
echo "$PPA_VERSION_CHECK"
|
||||
echo
|
||||
warn "Skipping upload to avoid duplicate. If this is a rebuild, increment the ppa number."
|
||||
cd "$PACKAGE_DIR"
|
||||
# Still clean up extracted sources
|
||||
case "$PACKAGE_NAME" in
|
||||
dms-git)
|
||||
rm -rf DankMaterialShell-*
|
||||
success "Cleaned up DankMaterialShell-*/ directory"
|
||||
;;
|
||||
esac
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Build source package
|
||||
info "Building source package..."
|
||||
echo
|
||||
|
||||
# Determine if we need to include orig tarball (-sa) or just debian changes (-sd)
|
||||
# Check if .orig.tar.xz already exists in parent directory (previous build)
|
||||
ORIG_TARBALL="${PACKAGE_NAME}_${VERSION%.ppa*}.orig.tar.xz"
|
||||
if [ -f "../$ORIG_TARBALL" ]; then
|
||||
info "Found existing orig tarball, using -sd (debian changes only)"
|
||||
DEBUILD_SOURCE_FLAG="-sd"
|
||||
else
|
||||
info "No existing orig tarball found, using -sa (include original source)"
|
||||
DEBUILD_SOURCE_FLAG="-sa"
|
||||
fi
|
||||
|
||||
# Use -S for source only, -sa/-sd for source inclusion
|
||||
# -d skips dependency checking (we're building on Fedora, not Ubuntu)
|
||||
# Pipe yes to automatically answer prompts (e.g., "continue anyway?")
|
||||
if yes | DEBIAN_FRONTEND=noninteractive debuild -S $DEBUILD_SOURCE_FLAG -d; then
|
||||
echo
|
||||
success "Source package built successfully!"
|
||||
|
||||
# List generated files
|
||||
info "Generated files in $(dirname "$PACKAGE_DIR"):"
|
||||
ls -lh "$(dirname "$PACKAGE_DIR")"/${SOURCE_NAME}_${CHANGELOG_VERSION}* 2>/dev/null || true
|
||||
|
||||
# Show what to do next
|
||||
echo
|
||||
info "Next steps:"
|
||||
echo " 1. Review the source package:"
|
||||
echo " cd $(dirname "$PACKAGE_DIR")"
|
||||
echo " ls -lh ${SOURCE_NAME}_${CHANGELOG_VERSION}*"
|
||||
echo
|
||||
echo " 2. Upload to PPA (stable):"
|
||||
echo " dput ppa:avengemedia/dms ${SOURCE_NAME}_${CHANGELOG_VERSION}_source.changes"
|
||||
echo
|
||||
echo " 3. Or upload to PPA (nightly):"
|
||||
echo " dput ppa:avengemedia/dms-git ${SOURCE_NAME}_${CHANGELOG_VERSION}_source.changes"
|
||||
echo
|
||||
echo " 4. Or use the upload script:"
|
||||
echo " ./upload-ppa.sh $(dirname "$PACKAGE_DIR")/${SOURCE_NAME}_${CHANGELOG_VERSION}_source.changes dms"
|
||||
|
||||
else
|
||||
error "Source package build failed!"
|
||||
exit 1
|
||||
fi
|
||||
179
distro/scripts/ppa-dput.sh
Executable file
179
distro/scripts/ppa-dput.sh
Executable file
@@ -0,0 +1,179 @@
|
||||
#!/bin/bash
|
||||
# Ubuntu PPA uploader for DMS packages
|
||||
# Usage: ./upload-ppa.sh <changes-file> <ppa-name>
|
||||
#
|
||||
# Example:
|
||||
# ./upload-ppa.sh ../dms_0.5.2ppa1_source.changes dms
|
||||
# ./upload-ppa.sh ../dms_0.5.2+git705.fdbb86appa1_source.changes dms-git
|
||||
|
||||
set -e
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
if [ $# -lt 2 ]; then
|
||||
error "Usage: $0 <changes-file> <ppa-name>"
|
||||
echo
|
||||
echo "Arguments:"
|
||||
echo " changes-file : Path to .changes file (e.g., ../dms_0.5.2ppa1_source.changes)"
|
||||
echo " ppa-name : PPA to upload to (dms or dms-git)"
|
||||
echo
|
||||
echo "Examples:"
|
||||
echo " $0 ../dms_0.5.2ppa1_source.changes dms"
|
||||
echo " $0 ../dms_0.5.2+git705.fdbb86appa1_source.changes dms-git"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CHANGES_FILE="$1"
|
||||
PPA_NAME="$2"
|
||||
|
||||
# Validate changes file
|
||||
if [ ! -f "$CHANGES_FILE" ]; then
|
||||
error "Changes file not found: $CHANGES_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! "$CHANGES_FILE" =~ \.changes$ ]]; then
|
||||
error "File must be a .changes file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate PPA name
|
||||
if [ "$PPA_NAME" != "dms" ] && [ "$PPA_NAME" != "dms-git" ] && [ "$PPA_NAME" != "danklinux" ]; then
|
||||
error "PPA name must be 'dms', 'dms-git', or 'danklinux'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get absolute path
|
||||
CHANGES_FILE=$(realpath "$CHANGES_FILE")
|
||||
|
||||
info "Uploading to PPA: ppa:avengemedia/$PPA_NAME"
|
||||
info "Changes file: $CHANGES_FILE"
|
||||
|
||||
# Check if dput or lftp is installed
|
||||
UPLOAD_METHOD=""
|
||||
if command -v dput &> /dev/null; then
|
||||
UPLOAD_METHOD="dput"
|
||||
elif command -v lftp &> /dev/null; then
|
||||
UPLOAD_METHOD="lftp"
|
||||
warn "dput not found, using lftp as fallback"
|
||||
else
|
||||
error "Neither dput nor lftp found. Install one with:"
|
||||
error " sudo dnf install dput-ng # Preferred but broken on Fedora"
|
||||
error " sudo dnf install lftp # Alternative upload method"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if ~/.dput.cf exists
|
||||
if [ ! -f "$HOME/.dput.cf" ]; then
|
||||
error "~/.dput.cf not found!"
|
||||
echo
|
||||
info "Create it from template:"
|
||||
echo " cp $(dirname "$0")/../dput.cf.template ~/.dput.cf"
|
||||
echo
|
||||
info "Or create it manually with:"
|
||||
cat <<'EOF'
|
||||
[ppa:avengemedia/dms]
|
||||
fqdn = ppa.launchpad.net
|
||||
method = ftp
|
||||
incoming = ~avengemedia/ubuntu/dms/
|
||||
login = anonymous
|
||||
allow_unsigned_uploads = 0
|
||||
|
||||
[ppa:avengemedia/dms-git]
|
||||
fqdn = ppa.launchpad.net
|
||||
method = ftp
|
||||
incoming = ~avengemedia/ubuntu/dms-git/
|
||||
login = anonymous
|
||||
allow_unsigned_uploads = 0
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if PPA is configured in dput.cf
|
||||
if ! grep -q "^\[ppa:avengemedia/$PPA_NAME\]" "$HOME/.dput.cf"; then
|
||||
error "PPA 'ppa:avengemedia/$PPA_NAME' not found in ~/.dput.cf"
|
||||
echo
|
||||
info "Add this to ~/.dput.cf:"
|
||||
cat <<EOF
|
||||
[ppa:avengemedia/$PPA_NAME]
|
||||
fqdn = ppa.launchpad.net
|
||||
method = ftp
|
||||
incoming = ~avengemedia/ubuntu/$PPA_NAME/
|
||||
login = anonymous
|
||||
allow_unsigned_uploads = 0
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract package info from changes file
|
||||
PACKAGE_NAME=$(grep "^Source:" "$CHANGES_FILE" | awk '{print $2}')
|
||||
VERSION=$(grep "^Version:" "$CHANGES_FILE" | awk '{print $2}')
|
||||
|
||||
info "Package: $PACKAGE_NAME"
|
||||
info "Version: $VERSION"
|
||||
|
||||
# Show files that will be uploaded
|
||||
echo
|
||||
info "Files to be uploaded:"
|
||||
grep "^ [a-f0-9]" "$CHANGES_FILE" | awk '{print " - " $5}' || true
|
||||
|
||||
# Verify GPG signature
|
||||
info "Verifying GPG signature..."
|
||||
if gpg --verify "$CHANGES_FILE" 2>/dev/null; then
|
||||
success "GPG signature valid"
|
||||
else
|
||||
error "GPG signature verification failed!"
|
||||
error "The .changes file must be signed with your GPG key"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ask for confirmation
|
||||
echo
|
||||
warn "About to upload to: ppa:avengemedia/$PPA_NAME"
|
||||
read -p "Continue? (y/N) " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
info "Upload cancelled"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Upload to PPA
|
||||
info "Uploading to Launchpad..."
|
||||
echo
|
||||
|
||||
if dput "ppa:avengemedia/$PPA_NAME" "$CHANGES_FILE"; then
|
||||
echo
|
||||
success "Upload successful!"
|
||||
echo
|
||||
info "Monitor build progress at:"
|
||||
echo " https://launchpad.net/~avengemedia/+archive/ubuntu/$PPA_NAME/+packages"
|
||||
echo
|
||||
info "Builds typically take 5-30 minutes depending on:"
|
||||
echo " - Build queue length"
|
||||
echo " - Package complexity"
|
||||
echo " - Number of target Ubuntu series"
|
||||
echo
|
||||
info "Once built, users can install with:"
|
||||
echo " sudo add-apt-repository ppa:avengemedia/$PPA_NAME"
|
||||
echo " sudo apt update"
|
||||
echo " sudo apt install $PACKAGE_NAME"
|
||||
|
||||
else
|
||||
error "Upload failed!"
|
||||
echo
|
||||
info "Common issues:"
|
||||
echo " - GPG key not verified on Launchpad (check https://launchpad.net/~/+editpgpkeys)"
|
||||
echo " - Version already uploaded (must increment version number)"
|
||||
echo " - Network/firewall blocking FTP (try HTTPS method in dput.cf)"
|
||||
echo " - Email in changelog doesn't match GPG key email"
|
||||
exit 1
|
||||
fi
|
||||
248
distro/scripts/ppa-upload.sh
Executable file
248
distro/scripts/ppa-upload.sh
Executable file
@@ -0,0 +1,248 @@
|
||||
#!/bin/bash
|
||||
# Build and upload PPA package with automatic cleanup
|
||||
# Usage: ./create-and-upload.sh <package-dir> <ppa-name> [ubuntu-series] [--keep-builds]
|
||||
#
|
||||
# Example:
|
||||
# ./create-and-upload.sh ../dms dms questing
|
||||
# ./create-and-upload.sh ../danklinux/dgop danklinux questing --keep-builds
|
||||
|
||||
set -e
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
# Parse arguments
|
||||
KEEP_BUILDS=false
|
||||
ARGS=()
|
||||
for arg in "$@"; do
|
||||
if [ "$arg" = "--keep-builds" ]; then
|
||||
KEEP_BUILDS=true
|
||||
else
|
||||
ARGS+=("$arg")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#ARGS[@]} -lt 2 ]; then
|
||||
error "Usage: $0 <package-dir> <ppa-name> [ubuntu-series] [--keep-builds]"
|
||||
echo
|
||||
echo "Arguments:"
|
||||
echo " package-dir : Path to package directory (e.g., ../dms, ../danklinux/dgop)"
|
||||
echo " ppa-name : PPA name (danklinux, dms, dms-git)"
|
||||
echo " ubuntu-series : Ubuntu series (optional, default: questing)"
|
||||
echo " Supported: questing (25.10) and newer only"
|
||||
echo " Note: Requires Qt 6.6+ (quickshell requirement)"
|
||||
echo " --keep-builds : Keep build artifacts after upload (optional)"
|
||||
echo
|
||||
echo "Examples:"
|
||||
echo " $0 ../dms dms questing"
|
||||
echo " $0 ../danklinux/dgop danklinux questing --keep-builds"
|
||||
echo " $0 ../dms-git dms-git # Defaults to questing"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PACKAGE_DIR="${ARGS[0]}"
|
||||
PPA_NAME="${ARGS[1]}"
|
||||
UBUNTU_SERIES="${ARGS[2]:-questing}"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
BUILD_SCRIPT="$SCRIPT_DIR/ppa-build.sh"
|
||||
UPLOAD_SCRIPT="$SCRIPT_DIR/ppa-dput.sh"
|
||||
|
||||
# Validate scripts exist
|
||||
if [ ! -f "$BUILD_SCRIPT" ]; then
|
||||
error "Build script not found: $BUILD_SCRIPT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get absolute path
|
||||
PACKAGE_DIR=$(cd "$PACKAGE_DIR" && pwd)
|
||||
PACKAGE_NAME=$(basename "$PACKAGE_DIR")
|
||||
PARENT_DIR=$(dirname "$PACKAGE_DIR")
|
||||
|
||||
info "Building and uploading: $PACKAGE_NAME"
|
||||
info "Package directory: $PACKAGE_DIR"
|
||||
info "PPA: ppa:avengemedia/$PPA_NAME"
|
||||
info "Ubuntu series: $UBUNTU_SERIES"
|
||||
echo
|
||||
|
||||
# Step 1: Build source package
|
||||
info "Step 1: Building source package..."
|
||||
if ! "$BUILD_SCRIPT" "$PACKAGE_DIR" "$UBUNTU_SERIES"; then
|
||||
error "Build failed!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Find the changes file
|
||||
CHANGES_FILE=$(find "$PARENT_DIR" -maxdepth 1 -name "${PACKAGE_NAME}_*_source.changes" -type f | sort -V | tail -1)
|
||||
|
||||
if [ -z "$CHANGES_FILE" ]; then
|
||||
error "Changes file not found in $PARENT_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
info "Found changes file: $CHANGES_FILE"
|
||||
echo
|
||||
|
||||
# Step 2: Upload to PPA
|
||||
info "Step 2: Uploading to PPA..."
|
||||
|
||||
# Check if using lftp (for all PPAs) or dput
|
||||
if [ "$PPA_NAME" = "danklinux" ] || [ "$PPA_NAME" = "dms" ] || [ "$PPA_NAME" = "dms-git" ]; then
|
||||
warn "Using lftp for upload"
|
||||
|
||||
# Extract version from changes file
|
||||
VERSION=$(grep "^Version:" "$CHANGES_FILE" | awk '{print $2}')
|
||||
SOURCE_NAME=$(grep "^Source:" "$CHANGES_FILE" | awk '{print $2}')
|
||||
|
||||
# Find all files to upload
|
||||
BUILD_DIR=$(dirname "$CHANGES_FILE")
|
||||
CHANGES_BASENAME=$(basename "$CHANGES_FILE")
|
||||
DSC_FILE="${CHANGES_BASENAME/_source.changes/.dsc}"
|
||||
TARBALL="${CHANGES_BASENAME/_source.changes/.tar.xz}"
|
||||
BUILDINFO="${CHANGES_BASENAME/_source.changes/_source.buildinfo}"
|
||||
|
||||
# Check all files exist
|
||||
MISSING_FILES=()
|
||||
[ ! -f "$BUILD_DIR/$DSC_FILE" ] && MISSING_FILES+=("$DSC_FILE")
|
||||
[ ! -f "$BUILD_DIR/$TARBALL" ] && MISSING_FILES+=("$TARBALL")
|
||||
[ ! -f "$BUILD_DIR/$BUILDINFO" ] && MISSING_FILES+=("$BUILDINFO")
|
||||
|
||||
if [ ${#MISSING_FILES[@]} -gt 0 ]; then
|
||||
error "Missing required files:"
|
||||
for file in "${MISSING_FILES[@]}"; do
|
||||
error " - $file"
|
||||
done
|
||||
exit 1
|
||||
fi
|
||||
|
||||
info "Uploading files:"
|
||||
info " - $CHANGES_BASENAME"
|
||||
info " - $DSC_FILE"
|
||||
info " - $TARBALL"
|
||||
info " - $BUILDINFO"
|
||||
echo
|
||||
|
||||
# lftp build dir change
|
||||
LFTP_SCRIPT=$(mktemp)
|
||||
cat > "$LFTP_SCRIPT" <<EOF
|
||||
cd ~avengemedia/ubuntu/$PPA_NAME/
|
||||
lcd $BUILD_DIR
|
||||
mput $CHANGES_BASENAME
|
||||
mput $DSC_FILE
|
||||
mput $TARBALL
|
||||
mput $BUILDINFO
|
||||
bye
|
||||
EOF
|
||||
|
||||
if lftp -d ftp://anonymous:@ppa.launchpad.net < "$LFTP_SCRIPT"; then
|
||||
success "Upload successful!"
|
||||
rm -f "$LFTP_SCRIPT"
|
||||
else
|
||||
error "Upload failed!"
|
||||
rm -f "$LFTP_SCRIPT"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
# Use dput for other PPAs
|
||||
if [ ! -f "$UPLOAD_SCRIPT" ]; then
|
||||
error "Upload script not found: $UPLOAD_SCRIPT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Auto-confirm upload (pipe 'y' to the confirmation prompt)
|
||||
if ! echo "y" | "$UPLOAD_SCRIPT" "$CHANGES_FILE" "$PPA_NAME"; then
|
||||
error "Upload failed!"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo
|
||||
success "Package uploaded successfully!"
|
||||
info "Monitor build progress at:"
|
||||
echo " https://launchpad.net/~avengemedia/+archive/ubuntu/$PPA_NAME/+packages"
|
||||
echo
|
||||
|
||||
# Step 3: Cleanup (unless --keep-builds is specified)
|
||||
if [ "$KEEP_BUILDS" = "false" ]; then
|
||||
info "Step 3: Cleaning up build artifacts..."
|
||||
|
||||
# Find all build artifacts in parent directory
|
||||
ARTIFACTS=(
|
||||
"${PACKAGE_NAME}_*.dsc"
|
||||
"${PACKAGE_NAME}_*.tar.xz"
|
||||
"${PACKAGE_NAME}_*.tar.gz"
|
||||
"${PACKAGE_NAME}_*_source.changes"
|
||||
"${PACKAGE_NAME}_*_source.buildinfo"
|
||||
"${PACKAGE_NAME}_*_source.build"
|
||||
)
|
||||
|
||||
REMOVED=0
|
||||
for pattern in "${ARTIFACTS[@]}"; do
|
||||
for file in "$PARENT_DIR"/$pattern; do
|
||||
if [ -f "$file" ]; then
|
||||
rm -f "$file"
|
||||
REMOVED=$((REMOVED + 1))
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
# Clean up downloaded binaries in package directory
|
||||
case "$PACKAGE_NAME" in
|
||||
danksearch)
|
||||
if [ -f "$PACKAGE_DIR/dsearch-amd64" ]; then
|
||||
rm -f "$PACKAGE_DIR/dsearch-amd64"
|
||||
REMOVED=$((REMOVED + 1))
|
||||
fi
|
||||
if [ -f "$PACKAGE_DIR/dsearch-arm64" ]; then
|
||||
rm -f "$PACKAGE_DIR/dsearch-arm64"
|
||||
REMOVED=$((REMOVED + 1))
|
||||
fi
|
||||
;;
|
||||
dms)
|
||||
# Remove downloaded binaries and source
|
||||
if [ -f "$PACKAGE_DIR/dms-distropkg-amd64.gz" ]; then
|
||||
rm -f "$PACKAGE_DIR/dms-distropkg-amd64.gz"
|
||||
REMOVED=$((REMOVED + 1))
|
||||
fi
|
||||
if [ -f "$PACKAGE_DIR/dms-source.tar.gz" ]; then
|
||||
rm -f "$PACKAGE_DIR/dms-source.tar.gz"
|
||||
REMOVED=$((REMOVED + 1))
|
||||
fi
|
||||
;;
|
||||
dms-git)
|
||||
# Remove git source directory binary
|
||||
if [ -d "$PACKAGE_DIR/dms-git-repo" ]; then
|
||||
rm -rf "$PACKAGE_DIR/dms-git-repo"
|
||||
REMOVED=$((REMOVED + 1))
|
||||
fi
|
||||
;;
|
||||
dms-greeter)
|
||||
# Remove downloaded source
|
||||
if [ -f "$PACKAGE_DIR/dms-greeter-source.tar.gz" ]; then
|
||||
rm -f "$PACKAGE_DIR/dms-greeter-source.tar.gz"
|
||||
REMOVED=$((REMOVED + 1))
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ $REMOVED -gt 0 ]; then
|
||||
success "Removed $REMOVED build artifact(s)"
|
||||
else
|
||||
info "No build artifacts to clean up"
|
||||
fi
|
||||
else
|
||||
info "Keeping build artifacts (--keep-builds specified)"
|
||||
info "Build artifacts in: $PARENT_DIR"
|
||||
fi
|
||||
|
||||
echo
|
||||
success "Done!"
|
||||
|
||||
5
distro/ubuntu/danklinux/danksearch/debian/changelog
Normal file
5
distro/ubuntu/danklinux/danksearch/debian/changelog
Normal file
@@ -0,0 +1,5 @@
|
||||
danksearch (0.0.7ppa3) questing; urgency=medium
|
||||
|
||||
* Rebuild for packaging fixes (ppa3)
|
||||
|
||||
-- Avenge Media <AvengeMedia.US@gmail.com> Fri, 21 Nov 2025 14:19:58 -0500
|
||||
24
distro/ubuntu/danklinux/danksearch/debian/control
Normal file
24
distro/ubuntu/danklinux/danksearch/debian/control
Normal file
@@ -0,0 +1,24 @@
|
||||
Source: danksearch
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Maintainer: Avenge Media <AvengeMedia.US@gmail.com>
|
||||
Build-Depends: debhelper-compat (= 13)
|
||||
Standards-Version: 4.6.2
|
||||
Homepage: https://github.com/AvengeMedia/danksearch
|
||||
Vcs-Browser: https://github.com/AvengeMedia/danksearch
|
||||
Vcs-Git: https://github.com/AvengeMedia/danksearch.git
|
||||
|
||||
Package: danksearch
|
||||
Architecture: amd64 arm64
|
||||
Depends: ${misc:Depends}
|
||||
Description: Fast file search utility for DMS
|
||||
DankSearch is a fast file search utility designed for DankMaterialShell.
|
||||
It provides efficient file and content search capabilities with minimal
|
||||
dependencies. This package contains the pre-built binary from the official
|
||||
GitHub release.
|
||||
.
|
||||
Features include:
|
||||
- Fast file searching
|
||||
- Lightweight and efficient
|
||||
- Designed for DMS integration
|
||||
- Minimal resource usage
|
||||
24
distro/ubuntu/danklinux/danksearch/debian/copyright
Normal file
24
distro/ubuntu/danklinux/danksearch/debian/copyright
Normal file
@@ -0,0 +1,24 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: danksearch
|
||||
Upstream-Contact: Avenge Media LLC <AvengeMedia.US@gmail.com>
|
||||
Source: https://github.com/AvengeMedia/danksearch
|
||||
|
||||
Files: *
|
||||
Copyright: 2025 Avenge Media LLC
|
||||
License: GPL-3.0-only
|
||||
|
||||
License: GPL-3.0-only
|
||||
This package is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 3 as
|
||||
published by the Free Software Foundation.
|
||||
.
|
||||
This package is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
.
|
||||
On Debian systems, the complete text of the GNU General
|
||||
Public License version 3 can be found in "/usr/share/common-licenses/GPL-3".
|
||||
1
distro/ubuntu/danklinux/danksearch/debian/files
Normal file
1
distro/ubuntu/danklinux/danksearch/debian/files
Normal file
@@ -0,0 +1 @@
|
||||
danksearch_0.0.7ppa3_source.buildinfo utils optional
|
||||
33
distro/ubuntu/danklinux/danksearch/debian/rules
Executable file
33
distro/ubuntu/danklinux/danksearch/debian/rules
Executable file
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
export DH_VERBOSE = 1
|
||||
|
||||
# Detect architecture for selecting correct binary
|
||||
DEB_HOST_ARCH := $(shell dpkg-architecture -qDEB_HOST_ARCH)
|
||||
|
||||
# Map Debian arch to binary filename
|
||||
ifeq ($(DEB_HOST_ARCH),amd64)
|
||||
BINARY_FILE := dsearch-amd64
|
||||
else ifeq ($(DEB_HOST_ARCH),arm64)
|
||||
BINARY_FILE := dsearch-arm64
|
||||
else
|
||||
$(error Unsupported architecture: $(DEB_HOST_ARCH))
|
||||
endif
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
override_dh_auto_build:
|
||||
# Binary is already included in source package (native format)
|
||||
# Downloaded by build-source.sh before upload
|
||||
# Just verify it exists and is executable
|
||||
test -f $(BINARY_FILE) || (echo "ERROR: $(BINARY_FILE) not found!" && exit 1)
|
||||
chmod +x $(BINARY_FILE)
|
||||
|
||||
override_dh_auto_install:
|
||||
# Install binary as danksearch
|
||||
install -Dm755 $(BINARY_FILE) debian/danksearch/usr/bin/danksearch
|
||||
|
||||
override_dh_auto_clean:
|
||||
# Don't delete binaries - they're part of the source package (native format)
|
||||
dh_auto_clean
|
||||
1
distro/ubuntu/danklinux/danksearch/debian/source/format
Normal file
1
distro/ubuntu/danklinux/danksearch/debian/source/format
Normal file
@@ -0,0 +1 @@
|
||||
3.0 (native)
|
||||
BIN
distro/ubuntu/danklinux/danksearch/dsearch-amd64
Executable file
BIN
distro/ubuntu/danklinux/danksearch/dsearch-amd64
Executable file
Binary file not shown.
BIN
distro/ubuntu/danklinux/danksearch/dsearch-arm64
Executable file
BIN
distro/ubuntu/danklinux/danksearch/dsearch-arm64
Executable file
Binary file not shown.
9
distro/ubuntu/danklinux/dgop/debian/changelog
Normal file
9
distro/ubuntu/danklinux/dgop/debian/changelog
Normal file
@@ -0,0 +1,9 @@
|
||||
dgop (0.1.11ppa2) questing; urgency=medium
|
||||
|
||||
* Rebuild for Questing (25.10) - Ubuntu 25.10+ only
|
||||
* Stateless CPU/GPU monitoring tool
|
||||
* Support for NVIDIA and AMD GPUs
|
||||
* JSON output for integration
|
||||
* Pre-built binary package for amd64 and arm64
|
||||
|
||||
-- Avenge Media <AvengeMedia.US@gmail.com> Sun, 16 Nov 2025 22:50:00 -0500
|
||||
27
distro/ubuntu/danklinux/dgop/debian/control
Normal file
27
distro/ubuntu/danklinux/dgop/debian/control
Normal file
@@ -0,0 +1,27 @@
|
||||
Source: dgop
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Maintainer: Avenge Media <AvengeMedia.US@gmail.com>
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
wget,
|
||||
gzip
|
||||
Standards-Version: 4.6.2
|
||||
Homepage: https://github.com/AvengeMedia/dgop
|
||||
Vcs-Browser: https://github.com/AvengeMedia/dgop
|
||||
Vcs-Git: https://github.com/AvengeMedia/dgop.git
|
||||
|
||||
Package: dgop
|
||||
Architecture: amd64 arm64
|
||||
Depends: ${misc:Depends}
|
||||
Description: Stateless CPU/GPU monitor for DankMaterialShell
|
||||
DGOP is a stateless system monitoring tool that provides CPU, GPU,
|
||||
memory, and network statistics. Designed for integration with
|
||||
DankMaterialShell but can be used standalone.
|
||||
.
|
||||
Features:
|
||||
- CPU usage monitoring
|
||||
- GPU usage and temperature (NVIDIA, AMD)
|
||||
- Memory and swap statistics
|
||||
- Network traffic monitoring
|
||||
- Zero-state design (no background processes)
|
||||
- JSON output for easy integration
|
||||
27
distro/ubuntu/danklinux/dgop/debian/copyright
Normal file
27
distro/ubuntu/danklinux/dgop/debian/copyright
Normal file
@@ -0,0 +1,27 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: dgop
|
||||
Upstream-Contact: Avenge Media LLC <AvengeMedia.US@gmail.com>
|
||||
Source: https://github.com/AvengeMedia/dgop
|
||||
|
||||
Files: *
|
||||
Copyright: 2025 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.
|
||||
1
distro/ubuntu/danklinux/dgop/debian/files
Normal file
1
distro/ubuntu/danklinux/dgop/debian/files
Normal file
@@ -0,0 +1 @@
|
||||
dgop_0.1.11ppa2_source.buildinfo utils optional
|
||||
38
distro/ubuntu/danklinux/dgop/debian/rules
Executable file
38
distro/ubuntu/danklinux/dgop/debian/rules
Executable file
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
export DH_VERBOSE = 1
|
||||
|
||||
# Extract version from debian/changelog
|
||||
DEB_VERSION := $(shell dpkg-parsechangelog -S Version)
|
||||
# Get upstream version (strip -1ppa1 suffix)
|
||||
UPSTREAM_VERSION := $(shell echo $(DEB_VERSION) | sed 's/-[^-]*$$//')
|
||||
|
||||
# Detect architecture for downloading correct binary
|
||||
DEB_HOST_ARCH := $(shell dpkg-architecture -qDEB_HOST_ARCH)
|
||||
|
||||
# Map Debian arch to GitHub release arch names
|
||||
ifeq ($(DEB_HOST_ARCH),amd64)
|
||||
GITHUB_ARCH := amd64
|
||||
else ifeq ($(DEB_HOST_ARCH),arm64)
|
||||
GITHUB_ARCH := arm64
|
||||
else
|
||||
$(error Unsupported architecture: $(DEB_HOST_ARCH))
|
||||
endif
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
override_dh_auto_build:
|
||||
# Binary is already included in source package (native format)
|
||||
# Just verify it exists and is executable
|
||||
test -f dgop || (echo "ERROR: dgop binary not found!" && exit 1)
|
||||
chmod +x dgop
|
||||
|
||||
override_dh_auto_install:
|
||||
# Install binary
|
||||
install -Dm755 dgop debian/dgop/usr/bin/dgop
|
||||
|
||||
override_dh_auto_clean:
|
||||
# Don't delete dgop binary - it's part of the source package (native format)
|
||||
rm -f dgop.gz
|
||||
dh_auto_clean
|
||||
1
distro/ubuntu/danklinux/dgop/debian/source/format
Normal file
1
distro/ubuntu/danklinux/dgop/debian/source/format
Normal file
@@ -0,0 +1 @@
|
||||
3.0 (native)
|
||||
5
distro/ubuntu/dms-git/debian/changelog
Normal file
5
distro/ubuntu/dms-git/debian/changelog
Normal file
@@ -0,0 +1,5 @@
|
||||
dms-git (0.6.2+git2169.f7f1bbbdppa10) questing; urgency=medium
|
||||
|
||||
* Git snapshot (commit 2169: f7f1bbbd)
|
||||
|
||||
-- Avenge Media <AvengeMedia.US@gmail.com> Wed, 26 Nov 2025 21:43:12 -0500
|
||||
51
distro/ubuntu/dms-git/debian/control
Normal file
51
distro/ubuntu/dms-git/debian/control
Normal file
@@ -0,0 +1,51 @@
|
||||
Source: dms-git
|
||||
Section: x11
|
||||
Priority: optional
|
||||
Maintainer: Avenge Media <AvengeMedia.US@gmail.com>
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
golang-go (>= 2:1.22~)
|
||||
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-git
|
||||
Architecture: amd64
|
||||
Depends: ${misc:Depends},
|
||||
quickshell-git | quickshell,
|
||||
accountsservice,
|
||||
cava,
|
||||
cliphist,
|
||||
danksearch,
|
||||
dgop,
|
||||
matugen,
|
||||
qml6-module-qtcore,
|
||||
qml6-module-qtmultimedia,
|
||||
qml6-module-qtqml,
|
||||
qml6-module-qtquick,
|
||||
qml6-module-qtquick-controls,
|
||||
qml6-module-qtquick-dialogs,
|
||||
qml6-module-qtquick-effects,
|
||||
qml6-module-qtquick-layouts,
|
||||
qml6-module-qtquick-templates,
|
||||
qml6-module-qtquick-window,
|
||||
qt6ct,
|
||||
wl-clipboard
|
||||
Provides: dms
|
||||
Conflicts: dms
|
||||
Replaces: dms
|
||||
Description: DankMaterialShell - Modern Wayland Desktop Shell (git nightly)
|
||||
DMS (DankMaterialShell) is a feature-rich desktop shell built on
|
||||
Quickshell, providing a modern and customizable user interface for
|
||||
Wayland compositors like niri, hyprland, and sway.
|
||||
.
|
||||
This is the nightly/git version built from the latest master branch.
|
||||
.
|
||||
Features include:
|
||||
- Material Design inspired UI
|
||||
- Customizable themes and appearance
|
||||
- Built-in application launcher
|
||||
- System tray and notifications
|
||||
- Network and Bluetooth management
|
||||
- Audio controls
|
||||
- Systemd integration
|
||||
27
distro/ubuntu/dms-git/debian/copyright
Normal file
27
distro/ubuntu/dms-git/debian/copyright
Normal file
@@ -0,0 +1,27 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: dms
|
||||
Upstream-Contact: Avenge Media LLC <AvengeMedia.US@gmail.com>
|
||||
Source: https://github.com/AvengeMedia/DankMaterialShell
|
||||
|
||||
Files: *
|
||||
Copyright: 2025 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.
|
||||
1
distro/ubuntu/dms-git/debian/files
Normal file
1
distro/ubuntu/dms-git/debian/files
Normal file
@@ -0,0 +1 @@
|
||||
dms-git_0.6.2+git2169.f7f1bbbdppa10_source.buildinfo x11 optional
|
||||
65
distro/ubuntu/dms-git/debian/rules
Executable file
65
distro/ubuntu/dms-git/debian/rules
Executable file
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
export DH_VERBOSE = 1
|
||||
|
||||
# Get git commit date for version
|
||||
GIT_DATE := $(shell date +%Y%m%d)
|
||||
GIT_COMMIT := HEAD
|
||||
|
||||
# Go needs writable directories for its caches
|
||||
export HOME := $(CURDIR)/debian/tmp-home
|
||||
export GOCACHE := $(CURDIR)/debian/tmp-home/go-cache
|
||||
export GOMODCACHE := $(CURDIR)/debian/tmp-home/go-mod
|
||||
|
||||
# Launchpad has no network access, so use local toolchain only
|
||||
export GOTOOLCHAIN := local
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
override_dh_auto_build:
|
||||
# Create Go cache directories (sbuild sets HOME to non-existent path)
|
||||
mkdir -p $(HOME) $(GOCACHE) $(GOMODCACHE)
|
||||
|
||||
# Git source is already included in source package (cloned by ppa-build.sh)
|
||||
# Launchpad build environment has no internet access
|
||||
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)
|
||||
sed -i 's/^go 1\.24\.[0-9]*/go 1.24/' dms-git-repo/core/go.mod
|
||||
|
||||
# Build dms-cli from source
|
||||
@if [ -f dms-git-repo/.dms-version ]; then \
|
||||
. dms-git-repo/.dms-version; \
|
||||
echo "Building with VERSION=$$VERSION COMMIT=$$COMMIT"; \
|
||||
cd dms-git-repo/core && $(MAKE) GOFLAGS="-mod=vendor" dist ARCH=amd64 VERSION="$$VERSION" COMMIT="$$COMMIT"; \
|
||||
else \
|
||||
echo "Warning: .dms-version not found, building without version info"; \
|
||||
cd dms-git-repo/core && $(MAKE) GOFLAGS="-mod=vendor" dist ARCH=amd64; \
|
||||
fi
|
||||
cp dms-git-repo/core/bin/dms-linux-amd64 dms
|
||||
chmod +x dms
|
||||
|
||||
override_dh_auto_install:
|
||||
# Install binary
|
||||
install -Dm755 dms debian/dms-git/usr/bin/dms
|
||||
|
||||
# Install QML files from git clone
|
||||
mkdir -p debian/dms-git/usr/share/quickshell/dms
|
||||
cp -r dms-git-repo/* debian/dms-git/usr/share/quickshell/dms/
|
||||
|
||||
# Remove unnecessary directories
|
||||
rm -rf debian/dms-git/usr/share/quickshell/dms/core
|
||||
rm -rf debian/dms-git/usr/share/quickshell/dms/distro
|
||||
|
||||
# Install systemd user service
|
||||
install -Dm644 dms-git-repo/quickshell/assets/systemd/dms.service \
|
||||
debian/dms-git/usr/lib/systemd/user/dms.service
|
||||
|
||||
override_dh_auto_clean:
|
||||
# Don't delete dms-git-repo directory - it's part of the source package (native format)
|
||||
# Clean up build artifacts
|
||||
rm -f dms
|
||||
rm -rf dms-git-repo/core/bin
|
||||
rm -rf debian/tmp-home
|
||||
dh_auto_clean
|
||||
1
distro/ubuntu/dms-git/debian/source/format
Normal file
1
distro/ubuntu/dms-git/debian/source/format
Normal file
@@ -0,0 +1 @@
|
||||
3.0 (native)
|
||||
1
distro/ubuntu/dms-git/debian/source/include-binaries
Normal file
1
distro/ubuntu/dms-git/debian/source/include-binaries
Normal file
@@ -0,0 +1 @@
|
||||
# No pre-built binaries needed - dms-cli is built from source
|
||||
3
distro/ubuntu/dms-git/debian/source/options
Normal file
3
distro/ubuntu/dms-git/debian/source/options
Normal file
@@ -0,0 +1,3 @@
|
||||
# Include files that are normally excluded by .gitignore
|
||||
# These are needed for the build process on Launchpad (which has no internet access)
|
||||
tar-ignore = !dms-git-repo
|
||||
5
distro/ubuntu/dms-greeter/debian/changelog
Normal file
5
distro/ubuntu/dms-greeter/debian/changelog
Normal file
@@ -0,0 +1,5 @@
|
||||
dms-greeter (0.6.2ppa3) questing; urgency=medium
|
||||
|
||||
* Rebuild for packaging fixes (ppa3)
|
||||
|
||||
-- Avenge Media <AvengeMedia.US@gmail.com> Thu, 27 Nov 2025 23:38:37 -0500
|
||||
23
distro/ubuntu/dms-greeter/debian/control
Normal file
23
distro/ubuntu/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: all
|
||||
Depends: ${misc:Depends},
|
||||
greetd,
|
||||
quickshell-git | quickshell
|
||||
Recommends: 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/ubuntu/dms-greeter/debian/copyright
Normal file
27
distro/ubuntu/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: 2025 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.
|
||||
1
distro/ubuntu/dms-greeter/debian/files
Normal file
1
distro/ubuntu/dms-greeter/debian/files
Normal file
@@ -0,0 +1 @@
|
||||
dms-greeter_0.6.2ppa3_source.buildinfo x11 optional
|
||||
108
distro/ubuntu/dms-greeter/debian/postinst
Executable file
108
distro/ubuntu/dms-greeter/debian/postinst
Executable 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/ubuntu/dms-greeter/debian/postrm
Executable file
14
distro/ubuntu/dms-greeter/debian/postrm
Executable 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
|
||||
55
distro/ubuntu/dms-greeter/debian/rules
Executable file
55
distro/ubuntu/dms-greeter/debian/rules
Executable file
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
export DH_VERBOSE = 1
|
||||
|
||||
# Extract version from debian/changelog
|
||||
DEB_VERSION := $(shell dpkg-parsechangelog -S Version)
|
||||
# Get upstream version (strip -1ppa1 suffix)
|
||||
UPSTREAM_VERSION := $(shell echo $(DEB_VERSION) | sed 's/-[^-]*$$//')
|
||||
BASE_VERSION := $(shell echo $(UPSTREAM_VERSION) | sed 's/ppa[0-9]*$$//' | sed 's/+git.*//')
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
override_dh_auto_build:
|
||||
# All files are included in source package
|
||||
test -f dms-greeter-source.tar.gz || (echo "ERROR: dms-greeter-source.tar.gz not found!" && exit 1)
|
||||
|
||||
# Extract source tarball
|
||||
tar -xzf dms-greeter-source.tar.gz
|
||||
# Find the extracted directory
|
||||
SOURCE_DIR=$$(find . -maxdepth 1 -type d -name "DankMaterialShell*" | head -n1); \
|
||||
if [ -n "$$SOURCE_DIR" ]; then \
|
||||
ln -sf $$SOURCE_DIR DankMaterialShell-$(BASE_VERSION); \
|
||||
fi
|
||||
|
||||
override_dh_auto_install:
|
||||
# Install greeter files to shared data location
|
||||
mkdir -p debian/dms-greeter/usr/share/quickshell/dms-greeter
|
||||
cp -r DankMaterialShell-$(BASE_VERSION)/quickshell/* debian/dms-greeter/usr/share/quickshell/dms-greeter/
|
||||
|
||||
# Install launcher script
|
||||
install -Dm755 DankMaterialShell-$(BASE_VERSION)/quickshell/Modules/Greetd/assets/dms-greeter \
|
||||
debian/dms-greeter/usr/bin/dms-greeter
|
||||
|
||||
# Install documentation
|
||||
install -Dm644 DankMaterialShell-$(BASE_VERSION)/quickshell/Modules/Greetd/README.md \
|
||||
debian/dms-greeter/usr/share/doc/dms-greeter/README.md
|
||||
|
||||
# Install LICENSE file
|
||||
install -Dm644 DankMaterialShell-$(BASE_VERSION)/LICENSE \
|
||||
debian/dms-greeter/usr/share/doc/dms-greeter/LICENSE
|
||||
|
||||
# Create cache directory structure (will be created by postinst)
|
||||
mkdir -p debian/dms-greeter/var/cache/dms-greeter
|
||||
|
||||
# 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 DankMaterialShell-*
|
||||
dh_auto_clean
|
||||
1
distro/ubuntu/dms-greeter/debian/source/format
Normal file
1
distro/ubuntu/dms-greeter/debian/source/format
Normal file
@@ -0,0 +1 @@
|
||||
3.0 (native)
|
||||
1
distro/ubuntu/dms-greeter/debian/source/include-binaries
Normal file
1
distro/ubuntu/dms-greeter/debian/source/include-binaries
Normal file
@@ -0,0 +1 @@
|
||||
dms-greeter-source.tar.gz
|
||||
3
distro/ubuntu/dms-greeter/debian/source/options
Normal file
3
distro/ubuntu/dms-greeter/debian/source/options
Normal file
@@ -0,0 +1,3 @@
|
||||
# Include files that are normally excluded by .gitignore
|
||||
# These are needed for the build process on Launchpad (which has no internet access)
|
||||
tar-ignore = !dms-greeter-source.tar.gz
|
||||
5
distro/ubuntu/dms/debian/changelog
Normal file
5
distro/ubuntu/dms/debian/changelog
Normal file
@@ -0,0 +1,5 @@
|
||||
dms (0.6.2ppa3) questing; urgency=medium
|
||||
|
||||
* Rebuild for packaging fixes (ppa3)
|
||||
|
||||
-- Avenge Media <AvengeMedia.US@gmail.com> Sun, 23 Nov 2025 00:40:41 -0500
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user