mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-06 05:25:41 -05:00
Compare commits
334 Commits
v0.5.1
...
8161fd6acb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8161fd6acb | ||
|
|
2137920e81 | ||
|
|
879102599c | ||
|
|
44190f07fe | ||
|
|
a41487eb8f | ||
|
|
e1acaaa27c | ||
|
|
08a97aeff8 | ||
|
|
5b7302b46d | ||
|
|
34c0bba130 | ||
|
|
5a53447272 | ||
|
|
b6847289ff | ||
|
|
d22c43e08b | ||
|
|
d9deaa8d74 | ||
|
|
6c7776a9a6 | ||
|
|
62bd6e41ef | ||
|
|
293c7b42c6 | ||
|
|
788da62777 | ||
|
|
2c7f24a913 | ||
|
|
f236706d6a | ||
|
|
b097700591 | ||
|
|
50b112c9d6 | ||
|
|
c2f478b088 | ||
|
|
dccbb137d7 | ||
|
|
90f9940dbd | ||
|
|
f3f7cc9077 | ||
|
|
c331e2f39e | ||
|
|
1c7ebc4323 | ||
|
|
5f5427266f | ||
|
|
33e655becd | ||
|
|
0ea0602aec | ||
|
|
46effd2ca4 | ||
|
|
de055e8260 | ||
|
|
c3077304af | ||
|
|
e15135911f | ||
|
|
d430cae944 | ||
|
|
f92dc6f71b | ||
|
|
a679be68b1 | ||
|
|
c5c5ce8409 | ||
|
|
e7cb0d397e | ||
|
|
b84308cb49 | ||
|
|
0df47d2ce3 | ||
|
|
e24b548b54 | ||
|
|
75af444cee | ||
|
|
02dd19962f | ||
|
|
f552b8ef7b | ||
|
|
9162e31489 | ||
|
|
01b28e3ee8 | ||
|
|
f5aa855125 | ||
|
|
db3610fcdb | ||
|
|
2e3f330058 | ||
|
|
1617a7f2c1 | ||
|
|
69a5566bf9 | ||
|
|
30e5d8b855 | ||
|
|
67ff7726e0 | ||
|
|
f96a2e2325 | ||
|
|
344c4f9385 | ||
|
|
89aa146845 | ||
|
|
468e569bc7 | ||
|
|
139c99001a | ||
|
|
bd99be15c2 | ||
|
|
1d91d8fd94 | ||
|
|
f425f86101 | ||
|
|
83a6b7567f | ||
|
|
9184c70883 | ||
|
|
f5ca4ccce5 | ||
|
|
50f174be92 | ||
|
|
e5d11ce535 | ||
|
|
94851a51aa | ||
|
|
cfc07f4411 | ||
|
|
c6e9abda9f | ||
|
|
25951ddc55 | ||
|
|
bcd9ece077 | ||
|
|
68adbc38ba | ||
|
|
79a4d06cc0 | ||
|
|
18bf3b7548 | ||
|
|
4e66d3532e | ||
|
|
1b6d567451 | ||
|
|
7959a79575 | ||
|
|
abf3249b67 | ||
|
|
35e0dc84e8 | ||
|
|
17639e8729 | ||
|
|
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
|
||||
|
||||
# DISABLED for now
|
||||
exit 0
|
||||
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
@@ -10,18 +6,61 @@ REPO_ROOT="$(cd "$HOOK_DIR/.." && pwd)"
|
||||
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
if [[ -z "${POEDITOR_API_TOKEN:-}" ]] || [[ -z "${POEDITOR_PROJECT_ID:-}" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
# =============================================================================
|
||||
# Go CI checks (when core/ files are staged)
|
||||
# =============================================================================
|
||||
STAGED_CORE_FILES=$(git diff --cached --name-only --diff-filter=ACMR | grep '^core/' || true)
|
||||
|
||||
if ! command -v python3 &>/dev/null; then
|
||||
exit 0
|
||||
fi
|
||||
if [[ -n "$STAGED_CORE_FILES" ]]; then
|
||||
echo "Go files staged in core/, running CI checks..."
|
||||
cd "$REPO_ROOT/core"
|
||||
|
||||
if ! python3 scripts/i18nsync.py check &>/dev/null; then
|
||||
echo "Translations out of sync"
|
||||
echo "run python3 scripts/i18nsync.py sync"
|
||||
# Format check
|
||||
echo " Checking gofmt..."
|
||||
UNFORMATTED=$(gofmt -s -l . 2>/dev/null || true)
|
||||
if [[ -n "$UNFORMATTED" ]]; then
|
||||
echo "The following files are not formatted:"
|
||||
echo "$UNFORMATTED"
|
||||
echo ""
|
||||
echo "Run: cd core && gofmt -s -w ."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# golangci-lint
|
||||
if command -v golangci-lint &>/dev/null; then
|
||||
echo " Running golangci-lint..."
|
||||
golangci-lint run ./...
|
||||
else
|
||||
echo " Warning: golangci-lint not installed, skipping lint"
|
||||
echo " Install: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest"
|
||||
fi
|
||||
|
||||
# Tests
|
||||
echo " Running tests..."
|
||||
go test ./... >/dev/null
|
||||
|
||||
# Build checks
|
||||
echo " Building..."
|
||||
mkdir -p bin
|
||||
go build -buildvcs=false -o bin/dms ./cmd/dms
|
||||
go build -buildvcs=false -o bin/dms-distro -tags distro_binary ./cmd/dms
|
||||
go build -buildvcs=false -o bin/dankinstall ./cmd/dankinstall
|
||||
|
||||
echo "All Go CI checks passed!"
|
||||
cd "$REPO_ROOT"
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# i18n sync check (DISABLED for now)
|
||||
# =============================================================================
|
||||
# if [[ -n "${POEDITOR_API_TOKEN:-}" ]] && [[ -n "${POEDITOR_PROJECT_ID:-}" ]]; then
|
||||
# if command -v python3 &>/dev/null; then
|
||||
# if ! python3 scripts/i18nsync.py check &>/dev/null; then
|
||||
# echo "Translations out of sync"
|
||||
# echo "Run: python3 scripts/i18nsync.py sync"
|
||||
# exit 1
|
||||
# fi
|
||||
# fi
|
||||
# fi
|
||||
|
||||
exit 0
|
||||
|
||||
26
.github/workflows/go-ci.yml
vendored
26
.github/workflows/go-ci.yml
vendored
@@ -3,13 +3,22 @@ name: Go CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
- "**"
|
||||
paths:
|
||||
- 'core/**'
|
||||
- '.github/workflows/go-ci.yml'
|
||||
- "core/**"
|
||||
- ".github/workflows/go-ci.yml"
|
||||
pull_request:
|
||||
branches: [master, main]
|
||||
paths:
|
||||
- "core/**"
|
||||
- ".github/workflows/go-ci.yml"
|
||||
|
||||
concurrency:
|
||||
group: go-ci-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
lint-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
@@ -32,11 +41,20 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Run golangci-lint
|
||||
uses: golangci/golangci-lint-action@v9
|
||||
with:
|
||||
version: v2.6
|
||||
working-directory: core
|
||||
|
||||
- name: Test
|
||||
run: go test -v ./...
|
||||
|
||||
- name: Build dms
|
||||
run: go build -v ./cmd/dms
|
||||
|
||||
- name: Build dms (distropkg)
|
||||
run: go build -v -tags distro_binary ./cmd/dms
|
||||
|
||||
- name: Build dankinstall
|
||||
run: go build -v ./cmd/dankinstall
|
||||
|
||||
120
.github/workflows/release.yml
vendored
120
.github/workflows/release.yml
vendored
@@ -35,6 +35,14 @@ jobs:
|
||||
with:
|
||||
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
|
||||
run: go test -v ./...
|
||||
|
||||
@@ -124,38 +132,40 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-core
|
||||
steps:
|
||||
- name: Create GitHub App token
|
||||
id: app_token
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app-id: ${{ secrets.APP_ID }}
|
||||
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
token: ${{ steps.app_token.outputs.token }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Update VERSION
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app_token.outputs.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config user.name "dms-ci[bot]"
|
||||
git config user.email "dms-ci[bot]@users.noreply.github.com"
|
||||
|
||||
version="${GITHUB_REF#refs/tags/}"
|
||||
version_no_v="${version#v}"
|
||||
echo "Updating to version: $version"
|
||||
|
||||
# Update VERSION file in quickshell/
|
||||
echo "${version}" > quickshell/VERSION
|
||||
|
||||
git add quickshell/VERSION
|
||||
|
||||
if ! git diff --cached --quiet; then
|
||||
git commit -m "chore: bump version to $version"
|
||||
git push origin HEAD:master || git push origin HEAD:main
|
||||
echo "Pushed version updates to master"
|
||||
else
|
||||
echo "No version changes needed"
|
||||
git pull --rebase origin master
|
||||
git push https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git HEAD:master
|
||||
fi
|
||||
|
||||
# Force-push the tag to point to the commit with updated VERSION
|
||||
git tag -f "${version}"
|
||||
git push -f origin "${version}"
|
||||
git push -f https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git "${version}"
|
||||
|
||||
release:
|
||||
runs-on: ubuntu-24.04
|
||||
@@ -168,6 +178,11 @@ jobs:
|
||||
with:
|
||||
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
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
@@ -255,6 +270,9 @@ jobs:
|
||||
cp _core_assets/completion.* _release_assets/ 2>/dev/null || true
|
||||
|
||||
# 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
|
||||
(cd quickshell && tar --exclude='.git' \
|
||||
--exclude='.github' \
|
||||
@@ -291,6 +309,11 @@ jobs:
|
||||
fi
|
||||
done
|
||||
|
||||
# Copy docs directory
|
||||
if [ -d "docs" ]; then
|
||||
cp -r docs _temp_full/
|
||||
fi
|
||||
|
||||
# Create installation guide
|
||||
cat > _temp_full/INSTALL.md << 'EOFINSTALL'
|
||||
# DankMaterialShell Installation
|
||||
@@ -365,6 +388,68 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
trigger-obs-update:
|
||||
runs-on: ubuntu-latest
|
||||
needs: release
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install OSC
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y osc
|
||||
|
||||
mkdir -p ~/.config/osc
|
||||
cat > ~/.config/osc/oscrc << EOF
|
||||
[general]
|
||||
apiurl = https://api.opensuse.org
|
||||
|
||||
[https://api.opensuse.org]
|
||||
user = ${{ secrets.OBS_USERNAME }}
|
||||
pass = ${{ secrets.OBS_PASSWORD }}
|
||||
EOF
|
||||
chmod 600 ~/.config/osc/oscrc
|
||||
|
||||
- name: Update OBS packages
|
||||
run: |
|
||||
VERSION="${{ github.ref_name }}"
|
||||
cd distro
|
||||
bash scripts/obs-upload.sh dms "Update to $VERSION"
|
||||
|
||||
trigger-ppa-update:
|
||||
runs-on: ubuntu-latest
|
||||
needs: release
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
debhelper \
|
||||
devscripts \
|
||||
dput \
|
||||
lftp \
|
||||
build-essential \
|
||||
fakeroot \
|
||||
dpkg-dev
|
||||
|
||||
- name: Configure GPG
|
||||
env:
|
||||
GPG_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
run: |
|
||||
echo "$GPG_KEY" | gpg --import
|
||||
GPG_KEY_ID=$(gpg --list-secret-keys --keyid-format LONG | grep sec | awk '{print $2}' | cut -d'/' -f2)
|
||||
echo "DEBSIGN_KEYID=$GPG_KEY_ID" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload to PPA
|
||||
run: |
|
||||
VERSION="${{ github.ref_name }}"
|
||||
cd distro/ubuntu/ppa
|
||||
bash create-and-upload.sh ../dms dms questing
|
||||
|
||||
copr-build:
|
||||
runs-on: ubuntu-latest
|
||||
needs: release
|
||||
@@ -512,7 +597,10 @@ jobs:
|
||||
%{_builddir}/dms-cli completion zsh > %{buildroot}%{_datadir}/zsh/site-functions/_dms || :
|
||||
%{_builddir}/dms-cli completion fish > %{buildroot}%{_datadir}/fish/vendor_completions.d/dms.fish || :
|
||||
|
||||
install -Dm644 %{_builddir}/dms-qml/assets/systemd/dms.service %{buildroot}%{_userunitdir}/dms.service
|
||||
install -Dm644 assets/systemd/dms.service %{buildroot}%{_userunitdir}/dms.service
|
||||
|
||||
install -Dm644 assets/dms-open.desktop %{buildroot}%{_datadir}/applications/dms-open.desktop
|
||||
install -Dm644 assets/danklogo.svg %{buildroot}%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
|
||||
|
||||
install -dm755 %{buildroot}%{_datadir}/quickshell/dms
|
||||
cp -r %{_builddir}/dms-qml/* %{buildroot}%{_datadir}/quickshell/dms/
|
||||
@@ -522,6 +610,8 @@ jobs:
|
||||
rm -rf %{buildroot}%{_datadir}/quickshell/dms/.github
|
||||
rm -rf %{buildroot}%{_datadir}/quickshell/dms/distro
|
||||
|
||||
echo "%{version}" > %{buildroot}%{_datadir}/quickshell/dms/VERSION
|
||||
|
||||
%posttrans
|
||||
if [ -d "%{_sysconfdir}/xdg/quickshell/dms" ]; then
|
||||
rmdir "%{_sysconfdir}/xdg/quickshell/dms" 2>/dev/null || true
|
||||
@@ -538,6 +628,8 @@ jobs:
|
||||
%doc README.md CONTRIBUTING.md
|
||||
%{_datadir}/quickshell/dms/
|
||||
%{_userunitdir}/dms.service
|
||||
%{_datadir}/applications/dms-open.desktop
|
||||
%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg
|
||||
|
||||
%files -n dms-cli
|
||||
%{_bindir}/dms
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: DMS Copr Stable Release (Manual)
|
||||
name: DMS Copr Stable Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -7,6 +7,10 @@ on:
|
||||
description: 'Versioning (e.g., 0.1.14, leave empty for latest release)'
|
||||
required: false
|
||||
default: ''
|
||||
release:
|
||||
description: 'Release number (e.g., 1, 2, 3 for hotfixes)'
|
||||
required: false
|
||||
default: '1'
|
||||
|
||||
jobs:
|
||||
build-and-upload:
|
||||
@@ -19,6 +23,7 @@ jobs:
|
||||
- name: Determine version
|
||||
id: version
|
||||
run: |
|
||||
# Get version from manual input or latest release
|
||||
if [ -n "${{ github.event.inputs.version }}" ]; then
|
||||
VERSION="${{ github.event.inputs.version }}"
|
||||
echo "Using manual version: $VERSION"
|
||||
@@ -27,8 +32,14 @@ jobs:
|
||||
echo "Using latest release version: $VERSION"
|
||||
fi
|
||||
|
||||
RELEASE="${{ github.event.inputs.release }}"
|
||||
if [ -z "$RELEASE" ]; then
|
||||
RELEASE="1"
|
||||
fi
|
||||
|
||||
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
|
||||
run: |
|
||||
@@ -57,6 +68,7 @@ jobs:
|
||||
- name: Generate stable spec file
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
RELEASE="${{ steps.version.outputs.release }}"
|
||||
CHANGELOG_DATE="$(date '+%a %b %d %Y')"
|
||||
|
||||
cat > ~/rpmbuild/SPECS/dms.spec <<'SPECEOF'
|
||||
@@ -68,7 +80,7 @@ jobs:
|
||||
|
||||
Name: dms
|
||||
Version: %{version}
|
||||
Release: 1%{?dist}
|
||||
Release: RELEASE_PLACEHOLDER%{?dist}
|
||||
Summary: %{pkg_summary}
|
||||
|
||||
License: MIT
|
||||
@@ -212,16 +224,17 @@ jobs:
|
||||
%{_bindir}/dgop
|
||||
|
||||
%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
|
||||
- Built from GitHub release
|
||||
- Includes latest dms-cli and dgop binaries
|
||||
SPECEOF
|
||||
|
||||
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
|
||||
|
||||
echo "✅ Spec file generated for v${VERSION}"
|
||||
echo "✅ Spec file generated for v${VERSION}-${RELEASE}"
|
||||
echo ""
|
||||
echo "=== Spec file preview ==="
|
||||
head -40 ~/rpmbuild/SPECS/dms.spec
|
||||
@@ -295,7 +308,7 @@ jobs:
|
||||
run: |
|
||||
echo "### 🎉 DMS Stable Build Summary" >> $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 "- **Project:** https://copr.fedorainfracloud.org/coprs/avengemedia/dms/" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
276
.github/workflows/run-obs.yml
vendored
Normal file
276
.github/workflows/run-obs.yml
vendored
Normal file
@@ -0,0 +1,276 @@
|
||||
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 current commit hash (8 chars to match spec format)
|
||||
CURRENT_COMMIT=$(git rev-parse --short=8 HEAD)
|
||||
|
||||
# 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
|
||||
|
||||
# Extract commit hash from spec Version line & format like; 0.6.2+git2264.a679be68
|
||||
if [[ -f "dms-git.spec" ]]; then
|
||||
OBS_COMMIT=$(grep "^Version:" "dms-git.spec" | grep -oP '\.[a-f0-9]{8}' | tr -d '.' || echo "")
|
||||
|
||||
if [[ -n "$OBS_COMMIT" ]]; then
|
||||
if [[ "$CURRENT_COMMIT" == "$OBS_COMMIT" ]]; then
|
||||
echo "has_updates=false" >> $GITHUB_OUTPUT
|
||||
echo "📋 Commit $CURRENT_COMMIT already uploaded to OBS, skipping"
|
||||
else
|
||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||
echo "📋 New commit detected: $CURRENT_COMMIT (OBS has $OBS_COMMIT)"
|
||||
fi
|
||||
else
|
||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||
echo "📋 Could not extract OBS commit, proceeding with update"
|
||||
fi
|
||||
else
|
||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||
echo "📋 No spec file in OBS, proceeding with update"
|
||||
fi
|
||||
|
||||
cd "${{ github.workspace }}"
|
||||
else
|
||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||
echo "📋 First upload to OBS, update needed"
|
||||
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 Debian dms-git changelog 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")
|
||||
|
||||
# Debian version format: 0.6.2+git2256.9162e314
|
||||
NEW_VERSION="${BASE_VERSION}+git${COMMIT_COUNT}.${COMMIT_HASH}"
|
||||
echo "📦 Updating Debian dms-git changelog to version: $NEW_VERSION"
|
||||
|
||||
CHANGELOG_DATE=$(date -R)
|
||||
|
||||
CHANGELOG_FILE="distro/debian/dms-git/debian/changelog"
|
||||
|
||||
# Get current version from changelog
|
||||
CURRENT_VERSION=$(head -1 "$CHANGELOG_FILE" | sed 's/.*(\([^)]*\)).*/\1/')
|
||||
|
||||
echo "Current Debian version: $CURRENT_VERSION"
|
||||
echo "New version: $NEW_VERSION"
|
||||
|
||||
# Only update if version changed
|
||||
if [ "$CURRENT_VERSION" != "$NEW_VERSION" ]; then
|
||||
# Create new changelog entry at top
|
||||
TEMP_CHANGELOG=$(mktemp)
|
||||
|
||||
cat > "$TEMP_CHANGELOG" << EOF
|
||||
dms-git ($NEW_VERSION) nightly; urgency=medium
|
||||
|
||||
* Git snapshot (commit $COMMIT_COUNT: $COMMIT_HASH)
|
||||
|
||||
-- Avenge Media <AvengeMedia.US@gmail.com> $CHANGELOG_DATE
|
||||
|
||||
EOF
|
||||
|
||||
# Prepend to existing changelog
|
||||
cat "$CHANGELOG_FILE" >> "$TEMP_CHANGELOG"
|
||||
mv "$TEMP_CHANGELOG" "$CHANGELOG_FILE"
|
||||
|
||||
echo "✓ Updated Debian changelog: $CURRENT_VERSION → $NEW_VERSION"
|
||||
else
|
||||
echo "✓ Debian changelog already at version $NEW_VERSION"
|
||||
fi
|
||||
|
||||
- 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
|
||||
188
.github/workflows/run-ppa.yml
vendored
Normal file
188
.github/workflows/run-ppa.yml
vendored
Normal file
@@ -0,0 +1,188 @@
|
||||
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:
|
||||
check-updates:
|
||||
name: Check for updates
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
outputs:
|
||||
has_updates: ${{ steps.check.outputs.has_updates }}
|
||||
packages: ${{ steps.check.outputs.packages }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check for updates
|
||||
id: check
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "schedule" ]]; then
|
||||
echo "packages=dms-git" >> $GITHUB_OUTPUT
|
||||
echo "Checking if dms-git source has changed..."
|
||||
|
||||
# Get current commit hash (8 chars to match changelog format)
|
||||
CURRENT_COMMIT=$(git rev-parse --short=8 HEAD)
|
||||
|
||||
# Extract commit hash from changelog
|
||||
# Format: dms-git (0.6.2+git2264.c5c5ce84) questing; urgency=medium
|
||||
CHANGELOG_FILE="distro/ubuntu/dms-git/debian/changelog"
|
||||
|
||||
if [[ -f "$CHANGELOG_FILE" ]]; then
|
||||
CHANGELOG_COMMIT=$(head -1 "$CHANGELOG_FILE" | grep -oP '\.[a-f0-9]{8}' | tr -d '.' || echo "")
|
||||
|
||||
if [[ -n "$CHANGELOG_COMMIT" ]]; then
|
||||
if [[ "$CURRENT_COMMIT" == "$CHANGELOG_COMMIT" ]]; then
|
||||
echo "has_updates=false" >> $GITHUB_OUTPUT
|
||||
echo "📋 Commit $CURRENT_COMMIT already in changelog, skipping upload"
|
||||
else
|
||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||
echo "📋 New commit detected: $CURRENT_COMMIT (changelog has $CHANGELOG_COMMIT)"
|
||||
fi
|
||||
else
|
||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||
echo "📋 Could not extract commit from changelog, proceeding with upload"
|
||||
fi
|
||||
else
|
||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||
echo "📋 No changelog file found, proceeding with upload"
|
||||
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=dms-git" >> $GITHUB_OUTPUT
|
||||
echo "has_updates=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
upload-ppa:
|
||||
name: Upload to PPA
|
||||
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: 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=${{ needs.check-updates.outputs.packages }}" >> $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=${{ needs.check-updates.outputs.packages }}" >> $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
|
||||
|
||||
if [[ "${{ needs.check-updates.outputs.has_updates }}" == "false" ]]; then
|
||||
echo "- **Status**: Skipped (no changes detected)" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
PACKAGES="${{ steps.packages.outputs.packages }}"
|
||||
if [[ "$PACKAGES" == "all" ]]; then
|
||||
echo "- **PPA dms**: https://launchpad.net/~avengemedia/+archive/ubuntu/dms/+packages" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **PPA dms-git**: https://launchpad.net/~avengemedia/+archive/ubuntu/dms-git/+packages" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **PPA danklinux**: https://launchpad.net/~avengemedia/+archive/ubuntu/danklinux/+packages" >> $GITHUB_STEP_SUMMARY
|
||||
elif [[ "$PACKAGES" == "dms" ]]; then
|
||||
echo "- **PPA**: https://launchpad.net/~avengemedia/+archive/ubuntu/dms/+packages" >> $GITHUB_STEP_SUMMARY
|
||||
elif [[ "$PACKAGES" == "dms-git" ]]; then
|
||||
echo "- **PPA**: https://launchpad.net/~avengemedia/+archive/ubuntu/dms-git/+packages" >> $GITHUB_STEP_SUMMARY
|
||||
elif [[ "$PACKAGES" == "dms-greeter" ]]; then
|
||||
echo "- **PPA**: https://launchpad.net/~avengemedia/+archive/ubuntu/danklinux/+packages" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [[ -n "${{ steps.packages.outputs.version }}" ]]; then
|
||||
echo "- **Version**: ${{ steps.packages.outputs.version }}" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Builds will appear once Launchpad processes the uploads." >> $GITHUB_STEP_SUMMARY
|
||||
66
.github/workflows/update-vendor-hash.yml
vendored
66
.github/workflows/update-vendor-hash.yml
vendored
@@ -1,6 +1,7 @@
|
||||
name: Update Vendor Hash
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- "core/go.mod"
|
||||
@@ -8,14 +9,25 @@ on:
|
||||
branches:
|
||||
- master
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
update-vendor-hash:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Create GitHub App token
|
||||
id: app_token
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app-id: ${{ secrets.APP_ID }}
|
||||
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ steps.app_token.outputs.token }}
|
||||
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v31
|
||||
@@ -23,68 +35,32 @@ jobs:
|
||||
- name: Update vendorHash in flake.nix
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Try to build and capture the expected hash from error message
|
||||
echo "Attempting nix build to get new vendorHash..."
|
||||
|
||||
if output=$(nix build .#dmsCli 2>&1); then
|
||||
echo "Build succeeded, no hash update needed"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Extract the expected hash from the error message
|
||||
new_hash=$(echo "$output" | grep -oP "got:\s+\K\S+" | head -n1)
|
||||
|
||||
if [ -z "$new_hash" ]; then
|
||||
echo "Could not extract new vendorHash from build output"
|
||||
echo "Build output:"
|
||||
echo "$output"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "New vendorHash: $new_hash"
|
||||
|
||||
# Get current hash from flake.nix
|
||||
[ -n "$new_hash" ] || { echo "Could not extract new vendorHash"; echo "$output"; exit 1; }
|
||||
current_hash=$(grep -oP 'vendorHash = "\K[^"]+' flake.nix)
|
||||
echo "Current vendorHash: $current_hash"
|
||||
|
||||
if [ "$current_hash" = "$new_hash" ]; then
|
||||
echo "vendorHash is already up to date"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Update the hash in flake.nix
|
||||
[ "$current_hash" = "$new_hash" ] && { echo "vendorHash already up to date"; exit 0; }
|
||||
sed -i "s|vendorHash = \"$current_hash\"|vendorHash = \"$new_hash\"|" flake.nix
|
||||
|
||||
# Verify the build works with the new hash
|
||||
echo "Verifying build with new vendorHash..."
|
||||
nix build .#dmsCli
|
||||
|
||||
echo "vendorHash updated successfully!"
|
||||
|
||||
- name: Commit and push vendorHash update
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app_token.outputs.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if ! git diff --quiet flake.nix; then
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
git config user.name "dms-ci[bot]"
|
||||
git config user.email "dms-ci[bot]@users.noreply.github.com"
|
||||
git add flake.nix
|
||||
git commit -m "nix: update vendorHash for go.mod changes"
|
||||
|
||||
for attempt in 1 2 3; do
|
||||
if git push; then
|
||||
echo "Successfully pushed vendorHash update"
|
||||
exit 0
|
||||
fi
|
||||
echo "Push attempt $attempt failed, pulling and retrying..."
|
||||
git pull --rebase
|
||||
sleep $((attempt*2))
|
||||
done
|
||||
|
||||
echo "Failed to push after retries" >&2
|
||||
exit 1
|
||||
git commit -m "nix: update vendorHash for go.mod changes" || exit 0
|
||||
git pull --rebase origin master
|
||||
git push https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git HEAD:master
|
||||
else
|
||||
echo "No changes to flake.nix"
|
||||
fi
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -136,3 +136,9 @@ go.work.sum
|
||||
# .vscode/
|
||||
|
||||
bin/
|
||||
|
||||
# Extracted source trees in Ubuntu package directories
|
||||
distro/ubuntu/*/dms-git-repo/
|
||||
distro/ubuntu/*/DankMaterialShell-*/
|
||||
distro/ubuntu/danklinux/*/dsearch-*/
|
||||
distro/ubuntu/danklinux/*/dgop-*/
|
||||
|
||||
@@ -2,28 +2,50 @@
|
||||
|
||||
Contributions are welcome and encouraged.
|
||||
|
||||
## Formatting
|
||||
To contribute fork this repository, make your changes, and open a pull request.
|
||||
|
||||
The preferred tool for formatting files is [qmlfmt](https://github.com/jesperhh/qmlfmt) (also available on aur as qmlfmt-git). It actually kinda sucks, but `qmlformat` doesn't work with null safe operators and ternarys and pragma statements and a bunch of other things that are supported.
|
||||
## Setup
|
||||
|
||||
We need some consistent style, so this at least gives the same formatter that Qt Creator uses.
|
||||
Enable pre-commit hooks to catch CI failures before pushing:
|
||||
|
||||
You can configure it to format on save in vscode by configuring the "custom local formatters" extension then adding this to settings json.
|
||||
|
||||
```json
|
||||
"customLocalFormatters.formatters": [
|
||||
{
|
||||
"command": "sh -c \"qmlfmt -t 4 -i 4 -b 250 | sed 's/pragma ComponentBehavior$/pragma ComponentBehavior: Bound/g'\"",
|
||||
"languages": ["qml"]
|
||||
}
|
||||
],
|
||||
"[qml]": {
|
||||
"editor.defaultFormatter": "jkillian.custom-local-formatters",
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
```bash
|
||||
git config core.hooksPath .githooks
|
||||
```
|
||||
|
||||
Sometimes it just breaks code though. Like turning `"_\""` into `"_""`, so you may not want to do formatOnSave.
|
||||
## VSCode Setup
|
||||
|
||||
This is a monorepo, the easiest thing to do is to open an editor in either `quickshell`, `core`, or both depending on which part of the project you are working on.
|
||||
|
||||
### QML (`quickshell` directory)
|
||||
|
||||
1. Install the [QML Extension](https://doc.qt.io/vscodeext/)
|
||||
2. Configure `ctrl+shift+p` -> user preferences (json) with qmlls path
|
||||
|
||||
```json
|
||||
{
|
||||
"qt-qml.doNotAskForQmllsDownload": true,
|
||||
"qt-qml.qmlls.customExePath": "/usr/lib/qt6/bin/qmlls"
|
||||
}
|
||||
```
|
||||
|
||||
3. Create empty `.qmlls.ini` file in `quickshell/` directory
|
||||
|
||||
```bash
|
||||
cd quickshell
|
||||
touch .qmlls.ini
|
||||
```
|
||||
|
||||
4. Restart dms to generate the `.qmlls.ini` file
|
||||
|
||||
5. Make your changes, test, and open a pull request.
|
||||
|
||||
### GO (`core` directory)
|
||||
|
||||
1. Install the [Go Extension](https://code.visualstudio.com/docs/languages/go)
|
||||
2. Ensure code is formatted with `make fmt`
|
||||
3. Add appropriate test coverage and ensure tests pass with `make test`
|
||||
4. Run `go mod tidy`
|
||||
5. Open pull request
|
||||
|
||||
## Pull request
|
||||
|
||||
|
||||
156
Makefile
Normal file
156
Makefile
Normal file
@@ -0,0 +1,156 @@
|
||||
# Root Makefile for DankMaterialShell (DMS)
|
||||
# Orchestrates building, installation, and systemd management
|
||||
|
||||
# Build configuration
|
||||
BINARY_NAME=dms
|
||||
CORE_DIR=core
|
||||
BUILD_DIR=$(CORE_DIR)/bin
|
||||
PREFIX ?= /usr/local
|
||||
INSTALL_DIR=$(PREFIX)/bin
|
||||
DATA_DIR=$(PREFIX)/share
|
||||
ICON_DIR=$(DATA_DIR)/icons/hicolor/scalable/apps
|
||||
|
||||
USER_HOME := $(if $(SUDO_USER),$(shell getent passwd $(SUDO_USER) | cut -d: -f6),$(HOME))
|
||||
SYSTEMD_USER_DIR=$(USER_HOME)/.config/systemd/user
|
||||
|
||||
SHELL_DIR=quickshell
|
||||
SHELL_INSTALL_DIR=$(DATA_DIR)/quickshell/dms
|
||||
ASSETS_DIR=assets
|
||||
APPLICATIONS_DIR=$(DATA_DIR)/applications
|
||||
|
||||
.PHONY: all build clean install install-bin install-shell install-completions install-systemd install-icon install-desktop uninstall uninstall-bin uninstall-shell uninstall-completions uninstall-systemd uninstall-icon uninstall-desktop help
|
||||
|
||||
all: build
|
||||
|
||||
build:
|
||||
@echo "Building $(BINARY_NAME)..."
|
||||
@$(MAKE) -C $(CORE_DIR) build
|
||||
@echo "Build complete"
|
||||
|
||||
clean:
|
||||
@echo "Cleaning build artifacts..."
|
||||
@$(MAKE) -C $(CORE_DIR) clean
|
||||
@echo "Clean complete"
|
||||
|
||||
# Installation targets
|
||||
install-bin:
|
||||
@echo "Installing $(BINARY_NAME) to $(INSTALL_DIR)..."
|
||||
@install -D -m 755 $(BUILD_DIR)/$(BINARY_NAME) $(INSTALL_DIR)/$(BINARY_NAME)
|
||||
@echo "Binary installed"
|
||||
|
||||
install-shell:
|
||||
@echo "Installing shell files to $(SHELL_INSTALL_DIR)..."
|
||||
@mkdir -p $(SHELL_INSTALL_DIR)
|
||||
@cp -r $(SHELL_DIR)/* $(SHELL_INSTALL_DIR)/
|
||||
@rm -rf $(SHELL_INSTALL_DIR)/.git* $(SHELL_INSTALL_DIR)/.github
|
||||
@$(MAKE) --no-print-directory -C $(CORE_DIR) print-version > $(SHELL_INSTALL_DIR)/VERSION
|
||||
@echo "Shell files installed"
|
||||
|
||||
install-completions:
|
||||
@echo "Installing shell completions..."
|
||||
@mkdir -p $(DATA_DIR)/bash-completion/completions
|
||||
@mkdir -p $(DATA_DIR)/zsh/site-functions
|
||||
@mkdir -p $(DATA_DIR)/fish/vendor_completions.d
|
||||
@$(BUILD_DIR)/$(BINARY_NAME) completion bash > $(DATA_DIR)/bash-completion/completions/dms 2>/dev/null || true
|
||||
@$(BUILD_DIR)/$(BINARY_NAME) completion zsh > $(DATA_DIR)/zsh/site-functions/_dms 2>/dev/null || true
|
||||
@$(BUILD_DIR)/$(BINARY_NAME) completion fish > $(DATA_DIR)/fish/vendor_completions.d/dms.fish 2>/dev/null || true
|
||||
@echo "Shell completions installed"
|
||||
|
||||
install-systemd:
|
||||
@echo "Installing systemd user service..."
|
||||
@mkdir -p $(SYSTEMD_USER_DIR)
|
||||
@if [ -n "$(SUDO_USER)" ]; then chown -R $(SUDO_USER):$(SUDO_USER) $(SYSTEMD_USER_DIR); fi
|
||||
@sed 's|/usr/bin/dms|$(INSTALL_DIR)/dms|g' $(ASSETS_DIR)/systemd/dms.service > $(SYSTEMD_USER_DIR)/dms.service
|
||||
@chmod 644 $(SYSTEMD_USER_DIR)/dms.service
|
||||
@if [ -n "$(SUDO_USER)" ]; then chown $(SUDO_USER):$(SUDO_USER) $(SYSTEMD_USER_DIR)/dms.service; fi
|
||||
@echo "Systemd service installed to $(SYSTEMD_USER_DIR)/dms.service"
|
||||
|
||||
install-icon:
|
||||
@echo "Installing icon..."
|
||||
@install -D -m 644 $(ASSETS_DIR)/danklogo.svg $(ICON_DIR)/danklogo.svg
|
||||
@gtk-update-icon-cache -q $(DATA_DIR)/icons/hicolor 2>/dev/null || true
|
||||
@echo "Icon installed"
|
||||
|
||||
install-desktop:
|
||||
@echo "Installing desktop entry..."
|
||||
@install -D -m 644 $(ASSETS_DIR)/dms-open.desktop $(APPLICATIONS_DIR)/dms-open.desktop
|
||||
@update-desktop-database -q $(APPLICATIONS_DIR) 2>/dev/null || true
|
||||
@echo "Desktop entry installed"
|
||||
|
||||
install: build install-bin install-shell install-completions install-systemd install-icon install-desktop
|
||||
@echo ""
|
||||
@echo "Installation complete!"
|
||||
@echo ""
|
||||
@echo "=== Cheers, the DMS Team! ==="
|
||||
|
||||
# Uninstallation targets
|
||||
uninstall-bin:
|
||||
@echo "Removing $(BINARY_NAME) from $(INSTALL_DIR)..."
|
||||
@rm -f $(INSTALL_DIR)/$(BINARY_NAME)
|
||||
@echo "Binary removed"
|
||||
|
||||
uninstall-shell:
|
||||
@echo "Removing shell files from $(SHELL_INSTALL_DIR)..."
|
||||
@rm -rf $(SHELL_INSTALL_DIR)
|
||||
@echo "Shell files removed"
|
||||
|
||||
uninstall-completions:
|
||||
@echo "Removing shell completions..."
|
||||
@rm -f $(DATA_DIR)/bash-completion/completions/dms
|
||||
@rm -f $(DATA_DIR)/zsh/site-functions/_dms
|
||||
@rm -f $(DATA_DIR)/fish/vendor_completions.d/dms.fish
|
||||
@echo "Shell completions removed"
|
||||
|
||||
uninstall-systemd:
|
||||
@echo "Removing systemd user service..."
|
||||
@rm -f $(SYSTEMD_USER_DIR)/dms.service
|
||||
@echo "Systemd service removed"
|
||||
@echo "Note: Stop/disable service manually if running: systemctl --user stop dms"
|
||||
|
||||
uninstall-icon:
|
||||
@echo "Removing icon..."
|
||||
@rm -f $(ICON_DIR)/danklogo.svg
|
||||
@gtk-update-icon-cache -q $(DATA_DIR)/icons/hicolor 2>/dev/null || true
|
||||
@echo "Icon removed"
|
||||
|
||||
uninstall-desktop:
|
||||
@echo "Removing desktop entry..."
|
||||
@rm -f $(APPLICATIONS_DIR)/dms-open.desktop
|
||||
@update-desktop-database -q $(APPLICATIONS_DIR) 2>/dev/null || true
|
||||
@echo "Desktop entry removed"
|
||||
|
||||
uninstall: uninstall-systemd uninstall-desktop uninstall-icon uninstall-completions uninstall-shell uninstall-bin
|
||||
@echo ""
|
||||
@echo "Uninstallation complete!"
|
||||
|
||||
# Target assist
|
||||
help:
|
||||
@echo "Available targets:"
|
||||
@echo ""
|
||||
@echo "Build:"
|
||||
@echo " all (default) - Build the DMS binary"
|
||||
@echo " build - Same as 'all'"
|
||||
@echo " clean - Clean build artifacts"
|
||||
@echo ""
|
||||
@echo "Install:"
|
||||
@echo " install - Build and install everything (requires sudo)"
|
||||
@echo " install-bin - Install only the binary"
|
||||
@echo " install-shell - Install only shell files"
|
||||
@echo " install-completions - Install only shell completions"
|
||||
@echo " install-systemd - Install only systemd service"
|
||||
@echo " install-icon - Install only icon"
|
||||
@echo " install-desktop - Install only desktop entry"
|
||||
@echo ""
|
||||
@echo "Uninstall:"
|
||||
@echo " uninstall - Remove everything (requires sudo)"
|
||||
@echo " uninstall-bin - Remove only the binary"
|
||||
@echo " uninstall-shell - Remove only shell files"
|
||||
@echo " uninstall-completions - Remove only shell completions"
|
||||
@echo " uninstall-systemd - Remove only systemd service"
|
||||
@echo " uninstall-icon - Remove only icon"
|
||||
@echo " uninstall-desktop - Remove only desktop entry"
|
||||
@echo ""
|
||||
@echo "Usage:"
|
||||
@echo " sudo make install - Build and install DMS"
|
||||
@echo " sudo make uninstall - Remove DMS"
|
||||
@echo " systemctl --user enable --now dms - Enable and start service"
|
||||
19
README.md
19
README.md
@@ -15,11 +15,11 @@
|
||||
[](https://github.com/AvengeMedia/DankMaterialShell/releases)
|
||||
[](https://aur.archlinux.org/packages/dms-shell-bin)
|
||||
[)](https://aur.archlinux.org/packages/dms-shell-git)
|
||||
[](https://ko-fi.com/avengemediallc)
|
||||
[](https://ko-fi.com/danklinux)
|
||||
|
||||
</div>
|
||||
|
||||
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
|
||||
|
||||
@@ -36,8 +36,10 @@ DankMaterialShell/
|
||||
│ ├── cmd/ # dms CLI and dankinstall binaries
|
||||
│ ├── internal/ # System integration, IPC, distro support
|
||||
│ └── pkg/ # Shared packages
|
||||
├── distro/ # Distribution packaging (Fedora RPM specs)
|
||||
├── nix/ # NixOS/home-manager modules
|
||||
├── distro/ # Distribution packaging
|
||||
│ ├── fedora/ # Fedora RPM specs
|
||||
│ ├── debian/ # Debian packaging
|
||||
│ └── nix/ # NixOS/home-manager modules
|
||||
└── flake.nix # Nix flake for declarative installation
|
||||
```
|
||||
|
||||
@@ -103,7 +105,7 @@ Extend functionality with the [plugin registry](https://plugins.danklinux.com).
|
||||
|
||||
## 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)
|
||||
|
||||
@@ -136,8 +138,7 @@ See component-specific documentation:
|
||||
|
||||
- **[quickshell/](quickshell/)** - QML shell development, widgets, and modules
|
||||
- **[core/](core/)** - Go backend, CLI tools, and system integration
|
||||
- **[distro/](distro/)** - Distribution packaging
|
||||
- **[nix/](nix/)** - NixOS and home-manager modules
|
||||
- **[distro/](distro/)** - Distribution packaging (Fedora, Debian, NixOS)
|
||||
|
||||
### 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
|
||||
- [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
|
||||
|
||||
MIT License - See [LICENSE](LICENSE) for details.
|
||||
|
||||
10
assets/dms-open.desktop
Normal file
10
assets/dms-open.desktop
Normal file
@@ -0,0 +1,10 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=DMS Application Picker
|
||||
Comment=Select an application to open links and files
|
||||
Exec=dms open %u
|
||||
Icon=danklogo
|
||||
Terminal=false
|
||||
NoDisplay=true
|
||||
MimeType=x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/file;text/html;
|
||||
Categories=Utility;
|
||||
@@ -5,11 +5,12 @@ After=graphical-session.target
|
||||
Requisite=graphical-session.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Type=dbus
|
||||
BusName=org.freedesktop.Notifications
|
||||
ExecStart=/usr/bin/dms run --session
|
||||
ExecReload=/usr/bin/pkill -USR1 -x dms
|
||||
Restart=always
|
||||
RestartSec=2
|
||||
RestartSec=1.23
|
||||
TimeoutStopSec=10
|
||||
|
||||
[Install]
|
||||
105
core/.golangci.yml
Normal file
105
core/.golangci.yml
Normal file
@@ -0,0 +1,105 @@
|
||||
version: "2"
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- revive
|
||||
|
||||
settings:
|
||||
revive:
|
||||
rules:
|
||||
- name: use-any
|
||||
severity: error
|
||||
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.RemoveAll
|
||||
- (*os.File).WriteString
|
||||
# Stdout/stderr writes (can't meaningfully handle failure)
|
||||
- fmt.Fprintln
|
||||
- fmt.Fprintf
|
||||
- fmt.Fprint
|
||||
# Writing to pipes (if pipe is bad, nothing we can do)
|
||||
- (*io.PipeWriter).Write
|
||||
- (*os.File).Write
|
||||
|
||||
exclusions:
|
||||
rules:
|
||||
# Exclude generated mocks from all linters
|
||||
- path: internal/mocks/
|
||||
linters:
|
||||
- errcheck
|
||||
- govet
|
||||
- unused
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
- gosimple
|
||||
- revive
|
||||
- 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:
|
||||
@@ -10,16 +10,19 @@ GO=go
|
||||
GOFLAGS=-ldflags="-s -w"
|
||||
|
||||
# Version and build info
|
||||
VERSION=$(shell git describe --tags --always 2>/dev/null || echo "dev")
|
||||
BUILD_TIME=$(shell date -u '+%Y-%m-%d_%H:%M:%S')
|
||||
COMMIT=$(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
||||
BASE_VERSION=$(shell git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo "0.0.0")
|
||||
COMMIT_COUNT=$(shell git rev-list --count HEAD 2>/dev/null || echo "0")
|
||||
COMMIT_HASH=$(shell git rev-parse --short=8 HEAD 2>/dev/null || echo "unknown")
|
||||
VERSION?=$(BASE_VERSION)+git$(COMMIT_COUNT).$(COMMIT_HASH)
|
||||
BUILD_TIME?=$(shell date -u '+%Y-%m-%d_%H:%M:%S')
|
||||
COMMIT?=$(COMMIT_HASH)
|
||||
|
||||
BUILD_LDFLAGS=-ldflags='-s -w -X main.Version=$(VERSION) -X main.buildTime=$(BUILD_TIME) -X main.commit=$(COMMIT)'
|
||||
|
||||
# Architecture to build for dist target (amd64, arm64, or all)
|
||||
ARCH ?= all
|
||||
|
||||
.PHONY: all build dankinstall dist clean install install-all install-dankinstall uninstall uninstall-all uninstall-dankinstall install-config uninstall-config test fmt vet deps help
|
||||
.PHONY: all build dankinstall dist clean install install-all install-dankinstall uninstall uninstall-all uninstall-dankinstall install-config uninstall-config test fmt vet deps print-version help
|
||||
|
||||
# Default target
|
||||
all: build
|
||||
@@ -132,6 +135,9 @@ version: check-go
|
||||
@echo "Build Time: $(BUILD_TIME)"
|
||||
@echo "Commit: $(COMMIT)"
|
||||
|
||||
print-version:
|
||||
@echo "$(VERSION)"
|
||||
|
||||
help:
|
||||
@echo "Available targets:"
|
||||
@echo " all - Build the main binary (dms) (default)"
|
||||
|
||||
@@ -16,6 +16,9 @@ Distribution-aware installer with TUI for deploying DMS and compositor configura
|
||||
|
||||
**Wayland Protocols**
|
||||
- `wlr-gamma-control-unstable-v1` - Night mode and gamma control
|
||||
- `wlr-screencopy-unstable-v1` - Screen capture for color picker
|
||||
- `wlr-layer-shell-unstable-v1` - Overlay surfaces for color picker
|
||||
- `wp-viewporter` - Fractional scaling support
|
||||
- `dwl-ipc-unstable-v2` - dwl/MangoWC workspace integration
|
||||
- `ext-workspace-v1` - Workspace protocol support
|
||||
- `wlr-output-management-unstable-v1` - Display configuration
|
||||
@@ -31,6 +34,7 @@ Distribution-aware installer with TUI for deploying DMS and compositor configura
|
||||
- DDC/CI protocol - External monitor brightness control (like `ddcutil`)
|
||||
- Backlight control - Internal display brightness via `login1` or sysfs
|
||||
- LED control - Keyboard/device LED management
|
||||
- evdev input monitoring - Keyboard state tracking (caps lock, etc.)
|
||||
|
||||
**Plugin System**
|
||||
- Plugin registry integration
|
||||
@@ -43,9 +47,24 @@ Distribution-aware installer with TUI for deploying DMS and compositor configura
|
||||
- `dms ipc <command>` - Send IPC commands (toggle launcher, notifications, etc.)
|
||||
- `dms plugins [install|browse|search]` - Plugin management
|
||||
- `dms brightness [list|set]` - Control display/monitor brightness
|
||||
- `dms color pick` - Native color picker (see below)
|
||||
- `dms update` - Update DMS and dependencies (disabled in distro packages)
|
||||
- `dms greeter install` - Install greetd greeter (disabled in distro packages)
|
||||
|
||||
### Color Picker
|
||||
|
||||
Native Wayland color picker with magnifier, no external dependencies. Supports HiDPI and fractional scaling.
|
||||
|
||||
```bash
|
||||
dms color pick # Pick color, output hex
|
||||
dms color pick --rgb # Output as RGB (255 128 64)
|
||||
dms color pick --hsv # Output as HSV (24 75% 100%)
|
||||
dms color pick --json # Output all formats as JSON
|
||||
dms color pick -a # Auto-copy to clipboard
|
||||
```
|
||||
|
||||
The on-screen preview displays the selected format. JSON output includes hex, RGB, HSL, HSV, and CMYK values.
|
||||
|
||||
## Building
|
||||
|
||||
Requires Go 1.24+
|
||||
@@ -71,6 +90,13 @@ sudo make install # Install to /usr/local/bin/dms
|
||||
|
||||
## Development
|
||||
|
||||
**Setup pre-commit hooks:**
|
||||
```bash
|
||||
git config core.hooksPath .githooks
|
||||
```
|
||||
|
||||
This runs gofmt, golangci-lint, tests, and builds before each commit when `core/` files are staged.
|
||||
|
||||
**Regenerating Wayland Protocol Bindings:**
|
||||
```bash
|
||||
go install github.com/rajveermalviya/go-wayland/cmd/go-wayland-scanner@latest
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/logger"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/tui"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
var Version = "dev"
|
||||
|
||||
func main() {
|
||||
fileLogger, err := logger.NewFileLogger()
|
||||
fileLogger, err := log.NewFileLogger()
|
||||
if err != nil {
|
||||
fmt.Printf("Warning: Failed to create log file: %v\n", err)
|
||||
fmt.Println("Continuing without file logging...")
|
||||
|
||||
@@ -28,7 +28,14 @@ var brightnessSetCmd = &cobra.Command{
|
||||
Short: "Set brightness for a device",
|
||||
Long: "Set brightness percentage (0-100) for a specific device",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: runBrightnessSet,
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) != 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
includeDDC, _ := cmd.Flags().GetBool("ddc")
|
||||
return getBrightnessDevices(includeDDC), cobra.ShellCompDirectiveNoFileComp
|
||||
},
|
||||
Run: runBrightnessSet,
|
||||
}
|
||||
|
||||
var brightnessGetCmd = &cobra.Command{
|
||||
@@ -36,7 +43,14 @@ var brightnessGetCmd = &cobra.Command{
|
||||
Short: "Get brightness for a device",
|
||||
Long: "Get current brightness percentage for a specific device",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: runBrightnessGet,
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) != 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
includeDDC, _ := cmd.Flags().GetBool("ddc")
|
||||
return getBrightnessDevices(includeDDC), cobra.ShellCompDirectiveNoFileComp
|
||||
},
|
||||
Run: runBrightnessGet,
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -105,9 +119,7 @@ Global Flags:
|
||||
brightnessCmd.AddCommand(brightnessListCmd, brightnessSetCmd, brightnessGetCmd)
|
||||
}
|
||||
|
||||
func runBrightnessList(cmd *cobra.Command, args []string) {
|
||||
includeDDC, _ := cmd.Flags().GetBool("ddc")
|
||||
|
||||
func getAllBrightnessDevices(includeDDC bool) []brightness.Device {
|
||||
allDevices := []brightness.Device{}
|
||||
|
||||
sysfs, err := brightness.NewSysfsBackend()
|
||||
@@ -138,6 +150,13 @@ func runBrightnessList(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
}
|
||||
|
||||
return allDevices
|
||||
}
|
||||
|
||||
func runBrightnessList(cmd *cobra.Command, args []string) {
|
||||
includeDDC, _ := cmd.Flags().GetBool("ddc")
|
||||
allDevices := getAllBrightnessDevices(includeDDC)
|
||||
|
||||
if len(allDevices) == 0 {
|
||||
fmt.Println("No brightness devices found")
|
||||
return
|
||||
@@ -261,31 +280,20 @@ func runBrightnessSet(cmd *cobra.Command, args []string) {
|
||||
log.Fatalf("Failed to set brightness for device: %s", deviceID)
|
||||
}
|
||||
|
||||
func getBrightnessDevices(includeDDC bool) []string {
|
||||
allDevices := getAllBrightnessDevices(includeDDC)
|
||||
|
||||
var deviceIDs []string
|
||||
for _, device := range allDevices {
|
||||
deviceIDs = append(deviceIDs, device.ID)
|
||||
}
|
||||
return deviceIDs
|
||||
}
|
||||
|
||||
func runBrightnessGet(cmd *cobra.Command, args []string) {
|
||||
deviceID := args[0]
|
||||
includeDDC, _ := cmd.Flags().GetBool("ddc")
|
||||
|
||||
allDevices := []brightness.Device{}
|
||||
|
||||
sysfs, err := brightness.NewSysfsBackend()
|
||||
if err == nil {
|
||||
devices, err := sysfs.GetDevices()
|
||||
if err == nil {
|
||||
allDevices = append(allDevices, devices...)
|
||||
}
|
||||
}
|
||||
|
||||
if includeDDC {
|
||||
ddc, err := brightness.NewDDCBackend()
|
||||
if err == nil {
|
||||
defer ddc.Close()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
devices, err := ddc.GetDevices()
|
||||
if err == nil {
|
||||
allDevices = append(allDevices, devices...)
|
||||
}
|
||||
}
|
||||
}
|
||||
allDevices := getAllBrightnessDevices(includeDDC)
|
||||
|
||||
for _, device := range allDevices {
|
||||
if device.ID == deviceID {
|
||||
|
||||
133
core/cmd/dms/commands_colorpicker.go
Normal file
133
core/cmd/dms/commands_colorpicker.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/colorpicker"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
colorOutputFmt string
|
||||
colorAutocopy bool
|
||||
colorNotify bool
|
||||
colorLowercase bool
|
||||
)
|
||||
|
||||
var colorCmd = &cobra.Command{
|
||||
Use: "color",
|
||||
Short: "Color utilities",
|
||||
Long: "Color utilities including picking colors from the screen",
|
||||
}
|
||||
|
||||
var colorPickCmd = &cobra.Command{
|
||||
Use: "pick",
|
||||
Short: "Pick a color from the screen",
|
||||
Long: `Pick a color from anywhere on your screen using an interactive color picker.
|
||||
|
||||
Click on any pixel to capture its color, or press Escape to cancel.
|
||||
|
||||
Output format flags (mutually exclusive, default: --hex):
|
||||
--hex - Hexadecimal (#RRGGBB)
|
||||
--rgb - RGB values (R G B)
|
||||
--hsl - HSL values (H S% L%)
|
||||
--hsv - HSV values (H S% V%)
|
||||
--cmyk - CMYK values (C% M% Y% K%)
|
||||
--json - JSON with all formats
|
||||
|
||||
Examples:
|
||||
dms color pick # Pick color, output as hex
|
||||
dms color pick --rgb # Output as RGB
|
||||
dms color pick --json # Output all formats as JSON
|
||||
dms color pick --hex -l # Output hex in lowercase
|
||||
dms color pick -a # Auto-copy result to clipboard`,
|
||||
Run: runColorPick,
|
||||
}
|
||||
|
||||
func init() {
|
||||
colorPickCmd.Flags().Bool("hex", false, "Output as hexadecimal (#RRGGBB)")
|
||||
colorPickCmd.Flags().Bool("rgb", false, "Output as RGB (R G B)")
|
||||
colorPickCmd.Flags().Bool("hsl", false, "Output as HSL (H S% L%)")
|
||||
colorPickCmd.Flags().Bool("hsv", false, "Output as HSV (H S% V%)")
|
||||
colorPickCmd.Flags().Bool("cmyk", false, "Output as CMYK (C% M% Y% K%)")
|
||||
colorPickCmd.Flags().Bool("json", false, "Output all formats as JSON")
|
||||
colorPickCmd.Flags().StringVarP(&colorOutputFmt, "output-format", "o", "", "Custom output format template")
|
||||
colorPickCmd.Flags().BoolVarP(&colorAutocopy, "autocopy", "a", false, "Copy result to clipboard")
|
||||
colorPickCmd.Flags().BoolVarP(&colorLowercase, "lowercase", "l", false, "Output hex in lowercase")
|
||||
|
||||
colorPickCmd.MarkFlagsMutuallyExclusive("hex", "rgb", "hsl", "hsv", "cmyk", "json")
|
||||
|
||||
colorCmd.AddCommand(colorPickCmd)
|
||||
}
|
||||
|
||||
func runColorPick(cmd *cobra.Command, args []string) {
|
||||
format := colorpicker.FormatHex // default
|
||||
jsonOutput, _ := cmd.Flags().GetBool("json")
|
||||
|
||||
if rgb, _ := cmd.Flags().GetBool("rgb"); rgb {
|
||||
format = colorpicker.FormatRGB
|
||||
} else if hsl, _ := cmd.Flags().GetBool("hsl"); hsl {
|
||||
format = colorpicker.FormatHSL
|
||||
} else if hsv, _ := cmd.Flags().GetBool("hsv"); hsv {
|
||||
format = colorpicker.FormatHSV
|
||||
} else if cmyk, _ := cmd.Flags().GetBool("cmyk"); cmyk {
|
||||
format = colorpicker.FormatCMYK
|
||||
}
|
||||
|
||||
config := colorpicker.Config{
|
||||
Format: format,
|
||||
CustomFormat: colorOutputFmt,
|
||||
Lowercase: colorLowercase,
|
||||
Autocopy: colorAutocopy,
|
||||
Notify: colorNotify,
|
||||
}
|
||||
|
||||
picker := colorpicker.New(config)
|
||||
color, err := picker.Run()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if color == nil {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
var output string
|
||||
if jsonOutput {
|
||||
jsonStr, err := color.ToJSON()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
output = jsonStr
|
||||
} else {
|
||||
output = color.Format(config.Format, config.Lowercase, config.CustomFormat)
|
||||
}
|
||||
|
||||
if colorAutocopy {
|
||||
copyToClipboard(output)
|
||||
}
|
||||
|
||||
if jsonOutput {
|
||||
fmt.Println(output)
|
||||
} else if color.IsDark() {
|
||||
fmt.Printf("\033[48;2;%d;%d;%dm\033[97m %s \033[0m\n", color.R, color.G, color.B, output)
|
||||
} else {
|
||||
fmt.Printf("\033[48;2;%d;%d;%dm\033[30m %s \033[0m\n", color.R, color.G, color.B, output)
|
||||
}
|
||||
}
|
||||
|
||||
func copyToClipboard(text string) {
|
||||
var cmd *exec.Cmd
|
||||
if _, err := exec.LookPath("wl-copy"); err == nil {
|
||||
cmd = exec.Command("wl-copy", text)
|
||||
} else {
|
||||
fmt.Fprintln(os.Stderr, "wl-copy not found, cannot copy to clipboard")
|
||||
return
|
||||
}
|
||||
|
||||
_ = cmd.Run()
|
||||
}
|
||||
@@ -2,6 +2,8 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
@@ -66,6 +68,10 @@ var ipcCmd = &cobra.Command{
|
||||
Short: "Send IPC commands to running DMS shell",
|
||||
Long: "Send IPC commands to running DMS shell (qs -c dms ipc <args>)",
|
||||
PreRunE: findConfig,
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
_ = findConfig(cmd, args)
|
||||
return getShellIPCCompletions(args, toComplete), cobra.ShellCompDirectiveNoFileComp
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
runShellIPCCommand(args)
|
||||
},
|
||||
@@ -115,6 +121,12 @@ var pluginsInstallCmd = &cobra.Command{
|
||||
Short: "Install a plugin by ID",
|
||||
Long: "Install a DMS plugin from the registry using its ID (e.g., 'myPlugin'). Plugin names with spaces are also supported for backward compatibility.",
|
||||
Args: cobra.ExactArgs(1),
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) != 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
return getAvailablePluginIDs(), cobra.ShellCompDirectiveNoFileComp
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := installPluginCLI(args[0]); err != nil {
|
||||
log.Fatalf("Error installing plugin: %v", err)
|
||||
@@ -127,6 +139,12 @@ var pluginsUninstallCmd = &cobra.Command{
|
||||
Short: "Uninstall a plugin by ID",
|
||||
Long: "Uninstall a DMS plugin using its ID (e.g., 'myPlugin'). Plugin names with spaces are also supported for backward compatibility.",
|
||||
Args: cobra.ExactArgs(1),
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) != 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
return getInstalledPluginIDs(), cobra.ShellCompDirectiveNoFileComp
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := uninstallPluginCLI(args[0]); err != nil {
|
||||
log.Fatalf("Error uninstalling plugin: %v", err)
|
||||
@@ -136,10 +154,59 @@ var pluginsUninstallCmd = &cobra.Command{
|
||||
|
||||
func runVersion(cmd *cobra.Command, args []string) {
|
||||
printASCII()
|
||||
fmt.Printf("%s\n", Version)
|
||||
fmt.Printf("%s\n", formatVersion(Version))
|
||||
}
|
||||
|
||||
// Git builds: dms (git) v0.6.2-XXXX
|
||||
// Stable releases: dms v0.6.2
|
||||
func formatVersion(version string) string {
|
||||
// Arch/Debian/Ubuntu/OpenSUSE git format: 0.6.2+git2264.c5c5ce84
|
||||
re := regexp.MustCompile(`^([\d.]+)\+git(\d+)\.`)
|
||||
if matches := re.FindStringSubmatch(version); matches != nil {
|
||||
return fmt.Sprintf("dms (git) v%s-%s", matches[1], matches[2])
|
||||
}
|
||||
|
||||
// Fedora COPR git format: 0.0.git.2267.d430cae9
|
||||
re = regexp.MustCompile(`^[\d.]+\.git\.(\d+)\.`)
|
||||
if matches := re.FindStringSubmatch(version); matches != nil {
|
||||
baseVersion := getBaseVersion()
|
||||
return fmt.Sprintf("dms (git) v%s-%s", baseVersion, matches[1])
|
||||
}
|
||||
|
||||
// Stable release format: 0.6.2
|
||||
re = regexp.MustCompile(`^([\d.]+)$`)
|
||||
if matches := re.FindStringSubmatch(version); matches != nil {
|
||||
return fmt.Sprintf("dms v%s", matches[1])
|
||||
}
|
||||
|
||||
return fmt.Sprintf("dms %s", version)
|
||||
}
|
||||
|
||||
func getBaseVersion() string {
|
||||
paths := []string{
|
||||
"/usr/share/quickshell/dms/VERSION",
|
||||
"/usr/local/share/quickshell/dms/VERSION",
|
||||
"/etc/xdg/quickshell/dms/VERSION",
|
||||
}
|
||||
|
||||
for _, path := range paths {
|
||||
if content, err := os.ReadFile(path); err == nil {
|
||||
ver := strings.TrimSpace(string(content))
|
||||
ver = strings.TrimPrefix(ver, "v")
|
||||
if re := regexp.MustCompile(`^([\d.]+)`); re.MatchString(ver) {
|
||||
if matches := re.FindStringSubmatch(ver); matches != nil {
|
||||
return matches[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback
|
||||
return "0.6.2"
|
||||
}
|
||||
|
||||
func startDebugServer() error {
|
||||
server.CLIVersion = Version
|
||||
return server.Start(true)
|
||||
}
|
||||
|
||||
@@ -298,6 +365,38 @@ func installPluginCLI(idOrName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getAvailablePluginIDs() []string {
|
||||
registry, err := plugins.NewRegistry()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
pluginList, err := registry.List()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var ids []string
|
||||
for _, p := range pluginList {
|
||||
ids = append(ids, p.ID)
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func getInstalledPluginIDs() []string {
|
||||
manager, err := plugins.NewManager()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
installed, err := manager.ListInstalled()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return installed
|
||||
}
|
||||
|
||||
func uninstallPluginCLI(idOrName string) error {
|
||||
manager, err := plugins.NewManager()
|
||||
if err != nil {
|
||||
@@ -368,8 +467,10 @@ func getCommonCommands() []*cobra.Command {
|
||||
pluginsCmd,
|
||||
dank16Cmd,
|
||||
brightnessCmd,
|
||||
dpmsCmd,
|
||||
keybindsCmd,
|
||||
greeterCmd,
|
||||
setupCmd,
|
||||
colorCmd,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/dank16"
|
||||
@@ -26,9 +25,11 @@ func init() {
|
||||
dank16Cmd.Flags().Bool("alacritty", false, "Output in Alacritty terminal format")
|
||||
dank16Cmd.Flags().Bool("ghostty", false, "Output in Ghostty terminal format")
|
||||
dank16Cmd.Flags().Bool("wezterm", false, "Output in Wezterm terminal format")
|
||||
dank16Cmd.Flags().String("vscode-enrich", "", "Enrich existing VSCode theme file with terminal colors")
|
||||
dank16Cmd.Flags().String("background", "", "Custom background color")
|
||||
dank16Cmd.Flags().String("contrast", "dps", "Contrast algorithm: dps (Delta Phi Star, default) or wcag")
|
||||
_ = dank16Cmd.RegisterFlagCompletionFunc("contrast", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return []string{"dps", "wcag"}, cobra.ShellCompDirectiveNoFileComp
|
||||
})
|
||||
}
|
||||
|
||||
func runDank16(cmd *cobra.Command, args []string) {
|
||||
@@ -44,7 +45,6 @@ func runDank16(cmd *cobra.Command, args []string) {
|
||||
isAlacritty, _ := cmd.Flags().GetBool("alacritty")
|
||||
isGhostty, _ := cmd.Flags().GetBool("ghostty")
|
||||
isWezterm, _ := cmd.Flags().GetBool("wezterm")
|
||||
vscodeEnrich, _ := cmd.Flags().GetString("vscode-enrich")
|
||||
background, _ := cmd.Flags().GetString("background")
|
||||
contrastAlgo, _ := cmd.Flags().GetString("contrast")
|
||||
|
||||
@@ -65,18 +65,7 @@ func runDank16(cmd *cobra.Command, args []string) {
|
||||
|
||||
colors := dank16.GeneratePalette(primaryColor, opts)
|
||||
|
||||
if vscodeEnrich != "" {
|
||||
data, err := os.ReadFile(vscodeEnrich)
|
||||
if err != nil {
|
||||
log.Fatalf("Error reading file: %v", err)
|
||||
}
|
||||
|
||||
enriched, err := dank16.EnrichVSCodeTheme(data, colors)
|
||||
if err != nil {
|
||||
log.Fatalf("Error enriching theme: %v", err)
|
||||
}
|
||||
fmt.Println(string(enriched))
|
||||
} else if isJson {
|
||||
if isJson {
|
||||
fmt.Print(dank16.GenerateJSON(colors))
|
||||
} else if isKitty {
|
||||
fmt.Print(dank16.GenerateKittyTheme(colors))
|
||||
|
||||
105
core/cmd/dms/commands_dpms.go
Normal file
105
core/cmd/dms/commands_dpms.go
Normal file
@@ -0,0 +1,105 @@
|
||||
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),
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) != 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
return getDPMSOutputs(), cobra.ShellCompDirectiveNoFileComp
|
||||
},
|
||||
Run: runDPMSOn,
|
||||
}
|
||||
|
||||
var dpmsOffCmd = &cobra.Command{
|
||||
Use: "off [output]",
|
||||
Short: "Turn display(s) off",
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) != 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
return getDPMSOutputs(), cobra.ShellCompDirectiveNoFileComp
|
||||
},
|
||||
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 getDPMSOutputs() []string {
|
||||
client, err := newDPMSClient()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer client.Close()
|
||||
return client.ListOutputs()
|
||||
}
|
||||
|
||||
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 {
|
||||
case distros.FamilyArch:
|
||||
updateErr = updateArchLinux()
|
||||
case distros.FamilyNix:
|
||||
updateErr = updateNixOS()
|
||||
case distros.FamilySUSE:
|
||||
updateErr = updateOtherDistros()
|
||||
default:
|
||||
@@ -152,27 +150,6 @@ func updateArchLinux() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateNixOS() error {
|
||||
fmt.Println("This will update DankMaterialShell using nix profile.")
|
||||
if !confirmUpdate() {
|
||||
return errdefs.ErrUpdateCancelled
|
||||
}
|
||||
|
||||
fmt.Println("\nRunning: nix profile upgrade github:AvengeMedia/DankMaterialShell")
|
||||
updateCmd := exec.Command("nix", "profile", "upgrade", "github:AvengeMedia/DankMaterialShell")
|
||||
updateCmd.Stdout = os.Stdout
|
||||
updateCmd.Stderr = os.Stderr
|
||||
err := updateCmd.Run()
|
||||
if err != nil {
|
||||
fmt.Printf("Error: Failed to update using nix profile: %v\n", err)
|
||||
fmt.Println("Falling back to git-based update method...")
|
||||
return updateOtherDistros()
|
||||
}
|
||||
|
||||
fmt.Println("dms successfully updated")
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateOtherDistros() error {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/greeter"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
var greeterCmd = &cobra.Command{
|
||||
@@ -217,6 +219,191 @@ func checkGroupExists(groupName string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func disableDisplayManager(dmName string) (bool, error) {
|
||||
state, err := getSystemdServiceState(dmName)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to check %s state: %w", dmName, err)
|
||||
}
|
||||
|
||||
if !state.Exists {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
fmt.Printf("\nChecking %s...\n", dmName)
|
||||
fmt.Printf(" Current state: enabled=%s\n", state.EnabledState)
|
||||
|
||||
actionTaken := false
|
||||
|
||||
if state.NeedsDisable {
|
||||
var disableCmd *exec.Cmd
|
||||
var actionVerb string
|
||||
|
||||
if state.EnabledState == "static" {
|
||||
fmt.Printf(" Masking %s (static service cannot be disabled)...\n", dmName)
|
||||
disableCmd = exec.Command("sudo", "systemctl", "mask", dmName)
|
||||
actionVerb = "masked"
|
||||
} else {
|
||||
fmt.Printf(" Disabling %s...\n", dmName)
|
||||
disableCmd = exec.Command("sudo", "systemctl", "disable", dmName)
|
||||
actionVerb = "disabled"
|
||||
}
|
||||
|
||||
disableCmd.Stdout = os.Stdout
|
||||
disableCmd.Stderr = os.Stderr
|
||||
if err := disableCmd.Run(); err != nil {
|
||||
return actionTaken, fmt.Errorf("failed to disable/mask %s: %w", dmName, err)
|
||||
}
|
||||
|
||||
enabledState, shouldDisable, verifyErr := checkSystemdServiceEnabled(dmName)
|
||||
if verifyErr != nil {
|
||||
fmt.Printf(" ⚠ Warning: Could not verify %s was %s: %v\n", dmName, actionVerb, verifyErr)
|
||||
} else if shouldDisable {
|
||||
return actionTaken, fmt.Errorf("%s is still in state '%s' after %s operation", dmName, enabledState, actionVerb)
|
||||
} else {
|
||||
fmt.Printf(" ✓ %s %s (now: %s)\n", cases.Title(language.English).String(actionVerb), dmName, enabledState)
|
||||
}
|
||||
|
||||
actionTaken = true
|
||||
} else {
|
||||
if state.EnabledState == "masked" || state.EnabledState == "masked-runtime" {
|
||||
fmt.Printf(" ✓ %s is already masked\n", dmName)
|
||||
} else {
|
||||
fmt.Printf(" ✓ %s is already disabled\n", dmName)
|
||||
}
|
||||
}
|
||||
|
||||
return actionTaken, nil
|
||||
}
|
||||
|
||||
func ensureGreetdEnabled() error {
|
||||
fmt.Println("\nChecking greetd service status...")
|
||||
|
||||
state, err := getSystemdServiceState("greetd")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check greetd state: %w", err)
|
||||
}
|
||||
|
||||
if !state.Exists {
|
||||
return fmt.Errorf("greetd service not found. Please install greetd first")
|
||||
}
|
||||
|
||||
fmt.Printf(" Current state: %s\n", state.EnabledState)
|
||||
|
||||
if state.EnabledState == "masked" || state.EnabledState == "masked-runtime" {
|
||||
fmt.Println(" Unmasking greetd...")
|
||||
unmaskCmd := exec.Command("sudo", "systemctl", "unmask", "greetd")
|
||||
unmaskCmd.Stdout = os.Stdout
|
||||
unmaskCmd.Stderr = os.Stderr
|
||||
if err := unmaskCmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to unmask greetd: %w", err)
|
||||
}
|
||||
fmt.Println(" ✓ Unmasked greetd")
|
||||
}
|
||||
|
||||
switch state.EnabledState {
|
||||
case "disabled", "masked", "masked-runtime":
|
||||
fmt.Println(" Enabling greetd service...")
|
||||
enableCmd := exec.Command("sudo", "systemctl", "enable", "greetd")
|
||||
enableCmd.Stdout = os.Stdout
|
||||
enableCmd.Stderr = os.Stderr
|
||||
if err := enableCmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to enable greetd: %w", err)
|
||||
}
|
||||
fmt.Println(" ✓ Enabled greetd service")
|
||||
case "enabled", "enabled-runtime":
|
||||
fmt.Println(" ✓ greetd is already enabled")
|
||||
default:
|
||||
fmt.Printf(" ℹ greetd is in state '%s' (should work, no action needed)\n", state.EnabledState)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensureGraphicalTarget() error {
|
||||
getDefaultCmd := exec.Command("systemctl", "get-default")
|
||||
currentTarget, err := getDefaultCmd.Output()
|
||||
if err != nil {
|
||||
fmt.Println("⚠ Warning: Could not detect current default systemd target")
|
||||
return nil
|
||||
}
|
||||
|
||||
currentTargetStr := strings.TrimSpace(string(currentTarget))
|
||||
if currentTargetStr != "graphical.target" {
|
||||
fmt.Printf("\nSetting graphical.target as default (current: %s)...\n", currentTargetStr)
|
||||
setDefaultCmd := exec.Command("sudo", "systemctl", "set-default", "graphical.target")
|
||||
setDefaultCmd.Stdout = os.Stdout
|
||||
setDefaultCmd.Stderr = os.Stderr
|
||||
if err := setDefaultCmd.Run(); err != nil {
|
||||
fmt.Println("⚠ Warning: Failed to set graphical.target as default")
|
||||
fmt.Println(" Greeter may not start on boot. Run manually:")
|
||||
fmt.Println(" sudo systemctl set-default graphical.target")
|
||||
return nil
|
||||
}
|
||||
fmt.Println("✓ Set graphical.target as default")
|
||||
} else {
|
||||
fmt.Println("✓ Default target already set to graphical.target")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleConflictingDisplayManagers() error {
|
||||
fmt.Println("\n=== Checking for Conflicting Display Managers ===")
|
||||
|
||||
conflictingDMs := []string{"gdm", "gdm3", "lightdm", "sddm", "lxdm", "xdm"}
|
||||
|
||||
disabledAny := false
|
||||
var errors []string
|
||||
|
||||
for _, dm := range conflictingDMs {
|
||||
actionTaken, err := disableDisplayManager(dm)
|
||||
if err != nil {
|
||||
errMsg := fmt.Sprintf("Failed to handle %s: %v", dm, err)
|
||||
errors = append(errors, errMsg)
|
||||
fmt.Printf(" ⚠⚠⚠ ERROR: %s\n", errMsg)
|
||||
continue
|
||||
}
|
||||
if actionTaken {
|
||||
disabledAny = true
|
||||
}
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
fmt.Println("\n╔════════════════════════════════════════════════════════════╗")
|
||||
fmt.Println("║ ⚠⚠⚠ ERRORS OCCURRED ⚠⚠⚠ ║")
|
||||
fmt.Println("╚════════════════════════════════════════════════════════════╝")
|
||||
fmt.Println("\nSome display managers could not be disabled:")
|
||||
for _, err := range errors {
|
||||
fmt.Printf(" ✗ %s\n", err)
|
||||
}
|
||||
fmt.Println("\nThis may prevent greetd from starting properly.")
|
||||
fmt.Println("You may need to manually disable them before greetd will work.")
|
||||
fmt.Println("\nManual commands to try:")
|
||||
for _, dm := range conflictingDMs {
|
||||
fmt.Printf(" sudo systemctl disable %s\n", dm)
|
||||
fmt.Printf(" sudo systemctl mask %s\n", dm)
|
||||
}
|
||||
fmt.Print("\nContinue with greeter enablement anyway? (Y/n): ")
|
||||
|
||||
var response string
|
||||
fmt.Scanln(&response)
|
||||
response = strings.ToLower(strings.TrimSpace(response))
|
||||
|
||||
if response == "n" || response == "no" {
|
||||
return fmt.Errorf("aborted due to display manager conflicts")
|
||||
}
|
||||
fmt.Println("\nContinuing despite errors...")
|
||||
}
|
||||
|
||||
if !disabledAny && len(errors) == 0 {
|
||||
fmt.Println("\n✓ No conflicting display managers found")
|
||||
} else if disabledAny && len(errors) == 0 {
|
||||
fmt.Println("\n✓ Successfully handled all conflicting display managers")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func enableGreeter() error {
|
||||
fmt.Println("=== DMS Greeter Enable ===")
|
||||
fmt.Println()
|
||||
@@ -232,8 +419,29 @@ func enableGreeter() error {
|
||||
}
|
||||
|
||||
configContent := string(data)
|
||||
if strings.Contains(configContent, "dms-greeter") {
|
||||
configAlreadyCorrect := strings.Contains(configContent, "dms-greeter")
|
||||
|
||||
if configAlreadyCorrect {
|
||||
fmt.Println("✓ Greeter is already configured with dms-greeter")
|
||||
|
||||
if err := ensureGraphicalTarget(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := handleConflictingDisplayManagers(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ensureGreetdEnabled(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("\n=== Enable Complete ===")
|
||||
fmt.Println("\nGreeter configuration verified and system state corrected.")
|
||||
fmt.Println("To start the greeter now, run:")
|
||||
fmt.Println(" sudo systemctl start greetd")
|
||||
fmt.Println("\nOr reboot to see the greeter at boot time.")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -322,11 +530,23 @@ func enableGreeter() error {
|
||||
}
|
||||
|
||||
fmt.Printf("✓ Updated greetd configuration to use %s\n", selectedCompositor)
|
||||
|
||||
if err := ensureGraphicalTarget(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := handleConflictingDisplayManagers(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ensureGreetdEnabled(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("\n=== Enable Complete ===")
|
||||
fmt.Println("\nTo start the greeter, run:")
|
||||
fmt.Println("\nTo start the greeter now, run:")
|
||||
fmt.Println(" sudo systemctl start greetd")
|
||||
fmt.Println("\nTo enable on boot, run:")
|
||||
fmt.Println(" sudo systemctl enable --now greetd")
|
||||
fmt.Println("\nOr reboot to see the greeter at boot time.")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -30,16 +30,44 @@ var keybindsShowCmd = &cobra.Command{
|
||||
Short: "Show keybinds for a provider",
|
||||
Long: "Display keybinds/cheatsheet for the specified provider",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: runKeybindsShow,
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) != 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
registry := keybinds.GetDefaultRegistry()
|
||||
return registry.List(), cobra.ShellCompDirectiveNoFileComp
|
||||
},
|
||||
Run: runKeybindsShow,
|
||||
}
|
||||
|
||||
var keybindsSetCmd = &cobra.Command{
|
||||
Use: "set <provider> <key> <action>",
|
||||
Short: "Set a keybind override",
|
||||
Long: "Create or update a keybind override for the specified provider",
|
||||
Args: cobra.ExactArgs(3),
|
||||
Run: runKeybindsSet,
|
||||
}
|
||||
|
||||
var keybindsRemoveCmd = &cobra.Command{
|
||||
Use: "remove <provider> <key>",
|
||||
Short: "Remove a keybind override",
|
||||
Long: "Remove a keybind override from the specified provider",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: runKeybindsRemove,
|
||||
}
|
||||
|
||||
func init() {
|
||||
keybindsShowCmd.Flags().String("hyprland-path", "$HOME/.config/hypr", "Path to Hyprland config directory")
|
||||
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")
|
||||
keybindsShowCmd.Flags().String("path", "", "Override config path for the provider")
|
||||
keybindsSetCmd.Flags().String("desc", "", "Description for hotkey overlay")
|
||||
keybindsSetCmd.Flags().Bool("allow-when-locked", false, "Allow when screen is locked")
|
||||
keybindsSetCmd.Flags().Int("cooldown-ms", 0, "Cooldown in milliseconds")
|
||||
keybindsSetCmd.Flags().Bool("no-repeat", false, "Disable key repeat")
|
||||
keybindsSetCmd.Flags().String("replace-key", "", "Original key to replace (removes old key)")
|
||||
|
||||
keybindsCmd.AddCommand(keybindsListCmd)
|
||||
keybindsCmd.AddCommand(keybindsShowCmd)
|
||||
keybindsCmd.AddCommand(keybindsSetCmd)
|
||||
keybindsCmd.AddCommand(keybindsRemoveCmd)
|
||||
|
||||
keybinds.SetJSONProviderFactory(func(filePath string) (keybinds.Provider, error) {
|
||||
return providers.NewJSONFileProvider(filePath)
|
||||
@@ -66,64 +94,133 @@ func initializeProviders() {
|
||||
log.Warnf("Failed to register Sway provider: %v", err)
|
||||
}
|
||||
|
||||
niriProvider := providers.NewNiriProvider("")
|
||||
if err := registry.Register(niriProvider); err != nil {
|
||||
log.Warnf("Failed to register Niri provider: %v", err)
|
||||
}
|
||||
|
||||
config := keybinds.DefaultDiscoveryConfig()
|
||||
if err := keybinds.AutoDiscoverProviders(registry, config); err != nil {
|
||||
log.Warnf("Failed to auto-discover providers: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func runKeybindsList(cmd *cobra.Command, args []string) {
|
||||
registry := keybinds.GetDefaultRegistry()
|
||||
providers := registry.List()
|
||||
|
||||
if len(providers) == 0 {
|
||||
func runKeybindsList(_ *cobra.Command, _ []string) {
|
||||
providerList := keybinds.GetDefaultRegistry().List()
|
||||
if len(providerList) == 0 {
|
||||
fmt.Fprintln(os.Stdout, "No providers available")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stdout, "Available providers:")
|
||||
for _, name := range providers {
|
||||
for _, name := range providerList {
|
||||
fmt.Fprintf(os.Stdout, " - %s\n", name)
|
||||
}
|
||||
}
|
||||
|
||||
func runKeybindsShow(cmd *cobra.Command, args []string) {
|
||||
providerName := args[0]
|
||||
|
||||
registry := keybinds.GetDefaultRegistry()
|
||||
|
||||
if providerName == "hyprland" {
|
||||
hyprlandPath, _ := cmd.Flags().GetString("hyprland-path")
|
||||
hyprlandProvider := providers.NewHyprlandProvider(hyprlandPath)
|
||||
registry.Register(hyprlandProvider)
|
||||
}
|
||||
|
||||
if providerName == "mangowc" {
|
||||
mangowcPath, _ := cmd.Flags().GetString("mangowc-path")
|
||||
mangowcProvider := providers.NewMangoWCProvider(mangowcPath)
|
||||
registry.Register(mangowcProvider)
|
||||
}
|
||||
|
||||
if providerName == "sway" {
|
||||
swayPath, _ := cmd.Flags().GetString("sway-path")
|
||||
swayProvider := providers.NewSwayProvider(swayPath)
|
||||
registry.Register(swayProvider)
|
||||
}
|
||||
|
||||
provider, err := registry.Get(providerName)
|
||||
if err != nil {
|
||||
log.Fatalf("Error: %v", err)
|
||||
func makeProviderWithPath(name, path string) keybinds.Provider {
|
||||
switch name {
|
||||
case "hyprland":
|
||||
return providers.NewHyprlandProvider(path)
|
||||
case "mangowc":
|
||||
return providers.NewMangoWCProvider(path)
|
||||
case "sway":
|
||||
return providers.NewSwayProvider(path)
|
||||
case "niri":
|
||||
return providers.NewNiriProvider(path)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func printCheatSheet(provider keybinds.Provider) {
|
||||
sheet, err := provider.GetCheatSheet()
|
||||
if err != nil {
|
||||
log.Fatalf("Error getting cheatsheet: %v", err)
|
||||
}
|
||||
|
||||
output, err := json.MarshalIndent(sheet, "", " ")
|
||||
if err != nil {
|
||||
log.Fatalf("Error generating JSON: %v", err)
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stdout, string(output))
|
||||
}
|
||||
|
||||
func runKeybindsShow(cmd *cobra.Command, args []string) {
|
||||
providerName := args[0]
|
||||
customPath, _ := cmd.Flags().GetString("path")
|
||||
|
||||
if customPath != "" {
|
||||
provider := makeProviderWithPath(providerName, customPath)
|
||||
if provider == nil {
|
||||
log.Fatalf("Provider %s does not support custom path", providerName)
|
||||
}
|
||||
printCheatSheet(provider)
|
||||
return
|
||||
}
|
||||
|
||||
provider, err := keybinds.GetDefaultRegistry().Get(providerName)
|
||||
if err != nil {
|
||||
log.Fatalf("Error: %v", err)
|
||||
}
|
||||
printCheatSheet(provider)
|
||||
}
|
||||
|
||||
func getWritableProvider(name string) keybinds.WritableProvider {
|
||||
provider, err := keybinds.GetDefaultRegistry().Get(name)
|
||||
if err != nil {
|
||||
log.Fatalf("Error: %v", err)
|
||||
}
|
||||
writable, ok := provider.(keybinds.WritableProvider)
|
||||
if !ok {
|
||||
log.Fatalf("Provider %s does not support writing keybinds", name)
|
||||
}
|
||||
return writable
|
||||
}
|
||||
|
||||
func runKeybindsSet(cmd *cobra.Command, args []string) {
|
||||
providerName, key, action := args[0], args[1], args[2]
|
||||
writable := getWritableProvider(providerName)
|
||||
|
||||
if replaceKey, _ := cmd.Flags().GetString("replace-key"); replaceKey != "" && replaceKey != key {
|
||||
_ = writable.RemoveBind(replaceKey)
|
||||
}
|
||||
|
||||
options := make(map[string]any)
|
||||
if v, _ := cmd.Flags().GetBool("allow-when-locked"); v {
|
||||
options["allow-when-locked"] = true
|
||||
}
|
||||
if v, _ := cmd.Flags().GetInt("cooldown-ms"); v > 0 {
|
||||
options["cooldown-ms"] = v
|
||||
}
|
||||
if v, _ := cmd.Flags().GetBool("no-repeat"); v {
|
||||
options["repeat"] = false
|
||||
}
|
||||
|
||||
desc, _ := cmd.Flags().GetString("desc")
|
||||
if err := writable.SetBind(key, action, desc, options); err != nil {
|
||||
log.Fatalf("Error setting keybind: %v", err)
|
||||
}
|
||||
|
||||
output, _ := json.MarshalIndent(map[string]any{
|
||||
"success": true,
|
||||
"key": key,
|
||||
"action": action,
|
||||
"path": writable.GetOverridePath(),
|
||||
}, "", " ")
|
||||
fmt.Fprintln(os.Stdout, string(output))
|
||||
}
|
||||
|
||||
func runKeybindsRemove(_ *cobra.Command, args []string) {
|
||||
providerName, key := args[0], args[1]
|
||||
writable := getWritableProvider(providerName)
|
||||
|
||||
if err := writable.RemoveBind(key); err != nil {
|
||||
log.Fatalf("Error removing keybind: %v", err)
|
||||
}
|
||||
|
||||
output, _ := json.MarshalIndent(map[string]any{
|
||||
"success": true,
|
||||
"key": key,
|
||||
"removed": true,
|
||||
}, "", " ")
|
||||
fmt.Fprintln(os.Stdout, string(output))
|
||||
}
|
||||
|
||||
227
core/cmd/dms/commands_open.go
Normal file
227
core/cmd/dms/commands_open.go
Normal file
@@ -0,0 +1,227 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"mime"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
openMimeType string
|
||||
openCategories []string
|
||||
openRequestType string
|
||||
)
|
||||
|
||||
var openCmd = &cobra.Command{
|
||||
Use: "open [target]",
|
||||
Short: "Open a file, URL, or resource with an application picker",
|
||||
Long: `Open a target (URL, file, or other resource) using the DMS application picker.
|
||||
By default, this opens URLs with the browser picker. You can customize the behavior
|
||||
with flags to handle different MIME types or application categories.
|
||||
|
||||
Examples:
|
||||
dms open https://example.com # Open URL with browser picker
|
||||
dms open file.pdf --mime application/pdf # Open PDF with compatible apps
|
||||
dms open document.odt --category Office # Open with office applications
|
||||
dms open --mime image/png image.png # Open image with image viewers`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
runOpen(args[0])
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(openCmd)
|
||||
openCmd.Flags().StringVar(&openMimeType, "mime", "", "MIME type for filtering applications")
|
||||
openCmd.Flags().StringSliceVar(&openCategories, "category", []string{}, "Application categories to filter (e.g., WebBrowser, Office, Graphics)")
|
||||
openCmd.Flags().StringVar(&openRequestType, "type", "url", "Request type (url, file, or custom)")
|
||||
_ = openCmd.RegisterFlagCompletionFunc("type", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return []string{"url", "file", "custom"}, cobra.ShellCompDirectiveNoFileComp
|
||||
})
|
||||
}
|
||||
|
||||
// mimeTypeToCategories maps MIME types to desktop file categories
|
||||
func mimeTypeToCategories(mimeType string) []string {
|
||||
// Split MIME type to get the main type
|
||||
parts := strings.Split(mimeType, "/")
|
||||
if len(parts) < 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
mainType := parts[0]
|
||||
|
||||
switch mainType {
|
||||
case "image":
|
||||
return []string{"Graphics", "Viewer"}
|
||||
case "video":
|
||||
return []string{"Video", "AudioVideo"}
|
||||
case "audio":
|
||||
return []string{"Audio", "AudioVideo"}
|
||||
case "text":
|
||||
if strings.Contains(mimeType, "html") {
|
||||
return []string{"WebBrowser"}
|
||||
}
|
||||
return []string{"TextEditor", "Office"}
|
||||
case "application":
|
||||
if strings.Contains(mimeType, "pdf") {
|
||||
return []string{"Office", "Viewer"}
|
||||
}
|
||||
if strings.Contains(mimeType, "document") || strings.Contains(mimeType, "spreadsheet") ||
|
||||
strings.Contains(mimeType, "presentation") || strings.Contains(mimeType, "msword") ||
|
||||
strings.Contains(mimeType, "ms-excel") || strings.Contains(mimeType, "ms-powerpoint") ||
|
||||
strings.Contains(mimeType, "opendocument") {
|
||||
return []string{"Office"}
|
||||
}
|
||||
if strings.Contains(mimeType, "zip") || strings.Contains(mimeType, "tar") ||
|
||||
strings.Contains(mimeType, "gzip") || strings.Contains(mimeType, "compress") {
|
||||
return []string{"Archiving", "Utility"}
|
||||
}
|
||||
return []string{"Office", "Viewer"}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runOpen(target string) {
|
||||
socketPath, err := server.FindSocket()
|
||||
if err != nil {
|
||||
log.Warnf("DMS socket not found: %v", err)
|
||||
fmt.Println("DMS is not running. Please start DMS first.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
conn, err := net.Dial("unix", socketPath)
|
||||
if err != nil {
|
||||
log.Warnf("DMS socket connection failed: %v", err)
|
||||
fmt.Println("DMS is not running. Please start DMS first.")
|
||||
os.Exit(1)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
buf := make([]byte, 1)
|
||||
for {
|
||||
_, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if buf[0] == '\n' {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Parse file:// URIs to extract the actual file path
|
||||
actualTarget := target
|
||||
detectedMimeType := openMimeType
|
||||
detectedCategories := openCategories
|
||||
detectedRequestType := openRequestType
|
||||
|
||||
log.Infof("Processing target: %s", target)
|
||||
|
||||
if parsedURL, err := url.Parse(target); err == nil && parsedURL.Scheme == "file" {
|
||||
// Extract file path from file:// URI and convert to absolute path
|
||||
actualTarget = parsedURL.Path
|
||||
if absPath, err := filepath.Abs(actualTarget); err == nil {
|
||||
actualTarget = absPath
|
||||
}
|
||||
|
||||
if detectedRequestType == "url" || detectedRequestType == "" {
|
||||
detectedRequestType = "file"
|
||||
}
|
||||
|
||||
log.Infof("Detected file:// URI, extracted absolute path: %s", actualTarget)
|
||||
|
||||
// Auto-detect MIME type if not provided
|
||||
if detectedMimeType == "" {
|
||||
ext := filepath.Ext(actualTarget)
|
||||
if ext != "" {
|
||||
detectedMimeType = mime.TypeByExtension(ext)
|
||||
log.Infof("Detected MIME type from extension %s: %s", ext, detectedMimeType)
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-detect categories based on MIME type if not provided
|
||||
if len(detectedCategories) == 0 && detectedMimeType != "" {
|
||||
detectedCategories = mimeTypeToCategories(detectedMimeType)
|
||||
log.Infof("Detected categories from MIME type: %v", detectedCategories)
|
||||
}
|
||||
} else if strings.HasPrefix(target, "http://") || strings.HasPrefix(target, "https://") {
|
||||
// Handle HTTP(S) URLs
|
||||
if detectedRequestType == "" {
|
||||
detectedRequestType = "url"
|
||||
}
|
||||
log.Infof("Detected HTTP(S) URL")
|
||||
} else if _, err := os.Stat(target); err == nil {
|
||||
// Handle local file paths directly (not file:// URIs)
|
||||
// Convert to absolute path
|
||||
if absPath, err := filepath.Abs(target); err == nil {
|
||||
actualTarget = absPath
|
||||
}
|
||||
|
||||
if detectedRequestType == "url" || detectedRequestType == "" {
|
||||
detectedRequestType = "file"
|
||||
}
|
||||
|
||||
log.Infof("Detected local file path, converted to absolute: %s", actualTarget)
|
||||
|
||||
// Auto-detect MIME type if not provided
|
||||
if detectedMimeType == "" {
|
||||
ext := filepath.Ext(actualTarget)
|
||||
if ext != "" {
|
||||
detectedMimeType = mime.TypeByExtension(ext)
|
||||
log.Infof("Detected MIME type from extension %s: %s", ext, detectedMimeType)
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-detect categories based on MIME type if not provided
|
||||
if len(detectedCategories) == 0 && detectedMimeType != "" {
|
||||
detectedCategories = mimeTypeToCategories(detectedMimeType)
|
||||
log.Infof("Detected categories from MIME type: %v", detectedCategories)
|
||||
}
|
||||
}
|
||||
|
||||
params := map[string]any{
|
||||
"target": actualTarget,
|
||||
}
|
||||
|
||||
if detectedMimeType != "" {
|
||||
params["mimeType"] = detectedMimeType
|
||||
}
|
||||
|
||||
if len(detectedCategories) > 0 {
|
||||
params["categories"] = detectedCategories
|
||||
}
|
||||
|
||||
if detectedRequestType != "" {
|
||||
params["requestType"] = detectedRequestType
|
||||
}
|
||||
|
||||
method := "apppicker.open"
|
||||
if detectedMimeType == "" && len(detectedCategories) == 0 && (strings.HasPrefix(target, "http://") || strings.HasPrefix(target, "https://")) {
|
||||
method = "browser.open"
|
||||
params["url"] = target
|
||||
}
|
||||
|
||||
req := models.Request{
|
||||
ID: 1,
|
||||
Method: method,
|
||||
Params: params,
|
||||
}
|
||||
|
||||
log.Infof("Sending request - Method: %s, Params: %+v", method, params)
|
||||
|
||||
if err := json.NewEncoder(conn).Encode(req); err != nil {
|
||||
log.Fatalf("Failed to send request: %v", err)
|
||||
}
|
||||
|
||||
log.Infof("Request sent successfully")
|
||||
}
|
||||
@@ -1,14 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/config"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/dms"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
@@ -76,14 +74,7 @@ func findConfig(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
}
|
||||
func runInteractiveMode(cmd *cobra.Command, args []string) {
|
||||
detector, err := dms.NewDetector()
|
||||
if err != nil && !errors.Is(err, &distros.UnsupportedDistributionError{}) {
|
||||
log.Fatalf("Error initializing DMS detector: %v", err)
|
||||
} else if errors.Is(err, &distros.UnsupportedDistributionError{}) {
|
||||
log.Error("Interactive mode is not supported on this distribution.")
|
||||
log.Info("Please run 'dms --help' for available commands.")
|
||||
os.Exit(1)
|
||||
}
|
||||
detector, _ := dms.NewDetector()
|
||||
|
||||
if !detector.IsDMSInstalled() {
|
||||
log.Error("DankMaterialShell (DMS) is not detected as installed on this system.")
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@ import (
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server"
|
||||
)
|
||||
|
||||
type ipcTargets map[string][]string
|
||||
|
||||
var isSessionManaged bool
|
||||
|
||||
func execDetachedRestart(targetPID int) {
|
||||
@@ -57,13 +59,18 @@ func getRuntimeDir() string {
|
||||
return os.TempDir()
|
||||
}
|
||||
|
||||
func hasSystemdRun() bool {
|
||||
_, err := exec.LookPath("systemd-run")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func getPIDFilePath() string {
|
||||
return filepath.Join(getRuntimeDir(), fmt.Sprintf("danklinux-%d.pid", os.Getpid()))
|
||||
}
|
||||
|
||||
func writePIDFile(childPID int) error {
|
||||
pidFile := getPIDFilePath()
|
||||
return os.WriteFile(pidFile, []byte(strconv.Itoa(childPID)), 0644)
|
||||
return os.WriteFile(pidFile, []byte(strconv.Itoa(childPID)), 0o644)
|
||||
}
|
||||
|
||||
func removePIDFile() {
|
||||
@@ -139,7 +146,7 @@ func runShellInteractive(session bool) {
|
||||
socketPath := server.GetSocketPath()
|
||||
|
||||
configStateFile := filepath.Join(getRuntimeDir(), "danklinux.path")
|
||||
if err := os.WriteFile(configStateFile, []byte(configPath), 0644); err != nil {
|
||||
if err := os.WriteFile(configStateFile, []byte(configPath), 0o644); err != nil {
|
||||
log.Warnf("Failed to write config state file: %v", err)
|
||||
}
|
||||
defer os.Remove(configStateFile)
|
||||
@@ -165,6 +172,10 @@ func runShellInteractive(session bool) {
|
||||
cmd.Env = append(cmd.Env, "QT_LOGGING_RULES="+qtRules)
|
||||
}
|
||||
|
||||
if isSessionManaged && hasSystemdRun() {
|
||||
cmd.Env = append(cmd.Env, "DMS_DEFAULT_LAUNCH_PREFIX=systemd-run --user --scope")
|
||||
}
|
||||
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err == nil && os.Getenv("DMS_DISABLE_HOT_RELOAD") == "" {
|
||||
if !strings.HasPrefix(configPath, homeDir) {
|
||||
@@ -361,7 +372,7 @@ func runShellDaemon(session bool) {
|
||||
socketPath := server.GetSocketPath()
|
||||
|
||||
configStateFile := filepath.Join(getRuntimeDir(), "danklinux.path")
|
||||
if err := os.WriteFile(configStateFile, []byte(configPath), 0644); err != nil {
|
||||
if err := os.WriteFile(configStateFile, []byte(configPath), 0o644); err != nil {
|
||||
log.Warnf("Failed to write config state file: %v", err)
|
||||
}
|
||||
defer os.Remove(configStateFile)
|
||||
@@ -374,6 +385,7 @@ func runShellDaemon(session bool) {
|
||||
errChan <- fmt.Errorf("server panic: %v", r)
|
||||
}
|
||||
}()
|
||||
server.CLIVersion = Version
|
||||
if err := server.Start(false); err != nil {
|
||||
errChan <- fmt.Errorf("server error: %w", err)
|
||||
}
|
||||
@@ -387,6 +399,10 @@ func runShellDaemon(session bool) {
|
||||
cmd.Env = append(cmd.Env, "QT_LOGGING_RULES="+qtRules)
|
||||
}
|
||||
|
||||
if isSessionManaged && hasSystemdRun() {
|
||||
cmd.Env = append(cmd.Env, "DMS_DEFAULT_LAUNCH_PREFIX=systemd-run --user --scope")
|
||||
}
|
||||
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err == nil && os.Getenv("DMS_DISABLE_HOT_RELOAD") == "" {
|
||||
if !strings.HasPrefix(configPath, homeDir) {
|
||||
@@ -459,6 +475,51 @@ func runShellDaemon(session bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func parseTargetsFromIPCShowOutput(output string) ipcTargets {
|
||||
targets := map[string][]string{}
|
||||
var currentTarget string
|
||||
for _, line := range strings.Split(output, "\n") {
|
||||
if strings.HasPrefix(line, "target ") {
|
||||
currentTarget = strings.TrimSpace(strings.TrimPrefix(line, "target "))
|
||||
}
|
||||
if strings.HasPrefix(line, " function") && currentTarget != "" {
|
||||
currentFunc := strings.TrimPrefix(line, " function ")
|
||||
currentFunc = strings.SplitN(currentFunc, "(", 2)[0]
|
||||
targets[currentTarget] = append(targets[currentTarget], currentFunc)
|
||||
}
|
||||
}
|
||||
return targets
|
||||
}
|
||||
|
||||
func getShellIPCCompletions(args []string, toComplete string) []string {
|
||||
cmdArgs := []string{"-p", configPath, "ipc", "show"}
|
||||
cmd := exec.Command("qs", cmdArgs...)
|
||||
var targets ipcTargets
|
||||
|
||||
if output, err := cmd.Output(); err == nil {
|
||||
log.Debugf("IPC show output: %s", string(output))
|
||||
targets = parseTargetsFromIPCShowOutput(string(output))
|
||||
} else {
|
||||
log.Debugf("Error getting IPC show output for completions: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(args) > 0 && args[0] == "call" {
|
||||
args = args[1:]
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
targetNames := make([]string, 0)
|
||||
targetNames = append(targetNames, "call")
|
||||
for k := range targets {
|
||||
targetNames = append(targetNames, k)
|
||||
}
|
||||
return targetNames
|
||||
}
|
||||
|
||||
return targets[args[0]]
|
||||
}
|
||||
|
||||
func runShellIPCCommand(args []string) {
|
||||
if len(args) == 0 {
|
||||
log.Error("IPC command requires arguments")
|
||||
|
||||
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func commandExists(cmd string) bool {
|
||||
@@ -24,3 +25,68 @@ func isArchPackageInstalled(packageName string) bool {
|
||||
err := cmd.Run()
|
||||
return err == nil
|
||||
}
|
||||
|
||||
type systemdServiceState struct {
|
||||
Name string
|
||||
EnabledState string
|
||||
NeedsDisable bool
|
||||
Exists bool
|
||||
}
|
||||
|
||||
// checkSystemdServiceEnabled returns (state, should_disable, error) for a systemd service
|
||||
func checkSystemdServiceEnabled(serviceName string) (string, bool, error) {
|
||||
cmd := exec.Command("systemctl", "is-enabled", serviceName)
|
||||
output, err := cmd.Output()
|
||||
|
||||
stateStr := strings.TrimSpace(string(output))
|
||||
|
||||
if err != nil {
|
||||
knownStates := []string{"disabled", "masked", "masked-runtime", "not-found", "enabled", "enabled-runtime", "static", "indirect", "alias"}
|
||||
isKnownState := false
|
||||
for _, known := range knownStates {
|
||||
if stateStr == known {
|
||||
isKnownState = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !isKnownState {
|
||||
return stateStr, false, fmt.Errorf("systemctl is-enabled failed: %w (output: %s)", err, stateStr)
|
||||
}
|
||||
}
|
||||
|
||||
shouldDisable := false
|
||||
switch stateStr {
|
||||
case "enabled", "enabled-runtime", "static", "indirect", "alias":
|
||||
shouldDisable = true
|
||||
case "disabled", "masked", "masked-runtime", "not-found":
|
||||
shouldDisable = false
|
||||
default:
|
||||
shouldDisable = true
|
||||
}
|
||||
|
||||
return stateStr, shouldDisable, nil
|
||||
}
|
||||
|
||||
func getSystemdServiceState(serviceName string) (*systemdServiceState, error) {
|
||||
state := &systemdServiceState{
|
||||
Name: serviceName,
|
||||
Exists: false,
|
||||
}
|
||||
|
||||
enabledState, needsDisable, err := checkSystemdServiceEnabled(serviceName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to check enabled state: %w", err)
|
||||
}
|
||||
|
||||
state.EnabledState = enabledState
|
||||
state.NeedsDisable = needsDisable
|
||||
|
||||
if enabledState == "not-found" {
|
||||
state.Exists = false
|
||||
return state, nil
|
||||
}
|
||||
|
||||
state.Exists = true
|
||||
return state, nil
|
||||
}
|
||||
|
||||
47
core/go.mod
47
core/go.mod
@@ -5,61 +5,66 @@ go 1.24.6
|
||||
require (
|
||||
github.com/Wifx/gonetworkmanager/v2 v2.2.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/log v0.4.2
|
||||
github.com/godbus/dbus/v5 v5.1.0
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/godbus/dbus/v5 v5.2.0
|
||||
github.com/holoplot/go-evdev v0.0.0-20250804134636-ab1d56a1fe83
|
||||
github.com/pilebones/go-udev v0.9.1
|
||||
github.com/sblinch/kdl-go v0.0.0-20250930225324-bf4099d4614a
|
||||
github.com/spf13/cobra v1.10.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-20231006140011-7918f672742d
|
||||
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.6.0 // indirect
|
||||
github.com/clipperhouse/stringish v0.1.1 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/go-git/gcfg/v2 v2.0.2 // indirect
|
||||
github.com/go-git/go-billy/v6 v6.0.0-20250627091229-31e2a16eef30 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
github.com/go-git/go-billy/v6 v6.0.0-20251126203821-7f9c95185ee0 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.1 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/kevinburke/ssh_config v1.4.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/pjbgf/sha1cd v0.5.0 // indirect
|
||||
github.com/sergi/go-diff v1.4.0 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
golang.org/x/crypto v0.42.0 // indirect
|
||||
golang.org/x/net v0.44.0 // indirect
|
||||
github.com/stretchr/objx v0.5.3 // indirect
|
||||
golang.org/x/crypto v0.45.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/atotto/clipboard v0.1.4 // 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/x/ansi v0.9.3 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
|
||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.11.2 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.14 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/go-git/go-git/v6 v6.0.0-20250929195514-145daf2492dd
|
||||
github.com/go-git/go-git/v6 v6.0.0-20251128074608-48f817f57805
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
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/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/termenv v0.16.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
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
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/sys v0.36.0
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
golang.org/x/sys v0.38.0
|
||||
golang.org/x/text v0.31.0
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
104
core/go.sum
104
core/go.sum
@@ -14,27 +14,33 @@ 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/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/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGsK6et5taU=
|
||||
github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc=
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
|
||||
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
||||
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
||||
github.com/charmbracelet/colorprofile v0.3.3 h1:DjJzJtLP6/NZ8p7Cgjno0CKGr7wwRJGxWUwh2IyhfAI=
|
||||
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/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
|
||||
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/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=
|
||||
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.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
||||
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||
github.com/charmbracelet/x/ansi v0.11.2 h1:XAG3FSjiVtFvgEgGrNBkCNNYrsucAt8c6bfxHyROLLs=
|
||||
github.com/charmbracelet/x/ansi v0.11.2/go.mod h1:9tY2bzX5SiJCU0iWyskjBeI2BRQfvPqI+J760Mjf+Rg=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.14/go.mod h1:P447lJl49ywBbil/KjCk2HexGh4tEY9LH0/1QrZZ9rA=
|
||||
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
||||
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
||||
github.com/clipperhouse/displaywidth v0.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/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
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.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
|
||||
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -44,23 +50,30 @@ github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc
|
||||
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/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/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/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-20250627091229-31e2a16eef30/go.mod h1:snwvGrbywVFy2d6KJdQ132zapq4aLyzLMgpo79XdEfM=
|
||||
github.com/go-git/go-billy/v6 v6.0.0-20251126203821-7f9c95185ee0 h1:eY5aB2GXiVdgTueBcqsBt53WuJTRZAuCdIS/86Pcq5c=
|
||||
github.com/go-git/go-billy/v6 v6.0.0-20251126203821-7f9c95185ee0/go.mod h1:0NjwVNrwtVFZBReAp5OoGklGJIgJFEbVyHneAr4lc8k=
|
||||
github.com/go-git/go-git-fixtures/v5 v5.1.1 h1:OH8i1ojV9bWfr0ZfasfpgtUXQHQyVS8HXik/V1C099w=
|
||||
github.com/go-git/go-git-fixtures/v5 v5.1.1/go.mod h1:Altk43lx3b1ks+dVoAG2300o5WWUnktvfY3VI6bcaXU=
|
||||
github.com/go-git/go-git/v6 v6.0.0-20250929195514-145daf2492dd h1:30HEd5KKVM7GgMJ1GSNuYxuZXEg8Pdlngp6T51faxoc=
|
||||
github.com/go-git/go-git/v6 v6.0.0-20250929195514-145daf2492dd/go.mod h1:lz8PQr/p79XpFq5ODVBwRJu5LnOF8Et7j95ehqmCMJU=
|
||||
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
||||
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/go-git/go-git/v6 v6.0.0-20251128074608-48f817f57805 h1:jxQ3BzYeErNRvlI/4+0mpwqMzvB4g97U+ksfgvrUEbY=
|
||||
github.com/go-git/go-git/v6 v6.0.0-20251128074608-48f817f57805/go.mod h1:dIwT3uWK1ooHInyVnK2JS5VfQ3peVGYaw2QPqX7uFvs=
|
||||
github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE=
|
||||
github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.2.0 h1:3WexO+U+yg9T70v9FdHr9kCxYlazaAXUhx2VMkbfax8=
|
||||
github.com/godbus/dbus/v5 v5.2.0/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
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/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/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ=
|
||||
@@ -71,66 +84,67 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
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/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
||||
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/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||
github.com/pilebones/go-udev v0.9.1 h1:uN72M1C1fgzhsVmBGEM8w9RD1JY4iVsPZpr+Z6rb3O8=
|
||||
github.com/pilebones/go-udev v0.9.1/go.mod h1:Bgcl07crebF3JSeS4+nuaRvhWFdCeFoBhXXeAp93XNo=
|
||||
github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
|
||||
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/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/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sblinch/kdl-go v0.0.0-20250930225324-bf4099d4614a h1:8ZZwZWIQKC0YVMyaCkbrdeI8faTjD1QBrRAAWc1TjMI=
|
||||
github.com/sblinch/kdl-go v0.0.0-20250930225324-bf4099d4614a/go.mod h1:b3oNGuAKOQzhsCKmuLc/urEOPzgHj6fB8vl8bwTBh28=
|
||||
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/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
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.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
||||
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
||||
github.com/spf13/pflag v1.0.9/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.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
|
||||
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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
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/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
github.com/yaslama/go-wayland/wayland v0.0.0-20250907155644-2874f32d9c34 h1:iTAt1me6SBYsuzrl/CmrxtATPlOG/pVviosM3DhUdKE=
|
||||
github.com/yaslama/go-wayland/wayland v0.0.0-20250907155644-2874f32d9c34/go.mod h1:jzmUN5lUAv2O8e63OvcauV4S30rIZ1BvF/PNYE37vDo=
|
||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
||||
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 h1:DHNhtq3sNNzrvduZZIiFyXWOL9IWaDPHqTnLJp+rCBY=
|
||||
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
|
||||
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
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 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
|
||||
306
core/internal/colorpicker/color.go
Normal file
306
core/internal/colorpicker/color.go
Normal file
@@ -0,0 +1,306 @@
|
||||
package colorpicker
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Color struct {
|
||||
R, G, B, A uint8
|
||||
}
|
||||
|
||||
type OutputFormat int
|
||||
|
||||
const (
|
||||
FormatHex OutputFormat = iota
|
||||
FormatRGB
|
||||
FormatHSL
|
||||
FormatHSV
|
||||
FormatCMYK
|
||||
)
|
||||
|
||||
func ParseFormat(s string) OutputFormat {
|
||||
switch strings.ToLower(s) {
|
||||
case "rgb":
|
||||
return FormatRGB
|
||||
case "hsl":
|
||||
return FormatHSL
|
||||
case "hsv":
|
||||
return FormatHSV
|
||||
case "cmyk":
|
||||
return FormatCMYK
|
||||
default:
|
||||
return FormatHex
|
||||
}
|
||||
}
|
||||
|
||||
func (c Color) ToHex(lowercase bool) string {
|
||||
if lowercase {
|
||||
return fmt.Sprintf("#%02x%02x%02x", c.R, c.G, c.B)
|
||||
}
|
||||
return fmt.Sprintf("#%02X%02X%02X", c.R, c.G, c.B)
|
||||
}
|
||||
|
||||
func (c Color) ToRGB() string {
|
||||
return fmt.Sprintf("%d %d %d", c.R, c.G, c.B)
|
||||
}
|
||||
|
||||
func (c Color) ToHSL() string {
|
||||
h, s, l := rgbToHSL(c.R, c.G, c.B)
|
||||
return fmt.Sprintf("%d %d%% %d%%", h, s, l)
|
||||
}
|
||||
|
||||
func (c Color) ToHSV() string {
|
||||
h, s, v := rgbToHSV(c.R, c.G, c.B)
|
||||
return fmt.Sprintf("%d %d%% %d%%", h, s, v)
|
||||
}
|
||||
|
||||
func (c Color) ToCMYK() string {
|
||||
cy, m, y, k := rgbToCMYK(c.R, c.G, c.B)
|
||||
return fmt.Sprintf("%d%% %d%% %d%% %d%%", cy, m, y, k)
|
||||
}
|
||||
|
||||
func (c Color) Format(format OutputFormat, lowercase bool, customFmt string) string {
|
||||
if customFmt != "" {
|
||||
return c.formatCustom(format, customFmt)
|
||||
}
|
||||
|
||||
switch format {
|
||||
case FormatRGB:
|
||||
return c.ToRGB()
|
||||
case FormatHSL:
|
||||
return c.ToHSL()
|
||||
case FormatHSV:
|
||||
return c.ToHSV()
|
||||
case FormatCMYK:
|
||||
return c.ToCMYK()
|
||||
default:
|
||||
return c.ToHex(lowercase)
|
||||
}
|
||||
}
|
||||
|
||||
func (c Color) formatCustom(format OutputFormat, customFmt string) string {
|
||||
switch format {
|
||||
case FormatRGB:
|
||||
return replaceArgs(customFmt, c.R, c.G, c.B)
|
||||
case FormatHSL:
|
||||
h, s, l := rgbToHSL(c.R, c.G, c.B)
|
||||
return replaceArgs(customFmt, h, s, l)
|
||||
case FormatHSV:
|
||||
h, s, v := rgbToHSV(c.R, c.G, c.B)
|
||||
return replaceArgs(customFmt, h, s, v)
|
||||
case FormatCMYK:
|
||||
cy, m, y, k := rgbToCMYK(c.R, c.G, c.B)
|
||||
return replaceArgs4(customFmt, cy, m, y, k)
|
||||
default:
|
||||
if strings.Contains(customFmt, "{0}") {
|
||||
r := fmt.Sprintf("%02X", c.R)
|
||||
g := fmt.Sprintf("%02X", c.G)
|
||||
b := fmt.Sprintf("%02X", c.B)
|
||||
return replaceArgsStr(customFmt, r, g, b)
|
||||
}
|
||||
return c.ToHex(false)
|
||||
}
|
||||
}
|
||||
|
||||
func replaceArgs[T any](format string, a, b, c T) string {
|
||||
result := format
|
||||
result = strings.ReplaceAll(result, "{0}", fmt.Sprintf("%v", a))
|
||||
result = strings.ReplaceAll(result, "{1}", fmt.Sprintf("%v", b))
|
||||
result = strings.ReplaceAll(result, "{2}", fmt.Sprintf("%v", c))
|
||||
return result
|
||||
}
|
||||
|
||||
func replaceArgs4[T any](format string, a, b, c, d T) string {
|
||||
result := format
|
||||
result = strings.ReplaceAll(result, "{0}", fmt.Sprintf("%v", a))
|
||||
result = strings.ReplaceAll(result, "{1}", fmt.Sprintf("%v", b))
|
||||
result = strings.ReplaceAll(result, "{2}", fmt.Sprintf("%v", c))
|
||||
result = strings.ReplaceAll(result, "{3}", fmt.Sprintf("%v", d))
|
||||
return result
|
||||
}
|
||||
|
||||
func replaceArgsStr(format, a, b, c string) string {
|
||||
result := format
|
||||
result = strings.ReplaceAll(result, "{0}", a)
|
||||
result = strings.ReplaceAll(result, "{1}", b)
|
||||
result = strings.ReplaceAll(result, "{2}", c)
|
||||
return result
|
||||
}
|
||||
|
||||
func rgbToHSL(r, g, b uint8) (int, int, int) {
|
||||
rf := float64(r) / 255.0
|
||||
gf := float64(g) / 255.0
|
||||
bf := float64(b) / 255.0
|
||||
|
||||
maxVal := math.Max(rf, math.Max(gf, bf))
|
||||
minVal := math.Min(rf, math.Min(gf, bf))
|
||||
l := (maxVal + minVal) / 2
|
||||
|
||||
if maxVal == minVal {
|
||||
return 0, 0, int(math.Round(l * 100))
|
||||
}
|
||||
|
||||
d := maxVal - minVal
|
||||
var s float64
|
||||
if l > 0.5 {
|
||||
s = d / (2 - maxVal - minVal)
|
||||
} else {
|
||||
s = d / (maxVal + minVal)
|
||||
}
|
||||
|
||||
var h float64
|
||||
switch maxVal {
|
||||
case rf:
|
||||
h = (gf - bf) / d
|
||||
if gf < bf {
|
||||
h += 6
|
||||
}
|
||||
case gf:
|
||||
h = (bf-rf)/d + 2
|
||||
case bf:
|
||||
h = (rf-gf)/d + 4
|
||||
}
|
||||
h /= 6
|
||||
|
||||
return int(math.Round(h * 360)), int(math.Round(s * 100)), int(math.Round(l * 100))
|
||||
}
|
||||
|
||||
func rgbToHSV(r, g, b uint8) (int, int, int) {
|
||||
rf := float64(r) / 255.0
|
||||
gf := float64(g) / 255.0
|
||||
bf := float64(b) / 255.0
|
||||
|
||||
maxVal := math.Max(rf, math.Max(gf, bf))
|
||||
minVal := math.Min(rf, math.Min(gf, bf))
|
||||
v := maxVal
|
||||
d := maxVal - minVal
|
||||
|
||||
var s float64
|
||||
if maxVal != 0 {
|
||||
s = d / maxVal
|
||||
}
|
||||
|
||||
if maxVal == minVal {
|
||||
return 0, int(math.Round(s * 100)), int(math.Round(v * 100))
|
||||
}
|
||||
|
||||
var h float64
|
||||
switch maxVal {
|
||||
case rf:
|
||||
h = (gf - bf) / d
|
||||
if gf < bf {
|
||||
h += 6
|
||||
}
|
||||
case gf:
|
||||
h = (bf-rf)/d + 2
|
||||
case bf:
|
||||
h = (rf-gf)/d + 4
|
||||
}
|
||||
h /= 6
|
||||
|
||||
return int(math.Round(h * 360)), int(math.Round(s * 100)), int(math.Round(v * 100))
|
||||
}
|
||||
|
||||
func rgbToCMYK(r, g, b uint8) (int, int, int, int) {
|
||||
if r == 0 && g == 0 && b == 0 {
|
||||
return 0, 0, 0, 100
|
||||
}
|
||||
|
||||
rf := float64(r) / 255.0
|
||||
gf := float64(g) / 255.0
|
||||
bf := float64(b) / 255.0
|
||||
|
||||
k := 1 - math.Max(rf, math.Max(gf, bf))
|
||||
c := (1 - rf - k) / (1 - k)
|
||||
m := (1 - gf - k) / (1 - k)
|
||||
y := (1 - bf - k) / (1 - k)
|
||||
|
||||
return int(math.Round(c * 100)), int(math.Round(m * 100)), int(math.Round(y * 100)), int(math.Round(k * 100))
|
||||
}
|
||||
|
||||
func (c Color) Luminance() float64 {
|
||||
r := float64(c.R) / 255.0
|
||||
g := float64(c.G) / 255.0
|
||||
b := float64(c.B) / 255.0
|
||||
|
||||
if r <= 0.03928 {
|
||||
r = r / 12.92
|
||||
} else {
|
||||
r = math.Pow((r+0.055)/1.055, 2.4)
|
||||
}
|
||||
|
||||
if g <= 0.03928 {
|
||||
g = g / 12.92
|
||||
} else {
|
||||
g = math.Pow((g+0.055)/1.055, 2.4)
|
||||
}
|
||||
|
||||
if b <= 0.03928 {
|
||||
b = b / 12.92
|
||||
} else {
|
||||
b = math.Pow((b+0.055)/1.055, 2.4)
|
||||
}
|
||||
|
||||
return 0.2126*r + 0.7152*g + 0.0722*b
|
||||
}
|
||||
|
||||
func (c Color) IsDark() bool {
|
||||
return c.Luminance() < 0.179
|
||||
}
|
||||
|
||||
type ColorJSON struct {
|
||||
Hex string `json:"hex"`
|
||||
RGB struct {
|
||||
R int `json:"r"`
|
||||
G int `json:"g"`
|
||||
B int `json:"b"`
|
||||
} `json:"rgb"`
|
||||
HSL struct {
|
||||
H int `json:"h"`
|
||||
S int `json:"s"`
|
||||
L int `json:"l"`
|
||||
} `json:"hsl"`
|
||||
HSV struct {
|
||||
H int `json:"h"`
|
||||
S int `json:"s"`
|
||||
V int `json:"v"`
|
||||
} `json:"hsv"`
|
||||
CMYK struct {
|
||||
C int `json:"c"`
|
||||
M int `json:"m"`
|
||||
Y int `json:"y"`
|
||||
K int `json:"k"`
|
||||
} `json:"cmyk"`
|
||||
}
|
||||
|
||||
func (c Color) ToJSON() (string, error) {
|
||||
h, s, l := rgbToHSL(c.R, c.G, c.B)
|
||||
hv, sv, v := rgbToHSV(c.R, c.G, c.B)
|
||||
cy, m, y, k := rgbToCMYK(c.R, c.G, c.B)
|
||||
|
||||
data := ColorJSON{
|
||||
Hex: c.ToHex(false),
|
||||
}
|
||||
data.RGB.R = int(c.R)
|
||||
data.RGB.G = int(c.G)
|
||||
data.RGB.B = int(c.B)
|
||||
data.HSL.H = h
|
||||
data.HSL.S = s
|
||||
data.HSL.L = l
|
||||
data.HSV.H = hv
|
||||
data.HSV.S = sv
|
||||
data.HSV.V = v
|
||||
data.CMYK.C = cy
|
||||
data.CMYK.M = m
|
||||
data.CMYK.Y = y
|
||||
data.CMYK.K = k
|
||||
|
||||
bytes, err := json.MarshalIndent(data, "", " ")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(bytes), nil
|
||||
}
|
||||
728
core/internal/colorpicker/picker.go
Normal file
728
core/internal/colorpicker/picker.go
Normal file
@@ -0,0 +1,728 @@
|
||||
package colorpicker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/keyboard_shortcuts_inhibit"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_layer_shell"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_screencopy"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wp_viewporter"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Format OutputFormat
|
||||
CustomFormat string
|
||||
Lowercase bool
|
||||
Autocopy bool
|
||||
Notify bool
|
||||
}
|
||||
|
||||
type Output struct {
|
||||
wlOutput *client.Output
|
||||
name string
|
||||
globalName uint32
|
||||
x, y int32
|
||||
width int32
|
||||
height int32
|
||||
scale int32
|
||||
fractionalScale float64
|
||||
}
|
||||
|
||||
type LayerSurface struct {
|
||||
output *Output
|
||||
state *SurfaceState
|
||||
wlSurface *client.Surface
|
||||
layerSurf *wlr_layer_shell.ZwlrLayerSurfaceV1
|
||||
viewport *wp_viewporter.WpViewport
|
||||
wlPool *client.ShmPool
|
||||
wlBuffer *client.Buffer
|
||||
configured bool
|
||||
hidden bool
|
||||
}
|
||||
|
||||
type Picker struct {
|
||||
config Config
|
||||
|
||||
display *client.Display
|
||||
registry *client.Registry
|
||||
ctx *client.Context
|
||||
|
||||
compositor *client.Compositor
|
||||
shm *client.Shm
|
||||
seat *client.Seat
|
||||
pointer *client.Pointer
|
||||
keyboard *client.Keyboard
|
||||
layerShell *wlr_layer_shell.ZwlrLayerShellV1
|
||||
screencopy *wlr_screencopy.ZwlrScreencopyManagerV1
|
||||
viewporter *wp_viewporter.WpViewporter
|
||||
|
||||
shortcutsInhibitMgr *keyboard_shortcuts_inhibit.ZwpKeyboardShortcutsInhibitManagerV1
|
||||
shortcutsInhibitor *keyboard_shortcuts_inhibit.ZwpKeyboardShortcutsInhibitorV1
|
||||
|
||||
outputs map[uint32]*Output
|
||||
outputsMu sync.Mutex
|
||||
|
||||
surfaces []*LayerSurface
|
||||
activeSurface *LayerSurface
|
||||
|
||||
running bool
|
||||
pickedColor *Color
|
||||
err error
|
||||
}
|
||||
|
||||
func New(config Config) *Picker {
|
||||
return &Picker{
|
||||
config: config,
|
||||
outputs: make(map[uint32]*Output),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Picker) Run() (*Color, error) {
|
||||
if err := p.connect(); err != nil {
|
||||
return nil, fmt.Errorf("wayland connect: %w", err)
|
||||
}
|
||||
defer p.cleanup()
|
||||
|
||||
if err := p.setupRegistry(); err != nil {
|
||||
return nil, fmt.Errorf("registry setup: %w", err)
|
||||
}
|
||||
|
||||
if err := p.roundtrip(); err != nil {
|
||||
return nil, fmt.Errorf("roundtrip: %w", err)
|
||||
}
|
||||
|
||||
if p.screencopy == nil {
|
||||
return nil, fmt.Errorf("compositor does not support wlr-screencopy-unstable-v1")
|
||||
}
|
||||
|
||||
if p.layerShell == nil {
|
||||
return nil, fmt.Errorf("compositor does not support wlr-layer-shell-unstable-v1")
|
||||
}
|
||||
|
||||
if p.seat == nil {
|
||||
return nil, fmt.Errorf("no seat available")
|
||||
}
|
||||
|
||||
if err := p.roundtrip(); err != nil {
|
||||
return nil, fmt.Errorf("roundtrip: %w", err)
|
||||
}
|
||||
|
||||
if err := p.createSurfaces(); err != nil {
|
||||
return nil, fmt.Errorf("create surfaces: %w", err)
|
||||
}
|
||||
|
||||
if err := p.roundtrip(); err != nil {
|
||||
return nil, fmt.Errorf("roundtrip: %w", err)
|
||||
}
|
||||
|
||||
p.running = true
|
||||
for p.running {
|
||||
if err := p.ctx.Dispatch(); err != nil {
|
||||
p.err = err
|
||||
break
|
||||
}
|
||||
|
||||
p.checkDone()
|
||||
}
|
||||
|
||||
if p.err != nil {
|
||||
return nil, p.err
|
||||
}
|
||||
|
||||
return p.pickedColor, nil
|
||||
}
|
||||
|
||||
func (p *Picker) checkDone() {
|
||||
for _, ls := range p.surfaces {
|
||||
picked, cancelled := ls.state.IsDone()
|
||||
switch {
|
||||
case cancelled:
|
||||
p.running = false
|
||||
return
|
||||
case picked:
|
||||
color, ok := ls.state.PickColor()
|
||||
if ok {
|
||||
p.pickedColor = &color
|
||||
}
|
||||
p.running = false
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Picker) connect() error {
|
||||
display, err := client.Connect("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.display = display
|
||||
p.ctx = display.Context()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Picker) roundtrip() error {
|
||||
callback, err := p.display.Sync()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
callback.SetDoneHandler(func(e client.CallbackDoneEvent) {
|
||||
close(done)
|
||||
})
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return nil
|
||||
default:
|
||||
if err := p.ctx.Dispatch(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Picker) setupRegistry() error {
|
||||
registry, err := p.display.GetRegistry()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.registry = registry
|
||||
|
||||
registry.SetGlobalHandler(func(e client.RegistryGlobalEvent) {
|
||||
p.handleGlobal(e)
|
||||
})
|
||||
|
||||
registry.SetGlobalRemoveHandler(func(e client.RegistryGlobalRemoveEvent) {
|
||||
p.outputsMu.Lock()
|
||||
delete(p.outputs, e.Name)
|
||||
p.outputsMu.Unlock()
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Picker) handleGlobal(e client.RegistryGlobalEvent) {
|
||||
switch e.Interface {
|
||||
case client.CompositorInterfaceName:
|
||||
compositor := client.NewCompositor(p.ctx)
|
||||
if err := p.registry.Bind(e.Name, e.Interface, e.Version, compositor); err == nil {
|
||||
p.compositor = compositor
|
||||
}
|
||||
|
||||
case client.ShmInterfaceName:
|
||||
shm := client.NewShm(p.ctx)
|
||||
if err := p.registry.Bind(e.Name, e.Interface, e.Version, shm); err == nil {
|
||||
p.shm = shm
|
||||
}
|
||||
|
||||
case client.SeatInterfaceName:
|
||||
seat := client.NewSeat(p.ctx)
|
||||
if err := p.registry.Bind(e.Name, e.Interface, e.Version, seat); err == nil {
|
||||
p.seat = seat
|
||||
p.setupInput()
|
||||
}
|
||||
|
||||
case client.OutputInterfaceName:
|
||||
output := client.NewOutput(p.ctx)
|
||||
version := e.Version
|
||||
if version > 4 {
|
||||
version = 4
|
||||
}
|
||||
if err := p.registry.Bind(e.Name, e.Interface, version, output); err == nil {
|
||||
p.outputsMu.Lock()
|
||||
p.outputs[e.Name] = &Output{
|
||||
wlOutput: output,
|
||||
globalName: e.Name,
|
||||
scale: 1,
|
||||
fractionalScale: 1.0,
|
||||
}
|
||||
p.outputsMu.Unlock()
|
||||
p.setupOutputHandlers(e.Name, output)
|
||||
}
|
||||
|
||||
case wlr_layer_shell.ZwlrLayerShellV1InterfaceName:
|
||||
layerShell := wlr_layer_shell.NewZwlrLayerShellV1(p.ctx)
|
||||
version := e.Version
|
||||
if version > 4 {
|
||||
version = 4
|
||||
}
|
||||
if err := p.registry.Bind(e.Name, e.Interface, version, layerShell); err == nil {
|
||||
p.layerShell = layerShell
|
||||
}
|
||||
|
||||
case wlr_screencopy.ZwlrScreencopyManagerV1InterfaceName:
|
||||
screencopy := wlr_screencopy.NewZwlrScreencopyManagerV1(p.ctx)
|
||||
version := e.Version
|
||||
if version > 3 {
|
||||
version = 3
|
||||
}
|
||||
if err := p.registry.Bind(e.Name, e.Interface, version, screencopy); err == nil {
|
||||
p.screencopy = screencopy
|
||||
}
|
||||
|
||||
case wp_viewporter.WpViewporterInterfaceName:
|
||||
viewporter := wp_viewporter.NewWpViewporter(p.ctx)
|
||||
if err := p.registry.Bind(e.Name, e.Interface, e.Version, viewporter); err == nil {
|
||||
p.viewporter = viewporter
|
||||
}
|
||||
|
||||
case keyboard_shortcuts_inhibit.ZwpKeyboardShortcutsInhibitManagerV1InterfaceName:
|
||||
mgr := keyboard_shortcuts_inhibit.NewZwpKeyboardShortcutsInhibitManagerV1(p.ctx)
|
||||
if err := p.registry.Bind(e.Name, e.Interface, e.Version, mgr); err == nil {
|
||||
p.shortcutsInhibitMgr = mgr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Picker) setupOutputHandlers(name uint32, output *client.Output) {
|
||||
output.SetGeometryHandler(func(e client.OutputGeometryEvent) {
|
||||
p.outputsMu.Lock()
|
||||
if o, ok := p.outputs[name]; ok {
|
||||
o.x = e.X
|
||||
o.y = e.Y
|
||||
}
|
||||
p.outputsMu.Unlock()
|
||||
})
|
||||
|
||||
output.SetModeHandler(func(e client.OutputModeEvent) {
|
||||
if e.Flags&uint32(client.OutputModeCurrent) == 0 {
|
||||
return
|
||||
}
|
||||
p.outputsMu.Lock()
|
||||
if o, ok := p.outputs[name]; ok {
|
||||
o.width = e.Width
|
||||
o.height = e.Height
|
||||
}
|
||||
p.outputsMu.Unlock()
|
||||
})
|
||||
|
||||
output.SetScaleHandler(func(e client.OutputScaleEvent) {
|
||||
p.outputsMu.Lock()
|
||||
if o, ok := p.outputs[name]; ok {
|
||||
o.scale = e.Factor
|
||||
o.fractionalScale = float64(e.Factor)
|
||||
}
|
||||
p.outputsMu.Unlock()
|
||||
})
|
||||
|
||||
output.SetNameHandler(func(e client.OutputNameEvent) {
|
||||
p.outputsMu.Lock()
|
||||
if o, ok := p.outputs[name]; ok {
|
||||
o.name = e.Name
|
||||
}
|
||||
p.outputsMu.Unlock()
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Picker) createSurfaces() error {
|
||||
p.outputsMu.Lock()
|
||||
outputs := make([]*Output, 0, len(p.outputs))
|
||||
for _, o := range p.outputs {
|
||||
outputs = append(outputs, o)
|
||||
}
|
||||
p.outputsMu.Unlock()
|
||||
|
||||
for _, output := range outputs {
|
||||
ls, err := p.createLayerSurface(output)
|
||||
if err != nil {
|
||||
return fmt.Errorf("output %s: %w", output.name, err)
|
||||
}
|
||||
p.surfaces = append(p.surfaces, ls)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Picker) createLayerSurface(output *Output) (*LayerSurface, error) {
|
||||
surface, err := p.compositor.CreateSurface()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create surface: %w", err)
|
||||
}
|
||||
|
||||
layerSurf, err := p.layerShell.GetLayerSurface(
|
||||
surface,
|
||||
output.wlOutput,
|
||||
uint32(wlr_layer_shell.ZwlrLayerShellV1LayerOverlay),
|
||||
"dms-colorpicker",
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get layer surface: %w", err)
|
||||
}
|
||||
|
||||
ls := &LayerSurface{
|
||||
output: output,
|
||||
state: NewSurfaceState(p.config.Format, p.config.Lowercase),
|
||||
wlSurface: surface,
|
||||
layerSurf: layerSurf,
|
||||
hidden: true, // Start hidden, will show overlay when pointer enters
|
||||
}
|
||||
|
||||
if p.viewporter != nil {
|
||||
vp, err := p.viewporter.GetViewport(surface)
|
||||
if err == nil {
|
||||
ls.viewport = vp
|
||||
}
|
||||
}
|
||||
|
||||
if err := layerSurf.SetAnchor(
|
||||
uint32(wlr_layer_shell.ZwlrLayerSurfaceV1AnchorTop) |
|
||||
uint32(wlr_layer_shell.ZwlrLayerSurfaceV1AnchorBottom) |
|
||||
uint32(wlr_layer_shell.ZwlrLayerSurfaceV1AnchorLeft) |
|
||||
uint32(wlr_layer_shell.ZwlrLayerSurfaceV1AnchorRight),
|
||||
); err != nil {
|
||||
log.Warn("failed to set layer anchor", "err", err)
|
||||
}
|
||||
if err := layerSurf.SetExclusiveZone(-1); err != nil {
|
||||
log.Warn("failed to set exclusive zone", "err", err)
|
||||
}
|
||||
if err := layerSurf.SetKeyboardInteractivity(uint32(wlr_layer_shell.ZwlrLayerSurfaceV1KeyboardInteractivityExclusive)); err != nil {
|
||||
log.Warn("failed to set keyboard interactivity", "err", err)
|
||||
}
|
||||
|
||||
layerSurf.SetConfigureHandler(func(e wlr_layer_shell.ZwlrLayerSurfaceV1ConfigureEvent) {
|
||||
if err := layerSurf.AckConfigure(e.Serial); err != nil {
|
||||
log.Warn("failed to ack configure", "err", err)
|
||||
}
|
||||
if err := ls.state.OnLayerConfigure(int(e.Width), int(e.Height)); err != nil {
|
||||
log.Warn("failed to handle layer configure", "err", err)
|
||||
}
|
||||
ls.configured = true
|
||||
|
||||
scale := p.computeSurfaceScale(ls)
|
||||
ls.state.SetScale(scale)
|
||||
|
||||
if !ls.state.IsReady() {
|
||||
p.captureForSurface(ls)
|
||||
} else {
|
||||
p.redrawSurface(ls)
|
||||
}
|
||||
|
||||
// Request shortcut inhibition once surface is configured
|
||||
p.ensureShortcutsInhibitor(ls)
|
||||
})
|
||||
|
||||
layerSurf.SetClosedHandler(func(e wlr_layer_shell.ZwlrLayerSurfaceV1ClosedEvent) {
|
||||
p.running = false
|
||||
})
|
||||
|
||||
if err := surface.Commit(); err != nil {
|
||||
log.Warn("failed to commit surface", "err", err)
|
||||
}
|
||||
return ls, nil
|
||||
}
|
||||
|
||||
func (p *Picker) computeSurfaceScale(ls *LayerSurface) int32 {
|
||||
out := ls.output
|
||||
if out == nil || out.fractionalScale <= 0 {
|
||||
return 1
|
||||
}
|
||||
|
||||
scale := int32(math.Ceil(out.fractionalScale))
|
||||
if scale <= 0 {
|
||||
scale = 1
|
||||
}
|
||||
return scale
|
||||
}
|
||||
|
||||
func (p *Picker) ensureShortcutsInhibitor(ls *LayerSurface) {
|
||||
if p.shortcutsInhibitMgr == nil || p.seat == nil || p.shortcutsInhibitor != nil {
|
||||
return
|
||||
}
|
||||
|
||||
inhibitor, err := p.shortcutsInhibitMgr.InhibitShortcuts(ls.wlSurface, p.seat)
|
||||
if err != nil {
|
||||
log.Debug("failed to create shortcuts inhibitor", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
p.shortcutsInhibitor = inhibitor
|
||||
|
||||
inhibitor.SetActiveHandler(func(e keyboard_shortcuts_inhibit.ZwpKeyboardShortcutsInhibitorV1ActiveEvent) {
|
||||
log.Debug("shortcuts inhibitor active")
|
||||
})
|
||||
|
||||
inhibitor.SetInactiveHandler(func(e keyboard_shortcuts_inhibit.ZwpKeyboardShortcutsInhibitorV1InactiveEvent) {
|
||||
log.Debug("shortcuts inhibitor deactivated by compositor")
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Picker) captureForSurface(ls *LayerSurface) {
|
||||
frame, err := p.screencopy.CaptureOutput(0, ls.output.wlOutput)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
frame.SetBufferHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1BufferEvent) {
|
||||
if err := ls.state.OnScreencopyBuffer(PixelFormat(e.Format), int(e.Width), int(e.Height), int(e.Stride)); err != nil {
|
||||
log.Error("failed to create screencopy buffer", "err", err)
|
||||
}
|
||||
})
|
||||
|
||||
frame.SetBufferDoneHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1BufferDoneEvent) {
|
||||
screenBuf := ls.state.ScreenBuffer()
|
||||
if screenBuf == nil {
|
||||
return
|
||||
}
|
||||
|
||||
pool, err := p.shm.CreatePool(screenBuf.Fd(), int32(screenBuf.Size()))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
wlBuffer, err := pool.CreateBuffer(0, int32(screenBuf.Width), int32(screenBuf.Height), int32(screenBuf.Stride), uint32(ls.state.screenFormat))
|
||||
if err != nil {
|
||||
pool.Destroy()
|
||||
return
|
||||
}
|
||||
|
||||
if err := frame.Copy(wlBuffer); err != nil {
|
||||
log.Error("failed to copy frame", "err", err)
|
||||
}
|
||||
pool.Destroy()
|
||||
})
|
||||
|
||||
frame.SetFlagsHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1FlagsEvent) {
|
||||
ls.state.OnScreencopyFlags(e.Flags)
|
||||
})
|
||||
|
||||
frame.SetReadyHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1ReadyEvent) {
|
||||
ls.state.OnScreencopyReady()
|
||||
scale := p.computeSurfaceScale(ls)
|
||||
ls.state.SetScale(scale)
|
||||
frame.Destroy()
|
||||
p.redrawSurface(ls)
|
||||
})
|
||||
|
||||
frame.SetFailedHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1FailedEvent) {
|
||||
frame.Destroy()
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Picker) redrawSurface(ls *LayerSurface) {
|
||||
var renderBuf *ShmBuffer
|
||||
if ls.hidden {
|
||||
// When hidden, just show the screenshot without overlay
|
||||
renderBuf = ls.state.RedrawScreenOnly()
|
||||
} else {
|
||||
renderBuf = ls.state.Redraw()
|
||||
}
|
||||
if renderBuf == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if ls.wlPool != nil {
|
||||
ls.wlPool.Destroy()
|
||||
ls.wlPool = nil
|
||||
}
|
||||
if ls.wlBuffer != nil {
|
||||
ls.wlBuffer.Destroy()
|
||||
ls.wlBuffer = nil
|
||||
}
|
||||
|
||||
pool, err := p.shm.CreatePool(renderBuf.Fd(), int32(renderBuf.Size()))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ls.wlPool = pool
|
||||
|
||||
wlBuffer, err := pool.CreateBuffer(0, int32(renderBuf.Width), int32(renderBuf.Height), int32(renderBuf.Stride), uint32(FormatARGB8888))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ls.wlBuffer = wlBuffer
|
||||
|
||||
logicalW, logicalH := ls.state.LogicalSize()
|
||||
if logicalW == 0 || logicalH == 0 {
|
||||
logicalW = int(ls.output.width)
|
||||
logicalH = int(ls.output.height)
|
||||
}
|
||||
|
||||
scale := ls.state.Scale()
|
||||
if scale <= 0 {
|
||||
scale = 1
|
||||
}
|
||||
|
||||
if ls.viewport != nil {
|
||||
srcW := float64(renderBuf.Width) / float64(scale)
|
||||
srcH := float64(renderBuf.Height) / float64(scale)
|
||||
if err := ls.viewport.SetSource(0, 0, srcW, srcH); err != nil {
|
||||
log.Warn("failed to set viewport source", "err", err)
|
||||
}
|
||||
if err := ls.viewport.SetDestination(int32(logicalW), int32(logicalH)); err != nil {
|
||||
log.Warn("failed to set viewport destination", "err", err)
|
||||
}
|
||||
if err := ls.wlSurface.SetBufferScale(scale); err != nil {
|
||||
log.Warn("failed to set buffer scale", "err", err)
|
||||
}
|
||||
} else {
|
||||
if err := ls.wlSurface.SetBufferScale(scale); err != nil {
|
||||
log.Warn("failed to set buffer scale", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := ls.wlSurface.Attach(wlBuffer, 0, 0); err != nil {
|
||||
log.Warn("failed to attach buffer", "err", err)
|
||||
}
|
||||
if err := ls.wlSurface.Damage(0, 0, int32(logicalW), int32(logicalH)); err != nil {
|
||||
log.Warn("failed to damage surface", "err", err)
|
||||
}
|
||||
if err := ls.wlSurface.Commit(); err != nil {
|
||||
log.Warn("failed to commit surface", "err", err)
|
||||
}
|
||||
|
||||
ls.state.SwapBuffers()
|
||||
}
|
||||
|
||||
func (p *Picker) hideSurface(ls *LayerSurface) {
|
||||
if ls == nil || ls.wlSurface == nil || ls.hidden {
|
||||
return
|
||||
}
|
||||
ls.hidden = true
|
||||
// Redraw without the crosshair overlay
|
||||
p.redrawSurface(ls)
|
||||
}
|
||||
|
||||
func (p *Picker) setupInput() {
|
||||
if p.seat == nil {
|
||||
return
|
||||
}
|
||||
|
||||
p.seat.SetCapabilitiesHandler(func(e client.SeatCapabilitiesEvent) {
|
||||
if e.Capabilities&uint32(client.SeatCapabilityPointer) != 0 && p.pointer == nil {
|
||||
pointer, err := p.seat.GetPointer()
|
||||
if err == nil {
|
||||
p.pointer = pointer
|
||||
p.setupPointerHandlers()
|
||||
}
|
||||
}
|
||||
if e.Capabilities&uint32(client.SeatCapabilityKeyboard) != 0 && p.keyboard == nil {
|
||||
keyboard, err := p.seat.GetKeyboard()
|
||||
if err == nil {
|
||||
p.keyboard = keyboard
|
||||
p.setupKeyboardHandlers()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Picker) setupPointerHandlers() {
|
||||
p.pointer.SetEnterHandler(func(e client.PointerEnterEvent) {
|
||||
if err := p.pointer.SetCursor(e.Serial, nil, 0, 0); err != nil {
|
||||
log.Debug("failed to hide cursor", "err", err)
|
||||
}
|
||||
|
||||
p.activeSurface = nil
|
||||
for _, ls := range p.surfaces {
|
||||
if ls.wlSurface.ID() == e.Surface.ID() {
|
||||
p.activeSurface = ls
|
||||
break
|
||||
}
|
||||
}
|
||||
if p.activeSurface == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// If surface was hidden, mark it as visible again
|
||||
if p.activeSurface.hidden {
|
||||
p.activeSurface.hidden = false
|
||||
}
|
||||
|
||||
p.activeSurface.state.OnPointerMotion(e.SurfaceX, e.SurfaceY)
|
||||
p.redrawSurface(p.activeSurface)
|
||||
})
|
||||
|
||||
p.pointer.SetLeaveHandler(func(e client.PointerLeaveEvent) {
|
||||
for _, ls := range p.surfaces {
|
||||
if ls.wlSurface.ID() == e.Surface.ID() {
|
||||
p.hideSurface(ls)
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
p.pointer.SetMotionHandler(func(e client.PointerMotionEvent) {
|
||||
if p.activeSurface == nil {
|
||||
return
|
||||
}
|
||||
p.activeSurface.state.OnPointerMotion(e.SurfaceX, e.SurfaceY)
|
||||
p.redrawSurface(p.activeSurface)
|
||||
})
|
||||
|
||||
p.pointer.SetButtonHandler(func(e client.PointerButtonEvent) {
|
||||
if p.activeSurface == nil {
|
||||
return
|
||||
}
|
||||
p.activeSurface.state.OnPointerButton(e.Button, e.State)
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Picker) setupKeyboardHandlers() {
|
||||
p.keyboard.SetKeyHandler(func(e client.KeyboardKeyEvent) {
|
||||
for _, ls := range p.surfaces {
|
||||
ls.state.OnKey(e.Key, e.State)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Picker) cleanup() {
|
||||
for _, ls := range p.surfaces {
|
||||
if ls.wlBuffer != nil {
|
||||
ls.wlBuffer.Destroy()
|
||||
}
|
||||
if ls.wlPool != nil {
|
||||
ls.wlPool.Destroy()
|
||||
}
|
||||
if ls.viewport != nil {
|
||||
ls.viewport.Destroy()
|
||||
}
|
||||
if ls.layerSurf != nil {
|
||||
ls.layerSurf.Destroy()
|
||||
}
|
||||
if ls.wlSurface != nil {
|
||||
ls.wlSurface.Destroy()
|
||||
}
|
||||
if ls.state != nil {
|
||||
ls.state.Destroy()
|
||||
}
|
||||
}
|
||||
|
||||
if p.shortcutsInhibitor != nil {
|
||||
if err := p.shortcutsInhibitor.Destroy(); err != nil {
|
||||
log.Debug("failed to destroy shortcuts inhibitor", "err", err)
|
||||
}
|
||||
p.shortcutsInhibitor = nil
|
||||
}
|
||||
|
||||
if p.shortcutsInhibitMgr != nil {
|
||||
if err := p.shortcutsInhibitMgr.Destroy(); err != nil {
|
||||
log.Debug("failed to destroy shortcuts inhibit manager", "err", err)
|
||||
}
|
||||
p.shortcutsInhibitMgr = nil
|
||||
}
|
||||
|
||||
if p.viewporter != nil {
|
||||
p.viewporter.Destroy()
|
||||
}
|
||||
|
||||
if p.screencopy != nil {
|
||||
p.screencopy.Destroy()
|
||||
}
|
||||
|
||||
if p.pointer != nil {
|
||||
p.pointer.Release()
|
||||
}
|
||||
|
||||
if p.keyboard != nil {
|
||||
p.keyboard.Release()
|
||||
}
|
||||
|
||||
if p.display != nil {
|
||||
p.ctx.Close()
|
||||
}
|
||||
}
|
||||
93
core/internal/colorpicker/shm.go
Normal file
93
core/internal/colorpicker/shm.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package colorpicker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type ShmBuffer struct {
|
||||
fd int
|
||||
data []byte
|
||||
size int
|
||||
Width int
|
||||
Height int
|
||||
Stride int
|
||||
}
|
||||
|
||||
func CreateShmBuffer(width, height, stride int) (*ShmBuffer, error) {
|
||||
size := stride * height
|
||||
|
||||
fd, err := unix.MemfdCreate("dms-colorpicker", 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create memfd: %w", err)
|
||||
}
|
||||
|
||||
if err := unix.Ftruncate(fd, int64(size)); err != nil {
|
||||
unix.Close(fd)
|
||||
return nil, fmt.Errorf("ftruncate failed: %w", err)
|
||||
}
|
||||
|
||||
data, err := unix.Mmap(fd, 0, size, unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED)
|
||||
if err != nil {
|
||||
unix.Close(fd)
|
||||
return nil, fmt.Errorf("mmap failed: %w", err)
|
||||
}
|
||||
|
||||
return &ShmBuffer{
|
||||
fd: fd,
|
||||
data: data,
|
||||
size: size,
|
||||
Width: width,
|
||||
Height: height,
|
||||
Stride: stride,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *ShmBuffer) Fd() int {
|
||||
return s.fd
|
||||
}
|
||||
|
||||
func (s *ShmBuffer) Size() int {
|
||||
return s.size
|
||||
}
|
||||
|
||||
func (s *ShmBuffer) Data() []byte {
|
||||
return s.data
|
||||
}
|
||||
|
||||
func (s *ShmBuffer) GetPixel(x, y int) Color {
|
||||
if x < 0 || x >= s.Width || y < 0 || y >= s.Height {
|
||||
return Color{}
|
||||
}
|
||||
|
||||
offset := y*s.Stride + x*4
|
||||
|
||||
if offset+3 >= len(s.data) {
|
||||
return Color{}
|
||||
}
|
||||
|
||||
return Color{
|
||||
B: s.data[offset],
|
||||
G: s.data[offset+1],
|
||||
R: s.data[offset+2],
|
||||
A: s.data[offset+3],
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ShmBuffer) Close() error {
|
||||
var firstErr error
|
||||
if s.data != nil {
|
||||
if err := unix.Munmap(s.data); err != nil && firstErr == nil {
|
||||
firstErr = fmt.Errorf("munmap failed: %w", err)
|
||||
}
|
||||
s.data = nil
|
||||
}
|
||||
if s.fd >= 0 {
|
||||
if err := unix.Close(s.fd); err != nil && firstErr == nil {
|
||||
firstErr = fmt.Errorf("close fd failed: %w", err)
|
||||
}
|
||||
s.fd = -1
|
||||
}
|
||||
return firstErr
|
||||
}
|
||||
1111
core/internal/colorpicker/state.go
Normal file
1111
core/internal/colorpicker/state.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -22,8 +22,19 @@ func LocateDMSConfig() (string, error) {
|
||||
primaryPaths = append(primaryPaths, filepath.Join(configHome, "quickshell", "dms"))
|
||||
}
|
||||
|
||||
primaryPaths = append(primaryPaths, "/usr/share/quickshell/dms")
|
||||
// System data directories
|
||||
dataDirs := os.Getenv("XDG_DATA_DIRS")
|
||||
if dataDirs == "" {
|
||||
dataDirs = "/usr/local/share:/usr/share"
|
||||
}
|
||||
|
||||
for _, dir := range strings.Split(dataDirs, ":") {
|
||||
if dir != "" {
|
||||
primaryPaths = append(primaryPaths, filepath.Join(dir, "quickshell", "dms"))
|
||||
}
|
||||
}
|
||||
|
||||
// System config directories (fallback)
|
||||
configDirs := os.Getenv("XDG_CONFIG_DIRS")
|
||||
if configDirs == "" {
|
||||
configDirs = "/etc/xdg"
|
||||
|
||||
@@ -125,6 +125,8 @@ windowrulev2 = noborder, class:^(kitty)$
|
||||
windowrulev2 = float, class:^(firefox)$, title:^(Picture-in-Picture)$
|
||||
windowrulev2 = float, class:^(zoom)$
|
||||
|
||||
# DMS windows floating by default
|
||||
windowrulev2 = float, class:^(org.quickshell)$
|
||||
windowrulev2 = opacity 0.9 0.9, floating:0, focus:0
|
||||
|
||||
layerrule = noanim, ^(quickshell)$
|
||||
@@ -138,8 +140,8 @@ $mod = SUPER
|
||||
bind = $mod, T, exec, {{TERMINAL_COMMAND}}
|
||||
bind = $mod, space, exec, dms ipc call spotlight toggle
|
||||
bind = $mod, V, exec, dms ipc call clipboard toggle
|
||||
bind = $mod, M, exec, dms ipc call processlist toggle
|
||||
bind = $mod, comma, exec, dms ipc call settings toggle
|
||||
bind = $mod, M, exec, dms ipc call processlist focusOrToggle
|
||||
bind = $mod, comma, exec, dms ipc call settings focusOrToggle
|
||||
bind = $mod, N, exec, dms ipc call notifications toggle
|
||||
bind = $mod SHIFT, N, exec, dms ipc call notepad toggle
|
||||
bind = $mod, Y, exec, dms ipc call dankdash wallpaper
|
||||
@@ -151,7 +153,7 @@ bind = $mod SHIFT, Slash, exec, dms ipc call keybinds toggle hyprland
|
||||
# === Security ===
|
||||
bind = $mod ALT, L, exec, dms ipc call lock lock
|
||||
bind = $mod SHIFT, E, exit
|
||||
bind = CTRL ALT, Delete, exec, dms ipc call processlist toggle
|
||||
bind = CTRL ALT, Delete, exec, dms ipc call processlist focusOrToggle
|
||||
|
||||
# === Audio Controls ===
|
||||
bindel = , XF86AudioRaiseVolume, exec, dms ipc call audio increment 3
|
||||
|
||||
@@ -218,6 +218,11 @@ window-rule {
|
||||
geometry-corner-radius 12
|
||||
clip-to-geometry true
|
||||
}
|
||||
// Open dms windows as floating by default
|
||||
window-rule {
|
||||
match app-id=r#"org.quickshell$"#
|
||||
open-floating true
|
||||
}
|
||||
binds {
|
||||
// === System & Overview ===
|
||||
Mod+D { spawn "niri" "msg" "action" "toggle-overview"; }
|
||||
@@ -233,10 +238,10 @@ binds {
|
||||
spawn "dms" "ipc" "call" "clipboard" "toggle";
|
||||
}
|
||||
Mod+M hotkey-overlay-title="Task Manager" {
|
||||
spawn "dms" "ipc" "call" "processlist" "toggle";
|
||||
spawn "dms" "ipc" "call" "processlist" "focusOrToggle";
|
||||
}
|
||||
Mod+Comma hotkey-overlay-title="Settings" {
|
||||
spawn "dms" "ipc" "call" "settings" "toggle";
|
||||
spawn "dms" "ipc" "call" "settings" "focusOrToggle";
|
||||
}
|
||||
Mod+Y hotkey-overlay-title="Browse Wallpapers" {
|
||||
spawn "dms" "ipc" "call" "dankdash" "wallpaper";
|
||||
@@ -250,7 +255,7 @@ binds {
|
||||
}
|
||||
Mod+Shift+E { quit; }
|
||||
Ctrl+Alt+Delete hotkey-overlay-title="Task Manager" {
|
||||
spawn "dms" "ipc" "call" "processlist" "toggle";
|
||||
spawn "dms" "ipc" "call" "processlist" "focusOrToggle";
|
||||
}
|
||||
|
||||
// === Audio Controls ===
|
||||
|
||||
BIN
core/internal/config/embedded/testpage.pdf
Normal file
BIN
core/internal/config/embedded/testpage.pdf
Normal file
Binary file not shown.
6
core/internal/config/testpage.go
Normal file
6
core/internal/config/testpage.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package config
|
||||
|
||||
import _ "embed"
|
||||
|
||||
//go:embed embedded/testpage.pdf
|
||||
var TestPage string
|
||||
@@ -15,6 +15,48 @@ type HSV struct {
|
||||
H, S, V float64
|
||||
}
|
||||
|
||||
type ColorInfo struct {
|
||||
Hex string `json:"hex"`
|
||||
HexStripped string `json:"hex_stripped"`
|
||||
R int `json:"r"`
|
||||
G int `json:"g"`
|
||||
B int `json:"b"`
|
||||
}
|
||||
|
||||
type Palette struct {
|
||||
Color0 ColorInfo `json:"color0"`
|
||||
Color1 ColorInfo `json:"color1"`
|
||||
Color2 ColorInfo `json:"color2"`
|
||||
Color3 ColorInfo `json:"color3"`
|
||||
Color4 ColorInfo `json:"color4"`
|
||||
Color5 ColorInfo `json:"color5"`
|
||||
Color6 ColorInfo `json:"color6"`
|
||||
Color7 ColorInfo `json:"color7"`
|
||||
Color8 ColorInfo `json:"color8"`
|
||||
Color9 ColorInfo `json:"color9"`
|
||||
Color10 ColorInfo `json:"color10"`
|
||||
Color11 ColorInfo `json:"color11"`
|
||||
Color12 ColorInfo `json:"color12"`
|
||||
Color13 ColorInfo `json:"color13"`
|
||||
Color14 ColorInfo `json:"color14"`
|
||||
Color15 ColorInfo `json:"color15"`
|
||||
}
|
||||
|
||||
func NewColorInfo(hex string) ColorInfo {
|
||||
rgb := HexToRGB(hex)
|
||||
stripped := hex
|
||||
if len(hex) > 0 && hex[0] == '#' {
|
||||
stripped = hex[1:]
|
||||
}
|
||||
return ColorInfo{
|
||||
Hex: hex,
|
||||
HexStripped: stripped,
|
||||
R: int(math.Round(rgb.R * 255)),
|
||||
G: int(math.Round(rgb.G * 255)),
|
||||
B: int(math.Round(rgb.B * 255)),
|
||||
}
|
||||
}
|
||||
|
||||
func HexToRGB(hex string) RGB {
|
||||
if hex[0] == '#' {
|
||||
hex = hex[1:]
|
||||
@@ -310,13 +352,13 @@ func DeriveContainer(primary string, isLight bool) string {
|
||||
return RGBToHex(HSVToRGB(HSV{H: hsv.H, S: containerS, V: containerV}))
|
||||
}
|
||||
|
||||
func GeneratePalette(primaryColor string, opts PaletteOptions) []string {
|
||||
func GeneratePalette(primaryColor string, opts PaletteOptions) Palette {
|
||||
baseColor := DeriveContainer(primaryColor, opts.IsLight)
|
||||
|
||||
rgb := HexToRGB(baseColor)
|
||||
hsv := RGBToHSV(rgb)
|
||||
|
||||
palette := make([]string, 0, 16)
|
||||
var palette Palette
|
||||
|
||||
var normalTextTarget, secondaryTarget float64
|
||||
if opts.UseDPS {
|
||||
@@ -335,7 +377,7 @@ func GeneratePalette(primaryColor string, opts PaletteOptions) []string {
|
||||
} else {
|
||||
bgColor = "#1a1a1a"
|
||||
}
|
||||
palette = append(palette, bgColor)
|
||||
palette.Color0 = NewColorInfo(bgColor)
|
||||
|
||||
hueShift := (hsv.H - 0.6) * 0.12
|
||||
satBoost := 1.15
|
||||
@@ -344,39 +386,39 @@ func GeneratePalette(primaryColor string, opts PaletteOptions) []string {
|
||||
var redColor string
|
||||
if opts.IsLight {
|
||||
redColor = RGBToHex(HSVToRGB(HSV{H: redH, S: math.Min(0.80*satBoost, 1.0), V: 0.55}))
|
||||
palette = append(palette, ensureContrastAuto(redColor, bgColor, normalTextTarget, opts))
|
||||
palette.Color1 = NewColorInfo(ensureContrastAuto(redColor, bgColor, normalTextTarget, opts))
|
||||
} else {
|
||||
redColor = RGBToHex(HSVToRGB(HSV{H: redH, S: math.Min(0.65*satBoost, 1.0), V: 0.80}))
|
||||
palette = append(palette, ensureContrastAuto(redColor, bgColor, normalTextTarget, opts))
|
||||
palette.Color1 = NewColorInfo(ensureContrastAuto(redColor, bgColor, normalTextTarget, opts))
|
||||
}
|
||||
|
||||
greenH := math.Mod(0.33+hueShift+1.0, 1.0)
|
||||
var greenColor string
|
||||
if opts.IsLight {
|
||||
greenColor = RGBToHex(HSVToRGB(HSV{H: greenH, S: math.Min(math.Max(hsv.S*0.9, 0.80)*satBoost, 1.0), V: 0.45}))
|
||||
palette = append(palette, ensureContrastAuto(greenColor, bgColor, normalTextTarget, opts))
|
||||
palette.Color2 = NewColorInfo(ensureContrastAuto(greenColor, bgColor, normalTextTarget, opts))
|
||||
} else {
|
||||
greenColor = RGBToHex(HSVToRGB(HSV{H: greenH, S: math.Min(0.42*satBoost, 1.0), V: 0.84}))
|
||||
palette = append(palette, ensureContrastAuto(greenColor, bgColor, normalTextTarget, opts))
|
||||
palette.Color2 = NewColorInfo(ensureContrastAuto(greenColor, bgColor, normalTextTarget, opts))
|
||||
}
|
||||
|
||||
yellowH := math.Mod(0.15+hueShift+1.0, 1.0)
|
||||
var yellowColor string
|
||||
if opts.IsLight {
|
||||
yellowColor = RGBToHex(HSVToRGB(HSV{H: yellowH, S: math.Min(0.75*satBoost, 1.0), V: 0.50}))
|
||||
palette = append(palette, ensureContrastAuto(yellowColor, bgColor, normalTextTarget, opts))
|
||||
palette.Color3 = NewColorInfo(ensureContrastAuto(yellowColor, bgColor, normalTextTarget, opts))
|
||||
} else {
|
||||
yellowColor = RGBToHex(HSVToRGB(HSV{H: yellowH, S: math.Min(0.38*satBoost, 1.0), V: 0.86}))
|
||||
palette = append(palette, ensureContrastAuto(yellowColor, bgColor, normalTextTarget, opts))
|
||||
palette.Color3 = NewColorInfo(ensureContrastAuto(yellowColor, bgColor, normalTextTarget, opts))
|
||||
}
|
||||
|
||||
var blueColor string
|
||||
if opts.IsLight {
|
||||
blueColor = RGBToHex(HSVToRGB(HSV{H: hsv.H, S: math.Max(hsv.S*0.9, 0.7), V: hsv.V * 1.1}))
|
||||
palette = append(palette, ensureContrastAuto(blueColor, bgColor, normalTextTarget, opts))
|
||||
palette.Color4 = NewColorInfo(ensureContrastAuto(blueColor, bgColor, normalTextTarget, opts))
|
||||
} else {
|
||||
blueColor = RGBToHex(HSVToRGB(HSV{H: hsv.H, S: math.Max(hsv.S*0.8, 0.6), V: math.Min(hsv.V*1.6, 1.0)}))
|
||||
palette = append(palette, ensureContrastAuto(blueColor, bgColor, normalTextTarget, opts))
|
||||
palette.Color4 = NewColorInfo(ensureContrastAuto(blueColor, bgColor, normalTextTarget, opts))
|
||||
}
|
||||
|
||||
magH := hsv.H - 0.03
|
||||
@@ -388,65 +430,64 @@ func GeneratePalette(primaryColor string, opts PaletteOptions) []string {
|
||||
hh := RGBToHSV(hr)
|
||||
if opts.IsLight {
|
||||
magColor = RGBToHex(HSVToRGB(HSV{H: hh.H, S: math.Max(hh.S*0.9, 0.7), V: hh.V * 0.85}))
|
||||
palette = append(palette, ensureContrastAuto(magColor, bgColor, normalTextTarget, opts))
|
||||
palette.Color5 = NewColorInfo(ensureContrastAuto(magColor, bgColor, normalTextTarget, opts))
|
||||
} else {
|
||||
magColor = RGBToHex(HSVToRGB(HSV{H: hh.H, S: hh.S * 0.8, V: hh.V * 0.75}))
|
||||
palette = append(palette, ensureContrastAuto(magColor, bgColor, normalTextTarget, opts))
|
||||
palette.Color5 = NewColorInfo(ensureContrastAuto(magColor, bgColor, normalTextTarget, opts))
|
||||
}
|
||||
|
||||
cyanH := hsv.H + 0.08
|
||||
if cyanH > 1.0 {
|
||||
cyanH -= 1.0
|
||||
}
|
||||
palette = append(palette, ensureContrastAuto(primaryColor, bgColor, normalTextTarget, opts))
|
||||
palette.Color6 = NewColorInfo(ensureContrastAuto(primaryColor, bgColor, normalTextTarget, opts))
|
||||
|
||||
if opts.IsLight {
|
||||
palette = append(palette, "#1a1a1a")
|
||||
palette = append(palette, "#2e2e2e")
|
||||
palette.Color7 = NewColorInfo("#1a1a1a")
|
||||
palette.Color8 = NewColorInfo("#2e2e2e")
|
||||
} else {
|
||||
palette = append(palette, "#abb2bf")
|
||||
palette = append(palette, "#5c6370")
|
||||
palette.Color7 = NewColorInfo("#abb2bf")
|
||||
palette.Color8 = NewColorInfo("#5c6370")
|
||||
}
|
||||
|
||||
if opts.IsLight {
|
||||
brightRed := RGBToHex(HSVToRGB(HSV{H: redH, S: math.Min(0.70*satBoost, 1.0), V: 0.65}))
|
||||
palette = append(palette, ensureContrastAuto(brightRed, bgColor, secondaryTarget, opts))
|
||||
palette.Color9 = NewColorInfo(ensureContrastAuto(brightRed, bgColor, secondaryTarget, opts))
|
||||
brightGreen := RGBToHex(HSVToRGB(HSV{H: greenH, S: math.Min(math.Max(hsv.S*0.85, 0.75)*satBoost, 1.0), V: 0.55}))
|
||||
palette = append(palette, ensureContrastAuto(brightGreen, bgColor, secondaryTarget, opts))
|
||||
palette.Color10 = NewColorInfo(ensureContrastAuto(brightGreen, bgColor, secondaryTarget, opts))
|
||||
brightYellow := RGBToHex(HSVToRGB(HSV{H: yellowH, S: math.Min(0.68*satBoost, 1.0), V: 0.60}))
|
||||
palette = append(palette, ensureContrastAuto(brightYellow, bgColor, secondaryTarget, opts))
|
||||
palette.Color11 = NewColorInfo(ensureContrastAuto(brightYellow, bgColor, secondaryTarget, opts))
|
||||
hr := HexToRGB(primaryColor)
|
||||
hh := RGBToHSV(hr)
|
||||
brightBlue := RGBToHex(HSVToRGB(HSV{H: hh.H, S: math.Min(hh.S*1.1, 1.0), V: math.Min(hh.V*1.2, 1.0)}))
|
||||
palette = append(palette, ensureContrastAuto(brightBlue, bgColor, secondaryTarget, opts))
|
||||
palette.Color12 = NewColorInfo(ensureContrastAuto(brightBlue, bgColor, secondaryTarget, opts))
|
||||
brightMag := RGBToHex(HSVToRGB(HSV{H: magH, S: math.Max(hsv.S*0.9, 0.75), V: math.Min(hsv.V*1.25, 1.0)}))
|
||||
palette = append(palette, ensureContrastAuto(brightMag, bgColor, secondaryTarget, opts))
|
||||
palette.Color13 = NewColorInfo(ensureContrastAuto(brightMag, bgColor, secondaryTarget, opts))
|
||||
brightCyan := RGBToHex(HSVToRGB(HSV{H: cyanH, S: math.Max(hsv.S*0.75, 0.65), V: math.Min(hsv.V*1.25, 1.0)}))
|
||||
palette = append(palette, ensureContrastAuto(brightCyan, bgColor, secondaryTarget, opts))
|
||||
palette.Color14 = NewColorInfo(ensureContrastAuto(brightCyan, bgColor, secondaryTarget, opts))
|
||||
} else {
|
||||
brightRed := RGBToHex(HSVToRGB(HSV{H: redH, S: math.Min(0.50*satBoost, 1.0), V: 0.88}))
|
||||
palette = append(palette, ensureContrastAuto(brightRed, bgColor, secondaryTarget, opts))
|
||||
palette.Color9 = NewColorInfo(ensureContrastAuto(brightRed, bgColor, secondaryTarget, opts))
|
||||
brightGreen := RGBToHex(HSVToRGB(HSV{H: greenH, S: math.Min(0.35*satBoost, 1.0), V: 0.88}))
|
||||
palette = append(palette, ensureContrastAuto(brightGreen, bgColor, secondaryTarget, opts))
|
||||
palette.Color10 = NewColorInfo(ensureContrastAuto(brightGreen, bgColor, secondaryTarget, opts))
|
||||
brightYellow := RGBToHex(HSVToRGB(HSV{H: yellowH, S: math.Min(0.30*satBoost, 1.0), V: 0.91}))
|
||||
palette = append(palette, ensureContrastAuto(brightYellow, bgColor, secondaryTarget, opts))
|
||||
// Make it way brighter for type names in dark mode
|
||||
palette.Color11 = NewColorInfo(ensureContrastAuto(brightYellow, bgColor, secondaryTarget, opts))
|
||||
brightBlue := retoneToL(primaryColor, 85.0)
|
||||
palette = append(palette, brightBlue)
|
||||
palette.Color12 = NewColorInfo(brightBlue)
|
||||
brightMag := RGBToHex(HSVToRGB(HSV{H: magH, S: math.Max(hsv.S*0.7, 0.6), V: math.Min(hsv.V*1.3, 0.9)}))
|
||||
palette = append(palette, ensureContrastAuto(brightMag, bgColor, secondaryTarget, opts))
|
||||
palette.Color13 = NewColorInfo(ensureContrastAuto(brightMag, bgColor, secondaryTarget, opts))
|
||||
brightCyanH := hsv.H + 0.02
|
||||
if brightCyanH > 1.0 {
|
||||
brightCyanH -= 1.0
|
||||
}
|
||||
brightCyan := RGBToHex(HSVToRGB(HSV{H: brightCyanH, S: math.Max(hsv.S*0.6, 0.5), V: math.Min(hsv.V*1.2, 0.85)}))
|
||||
palette = append(palette, ensureContrastAuto(brightCyan, bgColor, secondaryTarget, opts))
|
||||
palette.Color14 = NewColorInfo(ensureContrastAuto(brightCyan, bgColor, secondaryTarget, opts))
|
||||
}
|
||||
|
||||
if opts.IsLight {
|
||||
palette = append(palette, "#1a1a1a")
|
||||
palette.Color15 = NewColorInfo("#1a1a1a")
|
||||
} else {
|
||||
palette = append(palette, "#ffffff")
|
||||
palette.Color15 = NewColorInfo("#ffffff")
|
||||
}
|
||||
|
||||
return palette
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package dank16
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
@@ -346,106 +345,36 @@ func TestGeneratePalette(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := GeneratePalette(tt.base, tt.opts)
|
||||
|
||||
if len(result) != 16 {
|
||||
t.Errorf("GeneratePalette returned %d colors, expected 16", len(result))
|
||||
colors := []ColorInfo{
|
||||
result.Color0, result.Color1, result.Color2, result.Color3,
|
||||
result.Color4, result.Color5, result.Color6, result.Color7,
|
||||
result.Color8, result.Color9, result.Color10, result.Color11,
|
||||
result.Color12, result.Color13, result.Color14, result.Color15,
|
||||
}
|
||||
|
||||
for i, color := range result {
|
||||
if len(color) != 7 || color[0] != '#' {
|
||||
t.Errorf("Color at index %d (%s) is not a valid hex color", i, color)
|
||||
for i, color := range colors {
|
||||
if len(color.Hex) != 7 || color.Hex[0] != '#' {
|
||||
t.Errorf("Color at index %d (%s) is not a valid hex color", i, color.Hex)
|
||||
}
|
||||
}
|
||||
|
||||
if tt.opts.Background != "" && result[0] != tt.opts.Background {
|
||||
t.Errorf("Background color = %s, expected %s", result[0], tt.opts.Background)
|
||||
} else if !tt.opts.IsLight && tt.opts.Background == "" && result[0] != "#1a1a1a" {
|
||||
t.Errorf("Dark mode background = %s, expected #1a1a1a", result[0])
|
||||
} else if tt.opts.IsLight && tt.opts.Background == "" && result[0] != "#f8f8f8" {
|
||||
t.Errorf("Light mode background = %s, expected #f8f8f8", result[0])
|
||||
if tt.opts.Background != "" && result.Color0.Hex != tt.opts.Background {
|
||||
t.Errorf("Background color = %s, expected %s", result.Color0.Hex, tt.opts.Background)
|
||||
} else if !tt.opts.IsLight && tt.opts.Background == "" && result.Color0.Hex != "#1a1a1a" {
|
||||
t.Errorf("Dark mode background = %s, expected #1a1a1a", result.Color0.Hex)
|
||||
} else if tt.opts.IsLight && tt.opts.Background == "" && result.Color0.Hex != "#f8f8f8" {
|
||||
t.Errorf("Light mode background = %s, expected #f8f8f8", result.Color0.Hex)
|
||||
}
|
||||
|
||||
if tt.opts.IsLight && result[15] != "#1a1a1a" {
|
||||
t.Errorf("Light mode foreground = %s, expected #1a1a1a", result[15])
|
||||
} else if !tt.opts.IsLight && result[15] != "#ffffff" {
|
||||
t.Errorf("Dark mode foreground = %s, expected #ffffff", result[15])
|
||||
if tt.opts.IsLight && result.Color15.Hex != "#1a1a1a" {
|
||||
t.Errorf("Light mode foreground = %s, expected #1a1a1a", result.Color15.Hex)
|
||||
} else if !tt.opts.IsLight && result.Color15.Hex != "#ffffff" {
|
||||
t.Errorf("Dark mode foreground = %s, expected #ffffff", result.Color15.Hex)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnrichVSCodeTheme(t *testing.T) {
|
||||
colors := GeneratePalette("#625690", PaletteOptions{IsLight: false})
|
||||
|
||||
baseTheme := map[string]interface{}{
|
||||
"name": "Test Theme",
|
||||
"type": "dark",
|
||||
"colors": map[string]interface{}{
|
||||
"editor.background": "#000000",
|
||||
},
|
||||
}
|
||||
|
||||
themeJSON, err := json.Marshal(baseTheme)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal base theme: %v", err)
|
||||
}
|
||||
|
||||
result, err := EnrichVSCodeTheme(themeJSON, colors)
|
||||
if err != nil {
|
||||
t.Fatalf("EnrichVSCodeTheme failed: %v", err)
|
||||
}
|
||||
|
||||
var enriched map[string]interface{}
|
||||
if err := json.Unmarshal(result, &enriched); err != nil {
|
||||
t.Fatalf("Failed to unmarshal result: %v", err)
|
||||
}
|
||||
|
||||
colorsMap, ok := enriched["colors"].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatal("colors is not a map")
|
||||
}
|
||||
|
||||
terminalColors := []string{
|
||||
"terminal.ansiBlack",
|
||||
"terminal.ansiRed",
|
||||
"terminal.ansiGreen",
|
||||
"terminal.ansiYellow",
|
||||
"terminal.ansiBlue",
|
||||
"terminal.ansiMagenta",
|
||||
"terminal.ansiCyan",
|
||||
"terminal.ansiWhite",
|
||||
"terminal.ansiBrightBlack",
|
||||
"terminal.ansiBrightRed",
|
||||
"terminal.ansiBrightGreen",
|
||||
"terminal.ansiBrightYellow",
|
||||
"terminal.ansiBrightBlue",
|
||||
"terminal.ansiBrightMagenta",
|
||||
"terminal.ansiBrightCyan",
|
||||
"terminal.ansiBrightWhite",
|
||||
}
|
||||
|
||||
for i, key := range terminalColors {
|
||||
if val, ok := colorsMap[key]; !ok {
|
||||
t.Errorf("Missing terminal color: %s", key)
|
||||
} else if val != colors[i] {
|
||||
t.Errorf("%s = %s, expected %s", key, val, colors[i])
|
||||
}
|
||||
}
|
||||
|
||||
if colorsMap["editor.background"] != "#000000" {
|
||||
t.Error("Original theme colors should be preserved")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnrichVSCodeThemeInvalidJSON(t *testing.T) {
|
||||
colors := GeneratePalette("#625690", PaletteOptions{IsLight: false})
|
||||
invalidJSON := []byte("{invalid json")
|
||||
|
||||
_, err := EnrichVSCodeTheme(invalidJSON, colors)
|
||||
if err == nil {
|
||||
t.Error("Expected error for invalid JSON, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundTripConversion(t *testing.T) {
|
||||
testColors := []string{"#000000", "#ffffff", "#ff0000", "#00ff00", "#0000ff", "#625690", "#808080"}
|
||||
|
||||
@@ -635,23 +564,26 @@ func TestGeneratePaletteWithDPS(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := GeneratePalette(tt.base, tt.opts)
|
||||
|
||||
if len(result) != 16 {
|
||||
t.Errorf("GeneratePalette returned %d colors, expected 16", len(result))
|
||||
colors := []ColorInfo{
|
||||
result.Color0, result.Color1, result.Color2, result.Color3,
|
||||
result.Color4, result.Color5, result.Color6, result.Color7,
|
||||
result.Color8, result.Color9, result.Color10, result.Color11,
|
||||
result.Color12, result.Color13, result.Color14, result.Color15,
|
||||
}
|
||||
|
||||
for i, color := range result {
|
||||
if len(color) != 7 || color[0] != '#' {
|
||||
t.Errorf("Color at index %d (%s) is not a valid hex color", i, color)
|
||||
for i, color := range colors {
|
||||
if len(color.Hex) != 7 || color.Hex[0] != '#' {
|
||||
t.Errorf("Color at index %d (%s) is not a valid hex color", i, color.Hex)
|
||||
}
|
||||
}
|
||||
|
||||
bgColor := result[0]
|
||||
bgColor := result.Color0.Hex
|
||||
for i := 1; i < 8; i++ {
|
||||
lc := DeltaPhiStarContrast(result[i], bgColor, tt.opts.IsLight)
|
||||
lc := DeltaPhiStarContrast(colors[i].Hex, bgColor, tt.opts.IsLight)
|
||||
minLc := 30.0
|
||||
if lc < minLc && lc > 0 {
|
||||
t.Errorf("Color %d (%s) has insufficient DPS contrast %f with background %s (expected >= %f)",
|
||||
i, result[i], lc, bgColor, minLc)
|
||||
i, colors[i].Hex, lc, bgColor, minLc)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -708,17 +640,26 @@ func TestContrastAlgorithmComparison(t *testing.T) {
|
||||
paletteWCAG := GeneratePalette(base, optsWCAG)
|
||||
paletteDPS := GeneratePalette(base, optsDPS)
|
||||
|
||||
if len(paletteWCAG) != 16 || len(paletteDPS) != 16 {
|
||||
t.Fatal("Both palettes should have 16 colors")
|
||||
wcagColors := []ColorInfo{
|
||||
paletteWCAG.Color0, paletteWCAG.Color1, paletteWCAG.Color2, paletteWCAG.Color3,
|
||||
paletteWCAG.Color4, paletteWCAG.Color5, paletteWCAG.Color6, paletteWCAG.Color7,
|
||||
paletteWCAG.Color8, paletteWCAG.Color9, paletteWCAG.Color10, paletteWCAG.Color11,
|
||||
paletteWCAG.Color12, paletteWCAG.Color13, paletteWCAG.Color14, paletteWCAG.Color15,
|
||||
}
|
||||
dpsColors := []ColorInfo{
|
||||
paletteDPS.Color0, paletteDPS.Color1, paletteDPS.Color2, paletteDPS.Color3,
|
||||
paletteDPS.Color4, paletteDPS.Color5, paletteDPS.Color6, paletteDPS.Color7,
|
||||
paletteDPS.Color8, paletteDPS.Color9, paletteDPS.Color10, paletteDPS.Color11,
|
||||
paletteDPS.Color12, paletteDPS.Color13, paletteDPS.Color14, paletteDPS.Color15,
|
||||
}
|
||||
|
||||
if paletteWCAG[0] != paletteDPS[0] {
|
||||
t.Errorf("Background colors differ: WCAG=%s, DPS=%s", paletteWCAG[0], paletteDPS[0])
|
||||
if paletteWCAG.Color0.Hex != paletteDPS.Color0.Hex {
|
||||
t.Errorf("Background colors differ: WCAG=%s, DPS=%s", paletteWCAG.Color0.Hex, paletteDPS.Color0.Hex)
|
||||
}
|
||||
|
||||
differentCount := 0
|
||||
for i := 0; i < 16; i++ {
|
||||
if paletteWCAG[i] != paletteDPS[i] {
|
||||
if wcagColors[i].Hex != dpsColors[i].Hex {
|
||||
differentCount++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,135 +6,104 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GenerateJSON(colors []string) string {
|
||||
colorMap := make(map[string]string)
|
||||
|
||||
for i, color := range colors {
|
||||
colorMap[fmt.Sprintf("color%d", i)] = color
|
||||
}
|
||||
|
||||
marshalled, _ := json.Marshal(colorMap)
|
||||
|
||||
func GenerateJSON(p Palette) string {
|
||||
marshalled, _ := json.Marshal(p)
|
||||
return string(marshalled)
|
||||
}
|
||||
|
||||
func GenerateKittyTheme(colors []string) string {
|
||||
kittyColors := []struct {
|
||||
name string
|
||||
index int
|
||||
}{
|
||||
{"color0", 0},
|
||||
{"color1", 1},
|
||||
{"color2", 2},
|
||||
{"color3", 3},
|
||||
{"color4", 4},
|
||||
{"color5", 5},
|
||||
{"color6", 6},
|
||||
{"color7", 7},
|
||||
{"color8", 8},
|
||||
{"color9", 9},
|
||||
{"color10", 10},
|
||||
{"color11", 11},
|
||||
{"color12", 12},
|
||||
{"color13", 13},
|
||||
{"color14", 14},
|
||||
{"color15", 15},
|
||||
}
|
||||
|
||||
func GenerateKittyTheme(p Palette) string {
|
||||
var result strings.Builder
|
||||
for _, kc := range kittyColors {
|
||||
fmt.Fprintf(&result, "%s %s\n", kc.name, colors[kc.index])
|
||||
}
|
||||
fmt.Fprintf(&result, "color0 %s\n", p.Color0.Hex)
|
||||
fmt.Fprintf(&result, "color1 %s\n", p.Color1.Hex)
|
||||
fmt.Fprintf(&result, "color2 %s\n", p.Color2.Hex)
|
||||
fmt.Fprintf(&result, "color3 %s\n", p.Color3.Hex)
|
||||
fmt.Fprintf(&result, "color4 %s\n", p.Color4.Hex)
|
||||
fmt.Fprintf(&result, "color5 %s\n", p.Color5.Hex)
|
||||
fmt.Fprintf(&result, "color6 %s\n", p.Color6.Hex)
|
||||
fmt.Fprintf(&result, "color7 %s\n", p.Color7.Hex)
|
||||
fmt.Fprintf(&result, "color8 %s\n", p.Color8.Hex)
|
||||
fmt.Fprintf(&result, "color9 %s\n", p.Color9.Hex)
|
||||
fmt.Fprintf(&result, "color10 %s\n", p.Color10.Hex)
|
||||
fmt.Fprintf(&result, "color11 %s\n", p.Color11.Hex)
|
||||
fmt.Fprintf(&result, "color12 %s\n", p.Color12.Hex)
|
||||
fmt.Fprintf(&result, "color13 %s\n", p.Color13.Hex)
|
||||
fmt.Fprintf(&result, "color14 %s\n", p.Color14.Hex)
|
||||
fmt.Fprintf(&result, "color15 %s\n", p.Color15.Hex)
|
||||
return result.String()
|
||||
}
|
||||
|
||||
func GenerateFootTheme(colors []string) string {
|
||||
footColors := []struct {
|
||||
name string
|
||||
index int
|
||||
}{
|
||||
{"regular0", 0},
|
||||
{"regular1", 1},
|
||||
{"regular2", 2},
|
||||
{"regular3", 3},
|
||||
{"regular4", 4},
|
||||
{"regular5", 5},
|
||||
{"regular6", 6},
|
||||
{"regular7", 7},
|
||||
{"bright0", 8},
|
||||
{"bright1", 9},
|
||||
{"bright2", 10},
|
||||
{"bright3", 11},
|
||||
{"bright4", 12},
|
||||
{"bright5", 13},
|
||||
{"bright6", 14},
|
||||
{"bright7", 15},
|
||||
}
|
||||
|
||||
func GenerateFootTheme(p Palette) string {
|
||||
var result strings.Builder
|
||||
for _, fc := range footColors {
|
||||
fmt.Fprintf(&result, "%s=%s\n", fc.name, strings.TrimPrefix(colors[fc.index], "#"))
|
||||
}
|
||||
fmt.Fprintf(&result, "regular0=%s\n", p.Color0.HexStripped)
|
||||
fmt.Fprintf(&result, "regular1=%s\n", p.Color1.HexStripped)
|
||||
fmt.Fprintf(&result, "regular2=%s\n", p.Color2.HexStripped)
|
||||
fmt.Fprintf(&result, "regular3=%s\n", p.Color3.HexStripped)
|
||||
fmt.Fprintf(&result, "regular4=%s\n", p.Color4.HexStripped)
|
||||
fmt.Fprintf(&result, "regular5=%s\n", p.Color5.HexStripped)
|
||||
fmt.Fprintf(&result, "regular6=%s\n", p.Color6.HexStripped)
|
||||
fmt.Fprintf(&result, "regular7=%s\n", p.Color7.HexStripped)
|
||||
fmt.Fprintf(&result, "bright0=%s\n", p.Color8.HexStripped)
|
||||
fmt.Fprintf(&result, "bright1=%s\n", p.Color9.HexStripped)
|
||||
fmt.Fprintf(&result, "bright2=%s\n", p.Color10.HexStripped)
|
||||
fmt.Fprintf(&result, "bright3=%s\n", p.Color11.HexStripped)
|
||||
fmt.Fprintf(&result, "bright4=%s\n", p.Color12.HexStripped)
|
||||
fmt.Fprintf(&result, "bright5=%s\n", p.Color13.HexStripped)
|
||||
fmt.Fprintf(&result, "bright6=%s\n", p.Color14.HexStripped)
|
||||
fmt.Fprintf(&result, "bright7=%s\n", p.Color15.HexStripped)
|
||||
return result.String()
|
||||
}
|
||||
|
||||
func GenerateAlacrittyTheme(colors []string) string {
|
||||
alacrittyColors := []struct {
|
||||
section string
|
||||
name string
|
||||
index int
|
||||
}{
|
||||
{"normal", "black", 0},
|
||||
{"normal", "red", 1},
|
||||
{"normal", "green", 2},
|
||||
{"normal", "yellow", 3},
|
||||
{"normal", "blue", 4},
|
||||
{"normal", "magenta", 5},
|
||||
{"normal", "cyan", 6},
|
||||
{"normal", "white", 7},
|
||||
{"bright", "black", 8},
|
||||
{"bright", "red", 9},
|
||||
{"bright", "green", 10},
|
||||
{"bright", "yellow", 11},
|
||||
{"bright", "blue", 12},
|
||||
{"bright", "magenta", 13},
|
||||
{"bright", "cyan", 14},
|
||||
{"bright", "white", 15},
|
||||
}
|
||||
|
||||
func GenerateAlacrittyTheme(p Palette) string {
|
||||
var result strings.Builder
|
||||
currentSection := ""
|
||||
for _, ac := range alacrittyColors {
|
||||
if ac.section != currentSection {
|
||||
if currentSection != "" {
|
||||
result.WriteString("\n")
|
||||
}
|
||||
fmt.Fprintf(&result, "[colors.%s]\n", ac.section)
|
||||
currentSection = ac.section
|
||||
}
|
||||
fmt.Fprintf(&result, "%-7s = '%s'\n", ac.name, colors[ac.index])
|
||||
}
|
||||
result.WriteString("[colors.normal]\n")
|
||||
fmt.Fprintf(&result, "black = '%s'\n", p.Color0.Hex)
|
||||
fmt.Fprintf(&result, "red = '%s'\n", p.Color1.Hex)
|
||||
fmt.Fprintf(&result, "green = '%s'\n", p.Color2.Hex)
|
||||
fmt.Fprintf(&result, "yellow = '%s'\n", p.Color3.Hex)
|
||||
fmt.Fprintf(&result, "blue = '%s'\n", p.Color4.Hex)
|
||||
fmt.Fprintf(&result, "magenta = '%s'\n", p.Color5.Hex)
|
||||
fmt.Fprintf(&result, "cyan = '%s'\n", p.Color6.Hex)
|
||||
fmt.Fprintf(&result, "white = '%s'\n", p.Color7.Hex)
|
||||
result.WriteString("\n[colors.bright]\n")
|
||||
fmt.Fprintf(&result, "black = '%s'\n", p.Color8.Hex)
|
||||
fmt.Fprintf(&result, "red = '%s'\n", p.Color9.Hex)
|
||||
fmt.Fprintf(&result, "green = '%s'\n", p.Color10.Hex)
|
||||
fmt.Fprintf(&result, "yellow = '%s'\n", p.Color11.Hex)
|
||||
fmt.Fprintf(&result, "blue = '%s'\n", p.Color12.Hex)
|
||||
fmt.Fprintf(&result, "magenta = '%s'\n", p.Color13.Hex)
|
||||
fmt.Fprintf(&result, "cyan = '%s'\n", p.Color14.Hex)
|
||||
fmt.Fprintf(&result, "white = '%s'\n", p.Color15.Hex)
|
||||
return result.String()
|
||||
}
|
||||
|
||||
func GenerateGhosttyTheme(colors []string) string {
|
||||
func GenerateGhosttyTheme(p Palette) string {
|
||||
var result strings.Builder
|
||||
for i, color := range colors {
|
||||
fmt.Fprintf(&result, "palette = %d=%s\n", i, color)
|
||||
}
|
||||
fmt.Fprintf(&result, "palette = 0=%s\n", p.Color0.Hex)
|
||||
fmt.Fprintf(&result, "palette = 1=%s\n", p.Color1.Hex)
|
||||
fmt.Fprintf(&result, "palette = 2=%s\n", p.Color2.Hex)
|
||||
fmt.Fprintf(&result, "palette = 3=%s\n", p.Color3.Hex)
|
||||
fmt.Fprintf(&result, "palette = 4=%s\n", p.Color4.Hex)
|
||||
fmt.Fprintf(&result, "palette = 5=%s\n", p.Color5.Hex)
|
||||
fmt.Fprintf(&result, "palette = 6=%s\n", p.Color6.Hex)
|
||||
fmt.Fprintf(&result, "palette = 7=%s\n", p.Color7.Hex)
|
||||
fmt.Fprintf(&result, "palette = 8=%s\n", p.Color8.Hex)
|
||||
fmt.Fprintf(&result, "palette = 9=%s\n", p.Color9.Hex)
|
||||
fmt.Fprintf(&result, "palette = 10=%s\n", p.Color10.Hex)
|
||||
fmt.Fprintf(&result, "palette = 11=%s\n", p.Color11.Hex)
|
||||
fmt.Fprintf(&result, "palette = 12=%s\n", p.Color12.Hex)
|
||||
fmt.Fprintf(&result, "palette = 13=%s\n", p.Color13.Hex)
|
||||
fmt.Fprintf(&result, "palette = 14=%s\n", p.Color14.Hex)
|
||||
fmt.Fprintf(&result, "palette = 15=%s\n", p.Color15.Hex)
|
||||
return result.String()
|
||||
}
|
||||
|
||||
func GenerateWeztermTheme(colors []string) string {
|
||||
func GenerateWeztermTheme(p Palette) string {
|
||||
var result strings.Builder
|
||||
labels := []string{"ansi", "brights"}
|
||||
for j, label := range labels {
|
||||
start := j * 8
|
||||
colorSlice := make([]string, 8)
|
||||
for i, color := range colors[start : start+8] {
|
||||
colorSlice[i] = fmt.Sprintf("'%s'", color)
|
||||
}
|
||||
fmt.Fprintf(&result, "%s = [%s]\n", label, strings.Join(colorSlice, ", "))
|
||||
}
|
||||
fmt.Fprintf(&result, "ansi = ['%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s']\n",
|
||||
p.Color0.Hex, p.Color1.Hex, p.Color2.Hex, p.Color3.Hex,
|
||||
p.Color4.Hex, p.Color5.Hex, p.Color6.Hex, p.Color7.Hex)
|
||||
fmt.Fprintf(&result, "brights = ['%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s']\n",
|
||||
p.Color8.Hex, p.Color9.Hex, p.Color10.Hex, p.Color11.Hex,
|
||||
p.Color12.Hex, p.Color13.Hex, p.Color14.Hex, p.Color15.Hex)
|
||||
return result.String()
|
||||
}
|
||||
|
||||
@@ -1,250 +0,0 @@
|
||||
package dank16
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type VSCodeTheme struct {
|
||||
Schema string `json:"$schema"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Colors map[string]string `json:"colors"`
|
||||
TokenColors []VSCodeTokenColor `json:"tokenColors"`
|
||||
SemanticHighlighting bool `json:"semanticHighlighting"`
|
||||
SemanticTokenColors map[string]VSCodeTokenSetting `json:"semanticTokenColors"`
|
||||
}
|
||||
|
||||
type VSCodeTokenColor struct {
|
||||
Scope interface{} `json:"scope"`
|
||||
Settings VSCodeTokenSetting `json:"settings"`
|
||||
}
|
||||
|
||||
type VSCodeTokenSetting struct {
|
||||
Foreground string `json:"foreground,omitempty"`
|
||||
FontStyle string `json:"fontStyle,omitempty"`
|
||||
}
|
||||
|
||||
func updateTokenColor(tc interface{}, scopeToColor map[string]string) {
|
||||
tcMap, ok := tc.(map[string]interface{})
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
scopes, ok := tcMap["scope"].([]interface{})
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
settings, ok := tcMap["settings"].(map[string]interface{})
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
isYaml := hasScopeContaining(scopes, "yaml")
|
||||
|
||||
for _, scope := range scopes {
|
||||
scopeStr, ok := scope.(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if scopeStr == "string" && isYaml {
|
||||
continue
|
||||
}
|
||||
|
||||
if applyColorToScope(settings, scope, scopeToColor) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func applyColorToScope(settings map[string]interface{}, scope interface{}, scopeToColor map[string]string) bool {
|
||||
scopeStr, ok := scope.(string)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
newColor, exists := scopeToColor[scopeStr]
|
||||
if !exists {
|
||||
return false
|
||||
}
|
||||
|
||||
settings["foreground"] = newColor
|
||||
return true
|
||||
}
|
||||
|
||||
func hasScopeContaining(scopes []interface{}, substring string) bool {
|
||||
for _, scope := range scopes {
|
||||
scopeStr, ok := scope.(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
for i := 0; i <= len(scopeStr)-len(substring); i++ {
|
||||
if scopeStr[i:i+len(substring)] == substring {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func EnrichVSCodeTheme(themeData []byte, colors []string) ([]byte, error) {
|
||||
var theme map[string]interface{}
|
||||
if err := json.Unmarshal(themeData, &theme); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
colorsMap, ok := theme["colors"].(map[string]interface{})
|
||||
if !ok {
|
||||
colorsMap = make(map[string]interface{})
|
||||
theme["colors"] = colorsMap
|
||||
}
|
||||
|
||||
bg := colors[0]
|
||||
isLight := false
|
||||
if len(bg) == 7 && bg[0] == '#' {
|
||||
r, g, b := 0, 0, 0
|
||||
fmt.Sscanf(bg[1:], "%02x%02x%02x", &r, &g, &b)
|
||||
luminance := (0.299*float64(r) + 0.587*float64(g) + 0.114*float64(b)) / 255.0
|
||||
isLight = luminance > 0.5
|
||||
}
|
||||
|
||||
if isLight {
|
||||
theme["type"] = "light"
|
||||
} else {
|
||||
theme["type"] = "dark"
|
||||
}
|
||||
|
||||
colorsMap["terminal.ansiBlack"] = colors[0]
|
||||
colorsMap["terminal.ansiRed"] = colors[1]
|
||||
colorsMap["terminal.ansiGreen"] = colors[2]
|
||||
colorsMap["terminal.ansiYellow"] = colors[3]
|
||||
colorsMap["terminal.ansiBlue"] = colors[4]
|
||||
colorsMap["terminal.ansiMagenta"] = colors[5]
|
||||
colorsMap["terminal.ansiCyan"] = colors[6]
|
||||
colorsMap["terminal.ansiWhite"] = colors[7]
|
||||
colorsMap["terminal.ansiBrightBlack"] = colors[8]
|
||||
colorsMap["terminal.ansiBrightRed"] = colors[9]
|
||||
colorsMap["terminal.ansiBrightGreen"] = colors[10]
|
||||
colorsMap["terminal.ansiBrightYellow"] = colors[11]
|
||||
colorsMap["terminal.ansiBrightBlue"] = colors[12]
|
||||
colorsMap["terminal.ansiBrightMagenta"] = colors[13]
|
||||
colorsMap["terminal.ansiBrightCyan"] = colors[14]
|
||||
colorsMap["terminal.ansiBrightWhite"] = colors[15]
|
||||
|
||||
tokenColors, ok := theme["tokenColors"].([]interface{})
|
||||
if ok {
|
||||
scopeToColor := map[string]string{
|
||||
"comment": colors[8],
|
||||
"punctuation.definition.comment": colors[8],
|
||||
"keyword": colors[5],
|
||||
"storage.type": colors[13],
|
||||
"storage.modifier": colors[5],
|
||||
"variable": colors[15],
|
||||
"variable.parameter": colors[7],
|
||||
"meta.object-literal.key": colors[4],
|
||||
"meta.property.object": colors[4],
|
||||
"variable.other.property": colors[4],
|
||||
"constant.other.symbol": colors[12],
|
||||
"constant.numeric": colors[12],
|
||||
"constant.language": colors[12],
|
||||
"constant.character": colors[3],
|
||||
"entity.name.type": colors[12],
|
||||
"support.type": colors[13],
|
||||
"entity.name.class": colors[12],
|
||||
"entity.name.function": colors[2],
|
||||
"support.function": colors[2],
|
||||
"support.class": colors[15],
|
||||
"support.variable": colors[15],
|
||||
"variable.language": colors[12],
|
||||
"entity.name.tag.yaml": colors[12],
|
||||
"string.unquoted.plain.out.yaml": colors[15],
|
||||
"string.unquoted.yaml": colors[15],
|
||||
"string": colors[3],
|
||||
}
|
||||
|
||||
for i, tc := range tokenColors {
|
||||
updateTokenColor(tc, scopeToColor)
|
||||
tokenColors[i] = tc
|
||||
}
|
||||
|
||||
yamlRules := []VSCodeTokenColor{
|
||||
{
|
||||
Scope: "entity.name.tag.yaml",
|
||||
Settings: VSCodeTokenSetting{Foreground: colors[12]},
|
||||
},
|
||||
{
|
||||
Scope: []string{"string.unquoted.plain.out.yaml", "string.unquoted.yaml"},
|
||||
Settings: VSCodeTokenSetting{Foreground: colors[15]},
|
||||
},
|
||||
}
|
||||
|
||||
for _, rule := range yamlRules {
|
||||
tokenColors = append(tokenColors, rule)
|
||||
}
|
||||
|
||||
theme["tokenColors"] = tokenColors
|
||||
}
|
||||
|
||||
if semanticTokenColors, ok := theme["semanticTokenColors"].(map[string]interface{}); ok {
|
||||
updates := map[string]string{
|
||||
"variable": colors[15],
|
||||
"variable.readonly": colors[12],
|
||||
"property": colors[4],
|
||||
"function": colors[2],
|
||||
"method": colors[2],
|
||||
"type": colors[12],
|
||||
"class": colors[12],
|
||||
"typeParameter": colors[13],
|
||||
"enumMember": colors[12],
|
||||
"string": colors[3],
|
||||
"number": colors[12],
|
||||
"comment": colors[8],
|
||||
"keyword": colors[5],
|
||||
"operator": colors[15],
|
||||
"parameter": colors[7],
|
||||
"namespace": colors[15],
|
||||
}
|
||||
|
||||
for key, color := range updates {
|
||||
if existing, ok := semanticTokenColors[key].(map[string]interface{}); ok {
|
||||
existing["foreground"] = color
|
||||
} else {
|
||||
semanticTokenColors[key] = map[string]interface{}{
|
||||
"foreground": color,
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
semanticTokenColors := make(map[string]interface{})
|
||||
updates := map[string]string{
|
||||
"variable": colors[7],
|
||||
"variable.readonly": colors[12],
|
||||
"property": colors[4],
|
||||
"function": colors[2],
|
||||
"method": colors[2],
|
||||
"type": colors[12],
|
||||
"class": colors[12],
|
||||
"typeParameter": colors[13],
|
||||
"enumMember": colors[12],
|
||||
"string": colors[3],
|
||||
"number": colors[12],
|
||||
"comment": colors[8],
|
||||
"keyword": colors[5],
|
||||
"operator": colors[15],
|
||||
"parameter": colors[7],
|
||||
"namespace": colors[15],
|
||||
}
|
||||
|
||||
for key, color := range updates {
|
||||
semanticTokenColors[key] = map[string]interface{}{
|
||||
"foreground": color,
|
||||
}
|
||||
}
|
||||
theme["semanticTokenColors"] = semanticTokenColors
|
||||
}
|
||||
|
||||
return json.MarshalIndent(theme, "", " ")
|
||||
}
|
||||
@@ -37,6 +37,9 @@ func init() {
|
||||
Register("garuda", "#cba6f7", FamilyArch, func(config DistroConfig, logChan chan<- string) Distribution {
|
||||
return NewArchDistribution(config, logChan)
|
||||
})
|
||||
Register("artix", "#1793D1", FamilyArch, func(config DistroConfig, logChan chan<- string) Distribution {
|
||||
return NewArchDistribution(config, logChan)
|
||||
})
|
||||
}
|
||||
|
||||
type ArchDistribution struct {
|
||||
|
||||
@@ -70,7 +70,6 @@ func (d *DebianDistribution) DetectDependenciesWithTerminal(ctx context.Context,
|
||||
|
||||
dependencies = append(dependencies, d.detectMatugen())
|
||||
dependencies = append(dependencies, d.detectDgop())
|
||||
dependencies = append(dependencies, d.detectHyprpicker())
|
||||
dependencies = append(dependencies, d.detectClipboardTools()...)
|
||||
|
||||
return dependencies, nil
|
||||
@@ -139,7 +138,12 @@ func (d *DebianDistribution) packageInstalled(pkg string) bool {
|
||||
}
|
||||
|
||||
func (d *DebianDistribution) GetPackageMapping(wm deps.WindowManager) map[string]PackageMapping {
|
||||
return d.GetPackageMappingWithVariants(wm, make(map[string]deps.PackageVariant))
|
||||
}
|
||||
|
||||
func (d *DebianDistribution) GetPackageMappingWithVariants(wm deps.WindowManager, variants map[string]deps.PackageVariant) map[string]PackageMapping {
|
||||
packages := map[string]PackageMapping{
|
||||
// Standard APT packages
|
||||
"git": {Name: "git", Repository: RepoTypeSystem},
|
||||
"kitty": {Name: "kitty", Repository: RepoTypeSystem},
|
||||
"alacritty": {Name: "alacritty", Repository: RepoTypeSystem},
|
||||
@@ -148,24 +152,54 @@ func (d *DebianDistribution) GetPackageMapping(wm deps.WindowManager) map[string
|
||||
"mate-polkit": {Name: "mate-polkit", Repository: RepoTypeSystem},
|
||||
"accountsservice": {Name: "accountsservice", Repository: RepoTypeSystem},
|
||||
|
||||
"dms (DankMaterialShell)": {Name: "dms", Repository: RepoTypeManual, BuildFunc: "installDankMaterialShell"},
|
||||
"niri": {Name: "niri", Repository: RepoTypeManual, BuildFunc: "installNiri"},
|
||||
"quickshell": {Name: "quickshell", Repository: RepoTypeManual, BuildFunc: "installQuickshell"},
|
||||
"ghostty": {Name: "ghostty", Repository: RepoTypeManual, BuildFunc: "installGhostty"},
|
||||
"matugen": {Name: "matugen", Repository: RepoTypeManual, BuildFunc: "installMatugen"},
|
||||
"dgop": {Name: "dgop", Repository: RepoTypeManual, BuildFunc: "installDgop"},
|
||||
"cliphist": {Name: "cliphist", Repository: RepoTypeManual, BuildFunc: "installCliphist"},
|
||||
"hyprpicker": {Name: "hyprpicker", Repository: RepoTypeManual, BuildFunc: "installHyprpicker"},
|
||||
// DMS packages from OBS with variant support
|
||||
"dms (DankMaterialShell)": d.getDmsMapping(variants["dms (DankMaterialShell)"]),
|
||||
"quickshell": d.getQuickshellMapping(variants["quickshell"]),
|
||||
"matugen": {Name: "matugen", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
|
||||
"dgop": {Name: "dgop", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
|
||||
"cliphist": {Name: "cliphist", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
|
||||
|
||||
// Keep ghostty as manual (no OBS package yet)
|
||||
"ghostty": {Name: "ghostty", Repository: RepoTypeManual, BuildFunc: "installGhostty"},
|
||||
}
|
||||
|
||||
if wm == deps.WindowManagerNiri {
|
||||
packages["niri"] = PackageMapping{Name: "niri", Repository: RepoTypeManual, BuildFunc: "installNiri"}
|
||||
packages["xwayland-satellite"] = PackageMapping{Name: "xwayland-satellite", Repository: RepoTypeManual, BuildFunc: "installXwaylandSatellite"}
|
||||
niriVariant := variants["niri"]
|
||||
packages["niri"] = d.getNiriMapping(niriVariant)
|
||||
packages["xwayland-satellite"] = d.getXwaylandSatelliteMapping(niriVariant)
|
||||
}
|
||||
|
||||
return packages
|
||||
}
|
||||
|
||||
func (d *DebianDistribution) getDmsMapping(variant deps.PackageVariant) PackageMapping {
|
||||
if variant == deps.VariantGit {
|
||||
return PackageMapping{Name: "dms-git", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:dms-git"}
|
||||
}
|
||||
return PackageMapping{Name: "dms", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:dms"}
|
||||
}
|
||||
|
||||
func (d *DebianDistribution) getQuickshellMapping(variant deps.PackageVariant) PackageMapping {
|
||||
if forceQuickshellGit || variant == deps.VariantGit {
|
||||
return PackageMapping{Name: "quickshell-git", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}
|
||||
}
|
||||
return PackageMapping{Name: "quickshell", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}
|
||||
}
|
||||
|
||||
func (d *DebianDistribution) getNiriMapping(variant deps.PackageVariant) PackageMapping {
|
||||
if variant == deps.VariantGit {
|
||||
return PackageMapping{Name: "niri-git", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}
|
||||
}
|
||||
return PackageMapping{Name: "niri", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}
|
||||
}
|
||||
|
||||
func (d *DebianDistribution) getXwaylandSatelliteMapping(variant deps.PackageVariant) PackageMapping {
|
||||
if variant == deps.VariantGit {
|
||||
return PackageMapping{Name: "xwayland-satellite-git", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}
|
||||
}
|
||||
return PackageMapping{Name: "xwayland-satellite", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}
|
||||
}
|
||||
|
||||
func (d *DebianDistribution) InstallPrerequisites(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhasePrerequisites,
|
||||
@@ -209,7 +243,7 @@ func (d *DebianDistribution) InstallPrerequisites(ctx context.Context, sudoPassw
|
||||
}
|
||||
|
||||
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 {
|
||||
return fmt.Errorf("failed to install development tools: %w", err)
|
||||
}
|
||||
@@ -238,8 +272,23 @@ func (d *DebianDistribution) InstallPackages(ctx context.Context, dependencies [
|
||||
return fmt.Errorf("failed to install prerequisites: %w", err)
|
||||
}
|
||||
|
||||
systemPkgs, manualPkgs, variantMap := d.categorizePackages(dependencies, wm, reinstallFlags, disabledFlags)
|
||||
systemPkgs, obsPkgs, manualPkgs, variantMap := d.categorizePackages(dependencies, wm, reinstallFlags, disabledFlags)
|
||||
|
||||
// Enable OBS repositories
|
||||
if len(obsPkgs) > 0 {
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseSystemPackages,
|
||||
Progress: 0.15,
|
||||
Step: "Enabling OBS repositories...",
|
||||
IsComplete: false,
|
||||
LogOutput: "Setting up OBS repositories for additional packages",
|
||||
}
|
||||
if err := d.enableOBSRepos(ctx, obsPkgs, sudoPassword, progressChan); err != nil {
|
||||
return fmt.Errorf("failed to enable OBS repositories: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// System Packages
|
||||
if len(systemPkgs) > 0 {
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseSystemPackages,
|
||||
@@ -254,6 +303,22 @@ func (d *DebianDistribution) InstallPackages(ctx context.Context, dependencies [
|
||||
}
|
||||
}
|
||||
|
||||
// OBS Packages
|
||||
obsPkgNames := d.extractPackageNames(obsPkgs)
|
||||
if len(obsPkgNames) > 0 {
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseAURPackages,
|
||||
Progress: 0.65,
|
||||
Step: fmt.Sprintf("Installing %d OBS packages...", len(obsPkgNames)),
|
||||
IsComplete: false,
|
||||
LogOutput: fmt.Sprintf("Installing OBS packages: %s", strings.Join(obsPkgNames, ", ")),
|
||||
}
|
||||
if err := d.installAPTPackages(ctx, obsPkgNames, sudoPassword, progressChan); err != nil {
|
||||
return fmt.Errorf("failed to install OBS packages: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Manual Builds
|
||||
if len(manualPkgs) > 0 {
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseSystemPackages,
|
||||
@@ -297,8 +362,9 @@ func (d *DebianDistribution) InstallPackages(ctx context.Context, dependencies [
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DebianDistribution) categorizePackages(dependencies []deps.Dependency, wm deps.WindowManager, reinstallFlags map[string]bool, disabledFlags map[string]bool) ([]string, []string, map[string]deps.PackageVariant) {
|
||||
func (d *DebianDistribution) categorizePackages(dependencies []deps.Dependency, wm deps.WindowManager, reinstallFlags map[string]bool, disabledFlags map[string]bool) ([]string, []PackageMapping, []string, map[string]deps.PackageVariant) {
|
||||
systemPkgs := []string{}
|
||||
obsPkgs := []PackageMapping{}
|
||||
manualPkgs := []string{}
|
||||
|
||||
variantMap := make(map[string]deps.PackageVariant)
|
||||
@@ -306,7 +372,7 @@ func (d *DebianDistribution) categorizePackages(dependencies []deps.Dependency,
|
||||
variantMap[dep.Name] = dep.Variant
|
||||
}
|
||||
|
||||
packageMap := d.GetPackageMapping(wm)
|
||||
packageMap := d.GetPackageMappingWithVariants(wm, variantMap)
|
||||
|
||||
for _, dep := range dependencies {
|
||||
if disabledFlags[dep.Name] {
|
||||
@@ -326,12 +392,116 @@ func (d *DebianDistribution) categorizePackages(dependencies []deps.Dependency,
|
||||
switch pkgInfo.Repository {
|
||||
case RepoTypeSystem:
|
||||
systemPkgs = append(systemPkgs, pkgInfo.Name)
|
||||
case RepoTypeOBS:
|
||||
obsPkgs = append(obsPkgs, pkgInfo)
|
||||
case RepoTypeManual:
|
||||
manualPkgs = append(manualPkgs, dep.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return systemPkgs, manualPkgs, variantMap
|
||||
return systemPkgs, obsPkgs, manualPkgs, variantMap
|
||||
}
|
||||
|
||||
func (d *DebianDistribution) extractPackageNames(packages []PackageMapping) []string {
|
||||
names := make([]string, len(packages))
|
||||
for i, pkg := range packages {
|
||||
names[i] = pkg.Name
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func (d *DebianDistribution) enableOBSRepos(ctx context.Context, obsPkgs []PackageMapping, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||
enabledRepos := make(map[string]bool)
|
||||
|
||||
osInfo, err := GetOSInfo()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get OS info: %w", err)
|
||||
}
|
||||
|
||||
// Determine Debian version for OBS repository URL
|
||||
debianVersion := "Debian_13"
|
||||
if osInfo.VersionID == "testing" {
|
||||
debianVersion = "Debian_Testing"
|
||||
}
|
||||
|
||||
for _, pkg := range obsPkgs {
|
||||
if pkg.RepoURL != "" && !enabledRepos[pkg.RepoURL] {
|
||||
d.log(fmt.Sprintf("Enabling OBS repository: %s", pkg.RepoURL))
|
||||
|
||||
// RepoURL format: "home:AvengeMedia:danklinux"
|
||||
repoPath := strings.ReplaceAll(pkg.RepoURL, ":", ":/")
|
||||
repoName := strings.ReplaceAll(pkg.RepoURL, ":", "-")
|
||||
baseURL := fmt.Sprintf("https://download.opensuse.org/repositories/%s/%s", repoPath, debianVersion)
|
||||
|
||||
// Check if repository already exists
|
||||
listFile := fmt.Sprintf("/etc/apt/sources.list.d/%s.list", repoName)
|
||||
checkCmd := exec.CommandContext(ctx, "test", "-f", listFile)
|
||||
if checkCmd.Run() == nil {
|
||||
d.log(fmt.Sprintf("OBS repo %s already exists, skipping", pkg.RepoURL))
|
||||
enabledRepos[pkg.RepoURL] = true
|
||||
continue
|
||||
}
|
||||
|
||||
keyringPath := fmt.Sprintf("/etc/apt/keyrings/%s.gpg", repoName)
|
||||
|
||||
// Create keyrings directory if it doesn't exist
|
||||
mkdirCmd := ExecSudoCommand(ctx, sudoPassword, "mkdir -p /etc/apt/keyrings")
|
||||
if err := mkdirCmd.Run(); err != nil {
|
||||
d.log(fmt.Sprintf("Warning: failed to create keyrings directory: %v", err))
|
||||
}
|
||||
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseSystemPackages,
|
||||
Progress: 0.18,
|
||||
Step: fmt.Sprintf("Adding OBS GPG key for %s...", pkg.RepoURL),
|
||||
NeedsSudo: true,
|
||||
CommandInfo: fmt.Sprintf("curl & gpg to add key for %s", pkg.RepoURL),
|
||||
}
|
||||
|
||||
keyCmd := fmt.Sprintf("curl -fsSL %s/Release.key | gpg --dearmor -o %s", baseURL, keyringPath)
|
||||
cmd := ExecSudoCommand(ctx, sudoPassword, keyCmd)
|
||||
if err := d.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.18, 0.20); err != nil {
|
||||
return fmt.Errorf("failed to add OBS GPG key for %s: %w", pkg.RepoURL, err)
|
||||
}
|
||||
|
||||
// Add repository
|
||||
repoLine := fmt.Sprintf("deb [signed-by=%s] %s/ /", keyringPath, baseURL)
|
||||
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseSystemPackages,
|
||||
Progress: 0.20,
|
||||
Step: fmt.Sprintf("Adding OBS repository %s...", pkg.RepoURL),
|
||||
NeedsSudo: true,
|
||||
CommandInfo: fmt.Sprintf("echo '%s' | sudo tee %s", repoLine, listFile),
|
||||
}
|
||||
|
||||
addRepoCmd := ExecSudoCommand(ctx, sudoPassword,
|
||||
fmt.Sprintf("echo '%s' | tee %s", repoLine, listFile))
|
||||
if err := d.runWithProgress(addRepoCmd, progressChan, PhaseSystemPackages, 0.20, 0.22); err != nil {
|
||||
return fmt.Errorf("failed to add OBS repo %s: %w", pkg.RepoURL, err)
|
||||
}
|
||||
|
||||
enabledRepos[pkg.RepoURL] = true
|
||||
d.log(fmt.Sprintf("OBS repo %s enabled successfully", pkg.RepoURL))
|
||||
}
|
||||
}
|
||||
|
||||
if len(enabledRepos) > 0 {
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseSystemPackages,
|
||||
Progress: 0.25,
|
||||
Step: "Updating package lists...",
|
||||
NeedsSudo: true,
|
||||
CommandInfo: "sudo apt-get update",
|
||||
}
|
||||
|
||||
updateCmd := ExecSudoCommand(ctx, sudoPassword, "apt-get update")
|
||||
if err := d.runWithProgress(updateCmd, progressChan, PhaseSystemPackages, 0.25, 0.27); err != nil {
|
||||
return fmt.Errorf("failed to update package lists after adding OBS repos: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DebianDistribution) installAPTPackages(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||
|
||||
@@ -19,10 +19,12 @@ func init() {
|
||||
Register("fedora-asahi-remix", "#0B57A4", FamilyFedora, func(config DistroConfig, logChan chan<- string) Distribution {
|
||||
return NewFedoraDistribution(config, logChan)
|
||||
})
|
||||
|
||||
Register("bluefin", "#0B57A4", FamilyFedora, func(config DistroConfig, logChan chan<- string) Distribution {
|
||||
return NewFedoraDistribution(config, logChan)
|
||||
})
|
||||
Register("ultramarine", "#00078b", FamilyFedora, func(config DistroConfig, logChan chan<- string) Distribution {
|
||||
return NewFedoraDistribution(config, logChan)
|
||||
})
|
||||
}
|
||||
|
||||
type FedoraDistribution struct {
|
||||
@@ -165,7 +167,7 @@ func (f *FedoraDistribution) GetPackageMappingWithVariants(wm deps.WindowManager
|
||||
packages["jq"] = PackageMapping{Name: "jq", Repository: RepoTypeSystem}
|
||||
case deps.WindowManagerNiri:
|
||||
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
|
||||
@@ -203,7 +205,7 @@ func (f *FedoraDistribution) getNiriMapping(variant deps.PackageVariant) Package
|
||||
if variant == deps.VariantGit {
|
||||
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 {
|
||||
@@ -506,6 +508,14 @@ func (f *FedoraDistribution) installDNFPackages(ctx context.Context, packages []
|
||||
f.log(fmt.Sprintf("Installing DNF packages: %s", strings.Join(packages, ", ")))
|
||||
|
||||
args := []string{"dnf", "install", "-y"}
|
||||
|
||||
for _, pkg := range packages {
|
||||
if pkg == "niri" || pkg == "niri-git" {
|
||||
args = append(args, "--setopt=install_weak_deps=False")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
args = append(args, packages...)
|
||||
|
||||
progressChan <- InstallProgressMsg{
|
||||
|
||||
@@ -39,6 +39,7 @@ const (
|
||||
RepoTypeAUR RepositoryType = "aur" // Arch User Repository
|
||||
RepoTypeCOPR RepositoryType = "copr" // Fedora COPR
|
||||
RepoTypePPA RepositoryType = "ppa" // Ubuntu PPA
|
||||
RepoTypeOBS RepositoryType = "obs" // OpenBuild Service (Debian/OpenSUSE)
|
||||
RepoTypeFlake RepositoryType = "flake" // Nix flake
|
||||
RepoTypeGURU RepositoryType = "guru" // Gentoo GURU
|
||||
RepoTypeManual RepositoryType = "manual" // Manual build from source
|
||||
|
||||
@@ -478,6 +478,95 @@ func (m *ManualPackageInstaller) installHyprpicker(ctx context.Context, sudoPass
|
||||
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")
|
||||
if err := os.MkdirAll(tmpDir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create temp directory: %w", err)
|
||||
@@ -486,7 +575,7 @@ func (m *ManualPackageInstaller) installHyprpicker(ctx context.Context, sudoPass
|
||||
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseSystemPackages,
|
||||
Progress: 0.2,
|
||||
Progress: 0.35,
|
||||
Step: "Cloning hyprpicker repository...",
|
||||
IsComplete: false,
|
||||
CommandInfo: "git clone https://github.com/hyprwm/hyprpicker.git",
|
||||
@@ -499,16 +588,39 @@ func (m *ManualPackageInstaller) installHyprpicker(ctx context.Context, sudoPass
|
||||
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseSystemPackages,
|
||||
Progress: 0.4,
|
||||
Step: "Building hyprpicker...",
|
||||
Progress: 0.45,
|
||||
Step: "Configuring hyprpicker build...",
|
||||
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.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)
|
||||
}
|
||||
|
||||
@@ -518,10 +630,10 @@ func (m *ManualPackageInstaller) installHyprpicker(ctx context.Context, sudoPass
|
||||
Step: "Installing hyprpicker...",
|
||||
IsComplete: false,
|
||||
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
|
||||
if err := installCmd.Run(); err != nil {
|
||||
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
|
||||
}
|
||||
@@ -82,7 +82,6 @@ func (o *OpenSUSEDistribution) DetectDependenciesWithTerminal(ctx context.Contex
|
||||
// Base detections (common across distros)
|
||||
dependencies = append(dependencies, o.detectMatugen())
|
||||
dependencies = append(dependencies, o.detectDgop())
|
||||
dependencies = append(dependencies, o.detectHyprpicker())
|
||||
dependencies = append(dependencies, o.detectClipboardTools()...)
|
||||
|
||||
return dependencies, nil
|
||||
@@ -138,13 +137,12 @@ func (o *OpenSUSEDistribution) GetPackageMappingWithVariants(wm deps.WindowManag
|
||||
"mate-polkit": {Name: "mate-polkit", Repository: RepoTypeSystem},
|
||||
"accountsservice": {Name: "accountsservice", Repository: RepoTypeSystem},
|
||||
"cliphist": {Name: "cliphist", Repository: RepoTypeSystem},
|
||||
"hyprpicker": {Name: "hyprpicker", Repository: RepoTypeSystem},
|
||||
|
||||
// Manual builds
|
||||
"dms (DankMaterialShell)": {Name: "dms", Repository: RepoTypeManual, BuildFunc: "installDankMaterialShell"},
|
||||
"dgop": {Name: "dgop", Repository: RepoTypeManual, BuildFunc: "installDgop"},
|
||||
"quickshell": {Name: "quickshell", Repository: RepoTypeManual, BuildFunc: "installQuickshell"},
|
||||
"matugen": {Name: "matugen", Repository: RepoTypeManual, BuildFunc: "installMatugen"},
|
||||
// DMS packages from OBS
|
||||
"dms (DankMaterialShell)": o.getDmsMapping(variants["dms (DankMaterialShell)"]),
|
||||
"quickshell": o.getQuickshellMapping(variants["quickshell"]),
|
||||
"matugen": {Name: "matugen", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
|
||||
"dgop": {Name: "dgop", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
|
||||
}
|
||||
|
||||
switch wm {
|
||||
@@ -156,13 +154,43 @@ func (o *OpenSUSEDistribution) GetPackageMappingWithVariants(wm deps.WindowManag
|
||||
packages["grimblast"] = PackageMapping{Name: "grimblast", Repository: RepoTypeManual, BuildFunc: "installGrimblast"}
|
||||
packages["jq"] = PackageMapping{Name: "jq", Repository: RepoTypeSystem}
|
||||
case deps.WindowManagerNiri:
|
||||
packages["niri"] = PackageMapping{Name: "niri", Repository: RepoTypeSystem}
|
||||
packages["xwayland-satellite"] = PackageMapping{Name: "xwayland-satellite", Repository: RepoTypeSystem}
|
||||
// Niri stable has native package support on openSUSE
|
||||
niriVariant := variants["niri"]
|
||||
packages["niri"] = o.getNiriMapping(niriVariant)
|
||||
packages["xwayland-satellite"] = o.getXwaylandSatelliteMapping(niriVariant)
|
||||
}
|
||||
|
||||
return packages
|
||||
}
|
||||
|
||||
func (o *OpenSUSEDistribution) getDmsMapping(variant deps.PackageVariant) PackageMapping {
|
||||
if variant == deps.VariantGit {
|
||||
return PackageMapping{Name: "dms-git", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:dms-git"}
|
||||
}
|
||||
return PackageMapping{Name: "dms", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:dms"}
|
||||
}
|
||||
|
||||
func (o *OpenSUSEDistribution) getQuickshellMapping(variant deps.PackageVariant) PackageMapping {
|
||||
if forceQuickshellGit || variant == deps.VariantGit {
|
||||
return PackageMapping{Name: "quickshell-git", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}
|
||||
}
|
||||
return PackageMapping{Name: "quickshell", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}
|
||||
}
|
||||
|
||||
func (o *OpenSUSEDistribution) getNiriMapping(variant deps.PackageVariant) PackageMapping {
|
||||
if variant == deps.VariantGit {
|
||||
return PackageMapping{Name: "niri-git", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}
|
||||
}
|
||||
return PackageMapping{Name: "niri", Repository: RepoTypeSystem}
|
||||
}
|
||||
|
||||
func (o *OpenSUSEDistribution) getXwaylandSatelliteMapping(variant deps.PackageVariant) PackageMapping {
|
||||
if variant == deps.VariantGit {
|
||||
return PackageMapping{Name: "xwayland-satellite-git", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}
|
||||
}
|
||||
return PackageMapping{Name: "xwayland-satellite", Repository: RepoTypeSystem}
|
||||
}
|
||||
|
||||
func (o *OpenSUSEDistribution) detectXwaylandSatellite() deps.Dependency {
|
||||
status := deps.StatusMissing
|
||||
if o.commandExists("xwayland-satellite") {
|
||||
@@ -294,9 +322,23 @@ func (o *OpenSUSEDistribution) InstallPackages(ctx context.Context, dependencies
|
||||
return fmt.Errorf("failed to install prerequisites: %w", err)
|
||||
}
|
||||
|
||||
systemPkgs, manualPkgs, variantMap := o.categorizePackages(dependencies, wm, reinstallFlags, disabledFlags)
|
||||
systemPkgs, obsPkgs, manualPkgs, variantMap := o.categorizePackages(dependencies, wm, reinstallFlags, disabledFlags)
|
||||
|
||||
// Phase 2: System Packages (Zypper)
|
||||
// Enable OBS repositories
|
||||
if len(obsPkgs) > 0 {
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseSystemPackages,
|
||||
Progress: 0.15,
|
||||
Step: "Enabling OBS repositories...",
|
||||
IsComplete: false,
|
||||
LogOutput: "Setting up OBS repositories for additional packages",
|
||||
}
|
||||
if err := o.enableOBSRepos(ctx, obsPkgs, sudoPassword, progressChan); err != nil {
|
||||
return fmt.Errorf("failed to enable OBS repositories: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 3: System Packages (Zypper)
|
||||
if len(systemPkgs) > 0 {
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseSystemPackages,
|
||||
@@ -311,7 +353,22 @@ func (o *OpenSUSEDistribution) InstallPackages(ctx context.Context, dependencies
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 3: Manual Builds
|
||||
// OBS Packages
|
||||
obsPkgNames := o.extractPackageNames(obsPkgs)
|
||||
if len(obsPkgNames) > 0 {
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseAURPackages,
|
||||
Progress: 0.65,
|
||||
Step: fmt.Sprintf("Installing %d OBS packages...", len(obsPkgNames)),
|
||||
IsComplete: false,
|
||||
LogOutput: fmt.Sprintf("Installing OBS packages: %s", strings.Join(obsPkgNames, ", ")),
|
||||
}
|
||||
if err := o.installZypperPackages(ctx, obsPkgNames, sudoPassword, progressChan); err != nil {
|
||||
return fmt.Errorf("failed to install OBS packages: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Manual Builds
|
||||
if len(manualPkgs) > 0 {
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseSystemPackages,
|
||||
@@ -325,7 +382,7 @@ func (o *OpenSUSEDistribution) InstallPackages(ctx context.Context, dependencies
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 4: Configuration
|
||||
// Configuration
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseConfiguration,
|
||||
Progress: 0.90,
|
||||
@@ -334,7 +391,7 @@ func (o *OpenSUSEDistribution) InstallPackages(ctx context.Context, dependencies
|
||||
LogOutput: "Starting post-installation configuration...",
|
||||
}
|
||||
|
||||
// Phase 5: Complete
|
||||
// Complete
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseComplete,
|
||||
Progress: 1.0,
|
||||
@@ -346,8 +403,9 @@ func (o *OpenSUSEDistribution) InstallPackages(ctx context.Context, dependencies
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OpenSUSEDistribution) categorizePackages(dependencies []deps.Dependency, wm deps.WindowManager, reinstallFlags map[string]bool, disabledFlags map[string]bool) ([]string, []string, map[string]deps.PackageVariant) {
|
||||
func (o *OpenSUSEDistribution) categorizePackages(dependencies []deps.Dependency, wm deps.WindowManager, reinstallFlags map[string]bool, disabledFlags map[string]bool) ([]string, []PackageMapping, []string, map[string]deps.PackageVariant) {
|
||||
systemPkgs := []string{}
|
||||
obsPkgs := []PackageMapping{}
|
||||
manualPkgs := []string{}
|
||||
|
||||
variantMap := make(map[string]deps.PackageVariant)
|
||||
@@ -375,12 +433,80 @@ func (o *OpenSUSEDistribution) categorizePackages(dependencies []deps.Dependency
|
||||
switch pkgInfo.Repository {
|
||||
case RepoTypeSystem:
|
||||
systemPkgs = append(systemPkgs, pkgInfo.Name)
|
||||
case RepoTypeOBS:
|
||||
obsPkgs = append(obsPkgs, pkgInfo)
|
||||
case RepoTypeManual:
|
||||
manualPkgs = append(manualPkgs, dep.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return systemPkgs, manualPkgs, variantMap
|
||||
return systemPkgs, obsPkgs, manualPkgs, variantMap
|
||||
}
|
||||
|
||||
func (o *OpenSUSEDistribution) extractPackageNames(packages []PackageMapping) []string {
|
||||
names := make([]string, len(packages))
|
||||
for i, pkg := range packages {
|
||||
names[i] = pkg.Name
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func (o *OpenSUSEDistribution) enableOBSRepos(ctx context.Context, obsPkgs []PackageMapping, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||
enabledRepos := make(map[string]bool)
|
||||
|
||||
for _, pkg := range obsPkgs {
|
||||
if pkg.RepoURL != "" && !enabledRepos[pkg.RepoURL] {
|
||||
o.log(fmt.Sprintf("Enabling OBS repository: %s", pkg.RepoURL))
|
||||
|
||||
// RepoURL format: "home:AvengeMedia:danklinux"
|
||||
repoPath := strings.ReplaceAll(pkg.RepoURL, ":", ":/")
|
||||
repoName := strings.ReplaceAll(pkg.RepoURL, ":", "-")
|
||||
repoURL := fmt.Sprintf("https://download.opensuse.org/repositories/%s/openSUSE_Tumbleweed/%s.repo",
|
||||
repoPath, pkg.RepoURL)
|
||||
|
||||
checkCmd := exec.CommandContext(ctx, "zypper", "repos", repoName)
|
||||
if checkCmd.Run() == nil {
|
||||
o.log(fmt.Sprintf("OBS repo %s already exists, skipping", pkg.RepoURL))
|
||||
enabledRepos[pkg.RepoURL] = true
|
||||
continue
|
||||
}
|
||||
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseSystemPackages,
|
||||
Progress: 0.20,
|
||||
Step: fmt.Sprintf("Enabling OBS repo %s...", pkg.RepoURL),
|
||||
NeedsSudo: true,
|
||||
CommandInfo: fmt.Sprintf("sudo zypper addrepo %s", repoURL),
|
||||
}
|
||||
|
||||
cmd := ExecSudoCommand(ctx, sudoPassword,
|
||||
fmt.Sprintf("zypper addrepo -f %s", repoURL))
|
||||
if err := o.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.20, 0.22); err != nil {
|
||||
return fmt.Errorf("failed to enable OBS repo %s: %w", pkg.RepoURL, err)
|
||||
}
|
||||
|
||||
enabledRepos[pkg.RepoURL] = true
|
||||
o.log(fmt.Sprintf("OBS repo %s enabled successfully", pkg.RepoURL))
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh repositories with GPG auto-import
|
||||
if len(enabledRepos) > 0 {
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseSystemPackages,
|
||||
Progress: 0.25,
|
||||
Step: "Refreshing repositories...",
|
||||
NeedsSudo: true,
|
||||
CommandInfo: "sudo zypper --gpg-auto-import-keys refresh",
|
||||
}
|
||||
|
||||
refreshCmd := ExecSudoCommand(ctx, sudoPassword, "zypper --gpg-auto-import-keys refresh")
|
||||
if err := o.runWithProgress(refreshCmd, progressChan, PhaseSystemPackages, 0.25, 0.27); err != nil {
|
||||
return fmt.Errorf("failed to refresh repositories: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OpenSUSEDistribution) installZypperPackages(ctx context.Context, packages []string, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||
|
||||
@@ -82,7 +82,6 @@ func (u *UbuntuDistribution) DetectDependenciesWithTerminal(ctx context.Context,
|
||||
// Base detections (common across distros)
|
||||
dependencies = append(dependencies, u.detectMatugen())
|
||||
dependencies = append(dependencies, u.detectDgop())
|
||||
dependencies = append(dependencies, u.detectHyprpicker())
|
||||
dependencies = append(dependencies, u.detectClipboardTools()...)
|
||||
|
||||
return dependencies, nil
|
||||
@@ -151,6 +150,10 @@ func (u *UbuntuDistribution) packageInstalled(pkg string) bool {
|
||||
}
|
||||
|
||||
func (u *UbuntuDistribution) GetPackageMapping(wm deps.WindowManager) map[string]PackageMapping {
|
||||
return u.GetPackageMappingWithVariants(wm, make(map[string]deps.PackageVariant))
|
||||
}
|
||||
|
||||
func (u *UbuntuDistribution) GetPackageMappingWithVariants(wm deps.WindowManager, variants map[string]deps.PackageVariant) map[string]PackageMapping {
|
||||
packages := map[string]PackageMapping{
|
||||
// Standard APT packages
|
||||
"git": {Name: "git", Repository: RepoTypeSystem},
|
||||
@@ -160,16 +163,16 @@ func (u *UbuntuDistribution) GetPackageMapping(wm deps.WindowManager) map[string
|
||||
"xdg-desktop-portal-gtk": {Name: "xdg-desktop-portal-gtk", Repository: RepoTypeSystem},
|
||||
"mate-polkit": {Name: "mate-polkit", Repository: RepoTypeSystem},
|
||||
"accountsservice": {Name: "accountsservice", Repository: RepoTypeSystem},
|
||||
"hyprpicker": {Name: "hyprpicker", Repository: RepoTypePPA, RepoURL: "ppa:cppiber/hyprland"},
|
||||
|
||||
// Manual builds (niri and quickshell likely not available in Ubuntu repos or PPAs)
|
||||
"dms (DankMaterialShell)": {Name: "dms", Repository: RepoTypeManual, BuildFunc: "installDankMaterialShell"},
|
||||
"niri": {Name: "niri", Repository: RepoTypeManual, BuildFunc: "installNiri"},
|
||||
"quickshell": {Name: "quickshell", Repository: RepoTypeManual, BuildFunc: "installQuickshell"},
|
||||
"ghostty": {Name: "ghostty", Repository: RepoTypeManual, BuildFunc: "installGhostty"},
|
||||
"matugen": {Name: "matugen", Repository: RepoTypeManual, BuildFunc: "installMatugen"},
|
||||
"dgop": {Name: "dgop", Repository: RepoTypeManual, BuildFunc: "installDgop"},
|
||||
"cliphist": {Name: "cliphist", Repository: RepoTypeManual, BuildFunc: "installCliphist"},
|
||||
// DMS packages from PPAs
|
||||
"dms (DankMaterialShell)": u.getDmsMapping(variants["dms (DankMaterialShell)"]),
|
||||
"quickshell": u.getQuickshellMapping(variants["quickshell"]),
|
||||
"matugen": {Name: "matugen", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"},
|
||||
"dgop": {Name: "dgop", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"},
|
||||
"cliphist": {Name: "cliphist", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"},
|
||||
|
||||
// Keep ghostty as manual (no PPA available)
|
||||
"ghostty": {Name: "ghostty", Repository: RepoTypeManual, BuildFunc: "installGhostty"},
|
||||
}
|
||||
|
||||
switch wm {
|
||||
@@ -182,13 +185,42 @@ func (u *UbuntuDistribution) GetPackageMapping(wm deps.WindowManager) map[string
|
||||
packages["grimblast"] = PackageMapping{Name: "grimblast", Repository: RepoTypeManual, BuildFunc: "installGrimblast"}
|
||||
packages["jq"] = PackageMapping{Name: "jq", Repository: RepoTypeSystem}
|
||||
case deps.WindowManagerNiri:
|
||||
packages["niri"] = PackageMapping{Name: "niri", Repository: RepoTypeManual, BuildFunc: "installNiri"}
|
||||
packages["xwayland-satellite"] = PackageMapping{Name: "xwayland-satellite", Repository: RepoTypeManual, BuildFunc: "installXwaylandSatellite"}
|
||||
niriVariant := variants["niri"]
|
||||
packages["niri"] = u.getNiriMapping(niriVariant)
|
||||
packages["xwayland-satellite"] = u.getXwaylandSatelliteMapping(niriVariant)
|
||||
}
|
||||
|
||||
return packages
|
||||
}
|
||||
|
||||
func (u *UbuntuDistribution) getDmsMapping(variant deps.PackageVariant) PackageMapping {
|
||||
if variant == deps.VariantGit {
|
||||
return PackageMapping{Name: "dms-git", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/dms-git"}
|
||||
}
|
||||
return PackageMapping{Name: "dms", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/dms"}
|
||||
}
|
||||
|
||||
func (u *UbuntuDistribution) getQuickshellMapping(variant deps.PackageVariant) PackageMapping {
|
||||
if forceQuickshellGit || variant == deps.VariantGit {
|
||||
return PackageMapping{Name: "quickshell-git", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"}
|
||||
}
|
||||
return PackageMapping{Name: "quickshell", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"}
|
||||
}
|
||||
|
||||
func (u *UbuntuDistribution) getNiriMapping(variant deps.PackageVariant) PackageMapping {
|
||||
if variant == deps.VariantGit {
|
||||
return PackageMapping{Name: "niri-git", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"}
|
||||
}
|
||||
return PackageMapping{Name: "niri", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"}
|
||||
}
|
||||
|
||||
func (u *UbuntuDistribution) getXwaylandSatelliteMapping(variant deps.PackageVariant) PackageMapping {
|
||||
if variant == deps.VariantGit {
|
||||
return PackageMapping{Name: "xwayland-satellite-git", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"}
|
||||
}
|
||||
return PackageMapping{Name: "xwayland-satellite", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"}
|
||||
}
|
||||
|
||||
func (u *UbuntuDistribution) InstallPrerequisites(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhasePrerequisites,
|
||||
@@ -365,7 +397,7 @@ func (u *UbuntuDistribution) categorizePackages(dependencies []deps.Dependency,
|
||||
variantMap[dep.Name] = dep.Variant
|
||||
}
|
||||
|
||||
packageMap := u.GetPackageMapping(wm)
|
||||
packageMap := u.GetPackageMappingWithVariants(wm, variantMap)
|
||||
|
||||
for _, dep := range dependencies {
|
||||
if disabledFlags[dep.Name] {
|
||||
|
||||
@@ -105,14 +105,19 @@ type MenuItem struct {
|
||||
|
||||
func NewModel(version string) Model {
|
||||
detector, _ := NewDetector()
|
||||
dependencies := detector.GetInstalledComponents()
|
||||
var dependencies []DependencyInfo
|
||||
var hyprlandInstalled, niriInstalled bool
|
||||
var err error
|
||||
if detector != nil {
|
||||
dependencies = detector.GetInstalledComponents()
|
||||
|
||||
// Use the proper detection method for both window managers
|
||||
hyprlandInstalled, niriInstalled, err := detector.GetWindowManagerStatus()
|
||||
if err != nil {
|
||||
// Fallback to false if detection fails
|
||||
hyprlandInstalled = false
|
||||
niriInstalled = false
|
||||
// Use the proper detection method for both window managers
|
||||
hyprlandInstalled, niriInstalled, err = detector.GetWindowManagerStatus()
|
||||
if err != nil {
|
||||
// Fallback to false if detection fails
|
||||
hyprlandInstalled = false
|
||||
niriInstalled = false
|
||||
}
|
||||
}
|
||||
|
||||
updateToggles := make(map[string]bool)
|
||||
|
||||
@@ -122,15 +122,8 @@ func (d *Detector) GetInstalledComponents() []DependencyInfo {
|
||||
return []DependencyInfo{}
|
||||
}
|
||||
|
||||
isNixOS := d.isNixOS()
|
||||
|
||||
var components []DependencyInfo
|
||||
for _, dep := range dependencies {
|
||||
// On NixOS, filter out the window managers themselves but keep their components
|
||||
if isNixOS && (dep.Name == "hyprland" || dep.Name == "niri") {
|
||||
continue
|
||||
}
|
||||
|
||||
components = append(components, DependencyInfo{
|
||||
Name: dep.Name,
|
||||
Status: dep.Status,
|
||||
@@ -142,23 +135,6 @@ func (d *Detector) GetInstalledComponents() []DependencyInfo {
|
||||
return components
|
||||
}
|
||||
|
||||
func (d *Detector) isNixOS() bool {
|
||||
_, err := os.Stat("/etc/nixos")
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
// Alternative check
|
||||
if _, err := os.Stat("/nix/store"); err == nil {
|
||||
// Also check for nixos-version command
|
||||
if d.commandExists("nixos-version") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
type DependencyInfo struct {
|
||||
Name string
|
||||
Status deps.DependencyStatus
|
||||
|
||||
@@ -109,6 +109,9 @@ func (m Model) renderAboutView() string {
|
||||
|
||||
b.WriteString(normalStyle.Render("Components:"))
|
||||
b.WriteString("\n")
|
||||
if len(m.dependencies) == 0 {
|
||||
b.WriteString(normalStyle.Render("\n Component detection not supported on this platform."))
|
||||
}
|
||||
for _, dep := range m.dependencies {
|
||||
status := "✗"
|
||||
if dep.Status == 1 {
|
||||
|
||||
@@ -227,6 +227,7 @@ func SetupParentDirectoryACLs(logFunc func(string), sudoPassword string) error {
|
||||
{filepath.Join(homeDir, ".local"), ".local directory"},
|
||||
{filepath.Join(homeDir, ".cache"), ".cache 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...")
|
||||
@@ -239,8 +240,8 @@ func SetupParentDirectoryACLs(logFunc func(string), sudoPassword string) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Set ACL to allow greeter user execute (traverse) permission
|
||||
if err := runSudoCmd(sudoPassword, "setfacl", "-m", "u:greeter:x", dir.path); err != nil {
|
||||
// Set ACL to allow greeter user read+execute permission (for session discovery)
|
||||
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(" You may need to run manually: setfacl -m u:greeter:x %s", dir.path))
|
||||
continue
|
||||
@@ -287,6 +288,8 @@ func SetupDMSGroup(logFunc func(string), sudoPassword string) error {
|
||||
{filepath.Join(homeDir, ".local", "state", "DankMaterialShell"), "DankMaterialShell state"},
|
||||
{filepath.Join(homeDir, ".cache", "quickshell"), "quickshell cache"},
|
||||
{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 {
|
||||
@@ -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 {
|
||||
logFunc(fmt.Sprintf("⚠ Warning: Failed to create symlink for %s: %v", link.desc, err))
|
||||
|
||||
@@ -77,7 +77,7 @@ func (d *DiscoveryConfig) FindJSONFiles() ([]string, error) {
|
||||
func expandPath(path string) (string, error) {
|
||||
expandedPath := os.ExpandEnv(path)
|
||||
|
||||
if filepath.HasPrefix(expandedPath, "~") {
|
||||
if strings.HasPrefix(expandedPath, "~") {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/hyprland"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
|
||||
)
|
||||
|
||||
@@ -26,7 +25,7 @@ func (h *HyprlandProvider) Name() string {
|
||||
}
|
||||
|
||||
func (h *HyprlandProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
|
||||
section, err := hyprland.ParseKeys(h.configPath)
|
||||
section, err := ParseHyprlandKeys(h.configPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse hyprland config: %w", err)
|
||||
}
|
||||
@@ -41,7 +40,7 @@ func (h *HyprlandProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
|
||||
}, 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
|
||||
if section.Name != "" {
|
||||
currentSubcat = section.Name
|
||||
@@ -86,29 +85,31 @@ 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)
|
||||
rawAction := h.formatRawAction(kb.Dispatcher, kb.Params)
|
||||
desc := kb.Comment
|
||||
|
||||
if desc == "" {
|
||||
desc = h.generateDescription(kb.Dispatcher, kb.Params)
|
||||
desc = rawAction
|
||||
}
|
||||
|
||||
return keybinds.Keybind{
|
||||
Key: key,
|
||||
Description: desc,
|
||||
Action: rawAction,
|
||||
Subcategory: subcategory,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HyprlandProvider) generateDescription(dispatcher, params string) string {
|
||||
func (h *HyprlandProvider) formatRawAction(dispatcher, params string) string {
|
||||
if params != "" {
|
||||
return dispatcher + " " + params
|
||||
}
|
||||
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 = append(parts, kb.Mods...)
|
||||
parts = append(parts, kb.Key)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package hyprland
|
||||
package providers
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -15,7 +15,7 @@ const (
|
||||
|
||||
var ModSeparators = []rune{'+', ' '}
|
||||
|
||||
type KeyBinding struct {
|
||||
type HyprlandKeyBinding struct {
|
||||
Mods []string `json:"mods"`
|
||||
Key string `json:"key"`
|
||||
Dispatcher string `json:"dispatcher"`
|
||||
@@ -23,25 +23,25 @@ type KeyBinding struct {
|
||||
Comment string `json:"comment"`
|
||||
}
|
||||
|
||||
type Section struct {
|
||||
Children []Section `json:"children"`
|
||||
Keybinds []KeyBinding `json:"keybinds"`
|
||||
Name string `json:"name"`
|
||||
type HyprlandSection struct {
|
||||
Children []HyprlandSection `json:"children"`
|
||||
Keybinds []HyprlandKeyBinding `json:"keybinds"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type Parser struct {
|
||||
type HyprlandParser struct {
|
||||
contentLines []string
|
||||
readingLine int
|
||||
}
|
||||
|
||||
func NewParser() *Parser {
|
||||
return &Parser{
|
||||
func NewHyprlandParser() *HyprlandParser {
|
||||
return &HyprlandParser{
|
||||
contentLines: []string{},
|
||||
readingLine: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) ReadContent(directory string) error {
|
||||
func (p *HyprlandParser) ReadContent(directory string) error {
|
||||
expandedDir := os.ExpandEnv(directory)
|
||||
expandedDir = filepath.Clean(expandedDir)
|
||||
if strings.HasPrefix(expandedDir, "~") {
|
||||
@@ -87,7 +87,7 @@ func (p *Parser) ReadContent(directory string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func autogenerateComment(dispatcher, params string) string {
|
||||
func hyprlandAutogenerateComment(dispatcher, params string) string {
|
||||
switch dispatcher {
|
||||
case "resizewindow":
|
||||
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]
|
||||
parts := strings.SplitN(line, "=", 2)
|
||||
if len(parts) < 2 {
|
||||
@@ -232,7 +232,7 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
comment = autogenerateComment(dispatcher, params)
|
||||
comment = hyprlandAutogenerateComment(dispatcher, params)
|
||||
}
|
||||
|
||||
var modList []string
|
||||
@@ -256,7 +256,7 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
|
||||
}
|
||||
}
|
||||
|
||||
return &KeyBinding{
|
||||
return &HyprlandKeyBinding{
|
||||
Mods: modList,
|
||||
Key: key,
|
||||
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)
|
||||
|
||||
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:])
|
||||
p.readingLine++
|
||||
|
||||
childSection := &Section{
|
||||
Children: []Section{},
|
||||
Keybinds: []KeyBinding{},
|
||||
childSection := &HyprlandSection{
|
||||
Children: []HyprlandSection{},
|
||||
Keybinds: []HyprlandKeyBinding{},
|
||||
Name: sectionName,
|
||||
}
|
||||
result := p.getBindsRecursive(childSection, headingScope)
|
||||
@@ -312,18 +312,18 @@ func (p *Parser) getBindsRecursive(currentContent *Section, scope int) *Section
|
||||
return currentContent
|
||||
}
|
||||
|
||||
func (p *Parser) ParseKeys() *Section {
|
||||
func (p *HyprlandParser) ParseKeys() *HyprlandSection {
|
||||
p.readingLine = 0
|
||||
rootSection := &Section{
|
||||
Children: []Section{},
|
||||
Keybinds: []KeyBinding{},
|
||||
rootSection := &HyprlandSection{
|
||||
Children: []HyprlandSection{},
|
||||
Keybinds: []HyprlandKeyBinding{},
|
||||
Name: "",
|
||||
}
|
||||
return p.getBindsRecursive(rootSection, 0)
|
||||
}
|
||||
|
||||
func ParseKeys(path string) (*Section, error) {
|
||||
parser := NewParser()
|
||||
func ParseHyprlandKeys(path string) (*HyprlandSection, error) {
|
||||
parser := NewHyprlandParser()
|
||||
if err := parser.ReadContent(path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package hyprland
|
||||
package providers
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAutogenerateComment(t *testing.T) {
|
||||
func TestHyprlandAutogenerateComment(t *testing.T) {
|
||||
tests := []struct {
|
||||
dispatcher string
|
||||
params string
|
||||
@@ -51,25 +51,25 @@ func TestAutogenerateComment(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
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 {
|
||||
t.Errorf("autogenerateComment(%q, %q) = %q, want %q",
|
||||
t.Errorf("hyprlandAutogenerateComment(%q, %q) = %q, want %q",
|
||||
tt.dispatcher, tt.params, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetKeybindAtLine(t *testing.T) {
|
||||
func TestHyprlandGetKeybindAtLine(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
expected *KeyBinding
|
||||
expected *HyprlandKeyBinding
|
||||
}{
|
||||
{
|
||||
name: "basic_keybind",
|
||||
line: "bind = SUPER, Q, killactive",
|
||||
expected: &KeyBinding{
|
||||
expected: &HyprlandKeyBinding{
|
||||
Mods: []string{"SUPER"},
|
||||
Key: "Q",
|
||||
Dispatcher: "killactive",
|
||||
@@ -80,7 +80,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
||||
{
|
||||
name: "keybind_with_params",
|
||||
line: "bind = SUPER, left, movefocus, l",
|
||||
expected: &KeyBinding{
|
||||
expected: &HyprlandKeyBinding{
|
||||
Mods: []string{"SUPER"},
|
||||
Key: "left",
|
||||
Dispatcher: "movefocus",
|
||||
@@ -91,7 +91,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
||||
{
|
||||
name: "keybind_with_comment",
|
||||
line: "bind = SUPER, T, exec, kitty # Open terminal",
|
||||
expected: &KeyBinding{
|
||||
expected: &HyprlandKeyBinding{
|
||||
Mods: []string{"SUPER"},
|
||||
Key: "T",
|
||||
Dispatcher: "exec",
|
||||
@@ -107,7 +107,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
||||
{
|
||||
name: "keybind_multiple_mods",
|
||||
line: "bind = SUPER+SHIFT, F, fullscreen, 0",
|
||||
expected: &KeyBinding{
|
||||
expected: &HyprlandKeyBinding{
|
||||
Mods: []string{"SUPER", "SHIFT"},
|
||||
Key: "F",
|
||||
Dispatcher: "fullscreen",
|
||||
@@ -118,7 +118,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
||||
{
|
||||
name: "keybind_no_mods",
|
||||
line: "bind = , Print, exec, screenshot",
|
||||
expected: &KeyBinding{
|
||||
expected: &HyprlandKeyBinding{
|
||||
Mods: []string{},
|
||||
Key: "Print",
|
||||
Dispatcher: "exec",
|
||||
@@ -130,7 +130,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
parser := NewParser()
|
||||
parser := NewHyprlandParser()
|
||||
parser.contentLines = []string{tt.line}
|
||||
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()
|
||||
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)
|
||||
}
|
||||
|
||||
section, err := ParseKeys(tmpDir)
|
||||
section, err := ParseHyprlandKeys(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseKeys failed: %v", err)
|
||||
t.Fatalf("ParseHyprlandKeys failed: %v", err)
|
||||
}
|
||||
|
||||
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()
|
||||
configFile := filepath.Join(tmpDir, "test.conf")
|
||||
|
||||
@@ -249,9 +249,9 @@ bind = SUPER, B, exec, app2
|
||||
t.Fatalf("Failed to write test config: %v", err)
|
||||
}
|
||||
|
||||
section, err := ParseKeys(tmpDir)
|
||||
section, err := ParseHyprlandKeys(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseKeys failed: %v", err)
|
||||
t.Fatalf("ParseHyprlandKeys failed: %v", err)
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
file1 := filepath.Join(tmpDir, "a.conf")
|
||||
@@ -285,7 +285,7 @@ func TestReadContentMultipleFiles(t *testing.T) {
|
||||
t.Fatalf("Failed to write file2: %v", err)
|
||||
}
|
||||
|
||||
parser := NewParser()
|
||||
parser := NewHyprlandParser()
|
||||
if err := parser.ReadContent(tmpDir); err != nil {
|
||||
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 {
|
||||
name string
|
||||
path string
|
||||
@@ -313,7 +313,7 @@ func TestReadContentErrors(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := ParseKeys(tt.path)
|
||||
_, err := ParseHyprlandKeys(tt.path)
|
||||
if err == 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()
|
||||
if err != nil {
|
||||
t.Skip("Cannot get home directory")
|
||||
@@ -343,7 +343,7 @@ func TestReadContentWithTildeExpansion(t *testing.T) {
|
||||
t.Skip("Cannot create relative path")
|
||||
}
|
||||
|
||||
parser := NewParser()
|
||||
parser := NewHyprlandParser()
|
||||
tildePathMatch := "~/" + relPath
|
||||
err = parser.ReadContent(tildePathMatch)
|
||||
|
||||
@@ -352,8 +352,8 @@ func TestReadContentWithTildeExpansion(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeybindWithParamsContainingCommas(t *testing.T) {
|
||||
parser := NewParser()
|
||||
func TestHyprlandKeybindWithParamsContainingCommas(t *testing.T) {
|
||||
parser := NewHyprlandParser()
|
||||
parser.contentLines = []string{"bind = SUPER, R, exec, notify-send 'Title' 'Message, with comma'"}
|
||||
|
||||
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()
|
||||
configFile := filepath.Join(tmpDir, "test.conf")
|
||||
|
||||
@@ -385,9 +385,9 @@ bind = SUPER, T, exec, kitty
|
||||
t.Fatalf("Failed to write test config: %v", err)
|
||||
}
|
||||
|
||||
section, err := ParseKeys(tmpDir)
|
||||
section, err := ParseHyprlandKeys(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseKeys failed: %v", err)
|
||||
t.Fatalf("ParseHyprlandKeys failed: %v", err)
|
||||
}
|
||||
|
||||
if len(section.Keybinds) != 2 {
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
|
||||
)
|
||||
@@ -43,7 +44,7 @@ func (j *JSONFileProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
|
||||
return nil, fmt.Errorf("failed to read file: %w", err)
|
||||
}
|
||||
|
||||
var rawData map[string]interface{}
|
||||
var rawData map[string]any
|
||||
if err := json.Unmarshal(data, &rawData); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse JSON: %w", err)
|
||||
}
|
||||
@@ -62,9 +63,9 @@ func (j *JSONFileProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
|
||||
}
|
||||
|
||||
switch binds := bindsRaw.(type) {
|
||||
case map[string]interface{}:
|
||||
case map[string]any:
|
||||
for category, categoryBindsRaw := range binds {
|
||||
categoryBindsList, ok := categoryBindsRaw.([]interface{})
|
||||
categoryBindsList, ok := categoryBindsRaw.([]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
@@ -78,11 +79,12 @@ func (j *JSONFileProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
|
||||
categorizedBinds[category] = keybindsList
|
||||
}
|
||||
|
||||
case []interface{}:
|
||||
case []any:
|
||||
flatBindsJSON, _ := json.Marshal(binds)
|
||||
var flatBinds []struct {
|
||||
Key string `json:"key"`
|
||||
Description string `json:"desc"`
|
||||
Action string `json:"action,omitempty"`
|
||||
Category string `json:"cat,omitempty"`
|
||||
Subcategory string `json:"subcat,omitempty"`
|
||||
}
|
||||
@@ -99,6 +101,7 @@ func (j *JSONFileProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
|
||||
kb := keybinds.Keybind{
|
||||
Key: bind.Key,
|
||||
Description: bind.Description,
|
||||
Action: bind.Action,
|
||||
Subcategory: bind.Subcategory,
|
||||
}
|
||||
categorizedBinds[category] = append(categorizedBinds[category], kb)
|
||||
@@ -118,7 +121,7 @@ func (j *JSONFileProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
|
||||
func expandPath(path string) (string, error) {
|
||||
expandedPath := os.ExpandEnv(path)
|
||||
|
||||
if filepath.HasPrefix(expandedPath, "~") {
|
||||
if strings.HasPrefix(expandedPath, "~") {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/mangowc"
|
||||
)
|
||||
|
||||
type MangoWCProvider struct {
|
||||
@@ -26,7 +25,7 @@ func (m *MangoWCProvider) Name() string {
|
||||
}
|
||||
|
||||
func (m *MangoWCProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
|
||||
keybinds_list, err := mangowc.ParseKeys(m.configPath)
|
||||
keybinds_list, err := ParseMangoWCKeys(m.configPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse mangowc config: %w", err)
|
||||
}
|
||||
@@ -83,28 +82,30 @@ 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)
|
||||
rawAction := m.formatRawAction(kb.Command, kb.Params)
|
||||
desc := kb.Comment
|
||||
|
||||
if desc == "" {
|
||||
desc = m.generateDescription(kb.Command, kb.Params)
|
||||
desc = rawAction
|
||||
}
|
||||
|
||||
return keybinds.Keybind{
|
||||
Key: key,
|
||||
Description: desc,
|
||||
Action: rawAction,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MangoWCProvider) generateDescription(command, params string) string {
|
||||
func (m *MangoWCProvider) formatRawAction(command, params string) string {
|
||||
if params != "" {
|
||||
return command + " " + params
|
||||
}
|
||||
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 = append(parts, kb.Mods...)
|
||||
parts = append(parts, kb.Key)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package mangowc
|
||||
package providers
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -8,12 +8,12 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
HideComment = "[hidden]"
|
||||
MangoWCHideComment = "[hidden]"
|
||||
)
|
||||
|
||||
var ModSeparators = []rune{'+', ' '}
|
||||
var MangoWCModSeparators = []rune{'+', ' '}
|
||||
|
||||
type KeyBinding struct {
|
||||
type MangoWCKeyBinding struct {
|
||||
Mods []string `json:"mods"`
|
||||
Key string `json:"key"`
|
||||
Command string `json:"command"`
|
||||
@@ -21,19 +21,19 @@ type KeyBinding struct {
|
||||
Comment string `json:"comment"`
|
||||
}
|
||||
|
||||
type Parser struct {
|
||||
type MangoWCParser struct {
|
||||
contentLines []string
|
||||
readingLine int
|
||||
}
|
||||
|
||||
func NewParser() *Parser {
|
||||
return &Parser{
|
||||
func NewMangoWCParser() *MangoWCParser {
|
||||
return &MangoWCParser{
|
||||
contentLines: []string{},
|
||||
readingLine: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) ReadContent(path string) error {
|
||||
func (p *MangoWCParser) ReadContent(path string) error {
|
||||
expandedPath := os.ExpandEnv(path)
|
||||
expandedPath = filepath.Clean(expandedPath)
|
||||
if strings.HasPrefix(expandedPath, "~") {
|
||||
@@ -82,7 +82,7 @@ func (p *Parser) ReadContent(path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func autogenerateComment(command, params string) string {
|
||||
func mangowcAutogenerateComment(command, params string) string {
|
||||
switch command {
|
||||
case "spawn", "spawn_shell":
|
||||
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) {
|
||||
return nil
|
||||
}
|
||||
@@ -220,7 +220,7 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
|
||||
comment = strings.TrimSpace(parts[1])
|
||||
}
|
||||
|
||||
if strings.HasPrefix(comment, HideComment) {
|
||||
if strings.HasPrefix(comment, MangoWCHideComment) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -239,16 +239,16 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
|
||||
}
|
||||
|
||||
if comment == "" {
|
||||
comment = autogenerateComment(command, params)
|
||||
comment = mangowcAutogenerateComment(command, params)
|
||||
}
|
||||
|
||||
var modList []string
|
||||
if mods != "" && !strings.EqualFold(mods, "none") {
|
||||
modstring := mods + string(ModSeparators[0])
|
||||
modstring := mods + string(MangoWCModSeparators[0])
|
||||
p := 0
|
||||
for index, char := range modstring {
|
||||
isModSep := false
|
||||
for _, sep := range ModSeparators {
|
||||
for _, sep := range MangoWCModSeparators {
|
||||
if char == sep {
|
||||
isModSep = true
|
||||
break
|
||||
@@ -265,7 +265,7 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
|
||||
|
||||
_ = bindType
|
||||
|
||||
return &KeyBinding{
|
||||
return &MangoWCKeyBinding{
|
||||
Mods: modList,
|
||||
Key: key,
|
||||
Command: command,
|
||||
@@ -274,8 +274,8 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) ParseKeys() []KeyBinding {
|
||||
var keybinds []KeyBinding
|
||||
func (p *MangoWCParser) ParseKeys() []MangoWCKeyBinding {
|
||||
var keybinds []MangoWCKeyBinding
|
||||
|
||||
for lineNumber := 0; lineNumber < len(p.contentLines); lineNumber++ {
|
||||
line := p.contentLines[lineNumber]
|
||||
@@ -296,8 +296,8 @@ func (p *Parser) ParseKeys() []KeyBinding {
|
||||
return keybinds
|
||||
}
|
||||
|
||||
func ParseKeys(path string) ([]KeyBinding, error) {
|
||||
parser := NewParser()
|
||||
func ParseMangoWCKeys(path string) ([]MangoWCKeyBinding, error) {
|
||||
parser := NewMangoWCParser()
|
||||
if err := parser.ReadContent(path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package mangowc
|
||||
package providers
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAutogenerateComment(t *testing.T) {
|
||||
func TestMangoWCAutogenerateComment(t *testing.T) {
|
||||
tests := []struct {
|
||||
command string
|
||||
params string
|
||||
@@ -60,25 +60,25 @@ func TestAutogenerateComment(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
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 {
|
||||
t.Errorf("autogenerateComment(%q, %q) = %q, want %q",
|
||||
t.Errorf("mangowcAutogenerateComment(%q, %q) = %q, want %q",
|
||||
tt.command, tt.params, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetKeybindAtLine(t *testing.T) {
|
||||
func TestMangoWCGetKeybindAtLine(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
expected *KeyBinding
|
||||
expected *MangoWCKeyBinding
|
||||
}{
|
||||
{
|
||||
name: "basic_keybind",
|
||||
line: "bind=ALT,q,killclient,",
|
||||
expected: &KeyBinding{
|
||||
expected: &MangoWCKeyBinding{
|
||||
Mods: []string{"ALT"},
|
||||
Key: "q",
|
||||
Command: "killclient",
|
||||
@@ -89,7 +89,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
||||
{
|
||||
name: "keybind_with_params",
|
||||
line: "bind=ALT,Left,focusdir,left",
|
||||
expected: &KeyBinding{
|
||||
expected: &MangoWCKeyBinding{
|
||||
Mods: []string{"ALT"},
|
||||
Key: "Left",
|
||||
Command: "focusdir",
|
||||
@@ -100,7 +100,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
||||
{
|
||||
name: "keybind_with_comment",
|
||||
line: "bind=Alt,t,spawn,kitty # Open terminal",
|
||||
expected: &KeyBinding{
|
||||
expected: &MangoWCKeyBinding{
|
||||
Mods: []string{"Alt"},
|
||||
Key: "t",
|
||||
Command: "spawn",
|
||||
@@ -116,7 +116,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
||||
{
|
||||
name: "keybind_multiple_mods",
|
||||
line: "bind=SUPER+SHIFT,Up,exchange_client,up",
|
||||
expected: &KeyBinding{
|
||||
expected: &MangoWCKeyBinding{
|
||||
Mods: []string{"SUPER", "SHIFT"},
|
||||
Key: "Up",
|
||||
Command: "exchange_client",
|
||||
@@ -127,7 +127,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
||||
{
|
||||
name: "keybind_no_mods",
|
||||
line: "bind=NONE,Print,spawn,screenshot",
|
||||
expected: &KeyBinding{
|
||||
expected: &MangoWCKeyBinding{
|
||||
Mods: []string{},
|
||||
Key: "Print",
|
||||
Command: "spawn",
|
||||
@@ -138,7 +138,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
||||
{
|
||||
name: "keybind_multiple_params",
|
||||
line: "bind=Ctrl,1,view,1,0",
|
||||
expected: &KeyBinding{
|
||||
expected: &MangoWCKeyBinding{
|
||||
Mods: []string{"Ctrl"},
|
||||
Key: "1",
|
||||
Command: "view",
|
||||
@@ -149,7 +149,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
||||
{
|
||||
name: "bindl_flag",
|
||||
line: "bindl=SUPER+ALT,l,spawn,dms ipc call lock lock",
|
||||
expected: &KeyBinding{
|
||||
expected: &MangoWCKeyBinding{
|
||||
Mods: []string{"SUPER", "ALT"},
|
||||
Key: "l",
|
||||
Command: "spawn",
|
||||
@@ -160,7 +160,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
||||
{
|
||||
name: "keybind_with_spaces",
|
||||
line: "bind = SUPER, r, reload_config",
|
||||
expected: &KeyBinding{
|
||||
expected: &MangoWCKeyBinding{
|
||||
Mods: []string{"SUPER"},
|
||||
Key: "r",
|
||||
Command: "reload_config",
|
||||
@@ -172,7 +172,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
parser := NewParser()
|
||||
parser := NewMangoWCParser()
|
||||
parser.contentLines = []string{tt.line}
|
||||
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()
|
||||
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)
|
||||
}
|
||||
|
||||
keybinds, err := ParseKeys(configFile)
|
||||
keybinds, err := ParseMangoWCKeys(configFile)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseKeys failed: %v", err)
|
||||
t.Fatalf("ParseMangoWCKeys failed: %v", err)
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
file1 := filepath.Join(tmpDir, "a.conf")
|
||||
@@ -283,7 +283,7 @@ func TestReadContentMultipleFiles(t *testing.T) {
|
||||
t.Fatalf("Failed to write file2: %v", err)
|
||||
}
|
||||
|
||||
parser := NewParser()
|
||||
parser := NewMangoWCParser()
|
||||
if err := parser.ReadContent(tmpDir); err != nil {
|
||||
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()
|
||||
configFile := filepath.Join(tmpDir, "config.conf")
|
||||
|
||||
@@ -304,7 +304,7 @@ func TestReadContentSingleFile(t *testing.T) {
|
||||
t.Fatalf("Failed to write config: %v", err)
|
||||
}
|
||||
|
||||
parser := NewParser()
|
||||
parser := NewMangoWCParser()
|
||||
if err := parser.ReadContent(configFile); err != nil {
|
||||
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 {
|
||||
name string
|
||||
path string
|
||||
@@ -332,7 +332,7 @@ func TestReadContentErrors(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := ParseKeys(tt.path)
|
||||
_, err := ParseMangoWCKeys(tt.path)
|
||||
if err == 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()
|
||||
if err != nil {
|
||||
t.Skip("Cannot get home directory")
|
||||
@@ -362,7 +362,7 @@ func TestReadContentWithTildeExpansion(t *testing.T) {
|
||||
t.Skip("Cannot create relative path")
|
||||
}
|
||||
|
||||
parser := NewParser()
|
||||
parser := NewMangoWCParser()
|
||||
tildePathMatch := "~/" + relPath
|
||||
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()
|
||||
configFile := filepath.Join(tmpDir, "config.conf")
|
||||
|
||||
@@ -388,9 +388,9 @@ bind=Alt,t,spawn,kitty
|
||||
t.Fatalf("Failed to write test config: %v", err)
|
||||
}
|
||||
|
||||
keybinds, err := ParseKeys(configFile)
|
||||
keybinds, err := ParseMangoWCKeys(configFile)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseKeys failed: %v", err)
|
||||
t.Fatalf("ParseMangoWCKeys failed: %v", err)
|
||||
}
|
||||
|
||||
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 {
|
||||
name string
|
||||
line string
|
||||
@@ -419,7 +419,7 @@ func TestInvalidBindLines(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
parser := NewParser()
|
||||
parser := NewMangoWCParser()
|
||||
parser.contentLines = []string{tt.line}
|
||||
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()
|
||||
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)
|
||||
}
|
||||
|
||||
keybinds, err := ParseKeys(configFile)
|
||||
keybinds, err := ParseMangoWCKeys(configFile)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseKeys failed: %v", err)
|
||||
t.Fatalf("ParseMangoWCKeys failed: %v", err)
|
||||
}
|
||||
|
||||
if len(keybinds) < 14 {
|
||||
@@ -4,8 +4,6 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/mangowc"
|
||||
)
|
||||
|
||||
func TestMangoWCProviderName(t *testing.T) {
|
||||
@@ -88,12 +86,12 @@ func TestMangoWCCategorizeByCommand(t *testing.T) {
|
||||
func TestMangoWCFormatKey(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
keybind *mangowc.KeyBinding
|
||||
keybind *MangoWCKeyBinding
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "single_mod",
|
||||
keybind: &mangowc.KeyBinding{
|
||||
keybind: &MangoWCKeyBinding{
|
||||
Mods: []string{"ALT"},
|
||||
Key: "q",
|
||||
},
|
||||
@@ -101,7 +99,7 @@ func TestMangoWCFormatKey(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "multiple_mods",
|
||||
keybind: &mangowc.KeyBinding{
|
||||
keybind: &MangoWCKeyBinding{
|
||||
Mods: []string{"SUPER", "SHIFT"},
|
||||
Key: "Up",
|
||||
},
|
||||
@@ -109,7 +107,7 @@ func TestMangoWCFormatKey(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "no_mods",
|
||||
keybind: &mangowc.KeyBinding{
|
||||
keybind: &MangoWCKeyBinding{
|
||||
Mods: []string{},
|
||||
Key: "Print",
|
||||
},
|
||||
@@ -131,13 +129,13 @@ func TestMangoWCFormatKey(t *testing.T) {
|
||||
func TestMangoWCConvertKeybind(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
keybind *mangowc.KeyBinding
|
||||
keybind *MangoWCKeyBinding
|
||||
wantKey string
|
||||
wantDesc string
|
||||
}{
|
||||
{
|
||||
name: "with_comment",
|
||||
keybind: &mangowc.KeyBinding{
|
||||
keybind: &MangoWCKeyBinding{
|
||||
Mods: []string{"ALT"},
|
||||
Key: "t",
|
||||
Command: "spawn",
|
||||
@@ -149,7 +147,7 @@ func TestMangoWCConvertKeybind(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "without_comment",
|
||||
keybind: &mangowc.KeyBinding{
|
||||
keybind: &MangoWCKeyBinding{
|
||||
Mods: []string{"SUPER"},
|
||||
Key: "r",
|
||||
Command: "reload_config",
|
||||
@@ -161,7 +159,7 @@ func TestMangoWCConvertKeybind(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "with_params_no_comment",
|
||||
keybind: &mangowc.KeyBinding{
|
||||
keybind: &MangoWCKeyBinding{
|
||||
Mods: []string{"CTRL"},
|
||||
Key: "1",
|
||||
Command: "view",
|
||||
|
||||
539
core/internal/keybinds/providers/niri.go
Normal file
539
core/internal/keybinds/providers/niri.go
Normal file
@@ -0,0 +1,539 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
|
||||
"github.com/sblinch/kdl-go"
|
||||
"github.com/sblinch/kdl-go/document"
|
||||
)
|
||||
|
||||
type NiriProvider struct {
|
||||
configDir string
|
||||
dmsBindsIncluded bool
|
||||
parsed bool
|
||||
}
|
||||
|
||||
func NewNiriProvider(configDir string) *NiriProvider {
|
||||
if configDir == "" {
|
||||
configDir = defaultNiriConfigDir()
|
||||
}
|
||||
return &NiriProvider{
|
||||
configDir: configDir,
|
||||
}
|
||||
}
|
||||
|
||||
func defaultNiriConfigDir() string {
|
||||
if configHome := os.Getenv("XDG_CONFIG_HOME"); configHome != "" {
|
||||
return filepath.Join(configHome, "niri")
|
||||
}
|
||||
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return filepath.Join(home, ".config", "niri")
|
||||
}
|
||||
|
||||
func (n *NiriProvider) Name() string {
|
||||
return "niri"
|
||||
}
|
||||
|
||||
func (n *NiriProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
|
||||
result, err := ParseNiriKeys(n.configDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse niri config: %w", err)
|
||||
}
|
||||
|
||||
n.dmsBindsIncluded = result.DMSBindsIncluded
|
||||
n.parsed = true
|
||||
|
||||
categorizedBinds := make(map[string][]keybinds.Keybind)
|
||||
n.convertSection(result.Section, "", categorizedBinds, result.ConflictingConfigs)
|
||||
|
||||
sheet := &keybinds.CheatSheet{
|
||||
Title: "Niri Keybinds",
|
||||
Provider: n.Name(),
|
||||
Binds: categorizedBinds,
|
||||
DMSBindsIncluded: result.DMSBindsIncluded,
|
||||
}
|
||||
|
||||
if result.DMSStatus != nil {
|
||||
sheet.DMSStatus = &keybinds.DMSBindsStatus{
|
||||
Exists: result.DMSStatus.Exists,
|
||||
Included: result.DMSStatus.Included,
|
||||
IncludePosition: result.DMSStatus.IncludePosition,
|
||||
TotalIncludes: result.DMSStatus.TotalIncludes,
|
||||
BindsAfterDMS: result.DMSStatus.BindsAfterDMS,
|
||||
Effective: result.DMSStatus.Effective,
|
||||
OverriddenBy: result.DMSStatus.OverriddenBy,
|
||||
StatusMessage: result.DMSStatus.StatusMessage,
|
||||
}
|
||||
}
|
||||
|
||||
return sheet, nil
|
||||
}
|
||||
|
||||
func (n *NiriProvider) HasDMSBindsIncluded() bool {
|
||||
if n.parsed {
|
||||
return n.dmsBindsIncluded
|
||||
}
|
||||
|
||||
result, err := ParseNiriKeys(n.configDir)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
n.dmsBindsIncluded = result.DMSBindsIncluded
|
||||
n.parsed = true
|
||||
return n.dmsBindsIncluded
|
||||
}
|
||||
|
||||
func (n *NiriProvider) convertSection(section *NiriSection, subcategory string, categorizedBinds map[string][]keybinds.Keybind, conflicts map[string]*NiriKeyBinding) {
|
||||
currentSubcat := subcategory
|
||||
if section.Name != "" {
|
||||
currentSubcat = section.Name
|
||||
}
|
||||
|
||||
for _, kb := range section.Keybinds {
|
||||
category := n.categorizeByAction(kb.Action)
|
||||
bind := n.convertKeybind(&kb, currentSubcat, conflicts)
|
||||
categorizedBinds[category] = append(categorizedBinds[category], bind)
|
||||
}
|
||||
|
||||
for _, child := range section.Children {
|
||||
n.convertSection(&child, currentSubcat, categorizedBinds, conflicts)
|
||||
}
|
||||
}
|
||||
|
||||
func (n *NiriProvider) categorizeByAction(action string) string {
|
||||
switch {
|
||||
case action == "next-window" || action == "previous-window":
|
||||
return "Alt-Tab"
|
||||
case strings.Contains(action, "screenshot"):
|
||||
return "Screenshot"
|
||||
case action == "show-hotkey-overlay" || action == "toggle-overview":
|
||||
return "Overview"
|
||||
case action == "quit" ||
|
||||
action == "power-off-monitors" ||
|
||||
action == "toggle-keyboard-shortcuts-inhibit" ||
|
||||
strings.Contains(action, "dpms"):
|
||||
return "System"
|
||||
case action == "spawn":
|
||||
return "Execute"
|
||||
case strings.Contains(action, "workspace"):
|
||||
return "Workspace"
|
||||
case strings.HasPrefix(action, "focus-monitor") ||
|
||||
strings.HasPrefix(action, "move-column-to-monitor") ||
|
||||
strings.HasPrefix(action, "move-window-to-monitor"):
|
||||
return "Monitor"
|
||||
case strings.Contains(action, "window") ||
|
||||
strings.Contains(action, "focus") ||
|
||||
strings.Contains(action, "move") ||
|
||||
strings.Contains(action, "swap") ||
|
||||
strings.Contains(action, "resize") ||
|
||||
strings.Contains(action, "column"):
|
||||
return "Window"
|
||||
default:
|
||||
return "Other"
|
||||
}
|
||||
}
|
||||
|
||||
func (n *NiriProvider) convertKeybind(kb *NiriKeyBinding, subcategory string, conflicts map[string]*NiriKeyBinding) keybinds.Keybind {
|
||||
rawAction := n.formatRawAction(kb.Action, kb.Args)
|
||||
keyStr := n.formatKey(kb)
|
||||
|
||||
source := "config"
|
||||
if strings.Contains(kb.Source, "dms/binds.kdl") {
|
||||
source = "dms"
|
||||
}
|
||||
|
||||
bind := keybinds.Keybind{
|
||||
Key: keyStr,
|
||||
Description: kb.Description,
|
||||
Action: rawAction,
|
||||
Subcategory: subcategory,
|
||||
Source: source,
|
||||
}
|
||||
|
||||
if source == "dms" && conflicts != nil {
|
||||
if conflictKb, ok := conflicts[keyStr]; ok {
|
||||
bind.Conflict = &keybinds.Keybind{
|
||||
Key: keyStr,
|
||||
Description: conflictKb.Description,
|
||||
Action: n.formatRawAction(conflictKb.Action, conflictKb.Args),
|
||||
Source: "config",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bind
|
||||
}
|
||||
|
||||
func (n *NiriProvider) formatRawAction(action string, args []string) string {
|
||||
if len(args) == 0 {
|
||||
return action
|
||||
}
|
||||
|
||||
if action == "spawn" && len(args) >= 3 && args[1] == "-c" {
|
||||
switch args[0] {
|
||||
case "sh", "bash":
|
||||
cmd := strings.Join(args[2:], " ")
|
||||
return fmt.Sprintf("spawn %s -c \"%s\"", args[0], strings.ReplaceAll(cmd, "\"", "\\\""))
|
||||
}
|
||||
}
|
||||
|
||||
return action + " " + strings.Join(args, " ")
|
||||
}
|
||||
|
||||
func (n *NiriProvider) formatKey(kb *NiriKeyBinding) string {
|
||||
parts := make([]string, 0, len(kb.Mods)+1)
|
||||
parts = append(parts, kb.Mods...)
|
||||
parts = append(parts, kb.Key)
|
||||
return strings.Join(parts, "+")
|
||||
}
|
||||
|
||||
func (n *NiriProvider) GetOverridePath() string {
|
||||
return filepath.Join(n.configDir, "dms", "binds.kdl")
|
||||
}
|
||||
|
||||
func (n *NiriProvider) validateAction(action string) error {
|
||||
action = strings.TrimSpace(action)
|
||||
switch {
|
||||
case action == "":
|
||||
return fmt.Errorf("action cannot be empty")
|
||||
case action == "spawn" || action == "spawn ":
|
||||
return fmt.Errorf("spawn command requires arguments")
|
||||
case strings.HasPrefix(action, "spawn "):
|
||||
rest := strings.TrimSpace(strings.TrimPrefix(action, "spawn "))
|
||||
switch rest {
|
||||
case "":
|
||||
return fmt.Errorf("spawn command requires arguments")
|
||||
case "sh -c \"\"", "sh -c ''", "bash -c \"\"", "bash -c ''":
|
||||
return fmt.Errorf("shell command cannot be empty")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NiriProvider) SetBind(key, action, description string, options map[string]any) error {
|
||||
if err := n.validateAction(action); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
overridePath := n.GetOverridePath()
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(overridePath), 0755); err != nil {
|
||||
return fmt.Errorf("failed to create dms directory: %w", err)
|
||||
}
|
||||
|
||||
existingBinds, err := n.loadOverrideBinds()
|
||||
if err != nil {
|
||||
existingBinds = make(map[string]*overrideBind)
|
||||
}
|
||||
|
||||
existingBinds[key] = &overrideBind{
|
||||
Key: key,
|
||||
Action: action,
|
||||
Description: description,
|
||||
Options: options,
|
||||
}
|
||||
|
||||
return n.writeOverrideBinds(existingBinds)
|
||||
}
|
||||
|
||||
func (n *NiriProvider) RemoveBind(key string) error {
|
||||
existingBinds, err := n.loadOverrideBinds()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
delete(existingBinds, key)
|
||||
return n.writeOverrideBinds(existingBinds)
|
||||
}
|
||||
|
||||
type overrideBind struct {
|
||||
Key string
|
||||
Action string
|
||||
Description string
|
||||
Options map[string]any
|
||||
}
|
||||
|
||||
func (n *NiriProvider) loadOverrideBinds() (map[string]*overrideBind, error) {
|
||||
overridePath := n.GetOverridePath()
|
||||
binds := make(map[string]*overrideBind)
|
||||
|
||||
data, err := os.ReadFile(overridePath)
|
||||
if os.IsNotExist(err) {
|
||||
return binds, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parser := NewNiriParser(filepath.Dir(overridePath))
|
||||
parser.currentSource = overridePath
|
||||
|
||||
doc, err := kdl.Parse(strings.NewReader(string(data)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, node := range doc.Nodes {
|
||||
if node.Name.String() != "binds" || node.Children == nil {
|
||||
continue
|
||||
}
|
||||
for _, child := range node.Children {
|
||||
kb := parser.parseKeybindNode(child, "")
|
||||
if kb == nil {
|
||||
continue
|
||||
}
|
||||
keyStr := parser.formatBindKey(kb)
|
||||
binds[keyStr] = &overrideBind{
|
||||
Key: keyStr,
|
||||
Action: n.formatRawAction(kb.Action, kb.Args),
|
||||
Description: kb.Description,
|
||||
Options: n.extractOptions(child),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return binds, nil
|
||||
}
|
||||
|
||||
func (n *NiriProvider) extractOptions(node *document.Node) map[string]any {
|
||||
if node.Properties == nil {
|
||||
return make(map[string]any)
|
||||
}
|
||||
|
||||
opts := make(map[string]any)
|
||||
if val, ok := node.Properties.Get("repeat"); ok {
|
||||
opts["repeat"] = val.String() == "true"
|
||||
}
|
||||
if val, ok := node.Properties.Get("cooldown-ms"); ok {
|
||||
opts["cooldown-ms"] = val.String()
|
||||
}
|
||||
if val, ok := node.Properties.Get("allow-when-locked"); ok {
|
||||
opts["allow-when-locked"] = val.String() == "true"
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func (n *NiriProvider) isRecentWindowsAction(action string) bool {
|
||||
switch action {
|
||||
case "next-window", "previous-window":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (n *NiriProvider) parseSpawnArgs(s string) []string {
|
||||
var args []string
|
||||
var current strings.Builder
|
||||
var inQuote, escaped bool
|
||||
|
||||
for _, r := range s {
|
||||
switch {
|
||||
case escaped:
|
||||
current.WriteRune(r)
|
||||
escaped = false
|
||||
case r == '\\':
|
||||
escaped = true
|
||||
case r == '"':
|
||||
inQuote = !inQuote
|
||||
case r == ' ' && !inQuote:
|
||||
if current.Len() > 0 {
|
||||
args = append(args, current.String())
|
||||
current.Reset()
|
||||
}
|
||||
default:
|
||||
current.WriteRune(r)
|
||||
}
|
||||
}
|
||||
if current.Len() > 0 {
|
||||
args = append(args, current.String())
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func (n *NiriProvider) buildBindNode(bind *overrideBind) *document.Node {
|
||||
node := document.NewNode()
|
||||
node.SetName(bind.Key)
|
||||
|
||||
if bind.Options != nil {
|
||||
if v, ok := bind.Options["repeat"]; ok && v == false {
|
||||
node.AddProperty("repeat", false, "")
|
||||
}
|
||||
if v, ok := bind.Options["cooldown-ms"]; ok {
|
||||
node.AddProperty("cooldown-ms", v, "")
|
||||
}
|
||||
if v, ok := bind.Options["allow-when-locked"]; ok && v == true {
|
||||
node.AddProperty("allow-when-locked", true, "")
|
||||
}
|
||||
}
|
||||
|
||||
if bind.Description != "" {
|
||||
node.AddProperty("hotkey-overlay-title", bind.Description, "")
|
||||
}
|
||||
|
||||
actionNode := n.buildActionNode(bind.Action)
|
||||
node.AddNode(actionNode)
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (n *NiriProvider) buildActionNode(action string) *document.Node {
|
||||
action = strings.TrimSpace(action)
|
||||
node := document.NewNode()
|
||||
|
||||
if !strings.HasPrefix(action, "spawn ") {
|
||||
node.SetName(action)
|
||||
return node
|
||||
}
|
||||
|
||||
node.SetName("spawn")
|
||||
args := n.parseSpawnArgs(strings.TrimPrefix(action, "spawn "))
|
||||
for _, arg := range args {
|
||||
node.AddArgument(arg, "")
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
func (n *NiriProvider) writeOverrideBinds(binds map[string]*overrideBind) error {
|
||||
overridePath := n.GetOverridePath()
|
||||
content := n.generateBindsContent(binds)
|
||||
|
||||
if err := n.validateBindsContent(content); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(overridePath, []byte(content), 0644)
|
||||
}
|
||||
|
||||
func (n *NiriProvider) getBindSortPriority(action string) int {
|
||||
switch {
|
||||
case strings.HasPrefix(action, "spawn") && strings.Contains(action, "dms"):
|
||||
return 0
|
||||
case strings.Contains(action, "workspace"):
|
||||
return 1
|
||||
case strings.Contains(action, "window") || strings.Contains(action, "column") ||
|
||||
strings.Contains(action, "focus") || strings.Contains(action, "move") ||
|
||||
strings.Contains(action, "swap") || strings.Contains(action, "resize"):
|
||||
return 2
|
||||
case strings.HasPrefix(action, "focus-monitor") || strings.Contains(action, "monitor"):
|
||||
return 3
|
||||
case strings.Contains(action, "screenshot"):
|
||||
return 4
|
||||
case action == "quit" || action == "power-off-monitors" || strings.Contains(action, "dpms"):
|
||||
return 5
|
||||
case strings.HasPrefix(action, "spawn"):
|
||||
return 6
|
||||
default:
|
||||
return 7
|
||||
}
|
||||
}
|
||||
|
||||
func (n *NiriProvider) generateBindsContent(binds map[string]*overrideBind) string {
|
||||
if len(binds) == 0 {
|
||||
return "binds {}\n"
|
||||
}
|
||||
|
||||
var regularBinds, recentWindowsBinds []*overrideBind
|
||||
for _, bind := range binds {
|
||||
switch {
|
||||
case n.isRecentWindowsAction(bind.Action):
|
||||
recentWindowsBinds = append(recentWindowsBinds, bind)
|
||||
default:
|
||||
regularBinds = append(regularBinds, bind)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(regularBinds, func(i, j int) bool {
|
||||
pi, pj := n.getBindSortPriority(regularBinds[i].Action), n.getBindSortPriority(regularBinds[j].Action)
|
||||
if pi != pj {
|
||||
return pi < pj
|
||||
}
|
||||
return regularBinds[i].Key < regularBinds[j].Key
|
||||
})
|
||||
|
||||
sort.Slice(recentWindowsBinds, func(i, j int) bool {
|
||||
return recentWindowsBinds[i].Key < recentWindowsBinds[j].Key
|
||||
})
|
||||
|
||||
var sb strings.Builder
|
||||
|
||||
sb.WriteString("binds {\n")
|
||||
for _, bind := range regularBinds {
|
||||
n.writeBindNode(&sb, bind, " ")
|
||||
}
|
||||
sb.WriteString("}\n")
|
||||
|
||||
if len(recentWindowsBinds) > 0 {
|
||||
sb.WriteString("\nrecent-windows {\n")
|
||||
sb.WriteString(" binds {\n")
|
||||
for _, bind := range recentWindowsBinds {
|
||||
n.writeBindNode(&sb, bind, " ")
|
||||
}
|
||||
sb.WriteString(" }\n")
|
||||
sb.WriteString("}\n")
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (n *NiriProvider) writeBindNode(sb *strings.Builder, bind *overrideBind, indent string) {
|
||||
node := n.buildBindNode(bind)
|
||||
|
||||
sb.WriteString(indent)
|
||||
sb.WriteString(node.Name.String())
|
||||
|
||||
if node.Properties.Exist() {
|
||||
sb.WriteString(" ")
|
||||
sb.WriteString(strings.TrimLeft(node.Properties.String(), " "))
|
||||
}
|
||||
|
||||
sb.WriteString(" { ")
|
||||
if len(node.Children) > 0 {
|
||||
child := node.Children[0]
|
||||
sb.WriteString(child.Name.String())
|
||||
for _, arg := range child.Arguments {
|
||||
sb.WriteString(" ")
|
||||
n.writeQuotedArg(sb, arg.ValueString())
|
||||
}
|
||||
}
|
||||
sb.WriteString("; }\n")
|
||||
}
|
||||
|
||||
func (n *NiriProvider) writeQuotedArg(sb *strings.Builder, val string) {
|
||||
sb.WriteString("\"")
|
||||
sb.WriteString(strings.ReplaceAll(val, "\"", "\\\""))
|
||||
sb.WriteString("\"")
|
||||
}
|
||||
|
||||
func (n *NiriProvider) validateBindsContent(content string) error {
|
||||
tmpFile, err := os.CreateTemp("", "dms-binds-*.kdl")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create temp file: %w", err)
|
||||
}
|
||||
defer os.Remove(tmpFile.Name())
|
||||
|
||||
if _, err := tmpFile.WriteString(content); err != nil {
|
||||
tmpFile.Close()
|
||||
return fmt.Errorf("failed to write temp file: %w", err)
|
||||
}
|
||||
tmpFile.Close()
|
||||
|
||||
cmd := exec.Command("niri", "validate", "-c", tmpFile.Name())
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid config: %s", strings.TrimSpace(string(output)))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
358
core/internal/keybinds/providers/niri_parser.go
Normal file
358
core/internal/keybinds/providers/niri_parser.go
Normal file
@@ -0,0 +1,358 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/sblinch/kdl-go"
|
||||
"github.com/sblinch/kdl-go/document"
|
||||
)
|
||||
|
||||
type NiriKeyBinding struct {
|
||||
Mods []string
|
||||
Key string
|
||||
Action string
|
||||
Args []string
|
||||
Description string
|
||||
Source string
|
||||
}
|
||||
|
||||
type NiriSection struct {
|
||||
Name string
|
||||
Keybinds []NiriKeyBinding
|
||||
Children []NiriSection
|
||||
}
|
||||
|
||||
type NiriParser struct {
|
||||
configDir string
|
||||
processedFiles map[string]bool
|
||||
bindMap map[string]*NiriKeyBinding
|
||||
bindOrder []string
|
||||
currentSource string
|
||||
dmsBindsIncluded bool
|
||||
dmsBindsExists bool
|
||||
includeCount int
|
||||
dmsIncludePos int
|
||||
bindsBeforeDMS int
|
||||
bindsAfterDMS int
|
||||
dmsBindKeys map[string]bool
|
||||
configBindKeys map[string]bool
|
||||
dmsProcessed bool
|
||||
dmsBindMap map[string]*NiriKeyBinding
|
||||
conflictingConfigs map[string]*NiriKeyBinding
|
||||
}
|
||||
|
||||
func NewNiriParser(configDir string) *NiriParser {
|
||||
return &NiriParser{
|
||||
configDir: configDir,
|
||||
processedFiles: make(map[string]bool),
|
||||
bindMap: make(map[string]*NiriKeyBinding),
|
||||
bindOrder: []string{},
|
||||
currentSource: "",
|
||||
dmsIncludePos: -1,
|
||||
dmsBindKeys: make(map[string]bool),
|
||||
configBindKeys: make(map[string]bool),
|
||||
dmsBindMap: make(map[string]*NiriKeyBinding),
|
||||
conflictingConfigs: make(map[string]*NiriKeyBinding),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *NiriParser) Parse() (*NiriSection, error) {
|
||||
dmsBindsPath := filepath.Join(p.configDir, "dms", "binds.kdl")
|
||||
if _, err := os.Stat(dmsBindsPath); err == nil {
|
||||
p.dmsBindsExists = true
|
||||
}
|
||||
|
||||
configPath := filepath.Join(p.configDir, "config.kdl")
|
||||
section, err := p.parseFile(configPath, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if p.dmsBindsExists && !p.dmsProcessed {
|
||||
p.parseDMSBindsDirectly(dmsBindsPath, section)
|
||||
}
|
||||
|
||||
section.Keybinds = p.finalizeBinds()
|
||||
return section, nil
|
||||
}
|
||||
|
||||
func (p *NiriParser) parseDMSBindsDirectly(dmsBindsPath string, section *NiriSection) {
|
||||
data, err := os.ReadFile(dmsBindsPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
doc, err := kdl.Parse(strings.NewReader(string(data)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
prevSource := p.currentSource
|
||||
p.currentSource = dmsBindsPath
|
||||
baseDir := filepath.Dir(dmsBindsPath)
|
||||
p.processNodes(doc.Nodes, section, baseDir)
|
||||
p.currentSource = prevSource
|
||||
p.dmsProcessed = true
|
||||
}
|
||||
|
||||
func (p *NiriParser) finalizeBinds() []NiriKeyBinding {
|
||||
binds := make([]NiriKeyBinding, 0, len(p.bindOrder))
|
||||
for _, key := range p.bindOrder {
|
||||
if kb, ok := p.bindMap[key]; ok {
|
||||
binds = append(binds, *kb)
|
||||
}
|
||||
}
|
||||
return binds
|
||||
}
|
||||
|
||||
func (p *NiriParser) addBind(kb *NiriKeyBinding) {
|
||||
key := p.formatBindKey(kb)
|
||||
isDMSBind := strings.Contains(kb.Source, "dms/binds.kdl")
|
||||
|
||||
if isDMSBind {
|
||||
p.dmsBindKeys[key] = true
|
||||
p.dmsBindMap[key] = kb
|
||||
} else if p.dmsBindKeys[key] {
|
||||
p.bindsAfterDMS++
|
||||
p.conflictingConfigs[key] = kb
|
||||
p.configBindKeys[key] = true
|
||||
return
|
||||
} else {
|
||||
p.configBindKeys[key] = true
|
||||
}
|
||||
|
||||
if _, exists := p.bindMap[key]; !exists {
|
||||
p.bindOrder = append(p.bindOrder, key)
|
||||
}
|
||||
p.bindMap[key] = kb
|
||||
}
|
||||
|
||||
func (p *NiriParser) formatBindKey(kb *NiriKeyBinding) string {
|
||||
parts := make([]string, 0, len(kb.Mods)+1)
|
||||
parts = append(parts, kb.Mods...)
|
||||
parts = append(parts, kb.Key)
|
||||
return strings.Join(parts, "+")
|
||||
}
|
||||
|
||||
func (p *NiriParser) parseFile(filePath, sectionName string) (*NiriSection, error) {
|
||||
absPath, err := filepath.Abs(filePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve path %s: %w", filePath, err)
|
||||
}
|
||||
|
||||
if p.processedFiles[absPath] {
|
||||
return &NiriSection{Name: sectionName}, nil
|
||||
}
|
||||
p.processedFiles[absPath] = true
|
||||
|
||||
data, err := os.ReadFile(absPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read %s: %w", absPath, err)
|
||||
}
|
||||
|
||||
doc, err := kdl.Parse(strings.NewReader(string(data)))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse KDL in %s: %w", absPath, err)
|
||||
}
|
||||
|
||||
section := &NiriSection{
|
||||
Name: sectionName,
|
||||
}
|
||||
|
||||
prevSource := p.currentSource
|
||||
p.currentSource = absPath
|
||||
baseDir := filepath.Dir(absPath)
|
||||
p.processNodes(doc.Nodes, section, baseDir)
|
||||
p.currentSource = prevSource
|
||||
|
||||
return section, nil
|
||||
}
|
||||
|
||||
func (p *NiriParser) processNodes(nodes []*document.Node, section *NiriSection, baseDir string) {
|
||||
for _, node := range nodes {
|
||||
name := node.Name.String()
|
||||
|
||||
switch name {
|
||||
case "include":
|
||||
p.handleInclude(node, section, baseDir)
|
||||
case "binds":
|
||||
p.extractBinds(node, section, "")
|
||||
case "recent-windows":
|
||||
p.handleRecentWindows(node, section)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *NiriParser) handleInclude(node *document.Node, section *NiriSection, baseDir string) {
|
||||
if len(node.Arguments) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
includePath := strings.Trim(node.Arguments[0].String(), "\"")
|
||||
isDMSInclude := includePath == "dms/binds.kdl" || strings.HasSuffix(includePath, "/dms/binds.kdl")
|
||||
|
||||
p.includeCount++
|
||||
if isDMSInclude {
|
||||
p.dmsBindsIncluded = true
|
||||
p.dmsIncludePos = p.includeCount
|
||||
p.bindsBeforeDMS = len(p.bindMap)
|
||||
}
|
||||
|
||||
fullPath := filepath.Join(baseDir, includePath)
|
||||
if filepath.IsAbs(includePath) {
|
||||
fullPath = includePath
|
||||
}
|
||||
|
||||
if isDMSInclude {
|
||||
p.dmsProcessed = true
|
||||
}
|
||||
|
||||
includedSection, err := p.parseFile(fullPath, "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
section.Children = append(section.Children, includedSection.Children...)
|
||||
}
|
||||
|
||||
func (p *NiriParser) HasDMSBindsIncluded() bool {
|
||||
return p.dmsBindsIncluded
|
||||
}
|
||||
|
||||
func (p *NiriParser) handleRecentWindows(node *document.Node, section *NiriSection) {
|
||||
if node.Children == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, child := range node.Children {
|
||||
if child.Name.String() != "binds" {
|
||||
continue
|
||||
}
|
||||
p.extractBinds(child, section, "Alt-Tab")
|
||||
}
|
||||
}
|
||||
|
||||
func (p *NiriParser) extractBinds(node *document.Node, section *NiriSection, subcategory string) {
|
||||
if node.Children == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, child := range node.Children {
|
||||
kb := p.parseKeybindNode(child, subcategory)
|
||||
if kb == nil {
|
||||
continue
|
||||
}
|
||||
p.addBind(kb)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *NiriParser) parseKeybindNode(node *document.Node, _ string) *NiriKeyBinding {
|
||||
keyCombo := node.Name.String()
|
||||
if keyCombo == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
mods, key := p.parseKeyCombo(keyCombo)
|
||||
|
||||
var action string
|
||||
var args []string
|
||||
if len(node.Children) > 0 {
|
||||
actionNode := node.Children[0]
|
||||
action = actionNode.Name.String()
|
||||
for _, arg := range actionNode.Arguments {
|
||||
args = append(args, arg.ValueString())
|
||||
}
|
||||
}
|
||||
|
||||
var description string
|
||||
if node.Properties != nil {
|
||||
if val, ok := node.Properties.Get("hotkey-overlay-title"); ok {
|
||||
description = val.ValueString()
|
||||
}
|
||||
}
|
||||
|
||||
return &NiriKeyBinding{
|
||||
Mods: mods,
|
||||
Key: key,
|
||||
Action: action,
|
||||
Args: args,
|
||||
Description: description,
|
||||
Source: p.currentSource,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *NiriParser) parseKeyCombo(combo string) ([]string, string) {
|
||||
parts := strings.Split(combo, "+")
|
||||
|
||||
switch len(parts) {
|
||||
case 0:
|
||||
return nil, combo
|
||||
case 1:
|
||||
return nil, parts[0]
|
||||
default:
|
||||
return parts[:len(parts)-1], parts[len(parts)-1]
|
||||
}
|
||||
}
|
||||
|
||||
type NiriParseResult struct {
|
||||
Section *NiriSection
|
||||
DMSBindsIncluded bool
|
||||
DMSStatus *DMSBindsStatusInfo
|
||||
ConflictingConfigs map[string]*NiriKeyBinding
|
||||
}
|
||||
|
||||
type DMSBindsStatusInfo struct {
|
||||
Exists bool
|
||||
Included bool
|
||||
IncludePosition int
|
||||
TotalIncludes int
|
||||
BindsAfterDMS int
|
||||
Effective bool
|
||||
OverriddenBy int
|
||||
StatusMessage string
|
||||
}
|
||||
|
||||
func (p *NiriParser) buildDMSStatus() *DMSBindsStatusInfo {
|
||||
status := &DMSBindsStatusInfo{
|
||||
Exists: p.dmsBindsExists,
|
||||
Included: p.dmsBindsIncluded,
|
||||
IncludePosition: p.dmsIncludePos,
|
||||
TotalIncludes: p.includeCount,
|
||||
BindsAfterDMS: p.bindsAfterDMS,
|
||||
}
|
||||
|
||||
switch {
|
||||
case !p.dmsBindsExists:
|
||||
status.Effective = false
|
||||
status.StatusMessage = "dms/binds.kdl does not exist"
|
||||
case !p.dmsBindsIncluded:
|
||||
status.Effective = false
|
||||
status.StatusMessage = "dms/binds.kdl is not included in config.kdl"
|
||||
case p.bindsAfterDMS > 0:
|
||||
status.Effective = true
|
||||
status.OverriddenBy = p.bindsAfterDMS
|
||||
status.StatusMessage = "Some DMS binds may be overridden by config binds"
|
||||
default:
|
||||
status.Effective = true
|
||||
status.StatusMessage = "DMS binds are active"
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
func ParseNiriKeys(configDir string) (*NiriParseResult, error) {
|
||||
parser := NewNiriParser(configDir)
|
||||
section, err := parser.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &NiriParseResult{
|
||||
Section: section,
|
||||
DMSBindsIncluded: parser.HasDMSBindsIncluded(),
|
||||
DMSStatus: parser.buildDMSStatus(),
|
||||
ConflictingConfigs: parser.conflictingConfigs,
|
||||
}, nil
|
||||
}
|
||||
498
core/internal/keybinds/providers/niri_parser_test.go
Normal file
498
core/internal/keybinds/providers/niri_parser_test.go
Normal file
@@ -0,0 +1,498 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNiriParseKeyCombo(t *testing.T) {
|
||||
tests := []struct {
|
||||
combo string
|
||||
expectedMods []string
|
||||
expectedKey string
|
||||
}{
|
||||
{"Mod+Q", []string{"Mod"}, "Q"},
|
||||
{"Mod+Shift+F", []string{"Mod", "Shift"}, "F"},
|
||||
{"Ctrl+Alt+Delete", []string{"Ctrl", "Alt"}, "Delete"},
|
||||
{"Print", nil, "Print"},
|
||||
{"XF86AudioMute", nil, "XF86AudioMute"},
|
||||
{"Super+Tab", []string{"Super"}, "Tab"},
|
||||
{"Mod+Shift+Ctrl+H", []string{"Mod", "Shift", "Ctrl"}, "H"},
|
||||
}
|
||||
|
||||
parser := NewNiriParser("")
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.combo, func(t *testing.T) {
|
||||
mods, key := parser.parseKeyCombo(tt.combo)
|
||||
|
||||
if len(mods) != len(tt.expectedMods) {
|
||||
t.Errorf("Mods length = %d, want %d", len(mods), len(tt.expectedMods))
|
||||
} else {
|
||||
for i := range mods {
|
||||
if mods[i] != tt.expectedMods[i] {
|
||||
t.Errorf("Mods[%d] = %q, want %q", i, mods[i], tt.expectedMods[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if key != tt.expectedKey {
|
||||
t.Errorf("Key = %q, want %q", key, tt.expectedKey)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNiriParseBasicBinds(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
configFile := filepath.Join(tmpDir, "config.kdl")
|
||||
|
||||
content := `binds {
|
||||
Mod+Q { close-window; }
|
||||
Mod+F { fullscreen-window; }
|
||||
Mod+T hotkey-overlay-title="Open Terminal" { spawn "kitty"; }
|
||||
}
|
||||
`
|
||||
if err := os.WriteFile(configFile, []byte(content), 0644); err != nil {
|
||||
t.Fatalf("Failed to write test config: %v", err)
|
||||
}
|
||||
|
||||
result, err := ParseNiriKeys(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseNiriKeys failed: %v", err)
|
||||
}
|
||||
|
||||
if len(result.Section.Keybinds) != 3 {
|
||||
t.Errorf("Expected 3 keybinds, got %d", len(result.Section.Keybinds))
|
||||
}
|
||||
|
||||
foundClose := false
|
||||
foundFullscreen := false
|
||||
foundTerminal := false
|
||||
|
||||
for _, kb := range result.Section.Keybinds {
|
||||
switch kb.Action {
|
||||
case "close-window":
|
||||
foundClose = true
|
||||
if kb.Key != "Q" || len(kb.Mods) != 1 || kb.Mods[0] != "Mod" {
|
||||
t.Errorf("close-window keybind mismatch: %+v", kb)
|
||||
}
|
||||
case "fullscreen-window":
|
||||
foundFullscreen = true
|
||||
case "spawn":
|
||||
foundTerminal = true
|
||||
if kb.Description != "Open Terminal" {
|
||||
t.Errorf("spawn description = %q, want %q", kb.Description, "Open Terminal")
|
||||
}
|
||||
if len(kb.Args) != 1 || kb.Args[0] != "kitty" {
|
||||
t.Errorf("spawn args = %v, want [kitty]", kb.Args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !foundClose {
|
||||
t.Error("close-window keybind not found")
|
||||
}
|
||||
if !foundFullscreen {
|
||||
t.Error("fullscreen-window keybind not found")
|
||||
}
|
||||
if !foundTerminal {
|
||||
t.Error("spawn keybind not found")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNiriParseRecentWindows(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
configFile := filepath.Join(tmpDir, "config.kdl")
|
||||
|
||||
content := `recent-windows {
|
||||
binds {
|
||||
Alt+Tab { next-window scope="output"; }
|
||||
Alt+Shift+Tab { previous-window scope="output"; }
|
||||
}
|
||||
}
|
||||
`
|
||||
if err := os.WriteFile(configFile, []byte(content), 0644); err != nil {
|
||||
t.Fatalf("Failed to write test config: %v", err)
|
||||
}
|
||||
|
||||
result, err := ParseNiriKeys(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseNiriKeys failed: %v", err)
|
||||
}
|
||||
|
||||
if len(result.Section.Keybinds) != 2 {
|
||||
t.Errorf("Expected 2 keybinds from recent-windows, got %d", len(result.Section.Keybinds))
|
||||
}
|
||||
|
||||
foundNext := false
|
||||
foundPrev := false
|
||||
|
||||
for _, kb := range result.Section.Keybinds {
|
||||
switch kb.Action {
|
||||
case "next-window":
|
||||
foundNext = true
|
||||
case "previous-window":
|
||||
foundPrev = true
|
||||
}
|
||||
}
|
||||
|
||||
if !foundNext {
|
||||
t.Error("next-window keybind not found")
|
||||
}
|
||||
if !foundPrev {
|
||||
t.Error("previous-window keybind not found")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNiriParseInclude(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
subDir := filepath.Join(tmpDir, "dms")
|
||||
if err := os.MkdirAll(subDir, 0755); err != nil {
|
||||
t.Fatalf("Failed to create subdir: %v", err)
|
||||
}
|
||||
|
||||
mainConfig := filepath.Join(tmpDir, "config.kdl")
|
||||
includeConfig := filepath.Join(subDir, "binds.kdl")
|
||||
|
||||
mainContent := `binds {
|
||||
Mod+Q { close-window; }
|
||||
}
|
||||
include "dms/binds.kdl"
|
||||
`
|
||||
includeContent := `binds {
|
||||
Mod+T hotkey-overlay-title="Terminal" { spawn "kitty"; }
|
||||
}
|
||||
`
|
||||
|
||||
if err := os.WriteFile(mainConfig, []byte(mainContent), 0644); err != nil {
|
||||
t.Fatalf("Failed to write main config: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(includeConfig, []byte(includeContent), 0644); err != nil {
|
||||
t.Fatalf("Failed to write include config: %v", err)
|
||||
}
|
||||
|
||||
result, err := ParseNiriKeys(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseNiriKeys failed: %v", err)
|
||||
}
|
||||
|
||||
if len(result.Section.Keybinds) != 2 {
|
||||
t.Errorf("Expected 2 keybinds (1 main + 1 include), got %d", len(result.Section.Keybinds))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNiriParseIncludeOverride(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
subDir := filepath.Join(tmpDir, "dms")
|
||||
if err := os.MkdirAll(subDir, 0755); err != nil {
|
||||
t.Fatalf("Failed to create subdir: %v", err)
|
||||
}
|
||||
|
||||
mainConfig := filepath.Join(tmpDir, "config.kdl")
|
||||
includeConfig := filepath.Join(subDir, "binds.kdl")
|
||||
|
||||
mainContent := `binds {
|
||||
Mod+T hotkey-overlay-title="Main Terminal" { spawn "alacritty"; }
|
||||
}
|
||||
include "dms/binds.kdl"
|
||||
`
|
||||
includeContent := `binds {
|
||||
Mod+T hotkey-overlay-title="Override Terminal" { spawn "kitty"; }
|
||||
}
|
||||
`
|
||||
|
||||
if err := os.WriteFile(mainConfig, []byte(mainContent), 0644); err != nil {
|
||||
t.Fatalf("Failed to write main config: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(includeConfig, []byte(includeContent), 0644); err != nil {
|
||||
t.Fatalf("Failed to write include config: %v", err)
|
||||
}
|
||||
|
||||
result, err := ParseNiriKeys(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseNiriKeys failed: %v", err)
|
||||
}
|
||||
|
||||
if len(result.Section.Keybinds) != 1 {
|
||||
t.Errorf("Expected 1 keybind (later overrides earlier), got %d", len(result.Section.Keybinds))
|
||||
}
|
||||
|
||||
if len(result.Section.Keybinds) > 0 {
|
||||
kb := result.Section.Keybinds[0]
|
||||
if kb.Description != "Override Terminal" {
|
||||
t.Errorf("Expected description 'Override Terminal' (from include), got %q", kb.Description)
|
||||
}
|
||||
if len(kb.Args) != 1 || kb.Args[0] != "kitty" {
|
||||
t.Errorf("Expected args [kitty] (from include), got %v", kb.Args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNiriParseCircularInclude(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
mainConfig := filepath.Join(tmpDir, "config.kdl")
|
||||
otherConfig := filepath.Join(tmpDir, "other.kdl")
|
||||
|
||||
mainContent := `binds {
|
||||
Mod+Q { close-window; }
|
||||
}
|
||||
include "other.kdl"
|
||||
`
|
||||
otherContent := `binds {
|
||||
Mod+T { spawn "kitty"; }
|
||||
}
|
||||
include "config.kdl"
|
||||
`
|
||||
|
||||
if err := os.WriteFile(mainConfig, []byte(mainContent), 0644); err != nil {
|
||||
t.Fatalf("Failed to write main config: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(otherConfig, []byte(otherContent), 0644); err != nil {
|
||||
t.Fatalf("Failed to write other config: %v", err)
|
||||
}
|
||||
|
||||
result, err := ParseNiriKeys(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseNiriKeys failed (should handle circular includes): %v", err)
|
||||
}
|
||||
|
||||
if len(result.Section.Keybinds) != 2 {
|
||||
t.Errorf("Expected 2 keybinds (circular include handled), got %d", len(result.Section.Keybinds))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNiriParseMissingInclude(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
configFile := filepath.Join(tmpDir, "config.kdl")
|
||||
|
||||
content := `binds {
|
||||
Mod+Q { close-window; }
|
||||
}
|
||||
include "nonexistent/file.kdl"
|
||||
`
|
||||
if err := os.WriteFile(configFile, []byte(content), 0644); err != nil {
|
||||
t.Fatalf("Failed to write test config: %v", err)
|
||||
}
|
||||
|
||||
result, err := ParseNiriKeys(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseNiriKeys failed (should skip missing include): %v", err)
|
||||
}
|
||||
|
||||
if len(result.Section.Keybinds) != 1 {
|
||||
t.Errorf("Expected 1 keybind (missing include skipped), got %d", len(result.Section.Keybinds))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNiriParseNoBinds(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
configFile := filepath.Join(tmpDir, "config.kdl")
|
||||
|
||||
content := `cursor {
|
||||
xcursor-theme "Bibata"
|
||||
xcursor-size 24
|
||||
}
|
||||
|
||||
input {
|
||||
keyboard {
|
||||
numlock
|
||||
}
|
||||
}
|
||||
`
|
||||
if err := os.WriteFile(configFile, []byte(content), 0644); err != nil {
|
||||
t.Fatalf("Failed to write test config: %v", err)
|
||||
}
|
||||
|
||||
result, err := ParseNiriKeys(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseNiriKeys failed: %v", err)
|
||||
}
|
||||
|
||||
if len(result.Section.Keybinds) != 0 {
|
||||
t.Errorf("Expected 0 keybinds, got %d", len(result.Section.Keybinds))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNiriParseErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
}{
|
||||
{
|
||||
name: "nonexistent_directory",
|
||||
path: "/nonexistent/path/that/does/not/exist",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := ParseNiriKeys(tt.path)
|
||||
if err == nil {
|
||||
t.Error("Expected error, got nil")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNiriBindOverrideBehavior(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
configFile := filepath.Join(tmpDir, "config.kdl")
|
||||
|
||||
content := `binds {
|
||||
Mod+T hotkey-overlay-title="First" { spawn "first"; }
|
||||
Mod+Q { close-window; }
|
||||
Mod+T hotkey-overlay-title="Second" { spawn "second"; }
|
||||
Mod+F { fullscreen-window; }
|
||||
Mod+T hotkey-overlay-title="Third" { spawn "third"; }
|
||||
}
|
||||
`
|
||||
if err := os.WriteFile(configFile, []byte(content), 0644); err != nil {
|
||||
t.Fatalf("Failed to write test config: %v", err)
|
||||
}
|
||||
|
||||
result, err := ParseNiriKeys(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseNiriKeys failed: %v", err)
|
||||
}
|
||||
|
||||
if len(result.Section.Keybinds) != 3 {
|
||||
t.Fatalf("Expected 3 unique keybinds, got %d", len(result.Section.Keybinds))
|
||||
}
|
||||
|
||||
var modT *NiriKeyBinding
|
||||
for i := range result.Section.Keybinds {
|
||||
kb := &result.Section.Keybinds[i]
|
||||
if len(kb.Mods) == 1 && kb.Mods[0] == "Mod" && kb.Key == "T" {
|
||||
modT = kb
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if modT == nil {
|
||||
t.Fatal("Mod+T keybind not found")
|
||||
}
|
||||
|
||||
if modT.Description != "Third" {
|
||||
t.Errorf("Mod+T description = %q, want 'Third' (last definition wins)", modT.Description)
|
||||
}
|
||||
|
||||
if len(modT.Args) != 1 || modT.Args[0] != "third" {
|
||||
t.Errorf("Mod+T args = %v, want [third] (last definition wins)", modT.Args)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNiriBindOverrideWithIncludes(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
subDir := filepath.Join(tmpDir, "custom")
|
||||
if err := os.MkdirAll(subDir, 0755); err != nil {
|
||||
t.Fatalf("Failed to create subdir: %v", err)
|
||||
}
|
||||
|
||||
mainConfig := filepath.Join(tmpDir, "config.kdl")
|
||||
includeConfig := filepath.Join(subDir, "overrides.kdl")
|
||||
|
||||
mainContent := `binds {
|
||||
Mod+1 { focus-workspace 1; }
|
||||
Mod+2 { focus-workspace 2; }
|
||||
Mod+T hotkey-overlay-title="Default Terminal" { spawn "xterm"; }
|
||||
}
|
||||
include "custom/overrides.kdl"
|
||||
binds {
|
||||
Mod+3 { focus-workspace 3; }
|
||||
}
|
||||
`
|
||||
includeContent := `binds {
|
||||
Mod+T hotkey-overlay-title="Custom Terminal" { spawn "kitty"; }
|
||||
Mod+2 { focus-workspace 22; }
|
||||
}
|
||||
`
|
||||
|
||||
if err := os.WriteFile(mainConfig, []byte(mainContent), 0644); err != nil {
|
||||
t.Fatalf("Failed to write main config: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(includeConfig, []byte(includeContent), 0644); err != nil {
|
||||
t.Fatalf("Failed to write include config: %v", err)
|
||||
}
|
||||
|
||||
result, err := ParseNiriKeys(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseNiriKeys failed: %v", err)
|
||||
}
|
||||
|
||||
if len(result.Section.Keybinds) != 4 {
|
||||
t.Errorf("Expected 4 unique keybinds, got %d", len(result.Section.Keybinds))
|
||||
}
|
||||
|
||||
bindMap := make(map[string]*NiriKeyBinding)
|
||||
for i := range result.Section.Keybinds {
|
||||
kb := &result.Section.Keybinds[i]
|
||||
key := ""
|
||||
for _, m := range kb.Mods {
|
||||
key += m + "+"
|
||||
}
|
||||
key += kb.Key
|
||||
bindMap[key] = kb
|
||||
}
|
||||
|
||||
if kb, ok := bindMap["Mod+T"]; ok {
|
||||
if kb.Description != "Custom Terminal" {
|
||||
t.Errorf("Mod+T should be overridden by include, got description %q", kb.Description)
|
||||
}
|
||||
} else {
|
||||
t.Error("Mod+T not found")
|
||||
}
|
||||
|
||||
if kb, ok := bindMap["Mod+2"]; ok {
|
||||
if len(kb.Args) != 1 || kb.Args[0] != "22" {
|
||||
t.Errorf("Mod+2 should be overridden by include with workspace 22, got args %v", kb.Args)
|
||||
}
|
||||
} else {
|
||||
t.Error("Mod+2 not found")
|
||||
}
|
||||
|
||||
if _, ok := bindMap["Mod+1"]; !ok {
|
||||
t.Error("Mod+1 should exist (not overridden)")
|
||||
}
|
||||
|
||||
if _, ok := bindMap["Mod+3"]; !ok {
|
||||
t.Error("Mod+3 should exist (added after include)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNiriParseMultipleArgs(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
configFile := filepath.Join(tmpDir, "config.kdl")
|
||||
|
||||
content := `binds {
|
||||
Mod+Space hotkey-overlay-title="Application Launcher" {
|
||||
spawn "dms" "ipc" "call" "spotlight" "toggle";
|
||||
}
|
||||
}
|
||||
`
|
||||
if err := os.WriteFile(configFile, []byte(content), 0644); err != nil {
|
||||
t.Fatalf("Failed to write test config: %v", err)
|
||||
}
|
||||
|
||||
result, err := ParseNiriKeys(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseNiriKeys failed: %v", err)
|
||||
}
|
||||
|
||||
if len(result.Section.Keybinds) != 1 {
|
||||
t.Fatalf("Expected 1 keybind, got %d", len(result.Section.Keybinds))
|
||||
}
|
||||
|
||||
kb := result.Section.Keybinds[0]
|
||||
if len(kb.Args) != 5 {
|
||||
t.Errorf("Expected 5 args, got %d: %v", len(kb.Args), kb.Args)
|
||||
}
|
||||
|
||||
expectedArgs := []string{"dms", "ipc", "call", "spotlight", "toggle"}
|
||||
for i, arg := range expectedArgs {
|
||||
if i < len(kb.Args) && kb.Args[i] != arg {
|
||||
t.Errorf("Args[%d] = %q, want %q", i, kb.Args[i], arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
399
core/internal/keybinds/providers/niri_test.go
Normal file
399
core/internal/keybinds/providers/niri_test.go
Normal file
@@ -0,0 +1,399 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNiriProviderName(t *testing.T) {
|
||||
provider := NewNiriProvider("")
|
||||
if provider.Name() != "niri" {
|
||||
t.Errorf("Name() = %q, want %q", provider.Name(), "niri")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNiriProviderGetCheatSheet(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
configFile := filepath.Join(tmpDir, "config.kdl")
|
||||
|
||||
content := `binds {
|
||||
Mod+Q { close-window; }
|
||||
Mod+F { fullscreen-window; }
|
||||
Mod+T hotkey-overlay-title="Open Terminal" { spawn "kitty"; }
|
||||
Mod+1 { focus-workspace 1; }
|
||||
Mod+Shift+1 { move-column-to-workspace 1; }
|
||||
Print { screenshot; }
|
||||
Mod+Shift+E { quit; }
|
||||
}
|
||||
`
|
||||
if err := os.WriteFile(configFile, []byte(content), 0644); err != nil {
|
||||
t.Fatalf("Failed to write test config: %v", err)
|
||||
}
|
||||
|
||||
provider := NewNiriProvider(tmpDir)
|
||||
cheatSheet, err := provider.GetCheatSheet()
|
||||
if err != nil {
|
||||
t.Fatalf("GetCheatSheet failed: %v", err)
|
||||
}
|
||||
|
||||
if cheatSheet.Title != "Niri Keybinds" {
|
||||
t.Errorf("Title = %q, want %q", cheatSheet.Title, "Niri Keybinds")
|
||||
}
|
||||
|
||||
if cheatSheet.Provider != "niri" {
|
||||
t.Errorf("Provider = %q, want %q", cheatSheet.Provider, "niri")
|
||||
}
|
||||
|
||||
windowBinds := cheatSheet.Binds["Window"]
|
||||
if len(windowBinds) < 2 {
|
||||
t.Errorf("Expected at least 2 Window binds, got %d", len(windowBinds))
|
||||
}
|
||||
|
||||
execBinds := cheatSheet.Binds["Execute"]
|
||||
if len(execBinds) < 1 {
|
||||
t.Errorf("Expected at least 1 Execute bind, got %d", len(execBinds))
|
||||
}
|
||||
|
||||
workspaceBinds := cheatSheet.Binds["Workspace"]
|
||||
if len(workspaceBinds) < 2 {
|
||||
t.Errorf("Expected at least 2 Workspace binds, got %d", len(workspaceBinds))
|
||||
}
|
||||
|
||||
screenshotBinds := cheatSheet.Binds["Screenshot"]
|
||||
if len(screenshotBinds) < 1 {
|
||||
t.Errorf("Expected at least 1 Screenshot bind, got %d", len(screenshotBinds))
|
||||
}
|
||||
|
||||
systemBinds := cheatSheet.Binds["System"]
|
||||
if len(systemBinds) < 1 {
|
||||
t.Errorf("Expected at least 1 System bind, got %d", len(systemBinds))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNiriCategorizeByAction(t *testing.T) {
|
||||
provider := NewNiriProvider("")
|
||||
|
||||
tests := []struct {
|
||||
action string
|
||||
expected string
|
||||
}{
|
||||
{"focus-workspace", "Workspace"},
|
||||
{"focus-workspace-up", "Workspace"},
|
||||
{"move-column-to-workspace", "Workspace"},
|
||||
{"focus-monitor-left", "Monitor"},
|
||||
{"move-column-to-monitor-right", "Monitor"},
|
||||
{"close-window", "Window"},
|
||||
{"fullscreen-window", "Window"},
|
||||
{"maximize-column", "Window"},
|
||||
{"toggle-window-floating", "Window"},
|
||||
{"focus-column-left", "Window"},
|
||||
{"move-column-right", "Window"},
|
||||
{"spawn", "Execute"},
|
||||
{"quit", "System"},
|
||||
{"power-off-monitors", "System"},
|
||||
{"screenshot", "Screenshot"},
|
||||
{"screenshot-window", "Screenshot"},
|
||||
{"toggle-overview", "Overview"},
|
||||
{"show-hotkey-overlay", "Overview"},
|
||||
{"next-window", "Alt-Tab"},
|
||||
{"previous-window", "Alt-Tab"},
|
||||
{"unknown-action", "Other"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.action, func(t *testing.T) {
|
||||
result := provider.categorizeByAction(tt.action)
|
||||
if result != tt.expected {
|
||||
t.Errorf("categorizeByAction(%q) = %q, want %q", tt.action, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNiriFormatRawAction(t *testing.T) {
|
||||
provider := NewNiriProvider("")
|
||||
|
||||
tests := []struct {
|
||||
action string
|
||||
args []string
|
||||
expected string
|
||||
}{
|
||||
{"spawn", []string{"kitty"}, "spawn kitty"},
|
||||
{"spawn", []string{"dms", "ipc", "call"}, "spawn dms ipc call"},
|
||||
{"close-window", nil, "close-window"},
|
||||
{"fullscreen-window", nil, "fullscreen-window"},
|
||||
{"focus-workspace", []string{"1"}, "focus-workspace 1"},
|
||||
{"move-column-to-workspace", []string{"5"}, "move-column-to-workspace 5"},
|
||||
{"set-column-width", []string{"+10%"}, "set-column-width +10%"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.action, func(t *testing.T) {
|
||||
result := provider.formatRawAction(tt.action, tt.args)
|
||||
if result != tt.expected {
|
||||
t.Errorf("formatRawAction(%q, %v) = %q, want %q", tt.action, tt.args, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNiriFormatKey(t *testing.T) {
|
||||
provider := NewNiriProvider("")
|
||||
|
||||
tests := []struct {
|
||||
mods []string
|
||||
key string
|
||||
expected string
|
||||
}{
|
||||
{[]string{"Mod"}, "Q", "Mod+Q"},
|
||||
{[]string{"Mod", "Shift"}, "F", "Mod+Shift+F"},
|
||||
{[]string{"Ctrl", "Alt"}, "Delete", "Ctrl+Alt+Delete"},
|
||||
{nil, "Print", "Print"},
|
||||
{[]string{}, "XF86AudioMute", "XF86AudioMute"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.expected, func(t *testing.T) {
|
||||
kb := &NiriKeyBinding{
|
||||
Mods: tt.mods,
|
||||
Key: tt.key,
|
||||
}
|
||||
result := provider.formatKey(kb)
|
||||
if result != tt.expected {
|
||||
t.Errorf("formatKey(%v) = %q, want %q", kb, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNiriDefaultConfigDir(t *testing.T) {
|
||||
originalXDG := os.Getenv("XDG_CONFIG_HOME")
|
||||
defer os.Setenv("XDG_CONFIG_HOME", originalXDG)
|
||||
|
||||
os.Setenv("XDG_CONFIG_HOME", "/custom/config")
|
||||
dir := defaultNiriConfigDir()
|
||||
if dir != "/custom/config/niri" {
|
||||
t.Errorf("With XDG_CONFIG_HOME set, got %q, want %q", dir, "/custom/config/niri")
|
||||
}
|
||||
|
||||
os.Unsetenv("XDG_CONFIG_HOME")
|
||||
dir = defaultNiriConfigDir()
|
||||
home, _ := os.UserHomeDir()
|
||||
expected := filepath.Join(home, ".config", "niri")
|
||||
if dir != expected {
|
||||
t.Errorf("Without XDG_CONFIG_HOME, got %q, want %q", dir, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNiriGenerateBindsContent(t *testing.T) {
|
||||
provider := NewNiriProvider("")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
binds map[string]*overrideBind
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "empty binds",
|
||||
binds: map[string]*overrideBind{},
|
||||
expected: "binds {}\n",
|
||||
},
|
||||
{
|
||||
name: "simple spawn bind",
|
||||
binds: map[string]*overrideBind{
|
||||
"Mod+T": {
|
||||
Key: "Mod+T",
|
||||
Action: "spawn kitty",
|
||||
Description: "Open Terminal",
|
||||
},
|
||||
},
|
||||
expected: `binds {
|
||||
Mod+T hotkey-overlay-title="Open Terminal" { spawn "kitty"; }
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "spawn with multiple args",
|
||||
binds: map[string]*overrideBind{
|
||||
"Mod+Space": {
|
||||
Key: "Mod+Space",
|
||||
Action: `spawn "dms" "ipc" "call" "spotlight" "toggle"`,
|
||||
Description: "Application Launcher",
|
||||
},
|
||||
},
|
||||
expected: `binds {
|
||||
Mod+Space hotkey-overlay-title="Application Launcher" { spawn "dms" "ipc" "call" "spotlight" "toggle"; }
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "bind with allow-when-locked",
|
||||
binds: map[string]*overrideBind{
|
||||
"XF86AudioMute": {
|
||||
Key: "XF86AudioMute",
|
||||
Action: `spawn "dms" "ipc" "call" "audio" "mute"`,
|
||||
Options: map[string]any{"allow-when-locked": true},
|
||||
},
|
||||
},
|
||||
expected: `binds {
|
||||
XF86AudioMute allow-when-locked=true { spawn "dms" "ipc" "call" "audio" "mute"; }
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "simple action without args",
|
||||
binds: map[string]*overrideBind{
|
||||
"Mod+Q": {
|
||||
Key: "Mod+Q",
|
||||
Action: "close-window",
|
||||
Description: "Close Window",
|
||||
},
|
||||
},
|
||||
expected: `binds {
|
||||
Mod+Q hotkey-overlay-title="Close Window" { close-window; }
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "recent-windows action",
|
||||
binds: map[string]*overrideBind{
|
||||
"Alt+Tab": {
|
||||
Key: "Alt+Tab",
|
||||
Action: "next-window",
|
||||
},
|
||||
},
|
||||
expected: `binds {
|
||||
}
|
||||
|
||||
recent-windows {
|
||||
binds {
|
||||
Alt+Tab { next-window; }
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := provider.generateBindsContent(tt.binds)
|
||||
if result != tt.expected {
|
||||
t.Errorf("generateBindsContent() =\n%q\nwant:\n%q", result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNiriGenerateBindsContentRoundTrip(t *testing.T) {
|
||||
provider := NewNiriProvider("")
|
||||
|
||||
binds := map[string]*overrideBind{
|
||||
"Mod+Space": {
|
||||
Key: "Mod+Space",
|
||||
Action: `spawn "dms" "ipc" "call" "spotlight" "toggle"`,
|
||||
Description: "Application Launcher",
|
||||
},
|
||||
"XF86AudioMute": {
|
||||
Key: "XF86AudioMute",
|
||||
Action: `spawn "dms" "ipc" "call" "audio" "mute"`,
|
||||
Options: map[string]any{"allow-when-locked": true},
|
||||
},
|
||||
"Mod+Q": {
|
||||
Key: "Mod+Q",
|
||||
Action: "close-window",
|
||||
Description: "Close Window",
|
||||
},
|
||||
}
|
||||
|
||||
content := provider.generateBindsContent(binds)
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
configFile := filepath.Join(tmpDir, "config.kdl")
|
||||
if err := os.WriteFile(configFile, []byte(content), 0644); err != nil {
|
||||
t.Fatalf("Failed to write temp file: %v", err)
|
||||
}
|
||||
|
||||
result, err := ParseNiriKeys(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse generated content: %v\nContent was:\n%s", err, content)
|
||||
}
|
||||
|
||||
if len(result.Section.Keybinds) != 3 {
|
||||
t.Errorf("Expected 3 keybinds after round-trip, got %d", len(result.Section.Keybinds))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNiriProviderWithRealWorldConfig(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
configFile := filepath.Join(tmpDir, "config.kdl")
|
||||
|
||||
content := `binds {
|
||||
Mod+Shift+Ctrl+D { debug-toggle-damage; }
|
||||
Super+D { spawn "niri" "msg" "action" "toggle-overview"; }
|
||||
Super+Tab repeat=false { toggle-overview; }
|
||||
Mod+Shift+Slash { show-hotkey-overlay; }
|
||||
|
||||
Mod+T hotkey-overlay-title="Open Terminal" { spawn "kitty"; }
|
||||
Mod+Space hotkey-overlay-title="Application Launcher" {
|
||||
spawn "dms" "ipc" "call" "spotlight" "toggle";
|
||||
}
|
||||
|
||||
XF86AudioRaiseVolume allow-when-locked=true {
|
||||
spawn "dms" "ipc" "call" "audio" "increment" "3";
|
||||
}
|
||||
XF86AudioLowerVolume allow-when-locked=true {
|
||||
spawn "dms" "ipc" "call" "audio" "decrement" "3";
|
||||
}
|
||||
|
||||
Mod+Q repeat=false { close-window; }
|
||||
Mod+F { maximize-column; }
|
||||
Mod+Shift+F { fullscreen-window; }
|
||||
|
||||
Mod+Left { focus-column-left; }
|
||||
Mod+Down { focus-window-down; }
|
||||
Mod+Up { focus-window-up; }
|
||||
Mod+Right { focus-column-right; }
|
||||
|
||||
Mod+1 { focus-workspace 1; }
|
||||
Mod+2 { focus-workspace 2; }
|
||||
Mod+Shift+1 { move-column-to-workspace 1; }
|
||||
Mod+Shift+2 { move-column-to-workspace 2; }
|
||||
|
||||
Print { screenshot; }
|
||||
Ctrl+Print { screenshot-screen; }
|
||||
Alt+Print { screenshot-window; }
|
||||
|
||||
Mod+Shift+E { quit; }
|
||||
}
|
||||
|
||||
recent-windows {
|
||||
binds {
|
||||
Alt+Tab { next-window scope="output"; }
|
||||
Alt+Shift+Tab { previous-window scope="output"; }
|
||||
}
|
||||
}
|
||||
`
|
||||
if err := os.WriteFile(configFile, []byte(content), 0644); err != nil {
|
||||
t.Fatalf("Failed to write test config: %v", err)
|
||||
}
|
||||
|
||||
provider := NewNiriProvider(tmpDir)
|
||||
cheatSheet, err := provider.GetCheatSheet()
|
||||
if err != nil {
|
||||
t.Fatalf("GetCheatSheet failed: %v", err)
|
||||
}
|
||||
|
||||
totalBinds := 0
|
||||
for _, binds := range cheatSheet.Binds {
|
||||
totalBinds += len(binds)
|
||||
}
|
||||
|
||||
if totalBinds < 20 {
|
||||
t.Errorf("Expected at least 20 keybinds, got %d", totalBinds)
|
||||
}
|
||||
|
||||
if len(cheatSheet.Binds["Alt-Tab"]) < 2 {
|
||||
t.Errorf("Expected at least 2 Alt-Tab binds, got %d", len(cheatSheet.Binds["Alt-Tab"]))
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/sway"
|
||||
)
|
||||
|
||||
type SwayProvider struct {
|
||||
@@ -26,7 +25,7 @@ func (s *SwayProvider) Name() string {
|
||||
}
|
||||
|
||||
func (s *SwayProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
|
||||
section, err := sway.ParseKeys(s.configPath)
|
||||
section, err := ParseSwayKeys(s.configPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse sway config: %w", err)
|
||||
}
|
||||
@@ -41,7 +40,7 @@ func (s *SwayProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
|
||||
}, 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
|
||||
if 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)
|
||||
desc := kb.Comment
|
||||
|
||||
@@ -100,11 +99,12 @@ func (s *SwayProvider) convertKeybind(kb *sway.KeyBinding, subcategory string) k
|
||||
return keybinds.Keybind{
|
||||
Key: key,
|
||||
Description: desc,
|
||||
Action: kb.Command,
|
||||
Subcategory: subcategory,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SwayProvider) formatKey(kb *sway.KeyBinding) string {
|
||||
func (s *SwayProvider) formatKey(kb *SwayKeyBinding) string {
|
||||
parts := make([]string, 0, len(kb.Mods)+1)
|
||||
parts = append(parts, kb.Mods...)
|
||||
parts = append(parts, kb.Key)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package sway
|
||||
package providers
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -8,40 +8,40 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
TitleRegex = "#+!"
|
||||
HideComment = "[hidden]"
|
||||
SwayTitleRegex = "#+!"
|
||||
SwayHideComment = "[hidden]"
|
||||
)
|
||||
|
||||
var ModSeparators = []rune{'+', ' '}
|
||||
var SwayModSeparators = []rune{'+', ' '}
|
||||
|
||||
type KeyBinding struct {
|
||||
type SwayKeyBinding struct {
|
||||
Mods []string `json:"mods"`
|
||||
Key string `json:"key"`
|
||||
Command string `json:"command"`
|
||||
Comment string `json:"comment"`
|
||||
}
|
||||
|
||||
type Section struct {
|
||||
Children []Section `json:"children"`
|
||||
Keybinds []KeyBinding `json:"keybinds"`
|
||||
Name string `json:"name"`
|
||||
type SwaySection struct {
|
||||
Children []SwaySection `json:"children"`
|
||||
Keybinds []SwayKeyBinding `json:"keybinds"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type Parser struct {
|
||||
type SwayParser struct {
|
||||
contentLines []string
|
||||
readingLine int
|
||||
variables map[string]string
|
||||
}
|
||||
|
||||
func NewParser() *Parser {
|
||||
return &Parser{
|
||||
func NewSwayParser() *SwayParser {
|
||||
return &SwayParser{
|
||||
contentLines: []string{},
|
||||
readingLine: 0,
|
||||
variables: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) ReadContent(path string) error {
|
||||
func (p *SwayParser) ReadContent(path string) error {
|
||||
expandedPath := os.ExpandEnv(path)
|
||||
expandedPath = filepath.Clean(expandedPath)
|
||||
if strings.HasPrefix(expandedPath, "~") {
|
||||
@@ -88,7 +88,7 @@ func (p *Parser) ReadContent(path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Parser) parseVariables() {
|
||||
func (p *SwayParser) parseVariables() {
|
||||
setRegex := regexp.MustCompile(`^\s*set\s+\$(\w+)\s+(.+)$`)
|
||||
for _, line := range p.contentLines {
|
||||
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
|
||||
for varName, varValue := range p.variables {
|
||||
result = strings.ReplaceAll(result, "$"+varName, varValue)
|
||||
@@ -108,7 +108,7 @@ func (p *Parser) expandVariables(text string) string {
|
||||
return result
|
||||
}
|
||||
|
||||
func autogenerateComment(command string) string {
|
||||
func swayAutogenerateComment(command string) string {
|
||||
command = strings.TrimSpace(command)
|
||||
|
||||
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) {
|
||||
return nil
|
||||
}
|
||||
@@ -223,7 +223,7 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
|
||||
comment = strings.TrimSpace(parts[1])
|
||||
}
|
||||
|
||||
if strings.HasPrefix(comment, HideComment) {
|
||||
if strings.HasPrefix(comment, SwayHideComment) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -249,11 +249,11 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
|
||||
var modList []string
|
||||
var key string
|
||||
|
||||
modstring := keyCombo + string(ModSeparators[0])
|
||||
modstring := keyCombo + string(SwayModSeparators[0])
|
||||
pos := 0
|
||||
for index, char := range modstring {
|
||||
isModSep := false
|
||||
for _, sep := range ModSeparators {
|
||||
for _, sep := range SwayModSeparators {
|
||||
if char == sep {
|
||||
isModSep = true
|
||||
break
|
||||
@@ -262,7 +262,7 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
|
||||
if isModSep {
|
||||
if index-pos > 0 {
|
||||
part := modstring[pos:index]
|
||||
if isMod(part) {
|
||||
if swayIsMod(part) {
|
||||
modList = append(modList, part)
|
||||
} else {
|
||||
key = part
|
||||
@@ -273,12 +273,12 @@ func (p *Parser) getKeybindAtLine(lineNumber int) *KeyBinding {
|
||||
}
|
||||
|
||||
if comment == "" {
|
||||
comment = autogenerateComment(command)
|
||||
comment = swayAutogenerateComment(command)
|
||||
}
|
||||
|
||||
_ = flags
|
||||
|
||||
return &KeyBinding{
|
||||
return &SwayKeyBinding{
|
||||
Mods: modList,
|
||||
Key: key,
|
||||
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)
|
||||
if s == "mod1" || s == "mod2" || s == "mod3" || s == "mod4" || s == "mod5" ||
|
||||
s == "shift" || s == "control" || s == "ctrl" || s == "alt" || s == "super" ||
|
||||
@@ -307,8 +307,8 @@ func isMod(s string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *Parser) getBindsRecursive(currentContent *Section, scope int) *Section {
|
||||
titleRegex := regexp.MustCompile(TitleRegex)
|
||||
func (p *SwayParser) getBindsRecursive(currentContent *SwaySection, scope int) *SwaySection {
|
||||
titleRegex := regexp.MustCompile(SwayTitleRegex)
|
||||
|
||||
for p.readingLine < len(p.contentLines) {
|
||||
line := p.contentLines[p.readingLine]
|
||||
@@ -325,9 +325,9 @@ func (p *Parser) getBindsRecursive(currentContent *Section, scope int) *Section
|
||||
sectionName := strings.TrimSpace(line[headingScope+1:])
|
||||
p.readingLine++
|
||||
|
||||
childSection := &Section{
|
||||
Children: []Section{},
|
||||
Keybinds: []KeyBinding{},
|
||||
childSection := &SwaySection{
|
||||
Children: []SwaySection{},
|
||||
Keybinds: []SwayKeyBinding{},
|
||||
Name: sectionName,
|
||||
}
|
||||
result := p.getBindsRecursive(childSection, headingScope)
|
||||
@@ -348,18 +348,18 @@ func (p *Parser) getBindsRecursive(currentContent *Section, scope int) *Section
|
||||
return currentContent
|
||||
}
|
||||
|
||||
func (p *Parser) ParseKeys() *Section {
|
||||
func (p *SwayParser) ParseKeys() *SwaySection {
|
||||
p.readingLine = 0
|
||||
rootSection := &Section{
|
||||
Children: []Section{},
|
||||
Keybinds: []KeyBinding{},
|
||||
rootSection := &SwaySection{
|
||||
Children: []SwaySection{},
|
||||
Keybinds: []SwayKeyBinding{},
|
||||
Name: "",
|
||||
}
|
||||
return p.getBindsRecursive(rootSection, 0)
|
||||
}
|
||||
|
||||
func ParseKeys(path string) (*Section, error) {
|
||||
parser := NewParser()
|
||||
func ParseSwayKeys(path string) (*SwaySection, error) {
|
||||
parser := NewSwayParser()
|
||||
if err := parser.ReadContent(path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package sway
|
||||
package providers
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAutogenerateComment(t *testing.T) {
|
||||
func TestSwayAutogenerateComment(t *testing.T) {
|
||||
tests := []struct {
|
||||
command string
|
||||
expected string
|
||||
@@ -46,25 +46,25 @@ func TestAutogenerateComment(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.command, func(t *testing.T) {
|
||||
result := autogenerateComment(tt.command)
|
||||
result := swayAutogenerateComment(tt.command)
|
||||
if result != tt.expected {
|
||||
t.Errorf("autogenerateComment(%q) = %q, want %q",
|
||||
t.Errorf("swayAutogenerateComment(%q) = %q, want %q",
|
||||
tt.command, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetKeybindAtLine(t *testing.T) {
|
||||
func TestSwayGetKeybindAtLine(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
expected *KeyBinding
|
||||
expected *SwayKeyBinding
|
||||
}{
|
||||
{
|
||||
name: "basic_keybind",
|
||||
line: "bindsym Mod4+q kill",
|
||||
expected: &KeyBinding{
|
||||
expected: &SwayKeyBinding{
|
||||
Mods: []string{"Mod4"},
|
||||
Key: "q",
|
||||
Command: "kill",
|
||||
@@ -74,7 +74,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
||||
{
|
||||
name: "keybind_with_exec",
|
||||
line: "bindsym Mod4+t exec kitty",
|
||||
expected: &KeyBinding{
|
||||
expected: &SwayKeyBinding{
|
||||
Mods: []string{"Mod4"},
|
||||
Key: "t",
|
||||
Command: "exec kitty",
|
||||
@@ -84,7 +84,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
||||
{
|
||||
name: "keybind_with_comment",
|
||||
line: "bindsym Mod4+Space exec dms ipc call spotlight toggle # Open launcher",
|
||||
expected: &KeyBinding{
|
||||
expected: &SwayKeyBinding{
|
||||
Mods: []string{"Mod4"},
|
||||
Key: "Space",
|
||||
Command: "exec dms ipc call spotlight toggle",
|
||||
@@ -99,7 +99,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
||||
{
|
||||
name: "keybind_multiple_mods",
|
||||
line: "bindsym Mod4+Shift+e exit",
|
||||
expected: &KeyBinding{
|
||||
expected: &SwayKeyBinding{
|
||||
Mods: []string{"Mod4", "Shift"},
|
||||
Key: "e",
|
||||
Command: "exit",
|
||||
@@ -109,7 +109,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
||||
{
|
||||
name: "keybind_no_mods",
|
||||
line: "bindsym Print exec grim screenshot.png",
|
||||
expected: &KeyBinding{
|
||||
expected: &SwayKeyBinding{
|
||||
Mods: []string{},
|
||||
Key: "Print",
|
||||
Command: "exec grim screenshot.png",
|
||||
@@ -119,7 +119,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
||||
{
|
||||
name: "keybind_with_flags",
|
||||
line: "bindsym --release Mod4+x exec notify-send released",
|
||||
expected: &KeyBinding{
|
||||
expected: &SwayKeyBinding{
|
||||
Mods: []string{"Mod4"},
|
||||
Key: "x",
|
||||
Command: "exec notify-send released",
|
||||
@@ -129,7 +129,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
||||
{
|
||||
name: "keybind_focus_direction",
|
||||
line: "bindsym Mod4+Left focus left",
|
||||
expected: &KeyBinding{
|
||||
expected: &SwayKeyBinding{
|
||||
Mods: []string{"Mod4"},
|
||||
Key: "Left",
|
||||
Command: "focus left",
|
||||
@@ -139,7 +139,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
||||
{
|
||||
name: "keybind_workspace",
|
||||
line: "bindsym Mod4+1 workspace number 1",
|
||||
expected: &KeyBinding{
|
||||
expected: &SwayKeyBinding{
|
||||
Mods: []string{"Mod4"},
|
||||
Key: "1",
|
||||
Command: "workspace number 1",
|
||||
@@ -150,7 +150,7 @@ func TestGetKeybindAtLine(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
parser := NewParser()
|
||||
parser := NewSwayParser()
|
||||
parser.contentLines = []string{tt.line}
|
||||
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()
|
||||
configFile := filepath.Join(tmpDir, "config")
|
||||
|
||||
@@ -204,9 +204,9 @@ bindsym $mod+d exec $menu
|
||||
t.Fatalf("Failed to write test config: %v", err)
|
||||
}
|
||||
|
||||
section, err := ParseKeys(configFile)
|
||||
section, err := ParseSwayKeys(configFile)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseKeys failed: %v", err)
|
||||
t.Fatalf("ParseSwayKeys failed: %v", err)
|
||||
}
|
||||
|
||||
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()
|
||||
configFile := filepath.Join(tmpDir, "config")
|
||||
|
||||
@@ -251,9 +251,9 @@ bindsym $mod+t exec kitty # Terminal
|
||||
t.Fatalf("Failed to write test config: %v", err)
|
||||
}
|
||||
|
||||
section, err := ParseKeys(tmpDir)
|
||||
section, err := ParseSwayKeys(tmpDir)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseKeys failed: %v", err)
|
||||
t.Fatalf("ParseSwayKeys failed: %v", err)
|
||||
}
|
||||
|
||||
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 {
|
||||
name string
|
||||
path string
|
||||
@@ -313,7 +313,7 @@ func TestReadContentErrors(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := ParseKeys(tt.path)
|
||||
_, err := ParseSwayKeys(tt.path)
|
||||
if err == 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()
|
||||
if err != nil {
|
||||
t.Skip("Cannot get home directory")
|
||||
@@ -343,7 +343,7 @@ func TestReadContentWithTildeExpansion(t *testing.T) {
|
||||
t.Skip("Cannot create relative path")
|
||||
}
|
||||
|
||||
parser := NewParser()
|
||||
parser := NewSwayParser()
|
||||
tildePathMatch := "~/" + relPath
|
||||
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()
|
||||
configFile := filepath.Join(tmpDir, "config")
|
||||
|
||||
@@ -369,9 +369,9 @@ bindsym Mod4+t exec kitty
|
||||
t.Fatalf("Failed to write test config: %v", err)
|
||||
}
|
||||
|
||||
section, err := ParseKeys(configFile)
|
||||
section, err := ParseSwayKeys(configFile)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseKeys failed: %v", err)
|
||||
t.Fatalf("ParseSwayKeys failed: %v", err)
|
||||
}
|
||||
|
||||
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()
|
||||
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)
|
||||
}
|
||||
|
||||
section, err := ParseKeys(configFile)
|
||||
section, err := ParseSwayKeys(configFile)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseKeys failed: %v", err)
|
||||
t.Fatalf("ParseSwayKeys failed: %v", err)
|
||||
}
|
||||
|
||||
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 {
|
||||
input string
|
||||
expected bool
|
||||
@@ -462,9 +462,9 @@ func TestIsMod(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.input, func(t *testing.T) {
|
||||
result := isMod(tt.input)
|
||||
result := swayIsMod(tt.input)
|
||||
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"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/sway"
|
||||
)
|
||||
|
||||
func TestSwayProviderName(t *testing.T) {
|
||||
@@ -76,12 +74,12 @@ func TestSwayCategorizeByCommand(t *testing.T) {
|
||||
func TestSwayFormatKey(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
keybind *sway.KeyBinding
|
||||
keybind *SwayKeyBinding
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "single_mod",
|
||||
keybind: &sway.KeyBinding{
|
||||
keybind: &SwayKeyBinding{
|
||||
Mods: []string{"Mod4"},
|
||||
Key: "q",
|
||||
},
|
||||
@@ -89,7 +87,7 @@ func TestSwayFormatKey(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "multiple_mods",
|
||||
keybind: &sway.KeyBinding{
|
||||
keybind: &SwayKeyBinding{
|
||||
Mods: []string{"Mod4", "Shift"},
|
||||
Key: "e",
|
||||
},
|
||||
@@ -97,7 +95,7 @@ func TestSwayFormatKey(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "no_mods",
|
||||
keybind: &sway.KeyBinding{
|
||||
keybind: &SwayKeyBinding{
|
||||
Mods: []string{},
|
||||
Key: "Print",
|
||||
},
|
||||
@@ -119,13 +117,13 @@ func TestSwayFormatKey(t *testing.T) {
|
||||
func TestSwayConvertKeybind(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
keybind *sway.KeyBinding
|
||||
keybind *SwayKeyBinding
|
||||
wantKey string
|
||||
wantDesc string
|
||||
}{
|
||||
{
|
||||
name: "with_comment",
|
||||
keybind: &sway.KeyBinding{
|
||||
keybind: &SwayKeyBinding{
|
||||
Mods: []string{"Mod4"},
|
||||
Key: "t",
|
||||
Command: "exec kitty",
|
||||
@@ -136,7 +134,7 @@ func TestSwayConvertKeybind(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "without_comment",
|
||||
keybind: &sway.KeyBinding{
|
||||
keybind: &SwayKeyBinding{
|
||||
Mods: []string{"Mod4"},
|
||||
Key: "r",
|
||||
Command: "reload",
|
||||
|
||||
@@ -1,18 +1,41 @@
|
||||
package keybinds
|
||||
|
||||
type Keybind struct {
|
||||
Key string `json:"key"`
|
||||
Description string `json:"desc"`
|
||||
Subcategory string `json:"subcat,omitempty"`
|
||||
Key string `json:"key"`
|
||||
Description string `json:"desc"`
|
||||
Action string `json:"action,omitempty"`
|
||||
Subcategory string `json:"subcat,omitempty"`
|
||||
Source string `json:"source,omitempty"`
|
||||
Conflict *Keybind `json:"conflict,omitempty"`
|
||||
}
|
||||
|
||||
type DMSBindsStatus struct {
|
||||
Exists bool `json:"exists"`
|
||||
Included bool `json:"included"`
|
||||
IncludePosition int `json:"includePosition"`
|
||||
TotalIncludes int `json:"totalIncludes"`
|
||||
BindsAfterDMS int `json:"bindsAfterDms"`
|
||||
Effective bool `json:"effective"`
|
||||
OverriddenBy int `json:"overriddenBy"`
|
||||
StatusMessage string `json:"statusMessage"`
|
||||
}
|
||||
|
||||
type CheatSheet struct {
|
||||
Title string `json:"title"`
|
||||
Provider string `json:"provider"`
|
||||
Binds map[string][]Keybind `json:"binds"`
|
||||
Title string `json:"title"`
|
||||
Provider string `json:"provider"`
|
||||
Binds map[string][]Keybind `json:"binds"`
|
||||
DMSBindsIncluded bool `json:"dmsBindsIncluded"`
|
||||
DMSStatus *DMSBindsStatus `json:"dmsStatus,omitempty"`
|
||||
}
|
||||
|
||||
type Provider interface {
|
||||
Name() string
|
||||
GetCheatSheet() (*CheatSheet, error)
|
||||
}
|
||||
|
||||
type WritableProvider interface {
|
||||
Provider
|
||||
SetBind(key, action, description string, options map[string]any) error
|
||||
RemoveBind(key string) error
|
||||
GetOverridePath() string
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package logger
|
||||
package log
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@@ -64,7 +64,7 @@ func (l *FileLogger) writeToFile(message string) {
|
||||
redacted := l.redactPassword(message)
|
||||
timestamp := time.Now().Format("15:04:05.000")
|
||||
|
||||
l.writer.WriteString(fmt.Sprintf("[%s] %s\n", timestamp, redacted))
|
||||
fmt.Fprintf(l.writer, "[%s] %s\n", timestamp, redacted)
|
||||
l.writer.Flush()
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ func (l *FileLogger) Close() error {
|
||||
defer l.mu.Unlock()
|
||||
|
||||
footer := fmt.Sprintf("\n=== DankInstall Log End ===\nCompleted: %s\n", time.Now().Format(time.RFC3339))
|
||||
l.writer.WriteString(footer)
|
||||
l.writer.WriteString(footer) //nolint:errcheck
|
||||
l.writer.Flush()
|
||||
|
||||
if err := l.file.Sync(); err != nil {
|
||||
@@ -13,10 +13,10 @@ import (
|
||||
type Logger struct{ *cblog.Logger }
|
||||
|
||||
// Printf routes goose/info-style logs through Infof.
|
||||
func (l *Logger) Printf(format string, v ...interface{}) { l.Infof(format, v...) }
|
||||
func (l *Logger) Printf(format string, v ...any) { l.Infof(format, v...) }
|
||||
|
||||
// Fatalf keeps goose’s contract of exiting the program.
|
||||
func (l *Logger) Fatalf(format string, v ...interface{}) { l.Logger.Fatalf(format, v...) }
|
||||
func (l *Logger) Fatalf(format string, v ...any) { l.Logger.Fatalf(format, v...) }
|
||||
|
||||
var (
|
||||
logger *Logger
|
||||
@@ -104,13 +104,13 @@ func GetLogger() *Logger {
|
||||
|
||||
// * Convenience wrappers
|
||||
|
||||
func Debug(msg interface{}, keyvals ...interface{}) { GetLogger().Logger.Debug(msg, keyvals...) }
|
||||
func Debugf(format string, v ...interface{}) { GetLogger().Logger.Debugf(format, v...) }
|
||||
func Info(msg interface{}, keyvals ...interface{}) { GetLogger().Logger.Info(msg, keyvals...) }
|
||||
func Infof(format string, v ...interface{}) { GetLogger().Logger.Infof(format, v...) }
|
||||
func Warn(msg interface{}, keyvals ...interface{}) { GetLogger().Logger.Warn(msg, keyvals...) }
|
||||
func Warnf(format string, v ...interface{}) { GetLogger().Logger.Warnf(format, v...) }
|
||||
func Error(msg interface{}, keyvals ...interface{}) { GetLogger().Logger.Error(msg, keyvals...) }
|
||||
func Errorf(format string, v ...interface{}) { GetLogger().Logger.Errorf(format, v...) }
|
||||
func Fatal(msg interface{}, keyvals ...interface{}) { GetLogger().Logger.Fatal(msg, keyvals...) }
|
||||
func Fatalf(format string, v ...interface{}) { GetLogger().Logger.Fatalf(format, v...) }
|
||||
func Debug(msg any, keyvals ...any) { GetLogger().Debug(msg, keyvals...) }
|
||||
func Debugf(format string, v ...any) { GetLogger().Debugf(format, v...) }
|
||||
func Info(msg any, keyvals ...any) { GetLogger().Info(msg, keyvals...) }
|
||||
func Infof(format string, v ...any) { GetLogger().Infof(format, v...) }
|
||||
func Warn(msg any, keyvals ...any) { GetLogger().Warn(msg, keyvals...) }
|
||||
func Warnf(format string, v ...any) { GetLogger().Warnf(format, v...) }
|
||||
func Error(msg any, keyvals ...any) { GetLogger().Error(msg, keyvals...) }
|
||||
func Errorf(format string, v ...any) { GetLogger().Errorf(format, v...) }
|
||||
func Fatal(msg any, keyvals ...any) { GetLogger().Fatal(msg, keyvals...) }
|
||||
func Fatalf(format string, v ...any) { GetLogger().Fatalf(format, v...) }
|
||||
|
||||
@@ -93,7 +93,7 @@ type MockDBusConn_Object_Call struct {
|
||||
// Object is a helper method to define mock.On call
|
||||
// - dest string
|
||||
// - path dbus.ObjectPath
|
||||
func (_e *MockDBusConn_Expecter) Object(dest interface{}, path interface{}) *MockDBusConn_Object_Call {
|
||||
func (_e *MockDBusConn_Expecter) Object(dest any, path any) *MockDBusConn_Object_Call {
|
||||
return &MockDBusConn_Object_Call{Call: _e.mock.On("Object", dest, path)}
|
||||
}
|
||||
|
||||
@@ -119,7 +119,8 @@ func (_c *MockDBusConn_Object_Call) RunAndReturn(run func(string, dbus.ObjectPat
|
||||
func NewMockDBusConn(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockDBusConn {
|
||||
},
|
||||
) *MockDBusConn {
|
||||
mock := &MockDBusConn{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
|
||||
@@ -22,6 +22,99 @@ func (_m *MockCUPSClientInterface) EXPECT() *MockCUPSClientInterface_Expecter {
|
||||
return &MockCUPSClientInterface_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// AcceptJobs provides a mock function with given fields: printer
|
||||
func (_m *MockCUPSClientInterface) AcceptJobs(printer string) error {
|
||||
ret := _m.Called(printer)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for AcceptJobs")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(printer)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_AcceptJobs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AcceptJobs'
|
||||
type MockCUPSClientInterface_AcceptJobs_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// AcceptJobs is a helper method to define mock.On call
|
||||
// - printer string
|
||||
func (_e *MockCUPSClientInterface_Expecter) AcceptJobs(printer interface{}) *MockCUPSClientInterface_AcceptJobs_Call {
|
||||
return &MockCUPSClientInterface_AcceptJobs_Call{Call: _e.mock.On("AcceptJobs", printer)}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_AcceptJobs_Call) Run(run func(printer string)) *MockCUPSClientInterface_AcceptJobs_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_AcceptJobs_Call) Return(_a0 error) *MockCUPSClientInterface_AcceptJobs_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_AcceptJobs_Call) RunAndReturn(run func(string) error) *MockCUPSClientInterface_AcceptJobs_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// AddPrinterToClass provides a mock function with given fields: class, printer
|
||||
func (_m *MockCUPSClientInterface) AddPrinterToClass(class string, printer string) error {
|
||||
ret := _m.Called(class, printer)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for AddPrinterToClass")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string) error); ok {
|
||||
r0 = rf(class, printer)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_AddPrinterToClass_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddPrinterToClass'
|
||||
type MockCUPSClientInterface_AddPrinterToClass_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// AddPrinterToClass is a helper method to define mock.On call
|
||||
// - class string
|
||||
// - printer string
|
||||
func (_e *MockCUPSClientInterface_Expecter) AddPrinterToClass(class interface{}, printer interface{}) *MockCUPSClientInterface_AddPrinterToClass_Call {
|
||||
return &MockCUPSClientInterface_AddPrinterToClass_Call{Call: _e.mock.On("AddPrinterToClass", class, printer)}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_AddPrinterToClass_Call) Run(run func(class string, printer string)) *MockCUPSClientInterface_AddPrinterToClass_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_AddPrinterToClass_Call) Return(_a0 error) *MockCUPSClientInterface_AddPrinterToClass_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_AddPrinterToClass_Call) RunAndReturn(run func(string, string) error) *MockCUPSClientInterface_AddPrinterToClass_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// CancelAllJob provides a mock function with given fields: printer, purge
|
||||
func (_m *MockCUPSClientInterface) CancelAllJob(printer string, purge bool) error {
|
||||
ret := _m.Called(printer, purge)
|
||||
@@ -116,6 +209,312 @@ func (_c *MockCUPSClientInterface_CancelJob_Call) RunAndReturn(run func(int, boo
|
||||
return _c
|
||||
}
|
||||
|
||||
// CreatePrinter provides a mock function with given fields: name, deviceURI, ppd, shared, errorPolicy, information, location
|
||||
func (_m *MockCUPSClientInterface) CreatePrinter(name string, deviceURI string, ppd string, shared bool, errorPolicy string, information string, location string) error {
|
||||
ret := _m.Called(name, deviceURI, ppd, shared, errorPolicy, information, location)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CreatePrinter")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, string, bool, string, string, string) error); ok {
|
||||
r0 = rf(name, deviceURI, ppd, shared, errorPolicy, information, location)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_CreatePrinter_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreatePrinter'
|
||||
type MockCUPSClientInterface_CreatePrinter_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// CreatePrinter is a helper method to define mock.On call
|
||||
// - name string
|
||||
// - deviceURI string
|
||||
// - ppd string
|
||||
// - shared bool
|
||||
// - errorPolicy string
|
||||
// - information string
|
||||
// - location string
|
||||
func (_e *MockCUPSClientInterface_Expecter) CreatePrinter(name interface{}, deviceURI interface{}, ppd interface{}, shared interface{}, errorPolicy interface{}, information interface{}, location interface{}) *MockCUPSClientInterface_CreatePrinter_Call {
|
||||
return &MockCUPSClientInterface_CreatePrinter_Call{Call: _e.mock.On("CreatePrinter", name, deviceURI, ppd, shared, errorPolicy, information, location)}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_CreatePrinter_Call) Run(run func(name string, deviceURI string, ppd string, shared bool, errorPolicy string, information string, location string)) *MockCUPSClientInterface_CreatePrinter_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(string), args[2].(string), args[3].(bool), args[4].(string), args[5].(string), args[6].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_CreatePrinter_Call) Return(_a0 error) *MockCUPSClientInterface_CreatePrinter_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_CreatePrinter_Call) RunAndReturn(run func(string, string, string, bool, string, string, string) error) *MockCUPSClientInterface_CreatePrinter_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// DeleteClass provides a mock function with given fields: class
|
||||
func (_m *MockCUPSClientInterface) DeleteClass(class string) error {
|
||||
ret := _m.Called(class)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeleteClass")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(class)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_DeleteClass_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteClass'
|
||||
type MockCUPSClientInterface_DeleteClass_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// DeleteClass is a helper method to define mock.On call
|
||||
// - class string
|
||||
func (_e *MockCUPSClientInterface_Expecter) DeleteClass(class interface{}) *MockCUPSClientInterface_DeleteClass_Call {
|
||||
return &MockCUPSClientInterface_DeleteClass_Call{Call: _e.mock.On("DeleteClass", class)}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_DeleteClass_Call) Run(run func(class string)) *MockCUPSClientInterface_DeleteClass_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_DeleteClass_Call) Return(_a0 error) *MockCUPSClientInterface_DeleteClass_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_DeleteClass_Call) RunAndReturn(run func(string) error) *MockCUPSClientInterface_DeleteClass_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// DeletePrinter provides a mock function with given fields: printer
|
||||
func (_m *MockCUPSClientInterface) DeletePrinter(printer string) error {
|
||||
ret := _m.Called(printer)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeletePrinter")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(printer)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_DeletePrinter_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeletePrinter'
|
||||
type MockCUPSClientInterface_DeletePrinter_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// DeletePrinter is a helper method to define mock.On call
|
||||
// - printer string
|
||||
func (_e *MockCUPSClientInterface_Expecter) DeletePrinter(printer interface{}) *MockCUPSClientInterface_DeletePrinter_Call {
|
||||
return &MockCUPSClientInterface_DeletePrinter_Call{Call: _e.mock.On("DeletePrinter", printer)}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_DeletePrinter_Call) Run(run func(printer string)) *MockCUPSClientInterface_DeletePrinter_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_DeletePrinter_Call) Return(_a0 error) *MockCUPSClientInterface_DeletePrinter_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_DeletePrinter_Call) RunAndReturn(run func(string) error) *MockCUPSClientInterface_DeletePrinter_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// DeletePrinterFromClass provides a mock function with given fields: class, printer
|
||||
func (_m *MockCUPSClientInterface) DeletePrinterFromClass(class string, printer string) error {
|
||||
ret := _m.Called(class, printer)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeletePrinterFromClass")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string) error); ok {
|
||||
r0 = rf(class, printer)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_DeletePrinterFromClass_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeletePrinterFromClass'
|
||||
type MockCUPSClientInterface_DeletePrinterFromClass_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// DeletePrinterFromClass is a helper method to define mock.On call
|
||||
// - class string
|
||||
// - printer string
|
||||
func (_e *MockCUPSClientInterface_Expecter) DeletePrinterFromClass(class interface{}, printer interface{}) *MockCUPSClientInterface_DeletePrinterFromClass_Call {
|
||||
return &MockCUPSClientInterface_DeletePrinterFromClass_Call{Call: _e.mock.On("DeletePrinterFromClass", class, printer)}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_DeletePrinterFromClass_Call) Run(run func(class string, printer string)) *MockCUPSClientInterface_DeletePrinterFromClass_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_DeletePrinterFromClass_Call) Return(_a0 error) *MockCUPSClientInterface_DeletePrinterFromClass_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_DeletePrinterFromClass_Call) RunAndReturn(run func(string, string) error) *MockCUPSClientInterface_DeletePrinterFromClass_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetClasses provides a mock function with given fields: attributes
|
||||
func (_m *MockCUPSClientInterface) GetClasses(attributes []string) (map[string]ipp.Attributes, error) {
|
||||
ret := _m.Called(attributes)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetClasses")
|
||||
}
|
||||
|
||||
var r0 map[string]ipp.Attributes
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func([]string) (map[string]ipp.Attributes, error)); ok {
|
||||
return rf(attributes)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func([]string) map[string]ipp.Attributes); ok {
|
||||
r0 = rf(attributes)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(map[string]ipp.Attributes)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func([]string) error); ok {
|
||||
r1 = rf(attributes)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_GetClasses_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetClasses'
|
||||
type MockCUPSClientInterface_GetClasses_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetClasses is a helper method to define mock.On call
|
||||
// - attributes []string
|
||||
func (_e *MockCUPSClientInterface_Expecter) GetClasses(attributes interface{}) *MockCUPSClientInterface_GetClasses_Call {
|
||||
return &MockCUPSClientInterface_GetClasses_Call{Call: _e.mock.On("GetClasses", attributes)}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_GetClasses_Call) Run(run func(attributes []string)) *MockCUPSClientInterface_GetClasses_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].([]string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_GetClasses_Call) Return(_a0 map[string]ipp.Attributes, _a1 error) *MockCUPSClientInterface_GetClasses_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_GetClasses_Call) RunAndReturn(run func([]string) (map[string]ipp.Attributes, error)) *MockCUPSClientInterface_GetClasses_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetDevices provides a mock function with no fields
|
||||
func (_m *MockCUPSClientInterface) GetDevices() (map[string]ipp.Attributes, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetDevices")
|
||||
}
|
||||
|
||||
var r0 map[string]ipp.Attributes
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func() (map[string]ipp.Attributes, error)); ok {
|
||||
return rf()
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func() map[string]ipp.Attributes); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(map[string]ipp.Attributes)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_GetDevices_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetDevices'
|
||||
type MockCUPSClientInterface_GetDevices_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetDevices is a helper method to define mock.On call
|
||||
func (_e *MockCUPSClientInterface_Expecter) GetDevices() *MockCUPSClientInterface_GetDevices_Call {
|
||||
return &MockCUPSClientInterface_GetDevices_Call{Call: _e.mock.On("GetDevices")}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_GetDevices_Call) Run(run func()) *MockCUPSClientInterface_GetDevices_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_GetDevices_Call) Return(_a0 map[string]ipp.Attributes, _a1 error) *MockCUPSClientInterface_GetDevices_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_GetDevices_Call) RunAndReturn(run func() (map[string]ipp.Attributes, error)) *MockCUPSClientInterface_GetDevices_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetJobs provides a mock function with given fields: printer, class, whichJobs, myJobs, firstJobId, limit, attributes
|
||||
func (_m *MockCUPSClientInterface) GetJobs(printer string, class string, whichJobs string, myJobs bool, firstJobId int, limit int, attributes []string) (map[int]ipp.Attributes, error) {
|
||||
ret := _m.Called(printer, class, whichJobs, myJobs, firstJobId, limit, attributes)
|
||||
@@ -180,6 +579,63 @@ func (_c *MockCUPSClientInterface_GetJobs_Call) RunAndReturn(run func(string, st
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetPPDs provides a mock function with no fields
|
||||
func (_m *MockCUPSClientInterface) GetPPDs() (map[string]ipp.Attributes, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetPPDs")
|
||||
}
|
||||
|
||||
var r0 map[string]ipp.Attributes
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func() (map[string]ipp.Attributes, error)); ok {
|
||||
return rf()
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func() map[string]ipp.Attributes); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(map[string]ipp.Attributes)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_GetPPDs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetPPDs'
|
||||
type MockCUPSClientInterface_GetPPDs_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetPPDs is a helper method to define mock.On call
|
||||
func (_e *MockCUPSClientInterface_Expecter) GetPPDs() *MockCUPSClientInterface_GetPPDs_Call {
|
||||
return &MockCUPSClientInterface_GetPPDs_Call{Call: _e.mock.On("GetPPDs")}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_GetPPDs_Call) Run(run func()) *MockCUPSClientInterface_GetPPDs_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_GetPPDs_Call) Return(_a0 map[string]ipp.Attributes, _a1 error) *MockCUPSClientInterface_GetPPDs_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_GetPPDs_Call) RunAndReturn(run func() (map[string]ipp.Attributes, error)) *MockCUPSClientInterface_GetPPDs_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetPrinters provides a mock function with given fields: attributes
|
||||
func (_m *MockCUPSClientInterface) GetPrinters(attributes []string) (map[string]ipp.Attributes, error) {
|
||||
ret := _m.Called(attributes)
|
||||
@@ -238,6 +694,100 @@ func (_c *MockCUPSClientInterface_GetPrinters_Call) RunAndReturn(run func([]stri
|
||||
return _c
|
||||
}
|
||||
|
||||
// HoldJobUntil provides a mock function with given fields: jobID, holdUntil
|
||||
func (_m *MockCUPSClientInterface) HoldJobUntil(jobID int, holdUntil string) error {
|
||||
ret := _m.Called(jobID, holdUntil)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for HoldJobUntil")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(int, string) error); ok {
|
||||
r0 = rf(jobID, holdUntil)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_HoldJobUntil_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HoldJobUntil'
|
||||
type MockCUPSClientInterface_HoldJobUntil_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// HoldJobUntil is a helper method to define mock.On call
|
||||
// - jobID int
|
||||
// - holdUntil string
|
||||
func (_e *MockCUPSClientInterface_Expecter) HoldJobUntil(jobID interface{}, holdUntil interface{}) *MockCUPSClientInterface_HoldJobUntil_Call {
|
||||
return &MockCUPSClientInterface_HoldJobUntil_Call{Call: _e.mock.On("HoldJobUntil", jobID, holdUntil)}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_HoldJobUntil_Call) Run(run func(jobID int, holdUntil string)) *MockCUPSClientInterface_HoldJobUntil_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(int), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_HoldJobUntil_Call) Return(_a0 error) *MockCUPSClientInterface_HoldJobUntil_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_HoldJobUntil_Call) RunAndReturn(run func(int, string) error) *MockCUPSClientInterface_HoldJobUntil_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// MoveJob provides a mock function with given fields: jobID, destPrinter
|
||||
func (_m *MockCUPSClientInterface) MoveJob(jobID int, destPrinter string) error {
|
||||
ret := _m.Called(jobID, destPrinter)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for MoveJob")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(int, string) error); ok {
|
||||
r0 = rf(jobID, destPrinter)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_MoveJob_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MoveJob'
|
||||
type MockCUPSClientInterface_MoveJob_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// MoveJob is a helper method to define mock.On call
|
||||
// - jobID int
|
||||
// - destPrinter string
|
||||
func (_e *MockCUPSClientInterface_Expecter) MoveJob(jobID interface{}, destPrinter interface{}) *MockCUPSClientInterface_MoveJob_Call {
|
||||
return &MockCUPSClientInterface_MoveJob_Call{Call: _e.mock.On("MoveJob", jobID, destPrinter)}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_MoveJob_Call) Run(run func(jobID int, destPrinter string)) *MockCUPSClientInterface_MoveJob_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(int), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_MoveJob_Call) Return(_a0 error) *MockCUPSClientInterface_MoveJob_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_MoveJob_Call) RunAndReturn(run func(int, string) error) *MockCUPSClientInterface_MoveJob_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// PausePrinter provides a mock function with given fields: printer
|
||||
func (_m *MockCUPSClientInterface) PausePrinter(printer string) error {
|
||||
ret := _m.Called(printer)
|
||||
@@ -284,6 +834,156 @@ func (_c *MockCUPSClientInterface_PausePrinter_Call) RunAndReturn(run func(strin
|
||||
return _c
|
||||
}
|
||||
|
||||
// PrintTestPage provides a mock function with given fields: printer, testPageData, size
|
||||
func (_m *MockCUPSClientInterface) PrintTestPage(printer string, testPageData io.Reader, size int) (int, error) {
|
||||
ret := _m.Called(printer, testPageData, size)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for PrintTestPage")
|
||||
}
|
||||
|
||||
var r0 int
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string, io.Reader, int) (int, error)); ok {
|
||||
return rf(printer, testPageData, size)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string, io.Reader, int) int); ok {
|
||||
r0 = rf(printer, testPageData, size)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string, io.Reader, int) error); ok {
|
||||
r1 = rf(printer, testPageData, size)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_PrintTestPage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PrintTestPage'
|
||||
type MockCUPSClientInterface_PrintTestPage_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// PrintTestPage is a helper method to define mock.On call
|
||||
// - printer string
|
||||
// - testPageData io.Reader
|
||||
// - size int
|
||||
func (_e *MockCUPSClientInterface_Expecter) PrintTestPage(printer interface{}, testPageData interface{}, size interface{}) *MockCUPSClientInterface_PrintTestPage_Call {
|
||||
return &MockCUPSClientInterface_PrintTestPage_Call{Call: _e.mock.On("PrintTestPage", printer, testPageData, size)}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_PrintTestPage_Call) Run(run func(printer string, testPageData io.Reader, size int)) *MockCUPSClientInterface_PrintTestPage_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(io.Reader), args[2].(int))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_PrintTestPage_Call) Return(_a0 int, _a1 error) *MockCUPSClientInterface_PrintTestPage_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_PrintTestPage_Call) RunAndReturn(run func(string, io.Reader, int) (int, error)) *MockCUPSClientInterface_PrintTestPage_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// RejectJobs provides a mock function with given fields: printer
|
||||
func (_m *MockCUPSClientInterface) RejectJobs(printer string) error {
|
||||
ret := _m.Called(printer)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RejectJobs")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(printer)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_RejectJobs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RejectJobs'
|
||||
type MockCUPSClientInterface_RejectJobs_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// RejectJobs is a helper method to define mock.On call
|
||||
// - printer string
|
||||
func (_e *MockCUPSClientInterface_Expecter) RejectJobs(printer interface{}) *MockCUPSClientInterface_RejectJobs_Call {
|
||||
return &MockCUPSClientInterface_RejectJobs_Call{Call: _e.mock.On("RejectJobs", printer)}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_RejectJobs_Call) Run(run func(printer string)) *MockCUPSClientInterface_RejectJobs_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_RejectJobs_Call) Return(_a0 error) *MockCUPSClientInterface_RejectJobs_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_RejectJobs_Call) RunAndReturn(run func(string) error) *MockCUPSClientInterface_RejectJobs_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// RestartJob provides a mock function with given fields: jobID
|
||||
func (_m *MockCUPSClientInterface) RestartJob(jobID int) error {
|
||||
ret := _m.Called(jobID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RestartJob")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(int) error); ok {
|
||||
r0 = rf(jobID)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_RestartJob_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RestartJob'
|
||||
type MockCUPSClientInterface_RestartJob_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// RestartJob is a helper method to define mock.On call
|
||||
// - jobID int
|
||||
func (_e *MockCUPSClientInterface_Expecter) RestartJob(jobID interface{}) *MockCUPSClientInterface_RestartJob_Call {
|
||||
return &MockCUPSClientInterface_RestartJob_Call{Call: _e.mock.On("RestartJob", jobID)}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_RestartJob_Call) Run(run func(jobID int)) *MockCUPSClientInterface_RestartJob_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(int))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_RestartJob_Call) Return(_a0 error) *MockCUPSClientInterface_RestartJob_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_RestartJob_Call) RunAndReturn(run func(int) error) *MockCUPSClientInterface_RestartJob_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ResumePrinter provides a mock function with given fields: printer
|
||||
func (_m *MockCUPSClientInterface) ResumePrinter(printer string) error {
|
||||
ret := _m.Called(printer)
|
||||
@@ -390,6 +1090,147 @@ func (_c *MockCUPSClientInterface_SendRequest_Call) RunAndReturn(run func(string
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetPrinterInformation provides a mock function with given fields: printer, information
|
||||
func (_m *MockCUPSClientInterface) SetPrinterInformation(printer string, information string) error {
|
||||
ret := _m.Called(printer, information)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SetPrinterInformation")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string) error); ok {
|
||||
r0 = rf(printer, information)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_SetPrinterInformation_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetPrinterInformation'
|
||||
type MockCUPSClientInterface_SetPrinterInformation_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// SetPrinterInformation is a helper method to define mock.On call
|
||||
// - printer string
|
||||
// - information string
|
||||
func (_e *MockCUPSClientInterface_Expecter) SetPrinterInformation(printer interface{}, information interface{}) *MockCUPSClientInterface_SetPrinterInformation_Call {
|
||||
return &MockCUPSClientInterface_SetPrinterInformation_Call{Call: _e.mock.On("SetPrinterInformation", printer, information)}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_SetPrinterInformation_Call) Run(run func(printer string, information string)) *MockCUPSClientInterface_SetPrinterInformation_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_SetPrinterInformation_Call) Return(_a0 error) *MockCUPSClientInterface_SetPrinterInformation_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_SetPrinterInformation_Call) RunAndReturn(run func(string, string) error) *MockCUPSClientInterface_SetPrinterInformation_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetPrinterIsShared provides a mock function with given fields: printer, shared
|
||||
func (_m *MockCUPSClientInterface) SetPrinterIsShared(printer string, shared bool) error {
|
||||
ret := _m.Called(printer, shared)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SetPrinterIsShared")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, bool) error); ok {
|
||||
r0 = rf(printer, shared)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_SetPrinterIsShared_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetPrinterIsShared'
|
||||
type MockCUPSClientInterface_SetPrinterIsShared_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// SetPrinterIsShared is a helper method to define mock.On call
|
||||
// - printer string
|
||||
// - shared bool
|
||||
func (_e *MockCUPSClientInterface_Expecter) SetPrinterIsShared(printer interface{}, shared interface{}) *MockCUPSClientInterface_SetPrinterIsShared_Call {
|
||||
return &MockCUPSClientInterface_SetPrinterIsShared_Call{Call: _e.mock.On("SetPrinterIsShared", printer, shared)}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_SetPrinterIsShared_Call) Run(run func(printer string, shared bool)) *MockCUPSClientInterface_SetPrinterIsShared_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(bool))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_SetPrinterIsShared_Call) Return(_a0 error) *MockCUPSClientInterface_SetPrinterIsShared_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_SetPrinterIsShared_Call) RunAndReturn(run func(string, bool) error) *MockCUPSClientInterface_SetPrinterIsShared_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetPrinterLocation provides a mock function with given fields: printer, location
|
||||
func (_m *MockCUPSClientInterface) SetPrinterLocation(printer string, location string) error {
|
||||
ret := _m.Called(printer, location)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SetPrinterLocation")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string) error); ok {
|
||||
r0 = rf(printer, location)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCUPSClientInterface_SetPrinterLocation_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetPrinterLocation'
|
||||
type MockCUPSClientInterface_SetPrinterLocation_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// SetPrinterLocation is a helper method to define mock.On call
|
||||
// - printer string
|
||||
// - location string
|
||||
func (_e *MockCUPSClientInterface_Expecter) SetPrinterLocation(printer interface{}, location interface{}) *MockCUPSClientInterface_SetPrinterLocation_Call {
|
||||
return &MockCUPSClientInterface_SetPrinterLocation_Call{Call: _e.mock.On("SetPrinterLocation", printer, location)}
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_SetPrinterLocation_Call) Run(run func(printer string, location string)) *MockCUPSClientInterface_SetPrinterLocation_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_SetPrinterLocation_Call) Return(_a0 error) *MockCUPSClientInterface_SetPrinterLocation_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCUPSClientInterface_SetPrinterLocation_Call) RunAndReturn(run func(string, string) error) *MockCUPSClientInterface_SetPrinterLocation_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockCUPSClientInterface creates a new instance of MockCUPSClientInterface. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockCUPSClientInterface(t interface {
|
||||
|
||||
708
core/internal/mocks/cups_pkhelper/mock_PkHelper.go
Normal file
708
core/internal/mocks/cups_pkhelper/mock_PkHelper.go
Normal file
@@ -0,0 +1,708 @@
|
||||
// Code generated by mockery v2.53.5. DO NOT EDIT.
|
||||
|
||||
package mocks_cups_pkhelper
|
||||
|
||||
import (
|
||||
cups "github.com/AvengeMedia/DankMaterialShell/core/internal/server/cups"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// MockPkHelper is an autogenerated mock type for the PkHelper type
|
||||
type MockPkHelper struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type MockPkHelper_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockPkHelper) EXPECT() *MockPkHelper_Expecter {
|
||||
return &MockPkHelper_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// ClassAddPrinter provides a mock function with given fields: className, printerName
|
||||
func (_m *MockPkHelper) ClassAddPrinter(className string, printerName string) error {
|
||||
ret := _m.Called(className, printerName)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ClassAddPrinter")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string) error); ok {
|
||||
r0 = rf(className, printerName)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockPkHelper_ClassAddPrinter_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ClassAddPrinter'
|
||||
type MockPkHelper_ClassAddPrinter_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ClassAddPrinter is a helper method to define mock.On call
|
||||
// - className string
|
||||
// - printerName string
|
||||
func (_e *MockPkHelper_Expecter) ClassAddPrinter(className interface{}, printerName interface{}) *MockPkHelper_ClassAddPrinter_Call {
|
||||
return &MockPkHelper_ClassAddPrinter_Call{Call: _e.mock.On("ClassAddPrinter", className, printerName)}
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_ClassAddPrinter_Call) Run(run func(className string, printerName string)) *MockPkHelper_ClassAddPrinter_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_ClassAddPrinter_Call) Return(_a0 error) *MockPkHelper_ClassAddPrinter_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_ClassAddPrinter_Call) RunAndReturn(run func(string, string) error) *MockPkHelper_ClassAddPrinter_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ClassDelete provides a mock function with given fields: className
|
||||
func (_m *MockPkHelper) ClassDelete(className string) error {
|
||||
ret := _m.Called(className)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ClassDelete")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(className)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockPkHelper_ClassDelete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ClassDelete'
|
||||
type MockPkHelper_ClassDelete_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ClassDelete is a helper method to define mock.On call
|
||||
// - className string
|
||||
func (_e *MockPkHelper_Expecter) ClassDelete(className interface{}) *MockPkHelper_ClassDelete_Call {
|
||||
return &MockPkHelper_ClassDelete_Call{Call: _e.mock.On("ClassDelete", className)}
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_ClassDelete_Call) Run(run func(className string)) *MockPkHelper_ClassDelete_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_ClassDelete_Call) Return(_a0 error) *MockPkHelper_ClassDelete_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_ClassDelete_Call) RunAndReturn(run func(string) error) *MockPkHelper_ClassDelete_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ClassDeletePrinter provides a mock function with given fields: className, printerName
|
||||
func (_m *MockPkHelper) ClassDeletePrinter(className string, printerName string) error {
|
||||
ret := _m.Called(className, printerName)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ClassDeletePrinter")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string) error); ok {
|
||||
r0 = rf(className, printerName)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockPkHelper_ClassDeletePrinter_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ClassDeletePrinter'
|
||||
type MockPkHelper_ClassDeletePrinter_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ClassDeletePrinter is a helper method to define mock.On call
|
||||
// - className string
|
||||
// - printerName string
|
||||
func (_e *MockPkHelper_Expecter) ClassDeletePrinter(className interface{}, printerName interface{}) *MockPkHelper_ClassDeletePrinter_Call {
|
||||
return &MockPkHelper_ClassDeletePrinter_Call{Call: _e.mock.On("ClassDeletePrinter", className, printerName)}
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_ClassDeletePrinter_Call) Run(run func(className string, printerName string)) *MockPkHelper_ClassDeletePrinter_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_ClassDeletePrinter_Call) Return(_a0 error) *MockPkHelper_ClassDeletePrinter_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_ClassDeletePrinter_Call) RunAndReturn(run func(string, string) error) *MockPkHelper_ClassDeletePrinter_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// DevicesGet provides a mock function with given fields: timeout, limit, includeSchemes, excludeSchemes
|
||||
func (_m *MockPkHelper) DevicesGet(timeout int, limit int, includeSchemes []string, excludeSchemes []string) ([]cups.Device, error) {
|
||||
ret := _m.Called(timeout, limit, includeSchemes, excludeSchemes)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DevicesGet")
|
||||
}
|
||||
|
||||
var r0 []cups.Device
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(int, int, []string, []string) ([]cups.Device, error)); ok {
|
||||
return rf(timeout, limit, includeSchemes, excludeSchemes)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(int, int, []string, []string) []cups.Device); ok {
|
||||
r0 = rf(timeout, limit, includeSchemes, excludeSchemes)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]cups.Device)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(int, int, []string, []string) error); ok {
|
||||
r1 = rf(timeout, limit, includeSchemes, excludeSchemes)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockPkHelper_DevicesGet_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DevicesGet'
|
||||
type MockPkHelper_DevicesGet_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// DevicesGet is a helper method to define mock.On call
|
||||
// - timeout int
|
||||
// - limit int
|
||||
// - includeSchemes []string
|
||||
// - excludeSchemes []string
|
||||
func (_e *MockPkHelper_Expecter) DevicesGet(timeout interface{}, limit interface{}, includeSchemes interface{}, excludeSchemes interface{}) *MockPkHelper_DevicesGet_Call {
|
||||
return &MockPkHelper_DevicesGet_Call{Call: _e.mock.On("DevicesGet", timeout, limit, includeSchemes, excludeSchemes)}
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_DevicesGet_Call) Run(run func(timeout int, limit int, includeSchemes []string, excludeSchemes []string)) *MockPkHelper_DevicesGet_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(int), args[1].(int), args[2].([]string), args[3].([]string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_DevicesGet_Call) Return(_a0 []cups.Device, _a1 error) *MockPkHelper_DevicesGet_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_DevicesGet_Call) RunAndReturn(run func(int, int, []string, []string) ([]cups.Device, error)) *MockPkHelper_DevicesGet_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// JobCancelPurge provides a mock function with given fields: jobID, purge
|
||||
func (_m *MockPkHelper) JobCancelPurge(jobID int, purge bool) error {
|
||||
ret := _m.Called(jobID, purge)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for JobCancelPurge")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(int, bool) error); ok {
|
||||
r0 = rf(jobID, purge)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockPkHelper_JobCancelPurge_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'JobCancelPurge'
|
||||
type MockPkHelper_JobCancelPurge_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// JobCancelPurge is a helper method to define mock.On call
|
||||
// - jobID int
|
||||
// - purge bool
|
||||
func (_e *MockPkHelper_Expecter) JobCancelPurge(jobID interface{}, purge interface{}) *MockPkHelper_JobCancelPurge_Call {
|
||||
return &MockPkHelper_JobCancelPurge_Call{Call: _e.mock.On("JobCancelPurge", jobID, purge)}
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_JobCancelPurge_Call) Run(run func(jobID int, purge bool)) *MockPkHelper_JobCancelPurge_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(int), args[1].(bool))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_JobCancelPurge_Call) Return(_a0 error) *MockPkHelper_JobCancelPurge_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_JobCancelPurge_Call) RunAndReturn(run func(int, bool) error) *MockPkHelper_JobCancelPurge_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// JobRestart provides a mock function with given fields: jobID
|
||||
func (_m *MockPkHelper) JobRestart(jobID int) error {
|
||||
ret := _m.Called(jobID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for JobRestart")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(int) error); ok {
|
||||
r0 = rf(jobID)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockPkHelper_JobRestart_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'JobRestart'
|
||||
type MockPkHelper_JobRestart_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// JobRestart is a helper method to define mock.On call
|
||||
// - jobID int
|
||||
func (_e *MockPkHelper_Expecter) JobRestart(jobID interface{}) *MockPkHelper_JobRestart_Call {
|
||||
return &MockPkHelper_JobRestart_Call{Call: _e.mock.On("JobRestart", jobID)}
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_JobRestart_Call) Run(run func(jobID int)) *MockPkHelper_JobRestart_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(int))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_JobRestart_Call) Return(_a0 error) *MockPkHelper_JobRestart_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_JobRestart_Call) RunAndReturn(run func(int) error) *MockPkHelper_JobRestart_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// JobSetHoldUntil provides a mock function with given fields: jobID, holdUntil
|
||||
func (_m *MockPkHelper) JobSetHoldUntil(jobID int, holdUntil string) error {
|
||||
ret := _m.Called(jobID, holdUntil)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for JobSetHoldUntil")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(int, string) error); ok {
|
||||
r0 = rf(jobID, holdUntil)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockPkHelper_JobSetHoldUntil_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'JobSetHoldUntil'
|
||||
type MockPkHelper_JobSetHoldUntil_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// JobSetHoldUntil is a helper method to define mock.On call
|
||||
// - jobID int
|
||||
// - holdUntil string
|
||||
func (_e *MockPkHelper_Expecter) JobSetHoldUntil(jobID interface{}, holdUntil interface{}) *MockPkHelper_JobSetHoldUntil_Call {
|
||||
return &MockPkHelper_JobSetHoldUntil_Call{Call: _e.mock.On("JobSetHoldUntil", jobID, holdUntil)}
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_JobSetHoldUntil_Call) Run(run func(jobID int, holdUntil string)) *MockPkHelper_JobSetHoldUntil_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(int), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_JobSetHoldUntil_Call) Return(_a0 error) *MockPkHelper_JobSetHoldUntil_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_JobSetHoldUntil_Call) RunAndReturn(run func(int, string) error) *MockPkHelper_JobSetHoldUntil_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// PrinterAdd provides a mock function with given fields: name, uri, ppd, info, location
|
||||
func (_m *MockPkHelper) PrinterAdd(name string, uri string, ppd string, info string, location string) error {
|
||||
ret := _m.Called(name, uri, ppd, info, location)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for PrinterAdd")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, string, string, string) error); ok {
|
||||
r0 = rf(name, uri, ppd, info, location)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockPkHelper_PrinterAdd_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PrinterAdd'
|
||||
type MockPkHelper_PrinterAdd_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// PrinterAdd is a helper method to define mock.On call
|
||||
// - name string
|
||||
// - uri string
|
||||
// - ppd string
|
||||
// - info string
|
||||
// - location string
|
||||
func (_e *MockPkHelper_Expecter) PrinterAdd(name interface{}, uri interface{}, ppd interface{}, info interface{}, location interface{}) *MockPkHelper_PrinterAdd_Call {
|
||||
return &MockPkHelper_PrinterAdd_Call{Call: _e.mock.On("PrinterAdd", name, uri, ppd, info, location)}
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterAdd_Call) Run(run func(name string, uri string, ppd string, info string, location string)) *MockPkHelper_PrinterAdd_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(string), args[2].(string), args[3].(string), args[4].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterAdd_Call) Return(_a0 error) *MockPkHelper_PrinterAdd_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterAdd_Call) RunAndReturn(run func(string, string, string, string, string) error) *MockPkHelper_PrinterAdd_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// PrinterDelete provides a mock function with given fields: name
|
||||
func (_m *MockPkHelper) PrinterDelete(name string) error {
|
||||
ret := _m.Called(name)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for PrinterDelete")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(name)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockPkHelper_PrinterDelete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PrinterDelete'
|
||||
type MockPkHelper_PrinterDelete_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// PrinterDelete is a helper method to define mock.On call
|
||||
// - name string
|
||||
func (_e *MockPkHelper_Expecter) PrinterDelete(name interface{}) *MockPkHelper_PrinterDelete_Call {
|
||||
return &MockPkHelper_PrinterDelete_Call{Call: _e.mock.On("PrinterDelete", name)}
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterDelete_Call) Run(run func(name string)) *MockPkHelper_PrinterDelete_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterDelete_Call) Return(_a0 error) *MockPkHelper_PrinterDelete_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterDelete_Call) RunAndReturn(run func(string) error) *MockPkHelper_PrinterDelete_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// PrinterSetAcceptJobs provides a mock function with given fields: name, enabled, reason
|
||||
func (_m *MockPkHelper) PrinterSetAcceptJobs(name string, enabled bool, reason string) error {
|
||||
ret := _m.Called(name, enabled, reason)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for PrinterSetAcceptJobs")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, bool, string) error); ok {
|
||||
r0 = rf(name, enabled, reason)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockPkHelper_PrinterSetAcceptJobs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PrinterSetAcceptJobs'
|
||||
type MockPkHelper_PrinterSetAcceptJobs_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// PrinterSetAcceptJobs is a helper method to define mock.On call
|
||||
// - name string
|
||||
// - enabled bool
|
||||
// - reason string
|
||||
func (_e *MockPkHelper_Expecter) PrinterSetAcceptJobs(name interface{}, enabled interface{}, reason interface{}) *MockPkHelper_PrinterSetAcceptJobs_Call {
|
||||
return &MockPkHelper_PrinterSetAcceptJobs_Call{Call: _e.mock.On("PrinterSetAcceptJobs", name, enabled, reason)}
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterSetAcceptJobs_Call) Run(run func(name string, enabled bool, reason string)) *MockPkHelper_PrinterSetAcceptJobs_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(bool), args[2].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterSetAcceptJobs_Call) Return(_a0 error) *MockPkHelper_PrinterSetAcceptJobs_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterSetAcceptJobs_Call) RunAndReturn(run func(string, bool, string) error) *MockPkHelper_PrinterSetAcceptJobs_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// PrinterSetEnabled provides a mock function with given fields: name, enabled
|
||||
func (_m *MockPkHelper) PrinterSetEnabled(name string, enabled bool) error {
|
||||
ret := _m.Called(name, enabled)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for PrinterSetEnabled")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, bool) error); ok {
|
||||
r0 = rf(name, enabled)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockPkHelper_PrinterSetEnabled_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PrinterSetEnabled'
|
||||
type MockPkHelper_PrinterSetEnabled_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// PrinterSetEnabled is a helper method to define mock.On call
|
||||
// - name string
|
||||
// - enabled bool
|
||||
func (_e *MockPkHelper_Expecter) PrinterSetEnabled(name interface{}, enabled interface{}) *MockPkHelper_PrinterSetEnabled_Call {
|
||||
return &MockPkHelper_PrinterSetEnabled_Call{Call: _e.mock.On("PrinterSetEnabled", name, enabled)}
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterSetEnabled_Call) Run(run func(name string, enabled bool)) *MockPkHelper_PrinterSetEnabled_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(bool))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterSetEnabled_Call) Return(_a0 error) *MockPkHelper_PrinterSetEnabled_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterSetEnabled_Call) RunAndReturn(run func(string, bool) error) *MockPkHelper_PrinterSetEnabled_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// PrinterSetInfo provides a mock function with given fields: name, info
|
||||
func (_m *MockPkHelper) PrinterSetInfo(name string, info string) error {
|
||||
ret := _m.Called(name, info)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for PrinterSetInfo")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string) error); ok {
|
||||
r0 = rf(name, info)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockPkHelper_PrinterSetInfo_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PrinterSetInfo'
|
||||
type MockPkHelper_PrinterSetInfo_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// PrinterSetInfo is a helper method to define mock.On call
|
||||
// - name string
|
||||
// - info string
|
||||
func (_e *MockPkHelper_Expecter) PrinterSetInfo(name interface{}, info interface{}) *MockPkHelper_PrinterSetInfo_Call {
|
||||
return &MockPkHelper_PrinterSetInfo_Call{Call: _e.mock.On("PrinterSetInfo", name, info)}
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterSetInfo_Call) Run(run func(name string, info string)) *MockPkHelper_PrinterSetInfo_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterSetInfo_Call) Return(_a0 error) *MockPkHelper_PrinterSetInfo_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterSetInfo_Call) RunAndReturn(run func(string, string) error) *MockPkHelper_PrinterSetInfo_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// PrinterSetLocation provides a mock function with given fields: name, location
|
||||
func (_m *MockPkHelper) PrinterSetLocation(name string, location string) error {
|
||||
ret := _m.Called(name, location)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for PrinterSetLocation")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string) error); ok {
|
||||
r0 = rf(name, location)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockPkHelper_PrinterSetLocation_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PrinterSetLocation'
|
||||
type MockPkHelper_PrinterSetLocation_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// PrinterSetLocation is a helper method to define mock.On call
|
||||
// - name string
|
||||
// - location string
|
||||
func (_e *MockPkHelper_Expecter) PrinterSetLocation(name interface{}, location interface{}) *MockPkHelper_PrinterSetLocation_Call {
|
||||
return &MockPkHelper_PrinterSetLocation_Call{Call: _e.mock.On("PrinterSetLocation", name, location)}
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterSetLocation_Call) Run(run func(name string, location string)) *MockPkHelper_PrinterSetLocation_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterSetLocation_Call) Return(_a0 error) *MockPkHelper_PrinterSetLocation_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterSetLocation_Call) RunAndReturn(run func(string, string) error) *MockPkHelper_PrinterSetLocation_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// PrinterSetShared provides a mock function with given fields: name, shared
|
||||
func (_m *MockPkHelper) PrinterSetShared(name string, shared bool) error {
|
||||
ret := _m.Called(name, shared)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for PrinterSetShared")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, bool) error); ok {
|
||||
r0 = rf(name, shared)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockPkHelper_PrinterSetShared_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PrinterSetShared'
|
||||
type MockPkHelper_PrinterSetShared_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// PrinterSetShared is a helper method to define mock.On call
|
||||
// - name string
|
||||
// - shared bool
|
||||
func (_e *MockPkHelper_Expecter) PrinterSetShared(name interface{}, shared interface{}) *MockPkHelper_PrinterSetShared_Call {
|
||||
return &MockPkHelper_PrinterSetShared_Call{Call: _e.mock.On("PrinterSetShared", name, shared)}
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterSetShared_Call) Run(run func(name string, shared bool)) *MockPkHelper_PrinterSetShared_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(bool))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterSetShared_Call) Return(_a0 error) *MockPkHelper_PrinterSetShared_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockPkHelper_PrinterSetShared_Call) RunAndReturn(run func(string, bool) error) *MockPkHelper_PrinterSetShared_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockPkHelper creates a new instance of MockPkHelper. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockPkHelper(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockPkHelper {
|
||||
mock := &MockPkHelper{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -52,7 +52,7 @@ type MockGitClient_HasUpdates_Call struct {
|
||||
|
||||
// HasUpdates is a helper method to define mock.On call
|
||||
// - path string
|
||||
func (_e *MockGitClient_Expecter) HasUpdates(path interface{}) *MockGitClient_HasUpdates_Call {
|
||||
func (_e *MockGitClient_Expecter) HasUpdates(path any) *MockGitClient_HasUpdates_Call {
|
||||
return &MockGitClient_HasUpdates_Call{Call: _e.mock.On("HasUpdates", path)}
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ type MockGitClient_PlainClone_Call struct {
|
||||
// PlainClone is a helper method to define mock.On call
|
||||
// - path string
|
||||
// - url string
|
||||
func (_e *MockGitClient_Expecter) PlainClone(path interface{}, url interface{}) *MockGitClient_PlainClone_Call {
|
||||
func (_e *MockGitClient_Expecter) PlainClone(path any, url any) *MockGitClient_PlainClone_Call {
|
||||
return &MockGitClient_PlainClone_Call{Call: _e.mock.On("PlainClone", path, url)}
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ type MockGitClient_Pull_Call struct {
|
||||
|
||||
// Pull is a helper method to define mock.On call
|
||||
// - path string
|
||||
func (_e *MockGitClient_Expecter) Pull(path interface{}) *MockGitClient_Pull_Call {
|
||||
func (_e *MockGitClient_Expecter) Pull(path any) *MockGitClient_Pull_Call {
|
||||
return &MockGitClient_Pull_Call{Call: _e.mock.On("Pull", path)}
|
||||
}
|
||||
|
||||
|
||||
@@ -328,6 +328,52 @@ func (_c *MockBackend_ConnectWiFi_Call) RunAndReturn(run func(network.Connection
|
||||
return _c
|
||||
}
|
||||
|
||||
// DeleteVPN provides a mock function with given fields: uuidOrName
|
||||
func (_m *MockBackend) DeleteVPN(uuidOrName string) error {
|
||||
ret := _m.Called(uuidOrName)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeleteVPN")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(uuidOrName)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockBackend_DeleteVPN_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteVPN'
|
||||
type MockBackend_DeleteVPN_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// DeleteVPN is a helper method to define mock.On call
|
||||
// - uuidOrName string
|
||||
func (_e *MockBackend_Expecter) DeleteVPN(uuidOrName interface{}) *MockBackend_DeleteVPN_Call {
|
||||
return &MockBackend_DeleteVPN_Call{Call: _e.mock.On("DeleteVPN", uuidOrName)}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_DeleteVPN_Call) Run(run func(uuidOrName string)) *MockBackend_DeleteVPN_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_DeleteVPN_Call) Return(_a0 error) *MockBackend_DeleteVPN_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_DeleteVPN_Call) RunAndReturn(run func(string) error) *MockBackend_DeleteVPN_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// DisconnectAllVPN provides a mock function with no fields
|
||||
func (_m *MockBackend) DisconnectAllVPN() error {
|
||||
ret := _m.Called()
|
||||
@@ -418,6 +464,52 @@ func (_c *MockBackend_DisconnectEthernet_Call) RunAndReturn(run func() error) *M
|
||||
return _c
|
||||
}
|
||||
|
||||
// DisconnectEthernetDevice provides a mock function with given fields: device
|
||||
func (_m *MockBackend) DisconnectEthernetDevice(device string) error {
|
||||
ret := _m.Called(device)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DisconnectEthernetDevice")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(device)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockBackend_DisconnectEthernetDevice_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DisconnectEthernetDevice'
|
||||
type MockBackend_DisconnectEthernetDevice_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// DisconnectEthernetDevice is a helper method to define mock.On call
|
||||
// - device string
|
||||
func (_e *MockBackend_Expecter) DisconnectEthernetDevice(device interface{}) *MockBackend_DisconnectEthernetDevice_Call {
|
||||
return &MockBackend_DisconnectEthernetDevice_Call{Call: _e.mock.On("DisconnectEthernetDevice", device)}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_DisconnectEthernetDevice_Call) Run(run func(device string)) *MockBackend_DisconnectEthernetDevice_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_DisconnectEthernetDevice_Call) Return(_a0 error) *MockBackend_DisconnectEthernetDevice_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_DisconnectEthernetDevice_Call) RunAndReturn(run func(string) error) *MockBackend_DisconnectEthernetDevice_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// DisconnectVPN provides a mock function with given fields: uuidOrName
|
||||
func (_m *MockBackend) DisconnectVPN(uuidOrName string) error {
|
||||
ret := _m.Called(uuidOrName)
|
||||
@@ -509,6 +601,52 @@ func (_c *MockBackend_DisconnectWiFi_Call) RunAndReturn(run func() error) *MockB
|
||||
return _c
|
||||
}
|
||||
|
||||
// DisconnectWiFiDevice provides a mock function with given fields: device
|
||||
func (_m *MockBackend) DisconnectWiFiDevice(device string) error {
|
||||
ret := _m.Called(device)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DisconnectWiFiDevice")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(device)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockBackend_DisconnectWiFiDevice_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DisconnectWiFiDevice'
|
||||
type MockBackend_DisconnectWiFiDevice_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// DisconnectWiFiDevice is a helper method to define mock.On call
|
||||
// - device string
|
||||
func (_e *MockBackend_Expecter) DisconnectWiFiDevice(device interface{}) *MockBackend_DisconnectWiFiDevice_Call {
|
||||
return &MockBackend_DisconnectWiFiDevice_Call{Call: _e.mock.On("DisconnectWiFiDevice", device)}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_DisconnectWiFiDevice_Call) Run(run func(device string)) *MockBackend_DisconnectWiFiDevice_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_DisconnectWiFiDevice_Call) Return(_a0 error) *MockBackend_DisconnectWiFiDevice_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_DisconnectWiFiDevice_Call) RunAndReturn(run func(string) error) *MockBackend_DisconnectWiFiDevice_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ForgetWiFiNetwork provides a mock function with given fields: ssid
|
||||
func (_m *MockBackend) ForgetWiFiNetwork(ssid string) error {
|
||||
ret := _m.Called(ssid)
|
||||
@@ -612,6 +750,53 @@ func (_c *MockBackend_GetCurrentState_Call) RunAndReturn(run func() (*network.Ba
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetEthernetDevices provides a mock function with no fields
|
||||
func (_m *MockBackend) GetEthernetDevices() []network.EthernetDevice {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetEthernetDevices")
|
||||
}
|
||||
|
||||
var r0 []network.EthernetDevice
|
||||
if rf, ok := ret.Get(0).(func() []network.EthernetDevice); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]network.EthernetDevice)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockBackend_GetEthernetDevices_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetEthernetDevices'
|
||||
type MockBackend_GetEthernetDevices_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetEthernetDevices is a helper method to define mock.On call
|
||||
func (_e *MockBackend_Expecter) GetEthernetDevices() *MockBackend_GetEthernetDevices_Call {
|
||||
return &MockBackend_GetEthernetDevices_Call{Call: _e.mock.On("GetEthernetDevices")}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_GetEthernetDevices_Call) Run(run func()) *MockBackend_GetEthernetDevices_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_GetEthernetDevices_Call) Return(_a0 []network.EthernetDevice) *MockBackend_GetEthernetDevices_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_GetEthernetDevices_Call) RunAndReturn(run func() []network.EthernetDevice) *MockBackend_GetEthernetDevices_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetPromptBroker provides a mock function with no fields
|
||||
func (_m *MockBackend) GetPromptBroker() network.PromptBroker {
|
||||
ret := _m.Called()
|
||||
@@ -659,6 +844,111 @@ func (_c *MockBackend_GetPromptBroker_Call) RunAndReturn(run func() network.Prom
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetVPNConfig provides a mock function with given fields: uuidOrName
|
||||
func (_m *MockBackend) GetVPNConfig(uuidOrName string) (*network.VPNConfig, error) {
|
||||
ret := _m.Called(uuidOrName)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetVPNConfig")
|
||||
}
|
||||
|
||||
var r0 *network.VPNConfig
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string) (*network.VPNConfig, error)); ok {
|
||||
return rf(uuidOrName)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string) *network.VPNConfig); ok {
|
||||
r0 = rf(uuidOrName)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*network.VPNConfig)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(uuidOrName)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockBackend_GetVPNConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetVPNConfig'
|
||||
type MockBackend_GetVPNConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetVPNConfig is a helper method to define mock.On call
|
||||
// - uuidOrName string
|
||||
func (_e *MockBackend_Expecter) GetVPNConfig(uuidOrName interface{}) *MockBackend_GetVPNConfig_Call {
|
||||
return &MockBackend_GetVPNConfig_Call{Call: _e.mock.On("GetVPNConfig", uuidOrName)}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_GetVPNConfig_Call) Run(run func(uuidOrName string)) *MockBackend_GetVPNConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_GetVPNConfig_Call) Return(_a0 *network.VPNConfig, _a1 error) *MockBackend_GetVPNConfig_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_GetVPNConfig_Call) RunAndReturn(run func(string) (*network.VPNConfig, error)) *MockBackend_GetVPNConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetWiFiDevices provides a mock function with no fields
|
||||
func (_m *MockBackend) GetWiFiDevices() []network.WiFiDevice {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetWiFiDevices")
|
||||
}
|
||||
|
||||
var r0 []network.WiFiDevice
|
||||
if rf, ok := ret.Get(0).(func() []network.WiFiDevice); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]network.WiFiDevice)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockBackend_GetWiFiDevices_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetWiFiDevices'
|
||||
type MockBackend_GetWiFiDevices_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetWiFiDevices is a helper method to define mock.On call
|
||||
func (_e *MockBackend_Expecter) GetWiFiDevices() *MockBackend_GetWiFiDevices_Call {
|
||||
return &MockBackend_GetWiFiDevices_Call{Call: _e.mock.On("GetWiFiDevices")}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_GetWiFiDevices_Call) Run(run func()) *MockBackend_GetWiFiDevices_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_GetWiFiDevices_Call) Return(_a0 []network.WiFiDevice) *MockBackend_GetWiFiDevices_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_GetWiFiDevices_Call) RunAndReturn(run func() []network.WiFiDevice) *MockBackend_GetWiFiDevices_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetWiFiEnabled provides a mock function with no fields
|
||||
func (_m *MockBackend) GetWiFiEnabled() (bool, error) {
|
||||
ret := _m.Called()
|
||||
@@ -887,6 +1177,65 @@ func (_c *MockBackend_GetWiredNetworkDetails_Call) RunAndReturn(run func(string)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ImportVPN provides a mock function with given fields: filePath, name
|
||||
func (_m *MockBackend) ImportVPN(filePath string, name string) (*network.VPNImportResult, error) {
|
||||
ret := _m.Called(filePath, name)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ImportVPN")
|
||||
}
|
||||
|
||||
var r0 *network.VPNImportResult
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string, string) (*network.VPNImportResult, error)); ok {
|
||||
return rf(filePath, name)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string, string) *network.VPNImportResult); ok {
|
||||
r0 = rf(filePath, name)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*network.VPNImportResult)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string, string) error); ok {
|
||||
r1 = rf(filePath, name)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockBackend_ImportVPN_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ImportVPN'
|
||||
type MockBackend_ImportVPN_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ImportVPN is a helper method to define mock.On call
|
||||
// - filePath string
|
||||
// - name string
|
||||
func (_e *MockBackend_Expecter) ImportVPN(filePath interface{}, name interface{}) *MockBackend_ImportVPN_Call {
|
||||
return &MockBackend_ImportVPN_Call{Call: _e.mock.On("ImportVPN", filePath, name)}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_ImportVPN_Call) Run(run func(filePath string, name string)) *MockBackend_ImportVPN_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_ImportVPN_Call) Return(_a0 *network.VPNImportResult, _a1 error) *MockBackend_ImportVPN_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_ImportVPN_Call) RunAndReturn(run func(string, string) (*network.VPNImportResult, error)) *MockBackend_ImportVPN_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Initialize provides a mock function with no fields
|
||||
func (_m *MockBackend) Initialize() error {
|
||||
ret := _m.Called()
|
||||
@@ -989,6 +1338,63 @@ func (_c *MockBackend_ListActiveVPN_Call) RunAndReturn(run func() ([]network.VPN
|
||||
return _c
|
||||
}
|
||||
|
||||
// ListVPNPlugins provides a mock function with no fields
|
||||
func (_m *MockBackend) ListVPNPlugins() ([]network.VPNPlugin, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ListVPNPlugins")
|
||||
}
|
||||
|
||||
var r0 []network.VPNPlugin
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func() ([]network.VPNPlugin, error)); ok {
|
||||
return rf()
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func() []network.VPNPlugin); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]network.VPNPlugin)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockBackend_ListVPNPlugins_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListVPNPlugins'
|
||||
type MockBackend_ListVPNPlugins_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ListVPNPlugins is a helper method to define mock.On call
|
||||
func (_e *MockBackend_Expecter) ListVPNPlugins() *MockBackend_ListVPNPlugins_Call {
|
||||
return &MockBackend_ListVPNPlugins_Call{Call: _e.mock.On("ListVPNPlugins")}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_ListVPNPlugins_Call) Run(run func()) *MockBackend_ListVPNPlugins_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_ListVPNPlugins_Call) Return(_a0 []network.VPNPlugin, _a1 error) *MockBackend_ListVPNPlugins_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_ListVPNPlugins_Call) RunAndReturn(run func() ([]network.VPNPlugin, error)) *MockBackend_ListVPNPlugins_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ListVPNProfiles provides a mock function with no fields
|
||||
func (_m *MockBackend) ListVPNProfiles() ([]network.VPNProfile, error) {
|
||||
ret := _m.Called()
|
||||
@@ -1091,6 +1497,52 @@ func (_c *MockBackend_ScanWiFi_Call) RunAndReturn(run func() error) *MockBackend
|
||||
return _c
|
||||
}
|
||||
|
||||
// ScanWiFiDevice provides a mock function with given fields: device
|
||||
func (_m *MockBackend) ScanWiFiDevice(device string) error {
|
||||
ret := _m.Called(device)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ScanWiFiDevice")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(device)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockBackend_ScanWiFiDevice_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ScanWiFiDevice'
|
||||
type MockBackend_ScanWiFiDevice_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ScanWiFiDevice is a helper method to define mock.On call
|
||||
// - device string
|
||||
func (_e *MockBackend_Expecter) ScanWiFiDevice(device interface{}) *MockBackend_ScanWiFiDevice_Call {
|
||||
return &MockBackend_ScanWiFiDevice_Call{Call: _e.mock.On("ScanWiFiDevice", device)}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_ScanWiFiDevice_Call) Run(run func(device string)) *MockBackend_ScanWiFiDevice_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_ScanWiFiDevice_Call) Return(_a0 error) *MockBackend_ScanWiFiDevice_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_ScanWiFiDevice_Call) RunAndReturn(run func(string) error) *MockBackend_ScanWiFiDevice_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetPromptBroker provides a mock function with given fields: broker
|
||||
func (_m *MockBackend) SetPromptBroker(broker network.PromptBroker) error {
|
||||
ret := _m.Called(broker)
|
||||
@@ -1137,6 +1589,55 @@ func (_c *MockBackend_SetPromptBroker_Call) RunAndReturn(run func(network.Prompt
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetVPNCredentials provides a mock function with given fields: uuid, username, password, save
|
||||
func (_m *MockBackend) SetVPNCredentials(uuid string, username string, password string, save bool) error {
|
||||
ret := _m.Called(uuid, username, password, save)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SetVPNCredentials")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, string, bool) error); ok {
|
||||
r0 = rf(uuid, username, password, save)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockBackend_SetVPNCredentials_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetVPNCredentials'
|
||||
type MockBackend_SetVPNCredentials_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// SetVPNCredentials is a helper method to define mock.On call
|
||||
// - uuid string
|
||||
// - username string
|
||||
// - password string
|
||||
// - save bool
|
||||
func (_e *MockBackend_Expecter) SetVPNCredentials(uuid interface{}, username interface{}, password interface{}, save interface{}) *MockBackend_SetVPNCredentials_Call {
|
||||
return &MockBackend_SetVPNCredentials_Call{Call: _e.mock.On("SetVPNCredentials", uuid, username, password, save)}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_SetVPNCredentials_Call) Run(run func(uuid string, username string, password string, save bool)) *MockBackend_SetVPNCredentials_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(string), args[2].(string), args[3].(bool))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_SetVPNCredentials_Call) Return(_a0 error) *MockBackend_SetVPNCredentials_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_SetVPNCredentials_Call) RunAndReturn(run func(string, string, string, bool) error) *MockBackend_SetVPNCredentials_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetWiFiAutoconnect provides a mock function with given fields: ssid, autoconnect
|
||||
func (_m *MockBackend) SetWiFiAutoconnect(ssid string, autoconnect bool) error {
|
||||
ret := _m.Called(ssid, autoconnect)
|
||||
@@ -1356,6 +1857,53 @@ func (_c *MockBackend_SubmitCredentials_Call) RunAndReturn(run func(string, map[
|
||||
return _c
|
||||
}
|
||||
|
||||
// UpdateVPNConfig provides a mock function with given fields: uuid, updates
|
||||
func (_m *MockBackend) UpdateVPNConfig(uuid string, updates map[string]interface{}) error {
|
||||
ret := _m.Called(uuid, updates)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for UpdateVPNConfig")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, map[string]interface{}) error); ok {
|
||||
r0 = rf(uuid, updates)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockBackend_UpdateVPNConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateVPNConfig'
|
||||
type MockBackend_UpdateVPNConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// UpdateVPNConfig is a helper method to define mock.On call
|
||||
// - uuid string
|
||||
// - updates map[string]interface{}
|
||||
func (_e *MockBackend_Expecter) UpdateVPNConfig(uuid interface{}, updates interface{}) *MockBackend_UpdateVPNConfig_Call {
|
||||
return &MockBackend_UpdateVPNConfig_Call{Call: _e.mock.On("UpdateVPNConfig", uuid, updates)}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_UpdateVPNConfig_Call) Run(run func(uuid string, updates map[string]interface{})) *MockBackend_UpdateVPNConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string), args[1].(map[string]interface{}))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_UpdateVPNConfig_Call) Return(_a0 error) *MockBackend_UpdateVPNConfig_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_UpdateVPNConfig_Call) RunAndReturn(run func(string, map[string]interface{}) error) *MockBackend_UpdateVPNConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockBackend creates a new instance of MockBackend. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockBackend(t interface {
|
||||
|
||||
144
core/internal/mocks/version/mock_VersionFetcher.go
Normal file
144
core/internal/mocks/version/mock_VersionFetcher.go
Normal file
@@ -0,0 +1,144 @@
|
||||
// Code generated by mockery v2.53.5. DO NOT EDIT.
|
||||
|
||||
package mocks_version
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
|
||||
// MockVersionFetcher is an autogenerated mock type for the VersionFetcher type
|
||||
type MockVersionFetcher struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type MockVersionFetcher_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockVersionFetcher) EXPECT() *MockVersionFetcher_Expecter {
|
||||
return &MockVersionFetcher_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// GetCurrentVersion provides a mock function with given fields: dmsPath
|
||||
func (_m *MockVersionFetcher) GetCurrentVersion(dmsPath string) (string, error) {
|
||||
ret := _m.Called(dmsPath)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetCurrentVersion")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string) (string, error)); ok {
|
||||
return rf(dmsPath)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string) string); ok {
|
||||
r0 = rf(dmsPath)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(dmsPath)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockVersionFetcher_GetCurrentVersion_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetCurrentVersion'
|
||||
type MockVersionFetcher_GetCurrentVersion_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetCurrentVersion is a helper method to define mock.On call
|
||||
// - dmsPath string
|
||||
func (_e *MockVersionFetcher_Expecter) GetCurrentVersion(dmsPath interface{}) *MockVersionFetcher_GetCurrentVersion_Call {
|
||||
return &MockVersionFetcher_GetCurrentVersion_Call{Call: _e.mock.On("GetCurrentVersion", dmsPath)}
|
||||
}
|
||||
|
||||
func (_c *MockVersionFetcher_GetCurrentVersion_Call) Run(run func(dmsPath string)) *MockVersionFetcher_GetCurrentVersion_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockVersionFetcher_GetCurrentVersion_Call) Return(_a0 string, _a1 error) *MockVersionFetcher_GetCurrentVersion_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockVersionFetcher_GetCurrentVersion_Call) RunAndReturn(run func(string) (string, error)) *MockVersionFetcher_GetCurrentVersion_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetLatestVersion provides a mock function with given fields: dmsPath
|
||||
func (_m *MockVersionFetcher) GetLatestVersion(dmsPath string) (string, error) {
|
||||
ret := _m.Called(dmsPath)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetLatestVersion")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string) (string, error)); ok {
|
||||
return rf(dmsPath)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string) string); ok {
|
||||
r0 = rf(dmsPath)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(dmsPath)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockVersionFetcher_GetLatestVersion_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLatestVersion'
|
||||
type MockVersionFetcher_GetLatestVersion_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetLatestVersion is a helper method to define mock.On call
|
||||
// - dmsPath string
|
||||
func (_e *MockVersionFetcher_Expecter) GetLatestVersion(dmsPath interface{}) *MockVersionFetcher_GetLatestVersion_Call {
|
||||
return &MockVersionFetcher_GetLatestVersion_Call{Call: _e.mock.On("GetLatestVersion", dmsPath)}
|
||||
}
|
||||
|
||||
func (_c *MockVersionFetcher_GetLatestVersion_Call) Run(run func(dmsPath string)) *MockVersionFetcher_GetLatestVersion_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockVersionFetcher_GetLatestVersion_Call) Return(_a0 string, _a1 error) *MockVersionFetcher_GetLatestVersion_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockVersionFetcher_GetLatestVersion_Call) RunAndReturn(run func(string) (string, error)) *MockVersionFetcher_GetLatestVersion_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockVersionFetcher creates a new instance of MockVersionFetcher. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockVersionFetcher(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockVersionFetcher {
|
||||
mock := &MockVersionFetcher{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -93,7 +93,7 @@ func (m *Manager) Install(plugin Plugin) error {
|
||||
|
||||
if !repoExists {
|
||||
if err := m.gitClient.PlainClone(repoPath, plugin.Repo); err != nil {
|
||||
m.fs.RemoveAll(repoPath)
|
||||
m.fs.RemoveAll(repoPath) //nolint:errcheck
|
||||
return fmt.Errorf("failed to clone repository: %w", err)
|
||||
}
|
||||
} else {
|
||||
@@ -130,7 +130,7 @@ func (m *Manager) Install(plugin Plugin) error {
|
||||
}
|
||||
} else {
|
||||
if err := m.gitClient.PlainClone(pluginPath, plugin.Repo); err != nil {
|
||||
m.fs.RemoveAll(pluginPath)
|
||||
m.fs.RemoveAll(pluginPath) //nolint:errcheck
|
||||
return fmt.Errorf("failed to clone plugin: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// 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
|
||||
//
|
||||
// dwl_ipc_unstable_v2 Protocol Copyright:
|
||||
|
||||
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].
|
||||
// It can be used to match the [client.RegistryGlobalEvent.Interface] in the
|
||||
@@ -157,6 +157,16 @@ type ZdwlIpcOutputV2 struct {
|
||||
appidHandler ZdwlIpcOutputV2AppidHandlerFunc
|
||||
layoutSymbolHandler ZdwlIpcOutputV2LayoutSymbolHandlerFunc
|
||||
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
|
||||
@@ -251,6 +261,60 @@ func (i *ZdwlIpcOutputV2) SetLayout(index uint32) error {
|
||||
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
|
||||
|
||||
// ZdwlIpcOutputV2TagState :
|
||||
@@ -399,6 +463,136 @@ func (i *ZdwlIpcOutputV2) SetFrameHandler(f ZdwlIpcOutputV2FrameHandlerFunc) {
|
||||
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) {
|
||||
switch opcode {
|
||||
case 0:
|
||||
@@ -487,5 +681,111 @@ func (i *ZdwlIpcOutputV2) Dispatch(opcode uint32, fd int, data []byte) {
|
||||
var e ZdwlIpcOutputV2FrameEvent
|
||||
|
||||
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
|
||||
// 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
|
||||
//
|
||||
// ext_workspace_v1 Protocol Copyright:
|
||||
@@ -35,7 +35,8 @@ import (
|
||||
"reflect"
|
||||
"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.
|
||||
@@ -61,8 +62,9 @@ func registerServerProxy(ctx *client.Context, proxy client.Proxy, serverID uint3
|
||||
return
|
||||
}
|
||||
|
||||
objectsMap := reflect.NewAt(objectsField.Type(), unsafe.Pointer(objectsField.UnsafeAddr())).Elem()
|
||||
objectsMap.SetMapIndex(reflect.ValueOf(serverID), reflect.ValueOf(proxy))
|
||||
objectsMapPtr := unsafe.Pointer(objectsField.UnsafeAddr())
|
||||
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].
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
// Generated by go-wayland-scanner
|
||||
// https://github.com/yaslama/go-wayland/cmd/go-wayland-scanner
|
||||
// XML file : internal/proto/xml/keyboard-shortcuts-inhibit-unstable-v1.xml
|
||||
//
|
||||
// keyboard_shortcuts_inhibit_unstable_v1 Protocol Copyright:
|
||||
//
|
||||
// Copyright © 2017 Red Hat Inc.
|
||||
//
|
||||
// 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 keyboard_shortcuts_inhibit
|
||||
|
||||
import "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
||||
|
||||
// ZwpKeyboardShortcutsInhibitManagerV1InterfaceName 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 ZwpKeyboardShortcutsInhibitManagerV1InterfaceName = "zwp_keyboard_shortcuts_inhibit_manager_v1"
|
||||
|
||||
// ZwpKeyboardShortcutsInhibitManagerV1 : context object for keyboard grab_manager
|
||||
//
|
||||
// A global interface used for inhibiting the compositor keyboard shortcuts.
|
||||
type ZwpKeyboardShortcutsInhibitManagerV1 struct {
|
||||
client.BaseProxy
|
||||
}
|
||||
|
||||
// NewZwpKeyboardShortcutsInhibitManagerV1 : context object for keyboard grab_manager
|
||||
//
|
||||
// A global interface used for inhibiting the compositor keyboard shortcuts.
|
||||
func NewZwpKeyboardShortcutsInhibitManagerV1(ctx *client.Context) *ZwpKeyboardShortcutsInhibitManagerV1 {
|
||||
zwpKeyboardShortcutsInhibitManagerV1 := &ZwpKeyboardShortcutsInhibitManagerV1{}
|
||||
ctx.Register(zwpKeyboardShortcutsInhibitManagerV1)
|
||||
return zwpKeyboardShortcutsInhibitManagerV1
|
||||
}
|
||||
|
||||
// Destroy : destroy the keyboard shortcuts inhibitor object
|
||||
//
|
||||
// Destroy the keyboard shortcuts inhibitor manager.
|
||||
func (i *ZwpKeyboardShortcutsInhibitManagerV1) Destroy() error {
|
||||
defer i.Context().Unregister(i)
|
||||
const opcode = 0
|
||||
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
|
||||
}
|
||||
|
||||
// InhibitShortcuts : create a new keyboard shortcuts inhibitor object
|
||||
//
|
||||
// Create a new keyboard shortcuts inhibitor object associated with
|
||||
// the given surface for the given seat.
|
||||
//
|
||||
// If shortcuts are already inhibited for the specified seat and surface,
|
||||
// a protocol error "already_inhibited" is raised by the compositor.
|
||||
//
|
||||
// surface: the surface that inhibits the keyboard shortcuts behavior
|
||||
// seat: the wl_seat for which keyboard shortcuts should be disabled
|
||||
func (i *ZwpKeyboardShortcutsInhibitManagerV1) InhibitShortcuts(surface *client.Surface, seat *client.Seat) (*ZwpKeyboardShortcutsInhibitorV1, error) {
|
||||
id := NewZwpKeyboardShortcutsInhibitorV1(i.Context())
|
||||
const opcode = 1
|
||||
const _reqBufLen = 8 + 4 + 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], surface.ID())
|
||||
l += 4
|
||||
client.PutUint32(_reqBuf[l:l+4], seat.ID())
|
||||
l += 4
|
||||
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
||||
return id, err
|
||||
}
|
||||
|
||||
type ZwpKeyboardShortcutsInhibitManagerV1Error uint32
|
||||
|
||||
// ZwpKeyboardShortcutsInhibitManagerV1Error :
|
||||
const (
|
||||
// ZwpKeyboardShortcutsInhibitManagerV1ErrorAlreadyInhibited : the shortcuts are already inhibited for this surface
|
||||
ZwpKeyboardShortcutsInhibitManagerV1ErrorAlreadyInhibited ZwpKeyboardShortcutsInhibitManagerV1Error = 0
|
||||
)
|
||||
|
||||
func (e ZwpKeyboardShortcutsInhibitManagerV1Error) Name() string {
|
||||
switch e {
|
||||
case ZwpKeyboardShortcutsInhibitManagerV1ErrorAlreadyInhibited:
|
||||
return "already_inhibited"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (e ZwpKeyboardShortcutsInhibitManagerV1Error) Value() string {
|
||||
switch e {
|
||||
case ZwpKeyboardShortcutsInhibitManagerV1ErrorAlreadyInhibited:
|
||||
return "0"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (e ZwpKeyboardShortcutsInhibitManagerV1Error) String() string {
|
||||
return e.Name() + "=" + e.Value()
|
||||
}
|
||||
|
||||
// ZwpKeyboardShortcutsInhibitorV1InterfaceName 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 ZwpKeyboardShortcutsInhibitorV1InterfaceName = "zwp_keyboard_shortcuts_inhibitor_v1"
|
||||
|
||||
// ZwpKeyboardShortcutsInhibitorV1 : context object for keyboard shortcuts inhibitor
|
||||
//
|
||||
// A keyboard shortcuts inhibitor instructs the compositor to ignore
|
||||
// its own keyboard shortcuts when the associated surface has keyboard
|
||||
// focus. As a result, when the surface has keyboard focus on the given
|
||||
// seat, it will receive all key events originating from the specified
|
||||
// seat, even those which would normally be caught by the compositor for
|
||||
// its own shortcuts.
|
||||
//
|
||||
// The Wayland compositor is however under no obligation to disable
|
||||
// all of its shortcuts, and may keep some special key combo for its own
|
||||
// use, including but not limited to one allowing the user to forcibly
|
||||
// restore normal keyboard events routing in the case of an unwilling
|
||||
// client. The compositor may also use the same key combo to reactivate
|
||||
// an existing shortcut inhibitor that was previously deactivated on
|
||||
// user request.
|
||||
//
|
||||
// When the compositor restores its own keyboard shortcuts, an
|
||||
// "inactive" event is emitted to notify the client that the keyboard
|
||||
// shortcuts inhibitor is not effectively active for the surface and
|
||||
// seat any more, and the client should not expect to receive all
|
||||
// keyboard events.
|
||||
//
|
||||
// When the keyboard shortcuts inhibitor is inactive, the client has
|
||||
// no way to forcibly reactivate the keyboard shortcuts inhibitor.
|
||||
//
|
||||
// The user can chose to re-enable a previously deactivated keyboard
|
||||
// shortcuts inhibitor using any mechanism the compositor may offer,
|
||||
// in which case the compositor will send an "active" event to notify
|
||||
// the client.
|
||||
//
|
||||
// If the surface is destroyed, unmapped, or loses the seat's keyboard
|
||||
// focus, the keyboard shortcuts inhibitor becomes irrelevant and the
|
||||
// compositor will restore its own keyboard shortcuts but no "inactive"
|
||||
// event is emitted in this case.
|
||||
type ZwpKeyboardShortcutsInhibitorV1 struct {
|
||||
client.BaseProxy
|
||||
activeHandler ZwpKeyboardShortcutsInhibitorV1ActiveHandlerFunc
|
||||
inactiveHandler ZwpKeyboardShortcutsInhibitorV1InactiveHandlerFunc
|
||||
}
|
||||
|
||||
// NewZwpKeyboardShortcutsInhibitorV1 : context object for keyboard shortcuts inhibitor
|
||||
//
|
||||
// A keyboard shortcuts inhibitor instructs the compositor to ignore
|
||||
// its own keyboard shortcuts when the associated surface has keyboard
|
||||
// focus. As a result, when the surface has keyboard focus on the given
|
||||
// seat, it will receive all key events originating from the specified
|
||||
// seat, even those which would normally be caught by the compositor for
|
||||
// its own shortcuts.
|
||||
//
|
||||
// The Wayland compositor is however under no obligation to disable
|
||||
// all of its shortcuts, and may keep some special key combo for its own
|
||||
// use, including but not limited to one allowing the user to forcibly
|
||||
// restore normal keyboard events routing in the case of an unwilling
|
||||
// client. The compositor may also use the same key combo to reactivate
|
||||
// an existing shortcut inhibitor that was previously deactivated on
|
||||
// user request.
|
||||
//
|
||||
// When the compositor restores its own keyboard shortcuts, an
|
||||
// "inactive" event is emitted to notify the client that the keyboard
|
||||
// shortcuts inhibitor is not effectively active for the surface and
|
||||
// seat any more, and the client should not expect to receive all
|
||||
// keyboard events.
|
||||
//
|
||||
// When the keyboard shortcuts inhibitor is inactive, the client has
|
||||
// no way to forcibly reactivate the keyboard shortcuts inhibitor.
|
||||
//
|
||||
// The user can chose to re-enable a previously deactivated keyboard
|
||||
// shortcuts inhibitor using any mechanism the compositor may offer,
|
||||
// in which case the compositor will send an "active" event to notify
|
||||
// the client.
|
||||
//
|
||||
// If the surface is destroyed, unmapped, or loses the seat's keyboard
|
||||
// focus, the keyboard shortcuts inhibitor becomes irrelevant and the
|
||||
// compositor will restore its own keyboard shortcuts but no "inactive"
|
||||
// event is emitted in this case.
|
||||
func NewZwpKeyboardShortcutsInhibitorV1(ctx *client.Context) *ZwpKeyboardShortcutsInhibitorV1 {
|
||||
zwpKeyboardShortcutsInhibitorV1 := &ZwpKeyboardShortcutsInhibitorV1{}
|
||||
ctx.Register(zwpKeyboardShortcutsInhibitorV1)
|
||||
return zwpKeyboardShortcutsInhibitorV1
|
||||
}
|
||||
|
||||
// Destroy : destroy the keyboard shortcuts inhibitor object
|
||||
//
|
||||
// Remove the keyboard shortcuts inhibitor from the associated wl_surface.
|
||||
func (i *ZwpKeyboardShortcutsInhibitorV1) Destroy() error {
|
||||
defer i.Context().Unregister(i)
|
||||
const opcode = 0
|
||||
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
|
||||
}
|
||||
|
||||
// ZwpKeyboardShortcutsInhibitorV1ActiveEvent : shortcuts are inhibited
|
||||
//
|
||||
// This event indicates that the shortcut inhibitor is active.
|
||||
//
|
||||
// The compositor sends this event every time compositor shortcuts
|
||||
// are inhibited on behalf of the surface. When active, the client
|
||||
// may receive input events normally reserved by the compositor
|
||||
// (see zwp_keyboard_shortcuts_inhibitor_v1).
|
||||
//
|
||||
// This occurs typically when the initial request "inhibit_shortcuts"
|
||||
// first becomes active or when the user instructs the compositor to
|
||||
// re-enable and existing shortcuts inhibitor using any mechanism
|
||||
// offered by the compositor.
|
||||
type ZwpKeyboardShortcutsInhibitorV1ActiveEvent struct{}
|
||||
type ZwpKeyboardShortcutsInhibitorV1ActiveHandlerFunc func(ZwpKeyboardShortcutsInhibitorV1ActiveEvent)
|
||||
|
||||
// SetActiveHandler : sets handler for ZwpKeyboardShortcutsInhibitorV1ActiveEvent
|
||||
func (i *ZwpKeyboardShortcutsInhibitorV1) SetActiveHandler(f ZwpKeyboardShortcutsInhibitorV1ActiveHandlerFunc) {
|
||||
i.activeHandler = f
|
||||
}
|
||||
|
||||
// ZwpKeyboardShortcutsInhibitorV1InactiveEvent : shortcuts are restored
|
||||
//
|
||||
// This event indicates that the shortcuts inhibitor is inactive,
|
||||
// normal shortcuts processing is restored by the compositor.
|
||||
type ZwpKeyboardShortcutsInhibitorV1InactiveEvent struct{}
|
||||
type ZwpKeyboardShortcutsInhibitorV1InactiveHandlerFunc func(ZwpKeyboardShortcutsInhibitorV1InactiveEvent)
|
||||
|
||||
// SetInactiveHandler : sets handler for ZwpKeyboardShortcutsInhibitorV1InactiveEvent
|
||||
func (i *ZwpKeyboardShortcutsInhibitorV1) SetInactiveHandler(f ZwpKeyboardShortcutsInhibitorV1InactiveHandlerFunc) {
|
||||
i.inactiveHandler = f
|
||||
}
|
||||
|
||||
func (i *ZwpKeyboardShortcutsInhibitorV1) Dispatch(opcode uint32, fd int, data []byte) {
|
||||
switch opcode {
|
||||
case 0:
|
||||
if i.activeHandler == nil {
|
||||
return
|
||||
}
|
||||
var e ZwpKeyboardShortcutsInhibitorV1ActiveEvent
|
||||
|
||||
i.activeHandler(e)
|
||||
case 1:
|
||||
if i.inactiveHandler == nil {
|
||||
return
|
||||
}
|
||||
var e ZwpKeyboardShortcutsInhibitorV1InactiveEvent
|
||||
|
||||
i.inactiveHandler(e)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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
|
||||
//
|
||||
// wlr_gamma_control_unstable_v1 Protocol Copyright:
|
||||
@@ -31,7 +31,7 @@
|
||||
package wlr_gamma_control
|
||||
|
||||
import (
|
||||
"github.com/yaslama/go-wayland/wayland/client"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
|
||||
792
core/internal/proto/wlr_layer_shell/layer_shell.go
Normal file
792
core/internal/proto/wlr_layer_shell/layer_shell.go
Normal file
@@ -0,0 +1,792 @@
|
||||
// Generated by go-wayland-scanner
|
||||
// https://github.com/yaslama/go-wayland/cmd/go-wayland-scanner
|
||||
// XML file : internal/proto/xml/wlr-layer-shell-unstable-v1.xml
|
||||
//
|
||||
// wlr_layer_shell_unstable_v1 Protocol Copyright:
|
||||
//
|
||||
// Copyright © 2017 Drew DeVault
|
||||
//
|
||||
// Permission to use, copy, modify, distribute, and sell this
|
||||
// software and its documentation for any purpose is hereby granted
|
||||
// without fee, provided that the above copyright notice appear in
|
||||
// all copies and that both that copyright notice and this permission
|
||||
// notice appear in supporting documentation, and that the name of
|
||||
// the copyright holders not be used in advertising or publicity
|
||||
// pertaining to distribution of the software without specific,
|
||||
// written prior permission. The copyright holders make no
|
||||
// representations about the suitability of this software for any
|
||||
// purpose. It is provided "as is" without express or implied
|
||||
// warranty.
|
||||
//
|
||||
// THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
|
||||
// SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
// FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
// SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||
// ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
// THIS SOFTWARE.
|
||||
|
||||
package wlr_layer_shell
|
||||
|
||||
import (
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
||||
xdg_shell "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/stable/xdg-shell"
|
||||
)
|
||||
|
||||
// ZwlrLayerShellV1InterfaceName 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 ZwlrLayerShellV1InterfaceName = "zwlr_layer_shell_v1"
|
||||
|
||||
// ZwlrLayerShellV1 : create surfaces that are layers of the desktop
|
||||
//
|
||||
// Clients can use this interface to assign the surface_layer role to
|
||||
// wl_surfaces. Such surfaces are assigned to a "layer" of the output and
|
||||
// rendered with a defined z-depth respective to each other. They may also be
|
||||
// anchored to the edges and corners of a screen and specify input handling
|
||||
// semantics. This interface should be suitable for the implementation of
|
||||
// many desktop shell components, and a broad number of other applications
|
||||
// that interact with the desktop.
|
||||
type ZwlrLayerShellV1 struct {
|
||||
client.BaseProxy
|
||||
}
|
||||
|
||||
// NewZwlrLayerShellV1 : create surfaces that are layers of the desktop
|
||||
//
|
||||
// Clients can use this interface to assign the surface_layer role to
|
||||
// wl_surfaces. Such surfaces are assigned to a "layer" of the output and
|
||||
// rendered with a defined z-depth respective to each other. They may also be
|
||||
// anchored to the edges and corners of a screen and specify input handling
|
||||
// semantics. This interface should be suitable for the implementation of
|
||||
// many desktop shell components, and a broad number of other applications
|
||||
// that interact with the desktop.
|
||||
func NewZwlrLayerShellV1(ctx *client.Context) *ZwlrLayerShellV1 {
|
||||
zwlrLayerShellV1 := &ZwlrLayerShellV1{}
|
||||
ctx.Register(zwlrLayerShellV1)
|
||||
return zwlrLayerShellV1
|
||||
}
|
||||
|
||||
// GetLayerSurface : create a layer_surface from a surface
|
||||
//
|
||||
// Create a layer surface for an existing surface. This assigns the role of
|
||||
// layer_surface, or raises a protocol error if another role is already
|
||||
// assigned.
|
||||
//
|
||||
// Creating a layer surface from a wl_surface which has a buffer attached
|
||||
// or committed is a client error, and any attempts by a client to attach
|
||||
// or manipulate a buffer prior to the first layer_surface.configure call
|
||||
// must also be treated as errors.
|
||||
//
|
||||
// After creating a layer_surface object and setting it up, the client
|
||||
// must perform an initial commit without any buffer attached.
|
||||
// The compositor will reply with a layer_surface.configure event.
|
||||
// The client must acknowledge it and is then allowed to attach a buffer
|
||||
// to map the surface.
|
||||
//
|
||||
// You may pass NULL for output to allow the compositor to decide which
|
||||
// output to use. Generally this will be the one that the user most
|
||||
// recently interacted with.
|
||||
//
|
||||
// Clients can specify a namespace that defines the purpose of the layer
|
||||
// surface.
|
||||
//
|
||||
// layer: layer to add this surface to
|
||||
// namespace: namespace for the layer surface
|
||||
func (i *ZwlrLayerShellV1) GetLayerSurface(surface *client.Surface, output *client.Output, layer uint32, namespace string) (*ZwlrLayerSurfaceV1, error) {
|
||||
id := NewZwlrLayerSurfaceV1(i.Context())
|
||||
const opcode = 0
|
||||
namespaceLen := client.PaddedLen(len(namespace) + 1)
|
||||
_reqBufLen := 8 + 4 + 4 + 4 + 4 + (4 + namespaceLen)
|
||||
_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.PutUint32(_reqBuf[l:l+4], id.ID())
|
||||
l += 4
|
||||
client.PutUint32(_reqBuf[l:l+4], surface.ID())
|
||||
l += 4
|
||||
if output == nil {
|
||||
client.PutUint32(_reqBuf[l:l+4], 0)
|
||||
l += 4
|
||||
} else {
|
||||
client.PutUint32(_reqBuf[l:l+4], output.ID())
|
||||
l += 4
|
||||
}
|
||||
client.PutUint32(_reqBuf[l:l+4], uint32(layer))
|
||||
l += 4
|
||||
client.PutString(_reqBuf[l:l+(4+namespaceLen)], namespace)
|
||||
l += (4 + namespaceLen)
|
||||
err := i.Context().WriteMsg(_reqBuf, nil)
|
||||
return id, err
|
||||
}
|
||||
|
||||
// Destroy : destroy the layer_shell object
|
||||
//
|
||||
// This request indicates that the client will not use the layer_shell
|
||||
// object any more. Objects that have been created through this instance
|
||||
// are not affected.
|
||||
func (i *ZwlrLayerShellV1) 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 ZwlrLayerShellV1Error uint32
|
||||
|
||||
// ZwlrLayerShellV1Error :
|
||||
const (
|
||||
// ZwlrLayerShellV1ErrorRole : wl_surface has another role
|
||||
ZwlrLayerShellV1ErrorRole ZwlrLayerShellV1Error = 0
|
||||
// ZwlrLayerShellV1ErrorInvalidLayer : layer value is invalid
|
||||
ZwlrLayerShellV1ErrorInvalidLayer ZwlrLayerShellV1Error = 1
|
||||
// ZwlrLayerShellV1ErrorAlreadyConstructed : wl_surface has a buffer attached or committed
|
||||
ZwlrLayerShellV1ErrorAlreadyConstructed ZwlrLayerShellV1Error = 2
|
||||
)
|
||||
|
||||
func (e ZwlrLayerShellV1Error) Name() string {
|
||||
switch e {
|
||||
case ZwlrLayerShellV1ErrorRole:
|
||||
return "role"
|
||||
case ZwlrLayerShellV1ErrorInvalidLayer:
|
||||
return "invalid_layer"
|
||||
case ZwlrLayerShellV1ErrorAlreadyConstructed:
|
||||
return "already_constructed"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (e ZwlrLayerShellV1Error) Value() string {
|
||||
switch e {
|
||||
case ZwlrLayerShellV1ErrorRole:
|
||||
return "0"
|
||||
case ZwlrLayerShellV1ErrorInvalidLayer:
|
||||
return "1"
|
||||
case ZwlrLayerShellV1ErrorAlreadyConstructed:
|
||||
return "2"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (e ZwlrLayerShellV1Error) String() string {
|
||||
return e.Name() + "=" + e.Value()
|
||||
}
|
||||
|
||||
type ZwlrLayerShellV1Layer uint32
|
||||
|
||||
// ZwlrLayerShellV1Layer : available layers for surfaces
|
||||
//
|
||||
// These values indicate which layers a surface can be rendered in. They
|
||||
// are ordered by z depth, bottom-most first. Traditional shell surfaces
|
||||
// will typically be rendered between the bottom and top layers.
|
||||
// Fullscreen shell surfaces are typically rendered at the top layer.
|
||||
// Multiple surfaces can share a single layer, and ordering within a
|
||||
// single layer is undefined.
|
||||
const (
|
||||
ZwlrLayerShellV1LayerBackground ZwlrLayerShellV1Layer = 0
|
||||
ZwlrLayerShellV1LayerBottom ZwlrLayerShellV1Layer = 1
|
||||
ZwlrLayerShellV1LayerTop ZwlrLayerShellV1Layer = 2
|
||||
ZwlrLayerShellV1LayerOverlay ZwlrLayerShellV1Layer = 3
|
||||
)
|
||||
|
||||
func (e ZwlrLayerShellV1Layer) Name() string {
|
||||
switch e {
|
||||
case ZwlrLayerShellV1LayerBackground:
|
||||
return "background"
|
||||
case ZwlrLayerShellV1LayerBottom:
|
||||
return "bottom"
|
||||
case ZwlrLayerShellV1LayerTop:
|
||||
return "top"
|
||||
case ZwlrLayerShellV1LayerOverlay:
|
||||
return "overlay"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (e ZwlrLayerShellV1Layer) Value() string {
|
||||
switch e {
|
||||
case ZwlrLayerShellV1LayerBackground:
|
||||
return "0"
|
||||
case ZwlrLayerShellV1LayerBottom:
|
||||
return "1"
|
||||
case ZwlrLayerShellV1LayerTop:
|
||||
return "2"
|
||||
case ZwlrLayerShellV1LayerOverlay:
|
||||
return "3"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (e ZwlrLayerShellV1Layer) String() string {
|
||||
return e.Name() + "=" + e.Value()
|
||||
}
|
||||
|
||||
// ZwlrLayerSurfaceV1InterfaceName 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 ZwlrLayerSurfaceV1InterfaceName = "zwlr_layer_surface_v1"
|
||||
|
||||
// ZwlrLayerSurfaceV1 : layer metadata interface
|
||||
//
|
||||
// An interface that may be implemented by a wl_surface, for surfaces that
|
||||
// are designed to be rendered as a layer of a stacked desktop-like
|
||||
// environment.
|
||||
//
|
||||
// Layer surface state (layer, size, anchor, exclusive zone,
|
||||
// margin, interactivity) is double-buffered, and will be applied at the
|
||||
// time wl_surface.commit of the corresponding wl_surface is called.
|
||||
//
|
||||
// Attaching a null buffer to a layer surface unmaps it.
|
||||
//
|
||||
// Unmapping a layer_surface means that the surface cannot be shown by the
|
||||
// compositor until it is explicitly mapped again. The layer_surface
|
||||
// returns to the state it had right after layer_shell.get_layer_surface.
|
||||
// The client can re-map the surface by performing a commit without any
|
||||
// buffer attached, waiting for a configure event and handling it as usual.
|
||||
type ZwlrLayerSurfaceV1 struct {
|
||||
client.BaseProxy
|
||||
configureHandler ZwlrLayerSurfaceV1ConfigureHandlerFunc
|
||||
closedHandler ZwlrLayerSurfaceV1ClosedHandlerFunc
|
||||
}
|
||||
|
||||
// NewZwlrLayerSurfaceV1 : layer metadata interface
|
||||
//
|
||||
// An interface that may be implemented by a wl_surface, for surfaces that
|
||||
// are designed to be rendered as a layer of a stacked desktop-like
|
||||
// environment.
|
||||
//
|
||||
// Layer surface state (layer, size, anchor, exclusive zone,
|
||||
// margin, interactivity) is double-buffered, and will be applied at the
|
||||
// time wl_surface.commit of the corresponding wl_surface is called.
|
||||
//
|
||||
// Attaching a null buffer to a layer surface unmaps it.
|
||||
//
|
||||
// Unmapping a layer_surface means that the surface cannot be shown by the
|
||||
// compositor until it is explicitly mapped again. The layer_surface
|
||||
// returns to the state it had right after layer_shell.get_layer_surface.
|
||||
// The client can re-map the surface by performing a commit without any
|
||||
// buffer attached, waiting for a configure event and handling it as usual.
|
||||
func NewZwlrLayerSurfaceV1(ctx *client.Context) *ZwlrLayerSurfaceV1 {
|
||||
zwlrLayerSurfaceV1 := &ZwlrLayerSurfaceV1{}
|
||||
ctx.Register(zwlrLayerSurfaceV1)
|
||||
return zwlrLayerSurfaceV1
|
||||
}
|
||||
|
||||
// SetSize : sets the size of the surface
|
||||
//
|
||||
// Sets the size of the surface in surface-local coordinates. The
|
||||
// compositor will display the surface centered with respect to its
|
||||
// anchors.
|
||||
//
|
||||
// If you pass 0 for either value, the compositor will assign it and
|
||||
// inform you of the assignment in the configure event. You must set your
|
||||
// anchor to opposite edges in the dimensions you omit; not doing so is a
|
||||
// protocol error. Both values are 0 by default.
|
||||
//
|
||||
// Size is double-buffered, see wl_surface.commit.
|
||||
func (i *ZwlrLayerSurfaceV1) SetSize(width, height uint32) error {
|
||||
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], uint32(width))
|
||||
l += 4
|
||||
client.PutUint32(_reqBuf[l:l+4], uint32(height))
|
||||
l += 4
|
||||
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetAnchor : configures the anchor point of the surface
|
||||
//
|
||||
// Requests that the compositor anchor the surface to the specified edges
|
||||
// and corners. If two orthogonal edges are specified (e.g. 'top' and
|
||||
// 'left'), then the anchor point will be the intersection of the edges
|
||||
// (e.g. the top left corner of the output); otherwise the anchor point
|
||||
// will be centered on that edge, or in the center if none is specified.
|
||||
//
|
||||
// Anchor is double-buffered, see wl_surface.commit.
|
||||
func (i *ZwlrLayerSurfaceV1) SetAnchor(anchor uint32) error {
|
||||
const opcode = 1
|
||||
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(anchor))
|
||||
l += 4
|
||||
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetExclusiveZone : configures the exclusive geometry of this surface
|
||||
//
|
||||
// Requests that the compositor avoids occluding an area with other
|
||||
// surfaces. The compositor's use of this information is
|
||||
// implementation-dependent - do not assume that this region will not
|
||||
// actually be occluded.
|
||||
//
|
||||
// A positive value is only meaningful if the surface is anchored to one
|
||||
// edge or an edge and both perpendicular edges. If the surface is not
|
||||
// anchored, anchored to only two perpendicular edges (a corner), anchored
|
||||
// to only two parallel edges or anchored to all edges, a positive value
|
||||
// will be treated the same as zero.
|
||||
//
|
||||
// A positive zone is the distance from the edge in surface-local
|
||||
// coordinates to consider exclusive.
|
||||
//
|
||||
// Surfaces that do not wish to have an exclusive zone may instead specify
|
||||
// how they should interact with surfaces that do. If set to zero, the
|
||||
// surface indicates that it would like to be moved to avoid occluding
|
||||
// surfaces with a positive exclusive zone. If set to -1, the surface
|
||||
// indicates that it would not like to be moved to accommodate for other
|
||||
// surfaces, and the compositor should extend it all the way to the edges
|
||||
// it is anchored to.
|
||||
//
|
||||
// For example, a panel might set its exclusive zone to 10, so that
|
||||
// maximized shell surfaces are not shown on top of it. A notification
|
||||
// might set its exclusive zone to 0, so that it is moved to avoid
|
||||
// occluding the panel, but shell surfaces are shown underneath it. A
|
||||
// wallpaper or lock screen might set their exclusive zone to -1, so that
|
||||
// they stretch below or over the panel.
|
||||
//
|
||||
// The default value is 0.
|
||||
//
|
||||
// Exclusive zone is double-buffered, see wl_surface.commit.
|
||||
func (i *ZwlrLayerSurfaceV1) SetExclusiveZone(zone int32) error {
|
||||
const opcode = 2
|
||||
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(zone))
|
||||
l += 4
|
||||
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetMargin : sets a margin from the anchor point
|
||||
//
|
||||
// Requests that the surface be placed some distance away from the anchor
|
||||
// point on the output, in surface-local coordinates. Setting this value
|
||||
// for edges you are not anchored to has no effect.
|
||||
//
|
||||
// The exclusive zone includes the margin.
|
||||
//
|
||||
// Margin is double-buffered, see wl_surface.commit.
|
||||
func (i *ZwlrLayerSurfaceV1) SetMargin(top, right, bottom, left int32) error {
|
||||
const opcode = 3
|
||||
const _reqBufLen = 8 + 4 + 4 + 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], uint32(top))
|
||||
l += 4
|
||||
client.PutUint32(_reqBuf[l:l+4], uint32(right))
|
||||
l += 4
|
||||
client.PutUint32(_reqBuf[l:l+4], uint32(bottom))
|
||||
l += 4
|
||||
client.PutUint32(_reqBuf[l:l+4], uint32(left))
|
||||
l += 4
|
||||
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetKeyboardInteractivity : requests keyboard events
|
||||
//
|
||||
// Set how keyboard events are delivered to this surface. By default,
|
||||
// layer shell surfaces do not receive keyboard events; this request can
|
||||
// be used to change this.
|
||||
//
|
||||
// This setting is inherited by child surfaces set by the get_popup
|
||||
// request.
|
||||
//
|
||||
// Layer surfaces receive pointer, touch, and tablet events normally. If
|
||||
// you do not want to receive them, set the input region on your surface
|
||||
// to an empty region.
|
||||
//
|
||||
// Keyboard interactivity is double-buffered, see wl_surface.commit.
|
||||
func (i *ZwlrLayerSurfaceV1) SetKeyboardInteractivity(keyboardInteractivity uint32) error {
|
||||
const opcode = 4
|
||||
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(keyboardInteractivity))
|
||||
l += 4
|
||||
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetPopup : assign this layer_surface as an xdg_popup parent
|
||||
//
|
||||
// This assigns an xdg_popup's parent to this layer_surface. This popup
|
||||
// should have been created via xdg_surface::get_popup with the parent set
|
||||
// to NULL, and this request must be invoked before committing the popup's
|
||||
// initial state.
|
||||
//
|
||||
// See the documentation of xdg_popup for more details about what an
|
||||
// xdg_popup is and how it is used.
|
||||
func (i *ZwlrLayerSurfaceV1) GetPopup(popup *xdg_shell.Popup) error {
|
||||
const opcode = 5
|
||||
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], popup.ID())
|
||||
l += 4
|
||||
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// AckConfigure : ack a configure event
|
||||
//
|
||||
// When a configure event is received, if a client commits the
|
||||
// surface in response to the configure event, then the client
|
||||
// must make an ack_configure request sometime before the commit
|
||||
// request, passing along the serial of the configure event.
|
||||
//
|
||||
// If the client receives multiple configure events before it
|
||||
// can respond to one, it only has to ack the last configure event.
|
||||
//
|
||||
// A client is not required to commit immediately after sending
|
||||
// an ack_configure request - it may even ack_configure several times
|
||||
// before its next surface commit.
|
||||
//
|
||||
// A client may send multiple ack_configure requests before committing, but
|
||||
// only the last request sent before a commit indicates which configure
|
||||
// event the client really is responding to.
|
||||
//
|
||||
// serial: the serial from the configure event
|
||||
func (i *ZwlrLayerSurfaceV1) AckConfigure(serial uint32) error {
|
||||
const opcode = 6
|
||||
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(serial))
|
||||
l += 4
|
||||
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// Destroy : destroy the layer_surface
|
||||
//
|
||||
// This request destroys the layer surface.
|
||||
func (i *ZwlrLayerSurfaceV1) Destroy() error {
|
||||
defer i.Context().Unregister(i)
|
||||
const opcode = 7
|
||||
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
|
||||
}
|
||||
|
||||
// SetLayer : change the layer of the surface
|
||||
//
|
||||
// Change the layer that the surface is rendered on.
|
||||
//
|
||||
// Layer is double-buffered, see wl_surface.commit.
|
||||
//
|
||||
// layer: layer to move this surface to
|
||||
func (i *ZwlrLayerSurfaceV1) SetLayer(layer uint32) error {
|
||||
const opcode = 8
|
||||
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(layer))
|
||||
l += 4
|
||||
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetExclusiveEdge : set the edge the exclusive zone will be applied to
|
||||
//
|
||||
// Requests an edge for the exclusive zone to apply. The exclusive
|
||||
// edge will be automatically deduced from anchor points when possible,
|
||||
// but when the surface is anchored to a corner, it will be necessary
|
||||
// to set it explicitly to disambiguate, as it is not possible to deduce
|
||||
// which one of the two corner edges should be used.
|
||||
//
|
||||
// The edge must be one the surface is anchored to, otherwise the
|
||||
// invalid_exclusive_edge protocol error will be raised.
|
||||
func (i *ZwlrLayerSurfaceV1) SetExclusiveEdge(edge uint32) error {
|
||||
const opcode = 9
|
||||
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(edge))
|
||||
l += 4
|
||||
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
||||
return err
|
||||
}
|
||||
|
||||
type ZwlrLayerSurfaceV1KeyboardInteractivity uint32
|
||||
|
||||
// ZwlrLayerSurfaceV1KeyboardInteractivity : types of keyboard interaction possible for a layer shell surface
|
||||
//
|
||||
// Types of keyboard interaction possible for layer shell surfaces. The
|
||||
// rationale for this is twofold: (1) some applications are not interested
|
||||
// in keyboard events and not allowing them to be focused can improve the
|
||||
// desktop experience; (2) some applications will want to take exclusive
|
||||
// keyboard focus.
|
||||
const (
|
||||
ZwlrLayerSurfaceV1KeyboardInteractivityNone ZwlrLayerSurfaceV1KeyboardInteractivity = 0
|
||||
ZwlrLayerSurfaceV1KeyboardInteractivityExclusive ZwlrLayerSurfaceV1KeyboardInteractivity = 1
|
||||
ZwlrLayerSurfaceV1KeyboardInteractivityOnDemand ZwlrLayerSurfaceV1KeyboardInteractivity = 2
|
||||
)
|
||||
|
||||
func (e ZwlrLayerSurfaceV1KeyboardInteractivity) Name() string {
|
||||
switch e {
|
||||
case ZwlrLayerSurfaceV1KeyboardInteractivityNone:
|
||||
return "none"
|
||||
case ZwlrLayerSurfaceV1KeyboardInteractivityExclusive:
|
||||
return "exclusive"
|
||||
case ZwlrLayerSurfaceV1KeyboardInteractivityOnDemand:
|
||||
return "on_demand"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (e ZwlrLayerSurfaceV1KeyboardInteractivity) Value() string {
|
||||
switch e {
|
||||
case ZwlrLayerSurfaceV1KeyboardInteractivityNone:
|
||||
return "0"
|
||||
case ZwlrLayerSurfaceV1KeyboardInteractivityExclusive:
|
||||
return "1"
|
||||
case ZwlrLayerSurfaceV1KeyboardInteractivityOnDemand:
|
||||
return "2"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (e ZwlrLayerSurfaceV1KeyboardInteractivity) String() string {
|
||||
return e.Name() + "=" + e.Value()
|
||||
}
|
||||
|
||||
type ZwlrLayerSurfaceV1Error uint32
|
||||
|
||||
// ZwlrLayerSurfaceV1Error :
|
||||
const (
|
||||
// ZwlrLayerSurfaceV1ErrorInvalidSurfaceState : provided surface state is invalid
|
||||
ZwlrLayerSurfaceV1ErrorInvalidSurfaceState ZwlrLayerSurfaceV1Error = 0
|
||||
// ZwlrLayerSurfaceV1ErrorInvalidSize : size is invalid
|
||||
ZwlrLayerSurfaceV1ErrorInvalidSize ZwlrLayerSurfaceV1Error = 1
|
||||
// ZwlrLayerSurfaceV1ErrorInvalidAnchor : anchor bitfield is invalid
|
||||
ZwlrLayerSurfaceV1ErrorInvalidAnchor ZwlrLayerSurfaceV1Error = 2
|
||||
// ZwlrLayerSurfaceV1ErrorInvalidKeyboardInteractivity : keyboard interactivity is invalid
|
||||
ZwlrLayerSurfaceV1ErrorInvalidKeyboardInteractivity ZwlrLayerSurfaceV1Error = 3
|
||||
// ZwlrLayerSurfaceV1ErrorInvalidExclusiveEdge : exclusive edge is invalid given the surface anchors
|
||||
ZwlrLayerSurfaceV1ErrorInvalidExclusiveEdge ZwlrLayerSurfaceV1Error = 4
|
||||
)
|
||||
|
||||
func (e ZwlrLayerSurfaceV1Error) Name() string {
|
||||
switch e {
|
||||
case ZwlrLayerSurfaceV1ErrorInvalidSurfaceState:
|
||||
return "invalid_surface_state"
|
||||
case ZwlrLayerSurfaceV1ErrorInvalidSize:
|
||||
return "invalid_size"
|
||||
case ZwlrLayerSurfaceV1ErrorInvalidAnchor:
|
||||
return "invalid_anchor"
|
||||
case ZwlrLayerSurfaceV1ErrorInvalidKeyboardInteractivity:
|
||||
return "invalid_keyboard_interactivity"
|
||||
case ZwlrLayerSurfaceV1ErrorInvalidExclusiveEdge:
|
||||
return "invalid_exclusive_edge"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (e ZwlrLayerSurfaceV1Error) Value() string {
|
||||
switch e {
|
||||
case ZwlrLayerSurfaceV1ErrorInvalidSurfaceState:
|
||||
return "0"
|
||||
case ZwlrLayerSurfaceV1ErrorInvalidSize:
|
||||
return "1"
|
||||
case ZwlrLayerSurfaceV1ErrorInvalidAnchor:
|
||||
return "2"
|
||||
case ZwlrLayerSurfaceV1ErrorInvalidKeyboardInteractivity:
|
||||
return "3"
|
||||
case ZwlrLayerSurfaceV1ErrorInvalidExclusiveEdge:
|
||||
return "4"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (e ZwlrLayerSurfaceV1Error) String() string {
|
||||
return e.Name() + "=" + e.Value()
|
||||
}
|
||||
|
||||
type ZwlrLayerSurfaceV1Anchor uint32
|
||||
|
||||
// ZwlrLayerSurfaceV1Anchor :
|
||||
const (
|
||||
// ZwlrLayerSurfaceV1AnchorTop : the top edge of the anchor rectangle
|
||||
ZwlrLayerSurfaceV1AnchorTop ZwlrLayerSurfaceV1Anchor = 1
|
||||
// ZwlrLayerSurfaceV1AnchorBottom : the bottom edge of the anchor rectangle
|
||||
ZwlrLayerSurfaceV1AnchorBottom ZwlrLayerSurfaceV1Anchor = 2
|
||||
// ZwlrLayerSurfaceV1AnchorLeft : the left edge of the anchor rectangle
|
||||
ZwlrLayerSurfaceV1AnchorLeft ZwlrLayerSurfaceV1Anchor = 4
|
||||
// ZwlrLayerSurfaceV1AnchorRight : the right edge of the anchor rectangle
|
||||
ZwlrLayerSurfaceV1AnchorRight ZwlrLayerSurfaceV1Anchor = 8
|
||||
)
|
||||
|
||||
func (e ZwlrLayerSurfaceV1Anchor) Name() string {
|
||||
switch e {
|
||||
case ZwlrLayerSurfaceV1AnchorTop:
|
||||
return "top"
|
||||
case ZwlrLayerSurfaceV1AnchorBottom:
|
||||
return "bottom"
|
||||
case ZwlrLayerSurfaceV1AnchorLeft:
|
||||
return "left"
|
||||
case ZwlrLayerSurfaceV1AnchorRight:
|
||||
return "right"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (e ZwlrLayerSurfaceV1Anchor) Value() string {
|
||||
switch e {
|
||||
case ZwlrLayerSurfaceV1AnchorTop:
|
||||
return "1"
|
||||
case ZwlrLayerSurfaceV1AnchorBottom:
|
||||
return "2"
|
||||
case ZwlrLayerSurfaceV1AnchorLeft:
|
||||
return "4"
|
||||
case ZwlrLayerSurfaceV1AnchorRight:
|
||||
return "8"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (e ZwlrLayerSurfaceV1Anchor) String() string {
|
||||
return e.Name() + "=" + e.Value()
|
||||
}
|
||||
|
||||
// ZwlrLayerSurfaceV1ConfigureEvent : suggest a surface change
|
||||
//
|
||||
// The configure event asks the client to resize its surface.
|
||||
//
|
||||
// Clients should arrange their surface for the new states, and then send
|
||||
// an ack_configure request with the serial sent in this configure event at
|
||||
// some point before committing the new surface.
|
||||
//
|
||||
// The client is free to dismiss all but the last configure event it
|
||||
// received.
|
||||
//
|
||||
// The width and height arguments specify the size of the window in
|
||||
// surface-local coordinates.
|
||||
//
|
||||
// The size is a hint, in the sense that the client is free to ignore it if
|
||||
// it doesn't resize, pick a smaller size (to satisfy aspect ratio or
|
||||
// resize in steps of NxM pixels). If the client picks a smaller size and
|
||||
// is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the
|
||||
// surface will be centered on this axis.
|
||||
//
|
||||
// If the width or height arguments are zero, it means the client should
|
||||
// decide its own window dimension.
|
||||
type ZwlrLayerSurfaceV1ConfigureEvent struct {
|
||||
Serial uint32
|
||||
Width uint32
|
||||
Height uint32
|
||||
}
|
||||
type ZwlrLayerSurfaceV1ConfigureHandlerFunc func(ZwlrLayerSurfaceV1ConfigureEvent)
|
||||
|
||||
// SetConfigureHandler : sets handler for ZwlrLayerSurfaceV1ConfigureEvent
|
||||
func (i *ZwlrLayerSurfaceV1) SetConfigureHandler(f ZwlrLayerSurfaceV1ConfigureHandlerFunc) {
|
||||
i.configureHandler = f
|
||||
}
|
||||
|
||||
// ZwlrLayerSurfaceV1ClosedEvent : surface should be closed
|
||||
//
|
||||
// The closed event is sent by the compositor when the surface will no
|
||||
// longer be shown. The output may have been destroyed or the user may
|
||||
// have asked for it to be removed. Further changes to the surface will be
|
||||
// ignored. The client should destroy the resource after receiving this
|
||||
// event, and create a new surface if they so choose.
|
||||
type ZwlrLayerSurfaceV1ClosedEvent struct{}
|
||||
type ZwlrLayerSurfaceV1ClosedHandlerFunc func(ZwlrLayerSurfaceV1ClosedEvent)
|
||||
|
||||
// SetClosedHandler : sets handler for ZwlrLayerSurfaceV1ClosedEvent
|
||||
func (i *ZwlrLayerSurfaceV1) SetClosedHandler(f ZwlrLayerSurfaceV1ClosedHandlerFunc) {
|
||||
i.closedHandler = f
|
||||
}
|
||||
|
||||
func (i *ZwlrLayerSurfaceV1) Dispatch(opcode uint32, fd int, data []byte) {
|
||||
switch opcode {
|
||||
case 0:
|
||||
if i.configureHandler == nil {
|
||||
return
|
||||
}
|
||||
var e ZwlrLayerSurfaceV1ConfigureEvent
|
||||
l := 0
|
||||
e.Serial = client.Uint32(data[l : l+4])
|
||||
l += 4
|
||||
e.Width = client.Uint32(data[l : l+4])
|
||||
l += 4
|
||||
e.Height = client.Uint32(data[l : l+4])
|
||||
l += 4
|
||||
|
||||
i.configureHandler(e)
|
||||
case 1:
|
||||
if i.closedHandler == nil {
|
||||
return
|
||||
}
|
||||
var e ZwlrLayerSurfaceV1ClosedEvent
|
||||
|
||||
i.closedHandler(e)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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
|
||||
//
|
||||
// wlr_output_management_unstable_v1 Protocol Copyright:
|
||||
@@ -33,7 +33,8 @@ import (
|
||||
"reflect"
|
||||
"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) {
|
||||
@@ -47,9 +48,9 @@ func registerServerProxy(ctx *client.Context, proxy client.Proxy, serverID uint3
|
||||
if !objectsField.IsValid() {
|
||||
return
|
||||
}
|
||||
objectsField = reflect.NewAt(objectsField.Type(), unsafe.Pointer(objectsField.UnsafeAddr())).Elem()
|
||||
objectsMap := objectsField.Interface().(map[uint32]client.Proxy)
|
||||
objectsMap[serverID] = proxy
|
||||
objectsMapPtr := unsafe.Pointer(objectsField.UnsafeAddr())
|
||||
objectsMap := (*syncmap.Map[uint32, client.Proxy])(objectsMapPtr)
|
||||
objectsMap.Store(serverID, proxy)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
532
core/internal/proto/wlr_screencopy/screencopy.go
Normal file
532
core/internal/proto/wlr_screencopy/screencopy.go
Normal file
@@ -0,0 +1,532 @@
|
||||
// Generated by go-wayland-scanner
|
||||
// https://github.com/yaslama/go-wayland/cmd/go-wayland-scanner
|
||||
// XML file : internal/proto/xml/wlr-screencopy-unstable-v1.xml
|
||||
//
|
||||
// wlr_screencopy_unstable_v1 Protocol Copyright:
|
||||
//
|
||||
// Copyright © 2018 Simon Ser
|
||||
// Copyright © 2019 Andri Yngvason
|
||||
//
|
||||
// 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_screencopy
|
||||
|
||||
import "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
||||
|
||||
// ZwlrScreencopyManagerV1InterfaceName 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 ZwlrScreencopyManagerV1InterfaceName = "zwlr_screencopy_manager_v1"
|
||||
|
||||
// ZwlrScreencopyManagerV1 : manager to inform clients and begin capturing
|
||||
//
|
||||
// This object is a manager which offers requests to start capturing from a
|
||||
// source.
|
||||
type ZwlrScreencopyManagerV1 struct {
|
||||
client.BaseProxy
|
||||
}
|
||||
|
||||
// NewZwlrScreencopyManagerV1 : manager to inform clients and begin capturing
|
||||
//
|
||||
// This object is a manager which offers requests to start capturing from a
|
||||
// source.
|
||||
func NewZwlrScreencopyManagerV1(ctx *client.Context) *ZwlrScreencopyManagerV1 {
|
||||
zwlrScreencopyManagerV1 := &ZwlrScreencopyManagerV1{}
|
||||
ctx.Register(zwlrScreencopyManagerV1)
|
||||
return zwlrScreencopyManagerV1
|
||||
}
|
||||
|
||||
// CaptureOutput : capture an output
|
||||
//
|
||||
// Capture the next frame of an entire output.
|
||||
//
|
||||
// overlayCursor: composite cursor onto the frame
|
||||
func (i *ZwlrScreencopyManagerV1) CaptureOutput(overlayCursor int32, output *client.Output) (*ZwlrScreencopyFrameV1, error) {
|
||||
frame := NewZwlrScreencopyFrameV1(i.Context())
|
||||
const opcode = 0
|
||||
const _reqBufLen = 8 + 4 + 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], frame.ID())
|
||||
l += 4
|
||||
client.PutUint32(_reqBuf[l:l+4], uint32(overlayCursor))
|
||||
l += 4
|
||||
client.PutUint32(_reqBuf[l:l+4], output.ID())
|
||||
l += 4
|
||||
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
||||
return frame, err
|
||||
}
|
||||
|
||||
// CaptureOutputRegion : capture an output's region
|
||||
//
|
||||
// Capture the next frame of an output's region.
|
||||
//
|
||||
// The region is given in output logical coordinates, see
|
||||
// xdg_output.logical_size. The region will be clipped to the output's
|
||||
// extents.
|
||||
//
|
||||
// overlayCursor: composite cursor onto the frame
|
||||
func (i *ZwlrScreencopyManagerV1) CaptureOutputRegion(overlayCursor int32, output *client.Output, x, y, width, height int32) (*ZwlrScreencopyFrameV1, error) {
|
||||
frame := NewZwlrScreencopyFrameV1(i.Context())
|
||||
const opcode = 1
|
||||
const _reqBufLen = 8 + 4 + 4 + 4 + 4 + 4 + 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], frame.ID())
|
||||
l += 4
|
||||
client.PutUint32(_reqBuf[l:l+4], uint32(overlayCursor))
|
||||
l += 4
|
||||
client.PutUint32(_reqBuf[l:l+4], output.ID())
|
||||
l += 4
|
||||
client.PutUint32(_reqBuf[l:l+4], uint32(x))
|
||||
l += 4
|
||||
client.PutUint32(_reqBuf[l:l+4], uint32(y))
|
||||
l += 4
|
||||
client.PutUint32(_reqBuf[l:l+4], uint32(width))
|
||||
l += 4
|
||||
client.PutUint32(_reqBuf[l:l+4], uint32(height))
|
||||
l += 4
|
||||
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
||||
return frame, 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 *ZwlrScreencopyManagerV1) Destroy() error {
|
||||
defer i.Context().Unregister(i)
|
||||
const opcode = 2
|
||||
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
|
||||
}
|
||||
|
||||
// ZwlrScreencopyFrameV1InterfaceName 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 ZwlrScreencopyFrameV1InterfaceName = "zwlr_screencopy_frame_v1"
|
||||
|
||||
// ZwlrScreencopyFrameV1 : a frame ready for copy
|
||||
//
|
||||
// This object represents a single frame.
|
||||
//
|
||||
// When created, a series of buffer events will be sent, each representing a
|
||||
// supported buffer type. The "buffer_done" event is sent afterwards to
|
||||
// indicate that all supported buffer types have been enumerated. The client
|
||||
// will then be able to send a "copy" request. If the capture is successful,
|
||||
// the compositor will send a "flags" event followed by a "ready" event.
|
||||
//
|
||||
// For objects version 2 or lower, wl_shm buffers are always supported, ie.
|
||||
// the "buffer" event is guaranteed to be sent.
|
||||
//
|
||||
// If the capture failed, the "failed" event is sent. This can happen anytime
|
||||
// before the "ready" event.
|
||||
//
|
||||
// Once either a "ready" or a "failed" event is received, the client should
|
||||
// destroy the frame.
|
||||
type ZwlrScreencopyFrameV1 struct {
|
||||
client.BaseProxy
|
||||
bufferHandler ZwlrScreencopyFrameV1BufferHandlerFunc
|
||||
flagsHandler ZwlrScreencopyFrameV1FlagsHandlerFunc
|
||||
readyHandler ZwlrScreencopyFrameV1ReadyHandlerFunc
|
||||
failedHandler ZwlrScreencopyFrameV1FailedHandlerFunc
|
||||
damageHandler ZwlrScreencopyFrameV1DamageHandlerFunc
|
||||
linuxDmabufHandler ZwlrScreencopyFrameV1LinuxDmabufHandlerFunc
|
||||
bufferDoneHandler ZwlrScreencopyFrameV1BufferDoneHandlerFunc
|
||||
}
|
||||
|
||||
// NewZwlrScreencopyFrameV1 : a frame ready for copy
|
||||
//
|
||||
// This object represents a single frame.
|
||||
//
|
||||
// When created, a series of buffer events will be sent, each representing a
|
||||
// supported buffer type. The "buffer_done" event is sent afterwards to
|
||||
// indicate that all supported buffer types have been enumerated. The client
|
||||
// will then be able to send a "copy" request. If the capture is successful,
|
||||
// the compositor will send a "flags" event followed by a "ready" event.
|
||||
//
|
||||
// For objects version 2 or lower, wl_shm buffers are always supported, ie.
|
||||
// the "buffer" event is guaranteed to be sent.
|
||||
//
|
||||
// If the capture failed, the "failed" event is sent. This can happen anytime
|
||||
// before the "ready" event.
|
||||
//
|
||||
// Once either a "ready" or a "failed" event is received, the client should
|
||||
// destroy the frame.
|
||||
func NewZwlrScreencopyFrameV1(ctx *client.Context) *ZwlrScreencopyFrameV1 {
|
||||
zwlrScreencopyFrameV1 := &ZwlrScreencopyFrameV1{}
|
||||
ctx.Register(zwlrScreencopyFrameV1)
|
||||
return zwlrScreencopyFrameV1
|
||||
}
|
||||
|
||||
// Copy : copy the frame
|
||||
//
|
||||
// Copy the frame to the supplied buffer. The buffer must have the
|
||||
// correct size, see zwlr_screencopy_frame_v1.buffer and
|
||||
// zwlr_screencopy_frame_v1.linux_dmabuf. The buffer needs to have a
|
||||
// supported format.
|
||||
//
|
||||
// If the frame is successfully copied, "flags" and "ready" events are
|
||||
// sent. Otherwise, a "failed" event is sent.
|
||||
func (i *ZwlrScreencopyFrameV1) Copy(buffer *client.Buffer) 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], buffer.ID())
|
||||
l += 4
|
||||
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// Destroy : delete this object, used or not
|
||||
//
|
||||
// Destroys the frame. This request can be sent at any time by the client.
|
||||
func (i *ZwlrScreencopyFrameV1) 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
|
||||
}
|
||||
|
||||
// CopyWithDamage : copy the frame when it's damaged
|
||||
//
|
||||
// Same as copy, except it waits until there is damage to copy.
|
||||
func (i *ZwlrScreencopyFrameV1) CopyWithDamage(buffer *client.Buffer) error {
|
||||
const opcode = 2
|
||||
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], buffer.ID())
|
||||
l += 4
|
||||
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
||||
return err
|
||||
}
|
||||
|
||||
type ZwlrScreencopyFrameV1Error uint32
|
||||
|
||||
// ZwlrScreencopyFrameV1Error :
|
||||
const (
|
||||
// ZwlrScreencopyFrameV1ErrorAlreadyUsed : the object has already been used to copy a wl_buffer
|
||||
ZwlrScreencopyFrameV1ErrorAlreadyUsed ZwlrScreencopyFrameV1Error = 0
|
||||
// ZwlrScreencopyFrameV1ErrorInvalidBuffer : buffer attributes are invalid
|
||||
ZwlrScreencopyFrameV1ErrorInvalidBuffer ZwlrScreencopyFrameV1Error = 1
|
||||
)
|
||||
|
||||
func (e ZwlrScreencopyFrameV1Error) Name() string {
|
||||
switch e {
|
||||
case ZwlrScreencopyFrameV1ErrorAlreadyUsed:
|
||||
return "already_used"
|
||||
case ZwlrScreencopyFrameV1ErrorInvalidBuffer:
|
||||
return "invalid_buffer"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (e ZwlrScreencopyFrameV1Error) Value() string {
|
||||
switch e {
|
||||
case ZwlrScreencopyFrameV1ErrorAlreadyUsed:
|
||||
return "0"
|
||||
case ZwlrScreencopyFrameV1ErrorInvalidBuffer:
|
||||
return "1"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (e ZwlrScreencopyFrameV1Error) String() string {
|
||||
return e.Name() + "=" + e.Value()
|
||||
}
|
||||
|
||||
type ZwlrScreencopyFrameV1Flags uint32
|
||||
|
||||
// ZwlrScreencopyFrameV1Flags :
|
||||
const (
|
||||
// ZwlrScreencopyFrameV1FlagsYInvert : contents are y-inverted
|
||||
ZwlrScreencopyFrameV1FlagsYInvert ZwlrScreencopyFrameV1Flags = 1
|
||||
)
|
||||
|
||||
func (e ZwlrScreencopyFrameV1Flags) Name() string {
|
||||
switch e {
|
||||
case ZwlrScreencopyFrameV1FlagsYInvert:
|
||||
return "y_invert"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (e ZwlrScreencopyFrameV1Flags) Value() string {
|
||||
switch e {
|
||||
case ZwlrScreencopyFrameV1FlagsYInvert:
|
||||
return "1"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (e ZwlrScreencopyFrameV1Flags) String() string {
|
||||
return e.Name() + "=" + e.Value()
|
||||
}
|
||||
|
||||
// ZwlrScreencopyFrameV1BufferEvent : wl_shm buffer information
|
||||
//
|
||||
// Provides information about wl_shm buffer parameters that need to be
|
||||
// used for this frame. This event is sent once after the frame is created
|
||||
// if wl_shm buffers are supported.
|
||||
type ZwlrScreencopyFrameV1BufferEvent struct {
|
||||
Format uint32
|
||||
Width uint32
|
||||
Height uint32
|
||||
Stride uint32
|
||||
}
|
||||
type ZwlrScreencopyFrameV1BufferHandlerFunc func(ZwlrScreencopyFrameV1BufferEvent)
|
||||
|
||||
// SetBufferHandler : sets handler for ZwlrScreencopyFrameV1BufferEvent
|
||||
func (i *ZwlrScreencopyFrameV1) SetBufferHandler(f ZwlrScreencopyFrameV1BufferHandlerFunc) {
|
||||
i.bufferHandler = f
|
||||
}
|
||||
|
||||
// ZwlrScreencopyFrameV1FlagsEvent : frame flags
|
||||
//
|
||||
// Provides flags about the frame. This event is sent once before the
|
||||
// "ready" event.
|
||||
type ZwlrScreencopyFrameV1FlagsEvent struct {
|
||||
Flags uint32
|
||||
}
|
||||
type ZwlrScreencopyFrameV1FlagsHandlerFunc func(ZwlrScreencopyFrameV1FlagsEvent)
|
||||
|
||||
// SetFlagsHandler : sets handler for ZwlrScreencopyFrameV1FlagsEvent
|
||||
func (i *ZwlrScreencopyFrameV1) SetFlagsHandler(f ZwlrScreencopyFrameV1FlagsHandlerFunc) {
|
||||
i.flagsHandler = f
|
||||
}
|
||||
|
||||
// ZwlrScreencopyFrameV1ReadyEvent : indicates frame is available for reading
|
||||
//
|
||||
// Called as soon as the frame is copied, indicating it is available
|
||||
// for reading. This event includes the time at which the presentation took place.
|
||||
//
|
||||
// The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples,
|
||||
// each component being an unsigned 32-bit value. Whole seconds are in
|
||||
// tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo,
|
||||
// and the additional fractional part in tv_nsec as nanoseconds. Hence,
|
||||
// for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part
|
||||
// may have an arbitrary offset at start.
|
||||
//
|
||||
// After receiving this event, the client should destroy the object.
|
||||
type ZwlrScreencopyFrameV1ReadyEvent struct {
|
||||
TvSecHi uint32
|
||||
TvSecLo uint32
|
||||
TvNsec uint32
|
||||
}
|
||||
type ZwlrScreencopyFrameV1ReadyHandlerFunc func(ZwlrScreencopyFrameV1ReadyEvent)
|
||||
|
||||
// SetReadyHandler : sets handler for ZwlrScreencopyFrameV1ReadyEvent
|
||||
func (i *ZwlrScreencopyFrameV1) SetReadyHandler(f ZwlrScreencopyFrameV1ReadyHandlerFunc) {
|
||||
i.readyHandler = f
|
||||
}
|
||||
|
||||
// ZwlrScreencopyFrameV1FailedEvent : frame copy failed
|
||||
//
|
||||
// This event indicates that the attempted frame copy has failed.
|
||||
//
|
||||
// After receiving this event, the client should destroy the object.
|
||||
type ZwlrScreencopyFrameV1FailedEvent struct{}
|
||||
type ZwlrScreencopyFrameV1FailedHandlerFunc func(ZwlrScreencopyFrameV1FailedEvent)
|
||||
|
||||
// SetFailedHandler : sets handler for ZwlrScreencopyFrameV1FailedEvent
|
||||
func (i *ZwlrScreencopyFrameV1) SetFailedHandler(f ZwlrScreencopyFrameV1FailedHandlerFunc) {
|
||||
i.failedHandler = f
|
||||
}
|
||||
|
||||
// ZwlrScreencopyFrameV1DamageEvent : carries the coordinates of the damaged region
|
||||
//
|
||||
// This event is sent right before the ready event when copy_with_damage is
|
||||
// requested. It may be generated multiple times for each copy_with_damage
|
||||
// request.
|
||||
//
|
||||
// The arguments describe a box around an area that has changed since the
|
||||
// last copy request that was derived from the current screencopy manager
|
||||
// instance.
|
||||
//
|
||||
// The union of all regions received between the call to copy_with_damage
|
||||
// and a ready event is the total damage since the prior ready event.
|
||||
type ZwlrScreencopyFrameV1DamageEvent struct {
|
||||
X uint32
|
||||
Y uint32
|
||||
Width uint32
|
||||
Height uint32
|
||||
}
|
||||
type ZwlrScreencopyFrameV1DamageHandlerFunc func(ZwlrScreencopyFrameV1DamageEvent)
|
||||
|
||||
// SetDamageHandler : sets handler for ZwlrScreencopyFrameV1DamageEvent
|
||||
func (i *ZwlrScreencopyFrameV1) SetDamageHandler(f ZwlrScreencopyFrameV1DamageHandlerFunc) {
|
||||
i.damageHandler = f
|
||||
}
|
||||
|
||||
// ZwlrScreencopyFrameV1LinuxDmabufEvent : linux-dmabuf buffer information
|
||||
//
|
||||
// Provides information about linux-dmabuf buffer parameters that need to
|
||||
// be used for this frame. This event is sent once after the frame is
|
||||
// created if linux-dmabuf buffers are supported.
|
||||
type ZwlrScreencopyFrameV1LinuxDmabufEvent struct {
|
||||
Format uint32
|
||||
Width uint32
|
||||
Height uint32
|
||||
}
|
||||
type ZwlrScreencopyFrameV1LinuxDmabufHandlerFunc func(ZwlrScreencopyFrameV1LinuxDmabufEvent)
|
||||
|
||||
// SetLinuxDmabufHandler : sets handler for ZwlrScreencopyFrameV1LinuxDmabufEvent
|
||||
func (i *ZwlrScreencopyFrameV1) SetLinuxDmabufHandler(f ZwlrScreencopyFrameV1LinuxDmabufHandlerFunc) {
|
||||
i.linuxDmabufHandler = f
|
||||
}
|
||||
|
||||
// ZwlrScreencopyFrameV1BufferDoneEvent : all buffer types reported
|
||||
//
|
||||
// This event is sent once after all buffer events have been sent.
|
||||
//
|
||||
// The client should proceed to create a buffer of one of the supported
|
||||
// types, and send a "copy" request.
|
||||
type ZwlrScreencopyFrameV1BufferDoneEvent struct{}
|
||||
type ZwlrScreencopyFrameV1BufferDoneHandlerFunc func(ZwlrScreencopyFrameV1BufferDoneEvent)
|
||||
|
||||
// SetBufferDoneHandler : sets handler for ZwlrScreencopyFrameV1BufferDoneEvent
|
||||
func (i *ZwlrScreencopyFrameV1) SetBufferDoneHandler(f ZwlrScreencopyFrameV1BufferDoneHandlerFunc) {
|
||||
i.bufferDoneHandler = f
|
||||
}
|
||||
|
||||
func (i *ZwlrScreencopyFrameV1) Dispatch(opcode uint32, fd int, data []byte) {
|
||||
switch opcode {
|
||||
case 0:
|
||||
if i.bufferHandler == nil {
|
||||
return
|
||||
}
|
||||
var e ZwlrScreencopyFrameV1BufferEvent
|
||||
l := 0
|
||||
e.Format = client.Uint32(data[l : l+4])
|
||||
l += 4
|
||||
e.Width = client.Uint32(data[l : l+4])
|
||||
l += 4
|
||||
e.Height = client.Uint32(data[l : l+4])
|
||||
l += 4
|
||||
e.Stride = client.Uint32(data[l : l+4])
|
||||
l += 4
|
||||
|
||||
i.bufferHandler(e)
|
||||
case 1:
|
||||
if i.flagsHandler == nil {
|
||||
return
|
||||
}
|
||||
var e ZwlrScreencopyFrameV1FlagsEvent
|
||||
l := 0
|
||||
e.Flags = client.Uint32(data[l : l+4])
|
||||
l += 4
|
||||
|
||||
i.flagsHandler(e)
|
||||
case 2:
|
||||
if i.readyHandler == nil {
|
||||
return
|
||||
}
|
||||
var e ZwlrScreencopyFrameV1ReadyEvent
|
||||
l := 0
|
||||
e.TvSecHi = client.Uint32(data[l : l+4])
|
||||
l += 4
|
||||
e.TvSecLo = client.Uint32(data[l : l+4])
|
||||
l += 4
|
||||
e.TvNsec = client.Uint32(data[l : l+4])
|
||||
l += 4
|
||||
|
||||
i.readyHandler(e)
|
||||
case 3:
|
||||
if i.failedHandler == nil {
|
||||
return
|
||||
}
|
||||
var e ZwlrScreencopyFrameV1FailedEvent
|
||||
|
||||
i.failedHandler(e)
|
||||
case 4:
|
||||
if i.damageHandler == nil {
|
||||
return
|
||||
}
|
||||
var e ZwlrScreencopyFrameV1DamageEvent
|
||||
l := 0
|
||||
e.X = client.Uint32(data[l : l+4])
|
||||
l += 4
|
||||
e.Y = client.Uint32(data[l : l+4])
|
||||
l += 4
|
||||
e.Width = client.Uint32(data[l : l+4])
|
||||
l += 4
|
||||
e.Height = client.Uint32(data[l : l+4])
|
||||
l += 4
|
||||
|
||||
i.damageHandler(e)
|
||||
case 5:
|
||||
if i.linuxDmabufHandler == nil {
|
||||
return
|
||||
}
|
||||
var e ZwlrScreencopyFrameV1LinuxDmabufEvent
|
||||
l := 0
|
||||
e.Format = client.Uint32(data[l : l+4])
|
||||
l += 4
|
||||
e.Width = client.Uint32(data[l : l+4])
|
||||
l += 4
|
||||
e.Height = client.Uint32(data[l : l+4])
|
||||
l += 4
|
||||
|
||||
i.linuxDmabufHandler(e)
|
||||
case 6:
|
||||
if i.bufferDoneHandler == nil {
|
||||
return
|
||||
}
|
||||
var e ZwlrScreencopyFrameV1BufferDoneEvent
|
||||
|
||||
i.bufferDoneHandler(e)
|
||||
}
|
||||
}
|
||||
399
core/internal/proto/wp_viewporter/viewporter.go
Normal file
399
core/internal/proto/wp_viewporter/viewporter.go
Normal file
@@ -0,0 +1,399 @@
|
||||
// Generated by go-wayland-scanner
|
||||
// https://github.com/yaslama/go-wayland/cmd/go-wayland-scanner
|
||||
// XML file : /tmp/viewporter.xml
|
||||
//
|
||||
// viewporter Protocol Copyright:
|
||||
//
|
||||
// Copyright © 2013-2016 Collabora, Ltd.
|
||||
//
|
||||
// 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 wp_viewporter
|
||||
|
||||
import "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
||||
|
||||
// WpViewporterInterfaceName 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 WpViewporterInterfaceName = "wp_viewporter"
|
||||
|
||||
// WpViewporter : surface cropping and scaling
|
||||
//
|
||||
// The global interface exposing surface cropping and scaling
|
||||
// capabilities is used to instantiate an interface extension for a
|
||||
// wl_surface object. This extended interface will then allow
|
||||
// cropping and scaling the surface contents, effectively
|
||||
// disconnecting the direct relationship between the buffer and the
|
||||
// surface size.
|
||||
type WpViewporter struct {
|
||||
client.BaseProxy
|
||||
}
|
||||
|
||||
// NewWpViewporter : surface cropping and scaling
|
||||
//
|
||||
// The global interface exposing surface cropping and scaling
|
||||
// capabilities is used to instantiate an interface extension for a
|
||||
// wl_surface object. This extended interface will then allow
|
||||
// cropping and scaling the surface contents, effectively
|
||||
// disconnecting the direct relationship between the buffer and the
|
||||
// surface size.
|
||||
func NewWpViewporter(ctx *client.Context) *WpViewporter {
|
||||
wpViewporter := &WpViewporter{}
|
||||
ctx.Register(wpViewporter)
|
||||
return wpViewporter
|
||||
}
|
||||
|
||||
// Destroy : unbind from the cropping and scaling interface
|
||||
//
|
||||
// Informs the server that the client will not be using this
|
||||
// protocol object anymore. This does not affect any other objects,
|
||||
// wp_viewport objects included.
|
||||
func (i *WpViewporter) Destroy() error {
|
||||
defer i.Context().Unregister(i)
|
||||
const opcode = 0
|
||||
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
|
||||
}
|
||||
|
||||
// GetViewport : extend surface interface for crop and scale
|
||||
//
|
||||
// Instantiate an interface extension for the given wl_surface to
|
||||
// crop and scale its content. If the given wl_surface already has
|
||||
// a wp_viewport object associated, the viewport_exists
|
||||
// protocol error is raised.
|
||||
//
|
||||
// surface: the surface
|
||||
func (i *WpViewporter) GetViewport(surface *client.Surface) (*WpViewport, error) {
|
||||
id := NewWpViewport(i.Context())
|
||||
const opcode = 1
|
||||
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], surface.ID())
|
||||
l += 4
|
||||
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
||||
return id, err
|
||||
}
|
||||
|
||||
type WpViewporterError uint32
|
||||
|
||||
// WpViewporterError :
|
||||
const (
|
||||
// WpViewporterErrorViewportExists : the surface already has a viewport object associated
|
||||
WpViewporterErrorViewportExists WpViewporterError = 0
|
||||
)
|
||||
|
||||
func (e WpViewporterError) Name() string {
|
||||
switch e {
|
||||
case WpViewporterErrorViewportExists:
|
||||
return "viewport_exists"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (e WpViewporterError) Value() string {
|
||||
switch e {
|
||||
case WpViewporterErrorViewportExists:
|
||||
return "0"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (e WpViewporterError) String() string {
|
||||
return e.Name() + "=" + e.Value()
|
||||
}
|
||||
|
||||
// WpViewportInterfaceName 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 WpViewportInterfaceName = "wp_viewport"
|
||||
|
||||
// WpViewport : crop and scale interface to a wl_surface
|
||||
//
|
||||
// An additional interface to a wl_surface object, which allows the
|
||||
// client to specify the cropping and scaling of the surface
|
||||
// contents.
|
||||
//
|
||||
// This interface works with two concepts: the source rectangle (src_x,
|
||||
// src_y, src_width, src_height), and the destination size (dst_width,
|
||||
// dst_height). The contents of the source rectangle are scaled to the
|
||||
// destination size, and content outside the source rectangle is ignored.
|
||||
// This state is double-buffered, see wl_surface.commit.
|
||||
//
|
||||
// The two parts of crop and scale state are independent: the source
|
||||
// rectangle, and the destination size. Initially both are unset, that
|
||||
// is, no scaling is applied. The whole of the current wl_buffer is
|
||||
// used as the source, and the surface size is as defined in
|
||||
// wl_surface.attach.
|
||||
//
|
||||
// If the destination size is set, it causes the surface size to become
|
||||
// dst_width, dst_height. The source (rectangle) is scaled to exactly
|
||||
// this size. This overrides whatever the attached wl_buffer size is,
|
||||
// unless the wl_buffer is NULL. If the wl_buffer is NULL, the surface
|
||||
// has no content and therefore no size. Otherwise, the size is always
|
||||
// at least 1x1 in surface local coordinates.
|
||||
//
|
||||
// If the source rectangle is set, it defines what area of the wl_buffer is
|
||||
// taken as the source. If the source rectangle is set and the destination
|
||||
// size is not set, then src_width and src_height must be integers, and the
|
||||
// surface size becomes the source rectangle size. This results in cropping
|
||||
// without scaling. If src_width or src_height are not integers and
|
||||
// destination size is not set, the bad_size protocol error is raised when
|
||||
// the surface state is applied.
|
||||
//
|
||||
// The coordinate transformations from buffer pixel coordinates up to
|
||||
// the surface-local coordinates happen in the following order:
|
||||
// 1. buffer_transform (wl_surface.set_buffer_transform)
|
||||
// 2. buffer_scale (wl_surface.set_buffer_scale)
|
||||
// 3. crop and scale (wp_viewport.set*)
|
||||
// This means, that the source rectangle coordinates of crop and scale
|
||||
// are given in the coordinates after the buffer transform and scale,
|
||||
// i.e. in the coordinates that would be the surface-local coordinates
|
||||
// if the crop and scale was not applied.
|
||||
//
|
||||
// If src_x or src_y are negative, the bad_value protocol error is raised.
|
||||
// Otherwise, if the source rectangle is partially or completely outside of
|
||||
// the non-NULL wl_buffer, then the out_of_buffer protocol error is raised
|
||||
// when the surface state is applied. A NULL wl_buffer does not raise the
|
||||
// out_of_buffer error.
|
||||
//
|
||||
// If the wl_surface associated with the wp_viewport is destroyed,
|
||||
// all wp_viewport requests except 'destroy' raise the protocol error
|
||||
// no_surface.
|
||||
//
|
||||
// If the wp_viewport object is destroyed, the crop and scale
|
||||
// state is removed from the wl_surface. The change will be applied
|
||||
// on the next wl_surface.commit.
|
||||
type WpViewport struct {
|
||||
client.BaseProxy
|
||||
}
|
||||
|
||||
// NewWpViewport : crop and scale interface to a wl_surface
|
||||
//
|
||||
// An additional interface to a wl_surface object, which allows the
|
||||
// client to specify the cropping and scaling of the surface
|
||||
// contents.
|
||||
//
|
||||
// This interface works with two concepts: the source rectangle (src_x,
|
||||
// src_y, src_width, src_height), and the destination size (dst_width,
|
||||
// dst_height). The contents of the source rectangle are scaled to the
|
||||
// destination size, and content outside the source rectangle is ignored.
|
||||
// This state is double-buffered, see wl_surface.commit.
|
||||
//
|
||||
// The two parts of crop and scale state are independent: the source
|
||||
// rectangle, and the destination size. Initially both are unset, that
|
||||
// is, no scaling is applied. The whole of the current wl_buffer is
|
||||
// used as the source, and the surface size is as defined in
|
||||
// wl_surface.attach.
|
||||
//
|
||||
// If the destination size is set, it causes the surface size to become
|
||||
// dst_width, dst_height. The source (rectangle) is scaled to exactly
|
||||
// this size. This overrides whatever the attached wl_buffer size is,
|
||||
// unless the wl_buffer is NULL. If the wl_buffer is NULL, the surface
|
||||
// has no content and therefore no size. Otherwise, the size is always
|
||||
// at least 1x1 in surface local coordinates.
|
||||
//
|
||||
// If the source rectangle is set, it defines what area of the wl_buffer is
|
||||
// taken as the source. If the source rectangle is set and the destination
|
||||
// size is not set, then src_width and src_height must be integers, and the
|
||||
// surface size becomes the source rectangle size. This results in cropping
|
||||
// without scaling. If src_width or src_height are not integers and
|
||||
// destination size is not set, the bad_size protocol error is raised when
|
||||
// the surface state is applied.
|
||||
//
|
||||
// The coordinate transformations from buffer pixel coordinates up to
|
||||
// the surface-local coordinates happen in the following order:
|
||||
// 1. buffer_transform (wl_surface.set_buffer_transform)
|
||||
// 2. buffer_scale (wl_surface.set_buffer_scale)
|
||||
// 3. crop and scale (wp_viewport.set*)
|
||||
// This means, that the source rectangle coordinates of crop and scale
|
||||
// are given in the coordinates after the buffer transform and scale,
|
||||
// i.e. in the coordinates that would be the surface-local coordinates
|
||||
// if the crop and scale was not applied.
|
||||
//
|
||||
// If src_x or src_y are negative, the bad_value protocol error is raised.
|
||||
// Otherwise, if the source rectangle is partially or completely outside of
|
||||
// the non-NULL wl_buffer, then the out_of_buffer protocol error is raised
|
||||
// when the surface state is applied. A NULL wl_buffer does not raise the
|
||||
// out_of_buffer error.
|
||||
//
|
||||
// If the wl_surface associated with the wp_viewport is destroyed,
|
||||
// all wp_viewport requests except 'destroy' raise the protocol error
|
||||
// no_surface.
|
||||
//
|
||||
// If the wp_viewport object is destroyed, the crop and scale
|
||||
// state is removed from the wl_surface. The change will be applied
|
||||
// on the next wl_surface.commit.
|
||||
func NewWpViewport(ctx *client.Context) *WpViewport {
|
||||
wpViewport := &WpViewport{}
|
||||
ctx.Register(wpViewport)
|
||||
return wpViewport
|
||||
}
|
||||
|
||||
// Destroy : remove scaling and cropping from the surface
|
||||
//
|
||||
// The associated wl_surface's crop and scale state is removed.
|
||||
// The change is applied on the next wl_surface.commit.
|
||||
func (i *WpViewport) Destroy() error {
|
||||
defer i.Context().Unregister(i)
|
||||
const opcode = 0
|
||||
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
|
||||
}
|
||||
|
||||
// SetSource : set the source rectangle for cropping
|
||||
//
|
||||
// Set the source rectangle of the associated wl_surface. See
|
||||
// wp_viewport for the description, and relation to the wl_buffer
|
||||
// size.
|
||||
//
|
||||
// If all of x, y, width and height are -1.0, the source rectangle is
|
||||
// unset instead. Any other set of values where width or height are zero
|
||||
// or negative, or x or y are negative, raise the bad_value protocol
|
||||
// error.
|
||||
//
|
||||
// The crop and scale state is double-buffered, see wl_surface.commit.
|
||||
//
|
||||
// x: source rectangle x
|
||||
// y: source rectangle y
|
||||
// width: source rectangle width
|
||||
// height: source rectangle height
|
||||
func (i *WpViewport) SetSource(x, y, width, height float64) error {
|
||||
const opcode = 1
|
||||
const _reqBufLen = 8 + 4 + 4 + 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.PutFixed(_reqBuf[l:l+4], x)
|
||||
l += 4
|
||||
client.PutFixed(_reqBuf[l:l+4], y)
|
||||
l += 4
|
||||
client.PutFixed(_reqBuf[l:l+4], width)
|
||||
l += 4
|
||||
client.PutFixed(_reqBuf[l:l+4], height)
|
||||
l += 4
|
||||
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetDestination : set the surface size for scaling
|
||||
//
|
||||
// Set the destination size of the associated wl_surface. See
|
||||
// wp_viewport for the description, and relation to the wl_buffer
|
||||
// size.
|
||||
//
|
||||
// If width is -1 and height is -1, the destination size is unset
|
||||
// instead. Any other pair of values for width and height that
|
||||
// contains zero or negative values raises the bad_value protocol
|
||||
// error.
|
||||
//
|
||||
// The crop and scale state is double-buffered, see wl_surface.commit.
|
||||
//
|
||||
// width: surface width
|
||||
// height: surface height
|
||||
func (i *WpViewport) SetDestination(width, height int32) error {
|
||||
const opcode = 2
|
||||
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], uint32(width))
|
||||
l += 4
|
||||
client.PutUint32(_reqBuf[l:l+4], uint32(height))
|
||||
l += 4
|
||||
err := i.Context().WriteMsg(_reqBuf[:], nil)
|
||||
return err
|
||||
}
|
||||
|
||||
type WpViewportError uint32
|
||||
|
||||
// WpViewportError :
|
||||
const (
|
||||
// WpViewportErrorBadValue : negative or zero values in width or height
|
||||
WpViewportErrorBadValue WpViewportError = 0
|
||||
// WpViewportErrorBadSize : destination size is not integer
|
||||
WpViewportErrorBadSize WpViewportError = 1
|
||||
// WpViewportErrorOutOfBuffer : source rectangle extends outside of the content area
|
||||
WpViewportErrorOutOfBuffer WpViewportError = 2
|
||||
// WpViewportErrorNoSurface : the wl_surface was destroyed
|
||||
WpViewportErrorNoSurface WpViewportError = 3
|
||||
)
|
||||
|
||||
func (e WpViewportError) Name() string {
|
||||
switch e {
|
||||
case WpViewportErrorBadValue:
|
||||
return "bad_value"
|
||||
case WpViewportErrorBadSize:
|
||||
return "bad_size"
|
||||
case WpViewportErrorOutOfBuffer:
|
||||
return "out_of_buffer"
|
||||
case WpViewportErrorNoSurface:
|
||||
return "no_surface"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (e WpViewportError) Value() string {
|
||||
switch e {
|
||||
case WpViewportErrorBadValue:
|
||||
return "0"
|
||||
case WpViewportErrorBadSize:
|
||||
return "1"
|
||||
case WpViewportErrorOutOfBuffer:
|
||||
return "2"
|
||||
case WpViewportErrorNoSurface:
|
||||
return "3"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (e WpViewportError) String() string {
|
||||
return e.Name() + "=" + e.Value()
|
||||
}
|
||||
@@ -19,7 +19,7 @@ I would probably just submit raphi's patchset but I don't think that would be po
|
||||
reset.
|
||||
</description>
|
||||
|
||||
<interface name="zdwl_ipc_manager_v2" version="1">
|
||||
<interface name="zdwl_ipc_manager_v2" version="2">
|
||||
<description summary="manage dwl state">
|
||||
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>
|
||||
</interface>
|
||||
|
||||
<interface name="zdwl_ipc_output_v2" version="1">
|
||||
<interface name="zdwl_ipc_output_v2" version="2">
|
||||
<description summary="control 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"/>
|
||||
<arg name="index" type="uint" summary="index of a layout recieved by dwl_ipc_manager.layout"/>
|
||||
</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>
|
||||
</protocol>
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="keyboard_shortcuts_inhibit_unstable_v1">
|
||||
|
||||
<copyright>
|
||||
Copyright © 2017 Red Hat Inc.
|
||||
|
||||
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="Protocol for inhibiting the compositor keyboard shortcuts">
|
||||
This protocol specifies a way for a client to request the compositor
|
||||
to ignore its own keyboard shortcuts for a given seat, so that all
|
||||
key events from that seat get forwarded to a surface.
|
||||
|
||||
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="zwp_keyboard_shortcuts_inhibit_manager_v1" version="1">
|
||||
<description summary="context object for keyboard grab_manager">
|
||||
A global interface used for inhibiting the compositor keyboard shortcuts.
|
||||
</description>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the keyboard shortcuts inhibitor object">
|
||||
Destroy the keyboard shortcuts inhibitor manager.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="inhibit_shortcuts">
|
||||
<description summary="create a new keyboard shortcuts inhibitor object">
|
||||
Create a new keyboard shortcuts inhibitor object associated with
|
||||
the given surface for the given seat.
|
||||
|
||||
If shortcuts are already inhibited for the specified seat and surface,
|
||||
a protocol error "already_inhibited" is raised by the compositor.
|
||||
</description>
|
||||
<arg name="id" type="new_id" interface="zwp_keyboard_shortcuts_inhibitor_v1"/>
|
||||
<arg name="surface" type="object" interface="wl_surface"
|
||||
summary="the surface that inhibits the keyboard shortcuts behavior"/>
|
||||
<arg name="seat" type="object" interface="wl_seat"
|
||||
summary="the wl_seat for which keyboard shortcuts should be disabled"/>
|
||||
</request>
|
||||
|
||||
<enum name="error">
|
||||
<entry name="already_inhibited"
|
||||
value="0"
|
||||
summary="the shortcuts are already inhibited for this surface"/>
|
||||
</enum>
|
||||
</interface>
|
||||
|
||||
<interface name="zwp_keyboard_shortcuts_inhibitor_v1" version="1">
|
||||
<description summary="context object for keyboard shortcuts inhibitor">
|
||||
A keyboard shortcuts inhibitor instructs the compositor to ignore
|
||||
its own keyboard shortcuts when the associated surface has keyboard
|
||||
focus. As a result, when the surface has keyboard focus on the given
|
||||
seat, it will receive all key events originating from the specified
|
||||
seat, even those which would normally be caught by the compositor for
|
||||
its own shortcuts.
|
||||
|
||||
The Wayland compositor is however under no obligation to disable
|
||||
all of its shortcuts, and may keep some special key combo for its own
|
||||
use, including but not limited to one allowing the user to forcibly
|
||||
restore normal keyboard events routing in the case of an unwilling
|
||||
client. The compositor may also use the same key combo to reactivate
|
||||
an existing shortcut inhibitor that was previously deactivated on
|
||||
user request.
|
||||
|
||||
When the compositor restores its own keyboard shortcuts, an
|
||||
"inactive" event is emitted to notify the client that the keyboard
|
||||
shortcuts inhibitor is not effectively active for the surface and
|
||||
seat any more, and the client should not expect to receive all
|
||||
keyboard events.
|
||||
|
||||
When the keyboard shortcuts inhibitor is inactive, the client has
|
||||
no way to forcibly reactivate the keyboard shortcuts inhibitor.
|
||||
|
||||
The user can chose to re-enable a previously deactivated keyboard
|
||||
shortcuts inhibitor using any mechanism the compositor may offer,
|
||||
in which case the compositor will send an "active" event to notify
|
||||
the client.
|
||||
|
||||
If the surface is destroyed, unmapped, or loses the seat's keyboard
|
||||
focus, the keyboard shortcuts inhibitor becomes irrelevant and the
|
||||
compositor will restore its own keyboard shortcuts but no "inactive"
|
||||
event is emitted in this case.
|
||||
</description>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the keyboard shortcuts inhibitor object">
|
||||
Remove the keyboard shortcuts inhibitor from the associated wl_surface.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<event name="active">
|
||||
<description summary="shortcuts are inhibited">
|
||||
This event indicates that the shortcut inhibitor is active.
|
||||
|
||||
The compositor sends this event every time compositor shortcuts
|
||||
are inhibited on behalf of the surface. When active, the client
|
||||
may receive input events normally reserved by the compositor
|
||||
(see zwp_keyboard_shortcuts_inhibitor_v1).
|
||||
|
||||
This occurs typically when the initial request "inhibit_shortcuts"
|
||||
first becomes active or when the user instructs the compositor to
|
||||
re-enable and existing shortcuts inhibitor using any mechanism
|
||||
offered by the compositor.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<event name="inactive">
|
||||
<description summary="shortcuts are restored">
|
||||
This event indicates that the shortcuts inhibitor is inactive,
|
||||
normal shortcuts processing is restored by the compositor.
|
||||
</description>
|
||||
</event>
|
||||
</interface>
|
||||
</protocol>
|
||||
407
core/internal/proto/xml/wlr-layer-shell-unstable-v1.xml
Normal file
407
core/internal/proto/xml/wlr-layer-shell-unstable-v1.xml
Normal file
@@ -0,0 +1,407 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="wlr_layer_shell_unstable_v1">
|
||||
<copyright>
|
||||
Copyright © 2017 Drew DeVault
|
||||
|
||||
Permission to use, copy, modify, distribute, and sell this
|
||||
software and its documentation for any purpose is hereby granted
|
||||
without fee, provided that the above copyright notice appear in
|
||||
all copies and that both that copyright notice and this permission
|
||||
notice appear in supporting documentation, and that the name of
|
||||
the copyright holders not be used in advertising or publicity
|
||||
pertaining to distribution of the software without specific,
|
||||
written prior permission. The copyright holders make no
|
||||
representations about the suitability of this software for any
|
||||
purpose. It is provided "as is" without express or implied
|
||||
warranty.
|
||||
|
||||
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
|
||||
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<interface name="zwlr_layer_shell_v1" version="5">
|
||||
<description summary="create surfaces that are layers of the desktop">
|
||||
Clients can use this interface to assign the surface_layer role to
|
||||
wl_surfaces. Such surfaces are assigned to a "layer" of the output and
|
||||
rendered with a defined z-depth respective to each other. They may also be
|
||||
anchored to the edges and corners of a screen and specify input handling
|
||||
semantics. This interface should be suitable for the implementation of
|
||||
many desktop shell components, and a broad number of other applications
|
||||
that interact with the desktop.
|
||||
</description>
|
||||
|
||||
<request name="get_layer_surface">
|
||||
<description summary="create a layer_surface from a surface">
|
||||
Create a layer surface for an existing surface. This assigns the role of
|
||||
layer_surface, or raises a protocol error if another role is already
|
||||
assigned.
|
||||
|
||||
Creating a layer surface from a wl_surface which has a buffer attached
|
||||
or committed is a client error, and any attempts by a client to attach
|
||||
or manipulate a buffer prior to the first layer_surface.configure call
|
||||
must also be treated as errors.
|
||||
|
||||
After creating a layer_surface object and setting it up, the client
|
||||
must perform an initial commit without any buffer attached.
|
||||
The compositor will reply with a layer_surface.configure event.
|
||||
The client must acknowledge it and is then allowed to attach a buffer
|
||||
to map the surface.
|
||||
|
||||
You may pass NULL for output to allow the compositor to decide which
|
||||
output to use. Generally this will be the one that the user most
|
||||
recently interacted with.
|
||||
|
||||
Clients can specify a namespace that defines the purpose of the layer
|
||||
surface.
|
||||
</description>
|
||||
<arg name="id" type="new_id" interface="zwlr_layer_surface_v1"/>
|
||||
<arg name="surface" type="object" interface="wl_surface"/>
|
||||
<arg name="output" type="object" interface="wl_output" allow-null="true"/>
|
||||
<arg name="layer" type="uint" enum="layer" summary="layer to add this surface to"/>
|
||||
<arg name="namespace" type="string" summary="namespace for the layer surface"/>
|
||||
</request>
|
||||
|
||||
<enum name="error">
|
||||
<entry name="role" value="0" summary="wl_surface has another role"/>
|
||||
<entry name="invalid_layer" value="1" summary="layer value is invalid"/>
|
||||
<entry name="already_constructed" value="2" summary="wl_surface has a buffer attached or committed"/>
|
||||
</enum>
|
||||
|
||||
<enum name="layer">
|
||||
<description summary="available layers for surfaces">
|
||||
These values indicate which layers a surface can be rendered in. They
|
||||
are ordered by z depth, bottom-most first. Traditional shell surfaces
|
||||
will typically be rendered between the bottom and top layers.
|
||||
Fullscreen shell surfaces are typically rendered at the top layer.
|
||||
Multiple surfaces can share a single layer, and ordering within a
|
||||
single layer is undefined.
|
||||
</description>
|
||||
|
||||
<entry name="background" value="0"/>
|
||||
<entry name="bottom" value="1"/>
|
||||
<entry name="top" value="2"/>
|
||||
<entry name="overlay" value="3"/>
|
||||
</enum>
|
||||
|
||||
<!-- Version 3 additions -->
|
||||
|
||||
<request name="destroy" type="destructor" since="3">
|
||||
<description summary="destroy the layer_shell object">
|
||||
This request indicates that the client will not use the layer_shell
|
||||
object any more. Objects that have been created through this instance
|
||||
are not affected.
|
||||
</description>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="zwlr_layer_surface_v1" version="5">
|
||||
<description summary="layer metadata interface">
|
||||
An interface that may be implemented by a wl_surface, for surfaces that
|
||||
are designed to be rendered as a layer of a stacked desktop-like
|
||||
environment.
|
||||
|
||||
Layer surface state (layer, size, anchor, exclusive zone,
|
||||
margin, interactivity) is double-buffered, and will be applied at the
|
||||
time wl_surface.commit of the corresponding wl_surface is called.
|
||||
|
||||
Attaching a null buffer to a layer surface unmaps it.
|
||||
|
||||
Unmapping a layer_surface means that the surface cannot be shown by the
|
||||
compositor until it is explicitly mapped again. The layer_surface
|
||||
returns to the state it had right after layer_shell.get_layer_surface.
|
||||
The client can re-map the surface by performing a commit without any
|
||||
buffer attached, waiting for a configure event and handling it as usual.
|
||||
</description>
|
||||
|
||||
<request name="set_size">
|
||||
<description summary="sets the size of the surface">
|
||||
Sets the size of the surface in surface-local coordinates. The
|
||||
compositor will display the surface centered with respect to its
|
||||
anchors.
|
||||
|
||||
If you pass 0 for either value, the compositor will assign it and
|
||||
inform you of the assignment in the configure event. You must set your
|
||||
anchor to opposite edges in the dimensions you omit; not doing so is a
|
||||
protocol error. Both values are 0 by default.
|
||||
|
||||
Size is double-buffered, see wl_surface.commit.
|
||||
</description>
|
||||
<arg name="width" type="uint"/>
|
||||
<arg name="height" type="uint"/>
|
||||
</request>
|
||||
|
||||
<request name="set_anchor">
|
||||
<description summary="configures the anchor point of the surface">
|
||||
Requests that the compositor anchor the surface to the specified edges
|
||||
and corners. If two orthogonal edges are specified (e.g. 'top' and
|
||||
'left'), then the anchor point will be the intersection of the edges
|
||||
(e.g. the top left corner of the output); otherwise the anchor point
|
||||
will be centered on that edge, or in the center if none is specified.
|
||||
|
||||
Anchor is double-buffered, see wl_surface.commit.
|
||||
</description>
|
||||
<arg name="anchor" type="uint" enum="anchor"/>
|
||||
</request>
|
||||
|
||||
<request name="set_exclusive_zone">
|
||||
<description summary="configures the exclusive geometry of this surface">
|
||||
Requests that the compositor avoids occluding an area with other
|
||||
surfaces. The compositor's use of this information is
|
||||
implementation-dependent - do not assume that this region will not
|
||||
actually be occluded.
|
||||
|
||||
A positive value is only meaningful if the surface is anchored to one
|
||||
edge or an edge and both perpendicular edges. If the surface is not
|
||||
anchored, anchored to only two perpendicular edges (a corner), anchored
|
||||
to only two parallel edges or anchored to all edges, a positive value
|
||||
will be treated the same as zero.
|
||||
|
||||
A positive zone is the distance from the edge in surface-local
|
||||
coordinates to consider exclusive.
|
||||
|
||||
Surfaces that do not wish to have an exclusive zone may instead specify
|
||||
how they should interact with surfaces that do. If set to zero, the
|
||||
surface indicates that it would like to be moved to avoid occluding
|
||||
surfaces with a positive exclusive zone. If set to -1, the surface
|
||||
indicates that it would not like to be moved to accommodate for other
|
||||
surfaces, and the compositor should extend it all the way to the edges
|
||||
it is anchored to.
|
||||
|
||||
For example, a panel might set its exclusive zone to 10, so that
|
||||
maximized shell surfaces are not shown on top of it. A notification
|
||||
might set its exclusive zone to 0, so that it is moved to avoid
|
||||
occluding the panel, but shell surfaces are shown underneath it. A
|
||||
wallpaper or lock screen might set their exclusive zone to -1, so that
|
||||
they stretch below or over the panel.
|
||||
|
||||
The default value is 0.
|
||||
|
||||
Exclusive zone is double-buffered, see wl_surface.commit.
|
||||
</description>
|
||||
<arg name="zone" type="int"/>
|
||||
</request>
|
||||
|
||||
<request name="set_margin">
|
||||
<description summary="sets a margin from the anchor point">
|
||||
Requests that the surface be placed some distance away from the anchor
|
||||
point on the output, in surface-local coordinates. Setting this value
|
||||
for edges you are not anchored to has no effect.
|
||||
|
||||
The exclusive zone includes the margin.
|
||||
|
||||
Margin is double-buffered, see wl_surface.commit.
|
||||
</description>
|
||||
<arg name="top" type="int"/>
|
||||
<arg name="right" type="int"/>
|
||||
<arg name="bottom" type="int"/>
|
||||
<arg name="left" type="int"/>
|
||||
</request>
|
||||
|
||||
<enum name="keyboard_interactivity">
|
||||
<description summary="types of keyboard interaction possible for a layer shell surface">
|
||||
Types of keyboard interaction possible for layer shell surfaces. The
|
||||
rationale for this is twofold: (1) some applications are not interested
|
||||
in keyboard events and not allowing them to be focused can improve the
|
||||
desktop experience; (2) some applications will want to take exclusive
|
||||
keyboard focus.
|
||||
</description>
|
||||
|
||||
<entry name="none" value="0">
|
||||
<description summary="no keyboard focus is possible">
|
||||
This value indicates that this surface is not interested in keyboard
|
||||
events and the compositor should never assign it the keyboard focus.
|
||||
|
||||
This is the default value, set for newly created layer shell surfaces.
|
||||
|
||||
This is useful for e.g. desktop widgets that display information or
|
||||
only have interaction with non-keyboard input devices.
|
||||
</description>
|
||||
</entry>
|
||||
<entry name="exclusive" value="1">
|
||||
<description summary="request exclusive keyboard focus">
|
||||
Request exclusive keyboard focus if this surface is above the shell surface layer.
|
||||
|
||||
For the top and overlay layers, the seat will always give
|
||||
exclusive keyboard focus to the top-most layer which has keyboard
|
||||
interactivity set to exclusive. If this layer contains multiple
|
||||
surfaces with keyboard interactivity set to exclusive, the compositor
|
||||
determines the one receiving keyboard events in an implementation-
|
||||
defined manner. In this case, no guarantee is made when this surface
|
||||
will receive keyboard focus (if ever).
|
||||
|
||||
For the bottom and background layers, the compositor is allowed to use
|
||||
normal focus semantics.
|
||||
|
||||
This setting is mainly intended for applications that need to ensure
|
||||
they receive all keyboard events, such as a lock screen or a password
|
||||
prompt.
|
||||
</description>
|
||||
</entry>
|
||||
<entry name="on_demand" value="2" since="4">
|
||||
<description summary="request regular keyboard focus semantics">
|
||||
This requests the compositor to allow this surface to be focused and
|
||||
unfocused by the user in an implementation-defined manner. The user
|
||||
should be able to unfocus this surface even regardless of the layer
|
||||
it is on.
|
||||
|
||||
Typically, the compositor will want to use its normal mechanism to
|
||||
manage keyboard focus between layer shell surfaces with this setting
|
||||
and regular toplevels on the desktop layer (e.g. click to focus).
|
||||
Nevertheless, it is possible for a compositor to require a special
|
||||
interaction to focus or unfocus layer shell surfaces (e.g. requiring
|
||||
a click even if focus follows the mouse normally, or providing a
|
||||
keybinding to switch focus between layers).
|
||||
|
||||
This setting is mainly intended for desktop shell components (e.g.
|
||||
panels) that allow keyboard interaction. Using this option can allow
|
||||
implementing a desktop shell that can be fully usable without the
|
||||
mouse.
|
||||
</description>
|
||||
</entry>
|
||||
</enum>
|
||||
|
||||
<request name="set_keyboard_interactivity">
|
||||
<description summary="requests keyboard events">
|
||||
Set how keyboard events are delivered to this surface. By default,
|
||||
layer shell surfaces do not receive keyboard events; this request can
|
||||
be used to change this.
|
||||
|
||||
This setting is inherited by child surfaces set by the get_popup
|
||||
request.
|
||||
|
||||
Layer surfaces receive pointer, touch, and tablet events normally. If
|
||||
you do not want to receive them, set the input region on your surface
|
||||
to an empty region.
|
||||
|
||||
Keyboard interactivity is double-buffered, see wl_surface.commit.
|
||||
</description>
|
||||
<arg name="keyboard_interactivity" type="uint" enum="keyboard_interactivity"/>
|
||||
</request>
|
||||
|
||||
<request name="get_popup">
|
||||
<description summary="assign this layer_surface as an xdg_popup parent">
|
||||
This assigns an xdg_popup's parent to this layer_surface. This popup
|
||||
should have been created via xdg_surface::get_popup with the parent set
|
||||
to NULL, and this request must be invoked before committing the popup's
|
||||
initial state.
|
||||
|
||||
See the documentation of xdg_popup for more details about what an
|
||||
xdg_popup is and how it is used.
|
||||
</description>
|
||||
<arg name="popup" type="object" interface="xdg_popup"/>
|
||||
</request>
|
||||
|
||||
<request name="ack_configure">
|
||||
<description summary="ack a configure event">
|
||||
When a configure event is received, if a client commits the
|
||||
surface in response to the configure event, then the client
|
||||
must make an ack_configure request sometime before the commit
|
||||
request, passing along the serial of the configure event.
|
||||
|
||||
If the client receives multiple configure events before it
|
||||
can respond to one, it only has to ack the last configure event.
|
||||
|
||||
A client is not required to commit immediately after sending
|
||||
an ack_configure request - it may even ack_configure several times
|
||||
before its next surface commit.
|
||||
|
||||
A client may send multiple ack_configure requests before committing, but
|
||||
only the last request sent before a commit indicates which configure
|
||||
event the client really is responding to.
|
||||
</description>
|
||||
<arg name="serial" type="uint" summary="the serial from the configure event"/>
|
||||
</request>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the layer_surface">
|
||||
This request destroys the layer surface.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<event name="configure">
|
||||
<description summary="suggest a surface change">
|
||||
The configure event asks the client to resize its surface.
|
||||
|
||||
Clients should arrange their surface for the new states, and then send
|
||||
an ack_configure request with the serial sent in this configure event at
|
||||
some point before committing the new surface.
|
||||
|
||||
The client is free to dismiss all but the last configure event it
|
||||
received.
|
||||
|
||||
The width and height arguments specify the size of the window in
|
||||
surface-local coordinates.
|
||||
|
||||
The size is a hint, in the sense that the client is free to ignore it if
|
||||
it doesn't resize, pick a smaller size (to satisfy aspect ratio or
|
||||
resize in steps of NxM pixels). If the client picks a smaller size and
|
||||
is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the
|
||||
surface will be centered on this axis.
|
||||
|
||||
If the width or height arguments are zero, it means the client should
|
||||
decide its own window dimension.
|
||||
</description>
|
||||
<arg name="serial" type="uint"/>
|
||||
<arg name="width" type="uint"/>
|
||||
<arg name="height" type="uint"/>
|
||||
</event>
|
||||
|
||||
<event name="closed">
|
||||
<description summary="surface should be closed">
|
||||
The closed event is sent by the compositor when the surface will no
|
||||
longer be shown. The output may have been destroyed or the user may
|
||||
have asked for it to be removed. Further changes to the surface will be
|
||||
ignored. The client should destroy the resource after receiving this
|
||||
event, and create a new surface if they so choose.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<enum name="error">
|
||||
<entry name="invalid_surface_state" value="0" summary="provided surface state is invalid"/>
|
||||
<entry name="invalid_size" value="1" summary="size is invalid"/>
|
||||
<entry name="invalid_anchor" value="2" summary="anchor bitfield is invalid"/>
|
||||
<entry name="invalid_keyboard_interactivity" value="3" summary="keyboard interactivity is invalid"/>
|
||||
<entry name="invalid_exclusive_edge" value="4" summary="exclusive edge is invalid given the surface anchors"/>
|
||||
</enum>
|
||||
|
||||
<enum name="anchor" bitfield="true">
|
||||
<entry name="top" value="1" summary="the top edge of the anchor rectangle"/>
|
||||
<entry name="bottom" value="2" summary="the bottom edge of the anchor rectangle"/>
|
||||
<entry name="left" value="4" summary="the left edge of the anchor rectangle"/>
|
||||
<entry name="right" value="8" summary="the right edge of the anchor rectangle"/>
|
||||
</enum>
|
||||
|
||||
<!-- Version 2 additions -->
|
||||
|
||||
<request name="set_layer" since="2">
|
||||
<description summary="change the layer of the surface">
|
||||
Change the layer that the surface is rendered on.
|
||||
|
||||
Layer is double-buffered, see wl_surface.commit.
|
||||
</description>
|
||||
<arg name="layer" type="uint" enum="zwlr_layer_shell_v1.layer" summary="layer to move this surface to"/>
|
||||
</request>
|
||||
|
||||
<!-- Version 5 additions -->
|
||||
|
||||
<request name="set_exclusive_edge" since="5">
|
||||
<description summary="set the edge the exclusive zone will be applied to">
|
||||
Requests an edge for the exclusive zone to apply. The exclusive
|
||||
edge will be automatically deduced from anchor points when possible,
|
||||
but when the surface is anchored to a corner, it will be necessary
|
||||
to set it explicitly to disambiguate, as it is not possible to deduce
|
||||
which one of the two corner edges should be used.
|
||||
|
||||
The edge must be one the surface is anchored to, otherwise the
|
||||
invalid_exclusive_edge protocol error will be raised.
|
||||
</description>
|
||||
<arg name="edge" type="uint" enum="anchor"/>
|
||||
</request>
|
||||
</interface>
|
||||
</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>
|
||||
234
core/internal/proto/xml/wlr-screencopy-unstable-v1.xml
Normal file
234
core/internal/proto/xml/wlr-screencopy-unstable-v1.xml
Normal file
@@ -0,0 +1,234 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="wlr_screencopy_unstable_v1">
|
||||
<copyright>
|
||||
Copyright © 2018 Simon Ser
|
||||
Copyright © 2019 Andri Yngvason
|
||||
|
||||
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="screen content capturing on client buffers">
|
||||
This protocol allows clients to ask the compositor to copy part of the
|
||||
screen content to a client buffer.
|
||||
|
||||
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.
|
||||
|
||||
Note! This protocol is deprecated and not intended for production use.
|
||||
The ext-image-copy-capture-v1 protocol should be used instead.
|
||||
</description>
|
||||
|
||||
<interface name="zwlr_screencopy_manager_v1" version="3">
|
||||
<description summary="manager to inform clients and begin capturing">
|
||||
This object is a manager which offers requests to start capturing from a
|
||||
source.
|
||||
</description>
|
||||
|
||||
<request name="capture_output">
|
||||
<description summary="capture an output">
|
||||
Capture the next frame of an entire output.
|
||||
</description>
|
||||
<arg name="frame" type="new_id" interface="zwlr_screencopy_frame_v1"/>
|
||||
<arg name="overlay_cursor" type="int"
|
||||
summary="composite cursor onto the frame"/>
|
||||
<arg name="output" type="object" interface="wl_output"/>
|
||||
</request>
|
||||
|
||||
<request name="capture_output_region">
|
||||
<description summary="capture an output's region">
|
||||
Capture the next frame of an output's region.
|
||||
|
||||
The region is given in output logical coordinates, see
|
||||
xdg_output.logical_size. The region will be clipped to the output's
|
||||
extents.
|
||||
</description>
|
||||
<arg name="frame" type="new_id" interface="zwlr_screencopy_frame_v1"/>
|
||||
<arg name="overlay_cursor" type="int"
|
||||
summary="composite cursor onto the frame"/>
|
||||
<arg name="output" type="object" interface="wl_output"/>
|
||||
<arg name="x" type="int"/>
|
||||
<arg name="y" type="int"/>
|
||||
<arg name="width" type="int"/>
|
||||
<arg name="height" type="int"/>
|
||||
</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_screencopy_frame_v1" version="3">
|
||||
<description summary="a frame ready for copy">
|
||||
This object represents a single frame.
|
||||
|
||||
When created, a series of buffer events will be sent, each representing a
|
||||
supported buffer type. The "buffer_done" event is sent afterwards to
|
||||
indicate that all supported buffer types have been enumerated. The client
|
||||
will then be able to send a "copy" request. If the capture is successful,
|
||||
the compositor will send a "flags" event followed by a "ready" event.
|
||||
|
||||
For objects version 2 or lower, wl_shm buffers are always supported, ie.
|
||||
the "buffer" event is guaranteed to be sent.
|
||||
|
||||
If the capture failed, the "failed" event is sent. This can happen anytime
|
||||
before the "ready" event.
|
||||
|
||||
Once either a "ready" or a "failed" event is received, the client should
|
||||
destroy the frame.
|
||||
</description>
|
||||
|
||||
<event name="buffer">
|
||||
<description summary="wl_shm buffer information">
|
||||
Provides information about wl_shm buffer parameters that need to be
|
||||
used for this frame. This event is sent once after the frame is created
|
||||
if wl_shm buffers are supported.
|
||||
</description>
|
||||
<arg name="format" type="uint" enum="wl_shm.format" summary="buffer format"/>
|
||||
<arg name="width" type="uint" summary="buffer width"/>
|
||||
<arg name="height" type="uint" summary="buffer height"/>
|
||||
<arg name="stride" type="uint" summary="buffer stride"/>
|
||||
</event>
|
||||
|
||||
<request name="copy">
|
||||
<description summary="copy the frame">
|
||||
Copy the frame to the supplied buffer. The buffer must have the
|
||||
correct size, see zwlr_screencopy_frame_v1.buffer and
|
||||
zwlr_screencopy_frame_v1.linux_dmabuf. The buffer needs to have a
|
||||
supported format.
|
||||
|
||||
If the frame is successfully copied, "flags" and "ready" events are
|
||||
sent. Otherwise, a "failed" event is sent.
|
||||
</description>
|
||||
<arg name="buffer" type="object" interface="wl_buffer"/>
|
||||
</request>
|
||||
|
||||
<enum name="error">
|
||||
<entry name="already_used" value="0"
|
||||
summary="the object has already been used to copy a wl_buffer"/>
|
||||
<entry name="invalid_buffer" value="1"
|
||||
summary="buffer attributes are invalid"/>
|
||||
</enum>
|
||||
|
||||
<enum name="flags" bitfield="true">
|
||||
<entry name="y_invert" value="1" summary="contents are y-inverted"/>
|
||||
</enum>
|
||||
|
||||
<event name="flags">
|
||||
<description summary="frame flags">
|
||||
Provides flags about the frame. This event is sent once before the
|
||||
"ready" event.
|
||||
</description>
|
||||
<arg name="flags" type="uint" enum="flags" summary="frame flags"/>
|
||||
</event>
|
||||
|
||||
<event name="ready">
|
||||
<description summary="indicates frame is available for reading">
|
||||
Called as soon as the frame is copied, indicating it is available
|
||||
for reading. This event includes the time at which the presentation took place.
|
||||
|
||||
The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples,
|
||||
each component being an unsigned 32-bit value. Whole seconds are in
|
||||
tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo,
|
||||
and the additional fractional part in tv_nsec as nanoseconds. Hence,
|
||||
for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part
|
||||
may have an arbitrary offset at start.
|
||||
|
||||
After receiving this event, the client should destroy the object.
|
||||
</description>
|
||||
<arg name="tv_sec_hi" type="uint"
|
||||
summary="high 32 bits of the seconds part of the timestamp"/>
|
||||
<arg name="tv_sec_lo" type="uint"
|
||||
summary="low 32 bits of the seconds part of the timestamp"/>
|
||||
<arg name="tv_nsec" type="uint"
|
||||
summary="nanoseconds part of the timestamp"/>
|
||||
</event>
|
||||
|
||||
<event name="failed">
|
||||
<description summary="frame copy failed">
|
||||
This event indicates that the attempted frame copy has failed.
|
||||
|
||||
After receiving this event, the client should destroy the object.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="delete this object, used or not">
|
||||
Destroys the frame. This request can be sent at any time by the client.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<!-- Version 2 additions -->
|
||||
<request name="copy_with_damage" since="2">
|
||||
<description summary="copy the frame when it's damaged">
|
||||
Same as copy, except it waits until there is damage to copy.
|
||||
</description>
|
||||
<arg name="buffer" type="object" interface="wl_buffer"/>
|
||||
</request>
|
||||
|
||||
<event name="damage" since="2">
|
||||
<description summary="carries the coordinates of the damaged region">
|
||||
This event is sent right before the ready event when copy_with_damage is
|
||||
requested. It may be generated multiple times for each copy_with_damage
|
||||
request.
|
||||
|
||||
The arguments describe a box around an area that has changed since the
|
||||
last copy request that was derived from the current screencopy manager
|
||||
instance.
|
||||
|
||||
The union of all regions received between the call to copy_with_damage
|
||||
and a ready event is the total damage since the prior ready event.
|
||||
</description>
|
||||
<arg name="x" type="uint" summary="damaged x coordinates"/>
|
||||
<arg name="y" type="uint" summary="damaged y coordinates"/>
|
||||
<arg name="width" type="uint" summary="current width"/>
|
||||
<arg name="height" type="uint" summary="current height"/>
|
||||
</event>
|
||||
|
||||
<!-- Version 3 additions -->
|
||||
<event name="linux_dmabuf" since="3">
|
||||
<description summary="linux-dmabuf buffer information">
|
||||
Provides information about linux-dmabuf buffer parameters that need to
|
||||
be used for this frame. This event is sent once after the frame is
|
||||
created if linux-dmabuf buffers are supported.
|
||||
</description>
|
||||
<arg name="format" type="uint" summary="fourcc pixel format"/>
|
||||
<arg name="width" type="uint" summary="buffer width"/>
|
||||
<arg name="height" type="uint" summary="buffer height"/>
|
||||
</event>
|
||||
|
||||
<event name="buffer_done" since="3">
|
||||
<description summary="all buffer types reported">
|
||||
This event is sent once after all buffer events have been sent.
|
||||
|
||||
The client should proceed to create a buffer of one of the supported
|
||||
types, and send a "copy" request.
|
||||
</description>
|
||||
</event>
|
||||
</interface>
|
||||
</protocol>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user