mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-06 05:25:41 -05:00
Compare commits
154 Commits
v0.6.2
...
cbd1fd908c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbd1fd908c | ||
|
|
b2cf20f3d8 | ||
|
|
915f1a5036 | ||
|
|
a55ec6416c | ||
|
|
b1834b1958 | ||
|
|
1beeb9fb55 | ||
|
|
18d86354ec | ||
|
|
6297b0679c | ||
|
|
d62ef635a7 | ||
|
|
c53836040f | ||
|
|
0b638bf85f | ||
|
|
7f6a71b964 | ||
|
|
1b4363a54a | ||
|
|
16d168c970 | ||
|
|
4606d7960e | ||
|
|
4eee126d26 | ||
|
|
dde426658f | ||
|
|
f6874fbcad | ||
|
|
621d4e4d92 | ||
|
|
76062231fd | ||
|
|
261f55fea5 | ||
|
|
202cf4bcc9 | ||
|
|
b7572f727f | ||
|
|
50ab346d58 | ||
|
|
b11b375848 | ||
|
|
e6c3ae9397 | ||
|
|
df663aceb9 | ||
|
|
db7e597f67 | ||
|
|
1d3fe81ff7 | ||
|
|
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 |
@@ -1,8 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
# DISABLED for now
|
||||
exit 0
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
@@ -10,18 +6,61 @@ REPO_ROOT="$(cd "$HOOK_DIR/.." && pwd)"
|
||||
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
if [[ -z "${POEDITOR_API_TOKEN:-}" ]] || [[ -z "${POEDITOR_PROJECT_ID:-}" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
# =============================================================================
|
||||
# Go CI checks (when core/ files are staged)
|
||||
# =============================================================================
|
||||
STAGED_CORE_FILES=$(git diff --cached --name-only --diff-filter=ACMR | grep '^core/' || true)
|
||||
|
||||
if ! command -v python3 &>/dev/null; then
|
||||
exit 0
|
||||
fi
|
||||
if [[ -n "$STAGED_CORE_FILES" ]]; then
|
||||
echo "Go files staged in core/, running CI checks..."
|
||||
cd "$REPO_ROOT/core"
|
||||
|
||||
if ! python3 scripts/i18nsync.py check &>/dev/null; then
|
||||
echo "Translations out of sync"
|
||||
echo "run python3 scripts/i18nsync.py sync"
|
||||
# Format check
|
||||
echo " Checking gofmt..."
|
||||
UNFORMATTED=$(gofmt -s -l . 2>/dev/null || true)
|
||||
if [[ -n "$UNFORMATTED" ]]; then
|
||||
echo "The following files are not formatted:"
|
||||
echo "$UNFORMATTED"
|
||||
echo ""
|
||||
echo "Run: cd core && gofmt -s -w ."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# golangci-lint
|
||||
if command -v golangci-lint &>/dev/null; then
|
||||
echo " Running golangci-lint..."
|
||||
golangci-lint run ./...
|
||||
else
|
||||
echo " Warning: golangci-lint not installed, skipping lint"
|
||||
echo " Install: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest"
|
||||
fi
|
||||
|
||||
# Tests
|
||||
echo " Running tests..."
|
||||
go test ./... > /dev/null
|
||||
|
||||
# Build checks
|
||||
echo " Building..."
|
||||
mkdir -p bin
|
||||
go build -buildvcs=false -o bin/dms ./cmd/dms
|
||||
go build -buildvcs=false -o bin/dms-distro -tags distro_binary ./cmd/dms
|
||||
go build -buildvcs=false -o bin/dankinstall ./cmd/dankinstall
|
||||
|
||||
echo "All Go CI checks passed!"
|
||||
cd "$REPO_ROOT"
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# i18n sync check (DISABLED for now)
|
||||
# =============================================================================
|
||||
# if [[ -n "${POEDITOR_API_TOKEN:-}" ]] && [[ -n "${POEDITOR_PROJECT_ID:-}" ]]; then
|
||||
# if command -v python3 &>/dev/null; then
|
||||
# if ! python3 scripts/i18nsync.py check &>/dev/null; then
|
||||
# echo "Translations out of sync"
|
||||
# echo "Run: python3 scripts/i18nsync.py sync"
|
||||
# exit 1
|
||||
# fi
|
||||
# fi
|
||||
# fi
|
||||
|
||||
exit 0
|
||||
|
||||
20
.github/workflows/go-ci.yml
vendored
20
.github/workflows/go-ci.yml
vendored
@@ -7,9 +7,18 @@ on:
|
||||
paths:
|
||||
- 'core/**'
|
||||
- '.github/workflows/go-ci.yml'
|
||||
pull_request:
|
||||
branches: [master, main]
|
||||
paths:
|
||||
- 'core/**'
|
||||
- '.github/workflows/go-ci.yml'
|
||||
|
||||
concurrency:
|
||||
group: go-ci-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
lint-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
@@ -32,11 +41,20 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Run golangci-lint
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: latest
|
||||
working-directory: core
|
||||
|
||||
- name: Test
|
||||
run: go test -v ./...
|
||||
|
||||
- name: Build dms
|
||||
run: go build -v ./cmd/dms
|
||||
|
||||
- name: Build dms (distropkg)
|
||||
run: go build -v -tags distro_binary ./cmd/dms
|
||||
|
||||
- name: Build dankinstall
|
||||
run: go build -v ./cmd/dankinstall
|
||||
|
||||
90
.github/workflows/release.yml
vendored
90
.github/workflows/release.yml
vendored
@@ -132,38 +132,40 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-core
|
||||
steps:
|
||||
- name: Create GitHub App token
|
||||
id: app_token
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app-id: ${{ secrets.APP_ID }}
|
||||
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
token: ${{ steps.app_token.outputs.token }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Update VERSION
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app_token.outputs.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config user.name "dms-ci[bot]"
|
||||
git config user.email "dms-ci[bot]@users.noreply.github.com"
|
||||
|
||||
version="${GITHUB_REF#refs/tags/}"
|
||||
version_no_v="${version#v}"
|
||||
echo "Updating to version: $version"
|
||||
|
||||
# Update VERSION file in quickshell/
|
||||
echo "${version}" > quickshell/VERSION
|
||||
|
||||
git add quickshell/VERSION
|
||||
|
||||
if ! git diff --cached --quiet; then
|
||||
git commit -m "chore: bump version to $version"
|
||||
git push origin HEAD:master || git push origin HEAD:main
|
||||
echo "Pushed version updates to master"
|
||||
else
|
||||
echo "No version changes needed"
|
||||
git pull --rebase origin master
|
||||
git push https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git HEAD:master
|
||||
fi
|
||||
|
||||
# Force-push the tag to point to the commit with updated VERSION
|
||||
git tag -f "${version}"
|
||||
git push -f origin "${version}"
|
||||
git push -f https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git "${version}"
|
||||
|
||||
release:
|
||||
runs-on: ubuntu-24.04
|
||||
@@ -386,6 +388,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
|
||||
66
.github/workflows/update-vendor-hash.yml
vendored
66
.github/workflows/update-vendor-hash.yml
vendored
@@ -1,6 +1,7 @@
|
||||
name: Update Vendor Hash
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- "core/go.mod"
|
||||
@@ -8,14 +9,25 @@ on:
|
||||
branches:
|
||||
- master
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
update-vendor-hash:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Create GitHub App token
|
||||
id: app_token
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app-id: ${{ secrets.APP_ID }}
|
||||
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ steps.app_token.outputs.token }}
|
||||
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v31
|
||||
@@ -23,68 +35,32 @@ jobs:
|
||||
- name: Update vendorHash in flake.nix
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Try to build and capture the expected hash from error message
|
||||
echo "Attempting nix build to get new vendorHash..."
|
||||
|
||||
if output=$(nix build .#dmsCli 2>&1); then
|
||||
echo "Build succeeded, no hash update needed"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Extract the expected hash from the error message
|
||||
new_hash=$(echo "$output" | grep -oP "got:\s+\K\S+" | head -n1)
|
||||
|
||||
if [ -z "$new_hash" ]; then
|
||||
echo "Could not extract new vendorHash from build output"
|
||||
echo "Build output:"
|
||||
echo "$output"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "New vendorHash: $new_hash"
|
||||
|
||||
# Get current hash from flake.nix
|
||||
[ -n "$new_hash" ] || { echo "Could not extract new vendorHash"; echo "$output"; exit 1; }
|
||||
current_hash=$(grep -oP 'vendorHash = "\K[^"]+' flake.nix)
|
||||
echo "Current vendorHash: $current_hash"
|
||||
|
||||
if [ "$current_hash" = "$new_hash" ]; then
|
||||
echo "vendorHash is already up to date"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Update the hash in flake.nix
|
||||
[ "$current_hash" = "$new_hash" ] && { echo "vendorHash already up to date"; exit 0; }
|
||||
sed -i "s|vendorHash = \"$current_hash\"|vendorHash = \"$new_hash\"|" flake.nix
|
||||
|
||||
# Verify the build works with the new hash
|
||||
echo "Verifying build with new vendorHash..."
|
||||
nix build .#dmsCli
|
||||
|
||||
echo "vendorHash updated successfully!"
|
||||
|
||||
- name: Commit and push vendorHash update
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app_token.outputs.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if ! git diff --quiet flake.nix; then
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
git config user.name "dms-ci[bot]"
|
||||
git config user.email "dms-ci[bot]@users.noreply.github.com"
|
||||
git add flake.nix
|
||||
git commit -m "nix: update vendorHash for go.mod changes"
|
||||
|
||||
for attempt in 1 2 3; do
|
||||
if git push; then
|
||||
echo "Successfully pushed vendorHash update"
|
||||
exit 0
|
||||
fi
|
||||
echo "Push attempt $attempt failed, pulling and retrying..."
|
||||
git pull --rebase
|
||||
sleep $((attempt*2))
|
||||
done
|
||||
|
||||
echo "Failed to push after retries" >&2
|
||||
exit 1
|
||||
git commit -m "nix: update vendorHash for go.mod changes" || exit 0
|
||||
git pull --rebase origin master
|
||||
git push https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git HEAD:master
|
||||
else
|
||||
echo "No changes to flake.nix"
|
||||
fi
|
||||
|
||||
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,50 @@
|
||||
|
||||
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.
|
||||
## Setup
|
||||
|
||||
We need some consistent style, so this at least gives the same formatter that Qt Creator uses.
|
||||
Enable pre-commit hooks to catch CI failures before pushing:
|
||||
|
||||
You can configure it to format on save in vscode by configuring the "custom local formatters" extension then adding this to settings json.
|
||||
|
||||
```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
|
||||
},
|
||||
```bash
|
||||
git config core.hooksPath .githooks
|
||||
```
|
||||
|
||||
Sometimes it just breaks code though. Like turning `"_\""` into `"_""`, so you may not want to do formatOnSave.
|
||||
## VSCode Setup
|
||||
|
||||
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.
|
||||
|
||||
### 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
|
||||
{
|
||||
"qt-qml.doNotAskForQmllsDownload": true,
|
||||
"qt-qml.qmlls.customExePath": "/usr/lib/qt6/bin/qmlls"
|
||||
}
|
||||
```
|
||||
|
||||
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>
|
||||
|
||||
|
||||
77
core/.golangci.yml
Normal file
77
core/.golangci.yml
Normal file
@@ -0,0 +1,77 @@
|
||||
linters-settings:
|
||||
errcheck:
|
||||
check-type-assertions: false
|
||||
check-blank: false
|
||||
exclude-functions:
|
||||
# Cleanup/destroy operations
|
||||
- (io.Closer).Close
|
||||
- (*os.File).Close
|
||||
- (net.Conn).Close
|
||||
- (*net.Conn).Close
|
||||
# Signal handling
|
||||
- (*os.Process).Signal
|
||||
- (*os.Process).Kill
|
||||
# DBus cleanup
|
||||
- (*github.com/godbus/dbus/v5.Conn).RemoveMatchSignal
|
||||
- (*github.com/godbus/dbus/v5.Conn).RemoveSignal
|
||||
# Encoding to network connections (if conn is bad, nothing we can do)
|
||||
- (*encoding/json.Encoder).Encode
|
||||
- (net.Conn).Write
|
||||
# Command execution where failure is expected/ignored
|
||||
- (*os/exec.Cmd).Run
|
||||
- (*os/exec.Cmd).Start
|
||||
# Flush operations
|
||||
- (*bufio.Writer).Flush
|
||||
# Scanning user input
|
||||
- fmt.Scanln
|
||||
- fmt.Scanf
|
||||
# Parse operations where default value is acceptable
|
||||
- fmt.Sscanf
|
||||
# Flag operations
|
||||
- (*github.com/spf13/pflag.FlagSet).MarkHidden
|
||||
# Binary encoding to buffer (can't fail for basic types)
|
||||
- binary.Write
|
||||
# File operations in cleanup paths
|
||||
- os.Rename
|
||||
- os.Remove
|
||||
- (*os.File).WriteString
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- errcheck
|
||||
- govet
|
||||
- unused
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
- gosimple
|
||||
# Exclude cleanup/teardown method calls from errcheck
|
||||
- linters:
|
||||
- errcheck
|
||||
text: "Error return value of `.+\\.(Destroy|Release|Stop|Close|Roundtrip|Store)` is not checked"
|
||||
# Exclude internal state update methods that are best-effort
|
||||
- linters:
|
||||
- errcheck
|
||||
text: "Error return value of `[mb]\\.\\w*(update|initialize|recreate|acquire|enumerate|list|List|Ensure|refresh|Lock)\\w*` is not checked"
|
||||
# Exclude SetMode on wayland power controls (best-effort)
|
||||
- linters:
|
||||
- errcheck
|
||||
text: "Error return value of `.+\\.SetMode` is not checked"
|
||||
# Exclude AddMatchSignal which is best-effort monitoring setup
|
||||
- linters:
|
||||
- errcheck
|
||||
text: "Error return value of `.+\\.AddMatchSignal` is not checked"
|
||||
# Exclude wayland pkg from errcheck and ineffassign (generated code patterns)
|
||||
- linters:
|
||||
- errcheck
|
||||
- ineffassign
|
||||
path: pkg/go-wayland/
|
||||
# Exclude proto pkg from ineffassign (generated protocol code)
|
||||
- linters:
|
||||
- ineffassign
|
||||
path: internal/proto/
|
||||
# binary.Write to bytes.Buffer can't fail
|
||||
- linters:
|
||||
- errcheck
|
||||
text: "Error return value of `binary\\.Write` is not checked"
|
||||
@@ -28,21 +28,31 @@ 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
|
||||
interfaces:
|
||||
Backend:
|
||||
github.com/AvengeMedia/danklinux/internal/server/cups:
|
||||
github.com/AvengeMedia/DankMaterialShell/core/internal/server/cups:
|
||||
config:
|
||||
dir: "internal/mocks/cups"
|
||||
outpkg: mocks_cups
|
||||
interfaces:
|
||||
CUPSClientInterface:
|
||||
PkHelper:
|
||||
config:
|
||||
dir: "internal/mocks/cups_pkhelper"
|
||||
outpkg: mocks_cups_pkhelper
|
||||
github.com/AvengeMedia/DankMaterialShell/core/internal/server/evdev:
|
||||
config:
|
||||
dir: "internal/mocks/evdev"
|
||||
outpkg: mocks_evdev
|
||||
interfaces:
|
||||
EvdevDevice:
|
||||
github.com/AvengeMedia/DankMaterialShell/core/internal/version:
|
||||
config:
|
||||
dir: "internal/mocks/version"
|
||||
outpkg: mocks_version
|
||||
interfaces:
|
||||
VersionFetcher:
|
||||
|
||||
@@ -72,6 +72,13 @@ sudo make install # Install to /usr/local/bin/dms
|
||||
|
||||
## Development
|
||||
|
||||
**Setup pre-commit hooks:**
|
||||
```bash
|
||||
git config core.hooksPath .githooks
|
||||
```
|
||||
|
||||
This runs gofmt, golangci-lint, tests, and builds before each commit when `core/` files are staged.
|
||||
|
||||
**Regenerating Wayland Protocol Bindings:**
|
||||
```bash
|
||||
go install github.com/rajveermalviya/go-wayland/cmd/go-wayland-scanner@latest
|
||||
|
||||
@@ -140,6 +140,7 @@ func runVersion(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
|
||||
func startDebugServer() error {
|
||||
server.CLIVersion = Version
|
||||
return server.Start(true)
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -77,8 +77,6 @@ func runUpdate() {
|
||||
switch config.Family {
|
||||
case distros.FamilyArch:
|
||||
updateErr = updateArchLinux()
|
||||
case distros.FamilyNix:
|
||||
updateErr = updateNixOS()
|
||||
case distros.FamilySUSE:
|
||||
updateErr = updateOtherDistros()
|
||||
default:
|
||||
@@ -152,27 +150,6 @@ func updateArchLinux() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateNixOS() error {
|
||||
fmt.Println("This will update DankMaterialShell using nix profile.")
|
||||
if !confirmUpdate() {
|
||||
return errdefs.ErrUpdateCancelled
|
||||
}
|
||||
|
||||
fmt.Println("\nRunning: nix profile upgrade github:AvengeMedia/DankMaterialShell")
|
||||
updateCmd := exec.Command("nix", "profile", "upgrade", "github:AvengeMedia/DankMaterialShell")
|
||||
updateCmd.Stdout = os.Stdout
|
||||
updateCmd.Stderr = os.Stderr
|
||||
err := updateCmd.Run()
|
||||
if err != nil {
|
||||
fmt.Printf("Error: Failed to update using nix profile: %v\n", err)
|
||||
fmt.Println("Falling back to git-based update method...")
|
||||
return updateOtherDistros()
|
||||
}
|
||||
|
||||
fmt.Println("dms successfully updated")
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateOtherDistros() error {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/greeter"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
var greeterCmd = &cobra.Command{
|
||||
@@ -217,6 +219,191 @@ 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", cases.Title(language.English).String(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")
|
||||
}
|
||||
|
||||
switch state.EnabledState {
|
||||
case "disabled", "masked", "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")
|
||||
case "enabled", "enabled-runtime":
|
||||
fmt.Println(" ✓ greetd is already enabled")
|
||||
default:
|
||||
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 +419,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 +530,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
|
||||
}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/config"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/dms"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
@@ -76,14 +74,7 @@ func findConfig(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
}
|
||||
func runInteractiveMode(cmd *cobra.Command, args []string) {
|
||||
detector, err := dms.NewDetector()
|
||||
if err != nil && !errors.Is(err, &distros.UnsupportedDistributionError{}) {
|
||||
log.Fatalf("Error initializing DMS detector: %v", err)
|
||||
} else if errors.Is(err, &distros.UnsupportedDistributionError{}) {
|
||||
log.Error("Interactive mode is not supported on this distribution.")
|
||||
log.Info("Please run 'dms --help' for available commands.")
|
||||
os.Exit(1)
|
||||
}
|
||||
detector, _ := dms.NewDetector()
|
||||
|
||||
if !detector.IsDMSInstalled() {
|
||||
log.Error("DankMaterialShell (DMS) is not detected as installed on this system.")
|
||||
|
||||
@@ -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) {
|
||||
@@ -374,6 +383,7 @@ func runShellDaemon(session bool) {
|
||||
errChan <- fmt.Errorf("server panic: %v", r)
|
||||
}
|
||||
}()
|
||||
server.CLIVersion = Version
|
||||
if err := server.Start(false); err != nil {
|
||||
errChan <- fmt.Errorf("server error: %w", err)
|
||||
}
|
||||
@@ -387,6 +397,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
|
||||
}
|
||||
|
||||
18
core/go.mod
18
core/go.mod
@@ -9,24 +9,24 @@ require (
|
||||
github.com/charmbracelet/lipgloss v1.1.0
|
||||
github.com/charmbracelet/log v0.4.2
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/godbus/dbus/v5 v5.1.0
|
||||
github.com/godbus/dbus/v5 v5.2.0
|
||||
github.com/holoplot/go-evdev v0.0.0-20250804134636-ab1d56a1fe83
|
||||
github.com/spf13/cobra v1.10.1
|
||||
github.com/stretchr/testify v1.11.1
|
||||
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6
|
||||
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.5.0 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.6.0 // indirect
|
||||
github.com/clipperhouse/stringish v0.1.1 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.6.0 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/go-git/gcfg/v2 v2.0.2 // indirect
|
||||
github.com/go-git/go-billy/v6 v6.0.0-20251111123000-fb5ff8f3f0b0 // indirect
|
||||
github.com/go-git/go-billy/v6 v6.0.0-20251126203821-7f9c95185ee0 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.1 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/kevinburke/ssh_config v1.4.0 // indirect
|
||||
@@ -34,7 +34,7 @@ require (
|
||||
github.com/pjbgf/sha1cd v0.5.0 // indirect
|
||||
github.com/sergi/go-diff v1.4.0 // indirect
|
||||
github.com/stretchr/objx v0.5.3 // indirect
|
||||
golang.org/x/crypto v0.44.0 // indirect
|
||||
golang.org/x/crypto v0.45.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
)
|
||||
|
||||
@@ -43,12 +43,12 @@ require (
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.3.3 // indirect
|
||||
github.com/charmbracelet/harmonica v0.2.0 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.11.0 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.11.2 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.14 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/go-git/go-git/v6 v6.0.0-20251112161705-8cc3e21f07a9
|
||||
github.com/go-git/go-git/v6 v6.0.0-20251128074608-48f817f57805
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
@@ -63,6 +63,6 @@ require (
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
golang.org/x/sys v0.38.0
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
golang.org/x/text v0.31.0
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
16
core/go.sum
16
core/go.sum
@@ -26,12 +26,16 @@ github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsy
|
||||
github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
|
||||
github.com/charmbracelet/x/ansi v0.11.0 h1:uuIVK7GIplwX6UBIz8S2TF8nkr7xRlygSsBRjSJqIvA=
|
||||
github.com/charmbracelet/x/ansi v0.11.0/go.mod h1:uQt8bOrq/xgXjlGcFMc8U2WYbnxyjrKhnvTQluvfCaE=
|
||||
github.com/charmbracelet/x/ansi v0.11.2 h1:XAG3FSjiVtFvgEgGrNBkCNNYrsucAt8c6bfxHyROLLs=
|
||||
github.com/charmbracelet/x/ansi v0.11.2/go.mod h1:9tY2bzX5SiJCU0iWyskjBeI2BRQfvPqI+J760Mjf+Rg=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.14/go.mod h1:P447lJl49ywBbil/KjCk2HexGh4tEY9LH0/1QrZZ9rA=
|
||||
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
||||
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
||||
github.com/clipperhouse/displaywidth v0.5.0 h1:AIG5vQaSL2EKqzt0M9JMnvNxOCRTKUc4vUnLWGgP89I=
|
||||
github.com/clipperhouse/displaywidth v0.5.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
|
||||
github.com/clipperhouse/displaywidth v0.6.0 h1:k32vueaksef9WIKCNcoqRNyKbyvkvkysNYnAWz2fN4s=
|
||||
github.com/clipperhouse/displaywidth v0.6.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
|
||||
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
|
||||
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
|
||||
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
|
||||
@@ -41,6 +45,8 @@ github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZ
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is=
|
||||
github.com/cyphar/filepath-securejoin v0.6.0/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
|
||||
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -58,14 +64,20 @@ github.com/go-git/gcfg/v2 v2.0.2 h1:MY5SIIfTGGEMhdA7d7JePuVVxtKL7Hp+ApGDJAJ7dpo=
|
||||
github.com/go-git/gcfg/v2 v2.0.2/go.mod h1:/lv2NsxvhepuMrldsFilrgct6pxzpGdSRC13ydTLSLs=
|
||||
github.com/go-git/go-billy/v6 v6.0.0-20251111123000-fb5ff8f3f0b0 h1:EC9n6hr6yKDoVJ6g7Ko523LbbceJfR0ohbOp809Fyf4=
|
||||
github.com/go-git/go-billy/v6 v6.0.0-20251111123000-fb5ff8f3f0b0/go.mod h1:E3VhlS+AKkrq6ZNn1axE2/nDRJ87l1FJk9r5HT2vPX0=
|
||||
github.com/go-git/go-billy/v6 v6.0.0-20251126203821-7f9c95185ee0 h1:eY5aB2GXiVdgTueBcqsBt53WuJTRZAuCdIS/86Pcq5c=
|
||||
github.com/go-git/go-billy/v6 v6.0.0-20251126203821-7f9c95185ee0/go.mod h1:0NjwVNrwtVFZBReAp5OoGklGJIgJFEbVyHneAr4lc8k=
|
||||
github.com/go-git/go-git-fixtures/v5 v5.1.1 h1:OH8i1ojV9bWfr0ZfasfpgtUXQHQyVS8HXik/V1C099w=
|
||||
github.com/go-git/go-git-fixtures/v5 v5.1.1/go.mod h1:Altk43lx3b1ks+dVoAG2300o5WWUnktvfY3VI6bcaXU=
|
||||
github.com/go-git/go-git/v6 v6.0.0-20251112161705-8cc3e21f07a9 h1:SOFrnF9LCssC6q6Rb0084Bzg2aBYbe8QXv9xKGXmt/w=
|
||||
github.com/go-git/go-git/v6 v6.0.0-20251112161705-8cc3e21f07a9/go.mod h1:0wtvm/JfPC9RFVEAP3ks0ec5h64/qmZkTTUE3pjz7Hc=
|
||||
github.com/go-git/go-git/v6 v6.0.0-20251128074608-48f817f57805 h1:jxQ3BzYeErNRvlI/4+0mpwqMzvB4g97U+ksfgvrUEbY=
|
||||
github.com/go-git/go-git/v6 v6.0.0-20251128074608-48f817f57805/go.mod h1:dIwT3uWK1ooHInyVnK2JS5VfQ3peVGYaw2QPqX7uFvs=
|
||||
github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE=
|
||||
github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.2.0 h1:3WexO+U+yg9T70v9FdHr9kCxYlazaAXUhx2VMkbfax8=
|
||||
github.com/godbus/dbus/v5 v5.2.0/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
@@ -127,8 +139,12 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
|
||||
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0=
|
||||
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
|
||||
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 h1:DHNhtq3sNNzrvduZZIiFyXWOL9IWaDPHqTnLJp+rCBY=
|
||||
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
||||
@@ -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"; }
|
||||
|
||||
BIN
core/internal/config/embedded/testpage.pdf
Normal file
BIN
core/internal/config/embedded/testpage.pdf
Normal file
Binary file not shown.
6
core/internal/config/testpage.go
Normal file
6
core/internal/config/testpage.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package config
|
||||
|
||||
import _ "embed"
|
||||
|
||||
//go:embed embedded/testpage.pdf
|
||||
var TestPage string
|
||||
@@ -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{
|
||||
|
||||
@@ -1,466 +0,0 @@
|
||||
package distros
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Register("nixos", "#7EBAE4", FamilyNix, func(config DistroConfig, logChan chan<- string) Distribution {
|
||||
return NewNixOSDistribution(config, logChan)
|
||||
})
|
||||
}
|
||||
|
||||
type NixOSDistribution struct {
|
||||
*BaseDistribution
|
||||
config DistroConfig
|
||||
}
|
||||
|
||||
func NewNixOSDistribution(config DistroConfig, logChan chan<- string) *NixOSDistribution {
|
||||
base := NewBaseDistribution(logChan)
|
||||
return &NixOSDistribution{
|
||||
BaseDistribution: base,
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *NixOSDistribution) GetID() string {
|
||||
return n.config.ID
|
||||
}
|
||||
|
||||
func (n *NixOSDistribution) GetColorHex() string {
|
||||
return n.config.ColorHex
|
||||
}
|
||||
|
||||
func (n *NixOSDistribution) GetFamily() DistroFamily {
|
||||
return n.config.Family
|
||||
}
|
||||
|
||||
func (n *NixOSDistribution) GetPackageManager() PackageManagerType {
|
||||
return PackageManagerNix
|
||||
}
|
||||
|
||||
func (n *NixOSDistribution) DetectDependencies(ctx context.Context, wm deps.WindowManager) ([]deps.Dependency, error) {
|
||||
return n.DetectDependenciesWithTerminal(ctx, wm, deps.TerminalGhostty)
|
||||
}
|
||||
|
||||
func (n *NixOSDistribution) DetectDependenciesWithTerminal(ctx context.Context, wm deps.WindowManager, terminal deps.Terminal) ([]deps.Dependency, error) {
|
||||
var dependencies []deps.Dependency
|
||||
|
||||
// DMS at the top (shell is prominent)
|
||||
dependencies = append(dependencies, n.detectDMS())
|
||||
|
||||
// Terminal with choice support
|
||||
dependencies = append(dependencies, n.detectSpecificTerminal(terminal))
|
||||
|
||||
// Common detections using base methods
|
||||
dependencies = append(dependencies, n.detectGit())
|
||||
dependencies = append(dependencies, n.detectWindowManager(wm))
|
||||
dependencies = append(dependencies, n.detectQuickshell())
|
||||
dependencies = append(dependencies, n.detectXDGPortal())
|
||||
dependencies = append(dependencies, n.detectPolkitAgent())
|
||||
dependencies = append(dependencies, n.detectAccountsService())
|
||||
|
||||
// Hyprland-specific tools
|
||||
if wm == deps.WindowManagerHyprland {
|
||||
dependencies = append(dependencies, n.detectHyprlandTools()...)
|
||||
}
|
||||
|
||||
// Niri-specific tools
|
||||
if wm == deps.WindowManagerNiri {
|
||||
dependencies = append(dependencies, n.detectXwaylandSatellite())
|
||||
}
|
||||
|
||||
// Base detections (common across distros)
|
||||
dependencies = append(dependencies, n.detectMatugen())
|
||||
dependencies = append(dependencies, n.detectDgop())
|
||||
dependencies = append(dependencies, n.detectHyprpicker())
|
||||
dependencies = append(dependencies, n.detectClipboardTools()...)
|
||||
|
||||
return dependencies, nil
|
||||
}
|
||||
|
||||
func (n *NixOSDistribution) detectDMS() deps.Dependency {
|
||||
status := deps.StatusMissing
|
||||
|
||||
// For NixOS, check if quickshell can find the dms config
|
||||
cmd := exec.Command("qs", "-c", "dms", "--list")
|
||||
if err := cmd.Run(); err == nil {
|
||||
status = deps.StatusInstalled
|
||||
} else if n.packageInstalled("DankMaterialShell") {
|
||||
// Fallback: check if flake is in profile
|
||||
status = deps.StatusInstalled
|
||||
}
|
||||
|
||||
return deps.Dependency{
|
||||
Name: "dms (DankMaterialShell)",
|
||||
Status: status,
|
||||
Description: "Desktop Management System configuration (installed as flake)",
|
||||
Required: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *NixOSDistribution) detectXDGPortal() deps.Dependency {
|
||||
status := deps.StatusMissing
|
||||
if n.packageInstalled("xdg-desktop-portal-gtk") {
|
||||
status = deps.StatusInstalled
|
||||
}
|
||||
|
||||
return deps.Dependency{
|
||||
Name: "xdg-desktop-portal-gtk",
|
||||
Status: status,
|
||||
Description: "Desktop integration portal for GTK",
|
||||
Required: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *NixOSDistribution) detectWindowManager(wm deps.WindowManager) deps.Dependency {
|
||||
switch wm {
|
||||
case deps.WindowManagerHyprland:
|
||||
status := deps.StatusMissing
|
||||
description := "Dynamic tiling Wayland compositor"
|
||||
if n.commandExists("hyprland") || n.commandExists("Hyprland") {
|
||||
status = deps.StatusInstalled
|
||||
} else {
|
||||
description = "Install system-wide: programs.hyprland.enable = true; in configuration.nix"
|
||||
}
|
||||
return deps.Dependency{
|
||||
Name: "hyprland",
|
||||
Status: status,
|
||||
Description: description,
|
||||
Required: true,
|
||||
}
|
||||
case deps.WindowManagerNiri:
|
||||
status := deps.StatusMissing
|
||||
description := "Scrollable-tiling Wayland compositor"
|
||||
if n.commandExists("niri") {
|
||||
status = deps.StatusInstalled
|
||||
} else {
|
||||
description = "Install system-wide: environment.systemPackages = [ pkgs.niri ]; in configuration.nix"
|
||||
}
|
||||
return deps.Dependency{
|
||||
Name: "niri",
|
||||
Status: status,
|
||||
Description: description,
|
||||
Required: true,
|
||||
}
|
||||
default:
|
||||
return deps.Dependency{
|
||||
Name: "unknown-wm",
|
||||
Status: deps.StatusMissing,
|
||||
Description: "Unknown window manager",
|
||||
Required: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *NixOSDistribution) detectHyprlandTools() []deps.Dependency {
|
||||
var dependencies []deps.Dependency
|
||||
|
||||
tools := []struct {
|
||||
name string
|
||||
description string
|
||||
}{
|
||||
{"grim", "Screenshot utility for Wayland"},
|
||||
{"slurp", "Region selection utility for Wayland"},
|
||||
{"hyprctl", "Hyprland control utility (comes with system Hyprland)"},
|
||||
{"hyprpicker", "Color picker for Hyprland"},
|
||||
{"grimblast", "Screenshot script for Hyprland"},
|
||||
{"jq", "JSON processor"},
|
||||
}
|
||||
|
||||
for _, tool := range tools {
|
||||
status := deps.StatusMissing
|
||||
|
||||
// Special handling for hyprctl - it comes with system hyprland
|
||||
if tool.name == "hyprctl" {
|
||||
if n.commandExists("hyprctl") {
|
||||
status = deps.StatusInstalled
|
||||
}
|
||||
} else {
|
||||
if n.commandExists(tool.name) {
|
||||
status = deps.StatusInstalled
|
||||
}
|
||||
}
|
||||
|
||||
dependencies = append(dependencies, deps.Dependency{
|
||||
Name: tool.name,
|
||||
Status: status,
|
||||
Description: tool.description,
|
||||
Required: true,
|
||||
})
|
||||
}
|
||||
|
||||
return dependencies
|
||||
}
|
||||
|
||||
func (n *NixOSDistribution) detectXwaylandSatellite() deps.Dependency {
|
||||
status := deps.StatusMissing
|
||||
if n.commandExists("xwayland-satellite") {
|
||||
status = deps.StatusInstalled
|
||||
}
|
||||
|
||||
return deps.Dependency{
|
||||
Name: "xwayland-satellite",
|
||||
Status: status,
|
||||
Description: "Xwayland support",
|
||||
Required: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *NixOSDistribution) detectPolkitAgent() deps.Dependency {
|
||||
status := deps.StatusMissing
|
||||
if n.packageInstalled("mate-polkit") {
|
||||
status = deps.StatusInstalled
|
||||
}
|
||||
|
||||
return deps.Dependency{
|
||||
Name: "mate-polkit",
|
||||
Status: status,
|
||||
Description: "PolicyKit authentication agent",
|
||||
Required: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *NixOSDistribution) detectAccountsService() deps.Dependency {
|
||||
status := deps.StatusMissing
|
||||
if n.packageInstalled("accountsservice") {
|
||||
status = deps.StatusInstalled
|
||||
}
|
||||
|
||||
return deps.Dependency{
|
||||
Name: "accountsservice",
|
||||
Status: status,
|
||||
Description: "D-Bus interface for user account query and manipulation",
|
||||
Required: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *NixOSDistribution) packageInstalled(pkg string) bool {
|
||||
cmd := exec.Command("nix", "profile", "list")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(string(output), pkg)
|
||||
}
|
||||
|
||||
func (n *NixOSDistribution) GetPackageMapping(wm deps.WindowManager) map[string]PackageMapping {
|
||||
packages := map[string]PackageMapping{
|
||||
"git": {Name: "nixpkgs#git", Repository: RepoTypeSystem},
|
||||
"quickshell": {Name: "github:quickshell-mirror/quickshell", Repository: RepoTypeFlake},
|
||||
"matugen": {Name: "github:InioX/matugen", Repository: RepoTypeFlake},
|
||||
"dgop": {Name: "github:AvengeMedia/dgop", Repository: RepoTypeFlake},
|
||||
"dms (DankMaterialShell)": {Name: "github:AvengeMedia/DankMaterialShell", Repository: RepoTypeFlake},
|
||||
"ghostty": {Name: "nixpkgs#ghostty", Repository: RepoTypeSystem},
|
||||
"alacritty": {Name: "nixpkgs#alacritty", Repository: RepoTypeSystem},
|
||||
"cliphist": {Name: "nixpkgs#cliphist", Repository: RepoTypeSystem},
|
||||
"wl-clipboard": {Name: "nixpkgs#wl-clipboard", Repository: RepoTypeSystem},
|
||||
"xdg-desktop-portal-gtk": {Name: "nixpkgs#xdg-desktop-portal-gtk", Repository: RepoTypeSystem},
|
||||
"mate-polkit": {Name: "nixpkgs#mate.mate-polkit", Repository: RepoTypeSystem},
|
||||
"accountsservice": {Name: "nixpkgs#accountsservice", Repository: RepoTypeSystem},
|
||||
"hyprpicker": {Name: "nixpkgs#hyprpicker", Repository: RepoTypeSystem},
|
||||
}
|
||||
|
||||
// Note: Window managers (hyprland/niri) should be installed system-wide on NixOS
|
||||
// We only install the tools here
|
||||
switch wm {
|
||||
case deps.WindowManagerHyprland:
|
||||
// Skip hyprland itself - should be installed system-wide
|
||||
packages["grim"] = PackageMapping{Name: "nixpkgs#grim", Repository: RepoTypeSystem}
|
||||
packages["slurp"] = PackageMapping{Name: "nixpkgs#slurp", Repository: RepoTypeSystem}
|
||||
packages["grimblast"] = PackageMapping{Name: "github:hyprwm/contrib#grimblast", Repository: RepoTypeFlake}
|
||||
packages["jq"] = PackageMapping{Name: "nixpkgs#jq", Repository: RepoTypeSystem}
|
||||
case deps.WindowManagerNiri:
|
||||
// Skip niri itself - should be installed system-wide
|
||||
packages["xwayland-satellite"] = PackageMapping{Name: "nixpkgs#xwayland-satellite", Repository: RepoTypeFlake}
|
||||
}
|
||||
|
||||
return packages
|
||||
}
|
||||
|
||||
func (n *NixOSDistribution) InstallPrerequisites(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhasePrerequisites,
|
||||
Progress: 0.10,
|
||||
Step: "NixOS prerequisites ready",
|
||||
IsComplete: false,
|
||||
LogOutput: "NixOS package manager is ready to use",
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NixOSDistribution) InstallPackages(ctx context.Context, dependencies []deps.Dependency, wm deps.WindowManager, sudoPassword string, reinstallFlags map[string]bool, disabledFlags map[string]bool, skipGlobalUseFlags bool, progressChan chan<- InstallProgressMsg) error {
|
||||
// Phase 1: Check Prerequisites
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhasePrerequisites,
|
||||
Progress: 0.05,
|
||||
Step: "Checking system prerequisites...",
|
||||
IsComplete: false,
|
||||
LogOutput: "Starting prerequisite check...",
|
||||
}
|
||||
|
||||
if err := n.InstallPrerequisites(ctx, sudoPassword, progressChan); err != nil {
|
||||
return fmt.Errorf("failed to install prerequisites: %w", err)
|
||||
}
|
||||
|
||||
nixpkgsPkgs, flakePkgs, _ := n.categorizePackages(dependencies, wm, reinstallFlags, disabledFlags)
|
||||
|
||||
// Phase 2: Nixpkgs Packages
|
||||
if len(nixpkgsPkgs) > 0 {
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseSystemPackages,
|
||||
Progress: 0.35,
|
||||
Step: fmt.Sprintf("Installing %d packages from nixpkgs...", len(nixpkgsPkgs)),
|
||||
IsComplete: false,
|
||||
LogOutput: fmt.Sprintf("Installing nixpkgs packages: %s", strings.Join(nixpkgsPkgs, ", ")),
|
||||
}
|
||||
if err := n.installNixpkgsPackages(ctx, nixpkgsPkgs, progressChan); err != nil {
|
||||
return fmt.Errorf("failed to install nixpkgs packages: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 3: Flake Packages
|
||||
if len(flakePkgs) > 0 {
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseAURPackages,
|
||||
Progress: 0.65,
|
||||
Step: fmt.Sprintf("Installing %d packages from flakes...", len(flakePkgs)),
|
||||
IsComplete: false,
|
||||
LogOutput: fmt.Sprintf("Installing flake packages: %s", strings.Join(flakePkgs, ", ")),
|
||||
}
|
||||
if err := n.installFlakePackages(ctx, flakePkgs, progressChan); err != nil {
|
||||
return fmt.Errorf("failed to install flake packages: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 4: Configuration
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseConfiguration,
|
||||
Progress: 0.90,
|
||||
Step: "Configuring system...",
|
||||
IsComplete: false,
|
||||
LogOutput: "Starting post-installation configuration...",
|
||||
}
|
||||
if err := n.postInstallConfig(progressChan); err != nil {
|
||||
return fmt.Errorf("failed to configure system: %w", err)
|
||||
}
|
||||
|
||||
// Phase 5: Complete
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseComplete,
|
||||
Progress: 1.0,
|
||||
Step: "Installation complete!",
|
||||
IsComplete: true,
|
||||
LogOutput: "All packages installed and configured successfully",
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NixOSDistribution) categorizePackages(dependencies []deps.Dependency, wm deps.WindowManager, reinstallFlags map[string]bool, disabledFlags map[string]bool) ([]string, []string, map[string]deps.PackageVariant) {
|
||||
nixpkgsPkgs := []string{}
|
||||
flakePkgs := []string{}
|
||||
|
||||
variantMap := make(map[string]deps.PackageVariant)
|
||||
for _, dep := range dependencies {
|
||||
variantMap[dep.Name] = dep.Variant
|
||||
}
|
||||
|
||||
packageMap := n.GetPackageMapping(wm)
|
||||
|
||||
for _, dep := range dependencies {
|
||||
if disabledFlags[dep.Name] {
|
||||
continue
|
||||
}
|
||||
|
||||
if dep.Status == deps.StatusInstalled && !reinstallFlags[dep.Name] {
|
||||
continue
|
||||
}
|
||||
|
||||
pkgInfo, exists := packageMap[dep.Name]
|
||||
if !exists {
|
||||
n.log(fmt.Sprintf("Warning: No package mapping found for %s", dep.Name))
|
||||
continue
|
||||
}
|
||||
|
||||
switch pkgInfo.Repository {
|
||||
case RepoTypeSystem:
|
||||
nixpkgsPkgs = append(nixpkgsPkgs, pkgInfo.Name)
|
||||
case RepoTypeFlake:
|
||||
flakePkgs = append(flakePkgs, pkgInfo.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return nixpkgsPkgs, flakePkgs, variantMap
|
||||
}
|
||||
|
||||
func (n *NixOSDistribution) installNixpkgsPackages(ctx context.Context, packages []string, progressChan chan<- InstallProgressMsg) error {
|
||||
if len(packages) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
n.log(fmt.Sprintf("Installing nixpkgs packages: %s", strings.Join(packages, ", ")))
|
||||
|
||||
args := []string{"profile", "install"}
|
||||
args = append(args, packages...)
|
||||
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseSystemPackages,
|
||||
Progress: 0.40,
|
||||
Step: "Installing nixpkgs packages...",
|
||||
IsComplete: false,
|
||||
CommandInfo: fmt.Sprintf("nix %s", strings.Join(args, " ")),
|
||||
}
|
||||
|
||||
cmd := exec.CommandContext(ctx, "nix", args...)
|
||||
return n.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.40, 0.60)
|
||||
}
|
||||
|
||||
func (n *NixOSDistribution) installFlakePackages(ctx context.Context, packages []string, progressChan chan<- InstallProgressMsg) error {
|
||||
if len(packages) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
n.log(fmt.Sprintf("Installing flake packages: %s", strings.Join(packages, ", ")))
|
||||
|
||||
baseProgress := 0.65
|
||||
progressStep := 0.20 / float64(len(packages))
|
||||
|
||||
for i, pkg := range packages {
|
||||
currentProgress := baseProgress + (float64(i) * progressStep)
|
||||
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseAURPackages,
|
||||
Progress: currentProgress,
|
||||
Step: fmt.Sprintf("Installing flake package %s (%d/%d)...", pkg, i+1, len(packages)),
|
||||
IsComplete: false,
|
||||
CommandInfo: fmt.Sprintf("nix profile install %s", pkg),
|
||||
}
|
||||
|
||||
cmd := exec.CommandContext(ctx, "nix", "profile", "install", pkg)
|
||||
if err := n.runWithProgress(cmd, progressChan, PhaseAURPackages, currentProgress, currentProgress+progressStep); err != nil {
|
||||
return fmt.Errorf("failed to install flake package %s: %w", pkg, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NixOSDistribution) postInstallConfig(progressChan chan<- InstallProgressMsg) error {
|
||||
// For NixOS, DMS is installed as a flake package, so we skip both the binary installation and git clone
|
||||
// The flake installation handles both the binary and config files correctly
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseConfiguration,
|
||||
Progress: 0.95,
|
||||
Step: "NixOS configuration complete",
|
||||
IsComplete: false,
|
||||
LogOutput: "DMS installed via flake - binary and config handled by Nix",
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -122,15 +122,8 @@ func (d *Detector) GetInstalledComponents() []DependencyInfo {
|
||||
return []DependencyInfo{}
|
||||
}
|
||||
|
||||
isNixOS := d.isNixOS()
|
||||
|
||||
var components []DependencyInfo
|
||||
for _, dep := range dependencies {
|
||||
// On NixOS, filter out the window managers themselves but keep their components
|
||||
if isNixOS && (dep.Name == "hyprland" || dep.Name == "niri") {
|
||||
continue
|
||||
}
|
||||
|
||||
components = append(components, DependencyInfo{
|
||||
Name: dep.Name,
|
||||
Status: dep.Status,
|
||||
@@ -142,23 +135,6 @@ func (d *Detector) GetInstalledComponents() []DependencyInfo {
|
||||
return components
|
||||
}
|
||||
|
||||
func (d *Detector) isNixOS() bool {
|
||||
_, err := os.Stat("/etc/nixos")
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
// Alternative check
|
||||
if _, err := os.Stat("/nix/store"); err == nil {
|
||||
// Also check for nixos-version command
|
||||
if d.commandExists("nixos-version") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
type DependencyInfo struct {
|
||||
Name string
|
||||
Status deps.DependencyStatus
|
||||
|
||||
@@ -367,7 +367,7 @@ func SyncDMSConfigs(dmsPath string, logFunc func(string), sudoPassword string) e
|
||||
}
|
||||
}
|
||||
|
||||
runSudoCmd(sudoPassword, "rm", "-f", link.target)
|
||||
runSudoCmd(sudoPassword, "rm", "-f", link.target) //nolint:errcheck
|
||||
|
||||
if err := runSudoCmd(sudoPassword, "ln", "-sf", link.source, link.target); err != nil {
|
||||
logFunc(fmt.Sprintf("⚠ Warning: Failed to create symlink for %s: %v", link.desc, err))
|
||||
|
||||
@@ -77,7 +77,7 @@ func (d *DiscoveryConfig) FindJSONFiles() ([]string, error) {
|
||||
func expandPath(path string) (string, error) {
|
||||
expandedPath := os.ExpandEnv(path)
|
||||
|
||||
if filepath.HasPrefix(expandedPath, "~") {
|
||||
if strings.HasPrefix(expandedPath, "~") {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
|
||||
)
|
||||
@@ -118,7 +119,7 @@ func (j *JSONFileProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
|
||||
func expandPath(path string) (string, error) {
|
||||
expandedPath := os.ExpandEnv(path)
|
||||
|
||||
if filepath.HasPrefix(expandedPath, "~") {
|
||||
if strings.HasPrefix(expandedPath, "~") {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
@@ -64,7 +64,7 @@ func (l *FileLogger) writeToFile(message string) {
|
||||
redacted := l.redactPassword(message)
|
||||
timestamp := time.Now().Format("15:04:05.000")
|
||||
|
||||
l.writer.WriteString(fmt.Sprintf("[%s] %s\n", timestamp, redacted))
|
||||
l.writer.WriteString(fmt.Sprintf("[%s] %s\n", timestamp, redacted)) //nolint:errcheck
|
||||
l.writer.Flush()
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ func (l *FileLogger) Close() error {
|
||||
defer l.mu.Unlock()
|
||||
|
||||
footer := fmt.Sprintf("\n=== DankInstall Log End ===\nCompleted: %s\n", time.Now().Format(time.RFC3339))
|
||||
l.writer.WriteString(footer)
|
||||
l.writer.WriteString(footer) //nolint:errcheck
|
||||
l.writer.Flush()
|
||||
|
||||
if err := l.file.Sync(); err != nil {
|
||||
|
||||
@@ -22,6 +22,99 @@ func (_m *MockCUPSClientInterface) EXPECT() *MockCUPSClientInterface_Expecter {
|
||||
return &MockCUPSClientInterface_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// AcceptJobs provides a mock function with given fields: printer
|
||||
func (_m *MockCUPSClientInterface) AcceptJobs(printer string) error {
|
||||
ret := _m.Called(printer)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for AcceptJobs")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(printer)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_AcceptJobs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AcceptJobs'
|
||||
type MockCUPSClientInterface_AcceptJobs_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// AcceptJobs is a helper method to define mock.On call
|
||||
// - printer string
|
||||
func (_e *MockCUPSClientInterface_Expecter) AcceptJobs(printer interface{}) *MockCUPSClientInterface_AcceptJobs_Call {
|
||||
return &MockCUPSClientInterface_AcceptJobs_Call{Call: _e.mock.On("AcceptJobs", printer)}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_AcceptJobs_Call) Run(run func(printer string)) *MockCUPSClientInterface_AcceptJobs_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_AcceptJobs_Call) Return(_a0 error) *MockCUPSClientInterface_AcceptJobs_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_AcceptJobs_Call) RunAndReturn(run func(string) error) *MockCUPSClientInterface_AcceptJobs_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// AddPrinterToClass provides a mock function with given fields: class, printer
|
||||
func (_m *MockCUPSClientInterface) AddPrinterToClass(class string, printer string) error {
|
||||
ret := _m.Called(class, printer)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for AddPrinterToClass")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string) error); ok {
|
||||
r0 = rf(class, printer)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_AddPrinterToClass_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddPrinterToClass'
|
||||
type MockCUPSClientInterface_AddPrinterToClass_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// AddPrinterToClass is a helper method to define mock.On call
|
||||
// - class string
|
||||
// - printer string
|
||||
func (_e *MockCUPSClientInterface_Expecter) AddPrinterToClass(class interface{}, printer interface{}) *MockCUPSClientInterface_AddPrinterToClass_Call {
|
||||
return &MockCUPSClientInterface_AddPrinterToClass_Call{Call: _e.mock.On("AddPrinterToClass", class, printer)}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_AddPrinterToClass_Call) Run(run func(class string, printer string)) *MockCUPSClientInterface_AddPrinterToClass_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_AddPrinterToClass_Call) Return(_a0 error) *MockCUPSClientInterface_AddPrinterToClass_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_AddPrinterToClass_Call) RunAndReturn(run func(string, string) error) *MockCUPSClientInterface_AddPrinterToClass_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// CancelAllJob provides a mock function with given fields: printer, purge
|
||||
func (_m *MockCUPSClientInterface) CancelAllJob(printer string, purge bool) error {
|
||||
ret := _m.Called(printer, purge)
|
||||
@@ -116,6 +209,312 @@ func (_c *MockCUPSClientInterface_CancelJob_Call) RunAndReturn(run func(int, boo
|
||||
return _c
|
||||
}
|
||||
|
||||
// CreatePrinter provides a mock function with given fields: name, deviceURI, ppd, shared, errorPolicy, information, location
|
||||
func (_m *MockCUPSClientInterface) CreatePrinter(name string, deviceURI string, ppd string, shared bool, errorPolicy string, information string, location string) error {
|
||||
ret := _m.Called(name, deviceURI, ppd, shared, errorPolicy, information, location)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CreatePrinter")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, string, bool, string, string, string) error); ok {
|
||||
r0 = rf(name, deviceURI, ppd, shared, errorPolicy, information, location)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_CreatePrinter_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreatePrinter'
|
||||
type MockCUPSClientInterface_CreatePrinter_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// CreatePrinter is a helper method to define mock.On call
|
||||
// - name string
|
||||
// - deviceURI string
|
||||
// - ppd string
|
||||
// - shared bool
|
||||
// - errorPolicy string
|
||||
// - information string
|
||||
// - location string
|
||||
func (_e *MockCUPSClientInterface_Expecter) CreatePrinter(name interface{}, deviceURI interface{}, ppd interface{}, shared interface{}, errorPolicy interface{}, information interface{}, location interface{}) *MockCUPSClientInterface_CreatePrinter_Call {
|
||||
return &MockCUPSClientInterface_CreatePrinter_Call{Call: _e.mock.On("CreatePrinter", name, deviceURI, ppd, shared, errorPolicy, information, location)}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_CreatePrinter_Call) Run(run func(name string, deviceURI string, ppd string, shared bool, errorPolicy string, information string, location string)) *MockCUPSClientInterface_CreatePrinter_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(string), args[2].(string), args[3].(bool), args[4].(string), args[5].(string), args[6].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_CreatePrinter_Call) Return(_a0 error) *MockCUPSClientInterface_CreatePrinter_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_CreatePrinter_Call) RunAndReturn(run func(string, string, string, bool, string, string, string) error) *MockCUPSClientInterface_CreatePrinter_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// DeleteClass provides a mock function with given fields: class
|
||||
func (_m *MockCUPSClientInterface) DeleteClass(class string) error {
|
||||
ret := _m.Called(class)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeleteClass")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(class)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_DeleteClass_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteClass'
|
||||
type MockCUPSClientInterface_DeleteClass_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// DeleteClass is a helper method to define mock.On call
|
||||
// - class string
|
||||
func (_e *MockCUPSClientInterface_Expecter) DeleteClass(class interface{}) *MockCUPSClientInterface_DeleteClass_Call {
|
||||
return &MockCUPSClientInterface_DeleteClass_Call{Call: _e.mock.On("DeleteClass", class)}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_DeleteClass_Call) Run(run func(class string)) *MockCUPSClientInterface_DeleteClass_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_DeleteClass_Call) Return(_a0 error) *MockCUPSClientInterface_DeleteClass_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_DeleteClass_Call) RunAndReturn(run func(string) error) *MockCUPSClientInterface_DeleteClass_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// DeletePrinter provides a mock function with given fields: printer
|
||||
func (_m *MockCUPSClientInterface) DeletePrinter(printer string) error {
|
||||
ret := _m.Called(printer)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeletePrinter")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(printer)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_DeletePrinter_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeletePrinter'
|
||||
type MockCUPSClientInterface_DeletePrinter_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// DeletePrinter is a helper method to define mock.On call
|
||||
// - printer string
|
||||
func (_e *MockCUPSClientInterface_Expecter) DeletePrinter(printer interface{}) *MockCUPSClientInterface_DeletePrinter_Call {
|
||||
return &MockCUPSClientInterface_DeletePrinter_Call{Call: _e.mock.On("DeletePrinter", printer)}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_DeletePrinter_Call) Run(run func(printer string)) *MockCUPSClientInterface_DeletePrinter_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_DeletePrinter_Call) Return(_a0 error) *MockCUPSClientInterface_DeletePrinter_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_DeletePrinter_Call) RunAndReturn(run func(string) error) *MockCUPSClientInterface_DeletePrinter_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// DeletePrinterFromClass provides a mock function with given fields: class, printer
|
||||
func (_m *MockCUPSClientInterface) DeletePrinterFromClass(class string, printer string) error {
|
||||
ret := _m.Called(class, printer)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeletePrinterFromClass")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string) error); ok {
|
||||
r0 = rf(class, printer)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_DeletePrinterFromClass_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeletePrinterFromClass'
|
||||
type MockCUPSClientInterface_DeletePrinterFromClass_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// DeletePrinterFromClass is a helper method to define mock.On call
|
||||
// - class string
|
||||
// - printer string
|
||||
func (_e *MockCUPSClientInterface_Expecter) DeletePrinterFromClass(class interface{}, printer interface{}) *MockCUPSClientInterface_DeletePrinterFromClass_Call {
|
||||
return &MockCUPSClientInterface_DeletePrinterFromClass_Call{Call: _e.mock.On("DeletePrinterFromClass", class, printer)}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_DeletePrinterFromClass_Call) Run(run func(class string, printer string)) *MockCUPSClientInterface_DeletePrinterFromClass_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_DeletePrinterFromClass_Call) Return(_a0 error) *MockCUPSClientInterface_DeletePrinterFromClass_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_DeletePrinterFromClass_Call) RunAndReturn(run func(string, string) error) *MockCUPSClientInterface_DeletePrinterFromClass_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetClasses provides a mock function with given fields: attributes
|
||||
func (_m *MockCUPSClientInterface) GetClasses(attributes []string) (map[string]ipp.Attributes, error) {
|
||||
ret := _m.Called(attributes)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetClasses")
|
||||
}
|
||||
|
||||
var r0 map[string]ipp.Attributes
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func([]string) (map[string]ipp.Attributes, error)); ok {
|
||||
return rf(attributes)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func([]string) map[string]ipp.Attributes); ok {
|
||||
r0 = rf(attributes)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(map[string]ipp.Attributes)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func([]string) error); ok {
|
||||
r1 = rf(attributes)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_GetClasses_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetClasses'
|
||||
type MockCUPSClientInterface_GetClasses_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetClasses is a helper method to define mock.On call
|
||||
// - attributes []string
|
||||
func (_e *MockCUPSClientInterface_Expecter) GetClasses(attributes interface{}) *MockCUPSClientInterface_GetClasses_Call {
|
||||
return &MockCUPSClientInterface_GetClasses_Call{Call: _e.mock.On("GetClasses", attributes)}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_GetClasses_Call) Run(run func(attributes []string)) *MockCUPSClientInterface_GetClasses_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].([]string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_GetClasses_Call) Return(_a0 map[string]ipp.Attributes, _a1 error) *MockCUPSClientInterface_GetClasses_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_GetClasses_Call) RunAndReturn(run func([]string) (map[string]ipp.Attributes, error)) *MockCUPSClientInterface_GetClasses_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetDevices provides a mock function with no fields
|
||||
func (_m *MockCUPSClientInterface) GetDevices() (map[string]ipp.Attributes, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetDevices")
|
||||
}
|
||||
|
||||
var r0 map[string]ipp.Attributes
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func() (map[string]ipp.Attributes, error)); ok {
|
||||
return rf()
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func() map[string]ipp.Attributes); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(map[string]ipp.Attributes)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_GetDevices_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetDevices'
|
||||
type MockCUPSClientInterface_GetDevices_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetDevices is a helper method to define mock.On call
|
||||
func (_e *MockCUPSClientInterface_Expecter) GetDevices() *MockCUPSClientInterface_GetDevices_Call {
|
||||
return &MockCUPSClientInterface_GetDevices_Call{Call: _e.mock.On("GetDevices")}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_GetDevices_Call) Run(run func()) *MockCUPSClientInterface_GetDevices_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_GetDevices_Call) Return(_a0 map[string]ipp.Attributes, _a1 error) *MockCUPSClientInterface_GetDevices_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_GetDevices_Call) RunAndReturn(run func() (map[string]ipp.Attributes, error)) *MockCUPSClientInterface_GetDevices_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetJobs provides a mock function with given fields: printer, class, whichJobs, myJobs, firstJobId, limit, attributes
|
||||
func (_m *MockCUPSClientInterface) GetJobs(printer string, class string, whichJobs string, myJobs bool, firstJobId int, limit int, attributes []string) (map[int]ipp.Attributes, error) {
|
||||
ret := _m.Called(printer, class, whichJobs, myJobs, firstJobId, limit, attributes)
|
||||
@@ -180,6 +579,63 @@ func (_c *MockCUPSClientInterface_GetJobs_Call) RunAndReturn(run func(string, st
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetPPDs provides a mock function with no fields
|
||||
func (_m *MockCUPSClientInterface) GetPPDs() (map[string]ipp.Attributes, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetPPDs")
|
||||
}
|
||||
|
||||
var r0 map[string]ipp.Attributes
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func() (map[string]ipp.Attributes, error)); ok {
|
||||
return rf()
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func() map[string]ipp.Attributes); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(map[string]ipp.Attributes)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_GetPPDs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetPPDs'
|
||||
type MockCUPSClientInterface_GetPPDs_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetPPDs is a helper method to define mock.On call
|
||||
func (_e *MockCUPSClientInterface_Expecter) GetPPDs() *MockCUPSClientInterface_GetPPDs_Call {
|
||||
return &MockCUPSClientInterface_GetPPDs_Call{Call: _e.mock.On("GetPPDs")}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_GetPPDs_Call) Run(run func()) *MockCUPSClientInterface_GetPPDs_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_GetPPDs_Call) Return(_a0 map[string]ipp.Attributes, _a1 error) *MockCUPSClientInterface_GetPPDs_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_GetPPDs_Call) RunAndReturn(run func() (map[string]ipp.Attributes, error)) *MockCUPSClientInterface_GetPPDs_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetPrinters provides a mock function with given fields: attributes
|
||||
func (_m *MockCUPSClientInterface) GetPrinters(attributes []string) (map[string]ipp.Attributes, error) {
|
||||
ret := _m.Called(attributes)
|
||||
@@ -238,6 +694,100 @@ func (_c *MockCUPSClientInterface_GetPrinters_Call) RunAndReturn(run func([]stri
|
||||
return _c
|
||||
}
|
||||
|
||||
// HoldJobUntil provides a mock function with given fields: jobID, holdUntil
|
||||
func (_m *MockCUPSClientInterface) HoldJobUntil(jobID int, holdUntil string) error {
|
||||
ret := _m.Called(jobID, holdUntil)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for HoldJobUntil")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(int, string) error); ok {
|
||||
r0 = rf(jobID, holdUntil)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_HoldJobUntil_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HoldJobUntil'
|
||||
type MockCUPSClientInterface_HoldJobUntil_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// HoldJobUntil is a helper method to define mock.On call
|
||||
// - jobID int
|
||||
// - holdUntil string
|
||||
func (_e *MockCUPSClientInterface_Expecter) HoldJobUntil(jobID interface{}, holdUntil interface{}) *MockCUPSClientInterface_HoldJobUntil_Call {
|
||||
return &MockCUPSClientInterface_HoldJobUntil_Call{Call: _e.mock.On("HoldJobUntil", jobID, holdUntil)}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_HoldJobUntil_Call) Run(run func(jobID int, holdUntil string)) *MockCUPSClientInterface_HoldJobUntil_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(int), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_HoldJobUntil_Call) Return(_a0 error) *MockCUPSClientInterface_HoldJobUntil_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_HoldJobUntil_Call) RunAndReturn(run func(int, string) error) *MockCUPSClientInterface_HoldJobUntil_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// MoveJob provides a mock function with given fields: jobID, destPrinter
|
||||
func (_m *MockCUPSClientInterface) MoveJob(jobID int, destPrinter string) error {
|
||||
ret := _m.Called(jobID, destPrinter)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for MoveJob")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(int, string) error); ok {
|
||||
r0 = rf(jobID, destPrinter)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_MoveJob_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MoveJob'
|
||||
type MockCUPSClientInterface_MoveJob_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// MoveJob is a helper method to define mock.On call
|
||||
// - jobID int
|
||||
// - destPrinter string
|
||||
func (_e *MockCUPSClientInterface_Expecter) MoveJob(jobID interface{}, destPrinter interface{}) *MockCUPSClientInterface_MoveJob_Call {
|
||||
return &MockCUPSClientInterface_MoveJob_Call{Call: _e.mock.On("MoveJob", jobID, destPrinter)}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_MoveJob_Call) Run(run func(jobID int, destPrinter string)) *MockCUPSClientInterface_MoveJob_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(int), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_MoveJob_Call) Return(_a0 error) *MockCUPSClientInterface_MoveJob_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_MoveJob_Call) RunAndReturn(run func(int, string) error) *MockCUPSClientInterface_MoveJob_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// PausePrinter provides a mock function with given fields: printer
|
||||
func (_m *MockCUPSClientInterface) PausePrinter(printer string) error {
|
||||
ret := _m.Called(printer)
|
||||
@@ -284,6 +834,156 @@ func (_c *MockCUPSClientInterface_PausePrinter_Call) RunAndReturn(run func(strin
|
||||
return _c
|
||||
}
|
||||
|
||||
// PrintTestPage provides a mock function with given fields: printer, testPageData, size
|
||||
func (_m *MockCUPSClientInterface) PrintTestPage(printer string, testPageData io.Reader, size int) (int, error) {
|
||||
ret := _m.Called(printer, testPageData, size)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for PrintTestPage")
|
||||
}
|
||||
|
||||
var r0 int
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string, io.Reader, int) (int, error)); ok {
|
||||
return rf(printer, testPageData, size)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string, io.Reader, int) int); ok {
|
||||
r0 = rf(printer, testPageData, size)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string, io.Reader, int) error); ok {
|
||||
r1 = rf(printer, testPageData, size)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_PrintTestPage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PrintTestPage'
|
||||
type MockCUPSClientInterface_PrintTestPage_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// PrintTestPage is a helper method to define mock.On call
|
||||
// - printer string
|
||||
// - testPageData io.Reader
|
||||
// - size int
|
||||
func (_e *MockCUPSClientInterface_Expecter) PrintTestPage(printer interface{}, testPageData interface{}, size interface{}) *MockCUPSClientInterface_PrintTestPage_Call {
|
||||
return &MockCUPSClientInterface_PrintTestPage_Call{Call: _e.mock.On("PrintTestPage", printer, testPageData, size)}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_PrintTestPage_Call) Run(run func(printer string, testPageData io.Reader, size int)) *MockCUPSClientInterface_PrintTestPage_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(io.Reader), args[2].(int))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_PrintTestPage_Call) Return(_a0 int, _a1 error) *MockCUPSClientInterface_PrintTestPage_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_PrintTestPage_Call) RunAndReturn(run func(string, io.Reader, int) (int, error)) *MockCUPSClientInterface_PrintTestPage_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// RejectJobs provides a mock function with given fields: printer
|
||||
func (_m *MockCUPSClientInterface) RejectJobs(printer string) error {
|
||||
ret := _m.Called(printer)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RejectJobs")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(printer)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_RejectJobs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RejectJobs'
|
||||
type MockCUPSClientInterface_RejectJobs_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// RejectJobs is a helper method to define mock.On call
|
||||
// - printer string
|
||||
func (_e *MockCUPSClientInterface_Expecter) RejectJobs(printer interface{}) *MockCUPSClientInterface_RejectJobs_Call {
|
||||
return &MockCUPSClientInterface_RejectJobs_Call{Call: _e.mock.On("RejectJobs", printer)}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_RejectJobs_Call) Run(run func(printer string)) *MockCUPSClientInterface_RejectJobs_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_RejectJobs_Call) Return(_a0 error) *MockCUPSClientInterface_RejectJobs_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_RejectJobs_Call) RunAndReturn(run func(string) error) *MockCUPSClientInterface_RejectJobs_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// RestartJob provides a mock function with given fields: jobID
|
||||
func (_m *MockCUPSClientInterface) RestartJob(jobID int) error {
|
||||
ret := _m.Called(jobID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RestartJob")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(int) error); ok {
|
||||
r0 = rf(jobID)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_RestartJob_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RestartJob'
|
||||
type MockCUPSClientInterface_RestartJob_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// RestartJob is a helper method to define mock.On call
|
||||
// - jobID int
|
||||
func (_e *MockCUPSClientInterface_Expecter) RestartJob(jobID interface{}) *MockCUPSClientInterface_RestartJob_Call {
|
||||
return &MockCUPSClientInterface_RestartJob_Call{Call: _e.mock.On("RestartJob", jobID)}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_RestartJob_Call) Run(run func(jobID int)) *MockCUPSClientInterface_RestartJob_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(int))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_RestartJob_Call) Return(_a0 error) *MockCUPSClientInterface_RestartJob_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_RestartJob_Call) RunAndReturn(run func(int) error) *MockCUPSClientInterface_RestartJob_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ResumePrinter provides a mock function with given fields: printer
|
||||
func (_m *MockCUPSClientInterface) ResumePrinter(printer string) error {
|
||||
ret := _m.Called(printer)
|
||||
@@ -390,6 +1090,147 @@ func (_c *MockCUPSClientInterface_SendRequest_Call) RunAndReturn(run func(string
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetPrinterInformation provides a mock function with given fields: printer, information
|
||||
func (_m *MockCUPSClientInterface) SetPrinterInformation(printer string, information string) error {
|
||||
ret := _m.Called(printer, information)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SetPrinterInformation")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string) error); ok {
|
||||
r0 = rf(printer, information)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_SetPrinterInformation_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetPrinterInformation'
|
||||
type MockCUPSClientInterface_SetPrinterInformation_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// SetPrinterInformation is a helper method to define mock.On call
|
||||
// - printer string
|
||||
// - information string
|
||||
func (_e *MockCUPSClientInterface_Expecter) SetPrinterInformation(printer interface{}, information interface{}) *MockCUPSClientInterface_SetPrinterInformation_Call {
|
||||
return &MockCUPSClientInterface_SetPrinterInformation_Call{Call: _e.mock.On("SetPrinterInformation", printer, information)}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_SetPrinterInformation_Call) Run(run func(printer string, information string)) *MockCUPSClientInterface_SetPrinterInformation_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_SetPrinterInformation_Call) Return(_a0 error) *MockCUPSClientInterface_SetPrinterInformation_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_SetPrinterInformation_Call) RunAndReturn(run func(string, string) error) *MockCUPSClientInterface_SetPrinterInformation_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetPrinterIsShared provides a mock function with given fields: printer, shared
|
||||
func (_m *MockCUPSClientInterface) SetPrinterIsShared(printer string, shared bool) error {
|
||||
ret := _m.Called(printer, shared)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SetPrinterIsShared")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, bool) error); ok {
|
||||
r0 = rf(printer, shared)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_SetPrinterIsShared_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetPrinterIsShared'
|
||||
type MockCUPSClientInterface_SetPrinterIsShared_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// SetPrinterIsShared is a helper method to define mock.On call
|
||||
// - printer string
|
||||
// - shared bool
|
||||
func (_e *MockCUPSClientInterface_Expecter) SetPrinterIsShared(printer interface{}, shared interface{}) *MockCUPSClientInterface_SetPrinterIsShared_Call {
|
||||
return &MockCUPSClientInterface_SetPrinterIsShared_Call{Call: _e.mock.On("SetPrinterIsShared", printer, shared)}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_SetPrinterIsShared_Call) Run(run func(printer string, shared bool)) *MockCUPSClientInterface_SetPrinterIsShared_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(bool))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_SetPrinterIsShared_Call) Return(_a0 error) *MockCUPSClientInterface_SetPrinterIsShared_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_SetPrinterIsShared_Call) RunAndReturn(run func(string, bool) error) *MockCUPSClientInterface_SetPrinterIsShared_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetPrinterLocation provides a mock function with given fields: printer, location
|
||||
func (_m *MockCUPSClientInterface) SetPrinterLocation(printer string, location string) error {
|
||||
ret := _m.Called(printer, location)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SetPrinterLocation")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string) error); ok {
|
||||
r0 = rf(printer, location)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_SetPrinterLocation_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetPrinterLocation'
|
||||
type MockCUPSClientInterface_SetPrinterLocation_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// SetPrinterLocation is a helper method to define mock.On call
|
||||
// - printer string
|
||||
// - location string
|
||||
func (_e *MockCUPSClientInterface_Expecter) SetPrinterLocation(printer interface{}, location interface{}) *MockCUPSClientInterface_SetPrinterLocation_Call {
|
||||
return &MockCUPSClientInterface_SetPrinterLocation_Call{Call: _e.mock.On("SetPrinterLocation", printer, location)}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_SetPrinterLocation_Call) Run(run func(printer string, location string)) *MockCUPSClientInterface_SetPrinterLocation_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_SetPrinterLocation_Call) Return(_a0 error) *MockCUPSClientInterface_SetPrinterLocation_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_SetPrinterLocation_Call) RunAndReturn(run func(string, string) error) *MockCUPSClientInterface_SetPrinterLocation_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockCUPSClientInterface creates a new instance of MockCUPSClientInterface. 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 NewMockCUPSClientInterface(t interface {
|
||||
|
||||
708
core/internal/mocks/cups_pkhelper/mock_PkHelper.go
Normal file
708
core/internal/mocks/cups_pkhelper/mock_PkHelper.go
Normal file
@@ -0,0 +1,708 @@
|
||||
// Code generated by mockery v2.53.5. DO NOT EDIT.
|
||||
|
||||
package mocks_cups_pkhelper
|
||||
|
||||
import (
|
||||
cups "github.com/AvengeMedia/DankMaterialShell/core/internal/server/cups"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// MockPkHelper is an autogenerated mock type for the PkHelper type
|
||||
type MockPkHelper struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type MockPkHelper_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockPkHelper) EXPECT() *MockPkHelper_Expecter {
|
||||
return &MockPkHelper_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// ClassAddPrinter provides a mock function with given fields: className, printerName
|
||||
func (_m *MockPkHelper) ClassAddPrinter(className string, printerName string) error {
|
||||
ret := _m.Called(className, printerName)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ClassAddPrinter")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string) error); ok {
|
||||
r0 = rf(className, printerName)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockPkHelper_ClassAddPrinter_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ClassAddPrinter'
|
||||
type MockPkHelper_ClassAddPrinter_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ClassAddPrinter is a helper method to define mock.On call
|
||||
// - className string
|
||||
// - printerName string
|
||||
func (_e *MockPkHelper_Expecter) ClassAddPrinter(className interface{}, printerName interface{}) *MockPkHelper_ClassAddPrinter_Call {
|
||||
return &MockPkHelper_ClassAddPrinter_Call{Call: _e.mock.On("ClassAddPrinter", className, printerName)}
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_ClassAddPrinter_Call) Run(run func(className string, printerName string)) *MockPkHelper_ClassAddPrinter_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_ClassAddPrinter_Call) Return(_a0 error) *MockPkHelper_ClassAddPrinter_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_ClassAddPrinter_Call) RunAndReturn(run func(string, string) error) *MockPkHelper_ClassAddPrinter_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ClassDelete provides a mock function with given fields: className
|
||||
func (_m *MockPkHelper) ClassDelete(className string) error {
|
||||
ret := _m.Called(className)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ClassDelete")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(className)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockPkHelper_ClassDelete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ClassDelete'
|
||||
type MockPkHelper_ClassDelete_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ClassDelete is a helper method to define mock.On call
|
||||
// - className string
|
||||
func (_e *MockPkHelper_Expecter) ClassDelete(className interface{}) *MockPkHelper_ClassDelete_Call {
|
||||
return &MockPkHelper_ClassDelete_Call{Call: _e.mock.On("ClassDelete", className)}
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_ClassDelete_Call) Run(run func(className string)) *MockPkHelper_ClassDelete_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_ClassDelete_Call) Return(_a0 error) *MockPkHelper_ClassDelete_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_ClassDelete_Call) RunAndReturn(run func(string) error) *MockPkHelper_ClassDelete_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ClassDeletePrinter provides a mock function with given fields: className, printerName
|
||||
func (_m *MockPkHelper) ClassDeletePrinter(className string, printerName string) error {
|
||||
ret := _m.Called(className, printerName)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ClassDeletePrinter")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string) error); ok {
|
||||
r0 = rf(className, printerName)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockPkHelper_ClassDeletePrinter_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ClassDeletePrinter'
|
||||
type MockPkHelper_ClassDeletePrinter_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ClassDeletePrinter is a helper method to define mock.On call
|
||||
// - className string
|
||||
// - printerName string
|
||||
func (_e *MockPkHelper_Expecter) ClassDeletePrinter(className interface{}, printerName interface{}) *MockPkHelper_ClassDeletePrinter_Call {
|
||||
return &MockPkHelper_ClassDeletePrinter_Call{Call: _e.mock.On("ClassDeletePrinter", className, printerName)}
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_ClassDeletePrinter_Call) Run(run func(className string, printerName string)) *MockPkHelper_ClassDeletePrinter_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_ClassDeletePrinter_Call) Return(_a0 error) *MockPkHelper_ClassDeletePrinter_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_ClassDeletePrinter_Call) RunAndReturn(run func(string, string) error) *MockPkHelper_ClassDeletePrinter_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// DevicesGet provides a mock function with given fields: timeout, limit, includeSchemes, excludeSchemes
|
||||
func (_m *MockPkHelper) DevicesGet(timeout int, limit int, includeSchemes []string, excludeSchemes []string) ([]cups.Device, error) {
|
||||
ret := _m.Called(timeout, limit, includeSchemes, excludeSchemes)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DevicesGet")
|
||||
}
|
||||
|
||||
var r0 []cups.Device
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(int, int, []string, []string) ([]cups.Device, error)); ok {
|
||||
return rf(timeout, limit, includeSchemes, excludeSchemes)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(int, int, []string, []string) []cups.Device); ok {
|
||||
r0 = rf(timeout, limit, includeSchemes, excludeSchemes)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]cups.Device)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(int, int, []string, []string) error); ok {
|
||||
r1 = rf(timeout, limit, includeSchemes, excludeSchemes)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockPkHelper_DevicesGet_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DevicesGet'
|
||||
type MockPkHelper_DevicesGet_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// DevicesGet is a helper method to define mock.On call
|
||||
// - timeout int
|
||||
// - limit int
|
||||
// - includeSchemes []string
|
||||
// - excludeSchemes []string
|
||||
func (_e *MockPkHelper_Expecter) DevicesGet(timeout interface{}, limit interface{}, includeSchemes interface{}, excludeSchemes interface{}) *MockPkHelper_DevicesGet_Call {
|
||||
return &MockPkHelper_DevicesGet_Call{Call: _e.mock.On("DevicesGet", timeout, limit, includeSchemes, excludeSchemes)}
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_DevicesGet_Call) Run(run func(timeout int, limit int, includeSchemes []string, excludeSchemes []string)) *MockPkHelper_DevicesGet_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(int), args[1].(int), args[2].([]string), args[3].([]string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_DevicesGet_Call) Return(_a0 []cups.Device, _a1 error) *MockPkHelper_DevicesGet_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_DevicesGet_Call) RunAndReturn(run func(int, int, []string, []string) ([]cups.Device, error)) *MockPkHelper_DevicesGet_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// JobCancelPurge provides a mock function with given fields: jobID, purge
|
||||
func (_m *MockPkHelper) JobCancelPurge(jobID int, purge bool) error {
|
||||
ret := _m.Called(jobID, purge)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for JobCancelPurge")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(int, bool) error); ok {
|
||||
r0 = rf(jobID, purge)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockPkHelper_JobCancelPurge_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'JobCancelPurge'
|
||||
type MockPkHelper_JobCancelPurge_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// JobCancelPurge is a helper method to define mock.On call
|
||||
// - jobID int
|
||||
// - purge bool
|
||||
func (_e *MockPkHelper_Expecter) JobCancelPurge(jobID interface{}, purge interface{}) *MockPkHelper_JobCancelPurge_Call {
|
||||
return &MockPkHelper_JobCancelPurge_Call{Call: _e.mock.On("JobCancelPurge", jobID, purge)}
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_JobCancelPurge_Call) Run(run func(jobID int, purge bool)) *MockPkHelper_JobCancelPurge_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(int), args[1].(bool))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_JobCancelPurge_Call) Return(_a0 error) *MockPkHelper_JobCancelPurge_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_JobCancelPurge_Call) RunAndReturn(run func(int, bool) error) *MockPkHelper_JobCancelPurge_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// JobRestart provides a mock function with given fields: jobID
|
||||
func (_m *MockPkHelper) JobRestart(jobID int) error {
|
||||
ret := _m.Called(jobID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for JobRestart")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(int) error); ok {
|
||||
r0 = rf(jobID)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockPkHelper_JobRestart_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'JobRestart'
|
||||
type MockPkHelper_JobRestart_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// JobRestart is a helper method to define mock.On call
|
||||
// - jobID int
|
||||
func (_e *MockPkHelper_Expecter) JobRestart(jobID interface{}) *MockPkHelper_JobRestart_Call {
|
||||
return &MockPkHelper_JobRestart_Call{Call: _e.mock.On("JobRestart", jobID)}
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_JobRestart_Call) Run(run func(jobID int)) *MockPkHelper_JobRestart_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(int))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_JobRestart_Call) Return(_a0 error) *MockPkHelper_JobRestart_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_JobRestart_Call) RunAndReturn(run func(int) error) *MockPkHelper_JobRestart_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// JobSetHoldUntil provides a mock function with given fields: jobID, holdUntil
|
||||
func (_m *MockPkHelper) JobSetHoldUntil(jobID int, holdUntil string) error {
|
||||
ret := _m.Called(jobID, holdUntil)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for JobSetHoldUntil")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(int, string) error); ok {
|
||||
r0 = rf(jobID, holdUntil)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockPkHelper_JobSetHoldUntil_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'JobSetHoldUntil'
|
||||
type MockPkHelper_JobSetHoldUntil_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// JobSetHoldUntil is a helper method to define mock.On call
|
||||
// - jobID int
|
||||
// - holdUntil string
|
||||
func (_e *MockPkHelper_Expecter) JobSetHoldUntil(jobID interface{}, holdUntil interface{}) *MockPkHelper_JobSetHoldUntil_Call {
|
||||
return &MockPkHelper_JobSetHoldUntil_Call{Call: _e.mock.On("JobSetHoldUntil", jobID, holdUntil)}
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_JobSetHoldUntil_Call) Run(run func(jobID int, holdUntil string)) *MockPkHelper_JobSetHoldUntil_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(int), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_JobSetHoldUntil_Call) Return(_a0 error) *MockPkHelper_JobSetHoldUntil_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_JobSetHoldUntil_Call) RunAndReturn(run func(int, string) error) *MockPkHelper_JobSetHoldUntil_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// PrinterAdd provides a mock function with given fields: name, uri, ppd, info, location
|
||||
func (_m *MockPkHelper) PrinterAdd(name string, uri string, ppd string, info string, location string) error {
|
||||
ret := _m.Called(name, uri, ppd, info, location)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for PrinterAdd")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, string, string, string) error); ok {
|
||||
r0 = rf(name, uri, ppd, info, location)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockPkHelper_PrinterAdd_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PrinterAdd'
|
||||
type MockPkHelper_PrinterAdd_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// PrinterAdd is a helper method to define mock.On call
|
||||
// - name string
|
||||
// - uri string
|
||||
// - ppd string
|
||||
// - info string
|
||||
// - location string
|
||||
func (_e *MockPkHelper_Expecter) PrinterAdd(name interface{}, uri interface{}, ppd interface{}, info interface{}, location interface{}) *MockPkHelper_PrinterAdd_Call {
|
||||
return &MockPkHelper_PrinterAdd_Call{Call: _e.mock.On("PrinterAdd", name, uri, ppd, info, location)}
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterAdd_Call) Run(run func(name string, uri string, ppd string, info string, location string)) *MockPkHelper_PrinterAdd_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(string), args[2].(string), args[3].(string), args[4].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterAdd_Call) Return(_a0 error) *MockPkHelper_PrinterAdd_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterAdd_Call) RunAndReturn(run func(string, string, string, string, string) error) *MockPkHelper_PrinterAdd_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// PrinterDelete provides a mock function with given fields: name
|
||||
func (_m *MockPkHelper) PrinterDelete(name string) error {
|
||||
ret := _m.Called(name)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for PrinterDelete")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(name)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockPkHelper_PrinterDelete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PrinterDelete'
|
||||
type MockPkHelper_PrinterDelete_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// PrinterDelete is a helper method to define mock.On call
|
||||
// - name string
|
||||
func (_e *MockPkHelper_Expecter) PrinterDelete(name interface{}) *MockPkHelper_PrinterDelete_Call {
|
||||
return &MockPkHelper_PrinterDelete_Call{Call: _e.mock.On("PrinterDelete", name)}
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterDelete_Call) Run(run func(name string)) *MockPkHelper_PrinterDelete_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterDelete_Call) Return(_a0 error) *MockPkHelper_PrinterDelete_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterDelete_Call) RunAndReturn(run func(string) error) *MockPkHelper_PrinterDelete_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// PrinterSetAcceptJobs provides a mock function with given fields: name, enabled, reason
|
||||
func (_m *MockPkHelper) PrinterSetAcceptJobs(name string, enabled bool, reason string) error {
|
||||
ret := _m.Called(name, enabled, reason)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for PrinterSetAcceptJobs")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, bool, string) error); ok {
|
||||
r0 = rf(name, enabled, reason)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockPkHelper_PrinterSetAcceptJobs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PrinterSetAcceptJobs'
|
||||
type MockPkHelper_PrinterSetAcceptJobs_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// PrinterSetAcceptJobs is a helper method to define mock.On call
|
||||
// - name string
|
||||
// - enabled bool
|
||||
// - reason string
|
||||
func (_e *MockPkHelper_Expecter) PrinterSetAcceptJobs(name interface{}, enabled interface{}, reason interface{}) *MockPkHelper_PrinterSetAcceptJobs_Call {
|
||||
return &MockPkHelper_PrinterSetAcceptJobs_Call{Call: _e.mock.On("PrinterSetAcceptJobs", name, enabled, reason)}
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterSetAcceptJobs_Call) Run(run func(name string, enabled bool, reason string)) *MockPkHelper_PrinterSetAcceptJobs_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(bool), args[2].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterSetAcceptJobs_Call) Return(_a0 error) *MockPkHelper_PrinterSetAcceptJobs_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterSetAcceptJobs_Call) RunAndReturn(run func(string, bool, string) error) *MockPkHelper_PrinterSetAcceptJobs_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// PrinterSetEnabled provides a mock function with given fields: name, enabled
|
||||
func (_m *MockPkHelper) PrinterSetEnabled(name string, enabled bool) error {
|
||||
ret := _m.Called(name, enabled)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for PrinterSetEnabled")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, bool) error); ok {
|
||||
r0 = rf(name, enabled)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockPkHelper_PrinterSetEnabled_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PrinterSetEnabled'
|
||||
type MockPkHelper_PrinterSetEnabled_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// PrinterSetEnabled is a helper method to define mock.On call
|
||||
// - name string
|
||||
// - enabled bool
|
||||
func (_e *MockPkHelper_Expecter) PrinterSetEnabled(name interface{}, enabled interface{}) *MockPkHelper_PrinterSetEnabled_Call {
|
||||
return &MockPkHelper_PrinterSetEnabled_Call{Call: _e.mock.On("PrinterSetEnabled", name, enabled)}
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterSetEnabled_Call) Run(run func(name string, enabled bool)) *MockPkHelper_PrinterSetEnabled_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(bool))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterSetEnabled_Call) Return(_a0 error) *MockPkHelper_PrinterSetEnabled_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterSetEnabled_Call) RunAndReturn(run func(string, bool) error) *MockPkHelper_PrinterSetEnabled_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// PrinterSetInfo provides a mock function with given fields: name, info
|
||||
func (_m *MockPkHelper) PrinterSetInfo(name string, info string) error {
|
||||
ret := _m.Called(name, info)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for PrinterSetInfo")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string) error); ok {
|
||||
r0 = rf(name, info)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockPkHelper_PrinterSetInfo_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PrinterSetInfo'
|
||||
type MockPkHelper_PrinterSetInfo_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// PrinterSetInfo is a helper method to define mock.On call
|
||||
// - name string
|
||||
// - info string
|
||||
func (_e *MockPkHelper_Expecter) PrinterSetInfo(name interface{}, info interface{}) *MockPkHelper_PrinterSetInfo_Call {
|
||||
return &MockPkHelper_PrinterSetInfo_Call{Call: _e.mock.On("PrinterSetInfo", name, info)}
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterSetInfo_Call) Run(run func(name string, info string)) *MockPkHelper_PrinterSetInfo_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterSetInfo_Call) Return(_a0 error) *MockPkHelper_PrinterSetInfo_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterSetInfo_Call) RunAndReturn(run func(string, string) error) *MockPkHelper_PrinterSetInfo_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// PrinterSetLocation provides a mock function with given fields: name, location
|
||||
func (_m *MockPkHelper) PrinterSetLocation(name string, location string) error {
|
||||
ret := _m.Called(name, location)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for PrinterSetLocation")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string) error); ok {
|
||||
r0 = rf(name, location)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockPkHelper_PrinterSetLocation_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PrinterSetLocation'
|
||||
type MockPkHelper_PrinterSetLocation_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// PrinterSetLocation is a helper method to define mock.On call
|
||||
// - name string
|
||||
// - location string
|
||||
func (_e *MockPkHelper_Expecter) PrinterSetLocation(name interface{}, location interface{}) *MockPkHelper_PrinterSetLocation_Call {
|
||||
return &MockPkHelper_PrinterSetLocation_Call{Call: _e.mock.On("PrinterSetLocation", name, location)}
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterSetLocation_Call) Run(run func(name string, location string)) *MockPkHelper_PrinterSetLocation_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterSetLocation_Call) Return(_a0 error) *MockPkHelper_PrinterSetLocation_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterSetLocation_Call) RunAndReturn(run func(string, string) error) *MockPkHelper_PrinterSetLocation_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// PrinterSetShared provides a mock function with given fields: name, shared
|
||||
func (_m *MockPkHelper) PrinterSetShared(name string, shared bool) error {
|
||||
ret := _m.Called(name, shared)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for PrinterSetShared")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, bool) error); ok {
|
||||
r0 = rf(name, shared)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockPkHelper_PrinterSetShared_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PrinterSetShared'
|
||||
type MockPkHelper_PrinterSetShared_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// PrinterSetShared is a helper method to define mock.On call
|
||||
// - name string
|
||||
// - shared bool
|
||||
func (_e *MockPkHelper_Expecter) PrinterSetShared(name interface{}, shared interface{}) *MockPkHelper_PrinterSetShared_Call {
|
||||
return &MockPkHelper_PrinterSetShared_Call{Call: _e.mock.On("PrinterSetShared", name, shared)}
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterSetShared_Call) Run(run func(name string, shared bool)) *MockPkHelper_PrinterSetShared_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(bool))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterSetShared_Call) Return(_a0 error) *MockPkHelper_PrinterSetShared_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterSetShared_Call) RunAndReturn(run func(string, bool) error) *MockPkHelper_PrinterSetShared_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockPkHelper creates a new instance of MockPkHelper. 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 NewMockPkHelper(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockPkHelper {
|
||||
mock := &MockPkHelper{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -328,6 +328,52 @@ func (_c *MockBackend_ConnectWiFi_Call) RunAndReturn(run func(network.Connection
|
||||
return _c
|
||||
}
|
||||
|
||||
// DeleteVPN provides a mock function with given fields: uuidOrName
|
||||
func (_m *MockBackend) DeleteVPN(uuidOrName string) error {
|
||||
ret := _m.Called(uuidOrName)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeleteVPN")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(uuidOrName)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockBackend_DeleteVPN_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteVPN'
|
||||
type MockBackend_DeleteVPN_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// DeleteVPN is a helper method to define mock.On call
|
||||
// - uuidOrName string
|
||||
func (_e *MockBackend_Expecter) DeleteVPN(uuidOrName interface{}) *MockBackend_DeleteVPN_Call {
|
||||
return &MockBackend_DeleteVPN_Call{Call: _e.mock.On("DeleteVPN", uuidOrName)}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_DeleteVPN_Call) Run(run func(uuidOrName string)) *MockBackend_DeleteVPN_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_DeleteVPN_Call) Return(_a0 error) *MockBackend_DeleteVPN_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_DeleteVPN_Call) RunAndReturn(run func(string) error) *MockBackend_DeleteVPN_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// DisconnectAllVPN provides a mock function with no fields
|
||||
func (_m *MockBackend) DisconnectAllVPN() error {
|
||||
ret := _m.Called()
|
||||
@@ -418,6 +464,52 @@ func (_c *MockBackend_DisconnectEthernet_Call) RunAndReturn(run func() error) *M
|
||||
return _c
|
||||
}
|
||||
|
||||
// DisconnectEthernetDevice provides a mock function with given fields: device
|
||||
func (_m *MockBackend) DisconnectEthernetDevice(device string) error {
|
||||
ret := _m.Called(device)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DisconnectEthernetDevice")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(device)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockBackend_DisconnectEthernetDevice_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DisconnectEthernetDevice'
|
||||
type MockBackend_DisconnectEthernetDevice_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// DisconnectEthernetDevice is a helper method to define mock.On call
|
||||
// - device string
|
||||
func (_e *MockBackend_Expecter) DisconnectEthernetDevice(device interface{}) *MockBackend_DisconnectEthernetDevice_Call {
|
||||
return &MockBackend_DisconnectEthernetDevice_Call{Call: _e.mock.On("DisconnectEthernetDevice", device)}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_DisconnectEthernetDevice_Call) Run(run func(device string)) *MockBackend_DisconnectEthernetDevice_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_DisconnectEthernetDevice_Call) Return(_a0 error) *MockBackend_DisconnectEthernetDevice_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_DisconnectEthernetDevice_Call) RunAndReturn(run func(string) error) *MockBackend_DisconnectEthernetDevice_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// DisconnectVPN provides a mock function with given fields: uuidOrName
|
||||
func (_m *MockBackend) DisconnectVPN(uuidOrName string) error {
|
||||
ret := _m.Called(uuidOrName)
|
||||
@@ -509,6 +601,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)
|
||||
@@ -612,6 +750,53 @@ func (_c *MockBackend_GetCurrentState_Call) RunAndReturn(run func() (*network.Ba
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetEthernetDevices provides a mock function with no fields
|
||||
func (_m *MockBackend) GetEthernetDevices() []network.EthernetDevice {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetEthernetDevices")
|
||||
}
|
||||
|
||||
var r0 []network.EthernetDevice
|
||||
if rf, ok := ret.Get(0).(func() []network.EthernetDevice); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]network.EthernetDevice)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockBackend_GetEthernetDevices_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetEthernetDevices'
|
||||
type MockBackend_GetEthernetDevices_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetEthernetDevices is a helper method to define mock.On call
|
||||
func (_e *MockBackend_Expecter) GetEthernetDevices() *MockBackend_GetEthernetDevices_Call {
|
||||
return &MockBackend_GetEthernetDevices_Call{Call: _e.mock.On("GetEthernetDevices")}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_GetEthernetDevices_Call) Run(run func()) *MockBackend_GetEthernetDevices_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_GetEthernetDevices_Call) Return(_a0 []network.EthernetDevice) *MockBackend_GetEthernetDevices_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_GetEthernetDevices_Call) RunAndReturn(run func() []network.EthernetDevice) *MockBackend_GetEthernetDevices_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetPromptBroker provides a mock function with no fields
|
||||
func (_m *MockBackend) GetPromptBroker() network.PromptBroker {
|
||||
ret := _m.Called()
|
||||
@@ -659,6 +844,111 @@ func (_c *MockBackend_GetPromptBroker_Call) RunAndReturn(run func() network.Prom
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetVPNConfig provides a mock function with given fields: uuidOrName
|
||||
func (_m *MockBackend) GetVPNConfig(uuidOrName string) (*network.VPNConfig, error) {
|
||||
ret := _m.Called(uuidOrName)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetVPNConfig")
|
||||
}
|
||||
|
||||
var r0 *network.VPNConfig
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string) (*network.VPNConfig, error)); ok {
|
||||
return rf(uuidOrName)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string) *network.VPNConfig); ok {
|
||||
r0 = rf(uuidOrName)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*network.VPNConfig)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(uuidOrName)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockBackend_GetVPNConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetVPNConfig'
|
||||
type MockBackend_GetVPNConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetVPNConfig is a helper method to define mock.On call
|
||||
// - uuidOrName string
|
||||
func (_e *MockBackend_Expecter) GetVPNConfig(uuidOrName interface{}) *MockBackend_GetVPNConfig_Call {
|
||||
return &MockBackend_GetVPNConfig_Call{Call: _e.mock.On("GetVPNConfig", uuidOrName)}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_GetVPNConfig_Call) Run(run func(uuidOrName string)) *MockBackend_GetVPNConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_GetVPNConfig_Call) Return(_a0 *network.VPNConfig, _a1 error) *MockBackend_GetVPNConfig_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_GetVPNConfig_Call) RunAndReturn(run func(string) (*network.VPNConfig, error)) *MockBackend_GetVPNConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
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()
|
||||
@@ -887,6 +1177,65 @@ func (_c *MockBackend_GetWiredNetworkDetails_Call) RunAndReturn(run func(string)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ImportVPN provides a mock function with given fields: filePath, name
|
||||
func (_m *MockBackend) ImportVPN(filePath string, name string) (*network.VPNImportResult, error) {
|
||||
ret := _m.Called(filePath, name)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ImportVPN")
|
||||
}
|
||||
|
||||
var r0 *network.VPNImportResult
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string, string) (*network.VPNImportResult, error)); ok {
|
||||
return rf(filePath, name)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string, string) *network.VPNImportResult); ok {
|
||||
r0 = rf(filePath, name)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*network.VPNImportResult)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string, string) error); ok {
|
||||
r1 = rf(filePath, name)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockBackend_ImportVPN_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ImportVPN'
|
||||
type MockBackend_ImportVPN_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ImportVPN is a helper method to define mock.On call
|
||||
// - filePath string
|
||||
// - name string
|
||||
func (_e *MockBackend_Expecter) ImportVPN(filePath interface{}, name interface{}) *MockBackend_ImportVPN_Call {
|
||||
return &MockBackend_ImportVPN_Call{Call: _e.mock.On("ImportVPN", filePath, name)}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_ImportVPN_Call) Run(run func(filePath string, name string)) *MockBackend_ImportVPN_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_ImportVPN_Call) Return(_a0 *network.VPNImportResult, _a1 error) *MockBackend_ImportVPN_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_ImportVPN_Call) RunAndReturn(run func(string, string) (*network.VPNImportResult, error)) *MockBackend_ImportVPN_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Initialize provides a mock function with no fields
|
||||
func (_m *MockBackend) Initialize() error {
|
||||
ret := _m.Called()
|
||||
@@ -989,6 +1338,63 @@ func (_c *MockBackend_ListActiveVPN_Call) RunAndReturn(run func() ([]network.VPN
|
||||
return _c
|
||||
}
|
||||
|
||||
// ListVPNPlugins provides a mock function with no fields
|
||||
func (_m *MockBackend) ListVPNPlugins() ([]network.VPNPlugin, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ListVPNPlugins")
|
||||
}
|
||||
|
||||
var r0 []network.VPNPlugin
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func() ([]network.VPNPlugin, error)); ok {
|
||||
return rf()
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func() []network.VPNPlugin); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]network.VPNPlugin)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockBackend_ListVPNPlugins_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListVPNPlugins'
|
||||
type MockBackend_ListVPNPlugins_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ListVPNPlugins is a helper method to define mock.On call
|
||||
func (_e *MockBackend_Expecter) ListVPNPlugins() *MockBackend_ListVPNPlugins_Call {
|
||||
return &MockBackend_ListVPNPlugins_Call{Call: _e.mock.On("ListVPNPlugins")}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_ListVPNPlugins_Call) Run(run func()) *MockBackend_ListVPNPlugins_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_ListVPNPlugins_Call) Return(_a0 []network.VPNPlugin, _a1 error) *MockBackend_ListVPNPlugins_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_ListVPNPlugins_Call) RunAndReturn(run func() ([]network.VPNPlugin, error)) *MockBackend_ListVPNPlugins_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ListVPNProfiles provides a mock function with no fields
|
||||
func (_m *MockBackend) ListVPNProfiles() ([]network.VPNProfile, error) {
|
||||
ret := _m.Called()
|
||||
@@ -1091,6 +1497,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)
|
||||
@@ -1137,6 +1589,55 @@ func (_c *MockBackend_SetPromptBroker_Call) RunAndReturn(run func(network.Prompt
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetVPNCredentials provides a mock function with given fields: uuid, username, password, save
|
||||
func (_m *MockBackend) SetVPNCredentials(uuid string, username string, password string, save bool) error {
|
||||
ret := _m.Called(uuid, username, password, save)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SetVPNCredentials")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, string, bool) error); ok {
|
||||
r0 = rf(uuid, username, password, save)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockBackend_SetVPNCredentials_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetVPNCredentials'
|
||||
type MockBackend_SetVPNCredentials_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// SetVPNCredentials is a helper method to define mock.On call
|
||||
// - uuid string
|
||||
// - username string
|
||||
// - password string
|
||||
// - save bool
|
||||
func (_e *MockBackend_Expecter) SetVPNCredentials(uuid interface{}, username interface{}, password interface{}, save interface{}) *MockBackend_SetVPNCredentials_Call {
|
||||
return &MockBackend_SetVPNCredentials_Call{Call: _e.mock.On("SetVPNCredentials", uuid, username, password, save)}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_SetVPNCredentials_Call) Run(run func(uuid string, username string, password string, save bool)) *MockBackend_SetVPNCredentials_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(string), args[2].(string), args[3].(bool))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_SetVPNCredentials_Call) Return(_a0 error) *MockBackend_SetVPNCredentials_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_SetVPNCredentials_Call) RunAndReturn(run func(string, string, string, bool) error) *MockBackend_SetVPNCredentials_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetWiFiAutoconnect provides a mock function with given fields: ssid, autoconnect
|
||||
func (_m *MockBackend) SetWiFiAutoconnect(ssid string, autoconnect bool) error {
|
||||
ret := _m.Called(ssid, autoconnect)
|
||||
@@ -1356,6 +1857,53 @@ func (_c *MockBackend_SubmitCredentials_Call) RunAndReturn(run func(string, map[
|
||||
return _c
|
||||
}
|
||||
|
||||
// UpdateVPNConfig provides a mock function with given fields: uuid, updates
|
||||
func (_m *MockBackend) UpdateVPNConfig(uuid string, updates map[string]interface{}) error {
|
||||
ret := _m.Called(uuid, updates)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for UpdateVPNConfig")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, map[string]interface{}) error); ok {
|
||||
r0 = rf(uuid, updates)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockBackend_UpdateVPNConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateVPNConfig'
|
||||
type MockBackend_UpdateVPNConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// UpdateVPNConfig is a helper method to define mock.On call
|
||||
// - uuid string
|
||||
// - updates map[string]interface{}
|
||||
func (_e *MockBackend_Expecter) UpdateVPNConfig(uuid interface{}, updates interface{}) *MockBackend_UpdateVPNConfig_Call {
|
||||
return &MockBackend_UpdateVPNConfig_Call{Call: _e.mock.On("UpdateVPNConfig", uuid, updates)}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_UpdateVPNConfig_Call) Run(run func(uuid string, updates map[string]interface{})) *MockBackend_UpdateVPNConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(map[string]interface{}))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_UpdateVPNConfig_Call) Return(_a0 error) *MockBackend_UpdateVPNConfig_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_UpdateVPNConfig_Call) RunAndReturn(run func(string, map[string]interface{}) error) *MockBackend_UpdateVPNConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockBackend creates a new instance of MockBackend. 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 NewMockBackend(t interface {
|
||||
|
||||
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
|
||||
}
|
||||
@@ -93,7 +93,7 @@ func (m *Manager) Install(plugin Plugin) error {
|
||||
|
||||
if !repoExists {
|
||||
if err := m.gitClient.PlainClone(repoPath, plugin.Repo); err != nil {
|
||||
m.fs.RemoveAll(repoPath)
|
||||
m.fs.RemoveAll(repoPath) //nolint:errcheck
|
||||
return fmt.Errorf("failed to clone repository: %w", err)
|
||||
}
|
||||
} else {
|
||||
@@ -130,7 +130,7 @@ func (m *Manager) Install(plugin Plugin) error {
|
||||
}
|
||||
} else {
|
||||
if err := m.gitClient.PlainClone(pluginPath, plugin.Repo); err != nil {
|
||||
m.fs.RemoveAll(pluginPath)
|
||||
m.fs.RemoveAll(pluginPath) //nolint:errcheck
|
||||
return fmt.Errorf("failed to clone plugin: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ func (b *DDCBackend) probeDDCDevice(bus int) (*ddcDevice, error) {
|
||||
}
|
||||
|
||||
dummy := make([]byte, 32)
|
||||
syscall.Read(fd, dummy)
|
||||
syscall.Read(fd, dummy) //nolint:errcheck
|
||||
|
||||
writebuf := []byte{0x00}
|
||||
n, err := syscall.Write(fd, writebuf)
|
||||
|
||||
@@ -14,7 +14,6 @@ type DBusConn interface {
|
||||
|
||||
type LogindBackend struct {
|
||||
conn DBusConn
|
||||
connOnce bool
|
||||
}
|
||||
|
||||
func NewLogindBackend() (*LogindBackend, error) {
|
||||
|
||||
@@ -1,12 +1,34 @@
|
||||
package cups
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/config"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/pkg/ipp"
|
||||
)
|
||||
|
||||
func isAuthError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var httpErr ipp.HTTPError
|
||||
if errors.As(err, &httpErr) {
|
||||
return httpErr.Code == 401 || httpErr.Code == 403
|
||||
}
|
||||
|
||||
var ippErr ipp.IPPError
|
||||
if errors.As(err, &ippErr) {
|
||||
return ippErr.Status == ipp.StatusErrorForbidden ||
|
||||
ippErr.Status == ipp.StatusErrorNotAuthenticated ||
|
||||
ippErr.Status == ipp.StatusErrorNotAuthorized
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *Manager) GetPrinters() ([]Printer, error) {
|
||||
attributes := []string{
|
||||
ipp.AttributePrinterName,
|
||||
@@ -21,6 +43,9 @@ func (m *Manager) GetPrinters() ([]Printer, error) {
|
||||
|
||||
printerAttrs, err := m.client.GetPrinters(attributes)
|
||||
if err != nil {
|
||||
if isNoPrintersError(err) {
|
||||
return []Printer{}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -91,17 +116,289 @@ func (m *Manager) GetJobs(printerName string, whichJobs string) ([]Job, error) {
|
||||
}
|
||||
|
||||
func (m *Manager) CancelJob(jobID int) error {
|
||||
return m.client.CancelJob(jobID, false)
|
||||
err := m.client.CancelJob(jobID, false)
|
||||
if isAuthError(err) && m.pkHelper != nil {
|
||||
err = m.pkHelper.JobCancelPurge(jobID, false)
|
||||
}
|
||||
if err == nil {
|
||||
m.RefreshState()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Manager) PausePrinter(printerName string) error {
|
||||
return m.client.PausePrinter(printerName)
|
||||
err := m.client.PausePrinter(printerName)
|
||||
if isAuthError(err) && m.pkHelper != nil {
|
||||
err = m.pkHelper.PrinterSetEnabled(printerName, false)
|
||||
}
|
||||
if err == nil {
|
||||
m.RefreshState()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Manager) ResumePrinter(printerName string) error {
|
||||
return m.client.ResumePrinter(printerName)
|
||||
err := m.client.ResumePrinter(printerName)
|
||||
if isAuthError(err) && m.pkHelper != nil {
|
||||
err = m.pkHelper.PrinterSetEnabled(printerName, true)
|
||||
}
|
||||
if err == nil {
|
||||
m.RefreshState()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Manager) PurgeJobs(printerName string) error {
|
||||
return m.client.CancelAllJob(printerName, true)
|
||||
err := m.client.CancelAllJob(printerName, true)
|
||||
if err == nil {
|
||||
m.RefreshState()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Manager) GetDevices() ([]Device, error) {
|
||||
if m.pkHelper != nil {
|
||||
return m.pkHelper.DevicesGet(10, 0, nil, nil)
|
||||
}
|
||||
|
||||
deviceAttrs, err := m.client.GetDevices()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
devices := make([]Device, 0, len(deviceAttrs))
|
||||
for uri, attrs := range deviceAttrs {
|
||||
device := Device{
|
||||
URI: uri,
|
||||
Class: getStringAttr(attrs, "device-class"),
|
||||
Info: getStringAttr(attrs, "device-info"),
|
||||
MakeModel: getStringAttr(attrs, "device-make-and-model"),
|
||||
ID: getStringAttr(attrs, "device-id"),
|
||||
Location: getStringAttr(attrs, "device-location"),
|
||||
}
|
||||
devices = append(devices, device)
|
||||
}
|
||||
|
||||
return devices, nil
|
||||
}
|
||||
|
||||
func (m *Manager) GetPPDs() ([]PPD, error) {
|
||||
ppdAttrs, err := m.client.GetPPDs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ppds := make([]PPD, 0, len(ppdAttrs))
|
||||
for name, attrs := range ppdAttrs {
|
||||
ppd := PPD{
|
||||
Name: name,
|
||||
NaturalLanguage: getStringAttr(attrs, "ppd-natural-language"),
|
||||
MakeModel: getStringAttr(attrs, ipp.AttributePPDMakeAndModel),
|
||||
DeviceID: getStringAttr(attrs, "ppd-device-id"),
|
||||
Product: getStringAttr(attrs, "ppd-product"),
|
||||
PSVersion: getStringAttr(attrs, "ppd-psversion"),
|
||||
Type: getStringAttr(attrs, "ppd-type"),
|
||||
}
|
||||
ppds = append(ppds, ppd)
|
||||
}
|
||||
|
||||
return ppds, nil
|
||||
}
|
||||
|
||||
func (m *Manager) GetClasses() ([]PrinterClass, error) {
|
||||
attributes := []string{
|
||||
ipp.AttributePrinterName,
|
||||
ipp.AttributePrinterUriSupported,
|
||||
ipp.AttributePrinterState,
|
||||
ipp.AttributeMemberURIs,
|
||||
ipp.AttributeMemberNames,
|
||||
ipp.AttributePrinterLocation,
|
||||
ipp.AttributePrinterInfo,
|
||||
}
|
||||
|
||||
classAttrs, err := m.client.GetClasses(attributes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
classes := make([]PrinterClass, 0, len(classAttrs))
|
||||
for _, attrs := range classAttrs {
|
||||
class := PrinterClass{
|
||||
Name: getStringAttr(attrs, ipp.AttributePrinterName),
|
||||
URI: getStringAttr(attrs, ipp.AttributePrinterUriSupported),
|
||||
State: parsePrinterState(attrs),
|
||||
Location: getStringAttr(attrs, ipp.AttributePrinterLocation),
|
||||
Info: getStringAttr(attrs, ipp.AttributePrinterInfo),
|
||||
Members: getStringSliceAttr(attrs, ipp.AttributeMemberNames),
|
||||
}
|
||||
classes = append(classes, class)
|
||||
}
|
||||
|
||||
return classes, nil
|
||||
}
|
||||
|
||||
func (m *Manager) CreatePrinter(name, deviceURI, ppd string, shared bool, errorPolicy, information, location string) error {
|
||||
usedPkHelper := false
|
||||
|
||||
err := m.client.CreatePrinter(name, deviceURI, ppd, shared, errorPolicy, information, location)
|
||||
if isAuthError(err) && m.pkHelper != nil {
|
||||
if err = m.pkHelper.PrinterAdd(name, deviceURI, ppd, information, location); err != nil {
|
||||
return err
|
||||
}
|
||||
usedPkHelper = true
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if usedPkHelper {
|
||||
m.pkHelper.PrinterSetEnabled(name, true) //nolint:errcheck
|
||||
m.pkHelper.PrinterSetAcceptJobs(name, true, "") //nolint:errcheck
|
||||
} else {
|
||||
if err := m.client.ResumePrinter(name); isAuthError(err) && m.pkHelper != nil {
|
||||
m.pkHelper.PrinterSetEnabled(name, true) //nolint:errcheck
|
||||
}
|
||||
if err := m.client.AcceptJobs(name); isAuthError(err) && m.pkHelper != nil {
|
||||
m.pkHelper.PrinterSetAcceptJobs(name, true, "") //nolint:errcheck
|
||||
}
|
||||
}
|
||||
|
||||
m.RefreshState()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) DeletePrinter(printerName string) error {
|
||||
err := m.client.DeletePrinter(printerName)
|
||||
if isAuthError(err) && m.pkHelper != nil {
|
||||
err = m.pkHelper.PrinterDelete(printerName)
|
||||
}
|
||||
if err == nil {
|
||||
m.RefreshState()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Manager) AcceptJobs(printerName string) error {
|
||||
err := m.client.AcceptJobs(printerName)
|
||||
if isAuthError(err) && m.pkHelper != nil {
|
||||
err = m.pkHelper.PrinterSetAcceptJobs(printerName, true, "")
|
||||
}
|
||||
if err == nil {
|
||||
m.RefreshState()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Manager) RejectJobs(printerName string) error {
|
||||
err := m.client.RejectJobs(printerName)
|
||||
if isAuthError(err) && m.pkHelper != nil {
|
||||
err = m.pkHelper.PrinterSetAcceptJobs(printerName, false, "")
|
||||
}
|
||||
if err == nil {
|
||||
m.RefreshState()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Manager) SetPrinterShared(printerName string, shared bool) error {
|
||||
err := m.client.SetPrinterIsShared(printerName, shared)
|
||||
if isAuthError(err) && m.pkHelper != nil {
|
||||
err = m.pkHelper.PrinterSetShared(printerName, shared)
|
||||
}
|
||||
if err == nil {
|
||||
m.RefreshState()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Manager) SetPrinterLocation(printerName, location string) error {
|
||||
err := m.client.SetPrinterLocation(printerName, location)
|
||||
if isAuthError(err) && m.pkHelper != nil {
|
||||
err = m.pkHelper.PrinterSetLocation(printerName, location)
|
||||
}
|
||||
if err == nil {
|
||||
m.RefreshState()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Manager) SetPrinterInfo(printerName, info string) error {
|
||||
err := m.client.SetPrinterInformation(printerName, info)
|
||||
if isAuthError(err) && m.pkHelper != nil {
|
||||
err = m.pkHelper.PrinterSetInfo(printerName, info)
|
||||
}
|
||||
if err == nil {
|
||||
m.RefreshState()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Manager) MoveJob(jobID int, destPrinter string) error {
|
||||
err := m.client.MoveJob(jobID, destPrinter)
|
||||
if err == nil {
|
||||
m.RefreshState()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Manager) PrintTestPage(printerName string) (int, error) {
|
||||
jobID, err := m.client.PrintTestPage(printerName, strings.NewReader(config.TestPage), len(config.TestPage))
|
||||
if err == nil {
|
||||
m.RefreshState()
|
||||
}
|
||||
return jobID, err
|
||||
}
|
||||
|
||||
func (m *Manager) AddPrinterToClass(className, printerName string) error {
|
||||
err := m.client.AddPrinterToClass(className, printerName)
|
||||
if isAuthError(err) && m.pkHelper != nil {
|
||||
err = m.pkHelper.ClassAddPrinter(className, printerName)
|
||||
}
|
||||
if err == nil {
|
||||
m.RefreshState()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Manager) RemovePrinterFromClass(className, printerName string) error {
|
||||
err := m.client.DeletePrinterFromClass(className, printerName)
|
||||
if isAuthError(err) && m.pkHelper != nil {
|
||||
err = m.pkHelper.ClassDeletePrinter(className, printerName)
|
||||
}
|
||||
if err == nil {
|
||||
m.RefreshState()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Manager) DeleteClass(className string) error {
|
||||
err := m.client.DeleteClass(className)
|
||||
if isAuthError(err) && m.pkHelper != nil {
|
||||
err = m.pkHelper.ClassDelete(className)
|
||||
}
|
||||
if err == nil {
|
||||
m.RefreshState()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Manager) RestartJob(jobID int) error {
|
||||
err := m.client.RestartJob(jobID)
|
||||
if isAuthError(err) && m.pkHelper != nil {
|
||||
err = m.pkHelper.JobRestart(jobID)
|
||||
}
|
||||
if err == nil {
|
||||
m.RefreshState()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Manager) HoldJob(jobID int, holdUntil string) error {
|
||||
err := m.client.HoldJobUntil(jobID, holdUntil)
|
||||
if isAuthError(err) && m.pkHelper != nil {
|
||||
err = m.pkHelper.JobSetHoldUntil(jobID, holdUntil)
|
||||
}
|
||||
if err == nil {
|
||||
m.RefreshState()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
235
core/internal/server/cups/actions_pkhelper_test.go
Normal file
235
core/internal/server/cups/actions_pkhelper_test.go
Normal file
@@ -0,0 +1,235 @@
|
||||
package cups_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
mocks_cups "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/cups"
|
||||
mocks_pkhelper "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/cups_pkhelper"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/cups"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/pkg/ipp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
func authErr() error {
|
||||
return ipp.IPPError{Status: ipp.StatusErrorForbidden}
|
||||
}
|
||||
|
||||
func TestManager_CancelJob_WithPkHelper(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().CancelJob(1, false).Return(authErr())
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
mockPk := mocks_pkhelper.NewMockPkHelper(t)
|
||||
mockPk.EXPECT().JobCancelPurge(1, false).Return(nil)
|
||||
|
||||
m := cups.NewTestManager(mockClient, mockPk)
|
||||
assert.NoError(t, m.CancelJob(1))
|
||||
}
|
||||
|
||||
func TestManager_CancelJob_PkHelperError(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().CancelJob(1, false).Return(authErr())
|
||||
|
||||
mockPk := mocks_pkhelper.NewMockPkHelper(t)
|
||||
mockPk.EXPECT().JobCancelPurge(1, false).Return(assert.AnError)
|
||||
|
||||
m := cups.NewTestManager(mockClient, mockPk)
|
||||
assert.Error(t, m.CancelJob(1))
|
||||
}
|
||||
|
||||
func TestManager_PausePrinter_WithPkHelper(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().PausePrinter("printer1").Return(authErr())
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
mockPk := mocks_pkhelper.NewMockPkHelper(t)
|
||||
mockPk.EXPECT().PrinterSetEnabled("printer1", false).Return(nil)
|
||||
|
||||
m := cups.NewTestManager(mockClient, mockPk)
|
||||
assert.NoError(t, m.PausePrinter("printer1"))
|
||||
}
|
||||
|
||||
func TestManager_ResumePrinter_WithPkHelper(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().ResumePrinter("printer1").Return(authErr())
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
mockPk := mocks_pkhelper.NewMockPkHelper(t)
|
||||
mockPk.EXPECT().PrinterSetEnabled("printer1", true).Return(nil)
|
||||
|
||||
m := cups.NewTestManager(mockClient, mockPk)
|
||||
assert.NoError(t, m.ResumePrinter("printer1"))
|
||||
}
|
||||
|
||||
func TestManager_GetDevices_WithPkHelper(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
|
||||
mockPk := mocks_pkhelper.NewMockPkHelper(t)
|
||||
mockPk.EXPECT().DevicesGet(10, 0, []string(nil), []string(nil)).Return([]cups.Device{
|
||||
{URI: "usb://HP/LaserJet", Class: "direct"},
|
||||
}, nil)
|
||||
|
||||
m := cups.NewTestManager(mockClient, mockPk)
|
||||
got, err := m.GetDevices()
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, got, 1)
|
||||
assert.Equal(t, "usb://HP/LaserJet", got[0].URI)
|
||||
}
|
||||
|
||||
func TestManager_GetDevices_PkHelperError(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
|
||||
mockPk := mocks_pkhelper.NewMockPkHelper(t)
|
||||
mockPk.EXPECT().DevicesGet(10, 0, []string(nil), []string(nil)).Return(nil, assert.AnError)
|
||||
|
||||
m := cups.NewTestManager(mockClient, mockPk)
|
||||
_, err := m.GetDevices()
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestManager_CreatePrinter_WithPkHelper(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().CreatePrinter("newprinter", "usb://HP", "generic.ppd", true, "stop-printer", "info", "location").Return(authErr())
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
mockPk := mocks_pkhelper.NewMockPkHelper(t)
|
||||
mockPk.EXPECT().PrinterAdd("newprinter", "usb://HP", "generic.ppd", "info", "location").Return(nil)
|
||||
mockPk.EXPECT().PrinterSetEnabled("newprinter", true).Return(nil)
|
||||
mockPk.EXPECT().PrinterSetAcceptJobs("newprinter", true, "").Return(nil)
|
||||
|
||||
m := cups.NewTestManager(mockClient, mockPk)
|
||||
assert.NoError(t, m.CreatePrinter("newprinter", "usb://HP", "generic.ppd", true, "stop-printer", "info", "location"))
|
||||
}
|
||||
|
||||
func TestManager_DeletePrinter_WithPkHelper(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().DeletePrinter("printer1").Return(authErr())
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
mockPk := mocks_pkhelper.NewMockPkHelper(t)
|
||||
mockPk.EXPECT().PrinterDelete("printer1").Return(nil)
|
||||
|
||||
m := cups.NewTestManager(mockClient, mockPk)
|
||||
assert.NoError(t, m.DeletePrinter("printer1"))
|
||||
}
|
||||
|
||||
func TestManager_AcceptJobs_WithPkHelper(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().AcceptJobs("printer1").Return(authErr())
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
mockPk := mocks_pkhelper.NewMockPkHelper(t)
|
||||
mockPk.EXPECT().PrinterSetAcceptJobs("printer1", true, "").Return(nil)
|
||||
|
||||
m := cups.NewTestManager(mockClient, mockPk)
|
||||
assert.NoError(t, m.AcceptJobs("printer1"))
|
||||
}
|
||||
|
||||
func TestManager_RejectJobs_WithPkHelper(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().RejectJobs("printer1").Return(authErr())
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
mockPk := mocks_pkhelper.NewMockPkHelper(t)
|
||||
mockPk.EXPECT().PrinterSetAcceptJobs("printer1", false, "").Return(nil)
|
||||
|
||||
m := cups.NewTestManager(mockClient, mockPk)
|
||||
assert.NoError(t, m.RejectJobs("printer1"))
|
||||
}
|
||||
|
||||
func TestManager_SetPrinterShared_WithPkHelper(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().SetPrinterIsShared("printer1", true).Return(authErr())
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
mockPk := mocks_pkhelper.NewMockPkHelper(t)
|
||||
mockPk.EXPECT().PrinterSetShared("printer1", true).Return(nil)
|
||||
|
||||
m := cups.NewTestManager(mockClient, mockPk)
|
||||
assert.NoError(t, m.SetPrinterShared("printer1", true))
|
||||
}
|
||||
|
||||
func TestManager_SetPrinterLocation_WithPkHelper(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().SetPrinterLocation("printer1", "Office").Return(authErr())
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
mockPk := mocks_pkhelper.NewMockPkHelper(t)
|
||||
mockPk.EXPECT().PrinterSetLocation("printer1", "Office").Return(nil)
|
||||
|
||||
m := cups.NewTestManager(mockClient, mockPk)
|
||||
assert.NoError(t, m.SetPrinterLocation("printer1", "Office"))
|
||||
}
|
||||
|
||||
func TestManager_SetPrinterInfo_WithPkHelper(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().SetPrinterInformation("printer1", "Main Printer").Return(authErr())
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
mockPk := mocks_pkhelper.NewMockPkHelper(t)
|
||||
mockPk.EXPECT().PrinterSetInfo("printer1", "Main Printer").Return(nil)
|
||||
|
||||
m := cups.NewTestManager(mockClient, mockPk)
|
||||
assert.NoError(t, m.SetPrinterInfo("printer1", "Main Printer"))
|
||||
}
|
||||
|
||||
func TestManager_AddPrinterToClass_WithPkHelper(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().AddPrinterToClass("office", "printer1").Return(authErr())
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
mockPk := mocks_pkhelper.NewMockPkHelper(t)
|
||||
mockPk.EXPECT().ClassAddPrinter("office", "printer1").Return(nil)
|
||||
|
||||
m := cups.NewTestManager(mockClient, mockPk)
|
||||
assert.NoError(t, m.AddPrinterToClass("office", "printer1"))
|
||||
}
|
||||
|
||||
func TestManager_RemovePrinterFromClass_WithPkHelper(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().DeletePrinterFromClass("office", "printer1").Return(authErr())
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
mockPk := mocks_pkhelper.NewMockPkHelper(t)
|
||||
mockPk.EXPECT().ClassDeletePrinter("office", "printer1").Return(nil)
|
||||
|
||||
m := cups.NewTestManager(mockClient, mockPk)
|
||||
assert.NoError(t, m.RemovePrinterFromClass("office", "printer1"))
|
||||
}
|
||||
|
||||
func TestManager_DeleteClass_WithPkHelper(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().DeleteClass("office").Return(authErr())
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
mockPk := mocks_pkhelper.NewMockPkHelper(t)
|
||||
mockPk.EXPECT().ClassDelete("office").Return(nil)
|
||||
|
||||
m := cups.NewTestManager(mockClient, mockPk)
|
||||
assert.NoError(t, m.DeleteClass("office"))
|
||||
}
|
||||
|
||||
func TestManager_RestartJob_WithPkHelper(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().RestartJob(1).Return(authErr())
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
mockPk := mocks_pkhelper.NewMockPkHelper(t)
|
||||
mockPk.EXPECT().JobRestart(1).Return(nil)
|
||||
|
||||
m := cups.NewTestManager(mockClient, mockPk)
|
||||
assert.NoError(t, m.RestartJob(1))
|
||||
}
|
||||
|
||||
func TestManager_HoldJob_WithPkHelper(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().HoldJobUntil(1, "indefinite").Return(authErr())
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
mockPk := mocks_pkhelper.NewMockPkHelper(t)
|
||||
mockPk.EXPECT().JobSetHoldUntil(1, "indefinite").Return(nil)
|
||||
|
||||
m := cups.NewTestManager(mockClient, mockPk)
|
||||
assert.NoError(t, m.HoldJob(1, "indefinite"))
|
||||
}
|
||||
@@ -137,114 +137,30 @@ func TestManager_GetJobs(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestManager_CancelJob(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
mockErr error
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
mockErr: nil,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "error",
|
||||
mockErr: errors.New("test error"),
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().CancelJob(1, false).Return(tt.mockErr)
|
||||
mockClient.EXPECT().CancelJob(1, false).Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := &Manager{
|
||||
client: mockClient,
|
||||
}
|
||||
|
||||
err := m.CancelJob(1)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
m := NewTestManager(mockClient, nil)
|
||||
assert.NoError(t, m.CancelJob(1))
|
||||
}
|
||||
|
||||
func TestManager_PausePrinter(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
mockErr error
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
mockErr: nil,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "error",
|
||||
mockErr: errors.New("test error"),
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().PausePrinter("printer1").Return(tt.mockErr)
|
||||
mockClient.EXPECT().PausePrinter("printer1").Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := &Manager{
|
||||
client: mockClient,
|
||||
}
|
||||
|
||||
err := m.PausePrinter("printer1")
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
m := NewTestManager(mockClient, nil)
|
||||
assert.NoError(t, m.PausePrinter("printer1"))
|
||||
}
|
||||
|
||||
func TestManager_ResumePrinter(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
mockErr error
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
mockErr: nil,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "error",
|
||||
mockErr: errors.New("test error"),
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().ResumePrinter("printer1").Return(tt.mockErr)
|
||||
mockClient.EXPECT().ResumePrinter("printer1").Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := &Manager{
|
||||
client: mockClient,
|
||||
}
|
||||
|
||||
err := m.ResumePrinter("printer1")
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
m := NewTestManager(mockClient, nil)
|
||||
assert.NoError(t, m.ResumePrinter("printer1"))
|
||||
}
|
||||
|
||||
func TestManager_PurgeJobs(t *testing.T) {
|
||||
@@ -269,11 +185,12 @@ func TestManager_PurgeJobs(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().CancelAllJob("printer1", true).Return(tt.mockErr)
|
||||
|
||||
m := &Manager{
|
||||
client: mockClient,
|
||||
if !tt.wantErr {
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
}
|
||||
|
||||
m := NewTestManager(mockClient, nil)
|
||||
|
||||
err := m.PurgeJobs("printer1")
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
@@ -283,3 +200,251 @@ func TestManager_PurgeJobs(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestManager_GetDevices(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().GetDevices().Return(map[string]ipp.Attributes{
|
||||
"usb://HP/LaserJet": {
|
||||
"device-class": []ipp.Attribute{{Value: "direct"}},
|
||||
"device-info": []ipp.Attribute{{Value: "HP LaserJet"}},
|
||||
"device-make-and-model": []ipp.Attribute{{Value: "HP LaserJet 1020"}},
|
||||
},
|
||||
}, nil)
|
||||
|
||||
m := &Manager{client: mockClient}
|
||||
got, err := m.GetDevices()
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, got, 1)
|
||||
assert.Equal(t, "usb://HP/LaserJet", got[0].URI)
|
||||
assert.Equal(t, "direct", got[0].Class)
|
||||
}
|
||||
|
||||
func TestManager_GetPPDs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
mockRet map[string]ipp.Attributes
|
||||
mockErr error
|
||||
want int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
mockRet: map[string]ipp.Attributes{
|
||||
"drv:///sample.drv/generic.ppd": {
|
||||
"ppd-make-and-model": []ipp.Attribute{{Value: "Generic PostScript"}},
|
||||
"ppd-type": []ipp.Attribute{{Value: "ppd"}},
|
||||
},
|
||||
},
|
||||
mockErr: nil,
|
||||
want: 1,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "error",
|
||||
mockRet: nil,
|
||||
mockErr: errors.New("test error"),
|
||||
want: 0,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().GetPPDs().Return(tt.mockRet, tt.mockErr)
|
||||
|
||||
m := &Manager{client: mockClient}
|
||||
|
||||
got, err := m.GetPPDs()
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, len(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestManager_GetClasses(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
mockRet map[string]ipp.Attributes
|
||||
mockErr error
|
||||
want int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
mockRet: map[string]ipp.Attributes{
|
||||
"office": {
|
||||
ipp.AttributePrinterName: []ipp.Attribute{{Value: "office"}},
|
||||
ipp.AttributePrinterState: []ipp.Attribute{{Value: 3}},
|
||||
ipp.AttributeMemberNames: []ipp.Attribute{{Value: "printer1"}, {Value: "printer2"}},
|
||||
},
|
||||
},
|
||||
mockErr: nil,
|
||||
want: 1,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "error",
|
||||
mockRet: nil,
|
||||
mockErr: errors.New("test error"),
|
||||
want: 0,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().GetClasses(mock.Anything).Return(tt.mockRet, tt.mockErr)
|
||||
|
||||
m := &Manager{client: mockClient}
|
||||
|
||||
got, err := m.GetClasses()
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, len(got))
|
||||
if len(got) > 0 {
|
||||
assert.Equal(t, "office", got[0].Name)
|
||||
assert.Equal(t, 2, len(got[0].Members))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestManager_CreatePrinter(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().CreatePrinter("newprinter", "usb://HP", "generic.ppd", true, "stop-printer", "info", "location").Return(nil)
|
||||
mockClient.EXPECT().ResumePrinter("newprinter").Return(nil)
|
||||
mockClient.EXPECT().AcceptJobs("newprinter").Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := NewTestManager(mockClient, nil)
|
||||
assert.NoError(t, m.CreatePrinter("newprinter", "usb://HP", "generic.ppd", true, "stop-printer", "info", "location"))
|
||||
}
|
||||
|
||||
func TestManager_DeletePrinter(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().DeletePrinter("printer1").Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := NewTestManager(mockClient, nil)
|
||||
assert.NoError(t, m.DeletePrinter("printer1"))
|
||||
}
|
||||
|
||||
func TestManager_AcceptJobs(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().AcceptJobs("printer1").Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := NewTestManager(mockClient, nil)
|
||||
assert.NoError(t, m.AcceptJobs("printer1"))
|
||||
}
|
||||
|
||||
func TestManager_RejectJobs(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().RejectJobs("printer1").Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := NewTestManager(mockClient, nil)
|
||||
assert.NoError(t, m.RejectJobs("printer1"))
|
||||
}
|
||||
|
||||
func TestManager_SetPrinterShared(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().SetPrinterIsShared("printer1", true).Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := NewTestManager(mockClient, nil)
|
||||
assert.NoError(t, m.SetPrinterShared("printer1", true))
|
||||
}
|
||||
|
||||
func TestManager_SetPrinterLocation(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().SetPrinterLocation("printer1", "Office").Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := NewTestManager(mockClient, nil)
|
||||
assert.NoError(t, m.SetPrinterLocation("printer1", "Office"))
|
||||
}
|
||||
|
||||
func TestManager_SetPrinterInfo(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().SetPrinterInformation("printer1", "Main Printer").Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := NewTestManager(mockClient, nil)
|
||||
assert.NoError(t, m.SetPrinterInfo("printer1", "Main Printer"))
|
||||
}
|
||||
|
||||
func TestManager_MoveJob(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().MoveJob(1, "printer2").Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := NewTestManager(mockClient, nil)
|
||||
err := m.MoveJob(1, "printer2")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestManager_PrintTestPage(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().PrintTestPage("printer1", mock.Anything, mock.Anything).Return(42, nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := NewTestManager(mockClient, nil)
|
||||
jobID, err := m.PrintTestPage("printer1")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 42, jobID)
|
||||
}
|
||||
|
||||
func TestManager_AddPrinterToClass(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().AddPrinterToClass("office", "printer1").Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := NewTestManager(mockClient, nil)
|
||||
assert.NoError(t, m.AddPrinterToClass("office", "printer1"))
|
||||
}
|
||||
|
||||
func TestManager_RemovePrinterFromClass(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().DeletePrinterFromClass("office", "printer1").Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := NewTestManager(mockClient, nil)
|
||||
assert.NoError(t, m.RemovePrinterFromClass("office", "printer1"))
|
||||
}
|
||||
|
||||
func TestManager_DeleteClass(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().DeleteClass("office").Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := NewTestManager(mockClient, nil)
|
||||
assert.NoError(t, m.DeleteClass("office"))
|
||||
}
|
||||
|
||||
func TestManager_RestartJob(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().RestartJob(1).Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := NewTestManager(mockClient, nil)
|
||||
assert.NoError(t, m.RestartJob(1))
|
||||
}
|
||||
|
||||
func TestManager_HoldJob(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().HoldJobUntil(1, "indefinite").Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := NewTestManager(mockClient, nil)
|
||||
assert.NoError(t, m.HoldJob(1, "indefinite"))
|
||||
}
|
||||
|
||||
@@ -40,6 +40,40 @@ func HandleRequest(conn net.Conn, req Request, manager *Manager) {
|
||||
handleCancelJob(conn, req, manager)
|
||||
case "cups.purgeJobs":
|
||||
handlePurgeJobs(conn, req, manager)
|
||||
case "cups.getDevices":
|
||||
handleGetDevices(conn, req, manager)
|
||||
case "cups.getPPDs":
|
||||
handleGetPPDs(conn, req, manager)
|
||||
case "cups.getClasses":
|
||||
handleGetClasses(conn, req, manager)
|
||||
case "cups.createPrinter":
|
||||
handleCreatePrinter(conn, req, manager)
|
||||
case "cups.deletePrinter":
|
||||
handleDeletePrinter(conn, req, manager)
|
||||
case "cups.acceptJobs":
|
||||
handleAcceptJobs(conn, req, manager)
|
||||
case "cups.rejectJobs":
|
||||
handleRejectJobs(conn, req, manager)
|
||||
case "cups.setPrinterShared":
|
||||
handleSetPrinterShared(conn, req, manager)
|
||||
case "cups.setPrinterLocation":
|
||||
handleSetPrinterLocation(conn, req, manager)
|
||||
case "cups.setPrinterInfo":
|
||||
handleSetPrinterInfo(conn, req, manager)
|
||||
case "cups.moveJob":
|
||||
handleMoveJob(conn, req, manager)
|
||||
case "cups.printTestPage":
|
||||
handlePrintTestPage(conn, req, manager)
|
||||
case "cups.addPrinterToClass":
|
||||
handleAddPrinterToClass(conn, req, manager)
|
||||
case "cups.removePrinterFromClass":
|
||||
handleRemovePrinterFromClass(conn, req, manager)
|
||||
case "cups.deleteClass":
|
||||
handleDeleteClass(conn, req, manager)
|
||||
case "cups.restartJob":
|
||||
handleRestartJob(conn, req, manager)
|
||||
case "cups.holdJob":
|
||||
handleHoldJob(conn, req, manager)
|
||||
default:
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
|
||||
}
|
||||
@@ -158,3 +192,291 @@ func handleSubscribe(conn net.Conn, req Request, manager *Manager) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleGetDevices(conn net.Conn, req Request, manager *Manager) {
|
||||
devices, err := manager.GetDevices()
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
models.Respond(conn, req.ID, devices)
|
||||
}
|
||||
|
||||
func handleGetPPDs(conn net.Conn, req Request, manager *Manager) {
|
||||
ppds, err := manager.GetPPDs()
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
models.Respond(conn, req.ID, ppds)
|
||||
}
|
||||
|
||||
func handleGetClasses(conn net.Conn, req Request, manager *Manager) {
|
||||
classes, err := manager.GetClasses()
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
models.Respond(conn, req.ID, classes)
|
||||
}
|
||||
|
||||
func handleCreatePrinter(conn net.Conn, req Request, manager *Manager) {
|
||||
name, ok := req.Params["name"].(string)
|
||||
if !ok || name == "" {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'name' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
deviceURI, ok := req.Params["deviceURI"].(string)
|
||||
if !ok || deviceURI == "" {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'deviceURI' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
ppd, ok := req.Params["ppd"].(string)
|
||||
if !ok || ppd == "" {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'ppd' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
shared, _ := req.Params["shared"].(bool)
|
||||
errorPolicy, _ := req.Params["errorPolicy"].(string)
|
||||
information, _ := req.Params["information"].(string)
|
||||
location, _ := req.Params["location"].(string)
|
||||
|
||||
if err := manager.CreatePrinter(name, deviceURI, ppd, shared, errorPolicy, information, location); err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "printer created"})
|
||||
}
|
||||
|
||||
func handleDeletePrinter(conn net.Conn, req Request, manager *Manager) {
|
||||
printerName, ok := req.Params["printerName"].(string)
|
||||
if !ok || printerName == "" {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'printerName' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
if err := manager.DeletePrinter(printerName); err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "printer deleted"})
|
||||
}
|
||||
|
||||
func handleAcceptJobs(conn net.Conn, req Request, manager *Manager) {
|
||||
printerName, ok := req.Params["printerName"].(string)
|
||||
if !ok || printerName == "" {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'printerName' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
if err := manager.AcceptJobs(printerName); err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "accepting jobs"})
|
||||
}
|
||||
|
||||
func handleRejectJobs(conn net.Conn, req Request, manager *Manager) {
|
||||
printerName, ok := req.Params["printerName"].(string)
|
||||
if !ok || printerName == "" {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'printerName' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
if err := manager.RejectJobs(printerName); err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "rejecting jobs"})
|
||||
}
|
||||
|
||||
func handleSetPrinterShared(conn net.Conn, req Request, manager *Manager) {
|
||||
printerName, ok := req.Params["printerName"].(string)
|
||||
if !ok || printerName == "" {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'printerName' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
shared, ok := req.Params["shared"].(bool)
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'shared' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
if err := manager.SetPrinterShared(printerName, shared); err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "sharing updated"})
|
||||
}
|
||||
|
||||
func handleSetPrinterLocation(conn net.Conn, req Request, manager *Manager) {
|
||||
printerName, ok := req.Params["printerName"].(string)
|
||||
if !ok || printerName == "" {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'printerName' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
location, ok := req.Params["location"].(string)
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'location' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
if err := manager.SetPrinterLocation(printerName, location); err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "location updated"})
|
||||
}
|
||||
|
||||
func handleSetPrinterInfo(conn net.Conn, req Request, manager *Manager) {
|
||||
printerName, ok := req.Params["printerName"].(string)
|
||||
if !ok || printerName == "" {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'printerName' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
info, ok := req.Params["info"].(string)
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'info' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
if err := manager.SetPrinterInfo(printerName, info); err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "info updated"})
|
||||
}
|
||||
|
||||
func handleMoveJob(conn net.Conn, req Request, manager *Manager) {
|
||||
jobIDFloat, ok := req.Params["jobID"].(float64)
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'jobID' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
destPrinter, ok := req.Params["destPrinter"].(string)
|
||||
if !ok || destPrinter == "" {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'destPrinter' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
if err := manager.MoveJob(int(jobIDFloat), destPrinter); err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "job moved"})
|
||||
}
|
||||
|
||||
type TestPageResult struct {
|
||||
Success bool `json:"success"`
|
||||
JobID int `json:"jobId"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func handlePrintTestPage(conn net.Conn, req Request, manager *Manager) {
|
||||
printerName, ok := req.Params["printerName"].(string)
|
||||
if !ok || printerName == "" {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'printerName' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
jobID, err := manager.PrintTestPage(printerName)
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
models.Respond(conn, req.ID, TestPageResult{Success: true, JobID: jobID, Message: "test page queued"})
|
||||
}
|
||||
|
||||
func handleAddPrinterToClass(conn net.Conn, req Request, manager *Manager) {
|
||||
className, ok := req.Params["className"].(string)
|
||||
if !ok || className == "" {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'className' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
printerName, ok := req.Params["printerName"].(string)
|
||||
if !ok || printerName == "" {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'printerName' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
if err := manager.AddPrinterToClass(className, printerName); err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "printer added to class"})
|
||||
}
|
||||
|
||||
func handleRemovePrinterFromClass(conn net.Conn, req Request, manager *Manager) {
|
||||
className, ok := req.Params["className"].(string)
|
||||
if !ok || className == "" {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'className' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
printerName, ok := req.Params["printerName"].(string)
|
||||
if !ok || printerName == "" {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'printerName' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
if err := manager.RemovePrinterFromClass(className, printerName); err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "printer removed from class"})
|
||||
}
|
||||
|
||||
func handleDeleteClass(conn net.Conn, req Request, manager *Manager) {
|
||||
className, ok := req.Params["className"].(string)
|
||||
if !ok || className == "" {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'className' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
if err := manager.DeleteClass(className); err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "class deleted"})
|
||||
}
|
||||
|
||||
func handleRestartJob(conn net.Conn, req Request, manager *Manager) {
|
||||
jobIDFloat, ok := req.Params["jobID"].(float64)
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'jobID' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
if err := manager.RestartJob(int(jobIDFloat)); err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "job restarted"})
|
||||
}
|
||||
|
||||
func handleHoldJob(conn net.Conn, req Request, manager *Manager) {
|
||||
jobIDFloat, ok := req.Params["jobID"].(float64)
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing or invalid 'jobID' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
holdUntil, _ := req.Params["holdUntil"].(string)
|
||||
if holdUntil == "" {
|
||||
holdUntil = "indefinite"
|
||||
}
|
||||
|
||||
if err := manager.HoldJob(int(jobIDFloat), holdUntil); err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "job held"})
|
||||
}
|
||||
|
||||
@@ -145,10 +145,9 @@ func TestHandleGetJobs_MissingParam(t *testing.T) {
|
||||
func TestHandlePausePrinter(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().PausePrinter("printer1").Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := &Manager{
|
||||
client: mockClient,
|
||||
}
|
||||
m := NewTestManager(mockClient, nil)
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
conn := &mockConn{Buffer: buf}
|
||||
@@ -173,10 +172,9 @@ func TestHandlePausePrinter(t *testing.T) {
|
||||
func TestHandleResumePrinter(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().ResumePrinter("printer1").Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := &Manager{
|
||||
client: mockClient,
|
||||
}
|
||||
m := NewTestManager(mockClient, nil)
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
conn := &mockConn{Buffer: buf}
|
||||
@@ -201,10 +199,9 @@ func TestHandleResumePrinter(t *testing.T) {
|
||||
func TestHandleCancelJob(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().CancelJob(1, false).Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := &Manager{
|
||||
client: mockClient,
|
||||
}
|
||||
m := NewTestManager(mockClient, nil)
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
conn := &mockConn{Buffer: buf}
|
||||
@@ -229,10 +226,9 @@ func TestHandleCancelJob(t *testing.T) {
|
||||
func TestHandlePurgeJobs(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().CancelAllJob("printer1", true).Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := &Manager{
|
||||
client: mockClient,
|
||||
}
|
||||
m := NewTestManager(mockClient, nil)
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
conn := &mockConn{Buffer: buf}
|
||||
@@ -277,3 +273,439 @@ func TestHandleRequest_UnknownMethod(t *testing.T) {
|
||||
assert.Nil(t, resp.Result)
|
||||
assert.NotNil(t, resp.Error)
|
||||
}
|
||||
|
||||
func TestHandleGetDevices(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().GetDevices().Return(map[string]ipp.Attributes{
|
||||
"usb://HP/LaserJet": {
|
||||
"device-class": []ipp.Attribute{{Value: "direct"}},
|
||||
"device-info": []ipp.Attribute{{Value: "HP LaserJet"}},
|
||||
},
|
||||
}, nil)
|
||||
|
||||
m := &Manager{client: mockClient}
|
||||
buf := &bytes.Buffer{}
|
||||
conn := &mockConn{Buffer: buf}
|
||||
|
||||
req := Request{ID: 1, Method: "cups.getDevices"}
|
||||
handleGetDevices(conn, req, m)
|
||||
|
||||
var resp models.Response[[]Device]
|
||||
err := json.NewDecoder(buf).Decode(&resp)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, resp.Result)
|
||||
assert.Equal(t, 1, len(*resp.Result))
|
||||
}
|
||||
|
||||
func TestHandleGetPPDs(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().GetPPDs().Return(map[string]ipp.Attributes{
|
||||
"generic.ppd": {
|
||||
"ppd-make-and-model": []ipp.Attribute{{Value: "Generic"}},
|
||||
},
|
||||
}, nil)
|
||||
|
||||
m := &Manager{client: mockClient}
|
||||
buf := &bytes.Buffer{}
|
||||
conn := &mockConn{Buffer: buf}
|
||||
|
||||
req := Request{ID: 1, Method: "cups.getPPDs"}
|
||||
handleGetPPDs(conn, req, m)
|
||||
|
||||
var resp models.Response[[]PPD]
|
||||
err := json.NewDecoder(buf).Decode(&resp)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, resp.Result)
|
||||
assert.Equal(t, 1, len(*resp.Result))
|
||||
}
|
||||
|
||||
func TestHandleGetClasses(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().GetClasses(mock.Anything).Return(map[string]ipp.Attributes{
|
||||
"office": {
|
||||
ipp.AttributePrinterName: []ipp.Attribute{{Value: "office"}},
|
||||
ipp.AttributePrinterState: []ipp.Attribute{{Value: 3}},
|
||||
},
|
||||
}, nil)
|
||||
|
||||
m := &Manager{client: mockClient}
|
||||
buf := &bytes.Buffer{}
|
||||
conn := &mockConn{Buffer: buf}
|
||||
|
||||
req := Request{ID: 1, Method: "cups.getClasses"}
|
||||
handleGetClasses(conn, req, m)
|
||||
|
||||
var resp models.Response[[]PrinterClass]
|
||||
err := json.NewDecoder(buf).Decode(&resp)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, resp.Result)
|
||||
assert.Equal(t, 1, len(*resp.Result))
|
||||
}
|
||||
|
||||
func TestHandleCreatePrinter(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().CreatePrinter("newprinter", "usb://HP", "generic.ppd", false, "", "", "").Return(nil)
|
||||
mockClient.EXPECT().ResumePrinter("newprinter").Return(nil)
|
||||
mockClient.EXPECT().AcceptJobs("newprinter").Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := NewTestManager(mockClient, nil)
|
||||
buf := &bytes.Buffer{}
|
||||
conn := &mockConn{Buffer: buf}
|
||||
|
||||
req := Request{
|
||||
ID: 1,
|
||||
Method: "cups.createPrinter",
|
||||
Params: map[string]interface{}{
|
||||
"name": "newprinter",
|
||||
"deviceURI": "usb://HP",
|
||||
"ppd": "generic.ppd",
|
||||
},
|
||||
}
|
||||
handleCreatePrinter(conn, req, m)
|
||||
|
||||
var resp models.Response[SuccessResult]
|
||||
err := json.NewDecoder(buf).Decode(&resp)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, resp.Result)
|
||||
assert.True(t, resp.Result.Success)
|
||||
}
|
||||
|
||||
func TestHandleCreatePrinter_MissingParams(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
m := &Manager{client: mockClient}
|
||||
buf := &bytes.Buffer{}
|
||||
conn := &mockConn{Buffer: buf}
|
||||
|
||||
req := Request{ID: 1, Method: "cups.createPrinter", Params: map[string]interface{}{}}
|
||||
handleCreatePrinter(conn, req, m)
|
||||
|
||||
var resp models.Response[interface{}]
|
||||
err := json.NewDecoder(buf).Decode(&resp)
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, resp.Result)
|
||||
assert.NotNil(t, resp.Error)
|
||||
}
|
||||
|
||||
func TestHandleDeletePrinter(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().DeletePrinter("printer1").Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := NewTestManager(mockClient, nil)
|
||||
buf := &bytes.Buffer{}
|
||||
conn := &mockConn{Buffer: buf}
|
||||
|
||||
req := Request{
|
||||
ID: 1,
|
||||
Method: "cups.deletePrinter",
|
||||
Params: map[string]interface{}{"printerName": "printer1"},
|
||||
}
|
||||
handleDeletePrinter(conn, req, m)
|
||||
|
||||
var resp models.Response[SuccessResult]
|
||||
err := json.NewDecoder(buf).Decode(&resp)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, resp.Result)
|
||||
assert.True(t, resp.Result.Success)
|
||||
}
|
||||
|
||||
func TestHandleAcceptJobs(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().AcceptJobs("printer1").Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := NewTestManager(mockClient, nil)
|
||||
buf := &bytes.Buffer{}
|
||||
conn := &mockConn{Buffer: buf}
|
||||
|
||||
req := Request{
|
||||
ID: 1,
|
||||
Method: "cups.acceptJobs",
|
||||
Params: map[string]interface{}{"printerName": "printer1"},
|
||||
}
|
||||
handleAcceptJobs(conn, req, m)
|
||||
|
||||
var resp models.Response[SuccessResult]
|
||||
err := json.NewDecoder(buf).Decode(&resp)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, resp.Result)
|
||||
assert.True(t, resp.Result.Success)
|
||||
}
|
||||
|
||||
func TestHandleRejectJobs(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().RejectJobs("printer1").Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := NewTestManager(mockClient, nil)
|
||||
buf := &bytes.Buffer{}
|
||||
conn := &mockConn{Buffer: buf}
|
||||
|
||||
req := Request{
|
||||
ID: 1,
|
||||
Method: "cups.rejectJobs",
|
||||
Params: map[string]interface{}{"printerName": "printer1"},
|
||||
}
|
||||
handleRejectJobs(conn, req, m)
|
||||
|
||||
var resp models.Response[SuccessResult]
|
||||
err := json.NewDecoder(buf).Decode(&resp)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, resp.Result)
|
||||
assert.True(t, resp.Result.Success)
|
||||
}
|
||||
|
||||
func TestHandleSetPrinterShared(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().SetPrinterIsShared("printer1", true).Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := NewTestManager(mockClient, nil)
|
||||
buf := &bytes.Buffer{}
|
||||
conn := &mockConn{Buffer: buf}
|
||||
|
||||
req := Request{
|
||||
ID: 1,
|
||||
Method: "cups.setPrinterShared",
|
||||
Params: map[string]interface{}{"printerName": "printer1", "shared": true},
|
||||
}
|
||||
handleSetPrinterShared(conn, req, m)
|
||||
|
||||
var resp models.Response[SuccessResult]
|
||||
err := json.NewDecoder(buf).Decode(&resp)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, resp.Result)
|
||||
assert.True(t, resp.Result.Success)
|
||||
}
|
||||
|
||||
func TestHandleSetPrinterLocation(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().SetPrinterLocation("printer1", "Office").Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := NewTestManager(mockClient, nil)
|
||||
buf := &bytes.Buffer{}
|
||||
conn := &mockConn{Buffer: buf}
|
||||
|
||||
req := Request{
|
||||
ID: 1,
|
||||
Method: "cups.setPrinterLocation",
|
||||
Params: map[string]interface{}{"printerName": "printer1", "location": "Office"},
|
||||
}
|
||||
handleSetPrinterLocation(conn, req, m)
|
||||
|
||||
var resp models.Response[SuccessResult]
|
||||
err := json.NewDecoder(buf).Decode(&resp)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, resp.Result)
|
||||
assert.True(t, resp.Result.Success)
|
||||
}
|
||||
|
||||
func TestHandleSetPrinterInfo(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().SetPrinterInformation("printer1", "Main Printer").Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := NewTestManager(mockClient, nil)
|
||||
buf := &bytes.Buffer{}
|
||||
conn := &mockConn{Buffer: buf}
|
||||
|
||||
req := Request{
|
||||
ID: 1,
|
||||
Method: "cups.setPrinterInfo",
|
||||
Params: map[string]interface{}{"printerName": "printer1", "info": "Main Printer"},
|
||||
}
|
||||
handleSetPrinterInfo(conn, req, m)
|
||||
|
||||
var resp models.Response[SuccessResult]
|
||||
err := json.NewDecoder(buf).Decode(&resp)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, resp.Result)
|
||||
assert.True(t, resp.Result.Success)
|
||||
}
|
||||
|
||||
func TestHandleMoveJob(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().MoveJob(1, "printer2").Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := NewTestManager(mockClient, nil)
|
||||
buf := &bytes.Buffer{}
|
||||
conn := &mockConn{Buffer: buf}
|
||||
|
||||
req := Request{
|
||||
ID: 1,
|
||||
Method: "cups.moveJob",
|
||||
Params: map[string]interface{}{"jobID": float64(1), "destPrinter": "printer2"},
|
||||
}
|
||||
handleMoveJob(conn, req, m)
|
||||
|
||||
var resp models.Response[SuccessResult]
|
||||
err := json.NewDecoder(buf).Decode(&resp)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, resp.Result)
|
||||
assert.True(t, resp.Result.Success)
|
||||
}
|
||||
|
||||
func TestHandlePrintTestPage(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().PrintTestPage("printer1", mock.Anything, mock.Anything).Return(42, nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := NewTestManager(mockClient, nil)
|
||||
buf := &bytes.Buffer{}
|
||||
conn := &mockConn{Buffer: buf}
|
||||
|
||||
req := Request{
|
||||
ID: 1,
|
||||
Method: "cups.printTestPage",
|
||||
Params: map[string]interface{}{"printerName": "printer1"},
|
||||
}
|
||||
handlePrintTestPage(conn, req, m)
|
||||
|
||||
var resp models.Response[TestPageResult]
|
||||
err := json.NewDecoder(buf).Decode(&resp)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, resp.Result)
|
||||
assert.True(t, resp.Result.Success)
|
||||
assert.Equal(t, 42, resp.Result.JobID)
|
||||
}
|
||||
|
||||
func TestHandleAddPrinterToClass(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().AddPrinterToClass("office", "printer1").Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := NewTestManager(mockClient, nil)
|
||||
buf := &bytes.Buffer{}
|
||||
conn := &mockConn{Buffer: buf}
|
||||
|
||||
req := Request{
|
||||
ID: 1,
|
||||
Method: "cups.addPrinterToClass",
|
||||
Params: map[string]interface{}{"className": "office", "printerName": "printer1"},
|
||||
}
|
||||
handleAddPrinterToClass(conn, req, m)
|
||||
|
||||
var resp models.Response[SuccessResult]
|
||||
err := json.NewDecoder(buf).Decode(&resp)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, resp.Result)
|
||||
assert.True(t, resp.Result.Success)
|
||||
}
|
||||
|
||||
func TestHandleRemovePrinterFromClass(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().DeletePrinterFromClass("office", "printer1").Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := NewTestManager(mockClient, nil)
|
||||
buf := &bytes.Buffer{}
|
||||
conn := &mockConn{Buffer: buf}
|
||||
|
||||
req := Request{
|
||||
ID: 1,
|
||||
Method: "cups.removePrinterFromClass",
|
||||
Params: map[string]interface{}{"className": "office", "printerName": "printer1"},
|
||||
}
|
||||
handleRemovePrinterFromClass(conn, req, m)
|
||||
|
||||
var resp models.Response[SuccessResult]
|
||||
err := json.NewDecoder(buf).Decode(&resp)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, resp.Result)
|
||||
assert.True(t, resp.Result.Success)
|
||||
}
|
||||
|
||||
func TestHandleDeleteClass(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().DeleteClass("office").Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := NewTestManager(mockClient, nil)
|
||||
buf := &bytes.Buffer{}
|
||||
conn := &mockConn{Buffer: buf}
|
||||
|
||||
req := Request{
|
||||
ID: 1,
|
||||
Method: "cups.deleteClass",
|
||||
Params: map[string]interface{}{"className": "office"},
|
||||
}
|
||||
handleDeleteClass(conn, req, m)
|
||||
|
||||
var resp models.Response[SuccessResult]
|
||||
err := json.NewDecoder(buf).Decode(&resp)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, resp.Result)
|
||||
assert.True(t, resp.Result.Success)
|
||||
}
|
||||
|
||||
func TestHandleRestartJob(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().RestartJob(1).Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := NewTestManager(mockClient, nil)
|
||||
buf := &bytes.Buffer{}
|
||||
conn := &mockConn{Buffer: buf}
|
||||
|
||||
req := Request{
|
||||
ID: 1,
|
||||
Method: "cups.restartJob",
|
||||
Params: map[string]interface{}{"jobID": float64(1)},
|
||||
}
|
||||
handleRestartJob(conn, req, m)
|
||||
|
||||
var resp models.Response[SuccessResult]
|
||||
err := json.NewDecoder(buf).Decode(&resp)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, resp.Result)
|
||||
assert.True(t, resp.Result.Success)
|
||||
}
|
||||
|
||||
func TestHandleHoldJob(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().HoldJobUntil(1, "indefinite").Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := NewTestManager(mockClient, nil)
|
||||
buf := &bytes.Buffer{}
|
||||
conn := &mockConn{Buffer: buf}
|
||||
|
||||
req := Request{
|
||||
ID: 1,
|
||||
Method: "cups.holdJob",
|
||||
Params: map[string]interface{}{"jobID": float64(1)},
|
||||
}
|
||||
handleHoldJob(conn, req, m)
|
||||
|
||||
var resp models.Response[SuccessResult]
|
||||
err := json.NewDecoder(buf).Decode(&resp)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, resp.Result)
|
||||
assert.True(t, resp.Result.Success)
|
||||
}
|
||||
|
||||
func TestHandleHoldJob_WithHoldUntil(t *testing.T) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
mockClient.EXPECT().HoldJobUntil(1, "no-hold").Return(nil)
|
||||
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||
|
||||
m := NewTestManager(mockClient, nil)
|
||||
buf := &bytes.Buffer{}
|
||||
conn := &mockConn{Buffer: buf}
|
||||
|
||||
req := Request{
|
||||
ID: 1,
|
||||
Method: "cups.holdJob",
|
||||
Params: map[string]interface{}{"jobID": float64(1), "holdUntil": "no-hold"},
|
||||
}
|
||||
handleHoldJob(conn, req, m)
|
||||
|
||||
var resp models.Response[SuccessResult]
|
||||
err := json.NewDecoder(buf).Decode(&resp)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, resp.Result)
|
||||
assert.True(t, resp.Result.Success)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package cups
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
@@ -31,11 +32,21 @@ func NewManager() (*Manager, error) {
|
||||
client := ipp.NewCUPSClient(host, port, username, password, false)
|
||||
baseURL := fmt.Sprintf("http://%s:%d", host, port)
|
||||
|
||||
var pkHelper PkHelper
|
||||
if isLocalCUPS(host) {
|
||||
var err error
|
||||
pkHelper, err = NewPkHelper()
|
||||
if err != nil {
|
||||
log.Warnf("[CUPS] Failed to initialize pkhelper: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
m := &Manager{
|
||||
state: &CUPSState{
|
||||
Printers: make(map[string]*Printer),
|
||||
},
|
||||
client: client,
|
||||
pkHelper: pkHelper,
|
||||
baseURL: baseURL,
|
||||
stateMutex: sync.RWMutex{},
|
||||
stopChan: make(chan struct{}),
|
||||
@@ -98,6 +109,12 @@ func (m *Manager) eventHandler() {
|
||||
func (m *Manager) updateState() error {
|
||||
printers, err := m.GetPrinters()
|
||||
if err != nil {
|
||||
if isNoPrintersError(err) {
|
||||
m.stateMutex.Lock()
|
||||
m.state.Printers = make(map[string]*Printer)
|
||||
m.stateMutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -119,6 +136,19 @@ func (m *Manager) updateState() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func isNoPrintersError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var ippErr ipp.IPPError
|
||||
if errors.As(err, &ippErr) {
|
||||
return ippErr.Status == 1030
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *Manager) notifier() {
|
||||
defer m.notifierWg.Done()
|
||||
const minGap = 100 * time.Millisecond
|
||||
@@ -170,6 +200,14 @@ func (m *Manager) notifySubscribers() {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) RefreshState() {
|
||||
if err := m.updateState(); err != nil {
|
||||
log.Warnf("[CUPS] Failed to refresh state: %v", err)
|
||||
return
|
||||
}
|
||||
m.notifySubscribers()
|
||||
}
|
||||
|
||||
func (m *Manager) GetState() CUPSState {
|
||||
return m.snapshotState()
|
||||
}
|
||||
@@ -256,6 +294,7 @@ func stateChanged(old, new *CUPSState) bool {
|
||||
}
|
||||
if oldPrinter.State != newPrinter.State ||
|
||||
oldPrinter.StateReason != newPrinter.StateReason ||
|
||||
oldPrinter.Accepting != newPrinter.Accepting ||
|
||||
len(oldPrinter.Jobs) != len(newPrinter.Jobs) {
|
||||
return true
|
||||
}
|
||||
@@ -334,3 +373,18 @@ func getBoolAttr(attrs ipp.Attributes, key string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getStringSliceAttr(attrs ipp.Attributes, key string) []string {
|
||||
attr, ok := attrs[key]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := make([]string, 0, len(attr))
|
||||
for _, a := range attr {
|
||||
if val, ok := a.Value.(string); ok {
|
||||
result = append(result, val)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
184
core/internal/server/cups/pkhelper.go
Normal file
184
core/internal/server/cups/pkhelper.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package cups
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
)
|
||||
|
||||
const (
|
||||
pkHelperDest = "org.opensuse.CupsPkHelper.Mechanism"
|
||||
pkHelperPath = "/"
|
||||
pkHelperInterface = "org.opensuse.CupsPkHelper.Mechanism"
|
||||
)
|
||||
|
||||
type PkHelper interface {
|
||||
DevicesGet(timeout, limit int, includeSchemes, excludeSchemes []string) ([]Device, error)
|
||||
PrinterAdd(name, uri, ppd, info, location string) error
|
||||
PrinterDelete(name string) error
|
||||
PrinterSetEnabled(name string, enabled bool) error
|
||||
PrinterSetAcceptJobs(name string, enabled bool, reason string) error
|
||||
PrinterSetInfo(name, info string) error
|
||||
PrinterSetLocation(name, location string) error
|
||||
PrinterSetShared(name string, shared bool) error
|
||||
ClassAddPrinter(className, printerName string) error
|
||||
ClassDeletePrinter(className, printerName string) error
|
||||
ClassDelete(className string) error
|
||||
JobCancelPurge(jobID int, purge bool) error
|
||||
JobRestart(jobID int) error
|
||||
JobSetHoldUntil(jobID int, holdUntil string) error
|
||||
}
|
||||
|
||||
type DBusPkHelper struct {
|
||||
conn *dbus.Conn
|
||||
obj dbus.BusObject
|
||||
}
|
||||
|
||||
func NewPkHelper() (*DBusPkHelper, error) {
|
||||
conn, err := dbus.SystemBus()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to system bus: %w", err)
|
||||
}
|
||||
|
||||
return &DBusPkHelper{
|
||||
conn: conn,
|
||||
obj: conn.Object(pkHelperDest, pkHelperPath),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *DBusPkHelper) DevicesGet(timeout, limit int, includeSchemes, excludeSchemes []string) ([]Device, error) {
|
||||
if includeSchemes == nil {
|
||||
includeSchemes = []string{}
|
||||
}
|
||||
if excludeSchemes == nil {
|
||||
excludeSchemes = []string{}
|
||||
}
|
||||
|
||||
var errStr string
|
||||
var devicesMap map[string]string
|
||||
|
||||
call := p.obj.Call(pkHelperInterface+".DevicesGet", 0, int32(timeout), int32(limit), includeSchemes, excludeSchemes)
|
||||
if call.Err != nil {
|
||||
return nil, call.Err
|
||||
}
|
||||
if err := call.Store(&errStr, &devicesMap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if errStr != "" {
|
||||
return nil, fmt.Errorf("%s", errStr)
|
||||
}
|
||||
|
||||
return parseDevicesMap(devicesMap), nil
|
||||
}
|
||||
|
||||
func parseDevicesMap(devicesMap map[string]string) []Device {
|
||||
devicesByIndex := make(map[string]*Device)
|
||||
|
||||
for key, value := range devicesMap {
|
||||
idx := strings.LastIndex(key, ":")
|
||||
if idx == -1 {
|
||||
continue
|
||||
}
|
||||
|
||||
attr := key[:idx]
|
||||
index := key[idx+1:]
|
||||
|
||||
dev, ok := devicesByIndex[index]
|
||||
if !ok {
|
||||
dev = &Device{}
|
||||
devicesByIndex[index] = dev
|
||||
}
|
||||
|
||||
switch attr {
|
||||
case "device-uri":
|
||||
dev.URI = value
|
||||
case "device-class":
|
||||
dev.Class = value
|
||||
case "device-info":
|
||||
dev.Info = value
|
||||
case "device-make-and-model":
|
||||
dev.MakeModel = value
|
||||
case "device-id":
|
||||
dev.ID = value
|
||||
case "device-location":
|
||||
dev.Location = value
|
||||
}
|
||||
}
|
||||
|
||||
devices := make([]Device, 0, len(devicesByIndex))
|
||||
for _, dev := range devicesByIndex {
|
||||
if dev.URI != "" {
|
||||
devices = append(devices, *dev)
|
||||
}
|
||||
}
|
||||
return devices
|
||||
}
|
||||
|
||||
func (p *DBusPkHelper) PrinterAdd(name, uri, ppd, info, location string) error {
|
||||
return p.callSimple("PrinterAdd", name, uri, ppd, info, location)
|
||||
}
|
||||
|
||||
func (p *DBusPkHelper) PrinterDelete(name string) error {
|
||||
return p.callSimple("PrinterDelete", name)
|
||||
}
|
||||
|
||||
func (p *DBusPkHelper) PrinterSetEnabled(name string, enabled bool) error {
|
||||
return p.callSimple("PrinterSetEnabled", name, enabled)
|
||||
}
|
||||
|
||||
func (p *DBusPkHelper) PrinterSetAcceptJobs(name string, enabled bool, reason string) error {
|
||||
return p.callSimple("PrinterSetAcceptJobs", name, enabled, reason)
|
||||
}
|
||||
|
||||
func (p *DBusPkHelper) PrinterSetInfo(name, info string) error {
|
||||
return p.callSimple("PrinterSetInfo", name, info)
|
||||
}
|
||||
|
||||
func (p *DBusPkHelper) PrinterSetLocation(name, location string) error {
|
||||
return p.callSimple("PrinterSetLocation", name, location)
|
||||
}
|
||||
|
||||
func (p *DBusPkHelper) PrinterSetShared(name string, shared bool) error {
|
||||
return p.callSimple("PrinterSetShared", name, shared)
|
||||
}
|
||||
|
||||
func (p *DBusPkHelper) ClassAddPrinter(className, printerName string) error {
|
||||
return p.callSimple("ClassAddPrinter", className, printerName)
|
||||
}
|
||||
|
||||
func (p *DBusPkHelper) ClassDeletePrinter(className, printerName string) error {
|
||||
return p.callSimple("ClassDeletePrinter", className, printerName)
|
||||
}
|
||||
|
||||
func (p *DBusPkHelper) ClassDelete(className string) error {
|
||||
return p.callSimple("ClassDelete", className)
|
||||
}
|
||||
|
||||
func (p *DBusPkHelper) JobCancelPurge(jobID int, purge bool) error {
|
||||
return p.callSimple("JobCancelPurge", int32(jobID), purge)
|
||||
}
|
||||
|
||||
func (p *DBusPkHelper) JobRestart(jobID int) error {
|
||||
return p.callSimple("JobRestart", int32(jobID))
|
||||
}
|
||||
|
||||
func (p *DBusPkHelper) JobSetHoldUntil(jobID int, holdUntil string) error {
|
||||
return p.callSimple("JobSetHoldUntil", int32(jobID), holdUntil)
|
||||
}
|
||||
|
||||
func (p *DBusPkHelper) callSimple(method string, args ...interface{}) error {
|
||||
var errStr string
|
||||
|
||||
call := p.obj.Call(pkHelperInterface+"."+method, 0, args...)
|
||||
if call.Err != nil {
|
||||
return call.Err
|
||||
}
|
||||
if err := call.Store(&errStr); err != nil {
|
||||
return err
|
||||
}
|
||||
if errStr != "" {
|
||||
return fmt.Errorf("%s", errStr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
95
core/internal/server/cups/pkhelper_test.go
Normal file
95
core/internal/server/cups/pkhelper_test.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package cups
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseDevicesMap(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input map[string]string
|
||||
wantLen int
|
||||
wantURIs []string
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
input: map[string]string{},
|
||||
wantLen: 0,
|
||||
wantURIs: nil,
|
||||
},
|
||||
{
|
||||
name: "single_device",
|
||||
input: map[string]string{
|
||||
"device-uri:0": "usb://HP/LaserJet",
|
||||
"device-class:0": "direct",
|
||||
"device-info:0": "HP LaserJet",
|
||||
"device-make-and-model:0": "HP LaserJet 1020",
|
||||
"device-id:0": "MFG:HP;MDL:LaserJet",
|
||||
},
|
||||
wantLen: 1,
|
||||
wantURIs: []string{"usb://HP/LaserJet"},
|
||||
},
|
||||
{
|
||||
name: "multiple_devices",
|
||||
input: map[string]string{
|
||||
"device-uri:0": "usb://HP/LaserJet",
|
||||
"device-class:0": "direct",
|
||||
"device-info:0": "HP LaserJet",
|
||||
"device-uri:1": "socket://192.168.1.100",
|
||||
"device-class:1": "network",
|
||||
"device-info:1": "Network Printer",
|
||||
},
|
||||
wantLen: 2,
|
||||
wantURIs: []string{"usb://HP/LaserJet", "socket://192.168.1.100"},
|
||||
},
|
||||
{
|
||||
name: "malformed_key",
|
||||
input: map[string]string{
|
||||
"no-colon-here": "value",
|
||||
},
|
||||
wantLen: 0,
|
||||
wantURIs: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := parseDevicesMap(tt.input)
|
||||
assert.Len(t, got, tt.wantLen)
|
||||
|
||||
if tt.wantURIs != nil {
|
||||
gotURIs := make(map[string]bool)
|
||||
for _, d := range got {
|
||||
gotURIs[d.URI] = true
|
||||
}
|
||||
for _, uri := range tt.wantURIs {
|
||||
assert.True(t, gotURIs[uri], "expected URI %s not found", uri)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDevicesMap_Attributes(t *testing.T) {
|
||||
input := map[string]string{
|
||||
"device-uri:0": "usb://HP/LaserJet",
|
||||
"device-class:0": "direct",
|
||||
"device-info:0": "HP LaserJet",
|
||||
"device-make-and-model:0": "HP LaserJet 1020",
|
||||
"device-id:0": "MFG:HP;MDL:LaserJet",
|
||||
"device-location:0": "USB Port",
|
||||
}
|
||||
|
||||
got := parseDevicesMap(input)
|
||||
assert.Len(t, got, 1)
|
||||
|
||||
dev := got[0]
|
||||
assert.Equal(t, "usb://HP/LaserJet", dev.URI)
|
||||
assert.Equal(t, "direct", dev.Class)
|
||||
assert.Equal(t, "HP LaserJet", dev.Info)
|
||||
assert.Equal(t, "HP LaserJet 1020", dev.MakeModel)
|
||||
assert.Equal(t, "MFG:HP;MDL:LaserJet", dev.ID)
|
||||
assert.Equal(t, "USB Port", dev.Location)
|
||||
}
|
||||
13
core/internal/server/cups/testing.go
Normal file
13
core/internal/server/cups/testing.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package cups
|
||||
|
||||
func NewTestManager(client CUPSClientInterface, pkHelper PkHelper) *Manager {
|
||||
return &Manager{
|
||||
client: client,
|
||||
pkHelper: pkHelper,
|
||||
state: &CUPSState{
|
||||
Printers: make(map[string]*Printer),
|
||||
},
|
||||
stopChan: make(chan struct{}),
|
||||
dirty: make(chan struct{}, 1),
|
||||
}
|
||||
}
|
||||
@@ -35,9 +35,38 @@ type Job struct {
|
||||
TimeCreated time.Time `json:"timeCreated"`
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
URI string `json:"uri"`
|
||||
Class string `json:"class"`
|
||||
Info string `json:"info"`
|
||||
MakeModel string `json:"makeModel"`
|
||||
ID string `json:"id"`
|
||||
Location string `json:"location"`
|
||||
}
|
||||
|
||||
type PPD struct {
|
||||
Name string `json:"name"`
|
||||
NaturalLanguage string `json:"naturalLanguage"`
|
||||
MakeModel string `json:"makeModel"`
|
||||
DeviceID string `json:"deviceId"`
|
||||
Product string `json:"product"`
|
||||
PSVersion string `json:"psVersion"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type PrinterClass struct {
|
||||
Name string `json:"name"`
|
||||
URI string `json:"uri"`
|
||||
State string `json:"state"`
|
||||
Members []string `json:"members"`
|
||||
Location string `json:"location"`
|
||||
Info string `json:"info"`
|
||||
}
|
||||
|
||||
type Manager struct {
|
||||
state *CUPSState
|
||||
client CUPSClientInterface
|
||||
pkHelper PkHelper
|
||||
subscription SubscriptionManagerInterface
|
||||
stateMutex sync.RWMutex
|
||||
subscribers syncmap.Map[string, chan CUPSState]
|
||||
@@ -63,6 +92,24 @@ type CUPSClientInterface interface {
|
||||
ResumePrinter(printer string) error
|
||||
CancelAllJob(printer string, purge bool) error
|
||||
SendRequest(url string, req *ipp.Request, additionalResponseData io.Writer) (*ipp.Response, error)
|
||||
|
||||
GetDevices() (map[string]ipp.Attributes, error)
|
||||
GetPPDs() (map[string]ipp.Attributes, error)
|
||||
GetClasses(attributes []string) (map[string]ipp.Attributes, error)
|
||||
CreatePrinter(name, deviceURI, ppd string, shared bool, errorPolicy, information, location string) error
|
||||
DeletePrinter(printer string) error
|
||||
AcceptJobs(printer string) error
|
||||
RejectJobs(printer string) error
|
||||
SetPrinterIsShared(printer string, shared bool) error
|
||||
SetPrinterLocation(printer, location string) error
|
||||
SetPrinterInformation(printer, information string) error
|
||||
MoveJob(jobID int, destPrinter string) error
|
||||
PrintTestPage(printer string, testPageData io.Reader, size int) (int, error)
|
||||
AddPrinterToClass(class, printer string) error
|
||||
DeletePrinterFromClass(class, printer string) error
|
||||
DeleteClass(class string) error
|
||||
RestartJob(jobID int) error
|
||||
HoldJobUntil(jobID int, holdUntil string) error
|
||||
}
|
||||
|
||||
type SubscriptionEvent struct {
|
||||
|
||||
@@ -287,7 +287,7 @@ func TestNewManager(t *testing.T) {
|
||||
} else {
|
||||
assert.NotNil(t, manager)
|
||||
assert.NotNil(t, manager.state)
|
||||
assert.NotNil(t, manager.subscribers)
|
||||
assert.NotNil(t, &manager.subscribers)
|
||||
assert.NotNil(t, manager.stopChan)
|
||||
|
||||
manager.Close()
|
||||
|
||||
@@ -18,24 +18,11 @@ func TestEventType_Constants(t *testing.T) {
|
||||
func TestSessionState_Struct(t *testing.T) {
|
||||
state := SessionState{
|
||||
SessionID: "1",
|
||||
SessionPath: "/org/freedesktop/login1/session/_31",
|
||||
Locked: false,
|
||||
Active: true,
|
||||
IdleHint: false,
|
||||
IdleSinceHint: 0,
|
||||
LockedHint: false,
|
||||
SessionType: "wayland",
|
||||
SessionClass: "user",
|
||||
User: 1000,
|
||||
UserName: "testuser",
|
||||
RemoteHost: "",
|
||||
Service: "gdm-password",
|
||||
TTY: "tty2",
|
||||
Display: ":1",
|
||||
Remote: false,
|
||||
Seat: "seat0",
|
||||
VTNr: 2,
|
||||
PreparingForSleep: false,
|
||||
}
|
||||
|
||||
assert.Equal(t, "1", state.SessionID)
|
||||
|
||||
@@ -4,11 +4,15 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
"github.com/godbus/dbus/v5"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -116,17 +120,12 @@ func (a *SecretAgent) GetSecrets(
|
||||
log.Infof("[SecretAgent] GetSecrets called: path=%s, setting=%s, hints=%v, flags=%d",
|
||||
path, settingName, hints, flags)
|
||||
|
||||
const (
|
||||
NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION = 0x1
|
||||
NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW = 0x2
|
||||
NM_SECRET_AGENT_GET_SECRETS_FLAG_USER_REQUESTED = 0x4
|
||||
)
|
||||
|
||||
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,14 +162,26 @@ 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" {
|
||||
if a.backend != nil {
|
||||
a.backend.stateMutex.RLock()
|
||||
isConnectingVPN := a.backend.state.IsConnectingVPN
|
||||
a.backend.stateMutex.RUnlock()
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
fields = inferVPNFields(conn, vpnSvc)
|
||||
log.Infof("[SecretAgent] VPN with empty hints but we're connecting - inferred fields: %v", fields)
|
||||
} 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 len(fields) == 0 {
|
||||
const (
|
||||
NM_SETTING_SECRET_FLAG_NONE = 0
|
||||
NM_SETTING_SECRET_FLAG_AGENT_OWNED = 1
|
||||
@@ -216,6 +227,7 @@ func (a *SecretAgent) GetSecrets(
|
||||
return out, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reason := reasonFromFlags(flags)
|
||||
if a.manager != nil && connType == "802-11-wireless" && a.manager.WasRecentlyFailed(ssid) {
|
||||
@@ -236,6 +248,35 @@ func (a *SecretAgent) GetSecrets(
|
||||
}
|
||||
}
|
||||
|
||||
if settingName == "vpn" && a.backend != nil {
|
||||
a.backend.cachedVPNCredsMu.Lock()
|
||||
cached := a.backend.cachedVPNCreds
|
||||
if cached != nil && cached.ConnectionUUID == connUuid {
|
||||
a.backend.cachedVPNCreds = nil
|
||||
a.backend.cachedVPNCredsMu.Unlock()
|
||||
|
||||
log.Infof("[SecretAgent] Using cached password from pre-activation prompt")
|
||||
|
||||
out := nmSettingMap{}
|
||||
sec := nmVariantMap{}
|
||||
sec["password"] = dbus.MakeVariant(cached.Password)
|
||||
out[settingName] = sec
|
||||
|
||||
if cached.SavePassword {
|
||||
a.backend.pendingVPNSaveMu.Lock()
|
||||
a.backend.pendingVPNSave = &pendingVPNCredentials{
|
||||
ConnectionPath: string(path),
|
||||
Password: cached.Password,
|
||||
SavePassword: true,
|
||||
}
|
||||
a.backend.pendingVPNSaveMu.Unlock()
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
a.backend.cachedVPNCredsMu.Unlock()
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
@@ -246,6 +287,7 @@ func (a *SecretAgent) GetSecrets(
|
||||
VpnService: vpnSvc,
|
||||
SettingName: settingName,
|
||||
Fields: fields,
|
||||
FieldsInfo: buildFieldsInfo(settingName, fields, vpnSvc),
|
||||
Hints: hints,
|
||||
Reason: reason,
|
||||
ConnectionId: connId,
|
||||
@@ -268,6 +310,7 @@ func (a *SecretAgent) GetSecrets(
|
||||
wasConnecting := a.backend.state.IsConnecting
|
||||
wasConnectingVPN := a.backend.state.IsConnectingVPN
|
||||
cancelledSSID := a.backend.state.ConnectingSSID
|
||||
cancelledVPNUUID := a.backend.state.ConnectingVPNUUID
|
||||
if wasConnecting || wasConnectingVPN {
|
||||
log.Infof("[SecretAgent] Clearing connecting state due to cancelled prompt")
|
||||
a.backend.state.IsConnecting = false
|
||||
@@ -286,6 +329,14 @@ func (a *SecretAgent) GetSecrets(
|
||||
}
|
||||
}
|
||||
|
||||
// If this was a VPN connection that was cancelled, deactivate it
|
||||
if wasConnectingVPN && cancelledVPNUUID != "" {
|
||||
log.Infof("[SecretAgent] Deactivating cancelled VPN connection: %s", cancelledVPNUUID)
|
||||
if err := a.backend.DisconnectVPN(cancelledVPNUUID); err != nil {
|
||||
log.Warnf("[SecretAgent] Failed to deactivate cancelled VPN: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if (wasConnecting || wasConnectingVPN) && a.backend.onStateChange != nil {
|
||||
a.backend.onStateChange()
|
||||
}
|
||||
@@ -305,7 +356,12 @@ func (a *SecretAgent) GetSecrets(
|
||||
|
||||
out := nmSettingMap{}
|
||||
sec := nmVariantMap{}
|
||||
|
||||
var vpnUsername string
|
||||
for k, v := range reply.Secrets {
|
||||
if settingName == "vpn" && k == "username" {
|
||||
vpnUsername = v
|
||||
}
|
||||
sec[k] = dbus.MakeVariant(v)
|
||||
}
|
||||
out[settingName] = sec
|
||||
@@ -317,13 +373,22 @@ func (a *SecretAgent) GetSecrets(
|
||||
log.Infof("[SecretAgent] Returning VPN secrets with %d fields for %s", len(sec), vpnSvc)
|
||||
}
|
||||
|
||||
// If save=true, persist secrets in background after returning to NetworkManager
|
||||
// This MUST happen after we return secrets, in a goroutine
|
||||
if reply.Save {
|
||||
if settingName == "vpn" && a.backend != nil && (vpnUsername != "" || reply.Save) {
|
||||
pw := reply.Secrets["password"]
|
||||
a.backend.pendingVPNSaveMu.Lock()
|
||||
a.backend.pendingVPNSave = &pendingVPNCredentials{
|
||||
ConnectionPath: string(path),
|
||||
Username: vpnUsername,
|
||||
Password: pw,
|
||||
SavePassword: reply.Save,
|
||||
}
|
||||
a.backend.pendingVPNSaveMu.Unlock()
|
||||
log.Infof("[SecretAgent] Queued credentials persist for after connection succeeds")
|
||||
} else if reply.Save && settingName != "vpn" {
|
||||
// Non-VPN save logic
|
||||
go func() {
|
||||
log.Infof("[SecretAgent] Persisting secrets with Update2: path=%s, setting=%s", path, settingName)
|
||||
|
||||
// Get existing connection settings
|
||||
connObj := a.conn.Object("org.freedesktop.NetworkManager", path)
|
||||
var existingSettings map[string]map[string]dbus.Variant
|
||||
if err := connObj.Call("org.freedesktop.NetworkManager.Settings.Connection.GetSettings", 0).Store(&existingSettings); err != nil {
|
||||
@@ -331,62 +396,19 @@ func (a *SecretAgent) GetSecrets(
|
||||
return
|
||||
}
|
||||
|
||||
// Build minimal settings with ONLY the section we're updating
|
||||
// This avoids D-Bus type serialization issues with complex types like IPv6 addresses
|
||||
settings := make(map[string]map[string]dbus.Variant)
|
||||
|
||||
// Copy connection section (required for Update2)
|
||||
if connSection, ok := existingSettings["connection"]; ok {
|
||||
settings["connection"] = connSection
|
||||
}
|
||||
|
||||
// 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 {
|
||||
data = make(map[string]string)
|
||||
for k, v := range dm {
|
||||
data[k] = v
|
||||
}
|
||||
} else {
|
||||
data = make(map[string]string)
|
||||
}
|
||||
} else {
|
||||
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
|
||||
}
|
||||
vpn["secrets"] = dbus.MakeVariant(secs)
|
||||
settings["vpn"] = vpn
|
||||
|
||||
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 +416,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 +527,136 @@ func fieldsNeeded(setting string, hints []string) []string {
|
||||
}
|
||||
}
|
||||
|
||||
func buildFieldsInfo(setting string, fields []string, vpnService string) []FieldInfo {
|
||||
result := make([]FieldInfo, 0, len(fields))
|
||||
for _, f := range fields {
|
||||
info := FieldInfo{Name: f}
|
||||
switch setting {
|
||||
case "802-11-wireless-security":
|
||||
info.Label = "Password"
|
||||
info.IsSecret = true
|
||||
case "802-1x":
|
||||
switch f {
|
||||
case "identity":
|
||||
info.Label = "Username"
|
||||
info.IsSecret = false
|
||||
case "password":
|
||||
info.Label = "Password"
|
||||
info.IsSecret = true
|
||||
default:
|
||||
info.Label = f
|
||||
info.IsSecret = true
|
||||
}
|
||||
case "vpn":
|
||||
info.Label, info.IsSecret = vpnFieldMeta(f, vpnService)
|
||||
default:
|
||||
info.Label = f
|
||||
info.IsSecret = true
|
||||
}
|
||||
result = append(result, info)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func inferVPNFields(conn map[string]nmVariantMap, vpnService string) []string {
|
||||
fields := []string{"password"}
|
||||
|
||||
vpnSettings, ok := conn["vpn"]
|
||||
if !ok {
|
||||
return fields
|
||||
}
|
||||
|
||||
dataVariant, ok := vpnSettings["data"]
|
||||
if !ok {
|
||||
return fields
|
||||
}
|
||||
|
||||
dataMap, ok := dataVariant.Value().(map[string]string)
|
||||
if !ok {
|
||||
return fields
|
||||
}
|
||||
|
||||
connType := dataMap["connection-type"]
|
||||
|
||||
switch {
|
||||
case strings.Contains(vpnService, "openvpn"):
|
||||
if connType == "password" || connType == "password-tls" {
|
||||
if dataMap["username"] == "" {
|
||||
fields = []string{"username", "password"}
|
||||
}
|
||||
}
|
||||
case strings.Contains(vpnService, "vpnc"), strings.Contains(vpnService, "l2tp"),
|
||||
strings.Contains(vpnService, "pptp"), strings.Contains(vpnService, "openconnect"):
|
||||
if dataMap["username"] == "" {
|
||||
fields = []string{"username", "password"}
|
||||
}
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
func vpnFieldMeta(field, vpnService string) (label string, isSecret bool) {
|
||||
switch field {
|
||||
case "password":
|
||||
return "Password", true
|
||||
case "Xauth password":
|
||||
return "IPSec Password", true
|
||||
case "IPSec secret":
|
||||
return "IPSec Pre-Shared Key", true
|
||||
case "cert-pass":
|
||||
return "Certificate Password", true
|
||||
case "http-proxy-password":
|
||||
return "HTTP Proxy Password", true
|
||||
case "username":
|
||||
return "Username", false
|
||||
case "Xauth username":
|
||||
return "IPSec Username", false
|
||||
case "proxy-password":
|
||||
return "Proxy Password", true
|
||||
case "private-key-password":
|
||||
return "Private Key Password", true
|
||||
}
|
||||
titleCaser := cases.Title(language.English)
|
||||
if strings.HasSuffix(field, "password") || strings.HasSuffix(field, "secret") ||
|
||||
strings.HasSuffix(field, "pass") || strings.HasSuffix(field, "psk") {
|
||||
return titleCaser.String(strings.ReplaceAll(field, "-", " ")), true
|
||||
}
|
||||
return titleCaser.String(strings.ReplaceAll(field, "-", " ")), false
|
||||
}
|
||||
|
||||
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,17 +8,22 @@ 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
|
||||
|
||||
GetEthernetDevices() []EthernetDevice
|
||||
GetWiredConnections() ([]WiredConnection, error)
|
||||
GetWiredNetworkDetails(uuid string) (*WiredNetworkInfoResponse, error)
|
||||
ConnectEthernet() error
|
||||
DisconnectEthernet() error
|
||||
DisconnectEthernetDevice(device string) error
|
||||
ActivateWiredConnection(uuid string) error
|
||||
|
||||
ListVPNProfiles() ([]VPNProfile, error)
|
||||
@@ -27,6 +32,12 @@ type Backend interface {
|
||||
DisconnectVPN(uuidOrName string) error
|
||||
DisconnectAllVPN() error
|
||||
ClearVPNCredentials(uuidOrName string) error
|
||||
ListVPNPlugins() ([]VPNPlugin, error)
|
||||
ImportVPN(filePath string, name string) (*VPNImportResult, error)
|
||||
GetVPNConfig(uuidOrName string) (*VPNConfig, error)
|
||||
UpdateVPNConfig(uuid string, updates map[string]interface{}) error
|
||||
SetVPNCredentials(uuid string, username string, password string, save bool) error
|
||||
DeleteVPN(uuidOrName string) error
|
||||
|
||||
GetCurrentState() (*BackendState, error)
|
||||
|
||||
@@ -46,6 +57,7 @@ type BackendState struct {
|
||||
EthernetDevice string
|
||||
EthernetConnected bool
|
||||
EthernetConnectionUuid string
|
||||
EthernetDevices []EthernetDevice
|
||||
WiFiIP string
|
||||
WiFiDevice string
|
||||
WiFiConnected bool
|
||||
@@ -54,11 +66,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
|
||||
|
||||
@@ -2,14 +2,12 @@ package network
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type HybridIwdNetworkdBackend struct {
|
||||
wifi *IWDBackend
|
||||
l3 *SystemdNetworkdBackend
|
||||
onStateChange func()
|
||||
stateMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func NewHybridIwdNetworkdBackend(w *IWDBackend, n *SystemdNetworkdBackend) (*HybridIwdNetworkdBackend, error) {
|
||||
@@ -84,6 +82,7 @@ func (b *HybridIwdNetworkdBackend) GetCurrentState() (*BackendState, error) {
|
||||
merged.EthernetDevice = ls.EthernetDevice
|
||||
merged.EthernetConnectionUuid = ls.EthernetConnectionUuid
|
||||
merged.WiredConnections = ls.WiredConnections
|
||||
merged.EthernetDevices = ls.EthernetDevices
|
||||
|
||||
if ls.EthernetConnected && ls.EthernetIP != "" {
|
||||
merged.NetworkStatus = StatusEthernet
|
||||
@@ -119,7 +118,7 @@ func (b *HybridIwdNetworkdBackend) ConnectWiFi(req ConnectionRequest) error {
|
||||
|
||||
ws, err := b.wifi.GetCurrentState()
|
||||
if err == nil && ws.WiFiDevice != "" {
|
||||
b.l3.EnsureDhcpUp(ws.WiFiDevice)
|
||||
b.l3.EnsureDhcpUp(ws.WiFiDevice) //nolint:errcheck
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -149,6 +148,14 @@ func (b *HybridIwdNetworkdBackend) DisconnectEthernet() error {
|
||||
return b.l3.DisconnectEthernet()
|
||||
}
|
||||
|
||||
func (b *HybridIwdNetworkdBackend) DisconnectEthernetDevice(device string) error {
|
||||
return b.l3.DisconnectEthernetDevice(device)
|
||||
}
|
||||
|
||||
func (b *HybridIwdNetworkdBackend) GetEthernetDevices() []EthernetDevice {
|
||||
return b.l3.GetEthernetDevices()
|
||||
}
|
||||
|
||||
func (b *HybridIwdNetworkdBackend) ActivateWiredConnection(uuid string) error {
|
||||
return b.l3.ActivateWiredConnection(uuid)
|
||||
}
|
||||
@@ -177,6 +184,26 @@ func (b *HybridIwdNetworkdBackend) ClearVPNCredentials(uuidOrName string) error
|
||||
return fmt.Errorf("VPN not supported in hybrid mode")
|
||||
}
|
||||
|
||||
func (b *HybridIwdNetworkdBackend) ListVPNPlugins() ([]VPNPlugin, error) {
|
||||
return []VPNPlugin{}, nil
|
||||
}
|
||||
|
||||
func (b *HybridIwdNetworkdBackend) ImportVPN(filePath string, name string) (*VPNImportResult, error) {
|
||||
return nil, fmt.Errorf("VPN not supported in hybrid mode")
|
||||
}
|
||||
|
||||
func (b *HybridIwdNetworkdBackend) GetVPNConfig(uuidOrName string) (*VPNConfig, error) {
|
||||
return nil, fmt.Errorf("VPN not supported in hybrid mode")
|
||||
}
|
||||
|
||||
func (b *HybridIwdNetworkdBackend) UpdateVPNConfig(uuid string, updates map[string]interface{}) error {
|
||||
return fmt.Errorf("VPN not supported in hybrid mode")
|
||||
}
|
||||
|
||||
func (b *HybridIwdNetworkdBackend) DeleteVPN(uuidOrName string) error {
|
||||
return fmt.Errorf("VPN not supported in hybrid mode")
|
||||
}
|
||||
|
||||
func (b *HybridIwdNetworkdBackend) GetPromptBroker() PromptBroker {
|
||||
return b.wifi.GetPromptBroker()
|
||||
}
|
||||
@@ -196,3 +223,19 @@ 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()
|
||||
}
|
||||
|
||||
func (b *HybridIwdNetworkdBackend) SetVPNCredentials(uuid, username, password string, save bool) error {
|
||||
return fmt.Errorf("VPN not supported in hybrid mode")
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
"github.com/godbus/dbus/v5"
|
||||
)
|
||||
|
||||
@@ -139,9 +140,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
|
||||
}
|
||||
@@ -155,6 +160,7 @@ func (b *IWDBackend) OnUserCanceledPrompt() {
|
||||
|
||||
if cancelledSSID != "" {
|
||||
if err := b.ForgetWiFiNetwork(cancelledSSID); err != nil {
|
||||
log.Warnf("failed to forget cancelled WiFi network %s: %v", cancelledSSID, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,14 @@ func (b *IWDBackend) DisconnectEthernet() error {
|
||||
return fmt.Errorf("wired connections not supported by iwd")
|
||||
}
|
||||
|
||||
func (b *IWDBackend) DisconnectEthernetDevice(device string) error {
|
||||
return fmt.Errorf("wired connections not supported by iwd")
|
||||
}
|
||||
|
||||
func (b *IWDBackend) GetEthernetDevices() []EthernetDevice {
|
||||
return []EthernetDevice{}
|
||||
}
|
||||
|
||||
func (b *IWDBackend) ActivateWiredConnection(uuid string) error {
|
||||
return fmt.Errorf("wired connections not supported by iwd")
|
||||
}
|
||||
@@ -45,3 +53,62 @@ func (b *IWDBackend) DisconnectAllVPN() error {
|
||||
func (b *IWDBackend) ClearVPNCredentials(uuidOrName string) error {
|
||||
return fmt.Errorf("VPN not supported by iwd backend")
|
||||
}
|
||||
|
||||
func (b *IWDBackend) ListVPNPlugins() ([]VPNPlugin, error) {
|
||||
return nil, fmt.Errorf("VPN not supported by iwd backend")
|
||||
}
|
||||
|
||||
func (b *IWDBackend) ImportVPN(filePath string, name string) (*VPNImportResult, error) {
|
||||
return nil, fmt.Errorf("VPN not supported by iwd backend")
|
||||
}
|
||||
|
||||
func (b *IWDBackend) GetVPNConfig(uuidOrName string) (*VPNConfig, error) {
|
||||
return nil, fmt.Errorf("VPN not supported by iwd backend")
|
||||
}
|
||||
|
||||
func (b *IWDBackend) UpdateVPNConfig(uuid string, updates map[string]interface{}) error {
|
||||
return fmt.Errorf("VPN not supported by iwd backend")
|
||||
}
|
||||
|
||||
func (b *IWDBackend) DeleteVPN(uuidOrName string) error {
|
||||
return fmt.Errorf("VPN not supported by iwd backend")
|
||||
}
|
||||
|
||||
func (b *IWDBackend) SetVPNCredentials(uuid, username, password string, save bool) 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,
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -138,6 +138,7 @@ func (b *SystemdNetworkdBackend) updateState() error {
|
||||
}
|
||||
|
||||
var wiredConns []WiredConnection
|
||||
var ethernetDevices []EthernetDevice
|
||||
for name, link := range b.links {
|
||||
if b.isVirtualInterface(name) || strings.HasPrefix(name, "wlan") || strings.HasPrefix(name, "wlp") {
|
||||
continue
|
||||
@@ -151,6 +152,37 @@ func (b *SystemdNetworkdBackend) updateState() error {
|
||||
Type: "ethernet",
|
||||
IsActive: active,
|
||||
})
|
||||
|
||||
var ip string
|
||||
var hwAddr string
|
||||
if iface, err := net.InterfaceByName(name); err == nil {
|
||||
hwAddr = iface.HardwareAddr.String()
|
||||
if addrs := b.getAddresses(name); len(addrs) > 0 {
|
||||
ip = addrs[0]
|
||||
}
|
||||
}
|
||||
|
||||
stateStr := "disconnected"
|
||||
switch link.opState {
|
||||
case "routable":
|
||||
stateStr = "routable"
|
||||
case "carrier":
|
||||
stateStr = "carrier"
|
||||
case "degraded":
|
||||
stateStr = "degraded"
|
||||
case "no-carrier":
|
||||
stateStr = "no-carrier"
|
||||
case "off":
|
||||
stateStr = "off"
|
||||
}
|
||||
|
||||
ethernetDevices = append(ethernetDevices, EthernetDevice{
|
||||
Name: name,
|
||||
HwAddress: hwAddr,
|
||||
State: stateStr,
|
||||
Connected: active,
|
||||
IP: ip,
|
||||
})
|
||||
}
|
||||
|
||||
b.stateMutex.Lock()
|
||||
@@ -162,6 +194,7 @@ func (b *SystemdNetworkdBackend) updateState() error {
|
||||
b.state.WiFiConnected = false
|
||||
b.state.WiFiIP = ""
|
||||
b.state.WiredConnections = wiredConns
|
||||
b.state.EthernetDevices = ethernetDevices
|
||||
|
||||
if wiredIface != nil {
|
||||
b.state.EthernetDevice = wiredIface.name
|
||||
|
||||
@@ -108,3 +108,13 @@ func (b *SystemdNetworkdBackend) ActivateWiredConnection(id string) error {
|
||||
linkObj := b.conn.Object(networkdBusName, link.path)
|
||||
return linkObj.Call(networkdLinkIface+".Reconfigure", 0).Err
|
||||
}
|
||||
|
||||
func (b *SystemdNetworkdBackend) GetEthernetDevices() []EthernetDevice {
|
||||
b.stateMutex.RLock()
|
||||
defer b.stateMutex.RUnlock()
|
||||
return append([]EthernetDevice(nil), b.state.EthernetDevices...)
|
||||
}
|
||||
|
||||
func (b *SystemdNetworkdBackend) DisconnectEthernetDevice(device string) error {
|
||||
return fmt.Errorf("not supported by networkd backend")
|
||||
}
|
||||
|
||||
@@ -123,3 +123,25 @@ func TestSystemdNetworkdBackend_DisconnectEthernet(t *testing.T) {
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "not supported")
|
||||
}
|
||||
|
||||
func TestSystemdNetworkdBackend_GetEthernetDevices(t *testing.T) {
|
||||
backend, _ := NewSystemdNetworkdBackend()
|
||||
|
||||
backend.state.EthernetDevices = []EthernetDevice{
|
||||
{Name: "enp0s3", State: "routable", Connected: true},
|
||||
{Name: "enp0s8", State: "no-carrier", Connected: false},
|
||||
}
|
||||
|
||||
devices := backend.GetEthernetDevices()
|
||||
assert.Len(t, devices, 2)
|
||||
assert.Equal(t, "enp0s3", devices[0].Name)
|
||||
assert.True(t, devices[0].Connected)
|
||||
}
|
||||
|
||||
func TestSystemdNetworkdBackend_DisconnectEthernetDevice(t *testing.T) {
|
||||
backend, _ := NewSystemdNetworkdBackend()
|
||||
|
||||
err := backend.DisconnectEthernetDevice("enp0s3")
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "not supported")
|
||||
}
|
||||
|
||||
@@ -54,6 +54,42 @@ func (b *SystemdNetworkdBackend) ClearVPNCredentials(uuidOrName string) error {
|
||||
return fmt.Errorf("VPN not supported by networkd backend")
|
||||
}
|
||||
|
||||
func (b *SystemdNetworkdBackend) ListVPNPlugins() ([]VPNPlugin, error) {
|
||||
return []VPNPlugin{}, nil
|
||||
}
|
||||
|
||||
func (b *SystemdNetworkdBackend) ImportVPN(filePath string, name string) (*VPNImportResult, error) {
|
||||
return nil, fmt.Errorf("VPN not supported by networkd backend")
|
||||
}
|
||||
|
||||
func (b *SystemdNetworkdBackend) GetVPNConfig(uuidOrName string) (*VPNConfig, error) {
|
||||
return nil, fmt.Errorf("VPN not supported by networkd backend")
|
||||
}
|
||||
|
||||
func (b *SystemdNetworkdBackend) UpdateVPNConfig(uuid string, updates map[string]interface{}) error {
|
||||
return fmt.Errorf("VPN not supported by networkd backend")
|
||||
}
|
||||
|
||||
func (b *SystemdNetworkdBackend) DeleteVPN(uuidOrName string) error {
|
||||
return fmt.Errorf("VPN not supported by networkd backend")
|
||||
}
|
||||
|
||||
func (b *SystemdNetworkdBackend) SetVPNCredentials(uuid, username, password string, save bool) error {
|
||||
return fmt.Errorf("VPN not supported by networkd backend")
|
||||
}
|
||||
|
||||
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,28 @@ const (
|
||||
NmDeviceStateReasonNewActivation = 60
|
||||
)
|
||||
|
||||
type wifiDeviceInfo struct {
|
||||
device gonetworkmanager.Device
|
||||
wireless gonetworkmanager.DeviceWireless
|
||||
name string
|
||||
hwAddress string
|
||||
}
|
||||
|
||||
type ethernetDeviceInfo struct {
|
||||
device gonetworkmanager.Device
|
||||
wired gonetworkmanager.DeviceWired
|
||||
name string
|
||||
hwAddress string
|
||||
}
|
||||
|
||||
type NetworkManagerBackend struct {
|
||||
nmConn interface{}
|
||||
ethernetDevice interface{}
|
||||
ethernetDevices map[string]*ethernetDeviceInfo
|
||||
wifiDevice interface{}
|
||||
settings interface{}
|
||||
wifiDev interface{}
|
||||
wifiDevices map[string]*wifiDeviceInfo
|
||||
|
||||
dbusConn *dbus.Conn
|
||||
signals chan *dbus.Signal
|
||||
@@ -52,9 +68,27 @@ type NetworkManagerBackend struct {
|
||||
lastFailedTime int64
|
||||
failedMutex sync.RWMutex
|
||||
|
||||
pendingVPNSave *pendingVPNCredentials
|
||||
pendingVPNSaveMu sync.Mutex
|
||||
cachedVPNCreds *cachedVPNCredentials
|
||||
cachedVPNCredsMu sync.Mutex
|
||||
|
||||
onStateChange func()
|
||||
}
|
||||
|
||||
type pendingVPNCredentials struct {
|
||||
ConnectionPath string
|
||||
Username string
|
||||
Password string
|
||||
SavePassword bool
|
||||
}
|
||||
|
||||
type cachedVPNCredentials struct {
|
||||
ConnectionUUID string
|
||||
Password string
|
||||
SavePassword bool
|
||||
}
|
||||
|
||||
func NewNetworkManagerBackend(nmConn ...gonetworkmanager.NetworkManager) (*NetworkManagerBackend, error) {
|
||||
var nm gonetworkmanager.NetworkManager
|
||||
var err error
|
||||
@@ -73,6 +107,8 @@ func NewNetworkManagerBackend(nmConn ...gonetworkmanager.NetworkManager) (*Netwo
|
||||
backend := &NetworkManagerBackend{
|
||||
nmConn: nm,
|
||||
stopChan: make(chan struct{}),
|
||||
ethernetDevices: make(map[string]*ethernetDeviceInfo),
|
||||
wifiDevices: make(map[string]*wifiDeviceInfo),
|
||||
state: &BackendState{
|
||||
Backend: "networkmanager",
|
||||
},
|
||||
@@ -104,36 +140,78 @@ func (b *NetworkManagerBackend) Initialize() error {
|
||||
if managed, _ := dev.GetPropertyManaged(); !managed {
|
||||
continue
|
||||
}
|
||||
iface, err := dev.GetPropertyInterface()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
w, err := gonetworkmanager.NewDeviceWired(dev.GetPath())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
hwAddr, _ := w.GetPropertyHwAddress()
|
||||
|
||||
b.ethernetDevices[iface] = ðernetDeviceInfo{
|
||||
device: dev,
|
||||
wired: w,
|
||||
name: iface,
|
||||
hwAddress: hwAddr,
|
||||
}
|
||||
|
||||
if b.ethernetDevice == nil {
|
||||
b.ethernetDevice = dev
|
||||
}
|
||||
if err := b.updateEthernetState(); err != nil {
|
||||
continue
|
||||
}
|
||||
_, err := b.listEthernetConnections()
|
||||
_, err = b.listEthernetConnections()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get wired configurations: %w", err)
|
||||
}
|
||||
|
||||
case gonetworkmanager.NmDeviceTypeWifi:
|
||||
iface, err := dev.GetPropertyInterface()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
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
|
||||
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 {
|
||||
continue
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b.updateAllEthernetDevices()
|
||||
|
||||
if err := b.updatePrimaryConnection(); err != nil {
|
||||
return err
|
||||
@@ -165,7 +243,9 @@ 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.EthernetDevices = append([]EthernetDevice(nil), b.state.EthernetDevices...)
|
||||
state.VPNProfiles = append([]VPNProfile(nil), b.state.VPNProfiles...)
|
||||
state.VPNActive = append([]VPNActive(nil), b.state.VPNActive...)
|
||||
|
||||
|
||||
@@ -315,3 +315,88 @@ func (b *NetworkManagerBackend) listEthernetConnections() ([]WiredConnection, er
|
||||
|
||||
return wiredConfigs, nil
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) GetEthernetDevices() []EthernetDevice {
|
||||
b.stateMutex.RLock()
|
||||
defer b.stateMutex.RUnlock()
|
||||
return append([]EthernetDevice(nil), b.state.EthernetDevices...)
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) DisconnectEthernetDevice(device string) error {
|
||||
info, ok := b.ethernetDevices[device]
|
||||
if !ok {
|
||||
return fmt.Errorf("ethernet device %s not found", device)
|
||||
}
|
||||
|
||||
if err := info.device.Disconnect(); err != nil {
|
||||
return fmt.Errorf("failed to disconnect %s: %w", device, err)
|
||||
}
|
||||
|
||||
b.updateAllEthernetDevices()
|
||||
b.updateEthernetState()
|
||||
b.listEthernetConnections()
|
||||
b.updatePrimaryConnection()
|
||||
|
||||
if b.onStateChange != nil {
|
||||
b.onStateChange()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) updateAllEthernetDevices() {
|
||||
devices := make([]EthernetDevice, 0, len(b.ethernetDevices))
|
||||
|
||||
for name, info := range b.ethernetDevices {
|
||||
state, _ := info.device.GetPropertyState()
|
||||
connected := state == gonetworkmanager.NmDeviceStateActivated
|
||||
driver, _ := info.device.GetPropertyDriver()
|
||||
|
||||
var ip string
|
||||
var speed uint32 = 0
|
||||
if connected {
|
||||
ip = b.getDeviceIP(info.device)
|
||||
}
|
||||
if info.wired != nil {
|
||||
speed, _ = info.wired.GetPropertySpeed()
|
||||
}
|
||||
|
||||
stateStr := "disconnected"
|
||||
switch state {
|
||||
case gonetworkmanager.NmDeviceStateActivated:
|
||||
stateStr = "activated"
|
||||
case gonetworkmanager.NmDeviceStatePrepare:
|
||||
stateStr = "preparing"
|
||||
case gonetworkmanager.NmDeviceStateConfig:
|
||||
stateStr = "configuring"
|
||||
case gonetworkmanager.NmDeviceStateIpConfig:
|
||||
stateStr = "ip-config"
|
||||
case gonetworkmanager.NmDeviceStateIpCheck:
|
||||
stateStr = "ip-check"
|
||||
case gonetworkmanager.NmDeviceStateSecondaries:
|
||||
stateStr = "secondaries"
|
||||
case gonetworkmanager.NmDeviceStateDeactivating:
|
||||
stateStr = "deactivating"
|
||||
case gonetworkmanager.NmDeviceStateFailed:
|
||||
stateStr = "failed"
|
||||
case gonetworkmanager.NmDeviceStateUnavailable:
|
||||
stateStr = "unavailable"
|
||||
case gonetworkmanager.NmDeviceStateUnmanaged:
|
||||
stateStr = "unmanaged"
|
||||
}
|
||||
|
||||
devices = append(devices, EthernetDevice{
|
||||
Name: name,
|
||||
HwAddress: info.hwAddress,
|
||||
State: stateStr,
|
||||
Connected: connected,
|
||||
IP: ip,
|
||||
Speed: speed,
|
||||
Driver: driver,
|
||||
})
|
||||
}
|
||||
|
||||
b.stateMutex.Lock()
|
||||
b.state.EthernetDevices = devices
|
||||
b.stateMutex.Unlock()
|
||||
}
|
||||
|
||||
@@ -82,3 +82,53 @@ func TestNetworkManagerBackend_ListEthernetConnections_NoDevice(t *testing.T) {
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "no ethernet device available")
|
||||
}
|
||||
|
||||
func TestNetworkManagerBackend_GetEthernetDevices_Empty(t *testing.T) {
|
||||
mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
|
||||
|
||||
backend, err := NewNetworkManagerBackend(mockNM)
|
||||
assert.NoError(t, err)
|
||||
|
||||
devices := backend.GetEthernetDevices()
|
||||
assert.Empty(t, devices)
|
||||
}
|
||||
|
||||
func TestNetworkManagerBackend_GetEthernetDevices_WithState(t *testing.T) {
|
||||
mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
|
||||
|
||||
backend, err := NewNetworkManagerBackend(mockNM)
|
||||
assert.NoError(t, err)
|
||||
|
||||
backend.state.EthernetDevices = []EthernetDevice{
|
||||
{Name: "enp0s3", HwAddress: "00:11:22:33:44:55", State: "activated", Connected: true, IP: "192.168.1.100"},
|
||||
{Name: "enp0s8", HwAddress: "00:11:22:33:44:66", State: "disconnected", Connected: false},
|
||||
}
|
||||
|
||||
devices := backend.GetEthernetDevices()
|
||||
assert.Len(t, devices, 2)
|
||||
assert.Equal(t, "enp0s3", devices[0].Name)
|
||||
assert.True(t, devices[0].Connected)
|
||||
assert.Equal(t, "enp0s8", devices[1].Name)
|
||||
assert.False(t, devices[1].Connected)
|
||||
}
|
||||
|
||||
func TestNetworkManagerBackend_DisconnectEthernetDevice_NotFound(t *testing.T) {
|
||||
mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
|
||||
|
||||
backend, err := NewNetworkManagerBackend(mockNM)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = backend.DisconnectEthernetDevice("nonexistent")
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "not found")
|
||||
}
|
||||
|
||||
func TestNetworkManagerBackend_UpdateAllEthernetDevices_Empty(t *testing.T) {
|
||||
mockNM := mock_gonetworkmanager.NewMockNetworkManager(t)
|
||||
|
||||
backend, err := NewNetworkManagerBackend(mockNM)
|
||||
assert.NoError(t, err)
|
||||
|
||||
backend.updateAllEthernetDevices()
|
||||
assert.Empty(t, backend.state.EthernetDevices)
|
||||
}
|
||||
|
||||
@@ -61,6 +61,26 @@ func (b *NetworkManagerBackend) startSignalPump() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := conn.AddMatchSignal(
|
||||
dbus.WithMatchObjectPath(dbus.ObjectPath(dbusNMPath)),
|
||||
dbus.WithMatchInterface(dbusNMInterface),
|
||||
dbus.WithMatchMember("DeviceAdded"),
|
||||
); err != nil {
|
||||
conn.RemoveSignal(signals)
|
||||
conn.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
if err := conn.AddMatchSignal(
|
||||
dbus.WithMatchObjectPath(dbus.ObjectPath(dbusNMPath)),
|
||||
dbus.WithMatchInterface(dbusNMInterface),
|
||||
dbus.WithMatchMember("DeviceRemoved"),
|
||||
); err != nil {
|
||||
conn.RemoveSignal(signals)
|
||||
conn.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
if b.wifiDevice != nil {
|
||||
dev := b.wifiDevice.(gonetworkmanager.Device)
|
||||
if err := conn.AddMatchSignal(
|
||||
@@ -175,6 +195,24 @@ func (b *NetworkManagerBackend) handleDBusSignal(sig *dbus.Signal) {
|
||||
return
|
||||
}
|
||||
|
||||
if sig.Name == "org.freedesktop.NetworkManager.DeviceAdded" {
|
||||
if len(sig.Body) >= 1 {
|
||||
if devicePath, ok := sig.Body[0].(dbus.ObjectPath); ok {
|
||||
b.handleDeviceAdded(devicePath)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if sig.Name == "org.freedesktop.NetworkManager.DeviceRemoved" {
|
||||
if len(sig.Body) >= 1 {
|
||||
if devicePath, ok := sig.Body[0].(dbus.ObjectPath); ok {
|
||||
b.handleDeviceRemoved(devicePath)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if len(sig.Body) < 2 {
|
||||
return
|
||||
}
|
||||
@@ -319,3 +357,156 @@ func (b *NetworkManagerBackend) handleAccessPointChange(changes map[string]dbus.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) handleDeviceAdded(devicePath dbus.ObjectPath) {
|
||||
dev, err := gonetworkmanager.NewDevice(devicePath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
devType, err := dev.GetPropertyDeviceType()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
managed, _ := dev.GetPropertyManaged()
|
||||
if !managed {
|
||||
return
|
||||
}
|
||||
|
||||
iface, err := dev.GetPropertyInterface()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch devType {
|
||||
case gonetworkmanager.NmDeviceTypeEthernet:
|
||||
w, err := gonetworkmanager.NewDeviceWired(devicePath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
hwAddr, _ := w.GetPropertyHwAddress()
|
||||
|
||||
b.ethernetDevices[iface] = ðernetDeviceInfo{
|
||||
device: dev,
|
||||
wired: w,
|
||||
name: iface,
|
||||
hwAddress: hwAddr,
|
||||
}
|
||||
|
||||
if b.ethernetDevice == nil {
|
||||
b.ethernetDevice = dev
|
||||
}
|
||||
|
||||
if b.dbusConn != nil {
|
||||
b.dbusConn.AddMatchSignal(
|
||||
dbus.WithMatchObjectPath(devicePath),
|
||||
dbus.WithMatchInterface(dbusPropsInterface),
|
||||
dbus.WithMatchMember("PropertiesChanged"),
|
||||
)
|
||||
}
|
||||
|
||||
b.updateAllEthernetDevices()
|
||||
b.updateEthernetState()
|
||||
b.listEthernetConnections()
|
||||
b.updatePrimaryConnection()
|
||||
|
||||
case gonetworkmanager.NmDeviceTypeWifi:
|
||||
w, err := gonetworkmanager.NewDeviceWireless(devicePath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
if b.dbusConn != nil {
|
||||
b.dbusConn.AddMatchSignal(
|
||||
dbus.WithMatchObjectPath(devicePath),
|
||||
dbus.WithMatchInterface(dbusPropsInterface),
|
||||
dbus.WithMatchMember("PropertiesChanged"),
|
||||
)
|
||||
}
|
||||
|
||||
b.updateAllWiFiDevices()
|
||||
b.updateWiFiState()
|
||||
}
|
||||
|
||||
if b.onStateChange != nil {
|
||||
b.onStateChange()
|
||||
}
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) handleDeviceRemoved(devicePath dbus.ObjectPath) {
|
||||
if b.dbusConn != nil {
|
||||
b.dbusConn.RemoveMatchSignal(
|
||||
dbus.WithMatchObjectPath(devicePath),
|
||||
dbus.WithMatchInterface(dbusPropsInterface),
|
||||
dbus.WithMatchMember("PropertiesChanged"),
|
||||
)
|
||||
}
|
||||
|
||||
for iface, info := range b.ethernetDevices {
|
||||
if info.device.GetPath() == devicePath {
|
||||
delete(b.ethernetDevices, iface)
|
||||
|
||||
if b.ethernetDevice != nil {
|
||||
dev := b.ethernetDevice.(gonetworkmanager.Device)
|
||||
if dev.GetPath() == devicePath {
|
||||
b.ethernetDevice = nil
|
||||
for _, remaining := range b.ethernetDevices {
|
||||
b.ethernetDevice = remaining.device
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b.updateAllEthernetDevices()
|
||||
b.updateEthernetState()
|
||||
b.listEthernetConnections()
|
||||
b.updatePrimaryConnection()
|
||||
|
||||
if b.onStateChange != nil {
|
||||
b.onStateChange()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for iface, info := range b.wifiDevices {
|
||||
if info.device.GetPath() == devicePath {
|
||||
delete(b.wifiDevices, iface)
|
||||
|
||||
if b.wifiDevice != nil {
|
||||
dev := b.wifiDevice.(gonetworkmanager.Device)
|
||||
if dev.GetPath() == devicePath {
|
||||
b.wifiDevice = nil
|
||||
b.wifiDev = nil
|
||||
for _, remaining := range b.wifiDevices {
|
||||
b.wifiDevice = remaining.device
|
||||
b.wifiDev = remaining.wireless
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b.updateAllWiFiDevices()
|
||||
b.updateWiFiState()
|
||||
|
||||
if b.onStateChange != nil {
|
||||
b.onStateChange()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,33 +72,34 @@ func (b *NetworkManagerBackend) updatePrimaryConnection() error {
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) updateEthernetState() error {
|
||||
if b.ethernetDevice == nil {
|
||||
return nil
|
||||
var connectedDevice string
|
||||
var connectedIP string
|
||||
var anyConnected bool
|
||||
|
||||
for name, info := range b.ethernetDevices {
|
||||
state, err := info.device.GetPropertyState()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if state == gonetworkmanager.NmDeviceStateActivated {
|
||||
anyConnected = true
|
||||
connectedDevice = name
|
||||
connectedIP = b.getDeviceIP(info.device)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !anyConnected && b.ethernetDevice != nil {
|
||||
dev := b.ethernetDevice.(gonetworkmanager.Device)
|
||||
|
||||
iface, err := dev.GetPropertyInterface()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
state, err := dev.GetPropertyState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
connected := state == gonetworkmanager.NmDeviceStateActivated
|
||||
|
||||
var ip string
|
||||
if connected {
|
||||
ip = b.getDeviceIP(dev)
|
||||
iface, _ := dev.GetPropertyInterface()
|
||||
connectedDevice = iface
|
||||
}
|
||||
|
||||
b.stateMutex.Lock()
|
||||
b.state.EthernetDevice = iface
|
||||
b.state.EthernetConnected = connected
|
||||
b.state.EthernetIP = ip
|
||||
b.state.EthernetDevice = connectedDevice
|
||||
b.state.EthernetConnected = anyConnected
|
||||
b.state.EthernetIP = connectedIP
|
||||
b.stateMutex.Unlock()
|
||||
|
||||
return nil
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
"github.com/Wifx/gonetworkmanager/v2"
|
||||
"github.com/godbus/dbus/v5"
|
||||
)
|
||||
|
||||
func (b *NetworkManagerBackend) ListVPNProfiles() ([]VPNProfile, error) {
|
||||
@@ -46,11 +52,13 @@ func (b *NetworkManagerBackend) ListVPNProfiles() ([]VPNProfile, error) {
|
||||
|
||||
connID, _ := connMeta["id"].(string)
|
||||
connUUID, _ := connMeta["uuid"].(string)
|
||||
autoconnect, _ := connMeta["autoconnect"].(bool)
|
||||
|
||||
profile := VPNProfile{
|
||||
Name: connID,
|
||||
UUID: connUUID,
|
||||
Type: connType,
|
||||
Autoconnect: autoconnect,
|
||||
}
|
||||
|
||||
if connType == "vpn" {
|
||||
@@ -58,6 +66,16 @@ func (b *NetworkManagerBackend) ListVPNProfiles() ([]VPNProfile, error) {
|
||||
if svcType, ok := vpnSettings["service-type"].(string); ok {
|
||||
profile.ServiceType = svcType
|
||||
}
|
||||
// Get full data map
|
||||
if data, ok := vpnSettings["data"].(map[string]string); ok {
|
||||
profile.Data = data
|
||||
if remote, ok := data["remote"]; ok {
|
||||
profile.RemoteHost = remote
|
||||
}
|
||||
if username, ok := data["username"]; ok {
|
||||
profile.Username = username
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +138,31 @@ func (b *NetworkManagerBackend) ListActiveVPN() ([]VPNActive, error) {
|
||||
Plugin: "",
|
||||
}
|
||||
|
||||
// Get VPN device
|
||||
devices, _ := activeConn.GetPropertyDevices()
|
||||
if len(devices) > 0 {
|
||||
if iface, err := devices[0].GetPropertyInterface(); err == nil {
|
||||
vpnActive.Device = iface
|
||||
}
|
||||
}
|
||||
|
||||
// Get VPN IP from IP4Config
|
||||
if ip4Config, err := activeConn.GetPropertyIP4Config(); err == nil && ip4Config != nil {
|
||||
if addrData, err := ip4Config.GetPropertyAddressData(); err == nil && len(addrData) > 0 {
|
||||
vpnActive.IP = addrData[0].Address
|
||||
}
|
||||
if gw, err := ip4Config.GetPropertyGateway(); err == nil {
|
||||
vpnActive.Gateway = gw
|
||||
}
|
||||
}
|
||||
|
||||
// Get MTU from device
|
||||
if len(devices) > 0 {
|
||||
if mtu, err := devices[0].GetPropertyMtu(); err == nil {
|
||||
vpnActive.MTU = mtu
|
||||
}
|
||||
}
|
||||
|
||||
if connType == "vpn" {
|
||||
conn, _ := activeConn.GetPropertyConnection()
|
||||
if conn != nil {
|
||||
@@ -129,6 +172,16 @@ func (b *NetworkManagerBackend) ListActiveVPN() ([]VPNActive, error) {
|
||||
if svcType, ok := vpnSettings["service-type"].(string); ok {
|
||||
vpnActive.Plugin = svcType
|
||||
}
|
||||
// Get full data map
|
||||
if data, ok := vpnSettings["data"].(map[string]string); ok {
|
||||
vpnActive.Data = data
|
||||
if remote, ok := data["remote"]; ok {
|
||||
vpnActive.RemoteHost = remote
|
||||
}
|
||||
if username, ok := data["username"]; ok {
|
||||
vpnActive.Username = username
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -219,10 +272,122 @@ func (b *NetworkManagerBackend) ConnectVPN(uuidOrName string, singleActive bool)
|
||||
}
|
||||
|
||||
var targetUUID string
|
||||
var connName string
|
||||
if connMeta, ok := targetSettings["connection"]; ok {
|
||||
if uuid, ok := connMeta["uuid"].(string); ok {
|
||||
targetUUID = uuid
|
||||
}
|
||||
if id, ok := connMeta["id"].(string); ok {
|
||||
connName = id
|
||||
}
|
||||
}
|
||||
|
||||
needsUsernamePrePrompt := false
|
||||
var vpnServiceType string
|
||||
if vpnSettings, ok := targetSettings["vpn"]; ok {
|
||||
if svc, ok := vpnSettings["service-type"].(string); ok {
|
||||
vpnServiceType = svc
|
||||
}
|
||||
if data, ok := vpnSettings["data"].(map[string]string); ok {
|
||||
connType := data["connection-type"]
|
||||
username := data["username"]
|
||||
// OpenVPN password auth needs username in vpn.data
|
||||
if strings.Contains(vpnServiceType, "openvpn") &&
|
||||
(connType == "password" || connType == "password-tls") &&
|
||||
username == "" {
|
||||
needsUsernamePrePrompt = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If username is needed but missing, prompt for it before activating
|
||||
if needsUsernamePrePrompt && b.promptBroker != nil {
|
||||
log.Infof("[ConnectVPN] OpenVPN requires username in vpn.data - prompting before activation")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
token, err := b.promptBroker.Ask(ctx, PromptRequest{
|
||||
Name: connName,
|
||||
ConnType: "vpn",
|
||||
VpnService: vpnServiceType,
|
||||
SettingName: "vpn",
|
||||
Fields: []string{"username", "password"},
|
||||
FieldsInfo: []FieldInfo{{Name: "username", Label: "Username", IsSecret: false}, {Name: "password", Label: "Password", IsSecret: true}},
|
||||
Reason: "required",
|
||||
ConnectionId: connName,
|
||||
ConnectionUuid: targetUUID,
|
||||
ConnectionPath: string(targetConn.GetPath()),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to request credentials: %w", err)
|
||||
}
|
||||
|
||||
reply, err := b.promptBroker.Wait(ctx, token)
|
||||
if err != nil {
|
||||
return fmt.Errorf("credentials prompt failed: %w", err)
|
||||
}
|
||||
|
||||
username := reply.Secrets["username"]
|
||||
password := reply.Secrets["password"]
|
||||
if username != "" {
|
||||
connObj := b.dbusConn.Object("org.freedesktop.NetworkManager", targetConn.GetPath())
|
||||
var existingSettings map[string]map[string]dbus.Variant
|
||||
if err := connObj.Call("org.freedesktop.NetworkManager.Settings.Connection.GetSettings", 0).Store(&existingSettings); err != nil {
|
||||
return fmt.Errorf("failed to get settings for username save: %w", err)
|
||||
}
|
||||
|
||||
settings := make(map[string]map[string]dbus.Variant)
|
||||
if connSection, ok := existingSettings["connection"]; ok {
|
||||
settings["connection"] = connSection
|
||||
}
|
||||
vpn := existingSettings["vpn"]
|
||||
var data map[string]string
|
||||
if dataVariant, ok := vpn["data"]; ok {
|
||||
if dm, ok := dataVariant.Value().(map[string]string); ok {
|
||||
data = make(map[string]string)
|
||||
for k, v := range dm {
|
||||
data[k] = v
|
||||
}
|
||||
} else {
|
||||
data = make(map[string]string)
|
||||
}
|
||||
} else {
|
||||
data = make(map[string]string)
|
||||
}
|
||||
data["username"] = username
|
||||
|
||||
if reply.Save && password != "" {
|
||||
data["password-flags"] = "0"
|
||||
secs := make(map[string]string)
|
||||
secs["password"] = password
|
||||
vpn["secrets"] = dbus.MakeVariant(secs)
|
||||
log.Infof("[ConnectVPN] Saving username and password to vpn.data")
|
||||
} else {
|
||||
log.Infof("[ConnectVPN] Saving username to vpn.data (password will be prompted)")
|
||||
}
|
||||
|
||||
vpn["data"] = dbus.MakeVariant(data)
|
||||
settings["vpn"] = vpn
|
||||
|
||||
var result map[string]dbus.Variant
|
||||
if err := connObj.Call("org.freedesktop.NetworkManager.Settings.Connection.Update2", 0,
|
||||
settings, uint32(0x1), map[string]dbus.Variant{}).Store(&result); err != nil {
|
||||
return fmt.Errorf("failed to save username: %w", err)
|
||||
}
|
||||
log.Infof("[ConnectVPN] Username saved to connection, now activating")
|
||||
|
||||
if password != "" && !reply.Save {
|
||||
b.cachedVPNCredsMu.Lock()
|
||||
b.cachedVPNCreds = &cachedVPNCredentials{
|
||||
ConnectionUUID: targetUUID,
|
||||
Password: password,
|
||||
SavePassword: reply.Save,
|
||||
}
|
||||
b.cachedVPNCredsMu.Unlock()
|
||||
log.Infof("[ConnectVPN] Cached password for GetSecrets")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b.stateMutex.Lock()
|
||||
@@ -235,7 +400,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 +414,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
|
||||
}
|
||||
|
||||
@@ -484,7 +635,7 @@ func (b *NetworkManagerBackend) updateVPNConnectionState() {
|
||||
continue
|
||||
}
|
||||
|
||||
uuid, err := activeConn.GetPropertyUUID()
|
||||
connUUID, err := activeConn.GetPropertyUUID()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -492,20 +643,29 @@ func (b *NetworkManagerBackend) updateVPNConnectionState() {
|
||||
state, _ := activeConn.GetPropertyState()
|
||||
stateReason, _ := activeConn.GetPropertyStateFlags()
|
||||
|
||||
if uuid == connectingVPNUUID {
|
||||
if connUUID == connectingVPNUUID {
|
||||
foundConnection = true
|
||||
|
||||
switch state {
|
||||
case 2:
|
||||
log.Infof("[updateVPNConnectionState] VPN connection successful: %s", uuid)
|
||||
log.Infof("[updateVPNConnectionState] VPN connection successful: %s", connUUID)
|
||||
b.stateMutex.Lock()
|
||||
b.state.IsConnectingVPN = false
|
||||
b.state.ConnectingVPNUUID = ""
|
||||
b.state.LastError = ""
|
||||
b.stateMutex.Unlock()
|
||||
|
||||
b.pendingVPNSaveMu.Lock()
|
||||
pending := b.pendingVPNSave
|
||||
b.pendingVPNSave = nil
|
||||
b.pendingVPNSaveMu.Unlock()
|
||||
|
||||
if pending != nil {
|
||||
go b.saveVPNCredentials(pending)
|
||||
}
|
||||
return
|
||||
case 4:
|
||||
log.Warnf("[updateVPNConnectionState] VPN connection failed/deactivated: %s (state=%d, flags=%d)", uuid, state, stateReason)
|
||||
log.Warnf("[updateVPNConnectionState] VPN connection failed/deactivated: %s (state=%d, flags=%d)", connUUID, state, stateReason)
|
||||
b.stateMutex.Lock()
|
||||
b.state.IsConnectingVPN = false
|
||||
b.state.ConnectingVPNUUID = ""
|
||||
@@ -525,3 +685,622 @@ func (b *NetworkManagerBackend) updateVPNConnectionState() {
|
||||
b.stateMutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) saveVPNCredentials(creds *pendingVPNCredentials) {
|
||||
log.Infof("[saveVPNCredentials] Saving credentials for %s (username=%v, savePassword=%v)",
|
||||
creds.ConnectionPath, creds.Username != "", creds.SavePassword)
|
||||
|
||||
connObj := b.dbusConn.Object("org.freedesktop.NetworkManager", dbus.ObjectPath(creds.ConnectionPath))
|
||||
var existingSettings map[string]map[string]dbus.Variant
|
||||
if err := connObj.Call("org.freedesktop.NetworkManager.Settings.Connection.GetSettings", 0).Store(&existingSettings); err != nil {
|
||||
log.Warnf("[saveVPNCredentials] GetSettings failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
settings := make(map[string]map[string]dbus.Variant)
|
||||
if connSection, ok := existingSettings["connection"]; ok {
|
||||
settings["connection"] = connSection
|
||||
}
|
||||
|
||||
vpn, ok := existingSettings["vpn"]
|
||||
if !ok {
|
||||
vpn = make(map[string]dbus.Variant)
|
||||
}
|
||||
|
||||
// Get existing data map
|
||||
var data map[string]string
|
||||
if dataVariant, ok := vpn["data"]; ok {
|
||||
if dm, ok := dataVariant.Value().(map[string]string); ok {
|
||||
data = make(map[string]string)
|
||||
for k, v := range dm {
|
||||
data[k] = v
|
||||
}
|
||||
} else {
|
||||
data = make(map[string]string)
|
||||
}
|
||||
} else {
|
||||
data = make(map[string]string)
|
||||
}
|
||||
|
||||
// Always save username if provided
|
||||
if creds.Username != "" {
|
||||
data["username"] = creds.Username
|
||||
log.Infof("[saveVPNCredentials] Saving username")
|
||||
}
|
||||
|
||||
// Save password if requested
|
||||
if creds.SavePassword {
|
||||
data["password-flags"] = "0"
|
||||
secs := make(map[string]string)
|
||||
secs["password"] = creds.Password
|
||||
vpn["secrets"] = dbus.MakeVariant(secs)
|
||||
log.Infof("[saveVPNCredentials] Saving password with password-flags=0")
|
||||
}
|
||||
|
||||
vpn["data"] = dbus.MakeVariant(data)
|
||||
settings["vpn"] = vpn
|
||||
|
||||
var result map[string]dbus.Variant
|
||||
if err := connObj.Call("org.freedesktop.NetworkManager.Settings.Connection.Update2", 0,
|
||||
settings, uint32(0x1), map[string]dbus.Variant{}).Store(&result); err != nil {
|
||||
log.Warnf("[saveVPNCredentials] Update2 failed: %v", err)
|
||||
} else {
|
||||
log.Infof("[saveVPNCredentials] Successfully saved credentials")
|
||||
}
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) ListVPNPlugins() ([]VPNPlugin, error) {
|
||||
plugins := []VPNPlugin{}
|
||||
pluginDirs := []string{
|
||||
"/usr/lib/NetworkManager/VPN",
|
||||
"/usr/lib64/NetworkManager/VPN",
|
||||
"/etc/NetworkManager/VPN",
|
||||
}
|
||||
|
||||
seen := make(map[string]bool)
|
||||
|
||||
for _, dir := range pluginDirs {
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if !strings.HasSuffix(entry.Name(), ".name") {
|
||||
continue
|
||||
}
|
||||
|
||||
filePath := filepath.Join(dir, entry.Name())
|
||||
plugin, err := parseVPNPluginFile(filePath)
|
||||
if err != nil {
|
||||
log.Debugf("Failed to parse VPN plugin file %s: %v", filePath, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if seen[plugin.ServiceType] {
|
||||
continue
|
||||
}
|
||||
seen[plugin.ServiceType] = true
|
||||
plugins = append(plugins, *plugin)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(plugins, func(i, j int) bool {
|
||||
return strings.ToLower(plugins[i].Name) < strings.ToLower(plugins[j].Name)
|
||||
})
|
||||
|
||||
return plugins, nil
|
||||
}
|
||||
|
||||
func parseVPNPluginFile(path string) (*VPNPlugin, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
plugin := &VPNPlugin{}
|
||||
scanner := bufio.NewScanner(file)
|
||||
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, "[") {
|
||||
continue
|
||||
}
|
||||
|
||||
parts := strings.SplitN(line, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
key := strings.TrimSpace(parts[0])
|
||||
value := strings.TrimSpace(parts[1])
|
||||
|
||||
switch key {
|
||||
case "name":
|
||||
plugin.Name = value
|
||||
case "service":
|
||||
plugin.ServiceType = value
|
||||
case "program":
|
||||
plugin.Program = value
|
||||
case "supports":
|
||||
plugin.Supports = strings.Split(value, ",")
|
||||
for i := range plugin.Supports {
|
||||
plugin.Supports[i] = strings.TrimSpace(plugin.Supports[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if plugin.ServiceType == "" {
|
||||
return nil, fmt.Errorf("plugin file missing service type")
|
||||
}
|
||||
|
||||
plugin.FileExtensions = getVPNFileExtensions(plugin.ServiceType)
|
||||
|
||||
return plugin, nil
|
||||
}
|
||||
|
||||
func getVPNFileExtensions(serviceType string) []string {
|
||||
switch {
|
||||
case strings.Contains(serviceType, "openvpn"):
|
||||
return []string{".ovpn", ".conf"}
|
||||
case strings.Contains(serviceType, "wireguard"):
|
||||
return []string{".conf"}
|
||||
case strings.Contains(serviceType, "vpnc"), strings.Contains(serviceType, "cisco"):
|
||||
return []string{".pcf", ".conf"}
|
||||
case strings.Contains(serviceType, "openconnect"):
|
||||
return []string{".conf"}
|
||||
case strings.Contains(serviceType, "pptp"):
|
||||
return []string{".conf"}
|
||||
case strings.Contains(serviceType, "l2tp"):
|
||||
return []string{".conf"}
|
||||
case strings.Contains(serviceType, "strongswan"), strings.Contains(serviceType, "ipsec"):
|
||||
return []string{".conf", ".sswan"}
|
||||
default:
|
||||
return []string{".conf"}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) ImportVPN(filePath string, name string) (*VPNImportResult, error) {
|
||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||
return &VPNImportResult{
|
||||
Success: false,
|
||||
Error: fmt.Sprintf("file not found: %s", filePath),
|
||||
}, nil
|
||||
}
|
||||
|
||||
ext := strings.ToLower(filepath.Ext(filePath))
|
||||
|
||||
switch ext {
|
||||
case ".ovpn", ".conf":
|
||||
return b.importVPNWithNmcli(filePath, name)
|
||||
default:
|
||||
return b.importVPNWithNmcli(filePath, name)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) importVPNWithNmcli(filePath string, name string) (*VPNImportResult, error) {
|
||||
args := []string{"connection", "import", "type", "openvpn", "file", filePath}
|
||||
cmd := exec.Command("nmcli", args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
|
||||
if err != nil {
|
||||
outputStr := string(output)
|
||||
if strings.Contains(outputStr, "vpnc") || strings.Contains(outputStr, "unknown connection type") {
|
||||
for _, vpnType := range []string{"vpnc", "pptp", "l2tp", "openconnect", "strongswan", "wireguard"} {
|
||||
args = []string{"connection", "import", "type", vpnType, "file", filePath}
|
||||
cmd = exec.Command("nmcli", args...)
|
||||
output, err = cmd.CombinedOutput()
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return &VPNImportResult{
|
||||
Success: false,
|
||||
Error: fmt.Sprintf("import failed: %s", strings.TrimSpace(string(output))),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
outputStr := string(output)
|
||||
var connUUID, connName string
|
||||
|
||||
lines := strings.Split(outputStr, "\n")
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, "successfully added") {
|
||||
parts := strings.Fields(line)
|
||||
for i, part := range parts {
|
||||
if part == "(" && i+1 < len(parts) {
|
||||
connUUID = strings.TrimSuffix(parts[i+1], ")")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if name != "" && connUUID != "" {
|
||||
renameCmd := exec.Command("nmcli", "connection", "modify", connUUID, "connection.id", name)
|
||||
if err := renameCmd.Run(); err != nil {
|
||||
log.Warnf("Failed to rename imported VPN: %v", err)
|
||||
} else {
|
||||
connName = name
|
||||
}
|
||||
}
|
||||
|
||||
if connUUID == "" {
|
||||
s := b.settings
|
||||
if s == nil {
|
||||
var settingsErr error
|
||||
s, settingsErr = gonetworkmanager.NewSettings()
|
||||
if settingsErr == nil {
|
||||
b.settings = s
|
||||
}
|
||||
}
|
||||
|
||||
if s != nil {
|
||||
settingsMgr := s.(gonetworkmanager.Settings)
|
||||
connections, _ := settingsMgr.ListConnections()
|
||||
baseName := strings.TrimSuffix(filepath.Base(filePath), filepath.Ext(filePath))
|
||||
|
||||
for _, conn := range connections {
|
||||
settings, err := conn.GetSettings()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
connMeta, ok := settings["connection"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
connType, _ := connMeta["type"].(string)
|
||||
if connType != "vpn" && connType != "wireguard" {
|
||||
continue
|
||||
}
|
||||
connID, _ := connMeta["id"].(string)
|
||||
if strings.Contains(connID, baseName) || (name != "" && connID == name) {
|
||||
connUUID, _ = connMeta["uuid"].(string)
|
||||
connName = connID
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b.ListVPNProfiles()
|
||||
|
||||
if b.onStateChange != nil {
|
||||
b.onStateChange()
|
||||
}
|
||||
|
||||
return &VPNImportResult{
|
||||
Success: true,
|
||||
UUID: connUUID,
|
||||
Name: connName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) GetVPNConfig(uuidOrName string) (*VPNConfig, error) {
|
||||
s := b.settings
|
||||
if s == nil {
|
||||
var err error
|
||||
s, err = gonetworkmanager.NewSettings()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get settings: %w", err)
|
||||
}
|
||||
b.settings = s
|
||||
}
|
||||
|
||||
settingsMgr := s.(gonetworkmanager.Settings)
|
||||
connections, err := settingsMgr.ListConnections()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get connections: %w", err)
|
||||
}
|
||||
|
||||
for _, conn := range connections {
|
||||
settings, err := conn.GetSettings()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
connMeta, ok := settings["connection"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
connType, _ := connMeta["type"].(string)
|
||||
if connType != "vpn" && connType != "wireguard" {
|
||||
continue
|
||||
}
|
||||
|
||||
connID, _ := connMeta["id"].(string)
|
||||
connUUID, _ := connMeta["uuid"].(string)
|
||||
|
||||
if connUUID != uuidOrName && connID != uuidOrName {
|
||||
continue
|
||||
}
|
||||
|
||||
autoconnect := true
|
||||
if ac, ok := connMeta["autoconnect"].(bool); ok {
|
||||
autoconnect = ac
|
||||
}
|
||||
|
||||
config := &VPNConfig{
|
||||
UUID: connUUID,
|
||||
Name: connID,
|
||||
Type: connType,
|
||||
Autoconnect: autoconnect,
|
||||
Data: make(map[string]string),
|
||||
}
|
||||
|
||||
if connType == "vpn" {
|
||||
if vpnSettings, ok := settings["vpn"]; ok {
|
||||
if svcType, ok := vpnSettings["service-type"].(string); ok {
|
||||
config.ServiceType = svcType
|
||||
}
|
||||
if dataMap, ok := vpnSettings["data"].(map[string]string); ok {
|
||||
for k, v := range dataMap {
|
||||
if !strings.Contains(strings.ToLower(k), "password") &&
|
||||
!strings.Contains(strings.ToLower(k), "secret") &&
|
||||
!strings.Contains(strings.ToLower(k), "key") {
|
||||
config.Data[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("VPN connection not found: %s", uuidOrName)
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) UpdateVPNConfig(connUUID string, updates map[string]interface{}) error {
|
||||
s := b.settings
|
||||
if s == nil {
|
||||
var err error
|
||||
s, err = gonetworkmanager.NewSettings()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get settings: %w", err)
|
||||
}
|
||||
b.settings = s
|
||||
}
|
||||
|
||||
settingsMgr := s.(gonetworkmanager.Settings)
|
||||
connections, err := settingsMgr.ListConnections()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get connections: %w", err)
|
||||
}
|
||||
|
||||
for _, conn := range connections {
|
||||
settings, err := conn.GetSettings()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
connMeta, ok := settings["connection"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
connType, _ := connMeta["type"].(string)
|
||||
if connType != "vpn" && connType != "wireguard" {
|
||||
continue
|
||||
}
|
||||
|
||||
existingUUID, _ := connMeta["uuid"].(string)
|
||||
if existingUUID != connUUID {
|
||||
continue
|
||||
}
|
||||
|
||||
if name, ok := updates["name"].(string); ok && name != "" {
|
||||
connMeta["id"] = name
|
||||
}
|
||||
|
||||
if autoconnect, ok := updates["autoconnect"].(bool); ok {
|
||||
connMeta["autoconnect"] = autoconnect
|
||||
}
|
||||
|
||||
if data, ok := updates["data"].(map[string]interface{}); ok {
|
||||
if vpnSettings, ok := settings["vpn"]; ok {
|
||||
existingData, _ := vpnSettings["data"].(map[string]string)
|
||||
if existingData == nil {
|
||||
existingData = make(map[string]string)
|
||||
}
|
||||
for k, v := range data {
|
||||
if strVal, ok := v.(string); ok {
|
||||
existingData[k] = strVal
|
||||
}
|
||||
}
|
||||
vpnSettings["data"] = existingData
|
||||
}
|
||||
}
|
||||
|
||||
if ipv4, ok := settings["ipv4"]; ok {
|
||||
delete(ipv4, "addresses")
|
||||
delete(ipv4, "routes")
|
||||
delete(ipv4, "dns")
|
||||
}
|
||||
if ipv6, ok := settings["ipv6"]; ok {
|
||||
delete(ipv6, "addresses")
|
||||
delete(ipv6, "routes")
|
||||
delete(ipv6, "dns")
|
||||
}
|
||||
|
||||
if err := conn.Update(settings); err != nil {
|
||||
return fmt.Errorf("failed to update connection: %w", err)
|
||||
}
|
||||
|
||||
b.ListVPNProfiles()
|
||||
|
||||
if b.onStateChange != nil {
|
||||
b.onStateChange()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("VPN connection not found: %s", connUUID)
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) SetVPNCredentials(connUUID string, username string, password string, saveToKeyring bool) error {
|
||||
s := b.settings
|
||||
if s == nil {
|
||||
var err error
|
||||
s, err = gonetworkmanager.NewSettings()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get settings: %w", err)
|
||||
}
|
||||
b.settings = s
|
||||
}
|
||||
|
||||
settingsMgr := s.(gonetworkmanager.Settings)
|
||||
connections, err := settingsMgr.ListConnections()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get connections: %w", err)
|
||||
}
|
||||
|
||||
for _, conn := range connections {
|
||||
settings, err := conn.GetSettings()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
connMeta, ok := settings["connection"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
connType, _ := connMeta["type"].(string)
|
||||
if connType != "vpn" && connType != "wireguard" {
|
||||
continue
|
||||
}
|
||||
|
||||
existingUUID, _ := connMeta["uuid"].(string)
|
||||
if existingUUID != connUUID {
|
||||
continue
|
||||
}
|
||||
|
||||
vpnSettings, ok := settings["vpn"]
|
||||
if !ok {
|
||||
vpnSettings = make(map[string]interface{})
|
||||
settings["vpn"] = vpnSettings
|
||||
}
|
||||
|
||||
existingData, _ := vpnSettings["data"].(map[string]string)
|
||||
if existingData == nil {
|
||||
existingData = make(map[string]string)
|
||||
}
|
||||
|
||||
if username != "" {
|
||||
existingData["username"] = username
|
||||
}
|
||||
|
||||
if saveToKeyring {
|
||||
existingData["password-flags"] = "0"
|
||||
} else {
|
||||
existingData["password-flags"] = "2"
|
||||
}
|
||||
|
||||
vpnSettings["data"] = existingData
|
||||
|
||||
if password != "" {
|
||||
secrets := make(map[string]string)
|
||||
secrets["password"] = password
|
||||
vpnSettings["secrets"] = secrets
|
||||
}
|
||||
|
||||
if ipv4, ok := settings["ipv4"]; ok {
|
||||
delete(ipv4, "addresses")
|
||||
delete(ipv4, "routes")
|
||||
delete(ipv4, "dns")
|
||||
}
|
||||
if ipv6, ok := settings["ipv6"]; ok {
|
||||
delete(ipv6, "addresses")
|
||||
delete(ipv6, "routes")
|
||||
delete(ipv6, "dns")
|
||||
}
|
||||
|
||||
if err := conn.Update(settings); err != nil {
|
||||
return fmt.Errorf("failed to update connection: %w", err)
|
||||
}
|
||||
|
||||
log.Infof("Updated VPN credentials for %s (save=%v)", connUUID, saveToKeyring)
|
||||
|
||||
if b.onStateChange != nil {
|
||||
b.onStateChange()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("VPN connection not found: %s", connUUID)
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) DeleteVPN(uuidOrName string) error {
|
||||
active, _ := b.ListActiveVPN()
|
||||
for _, vpn := range active {
|
||||
if vpn.UUID == uuidOrName || vpn.Name == uuidOrName {
|
||||
if err := b.DisconnectVPN(uuidOrName); err != nil {
|
||||
log.Warnf("Failed to disconnect VPN before deletion: %v", err)
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
s := b.settings
|
||||
if s == nil {
|
||||
var err error
|
||||
s, err = gonetworkmanager.NewSettings()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get settings: %w", err)
|
||||
}
|
||||
b.settings = s
|
||||
}
|
||||
|
||||
settingsMgr := s.(gonetworkmanager.Settings)
|
||||
connections, err := settingsMgr.ListConnections()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get connections: %w", err)
|
||||
}
|
||||
|
||||
for _, conn := range connections {
|
||||
settings, err := conn.GetSettings()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
connMeta, ok := settings["connection"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
connType, _ := connMeta["type"].(string)
|
||||
if connType != "vpn" && connType != "wireguard" {
|
||||
continue
|
||||
}
|
||||
|
||||
connID, _ := connMeta["id"].(string)
|
||||
connUUID, _ := connMeta["uuid"].(string)
|
||||
|
||||
if connUUID == uuidOrName || connID == uuidOrName {
|
||||
if err := conn.Delete(); err != nil {
|
||||
return fmt.Errorf("failed to delete VPN: %w", err)
|
||||
}
|
||||
|
||||
b.ListVPNProfiles()
|
||||
|
||||
if b.onStateChange != nil {
|
||||
b.onStateChange()
|
||||
}
|
||||
|
||||
log.Infof("Deleted VPN connection: %s (%s)", connID, connUUID)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("VPN connection not found: %s", uuidOrName)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -579,31 +579,59 @@ func (b *NetworkManagerBackend) createAndConnectWiFi(req ConnectionRequest) erro
|
||||
"key-mgmt": "wpa-eap",
|
||||
}
|
||||
|
||||
eapMethod := "peap"
|
||||
if req.EAPMethod != "" {
|
||||
eapMethod = req.EAPMethod
|
||||
}
|
||||
|
||||
phase2Auth := "mschapv2"
|
||||
if req.Phase2Auth != "" {
|
||||
phase2Auth = req.Phase2Auth
|
||||
}
|
||||
|
||||
useSystemCACerts := false
|
||||
if req.UseSystemCACerts != nil {
|
||||
useSystemCACerts = *req.UseSystemCACerts
|
||||
}
|
||||
|
||||
x := map[string]interface{}{
|
||||
"eap": []string{"peap"},
|
||||
"phase2-auth": "mschapv2",
|
||||
"system-ca-certs": false,
|
||||
"eap": []string{eapMethod},
|
||||
"system-ca-certs": useSystemCACerts,
|
||||
"password-flags": uint32(0),
|
||||
}
|
||||
|
||||
switch eapMethod {
|
||||
case "peap", "ttls":
|
||||
x["phase2-auth"] = phase2Auth
|
||||
case "tls":
|
||||
if req.ClientCertPath != "" {
|
||||
x["client-cert"] = []byte("file://" + req.ClientCertPath)
|
||||
}
|
||||
if req.PrivateKeyPath != "" {
|
||||
x["private-key"] = []byte("file://" + req.PrivateKeyPath)
|
||||
}
|
||||
}
|
||||
|
||||
if req.Username != "" {
|
||||
x["identity"] = req.Username
|
||||
}
|
||||
if req.Password != "" {
|
||||
x["password"] = req.Password
|
||||
}
|
||||
|
||||
if req.AnonymousIdentity != "" {
|
||||
x["anonymous-identity"] = req.AnonymousIdentity
|
||||
}
|
||||
if req.DomainSuffixMatch != "" {
|
||||
x["domain-suffix-match"] = req.DomainSuffixMatch
|
||||
}
|
||||
if req.CACertPath != "" {
|
||||
x["ca-cert"] = []byte("file://" + req.CACertPath)
|
||||
}
|
||||
|
||||
settings["802-1x"] = x
|
||||
|
||||
log.Infof("[createAndConnectWiFi] WPA-EAP settings: eap=peap, phase2-auth=mschapv2, identity=%s, interactive=%v, system-ca-certs=%v, domain-suffix-match=%q",
|
||||
req.Username, req.Interactive, x["system-ca-certs"], req.DomainSuffixMatch)
|
||||
log.Infof("[createAndConnectWiFi] WPA-EAP settings: eap=%s, phase2-auth=%s, identity=%s, interactive=%v, system-ca-certs=%v, domain-suffix-match=%q",
|
||||
eapMethod, phase2Auth, req.Username, req.Interactive, useSystemCACerts, req.DomainSuffixMatch)
|
||||
|
||||
case isPsk:
|
||||
sec := map[string]interface{}{
|
||||
@@ -716,3 +744,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"}
|
||||
|
||||
@@ -70,6 +70,18 @@ func HandleRequest(conn net.Conn, req Request, manager *Manager) {
|
||||
handleDisconnectAllVPN(conn, req, manager)
|
||||
case "network.vpn.clearCredentials":
|
||||
handleClearVPNCredentials(conn, req, manager)
|
||||
case "network.vpn.plugins":
|
||||
handleListVPNPlugins(conn, req, manager)
|
||||
case "network.vpn.import":
|
||||
handleImportVPN(conn, req, manager)
|
||||
case "network.vpn.getConfig":
|
||||
handleGetVPNConfig(conn, req, manager)
|
||||
case "network.vpn.updateConfig":
|
||||
handleUpdateVPNConfig(conn, req, manager)
|
||||
case "network.vpn.delete":
|
||||
handleDeleteVPN(conn, req, manager)
|
||||
case "network.vpn.setCredentials":
|
||||
handleSetVPNCredentials(conn, req, manager)
|
||||
case "network.wifi.setAutoconnect":
|
||||
handleSetWiFiAutoconnect(conn, req, manager)
|
||||
default:
|
||||
@@ -135,7 +147,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 +182,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 +192,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)
|
||||
@@ -190,6 +212,24 @@ func handleConnectWiFi(conn net.Conn, req Request, manager *Manager) {
|
||||
if domainSuffixMatch, ok := req.Params["domainSuffixMatch"].(string); ok {
|
||||
connReq.DomainSuffixMatch = domainSuffixMatch
|
||||
}
|
||||
if eapMethod, ok := req.Params["eapMethod"].(string); ok {
|
||||
connReq.EAPMethod = eapMethod
|
||||
}
|
||||
if phase2Auth, ok := req.Params["phase2Auth"].(string); ok {
|
||||
connReq.Phase2Auth = phase2Auth
|
||||
}
|
||||
if caCertPath, ok := req.Params["caCertPath"].(string); ok {
|
||||
connReq.CACertPath = caCertPath
|
||||
}
|
||||
if clientCertPath, ok := req.Params["clientCertPath"].(string); ok {
|
||||
connReq.ClientCertPath = clientCertPath
|
||||
}
|
||||
if privateKeyPath, ok := req.Params["privateKeyPath"].(string); ok {
|
||||
connReq.PrivateKeyPath = privateKeyPath
|
||||
}
|
||||
if useSystemCACerts, ok := req.Params["useSystemCACerts"].(bool); ok {
|
||||
connReq.UseSystemCACerts = &useSystemCACerts
|
||||
}
|
||||
|
||||
if err := manager.ConnectWiFi(connReq); err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
@@ -200,7 +240,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
|
||||
}
|
||||
@@ -270,7 +317,14 @@ func handleConnectEthernet(conn net.Conn, req Request, manager *Manager) {
|
||||
}
|
||||
|
||||
func handleDisconnectEthernet(conn net.Conn, req Request, manager *Manager) {
|
||||
if err := manager.DisconnectEthernet(); err != nil {
|
||||
device, _ := req.Params["device"].(string)
|
||||
var err error
|
||||
if device != "" {
|
||||
err = manager.DisconnectEthernetDevice(device)
|
||||
} else {
|
||||
err = manager.DisconnectEthernet()
|
||||
}
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
@@ -485,3 +539,138 @@ func handleSetWiFiAutoconnect(conn net.Conn, req Request, manager *Manager) {
|
||||
|
||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "autoconnect updated"})
|
||||
}
|
||||
|
||||
func handleListVPNPlugins(conn net.Conn, req Request, manager *Manager) {
|
||||
plugins, err := manager.ListVPNPlugins()
|
||||
if err != nil {
|
||||
log.Warnf("handleListVPNPlugins: failed to list plugins: %v", err)
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to list VPN plugins: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
models.Respond(conn, req.ID, plugins)
|
||||
}
|
||||
|
||||
func handleImportVPN(conn net.Conn, req Request, manager *Manager) {
|
||||
filePath, ok := req.Params["file"].(string)
|
||||
if !ok {
|
||||
filePath, ok = req.Params["path"].(string)
|
||||
}
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing 'file' or 'path' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
name, _ := req.Params["name"].(string)
|
||||
|
||||
result, err := manager.ImportVPN(filePath, name)
|
||||
if err != nil {
|
||||
log.Warnf("handleImportVPN: failed to import: %v", err)
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to import VPN: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
models.Respond(conn, req.ID, result)
|
||||
}
|
||||
|
||||
func handleGetVPNConfig(conn net.Conn, req Request, manager *Manager) {
|
||||
uuidOrName, ok := req.Params["uuid"].(string)
|
||||
if !ok {
|
||||
uuidOrName, ok = req.Params["name"].(string)
|
||||
}
|
||||
if !ok {
|
||||
uuidOrName, ok = req.Params["uuidOrName"].(string)
|
||||
}
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing 'uuid', 'name', or 'uuidOrName' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
config, err := manager.GetVPNConfig(uuidOrName)
|
||||
if err != nil {
|
||||
log.Warnf("handleGetVPNConfig: failed to get config: %v", err)
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to get VPN config: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
models.Respond(conn, req.ID, config)
|
||||
}
|
||||
|
||||
func handleUpdateVPNConfig(conn net.Conn, req Request, manager *Manager) {
|
||||
connUUID, ok := req.Params["uuid"].(string)
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing 'uuid' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
updates := make(map[string]interface{})
|
||||
|
||||
if name, ok := req.Params["name"].(string); ok {
|
||||
updates["name"] = name
|
||||
}
|
||||
if autoconnect, ok := req.Params["autoconnect"].(bool); ok {
|
||||
updates["autoconnect"] = autoconnect
|
||||
}
|
||||
if data, ok := req.Params["data"].(map[string]interface{}); ok {
|
||||
updates["data"] = data
|
||||
}
|
||||
|
||||
if len(updates) == 0 {
|
||||
models.RespondError(conn, req.ID, "no updates provided")
|
||||
return
|
||||
}
|
||||
|
||||
if err := manager.UpdateVPNConfig(connUUID, updates); err != nil {
|
||||
log.Warnf("handleUpdateVPNConfig: failed to update: %v", err)
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to update VPN config: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "VPN config updated"})
|
||||
}
|
||||
|
||||
func handleDeleteVPN(conn net.Conn, req Request, manager *Manager) {
|
||||
uuidOrName, ok := req.Params["uuid"].(string)
|
||||
if !ok {
|
||||
uuidOrName, ok = req.Params["name"].(string)
|
||||
}
|
||||
if !ok {
|
||||
uuidOrName, ok = req.Params["uuidOrName"].(string)
|
||||
}
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing 'uuid', 'name', or 'uuidOrName' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
if err := manager.DeleteVPN(uuidOrName); err != nil {
|
||||
log.Warnf("handleDeleteVPN: failed to delete: %v", err)
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to delete VPN: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "VPN deleted"})
|
||||
}
|
||||
|
||||
func handleSetVPNCredentials(conn net.Conn, req Request, manager *Manager) {
|
||||
connUUID, ok := req.Params["uuid"].(string)
|
||||
if !ok {
|
||||
models.RespondError(conn, req.ID, "missing 'uuid' parameter")
|
||||
return
|
||||
}
|
||||
|
||||
username, _ := req.Params["username"].(string)
|
||||
password, _ := req.Params["password"].(string)
|
||||
|
||||
save := true
|
||||
if saveParam, ok := req.Params["save"].(bool); ok {
|
||||
save = saveParam
|
||||
}
|
||||
|
||||
if err := manager.SetVPNCredentials(connUUID, username, password, save); err != nil {
|
||||
log.Warnf("handleSetVPNCredentials: failed to set credentials: %v", err)
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to set VPN credentials: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
models.Respond(conn, req.ID, SuccessResult{Success: true, Message: "VPN credentials set"})
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@ func (m *Manager) syncStateFromBackend() error {
|
||||
m.state.EthernetDevice = backendState.EthernetDevice
|
||||
m.state.EthernetConnected = backendState.EthernetConnected
|
||||
m.state.EthernetConnectionUuid = backendState.EthernetConnectionUuid
|
||||
m.state.EthernetDevices = backendState.EthernetDevices
|
||||
m.state.WiFiIP = backendState.WiFiIP
|
||||
m.state.WiFiDevice = backendState.WiFiDevice
|
||||
m.state.WiFiConnected = backendState.WiFiConnected
|
||||
@@ -117,11 +118,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,7 +154,9 @@ 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.EthernetDevices = append([]EthernetDevice(nil), m.state.EthernetDevices...)
|
||||
s.VPNProfiles = append([]VPNProfile(nil), m.state.VPNProfiles...)
|
||||
s.VPNActive = append([]VPNActive(nil), m.state.VPNActive...)
|
||||
return s
|
||||
@@ -204,9 +209,15 @@ 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
|
||||
}
|
||||
if len(old.EthernetDevices) != len(new.EthernetDevices) {
|
||||
return true
|
||||
}
|
||||
|
||||
for i := range old.WiFiNetworks {
|
||||
oldNet := &old.WiFiNetworks[i]
|
||||
@@ -236,6 +247,23 @@ func stateChangedMeaningfully(old, new *NetworkState) bool {
|
||||
}
|
||||
}
|
||||
|
||||
for i := range old.EthernetDevices {
|
||||
oldDev := &old.EthernetDevices[i]
|
||||
newDev := &new.EthernetDevices[i]
|
||||
if oldDev.Name != newDev.Name {
|
||||
return true
|
||||
}
|
||||
if oldDev.Connected != newDev.Connected {
|
||||
return true
|
||||
}
|
||||
if oldDev.State != newDev.State {
|
||||
return true
|
||||
}
|
||||
if oldDev.IP != newDev.IP {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Check VPN profiles count
|
||||
if len(old.VPNProfiles) != len(new.VPNProfiles) {
|
||||
return true
|
||||
@@ -474,6 +502,18 @@ func (m *Manager) DisconnectEthernet() error {
|
||||
return m.backend.DisconnectEthernet()
|
||||
}
|
||||
|
||||
func (m *Manager) DisconnectEthernetDevice(device string) error {
|
||||
return m.backend.DisconnectEthernetDevice(device)
|
||||
}
|
||||
|
||||
func (m *Manager) GetEthernetDevices() []EthernetDevice {
|
||||
m.stateMutex.RLock()
|
||||
defer m.stateMutex.RUnlock()
|
||||
devices := make([]EthernetDevice, len(m.state.EthernetDevices))
|
||||
copy(devices, m.state.EthernetDevices)
|
||||
return devices
|
||||
}
|
||||
|
||||
func (m *Manager) activateConnection(uuid string) error {
|
||||
return m.backend.ActivateWiredConnection(uuid)
|
||||
}
|
||||
@@ -502,6 +542,46 @@ func (m *Manager) ClearVPNCredentials(uuidOrName string) error {
|
||||
return m.backend.ClearVPNCredentials(uuidOrName)
|
||||
}
|
||||
|
||||
func (m *Manager) ListVPNPlugins() ([]VPNPlugin, error) {
|
||||
return m.backend.ListVPNPlugins()
|
||||
}
|
||||
|
||||
func (m *Manager) ImportVPN(filePath string, name string) (*VPNImportResult, error) {
|
||||
return m.backend.ImportVPN(filePath, name)
|
||||
}
|
||||
|
||||
func (m *Manager) GetVPNConfig(uuidOrName string) (*VPNConfig, error) {
|
||||
return m.backend.GetVPNConfig(uuidOrName)
|
||||
}
|
||||
|
||||
func (m *Manager) UpdateVPNConfig(uuid string, updates map[string]interface{}) error {
|
||||
return m.backend.UpdateVPNConfig(uuid, updates)
|
||||
}
|
||||
|
||||
func (m *Manager) DeleteVPN(uuidOrName string) error {
|
||||
return m.backend.DeleteVPN(uuidOrName)
|
||||
}
|
||||
|
||||
func (m *Manager) SetVPNCredentials(uuid, username, password string, save bool) error {
|
||||
return m.backend.SetVPNCredentials(uuid, username, password, save)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ func (b *SubscriptionBroker) Ask(ctx context.Context, req PromptRequest) (string
|
||||
VpnService: req.VpnService,
|
||||
Setting: req.SettingName,
|
||||
Fields: req.Fields,
|
||||
FieldsInfo: req.FieldsInfo,
|
||||
Hints: req.Hints,
|
||||
Reason: req.Reason,
|
||||
ConnectionId: req.ConnectionId,
|
||||
|
||||
@@ -37,6 +37,29 @@ 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 EthernetDevice struct {
|
||||
Name string `json:"name"`
|
||||
HwAddress string `json:"hwAddress"`
|
||||
State string `json:"state"`
|
||||
Connected bool `json:"connected"`
|
||||
IP string `json:"ip,omitempty"`
|
||||
Speed uint32 `json:"speed,omitempty"`
|
||||
Driver string `json:"driver,omitempty"`
|
||||
}
|
||||
|
||||
type VPNProfile struct {
|
||||
@@ -44,6 +67,10 @@ type VPNProfile struct {
|
||||
UUID string `json:"uuid"`
|
||||
Type string `json:"type"`
|
||||
ServiceType string `json:"serviceType"`
|
||||
RemoteHost string `json:"remoteHost,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Autoconnect bool `json:"autoconnect"`
|
||||
Data map[string]string `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type VPNActive struct {
|
||||
@@ -53,6 +80,12 @@ type VPNActive struct {
|
||||
State string `json:"state,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Plugin string `json:"serviceType"`
|
||||
IP string `json:"ip,omitempty"`
|
||||
Gateway string `json:"gateway,omitempty"`
|
||||
RemoteHost string `json:"remoteHost,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
MTU uint32 `json:"mtu,omitempty"`
|
||||
Data map[string]string `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type VPNState struct {
|
||||
@@ -68,6 +101,7 @@ type NetworkState struct {
|
||||
EthernetDevice string `json:"ethernetDevice"`
|
||||
EthernetConnected bool `json:"ethernetConnected"`
|
||||
EthernetConnectionUuid string `json:"ethernetConnectionUuid"`
|
||||
EthernetDevices []EthernetDevice `json:"ethernetDevices"`
|
||||
WiFiIP string `json:"wifiIP"`
|
||||
WiFiDevice string `json:"wifiDevice"`
|
||||
WiFiConnected bool `json:"wifiConnected"`
|
||||
@@ -76,11 +110,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 +127,13 @@ type ConnectionRequest struct {
|
||||
AnonymousIdentity string `json:"anonymousIdentity,omitempty"`
|
||||
DomainSuffixMatch string `json:"domainSuffixMatch,omitempty"`
|
||||
Interactive bool `json:"interactive,omitempty"`
|
||||
Device string `json:"device,omitempty"`
|
||||
EAPMethod string `json:"eapMethod,omitempty"`
|
||||
Phase2Auth string `json:"phase2Auth,omitempty"`
|
||||
CACertPath string `json:"caCertPath,omitempty"`
|
||||
ClientCertPath string `json:"clientCertPath,omitempty"`
|
||||
PrivateKeyPath string `json:"privateKeyPath,omitempty"`
|
||||
UseSystemCACerts *bool `json:"useSystemCACerts,omitempty"`
|
||||
}
|
||||
|
||||
type WiredConnection struct {
|
||||
@@ -140,6 +183,7 @@ type PromptRequest struct {
|
||||
VpnService string `json:"vpnService"`
|
||||
SettingName string `json:"setting"`
|
||||
Fields []string `json:"fields"`
|
||||
FieldsInfo []FieldInfo `json:"fieldsInfo"`
|
||||
Hints []string `json:"hints"`
|
||||
Reason string `json:"reason"`
|
||||
ConnectionId string `json:"connectionId"`
|
||||
@@ -153,6 +197,12 @@ type PromptReply struct {
|
||||
Cancel bool `json:"cancel"`
|
||||
}
|
||||
|
||||
type FieldInfo struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
IsSecret bool `json:"isSecret"`
|
||||
}
|
||||
|
||||
type CredentialPrompt struct {
|
||||
Token string `json:"token"`
|
||||
Name string `json:"name"`
|
||||
@@ -161,6 +211,7 @@ type CredentialPrompt struct {
|
||||
VpnService string `json:"vpnService"`
|
||||
Setting string `json:"setting"`
|
||||
Fields []string `json:"fields"`
|
||||
FieldsInfo []FieldInfo `json:"fieldsInfo"`
|
||||
Hints []string `json:"hints"`
|
||||
Reason string `json:"reason"`
|
||||
ConnectionId string `json:"connectionId"`
|
||||
@@ -187,3 +238,28 @@ type WiredIPConfig struct {
|
||||
Gateway string `json:"gateway"`
|
||||
DNS string `json:"dns"`
|
||||
}
|
||||
|
||||
type VPNPlugin struct {
|
||||
Name string `json:"name"`
|
||||
ServiceType string `json:"serviceType"`
|
||||
Program string `json:"program,omitempty"`
|
||||
Supports []string `json:"supports,omitempty"`
|
||||
FileExtensions []string `json:"fileExtensions"`
|
||||
}
|
||||
|
||||
type VPNConfig struct {
|
||||
UUID string `json:"uuid"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
ServiceType string `json:"serviceType,omitempty"`
|
||||
Autoconnect bool `json:"autoconnect"`
|
||||
Data map[string]string `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type VPNImportResult struct {
|
||||
Success bool `json:"success"`
|
||||
UUID string `json:"uuid,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
ServiceType string `json:"serviceType,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
@@ -21,3 +21,31 @@ func TestManager_GetWiredConfigs(t *testing.T) {
|
||||
assert.Len(t, configs, 1)
|
||||
assert.Equal(t, "Test", configs[0].ID)
|
||||
}
|
||||
|
||||
func TestManager_GetEthernetDevices(t *testing.T) {
|
||||
manager := &Manager{
|
||||
state: &NetworkState{
|
||||
EthernetDevices: []EthernetDevice{
|
||||
{Name: "enp0s3", Connected: true, IP: "192.168.1.100"},
|
||||
{Name: "enp0s8", Connected: false},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
devices := manager.GetEthernetDevices()
|
||||
|
||||
assert.Len(t, devices, 2)
|
||||
assert.Equal(t, "enp0s3", devices[0].Name)
|
||||
assert.True(t, devices[0].Connected)
|
||||
assert.Equal(t, "enp0s8", devices[1].Name)
|
||||
assert.False(t, devices[1].Connected)
|
||||
}
|
||||
|
||||
func TestManager_GetEthernetDevices_Empty(t *testing.T) {
|
||||
manager := &Manager{
|
||||
state: &NetworkState{},
|
||||
}
|
||||
|
||||
devices := manager.GetEthernetDevices()
|
||||
assert.Empty(t, devices)
|
||||
}
|
||||
|
||||
@@ -31,7 +31,9 @@ import (
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
|
||||
)
|
||||
|
||||
const APIVersion = 19
|
||||
const APIVersion = 22
|
||||
|
||||
var CLIVersion = "dev"
|
||||
|
||||
type Capabilities struct {
|
||||
Capabilities []string `json:"capabilities"`
|
||||
@@ -39,6 +41,7 @@ type Capabilities struct {
|
||||
|
||||
type ServerInfo struct {
|
||||
APIVersion int `json:"apiVersion"`
|
||||
CLIVersion string `json:"cliVersion,omitempty"`
|
||||
Capabilities []string `json:"capabilities"`
|
||||
}
|
||||
|
||||
@@ -316,7 +319,6 @@ func handleConnection(conn net.Conn) {
|
||||
capsData, _ := json.Marshal(caps)
|
||||
conn.Write(capsData)
|
||||
conn.Write([]byte("\n"))
|
||||
|
||||
scanner := bufio.NewScanner(conn)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Bytes()
|
||||
@@ -431,6 +433,7 @@ func getServerInfo() ServerInfo {
|
||||
|
||||
return ServerInfo{
|
||||
APIVersion: APIVersion,
|
||||
CLIVersion: CLIVersion,
|
||||
Capabilities: caps,
|
||||
}
|
||||
}
|
||||
@@ -1071,10 +1074,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?, eapMethod?, phase2Auth?, caCertPath?, clientCertPath?, privateKeyPath?, useSystemCACerts?)")
|
||||
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")
|
||||
@@ -1089,6 +1092,11 @@ func Start(printDocs bool) error {
|
||||
log.Info(" network.vpn.disconnect - Disconnect VPN (params: uuidOrName|name|uuid)")
|
||||
log.Info(" network.vpn.disconnectAll - Disconnect all VPNs")
|
||||
log.Info(" network.vpn.clearCredentials - Clear saved VPN credentials (params: uuidOrName|name|uuid)")
|
||||
log.Info(" network.vpn.plugins - List available VPN plugins")
|
||||
log.Info(" network.vpn.import - Import VPN from file (params: file|path, name?)")
|
||||
log.Info(" network.vpn.getConfig - Get VPN configuration (params: uuid|name|uuidOrName)")
|
||||
log.Info(" network.vpn.updateConfig - Update VPN configuration (params: uuid, name?, autoconnect?, data?)")
|
||||
log.Info(" network.vpn.delete - Delete VPN connection (params: uuid|name|uuidOrName)")
|
||||
log.Info(" network.preference.set - Set preference (params: preference [auto|wifi|ethernet])")
|
||||
log.Info(" network.info - Get network info (params: ssid)")
|
||||
log.Info(" network.credentials.submit - Submit credentials for prompt (params: token, secrets, save?)")
|
||||
|
||||
@@ -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,17 +1227,8 @@ 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)
|
||||
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)
|
||||
@@ -1287,21 +1243,17 @@ func (m *Manager) SetEnabled(enabled bool) {
|
||||
})
|
||||
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()
|
||||
|
||||
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")
|
||||
})
|
||||
} else {
|
||||
log.Infof("Disabling: transitioning to %dK before destroying controls", identityTemp)
|
||||
m.startTransition(identityTemp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,8 +139,6 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m.updateSelectWindowManagerState(msg)
|
||||
case StateSelectTerminal:
|
||||
return m.updateSelectTerminalState(msg)
|
||||
case StateMissingWMInstructions:
|
||||
return m.updateMissingWMInstructionsState(msg)
|
||||
case StateDetectingDeps:
|
||||
return m.updateDetectingDepsState(msg)
|
||||
case StateDependencyReview:
|
||||
@@ -183,8 +181,6 @@ func (m Model) View() string {
|
||||
return m.viewSelectWindowManager()
|
||||
case StateSelectTerminal:
|
||||
return m.viewSelectTerminal()
|
||||
case StateMissingWMInstructions:
|
||||
return m.viewMissingWMInstructions()
|
||||
case StateDetectingDeps:
|
||||
return m.viewDetectingDeps()
|
||||
case StateDependencyReview:
|
||||
|
||||
@@ -6,7 +6,6 @@ const (
|
||||
StateWelcome ApplicationState = iota
|
||||
StateSelectWindowManager
|
||||
StateSelectTerminal
|
||||
StateMissingWMInstructions
|
||||
StateDetectingDeps
|
||||
StateDependencyReview
|
||||
StateGentooUseFlags
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
func (m Model) viewMissingWMInstructions() string {
|
||||
var b strings.Builder
|
||||
|
||||
b.WriteString(m.renderBanner())
|
||||
b.WriteString("\n\n")
|
||||
|
||||
// Determine which WM is missing
|
||||
wmName := "Niri"
|
||||
installCmd := `environment.systemPackages = with pkgs; [
|
||||
niri
|
||||
];`
|
||||
alternateCmd := `# Or enable the module if available:
|
||||
# programs.niri.enable = true;`
|
||||
|
||||
if m.selectedWM == 1 {
|
||||
wmName = "Hyprland"
|
||||
installCmd = `programs.hyprland.enable = true;`
|
||||
alternateCmd = `# Or add to systemPackages:
|
||||
# environment.systemPackages = with pkgs; [
|
||||
# hyprland
|
||||
# ];`
|
||||
}
|
||||
|
||||
// Title
|
||||
title := m.styles.Title.Render("⚠️ " + wmName + " Not Installed")
|
||||
b.WriteString(title)
|
||||
b.WriteString("\n\n")
|
||||
|
||||
// Explanation
|
||||
explanation := m.styles.Normal.Render(wmName + " needs to be installed system-wide on NixOS.")
|
||||
b.WriteString(explanation)
|
||||
b.WriteString("\n\n")
|
||||
|
||||
// Instructions
|
||||
instructions := m.styles.Subtle.Render("To install " + wmName + ", add this to your /etc/nixos/configuration.nix:")
|
||||
b.WriteString(instructions)
|
||||
b.WriteString("\n\n")
|
||||
|
||||
// Command box
|
||||
cmdBox := m.styles.CodeBlock.Render(installCmd)
|
||||
b.WriteString(cmdBox)
|
||||
b.WriteString("\n\n")
|
||||
|
||||
// Alternate command
|
||||
altBox := m.styles.Subtle.Render(alternateCmd)
|
||||
b.WriteString(altBox)
|
||||
b.WriteString("\n\n")
|
||||
|
||||
// Rebuild instruction
|
||||
rebuildInstruction := m.styles.Normal.Render("Then rebuild your system:")
|
||||
b.WriteString(rebuildInstruction)
|
||||
b.WriteString("\n")
|
||||
|
||||
rebuildCmd := m.styles.CodeBlock.Render("sudo nixos-rebuild switch")
|
||||
b.WriteString(rebuildCmd)
|
||||
b.WriteString("\n\n")
|
||||
|
||||
// Navigation help
|
||||
help := m.styles.Subtle.Render("Press Esc to go back and select a different window manager, or Ctrl+C to exit")
|
||||
b.WriteString(help)
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (m Model) updateMissingWMInstructionsState(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if keyMsg, ok := msg.(tea.KeyMsg); ok {
|
||||
switch keyMsg.String() {
|
||||
case "esc":
|
||||
// Go back to window manager selection
|
||||
m.state = StateSelectWindowManager
|
||||
return m, m.listenForLogs()
|
||||
case "ctrl+c":
|
||||
return m, tea.Quit
|
||||
}
|
||||
}
|
||||
return m, m.listenForLogs()
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package tui
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
||||
@@ -72,10 +71,10 @@ func (m Model) viewSelectTerminal() string {
|
||||
b.WriteString(title)
|
||||
b.WriteString("\n\n")
|
||||
|
||||
options := []struct {
|
||||
var options []struct {
|
||||
name string
|
||||
description string
|
||||
}{}
|
||||
}
|
||||
|
||||
if m.osInfo != nil && m.osInfo.Distribution.ID == "gentoo" {
|
||||
options = []struct {
|
||||
@@ -140,20 +139,6 @@ func (m Model) updateSelectTerminalState(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.selectedTerminal++
|
||||
}
|
||||
case "enter":
|
||||
if m.osInfo != nil && m.osInfo.Distribution.ID == "nixos" {
|
||||
var wmInstalled bool
|
||||
if m.selectedWM == 0 {
|
||||
wmInstalled = m.commandExists("niri")
|
||||
} else {
|
||||
wmInstalled = m.commandExists("hyprland") || m.commandExists("Hyprland")
|
||||
}
|
||||
|
||||
if !wmInstalled {
|
||||
m.state = StateMissingWMInstructions
|
||||
return m, m.listenForLogs()
|
||||
}
|
||||
}
|
||||
|
||||
m.state = StateDetectingDeps
|
||||
m.isLoading = true
|
||||
return m, tea.Batch(m.spinner.Tick, m.detectDependencies())
|
||||
@@ -193,11 +178,6 @@ func (m Model) updateSelectWindowManagerState(msg tea.Msg) (tea.Model, tea.Cmd)
|
||||
return m, m.listenForLogs()
|
||||
}
|
||||
|
||||
func (m Model) commandExists(cmd string) bool {
|
||||
_, err := exec.LookPath(cmd)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (m Model) detectDependencies() tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
if m.osInfo == nil {
|
||||
|
||||
@@ -69,7 +69,7 @@ func (m Model) viewWelcome() string {
|
||||
case "debian":
|
||||
errorMsg = fmt.Sprintf("Debian %s is not supported.\n\nOnly Debian 13+ (Trixie) is supported.\n\nPlease upgrade to Debian 13 or later.", m.osInfo.VersionID)
|
||||
case "nixos":
|
||||
errorMsg = "NixOS is currently not supported, but there is a DankMaterialShell flake available."
|
||||
errorMsg = "See the NixOS documentation for installation instructions: https://danklinux.com/docs/dankmaterialshell/nixos."
|
||||
default:
|
||||
errorMsg = fmt.Sprintf("%s is not supported.\nFeel free to request on https://github.com/AvengeMedia/DankMaterialShell", m.osInfo.PrettyName)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
)
|
||||
|
||||
type VersionInfo struct {
|
||||
@@ -18,28 +20,31 @@ 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")
|
||||
}
|
||||
// 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)
|
||||
defer func() {
|
||||
if err := os.Chdir(originalDir); err != nil {
|
||||
log.Warnf("failed to change back to original directory: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := os.Chdir(dmsPath); err != nil {
|
||||
return "", fmt.Errorf("failed to change to DMS directory: %w", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(filepath.Join(dmsPath, ".git")); err == nil {
|
||||
tagCmd := exec.Command("git", "describe", "--exact-match", "--tags", "HEAD")
|
||||
if tagOutput, err := tagCmd.Output(); err == nil {
|
||||
return strings.TrimSpace(string(tagOutput)), nil
|
||||
@@ -65,21 +70,18 @@ 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")
|
||||
|
||||
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)
|
||||
defer func() {
|
||||
if err := os.Chdir(originalDir); err != nil {
|
||||
log.Warnf("failed to change back to original directory: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err := os.Stat(filepath.Join(dmsPath, ".git")); err == nil {
|
||||
if err := os.Chdir(dmsPath); err != nil {
|
||||
return "", fmt.Errorf("failed to change to DMS directory: %w", err)
|
||||
}
|
||||
@@ -93,7 +95,9 @@ func GetLatestDMSVersion() (string, error) {
|
||||
if _, err := tagCmd.Output(); err == nil {
|
||||
// Add timeout to git fetch to prevent hanging
|
||||
fetchCmd := exec.Command("timeout", "5s", "git", "fetch", "origin", "--tags", "--quiet")
|
||||
fetchCmd.Run()
|
||||
if err := fetchCmd.Run(); err != nil {
|
||||
log.Debugf("git fetch tags failed (continuing with local tags): %v", err)
|
||||
}
|
||||
|
||||
latestTagCmd := exec.Command("git", "tag", "-l", "v*", "--sort=-version:refname")
|
||||
latestTagOutput, err := latestTagCmd.Output()
|
||||
@@ -117,7 +121,9 @@ func GetLatestDMSVersion() (string, error) {
|
||||
|
||||
// Add timeout to git fetch to prevent hanging
|
||||
fetchCmd := exec.Command("timeout", "5s", "git", "fetch", "origin", currentBranch, "--quiet")
|
||||
fetchCmd.Run()
|
||||
if err := fetchCmd.Run(); err != nil {
|
||||
log.Debugf("git fetch branch failed (continuing with local ref): %v", err)
|
||||
}
|
||||
|
||||
remoteRevCmd := exec.Command("git", "rev-parse", "--short", fmt.Sprintf("origin/%s", currentBranch))
|
||||
remoteRevOutput, err := remoteRevCmd.Output()
|
||||
@@ -154,13 +160,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)
|
||||
}
|
||||
@@ -203,10 +250,10 @@ func CompareVersions(v1, v2 string) int {
|
||||
for i := 0; i < maxLen; i++ {
|
||||
var p1, p2 int
|
||||
if i < len(parts1) {
|
||||
fmt.Sscanf(parts1[i], "%d", &p1)
|
||||
fmt.Sscanf(parts1[i], "%d", &p1) //nolint:errcheck
|
||||
}
|
||||
if i < len(parts2) {
|
||||
fmt.Sscanf(parts2[i], "%d", &p2)
|
||||
fmt.Sscanf(parts2[i], "%d", &p2) //nolint:errcheck
|
||||
}
|
||||
|
||||
if p1 < p2 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -82,7 +82,6 @@ func (i *Display) Sync() (*Callback, error) {
|
||||
PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff))
|
||||
l += 4
|
||||
PutUint32(_reqBuf[l:l+4], callback.ID())
|
||||
l += 4
|
||||
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
||||
return callback, err
|
||||
}
|
||||
@@ -109,7 +108,6 @@ func (i *Display) GetRegistry() (*Registry, error) {
|
||||
PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff))
|
||||
l += 4
|
||||
PutUint32(_reqBuf[l:l+4], registry.ID())
|
||||
l += 4
|
||||
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
||||
return registry, err
|
||||
}
|
||||
@@ -223,7 +221,6 @@ func (i *Display) Dispatch(opcode uint32, fd int, data []byte) {
|
||||
messageLen := PaddedLen(int(Uint32(data[l : l+4])))
|
||||
l += 4
|
||||
e.Message = String(data[l : l+messageLen])
|
||||
l += messageLen
|
||||
|
||||
i.errorHandler(e)
|
||||
case 1:
|
||||
|
||||
@@ -288,6 +288,7 @@ const (
|
||||
// useful mime types for ipp
|
||||
const (
|
||||
MimeTypePostscript = "application/postscript"
|
||||
MimeTypePDF = "application/pdf"
|
||||
MimeTypeOctetStream = "application/octet-stream"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ipp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -300,22 +300,13 @@ func (c *CUPSClient) GetClasses(attributes []string) (map[string]Attributes, err
|
||||
return printerNameMap, nil
|
||||
}
|
||||
|
||||
// PrintTestPage prints a test page of type application/vnd.cups-pdf-banner
|
||||
func (c *CUPSClient) PrintTestPage(printer string) (int, error) {
|
||||
testPage := new(bytes.Buffer)
|
||||
testPage.WriteString("#PDF-BANNER\n")
|
||||
testPage.WriteString("Template default-testpage.pdf\n")
|
||||
testPage.WriteString("Show printer-name printer-info printer-location printer-make-and-model printer-driver-name")
|
||||
testPage.WriteString("printer-driver-version paper-size imageable-area job-id options time-at-creation")
|
||||
testPage.WriteString("time-at-processing\n\n")
|
||||
|
||||
return c.PrintDocuments([]Document{
|
||||
{
|
||||
Document: testPage,
|
||||
// PrintTestPage prints a test page using the provided PDF data
|
||||
func (c *CUPSClient) PrintTestPage(printer string, testPageData io.Reader, size int) (int, error) {
|
||||
return c.PrintJob(Document{
|
||||
Document: testPageData,
|
||||
Name: "Test Page",
|
||||
Size: testPage.Len(),
|
||||
MimeType: MimeTypePostscript,
|
||||
},
|
||||
Size: size,
|
||||
MimeType: MimeTypePDF,
|
||||
}, printer, map[string]interface{}{
|
||||
AttributeJobName: "Test Page",
|
||||
})
|
||||
|
||||
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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user