mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-06 05:25:41 -05:00
Compare commits
253 Commits
v0.5.1
...
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 | ||
|
|
e39465aece | ||
|
|
8fd616b680 | ||
|
|
cc054b27de | ||
|
|
dfdaa82245 | ||
|
|
99a307e0ad | ||
|
|
5ddea836a1 | ||
|
|
208d92aa06 | ||
|
|
6ef9ddd4f3 | ||
|
|
1c92d39185 | ||
|
|
c0f072217c | ||
|
|
542562f988 | ||
|
|
4e6f0d5e87 | ||
|
|
10639a5ead | ||
|
|
06d668e710 | ||
|
|
d1472dfcba | ||
|
|
ccb4da3cd8 | ||
|
|
46e96b49f0 | ||
|
|
984cfe7f98 | ||
|
|
d769300137 | ||
|
|
d175d66828 | ||
|
|
c1a314332e | ||
|
|
046ac59d21 | ||
|
|
00c06f07d0 | ||
|
|
3e2ab40c6a | ||
|
|
350ffd0052 | ||
|
|
ecd1a622d2 | ||
|
|
f13968aa61 | ||
|
|
4d1ffde54c | ||
|
|
d69017a706 | ||
|
|
f2deaeccdb | ||
|
|
ea9b0d2a79 | ||
|
|
2e6dbedb8b | ||
|
|
6f359df8f9 | ||
|
|
f6db20cd06 | ||
|
|
6287fae065 | ||
|
|
e441607ce3 | ||
|
|
b5379a95fa | ||
|
|
64ec5be919 | ||
|
|
3916512d66 | ||
|
|
e2f426a1bd | ||
|
|
aa1df8dfcf | ||
|
|
67557555f2 | ||
|
|
4cb652abd9 | ||
|
|
d11868b99f | ||
|
|
1798417e6a | ||
|
|
43dc3e5bb1 | ||
|
|
91891a14ed | ||
|
|
20f7d60147 | ||
|
|
7e17e7d37a | ||
|
|
cbb244f785 | ||
|
|
1c264d858b | ||
|
|
217037c2ae | ||
|
|
b4dbd0b69c | ||
|
|
89a2b5c00b | ||
|
|
929b6dae1a | ||
|
|
52fe493da9 | ||
|
|
3e6be3e762 | ||
|
|
7a8cc449b9 | ||
|
|
8f5a9d6e9f | ||
|
|
1c5e31fea9 | ||
|
|
fd08ae18ab | ||
|
|
a7eb3de06e | ||
|
|
8902dd7c44 | ||
|
|
6387d8400c | ||
|
|
597cacb9cc | ||
|
|
3e285ad9ff | ||
|
|
cc1fa89790 | ||
|
|
b0ed007751 | ||
|
|
e1e2650d2b | ||
|
|
b23f17b633 | ||
|
|
818e40b2df | ||
|
|
5685e39631 | ||
|
|
72534b7674 | ||
|
|
328490d23d | ||
|
|
97a0696930 | ||
|
|
cb4e0660e0 | ||
|
|
67c642de4c | ||
|
|
0d7c2e1024 | ||
|
|
16a779a41b | ||
|
|
c4ca3c8644 | ||
|
|
aabcbe34f3 | ||
|
|
f06626e441 | ||
|
|
c4e1a71776 | ||
|
|
77e6c16bd2 | ||
|
|
9d1fac3570 | ||
|
|
b7aeaa7fc5 | ||
|
|
f6d8c9ff61 | ||
|
|
0490794d6c | ||
|
|
335c83dd3c | ||
|
|
91da720c26 | ||
|
|
b6ac744a68 | ||
|
|
526c4092fd | ||
|
|
ed06dda384 | ||
|
|
6465b11e9b | ||
|
|
b2879878a1 | ||
|
|
3e17b086fb | ||
|
|
0545e6bcda | ||
|
|
27a907433f | ||
|
|
69616800e3 |
@@ -1,8 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# DISABLED for now
|
|
||||||
exit 0
|
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
@@ -10,18 +6,61 @@ REPO_ROOT="$(cd "$HOOK_DIR/.." && pwd)"
|
|||||||
|
|
||||||
cd "$REPO_ROOT"
|
cd "$REPO_ROOT"
|
||||||
|
|
||||||
if [[ -z "${POEDITOR_API_TOKEN:-}" ]] || [[ -z "${POEDITOR_PROJECT_ID:-}" ]]; then
|
# =============================================================================
|
||||||
exit 0
|
# Go CI checks (when core/ files are staged)
|
||||||
fi
|
# =============================================================================
|
||||||
|
STAGED_CORE_FILES=$(git diff --cached --name-only --diff-filter=ACMR | grep '^core/' || true)
|
||||||
|
|
||||||
if ! command -v python3 &>/dev/null; then
|
if [[ -n "$STAGED_CORE_FILES" ]]; then
|
||||||
exit 0
|
echo "Go files staged in core/, running CI checks..."
|
||||||
fi
|
cd "$REPO_ROOT/core"
|
||||||
|
|
||||||
if ! python3 scripts/i18nsync.py check &>/dev/null; then
|
# Format check
|
||||||
echo "Translations out of sync"
|
echo " Checking gofmt..."
|
||||||
echo "run python3 scripts/i18nsync.py sync"
|
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
|
exit 1
|
||||||
fi
|
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
|
exit 0
|
||||||
|
|||||||
20
.github/workflows/go-ci.yml
vendored
20
.github/workflows/go-ci.yml
vendored
@@ -7,9 +7,18 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- 'core/**'
|
- 'core/**'
|
||||||
- '.github/workflows/go-ci.yml'
|
- '.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:
|
jobs:
|
||||||
test:
|
lint-and-test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
@@ -32,11 +41,20 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
- name: Run golangci-lint
|
||||||
|
uses: golangci/golangci-lint-action@v6
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
working-directory: core
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: go test -v ./...
|
run: go test -v ./...
|
||||||
|
|
||||||
- name: Build dms
|
- name: Build dms
|
||||||
run: go build -v ./cmd/dms
|
run: go build -v ./cmd/dms
|
||||||
|
|
||||||
|
- name: Build dms (distropkg)
|
||||||
|
run: go build -v -tags distro_binary ./cmd/dms
|
||||||
|
|
||||||
- name: Build dankinstall
|
- name: Build dankinstall
|
||||||
run: go build -v ./cmd/dankinstall
|
run: go build -v ./cmd/dankinstall
|
||||||
|
|||||||
111
.github/workflows/release.yml
vendored
111
.github/workflows/release.yml
vendored
@@ -35,6 +35,14 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version-file: ./core/go.mod
|
go-version-file: ./core/go.mod
|
||||||
|
|
||||||
|
- name: Format check
|
||||||
|
run: |
|
||||||
|
if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then
|
||||||
|
echo "The following files are not formatted:"
|
||||||
|
gofmt -s -l .
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: go test -v ./...
|
run: go test -v ./...
|
||||||
|
|
||||||
@@ -124,38 +132,40 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: build-core
|
needs: build-core
|
||||||
steps:
|
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
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ steps.app_token.outputs.token }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Update VERSION
|
- name: Update VERSION
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ steps.app_token.outputs.token }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
git config user.name "github-actions[bot]"
|
git config user.name "dms-ci[bot]"
|
||||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
git config user.email "dms-ci[bot]@users.noreply.github.com"
|
||||||
|
|
||||||
version="${GITHUB_REF#refs/tags/}"
|
version="${GITHUB_REF#refs/tags/}"
|
||||||
version_no_v="${version#v}"
|
|
||||||
echo "Updating to version: $version"
|
echo "Updating to version: $version"
|
||||||
|
|
||||||
# Update VERSION file in quickshell/
|
|
||||||
echo "${version}" > quickshell/VERSION
|
echo "${version}" > quickshell/VERSION
|
||||||
|
|
||||||
git add quickshell/VERSION
|
git add quickshell/VERSION
|
||||||
|
|
||||||
if ! git diff --cached --quiet; then
|
if ! git diff --cached --quiet; then
|
||||||
git commit -m "chore: bump version to $version"
|
git commit -m "chore: bump version to $version"
|
||||||
git push origin HEAD:master || git push origin HEAD:main
|
git pull --rebase origin master
|
||||||
echo "Pushed version updates to master"
|
git push https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git HEAD:master
|
||||||
else
|
|
||||||
echo "No version changes needed"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Force-push the tag to point to the commit with updated VERSION
|
|
||||||
git tag -f "${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:
|
release:
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
@@ -168,6 +178,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Fetch updated tag after version bump
|
||||||
|
run: |
|
||||||
|
git fetch origin --force tag ${{ github.ref_name }}
|
||||||
|
git checkout ${{ github.ref_name }}
|
||||||
|
|
||||||
- name: Download core artifacts
|
- name: Download core artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
@@ -255,6 +270,9 @@ jobs:
|
|||||||
cp _core_assets/completion.* _release_assets/ 2>/dev/null || true
|
cp _core_assets/completion.* _release_assets/ 2>/dev/null || true
|
||||||
|
|
||||||
# Create QML source package (exclude build artifacts and git files)
|
# Create QML source package (exclude build artifacts and git files)
|
||||||
|
# Copy root LICENSE and CONTRIBUTING.md to quickshell/ for packaging
|
||||||
|
cp LICENSE CONTRIBUTING.md quickshell/
|
||||||
|
|
||||||
# Tar the CONTENTS of quickshell/, not the directory itself
|
# Tar the CONTENTS of quickshell/, not the directory itself
|
||||||
(cd quickshell && tar --exclude='.git' \
|
(cd quickshell && tar --exclude='.git' \
|
||||||
--exclude='.github' \
|
--exclude='.github' \
|
||||||
@@ -291,6 +309,11 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Copy docs directory
|
||||||
|
if [ -d "docs" ]; then
|
||||||
|
cp -r docs _temp_full/
|
||||||
|
fi
|
||||||
|
|
||||||
# Create installation guide
|
# Create installation guide
|
||||||
cat > _temp_full/INSTALL.md << 'EOFINSTALL'
|
cat > _temp_full/INSTALL.md << 'EOFINSTALL'
|
||||||
# DankMaterialShell Installation
|
# DankMaterialShell Installation
|
||||||
@@ -365,6 +388,68 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
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:
|
copr-build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: release
|
needs: release
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
name: DMS Copr Stable Release (Manual)
|
name: DMS Copr Stable Release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -7,6 +7,10 @@ on:
|
|||||||
description: 'Versioning (e.g., 0.1.14, leave empty for latest release)'
|
description: 'Versioning (e.g., 0.1.14, leave empty for latest release)'
|
||||||
required: false
|
required: false
|
||||||
default: ''
|
default: ''
|
||||||
|
release:
|
||||||
|
description: 'Release number (e.g., 1, 2, 3 for hotfixes)'
|
||||||
|
required: false
|
||||||
|
default: '1'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-upload:
|
build-and-upload:
|
||||||
@@ -19,6 +23,7 @@ jobs:
|
|||||||
- name: Determine version
|
- name: Determine version
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
|
# Get version from manual input or latest release
|
||||||
if [ -n "${{ github.event.inputs.version }}" ]; then
|
if [ -n "${{ github.event.inputs.version }}" ]; then
|
||||||
VERSION="${{ github.event.inputs.version }}"
|
VERSION="${{ github.event.inputs.version }}"
|
||||||
echo "Using manual version: $VERSION"
|
echo "Using manual version: $VERSION"
|
||||||
@@ -27,8 +32,14 @@ jobs:
|
|||||||
echo "Using latest release version: $VERSION"
|
echo "Using latest release version: $VERSION"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
RELEASE="${{ github.event.inputs.release }}"
|
||||||
|
if [ -z "$RELEASE" ]; then
|
||||||
|
RELEASE="1"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||||
echo "✅ Building DMS stable version: $VERSION"
|
echo "release=$RELEASE" >> $GITHUB_OUTPUT
|
||||||
|
echo "✅ Building DMS hotfix version: $VERSION-$RELEASE"
|
||||||
|
|
||||||
- name: Setup build environment
|
- name: Setup build environment
|
||||||
run: |
|
run: |
|
||||||
@@ -57,6 +68,7 @@ jobs:
|
|||||||
- name: Generate stable spec file
|
- name: Generate stable spec file
|
||||||
run: |
|
run: |
|
||||||
VERSION="${{ steps.version.outputs.version }}"
|
VERSION="${{ steps.version.outputs.version }}"
|
||||||
|
RELEASE="${{ steps.version.outputs.release }}"
|
||||||
CHANGELOG_DATE="$(date '+%a %b %d %Y')"
|
CHANGELOG_DATE="$(date '+%a %b %d %Y')"
|
||||||
|
|
||||||
cat > ~/rpmbuild/SPECS/dms.spec <<'SPECEOF'
|
cat > ~/rpmbuild/SPECS/dms.spec <<'SPECEOF'
|
||||||
@@ -68,7 +80,7 @@ jobs:
|
|||||||
|
|
||||||
Name: dms
|
Name: dms
|
||||||
Version: %{version}
|
Version: %{version}
|
||||||
Release: 1%{?dist}
|
Release: RELEASE_PLACEHOLDER%{?dist}
|
||||||
Summary: %{pkg_summary}
|
Summary: %{pkg_summary}
|
||||||
|
|
||||||
License: MIT
|
License: MIT
|
||||||
@@ -212,16 +224,17 @@ jobs:
|
|||||||
%{_bindir}/dgop
|
%{_bindir}/dgop
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
* CHANGELOG_DATE_PLACEHOLDER AvengeMedia <contact@avengemedia.com> - VERSION_PLACEHOLDER-1
|
* CHANGELOG_DATE_PLACEHOLDER AvengeMedia <contact@avengemedia.com> - VERSION_PLACEHOLDER-RELEASE_PLACEHOLDER
|
||||||
- Stable release VERSION_PLACEHOLDER
|
- Stable release VERSION_PLACEHOLDER
|
||||||
- Built from GitHub release
|
- Built from GitHub release
|
||||||
- Includes latest dms-cli and dgop binaries
|
- Includes latest dms-cli and dgop binaries
|
||||||
SPECEOF
|
SPECEOF
|
||||||
|
|
||||||
sed -i "s/VERSION_PLACEHOLDER/${VERSION}/g" ~/rpmbuild/SPECS/dms.spec
|
sed -i "s/VERSION_PLACEHOLDER/${VERSION}/g" ~/rpmbuild/SPECS/dms.spec
|
||||||
|
sed -i "s/RELEASE_PLACEHOLDER/${RELEASE}/g" ~/rpmbuild/SPECS/dms.spec
|
||||||
sed -i "s/CHANGELOG_DATE_PLACEHOLDER/${CHANGELOG_DATE}/g" ~/rpmbuild/SPECS/dms.spec
|
sed -i "s/CHANGELOG_DATE_PLACEHOLDER/${CHANGELOG_DATE}/g" ~/rpmbuild/SPECS/dms.spec
|
||||||
|
|
||||||
echo "✅ Spec file generated for v${VERSION}"
|
echo "✅ Spec file generated for v${VERSION}-${RELEASE}"
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Spec file preview ==="
|
echo "=== Spec file preview ==="
|
||||||
head -40 ~/rpmbuild/SPECS/dms.spec
|
head -40 ~/rpmbuild/SPECS/dms.spec
|
||||||
@@ -295,7 +308,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "### 🎉 DMS Stable Build Summary" >> $GITHUB_STEP_SUMMARY
|
echo "### 🎉 DMS Stable Build Summary" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "- **Version:** ${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
|
echo "- **Version:** ${{ steps.version.outputs.version }}-${{ steps.version.outputs.release }}" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "- **SRPM:** ${{ steps.build.outputs.srpm_name }}" >> $GITHUB_STEP_SUMMARY
|
echo "- **SRPM:** ${{ steps.build.outputs.srpm_name }}" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "- **Project:** https://copr.fedorainfracloud.org/coprs/avengemedia/dms/" >> $GITHUB_STEP_SUMMARY
|
echo "- **Project:** https://copr.fedorainfracloud.org/coprs/avengemedia/dms/" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
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
|
name: Update Vendor Hash
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
paths:
|
paths:
|
||||||
- "core/go.mod"
|
- "core/go.mod"
|
||||||
@@ -8,14 +9,25 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
update-vendor-hash:
|
update-vendor-hash:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
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
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
token: ${{ steps.app_token.outputs.token }}
|
||||||
|
|
||||||
- name: Install Nix
|
- name: Install Nix
|
||||||
uses: cachix/install-nix-action@v31
|
uses: cachix/install-nix-action@v31
|
||||||
@@ -23,68 +35,32 @@ jobs:
|
|||||||
- name: Update vendorHash in flake.nix
|
- name: Update vendorHash in flake.nix
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# Try to build and capture the expected hash from error message
|
|
||||||
echo "Attempting nix build to get new vendorHash..."
|
echo "Attempting nix build to get new vendorHash..."
|
||||||
|
|
||||||
if output=$(nix build .#dmsCli 2>&1); then
|
if output=$(nix build .#dmsCli 2>&1); then
|
||||||
echo "Build succeeded, no hash update needed"
|
echo "Build succeeded, no hash update needed"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Extract the expected hash from the error message
|
|
||||||
new_hash=$(echo "$output" | grep -oP "got:\s+\K\S+" | head -n1)
|
new_hash=$(echo "$output" | grep -oP "got:\s+\K\S+" | head -n1)
|
||||||
|
[ -n "$new_hash" ] || { echo "Could not extract new vendorHash"; echo "$output"; exit 1; }
|
||||||
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
|
|
||||||
current_hash=$(grep -oP 'vendorHash = "\K[^"]+' flake.nix)
|
current_hash=$(grep -oP 'vendorHash = "\K[^"]+' flake.nix)
|
||||||
echo "Current vendorHash: $current_hash"
|
[ "$current_hash" = "$new_hash" ] && { echo "vendorHash already up to date"; exit 0; }
|
||||||
|
|
||||||
if [ "$current_hash" = "$new_hash" ]; then
|
|
||||||
echo "vendorHash is already up to date"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Update the hash in flake.nix
|
|
||||||
sed -i "s|vendorHash = \"$current_hash\"|vendorHash = \"$new_hash\"|" flake.nix
|
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..."
|
echo "Verifying build with new vendorHash..."
|
||||||
nix build .#dmsCli
|
nix build .#dmsCli
|
||||||
|
|
||||||
echo "vendorHash updated successfully!"
|
echo "vendorHash updated successfully!"
|
||||||
|
|
||||||
- name: Commit and push vendorHash update
|
- name: Commit and push vendorHash update
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ steps.app_token.outputs.token }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
if ! git diff --quiet flake.nix; then
|
if ! git diff --quiet flake.nix; then
|
||||||
git config user.name "github-actions[bot]"
|
git config user.name "dms-ci[bot]"
|
||||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
git config user.email "dms-ci[bot]@users.noreply.github.com"
|
||||||
|
|
||||||
git add flake.nix
|
git add flake.nix
|
||||||
git commit -m "nix: update vendorHash for go.mod changes"
|
git commit -m "nix: update vendorHash for go.mod changes" || exit 0
|
||||||
|
git pull --rebase origin master
|
||||||
for attempt in 1 2 3; do
|
git push https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git HEAD:master
|
||||||
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
|
|
||||||
else
|
else
|
||||||
echo "No changes to flake.nix"
|
echo "No changes to flake.nix"
|
||||||
fi
|
fi
|
||||||
|
|||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -136,3 +136,9 @@ go.work.sum
|
|||||||
# .vscode/
|
# .vscode/
|
||||||
|
|
||||||
bin/
|
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.
|
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.
|
```bash
|
||||||
|
git config core.hooksPath .githooks
|
||||||
```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
|
|
||||||
},
|
|
||||||
```
|
```
|
||||||
|
|
||||||
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
|
## Pull request
|
||||||
|
|
||||||
|
|||||||
19
README.md
19
README.md
@@ -15,11 +15,11 @@
|
|||||||
[](https://github.com/AvengeMedia/DankMaterialShell/releases)
|
[](https://github.com/AvengeMedia/DankMaterialShell/releases)
|
||||||
[](https://aur.archlinux.org/packages/dms-shell-bin)
|
[](https://aur.archlinux.org/packages/dms-shell-bin)
|
||||||
[)](https://aur.archlinux.org/packages/dms-shell-git)
|
[)](https://aur.archlinux.org/packages/dms-shell-git)
|
||||||
[](https://ko-fi.com/avengemediallc)
|
[](https://ko-fi.com/danklinux)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
DankMaterialShell is a complete desktop shell for [niri](https://github.com/YaLTeR/niri), [Hyprland](https://hyprland.org/), [MangoWC](https://github.com/DreamMaoMao/mangowc), [Sway](https://swaywm.org), and other Wayland compositors. It replaces waybar, swaylock, swayidle, mako, fuzzel, polkit, and everything else you'd normally stitch together to make a desktop.
|
DankMaterialShell is a complete desktop shell for [niri](https://github.com/YaLTeR/niri), [Hyprland](https://hyprland.org/), [MangoWC](https://github.com/DreamMaoMao/mangowc), [Sway](https://swaywm.org), [labwc](https://labwc.github.io/), and other Wayland compositors. It replaces waybar, swaylock, swayidle, mako, fuzzel, polkit, and everything else you'd normally stitch together to make a desktop.
|
||||||
|
|
||||||
## Repository Structure
|
## Repository Structure
|
||||||
|
|
||||||
@@ -36,8 +36,10 @@ DankMaterialShell/
|
|||||||
│ ├── cmd/ # dms CLI and dankinstall binaries
|
│ ├── cmd/ # dms CLI and dankinstall binaries
|
||||||
│ ├── internal/ # System integration, IPC, distro support
|
│ ├── internal/ # System integration, IPC, distro support
|
||||||
│ └── pkg/ # Shared packages
|
│ └── pkg/ # Shared packages
|
||||||
├── distro/ # Distribution packaging (Fedora RPM specs)
|
├── distro/ # Distribution packaging
|
||||||
├── nix/ # NixOS/home-manager modules
|
│ ├── fedora/ # Fedora RPM specs
|
||||||
|
│ ├── debian/ # Debian packaging
|
||||||
|
│ └── nix/ # NixOS/home-manager modules
|
||||||
└── flake.nix # Nix flake for declarative installation
|
└── flake.nix # Nix flake for declarative installation
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -103,7 +105,7 @@ Extend functionality with the [plugin registry](https://plugins.danklinux.com).
|
|||||||
|
|
||||||
## Supported Compositors
|
## Supported Compositors
|
||||||
|
|
||||||
Works best with [niri](https://github.com/YaLTeR/niri), [Hyprland](https://hyprland.org/), [Sway](https://swaywm.org/), and [MangoWC](https://github.com/DreamMaoMao/mangowc) with full workspace switching, overview integration, and monitor management. Other Wayland compositors work with reduced features.
|
Works best with [niri](https://github.com/YaLTeR/niri), [Hyprland](https://hyprland.org/), [Sway](https://swaywm.org/), [MangoWC](https://github.com/DreamMaoMao/mangowc), and [labwc](https://labwc.github.io/) with full workspace switching, overview integration, and monitor management. Other Wayland compositors work with reduced features.
|
||||||
|
|
||||||
[Compositor configuration guide](https://danklinux.com/docs/dankmaterialshell/compositors)
|
[Compositor configuration guide](https://danklinux.com/docs/dankmaterialshell/compositors)
|
||||||
|
|
||||||
@@ -136,8 +138,7 @@ See component-specific documentation:
|
|||||||
|
|
||||||
- **[quickshell/](quickshell/)** - QML shell development, widgets, and modules
|
- **[quickshell/](quickshell/)** - QML shell development, widgets, and modules
|
||||||
- **[core/](core/)** - Go backend, CLI tools, and system integration
|
- **[core/](core/)** - Go backend, CLI tools, and system integration
|
||||||
- **[distro/](distro/)** - Distribution packaging
|
- **[distro/](distro/)** - Distribution packaging (Fedora, Debian, NixOS)
|
||||||
- **[nix/](nix/)** - NixOS and home-manager modules
|
|
||||||
|
|
||||||
### Building from Source
|
### Building from Source
|
||||||
|
|
||||||
@@ -182,6 +183,10 @@ For documentation contributions, see [DankLinux-Docs](https://github.com/AvengeM
|
|||||||
- [soramanew](https://github.com/soramanew) - [Caelestia](https://github.com/caelestia-dots/shell) inspiration
|
- [soramanew](https://github.com/soramanew) - [Caelestia](https://github.com/caelestia-dots/shell) inspiration
|
||||||
- [end-4](https://github.com/end-4) - [dots-hyprland](https://github.com/end-4/dots-hyprland) inspiration
|
- [end-4](https://github.com/end-4) - [dots-hyprland](https://github.com/end-4/dots-hyprland) inspiration
|
||||||
|
|
||||||
|
## Star History
|
||||||
|
|
||||||
|
[](https://www.star-history.com/#AvengeMedia/DankMaterialShell&type=date&legend=top-left)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT License - See [LICENSE](LICENSE) for details.
|
MIT License - See [LICENSE](LICENSE) for details.
|
||||||
|
|||||||
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"
|
||||||
58
core/.mockery.yml
Normal file
58
core/.mockery.yml
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
with-expecter: true
|
||||||
|
dir: "internal/mocks/{{.InterfaceDirRelative}}"
|
||||||
|
mockname: "Mock{{.InterfaceName}}"
|
||||||
|
outpkg: "{{.PackageName}}"
|
||||||
|
packages:
|
||||||
|
github.com/Wifx/gonetworkmanager/v2:
|
||||||
|
interfaces:
|
||||||
|
NetworkManager:
|
||||||
|
Device:
|
||||||
|
DeviceWireless:
|
||||||
|
AccessPoint:
|
||||||
|
Connection:
|
||||||
|
Settings:
|
||||||
|
ActiveConnection:
|
||||||
|
IP4Config:
|
||||||
|
net:
|
||||||
|
interfaces:
|
||||||
|
Conn:
|
||||||
|
github.com/AvengeMedia/danklinux/internal/plugins:
|
||||||
|
interfaces:
|
||||||
|
GitClient:
|
||||||
|
github.com/godbus/dbus/v5:
|
||||||
|
interfaces:
|
||||||
|
BusObject:
|
||||||
|
github.com/AvengeMedia/danklinux/internal/server/brightness:
|
||||||
|
config:
|
||||||
|
dir: "internal/mocks/brightness"
|
||||||
|
outpkg: mocks_brightness
|
||||||
|
interfaces:
|
||||||
|
DBusConn:
|
||||||
|
github.com/AvengeMedia/DankMaterialShell/core/internal/server/network:
|
||||||
|
config:
|
||||||
|
dir: "internal/mocks/network"
|
||||||
|
outpkg: mocks_network
|
||||||
|
interfaces:
|
||||||
|
Backend:
|
||||||
|
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:
|
||||||
@@ -31,6 +31,7 @@ Distribution-aware installer with TUI for deploying DMS and compositor configura
|
|||||||
- DDC/CI protocol - External monitor brightness control (like `ddcutil`)
|
- DDC/CI protocol - External monitor brightness control (like `ddcutil`)
|
||||||
- Backlight control - Internal display brightness via `login1` or sysfs
|
- Backlight control - Internal display brightness via `login1` or sysfs
|
||||||
- LED control - Keyboard/device LED management
|
- LED control - Keyboard/device LED management
|
||||||
|
- evdev input monitoring - Keyboard state tracking (caps lock, etc.)
|
||||||
|
|
||||||
**Plugin System**
|
**Plugin System**
|
||||||
- Plugin registry integration
|
- Plugin registry integration
|
||||||
@@ -71,6 +72,13 @@ sudo make install # Install to /usr/local/bin/dms
|
|||||||
|
|
||||||
## Development
|
## 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:**
|
**Regenerating Wayland Protocol Bindings:**
|
||||||
```bash
|
```bash
|
||||||
go install github.com/rajveermalviya/go-wayland/cmd/go-wayland-scanner@latest
|
go install github.com/rajveermalviya/go-wayland/cmd/go-wayland-scanner@latest
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/logger"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/tui"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/tui"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
)
|
)
|
||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
var Version = "dev"
|
var Version = "dev"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fileLogger, err := logger.NewFileLogger()
|
fileLogger, err := log.NewFileLogger()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Warning: Failed to create log file: %v\n", err)
|
fmt.Printf("Warning: Failed to create log file: %v\n", err)
|
||||||
fmt.Println("Continuing without file logging...")
|
fmt.Println("Continuing without file logging...")
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ func runVersion(cmd *cobra.Command, args []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func startDebugServer() error {
|
func startDebugServer() error {
|
||||||
|
server.CLIVersion = Version
|
||||||
return server.Start(true)
|
return server.Start(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,6 +369,7 @@ func getCommonCommands() []*cobra.Command {
|
|||||||
pluginsCmd,
|
pluginsCmd,
|
||||||
dank16Cmd,
|
dank16Cmd,
|
||||||
brightnessCmd,
|
brightnessCmd,
|
||||||
|
dpmsCmd,
|
||||||
keybindsCmd,
|
keybindsCmd,
|
||||||
greeterCmd,
|
greeterCmd,
|
||||||
setupCmd,
|
setupCmd,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/dank16"
|
"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("alacritty", false, "Output in Alacritty terminal format")
|
||||||
dank16Cmd.Flags().Bool("ghostty", false, "Output in Ghostty terminal format")
|
dank16Cmd.Flags().Bool("ghostty", false, "Output in Ghostty terminal format")
|
||||||
dank16Cmd.Flags().Bool("wezterm", false, "Output in Wezterm 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("background", "", "Custom background color")
|
||||||
dank16Cmd.Flags().String("contrast", "dps", "Contrast algorithm: dps (Delta Phi Star, default) or wcag")
|
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")
|
isAlacritty, _ := cmd.Flags().GetBool("alacritty")
|
||||||
isGhostty, _ := cmd.Flags().GetBool("ghostty")
|
isGhostty, _ := cmd.Flags().GetBool("ghostty")
|
||||||
isWezterm, _ := cmd.Flags().GetBool("wezterm")
|
isWezterm, _ := cmd.Flags().GetBool("wezterm")
|
||||||
vscodeEnrich, _ := cmd.Flags().GetString("vscode-enrich")
|
|
||||||
background, _ := cmd.Flags().GetString("background")
|
background, _ := cmd.Flags().GetString("background")
|
||||||
contrastAlgo, _ := cmd.Flags().GetString("contrast")
|
contrastAlgo, _ := cmd.Flags().GetString("contrast")
|
||||||
|
|
||||||
@@ -65,18 +62,7 @@ func runDank16(cmd *cobra.Command, args []string) {
|
|||||||
|
|
||||||
colors := dank16.GeneratePalette(primaryColor, opts)
|
colors := dank16.GeneratePalette(primaryColor, opts)
|
||||||
|
|
||||||
if vscodeEnrich != "" {
|
if isJson {
|
||||||
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 {
|
|
||||||
fmt.Print(dank16.GenerateJSON(colors))
|
fmt.Print(dank16.GenerateJSON(colors))
|
||||||
} else if isKitty {
|
} else if isKitty {
|
||||||
fmt.Print(dank16.GenerateKittyTheme(colors))
|
fmt.Print(dank16.GenerateKittyTheme(colors))
|
||||||
|
|||||||
84
core/cmd/dms/commands_dpms.go
Normal file
84
core/cmd/dms/commands_dpms.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var dpmsCmd = &cobra.Command{
|
||||||
|
Use: "dpms",
|
||||||
|
Short: "Control display power management",
|
||||||
|
}
|
||||||
|
|
||||||
|
var dpmsOnCmd = &cobra.Command{
|
||||||
|
Use: "on [output]",
|
||||||
|
Short: "Turn display(s) on",
|
||||||
|
Args: cobra.MaximumNArgs(1),
|
||||||
|
Run: runDPMSOn,
|
||||||
|
}
|
||||||
|
|
||||||
|
var dpmsOffCmd = &cobra.Command{
|
||||||
|
Use: "off [output]",
|
||||||
|
Short: "Turn display(s) off",
|
||||||
|
Args: cobra.MaximumNArgs(1),
|
||||||
|
Run: runDPMSOff,
|
||||||
|
}
|
||||||
|
|
||||||
|
var dpmsListCmd = &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Short: "List outputs",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
Run: runDPMSList,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
dpmsCmd.AddCommand(dpmsOnCmd, dpmsOffCmd, dpmsListCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runDPMSOn(cmd *cobra.Command, args []string) {
|
||||||
|
outputName := ""
|
||||||
|
if len(args) > 0 {
|
||||||
|
outputName = args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := newDPMSClient()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("%v", err)
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
if err := client.SetDPMS(outputName, true); err != nil {
|
||||||
|
log.Fatalf("%v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runDPMSOff(cmd *cobra.Command, args []string) {
|
||||||
|
outputName := ""
|
||||||
|
if len(args) > 0 {
|
||||||
|
outputName = args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := newDPMSClient()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("%v", err)
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
if err := client.SetDPMS(outputName, false); err != nil {
|
||||||
|
log.Fatalf("%v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runDPMSList(cmd *cobra.Command, args []string) {
|
||||||
|
client, err := newDPMSClient()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("%v", err)
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
for _, output := range client.ListOutputs() {
|
||||||
|
fmt.Println(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -77,8 +77,6 @@ func runUpdate() {
|
|||||||
switch config.Family {
|
switch config.Family {
|
||||||
case distros.FamilyArch:
|
case distros.FamilyArch:
|
||||||
updateErr = updateArchLinux()
|
updateErr = updateArchLinux()
|
||||||
case distros.FamilyNix:
|
|
||||||
updateErr = updateNixOS()
|
|
||||||
case distros.FamilySUSE:
|
case distros.FamilySUSE:
|
||||||
updateErr = updateOtherDistros()
|
updateErr = updateOtherDistros()
|
||||||
default:
|
default:
|
||||||
@@ -152,27 +150,6 @@ func updateArchLinux() error {
|
|||||||
return nil
|
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 {
|
func updateOtherDistros() error {
|
||||||
homeDir, err := os.UserHomeDir()
|
homeDir, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import (
|
|||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/greeter"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/greeter"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/text/cases"
|
||||||
|
"golang.org/x/text/language"
|
||||||
)
|
)
|
||||||
|
|
||||||
var greeterCmd = &cobra.Command{
|
var greeterCmd = &cobra.Command{
|
||||||
@@ -217,6 +219,191 @@ func checkGroupExists(groupName string) bool {
|
|||||||
return false
|
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 {
|
func enableGreeter() error {
|
||||||
fmt.Println("=== DMS Greeter Enable ===")
|
fmt.Println("=== DMS Greeter Enable ===")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
@@ -232,8 +419,29 @@ func enableGreeter() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
configContent := string(data)
|
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")
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,11 +530,23 @@ func enableGreeter() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("✓ Updated greetd configuration to use %s\n", selectedCompositor)
|
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("\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(" sudo systemctl start greetd")
|
||||||
fmt.Println("\nTo enable on boot, run:")
|
fmt.Println("\nOr reboot to see the greeter at boot time.")
|
||||||
fmt.Println(" sudo systemctl enable --now greetd")
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,9 +34,7 @@ var keybindsShowCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
keybindsShowCmd.Flags().String("hyprland-path", "$HOME/.config/hypr", "Path to Hyprland config directory")
|
keybindsShowCmd.Flags().String("path", "", "Override config path for the provider")
|
||||||
keybindsShowCmd.Flags().String("mangowc-path", "$HOME/.config/mango", "Path to MangoWC config directory")
|
|
||||||
keybindsShowCmd.Flags().String("sway-path", "$HOME/.config/sway", "Path to Sway config directory")
|
|
||||||
|
|
||||||
keybindsCmd.AddCommand(keybindsListCmd)
|
keybindsCmd.AddCommand(keybindsListCmd)
|
||||||
keybindsCmd.AddCommand(keybindsShowCmd)
|
keybindsCmd.AddCommand(keybindsShowCmd)
|
||||||
@@ -89,25 +87,34 @@ func runKeybindsList(cmd *cobra.Command, args []string) {
|
|||||||
|
|
||||||
func runKeybindsShow(cmd *cobra.Command, args []string) {
|
func runKeybindsShow(cmd *cobra.Command, args []string) {
|
||||||
providerName := args[0]
|
providerName := args[0]
|
||||||
|
|
||||||
registry := keybinds.GetDefaultRegistry()
|
registry := keybinds.GetDefaultRegistry()
|
||||||
|
|
||||||
if providerName == "hyprland" {
|
customPath, _ := cmd.Flags().GetString("path")
|
||||||
hyprlandPath, _ := cmd.Flags().GetString("hyprland-path")
|
if customPath != "" {
|
||||||
hyprlandProvider := providers.NewHyprlandProvider(hyprlandPath)
|
var provider keybinds.Provider
|
||||||
registry.Register(hyprlandProvider)
|
switch providerName {
|
||||||
|
case "hyprland":
|
||||||
|
provider = providers.NewHyprlandProvider(customPath)
|
||||||
|
case "mangowc":
|
||||||
|
provider = providers.NewMangoWCProvider(customPath)
|
||||||
|
case "sway":
|
||||||
|
provider = providers.NewSwayProvider(customPath)
|
||||||
|
default:
|
||||||
|
log.Fatalf("Provider %s does not support custom path", providerName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if providerName == "mangowc" {
|
sheet, err := provider.GetCheatSheet()
|
||||||
mangowcPath, _ := cmd.Flags().GetString("mangowc-path")
|
if err != nil {
|
||||||
mangowcProvider := providers.NewMangoWCProvider(mangowcPath)
|
log.Fatalf("Error getting cheatsheet: %v", err)
|
||||||
registry.Register(mangowcProvider)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if providerName == "sway" {
|
output, err := json.MarshalIndent(sheet, "", " ")
|
||||||
swayPath, _ := cmd.Flags().GetString("sway-path")
|
if err != nil {
|
||||||
swayProvider := providers.NewSwayProvider(swayPath)
|
log.Fatalf("Error generating JSON: %v", err)
|
||||||
registry.Register(swayProvider)
|
}
|
||||||
|
|
||||||
|
fmt.Fprintln(os.Stdout, string(output))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
provider, err := registry.Get(providerName)
|
provider, err := registry.Get(providerName)
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/config"
|
"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/dms"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
@@ -76,14 +74,7 @@ func findConfig(cmd *cobra.Command, args []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func runInteractiveMode(cmd *cobra.Command, args []string) {
|
func runInteractiveMode(cmd *cobra.Command, args []string) {
|
||||||
detector, err := dms.NewDetector()
|
detector, _ := 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !detector.IsDMSInstalled() {
|
if !detector.IsDMSInstalled() {
|
||||||
log.Error("DankMaterialShell (DMS) is not detected as installed on this system.")
|
log.Error("DankMaterialShell (DMS) is not detected as installed on this system.")
|
||||||
|
|||||||
345
core/cmd/dms/dpms_client.go
Normal file
345
core/cmd/dms/dpms_client.go
Normal file
@@ -0,0 +1,345 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_output_power"
|
||||||
|
wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cmd struct {
|
||||||
|
fn func()
|
||||||
|
done chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
type dpmsClient struct {
|
||||||
|
display *wlclient.Display
|
||||||
|
ctx *wlclient.Context
|
||||||
|
powerMgr *wlr_output_power.ZwlrOutputPowerManagerV1
|
||||||
|
outputs map[string]*outputState
|
||||||
|
mu sync.Mutex
|
||||||
|
syncRound int
|
||||||
|
done bool
|
||||||
|
err error
|
||||||
|
cmdq chan cmd
|
||||||
|
stopChan chan struct{}
|
||||||
|
wg sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
type outputState struct {
|
||||||
|
wlOutput *wlclient.Output
|
||||||
|
powerCtrl *wlr_output_power.ZwlrOutputPowerV1
|
||||||
|
name string
|
||||||
|
mode uint32
|
||||||
|
failed bool
|
||||||
|
waitCh chan struct{}
|
||||||
|
wantMode *uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *dpmsClient) post(fn func()) {
|
||||||
|
done := make(chan error, 1)
|
||||||
|
select {
|
||||||
|
case c.cmdq <- cmd{fn: fn, done: done}:
|
||||||
|
<-done
|
||||||
|
case <-c.stopChan:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *dpmsClient) waylandActor() {
|
||||||
|
defer c.wg.Done()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-c.stopChan:
|
||||||
|
return
|
||||||
|
case cmd := <-c.cmdq:
|
||||||
|
cmd.fn()
|
||||||
|
close(cmd.done)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDPMSClient() (*dpmsClient, error) {
|
||||||
|
display, err := wlclient.Connect("")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to connect to Wayland: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &dpmsClient{
|
||||||
|
display: display,
|
||||||
|
ctx: display.Context(),
|
||||||
|
outputs: make(map[string]*outputState),
|
||||||
|
cmdq: make(chan cmd, 128),
|
||||||
|
stopChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
c.wg.Add(1)
|
||||||
|
go c.waylandActor()
|
||||||
|
|
||||||
|
registry, err := display.GetRegistry()
|
||||||
|
if err != nil {
|
||||||
|
display.Context().Close()
|
||||||
|
return nil, fmt.Errorf("failed to get registry: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) {
|
||||||
|
switch e.Interface {
|
||||||
|
case wlr_output_power.ZwlrOutputPowerManagerV1InterfaceName:
|
||||||
|
powerMgr := wlr_output_power.NewZwlrOutputPowerManagerV1(c.ctx)
|
||||||
|
version := e.Version
|
||||||
|
if version > 1 {
|
||||||
|
version = 1
|
||||||
|
}
|
||||||
|
if err := registry.Bind(e.Name, e.Interface, version, powerMgr); err == nil {
|
||||||
|
c.powerMgr = powerMgr
|
||||||
|
}
|
||||||
|
|
||||||
|
case "wl_output":
|
||||||
|
output := wlclient.NewOutput(c.ctx)
|
||||||
|
version := e.Version
|
||||||
|
if version > 4 {
|
||||||
|
version = 4
|
||||||
|
}
|
||||||
|
if err := registry.Bind(e.Name, e.Interface, version, output); err == nil {
|
||||||
|
outputID := fmt.Sprintf("output-%d", output.ID())
|
||||||
|
state := &outputState{
|
||||||
|
wlOutput: output,
|
||||||
|
name: outputID,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.mu.Lock()
|
||||||
|
c.outputs[outputID] = state
|
||||||
|
c.mu.Unlock()
|
||||||
|
|
||||||
|
output.SetNameHandler(func(ev wlclient.OutputNameEvent) {
|
||||||
|
c.mu.Lock()
|
||||||
|
delete(c.outputs, state.name)
|
||||||
|
state.name = ev.Name
|
||||||
|
c.outputs[ev.Name] = state
|
||||||
|
c.mu.Unlock()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
syncCallback, err := display.Sync()
|
||||||
|
if err != nil {
|
||||||
|
c.Close()
|
||||||
|
return nil, fmt.Errorf("failed to sync display: %w", err)
|
||||||
|
}
|
||||||
|
syncCallback.SetDoneHandler(func(e wlclient.CallbackDoneEvent) {
|
||||||
|
c.handleSync()
|
||||||
|
})
|
||||||
|
|
||||||
|
for !c.done {
|
||||||
|
if err := c.ctx.Dispatch(); err != nil {
|
||||||
|
c.Close()
|
||||||
|
return nil, fmt.Errorf("dispatch error: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.err != nil {
|
||||||
|
c.Close()
|
||||||
|
return nil, c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *dpmsClient) handleSync() {
|
||||||
|
c.syncRound++
|
||||||
|
|
||||||
|
switch c.syncRound {
|
||||||
|
case 1:
|
||||||
|
if c.powerMgr == nil {
|
||||||
|
c.err = fmt.Errorf("wlr-output-power-management protocol not supported by compositor")
|
||||||
|
c.done = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.mu.Lock()
|
||||||
|
for _, state := range c.outputs {
|
||||||
|
powerCtrl, err := c.powerMgr.GetOutputPower(state.wlOutput)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
state.powerCtrl = powerCtrl
|
||||||
|
|
||||||
|
powerCtrl.SetModeHandler(func(e wlr_output_power.ZwlrOutputPowerV1ModeEvent) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
if state.powerCtrl == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
state.mode = e.Mode
|
||||||
|
if state.wantMode != nil && e.Mode == *state.wantMode && state.waitCh != nil {
|
||||||
|
close(state.waitCh)
|
||||||
|
state.wantMode = nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
powerCtrl.SetFailedHandler(func(e wlr_output_power.ZwlrOutputPowerV1FailedEvent) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
if state.powerCtrl == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
state.failed = true
|
||||||
|
if state.waitCh != nil {
|
||||||
|
close(state.waitCh)
|
||||||
|
state.wantMode = nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
c.mu.Unlock()
|
||||||
|
|
||||||
|
syncCallback, err := c.display.Sync()
|
||||||
|
if err != nil {
|
||||||
|
c.err = fmt.Errorf("failed to sync display: %w", err)
|
||||||
|
c.done = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
syncCallback.SetDoneHandler(func(e wlclient.CallbackDoneEvent) {
|
||||||
|
c.handleSync()
|
||||||
|
})
|
||||||
|
|
||||||
|
default:
|
||||||
|
c.done = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *dpmsClient) ListOutputs() []string {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
names := make([]string, 0, len(c.outputs))
|
||||||
|
for name := range c.outputs {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *dpmsClient) SetDPMS(outputName string, on bool) error {
|
||||||
|
var mode uint32
|
||||||
|
if on {
|
||||||
|
mode = uint32(wlr_output_power.ZwlrOutputPowerV1ModeOn)
|
||||||
|
} else {
|
||||||
|
mode = uint32(wlr_output_power.ZwlrOutputPowerV1ModeOff)
|
||||||
|
}
|
||||||
|
|
||||||
|
var setErr error
|
||||||
|
c.post(func() {
|
||||||
|
c.mu.Lock()
|
||||||
|
var waitStates []*outputState
|
||||||
|
|
||||||
|
if outputName == "" || outputName == "all" {
|
||||||
|
if len(c.outputs) == 0 {
|
||||||
|
c.mu.Unlock()
|
||||||
|
setErr = fmt.Errorf("no outputs found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, state := range c.outputs {
|
||||||
|
if state.powerCtrl == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
state.wantMode = &mode
|
||||||
|
state.waitCh = make(chan struct{})
|
||||||
|
state.failed = false
|
||||||
|
waitStates = append(waitStates, state)
|
||||||
|
state.powerCtrl.SetMode(mode)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state, ok := c.outputs[outputName]
|
||||||
|
if !ok {
|
||||||
|
c.mu.Unlock()
|
||||||
|
setErr = fmt.Errorf("output not found: %s", outputName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if state.powerCtrl == nil {
|
||||||
|
c.mu.Unlock()
|
||||||
|
setErr = fmt.Errorf("output %s has nil powerCtrl", outputName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
state.wantMode = &mode
|
||||||
|
state.waitCh = make(chan struct{})
|
||||||
|
state.failed = false
|
||||||
|
waitStates = append(waitStates, state)
|
||||||
|
state.powerCtrl.SetMode(mode)
|
||||||
|
}
|
||||||
|
c.mu.Unlock()
|
||||||
|
|
||||||
|
deadline := time.Now().Add(10 * time.Second)
|
||||||
|
|
||||||
|
for _, state := range waitStates {
|
||||||
|
c.mu.Lock()
|
||||||
|
ch := state.waitCh
|
||||||
|
c.mu.Unlock()
|
||||||
|
|
||||||
|
done := false
|
||||||
|
for !done {
|
||||||
|
if err := c.ctx.Dispatch(); err != nil {
|
||||||
|
setErr = fmt.Errorf("dispatch error: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ch:
|
||||||
|
c.mu.Lock()
|
||||||
|
if state.failed {
|
||||||
|
setErr = fmt.Errorf("compositor reported failed for %s", state.name)
|
||||||
|
c.mu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.mu.Unlock()
|
||||||
|
done = true
|
||||||
|
default:
|
||||||
|
if time.Now().After(deadline) {
|
||||||
|
setErr = fmt.Errorf("timeout waiting for mode change on %s", state.name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.mu.Lock()
|
||||||
|
for _, state := range waitStates {
|
||||||
|
if state.powerCtrl != nil {
|
||||||
|
state.powerCtrl.Destroy()
|
||||||
|
state.powerCtrl = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.mu.Unlock()
|
||||||
|
|
||||||
|
c.display.Roundtrip()
|
||||||
|
})
|
||||||
|
|
||||||
|
return setErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *dpmsClient) Close() {
|
||||||
|
close(c.stopChan)
|
||||||
|
c.wg.Wait()
|
||||||
|
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
for _, state := range c.outputs {
|
||||||
|
if state.powerCtrl != nil {
|
||||||
|
state.powerCtrl.Destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.outputs = nil
|
||||||
|
|
||||||
|
if c.powerMgr != nil {
|
||||||
|
c.powerMgr.Destroy()
|
||||||
|
c.powerMgr = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.display != nil {
|
||||||
|
c.ctx.Close()
|
||||||
|
c.display = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -57,6 +57,11 @@ func getRuntimeDir() string {
|
|||||||
return os.TempDir()
|
return os.TempDir()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hasSystemdRun() bool {
|
||||||
|
_, err := exec.LookPath("systemd-run")
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
func getPIDFilePath() string {
|
func getPIDFilePath() string {
|
||||||
return filepath.Join(getRuntimeDir(), fmt.Sprintf("danklinux-%d.pid", os.Getpid()))
|
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)
|
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()
|
homeDir, err := os.UserHomeDir()
|
||||||
if err == nil && os.Getenv("DMS_DISABLE_HOT_RELOAD") == "" {
|
if err == nil && os.Getenv("DMS_DISABLE_HOT_RELOAD") == "" {
|
||||||
if !strings.HasPrefix(configPath, homeDir) {
|
if !strings.HasPrefix(configPath, homeDir) {
|
||||||
@@ -374,6 +383,7 @@ func runShellDaemon(session bool) {
|
|||||||
errChan <- fmt.Errorf("server panic: %v", r)
|
errChan <- fmt.Errorf("server panic: %v", r)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
server.CLIVersion = Version
|
||||||
if err := server.Start(false); err != nil {
|
if err := server.Start(false); err != nil {
|
||||||
errChan <- fmt.Errorf("server error: %w", err)
|
errChan <- fmt.Errorf("server error: %w", err)
|
||||||
}
|
}
|
||||||
@@ -387,6 +397,10 @@ func runShellDaemon(session bool) {
|
|||||||
cmd.Env = append(cmd.Env, "QT_LOGGING_RULES="+qtRules)
|
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()
|
homeDir, err := os.UserHomeDir()
|
||||||
if err == nil && os.Getenv("DMS_DISABLE_HOT_RELOAD") == "" {
|
if err == nil && os.Getenv("DMS_DISABLE_HOT_RELOAD") == "" {
|
||||||
if !strings.HasPrefix(configPath, homeDir) {
|
if !strings.HasPrefix(configPath, homeDir) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func commandExists(cmd string) bool {
|
func commandExists(cmd string) bool {
|
||||||
@@ -24,3 +25,68 @@ func isArchPackageInstalled(packageName string) bool {
|
|||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
return err == nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
45
core/go.mod
45
core/go.mod
@@ -5,61 +5,64 @@ go 1.24.6
|
|||||||
require (
|
require (
|
||||||
github.com/Wifx/gonetworkmanager/v2 v2.2.0
|
github.com/Wifx/gonetworkmanager/v2 v2.2.0
|
||||||
github.com/charmbracelet/bubbles v0.21.0
|
github.com/charmbracelet/bubbles v0.21.0
|
||||||
github.com/charmbracelet/bubbletea v1.3.6
|
github.com/charmbracelet/bubbletea v1.3.10
|
||||||
github.com/charmbracelet/lipgloss v1.1.0
|
github.com/charmbracelet/lipgloss v1.1.0
|
||||||
github.com/charmbracelet/log v0.4.2
|
github.com/charmbracelet/log v0.4.2
|
||||||
github.com/godbus/dbus/v5 v5.1.0
|
github.com/fsnotify/fsnotify v1.9.0
|
||||||
github.com/spf13/cobra v1.9.1
|
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
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/yaslama/go-wayland/wayland v0.0.0-20250907155644-2874f32d9c34
|
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
github.com/ProtonMail/go-crypto v1.3.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/cloudflare/circl v1.6.1 // indirect
|
||||||
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||||
github.com/emirpasic/gods v1.18.1 // indirect
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
github.com/go-git/gcfg/v2 v2.0.2 // indirect
|
github.com/go-git/gcfg/v2 v2.0.2 // indirect
|
||||||
github.com/go-git/go-billy/v6 v6.0.0-20250627091229-31e2a16eef30 // indirect
|
github.com/go-git/go-billy/v6 v6.0.0-20251126203821-7f9c95185ee0 // indirect
|
||||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
github.com/go-logfmt/logfmt v0.6.1 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||||
github.com/kevinburke/ssh_config v1.4.0 // indirect
|
github.com/kevinburke/ssh_config v1.4.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
github.com/pjbgf/sha1cd v0.5.0 // indirect
|
github.com/pjbgf/sha1cd v0.5.0 // indirect
|
||||||
github.com/sergi/go-diff v1.4.0 // indirect
|
github.com/sergi/go-diff v1.4.0 // indirect
|
||||||
github.com/stretchr/objx v0.5.2 // indirect
|
github.com/stretchr/objx v0.5.3 // indirect
|
||||||
golang.org/x/crypto v0.42.0 // indirect
|
golang.org/x/crypto v0.45.0 // indirect
|
||||||
golang.org/x/net v0.44.0 // indirect
|
golang.org/x/net v0.47.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/atotto/clipboard v0.1.4 // indirect
|
github.com/atotto/clipboard v0.1.4 // indirect
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
github.com/charmbracelet/colorprofile v0.3.3 // indirect
|
||||||
github.com/charmbracelet/harmonica v0.2.0 // indirect
|
github.com/charmbracelet/harmonica v0.2.0 // indirect
|
||||||
github.com/charmbracelet/x/ansi v0.9.3 // indirect
|
github.com/charmbracelet/x/ansi v0.11.2 // indirect
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
|
github.com/charmbracelet/x/cellbuf v0.0.14 // indirect
|
||||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||||
github.com/go-git/go-git/v6 v6.0.0-20250929195514-145daf2492dd
|
github.com/go-git/go-git/v6 v6.0.0-20251128074608-48f817f57805
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.3.0
|
github.com/lucasb-eyer/go-colorful v1.3.0
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
github.com/mattn/go-runewidth v0.0.19 // indirect
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||||
github.com/muesli/termenv v0.16.0 // indirect
|
github.com/muesli/termenv v0.16.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/spf13/afero v1.15.0
|
github.com/spf13/afero v1.15.0
|
||||||
github.com/spf13/pflag v1.0.6 // indirect
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
golang.org/x/sync v0.17.0 // indirect
|
golang.org/x/sys v0.38.0
|
||||||
golang.org/x/sys v0.36.0
|
golang.org/x/text v0.31.0
|
||||||
golang.org/x/text v0.29.0 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
110
core/go.sum
110
core/go.sum
@@ -14,27 +14,39 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE
|
|||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||||
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
|
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
|
||||||
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
|
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
|
||||||
github.com/charmbracelet/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGsK6et5taU=
|
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
||||||
github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc=
|
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
||||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
|
github.com/charmbracelet/colorprofile v0.3.3 h1:DjJzJtLP6/NZ8p7Cgjno0CKGr7wwRJGxWUwh2IyhfAI=
|
||||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
|
github.com/charmbracelet/colorprofile v0.3.3/go.mod h1:nB1FugsAbzq284eJcjfah2nhdSLppN2NqvfotkfRYP4=
|
||||||
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
|
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
|
||||||
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
|
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
|
||||||
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||||
github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=
|
github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=
|
||||||
github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
|
github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
|
||||||
github.com/charmbracelet/x/ansi v0.9.3 h1:BXt5DHS/MKF+LjuK4huWrC6NCvHtexww7dMayh6GXd0=
|
github.com/charmbracelet/x/ansi v0.11.0 h1:uuIVK7GIplwX6UBIz8S2TF8nkr7xRlygSsBRjSJqIvA=
|
||||||
github.com/charmbracelet/x/ansi v0.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
|
github.com/charmbracelet/x/ansi v0.11.0/go.mod h1:uQt8bOrq/xgXjlGcFMc8U2WYbnxyjrKhnvTQluvfCaE=
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
|
github.com/charmbracelet/x/ansi v0.11.2 h1:XAG3FSjiVtFvgEgGrNBkCNNYrsucAt8c6bfxHyROLLs=
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
github.com/charmbracelet/x/ansi v0.11.2/go.mod h1:9tY2bzX5SiJCU0iWyskjBeI2BRQfvPqI+J760Mjf+Rg=
|
||||||
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4=
|
||||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
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=
|
||||||
|
github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
||||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is=
|
||||||
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@@ -44,23 +56,35 @@ github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc
|
|||||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||||
github.com/go-git/gcfg/v2 v2.0.2 h1:MY5SIIfTGGEMhdA7d7JePuVVxtKL7Hp+ApGDJAJ7dpo=
|
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/gcfg/v2 v2.0.2/go.mod h1:/lv2NsxvhepuMrldsFilrgct6pxzpGdSRC13ydTLSLs=
|
||||||
github.com/go-git/go-billy/v6 v6.0.0-20250627091229-31e2a16eef30 h1:4KqVJTL5eanN8Sgg3BV6f2/QzfZEFbCd+rTak1fGRRA=
|
github.com/go-git/go-billy/v6 v6.0.0-20251111123000-fb5ff8f3f0b0 h1:EC9n6hr6yKDoVJ6g7Ko523LbbceJfR0ohbOp809Fyf4=
|
||||||
github.com/go-git/go-billy/v6 v6.0.0-20250627091229-31e2a16eef30/go.mod h1:snwvGrbywVFy2d6KJdQ132zapq4aLyzLMgpo79XdEfM=
|
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 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-fixtures/v5 v5.1.1/go.mod h1:Altk43lx3b1ks+dVoAG2300o5WWUnktvfY3VI6bcaXU=
|
||||||
github.com/go-git/go-git/v6 v6.0.0-20250929195514-145daf2492dd h1:30HEd5KKVM7GgMJ1GSNuYxuZXEg8Pdlngp6T51faxoc=
|
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-20250929195514-145daf2492dd/go.mod h1:lz8PQr/p79XpFq5ODVBwRJu5LnOF8Et7j95ehqmCMJU=
|
github.com/go-git/go-git/v6 v6.0.0-20251112161705-8cc3e21f07a9/go.mod h1:0wtvm/JfPC9RFVEAP3ks0ec5h64/qmZkTTUE3pjz7Hc=
|
||||||
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
github.com/go-git/go-git/v6 v6.0.0-20251128074608-48f817f57805 h1:jxQ3BzYeErNRvlI/4+0mpwqMzvB4g97U+ksfgvrUEbY=
|
||||||
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
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 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
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 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/holoplot/go-evdev v0.0.0-20250804134636-ab1d56a1fe83 h1:B+A58zGFuDrvEZpPN+yS6swJA0nzqgZvDzgl/OPyefU=
|
||||||
|
github.com/holoplot/go-evdev v0.0.0-20250804134636-ab1d56a1fe83/go.mod h1:iHAf8OIncO2gcQ8XOjS7CMJ2aPbX2Bs0wl5pZyanEqk=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ=
|
github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ=
|
||||||
@@ -79,8 +103,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
|||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||||
@@ -91,7 +115,6 @@ github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
|
|||||||
github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
|
github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
@@ -101,36 +124,37 @@ github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
|||||||
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
||||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
||||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||||
|
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||||
github.com/yaslama/go-wayland/wayland v0.0.0-20250907155644-2874f32d9c34 h1:iTAt1me6SBYsuzrl/CmrxtATPlOG/pVviosM3DhUdKE=
|
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
|
||||||
github.com/yaslama/go-wayland/wayland v0.0.0-20250907155644-2874f32d9c34/go.mod h1:jzmUN5lUAv2O8e63OvcauV4S30rIZ1BvF/PNYE37vDo=
|
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
|
||||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0=
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
|
||||||
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 h1:DHNhtq3sNNzrvduZZIiFyXWOL9IWaDPHqTnLJp+rCBY=
|
||||||
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
|
||||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
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=
|
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
|
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||||
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
|
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
|||||||
@@ -125,6 +125,8 @@ windowrulev2 = noborder, class:^(kitty)$
|
|||||||
windowrulev2 = float, class:^(firefox)$, title:^(Picture-in-Picture)$
|
windowrulev2 = float, class:^(firefox)$, title:^(Picture-in-Picture)$
|
||||||
windowrulev2 = float, class:^(zoom)$
|
windowrulev2 = float, class:^(zoom)$
|
||||||
|
|
||||||
|
# DMS windows floating by default
|
||||||
|
windowrulev2 = float, class:^(org.quickshell)$
|
||||||
windowrulev2 = opacity 0.9 0.9, floating:0, focus:0
|
windowrulev2 = opacity 0.9 0.9, floating:0, focus:0
|
||||||
|
|
||||||
layerrule = noanim, ^(quickshell)$
|
layerrule = noanim, ^(quickshell)$
|
||||||
|
|||||||
@@ -218,6 +218,11 @@ window-rule {
|
|||||||
geometry-corner-radius 12
|
geometry-corner-radius 12
|
||||||
clip-to-geometry true
|
clip-to-geometry true
|
||||||
}
|
}
|
||||||
|
// Open dms windows as floating by default
|
||||||
|
window-rule {
|
||||||
|
match app-id=r#"org.quickshell$"#
|
||||||
|
open-floating true
|
||||||
|
}
|
||||||
binds {
|
binds {
|
||||||
// === System & Overview ===
|
// === System & Overview ===
|
||||||
Mod+D { spawn "niri" "msg" "action" "toggle-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
|
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 {
|
func HexToRGB(hex string) RGB {
|
||||||
if hex[0] == '#' {
|
if hex[0] == '#' {
|
||||||
hex = hex[1:]
|
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}))
|
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)
|
baseColor := DeriveContainer(primaryColor, opts.IsLight)
|
||||||
|
|
||||||
rgb := HexToRGB(baseColor)
|
rgb := HexToRGB(baseColor)
|
||||||
hsv := RGBToHSV(rgb)
|
hsv := RGBToHSV(rgb)
|
||||||
|
|
||||||
palette := make([]string, 0, 16)
|
var palette Palette
|
||||||
|
|
||||||
var normalTextTarget, secondaryTarget float64
|
var normalTextTarget, secondaryTarget float64
|
||||||
if opts.UseDPS {
|
if opts.UseDPS {
|
||||||
@@ -335,7 +377,7 @@ func GeneratePalette(primaryColor string, opts PaletteOptions) []string {
|
|||||||
} else {
|
} else {
|
||||||
bgColor = "#1a1a1a"
|
bgColor = "#1a1a1a"
|
||||||
}
|
}
|
||||||
palette = append(palette, bgColor)
|
palette.Color0 = NewColorInfo(bgColor)
|
||||||
|
|
||||||
hueShift := (hsv.H - 0.6) * 0.12
|
hueShift := (hsv.H - 0.6) * 0.12
|
||||||
satBoost := 1.15
|
satBoost := 1.15
|
||||||
@@ -344,39 +386,39 @@ func GeneratePalette(primaryColor string, opts PaletteOptions) []string {
|
|||||||
var redColor string
|
var redColor string
|
||||||
if opts.IsLight {
|
if opts.IsLight {
|
||||||
redColor = RGBToHex(HSVToRGB(HSV{H: redH, S: math.Min(0.80*satBoost, 1.0), V: 0.55}))
|
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 {
|
} else {
|
||||||
redColor = RGBToHex(HSVToRGB(HSV{H: redH, S: math.Min(0.65*satBoost, 1.0), V: 0.80}))
|
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)
|
greenH := math.Mod(0.33+hueShift+1.0, 1.0)
|
||||||
var greenColor string
|
var greenColor string
|
||||||
if opts.IsLight {
|
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}))
|
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 {
|
} else {
|
||||||
greenColor = RGBToHex(HSVToRGB(HSV{H: greenH, S: math.Min(0.42*satBoost, 1.0), V: 0.84}))
|
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)
|
yellowH := math.Mod(0.15+hueShift+1.0, 1.0)
|
||||||
var yellowColor string
|
var yellowColor string
|
||||||
if opts.IsLight {
|
if opts.IsLight {
|
||||||
yellowColor = RGBToHex(HSVToRGB(HSV{H: yellowH, S: math.Min(0.75*satBoost, 1.0), V: 0.50}))
|
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 {
|
} else {
|
||||||
yellowColor = RGBToHex(HSVToRGB(HSV{H: yellowH, S: math.Min(0.38*satBoost, 1.0), V: 0.86}))
|
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
|
var blueColor string
|
||||||
if opts.IsLight {
|
if opts.IsLight {
|
||||||
blueColor = RGBToHex(HSVToRGB(HSV{H: hsv.H, S: math.Max(hsv.S*0.9, 0.7), V: hsv.V * 1.1}))
|
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 {
|
} 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)}))
|
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
|
magH := hsv.H - 0.03
|
||||||
@@ -388,65 +430,64 @@ func GeneratePalette(primaryColor string, opts PaletteOptions) []string {
|
|||||||
hh := RGBToHSV(hr)
|
hh := RGBToHSV(hr)
|
||||||
if opts.IsLight {
|
if opts.IsLight {
|
||||||
magColor = RGBToHex(HSVToRGB(HSV{H: hh.H, S: math.Max(hh.S*0.9, 0.7), V: hh.V * 0.85}))
|
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 {
|
} else {
|
||||||
magColor = RGBToHex(HSVToRGB(HSV{H: hh.H, S: hh.S * 0.8, V: hh.V * 0.75}))
|
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
|
cyanH := hsv.H + 0.08
|
||||||
if cyanH > 1.0 {
|
if cyanH > 1.0 {
|
||||||
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 {
|
if opts.IsLight {
|
||||||
palette = append(palette, "#1a1a1a")
|
palette.Color7 = NewColorInfo("#1a1a1a")
|
||||||
palette = append(palette, "#2e2e2e")
|
palette.Color8 = NewColorInfo("#2e2e2e")
|
||||||
} else {
|
} else {
|
||||||
palette = append(palette, "#abb2bf")
|
palette.Color7 = NewColorInfo("#abb2bf")
|
||||||
palette = append(palette, "#5c6370")
|
palette.Color8 = NewColorInfo("#5c6370")
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.IsLight {
|
if opts.IsLight {
|
||||||
brightRed := RGBToHex(HSVToRGB(HSV{H: redH, S: math.Min(0.70*satBoost, 1.0), V: 0.65}))
|
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}))
|
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}))
|
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)
|
hr := HexToRGB(primaryColor)
|
||||||
hh := RGBToHSV(hr)
|
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)}))
|
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)}))
|
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)}))
|
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 {
|
} else {
|
||||||
brightRed := RGBToHex(HSVToRGB(HSV{H: redH, S: math.Min(0.50*satBoost, 1.0), V: 0.88}))
|
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}))
|
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}))
|
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))
|
palette.Color11 = NewColorInfo(ensureContrastAuto(brightYellow, bgColor, secondaryTarget, opts))
|
||||||
// Make it way brighter for type names in dark mode
|
|
||||||
brightBlue := retoneToL(primaryColor, 85.0)
|
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)}))
|
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
|
brightCyanH := hsv.H + 0.02
|
||||||
if brightCyanH > 1.0 {
|
if brightCyanH > 1.0 {
|
||||||
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)}))
|
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 {
|
if opts.IsLight {
|
||||||
palette = append(palette, "#1a1a1a")
|
palette.Color15 = NewColorInfo("#1a1a1a")
|
||||||
} else {
|
} else {
|
||||||
palette = append(palette, "#ffffff")
|
palette.Color15 = NewColorInfo("#ffffff")
|
||||||
}
|
}
|
||||||
|
|
||||||
return palette
|
return palette
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package dank16
|
package dank16
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"math"
|
"math"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -346,106 +345,36 @@ func TestGeneratePalette(t *testing.T) {
|
|||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
result := GeneratePalette(tt.base, tt.opts)
|
result := GeneratePalette(tt.base, tt.opts)
|
||||||
|
|
||||||
if len(result) != 16 {
|
colors := []ColorInfo{
|
||||||
t.Errorf("GeneratePalette returned %d colors, expected 16", len(result))
|
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 {
|
for i, color := range colors {
|
||||||
if len(color) != 7 || color[0] != '#' {
|
if len(color.Hex) != 7 || color.Hex[0] != '#' {
|
||||||
t.Errorf("Color at index %d (%s) is not a valid hex color", i, color)
|
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 {
|
if tt.opts.Background != "" && result.Color0.Hex != tt.opts.Background {
|
||||||
t.Errorf("Background color = %s, expected %s", result[0], 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[0] != "#1a1a1a" {
|
} else if !tt.opts.IsLight && tt.opts.Background == "" && result.Color0.Hex != "#1a1a1a" {
|
||||||
t.Errorf("Dark mode background = %s, expected #1a1a1a", result[0])
|
t.Errorf("Dark mode background = %s, expected #1a1a1a", result.Color0.Hex)
|
||||||
} else if tt.opts.IsLight && tt.opts.Background == "" && result[0] != "#f8f8f8" {
|
} else if tt.opts.IsLight && tt.opts.Background == "" && result.Color0.Hex != "#f8f8f8" {
|
||||||
t.Errorf("Light mode background = %s, expected #f8f8f8", result[0])
|
t.Errorf("Light mode background = %s, expected #f8f8f8", result.Color0.Hex)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tt.opts.IsLight && result[15] != "#1a1a1a" {
|
if tt.opts.IsLight && result.Color15.Hex != "#1a1a1a" {
|
||||||
t.Errorf("Light mode foreground = %s, expected #1a1a1a", result[15])
|
t.Errorf("Light mode foreground = %s, expected #1a1a1a", result.Color15.Hex)
|
||||||
} else if !tt.opts.IsLight && result[15] != "#ffffff" {
|
} else if !tt.opts.IsLight && result.Color15.Hex != "#ffffff" {
|
||||||
t.Errorf("Dark mode foreground = %s, expected #ffffff", result[15])
|
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) {
|
func TestRoundTripConversion(t *testing.T) {
|
||||||
testColors := []string{"#000000", "#ffffff", "#ff0000", "#00ff00", "#0000ff", "#625690", "#808080"}
|
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) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
result := GeneratePalette(tt.base, tt.opts)
|
result := GeneratePalette(tt.base, tt.opts)
|
||||||
|
|
||||||
if len(result) != 16 {
|
colors := []ColorInfo{
|
||||||
t.Errorf("GeneratePalette returned %d colors, expected 16", len(result))
|
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 {
|
for i, color := range colors {
|
||||||
if len(color) != 7 || color[0] != '#' {
|
if len(color.Hex) != 7 || color.Hex[0] != '#' {
|
||||||
t.Errorf("Color at index %d (%s) is not a valid hex color", i, color)
|
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++ {
|
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
|
minLc := 30.0
|
||||||
if lc < minLc && lc > 0 {
|
if lc < minLc && lc > 0 {
|
||||||
t.Errorf("Color %d (%s) has insufficient DPS contrast %f with background %s (expected >= %f)",
|
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)
|
paletteWCAG := GeneratePalette(base, optsWCAG)
|
||||||
paletteDPS := GeneratePalette(base, optsDPS)
|
paletteDPS := GeneratePalette(base, optsDPS)
|
||||||
|
|
||||||
if len(paletteWCAG) != 16 || len(paletteDPS) != 16 {
|
wcagColors := []ColorInfo{
|
||||||
t.Fatal("Both palettes should have 16 colors")
|
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] {
|
if paletteWCAG.Color0.Hex != paletteDPS.Color0.Hex {
|
||||||
t.Errorf("Background colors differ: WCAG=%s, DPS=%s", paletteWCAG[0], paletteDPS[0])
|
t.Errorf("Background colors differ: WCAG=%s, DPS=%s", paletteWCAG.Color0.Hex, paletteDPS.Color0.Hex)
|
||||||
}
|
}
|
||||||
|
|
||||||
differentCount := 0
|
differentCount := 0
|
||||||
for i := 0; i < 16; i++ {
|
for i := 0; i < 16; i++ {
|
||||||
if paletteWCAG[i] != paletteDPS[i] {
|
if wcagColors[i].Hex != dpsColors[i].Hex {
|
||||||
differentCount++
|
differentCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,135 +6,104 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GenerateJSON(colors []string) string {
|
func GenerateJSON(p Palette) string {
|
||||||
colorMap := make(map[string]string)
|
marshalled, _ := json.Marshal(p)
|
||||||
|
|
||||||
for i, color := range colors {
|
|
||||||
colorMap[fmt.Sprintf("color%d", i)] = color
|
|
||||||
}
|
|
||||||
|
|
||||||
marshalled, _ := json.Marshal(colorMap)
|
|
||||||
|
|
||||||
return string(marshalled)
|
return string(marshalled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateKittyTheme(colors []string) string {
|
func GenerateKittyTheme(p Palette) 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},
|
|
||||||
}
|
|
||||||
|
|
||||||
var result strings.Builder
|
var result strings.Builder
|
||||||
for _, kc := range kittyColors {
|
fmt.Fprintf(&result, "color0 %s\n", p.Color0.Hex)
|
||||||
fmt.Fprintf(&result, "%s %s\n", kc.name, colors[kc.index])
|
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()
|
return result.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateFootTheme(colors []string) string {
|
func GenerateFootTheme(p Palette) 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},
|
|
||||||
}
|
|
||||||
|
|
||||||
var result strings.Builder
|
var result strings.Builder
|
||||||
for _, fc := range footColors {
|
fmt.Fprintf(&result, "regular0=%s\n", p.Color0.HexStripped)
|
||||||
fmt.Fprintf(&result, "%s=%s\n", fc.name, strings.TrimPrefix(colors[fc.index], "#"))
|
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()
|
return result.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateAlacrittyTheme(colors []string) string {
|
func GenerateAlacrittyTheme(p Palette) 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},
|
|
||||||
}
|
|
||||||
|
|
||||||
var result strings.Builder
|
var result strings.Builder
|
||||||
currentSection := ""
|
result.WriteString("[colors.normal]\n")
|
||||||
for _, ac := range alacrittyColors {
|
fmt.Fprintf(&result, "black = '%s'\n", p.Color0.Hex)
|
||||||
if ac.section != currentSection {
|
fmt.Fprintf(&result, "red = '%s'\n", p.Color1.Hex)
|
||||||
if currentSection != "" {
|
fmt.Fprintf(&result, "green = '%s'\n", p.Color2.Hex)
|
||||||
result.WriteString("\n")
|
fmt.Fprintf(&result, "yellow = '%s'\n", p.Color3.Hex)
|
||||||
}
|
fmt.Fprintf(&result, "blue = '%s'\n", p.Color4.Hex)
|
||||||
fmt.Fprintf(&result, "[colors.%s]\n", ac.section)
|
fmt.Fprintf(&result, "magenta = '%s'\n", p.Color5.Hex)
|
||||||
currentSection = ac.section
|
fmt.Fprintf(&result, "cyan = '%s'\n", p.Color6.Hex)
|
||||||
}
|
fmt.Fprintf(&result, "white = '%s'\n", p.Color7.Hex)
|
||||||
fmt.Fprintf(&result, "%-7s = '%s'\n", ac.name, colors[ac.index])
|
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()
|
return result.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateGhosttyTheme(colors []string) string {
|
func GenerateGhosttyTheme(p Palette) string {
|
||||||
var result strings.Builder
|
var result strings.Builder
|
||||||
for i, color := range colors {
|
fmt.Fprintf(&result, "palette = 0=%s\n", p.Color0.Hex)
|
||||||
fmt.Fprintf(&result, "palette = %d=%s\n", i, color)
|
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()
|
return result.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateWeztermTheme(colors []string) string {
|
func GenerateWeztermTheme(p Palette) string {
|
||||||
var result strings.Builder
|
var result strings.Builder
|
||||||
labels := []string{"ansi", "brights"}
|
fmt.Fprintf(&result, "ansi = ['%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s']\n",
|
||||||
for j, label := range labels {
|
p.Color0.Hex, p.Color1.Hex, p.Color2.Hex, p.Color3.Hex,
|
||||||
start := j * 8
|
p.Color4.Hex, p.Color5.Hex, p.Color6.Hex, p.Color7.Hex)
|
||||||
colorSlice := make([]string, 8)
|
fmt.Fprintf(&result, "brights = ['%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s']\n",
|
||||||
for i, color := range colors[start : start+8] {
|
p.Color8.Hex, p.Color9.Hex, p.Color10.Hex, p.Color11.Hex,
|
||||||
colorSlice[i] = fmt.Sprintf("'%s'", color)
|
p.Color12.Hex, p.Color13.Hex, p.Color14.Hex, p.Color15.Hex)
|
||||||
}
|
|
||||||
fmt.Fprintf(&result, "%s = [%s]\n", label, strings.Join(colorSlice, ", "))
|
|
||||||
}
|
|
||||||
return result.String()
|
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 {
|
Register("garuda", "#cba6f7", FamilyArch, func(config DistroConfig, logChan chan<- string) Distribution {
|
||||||
return NewArchDistribution(config, logChan)
|
return NewArchDistribution(config, logChan)
|
||||||
})
|
})
|
||||||
|
Register("artix", "#1793D1", FamilyArch, func(config DistroConfig, logChan chan<- string) Distribution {
|
||||||
|
return NewArchDistribution(config, logChan)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type ArchDistribution struct {
|
type ArchDistribution struct {
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ func (d *DebianDistribution) InstallPrerequisites(ctx context.Context, sudoPassw
|
|||||||
}
|
}
|
||||||
|
|
||||||
devToolsCmd := ExecSudoCommand(ctx, sudoPassword,
|
devToolsCmd := ExecSudoCommand(ctx, sudoPassword,
|
||||||
"apt-get install -y curl wget git cmake ninja-build pkg-config libxcb-cursor-dev libglib2.0-dev libpolkit-agent-1-dev")
|
"apt-get install -y curl wget git cmake ninja-build pkg-config libxcb-cursor-dev libglib2.0-dev libpolkit-agent-1-dev libjpeg-dev libpugixml-dev")
|
||||||
if err := d.runWithProgress(devToolsCmd, progressChan, PhasePrerequisites, 0.10, 0.12); err != nil {
|
if err := d.runWithProgress(devToolsCmd, progressChan, PhasePrerequisites, 0.10, 0.12); err != nil {
|
||||||
return fmt.Errorf("failed to install development tools: %w", err)
|
return fmt.Errorf("failed to install development tools: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,12 @@ func init() {
|
|||||||
Register("fedora-asahi-remix", "#0B57A4", FamilyFedora, func(config DistroConfig, logChan chan<- string) Distribution {
|
Register("fedora-asahi-remix", "#0B57A4", FamilyFedora, func(config DistroConfig, logChan chan<- string) Distribution {
|
||||||
return NewFedoraDistribution(config, logChan)
|
return NewFedoraDistribution(config, logChan)
|
||||||
})
|
})
|
||||||
|
|
||||||
Register("bluefin", "#0B57A4", FamilyFedora, func(config DistroConfig, logChan chan<- string) Distribution {
|
Register("bluefin", "#0B57A4", FamilyFedora, func(config DistroConfig, logChan chan<- string) Distribution {
|
||||||
return NewFedoraDistribution(config, logChan)
|
return NewFedoraDistribution(config, logChan)
|
||||||
})
|
})
|
||||||
|
Register("ultramarine", "#00078b", FamilyFedora, func(config DistroConfig, logChan chan<- string) Distribution {
|
||||||
|
return NewFedoraDistribution(config, logChan)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type FedoraDistribution struct {
|
type FedoraDistribution struct {
|
||||||
@@ -165,7 +167,7 @@ func (f *FedoraDistribution) GetPackageMappingWithVariants(wm deps.WindowManager
|
|||||||
packages["jq"] = PackageMapping{Name: "jq", Repository: RepoTypeSystem}
|
packages["jq"] = PackageMapping{Name: "jq", Repository: RepoTypeSystem}
|
||||||
case deps.WindowManagerNiri:
|
case deps.WindowManagerNiri:
|
||||||
packages["niri"] = f.getNiriMapping(variants["niri"])
|
packages["niri"] = f.getNiriMapping(variants["niri"])
|
||||||
packages["xwayland-satellite"] = PackageMapping{Name: "xwayland-satellite", Repository: RepoTypeCOPR, RepoURL: "yalter/niri"}
|
packages["xwayland-satellite"] = PackageMapping{Name: "xwayland-satellite", Repository: RepoTypeSystem}
|
||||||
}
|
}
|
||||||
|
|
||||||
return packages
|
return packages
|
||||||
@@ -203,7 +205,7 @@ func (f *FedoraDistribution) getNiriMapping(variant deps.PackageVariant) Package
|
|||||||
if variant == deps.VariantGit {
|
if variant == deps.VariantGit {
|
||||||
return PackageMapping{Name: "niri", Repository: RepoTypeCOPR, RepoURL: "yalter/niri-git"}
|
return PackageMapping{Name: "niri", Repository: RepoTypeCOPR, RepoURL: "yalter/niri-git"}
|
||||||
}
|
}
|
||||||
return PackageMapping{Name: "niri", Repository: RepoTypeCOPR, RepoURL: "yalter/niri"}
|
return PackageMapping{Name: "niri", Repository: RepoTypeSystem}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FedoraDistribution) detectXwaylandSatellite() deps.Dependency {
|
func (f *FedoraDistribution) detectXwaylandSatellite() deps.Dependency {
|
||||||
@@ -506,6 +508,14 @@ func (f *FedoraDistribution) installDNFPackages(ctx context.Context, packages []
|
|||||||
f.log(fmt.Sprintf("Installing DNF packages: %s", strings.Join(packages, ", ")))
|
f.log(fmt.Sprintf("Installing DNF packages: %s", strings.Join(packages, ", ")))
|
||||||
|
|
||||||
args := []string{"dnf", "install", "-y"}
|
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...)
|
args = append(args, packages...)
|
||||||
|
|
||||||
progressChan <- InstallProgressMsg{
|
progressChan <- InstallProgressMsg{
|
||||||
|
|||||||
@@ -478,6 +478,95 @@ func (m *ManualPackageInstaller) installHyprpicker(ctx context.Context, sudoPass
|
|||||||
return fmt.Errorf("failed to create cache directory: %w", err)
|
return fmt.Errorf("failed to create cache directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Install hyprutils first
|
||||||
|
progressChan <- InstallProgressMsg{
|
||||||
|
Phase: PhaseSystemPackages,
|
||||||
|
Progress: 0.05,
|
||||||
|
Step: "Building hyprutils dependency...",
|
||||||
|
IsComplete: false,
|
||||||
|
CommandInfo: "git clone https://github.com/hyprwm/hyprutils.git",
|
||||||
|
}
|
||||||
|
|
||||||
|
hyprutilsDir := filepath.Join(cacheDir, "hyprutils-build")
|
||||||
|
if err := os.MkdirAll(hyprutilsDir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create hyprutils directory: %w", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(hyprutilsDir)
|
||||||
|
|
||||||
|
cloneUtilsCmd := exec.CommandContext(ctx, "git", "clone", "https://github.com/hyprwm/hyprutils.git", hyprutilsDir)
|
||||||
|
if err := cloneUtilsCmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("failed to clone hyprutils: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
configureUtilsCmd := exec.CommandContext(ctx, "cmake",
|
||||||
|
"--no-warn-unused-cli",
|
||||||
|
"-DCMAKE_BUILD_TYPE:STRING=Release",
|
||||||
|
"-DCMAKE_INSTALL_PREFIX:PATH=/usr",
|
||||||
|
"-DBUILD_TESTING=off",
|
||||||
|
"-S", ".",
|
||||||
|
"-B", "./build")
|
||||||
|
configureUtilsCmd.Dir = hyprutilsDir
|
||||||
|
configureUtilsCmd.Env = append(os.Environ(), "TMPDIR="+cacheDir)
|
||||||
|
if err := m.runWithProgressStep(configureUtilsCmd, progressChan, PhaseSystemPackages, 0.05, 0.1, "Configuring hyprutils..."); err != nil {
|
||||||
|
return fmt.Errorf("failed to configure hyprutils: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buildUtilsCmd := exec.CommandContext(ctx, "cmake", "--build", "./build", "--config", "Release", "--target", "all")
|
||||||
|
buildUtilsCmd.Dir = hyprutilsDir
|
||||||
|
buildUtilsCmd.Env = append(os.Environ(), "TMPDIR="+cacheDir)
|
||||||
|
if err := m.runWithProgressStep(buildUtilsCmd, progressChan, PhaseSystemPackages, 0.1, 0.2, "Building hyprutils..."); err != nil {
|
||||||
|
return fmt.Errorf("failed to build hyprutils: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
installUtilsCmd := ExecSudoCommand(ctx, sudoPassword, "cmake --install ./build")
|
||||||
|
installUtilsCmd.Dir = hyprutilsDir
|
||||||
|
if err := installUtilsCmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("failed to install hyprutils: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install hyprwayland-scanner
|
||||||
|
progressChan <- InstallProgressMsg{
|
||||||
|
Phase: PhaseSystemPackages,
|
||||||
|
Progress: 0.2,
|
||||||
|
Step: "Building hyprwayland-scanner dependency...",
|
||||||
|
IsComplete: false,
|
||||||
|
CommandInfo: "git clone https://github.com/hyprwm/hyprwayland-scanner.git",
|
||||||
|
}
|
||||||
|
|
||||||
|
scannerDir := filepath.Join(cacheDir, "hyprwayland-scanner-build")
|
||||||
|
if err := os.MkdirAll(scannerDir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create scanner directory: %w", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(scannerDir)
|
||||||
|
|
||||||
|
cloneScannerCmd := exec.CommandContext(ctx, "git", "clone", "https://github.com/hyprwm/hyprwayland-scanner.git", scannerDir)
|
||||||
|
if err := cloneScannerCmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("failed to clone hyprwayland-scanner: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
configureScannerCmd := exec.CommandContext(ctx, "cmake",
|
||||||
|
"-DCMAKE_INSTALL_PREFIX=/usr",
|
||||||
|
"-B", "build")
|
||||||
|
configureScannerCmd.Dir = scannerDir
|
||||||
|
configureScannerCmd.Env = append(os.Environ(), "TMPDIR="+cacheDir)
|
||||||
|
if err := m.runWithProgressStep(configureScannerCmd, progressChan, PhaseSystemPackages, 0.2, 0.25, "Configuring hyprwayland-scanner..."); err != nil {
|
||||||
|
return fmt.Errorf("failed to configure hyprwayland-scanner: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buildScannerCmd := exec.CommandContext(ctx, "cmake", "--build", "build", "-j")
|
||||||
|
buildScannerCmd.Dir = scannerDir
|
||||||
|
buildScannerCmd.Env = append(os.Environ(), "TMPDIR="+cacheDir)
|
||||||
|
if err := m.runWithProgressStep(buildScannerCmd, progressChan, PhaseSystemPackages, 0.25, 0.35, "Building hyprwayland-scanner..."); err != nil {
|
||||||
|
return fmt.Errorf("failed to build hyprwayland-scanner: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
installScannerCmd := ExecSudoCommand(ctx, sudoPassword, "cmake --install build")
|
||||||
|
installScannerCmd.Dir = scannerDir
|
||||||
|
if err := installScannerCmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("failed to install hyprwayland-scanner: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now build hyprpicker
|
||||||
tmpDir := filepath.Join(cacheDir, "hyprpicker-build")
|
tmpDir := filepath.Join(cacheDir, "hyprpicker-build")
|
||||||
if err := os.MkdirAll(tmpDir, 0755); err != nil {
|
if err := os.MkdirAll(tmpDir, 0755); err != nil {
|
||||||
return fmt.Errorf("failed to create temp directory: %w", err)
|
return fmt.Errorf("failed to create temp directory: %w", err)
|
||||||
@@ -486,7 +575,7 @@ func (m *ManualPackageInstaller) installHyprpicker(ctx context.Context, sudoPass
|
|||||||
|
|
||||||
progressChan <- InstallProgressMsg{
|
progressChan <- InstallProgressMsg{
|
||||||
Phase: PhaseSystemPackages,
|
Phase: PhaseSystemPackages,
|
||||||
Progress: 0.2,
|
Progress: 0.35,
|
||||||
Step: "Cloning hyprpicker repository...",
|
Step: "Cloning hyprpicker repository...",
|
||||||
IsComplete: false,
|
IsComplete: false,
|
||||||
CommandInfo: "git clone https://github.com/hyprwm/hyprpicker.git",
|
CommandInfo: "git clone https://github.com/hyprwm/hyprpicker.git",
|
||||||
@@ -499,16 +588,39 @@ func (m *ManualPackageInstaller) installHyprpicker(ctx context.Context, sudoPass
|
|||||||
|
|
||||||
progressChan <- InstallProgressMsg{
|
progressChan <- InstallProgressMsg{
|
||||||
Phase: PhaseSystemPackages,
|
Phase: PhaseSystemPackages,
|
||||||
Progress: 0.4,
|
Progress: 0.45,
|
||||||
Step: "Building hyprpicker...",
|
Step: "Configuring hyprpicker build...",
|
||||||
IsComplete: false,
|
IsComplete: false,
|
||||||
CommandInfo: "make all",
|
CommandInfo: "cmake -B build -S . -DCMAKE_BUILD_TYPE=Release",
|
||||||
}
|
}
|
||||||
|
|
||||||
buildCmd := exec.CommandContext(ctx, "make", "all")
|
configureCmd := exec.CommandContext(ctx, "cmake",
|
||||||
|
"--no-warn-unused-cli",
|
||||||
|
"-DCMAKE_BUILD_TYPE:STRING=Release",
|
||||||
|
"-DCMAKE_INSTALL_PREFIX:PATH=/usr",
|
||||||
|
"-S", ".",
|
||||||
|
"-B", "./build")
|
||||||
|
configureCmd.Dir = tmpDir
|
||||||
|
configureCmd.Env = append(os.Environ(), "TMPDIR="+cacheDir)
|
||||||
|
|
||||||
|
output, err := configureCmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
m.log(fmt.Sprintf("cmake configure failed. Output:\n%s", string(output)))
|
||||||
|
return fmt.Errorf("failed to configure hyprpicker: %w\nCMake output:\n%s", err, string(output))
|
||||||
|
}
|
||||||
|
|
||||||
|
progressChan <- InstallProgressMsg{
|
||||||
|
Phase: PhaseSystemPackages,
|
||||||
|
Progress: 0.55,
|
||||||
|
Step: "Building hyprpicker...",
|
||||||
|
IsComplete: false,
|
||||||
|
CommandInfo: "cmake --build build --target hyprpicker",
|
||||||
|
}
|
||||||
|
|
||||||
|
buildCmd := exec.CommandContext(ctx, "cmake", "--build", "./build", "--config", "Release", "--target", "hyprpicker")
|
||||||
buildCmd.Dir = tmpDir
|
buildCmd.Dir = tmpDir
|
||||||
buildCmd.Env = append(os.Environ(), "TMPDIR="+cacheDir)
|
buildCmd.Env = append(os.Environ(), "TMPDIR="+cacheDir)
|
||||||
if err := buildCmd.Run(); err != nil {
|
if err := m.runWithProgressStep(buildCmd, progressChan, PhaseSystemPackages, 0.55, 0.8, "Building hyprpicker..."); err != nil {
|
||||||
return fmt.Errorf("failed to build hyprpicker: %w", err)
|
return fmt.Errorf("failed to build hyprpicker: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -518,10 +630,10 @@ func (m *ManualPackageInstaller) installHyprpicker(ctx context.Context, sudoPass
|
|||||||
Step: "Installing hyprpicker...",
|
Step: "Installing hyprpicker...",
|
||||||
IsComplete: false,
|
IsComplete: false,
|
||||||
NeedsSudo: true,
|
NeedsSudo: true,
|
||||||
CommandInfo: "sudo make install",
|
CommandInfo: "sudo cmake --install build",
|
||||||
}
|
}
|
||||||
|
|
||||||
installCmd := ExecSudoCommand(ctx, sudoPassword, "make install")
|
installCmd := ExecSudoCommand(ctx, sudoPassword, "cmake --install ./build")
|
||||||
installCmd.Dir = tmpDir
|
installCmd.Dir = tmpDir
|
||||||
if err := installCmd.Run(); err != nil {
|
if err := installCmd.Run(); err != nil {
|
||||||
return fmt.Errorf("failed to install hyprpicker: %w", err)
|
return fmt.Errorf("failed to install hyprpicker: %w", err)
|
||||||
|
|||||||
@@ -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{}
|
return []DependencyInfo{}
|
||||||
}
|
}
|
||||||
|
|
||||||
isNixOS := d.isNixOS()
|
|
||||||
|
|
||||||
var components []DependencyInfo
|
var components []DependencyInfo
|
||||||
for _, dep := range dependencies {
|
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{
|
components = append(components, DependencyInfo{
|
||||||
Name: dep.Name,
|
Name: dep.Name,
|
||||||
Status: dep.Status,
|
Status: dep.Status,
|
||||||
@@ -142,23 +135,6 @@ func (d *Detector) GetInstalledComponents() []DependencyInfo {
|
|||||||
return components
|
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 {
|
type DependencyInfo struct {
|
||||||
Name string
|
Name string
|
||||||
Status deps.DependencyStatus
|
Status deps.DependencyStatus
|
||||||
|
|||||||
@@ -227,6 +227,7 @@ func SetupParentDirectoryACLs(logFunc func(string), sudoPassword string) error {
|
|||||||
{filepath.Join(homeDir, ".local"), ".local directory"},
|
{filepath.Join(homeDir, ".local"), ".local directory"},
|
||||||
{filepath.Join(homeDir, ".cache"), ".cache directory"},
|
{filepath.Join(homeDir, ".cache"), ".cache directory"},
|
||||||
{filepath.Join(homeDir, ".local", "state"), ".local/state directory"},
|
{filepath.Join(homeDir, ".local", "state"), ".local/state directory"},
|
||||||
|
{filepath.Join(homeDir, ".local", "share"), ".local/share directory"},
|
||||||
}
|
}
|
||||||
|
|
||||||
logFunc("\nSetting up parent directory ACLs for greeter user access...")
|
logFunc("\nSetting up parent directory ACLs for greeter user access...")
|
||||||
@@ -239,8 +240,8 @@ func SetupParentDirectoryACLs(logFunc func(string), sudoPassword string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set ACL to allow greeter user execute (traverse) permission
|
// Set ACL to allow greeter user read+execute permission (for session discovery)
|
||||||
if err := runSudoCmd(sudoPassword, "setfacl", "-m", "u:greeter:x", dir.path); err != nil {
|
if err := runSudoCmd(sudoPassword, "setfacl", "-m", "u:greeter:rx", dir.path); err != nil {
|
||||||
logFunc(fmt.Sprintf("⚠ Warning: Failed to set ACL on %s: %v", dir.desc, err))
|
logFunc(fmt.Sprintf("⚠ Warning: Failed to set ACL on %s: %v", dir.desc, err))
|
||||||
logFunc(fmt.Sprintf(" You may need to run manually: setfacl -m u:greeter:x %s", dir.path))
|
logFunc(fmt.Sprintf(" You may need to run manually: setfacl -m u:greeter:x %s", dir.path))
|
||||||
continue
|
continue
|
||||||
@@ -287,6 +288,8 @@ func SetupDMSGroup(logFunc func(string), sudoPassword string) error {
|
|||||||
{filepath.Join(homeDir, ".local", "state", "DankMaterialShell"), "DankMaterialShell state"},
|
{filepath.Join(homeDir, ".local", "state", "DankMaterialShell"), "DankMaterialShell state"},
|
||||||
{filepath.Join(homeDir, ".cache", "quickshell"), "quickshell cache"},
|
{filepath.Join(homeDir, ".cache", "quickshell"), "quickshell cache"},
|
||||||
{filepath.Join(homeDir, ".config", "quickshell"), "quickshell config"},
|
{filepath.Join(homeDir, ".config", "quickshell"), "quickshell config"},
|
||||||
|
{filepath.Join(homeDir, ".local", "share", "wayland-sessions"), "wayland sessions"},
|
||||||
|
{filepath.Join(homeDir, ".local", "share", "xsessions"), "xsessions"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, dir := range configDirs {
|
for _, dir := range configDirs {
|
||||||
@@ -364,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 {
|
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))
|
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) {
|
func expandPath(path string) (string, error) {
|
||||||
expandedPath := os.ExpandEnv(path)
|
expandedPath := os.ExpandEnv(path)
|
||||||
|
|
||||||
if filepath.HasPrefix(expandedPath, "~") {
|
if strings.HasPrefix(expandedPath, "~") {
|
||||||
home, err := os.UserHomeDir()
|
home, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/hyprland"
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,7 +25,7 @@ func (h *HyprlandProvider) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *HyprlandProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
|
func (h *HyprlandProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
|
||||||
section, err := hyprland.ParseKeys(h.configPath)
|
section, err := ParseHyprlandKeys(h.configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse hyprland config: %w", err)
|
return nil, fmt.Errorf("failed to parse hyprland config: %w", err)
|
||||||
}
|
}
|
||||||
@@ -41,7 +40,7 @@ func (h *HyprlandProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HyprlandProvider) convertSection(section *hyprland.Section, subcategory string, categorizedBinds map[string][]keybinds.Keybind) {
|
func (h *HyprlandProvider) convertSection(section *HyprlandSection, subcategory string, categorizedBinds map[string][]keybinds.Keybind) {
|
||||||
currentSubcat := subcategory
|
currentSubcat := subcategory
|
||||||
if section.Name != "" {
|
if section.Name != "" {
|
||||||
currentSubcat = section.Name
|
currentSubcat = section.Name
|
||||||
@@ -86,7 +85,7 @@ func (h *HyprlandProvider) categorizeByDispatcher(dispatcher string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HyprlandProvider) convertKeybind(kb *hyprland.KeyBinding, subcategory string) keybinds.Keybind {
|
func (h *HyprlandProvider) convertKeybind(kb *HyprlandKeyBinding, subcategory string) keybinds.Keybind {
|
||||||
key := h.formatKey(kb)
|
key := h.formatKey(kb)
|
||||||
desc := kb.Comment
|
desc := kb.Comment
|
||||||
|
|
||||||
@@ -108,7 +107,7 @@ func (h *HyprlandProvider) generateDescription(dispatcher, params string) string
|
|||||||
return dispatcher
|
return dispatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HyprlandProvider) formatKey(kb *hyprland.KeyBinding) string {
|
func (h *HyprlandProvider) formatKey(kb *HyprlandKeyBinding) string {
|
||||||
parts := make([]string, 0, len(kb.Mods)+1)
|
parts := make([]string, 0, len(kb.Mods)+1)
|
||||||
parts = append(parts, kb.Mods...)
|
parts = append(parts, kb.Mods...)
|
||||||
parts = append(parts, kb.Key)
|
parts = append(parts, kb.Key)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package hyprland
|
package providers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
@@ -15,7 +15,7 @@ const (
|
|||||||
|
|
||||||
var ModSeparators = []rune{'+', ' '}
|
var ModSeparators = []rune{'+', ' '}
|
||||||
|
|
||||||
type KeyBinding struct {
|
type HyprlandKeyBinding struct {
|
||||||
Mods []string `json:"mods"`
|
Mods []string `json:"mods"`
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
Dispatcher string `json:"dispatcher"`
|
Dispatcher string `json:"dispatcher"`
|
||||||
@@ -23,25 +23,25 @@ type KeyBinding struct {
|
|||||||
Comment string `json:"comment"`
|
Comment string `json:"comment"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Section struct {
|
type HyprlandSection struct {
|
||||||
Children []Section `json:"children"`
|
Children []HyprlandSection `json:"children"`
|
||||||
Keybinds []KeyBinding `json:"keybinds"`
|
Keybinds []HyprlandKeyBinding `json:"keybinds"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Parser struct {
|
type HyprlandParser struct {
|
||||||
contentLines []string
|
contentLines []string
|
||||||
readingLine int
|
readingLine int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewParser() *Parser {
|
func NewHyprlandParser() *HyprlandParser {
|
||||||
return &Parser{
|
return &HyprlandParser{
|
||||||
contentLines: []string{},
|
contentLines: []string{},
|
||||||
readingLine: 0,
|
readingLine: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) ReadContent(directory string) error {
|
func (p *HyprlandParser) ReadContent(directory string) error {
|
||||||
expandedDir := os.ExpandEnv(directory)
|
expandedDir := os.ExpandEnv(directory)
|
||||||
expandedDir = filepath.Clean(expandedDir)
|
expandedDir = filepath.Clean(expandedDir)
|
||||||
if strings.HasPrefix(expandedDir, "~") {
|
if strings.HasPrefix(expandedDir, "~") {
|
||||||
@@ -87,7 +87,7 @@ func (p *Parser) ReadContent(directory string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func autogenerateComment(dispatcher, params string) string {
|
func hyprlandAutogenerateComment(dispatcher, params string) string {
|
||||||
switch dispatcher {
|
switch dispatcher {
|
||||||
case "resizewindow":
|
case "resizewindow":
|
||||||
return "Resize window"
|
return "Resize window"
|
||||||
@@ -196,7 +196,7 @@ func autogenerateComment(dispatcher, params string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
|
func (p *HyprlandParser) getKeybindAtLine(lineNumber int) *HyprlandKeyBinding {
|
||||||
line := p.contentLines[lineNumber]
|
line := p.contentLines[lineNumber]
|
||||||
parts := strings.SplitN(line, "=", 2)
|
parts := strings.SplitN(line, "=", 2)
|
||||||
if len(parts) < 2 {
|
if len(parts) < 2 {
|
||||||
@@ -232,7 +232,7 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
comment = autogenerateComment(dispatcher, params)
|
comment = hyprlandAutogenerateComment(dispatcher, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
var modList []string
|
var modList []string
|
||||||
@@ -256,7 +256,7 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &KeyBinding{
|
return &HyprlandKeyBinding{
|
||||||
Mods: modList,
|
Mods: modList,
|
||||||
Key: key,
|
Key: key,
|
||||||
Dispatcher: dispatcher,
|
Dispatcher: dispatcher,
|
||||||
@@ -265,7 +265,7 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) getBindsRecursive(currentContent *Section, scope int) *Section {
|
func (p *HyprlandParser) getBindsRecursive(currentContent *HyprlandSection, scope int) *HyprlandSection {
|
||||||
titleRegex := regexp.MustCompile(TitleRegex)
|
titleRegex := regexp.MustCompile(TitleRegex)
|
||||||
|
|
||||||
for p.readingLine < len(p.contentLines) {
|
for p.readingLine < len(p.contentLines) {
|
||||||
@@ -283,9 +283,9 @@ func (p *Parser) getBindsRecursive(currentContent *Section, scope int) *Section
|
|||||||
sectionName := strings.TrimSpace(line[headingScope+1:])
|
sectionName := strings.TrimSpace(line[headingScope+1:])
|
||||||
p.readingLine++
|
p.readingLine++
|
||||||
|
|
||||||
childSection := &Section{
|
childSection := &HyprlandSection{
|
||||||
Children: []Section{},
|
Children: []HyprlandSection{},
|
||||||
Keybinds: []KeyBinding{},
|
Keybinds: []HyprlandKeyBinding{},
|
||||||
Name: sectionName,
|
Name: sectionName,
|
||||||
}
|
}
|
||||||
result := p.getBindsRecursive(childSection, headingScope)
|
result := p.getBindsRecursive(childSection, headingScope)
|
||||||
@@ -312,18 +312,18 @@ func (p *Parser) getBindsRecursive(currentContent *Section, scope int) *Section
|
|||||||
return currentContent
|
return currentContent
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) ParseKeys() *Section {
|
func (p *HyprlandParser) ParseKeys() *HyprlandSection {
|
||||||
p.readingLine = 0
|
p.readingLine = 0
|
||||||
rootSection := &Section{
|
rootSection := &HyprlandSection{
|
||||||
Children: []Section{},
|
Children: []HyprlandSection{},
|
||||||
Keybinds: []KeyBinding{},
|
Keybinds: []HyprlandKeyBinding{},
|
||||||
Name: "",
|
Name: "",
|
||||||
}
|
}
|
||||||
return p.getBindsRecursive(rootSection, 0)
|
return p.getBindsRecursive(rootSection, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseKeys(path string) (*Section, error) {
|
func ParseHyprlandKeys(path string) (*HyprlandSection, error) {
|
||||||
parser := NewParser()
|
parser := NewHyprlandParser()
|
||||||
if err := parser.ReadContent(path); err != nil {
|
if err := parser.ReadContent(path); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package hyprland
|
package providers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAutogenerateComment(t *testing.T) {
|
func TestHyprlandAutogenerateComment(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
dispatcher string
|
dispatcher string
|
||||||
params string
|
params string
|
||||||
@@ -51,25 +51,25 @@ func TestAutogenerateComment(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.dispatcher+"_"+tt.params, func(t *testing.T) {
|
t.Run(tt.dispatcher+"_"+tt.params, func(t *testing.T) {
|
||||||
result := autogenerateComment(tt.dispatcher, tt.params)
|
result := hyprlandAutogenerateComment(tt.dispatcher, tt.params)
|
||||||
if result != tt.expected {
|
if result != tt.expected {
|
||||||
t.Errorf("autogenerateComment(%q, %q) = %q, want %q",
|
t.Errorf("hyprlandAutogenerateComment(%q, %q) = %q, want %q",
|
||||||
tt.dispatcher, tt.params, result, tt.expected)
|
tt.dispatcher, tt.params, result, tt.expected)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetKeybindAtLine(t *testing.T) {
|
func TestHyprlandGetKeybindAtLine(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
line string
|
line string
|
||||||
expected *KeyBinding
|
expected *HyprlandKeyBinding
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "basic_keybind",
|
name: "basic_keybind",
|
||||||
line: "bind = SUPER, Q, killactive",
|
line: "bind = SUPER, Q, killactive",
|
||||||
expected: &KeyBinding{
|
expected: &HyprlandKeyBinding{
|
||||||
Mods: []string{"SUPER"},
|
Mods: []string{"SUPER"},
|
||||||
Key: "Q",
|
Key: "Q",
|
||||||
Dispatcher: "killactive",
|
Dispatcher: "killactive",
|
||||||
@@ -80,7 +80,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "keybind_with_params",
|
name: "keybind_with_params",
|
||||||
line: "bind = SUPER, left, movefocus, l",
|
line: "bind = SUPER, left, movefocus, l",
|
||||||
expected: &KeyBinding{
|
expected: &HyprlandKeyBinding{
|
||||||
Mods: []string{"SUPER"},
|
Mods: []string{"SUPER"},
|
||||||
Key: "left",
|
Key: "left",
|
||||||
Dispatcher: "movefocus",
|
Dispatcher: "movefocus",
|
||||||
@@ -91,7 +91,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "keybind_with_comment",
|
name: "keybind_with_comment",
|
||||||
line: "bind = SUPER, T, exec, kitty # Open terminal",
|
line: "bind = SUPER, T, exec, kitty # Open terminal",
|
||||||
expected: &KeyBinding{
|
expected: &HyprlandKeyBinding{
|
||||||
Mods: []string{"SUPER"},
|
Mods: []string{"SUPER"},
|
||||||
Key: "T",
|
Key: "T",
|
||||||
Dispatcher: "exec",
|
Dispatcher: "exec",
|
||||||
@@ -107,7 +107,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "keybind_multiple_mods",
|
name: "keybind_multiple_mods",
|
||||||
line: "bind = SUPER+SHIFT, F, fullscreen, 0",
|
line: "bind = SUPER+SHIFT, F, fullscreen, 0",
|
||||||
expected: &KeyBinding{
|
expected: &HyprlandKeyBinding{
|
||||||
Mods: []string{"SUPER", "SHIFT"},
|
Mods: []string{"SUPER", "SHIFT"},
|
||||||
Key: "F",
|
Key: "F",
|
||||||
Dispatcher: "fullscreen",
|
Dispatcher: "fullscreen",
|
||||||
@@ -118,7 +118,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "keybind_no_mods",
|
name: "keybind_no_mods",
|
||||||
line: "bind = , Print, exec, screenshot",
|
line: "bind = , Print, exec, screenshot",
|
||||||
expected: &KeyBinding{
|
expected: &HyprlandKeyBinding{
|
||||||
Mods: []string{},
|
Mods: []string{},
|
||||||
Key: "Print",
|
Key: "Print",
|
||||||
Dispatcher: "exec",
|
Dispatcher: "exec",
|
||||||
@@ -130,7 +130,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
parser := NewParser()
|
parser := NewHyprlandParser()
|
||||||
parser.contentLines = []string{tt.line}
|
parser.contentLines = []string{tt.line}
|
||||||
result := parser.getKeybindAtLine(0)
|
result := parser.getKeybindAtLine(0)
|
||||||
|
|
||||||
@@ -171,7 +171,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseKeysWithSections(t *testing.T) {
|
func TestHyprlandParseKeysWithSections(t *testing.T) {
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
configFile := filepath.Join(tmpDir, "hyprland.conf")
|
configFile := filepath.Join(tmpDir, "hyprland.conf")
|
||||||
|
|
||||||
@@ -191,9 +191,9 @@ bind = SUPER, T, exec, kitty # Terminal
|
|||||||
t.Fatalf("Failed to write test config: %v", err)
|
t.Fatalf("Failed to write test config: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
section, err := ParseKeys(tmpDir)
|
section, err := ParseHyprlandKeys(tmpDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ParseKeys failed: %v", err)
|
t.Fatalf("ParseHyprlandKeys failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(section.Children) != 2 {
|
if len(section.Children) != 2 {
|
||||||
@@ -236,7 +236,7 @@ bind = SUPER, T, exec, kitty # Terminal
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseKeysWithCommentBinds(t *testing.T) {
|
func TestHyprlandParseKeysWithCommentBinds(t *testing.T) {
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
configFile := filepath.Join(tmpDir, "test.conf")
|
configFile := filepath.Join(tmpDir, "test.conf")
|
||||||
|
|
||||||
@@ -249,9 +249,9 @@ bind = SUPER, B, exec, app2
|
|||||||
t.Fatalf("Failed to write test config: %v", err)
|
t.Fatalf("Failed to write test config: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
section, err := ParseKeys(tmpDir)
|
section, err := ParseHyprlandKeys(tmpDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ParseKeys failed: %v", err)
|
t.Fatalf("ParseHyprlandKeys failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(section.Keybinds) != 3 {
|
if len(section.Keybinds) != 3 {
|
||||||
@@ -269,7 +269,7 @@ bind = SUPER, B, exec, app2
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadContentMultipleFiles(t *testing.T) {
|
func TestHyprlandReadContentMultipleFiles(t *testing.T) {
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
|
|
||||||
file1 := filepath.Join(tmpDir, "a.conf")
|
file1 := filepath.Join(tmpDir, "a.conf")
|
||||||
@@ -285,7 +285,7 @@ func TestReadContentMultipleFiles(t *testing.T) {
|
|||||||
t.Fatalf("Failed to write file2: %v", err)
|
t.Fatalf("Failed to write file2: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
parser := NewParser()
|
parser := NewHyprlandParser()
|
||||||
if err := parser.ReadContent(tmpDir); err != nil {
|
if err := parser.ReadContent(tmpDir); err != nil {
|
||||||
t.Fatalf("ReadContent failed: %v", err)
|
t.Fatalf("ReadContent failed: %v", err)
|
||||||
}
|
}
|
||||||
@@ -296,7 +296,7 @@ func TestReadContentMultipleFiles(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadContentErrors(t *testing.T) {
|
func TestHyprlandReadContentErrors(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
path string
|
path string
|
||||||
@@ -313,7 +313,7 @@ func TestReadContentErrors(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
_, err := ParseKeys(tt.path)
|
_, err := ParseHyprlandKeys(tt.path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("Expected error, got nil")
|
t.Error("Expected error, got nil")
|
||||||
}
|
}
|
||||||
@@ -321,7 +321,7 @@ func TestReadContentErrors(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadContentWithTildeExpansion(t *testing.T) {
|
func TestHyprlandReadContentWithTildeExpansion(t *testing.T) {
|
||||||
homeDir, err := os.UserHomeDir()
|
homeDir, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Skip("Cannot get home directory")
|
t.Skip("Cannot get home directory")
|
||||||
@@ -343,7 +343,7 @@ func TestReadContentWithTildeExpansion(t *testing.T) {
|
|||||||
t.Skip("Cannot create relative path")
|
t.Skip("Cannot create relative path")
|
||||||
}
|
}
|
||||||
|
|
||||||
parser := NewParser()
|
parser := NewHyprlandParser()
|
||||||
tildePathMatch := "~/" + relPath
|
tildePathMatch := "~/" + relPath
|
||||||
err = parser.ReadContent(tildePathMatch)
|
err = parser.ReadContent(tildePathMatch)
|
||||||
|
|
||||||
@@ -352,8 +352,8 @@ func TestReadContentWithTildeExpansion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestKeybindWithParamsContainingCommas(t *testing.T) {
|
func TestHyprlandKeybindWithParamsContainingCommas(t *testing.T) {
|
||||||
parser := NewParser()
|
parser := NewHyprlandParser()
|
||||||
parser.contentLines = []string{"bind = SUPER, R, exec, notify-send 'Title' 'Message, with comma'"}
|
parser.contentLines = []string{"bind = SUPER, R, exec, notify-send 'Title' 'Message, with comma'"}
|
||||||
|
|
||||||
result := parser.getKeybindAtLine(0)
|
result := parser.getKeybindAtLine(0)
|
||||||
@@ -368,7 +368,7 @@ func TestKeybindWithParamsContainingCommas(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEmptyAndCommentLines(t *testing.T) {
|
func TestHyprlandEmptyAndCommentLines(t *testing.T) {
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
configFile := filepath.Join(tmpDir, "test.conf")
|
configFile := filepath.Join(tmpDir, "test.conf")
|
||||||
|
|
||||||
@@ -385,9 +385,9 @@ bind = SUPER, T, exec, kitty
|
|||||||
t.Fatalf("Failed to write test config: %v", err)
|
t.Fatalf("Failed to write test config: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
section, err := ParseKeys(tmpDir)
|
section, err := ParseHyprlandKeys(tmpDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ParseKeys failed: %v", err)
|
t.Fatalf("ParseHyprlandKeys failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(section.Keybinds) != 2 {
|
if len(section.Keybinds) != 2 {
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
|
||||||
)
|
)
|
||||||
@@ -118,7 +119,7 @@ func (j *JSONFileProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
|
|||||||
func expandPath(path string) (string, error) {
|
func expandPath(path string) (string, error) {
|
||||||
expandedPath := os.ExpandEnv(path)
|
expandedPath := os.ExpandEnv(path)
|
||||||
|
|
||||||
if filepath.HasPrefix(expandedPath, "~") {
|
if strings.HasPrefix(expandedPath, "~") {
|
||||||
home, err := os.UserHomeDir()
|
home, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/mangowc"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type MangoWCProvider struct {
|
type MangoWCProvider struct {
|
||||||
@@ -26,7 +25,7 @@ func (m *MangoWCProvider) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *MangoWCProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
|
func (m *MangoWCProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
|
||||||
keybinds_list, err := mangowc.ParseKeys(m.configPath)
|
keybinds_list, err := ParseMangoWCKeys(m.configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse mangowc config: %w", err)
|
return nil, fmt.Errorf("failed to parse mangowc config: %w", err)
|
||||||
}
|
}
|
||||||
@@ -83,7 +82,7 @@ func (m *MangoWCProvider) categorizeByCommand(command string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MangoWCProvider) convertKeybind(kb *mangowc.KeyBinding) keybinds.Keybind {
|
func (m *MangoWCProvider) convertKeybind(kb *MangoWCKeyBinding) keybinds.Keybind {
|
||||||
key := m.formatKey(kb)
|
key := m.formatKey(kb)
|
||||||
desc := kb.Comment
|
desc := kb.Comment
|
||||||
|
|
||||||
@@ -104,7 +103,7 @@ func (m *MangoWCProvider) generateDescription(command, params string) string {
|
|||||||
return command
|
return command
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MangoWCProvider) formatKey(kb *mangowc.KeyBinding) string {
|
func (m *MangoWCProvider) formatKey(kb *MangoWCKeyBinding) string {
|
||||||
parts := make([]string, 0, len(kb.Mods)+1)
|
parts := make([]string, 0, len(kb.Mods)+1)
|
||||||
parts = append(parts, kb.Mods...)
|
parts = append(parts, kb.Mods...)
|
||||||
parts = append(parts, kb.Key)
|
parts = append(parts, kb.Key)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package mangowc
|
package providers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
@@ -8,12 +8,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
HideComment = "[hidden]"
|
MangoWCHideComment = "[hidden]"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ModSeparators = []rune{'+', ' '}
|
var MangoWCModSeparators = []rune{'+', ' '}
|
||||||
|
|
||||||
type KeyBinding struct {
|
type MangoWCKeyBinding struct {
|
||||||
Mods []string `json:"mods"`
|
Mods []string `json:"mods"`
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
Command string `json:"command"`
|
Command string `json:"command"`
|
||||||
@@ -21,19 +21,19 @@ type KeyBinding struct {
|
|||||||
Comment string `json:"comment"`
|
Comment string `json:"comment"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Parser struct {
|
type MangoWCParser struct {
|
||||||
contentLines []string
|
contentLines []string
|
||||||
readingLine int
|
readingLine int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewParser() *Parser {
|
func NewMangoWCParser() *MangoWCParser {
|
||||||
return &Parser{
|
return &MangoWCParser{
|
||||||
contentLines: []string{},
|
contentLines: []string{},
|
||||||
readingLine: 0,
|
readingLine: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) ReadContent(path string) error {
|
func (p *MangoWCParser) ReadContent(path string) error {
|
||||||
expandedPath := os.ExpandEnv(path)
|
expandedPath := os.ExpandEnv(path)
|
||||||
expandedPath = filepath.Clean(expandedPath)
|
expandedPath = filepath.Clean(expandedPath)
|
||||||
if strings.HasPrefix(expandedPath, "~") {
|
if strings.HasPrefix(expandedPath, "~") {
|
||||||
@@ -82,7 +82,7 @@ func (p *Parser) ReadContent(path string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func autogenerateComment(command, params string) string {
|
func mangowcAutogenerateComment(command, params string) string {
|
||||||
switch command {
|
switch command {
|
||||||
case "spawn", "spawn_shell":
|
case "spawn", "spawn_shell":
|
||||||
return params
|
return params
|
||||||
@@ -196,7 +196,7 @@ func autogenerateComment(command, params string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
|
func (p *MangoWCParser) getKeybindAtLine(lineNumber int) *MangoWCKeyBinding {
|
||||||
if lineNumber >= len(p.contentLines) {
|
if lineNumber >= len(p.contentLines) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -220,7 +220,7 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
|
|||||||
comment = strings.TrimSpace(parts[1])
|
comment = strings.TrimSpace(parts[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(comment, HideComment) {
|
if strings.HasPrefix(comment, MangoWCHideComment) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,16 +239,16 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if comment == "" {
|
if comment == "" {
|
||||||
comment = autogenerateComment(command, params)
|
comment = mangowcAutogenerateComment(command, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
var modList []string
|
var modList []string
|
||||||
if mods != "" && !strings.EqualFold(mods, "none") {
|
if mods != "" && !strings.EqualFold(mods, "none") {
|
||||||
modstring := mods + string(ModSeparators[0])
|
modstring := mods + string(MangoWCModSeparators[0])
|
||||||
p := 0
|
p := 0
|
||||||
for index, char := range modstring {
|
for index, char := range modstring {
|
||||||
isModSep := false
|
isModSep := false
|
||||||
for _, sep := range ModSeparators {
|
for _, sep := range MangoWCModSeparators {
|
||||||
if char == sep {
|
if char == sep {
|
||||||
isModSep = true
|
isModSep = true
|
||||||
break
|
break
|
||||||
@@ -265,7 +265,7 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
|
|||||||
|
|
||||||
_ = bindType
|
_ = bindType
|
||||||
|
|
||||||
return &KeyBinding{
|
return &MangoWCKeyBinding{
|
||||||
Mods: modList,
|
Mods: modList,
|
||||||
Key: key,
|
Key: key,
|
||||||
Command: command,
|
Command: command,
|
||||||
@@ -274,8 +274,8 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) ParseKeys() []KeyBinding {
|
func (p *MangoWCParser) ParseKeys() []MangoWCKeyBinding {
|
||||||
var keybinds []KeyBinding
|
var keybinds []MangoWCKeyBinding
|
||||||
|
|
||||||
for lineNumber := 0; lineNumber < len(p.contentLines); lineNumber++ {
|
for lineNumber := 0; lineNumber < len(p.contentLines); lineNumber++ {
|
||||||
line := p.contentLines[lineNumber]
|
line := p.contentLines[lineNumber]
|
||||||
@@ -296,8 +296,8 @@ func (p *Parser) ParseKeys() []KeyBinding {
|
|||||||
return keybinds
|
return keybinds
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseKeys(path string) ([]KeyBinding, error) {
|
func ParseMangoWCKeys(path string) ([]MangoWCKeyBinding, error) {
|
||||||
parser := NewParser()
|
parser := NewMangoWCParser()
|
||||||
if err := parser.ReadContent(path); err != nil {
|
if err := parser.ReadContent(path); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package mangowc
|
package providers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAutogenerateComment(t *testing.T) {
|
func TestMangoWCAutogenerateComment(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
command string
|
command string
|
||||||
params string
|
params string
|
||||||
@@ -60,25 +60,25 @@ func TestAutogenerateComment(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.command+"_"+tt.params, func(t *testing.T) {
|
t.Run(tt.command+"_"+tt.params, func(t *testing.T) {
|
||||||
result := autogenerateComment(tt.command, tt.params)
|
result := mangowcAutogenerateComment(tt.command, tt.params)
|
||||||
if result != tt.expected {
|
if result != tt.expected {
|
||||||
t.Errorf("autogenerateComment(%q, %q) = %q, want %q",
|
t.Errorf("mangowcAutogenerateComment(%q, %q) = %q, want %q",
|
||||||
tt.command, tt.params, result, tt.expected)
|
tt.command, tt.params, result, tt.expected)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetKeybindAtLine(t *testing.T) {
|
func TestMangoWCGetKeybindAtLine(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
line string
|
line string
|
||||||
expected *KeyBinding
|
expected *MangoWCKeyBinding
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "basic_keybind",
|
name: "basic_keybind",
|
||||||
line: "bind=ALT,q,killclient,",
|
line: "bind=ALT,q,killclient,",
|
||||||
expected: &KeyBinding{
|
expected: &MangoWCKeyBinding{
|
||||||
Mods: []string{"ALT"},
|
Mods: []string{"ALT"},
|
||||||
Key: "q",
|
Key: "q",
|
||||||
Command: "killclient",
|
Command: "killclient",
|
||||||
@@ -89,7 +89,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "keybind_with_params",
|
name: "keybind_with_params",
|
||||||
line: "bind=ALT,Left,focusdir,left",
|
line: "bind=ALT,Left,focusdir,left",
|
||||||
expected: &KeyBinding{
|
expected: &MangoWCKeyBinding{
|
||||||
Mods: []string{"ALT"},
|
Mods: []string{"ALT"},
|
||||||
Key: "Left",
|
Key: "Left",
|
||||||
Command: "focusdir",
|
Command: "focusdir",
|
||||||
@@ -100,7 +100,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "keybind_with_comment",
|
name: "keybind_with_comment",
|
||||||
line: "bind=Alt,t,spawn,kitty # Open terminal",
|
line: "bind=Alt,t,spawn,kitty # Open terminal",
|
||||||
expected: &KeyBinding{
|
expected: &MangoWCKeyBinding{
|
||||||
Mods: []string{"Alt"},
|
Mods: []string{"Alt"},
|
||||||
Key: "t",
|
Key: "t",
|
||||||
Command: "spawn",
|
Command: "spawn",
|
||||||
@@ -116,7 +116,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "keybind_multiple_mods",
|
name: "keybind_multiple_mods",
|
||||||
line: "bind=SUPER+SHIFT,Up,exchange_client,up",
|
line: "bind=SUPER+SHIFT,Up,exchange_client,up",
|
||||||
expected: &KeyBinding{
|
expected: &MangoWCKeyBinding{
|
||||||
Mods: []string{"SUPER", "SHIFT"},
|
Mods: []string{"SUPER", "SHIFT"},
|
||||||
Key: "Up",
|
Key: "Up",
|
||||||
Command: "exchange_client",
|
Command: "exchange_client",
|
||||||
@@ -127,7 +127,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "keybind_no_mods",
|
name: "keybind_no_mods",
|
||||||
line: "bind=NONE,Print,spawn,screenshot",
|
line: "bind=NONE,Print,spawn,screenshot",
|
||||||
expected: &KeyBinding{
|
expected: &MangoWCKeyBinding{
|
||||||
Mods: []string{},
|
Mods: []string{},
|
||||||
Key: "Print",
|
Key: "Print",
|
||||||
Command: "spawn",
|
Command: "spawn",
|
||||||
@@ -138,7 +138,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "keybind_multiple_params",
|
name: "keybind_multiple_params",
|
||||||
line: "bind=Ctrl,1,view,1,0",
|
line: "bind=Ctrl,1,view,1,0",
|
||||||
expected: &KeyBinding{
|
expected: &MangoWCKeyBinding{
|
||||||
Mods: []string{"Ctrl"},
|
Mods: []string{"Ctrl"},
|
||||||
Key: "1",
|
Key: "1",
|
||||||
Command: "view",
|
Command: "view",
|
||||||
@@ -149,7 +149,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "bindl_flag",
|
name: "bindl_flag",
|
||||||
line: "bindl=SUPER+ALT,l,spawn,dms ipc call lock lock",
|
line: "bindl=SUPER+ALT,l,spawn,dms ipc call lock lock",
|
||||||
expected: &KeyBinding{
|
expected: &MangoWCKeyBinding{
|
||||||
Mods: []string{"SUPER", "ALT"},
|
Mods: []string{"SUPER", "ALT"},
|
||||||
Key: "l",
|
Key: "l",
|
||||||
Command: "spawn",
|
Command: "spawn",
|
||||||
@@ -160,7 +160,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "keybind_with_spaces",
|
name: "keybind_with_spaces",
|
||||||
line: "bind = SUPER, r, reload_config",
|
line: "bind = SUPER, r, reload_config",
|
||||||
expected: &KeyBinding{
|
expected: &MangoWCKeyBinding{
|
||||||
Mods: []string{"SUPER"},
|
Mods: []string{"SUPER"},
|
||||||
Key: "r",
|
Key: "r",
|
||||||
Command: "reload_config",
|
Command: "reload_config",
|
||||||
@@ -172,7 +172,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
parser := NewParser()
|
parser := NewMangoWCParser()
|
||||||
parser.contentLines = []string{tt.line}
|
parser.contentLines = []string{tt.line}
|
||||||
result := parser.getKeybindAtLine(0)
|
result := parser.getKeybindAtLine(0)
|
||||||
|
|
||||||
@@ -213,7 +213,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseKeys(t *testing.T) {
|
func TestMangoWCParseKeys(t *testing.T) {
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
configFile := filepath.Join(tmpDir, "config.conf")
|
configFile := filepath.Join(tmpDir, "config.conf")
|
||||||
|
|
||||||
@@ -242,9 +242,9 @@ bind=Ctrl,2,view,2,0
|
|||||||
t.Fatalf("Failed to write test config: %v", err)
|
t.Fatalf("Failed to write test config: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
keybinds, err := ParseKeys(configFile)
|
keybinds, err := ParseMangoWCKeys(configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ParseKeys failed: %v", err)
|
t.Fatalf("ParseMangoWCKeys failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedCount := 7
|
expectedCount := 7
|
||||||
@@ -267,7 +267,7 @@ bind=Ctrl,2,view,2,0
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadContentMultipleFiles(t *testing.T) {
|
func TestMangoWCReadContentMultipleFiles(t *testing.T) {
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
|
|
||||||
file1 := filepath.Join(tmpDir, "a.conf")
|
file1 := filepath.Join(tmpDir, "a.conf")
|
||||||
@@ -283,7 +283,7 @@ func TestReadContentMultipleFiles(t *testing.T) {
|
|||||||
t.Fatalf("Failed to write file2: %v", err)
|
t.Fatalf("Failed to write file2: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
parser := NewParser()
|
parser := NewMangoWCParser()
|
||||||
if err := parser.ReadContent(tmpDir); err != nil {
|
if err := parser.ReadContent(tmpDir); err != nil {
|
||||||
t.Fatalf("ReadContent failed: %v", err)
|
t.Fatalf("ReadContent failed: %v", err)
|
||||||
}
|
}
|
||||||
@@ -294,7 +294,7 @@ func TestReadContentMultipleFiles(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadContentSingleFile(t *testing.T) {
|
func TestMangoWCReadContentSingleFile(t *testing.T) {
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
configFile := filepath.Join(tmpDir, "config.conf")
|
configFile := filepath.Join(tmpDir, "config.conf")
|
||||||
|
|
||||||
@@ -304,7 +304,7 @@ func TestReadContentSingleFile(t *testing.T) {
|
|||||||
t.Fatalf("Failed to write config: %v", err)
|
t.Fatalf("Failed to write config: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
parser := NewParser()
|
parser := NewMangoWCParser()
|
||||||
if err := parser.ReadContent(configFile); err != nil {
|
if err := parser.ReadContent(configFile); err != nil {
|
||||||
t.Fatalf("ReadContent failed: %v", err)
|
t.Fatalf("ReadContent failed: %v", err)
|
||||||
}
|
}
|
||||||
@@ -315,7 +315,7 @@ func TestReadContentSingleFile(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadContentErrors(t *testing.T) {
|
func TestMangoWCReadContentErrors(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
path string
|
path string
|
||||||
@@ -332,7 +332,7 @@ func TestReadContentErrors(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
_, err := ParseKeys(tt.path)
|
_, err := ParseMangoWCKeys(tt.path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("Expected error, got nil")
|
t.Error("Expected error, got nil")
|
||||||
}
|
}
|
||||||
@@ -340,7 +340,7 @@ func TestReadContentErrors(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadContentWithTildeExpansion(t *testing.T) {
|
func TestMangoWCReadContentWithTildeExpansion(t *testing.T) {
|
||||||
homeDir, err := os.UserHomeDir()
|
homeDir, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Skip("Cannot get home directory")
|
t.Skip("Cannot get home directory")
|
||||||
@@ -362,7 +362,7 @@ func TestReadContentWithTildeExpansion(t *testing.T) {
|
|||||||
t.Skip("Cannot create relative path")
|
t.Skip("Cannot create relative path")
|
||||||
}
|
}
|
||||||
|
|
||||||
parser := NewParser()
|
parser := NewMangoWCParser()
|
||||||
tildePathMatch := "~/" + relPath
|
tildePathMatch := "~/" + relPath
|
||||||
err = parser.ReadContent(tildePathMatch)
|
err = parser.ReadContent(tildePathMatch)
|
||||||
|
|
||||||
@@ -371,7 +371,7 @@ func TestReadContentWithTildeExpansion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEmptyAndCommentLines(t *testing.T) {
|
func TestMangoWCEmptyAndCommentLines(t *testing.T) {
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
configFile := filepath.Join(tmpDir, "config.conf")
|
configFile := filepath.Join(tmpDir, "config.conf")
|
||||||
|
|
||||||
@@ -388,9 +388,9 @@ bind=Alt,t,spawn,kitty
|
|||||||
t.Fatalf("Failed to write test config: %v", err)
|
t.Fatalf("Failed to write test config: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
keybinds, err := ParseKeys(configFile)
|
keybinds, err := ParseMangoWCKeys(configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ParseKeys failed: %v", err)
|
t.Fatalf("ParseMangoWCKeys failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(keybinds) != 2 {
|
if len(keybinds) != 2 {
|
||||||
@@ -398,7 +398,7 @@ bind=Alt,t,spawn,kitty
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInvalidBindLines(t *testing.T) {
|
func TestMangoWCInvalidBindLines(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
line string
|
line string
|
||||||
@@ -419,7 +419,7 @@ func TestInvalidBindLines(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
parser := NewParser()
|
parser := NewMangoWCParser()
|
||||||
parser.contentLines = []string{tt.line}
|
parser.contentLines = []string{tt.line}
|
||||||
result := parser.getKeybindAtLine(0)
|
result := parser.getKeybindAtLine(0)
|
||||||
|
|
||||||
@@ -430,7 +430,7 @@ func TestInvalidBindLines(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRealWorldConfig(t *testing.T) {
|
func TestMangoWCRealWorldConfig(t *testing.T) {
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
configFile := filepath.Join(tmpDir, "config.conf")
|
configFile := filepath.Join(tmpDir, "config.conf")
|
||||||
|
|
||||||
@@ -462,9 +462,9 @@ bind=Ctrl,3,view,3,0
|
|||||||
t.Fatalf("Failed to write test config: %v", err)
|
t.Fatalf("Failed to write test config: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
keybinds, err := ParseKeys(configFile)
|
keybinds, err := ParseMangoWCKeys(configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ParseKeys failed: %v", err)
|
t.Fatalf("ParseMangoWCKeys failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(keybinds) < 14 {
|
if len(keybinds) < 14 {
|
||||||
@@ -4,8 +4,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/mangowc"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMangoWCProviderName(t *testing.T) {
|
func TestMangoWCProviderName(t *testing.T) {
|
||||||
@@ -88,12 +86,12 @@ func TestMangoWCCategorizeByCommand(t *testing.T) {
|
|||||||
func TestMangoWCFormatKey(t *testing.T) {
|
func TestMangoWCFormatKey(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
keybind *mangowc.KeyBinding
|
keybind *MangoWCKeyBinding
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "single_mod",
|
name: "single_mod",
|
||||||
keybind: &mangowc.KeyBinding{
|
keybind: &MangoWCKeyBinding{
|
||||||
Mods: []string{"ALT"},
|
Mods: []string{"ALT"},
|
||||||
Key: "q",
|
Key: "q",
|
||||||
},
|
},
|
||||||
@@ -101,7 +99,7 @@ func TestMangoWCFormatKey(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multiple_mods",
|
name: "multiple_mods",
|
||||||
keybind: &mangowc.KeyBinding{
|
keybind: &MangoWCKeyBinding{
|
||||||
Mods: []string{"SUPER", "SHIFT"},
|
Mods: []string{"SUPER", "SHIFT"},
|
||||||
Key: "Up",
|
Key: "Up",
|
||||||
},
|
},
|
||||||
@@ -109,7 +107,7 @@ func TestMangoWCFormatKey(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no_mods",
|
name: "no_mods",
|
||||||
keybind: &mangowc.KeyBinding{
|
keybind: &MangoWCKeyBinding{
|
||||||
Mods: []string{},
|
Mods: []string{},
|
||||||
Key: "Print",
|
Key: "Print",
|
||||||
},
|
},
|
||||||
@@ -131,13 +129,13 @@ func TestMangoWCFormatKey(t *testing.T) {
|
|||||||
func TestMangoWCConvertKeybind(t *testing.T) {
|
func TestMangoWCConvertKeybind(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
keybind *mangowc.KeyBinding
|
keybind *MangoWCKeyBinding
|
||||||
wantKey string
|
wantKey string
|
||||||
wantDesc string
|
wantDesc string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "with_comment",
|
name: "with_comment",
|
||||||
keybind: &mangowc.KeyBinding{
|
keybind: &MangoWCKeyBinding{
|
||||||
Mods: []string{"ALT"},
|
Mods: []string{"ALT"},
|
||||||
Key: "t",
|
Key: "t",
|
||||||
Command: "spawn",
|
Command: "spawn",
|
||||||
@@ -149,7 +147,7 @@ func TestMangoWCConvertKeybind(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "without_comment",
|
name: "without_comment",
|
||||||
keybind: &mangowc.KeyBinding{
|
keybind: &MangoWCKeyBinding{
|
||||||
Mods: []string{"SUPER"},
|
Mods: []string{"SUPER"},
|
||||||
Key: "r",
|
Key: "r",
|
||||||
Command: "reload_config",
|
Command: "reload_config",
|
||||||
@@ -161,7 +159,7 @@ func TestMangoWCConvertKeybind(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "with_params_no_comment",
|
name: "with_params_no_comment",
|
||||||
keybind: &mangowc.KeyBinding{
|
keybind: &MangoWCKeyBinding{
|
||||||
Mods: []string{"CTRL"},
|
Mods: []string{"CTRL"},
|
||||||
Key: "1",
|
Key: "1",
|
||||||
Command: "view",
|
Command: "view",
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/sway"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SwayProvider struct {
|
type SwayProvider struct {
|
||||||
@@ -26,7 +25,7 @@ func (s *SwayProvider) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *SwayProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
|
func (s *SwayProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
|
||||||
section, err := sway.ParseKeys(s.configPath)
|
section, err := ParseSwayKeys(s.configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse sway config: %w", err)
|
return nil, fmt.Errorf("failed to parse sway config: %w", err)
|
||||||
}
|
}
|
||||||
@@ -41,7 +40,7 @@ func (s *SwayProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SwayProvider) convertSection(section *sway.Section, subcategory string, categorizedBinds map[string][]keybinds.Keybind) {
|
func (s *SwayProvider) convertSection(section *SwaySection, subcategory string, categorizedBinds map[string][]keybinds.Keybind) {
|
||||||
currentSubcat := subcategory
|
currentSubcat := subcategory
|
||||||
if section.Name != "" {
|
if section.Name != "" {
|
||||||
currentSubcat = section.Name
|
currentSubcat = section.Name
|
||||||
@@ -89,7 +88,7 @@ func (s *SwayProvider) categorizeByCommand(command string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SwayProvider) convertKeybind(kb *sway.KeyBinding, subcategory string) keybinds.Keybind {
|
func (s *SwayProvider) convertKeybind(kb *SwayKeyBinding, subcategory string) keybinds.Keybind {
|
||||||
key := s.formatKey(kb)
|
key := s.formatKey(kb)
|
||||||
desc := kb.Comment
|
desc := kb.Comment
|
||||||
|
|
||||||
@@ -104,7 +103,7 @@ func (s *SwayProvider) convertKeybind(kb *sway.KeyBinding, subcategory string) k
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SwayProvider) formatKey(kb *sway.KeyBinding) string {
|
func (s *SwayProvider) formatKey(kb *SwayKeyBinding) string {
|
||||||
parts := make([]string, 0, len(kb.Mods)+1)
|
parts := make([]string, 0, len(kb.Mods)+1)
|
||||||
parts = append(parts, kb.Mods...)
|
parts = append(parts, kb.Mods...)
|
||||||
parts = append(parts, kb.Key)
|
parts = append(parts, kb.Key)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package sway
|
package providers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
@@ -8,40 +8,40 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TitleRegex = "#+!"
|
SwayTitleRegex = "#+!"
|
||||||
HideComment = "[hidden]"
|
SwayHideComment = "[hidden]"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ModSeparators = []rune{'+', ' '}
|
var SwayModSeparators = []rune{'+', ' '}
|
||||||
|
|
||||||
type KeyBinding struct {
|
type SwayKeyBinding struct {
|
||||||
Mods []string `json:"mods"`
|
Mods []string `json:"mods"`
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
Command string `json:"command"`
|
Command string `json:"command"`
|
||||||
Comment string `json:"comment"`
|
Comment string `json:"comment"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Section struct {
|
type SwaySection struct {
|
||||||
Children []Section `json:"children"`
|
Children []SwaySection `json:"children"`
|
||||||
Keybinds []KeyBinding `json:"keybinds"`
|
Keybinds []SwayKeyBinding `json:"keybinds"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Parser struct {
|
type SwayParser struct {
|
||||||
contentLines []string
|
contentLines []string
|
||||||
readingLine int
|
readingLine int
|
||||||
variables map[string]string
|
variables map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewParser() *Parser {
|
func NewSwayParser() *SwayParser {
|
||||||
return &Parser{
|
return &SwayParser{
|
||||||
contentLines: []string{},
|
contentLines: []string{},
|
||||||
readingLine: 0,
|
readingLine: 0,
|
||||||
variables: make(map[string]string),
|
variables: make(map[string]string),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) ReadContent(path string) error {
|
func (p *SwayParser) ReadContent(path string) error {
|
||||||
expandedPath := os.ExpandEnv(path)
|
expandedPath := os.ExpandEnv(path)
|
||||||
expandedPath = filepath.Clean(expandedPath)
|
expandedPath = filepath.Clean(expandedPath)
|
||||||
if strings.HasPrefix(expandedPath, "~") {
|
if strings.HasPrefix(expandedPath, "~") {
|
||||||
@@ -88,7 +88,7 @@ func (p *Parser) ReadContent(path string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) parseVariables() {
|
func (p *SwayParser) parseVariables() {
|
||||||
setRegex := regexp.MustCompile(`^\s*set\s+\$(\w+)\s+(.+)$`)
|
setRegex := regexp.MustCompile(`^\s*set\s+\$(\w+)\s+(.+)$`)
|
||||||
for _, line := range p.contentLines {
|
for _, line := range p.contentLines {
|
||||||
matches := setRegex.FindStringSubmatch(line)
|
matches := setRegex.FindStringSubmatch(line)
|
||||||
@@ -100,7 +100,7 @@ func (p *Parser) parseVariables() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) expandVariables(text string) string {
|
func (p *SwayParser) expandVariables(text string) string {
|
||||||
result := text
|
result := text
|
||||||
for varName, varValue := range p.variables {
|
for varName, varValue := range p.variables {
|
||||||
result = strings.ReplaceAll(result, "$"+varName, varValue)
|
result = strings.ReplaceAll(result, "$"+varName, varValue)
|
||||||
@@ -108,7 +108,7 @@ func (p *Parser) expandVariables(text string) string {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func autogenerateComment(command string) string {
|
func swayAutogenerateComment(command string) string {
|
||||||
command = strings.TrimSpace(command)
|
command = strings.TrimSpace(command)
|
||||||
|
|
||||||
if strings.HasPrefix(command, "exec ") {
|
if strings.HasPrefix(command, "exec ") {
|
||||||
@@ -200,7 +200,7 @@ func autogenerateComment(command string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
|
func (p *SwayParser) getKeybindAtLine(lineNumber int) *SwayKeyBinding {
|
||||||
if lineNumber >= len(p.contentLines) {
|
if lineNumber >= len(p.contentLines) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -223,7 +223,7 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
|
|||||||
comment = strings.TrimSpace(parts[1])
|
comment = strings.TrimSpace(parts[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(comment, HideComment) {
|
if strings.HasPrefix(comment, SwayHideComment) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,11 +249,11 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
|
|||||||
var modList []string
|
var modList []string
|
||||||
var key string
|
var key string
|
||||||
|
|
||||||
modstring := keyCombo + string(ModSeparators[0])
|
modstring := keyCombo + string(SwayModSeparators[0])
|
||||||
pos := 0
|
pos := 0
|
||||||
for index, char := range modstring {
|
for index, char := range modstring {
|
||||||
isModSep := false
|
isModSep := false
|
||||||
for _, sep := range ModSeparators {
|
for _, sep := range SwayModSeparators {
|
||||||
if char == sep {
|
if char == sep {
|
||||||
isModSep = true
|
isModSep = true
|
||||||
break
|
break
|
||||||
@@ -262,7 +262,7 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
|
|||||||
if isModSep {
|
if isModSep {
|
||||||
if index-pos > 0 {
|
if index-pos > 0 {
|
||||||
part := modstring[pos:index]
|
part := modstring[pos:index]
|
||||||
if isMod(part) {
|
if swayIsMod(part) {
|
||||||
modList = append(modList, part)
|
modList = append(modList, part)
|
||||||
} else {
|
} else {
|
||||||
key = part
|
key = part
|
||||||
@@ -273,12 +273,12 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if comment == "" {
|
if comment == "" {
|
||||||
comment = autogenerateComment(command)
|
comment = swayAutogenerateComment(command)
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = flags
|
_ = flags
|
||||||
|
|
||||||
return &KeyBinding{
|
return &SwayKeyBinding{
|
||||||
Mods: modList,
|
Mods: modList,
|
||||||
Key: key,
|
Key: key,
|
||||||
Command: command,
|
Command: command,
|
||||||
@@ -286,7 +286,7 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func isMod(s string) bool {
|
func swayIsMod(s string) bool {
|
||||||
s = strings.ToLower(s)
|
s = strings.ToLower(s)
|
||||||
if s == "mod1" || s == "mod2" || s == "mod3" || s == "mod4" || s == "mod5" ||
|
if s == "mod1" || s == "mod2" || s == "mod3" || s == "mod4" || s == "mod5" ||
|
||||||
s == "shift" || s == "control" || s == "ctrl" || s == "alt" || s == "super" ||
|
s == "shift" || s == "control" || s == "ctrl" || s == "alt" || s == "super" ||
|
||||||
@@ -307,8 +307,8 @@ func isMod(s string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) getBindsRecursive(currentContent *Section, scope int) *Section {
|
func (p *SwayParser) getBindsRecursive(currentContent *SwaySection, scope int) *SwaySection {
|
||||||
titleRegex := regexp.MustCompile(TitleRegex)
|
titleRegex := regexp.MustCompile(SwayTitleRegex)
|
||||||
|
|
||||||
for p.readingLine < len(p.contentLines) {
|
for p.readingLine < len(p.contentLines) {
|
||||||
line := p.contentLines[p.readingLine]
|
line := p.contentLines[p.readingLine]
|
||||||
@@ -325,9 +325,9 @@ func (p *Parser) getBindsRecursive(currentContent *Section, scope int) *Section
|
|||||||
sectionName := strings.TrimSpace(line[headingScope+1:])
|
sectionName := strings.TrimSpace(line[headingScope+1:])
|
||||||
p.readingLine++
|
p.readingLine++
|
||||||
|
|
||||||
childSection := &Section{
|
childSection := &SwaySection{
|
||||||
Children: []Section{},
|
Children: []SwaySection{},
|
||||||
Keybinds: []KeyBinding{},
|
Keybinds: []SwayKeyBinding{},
|
||||||
Name: sectionName,
|
Name: sectionName,
|
||||||
}
|
}
|
||||||
result := p.getBindsRecursive(childSection, headingScope)
|
result := p.getBindsRecursive(childSection, headingScope)
|
||||||
@@ -348,18 +348,18 @@ func (p *Parser) getBindsRecursive(currentContent *Section, scope int) *Section
|
|||||||
return currentContent
|
return currentContent
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) ParseKeys() *Section {
|
func (p *SwayParser) ParseKeys() *SwaySection {
|
||||||
p.readingLine = 0
|
p.readingLine = 0
|
||||||
rootSection := &Section{
|
rootSection := &SwaySection{
|
||||||
Children: []Section{},
|
Children: []SwaySection{},
|
||||||
Keybinds: []KeyBinding{},
|
Keybinds: []SwayKeyBinding{},
|
||||||
Name: "",
|
Name: "",
|
||||||
}
|
}
|
||||||
return p.getBindsRecursive(rootSection, 0)
|
return p.getBindsRecursive(rootSection, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseKeys(path string) (*Section, error) {
|
func ParseSwayKeys(path string) (*SwaySection, error) {
|
||||||
parser := NewParser()
|
parser := NewSwayParser()
|
||||||
if err := parser.ReadContent(path); err != nil {
|
if err := parser.ReadContent(path); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package sway
|
package providers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAutogenerateComment(t *testing.T) {
|
func TestSwayAutogenerateComment(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
command string
|
command string
|
||||||
expected string
|
expected string
|
||||||
@@ -46,25 +46,25 @@ func TestAutogenerateComment(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.command, func(t *testing.T) {
|
t.Run(tt.command, func(t *testing.T) {
|
||||||
result := autogenerateComment(tt.command)
|
result := swayAutogenerateComment(tt.command)
|
||||||
if result != tt.expected {
|
if result != tt.expected {
|
||||||
t.Errorf("autogenerateComment(%q) = %q, want %q",
|
t.Errorf("swayAutogenerateComment(%q) = %q, want %q",
|
||||||
tt.command, result, tt.expected)
|
tt.command, result, tt.expected)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetKeybindAtLine(t *testing.T) {
|
func TestSwayGetKeybindAtLine(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
line string
|
line string
|
||||||
expected *KeyBinding
|
expected *SwayKeyBinding
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "basic_keybind",
|
name: "basic_keybind",
|
||||||
line: "bindsym Mod4+q kill",
|
line: "bindsym Mod4+q kill",
|
||||||
expected: &KeyBinding{
|
expected: &SwayKeyBinding{
|
||||||
Mods: []string{"Mod4"},
|
Mods: []string{"Mod4"},
|
||||||
Key: "q",
|
Key: "q",
|
||||||
Command: "kill",
|
Command: "kill",
|
||||||
@@ -74,7 +74,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "keybind_with_exec",
|
name: "keybind_with_exec",
|
||||||
line: "bindsym Mod4+t exec kitty",
|
line: "bindsym Mod4+t exec kitty",
|
||||||
expected: &KeyBinding{
|
expected: &SwayKeyBinding{
|
||||||
Mods: []string{"Mod4"},
|
Mods: []string{"Mod4"},
|
||||||
Key: "t",
|
Key: "t",
|
||||||
Command: "exec kitty",
|
Command: "exec kitty",
|
||||||
@@ -84,7 +84,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "keybind_with_comment",
|
name: "keybind_with_comment",
|
||||||
line: "bindsym Mod4+Space exec dms ipc call spotlight toggle # Open launcher",
|
line: "bindsym Mod4+Space exec dms ipc call spotlight toggle # Open launcher",
|
||||||
expected: &KeyBinding{
|
expected: &SwayKeyBinding{
|
||||||
Mods: []string{"Mod4"},
|
Mods: []string{"Mod4"},
|
||||||
Key: "Space",
|
Key: "Space",
|
||||||
Command: "exec dms ipc call spotlight toggle",
|
Command: "exec dms ipc call spotlight toggle",
|
||||||
@@ -99,7 +99,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "keybind_multiple_mods",
|
name: "keybind_multiple_mods",
|
||||||
line: "bindsym Mod4+Shift+e exit",
|
line: "bindsym Mod4+Shift+e exit",
|
||||||
expected: &KeyBinding{
|
expected: &SwayKeyBinding{
|
||||||
Mods: []string{"Mod4", "Shift"},
|
Mods: []string{"Mod4", "Shift"},
|
||||||
Key: "e",
|
Key: "e",
|
||||||
Command: "exit",
|
Command: "exit",
|
||||||
@@ -109,7 +109,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "keybind_no_mods",
|
name: "keybind_no_mods",
|
||||||
line: "bindsym Print exec grim screenshot.png",
|
line: "bindsym Print exec grim screenshot.png",
|
||||||
expected: &KeyBinding{
|
expected: &SwayKeyBinding{
|
||||||
Mods: []string{},
|
Mods: []string{},
|
||||||
Key: "Print",
|
Key: "Print",
|
||||||
Command: "exec grim screenshot.png",
|
Command: "exec grim screenshot.png",
|
||||||
@@ -119,7 +119,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "keybind_with_flags",
|
name: "keybind_with_flags",
|
||||||
line: "bindsym --release Mod4+x exec notify-send released",
|
line: "bindsym --release Mod4+x exec notify-send released",
|
||||||
expected: &KeyBinding{
|
expected: &SwayKeyBinding{
|
||||||
Mods: []string{"Mod4"},
|
Mods: []string{"Mod4"},
|
||||||
Key: "x",
|
Key: "x",
|
||||||
Command: "exec notify-send released",
|
Command: "exec notify-send released",
|
||||||
@@ -129,7 +129,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "keybind_focus_direction",
|
name: "keybind_focus_direction",
|
||||||
line: "bindsym Mod4+Left focus left",
|
line: "bindsym Mod4+Left focus left",
|
||||||
expected: &KeyBinding{
|
expected: &SwayKeyBinding{
|
||||||
Mods: []string{"Mod4"},
|
Mods: []string{"Mod4"},
|
||||||
Key: "Left",
|
Key: "Left",
|
||||||
Command: "focus left",
|
Command: "focus left",
|
||||||
@@ -139,7 +139,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "keybind_workspace",
|
name: "keybind_workspace",
|
||||||
line: "bindsym Mod4+1 workspace number 1",
|
line: "bindsym Mod4+1 workspace number 1",
|
||||||
expected: &KeyBinding{
|
expected: &SwayKeyBinding{
|
||||||
Mods: []string{"Mod4"},
|
Mods: []string{"Mod4"},
|
||||||
Key: "1",
|
Key: "1",
|
||||||
Command: "workspace number 1",
|
Command: "workspace number 1",
|
||||||
@@ -150,7 +150,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
parser := NewParser()
|
parser := NewSwayParser()
|
||||||
parser.contentLines = []string{tt.line}
|
parser.contentLines = []string{tt.line}
|
||||||
result := parser.getKeybindAtLine(0)
|
result := parser.getKeybindAtLine(0)
|
||||||
|
|
||||||
@@ -188,7 +188,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVariableExpansion(t *testing.T) {
|
func TestSwayVariableExpansion(t *testing.T) {
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
configFile := filepath.Join(tmpDir, "config")
|
configFile := filepath.Join(tmpDir, "config")
|
||||||
|
|
||||||
@@ -204,9 +204,9 @@ bindsym $mod+d exec $menu
|
|||||||
t.Fatalf("Failed to write test config: %v", err)
|
t.Fatalf("Failed to write test config: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
section, err := ParseKeys(configFile)
|
section, err := ParseSwayKeys(configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ParseKeys failed: %v", err)
|
t.Fatalf("ParseSwayKeys failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(section.Keybinds) != 2 {
|
if len(section.Keybinds) != 2 {
|
||||||
@@ -229,7 +229,7 @@ bindsym $mod+d exec $menu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseKeysWithSections(t *testing.T) {
|
func TestSwayParseKeysWithSections(t *testing.T) {
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
configFile := filepath.Join(tmpDir, "config")
|
configFile := filepath.Join(tmpDir, "config")
|
||||||
|
|
||||||
@@ -251,9 +251,9 @@ bindsym $mod+t exec kitty # Terminal
|
|||||||
t.Fatalf("Failed to write test config: %v", err)
|
t.Fatalf("Failed to write test config: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
section, err := ParseKeys(tmpDir)
|
section, err := ParseSwayKeys(tmpDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ParseKeys failed: %v", err)
|
t.Fatalf("ParseSwayKeys failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(section.Children) != 2 {
|
if len(section.Children) != 2 {
|
||||||
@@ -296,7 +296,7 @@ bindsym $mod+t exec kitty # Terminal
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadContentErrors(t *testing.T) {
|
func TestSwayReadContentErrors(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
path string
|
path string
|
||||||
@@ -313,7 +313,7 @@ func TestReadContentErrors(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
_, err := ParseKeys(tt.path)
|
_, err := ParseSwayKeys(tt.path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("Expected error, got nil")
|
t.Error("Expected error, got nil")
|
||||||
}
|
}
|
||||||
@@ -321,7 +321,7 @@ func TestReadContentErrors(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadContentWithTildeExpansion(t *testing.T) {
|
func TestSwayReadContentWithTildeExpansion(t *testing.T) {
|
||||||
homeDir, err := os.UserHomeDir()
|
homeDir, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Skip("Cannot get home directory")
|
t.Skip("Cannot get home directory")
|
||||||
@@ -343,7 +343,7 @@ func TestReadContentWithTildeExpansion(t *testing.T) {
|
|||||||
t.Skip("Cannot create relative path")
|
t.Skip("Cannot create relative path")
|
||||||
}
|
}
|
||||||
|
|
||||||
parser := NewParser()
|
parser := NewSwayParser()
|
||||||
tildePathMatch := "~/" + relPath
|
tildePathMatch := "~/" + relPath
|
||||||
err = parser.ReadContent(tildePathMatch)
|
err = parser.ReadContent(tildePathMatch)
|
||||||
|
|
||||||
@@ -352,7 +352,7 @@ func TestReadContentWithTildeExpansion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEmptyAndCommentLines(t *testing.T) {
|
func TestSwayEmptyAndCommentLines(t *testing.T) {
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
configFile := filepath.Join(tmpDir, "config")
|
configFile := filepath.Join(tmpDir, "config")
|
||||||
|
|
||||||
@@ -369,9 +369,9 @@ bindsym Mod4+t exec kitty
|
|||||||
t.Fatalf("Failed to write test config: %v", err)
|
t.Fatalf("Failed to write test config: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
section, err := ParseKeys(configFile)
|
section, err := ParseSwayKeys(configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ParseKeys failed: %v", err)
|
t.Fatalf("ParseSwayKeys failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(section.Keybinds) != 2 {
|
if len(section.Keybinds) != 2 {
|
||||||
@@ -379,7 +379,7 @@ bindsym Mod4+t exec kitty
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRealWorldConfig(t *testing.T) {
|
func TestSwayRealWorldConfig(t *testing.T) {
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
configFile := filepath.Join(tmpDir, "config")
|
configFile := filepath.Join(tmpDir, "config")
|
||||||
|
|
||||||
@@ -408,9 +408,9 @@ bindsym $mod+Shift+1 move container to workspace number 1
|
|||||||
t.Fatalf("Failed to write test config: %v", err)
|
t.Fatalf("Failed to write test config: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
section, err := ParseKeys(configFile)
|
section, err := ParseSwayKeys(configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ParseKeys failed: %v", err)
|
t.Fatalf("ParseSwayKeys failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(section.Keybinds) < 9 {
|
if len(section.Keybinds) < 9 {
|
||||||
@@ -444,7 +444,7 @@ bindsym $mod+Shift+1 move container to workspace number 1
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsMod(t *testing.T) {
|
func TestSwayIsMod(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
input string
|
input string
|
||||||
expected bool
|
expected bool
|
||||||
@@ -462,9 +462,9 @@ func TestIsMod(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.input, func(t *testing.T) {
|
t.Run(tt.input, func(t *testing.T) {
|
||||||
result := isMod(tt.input)
|
result := swayIsMod(tt.input)
|
||||||
if result != tt.expected {
|
if result != tt.expected {
|
||||||
t.Errorf("isMod(%q) = %v, want %v", tt.input, result, tt.expected)
|
t.Errorf("swayIsMod(%q) = %v, want %v", tt.input, result, tt.expected)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -4,8 +4,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/sway"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSwayProviderName(t *testing.T) {
|
func TestSwayProviderName(t *testing.T) {
|
||||||
@@ -76,12 +74,12 @@ func TestSwayCategorizeByCommand(t *testing.T) {
|
|||||||
func TestSwayFormatKey(t *testing.T) {
|
func TestSwayFormatKey(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
keybind *sway.KeyBinding
|
keybind *SwayKeyBinding
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "single_mod",
|
name: "single_mod",
|
||||||
keybind: &sway.KeyBinding{
|
keybind: &SwayKeyBinding{
|
||||||
Mods: []string{"Mod4"},
|
Mods: []string{"Mod4"},
|
||||||
Key: "q",
|
Key: "q",
|
||||||
},
|
},
|
||||||
@@ -89,7 +87,7 @@ func TestSwayFormatKey(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multiple_mods",
|
name: "multiple_mods",
|
||||||
keybind: &sway.KeyBinding{
|
keybind: &SwayKeyBinding{
|
||||||
Mods: []string{"Mod4", "Shift"},
|
Mods: []string{"Mod4", "Shift"},
|
||||||
Key: "e",
|
Key: "e",
|
||||||
},
|
},
|
||||||
@@ -97,7 +95,7 @@ func TestSwayFormatKey(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no_mods",
|
name: "no_mods",
|
||||||
keybind: &sway.KeyBinding{
|
keybind: &SwayKeyBinding{
|
||||||
Mods: []string{},
|
Mods: []string{},
|
||||||
Key: "Print",
|
Key: "Print",
|
||||||
},
|
},
|
||||||
@@ -119,13 +117,13 @@ func TestSwayFormatKey(t *testing.T) {
|
|||||||
func TestSwayConvertKeybind(t *testing.T) {
|
func TestSwayConvertKeybind(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
keybind *sway.KeyBinding
|
keybind *SwayKeyBinding
|
||||||
wantKey string
|
wantKey string
|
||||||
wantDesc string
|
wantDesc string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "with_comment",
|
name: "with_comment",
|
||||||
keybind: &sway.KeyBinding{
|
keybind: &SwayKeyBinding{
|
||||||
Mods: []string{"Mod4"},
|
Mods: []string{"Mod4"},
|
||||||
Key: "t",
|
Key: "t",
|
||||||
Command: "exec kitty",
|
Command: "exec kitty",
|
||||||
@@ -136,7 +134,7 @@ func TestSwayConvertKeybind(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "without_comment",
|
name: "without_comment",
|
||||||
keybind: &sway.KeyBinding{
|
keybind: &SwayKeyBinding{
|
||||||
Mods: []string{"Mod4"},
|
Mods: []string{"Mod4"},
|
||||||
Key: "r",
|
Key: "r",
|
||||||
Command: "reload",
|
Command: "reload",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package logger
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@@ -64,7 +64,7 @@ func (l *FileLogger) writeToFile(message string) {
|
|||||||
redacted := l.redactPassword(message)
|
redacted := l.redactPassword(message)
|
||||||
timestamp := time.Now().Format("15:04:05.000")
|
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()
|
l.writer.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ func (l *FileLogger) Close() error {
|
|||||||
defer l.mu.Unlock()
|
defer l.mu.Unlock()
|
||||||
|
|
||||||
footer := fmt.Sprintf("\n=== DankInstall Log End ===\nCompleted: %s\n", time.Now().Format(time.RFC3339))
|
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()
|
l.writer.Flush()
|
||||||
|
|
||||||
if err := l.file.Sync(); err != nil {
|
if err := l.file.Sync(); err != nil {
|
||||||
@@ -22,6 +22,99 @@ func (_m *MockCUPSClientInterface) EXPECT() *MockCUPSClientInterface_Expecter {
|
|||||||
return &MockCUPSClientInterface_Expecter{mock: &_m.Mock}
|
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
|
// CancelAllJob provides a mock function with given fields: printer, purge
|
||||||
func (_m *MockCUPSClientInterface) CancelAllJob(printer string, purge bool) error {
|
func (_m *MockCUPSClientInterface) CancelAllJob(printer string, purge bool) error {
|
||||||
ret := _m.Called(printer, purge)
|
ret := _m.Called(printer, purge)
|
||||||
@@ -116,6 +209,312 @@ func (_c *MockCUPSClientInterface_CancelJob_Call) RunAndReturn(run func(int, boo
|
|||||||
return _c
|
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
|
// 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) {
|
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)
|
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
|
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
|
// GetPrinters provides a mock function with given fields: attributes
|
||||||
func (_m *MockCUPSClientInterface) GetPrinters(attributes []string) (map[string]ipp.Attributes, error) {
|
func (_m *MockCUPSClientInterface) GetPrinters(attributes []string) (map[string]ipp.Attributes, error) {
|
||||||
ret := _m.Called(attributes)
|
ret := _m.Called(attributes)
|
||||||
@@ -238,6 +694,100 @@ func (_c *MockCUPSClientInterface_GetPrinters_Call) RunAndReturn(run func([]stri
|
|||||||
return _c
|
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
|
// PausePrinter provides a mock function with given fields: printer
|
||||||
func (_m *MockCUPSClientInterface) PausePrinter(printer string) error {
|
func (_m *MockCUPSClientInterface) PausePrinter(printer string) error {
|
||||||
ret := _m.Called(printer)
|
ret := _m.Called(printer)
|
||||||
@@ -284,6 +834,156 @@ func (_c *MockCUPSClientInterface_PausePrinter_Call) RunAndReturn(run func(strin
|
|||||||
return _c
|
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
|
// ResumePrinter provides a mock function with given fields: printer
|
||||||
func (_m *MockCUPSClientInterface) ResumePrinter(printer string) error {
|
func (_m *MockCUPSClientInterface) ResumePrinter(printer string) error {
|
||||||
ret := _m.Called(printer)
|
ret := _m.Called(printer)
|
||||||
@@ -390,6 +1090,147 @@ func (_c *MockCUPSClientInterface_SendRequest_Call) RunAndReturn(run func(string
|
|||||||
return _c
|
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.
|
// 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.
|
// The first argument is typically a *testing.T value.
|
||||||
func NewMockCUPSClientInterface(t interface {
|
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
|
||||||
|
}
|
||||||
295
core/internal/mocks/evdev/mock_EvdevDevice.go
Normal file
295
core/internal/mocks/evdev/mock_EvdevDevice.go
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
// Code generated by mockery v2.53.5. DO NOT EDIT.
|
||||||
|
|
||||||
|
package mocks_evdev
|
||||||
|
|
||||||
|
import (
|
||||||
|
go_evdev "github.com/holoplot/go-evdev"
|
||||||
|
mock "github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockEvdevDevice is an autogenerated mock type for the EvdevDevice type
|
||||||
|
type MockEvdevDevice struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
type MockEvdevDevice_Expecter struct {
|
||||||
|
mock *mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_m *MockEvdevDevice) EXPECT() *MockEvdevDevice_Expecter {
|
||||||
|
return &MockEvdevDevice_Expecter{mock: &_m.Mock}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close provides a mock function with no fields
|
||||||
|
func (_m *MockEvdevDevice) Close() error {
|
||||||
|
ret := _m.Called()
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Close")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if rf, ok := ret.Get(0).(func() error); ok {
|
||||||
|
r0 = rf()
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockEvdevDevice_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close'
|
||||||
|
type MockEvdevDevice_Close_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close is a helper method to define mock.On call
|
||||||
|
func (_e *MockEvdevDevice_Expecter) Close() *MockEvdevDevice_Close_Call {
|
||||||
|
return &MockEvdevDevice_Close_Call{Call: _e.mock.On("Close")}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockEvdevDevice_Close_Call) Run(run func()) *MockEvdevDevice_Close_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run()
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockEvdevDevice_Close_Call) Return(_a0 error) *MockEvdevDevice_Close_Call {
|
||||||
|
_c.Call.Return(_a0)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockEvdevDevice_Close_Call) RunAndReturn(run func() error) *MockEvdevDevice_Close_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name provides a mock function with no fields
|
||||||
|
func (_m *MockEvdevDevice) Name() (string, error) {
|
||||||
|
ret := _m.Called()
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Name")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 string
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func() (string, error)); ok {
|
||||||
|
return rf()
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func() string); ok {
|
||||||
|
r0 = rf()
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func() error); ok {
|
||||||
|
r1 = rf()
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockEvdevDevice_Name_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Name'
|
||||||
|
type MockEvdevDevice_Name_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name is a helper method to define mock.On call
|
||||||
|
func (_e *MockEvdevDevice_Expecter) Name() *MockEvdevDevice_Name_Call {
|
||||||
|
return &MockEvdevDevice_Name_Call{Call: _e.mock.On("Name")}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockEvdevDevice_Name_Call) Run(run func()) *MockEvdevDevice_Name_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run()
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockEvdevDevice_Name_Call) Return(_a0 string, _a1 error) *MockEvdevDevice_Name_Call {
|
||||||
|
_c.Call.Return(_a0, _a1)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockEvdevDevice_Name_Call) RunAndReturn(run func() (string, error)) *MockEvdevDevice_Name_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path provides a mock function with no fields
|
||||||
|
func (_m *MockEvdevDevice) Path() string {
|
||||||
|
ret := _m.Called()
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Path")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 string
|
||||||
|
if rf, ok := ret.Get(0).(func() string); ok {
|
||||||
|
r0 = rf()
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockEvdevDevice_Path_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Path'
|
||||||
|
type MockEvdevDevice_Path_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path is a helper method to define mock.On call
|
||||||
|
func (_e *MockEvdevDevice_Expecter) Path() *MockEvdevDevice_Path_Call {
|
||||||
|
return &MockEvdevDevice_Path_Call{Call: _e.mock.On("Path")}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockEvdevDevice_Path_Call) Run(run func()) *MockEvdevDevice_Path_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run()
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockEvdevDevice_Path_Call) Return(_a0 string) *MockEvdevDevice_Path_Call {
|
||||||
|
_c.Call.Return(_a0)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockEvdevDevice_Path_Call) RunAndReturn(run func() string) *MockEvdevDevice_Path_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadOne provides a mock function with no fields
|
||||||
|
func (_m *MockEvdevDevice) ReadOne() (*go_evdev.InputEvent, error) {
|
||||||
|
ret := _m.Called()
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for ReadOne")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 *go_evdev.InputEvent
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func() (*go_evdev.InputEvent, error)); ok {
|
||||||
|
return rf()
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func() *go_evdev.InputEvent); ok {
|
||||||
|
r0 = rf()
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(*go_evdev.InputEvent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func() error); ok {
|
||||||
|
r1 = rf()
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockEvdevDevice_ReadOne_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReadOne'
|
||||||
|
type MockEvdevDevice_ReadOne_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadOne is a helper method to define mock.On call
|
||||||
|
func (_e *MockEvdevDevice_Expecter) ReadOne() *MockEvdevDevice_ReadOne_Call {
|
||||||
|
return &MockEvdevDevice_ReadOne_Call{Call: _e.mock.On("ReadOne")}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockEvdevDevice_ReadOne_Call) Run(run func()) *MockEvdevDevice_ReadOne_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run()
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockEvdevDevice_ReadOne_Call) Return(_a0 *go_evdev.InputEvent, _a1 error) *MockEvdevDevice_ReadOne_Call {
|
||||||
|
_c.Call.Return(_a0, _a1)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockEvdevDevice_ReadOne_Call) RunAndReturn(run func() (*go_evdev.InputEvent, error)) *MockEvdevDevice_ReadOne_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// State provides a mock function with given fields: t
|
||||||
|
func (_m *MockEvdevDevice) State(t go_evdev.EvType) (go_evdev.StateMap, error) {
|
||||||
|
ret := _m.Called(t)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for State")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 go_evdev.StateMap
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func(go_evdev.EvType) (go_evdev.StateMap, error)); ok {
|
||||||
|
return rf(t)
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func(go_evdev.EvType) go_evdev.StateMap); ok {
|
||||||
|
r0 = rf(t)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(go_evdev.StateMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(go_evdev.EvType) error); ok {
|
||||||
|
r1 = rf(t)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockEvdevDevice_State_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'State'
|
||||||
|
type MockEvdevDevice_State_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// State is a helper method to define mock.On call
|
||||||
|
// - t go_evdev.EvType
|
||||||
|
func (_e *MockEvdevDevice_Expecter) State(t interface{}) *MockEvdevDevice_State_Call {
|
||||||
|
return &MockEvdevDevice_State_Call{Call: _e.mock.On("State", t)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockEvdevDevice_State_Call) Run(run func(t go_evdev.EvType)) *MockEvdevDevice_State_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(go_evdev.EvType))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockEvdevDevice_State_Call) Return(_a0 go_evdev.StateMap, _a1 error) *MockEvdevDevice_State_Call {
|
||||||
|
_c.Call.Return(_a0, _a1)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *MockEvdevDevice_State_Call) RunAndReturn(run func(go_evdev.EvType) (go_evdev.StateMap, error)) *MockEvdevDevice_State_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockEvdevDevice creates a new instance of MockEvdevDevice. 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 NewMockEvdevDevice(t interface {
|
||||||
|
mock.TestingT
|
||||||
|
Cleanup(func())
|
||||||
|
}) *MockEvdevDevice {
|
||||||
|
mock := &MockEvdevDevice{}
|
||||||
|
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
|
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
|
// DisconnectAllVPN provides a mock function with no fields
|
||||||
func (_m *MockBackend) DisconnectAllVPN() error {
|
func (_m *MockBackend) DisconnectAllVPN() error {
|
||||||
ret := _m.Called()
|
ret := _m.Called()
|
||||||
@@ -418,6 +464,52 @@ func (_c *MockBackend_DisconnectEthernet_Call) RunAndReturn(run func() error) *M
|
|||||||
return _c
|
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
|
// DisconnectVPN provides a mock function with given fields: uuidOrName
|
||||||
func (_m *MockBackend) DisconnectVPN(uuidOrName string) error {
|
func (_m *MockBackend) DisconnectVPN(uuidOrName string) error {
|
||||||
ret := _m.Called(uuidOrName)
|
ret := _m.Called(uuidOrName)
|
||||||
@@ -509,6 +601,52 @@ func (_c *MockBackend_DisconnectWiFi_Call) RunAndReturn(run func() error) *MockB
|
|||||||
return _c
|
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
|
// ForgetWiFiNetwork provides a mock function with given fields: ssid
|
||||||
func (_m *MockBackend) ForgetWiFiNetwork(ssid string) error {
|
func (_m *MockBackend) ForgetWiFiNetwork(ssid string) error {
|
||||||
ret := _m.Called(ssid)
|
ret := _m.Called(ssid)
|
||||||
@@ -612,6 +750,53 @@ func (_c *MockBackend_GetCurrentState_Call) RunAndReturn(run func() (*network.Ba
|
|||||||
return _c
|
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
|
// GetPromptBroker provides a mock function with no fields
|
||||||
func (_m *MockBackend) GetPromptBroker() network.PromptBroker {
|
func (_m *MockBackend) GetPromptBroker() network.PromptBroker {
|
||||||
ret := _m.Called()
|
ret := _m.Called()
|
||||||
@@ -659,6 +844,111 @@ func (_c *MockBackend_GetPromptBroker_Call) RunAndReturn(run func() network.Prom
|
|||||||
return _c
|
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
|
// GetWiFiEnabled provides a mock function with no fields
|
||||||
func (_m *MockBackend) GetWiFiEnabled() (bool, error) {
|
func (_m *MockBackend) GetWiFiEnabled() (bool, error) {
|
||||||
ret := _m.Called()
|
ret := _m.Called()
|
||||||
@@ -887,6 +1177,65 @@ func (_c *MockBackend_GetWiredNetworkDetails_Call) RunAndReturn(run func(string)
|
|||||||
return _c
|
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
|
// Initialize provides a mock function with no fields
|
||||||
func (_m *MockBackend) Initialize() error {
|
func (_m *MockBackend) Initialize() error {
|
||||||
ret := _m.Called()
|
ret := _m.Called()
|
||||||
@@ -989,6 +1338,63 @@ func (_c *MockBackend_ListActiveVPN_Call) RunAndReturn(run func() ([]network.VPN
|
|||||||
return _c
|
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
|
// ListVPNProfiles provides a mock function with no fields
|
||||||
func (_m *MockBackend) ListVPNProfiles() ([]network.VPNProfile, error) {
|
func (_m *MockBackend) ListVPNProfiles() ([]network.VPNProfile, error) {
|
||||||
ret := _m.Called()
|
ret := _m.Called()
|
||||||
@@ -1091,6 +1497,52 @@ func (_c *MockBackend_ScanWiFi_Call) RunAndReturn(run func() error) *MockBackend
|
|||||||
return _c
|
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
|
// SetPromptBroker provides a mock function with given fields: broker
|
||||||
func (_m *MockBackend) SetPromptBroker(broker network.PromptBroker) error {
|
func (_m *MockBackend) SetPromptBroker(broker network.PromptBroker) error {
|
||||||
ret := _m.Called(broker)
|
ret := _m.Called(broker)
|
||||||
@@ -1137,6 +1589,55 @@ func (_c *MockBackend_SetPromptBroker_Call) RunAndReturn(run func(network.Prompt
|
|||||||
return _c
|
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
|
// SetWiFiAutoconnect provides a mock function with given fields: ssid, autoconnect
|
||||||
func (_m *MockBackend) SetWiFiAutoconnect(ssid string, autoconnect bool) error {
|
func (_m *MockBackend) SetWiFiAutoconnect(ssid string, autoconnect bool) error {
|
||||||
ret := _m.Called(ssid, autoconnect)
|
ret := _m.Called(ssid, autoconnect)
|
||||||
@@ -1356,6 +1857,53 @@ func (_c *MockBackend_SubmitCredentials_Call) RunAndReturn(run func(string, map[
|
|||||||
return _c
|
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.
|
// 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.
|
// The first argument is typically a *testing.T value.
|
||||||
func NewMockBackend(t interface {
|
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 !repoExists {
|
||||||
if err := m.gitClient.PlainClone(repoPath, plugin.Repo); err != nil {
|
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)
|
return fmt.Errorf("failed to clone repository: %w", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -130,7 +130,7 @@ func (m *Manager) Install(plugin Plugin) error {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := m.gitClient.PlainClone(pluginPath, plugin.Repo); err != nil {
|
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)
|
return fmt.Errorf("failed to clone plugin: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
// Generated by go-wayland-scanner
|
// Generated by go-wayland-scanner
|
||||||
// https://github.com/yaslama/go-wayland/cmd/go-wayland-scanner
|
// https://github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/cmd/go-wayland-scanner
|
||||||
// XML file : internal/proto/xml/dwl-ipc-unstable-v2.xml
|
// XML file : internal/proto/xml/dwl-ipc-unstable-v2.xml
|
||||||
//
|
//
|
||||||
// dwl_ipc_unstable_v2 Protocol Copyright:
|
// dwl_ipc_unstable_v2 Protocol Copyright:
|
||||||
|
|
||||||
package dwl_ipc
|
package dwl_ipc
|
||||||
|
|
||||||
import "github.com/yaslama/go-wayland/wayland/client"
|
import "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
||||||
|
|
||||||
// ZdwlIpcManagerV2InterfaceName is the name of the interface as it appears in the [client.Registry].
|
// ZdwlIpcManagerV2InterfaceName is the name of the interface as it appears in the [client.Registry].
|
||||||
// It can be used to match the [client.RegistryGlobalEvent.Interface] in the
|
// It can be used to match the [client.RegistryGlobalEvent.Interface] in the
|
||||||
@@ -157,6 +157,16 @@ type ZdwlIpcOutputV2 struct {
|
|||||||
appidHandler ZdwlIpcOutputV2AppidHandlerFunc
|
appidHandler ZdwlIpcOutputV2AppidHandlerFunc
|
||||||
layoutSymbolHandler ZdwlIpcOutputV2LayoutSymbolHandlerFunc
|
layoutSymbolHandler ZdwlIpcOutputV2LayoutSymbolHandlerFunc
|
||||||
frameHandler ZdwlIpcOutputV2FrameHandlerFunc
|
frameHandler ZdwlIpcOutputV2FrameHandlerFunc
|
||||||
|
fullscreenHandler ZdwlIpcOutputV2FullscreenHandlerFunc
|
||||||
|
floatingHandler ZdwlIpcOutputV2FloatingHandlerFunc
|
||||||
|
xHandler ZdwlIpcOutputV2XHandlerFunc
|
||||||
|
yHandler ZdwlIpcOutputV2YHandlerFunc
|
||||||
|
widthHandler ZdwlIpcOutputV2WidthHandlerFunc
|
||||||
|
heightHandler ZdwlIpcOutputV2HeightHandlerFunc
|
||||||
|
lastLayerHandler ZdwlIpcOutputV2LastLayerHandlerFunc
|
||||||
|
kbLayoutHandler ZdwlIpcOutputV2KbLayoutHandlerFunc
|
||||||
|
keymodeHandler ZdwlIpcOutputV2KeymodeHandlerFunc
|
||||||
|
scalefactorHandler ZdwlIpcOutputV2ScalefactorHandlerFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewZdwlIpcOutputV2 : control dwl output
|
// NewZdwlIpcOutputV2 : control dwl output
|
||||||
@@ -251,6 +261,60 @@ func (i *ZdwlIpcOutputV2) SetLayout(index uint32) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Quit : Quit mango
|
||||||
|
// This request allows clients to instruct the compositor to quit mango.
|
||||||
|
func (i *ZdwlIpcOutputV2) Quit() error {
|
||||||
|
const opcode = 4
|
||||||
|
const _reqBufLen = 8
|
||||||
|
var _reqBuf [_reqBufLen]byte
|
||||||
|
l := 0
|
||||||
|
client.PutUint32(_reqBuf[l:4], i.ID())
|
||||||
|
l += 4
|
||||||
|
client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff))
|
||||||
|
l += 4
|
||||||
|
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendDispatch : Set the active tags of this output
|
||||||
|
//
|
||||||
|
// dispatch: dispatch name.
|
||||||
|
// arg1: arg1.
|
||||||
|
// arg2: arg2.
|
||||||
|
// arg3: arg3.
|
||||||
|
// arg4: arg4.
|
||||||
|
// arg5: arg5.
|
||||||
|
func (i *ZdwlIpcOutputV2) SendDispatch(dispatch, arg1, arg2, arg3, arg4, arg5 string) error {
|
||||||
|
const opcode = 5
|
||||||
|
dispatchLen := client.PaddedLen(len(dispatch) + 1)
|
||||||
|
arg1Len := client.PaddedLen(len(arg1) + 1)
|
||||||
|
arg2Len := client.PaddedLen(len(arg2) + 1)
|
||||||
|
arg3Len := client.PaddedLen(len(arg3) + 1)
|
||||||
|
arg4Len := client.PaddedLen(len(arg4) + 1)
|
||||||
|
arg5Len := client.PaddedLen(len(arg5) + 1)
|
||||||
|
_reqBufLen := 8 + (4 + dispatchLen) + (4 + arg1Len) + (4 + arg2Len) + (4 + arg3Len) + (4 + arg4Len) + (4 + arg5Len)
|
||||||
|
_reqBuf := make([]byte, _reqBufLen)
|
||||||
|
l := 0
|
||||||
|
client.PutUint32(_reqBuf[l:4], i.ID())
|
||||||
|
l += 4
|
||||||
|
client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff))
|
||||||
|
l += 4
|
||||||
|
client.PutString(_reqBuf[l:l+(4+dispatchLen)], dispatch)
|
||||||
|
l += (4 + dispatchLen)
|
||||||
|
client.PutString(_reqBuf[l:l+(4+arg1Len)], arg1)
|
||||||
|
l += (4 + arg1Len)
|
||||||
|
client.PutString(_reqBuf[l:l+(4+arg2Len)], arg2)
|
||||||
|
l += (4 + arg2Len)
|
||||||
|
client.PutString(_reqBuf[l:l+(4+arg3Len)], arg3)
|
||||||
|
l += (4 + arg3Len)
|
||||||
|
client.PutString(_reqBuf[l:l+(4+arg4Len)], arg4)
|
||||||
|
l += (4 + arg4Len)
|
||||||
|
client.PutString(_reqBuf[l:l+(4+arg5Len)], arg5)
|
||||||
|
l += (4 + arg5Len)
|
||||||
|
err := i.Context().WriteMsg(_reqBuf, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
type ZdwlIpcOutputV2TagState uint32
|
type ZdwlIpcOutputV2TagState uint32
|
||||||
|
|
||||||
// ZdwlIpcOutputV2TagState :
|
// ZdwlIpcOutputV2TagState :
|
||||||
@@ -399,6 +463,136 @@ func (i *ZdwlIpcOutputV2) SetFrameHandler(f ZdwlIpcOutputV2FrameHandlerFunc) {
|
|||||||
i.frameHandler = f
|
i.frameHandler = f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ZdwlIpcOutputV2FullscreenEvent : Update fullscreen status
|
||||||
|
//
|
||||||
|
// Indicates if the selected client on this output is fullscreen.
|
||||||
|
type ZdwlIpcOutputV2FullscreenEvent struct {
|
||||||
|
IsFullscreen uint32
|
||||||
|
}
|
||||||
|
type ZdwlIpcOutputV2FullscreenHandlerFunc func(ZdwlIpcOutputV2FullscreenEvent)
|
||||||
|
|
||||||
|
// SetFullscreenHandler : sets handler for ZdwlIpcOutputV2FullscreenEvent
|
||||||
|
func (i *ZdwlIpcOutputV2) SetFullscreenHandler(f ZdwlIpcOutputV2FullscreenHandlerFunc) {
|
||||||
|
i.fullscreenHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZdwlIpcOutputV2FloatingEvent : Update the floating status
|
||||||
|
//
|
||||||
|
// Indicates if the selected client on this output is floating.
|
||||||
|
type ZdwlIpcOutputV2FloatingEvent struct {
|
||||||
|
IsFloating uint32
|
||||||
|
}
|
||||||
|
type ZdwlIpcOutputV2FloatingHandlerFunc func(ZdwlIpcOutputV2FloatingEvent)
|
||||||
|
|
||||||
|
// SetFloatingHandler : sets handler for ZdwlIpcOutputV2FloatingEvent
|
||||||
|
func (i *ZdwlIpcOutputV2) SetFloatingHandler(f ZdwlIpcOutputV2FloatingHandlerFunc) {
|
||||||
|
i.floatingHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZdwlIpcOutputV2XEvent : Update the x coordinates
|
||||||
|
//
|
||||||
|
// Indicates if x coordinates of the selected client.
|
||||||
|
type ZdwlIpcOutputV2XEvent struct {
|
||||||
|
X int32
|
||||||
|
}
|
||||||
|
type ZdwlIpcOutputV2XHandlerFunc func(ZdwlIpcOutputV2XEvent)
|
||||||
|
|
||||||
|
// SetXHandler : sets handler for ZdwlIpcOutputV2XEvent
|
||||||
|
func (i *ZdwlIpcOutputV2) SetXHandler(f ZdwlIpcOutputV2XHandlerFunc) {
|
||||||
|
i.xHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZdwlIpcOutputV2YEvent : Update the y coordinates
|
||||||
|
//
|
||||||
|
// Indicates if y coordinates of the selected client.
|
||||||
|
type ZdwlIpcOutputV2YEvent struct {
|
||||||
|
Y int32
|
||||||
|
}
|
||||||
|
type ZdwlIpcOutputV2YHandlerFunc func(ZdwlIpcOutputV2YEvent)
|
||||||
|
|
||||||
|
// SetYHandler : sets handler for ZdwlIpcOutputV2YEvent
|
||||||
|
func (i *ZdwlIpcOutputV2) SetYHandler(f ZdwlIpcOutputV2YHandlerFunc) {
|
||||||
|
i.yHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZdwlIpcOutputV2WidthEvent : Update the width
|
||||||
|
//
|
||||||
|
// Indicates if width of the selected client.
|
||||||
|
type ZdwlIpcOutputV2WidthEvent struct {
|
||||||
|
Width int32
|
||||||
|
}
|
||||||
|
type ZdwlIpcOutputV2WidthHandlerFunc func(ZdwlIpcOutputV2WidthEvent)
|
||||||
|
|
||||||
|
// SetWidthHandler : sets handler for ZdwlIpcOutputV2WidthEvent
|
||||||
|
func (i *ZdwlIpcOutputV2) SetWidthHandler(f ZdwlIpcOutputV2WidthHandlerFunc) {
|
||||||
|
i.widthHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZdwlIpcOutputV2HeightEvent : Update the height
|
||||||
|
//
|
||||||
|
// Indicates if height of the selected client.
|
||||||
|
type ZdwlIpcOutputV2HeightEvent struct {
|
||||||
|
Height int32
|
||||||
|
}
|
||||||
|
type ZdwlIpcOutputV2HeightHandlerFunc func(ZdwlIpcOutputV2HeightEvent)
|
||||||
|
|
||||||
|
// SetHeightHandler : sets handler for ZdwlIpcOutputV2HeightEvent
|
||||||
|
func (i *ZdwlIpcOutputV2) SetHeightHandler(f ZdwlIpcOutputV2HeightHandlerFunc) {
|
||||||
|
i.heightHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZdwlIpcOutputV2LastLayerEvent : last map layer.
|
||||||
|
//
|
||||||
|
// last map layer.
|
||||||
|
type ZdwlIpcOutputV2LastLayerEvent struct {
|
||||||
|
LastLayer string
|
||||||
|
}
|
||||||
|
type ZdwlIpcOutputV2LastLayerHandlerFunc func(ZdwlIpcOutputV2LastLayerEvent)
|
||||||
|
|
||||||
|
// SetLastLayerHandler : sets handler for ZdwlIpcOutputV2LastLayerEvent
|
||||||
|
func (i *ZdwlIpcOutputV2) SetLastLayerHandler(f ZdwlIpcOutputV2LastLayerHandlerFunc) {
|
||||||
|
i.lastLayerHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZdwlIpcOutputV2KbLayoutEvent : current keyboard layout.
|
||||||
|
//
|
||||||
|
// current keyboard layout.
|
||||||
|
type ZdwlIpcOutputV2KbLayoutEvent struct {
|
||||||
|
KbLayout string
|
||||||
|
}
|
||||||
|
type ZdwlIpcOutputV2KbLayoutHandlerFunc func(ZdwlIpcOutputV2KbLayoutEvent)
|
||||||
|
|
||||||
|
// SetKbLayoutHandler : sets handler for ZdwlIpcOutputV2KbLayoutEvent
|
||||||
|
func (i *ZdwlIpcOutputV2) SetKbLayoutHandler(f ZdwlIpcOutputV2KbLayoutHandlerFunc) {
|
||||||
|
i.kbLayoutHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZdwlIpcOutputV2KeymodeEvent : current keybind mode.
|
||||||
|
//
|
||||||
|
// current keybind mode.
|
||||||
|
type ZdwlIpcOutputV2KeymodeEvent struct {
|
||||||
|
Keymode string
|
||||||
|
}
|
||||||
|
type ZdwlIpcOutputV2KeymodeHandlerFunc func(ZdwlIpcOutputV2KeymodeEvent)
|
||||||
|
|
||||||
|
// SetKeymodeHandler : sets handler for ZdwlIpcOutputV2KeymodeEvent
|
||||||
|
func (i *ZdwlIpcOutputV2) SetKeymodeHandler(f ZdwlIpcOutputV2KeymodeHandlerFunc) {
|
||||||
|
i.keymodeHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZdwlIpcOutputV2ScalefactorEvent : scale factor of monitor.
|
||||||
|
//
|
||||||
|
// scale factor of monitor.
|
||||||
|
type ZdwlIpcOutputV2ScalefactorEvent struct {
|
||||||
|
Scalefactor uint32
|
||||||
|
}
|
||||||
|
type ZdwlIpcOutputV2ScalefactorHandlerFunc func(ZdwlIpcOutputV2ScalefactorEvent)
|
||||||
|
|
||||||
|
// SetScalefactorHandler : sets handler for ZdwlIpcOutputV2ScalefactorEvent
|
||||||
|
func (i *ZdwlIpcOutputV2) SetScalefactorHandler(f ZdwlIpcOutputV2ScalefactorHandlerFunc) {
|
||||||
|
i.scalefactorHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
func (i *ZdwlIpcOutputV2) Dispatch(opcode uint32, fd int, data []byte) {
|
func (i *ZdwlIpcOutputV2) Dispatch(opcode uint32, fd int, data []byte) {
|
||||||
switch opcode {
|
switch opcode {
|
||||||
case 0:
|
case 0:
|
||||||
@@ -487,5 +681,111 @@ func (i *ZdwlIpcOutputV2) Dispatch(opcode uint32, fd int, data []byte) {
|
|||||||
var e ZdwlIpcOutputV2FrameEvent
|
var e ZdwlIpcOutputV2FrameEvent
|
||||||
|
|
||||||
i.frameHandler(e)
|
i.frameHandler(e)
|
||||||
|
case 8:
|
||||||
|
if i.fullscreenHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZdwlIpcOutputV2FullscreenEvent
|
||||||
|
l := 0
|
||||||
|
e.IsFullscreen = client.Uint32(data[l : l+4])
|
||||||
|
l += 4
|
||||||
|
|
||||||
|
i.fullscreenHandler(e)
|
||||||
|
case 9:
|
||||||
|
if i.floatingHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZdwlIpcOutputV2FloatingEvent
|
||||||
|
l := 0
|
||||||
|
e.IsFloating = client.Uint32(data[l : l+4])
|
||||||
|
l += 4
|
||||||
|
|
||||||
|
i.floatingHandler(e)
|
||||||
|
case 10:
|
||||||
|
if i.xHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZdwlIpcOutputV2XEvent
|
||||||
|
l := 0
|
||||||
|
e.X = int32(client.Uint32(data[l : l+4]))
|
||||||
|
l += 4
|
||||||
|
|
||||||
|
i.xHandler(e)
|
||||||
|
case 11:
|
||||||
|
if i.yHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZdwlIpcOutputV2YEvent
|
||||||
|
l := 0
|
||||||
|
e.Y = int32(client.Uint32(data[l : l+4]))
|
||||||
|
l += 4
|
||||||
|
|
||||||
|
i.yHandler(e)
|
||||||
|
case 12:
|
||||||
|
if i.widthHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZdwlIpcOutputV2WidthEvent
|
||||||
|
l := 0
|
||||||
|
e.Width = int32(client.Uint32(data[l : l+4]))
|
||||||
|
l += 4
|
||||||
|
|
||||||
|
i.widthHandler(e)
|
||||||
|
case 13:
|
||||||
|
if i.heightHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZdwlIpcOutputV2HeightEvent
|
||||||
|
l := 0
|
||||||
|
e.Height = int32(client.Uint32(data[l : l+4]))
|
||||||
|
l += 4
|
||||||
|
|
||||||
|
i.heightHandler(e)
|
||||||
|
case 14:
|
||||||
|
if i.lastLayerHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZdwlIpcOutputV2LastLayerEvent
|
||||||
|
l := 0
|
||||||
|
lastLayerLen := client.PaddedLen(int(client.Uint32(data[l : l+4])))
|
||||||
|
l += 4
|
||||||
|
e.LastLayer = client.String(data[l : l+lastLayerLen])
|
||||||
|
l += lastLayerLen
|
||||||
|
|
||||||
|
i.lastLayerHandler(e)
|
||||||
|
case 15:
|
||||||
|
if i.kbLayoutHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZdwlIpcOutputV2KbLayoutEvent
|
||||||
|
l := 0
|
||||||
|
kbLayoutLen := client.PaddedLen(int(client.Uint32(data[l : l+4])))
|
||||||
|
l += 4
|
||||||
|
e.KbLayout = client.String(data[l : l+kbLayoutLen])
|
||||||
|
l += kbLayoutLen
|
||||||
|
|
||||||
|
i.kbLayoutHandler(e)
|
||||||
|
case 16:
|
||||||
|
if i.keymodeHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZdwlIpcOutputV2KeymodeEvent
|
||||||
|
l := 0
|
||||||
|
keymodeLen := client.PaddedLen(int(client.Uint32(data[l : l+4])))
|
||||||
|
l += 4
|
||||||
|
e.Keymode = client.String(data[l : l+keymodeLen])
|
||||||
|
l += keymodeLen
|
||||||
|
|
||||||
|
i.keymodeHandler(e)
|
||||||
|
case 17:
|
||||||
|
if i.scalefactorHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZdwlIpcOutputV2ScalefactorEvent
|
||||||
|
l := 0
|
||||||
|
e.Scalefactor = client.Uint32(data[l : l+4])
|
||||||
|
l += 4
|
||||||
|
|
||||||
|
i.scalefactorHandler(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Generated by go-wayland-scanner
|
// Generated by go-wayland-scanner
|
||||||
// https://github.com/yaslama/go-wayland/cmd/go-wayland-scanner
|
// https://github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/cmd/go-wayland-scanner
|
||||||
// XML file : ext-workspace-v1.xml
|
// XML file : ext-workspace-v1.xml
|
||||||
//
|
//
|
||||||
// ext_workspace_v1 Protocol Copyright:
|
// ext_workspace_v1 Protocol Copyright:
|
||||||
@@ -35,7 +35,8 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/yaslama/go-wayland/wayland/client"
|
"github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
|
||||||
)
|
)
|
||||||
|
|
||||||
// registerServerProxy registers a proxy with a server-assigned ID.
|
// registerServerProxy registers a proxy with a server-assigned ID.
|
||||||
@@ -61,8 +62,9 @@ func registerServerProxy(ctx *client.Context, proxy client.Proxy, serverID uint3
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
objectsMap := reflect.NewAt(objectsField.Type(), unsafe.Pointer(objectsField.UnsafeAddr())).Elem()
|
objectsMapPtr := unsafe.Pointer(objectsField.UnsafeAddr())
|
||||||
objectsMap.SetMapIndex(reflect.ValueOf(serverID), reflect.ValueOf(proxy))
|
objectsMap := (*syncmap.Map[uint32, client.Proxy])(objectsMapPtr)
|
||||||
|
objectsMap.Store(serverID, proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtWorkspaceManagerV1InterfaceName is the name of the interface as it appears in the [client.Registry].
|
// ExtWorkspaceManagerV1InterfaceName is the name of the interface as it appears in the [client.Registry].
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Generated by go-wayland-scanner
|
// Generated by go-wayland-scanner
|
||||||
// https://github.com/yaslama/go-wayland/cmd/go-wayland-scanner
|
// https://github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/cmd/go-wayland-scanner
|
||||||
// XML file : wayland-protocols/wlr-gamma-control-unstable-v1.xml
|
// XML file : wayland-protocols/wlr-gamma-control-unstable-v1.xml
|
||||||
//
|
//
|
||||||
// wlr_gamma_control_unstable_v1 Protocol Copyright:
|
// wlr_gamma_control_unstable_v1 Protocol Copyright:
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
package wlr_gamma_control
|
package wlr_gamma_control
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/yaslama/go-wayland/wayland/client"
|
"github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Generated by go-wayland-scanner
|
// Generated by go-wayland-scanner
|
||||||
// https://github.com/yaslama/go-wayland/cmd/go-wayland-scanner
|
// https://github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/cmd/go-wayland-scanner
|
||||||
// XML file : /home/brandon/repos/dankdots/wlr-output-management-unstable-v1.xml
|
// XML file : /home/brandon/repos/dankdots/wlr-output-management-unstable-v1.xml
|
||||||
//
|
//
|
||||||
// wlr_output_management_unstable_v1 Protocol Copyright:
|
// wlr_output_management_unstable_v1 Protocol Copyright:
|
||||||
@@ -33,7 +33,8 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/yaslama/go-wayland/wayland/client"
|
"github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func registerServerProxy(ctx *client.Context, proxy client.Proxy, serverID uint32) {
|
func registerServerProxy(ctx *client.Context, proxy client.Proxy, serverID uint32) {
|
||||||
@@ -47,9 +48,9 @@ func registerServerProxy(ctx *client.Context, proxy client.Proxy, serverID uint3
|
|||||||
if !objectsField.IsValid() {
|
if !objectsField.IsValid() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
objectsField = reflect.NewAt(objectsField.Type(), unsafe.Pointer(objectsField.UnsafeAddr())).Elem()
|
objectsMapPtr := unsafe.Pointer(objectsField.UnsafeAddr())
|
||||||
objectsMap := objectsField.Interface().(map[uint32]client.Proxy)
|
objectsMap := (*syncmap.Map[uint32, client.Proxy])(objectsMapPtr)
|
||||||
objectsMap[serverID] = proxy
|
objectsMap.Store(serverID, proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ZwlrOutputManagerV1InterfaceName is the name of the interface as it appears in the [client.Registry].
|
// ZwlrOutputManagerV1InterfaceName is the name of the interface as it appears in the [client.Registry].
|
||||||
|
|||||||
283
core/internal/proto/wlr_output_power/output_power.go
Normal file
283
core/internal/proto/wlr_output_power/output_power.go
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
// Generated by go-wayland-scanner
|
||||||
|
// https://github.com/yaslama/go-wayland/cmd/go-wayland-scanner
|
||||||
|
// XML file : internal/proto/xml/wlr-output-power-management-unstable-v1.xml
|
||||||
|
//
|
||||||
|
// wlr_output_power_management_unstable_v1 Protocol Copyright:
|
||||||
|
//
|
||||||
|
// Copyright © 2019 Purism SPC
|
||||||
|
//
|
||||||
|
// 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 (including the next
|
||||||
|
// paragraph) 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.
|
||||||
|
|
||||||
|
package wlr_output_power
|
||||||
|
|
||||||
|
import "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
||||||
|
|
||||||
|
// ZwlrOutputPowerManagerV1InterfaceName is the name of the interface as it appears in the [client.Registry].
|
||||||
|
// It can be used to match the [client.RegistryGlobalEvent.Interface] in the
|
||||||
|
// [Registry.SetGlobalHandler] and can be used in [Registry.Bind] if this applies.
|
||||||
|
const ZwlrOutputPowerManagerV1InterfaceName = "zwlr_output_power_manager_v1"
|
||||||
|
|
||||||
|
// ZwlrOutputPowerManagerV1 : manager to create per-output power management
|
||||||
|
//
|
||||||
|
// This interface is a manager that allows creating per-output power
|
||||||
|
// management mode controls.
|
||||||
|
type ZwlrOutputPowerManagerV1 struct {
|
||||||
|
client.BaseProxy
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewZwlrOutputPowerManagerV1 : manager to create per-output power management
|
||||||
|
//
|
||||||
|
// This interface is a manager that allows creating per-output power
|
||||||
|
// management mode controls.
|
||||||
|
func NewZwlrOutputPowerManagerV1(ctx *client.Context) *ZwlrOutputPowerManagerV1 {
|
||||||
|
zwlrOutputPowerManagerV1 := &ZwlrOutputPowerManagerV1{}
|
||||||
|
ctx.Register(zwlrOutputPowerManagerV1)
|
||||||
|
return zwlrOutputPowerManagerV1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOutputPower : get a power management for an output
|
||||||
|
//
|
||||||
|
// Create an output power management mode control that can be used to
|
||||||
|
// adjust the power management mode for a given output.
|
||||||
|
func (i *ZwlrOutputPowerManagerV1) GetOutputPower(output *client.Output) (*ZwlrOutputPowerV1, error) {
|
||||||
|
id := NewZwlrOutputPowerV1(i.Context())
|
||||||
|
const opcode = 0
|
||||||
|
const _reqBufLen = 8 + 4 + 4
|
||||||
|
var _reqBuf [_reqBufLen]byte
|
||||||
|
l := 0
|
||||||
|
client.PutUint32(_reqBuf[l:4], i.ID())
|
||||||
|
l += 4
|
||||||
|
client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff))
|
||||||
|
l += 4
|
||||||
|
client.PutUint32(_reqBuf[l:l+4], id.ID())
|
||||||
|
l += 4
|
||||||
|
client.PutUint32(_reqBuf[l:l+4], output.ID())
|
||||||
|
l += 4
|
||||||
|
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy : destroy the manager
|
||||||
|
//
|
||||||
|
// All objects created by the manager will still remain valid, until their
|
||||||
|
// appropriate destroy request has been called.
|
||||||
|
func (i *ZwlrOutputPowerManagerV1) Destroy() error {
|
||||||
|
defer i.Context().Unregister(i)
|
||||||
|
const opcode = 1
|
||||||
|
const _reqBufLen = 8
|
||||||
|
var _reqBuf [_reqBufLen]byte
|
||||||
|
l := 0
|
||||||
|
client.PutUint32(_reqBuf[l:4], i.ID())
|
||||||
|
l += 4
|
||||||
|
client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff))
|
||||||
|
l += 4
|
||||||
|
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZwlrOutputPowerV1InterfaceName is the name of the interface as it appears in the [client.Registry].
|
||||||
|
// It can be used to match the [client.RegistryGlobalEvent.Interface] in the
|
||||||
|
// [Registry.SetGlobalHandler] and can be used in [Registry.Bind] if this applies.
|
||||||
|
const ZwlrOutputPowerV1InterfaceName = "zwlr_output_power_v1"
|
||||||
|
|
||||||
|
// ZwlrOutputPowerV1 : adjust power management mode for an output
|
||||||
|
//
|
||||||
|
// This object offers requests to set the power management mode of
|
||||||
|
// an output.
|
||||||
|
type ZwlrOutputPowerV1 struct {
|
||||||
|
client.BaseProxy
|
||||||
|
modeHandler ZwlrOutputPowerV1ModeHandlerFunc
|
||||||
|
failedHandler ZwlrOutputPowerV1FailedHandlerFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewZwlrOutputPowerV1 : adjust power management mode for an output
|
||||||
|
//
|
||||||
|
// This object offers requests to set the power management mode of
|
||||||
|
// an output.
|
||||||
|
func NewZwlrOutputPowerV1(ctx *client.Context) *ZwlrOutputPowerV1 {
|
||||||
|
zwlrOutputPowerV1 := &ZwlrOutputPowerV1{}
|
||||||
|
ctx.Register(zwlrOutputPowerV1)
|
||||||
|
return zwlrOutputPowerV1
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMode : Set an outputs power save mode
|
||||||
|
//
|
||||||
|
// Set an output's power save mode to the given mode. The mode change
|
||||||
|
// is effective immediately. If the output does not support the given
|
||||||
|
// mode a failed event is sent.
|
||||||
|
//
|
||||||
|
// mode: the power save mode to set
|
||||||
|
func (i *ZwlrOutputPowerV1) SetMode(mode uint32) error {
|
||||||
|
const opcode = 0
|
||||||
|
const _reqBufLen = 8 + 4
|
||||||
|
var _reqBuf [_reqBufLen]byte
|
||||||
|
l := 0
|
||||||
|
client.PutUint32(_reqBuf[l:4], i.ID())
|
||||||
|
l += 4
|
||||||
|
client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff))
|
||||||
|
l += 4
|
||||||
|
client.PutUint32(_reqBuf[l:l+4], uint32(mode))
|
||||||
|
l += 4
|
||||||
|
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy : destroy this power management
|
||||||
|
//
|
||||||
|
// Destroys the output power management mode control object.
|
||||||
|
func (i *ZwlrOutputPowerV1) Destroy() error {
|
||||||
|
defer i.Context().Unregister(i)
|
||||||
|
const opcode = 1
|
||||||
|
const _reqBufLen = 8
|
||||||
|
var _reqBuf [_reqBufLen]byte
|
||||||
|
l := 0
|
||||||
|
client.PutUint32(_reqBuf[l:4], i.ID())
|
||||||
|
l += 4
|
||||||
|
client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff))
|
||||||
|
l += 4
|
||||||
|
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type ZwlrOutputPowerV1Mode uint32
|
||||||
|
|
||||||
|
// ZwlrOutputPowerV1Mode :
|
||||||
|
const (
|
||||||
|
// ZwlrOutputPowerV1ModeOff : Output is turned off.
|
||||||
|
ZwlrOutputPowerV1ModeOff ZwlrOutputPowerV1Mode = 0
|
||||||
|
// ZwlrOutputPowerV1ModeOn : Output is turned on, no power saving
|
||||||
|
ZwlrOutputPowerV1ModeOn ZwlrOutputPowerV1Mode = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e ZwlrOutputPowerV1Mode) Name() string {
|
||||||
|
switch e {
|
||||||
|
case ZwlrOutputPowerV1ModeOff:
|
||||||
|
return "off"
|
||||||
|
case ZwlrOutputPowerV1ModeOn:
|
||||||
|
return "on"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ZwlrOutputPowerV1Mode) Value() string {
|
||||||
|
switch e {
|
||||||
|
case ZwlrOutputPowerV1ModeOff:
|
||||||
|
return "0"
|
||||||
|
case ZwlrOutputPowerV1ModeOn:
|
||||||
|
return "1"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ZwlrOutputPowerV1Mode) String() string {
|
||||||
|
return e.Name() + "=" + e.Value()
|
||||||
|
}
|
||||||
|
|
||||||
|
type ZwlrOutputPowerV1Error uint32
|
||||||
|
|
||||||
|
// ZwlrOutputPowerV1Error :
|
||||||
|
const (
|
||||||
|
// ZwlrOutputPowerV1ErrorInvalidMode : nonexistent power save mode
|
||||||
|
ZwlrOutputPowerV1ErrorInvalidMode ZwlrOutputPowerV1Error = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e ZwlrOutputPowerV1Error) Name() string {
|
||||||
|
switch e {
|
||||||
|
case ZwlrOutputPowerV1ErrorInvalidMode:
|
||||||
|
return "invalid_mode"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ZwlrOutputPowerV1Error) Value() string {
|
||||||
|
switch e {
|
||||||
|
case ZwlrOutputPowerV1ErrorInvalidMode:
|
||||||
|
return "1"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ZwlrOutputPowerV1Error) String() string {
|
||||||
|
return e.Name() + "=" + e.Value()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZwlrOutputPowerV1ModeEvent : Report a power management mode change
|
||||||
|
//
|
||||||
|
// Report the power management mode change of an output.
|
||||||
|
//
|
||||||
|
// The mode event is sent after an output changed its power
|
||||||
|
// management mode. The reason can be a client using set_mode or the
|
||||||
|
// compositor deciding to change an output's mode.
|
||||||
|
// This event is also sent immediately when the object is created
|
||||||
|
// so the client is informed about the current power management mode.
|
||||||
|
type ZwlrOutputPowerV1ModeEvent struct {
|
||||||
|
Mode uint32
|
||||||
|
}
|
||||||
|
type ZwlrOutputPowerV1ModeHandlerFunc func(ZwlrOutputPowerV1ModeEvent)
|
||||||
|
|
||||||
|
// SetModeHandler : sets handler for ZwlrOutputPowerV1ModeEvent
|
||||||
|
func (i *ZwlrOutputPowerV1) SetModeHandler(f ZwlrOutputPowerV1ModeHandlerFunc) {
|
||||||
|
i.modeHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZwlrOutputPowerV1FailedEvent : object no longer valid
|
||||||
|
//
|
||||||
|
// This event indicates that the output power management mode control
|
||||||
|
// is no longer valid. This can happen for a number of reasons,
|
||||||
|
// including:
|
||||||
|
// - The output doesn't support power management
|
||||||
|
// - Another client already has exclusive power management mode control
|
||||||
|
// for this output
|
||||||
|
// - The output disappeared
|
||||||
|
//
|
||||||
|
// Upon receiving this event, the client should destroy this object.
|
||||||
|
type ZwlrOutputPowerV1FailedEvent struct{}
|
||||||
|
type ZwlrOutputPowerV1FailedHandlerFunc func(ZwlrOutputPowerV1FailedEvent)
|
||||||
|
|
||||||
|
// SetFailedHandler : sets handler for ZwlrOutputPowerV1FailedEvent
|
||||||
|
func (i *ZwlrOutputPowerV1) SetFailedHandler(f ZwlrOutputPowerV1FailedHandlerFunc) {
|
||||||
|
i.failedHandler = f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ZwlrOutputPowerV1) Dispatch(opcode uint32, fd int, data []byte) {
|
||||||
|
switch opcode {
|
||||||
|
case 0:
|
||||||
|
if i.modeHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZwlrOutputPowerV1ModeEvent
|
||||||
|
l := 0
|
||||||
|
e.Mode = client.Uint32(data[l : l+4])
|
||||||
|
l += 4
|
||||||
|
|
||||||
|
i.modeHandler(e)
|
||||||
|
case 1:
|
||||||
|
if i.failedHandler == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var e ZwlrOutputPowerV1FailedEvent
|
||||||
|
|
||||||
|
i.failedHandler(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ I would probably just submit raphi's patchset but I don't think that would be po
|
|||||||
reset.
|
reset.
|
||||||
</description>
|
</description>
|
||||||
|
|
||||||
<interface name="zdwl_ipc_manager_v2" version="1">
|
<interface name="zdwl_ipc_manager_v2" version="2">
|
||||||
<description summary="manage dwl state">
|
<description summary="manage dwl state">
|
||||||
This interface is exposed as a global in wl_registry.
|
This interface is exposed as a global in wl_registry.
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ I would probably just submit raphi's patchset but I don't think that would be po
|
|||||||
</event>
|
</event>
|
||||||
</interface>
|
</interface>
|
||||||
|
|
||||||
<interface name="zdwl_ipc_output_v2" version="1">
|
<interface name="zdwl_ipc_output_v2" version="2">
|
||||||
<description summary="control dwl output">
|
<description summary="control dwl output">
|
||||||
Observe and control a dwl output.
|
Observe and control a dwl output.
|
||||||
|
|
||||||
@@ -162,5 +162,91 @@ I would probably just submit raphi's patchset but I don't think that would be po
|
|||||||
<description summary="Set the layout of this output"/>
|
<description summary="Set the layout of this output"/>
|
||||||
<arg name="index" type="uint" summary="index of a layout recieved by dwl_ipc_manager.layout"/>
|
<arg name="index" type="uint" summary="index of a layout recieved by dwl_ipc_manager.layout"/>
|
||||||
</request>
|
</request>
|
||||||
|
|
||||||
|
<request name="quit" since="2">
|
||||||
|
<description summary="Quit mango">This request allows clients to instruct the compositor to quit mango.</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="dispatch" since="2">
|
||||||
|
<description summary="Set the active tags of this output"/>
|
||||||
|
<arg name="dispatch" type="string" summary="dispatch name."/>
|
||||||
|
<arg name="arg1" type="string" summary="arg1."/>
|
||||||
|
<arg name="arg2" type="string" summary="arg2."/>
|
||||||
|
<arg name="arg3" type="string" summary="arg3."/>
|
||||||
|
<arg name="arg4" type="string" summary="arg4."/>
|
||||||
|
<arg name="arg5" type="string" summary="arg5."/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<!-- Version 2 -->
|
||||||
|
<event name="fullscreen" since="2">
|
||||||
|
<description summary="Update fullscreen status">
|
||||||
|
Indicates if the selected client on this output is fullscreen.
|
||||||
|
</description>
|
||||||
|
<arg name="is_fullscreen" type="uint" summary="If the selected client is fullscreen. Nonzero is valid, zero invalid"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="floating" since="2">
|
||||||
|
<description summary="Update the floating status">
|
||||||
|
Indicates if the selected client on this output is floating.
|
||||||
|
</description>
|
||||||
|
<arg name="is_floating" type="uint" summary="If the selected client is floating. Nonzero is valid, zero invalid"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="x" since="2">
|
||||||
|
<description summary="Update the x coordinates">
|
||||||
|
Indicates if x coordinates of the selected client.
|
||||||
|
</description>
|
||||||
|
<arg name="x" type="int" summary="x coordinate of the selected client"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="y" since="2">
|
||||||
|
<description summary="Update the y coordinates">
|
||||||
|
Indicates if y coordinates of the selected client.
|
||||||
|
</description>
|
||||||
|
<arg name="y" type="int" summary="y coordinate of the selected client"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="width" since="2">
|
||||||
|
<description summary="Update the width">
|
||||||
|
Indicates if width of the selected client.
|
||||||
|
</description>
|
||||||
|
<arg name="width" type="int" summary="width of the selected client"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="height" since="2">
|
||||||
|
<description summary="Update the height">
|
||||||
|
Indicates if height of the selected client.
|
||||||
|
</description>
|
||||||
|
<arg name="height" type="int" summary="height of the selected client"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="last_layer" since="2">
|
||||||
|
<description summary="last map layer.">
|
||||||
|
last map layer.
|
||||||
|
</description>
|
||||||
|
<arg name="last_layer" type="string" summary="last map layer."/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="kb_layout" since="2">
|
||||||
|
<description summary="current keyboard layout.">
|
||||||
|
current keyboard layout.
|
||||||
|
</description>
|
||||||
|
<arg name="kb_layout" type="string" summary="current keyboard layout."/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="keymode" since="2">
|
||||||
|
<description summary="current keybind mode.">
|
||||||
|
current keybind mode.
|
||||||
|
</description>
|
||||||
|
<arg name="keymode" type="string" summary="current keybind mode."/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="scalefactor" since="2">
|
||||||
|
<description summary="scale factor of monitor.">
|
||||||
|
scale factor of monitor.
|
||||||
|
</description>
|
||||||
|
<arg name="scalefactor" type="uint" summary="scale factor of monitor."/>
|
||||||
|
</event>
|
||||||
|
|
||||||
</interface>
|
</interface>
|
||||||
</protocol>
|
</protocol>
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<protocol name="wlr_output_power_management_unstable_v1">
|
||||||
|
<copyright>
|
||||||
|
Copyright © 2019 Purism SPC
|
||||||
|
|
||||||
|
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 (including the next
|
||||||
|
paragraph) 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.
|
||||||
|
</copyright>
|
||||||
|
|
||||||
|
<description summary="Control power management modes of outputs">
|
||||||
|
This protocol allows clients to control power management modes
|
||||||
|
of outputs that are currently part of the compositor space. The
|
||||||
|
intent is to allow special clients like desktop shells to power
|
||||||
|
down outputs when the system is idle.
|
||||||
|
|
||||||
|
To modify outputs not currently part of the compositor space see
|
||||||
|
wlr-output-management.
|
||||||
|
|
||||||
|
Warning! The protocol described in this file is experimental and
|
||||||
|
backward incompatible changes may be made. Backward compatible changes
|
||||||
|
may be added together with the corresponding interface version bump.
|
||||||
|
Backward incompatible changes are done by bumping the version number in
|
||||||
|
the protocol and interface names and resetting the interface version.
|
||||||
|
Once the protocol is to be declared stable, the 'z' prefix and the
|
||||||
|
version number in the protocol and interface names are removed and the
|
||||||
|
interface version number is reset.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<interface name="zwlr_output_power_manager_v1" version="1">
|
||||||
|
<description summary="manager to create per-output power management">
|
||||||
|
This interface is a manager that allows creating per-output power
|
||||||
|
management mode controls.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<request name="get_output_power">
|
||||||
|
<description summary="get a power management for an output">
|
||||||
|
Create an output power management mode control that can be used to
|
||||||
|
adjust the power management mode for a given output.
|
||||||
|
</description>
|
||||||
|
<arg name="id" type="new_id" interface="zwlr_output_power_v1"/>
|
||||||
|
<arg name="output" type="object" interface="wl_output"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="destroy" type="destructor">
|
||||||
|
<description summary="destroy the manager">
|
||||||
|
All objects created by the manager will still remain valid, until their
|
||||||
|
appropriate destroy request has been called.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
</interface>
|
||||||
|
|
||||||
|
<interface name="zwlr_output_power_v1" version="1">
|
||||||
|
<description summary="adjust power management mode for an output">
|
||||||
|
This object offers requests to set the power management mode of
|
||||||
|
an output.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<enum name="mode">
|
||||||
|
<entry name="off" value="0"
|
||||||
|
summary="Output is turned off."/>
|
||||||
|
<entry name="on" value="1"
|
||||||
|
summary="Output is turned on, no power saving"/>
|
||||||
|
</enum>
|
||||||
|
|
||||||
|
<enum name="error">
|
||||||
|
<entry name="invalid_mode" value="1" summary="nonexistent power save mode"/>
|
||||||
|
</enum>
|
||||||
|
|
||||||
|
<request name="set_mode">
|
||||||
|
<description summary="Set an outputs power save mode">
|
||||||
|
Set an output's power save mode to the given mode. The mode change
|
||||||
|
is effective immediately. If the output does not support the given
|
||||||
|
mode a failed event is sent.
|
||||||
|
</description>
|
||||||
|
<arg name="mode" type="uint" enum="mode" summary="the power save mode to set"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<event name="mode">
|
||||||
|
<description summary="Report a power management mode change">
|
||||||
|
Report the power management mode change of an output.
|
||||||
|
|
||||||
|
The mode event is sent after an output changed its power
|
||||||
|
management mode. The reason can be a client using set_mode or the
|
||||||
|
compositor deciding to change an output's mode.
|
||||||
|
This event is also sent immediately when the object is created
|
||||||
|
so the client is informed about the current power management mode.
|
||||||
|
</description>
|
||||||
|
<arg name="mode" type="uint" enum="mode"
|
||||||
|
summary="the output's new power management mode"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="failed">
|
||||||
|
<description summary="object no longer valid">
|
||||||
|
This event indicates that the output power management mode control
|
||||||
|
is no longer valid. This can happen for a number of reasons,
|
||||||
|
including:
|
||||||
|
- The output doesn't support power management
|
||||||
|
- Another client already has exclusive power management mode control
|
||||||
|
for this output
|
||||||
|
- The output disappeared
|
||||||
|
|
||||||
|
Upon receiving this event, the client should destroy this object.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<request name="destroy" type="destructor">
|
||||||
|
<description summary="destroy this power management">
|
||||||
|
Destroys the output power management mode control object.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
</interface>
|
||||||
|
</protocol>
|
||||||
@@ -165,12 +165,11 @@ func (a *BluezAgent) DisplayPasskey(device dbus.ObjectPath, passkey uint32, ente
|
|||||||
log.Infof("[BluezAgent] DisplayPasskey: device=%s, passkey=%06d, entered=%d", device, passkey, entered)
|
log.Infof("[BluezAgent] DisplayPasskey: device=%s, passkey=%06d, entered=%d", device, passkey, entered)
|
||||||
|
|
||||||
if entered == 0 {
|
if entered == 0 {
|
||||||
pk := passkey
|
passkeyStr := strconv.FormatUint(uint64(passkey), 10)
|
||||||
_, err := a.promptFor(device, "display-passkey", []string{}, nil)
|
_, err := a.promptFor(device, "display-passkey", []string{}, &passkeyStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("[BluezAgent] DisplayPasskey acknowledgment failed: %v", err)
|
log.Warnf("[BluezAgent] DisplayPasskey acknowledgment failed: %v", err)
|
||||||
}
|
}
|
||||||
_ = pk
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -179,7 +178,8 @@ func (a *BluezAgent) DisplayPasskey(device dbus.ObjectPath, passkey uint32, ente
|
|||||||
func (a *BluezAgent) RequestConfirmation(device dbus.ObjectPath, passkey uint32) *dbus.Error {
|
func (a *BluezAgent) RequestConfirmation(device dbus.ObjectPath, passkey uint32) *dbus.Error {
|
||||||
log.Infof("[BluezAgent] RequestConfirmation: device=%s, passkey=%06d", device, passkey)
|
log.Infof("[BluezAgent] RequestConfirmation: device=%s, passkey=%06d", device, passkey)
|
||||||
|
|
||||||
secrets, err := a.promptFor(device, "confirm", []string{"decision"}, nil)
|
passkeyStr := strconv.FormatUint(uint64(passkey), 10)
|
||||||
|
secrets, err := a.promptFor(device, "confirm", []string{"decision"}, &passkeyStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("[BluezAgent] RequestConfirmation failed: %v", err)
|
log.Warnf("[BluezAgent] RequestConfirmation failed: %v", err)
|
||||||
return a.errorFrom(err)
|
return a.errorFrom(err)
|
||||||
|
|||||||
@@ -31,15 +31,11 @@ func NewManager() (*Manager, error) {
|
|||||||
ConnectedDevices: []Device{},
|
ConnectedDevices: []Device{},
|
||||||
},
|
},
|
||||||
stateMutex: sync.RWMutex{},
|
stateMutex: sync.RWMutex{},
|
||||||
subscribers: make(map[string]chan BluetoothState),
|
|
||||||
subMutex: sync.RWMutex{},
|
|
||||||
stopChan: make(chan struct{}),
|
stopChan: make(chan struct{}),
|
||||||
dbusConn: conn,
|
dbusConn: conn,
|
||||||
signals: make(chan *dbus.Signal, 256),
|
signals: make(chan *dbus.Signal, 256),
|
||||||
pairingSubscribers: make(map[string]chan PairingPrompt),
|
|
||||||
pairingSubMutex: sync.RWMutex{},
|
|
||||||
dirty: make(chan struct{}, 1),
|
dirty: make(chan struct{}, 1),
|
||||||
pendingPairings: make(map[string]bool),
|
|
||||||
eventQueue: make(chan func(), 32),
|
eventQueue: make(chan func(), 32),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,14 +354,10 @@ func (m *Manager) handleDevicePropertiesChanged(path dbus.ObjectPath, changed ma
|
|||||||
_, hasTrusted := changed["Trusted"]
|
_, hasTrusted := changed["Trusted"]
|
||||||
|
|
||||||
if hasPaired {
|
if hasPaired {
|
||||||
if paired, ok := pairedVar.Value().(bool); ok && paired {
|
|
||||||
devicePath := string(path)
|
devicePath := string(path)
|
||||||
m.pendingPairingsMux.Lock()
|
if paired, ok := pairedVar.Value().(bool); ok {
|
||||||
wasPending := m.pendingPairings[devicePath]
|
if paired {
|
||||||
if wasPending {
|
_, wasPending := m.pendingPairings.LoadAndDelete(devicePath)
|
||||||
delete(m.pendingPairings, devicePath)
|
|
||||||
}
|
|
||||||
m.pendingPairingsMux.Unlock()
|
|
||||||
|
|
||||||
if wasPending {
|
if wasPending {
|
||||||
select {
|
select {
|
||||||
@@ -379,6 +371,9 @@ func (m *Manager) handleDevicePropertiesChanged(path dbus.ObjectPath, changed ma
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
m.pendingPairings.Delete(devicePath)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,28 +425,20 @@ func (m *Manager) notifier() {
|
|||||||
}
|
}
|
||||||
m.updateDevices()
|
m.updateDevices()
|
||||||
|
|
||||||
m.subMutex.RLock()
|
|
||||||
if len(m.subscribers) == 0 {
|
|
||||||
m.subMutex.RUnlock()
|
|
||||||
pending = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
currentState := m.snapshotState()
|
currentState := m.snapshotState()
|
||||||
|
|
||||||
if m.lastNotifiedState != nil && !stateChanged(m.lastNotifiedState, ¤tState) {
|
if m.lastNotifiedState != nil && !stateChanged(m.lastNotifiedState, ¤tState) {
|
||||||
m.subMutex.RUnlock()
|
|
||||||
pending = false
|
pending = false
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ch := range m.subscribers {
|
m.subscribers.Range(func(key string, ch chan BluetoothState) bool {
|
||||||
select {
|
select {
|
||||||
case ch <- currentState:
|
case ch <- currentState:
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
return true
|
||||||
m.subMutex.RUnlock()
|
})
|
||||||
|
|
||||||
stateCopy := currentState
|
stateCopy := currentState
|
||||||
m.lastNotifiedState = &stateCopy
|
m.lastNotifiedState = &stateCopy
|
||||||
@@ -484,48 +471,36 @@ func (m *Manager) snapshotState() BluetoothState {
|
|||||||
|
|
||||||
func (m *Manager) Subscribe(id string) chan BluetoothState {
|
func (m *Manager) Subscribe(id string) chan BluetoothState {
|
||||||
ch := make(chan BluetoothState, 64)
|
ch := make(chan BluetoothState, 64)
|
||||||
m.subMutex.Lock()
|
m.subscribers.Store(id, ch)
|
||||||
m.subscribers[id] = ch
|
|
||||||
m.subMutex.Unlock()
|
|
||||||
return ch
|
return ch
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Unsubscribe(id string) {
|
func (m *Manager) Unsubscribe(id string) {
|
||||||
m.subMutex.Lock()
|
if ch, ok := m.subscribers.LoadAndDelete(id); ok {
|
||||||
if ch, ok := m.subscribers[id]; ok {
|
|
||||||
close(ch)
|
close(ch)
|
||||||
delete(m.subscribers, id)
|
|
||||||
}
|
}
|
||||||
m.subMutex.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) SubscribePairing(id string) chan PairingPrompt {
|
func (m *Manager) SubscribePairing(id string) chan PairingPrompt {
|
||||||
ch := make(chan PairingPrompt, 16)
|
ch := make(chan PairingPrompt, 16)
|
||||||
m.pairingSubMutex.Lock()
|
m.pairingSubscribers.Store(id, ch)
|
||||||
m.pairingSubscribers[id] = ch
|
|
||||||
m.pairingSubMutex.Unlock()
|
|
||||||
return ch
|
return ch
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) UnsubscribePairing(id string) {
|
func (m *Manager) UnsubscribePairing(id string) {
|
||||||
m.pairingSubMutex.Lock()
|
if ch, ok := m.pairingSubscribers.LoadAndDelete(id); ok {
|
||||||
if ch, ok := m.pairingSubscribers[id]; ok {
|
|
||||||
close(ch)
|
close(ch)
|
||||||
delete(m.pairingSubscribers, id)
|
|
||||||
}
|
}
|
||||||
m.pairingSubMutex.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) broadcastPairingPrompt(prompt PairingPrompt) {
|
func (m *Manager) broadcastPairingPrompt(prompt PairingPrompt) {
|
||||||
m.pairingSubMutex.RLock()
|
m.pairingSubscribers.Range(func(key string, ch chan PairingPrompt) bool {
|
||||||
defer m.pairingSubMutex.RUnlock()
|
|
||||||
|
|
||||||
for _, ch := range m.pairingSubscribers {
|
|
||||||
select {
|
select {
|
||||||
case ch <- prompt:
|
case ch <- prompt:
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
return true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) SubmitPairing(token string, secrets map[string]string, accept bool) error {
|
func (m *Manager) SubmitPairing(token string, secrets map[string]string, accept bool) error {
|
||||||
@@ -566,17 +541,13 @@ func (m *Manager) SetPowered(powered bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) PairDevice(devicePath string) error {
|
func (m *Manager) PairDevice(devicePath string) error {
|
||||||
m.pendingPairingsMux.Lock()
|
m.pendingPairings.Store(devicePath, true)
|
||||||
m.pendingPairings[devicePath] = true
|
|
||||||
m.pendingPairingsMux.Unlock()
|
|
||||||
|
|
||||||
obj := m.dbusConn.Object(bluezService, dbus.ObjectPath(devicePath))
|
obj := m.dbusConn.Object(bluezService, dbus.ObjectPath(devicePath))
|
||||||
err := obj.Call(device1Iface+".Pair", 0).Err
|
err := obj.Call(device1Iface+".Pair", 0).Err
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.pendingPairingsMux.Lock()
|
m.pendingPairings.Delete(devicePath)
|
||||||
delete(m.pendingPairings, devicePath)
|
|
||||||
m.pendingPairingsMux.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
@@ -618,19 +589,17 @@ func (m *Manager) Close() {
|
|||||||
m.agent.Close()
|
m.agent.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
m.subMutex.Lock()
|
m.subscribers.Range(func(key string, ch chan BluetoothState) bool {
|
||||||
for _, ch := range m.subscribers {
|
|
||||||
close(ch)
|
close(ch)
|
||||||
}
|
m.subscribers.Delete(key)
|
||||||
m.subscribers = make(map[string]chan BluetoothState)
|
return true
|
||||||
m.subMutex.Unlock()
|
})
|
||||||
|
|
||||||
m.pairingSubMutex.Lock()
|
m.pairingSubscribers.Range(func(key string, ch chan PairingPrompt) bool {
|
||||||
for _, ch := range m.pairingSubscribers {
|
|
||||||
close(ch)
|
close(ch)
|
||||||
}
|
m.pairingSubscribers.Delete(key)
|
||||||
m.pairingSubscribers = make(map[string]chan PairingPrompt)
|
return true
|
||||||
m.pairingSubMutex.Unlock()
|
})
|
||||||
|
|
||||||
if m.dbusConn != nil {
|
if m.dbusConn != nil {
|
||||||
m.dbusConn.Close()
|
m.dbusConn.Close()
|
||||||
|
|||||||
@@ -3,22 +3,19 @@ package bluez
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SubscriptionBroker struct {
|
type SubscriptionBroker struct {
|
||||||
mu sync.RWMutex
|
pending syncmap.Map[string, chan PromptReply]
|
||||||
pending map[string]chan PromptReply
|
requests syncmap.Map[string, PromptRequest]
|
||||||
requests map[string]PromptRequest
|
|
||||||
broadcastPrompt func(PairingPrompt)
|
broadcastPrompt func(PairingPrompt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSubscriptionBroker(broadcastPrompt func(PairingPrompt)) PromptBroker {
|
func NewSubscriptionBroker(broadcastPrompt func(PairingPrompt)) PromptBroker {
|
||||||
return &SubscriptionBroker{
|
return &SubscriptionBroker{
|
||||||
pending: make(map[string]chan PromptReply),
|
|
||||||
requests: make(map[string]PromptRequest),
|
|
||||||
broadcastPrompt: broadcastPrompt,
|
broadcastPrompt: broadcastPrompt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -30,10 +27,8 @@ func (b *SubscriptionBroker) Ask(ctx context.Context, req PromptRequest) (string
|
|||||||
}
|
}
|
||||||
|
|
||||||
replyChan := make(chan PromptReply, 1)
|
replyChan := make(chan PromptReply, 1)
|
||||||
b.mu.Lock()
|
b.pending.Store(token, replyChan)
|
||||||
b.pending[token] = replyChan
|
b.requests.Store(token, req)
|
||||||
b.requests[token] = req
|
|
||||||
b.mu.Unlock()
|
|
||||||
|
|
||||||
if b.broadcastPrompt != nil {
|
if b.broadcastPrompt != nil {
|
||||||
prompt := PairingPrompt{
|
prompt := PairingPrompt{
|
||||||
@@ -53,10 +48,7 @@ func (b *SubscriptionBroker) Ask(ctx context.Context, req PromptRequest) (string
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *SubscriptionBroker) Wait(ctx context.Context, token string) (PromptReply, error) {
|
func (b *SubscriptionBroker) Wait(ctx context.Context, token string) (PromptReply, error) {
|
||||||
b.mu.RLock()
|
replyChan, exists := b.pending.Load(token)
|
||||||
replyChan, exists := b.pending[token]
|
|
||||||
b.mu.RUnlock()
|
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
return PromptReply{}, fmt.Errorf("unknown token: %s", token)
|
return PromptReply{}, fmt.Errorf("unknown token: %s", token)
|
||||||
}
|
}
|
||||||
@@ -75,10 +67,7 @@ func (b *SubscriptionBroker) Wait(ctx context.Context, token string) (PromptRepl
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *SubscriptionBroker) Resolve(token string, reply PromptReply) error {
|
func (b *SubscriptionBroker) Resolve(token string, reply PromptReply) error {
|
||||||
b.mu.RLock()
|
replyChan, exists := b.pending.Load(token)
|
||||||
replyChan, exists := b.pending[token]
|
|
||||||
b.mu.RUnlock()
|
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
return fmt.Errorf("unknown or expired token: %s", token)
|
return fmt.Errorf("unknown or expired token: %s", token)
|
||||||
}
|
}
|
||||||
@@ -92,8 +81,6 @@ func (b *SubscriptionBroker) Resolve(token string, reply PromptReply) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *SubscriptionBroker) cleanup(token string) {
|
func (b *SubscriptionBroker) cleanup(token string) {
|
||||||
b.mu.Lock()
|
b.pending.Delete(token)
|
||||||
delete(b.pending, token)
|
b.requests.Delete(token)
|
||||||
delete(b.requests, token)
|
|
||||||
b.mu.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package bluez
|
|||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
|
||||||
"github.com/godbus/dbus/v5"
|
"github.com/godbus/dbus/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -59,22 +60,19 @@ type PairingPrompt struct {
|
|||||||
type Manager struct {
|
type Manager struct {
|
||||||
state *BluetoothState
|
state *BluetoothState
|
||||||
stateMutex sync.RWMutex
|
stateMutex sync.RWMutex
|
||||||
subscribers map[string]chan BluetoothState
|
subscribers syncmap.Map[string, chan BluetoothState]
|
||||||
subMutex sync.RWMutex
|
|
||||||
stopChan chan struct{}
|
stopChan chan struct{}
|
||||||
dbusConn *dbus.Conn
|
dbusConn *dbus.Conn
|
||||||
signals chan *dbus.Signal
|
signals chan *dbus.Signal
|
||||||
sigWG sync.WaitGroup
|
sigWG sync.WaitGroup
|
||||||
agent *BluezAgent
|
agent *BluezAgent
|
||||||
promptBroker PromptBroker
|
promptBroker PromptBroker
|
||||||
pairingSubscribers map[string]chan PairingPrompt
|
pairingSubscribers syncmap.Map[string, chan PairingPrompt]
|
||||||
pairingSubMutex sync.RWMutex
|
|
||||||
dirty chan struct{}
|
dirty chan struct{}
|
||||||
notifierWg sync.WaitGroup
|
notifierWg sync.WaitGroup
|
||||||
lastNotifiedState *BluetoothState
|
lastNotifiedState *BluetoothState
|
||||||
adapterPath dbus.ObjectPath
|
adapterPath dbus.ObjectPath
|
||||||
pendingPairings map[string]bool
|
pendingPairings syncmap.Map[string, bool]
|
||||||
pendingPairingsMux sync.Mutex
|
|
||||||
eventQueue chan func()
|
eventQueue chan func()
|
||||||
eventWg sync.WaitGroup
|
eventWg sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ const (
|
|||||||
|
|
||||||
func NewDDCBackend() (*DDCBackend, error) {
|
func NewDDCBackend() (*DDCBackend, error) {
|
||||||
b := &DDCBackend{
|
b := &DDCBackend{
|
||||||
devices: make(map[string]*ddcDevice),
|
|
||||||
scanInterval: 30 * time.Second,
|
scanInterval: 30 * time.Second,
|
||||||
debounceTimers: make(map[string]*time.Timer),
|
debounceTimers: make(map[string]*time.Timer),
|
||||||
debouncePending: make(map[string]ddcPendingSet),
|
debouncePending: make(map[string]ddcPendingSet),
|
||||||
@@ -53,10 +52,10 @@ func (b *DDCBackend) scanI2CDevices() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
b.devicesMutex.Lock()
|
b.devices.Range(func(key string, value *ddcDevice) bool {
|
||||||
defer b.devicesMutex.Unlock()
|
b.devices.Delete(key)
|
||||||
|
return true
|
||||||
b.devices = make(map[string]*ddcDevice)
|
})
|
||||||
|
|
||||||
for i := 0; i < 32; i++ {
|
for i := 0; i < 32; i++ {
|
||||||
busPath := fmt.Sprintf("/dev/i2c-%d", i)
|
busPath := fmt.Sprintf("/dev/i2c-%d", i)
|
||||||
@@ -64,7 +63,6 @@ func (b *DDCBackend) scanI2CDevices() error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip SMBus, GPU internal buses (e.g. AMDGPU SMU) to prevent GPU hangs
|
|
||||||
if isIgnorableI2CBus(i) {
|
if isIgnorableI2CBus(i) {
|
||||||
log.Debugf("Skipping ignorable i2c-%d", i)
|
log.Debugf("Skipping ignorable i2c-%d", i)
|
||||||
continue
|
continue
|
||||||
@@ -77,7 +75,7 @@ func (b *DDCBackend) scanI2CDevices() error {
|
|||||||
|
|
||||||
id := fmt.Sprintf("ddc:i2c-%d", i)
|
id := fmt.Sprintf("ddc:i2c-%d", i)
|
||||||
dev.id = id
|
dev.id = id
|
||||||
b.devices[id] = dev
|
b.devices.Store(id, dev)
|
||||||
log.Debugf("found DDC device on i2c-%d", i)
|
log.Debugf("found DDC device on i2c-%d", i)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +98,7 @@ func (b *DDCBackend) probeDDCDevice(bus int) (*ddcDevice, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dummy := make([]byte, 32)
|
dummy := make([]byte, 32)
|
||||||
syscall.Read(fd, dummy)
|
syscall.Read(fd, dummy) //nolint:errcheck
|
||||||
|
|
||||||
writebuf := []byte{0x00}
|
writebuf := []byte{0x00}
|
||||||
n, err := syscall.Write(fd, writebuf)
|
n, err := syscall.Write(fd, writebuf)
|
||||||
@@ -164,12 +162,9 @@ func (b *DDCBackend) GetDevices() ([]Device, error) {
|
|||||||
log.Debugf("DDC scan error: %v", err)
|
log.Debugf("DDC scan error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
b.devicesMutex.Lock()
|
devices := make([]Device, 0)
|
||||||
defer b.devicesMutex.Unlock()
|
|
||||||
|
|
||||||
devices := make([]Device, 0, len(b.devices))
|
b.devices.Range(func(id string, dev *ddcDevice) bool {
|
||||||
|
|
||||||
for id, dev := range b.devices {
|
|
||||||
devices = append(devices, Device{
|
devices = append(devices, Device{
|
||||||
Class: ClassDDC,
|
Class: ClassDDC,
|
||||||
ID: id,
|
ID: id,
|
||||||
@@ -179,7 +174,8 @@ func (b *DDCBackend) GetDevices() ([]Device, error) {
|
|||||||
CurrentPercent: dev.lastBrightness,
|
CurrentPercent: dev.lastBrightness,
|
||||||
Backend: "ddc",
|
Backend: "ddc",
|
||||||
})
|
})
|
||||||
}
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
return devices, nil
|
return devices, nil
|
||||||
}
|
}
|
||||||
@@ -189,9 +185,7 @@ func (b *DDCBackend) SetBrightness(id string, value int, exponential bool, callb
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *DDCBackend) SetBrightnessWithExponent(id string, value int, exponential bool, exponent float64, callback func()) error {
|
func (b *DDCBackend) SetBrightnessWithExponent(id string, value int, exponential bool, exponent float64, callback func()) error {
|
||||||
b.devicesMutex.RLock()
|
_, ok := b.devices.Load(id)
|
||||||
_, ok := b.devices[id]
|
|
||||||
b.devicesMutex.RUnlock()
|
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("device not found: %s", id)
|
return fmt.Errorf("device not found: %s", id)
|
||||||
@@ -202,8 +196,6 @@ func (b *DDCBackend) SetBrightnessWithExponent(id string, value int, exponential
|
|||||||
}
|
}
|
||||||
|
|
||||||
b.debounceMutex.Lock()
|
b.debounceMutex.Lock()
|
||||||
defer b.debounceMutex.Unlock()
|
|
||||||
|
|
||||||
b.debouncePending[id] = ddcPendingSet{
|
b.debouncePending[id] = ddcPendingSet{
|
||||||
percent: value,
|
percent: value,
|
||||||
callback: callback,
|
callback: callback,
|
||||||
@@ -234,14 +226,13 @@ func (b *DDCBackend) SetBrightnessWithExponent(id string, value int, exponential
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
b.debounceMutex.Unlock()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *DDCBackend) setBrightnessImmediateWithExponent(id string, value int) error {
|
func (b *DDCBackend) setBrightnessImmediateWithExponent(id string, value int) error {
|
||||||
b.devicesMutex.RLock()
|
dev, ok := b.devices.Load(id)
|
||||||
dev, ok := b.devices[id]
|
|
||||||
b.devicesMutex.RUnlock()
|
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("device not found: %s", id)
|
return fmt.Errorf("device not found: %s", id)
|
||||||
@@ -266,9 +257,8 @@ func (b *DDCBackend) setBrightnessImmediateWithExponent(id string, value int) er
|
|||||||
return fmt.Errorf("get current capability: %w", err)
|
return fmt.Errorf("get current capability: %w", err)
|
||||||
}
|
}
|
||||||
max = cap.max
|
max = cap.max
|
||||||
b.devicesMutex.Lock()
|
|
||||||
dev.max = max
|
dev.max = max
|
||||||
b.devicesMutex.Unlock()
|
b.devices.Store(id, dev)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := b.setVCPFeature(fd, VCP_BRIGHTNESS, value); err != nil {
|
if err := b.setVCPFeature(fd, VCP_BRIGHTNESS, value); err != nil {
|
||||||
@@ -277,10 +267,9 @@ func (b *DDCBackend) setBrightnessImmediateWithExponent(id string, value int) er
|
|||||||
|
|
||||||
log.Debugf("set %s to %d/%d", id, value, max)
|
log.Debugf("set %s to %d/%d", id, value, max)
|
||||||
|
|
||||||
b.devicesMutex.Lock()
|
|
||||||
dev.max = max
|
dev.max = max
|
||||||
dev.lastBrightness = value
|
dev.lastBrightness = value
|
||||||
b.devicesMutex.Unlock()
|
b.devices.Store(id, dev)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ type DBusConn interface {
|
|||||||
|
|
||||||
type LogindBackend struct {
|
type LogindBackend struct {
|
||||||
conn DBusConn
|
conn DBusConn
|
||||||
connOnce bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLogindBackend() (*LogindBackend, error) {
|
func NewLogindBackend() (*LogindBackend, error) {
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ func NewManager() (*Manager, error) {
|
|||||||
|
|
||||||
func NewManagerWithOptions(exponential bool) (*Manager, error) {
|
func NewManagerWithOptions(exponential bool) (*Manager, error) {
|
||||||
m := &Manager{
|
m := &Manager{
|
||||||
subscribers: make(map[string]chan State),
|
|
||||||
updateSubscribers: make(map[string]chan DeviceUpdate),
|
|
||||||
stopChan: make(chan struct{}),
|
stopChan: make(chan struct{}),
|
||||||
exponential: exponential,
|
exponential: exponential,
|
||||||
}
|
}
|
||||||
@@ -360,20 +358,13 @@ func (m *Manager) broadcastDeviceUpdate(deviceID string) {
|
|||||||
|
|
||||||
update := DeviceUpdate{Device: *targetDevice}
|
update := DeviceUpdate{Device: *targetDevice}
|
||||||
|
|
||||||
m.subMutex.RLock()
|
|
||||||
defer m.subMutex.RUnlock()
|
|
||||||
|
|
||||||
if len(m.updateSubscribers) == 0 {
|
|
||||||
log.Debugf("No update subscribers for device: %s", deviceID)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("Broadcasting device update: %s at %d%%", deviceID, targetDevice.CurrentPercent)
|
log.Debugf("Broadcasting device update: %s at %d%%", deviceID, targetDevice.CurrentPercent)
|
||||||
|
|
||||||
for _, ch := range m.updateSubscribers {
|
m.updateSubscribers.Range(func(key string, ch chan DeviceUpdate) bool {
|
||||||
select {
|
select {
|
||||||
case ch <- update:
|
case ch <- update:
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
return true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ func NewSysfsBackend() (*SysfsBackend, error) {
|
|||||||
b := &SysfsBackend{
|
b := &SysfsBackend{
|
||||||
basePath: "/sys/class",
|
basePath: "/sys/class",
|
||||||
classes: []string{"backlight", "leds"},
|
classes: []string{"backlight", "leds"},
|
||||||
deviceCache: make(map[string]*sysfsDevice),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := b.scanDevices(); err != nil {
|
if err := b.scanDevices(); err != nil {
|
||||||
@@ -26,9 +25,6 @@ func NewSysfsBackend() (*SysfsBackend, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *SysfsBackend) scanDevices() error {
|
func (b *SysfsBackend) scanDevices() error {
|
||||||
b.deviceCacheMutex.Lock()
|
|
||||||
defer b.deviceCacheMutex.Unlock()
|
|
||||||
|
|
||||||
for _, class := range b.classes {
|
for _, class := range b.classes {
|
||||||
classPath := filepath.Join(b.basePath, class)
|
classPath := filepath.Join(b.basePath, class)
|
||||||
entries, err := os.ReadDir(classPath)
|
entries, err := os.ReadDir(classPath)
|
||||||
@@ -68,13 +64,13 @@ func (b *SysfsBackend) scanDevices() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deviceID := fmt.Sprintf("%s:%s", class, entry.Name())
|
deviceID := fmt.Sprintf("%s:%s", class, entry.Name())
|
||||||
b.deviceCache[deviceID] = &sysfsDevice{
|
b.deviceCache.Store(deviceID, &sysfsDevice{
|
||||||
class: deviceClass,
|
class: deviceClass,
|
||||||
id: deviceID,
|
id: deviceID,
|
||||||
name: entry.Name(),
|
name: entry.Name(),
|
||||||
maxBrightness: maxBrightness,
|
maxBrightness: maxBrightness,
|
||||||
minValue: minValue,
|
minValue: minValue,
|
||||||
}
|
})
|
||||||
|
|
||||||
log.Debugf("found %s device: %s (max=%d)", class, entry.Name(), maxBrightness)
|
log.Debugf("found %s device: %s (max=%d)", class, entry.Name(), maxBrightness)
|
||||||
}
|
}
|
||||||
@@ -106,19 +102,16 @@ func shouldSuppressDevice(name string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *SysfsBackend) GetDevices() ([]Device, error) {
|
func (b *SysfsBackend) GetDevices() ([]Device, error) {
|
||||||
b.deviceCacheMutex.RLock()
|
devices := make([]Device, 0)
|
||||||
defer b.deviceCacheMutex.RUnlock()
|
|
||||||
|
|
||||||
devices := make([]Device, 0, len(b.deviceCache))
|
b.deviceCache.Range(func(key string, dev *sysfsDevice) bool {
|
||||||
|
|
||||||
for _, dev := range b.deviceCache {
|
|
||||||
if shouldSuppressDevice(dev.name) {
|
if shouldSuppressDevice(dev.name) {
|
||||||
continue
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
parts := strings.SplitN(dev.id, ":", 2)
|
parts := strings.SplitN(dev.id, ":", 2)
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
continue
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
class := parts[0]
|
class := parts[0]
|
||||||
@@ -130,13 +123,13 @@ func (b *SysfsBackend) GetDevices() ([]Device, error) {
|
|||||||
brightnessData, err := os.ReadFile(brightnessPath)
|
brightnessData, err := os.ReadFile(brightnessPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("failed to read brightness for %s: %v", dev.id, err)
|
log.Debugf("failed to read brightness for %s: %v", dev.id, err)
|
||||||
continue
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
current, err := strconv.Atoi(strings.TrimSpace(string(brightnessData)))
|
current, err := strconv.Atoi(strings.TrimSpace(string(brightnessData)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("failed to parse brightness for %s: %v", dev.id, err)
|
log.Debugf("failed to parse brightness for %s: %v", dev.id, err)
|
||||||
continue
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
percent := b.ValueToPercent(current, dev, false)
|
percent := b.ValueToPercent(current, dev, false)
|
||||||
@@ -150,16 +143,14 @@ func (b *SysfsBackend) GetDevices() ([]Device, error) {
|
|||||||
CurrentPercent: percent,
|
CurrentPercent: percent,
|
||||||
Backend: "sysfs",
|
Backend: "sysfs",
|
||||||
})
|
})
|
||||||
}
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
return devices, nil
|
return devices, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *SysfsBackend) GetDevice(id string) (*sysfsDevice, error) {
|
func (b *SysfsBackend) GetDevice(id string) (*sysfsDevice, error) {
|
||||||
b.deviceCacheMutex.RLock()
|
dev, ok := b.deviceCache.Load(id)
|
||||||
defer b.deviceCacheMutex.RUnlock()
|
|
||||||
|
|
||||||
dev, ok := b.deviceCache[id]
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("device not found: %s", id)
|
return nil, fmt.Errorf("device not found: %s", id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ func TestManager_SetBrightness_LogindSuccess(t *testing.T) {
|
|||||||
sysfs := &SysfsBackend{
|
sysfs := &SysfsBackend{
|
||||||
basePath: tmpDir,
|
basePath: tmpDir,
|
||||||
classes: []string{"backlight"},
|
classes: []string{"backlight"},
|
||||||
deviceCache: make(map[string]*sysfsDevice),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := sysfs.scanDevices(); err != nil {
|
if err := sysfs.scanDevices(); err != nil {
|
||||||
@@ -45,8 +44,6 @@ func TestManager_SetBrightness_LogindSuccess(t *testing.T) {
|
|||||||
sysfsBackend: sysfs,
|
sysfsBackend: sysfs,
|
||||||
logindReady: true,
|
logindReady: true,
|
||||||
sysfsReady: true,
|
sysfsReady: true,
|
||||||
subscribers: make(map[string]chan State),
|
|
||||||
updateSubscribers: make(map[string]chan DeviceUpdate),
|
|
||||||
stopChan: make(chan struct{}),
|
stopChan: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +104,6 @@ func TestManager_SetBrightness_LogindFailsFallbackToSysfs(t *testing.T) {
|
|||||||
sysfs := &SysfsBackend{
|
sysfs := &SysfsBackend{
|
||||||
basePath: tmpDir,
|
basePath: tmpDir,
|
||||||
classes: []string{"backlight"},
|
classes: []string{"backlight"},
|
||||||
deviceCache: make(map[string]*sysfsDevice),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := sysfs.scanDevices(); err != nil {
|
if err := sysfs.scanDevices(); err != nil {
|
||||||
@@ -119,8 +115,6 @@ func TestManager_SetBrightness_LogindFailsFallbackToSysfs(t *testing.T) {
|
|||||||
sysfsBackend: sysfs,
|
sysfsBackend: sysfs,
|
||||||
logindReady: true,
|
logindReady: true,
|
||||||
sysfsReady: true,
|
sysfsReady: true,
|
||||||
subscribers: make(map[string]chan State),
|
|
||||||
updateSubscribers: make(map[string]chan DeviceUpdate),
|
|
||||||
stopChan: make(chan struct{}),
|
stopChan: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,7 +171,6 @@ func TestManager_SetBrightness_NoLogind(t *testing.T) {
|
|||||||
sysfs := &SysfsBackend{
|
sysfs := &SysfsBackend{
|
||||||
basePath: tmpDir,
|
basePath: tmpDir,
|
||||||
classes: []string{"backlight"},
|
classes: []string{"backlight"},
|
||||||
deviceCache: make(map[string]*sysfsDevice),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := sysfs.scanDevices(); err != nil {
|
if err := sysfs.scanDevices(); err != nil {
|
||||||
@@ -189,8 +182,6 @@ func TestManager_SetBrightness_NoLogind(t *testing.T) {
|
|||||||
sysfsBackend: sysfs,
|
sysfsBackend: sysfs,
|
||||||
logindReady: false,
|
logindReady: false,
|
||||||
sysfsReady: true,
|
sysfsReady: true,
|
||||||
subscribers: make(map[string]chan State),
|
|
||||||
updateSubscribers: make(map[string]chan DeviceUpdate),
|
|
||||||
stopChan: make(chan struct{}),
|
stopChan: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,7 +233,6 @@ func TestManager_SetBrightness_LEDWithLogind(t *testing.T) {
|
|||||||
sysfs := &SysfsBackend{
|
sysfs := &SysfsBackend{
|
||||||
basePath: tmpDir,
|
basePath: tmpDir,
|
||||||
classes: []string{"leds"},
|
classes: []string{"leds"},
|
||||||
deviceCache: make(map[string]*sysfsDevice),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := sysfs.scanDevices(); err != nil {
|
if err := sysfs.scanDevices(); err != nil {
|
||||||
@@ -254,8 +244,6 @@ func TestManager_SetBrightness_LEDWithLogind(t *testing.T) {
|
|||||||
sysfsBackend: sysfs,
|
sysfsBackend: sysfs,
|
||||||
logindReady: true,
|
logindReady: true,
|
||||||
sysfsReady: true,
|
sysfsReady: true,
|
||||||
subscribers: make(map[string]chan State),
|
|
||||||
updateSubscribers: make(map[string]chan DeviceUpdate),
|
|
||||||
stopChan: make(chan struct{}),
|
stopChan: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -162,24 +162,19 @@ func TestSysfsBackend_ScanDevices(t *testing.T) {
|
|||||||
b := &SysfsBackend{
|
b := &SysfsBackend{
|
||||||
basePath: tmpDir,
|
basePath: tmpDir,
|
||||||
classes: []string{"backlight", "leds"},
|
classes: []string{"backlight", "leds"},
|
||||||
deviceCache: make(map[string]*sysfsDevice),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := b.scanDevices(); err != nil {
|
if err := b.scanDevices(); err != nil {
|
||||||
t.Fatalf("scanDevices() error = %v", err)
|
t.Fatalf("scanDevices() error = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(b.deviceCache) != 2 {
|
|
||||||
t.Errorf("expected 2 devices, got %d", len(b.deviceCache))
|
|
||||||
}
|
|
||||||
|
|
||||||
backlightID := "backlight:test_backlight"
|
backlightID := "backlight:test_backlight"
|
||||||
if _, ok := b.deviceCache[backlightID]; !ok {
|
if _, ok := b.deviceCache.Load(backlightID); !ok {
|
||||||
t.Errorf("backlight device not found")
|
t.Errorf("backlight device not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
ledID := "leds:test_led"
|
ledID := "leds:test_led"
|
||||||
if _, ok := b.deviceCache[ledID]; !ok {
|
if _, ok := b.deviceCache.Load(ledID); !ok {
|
||||||
t.Errorf("LED device not found")
|
t.Errorf("LED device not found")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package brightness
|
|||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DeviceClass string
|
type DeviceClass string
|
||||||
@@ -51,9 +53,8 @@ type Manager struct {
|
|||||||
stateMutex sync.RWMutex
|
stateMutex sync.RWMutex
|
||||||
state State
|
state State
|
||||||
|
|
||||||
subscribers map[string]chan State
|
subscribers syncmap.Map[string, chan State]
|
||||||
updateSubscribers map[string]chan DeviceUpdate
|
updateSubscribers syncmap.Map[string, chan DeviceUpdate]
|
||||||
subMutex sync.RWMutex
|
|
||||||
|
|
||||||
broadcastMutex sync.Mutex
|
broadcastMutex sync.Mutex
|
||||||
broadcastTimer *time.Timer
|
broadcastTimer *time.Timer
|
||||||
@@ -67,8 +68,7 @@ type SysfsBackend struct {
|
|||||||
basePath string
|
basePath string
|
||||||
classes []string
|
classes []string
|
||||||
|
|
||||||
deviceCache map[string]*sysfsDevice
|
deviceCache syncmap.Map[string, *sysfsDevice]
|
||||||
deviceCacheMutex sync.RWMutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type sysfsDevice struct {
|
type sysfsDevice struct {
|
||||||
@@ -80,8 +80,7 @@ type sysfsDevice struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DDCBackend struct {
|
type DDCBackend struct {
|
||||||
devices map[string]*ddcDevice
|
devices syncmap.Map[string, *ddcDevice]
|
||||||
devicesMutex sync.RWMutex
|
|
||||||
|
|
||||||
scanMutex sync.Mutex
|
scanMutex sync.Mutex
|
||||||
lastScan time.Time
|
lastScan time.Time
|
||||||
@@ -121,36 +120,31 @@ type SetBrightnessParams struct {
|
|||||||
|
|
||||||
func (m *Manager) Subscribe(id string) chan State {
|
func (m *Manager) Subscribe(id string) chan State {
|
||||||
ch := make(chan State, 16)
|
ch := make(chan State, 16)
|
||||||
m.subMutex.Lock()
|
|
||||||
m.subscribers[id] = ch
|
m.subscribers.Store(id, ch)
|
||||||
m.subMutex.Unlock()
|
|
||||||
return ch
|
return ch
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Unsubscribe(id string) {
|
func (m *Manager) Unsubscribe(id string) {
|
||||||
m.subMutex.Lock()
|
|
||||||
if ch, ok := m.subscribers[id]; ok {
|
if val, ok := m.subscribers.LoadAndDelete(id); ok {
|
||||||
close(ch)
|
close(val)
|
||||||
delete(m.subscribers, id)
|
|
||||||
}
|
}
|
||||||
m.subMutex.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) SubscribeUpdates(id string) chan DeviceUpdate {
|
func (m *Manager) SubscribeUpdates(id string) chan DeviceUpdate {
|
||||||
ch := make(chan DeviceUpdate, 16)
|
ch := make(chan DeviceUpdate, 16)
|
||||||
m.subMutex.Lock()
|
m.updateSubscribers.Store(id, ch)
|
||||||
m.updateSubscribers[id] = ch
|
|
||||||
m.subMutex.Unlock()
|
|
||||||
return ch
|
return ch
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) UnsubscribeUpdates(id string) {
|
func (m *Manager) UnsubscribeUpdates(id string) {
|
||||||
m.subMutex.Lock()
|
if val, ok := m.updateSubscribers.LoadAndDelete(id); ok {
|
||||||
if ch, ok := m.updateSubscribers[id]; ok {
|
close(val)
|
||||||
close(ch)
|
|
||||||
delete(m.updateSubscribers, id)
|
|
||||||
}
|
}
|
||||||
m.subMutex.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) NotifySubscribers() {
|
func (m *Manager) NotifySubscribers() {
|
||||||
@@ -158,15 +152,13 @@ func (m *Manager) NotifySubscribers() {
|
|||||||
state := m.state
|
state := m.state
|
||||||
m.stateMutex.RUnlock()
|
m.stateMutex.RUnlock()
|
||||||
|
|
||||||
m.subMutex.RLock()
|
m.subscribers.Range(func(key string, ch chan State) bool {
|
||||||
defer m.subMutex.RUnlock()
|
|
||||||
|
|
||||||
for _, ch := range m.subscribers {
|
|
||||||
select {
|
select {
|
||||||
case ch <- state:
|
case ch <- state:
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
return true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) GetState() State {
|
func (m *Manager) GetState() State {
|
||||||
@@ -178,16 +170,16 @@ func (m *Manager) GetState() State {
|
|||||||
func (m *Manager) Close() {
|
func (m *Manager) Close() {
|
||||||
close(m.stopChan)
|
close(m.stopChan)
|
||||||
|
|
||||||
m.subMutex.Lock()
|
m.subscribers.Range(func(key string, ch chan State) bool {
|
||||||
for _, ch := range m.subscribers {
|
|
||||||
close(ch)
|
close(ch)
|
||||||
}
|
m.subscribers.Delete(key)
|
||||||
m.subscribers = make(map[string]chan State)
|
return true
|
||||||
for _, ch := range m.updateSubscribers {
|
})
|
||||||
|
m.updateSubscribers.Range(func(key string, ch chan DeviceUpdate) bool {
|
||||||
close(ch)
|
close(ch)
|
||||||
}
|
m.updateSubscribers.Delete(key)
|
||||||
m.updateSubscribers = make(map[string]chan DeviceUpdate)
|
return true
|
||||||
m.subMutex.Unlock()
|
})
|
||||||
|
|
||||||
if m.logindBackend != nil {
|
if m.logindBackend != nil {
|
||||||
m.logindBackend.Close()
|
m.logindBackend.Close()
|
||||||
|
|||||||
@@ -1,12 +1,34 @@
|
|||||||
package cups
|
package cups
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/config"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/pkg/ipp"
|
"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) {
|
func (m *Manager) GetPrinters() ([]Printer, error) {
|
||||||
attributes := []string{
|
attributes := []string{
|
||||||
ipp.AttributePrinterName,
|
ipp.AttributePrinterName,
|
||||||
@@ -21,6 +43,9 @@ func (m *Manager) GetPrinters() ([]Printer, error) {
|
|||||||
|
|
||||||
printerAttrs, err := m.client.GetPrinters(attributes)
|
printerAttrs, err := m.client.GetPrinters(attributes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if isNoPrintersError(err) {
|
||||||
|
return []Printer{}, nil
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,17 +116,289 @@ func (m *Manager) GetJobs(printerName string, whichJobs string) ([]Job, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) CancelJob(jobID int) 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 {
|
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 {
|
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 {
|
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) {
|
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 := 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{
|
m := NewTestManager(mockClient, nil)
|
||||||
client: mockClient,
|
assert.NoError(t, m.CancelJob(1))
|
||||||
}
|
|
||||||
|
|
||||||
err := m.CancelJob(1)
|
|
||||||
if tt.wantErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManager_PausePrinter(t *testing.T) {
|
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 := 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{
|
m := NewTestManager(mockClient, nil)
|
||||||
client: mockClient,
|
assert.NoError(t, m.PausePrinter("printer1"))
|
||||||
}
|
|
||||||
|
|
||||||
err := m.PausePrinter("printer1")
|
|
||||||
if tt.wantErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManager_ResumePrinter(t *testing.T) {
|
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 := 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{
|
m := NewTestManager(mockClient, nil)
|
||||||
client: mockClient,
|
assert.NoError(t, m.ResumePrinter("printer1"))
|
||||||
}
|
|
||||||
|
|
||||||
err := m.ResumePrinter("printer1")
|
|
||||||
if tt.wantErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManager_PurgeJobs(t *testing.T) {
|
func TestManager_PurgeJobs(t *testing.T) {
|
||||||
@@ -269,11 +185,12 @@ func TestManager_PurgeJobs(t *testing.T) {
|
|||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||||
mockClient.EXPECT().CancelAllJob("printer1", true).Return(tt.mockErr)
|
mockClient.EXPECT().CancelAllJob("printer1", true).Return(tt.mockErr)
|
||||||
|
if !tt.wantErr {
|
||||||
m := &Manager{
|
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||||
client: mockClient,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m := NewTestManager(mockClient, nil)
|
||||||
|
|
||||||
err := m.PurgeJobs("printer1")
|
err := m.PurgeJobs("printer1")
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
assert.Error(t, err)
|
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)
|
handleCancelJob(conn, req, manager)
|
||||||
case "cups.purgeJobs":
|
case "cups.purgeJobs":
|
||||||
handlePurgeJobs(conn, req, manager)
|
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:
|
default:
|
||||||
models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
|
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) {
|
func TestHandlePausePrinter(t *testing.T) {
|
||||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||||
mockClient.EXPECT().PausePrinter("printer1").Return(nil)
|
mockClient.EXPECT().PausePrinter("printer1").Return(nil)
|
||||||
|
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||||
|
|
||||||
m := &Manager{
|
m := NewTestManager(mockClient, nil)
|
||||||
client: mockClient,
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
@@ -173,10 +172,9 @@ func TestHandlePausePrinter(t *testing.T) {
|
|||||||
func TestHandleResumePrinter(t *testing.T) {
|
func TestHandleResumePrinter(t *testing.T) {
|
||||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||||
mockClient.EXPECT().ResumePrinter("printer1").Return(nil)
|
mockClient.EXPECT().ResumePrinter("printer1").Return(nil)
|
||||||
|
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||||
|
|
||||||
m := &Manager{
|
m := NewTestManager(mockClient, nil)
|
||||||
client: mockClient,
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
@@ -201,10 +199,9 @@ func TestHandleResumePrinter(t *testing.T) {
|
|||||||
func TestHandleCancelJob(t *testing.T) {
|
func TestHandleCancelJob(t *testing.T) {
|
||||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||||
mockClient.EXPECT().CancelJob(1, false).Return(nil)
|
mockClient.EXPECT().CancelJob(1, false).Return(nil)
|
||||||
|
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||||
|
|
||||||
m := &Manager{
|
m := NewTestManager(mockClient, nil)
|
||||||
client: mockClient,
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
@@ -229,10 +226,9 @@ func TestHandleCancelJob(t *testing.T) {
|
|||||||
func TestHandlePurgeJobs(t *testing.T) {
|
func TestHandlePurgeJobs(t *testing.T) {
|
||||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||||
mockClient.EXPECT().CancelAllJob("printer1", true).Return(nil)
|
mockClient.EXPECT().CancelAllJob("printer1", true).Return(nil)
|
||||||
|
mockClient.EXPECT().GetPrinters(mock.Anything).Return(map[string]ipp.Attributes{}, nil)
|
||||||
|
|
||||||
m := &Manager{
|
m := NewTestManager(mockClient, nil)
|
||||||
client: mockClient,
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
conn := &mockConn{Buffer: buf}
|
conn := &mockConn{Buffer: buf}
|
||||||
@@ -277,3 +273,439 @@ func TestHandleRequest_UnknownMethod(t *testing.T) {
|
|||||||
assert.Nil(t, resp.Result)
|
assert.Nil(t, resp.Result)
|
||||||
assert.NotNil(t, resp.Error)
|
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
|
package cups
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -31,17 +32,25 @@ func NewManager() (*Manager, error) {
|
|||||||
client := ipp.NewCUPSClient(host, port, username, password, false)
|
client := ipp.NewCUPSClient(host, port, username, password, false)
|
||||||
baseURL := fmt.Sprintf("http://%s:%d", host, port)
|
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{
|
m := &Manager{
|
||||||
state: &CUPSState{
|
state: &CUPSState{
|
||||||
Printers: make(map[string]*Printer),
|
Printers: make(map[string]*Printer),
|
||||||
},
|
},
|
||||||
client: client,
|
client: client,
|
||||||
|
pkHelper: pkHelper,
|
||||||
baseURL: baseURL,
|
baseURL: baseURL,
|
||||||
stateMutex: sync.RWMutex{},
|
stateMutex: sync.RWMutex{},
|
||||||
stopChan: make(chan struct{}),
|
stopChan: make(chan struct{}),
|
||||||
dirty: make(chan struct{}, 1),
|
dirty: make(chan struct{}, 1),
|
||||||
subscribers: make(map[string]chan CUPSState),
|
|
||||||
subMutex: sync.RWMutex{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := m.updateState(); err != nil {
|
if err := m.updateState(); err != nil {
|
||||||
@@ -100,6 +109,12 @@ func (m *Manager) eventHandler() {
|
|||||||
func (m *Manager) updateState() error {
|
func (m *Manager) updateState() error {
|
||||||
printers, err := m.GetPrinters()
|
printers, err := m.GetPrinters()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if isNoPrintersError(err) {
|
||||||
|
m.stateMutex.Lock()
|
||||||
|
m.state.Printers = make(map[string]*Printer)
|
||||||
|
m.stateMutex.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,6 +136,19 @@ func (m *Manager) updateState() error {
|
|||||||
return nil
|
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() {
|
func (m *Manager) notifier() {
|
||||||
defer m.notifierWg.Done()
|
defer m.notifierWg.Done()
|
||||||
const minGap = 100 * time.Millisecond
|
const minGap = 100 * time.Millisecond
|
||||||
@@ -142,28 +170,21 @@ func (m *Manager) notifier() {
|
|||||||
if !pending {
|
if !pending {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
m.subMutex.RLock()
|
|
||||||
if len(m.subscribers) == 0 {
|
|
||||||
m.subMutex.RUnlock()
|
|
||||||
pending = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
currentState := m.snapshotState()
|
currentState := m.snapshotState()
|
||||||
|
|
||||||
if m.lastNotifiedState != nil && !stateChanged(m.lastNotifiedState, ¤tState) {
|
if m.lastNotifiedState != nil && !stateChanged(m.lastNotifiedState, ¤tState) {
|
||||||
m.subMutex.RUnlock()
|
|
||||||
pending = false
|
pending = false
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ch := range m.subscribers {
|
m.subscribers.Range(func(key string, ch chan CUPSState) bool {
|
||||||
select {
|
select {
|
||||||
case ch <- currentState:
|
case ch <- currentState:
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
return true
|
||||||
m.subMutex.RUnlock()
|
})
|
||||||
|
|
||||||
stateCopy := currentState
|
stateCopy := currentState
|
||||||
m.lastNotifiedState = &stateCopy
|
m.lastNotifiedState = &stateCopy
|
||||||
@@ -179,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 {
|
func (m *Manager) GetState() CUPSState {
|
||||||
return m.snapshotState()
|
return m.snapshotState()
|
||||||
}
|
}
|
||||||
@@ -199,10 +228,14 @@ func (m *Manager) snapshotState() CUPSState {
|
|||||||
|
|
||||||
func (m *Manager) Subscribe(id string) chan CUPSState {
|
func (m *Manager) Subscribe(id string) chan CUPSState {
|
||||||
ch := make(chan CUPSState, 64)
|
ch := make(chan CUPSState, 64)
|
||||||
m.subMutex.Lock()
|
|
||||||
wasEmpty := len(m.subscribers) == 0
|
wasEmpty := true
|
||||||
m.subscribers[id] = ch
|
m.subscribers.Range(func(key string, ch chan CUPSState) bool {
|
||||||
m.subMutex.Unlock()
|
wasEmpty = false
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
m.subscribers.Store(id, ch)
|
||||||
|
|
||||||
if wasEmpty && m.subscription != nil {
|
if wasEmpty && m.subscription != nil {
|
||||||
if err := m.subscription.Start(); err != nil {
|
if err := m.subscription.Start(); err != nil {
|
||||||
@@ -217,13 +250,15 @@ func (m *Manager) Subscribe(id string) chan CUPSState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Unsubscribe(id string) {
|
func (m *Manager) Unsubscribe(id string) {
|
||||||
m.subMutex.Lock()
|
if val, ok := m.subscribers.LoadAndDelete(id); ok {
|
||||||
if ch, ok := m.subscribers[id]; ok {
|
close(val)
|
||||||
close(ch)
|
|
||||||
delete(m.subscribers, id)
|
|
||||||
}
|
}
|
||||||
isEmpty := len(m.subscribers) == 0
|
|
||||||
m.subMutex.Unlock()
|
isEmpty := true
|
||||||
|
m.subscribers.Range(func(key string, ch chan CUPSState) bool {
|
||||||
|
isEmpty = false
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
if isEmpty && m.subscription != nil {
|
if isEmpty && m.subscription != nil {
|
||||||
m.subscription.Stop()
|
m.subscription.Stop()
|
||||||
@@ -241,12 +276,11 @@ func (m *Manager) Close() {
|
|||||||
m.eventWG.Wait()
|
m.eventWG.Wait()
|
||||||
m.notifierWg.Wait()
|
m.notifierWg.Wait()
|
||||||
|
|
||||||
m.subMutex.Lock()
|
m.subscribers.Range(func(key string, ch chan CUPSState) bool {
|
||||||
for _, ch := range m.subscribers {
|
|
||||||
close(ch)
|
close(ch)
|
||||||
}
|
m.subscribers.Delete(key)
|
||||||
m.subscribers = make(map[string]chan CUPSState)
|
return true
|
||||||
m.subMutex.Unlock()
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func stateChanged(old, new *CUPSState) bool {
|
func stateChanged(old, new *CUPSState) bool {
|
||||||
@@ -260,6 +294,7 @@ func stateChanged(old, new *CUPSState) bool {
|
|||||||
}
|
}
|
||||||
if oldPrinter.State != newPrinter.State ||
|
if oldPrinter.State != newPrinter.State ||
|
||||||
oldPrinter.StateReason != newPrinter.StateReason ||
|
oldPrinter.StateReason != newPrinter.StateReason ||
|
||||||
|
oldPrinter.Accepting != newPrinter.Accepting ||
|
||||||
len(oldPrinter.Jobs) != len(newPrinter.Jobs) {
|
len(oldPrinter.Jobs) != len(newPrinter.Jobs) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -338,3 +373,18 @@ func getBoolAttr(attrs ipp.Attributes, key string) bool {
|
|||||||
}
|
}
|
||||||
return false
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ func TestNewManager(t *testing.T) {
|
|||||||
client: nil,
|
client: nil,
|
||||||
stopChan: make(chan struct{}),
|
stopChan: make(chan struct{}),
|
||||||
dirty: make(chan struct{}, 1),
|
dirty: make(chan struct{}, 1),
|
||||||
subscribers: make(map[string]chan CUPSState),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.NotNil(t, m)
|
assert.NotNil(t, m)
|
||||||
@@ -38,7 +37,6 @@ func TestManager_GetState(t *testing.T) {
|
|||||||
client: mockClient,
|
client: mockClient,
|
||||||
stopChan: make(chan struct{}),
|
stopChan: make(chan struct{}),
|
||||||
dirty: make(chan struct{}, 1),
|
dirty: make(chan struct{}, 1),
|
||||||
subscribers: make(map[string]chan CUPSState),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
state := m.GetState()
|
state := m.GetState()
|
||||||
@@ -56,15 +54,25 @@ func TestManager_Subscribe(t *testing.T) {
|
|||||||
client: mockClient,
|
client: mockClient,
|
||||||
stopChan: make(chan struct{}),
|
stopChan: make(chan struct{}),
|
||||||
dirty: make(chan struct{}, 1),
|
dirty: make(chan struct{}, 1),
|
||||||
subscribers: make(map[string]chan CUPSState),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ch := m.Subscribe("test-client")
|
ch := m.Subscribe("test-client")
|
||||||
assert.NotNil(t, ch)
|
assert.NotNil(t, ch)
|
||||||
assert.Equal(t, 1, len(m.subscribers))
|
|
||||||
|
count := 0
|
||||||
|
m.subscribers.Range(func(key string, ch chan CUPSState) bool {
|
||||||
|
count++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
assert.Equal(t, 1, count)
|
||||||
|
|
||||||
m.Unsubscribe("test-client")
|
m.Unsubscribe("test-client")
|
||||||
assert.Equal(t, 0, len(m.subscribers))
|
count = 0
|
||||||
|
m.subscribers.Range(func(key string, ch chan CUPSState) bool {
|
||||||
|
count++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
assert.Equal(t, 0, count)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManager_Close(t *testing.T) {
|
func TestManager_Close(t *testing.T) {
|
||||||
@@ -77,7 +85,6 @@ func TestManager_Close(t *testing.T) {
|
|||||||
client: mockClient,
|
client: mockClient,
|
||||||
stopChan: make(chan struct{}),
|
stopChan: make(chan struct{}),
|
||||||
dirty: make(chan struct{}, 1),
|
dirty: make(chan struct{}, 1),
|
||||||
subscribers: make(map[string]chan CUPSState),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m.eventWG.Add(1)
|
m.eventWG.Add(1)
|
||||||
@@ -93,7 +100,12 @@ func TestManager_Close(t *testing.T) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
m.Close()
|
m.Close()
|
||||||
assert.Equal(t, 0, len(m.subscribers))
|
count := 0
|
||||||
|
m.subscribers.Range(func(key string, ch chan CUPSState) bool {
|
||||||
|
count++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
assert.Equal(t, 0, count)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStateChanged(t *testing.T) {
|
func TestStateChanged(t *testing.T) {
|
||||||
|
|||||||
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/pkg/ipp"
|
"github.com/AvengeMedia/DankMaterialShell/core/pkg/ipp"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CUPSState struct {
|
type CUPSState struct {
|
||||||
@@ -34,13 +35,41 @@ type Job struct {
|
|||||||
TimeCreated time.Time `json:"timeCreated"`
|
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 {
|
type Manager struct {
|
||||||
state *CUPSState
|
state *CUPSState
|
||||||
client CUPSClientInterface
|
client CUPSClientInterface
|
||||||
|
pkHelper PkHelper
|
||||||
subscription SubscriptionManagerInterface
|
subscription SubscriptionManagerInterface
|
||||||
stateMutex sync.RWMutex
|
stateMutex sync.RWMutex
|
||||||
subscribers map[string]chan CUPSState
|
subscribers syncmap.Map[string, chan CUPSState]
|
||||||
subMutex sync.RWMutex
|
|
||||||
stopChan chan struct{}
|
stopChan chan struct{}
|
||||||
eventWG sync.WaitGroup
|
eventWG sync.WaitGroup
|
||||||
dirty chan struct{}
|
dirty chan struct{}
|
||||||
@@ -63,6 +92,24 @@ type CUPSClientInterface interface {
|
|||||||
ResumePrinter(printer string) error
|
ResumePrinter(printer string) error
|
||||||
CancelAllJob(printer string, purge bool) error
|
CancelAllJob(printer string, purge bool) error
|
||||||
SendRequest(url string, req *ipp.Request, additionalResponseData io.Writer) (*ipp.Response, 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 {
|
type SubscriptionEvent struct {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
wlclient "github.com/yaslama/go-wayland/wayland/client"
|
wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/dwl_ipc"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/dwl_ipc"
|
||||||
@@ -13,11 +13,11 @@ import (
|
|||||||
func NewManager(display *wlclient.Display) (*Manager, error) {
|
func NewManager(display *wlclient.Display) (*Manager, error) {
|
||||||
m := &Manager{
|
m := &Manager{
|
||||||
display: display,
|
display: display,
|
||||||
outputs: make(map[uint32]*outputState),
|
ctx: display.Context(),
|
||||||
cmdq: make(chan cmd, 128),
|
cmdq: make(chan cmd, 128),
|
||||||
outputSetupReq: make(chan uint32, 16),
|
outputSetupReq: make(chan uint32, 16),
|
||||||
stopChan: make(chan struct{}),
|
stopChan: make(chan struct{}),
|
||||||
subscribers: make(map[string]chan State),
|
|
||||||
dirty: make(chan struct{}, 1),
|
dirty: make(chan struct{}, 1),
|
||||||
layouts: make([]string, 0),
|
layouts: make([]string, 0),
|
||||||
}
|
}
|
||||||
@@ -55,10 +55,7 @@ func (m *Manager) waylandActor() {
|
|||||||
case c := <-m.cmdq:
|
case c := <-m.cmdq:
|
||||||
c.fn()
|
c.fn()
|
||||||
case outputID := <-m.outputSetupReq:
|
case outputID := <-m.outputSetupReq:
|
||||||
m.outputsMutex.RLock()
|
out, exists := m.outputs.Load(outputID)
|
||||||
out, exists := m.outputs[outputID]
|
|
||||||
m.outputsMutex.RUnlock()
|
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
log.Warnf("DWL: Output %d no longer exists, skipping setup", outputID)
|
log.Warnf("DWL: Output %d no longer exists, skipping setup", outputID)
|
||||||
continue
|
continue
|
||||||
@@ -86,7 +83,6 @@ func (m *Manager) waylandActor() {
|
|||||||
|
|
||||||
func (m *Manager) setupRegistry() error {
|
func (m *Manager) setupRegistry() error {
|
||||||
log.Info("DWL: starting registry setup")
|
log.Info("DWL: starting registry setup")
|
||||||
ctx := m.display.Context()
|
|
||||||
|
|
||||||
registry, err := m.display.GetRegistry()
|
registry, err := m.display.GetRegistry()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -102,10 +98,10 @@ func (m *Manager) setupRegistry() error {
|
|||||||
switch e.Interface {
|
switch e.Interface {
|
||||||
case dwl_ipc.ZdwlIpcManagerV2InterfaceName:
|
case dwl_ipc.ZdwlIpcManagerV2InterfaceName:
|
||||||
log.Infof("DWL: found %s", dwl_ipc.ZdwlIpcManagerV2InterfaceName)
|
log.Infof("DWL: found %s", dwl_ipc.ZdwlIpcManagerV2InterfaceName)
|
||||||
manager := dwl_ipc.NewZdwlIpcManagerV2(ctx)
|
manager := dwl_ipc.NewZdwlIpcManagerV2(m.ctx)
|
||||||
version := e.Version
|
version := e.Version
|
||||||
if version > 1 {
|
if version > 2 {
|
||||||
version = 1
|
version = 2
|
||||||
}
|
}
|
||||||
if err := registry.Bind(e.Name, e.Interface, version, manager); err == nil {
|
if err := registry.Bind(e.Name, e.Interface, version, manager); err == nil {
|
||||||
dwlMgr = manager
|
dwlMgr = manager
|
||||||
@@ -128,7 +124,7 @@ func (m *Manager) setupRegistry() error {
|
|||||||
}
|
}
|
||||||
case "wl_output":
|
case "wl_output":
|
||||||
log.Debugf("DWL: found wl_output (name=%d)", e.Name)
|
log.Debugf("DWL: found wl_output (name=%d)", e.Name)
|
||||||
output := wlclient.NewOutput(ctx)
|
output := wlclient.NewOutput(m.ctx)
|
||||||
|
|
||||||
outState := &outputState{
|
outState := &outputState{
|
||||||
registryName: e.Name,
|
registryName: e.Name,
|
||||||
@@ -156,9 +152,7 @@ func (m *Manager) setupRegistry() error {
|
|||||||
outputs = append(outputs, output)
|
outputs = append(outputs, output)
|
||||||
outputRegNames[outputID] = e.Name
|
outputRegNames[outputID] = e.Name
|
||||||
|
|
||||||
m.outputsMutex.Lock()
|
m.outputs.Store(outputID, outState)
|
||||||
m.outputs[outputID] = outState
|
|
||||||
m.outputsMutex.Unlock()
|
|
||||||
|
|
||||||
if m.manager != nil {
|
if m.manager != nil {
|
||||||
select {
|
select {
|
||||||
@@ -176,17 +170,16 @@ func (m *Manager) setupRegistry() error {
|
|||||||
|
|
||||||
registry.SetGlobalRemoveHandler(func(e wlclient.RegistryGlobalRemoveEvent) {
|
registry.SetGlobalRemoveHandler(func(e wlclient.RegistryGlobalRemoveEvent) {
|
||||||
m.post(func() {
|
m.post(func() {
|
||||||
m.outputsMutex.Lock()
|
|
||||||
var outToRelease *outputState
|
var outToRelease *outputState
|
||||||
for id, out := range m.outputs {
|
m.outputs.Range(func(id uint32, out *outputState) bool {
|
||||||
if out.registryName == e.Name {
|
if out.registryName == e.Name {
|
||||||
log.Infof("DWL: Output %d removed", id)
|
log.Infof("DWL: Output %d removed", id)
|
||||||
outToRelease = out
|
outToRelease = out
|
||||||
delete(m.outputs, id)
|
m.outputs.Delete(id)
|
||||||
break
|
return false
|
||||||
}
|
}
|
||||||
}
|
return true
|
||||||
m.outputsMutex.Unlock()
|
})
|
||||||
|
|
||||||
if outToRelease != nil {
|
if outToRelease != nil {
|
||||||
if ipcOut, ok := outToRelease.ipcOutput.(*dwl_ipc.ZdwlIpcOutputV2); ok && ipcOut != nil {
|
if ipcOut, ok := outToRelease.ipcOutput.(*dwl_ipc.ZdwlIpcOutputV2); ok && ipcOut != nil {
|
||||||
@@ -236,14 +229,11 @@ func (m *Manager) setupOutput(manager *dwl_ipc.ZdwlIpcManagerV2, output *wlclien
|
|||||||
return fmt.Errorf("failed to get dwl output: %w", err)
|
return fmt.Errorf("failed to get dwl output: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
m.outputsMutex.Lock()
|
outState, exists := m.outputs.Load(output.ID())
|
||||||
outState, exists := m.outputs[output.ID()]
|
|
||||||
if !exists {
|
if !exists {
|
||||||
m.outputsMutex.Unlock()
|
|
||||||
return fmt.Errorf("output state not found for id %d", output.ID())
|
return fmt.Errorf("output state not found for id %d", output.ID())
|
||||||
}
|
}
|
||||||
outState.ipcOutput = ipcOutput
|
outState.ipcOutput = ipcOutput
|
||||||
m.outputsMutex.Unlock()
|
|
||||||
|
|
||||||
ipcOutput.SetActiveHandler(func(e dwl_ipc.ZdwlIpcOutputV2ActiveEvent) {
|
ipcOutput.SetActiveHandler(func(e dwl_ipc.ZdwlIpcOutputV2ActiveEvent) {
|
||||||
outState.active = e.Active
|
outState.active = e.Active
|
||||||
@@ -292,6 +282,14 @@ func (m *Manager) setupOutput(manager *dwl_ipc.ZdwlIpcManagerV2, output *wlclien
|
|||||||
outState.layoutSymbol = e.Layout
|
outState.layoutSymbol = e.Layout
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ipcOutput.SetKbLayoutHandler(func(e dwl_ipc.ZdwlIpcOutputV2KbLayoutEvent) {
|
||||||
|
outState.kbLayout = e.KbLayout
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcOutput.SetKeymodeHandler(func(e dwl_ipc.ZdwlIpcOutputV2KeymodeEvent) {
|
||||||
|
outState.keymode = e.Keymode
|
||||||
|
})
|
||||||
|
|
||||||
ipcOutput.SetFrameHandler(func(e dwl_ipc.ZdwlIpcOutputV2FrameEvent) {
|
ipcOutput.SetFrameHandler(func(e dwl_ipc.ZdwlIpcOutputV2FrameEvent) {
|
||||||
m.updateState()
|
m.updateState()
|
||||||
})
|
})
|
||||||
@@ -300,11 +298,10 @@ func (m *Manager) setupOutput(manager *dwl_ipc.ZdwlIpcManagerV2, output *wlclien
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) updateState() {
|
func (m *Manager) updateState() {
|
||||||
m.outputsMutex.RLock()
|
|
||||||
outputs := make(map[string]*OutputState)
|
outputs := make(map[string]*OutputState)
|
||||||
activeOutput := ""
|
activeOutput := ""
|
||||||
|
|
||||||
for _, out := range m.outputs {
|
m.outputs.Range(func(key uint32, out *outputState) bool {
|
||||||
name := out.name
|
name := out.name
|
||||||
if name == "" {
|
if name == "" {
|
||||||
name = fmt.Sprintf("output-%d", out.id)
|
name = fmt.Sprintf("output-%d", out.id)
|
||||||
@@ -321,13 +318,15 @@ func (m *Manager) updateState() {
|
|||||||
LayoutSymbol: out.layoutSymbol,
|
LayoutSymbol: out.layoutSymbol,
|
||||||
Title: out.title,
|
Title: out.title,
|
||||||
AppID: out.appID,
|
AppID: out.appID,
|
||||||
|
KbLayout: out.kbLayout,
|
||||||
|
Keymode: out.keymode,
|
||||||
}
|
}
|
||||||
|
|
||||||
if out.active != 0 {
|
if out.active != 0 {
|
||||||
activeOutput = name
|
activeOutput = name
|
||||||
}
|
}
|
||||||
}
|
return true
|
||||||
m.outputsMutex.RUnlock()
|
})
|
||||||
|
|
||||||
newState := State{
|
newState := State{
|
||||||
Outputs: outputs,
|
Outputs: outputs,
|
||||||
@@ -365,14 +364,6 @@ func (m *Manager) notifier() {
|
|||||||
if !pending {
|
if !pending {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
m.subMutex.RLock()
|
|
||||||
subCount := len(m.subscribers)
|
|
||||||
m.subMutex.RUnlock()
|
|
||||||
|
|
||||||
if subCount == 0 {
|
|
||||||
pending = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
currentState := m.GetState()
|
currentState := m.GetState()
|
||||||
|
|
||||||
@@ -381,15 +372,14 @@ func (m *Manager) notifier() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
m.subMutex.RLock()
|
m.subscribers.Range(func(key string, ch chan State) bool {
|
||||||
for _, ch := range m.subscribers {
|
|
||||||
select {
|
select {
|
||||||
case ch <- currentState:
|
case ch <- currentState:
|
||||||
default:
|
default:
|
||||||
log.Warn("DWL: subscriber channel full, dropping update")
|
log.Warn("DWL: subscriber channel full, dropping update")
|
||||||
}
|
}
|
||||||
}
|
return true
|
||||||
m.subMutex.RUnlock()
|
})
|
||||||
|
|
||||||
stateCopy := currentState
|
stateCopy := currentState
|
||||||
m.lastNotified = &stateCopy
|
m.lastNotified = &stateCopy
|
||||||
@@ -407,11 +397,9 @@ func (m *Manager) ensureOutputSetup(out *outputState) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) SetTags(outputName string, tagmask uint32, toggleTagset uint32) error {
|
func (m *Manager) SetTags(outputName string, tagmask uint32, toggleTagset uint32) error {
|
||||||
m.outputsMutex.RLock()
|
availableOutputs := make([]string, 0)
|
||||||
|
|
||||||
availableOutputs := make([]string, 0, len(m.outputs))
|
|
||||||
var targetOut *outputState
|
var targetOut *outputState
|
||||||
for _, out := range m.outputs {
|
m.outputs.Range(func(key uint32, out *outputState) bool {
|
||||||
name := out.name
|
name := out.name
|
||||||
if name == "" {
|
if name == "" {
|
||||||
name = fmt.Sprintf("output-%d", out.id)
|
name = fmt.Sprintf("output-%d", out.id)
|
||||||
@@ -419,10 +407,10 @@ func (m *Manager) SetTags(outputName string, tagmask uint32, toggleTagset uint32
|
|||||||
availableOutputs = append(availableOutputs, name)
|
availableOutputs = append(availableOutputs, name)
|
||||||
if name == outputName {
|
if name == outputName {
|
||||||
targetOut = out
|
targetOut = out
|
||||||
break
|
return false
|
||||||
}
|
}
|
||||||
}
|
return true
|
||||||
m.outputsMutex.RUnlock()
|
})
|
||||||
|
|
||||||
if targetOut == nil {
|
if targetOut == nil {
|
||||||
return fmt.Errorf("output not found: %s (available: %v)", outputName, availableOutputs)
|
return fmt.Errorf("output not found: %s (available: %v)", outputName, availableOutputs)
|
||||||
@@ -444,20 +432,18 @@ func (m *Manager) SetTags(outputName string, tagmask uint32, toggleTagset uint32
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) SetClientTags(outputName string, andTags uint32, xorTags uint32) error {
|
func (m *Manager) SetClientTags(outputName string, andTags uint32, xorTags uint32) error {
|
||||||
m.outputsMutex.RLock()
|
|
||||||
|
|
||||||
var targetOut *outputState
|
var targetOut *outputState
|
||||||
for _, out := range m.outputs {
|
m.outputs.Range(func(key uint32, out *outputState) bool {
|
||||||
name := out.name
|
name := out.name
|
||||||
if name == "" {
|
if name == "" {
|
||||||
name = fmt.Sprintf("output-%d", out.id)
|
name = fmt.Sprintf("output-%d", out.id)
|
||||||
}
|
}
|
||||||
if name == outputName {
|
if name == outputName {
|
||||||
targetOut = out
|
targetOut = out
|
||||||
break
|
return false
|
||||||
}
|
}
|
||||||
}
|
return true
|
||||||
m.outputsMutex.RUnlock()
|
})
|
||||||
|
|
||||||
if targetOut == nil {
|
if targetOut == nil {
|
||||||
return fmt.Errorf("output not found: %s", outputName)
|
return fmt.Errorf("output not found: %s", outputName)
|
||||||
@@ -479,20 +465,18 @@ func (m *Manager) SetClientTags(outputName string, andTags uint32, xorTags uint3
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) SetLayout(outputName string, index uint32) error {
|
func (m *Manager) SetLayout(outputName string, index uint32) error {
|
||||||
m.outputsMutex.RLock()
|
|
||||||
|
|
||||||
var targetOut *outputState
|
var targetOut *outputState
|
||||||
for _, out := range m.outputs {
|
m.outputs.Range(func(key uint32, out *outputState) bool {
|
||||||
name := out.name
|
name := out.name
|
||||||
if name == "" {
|
if name == "" {
|
||||||
name = fmt.Sprintf("output-%d", out.id)
|
name = fmt.Sprintf("output-%d", out.id)
|
||||||
}
|
}
|
||||||
if name == outputName {
|
if name == outputName {
|
||||||
targetOut = out
|
targetOut = out
|
||||||
break
|
return false
|
||||||
}
|
}
|
||||||
}
|
return true
|
||||||
m.outputsMutex.RUnlock()
|
})
|
||||||
|
|
||||||
if targetOut == nil {
|
if targetOut == nil {
|
||||||
return fmt.Errorf("output not found: %s", outputName)
|
return fmt.Errorf("output not found: %s", outputName)
|
||||||
@@ -518,21 +502,19 @@ func (m *Manager) Close() {
|
|||||||
m.wg.Wait()
|
m.wg.Wait()
|
||||||
m.notifierWg.Wait()
|
m.notifierWg.Wait()
|
||||||
|
|
||||||
m.subMutex.Lock()
|
m.subscribers.Range(func(key string, ch chan State) bool {
|
||||||
for _, ch := range m.subscribers {
|
|
||||||
close(ch)
|
close(ch)
|
||||||
}
|
m.subscribers.Delete(key)
|
||||||
m.subscribers = make(map[string]chan State)
|
return true
|
||||||
m.subMutex.Unlock()
|
})
|
||||||
|
|
||||||
m.outputsMutex.Lock()
|
m.outputs.Range(func(key uint32, out *outputState) bool {
|
||||||
for _, out := range m.outputs {
|
|
||||||
if ipcOut, ok := out.ipcOutput.(*dwl_ipc.ZdwlIpcOutputV2); ok {
|
if ipcOut, ok := out.ipcOutput.(*dwl_ipc.ZdwlIpcOutputV2); ok {
|
||||||
ipcOut.Release()
|
ipcOut.Release()
|
||||||
}
|
}
|
||||||
}
|
m.outputs.Delete(key)
|
||||||
m.outputs = make(map[uint32]*outputState)
|
return true
|
||||||
m.outputsMutex.Unlock()
|
})
|
||||||
|
|
||||||
if mgr, ok := m.manager.(*dwl_ipc.ZdwlIpcManagerV2); ok {
|
if mgr, ok := m.manager.(*dwl_ipc.ZdwlIpcManagerV2); ok {
|
||||||
mgr.Release()
|
mgr.Release()
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ package dwl
|
|||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
wlclient "github.com/yaslama/go-wayland/wayland/client"
|
wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TagState struct {
|
type TagState struct {
|
||||||
@@ -21,6 +22,8 @@ type OutputState struct {
|
|||||||
LayoutSymbol string `json:"layoutSymbol"`
|
LayoutSymbol string `json:"layoutSymbol"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
AppID string `json:"appId"`
|
AppID string `json:"appId"`
|
||||||
|
KbLayout string `json:"kbLayout"`
|
||||||
|
Keymode string `json:"keymode"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type State struct {
|
type State struct {
|
||||||
@@ -36,11 +39,11 @@ type cmd struct {
|
|||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
display *wlclient.Display
|
display *wlclient.Display
|
||||||
|
ctx *wlclient.Context
|
||||||
registry *wlclient.Registry
|
registry *wlclient.Registry
|
||||||
manager interface{}
|
manager interface{}
|
||||||
|
|
||||||
outputs map[uint32]*outputState
|
outputs syncmap.Map[uint32, *outputState]
|
||||||
outputsMutex sync.RWMutex
|
|
||||||
|
|
||||||
tagCount uint32
|
tagCount uint32
|
||||||
layouts []string
|
layouts []string
|
||||||
@@ -51,8 +54,7 @@ type Manager struct {
|
|||||||
stopChan chan struct{}
|
stopChan chan struct{}
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
|
|
||||||
subscribers map[string]chan State
|
subscribers syncmap.Map[string, chan State]
|
||||||
subMutex sync.RWMutex
|
|
||||||
dirty chan struct{}
|
dirty chan struct{}
|
||||||
notifierWg sync.WaitGroup
|
notifierWg sync.WaitGroup
|
||||||
lastNotified *State
|
lastNotified *State
|
||||||
@@ -73,6 +75,8 @@ type outputState struct {
|
|||||||
layoutSymbol string
|
layoutSymbol string
|
||||||
title string
|
title string
|
||||||
appID string
|
appID string
|
||||||
|
kbLayout string
|
||||||
|
keymode string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) GetState() State {
|
func (m *Manager) GetState() State {
|
||||||
@@ -91,19 +95,16 @@ func (m *Manager) GetState() State {
|
|||||||
|
|
||||||
func (m *Manager) Subscribe(id string) chan State {
|
func (m *Manager) Subscribe(id string) chan State {
|
||||||
ch := make(chan State, 64)
|
ch := make(chan State, 64)
|
||||||
m.subMutex.Lock()
|
|
||||||
m.subscribers[id] = ch
|
m.subscribers.Store(id, ch)
|
||||||
m.subMutex.Unlock()
|
|
||||||
return ch
|
return ch
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Unsubscribe(id string) {
|
func (m *Manager) Unsubscribe(id string) {
|
||||||
m.subMutex.Lock()
|
if val, ok := m.subscribers.LoadAndDelete(id); ok {
|
||||||
if ch, ok := m.subscribers[id]; ok {
|
close(val)
|
||||||
close(ch)
|
|
||||||
delete(m.subscribers, id)
|
|
||||||
}
|
}
|
||||||
m.subMutex.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) notifySubscribers() {
|
func (m *Manager) notifySubscribers() {
|
||||||
@@ -150,6 +151,12 @@ func stateChanged(old, new *State) bool {
|
|||||||
if oldOut.AppID != newOut.AppID {
|
if oldOut.AppID != newOut.AppID {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if oldOut.KbLayout != newOut.KbLayout {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if oldOut.Keymode != newOut.Keymode {
|
||||||
|
return true
|
||||||
|
}
|
||||||
if len(oldOut.Tags) != len(newOut.Tags) {
|
if len(oldOut.Tags) != len(newOut.Tags) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
27
core/internal/server/evdev/handlers.go
Normal file
27
core/internal/server/evdev/handlers.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package evdev
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Request struct {
|
||||||
|
ID interface{} `json:"id"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
Params map[string]interface{} `json:"params"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleRequest(conn net.Conn, req Request, m *Manager) {
|
||||||
|
switch req.Method {
|
||||||
|
case "evdev.getState":
|
||||||
|
handleGetState(conn, req, m)
|
||||||
|
default:
|
||||||
|
models.RespondError(conn, req.ID.(int), "unknown method: "+req.Method)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleGetState(conn net.Conn, req Request, m *Manager) {
|
||||||
|
state := m.GetState()
|
||||||
|
models.Respond(conn, req.ID.(int), state)
|
||||||
|
}
|
||||||
130
core/internal/server/evdev/handlers_test.go
Normal file
130
core/internal/server/evdev/handlers_test.go
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
package evdev
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
mocks "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/evdev"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockNetConn struct {
|
||||||
|
net.Conn
|
||||||
|
readBuf *bytes.Buffer
|
||||||
|
writeBuf *bytes.Buffer
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMockNetConn() *mockNetConn {
|
||||||
|
return &mockNetConn{
|
||||||
|
readBuf: &bytes.Buffer{},
|
||||||
|
writeBuf: &bytes.Buffer{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockNetConn) Read(b []byte) (n int, err error) {
|
||||||
|
return m.readBuf.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockNetConn) Write(b []byte) (n int, err error) {
|
||||||
|
return m.writeBuf.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockNetConn) Close() error {
|
||||||
|
m.closed = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleRequest(t *testing.T) {
|
||||||
|
t.Run("getState request", func(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe()
|
||||||
|
|
||||||
|
m := &Manager{
|
||||||
|
devices: []EvdevDevice{mockDevice},
|
||||||
|
state: State{Available: true, CapsLock: true},
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := newMockNetConn()
|
||||||
|
req := Request{
|
||||||
|
ID: 123,
|
||||||
|
Method: "evdev.getState",
|
||||||
|
Params: map[string]interface{}{},
|
||||||
|
}
|
||||||
|
|
||||||
|
HandleRequest(conn, req, m)
|
||||||
|
|
||||||
|
var resp models.Response[State]
|
||||||
|
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 123, resp.ID)
|
||||||
|
assert.NotNil(t, resp.Result)
|
||||||
|
assert.True(t, resp.Result.Available)
|
||||||
|
assert.True(t, resp.Result.CapsLock)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("unknown method", func(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe()
|
||||||
|
|
||||||
|
m := &Manager{
|
||||||
|
devices: []EvdevDevice{mockDevice},
|
||||||
|
state: State{Available: true, CapsLock: false},
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := newMockNetConn()
|
||||||
|
req := Request{
|
||||||
|
ID: 456,
|
||||||
|
Method: "evdev.unknownMethod",
|
||||||
|
Params: map[string]interface{}{},
|
||||||
|
}
|
||||||
|
|
||||||
|
HandleRequest(conn, req, m)
|
||||||
|
|
||||||
|
var resp models.Response[any]
|
||||||
|
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 456, resp.ID)
|
||||||
|
assert.NotEmpty(t, resp.Error)
|
||||||
|
assert.Contains(t, resp.Error, "unknown method")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleGetState(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe()
|
||||||
|
|
||||||
|
m := &Manager{
|
||||||
|
devices: []EvdevDevice{mockDevice},
|
||||||
|
state: State{Available: true, CapsLock: false},
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := newMockNetConn()
|
||||||
|
req := Request{
|
||||||
|
ID: 789,
|
||||||
|
Method: "evdev.getState",
|
||||||
|
Params: map[string]interface{}{},
|
||||||
|
}
|
||||||
|
|
||||||
|
handleGetState(conn, req, m)
|
||||||
|
|
||||||
|
var resp models.Response[State]
|
||||||
|
err := json.NewDecoder(conn.writeBuf).Decode(&resp)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 789, resp.ID)
|
||||||
|
assert.NotNil(t, resp.Result)
|
||||||
|
assert.True(t, resp.Result.Available)
|
||||||
|
assert.False(t, resp.Result.CapsLock)
|
||||||
|
}
|
||||||
404
core/internal/server/evdev/manager.go
Normal file
404
core/internal/server/evdev/manager.go
Normal file
@@ -0,0 +1,404 @@
|
|||||||
|
package evdev
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
evdev "github.com/holoplot/go-evdev"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
evKeyType = 0x01
|
||||||
|
evLedType = 0x11
|
||||||
|
keyCapslockKey = 58
|
||||||
|
ledCapslockKey = 1
|
||||||
|
keyStateOn = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
type EvdevDevice interface {
|
||||||
|
Name() (string, error)
|
||||||
|
Path() string
|
||||||
|
Close() error
|
||||||
|
ReadOne() (*evdev.InputEvent, error)
|
||||||
|
State(t evdev.EvType) (evdev.StateMap, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Manager struct {
|
||||||
|
devices []EvdevDevice
|
||||||
|
devicesMutex sync.RWMutex
|
||||||
|
monitoredPaths map[string]bool
|
||||||
|
state State
|
||||||
|
stateMutex sync.RWMutex
|
||||||
|
subscribers syncmap.Map[string, chan State]
|
||||||
|
closeChan chan struct{}
|
||||||
|
closeOnce sync.Once
|
||||||
|
watcher *fsnotify.Watcher
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewManager() (*Manager, error) {
|
||||||
|
devices, err := findKeyboards()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to find keyboards: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
initialCapsLock := readInitialCapsLockState(devices[0])
|
||||||
|
|
||||||
|
watcher, err := fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Failed to create fsnotify watcher, hotplug detection disabled: %v", err)
|
||||||
|
watcher = nil
|
||||||
|
} else if err := watcher.Add("/dev/input"); err != nil {
|
||||||
|
log.Warnf("Failed to watch /dev/input, hotplug detection disabled: %v", err)
|
||||||
|
watcher.Close()
|
||||||
|
watcher = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
monitoredPaths := make(map[string]bool)
|
||||||
|
for _, device := range devices {
|
||||||
|
monitoredPaths[device.Path()] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
m := &Manager{
|
||||||
|
devices: devices,
|
||||||
|
monitoredPaths: monitoredPaths,
|
||||||
|
state: State{Available: true, CapsLock: initialCapsLock},
|
||||||
|
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
watcher: watcher,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, device := range devices {
|
||||||
|
go m.monitorDevice(device, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
if watcher != nil {
|
||||||
|
go m.watchForNewKeyboards()
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readInitialCapsLockState(device EvdevDevice) bool {
|
||||||
|
ledStates, err := device.State(evLedType)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("Could not read LED state: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return ledStates[ledCapslockKey]
|
||||||
|
}
|
||||||
|
|
||||||
|
func findKeyboards() ([]EvdevDevice, error) {
|
||||||
|
pattern := "/dev/input/event*"
|
||||||
|
matches, err := filepath.Glob(pattern)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to glob input devices: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(matches) == 0 {
|
||||||
|
return nil, fmt.Errorf("no input devices found")
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyboards []EvdevDevice
|
||||||
|
for _, path := range matches {
|
||||||
|
device, err := evdev.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isKeyboard(device) {
|
||||||
|
device.Close()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceName, _ := device.Name()
|
||||||
|
log.Debugf("Found keyboard: %s at %s", deviceName, path)
|
||||||
|
keyboards = append(keyboards, device)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(keyboards) == 0 {
|
||||||
|
return nil, fmt.Errorf("no keyboard device found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyboards, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isKeyboard(device EvdevDevice) bool {
|
||||||
|
deviceName, err := device.Name()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
name := strings.ToLower(deviceName)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case strings.Contains(name, "keyboard"):
|
||||||
|
return true
|
||||||
|
case strings.Contains(name, "kbd"):
|
||||||
|
return true
|
||||||
|
case strings.Contains(name, "input") && strings.Contains(name, "key"):
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
keyStates, err := device.State(evKeyType)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
hasKeyA := len(keyStates) > 30
|
||||||
|
hasKeyZ := len(keyStates) > 44
|
||||||
|
hasEnter := len(keyStates) > 28
|
||||||
|
|
||||||
|
return hasKeyA && hasKeyZ && hasEnter && len(keyStates) > 100
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) watchForNewKeyboards() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
log.Errorf("Panic in keyboard hotplug monitor: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-m.closeChan:
|
||||||
|
return
|
||||||
|
case event, ok := <-m.watcher.Events:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(filepath.Base(event.Name), "event") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.Op&fsnotify.Create == fsnotify.Create {
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
m.devicesMutex.Lock()
|
||||||
|
if m.monitoredPaths[event.Name] {
|
||||||
|
m.devicesMutex.Unlock()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
device, err := evdev.Open(event.Name)
|
||||||
|
if err != nil {
|
||||||
|
m.devicesMutex.Unlock()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isKeyboard(device) {
|
||||||
|
device.Close()
|
||||||
|
m.devicesMutex.Unlock()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceName, _ := device.Name()
|
||||||
|
log.Debugf("Hotplugged keyboard: %s at %s", deviceName, event.Name)
|
||||||
|
|
||||||
|
m.devices = append(m.devices, device)
|
||||||
|
m.monitoredPaths[event.Name] = true
|
||||||
|
deviceIndex := len(m.devices) - 1
|
||||||
|
m.devicesMutex.Unlock()
|
||||||
|
|
||||||
|
go m.monitorDevice(device, deviceIndex)
|
||||||
|
} else if event.Op&fsnotify.Remove == fsnotify.Remove {
|
||||||
|
m.devicesMutex.Lock()
|
||||||
|
if !m.monitoredPaths[event.Name] {
|
||||||
|
m.devicesMutex.Unlock()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(m.monitoredPaths, event.Name)
|
||||||
|
for i, device := range m.devices {
|
||||||
|
if device != nil && device.Path() == event.Name {
|
||||||
|
log.Debugf("Keyboard removed: %s", event.Name)
|
||||||
|
device.Close()
|
||||||
|
m.devices[i] = nil
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.devicesMutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
case err, ok := <-m.watcher.Errors:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Warnf("Keyboard hotplug watcher error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) monitorDevice(device EvdevDevice, deviceIndex int) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
log.Errorf("Panic in evdev monitor: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-m.closeChan:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
event, err := device.ReadOne()
|
||||||
|
if err != nil {
|
||||||
|
if isClosedError(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Warnf("Failed to read evdev event: %v", err)
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if event == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.Type == evKeyType && event.Code == keyCapslockKey && event.Value == keyStateOn {
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
m.readAndUpdateCapsLockState(deviceIndex)
|
||||||
|
} else if event.Type == evLedType && event.Code == ledCapslockKey {
|
||||||
|
capsLockState := event.Value == keyStateOn
|
||||||
|
m.updateCapsLockStateDirect(capsLockState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isClosedError(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
errStr := err.Error()
|
||||||
|
switch {
|
||||||
|
case strings.Contains(errStr, "closed"):
|
||||||
|
return true
|
||||||
|
case strings.Contains(errStr, "bad file descriptor"):
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) readAndUpdateCapsLockState(deviceIndex int) {
|
||||||
|
m.devicesMutex.RLock()
|
||||||
|
if deviceIndex >= len(m.devices) {
|
||||||
|
m.devicesMutex.RUnlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
device := m.devices[deviceIndex]
|
||||||
|
m.devicesMutex.RUnlock()
|
||||||
|
|
||||||
|
ledStates, err := device.State(evLedType)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Failed to read LED state: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
capsLockState := ledStates[ledCapslockKey]
|
||||||
|
m.updateCapsLockStateDirect(capsLockState)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) updateCapsLockStateDirect(capsLockState bool) {
|
||||||
|
m.stateMutex.Lock()
|
||||||
|
if m.state.CapsLock == capsLockState {
|
||||||
|
m.stateMutex.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m.state.CapsLock = capsLockState
|
||||||
|
newState := m.state
|
||||||
|
m.stateMutex.Unlock()
|
||||||
|
|
||||||
|
log.Debugf("Caps lock state: %v", newState.CapsLock)
|
||||||
|
m.notifySubscribers(newState)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) GetState() State {
|
||||||
|
m.stateMutex.RLock()
|
||||||
|
defer m.stateMutex.RUnlock()
|
||||||
|
return m.state
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Subscribe(id string) chan State {
|
||||||
|
ch := make(chan State, 16)
|
||||||
|
m.subscribers.Store(id, ch)
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Unsubscribe(id string) {
|
||||||
|
if val, ok := m.subscribers.LoadAndDelete(id); ok {
|
||||||
|
close(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) notifySubscribers(state State) {
|
||||||
|
m.subscribers.Range(func(key string, ch chan State) bool {
|
||||||
|
select {
|
||||||
|
case ch <- state:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Close() {
|
||||||
|
m.closeOnce.Do(func() {
|
||||||
|
close(m.closeChan)
|
||||||
|
|
||||||
|
if m.watcher != nil {
|
||||||
|
m.watcher.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
m.devicesMutex.Lock()
|
||||||
|
for _, device := range m.devices {
|
||||||
|
if device == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := device.Close(); err != nil && !isClosedError(err) {
|
||||||
|
log.Warnf("Error closing evdev device: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.devicesMutex.Unlock()
|
||||||
|
|
||||||
|
m.subscribers.Range(func(key string, ch chan State) bool {
|
||||||
|
close(ch)
|
||||||
|
m.subscribers.Delete(key)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitializeManager() (*Manager, error) {
|
||||||
|
if os.Getuid() != 0 && !hasInputGroupAccess() {
|
||||||
|
return nil, fmt.Errorf("insufficient permissions to access input devices")
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewManager()
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasInputGroupAccess() bool {
|
||||||
|
pattern := "/dev/input/event*"
|
||||||
|
matches, err := filepath.Glob(pattern)
|
||||||
|
if err != nil || len(matches) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
testFile, err := os.Open(matches[0])
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
testFile.Close()
|
||||||
|
return true
|
||||||
|
}
|
||||||
344
core/internal/server/evdev/manager_test.go
Normal file
344
core/internal/server/evdev/manager_test.go
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
package evdev
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
evdev "github.com/holoplot/go-evdev"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
mocks "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/evdev"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestManager_Creation(t *testing.T) {
|
||||||
|
t.Run("manager created successfully with caps lock off", func(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe()
|
||||||
|
|
||||||
|
m := &Manager{
|
||||||
|
devices: []EvdevDevice{mockDevice},
|
||||||
|
state: State{Available: true, CapsLock: false},
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NotNil(t, m)
|
||||||
|
assert.True(t, m.state.Available)
|
||||||
|
assert.False(t, m.state.CapsLock)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("manager created successfully with caps lock on", func(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe()
|
||||||
|
|
||||||
|
m := &Manager{
|
||||||
|
devices: []EvdevDevice{mockDevice},
|
||||||
|
state: State{Available: true, CapsLock: true},
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NotNil(t, m)
|
||||||
|
assert.True(t, m.state.Available)
|
||||||
|
assert.True(t, m.state.CapsLock)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManager_GetState(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe()
|
||||||
|
|
||||||
|
m := &Manager{
|
||||||
|
devices: []EvdevDevice{mockDevice},
|
||||||
|
monitoredPaths: make(map[string]bool),
|
||||||
|
state: State{Available: true, CapsLock: false},
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
state := m.GetState()
|
||||||
|
assert.True(t, state.Available)
|
||||||
|
assert.False(t, state.CapsLock)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManager_Subscribe(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe()
|
||||||
|
|
||||||
|
m := &Manager{
|
||||||
|
devices: []EvdevDevice{mockDevice},
|
||||||
|
monitoredPaths: make(map[string]bool),
|
||||||
|
state: State{Available: true, CapsLock: false},
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := m.Subscribe("test-client")
|
||||||
|
assert.NotNil(t, ch)
|
||||||
|
count := 0
|
||||||
|
m.subscribers.Range(func(key string, ch chan State) bool {
|
||||||
|
count++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
assert.Equal(t, 1, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManager_Unsubscribe(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe()
|
||||||
|
|
||||||
|
m := &Manager{
|
||||||
|
devices: []EvdevDevice{mockDevice},
|
||||||
|
monitoredPaths: make(map[string]bool),
|
||||||
|
state: State{Available: true, CapsLock: false},
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := m.Subscribe("test-client")
|
||||||
|
count := 0
|
||||||
|
m.subscribers.Range(func(key string, ch chan State) bool {
|
||||||
|
count++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
assert.Equal(t, 1, count)
|
||||||
|
|
||||||
|
m.Unsubscribe("test-client")
|
||||||
|
count = 0
|
||||||
|
m.subscribers.Range(func(key string, ch chan State) bool {
|
||||||
|
count++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
assert.Equal(t, 0, count)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case _, ok := <-ch:
|
||||||
|
assert.False(t, ok, "channel should be closed")
|
||||||
|
default:
|
||||||
|
t.Error("channel should be closed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManager_UpdateCapsLock(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe()
|
||||||
|
|
||||||
|
m := &Manager{
|
||||||
|
devices: []EvdevDevice{mockDevice},
|
||||||
|
monitoredPaths: make(map[string]bool),
|
||||||
|
state: State{Available: true, CapsLock: false},
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := m.Subscribe("test-client")
|
||||||
|
|
||||||
|
ledStateOn := evdev.StateMap{ledCapslockKey: true}
|
||||||
|
mockDevice.EXPECT().State(evdev.EvType(evLedType)).Return(ledStateOn, nil).Once()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
m.readAndUpdateCapsLockState(0)
|
||||||
|
}()
|
||||||
|
|
||||||
|
newState := <-ch
|
||||||
|
assert.True(t, newState.CapsLock)
|
||||||
|
|
||||||
|
ledStateOff := evdev.StateMap{ledCapslockKey: false}
|
||||||
|
mockDevice.EXPECT().State(evdev.EvType(evLedType)).Return(ledStateOff, nil).Once()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
m.readAndUpdateCapsLockState(0)
|
||||||
|
}()
|
||||||
|
|
||||||
|
newState = <-ch
|
||||||
|
assert.False(t, newState.CapsLock)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManager_Close(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
mockDevice.EXPECT().Close().Return(nil).Once()
|
||||||
|
mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe()
|
||||||
|
|
||||||
|
m := &Manager{
|
||||||
|
devices: []EvdevDevice{mockDevice},
|
||||||
|
monitoredPaths: make(map[string]bool),
|
||||||
|
state: State{Available: true, CapsLock: false},
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
ch1 := m.Subscribe("client1")
|
||||||
|
ch2 := m.Subscribe("client2")
|
||||||
|
|
||||||
|
m.Close()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case _, ok := <-ch1:
|
||||||
|
assert.False(t, ok, "channel 1 should be closed")
|
||||||
|
default:
|
||||||
|
t.Error("channel 1 should be closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case _, ok := <-ch2:
|
||||||
|
assert.False(t, ok, "channel 2 should be closed")
|
||||||
|
default:
|
||||||
|
t.Error("channel 2 should be closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
count := 0
|
||||||
|
m.subscribers.Range(func(key string, ch chan State) bool {
|
||||||
|
count++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
assert.Equal(t, 0, count)
|
||||||
|
|
||||||
|
m.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsKeyboard(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
devName string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"keyboard in name", "AT Translated Set 2 keyboard", true},
|
||||||
|
{"kbd in name", "USB kbd", true},
|
||||||
|
{"input and key", "input key device", true},
|
||||||
|
{"random device", "Mouse", false},
|
||||||
|
{"empty name", "", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
mockDevice.EXPECT().Name().Return(tt.devName, nil).Once()
|
||||||
|
|
||||||
|
if !tt.expected {
|
||||||
|
mockDevice.EXPECT().State(evdev.EvType(evKeyType)).Return(evdev.StateMap{}, nil).Maybe()
|
||||||
|
}
|
||||||
|
|
||||||
|
result := isKeyboard(mockDevice)
|
||||||
|
assert.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsKeyboard_ErrorHandling(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
mockDevice.EXPECT().Name().Return("", errors.New("device error")).Once()
|
||||||
|
|
||||||
|
result := isKeyboard(mockDevice)
|
||||||
|
assert.False(t, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManager_MonitorDevice(t *testing.T) {
|
||||||
|
t.Run("caps lock key press updates state", func(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
|
||||||
|
capsLockEvent := &evdev.InputEvent{
|
||||||
|
Type: evKeyType,
|
||||||
|
Code: keyCapslockKey,
|
||||||
|
Value: keyStateOn,
|
||||||
|
}
|
||||||
|
|
||||||
|
ledState := evdev.StateMap{ledCapslockKey: true}
|
||||||
|
|
||||||
|
mockDevice.EXPECT().ReadOne().Return(capsLockEvent, nil).Once()
|
||||||
|
mockDevice.EXPECT().State(evdev.EvType(evLedType)).Return(ledState, nil).Once()
|
||||||
|
mockDevice.EXPECT().ReadOne().Return(nil, errors.New("stop")).Maybe()
|
||||||
|
mockDevice.EXPECT().Close().Return(nil).Maybe()
|
||||||
|
|
||||||
|
m := &Manager{
|
||||||
|
devices: []EvdevDevice{mockDevice},
|
||||||
|
state: State{Available: true, CapsLock: false},
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := m.Subscribe("test")
|
||||||
|
|
||||||
|
go m.monitorDevice(mockDevice, 0)
|
||||||
|
|
||||||
|
state := <-ch
|
||||||
|
assert.True(t, state.CapsLock)
|
||||||
|
|
||||||
|
m.Close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsClosedError(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"nil error", nil, false},
|
||||||
|
{"closed error", errors.New("device closed"), true},
|
||||||
|
{"bad file descriptor", errors.New("bad file descriptor"), true},
|
||||||
|
{"other error", errors.New("some other error"), false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := isClosedError(tt.err)
|
||||||
|
assert.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNotifySubscribers(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe()
|
||||||
|
mockDevice.EXPECT().Close().Return(nil).Maybe()
|
||||||
|
|
||||||
|
m := &Manager{
|
||||||
|
devices: []EvdevDevice{mockDevice},
|
||||||
|
monitoredPaths: make(map[string]bool),
|
||||||
|
state: State{Available: true, CapsLock: false},
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
ch1 := m.Subscribe("client1")
|
||||||
|
ch2 := m.Subscribe("client2")
|
||||||
|
|
||||||
|
newState := State{Available: true, CapsLock: true}
|
||||||
|
go m.notifySubscribers(newState)
|
||||||
|
|
||||||
|
state1 := <-ch1
|
||||||
|
state2 := <-ch2
|
||||||
|
|
||||||
|
assert.Equal(t, newState, state1)
|
||||||
|
assert.Equal(t, newState, state2)
|
||||||
|
|
||||||
|
m.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadInitialCapsLockState(t *testing.T) {
|
||||||
|
t.Run("caps lock is on", func(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
ledState := evdev.StateMap{
|
||||||
|
ledCapslockKey: true,
|
||||||
|
}
|
||||||
|
mockDevice.EXPECT().State(evdev.EvType(evLedType)).Return(ledState, nil).Once()
|
||||||
|
|
||||||
|
result := readInitialCapsLockState(mockDevice)
|
||||||
|
assert.True(t, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("caps lock is off", func(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
ledState := evdev.StateMap{
|
||||||
|
ledCapslockKey: false,
|
||||||
|
}
|
||||||
|
mockDevice.EXPECT().State(evdev.EvType(evLedType)).Return(ledState, nil).Once()
|
||||||
|
|
||||||
|
result := readInitialCapsLockState(mockDevice)
|
||||||
|
assert.False(t, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("error reading LED state", func(t *testing.T) {
|
||||||
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
mockDevice.EXPECT().State(evdev.EvType(evLedType)).Return(nil, errors.New("read error")).Once()
|
||||||
|
|
||||||
|
result := readInitialCapsLockState(mockDevice)
|
||||||
|
assert.False(t, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHasInputGroupAccess(t *testing.T) {
|
||||||
|
result := hasInputGroupAccess()
|
||||||
|
t.Logf("hasInputGroupAccess: %v", result)
|
||||||
|
}
|
||||||
6
core/internal/server/evdev/models.go
Normal file
6
core/internal/server/evdev/models.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package evdev
|
||||||
|
|
||||||
|
type State struct {
|
||||||
|
Available bool `json:"available"`
|
||||||
|
CapsLock bool `json:"capsLock"`
|
||||||
|
}
|
||||||
@@ -6,19 +6,45 @@ import (
|
|||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/ext_workspace"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/ext_workspace"
|
||||||
wlclient "github.com/yaslama/go-wayland/wayland/client"
|
wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func CheckCapability() bool {
|
||||||
|
display, err := wlclient.Connect("")
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer display.Destroy()
|
||||||
|
|
||||||
|
registry, err := display.GetRegistry()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer registry.Destroy()
|
||||||
|
|
||||||
|
found := false
|
||||||
|
|
||||||
|
registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) {
|
||||||
|
if e.Interface == ext_workspace.ExtWorkspaceManagerV1InterfaceName {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Roundtrip to ensure all registry events are processed
|
||||||
|
if err := display.Roundtrip(); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
func NewManager(display *wlclient.Display) (*Manager, error) {
|
func NewManager(display *wlclient.Display) (*Manager, error) {
|
||||||
m := &Manager{
|
m := &Manager{
|
||||||
display: display,
|
display: display,
|
||||||
outputs: make(map[uint32]*wlclient.Output),
|
ctx: display.Context(),
|
||||||
outputNames: make(map[uint32]string),
|
|
||||||
groups: make(map[uint32]*workspaceGroupState),
|
|
||||||
workspaces: make(map[uint32]*workspaceState),
|
|
||||||
cmdq: make(chan cmd, 128),
|
cmdq: make(chan cmd, 128),
|
||||||
stopChan: make(chan struct{}),
|
stopChan: make(chan struct{}),
|
||||||
subscribers: make(map[string]chan State),
|
|
||||||
dirty: make(chan struct{}, 1),
|
dirty: make(chan struct{}, 1),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +88,6 @@ func (m *Manager) waylandActor() {
|
|||||||
|
|
||||||
func (m *Manager) setupRegistry() error {
|
func (m *Manager) setupRegistry() error {
|
||||||
log.Info("ExtWorkspace: starting registry setup")
|
log.Info("ExtWorkspace: starting registry setup")
|
||||||
ctx := m.display.Context()
|
|
||||||
|
|
||||||
registry, err := m.display.GetRegistry()
|
registry, err := m.display.GetRegistry()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -72,15 +97,16 @@ func (m *Manager) setupRegistry() error {
|
|||||||
|
|
||||||
registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) {
|
registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) {
|
||||||
if e.Interface == "wl_output" {
|
if e.Interface == "wl_output" {
|
||||||
output := wlclient.NewOutput(ctx)
|
output := wlclient.NewOutput(m.ctx)
|
||||||
if err := registry.Bind(e.Name, e.Interface, 4, output); err == nil {
|
if err := registry.Bind(e.Name, e.Interface, 4, output); err == nil {
|
||||||
outputID := output.ID()
|
outputID := output.ID()
|
||||||
|
|
||||||
output.SetNameHandler(func(ev wlclient.OutputNameEvent) {
|
output.SetNameHandler(func(ev wlclient.OutputNameEvent) {
|
||||||
m.outputsMutex.Lock()
|
m.outputNames.Store(outputID, ev.Name)
|
||||||
m.outputNames[outputID] = ev.Name
|
|
||||||
m.outputsMutex.Unlock()
|
|
||||||
log.Debugf("ExtWorkspace: Output %d (%s) name received", outputID, ev.Name)
|
log.Debugf("ExtWorkspace: Output %d (%s) name received", outputID, ev.Name)
|
||||||
|
m.post(func() {
|
||||||
|
m.updateState()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -88,7 +114,7 @@ func (m *Manager) setupRegistry() error {
|
|||||||
|
|
||||||
if e.Interface == ext_workspace.ExtWorkspaceManagerV1InterfaceName {
|
if e.Interface == ext_workspace.ExtWorkspaceManagerV1InterfaceName {
|
||||||
log.Infof("ExtWorkspace: found %s", ext_workspace.ExtWorkspaceManagerV1InterfaceName)
|
log.Infof("ExtWorkspace: found %s", ext_workspace.ExtWorkspaceManagerV1InterfaceName)
|
||||||
manager := ext_workspace.NewExtWorkspaceManagerV1(ctx)
|
manager := ext_workspace.NewExtWorkspaceManagerV1(m.ctx)
|
||||||
version := e.Version
|
version := e.Version
|
||||||
if version > 1 {
|
if version > 1 {
|
||||||
version = 1
|
version = 1
|
||||||
@@ -139,9 +165,7 @@ func (m *Manager) handleWorkspaceGroup(e ext_workspace.ExtWorkspaceManagerV1Work
|
|||||||
workspaceIDs: make([]uint32, 0),
|
workspaceIDs: make([]uint32, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
m.groupsMutex.Lock()
|
m.groups.Store(groupID, group)
|
||||||
m.groups[groupID] = group
|
|
||||||
m.groupsMutex.Unlock()
|
|
||||||
|
|
||||||
handle.SetCapabilitiesHandler(func(e ext_workspace.ExtWorkspaceGroupHandleV1CapabilitiesEvent) {
|
handle.SetCapabilitiesHandler(func(e ext_workspace.ExtWorkspaceGroupHandleV1CapabilitiesEvent) {
|
||||||
log.Debugf("ExtWorkspace: Group %d capabilities: %d", groupID, e.Capabilities)
|
log.Debugf("ExtWorkspace: Group %d capabilities: %d", groupID, e.Capabilities)
|
||||||
@@ -151,9 +175,8 @@ func (m *Manager) handleWorkspaceGroup(e ext_workspace.ExtWorkspaceManagerV1Work
|
|||||||
outputID := e.Output.ID()
|
outputID := e.Output.ID()
|
||||||
log.Debugf("ExtWorkspace: Group %d output enter (output=%d)", groupID, outputID)
|
log.Debugf("ExtWorkspace: Group %d output enter (output=%d)", groupID, outputID)
|
||||||
|
|
||||||
group.outputIDs[outputID] = true
|
|
||||||
|
|
||||||
m.post(func() {
|
m.post(func() {
|
||||||
|
group.outputIDs[outputID] = true
|
||||||
m.updateState()
|
m.updateState()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -161,8 +184,8 @@ func (m *Manager) handleWorkspaceGroup(e ext_workspace.ExtWorkspaceManagerV1Work
|
|||||||
handle.SetOutputLeaveHandler(func(e ext_workspace.ExtWorkspaceGroupHandleV1OutputLeaveEvent) {
|
handle.SetOutputLeaveHandler(func(e ext_workspace.ExtWorkspaceGroupHandleV1OutputLeaveEvent) {
|
||||||
outputID := e.Output.ID()
|
outputID := e.Output.ID()
|
||||||
log.Debugf("ExtWorkspace: Group %d output leave (output=%d)", groupID, outputID)
|
log.Debugf("ExtWorkspace: Group %d output leave (output=%d)", groupID, outputID)
|
||||||
delete(group.outputIDs, outputID)
|
|
||||||
m.post(func() {
|
m.post(func() {
|
||||||
|
delete(group.outputIDs, outputID)
|
||||||
m.updateState()
|
m.updateState()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -171,14 +194,12 @@ func (m *Manager) handleWorkspaceGroup(e ext_workspace.ExtWorkspaceManagerV1Work
|
|||||||
workspaceID := e.Workspace.ID()
|
workspaceID := e.Workspace.ID()
|
||||||
log.Debugf("ExtWorkspace: Group %d workspace enter (workspace=%d)", groupID, workspaceID)
|
log.Debugf("ExtWorkspace: Group %d workspace enter (workspace=%d)", groupID, workspaceID)
|
||||||
|
|
||||||
m.workspacesMutex.Lock()
|
m.post(func() {
|
||||||
if ws, exists := m.workspaces[workspaceID]; exists {
|
if ws, ok := m.workspaces.Load(workspaceID); ok {
|
||||||
ws.groupID = groupID
|
ws.groupID = groupID
|
||||||
}
|
}
|
||||||
m.workspacesMutex.Unlock()
|
|
||||||
|
|
||||||
group.workspaceIDs = append(group.workspaceIDs, workspaceID)
|
group.workspaceIDs = append(group.workspaceIDs, workspaceID)
|
||||||
m.post(func() {
|
|
||||||
m.updateState()
|
m.updateState()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -187,11 +208,10 @@ func (m *Manager) handleWorkspaceGroup(e ext_workspace.ExtWorkspaceManagerV1Work
|
|||||||
workspaceID := e.Workspace.ID()
|
workspaceID := e.Workspace.ID()
|
||||||
log.Debugf("ExtWorkspace: Group %d workspace leave (workspace=%d)", groupID, workspaceID)
|
log.Debugf("ExtWorkspace: Group %d workspace leave (workspace=%d)", groupID, workspaceID)
|
||||||
|
|
||||||
m.workspacesMutex.Lock()
|
m.post(func() {
|
||||||
if ws, exists := m.workspaces[workspaceID]; exists {
|
if ws, ok := m.workspaces.Load(workspaceID); ok {
|
||||||
ws.groupID = 0
|
ws.groupID = 0
|
||||||
}
|
}
|
||||||
m.workspacesMutex.Unlock()
|
|
||||||
|
|
||||||
for i, id := range group.workspaceIDs {
|
for i, id := range group.workspaceIDs {
|
||||||
if id == workspaceID {
|
if id == workspaceID {
|
||||||
@@ -199,20 +219,18 @@ func (m *Manager) handleWorkspaceGroup(e ext_workspace.ExtWorkspaceManagerV1Work
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m.post(func() {
|
|
||||||
m.updateState()
|
m.updateState()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
handle.SetRemovedHandler(func(e ext_workspace.ExtWorkspaceGroupHandleV1RemovedEvent) {
|
handle.SetRemovedHandler(func(e ext_workspace.ExtWorkspaceGroupHandleV1RemovedEvent) {
|
||||||
log.Debugf("ExtWorkspace: Group %d removed", groupID)
|
log.Debugf("ExtWorkspace: Group %d removed", groupID)
|
||||||
group.removed = true
|
|
||||||
|
|
||||||
m.groupsMutex.Lock()
|
|
||||||
delete(m.groups, groupID)
|
|
||||||
m.groupsMutex.Unlock()
|
|
||||||
|
|
||||||
m.post(func() {
|
m.post(func() {
|
||||||
|
group.removed = true
|
||||||
|
|
||||||
|
m.groups.Delete(groupID)
|
||||||
|
|
||||||
m.wlMutex.Lock()
|
m.wlMutex.Lock()
|
||||||
handle.Destroy()
|
handle.Destroy()
|
||||||
m.wlMutex.Unlock()
|
m.wlMutex.Unlock()
|
||||||
@@ -234,22 +252,20 @@ func (m *Manager) handleWorkspace(e ext_workspace.ExtWorkspaceManagerV1Workspace
|
|||||||
coordinates: make([]uint32, 0),
|
coordinates: make([]uint32, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
m.workspacesMutex.Lock()
|
m.workspaces.Store(workspaceID, ws)
|
||||||
m.workspaces[workspaceID] = ws
|
|
||||||
m.workspacesMutex.Unlock()
|
|
||||||
|
|
||||||
handle.SetIdHandler(func(e ext_workspace.ExtWorkspaceHandleV1IdEvent) {
|
handle.SetIdHandler(func(e ext_workspace.ExtWorkspaceHandleV1IdEvent) {
|
||||||
log.Debugf("ExtWorkspace: Workspace %d id: %s", workspaceID, e.Id)
|
log.Debugf("ExtWorkspace: Workspace %d id: %s", workspaceID, e.Id)
|
||||||
ws.workspaceID = e.Id
|
|
||||||
m.post(func() {
|
m.post(func() {
|
||||||
|
ws.workspaceID = e.Id
|
||||||
m.updateState()
|
m.updateState()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
handle.SetNameHandler(func(e ext_workspace.ExtWorkspaceHandleV1NameEvent) {
|
handle.SetNameHandler(func(e ext_workspace.ExtWorkspaceHandleV1NameEvent) {
|
||||||
log.Debugf("ExtWorkspace: Workspace %d name: %s", workspaceID, e.Name)
|
log.Debugf("ExtWorkspace: Workspace %d name: %s", workspaceID, e.Name)
|
||||||
ws.name = e.Name
|
|
||||||
m.post(func() {
|
m.post(func() {
|
||||||
|
ws.name = e.Name
|
||||||
m.updateState()
|
m.updateState()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -266,16 +282,16 @@ func (m *Manager) handleWorkspace(e ext_workspace.ExtWorkspaceManagerV1Workspace
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Debugf("ExtWorkspace: Workspace %d coordinates: %v", workspaceID, coords)
|
log.Debugf("ExtWorkspace: Workspace %d coordinates: %v", workspaceID, coords)
|
||||||
ws.coordinates = coords
|
|
||||||
m.post(func() {
|
m.post(func() {
|
||||||
|
ws.coordinates = coords
|
||||||
m.updateState()
|
m.updateState()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
handle.SetStateHandler(func(e ext_workspace.ExtWorkspaceHandleV1StateEvent) {
|
handle.SetStateHandler(func(e ext_workspace.ExtWorkspaceHandleV1StateEvent) {
|
||||||
log.Debugf("ExtWorkspace: Workspace %d state: %d", workspaceID, e.State)
|
log.Debugf("ExtWorkspace: Workspace %d state: %d", workspaceID, e.State)
|
||||||
ws.state = e.State
|
|
||||||
m.post(func() {
|
m.post(func() {
|
||||||
|
ws.state = e.State
|
||||||
m.updateState()
|
m.updateState()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -286,13 +302,12 @@ func (m *Manager) handleWorkspace(e ext_workspace.ExtWorkspaceManagerV1Workspace
|
|||||||
|
|
||||||
handle.SetRemovedHandler(func(e ext_workspace.ExtWorkspaceHandleV1RemovedEvent) {
|
handle.SetRemovedHandler(func(e ext_workspace.ExtWorkspaceHandleV1RemovedEvent) {
|
||||||
log.Debugf("ExtWorkspace: Workspace %d removed", workspaceID)
|
log.Debugf("ExtWorkspace: Workspace %d removed", workspaceID)
|
||||||
ws.removed = true
|
|
||||||
|
|
||||||
m.workspacesMutex.Lock()
|
|
||||||
delete(m.workspaces, workspaceID)
|
|
||||||
m.workspacesMutex.Unlock()
|
|
||||||
|
|
||||||
m.post(func() {
|
m.post(func() {
|
||||||
|
ws.removed = true
|
||||||
|
|
||||||
|
m.workspaces.Delete(workspaceID)
|
||||||
|
|
||||||
m.wlMutex.Lock()
|
m.wlMutex.Lock()
|
||||||
handle.Destroy()
|
handle.Destroy()
|
||||||
m.wlMutex.Unlock()
|
m.wlMutex.Unlock()
|
||||||
@@ -303,32 +318,27 @@ func (m *Manager) handleWorkspace(e ext_workspace.ExtWorkspaceManagerV1Workspace
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) updateState() {
|
func (m *Manager) updateState() {
|
||||||
m.groupsMutex.RLock()
|
|
||||||
m.workspacesMutex.RLock()
|
|
||||||
|
|
||||||
groups := make([]*WorkspaceGroup, 0)
|
groups := make([]*WorkspaceGroup, 0)
|
||||||
|
|
||||||
for _, group := range m.groups {
|
m.groups.Range(func(key uint32, group *workspaceGroupState) bool {
|
||||||
if group.removed {
|
if group.removed {
|
||||||
continue
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
outputs := make([]string, 0)
|
outputs := make([]string, 0)
|
||||||
for outputID := range group.outputIDs {
|
for outputID := range group.outputIDs {
|
||||||
m.outputsMutex.RLock()
|
if name, ok := m.outputNames.Load(outputID); ok && name != "" {
|
||||||
name := m.outputNames[outputID]
|
|
||||||
m.outputsMutex.RUnlock()
|
|
||||||
if name != "" {
|
|
||||||
outputs = append(outputs, name)
|
outputs = append(outputs, name)
|
||||||
} else {
|
|
||||||
outputs = append(outputs, fmt.Sprintf("output-%d", outputID))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
workspaces := make([]*Workspace, 0)
|
workspaces := make([]*Workspace, 0)
|
||||||
for _, wsID := range group.workspaceIDs {
|
for _, wsID := range group.workspaceIDs {
|
||||||
ws, exists := m.workspaces[wsID]
|
ws, exists := m.workspaces.Load(wsID)
|
||||||
if !exists || ws.removed {
|
if !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ws.removed {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,10 +360,8 @@ func (m *Manager) updateState() {
|
|||||||
Workspaces: workspaces,
|
Workspaces: workspaces,
|
||||||
}
|
}
|
||||||
groups = append(groups, groupState)
|
groups = append(groups, groupState)
|
||||||
}
|
return true
|
||||||
|
})
|
||||||
m.workspacesMutex.RUnlock()
|
|
||||||
m.groupsMutex.RUnlock()
|
|
||||||
|
|
||||||
newState := State{
|
newState := State{
|
||||||
Groups: groups,
|
Groups: groups,
|
||||||
@@ -388,14 +396,6 @@ func (m *Manager) notifier() {
|
|||||||
if !pending {
|
if !pending {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
m.subMutex.RLock()
|
|
||||||
subCount := len(m.subscribers)
|
|
||||||
m.subMutex.RUnlock()
|
|
||||||
|
|
||||||
if subCount == 0 {
|
|
||||||
pending = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
currentState := m.GetState()
|
currentState := m.GetState()
|
||||||
|
|
||||||
@@ -404,15 +404,14 @@ func (m *Manager) notifier() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
m.subMutex.RLock()
|
m.subscribers.Range(func(key string, ch chan State) bool {
|
||||||
for _, ch := range m.subscribers {
|
|
||||||
select {
|
select {
|
||||||
case ch <- currentState:
|
case ch <- currentState:
|
||||||
default:
|
default:
|
||||||
log.Warn("ExtWorkspace: subscriber channel full, dropping update")
|
log.Warn("ExtWorkspace: subscriber channel full, dropping update")
|
||||||
}
|
}
|
||||||
}
|
return true
|
||||||
m.subMutex.RUnlock()
|
})
|
||||||
|
|
||||||
stateCopy := currentState
|
stateCopy := currentState
|
||||||
m.lastNotified = &stateCopy
|
m.lastNotified = &stateCopy
|
||||||
@@ -422,9 +421,9 @@ func (m *Manager) notifier() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) ActivateWorkspace(groupID, workspaceID string) error {
|
func (m *Manager) ActivateWorkspace(groupID, workspaceID string) error {
|
||||||
m.workspacesMutex.RLock()
|
errChan := make(chan error, 1)
|
||||||
defer m.workspacesMutex.RUnlock()
|
|
||||||
|
|
||||||
|
m.post(func() {
|
||||||
var targetGroupID uint32
|
var targetGroupID uint32
|
||||||
if groupID != "" {
|
if groupID != "" {
|
||||||
var parsedID uint32
|
var parsedID uint32
|
||||||
@@ -433,9 +432,10 @@ func (m *Manager) ActivateWorkspace(groupID, workspaceID string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ws := range m.workspaces {
|
var found bool
|
||||||
|
m.workspaces.Range(func(key uint32, ws *workspaceState) bool {
|
||||||
if targetGroupID != 0 && ws.groupID != targetGroupID {
|
if targetGroupID != 0 && ws.groupID != targetGroupID {
|
||||||
continue
|
return true
|
||||||
}
|
}
|
||||||
if ws.workspaceID == workspaceID || ws.name == workspaceID {
|
if ws.workspaceID == workspaceID || ws.name == workspaceID {
|
||||||
m.wlMutex.Lock()
|
m.wlMutex.Lock()
|
||||||
@@ -444,17 +444,25 @@ func (m *Manager) ActivateWorkspace(groupID, workspaceID string) error {
|
|||||||
err = m.manager.Commit()
|
err = m.manager.Commit()
|
||||||
}
|
}
|
||||||
m.wlMutex.Unlock()
|
m.wlMutex.Unlock()
|
||||||
return err
|
errChan <- err
|
||||||
}
|
found = true
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
return fmt.Errorf("workspace not found: %s in group %s", workspaceID, groupID)
|
if !found {
|
||||||
|
errChan <- fmt.Errorf("workspace not found: %s in group %s", workspaceID, groupID)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return <-errChan
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) DeactivateWorkspace(groupID, workspaceID string) error {
|
func (m *Manager) DeactivateWorkspace(groupID, workspaceID string) error {
|
||||||
m.workspacesMutex.RLock()
|
errChan := make(chan error, 1)
|
||||||
defer m.workspacesMutex.RUnlock()
|
|
||||||
|
|
||||||
|
m.post(func() {
|
||||||
var targetGroupID uint32
|
var targetGroupID uint32
|
||||||
if groupID != "" {
|
if groupID != "" {
|
||||||
var parsedID uint32
|
var parsedID uint32
|
||||||
@@ -463,9 +471,10 @@ func (m *Manager) DeactivateWorkspace(groupID, workspaceID string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ws := range m.workspaces {
|
var found bool
|
||||||
|
m.workspaces.Range(func(key uint32, ws *workspaceState) bool {
|
||||||
if targetGroupID != 0 && ws.groupID != targetGroupID {
|
if targetGroupID != 0 && ws.groupID != targetGroupID {
|
||||||
continue
|
return true
|
||||||
}
|
}
|
||||||
if ws.workspaceID == workspaceID || ws.name == workspaceID {
|
if ws.workspaceID == workspaceID || ws.name == workspaceID {
|
||||||
m.wlMutex.Lock()
|
m.wlMutex.Lock()
|
||||||
@@ -474,17 +483,25 @@ func (m *Manager) DeactivateWorkspace(groupID, workspaceID string) error {
|
|||||||
err = m.manager.Commit()
|
err = m.manager.Commit()
|
||||||
}
|
}
|
||||||
m.wlMutex.Unlock()
|
m.wlMutex.Unlock()
|
||||||
return err
|
errChan <- err
|
||||||
}
|
found = true
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
return fmt.Errorf("workspace not found: %s in group %s", workspaceID, groupID)
|
if !found {
|
||||||
|
errChan <- fmt.Errorf("workspace not found: %s in group %s", workspaceID, groupID)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return <-errChan
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) RemoveWorkspace(groupID, workspaceID string) error {
|
func (m *Manager) RemoveWorkspace(groupID, workspaceID string) error {
|
||||||
m.workspacesMutex.RLock()
|
errChan := make(chan error, 1)
|
||||||
defer m.workspacesMutex.RUnlock()
|
|
||||||
|
|
||||||
|
m.post(func() {
|
||||||
var targetGroupID uint32
|
var targetGroupID uint32
|
||||||
if groupID != "" {
|
if groupID != "" {
|
||||||
var parsedID uint32
|
var parsedID uint32
|
||||||
@@ -493,9 +510,10 @@ func (m *Manager) RemoveWorkspace(groupID, workspaceID string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ws := range m.workspaces {
|
var found bool
|
||||||
|
m.workspaces.Range(func(key uint32, ws *workspaceState) bool {
|
||||||
if targetGroupID != 0 && ws.groupID != targetGroupID {
|
if targetGroupID != 0 && ws.groupID != targetGroupID {
|
||||||
continue
|
return true
|
||||||
}
|
}
|
||||||
if ws.workspaceID == workspaceID || ws.name == workspaceID {
|
if ws.workspaceID == workspaceID || ws.name == workspaceID {
|
||||||
m.wlMutex.Lock()
|
m.wlMutex.Lock()
|
||||||
@@ -504,18 +522,27 @@ func (m *Manager) RemoveWorkspace(groupID, workspaceID string) error {
|
|||||||
err = m.manager.Commit()
|
err = m.manager.Commit()
|
||||||
}
|
}
|
||||||
m.wlMutex.Unlock()
|
m.wlMutex.Unlock()
|
||||||
return err
|
errChan <- err
|
||||||
}
|
found = true
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
return fmt.Errorf("workspace not found: %s in group %s", workspaceID, groupID)
|
if !found {
|
||||||
|
errChan <- fmt.Errorf("workspace not found: %s in group %s", workspaceID, groupID)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return <-errChan
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) CreateWorkspace(groupID, workspaceName string) error {
|
func (m *Manager) CreateWorkspace(groupID, workspaceName string) error {
|
||||||
m.groupsMutex.RLock()
|
errChan := make(chan error, 1)
|
||||||
defer m.groupsMutex.RUnlock()
|
|
||||||
|
|
||||||
for _, group := range m.groups {
|
m.post(func() {
|
||||||
|
var found bool
|
||||||
|
m.groups.Range(func(key uint32, group *workspaceGroupState) bool {
|
||||||
if fmt.Sprintf("group-%d", group.id) == groupID {
|
if fmt.Sprintf("group-%d", group.id) == groupID {
|
||||||
m.wlMutex.Lock()
|
m.wlMutex.Lock()
|
||||||
err := group.handle.CreateWorkspace(workspaceName)
|
err := group.handle.CreateWorkspace(workspaceName)
|
||||||
@@ -523,11 +550,19 @@ func (m *Manager) CreateWorkspace(groupID, workspaceName string) error {
|
|||||||
err = m.manager.Commit()
|
err = m.manager.Commit()
|
||||||
}
|
}
|
||||||
m.wlMutex.Unlock()
|
m.wlMutex.Unlock()
|
||||||
return err
|
errChan <- err
|
||||||
}
|
found = true
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
return fmt.Errorf("workspace group not found: %s", groupID)
|
if !found {
|
||||||
|
errChan <- fmt.Errorf("workspace group not found: %s", groupID)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return <-errChan
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Close() {
|
func (m *Manager) Close() {
|
||||||
@@ -535,30 +570,27 @@ func (m *Manager) Close() {
|
|||||||
m.wg.Wait()
|
m.wg.Wait()
|
||||||
m.notifierWg.Wait()
|
m.notifierWg.Wait()
|
||||||
|
|
||||||
m.subMutex.Lock()
|
m.subscribers.Range(func(key string, ch chan State) bool {
|
||||||
for _, ch := range m.subscribers {
|
|
||||||
close(ch)
|
close(ch)
|
||||||
}
|
m.subscribers.Delete(key)
|
||||||
m.subscribers = make(map[string]chan State)
|
return true
|
||||||
m.subMutex.Unlock()
|
})
|
||||||
|
|
||||||
m.workspacesMutex.Lock()
|
m.workspaces.Range(func(key uint32, ws *workspaceState) bool {
|
||||||
for _, ws := range m.workspaces {
|
|
||||||
if ws.handle != nil {
|
if ws.handle != nil {
|
||||||
ws.handle.Destroy()
|
ws.handle.Destroy()
|
||||||
}
|
}
|
||||||
}
|
m.workspaces.Delete(key)
|
||||||
m.workspaces = make(map[uint32]*workspaceState)
|
return true
|
||||||
m.workspacesMutex.Unlock()
|
})
|
||||||
|
|
||||||
m.groupsMutex.Lock()
|
m.groups.Range(func(key uint32, group *workspaceGroupState) bool {
|
||||||
for _, group := range m.groups {
|
|
||||||
if group.handle != nil {
|
if group.handle != nil {
|
||||||
group.handle.Destroy()
|
group.handle.Destroy()
|
||||||
}
|
}
|
||||||
}
|
m.groups.Delete(key)
|
||||||
m.groups = make(map[uint32]*workspaceGroupState)
|
return true
|
||||||
m.groupsMutex.Unlock()
|
})
|
||||||
|
|
||||||
if m.manager != nil {
|
if m.manager != nil {
|
||||||
m.manager.Stop()
|
m.manager.Stop()
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/ext_workspace"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/ext_workspace"
|
||||||
wlclient "github.com/yaslama/go-wayland/wayland/client"
|
wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Workspace struct {
|
type Workspace struct {
|
||||||
@@ -33,26 +34,22 @@ type cmd struct {
|
|||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
display *wlclient.Display
|
display *wlclient.Display
|
||||||
|
ctx *wlclient.Context
|
||||||
registry *wlclient.Registry
|
registry *wlclient.Registry
|
||||||
manager *ext_workspace.ExtWorkspaceManagerV1
|
manager *ext_workspace.ExtWorkspaceManagerV1
|
||||||
|
|
||||||
outputsMutex sync.RWMutex
|
outputNames syncmap.Map[uint32, string]
|
||||||
outputs map[uint32]*wlclient.Output
|
|
||||||
outputNames map[uint32]string
|
|
||||||
|
|
||||||
groupsMutex sync.RWMutex
|
groups syncmap.Map[uint32, *workspaceGroupState]
|
||||||
groups map[uint32]*workspaceGroupState
|
|
||||||
|
|
||||||
workspacesMutex sync.RWMutex
|
workspaces syncmap.Map[uint32, *workspaceState]
|
||||||
workspaces map[uint32]*workspaceState
|
|
||||||
|
|
||||||
wlMutex sync.Mutex
|
wlMutex sync.Mutex
|
||||||
cmdq chan cmd
|
cmdq chan cmd
|
||||||
stopChan chan struct{}
|
stopChan chan struct{}
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
|
|
||||||
subscribers map[string]chan State
|
subscribers syncmap.Map[string, chan State]
|
||||||
subMutex sync.RWMutex
|
|
||||||
dirty chan struct{}
|
dirty chan struct{}
|
||||||
notifierWg sync.WaitGroup
|
notifierWg sync.WaitGroup
|
||||||
lastNotified *State
|
lastNotified *State
|
||||||
@@ -94,19 +91,16 @@ func (m *Manager) GetState() State {
|
|||||||
|
|
||||||
func (m *Manager) Subscribe(id string) chan State {
|
func (m *Manager) Subscribe(id string) chan State {
|
||||||
ch := make(chan State, 64)
|
ch := make(chan State, 64)
|
||||||
m.subMutex.Lock()
|
|
||||||
m.subscribers[id] = ch
|
m.subscribers.Store(id, ch)
|
||||||
m.subMutex.Unlock()
|
|
||||||
return ch
|
return ch
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Unsubscribe(id string) {
|
func (m *Manager) Unsubscribe(id string) {
|
||||||
m.subMutex.Lock()
|
if ch, ok := m.subscribers.LoadAndDelete(id); ok {
|
||||||
if ch, ok := m.subscribers[id]; ok {
|
|
||||||
close(ch)
|
close(ch)
|
||||||
delete(m.subscribers, id)
|
|
||||||
}
|
}
|
||||||
m.subMutex.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) notifySubscribers() {
|
func (m *Manager) notifySubscribers() {
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ func NewManager() (*Manager, error) {
|
|||||||
systemConn: systemConn,
|
systemConn: systemConn,
|
||||||
sessionConn: sessionConn,
|
sessionConn: sessionConn,
|
||||||
currentUID: uint64(os.Getuid()),
|
currentUID: uint64(os.Getuid()),
|
||||||
subscribers: make(map[string]chan FreedeskState),
|
|
||||||
subMutex: sync.RWMutex{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m.initializeAccounts()
|
m.initializeAccounts()
|
||||||
@@ -206,41 +204,33 @@ func (m *Manager) GetState() FreedeskState {
|
|||||||
|
|
||||||
func (m *Manager) Subscribe(id string) chan FreedeskState {
|
func (m *Manager) Subscribe(id string) chan FreedeskState {
|
||||||
ch := make(chan FreedeskState, 64)
|
ch := make(chan FreedeskState, 64)
|
||||||
m.subMutex.Lock()
|
m.subscribers.Store(id, ch)
|
||||||
m.subscribers[id] = ch
|
|
||||||
m.subMutex.Unlock()
|
|
||||||
return ch
|
return ch
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Unsubscribe(id string) {
|
func (m *Manager) Unsubscribe(id string) {
|
||||||
m.subMutex.Lock()
|
if val, ok := m.subscribers.LoadAndDelete(id); ok {
|
||||||
if ch, ok := m.subscribers[id]; ok {
|
close(val)
|
||||||
close(ch)
|
|
||||||
delete(m.subscribers, id)
|
|
||||||
}
|
}
|
||||||
m.subMutex.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) NotifySubscribers() {
|
func (m *Manager) NotifySubscribers() {
|
||||||
m.subMutex.RLock()
|
|
||||||
defer m.subMutex.RUnlock()
|
|
||||||
|
|
||||||
state := m.GetState()
|
state := m.GetState()
|
||||||
for _, ch := range m.subscribers {
|
m.subscribers.Range(func(key string, ch chan FreedeskState) bool {
|
||||||
select {
|
select {
|
||||||
case ch <- state:
|
case ch <- state:
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
return true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Close() {
|
func (m *Manager) Close() {
|
||||||
m.subMutex.Lock()
|
m.subscribers.Range(func(key string, ch chan FreedeskState) bool {
|
||||||
for id, ch := range m.subscribers {
|
|
||||||
close(ch)
|
close(ch)
|
||||||
delete(m.subscribers, id)
|
m.subscribers.Delete(key)
|
||||||
}
|
return true
|
||||||
m.subMutex.Unlock()
|
})
|
||||||
|
|
||||||
if m.systemConn != nil {
|
if m.systemConn != nil {
|
||||||
m.systemConn.Close()
|
m.systemConn.Close()
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user