mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-22 11:14:01 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84bb4d4753 | ||
|
|
f5acb716b9 | ||
|
|
b138ec1b0f | ||
|
|
5a72cd9616 | ||
|
|
ce1ecfc6bc | ||
|
|
80472c4db8 | ||
|
|
49513047b2 | ||
|
|
045c5c3a89 | ||
|
|
398b360c48 | ||
|
|
a39196e992 | ||
|
|
2ac1fe670c | ||
|
|
f39529d58c | ||
|
|
8874a804b6 | ||
|
|
5baef07048 | ||
|
|
f402731ef2 | ||
|
|
59f6f1fa71 | ||
|
|
c0f775ae49 | ||
|
|
3d6e45f430 | ||
|
|
85bc7604c2 | ||
|
|
3674d0f480 | ||
|
|
5f124de8d4 | ||
|
|
59a03f450d | ||
|
|
3797422832 | ||
|
|
e4793327b8 | ||
|
|
e18931353b | ||
|
|
cf3c76a79f | ||
|
|
7e18a68b82 | ||
|
|
96a21fbe08 | ||
|
|
fbffa410af | ||
|
|
936b42b4e7 | ||
|
|
eb53d8983e | ||
|
|
4e2afb53cc | ||
|
|
a665ba8ca5 | ||
|
|
d57e32c5cb | ||
|
|
07d8e28668 | ||
|
|
ec22e9e594 | ||
|
|
433b13cb0e | ||
|
|
cbe572eea6 | ||
|
|
c1f1da1d26 | ||
|
|
2008c39af3 | ||
|
|
1f22ad4b33 | ||
|
|
8a871aafac | ||
|
|
0bbe83380c | ||
|
|
6a4ce613bb | ||
|
|
ea0b3159dd | ||
|
|
ad30c2155a | ||
|
|
5142d576f5 | ||
|
|
592ca3ee8f | ||
|
|
6c54855fdd | ||
|
|
41c01a9319 | ||
|
|
f9a9e9baf8 | ||
|
|
053337d9b8 | ||
|
|
4693178345 | ||
|
|
0fc4aac964 | ||
|
|
ab1e57dda5 | ||
|
|
1d2ba798b0 | ||
|
|
78e3308f40 | ||
|
|
99e2368b1c | ||
|
|
c6c14576eb | ||
|
|
f022aa6796 | ||
|
|
0fe56c4bfa | ||
|
|
9378dfc44e | ||
|
|
f65e5ef2f2 | ||
|
|
81cbcef3bc | ||
|
|
e8082ad75d | ||
|
|
5ed5845eaf | ||
|
|
13fafa1dff | ||
|
|
6df71083ec | ||
|
|
863152a441 | ||
|
|
65ee46e330 | ||
|
|
0a46f84aaa | ||
|
|
de24b1515a | ||
|
|
a781b7b236 | ||
|
|
3effe6e7db | ||
|
|
875d32176d | ||
|
|
63ec4bd945 | ||
|
|
9a7177f929 | ||
|
|
4f0cbf5b03 | ||
|
|
95ee917e37 | ||
|
|
4d6d7e0d5f | ||
|
|
ed9e01e438 | ||
|
|
226868b6f0 | ||
|
|
dd068dbd13 | ||
|
|
5f4ba58acb | ||
|
|
881cfd2fd3 | ||
|
|
4f6841d8d2 | ||
|
|
823f5f85d5 | ||
|
|
7fa9c42f46 | ||
|
|
c0135b11e5 | ||
|
|
5bb6058751 | ||
|
|
0c51946f4f | ||
|
|
ef63de44b4 | ||
|
|
0b87df2608 | ||
|
|
ba04356d64 | ||
|
|
8fb83d251c | ||
|
|
3b990694c2 | ||
|
|
0540091a04 | ||
|
|
b32fe1e806 | ||
|
|
ae7ed119a6 | ||
|
|
fa445388e4 | ||
|
|
d23226f3ea | ||
|
|
99d36e9421 | ||
|
|
f273a64703 | ||
|
|
9ea8d2015b | ||
|
|
0604de881e | ||
|
|
16c098d09b | ||
|
|
99c5f96575 | ||
|
|
7cc0955358 | ||
|
|
285ce66d99 | ||
|
|
887509548b | ||
|
|
c580aa26c0 | ||
|
|
e1b2279a70 | ||
|
|
9eaaed3203 | ||
|
|
5c552f97e4 | ||
|
|
212271291c | ||
|
|
905726547d | ||
|
|
4624ceb8f0 | ||
|
|
7372b79c2d | ||
|
|
9522049881 | ||
|
|
ac46c7dd1b | ||
|
|
4cdfc53dcd | ||
|
|
393a54f7eb | ||
|
|
151aedbb68 | ||
|
|
9628628d38 | ||
|
|
c68b7e5333 | ||
|
|
9bd31e30f4 | ||
|
|
7f73a81c59 | ||
|
|
b8cceb7b3a | ||
|
|
ce879a6219 | ||
|
|
22ff24b0e4 | ||
|
|
e99813b07d | ||
|
|
08dde7f79a | ||
|
|
6ce86a07a5 | ||
|
|
f29a48f681 | ||
|
|
feab79df83 | ||
|
|
754040ca42 | ||
|
|
d272e7d9da | ||
|
|
104bbebbc2 | ||
|
|
3306772f97 | ||
|
|
8354113c34 | ||
|
|
bada28ac4b | ||
|
|
f0867798e6 | ||
|
|
2f19ccf707 | ||
|
|
ae3acb2462 | ||
|
|
f1bbb97b2a | ||
|
|
97b5641f67 | ||
|
|
9f136da2e1 | ||
|
|
93a7c17e59 | ||
|
|
618ad1135d | ||
|
|
7aa1b806d2 | ||
|
|
4aac96c845 | ||
|
|
fdabf949f1 | ||
|
|
4c90a36ecf | ||
|
|
f0753dcfd8 | ||
|
|
2c471e1c9e | ||
|
|
cef1eb962e | ||
|
|
00f7db98b5 | ||
|
|
07f89e314f | ||
|
|
54a64eb0b2 | ||
|
|
3604fc83a2 | ||
|
|
3538bdb9cd | ||
|
|
1486eafa75 | ||
|
|
d8ceb12164 | ||
|
|
23faed0bd4 | ||
|
|
95e617fa95 | ||
|
|
e620677f97 | ||
|
|
030974ce75 | ||
|
|
9255596b08 | ||
|
|
48e4709eb5 | ||
|
|
b97c9b0b9a | ||
|
|
17d535b7a4 | ||
|
|
52f393d821 | ||
|
|
c0c2c73e8f | ||
|
|
460013a77d | ||
|
|
8fabf22e3d | ||
|
|
f8cf761fa6 | ||
|
|
78352fd1fc | ||
|
|
dca1bd3591 | ||
|
|
35aa1329e8 | ||
|
|
ed60fb7e4f | ||
|
|
2e8257661c | ||
|
|
fdffc61d39 | ||
|
|
530c58b928 | ||
|
|
12df4bdb8f | ||
|
|
80c85bf929 | ||
|
|
fc42786fec | ||
|
|
be9aeaefbc | ||
|
|
8d36628df1 | ||
|
|
422dd2c9b0 | ||
|
|
d81aca468e | ||
|
|
9ed4acd4f8 | ||
|
|
42080db61b | ||
|
|
b7034fcadb | ||
|
|
d01a6d87d5 | ||
|
|
2934188296 | ||
|
|
25e95d597c | ||
|
|
0091e703a3 | ||
|
|
64d0e40baf | ||
|
|
c653378db7 | ||
|
|
3b527f1f7f | ||
|
|
662dffc141 | ||
|
|
67968aef79 | ||
|
|
f91b43b29d | ||
|
|
5b8f083fe5 | ||
|
|
ea0b158eb8 | ||
|
|
6feac1f79e | ||
|
|
34cf6e6346 | ||
|
|
c8ec045a3c | ||
|
|
d9462c5484 | ||
|
|
778efb832e | ||
|
|
195ec3bf6e | ||
|
|
7802572193 | ||
|
|
0bee6a2844 | ||
|
|
fb96661092 | ||
|
|
aabfb080e1 | ||
|
|
d951dbddbd | ||
|
|
4cf375f2fd | ||
|
|
d4069a778b | ||
|
|
ca7b165eb8 | ||
|
|
c9e6144de8 | ||
|
|
4f9b17503e | ||
|
|
1065bf07b6 | ||
|
|
20b86057d4 | ||
|
|
c31fad6381 | ||
|
|
eadb8cf9c7 | ||
|
|
744fa54ae0 | ||
|
|
4d9d3422d2 | ||
|
|
2ce790bcfe | ||
|
|
44e82c6e42 | ||
|
|
8bff5eb4fc | ||
|
|
2c44b794f1 | ||
|
|
5c9dd50c2e | ||
|
|
7109a52ed1 | ||
|
|
36cfd5faa1 | ||
|
|
60a8310002 | ||
|
|
c57fd7aad1 | ||
|
|
4447d77ba6 | ||
|
|
f89b21a0ef | ||
|
|
4a021a73bf | ||
|
|
b482e9b898 | ||
|
|
26fb129887 | ||
|
|
92d3550dc4 | ||
|
|
c6083d6177 | ||
|
|
45cd00bed3 | ||
|
|
523e0232d2 | ||
|
|
d22dae1494 | ||
|
|
a669253c58 | ||
|
|
124578b695 | ||
|
|
3630b46ee3 | ||
|
|
20aafebd87 | ||
|
|
90c5f65b0f | ||
|
|
589737e318 | ||
|
|
2f4afeb889 | ||
|
|
e6a5fae190 | ||
|
|
52244c01f7 | ||
|
|
44bae0934d | ||
|
|
c90b0cddab | ||
|
|
486febd4ce | ||
|
|
47fe1eb512 | ||
|
|
6a0197d252 | ||
|
|
7f7155b5d5 | ||
|
|
9b04b47e98 | ||
|
|
c694d9938c | ||
|
|
b6436196be | ||
|
|
0253f9daa5 | ||
|
|
521b56f1e1 | ||
|
|
0daf24095c | ||
|
|
7ebe072255 | ||
|
|
7028ea323d | ||
|
|
4147d81f01 | ||
|
|
0d2215e18c | ||
|
|
f6259d0ff7 | ||
|
|
d10ca38651 | ||
|
|
148d8440ec | ||
|
|
c67450cd2e | ||
|
|
754ebab48d | ||
|
|
fde8bb6487 | ||
|
|
afe0dbb104 | ||
|
|
396d490979 | ||
|
|
8bebd68068 | ||
|
|
101841585f | ||
|
|
c30ee19ec6 | ||
|
|
e78951d3c3 | ||
|
|
161c597c55 | ||
|
|
327aad210c | ||
|
|
084e6ce608 | ||
|
|
262acda336 | ||
|
|
5ca033d839 | ||
|
|
d83a0037f3 | ||
|
|
f44b7b9851 | ||
|
|
978203cd52 | ||
|
|
dce1095f8d | ||
|
|
c3fd526698 | ||
|
|
d7b19809aa | ||
|
|
a503e869cf | ||
|
|
0100c64d7a | ||
|
|
a56360d8ed | ||
|
|
9abaf4051e | ||
|
|
8485d66139 | ||
|
|
fc1c9f6c54 | ||
|
|
070090c35a | ||
|
|
d067959f1c | ||
|
|
52abc81011 | ||
|
|
d4fecfcd5c | ||
|
|
ed8fbf47b0 | ||
|
|
ba360213dd | ||
|
|
214de4a570 | ||
|
|
8527b5ed0e | ||
|
|
2ca2e53b6b | ||
|
|
c93767fdfe | ||
|
|
cb67a5c51a | ||
|
|
7d8532b075 | ||
|
|
fdf0d73b30 | ||
|
|
ef05dbed9b |
@@ -514,6 +514,37 @@ Quickshell.execDetached(["dms", "cl", "copy", textToCopy])
|
|||||||
|
|
||||||
**Do NOT use** `globalThis.clipboard` or browser JavaScript APIs - they don't exist in the QML runtime.
|
**Do NOT use** `globalThis.clipboard` or browser JavaScript APIs - they don't exist in the QML runtime.
|
||||||
|
|
||||||
|
## Translations (Optional)
|
||||||
|
|
||||||
|
Ship per-locale files in a `translations/` directory; DMS loads the one matching the active locale and re-reads it on locale changes:
|
||||||
|
|
||||||
|
```
|
||||||
|
MyPlugin/
|
||||||
|
└── translations/
|
||||||
|
├── es.json
|
||||||
|
└── zh_CN.json
|
||||||
|
```
|
||||||
|
|
||||||
|
File names follow DMS locale naming (`es.json`, `pt.json`, `zh_CN.json`); resolution tries the exact tag, then the hyphenated form, then the bare language. No `en.json` - the QML terms are the English strings.
|
||||||
|
|
||||||
|
Each file maps the English term to its translation, using the term as its own context bucket:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Cycle Speed": {
|
||||||
|
"Cycle Speed": "Velocidad de ciclo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In QML, call `I18n.trFor` with the plugin id as a literal string matching `plugin.json`:
|
||||||
|
|
||||||
|
```qml
|
||||||
|
text: I18n.trFor("myPlugin", "Cycle Speed")
|
||||||
|
```
|
||||||
|
|
||||||
|
Lookup order: plugin file, then the global DMS catalog, then the English term. `ExampleEmojiPlugin` in `quickshell/PLUGINS/` demonstrates the full setup. Registry plugins can apply for central POEditor translation via the registry's CONTRIBUTING guide.
|
||||||
|
|
||||||
## Step 11: Validate and Test
|
## Step 11: Validate and Test
|
||||||
|
|
||||||
1. Validate `plugin.json` against the schema at [assets/plugin-schema.json](assets/plugin-schema.json)
|
1. Validate `plugin.json` against the schema at [assets/plugin-schema.json](assets/plugin-schema.json)
|
||||||
|
|||||||
Executable
+150
@@ -0,0 +1,150 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Prunes retired XBPS packages from the R2 bucket so it stays inside the
|
||||||
|
# Cloudflare free tier. Only objects under ARCHIVE_PREFIX are eligible; live
|
||||||
|
# repositories (<repo>/current/) are never touched.
|
||||||
|
#
|
||||||
|
# publish-void-r2.sh writes retired packages to archive/<repo>/<published_at>/.
|
||||||
|
# Snapshots are dropped oldest first:
|
||||||
|
# 1. snapshots older than KEEP_DAYS, keeping KEEP_MIN_SNAPSHOTS per repo
|
||||||
|
# 2. then more until the whole bucket is <= MAX_BUCKET_GB
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
require_env() {
|
||||||
|
local name
|
||||||
|
for name in "$@"; do
|
||||||
|
if [[ -z "${!name:-}" ]]; then
|
||||||
|
echo "error: $name is required" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
require_env R2_BUCKET R2_ENDPOINT
|
||||||
|
|
||||||
|
ARCHIVE_PREFIX="${ARCHIVE_PREFIX:-archive/}"
|
||||||
|
MAX_BUCKET_GB="${MAX_BUCKET_GB:-8}"
|
||||||
|
KEEP_DAYS="${KEEP_DAYS:-30}"
|
||||||
|
KEEP_MIN_SNAPSHOTS="${KEEP_MIN_SNAPSHOTS:-2}"
|
||||||
|
DRY_RUN="${DRY_RUN:-false}"
|
||||||
|
|
||||||
|
case "$ARCHIVE_PREFIX" in
|
||||||
|
*/) ;;
|
||||||
|
*) echo "error: ARCHIVE_PREFIX must end with /" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
aws_r2() {
|
||||||
|
aws --endpoint-url "$R2_ENDPOINT" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
human() {
|
||||||
|
numfmt --to=iec-i --suffix=B "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
objects="$(aws_r2 s3api list-objects-v2 \
|
||||||
|
--bucket "$R2_BUCKET" \
|
||||||
|
--output json \
|
||||||
|
--query 'Contents[].{key:Key,size:Size}')"
|
||||||
|
[[ -n "$objects" && "$objects" != null ]] || objects='[]'
|
||||||
|
|
||||||
|
total="$(jq 'map(.size) | add // 0' <<<"$objects")"
|
||||||
|
budget="$(awk -v g="$MAX_BUCKET_GB" 'BEGIN { printf "%d", g * 1073741824 }')"
|
||||||
|
now="$(date -u +%s)"
|
||||||
|
|
||||||
|
snapshots="$(jq -c --arg p "$ARCHIVE_PREFIX" '
|
||||||
|
map(select(.key | startswith($p)))
|
||||||
|
| map(.rel = (.key[($p | length):] | split("/")))
|
||||||
|
| map(select((.rel | length) >= 3))
|
||||||
|
| group_by(.rel[0:2])
|
||||||
|
| map({
|
||||||
|
repo: .[0].rel[0],
|
||||||
|
stamp: .[0].rel[1],
|
||||||
|
prefix: ($p + (.[0].rel[0:2] | join("/")) + "/"),
|
||||||
|
size: (map(.size) | add),
|
||||||
|
count: length
|
||||||
|
})
|
||||||
|
| sort_by(.stamp)
|
||||||
|
' <<<"$objects")"
|
||||||
|
|
||||||
|
to_delete="$(jq -c \
|
||||||
|
--argjson now "$now" \
|
||||||
|
--argjson keepDays "$KEEP_DAYS" \
|
||||||
|
--argjson keepMin "$KEEP_MIN_SNAPSHOTS" \
|
||||||
|
--argjson total "$total" \
|
||||||
|
--argjson budget "$budget" '
|
||||||
|
def epoch:
|
||||||
|
.stamp
|
||||||
|
| sub("T(?<h>[0-9]{2})-(?<m>[0-9]{2})-(?<s>[0-9]{2})Z$"; "T\(.h):\(.m):\(.s)Z")
|
||||||
|
| try fromdateiso8601 catch null;
|
||||||
|
(group_by(.repo) | map({key: .[0].repo, value: length}) | from_entries) as $perRepo
|
||||||
|
| reduce .[] as $s ({kept: $perRepo, total: $total, delete: []};
|
||||||
|
($s | epoch) as $e
|
||||||
|
| (.kept[$s.repo] > $keepMin and $e != null and ($now - $e) > $keepDays * 86400) as $expired
|
||||||
|
| (.total > $budget) as $over
|
||||||
|
| if $expired or $over then
|
||||||
|
.delete += [$s + {reason: (if $over then "budget" else "age" end)}]
|
||||||
|
| .total -= $s.size
|
||||||
|
| .kept[$s.repo] -= 1
|
||||||
|
else . end)
|
||||||
|
| .delete
|
||||||
|
' <<<"$snapshots")"
|
||||||
|
|
||||||
|
echo "bucket: $R2_BUCKET ($(human "$total") in $(jq length <<<"$objects") objects)"
|
||||||
|
echo "budget: $(human "$budget") (MAX_BUCKET_GB=$MAX_BUCKET_GB)"
|
||||||
|
echo "archive: $(jq length <<<"$snapshots") snapshots, $(human "$(jq 'map(.size) | add // 0' <<<"$snapshots")")"
|
||||||
|
echo "policy: KEEP_DAYS=$KEEP_DAYS KEEP_MIN_SNAPSHOTS=$KEEP_MIN_SNAPSHOTS DRY_RUN=$DRY_RUN"
|
||||||
|
|
||||||
|
deleted=0
|
||||||
|
freed=0
|
||||||
|
while IFS=$'\t' read -r prefix size count reason; do
|
||||||
|
[[ -n "$prefix" ]] || continue
|
||||||
|
# Belt and braces: never delete outside the archive or anything live.
|
||||||
|
case "$prefix" in
|
||||||
|
"$ARCHIVE_PREFIX"*) ;;
|
||||||
|
*) echo "error: refusing to delete $prefix (outside $ARCHIVE_PREFIX)" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case "$prefix" in
|
||||||
|
*/current/*) echo "error: refusing to delete live prefix $prefix" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [[ "$DRY_RUN" == "true" ]]; then
|
||||||
|
echo "would delete $prefix ($count objects, $(human "$size"), $reason)"
|
||||||
|
else
|
||||||
|
echo "deleting $prefix ($count objects, $(human "$size"), $reason)"
|
||||||
|
aws_r2 s3 rm "s3://${R2_BUCKET}/${prefix}" --recursive --only-show-errors
|
||||||
|
fi
|
||||||
|
deleted=$((deleted + 1))
|
||||||
|
freed=$((freed + size))
|
||||||
|
done < <(jq -r '.[] | [.prefix, .size, .count, .reason] | @tsv' <<<"$to_delete")
|
||||||
|
|
||||||
|
remaining=$((total - freed))
|
||||||
|
echo "pruned: $deleted snapshots, $(human "$freed") freed, $(human "$remaining") remaining"
|
||||||
|
|
||||||
|
if (( remaining > budget )); then
|
||||||
|
echo "::warning::bucket is still $(human "$remaining") after pruning (budget $(human "$budget")); live repositories exceed the budget"
|
||||||
|
fi
|
||||||
|
|
||||||
|
note=""
|
||||||
|
if [[ "$DRY_RUN" == "true" ]]; then
|
||||||
|
note=" (dry run)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "${GITHUB_STEP_SUMMARY:-}" ]]; then
|
||||||
|
{
|
||||||
|
echo "### R2 prune: \`$R2_BUCKET\`"
|
||||||
|
echo
|
||||||
|
echo "| | |"
|
||||||
|
echo "|---|---|"
|
||||||
|
echo "| Before | $(human "$total") |"
|
||||||
|
echo "| Budget | $(human "$budget") |"
|
||||||
|
echo "| Snapshots pruned | $deleted$note |"
|
||||||
|
echo "| Freed | $(human "$freed") |"
|
||||||
|
echo "| After | $(human "$remaining") |"
|
||||||
|
if (( deleted > 0 )); then
|
||||||
|
echo
|
||||||
|
echo "| Snapshot | Objects | Bytes | Reason |"
|
||||||
|
echo "|---|---|---|---|"
|
||||||
|
jq -r '.[] | "| `\(.prefix)` | \(.count) | \(.size) | \(.reason) |"' <<<"$to_delete"
|
||||||
|
fi
|
||||||
|
} >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
name: Claude Review
|
||||||
|
|
||||||
|
# `/claude review` on a PR by a member of an allowed team. Review only, no edits or pushes.
|
||||||
|
# Secrets: CLAUDE_CODE_OAUTH_TOKEN (claude setup-token), CLAUDE_REVIEW_ORG_READ_TOKEN (PAT, org Members: read)
|
||||||
|
|
||||||
|
on:
|
||||||
|
issue_comment:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
gate:
|
||||||
|
if: github.event.issue.pull_request && startsWith(github.event.comment.body, '/claude review')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
outputs:
|
||||||
|
allowed: ${{ steps.team.outputs.allowed }}
|
||||||
|
steps:
|
||||||
|
- name: Check team membership
|
||||||
|
id: team
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.CLAUDE_REVIEW_ORG_READ_TOKEN }}
|
||||||
|
ORG: ${{ github.repository_owner }}
|
||||||
|
USER: ${{ github.event.comment.user.login }}
|
||||||
|
TEAMS: dms-collaborators owners
|
||||||
|
run: |
|
||||||
|
allowed=false
|
||||||
|
for team in $TEAMS; do
|
||||||
|
state=$(gh api "orgs/$ORG/teams/$team/memberships/$USER" --jq .state 2>/dev/null || true)
|
||||||
|
[ "$state" = active ] && allowed=true && break
|
||||||
|
done
|
||||||
|
echo "allowed=$allowed" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: React
|
||||||
|
continue-on-error: true
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
ALLOWED: ${{ steps.team.outputs.allowed }}
|
||||||
|
COMMENT_ID: ${{ github.event.comment.id }}
|
||||||
|
run: |
|
||||||
|
[ "$ALLOWED" = true ] && content=eyes || content=-1
|
||||||
|
gh api -X POST "repos/${{ github.repository }}/issues/comments/$COMMENT_ID/reactions" -f "content=$content"
|
||||||
|
|
||||||
|
review:
|
||||||
|
needs: gate
|
||||||
|
if: needs.gate.outputs.allowed == 'true'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 30
|
||||||
|
concurrency:
|
||||||
|
group: claude-review-${{ github.event.issue.number }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
issues: write
|
||||||
|
id-token: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
ref: refs/pull/${{ github.event.issue.number }}/head
|
||||||
|
fetch-depth: 0
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- uses: anthropics/claude-code-action@v1
|
||||||
|
with:
|
||||||
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||||
|
prompt: |
|
||||||
|
Review pull request #${{ github.event.issue.number }} in ${{ github.repository }}, requested by @${{ github.event.comment.user.login }}:
|
||||||
|
|
||||||
|
---
|
||||||
|
${{ github.event.comment.body }}
|
||||||
|
---
|
||||||
|
|
||||||
|
Text after `/claude review` is an optional focus. Honor it but still report anything serious outside it.
|
||||||
|
The PR head is checked out. Use `gh pr view ${{ github.event.issue.number }}` and `gh pr diff ${{ github.event.issue.number }}`, read surrounding code as needed.
|
||||||
|
|
||||||
|
Steps:
|
||||||
|
1. Read CONTRIBUTING.md.
|
||||||
|
2. Read the PR title, description and diff.
|
||||||
|
3. Check existing comments so you don't repeat them: `gh api repos/${{ github.repository }}/pulls/${{ github.event.issue.number }}/comments` and `gh pr view ${{ github.event.issue.number }} --comments`. Skip findings already covered by a comment that still applies.
|
||||||
|
4. Verify each candidate finding against the code. Drop anything you can't confirm.
|
||||||
|
5. Post one inline comment per confirmed finding with `mcp__github_inline_comment__create_inline_comment` (`confirmed: true`). Say what's wrong, why, and the fix. Add a committable suggestion only if it alone fully fixes the issue.
|
||||||
|
6. Post one new summary comment with `gh pr comment ${{ github.event.issue.number }} --body ...`. Never edit earlier comments. Format:
|
||||||
|
|
||||||
|
## Claude review
|
||||||
|
<one line verdict>
|
||||||
|
- <finding, file:line> per inline comment posted this run
|
||||||
|
Checked: <one line>. Model: claude-opus-5.
|
||||||
|
|
||||||
|
No findings: "No issues found." plus what was checked.
|
||||||
|
|
||||||
|
Flag only issues introduced by this PR:
|
||||||
|
- Bugs: wrong logic, crashes, nil/undefined access, races, leaks (timers, connections, processes, handles), bad Go error handling, edge cases that will clearly be hit.
|
||||||
|
- QML: undefined refs, missing imports, binding loops, bindings that never update, handlers on the wrong object, raw Flickable/ListView/ScrollView instead of the Dank* wrappers.
|
||||||
|
- Security: command injection, path traversal, unsafe file permissions, secrets in code.
|
||||||
|
- CONTRIBUTING.md rules: user-facing strings in `I18n.tr()` with translator context, reuse existing terms from quickshell/translations/en.json; en.json and template.json must not be touched in a PR; mocks under core/internal/mocks/ are regenerated not hand-edited; new dank-qml-common contract properties need the matching library change.
|
||||||
|
- Go: unformatted code, go.mod/go.sum out of sync with imports, tests that no longer compile.
|
||||||
|
|
||||||
|
Don't flag style or naming, pre-existing issues, anything a linter or compiler catches, speculative input-dependent problems, missing tests or docs, or refactor ideas. Not sure it's real: don't post it.
|
||||||
|
|
||||||
|
Review only. Never edit files, commit, push, approve or request changes. Post each finding once. Keep comments short.
|
||||||
|
claude_args: |
|
||||||
|
--model claude-opus-5
|
||||||
|
--max-turns 80
|
||||||
|
--allowedTools "Read,Grep,Glob,LS,Task,mcp__github_inline_comment__create_inline_comment,Bash(gh pr view:*),Bash(gh pr diff:*),Bash(gh pr comment:*),Bash(gh api repos/${{ github.repository }}/pulls/${{ github.event.issue.number }}/comments:*),Bash(git diff:*),Bash(git log:*),Bash(git show:*),Bash(git blame:*)"
|
||||||
|
--disallowedTools "Edit,Write,MultiEdit,NotebookEdit,WebFetch,WebSearch"
|
||||||
|
|
||||||
|
- if: failure()
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
gh pr comment ${{ github.event.issue.number }} --body "Claude review failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||||
@@ -31,18 +31,25 @@ jobs:
|
|||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
- name: Install flatpak
|
- name: Install flatpak
|
||||||
run: sudo apt update && sudo apt install -y flatpak
|
run: sudo apt-get update && sudo apt-get install -y flatpak
|
||||||
|
|
||||||
- name: Add flathub
|
- name: Cache flatpak runtime
|
||||||
run: sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ~/.local/share/flatpak
|
||||||
|
key: flatpak-org.freedesktop.Platform-24.08
|
||||||
|
|
||||||
- name: Add a flatpak that mutagen could support
|
- name: Install a flatpak for tests
|
||||||
run: sudo flatpak install -y org.freedesktop.Platform/x86_64/24.08 app.zen_browser.zen
|
run: |
|
||||||
|
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
|
||||||
|
flatpak info org.freedesktop.Platform//24.08 >/dev/null 2>&1 || \
|
||||||
|
flatpak install --user -y org.freedesktop.Platform//24.08
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v6
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version-file: ./core/go.mod
|
go-version-file: ./core/go.mod
|
||||||
|
cache-dependency-path: ./core/go.sum
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: go test -v ./...
|
run: go test -v ./...
|
||||||
@@ -75,6 +82,7 @@ jobs:
|
|||||||
uses: actions/setup-go@v6
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version-file: ./core/go.mod
|
go-version-file: ./core/go.mod
|
||||||
|
cache-dependency-path: ./core/go.sum
|
||||||
|
|
||||||
- name: Build all packages
|
- name: Build all packages
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
name: Prune Void R2 archive
|
||||||
|
|
||||||
|
# Keeps the danklinux-void bucket inside the Cloudflare R2 free tier by
|
||||||
|
# deleting the oldest archive/<repo>/<timestamp>/ snapshots written by
|
||||||
|
# publish-void-r2.sh. Live repositories (<repo>/current/) are never touched.
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "30 6 * * *"
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
dry_run:
|
||||||
|
description: 'Report what would be deleted without deleting'
|
||||||
|
type: boolean
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
max_bucket_gb:
|
||||||
|
description: 'Total bucket size to prune down to (GB)'
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
default: '8'
|
||||||
|
keep_days:
|
||||||
|
description: 'Delete archive snapshots older than this many days'
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
default: '30'
|
||||||
|
keep_min_snapshots:
|
||||||
|
description: 'Always keep at least this many snapshots per repo (age rule only)'
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
default: '2'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
# Serialize with the XBPS publisher so a snapshot is never pruned mid-publish.
|
||||||
|
concurrency:
|
||||||
|
group: void-xbps-${{ github.ref }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
prune:
|
||||||
|
name: Prune archive snapshots
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.repository == 'AvengeMedia/DankMaterialShell'
|
||||||
|
env:
|
||||||
|
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
|
||||||
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
|
||||||
|
AWS_DEFAULT_REGION: auto
|
||||||
|
AWS_EC2_METADATA_DISABLED: true
|
||||||
|
R2_BUCKET: danklinux-void
|
||||||
|
R2_ENDPOINT: https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com
|
||||||
|
ARCHIVE_PREFIX: archive/
|
||||||
|
MAX_BUCKET_GB: ${{ github.event.inputs.max_bucket_gb || '8' }}
|
||||||
|
KEEP_DAYS: ${{ github.event.inputs.keep_days || '30' }}
|
||||||
|
KEEP_MIN_SNAPSHOTS: ${{ github.event.inputs.keep_min_snapshots || '2' }}
|
||||||
|
DRY_RUN: ${{ github.event.inputs.dry_run || 'false' }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
sparse-checkout: .github/scripts
|
||||||
|
sparse-checkout-cone-mode: false
|
||||||
|
|
||||||
|
- name: Prune archive
|
||||||
|
run: bash .github/scripts/prune-void-r2.sh
|
||||||
@@ -23,7 +23,26 @@ concurrency:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
verify-version:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
ref: ${{ inputs.tag }}
|
||||||
|
|
||||||
|
- name: Verify quickshell/VERSION matches the tag
|
||||||
|
env:
|
||||||
|
TAG: ${{ inputs.tag }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
version=$(cat quickshell/VERSION)
|
||||||
|
if [ "${version#v}" != "${TAG#v}" ]; then
|
||||||
|
echo "::error::quickshell/VERSION is '${version}' but the tag is '${TAG}'"; exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
build-core:
|
build-core:
|
||||||
|
needs: [verify-version]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@@ -497,3 +516,41 @@ jobs:
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
bash scripts/notify-issues.sh "$PREVIOUS_TAG" "$TAG"
|
bash scripts/notify-issues.sh "$PREVIOUS_TAG" "$TAG"
|
||||||
|
|
||||||
|
# Pin the satellite repos to this release's version. Each dispatch target
|
||||||
|
# decides for itself whether to tag: X.Y.0 always tags, X.Y.Z skips when the
|
||||||
|
# repo has no commits of its own since its last tag.
|
||||||
|
fanout:
|
||||||
|
name: Pin ${{ matrix.repo }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [release]
|
||||||
|
if: ${{ !contains(inputs.tag, '-') }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
repo:
|
||||||
|
- dgop
|
||||||
|
- dankgo
|
||||||
|
- dank-qml-common
|
||||||
|
- dankcalendar
|
||||||
|
- danksearch
|
||||||
|
- dank-greeter
|
||||||
|
steps:
|
||||||
|
- name: Create GitHub App token
|
||||||
|
id: app_token
|
||||||
|
uses: actions/create-github-app-token@v3
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.APP_ID }}
|
||||||
|
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||||
|
owner: ${{ github.repository_owner }}
|
||||||
|
repositories: ${{ matrix.repo }}
|
||||||
|
|
||||||
|
- name: Dispatch pin-release
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ steps.app_token.outputs.token }}
|
||||||
|
REPO: ${{ github.repository_owner }}/${{ matrix.repo }}
|
||||||
|
TAG: ${{ inputs.tag }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
gh workflow run pin-release.yml --repo "$REPO" -f version="$TAG"
|
||||||
|
echo "dispatched pin-release ${TAG} to ${REPO}" >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ core.*
|
|||||||
.envrc
|
.envrc
|
||||||
.direnv/
|
.direnv/
|
||||||
quickshell/dms-plugins
|
quickshell/dms-plugins
|
||||||
|
quickshell/dms-plugins-external
|
||||||
__pycache__
|
__pycache__
|
||||||
|
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ repos:
|
|||||||
name: settings search index is up to date
|
name: settings search index is up to date
|
||||||
entry: bash -c 'python3 quickshell/translations/extract_settings_index.py >/dev/null || exit 1; if ! git diff --exit-code -- quickshell/translations/settings_search_index.json; then echo "settings_search_index.json is out of date; run quickshell/translations/extract_settings_index.py and stage the result" >&2; exit 1; fi'
|
entry: bash -c 'python3 quickshell/translations/extract_settings_index.py >/dev/null || exit 1; if ! git diff --exit-code -- quickshell/translations/settings_search_index.json; then echo "settings_search_index.json is out of date; run quickshell/translations/extract_settings_index.py and stage the result" >&2; exit 1; fi'
|
||||||
language: system
|
language: system
|
||||||
files: ^quickshell/(Modules/Settings/.*\.qml|Modals/Settings/SettingsSidebar\.qml|translations/extract_settings_index\.py)$
|
files: ^quickshell/(Modules/Settings/.*\.qml|Common/SettingsTabs\.qml|Services/SettingsSearchService\.qml|translations/(extract_settings_index\.py|en\.json))$
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
- repo: local
|
- repo: local
|
||||||
hooks:
|
hooks:
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
## What is this?
|
||||||
|
|
||||||
|
DMS is an open-source, MIT-licensed desktop shell for Wayland compositors on linux. Like KWin, gnome-shell, or cosmic-shell - but designed to work with many different compositors.
|
||||||
|
|
||||||
|
## Repo Structure:
|
||||||
|
|
||||||
|
There are 2 main apps in the repo, they are designed to operate together. Additionally there is a dank-qml-common submodule which contains common QML widgets, like a shared library, that is leveraged by other quickshell apps in the Dank Desktop suite.
|
||||||
|
|
||||||
|
There is a GO module (dankgo) that is used by core for certain common go functionality.
|
||||||
|
|
||||||
|
### 1- core:
|
||||||
|
|
||||||
|
Contains 2 apps:
|
||||||
|
|
||||||
|
1. cmd/dms - The parent process for quickshell (shell runner), and general purpose tools and utilities (such as screenshot, clipboard). It also runs the unix socket server the UI uses for functionality quickshell doesn't provide.
|
||||||
|
2. cmd/dankinstall - An entirely separate app, a TUI for quickly installing DMS
|
||||||
|
|
||||||
|
### 2- quickshell:
|
||||||
|
|
||||||
|
A quickshell application, the entirety of the UI for DMS. It uses quickshells own services supplemented by the core unix socket server for additional functionality.
|
||||||
|
|
||||||
|
Layout:
|
||||||
|
|
||||||
|
- Common/ - app-wide singletons and helpers (Theme, SettingsData, SessionData, I18n, PopoutManager)
|
||||||
|
- Services/ - headless singletons that talk to the system and to core
|
||||||
|
- Modules/ - the visible shell: DankBar, ControlCenter, Dock, Lock, Notifications, OSD, and so on
|
||||||
|
- Modals/ - large standalone surfaces: Settings, DankLauncherV2, Clipboard, Greeter
|
||||||
|
- Widgets/ - reusable Dank* components (DankListView, DankIcon, DankToggle, ...)
|
||||||
|
- PLUGINS/ - the plugin system; third-party plugins load from the user's config dir
|
||||||
|
- translations/ - POEditor-synced catalogs
|
||||||
|
|
||||||
|
### Other directories:
|
||||||
|
|
||||||
|
- docs/ - IPC reference, custom theme docs
|
||||||
|
- distro/ - packaging for debian, fedora, nix, opensuse, ubuntu, void
|
||||||
|
|
||||||
|
## General Rules:
|
||||||
|
|
||||||
|
- Keep it simple. Do not overcomplicate things.
|
||||||
|
- Follow each apps' own conventions. For example, QML widgets use Theme tokens instead of hardcoding colors, spacing, or other constants.
|
||||||
|
- Resource usage is extremely important to DMS. The shell runs 24/7 on every machine it's installed on - audit any change for idle CPU cost, extra processes, timers, and retained memory.
|
||||||
|
- `core` and `quickshell` are tightly coupled through the unix socket protocol. When you are changing one, be mindful of the consequences for the other.
|
||||||
|
- Use the Dank* wrappers in Widgets/ (DankListView, DankFlickable, and so on) instead of raw ListView/Flickable/ScrollView.
|
||||||
|
- All user-facing text goes through I18n.tr(). Prefer reusing existing catalog terms over adding new ones. Never edit the translation catalogs by hand, they are synced with POEditor.
|
||||||
|
- Do not start editing code in response to a question. We'll tell you when to edit code.
|
||||||
|
- Do not leave paragraphs of comments on top of the code. You should try to avoid them as much as possible with understandable function names and code. If they are necessary even then, make them concise. Remove such comments when you come by them in the codebase. Comments should always move with code, not be left behind.
|
||||||
|
- Use guard statement patterns in any code you write.
|
||||||
|
- Do not write any useless tests, tests should cover input output validation - not useless things like "main.go contains func main()"
|
||||||
|
- Do not edit generated code directly, mocks are generated with mockery. Not edited by hand
|
||||||
|
- If we are missing a glaring issue when we ask you to do something, do not hesitate to point it out.
|
||||||
|
- Reinvent the wheel but do not reinvent the car. If you are solving a simple problem do not introduce a library. If you are solving a complex but a common problem, there is likely a modern library for it, if so, use it.
|
||||||
|
- Never commit or push code unless explicitly asked to do so.
|
||||||
|
- Never make a PR unless explicitly asked to do so.
|
||||||
|
|
||||||
|
## Commit Messages
|
||||||
|
|
||||||
|
Commit messages start with the part of the system they touched, followed by a short lowercase explanation of the work:
|
||||||
|
|
||||||
|
network: report wifi state from the associated adapter
|
||||||
|
doctor: flag config files that fail to parse
|
||||||
|
|
||||||
|
The title should be concise. Description should explain the work in more detail (only if required) while still being concise. Use simple language, do not try to sound smart.
|
||||||
@@ -159,6 +159,8 @@ Text {
|
|||||||
|
|
||||||
Preferably, try to keep new terms to a minimum and re-use existing terms where possible. See `quickshell/translations/en.json` for the list of existing terms. (This isn't always possible obviously, but instead of using `Auto-connect` you would use `Autoconnect` since it's already translated)
|
Preferably, try to keep new terms to a minimum and re-use existing terms where possible. See `quickshell/translations/en.json` for the list of existing terms. (This isn't always possible obviously, but instead of using `Auto-connect` you would use `Autoconnect` since it's already translated)
|
||||||
|
|
||||||
|
Don't re-extract the translations. `en.json` and `template.json` are synced with POEditor by a maintainer script, so running `extract_translations.py` in your PR just makes a diff that fights the next sync. Add your `I18n.tr()` calls and leave the catalogs alone. (`settings_search_index.json` is the exception, the pre-commit hook regenerates that one when you touch settings QML.)
|
||||||
|
|
||||||
Strings inside `quickshell/DankCommon/` are owned by the dank-qml-common repo but stay in the DMS POEditor project — extraction here deliberately skips them, and `scripts/i18nsync.py sync` uploads the union of app terms and the submodule's terms instead (common terms carry the `dank-qml-common` tag). On download the sync splits the exports: app translations go to `quickshell/translations/poexports/`, common translations go to `dank-qml-common/DankCommon/translations/poexports/` for you to commit in that repo and bump. At runtime `I18n` merges both catalogs (app terms win). Other apps (dankcalendar) keep their own POEditor projects and merge the `dank-qml-common`-tagged terms from the DMS project.
|
Strings inside `quickshell/DankCommon/` are owned by the dank-qml-common repo but stay in the DMS POEditor project — extraction here deliberately skips them, and `scripts/i18nsync.py sync` uploads the union of app terms and the submodule's terms instead (common terms carry the `dank-qml-common` tag). On download the sync splits the exports: app translations go to `quickshell/translations/poexports/`, common translations go to `dank-qml-common/DankCommon/translations/poexports/` for you to commit in that repo and bump. At runtime `I18n` merges both catalogs (app terms win). Other apps (dankcalendar) keep their own POEditor projects and merge the `dank-qml-common`-tagged terms from the DMS project.
|
||||||
|
|
||||||
### GO (`core` directory)
|
### GO (`core` directory)
|
||||||
@@ -169,6 +171,21 @@ Strings inside `quickshell/DankCommon/` are owned by the dank-qml-common repo bu
|
|||||||
4. Run `go mod tidy`
|
4. Run `go mod tidy`
|
||||||
5. Open pull request
|
5. Open pull request
|
||||||
|
|
||||||
|
golangci-lint runs as a pre-commit hook and covers `go vet`, so there's nothing separate to run. If you run `go vet ./...` yourself you'll see complaints about the generated mocks, ignore them, `core/.golangci.yml` excludes that directory.
|
||||||
|
|
||||||
|
#### Mocks
|
||||||
|
|
||||||
|
Test mocks under `core/internal/mocks/` are generated with [mockery](https://vektra.github.io/mockery/) v3. Don't edit them by hand, regenerate after changing a mocked interface:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd core
|
||||||
|
go run github.com/vektra/mockery/v3@latest
|
||||||
|
# or with it installed (go install github.com/vektra/mockery/v3@latest):
|
||||||
|
mockery
|
||||||
|
```
|
||||||
|
|
||||||
|
`core/.mockery.yml` lists every mocked interface and where its mock goes (e.g. `network.Backend` -> `internal/mocks/network/mock_Backend.go`). To mock a new interface, add it there under its package and regenerate.
|
||||||
|
|
||||||
## Pull request
|
## Pull request
|
||||||
|
|
||||||
Include screenshots/video if applicable in your pull request if applicable, to visualize what your change is affecting.
|
Include screenshots/video if applicable in your pull request if applicable, to visualize what your change is affecting.
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ SHELL_INSTALL_DIR=$(DATA_DIR)/quickshell/dms
|
|||||||
ASSETS_DIR=assets
|
ASSETS_DIR=assets
|
||||||
APPLICATIONS_DIR=$(DATA_DIR)/applications
|
APPLICATIONS_DIR=$(DATA_DIR)/applications
|
||||||
|
|
||||||
.PHONY: all build dev run clean lint-qml 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
|
.PHONY: all build dev run clean lint-qml install install-bin install-completions install-systemd install-icon install-desktop uninstall uninstall-bin uninstall-shell uninstall-completions uninstall-systemd uninstall-icon uninstall-desktop help
|
||||||
|
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
@@ -53,14 +53,6 @@ install-bin:
|
|||||||
@install -D -m 755 $(BUILD_DIR)/$(BINARY_NAME) $(INSTALL_DIR)/$(BINARY_NAME)
|
@install -D -m 755 $(BUILD_DIR)/$(BINARY_NAME) $(INSTALL_DIR)/$(BINARY_NAME)
|
||||||
@echo "Binary installed"
|
@echo "Binary installed"
|
||||||
|
|
||||||
install-shell:
|
|
||||||
@echo "Installing shell files to $(SHELL_INSTALL_DIR)..."
|
|
||||||
@test -e $(SHELL_DIR)/DankCommon/Widgets/DankIcon.qml || { echo "DankCommon missing: run git submodule update --init"; exit 1; }
|
|
||||||
@mkdir -p $(SHELL_INSTALL_DIR)
|
|
||||||
@cp -rL $(SHELL_DIR)/* $(SHELL_INSTALL_DIR)/
|
|
||||||
@rm -rf $(SHELL_INSTALL_DIR)/.git* $(SHELL_INSTALL_DIR)/.github
|
|
||||||
@echo "Shell files installed"
|
|
||||||
|
|
||||||
install-completions:
|
install-completions:
|
||||||
@echo "Installing shell completions..."
|
@echo "Installing shell completions..."
|
||||||
@mkdir -p $(DATA_DIR)/bash-completion/completions
|
@mkdir -p $(DATA_DIR)/bash-completion/completions
|
||||||
@@ -98,7 +90,7 @@ install-desktop:
|
|||||||
@update-desktop-database -q $(APPLICATIONS_DIR) 2>/dev/null || true
|
@update-desktop-database -q $(APPLICATIONS_DIR) 2>/dev/null || true
|
||||||
@echo "Desktop entries installed"
|
@echo "Desktop entries installed"
|
||||||
|
|
||||||
install: install-bin install-shell install-completions install-systemd install-icon install-desktop
|
install: install-bin install-completions install-systemd install-icon install-desktop
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Installation complete!"
|
@echo "Installation complete!"
|
||||||
@echo ""
|
@echo ""
|
||||||
@@ -111,7 +103,7 @@ uninstall-bin:
|
|||||||
@echo "Binary removed"
|
@echo "Binary removed"
|
||||||
|
|
||||||
uninstall-shell:
|
uninstall-shell:
|
||||||
@echo "Removing shell files from $(SHELL_INSTALL_DIR)..."
|
@echo "Removing pre-1.6 shell files from $(SHELL_INSTALL_DIR)..."
|
||||||
@rm -rf $(SHELL_INSTALL_DIR)
|
@rm -rf $(SHELL_INSTALL_DIR)
|
||||||
@echo "Shell files removed"
|
@echo "Shell files removed"
|
||||||
|
|
||||||
@@ -159,7 +151,6 @@ help:
|
|||||||
@echo "Install:"
|
@echo "Install:"
|
||||||
@echo " install - Build and install everything (requires sudo)"
|
@echo " install - Build and install everything (requires sudo)"
|
||||||
@echo " install-bin - Install only the binary"
|
@echo " install-bin - Install only the binary"
|
||||||
@echo " install-shell - Install only shell files"
|
|
||||||
@echo " install-completions - Install only shell completions"
|
@echo " install-completions - Install only shell completions"
|
||||||
@echo " install-systemd - Install only systemd service"
|
@echo " install-systemd - Install only systemd service"
|
||||||
@echo " install-icon - Install only icon"
|
@echo " install-icon - Install only icon"
|
||||||
@@ -168,7 +159,7 @@ help:
|
|||||||
@echo "Uninstall:"
|
@echo "Uninstall:"
|
||||||
@echo " uninstall - Remove everything (requires sudo)"
|
@echo " uninstall - Remove everything (requires sudo)"
|
||||||
@echo " uninstall-bin - Remove only the binary"
|
@echo " uninstall-bin - Remove only the binary"
|
||||||
@echo " uninstall-shell - Remove only shell files"
|
@echo " uninstall-shell - Remove shell files left by pre-1.6 installs"
|
||||||
@echo " uninstall-completions - Remove only shell completions"
|
@echo " uninstall-completions - Remove only shell completions"
|
||||||
@echo " uninstall-systemd - Remove only systemd service"
|
@echo " uninstall-systemd - Remove only systemd service"
|
||||||
@echo " uninstall-icon - Remove only icon"
|
@echo " uninstall-icon - Remove only icon"
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ DankMaterialShell/
|
|||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
https://github.com/user-attachments/assets/1200a739-7770-4601-8b85-695ca527819a
|
https://github.com/user-attachments/assets/d310418b-cc44-4ace-ac0d-2fc0c851291d
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -203,7 +203,7 @@ For documentation contributions, see [DankLinux-Docs](https://github.com/AvengeM
|
|||||||
|
|
||||||
## Star History
|
## Star History
|
||||||
|
|
||||||
[](https://www.star-history.com/#AvengeMedia/DankMaterialShell&type=date&legend=top-left)
|
[](https://star-history.dera.page/#AvengeMedia/DankMaterialShell&type=date&legend=top-left)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,13 @@ Requisite=graphical-session.target
|
|||||||
Type=dbus
|
Type=dbus
|
||||||
BusName=org.freedesktop.Notifications
|
BusName=org.freedesktop.Notifications
|
||||||
ExecStart=/usr/bin/dms run --session
|
ExecStart=/usr/bin/dms run --session
|
||||||
ExecReload=/usr/bin/pkill -USR1 -x dms
|
ExecReload=/bin/kill -USR1 $MAINPID
|
||||||
|
LimitNOFILE=16384:infinity
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
RestartForceExitStatus=TEMPFAIL
|
||||||
|
SuccessExitStatus=TEMPFAIL
|
||||||
RestartSec=1.23
|
RestartSec=1.23
|
||||||
|
TimeoutStartSec=90s
|
||||||
TimeoutStopSec=10
|
TimeoutStopSec=10
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
|
|||||||
+5
-1
@@ -72,7 +72,6 @@ linters:
|
|||||||
- path: _test\.go
|
- path: _test\.go
|
||||||
linters:
|
linters:
|
||||||
- errcheck
|
- errcheck
|
||||||
- govet
|
|
||||||
- unused
|
- unused
|
||||||
- ineffassign
|
- ineffassign
|
||||||
- staticcheck
|
- staticcheck
|
||||||
@@ -97,6 +96,11 @@ linters:
|
|||||||
- linters:
|
- linters:
|
||||||
- ineffassign
|
- ineffassign
|
||||||
path: internal/proto/
|
path: internal/proto/
|
||||||
|
# Exclude proto pkg SA4006 (generated protocol code has trailing dead offset increments)
|
||||||
|
- linters:
|
||||||
|
- staticcheck
|
||||||
|
text: "SA4006:"
|
||||||
|
path: internal/proto/
|
||||||
# binary.Write/Read to bytes.Buffer can't fail
|
# binary.Write/Read to bytes.Buffer can't fail
|
||||||
- linters:
|
- linters:
|
||||||
- errcheck
|
- errcheck
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/golangci/golangci-lint
|
- repo: https://github.com/golangci/golangci-lint
|
||||||
rev: v2.12.2
|
rev: v2.13.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: golangci-lint-fmt
|
- id: golangci-lint-fmt
|
||||||
require_serial: true
|
require_serial: true
|
||||||
|
|||||||
+2
-1
@@ -135,7 +135,8 @@ fmt:
|
|||||||
|
|
||||||
vet:
|
vet:
|
||||||
@echo "Running go vet..."
|
@echo "Running go vet..."
|
||||||
$(GO) vet ./...
|
$(GO) vet $$($(GO) list ./... | grep -v internal/highlight/lexers)
|
||||||
|
$(GO) vet -composites=false ./internal/highlight/lexers
|
||||||
|
|
||||||
deps:
|
deps:
|
||||||
@echo "Updating dependencies..."
|
@echo "Updating dependencies..."
|
||||||
|
|||||||
+18
-8
@@ -132,9 +132,11 @@ This runs gofmt, golangci-lint, tests, and builds before each commit when `core/
|
|||||||
|
|
||||||
**Regenerating Wayland Protocol Bindings:**
|
**Regenerating Wayland Protocol Bindings:**
|
||||||
|
|
||||||
|
Protocol XML files live in `internal/proto/xml/`. Bindings are generated with the [dankgo](https://github.com/AvengeMedia/dankgo) `go-wayland-scanner`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go install github.com/rajveermalviya/go-wayland/cmd/go-wayland-scanner@latest
|
go run github.com/AvengeMedia/dankgo/cmd/go-wayland-scanner@latest \
|
||||||
go-wayland-scanner -i internal/proto/xml/wlr-gamma-control-unstable-v1.xml \
|
-i internal/proto/xml/wlr-gamma-control-unstable-v1.xml \
|
||||||
-pkg wlr_gamma_control -o internal/proto/wlr_gamma_control/gamma_control.go
|
-pkg wlr_gamma_control -o internal/proto/wlr_gamma_control/gamma_control.go
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -155,17 +157,25 @@ curl -fsSL https://install.danklinux.com | sh
|
|||||||
|
|
||||||
**Headless (unattended):**
|
**Headless (unattended):**
|
||||||
|
|
||||||
Headless mode requires cached sudo credentials. Run `sudo -v` first:
|
Headless mode requires cached credentials or a passwordless rule for your privilege escalation tool (sudo, doas, or run0). With sudo, run `sudo -v` first:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo -v && curl -fsSL https://install.danklinux.com | sh -s -- -c niri -t ghostty -y
|
sudo -v && curl -fsSL https://install.danklinux.com | sh -s -- -c niri -t ghostty -y
|
||||||
sudo -v && curl -fsSL https://install.danklinux.com | sh -s -- -c hyprland -t kitty --include-deps dms-greeter -y
|
sudo -v && curl -fsSL https://install.danklinux.com | sh -s -- -c hyprland -t kitty --dms-greeter -y
|
||||||
|
sudo -v && curl -fsSL https://install.danklinux.com | sh -s -- -c niri -t ghostty --git-deps niri,quickshell --all-features -y
|
||||||
```
|
```
|
||||||
|
|
||||||
| Flag | Short | Description |
|
| Flag | Short | Description |
|
||||||
|------|-------|-------------|
|
|------|-------|-------------|
|
||||||
| `--compositor <niri|hyprland>` | `-c` | Compositor/WM to install (required for headless) |
|
| `--compositor <niri\|hyprland\|mango>` | `-c` | Compositor/WM to install (required for headless) |
|
||||||
| `--term <ghostty|kitty|alacritty>` | `-t` | Terminal emulator (required for headless) |
|
| `--term <ghostty\|kitty\|alacritty>` | `-t` | Terminal emulator (required for headless) |
|
||||||
|
| `--privesc <sudo\|doas\|run0>` | | Privilege escalation tool (default: autodetect) |
|
||||||
|
| `--git` | | Install the git version of every dependency that has one |
|
||||||
|
| `--git-deps <name,...>` | | Install the git version of specific dependencies (e.g. `niri,quickshell`) |
|
||||||
|
| `--all-features` | | Enable all optional dependencies (`dms-greeter`, `danksearch`, `dankcalendar`) |
|
||||||
|
| `--dms-greeter` | | Install dms-greeter |
|
||||||
|
| `--danksearch` | | Install danksearch and enable its user indexing service |
|
||||||
|
| `--dankcalendar` | | Install dankcalendar |
|
||||||
| `--include-deps <name,...>` | | Enable optional dependencies (e.g. `dms-greeter`) |
|
| `--include-deps <name,...>` | | Enable optional dependencies (e.g. `dms-greeter`) |
|
||||||
| `--exclude-deps <name,...>` | | Skip specific dependencies |
|
| `--exclude-deps <name,...>` | | Skip specific dependencies |
|
||||||
| `--replace-configs <name,...>` | | Replace specific configuration files (mutually exclusive with `--replace-configs-all`) |
|
| `--replace-configs <name,...>` | | Replace specific configuration files (mutually exclusive with `--replace-configs-all`) |
|
||||||
@@ -174,7 +184,7 @@ sudo -v && curl -fsSL https://install.danklinux.com | sh -s -- -c hyprland -t ki
|
|||||||
|
|
||||||
Headless mode requires `--yes` to proceed; without it, the installer exits with an error.
|
Headless mode requires `--yes` to proceed; without it, the installer exits with an error.
|
||||||
Configuration files are not replaced by default unless `--replace-configs` or `--replace-configs-all` is specified.
|
Configuration files are not replaced by default unless `--replace-configs` or `--replace-configs-all` is specified.
|
||||||
`dms-greeter` is disabled by default; use `--include-deps dms-greeter` to enable it.
|
Optional packages (`dms-greeter`, `danksearch`, `dankcalendar`) are disabled by default; enable them with their dedicated flags, `--include-deps`, or `--all-features`.
|
||||||
|
|
||||||
When no flags are provided, `dankinstall` launches the interactive TUI.
|
When no flags are provided, `dankinstall` launches the interactive TUI.
|
||||||
|
|
||||||
@@ -183,7 +193,7 @@ When no flags are provided, `dankinstall` launches the interactive TUI.
|
|||||||
Headless mode activates when `--compositor` or `--term` is provided.
|
Headless mode activates when `--compositor` or `--term` is provided.
|
||||||
|
|
||||||
- Both `--compositor` and `--term` are required; providing only one results in an error.
|
- Both `--compositor` and `--term` are required; providing only one results in an error.
|
||||||
- Headless-only flags (`--include-deps`, `--exclude-deps`, `--replace-configs`, `--replace-configs-all`, `--yes`) are rejected in TUI mode.
|
- Headless-only flags (`--privesc`, `--git`, `--git-deps`, `--all-features`, `--include-deps`, `--exclude-deps`, `--replace-configs`, `--replace-configs-all`, `--yes`, `--danksearch`, `--dankcalendar`, `--dms-greeter`) are rejected in TUI mode.
|
||||||
- Positional arguments are not accepted.
|
- Positional arguments are not accepted.
|
||||||
|
|
||||||
### Log file location
|
### Log file location
|
||||||
|
|||||||
@@ -14,10 +14,13 @@ import (
|
|||||||
|
|
||||||
var Version = "dev"
|
var Version = "dev"
|
||||||
|
|
||||||
// Flag variables bound via pflag
|
|
||||||
var (
|
var (
|
||||||
compositor string
|
compositor string
|
||||||
term string
|
term string
|
||||||
|
privescTool string
|
||||||
|
gitAll bool
|
||||||
|
gitDeps []string
|
||||||
|
allFeatures bool
|
||||||
includeDeps []string
|
includeDeps []string
|
||||||
excludeDeps []string
|
excludeDeps []string
|
||||||
replaceConfigs []string
|
replaceConfigs []string
|
||||||
@@ -25,6 +28,7 @@ var (
|
|||||||
yes bool
|
yes bool
|
||||||
danksearch bool
|
danksearch bool
|
||||||
dankcalendar bool
|
dankcalendar bool
|
||||||
|
dmsGreeter bool
|
||||||
)
|
)
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
@@ -35,8 +39,8 @@ var rootCmd = &cobra.Command{
|
|||||||
Without flags, it launches an interactive TUI. Providing either --compositor
|
Without flags, it launches an interactive TUI. Providing either --compositor
|
||||||
or --term activates headless (unattended) mode, which requires both flags.
|
or --term activates headless (unattended) mode, which requires both flags.
|
||||||
|
|
||||||
Headless mode requires cached sudo credentials. Run 'sudo -v' beforehand, or
|
Headless mode requires cached credentials or a passwordless rule for your
|
||||||
configure passwordless sudo for your user.`,
|
privilege escalation tool (sudo, doas, or run0).`,
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
RunE: runDankinstall,
|
RunE: runDankinstall,
|
||||||
SilenceErrors: true,
|
SilenceErrors: true,
|
||||||
@@ -46,6 +50,10 @@ configure passwordless sudo for your user.`,
|
|||||||
func init() {
|
func init() {
|
||||||
rootCmd.Flags().StringVarP(&compositor, "compositor", "c", "", "Compositor/WM to install: niri, hyprland, or mango (enables headless mode)")
|
rootCmd.Flags().StringVarP(&compositor, "compositor", "c", "", "Compositor/WM to install: niri, hyprland, or mango (enables headless mode)")
|
||||||
rootCmd.Flags().StringVarP(&term, "term", "t", "", "Terminal emulator to install: ghostty, kitty, or alacritty (enables headless mode)")
|
rootCmd.Flags().StringVarP(&term, "term", "t", "", "Terminal emulator to install: ghostty, kitty, or alacritty (enables headless mode)")
|
||||||
|
rootCmd.Flags().StringVar(&privescTool, "privesc", "", "Privilege escalation tool: sudo, doas, or run0 (default: autodetect)")
|
||||||
|
rootCmd.Flags().BoolVar(&gitAll, "git", false, "Install the git version of every dep that has one")
|
||||||
|
rootCmd.Flags().StringSliceVar(&gitDeps, "git-deps", []string{}, "Deps to install the git version of (e.g. niri,quickshell)")
|
||||||
|
rootCmd.Flags().BoolVar(&allFeatures, "all-features", false, "Enable all optional deps (dms-greeter, danksearch, dankcalendar)")
|
||||||
rootCmd.Flags().StringSliceVar(&includeDeps, "include-deps", []string{}, "Optional deps to enable (e.g. dms-greeter)")
|
rootCmd.Flags().StringSliceVar(&includeDeps, "include-deps", []string{}, "Optional deps to enable (e.g. dms-greeter)")
|
||||||
rootCmd.Flags().StringSliceVar(&excludeDeps, "exclude-deps", []string{}, "Deps to skip during installation")
|
rootCmd.Flags().StringSliceVar(&excludeDeps, "exclude-deps", []string{}, "Deps to skip during installation")
|
||||||
rootCmd.Flags().StringSliceVar(&replaceConfigs, "replace-configs", []string{}, "Deploy only named configs (e.g. niri,ghostty)")
|
rootCmd.Flags().StringSliceVar(&replaceConfigs, "replace-configs", []string{}, "Deploy only named configs (e.g. niri,ghostty)")
|
||||||
@@ -53,6 +61,7 @@ func init() {
|
|||||||
rootCmd.Flags().BoolVarP(&yes, "yes", "y", false, "Auto-confirm all prompts")
|
rootCmd.Flags().BoolVarP(&yes, "yes", "y", false, "Auto-confirm all prompts")
|
||||||
rootCmd.Flags().BoolVar(&danksearch, "danksearch", false, "Install danksearch and enable its user indexing service")
|
rootCmd.Flags().BoolVar(&danksearch, "danksearch", false, "Install danksearch and enable its user indexing service")
|
||||||
rootCmd.Flags().BoolVar(&dankcalendar, "dankcalendar", false, "Install dankcalendar")
|
rootCmd.Flags().BoolVar(&dankcalendar, "dankcalendar", false, "Install dankcalendar")
|
||||||
|
rootCmd.Flags().BoolVar(&dmsGreeter, "dms-greeter", false, "Install dms-greeter")
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -71,8 +80,11 @@ func runDankinstall(cmd *cobra.Command, args []string) error {
|
|||||||
headlessMode := compositor != "" || term != ""
|
headlessMode := compositor != "" || term != ""
|
||||||
|
|
||||||
if !headlessMode {
|
if !headlessMode {
|
||||||
// Reject headless-only flags when running in TUI mode.
|
|
||||||
headlessOnly := []string{
|
headlessOnly := []string{
|
||||||
|
"privesc",
|
||||||
|
"git",
|
||||||
|
"git-deps",
|
||||||
|
"all-features",
|
||||||
"include-deps",
|
"include-deps",
|
||||||
"exclude-deps",
|
"exclude-deps",
|
||||||
"replace-configs",
|
"replace-configs",
|
||||||
@@ -80,6 +92,7 @@ func runDankinstall(cmd *cobra.Command, args []string) error {
|
|||||||
"yes",
|
"yes",
|
||||||
"danksearch",
|
"danksearch",
|
||||||
"dankcalendar",
|
"dankcalendar",
|
||||||
|
"dms-greeter",
|
||||||
}
|
}
|
||||||
var set []string
|
var set []string
|
||||||
for _, name := range headlessOnly {
|
for _, name := range headlessOnly {
|
||||||
@@ -99,7 +112,6 @@ func runDankinstall(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runHeadless() error {
|
func runHeadless() error {
|
||||||
// Validate required flags
|
|
||||||
if compositor == "" {
|
if compositor == "" {
|
||||||
return fmt.Errorf("--compositor is required for headless mode (niri, hyprland, or mango)")
|
return fmt.Errorf("--compositor is required for headless mode (niri, hyprland, or mango)")
|
||||||
}
|
}
|
||||||
@@ -110,6 +122,10 @@ func runHeadless() error {
|
|||||||
cfg := headless.Config{
|
cfg := headless.Config{
|
||||||
Compositor: compositor,
|
Compositor: compositor,
|
||||||
Terminal: term,
|
Terminal: term,
|
||||||
|
PrivescTool: privescTool,
|
||||||
|
GitAll: gitAll,
|
||||||
|
GitDeps: gitDeps,
|
||||||
|
AllFeatures: allFeatures,
|
||||||
IncludeDeps: includeDeps,
|
IncludeDeps: includeDeps,
|
||||||
ExcludeDeps: excludeDeps,
|
ExcludeDeps: excludeDeps,
|
||||||
ReplaceConfigs: replaceConfigs,
|
ReplaceConfigs: replaceConfigs,
|
||||||
@@ -117,11 +133,11 @@ func runHeadless() error {
|
|||||||
Yes: yes,
|
Yes: yes,
|
||||||
DankSearch: danksearch,
|
DankSearch: danksearch,
|
||||||
DankCalendar: dankcalendar,
|
DankCalendar: dankcalendar,
|
||||||
|
DmsGreeter: dmsGreeter,
|
||||||
}
|
}
|
||||||
|
|
||||||
runner := headless.NewRunner(cfg)
|
runner := headless.NewRunner(cfg)
|
||||||
|
|
||||||
// Set up file logging
|
|
||||||
fileLogger, err := log.NewFileLogger()
|
fileLogger, err := log.NewFileLogger()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Warning: Failed to create log file: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Warning: Failed to create log file: %v\n", err)
|
||||||
@@ -136,10 +152,6 @@ func runHeadless() error {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
} else {
|
} else {
|
||||||
// Drain the log channel to prevent blocking sends from deadlocking
|
|
||||||
// downstream components (distros, config deployer) that write to it.
|
|
||||||
// Use an explicit stop signal because this code does not own the
|
|
||||||
// runner log channel and cannot assume it will be closed.
|
|
||||||
defer drainLogChan(runner.GetLogChan())()
|
defer drainLogChan(runner.GetLogChan())()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,10 +191,6 @@ func runTUI() error {
|
|||||||
if fileLogger != nil {
|
if fileLogger != nil {
|
||||||
fileLogger.StartListening(model.GetLogChan())
|
fileLogger.StartListening(model.GetLogChan())
|
||||||
} else {
|
} else {
|
||||||
// Drain the log channel to prevent blocking sends from deadlocking
|
|
||||||
// downstream components (distros, config deployer) that write to it.
|
|
||||||
// Use an explicit stop signal because this code does not own the
|
|
||||||
// model log channel and cannot assume it will be closed.
|
|
||||||
defer drainLogChan(model.GetLogChan())()
|
defer drainLogChan(model.GetLogChan())()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,10 +208,9 @@ func runTUI() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// drainLogChan starts a goroutine that discards all messages from logCh,
|
// drainLogChan discards messages from logCh so blocking sends in downstream
|
||||||
// preventing blocking sends from deadlocking downstream components. It returns
|
// components cannot deadlock when no file logger is attached; the returned
|
||||||
// a cleanup function that signals the goroutine to stop and waits for it to
|
// cleanup stops the drain without assuming the channel will ever close.
|
||||||
// exit. Callers should defer the returned function.
|
|
||||||
func drainLogChan(logCh <-chan string) func() {
|
func drainLogChan(logCh <-chan string) func() {
|
||||||
drainStop := make(chan struct{})
|
drainStop := make(chan struct{})
|
||||||
drainDone := make(chan struct{})
|
drainDone := make(chan struct{})
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/highlight"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/highlight/lexers"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/highlight/styles"
|
||||||
"github.com/alecthomas/chroma/v2"
|
"github.com/alecthomas/chroma/v2"
|
||||||
"github.com/alecthomas/chroma/v2/formatters/html"
|
"github.com/alecthomas/chroma/v2/formatters/html"
|
||||||
"github.com/alecthomas/chroma/v2/lexers"
|
|
||||||
"github.com/alecthomas/chroma/v2/styles"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/yuin/goldmark"
|
"github.com/yuin/goldmark"
|
||||||
highlighting "github.com/yuin/goldmark-highlighting/v2"
|
|
||||||
"github.com/yuin/goldmark/extension"
|
"github.com/yuin/goldmark/extension"
|
||||||
"github.com/yuin/goldmark/parser"
|
"github.com/yuin/goldmark/parser"
|
||||||
ghtml "github.com/yuin/goldmark/renderer/html"
|
ghtml "github.com/yuin/goldmark/renderer/html"
|
||||||
@@ -116,7 +116,7 @@ func getCachedStyle(name string) *chroma.Style {
|
|||||||
style := styles.Get(name)
|
style := styles.Get(name)
|
||||||
if style == nil {
|
if style == nil {
|
||||||
fmt.Fprintf(os.Stderr, "Warning: Style '%s' not found, using fallback\n", name)
|
fmt.Fprintf(os.Stderr, "Warning: Style '%s' not found, using fallback\n", name)
|
||||||
style = styles.Fallback
|
style = styles.Fallback()
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheMutex.Lock()
|
cacheMutex.Lock()
|
||||||
@@ -209,12 +209,10 @@ func runChroma(cmd *cobra.Command, args []string) {
|
|||||||
md := goldmark.New(
|
md := goldmark.New(
|
||||||
goldmark.WithExtensions(
|
goldmark.WithExtensions(
|
||||||
extension.GFM,
|
extension.GFM,
|
||||||
highlighting.NewHighlighting(
|
highlight.Markdown{
|
||||||
highlighting.WithStyle(chromaStyle),
|
Style: styles.Get(chromaStyle),
|
||||||
highlighting.WithFormatOptions(
|
Formatter: html.New(html.WithClasses(!chromaInline)),
|
||||||
html.WithClasses(!chromaInline),
|
},
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
goldmark.WithParserOptions(
|
goldmark.WithParserOptions(
|
||||||
parser.WithAutoHeadingID(),
|
parser.WithAutoHeadingID(),
|
||||||
|
|||||||
@@ -932,7 +932,7 @@ func downloadToTempFile(rawURL string) (string, error) {
|
|||||||
var contentType string
|
var contentType string
|
||||||
var lastErr error
|
var lastErr error
|
||||||
|
|
||||||
for attempt := 0; attempt < 3; attempt++ {
|
for attempt := range 3 {
|
||||||
if attempt > 0 {
|
if attempt > 0 {
|
||||||
time.Sleep(time.Duration(attempt) * 500 * time.Millisecond)
|
time.Sleep(time.Duration(attempt) * 500 * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime/debug"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/clipboard"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/clipboard"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/colorpicker"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/colorpicker"
|
||||||
@@ -10,10 +11,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
colorOutputFmt string
|
colorOutputFmt string
|
||||||
colorAutocopy bool
|
colorAutocopy bool
|
||||||
colorNotify bool
|
colorNotify bool
|
||||||
colorLowercase bool
|
colorLowercase bool
|
||||||
|
colorAllowMulti bool
|
||||||
)
|
)
|
||||||
|
|
||||||
var colorCmd = &cobra.Command{
|
var colorCmd = &cobra.Command{
|
||||||
@@ -53,7 +55,8 @@ Examples:
|
|||||||
dms color pick --rgb # Output as RGB
|
dms color pick --rgb # Output as RGB
|
||||||
dms color pick --json # Output all formats as JSON
|
dms color pick --json # Output all formats as JSON
|
||||||
dms color pick --hex -l # Output hex in lowercase
|
dms color pick --hex -l # Output hex in lowercase
|
||||||
dms color pick -a # Auto-copy result to clipboard`,
|
dms color pick -a # Auto-copy result to clipboard
|
||||||
|
dms color pick --allow-multiple # Skip the one-picker-at-a-time guard`,
|
||||||
Run: runColorPick,
|
Run: runColorPick,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,6 +71,7 @@ func init() {
|
|||||||
colorPickCmd.Flags().StringVarP(&colorOutputFmt, "output-format", "o", "", "Custom output format template")
|
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(&colorAutocopy, "autocopy", "a", false, "Copy result to clipboard")
|
||||||
colorPickCmd.Flags().BoolVarP(&colorLowercase, "lowercase", "l", false, "Output hex in lowercase")
|
colorPickCmd.Flags().BoolVarP(&colorLowercase, "lowercase", "l", false, "Output hex in lowercase")
|
||||||
|
colorPickCmd.Flags().BoolVar(&colorAllowMulti, "allow-multiple", false, "Open a picker even when another selection overlay is already open")
|
||||||
|
|
||||||
colorPickCmd.MarkFlagsMutuallyExclusive("hex", "rgb", "hsl", "hsv", "cmyk", "json")
|
colorPickCmd.MarkFlagsMutuallyExclusive("hex", "rgb", "hsl", "hsv", "cmyk", "json")
|
||||||
|
|
||||||
@@ -89,13 +93,18 @@ func runColorPick(cmd *cobra.Command, args []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
config := colorpicker.Config{
|
config := colorpicker.Config{
|
||||||
Format: format,
|
Format: format,
|
||||||
CustomFormat: colorOutputFmt,
|
CustomFormat: colorOutputFmt,
|
||||||
Lowercase: colorLowercase,
|
Lowercase: colorLowercase,
|
||||||
Autocopy: colorAutocopy,
|
Autocopy: colorAutocopy,
|
||||||
Notify: colorNotify,
|
Notify: colorNotify,
|
||||||
|
AllowMultiple: colorAllowMulti,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// One-shot process holding a few screen-sized buffers: skip GC cycles while it runs.
|
||||||
|
debug.SetGCPercent(-1)
|
||||||
|
debug.SetMemoryLimit(1 << 30)
|
||||||
|
|
||||||
picker := colorpicker.New(config)
|
picker := colorpicker.New(config)
|
||||||
color, err := picker.Run()
|
color, err := picker.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -150,9 +150,9 @@ func hyprlandFindIncludeHyprlang(filePath, target string, processed map[string]b
|
|||||||
}
|
}
|
||||||
|
|
||||||
baseDir := filepath.Dir(absPath)
|
baseDir := filepath.Dir(absPath)
|
||||||
lines := strings.Split(string(data), "\n")
|
lines := strings.SplitSeq(string(data), "\n")
|
||||||
|
|
||||||
for _, line := range lines {
|
for line := range lines {
|
||||||
trimmed := strings.TrimSpace(line)
|
trimmed := strings.TrimSpace(line)
|
||||||
if strings.HasPrefix(trimmed, "#") || trimmed == "" {
|
if strings.HasPrefix(trimmed, "#") || trimmed == "" {
|
||||||
continue
|
continue
|
||||||
@@ -229,7 +229,7 @@ func niriFindInclude(filePath, target string, processed map[string]bool) bool {
|
|||||||
baseDir := filepath.Dir(absPath)
|
baseDir := filepath.Dir(absPath)
|
||||||
content := string(data)
|
content := string(data)
|
||||||
|
|
||||||
for _, line := range strings.Split(content, "\n") {
|
for line := range strings.SplitSeq(content, "\n") {
|
||||||
trimmed := strings.TrimSpace(line)
|
trimmed := strings.TrimSpace(line)
|
||||||
if strings.HasPrefix(trimmed, "//") || trimmed == "" {
|
if strings.HasPrefix(trimmed, "//") || trimmed == "" {
|
||||||
continue
|
continue
|
||||||
@@ -306,9 +306,9 @@ func mangowcFindInclude(filePath, target string, processed map[string]bool) bool
|
|||||||
}
|
}
|
||||||
|
|
||||||
baseDir := filepath.Dir(absPath)
|
baseDir := filepath.Dir(absPath)
|
||||||
lines := strings.Split(string(data), "\n")
|
lines := strings.SplitSeq(string(data), "\n")
|
||||||
|
|
||||||
for _, line := range lines {
|
for line := range lines {
|
||||||
trimmed := strings.TrimSpace(line)
|
trimmed := strings.TrimSpace(line)
|
||||||
if strings.HasPrefix(trimmed, "#") || trimmed == "" {
|
if strings.HasPrefix(trimmed, "#") || trimmed == "" {
|
||||||
continue
|
continue
|
||||||
|
|||||||
+328
-20
@@ -2,7 +2,9 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -15,6 +17,7 @@ import (
|
|||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/clipboard"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/clipboard"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/config"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/config"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/matugen"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/brightness"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/brightness"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/network"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/network"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/tui"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/tui"
|
||||||
@@ -319,8 +322,16 @@ func checkSystemInfo() []checkResult {
|
|||||||
func checkEnvironmentVars() []checkResult {
|
func checkEnvironmentVars() []checkResult {
|
||||||
var results []checkResult
|
var results []checkResult
|
||||||
results = append(results, checkEnvVar("QT_QPA_PLATFORMTHEME")...)
|
results = append(results, checkEnvVar("QT_QPA_PLATFORMTHEME")...)
|
||||||
|
results = append(results, checkQtPlatformThemePlugin("QT_QPA_PLATFORMTHEME")...)
|
||||||
|
results = append(results, checkEnvVar("QT_QPA_PLATFORMTHEME_QT6")...)
|
||||||
|
if os.Getenv("QT_QPA_PLATFORMTHEME_QT6") != os.Getenv("QT_QPA_PLATFORMTHEME") {
|
||||||
|
results = append(results, checkQtPlatformThemePlugin("QT_QPA_PLATFORMTHEME_QT6")...)
|
||||||
|
}
|
||||||
results = append(results, checkEnvVar("QS_ICON_THEME")...)
|
results = append(results, checkEnvVar("QS_ICON_THEME")...)
|
||||||
results = append(results, checkXDGMenuPrefix()...)
|
results = append(results, checkXDGMenuPrefix()...)
|
||||||
|
if matugen.QtengineActive() {
|
||||||
|
results = append(results, checkQtenginePlugin()...)
|
||||||
|
}
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,6 +363,228 @@ func checkXDGMenuPrefix() []checkResult {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// qtQueryBinaries are the Qt build tools that can be asked where Qt looks for
|
||||||
|
// plugins, in probe order. A binary's name does not reliably say which Qt it
|
||||||
|
// belongs to — plain "qmake" is Qt5 on Arch and Qt6 elsewhere — so each one is
|
||||||
|
// asked for QT_VERSION and the major is taken from the answer.
|
||||||
|
var qtQueryBinaries = []struct{ bin, flag string }{
|
||||||
|
{"qmake6", "-query"},
|
||||||
|
{"qmake-qt6", "-query"},
|
||||||
|
{"qtpaths6", "--query"},
|
||||||
|
{"qtpaths-qt6", "--query"},
|
||||||
|
{"qmake-qt5", "-query"},
|
||||||
|
{"qtpaths-qt5", "--query"},
|
||||||
|
{"qmake", "-query"},
|
||||||
|
{"qtpaths", "--query"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkQtenginePlugin verifies the qtengine platform theme plugin is installed
|
||||||
|
// where Qt actually searches, for every Qt major that can be interrogated.
|
||||||
|
//
|
||||||
|
// Qt is asked where it looks rather than the filesystem searched, because a
|
||||||
|
// package can install the Qt5 plugin under a prefix Qt5 does not search, after
|
||||||
|
// which Qt5 apps silently fall back while Qt6 apps look correct. A search would
|
||||||
|
// find the plugin and report OK on exactly that bug.
|
||||||
|
func checkQtenginePlugin() []checkResult {
|
||||||
|
url := doctorDocsURL + "#environment-variables"
|
||||||
|
|
||||||
|
pluginRoots := qtPluginRoots()
|
||||||
|
|
||||||
|
// qmake/qtpaths ship in dev packages most users lack — never warn on a Qt
|
||||||
|
// that could not be interrogated, and never claim it is fine either.
|
||||||
|
if len(pluginRoots) == 0 {
|
||||||
|
return []checkResult{{
|
||||||
|
catEnvironment, "qtengine plugin", statusInfo, "Cannot verify (no qmake/qtpaths)",
|
||||||
|
"Install a Qt development package to let dms doctor check the plugin against the path Qt actually searches.",
|
||||||
|
url,
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Qt also searches QT_PLUGIN_PATH, so a plugin found there is loadable.
|
||||||
|
envRoots := filepath.SplitList(os.Getenv("QT_PLUGIN_PATH"))
|
||||||
|
|
||||||
|
var results []checkResult
|
||||||
|
for _, major := range slices.Sorted(maps.Keys(pluginRoots)) {
|
||||||
|
// qtengine only ships Qt5 and Qt6 plugins.
|
||||||
|
if major != "5" && major != "6" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
root := pluginRoots[major]
|
||||||
|
name := fmt.Sprintf("qtengine plugin (Qt%s)", major)
|
||||||
|
|
||||||
|
plugin := qtenginePluginPath(append([]string{root}, envRoots...), major)
|
||||||
|
if plugin == "" {
|
||||||
|
expected := qtenginePluginFile(root, major)
|
||||||
|
results = append(results, checkResult{
|
||||||
|
catEnvironment, name, statusWarn, fmt.Sprintf("Not found in Qt%s plugin path", major),
|
||||||
|
fmt.Sprintf("Expected %s — Qt%s apps will silently fall back to an unthemed palette. The plugin is likely installed under a prefix Qt%s does not search.", expected, major, major),
|
||||||
|
url,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
results = append(results, checkResult{catEnvironment, name, statusOK, "Installed", plugin, url})
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkQtPlatformThemePlugin validates one of the QT_QPA_PLATFORMTHEME
|
||||||
|
// variables beyond mere presence: the value must name a platform theme whose
|
||||||
|
// plugin Qt can actually load. A wrong value fails silently at run time — Qt
|
||||||
|
// apps fall back to a built-in theme, icon lookups return nothing, and
|
||||||
|
// Quickshell tray menus draw a placeholder for every icon an app publishes.
|
||||||
|
func checkQtPlatformThemePlugin(varName string) []checkResult {
|
||||||
|
value := os.Getenv(varName)
|
||||||
|
// Unset is reported by checkEnvVar; qtengine has its own plugin check.
|
||||||
|
if value == "" || value == "qtengine" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
url := doctorDocsURL + "#environment-variables"
|
||||||
|
|
||||||
|
// qt6ct-kde is the AUR package name, not the theme value: with it set,
|
||||||
|
// Qt finds no platform theme at all and every Qt app falls back silently.
|
||||||
|
if value == "qt6ct-kde" {
|
||||||
|
return []checkResult{{
|
||||||
|
catEnvironment, value, statusWarn, "Package name, not a theme",
|
||||||
|
fmt.Sprintf("qt6ct-kde is the AUR package name; the theme value Qt expects is qt6ct. Qt apps get no platform theme, so palettes stay unstyled and icon lookups fail. Set %s=qt6ct, then restart the shell.", varName),
|
||||||
|
url,
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DMS themes Qt6 through these variables; qt5ct is the Qt5 counterpart.
|
||||||
|
major := "6"
|
||||||
|
if value == "qt5ct" {
|
||||||
|
major = "5"
|
||||||
|
}
|
||||||
|
files := qtPlatformThemePluginFiles(value)
|
||||||
|
name := fmt.Sprintf("%s plugin (Qt%s)", value, major)
|
||||||
|
|
||||||
|
root := qtPluginRoots()[major]
|
||||||
|
// qmake/qtpaths ship in dev packages most users lack — never warn on a Qt
|
||||||
|
// that could not be interrogated, and never claim it is fine either.
|
||||||
|
if root == "" {
|
||||||
|
return []checkResult{{
|
||||||
|
catEnvironment, name, statusInfo, "Cannot verify (no qmake/qtpaths)",
|
||||||
|
"Install a Qt development package to let dms doctor check the plugin against the path Qt actually searches.",
|
||||||
|
url,
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Qt also searches QT_PLUGIN_PATH, so a plugin found there is loadable.
|
||||||
|
plugin := qtPluginPath(append([]string{root}, filepath.SplitList(os.Getenv("QT_PLUGIN_PATH"))...), files...)
|
||||||
|
if plugin == "" {
|
||||||
|
// plasma-integration ships the kde theme under a file name that has
|
||||||
|
// changed across versions; a miss says nothing reliable about the
|
||||||
|
// install, so degrade to info instead of warning at a working setup.
|
||||||
|
if value == "kde" {
|
||||||
|
return []checkResult{{
|
||||||
|
catEnvironment, name, statusInfo, "Cannot verify (plasma-integration)",
|
||||||
|
"The kde theme comes from plasma-integration, whose plugin file name varies across versions. Verify visually that Qt apps follow your theme.",
|
||||||
|
url,
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
expected := filepath.Join(root, "platformthemes", files[0])
|
||||||
|
return []checkResult{{
|
||||||
|
catEnvironment, name, statusWarn, fmt.Sprintf("Not found in Qt%s plugin path", major),
|
||||||
|
fmt.Sprintf("Expected %s — Qt apps silently fall back to a built-in theme, breaking icon lookups and palettes. Install the package providing it, or correct %s.", expected, varName),
|
||||||
|
url,
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
return []checkResult{{catEnvironment, name, statusOK, "Installed", plugin, url}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// qtPlatformThemePluginFiles lists the plugin file names that can provide a
|
||||||
|
// QT_QPA_PLATFORMTHEME value, most conventional first. Qt resolves the value
|
||||||
|
// through plugin metadata keys rather than file names, so these are the names
|
||||||
|
// each integration is known to ship; unmapped values fall back to Qt's
|
||||||
|
// libq<value> naming convention.
|
||||||
|
func qtPlatformThemePluginFiles(value string) []string {
|
||||||
|
if files, ok := map[string][]string{
|
||||||
|
"qt6ct": {"libqt6ct.so"},
|
||||||
|
"qt5ct": {"libqt5ct.so"},
|
||||||
|
"gtk3": {"libqgtk3.so"},
|
||||||
|
"kde": {"libqkdeplatformtheme.so"},
|
||||||
|
}[value]; ok {
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
return []string{"libq" + value + ".so", "lib" + value + ".so"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// qtPluginRoots asks every available Qt build tool where its Qt looks for
|
||||||
|
// plugins, keyed by Qt major. A binary's name does not reliably say which Qt
|
||||||
|
// it belongs to — plain "qmake" is Qt5 on Arch and Qt6 elsewhere — so each one
|
||||||
|
// is asked for QT_VERSION and the major is taken from the answer.
|
||||||
|
func qtPluginRoots() map[string]string {
|
||||||
|
pluginRoots := make(map[string]string)
|
||||||
|
for _, q := range qtQueryBinaries {
|
||||||
|
if _, err := exec.LookPath(q.bin); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
major, _, _ := strings.Cut(qtQuery(q.bin, q.flag, "QT_VERSION"), ".")
|
||||||
|
if major == "" || pluginRoots[major] != "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if root := qtQuery(q.bin, q.flag, "QT_INSTALL_PLUGINS"); root != "" {
|
||||||
|
pluginRoots[major] = root
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pluginRoots
|
||||||
|
}
|
||||||
|
|
||||||
|
// qtPluginPath returns the first of roots that holds any of files under
|
||||||
|
// platformthemes/, or "" if none does.
|
||||||
|
func qtPluginPath(roots []string, files ...string) string {
|
||||||
|
for _, root := range roots {
|
||||||
|
if root == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, file := range files {
|
||||||
|
path := filepath.Join(root, "platformthemes", file)
|
||||||
|
if _, err := os.Stat(path); err == nil {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// qtenginePluginPath returns the first of roots that holds the qtengine platform
|
||||||
|
// theme plugin for the given Qt major, or "" if none does.
|
||||||
|
func qtenginePluginPath(roots []string, major string) string {
|
||||||
|
return qtPluginPath(roots, fmt.Sprintf("libqt%sengine-plugin.so", major))
|
||||||
|
}
|
||||||
|
|
||||||
|
func qtenginePluginFile(root, major string) string {
|
||||||
|
return filepath.Join(root, "platformthemes", fmt.Sprintf("libqt%sengine-plugin.so", major))
|
||||||
|
}
|
||||||
|
|
||||||
|
// qtQuery asks a Qt build tool for a single QLibraryInfo property.
|
||||||
|
func qtQuery(bin, flag, key string) string {
|
||||||
|
output, err := exec.Command(bin, flag, key).Output()
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return qtQueryValue(string(output), key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// qtQueryValue extracts one property from a Qt query. Most tools print the bare
|
||||||
|
// value, but some ignore the requested property and dump every one as
|
||||||
|
// "KEY:value" lines; taking the whole output there would yield a bogus path.
|
||||||
|
func qtQueryValue(output, key string) string {
|
||||||
|
lines := strings.Split(strings.TrimSpace(output), "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
if value, found := strings.CutPrefix(strings.TrimSpace(line), key+":"); found {
|
||||||
|
return strings.TrimSpace(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(lines) == 1 {
|
||||||
|
return strings.TrimSpace(lines[0])
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func checkXDGMenuFile(prefix string) bool {
|
func checkXDGMenuFile(prefix string) bool {
|
||||||
menuPath := fmt.Sprintf("/etc/xdg/menus/%sapplications.menu", prefix)
|
menuPath := fmt.Sprintf("/etc/xdg/menus/%sapplications.menu", prefix)
|
||||||
_, err := os.Stat(menuPath)
|
_, err := os.Stat(menuPath)
|
||||||
@@ -383,9 +616,8 @@ func checkVersions(qsMissingFeatures bool) []checkResult {
|
|||||||
{catVersions, "DMS CLI", statusOK, formatVersion(Version), dmsCliDetails, doctorDocsURL + "#dms-cli"},
|
{catVersions, "DMS CLI", statusOK, formatVersion(Version), dmsCliDetails, doctorDocsURL + "#dms-cli"},
|
||||||
}
|
}
|
||||||
|
|
||||||
qsVersion, qsStatus, qsPath := getQuickshellVersionInfo(qsMissingFeatures)
|
qsVersion, qsStatus, qsPath, qsDetails := getQuickshellVersionInfo(qsMissingFeatures)
|
||||||
qsDetails := ""
|
if doctorVerbose && qsPath != "" && qsDetails == "" {
|
||||||
if doctorVerbose && qsPath != "" {
|
|
||||||
qsDetails = qsPath
|
qsDetails = qsPath
|
||||||
}
|
}
|
||||||
results = append(results, checkResult{catVersions, "Quickshell", qsStatus, qsVersion, qsDetails, doctorDocsURL + "#quickshell"})
|
results = append(results, checkResult{catVersions, "Quickshell", qsStatus, qsVersion, qsDetails, doctorDocsURL + "#quickshell"})
|
||||||
@@ -420,30 +652,43 @@ func getDMSShellVersion() (version, path string) {
|
|||||||
return "", ""
|
return "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func getQuickshellVersionInfo(missingFeatures bool) (string, status, string) {
|
func getQuickshellVersionInfo(missingFeatures bool) (string, status, string, string) {
|
||||||
if !utils.CommandExists("qs") {
|
if !utils.CommandExists("qs") {
|
||||||
return "Not installed", statusError, ""
|
return "Not installed", statusError, "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
qsPath, _ := exec.LookPath("qs")
|
qsPath, _ := exec.LookPath("qs")
|
||||||
|
|
||||||
output, err := exec.Command("qs", "--version").Output()
|
output, err := exec.Command("qs", "--version").Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "Installed (version check failed)", statusWarn, qsPath
|
details := "Run 'qs --version' to inspect the failure."
|
||||||
|
var exitErr *exec.ExitError
|
||||||
|
if errors.As(err, &exitErr) {
|
||||||
|
details = quickshellVersionFailureDetails(string(exitErr.Stderr))
|
||||||
|
}
|
||||||
|
return "Installed (version check failed)", statusWarn, qsPath, details
|
||||||
}
|
}
|
||||||
|
|
||||||
fullVersion := strings.TrimSpace(string(output))
|
fullVersion := strings.TrimSpace(string(output))
|
||||||
if matches := quickshellVersionRegex.FindStringSubmatch(fullVersion); len(matches) >= 2 {
|
if matches := quickshellVersionRegex.FindStringSubmatch(fullVersion); len(matches) >= 2 {
|
||||||
if version.CompareVersions(matches[1], "0.2.0") < 0 {
|
if version.CompareVersions(matches[1], "0.2.0") < 0 {
|
||||||
return fmt.Sprintf("%s (needs >= 0.2.0)", fullVersion), statusError, qsPath
|
return fmt.Sprintf("%s (needs >= 0.2.0)", fullVersion), statusError, qsPath, ""
|
||||||
}
|
}
|
||||||
if missingFeatures {
|
if missingFeatures {
|
||||||
return fullVersion, statusWarn, qsPath
|
return fullVersion, statusWarn, qsPath, ""
|
||||||
}
|
}
|
||||||
return fullVersion, statusOK, qsPath
|
return fullVersion, statusOK, qsPath, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return fullVersion, statusWarn, qsPath
|
return fullVersion, statusWarn, qsPath, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func quickshellVersionFailureDetails(stderr string) string {
|
||||||
|
if strings.Contains(stderr, "undefined symbol:") && strings.Contains(stderr, "Qt_6_PRIVATE_API") {
|
||||||
|
return "Quickshell is incompatible with the installed Qt libraries. Rebuild or reinstall Quickshell against the current Qt version."
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Run 'qs --version' to inspect the failure."
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkDMSInstallation() []checkResult {
|
func checkDMSInstallation() []checkResult {
|
||||||
@@ -789,17 +1034,15 @@ func findQtPluginDirs() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check all paths in QT_PLUGIN_PATH env var (used by NixOS and custom setups)
|
// Check all paths in QT_PLUGIN_PATH env var (used by NixOS and custom setups)
|
||||||
if envPath := os.Getenv("QT_PLUGIN_PATH"); envPath != "" {
|
for _, dir := range filepath.SplitList(os.Getenv("QT_PLUGIN_PATH")) {
|
||||||
for dir := range strings.SplitSeq(envPath, ":") {
|
addDir(dir)
|
||||||
addDir(dir)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try qtpaths
|
for _, q := range qtQueryBinaries {
|
||||||
for _, cmd := range []string{"qtpaths6", "qtpaths"} {
|
if _, err := exec.LookPath(q.bin); err != nil {
|
||||||
if output, err := exec.Command(cmd, "-query", "QT_INSTALL_PLUGINS").Output(); err == nil {
|
continue
|
||||||
addDir(strings.TrimSpace(string(output)))
|
|
||||||
}
|
}
|
||||||
|
addDir(qtQuery(q.bin, q.flag, "QT_INSTALL_PLUGINS"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: common distro paths
|
// Fallback: common distro paths
|
||||||
@@ -923,9 +1166,62 @@ func checkOptionalDependencies() []checkResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
results = append(results, checkAdwGtk3())
|
||||||
|
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mirrors the search in quickshell/scripts/gtk.sh: user theme dirs, then XDG_DATA_DIRS.
|
||||||
|
func checkAdwGtk3() checkResult {
|
||||||
|
optionalFeaturesURL := doctorDocsURL + "#optional-features"
|
||||||
|
home, _ := os.UserHomeDir()
|
||||||
|
dataHome := os.Getenv("XDG_DATA_HOME")
|
||||||
|
if dataHome == "" {
|
||||||
|
dataHome = filepath.Join(home, ".local", "share")
|
||||||
|
}
|
||||||
|
userRoots := []string{filepath.Join(dataHome, "themes"), filepath.Join(home, ".themes")}
|
||||||
|
|
||||||
|
dataDirs := os.Getenv("XDG_DATA_DIRS")
|
||||||
|
if dataDirs == "" {
|
||||||
|
dataDirs = "/usr/local/share:/usr/share"
|
||||||
|
}
|
||||||
|
var systemRoots []string
|
||||||
|
for _, d := range strings.Split(dataDirs, ":") {
|
||||||
|
if d != "" {
|
||||||
|
systemRoots = append(systemRoots, filepath.Join(d, "themes"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
locate := func(roots []string, variant string) bool {
|
||||||
|
for _, root := range roots {
|
||||||
|
if info, err := os.Stat(filepath.Join(root, "adw-gtk3"+variant, "gtk-3.0")); err == nil && info.IsDir() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
userLight := locate(userRoots, "")
|
||||||
|
userDark := locate(userRoots, "-dark")
|
||||||
|
sysLight := locate(systemRoots, "")
|
||||||
|
sysDark := locate(systemRoots, "-dark")
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case userLight && userDark:
|
||||||
|
return checkResult{catOptionalFeatures, "adw-gtk3", statusOK, "Installed", "GTK3 dynamic theming (user copy)", optionalFeaturesURL}
|
||||||
|
case sysLight && sysDark:
|
||||||
|
return checkResult{catOptionalFeatures, "adw-gtk3", statusOK, "Installed", "GTK3 dynamic theming (system copy, applied on next theme apply)", optionalFeaturesURL}
|
||||||
|
case userLight || userDark || sysLight || sysDark:
|
||||||
|
missing := "adw-gtk3-dark"
|
||||||
|
if userDark || sysDark {
|
||||||
|
missing = "adw-gtk3"
|
||||||
|
}
|
||||||
|
return checkResult{catOptionalFeatures, "adw-gtk3", statusWarn, "Missing " + missing, "GTK3 dynamic theming needs both variants", optionalFeaturesURL}
|
||||||
|
default:
|
||||||
|
return checkResult{catOptionalFeatures, "adw-gtk3", statusInfo, "Not installed", "GTK3 dynamic theming; without it the global CSS override applies", optionalFeaturesURL}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func checkConfigurationFiles() []checkResult {
|
func checkConfigurationFiles() []checkResult {
|
||||||
configDir, _ := os.UserConfigDir()
|
configDir, _ := os.UserConfigDir()
|
||||||
cacheDir, _ := os.UserCacheDir()
|
cacheDir, _ := os.UserCacheDir()
|
||||||
@@ -953,6 +1249,11 @@ func checkConfigurationFiles() []checkResult {
|
|||||||
status = statusWarn
|
status = statusWarn
|
||||||
message += " (read-only)"
|
message += " (read-only)"
|
||||||
}
|
}
|
||||||
|
// The shell falls back to defaults on a parse failure, so a broken file must not report ok.
|
||||||
|
if data, readErr := os.ReadFile(cf.path); readErr == nil && len(data) > 0 && !json.Valid(data) {
|
||||||
|
status = statusError
|
||||||
|
message = "Invalid JSON - the shell ignores this file and runs on defaults"
|
||||||
|
}
|
||||||
results = append(results, checkResult{catConfigFiles, cf.name, status, message, cf.path, doctorDocsURL + "#config-files"})
|
results = append(results, checkResult{catConfigFiles, cf.name, status, message, cf.path, doctorDocsURL + "#config-files"})
|
||||||
}
|
}
|
||||||
return results
|
return results
|
||||||
@@ -985,6 +1286,13 @@ func checkSystemdServices() []checkResult {
|
|||||||
results = append(results, checkResult{catServices, "dms.service", status, message, "", doctorDocsURL + "#services"})
|
results = append(results, checkResult{catServices, "dms.service", status, message, "", doctorDocsURL + "#services"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if dmsState.exists && dmsState.enabled == "enabled" {
|
||||||
|
gsState := getServiceState("graphical-session.target", true)
|
||||||
|
if gsState.exists && gsState.active != "active" {
|
||||||
|
results = append(results, checkResult{catServices, "graphical-session.target", statusWarn, "Inactive", "Compositor session never activated it, so dms.service cannot autostart. On Hyprland, hyprland-session.target must exist and be started by the compositor.", doctorDocsURL + "#services"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
greetdState := getServiceState("greetd", false)
|
greetdState := getServiceState("greetd", false)
|
||||||
switch {
|
switch {
|
||||||
case greetdState.exists:
|
case greetdState.exists:
|
||||||
@@ -1222,8 +1530,8 @@ func fontInFcList(name, cacheLower string) bool {
|
|||||||
if target == "" {
|
if target == "" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for _, line := range strings.Split(cacheLower, "\n") {
|
for line := range strings.SplitSeq(cacheLower, "\n") {
|
||||||
for _, fam := range strings.Split(strings.TrimSpace(line), ",") {
|
for fam := range strings.SplitSeq(strings.TrimSpace(line), ",") {
|
||||||
if strings.TrimSpace(fam) == target {
|
if strings.TrimSpace(fam) == target {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,237 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestQuickshellVersionFailureDetails(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
stderr string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Qt private API symbol mismatch",
|
||||||
|
stderr: "qs: symbol lookup error: qs: undefined symbol: _ZN23QUntypedPropertyBindingC1EP23QPropertyBindingPrivate, version Qt_6_PRIVATE_API",
|
||||||
|
want: "Quickshell is incompatible with the installed Qt libraries. Rebuild or reinstall Quickshell against the current Qt version.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "other failure",
|
||||||
|
stderr: "qs: unsupported option --version",
|
||||||
|
want: "Run 'qs --version' to inspect the failure.",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if got := quickshellVersionFailureDetails(tc.stderr); got != tc.want {
|
||||||
|
t.Fatalf("quickshellVersionFailureDetails(%q) = %q, want %q", tc.stderr, got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parsing a full property dump as a bare value would yield a bogus plugin root,
|
||||||
|
// making the qtengine check warn at a user whose install is fine.
|
||||||
|
func TestQtQueryValue(t *testing.T) {
|
||||||
|
const dumpAll = `QT_INSTALL_PREFIX:/usr
|
||||||
|
QT_INSTALL_PLUGINS:/usr/lib/qt/plugins
|
||||||
|
QT_VERSION:5.15.19`
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
output string
|
||||||
|
key string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "bare value as qmake prints it",
|
||||||
|
output: "/usr/lib/qt6/plugins\n",
|
||||||
|
key: "QT_INSTALL_PLUGINS",
|
||||||
|
want: "/usr/lib/qt6/plugins",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "key:value line among a full property dump",
|
||||||
|
output: dumpAll,
|
||||||
|
key: "QT_INSTALL_PLUGINS",
|
||||||
|
want: "/usr/lib/qt/plugins",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "a different key from the same dump",
|
||||||
|
output: dumpAll,
|
||||||
|
key: "QT_VERSION",
|
||||||
|
want: "5.15.19",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "key absent from a multi line dump yields nothing",
|
||||||
|
output: dumpAll,
|
||||||
|
key: "QT_INSTALL_LIBEXECS",
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty output yields nothing",
|
||||||
|
output: "",
|
||||||
|
key: "QT_INSTALL_PLUGINS",
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "surrounding whitespace is trimmed",
|
||||||
|
output: " QT_INSTALL_PLUGINS: /usr/lib/qt/plugins \n",
|
||||||
|
key: "QT_INSTALL_PLUGINS",
|
||||||
|
want: "/usr/lib/qt/plugins",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if got := qtQueryValue(tc.output, tc.key); got != tc.want {
|
||||||
|
t.Fatalf("qtQueryValue(%q, %q) = %q, want %q", tc.output, tc.key, got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stubQtQueryTool writes a fake Qt build tool answering QT_VERSION and
|
||||||
|
// QT_INSTALL_PLUGINS from the given values, and points PATH at its directory
|
||||||
|
// alone so the real toolchain cannot interfere.
|
||||||
|
func stubQtQueryTool(t *testing.T, bin, version, plugins string) string {
|
||||||
|
t.Helper()
|
||||||
|
dir := t.TempDir()
|
||||||
|
script := "#!/bin/sh\ncase \"$2\" in\n QT_VERSION) echo " + version +
|
||||||
|
" ;;\n QT_INSTALL_PLUGINS) echo " + plugins + " ;;\nesac\n"
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, bin), []byte(script), 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Setenv("PATH", dir)
|
||||||
|
return dir
|
||||||
|
}
|
||||||
|
|
||||||
|
func writePlugin(t *testing.T, pluginsDir, file string) {
|
||||||
|
t.Helper()
|
||||||
|
if err := os.MkdirAll(filepath.Join(pluginsDir, "platformthemes"), 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(pluginsDir, "platformthemes", file), nil, 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// qtenginePluginFile("", major) is a path with a directory component, and
|
||||||
|
// qtPluginPath joins platformthemes/ again — a regression here silently warns
|
||||||
|
// at every correctly installed system, so pin the full path down.
|
||||||
|
func TestQtenginePluginPath(t *testing.T) {
|
||||||
|
root := t.TempDir()
|
||||||
|
if got := qtenginePluginPath([]string{root}, "6"); got != "" {
|
||||||
|
t.Fatalf("missing plugin should yield an empty path, got %q", got)
|
||||||
|
}
|
||||||
|
want := filepath.Join(root, "platformthemes", "libqt6engine-plugin.so")
|
||||||
|
if err := os.MkdirAll(filepath.Join(root, "platformthemes"), 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(want, nil, 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got := qtenginePluginPath([]string{root}, "6"); got != want {
|
||||||
|
t.Fatalf("qtenginePluginPath = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckQtPlatformThemePlugin(t *testing.T) {
|
||||||
|
t.Run("qt6ct-kde is flagged as a package name, not a theme", func(t *testing.T) {
|
||||||
|
t.Setenv("QT_QPA_PLATFORMTHEME", "qt6ct-kde")
|
||||||
|
t.Setenv("PATH", t.TempDir()) // no Qt tools reachable
|
||||||
|
results := checkQtPlatformThemePlugin("QT_QPA_PLATFORMTHEME")
|
||||||
|
if len(results) != 1 {
|
||||||
|
t.Fatalf("got %d results, want 1: %+v", len(results), results)
|
||||||
|
}
|
||||||
|
if results[0].status != statusWarn {
|
||||||
|
t.Fatalf("status = %q, want warn", results[0].status)
|
||||||
|
}
|
||||||
|
if !strings.Contains(results[0].details, "package name") || !strings.Contains(results[0].details, "qt6ct") {
|
||||||
|
t.Fatalf("details should name the package/value mixup, got %q", results[0].details)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("installed plugin reports OK", func(t *testing.T) {
|
||||||
|
t.Setenv("QT_QPA_PLATFORMTHEME", "qt6ct")
|
||||||
|
plugins := t.TempDir()
|
||||||
|
writePlugin(t, plugins, "libqt6ct.so")
|
||||||
|
stubQtQueryTool(t, "qmake6", "6.11.2", plugins)
|
||||||
|
results := checkQtPlatformThemePlugin("QT_QPA_PLATFORMTHEME")
|
||||||
|
if len(results) != 1 || results[0].status != statusOK {
|
||||||
|
t.Fatalf("got %+v, want one OK result", results)
|
||||||
|
}
|
||||||
|
if !strings.Contains(results[0].details, "libqt6ct.so") {
|
||||||
|
t.Fatalf("details should point at the plugin, got %q", results[0].details)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("missing plugin warns with the expected path", func(t *testing.T) {
|
||||||
|
t.Setenv("QT_QPA_PLATFORMTHEME", "qt6ct")
|
||||||
|
plugins := t.TempDir()
|
||||||
|
stubQtQueryTool(t, "qtpaths6", "6.11.2", plugins)
|
||||||
|
results := checkQtPlatformThemePlugin("QT_QPA_PLATFORMTHEME")
|
||||||
|
if len(results) != 1 || results[0].status != statusWarn {
|
||||||
|
t.Fatalf("got %+v, want one warn result", results)
|
||||||
|
}
|
||||||
|
want := filepath.Join(plugins, "platformthemes", "libqt6ct.so")
|
||||||
|
if !strings.Contains(results[0].details, want) {
|
||||||
|
t.Fatalf("details should contain expected path %s, got %q", want, results[0].details)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("no Qt build tools degrades to info", func(t *testing.T) {
|
||||||
|
t.Setenv("QT_QPA_PLATFORMTHEME", "qt6ct")
|
||||||
|
t.Setenv("PATH", t.TempDir())
|
||||||
|
results := checkQtPlatformThemePlugin("QT_QPA_PLATFORMTHEME")
|
||||||
|
if len(results) != 1 || results[0].status != statusInfo {
|
||||||
|
t.Fatalf("got %+v, want one info result", results)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("unset and qtengine are left to their own checks", func(t *testing.T) {
|
||||||
|
t.Setenv("PATH", t.TempDir())
|
||||||
|
t.Setenv("QT_QPA_PLATFORMTHEME", "")
|
||||||
|
if results := checkQtPlatformThemePlugin("QT_QPA_PLATFORMTHEME"); results != nil {
|
||||||
|
t.Fatalf("unset should yield no results, got %+v", results)
|
||||||
|
}
|
||||||
|
t.Setenv("QT_QPA_PLATFORMTHEME", "qtengine")
|
||||||
|
if results := checkQtPlatformThemePlugin("QT_QPA_PLATFORMTHEME"); results != nil {
|
||||||
|
t.Fatalf("qtengine should yield no results, got %+v", results)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("qt6 variable is not satisfied by a Qt5 tool alone", func(t *testing.T) {
|
||||||
|
t.Setenv("QT_QPA_PLATFORMTHEME_QT6", "qt6ct")
|
||||||
|
plugins := t.TempDir()
|
||||||
|
stubQtQueryTool(t, "qmake-qt5", "5.15.19", plugins)
|
||||||
|
results := checkQtPlatformThemePlugin("QT_QPA_PLATFORMTHEME_QT6")
|
||||||
|
if len(results) != 1 || results[0].status != statusInfo {
|
||||||
|
t.Fatalf("got %+v, want one info result", results)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("kde plugin miss degrades to info", func(t *testing.T) {
|
||||||
|
t.Setenv("QT_QPA_PLATFORMTHEME", "kde")
|
||||||
|
plugins := t.TempDir()
|
||||||
|
stubQtQueryTool(t, "qmake6", "6.11.2", plugins)
|
||||||
|
results := checkQtPlatformThemePlugin("QT_QPA_PLATFORMTHEME")
|
||||||
|
if len(results) != 1 || results[0].status != statusInfo {
|
||||||
|
t.Fatalf("got %+v, want one info result", results)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("gtk3 resolves through the libq convention", func(t *testing.T) {
|
||||||
|
t.Setenv("QT_QPA_PLATFORMTHEME", "gtk3")
|
||||||
|
plugins := t.TempDir()
|
||||||
|
writePlugin(t, plugins, "libqgtk3.so")
|
||||||
|
stubQtQueryTool(t, "qmake6", "6.11.2", plugins)
|
||||||
|
results := checkQtPlatformThemePlugin("QT_QPA_PLATFORMTHEME")
|
||||||
|
if len(results) != 1 || results[0].status != statusOK {
|
||||||
|
t.Fatalf("got %+v, want one OK result", results)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -3,19 +3,19 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/netfetch"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var dlOutput string
|
var dlOutput string
|
||||||
var dlUserAgent string
|
var dlUserAgent string
|
||||||
|
var dlHeaders []string
|
||||||
var dlTimeout int
|
var dlTimeout int
|
||||||
|
var dlConnectTimeout int
|
||||||
var dlIPv4Only bool
|
var dlIPv4Only bool
|
||||||
|
|
||||||
var dlCmd = &cobra.Command{
|
var dlCmd = &cobra.Command{
|
||||||
@@ -33,65 +33,52 @@ var dlCmd = &cobra.Command{
|
|||||||
func init() {
|
func init() {
|
||||||
dlCmd.Flags().StringVarP(&dlOutput, "output", "o", "", "Output file path (default: stdout)")
|
dlCmd.Flags().StringVarP(&dlOutput, "output", "o", "", "Output file path (default: stdout)")
|
||||||
dlCmd.Flags().StringVar(&dlUserAgent, "user-agent", "", "Custom User-Agent header")
|
dlCmd.Flags().StringVar(&dlUserAgent, "user-agent", "", "Custom User-Agent header")
|
||||||
dlCmd.Flags().IntVar(&dlTimeout, "timeout", 10, "Request timeout in seconds")
|
dlCmd.Flags().StringArrayVarP(&dlHeaders, "header", "H", nil, "Extra request header as 'Name: value' (repeatable)")
|
||||||
|
dlCmd.Flags().IntVar(&dlTimeout, "timeout", 10, "Total request timeout in seconds")
|
||||||
|
dlCmd.Flags().IntVar(&dlConnectTimeout, "connect-timeout", 5, "Connection timeout in seconds")
|
||||||
dlCmd.Flags().BoolVarP(&dlIPv4Only, "ipv4", "4", false, "Force IPv4 only")
|
dlCmd.Flags().BoolVarP(&dlIPv4Only, "ipv4", "4", false, "Force IPv4 only")
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDownload(url string) error {
|
func parseHeaders(raw []string) (map[string]string, error) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(dlTimeout)*time.Second)
|
if len(raw) == 0 {
|
||||||
defer cancel()
|
return nil, nil
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid request: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
headers := make(map[string]string, len(raw))
|
||||||
case dlUserAgent != "":
|
for _, entry := range raw {
|
||||||
req.Header.Set("User-Agent", dlUserAgent)
|
name, value, found := strings.Cut(entry, ":")
|
||||||
default:
|
if !found {
|
||||||
req.Header.Set("User-Agent", "DankMaterialShell/1.0 (Linux)")
|
return nil, fmt.Errorf("invalid header %q: expected 'Name: value'", entry)
|
||||||
}
|
|
||||||
|
|
||||||
dialer := &net.Dialer{Timeout: 5 * time.Second}
|
|
||||||
transport := &http.Transport{DialContext: dialer.DialContext}
|
|
||||||
if dlIPv4Only {
|
|
||||||
transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
return dialer.DialContext(ctx, "tcp4", addr)
|
|
||||||
}
|
}
|
||||||
|
name = strings.TrimSpace(name)
|
||||||
|
if name == "" {
|
||||||
|
return nil, fmt.Errorf("invalid header %q: empty name", entry)
|
||||||
|
}
|
||||||
|
headers[name] = strings.TrimSpace(value)
|
||||||
}
|
}
|
||||||
client := &http.Client{Transport: transport}
|
return headers, nil
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
func runDownload(url string) error {
|
||||||
|
headers, err := parseHeaders(dlHeaders)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("download failed: %w", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
||||||
return fmt.Errorf("HTTP %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
if dlOutput == "" {
|
|
||||||
_, err = io.Copy(os.Stdout, resp.Body)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if dir := filepath.Dir(dlOutput); dir != "." {
|
opts := netfetch.Options{
|
||||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
Headers: headers,
|
||||||
return fmt.Errorf("mkdir failed: %w", err)
|
UserAgent: dlUserAgent,
|
||||||
}
|
Timeout: time.Duration(dlTimeout) * time.Second,
|
||||||
|
ConnectTimeout: time.Duration(dlConnectTimeout) * time.Second,
|
||||||
|
IPv4Only: dlIPv4Only,
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.Create(dlOutput)
|
if dlOutput == "" {
|
||||||
if err != nil {
|
return netfetch.ToWriter(context.Background(), url, opts, os.Stdout)
|
||||||
return fmt.Errorf("create failed: %w", err)
|
|
||||||
}
|
}
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
if _, err := io.Copy(f, resp.Body); err != nil {
|
if err := netfetch.ToFile(context.Background(), url, opts, dlOutput); err != nil {
|
||||||
os.Remove(dlOutput)
|
return err
|
||||||
return fmt.Errorf("write failed: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(dlOutput)
|
fmt.Println(dlOutput)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/netfetch"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/privesc"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/privesc"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/version"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/version"
|
||||||
@@ -412,27 +413,11 @@ func updateDMSBinary() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Fetching latest release version...")
|
fmt.Println("Fetching latest release version...")
|
||||||
cmd := exec.Command("curl", "-s", "https://api.github.com/repos/AvengeMedia/DankMaterialShell/releases/latest")
|
version, err := netfetch.LatestReleaseTag(context.Background())
|
||||||
output, err := cmd.Output()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to fetch latest release: %w", err)
|
return fmt.Errorf("failed to fetch latest release: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
version := ""
|
|
||||||
for line := range strings.SplitSeq(string(output), "\n") {
|
|
||||||
if strings.Contains(line, "\"tag_name\"") {
|
|
||||||
parts := strings.Split(line, "\"")
|
|
||||||
if len(parts) >= 4 {
|
|
||||||
version = parts[3]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if version == "" {
|
|
||||||
return fmt.Errorf("could not determine latest version")
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Latest version: %s\n", version)
|
fmt.Printf("Latest version: %s\n", version)
|
||||||
|
|
||||||
tempDir, err := os.MkdirTemp("", "dms-update-*")
|
tempDir, err := os.MkdirTemp("", "dms-update-*")
|
||||||
@@ -448,14 +433,12 @@ func updateDMSBinary() error {
|
|||||||
checksumPath := filepath.Join(tempDir, "dms.gz.sha256")
|
checksumPath := filepath.Join(tempDir, "dms.gz.sha256")
|
||||||
|
|
||||||
fmt.Println("Downloading dms binary...")
|
fmt.Println("Downloading dms binary...")
|
||||||
downloadCmd := exec.Command("curl", "-L", binaryURL, "-o", binaryPath)
|
if err := netfetch.ToFile(context.Background(), binaryURL, netfetch.Options{Timeout: 5 * time.Minute}, binaryPath); err != nil {
|
||||||
if err := downloadCmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf("failed to download binary: %w", err)
|
return fmt.Errorf("failed to download binary: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Downloading checksum...")
|
fmt.Println("Downloading checksum...")
|
||||||
downloadCmd = exec.Command("curl", "-L", checksumURL, "-o", checksumPath)
|
if err := netfetch.ToFile(context.Background(), checksumURL, netfetch.Options{Timeout: 30 * time.Second}, checksumPath); err != nil {
|
||||||
if err := downloadCmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf("failed to download checksum: %w", err)
|
return fmt.Errorf("failed to download checksum: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ func greeterPackageInstallHint() string {
|
|||||||
case distros.FamilyFedora:
|
case distros.FamilyFedora:
|
||||||
return "Install with 'sudo dnf install dms-greeter' (requires COPR: sudo dnf copr enable avengemedia/danklinux)"
|
return "Install with 'sudo dnf install dms-greeter' (requires COPR: sudo dnf copr enable avengemedia/danklinux)"
|
||||||
case distros.FamilyArch:
|
case distros.FamilyArch:
|
||||||
return "Install from AUR with 'paru -S greetd-dms-greeter-git' or 'yay -S greetd-dms-greeter-git'"
|
return "Install from AUR with 'paru -S greetd-dms-greeter-bin' or 'yay -S greetd-dms-greeter-bin'"
|
||||||
case distros.FamilyVoid:
|
case distros.FamilyVoid:
|
||||||
return "Install with 'sudo xbps-install -S dms-greeter' (requires DMS XBPS repo: echo 'repository=https://void.danklinux.com/dms/current' | sudo tee /etc/xbps.d/dms.conf)"
|
return "Install with 'sudo xbps-install -S dms-greeter' (requires DMS XBPS repo: echo 'repository=https://void.danklinux.com/dms/current' | sudo tee /etc/xbps.d/dms.conf)"
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -46,7 +47,7 @@ var keybindsSetCmd = &cobra.Command{
|
|||||||
Short: "Set a keybind override",
|
Short: "Set a keybind override",
|
||||||
Long: "Create or update a keybind override for the specified provider",
|
Long: "Create or update a keybind override for the specified provider",
|
||||||
Args: cobra.ExactArgs(3),
|
Args: cobra.ExactArgs(3),
|
||||||
Run: runKeybindsSet,
|
Run: runKeybindsEdit,
|
||||||
}
|
}
|
||||||
|
|
||||||
var keybindsRemoveCmd = &cobra.Command{
|
var keybindsRemoveCmd = &cobra.Command{
|
||||||
@@ -54,7 +55,7 @@ var keybindsRemoveCmd = &cobra.Command{
|
|||||||
Short: "Remove a keybind",
|
Short: "Remove a keybind",
|
||||||
Long: "Remove a keybind. For Hyprland this writes a negative override to dms/binds-user.lua so the key stays unbound across DMS updates. For other providers it deletes the entry from the managed file.",
|
Long: "Remove a keybind. For Hyprland this writes a negative override to dms/binds-user.lua so the key stays unbound across DMS updates. For other providers it deletes the entry from the managed file.",
|
||||||
Args: cobra.ExactArgs(2),
|
Args: cobra.ExactArgs(2),
|
||||||
Run: runKeybindsRemove,
|
Run: runKeybindsEdit,
|
||||||
}
|
}
|
||||||
|
|
||||||
var keybindsResetCmd = &cobra.Command{
|
var keybindsResetCmd = &cobra.Command{
|
||||||
@@ -62,10 +63,14 @@ var keybindsResetCmd = &cobra.Command{
|
|||||||
Short: "Reset a keybind override to its DMS default",
|
Short: "Reset a keybind override to its DMS default",
|
||||||
Long: "Drop the user override for the given key so the DMS default re-applies. For providers without a separate default file (Niri, MangoWC) this is equivalent to remove.",
|
Long: "Drop the user override for the given key so the DMS default re-applies. For providers without a separate default file (Niri, MangoWC) this is equivalent to remove.",
|
||||||
Args: cobra.ExactArgs(2),
|
Args: cobra.ExactArgs(2),
|
||||||
Run: runKeybindsReset,
|
Run: runKeybindsEdit,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
keybindsCmd.PersistentFlags().String("expected-generation", "", "Configuration generation observed when editing Aqueous bindings")
|
||||||
|
for _, command := range []*cobra.Command{keybindsSetCmd, keybindsRemoveCmd, keybindsResetCmd} {
|
||||||
|
command.Flags().Bool("json", false, "Return structured mutation results, including errors")
|
||||||
|
}
|
||||||
keybindsListCmd.Flags().BoolP("json", "j", false, "Output as JSON")
|
keybindsListCmd.Flags().BoolP("json", "j", false, "Output as JSON")
|
||||||
keybindsShowCmd.Flags().String("path", "", "Override config path for the provider")
|
keybindsShowCmd.Flags().String("path", "", "Override config path for the provider")
|
||||||
keybindsSetCmd.Flags().String("desc", "", "Description for hotkey overlay")
|
keybindsSetCmd.Flags().String("desc", "", "Description for hotkey overlay")
|
||||||
@@ -93,6 +98,9 @@ func initializeProviders() {
|
|||||||
registry := keybinds.GetDefaultRegistry()
|
registry := keybinds.GetDefaultRegistry()
|
||||||
|
|
||||||
hyprlandProvider := providers.NewHyprlandProvider("")
|
hyprlandProvider := providers.NewHyprlandProvider("")
|
||||||
|
if err := registry.Register(providers.NewAqueousProvider()); err != nil {
|
||||||
|
log.Warnf("Failed to register Aqueous provider: %v", err)
|
||||||
|
}
|
||||||
if err := registry.Register(hyprlandProvider); err != nil {
|
if err := registry.Register(hyprlandProvider); err != nil {
|
||||||
log.Warnf("Failed to register Hyprland provider: %v", err)
|
log.Warnf("Failed to register Hyprland provider: %v", err)
|
||||||
}
|
}
|
||||||
@@ -206,85 +214,92 @@ func runKeybindsShow(cmd *cobra.Command, args []string) {
|
|||||||
printCheatSheet(provider)
|
printCheatSheet(provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getWritableProvider(name string) keybinds.WritableProvider {
|
func keybindEditFailure(err error) map[string]any {
|
||||||
provider, err := keybinds.GetDefaultRegistry().Get(name)
|
code := "command_failed"
|
||||||
|
var helperError *providers.AqueousError
|
||||||
|
if errors.As(err, &helperError) {
|
||||||
|
code = helperError.Code
|
||||||
|
}
|
||||||
|
return map[string]any{"success": false, "code": code, "message": err.Error()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runKeybindsEdit(cmd *cobra.Command, args []string) {
|
||||||
|
result, err := editKeybind(cmd, args)
|
||||||
|
if err == nil {
|
||||||
|
_ = json.NewEncoder(cmd.OutOrStdout()).Encode(result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
structured, _ := cmd.Flags().GetBool("json")
|
||||||
|
if !structured {
|
||||||
|
log.Fatalf("Failed to save keybind: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = json.NewEncoder(cmd.OutOrStdout()).Encode(keybindEditFailure(err))
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func editKeybind(cmd *cobra.Command, args []string) (map[string]any, error) {
|
||||||
|
provider, err := keybinds.GetDefaultRegistry().Get(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error: %v", err)
|
return nil, err
|
||||||
|
}
|
||||||
|
key := args[1]
|
||||||
|
if aqueous, ok := provider.(*providers.AqueousProvider); ok {
|
||||||
|
generation, _ := cmd.Flags().GetString("expected-generation")
|
||||||
|
edit := providers.AqueousBindEdit{Generation: generation, Key: key, Remove: cmd.Name() != "set"}
|
||||||
|
if !edit.Remove {
|
||||||
|
for _, flag := range []string{"desc", "allow-when-locked", "cooldown-ms", "no-repeat", "no-inhibiting", "flags"} {
|
||||||
|
if cmd.Flags().Changed(flag) {
|
||||||
|
return nil, fmt.Errorf("aqueous does not support --%s in this provider", flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
edit.Action = args[2]
|
||||||
|
edit.OriginalKey, _ = cmd.Flags().GetString("replace-key")
|
||||||
|
}
|
||||||
|
result, err := aqueous.Edit(cmd.Context(), edit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return map[string]any{"success": true, "code": "applied", "generation": result.String("generation"), "key": key}, nil
|
||||||
}
|
}
|
||||||
writable, ok := provider.(keybinds.WritableProvider)
|
writable, ok := provider.(keybinds.WritableProvider)
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Fatalf("Provider %s does not support writing keybinds", name)
|
return nil, fmt.Errorf("provider %s does not support writing keybinds", args[0])
|
||||||
}
|
}
|
||||||
return writable
|
result := map[string]any{"success": true, "key": key}
|
||||||
}
|
switch cmd.Name() {
|
||||||
|
case "set":
|
||||||
func runKeybindsSet(cmd *cobra.Command, args []string) {
|
options := make(map[string]any)
|
||||||
providerName, key, action := args[0], args[1], args[2]
|
if v, _ := cmd.Flags().GetBool("allow-when-locked"); v {
|
||||||
writable := getWritableProvider(providerName)
|
options["allow-when-locked"] = true
|
||||||
|
}
|
||||||
if replaceKey, _ := cmd.Flags().GetString("replace-key"); replaceKey != "" && replaceKey != key {
|
if v, _ := cmd.Flags().GetInt("cooldown-ms"); v > 0 {
|
||||||
_ = writable.RemoveBind(replaceKey)
|
options["cooldown-ms"] = v
|
||||||
}
|
}
|
||||||
|
if v, _ := cmd.Flags().GetBool("no-repeat"); v {
|
||||||
options := make(map[string]any)
|
options["repeat"] = false
|
||||||
if v, _ := cmd.Flags().GetBool("allow-when-locked"); v {
|
}
|
||||||
options["allow-when-locked"] = true
|
if v, _ := cmd.Flags().GetBool("no-inhibiting"); v {
|
||||||
}
|
options["allow-inhibiting"] = false
|
||||||
if v, _ := cmd.Flags().GetInt("cooldown-ms"); v > 0 {
|
}
|
||||||
options["cooldown-ms"] = v
|
if v, _ := cmd.Flags().GetString("flags"); v != "" {
|
||||||
}
|
options["flags"] = v
|
||||||
if v, _ := cmd.Flags().GetBool("no-repeat"); v {
|
}
|
||||||
options["repeat"] = false
|
if replaceKey, _ := cmd.Flags().GetString("replace-key"); replaceKey != "" && replaceKey != key {
|
||||||
}
|
_ = writable.RemoveBind(replaceKey)
|
||||||
if v, _ := cmd.Flags().GetBool("no-inhibiting"); v {
|
}
|
||||||
options["allow-inhibiting"] = false
|
desc, _ := cmd.Flags().GetString("desc")
|
||||||
}
|
err = writable.SetBind(key, args[2], desc, options)
|
||||||
if v, _ := cmd.Flags().GetString("flags"); v != "" {
|
result["action"] = args[2]
|
||||||
options["flags"] = v
|
result["path"] = writable.GetOverridePath()
|
||||||
}
|
case "remove":
|
||||||
|
err = writable.RemoveBind(key)
|
||||||
desc, _ := cmd.Flags().GetString("desc")
|
result["removed"] = true
|
||||||
if err := writable.SetBind(key, action, desc, options); err != nil {
|
case "reset":
|
||||||
log.Fatalf("Error setting keybind: %v", err)
|
err = writable.ResetBind(key)
|
||||||
}
|
result["reset"] = true
|
||||||
|
default:
|
||||||
output, _ := json.MarshalIndent(map[string]any{
|
return nil, fmt.Errorf("unsupported keybind operation: %s", cmd.Name())
|
||||||
"success": true,
|
}
|
||||||
"key": key,
|
return result, err
|
||||||
"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))
|
|
||||||
}
|
|
||||||
|
|
||||||
func runKeybindsReset(_ *cobra.Command, args []string) {
|
|
||||||
providerName, key := args[0], args[1]
|
|
||||||
writable := getWritableProvider(providerName)
|
|
||||||
|
|
||||||
if err := writable.ResetBind(key); err != nil {
|
|
||||||
log.Fatalf("Error resetting keybind: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
output, _ := json.MarshalIndent(map[string]any{
|
|
||||||
"success": true,
|
|
||||||
"key": key,
|
|
||||||
"reset": true,
|
|
||||||
}, "", " ")
|
|
||||||
fmt.Fprintln(os.Stdout, string(output))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds/providers"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestKeybindEditFailure(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
err error
|
||||||
|
code string
|
||||||
|
}{
|
||||||
|
{&providers.AqueousError{Code: "external_change", Message: "changed"}, "external_change"},
|
||||||
|
{fmt.Errorf("helper: %w", &providers.AqueousError{Code: "read_only", Message: "denied"}), "read_only"},
|
||||||
|
{errors.New("transport failed"), "command_failed"},
|
||||||
|
} {
|
||||||
|
result := keybindEditFailure(tc.err)
|
||||||
|
if result["success"] != false || result["code"] != tc.code || result["message"] != tc.err.Error() {
|
||||||
|
t.Fatalf("unexpected failure result: %#v", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,11 +44,18 @@ var matugenPreviewCmd = &cobra.Command{
|
|||||||
Run: runMatugenPreview,
|
Run: runMatugenPreview,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var matugenQtengineCmd = &cobra.Command{
|
||||||
|
Use: "qtengine",
|
||||||
|
Short: "Sync qtengine's config with the current DMS theme",
|
||||||
|
Run: runMatugenQtengine,
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
matugenCmd.AddCommand(matugenGenerateCmd)
|
matugenCmd.AddCommand(matugenGenerateCmd)
|
||||||
matugenCmd.AddCommand(matugenQueueCmd)
|
matugenCmd.AddCommand(matugenQueueCmd)
|
||||||
matugenCmd.AddCommand(matugenCheckCmd)
|
matugenCmd.AddCommand(matugenCheckCmd)
|
||||||
matugenCmd.AddCommand(matugenPreviewCmd)
|
matugenCmd.AddCommand(matugenPreviewCmd)
|
||||||
|
matugenCmd.AddCommand(matugenQtengineCmd)
|
||||||
|
|
||||||
for _, cmd := range []*cobra.Command{matugenGenerateCmd, matugenQueueCmd} {
|
for _, cmd := range []*cobra.Command{matugenGenerateCmd, matugenQueueCmd} {
|
||||||
cmd.Flags().String("state-dir", "", "State directory for cache files")
|
cmd.Flags().String("state-dir", "", "State directory for cache files")
|
||||||
@@ -56,21 +63,25 @@ func init() {
|
|||||||
cmd.Flags().String("config-dir", "", "User config directory")
|
cmd.Flags().String("config-dir", "", "User config directory")
|
||||||
cmd.Flags().String("kind", "image", "Source type: image or hex")
|
cmd.Flags().String("kind", "image", "Source type: image or hex")
|
||||||
cmd.Flags().String("value", "", "Wallpaper path or hex color")
|
cmd.Flags().String("value", "", "Wallpaper path or hex color")
|
||||||
cmd.Flags().String("mode", "dark", "Color mode: dark or light")
|
cmd.Flags().String("mode", "dark", "Color mode: dark, light, or smart (smart requires matugen 4.2+ and an image source)")
|
||||||
cmd.Flags().String("icon-theme", "System Default", "Icon theme name")
|
cmd.Flags().String("icon-theme", "System Default", "Icon theme name")
|
||||||
cmd.Flags().String("matugen-type", "scheme-tonal-spot", "Matugen scheme type")
|
cmd.Flags().String("matugen-type", "scheme-tonal-spot", "Matugen scheme type (supports scheme-smart on matugen 4.2+)")
|
||||||
cmd.Flags().Bool("run-user-templates", true, "Run user matugen templates")
|
cmd.Flags().Bool("run-user-templates", true, "Run user matugen templates")
|
||||||
cmd.Flags().String("stock-colors", "", "Stock theme colors JSON")
|
cmd.Flags().String("stock-colors", "", "Stock theme colors JSON")
|
||||||
cmd.Flags().Bool("sync-mode-with-portal", false, "Sync color scheme with GNOME portal")
|
cmd.Flags().Bool("sync-mode-with-portal", false, "Sync color scheme with GNOME portal")
|
||||||
cmd.Flags().Bool("terminals-always-dark", false, "Force terminal themes to dark variant")
|
cmd.Flags().Bool("terminals-always-dark", false, "Force terminal themes to dark variant")
|
||||||
cmd.Flags().String("skip-templates", "", "Comma-separated list of templates to skip")
|
cmd.Flags().String("skip-templates", "", "Comma-separated list of templates to skip")
|
||||||
cmd.Flags().Float64("contrast", 0, "Contrast value from -1 to 1 (0 = standard)")
|
cmd.Flags().Float64("contrast", 0, "Contrast value from -1 to 1 (0 = standard)")
|
||||||
|
cmd.Flags().String("source-mode", "", "Source color selection: dominant, colorful, darkness, lightness, saturation, less-saturation, value")
|
||||||
}
|
}
|
||||||
|
|
||||||
matugenQueueCmd.Flags().Bool("wait", true, "Wait for completion")
|
matugenQueueCmd.Flags().Bool("wait", true, "Wait for completion")
|
||||||
matugenQueueCmd.Flags().Duration("timeout", 90*time.Second, "Timeout for waiting")
|
matugenQueueCmd.Flags().Duration("timeout", 90*time.Second, "Timeout for waiting")
|
||||||
matugenPreviewCmd.Flags().String("source-color", "", "Source color used to generate previews")
|
matugenPreviewCmd.Flags().String("source-color", "", "Source color used to generate previews")
|
||||||
|
matugenPreviewCmd.Flags().String("image", "", "Wallpaper image used to resolve the scheme-smart preview")
|
||||||
matugenPreviewCmd.Flags().Float64("contrast", 0, "Contrast value from -1 to 1 (0 = standard)")
|
matugenPreviewCmd.Flags().Float64("contrast", 0, "Contrast value from -1 to 1 (0 = standard)")
|
||||||
|
matugenQtengineCmd.Flags().String("config-dir", "", "User config directory")
|
||||||
|
matugenQtengineCmd.Flags().String("icon-theme", "", "Icon theme name")
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildMatugenOptions(cmd *cobra.Command) matugen.Options {
|
func buildMatugenOptions(cmd *cobra.Command) matugen.Options {
|
||||||
@@ -88,6 +99,7 @@ func buildMatugenOptions(cmd *cobra.Command) matugen.Options {
|
|||||||
terminalsAlwaysDark, _ := cmd.Flags().GetBool("terminals-always-dark")
|
terminalsAlwaysDark, _ := cmd.Flags().GetBool("terminals-always-dark")
|
||||||
skipTemplates, _ := cmd.Flags().GetString("skip-templates")
|
skipTemplates, _ := cmd.Flags().GetString("skip-templates")
|
||||||
contrast, _ := cmd.Flags().GetFloat64("contrast")
|
contrast, _ := cmd.Flags().GetFloat64("contrast")
|
||||||
|
sourceMode, _ := cmd.Flags().GetString("source-mode")
|
||||||
|
|
||||||
return matugen.Options{
|
return matugen.Options{
|
||||||
StateDir: stateDir,
|
StateDir: stateDir,
|
||||||
@@ -104,6 +116,7 @@ func buildMatugenOptions(cmd *cobra.Command) matugen.Options {
|
|||||||
SyncModeWithPortal: syncModeWithPortal,
|
SyncModeWithPortal: syncModeWithPortal,
|
||||||
TerminalsAlwaysDark: terminalsAlwaysDark,
|
TerminalsAlwaysDark: terminalsAlwaysDark,
|
||||||
SkipTemplates: skipTemplates,
|
SkipTemplates: skipTemplates,
|
||||||
|
SourceMode: sourceMode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,6 +154,7 @@ func runMatugenQueue(cmd *cobra.Command, args []string) {
|
|||||||
"terminalsAlwaysDark": opts.TerminalsAlwaysDark,
|
"terminalsAlwaysDark": opts.TerminalsAlwaysDark,
|
||||||
"skipTemplates": opts.SkipTemplates,
|
"skipTemplates": opts.SkipTemplates,
|
||||||
"contrast": opts.Contrast,
|
"contrast": opts.Contrast,
|
||||||
|
"sourceMode": opts.SourceMode,
|
||||||
"wait": wait,
|
"wait": wait,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -212,8 +226,9 @@ func runMatugenCheck(cmd *cobra.Command, args []string) {
|
|||||||
|
|
||||||
func runMatugenPreview(cmd *cobra.Command, args []string) {
|
func runMatugenPreview(cmd *cobra.Command, args []string) {
|
||||||
sourceColor, _ := cmd.Flags().GetString("source-color")
|
sourceColor, _ := cmd.Flags().GetString("source-color")
|
||||||
|
imagePath, _ := cmd.Flags().GetString("image")
|
||||||
contrast, _ := cmd.Flags().GetFloat64("contrast")
|
contrast, _ := cmd.Flags().GetFloat64("contrast")
|
||||||
previews, err := matugen.PreviewSchemes(sourceColor, contrast)
|
previews, err := matugen.PreviewSchemes(sourceColor, contrast, imagePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to generate Matugen previews: %v", err)
|
log.Fatalf("Failed to generate Matugen previews: %v", err)
|
||||||
}
|
}
|
||||||
@@ -223,3 +238,12 @@ func runMatugenPreview(cmd *cobra.Command, args []string) {
|
|||||||
}
|
}
|
||||||
fmt.Println(string(data))
|
fmt.Println(string(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runMatugenQtengine(cmd *cobra.Command, args []string) {
|
||||||
|
configDir, _ := cmd.Flags().GetString("config-dir")
|
||||||
|
iconTheme, _ := cmd.Flags().GetString("icon-theme")
|
||||||
|
|
||||||
|
if err := matugen.SyncQtengineConfigAt(configDir, iconTheme); err != nil {
|
||||||
|
log.Fatalf("Failed to sync qtengine config: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMatugenQtengineCommandAcceptsConfigDir(t *testing.T) {
|
||||||
|
flag := matugenQtengineCmd.Flags().Lookup("config-dir")
|
||||||
|
require.NotNil(t, flag)
|
||||||
|
assert.Equal(t, "", flag.DefValue)
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/notify"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/notify"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -61,8 +62,27 @@ func runNotify(cmd *cobra.Command, args []string) {
|
|||||||
Timeout: int32(notifyTimeout),
|
Timeout: int32(notifyTimeout),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := notify.Send(n); err != nil {
|
id, err := notify.Send(n)
|
||||||
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
if n.FilePath == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
watchNotificationAction(id, n.FilePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func watchNotificationAction(id uint32, path string) {
|
||||||
|
if id == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, ok := tryServerRequest(models.Request{
|
||||||
|
Method: "notify.watchAction",
|
||||||
|
Params: map[string]any{"id": id, "path": path},
|
||||||
|
})
|
||||||
|
if ok && resp.Error == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
notify.SpawnActionListener(id, path)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,21 +2,25 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime/debug"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/clipboard"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/clipboard"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/notify"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/screenshot"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/screenshot"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ssOutputName string
|
ssOutputName string
|
||||||
|
ssSeat string
|
||||||
ssCursor string
|
ssCursor string
|
||||||
ssFormat string
|
ssFormat string
|
||||||
ssQuality int
|
ssQuality int
|
||||||
@@ -29,11 +33,15 @@ var (
|
|||||||
ssReset bool
|
ssReset bool
|
||||||
ssStdout bool
|
ssStdout bool
|
||||||
ssJSON bool
|
ssJSON bool
|
||||||
|
ssGeometry bool
|
||||||
|
ssAllowMulti bool
|
||||||
)
|
)
|
||||||
|
|
||||||
type screenshotMetadata struct {
|
type screenshotMetadata struct {
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Path string `json:"path,omitempty"`
|
Path string `json:"path,omitempty"`
|
||||||
|
X *int `json:"x,omitempty"`
|
||||||
|
Y *int `json:"y,omitempty"`
|
||||||
Width int `json:"width,omitempty"`
|
Width int `json:"width,omitempty"`
|
||||||
Height int `json:"height,omitempty"`
|
Height int `json:"height,omitempty"`
|
||||||
Scale float64 `json:"scale,omitempty"`
|
Scale float64 `json:"scale,omitempty"`
|
||||||
@@ -51,7 +59,7 @@ Modes:
|
|||||||
full - Capture the focused output
|
full - Capture the focused output
|
||||||
all - Capture all outputs combined
|
all - Capture all outputs combined
|
||||||
output - Capture a specific output by name
|
output - Capture a specific output by name
|
||||||
window - Capture the focused window (Hyprland/Mango/niri)
|
window - Capture the focused window (Hyprland/Mango/niri/Aqueous)
|
||||||
last - Capture the last selected region
|
last - Capture the last selected region
|
||||||
scroll - Select a region, then scroll to capture a stitched tall image
|
scroll - Select a region, then scroll to capture a stitched tall image
|
||||||
|
|
||||||
@@ -65,7 +73,7 @@ Examples:
|
|||||||
dms screenshot full # Full screen of focused output
|
dms screenshot full # Full screen of focused output
|
||||||
dms screenshot all # All screens combined
|
dms screenshot all # All screens combined
|
||||||
dms screenshot output -o DP-1 # Specific output
|
dms screenshot output -o DP-1 # Specific output
|
||||||
dms screenshot window # Focused window (Hyprland)
|
dms screenshot window # Focused window
|
||||||
dms screenshot last # Last region (pre-selected)
|
dms screenshot last # Last region (pre-selected)
|
||||||
dms screenshot --reset # Reset last region pre-selection
|
dms screenshot --reset # Reset last region pre-selection
|
||||||
dms screenshot --no-clipboard # Save file only
|
dms screenshot --no-clipboard # Save file only
|
||||||
@@ -74,6 +82,8 @@ Examples:
|
|||||||
dms screenshot --cursor=on # Include cursor
|
dms screenshot --cursor=on # Include cursor
|
||||||
dms screenshot -f jpg -q 85 # JPEG with quality 85
|
dms screenshot -f jpg -q 85 # JPEG with quality 85
|
||||||
dms screenshot --json # Print capture metadata as JSON
|
dms screenshot --json # Print capture metadata as JSON
|
||||||
|
dms screenshot --allow-multiple # Skip the one-selector-at-a-time guard
|
||||||
|
dms screenshot -g # Print selected region geometry (X,Y WxH) to stdout
|
||||||
dms screenshot scroll # Scroll capture, Enter finishes / Esc cancels
|
dms screenshot scroll # Scroll capture, Enter finishes / Esc cancels
|
||||||
dms screenshot scroll --interval 250`,
|
dms screenshot scroll --interval 250`,
|
||||||
}
|
}
|
||||||
@@ -113,7 +123,7 @@ If no previous region exists, falls back to interactive selection.`,
|
|||||||
var ssWindowCmd = &cobra.Command{
|
var ssWindowCmd = &cobra.Command{
|
||||||
Use: "window",
|
Use: "window",
|
||||||
Short: "Capture the focused window",
|
Short: "Capture the focused window",
|
||||||
Long: `Capture the currently focused window. Supported on Hyprland, Mango, and niri.`,
|
Long: `Capture the currently focused window. Supported on Hyprland, Mango, niri, and Aqueous. Aqueous requires a running DMS shell and crops the output including borders and overlapping windows.`,
|
||||||
Run: runScreenshotWindow,
|
Run: runScreenshotWindow,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,12 +160,13 @@ var notifyActionCmd = &cobra.Command{
|
|||||||
Use: "notify-action",
|
Use: "notify-action",
|
||||||
Hidden: true,
|
Hidden: true,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
screenshot.RunNotifyActionListener(args)
|
notify.RunActionListener(args)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
screenshotCmd.PersistentFlags().StringVarP(&ssOutputName, "output", "o", "", "Output name for 'output' mode")
|
screenshotCmd.PersistentFlags().StringVarP(&ssOutputName, "output", "o", "", "Output name for 'output' mode")
|
||||||
|
screenshotCmd.PersistentFlags().StringVar(&ssSeat, "seat", "", "Seat for Aqueous capture (required with multiple seats)")
|
||||||
screenshotCmd.PersistentFlags().StringVar(&ssCursor, "cursor", "off", "Include cursor in screenshot (on/off)")
|
screenshotCmd.PersistentFlags().StringVar(&ssCursor, "cursor", "off", "Include cursor in screenshot (on/off)")
|
||||||
screenshotCmd.PersistentFlags().StringVarP(&ssFormat, "format", "f", "png", "Output format (png, jpg, ppm)")
|
screenshotCmd.PersistentFlags().StringVarP(&ssFormat, "format", "f", "png", "Output format (png, jpg, ppm)")
|
||||||
screenshotCmd.PersistentFlags().IntVarP(&ssQuality, "quality", "q", 90, "JPEG quality (1-100)")
|
screenshotCmd.PersistentFlags().IntVarP(&ssQuality, "quality", "q", 90, "JPEG quality (1-100)")
|
||||||
@@ -168,6 +179,8 @@ func init() {
|
|||||||
screenshotCmd.PersistentFlags().BoolVar(&ssReset, "reset", false, "Reset saved last-region preselection before capturing")
|
screenshotCmd.PersistentFlags().BoolVar(&ssReset, "reset", false, "Reset saved last-region preselection before capturing")
|
||||||
screenshotCmd.PersistentFlags().BoolVar(&ssStdout, "stdout", false, "Output image to stdout (for piping to swappy, etc.)")
|
screenshotCmd.PersistentFlags().BoolVar(&ssStdout, "stdout", false, "Output image to stdout (for piping to swappy, etc.)")
|
||||||
screenshotCmd.PersistentFlags().BoolVar(&ssJSON, "json", false, "Print capture metadata as JSON")
|
screenshotCmd.PersistentFlags().BoolVar(&ssJSON, "json", false, "Print capture metadata as JSON")
|
||||||
|
screenshotCmd.PersistentFlags().BoolVarP(&ssGeometry, "geometry", "g", false, "Print selected region geometry (X,Y WxH) to stdout without capturing an image")
|
||||||
|
screenshotCmd.PersistentFlags().BoolVar(&ssAllowMulti, "allow-multiple", false, "Open a selector even when another one is already open")
|
||||||
|
|
||||||
ssScrollCmd.Flags().IntVar(&ssScrollInterval, "interval", 45, "Capture interval in milliseconds (30-1000)")
|
ssScrollCmd.Flags().IntVar(&ssScrollInterval, "interval", 45, "Capture interval in milliseconds (30-1000)")
|
||||||
|
|
||||||
@@ -187,6 +200,7 @@ func getScreenshotConfig(mode screenshot.Mode) screenshot.Config {
|
|||||||
config := screenshot.DefaultConfig()
|
config := screenshot.DefaultConfig()
|
||||||
config.Mode = mode
|
config.Mode = mode
|
||||||
config.OutputName = ssOutputName
|
config.OutputName = ssOutputName
|
||||||
|
config.Seat = ssSeat
|
||||||
if strings.EqualFold(ssCursor, "on") {
|
if strings.EqualFold(ssCursor, "on") {
|
||||||
config.Cursor = screenshot.CursorOn
|
config.Cursor = screenshot.CursorOn
|
||||||
}
|
}
|
||||||
@@ -196,6 +210,14 @@ func getScreenshotConfig(mode screenshot.Mode) screenshot.Config {
|
|||||||
config.NoConfirm = ssNoConfirm
|
config.NoConfirm = ssNoConfirm
|
||||||
config.Reset = ssReset
|
config.Reset = ssReset
|
||||||
config.Stdout = ssStdout
|
config.Stdout = ssStdout
|
||||||
|
config.Geometry = ssGeometry
|
||||||
|
config.AllowMultiple = ssAllowMulti
|
||||||
|
|
||||||
|
if ssGeometry {
|
||||||
|
config.Clipboard = false
|
||||||
|
config.SaveFile = false
|
||||||
|
config.Notify = false
|
||||||
|
}
|
||||||
|
|
||||||
if ssOutputDir != "" {
|
if ssOutputDir != "" {
|
||||||
config.OutputDir = ssOutputDir
|
config.OutputDir = ssOutputDir
|
||||||
@@ -224,7 +246,8 @@ func getScreenshotConfig(mode screenshot.Mode) screenshot.Config {
|
|||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
// setPopoutScreenshotMode toggles the shell handshake so popouts drop their keyboard grab during region select. Best-effort.
|
// setPopoutScreenshotMode toggles the shell handshake so popouts drop their keyboard grab during region select.
|
||||||
|
// Best-effort and not awaited: qs takes ~20ms to come up, longer than the selector needs to show.
|
||||||
func setPopoutScreenshotMode(begin bool) {
|
func setPopoutScreenshotMode(begin bool) {
|
||||||
fn := "end"
|
fn := "end"
|
||||||
if begin {
|
if begin {
|
||||||
@@ -243,7 +266,7 @@ func setPopoutScreenshotMode(begin bool) {
|
|||||||
cmdArgs = append(cmdArgs, "-p", shellApp.ConfigPath())
|
cmdArgs = append(cmdArgs, "-p", shellApp.ConfigPath())
|
||||||
}
|
}
|
||||||
cmdArgs = append(cmdArgs, "call", "screenshot", fn)
|
cmdArgs = append(cmdArgs, "call", "screenshot", fn)
|
||||||
_ = exec.Command("qs", cmdArgs...).Run()
|
_ = exec.Command("qs", cmdArgs...).Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeScreenshotJSON(meta screenshotMetadata) {
|
func writeScreenshotJSON(meta screenshotMetadata) {
|
||||||
@@ -275,17 +298,24 @@ func runScreenshot(config screenshot.Config) {
|
|||||||
fmt.Fprintln(os.Stderr, "Error: --json cannot be combined with --stdout")
|
fmt.Fprintln(os.Stderr, "Error: --json cannot be combined with --stdout")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
if config.Geometry {
|
||||||
|
if config.Stdout {
|
||||||
|
fmt.Fprintln(os.Stderr, "Error: --geometry cannot be combined with --stdout")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if config.Mode == screenshot.ModeScroll {
|
||||||
|
fmt.Fprintln(os.Stderr, "Error: --geometry cannot be combined with scroll mode")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Short-lived process over a few tens of MB: let the heap grow instead of paying GC cycles mid-capture.
|
||||||
|
debug.SetGCPercent(-1)
|
||||||
|
debug.SetMemoryLimit(1 << 30)
|
||||||
|
|
||||||
// Region select needs the keyboard; drop popout grabs for its duration.
|
// Region select needs the keyboard; drop popout grabs for its duration.
|
||||||
result, err := func() (*screenshot.CaptureResult, error) {
|
config.SelectorHook = setPopoutScreenshotMode
|
||||||
interactive := config.Mode == screenshot.ModeRegion || config.Mode == screenshot.ModeLastRegion || config.Mode == screenshot.ModeScroll
|
result, err := screenshot.New(config).Run()
|
||||||
if interactive {
|
|
||||||
setPopoutScreenshotMode(true)
|
|
||||||
defer setPopoutScreenshotMode(false)
|
|
||||||
}
|
|
||||||
return screenshot.New(config).Run()
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
exitScreenshotError("", err)
|
exitScreenshotError("", err)
|
||||||
}
|
}
|
||||||
@@ -294,17 +324,39 @@ func runScreenshot(config screenshot.Config) {
|
|||||||
if ssJSON {
|
if ssJSON {
|
||||||
writeScreenshotJSON(screenshotMetadata{Status: "aborted", Error: "User cancelled selection"})
|
writeScreenshotJSON(screenshotMetadata{Status: "aborted", Error: "User cancelled selection"})
|
||||||
}
|
}
|
||||||
|
if config.Geometry {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer result.Buffer.Close()
|
if config.Geometry {
|
||||||
|
if ssJSON {
|
||||||
|
x := int(result.Region.X)
|
||||||
|
y := int(result.Region.Y)
|
||||||
|
writeScreenshotJSON(screenshotMetadata{
|
||||||
|
Status: "success",
|
||||||
|
X: &x,
|
||||||
|
Y: &y,
|
||||||
|
Width: int(result.Region.Width),
|
||||||
|
Height: int(result.Region.Height),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
fmt.Println(result.Region.GeometryString())
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
if result.YInverted {
|
if result.Buffer != nil {
|
||||||
result.Buffer.FlipVertical()
|
defer result.Buffer.Close()
|
||||||
|
|
||||||
|
if result.YInverted {
|
||||||
|
result.Buffer.FlipVertical()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Stdout {
|
if config.Stdout {
|
||||||
if err := writeImageToStdout(result.Buffer, config.Format, config.Quality, result.Format); err != nil {
|
if err := writeImageToStdout(result.Buffer, config.Format, config.Quality, result.Format, result.CICP); err != nil {
|
||||||
exitScreenshotError(" writing to stdout", err)
|
exitScreenshotError(" writing to stdout", err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -324,7 +376,7 @@ func runScreenshot(config screenshot.Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
filePath = filepath.Join(outputDir, filename)
|
filePath = filepath.Join(outputDir, filename)
|
||||||
if err := screenshot.WriteToFileWithFormat(result.Buffer, filePath, config.Format, config.Quality, result.Format); err != nil {
|
if err := screenshot.WriteToFileWithFormat(result.Buffer, filePath, config.Format, config.Quality, result.Format, result.CICP); err != nil {
|
||||||
exitScreenshotError(" writing file", err)
|
exitScreenshotError(" writing file", err)
|
||||||
}
|
}
|
||||||
if !ssJSON {
|
if !ssJSON {
|
||||||
@@ -333,7 +385,7 @@ func runScreenshot(config screenshot.Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if config.Clipboard {
|
if config.Clipboard {
|
||||||
if err := copyImageToClipboard(result.Buffer, config.Format, config.Quality, result.Format); err != nil {
|
if err := copyImageToClipboard(result.Buffer, config.Format, config.Quality, result.Format, result.CICP); err != nil {
|
||||||
exitScreenshotError(" copying to clipboard", err)
|
exitScreenshotError(" copying to clipboard", err)
|
||||||
}
|
}
|
||||||
if !ssJSON && !config.SaveFile {
|
if !ssJSON && !config.SaveFile {
|
||||||
@@ -358,31 +410,30 @@ func runScreenshot(config screenshot.Config) {
|
|||||||
|
|
||||||
if config.Notify {
|
if config.Notify {
|
||||||
thumbData, thumbW, thumbH := bufferToRGBThumbnail(result.Buffer, 256, result.Format)
|
thumbData, thumbW, thumbH := bufferToRGBThumbnail(result.Buffer, 256, result.Format)
|
||||||
screenshot.SendNotification(screenshot.NotifyResult{
|
id := screenshot.SendNotification(screenshot.NotifyResult{
|
||||||
FilePath: filePath,
|
FilePath: filePath,
|
||||||
Clipboard: config.Clipboard,
|
Clipboard: config.Clipboard,
|
||||||
ImageData: thumbData,
|
ImageData: thumbData,
|
||||||
Width: thumbW,
|
Width: thumbW,
|
||||||
Height: thumbH,
|
Height: thumbH,
|
||||||
})
|
})
|
||||||
|
watchNotificationAction(id, filePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyImageToClipboard(buf *screenshot.ShmBuffer, format screenshot.Format, quality int, pixelFormat uint32) error {
|
func copyImageToClipboard(buf *screenshot.ShmBuffer, format screenshot.Format, quality int, pixelFormat uint32, cicp *screenshot.CICP) error {
|
||||||
var mimeType string
|
var mimeType string
|
||||||
var data bytes.Buffer
|
var data bytes.Buffer
|
||||||
|
|
||||||
img := screenshot.BufferToImageWithFormat(buf, pixelFormat)
|
|
||||||
|
|
||||||
switch format {
|
switch format {
|
||||||
case screenshot.FormatJPEG:
|
case screenshot.FormatJPEG:
|
||||||
mimeType = "image/jpeg"
|
mimeType = "image/jpeg"
|
||||||
if err := screenshot.EncodeJPEG(&data, img, quality); err != nil {
|
if err := screenshot.EncodeBufferJPEG(&data, buf, pixelFormat, quality); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
mimeType = "image/png"
|
mimeType = "image/png"
|
||||||
if err := screenshot.EncodePNG(&data, img); err != nil {
|
if err := screenshot.EncodeBufferPNG(&data, buf, pixelFormat, cicp); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -390,14 +441,12 @@ func copyImageToClipboard(buf *screenshot.ShmBuffer, format screenshot.Format, q
|
|||||||
return clipboard.Copy(data.Bytes(), mimeType)
|
return clipboard.Copy(data.Bytes(), mimeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeImageToStdout(buf *screenshot.ShmBuffer, format screenshot.Format, quality int, pixelFormat uint32) error {
|
func writeImageToStdout(buf *screenshot.ShmBuffer, format screenshot.Format, quality int, pixelFormat uint32, cicp *screenshot.CICP) error {
|
||||||
img := screenshot.BufferToImageWithFormat(buf, pixelFormat)
|
|
||||||
|
|
||||||
switch format {
|
switch format {
|
||||||
case screenshot.FormatJPEG:
|
case screenshot.FormatJPEG:
|
||||||
return screenshot.EncodeJPEG(os.Stdout, img, quality)
|
return screenshot.EncodeBufferJPEG(os.Stdout, buf, pixelFormat, quality)
|
||||||
default:
|
default:
|
||||||
return screenshot.EncodePNG(os.Stdout, img)
|
return screenshot.EncodeBufferPNG(os.Stdout, buf, pixelFormat, cicp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,9 +473,12 @@ func bufferToRGBThumbnail(buf *screenshot.ShmBuffer, maxSize int, pixelFormat ui
|
|||||||
data := buf.Data()
|
data := buf.Data()
|
||||||
rgb := make([]byte, dstW*dstH*3)
|
rgb := make([]byte, dstW*dstH*3)
|
||||||
|
|
||||||
|
is10Bit := screenshot.PixelFormat(pixelFormat).Is10Bit()
|
||||||
|
|
||||||
var swapRB bool
|
var swapRB bool
|
||||||
switch pixelFormat {
|
switch pixelFormat {
|
||||||
case uint32(screenshot.FormatABGR8888), uint32(screenshot.FormatXBGR8888):
|
case uint32(screenshot.FormatABGR8888), uint32(screenshot.FormatXBGR8888),
|
||||||
|
uint32(screenshot.FormatABGR2101010), uint32(screenshot.FormatXBGR2101010):
|
||||||
swapRB = false
|
swapRB = false
|
||||||
default:
|
default:
|
||||||
swapRB = true
|
swapRB = true
|
||||||
@@ -447,11 +499,19 @@ func bufferToRGBThumbnail(buf *screenshot.ShmBuffer, maxSize int, pixelFormat ui
|
|||||||
if si+3 >= len(data) {
|
if si+3 >= len(data) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if swapRB {
|
switch {
|
||||||
|
case is10Bit:
|
||||||
|
v := binary.LittleEndian.Uint32(data[si:])
|
||||||
|
c0, c1, c2 := uint8(v>>2), uint8(v>>12), uint8(v>>22)
|
||||||
|
if swapRB {
|
||||||
|
c0, c2 = c2, c0
|
||||||
|
}
|
||||||
|
rgb[di+0], rgb[di+1], rgb[di+2] = c0, c1, c2
|
||||||
|
case swapRB:
|
||||||
rgb[di+0] = data[si+2]
|
rgb[di+0] = data[si+2]
|
||||||
rgb[di+1] = data[si+1]
|
rgb[di+1] = data[si+1]
|
||||||
rgb[di+2] = data[si+0]
|
rgb[di+2] = data[si+0]
|
||||||
} else {
|
default:
|
||||||
rgb[di+0] = data[si+0]
|
rgb[di+0] = data[si+0]
|
||||||
rgb[di+1] = data[si+1]
|
rgb[di+1] = data[si+1]
|
||||||
rgb[di+2] = data[si+2]
|
rgb[di+2] = data[si+2]
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ func showSession(id string) (sessionInfo, error) {
|
|||||||
return sessionInfo{}, err
|
return sessionInfo{}, err
|
||||||
}
|
}
|
||||||
fields := map[string]string{}
|
fields := map[string]string{}
|
||||||
for _, line := range strings.Split(string(out), "\n") {
|
for line := range strings.SplitSeq(string(out), "\n") {
|
||||||
idx := strings.IndexByte(line, '=')
|
idx := strings.IndexByte(line, '=')
|
||||||
if idx <= 0 {
|
if idx <= 0 {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -356,22 +356,18 @@ func runSetup() error {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
var results []config.DeploymentResult
|
var results []config.DeploymentResult
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if wmSelected && terminalSelected {
|
if wmSelected {
|
||||||
results, err = deployer.DeployConfigurationsWithSystemd(ctx, wm, terminal, useSystemd)
|
var result config.DeploymentResult
|
||||||
} else if wmSelected {
|
result, err = deployer.DeployCompositor(wm, terminal, useSystemd)
|
||||||
results, err = deployer.DeployConfigurationsWithSystemd(ctx, wm, deps.TerminalGhostty, useSystemd)
|
results = append(results, result)
|
||||||
if len(results) > 1 {
|
}
|
||||||
results = results[:1]
|
if err == nil && terminalSelected {
|
||||||
}
|
var terminalResults []config.DeploymentResult
|
||||||
} else if terminalSelected {
|
terminalResults, err = deployer.DeployTerminal(terminal)
|
||||||
results, err = deployer.DeployConfigurationsWithSystemd(ctx, deps.WindowManagerNiri, terminal, useSystemd)
|
results = append(results, terminalResults...)
|
||||||
if len(results) > 0 && results[0].ConfigType == "Niri" {
|
|
||||||
results = results[1:]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
close(logChan)
|
close(logChan)
|
||||||
@@ -495,49 +491,54 @@ func promptSystemd() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func checkExistingConfigs(wm deps.WindowManager, wmSelected bool, terminal deps.Terminal, terminalSelected bool) bool {
|
func checkExistingConfigs(wm deps.WindowManager, wmSelected bool, terminal deps.Terminal, terminalSelected bool) bool {
|
||||||
homeDir := os.Getenv("HOME")
|
if wmSelected && firstExistingPath(compositorPrimaryPaths(wm)) != "" {
|
||||||
willBackup := false
|
return true
|
||||||
|
|
||||||
if wmSelected {
|
|
||||||
var configPaths []string
|
|
||||||
switch wm {
|
|
||||||
case deps.WindowManagerNiri:
|
|
||||||
configPaths = []string{filepath.Join(homeDir, ".config", "niri", "config.kdl")}
|
|
||||||
case deps.WindowManagerHyprland:
|
|
||||||
configPaths = []string{
|
|
||||||
filepath.Join(homeDir, ".config", "hypr", "hyprland.lua"),
|
|
||||||
filepath.Join(homeDir, ".config", "hypr", "hyprland.conf"),
|
|
||||||
}
|
|
||||||
case deps.WindowManagerMango:
|
|
||||||
configPaths = []string{
|
|
||||||
filepath.Join(homeDir, ".config", "mango", "config.conf"),
|
|
||||||
filepath.Join(homeDir, ".config", "mango", "mango.conf"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, configPath := range configPaths {
|
|
||||||
if _, err := os.Stat(configPath); err == nil {
|
|
||||||
willBackup = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return terminalSelected && firstExistingPath([]string{terminalPrimaryPath(terminal)}) != ""
|
||||||
|
}
|
||||||
|
|
||||||
if terminalSelected {
|
func compositorPrimaryPaths(wm deps.WindowManager) []string {
|
||||||
var configPath string
|
homeDir := os.Getenv("HOME")
|
||||||
switch terminal {
|
switch wm {
|
||||||
case deps.TerminalGhostty:
|
case deps.WindowManagerNiri:
|
||||||
configPath = filepath.Join(homeDir, ".config", "ghostty", "config")
|
return []string{filepath.Join(homeDir, ".config", "niri", "config.kdl")}
|
||||||
case deps.TerminalKitty:
|
case deps.WindowManagerHyprland:
|
||||||
configPath = filepath.Join(homeDir, ".config", "kitty", "kitty.conf")
|
return []string{
|
||||||
case deps.TerminalAlacritty:
|
filepath.Join(homeDir, ".config", "hypr", "hyprland.lua"),
|
||||||
configPath = filepath.Join(homeDir, ".config", "alacritty", "alacritty.toml")
|
filepath.Join(homeDir, ".config", "hypr", "hyprland.conf"),
|
||||||
}
|
}
|
||||||
|
case deps.WindowManagerMango:
|
||||||
|
return []string{
|
||||||
|
filepath.Join(homeDir, ".config", "mango", "config.conf"),
|
||||||
|
filepath.Join(homeDir, ".config", "mango", "mango.conf"),
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func terminalPrimaryPath(terminal deps.Terminal) string {
|
||||||
|
homeDir := os.Getenv("HOME")
|
||||||
|
switch terminal {
|
||||||
|
case deps.TerminalGhostty:
|
||||||
|
return filepath.Join(homeDir, ".config", "ghostty", "config")
|
||||||
|
case deps.TerminalKitty:
|
||||||
|
return filepath.Join(homeDir, ".config", "kitty", "kitty.conf")
|
||||||
|
case deps.TerminalAlacritty:
|
||||||
|
return filepath.Join(homeDir, ".config", "alacritty", "alacritty.toml")
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := os.Stat(configPath); err == nil {
|
func firstExistingPath(paths []string) string {
|
||||||
willBackup = true
|
for _, path := range paths {
|
||||||
|
if path == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(path); err == nil {
|
||||||
|
return path
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return ""
|
||||||
return willBackup
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/config"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var setupHeadlessCmd = &cobra.Command{
|
||||||
|
Use: "headless",
|
||||||
|
Short: "Deploy DMS configurations without prompts",
|
||||||
|
Long: "Deploy compositor and terminal configurations non-interactively, for installers and scripts",
|
||||||
|
PersistentPreRunE: requireMutableSystemCommand,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
compositor, _ := cmd.Flags().GetString("compositor")
|
||||||
|
terminal, _ := cmd.Flags().GetString("terminal")
|
||||||
|
noSystemd, _ := cmd.Flags().GetBool("no-systemd")
|
||||||
|
force, _ := cmd.Flags().GetBool("force")
|
||||||
|
skipExisting, _ := cmd.Flags().GetBool("skip-existing")
|
||||||
|
cmd.SilenceUsage = true
|
||||||
|
return runSetupHeadless(compositor, terminal, noSystemd, force, skipExisting)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
setupHeadlessCmd.Flags().String("compositor", "", "Compositor to configure: niri, hyprland, or mango")
|
||||||
|
setupHeadlessCmd.Flags().String("terminal", "", "Also deploy a terminal config: ghostty, kitty, or alacritty")
|
||||||
|
setupHeadlessCmd.Flags().Bool("no-systemd", false, "Deploy session config without systemd integration")
|
||||||
|
setupHeadlessCmd.Flags().Bool("force", false, "Overwrite existing configs (timestamped backups are created)")
|
||||||
|
setupHeadlessCmd.Flags().Bool("skip-existing", false, "Warn and skip instead of failing when a config already exists")
|
||||||
|
_ = setupHeadlessCmd.MarkFlagRequired("compositor")
|
||||||
|
setupHeadlessCmd.MarkFlagsMutuallyExclusive("force", "skip-existing")
|
||||||
|
}
|
||||||
|
|
||||||
|
func runSetupHeadless(compositor, terminal string, noSystemd, force, skipExisting bool) error {
|
||||||
|
wm, err := parseHeadlessCompositor(compositor)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
term, termSelected, err := parseHeadlessTerminal(terminal)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
deployCompositor := true
|
||||||
|
deployTerminal := termSelected
|
||||||
|
|
||||||
|
if !force {
|
||||||
|
if existing := firstExistingPath(compositorPrimaryPaths(wm)); existing != "" {
|
||||||
|
if !skipExisting {
|
||||||
|
return fmt.Errorf("%s config already exists: %s (use --force to overwrite with backup, or --skip-existing)", compositor, existing)
|
||||||
|
}
|
||||||
|
fmt.Printf("⚠ Skipping %s config, already exists: %s\n", compositor, existing)
|
||||||
|
deployCompositor = false
|
||||||
|
}
|
||||||
|
if deployTerminal {
|
||||||
|
if existing := firstExistingPath([]string{terminalPrimaryPath(term)}); existing != "" {
|
||||||
|
if !skipExisting {
|
||||||
|
return fmt.Errorf("%s config already exists: %s (use --force to overwrite with backup, or --skip-existing)", terminal, existing)
|
||||||
|
}
|
||||||
|
fmt.Printf("⚠ Skipping %s config, already exists: %s\n", terminal, existing)
|
||||||
|
deployTerminal = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !deployCompositor && !deployTerminal {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
logChan := make(chan string, 100)
|
||||||
|
logDone := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
for msg := range logChan {
|
||||||
|
fmt.Println(" " + msg)
|
||||||
|
}
|
||||||
|
close(logDone)
|
||||||
|
}()
|
||||||
|
deployer := config.NewConfigDeployer(logChan)
|
||||||
|
|
||||||
|
var results []config.DeploymentResult
|
||||||
|
var deployErr error
|
||||||
|
if deployCompositor {
|
||||||
|
result, err := deployer.DeployCompositor(wm, term, headlessUseSystemd(wm, noSystemd))
|
||||||
|
results = append(results, result)
|
||||||
|
deployErr = err
|
||||||
|
}
|
||||||
|
if deployErr == nil && deployTerminal {
|
||||||
|
terminalResults, err := deployer.DeployTerminal(term)
|
||||||
|
results = append(results, terminalResults...)
|
||||||
|
deployErr = err
|
||||||
|
}
|
||||||
|
|
||||||
|
close(logChan)
|
||||||
|
<-logDone
|
||||||
|
|
||||||
|
for _, result := range results {
|
||||||
|
if !result.Deployed {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Printf("✓ %s: %s\n", result.ConfigType, result.Path)
|
||||||
|
if result.BackupPath != "" {
|
||||||
|
fmt.Printf(" Backup: %s\n", result.BackupPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if deployErr != nil {
|
||||||
|
return fmt.Errorf("deployment failed: %w", deployErr)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseHeadlessCompositor(name string) (deps.WindowManager, error) {
|
||||||
|
switch name {
|
||||||
|
case "niri":
|
||||||
|
return deps.WindowManagerNiri, nil
|
||||||
|
case "hyprland":
|
||||||
|
return deps.WindowManagerHyprland, nil
|
||||||
|
case "mango":
|
||||||
|
return deps.WindowManagerMango, nil
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("unknown compositor %q (expected niri, hyprland, or mango)", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseHeadlessTerminal(name string) (deps.Terminal, bool, error) {
|
||||||
|
switch name {
|
||||||
|
case "":
|
||||||
|
return deps.TerminalGhostty, false, nil
|
||||||
|
case "ghostty":
|
||||||
|
return deps.TerminalGhostty, true, nil
|
||||||
|
case "kitty":
|
||||||
|
return deps.TerminalKitty, true, nil
|
||||||
|
case "alacritty":
|
||||||
|
return deps.TerminalAlacritty, true, nil
|
||||||
|
default:
|
||||||
|
return 0, false, fmt.Errorf("unknown terminal %q (expected ghostty, kitty, or alacritty)", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func headlessUseSystemd(wm deps.WindowManager, noSystemd bool) bool {
|
||||||
|
if noSystemd || wm == deps.WindowManagerMango {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return !isVoidSetup()
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ var Version = "dev"
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
authCmd.AddCommand(authSyncCmd, authResolveLockCmd, authListServicesCmd, authValidateCmd)
|
authCmd.AddCommand(authSyncCmd, authResolveLockCmd, authListServicesCmd, authValidateCmd)
|
||||||
setupCmd.AddCommand(setupBindsCmd, setupLayoutCmd, setupColorsCmd, setupAlttabCmd, setupOutputsCmd, setupCursorCmd, setupWindowrulesCmd)
|
setupCmd.AddCommand(setupHeadlessCmd, setupBindsCmd, setupLayoutCmd, setupColorsCmd, setupAlttabCmd, setupOutputsCmd, setupCursorCmd, setupWindowrulesCmd)
|
||||||
updateCmd.AddCommand(updateCheckCmd)
|
updateCmd.AddCommand(updateCheckCmd)
|
||||||
pluginsCmd.AddCommand(pluginsBrowseCmd, pluginsListCmd, pluginsInstallCmd, pluginsUninstallCmd, pluginsUpdateCmd, pluginsLockCmd, pluginsRestoreCmd)
|
pluginsCmd.AddCommand(pluginsBrowseCmd, pluginsListCmd, pluginsInstallCmd, pluginsUninstallCmd, pluginsUpdateCmd, pluginsLockCmd, pluginsRestoreCmd)
|
||||||
registryCmd.AddCommand(registryListCmd, registryAddCmd, registryRemoveCmd)
|
registryCmd.AddCommand(registryListCmd, registryAddCmd, registryRemoveCmd)
|
||||||
@@ -26,6 +26,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
disableMemProfilingUnlessRequested()
|
||||||
clipboard.MaybeServeAndExit()
|
clipboard.MaybeServeAndExit()
|
||||||
|
|
||||||
if os.Geteuid() == 0 && !isReadOnlyCommand(os.Args) {
|
if os.Geteuid() == 0 && !isReadOnlyCommand(os.Args) {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ var Version = "dev"
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
authCmd.AddCommand(authSyncCmd, authResolveLockCmd, authListServicesCmd, authValidateCmd)
|
authCmd.AddCommand(authSyncCmd, authResolveLockCmd, authListServicesCmd, authValidateCmd)
|
||||||
setupCmd.AddCommand(setupBindsCmd, setupLayoutCmd, setupColorsCmd, setupAlttabCmd, setupOutputsCmd, setupCursorCmd, setupWindowrulesCmd)
|
setupCmd.AddCommand(setupHeadlessCmd, setupBindsCmd, setupLayoutCmd, setupColorsCmd, setupAlttabCmd, setupOutputsCmd, setupCursorCmd, setupWindowrulesCmd)
|
||||||
pluginsCmd.AddCommand(pluginsBrowseCmd, pluginsListCmd, pluginsInstallCmd, pluginsUninstallCmd, pluginsUpdateCmd, pluginsLockCmd, pluginsRestoreCmd)
|
pluginsCmd.AddCommand(pluginsBrowseCmd, pluginsListCmd, pluginsInstallCmd, pluginsUninstallCmd, pluginsUpdateCmd, pluginsLockCmd, pluginsRestoreCmd)
|
||||||
rootCmd.AddCommand(getCommonCommands()...)
|
rootCmd.AddCommand(getCommonCommands()...)
|
||||||
rootCmd.AddCommand(authCmd)
|
rootCmd.AddCommand(authCmd)
|
||||||
@@ -22,6 +22,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
disableMemProfilingUnlessRequested()
|
||||||
clipboard.MaybeServeAndExit()
|
clipboard.MaybeServeAndExit()
|
||||||
|
|
||||||
if os.Geteuid() == 0 && !isReadOnlyCommand(os.Args) {
|
if os.Geteuid() == 0 && !isReadOnlyCommand(os.Args) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// maxIPCMessageSize allows room for a 50 MB clipboard entry plus JSON/base64
|
// maxIPCMessageSize allows room for a 50 MB clipboard entry plus JSON/base64
|
||||||
@@ -96,10 +97,7 @@ func tryServerRequest(req models.Request) (*models.Response[any], bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getServerSocketPath() string {
|
func getServerSocketPath() string {
|
||||||
runtimeDir := os.Getenv("XDG_RUNTIME_DIR")
|
runtimeDir := utils.RuntimeDir()
|
||||||
if runtimeDir == "" {
|
|
||||||
runtimeDir = os.TempDir()
|
|
||||||
}
|
|
||||||
|
|
||||||
if sessionSock, ok := shellApp.SessionSocketPath(); ok {
|
if sessionSock, ok := shellApp.SessionSocketPath(); ok {
|
||||||
return sessionSock
|
return sessionSock
|
||||||
|
|||||||
+27
-2
@@ -14,6 +14,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/qsipc"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ipcTargets map[string]map[string][]string
|
type ipcTargets map[string]map[string][]string
|
||||||
@@ -137,6 +138,9 @@ func runShellIPCCommand(args []string) {
|
|||||||
if args[0] != "call" {
|
if args[0] != "call" {
|
||||||
args = append([]string{"call"}, args...)
|
args = append([]string{"call"}, args...)
|
||||||
}
|
}
|
||||||
|
if tryDirectIPCCall(args) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
baseArgs, err := buildQsIPCBaseArgs()
|
baseArgs, err := buildQsIPCBaseArgs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -153,6 +157,27 @@ func runShellIPCCommand(args []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tryDirectIPCCall invokes the shell over its IPC socket without spawning qs.
|
||||||
|
// Any failure falls back to the qs child process path, which owns error output.
|
||||||
|
func tryDirectIPCCall(args []string) bool {
|
||||||
|
if len(args) < 3 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
pid, ok := shellApp.SessionPID()
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
result, isVoid, err := qsipc.Call(qsipc.SocketPathForPID(pid), args[1], args[2], args[3:])
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("Direct Quickshell IPC failed, falling back to qs: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !isVoid {
|
||||||
|
fmt.Println(result)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func printIPCHelp() {
|
func printIPCHelp() {
|
||||||
fmt.Println("Usage: dms ipc call <target> <function> [args...]")
|
fmt.Println("Usage: dms ipc call <target> <function> [args...]")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
@@ -261,8 +286,8 @@ func ensureFontCache() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func fontInCache(target, cache string) bool {
|
func fontInCache(target, cache string) bool {
|
||||||
for _, line := range strings.Split(cache, "\n") {
|
for line := range strings.SplitSeq(cache, "\n") {
|
||||||
for _, fam := range strings.Split(strings.TrimSpace(line), ",") {
|
for fam := range strings.SplitSeq(strings.TrimSpace(line), ",") {
|
||||||
if strings.TrimSpace(fam) == target {
|
if strings.TrimSpace(fam) == target {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/config"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/config"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/gpu"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/shellembed"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/shellembed"
|
||||||
@@ -13,15 +14,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var shellApp = shellapp.New(shellapp.Config{
|
var shellApp = shellapp.New(shellapp.Config{
|
||||||
ID: "danklinux",
|
ID: "danklinux",
|
||||||
EnvPrefix: "DMS",
|
EnvPrefix: "DMS",
|
||||||
QSAppID: "com.danklinux.dms",
|
QSAppID: "com.danklinux.dms",
|
||||||
Version: Version,
|
Version: Version,
|
||||||
Embedded: embeddedShell{},
|
Embedded: embeddedShell{},
|
||||||
Boot: bootBackend,
|
Boot: bootBackend,
|
||||||
PreLaunch: preLaunch,
|
PreLaunch: preLaunch,
|
||||||
ExtraEnv: dmsExtraEnv,
|
ExtraEnv: dmsExtraEnv,
|
||||||
OnUIExit: logStartupFailure,
|
OnUIExit: logStartupFailure,
|
||||||
|
SessionRestartExitCode: dmsSessionRestartExitCode,
|
||||||
|
TryManagedRestart: trySystemdRestart,
|
||||||
})
|
})
|
||||||
|
|
||||||
type embeddedShell struct{}
|
type embeddedShell struct{}
|
||||||
@@ -78,5 +81,9 @@ func dmsExtraEnv(string) []string {
|
|||||||
if os.Getenv("QSG_USE_SIMPLE_ANIMATION_DRIVER") == "" {
|
if os.Getenv("QSG_USE_SIMPLE_ANIMATION_DRIVER") == "" {
|
||||||
env = append(env, "QSG_USE_SIMPLE_ANIMATION_DRIVER=1")
|
env = append(env, "QSG_USE_SIMPLE_ANIMATION_DRIVER=1")
|
||||||
}
|
}
|
||||||
|
if _, set := os.LookupEnv("MALLOC_CONF"); !set {
|
||||||
|
env = append(env, "MALLOC_CONF=thp:never,narenas:4,dirty_decay_ms:3000")
|
||||||
|
}
|
||||||
|
env = append(env, gpu.EGLVendorEnv()...)
|
||||||
return env
|
return env
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const dmsSystemdUnit = "dms.service"
|
||||||
|
const dmsSessionRestartExitCode = 75 // EX_TEMPFAIL
|
||||||
|
|
||||||
|
var runSystemctl = func(args ...string) error {
|
||||||
|
output, err := exec.Command("systemctl", args...).CombinedOutput()
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if detail := strings.TrimSpace(string(output)); detail != "" {
|
||||||
|
return fmt.Errorf("%s: %w", detail, err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func trySystemdRestart() (bool, error) {
|
||||||
|
if err := runSystemctl("--user", "is-active", "--quiet", dmsSystemdUnit); err != nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := runSystemctl("--user", "restart", dmsSystemdUnit); err != nil {
|
||||||
|
return true, fmt.Errorf("restarting %s: %w", dmsSystemdUnit, err)
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
+6
-6
@@ -15,12 +15,12 @@ func getThemedASCII() string {
|
|||||||
theme := tui.TerminalTheme()
|
theme := tui.TerminalTheme()
|
||||||
|
|
||||||
logo := `
|
logo := `
|
||||||
██████╗ █████╗ ███╗ ██╗██╗ ██╗
|
██████╗ ███╗ ███╗███████╗
|
||||||
██╔══██╗██╔══██╗████╗ ██║██║ ██╔╝
|
██╔══██╗████╗ ████║██╔════╝
|
||||||
██║ ██║███████║██╔██╗ ██║█████╔╝
|
██║ ██║██╔████╔██║███████╗
|
||||||
██║ ██║██╔══██║██║╚██╗██║██╔═██╗
|
██║ ██║██║╚██╔╝██║╚════██║
|
||||||
██████╔╝██║ ██║██║ ╚████║██║ ██╗
|
██████╔╝██║ ╚═╝ ██║███████║
|
||||||
╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝`
|
╚═════╝ ╚═╝ ╚═╝╚══════╝`
|
||||||
|
|
||||||
style := lipgloss.NewStyle().
|
style := lipgloss.NewStyle().
|
||||||
Foreground(lipgloss.Color(theme.Primary)).
|
Foreground(lipgloss.Color(theme.Primary)).
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func disableMemProfilingUnlessRequested() {
|
||||||
|
if os.Getenv("DMS_PPROF") != "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
runtime.MemProfileRate = 0
|
||||||
|
}
|
||||||
|
|
||||||
// isReadOnlyCommand returns true if the CLI args indicate a command that is
|
// isReadOnlyCommand returns true if the CLI args indicate a command that is
|
||||||
// safe to run as root (e.g. shell completion, help).
|
// safe to run as root (e.g. shell completion, help).
|
||||||
func isReadOnlyCommand(args []string) bool {
|
func isReadOnlyCommand(args []string) bool {
|
||||||
|
|||||||
+8
-8
@@ -1,17 +1,20 @@
|
|||||||
module github.com/AvengeMedia/DankMaterialShell/core
|
module github.com/AvengeMedia/DankMaterialShell/core
|
||||||
|
|
||||||
go 1.26.4
|
go 1.26.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AvengeMedia/dgop v0.2.4-0.20260808141551-f559f2f31d2a
|
github.com/AvengeMedia/dgop v0.2.4-0.20260819141338-085d828cf577
|
||||||
|
github.com/Nadim147c/material/v3 v3.1.1
|
||||||
github.com/Wifx/gonetworkmanager/v2 v2.2.0
|
github.com/Wifx/gonetworkmanager/v2 v2.2.0
|
||||||
github.com/alecthomas/chroma/v2 v2.27.0
|
github.com/alecthomas/chroma/v2 v2.27.0
|
||||||
github.com/charmbracelet/bubbles v1.0.0
|
github.com/charmbracelet/bubbles v1.0.0
|
||||||
github.com/charmbracelet/bubbletea v1.3.10
|
github.com/charmbracelet/bubbletea v1.3.10
|
||||||
github.com/charmbracelet/lipgloss v1.1.0
|
github.com/charmbracelet/lipgloss v1.1.0
|
||||||
|
github.com/dlclark/regexp2/v2 v2.5.1
|
||||||
github.com/fsnotify/fsnotify v1.10.1
|
github.com/fsnotify/fsnotify v1.10.1
|
||||||
github.com/godbus/dbus/v5 v5.2.2
|
github.com/godbus/dbus/v5 v5.2.2
|
||||||
github.com/holoplot/go-evdev v0.0.0-20260504100651-66d1748fe847
|
github.com/holoplot/go-evdev v0.0.0-20260504100651-66d1748fe847
|
||||||
|
github.com/klauspost/compress v1.19.2
|
||||||
github.com/pilebones/go-udev v0.9.1
|
github.com/pilebones/go-udev v0.9.1
|
||||||
github.com/sblinch/kdl-go v0.0.0-20260121213736-8b7053306ca6
|
github.com/sblinch/kdl-go v0.0.0-20260121213736-8b7053306ca6
|
||||||
github.com/spf13/cobra v1.10.2
|
github.com/spf13/cobra v1.10.2
|
||||||
@@ -19,11 +22,10 @@ require (
|
|||||||
github.com/yeqown/go-qrcode/v2 v2.2.5
|
github.com/yeqown/go-qrcode/v2 v2.2.5
|
||||||
github.com/yeqown/go-qrcode/writer/standard v1.3.0
|
github.com/yeqown/go-qrcode/writer/standard v1.3.0
|
||||||
github.com/yuin/goldmark v1.8.4
|
github.com/yuin/goldmark v1.8.4
|
||||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
|
||||||
go.etcd.io/bbolt v1.5.0
|
go.etcd.io/bbolt v1.5.0
|
||||||
go4.org/mem v0.0.0-20240501181205-ae6ca9944745
|
go4.org/mem v0.0.0-20240501181205-ae6ca9944745
|
||||||
golang.org/x/image v0.44.0
|
golang.org/x/image v0.44.0
|
||||||
tailscale.com v1.100.0
|
tailscale.com v1.102.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -38,7 +40,6 @@ require (
|
|||||||
github.com/coder/websocket v1.8.15 // indirect
|
github.com/coder/websocket v1.8.15 // indirect
|
||||||
github.com/danielgtaylor/huma/v2 v2.39.1 // indirect
|
github.com/danielgtaylor/huma/v2 v2.39.1 // indirect
|
||||||
github.com/dblohm7/wingoes v0.0.0-20260526185140-fb298caac7ca // indirect
|
github.com/dblohm7/wingoes v0.0.0-20260526185140-fb298caac7ca // indirect
|
||||||
github.com/dlclark/regexp2/v2 v2.5.1 // indirect
|
|
||||||
github.com/ebitengine/purego v0.10.2 // indirect
|
github.com/ebitengine/purego v0.10.2 // indirect
|
||||||
github.com/emirpasic/gods v1.18.1 // indirect
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
github.com/fogleman/gg v1.3.0 // indirect
|
github.com/fogleman/gg v1.3.0 // indirect
|
||||||
@@ -79,11 +80,10 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AvengeMedia/dankgo v0.0.0-20260812195758-5935f34d40ce
|
github.com/AvengeMedia/dankgo v0.0.0-20260902003743-aeb6467c214e
|
||||||
github.com/atotto/clipboard v0.1.4 // indirect
|
github.com/atotto/clipboard v0.1.4 // indirect
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
github.com/charmbracelet/colorprofile v0.4.3 // indirect
|
github.com/charmbracelet/colorprofile v0.4.3 // indirect
|
||||||
github.com/charmbracelet/harmonica v0.2.0 // indirect
|
|
||||||
github.com/charmbracelet/x/ansi v0.11.7 // indirect
|
github.com/charmbracelet/x/ansi v0.11.7 // indirect
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
|
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
|
||||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||||
@@ -94,7 +94,7 @@ require (
|
|||||||
github.com/lucasb-eyer/go-colorful v1.4.1
|
github.com/lucasb-eyer/go-colorful v1.4.1
|
||||||
github.com/mattn/go-isatty v0.0.24
|
github.com/mattn/go-isatty v0.0.24
|
||||||
github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 // indirect
|
github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.27 // indirect
|
github.com/mattn/go-runewidth v0.0.28 // indirect
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||||
github.com/muesli/termenv v0.16.0 // indirect
|
github.com/muesli/termenv v0.16.0 // indirect
|
||||||
|
|||||||
+16
-21
@@ -1,11 +1,13 @@
|
|||||||
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||||
github.com/AvengeMedia/dankgo v0.0.0-20260812195758-5935f34d40ce h1:vNccNlfXze7Fmg9PXrsyxhfAMaifK04bsQHWUjVfe2w=
|
github.com/AvengeMedia/dankgo v0.0.0-20260902003743-aeb6467c214e h1:AUCBM5EF+hxCG55uJl30waRQHhR5+Wij9u8AfoTkQvs=
|
||||||
github.com/AvengeMedia/dankgo v0.0.0-20260812195758-5935f34d40ce/go.mod h1:xt8RldAfti0QCWidwYIzsSSoJWsE61WgEhTu2H9UpD4=
|
github.com/AvengeMedia/dankgo v0.0.0-20260902003743-aeb6467c214e/go.mod h1:w1W/vDrWJCLdjVI4E/mURiKUQLMCmiGiPLxZEeP5kp8=
|
||||||
github.com/AvengeMedia/dgop v0.2.4-0.20260808141551-f559f2f31d2a h1:bGwK4G22ixHUs5NBeNy4JYMKUMMFoZtQz180OnbSFKE=
|
github.com/AvengeMedia/dgop v0.2.4-0.20260819141338-085d828cf577 h1:z7daEy56wLH6R7PGS3MKIVoDHNdVoDptY/f2i61EDCQ=
|
||||||
github.com/AvengeMedia/dgop v0.2.4-0.20260808141551-f559f2f31d2a/go.mod h1:2kt1u9YgHwi2npqB5ago8kwZdFrHlPGXv3PM/vwRHfY=
|
github.com/AvengeMedia/dgop v0.2.4-0.20260819141338-085d828cf577/go.mod h1:2kt1u9YgHwi2npqB5ago8kwZdFrHlPGXv3PM/vwRHfY=
|
||||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
|
github.com/Nadim147c/material/v3 v3.1.1 h1:3ZuTPWBJWUpzzWW1rtqe28RMZXhA4f0/mg/Jk3JG94Y=
|
||||||
|
github.com/Nadim147c/material/v3 v3.1.1/go.mod h1:ThN+dRdC+CDN2qXSGMn32KqtcXwYtb/trDM82cdZI6g=
|
||||||
github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM=
|
github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM=
|
||||||
github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
|
github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
|
||||||
github.com/Wifx/gonetworkmanager/v2 v2.2.0 h1:kPstgsQtY8CmDOOFZd81ytM9Gi3f6ImzPCKF7nNhQ2U=
|
github.com/Wifx/gonetworkmanager/v2 v2.2.0 h1:kPstgsQtY8CmDOOFZd81ytM9Gi3f6ImzPCKF7nNhQ2U=
|
||||||
@@ -14,10 +16,8 @@ github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A=
|
|||||||
github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw=
|
github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw=
|
||||||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||||
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||||
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
|
|
||||||
github.com/alecthomas/chroma/v2 v2.27.0 h1:FodwmyOBgJULFYmDqibcp9pvfDLWdtPRh9v/r5BXYZs=
|
github.com/alecthomas/chroma/v2 v2.27.0 h1:FodwmyOBgJULFYmDqibcp9pvfDLWdtPRh9v/r5BXYZs=
|
||||||
github.com/alecthomas/chroma/v2 v2.27.0/go.mod h1:NjJ3ciIgrqBNeIkWZ4e46nseoLDslxU1LmfCoL+wcY8=
|
github.com/alecthomas/chroma/v2 v2.27.0/go.mod h1:NjJ3ciIgrqBNeIkWZ4e46nseoLDslxU1LmfCoL+wcY8=
|
||||||
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
|
||||||
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
|
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
|
||||||
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||||
@@ -34,8 +34,6 @@ github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlv
|
|||||||
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
||||||
github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
|
github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
|
||||||
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
|
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
|
||||||
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 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||||
github.com/charmbracelet/log v1.0.0 h1:HVVVMmfOorfj3BA9i8X8UL69Hoz9lI0PYwXfJvOdRc4=
|
github.com/charmbracelet/log v1.0.0 h1:HVVVMmfOorfj3BA9i8X8UL69Hoz9lI0PYwXfJvOdRc4=
|
||||||
@@ -67,8 +65,6 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
|
|||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dblohm7/wingoes v0.0.0-20260526185140-fb298caac7ca h1:h1Awca4lQOspNR/2eeo04Ricn5NixDX9mb17WSAgLhQ=
|
github.com/dblohm7/wingoes v0.0.0-20260526185140-fb298caac7ca h1:h1Awca4lQOspNR/2eeo04Ricn5NixDX9mb17WSAgLhQ=
|
||||||
github.com/dblohm7/wingoes v0.0.0-20260526185140-fb298caac7ca/go.mod h1:2TGl1jRJrRpbzykmg7asHm3h08TqutUgQqY5v9k/g3c=
|
github.com/dblohm7/wingoes v0.0.0-20260526185140-fb298caac7ca/go.mod h1:2TGl1jRJrRpbzykmg7asHm3h08TqutUgQqY5v9k/g3c=
|
||||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
|
||||||
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
|
||||||
github.com/dlclark/regexp2/v2 v2.5.1 h1:E5Ug7Dh264W1ymdySmiHNcDG7fmsR307APCE5R07a20=
|
github.com/dlclark/regexp2/v2 v2.5.1 h1:E5Ug7Dh264W1ymdySmiHNcDG7fmsR307APCE5R07a20=
|
||||||
github.com/dlclark/regexp2/v2 v2.5.1/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU=
|
github.com/dlclark/regexp2/v2 v2.5.1/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU=
|
||||||
github.com/ebitengine/purego v0.10.2 h1:W809HbnvzAxgdm+aOvlSekrM16wGCdT/e76+9tS7gzE=
|
github.com/ebitengine/purego v0.10.2 h1:W809HbnvzAxgdm+aOvlSekrM16wGCdT/e76+9tS7gzE=
|
||||||
@@ -130,6 +126,8 @@ github.com/jsimonetti/rtnetlink v1.4.2 h1:Df9w9TZ3npHTyDn0Ev9e1uzmN2odmXd0QX+J5G
|
|||||||
github.com/jsimonetti/rtnetlink v1.4.2/go.mod h1:92s6LJdE+1iOrw+F2/RO7LYI2Qd8pPpFNNUYW06gcoM=
|
github.com/jsimonetti/rtnetlink v1.4.2/go.mod h1:92s6LJdE+1iOrw+F2/RO7LYI2Qd8pPpFNNUYW06gcoM=
|
||||||
github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY=
|
github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY=
|
||||||
github.com/kevinburke/ssh_config v1.6.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
|
github.com/kevinburke/ssh_config v1.6.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
|
||||||
|
github.com/klauspost/compress v1.19.2 h1:hMRETovs/pu/dVWN7zIT1PGG8t509MwT6bO7XSi26R8=
|
||||||
|
github.com/klauspost/compress v1.19.2/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||||
github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
|
github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
|
||||||
github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
|
github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
@@ -147,8 +145,8 @@ github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsRe
|
|||||||
github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
|
github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
|
||||||
github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 h1:P8UmIzZMYDR+NGImiFvErt6VWfIRPuGM+vyjiEdkmIw=
|
github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 h1:P8UmIzZMYDR+NGImiFvErt6VWfIRPuGM+vyjiEdkmIw=
|
||||||
github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||||
github.com/mattn/go-runewidth v0.0.27 h1:Feg/Oou5zI/wnpgDF6omIU0OokC9GxLC/WRknhVlIR0=
|
github.com/mattn/go-runewidth v0.0.28 h1:rPyg2ybwEKPebvpzVWe1gKBkH8EQFkxO4Y0hjBeLaBU=
|
||||||
github.com/mattn/go-runewidth v0.0.27/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8=
|
github.com/mattn/go-runewidth v0.0.28/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8=
|
||||||
github.com/mdlayher/netlink v1.11.2 h1:HKh2jqe+omdSWcQ88nrT7INE61B0NXfiSPFdgL4YbNI=
|
github.com/mdlayher/netlink v1.11.2 h1:HKh2jqe+omdSWcQ88nrT7INE61B0NXfiSPFdgL4YbNI=
|
||||||
github.com/mdlayher/netlink v1.11.2/go.mod h1:uT2Yc/QLaZubzDpZIBi9d4GoeLwtp3x1AMeqSRrK2sA=
|
github.com/mdlayher/netlink v1.11.2/go.mod h1:uT2Yc/QLaZubzDpZIBi9d4GoeLwtp3x1AMeqSRrK2sA=
|
||||||
github.com/mdlayher/socket v0.6.1 h1:M7uj2NtuujUY4mYr1C57NmfNiRHbkKpnBxO856lsc3A=
|
github.com/mdlayher/socket v0.6.1 h1:M7uj2NtuujUY4mYr1C57NmfNiRHbkKpnBxO856lsc3A=
|
||||||
@@ -194,13 +192,12 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||||||
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
|
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/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4=
|
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4=
|
||||||
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg=
|
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg=
|
||||||
github.com/tailscale/wireguard-go v0.0.0-20260527010701-b48af7099cad h1:Ky26FR5yZ5IKEB0xtm5A8xSTb06ImY7kxBFrvgOmJSg=
|
github.com/tailscale/wireguard-go v0.0.0-20260715223240-2e01ba5b00f0 h1:CnIEL2n7Xql6Ux1k+Vu5S5ubDHCT/kxFgkKCY8FjefU=
|
||||||
github.com/tailscale/wireguard-go v0.0.0-20260527010701-b48af7099cad/go.mod h1:6SerzcvHWQchKO2BfNdmquA77CHSECZuFl+D9fp4RnI=
|
github.com/tailscale/wireguard-go v0.0.0-20260715223240-2e01ba5b00f0/go.mod h1:6SerzcvHWQchKO2BfNdmquA77CHSECZuFl+D9fp4RnI=
|
||||||
github.com/tklauser/go-sysconf v0.4.0 h1:7H0uAN+7RkwWRaxhYXDLqa5V3LPrJeV8wmD9dRUgPQU=
|
github.com/tklauser/go-sysconf v0.4.0 h1:7H0uAN+7RkwWRaxhYXDLqa5V3LPrJeV8wmD9dRUgPQU=
|
||||||
github.com/tklauser/go-sysconf v0.4.0/go.mod h1:8mTNWyog7H+MpKijp4VmKJAd2bbYQ2zuUwkYRbUArPI=
|
github.com/tklauser/go-sysconf v0.4.0/go.mod h1:8mTNWyog7H+MpKijp4VmKJAd2bbYQ2zuUwkYRbUArPI=
|
||||||
github.com/tklauser/numcpus v0.12.0 h1:NR85qdvHA9pFse3x3weVZ0r0ST8R6l5RHbZrlRaqob4=
|
github.com/tklauser/numcpus v0.12.0 h1:NR85qdvHA9pFse3x3weVZ0r0ST8R6l5RHbZrlRaqob4=
|
||||||
@@ -215,11 +212,8 @@ github.com/yeqown/go-qrcode/writer/standard v1.3.0 h1:chdyhEfRtUPgQtuPeaWVGQ/TQx
|
|||||||
github.com/yeqown/go-qrcode/writer/standard v1.3.0/go.mod h1:O4MbzsotGCvy8upYPCR91j81dr5XLT7heuljcNXW+oQ=
|
github.com/yeqown/go-qrcode/writer/standard v1.3.0/go.mod h1:O4MbzsotGCvy8upYPCR91j81dr5XLT7heuljcNXW+oQ=
|
||||||
github.com/yeqown/reedsolomon v1.0.0 h1:x1h/Ej/uJnNu8jaX7GLHBWmZKCAWjEJTetkqaabr4B0=
|
github.com/yeqown/reedsolomon v1.0.0 h1:x1h/Ej/uJnNu8jaX7GLHBWmZKCAWjEJTetkqaabr4B0=
|
||||||
github.com/yeqown/reedsolomon v1.0.0/go.mod h1:P76zpcn2TCuL0ul1Fso373qHRc69LKwAw/Iy6g1WiiM=
|
github.com/yeqown/reedsolomon v1.0.0/go.mod h1:P76zpcn2TCuL0ul1Fso373qHRc69LKwAw/Iy6g1WiiM=
|
||||||
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
|
||||||
github.com/yuin/goldmark v1.8.4 h1:oat/nd3U6NeQqFEL3xpEJq7d7c86NI+DbSNGAs4xnjA=
|
github.com/yuin/goldmark v1.8.4 h1:oat/nd3U6NeQqFEL3xpEJq7d7c86NI+DbSNGAs4xnjA=
|
||||||
github.com/yuin/goldmark v1.8.4/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
github.com/yuin/goldmark v1.8.4/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
|
|
||||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
go.etcd.io/bbolt v1.5.0 h1:S7GAl7Fxv12yohbwFfIbQCGDWbQbtDGPET4P/bD4lxU=
|
go.etcd.io/bbolt v1.5.0 h1:S7GAl7Fxv12yohbwFfIbQCGDWbQbtDGPET4P/bD4lxU=
|
||||||
@@ -251,6 +245,8 @@ golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
|
|||||||
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
|
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
|
||||||
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
|
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
|
||||||
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
|
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
|
||||||
|
golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
|
||||||
|
golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
golang.zx2c4.com/wireguard/windows v1.0.1 h1:eOxiDVbywPC+ZQqvdCK7x+ZwWXKbYv50TtH8ysFIbw8=
|
golang.zx2c4.com/wireguard/windows v1.0.1 h1:eOxiDVbywPC+ZQqvdCK7x+ZwWXKbYv50TtH8ysFIbw8=
|
||||||
@@ -260,8 +256,7 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogR
|
|||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
tailscale.com v1.100.0 h1:nm/M/dEaW9RaRsGUjW2HsSDpsZ60Jwd9k4gNW9tTFiE=
|
tailscale.com v1.102.0 h1:yAYrykMZWlgp7Qq2ECDrDnxeySWBhNXNMkXdDmVMyrE=
|
||||||
tailscale.com v1.100.0/go.mod h1:DQ9YBy85DpNlSyeU2XRIWzbAu3RsGp/frv+Khg57meE=
|
tailscale.com v1.102.0/go.mod h1:NdndXfkSklVPbRUZMAzzRuDNaR5B+YWGbAyE1HpSQZg=
|
||||||
|
|||||||
+2
-7
@@ -47,6 +47,7 @@ printf "%bInstalling Dankinstall %s for %s...%b\n" "$GREEN" "$LATEST_VERSION" "$
|
|||||||
|
|
||||||
# Download and install
|
# Download and install
|
||||||
TEMP_DIR=$(mktemp -d)
|
TEMP_DIR=$(mktemp -d)
|
||||||
|
trap 'cd / && rm -rf "$TEMP_DIR"' EXIT
|
||||||
cd "$TEMP_DIR" || exit 1
|
cd "$TEMP_DIR" || exit 1
|
||||||
|
|
||||||
# Download the gzipped binary and its checksum
|
# Download the gzipped binary and its checksum
|
||||||
@@ -67,8 +68,6 @@ if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ]; then
|
|||||||
printf "Expected: %s\n" "$EXPECTED_CHECKSUM"
|
printf "Expected: %s\n" "$EXPECTED_CHECKSUM"
|
||||||
printf "Got: %s\n" "$ACTUAL_CHECKSUM"
|
printf "Got: %s\n" "$ACTUAL_CHECKSUM"
|
||||||
printf "The downloaded file may be corrupted or tampered with\n"
|
printf "The downloaded file may be corrupted or tampered with\n"
|
||||||
cd - >/dev/null
|
|
||||||
rm -rf "$TEMP_DIR"
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -79,8 +78,4 @@ chmod +x installer
|
|||||||
|
|
||||||
# Execute the installer
|
# Execute the installer
|
||||||
printf "%bRunning installer...%b\n" "$GREEN" "$NC"
|
printf "%bRunning installer...%b\n" "$GREEN" "$NC"
|
||||||
./installer
|
./installer "$@"
|
||||||
|
|
||||||
# Cleanup
|
|
||||||
cd - >/dev/null
|
|
||||||
rm -rf "$TEMP_DIR"
|
|
||||||
|
|||||||
@@ -2,35 +2,26 @@ package clipboard
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/wayland/keymap"
|
||||||
wlclient "github.com/AvengeMedia/dankgo/wayland/client"
|
wlclient "github.com/AvengeMedia/dankgo/wayland/client"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
xkbKeymapFormatV1 = 1
|
|
||||||
|
|
||||||
keyStateReleased = 0
|
keyStateReleased = 0
|
||||||
keyStatePressed = 1
|
keyStatePressed = 1
|
||||||
|
|
||||||
// xkb real modifier bit positions are fixed: Shift=0, Lock=1, Control=2
|
// xkb real modifier bit positions are fixed: Shift=0, Lock=1, Control=2
|
||||||
shiftModMask = 1 << 0
|
shiftModMask = 1 << 0
|
||||||
ctrlModMask = 1 << 2
|
ctrlModMask = 1 << 2
|
||||||
|
|
||||||
// evdev fallbacks for a standard pc105 map
|
|
||||||
fallbackCtrlKey = 29 // KEY_LEFTCTRL
|
|
||||||
fallbackShiftKey = 42 // KEY_LEFTSHIFT
|
|
||||||
fallbackVKey = 47 // KEY_V
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// SendPasteKeystroke emulates a paste shortcut via zwp_virtual_keyboard_v1
|
// SendPasteKeystroke emulates a paste shortcut via zwp_virtual_keyboard_v1
|
||||||
// using the seat's own keymap, so keycodes stay valid for XWayland clients
|
// using the seat's own keymap, so keycodes stay valid for XWayland clients
|
||||||
// (a synthetic wtype-style keymap breaks X11 apps like Steam). withShift
|
// (a synthetic wtype-style keymap breaks X11 apps like Steam). withShift
|
||||||
// selects ctrl+shift+v for terminal targets.
|
// selects ctrl+shift+v for terminal targets.
|
||||||
func SendPasteKeystroke(withShift bool) error {
|
func SendPasteKeystroke(withShift bool) (err error) {
|
||||||
s, err := connectSession()
|
s, err := connectSession()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -50,25 +41,25 @@ func SendPasteKeystroke(withShift bool) error {
|
|||||||
}
|
}
|
||||||
defer keyboard.Release()
|
defer keyboard.Release()
|
||||||
|
|
||||||
var keymap *wlclient.KeyboardKeymapEvent
|
var seatKeymap *wlclient.KeyboardKeymapEvent
|
||||||
keyboard.SetKeymapHandler(func(e wlclient.KeyboardKeymapEvent) {
|
keyboard.SetKeymapHandler(func(e wlclient.KeyboardKeymapEvent) {
|
||||||
if keymap == nil {
|
if seatKeymap == nil {
|
||||||
keymap = &e
|
seatKeymap = &e
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
s.display.Roundtrip()
|
s.display.Roundtrip()
|
||||||
|
|
||||||
if keymap == nil || keymap.Format != xkbKeymapFormatV1 {
|
if seatKeymap == nil || seatKeymap.Format != keymap.FormatXkbV1 {
|
||||||
return fmt.Errorf("no xkb keymap from seat")
|
return fmt.Errorf("no xkb keymap from seat")
|
||||||
}
|
}
|
||||||
defer unix.Close(keymap.Fd)
|
defer unix.Close(seatKeymap.Fd)
|
||||||
|
|
||||||
keymapText, err := readKeymap(keymap.Fd, keymap.Size)
|
keymapText, err := keymap.Read(seatKeymap.Fd, seatKeymap.Size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("read keymap: %w", err)
|
return fmt.Errorf("read keymap: %w", err)
|
||||||
}
|
}
|
||||||
keys := resolveKeycodes(keymapText)
|
keys := keymap.Parse(keymapText)
|
||||||
|
|
||||||
vk, err := s.virtualKeyboardMgr.CreateVirtualKeyboard(s.seat)
|
vk, err := s.virtualKeyboardMgr.CreateVirtualKeyboard(s.seat)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -76,131 +67,53 @@ func SendPasteKeystroke(withShift bool) error {
|
|||||||
}
|
}
|
||||||
defer vk.Destroy()
|
defer vk.Destroy()
|
||||||
|
|
||||||
if err := vk.Keymap(xkbKeymapFormatV1, keymap.Fd, keymap.Size); err != nil {
|
if err := vk.Keymap(keymap.FormatXkbV1, seatKeymap.Fd, seatKeymap.Size); err != nil {
|
||||||
return fmt.Errorf("set keymap: %w", err)
|
return fmt.Errorf("set keymap: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
mods := uint32(ctrlModMask)
|
mods := uint32(ctrlModMask)
|
||||||
held := []uint32{keys.ctrl}
|
held := []uint32{keys.Keycode("Control_L")}
|
||||||
if withShift {
|
if withShift {
|
||||||
mods |= shiftModMask
|
mods |= shiftModMask
|
||||||
held = append(held, keys.shift)
|
held = append(held, keys.Keycode("Shift_L"))
|
||||||
}
|
}
|
||||||
|
|
||||||
t := uint32(0)
|
t := uint32(0)
|
||||||
press := func(key, state uint32) error {
|
var pressed []uint32
|
||||||
|
press := func(key uint32) error {
|
||||||
t++
|
t++
|
||||||
return vk.Key(t, key, state)
|
if err := vk.Key(t, key, keyStatePressed); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pressed = append(pressed, key)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// all releases happen here so error paths can't leave keys held or
|
||||||
|
// modifiers latched for the compositor to interpret on destroy
|
||||||
|
defer func() {
|
||||||
|
for i := len(pressed) - 1; i >= 0; i-- {
|
||||||
|
t++
|
||||||
|
if e := vk.Key(t, pressed[i], keyStateReleased); e != nil && err == nil {
|
||||||
|
err = fmt.Errorf("key release: %w", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if e := vk.Modifiers(0, 0, 0, 0); e != nil && err == nil {
|
||||||
|
err = fmt.Errorf("clear modifiers: %w", e)
|
||||||
|
}
|
||||||
|
s.display.Roundtrip()
|
||||||
|
}()
|
||||||
|
|
||||||
for _, key := range held {
|
for _, key := range held {
|
||||||
if err := press(key, keyStatePressed); err != nil {
|
if err := press(key); err != nil {
|
||||||
return fmt.Errorf("key press: %w", err)
|
return fmt.Errorf("key press: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := vk.Modifiers(mods, 0, 0, 0); err != nil {
|
if err := vk.Modifiers(mods, 0, 0, 0); err != nil {
|
||||||
return fmt.Errorf("set modifiers: %w", err)
|
return fmt.Errorf("set modifiers: %w", err)
|
||||||
}
|
}
|
||||||
if err := press(keys.v, keyStatePressed); err != nil {
|
if err := press(keys.Keycode("v")); err != nil {
|
||||||
return fmt.Errorf("key press: %w", err)
|
return fmt.Errorf("key press: %w", err)
|
||||||
}
|
}
|
||||||
if err := press(keys.v, keyStateReleased); err != nil {
|
|
||||||
return fmt.Errorf("key release: %w", err)
|
|
||||||
}
|
|
||||||
for i := len(held) - 1; i >= 0; i-- {
|
|
||||||
if err := press(held[i], keyStateReleased); err != nil {
|
|
||||||
return fmt.Errorf("key release: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := vk.Modifiers(0, 0, 0, 0); err != nil {
|
|
||||||
return fmt.Errorf("clear modifiers: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.display.Roundtrip()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func readKeymap(fd int, size uint32) (string, error) {
|
|
||||||
data, err := unix.Mmap(fd, 0, int(size), unix.PROT_READ, unix.MAP_PRIVATE)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
text := strings.TrimRight(string(data), "\x00")
|
|
||||||
return text, unix.Munmap(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
type pasteKeycodes struct {
|
|
||||||
ctrl uint32
|
|
||||||
shift uint32
|
|
||||||
v uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
keycodeDefRe = regexp.MustCompile(`<([A-Za-z0-9+_-]+)>\s*=\s*(\d+)`)
|
|
||||||
keySymbolsRe = regexp.MustCompile(`key\s*<([A-Za-z0-9+_-]+)>\s*\{([^}]*)\}`)
|
|
||||||
groupIndexRe = regexp.MustCompile(`\w+\[\d+\]\s*=`)
|
|
||||||
symbolListRe = regexp.MustCompile(`\[([^\]]*)\]`)
|
|
||||||
)
|
|
||||||
|
|
||||||
// xkbcommon may serialize keysyms as hex escapes instead of names
|
|
||||||
// (e.g. "0x76" for v, "0xffe3" for Control_L).
|
|
||||||
var keysymNames = map[uint32]string{
|
|
||||||
0x76: "v",
|
|
||||||
0xffe3: "Control_L",
|
|
||||||
0xffe1: "Shift_L",
|
|
||||||
}
|
|
||||||
|
|
||||||
func canonicalKeysym(sym string) string {
|
|
||||||
if !strings.HasPrefix(sym, "0x") && !strings.HasPrefix(sym, "0X") {
|
|
||||||
return sym
|
|
||||||
}
|
|
||||||
value, err := strconv.ParseUint(sym[2:], 16, 32)
|
|
||||||
if err != nil {
|
|
||||||
return sym
|
|
||||||
}
|
|
||||||
if name, ok := keysymNames[uint32(value)]; ok {
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
return sym
|
|
||||||
}
|
|
||||||
|
|
||||||
// resolveKeycodes finds the evdev keycodes producing the keysyms we need in
|
|
||||||
// the seat keymap's first group, falling back to pc105 positions.
|
|
||||||
func resolveKeycodes(keymap string) pasteKeycodes {
|
|
||||||
codes := map[string]uint32{}
|
|
||||||
for _, m := range keycodeDefRe.FindAllStringSubmatch(keymap, -1) {
|
|
||||||
if code, err := strconv.Atoi(m[2]); err == nil {
|
|
||||||
codes[m[1]] = uint32(code)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
keys := pasteKeycodes{ctrl: fallbackCtrlKey, shift: fallbackShiftKey, v: fallbackVKey}
|
|
||||||
want := map[string]*uint32{
|
|
||||||
"Control_L": &keys.ctrl,
|
|
||||||
"Shift_L": &keys.shift,
|
|
||||||
"v": &keys.v,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, m := range keySymbolsRe.FindAllStringSubmatch(keymap, -1) {
|
|
||||||
group := symbolListRe.FindStringSubmatch(groupIndexRe.ReplaceAllString(m[2], ""))
|
|
||||||
if group == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
xkbCode, ok := codes[m[1]]
|
|
||||||
if !ok || xkbCode < 8 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
level1 := canonicalKeysym(strings.TrimSpace(strings.Split(group[1], ",")[0]))
|
|
||||||
target, wanted := want[level1]
|
|
||||||
if !wanted {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
*target = xkbCode - 8
|
|
||||||
delete(want, level1)
|
|
||||||
if len(want) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return keys
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/wayland/keymap"
|
||||||
wlclient "github.com/AvengeMedia/dankgo/wayland/client"
|
wlclient "github.com/AvengeMedia/dankgo/wayland/client"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
@@ -32,20 +33,20 @@ func TestLiveSeatKeymapResolution(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer keyboard.Release()
|
defer keyboard.Release()
|
||||||
|
|
||||||
var keymap *wlclient.KeyboardKeymapEvent
|
var seatKeymap *wlclient.KeyboardKeymapEvent
|
||||||
keyboard.SetKeymapHandler(func(e wlclient.KeyboardKeymapEvent) {
|
keyboard.SetKeymapHandler(func(e wlclient.KeyboardKeymapEvent) {
|
||||||
if keymap == nil {
|
if seatKeymap == nil {
|
||||||
keymap = &e
|
seatKeymap = &e
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
s.display.Roundtrip()
|
s.display.Roundtrip()
|
||||||
|
|
||||||
if keymap == nil {
|
if seatKeymap == nil {
|
||||||
t.Fatal("no keymap event")
|
t.Fatal("no keymap event")
|
||||||
}
|
}
|
||||||
defer unix.Close(keymap.Fd)
|
defer unix.Close(seatKeymap.Fd)
|
||||||
|
|
||||||
text, err := readKeymap(keymap.Fd, keymap.Size)
|
text, err := keymap.Read(seatKeymap.Fd, seatKeymap.Size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("read keymap: %v", err)
|
t.Fatalf("read keymap: %v", err)
|
||||||
}
|
}
|
||||||
@@ -56,10 +57,7 @@ func TestLiveSeatKeymapResolution(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
keys := resolveKeycodes(text)
|
keys := keymap.Parse(text)
|
||||||
t.Logf("keymap size=%d resolved ctrl=%d shift=%d v=%d", keymap.Size, keys.ctrl, keys.shift, keys.v)
|
t.Logf("keymap size=%d resolved ctrl=%d shift=%d v=%d", seatKeymap.Size,
|
||||||
|
keys.Keycode("Control_L"), keys.Keycode("Shift_L"), keys.Keycode("v"))
|
||||||
if keys.ctrl == fallbackCtrlKey && keys.shift == fallbackShiftKey && keys.v == fallbackVKey {
|
|
||||||
t.Log("all keycodes are fallbacks - parsing may not have matched the live keymap")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
package clipboard
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
const qwertyKeymap = `xkb_keymap {
|
|
||||||
xkb_keycodes "(unnamed)" {
|
|
||||||
minimum = 8;
|
|
||||||
maximum = 708;
|
|
||||||
<ESC> = 9;
|
|
||||||
<AB04> = 55;
|
|
||||||
<LCTL> = 37;
|
|
||||||
<LFSH> = 50;
|
|
||||||
alias <AL01> = <AC01>;
|
|
||||||
indicator 1 = "Caps Lock";
|
|
||||||
};
|
|
||||||
xkb_types "(unnamed)" {
|
|
||||||
type "ALPHABETIC" {
|
|
||||||
modifiers = Shift+Lock;
|
|
||||||
map[Shift] = Level2;
|
|
||||||
level_name[Level1] = "Base";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
xkb_symbols "(unnamed)" {
|
|
||||||
key <ESC> { [ Escape ] };
|
|
||||||
key <AB04> { type= "ALPHABETIC", [ v, V ] };
|
|
||||||
key <LCTL> { [ Control_L ] };
|
|
||||||
key <LFSH> { [ Shift_L ] };
|
|
||||||
};
|
|
||||||
};`
|
|
||||||
|
|
||||||
const hexKeymap = `xkb_keymap {
|
|
||||||
xkb_keycodes "(unnamed)" {
|
|
||||||
<AB04> = 56;
|
|
||||||
<LCTL> = 38;
|
|
||||||
<LFSH> = 51;
|
|
||||||
};
|
|
||||||
xkb_symbols "(unnamed)" {
|
|
||||||
key <LCTL> { [ 0xffe3 ] };
|
|
||||||
key <LFSH> {
|
|
||||||
type= "PC_ALT_LEVEL2",
|
|
||||||
symbols[1]= [ 0xffe1, 0xfe08 ]
|
|
||||||
};
|
|
||||||
key <AB04> { [ 0x76, 0x56 ] };
|
|
||||||
};
|
|
||||||
};`
|
|
||||||
|
|
||||||
const dvorakKeymap = `xkb_keymap {
|
|
||||||
xkb_keycodes "(unnamed)" {
|
|
||||||
<AB09> = 60;
|
|
||||||
<LCTL> = 37;
|
|
||||||
<LFSH> = 50;
|
|
||||||
};
|
|
||||||
xkb_symbols "(unnamed)" {
|
|
||||||
key <AB09> { [ v, V ] };
|
|
||||||
key <LCTL> { [ Control_L ] };
|
|
||||||
key <LFSH> { [ Shift_L ] };
|
|
||||||
};
|
|
||||||
};`
|
|
||||||
|
|
||||||
func TestResolveKeycodes(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
keymap string
|
|
||||||
want pasteKeycodes
|
|
||||||
}{
|
|
||||||
{"qwerty", qwertyKeymap, pasteKeycodes{ctrl: 29, shift: 42, v: 47}},
|
|
||||||
{"dvorak", dvorakKeymap, pasteKeycodes{ctrl: 29, shift: 42, v: 52}},
|
|
||||||
{"hex keysyms", hexKeymap, pasteKeycodes{ctrl: 30, shift: 43, v: 48}},
|
|
||||||
{"empty falls back", "", pasteKeycodes{ctrl: fallbackCtrlKey, shift: fallbackShiftKey, v: fallbackVKey}},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got := resolveKeycodes(tt.keymap)
|
|
||||||
if got != tt.want {
|
|
||||||
t.Errorf("resolveKeycodes() = %+v, want %+v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -47,7 +47,15 @@ func Store(data []byte, mimeType string) error {
|
|||||||
return StoreWithConfig(data, mimeType, DefaultStoreConfig())
|
return StoreWithConfig(data, mimeType, DefaultStoreConfig())
|
||||||
}
|
}
|
||||||
|
|
||||||
func StoreWithConfig(data []byte, mimeType string, cfg StoreConfig) error {
|
func StoreWithConfig(data []byte, mimeType string, cfg StoreConfig) (err error) {
|
||||||
|
defer func() {
|
||||||
|
r := recover()
|
||||||
|
if r == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("clipboard db panic: %v", r)
|
||||||
|
}()
|
||||||
|
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,17 +8,21 @@ import (
|
|||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/keyboard_shortcuts_inhibit"
|
"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_layer_shell"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_screencopy"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_screencopy"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wp_color_management"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wp_viewporter"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wp_viewporter"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||||
wlhelpers "github.com/AvengeMedia/DankMaterialShell/core/internal/wayland/client"
|
wlhelpers "github.com/AvengeMedia/DankMaterialShell/core/internal/wayland/client"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/wayland/keymap"
|
||||||
"github.com/AvengeMedia/dankgo/wayland/client"
|
"github.com/AvengeMedia/dankgo/wayland/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Format OutputFormat
|
Format OutputFormat
|
||||||
CustomFormat string
|
CustomFormat string
|
||||||
Lowercase bool
|
Lowercase bool
|
||||||
Autocopy bool
|
Autocopy bool
|
||||||
Notify bool
|
Notify bool
|
||||||
|
AllowMultiple bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Output struct {
|
type Output struct {
|
||||||
@@ -34,18 +38,19 @@ type Output struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type LayerSurface struct {
|
type LayerSurface struct {
|
||||||
output *Output
|
output *Output
|
||||||
state *SurfaceState
|
state *SurfaceState
|
||||||
wlSurface *client.Surface
|
wlSurface *client.Surface
|
||||||
layerSurf *wlr_layer_shell.ZwlrLayerSurfaceV1
|
layerSurf *wlr_layer_shell.ZwlrLayerSurfaceV1
|
||||||
viewport *wp_viewporter.WpViewport
|
viewport *wp_viewporter.WpViewport
|
||||||
wlPools [2]*client.ShmPool
|
wlPools [2]*client.ShmPool
|
||||||
wlBuffers [2]*client.Buffer
|
wlBuffers [2]*client.Buffer
|
||||||
slotBusy [2]bool
|
slotBusy [2]bool
|
||||||
needsRedraw bool
|
needsRedraw bool
|
||||||
scopyBuffer *client.Buffer
|
framePending bool
|
||||||
configured bool
|
scopyBuffer *client.Buffer
|
||||||
hidden bool
|
configured bool
|
||||||
|
hidden bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Picker struct {
|
type Picker struct {
|
||||||
@@ -60,9 +65,13 @@ type Picker struct {
|
|||||||
seat *client.Seat
|
seat *client.Seat
|
||||||
pointer *client.Pointer
|
pointer *client.Pointer
|
||||||
keyboard *client.Keyboard
|
keyboard *client.Keyboard
|
||||||
|
keymap *keymap.Keymap
|
||||||
layerShell *wlr_layer_shell.ZwlrLayerShellV1
|
layerShell *wlr_layer_shell.ZwlrLayerShellV1
|
||||||
screencopy *wlr_screencopy.ZwlrScreencopyManagerV1
|
screencopy *wlr_screencopy.ZwlrScreencopyManagerV1
|
||||||
viewporter *wp_viewporter.WpViewporter
|
viewporter *wp_viewporter.WpViewporter
|
||||||
|
colorMgr *wp_color_management.WpColorManagerV1
|
||||||
|
|
||||||
|
compositorVersion uint32
|
||||||
|
|
||||||
shortcutsInhibitMgr *keyboard_shortcuts_inhibit.ZwpKeyboardShortcutsInhibitManagerV1
|
shortcutsInhibitMgr *keyboard_shortcuts_inhibit.ZwpKeyboardShortcutsInhibitManagerV1
|
||||||
shortcutsInhibitor *keyboard_shortcuts_inhibit.ZwpKeyboardShortcutsInhibitorV1
|
shortcutsInhibitor *keyboard_shortcuts_inhibit.ZwpKeyboardShortcutsInhibitorV1
|
||||||
@@ -86,6 +95,14 @@ func New(config Config) *Picker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Picker) Run() (*Color, error) {
|
func (p *Picker) Run() (*Color, error) {
|
||||||
|
if !p.config.AllowMultiple {
|
||||||
|
lock, err := utils.LockSelectionOverlay()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer lock.Release()
|
||||||
|
}
|
||||||
|
|
||||||
if err := p.connect(); err != nil {
|
if err := p.connect(); err != nil {
|
||||||
return nil, fmt.Errorf("wayland connect: %w", err)
|
return nil, fmt.Errorf("wayland connect: %w", err)
|
||||||
}
|
}
|
||||||
@@ -164,9 +181,10 @@ func (p *Picker) checkDone() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// flushRedraws paints queued surfaces, at most once per compositor frame each.
|
||||||
func (p *Picker) flushRedraws() {
|
func (p *Picker) flushRedraws() {
|
||||||
for _, ls := range p.surfaces {
|
for _, ls := range p.surfaces {
|
||||||
if !ls.needsRedraw {
|
if !ls.needsRedraw || ls.framePending {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
p.redrawSurface(ls)
|
p.redrawSurface(ls)
|
||||||
@@ -213,6 +231,7 @@ func (p *Picker) handleGlobal(e client.RegistryGlobalEvent) {
|
|||||||
compositor := client.NewCompositor(p.ctx)
|
compositor := client.NewCompositor(p.ctx)
|
||||||
if err := p.registry.Bind(e.Name, e.Interface, e.Version, compositor); err == nil {
|
if err := p.registry.Bind(e.Name, e.Interface, e.Version, compositor); err == nil {
|
||||||
p.compositor = compositor
|
p.compositor = compositor
|
||||||
|
p.compositorVersion = e.Version
|
||||||
}
|
}
|
||||||
|
|
||||||
case client.ShmInterfaceName:
|
case client.ShmInterfaceName:
|
||||||
@@ -263,6 +282,12 @@ func (p *Picker) handleGlobal(e client.RegistryGlobalEvent) {
|
|||||||
p.viewporter = viewporter
|
p.viewporter = viewporter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case wp_color_management.WpColorManagerV1InterfaceName:
|
||||||
|
mgr := wp_color_management.NewWpColorManagerV1(p.ctx)
|
||||||
|
if err := p.registry.Bind(e.Name, e.Interface, 1, mgr); err == nil {
|
||||||
|
p.colorMgr = mgr
|
||||||
|
}
|
||||||
|
|
||||||
case keyboard_shortcuts_inhibit.ZwpKeyboardShortcutsInhibitManagerV1InterfaceName:
|
case keyboard_shortcuts_inhibit.ZwpKeyboardShortcutsInhibitManagerV1InterfaceName:
|
||||||
mgr := keyboard_shortcuts_inhibit.NewZwpKeyboardShortcutsInhibitManagerV1(p.ctx)
|
mgr := keyboard_shortcuts_inhibit.NewZwpKeyboardShortcutsInhibitManagerV1(p.ctx)
|
||||||
if err := p.registry.Bind(e.Name, e.Interface, e.Version, mgr); err == nil {
|
if err := p.registry.Bind(e.Name, e.Interface, e.Version, mgr); err == nil {
|
||||||
@@ -321,16 +346,38 @@ func (p *Picker) createSurfaces() error {
|
|||||||
p.outputsMu.Unlock()
|
p.outputsMu.Unlock()
|
||||||
|
|
||||||
for _, output := range outputs {
|
for _, output := range outputs {
|
||||||
|
refLum, pq := p.outputPQEncoding(output)
|
||||||
ls, err := p.createLayerSurface(output)
|
ls, err := p.createLayerSurface(output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("output %s: %w", output.name, err)
|
return fmt.Errorf("output %s: %w", output.name, err)
|
||||||
}
|
}
|
||||||
|
if pq {
|
||||||
|
ls.state.SetPQEncoding(refLum)
|
||||||
|
}
|
||||||
p.surfaces = append(p.surfaces, ls)
|
p.surfaces = append(p.surfaces, ls)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Picker) outputPQEncoding(output *Output) (float64, bool) {
|
||||||
|
if p.colorMgr == nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
desc, ok := wp_color_management.QueryOutputDescription(p.colorMgr, output.wlOutput, p.roundtrip)
|
||||||
|
if !ok {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
pq := desc.TF == uint32(wp_color_management.WpColorManagerV1TransferFunctionSt2084Pq) &&
|
||||||
|
desc.Primaries == uint32(wp_color_management.WpColorManagerV1PrimariesBt2020)
|
||||||
|
if !pq {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return float64(desc.ReferenceLum), true
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Picker) createLayerSurface(output *Output) (*LayerSurface, error) {
|
func (p *Picker) createLayerSurface(output *Output) (*LayerSurface, error) {
|
||||||
surface, err := p.compositor.CreateSurface()
|
surface, err := p.compositor.CreateSurface()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -517,17 +564,18 @@ func (p *Picker) captureForSurface(ls *LayerSurface) {
|
|||||||
|
|
||||||
func (p *Picker) redrawSurface(ls *LayerSurface) {
|
func (p *Picker) redrawSurface(ls *LayerSurface) {
|
||||||
slot := ls.state.FrontIndex()
|
slot := ls.state.FrontIndex()
|
||||||
if ls.slotBusy[slot] {
|
if ls.slotBusy[slot] || ls.framePending {
|
||||||
ls.needsRedraw = true
|
ls.needsRedraw = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var renderBuf *ShmBuffer
|
var renderBuf *ShmBuffer
|
||||||
|
var damage []rect
|
||||||
switch {
|
switch {
|
||||||
case ls.hidden:
|
case ls.hidden:
|
||||||
renderBuf = ls.state.RedrawScreenOnly()
|
renderBuf, damage = ls.state.RedrawScreenOnly()
|
||||||
default:
|
default:
|
||||||
renderBuf = ls.state.Redraw()
|
renderBuf, damage = ls.state.Redraw()
|
||||||
}
|
}
|
||||||
if renderBuf == nil {
|
if renderBuf == nil {
|
||||||
return
|
return
|
||||||
@@ -574,7 +622,25 @@ func (p *Picker) redrawSurface(ls *LayerSurface) {
|
|||||||
_ = ls.wlSurface.SetBufferScale(bufferScale)
|
_ = ls.wlSurface.SetBufferScale(bufferScale)
|
||||||
}
|
}
|
||||||
_ = ls.wlSurface.Attach(ls.wlBuffers[slot], 0, 0)
|
_ = ls.wlSurface.Attach(ls.wlBuffers[slot], 0, 0)
|
||||||
_ = ls.wlSurface.Damage(0, 0, int32(logicalW), int32(logicalH))
|
switch {
|
||||||
|
case p.compositorVersion < 4:
|
||||||
|
_ = ls.wlSurface.Damage(0, 0, int32(logicalW), int32(logicalH))
|
||||||
|
default:
|
||||||
|
for _, r := range damage {
|
||||||
|
r = r.clip(renderBuf.Width, renderBuf.Height)
|
||||||
|
if r.w <= 0 || r.h <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_ = ls.wlSurface.DamageBuffer(int32(r.x), int32(r.y), int32(r.w), int32(r.h))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cb, err := ls.wlSurface.Frame(); err == nil {
|
||||||
|
ls.framePending = true
|
||||||
|
cb.SetDoneHandler(func(client.CallbackDoneEvent) {
|
||||||
|
_ = cb.Destroy()
|
||||||
|
ls.framePending = false
|
||||||
|
})
|
||||||
|
}
|
||||||
_ = ls.wlSurface.Commit()
|
_ = ls.wlSurface.Commit()
|
||||||
|
|
||||||
ls.state.SwapBuffers()
|
ls.state.SwapBuffers()
|
||||||
@@ -674,9 +740,14 @@ func (p *Picker) setupPointerHandlers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Picker) setupKeyboardHandlers() {
|
func (p *Picker) setupKeyboardHandlers() {
|
||||||
|
p.keyboard.SetKeymapHandler(func(e client.KeyboardKeymapEvent) {
|
||||||
|
p.keymap = keymap.FromEvent(e)
|
||||||
|
})
|
||||||
|
|
||||||
p.keyboard.SetKeyHandler(func(e client.KeyboardKeyEvent) {
|
p.keyboard.SetKeyHandler(func(e client.KeyboardKeyEvent) {
|
||||||
|
sym := p.keymap.Keysym(e.Key)
|
||||||
for _, ls := range p.surfaces {
|
for _, ls := range p.surfaces {
|
||||||
ls.state.OnKey(e.Key, e.State)
|
ls.state.OnKey(sym, e.State)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -726,6 +797,10 @@ func (p *Picker) cleanup() {
|
|||||||
p.viewporter.Destroy()
|
p.viewporter.Destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.colorMgr != nil {
|
||||||
|
p.colorMgr.Destroy()
|
||||||
|
}
|
||||||
|
|
||||||
if p.screencopy != nil {
|
if p.screencopy != nil {
|
||||||
p.screencopy.Destroy()
|
p.screencopy.Destroy()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ type SurfaceState struct {
|
|||||||
|
|
||||||
screenBuf *ShmBuffer
|
screenBuf *ShmBuffer
|
||||||
screenFormat PixelFormat
|
screenFormat PixelFormat
|
||||||
|
pqEncoded bool
|
||||||
|
refLum float64
|
||||||
yInverted bool
|
yInverted bool
|
||||||
|
|
||||||
logicalW int
|
logicalW int
|
||||||
@@ -32,6 +34,10 @@ type SurfaceState struct {
|
|||||||
|
|
||||||
renderBufs [2]*ShmBuffer
|
renderBufs [2]*ShmBuffer
|
||||||
front int
|
front int
|
||||||
|
primed [2]bool
|
||||||
|
drawn [2][]rect
|
||||||
|
shown []rect
|
||||||
|
fullDamage bool
|
||||||
|
|
||||||
scale int32
|
scale int32
|
||||||
scaleX float64
|
scaleX float64
|
||||||
@@ -56,6 +62,13 @@ func NewSurfaceState(format OutputFormat, lowercase bool) *SurfaceState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SurfaceState) SetPQEncoding(refLum float64) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
s.pqEncoded = true
|
||||||
|
s.refLum = refLum
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SurfaceState) SetScale(scale int32) {
|
func (s *SurfaceState) SetScale(scale int32) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
@@ -152,6 +165,17 @@ func (s *SurfaceState) OnScreencopyReady() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Magnifier rendering and pixel picking work on 8-bit pixels only
|
||||||
|
if s.screenFormat.Is10Bit() {
|
||||||
|
switch {
|
||||||
|
case s.pqEncoded:
|
||||||
|
s.screenBuf.ConvertPQ2020ToSRGB(s.refLum)
|
||||||
|
default:
|
||||||
|
s.screenBuf.Convert10To8()
|
||||||
|
}
|
||||||
|
s.screenFormat = s.screenBuf.Format
|
||||||
|
}
|
||||||
|
|
||||||
s.recomputeScale()
|
s.recomputeScale()
|
||||||
s.ensureRenderBuffers()
|
s.ensureRenderBuffers()
|
||||||
s.readyForDisplay = true
|
s.readyForDisplay = true
|
||||||
@@ -192,6 +216,10 @@ func (s *SurfaceState) ensureRenderBuffers() {
|
|||||||
if s.screenBuf == nil {
|
if s.screenBuf == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
s.primed = [2]bool{}
|
||||||
|
s.drawn = [2][]rect{}
|
||||||
|
s.shown = nil
|
||||||
|
s.fullDamage = true
|
||||||
|
|
||||||
width := s.screenBuf.Width
|
width := s.screenBuf.Width
|
||||||
height := s.screenBuf.Height
|
height := s.screenBuf.Height
|
||||||
@@ -238,7 +266,7 @@ func (s *SurfaceState) OnPointerButton(button, state uint32) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SurfaceState) OnKey(key, state uint32) {
|
func (s *SurfaceState) OnKey(sym string, state uint32) {
|
||||||
if state != 1 {
|
if state != 1 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -246,10 +274,10 @@ func (s *SurfaceState) OnKey(key, state uint32) {
|
|||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
switch key {
|
switch sym {
|
||||||
case 1: // KEY_ESC
|
case "Escape":
|
||||||
s.cancelled = true
|
s.cancelled = true
|
||||||
case 28: // KEY_ENTER
|
case "Return", "KP_Enter":
|
||||||
if s.readyForDisplay && s.screenBuf != nil {
|
if s.readyForDisplay && s.screenBuf != nil {
|
||||||
s.colorPicked = true
|
s.colorPicked = true
|
||||||
}
|
}
|
||||||
@@ -286,21 +314,70 @@ func (s *SurfaceState) SwapBuffers() {
|
|||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SurfaceState) Redraw() *ShmBuffer {
|
// rect is a buffer-pixel area, clipped on use.
|
||||||
|
type rect struct{ x, y, w, h int }
|
||||||
|
|
||||||
|
func (r rect) clip(w, h int) rect {
|
||||||
|
x1, y1 := max(r.x, 0), max(r.y, 0)
|
||||||
|
x2, y2 := min(r.x+r.w, w), min(r.y+r.h, h)
|
||||||
|
return rect{x1, y1, max(x2-x1, 0), max(y2-y1, 0)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func magnifierRect(cx, cy int) rect {
|
||||||
|
const reach = magnifierRadius + 2
|
||||||
|
return rect{cx - reach, cy - reach, 2*reach + 1, 2*reach + 1}
|
||||||
|
}
|
||||||
|
|
||||||
|
// restore puts screen pixels back under the front buffer's previous overlay and
|
||||||
|
// records cur as its new footprint. It returns what differs from the frame on screen.
|
||||||
|
func (s *SurfaceState) restore(dst *ShmBuffer, cur []rect) []rect {
|
||||||
|
i := s.front
|
||||||
|
switch {
|
||||||
|
case !s.primed[i]:
|
||||||
|
dst.CopyFrom(s.screenBuf)
|
||||||
|
s.primed[i] = true
|
||||||
|
default:
|
||||||
|
for _, r := range s.drawn[i] {
|
||||||
|
s.copyRect(dst, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.drawn[i] = cur
|
||||||
|
|
||||||
|
damage := append(append([]rect(nil), s.shown...), cur...)
|
||||||
|
if s.fullDamage {
|
||||||
|
s.fullDamage = false
|
||||||
|
damage = []rect{{0, 0, dst.Width, dst.Height}}
|
||||||
|
}
|
||||||
|
s.shown = cur
|
||||||
|
return damage
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SurfaceState) copyRect(dst *ShmBuffer, r rect) {
|
||||||
|
r = r.clip(min(dst.Width, s.screenBuf.Width), min(dst.Height, s.screenBuf.Height))
|
||||||
|
if r.w <= 0 || r.h <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
src := s.screenBuf
|
||||||
|
for y := r.y; y < r.y+r.h; y++ {
|
||||||
|
copy(dst.Data()[y*dst.Stride+r.x*4:][:r.w*4], src.Data()[y*src.Stride+r.x*4:][:r.w*4])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redraw draws the magnifier and preview at the pointer into the front buffer,
|
||||||
|
// touching only the previous and new overlay areas; the rects are the damage.
|
||||||
|
func (s *SurfaceState) Redraw() (*ShmBuffer, []rect) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
if !s.readyForDisplay || s.screenBuf == nil {
|
if !s.readyForDisplay || s.screenBuf == nil {
|
||||||
return nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
dst := s.renderBufs[s.front]
|
dst := s.renderBufs[s.front]
|
||||||
if dst == nil {
|
if dst == nil {
|
||||||
return nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
dst.CopyFrom(s.screenBuf)
|
|
||||||
|
|
||||||
px := int(math.Round(float64(s.pointerX) * s.scaleX))
|
px := int(math.Round(float64(s.pointerX) * s.scaleX))
|
||||||
py := int(math.Round(float64(s.pointerY) * s.scaleY))
|
py := int(math.Round(float64(s.pointerY) * s.scaleY))
|
||||||
|
|
||||||
@@ -313,6 +390,8 @@ func (s *SurfaceState) Redraw() *ShmBuffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
picked := GetPixelColorWithFormat(s.screenBuf, px, sampleY, s.screenFormat)
|
picked := GetPixelColorWithFormat(s.screenBuf, px, sampleY, s.screenFormat)
|
||||||
|
text := formatColorForPreview(picked, s.displayFormat, s.lowercase)
|
||||||
|
damage := s.restore(dst, []rect{magnifierRect(px, py), previewRect(dst.Width, dst.Height, px, py, text)})
|
||||||
|
|
||||||
drawMagnifierWithInversion(
|
drawMagnifierWithInversion(
|
||||||
dst.Data(), dst.Stride, dst.Width, dst.Height,
|
dst.Data(), dst.Stride, dst.Width, dst.Height,
|
||||||
@@ -322,26 +401,25 @@ func (s *SurfaceState) Redraw() *ShmBuffer {
|
|||||||
|
|
||||||
drawColorPreview(dst.Data(), dst.Stride, dst.Width, dst.Height, px, py, picked, s.displayFormat, s.lowercase, s.screenFormat)
|
drawColorPreview(dst.Data(), dst.Stride, dst.Width, dst.Height, px, py, picked, s.displayFormat, s.lowercase, s.screenFormat)
|
||||||
|
|
||||||
return dst
|
return dst, damage
|
||||||
}
|
}
|
||||||
|
|
||||||
// RedrawScreenOnly renders just the screenshot without any overlay (magnifier, preview).
|
// RedrawScreenOnly renders just the screenshot without any overlay (magnifier, preview).
|
||||||
// Used for when pointer leaves the surface.
|
// Used for when pointer leaves the surface.
|
||||||
func (s *SurfaceState) RedrawScreenOnly() *ShmBuffer {
|
func (s *SurfaceState) RedrawScreenOnly() (*ShmBuffer, []rect) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
if !s.readyForDisplay || s.screenBuf == nil {
|
if !s.readyForDisplay || s.screenBuf == nil {
|
||||||
return nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
dst := s.renderBufs[s.front]
|
dst := s.renderBufs[s.front]
|
||||||
if dst == nil {
|
if dst == nil {
|
||||||
return nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
dst.CopyFrom(s.screenBuf)
|
return dst, s.restore(dst, nil)
|
||||||
return dst
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SurfaceState) PickColor() (Color, bool) {
|
func (s *SurfaceState) PickColor() (Color, bool) {
|
||||||
@@ -422,6 +500,8 @@ func blendColors(bg, fg Color, alpha float64) Color {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const magnifierRadius = 80
|
||||||
|
|
||||||
func drawMagnifierWithInversion(
|
func drawMagnifierWithInversion(
|
||||||
dst []byte, dstStride, dstW, dstH int,
|
dst []byte, dstStride, dstW, dstH int,
|
||||||
src []byte, srcStride, srcW, srcH int,
|
src []byte, srcStride, srcW, srcH int,
|
||||||
@@ -435,7 +515,7 @@ func drawMagnifierWithInversion(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
outerRadius = 80
|
outerRadius = magnifierRadius
|
||||||
borderThickness = 4
|
borderThickness = 4
|
||||||
aaWidth = 1.5
|
aaWidth = 1.5
|
||||||
zoom = 8.0
|
zoom = 8.0
|
||||||
@@ -530,7 +610,7 @@ func drawMagnifierWithInversion(
|
|||||||
}
|
}
|
||||||
srcOff := sy*srcStride + sx*4
|
srcOff := sy*srcStride + sx*4
|
||||||
if srcOff+4 <= len(src) {
|
if srcOff+4 <= len(src) {
|
||||||
finalColor = Color{R: src[srcOff+rOff], G: src[srcOff+1], B: src[srcOff+bOff], A: 255}
|
finalColor = Color{R: src[srcOff+rOff], G: src[srcOff+1], B: src[srcOff+bOff]}
|
||||||
} else {
|
} else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -543,12 +623,11 @@ func drawMagnifierWithInversion(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
drawMagnifierCrosshair(dst, dstStride, dstW, dstH, cx, cy, int(innerRadius), crossThickness, crossInnerRadius, format)
|
drawMagnifierCrosshair(dst, dstStride, dstW, dstH, cx, cy, int(innerRadius), crossThickness, crossInnerRadius)
|
||||||
}
|
}
|
||||||
|
|
||||||
func drawMagnifierCrosshair(
|
func drawMagnifierCrosshair(
|
||||||
data []byte, stride, width, height, cx, cy, radius, thickness, innerRadius int,
|
data []byte, stride, width, height, cx, cy, radius, thickness, innerRadius int,
|
||||||
format PixelFormat,
|
|
||||||
) {
|
) {
|
||||||
if width <= 0 || height <= 0 {
|
if width <= 0 || height <= 0 {
|
||||||
return
|
return
|
||||||
@@ -1046,30 +1125,28 @@ var fontGlyphs = map[rune][fontH]uint8{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func drawColorPreview(data []byte, stride, width, height int, cx, cy int, c Color, format OutputFormat, lowercase bool, pixelFormat PixelFormat) {
|
const (
|
||||||
text := formatColorForPreview(c, format, lowercase)
|
previewPaddingX = 8
|
||||||
|
previewPaddingY = 4
|
||||||
|
previewSpace = 2
|
||||||
|
previewOffset = 88
|
||||||
|
)
|
||||||
|
|
||||||
|
// previewRect places the color label beside the magnifier, flipping to the left
|
||||||
|
// and clamping so it stays on the buffer.
|
||||||
|
func previewRect(width, height, cx, cy int, text string) rect {
|
||||||
if len(text) == 0 {
|
if len(text) == 0 {
|
||||||
return
|
return rect{}
|
||||||
}
|
}
|
||||||
|
textW := len(text)*(fontW+previewSpace) - previewSpace
|
||||||
|
boxW := textW + previewPaddingX*2
|
||||||
|
boxH := fontH + previewPaddingY*2
|
||||||
|
|
||||||
const (
|
x := cx + previewOffset
|
||||||
paddingX = 8
|
|
||||||
paddingY = 4
|
|
||||||
space = 2
|
|
||||||
offset = 88
|
|
||||||
)
|
|
||||||
|
|
||||||
textW := len(text)*(fontW+space) - space
|
|
||||||
textH := fontH
|
|
||||||
|
|
||||||
boxW := textW + paddingX*2
|
|
||||||
boxH := textH + paddingY*2
|
|
||||||
|
|
||||||
x := cx + offset
|
|
||||||
y := cy - boxH/2
|
y := cy - boxH/2
|
||||||
|
|
||||||
if x+boxW >= width {
|
if x+boxW >= width {
|
||||||
x = cx - boxW - offset
|
x = cx - boxW - previewOffset
|
||||||
}
|
}
|
||||||
if x < 0 {
|
if x < 0 {
|
||||||
x = 0
|
x = 0
|
||||||
@@ -1080,8 +1157,18 @@ func drawColorPreview(data []byte, stride, width, height int, cx, cy int, c Colo
|
|||||||
if y+boxH >= height {
|
if y+boxH >= height {
|
||||||
y = height - boxH
|
y = height - boxH
|
||||||
}
|
}
|
||||||
|
return rect{x, y, boxW, boxH}
|
||||||
|
}
|
||||||
|
|
||||||
drawFilledRect(data, stride, width, height, x, y, boxW, boxH, c, pixelFormat)
|
func drawColorPreview(data []byte, stride, width, height int, cx, cy int, c Color, format OutputFormat, lowercase bool, pixelFormat PixelFormat) {
|
||||||
|
text := formatColorForPreview(c, format, lowercase)
|
||||||
|
if len(text) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
box := previewRect(width, height, cx, cy, text)
|
||||||
|
x, y := box.x, box.y
|
||||||
|
drawFilledRect(data, stride, width, height, x, y, box.w, box.h, c, pixelFormat)
|
||||||
|
|
||||||
lum := 0.299*float64(c.R) + 0.587*float64(c.G) + 0.114*float64(c.B)
|
lum := 0.299*float64(c.R) + 0.587*float64(c.G) + 0.114*float64(c.B)
|
||||||
var fg Color
|
var fg Color
|
||||||
@@ -1090,7 +1177,7 @@ func drawColorPreview(data []byte, stride, width, height int, cx, cy int, c Colo
|
|||||||
} else {
|
} else {
|
||||||
fg = Color{R: 255, G: 255, B: 255, A: 255}
|
fg = Color{R: 255, G: 255, B: 255, A: 255}
|
||||||
}
|
}
|
||||||
drawText(data, stride, width, height, x+paddingX, y+paddingY, text, fg, pixelFormat)
|
drawText(data, stride, width, height, x+previewPaddingX, y+previewPaddingY, text, fg, pixelFormat)
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatColorForPreview(c Color, format OutputFormat, lowercase bool) string {
|
func formatColorForPreview(c Color, format OutputFormat, lowercase bool) string {
|
||||||
|
|||||||
@@ -45,14 +45,12 @@ func TestSurfaceState_ConcurrentScaleAccess(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for range goroutines / 2 {
|
for range goroutines / 2 {
|
||||||
wg.Add(1)
|
wg.Go(func() {
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for range iterations {
|
for range iterations {
|
||||||
scale := s.Scale()
|
scale := s.Scale()
|
||||||
assert.GreaterOrEqual(t, scale, int32(1))
|
assert.GreaterOrEqual(t, scale, int32(1))
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
@@ -76,15 +74,13 @@ func TestSurfaceState_ConcurrentLogicalSize(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for range goroutines / 2 {
|
for range goroutines / 2 {
|
||||||
wg.Add(1)
|
wg.Go(func() {
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for range iterations {
|
for range iterations {
|
||||||
w, h := s.LogicalSize()
|
w, h := s.LogicalSize()
|
||||||
_ = w
|
_ = w
|
||||||
_ = h
|
_ = h
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
@@ -98,35 +94,29 @@ func TestSurfaceState_ConcurrentIsDone(t *testing.T) {
|
|||||||
const iterations = 100
|
const iterations = 100
|
||||||
|
|
||||||
for range goroutines / 3 {
|
for range goroutines / 3 {
|
||||||
wg.Add(1)
|
wg.Go(func() {
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for range iterations {
|
for range iterations {
|
||||||
s.OnPointerButton(0x110, 1)
|
s.OnPointerButton(0x110, 1)
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
for range goroutines / 3 {
|
for range goroutines / 3 {
|
||||||
wg.Add(1)
|
wg.Go(func() {
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for range iterations {
|
for range iterations {
|
||||||
s.OnKey(1, 1)
|
s.OnKey("Escape", 1)
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
for range goroutines / 3 {
|
for range goroutines / 3 {
|
||||||
wg.Add(1)
|
wg.Go(func() {
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for range iterations {
|
for range iterations {
|
||||||
picked, cancelled := s.IsDone()
|
picked, cancelled := s.IsDone()
|
||||||
_ = picked
|
_ = picked
|
||||||
_ = cancelled
|
_ = cancelled
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
@@ -140,13 +130,11 @@ func TestSurfaceState_ConcurrentIsReady(t *testing.T) {
|
|||||||
const iterations = 100
|
const iterations = 100
|
||||||
|
|
||||||
for range goroutines {
|
for range goroutines {
|
||||||
wg.Add(1)
|
wg.Go(func() {
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for range iterations {
|
for range iterations {
|
||||||
_ = s.IsReady()
|
_ = s.IsReady()
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
@@ -160,13 +148,11 @@ func TestSurfaceState_ConcurrentSwapBuffers(t *testing.T) {
|
|||||||
const iterations = 100
|
const iterations = 100
|
||||||
|
|
||||||
for range goroutines {
|
for range goroutines {
|
||||||
wg.Add(1)
|
wg.Go(func() {
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for range iterations {
|
for range iterations {
|
||||||
s.SwapBuffers()
|
s.SwapBuffers()
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
@@ -210,13 +196,13 @@ func TestSurfaceState_PickColorNilBuffer(t *testing.T) {
|
|||||||
|
|
||||||
func TestSurfaceState_RedrawNilBuffer(t *testing.T) {
|
func TestSurfaceState_RedrawNilBuffer(t *testing.T) {
|
||||||
s := NewSurfaceState(FormatHex, false)
|
s := NewSurfaceState(FormatHex, false)
|
||||||
buf := s.Redraw()
|
buf, _ := s.Redraw()
|
||||||
assert.Nil(t, buf)
|
assert.Nil(t, buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSurfaceState_RedrawScreenOnlyNilBuffer(t *testing.T) {
|
func TestSurfaceState_RedrawScreenOnlyNilBuffer(t *testing.T) {
|
||||||
s := NewSurfaceState(FormatHex, false)
|
s := NewSurfaceState(FormatHex, false)
|
||||||
buf := s.RedrawScreenOnly()
|
buf, _ := s.RedrawScreenOnly()
|
||||||
assert.Nil(t, buf)
|
assert.Nil(t, buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,3 +298,99 @@ func TestBlendColors(t *testing.T) {
|
|||||||
result = blendColors(bg, fg, 2.0)
|
result = blendColors(bg, fg, 2.0)
|
||||||
assert.Equal(t, fg.R, result.R)
|
assert.Equal(t, fg.R, result.R)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newReadyState(t *testing.T, w, h int) *SurfaceState {
|
||||||
|
t.Helper()
|
||||||
|
s := NewSurfaceState(FormatHex, false)
|
||||||
|
if err := s.OnScreencopyBuffer(FormatXRGB8888, w, h, w*4+16); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Cleanup(s.Destroy)
|
||||||
|
data := s.ScreenBuffer().Data()
|
||||||
|
for i := range data {
|
||||||
|
data[i] = byte(i*31 + i/7)
|
||||||
|
}
|
||||||
|
if err := s.OnLayerConfigure(w, h); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
s.OnScreencopyReady()
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSurfaceState_RedrawIncremental(t *testing.T) {
|
||||||
|
const w, h = 400, 300
|
||||||
|
s := newReadyState(t, w, h)
|
||||||
|
screen := s.ScreenBuffer()
|
||||||
|
|
||||||
|
reference := func(px, py int, overlay bool) []byte {
|
||||||
|
ref, err := CreateShmBuffer(w, h, screen.Stride)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer ref.Close()
|
||||||
|
ref.CopyFrom(screen)
|
||||||
|
if overlay {
|
||||||
|
c := GetPixelColorWithFormat(screen, px, py, s.screenFormat)
|
||||||
|
drawMagnifierWithInversion(ref.Data(), ref.Stride, w, h, screen.Data(), screen.Stride, w, h, px, py, c, false, s.screenFormat)
|
||||||
|
drawColorPreview(ref.Data(), ref.Stride, w, h, px, py, c, FormatHex, false, s.screenFormat)
|
||||||
|
}
|
||||||
|
return append([]byte(nil), ref.Data()...)
|
||||||
|
}
|
||||||
|
|
||||||
|
inDamage := func(x, y int, damage []rect) bool {
|
||||||
|
for _, r := range damage {
|
||||||
|
if x >= r.x && x < r.x+r.w && y >= r.y && y < r.y+r.h {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var onScreen []byte
|
||||||
|
positions := [][2]int{{10, 10}, {200, 150}, {205, 152}, {399, 299}, {0, 299}, {-1, -1}, {120, 40}, {125, 40}, {300, 290}}
|
||||||
|
for i, pos := range positions {
|
||||||
|
hidden := pos[0] < 0
|
||||||
|
var buf *ShmBuffer
|
||||||
|
var damage []rect
|
||||||
|
px, py := pos[0], pos[1]
|
||||||
|
switch {
|
||||||
|
case hidden:
|
||||||
|
buf, damage = s.RedrawScreenOnly()
|
||||||
|
default:
|
||||||
|
s.OnPointerMotion(float64(px), float64(py))
|
||||||
|
buf, damage = s.Redraw()
|
||||||
|
}
|
||||||
|
if buf == nil {
|
||||||
|
t.Fatalf("step %d: no buffer", i)
|
||||||
|
}
|
||||||
|
want := reference(px, py, !hidden)
|
||||||
|
if !bytesEqual(buf.Data(), want) {
|
||||||
|
t.Fatalf("step %d: incremental frame differs from full render", i)
|
||||||
|
}
|
||||||
|
if onScreen != nil {
|
||||||
|
for y := range h {
|
||||||
|
for x := range w {
|
||||||
|
off := y*buf.Stride + x*4
|
||||||
|
if bytesEqual(want[off:off+4], onScreen[off:off+4]) || inDamage(x, y, damage) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.Fatalf("step %d: pixel %d,%d changed outside damage %v", i, x, y, damage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onScreen = want
|
||||||
|
s.SwapBuffers()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func bytesEqual(a, b []byte) bool {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := range a {
|
||||||
|
if a[i] != b[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,25 +38,6 @@ func (cd *ConfigDeployer) log(message string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeployConfigurations deploys all necessary configurations based on the chosen window manager
|
|
||||||
func (cd *ConfigDeployer) DeployConfigurations(ctx context.Context, wm deps.WindowManager) ([]DeploymentResult, error) {
|
|
||||||
return cd.DeployConfigurationsWithTerminal(ctx, wm, deps.TerminalGhostty)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeployConfigurationsWithTerminal deploys all necessary configurations based on chosen window manager and terminal
|
|
||||||
func (cd *ConfigDeployer) DeployConfigurationsWithTerminal(ctx context.Context, wm deps.WindowManager, terminal deps.Terminal) ([]DeploymentResult, error) {
|
|
||||||
return cd.DeployConfigurationsSelective(ctx, wm, terminal, nil, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeployConfigurationsWithSystemd deploys configurations with systemd option
|
|
||||||
func (cd *ConfigDeployer) DeployConfigurationsWithSystemd(ctx context.Context, wm deps.WindowManager, terminal deps.Terminal, useSystemd bool) ([]DeploymentResult, error) {
|
|
||||||
return cd.deployConfigurationsInternal(ctx, wm, terminal, nil, nil, nil, useSystemd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cd *ConfigDeployer) DeployConfigurationsSelective(ctx context.Context, wm deps.WindowManager, terminal deps.Terminal, installedDeps []deps.Dependency, replaceConfigs map[string]bool) ([]DeploymentResult, error) {
|
|
||||||
return cd.DeployConfigurationsSelectiveWithReinstalls(ctx, wm, terminal, installedDeps, replaceConfigs, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cd *ConfigDeployer) DeployConfigurationsSelectiveWithReinstalls(ctx context.Context, wm deps.WindowManager, terminal deps.Terminal, installedDeps []deps.Dependency, replaceConfigs map[string]bool, reinstallItems map[string]bool) ([]DeploymentResult, error) {
|
func (cd *ConfigDeployer) DeployConfigurationsSelectiveWithReinstalls(ctx context.Context, wm deps.WindowManager, terminal deps.Terminal, installedDeps []deps.Dependency, replaceConfigs map[string]bool, reinstallItems map[string]bool) ([]DeploymentResult, error) {
|
||||||
return cd.deployConfigurationsInternal(ctx, wm, terminal, installedDeps, replaceConfigs, reinstallItems, true)
|
return cd.deployConfigurationsInternal(ctx, wm, terminal, installedDeps, replaceConfigs, reinstallItems, true)
|
||||||
}
|
}
|
||||||
@@ -65,7 +46,7 @@ func (cd *ConfigDeployer) DeployConfigurationsSelectiveWithReinstallsAndSystemd(
|
|||||||
return cd.deployConfigurationsInternal(ctx, wm, terminal, installedDeps, replaceConfigs, reinstallItems, useSystemd)
|
return cd.deployConfigurationsInternal(ctx, wm, terminal, installedDeps, replaceConfigs, reinstallItems, useSystemd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cd *ConfigDeployer) deployConfigurationsInternal(ctx context.Context, wm deps.WindowManager, terminal deps.Terminal, installedDeps []deps.Dependency, replaceConfigs map[string]bool, reinstallItems map[string]bool, useSystemd bool) ([]DeploymentResult, error) {
|
func (cd *ConfigDeployer) deployConfigurationsInternal(_ context.Context, wm deps.WindowManager, terminal deps.Terminal, _ []deps.Dependency, replaceConfigs map[string]bool, _ map[string]bool, useSystemd bool) ([]DeploymentResult, error) {
|
||||||
var results []DeploymentResult
|
var results []DeploymentResult
|
||||||
|
|
||||||
// Primary config file paths used to detect fresh installs.
|
// Primary config file paths used to detect fresh installs.
|
||||||
@@ -174,6 +155,32 @@ func (cd *ConfigDeployer) deployConfigurationsInternal(ctx context.Context, wm d
|
|||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cd *ConfigDeployer) DeployCompositor(wm deps.WindowManager, terminal deps.Terminal, useSystemd bool) (DeploymentResult, error) {
|
||||||
|
switch wm {
|
||||||
|
case deps.WindowManagerNiri:
|
||||||
|
return cd.deployNiriConfig(terminal, useSystemd)
|
||||||
|
case deps.WindowManagerHyprland:
|
||||||
|
return cd.deployHyprlandConfig(terminal, useSystemd)
|
||||||
|
case deps.WindowManagerMango:
|
||||||
|
return cd.deployMangoConfig(terminal, useSystemd)
|
||||||
|
default:
|
||||||
|
return DeploymentResult{}, fmt.Errorf("unsupported window manager")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cd *ConfigDeployer) DeployTerminal(terminal deps.Terminal) ([]DeploymentResult, error) {
|
||||||
|
switch terminal {
|
||||||
|
case deps.TerminalGhostty:
|
||||||
|
return cd.deployGhosttyConfig()
|
||||||
|
case deps.TerminalKitty:
|
||||||
|
return cd.deployKittyConfig()
|
||||||
|
case deps.TerminalAlacritty:
|
||||||
|
return cd.deployAlacrittyConfig()
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported terminal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (cd *ConfigDeployer) deployNiriConfig(terminal deps.Terminal, useSystemd bool) (DeploymentResult, error) {
|
func (cd *ConfigDeployer) deployNiriConfig(terminal deps.Terminal, useSystemd bool) (DeploymentResult, error) {
|
||||||
result := DeploymentResult{
|
result := DeploymentResult{
|
||||||
ConfigType: "Niri",
|
ConfigType: "Niri",
|
||||||
@@ -286,7 +293,7 @@ func (cd *ConfigDeployer) deployNiriDmsConfigs(dmsDir, terminalCommand string) e
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cd *ConfigDeployer) deployMangoConfig(terminal deps.Terminal, useSystemd bool) (DeploymentResult, error) {
|
func (cd *ConfigDeployer) deployMangoConfig(terminal deps.Terminal, _ bool) (DeploymentResult, error) {
|
||||||
result := DeploymentResult{
|
result := DeploymentResult{
|
||||||
ConfigType: "Mango",
|
ConfigType: "Mango",
|
||||||
Path: filepath.Join(os.Getenv("HOME"), ".config", "mango", "config.conf"),
|
Path: filepath.Join(os.Getenv("HOME"), ".config", "mango", "config.conf"),
|
||||||
@@ -572,7 +579,13 @@ func (cd *ConfigDeployer) deployAlacrittyConfig() ([]DeploymentResult, error) {
|
|||||||
|
|
||||||
func (cd *ConfigDeployer) mergeNiriOutputSections(newConfig, existingConfig, dmsDir string) (string, error) {
|
func (cd *ConfigDeployer) mergeNiriOutputSections(newConfig, existingConfig, dmsDir string) (string, error) {
|
||||||
outputRegex := regexp.MustCompile(`(?m)^(/-)?\s*output\s+"[^"]+"\s*\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}`)
|
outputRegex := regexp.MustCompile(`(?m)^(/-)?\s*output\s+"[^"]+"\s*\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}`)
|
||||||
existingOutputs := outputRegex.FindAllString(existingConfig, -1)
|
var existingOutputs []string
|
||||||
|
for _, output := range outputRegex.FindAllString(existingConfig, -1) {
|
||||||
|
if strings.Contains(NiriConfig, output) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
existingOutputs = append(existingOutputs, output)
|
||||||
|
}
|
||||||
|
|
||||||
if len(existingOutputs) == 0 {
|
if len(existingOutputs) == 0 {
|
||||||
return newConfig, nil
|
return newConfig, nil
|
||||||
@@ -690,6 +703,14 @@ func (cd *ConfigDeployer) deployHyprlandConfig(terminal deps.Terminal, useSystem
|
|||||||
return result, result.Error
|
return result, result.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if useSystemd {
|
||||||
|
if targetPath, err := EnsureHyprlandSessionTarget(); err != nil {
|
||||||
|
cd.log(fmt.Sprintf("Warning: failed to write hyprland-session.target: %v", err))
|
||||||
|
} else {
|
||||||
|
cd.log(fmt.Sprintf("Ensured hyprland session target at %s", targetPath))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
movedLegacy, err := backupLegacyHyprlandConfFiles(configDir, dmsDir, backupDir)
|
movedLegacy, err := backupLegacyHyprlandConfFiles(configDir, dmsDir, backupDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result.Error = fmt.Errorf("failed to back up legacy hyprlang configs: %w", err)
|
result.Error = fmt.Errorf("failed to back up legacy hyprlang configs: %w", err)
|
||||||
|
|||||||
@@ -212,12 +212,12 @@ binds {
|
|||||||
Mod+Shift+Equal { set-window-height "+10%"; }
|
Mod+Shift+Equal { set-window-height "+10%"; }
|
||||||
|
|
||||||
// === Screenshots ===
|
// === Screenshots ===
|
||||||
XF86Launch1 { screenshot; }
|
XF86Launch1 hotkey-overlay-title="Screenshot: Region" { spawn "dms" "screenshot"; }
|
||||||
Ctrl+XF86Launch1 { screenshot-screen; }
|
Ctrl+XF86Launch1 hotkey-overlay-title="Screenshot: Full Screen" { spawn "dms" "screenshot" "full"; }
|
||||||
Alt+XF86Launch1 { screenshot-window; }
|
Alt+XF86Launch1 hotkey-overlay-title="Screenshot: Window" { spawn "dms" "screenshot" "window"; }
|
||||||
Print { screenshot; }
|
Print hotkey-overlay-title="Screenshot: Region" { spawn "dms" "screenshot"; }
|
||||||
Ctrl+Print { screenshot-screen; }
|
Ctrl+Print hotkey-overlay-title="Screenshot: Full Screen" { spawn "dms" "screenshot" "full"; }
|
||||||
Alt+Print { screenshot-window; }
|
Alt+Print hotkey-overlay-title="Screenshot: Window" { spawn "dms" "screenshot" "window"; }
|
||||||
// === Display Profiles ===
|
// === Display Profiles ===
|
||||||
Mod+P hotkey-overlay-title="Cycle Display Profile" {
|
Mod+P hotkey-overlay-title="Cycle Display Profile" {
|
||||||
spawn "dms" "ipc" "outputs" "cycleProfile";
|
spawn "dms" "ipc" "outputs" "cycleProfile";
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
const hyprlandSessionTargetUnit = `[Unit]
|
||||||
|
Description=Hyprland Session Target
|
||||||
|
BindsTo=graphical-session.target
|
||||||
|
Before=graphical-session.target
|
||||||
|
Wants=graphical-session-pre.target
|
||||||
|
After=graphical-session-pre.target
|
||||||
|
`
|
||||||
|
|
||||||
|
const legacyHyprlandSessionTargetUnit = `[Unit]
|
||||||
|
Description=Hyprland Session Target
|
||||||
|
Requires=graphical-session.target
|
||||||
|
After=graphical-session.target
|
||||||
|
`
|
||||||
|
|
||||||
|
// Only a missing or dankinstall-written unit is replaced; hand-written units are left alone.
|
||||||
|
func EnsureHyprlandSessionTarget() (string, error) {
|
||||||
|
homeDir, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
targetPath := filepath.Join(homeDir, ".config", "systemd", "user", "hyprland-session.target")
|
||||||
|
existing, err := os.ReadFile(targetPath)
|
||||||
|
if err == nil {
|
||||||
|
content := string(existing)
|
||||||
|
if content == hyprlandSessionTargetUnit {
|
||||||
|
return targetPath, nil
|
||||||
|
}
|
||||||
|
if content != legacyHyprlandSessionTargetUnit {
|
||||||
|
return targetPath, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(filepath.Dir(targetPath), 0o755); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(targetPath, []byte(hyprlandSessionTargetUnit), 0o644); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return targetPath, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func targetPathFor(t *testing.T, home string) string {
|
||||||
|
t.Helper()
|
||||||
|
return filepath.Join(home, ".config", "systemd", "user", "hyprland-session.target")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnsureHyprlandSessionTargetWritesMissing(t *testing.T) {
|
||||||
|
home := t.TempDir()
|
||||||
|
t.Setenv("HOME", home)
|
||||||
|
|
||||||
|
path, err := EnsureHyprlandSessionTarget()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if path != targetPathFor(t, home) {
|
||||||
|
t.Fatalf("unexpected path %s", path)
|
||||||
|
}
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(data) != hyprlandSessionTargetUnit {
|
||||||
|
t.Fatalf("unexpected content:\n%s", data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnsureHyprlandSessionTargetUpgradesLegacy(t *testing.T) {
|
||||||
|
home := t.TempDir()
|
||||||
|
t.Setenv("HOME", home)
|
||||||
|
path := targetPathFor(t, home)
|
||||||
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(path, []byte(legacyHyprlandSessionTargetUnit), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := EnsureHyprlandSessionTarget(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
data, _ := os.ReadFile(path)
|
||||||
|
if string(data) != hyprlandSessionTargetUnit {
|
||||||
|
t.Fatalf("legacy unit not upgraded:\n%s", data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnsureHyprlandSessionTargetKeepsCustom(t *testing.T) {
|
||||||
|
home := t.TempDir()
|
||||||
|
t.Setenv("HOME", home)
|
||||||
|
path := targetPathFor(t, home)
|
||||||
|
custom := "[Unit]\nDescription=mine\n"
|
||||||
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(path, []byte(custom), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := EnsureHyprlandSessionTarget(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
data, _ := os.ReadFile(path)
|
||||||
|
if string(data) != custom {
|
||||||
|
t.Fatalf("custom unit was overwritten:\n%s", data)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package dank16
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/lucasb-eyer/go-colorful"
|
"github.com/lucasb-eyer/go-colorful"
|
||||||
)
|
)
|
||||||
@@ -11,27 +12,38 @@ type RGB struct {
|
|||||||
R, G, B float64
|
R, G, B float64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HSL struct {
|
||||||
|
H, S, L float64
|
||||||
|
}
|
||||||
|
|
||||||
type HSV struct {
|
type HSV struct {
|
||||||
H, S, V float64
|
H, S, V float64
|
||||||
}
|
}
|
||||||
|
|
||||||
type ColorInfo struct {
|
type ColorInfo struct {
|
||||||
Hex string `json:"hex"`
|
Hex string `json:"hex"`
|
||||||
HexStripped string `json:"hex_stripped"`
|
HexStripped string `json:"hex_stripped"`
|
||||||
R int `json:"r"`
|
HexAlpha string `json:"hex_alpha"`
|
||||||
G int `json:"g"`
|
HexAlphaStripped string `json:"hex_alpha_stripped"`
|
||||||
B int `json:"b"`
|
AlphaHex string `json:"alpha_hex"`
|
||||||
}
|
AlphaHexStripped string `json:"alpha_hex_stripped"`
|
||||||
|
RGB string `json:"rgb"`
|
||||||
type VariantColorValue struct {
|
RGBA string `json:"rgba"`
|
||||||
Hex string `json:"hex"`
|
HSL string `json:"hsl"`
|
||||||
HexStripped string `json:"hex_stripped"`
|
HSLA string `json:"hsla"`
|
||||||
|
R string `json:"red"`
|
||||||
|
G string `json:"green"`
|
||||||
|
B string `json:"blue"`
|
||||||
|
A string `json:"alpha"`
|
||||||
|
H string `json:"hue"`
|
||||||
|
S string `json:"saturation"`
|
||||||
|
L string `json:"lightness"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type VariantColorInfo struct {
|
type VariantColorInfo struct {
|
||||||
Dark VariantColorValue `json:"dark"`
|
Dark ColorInfo `json:"dark"`
|
||||||
Light VariantColorValue `json:"light"`
|
Light ColorInfo `json:"light"`
|
||||||
Default VariantColorValue `json:"default"`
|
Default ColorInfo `json:"default"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Palette struct {
|
type Palette struct {
|
||||||
@@ -74,16 +86,41 @@ type VariantPalette struct {
|
|||||||
|
|
||||||
func NewColorInfo(hex string) ColorInfo {
|
func NewColorInfo(hex string) ColorInfo {
|
||||||
rgb := HexToRGB(hex)
|
rgb := HexToRGB(hex)
|
||||||
|
hsl := RGBToHSL(rgb)
|
||||||
|
|
||||||
|
var r, g, b, a, h, s, l string
|
||||||
|
|
||||||
|
r = strconv.FormatInt(int64(math.Round(rgb.R*255)), 10)
|
||||||
|
g = strconv.FormatInt(int64(math.Round(rgb.G*255)), 10)
|
||||||
|
b = strconv.FormatInt(int64(math.Round(rgb.B*255)), 10)
|
||||||
|
a = "1.0"
|
||||||
|
|
||||||
|
h = strconv.FormatFloat(math.Round(hsl.H*3600)/10, 'f', 1, 64)
|
||||||
|
s = fmt.Sprintf("%.1f%%", math.Round(hsl.S*1000.0)/10.0)
|
||||||
|
l = fmt.Sprintf("%.1f%%", math.Round(hsl.L*1000.0)/10.0)
|
||||||
|
|
||||||
stripped := hex
|
stripped := hex
|
||||||
if len(hex) > 0 && hex[0] == '#' {
|
if len(hex) > 0 && hex[0] == '#' {
|
||||||
stripped = hex[1:]
|
stripped = hex[1:]
|
||||||
}
|
}
|
||||||
return ColorInfo{
|
return ColorInfo{
|
||||||
Hex: hex,
|
Hex: hex,
|
||||||
HexStripped: stripped,
|
HexStripped: stripped,
|
||||||
R: int(math.Round(rgb.R * 255)),
|
HexAlpha: hex + "ff",
|
||||||
G: int(math.Round(rgb.G * 255)),
|
HexAlphaStripped: stripped + "ff",
|
||||||
B: int(math.Round(rgb.B * 255)),
|
AlphaHex: "#ff" + stripped,
|
||||||
|
AlphaHexStripped: "ff" + stripped,
|
||||||
|
RGB: fmt.Sprintf("rgb(%s, %s, %s)", r, g, b),
|
||||||
|
RGBA: fmt.Sprintf("rgba(%s, %s, %s, %s)", r, g, b, a),
|
||||||
|
HSL: fmt.Sprintf("hsl(%s, %s, %s)", h, s, l),
|
||||||
|
HSLA: fmt.Sprintf("hsla(%s, %s, %s, %s)", h, s, l, a),
|
||||||
|
R: r,
|
||||||
|
G: g,
|
||||||
|
B: b,
|
||||||
|
A: a,
|
||||||
|
H: h,
|
||||||
|
S: s,
|
||||||
|
L: l,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,6 +175,35 @@ func RGBToHSV(rgb RGB) HSV {
|
|||||||
return HSV{H: h, S: s, V: max}
|
return HSV{H: h, S: s, V: max}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RGBToHSL(rgb RGB) HSL {
|
||||||
|
max := math.Max(math.Max(rgb.R, rgb.G), rgb.B)
|
||||||
|
min := math.Min(math.Min(rgb.R, rgb.G), rgb.B)
|
||||||
|
delta := max - min
|
||||||
|
var h, s, l float64
|
||||||
|
|
||||||
|
l = (max + min) / 2
|
||||||
|
if delta == 0 {
|
||||||
|
return HSL{H: 0, S: 0, L: l}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch max {
|
||||||
|
case rgb.R:
|
||||||
|
h = math.Mod((rgb.G-rgb.B)/delta, 6.0) / 6.0
|
||||||
|
case rgb.G:
|
||||||
|
h = ((rgb.B-rgb.R)/delta + 2.0) / 6.0
|
||||||
|
default:
|
||||||
|
h = ((rgb.R-rgb.G)/delta + 4.0) / 6.0
|
||||||
|
}
|
||||||
|
|
||||||
|
if h < 0 {
|
||||||
|
h += 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
s = delta / (1 - math.Abs((2*l)-1))
|
||||||
|
|
||||||
|
return HSL{H: h, S: s, L: l}
|
||||||
|
}
|
||||||
|
|
||||||
func HSVToRGB(hsv HSV) RGB {
|
func HSVToRGB(hsv HSV) RGB {
|
||||||
h := hsv.H * 6.0
|
h := hsv.H * 6.0
|
||||||
c := hsv.V * hsv.S
|
c := hsv.V * hsv.S
|
||||||
@@ -623,17 +689,14 @@ type VariantOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func mergeColorInfo(dark, light ColorInfo, isLightMode bool) VariantColorInfo {
|
func mergeColorInfo(dark, light ColorInfo, isLightMode bool) VariantColorInfo {
|
||||||
darkVal := VariantColorValue{Hex: dark.Hex, HexStripped: dark.HexStripped}
|
defaultVal := dark
|
||||||
lightVal := VariantColorValue{Hex: light.Hex, HexStripped: light.HexStripped}
|
|
||||||
|
|
||||||
defaultVal := darkVal
|
|
||||||
if isLightMode {
|
if isLightMode {
|
||||||
defaultVal = lightVal
|
defaultVal = light
|
||||||
}
|
}
|
||||||
|
|
||||||
return VariantColorInfo{
|
return VariantColorInfo{
|
||||||
Dark: darkVal,
|
Dark: dark,
|
||||||
Light: lightVal,
|
Light: light,
|
||||||
Default: defaultVal,
|
Default: defaultVal,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package dank16
|
package dank16
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"math"
|
"math"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -91,6 +93,54 @@ func TestRGBToHex(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRGBToHSL(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input RGB
|
||||||
|
expected HSL
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "black",
|
||||||
|
input: RGB{R: 0.0, G: 0.0, B: 0.0},
|
||||||
|
expected: HSL{H: 0.0, S: 0.0, L: 0.0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "white",
|
||||||
|
input: RGB{R: 1.0, G: 1.0, B: 1.0},
|
||||||
|
expected: HSL{H: 0.0, S: 0.0, L: 1.0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "red",
|
||||||
|
input: RGB{R: 1.0, G: 0.0, B: 0.0},
|
||||||
|
expected: HSL{H: 0.0, S: 1.0, L: 0.5},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "green",
|
||||||
|
input: RGB{R: 0.0, G: 1.0, B: 0.0},
|
||||||
|
expected: HSL{H: 120.0 / 360.0, S: 1.0, L: 0.5},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "blue",
|
||||||
|
input: RGB{R: 0.0, G: 0.0, B: 1.0},
|
||||||
|
expected: HSL{H: 240.0 / 360.0, S: 1.0, L: 0.5},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "magenta",
|
||||||
|
input: RGB{R: 1.0, G: 0.0, B: 1.0},
|
||||||
|
expected: HSL{H: 300 / 360.0, S: 1.0, L: 0.5},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := RGBToHSL(tt.input)
|
||||||
|
if !floatEqual(result.H, tt.expected.H) || !floatEqual(result.S, tt.expected.S) || !floatEqual(result.L, tt.expected.L) {
|
||||||
|
t.Errorf("RGBToHSL(%v) = %v, expected %v", tt.input, result, tt.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRGBToHSV(t *testing.T) {
|
func TestRGBToHSV(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -309,31 +359,41 @@ func TestEnsureContrast(t *testing.T) {
|
|||||||
|
|
||||||
func TestGeneratePalette(t *testing.T) {
|
func TestGeneratePalette(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
base string
|
base string
|
||||||
opts PaletteOptions
|
backgroundHex string
|
||||||
|
backgroundHSL string
|
||||||
|
opts PaletteOptions
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "dark theme default",
|
name: "dark theme default",
|
||||||
base: "#625690",
|
base: "#625690",
|
||||||
opts: PaletteOptions{IsLight: false},
|
backgroundHex: "#1a1a1a",
|
||||||
|
backgroundHSL: "hsl(0.0, 0.0%, 10.2%)",
|
||||||
|
opts: PaletteOptions{IsLight: false},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "light theme default",
|
name: "light theme default",
|
||||||
base: "#625690",
|
base: "#625690",
|
||||||
opts: PaletteOptions{IsLight: true},
|
backgroundHex: "#f8f8f8",
|
||||||
|
backgroundHSL: "hsl(0.0, 0.0%, 97.3%)",
|
||||||
|
opts: PaletteOptions{IsLight: true},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "light theme with custom background",
|
name: "light theme with custom background",
|
||||||
base: "#625690",
|
base: "#625690",
|
||||||
|
backgroundHex: "#fafafa",
|
||||||
|
backgroundHSL: "hsl(0.0, 0.0%, 98.0%)",
|
||||||
opts: PaletteOptions{
|
opts: PaletteOptions{
|
||||||
IsLight: true,
|
IsLight: true,
|
||||||
Background: "#fafafa",
|
Background: "#fafafa",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "dark theme with custom background",
|
name: "dark theme with custom background",
|
||||||
base: "#625690",
|
base: "#625690",
|
||||||
|
backgroundHex: "#0a0a0a",
|
||||||
|
backgroundHSL: "hsl(0.0, 0.0%, 3.9%)",
|
||||||
opts: PaletteOptions{
|
opts: PaletteOptions{
|
||||||
IsLight: false,
|
IsLight: false,
|
||||||
Background: "#0a0a0a",
|
Background: "#0a0a0a",
|
||||||
@@ -358,12 +418,12 @@ func TestGeneratePalette(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if tt.opts.Background != "" && result.Color0.Hex != tt.opts.Background {
|
if result.Color0.Hex != tt.backgroundHex {
|
||||||
t.Errorf("Background color = %s, expected %s", result.Color0.Hex, tt.opts.Background)
|
t.Errorf("Background color hex = %s, expected %s", result.Color0.Hex, tt.backgroundHex)
|
||||||
} 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" {
|
if result.Color0.HSL != tt.backgroundHSL {
|
||||||
t.Errorf("Light mode background = %s, expected #f8f8f8", result.Color0.Hex)
|
t.Errorf("Background color hsl = %s, expected %s", result.Color0.HSL, tt.backgroundHSL)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Color15 is now derived from primary, so just verify it's a valid color
|
// Color15 is now derived from primary, so just verify it's a valid color
|
||||||
@@ -384,6 +444,59 @@ func TestGeneratePalette(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFormatOutput(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in string
|
||||||
|
out []byte
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
in: "#000000",
|
||||||
|
out: []byte(`{"hex":"#000000","hex_stripped":"000000","hex_alpha":"#000000ff","hex_alpha_stripped":"000000ff","alpha_hex":"#ff000000","alpha_hex_stripped":"ff000000","rgb":"rgb(0, 0, 0)","rgba":"rgba(0, 0, 0, 1.0)","hsl":"hsl(0.0, 0.0%, 0.0%)","hsla":"hsla(0.0, 0.0%, 0.0%, 1.0)","red":"0","green":"0","blue":"0","alpha":"1.0","hue":"0.0","saturation":"0.0%","lightness":"0.0%"}`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: "#ffffff",
|
||||||
|
out: []byte(`{"hex":"#ffffff","hex_stripped":"ffffff","hex_alpha":"#ffffffff","hex_alpha_stripped":"ffffffff","alpha_hex":"#ffffffff","alpha_hex_stripped":"ffffffff","rgb":"rgb(255, 255, 255)","rgba":"rgba(255, 255, 255, 1.0)","hsl":"hsl(0.0, 0.0%, 100.0%)","hsla":"hsla(0.0, 0.0%, 100.0%, 1.0)","red":"255","green":"255","blue":"255","alpha":"1.0","hue":"0.0","saturation":"0.0%","lightness":"100.0%"}`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: "#ff0000",
|
||||||
|
out: []byte(`{"hex":"#ff0000","hex_stripped":"ff0000","hex_alpha":"#ff0000ff","hex_alpha_stripped":"ff0000ff","alpha_hex":"#ffff0000","alpha_hex_stripped":"ffff0000","rgb":"rgb(255, 0, 0)","rgba":"rgba(255, 0, 0, 1.0)","hsl":"hsl(0.0, 100.0%, 50.0%)","hsla":"hsla(0.0, 100.0%, 50.0%, 1.0)","red":"255","green":"0","blue":"0","alpha":"1.0","hue":"0.0","saturation":"100.0%","lightness":"50.0%"}`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: "#00ff00",
|
||||||
|
out: []byte(`{"hex":"#00ff00","hex_stripped":"00ff00","hex_alpha":"#00ff00ff","hex_alpha_stripped":"00ff00ff","alpha_hex":"#ff00ff00","alpha_hex_stripped":"ff00ff00","rgb":"rgb(0, 255, 0)","rgba":"rgba(0, 255, 0, 1.0)","hsl":"hsl(120.0, 100.0%, 50.0%)","hsla":"hsla(120.0, 100.0%, 50.0%, 1.0)","red":"0","green":"255","blue":"0","alpha":"1.0","hue":"120.0","saturation":"100.0%","lightness":"50.0%"}`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: "#0000ff",
|
||||||
|
out: []byte(`{"hex":"#0000ff","hex_stripped":"0000ff","hex_alpha":"#0000ffff","hex_alpha_stripped":"0000ffff","alpha_hex":"#ff0000ff","alpha_hex_stripped":"ff0000ff","rgb":"rgb(0, 0, 255)","rgba":"rgba(0, 0, 255, 1.0)","hsl":"hsl(240.0, 100.0%, 50.0%)","hsla":"hsla(240.0, 100.0%, 50.0%, 1.0)","red":"0","green":"0","blue":"255","alpha":"1.0","hue":"240.0","saturation":"100.0%","lightness":"50.0%"}`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: "#625690",
|
||||||
|
out: []byte(`{"hex":"#625690","hex_stripped":"625690","hex_alpha":"#625690ff","hex_alpha_stripped":"625690ff","alpha_hex":"#ff625690","alpha_hex_stripped":"ff625690","rgb":"rgb(98, 86, 144)","rgba":"rgba(98, 86, 144, 1.0)","hsl":"hsl(252.4, 25.2%, 45.1%)","hsla":"hsla(252.4, 25.2%, 45.1%, 1.0)","red":"98","green":"86","blue":"144","alpha":"1.0","hue":"252.4","saturation":"25.2%","lightness":"45.1%"}`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: "#ff00ff",
|
||||||
|
out: []byte(`{"hex":"#ff00ff","hex_stripped":"ff00ff","hex_alpha":"#ff00ffff","hex_alpha_stripped":"ff00ffff","alpha_hex":"#ffff00ff","alpha_hex_stripped":"ffff00ff","rgb":"rgb(255, 0, 255)","rgba":"rgba(255, 0, 255, 1.0)","hsl":"hsl(300.0, 100.0%, 50.0%)","hsla":"hsla(300.0, 100.0%, 50.0%, 1.0)","red":"255","green":"0","blue":"255","alpha":"1.0","hue":"300.0","saturation":"100.0%","lightness":"50.0%"}`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: "#888888",
|
||||||
|
out: []byte(`{"hex":"#888888","hex_stripped":"888888","hex_alpha":"#888888ff","hex_alpha_stripped":"888888ff","alpha_hex":"#ff888888","alpha_hex_stripped":"ff888888","rgb":"rgb(136, 136, 136)","rgba":"rgba(136, 136, 136, 1.0)","hsl":"hsl(0.0, 0.0%, 53.3%)","hsla":"hsla(0.0, 0.0%, 53.3%, 1.0)","red":"136","green":"136","blue":"136","alpha":"1.0","hue":"0.0","saturation":"0.0%","lightness":"53.3%"}`),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.in, func(t *testing.T) {
|
||||||
|
ci := NewColorInfo(tt.in)
|
||||||
|
out, err := json.Marshal(ci)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Failed encoding", err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(out, tt.out) {
|
||||||
|
t.Errorf("Color output mismatch, got = %s, expected %s", out, tt.out)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRoundTripConversion(t *testing.T) {
|
func TestRoundTripConversion(t *testing.T) {
|
||||||
testColors := []string{"#000000", "#ffffff", "#ff0000", "#00ff00", "#0000ff", "#625690", "#808080"}
|
testColors := []string{"#000000", "#ffffff", "#ff0000", "#00ff00", "#0000ff", "#625690", "#808080"}
|
||||||
|
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ func parseSRCINFODeps(srcinfoPath string) (deps []string, makedeps []string, err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
for _, line := range strings.Split(string(data), "\n") {
|
for line := range strings.SplitSeq(string(data), "\n") {
|
||||||
line = strings.TrimSpace(line)
|
line = strings.TrimSpace(line)
|
||||||
var pkg string
|
var pkg string
|
||||||
var target *[]string
|
var target *[]string
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/config"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/netfetch"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/privesc"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/privesc"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/version"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/version"
|
||||||
)
|
)
|
||||||
@@ -615,24 +617,8 @@ func (b *BaseDistribution) WriteWindowManagerConfig(wm deps.WindowManager) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *BaseDistribution) WriteHyprlandSessionTarget() error {
|
func (b *BaseDistribution) WriteHyprlandSessionTarget() error {
|
||||||
homeDir, err := os.UserHomeDir()
|
targetPath, err := config.EnsureHyprlandSessionTarget()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get home directory: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
targetDir := filepath.Join(homeDir, ".config", "systemd", "user")
|
|
||||||
if err := os.MkdirAll(targetDir, 0o755); err != nil {
|
|
||||||
return fmt.Errorf("failed to create systemd user directory: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
targetPath := filepath.Join(targetDir, "hyprland-session.target")
|
|
||||||
content := `[Unit]
|
|
||||||
Description=Hyprland Session Target
|
|
||||||
Requires=graphical-session.target
|
|
||||||
After=graphical-session.target
|
|
||||||
`
|
|
||||||
|
|
||||||
if err := os.WriteFile(targetPath, []byte(content), 0o644); err != nil {
|
|
||||||
return fmt.Errorf("failed to write hyprland-session.target: %w", err)
|
return fmt.Errorf("failed to write hyprland-session.target: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -661,17 +647,10 @@ func (b *BaseDistribution) installDMSBinary(ctx context.Context, sudoPassword st
|
|||||||
CommandInfo: fmt.Sprintf("Downloading dms-%s.gz", arch),
|
CommandInfo: fmt.Sprintf("Downloading dms-%s.gz", arch),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get latest release version
|
version, err := netfetch.LatestReleaseTag(ctx)
|
||||||
latestVersionCmd := exec.CommandContext(ctx, "bash", "-c",
|
|
||||||
`curl -s https://api.github.com/repos/AvengeMedia/DankMaterialShell/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/'`)
|
|
||||||
versionOutput, err := latestVersionCmd.Output()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get latest DMS version: %w", err)
|
return fmt.Errorf("failed to get latest DMS version: %w", err)
|
||||||
}
|
}
|
||||||
version := strings.TrimSpace(string(versionOutput))
|
|
||||||
if version == "" {
|
|
||||||
return fmt.Errorf("could not determine latest DMS version")
|
|
||||||
}
|
|
||||||
|
|
||||||
homeDir, err := os.UserHomeDir()
|
homeDir, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -687,8 +666,7 @@ func (b *BaseDistribution) installDMSBinary(ctx context.Context, sudoPassword st
|
|||||||
downloadURL := fmt.Sprintf("https://github.com/AvengeMedia/DankMaterialShell/releases/download/%s/dms-cli-%s.gz", version, arch)
|
downloadURL := fmt.Sprintf("https://github.com/AvengeMedia/DankMaterialShell/releases/download/%s/dms-cli-%s.gz", version, arch)
|
||||||
gzPath := filepath.Join(tmpDir, "dms.gz")
|
gzPath := filepath.Join(tmpDir, "dms.gz")
|
||||||
|
|
||||||
downloadCmd := exec.CommandContext(ctx, "curl", "-L", downloadURL, "-o", gzPath)
|
if err := netfetch.ToFile(ctx, downloadURL, netfetch.Options{Timeout: 5 * time.Minute}, gzPath); err != nil {
|
||||||
if err := downloadCmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf("failed to download DMS binary: %w", err)
|
return fmt.Errorf("failed to download DMS binary: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package distros
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -267,12 +268,12 @@ func (d *DebianDistribution) InstallPrerequisites(ctx context.Context, sudoPassw
|
|||||||
Step: "Installing development dependencies...",
|
Step: "Installing development dependencies...",
|
||||||
IsComplete: false,
|
IsComplete: false,
|
||||||
NeedsSudo: true,
|
NeedsSudo: true,
|
||||||
CommandInfo: "sudo apt-get install -y curl wget git cmake ninja-build pkg-config gnupg libxcb-cursor-dev libglib2.0-dev libpolkit-agent-1-dev",
|
CommandInfo: "sudo apt-get install -y curl wget git cmake ninja-build pkg-config libxcb-cursor-dev libglib2.0-dev libpolkit-agent-1-dev",
|
||||||
LogOutput: "Installing additional development tools",
|
LogOutput: "Installing additional development tools",
|
||||||
}
|
}
|
||||||
|
|
||||||
devToolsCmd := privesc.ExecCommand(ctx, sudoPassword,
|
devToolsCmd := privesc.ExecCommand(ctx, sudoPassword,
|
||||||
"DEBIAN_FRONTEND=noninteractive apt-get install -y curl wget git cmake ninja-build pkg-config gnupg libxcb-cursor-dev libglib2.0-dev libpolkit-agent-1-dev libjpeg-dev libpugixml-dev")
|
"DEBIAN_FRONTEND=noninteractive apt-get install -y curl wget git cmake ninja-build pkg-config libxcb-cursor-dev libglib2.0-dev libpolkit-agent-1-dev libjpeg-dev libpugixml-dev")
|
||||||
if err := d.runWithProgress(devToolsCmd, progressChan, PhasePrerequisites, 0.10, 0.12); err != nil {
|
if err := d.runWithProgress(devToolsCmd, progressChan, PhasePrerequisites, 0.10, 0.12); err != nil {
|
||||||
return fmt.Errorf("failed to install development tools: %w", err)
|
return fmt.Errorf("failed to install development tools: %w", err)
|
||||||
}
|
}
|
||||||
@@ -505,12 +506,22 @@ func (d *DebianDistribution) enableOBSRepos(ctx context.Context, obsPkgs []Packa
|
|||||||
Progress: 0.18,
|
Progress: 0.18,
|
||||||
Step: fmt.Sprintf("Adding OBS GPG key for %s...", pkg.RepoURL),
|
Step: fmt.Sprintf("Adding OBS GPG key for %s...", pkg.RepoURL),
|
||||||
NeedsSudo: true,
|
NeedsSudo: true,
|
||||||
CommandInfo: fmt.Sprintf("curl & gpg to add key for %s", pkg.RepoURL),
|
CommandInfo: fmt.Sprintf("install keyring for %s", pkg.RepoURL),
|
||||||
}
|
}
|
||||||
|
|
||||||
keyCmd := fmt.Sprintf("bash -c 'rm -f %s && curl -fsSL %s/Release.key | gpg --batch --dearmor -o %s'", keyringPath, baseURL, keyringPath)
|
keyring, err := fetchDearmoredKey(ctx, baseURL+"/Release.key")
|
||||||
cmd := privesc.ExecCommand(ctx, sudoPassword, keyCmd)
|
if err != nil {
|
||||||
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)
|
||||||
|
}
|
||||||
|
tmpKeyring, err := writeTempKeyring(keyring)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to add OBS GPG key for %s: %w", pkg.RepoURL, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := privesc.ExecCommand(ctx, sudoPassword, fmt.Sprintf("install -m 0644 %s %s", tmpKeyring, keyringPath))
|
||||||
|
err = d.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.18, 0.20)
|
||||||
|
os.Remove(tmpKeyring)
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("failed to add OBS GPG key for %s: %w", pkg.RepoURL, err)
|
return fmt.Errorf("failed to add OBS GPG key for %s: %w", pkg.RepoURL, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ func debianAPTSuites(sources string) []string {
|
|||||||
stanza = nil
|
stanza = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, raw := range strings.Split(sources, "\n") {
|
for raw := range strings.SplitSeq(sources, "\n") {
|
||||||
line := strings.TrimSpace(raw)
|
line := strings.TrimSpace(raw)
|
||||||
if line == "" {
|
if line == "" {
|
||||||
flushStanza()
|
flushStanza()
|
||||||
@@ -164,8 +164,8 @@ func normalizeDebianSuite(suite string) string {
|
|||||||
suite = strings.ToLower(strings.TrimSpace(suite))
|
suite = strings.ToLower(strings.TrimSpace(suite))
|
||||||
suite = strings.Trim(suite, "/")
|
suite = strings.Trim(suite, "/")
|
||||||
for _, suffix := range []string{"-security", "-updates", "-backports", "-proposed-updates"} {
|
for _, suffix := range []string{"-security", "-updates", "-backports", "-proposed-updates"} {
|
||||||
if strings.HasSuffix(suite, suffix) {
|
if before, ok := strings.CutSuffix(suite, suffix); ok {
|
||||||
return strings.TrimSuffix(suite, suffix)
|
return before
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return suite
|
return suite
|
||||||
|
|||||||
@@ -457,7 +457,7 @@ func (f *FedoraDistribution) extractPackageNames(packages []PackageMapping) []st
|
|||||||
// enableTerraRepo registers the persistent Terra repo (via terra-release) so
|
// enableTerraRepo registers the persistent Terra repo (via terra-release) so
|
||||||
// `mangowm` resolves in the DNF phase. $releasever is single-quoted so dnf, not
|
// `mangowm` resolves in the DNF phase. $releasever is single-quoted so dnf, not
|
||||||
// the shell, expands it.
|
// the shell, expands it.
|
||||||
func (f *FedoraDistribution) enableTerraRepo(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
func (f *FedoraDistribution) enableTerraRepo(ctx context.Context, sudoPassword string, _ chan<- InstallProgressMsg) error {
|
||||||
// Skip if Terra is already configured
|
// Skip if Terra is already configured
|
||||||
if exec.CommandContext(ctx, "sh", "-c",
|
if exec.CommandContext(ctx, "sh", "-c",
|
||||||
"rpm -q terra-release >/dev/null 2>&1 || test -f /etc/yum.repos.d/terra.repo").Run() == nil {
|
"rpm -q terra-release >/dev/null 2>&1 || test -f /etc/yum.repos.d/terra.repo").Run() == nil {
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ type Distribution interface {
|
|||||||
|
|
||||||
// Package Mapping
|
// Package Mapping
|
||||||
GetPackageMapping(wm deps.WindowManager) map[string]PackageMapping
|
GetPackageMapping(wm deps.WindowManager) map[string]PackageMapping
|
||||||
|
GetPackageMappingWithVariants(wm deps.WindowManager, variants map[string]deps.PackageVariant) map[string]PackageMapping
|
||||||
|
|
||||||
// Prerequisites
|
// Prerequisites
|
||||||
InstallPrerequisites(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error
|
InstallPrerequisites(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error
|
||||||
|
|||||||
@@ -531,7 +531,7 @@ func isOpenSUSEInstallMediaURI(uri string) bool {
|
|||||||
func parseZypperInstallMediaAliases(output string) []string {
|
func parseZypperInstallMediaAliases(output string) []string {
|
||||||
var aliases []string
|
var aliases []string
|
||||||
|
|
||||||
for _, line := range strings.Split(output, "\n") {
|
for line := range strings.SplitSeq(output, "\n") {
|
||||||
line = strings.TrimSpace(line)
|
line = strings.TrimSpace(line)
|
||||||
if line == "" || !strings.Contains(line, "|") {
|
if line == "" || !strings.Contains(line, "|") {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
package distros
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/netfetch"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
crc24Init = 0xB704CE
|
||||||
|
crc24Poly = 0x1864CFB
|
||||||
|
)
|
||||||
|
|
||||||
|
func crc24(data []byte) uint32 {
|
||||||
|
crc := uint32(crc24Init)
|
||||||
|
for _, b := range data {
|
||||||
|
crc ^= uint32(b) << 16
|
||||||
|
for range 8 {
|
||||||
|
crc <<= 1
|
||||||
|
if crc&0x1000000 != 0 {
|
||||||
|
crc ^= crc24Poly
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return crc & 0xFFFFFF
|
||||||
|
}
|
||||||
|
|
||||||
|
func dearmorPGP(armored []byte) ([]byte, error) {
|
||||||
|
lines := strings.Split(strings.ReplaceAll(string(armored), "\r\n", "\n"), "\n")
|
||||||
|
|
||||||
|
start := -1
|
||||||
|
for i, line := range lines {
|
||||||
|
if strings.HasPrefix(strings.TrimSpace(line), "-----BEGIN PGP ") {
|
||||||
|
start = i + 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if start < 0 {
|
||||||
|
return nil, fmt.Errorf("no PGP armor header found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Armor headers run until the first blank line; a headerless block has none.
|
||||||
|
for start < len(lines) && strings.Contains(lines[start], ": ") {
|
||||||
|
start++
|
||||||
|
}
|
||||||
|
for start < len(lines) && strings.TrimSpace(lines[start]) == "" {
|
||||||
|
start++
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload strings.Builder
|
||||||
|
checksum := ""
|
||||||
|
ended := false
|
||||||
|
for _, line := range lines[start:] {
|
||||||
|
trimmed := strings.TrimSpace(line)
|
||||||
|
switch {
|
||||||
|
case trimmed == "":
|
||||||
|
continue
|
||||||
|
case strings.HasPrefix(trimmed, "-----END PGP "):
|
||||||
|
ended = true
|
||||||
|
case strings.HasPrefix(trimmed, "="):
|
||||||
|
checksum = trimmed[1:]
|
||||||
|
default:
|
||||||
|
payload.WriteString(trimmed)
|
||||||
|
}
|
||||||
|
if ended {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ended {
|
||||||
|
return nil, fmt.Errorf("unterminated PGP armor block")
|
||||||
|
}
|
||||||
|
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(payload.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid base64 in PGP armor: %w", err)
|
||||||
|
}
|
||||||
|
if len(decoded) == 0 {
|
||||||
|
return nil, fmt.Errorf("PGP armor block is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if checksum == "" {
|
||||||
|
return decoded, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
want, err := base64.StdEncoding.DecodeString(checksum)
|
||||||
|
if err != nil || len(want) != 3 {
|
||||||
|
return nil, fmt.Errorf("invalid PGP armor checksum %q", checksum)
|
||||||
|
}
|
||||||
|
got := crc24(decoded)
|
||||||
|
if uint32(want[0])<<16|uint32(want[1])<<8|uint32(want[2]) != got {
|
||||||
|
return nil, fmt.Errorf("PGP armor checksum mismatch")
|
||||||
|
}
|
||||||
|
return decoded, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchDearmoredKey(ctx context.Context, url string) ([]byte, error) {
|
||||||
|
armored, err := netfetch.Bytes(ctx, url, netfetch.Options{Timeout: 30 * time.Second})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch %s: %w", url, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
keyring, err := dearmorPGP(armored)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to dearmor key from %s: %w", url, err)
|
||||||
|
}
|
||||||
|
return keyring, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// privesc.ExecCommand owns stdin for the sudo password, so the key cannot be piped in.
|
||||||
|
func writeTempKeyring(keyring []byte) (string, error) {
|
||||||
|
f, err := os.CreateTemp("", "dms-repo-key-*.gpg")
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to create temp keyring: %w", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
if _, err := f.Write(keyring); err != nil {
|
||||||
|
os.Remove(f.Name())
|
||||||
|
return "", fmt.Errorf("failed to write temp keyring: %w", err)
|
||||||
|
}
|
||||||
|
if err := f.Chmod(0o644); err != nil {
|
||||||
|
os.Remove(f.Name())
|
||||||
|
return "", fmt.Errorf("failed to chmod temp keyring: %w", err)
|
||||||
|
}
|
||||||
|
return f.Name(), nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package distros
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Golden is `gpg --batch --dearmor` output for the fixture.
|
||||||
|
func TestDearmorPGPMatchesGPGGolden(t *testing.T) {
|
||||||
|
armored, err := os.ReadFile("testdata/obs-release.asc")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read fixture: %v", err)
|
||||||
|
}
|
||||||
|
want, err := os.ReadFile("testdata/obs-release.gpg")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read golden: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := dearmorPGP(armored)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("dearmorPGP: %v", err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(got, want) {
|
||||||
|
t.Fatalf("got %d bytes, want %d", len(got), len(want))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDearmorPGPAcceptsCRLF(t *testing.T) {
|
||||||
|
armored, err := os.ReadFile("testdata/obs-release.asc")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read fixture: %v", err)
|
||||||
|
}
|
||||||
|
want, err := dearmorPGP(armored)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("dearmorPGP: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
crlf := []byte(strings.ReplaceAll(string(armored), "\n", "\r\n"))
|
||||||
|
got, err := dearmorPGP(crlf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("dearmorPGP on CRLF input: %v", err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(got, want) {
|
||||||
|
t.Fatal("CRLF input decoded differently")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDearmorPGPRejects(t *testing.T) {
|
||||||
|
tests := map[string]string{
|
||||||
|
"no header": "mQINBAAA\n",
|
||||||
|
"unterminated": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBAAA\n",
|
||||||
|
"bad base64": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\n!!!!\n-----END PGP PUBLIC KEY BLOCK-----\n",
|
||||||
|
"empty payload": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\n-----END PGP PUBLIC KEY BLOCK-----\n",
|
||||||
|
"checksum mismatch": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBAAA\n=AAAA\n-----END PGP PUBLIC KEY BLOCK-----\n",
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, input := range tests {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
if _, err := dearmorPGP([]byte(input)); err == nil {
|
||||||
|
t.Fatal("expected an error")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
mQINBGkdCo0BEADPLTDkieySQmsnUYbj6VyDve3OVY23AfUukdNoqe/FwOPRm9eW
|
||||||
|
hduEMo28W5mQZp2TCOWGc7E4V63l3DWjrAzDlYtgDmupZ0QiTLbz0BUWHPsZHRfF
|
||||||
|
+xVh/pbfcixMtSk/InAe4Dv0p7jc+LjM1u8E204FWsJAPc7DRurwowXS6nEBZlMu
|
||||||
|
cjTzX0jehRQ2QShtib0iexd5YpfvW4ZGkQ6uSBnOAn5e/jwiSUbLZZhYR7RHdxhG
|
||||||
|
XR08PVP0SBUQvsTo2lC7Fqi8O3mEZm5NHeuj7dulNJxNu4LbdGYftev7GzJ5yT8O
|
||||||
|
dNem+rYspFpLaBbdOieRVFCcyVf+jGNHJiWnBkxUNGNSZr+M+26v455/v83mdQW4
|
||||||
|
1pGBhFyFVirH8wxLmJm7P7CYEBes9HujwaLHBnSGg9VAzXfWb2g5FSdDb3drG8FH
|
||||||
|
g40HEMl1Kuwnm0o3HH9k94C2cBaS3mji9aE21RgoxGoRZ4vEZEQpoWZ8CVFrKHhU
|
||||||
|
tU1rbXLWsuG37MRZJYYRb2gCM/zgoEmgBFvoHeVqqd1i8T56j11UUCEfK68iCVVY
|
||||||
|
GlHfXxEzhBO54qGpfbh3sC9ce5koicyXUdhkBz0Ob+APJ7TLFUpLarxNb/rrJ7YC
|
||||||
|
0RnVgqtPyS78rMcpOAJTEyZgl9Blad5escDB7394Snkgu/Ghsol8DS+sRwARAQAB
|
||||||
|
tEJob21lOkF2ZW5nZU1lZGlhIE9CUyBQcm9qZWN0IDxob21lOkF2ZW5nZU1lZGlh
|
||||||
|
QGJ1aWxkLm9wZW5zdXNlLm9yZz6JAlQEEwEIAD4WIQQgyj5YBMp1AJs2caQoR3hI
|
||||||
|
O8hhFQUCaR0KjQIbAwUJBB6wAAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRAo
|
||||||
|
R3hIO8hhFVgaEACxFCL5YwVJLlQvMjJuvdQt9D1T+vE9uvaYP8SONQOxhpRpOS0U
|
||||||
|
t+rHJXy73uy4cUieT0o1AS17/8C9b0ur+VlNdjgLf1fqPbAmcvEAB6UQ8yQzLC7j
|
||||||
|
/8XNJuc/aHcSX3fWFM7YwhVc8dXPE+eRUufxctFXqD8Otg3SDkmeObJE0HlR5m08
|
||||||
|
2BH8TaPE4HkkJJ4kZV23wnbNs0x4OdoOFOCT7CnCr9k0rgBNMP422S3vJ7zjf08t
|
||||||
|
GX0YGdNWyB0SSUt7OR372WPabImAeOUTB3f05vfd9fLWBeEPLt/I8wQj1pbo9SSF
|
||||||
|
upNXsoHcAnTl9gpBVxCZYJXVF+uefxY5d68ZZEYBVEAfzwwdxrvjosYqh0Whcv3V
|
||||||
|
DNlg2kQ2y9MCptv4a6qNTkVUTKAYv5SV0feV7b/OGUpAqu6dvjkzncMhSDZCmRUr
|
||||||
|
zjzG2/USv56ef1Hnayuw0YNyrM3IQuEz6jEKEwjofENIk7W2rcKQRZB8gp/BNI1M
|
||||||
|
MtRxC7YufcIRkyBANCtdM95Wi1tnV64LrPk3vnreG1HVII237xOBtiCn8XgBtHyA
|
||||||
|
NfJwFctjpaZCRcL6FgWO6uUHOfkZjLeLPehbNIFx/DCqphLWkusuWfH6jiDl0Y49
|
||||||
|
fpx4oJ6CfIfzsow2t1CL+JCF3D/OX5G20IlMhCMwdXoQ2H8Z/MNiHFJMKYkCMwQT
|
||||||
|
AQgAHRYhBMw1zD015aNkPlRaQ88Lkoze1k87BQJpHQqQAAoJEM8Lkoze1k87wTQP
|
||||||
|
/0b1PY/4uBWc0Uf5CK2t4lBUw+D9sL2wsLkCrSMS4mPlcutF+qh3xnO+d74I4Y8R
|
||||||
|
dg4RYlwHlRGFa2wMKp9q2HEeMxnqG4mRQBo3nCXvQDhPTerJG0mhnDzLVvS+4MaY
|
||||||
|
vcuxPntHvMygQDrguIChsdehU43FqlexjNKSBlAUBqDIkmgvTlcQjx62/m9m89Sr
|
||||||
|
Jg1QGqGoAYSaAPK3gDBpnBxYMpLMMCUtiIsHCbZFwxvOGBvTd+HWqaRpB56hm1sf
|
||||||
|
OFYXjoXl8ydkIh6HuAtP8NHHkHt3exL+dE5mL//KtmeLtm0aDklrd4Ol5ZUo25jQ
|
||||||
|
sOM0Tc/7AF0X3EyV3wTuz/MsyPFDMU6yJfrrlvpKIRhRtkDjkt8Ut9IOaCJ/iUoO
|
||||||
|
Fap45D4euq5M326HhkFfPKfnwO7MHWi82CNaQx05l7Lam+NniEOaBNMW0oAAS9JE
|
||||||
|
D6uoYgJSdCUHmhP8mt4f4C3j+VnQB+LsfPhQpqeEiqQtEar+arJ2KUgaOP/g3Z89
|
||||||
|
zaOiZN2nbpbK0wPmJQasJeyIGv1lQccjxD9ZxUd8iG8L4CJjnmXkUf1IrRyUZOoh
|
||||||
|
PK6GJB0YOGpiNnxG4bhiOjgdUMd9J5nBfjWTGNTjI+RzogfFbka+utDWuHR6ASCt
|
||||||
|
lNMZpysq5D+jaPXphRaKzHqaXrjtxWYiQN6JhGH8fgOF
|
||||||
|
=kr/Y
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
BIN
Binary file not shown.
@@ -26,6 +26,8 @@ const (
|
|||||||
ErrTypeSecretPromptTimeout
|
ErrTypeSecretPromptTimeout
|
||||||
ErrTypeSecretAgentFailed
|
ErrTypeSecretAgentFailed
|
||||||
ErrTypeGeneric
|
ErrTypeGeneric
|
||||||
|
ErrTypeInvalidDuration
|
||||||
|
ErrTypeInvalidContrast
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewCustomError(errType ErrorType, message string) error {
|
func NewCustomError(errType ErrorType, message string) error {
|
||||||
@@ -52,8 +54,10 @@ var (
|
|||||||
ErrNoUpdateNeeded = NewCustomError(ErrTypeNoUpdateNeeded, "no update needed")
|
ErrNoUpdateNeeded = NewCustomError(ErrTypeNoUpdateNeeded, "no update needed")
|
||||||
ErrInvalidTemperature = NewCustomError(ErrTypeInvalidTemperature, "temperature must be between 1000 and 10000")
|
ErrInvalidTemperature = NewCustomError(ErrTypeInvalidTemperature, "temperature must be between 1000 and 10000")
|
||||||
ErrInvalidGamma = NewCustomError(ErrTypeInvalidGamma, "gamma must be between 0 and 10")
|
ErrInvalidGamma = NewCustomError(ErrTypeInvalidGamma, "gamma must be between 0 and 10")
|
||||||
|
ErrInvalidContrast = NewCustomError(ErrTypeInvalidContrast, "contrast must be between 0.5 and 2")
|
||||||
ErrInvalidLocation = NewCustomError(ErrTypeInvalidLocation, "invalid latitude/longitude")
|
ErrInvalidLocation = NewCustomError(ErrTypeInvalidLocation, "invalid latitude/longitude")
|
||||||
ErrInvalidManualTimes = NewCustomError(ErrTypeInvalidManualTimes, "both sunrise and sunset must be set or neither")
|
ErrInvalidManualTimes = NewCustomError(ErrTypeInvalidManualTimes, "both sunrise and sunset must be set or neither")
|
||||||
|
ErrInvalidDuration = NewCustomError(ErrTypeInvalidDuration, "duration must be between 0 and 6 hours")
|
||||||
ErrNoWaylandDisplay = NewCustomError(ErrTypeNoWaylandDisplay, "no wayland display available")
|
ErrNoWaylandDisplay = NewCustomError(ErrTypeNoWaylandDisplay, "no wayland display available")
|
||||||
ErrNoGammaControl = NewCustomError(ErrTypeNoGammaControl, "compositor does not support gamma control")
|
ErrNoGammaControl = NewCustomError(ErrTypeNoGammaControl, "compositor does not support gamma control")
|
||||||
ErrNotInitialized = NewCustomError(ErrTypeNotInitialized, "manager not initialized")
|
ErrNotInitialized = NewCustomError(ErrTypeNotInitialized, "manager not initialized")
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ type GeoClueClient struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newGeoClueClient() (*GeoClueClient, error) {
|
func newGeoClueClient() (*GeoClueClient, error) {
|
||||||
dbusConn, err := dbus.ConnectSystemBus()
|
dbusConn, err := dbus.SystemBus()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("system bus connection failed: %w", err)
|
return nil, fmt.Errorf("system bus connection failed: %w", err)
|
||||||
}
|
}
|
||||||
@@ -64,7 +64,6 @@ func newGeoClueClient() (*GeoClueClient, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := c.setupClient(); err != nil {
|
if err := c.setupClient(); err != nil {
|
||||||
dbusConn.Close()
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,10 +89,6 @@ func (c *GeoClueClient) Close() {
|
|||||||
c.subscribers.Delete(key)
|
c.subscribers.Delete(key)
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
if c.dbusConn != nil {
|
|
||||||
c.dbusConn.Close()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GeoClueClient) Subscribe(id string) chan Location {
|
func (c *GeoClueClient) Subscribe(id string) chan Location {
|
||||||
@@ -138,9 +133,7 @@ func (c *GeoClueClient) startSignalPump() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.sigWG.Add(1)
|
c.sigWG.Go(func() {
|
||||||
go func() {
|
|
||||||
defer c.sigWG.Done()
|
|
||||||
|
|
||||||
clientObj := c.dbusConn.Object(dbusGeoClueService, c.clientPath)
|
clientObj := c.dbusConn.Object(dbusGeoClueService, c.clientPath)
|
||||||
clientObj.Call(dbusGeoClueClientTimeStart, 0)
|
clientObj.Call(dbusGeoClueClientTimeStart, 0)
|
||||||
@@ -161,7 +154,7 @@ func (c *GeoClueClient) startSignalPump() error {
|
|||||||
c.handleSignal(sig)
|
c.handleSignal(sig)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
package gpu
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/linux_dmabuf"
|
||||||
|
wlclient "github.com/AvengeMedia/dankgo/wayland/client"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
var eglVendorDirs = []string{"/etc/glvnd/egl_vendor.d", "/usr/share/glvnd/egl_vendor.d"}
|
||||||
|
|
||||||
|
const (
|
||||||
|
dmabufFeedbackMinVersion = 4
|
||||||
|
// v6 stops sending main_device
|
||||||
|
dmabufMainDeviceMaxVersion = 5
|
||||||
|
compositorProbeTimeout = 2 * time.Second
|
||||||
|
sysDevCharDir = "/sys/dev/char"
|
||||||
|
)
|
||||||
|
|
||||||
|
func EGLVendorEnv() []string {
|
||||||
|
if os.Getenv("__EGL_VENDOR_LIBRARY_FILENAMES") != "" || os.Getenv("__EGL_VENDOR_LIBRARY_DIRS") != "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
mesa, nvidia := findEGLVendorICDs(eglVendorDirs)
|
||||||
|
if mesa == "" || nvidia == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
driver, err := compositorRenderDriver()
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("egl: keeping glvnd vendor defaults: %v", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if driver == "nvidia" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
log.Infof("egl: compositor renders on %s, loading mesa vendor only", driver)
|
||||||
|
return []string{"__EGL_VENDOR_LIBRARY_FILENAMES=" + mesa}
|
||||||
|
}
|
||||||
|
|
||||||
|
type eglVendorICD struct {
|
||||||
|
ICD struct {
|
||||||
|
LibraryPath string `json:"library_path"`
|
||||||
|
} `json:"ICD"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func findEGLVendorICDs(dirs []string) (mesa, nvidia string) {
|
||||||
|
for _, dir := range dirs {
|
||||||
|
entries, err := filepath.Glob(filepath.Join(dir, "*.json"))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, path := range entries {
|
||||||
|
switch eglVendorLibrary(path) {
|
||||||
|
case "libEGL_mesa":
|
||||||
|
if mesa == "" {
|
||||||
|
mesa = path
|
||||||
|
}
|
||||||
|
case "libEGL_nvidia":
|
||||||
|
if nvidia == "" {
|
||||||
|
nvidia = path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mesa, nvidia
|
||||||
|
}
|
||||||
|
|
||||||
|
func eglVendorLibrary(path string) string {
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var icd eglVendorICD
|
||||||
|
if err := json.Unmarshal(data, &icd); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
base := filepath.Base(icd.ICD.LibraryPath)
|
||||||
|
name, _, _ := strings.Cut(base, ".so")
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
func compositorRenderDriver() (string, error) {
|
||||||
|
dev, err := compositorMainDevice()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return drmDeviceDriver(sysDevCharDir, dev)
|
||||||
|
}
|
||||||
|
|
||||||
|
func drmDeviceDriver(charDir string, dev uint64) (string, error) {
|
||||||
|
link := filepath.Join(charDir, fmt.Sprintf("%d:%d", unix.Major(dev), unix.Minor(dev)), "device", "driver")
|
||||||
|
target, err := os.Readlink(link)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return filepath.Base(target), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func compositorMainDevice() (uint64, error) {
|
||||||
|
display, err := wlclient.Connect("")
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
ctx := display.Context()
|
||||||
|
defer ctx.Close()
|
||||||
|
if err := ctx.SetReadDeadline(time.Now().Add(compositorProbeTimeout)); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
registry, err := display.GetRegistry()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
var dmabuf *linux_dmabuf.ZwpLinuxDmabufV1
|
||||||
|
registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) {
|
||||||
|
if e.Interface != linux_dmabuf.ZwpLinuxDmabufV1InterfaceName || e.Version < dmabufFeedbackMinVersion {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
candidate := linux_dmabuf.NewZwpLinuxDmabufV1(ctx)
|
||||||
|
if err := registry.Bind(e.Name, e.Interface, min(e.Version, dmabufMainDeviceMaxVersion), candidate); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dmabuf = candidate
|
||||||
|
})
|
||||||
|
if err := display.Roundtrip(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if dmabuf == nil {
|
||||||
|
return 0, errors.New("compositor lacks linux-dmabuf feedback")
|
||||||
|
}
|
||||||
|
defer dmabuf.Destroy()
|
||||||
|
|
||||||
|
feedback, err := dmabuf.GetDefaultFeedback()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer feedback.Destroy()
|
||||||
|
|
||||||
|
var device []byte
|
||||||
|
done := false
|
||||||
|
feedback.SetMainDeviceHandler(func(e linux_dmabuf.ZwpLinuxDmabufFeedbackV1MainDeviceEvent) {
|
||||||
|
device = e.Device
|
||||||
|
})
|
||||||
|
feedback.SetDoneHandler(func(linux_dmabuf.ZwpLinuxDmabufFeedbackV1DoneEvent) {
|
||||||
|
done = true
|
||||||
|
})
|
||||||
|
for !done {
|
||||||
|
if err := ctx.Dispatch(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mainDeviceFromEvent(device)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mainDeviceFromEvent(device []byte) (uint64, error) {
|
||||||
|
if len(device) != 8 {
|
||||||
|
return 0, fmt.Errorf("main_device has %d bytes, want 8", len(device))
|
||||||
|
}
|
||||||
|
return binary.NativeEndian.Uint64(device), nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package gpu
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func writeICD(t *testing.T, dir, name, library string) string {
|
||||||
|
t.Helper()
|
||||||
|
path := filepath.Join(dir, name)
|
||||||
|
if err := os.WriteFile(path, []byte(`{"file_format_version":"1.0.0","ICD":{"library_path":"`+library+`"}}`), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindEGLVendorICDs(t *testing.T) {
|
||||||
|
etc := t.TempDir()
|
||||||
|
usr := t.TempDir()
|
||||||
|
writeICD(t, usr, "10_nvidia.json", "libEGL_nvidia.so.0")
|
||||||
|
mesaPath := writeICD(t, usr, "50_mesa.json", "libEGL_mesa.so.0")
|
||||||
|
nvidiaPath := writeICD(t, etc, "10_nvidia.json", "/usr/lib/libEGL_nvidia.so.0")
|
||||||
|
if err := os.WriteFile(filepath.Join(etc, "broken.json"), []byte("{"), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mesa, nvidia := findEGLVendorICDs([]string{etc, usr})
|
||||||
|
if mesa != mesaPath {
|
||||||
|
t.Errorf("mesa = %q, want %q", mesa, mesaPath)
|
||||||
|
}
|
||||||
|
if nvidia != nvidiaPath {
|
||||||
|
t.Errorf("nvidia = %q, want %q", nvidia, nvidiaPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
mesa, nvidia = findEGLVendorICDs([]string{filepath.Join(etc, "missing")})
|
||||||
|
if mesa != "" || nvidia != "" {
|
||||||
|
t.Errorf("missing dir: got %q %q, want empty", mesa, nvidia)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDrmDeviceDriver(t *testing.T) {
|
||||||
|
root := t.TempDir()
|
||||||
|
charDir := filepath.Join(root, "char")
|
||||||
|
deviceDir := filepath.Join(charDir, "226:128", "device")
|
||||||
|
if err := os.MkdirAll(deviceDir, 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.Symlink("../../../../bus/pci/drivers/amdgpu", filepath.Join(deviceDir, "driver")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
driver, err := drmDeviceDriver(charDir, 226<<8|128)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if driver != "amdgpu" {
|
||||||
|
t.Errorf("driver = %q, want amdgpu", driver)
|
||||||
|
}
|
||||||
|
if _, err := drmDeviceDriver(charDir, 226<<8|129); err == nil {
|
||||||
|
t.Error("unknown device: want error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMainDeviceFromEvent(t *testing.T) {
|
||||||
|
raw := make([]byte, 8)
|
||||||
|
binary.NativeEndian.PutUint64(raw, 226<<8|128)
|
||||||
|
dev, err := mainDeviceFromEvent(raw)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if dev != 226<<8|128 {
|
||||||
|
t.Errorf("dev = %d", dev)
|
||||||
|
}
|
||||||
|
if _, err := mainDeviceFromEvent(raw[:4]); err == nil {
|
||||||
|
t.Error("short array: want error")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,8 +34,12 @@ var orderedConfigNames = []string{"niri", "hyprland", "ghostty", "kitty", "alacr
|
|||||||
|
|
||||||
// Config holds all CLI parameters for unattended installation.
|
// Config holds all CLI parameters for unattended installation.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Compositor string // "niri" or "hyprland"
|
Compositor string // "niri", "hyprland", or "mango"
|
||||||
Terminal string // "ghostty", "kitty", or "alacritty"
|
Terminal string // "ghostty", "kitty", or "alacritty"
|
||||||
|
PrivescTool string // "sudo", "doas", or "run0"; empty means autodetect
|
||||||
|
GitAll bool // git variant for every dep that supports it
|
||||||
|
GitDeps []string // deps to force the git variant of
|
||||||
|
AllFeatures bool // enable all optional deps
|
||||||
IncludeDeps []string
|
IncludeDeps []string
|
||||||
ExcludeDeps []string
|
ExcludeDeps []string
|
||||||
ReplaceConfigs []string // specific configs to deploy (e.g. "niri", "ghostty")
|
ReplaceConfigs []string // specific configs to deploy (e.g. "niri", "ghostty")
|
||||||
@@ -43,6 +47,7 @@ type Config struct {
|
|||||||
Yes bool
|
Yes bool
|
||||||
DankSearch bool // install danksearch and enable its user service
|
DankSearch bool // install danksearch and enable its user service
|
||||||
DankCalendar bool // install dankcalendar
|
DankCalendar bool // install dankcalendar
|
||||||
|
DmsGreeter bool // install dms-greeter
|
||||||
}
|
}
|
||||||
|
|
||||||
// Runner orchestrates unattended (headless) installation.
|
// Runner orchestrates unattended (headless) installation.
|
||||||
@@ -68,6 +73,10 @@ func (r *Runner) GetLogChan() <-chan string {
|
|||||||
func (r *Runner) Run() error {
|
func (r *Runner) Run() error {
|
||||||
r.log("Starting headless installation")
|
r.log("Starting headless installation")
|
||||||
|
|
||||||
|
if err := r.applyPrivescTool(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// 1. Parse compositor and terminal selections
|
// 1. Parse compositor and terminal selections
|
||||||
wm, err := r.parseWindowManager()
|
wm, err := r.parseWindowManager()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -112,14 +121,16 @@ func (r *Runner) Run() error {
|
|||||||
return fmt.Errorf("dependency detection failed: %w", err)
|
return fmt.Errorf("dependency detection failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reinstallItems, err := r.applyGitVariants(dependencies, gitVariantChecker(distro, wm, dependencies))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// 5. Apply include/exclude filters and build the disabled-items map.
|
// 5. Apply include/exclude filters and build the disabled-items map.
|
||||||
// Headless mode does not currently collect any explicit reinstall selections,
|
|
||||||
// so keep reinstallItems nil instead of constructing an always-empty map.
|
|
||||||
disabledItems, err := r.buildDisabledItems(dependencies)
|
disabledItems, err := r.buildDisabledItems(dependencies)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var reinstallItems map[string]bool
|
|
||||||
|
|
||||||
// Print dependency summary
|
// Print dependency summary
|
||||||
fmt.Fprintln(os.Stdout, "\nDependencies:")
|
fmt.Fprintln(os.Stdout, "\nDependencies:")
|
||||||
@@ -129,6 +140,9 @@ func (r *Runner) Run() error {
|
|||||||
if disabledItems[dep.Name] {
|
if disabledItems[dep.Name] {
|
||||||
marker = " SKIP "
|
marker = " SKIP "
|
||||||
status = "(disabled)"
|
status = "(disabled)"
|
||||||
|
} else if reinstallItems[dep.Name] {
|
||||||
|
marker = " RE "
|
||||||
|
status = "(will reinstall)"
|
||||||
} else {
|
} else {
|
||||||
switch dep.Status {
|
switch dep.Status {
|
||||||
case deps.StatusInstalled:
|
case deps.StatusInstalled:
|
||||||
@@ -283,15 +297,96 @@ func (r *Runner) Run() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Runner) applyPrivescTool() error {
|
||||||
|
if r.cfg.PrivescTool == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
switch tool := privesc.Tool(strings.ToLower(r.cfg.PrivescTool)); tool {
|
||||||
|
case privesc.ToolSudo, privesc.ToolDoas, privesc.ToolRun0:
|
||||||
|
if err := privesc.SetTool(tool); err != nil {
|
||||||
|
return fmt.Errorf("--privesc: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid --privesc value %q: must be 'sudo', 'doas', or 'run0'", r.cfg.PrivescTool)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanToggle is not enough: some distros discard the variant (hyprland on Arch maps to stable either way)
|
||||||
|
func gitVariantChecker(distro distros.Distribution, wm deps.WindowManager, dependencies []deps.Dependency) func(name string) bool {
|
||||||
|
stable := distro.GetPackageMappingWithVariants(wm, map[string]deps.PackageVariant{})
|
||||||
|
allGit := make(map[string]deps.PackageVariant, len(dependencies))
|
||||||
|
for _, dep := range dependencies {
|
||||||
|
allGit[dep.Name] = deps.VariantGit
|
||||||
|
}
|
||||||
|
git := distro.GetPackageMappingWithVariants(wm, allGit)
|
||||||
|
return func(name string) bool {
|
||||||
|
mapping, ok := git[name]
|
||||||
|
return ok && mapping != stable[name]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Runner) applyGitVariants(dependencies []deps.Dependency, hasGitVariant func(name string) bool) (map[string]bool, error) {
|
||||||
|
reinstallItems := make(map[string]bool)
|
||||||
|
|
||||||
|
// InstallPackages skips StatusInstalled deps unless marked for reinstall
|
||||||
|
markGit := func(dep *deps.Dependency) {
|
||||||
|
if dep.Variant == deps.VariantStable && dep.Status == deps.StatusInstalled {
|
||||||
|
reinstallItems[dep.Name] = true
|
||||||
|
}
|
||||||
|
dep.Variant = deps.VariantGit
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, name := range r.cfg.GitDeps {
|
||||||
|
name = strings.ToLower(strings.TrimSpace(name))
|
||||||
|
if name == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dep := findDepFold(dependencies, name)
|
||||||
|
if dep == nil {
|
||||||
|
return nil, fmt.Errorf("--git-deps: unknown dependency %q", name)
|
||||||
|
}
|
||||||
|
if !dep.CanToggle || !hasGitVariant(dep.Name) {
|
||||||
|
return nil, fmt.Errorf("--git-deps: %q does not have a git variant on this distribution", dep.Name)
|
||||||
|
}
|
||||||
|
markGit(dep)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !r.cfg.GitAll {
|
||||||
|
return reinstallItems, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range dependencies {
|
||||||
|
if dependencies[i].CanToggle && hasGitVariant(dependencies[i].Name) {
|
||||||
|
markGit(&dependencies[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reinstallItems, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findDepFold(dependencies []deps.Dependency, lowerName string) *deps.Dependency {
|
||||||
|
if lowerName == "dms" {
|
||||||
|
lowerName = "dms (dankmaterialshell)"
|
||||||
|
}
|
||||||
|
for i := range dependencies {
|
||||||
|
if strings.ToLower(dependencies[i].Name) == lowerName {
|
||||||
|
return &dependencies[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// buildDisabledItems computes the set of dependencies that should be skipped
|
// buildDisabledItems computes the set of dependencies that should be skipped
|
||||||
// during installation. Optional components are opt-in (disabled by default),
|
// during installation. Optional components are opt-in (disabled by default),
|
||||||
// then re-enabled by the dedicated flags and --include-deps.
|
// then re-enabled by --all-features, the dedicated flags, and --include-deps.
|
||||||
func (r *Runner) buildDisabledItems(dependencies []deps.Dependency) (map[string]bool, error) {
|
func (r *Runner) buildDisabledItems(dependencies []deps.Dependency) (map[string]bool, error) {
|
||||||
disabledItems := make(map[string]bool)
|
disabledItems := make(map[string]bool)
|
||||||
|
|
||||||
for i := range dependencies {
|
if !r.cfg.AllFeatures {
|
||||||
if !dependencies[i].Required {
|
for i := range dependencies {
|
||||||
disabledItems[dependencies[i].Name] = true
|
if !dependencies[i].Required {
|
||||||
|
disabledItems[dependencies[i].Name] = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,6 +403,12 @@ func (r *Runner) buildDisabledItems(dependencies []deps.Dependency) (map[string]
|
|||||||
}
|
}
|
||||||
delete(disabledItems, "dankcalendar")
|
delete(disabledItems, "dankcalendar")
|
||||||
}
|
}
|
||||||
|
if r.cfg.DmsGreeter {
|
||||||
|
if !r.depExists(dependencies, "dms-greeter") {
|
||||||
|
return nil, fmt.Errorf("--dms-greeter: not available on this distribution")
|
||||||
|
}
|
||||||
|
delete(disabledItems, "dms-greeter")
|
||||||
|
}
|
||||||
|
|
||||||
// Process --include-deps (enable items that are disabled by default)
|
// Process --include-deps (enable items that are disabled by default)
|
||||||
for _, name := range r.cfg.IncludeDeps {
|
for _, name := range r.cfg.IncludeDeps {
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ func TestLog(t *testing.T) {
|
|||||||
r := NewRunner(Config{})
|
r := NewRunner(Config{})
|
||||||
|
|
||||||
// log should not block even if channel is full
|
// log should not block even if channel is full
|
||||||
for i := 0; i < 1100; i++ {
|
for range 1100 {
|
||||||
r.log("message")
|
r.log("message")
|
||||||
}
|
}
|
||||||
// If we reach here without hanging, the non-blocking send works
|
// If we reach here without hanging, the non-blocking send works
|
||||||
@@ -357,6 +357,8 @@ func TestBuildDisabledItems(t *testing.T) {
|
|||||||
excludeDeps []string
|
excludeDeps []string
|
||||||
dankSearch bool
|
dankSearch bool
|
||||||
dankCalendar bool
|
dankCalendar bool
|
||||||
|
dmsGreeter bool
|
||||||
|
allFeatures bool
|
||||||
deps []deps.Dependency // nil means use the shared fixture
|
deps []deps.Dependency // nil means use the shared fixture
|
||||||
wantErr bool
|
wantErr bool
|
||||||
errContains string // substring expected in error message
|
errContains string // substring expected in error message
|
||||||
@@ -442,6 +444,31 @@ func TestBuildDisabledItems(t *testing.T) {
|
|||||||
wantErr: true,
|
wantErr: true,
|
||||||
errContains: "--dankcalendar",
|
errContains: "--dankcalendar",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "dms-greeter flag enables it",
|
||||||
|
dmsGreeter: true,
|
||||||
|
wantEnabled: []string{"dms-greeter"},
|
||||||
|
wantDisabled: []string{"danksearch", "dankcalendar"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dms-greeter flag when unavailable errors",
|
||||||
|
dmsGreeter: true,
|
||||||
|
deps: []deps.Dependency{{Name: "niri", Status: deps.StatusInstalled, Required: true}},
|
||||||
|
wantErr: true,
|
||||||
|
errContains: "--dms-greeter",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all-features enables every optional dep",
|
||||||
|
allFeatures: true,
|
||||||
|
wantEnabled: []string{"dms-greeter", "danksearch", "dankcalendar", "niri", "ghostty", "waybar"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all-features with exclude still disables the excluded dep",
|
||||||
|
allFeatures: true,
|
||||||
|
excludeDeps: []string{"dankcalendar"},
|
||||||
|
wantEnabled: []string{"dms-greeter", "danksearch"},
|
||||||
|
wantDisabled: []string{"dankcalendar"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@@ -451,6 +478,8 @@ func TestBuildDisabledItems(t *testing.T) {
|
|||||||
ExcludeDeps: tt.excludeDeps,
|
ExcludeDeps: tt.excludeDeps,
|
||||||
DankSearch: tt.dankSearch,
|
DankSearch: tt.dankSearch,
|
||||||
DankCalendar: tt.dankCalendar,
|
DankCalendar: tt.dankCalendar,
|
||||||
|
DmsGreeter: tt.dmsGreeter,
|
||||||
|
AllFeatures: tt.allFeatures,
|
||||||
})
|
})
|
||||||
d := tt.deps
|
d := tt.deps
|
||||||
if d == nil {
|
if d == nil {
|
||||||
@@ -491,3 +520,152 @@ func TestBuildDisabledItems(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestApplyGitVariants(t *testing.T) {
|
||||||
|
freshDeps := func() []deps.Dependency {
|
||||||
|
return []deps.Dependency{
|
||||||
|
{Name: "niri", Status: deps.StatusInstalled, Variant: deps.VariantStable, CanToggle: true},
|
||||||
|
{Name: "quickshell", Status: deps.StatusMissing, Variant: deps.VariantStable, CanToggle: true},
|
||||||
|
{Name: "hyprland", Status: deps.StatusInstalled, Variant: deps.VariantGit, CanToggle: true},
|
||||||
|
{Name: "dms (DankMaterialShell)", Status: deps.StatusMissing, Variant: deps.VariantStable, CanToggle: true},
|
||||||
|
{Name: "ghostty", Status: deps.StatusInstalled, Variant: deps.VariantStable, CanToggle: false},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
gitAll bool
|
||||||
|
gitDeps []string
|
||||||
|
noGitVariant []string // deps whose git and stable packages resolve identically
|
||||||
|
wantErr bool
|
||||||
|
errContains string
|
||||||
|
wantGit []string
|
||||||
|
wantStable []string
|
||||||
|
wantReinstall []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no git flags leaves variants untouched",
|
||||||
|
wantGit: []string{"hyprland"},
|
||||||
|
wantStable: []string{"niri", "quickshell", "dms (DankMaterialShell)", "ghostty"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "git-deps flips only the named deps",
|
||||||
|
gitDeps: []string{"niri", "quickshell"},
|
||||||
|
wantGit: []string{"niri", "quickshell", "hyprland"},
|
||||||
|
wantStable: []string{"dms (DankMaterialShell)", "ghostty"},
|
||||||
|
wantReinstall: []string{"niri"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "git-deps matches names case-insensitively and accepts the dms alias",
|
||||||
|
gitDeps: []string{"NIRI", "dms"},
|
||||||
|
wantGit: []string{"niri", "dms (DankMaterialShell)", "hyprland"},
|
||||||
|
wantStable: []string{"quickshell", "ghostty"},
|
||||||
|
wantReinstall: []string{"niri"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty and whitespace entries are skipped",
|
||||||
|
gitDeps: []string{"", " "},
|
||||||
|
wantGit: []string{"hyprland"},
|
||||||
|
wantStable: []string{"niri", "quickshell", "dms (DankMaterialShell)", "ghostty"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown dep errors",
|
||||||
|
gitDeps: []string{"nonexistent"},
|
||||||
|
wantErr: true,
|
||||||
|
errContains: "--git-deps",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dep without git variant errors",
|
||||||
|
gitDeps: []string{"ghostty"},
|
||||||
|
wantErr: true,
|
||||||
|
errContains: "does not have a git variant",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "git flips every toggleable dep",
|
||||||
|
gitAll: true,
|
||||||
|
wantGit: []string{"niri", "quickshell", "hyprland", "dms (DankMaterialShell)"},
|
||||||
|
wantStable: []string{"ghostty"},
|
||||||
|
wantReinstall: []string{"niri"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "git-deps errors when the distro has no distinct git package",
|
||||||
|
gitDeps: []string{"niri"},
|
||||||
|
noGitVariant: []string{"niri"},
|
||||||
|
wantErr: true,
|
||||||
|
errContains: "does not have a git variant",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "git skips deps without a distinct git package",
|
||||||
|
gitAll: true,
|
||||||
|
noGitVariant: []string{"niri"},
|
||||||
|
wantGit: []string{"quickshell", "hyprland", "dms (DankMaterialShell)"},
|
||||||
|
wantStable: []string{"niri", "ghostty"},
|
||||||
|
wantReinstall: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
r := NewRunner(Config{GitAll: tt.gitAll, GitDeps: tt.gitDeps})
|
||||||
|
d := freshDeps()
|
||||||
|
|
||||||
|
hasGitVariant := func(name string) bool {
|
||||||
|
for _, n := range tt.noGitVariant {
|
||||||
|
if n == name {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
reinstall, err := r.applyGitVariants(d, hasGitVariant)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Fatalf("applyGitVariants() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
if tt.wantErr {
|
||||||
|
if tt.errContains != "" && !strings.Contains(err.Error(), tt.errContains) {
|
||||||
|
t.Errorf("error %q does not contain %q", err.Error(), tt.errContains)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
variants := make(map[string]deps.PackageVariant)
|
||||||
|
for _, dep := range d {
|
||||||
|
variants[dep.Name] = dep.Variant
|
||||||
|
}
|
||||||
|
for _, name := range tt.wantGit {
|
||||||
|
if variants[name] != deps.VariantGit {
|
||||||
|
t.Errorf("expected %q to be VariantGit", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, name := range tt.wantStable {
|
||||||
|
if variants[name] != deps.VariantStable {
|
||||||
|
t.Errorf("expected %q to stay VariantStable", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(reinstall) != len(tt.wantReinstall) {
|
||||||
|
t.Errorf("reinstall = %v, want %v", reinstall, tt.wantReinstall)
|
||||||
|
}
|
||||||
|
for _, name := range tt.wantReinstall {
|
||||||
|
if !reinstall[name] {
|
||||||
|
t.Errorf("expected %q to be marked for reinstall", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApplyPrivescTool(t *testing.T) {
|
||||||
|
if err := NewRunner(Config{}).applyPrivescTool(); err != nil {
|
||||||
|
t.Errorf("empty PrivescTool should be a no-op, got error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := NewRunner(Config{PrivescTool: "pkexec"}).applyPrivescTool()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error for unsupported privesc tool, got nil")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "--privesc") {
|
||||||
|
t.Errorf("error %q does not mention --privesc", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
Copyright (C) 2017 Alec Thomas
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
|
// formatters/svg/font_liberation_mono.go
|
||||||
|
|
||||||
|
Digitized data copyright (c) 2010 Google Corporation
|
||||||
|
with Reserved Font Arimo, Tinos and Cousine.
|
||||||
|
Copyright (c) 2012 Red Hat, Inc.
|
||||||
|
with Reserved Font Name Liberation.
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
https://openfontlicense.org
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Package highlight replaces chroma's lexers and styles packages, whose
|
||||||
|
// package init parses every embedded XML definition (~10ms on every dms
|
||||||
|
// invocation). The copies under lexers/ and styles/ are chroma's own files,
|
||||||
|
// refreshed by go generate; only lexers/lexers.go and styles/styles.go are
|
||||||
|
// ours and build the registries on first use.
|
||||||
|
package highlight
|
||||||
|
|
||||||
|
//go:generate sh generate.sh
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Refreshes the chroma lexer and style copies from the module version in go.mod.
|
||||||
|
set -e
|
||||||
|
d=$(go list -m -f '{{.Dir}}' github.com/alecthomas/chroma/v2)
|
||||||
|
rm -f lexers/embedded/*.xml styles/*.xml
|
||||||
|
find lexers -maxdepth 1 -name '*.go' ! -name lexers.go -delete
|
||||||
|
install -m 0644 "$d"/lexers/embedded/*.xml lexers/embedded/
|
||||||
|
install -m 0644 "$d"/styles/*.xml styles/
|
||||||
|
install -m 0644 "$d"/COPYING COPYING
|
||||||
|
for f in "$d"/lexers/*.go; do
|
||||||
|
case $(basename "$f") in
|
||||||
|
lexers.go | dns.go | mysql.go | zed.go | *_test.go) continue ;;
|
||||||
|
esac
|
||||||
|
install -m 0644 "$f" lexers/
|
||||||
|
done
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package highlight
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/highlight/styles"
|
||||||
|
"github.com/alecthomas/chroma/v2/formatters/html"
|
||||||
|
"github.com/yuin/goldmark"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMarkdown(t *testing.T) {
|
||||||
|
md := goldmark.New(goldmark.WithExtensions(Markdown{Style: styles.Get("monokai"), Formatter: html.New(html.WithClasses(false))}))
|
||||||
|
cases := []struct{ in, want string }{
|
||||||
|
{"```go\nfunc main() {}\n```\n", `style="color:`},
|
||||||
|
{"```nope\na < b\n```\n", `<pre><code class="language-nope">a < b`},
|
||||||
|
{"```\nplain\n```\n", `<pre><code>plain`},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
var out bytes.Buffer
|
||||||
|
if err := md.Convert([]byte(tc.in), &out); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(out.String(), tc.want) {
|
||||||
|
t.Errorf("%q rendered %q, want %q", tc.in, out.String(), tc.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,275 @@
|
|||||||
|
package lexers
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/alecthomas/chroma/v2" // nolint
|
||||||
|
)
|
||||||
|
|
||||||
|
// Matcher token stub for docs, or
|
||||||
|
// Named matcher: @name, or
|
||||||
|
// Path matcher: /foo, or
|
||||||
|
// Wildcard path matcher: *
|
||||||
|
// nolint: gosec
|
||||||
|
var caddyfileMatcherTokenRegexp = `(\[\<matcher\>\]|@[^\s]+|/[^\s]+|\*)`
|
||||||
|
|
||||||
|
// Comment at start of line, or
|
||||||
|
// Comment preceded by whitespace
|
||||||
|
var caddyfileCommentRegexp = `(^|\s+)#.*\n`
|
||||||
|
|
||||||
|
// caddyfileCommon are the rules common to both of the lexer variants
|
||||||
|
func caddyfileCommonRules() Rules {
|
||||||
|
return Rules{
|
||||||
|
"site_block_common": {
|
||||||
|
Include("site_body"),
|
||||||
|
// Any other directive
|
||||||
|
{Pattern: `[^\s#]+`, Type: Keyword, Mutator: Push("directive")},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"site_body": {
|
||||||
|
// Import keyword
|
||||||
|
{Pattern: `\b(import|invoke)\b( [^\s#]+)`, Type: ByGroups(Keyword, Text), Mutator: Push("subdirective")},
|
||||||
|
// Matcher definition
|
||||||
|
{Pattern: `@[^\s]+(?=\s)`, Type: NameDecorator, Mutator: Push("matcher")},
|
||||||
|
// Matcher token stub for docs
|
||||||
|
{Pattern: `\[\<matcher\>\]`, Type: NameDecorator, Mutator: Push("matcher")},
|
||||||
|
// These cannot have matchers but may have things that look like
|
||||||
|
// matchers in their arguments, so we just parse as a subdirective.
|
||||||
|
{Pattern: `\b(try_files|tls|log|bind)\b`, Type: Keyword, Mutator: Push("subdirective")},
|
||||||
|
// These are special, they can nest more directives
|
||||||
|
{Pattern: `\b(handle_errors|handle_path|handle_response|replace_status|handle|route)\b`, Type: Keyword, Mutator: Push("nested_directive")},
|
||||||
|
// uri directive has special syntax
|
||||||
|
{Pattern: `\b(uri)\b`, Type: Keyword, Mutator: Push("uri_directive")},
|
||||||
|
},
|
||||||
|
"matcher": {
|
||||||
|
{Pattern: `\{`, Type: Punctuation, Mutator: Push("block")},
|
||||||
|
// Not can be one-liner
|
||||||
|
{Pattern: `not`, Type: Keyword, Mutator: Push("deep_not_matcher")},
|
||||||
|
// Heredoc for CEL expression
|
||||||
|
Include("heredoc"),
|
||||||
|
// Backtick for CEL expression
|
||||||
|
{Pattern: "`", Type: StringBacktick, Mutator: Push("backticks")},
|
||||||
|
// Any other same-line matcher
|
||||||
|
{Pattern: `[^\s#]+`, Type: Keyword, Mutator: Push("arguments")},
|
||||||
|
// Terminators
|
||||||
|
{Pattern: `\s*\n`, Type: Text, Mutator: Pop(1)},
|
||||||
|
{Pattern: `\}`, Type: Punctuation, Mutator: Pop(1)},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"block": {
|
||||||
|
{Pattern: `\}`, Type: Punctuation, Mutator: Pop(2)},
|
||||||
|
// Using double quotes doesn't stop at spaces
|
||||||
|
{Pattern: `"`, Type: StringDouble, Mutator: Push("double_quotes")},
|
||||||
|
// Using backticks doesn't stop at spaces
|
||||||
|
{Pattern: "`", Type: StringBacktick, Mutator: Push("backticks")},
|
||||||
|
// Not can be one-liner
|
||||||
|
{Pattern: `not`, Type: Keyword, Mutator: Push("not_matcher")},
|
||||||
|
// Directives & matcher definitions
|
||||||
|
Include("site_body"),
|
||||||
|
// Any directive
|
||||||
|
{Pattern: `[^\s#]+`, Type: Keyword, Mutator: Push("subdirective")},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"nested_block": {
|
||||||
|
{Pattern: `\}`, Type: Punctuation, Mutator: Pop(2)},
|
||||||
|
// Using double quotes doesn't stop at spaces
|
||||||
|
{Pattern: `"`, Type: StringDouble, Mutator: Push("double_quotes")},
|
||||||
|
// Using backticks doesn't stop at spaces
|
||||||
|
{Pattern: "`", Type: StringBacktick, Mutator: Push("backticks")},
|
||||||
|
// Not can be one-liner
|
||||||
|
{Pattern: `not`, Type: Keyword, Mutator: Push("not_matcher")},
|
||||||
|
// Directives & matcher definitions
|
||||||
|
Include("site_body"),
|
||||||
|
// Any other subdirective
|
||||||
|
{Pattern: `[^\s#]+`, Type: Keyword, Mutator: Push("directive")},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"not_matcher": {
|
||||||
|
{Pattern: `\}`, Type: Punctuation, Mutator: Pop(2)},
|
||||||
|
{Pattern: `\{(?=\s)`, Type: Punctuation, Mutator: Push("block")},
|
||||||
|
{Pattern: `[^\s#]+`, Type: Keyword, Mutator: Push("arguments")},
|
||||||
|
{Pattern: `\s+`, Type: Text, Mutator: nil},
|
||||||
|
},
|
||||||
|
"deep_not_matcher": {
|
||||||
|
{Pattern: `\}`, Type: Punctuation, Mutator: Pop(2)},
|
||||||
|
{Pattern: `\{(?=\s)`, Type: Punctuation, Mutator: Push("block")},
|
||||||
|
{Pattern: `[^\s#]+`, Type: Keyword, Mutator: Push("deep_subdirective")},
|
||||||
|
{Pattern: `\s+`, Type: Text, Mutator: nil},
|
||||||
|
},
|
||||||
|
"directive": {
|
||||||
|
{Pattern: `\{(?=\s)`, Type: Punctuation, Mutator: Push("block")},
|
||||||
|
{Pattern: caddyfileMatcherTokenRegexp, Type: NameDecorator, Mutator: Push("arguments")},
|
||||||
|
{Pattern: caddyfileCommentRegexp, Type: CommentSingle, Mutator: Pop(1)},
|
||||||
|
{Pattern: `\s*\n`, Type: Text, Mutator: Pop(1)},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"nested_directive": {
|
||||||
|
{Pattern: `\{(?=\s)`, Type: Punctuation, Mutator: Push("nested_block")},
|
||||||
|
{Pattern: caddyfileMatcherTokenRegexp, Type: NameDecorator, Mutator: Push("nested_arguments")},
|
||||||
|
{Pattern: caddyfileCommentRegexp, Type: CommentSingle, Mutator: Pop(1)},
|
||||||
|
{Pattern: `\s*\n`, Type: Text, Mutator: Pop(1)},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"subdirective": {
|
||||||
|
{Pattern: `\{(?=\s)`, Type: Punctuation, Mutator: Push("block")},
|
||||||
|
{Pattern: caddyfileCommentRegexp, Type: CommentSingle, Mutator: Pop(1)},
|
||||||
|
{Pattern: `\s*\n`, Type: Text, Mutator: Pop(1)},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"arguments": {
|
||||||
|
{Pattern: `\{(?=\s)`, Type: Punctuation, Mutator: Push("block")},
|
||||||
|
{Pattern: caddyfileCommentRegexp, Type: CommentSingle, Mutator: Pop(2)},
|
||||||
|
{Pattern: `\\\n`, Type: Text, Mutator: nil}, // Skip escaped newlines
|
||||||
|
{Pattern: `\s*\n`, Type: Text, Mutator: Pop(2)},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"nested_arguments": {
|
||||||
|
{Pattern: `\{(?=\s)`, Type: Punctuation, Mutator: Push("nested_block")},
|
||||||
|
{Pattern: caddyfileCommentRegexp, Type: CommentSingle, Mutator: Pop(2)},
|
||||||
|
{Pattern: `\\\n`, Type: Text, Mutator: nil}, // Skip escaped newlines
|
||||||
|
{Pattern: `\s*\n`, Type: Text, Mutator: Pop(2)},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"deep_subdirective": {
|
||||||
|
{Pattern: `\{(?=\s)`, Type: Punctuation, Mutator: Push("block")},
|
||||||
|
{Pattern: caddyfileCommentRegexp, Type: CommentSingle, Mutator: Pop(3)},
|
||||||
|
{Pattern: `\s*\n`, Type: Text, Mutator: Pop(3)},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"uri_directive": {
|
||||||
|
{Pattern: `\{(?=\s)`, Type: Punctuation, Mutator: Push("block")},
|
||||||
|
{Pattern: caddyfileMatcherTokenRegexp, Type: NameDecorator, Mutator: nil},
|
||||||
|
{Pattern: `(strip_prefix|strip_suffix|replace|path_regexp)`, Type: NameConstant, Mutator: Push("arguments")},
|
||||||
|
{Pattern: caddyfileCommentRegexp, Type: CommentSingle, Mutator: Pop(1)},
|
||||||
|
{Pattern: `\s*\n`, Type: Text, Mutator: Pop(1)},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"double_quotes": {
|
||||||
|
Include("placeholder"),
|
||||||
|
{Pattern: `\\"`, Type: StringDouble, Mutator: nil},
|
||||||
|
{Pattern: `[^"]`, Type: StringDouble, Mutator: nil},
|
||||||
|
{Pattern: `"`, Type: StringDouble, Mutator: Pop(1)},
|
||||||
|
},
|
||||||
|
"backticks": {
|
||||||
|
Include("placeholder"),
|
||||||
|
{Pattern: "\\\\`", Type: StringBacktick, Mutator: nil},
|
||||||
|
{Pattern: "[^`]", Type: StringBacktick, Mutator: nil},
|
||||||
|
{Pattern: "`", Type: StringBacktick, Mutator: Pop(1)},
|
||||||
|
},
|
||||||
|
"optional": {
|
||||||
|
// Docs syntax for showing optional parts with [ ]
|
||||||
|
{Pattern: `\[`, Type: Punctuation, Mutator: Push("optional")},
|
||||||
|
Include("name_constants"),
|
||||||
|
{Pattern: `\|`, Type: Punctuation, Mutator: nil},
|
||||||
|
{Pattern: `[^\[\]\|]+`, Type: String, Mutator: nil},
|
||||||
|
{Pattern: `\]`, Type: Punctuation, Mutator: Pop(1)},
|
||||||
|
},
|
||||||
|
"heredoc": {
|
||||||
|
{Pattern: `(<<([a-zA-Z0-9_-]+))(\n(.*|\n)*)(\s*)(\2)`, Type: ByGroups(StringHeredoc, nil, String, String, String, StringHeredoc), Mutator: nil},
|
||||||
|
},
|
||||||
|
"name_constants": {
|
||||||
|
{Pattern: `\b(most_recently_modified|largest_size|smallest_size|first_exist|internal|disable_redirects|ignore_loaded_certs|disable_certs|private_ranges|first|last|before|after|on|off)\b(\||(?=\]|\s|$))`, Type: ByGroups(NameConstant, Punctuation), Mutator: nil},
|
||||||
|
},
|
||||||
|
"placeholder": {
|
||||||
|
// Placeholder with dots, colon for default value, brackets for args[0:]
|
||||||
|
{Pattern: `\{[\w+.\[\]\:\$-]+\}`, Type: StringEscape, Mutator: nil},
|
||||||
|
// Handle opening brackets with no matching closing one
|
||||||
|
{Pattern: `\{[^\}\s]*\b`, Type: String, Mutator: nil},
|
||||||
|
},
|
||||||
|
"base": {
|
||||||
|
{Pattern: caddyfileCommentRegexp, Type: CommentSingle, Mutator: nil},
|
||||||
|
{Pattern: `\[\<matcher\>\]`, Type: NameDecorator, Mutator: nil},
|
||||||
|
Include("name_constants"),
|
||||||
|
Include("heredoc"),
|
||||||
|
{Pattern: `(https?://)?([a-z0-9.-]+)(:)([0-9]+)([^\s]*)`, Type: ByGroups(Name, Name, Punctuation, NumberInteger, Name), Mutator: nil},
|
||||||
|
{Pattern: `\[`, Type: Punctuation, Mutator: Push("optional")},
|
||||||
|
{Pattern: "`", Type: StringBacktick, Mutator: Push("backticks")},
|
||||||
|
{Pattern: `"`, Type: StringDouble, Mutator: Push("double_quotes")},
|
||||||
|
Include("placeholder"),
|
||||||
|
{Pattern: `[a-z-]+/[a-z-+]+`, Type: String, Mutator: nil},
|
||||||
|
{Pattern: `[0-9]+([smhdk]|ns|us|µs|ms)?\b`, Type: NumberInteger, Mutator: nil},
|
||||||
|
{Pattern: `[^\s\n#\{]+`, Type: String, Mutator: nil},
|
||||||
|
{Pattern: `/[^\s#]*`, Type: Name, Mutator: nil},
|
||||||
|
{Pattern: `\s+`, Type: Text, Mutator: nil},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Caddyfile lexer.
|
||||||
|
var Caddyfile = Register(MustNewLexer(
|
||||||
|
&Config{
|
||||||
|
Name: "Caddyfile",
|
||||||
|
Aliases: []string{"caddyfile", "caddy"},
|
||||||
|
Filenames: []string{"Caddyfile*"},
|
||||||
|
MimeTypes: []string{},
|
||||||
|
},
|
||||||
|
caddyfileRules,
|
||||||
|
))
|
||||||
|
|
||||||
|
func caddyfileRules() Rules {
|
||||||
|
return Rules{
|
||||||
|
"root": {
|
||||||
|
{Pattern: caddyfileCommentRegexp, Type: CommentSingle, Mutator: nil},
|
||||||
|
// Global options block
|
||||||
|
{Pattern: `^\s*(\{)\s*$`, Type: ByGroups(Punctuation), Mutator: Push("globals")},
|
||||||
|
// Top level import
|
||||||
|
{Pattern: `(import)(\s+)([^\s]+)`, Type: ByGroups(Keyword, Text, NameVariableMagic), Mutator: nil},
|
||||||
|
// Snippets
|
||||||
|
{Pattern: `(&?\([^\s#]+\))(\s*)(\{)`, Type: ByGroups(NameVariableAnonymous, Text, Punctuation), Mutator: Push("snippet")},
|
||||||
|
// Site label
|
||||||
|
{Pattern: `[^#{(\s,]+`, Type: GenericHeading, Mutator: Push("label")},
|
||||||
|
// Site label with placeholder
|
||||||
|
{Pattern: `\{[\w+.\[\]\:\$-]+\}`, Type: StringEscape, Mutator: Push("label")},
|
||||||
|
{Pattern: `\s+`, Type: Text, Mutator: nil},
|
||||||
|
},
|
||||||
|
"globals": {
|
||||||
|
{Pattern: `\}`, Type: Punctuation, Mutator: Pop(1)},
|
||||||
|
// Global options are parsed as subdirectives (no matcher)
|
||||||
|
{Pattern: `[^\s#]+`, Type: Keyword, Mutator: Push("subdirective")},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"snippet": {
|
||||||
|
{Pattern: `\}`, Type: Punctuation, Mutator: Pop(1)},
|
||||||
|
Include("site_body"),
|
||||||
|
// Any other directive
|
||||||
|
{Pattern: `[^\s#]+`, Type: Keyword, Mutator: Push("directive")},
|
||||||
|
Include("base"),
|
||||||
|
},
|
||||||
|
"label": {
|
||||||
|
// Allow multiple labels, comma separated, newlines after
|
||||||
|
// a comma means another label is coming
|
||||||
|
{Pattern: `,\s*\n?`, Type: Text, Mutator: nil},
|
||||||
|
{Pattern: ` `, Type: Text, Mutator: nil},
|
||||||
|
// Site label with placeholder
|
||||||
|
Include("placeholder"),
|
||||||
|
// Site label
|
||||||
|
{Pattern: `[^#{(\s,]+`, Type: GenericHeading, Mutator: nil},
|
||||||
|
// Comment after non-block label (hack because comments end in \n)
|
||||||
|
{Pattern: `#.*\n`, Type: CommentSingle, Mutator: Push("site_block")},
|
||||||
|
// Note: if \n, we'll never pop out of the site_block, it's valid
|
||||||
|
{Pattern: `\{(?=\s)|\n`, Type: Punctuation, Mutator: Push("site_block")},
|
||||||
|
},
|
||||||
|
"site_block": {
|
||||||
|
{Pattern: `\}`, Type: Punctuation, Mutator: Pop(2)},
|
||||||
|
Include("site_block_common"),
|
||||||
|
},
|
||||||
|
}.Merge(caddyfileCommonRules())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Caddyfile directive-only lexer.
|
||||||
|
var CaddyfileDirectives = Register(MustNewLexer(
|
||||||
|
&Config{
|
||||||
|
Name: "Caddyfile Directives",
|
||||||
|
Aliases: []string{"caddyfile-directives", "caddyfile-d", "caddy-d"},
|
||||||
|
Filenames: []string{},
|
||||||
|
MimeTypes: []string{},
|
||||||
|
},
|
||||||
|
caddyfileDirectivesRules,
|
||||||
|
))
|
||||||
|
|
||||||
|
func caddyfileDirectivesRules() Rules {
|
||||||
|
return Rules{
|
||||||
|
// Same as "site_block" in Caddyfile
|
||||||
|
"root": {
|
||||||
|
Include("site_block_common"),
|
||||||
|
},
|
||||||
|
}.Merge(caddyfileCommonRules())
|
||||||
|
}
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
package lexers
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/alecthomas/chroma/v2" // nolint
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
clBuiltinFunctions = []string{
|
||||||
|
"<", "<=", "=", ">", ">=", "-", "/", "/=", "*", "+", "1-", "1+",
|
||||||
|
"abort", "abs", "acons", "acos", "acosh", "add-method", "adjoin",
|
||||||
|
"adjustable-array-p", "adjust-array", "allocate-instance",
|
||||||
|
"alpha-char-p", "alphanumericp", "append", "apply", "apropos",
|
||||||
|
"apropos-list", "aref", "arithmetic-error-operands",
|
||||||
|
"arithmetic-error-operation", "array-dimension", "array-dimensions",
|
||||||
|
"array-displacement", "array-element-type", "array-has-fill-pointer-p",
|
||||||
|
"array-in-bounds-p", "arrayp", "array-rank", "array-row-major-index",
|
||||||
|
"array-total-size", "ash", "asin", "asinh", "assoc", "assoc-if",
|
||||||
|
"assoc-if-not", "atan", "atanh", "atom", "bit", "bit-and", "bit-andc1",
|
||||||
|
"bit-andc2", "bit-eqv", "bit-ior", "bit-nand", "bit-nor", "bit-not",
|
||||||
|
"bit-orc1", "bit-orc2", "bit-vector-p", "bit-xor", "boole",
|
||||||
|
"both-case-p", "boundp", "break", "broadcast-stream-streams",
|
||||||
|
"butlast", "byte", "byte-position", "byte-size", "caaaar", "caaadr",
|
||||||
|
"caaar", "caadar", "caaddr", "caadr", "caar", "cadaar", "cadadr",
|
||||||
|
"cadar", "caddar", "cadddr", "caddr", "cadr", "call-next-method", "car",
|
||||||
|
"cdaaar", "cdaadr", "cdaar", "cdadar", "cdaddr", "cdadr", "cdar",
|
||||||
|
"cddaar", "cddadr", "cddar", "cdddar", "cddddr", "cdddr", "cddr", "cdr",
|
||||||
|
"ceiling", "cell-error-name", "cerror", "change-class", "char", "char<",
|
||||||
|
"char<=", "char=", "char>", "char>=", "char/=", "character",
|
||||||
|
"characterp", "char-code", "char-downcase", "char-equal",
|
||||||
|
"char-greaterp", "char-int", "char-lessp", "char-name",
|
||||||
|
"char-not-equal", "char-not-greaterp", "char-not-lessp", "char-upcase",
|
||||||
|
"cis", "class-name", "class-of", "clear-input", "clear-output",
|
||||||
|
"close", "clrhash", "code-char", "coerce", "compile",
|
||||||
|
"compiled-function-p", "compile-file", "compile-file-pathname",
|
||||||
|
"compiler-macro-function", "complement", "complex", "complexp",
|
||||||
|
"compute-applicable-methods", "compute-restarts", "concatenate",
|
||||||
|
"concatenated-stream-streams", "conjugate", "cons", "consp",
|
||||||
|
"constantly", "constantp", "continue", "copy-alist", "copy-list",
|
||||||
|
"copy-pprint-dispatch", "copy-readtable", "copy-seq", "copy-structure",
|
||||||
|
"copy-symbol", "copy-tree", "cos", "cosh", "count", "count-if",
|
||||||
|
"count-if-not", "decode-float", "decode-universal-time", "delete",
|
||||||
|
"delete-duplicates", "delete-file", "delete-if", "delete-if-not",
|
||||||
|
"delete-package", "denominator", "deposit-field", "describe",
|
||||||
|
"describe-object", "digit-char", "digit-char-p", "directory",
|
||||||
|
"directory-namestring", "disassemble", "documentation", "dpb",
|
||||||
|
"dribble", "echo-stream-input-stream", "echo-stream-output-stream",
|
||||||
|
"ed", "eighth", "elt", "encode-universal-time", "endp",
|
||||||
|
"enough-namestring", "ensure-directories-exist",
|
||||||
|
"ensure-generic-function", "eq", "eql", "equal", "equalp", "error",
|
||||||
|
"eval", "evenp", "every", "exp", "export", "expt", "fboundp",
|
||||||
|
"fceiling", "fdefinition", "ffloor", "fifth", "file-author",
|
||||||
|
"file-error-pathname", "file-length", "file-namestring",
|
||||||
|
"file-position", "file-string-length", "file-write-date",
|
||||||
|
"fill", "fill-pointer", "find", "find-all-symbols", "find-class",
|
||||||
|
"find-if", "find-if-not", "find-method", "find-package", "find-restart",
|
||||||
|
"find-symbol", "finish-output", "first", "float", "float-digits",
|
||||||
|
"floatp", "float-precision", "float-radix", "float-sign", "floor",
|
||||||
|
"fmakunbound", "force-output", "format", "fourth", "fresh-line",
|
||||||
|
"fround", "ftruncate", "funcall", "function-keywords",
|
||||||
|
"function-lambda-expression", "functionp", "gcd", "gensym", "gentemp",
|
||||||
|
"get", "get-decoded-time", "get-dispatch-macro-character", "getf",
|
||||||
|
"gethash", "get-internal-real-time", "get-internal-run-time",
|
||||||
|
"get-macro-character", "get-output-stream-string", "get-properties",
|
||||||
|
"get-setf-expansion", "get-universal-time", "graphic-char-p",
|
||||||
|
"hash-table-count", "hash-table-p", "hash-table-rehash-size",
|
||||||
|
"hash-table-rehash-threshold", "hash-table-size", "hash-table-test",
|
||||||
|
"host-namestring", "identity", "imagpart", "import",
|
||||||
|
"initialize-instance", "input-stream-p", "inspect",
|
||||||
|
"integer-decode-float", "integer-length", "integerp",
|
||||||
|
"interactive-stream-p", "intern", "intersection",
|
||||||
|
"invalid-method-error", "invoke-debugger", "invoke-restart",
|
||||||
|
"invoke-restart-interactively", "isqrt", "keywordp", "last", "lcm",
|
||||||
|
"ldb", "ldb-test", "ldiff", "length", "lisp-implementation-type",
|
||||||
|
"lisp-implementation-version", "list", "list*", "list-all-packages",
|
||||||
|
"listen", "list-length", "listp", "load",
|
||||||
|
"load-logical-pathname-translations", "log", "logand", "logandc1",
|
||||||
|
"logandc2", "logbitp", "logcount", "logeqv", "logical-pathname",
|
||||||
|
"logical-pathname-translations", "logior", "lognand", "lognor",
|
||||||
|
"lognot", "logorc1", "logorc2", "logtest", "logxor", "long-site-name",
|
||||||
|
"lower-case-p", "machine-instance", "machine-type", "machine-version",
|
||||||
|
"macroexpand", "macroexpand-1", "macro-function", "make-array",
|
||||||
|
"make-broadcast-stream", "make-concatenated-stream", "make-condition",
|
||||||
|
"make-dispatch-macro-character", "make-echo-stream", "make-hash-table",
|
||||||
|
"make-instance", "make-instances-obsolete", "make-list",
|
||||||
|
"make-load-form", "make-load-form-saving-slots", "make-package",
|
||||||
|
"make-pathname", "make-random-state", "make-sequence", "make-string",
|
||||||
|
"make-string-input-stream", "make-string-output-stream", "make-symbol",
|
||||||
|
"make-synonym-stream", "make-two-way-stream", "makunbound", "map",
|
||||||
|
"mapc", "mapcan", "mapcar", "mapcon", "maphash", "map-into", "mapl",
|
||||||
|
"maplist", "mask-field", "max", "member", "member-if", "member-if-not",
|
||||||
|
"merge", "merge-pathnames", "method-combination-error",
|
||||||
|
"method-qualifiers", "min", "minusp", "mismatch", "mod",
|
||||||
|
"muffle-warning", "name-char", "namestring", "nbutlast", "nconc",
|
||||||
|
"next-method-p", "nintersection", "ninth", "no-applicable-method",
|
||||||
|
"no-next-method", "not", "notany", "notevery", "nreconc", "nreverse",
|
||||||
|
"nset-difference", "nset-exclusive-or", "nstring-capitalize",
|
||||||
|
"nstring-downcase", "nstring-upcase", "nsublis", "nsubst", "nsubst-if",
|
||||||
|
"nsubst-if-not", "nsubstitute", "nsubstitute-if", "nsubstitute-if-not",
|
||||||
|
"nth", "nthcdr", "null", "numberp", "numerator", "nunion", "oddp",
|
||||||
|
"open", "open-stream-p", "output-stream-p", "package-error-package",
|
||||||
|
"package-name", "package-nicknames", "packagep",
|
||||||
|
"package-shadowing-symbols", "package-used-by-list", "package-use-list",
|
||||||
|
"pairlis", "parse-integer", "parse-namestring", "pathname",
|
||||||
|
"pathname-device", "pathname-directory", "pathname-host",
|
||||||
|
"pathname-match-p", "pathname-name", "pathnamep", "pathname-type",
|
||||||
|
"pathname-version", "peek-char", "phase", "plusp", "position",
|
||||||
|
"position-if", "position-if-not", "pprint", "pprint-dispatch",
|
||||||
|
"pprint-fill", "pprint-indent", "pprint-linear", "pprint-newline",
|
||||||
|
"pprint-tab", "pprint-tabular", "prin1", "prin1-to-string", "princ",
|
||||||
|
"princ-to-string", "print", "print-object", "probe-file", "proclaim",
|
||||||
|
"provide", "random", "random-state-p", "rassoc", "rassoc-if",
|
||||||
|
"rassoc-if-not", "rational", "rationalize", "rationalp", "read",
|
||||||
|
"read-byte", "read-char", "read-char-no-hang", "read-delimited-list",
|
||||||
|
"read-from-string", "read-line", "read-preserving-whitespace",
|
||||||
|
"read-sequence", "readtable-case", "readtablep", "realp", "realpart",
|
||||||
|
"reduce", "reinitialize-instance", "rem", "remhash", "remove",
|
||||||
|
"remove-duplicates", "remove-if", "remove-if-not", "remove-method",
|
||||||
|
"remprop", "rename-file", "rename-package", "replace", "require",
|
||||||
|
"rest", "restart-name", "revappend", "reverse", "room", "round",
|
||||||
|
"row-major-aref", "rplaca", "rplacd", "sbit", "scale-float", "schar",
|
||||||
|
"search", "second", "set", "set-difference",
|
||||||
|
"set-dispatch-macro-character", "set-exclusive-or",
|
||||||
|
"set-macro-character", "set-pprint-dispatch", "set-syntax-from-char",
|
||||||
|
"seventh", "shadow", "shadowing-import", "shared-initialize",
|
||||||
|
"short-site-name", "signal", "signum", "simple-bit-vector-p",
|
||||||
|
"simple-condition-format-arguments", "simple-condition-format-control",
|
||||||
|
"simple-string-p", "simple-vector-p", "sin", "sinh", "sixth", "sleep",
|
||||||
|
"slot-boundp", "slot-exists-p", "slot-makunbound", "slot-missing",
|
||||||
|
"slot-unbound", "slot-value", "software-type", "software-version",
|
||||||
|
"some", "sort", "special-operator-p", "sqrt", "stable-sort",
|
||||||
|
"standard-char-p", "store-value", "stream-element-type",
|
||||||
|
"stream-error-stream", "stream-external-format", "streamp", "string",
|
||||||
|
"string<", "string<=", "string=", "string>", "string>=", "string/=",
|
||||||
|
"string-capitalize", "string-downcase", "string-equal",
|
||||||
|
"string-greaterp", "string-left-trim", "string-lessp",
|
||||||
|
"string-not-equal", "string-not-greaterp", "string-not-lessp",
|
||||||
|
"stringp", "string-right-trim", "string-trim", "string-upcase",
|
||||||
|
"sublis", "subseq", "subsetp", "subst", "subst-if", "subst-if-not",
|
||||||
|
"substitute", "substitute-if", "substitute-if-not", "subtypep", "svref",
|
||||||
|
"sxhash", "symbol-function", "symbol-name", "symbolp", "symbol-package",
|
||||||
|
"symbol-plist", "symbol-value", "synonym-stream-symbol", "syntax:",
|
||||||
|
"tailp", "tan", "tanh", "tenth", "terpri", "third",
|
||||||
|
"translate-logical-pathname", "translate-pathname", "tree-equal",
|
||||||
|
"truename", "truncate", "two-way-stream-input-stream",
|
||||||
|
"two-way-stream-output-stream", "type-error-datum",
|
||||||
|
"type-error-expected-type", "type-of", "typep", "unbound-slot-instance",
|
||||||
|
"unexport", "unintern", "union", "unread-char", "unuse-package",
|
||||||
|
"update-instance-for-different-class",
|
||||||
|
"update-instance-for-redefined-class", "upgraded-array-element-type",
|
||||||
|
"upgraded-complex-part-type", "upper-case-p", "use-package",
|
||||||
|
"user-homedir-pathname", "use-value", "values", "values-list", "vector",
|
||||||
|
"vectorp", "vector-pop", "vector-push", "vector-push-extend", "warn",
|
||||||
|
"wild-pathname-p", "write", "write-byte", "write-char", "write-line",
|
||||||
|
"write-sequence", "write-string", "write-to-string", "yes-or-no-p",
|
||||||
|
"y-or-n-p", "zerop",
|
||||||
|
}
|
||||||
|
|
||||||
|
clSpecialForms = []string{
|
||||||
|
"block", "catch", "declare", "eval-when", "flet", "function", "go", "if",
|
||||||
|
"labels", "lambda", "let", "let*", "load-time-value", "locally", "macrolet",
|
||||||
|
"multiple-value-call", "multiple-value-prog1", "progn", "progv", "quote",
|
||||||
|
"return-from", "setq", "symbol-macrolet", "tagbody", "the", "throw",
|
||||||
|
"unwind-protect",
|
||||||
|
}
|
||||||
|
|
||||||
|
clMacros = []string{
|
||||||
|
"and", "assert", "call-method", "case", "ccase", "check-type", "cond",
|
||||||
|
"ctypecase", "decf", "declaim", "defclass", "defconstant", "defgeneric",
|
||||||
|
"define-compiler-macro", "define-condition", "define-method-combination",
|
||||||
|
"define-modify-macro", "define-setf-expander", "define-symbol-macro",
|
||||||
|
"defmacro", "defmethod", "defpackage", "defparameter", "defsetf",
|
||||||
|
"defstruct", "deftype", "defun", "defvar", "destructuring-bind", "do",
|
||||||
|
"do*", "do-all-symbols", "do-external-symbols", "dolist", "do-symbols",
|
||||||
|
"dotimes", "ecase", "etypecase", "formatter", "handler-bind",
|
||||||
|
"handler-case", "ignore-errors", "incf", "in-package", "lambda", "loop",
|
||||||
|
"loop-finish", "make-method", "multiple-value-bind", "multiple-value-list",
|
||||||
|
"multiple-value-setq", "nth-value", "or", "pop",
|
||||||
|
"pprint-exit-if-list-exhausted", "pprint-logical-block", "pprint-pop",
|
||||||
|
"print-unreadable-object", "prog", "prog*", "prog1", "prog2", "psetf",
|
||||||
|
"psetq", "push", "pushnew", "remf", "restart-bind", "restart-case",
|
||||||
|
"return", "rotatef", "setf", "shiftf", "step", "time", "trace", "typecase",
|
||||||
|
"unless", "untrace", "when", "with-accessors", "with-compilation-unit",
|
||||||
|
"with-condition-restarts", "with-hash-table-iterator",
|
||||||
|
"with-input-from-string", "with-open-file", "with-open-stream",
|
||||||
|
"with-output-to-string", "with-package-iterator", "with-simple-restart",
|
||||||
|
"with-slots", "with-standard-io-syntax",
|
||||||
|
}
|
||||||
|
|
||||||
|
clLambdaListKeywords = []string{
|
||||||
|
"&allow-other-keys", "&aux", "&body", "&environment", "&key", "&optional",
|
||||||
|
"&rest", "&whole",
|
||||||
|
}
|
||||||
|
|
||||||
|
clDeclarations = []string{
|
||||||
|
"dynamic-extent", "ignore", "optimize", "ftype", "inline", "special",
|
||||||
|
"ignorable", "notinline", "type",
|
||||||
|
}
|
||||||
|
|
||||||
|
clBuiltinTypes = []string{
|
||||||
|
"atom", "boolean", "base-char", "base-string", "bignum", "bit",
|
||||||
|
"compiled-function", "extended-char", "fixnum", "keyword", "nil",
|
||||||
|
"signed-byte", "short-float", "single-float", "double-float", "long-float",
|
||||||
|
"simple-array", "simple-base-string", "simple-bit-vector", "simple-string",
|
||||||
|
"simple-vector", "standard-char", "unsigned-byte",
|
||||||
|
|
||||||
|
// Condition Types
|
||||||
|
"arithmetic-error", "cell-error", "condition", "control-error",
|
||||||
|
"division-by-zero", "end-of-file", "error", "file-error",
|
||||||
|
"floating-point-inexact", "floating-point-overflow",
|
||||||
|
"floating-point-underflow", "floating-point-invalid-operation",
|
||||||
|
"parse-error", "package-error", "print-not-readable", "program-error",
|
||||||
|
"reader-error", "serious-condition", "simple-condition", "simple-error",
|
||||||
|
"simple-type-error", "simple-warning", "stream-error", "storage-condition",
|
||||||
|
"style-warning", "type-error", "unbound-variable", "unbound-slot",
|
||||||
|
"undefined-function", "warning",
|
||||||
|
}
|
||||||
|
|
||||||
|
clBuiltinClasses = []string{
|
||||||
|
"array", "broadcast-stream", "bit-vector", "built-in-class", "character",
|
||||||
|
"class", "complex", "concatenated-stream", "cons", "echo-stream",
|
||||||
|
"file-stream", "float", "function", "generic-function", "hash-table",
|
||||||
|
"integer", "list", "logical-pathname", "method-combination", "method",
|
||||||
|
"null", "number", "package", "pathname", "ratio", "rational", "readtable",
|
||||||
|
"real", "random-state", "restart", "sequence", "standard-class",
|
||||||
|
"standard-generic-function", "standard-method", "standard-object",
|
||||||
|
"string-stream", "stream", "string", "structure-class", "structure-object",
|
||||||
|
"symbol", "synonym-stream", "t", "two-way-stream", "vector",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Common Lisp lexer.
|
||||||
|
var CommonLisp = Register(TypeRemappingLexer(MustNewXMLLexer(
|
||||||
|
embedded,
|
||||||
|
"embedded/common_lisp.xml",
|
||||||
|
), TypeMapping{
|
||||||
|
{NameVariable, NameFunction, clBuiltinFunctions},
|
||||||
|
{NameVariable, Keyword, clSpecialForms},
|
||||||
|
{NameVariable, NameBuiltin, clMacros},
|
||||||
|
{NameVariable, Keyword, clLambdaListKeywords},
|
||||||
|
{NameVariable, Keyword, clDeclarations},
|
||||||
|
{NameVariable, KeywordType, clBuiltinTypes},
|
||||||
|
{NameVariable, NameClass, clBuiltinClasses},
|
||||||
|
}))
|
||||||
@@ -0,0 +1,533 @@
|
|||||||
|
package lexers
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/alecthomas/chroma/v2" // nolint
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
emacsMacros = []string{
|
||||||
|
"atomic-change-group", "case", "block", "cl-block", "cl-callf", "cl-callf2",
|
||||||
|
"cl-case", "cl-decf", "cl-declaim", "cl-declare",
|
||||||
|
"cl-define-compiler-macro", "cl-defmacro", "cl-defstruct",
|
||||||
|
"cl-defsubst", "cl-deftype", "cl-defun", "cl-destructuring-bind",
|
||||||
|
"cl-do", "cl-do*", "cl-do-all-symbols", "cl-do-symbols", "cl-dolist",
|
||||||
|
"cl-dotimes", "cl-ecase", "cl-etypecase", "eval-when", "cl-eval-when", "cl-flet",
|
||||||
|
"cl-flet*", "cl-function", "cl-incf", "cl-labels", "cl-letf",
|
||||||
|
"cl-letf*", "cl-load-time-value", "cl-locally", "cl-loop",
|
||||||
|
"cl-macrolet", "cl-multiple-value-bind", "cl-multiple-value-setq",
|
||||||
|
"cl-progv", "cl-psetf", "cl-psetq", "cl-pushnew", "cl-remf",
|
||||||
|
"cl-return", "cl-return-from", "cl-rotatef", "cl-shiftf",
|
||||||
|
"cl-symbol-macrolet", "cl-tagbody", "cl-the", "cl-typecase",
|
||||||
|
"combine-after-change-calls", "condition-case-unless-debug", "decf",
|
||||||
|
"declaim", "declare", "declare-function", "def-edebug-spec",
|
||||||
|
"defadvice", "defclass", "defcustom", "defface", "defgeneric",
|
||||||
|
"defgroup", "define-advice", "define-alternatives",
|
||||||
|
"define-compiler-macro", "define-derived-mode", "define-generic-mode",
|
||||||
|
"define-global-minor-mode", "define-globalized-minor-mode",
|
||||||
|
"define-minor-mode", "define-modify-macro",
|
||||||
|
"define-obsolete-face-alias", "define-obsolete-function-alias",
|
||||||
|
"define-obsolete-variable-alias", "define-setf-expander",
|
||||||
|
"define-skeleton", "defmacro", "defmethod", "defsetf", "defstruct",
|
||||||
|
"defsubst", "deftheme", "deftype", "defun", "defvar-local",
|
||||||
|
"delay-mode-hooks", "destructuring-bind", "do", "do*",
|
||||||
|
"do-all-symbols", "do-symbols", "dolist", "dont-compile", "dotimes",
|
||||||
|
"dotimes-with-progress-reporter", "ecase", "ert-deftest", "etypecase",
|
||||||
|
"eval-and-compile", "eval-when-compile", "flet", "ignore-errors",
|
||||||
|
"incf", "labels", "lambda", "letrec", "lexical-let", "lexical-let*",
|
||||||
|
"loop", "multiple-value-bind", "multiple-value-setq", "noreturn",
|
||||||
|
"oref", "oref-default", "oset", "oset-default", "pcase",
|
||||||
|
"pcase-defmacro", "pcase-dolist", "pcase-exhaustive", "pcase-let",
|
||||||
|
"pcase-let*", "pop", "psetf", "psetq", "push", "pushnew", "remf",
|
||||||
|
"return", "rotatef", "rx", "save-match-data", "save-selected-window",
|
||||||
|
"save-window-excursion", "setf", "setq-local", "shiftf",
|
||||||
|
"track-mouse", "typecase", "unless", "use-package", "when",
|
||||||
|
"while-no-input", "with-case-table", "with-category-table",
|
||||||
|
"with-coding-priority", "with-current-buffer", "with-demoted-errors",
|
||||||
|
"with-eval-after-load", "with-file-modes", "with-local-quit",
|
||||||
|
"with-output-to-string", "with-output-to-temp-buffer",
|
||||||
|
"with-parsed-tramp-file-name", "with-selected-frame",
|
||||||
|
"with-selected-window", "with-silent-modifications", "with-slots",
|
||||||
|
"with-syntax-table", "with-temp-buffer", "with-temp-file",
|
||||||
|
"with-temp-message", "with-timeout", "with-tramp-connection-property",
|
||||||
|
"with-tramp-file-property", "with-tramp-progress-reporter",
|
||||||
|
"with-wrapper-hook", "load-time-value", "locally", "macrolet", "progv",
|
||||||
|
"return-from",
|
||||||
|
}
|
||||||
|
|
||||||
|
emacsSpecialForms = []string{
|
||||||
|
"and", "catch", "cond", "condition-case", "defconst", "defvar",
|
||||||
|
"function", "if", "interactive", "let", "let*", "or", "prog1",
|
||||||
|
"prog2", "progn", "quote", "save-current-buffer", "save-excursion",
|
||||||
|
"save-restriction", "setq", "setq-default", "subr-arity",
|
||||||
|
"unwind-protect", "while",
|
||||||
|
}
|
||||||
|
|
||||||
|
emacsBuiltinFunction = []string{
|
||||||
|
"%", "*", "+", "-", "/", "/=", "1+", "1-", "<", "<=", "=", ">", ">=",
|
||||||
|
"Snarf-documentation", "abort-recursive-edit", "abs",
|
||||||
|
"accept-process-output", "access-file", "accessible-keymaps", "acos",
|
||||||
|
"active-minibuffer-window", "add-face-text-property",
|
||||||
|
"add-name-to-file", "add-text-properties", "all-completions",
|
||||||
|
"append", "apply", "apropos-internal", "aref", "arrayp", "aset",
|
||||||
|
"ash", "asin", "assoc", "assoc-string", "assq", "atan", "atom",
|
||||||
|
"autoload", "autoload-do-load", "backtrace", "backtrace--locals",
|
||||||
|
"backtrace-debug", "backtrace-eval", "backtrace-frame",
|
||||||
|
"backward-char", "backward-prefix-chars", "barf-if-buffer-read-only",
|
||||||
|
"base64-decode-region", "base64-decode-string",
|
||||||
|
"base64-encode-region", "base64-encode-string", "beginning-of-line",
|
||||||
|
"bidi-find-overridden-directionality", "bidi-resolved-levels",
|
||||||
|
"bitmap-spec-p", "bobp", "bolp", "bool-vector",
|
||||||
|
"bool-vector-count-consecutive", "bool-vector-count-population",
|
||||||
|
"bool-vector-exclusive-or", "bool-vector-intersection",
|
||||||
|
"bool-vector-not", "bool-vector-p", "bool-vector-set-difference",
|
||||||
|
"bool-vector-subsetp", "bool-vector-union", "boundp",
|
||||||
|
"buffer-base-buffer", "buffer-chars-modified-tick",
|
||||||
|
"buffer-enable-undo", "buffer-file-name", "buffer-has-markers-at",
|
||||||
|
"buffer-list", "buffer-live-p", "buffer-local-value",
|
||||||
|
"buffer-local-variables", "buffer-modified-p", "buffer-modified-tick",
|
||||||
|
"buffer-name", "buffer-size", "buffer-string", "buffer-substring",
|
||||||
|
"buffer-substring-no-properties", "buffer-swap-text", "bufferp",
|
||||||
|
"bury-buffer-internal", "byte-code", "byte-code-function-p",
|
||||||
|
"byte-to-position", "byte-to-string", "byteorder",
|
||||||
|
"call-interactively", "call-last-kbd-macro", "call-process",
|
||||||
|
"call-process-region", "cancel-kbd-macro-events", "capitalize",
|
||||||
|
"capitalize-region", "capitalize-word", "car", "car-less-than-car",
|
||||||
|
"car-safe", "case-table-p", "category-docstring",
|
||||||
|
"category-set-mnemonics", "category-table", "category-table-p",
|
||||||
|
"ccl-execute", "ccl-execute-on-string", "ccl-program-p", "cdr",
|
||||||
|
"cdr-safe", "ceiling", "char-after", "char-before",
|
||||||
|
"char-category-set", "char-charset", "char-equal", "char-or-string-p",
|
||||||
|
"char-resolve-modifiers", "char-syntax", "char-table-extra-slot",
|
||||||
|
"char-table-p", "char-table-parent", "char-table-range",
|
||||||
|
"char-table-subtype", "char-to-string", "char-width", "characterp",
|
||||||
|
"charset-after", "charset-id-internal", "charset-plist",
|
||||||
|
"charset-priority-list", "charsetp", "check-coding-system",
|
||||||
|
"check-coding-systems-region", "clear-buffer-auto-save-failure",
|
||||||
|
"clear-charset-maps", "clear-face-cache", "clear-font-cache",
|
||||||
|
"clear-image-cache", "clear-string", "clear-this-command-keys",
|
||||||
|
"close-font", "clrhash", "coding-system-aliases",
|
||||||
|
"coding-system-base", "coding-system-eol-type", "coding-system-p",
|
||||||
|
"coding-system-plist", "coding-system-priority-list",
|
||||||
|
"coding-system-put", "color-distance", "color-gray-p",
|
||||||
|
"color-supported-p", "combine-after-change-execute",
|
||||||
|
"command-error-default-function", "command-remapping", "commandp",
|
||||||
|
"compare-buffer-substrings", "compare-strings",
|
||||||
|
"compare-window-configurations", "completing-read",
|
||||||
|
"compose-region-internal", "compose-string-internal",
|
||||||
|
"composition-get-gstring", "compute-motion", "concat", "cons",
|
||||||
|
"consp", "constrain-to-field", "continue-process",
|
||||||
|
"controlling-tty-p", "coordinates-in-window-p", "copy-alist",
|
||||||
|
"copy-category-table", "copy-file", "copy-hash-table", "copy-keymap",
|
||||||
|
"copy-marker", "copy-sequence", "copy-syntax-table", "copysign",
|
||||||
|
"cos", "current-active-maps", "current-bidi-paragraph-direction",
|
||||||
|
"current-buffer", "current-case-table", "current-column",
|
||||||
|
"current-global-map", "current-idle-time", "current-indentation",
|
||||||
|
"current-input-mode", "current-local-map", "current-message",
|
||||||
|
"current-minor-mode-maps", "current-time", "current-time-string",
|
||||||
|
"current-time-zone", "current-window-configuration",
|
||||||
|
"cygwin-convert-file-name-from-windows",
|
||||||
|
"cygwin-convert-file-name-to-windows", "daemon-initialized",
|
||||||
|
"daemonp", "dbus--init-bus", "dbus-get-unique-name",
|
||||||
|
"dbus-message-internal", "debug-timer-check", "declare-equiv-charset",
|
||||||
|
"decode-big5-char", "decode-char", "decode-coding-region",
|
||||||
|
"decode-coding-string", "decode-sjis-char", "decode-time",
|
||||||
|
"default-boundp", "default-file-modes", "default-printer-name",
|
||||||
|
"default-toplevel-value", "default-value", "define-category",
|
||||||
|
"define-charset-alias", "define-charset-internal",
|
||||||
|
"define-coding-system-alias", "define-coding-system-internal",
|
||||||
|
"define-fringe-bitmap", "define-hash-table-test", "define-key",
|
||||||
|
"define-prefix-command", "delete",
|
||||||
|
"delete-all-overlays", "delete-and-extract-region", "delete-char",
|
||||||
|
"delete-directory-internal", "delete-field", "delete-file",
|
||||||
|
"delete-frame", "delete-other-windows-internal", "delete-overlay",
|
||||||
|
"delete-process", "delete-region", "delete-terminal",
|
||||||
|
"delete-window-internal", "delq", "describe-buffer-bindings",
|
||||||
|
"describe-vector", "destroy-fringe-bitmap", "detect-coding-region",
|
||||||
|
"detect-coding-string", "ding", "directory-file-name",
|
||||||
|
"directory-files", "directory-files-and-attributes", "discard-input",
|
||||||
|
"display-supports-face-attributes-p", "do-auto-save", "documentation",
|
||||||
|
"documentation-property", "downcase", "downcase-region",
|
||||||
|
"downcase-word", "draw-string", "dump-colors", "dump-emacs",
|
||||||
|
"dump-face", "dump-frame-glyph-matrix", "dump-glyph-matrix",
|
||||||
|
"dump-glyph-row", "dump-redisplay-history", "dump-tool-bar-row",
|
||||||
|
"elt", "emacs-pid", "encode-big5-char", "encode-char",
|
||||||
|
"encode-coding-region", "encode-coding-string", "encode-sjis-char",
|
||||||
|
"encode-time", "end-kbd-macro", "end-of-line", "eobp", "eolp", "eq",
|
||||||
|
"eql", "equal", "equal-including-properties", "erase-buffer",
|
||||||
|
"error-message-string", "eval", "eval-buffer", "eval-region",
|
||||||
|
"event-convert-list", "execute-kbd-macro", "exit-recursive-edit",
|
||||||
|
"exp", "expand-file-name", "expt", "external-debugging-output",
|
||||||
|
"face-attribute-relative-p", "face-attributes-as-vector", "face-font",
|
||||||
|
"fboundp", "fceiling", "fetch-bytecode", "ffloor",
|
||||||
|
"field-beginning", "field-end", "field-string",
|
||||||
|
"field-string-no-properties", "file-accessible-directory-p",
|
||||||
|
"file-acl", "file-attributes", "file-attributes-lessp",
|
||||||
|
"file-directory-p", "file-executable-p", "file-exists-p",
|
||||||
|
"file-locked-p", "file-modes", "file-name-absolute-p",
|
||||||
|
"file-name-all-completions", "file-name-as-directory",
|
||||||
|
"file-name-completion", "file-name-directory",
|
||||||
|
"file-name-nondirectory", "file-newer-than-file-p", "file-readable-p",
|
||||||
|
"file-regular-p", "file-selinux-context", "file-symlink-p",
|
||||||
|
"file-system-info", "file-system-info", "file-writable-p",
|
||||||
|
"fillarray", "find-charset-region", "find-charset-string",
|
||||||
|
"find-coding-systems-region-internal", "find-composition-internal",
|
||||||
|
"find-file-name-handler", "find-font", "find-operation-coding-system",
|
||||||
|
"float", "float-time", "floatp", "floor", "fmakunbound",
|
||||||
|
"following-char", "font-at", "font-drive-otf", "font-face-attributes",
|
||||||
|
"font-family-list", "font-get", "font-get-glyphs",
|
||||||
|
"font-get-system-font", "font-get-system-normal-font", "font-info",
|
||||||
|
"font-match-p", "font-otf-alternates", "font-put",
|
||||||
|
"font-shape-gstring", "font-spec", "font-variation-glyphs",
|
||||||
|
"font-xlfd-name", "fontp", "fontset-font", "fontset-info",
|
||||||
|
"fontset-list", "fontset-list-all", "force-mode-line-update",
|
||||||
|
"force-window-update", "format", "format-mode-line",
|
||||||
|
"format-network-address", "format-time-string", "forward-char",
|
||||||
|
"forward-comment", "forward-line", "forward-word",
|
||||||
|
"frame-border-width", "frame-bottom-divider-width",
|
||||||
|
"frame-can-run-window-configuration-change-hook", "frame-char-height",
|
||||||
|
"frame-char-width", "frame-face-alist", "frame-first-window",
|
||||||
|
"frame-focus", "frame-font-cache", "frame-fringe-width", "frame-list",
|
||||||
|
"frame-live-p", "frame-or-buffer-changed-p", "frame-parameter",
|
||||||
|
"frame-parameters", "frame-pixel-height", "frame-pixel-width",
|
||||||
|
"frame-pointer-visible-p", "frame-right-divider-width",
|
||||||
|
"frame-root-window", "frame-scroll-bar-height",
|
||||||
|
"frame-scroll-bar-width", "frame-selected-window", "frame-terminal",
|
||||||
|
"frame-text-cols", "frame-text-height", "frame-text-lines",
|
||||||
|
"frame-text-width", "frame-total-cols", "frame-total-lines",
|
||||||
|
"frame-visible-p", "framep", "frexp", "fringe-bitmaps-at-pos",
|
||||||
|
"fround", "fset", "ftruncate", "funcall", "funcall-interactively",
|
||||||
|
"function-equal", "functionp", "gap-position", "gap-size",
|
||||||
|
"garbage-collect", "gc-status", "generate-new-buffer-name", "get",
|
||||||
|
"get-buffer", "get-buffer-create", "get-buffer-process",
|
||||||
|
"get-buffer-window", "get-byte", "get-char-property",
|
||||||
|
"get-char-property-and-overlay", "get-file-buffer", "get-file-char",
|
||||||
|
"get-internal-run-time", "get-load-suffixes", "get-pos-property",
|
||||||
|
"get-process", "get-screen-color", "get-text-property",
|
||||||
|
"get-unicode-property-internal", "get-unused-category",
|
||||||
|
"get-unused-iso-final-char", "getenv-internal", "gethash",
|
||||||
|
"gfile-add-watch", "gfile-rm-watch", "global-key-binding",
|
||||||
|
"gnutls-available-p", "gnutls-boot", "gnutls-bye", "gnutls-deinit",
|
||||||
|
"gnutls-error-fatalp", "gnutls-error-string", "gnutls-errorp",
|
||||||
|
"gnutls-get-initstage", "gnutls-peer-status",
|
||||||
|
"gnutls-peer-status-warning-describe", "goto-char", "gpm-mouse-start",
|
||||||
|
"gpm-mouse-stop", "group-gid", "group-real-gid",
|
||||||
|
"handle-save-session", "handle-switch-frame", "hash-table-count",
|
||||||
|
"hash-table-p", "hash-table-rehash-size",
|
||||||
|
"hash-table-rehash-threshold", "hash-table-size", "hash-table-test",
|
||||||
|
"hash-table-weakness", "iconify-frame", "identity", "image-flush",
|
||||||
|
"image-mask-p", "image-metadata", "image-size", "imagemagick-types",
|
||||||
|
"imagep", "indent-to", "indirect-function", "indirect-variable",
|
||||||
|
"init-image-library", "inotify-add-watch", "inotify-rm-watch",
|
||||||
|
"input-pending-p", "insert", "insert-and-inherit",
|
||||||
|
"insert-before-markers", "insert-before-markers-and-inherit",
|
||||||
|
"insert-buffer-substring", "insert-byte", "insert-char",
|
||||||
|
"insert-file-contents", "insert-startup-screen", "int86",
|
||||||
|
"integer-or-marker-p", "integerp", "interactive-form", "intern",
|
||||||
|
"intern-soft", "internal--track-mouse", "internal-char-font",
|
||||||
|
"internal-complete-buffer", "internal-copy-lisp-face",
|
||||||
|
"internal-default-process-filter",
|
||||||
|
"internal-default-process-sentinel", "internal-describe-syntax-value",
|
||||||
|
"internal-event-symbol-parse-modifiers",
|
||||||
|
"internal-face-x-get-resource", "internal-get-lisp-face-attribute",
|
||||||
|
"internal-lisp-face-attribute-values", "internal-lisp-face-empty-p",
|
||||||
|
"internal-lisp-face-equal-p", "internal-lisp-face-p",
|
||||||
|
"internal-make-lisp-face", "internal-make-var-non-special",
|
||||||
|
"internal-merge-in-global-face",
|
||||||
|
"internal-set-alternative-font-family-alist",
|
||||||
|
"internal-set-alternative-font-registry-alist",
|
||||||
|
"internal-set-font-selection-order",
|
||||||
|
"internal-set-lisp-face-attribute",
|
||||||
|
"internal-set-lisp-face-attribute-from-resource",
|
||||||
|
"internal-show-cursor", "internal-show-cursor-p", "interrupt-process",
|
||||||
|
"invisible-p", "invocation-directory", "invocation-name", "isnan",
|
||||||
|
"iso-charset", "key-binding", "key-description",
|
||||||
|
"keyboard-coding-system", "keymap-parent", "keymap-prompt", "keymapp",
|
||||||
|
"keywordp", "kill-all-local-variables", "kill-buffer", "kill-emacs",
|
||||||
|
"kill-local-variable", "kill-process", "last-nonminibuffer-frame",
|
||||||
|
"lax-plist-get", "lax-plist-put", "ldexp", "length",
|
||||||
|
"libxml-parse-html-region", "libxml-parse-xml-region",
|
||||||
|
"line-beginning-position", "line-end-position", "line-pixel-height",
|
||||||
|
"list", "list-fonts", "list-system-processes", "listp", "load",
|
||||||
|
"load-average", "local-key-binding", "local-variable-if-set-p",
|
||||||
|
"local-variable-p", "locale-info", "locate-file-internal",
|
||||||
|
"lock-buffer", "log", "logand", "logb", "logior", "lognot", "logxor",
|
||||||
|
"looking-at", "lookup-image", "lookup-image-map", "lookup-key",
|
||||||
|
"lower-frame", "lsh", "macroexpand", "make-bool-vector",
|
||||||
|
"make-byte-code", "make-category-set", "make-category-table",
|
||||||
|
"make-char", "make-char-table", "make-directory-internal",
|
||||||
|
"make-frame-invisible", "make-frame-visible", "make-hash-table",
|
||||||
|
"make-indirect-buffer", "make-keymap", "make-list",
|
||||||
|
"make-local-variable", "make-marker", "make-network-process",
|
||||||
|
"make-overlay", "make-serial-process", "make-sparse-keymap",
|
||||||
|
"make-string", "make-symbol", "make-symbolic-link", "make-temp-name",
|
||||||
|
"make-terminal-frame", "make-variable-buffer-local",
|
||||||
|
"make-variable-frame-local", "make-vector", "makunbound",
|
||||||
|
"map-char-table", "map-charset-chars", "map-keymap",
|
||||||
|
"map-keymap-internal", "mapatoms", "mapc", "mapcar", "mapconcat",
|
||||||
|
"maphash", "mark-marker", "marker-buffer", "marker-insertion-type",
|
||||||
|
"marker-position", "markerp", "match-beginning", "match-data",
|
||||||
|
"match-end", "matching-paren", "max", "max-char", "md5", "member",
|
||||||
|
"memory-info", "memory-limit", "memory-use-counts", "memq", "memql",
|
||||||
|
"menu-bar-menu-at-x-y", "menu-or-popup-active-p",
|
||||||
|
"menu-or-popup-active-p", "merge-face-attribute", "message",
|
||||||
|
"message-box", "message-or-box", "min",
|
||||||
|
"minibuffer-completion-contents", "minibuffer-contents",
|
||||||
|
"minibuffer-contents-no-properties", "minibuffer-depth",
|
||||||
|
"minibuffer-prompt", "minibuffer-prompt-end",
|
||||||
|
"minibuffer-selected-window", "minibuffer-window", "minibufferp",
|
||||||
|
"minor-mode-key-binding", "mod", "modify-category-entry",
|
||||||
|
"modify-frame-parameters", "modify-syntax-entry",
|
||||||
|
"mouse-pixel-position", "mouse-position", "move-overlay",
|
||||||
|
"move-point-visually", "move-to-column", "move-to-window-line",
|
||||||
|
"msdos-downcase-filename", "msdos-long-file-names", "msdos-memget",
|
||||||
|
"msdos-memput", "msdos-mouse-disable", "msdos-mouse-enable",
|
||||||
|
"msdos-mouse-init", "msdos-mouse-p", "msdos-remember-default-colors",
|
||||||
|
"msdos-set-keyboard", "msdos-set-mouse-buttons",
|
||||||
|
"multibyte-char-to-unibyte", "multibyte-string-p", "narrow-to-region",
|
||||||
|
"natnump", "nconc", "network-interface-info",
|
||||||
|
"network-interface-list", "new-fontset", "newline-cache-check",
|
||||||
|
"next-char-property-change", "next-frame", "next-overlay-change",
|
||||||
|
"next-property-change", "next-read-file-uses-dialog-p",
|
||||||
|
"next-single-char-property-change", "next-single-property-change",
|
||||||
|
"next-window", "nlistp", "nreverse", "nth", "nthcdr", "null",
|
||||||
|
"number-or-marker-p", "number-to-string", "numberp",
|
||||||
|
"open-dribble-file", "open-font", "open-termscript",
|
||||||
|
"optimize-char-table", "other-buffer", "other-window-for-scrolling",
|
||||||
|
"overlay-buffer", "overlay-end", "overlay-get", "overlay-lists",
|
||||||
|
"overlay-properties", "overlay-put", "overlay-recenter",
|
||||||
|
"overlay-start", "overlayp", "overlays-at", "overlays-in",
|
||||||
|
"parse-partial-sexp", "play-sound-internal", "plist-get",
|
||||||
|
"plist-member", "plist-put", "point", "point-marker", "point-max",
|
||||||
|
"point-max-marker", "point-min", "point-min-marker",
|
||||||
|
"pos-visible-in-window-p", "position-bytes", "posix-looking-at",
|
||||||
|
"posix-search-backward", "posix-search-forward", "posix-string-match",
|
||||||
|
"posn-at-point", "posn-at-x-y", "preceding-char",
|
||||||
|
"prefix-numeric-value", "previous-char-property-change",
|
||||||
|
"previous-frame", "previous-overlay-change",
|
||||||
|
"previous-property-change", "previous-single-char-property-change",
|
||||||
|
"previous-single-property-change", "previous-window", "prin1",
|
||||||
|
"prin1-to-string", "princ", "print", "process-attributes",
|
||||||
|
"process-buffer", "process-coding-system", "process-command",
|
||||||
|
"process-connection", "process-contact", "process-datagram-address",
|
||||||
|
"process-exit-status", "process-filter", "process-filter-multibyte-p",
|
||||||
|
"process-id", "process-inherit-coding-system-flag", "process-list",
|
||||||
|
"process-mark", "process-name", "process-plist",
|
||||||
|
"process-query-on-exit-flag", "process-running-child-p",
|
||||||
|
"process-send-eof", "process-send-region", "process-send-string",
|
||||||
|
"process-sentinel", "process-status", "process-tty-name",
|
||||||
|
"process-type", "processp", "profiler-cpu-log",
|
||||||
|
"profiler-cpu-running-p", "profiler-cpu-start", "profiler-cpu-stop",
|
||||||
|
"profiler-memory-log", "profiler-memory-running-p",
|
||||||
|
"profiler-memory-start", "profiler-memory-stop", "propertize",
|
||||||
|
"purecopy", "put", "put-text-property",
|
||||||
|
"put-unicode-property-internal", "puthash", "query-font",
|
||||||
|
"query-fontset", "quit-process", "raise-frame", "random", "rassoc",
|
||||||
|
"rassq", "re-search-backward", "re-search-forward", "read",
|
||||||
|
"read-buffer", "read-char", "read-char-exclusive",
|
||||||
|
"read-coding-system", "read-command", "read-event",
|
||||||
|
"read-from-minibuffer", "read-from-string", "read-function",
|
||||||
|
"read-key-sequence", "read-key-sequence-vector",
|
||||||
|
"read-no-blanks-input", "read-non-nil-coding-system", "read-string",
|
||||||
|
"read-variable", "recent-auto-save-p", "recent-doskeys",
|
||||||
|
"recent-keys", "recenter", "recursion-depth", "recursive-edit",
|
||||||
|
"redirect-debugging-output", "redirect-frame-focus", "redisplay",
|
||||||
|
"redraw-display", "redraw-frame", "regexp-quote", "region-beginning",
|
||||||
|
"region-end", "register-ccl-program", "register-code-conversion-map",
|
||||||
|
"remhash", "remove-list-of-text-properties", "remove-text-properties",
|
||||||
|
"rename-buffer", "rename-file", "replace-match",
|
||||||
|
"reset-this-command-lengths", "resize-mini-window-internal",
|
||||||
|
"restore-buffer-modified-p", "resume-tty", "reverse", "round",
|
||||||
|
"run-hook-with-args", "run-hook-with-args-until-failure",
|
||||||
|
"run-hook-with-args-until-success", "run-hook-wrapped", "run-hooks",
|
||||||
|
"run-window-configuration-change-hook", "run-window-scroll-functions",
|
||||||
|
"safe-length", "scan-lists", "scan-sexps", "scroll-down",
|
||||||
|
"scroll-left", "scroll-other-window", "scroll-right", "scroll-up",
|
||||||
|
"search-backward", "search-forward", "secure-hash", "select-frame",
|
||||||
|
"select-window", "selected-frame", "selected-window",
|
||||||
|
"self-insert-command", "send-string-to-terminal", "sequencep",
|
||||||
|
"serial-process-configure", "set", "set-buffer",
|
||||||
|
"set-buffer-auto-saved", "set-buffer-major-mode",
|
||||||
|
"set-buffer-modified-p", "set-buffer-multibyte", "set-case-table",
|
||||||
|
"set-category-table", "set-char-table-extra-slot",
|
||||||
|
"set-char-table-parent", "set-char-table-range", "set-charset-plist",
|
||||||
|
"set-charset-priority", "set-coding-system-priority",
|
||||||
|
"set-cursor-size", "set-default", "set-default-file-modes",
|
||||||
|
"set-default-toplevel-value", "set-file-acl", "set-file-modes",
|
||||||
|
"set-file-selinux-context", "set-file-times", "set-fontset-font",
|
||||||
|
"set-frame-height", "set-frame-position", "set-frame-selected-window",
|
||||||
|
"set-frame-size", "set-frame-width", "set-fringe-bitmap-face",
|
||||||
|
"set-input-interrupt-mode", "set-input-meta-mode", "set-input-mode",
|
||||||
|
"set-keyboard-coding-system-internal", "set-keymap-parent",
|
||||||
|
"set-marker", "set-marker-insertion-type", "set-match-data",
|
||||||
|
"set-message-beep", "set-minibuffer-window",
|
||||||
|
"set-mouse-pixel-position", "set-mouse-position",
|
||||||
|
"set-network-process-option", "set-output-flow-control",
|
||||||
|
"set-process-buffer", "set-process-coding-system",
|
||||||
|
"set-process-datagram-address", "set-process-filter",
|
||||||
|
"set-process-filter-multibyte",
|
||||||
|
"set-process-inherit-coding-system-flag", "set-process-plist",
|
||||||
|
"set-process-query-on-exit-flag", "set-process-sentinel",
|
||||||
|
"set-process-window-size", "set-quit-char",
|
||||||
|
"set-safe-terminal-coding-system-internal", "set-screen-color",
|
||||||
|
"set-standard-case-table", "set-syntax-table",
|
||||||
|
"set-terminal-coding-system-internal", "set-terminal-local-value",
|
||||||
|
"set-terminal-parameter", "set-text-properties", "set-time-zone-rule",
|
||||||
|
"set-visited-file-modtime", "set-window-buffer",
|
||||||
|
"set-window-combination-limit", "set-window-configuration",
|
||||||
|
"set-window-dedicated-p", "set-window-display-table",
|
||||||
|
"set-window-fringes", "set-window-hscroll", "set-window-margins",
|
||||||
|
"set-window-new-normal", "set-window-new-pixel",
|
||||||
|
"set-window-new-total", "set-window-next-buffers",
|
||||||
|
"set-window-parameter", "set-window-point", "set-window-prev-buffers",
|
||||||
|
"set-window-redisplay-end-trigger", "set-window-scroll-bars",
|
||||||
|
"set-window-start", "set-window-vscroll", "setcar", "setcdr",
|
||||||
|
"setplist", "show-face-resources", "signal", "signal-process", "sin",
|
||||||
|
"single-key-description", "skip-chars-backward", "skip-chars-forward",
|
||||||
|
"skip-syntax-backward", "skip-syntax-forward", "sleep-for", "sort",
|
||||||
|
"sort-charsets", "special-variable-p", "split-char",
|
||||||
|
"split-window-internal", "sqrt", "standard-case-table",
|
||||||
|
"standard-category-table", "standard-syntax-table", "start-kbd-macro",
|
||||||
|
"start-process", "stop-process", "store-kbd-macro-event", "string",
|
||||||
|
"string-as-multibyte", "string-as-unibyte", "string-bytes",
|
||||||
|
"string-collate-equalp", "string-collate-lessp", "string-equal",
|
||||||
|
"string-lessp", "string-make-multibyte", "string-make-unibyte",
|
||||||
|
"string-match", "string-to-char", "string-to-multibyte",
|
||||||
|
"string-to-number", "string-to-syntax", "string-to-unibyte",
|
||||||
|
"string-width", "stringp", "subr-name", "subrp",
|
||||||
|
"subst-char-in-region", "substitute-command-keys",
|
||||||
|
"substitute-in-file-name", "substring", "substring-no-properties",
|
||||||
|
"suspend-emacs", "suspend-tty", "suspicious-object", "sxhash",
|
||||||
|
"symbol-function", "symbol-name", "symbol-plist", "symbol-value",
|
||||||
|
"symbolp", "syntax-table", "syntax-table-p", "system-groups",
|
||||||
|
"system-move-file-to-trash", "system-name", "system-users", "tan",
|
||||||
|
"terminal-coding-system", "terminal-list", "terminal-live-p",
|
||||||
|
"terminal-local-value", "terminal-name", "terminal-parameter",
|
||||||
|
"terminal-parameters", "terpri", "test-completion",
|
||||||
|
"text-char-description", "text-properties-at", "text-property-any",
|
||||||
|
"text-property-not-all", "this-command-keys",
|
||||||
|
"this-command-keys-vector", "this-single-command-keys",
|
||||||
|
"this-single-command-raw-keys", "time-add", "time-less-p",
|
||||||
|
"time-subtract", "tool-bar-get-system-style", "tool-bar-height",
|
||||||
|
"tool-bar-pixel-width", "top-level", "trace-redisplay",
|
||||||
|
"trace-to-stderr", "translate-region-internal", "transpose-regions",
|
||||||
|
"truncate", "try-completion", "tty-display-color-cells",
|
||||||
|
"tty-display-color-p", "tty-no-underline",
|
||||||
|
"tty-suppress-bold-inverse-default-colors", "tty-top-frame",
|
||||||
|
"tty-type", "type-of", "undo-boundary", "unencodable-char-position",
|
||||||
|
"unhandled-file-name-directory", "unibyte-char-to-multibyte",
|
||||||
|
"unibyte-string", "unicode-property-table-internal", "unify-charset",
|
||||||
|
"unintern", "unix-sync", "unlock-buffer", "upcase", "upcase-initials",
|
||||||
|
"upcase-initials-region", "upcase-region", "upcase-word",
|
||||||
|
"use-global-map", "use-local-map", "user-full-name",
|
||||||
|
"user-login-name", "user-real-login-name", "user-real-uid",
|
||||||
|
"user-uid", "variable-binding-locus", "vconcat", "vector",
|
||||||
|
"vector-or-char-table-p", "vectorp", "verify-visited-file-modtime",
|
||||||
|
"vertical-motion", "visible-frame-list", "visited-file-modtime",
|
||||||
|
"w16-get-clipboard-data", "w16-selection-exists-p",
|
||||||
|
"w16-set-clipboard-data", "w32-battery-status",
|
||||||
|
"w32-default-color-map", "w32-define-rgb-color",
|
||||||
|
"w32-display-monitor-attributes-list", "w32-frame-menu-bar-size",
|
||||||
|
"w32-frame-rect", "w32-get-clipboard-data",
|
||||||
|
"w32-get-codepage-charset", "w32-get-console-codepage",
|
||||||
|
"w32-get-console-output-codepage", "w32-get-current-locale-id",
|
||||||
|
"w32-get-default-locale-id", "w32-get-keyboard-layout",
|
||||||
|
"w32-get-locale-info", "w32-get-valid-codepages",
|
||||||
|
"w32-get-valid-keyboard-layouts", "w32-get-valid-locale-ids",
|
||||||
|
"w32-has-winsock", "w32-long-file-name", "w32-reconstruct-hot-key",
|
||||||
|
"w32-register-hot-key", "w32-registered-hot-keys",
|
||||||
|
"w32-selection-exists-p", "w32-send-sys-command",
|
||||||
|
"w32-set-clipboard-data", "w32-set-console-codepage",
|
||||||
|
"w32-set-console-output-codepage", "w32-set-current-locale",
|
||||||
|
"w32-set-keyboard-layout", "w32-set-process-priority",
|
||||||
|
"w32-shell-execute", "w32-short-file-name", "w32-toggle-lock-key",
|
||||||
|
"w32-unload-winsock", "w32-unregister-hot-key", "w32-window-exists-p",
|
||||||
|
"w32notify-add-watch", "w32notify-rm-watch",
|
||||||
|
"waiting-for-user-input-p", "where-is-internal", "widen",
|
||||||
|
"widget-apply", "widget-get", "widget-put",
|
||||||
|
"window-absolute-pixel-edges", "window-at", "window-body-height",
|
||||||
|
"window-body-width", "window-bottom-divider-width", "window-buffer",
|
||||||
|
"window-combination-limit", "window-configuration-frame",
|
||||||
|
"window-configuration-p", "window-dedicated-p",
|
||||||
|
"window-display-table", "window-edges", "window-end", "window-frame",
|
||||||
|
"window-fringes", "window-header-line-height", "window-hscroll",
|
||||||
|
"window-inside-absolute-pixel-edges", "window-inside-edges",
|
||||||
|
"window-inside-pixel-edges", "window-left-child",
|
||||||
|
"window-left-column", "window-line-height", "window-list",
|
||||||
|
"window-list-1", "window-live-p", "window-margins",
|
||||||
|
"window-minibuffer-p", "window-mode-line-height", "window-new-normal",
|
||||||
|
"window-new-pixel", "window-new-total", "window-next-buffers",
|
||||||
|
"window-next-sibling", "window-normal-size", "window-old-point",
|
||||||
|
"window-parameter", "window-parameters", "window-parent",
|
||||||
|
"window-pixel-edges", "window-pixel-height", "window-pixel-left",
|
||||||
|
"window-pixel-top", "window-pixel-width", "window-point",
|
||||||
|
"window-prev-buffers", "window-prev-sibling",
|
||||||
|
"window-redisplay-end-trigger", "window-resize-apply",
|
||||||
|
"window-resize-apply-total", "window-right-divider-width",
|
||||||
|
"window-scroll-bar-height", "window-scroll-bar-width",
|
||||||
|
"window-scroll-bars", "window-start", "window-system",
|
||||||
|
"window-text-height", "window-text-pixel-size", "window-text-width",
|
||||||
|
"window-top-child", "window-top-line", "window-total-height",
|
||||||
|
"window-total-width", "window-use-time", "window-valid-p",
|
||||||
|
"window-vscroll", "windowp", "write-char", "write-region",
|
||||||
|
"x-backspace-delete-keys-p", "x-change-window-property",
|
||||||
|
"x-change-window-property", "x-close-connection",
|
||||||
|
"x-close-connection", "x-create-frame", "x-create-frame",
|
||||||
|
"x-delete-window-property", "x-delete-window-property",
|
||||||
|
"x-disown-selection-internal", "x-display-backing-store",
|
||||||
|
"x-display-backing-store", "x-display-color-cells",
|
||||||
|
"x-display-color-cells", "x-display-grayscale-p",
|
||||||
|
"x-display-grayscale-p", "x-display-list", "x-display-list",
|
||||||
|
"x-display-mm-height", "x-display-mm-height", "x-display-mm-width",
|
||||||
|
"x-display-mm-width", "x-display-monitor-attributes-list",
|
||||||
|
"x-display-pixel-height", "x-display-pixel-height",
|
||||||
|
"x-display-pixel-width", "x-display-pixel-width", "x-display-planes",
|
||||||
|
"x-display-planes", "x-display-save-under", "x-display-save-under",
|
||||||
|
"x-display-screens", "x-display-screens", "x-display-visual-class",
|
||||||
|
"x-display-visual-class", "x-family-fonts", "x-file-dialog",
|
||||||
|
"x-file-dialog", "x-file-dialog", "x-focus-frame", "x-frame-geometry",
|
||||||
|
"x-frame-geometry", "x-get-atom-name", "x-get-resource",
|
||||||
|
"x-get-selection-internal", "x-hide-tip", "x-hide-tip",
|
||||||
|
"x-list-fonts", "x-load-color-file", "x-menu-bar-open-internal",
|
||||||
|
"x-menu-bar-open-internal", "x-open-connection", "x-open-connection",
|
||||||
|
"x-own-selection-internal", "x-parse-geometry", "x-popup-dialog",
|
||||||
|
"x-popup-menu", "x-register-dnd-atom", "x-select-font",
|
||||||
|
"x-select-font", "x-selection-exists-p", "x-selection-owner-p",
|
||||||
|
"x-send-client-message", "x-server-max-request-size",
|
||||||
|
"x-server-max-request-size", "x-server-vendor", "x-server-vendor",
|
||||||
|
"x-server-version", "x-server-version", "x-show-tip", "x-show-tip",
|
||||||
|
"x-synchronize", "x-synchronize", "x-uses-old-gtk-dialog",
|
||||||
|
"x-window-property", "x-window-property", "x-wm-set-size-hint",
|
||||||
|
"xw-color-defined-p", "xw-color-defined-p", "xw-color-values",
|
||||||
|
"xw-color-values", "xw-display-color-p", "xw-display-color-p",
|
||||||
|
"yes-or-no-p", "zlib-available-p", "zlib-decompress-region",
|
||||||
|
"forward-point",
|
||||||
|
}
|
||||||
|
|
||||||
|
emacsBuiltinFunctionHighlighted = []string{
|
||||||
|
"defvaralias", "provide", "require",
|
||||||
|
"with-no-warnings", "define-widget", "with-electric-help",
|
||||||
|
"throw", "defalias", "featurep",
|
||||||
|
}
|
||||||
|
|
||||||
|
emacsLambdaListKeywords = []string{
|
||||||
|
"&allow-other-keys", "&aux", "&body", "&environment", "&key", "&optional",
|
||||||
|
"&rest", "&whole",
|
||||||
|
}
|
||||||
|
|
||||||
|
emacsErrorKeywords = []string{
|
||||||
|
"cl-assert", "cl-check-type", "error", "signal",
|
||||||
|
"user-error", "warn",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// EmacsLisp lexer.
|
||||||
|
var EmacsLisp = Register(TypeRemappingLexer(MustNewXMLLexer(
|
||||||
|
embedded,
|
||||||
|
"embedded/emacslisp.xml",
|
||||||
|
), TypeMapping{
|
||||||
|
{NameVariable, NameFunction, emacsBuiltinFunction},
|
||||||
|
{NameVariable, NameBuiltin, emacsSpecialForms},
|
||||||
|
{NameVariable, NameException, emacsErrorKeywords},
|
||||||
|
{NameVariable, NameBuiltin, append(emacsBuiltinFunctionHighlighted, emacsMacros...)},
|
||||||
|
{NameVariable, KeywordPseudo, emacsLambdaListKeywords},
|
||||||
|
}))
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
<lexer>
|
||||||
|
<config>
|
||||||
|
<name>ABAP</name>
|
||||||
|
<alias>abap</alias>
|
||||||
|
<filename>*.abap</filename>
|
||||||
|
<filename>*.ABAP</filename>
|
||||||
|
<mime_type>text/x-abap</mime_type>
|
||||||
|
<case_insensitive>true</case_insensitive>
|
||||||
|
</config>
|
||||||
|
<rules>
|
||||||
|
<state name="common">
|
||||||
|
<rule pattern="\s+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="^\*.*$">
|
||||||
|
<token type="CommentSingle"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\".*?\n">
|
||||||
|
<token type="CommentSingle"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="##\w+">
|
||||||
|
<token type="CommentSpecial"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="variable-names">
|
||||||
|
<rule pattern="<\S+>">
|
||||||
|
<token type="NameVariable"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\w[\w~]*(?:(\[\])|->\*)?">
|
||||||
|
<token type="NameVariable"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="root">
|
||||||
|
<rule>
|
||||||
|
<include state="common"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="CALL\s+(?:BADI|CUSTOMER-FUNCTION|FUNCTION)">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(CALL\s+(?:DIALOG|SCREEN|SUBSCREEN|SELECTION-SCREEN|TRANSACTION|TRANSFORMATION))\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(FORM|PERFORM)(\s+)(\w+)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="NameFunction"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(PERFORM)(\s+)(\()(\w+)(\))">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="NameVariable"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(MODULE)(\s+)(\S+)(\s+)(INPUT|OUTPUT)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="NameFunction"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(METHOD)(\s+)([\w~]+)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="NameFunction"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(\s+)([\w\-]+)([=\-]>)([\w\-~]+)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="NameVariable"/>
|
||||||
|
<token type="Operator"/>
|
||||||
|
<token type="NameFunction"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(?<=(=|-)>)([\w\-~]+)(?=\()">
|
||||||
|
<token type="NameFunction"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(TEXT)(-)(\d{3})">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="LiteralNumberInteger"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(TEXT)(-)(\w{3})">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="NameVariable"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(ADD-CORRESPONDING|AUTHORITY-CHECK|CLASS-DATA|CLASS-EVENTS|CLASS-METHODS|CLASS-POOL|DELETE-ADJACENT|DIVIDE-CORRESPONDING|EDITOR-CALL|ENHANCEMENT-POINT|ENHANCEMENT-SECTION|EXIT-COMMAND|FIELD-GROUPS|FIELD-SYMBOLS|FUNCTION-POOL|INTERFACE-POOL|INVERTED-DATE|LOAD-OF-PROGRAM|LOG-POINT|MESSAGE-ID|MOVE-CORRESPONDING|MULTIPLY-CORRESPONDING|NEW-LINE|NEW-PAGE|NEW-SECTION|NO-EXTENSION|OUTPUT-LENGTH|PRINT-CONTROL|SELECT-OPTIONS|START-OF-SELECTION|SUBTRACT-CORRESPONDING|SYNTAX-CHECK|SYSTEM-EXCEPTIONS|TYPE-POOL|TYPE-POOLS|NO-DISPLAY)\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(?<![-\>])(CREATE\s+(PUBLIC|PRIVATE|DATA|OBJECT)|(PUBLIC|PRIVATE|PROTECTED)\s+SECTION|(TYPE|LIKE)\s+((LINE\s+OF|REF\s+TO|(SORTED|STANDARD|HASHED)\s+TABLE\s+OF))?|FROM\s+(DATABASE|MEMORY)|CALL\s+METHOD|(GROUP|ORDER) BY|HAVING|SEPARATED BY|GET\s+(BADI|BIT|CURSOR|DATASET|LOCALE|PARAMETER|PF-STATUS|(PROPERTY|REFERENCE)\s+OF|RUN\s+TIME|TIME\s+(STAMP)?)?|SET\s+(BIT|BLANK\s+LINES|COUNTRY|CURSOR|DATASET|EXTENDED\s+CHECK|HANDLER|HOLD\s+DATA|LANGUAGE|LEFT\s+SCROLL-BOUNDARY|LOCALE|MARGIN|PARAMETER|PF-STATUS|PROPERTY\s+OF|RUN\s+TIME\s+(ANALYZER|CLOCK\s+RESOLUTION)|SCREEN|TITLEBAR|UPADTE\s+TASK\s+LOCAL|USER-COMMAND)|CONVERT\s+((INVERTED-)?DATE|TIME|TIME\s+STAMP|TEXT)|(CLOSE|OPEN)\s+(DATASET|CURSOR)|(TO|FROM)\s+(DATA BUFFER|INTERNAL TABLE|MEMORY ID|DATABASE|SHARED\s+(MEMORY|BUFFER))|DESCRIBE\s+(DISTANCE\s+BETWEEN|FIELD|LIST|TABLE)|FREE\s(MEMORY|OBJECT)?|PROCESS\s+(BEFORE\s+OUTPUT|AFTER\s+INPUT|ON\s+(VALUE-REQUEST|HELP-REQUEST))|AT\s+(LINE-SELECTION|USER-COMMAND|END\s+OF|NEW)|AT\s+SELECTION-SCREEN(\s+(ON(\s+(BLOCK|(HELP|VALUE)-REQUEST\s+FOR|END\s+OF|RADIOBUTTON\s+GROUP))?|OUTPUT))?|SELECTION-SCREEN:?\s+((BEGIN|END)\s+OF\s+((TABBED\s+)?BLOCK|LINE|SCREEN)|COMMENT|FUNCTION\s+KEY|INCLUDE\s+BLOCKS|POSITION|PUSHBUTTON|SKIP|ULINE)|LEAVE\s+(LIST-PROCESSING|PROGRAM|SCREEN|TO LIST-PROCESSING|TO TRANSACTION)(ENDING|STARTING)\s+AT|FORMAT\s+(COLOR|INTENSIFIED|INVERSE|HOTSPOT|INPUT|FRAMES|RESET)|AS\s+(CHECKBOX|SUBSCREEN|WINDOW)|WITH\s+(((NON-)?UNIQUE)?\s+KEY|FRAME)|(BEGIN|END)\s+OF|DELETE(\s+ADJACENT\s+DUPLICATES\sFROM)?|COMPARING(\s+ALL\s+FIELDS)?|(INSERT|APPEND)(\s+INITIAL\s+LINE\s+(IN)?TO|\s+LINES\s+OF)?|IN\s+((BYTE|CHARACTER)\s+MODE|PROGRAM)|END-OF-(DEFINITION|PAGE|SELECTION)|WITH\s+FRAME(\s+TITLE)|(REPLACE|FIND)\s+((FIRST|ALL)\s+OCCURRENCES?\s+OF\s+)?(SUBSTRING|REGEX)?|MATCH\s+(LENGTH|COUNT|LINE|OFFSET)|(RESPECTING|IGNORING)\s+CASE|IN\s+UPDATE\s+TASK|(SOURCE|RESULT)\s+(XML)?|REFERENCE\s+INTO|AND\s+(MARK|RETURN)|CLIENT\s+SPECIFIED|CORRESPONDING\s+FIELDS\s+OF|IF\s+FOUND|FOR\s+EVENT|INHERITING\s+FROM|LEAVE\s+TO\s+SCREEN|LOOP\s+AT\s+(SCREEN)?|LOWER\s+CASE|MATCHCODE\s+OBJECT|MODIF\s+ID|MODIFY\s+SCREEN|NESTING\s+LEVEL|NO\s+INTERVALS|OF\s+STRUCTURE|RADIOBUTTON\s+GROUP|RANGE\s+OF|REF\s+TO|SUPPRESS DIALOG|TABLE\s+OF|UPPER\s+CASE|TRANSPORTING\s+NO\s+FIELDS|VALUE\s+CHECK|VISIBLE\s+LENGTH|HEADER\s+LINE|COMMON\s+PART)\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(^|(?<=(\s|\.)))(ABBREVIATED|ABSTRACT|ADD|ALIASES|ALIGN|ALPHA|ASSERT|AS|ASSIGN(ING)?|AT(\s+FIRST)?|BACK|BLOCK|BREAK-POINT|CASE|CATCH|CHANGING|CHECK|CLASS|CLEAR|COLLECT|COLOR|COMMIT|CREATE|COMMUNICATION|COMPONENTS?|COMPUTE|CONCATENATE|CONDENSE|CONSTANTS|CONTEXTS|CONTINUE|CONTROLS|COUNTRY|CURRENCY|DATA|DATE|DECIMALS|DEFAULT|DEFINE|DEFINITION|DEFERRED|DEMAND|DETAIL|DIRECTORY|DIVIDE|DO|DUMMY|ELSE(IF)?|ENDAT|ENDCASE|ENDCATCH|ENDCLASS|ENDDO|ENDFORM|ENDFUNCTION|ENDIF|ENDINTERFACE|ENDLOOP|ENDMETHOD|ENDMODULE|ENDSELECT|ENDTRY|ENDWHILE|ENHANCEMENT|EVENTS|EXACT|EXCEPTIONS?|EXIT|EXPONENT|EXPORT|EXPORTING|EXTRACT|FETCH|FIELDS?|FOR|FORM|FORMAT|FREE|FROM|FUNCTION|HIDE|ID|IF|IMPORT|IMPLEMENTATION|IMPORTING|IN|INCLUDE|INCLUDING|INDEX|INFOTYPES|INITIALIZATION|INTERFACE|INTERFACES|INTO|LANGUAGE|LEAVE|LENGTH|LINES|LOAD|LOCAL|JOIN|KEY|NEXT|MAXIMUM|MESSAGE|METHOD[S]?|MINIMUM|MODULE|MODIFIER|MODIFY|MOVE|MULTIPLY|NODES|NUMBER|OBLIGATORY|OBJECT|OF|OFF|ON|OTHERS|OVERLAY|PACK|PAD|PARAMETERS|PERCENTAGE|POSITION|PROGRAM|PROVIDE|PUBLIC|PUT|PF\d\d|RAISE|RAISING|RANGES?|READ|RECEIVE|REDEFINITION|REFRESH|REJECT|REPORT|RESERVE|RESUME|RETRY|RETURN|RETURNING|RIGHT|ROLLBACK|REPLACE|SCROLL|SEARCH|SELECT|SHIFT|SIGN|SINGLE|SIZE|SKIP|SORT|SPLIT|STATICS|STOP|STYLE|SUBMATCHES|SUBMIT|SUBTRACT|SUM(?!\()|SUMMARY|SUMMING|SUPPLY|TABLE|TABLES|TIMESTAMP|TIMES?|TIMEZONE|TITLE|\??TO|TOP-OF-PAGE|TRANSFER|TRANSLATE|TRY|TYPES|ULINE|UNDER|UNPACK|UPDATE|USING|VALUE|VALUES|VIA|VARYING|VARY|WAIT|WHEN|WHERE|WIDTH|WHILE|WITH|WINDOW|WRITE|XSD|ZERO)\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(abs|acos|asin|atan|boolc|boolx|bit_set|char_off|charlen|ceil|cmax|cmin|condense|contains|contains_any_of|contains_any_not_of|concat_lines_of|cos|cosh|count|count_any_of|count_any_not_of|dbmaxlen|distance|escape|exp|find|find_end|find_any_of|find_any_not_of|floor|frac|from_mixed|insert|lines|log|log10|match|matches|nmax|nmin|numofchar|repeat|replace|rescale|reverse|round|segment|shift_left|shift_right|sign|sin|sinh|sqrt|strlen|substring|substring_after|substring_from|substring_before|substring_to|tan|tanh|to_upper|to_lower|to_mixed|translate|trunc|xstrlen)(\()\b">
|
||||||
|
<bygroups>
|
||||||
|
<token type="NameBuiltin"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="&[0-9]">
|
||||||
|
<token type="Name"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[0-9]+">
|
||||||
|
<token type="LiteralNumberInteger"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(?<=(\s|.))(AND|OR|EQ|NE|GT|LT|GE|LE|CO|CN|CA|NA|CS|NOT|NS|CP|NP|BYTE-CO|BYTE-CN|BYTE-CA|BYTE-NA|BYTE-CS|BYTE-NS|IS\s+(NOT\s+)?(INITIAL|ASSIGNED|REQUESTED|BOUND))\b">
|
||||||
|
<token type="OperatorWord"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="variable-names"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[?*<>=\-+&]">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="'(''|[^'])*'">
|
||||||
|
<token type="LiteralStringSingle"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="`([^`])*`">
|
||||||
|
<token type="LiteralStringSingle"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="([|}])([^{}|]*?)([|{])">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="LiteralStringSingle"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[/;:()\[\],.]">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(!)(\w+)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Operator"/>
|
||||||
|
<token type="Name"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
</rules>
|
||||||
|
</lexer>
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<lexer>
|
||||||
|
<config>
|
||||||
|
<name>ABNF</name>
|
||||||
|
<alias>abnf</alias>
|
||||||
|
<filename>*.abnf</filename>
|
||||||
|
<mime_type>text/x-abnf</mime_type>
|
||||||
|
</config>
|
||||||
|
<rules>
|
||||||
|
<state name="root">
|
||||||
|
<rule pattern=";.*$">
|
||||||
|
<token type="CommentSingle"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(%[si])?"[^"]*"">
|
||||||
|
<token type="Literal"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="%b[01]+\-[01]+\b">
|
||||||
|
<token type="Literal"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="%b[01]+(\.[01]+)*\b">
|
||||||
|
<token type="Literal"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="%d[0-9]+\-[0-9]+\b">
|
||||||
|
<token type="Literal"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="%d[0-9]+(\.[0-9]+)*\b">
|
||||||
|
<token type="Literal"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="%x[0-9a-fA-F]+\-[0-9a-fA-F]+\b">
|
||||||
|
<token type="Literal"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="%x[0-9a-fA-F]+(\.[0-9a-fA-F]+)*\b">
|
||||||
|
<token type="Literal"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b[0-9]+\*[0-9]+">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b[0-9]+\*">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b[0-9]+">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\*">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(HEXDIG|DQUOTE|DIGIT|VCHAR|OCTET|ALPHA|CHAR|CRLF|HTAB|LWSP|BIT|CTL|WSP|LF|SP|CR)\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[a-zA-Z][a-zA-Z0-9-]+\b">
|
||||||
|
<token type="NameClass"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(=/|=|/)">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[\[\]()]">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\s+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=".">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
</rules>
|
||||||
|
</lexer>
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<lexer>
|
||||||
|
<config>
|
||||||
|
<name>ActionScript</name>
|
||||||
|
<alias>as</alias>
|
||||||
|
<alias>actionscript</alias>
|
||||||
|
<filename>*.as</filename>
|
||||||
|
<mime_type>application/x-actionscript</mime_type>
|
||||||
|
<mime_type>text/x-actionscript</mime_type>
|
||||||
|
<mime_type>text/actionscript</mime_type>
|
||||||
|
<dot_all>true</dot_all>
|
||||||
|
<not_multiline>true</not_multiline>
|
||||||
|
</config>
|
||||||
|
<rules>
|
||||||
|
<state name="root">
|
||||||
|
<rule pattern="\s+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="//.*?\n">
|
||||||
|
<token type="CommentSingle"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="/\*.*?\*/">
|
||||||
|
<token type="CommentMultiline"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="/(\\\\|\\/|[^/\n])*/[gim]*">
|
||||||
|
<token type="LiteralStringRegex"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[~^*!%&<>|+=:;,/?\\-]+">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[{}\[\]();.]+">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(instanceof|arguments|continue|default|typeof|switch|return|catch|break|while|throw|each|this|with|else|case|var|new|for|try|if|do|in)\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(implements|protected|namespace|interface|intrinsic|override|function|internal|private|package|extends|dynamic|import|native|return|public|static|class|const|super|final|get|set)\b">
|
||||||
|
<token type="KeywordDeclaration"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(true|false|null|NaN|Infinity|-Infinity|undefined|Void)\b">
|
||||||
|
<token type="KeywordConstant"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(IDynamicPropertyOutputIDynamicPropertyWriter|DisplacmentMapFilterMode|AccessibilityProperties|ContextMenuBuiltInItems|SharedObjectFlushStatus|DisplayObjectContainer|IllegalOperationError|DisplacmentMapFilter|InterpolationMethod|URLLoaderDataFormat|PrintJobOrientation|ActionScriptVersion|BitmapFilterQuality|GradientBevelFilter|GradientGlowFilter|DeleteObjectSample|StackOverflowError|SoundLoaderContext|ScriptTimeoutError|SecurityErrorEvent|InteractiveObject|StageDisplayState|FileReferenceList|TextFieldAutoSize|ApplicationDomain|BitmapDataChannel|ColorMatrixFilter|ExternalInterface|IMEConversionMode|DropShadowFilter|URLRequestHeader|ContextMenuEvent|ConvultionFilter|URLRequestMethod|BitmapFilterType|IEventDispatcher|ContextMenuItem|LocalConnection|InvalidSWFError|AsyncErrorEvent|MovieClipLoader|IBitmapDrawable|PrintJobOptions|EventDispatcher|NewObjectSample|HTTPStatusEvent|TextFormatAlign|IExternalizable|FullScreenEvent|DefinitionError|TextLineMetrics|NetStatusEvent|ColorTransform|ObjectEncoding|SecurityDomain|StageScaleMode|FocusDirection|ReferenceError|SoundTransform|KeyboardEvent|DisplayObject|PixelSnapping|LoaderContext|NetConnection|SecurityPanel|SecurityError|FileReference|AsBroadcaster|LineScaleMode|AntiAliasType|Accessibility|TextFieldType|URLVariabeles|ActivityEvent|ProgressEvent|TextColorType|StageQuality|TextSnapshot|Capabilities|BitmapFilter|SpreadMethod|GradientType|TextRenderer|SoundChannel|SharedObject|IOErrorEvent|SimpleButton|ContextMenu|InvokeEvent|CSMSettings|SyntaxError|StatusEvent|KeyLocation|IDataOutput|VerifyError|XMLDocument|XMLNodeType|MemoryError|GridFitType|BevelFilter|ErrorEvent|FrameLabel|GlowFilter|LoaderInfo|Microphone|MorphShape|BlurFilter|MouseEvent|FocusEvent|SoundMixer|FileFilter|TimerEvent|JointStyle|EventPhase|StageAlign|Dictionary|URLRequest|StyleSheet|SWFVersion|IDataInput|StaticText|RangeError|BitmapData|TextFormat|StackFrame|Namespace|SyncEvent|Rectangle|URLLoader|TypeError|Responder|NetStream|BlendMode|CapsStyle|DataEvent|ByteArray|MovieClip|Transform|TextField|Selection|AVM1Movie|XMLSocket|URLStream|FontStyle|EvalError|FontType|LoadVars|Graphics|Security|IMEEvent|URIError|Keyboard|Function|EOFError|PrintJob|IOError|XMLList|Boolean|ID3Info|XMLNode|Bitmap|String|RegExp|Sample|Object|Sprite|System|Endian|Matrix|Camera|Locale|Number|Loader|Socket|QName|Class|Timer|Sound|Shape|XMLUI|Mouse|Scene|Stage|Color|Point|Video|Error|Event|Proxy|Array|Date|uint|Math|Font|int|Key|IME|XML)\b">
|
||||||
|
<token type="NameBuiltin"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(decodeURIComponent|updateAfterEvent|clearInterval|setInterval|getVersion|parseFloat|fscommand|isXMLName|encodeURI|decodeURI|getTimer|unescape|isFinite|parseInt|getURL|escape|trace|isNaN|eval)\b">
|
||||||
|
<token type="NameFunction"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[$a-zA-Z_]\w*">
|
||||||
|
<token type="NameOther"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[0-9][0-9]*\.[0-9]+([eE][0-9]+)?[fd]?">
|
||||||
|
<token type="LiteralNumberFloat"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="0x[0-9a-f]+">
|
||||||
|
<token type="LiteralNumberHex"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[0-9]+">
|
||||||
|
<token type="LiteralNumberInteger"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=""(\\\\|\\"|[^"])*"">
|
||||||
|
<token type="LiteralStringDouble"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="'(\\\\|\\'|[^'])*'">
|
||||||
|
<token type="LiteralStringSingle"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
</rules>
|
||||||
|
</lexer>
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
<lexer>
|
||||||
|
<config>
|
||||||
|
<name>ActionScript 3</name>
|
||||||
|
<alias>as3</alias>
|
||||||
|
<alias>actionscript3</alias>
|
||||||
|
<filename>*.as</filename>
|
||||||
|
<mime_type>application/x-actionscript3</mime_type>
|
||||||
|
<mime_type>text/x-actionscript3</mime_type>
|
||||||
|
<mime_type>text/actionscript3</mime_type>
|
||||||
|
<dot_all>true</dot_all>
|
||||||
|
</config>
|
||||||
|
<rules>
|
||||||
|
<state name="funcparams">
|
||||||
|
<rule pattern="\s+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(\s*)(\.\.\.)?([$a-zA-Z_]\w*)(\s*)(:)(\s*)([$a-zA-Z_]\w*(?:\.<\w+>)?|\*)(\s*)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="Name"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="Operator"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="KeywordType"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
</bygroups>
|
||||||
|
<push state="defval"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\)">
|
||||||
|
<token type="Operator"/>
|
||||||
|
<push state="type"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="type">
|
||||||
|
<rule pattern="(\s*)(:)(\s*)([$a-zA-Z_]\w*(?:\.<\w+>)?|\*)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="Operator"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="KeywordType"/>
|
||||||
|
</bygroups>
|
||||||
|
<pop depth="2"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\s+">
|
||||||
|
<token type="Text"/>
|
||||||
|
<pop depth="2"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<pop depth="2"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="defval">
|
||||||
|
<rule pattern="(=)(\s*)([^(),]+)(\s*)(,?)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Operator"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<usingself state="root"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="Operator"/>
|
||||||
|
</bygroups>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=",">
|
||||||
|
<token type="Operator"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="root">
|
||||||
|
<rule pattern="\s+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(function\s+)([$a-zA-Z_]\w*)(\s*)(\()">
|
||||||
|
<bygroups>
|
||||||
|
<token type="KeywordDeclaration"/>
|
||||||
|
<token type="NameFunction"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="Operator"/>
|
||||||
|
</bygroups>
|
||||||
|
<push state="funcparams"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(var|const)(\s+)([$a-zA-Z_]\w*)(\s*)(:)(\s*)([$a-zA-Z_]\w*(?:\.<\w+>)?)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="KeywordDeclaration"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="Name"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="KeywordType"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(import|package)(\s+)((?:[$a-zA-Z_]\w*|\.)+)(\s*)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="NameNamespace"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(new)(\s+)([$a-zA-Z_]\w*(?:\.<\w+>)?)(\s*)(\()">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="KeywordType"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="Operator"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="//.*?\n">
|
||||||
|
<token type="CommentSingle"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="/\*.*?\*/">
|
||||||
|
<token type="CommentMultiline"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="/(\\\\|\\/|[^\n])*/[gisx]*">
|
||||||
|
<token type="LiteralStringRegex"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(\.)([$a-zA-Z_]\w*)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Operator"/>
|
||||||
|
<token type="NameAttribute"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(case|default|for|each|in|while|do|break|return|continue|if|else|throw|try|catch|with|new|typeof|arguments|instanceof|this|switch|import|include|as|is)\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(class|public|final|internal|native|override|private|protected|static|import|extends|implements|interface|intrinsic|return|super|dynamic|function|const|get|namespace|package|set)\b">
|
||||||
|
<token type="KeywordDeclaration"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(true|false|null|NaN|Infinity|-Infinity|undefined|void)\b">
|
||||||
|
<token type="KeywordConstant"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(decodeURI|decodeURIComponent|encodeURI|escape|eval|isFinite|isNaN|isXMLName|clearInterval|fscommand|getTimer|getURL|getVersion|isFinite|parseFloat|parseInt|setInterval|trace|updateAfterEvent|unescape)\b">
|
||||||
|
<token type="NameFunction"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[$a-zA-Z_]\w*">
|
||||||
|
<token type="Name"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[0-9][0-9]*\.[0-9]+([eE][0-9]+)?[fd]?">
|
||||||
|
<token type="LiteralNumberFloat"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="0x[0-9a-f]+">
|
||||||
|
<token type="LiteralNumberHex"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[0-9]+">
|
||||||
|
<token type="LiteralNumberInteger"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=""(\\\\|\\"|[^"])*"">
|
||||||
|
<token type="LiteralStringDouble"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="'(\\\\|\\'|[^'])*'">
|
||||||
|
<token type="LiteralStringSingle"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[~^*!%&<>|+=:;,/?\\{}\[\]().-]+">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
</rules>
|
||||||
|
</lexer>
|
||||||
@@ -0,0 +1,321 @@
|
|||||||
|
<lexer>
|
||||||
|
<config>
|
||||||
|
<name>Ada</name>
|
||||||
|
<alias>ada</alias>
|
||||||
|
<alias>ada95</alias>
|
||||||
|
<alias>ada2005</alias>
|
||||||
|
<filename>*.adb</filename>
|
||||||
|
<filename>*.ads</filename>
|
||||||
|
<filename>*.ada</filename>
|
||||||
|
<mime_type>text/x-ada</mime_type>
|
||||||
|
<case_insensitive>true</case_insensitive>
|
||||||
|
</config>
|
||||||
|
<rules>
|
||||||
|
<state name="end">
|
||||||
|
<rule pattern="(if|case|record|loop|select)">
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=""[^"]+"|[\w.]+">
|
||||||
|
<token type="NameFunction"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\s+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=";">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="array_def">
|
||||||
|
<rule pattern=";">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(\w+)(\s+)(range)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="KeywordType"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="root"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="package_instantiation">
|
||||||
|
<rule pattern="("[^"]+"|\w+)(\s+)(=>)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="NameVariable"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[\w.\'"]">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\)">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="root"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="subprogram">
|
||||||
|
<rule pattern="\(">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<push state="#pop" state="formal_part"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=";">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="is\b">
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=""[^"]+"|\w+">
|
||||||
|
<token type="NameFunction"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="root"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="type_def">
|
||||||
|
<rule pattern=";">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\(">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<push state="formal_part"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="with|and|use">
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="array\b">
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
<push state="#pop" state="array_def"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="record\b">
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
<push state="record_def"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(null record)(;)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</bygroups>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="root"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="import">
|
||||||
|
<rule pattern="[\w.]+">
|
||||||
|
<token type="NameNamespace"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="formal_part">
|
||||||
|
<rule pattern="\)">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\w+">
|
||||||
|
<token type="NameVariable"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=",|:[^=]">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(in|not|null|out|access)\b">
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="root"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="package">
|
||||||
|
<rule pattern="body">
|
||||||
|
<token type="KeywordDeclaration"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="is\s+new|renames">
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="is">
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=";">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\(">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<push state="package_instantiation"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="([\w.]+)">
|
||||||
|
<token type="NameClass"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="root"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="attribute">
|
||||||
|
<rule pattern="(')(\w+)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="NameAttribute"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="record_def">
|
||||||
|
<rule pattern="end record">
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="root"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="root">
|
||||||
|
<rule pattern="[^\S\n]+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="--.*?\n">
|
||||||
|
<token type="CommentSingle"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[^\S\n]+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="function|procedure|entry">
|
||||||
|
<token type="KeywordDeclaration"/>
|
||||||
|
<push state="subprogram"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(subtype|type)(\s+)(\w+)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="KeywordDeclaration"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="KeywordType"/>
|
||||||
|
</bygroups>
|
||||||
|
<push state="type_def"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="task|protected">
|
||||||
|
<token type="KeywordDeclaration"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(subtype)(\s+)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="KeywordDeclaration"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(end)(\s+)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
</bygroups>
|
||||||
|
<push state="end"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(pragma)(\s+)(\w+)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="CommentPreproc"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(true|false|null)\b">
|
||||||
|
<token type="KeywordConstant"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(Short_Short_Integer|Short_Short_Float|Long_Long_Integer|Long_Long_Float|Wide_Character|Reference_Type|Short_Integer|Long_Integer|Wide_String|Short_Float|Controlled|Long_Float|Character|Generator|File_Type|File_Mode|Positive|Duration|Boolean|Natural|Integer|Address|Cursor|String|Count|Float|Byte)\b">
|
||||||
|
<token type="KeywordType"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(and(\s+then)?|in|mod|not|or(\s+else)|rem)\b">
|
||||||
|
<token type="OperatorWord"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="generic|private">
|
||||||
|
<token type="KeywordDeclaration"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="package">
|
||||||
|
<token type="KeywordDeclaration"/>
|
||||||
|
<push state="package"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="array\b">
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
<push state="array_def"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(with|use)(\s+)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="KeywordNamespace"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
</bygroups>
|
||||||
|
<push state="import"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(\w+)(\s*)(:)(\s*)(constant)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="NameConstant"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="<<\w+>>">
|
||||||
|
<token type="NameLabel"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(\w+)(\s*)(:)(\s*)(declare|begin|loop|for|while)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="NameLabel"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b(synchronized|overriding|terminate|interface|exception|protected|separate|constant|abstract|renames|reverse|subtype|aliased|declare|requeue|limited|return|tagged|access|record|select|accept|digits|others|pragma|entry|elsif|delta|delay|array|until|range|raise|while|begin|abort|else|loop|when|type|null|then|body|task|goto|case|exit|end|for|abs|xor|all|new|out|is|of|if|or|do|at)\b">
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=""[^"]*"">
|
||||||
|
<token type="LiteralString"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="attribute"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="numbers"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="'[^']'">
|
||||||
|
<token type="LiteralStringChar"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(\w+)(\s*|[(,])">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Name"/>
|
||||||
|
<usingself state="root"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(<>|=>|:=|[()|:;,.'])">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[*<>+=/&-]">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\n+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="numbers">
|
||||||
|
<rule pattern="[0-9_]+#[0-9a-f]+#">
|
||||||
|
<token type="LiteralNumberHex"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[0-9_]+\.[0-9_]*">
|
||||||
|
<token type="LiteralNumberFloat"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[0-9_]+">
|
||||||
|
<token type="LiteralNumberInteger"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
</rules>
|
||||||
|
</lexer>
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<lexer>
|
||||||
|
<config>
|
||||||
|
<name>Agda</name>
|
||||||
|
<alias>agda</alias>
|
||||||
|
<filename>*.agda</filename>
|
||||||
|
<mime_type>text/x-agda</mime_type>
|
||||||
|
</config>
|
||||||
|
<rules>
|
||||||
|
<state name="root">
|
||||||
|
<rule pattern="^(\s*)([^\s(){}]+)(\s*)(:)(\s*)"><bygroups><token type="TextWhitespace"/><token type="NameFunction"/><token type="TextWhitespace"/><token type="OperatorWord"/><token type="TextWhitespace"/></bygroups></rule>
|
||||||
|
<rule pattern="--(?![!#$%&*+./<=>?@^|_~:\\]).*?$"><token type="CommentSingle"/></rule>
|
||||||
|
<rule pattern="\{-"><token type="CommentMultiline"/><push state="comment"/></rule>
|
||||||
|
<rule pattern="\{!"><token type="CommentMultiline"/><push state="hole"/></rule>
|
||||||
|
<rule pattern="\b(abstract|codata|coinductive|constructor|data|do|eta-equality|field|forall|hiding|in|inductive|infix|infixl|infixr|instance|interleaved|let|macro|mutual|no-eta-equality|open|overlap|pattern|postulate|primitive|private|quote|quoteTerm|record|renaming|rewrite|syntax|tactic|unquote|unquoteDecl|unquoteDef|using|variable|where|with)(?!\')\b"><token type="KeywordReserved"/></rule>
|
||||||
|
<rule pattern="(import|module)(\s+)"><bygroups><token type="KeywordReserved"/><token type="TextWhitespace"/></bygroups><push state="module"/></rule>
|
||||||
|
<rule pattern="\b(Set|Prop)[\u2080-\u2089]*\b"><token type="KeywordType"/></rule>
|
||||||
|
<rule pattern="(\(|\)|\{|\})"><token type="Operator"/></rule>
|
||||||
|
<rule pattern="(\.{1,3}|\||\u03BB|\u2200|\u2192|:|=|->)"><token type="OperatorWord"/></rule>
|
||||||
|
<rule pattern="\d+[eE][+-]?\d+"><token type="LiteralNumberFloat"/></rule>
|
||||||
|
<rule pattern="\d+\.\d+([eE][+-]?\d+)?"><token type="LiteralNumberFloat"/></rule>
|
||||||
|
<rule pattern="0[xX][\da-fA-F]+"><token type="LiteralNumberHex"/></rule>
|
||||||
|
<rule pattern="\d+"><token type="LiteralNumberInteger"/></rule>
|
||||||
|
<rule pattern="'"><token type="LiteralStringChar"/><push state="character"/></rule>
|
||||||
|
<rule pattern="""><token type="LiteralString"/><push state="string"/></rule>
|
||||||
|
<rule pattern="[^\s(){}]+"><token type="Text"/></rule>
|
||||||
|
<rule pattern="\s+?"><token type="TextWhitespace"/></rule>
|
||||||
|
</state>
|
||||||
|
<state name="hole">
|
||||||
|
<rule pattern="[^!{}]+"><token type="CommentMultiline"/></rule>
|
||||||
|
<rule pattern="\{!"><token type="CommentMultiline"/><push/></rule>
|
||||||
|
<rule pattern="!\}"><token type="CommentMultiline"/><pop depth="1"/></rule>
|
||||||
|
<rule pattern="[!{}]"><token type="CommentMultiline"/></rule>
|
||||||
|
</state>
|
||||||
|
<state name="module">
|
||||||
|
<rule pattern="\{-"><token type="CommentMultiline"/><push state="comment"/></rule>
|
||||||
|
<rule pattern="[a-zA-Z][\w.\']*"><token type="Name"/><pop depth="1"/></rule>
|
||||||
|
<rule pattern="[\W0-9_]+"><token type="Text"/></rule>
|
||||||
|
</state>
|
||||||
|
<state name="comment">
|
||||||
|
<rule pattern="[^-{}]+"><token type="CommentMultiline"/></rule>
|
||||||
|
<rule pattern="\{-"><token type="CommentMultiline"/><push/></rule>
|
||||||
|
<rule pattern="-\}"><token type="CommentMultiline"/><pop depth="1"/></rule>
|
||||||
|
<rule pattern="[-{}]"><token type="CommentMultiline"/></rule>
|
||||||
|
</state>
|
||||||
|
<state name="character">
|
||||||
|
<rule pattern="[^\\']'"><token type="LiteralStringChar"/><pop depth="1"/></rule>
|
||||||
|
<rule pattern="\\"><token type="LiteralStringEscape"/><push state="escape"/></rule>
|
||||||
|
<rule pattern="'"><token type="LiteralStringChar"/><pop depth="1"/></rule>
|
||||||
|
</state>
|
||||||
|
<state name="string">
|
||||||
|
<rule pattern="[^\\"]+"><token type="LiteralString"/></rule>
|
||||||
|
<rule pattern="\\"><token type="LiteralStringEscape"/><push state="escape"/></rule>
|
||||||
|
<rule pattern="""><token type="LiteralString"/><pop depth="1"/></rule>
|
||||||
|
</state>
|
||||||
|
<state name="escape">
|
||||||
|
<rule pattern="[abfnrtv"\'&\\]"><token type="LiteralStringEscape"/><pop depth="1"/></rule>
|
||||||
|
<rule pattern="\^[][A-ZÀ-ÖØ-ÞĀĂĄĆĈĊČĎĐĒĔĖĘĚĜĞĠĢĤĦĨĪĬĮİIJĴĶĹĻĽĿŁŃŅŇŊŌŎŐŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶŸ-ŹŻŽƁ-ƂƄƆ-ƇƉ-ƋƎ-ƑƓ-ƔƖ-ƘƜ-ƝƟ-ƠƢƤƦ-ƧƩƬƮ-ƯƱ-ƳƵƷ-ƸƼDŽLJNJǍǏǑǓǕǗǙǛǞǠǢǤǦǨǪǬǮDZǴǶ-ǸǺǼǾȀȂȄȆȈȊȌȎȐȒȔȖȘȚȜȞȠȢȤȦȨȪȬȮȰȲȺ-ȻȽ-ȾɁɃ-ɆɈɊɌɎͰͲͶͿΆΈ-ΊΌΎ-ΏΑ-ΡΣ-ΫϏϒ-ϔϘϚϜϞϠϢϤϦϨϪϬϮϴϷϹ-ϺϽ-ЯѠѢѤѦѨѪѬѮѰѲѴѶѸѺѼѾҀҊҌҎҐҒҔҖҘҚҜҞҠҢҤҦҨҪҬҮҰҲҴҶҸҺҼҾӀ-ӁӃӅӇӉӋӍӐӒӔӖӘӚӜӞӠӢӤӦӨӪӬӮӰӲӴӶӸӺӼӾԀԂԄԆԈԊԌԎԐԒԔԖԘԚԜԞԠԢԤԦԨԪԬԮԱ-ՖႠ-ჅჇჍᎠ-ᏵᲐ-ᲺᲽ-ᲿḀḂḄḆḈḊḌḎḐḒḔḖḘḚḜḞḠḢḤḦḨḪḬḮḰḲḴḶḸḺḼḾṀṂṄṆṈṊṌṎṐṒṔṖṘṚṜṞṠṢṤṦṨṪṬṮṰṲṴṶṸṺṼṾẀẂẄẆẈẊẌẎẐẒẔẞẠẢẤẦẨẪẬẮẰẲẴẶẸẺẼẾỀỂỄỆỈỊỌỎỐỒỔỖỘỚỜỞỠỢỤỦỨỪỬỮỰỲỴỶỸỺỼỾἈ-ἏἘ-ἝἨ-ἯἸ-ἿὈ-ὍὙὛὝὟὨ-ὯᾸ-ΆῈ-ΉῘ-ΊῨ-ῬῸ-Ώℂℇℋ-ℍℐ-ℒℕℙ-ℝℤΩℨK-ℭℰ-ℳℾ-ℿⅅↃⰀ-ⰮⱠⱢ-ⱤⱧⱩⱫⱭ-ⱰⱲⱵⱾ-ⲀⲂⲄⲆⲈⲊⲌⲎⲐⲒⲔⲖⲘⲚⲜⲞⲠⲢⲤⲦⲨⲪⲬⲮⲰⲲⲴⲶⲸⲺⲼⲾⳀⳂⳄⳆⳈⳊⳌⳎⳐⳒⳔⳖⳘⳚⳜⳞⳠⳢⳫⳭⳲꙀꙂꙄꙆꙈꙊꙌꙎꙐꙒꙔꙖꙘꙚꙜꙞꙠꙢꙤꙦꙨꙪꙬꚀꚂꚄꚆꚈꚊꚌꚎꚐꚒꚔꚖꚘꚚꜢꜤꜦꜨꜪꜬꜮꜲꜴꜶꜸꜺꜼꜾꝀꝂꝄꝆꝈꝊꝌꝎꝐꝒꝔꝖꝘꝚꝜꝞꝠꝢꝤꝦꝨꝪꝬꝮꝹꝻꝽ-ꝾꞀꞂꞄꞆꞋꞍꞐꞒꞖꞘꞚꞜꞞꞠꞢꞤꞦꞨꞪ-ꞮꞰ-ꞴꞶꞸA-Z𐐀-𐐧𐒰-𐓓𐲀-𐲲𑢠-𑢿𖹀-𖹟𝐀-𝐙𝐴-𝑍𝑨-𝒁𝒜𝒞-𝒟𝒢𝒥-𝒦𝒩-𝒬𝒮-𝒵𝓐-𝓩𝔄-𝔅𝔇-𝔊𝔍-𝔔𝔖-𝔜𝔸-𝔹𝔻-𝔾𝕀-𝕄𝕆𝕊-𝕐𝕬-𝖅𝖠-𝖹𝗔-𝗭𝘈-𝘡𝘼-𝙕𝙰-𝚉𝚨-𝛀𝛢-𝛺𝜜-𝜴𝝖-𝝮𝞐-𝞨𝟊𞤀-𞤡@^_]"><token type="LiteralStringEscape"/><pop depth="1"/></rule>
|
||||||
|
<rule pattern="NUL|SOH|[SE]TX|EOT|ENQ|ACK|BEL|BS|HT|LF|VT|FF|CR|S[OI]|DLE|DC[1-4]|NAK|SYN|ETB|CAN|EM|SUB|ESC|[FGRU]S|SP|DEL"><token type="LiteralStringEscape"/><pop depth="1"/></rule>
|
||||||
|
<rule pattern="o[0-7]+"><token type="LiteralStringEscape"/><pop depth="1"/></rule>
|
||||||
|
<rule pattern="x[\da-fA-F]+"><token type="LiteralStringEscape"/><pop depth="1"/></rule>
|
||||||
|
<rule pattern="\d+"><token type="LiteralStringEscape"/><pop depth="1"/></rule>
|
||||||
|
<rule pattern="(\s+)(\\)"><bygroups><token type="TextWhitespace"/><token type="LiteralStringEscape"/></bygroups><pop depth="1"/></rule>
|
||||||
|
</state>
|
||||||
|
</rules>
|
||||||
|
</lexer>
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
<lexer>
|
||||||
|
<config>
|
||||||
|
<name>AL</name>
|
||||||
|
<alias>al</alias>
|
||||||
|
<filename>*.al</filename>
|
||||||
|
<filename>*.dal</filename>
|
||||||
|
<mime_type>text/x-al</mime_type>
|
||||||
|
<case_insensitive>true</case_insensitive>
|
||||||
|
<dot_all>true</dot_all>
|
||||||
|
</config>
|
||||||
|
<rules>
|
||||||
|
<state name="root">
|
||||||
|
<rule pattern="\s+">
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(?s)\/\*.*?\\*\*\/">
|
||||||
|
<token type="CommentMultiline"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(?s)//.*?\n">
|
||||||
|
<token type="CommentSingle"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\"([^\"])*\"">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="'([^'])*'">
|
||||||
|
<token type="LiteralString"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b(?i:(ARRAY|ASSERTERROR|BEGIN|BREAK|CASE|DO|DOWNTO|ELSE|END|EVENT|EXIT|FOR|FOREACH|FUNCTION|IF|IMPLEMENTS|IN|INDATASET|INTERFACE|INTERNAL|LOCAL|OF|PROCEDURE|PROGRAM|PROTECTED|REPEAT|RUNONCLIENT|SECURITYFILTERING|SUPPRESSDISPOSE|TEMPORARY|THEN|TO|TRIGGER|UNTIL|VAR|WHILE|WITH|WITHEVENTS))\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b(?i:(AND|DIV|MOD|NOT|OR|XOR))\b">
|
||||||
|
<token type="OperatorWord"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b(?i:(AVERAGE|CONST|COUNT|EXIST|FIELD|FILTER|LOOKUP|MAX|MIN|ORDER|SORTING|SUM|TABLEDATA|UPPERLIMIT|WHERE|ASCENDING|DESCENDING))\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b(?i:(CODEUNIT|PAGE|PAGEEXTENSION|PAGECUSTOMIZATION|DOTNET|ENUM|ENUMEXTENSION|VALUE|QUERY|REPORT|TABLE|TABLEEXTENSION|XMLPORT|PROFILE|CONTROLADDIN|REPORTEXTENSION|INTERFACE|PERMISSIONSET|PERMISSIONSETEXTENSION|ENTITLEMENT))\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b(?i:(Action|Array|Automation|BigInteger|BigText|Blob|Boolean|Byte|Char|ClientType|Code|Codeunit|CompletionTriggerErrorLevel|ConnectionType|Database|DataClassification|DataScope|Date|DateFormula|DateTime|Decimal|DefaultLayout|Dialog|Dictionary|DotNet|DotNetAssembly|DotNetTypeDeclaration|Duration|Enum|ErrorInfo|ErrorType|ExecutionContext|ExecutionMode|FieldClass|FieldRef|FieldType|File|FilterPageBuilder|Guid|InStream|Integer|Joker|KeyRef|List|ModuleDependencyInfo|ModuleInfo|None|Notification|NotificationScope|ObjectType|Option|OutStream|Page|PageResult|Query|Record|RecordId|RecordRef|Report|ReportFormat|SecurityFilter|SecurityFiltering|Table|TableConnectionType|TableFilter|TestAction|TestField|TestFilterField|TestPage|TestPermissions|TestRequestPage|Text|TextBuilder|TextConst|TextEncoding|Time|TransactionModel|TransactionType|Variant|Verbosity|Version|XmlPort|HttpContent|HttpHeaders|HttpClient|HttpRequestMessage|HttpResponseMessage|JsonToken|JsonValue|JsonArray|JsonObject|View|Views|XmlAttribute|XmlAttributeCollection|XmlComment|XmlCData|XmlDeclaration|XmlDocument|XmlDocumentType|XmlElement|XmlNamespaceManager|XmlNameTable|XmlNode|XmlNodeList|XmlProcessingInstruction|XmlReadOptions|XmlText|XmlWriteOptions|WebServiceActionContext|WebServiceActionResultCode|SessionSettings))\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b([<>]=|<>|<|>)\b?">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b(\-|\+|\/|\*)\b">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\s*(\:=|\+=|-=|\/=|\*=)\s*?">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b(?i:(ADD|ADDFIRST|ADDLAST|ADDAFTER|ADDBEFORE|ACTION|ACTIONS|AREA|ASSEMBLY|CHARTPART|CUEGROUP|CUSTOMIZES|COLUMN|DATAITEM|DATASET|ELEMENTS|EXTENDS|FIELD|FIELDGROUP|FIELDATTRIBUTE|FIELDELEMENT|FIELDGROUPS|FIELDS|FILTER|FIXED|GRID|GROUP|MOVEAFTER|MOVEBEFORE|KEY|KEYS|LABEL|LABELS|LAYOUT|MODIFY|MOVEFIRST|MOVELAST|MOVEBEFORE|MOVEAFTER|PART|REPEATER|USERCONTROL|REQUESTPAGE|SCHEMA|SEPARATOR|SYSTEMPART|TABLEELEMENT|TEXTATTRIBUTE|TEXTELEMENT|TYPE))\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\s*[(\.\.)&\|]\s*">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b((0(x|X)[0-9a-fA-F]*)|(([0-9]+\.?[0-9]*)|(\.[0-9]+))((e|E)(\+|-)?[0-9]+)?)(L|l|UL|ul|u|U|F|f|ll|LL|ull|ULL)?\b">
|
||||||
|
<token type="LiteralNumber"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[;:,]">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="#[ \t]*(if|else|elif|endif|define|undef|region|endregion|pragma)\b.*?\n">
|
||||||
|
<token type="CommentPreproc"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\w+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=".">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
</rules>
|
||||||
|
</lexer>
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
|
||||||
|
<lexer>
|
||||||
|
<config>
|
||||||
|
<name>Alloy</name>
|
||||||
|
<alias>alloy</alias>
|
||||||
|
<filename>*.als</filename>
|
||||||
|
<mime_type>text/x-alloy</mime_type>
|
||||||
|
<dot_all>true</dot_all>
|
||||||
|
</config>
|
||||||
|
<rules>
|
||||||
|
<state name="sig">
|
||||||
|
<rule pattern="(extends)\b"><token type="Keyword"/><pop depth="1"/></rule>
|
||||||
|
<rule pattern="[a-zA-Z_][\w]*"*"><token type="Name"/></rule>
|
||||||
|
<rule pattern="[^\S\n]+"><token type="TextWhitespace"/></rule>
|
||||||
|
<rule pattern=","><token type="Punctuation"/></rule>
|
||||||
|
<rule pattern="\{"><token type="Operator"/><pop depth="1"/></rule>
|
||||||
|
</state>
|
||||||
|
<state name="module">
|
||||||
|
<rule pattern="[^\S\n]+"><token type="TextWhitespace"/></rule>
|
||||||
|
<rule pattern="[a-zA-Z_][\w]*"*"><token type="Name"/><pop depth="1"/></rule>
|
||||||
|
</state>
|
||||||
|
<state name="fun">
|
||||||
|
<rule pattern="[^\S\n]+"><token type="TextWhitespace"/></rule>
|
||||||
|
<rule pattern="\{"><token type="Operator"/><pop depth="1"/></rule>
|
||||||
|
<rule pattern="[a-zA-Z_][\w]*"*"><token type="Name"/><pop depth="1"/></rule>
|
||||||
|
</state>
|
||||||
|
<state name="fact">
|
||||||
|
<rule><include state="fun"/></rule>
|
||||||
|
<rule pattern=""\b(\\\\|\\[^\\]|[^"\\])*""><token type="LiteralString"/><pop depth="1"/></rule>
|
||||||
|
</state>
|
||||||
|
<state name="root">
|
||||||
|
<rule pattern="--.*?$"><token type="CommentSingle"/></rule>
|
||||||
|
<rule pattern="//.*?$"><token type="CommentSingle"/></rule>
|
||||||
|
<rule pattern="/\*.*?\*/"><token type="CommentMultiline"/></rule>
|
||||||
|
<rule pattern="[^\S\n]+"><token type="TextWhitespace"/></rule>
|
||||||
|
<rule pattern="(module|open)(\s+)"><bygroups><token type="KeywordNamespace"/><token type="TextWhitespace"/></bygroups><push state="module"/></rule>
|
||||||
|
<rule pattern="(sig|enum)(\s+)"><bygroups><token type="KeywordDeclaration"/><token type="TextWhitespace"/></bygroups><push state="sig"/></rule>
|
||||||
|
<rule pattern="(iden|univ|none)\b"><token type="KeywordConstant"/></rule>
|
||||||
|
<rule pattern="(int|Int)\b"><token type="KeywordType"/></rule>
|
||||||
|
<rule pattern="(var|this|abstract|extends|set|seq|one|lone|let)\b"><token type="Keyword"/></rule>
|
||||||
|
<rule pattern="(all|some|no|sum|disj|when|else)\b"><token type="Keyword"/></rule>
|
||||||
|
<rule pattern="(run|check|for|but|exactly|expect|as|steps)\b"><token type="Keyword"/></rule>
|
||||||
|
<rule pattern="(always|after|eventually|until|release)\b"><token type="Keyword"/></rule>
|
||||||
|
<rule pattern="(historically|before|once|since|triggered)\b"><token type="Keyword"/></rule>
|
||||||
|
<rule pattern="(and|or|implies|iff|in)\b"><token type="OperatorWord"/></rule>
|
||||||
|
<rule pattern="(fun|pred|assert)(\s+)"><bygroups><token type="Keyword"/><token type="TextWhitespace"/></bygroups><push state="fun"/></rule>
|
||||||
|
<rule pattern="(fact)(\s+)"><bygroups><token type="Keyword"/><token type="TextWhitespace"/></bygroups><push state="fact"/></rule>
|
||||||
|
<rule pattern="!|#|&&|\+\+|<<|>>|>=|<=>|<=|\.\.|\.|->"><token type="Operator"/></rule>
|
||||||
|
<rule pattern="[-+/*%=<>&!^|~{}\[\]().\';]"><token type="Operator"/></rule>
|
||||||
|
<rule pattern="[a-zA-Z_][\w]*"*"><token type="Name"/></rule>
|
||||||
|
<rule pattern="[:,]"><token type="Punctuation"/></rule>
|
||||||
|
<rule pattern="[0-9]+"><token type="LiteralNumberInteger"/></rule>
|
||||||
|
<rule pattern=""\b(\\\\|\\[^\\]|[^"\\])*""><token type="LiteralString"/></rule>
|
||||||
|
<rule pattern="\n"><token type="TextWhitespace"/></rule>
|
||||||
|
</state>
|
||||||
|
</rules>
|
||||||
|
</lexer>
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
<lexer>
|
||||||
|
<config>
|
||||||
|
<name>AMPL</name>
|
||||||
|
<alias>ampl</alias>
|
||||||
|
<filename>*.mod</filename>
|
||||||
|
<filename>*.run</filename>
|
||||||
|
<mime_type>text/x-ampl</mime_type>
|
||||||
|
<case_insensitive>false</case_insensitive>
|
||||||
|
<dot_all>true</dot_all>
|
||||||
|
</config>
|
||||||
|
<rules>
|
||||||
|
<state name="root">
|
||||||
|
<!-- Whitespace -->
|
||||||
|
<rule pattern="\s+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
<!-- Comments: '#' to end-of-line and C-style comments -->
|
||||||
|
<rule pattern="#[^\n]*">
|
||||||
|
<token type="Comment"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="/\*">
|
||||||
|
<token type="Comment"/>
|
||||||
|
<push state="comment"/>
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
<!-- Strings -->
|
||||||
|
<rule pattern=""(\\.|[^\\"])*"">
|
||||||
|
<token type="LiteralString"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="'(\\.|[^\\'])*'">
|
||||||
|
<token type="LiteralString"/>
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
<!-- Numbers -->
|
||||||
|
<rule pattern="\b\d+\.\d*([eE][+-]?\d+)?\b">
|
||||||
|
<token type="LiteralNumberFloat"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b\d+[eE][+-]?\d+\b">
|
||||||
|
<token type="LiteralNumberFloat"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b\d+\b">
|
||||||
|
<token type="LiteralNumberInteger"/>
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
<!-- Commands / Reserved keywords -->
|
||||||
|
<rule pattern="\b(call|cd|close|commands|data|delete|display|drop|end|environ|exit|expand|include|load|model|objective|option|problem|purge|quit|redeclare|reload|remove|reset|restore|shell|show|solexpand|solution|solve|update|unload|xref|coeff|coef|cover|obj|interval|default|from|to|to_come|net_in|net_out|dimen|dimension|check|complements|write|function|pipe|format|if|then|else|in|while|repeat|for)\b">
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
<!-- Types -->
|
||||||
|
<rule pattern="\b(integer|binary|symbolic|ordered|circular|reversed|INOUT|IN|OUT|LOCAL)\b">
|
||||||
|
<token type="KeywordType"/>
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
<!-- Declarations (set/param/var etc) -->
|
||||||
|
<rule pattern="\b(set|param|var|arc|minimize|maximize|subject to|s\.t\.|subj to|node|table|suffix|read table|write table)\b">
|
||||||
|
<token type="KeywordDeclaration"/>
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
<!-- Builtins / Functions -->
|
||||||
|
<rule pattern="\b(abs|acos|acosh|alias|asin|asinh|atan|atan2|atanh|ceil|ctime|cos|exp|floor|log|log10|max|min|precision|round|sin|sinh|sqrt|tan|tanh|time|trunc|Beta|Cauchy|Exponential|Gamma|Irand224|Normal|Normal01|Poisson|Uniform|Uniform01|num|num0|ichar|char|length|substr|sprintf|match|sub|gsub|print|printf|next|nextw|prev|prevw|first|last|ord|ord0|card|arity|indexarity)\b">
|
||||||
|
<token type="NameBuiltin"/>
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
<!-- Operator words (logical/set operators) -->
|
||||||
|
<rule pattern="\b(or|exists|forall|and|in|not|within|union|diff|difference|symdiff|inter|intersect|intersection|cross|setof|by|less|sum|prod|product|div|mod)\b">
|
||||||
|
<token type="OperatorWord"/>
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
<!-- Identifiers -->
|
||||||
|
<rule pattern="[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z0-9_]+)*">
|
||||||
|
<token type="Name"/>
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
<!-- Operators and punctuation -->
|
||||||
|
<rule pattern="\+|\-|\*|/|\*\*|=|<=|>=|==|\||\^|<|>|\!|\.\.|:=|\&|\!=|<<|>>">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[(),;:\[\]{}.]">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
|
||||||
|
<state name="comment">
|
||||||
|
<rule pattern="[^*]+">
|
||||||
|
<token type="Comment"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\*/">
|
||||||
|
<token type="Comment"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\*">
|
||||||
|
<token type="Comment"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
</rules>
|
||||||
|
</lexer>
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
<lexer>
|
||||||
|
<config>
|
||||||
|
<name>Angular2</name>
|
||||||
|
<alias>ng2</alias>
|
||||||
|
</config>
|
||||||
|
<rules>
|
||||||
|
<state name="attr">
|
||||||
|
<rule pattern="".*?"">
|
||||||
|
<token type="LiteralString" />
|
||||||
|
<pop depth="1" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="'.*?'">
|
||||||
|
<token type="LiteralString" />
|
||||||
|
<pop depth="1" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[^\s>]+">
|
||||||
|
<token type="LiteralString" />
|
||||||
|
<pop depth="1" />
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="root">
|
||||||
|
<rule pattern="[^{([*#]+">
|
||||||
|
<token type="Other" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(\{\{)(\s*)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="CommentPreproc" />
|
||||||
|
<token type="Text" />
|
||||||
|
</bygroups>
|
||||||
|
<push state="ngExpression" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="([([]+)([\w:.-]+)([\])]+)(\s*)(=)(\s*)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Punctuation" />
|
||||||
|
<token type="NameAttribute" />
|
||||||
|
<token type="Punctuation" />
|
||||||
|
<token type="Text" />
|
||||||
|
<token type="Operator" />
|
||||||
|
<token type="Text" />
|
||||||
|
</bygroups>
|
||||||
|
<push state="attr" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="([([]+)([\w:.-]+)([\])]+)(\s*)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Punctuation" />
|
||||||
|
<token type="NameAttribute" />
|
||||||
|
<token type="Punctuation" />
|
||||||
|
<token type="TextWhitespace" />
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="([*#])([\w:.-]+)(\s*)(=)(\s*)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Punctuation" />
|
||||||
|
<token type="NameAttribute" />
|
||||||
|
<token type="Punctuation" />
|
||||||
|
<token type="Operator" />
|
||||||
|
<token type="TextWhitespace" />
|
||||||
|
</bygroups>
|
||||||
|
<push state="attr" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="([*#])([\w:.-]+)(\s*)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Punctuation" />
|
||||||
|
<token type="NameAttribute" />
|
||||||
|
<token type="Punctuation" />
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="ngExpression">
|
||||||
|
<rule pattern="\s+(\|\s+)?">
|
||||||
|
<token type="Text" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\}\}">
|
||||||
|
<token type="CommentPreproc" />
|
||||||
|
<pop depth="1" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern=":?(true|false)">
|
||||||
|
<token type="LiteralStringBoolean" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern=":?"(\\\\|\\"|[^"])*"">
|
||||||
|
<token type="LiteralStringDouble" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern=":?'(\\\\|\\'|[^'])*'">
|
||||||
|
<token type="LiteralStringSingle" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[0-9](\.[0-9]*)?(eE[+-][0-9])?[flFLdD]?|0[xX][0-9a-fA-F]+[Ll]?">
|
||||||
|
<token type="LiteralNumber" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[a-zA-Z][\w-]*(\(.*\))?">
|
||||||
|
<token type="NameVariable" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\.[\w-]+(\(.*\))?">
|
||||||
|
<token type="NameVariable" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(\?)(\s*)([^}\s]+)(\s*)(:)(\s*)([^}\s]+)(\s*)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Operator" />
|
||||||
|
<token type="Text" />
|
||||||
|
<token type="LiteralString" />
|
||||||
|
<token type="Text" />
|
||||||
|
<token type="Operator" />
|
||||||
|
<token type="Text" />
|
||||||
|
<token type="LiteralString" />
|
||||||
|
<token type="Text" />
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
</rules>
|
||||||
|
</lexer>
|
||||||
@@ -0,0 +1,317 @@
|
|||||||
|
<lexer>
|
||||||
|
<config>
|
||||||
|
<name>ANTLR</name>
|
||||||
|
<alias>antlr</alias>
|
||||||
|
</config>
|
||||||
|
<rules>
|
||||||
|
<state name="nested-arg-action">
|
||||||
|
<rule pattern="([^$\[\]\'"/]+|"(\\\\|\\"|[^"])*"|'(\\\\|\\'|[^'])*'|//.*$\n?|/\*(.|\n)*?\*/|/(?!\*)(\\\\|\\/|[^/])*/|/)+">
|
||||||
|
<token type="Other"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\[">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<push/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\]">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(\$[a-zA-Z]+)(\.?)(text|value)?">
|
||||||
|
<bygroups>
|
||||||
|
<token type="NameVariable"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="NameProperty"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(\\\\|\\\]|\\\[|[^\[\]])+">
|
||||||
|
<token type="Other"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="exception">
|
||||||
|
<rule pattern="\n">
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\s">
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="comments"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\[">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<push state="nested-arg-action"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\{">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<push state="action"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="whitespace">
|
||||||
|
<rule pattern="\s+">
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="root">
|
||||||
|
<rule>
|
||||||
|
<include state="whitespace"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="comments"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(lexer|parser|tree)?(\s*)(grammar\b)(\s*)([A-Za-z]\w*)(;)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="NameClass"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="options\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<push state="options"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="tokens\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<push state="tokens"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(scope)(\s*)([A-Za-z]\w*)(\s*)(\{)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="NameVariable"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</bygroups>
|
||||||
|
<push state="action"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(catch|finally)\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<push state="exception"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(@[A-Za-z]\w*)(\s*)(::)?(\s*)([A-Za-z]\w*)(\s*)(\{)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="NameLabel"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="NameLabel"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</bygroups>
|
||||||
|
<push state="action"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="((?:protected|private|public|fragment)\b)?(\s*)([A-Za-z]\w*)(!)?">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="NameLabel"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</bygroups>
|
||||||
|
<push state="rule-alts" state="rule-prelims"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="tokens">
|
||||||
|
<rule>
|
||||||
|
<include state="whitespace"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="comments"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\{">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="([A-Z]\w*)(\s*)(=)?(\s*)(\'(?:\\\\|\\\'|[^\']*)\')?(\s*)(;)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="NameLabel"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="LiteralString"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\}">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="options">
|
||||||
|
<rule>
|
||||||
|
<include state="whitespace"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="comments"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\{">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="([A-Za-z]\w*)(\s*)(=)(\s*)([A-Za-z]\w*|\'(?:\\\\|\\\'|[^\']*)\'|[0-9]+|\*)(\s*)(;)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="NameVariable"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\}">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="rule-alts">
|
||||||
|
<rule>
|
||||||
|
<include state="whitespace"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="comments"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="options\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<push state="options"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=":">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="'(\\\\|\\'|[^'])*'">
|
||||||
|
<token type="LiteralString"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=""(\\\\|\\"|[^"])*"">
|
||||||
|
<token type="LiteralString"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="<<([^>]|>[^>])>>">
|
||||||
|
<token type="LiteralString"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\$?[A-Z_]\w*">
|
||||||
|
<token type="NameConstant"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\$?[a-z_]\w*">
|
||||||
|
<token type="NameVariable"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(\+|\||->|=>|=|\(|\)|\.\.|\.|\?|\*|\^|!|\#|~)">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=",">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\[">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<push state="nested-arg-action"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\{">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<push state="action"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=";">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="rule-prelims">
|
||||||
|
<rule>
|
||||||
|
<include state="whitespace"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="comments"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="returns\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\[">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<push state="nested-arg-action"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\{">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<push state="action"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(throws)(\s+)([A-Za-z]\w*)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="NameLabel"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(,)(\s*)([A-Za-z]\w*)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="NameLabel"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="options\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<push state="options"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(scope)(\s+)(\{)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</bygroups>
|
||||||
|
<push state="action"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(scope)(\s+)([A-Za-z]\w*)(\s*)(;)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="NameLabel"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(@[A-Za-z]\w*)(\s*)(\{)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="NameLabel"/>
|
||||||
|
<token type="TextWhitespace"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</bygroups>
|
||||||
|
<push state="action"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=":">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="action">
|
||||||
|
<rule pattern="([^${}\'"/\\]+|"(\\\\|\\"|[^"])*"|'(\\\\|\\'|[^'])*'|//.*$\n?|/\*(.|\n)*?\*/|/(?!\*)(\\\\|\\/|[^/])*/|\\(?!%)|/)+">
|
||||||
|
<token type="Other"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(\\)(%)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="Other"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(\$[a-zA-Z]+)(\.?)(text|value)?">
|
||||||
|
<bygroups>
|
||||||
|
<token type="NameVariable"/>
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<token type="NameProperty"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\{">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<push/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\}">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="comments">
|
||||||
|
<rule pattern="//.*$">
|
||||||
|
<token type="Comment"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="/\*(.|\n)*?\*/">
|
||||||
|
<token type="Comment"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
</rules>
|
||||||
|
</lexer>
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<lexer>
|
||||||
|
<config>
|
||||||
|
<name>ApacheConf</name>
|
||||||
|
<alias>apacheconf</alias>
|
||||||
|
<alias>aconf</alias>
|
||||||
|
<alias>apache</alias>
|
||||||
|
<filename>.htaccess</filename>
|
||||||
|
<filename>apache.conf</filename>
|
||||||
|
<filename>apache2.conf</filename>
|
||||||
|
<mime_type>text/x-apacheconf</mime_type>
|
||||||
|
<case_insensitive>true</case_insensitive>
|
||||||
|
</config>
|
||||||
|
<rules>
|
||||||
|
<state name="root">
|
||||||
|
<rule pattern="\s+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(#.*?)$">
|
||||||
|
<token type="Comment"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(<[^\s>]+)(?:(\s+)(.*?))?(>)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="NameTag"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="LiteralString"/>
|
||||||
|
<token type="NameTag"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="([a-z]\w*)(\s+)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="NameBuiltin"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
</bygroups>
|
||||||
|
<push state="value"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\.+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="value">
|
||||||
|
<rule pattern="\\\n">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="$">
|
||||||
|
<token type="Text"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\\">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[^\S\n]+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\d+\.\d+\.\d+\.\d+(?:/\d+)?">
|
||||||
|
<token type="LiteralNumber"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\d+">
|
||||||
|
<token type="LiteralNumber"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="/([a-z0-9][\w./-]+)">
|
||||||
|
<token type="LiteralStringOther"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(on|off|none|any|all|double|email|dns|min|minimal|os|productonly|full|emerg|alert|crit|error|warn|notice|info|debug|registry|script|inetd|standalone|user|group)\b">
|
||||||
|
<token type="Keyword"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=""([^"\\]*(?:\\.[^"\\]*)*)"">
|
||||||
|
<token type="LiteralStringDouble"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[^\s"\\]+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
</rules>
|
||||||
|
</lexer>
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<lexer>
|
||||||
|
<config>
|
||||||
|
<name>APL</name>
|
||||||
|
<alias>apl</alias>
|
||||||
|
<filename>*.apl</filename>
|
||||||
|
</config>
|
||||||
|
<rules>
|
||||||
|
<state name="root">
|
||||||
|
<rule pattern="\s+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[⍝#].*$">
|
||||||
|
<token type="CommentSingle"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\'((\'\')|[^\'])*\'">
|
||||||
|
<token type="LiteralStringSingle"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=""(("")|[^"])*"">
|
||||||
|
<token type="LiteralStringDouble"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[⋄◇()]">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[\[\];]">
|
||||||
|
<token type="LiteralStringRegex"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="⎕[A-Za-zΔ∆⍙][A-Za-zΔ∆⍙_¯0-9]*">
|
||||||
|
<token type="NameFunction"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[A-Za-zΔ∆⍙_][A-Za-zΔ∆⍙_¯0-9]*">
|
||||||
|
<token type="NameVariable"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="¯?(0[Xx][0-9A-Fa-f]+|[0-9]*\.?[0-9]+([Ee][+¯]?[0-9]+)?|¯|∞)([Jj]¯?(0[Xx][0-9A-Fa-f]+|[0-9]*\.?[0-9]+([Ee][+¯]?[0-9]+)?|¯|∞))?">
|
||||||
|
<token type="LiteralNumber"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[\.\\/⌿⍀¨⍣⍨⍠⍤∘⍥@⌺⌶⍢]">
|
||||||
|
<token type="NameAttribute"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[+\-×÷⌈⌊∣|⍳?*⍟○!⌹<≤=>≥≠≡≢∊⍷∪∩~∨∧⍱⍲⍴,⍪⌽⊖⍉↑↓⊂⊃⌷⍋⍒⊤⊥⍕⍎⊣⊢⍁⍂≈⌸⍯↗⊆⍸]">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="⍬">
|
||||||
|
<token type="NameConstant"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[⎕⍞]">
|
||||||
|
<token type="NameVariableGlobal"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[←→]">
|
||||||
|
<token type="KeywordDeclaration"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[⍺⍵⍶⍹∇:]">
|
||||||
|
<token type="NameBuiltinPseudo"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[{}]">
|
||||||
|
<token type="KeywordType"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
</rules>
|
||||||
|
</lexer>
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
<lexer>
|
||||||
|
<config>
|
||||||
|
<name>AppleScript</name>
|
||||||
|
<alias>applescript</alias>
|
||||||
|
<filename>*.applescript</filename>
|
||||||
|
<dot_all>true</dot_all>
|
||||||
|
</config>
|
||||||
|
<rules>
|
||||||
|
<state name="root">
|
||||||
|
<rule pattern="\s+">
|
||||||
|
<token type="Text" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="¬\n">
|
||||||
|
<token type="LiteralStringEscape" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="'s\s+">
|
||||||
|
<token type="Text" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(--|#).*?$">
|
||||||
|
<token type="Comment" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\(\*">
|
||||||
|
<token type="CommentMultiline" />
|
||||||
|
<push state="comment" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[(){}!,.:]">
|
||||||
|
<token type="Punctuation" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(«)([^»]+)(»)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Text" />
|
||||||
|
<token type="NameBuiltin" />
|
||||||
|
<token type="Text" />
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule
|
||||||
|
pattern="\b((?:considering|ignoring)\s*)(application responses|case|diacriticals|hyphens|numeric strings|punctuation|white space)"
|
||||||
|
>
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword" />
|
||||||
|
<token type="NameBuiltin" />
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(-|\*|\+|&|≠|>=?|<=?|=|≥|≤|/|÷|\^)">
|
||||||
|
<token type="Operator" />
|
||||||
|
</rule>
|
||||||
|
<rule
|
||||||
|
pattern="\b(and|or|is equal|equals|(is )?equal to|is not|isn't|isn't equal( to)?|is not equal( to)?|doesn't equal|does not equal|(is )?greater than|comes after|is not less than or equal( to)?|isn't less than or equal( to)?|(is )?less than|comes before|is not greater than or equal( to)?|isn't greater than or equal( to)?|(is )?greater than or equal( to)?|is not less than|isn't less than|does not come before|doesn't come before|(is )?less than or equal( to)?|is not greater than|isn't greater than|does not come after|doesn't come after|starts? with|begins? with|ends? with|contains?|does not contain|doesn't contain|is in|is contained by|is not in|is not contained by|isn't contained by|div|mod|not|(a )?(ref( to)?|reference to)|is|does)\b"
|
||||||
|
>
|
||||||
|
<token type="OperatorWord" />
|
||||||
|
</rule>
|
||||||
|
<rule
|
||||||
|
pattern="^(\s*(?:on|end)\s+)(zoomed|write to file|will zoom|will show|will select tab view item|will resize( sub views)?|will resign active|will quit|will pop up|will open|will move|will miniaturize|will hide|will finish launching|will display outline cell|will display item cell|will display cell|will display browser cell|will dismiss|will close|will become active|was miniaturized|was hidden|update toolbar item|update parameters|update menu item|shown|should zoom|should selection change|should select tab view item|should select row|should select item|should select column|should quit( after last window closed)?|should open( untitled)?|should expand item|should end editing|should collapse item|should close|should begin editing|selection changing|selection changed|selected tab view item|scroll wheel|rows changed|right mouse up|right mouse dragged|right mouse down|resized( sub views)?|resigned main|resigned key|resigned active|read from file|prepare table drop|prepare table drag|prepare outline drop|prepare outline drag|prepare drop|plugin loaded|parameters updated|panel ended|opened|open untitled|number of rows|number of items|number of browser rows|moved|mouse up|mouse moved|mouse exited|mouse entered|mouse dragged|mouse down|miniaturized|load data representation|launched|keyboard up|keyboard down|items changed|item value changed|item value|item expandable|idle|exposed|end editing|drop|drag( (entered|exited|updated))?|double clicked|document nib name|dialog ended|deminiaturized|data representation|conclude drop|column resized|column moved|column clicked|closed|clicked toolbar item|clicked|choose menu item|child of item|changed|change item value|change cell value|cell value changed|cell value|bounds changed|begin editing|became main|became key|awake from nib|alert ended|activated|action|accept table drop|accept outline drop)"
|
||||||
|
>
|
||||||
|
<token type="Keyword" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="^(\s*)(in|on|script|to)(\s+)">
|
||||||
|
<bygroups>
|
||||||
|
<token type="Text" />
|
||||||
|
<token type="Keyword" />
|
||||||
|
<token type="Text" />
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule
|
||||||
|
pattern="\b(as )(alias |application |boolean |class |constant |date |file |integer |list |number |POSIX file |real |record |reference |RGB color |script |text |unit types|(?:Unicode )?text|string)\b"
|
||||||
|
>
|
||||||
|
<bygroups>
|
||||||
|
<token type="Keyword" />
|
||||||
|
<token type="NameClass" />
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule
|
||||||
|
pattern="\b(AppleScript|current application|false|linefeed|missing value|pi|quote|result|return|space|tab|text item delimiters|true|version)\b"
|
||||||
|
>
|
||||||
|
<token type="NameConstant" />
|
||||||
|
</rule>
|
||||||
|
<rule
|
||||||
|
pattern="\b(ASCII (character|number)|activate|beep|choose URL|choose application|choose color|choose file( name)?|choose folder|choose from list|choose remote application|clipboard info|close( access)?|copy|count|current date|delay|delete|display (alert|dialog)|do shell script|duplicate|exists|get eof|get volume settings|info for|launch|list (disks|folder)|load script|log|make|mount volume|new|offset|open( (for access|location))?|path to|print|quit|random number|read|round|run( script)?|say|scripting components|set (eof|the clipboard to|volume)|store script|summarize|system attribute|system info|the clipboard|time to GMT|write|quoted form)\b"
|
||||||
|
>
|
||||||
|
<token type="NameBuiltin" />
|
||||||
|
</rule>
|
||||||
|
<rule
|
||||||
|
pattern="\b(considering|else|error|exit|from|if|ignoring|in|repeat|tell|then|times|to|try|until|using terms from|while|with|with timeout( of)?|with transaction|by|continue|end|its?|me|my|return|of|as)\b"
|
||||||
|
>
|
||||||
|
<token type="Keyword" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b(global|local|prop(erty)?|set|get)\b">
|
||||||
|
<token type="Keyword" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b(but|put|returning|the)\b">
|
||||||
|
<token type="NameBuiltin" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b(attachment|attribute run|character|day|month|paragraph|word|year)s?\b">
|
||||||
|
<token type="NameBuiltin" />
|
||||||
|
</rule>
|
||||||
|
<rule
|
||||||
|
pattern="\b(about|above|against|apart from|around|aside from|at|below|beneath|beside|between|for|given|instead of|on|onto|out of|over|since)\b"
|
||||||
|
>
|
||||||
|
<token type="NameBuiltin" />
|
||||||
|
</rule>
|
||||||
|
<rule
|
||||||
|
pattern="\b(accepts arrow key|action method|active|alignment|allowed identifiers|allows branch selection|allows column reordering|allows column resizing|allows column selection|allows customization|allows editing text attributes|allows empty selection|allows mixed state|allows multiple selection|allows reordering|allows undo|alpha( value)?|alternate image|alternate increment value|alternate title|animation delay|associated file name|associated object|auto completes|auto display|auto enables items|auto repeat|auto resizes( outline column)?|auto save expanded items|auto save name|auto save table columns|auto saves configuration|auto scroll|auto sizes all columns to fit|auto sizes cells|background color|bezel state|bezel style|bezeled|border rect|border type|bordered|bounds( rotation)?|box type|button returned|button type|can choose directories|can choose files|can draw|can hide|cell( (background color|size|type))?|characters|class|click count|clicked( data)? column|clicked data item|clicked( data)? row|closeable|collating|color( (mode|panel))|command key down|configuration|content(s| (size|view( margins)?))?|context|continuous|control key down|control size|control tint|control view|controller visible|coordinate system|copies( on scroll)?|corner view|current cell|current column|current( field)? editor|current( menu)? item|current row|current tab view item|data source|default identifiers|delta (x|y|z)|destination window|directory|display mode|displayed cell|document( (edited|rect|view))?|double value|dragged column|dragged distance|dragged items|draws( cell)? background|draws grid|dynamically scrolls|echos bullets|edge|editable|edited( data)? column|edited data item|edited( data)? row|enabled|enclosing scroll view|ending page|error handling|event number|event type|excluded from windows menu|executable path|expanded|fax number|field editor|file kind|file name|file type|first responder|first visible column|flipped|floating|font( panel)?|formatter|frameworks path|frontmost|gave up|grid color|has data items|has horizontal ruler|has horizontal scroller|has parent data item|has resize indicator|has shadow|has sub menu|has vertical ruler|has vertical scroller|header cell|header view|hidden|hides when deactivated|highlights by|horizontal line scroll|horizontal page scroll|horizontal ruler view|horizontally resizable|icon image|id|identifier|ignores multiple clicks|image( (alignment|dims when disabled|frame style|scaling))?|imports graphics|increment value|indentation per level|indeterminate|index|integer value|intercell spacing|item height|key( (code|equivalent( modifier)?|window))?|knob thickness|label|last( visible)? column|leading offset|leaf|level|line scroll|loaded|localized sort|location|loop mode|main( (bunde|menu|window))?|marker follows cell|matrix mode|maximum( content)? size|maximum visible columns|menu( form representation)?|miniaturizable|miniaturized|minimized image|minimized title|minimum column width|minimum( content)? size|modal|modified|mouse down state|movie( (controller|file|rect))?|muted|name|needs display|next state|next text|number of tick marks|only tick mark values|opaque|open panel|option key down|outline table column|page scroll|pages across|pages down|palette label|pane splitter|parent data item|parent window|pasteboard|path( (names|separator))?|playing|plays every frame|plays selection only|position|preferred edge|preferred type|pressure|previous text|prompt|properties|prototype cell|pulls down|rate|released when closed|repeated|requested print time|required file type|resizable|resized column|resource path|returns records|reuses columns|rich text|roll over|row height|rulers visible|save panel|scripts path|scrollable|selectable( identifiers)?|selected cell|selected( data)? columns?|selected data items?|selected( data)? rows?|selected item identifier|selection by rect|send action on arrow key|sends action when done editing|separates columns|separator item|sequence number|services menu|shared frameworks path|shared support path|sheet|shift key down|shows alpha|shows state by|size( mode)?|smart insert delete enabled|sort case sensitivity|sort column|sort order|sort type|sorted( data rows)?|sound|source( mask)?|spell checking enabled|starting page|state|string value|sub menu|super menu|super view|tab key traverses cells|tab state|tab type|tab view|table view|tag|target( printer)?|text color|text container insert|text container origin|text returned|tick mark position|time stamp|title(d| (cell|font|height|position|rect))?|tool tip|toolbar|trailing offset|transparent|treat packages as directories|truncated labels|types|unmodified characters|update views|use sort indicator|user defaults|uses data source|uses ruler|uses threaded animation|uses title from previous column|value wraps|version|vertical( (line scroll|page scroll|ruler view))?|vertically resizable|view|visible( document rect)?|volume|width|window|windows menu|wraps|zoomable|zoomed)\b"
|
||||||
|
>
|
||||||
|
<token type="NameAttribute" />
|
||||||
|
</rule>
|
||||||
|
<rule
|
||||||
|
pattern="\b(action cell|alert reply|application|box|browser( cell)?|bundle|button( cell)?|cell|clip view|color well|color-panel|combo box( item)?|control|data( (cell|column|item|row|source))?|default entry|dialog reply|document|drag info|drawer|event|font(-panel)?|formatter|image( (cell|view))?|matrix|menu( item)?|item|movie( view)?|open-panel|outline view|panel|pasteboard|plugin|popup button|progress indicator|responder|save-panel|scroll view|secure text field( cell)?|slider|sound|split view|stepper|tab view( item)?|table( (column|header cell|header view|view))|text( (field( cell)?|view))?|toolbar( item)?|user-defaults|view|window)s?\b"
|
||||||
|
>
|
||||||
|
<token type="NameBuiltin" />
|
||||||
|
</rule>
|
||||||
|
<rule
|
||||||
|
pattern="\b(animate|append|call method|center|close drawer|close panel|display|display alert|display dialog|display panel|go|hide|highlight|increment|item for|load image|load movie|load nib|load panel|load sound|localized string|lock focus|log|open drawer|path for|pause|perform action|play|register|resume|scroll|select( all)?|show|size to fit|start|step back|step forward|stop|synchronize|unlock focus|update)\b"
|
||||||
|
>
|
||||||
|
<token type="NameBuiltin" />
|
||||||
|
</rule>
|
||||||
|
<rule
|
||||||
|
pattern="\b((in )?back of|(in )?front of|[0-9]+(st|nd|rd|th)|first|second|third|fourth|fifth|sixth|seventh|eighth|ninth|tenth|after|back|before|behind|every|front|index|last|middle|some|that|through|thru|where|whose)\b"
|
||||||
|
>
|
||||||
|
<token type="NameBuiltin" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern=""(\\\\|\\"|[^"])*"">
|
||||||
|
<token type="LiteralStringDouble" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\b([a-zA-Z]\w*)\b">
|
||||||
|
<token type="NameVariable" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[-+]?(\d+\.\d*|\d*\.\d+)(E[-+][0-9]+)?">
|
||||||
|
<token type="LiteralNumberFloat" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[-+]?\d+">
|
||||||
|
<token type="LiteralNumberInteger" />
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="comment">
|
||||||
|
<rule pattern="\(\*">
|
||||||
|
<token type="CommentMultiline" />
|
||||||
|
<push />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\*\)">
|
||||||
|
<token type="CommentMultiline" />
|
||||||
|
<pop depth="1" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[^*(]+">
|
||||||
|
<token type="CommentMultiline" />
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[*(]">
|
||||||
|
<token type="CommentMultiline" />
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
</rules>
|
||||||
|
</lexer>
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
<lexer>
|
||||||
|
<config>
|
||||||
|
<name>ArangoDB AQL</name>
|
||||||
|
<alias>aql</alias>
|
||||||
|
<filename>*.aql</filename>
|
||||||
|
<mime_type>text/x-aql</mime_type>
|
||||||
|
<case_insensitive>true</case_insensitive>
|
||||||
|
<dot_all>true</dot_all>
|
||||||
|
<ensure_nl>true</ensure_nl>
|
||||||
|
</config>
|
||||||
|
<rules>
|
||||||
|
<state name="comments-and-whitespace">
|
||||||
|
<rule pattern="\s+">
|
||||||
|
<token type="Text"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="//.*?\n">
|
||||||
|
<token type="CommentSingle"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="/\*">
|
||||||
|
<token type="CommentMultiline"/>
|
||||||
|
<push state="multiline-comment"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="multiline-comment">
|
||||||
|
<rule pattern="[^*]+">
|
||||||
|
<token type="CommentMultiline"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\*/">
|
||||||
|
<token type="CommentMultiline"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="\*">
|
||||||
|
<token type="CommentMultiline"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="double-quote">
|
||||||
|
<rule pattern="\\.">
|
||||||
|
<token type="LiteralStringDouble"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[^"\\]+">
|
||||||
|
<token type="LiteralStringDouble"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=""">
|
||||||
|
<token type="LiteralStringDouble"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="single-quote">
|
||||||
|
<rule pattern="\\.">
|
||||||
|
<token type="LiteralStringSingle"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[^'\\]+">
|
||||||
|
<token type="LiteralStringSingle"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="'">
|
||||||
|
<token type="LiteralStringSingle"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="backtick">
|
||||||
|
<rule pattern="\\.">
|
||||||
|
<token type="Name"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[^`\\]+">
|
||||||
|
<token type="Name"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="`">
|
||||||
|
<token type="Name"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="forwardtick">
|
||||||
|
<rule pattern="\\.">
|
||||||
|
<token type="Name"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[^´\\]+">
|
||||||
|
<token type="Name"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="´">
|
||||||
|
<token type="Name"/>
|
||||||
|
<pop depth="1"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="identifier">
|
||||||
|
<rule pattern="(?:\$?|_+)[a-z]+[_a-z0-9]*">
|
||||||
|
<token type="Name"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="`">
|
||||||
|
<token type="Name"/>
|
||||||
|
<push state="backtick"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="´">
|
||||||
|
<token type="Name"/>
|
||||||
|
<push state="forwardtick"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
<state name="root">
|
||||||
|
<rule>
|
||||||
|
<include state="comments-and-whitespace"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="0b[01]+">
|
||||||
|
<token type="LiteralNumberBin"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="0x[0-9a-f]+">
|
||||||
|
<token type="LiteralNumberHex"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(?:0|[1-9][0-9]*)(?![\.e])">
|
||||||
|
<token type="LiteralNumberInteger"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(?:(?:0|[1-9][0-9]*)(?:\.[0-9]+)?|\.[0-9]+)(?:e[\-\+]?[0-9]+)?">
|
||||||
|
<token type="LiteralNumberFloat"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="@@(?:_+[a-z0-9]+[a-z0-9_]*|[a-z0-9][a-z0-9_]*)">
|
||||||
|
<token type="NameVariableGlobal"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="@(?:_+[a-z0-9]+[a-z0-9_]*|[a-z0-9][a-z0-9_]*)">
|
||||||
|
<token type="NameVariable"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="=~|!~|[=!<>]=?|[%?:/*+-]|\.\.|&&|\|\|">
|
||||||
|
<token type="Operator"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[.,(){}\[\]]">
|
||||||
|
<token type="Punctuation"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="[a-zA-Z0-9][a-zA-Z0-9_]*(?:::[a-zA-Z0-9_]+)+(?=\s*\()">
|
||||||
|
<token type="NameFunction"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(WITH)(\s+)(COUNT)(\s+)(INTO)\b">
|
||||||
|
<bygroups>
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="KeywordPseudo"/>
|
||||||
|
<token type="Text"/>
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
</bygroups>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(?:KEEP|PRUNE|SEARCH|TO)\b">
|
||||||
|
<token type="KeywordPseudo"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="OPTIONS(?=\s*\{)">
|
||||||
|
<token type="KeywordPseudo"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(?:AGGREGATE|ALL|ALL_SHORTEST_PATHS|AND|ANY|ASC|AT LEAST|COLLECT|DESC|DISTINCT|FILTER|FOR|GRAPH|IN|INBOUND|INSERT|INTO|K_PATHS|K_SHORTEST_PATHS|LIKE|LIMIT|NONE|NOT|OR|OUTBOUND|REMOVE|REPLACE|RETURN|SHORTEST_PATH|SORT|UPDATE|UPSERT|WITH|WINDOW)\b">
|
||||||
|
<token type="KeywordReserved"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="LET\b">
|
||||||
|
<token type="KeywordDeclaration"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(?:true|false|null)\b">
|
||||||
|
<token type="KeywordConstant"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(?-i)(?:CURRENT|NEW|OLD)\b">
|
||||||
|
<token type="NameBuiltinPseudo"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="(?:to_bool|to_number|to_char|to_string|to_array|to_list|is_null|is_bool|is_number|is_string|is_array|is_list|is_object|is_document|is_datestring|typename|json_stringify|json_parse|concat|concat_separator|char_length|lower|upper|substring|substring_bytes|left|right|trim|reverse|repeat|contains|log|log2|log10|exp|exp2|sin|cos|tan|asin|acos|atan|atan2|radians|degrees|pi|regex_test|regex_replace|like|floor|ceil|round|abs|rand|random|sqrt|pow|length|count|min|max|average|avg|sum|product|median|variance_population|variance_sample|variance|percentile|bit_and|bit_or|bit_xor|bit_negate|bit_test|bit_popcount|bit_shift_left|bit_shift_right|bit_construct|bit_deconstruct|bit_to_string|bit_from_string|first|last|unique|outersection|interleave|in_range|jaccard|matches|merge|merge_recursive|has|attributes|keys|values|entries|unset|unset_recursive|keep|keep_recursive|near|within|within_rectangle|is_in_polygon|distance|fulltext|stddev_sample|stddev_population|stddev|slice|nth|position|contains_array|translate|zip|call|apply|push|append|pop|shift|unshift|remove_value|remove_values|remove_nth|replace_nth|date_now|date_timestamp|date_iso8601|date_dayofweek|date_year|date_month|date_day|date_hour|date_minute|date_second|date_millisecond|date_dayofyear|date_isoweek|date_isoweekyear|date_leapyear|date_quarter|date_days_in_month|date_trunc|date_round|date_add|date_subtract|date_diff|date_compare|date_format|date_utctolocal|date_localtoutc|date_timezone|date_timezones|fail|passthru|v8|sleep|schema_get|schema_validate|shard_id|version|noopt|noeval|not_null|first_list|first_document|parse_identifier|parse_collection|parse_key|current_user|current_database|collection_count|pregel_result|collections|document|decode_rev|range|union|union_distinct|minus|intersection|flatten|is_same_collection|check_document|ltrim|rtrim|find_first|find_last|split|substitute|ipv4_to_number|ipv4_from_number|is_ipv4|md5|sha1|sha256|sha512|crc32|fnv64|hash|random_token|to_base64|to_hex|encode_uri_component|soundex|assert|warn|is_key|sorted|sorted_unique|count_distinct|count_unique|levenshtein_distance|levenshtein_match|regex_matches|regex_split|ngram_match|ngram_similarity|ngram_positional_similarity|uuid|tokens|exists|starts_with|phrase|min_match|bm25|tfidf|boost|analyzer|offset_info|value|cosine_similarity|decay_exp|decay_gauss|decay_linear|l1_distance|l2_distance|minhash|minhash_count|minhash_error|minhash_match|geo_point|geo_multipoint|geo_polygon|geo_multipolygon|geo_linestring|geo_multilinestring|geo_contains|geo_intersects|geo_equals|geo_distance|geo_area|geo_in_range)(?=\s*\()">
|
||||||
|
<token type="NameFunction"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern=""">
|
||||||
|
<token type="LiteralStringDouble"/>
|
||||||
|
<push state="double-quote"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="'">
|
||||||
|
<token type="LiteralStringSingle"/>
|
||||||
|
<push state="single-quote"/>
|
||||||
|
</rule>
|
||||||
|
<rule pattern="#\d+\b">
|
||||||
|
<token type="NameLabel"/>
|
||||||
|
</rule>
|
||||||
|
<rule>
|
||||||
|
<include state="identifier"/>
|
||||||
|
</rule>
|
||||||
|
</state>
|
||||||
|
</rules>
|
||||||
|
</lexer>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user