mirror of
https://github.com/hyprwm/Hyprland
synced 2025-12-20 09:30:15 +01:00
Compare commits
451 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
315806f598 | ||
|
|
6175ecd4c4 | ||
| f88deb928a | |||
|
|
18901b8e59 | ||
| 7098558420 | |||
|
|
59438908de | ||
| cbfdbe9fa1 | |||
| c94a981711 | |||
| beb1b578e8 | |||
| c5beecb2c3 | |||
|
|
6e09eb2e6c | ||
|
|
6b491e4d6b | ||
| 4036c37e55 | |||
|
|
7ccc57eb7c | ||
|
|
e4a8f2b14f | ||
|
|
6535ff07c9 | ||
|
|
05ccbb2f2d | ||
| 09e195d1f2 | |||
| fd5e790d08 | |||
|
|
69db0bcae6 | ||
|
|
8dfdcfb353 | ||
|
|
5700736505 | ||
|
|
75f6435f70 | ||
|
|
5dd224805d | ||
| 2ca7ad7efc | |||
| 9aa313402b | |||
|
|
1ff801f5f3 | ||
|
|
3cf6dfd7e6 | ||
|
|
f58c80fd39 | ||
|
|
6712fb954f | ||
| efe665b455 | |||
|
|
920353370b | ||
|
|
834f019bab | ||
| a5b7c91329 | |||
|
|
916e5d1aea | ||
|
|
9584b2d40e | ||
|
|
532ca053d6 | ||
|
|
ca99e8228c | ||
|
|
8ca40479a7 | ||
| c26e91f074 | |||
|
|
76ac655c9e | ||
| f8d5aad1a1 | |||
| b8bb5e9bde | |||
|
|
7797deb935 | ||
| d3c9c54b79 | |||
|
|
cedadf4fdc | ||
|
|
222dbe99d0 | ||
|
|
ebe74be75a | ||
| afeda6cee6 | |||
| 6a1daff5f3 | |||
|
|
016eb7a23d | ||
|
|
ec6756f961 | ||
|
|
9264436f35 | ||
|
|
d5c52ef58e | ||
|
|
52b3c8cbc6 | ||
|
|
279a07c2ce | ||
|
|
17ae3fb704 | ||
|
|
43ed0db3b3 | ||
|
|
38f912c401 | ||
|
|
9cd070fd31 | ||
|
|
d9657a95cb | ||
| 9b1891e476 | |||
|
|
93e5e92b0a | ||
|
|
3cf0280b11 | ||
|
|
2cadc8abab | ||
|
|
f82a8630d7 | ||
|
|
bb963fb002 | ||
|
|
f11cf6f1de | ||
| 574ee71d56 | |||
|
|
7e1e24fea6 | ||
|
|
68eecf61cd | ||
| f9742ab501 | |||
|
|
e42185b83d | ||
|
|
a51918fd27 | ||
|
|
379ee99c68 | ||
|
|
4036e35e73 | ||
|
|
d21f2e5729 | ||
|
|
4e5a284bc4 | ||
|
|
210930bef9 | ||
|
|
40d8fa8491 | ||
|
|
1c1746de61 | ||
|
|
619e9d285b | ||
|
|
ec3b3403e7 | ||
| 703394affb | |||
|
|
fe6a855bbb | ||
|
|
475e87b351 | ||
|
|
3d7ea9c02f | ||
|
|
2b0fd417d3 | ||
|
|
56904edbd2 | ||
|
|
e584a8bade | ||
| 2ac9ded2ac | |||
| abb2f7ee6f | |||
| 79a2781923 | |||
| d66c9222b0 | |||
|
|
b5a2ef77b7 | ||
|
|
c5d45b7653 | ||
|
|
c249a9f4b8 | ||
|
|
00cce1c8ff | ||
| 6b8e3358d6 | |||
|
|
80b96a3166 | ||
|
|
f9d1da6667 | ||
| 7532115318 | |||
|
|
1c29d6b1ba | ||
|
|
d0503bea43 | ||
| fbb31503f1 | |||
|
|
9f02dca8de | ||
|
|
6a8d306992 | ||
| e4b40abce6 | |||
|
|
9495f989b4 | ||
|
|
2c9c4d0905 | ||
| e15409bbeb | |||
|
|
312073ce79 | ||
| edc311544a | |||
| 37fe7b2efd | |||
|
|
c2670e9ab9 | ||
| 95ee08b340 | |||
|
|
ff6d771be0 | ||
| 64e4e913e1 | |||
|
|
ad52ba9c13 | ||
| 526aa1d020 | |||
| 5f0575737f | |||
|
|
4695f85829 | ||
|
|
1796dbcdc3 | ||
|
|
e354066945 | ||
|
|
2b14f27ca8 | ||
| 68c23fbdaf | |||
| 484d87d469 | |||
| cefa63c2af | |||
|
|
9d67511871 | ||
|
|
5265fa3be8 | ||
|
|
dfb4dcd55c | ||
|
|
9d02fe9c23 | ||
|
|
76edcfc66c | ||
|
|
11451d68b7 | ||
| 3534dbdb89 | |||
|
|
6e2fe103bc | ||
|
|
7910bc42af | ||
|
|
0770494ddf | ||
| 49c0c97c5a | |||
| e948445f6e | |||
|
|
7a6177532b | ||
| f0de61ca21 | |||
| c02a6184d3 | |||
| 15b4b1dd91 | |||
| a6b877fec2 | |||
| d2d1613e4f | |||
|
|
c7e14ecd30 | ||
|
|
9321f52e07 | ||
|
|
b04e8e00b0 | ||
|
|
5b373ea9f5 | ||
|
|
d52639fdfa | ||
|
|
e616e595ae | ||
|
|
cb47eb1d11 | ||
|
|
9b006b2c85 | ||
|
|
b35f78431f | ||
|
|
b62ab4b578 | ||
|
|
43527d3634 | ||
|
|
55a93b8a52 | ||
|
|
64ee8f8a72 | ||
| b77cbad502 | |||
|
|
308226a4fc | ||
|
|
ee2168c665 | ||
|
|
c330d4334f | ||
|
|
cadf922417 | ||
|
|
ac8edc6a80 | ||
|
|
b2ea6b010c | ||
| 0b1d690676 | |||
| 2931184921 | |||
|
|
0bd11d5eb9 | ||
|
|
06b37c3907 | ||
| 522edc8712 | |||
| f56ec180d3 | |||
|
|
fd50e78bc9 | ||
|
|
061981201d | ||
|
|
3fc8cb828c | ||
|
|
1ca6058bda | ||
|
|
ca4b68e425 | ||
|
|
8e8bfbb0b1 | ||
|
|
c757fd375c | ||
|
|
46b71eda64 | ||
|
|
d82538c69f | ||
|
|
8e9add2afd | ||
|
|
5e6cec962c | ||
|
|
6ade4d58ca | ||
| 83a0a62004 | |||
|
|
ff50dc36e9 | ||
|
|
ce9787b3f4 | ||
| 9eb82774e5 | |||
| a2f48ea418 | |||
| 309c3c7848 | |||
| 0907fdf49c | |||
| 431325ff0c | |||
| 40831a90a0 | |||
| b186d3bf1b | |||
| 560c53d87d | |||
| fd42e9d082 | |||
|
|
17d0d696be | ||
|
|
88e34d7dd2 | ||
| 05aa4e1c54 | |||
| 748d2f656e | |||
|
|
6ea4769b39 | ||
|
|
72cbb7906a | ||
|
|
b6f946991d | ||
|
|
b10b966000 | ||
|
|
da04afa44e | ||
|
|
34812c33db | ||
|
|
117e38db35 | ||
|
|
151b5f6978 | ||
|
|
aa5a239ac9 | ||
|
|
019589e23f | ||
|
|
057695bc3f | ||
|
|
892f642f58 | ||
| d560c26419 | |||
| 02b0c563f3 | |||
| 4926332c37 | |||
|
|
46dab01bcc | ||
|
|
474cd004df | ||
|
|
a4200acfa6 | ||
|
|
59ff7b2f89 | ||
|
|
ba077d8ff0 | ||
|
|
39d62e1487 | ||
|
|
6607c6440d | ||
|
|
f3e13193a6 | ||
|
|
8164b90bc2 | ||
| 36c0477dd0 | |||
|
|
ab11af9664 | ||
|
|
e40873be51 | ||
| 60529e810d | |||
| f324a3a564 | |||
| ee5d05f0fc | |||
| bbb83317c0 | |||
| 0d6d19b280 | |||
| 541ef60fd7 | |||
|
|
4b55ec6830 | ||
|
|
7fcaf332e8 | ||
| 6582f42db8 | |||
|
|
ed93643021 | ||
|
|
d599513d4a | ||
|
|
6a01c399a9 | ||
|
|
da31e82aab | ||
|
|
32f3233324 | ||
| 2b0926dcd4 | |||
|
|
b965fb2a40 | ||
|
|
82759d4095 | ||
|
|
0dc45b54f3 | ||
|
|
ba24547d3d | ||
|
|
5ba2d2217b | ||
| 5a20862126 | |||
| c3747fab56 | |||
| dc72259a54 | |||
|
|
02cda6bebf | ||
|
|
73f06434a4 | ||
|
|
17e77e0407 | ||
|
|
cfac27251a | ||
|
|
76d998743a | ||
|
|
b7ef892ecf | ||
|
|
f0b4164e2e | ||
|
|
3bcfa94ee4 | ||
|
|
c467bb2640 | ||
|
|
e0c96276df | ||
| 378438ffe7 | |||
|
|
13648d196a | ||
|
|
8c54c9b412 | ||
|
|
38c1e72c9d | ||
|
|
0959672591 | ||
|
|
4d82cc5957 | ||
|
|
43fb4753fc | ||
|
|
f854b5bffb | ||
|
|
eb25dfd399 | ||
|
|
b627885788 | ||
| c30036bdac | |||
|
|
766acadcf1 | ||
|
|
ef479ff539 | ||
|
|
6f1d2e771d | ||
|
|
ae445606e2 | ||
|
|
4f3dd1ddb4 | ||
|
|
d8f615751a | ||
|
|
7ce451d20c | ||
|
|
5212099b9f | ||
|
|
8cce3b98ce | ||
|
|
683fc77f80 | ||
|
|
ec9a72d9fb | ||
|
|
31bd9ec417 | ||
|
|
29b103c376 | ||
|
|
70a7047ee1 | ||
|
|
26f293523a | ||
|
|
45f007d412 | ||
|
|
22c8bc9b9b | ||
|
|
26cbc67385 | ||
|
|
41dad38177 | ||
|
|
838439080a | ||
|
|
6a88f2e880 | ||
|
|
8832607574 | ||
|
|
8fc7b2c171 | ||
|
|
afd1e71761 | ||
|
|
4fc95d646d | ||
| 91f592a875 | |||
| 059ec60e9f | |||
|
|
5648077978 | ||
|
|
1cb8cd3930 | ||
| 7fd6998f7c | |||
| 5e96fac52f | |||
| 4a9c4dbc04 | |||
| 9e74d0aea7 | |||
| 559024c331 | |||
|
|
16c18dde24 | ||
|
|
adbf7c8663 | ||
|
|
797bfe905e | ||
|
|
38169c8fdd | ||
|
|
c7b9969129 | ||
| 231b800784 | |||
| 8a959b4342 | |||
| 46174f78b3 | |||
|
|
b8cff8a434 | ||
|
|
150d693fe7 | ||
|
|
ecc9e4d8cd | ||
| 1e3a06560f | |||
|
|
b619f39555 | ||
|
|
02bb350bb3 | ||
|
|
bce43f74eb | ||
|
|
56dd1124ab | ||
| 4e785d12a9 | |||
|
|
127aab8159 | ||
|
|
78e86d879f | ||
|
|
00423bb738 | ||
|
|
8a64168a43 | ||
| 641d85b14e | |||
|
|
5bb8adbc32 | ||
|
|
ea42041f93 | ||
|
|
05a1c0aa73 | ||
|
|
790e544689 | ||
| a209f9911c | |||
|
|
4b2bfbd85f | ||
|
|
4e8657568c | ||
|
|
81bf4eccba | ||
|
|
378e130f14 | ||
| d7cf95b515 | |||
| 0ed880f3f7 | |||
|
|
b329ea8e96 | ||
|
|
ced38b1b0f | ||
|
|
d9cf1cb78e | ||
|
|
0d45b277d6 | ||
|
|
e95ba5bf59 | ||
|
|
4e8875b5e9 | ||
|
|
fdf1612f0f | ||
|
|
42caff5587 | ||
| 50a242f16a | |||
| 1ac1ff457a | |||
| 9a20206945 | |||
|
|
10cec2b7e2 | ||
|
|
d0d728c6a6 | ||
|
|
3370a6a83d | ||
|
|
1d67987459 | ||
|
|
1a0ed00f74 | ||
|
|
21953ddf3d | ||
|
|
d890178610 | ||
|
|
bca96a5d3b | ||
| dfe58c4809 | |||
| e8731883a5 | |||
|
|
0840103ae0 | ||
|
|
251288ec59 | ||
|
|
3d4dc19412 | ||
| 78c9e2080c | |||
|
|
1cbb62ed6a | ||
|
|
7580a9aaaa | ||
|
|
edc473e8b0 | ||
|
|
aaedce596e | ||
| 60d769a899 | |||
|
|
beee22a95e | ||
|
|
aa6a78f0a4 | ||
|
|
2b6e2ceb2e | ||
|
|
449d5e1113 | ||
| cb6589db98 | |||
|
|
584b844aaf | ||
|
|
69c3ab1a49 | ||
|
|
00da4450db | ||
|
|
afbd879685 | ||
|
|
6a1baa89b1 | ||
| a4529beb7c | |||
| d1c8dc5420 | |||
|
|
ec26b753a2 | ||
|
|
0c317f2508 | ||
|
|
3c6536d932 | ||
|
|
2859f1b795 | ||
|
|
2be309de1d | ||
|
|
6491bb4fb7 | ||
| 1b86d35f7e | |||
| 549f5e8dff | |||
| 0f1484c2f4 | |||
| f6d8e86439 | |||
| 61826dc7ac | |||
|
|
855d103aef | ||
| 77068c781d | |||
| bfe7e090bc | |||
| 824438949e | |||
|
|
f1f1161c17 | ||
| e1e23eb9bd | |||
|
|
c14f792f8f | ||
|
|
310fc629b0 | ||
|
|
314a0ea441 | ||
|
|
9607e3b5a8 | ||
|
|
a907ecd4ff | ||
|
|
3e35797b18 | ||
|
|
23be1db1e3 | ||
|
|
f309d86003 | ||
|
|
38e13282cd | ||
| 84c5e74459 | |||
|
|
36a8b2226f | ||
| 43966cc787 | |||
|
|
f51be7f201 | ||
| 66a6ef3859 | |||
| 745a671ce6 | |||
| abe29647ae | |||
|
|
c63d0003a1 | ||
|
|
5d4b4ecbfb | ||
|
|
211199e864 | ||
| e1fff05d0d | |||
|
|
5c8d675eed | ||
| fd0c1f2ab4 | |||
|
|
31cc7f3b87 | ||
|
|
ecc04e8ba7 | ||
| c51c6e38ac | |||
|
|
55f2daa21e | ||
|
|
2d2a5bebff | ||
|
|
6ca7c14b58 | ||
| fdbbad04bb | |||
|
|
873914a2a6 | ||
|
|
50758505d5 | ||
|
|
462729d865 | ||
|
|
bf1602d9f9 | ||
|
|
d4de69381e | ||
|
|
503fc458d8 | ||
| a3d59b525b | |||
|
|
b7a91e02e9 | ||
| 58b6eceb6d | |||
| 91d8a629eb | |||
| 8b38353012 | |||
| 3b04131259 | |||
|
|
d84699d8e5 | ||
|
|
ae3cc48f22 | ||
|
|
4adf658907 | ||
|
|
260a13a12f | ||
|
|
088e8af955 | ||
| 49abc193f7 | |||
| a05c797e4a | |||
|
|
b46dc9ee0c | ||
|
|
75c0675e14 | ||
| 49d73d1893 | |||
|
|
409b56f6a3 | ||
| 148718b3bc | |||
| 5349667992 |
473 changed files with 28071 additions and 13709 deletions
109
.clang-tidy
109
.clang-tidy
|
|
@ -1,4 +1,111 @@
|
||||||
WarningsAsErrors: '*'
|
WarningsAsErrors: >
|
||||||
|
-*,
|
||||||
|
bugprone-*,
|
||||||
|
-bugprone-multi-level-implicit-pointer-conversion,
|
||||||
|
-bugprone-empty-catch,
|
||||||
|
-bugprone-unused-return-value,
|
||||||
|
-bugprone-reserved-identifier,
|
||||||
|
-bugprone-switch-missing-default-case,
|
||||||
|
-bugprone-unused-local-non-trivial-variable,
|
||||||
|
-bugprone-easily-swappable-parameters,
|
||||||
|
-bugprone-forward-declararion-namespace,
|
||||||
|
-bugprone-forward-declararion-namespace,
|
||||||
|
-bugprone-macro-parentheses,
|
||||||
|
-bugprone-narrowing-conversions,
|
||||||
|
-bugprone-branch-clone,
|
||||||
|
-bugprone-assignment-in-if-condition,
|
||||||
|
concurrency-*,
|
||||||
|
-concurrency-mt-unsafe,
|
||||||
|
cppcoreguidelines-*,
|
||||||
|
-cppcoreguidelines-pro-type-const-cast,
|
||||||
|
-cppcoreguidelines-owning-memory,
|
||||||
|
-cppcoreguidelines-avoid-magic-numbers,
|
||||||
|
-cppcoreguidelines-pro-bounds-constant-array-index,
|
||||||
|
-cppcoreguidelines-avoid-const-or-ref-data-members,
|
||||||
|
-cppcoreguidelines-non-private-member-variables-in-classes,
|
||||||
|
-cppcoreguidelines-avoid-goto,
|
||||||
|
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
|
||||||
|
-cppcoreguidelines-avoid-do-while,
|
||||||
|
-cppcoreguidelines-avoid-non-const-global-variables,
|
||||||
|
-cppcoreguidelines-special-member-functions,
|
||||||
|
-cppcoreguidelines-explicit-virtual-functions,
|
||||||
|
-cppcoreguidelines-avoid-c-arrays,
|
||||||
|
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
|
||||||
|
-cppcoreguidelines-narrowing-conversions,
|
||||||
|
-cppcoreguidelines-pro-type-union-access,
|
||||||
|
-cppcoreguidelines-pro-type-member-init,
|
||||||
|
-cppcoreguidelines-macro-usage,
|
||||||
|
-cppcoreguidelines-macro-to-enum,
|
||||||
|
-cppcoreguidelines-init-variables,
|
||||||
|
-cppcoreguidelines-pro-type-cstyle-cast,
|
||||||
|
-cppcoreguidelines-pro-type-vararg,
|
||||||
|
-cppcoreguidelines-pro-type-reinterpret-cast,
|
||||||
|
-google-global-names-in-headers,
|
||||||
|
-google-readability-casting,
|
||||||
|
google-runtime-operator,
|
||||||
|
misc-*,
|
||||||
|
-misc-use-internal-linkage,
|
||||||
|
-misc-unused-parameters,
|
||||||
|
-misc-no-recursion,
|
||||||
|
-misc-non-private-member-variables-in-classes,
|
||||||
|
-misc-include-cleaner,
|
||||||
|
-misc-use-anonymous-namespace,
|
||||||
|
-misc-const-correctness,
|
||||||
|
modernize-*,
|
||||||
|
-modernize-use-emplace,
|
||||||
|
-modernize-redundant-void-arg,
|
||||||
|
-modernize-use-starts-ends-with,
|
||||||
|
-modernize-use-designated-initializers,
|
||||||
|
-modernize-use-std-numbers,
|
||||||
|
-modernize-return-braced-init-list,
|
||||||
|
-modernize-use-trailing-return-type,
|
||||||
|
-modernize-use-using,
|
||||||
|
-modernize-use-override,
|
||||||
|
-modernize-avoid-c-arrays,
|
||||||
|
-modernize-macro-to-enum,
|
||||||
|
-modernize-loop-convert,
|
||||||
|
-modernize-use-nodiscard,
|
||||||
|
-modernize-pass-by-value,
|
||||||
|
-modernize-use-auto,
|
||||||
|
performance-*,
|
||||||
|
-performance-inefficient-vector-operation,
|
||||||
|
-performance-inefficient-string-concatenation,
|
||||||
|
-performance-enum-size,
|
||||||
|
-performance-move-const-arg,
|
||||||
|
-performance-avoid-endl,
|
||||||
|
-performance-unnecessary-value-param,
|
||||||
|
portability-std-allocator-const,
|
||||||
|
readability-*,
|
||||||
|
-readability-identifier-naming,
|
||||||
|
-readability-use-std-min-max,
|
||||||
|
-readability-math-missing-parentheses,
|
||||||
|
-readability-simplify-boolean-expr,
|
||||||
|
-readability-static-accessed-through-instance,
|
||||||
|
-readability-use-anyofallof,
|
||||||
|
-readability-enum-initial-value,
|
||||||
|
-readability-redundant-inline-specifier,
|
||||||
|
-readability-function-cognitive-complexity,
|
||||||
|
-readability-function-size,
|
||||||
|
-readability-identifier-length,
|
||||||
|
-readability-magic-numbers,
|
||||||
|
-readability-uppercase-literal-suffix,
|
||||||
|
-readability-braces-around-statements,
|
||||||
|
-readability-redundant-access-specifiers,
|
||||||
|
-readability-else-after-return,
|
||||||
|
-readability-container-data-pointer,
|
||||||
|
-readability-implicit-bool-conversion,
|
||||||
|
-readability-avoid-nested-conditional-operator,
|
||||||
|
-readability-redundant-member-init,
|
||||||
|
-readability-redundant-string-init,
|
||||||
|
-readability-avoid-const-params-in-decls,
|
||||||
|
-readability-named-parameter,
|
||||||
|
-readability-convert-member-functions-to-static,
|
||||||
|
-readability-qualified-auto,
|
||||||
|
-readability-make-member-function-const,
|
||||||
|
-readability-isolate-declaration,
|
||||||
|
-readability-inconsistent-declaration-parameter-name,
|
||||||
|
-clang-diagnostic-error,
|
||||||
|
|
||||||
HeaderFilterRegex: '.*\.hpp'
|
HeaderFilterRegex: '.*\.hpp'
|
||||||
FormatStyle: file
|
FormatStyle: file
|
||||||
Checks: >
|
Checks: >
|
||||||
|
|
|
||||||
15
.github/actions/setup_base/action.yml
vendored
15
.github/actions/setup_base/action.yml
vendored
|
|
@ -24,6 +24,7 @@ runs:
|
||||||
glm \
|
glm \
|
||||||
glslang \
|
glslang \
|
||||||
go \
|
go \
|
||||||
|
gtest \
|
||||||
hyprlang \
|
hyprlang \
|
||||||
hyprcursor \
|
hyprcursor \
|
||||||
jq \
|
jq \
|
||||||
|
|
@ -45,6 +46,7 @@ runs:
|
||||||
libxkbfile \
|
libxkbfile \
|
||||||
lld \
|
lld \
|
||||||
meson \
|
meson \
|
||||||
|
muparser \
|
||||||
ninja \
|
ninja \
|
||||||
pango \
|
pango \
|
||||||
pixman \
|
pixman \
|
||||||
|
|
@ -74,16 +76,25 @@ runs:
|
||||||
cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
|
cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
|
||||||
cmake --install build
|
cmake --install build
|
||||||
|
|
||||||
- name: Get hyprgraphics-git
|
- name: Get hyprwire-git
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
git clone https://github.com/hyprwm/hyprgraphics && cd hyprgraphics && cmake -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -B build && cmake --build build --target hyprgraphics && cmake --install build
|
git clone https://github.com/hyprwm/hyprwire --recursive
|
||||||
|
cd hyprwire
|
||||||
|
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build
|
||||||
|
cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
|
||||||
|
cmake --install build
|
||||||
|
|
||||||
- name: Get hyprutils-git
|
- name: Get hyprutils-git
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
git clone https://github.com/hyprwm/hyprutils && cd hyprutils && cmake -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -B build && cmake --build build --target hyprutils && cmake --install build
|
git clone https://github.com/hyprwm/hyprutils && cd hyprutils && cmake -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -B build && cmake --build build --target hyprutils && cmake --install build
|
||||||
|
|
||||||
|
- name: Get hyprgraphics-git
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
git clone https://github.com/hyprwm/hyprgraphics && cd hyprgraphics && cmake -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -B build && cmake --build build --target hyprgraphics && cmake --install build
|
||||||
|
|
||||||
- name: Get aquamarine-git
|
- name: Get aquamarine-git
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
|
|
||||||
4
.github/labeler.yml
vendored
4
.github/labeler.yml
vendored
|
|
@ -22,6 +22,10 @@ protocols:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file: ["protocols/**", "src/protocols/**"]
|
- any-glob-to-any-file: ["protocols/**", "src/protocols/**"]
|
||||||
|
|
||||||
|
start:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: "start/**"
|
||||||
|
|
||||||
core:
|
core:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file: "src/**"
|
- any-glob-to-any-file: "src/**"
|
||||||
|
|
|
||||||
87
.github/workflows/ci.yaml
vendored
87
.github/workflows/ci.yaml
vendored
|
|
@ -21,22 +21,19 @@ jobs:
|
||||||
|
|
||||||
- name: Build Hyprland
|
- name: Build Hyprland
|
||||||
run: |
|
run: |
|
||||||
CFLAGS=-Werror CXXFLAGS=-Werror make all
|
CFLAGS=-Werror CXXFLAGS=-Werror make nopch
|
||||||
|
|
||||||
- name: Compress and package artifacts
|
- name: Compress and package artifacts
|
||||||
run: |
|
run: |
|
||||||
mkdir x86_64-pc-linux-gnu
|
mkdir x86_64-pc-linux-gnu
|
||||||
mkdir hyprland
|
mkdir hyprland
|
||||||
mkdir hyprland/example
|
|
||||||
mkdir hyprland/assets
|
|
||||||
cp ./LICENSE hyprland/
|
cp ./LICENSE hyprland/
|
||||||
cp build/Hyprland hyprland/
|
cp build/Hyprland hyprland/
|
||||||
cp build/hyprctl/hyprctl hyprland/
|
cp build/hyprctl/hyprctl hyprland/
|
||||||
cp build/hyprpm/hyprpm hyprland/
|
cp build/hyprpm/hyprpm hyprland/
|
||||||
cp build/Hyprland hyprland/
|
|
||||||
cp -r example/ hyprland/
|
cp -r example/ hyprland/
|
||||||
cp -r assets/ hyprland/
|
cp -r assets/ hyprland/
|
||||||
tar -cvf Hyprland.tar.xz hyprland
|
tar -cvJf Hyprland.tar.xz hyprland
|
||||||
|
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
|
|
@ -44,86 +41,16 @@ jobs:
|
||||||
name: Build archive
|
name: Build archive
|
||||||
path: Hyprland.tar.xz
|
path: Hyprland.tar.xz
|
||||||
|
|
||||||
meson:
|
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork
|
|
||||||
name: "Build Hyprland with Meson (Arch)"
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container:
|
|
||||||
image: archlinux
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository actions
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
sparse-checkout: .github/actions
|
|
||||||
|
|
||||||
- name: Setup base
|
|
||||||
uses: ./.github/actions/setup_base
|
|
||||||
|
|
||||||
- name: Configure
|
|
||||||
run: meson setup build -Ddefault_library=static
|
|
||||||
|
|
||||||
- name: Compile
|
|
||||||
run: ninja -C build
|
|
||||||
|
|
||||||
no-pch:
|
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork
|
|
||||||
name: "Build Hyprland without precompiled headers (Arch)"
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container:
|
|
||||||
image: archlinux
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository actions
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
sparse-checkout: .github/actions
|
|
||||||
|
|
||||||
- name: Setup base
|
|
||||||
uses: ./.github/actions/setup_base
|
|
||||||
with:
|
|
||||||
INSTALL_XORG_PKGS: true
|
|
||||||
|
|
||||||
- name: Compile
|
|
||||||
run: make nopch
|
|
||||||
|
|
||||||
noxwayland:
|
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork
|
|
||||||
name: "Build Hyprland in pure Wayland (Arch)"
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container:
|
|
||||||
image: archlinux
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository actions
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
sparse-checkout: .github/actions
|
|
||||||
|
|
||||||
- name: Setup base
|
|
||||||
uses: ./.github/actions/setup_base
|
|
||||||
|
|
||||||
- name: Configure
|
|
||||||
run: mkdir -p build && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DNO_XWAYLAND:STRING=true -H./ -B./build -G Ninja
|
|
||||||
|
|
||||||
- name: Compile
|
|
||||||
run: make release
|
|
||||||
|
|
||||||
clang-format:
|
clang-format:
|
||||||
permissions: read-all
|
permissions: read-all
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork
|
||||||
name: "Code Style (Arch)"
|
name: "Code Style"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
|
||||||
image: archlinux
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository actions
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
sparse-checkout: .github/actions
|
|
||||||
|
|
||||||
- name: Setup base
|
|
||||||
uses: ./.github/actions/setup_base
|
|
||||||
|
|
||||||
- name: Configure
|
|
||||||
run: meson setup build -Ddefault_library=static
|
|
||||||
|
|
||||||
- name: clang-format check
|
- name: clang-format check
|
||||||
run: ninja -C build clang-format-check
|
uses: jidicula/clang-format-action@v4.16.0
|
||||||
|
with:
|
||||||
|
exclude-regex: ^subprojects$
|
||||||
|
|
|
||||||
36
.github/workflows/clang-format.yml
vendored
36
.github/workflows/clang-format.yml
vendored
|
|
@ -4,43 +4,23 @@ jobs:
|
||||||
clang-format:
|
clang-format:
|
||||||
permissions: write-all
|
permissions: write-all
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork
|
||||||
name: "Code Style (Arch)"
|
name: "Code Style"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
|
||||||
image: archlinux
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository actions
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
sparse-checkout: .github/actions
|
|
||||||
|
|
||||||
- name: Setup base
|
|
||||||
uses: ./.github/actions/setup_base
|
|
||||||
|
|
||||||
- name: Configure
|
|
||||||
run: meson setup build -Ddefault_library=static
|
|
||||||
|
|
||||||
- name: clang-format check
|
- name: clang-format check
|
||||||
run: ninja -C build clang-format-check
|
uses: jidicula/clang-format-action@v4.16.0
|
||||||
|
with:
|
||||||
|
exclude-regex: ^subprojects$
|
||||||
|
|
||||||
- name: clang-format apply
|
- name: Create comment
|
||||||
if: ${{ failure() && github.event_name == 'pull_request' }}
|
|
||||||
run: ninja -C build clang-format
|
|
||||||
|
|
||||||
- name: Create patch
|
|
||||||
if: ${{ failure() && github.event_name == 'pull_request' }}
|
if: ${{ failure() && github.event_name == 'pull_request' }}
|
||||||
run: |
|
run: |
|
||||||
echo 'Please fix the formatting issues by running [`clang-format`](https://wiki.hyprland.org/Contributing-and-Debugging/PR-Guidelines/#code-style), or directly apply this patch:' > clang-format.patch
|
echo 'Please fix the formatting issues by running [`clang-format`](https://wiki.hyprland.org/Contributing-and-Debugging/PR-Guidelines/#code-style).' > clang-format.patch
|
||||||
echo '<details>' >> clang-format.patch
|
|
||||||
echo '<summary>clang-format.patch</summary>' >> clang-format.patch
|
|
||||||
echo >> clang-format.patch
|
|
||||||
echo '```diff' >> clang-format.patch
|
|
||||||
git diff >> clang-format.patch
|
|
||||||
echo '```' >> clang-format.patch
|
|
||||||
echo >> clang-format.patch
|
|
||||||
echo '</details>' >> clang-format.patch
|
|
||||||
|
|
||||||
- name: Comment patch
|
- name: Post comment
|
||||||
if: ${{ failure() && github.event_name == 'pull_request' }}
|
if: ${{ failure() && github.event_name == 'pull_request' }}
|
||||||
uses: mshick/add-pr-comment@v2
|
uses: mshick/add-pr-comment@v2
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
45
.github/workflows/new-pr-comment.yml
vendored
Normal file
45
.github/workflows/new-pr-comment.yml
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
name: "New MR welcome comment"
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
comment:
|
||||||
|
if: >
|
||||||
|
github.event.pull_request.user.login != 'vaxerski' &&
|
||||||
|
github.event.pull_request.user.login != 'fufexan' &&
|
||||||
|
github.event.pull_request.user.login != 'gulafaran' &&
|
||||||
|
github.event.pull_request.user.login != 'ujint34' &&
|
||||||
|
github.event.pull_request.user.login != 'paideiadilemma' &&
|
||||||
|
github.event.pull_request.user.login != 'notashelf'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
env:
|
||||||
|
PR_COMMENT: |
|
||||||
|
Hello and thank you for making a PR to Hyprland!
|
||||||
|
|
||||||
|
Please check the [PR Guidelines](https://wiki.hypr.land/Contributing-and-Debugging/PR-Guidelines/) and make sure your PR follows them.
|
||||||
|
It will make the entire review process faster. :)
|
||||||
|
|
||||||
|
If your code can be tested, please always add tests. See more [here](https://wiki.hypr.land/Contributing-and-Debugging/Tests/).
|
||||||
|
|
||||||
|
_beep boop, I'm just a bot. A real human will review your PR soon._
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Add comment to PR
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
script: |
|
||||||
|
const pr = context.payload.pull_request;
|
||||||
|
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: pr.number,
|
||||||
|
body: process.env.PR_COMMENT,
|
||||||
|
});
|
||||||
5
.github/workflows/nix-ci.yml
vendored
5
.github/workflows/nix-ci.yml
vendored
|
|
@ -13,7 +13,7 @@ jobs:
|
||||||
uses: ./.github/workflows/nix.yml
|
uses: ./.github/workflows/nix.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
with:
|
with:
|
||||||
command: nix build 'github:hyprwm/Hyprland?ref=${{ github.ref }}' -L --extra-substituters "https://hyprland.cachix.org"
|
command: nix build 'github:${{ github.repository }}?ref=${{ github.ref }}' -L --extra-substituters "https://hyprland.cachix.org"
|
||||||
|
|
||||||
xdph:
|
xdph:
|
||||||
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork)
|
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork)
|
||||||
|
|
@ -21,10 +21,9 @@ jobs:
|
||||||
uses: ./.github/workflows/nix.yml
|
uses: ./.github/workflows/nix.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
with:
|
with:
|
||||||
command: nix build 'github:hyprwm/Hyprland?ref=${{ github.ref }}#xdg-desktop-portal-hyprland' -L --extra-substituters "https://hyprland.cachix.org"
|
command: nix build 'github:${{ github.repository }}?ref=${{ github.ref }}#xdg-desktop-portal-hyprland' -L --extra-substituters "https://hyprland.cachix.org"
|
||||||
|
|
||||||
test:
|
test:
|
||||||
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork)
|
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork)
|
||||||
needs: hyprland
|
|
||||||
uses: ./.github/workflows/nix-test.yml
|
uses: ./.github/workflows/nix-test.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
|
||||||
18
.github/workflows/nix-test.yml
vendored
18
.github/workflows/nix-test.yml
vendored
|
|
@ -20,25 +20,13 @@ jobs:
|
||||||
- name: Restore and save Nix store
|
- name: Restore and save Nix store
|
||||||
uses: nix-community/cache-nix-action@v6
|
uses: nix-community/cache-nix-action@v6
|
||||||
with:
|
with:
|
||||||
# restore and save a cache using this key
|
# restore and save a cache using this key (per job)
|
||||||
primary-key: nix-${{ runner.os }}
|
primary-key: nix-${{ runner.os }}-${{ github.job }}
|
||||||
# if there's no cache hit, restore a cache by this prefix
|
# if there's no cache hit, restore a cache by this prefix
|
||||||
restore-prefixes-first-match: nix-${{ runner.os }}
|
restore-prefixes-first-match: nix-${{ runner.os }}
|
||||||
# collect garbage until the Nix store size (in bytes) is at most this number
|
# collect garbage until the Nix store size (in bytes) is at most this number
|
||||||
# before trying to save a new cache
|
# before trying to save a new cache
|
||||||
# 1G = 1073741824
|
|
||||||
gc-max-store-size-linux: 5G
|
gc-max-store-size-linux: 5G
|
||||||
# do purge caches
|
|
||||||
purge: true
|
|
||||||
# purge all versions of the cache
|
|
||||||
purge-prefixes: nix-${{ runner.os }}
|
|
||||||
# created more than this number of seconds ago
|
|
||||||
purge-created: 0
|
|
||||||
# or, last accessed more than this number of seconds ago
|
|
||||||
# relative to the start of the `Post Restore and save Nix store` phase
|
|
||||||
purge-last-accessed: 0
|
|
||||||
# except any version with the key that is the same as the `primary-key`
|
|
||||||
purge-primary-key: never
|
|
||||||
|
|
||||||
- uses: cachix/cachix-action@v15
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
|
|
@ -46,7 +34,7 @@ jobs:
|
||||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||||
|
|
||||||
- name: Run test VM
|
- name: Run test VM
|
||||||
run: nix build 'github:hyprwm/Hyprland?ref=${{ github.ref }}#checks.x86_64-linux.tests' -L --extra-substituters "https://hyprland.cachix.org"
|
run: nix build 'github:${{ github.repository }}?ref=${{ github.ref }}#checks.x86_64-linux.tests' -L --extra-substituters "https://hyprland.cachix.org"
|
||||||
|
|
||||||
- name: Check exit status
|
- name: Check exit status
|
||||||
run: grep 0 result/exit_status
|
run: grep 0 result/exit_status
|
||||||
|
|
|
||||||
20
.github/workflows/nix-update-inputs.yml
vendored
20
.github/workflows/nix-update-inputs.yml
vendored
|
|
@ -27,25 +27,13 @@ jobs:
|
||||||
- name: Restore and save Nix store
|
- name: Restore and save Nix store
|
||||||
uses: nix-community/cache-nix-action@v6
|
uses: nix-community/cache-nix-action@v6
|
||||||
with:
|
with:
|
||||||
# restore and save a cache using this key
|
# restore and save a cache using this key (per job)
|
||||||
primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
|
primary-key: nix-${{ runner.os }}-${{ github.job }}
|
||||||
# if there's no cache hit, restore a cache by this prefix
|
# if there's no cache hit, restore a cache by this prefix
|
||||||
restore-prefixes-first-match: nix-${{ runner.os }}-
|
restore-prefixes-first-match: nix-${{ runner.os }}
|
||||||
# collect garbage until the Nix store size (in bytes) is at most this number
|
# collect garbage until the Nix store size (in bytes) is at most this number
|
||||||
# before trying to save a new cache
|
# before trying to save a new cache
|
||||||
# 1G = 1073741824
|
gc-max-store-size-linux: 5G
|
||||||
gc-max-store-size-linux: 1G
|
|
||||||
# do purge caches
|
|
||||||
purge: true
|
|
||||||
# purge all versions of the cache
|
|
||||||
purge-prefixes: nix-${{ runner.os }}-
|
|
||||||
# created more than this number of seconds ago
|
|
||||||
purge-created: 0
|
|
||||||
# or, last accessed more than this number of seconds ago
|
|
||||||
# relative to the start of the `Post Restore and save Nix store` phase
|
|
||||||
purge-last-accessed: 0
|
|
||||||
# except any version with the key that is the same as the `primary-key`
|
|
||||||
purge-primary-key: never
|
|
||||||
|
|
||||||
- name: Update inputs
|
- name: Update inputs
|
||||||
run: nix/update-inputs.sh
|
run: nix/update-inputs.sh
|
||||||
|
|
|
||||||
16
.github/workflows/nix.yml
vendored
16
.github/workflows/nix.yml
vendored
|
|
@ -25,25 +25,13 @@ jobs:
|
||||||
- name: Restore and save Nix store
|
- name: Restore and save Nix store
|
||||||
uses: nix-community/cache-nix-action@v6
|
uses: nix-community/cache-nix-action@v6
|
||||||
with:
|
with:
|
||||||
# restore and save a cache using this key
|
# restore and save a cache using this key (per job)
|
||||||
primary-key: nix-${{ runner.os }}
|
primary-key: nix-${{ runner.os }}-${{ github.job }}
|
||||||
# if there's no cache hit, restore a cache by this prefix
|
# if there's no cache hit, restore a cache by this prefix
|
||||||
restore-prefixes-first-match: nix-${{ runner.os }}
|
restore-prefixes-first-match: nix-${{ runner.os }}
|
||||||
# collect garbage until the Nix store size (in bytes) is at most this number
|
# collect garbage until the Nix store size (in bytes) is at most this number
|
||||||
# before trying to save a new cache
|
# before trying to save a new cache
|
||||||
# 1G = 1073741824
|
|
||||||
gc-max-store-size-linux: 5G
|
gc-max-store-size-linux: 5G
|
||||||
# do purge caches
|
|
||||||
purge: true
|
|
||||||
# purge all versions of the cache
|
|
||||||
purge-prefixes: nix-${{ runner.os }}
|
|
||||||
# created more than this number of seconds ago
|
|
||||||
purge-created: 0
|
|
||||||
# or, last accessed more than this number of seconds ago
|
|
||||||
# relative to the start of the `Post Restore and save Nix store` phase
|
|
||||||
purge-last-accessed: 0
|
|
||||||
# except any version with the key that is the same as the `primary-key`
|
|
||||||
purge-primary-key: never
|
|
||||||
|
|
||||||
- uses: cachix/cachix-action@v15
|
- uses: cachix/cachix-action@v15
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
33
.github/workflows/release.yaml
vendored
33
.github/workflows/release.yaml
vendored
|
|
@ -9,17 +9,36 @@ jobs:
|
||||||
source-tarball:
|
source-tarball:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Hyprland
|
- name: Checkout repository
|
||||||
id: checkout
|
uses: actions/checkout@v5
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
- name: Generate version
|
- name: Populate git info in version.h.in
|
||||||
id: genversion
|
|
||||||
run: |
|
run: |
|
||||||
git fetch --unshallow || echo "failed unshallowing"
|
git fetch --tags --unshallow || true
|
||||||
bash -c scripts/generateVersion.sh
|
|
||||||
|
COMMIT_HASH=$(git rev-parse HEAD)
|
||||||
|
BRANCH="${GITHUB_REF_NAME:-$(git rev-parse --abbrev-ref HEAD)}"
|
||||||
|
COMMIT_MSG=$(git show -s --format=%s | sed 's/[&/]/\\&/g')
|
||||||
|
COMMIT_DATE=$(git show -s --format=%cd --date=local)
|
||||||
|
GIT_DIRTY=$(git diff-index --quiet HEAD -- && echo "clean" || echo "dirty")
|
||||||
|
GIT_TAG=$(git describe --tags --always || echo "unknown")
|
||||||
|
GIT_COMMITS=$(git rev-list --count HEAD)
|
||||||
|
|
||||||
|
echo "Branch: $BRANCH"
|
||||||
|
echo "Tag: $GIT_TAG"
|
||||||
|
|
||||||
|
sed -i \
|
||||||
|
-e "s|@GIT_COMMIT_HASH@|$COMMIT_HASH|" \
|
||||||
|
-e "s|@GIT_BRANCH@|$BRANCH|" \
|
||||||
|
-e "s|@GIT_COMMIT_MESSAGE@|$COMMIT_MSG|" \
|
||||||
|
-e "s|@GIT_COMMIT_DATE@|$COMMIT_DATE|" \
|
||||||
|
-e "s|@GIT_DIRTY@|$GIT_DIRTY|" \
|
||||||
|
-e "s|@GIT_TAG@|$GIT_TAG|" \
|
||||||
|
-e "s|@GIT_COMMITS@|$GIT_COMMITS|" \
|
||||||
|
src/version.h.in
|
||||||
|
|
||||||
- name: Create tarball with submodules
|
- name: Create tarball with submodules
|
||||||
id: tar
|
id: tar
|
||||||
|
|
|
||||||
139
.github/workflows/translation-ai-check.yml
vendored
Normal file
139
.github/workflows/translation-ai-check.yml
vendored
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
name: AI Translation Check
|
||||||
|
|
||||||
|
on:
|
||||||
|
# pull_request_target:
|
||||||
|
# types:
|
||||||
|
# - opened
|
||||||
|
issue_comment:
|
||||||
|
types:
|
||||||
|
- created
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
issues: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
review:
|
||||||
|
name: Review Translation
|
||||||
|
if: ${{ github.event_name == 'pull_request_target' || (github.event_name == 'issue_comment' && github.event.action == 'created' && github.event.issue.pull_request != null && github.event.comment.user.login == 'vaxerski' && github.event.comment.body == 'ai, please recheck' ) }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
OPENAI_MODEL: gpt-5-mini
|
||||||
|
SYSTEM_PROMPT: |
|
||||||
|
You are a programmer and a translator. Your job is to review the attached patch for adding translation to a piece of software and make sure the submitted translation is not malicious, and that it makes sense. If the translation is not malicious, and doesn't contain obvious grammatical mistakes, say "Translation check OK". Otherwise, say "Translation check not ok" and list bad entries.
|
||||||
|
Examples of bad translations include obvious trolling (slurs, etc) or nonsense sentences. Meaningful improvements may be suggested, but if there are only minor improvements, just reply with "Translation check OK". Do not provide anything but the result and (if applicable) the bad entries or improvements.
|
||||||
|
|
||||||
|
AI_PROMPT: Translation patch below.
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- uses: dorny/paths-filter@v3
|
||||||
|
id: changes
|
||||||
|
with:
|
||||||
|
filters: |
|
||||||
|
i18n:
|
||||||
|
- 'src/i18n/**'
|
||||||
|
|
||||||
|
- name: Stop if i18n not changed
|
||||||
|
if: steps.changes.outputs.i18n != 'true'
|
||||||
|
run: echo "No i18n changes in this PR; skipping." && exit 0
|
||||||
|
|
||||||
|
- name: Determine PR number
|
||||||
|
id: pr
|
||||||
|
run: |
|
||||||
|
if [ "${{ github.event_name }}" = "pull_request_target" ]; then
|
||||||
|
echo "number=${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "number=${{ github.event.issue.number }}" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Download combined PR diff
|
||||||
|
id: get_diff
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
PR_NUMBER: ${{ steps.pr.outputs.number }}
|
||||||
|
run: |
|
||||||
|
# Get the combined diff for the entire PR
|
||||||
|
curl -sSL \
|
||||||
|
-H "Authorization: token $GITHUB_TOKEN" \
|
||||||
|
-H "Accept: application/vnd.github.v3.diff" \
|
||||||
|
"https://api.github.com/repos/${{ github.repository }}/pulls/$PR_NUMBER" \
|
||||||
|
-o pr.diff
|
||||||
|
|
||||||
|
# Compute character length
|
||||||
|
LEN=$(wc -c < pr.diff | tr -d ' ')
|
||||||
|
echo "len=$LEN" >> "$GITHUB_OUTPUT"
|
||||||
|
if [ "$LEN" -gt 25000 ]; then
|
||||||
|
echo "too_long=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "too_long=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "got diff:"
|
||||||
|
cat pr.diff
|
||||||
|
|
||||||
|
- name: Comment when diff length exceeded
|
||||||
|
if: steps.get_diff.outputs.too_long == 'true'
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
PR_NUMBER: ${{ steps.pr.outputs.number }}
|
||||||
|
run: |
|
||||||
|
jq -n --arg body "Diff length exceeded, can't query API" '{body: ("AI translation check result:\n\n" + $body)}' > body.json
|
||||||
|
curl -sS -X POST \
|
||||||
|
-H "Authorization: token $GITHUB_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"https://api.github.com/repos/${{ github.repository }}/issues/$PR_NUMBER/comments" \
|
||||||
|
--data @body.json
|
||||||
|
|
||||||
|
- name: Query OpenAI and post review
|
||||||
|
if: steps.get_diff.outputs.too_long == 'false'
|
||||||
|
env:
|
||||||
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||||
|
OPENAI_MODEL: ${{ env.OPENAI_MODEL }}
|
||||||
|
SYSTEM_PROMPT: ${{ env.SYSTEM_PROMPT }}
|
||||||
|
AI_PROMPT: ${{ env.AI_PROMPT }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
PR_NUMBER: ${{ steps.pr.outputs.number }}
|
||||||
|
run: |
|
||||||
|
# Prepare OpenAI chat request payload (embed diff safely)
|
||||||
|
jq -n \
|
||||||
|
--arg model "$OPENAI_MODEL" \
|
||||||
|
--arg sys "$SYSTEM_PROMPT" \
|
||||||
|
--arg prompt "$AI_PROMPT" \
|
||||||
|
--rawfile diff pr.diff \
|
||||||
|
'{model:$model,
|
||||||
|
messages:[
|
||||||
|
{role:"system", content:$sys},
|
||||||
|
{role:"user", content: ($prompt + "\n\n```diff\n" + $diff + "\n```")}
|
||||||
|
]
|
||||||
|
}' > payload.json
|
||||||
|
|
||||||
|
# Call OpenAI
|
||||||
|
curl -sS https://api.openai.com/v1/chat/completions \
|
||||||
|
-H "Authorization: Bearer $OPENAI_API_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d @payload.json > response.json
|
||||||
|
|
||||||
|
# Extract response text
|
||||||
|
COMMENT=$(jq -r '.choices[0].message.content // empty' response.json)
|
||||||
|
if [ -z "$COMMENT" ]; then
|
||||||
|
COMMENT="AI did not return a response."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If failed, add a note
|
||||||
|
ADDITIONAL_NOTE=""
|
||||||
|
if [[ "$COMMENT" == *"not ok"* ]]; then
|
||||||
|
ADDITIONAL_NOTE=$(echo -ne "\n\nPlease note this check is a guideline, not a hard requirement. It is here to help you translate. If you disagree with some points, just state that. Any typos should be fixed.")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Post the review as a PR comment
|
||||||
|
jq -n --arg body "$COMMENT" --arg note "$ADDITIONAL_NOTE" '{body: ("AI translation check result:\n\n" + $body + $note)}' > body.json
|
||||||
|
echo "CURLing https://api.github.com/repos/${{ github.repository }}/issues/$PR_NUMBER/comments"
|
||||||
|
curl -sS -X POST \
|
||||||
|
-H "Authorization: token $GITHUB_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"https://api.github.com/repos/${{ github.repository }}/issues/$PR_NUMBER/comments" \
|
||||||
|
--data @body.json
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -32,6 +32,8 @@ src/render/shaders/*.inc
|
||||||
src/render/shaders/Shaders.hpp
|
src/render/shaders/Shaders.hpp
|
||||||
|
|
||||||
hyprctl/hyprctl
|
hyprctl/hyprctl
|
||||||
|
hyprctl/hw-protocols/*.c*
|
||||||
|
hyprctl/hw-protocols/*.h*
|
||||||
|
|
||||||
gmon.out
|
gmon.out
|
||||||
*.out
|
*.out
|
||||||
|
|
|
||||||
322
CMakeLists.txt
322
CMakeLists.txt
|
|
@ -17,18 +17,21 @@ set(HYPRLAND_VERSION ${VER})
|
||||||
set(PREFIX ${CMAKE_INSTALL_PREFIX})
|
set(PREFIX ${CMAKE_INSTALL_PREFIX})
|
||||||
set(INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR})
|
set(INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR})
|
||||||
set(BINDIR ${CMAKE_INSTALL_BINDIR})
|
set(BINDIR ${CMAKE_INSTALL_BINDIR})
|
||||||
configure_file(hyprland.pc.in hyprland.pc @ONLY)
|
|
||||||
|
|
||||||
set(CMAKE_MESSAGE_LOG_LEVEL "STATUS")
|
set(CMAKE_MESSAGE_LOG_LEVEL "STATUS")
|
||||||
|
|
||||||
message(STATUS "Gathering git info")
|
message(STATUS "Gathering git info")
|
||||||
|
|
||||||
# Get git info hash and branch
|
|
||||||
execute_process(COMMAND ./scripts/generateVersion.sh
|
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
|
|
||||||
# Make shader files includable
|
# Make shader files includable
|
||||||
execute_process(COMMAND ./scripts/generateShaderIncludes.sh
|
execute_process(COMMAND ./scripts/generateShaderIncludes.sh
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
|
RESULT_VARIABLE HYPR_SHADER_GEN_RESULT)
|
||||||
|
if(NOT HYPR_SHADER_GEN_RESULT EQUAL 0)
|
||||||
|
message(
|
||||||
|
FATAL_ERROR
|
||||||
|
"Failed to generate shader includes (scripts/generateShaderIncludes.sh), exit code: ${HYPR_SHADER_GEN_RESULT}"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
find_package(PkgConfig REQUIRED)
|
find_package(PkgConfig REQUIRED)
|
||||||
|
|
||||||
|
|
@ -36,11 +39,23 @@ find_package(PkgConfig REQUIRED)
|
||||||
# provide a .pc file and won't be detected this way
|
# provide a .pc file and won't be detected this way
|
||||||
pkg_check_modules(udis_dep IMPORTED_TARGET udis86>=1.7.2)
|
pkg_check_modules(udis_dep IMPORTED_TARGET udis86>=1.7.2)
|
||||||
|
|
||||||
# Fallback to subproject
|
# Find non-pkgconfig udis86, otherwise fallback to subproject
|
||||||
if(NOT udis_dep_FOUND)
|
if(NOT udis_dep_FOUND)
|
||||||
|
find_library(udis_nopc udis86)
|
||||||
|
if(NOT("${udis_nopc}" MATCHES "udis_nopc-NOTFOUND"))
|
||||||
|
message(STATUS "Found udis86 at ${udis_nopc}")
|
||||||
|
else()
|
||||||
add_subdirectory("subprojects/udis86")
|
add_subdirectory("subprojects/udis86")
|
||||||
include_directories("subprojects/udis86")
|
include_directories("subprojects/udis86")
|
||||||
message(STATUS "udis86 dependency not found, falling back to subproject")
|
message(STATUS "udis86 dependency not found, falling back to subproject")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
find_library(librt rt)
|
||||||
|
if("${librt}" MATCHES "librt-NOTFOUND")
|
||||||
|
unset(LIBRT)
|
||||||
|
else()
|
||||||
|
set(LIBRT rt)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(CMAKE_BUILD_TYPE)
|
if(CMAKE_BUILD_TYPE)
|
||||||
|
|
@ -71,9 +86,11 @@ message(
|
||||||
if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
|
if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
|
||||||
message(STATUS "Configuring Hyprland in Debug with CMake")
|
message(STATUS "Configuring Hyprland in Debug with CMake")
|
||||||
add_compile_definitions(HYPRLAND_DEBUG)
|
add_compile_definitions(HYPRLAND_DEBUG)
|
||||||
|
set(BUILD_TESTING ON)
|
||||||
else()
|
else()
|
||||||
add_compile_options(-O3)
|
add_compile_options(-O3)
|
||||||
message(STATUS "Configuring Hyprland in Release with CMake")
|
message(STATUS "Configuring Hyprland in Release with CMake")
|
||||||
|
set(BUILD_TESTING OFF)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_compile_definitions(HYPRLAND_VERSION="${HYPRLAND_VERSION}")
|
add_compile_definitions(HYPRLAND_VERSION="${HYPRLAND_VERSION}")
|
||||||
|
|
@ -85,15 +102,19 @@ set(CXX_STANDARD_REQUIRED ON)
|
||||||
add_compile_options(
|
add_compile_options(
|
||||||
-Wall
|
-Wall
|
||||||
-Wextra
|
-Wextra
|
||||||
|
-Wpedantic
|
||||||
-Wno-unused-parameter
|
-Wno-unused-parameter
|
||||||
-Wno-unused-value
|
-Wno-unused-value
|
||||||
-Wno-missing-field-initializers
|
-Wno-missing-field-initializers
|
||||||
|
-Wno-gnu-zero-variadic-macro-arguments
|
||||||
-Wno-narrowing
|
-Wno-narrowing
|
||||||
-Wno-pointer-arith
|
-Wno-pointer-arith
|
||||||
-Wno-clobbered
|
-Wno-clobbered
|
||||||
-Wpedantic
|
|
||||||
-fmacro-prefix-map=${CMAKE_SOURCE_DIR}/=)
|
-fmacro-prefix-map=${CMAKE_SOURCE_DIR}/=)
|
||||||
|
|
||||||
|
# disable lto as it may break plugins
|
||||||
|
add_compile_options(-fno-lto)
|
||||||
|
|
||||||
set(CMAKE_EXECUTABLE_ENABLE_EXPORTS TRUE)
|
set(CMAKE_EXECUTABLE_ENABLE_EXPORTS TRUE)
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
|
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
|
||||||
|
|
||||||
|
|
@ -104,48 +125,153 @@ find_package(Threads REQUIRED)
|
||||||
set(GLES_VERSION "GLES3")
|
set(GLES_VERSION "GLES3")
|
||||||
find_package(OpenGL REQUIRED COMPONENTS ${GLES_VERSION})
|
find_package(OpenGL REQUIRED COMPONENTS ${GLES_VERSION})
|
||||||
|
|
||||||
pkg_check_modules(aquamarine_dep REQUIRED IMPORTED_TARGET aquamarine>=0.9.0)
|
set(AQUAMARINE_MINIMUM_VERSION 0.9.3)
|
||||||
pkg_check_modules(hyprlang_dep REQUIRED IMPORTED_TARGET hyprlang>=0.3.2)
|
set(HYPRLANG_MINIMUM_VERSION 0.6.7)
|
||||||
pkg_check_modules(hyprcursor_dep REQUIRED IMPORTED_TARGET hyprcursor>=0.1.7)
|
set(HYPRCURSOR_MINIMUM_VERSION 0.1.7)
|
||||||
pkg_check_modules(hyprutils_dep REQUIRED IMPORTED_TARGET hyprutils>=0.8.1)
|
set(HYPRUTILS_MINIMUM_VERSION 0.11.0)
|
||||||
pkg_check_modules(hyprgraphics_dep REQUIRED IMPORTED_TARGET hyprgraphics>=0.1.3)
|
set(HYPRGRAPHICS_MINIMUM_VERSION 0.1.6)
|
||||||
|
|
||||||
|
pkg_check_modules(aquamarine_dep REQUIRED IMPORTED_TARGET aquamarine>=${AQUAMARINE_MINIMUM_VERSION})
|
||||||
|
pkg_check_modules(hyprlang_dep REQUIRED IMPORTED_TARGET hyprlang>=${HYPRLANG_MINIMUM_VERSION})
|
||||||
|
pkg_check_modules(hyprcursor_dep REQUIRED IMPORTED_TARGET hyprcursor>=${HYPRCURSOR_MINIMUM_VERSION})
|
||||||
|
pkg_check_modules(hyprutils_dep REQUIRED IMPORTED_TARGET hyprutils>=${HYPRUTILS_MINIMUM_VERSION})
|
||||||
|
pkg_check_modules(hyprgraphics_dep REQUIRED IMPORTED_TARGET hyprgraphics>=${HYPRGRAPHICS_MINIMUM_VERSION})
|
||||||
|
|
||||||
string(REPLACE "." ";" AQ_VERSION_LIST ${aquamarine_dep_VERSION})
|
string(REPLACE "." ";" AQ_VERSION_LIST ${aquamarine_dep_VERSION})
|
||||||
list(GET AQ_VERSION_LIST 0 AQ_VERSION_MAJOR)
|
list(GET AQ_VERSION_LIST 0 AQ_VERSION_MAJOR)
|
||||||
list(GET AQ_VERSION_LIST 1 AQ_VERSION_MINOR)
|
list(GET AQ_VERSION_LIST 1 AQ_VERSION_MINOR)
|
||||||
list(GET AQ_VERSION_LIST 2 AQ_VERSION_PATCH)
|
list(GET AQ_VERSION_LIST 2 AQ_VERSION_PATCH)
|
||||||
|
|
||||||
add_compile_definitions(AQUAMARINE_VERSION="${aquamarine_dep_VERSION}")
|
set(AQUAMARINE_VERSION "${aquamarine_dep_VERSION}")
|
||||||
add_compile_definitions(AQUAMARINE_VERSION_MAJOR=${AQ_VERSION_MAJOR})
|
set(AQUAMARINE_VERSION_MAJOR "${AQ_VERSION_MAJOR}")
|
||||||
add_compile_definitions(AQUAMARINE_VERSION_MINOR=${AQ_VERSION_MINOR})
|
set(AQUAMARINE_VERSION_MINOR "${AQ_VERSION_MINOR}")
|
||||||
add_compile_definitions(AQUAMARINE_VERSION_PATCH=${AQ_VERSION_PATCH})
|
set(AQUAMARINE_VERSION_PATCH "${AQ_VERSION_PATCH}")
|
||||||
add_compile_definitions(HYPRLANG_VERSION="${hyprlang_dep_VERSION}")
|
set(HYPRLANG_VERSION "${hyprlang_dep_VERSION}")
|
||||||
add_compile_definitions(HYPRUTILS_VERSION="${hyprutils_dep_VERSION}")
|
set(HYPRUTILS_VERSION "${hyprutils_dep_VERSION}")
|
||||||
add_compile_definitions(HYPRCURSOR_VERSION="${hyprcursor_dep_VERSION}")
|
set(HYPRCURSOR_VERSION "${hyprcursor_dep_VERSION}")
|
||||||
add_compile_definitions(HYPRGRAPHICS_VERSION="${hyprgraphics_dep_VERSION}")
|
set(HYPRGRAPHICS_VERSION "${hyprgraphics_dep_VERSION}")
|
||||||
|
|
||||||
|
|
||||||
|
find_package(Git QUIET)
|
||||||
|
|
||||||
|
# Populate variables with env vars if present
|
||||||
|
set(GIT_COMMIT_HASH "$ENV{GIT_COMMIT_HASH}")
|
||||||
|
if(NOT GIT_COMMIT_HASH)
|
||||||
|
set(GIT_COMMIT_HASH "unknown")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(GIT_BRANCH "$ENV{GIT_BRANCH}")
|
||||||
|
if(NOT GIT_BRANCH)
|
||||||
|
set(GIT_BRANCH "unknown")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(GIT_COMMIT_MESSAGE "$ENV{GIT_COMMIT_MESSAGE}")
|
||||||
|
if(NOT GIT_COMMIT_MESSAGE)
|
||||||
|
set(GIT_COMMIT_MESSAGE "unknown")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(GIT_COMMIT_DATE "$ENV{GIT_COMMIT_DATE}")
|
||||||
|
if(NOT GIT_COMMIT_DATE)
|
||||||
|
set(GIT_COMMIT_DATE "unknown")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(GIT_DIRTY "$ENV{GIT_DIRTY}")
|
||||||
|
if(NOT GIT_DIRTY)
|
||||||
|
set(GIT_DIRTY "unknown")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(GIT_TAG "$ENV{GIT_TAG}")
|
||||||
|
if(NOT GIT_TAG)
|
||||||
|
set(GIT_TAG "unknown")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(GIT_COMMITS "$ENV{GIT_COMMITS}")
|
||||||
|
if(NOT GIT_COMMITS)
|
||||||
|
set(GIT_COMMITS "0")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(Git_FOUND)
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${GIT_EXECUTABLE} rev-parse --show-toplevel
|
||||||
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
|
OUTPUT_VARIABLE GIT_TOPLEVEL
|
||||||
|
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||||
|
ERROR_QUIET
|
||||||
|
RESULT_VARIABLE GIT_TOPLEVEL_RESULT
|
||||||
|
)
|
||||||
|
|
||||||
|
if(GIT_TOPLEVEL_RESULT EQUAL 0)
|
||||||
|
message(STATUS "Detected git repository root: ${GIT_TOPLEVEL}")
|
||||||
|
|
||||||
|
execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse HEAD
|
||||||
|
WORKING_DIRECTORY ${GIT_TOPLEVEL}
|
||||||
|
OUTPUT_VARIABLE GIT_COMMIT_HASH OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||||
|
execute_process(COMMAND ${GIT_EXECUTABLE} branch --show-current
|
||||||
|
WORKING_DIRECTORY ${GIT_TOPLEVEL}
|
||||||
|
OUTPUT_VARIABLE GIT_BRANCH OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||||
|
execute_process(COMMAND sh "-c" "${GIT_EXECUTABLE} show -s --format=%s --no-show-signature | sed \"s/\\\"/\'/g\""
|
||||||
|
WORKING_DIRECTORY ${GIT_TOPLEVEL}
|
||||||
|
OUTPUT_VARIABLE GIT_COMMIT_MESSAGE OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||||
|
execute_process(COMMAND ${GIT_EXECUTABLE} show -s --format=%cd --date=local --no-show-signature
|
||||||
|
WORKING_DIRECTORY ${GIT_TOPLEVEL}
|
||||||
|
OUTPUT_VARIABLE GIT_COMMIT_DATE OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||||
|
execute_process(COMMAND ${GIT_EXECUTABLE} diff-index --quiet HEAD --
|
||||||
|
WORKING_DIRECTORY ${GIT_TOPLEVEL}
|
||||||
|
RESULT_VARIABLE GIT_DIRTY_RESULT)
|
||||||
|
if(NOT GIT_DIRTY_RESULT EQUAL 0)
|
||||||
|
set(GIT_DIRTY "dirty")
|
||||||
|
else()
|
||||||
|
set(GIT_DIRTY "clean")
|
||||||
|
endif()
|
||||||
|
execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags
|
||||||
|
WORKING_DIRECTORY ${GIT_TOPLEVEL}
|
||||||
|
OUTPUT_VARIABLE GIT_TAG OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||||
|
execute_process(COMMAND ${GIT_EXECUTABLE} rev-list --count HEAD
|
||||||
|
WORKING_DIRECTORY ${GIT_TOPLEVEL}
|
||||||
|
OUTPUT_VARIABLE GIT_COMMITS OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||||
|
else()
|
||||||
|
message(WARNING "No Git repository detected in ${CMAKE_SOURCE_DIR}")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
configure_file(
|
||||||
|
${CMAKE_SOURCE_DIR}/src/version.h.in
|
||||||
|
${CMAKE_SOURCE_DIR}/src/version.h
|
||||||
|
@ONLY
|
||||||
|
)
|
||||||
|
|
||||||
|
set_source_files_properties(${CMAKE_SOURCE_DIR}/src/version.h PROPERTIES GENERATED TRUE)
|
||||||
|
|
||||||
|
set(XKBCOMMON_MINIMUM_VERSION 1.11.0)
|
||||||
|
set(WAYLAND_SERVER_MINIMUM_VERSION 1.22.90)
|
||||||
|
set(WAYLAND_SERVER_PROTOCOLS_MINIMUM_VERSION 1.45)
|
||||||
|
set(LIBINPUT_MINIMUM_VERSION 1.28)
|
||||||
|
|
||||||
pkg_check_modules(
|
pkg_check_modules(
|
||||||
deps
|
deps
|
||||||
REQUIRED
|
REQUIRED
|
||||||
IMPORTED_TARGET
|
IMPORTED_TARGET GLOBAL
|
||||||
xkbcommon
|
xkbcommon>=${XKBCOMMON_MINIMUM_VERSION}
|
||||||
uuid
|
uuid
|
||||||
wayland-server>=1.22.90
|
wayland-server>=${WAYLAND_SERVER_MINIMUM_VERSION}
|
||||||
wayland-protocols>=1.43
|
wayland-protocols>=${WAYLAND_SERVER_PROTOCOLS_MINIMUM_VERSION}
|
||||||
cairo
|
cairo
|
||||||
pango
|
pango
|
||||||
pangocairo
|
pangocairo
|
||||||
pixman-1
|
pixman-1
|
||||||
xcursor
|
xcursor
|
||||||
libdrm
|
libdrm
|
||||||
libinput>=1.28
|
libinput>=${LIBINPUT_MINIMUM_VERSION}
|
||||||
gbm
|
gbm
|
||||||
gio-2.0
|
gio-2.0
|
||||||
re2)
|
re2
|
||||||
|
muparser)
|
||||||
|
|
||||||
find_package(hyprwayland-scanner 0.3.10 REQUIRED)
|
find_package(hyprwayland-scanner 0.3.10 REQUIRED)
|
||||||
|
|
||||||
file(GLOB_RECURSE SRCFILES "src/*.cpp")
|
file(GLOB_RECURSE SRCFILES "src/*.cpp")
|
||||||
|
get_filename_component(FULL_MAIN_PATH ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp ABSOLUTE)
|
||||||
|
list(REMOVE_ITEM SRCFILES "${FULL_MAIN_PATH}")
|
||||||
|
|
||||||
set(TRACY_CPP_FILES "")
|
set(TRACY_CPP_FILES "")
|
||||||
if(USE_TRACY)
|
if(USE_TRACY)
|
||||||
|
|
@ -153,7 +279,12 @@ if(USE_TRACY)
|
||||||
message(STATUS "Tracy enabled, TRACY_CPP_FILES: " ${TRACY_CPP_FILES})
|
message(STATUS "Tracy enabled, TRACY_CPP_FILES: " ${TRACY_CPP_FILES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_executable(Hyprland ${SRCFILES} ${TRACY_CPP_FILES})
|
add_library(hyprland_lib STATIC ${SRCFILES})
|
||||||
|
add_executable(Hyprland src/main.cpp ${TRACY_CPP_FILES})
|
||||||
|
target_link_libraries(Hyprland hyprland_lib)
|
||||||
|
|
||||||
|
target_include_directories(hyprland_lib PUBLIC ${deps_INCLUDE_DIRS})
|
||||||
|
target_include_directories(Hyprland PUBLIC ${deps_INCLUDE_DIRS})
|
||||||
|
|
||||||
set(USE_GPROF OFF)
|
set(USE_GPROF OFF)
|
||||||
|
|
||||||
|
|
@ -163,8 +294,8 @@ if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
|
||||||
if(WITH_ASAN)
|
if(WITH_ASAN)
|
||||||
message(STATUS "Enabling ASan")
|
message(STATUS "Enabling ASan")
|
||||||
|
|
||||||
target_link_libraries(Hyprland asan)
|
target_link_libraries(hyprland_lib PUBLIC asan)
|
||||||
target_compile_options(Hyprland PUBLIC -fsanitize=address)
|
target_compile_options(hyprland_lib PUBLIC -fsanitize=address)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(USE_TRACY)
|
if(USE_TRACY)
|
||||||
|
|
@ -174,7 +305,7 @@ if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
|
||||||
option(TRACY_ON_DEMAND "" ON)
|
option(TRACY_ON_DEMAND "" ON)
|
||||||
add_subdirectory(subprojects/tracy)
|
add_subdirectory(subprojects/tracy)
|
||||||
|
|
||||||
target_link_libraries(Hyprland Tracy::TracyClient)
|
target_link_libraries(hyprland_lib PUBLIC Tracy::TracyClient)
|
||||||
|
|
||||||
if(USE_TRACY_GPU)
|
if(USE_TRACY_GPU)
|
||||||
message(STATUS "Tracy GPU Profiling is turned on")
|
message(STATUS "Tracy GPU Profiling is turned on")
|
||||||
|
|
@ -190,6 +321,10 @@ if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(BUILT_WITH_NIX)
|
||||||
|
add_compile_definitions(BUILT_WITH_NIX)
|
||||||
|
endif()
|
||||||
|
|
||||||
check_include_file("execinfo.h" EXECINFOH)
|
check_include_file("execinfo.h" EXECINFOH)
|
||||||
if(EXECINFOH)
|
if(EXECINFOH)
|
||||||
message(STATUS "Configuration supports execinfo")
|
message(STATUS "Configuration supports execinfo")
|
||||||
|
|
@ -199,19 +334,19 @@ endif()
|
||||||
include(CheckLibraryExists)
|
include(CheckLibraryExists)
|
||||||
check_library_exists(execinfo backtrace "" HAVE_LIBEXECINFO)
|
check_library_exists(execinfo backtrace "" HAVE_LIBEXECINFO)
|
||||||
if(HAVE_LIBEXECINFO)
|
if(HAVE_LIBEXECINFO)
|
||||||
target_link_libraries(Hyprland execinfo)
|
target_link_libraries(hyprland_lib PUBLIC execinfo)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
check_include_file("sys/timerfd.h" HAS_TIMERFD)
|
check_include_file("sys/timerfd.h" HAS_TIMERFD)
|
||||||
pkg_check_modules(epoll IMPORTED_TARGET epoll-shim)
|
pkg_check_modules(epoll IMPORTED_TARGET epoll-shim)
|
||||||
if(NOT HAS_TIMERFD AND epoll_FOUND)
|
if(NOT HAS_TIMERFD AND epoll_FOUND)
|
||||||
target_link_libraries(Hyprland PkgConfig::epoll)
|
target_link_libraries(hyprland_lib PUBLIC PkgConfig::epoll)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
check_include_file("sys/inotify.h" HAS_INOTIFY)
|
check_include_file("sys/inotify.h" HAS_INOTIFY)
|
||||||
pkg_check_modules(inotify IMPORTED_TARGET libinotify)
|
pkg_check_modules(inotify IMPORTED_TARGET libinotify)
|
||||||
if(NOT HAS_INOTIFY AND inotify_FOUND)
|
if(NOT HAS_INOTIFY AND inotify_FOUND)
|
||||||
target_link_libraries(Hyprland PkgConfig::inotify)
|
target_link_libraries(hyprland_lib PUBLIC PkgConfig::inotify)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NO_XWAYLAND)
|
if(NO_XWAYLAND)
|
||||||
|
|
@ -219,10 +354,7 @@ if(NO_XWAYLAND)
|
||||||
add_compile_definitions(NO_XWAYLAND)
|
add_compile_definitions(NO_XWAYLAND)
|
||||||
else()
|
else()
|
||||||
message(STATUS "XWAYLAND Enabled (NO_XWAYLAND not defined) checking deps...")
|
message(STATUS "XWAYLAND Enabled (NO_XWAYLAND not defined) checking deps...")
|
||||||
pkg_check_modules(
|
set(XWAYLAND_DEPENDENCIES
|
||||||
xdeps
|
|
||||||
REQUIRED
|
|
||||||
IMPORTED_TARGET
|
|
||||||
xcb
|
xcb
|
||||||
xcb-render
|
xcb-render
|
||||||
xcb-xfixes
|
xcb-xfixes
|
||||||
|
|
@ -230,9 +362,21 @@ else()
|
||||||
xcb-composite
|
xcb-composite
|
||||||
xcb-res
|
xcb-res
|
||||||
xcb-errors)
|
xcb-errors)
|
||||||
target_link_libraries(Hyprland PkgConfig::xdeps)
|
|
||||||
|
pkg_check_modules(
|
||||||
|
xdeps
|
||||||
|
REQUIRED
|
||||||
|
IMPORTED_TARGET
|
||||||
|
${XWAYLAND_DEPENDENCIES})
|
||||||
|
|
||||||
|
string(JOIN ", " PKGCONFIG_XWAYLAND_DEPENDENCIES ${XWAYLAND_DEPENDENCIES})
|
||||||
|
string(PREPEND PKGCONFIG_XWAYLAND_DEPENDENCIES ", ")
|
||||||
|
|
||||||
|
target_link_libraries(hyprland_lib PUBLIC PkgConfig::xdeps)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
configure_file(hyprland.pc.in hyprland.pc @ONLY)
|
||||||
|
|
||||||
if(NO_SYSTEMD)
|
if(NO_SYSTEMD)
|
||||||
message(STATUS "SYSTEMD support is disabled...")
|
message(STATUS "SYSTEMD support is disabled...")
|
||||||
else()
|
else()
|
||||||
|
|
@ -253,30 +397,42 @@ set(CPACK_PROJECT_NAME ${PROJECT_NAME})
|
||||||
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
|
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
|
||||||
include(CPack)
|
include(CPack)
|
||||||
|
|
||||||
message(STATUS "Setting precompiled headers")
|
if(CMAKE_DISABLE_PRECOMPILE_HEADERS)
|
||||||
|
message(STATUS "Not using precompiled headers")
|
||||||
target_precompile_headers(Hyprland PRIVATE
|
else()
|
||||||
|
message(STATUS "Setting precompiled headers")
|
||||||
|
target_precompile_headers(hyprland_lib PRIVATE
|
||||||
$<$<COMPILE_LANGUAGE:CXX>:src/pch/pch.hpp>)
|
$<$<COMPILE_LANGUAGE:CXX>:src/pch/pch.hpp>)
|
||||||
|
endif()
|
||||||
|
|
||||||
message(STATUS "Setting link libraries")
|
message(STATUS "Setting link libraries")
|
||||||
|
|
||||||
target_link_libraries(
|
target_link_libraries(
|
||||||
Hyprland
|
hyprland_lib
|
||||||
rt
|
PUBLIC
|
||||||
PkgConfig::aquamarine_dep
|
PkgConfig::aquamarine_dep
|
||||||
PkgConfig::hyprlang_dep
|
PkgConfig::hyprlang_dep
|
||||||
PkgConfig::hyprutils_dep
|
PkgConfig::hyprutils_dep
|
||||||
PkgConfig::hyprcursor_dep
|
PkgConfig::hyprcursor_dep
|
||||||
PkgConfig::hyprgraphics_dep
|
PkgConfig::hyprgraphics_dep
|
||||||
PkgConfig::deps)
|
PkgConfig::deps
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(
|
||||||
|
Hyprland
|
||||||
|
${LIBRT}
|
||||||
|
hyprland_lib)
|
||||||
if(udis_dep_FOUND)
|
if(udis_dep_FOUND)
|
||||||
target_link_libraries(Hyprland PkgConfig::udis_dep)
|
target_link_libraries(hyprland_lib PUBLIC PkgConfig::udis_dep)
|
||||||
|
elseif(NOT("${udis_nopc}" MATCHES "udis_nopc-NOTFOUND"))
|
||||||
|
target_link_libraries(hyprland_lib PUBLIC ${udis_nopc})
|
||||||
else()
|
else()
|
||||||
target_link_libraries(Hyprland libudis86)
|
target_link_libraries(hyprland_lib PUBLIC libudis86)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# used by `make installheaders`, to ensure the headers are generated
|
# used by `make installheaders`, to ensure the headers are generated
|
||||||
add_custom_target(generate-protocol-headers)
|
add_custom_target(generate-protocol-headers)
|
||||||
|
set(PROTOCOL_SOURCES "")
|
||||||
|
|
||||||
function(protocolnew protoPath protoName external)
|
function(protocolnew protoPath protoName external)
|
||||||
if(external)
|
if(external)
|
||||||
|
|
@ -290,10 +446,15 @@ function(protocolnew protoPath protoName external)
|
||||||
COMMAND hyprwayland-scanner ${path}/${protoName}.xml
|
COMMAND hyprwayland-scanner ${path}/${protoName}.xml
|
||||||
${CMAKE_SOURCE_DIR}/protocols/
|
${CMAKE_SOURCE_DIR}/protocols/
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
|
||||||
target_sources(Hyprland PRIVATE protocols/${protoName}.cpp
|
target_sources(hyprland_lib PRIVATE protocols/${protoName}.cpp
|
||||||
protocols/${protoName}.hpp)
|
protocols/${protoName}.hpp)
|
||||||
target_sources(generate-protocol-headers
|
target_sources(generate-protocol-headers
|
||||||
PRIVATE ${CMAKE_SOURCE_DIR}/protocols/${protoName}.hpp)
|
PRIVATE ${CMAKE_SOURCE_DIR}/protocols/${protoName}.hpp)
|
||||||
|
|
||||||
|
list(APPEND PROTOCOL_SOURCES "${CMAKE_SOURCE_DIR}/protocols/${protoName}.cpp")
|
||||||
|
set(PROTOCOL_SOURCES "${PROTOCOL_SOURCES}" PARENT_SCOPE)
|
||||||
|
list(APPEND PROTOCOL_SOURCES "${CMAKE_SOURCE_DIR}/protocols/${protoName}.hpp")
|
||||||
|
set(PROTOCOL_SOURCES "${PROTOCOL_SOURCES}" PARENT_SCOPE)
|
||||||
endfunction()
|
endfunction()
|
||||||
function(protocolWayland)
|
function(protocolWayland)
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
|
|
@ -303,12 +464,17 @@ function(protocolWayland)
|
||||||
hyprwayland-scanner --wayland-enums
|
hyprwayland-scanner --wayland-enums
|
||||||
${WAYLAND_SCANNER_PKGDATA_DIR}/wayland.xml ${CMAKE_SOURCE_DIR}/protocols/
|
${WAYLAND_SCANNER_PKGDATA_DIR}/wayland.xml ${CMAKE_SOURCE_DIR}/protocols/
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
|
||||||
target_sources(Hyprland PRIVATE protocols/wayland.cpp protocols/wayland.hpp)
|
target_sources(hyprland_lib PRIVATE protocols/wayland.cpp protocols/wayland.hpp)
|
||||||
target_sources(generate-protocol-headers
|
target_sources(generate-protocol-headers
|
||||||
PRIVATE ${CMAKE_SOURCE_DIR}/protocols/wayland.hpp)
|
PRIVATE ${CMAKE_SOURCE_DIR}/protocols/wayland.hpp)
|
||||||
|
|
||||||
|
list(APPEND PROTOCOL_SOURCES "${CMAKE_SOURCE_DIR}/protocols/wayland.hpp")
|
||||||
|
set(PROTOCOL_SOURCES "${PROTOCOL_SOURCES}" PARENT_SCOPE)
|
||||||
|
list(APPEND PROTOCOL_SOURCES "${CMAKE_SOURCE_DIR}/protocols/wayland.cpp")
|
||||||
|
set(PROTOCOL_SOURCES "${PROTOCOL_SOURCES}" PARENT_SCOPE)
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
target_link_libraries(Hyprland OpenGL::EGL OpenGL::GL Threads::Threads)
|
target_link_libraries(hyprland_lib PUBLIC OpenGL::EGL OpenGL::GL Threads::Threads)
|
||||||
|
|
||||||
pkg_check_modules(hyprland_protocols_dep hyprland-protocols>=0.6.4)
|
pkg_check_modules(hyprland_protocols_dep hyprland-protocols>=0.6.4)
|
||||||
if(hyprland_protocols_dep_FOUND)
|
if(hyprland_protocols_dep_FOUND)
|
||||||
|
|
@ -381,11 +547,16 @@ protocolnew("staging/color-management" "color-management-v1" false)
|
||||||
protocolnew("staging/xdg-toplevel-tag" "xdg-toplevel-tag-v1" false)
|
protocolnew("staging/xdg-toplevel-tag" "xdg-toplevel-tag-v1" false)
|
||||||
protocolnew("staging/xdg-system-bell" "xdg-system-bell-v1" false)
|
protocolnew("staging/xdg-system-bell" "xdg-system-bell-v1" false)
|
||||||
protocolnew("staging/ext-workspace" "ext-workspace-v1" false)
|
protocolnew("staging/ext-workspace" "ext-workspace-v1" false)
|
||||||
|
protocolnew("staging/ext-data-control" "ext-data-control-v1" false)
|
||||||
|
protocolnew("staging/pointer-warp" "pointer-warp-v1" false)
|
||||||
|
protocolnew("staging/fifo" "fifo-v1" false)
|
||||||
|
protocolnew("staging/commit-timing" "commit-timing-v1" false)
|
||||||
|
|
||||||
protocolwayland()
|
protocolwayland()
|
||||||
|
|
||||||
# tools
|
# tools
|
||||||
add_subdirectory(hyprctl)
|
add_subdirectory(hyprctl)
|
||||||
|
add_subdirectory(start)
|
||||||
|
|
||||||
if(NO_HYPRPM)
|
if(NO_HYPRPM)
|
||||||
message(STATUS "hyprpm is disabled")
|
message(STATUS "hyprpm is disabled")
|
||||||
|
|
@ -394,12 +565,6 @@ else()
|
||||||
message(STATUS "hyprpm is enabled (NO_HYPRPM not defined)")
|
message(STATUS "hyprpm is enabled (NO_HYPRPM not defined)")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NO_TESTS)
|
|
||||||
message(STATUS "building tests is disabled")
|
|
||||||
else()
|
|
||||||
message(STATUS "building tests is enabled (NO_TESTS not defined)")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# binary and symlink
|
# binary and symlink
|
||||||
install(TARGETS Hyprland)
|
install(TARGETS Hyprland)
|
||||||
|
|
||||||
|
|
@ -418,7 +583,6 @@ add_compile_definitions(DATAROOTDIR="${CMAKE_INSTALL_FULL_DATAROOTDIR}")
|
||||||
|
|
||||||
# installable assets
|
# installable assets
|
||||||
file(GLOB_RECURSE INSTALLABLE_ASSETS "assets/install/*")
|
file(GLOB_RECURSE INSTALLABLE_ASSETS "assets/install/*")
|
||||||
list(FILTER INSTALLABLE_ASSETS EXCLUDE REGEX "meson.build")
|
|
||||||
install(FILES ${INSTALLABLE_ASSETS}
|
install(FILES ${INSTALLABLE_ASSETS}
|
||||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/hypr)
|
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/hypr)
|
||||||
|
|
||||||
|
|
@ -456,15 +620,47 @@ install(
|
||||||
PATTERN "*.hpp"
|
PATTERN "*.hpp"
|
||||||
PATTERN "*.inc")
|
PATTERN "*.inc")
|
||||||
|
|
||||||
if(TESTS)
|
if(BUILD_TESTING OR WITH_TESTS)
|
||||||
|
message(STATUS "Building tests")
|
||||||
|
|
||||||
|
# hyprtester
|
||||||
|
add_subdirectory(hyprtester)
|
||||||
|
|
||||||
|
# GTest
|
||||||
|
find_package(GTest CONFIG REQUIRED)
|
||||||
|
include(GoogleTest)
|
||||||
|
file(GLOB_RECURSE TESTFILES "tests/*.cpp")
|
||||||
|
add_executable(hyprland_gtests ${TESTFILES})
|
||||||
|
target_compile_options(hyprland_gtests PRIVATE --coverage)
|
||||||
|
target_link_options(hyprland_gtests PRIVATE --coverage)
|
||||||
|
target_include_directories(
|
||||||
|
hyprland_gtests
|
||||||
|
PUBLIC "./include"
|
||||||
|
PRIVATE "./src" "./src/include" "./protocols" "${CMAKE_BINARY_DIR}")
|
||||||
|
|
||||||
|
target_link_libraries(hyprland_gtests hyprland_lib GTest::gtest_main)
|
||||||
|
|
||||||
|
gtest_discover_tests(hyprland_gtests)
|
||||||
|
|
||||||
|
# Enable coverage in main hyprland lib
|
||||||
|
target_compile_options(hyprland_lib PRIVATE --coverage)
|
||||||
|
target_link_options(hyprland_lib PRIVATE --coverage)
|
||||||
|
target_link_libraries(hyprland_lib PUBLIC gcov)
|
||||||
|
|
||||||
|
# Enable coverage in hyprland exe
|
||||||
|
target_compile_options(Hyprland PRIVATE --coverage)
|
||||||
|
target_link_options(Hyprland PRIVATE --coverage)
|
||||||
|
target_link_libraries(Hyprland gcov)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(BUILD_TESTING)
|
||||||
|
message(STATUS "Testing is enabled")
|
||||||
|
|
||||||
enable_testing()
|
enable_testing()
|
||||||
add_custom_target(tests)
|
add_custom_target(tests)
|
||||||
|
|
||||||
add_subdirectory(hyprtester)
|
add_dependencies(tests hyprland_gtests)
|
||||||
add_test(
|
|
||||||
NAME "Main Test"
|
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/hyprtester
|
|
||||||
COMMAND hyprtester)
|
|
||||||
|
|
||||||
add_dependencies(tests hyprtester)
|
else()
|
||||||
|
message(STATUS "Testing is disabled")
|
||||||
endif()
|
endif()
|
||||||
|
|
|
||||||
2
LICENSE
2
LICENSE
|
|
@ -1,6 +1,6 @@
|
||||||
BSD 3-Clause License
|
BSD 3-Clause License
|
||||||
|
|
||||||
Copyright (c) 2022-2024, vaxerski
|
Copyright (c) 2022-2025, vaxerski
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
|
|
||||||
6
Makefile
6
Makefile
|
|
@ -8,7 +8,7 @@ release:
|
||||||
cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
|
cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
|
||||||
|
|
||||||
debug:
|
debug:
|
||||||
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -DCMAKE_INSTALL_PREFIX:STRING=${PREFIX} -S . -B ./build
|
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -DTESTS=true -DCMAKE_INSTALL_PREFIX:STRING=${PREFIX} -S . -B ./build
|
||||||
cmake --build ./build --config Debug --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
|
cmake --build ./build --config Debug --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
|
||||||
|
|
||||||
nopch:
|
nopch:
|
||||||
|
|
@ -92,3 +92,7 @@ asan:
|
||||||
@echo "Hyprland done"
|
@echo "Hyprland done"
|
||||||
|
|
||||||
ASAN_OPTIONS="detect_odr_violation=0,log_path=asan.log" HYPRLAND_NO_CRASHREPORTER=1 ./build/Hyprland -c ~/.config/hypr/hyprland.conf
|
ASAN_OPTIONS="detect_odr_violation=0,log_path=asan.log" HYPRLAND_NO_CRASHREPORTER=1 ./build/Hyprland -c ~/.config/hypr/hyprland.conf
|
||||||
|
|
||||||
|
test:
|
||||||
|
$(MAKE) debug
|
||||||
|
./build/hyprtester/hyprtester -c hyprtester/test.conf -b ./build/Hyprland -p hyprtester/plugin/hyprtestplugin.so
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
![Badge Workflow]
|
[![Badge Workflow]][Workflow]
|
||||||
[![Badge License]][License]
|
[![Badge License]][License]
|
||||||
![Badge Language]
|
![Badge Language]
|
||||||
[![Badge Pull Requests]][Pull Requests]
|
[![Badge Pull Requests]][Pull Requests]
|
||||||
|
|
@ -111,6 +111,7 @@ easy IPC, much more QoL stuff than other compositors and more...
|
||||||
[Contribute]: https://wiki.hypr.land/Contributing-and-Debugging/
|
[Contribute]: https://wiki.hypr.land/Contributing-and-Debugging/
|
||||||
[Install]: https://wiki.hypr.land/Getting-Started/Installation/
|
[Install]: https://wiki.hypr.land/Getting-Started/Installation/
|
||||||
[Quick Start]: https://wiki.hypr.land/Getting-Started/Master-Tutorial/
|
[Quick Start]: https://wiki.hypr.land/Getting-Started/Master-Tutorial/
|
||||||
|
[Workflow]: https://github.com/hyprwm/Hyprland/actions/workflows/ci.yaml
|
||||||
[License]: LICENSE
|
[License]: LICENSE
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
0.50.0
|
0.52.0
|
||||||
|
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
globber = run_command('sh', '-c', 'find . -type f -not -name "*.build"', check: true)
|
|
||||||
files = globber.stdout().strip().split('\n')
|
|
||||||
|
|
||||||
foreach file : files
|
|
||||||
install_data(
|
|
||||||
file,
|
|
||||||
install_dir: join_paths(get_option('datadir'), 'hypr'),
|
|
||||||
install_tag: 'runtime',
|
|
||||||
)
|
|
||||||
endforeach
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
install_data(
|
|
||||||
'hyprland-portals.conf',
|
|
||||||
install_dir: join_paths(get_option('datadir'), 'xdg-desktop-portal'),
|
|
||||||
install_tag: 'runtime',
|
|
||||||
)
|
|
||||||
|
|
||||||
subdir('install')
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
install_man('Hyprland.1')
|
|
||||||
install_man('hyprctl.1')
|
|
||||||
|
|
@ -27,7 +27,7 @@ monitor=,preferred,auto,auto
|
||||||
# Set programs that you use
|
# Set programs that you use
|
||||||
$terminal = kitty
|
$terminal = kitty
|
||||||
$fileManager = dolphin
|
$fileManager = dolphin
|
||||||
$menu = wofi --show drun
|
$menu = hyprlauncher
|
||||||
|
|
||||||
|
|
||||||
#################
|
#################
|
||||||
|
|
@ -125,14 +125,16 @@ decoration {
|
||||||
animations {
|
animations {
|
||||||
enabled = yes, please :)
|
enabled = yes, please :)
|
||||||
|
|
||||||
# Default animations, see https://wiki.hypr.land/Configuring/Animations/ for more
|
# Default curves, see https://wiki.hypr.land/Configuring/Animations/#curves
|
||||||
|
# NAME, X0, Y0, X1, Y1
|
||||||
bezier = easeOutQuint,0.23,1,0.32,1
|
bezier = easeOutQuint, 0.23, 1, 0.32, 1
|
||||||
bezier = easeInOutCubic,0.65,0.05,0.36,1
|
bezier = easeInOutCubic, 0.65, 0.05, 0.36, 1
|
||||||
bezier = linear,0,0,1,1
|
bezier = linear, 0, 0, 1, 1
|
||||||
bezier = almostLinear,0.5,0.5,0.75,1.0
|
bezier = almostLinear, 0.5, 0.5, 0.75, 1
|
||||||
bezier = quick,0.15,0,0.1,1
|
bezier = quick, 0.15, 0, 0.1, 1
|
||||||
|
|
||||||
|
# Default animations, see https://wiki.hypr.land/Configuring/Animations/
|
||||||
|
# NAME, ONOFF, SPEED, CURVE, [STYLE]
|
||||||
animation = global, 1, 10, default
|
animation = global, 1, 10, default
|
||||||
animation = border, 1, 5.39, easeOutQuint
|
animation = border, 1, 5.39, easeOutQuint
|
||||||
animation = windows, 1, 4.79, easeOutQuint
|
animation = windows, 1, 4.79, easeOutQuint
|
||||||
|
|
@ -149,6 +151,7 @@ animations {
|
||||||
animation = workspaces, 1, 1.94, almostLinear, fade
|
animation = workspaces, 1, 1.94, almostLinear, fade
|
||||||
animation = workspacesIn, 1, 1.21, almostLinear, fade
|
animation = workspacesIn, 1, 1.21, almostLinear, fade
|
||||||
animation = workspacesOut, 1, 1.94, almostLinear, fade
|
animation = workspacesOut, 1, 1.94, almostLinear, fade
|
||||||
|
animation = zoomFactor, 1, 7, quick
|
||||||
}
|
}
|
||||||
|
|
||||||
# Ref https://wiki.hypr.land/Configuring/Workspace-Rules/
|
# Ref https://wiki.hypr.land/Configuring/Workspace-Rules/
|
||||||
|
|
@ -156,10 +159,23 @@ animations {
|
||||||
# uncomment all if you wish to use that.
|
# uncomment all if you wish to use that.
|
||||||
# workspace = w[tv1], gapsout:0, gapsin:0
|
# workspace = w[tv1], gapsout:0, gapsin:0
|
||||||
# workspace = f[1], gapsout:0, gapsin:0
|
# workspace = f[1], gapsout:0, gapsin:0
|
||||||
# windowrule = bordersize 0, floating:0, onworkspace:w[tv1]
|
# windowrule {
|
||||||
# windowrule = rounding 0, floating:0, onworkspace:w[tv1]
|
# name = no-gaps-wtv1
|
||||||
# windowrule = bordersize 0, floating:0, onworkspace:f[1]
|
# match:float = false
|
||||||
# windowrule = rounding 0, floating:0, onworkspace:f[1]
|
# match:workspace = w[tv1]
|
||||||
|
#
|
||||||
|
# border_size = 0
|
||||||
|
# rounding = 0
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# windowrule {
|
||||||
|
# name = no-gaps-f1
|
||||||
|
# match:float = false
|
||||||
|
# match:workspace = f[1]
|
||||||
|
#
|
||||||
|
# border_size = 0
|
||||||
|
# rounding = 0
|
||||||
|
# }
|
||||||
|
|
||||||
# See https://wiki.hypr.land/Configuring/Dwindle-Layout/ for more
|
# See https://wiki.hypr.land/Configuring/Dwindle-Layout/ for more
|
||||||
dwindle {
|
dwindle {
|
||||||
|
|
@ -200,10 +216,8 @@ input {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# https://wiki.hypr.land/Configuring/Variables/#gestures
|
# See https://wiki.hypr.land/Configuring/Gestures
|
||||||
gestures {
|
gesture = 3, horizontal, workspace
|
||||||
workspace_swipe = false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Example per-device config
|
# Example per-device config
|
||||||
# See https://wiki.hypr.land/Configuring/Keywords/#per-device-input-configs for more
|
# See https://wiki.hypr.land/Configuring/Keywords/#per-device-input-configs for more
|
||||||
|
|
@ -223,7 +237,7 @@ $mainMod = SUPER # Sets "Windows" key as main modifier
|
||||||
# Example binds, see https://wiki.hypr.land/Configuring/Binds/ for more
|
# Example binds, see https://wiki.hypr.land/Configuring/Binds/ for more
|
||||||
bind = $mainMod, Q, exec, $terminal
|
bind = $mainMod, Q, exec, $terminal
|
||||||
bind = $mainMod, C, killactive,
|
bind = $mainMod, C, killactive,
|
||||||
bind = $mainMod, M, exit,
|
bind = $mainMod, M, exec, command -v hyprshutdown >/dev/null 2>&1 && hyprshutdown || hyprctl dispatch exit
|
||||||
bind = $mainMod, E, exec, $fileManager
|
bind = $mainMod, E, exec, $fileManager
|
||||||
bind = $mainMod, V, togglefloating,
|
bind = $mainMod, V, togglefloating,
|
||||||
bind = $mainMod, R, exec, $menu
|
bind = $mainMod, R, exec, $menu
|
||||||
|
|
@ -293,11 +307,35 @@ bindl = , XF86AudioPrev, exec, playerctl previous
|
||||||
# See https://wiki.hypr.land/Configuring/Window-Rules/ for more
|
# See https://wiki.hypr.land/Configuring/Window-Rules/ for more
|
||||||
# See https://wiki.hypr.land/Configuring/Workspace-Rules/ for workspace rules
|
# See https://wiki.hypr.land/Configuring/Workspace-Rules/ for workspace rules
|
||||||
|
|
||||||
# Example windowrule
|
# Example windowrules that are useful
|
||||||
# windowrule = float,class:^(kitty)$,title:^(kitty)$
|
|
||||||
|
|
||||||
# Ignore maximize requests from apps. You'll probably like this.
|
windowrule {
|
||||||
windowrule = suppressevent maximize, class:.*
|
# Ignore maximize requests from all apps. You'll probably like this.
|
||||||
|
name = suppress-maximize-events
|
||||||
|
match:class = .*
|
||||||
|
|
||||||
# Fix some dragging issues with XWayland
|
suppress_event = maximize
|
||||||
windowrule = nofocus,class:^$,title:^$,xwayland:1,floating:1,fullscreen:0,pinned:0
|
}
|
||||||
|
|
||||||
|
windowrule {
|
||||||
|
# Fix some dragging issues with XWayland
|
||||||
|
name = fix-xwayland-drags
|
||||||
|
match:class = ^$
|
||||||
|
match:title = ^$
|
||||||
|
match:xwayland = true
|
||||||
|
match:float = true
|
||||||
|
match:fullscreen = false
|
||||||
|
match:pin = false
|
||||||
|
|
||||||
|
no_focus = true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Hyprland-run windowrule
|
||||||
|
windowrule {
|
||||||
|
name = move-hyprland-run
|
||||||
|
|
||||||
|
match:class = hyprland-run
|
||||||
|
|
||||||
|
move = 20 monitor_h-120
|
||||||
|
float = yes
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Name=Hyprland
|
Name=Hyprland
|
||||||
Comment=An intelligent dynamic tiling Wayland compositor
|
Comment=An intelligent dynamic tiling Wayland compositor
|
||||||
Exec=Hyprland
|
Exec=start-hyprland
|
||||||
Type=Application
|
Type=Application
|
||||||
DesktopNames=Hyprland
|
DesktopNames=Hyprland
|
||||||
Keywords=tiling;wayland;compositor;
|
Keywords=tiling;wayland;compositor;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
install_data(
|
|
||||||
'hyprland.conf',
|
|
||||||
install_dir: join_paths(get_option('datadir'), 'hypr'),
|
|
||||||
install_tag: 'runtime',
|
|
||||||
)
|
|
||||||
install_data(
|
|
||||||
'hyprland.desktop',
|
|
||||||
install_dir: join_paths(get_option('datadir'), 'wayland-sessions'),
|
|
||||||
install_tag: 'runtime',
|
|
||||||
)
|
|
||||||
|
|
@ -2,15 +2,18 @@
|
||||||
// Example blue light filter shader.
|
// Example blue light filter shader.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#version 300 es
|
||||||
|
|
||||||
precision mediump float;
|
precision mediump float;
|
||||||
varying vec2 v_texcoord;
|
in vec2 v_texcoord;
|
||||||
|
layout(location = 0) out vec4 fragColor;
|
||||||
uniform sampler2D tex;
|
uniform sampler2D tex;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
|
||||||
vec4 pixColor = texture2D(tex, v_texcoord);
|
vec4 pixColor = texture(tex, v_texcoord);
|
||||||
|
|
||||||
pixColor[2] *= 0.8;
|
pixColor[2] *= 0.8;
|
||||||
|
|
||||||
gl_FragColor = pixColor;
|
fragColor = pixColor;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
240
flake.lock
generated
240
flake.lock
generated
|
|
@ -16,11 +16,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1751740947,
|
"lastModified": 1764714051,
|
||||||
"narHash": "sha256-35040CHH7P3JGmhGVfEb2oJHL/A5mI2IXumhkxrBnao=",
|
"narHash": "sha256-AjcMlM3UoavFoLzr0YrcvsIxALShjyvwe+o7ikibpCM=",
|
||||||
"owner": "hyprwm",
|
"owner": "hyprwm",
|
||||||
"repo": "aquamarine",
|
"repo": "aquamarine",
|
||||||
"rev": "dfc1db15a08c4cd234288f66e1199c653495301f",
|
"rev": "a43bedcceced5c21ad36578ed823e6099af78214",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -32,11 +32,11 @@
|
||||||
"flake-compat": {
|
"flake-compat": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1696426674,
|
"lastModified": 1761588595,
|
||||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
"narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=",
|
||||||
"owner": "edolstra",
|
"owner": "edolstra",
|
||||||
"repo": "flake-compat",
|
"repo": "flake-compat",
|
||||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
"rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -79,11 +79,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1749155331,
|
"lastModified": 1753964049,
|
||||||
"narHash": "sha256-XR9fsI0zwLiFWfqi/pdS/VD+YNorKb3XIykgTg4l1nA=",
|
"narHash": "sha256-lIqabfBY7z/OANxHoPeIrDJrFyYy9jAM4GQLzZ2feCM=",
|
||||||
"owner": "hyprwm",
|
"owner": "hyprwm",
|
||||||
"repo": "hyprcursor",
|
"repo": "hyprcursor",
|
||||||
"rev": "45fcc10b4c282746d93ec406a740c43b48b4ef80",
|
"rev": "44e91d467bdad8dcf8bbd2ac7cf49972540980a5",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -105,11 +105,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1751808145,
|
"lastModified": 1763733840,
|
||||||
"narHash": "sha256-OXgL0XaKMmfX2rRQkt9SkJw+QNfv0jExlySt1D6O72g=",
|
"narHash": "sha256-JnET78yl5RvpGuDQy3rCycOCkiKoLr5DN1fPhRNNMco=",
|
||||||
"owner": "hyprwm",
|
"owner": "hyprwm",
|
||||||
"repo": "hyprgraphics",
|
"repo": "hyprgraphics",
|
||||||
"rev": "b841473a0bd4a1a74a0b64f1ec2ab199035c349f",
|
"rev": "8f1bec691b2d198c60cccabca7a94add2df4ed1a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -118,6 +118,45 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"hyprland-guiutils": {
|
||||||
|
"inputs": {
|
||||||
|
"aquamarine": [
|
||||||
|
"aquamarine"
|
||||||
|
],
|
||||||
|
"hyprgraphics": [
|
||||||
|
"hyprgraphics"
|
||||||
|
],
|
||||||
|
"hyprlang": [
|
||||||
|
"hyprlang"
|
||||||
|
],
|
||||||
|
"hyprtoolkit": "hyprtoolkit",
|
||||||
|
"hyprutils": [
|
||||||
|
"hyprutils"
|
||||||
|
],
|
||||||
|
"hyprwayland-scanner": [
|
||||||
|
"hyprwayland-scanner"
|
||||||
|
],
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"systems": [
|
||||||
|
"systems"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1764812575,
|
||||||
|
"narHash": "sha256-1bK1yGgaR82vajUrt6z+BSljQvFn91D74WJ/vJsydtE=",
|
||||||
|
"owner": "hyprwm",
|
||||||
|
"repo": "hyprland-guiutils",
|
||||||
|
"rev": "fd321368a40c782cfa299991e5584ca338e36ebe",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "hyprwm",
|
||||||
|
"repo": "hyprland-guiutils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"hyprland-protocols": {
|
"hyprland-protocols": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
|
|
@ -128,11 +167,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1749046714,
|
"lastModified": 1759610243,
|
||||||
"narHash": "sha256-kymV5FMnddYGI+UjwIw8ceDjdeg7ToDVjbHCvUlhn14=",
|
"narHash": "sha256-+KEVnKBe8wz+a6dTLq8YDcF3UrhQElwsYJaVaHXJtoI=",
|
||||||
"owner": "hyprwm",
|
"owner": "hyprwm",
|
||||||
"repo": "hyprland-protocols",
|
"repo": "hyprland-protocols",
|
||||||
"rev": "613878cb6f459c5e323aaafe1e6f388ac8a36330",
|
"rev": "bd153e76f751f150a09328dbdeb5e4fab9d23622",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -141,67 +180,6 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"hyprland-qt-support": {
|
|
||||||
"inputs": {
|
|
||||||
"hyprlang": [
|
|
||||||
"hyprland-qtutils",
|
|
||||||
"hyprlang"
|
|
||||||
],
|
|
||||||
"nixpkgs": [
|
|
||||||
"hyprland-qtutils",
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"systems": [
|
|
||||||
"hyprland-qtutils",
|
|
||||||
"systems"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1749154592,
|
|
||||||
"narHash": "sha256-DO7z5CeT/ddSGDEnK9mAXm1qlGL47L3VAHLlLXoCjhE=",
|
|
||||||
"owner": "hyprwm",
|
|
||||||
"repo": "hyprland-qt-support",
|
|
||||||
"rev": "4c8053c3c888138a30c3a6c45c2e45f5484f2074",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "hyprwm",
|
|
||||||
"repo": "hyprland-qt-support",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"hyprland-qtutils": {
|
|
||||||
"inputs": {
|
|
||||||
"hyprland-qt-support": "hyprland-qt-support",
|
|
||||||
"hyprlang": [
|
|
||||||
"hyprlang"
|
|
||||||
],
|
|
||||||
"hyprutils": [
|
|
||||||
"hyprland-qtutils",
|
|
||||||
"hyprlang",
|
|
||||||
"hyprutils"
|
|
||||||
],
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"systems": [
|
|
||||||
"systems"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1750371812,
|
|
||||||
"narHash": "sha256-D868K1dVEACw17elVxRgXC6hOxY+54wIEjURztDWLk8=",
|
|
||||||
"owner": "hyprwm",
|
|
||||||
"repo": "hyprland-qtutils",
|
|
||||||
"rev": "b13c7481e37856f322177010bdf75fccacd1adc8",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "hyprwm",
|
|
||||||
"repo": "hyprland-qtutils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"hyprlang": {
|
"hyprlang": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"hyprutils": [
|
"hyprutils": [
|
||||||
|
|
@ -215,11 +193,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1750371198,
|
"lastModified": 1764612430,
|
||||||
"narHash": "sha256-/iuJ1paQOBoSLqHflRNNGyroqfF/yvPNurxzcCT0cAE=",
|
"narHash": "sha256-54ltTSbI6W+qYGMchAgCR6QnC1kOdKXN6X6pJhOWxFg=",
|
||||||
"owner": "hyprwm",
|
"owner": "hyprwm",
|
||||||
"repo": "hyprlang",
|
"repo": "hyprlang",
|
||||||
"rev": "cee01452bca58d6cadb3224e21e370de8bc20f0b",
|
"rev": "0d00dc118981531aa731150b6ea551ef037acddd",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -228,6 +206,51 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"hyprtoolkit": {
|
||||||
|
"inputs": {
|
||||||
|
"aquamarine": [
|
||||||
|
"hyprland-guiutils",
|
||||||
|
"aquamarine"
|
||||||
|
],
|
||||||
|
"hyprgraphics": [
|
||||||
|
"hyprland-guiutils",
|
||||||
|
"hyprgraphics"
|
||||||
|
],
|
||||||
|
"hyprlang": [
|
||||||
|
"hyprland-guiutils",
|
||||||
|
"hyprlang"
|
||||||
|
],
|
||||||
|
"hyprutils": [
|
||||||
|
"hyprland-guiutils",
|
||||||
|
"hyprutils"
|
||||||
|
],
|
||||||
|
"hyprwayland-scanner": [
|
||||||
|
"hyprland-guiutils",
|
||||||
|
"hyprwayland-scanner"
|
||||||
|
],
|
||||||
|
"nixpkgs": [
|
||||||
|
"hyprland-guiutils",
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"systems": [
|
||||||
|
"hyprland-guiutils",
|
||||||
|
"systems"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1764592794,
|
||||||
|
"narHash": "sha256-7CcO+wbTJ1L1NBQHierHzheQGPWwkIQug/w+fhTAVuU=",
|
||||||
|
"owner": "hyprwm",
|
||||||
|
"repo": "hyprtoolkit",
|
||||||
|
"rev": "5cfe0743f0e608e1462972303778d8a0859ee63e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "hyprwm",
|
||||||
|
"repo": "hyprtoolkit",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"hyprutils": {
|
"hyprutils": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
|
|
@ -238,11 +261,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1751888065,
|
"lastModified": 1764962281,
|
||||||
"narHash": "sha256-F2SV9WGqgtRsXIdUrl3sRe0wXlQD+kRRZcSfbepjPJY=",
|
"narHash": "sha256-rGbEMhTTyTzw4iyz45lch5kXseqnqcEpmrHdy+zHsfo=",
|
||||||
"owner": "hyprwm",
|
"owner": "hyprwm",
|
||||||
"repo": "hyprutils",
|
"repo": "hyprutils",
|
||||||
"rev": "a8229739cf36d159001cfc203871917b83fdf917",
|
"rev": "fe686486ac867a1a24f99c753bb40ffed338e4b0",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -261,11 +284,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1751881472,
|
"lastModified": 1763640274,
|
||||||
"narHash": "sha256-meB0SnXbwIe2trD041MLKEv6R7NZ759QwBcVIhlSBfE=",
|
"narHash": "sha256-Uan1Nl9i4TF/kyFoHnTq1bd/rsWh4GAK/9/jDqLbY5A=",
|
||||||
"owner": "hyprwm",
|
"owner": "hyprwm",
|
||||||
"repo": "hyprwayland-scanner",
|
"repo": "hyprwayland-scanner",
|
||||||
"rev": "8fb426b3e5452fd9169453fd6c10f8c14ca37120",
|
"rev": "f6cf414ca0e16a4d30198fd670ec86df3c89f671",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -274,13 +297,39 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"hyprwire": {
|
||||||
|
"inputs": {
|
||||||
|
"hyprutils": [
|
||||||
|
"hyprutils"
|
||||||
|
],
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"systems": [
|
||||||
|
"systems"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1764872015,
|
||||||
|
"narHash": "sha256-INI9AVrQG5nJZFvGPSiUZ9FEUZJLfGdsqjF1QSak7Gc=",
|
||||||
|
"owner": "hyprwm",
|
||||||
|
"repo": "hyprwire",
|
||||||
|
"rev": "7997451dcaab7b9d9d442f18985d514ec5891608",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "hyprwm",
|
||||||
|
"repo": "hyprwire",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1751792365,
|
"lastModified": 1764950072,
|
||||||
"narHash": "sha256-J1kI6oAj25IG4EdVlg2hQz8NZTBNYvIS0l4wpr9KcUo=",
|
"narHash": "sha256-BmPWzogsG2GsXZtlT+MTcAWeDK5hkbGRZTeZNW42fwA=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "1fd8bada0b6117e6c7eb54aad5813023eed37ccb",
|
"rev": "f61125a668a320878494449750330ca58b78c557",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -299,11 +348,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1750779888,
|
"lastModified": 1765016596,
|
||||||
"narHash": "sha256-wibppH3g/E2lxU43ZQHC5yA/7kIKLGxVEnsnVK1BtRg=",
|
"narHash": "sha256-rhSqPNxDVow7OQKi4qS5H8Au0P4S3AYbawBSmJNUtBQ=",
|
||||||
"owner": "cachix",
|
"owner": "cachix",
|
||||||
"repo": "git-hooks.nix",
|
"repo": "git-hooks.nix",
|
||||||
"rev": "16ec914f6fb6f599ce988427d9d94efddf25fe6d",
|
"rev": "548fc44fca28a5e81c5d6b846e555e6b9c2a5a3c",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -317,11 +366,12 @@
|
||||||
"aquamarine": "aquamarine",
|
"aquamarine": "aquamarine",
|
||||||
"hyprcursor": "hyprcursor",
|
"hyprcursor": "hyprcursor",
|
||||||
"hyprgraphics": "hyprgraphics",
|
"hyprgraphics": "hyprgraphics",
|
||||||
|
"hyprland-guiutils": "hyprland-guiutils",
|
||||||
"hyprland-protocols": "hyprland-protocols",
|
"hyprland-protocols": "hyprland-protocols",
|
||||||
"hyprland-qtutils": "hyprland-qtutils",
|
|
||||||
"hyprlang": "hyprlang",
|
"hyprlang": "hyprlang",
|
||||||
"hyprutils": "hyprutils",
|
"hyprutils": "hyprutils",
|
||||||
"hyprwayland-scanner": "hyprwayland-scanner",
|
"hyprwayland-scanner": "hyprwayland-scanner",
|
||||||
|
"hyprwire": "hyprwire",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs",
|
||||||
"pre-commit-hooks": "pre-commit-hooks",
|
"pre-commit-hooks": "pre-commit-hooks",
|
||||||
"systems": "systems",
|
"systems": "systems",
|
||||||
|
|
@ -365,11 +415,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1751300244,
|
"lastModified": 1761431178,
|
||||||
"narHash": "sha256-PFuv1TZVYvQhha0ac53E3YgdtmLShrN0t4T6xqHl0jE=",
|
"narHash": "sha256-xzjC1CV3+wpUQKNF+GnadnkeGUCJX+vgaWIZsnz9tzI=",
|
||||||
"owner": "hyprwm",
|
"owner": "hyprwm",
|
||||||
"repo": "xdg-desktop-portal-hyprland",
|
"repo": "xdg-desktop-portal-hyprland",
|
||||||
"rev": "6115f3fdcb2c1a57b4a80a69f3c797e47607b90a",
|
"rev": "4b8801228ff958d028f588f0c2b911dbf32297f9",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
||||||
19
flake.nix
19
flake.nix
|
|
@ -35,11 +35,15 @@
|
||||||
inputs.systems.follows = "systems";
|
inputs.systems.follows = "systems";
|
||||||
};
|
};
|
||||||
|
|
||||||
hyprland-qtutils = {
|
hyprland-guiutils = {
|
||||||
url = "github:hyprwm/hyprland-qtutils";
|
url = "github:hyprwm/hyprland-guiutils";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
inputs.systems.follows = "systems";
|
inputs.systems.follows = "systems";
|
||||||
|
inputs.aquamarine.follows = "aquamarine";
|
||||||
|
inputs.hyprgraphics.follows = "hyprgraphics";
|
||||||
|
inputs.hyprutils.follows = "hyprutils";
|
||||||
inputs.hyprlang.follows = "hyprlang";
|
inputs.hyprlang.follows = "hyprlang";
|
||||||
|
inputs.hyprwayland-scanner.follows = "hyprwayland-scanner";
|
||||||
};
|
};
|
||||||
|
|
||||||
hyprlang = {
|
hyprlang = {
|
||||||
|
|
@ -61,6 +65,13 @@
|
||||||
inputs.systems.follows = "systems";
|
inputs.systems.follows = "systems";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
hyprwire = {
|
||||||
|
url = "github:hyprwm/hyprwire";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
inputs.systems.follows = "systems";
|
||||||
|
inputs.hyprutils.follows = "hyprutils";
|
||||||
|
};
|
||||||
|
|
||||||
xdph = {
|
xdph = {
|
||||||
url = "github:hyprwm/xdg-desktop-portal-hyprland";
|
url = "github:hyprwm/xdg-desktop-portal-hyprland";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
|
@ -148,7 +159,7 @@
|
||||||
# hyprland-packages
|
# hyprland-packages
|
||||||
hyprland
|
hyprland
|
||||||
hyprland-unwrapped
|
hyprland-unwrapped
|
||||||
hyprtester
|
hyprland-with-tests
|
||||||
# hyprland-extras
|
# hyprland-extras
|
||||||
xdg-desktop-portal-hyprland
|
xdg-desktop-portal-hyprland
|
||||||
;
|
;
|
||||||
|
|
@ -176,7 +187,7 @@
|
||||||
homeManagerModules.default = import ./nix/hm-module.nix self;
|
homeManagerModules.default = import ./nix/hm-module.nix self;
|
||||||
|
|
||||||
# Hydra build jobs
|
# Hydra build jobs
|
||||||
# Recent versions of Hydra can aggregate jobsets from 'hydraJobs' intead of a release.nix
|
# Recent versions of Hydra can aggregate jobsets from 'hydraJobs' instead of a release.nix
|
||||||
# or similar. Remember to filter large or incompatible attributes here. More eval jobs can
|
# or similar. Remember to filter large or incompatible attributes here. More eval jobs can
|
||||||
# be added by merging, e.g., self.packages // self.devShells.
|
# be added by merging, e.g., self.packages // self.devShells.
|
||||||
hydraJobs = self.packages;
|
hydraJobs = self.packages;
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,32 @@ project(
|
||||||
DESCRIPTION "Control utility for Hyprland"
|
DESCRIPTION "Control utility for Hyprland"
|
||||||
)
|
)
|
||||||
|
|
||||||
pkg_check_modules(hyprctl_deps REQUIRED IMPORTED_TARGET hyprutils>=0.2.4 re2)
|
pkg_check_modules(hyprctl_deps REQUIRED IMPORTED_TARGET hyprutils>=0.2.4 hyprwire re2)
|
||||||
|
|
||||||
add_executable(hyprctl "main.cpp")
|
file(GLOB_RECURSE HYPRCTL_SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "hw-protocols/*.cpp" "include/*.hpp")
|
||||||
|
|
||||||
|
add_executable(hyprctl ${HYPRCTL_SRCFILES})
|
||||||
|
|
||||||
target_link_libraries(hyprctl PUBLIC PkgConfig::hyprctl_deps)
|
target_link_libraries(hyprctl PUBLIC PkgConfig::hyprctl_deps)
|
||||||
|
target_include_directories(hyprctl PRIVATE "hw-protocols")
|
||||||
|
|
||||||
|
# Hyprwire
|
||||||
|
|
||||||
|
function(hyprprotocol protoPath protoName)
|
||||||
|
set(path ${CMAKE_CURRENT_SOURCE_DIR}/${protoPath})
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/hw-protocols/${protoName}-client.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/hw-protocols/${protoName}-client.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/hw-protocols/${protoName}-spec.hpp
|
||||||
|
COMMAND hyprwire-scanner --client ${path}/${protoName}.xml
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/hw-protocols/
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
target_sources(hyprctl PRIVATE hw-protocols/${protoName}-client.cpp
|
||||||
|
hw-protocols/${protoName}-client.hpp
|
||||||
|
hw-protocols/${protoName}-spec.hpp)
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
hyprprotocol(hw-protocols hyprpaper_core)
|
||||||
|
|
||||||
# binary
|
# binary
|
||||||
install(TARGETS hyprctl)
|
install(TARGETS hyprctl)
|
||||||
|
|
|
||||||
144
hyprctl/hw-protocols/hyprpaper_core.xml
Normal file
144
hyprctl/hw-protocols/hyprpaper_core.xml
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<protocol name="hyprpaper_core" version="1">
|
||||||
|
<copyright>
|
||||||
|
BSD 3-Clause License
|
||||||
|
|
||||||
|
Copyright (c) 2025, Hypr Development
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
</copyright>
|
||||||
|
|
||||||
|
<object name="hyprpaper_core_manager" version="1">
|
||||||
|
<description summary="manager object">
|
||||||
|
This is the core manager object for hyprpaper operations
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<c2s name="get_wallpaper_object">
|
||||||
|
<description summary="Get a wallpaper object">
|
||||||
|
Creates a wallpaper object
|
||||||
|
</description>
|
||||||
|
<returns iface="hyprpaper_wallpaper"/>
|
||||||
|
</c2s>
|
||||||
|
|
||||||
|
<s2c name="add_monitor">
|
||||||
|
<description summary="New monitor added">
|
||||||
|
Emitted when a new monitor is added.
|
||||||
|
</description>
|
||||||
|
<arg name="monitor_name" type="varchar" summary="the monitor's name"/>
|
||||||
|
</s2c>
|
||||||
|
|
||||||
|
<s2c name="remove_monitor">
|
||||||
|
<description summary="A monitor was removed">
|
||||||
|
Emitted when a monitor is removed.
|
||||||
|
</description>
|
||||||
|
<arg name="monitor_name" type="varchar" summary="the monitor's name"/>
|
||||||
|
</s2c>
|
||||||
|
|
||||||
|
<c2s name="destroy" destructor="true">
|
||||||
|
<description summary="Destroy this object">
|
||||||
|
Destroys this object. Children remain alive until destroyed.
|
||||||
|
</description>
|
||||||
|
</c2s>
|
||||||
|
</object>
|
||||||
|
|
||||||
|
<enum name="wallpaper_fit_mode">
|
||||||
|
<value idx="0" name="stretch"/>
|
||||||
|
<value idx="1" name="cover"/>
|
||||||
|
<value idx="2" name="contain"/>
|
||||||
|
<value idx="3" name="tile"/>
|
||||||
|
</enum>
|
||||||
|
|
||||||
|
<enum name="wallpaper_errors">
|
||||||
|
<value idx="0" name="inert_wallpaper_object" description="attempted to use an inert wallpaper object"/>
|
||||||
|
</enum>
|
||||||
|
|
||||||
|
<enum name="applying_error">
|
||||||
|
<value idx="0" name="invalid_path" description="path provided was invalid"/>
|
||||||
|
<value idx="1" name="invalid_monitor" description="monitor provided was invalid"/>
|
||||||
|
<value idx="2" name="unknown_error" description="unknown error"/>
|
||||||
|
</enum>
|
||||||
|
|
||||||
|
<object name="hyprpaper_wallpaper" version="1">
|
||||||
|
<description summary="wallpaper object">
|
||||||
|
This is an object describing a wallpaper
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<c2s name="path">
|
||||||
|
<description summary="Set a path">
|
||||||
|
Set a file path for the wallpaper. This has to be an absolute path from the fs root.
|
||||||
|
This is required.
|
||||||
|
</description>
|
||||||
|
<arg name="wallpaper" type="varchar" summary="path"/>
|
||||||
|
</c2s>
|
||||||
|
|
||||||
|
<c2s name="fit_mode">
|
||||||
|
<description summary="Set a fit mode">
|
||||||
|
Set a fit mode for the wallpaper. This is set to cover by default.
|
||||||
|
</description>
|
||||||
|
<arg name="fit_mode" type="enum" interface="wallpaper_fit_mode" summary="path"/>
|
||||||
|
</c2s>
|
||||||
|
|
||||||
|
<c2s name="monitor_name">
|
||||||
|
<description summary="Set the monitor name">
|
||||||
|
Set a monitor for the wallpaper. Setting this to empty (or not setting at all) will
|
||||||
|
treat this as a wildcard fallback.
|
||||||
|
|
||||||
|
See hyprpaper_core_manager.add_monitor and hyprpaper_core_manager.remove_monitor
|
||||||
|
for tracking monitor names.
|
||||||
|
</description>
|
||||||
|
<arg name="monitor_name" type="varchar" summary="monitor name"/>
|
||||||
|
</c2s>
|
||||||
|
|
||||||
|
<c2s name="apply">
|
||||||
|
<description summary="Apply this wallpaper">
|
||||||
|
Applies this object's state to the wallpaper state. Will emit .success on success,
|
||||||
|
and .failed on failure.
|
||||||
|
|
||||||
|
This object becomes inert after .succeess or .failed, the only valid operation
|
||||||
|
is to destroy it afterwards.
|
||||||
|
</description>
|
||||||
|
</c2s>
|
||||||
|
|
||||||
|
<s2c name="success">
|
||||||
|
<description summary="Operation succeeded">
|
||||||
|
Wallpaper was applied successfully.
|
||||||
|
</description>
|
||||||
|
</s2c>
|
||||||
|
|
||||||
|
<s2c name="failed">
|
||||||
|
<description summary="Operation failed">
|
||||||
|
Wallpaper was not applied. See the error field for more information.
|
||||||
|
</description>
|
||||||
|
<arg name="error" type="enum" interface="hyprpaper_wallpaper_application_error" summary="path"/>
|
||||||
|
</s2c>
|
||||||
|
|
||||||
|
<c2s name="destroy" destructor="true">
|
||||||
|
<description summary="Destroy this object">
|
||||||
|
Destroys this object.
|
||||||
|
</description>
|
||||||
|
</c2s>
|
||||||
|
</object>
|
||||||
|
</protocol>
|
||||||
|
|
@ -48,7 +48,7 @@ function _hyprctl
|
||||||
set descriptions[22] "Focus the urgent window or the last window"
|
set descriptions[22] "Focus the urgent window or the last window"
|
||||||
set descriptions[23] "Get the list of defined workspace rules"
|
set descriptions[23] "Get the list of defined workspace rules"
|
||||||
set descriptions[24] "Move the active workspace to a monitor"
|
set descriptions[24] "Move the active workspace to a monitor"
|
||||||
set descriptions[25] "Move window doesnt switch to the workspace"
|
set descriptions[25] "Move window doesn't switch to the workspace"
|
||||||
set descriptions[26] "Interact with hyprpaper if present"
|
set descriptions[26] "Interact with hyprpaper if present"
|
||||||
set descriptions[29] "Swap the active window with the next or previous in a group"
|
set descriptions[29] "Swap the active window with the next or previous in a group"
|
||||||
set descriptions[30] "Move the cursor to the corner of the active window"
|
set descriptions[30] "Move the cursor to the corner of the active window"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# This is a file feeded to complgen to generate bash/fish/zsh completions
|
# This is a file fed to complgen to generate bash/fish/zsh completions
|
||||||
# Repo: https://github.com/adaszko/complgen
|
# Repo: https://github.com/adaszko/complgen
|
||||||
# Generate completion scripts: "complgen aot --bash-script hyprctl.bash --fish-script hyprctl.fish --zsh-script hyprctl.zsh ./hyprctl.usage"
|
# Generate completion scripts: "complgen aot --bash-script hyprctl.bash --fish-script hyprctl.fish --zsh-script hyprctl.zsh ./hyprctl.usage"
|
||||||
|
|
||||||
|
|
@ -111,7 +111,7 @@ hyprctl [<OPTIONS>]... <ARGUMENTS>
|
||||||
| (closewindow) "Close a specified window"
|
| (closewindow) "Close a specified window"
|
||||||
| (workspace) "Change the workspace"
|
| (workspace) "Change the workspace"
|
||||||
| (movetoworkspace) "Move the focused window to a workspace"
|
| (movetoworkspace) "Move the focused window to a workspace"
|
||||||
| (movetoworkspacesilent) "Move window doesnt switch to the workspace"
|
| (movetoworkspacesilent) "Move window doesn't switch to the workspace"
|
||||||
| (togglefloating) "Toggle the current window's floating state"
|
| (togglefloating) "Toggle the current window's floating state"
|
||||||
| (setfloating) "Set the current window's floating state to true"
|
| (setfloating) "Set the current window's floating state to true"
|
||||||
| (settiled) "Set the current window's floating state to false"
|
| (settiled) "Set the current window's floating state to false"
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ _hyprctl () {
|
||||||
descriptions[22]="Focus the urgent window or the last window"
|
descriptions[22]="Focus the urgent window or the last window"
|
||||||
descriptions[23]="Get the list of defined workspace rules"
|
descriptions[23]="Get the list of defined workspace rules"
|
||||||
descriptions[24]="Move the active workspace to a monitor"
|
descriptions[24]="Move the active workspace to a monitor"
|
||||||
descriptions[25]="Move window doesnt switch to the workspace"
|
descriptions[25]="Move window doesn't switch to the workspace"
|
||||||
descriptions[26]="Interact with hyprpaper if present"
|
descriptions[26]="Interact with hyprpaper if present"
|
||||||
descriptions[29]="Swap the active window with the next or previous in a group"
|
descriptions[29]="Swap the active window with the next or previous in a group"
|
||||||
descriptions[30]="Move the cursor to the corner of the active window"
|
descriptions[30]="Move the cursor to the corner of the active window"
|
||||||
|
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
executable(
|
|
||||||
'hyprctl',
|
|
||||||
'main.cpp',
|
|
||||||
dependencies: [
|
|
||||||
dependency('hyprutils', version: '>= 0.1.1'),
|
|
||||||
dependency('re2', required: true)
|
|
||||||
],
|
|
||||||
install: true,
|
|
||||||
)
|
|
||||||
|
|
||||||
install_data(
|
|
||||||
'hyprctl.bash',
|
|
||||||
install_dir: join_paths(get_option('datadir'), 'bash-completion/completions'),
|
|
||||||
install_tag: 'runtime',
|
|
||||||
rename: 'hyprctl',
|
|
||||||
)
|
|
||||||
install_data(
|
|
||||||
'hyprctl.fish',
|
|
||||||
install_dir: join_paths(get_option('datadir'), 'fish/vendor_completions.d'),
|
|
||||||
install_tag: 'runtime',
|
|
||||||
)
|
|
||||||
install_data(
|
|
||||||
'hyprctl.zsh',
|
|
||||||
install_dir: join_paths(get_option('datadir'), 'zsh/site-functions'),
|
|
||||||
install_tag: 'runtime',
|
|
||||||
rename: '_hyprctl',
|
|
||||||
)
|
|
||||||
|
|
@ -49,6 +49,7 @@ commands:
|
||||||
the same format as in colors in config. Will reset
|
the same format as in colors in config. Will reset
|
||||||
when Hyprland's config is reloaded
|
when Hyprland's config is reloaded
|
||||||
setprop ... → Sets a window property
|
setprop ... → Sets a window property
|
||||||
|
getprop ... → Gets a window property
|
||||||
splash → Get the current splash
|
splash → Get the current splash
|
||||||
switchxkblayout ... → Sets the xkb layout index for a keyboard
|
switchxkblayout ... → Sets the xkb layout index for a keyboard
|
||||||
systeminfo → Get system info
|
systeminfo → Get system info
|
||||||
|
|
@ -73,11 +74,8 @@ flags:
|
||||||
const std::string_view HYPRPAPER_HELP = R"#(usage: hyprctl [flags] hyprpaper <request>
|
const std::string_view HYPRPAPER_HELP = R"#(usage: hyprctl [flags] hyprpaper <request>
|
||||||
|
|
||||||
requests:
|
requests:
|
||||||
listactive → Lists all active images
|
wallpaper → Issue a wallpaper to call a config wallpaper dynamically.
|
||||||
listloaded → Lists all loaded images
|
Arguments are [mon],[path],[fit_mode]. Fit mode is optional.
|
||||||
preload <path> → Preloads image
|
|
||||||
unload <path> → Unloads image. Pass 'all' as path to unload all images
|
|
||||||
wallpaper → Issue a wallpaper to call a config wallpaper dynamically
|
|
||||||
|
|
||||||
flags:
|
flags:
|
||||||
See 'hyprctl --help')#";
|
See 'hyprctl --help')#";
|
||||||
|
|
@ -159,6 +157,18 @@ lock:
|
||||||
flags:
|
flags:
|
||||||
See 'hyprctl --help')#";
|
See 'hyprctl --help')#";
|
||||||
|
|
||||||
|
const std::string_view GETPROP_HELP = R"#(usage: hyprctl [flags] getprop <regex> <property>
|
||||||
|
|
||||||
|
regex:
|
||||||
|
Regular expression by which a window will be searched
|
||||||
|
|
||||||
|
property:
|
||||||
|
See https://wiki.hypr.land/Configuring/Using-hyprctl/#setprop for list
|
||||||
|
of properties
|
||||||
|
|
||||||
|
flags:
|
||||||
|
See 'hyprctl --help')#";
|
||||||
|
|
||||||
const std::string_view SWITCHXKBLAYOUT_HELP = R"#(usage: [flags] switchxkblayout <device> <cmd>
|
const std::string_view SWITCHXKBLAYOUT_HELP = R"#(usage: [flags] switchxkblayout <device> <cmd>
|
||||||
|
|
||||||
device:
|
device:
|
||||||
11
hyprctl/src/helpers/Memory.hpp
Normal file
11
hyprctl/src/helpers/Memory.hpp
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <hyprutils/memory/SharedPtr.hpp>
|
||||||
|
#include <hyprutils/memory/UniquePtr.hpp>
|
||||||
|
#include <hyprutils/memory/Atomic.hpp>
|
||||||
|
|
||||||
|
using namespace Hyprutils::Memory;
|
||||||
|
|
||||||
|
#define SP CSharedPointer
|
||||||
|
#define WP CWeakPointer
|
||||||
|
#define UP CUniquePointer
|
||||||
148
hyprctl/src/hyprpaper/Hyprpaper.cpp
Normal file
148
hyprctl/src/hyprpaper/Hyprpaper.cpp
Normal file
|
|
@ -0,0 +1,148 @@
|
||||||
|
#include "Hyprpaper.hpp"
|
||||||
|
#include "../helpers/Memory.hpp"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <format>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
#include <hyprpaper_core-client.hpp>
|
||||||
|
|
||||||
|
#include <hyprutils/string/VarList2.hpp>
|
||||||
|
using namespace Hyprutils::String;
|
||||||
|
|
||||||
|
using namespace std::string_literals;
|
||||||
|
|
||||||
|
constexpr const char* SOCKET_NAME = ".hyprpaper.sock";
|
||||||
|
static SP<CCHyprpaperCoreImpl> g_coreImpl;
|
||||||
|
|
||||||
|
constexpr const uint32_t PROTOCOL_VERSION_SUPPORTED = 1;
|
||||||
|
|
||||||
|
//
|
||||||
|
static hyprpaperCoreWallpaperFitMode fitFromString(const std::string_view& sv) {
|
||||||
|
if (sv == "contain")
|
||||||
|
return HYPRPAPER_CORE_WALLPAPER_FIT_MODE_CONTAIN;
|
||||||
|
if (sv == "fit" || sv == "stretch")
|
||||||
|
return HYPRPAPER_CORE_WALLPAPER_FIT_MODE_STRETCH;
|
||||||
|
if (sv == "tile")
|
||||||
|
return HYPRPAPER_CORE_WALLPAPER_FIT_MODE_TILE;
|
||||||
|
return HYPRPAPER_CORE_WALLPAPER_FIT_MODE_COVER;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::expected<std::string, std::string> resolvePath(const std::string_view& sv) {
|
||||||
|
std::error_code ec;
|
||||||
|
auto can = std::filesystem::canonical(sv, ec);
|
||||||
|
|
||||||
|
if (ec)
|
||||||
|
return std::unexpected(std::format("invalid path: {}", ec.message()));
|
||||||
|
|
||||||
|
return can;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::expected<std::string, std::string> getFullPath(const std::string_view& sv) {
|
||||||
|
if (sv.empty())
|
||||||
|
return std::unexpected("empty path");
|
||||||
|
|
||||||
|
if (sv[0] == '~') {
|
||||||
|
static auto HOME = getenv("HOME");
|
||||||
|
if (!HOME || HOME[0] == '\0')
|
||||||
|
return std::unexpected("home path but no $HOME");
|
||||||
|
|
||||||
|
return resolvePath(std::string{HOME} + "/"s + std::string{sv.substr(1)});
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolvePath(sv);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, std::string> Hyprpaper::makeHyprpaperRequest(const std::string_view& rq) {
|
||||||
|
if (!rq.contains(' '))
|
||||||
|
return std::unexpected("Invalid request");
|
||||||
|
|
||||||
|
if (!rq.starts_with("/hyprpaper "))
|
||||||
|
return std::unexpected("Invalid request");
|
||||||
|
|
||||||
|
std::string_view LHS, RHS;
|
||||||
|
auto spacePos = rq.find(' ', 12);
|
||||||
|
LHS = rq.substr(11, spacePos - 11);
|
||||||
|
RHS = rq.substr(spacePos + 1);
|
||||||
|
|
||||||
|
if (LHS != "wallpaper")
|
||||||
|
return std::unexpected("Unknown hyprpaper request");
|
||||||
|
|
||||||
|
CVarList2 args(std::string{RHS}, 0, ',');
|
||||||
|
|
||||||
|
const std::string MONITOR = std::string{args[0]};
|
||||||
|
const auto& PATH_RAW = args[1];
|
||||||
|
const auto& FIT = args[2];
|
||||||
|
|
||||||
|
if (PATH_RAW.empty())
|
||||||
|
return std::unexpected("not enough args");
|
||||||
|
|
||||||
|
const auto RTDIR = getenv("XDG_RUNTIME_DIR");
|
||||||
|
|
||||||
|
if (!RTDIR || RTDIR[0] == '\0')
|
||||||
|
return std::unexpected("can't send: no XDG_RUNTIME_DIR");
|
||||||
|
|
||||||
|
const auto HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE");
|
||||||
|
|
||||||
|
if (!HIS || HIS[0] == '\0')
|
||||||
|
return std::unexpected("can't send: no HYPRLAND_INSTANCE_SIGNATURE (not running under hyprland)");
|
||||||
|
|
||||||
|
const auto PATH = getFullPath(PATH_RAW);
|
||||||
|
|
||||||
|
if (!PATH)
|
||||||
|
return std::unexpected(std::format("bad path: {}", PATH_RAW));
|
||||||
|
|
||||||
|
auto socketPath = RTDIR + "/hypr/"s + HIS + "/"s + SOCKET_NAME;
|
||||||
|
|
||||||
|
auto socket = Hyprwire::IClientSocket::open(socketPath);
|
||||||
|
|
||||||
|
if (!socket)
|
||||||
|
return std::unexpected("can't send: failed to connect to hyprpaper (is it running?)");
|
||||||
|
|
||||||
|
g_coreImpl = makeShared<CCHyprpaperCoreImpl>(1);
|
||||||
|
|
||||||
|
socket->addImplementation(g_coreImpl);
|
||||||
|
|
||||||
|
if (!socket->waitForHandshake())
|
||||||
|
return std::unexpected("can't send: wire handshake failed");
|
||||||
|
|
||||||
|
auto spec = socket->getSpec(g_coreImpl->protocol()->specName());
|
||||||
|
|
||||||
|
if (!spec)
|
||||||
|
return std::unexpected("can't send: hyprpaper doesn't have the spec?!");
|
||||||
|
|
||||||
|
auto manager = makeShared<CCHyprpaperCoreManagerObject>(socket->bindProtocol(g_coreImpl->protocol(), PROTOCOL_VERSION_SUPPORTED));
|
||||||
|
|
||||||
|
if (!manager)
|
||||||
|
return std::unexpected("wire error: couldn't create manager");
|
||||||
|
|
||||||
|
auto wallpaper = makeShared<CCHyprpaperWallpaperObject>(manager->sendGetWallpaperObject());
|
||||||
|
|
||||||
|
if (!wallpaper)
|
||||||
|
return std::unexpected("wire error: couldn't create wallpaper object");
|
||||||
|
|
||||||
|
bool canExit = false;
|
||||||
|
std::optional<std::string> err;
|
||||||
|
|
||||||
|
wallpaper->setFailed([&canExit, &err](uint32_t code) {
|
||||||
|
canExit = true;
|
||||||
|
err = std::format("failed to set wallpaper, code {}", code);
|
||||||
|
});
|
||||||
|
wallpaper->setSuccess([&canExit]() { canExit = true; });
|
||||||
|
|
||||||
|
wallpaper->sendPath(PATH->c_str());
|
||||||
|
wallpaper->sendMonitorName(MONITOR.c_str());
|
||||||
|
if (!FIT.empty())
|
||||||
|
wallpaper->sendFitMode(fitFromString(FIT));
|
||||||
|
|
||||||
|
wallpaper->sendApply();
|
||||||
|
|
||||||
|
while (!canExit) {
|
||||||
|
socket->dispatchEvents(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err)
|
||||||
|
return std::unexpected(*err);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
8
hyprctl/src/hyprpaper/Hyprpaper.hpp
Normal file
8
hyprctl/src/hyprpaper/Hyprpaper.hpp
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <expected>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Hyprpaper {
|
||||||
|
std::expected<void, std::string> makeHyprpaperRequest(const std::string_view& rq);
|
||||||
|
};
|
||||||
|
|
@ -26,9 +26,12 @@
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
#include <hyprutils/string/String.hpp>
|
#include <hyprutils/string/String.hpp>
|
||||||
|
#include <hyprutils/memory/Casts.hpp>
|
||||||
using namespace Hyprutils::String;
|
using namespace Hyprutils::String;
|
||||||
|
using namespace Hyprutils::Memory;
|
||||||
|
|
||||||
#include "Strings.hpp"
|
#include "Strings.hpp"
|
||||||
|
#include "hyprpaper/Hyprpaper.hpp"
|
||||||
|
|
||||||
std::string instanceSignature;
|
std::string instanceSignature;
|
||||||
bool quiet = false;
|
bool quiet = false;
|
||||||
|
|
@ -206,7 +209,7 @@ int request(std::string_view arg, int minArgs = 0, bool needRoll = false) {
|
||||||
|
|
||||||
strncpy(serverAddress.sun_path, socketPath.c_str(), sizeof(serverAddress.sun_path) - 1);
|
strncpy(serverAddress.sun_path, socketPath.c_str(), sizeof(serverAddress.sun_path) - 1);
|
||||||
|
|
||||||
if (connect(SERVERSOCKET, (sockaddr*)&serverAddress, SUN_LEN(&serverAddress)) < 0) {
|
if (connect(SERVERSOCKET, rc<sockaddr*>(&serverAddress), SUN_LEN(&serverAddress)) < 0) {
|
||||||
log("Couldn't connect to " + socketPath + ". (4)");
|
log("Couldn't connect to " + socketPath + ". (4)");
|
||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
|
|
@ -272,7 +275,7 @@ int requestIPC(std::string_view filename, std::string_view arg) {
|
||||||
|
|
||||||
strncpy(serverAddress.sun_path, socketPath.c_str(), sizeof(serverAddress.sun_path) - 1);
|
strncpy(serverAddress.sun_path, socketPath.c_str(), sizeof(serverAddress.sun_path) - 1);
|
||||||
|
|
||||||
if (connect(SERVERSOCKET, (sockaddr*)&serverAddress, SUN_LEN(&serverAddress)) < 0) {
|
if (connect(SERVERSOCKET, rc<sockaddr*>(&serverAddress), SUN_LEN(&serverAddress)) < 0) {
|
||||||
log("Couldn't connect to " + socketPath + ". (3)");
|
log("Couldn't connect to " + socketPath + ". (3)");
|
||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
|
|
@ -303,10 +306,6 @@ int requestIPC(std::string_view filename, std::string_view arg) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int requestHyprpaper(std::string_view arg) {
|
|
||||||
return requestIPC(".hyprpaper.sock", arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
int requestHyprsunset(std::string_view arg) {
|
int requestHyprsunset(std::string_view arg) {
|
||||||
return requestIPC(".hyprsunset.sock", arg);
|
return requestIPC(".hyprsunset.sock", arg);
|
||||||
}
|
}
|
||||||
|
|
@ -425,6 +424,8 @@ int main(int argc, char** argv) {
|
||||||
std::println("{}", PLUGIN_HELP);
|
std::println("{}", PLUGIN_HELP);
|
||||||
} else if (cmd == "setprop") {
|
} else if (cmd == "setprop") {
|
||||||
std::println("{}", SETPROP_HELP);
|
std::println("{}", SETPROP_HELP);
|
||||||
|
} else if (cmd == "getprop") {
|
||||||
|
std::println("{}", GETPROP_HELP);
|
||||||
} else if (cmd == "switchxkblayout") {
|
} else if (cmd == "switchxkblayout") {
|
||||||
std::println("{}", SWITCHXKBLAYOUT_HELP);
|
std::println("{}", SWITCHXKBLAYOUT_HELP);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -475,7 +476,7 @@ int main(int argc, char** argv) {
|
||||||
|
|
||||||
const auto INSTANCES = instances();
|
const auto INSTANCES = instances();
|
||||||
|
|
||||||
if (INSTANCENO < 0 || static_cast<std::size_t>(INSTANCENO) >= INSTANCES.size()) {
|
if (INSTANCENO < 0 || sc<std::size_t>(INSTANCENO) >= INSTANCES.size()) {
|
||||||
log("no such instance\n");
|
log("no such instance\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
@ -496,9 +497,12 @@ int main(int argc, char** argv) {
|
||||||
|
|
||||||
if (fullRequest.contains("/--batch"))
|
if (fullRequest.contains("/--batch"))
|
||||||
batchRequest(fullRequest, json);
|
batchRequest(fullRequest, json);
|
||||||
else if (fullRequest.contains("/hyprpaper"))
|
else if (fullRequest.contains("/hyprpaper")) {
|
||||||
exitStatus = requestHyprpaper(fullRequest);
|
auto result = Hyprpaper::makeHyprpaperRequest(fullRequest);
|
||||||
else if (fullRequest.contains("/hyprsunset"))
|
if (!result)
|
||||||
|
log(std::format("error: {}", result.error()));
|
||||||
|
exitStatus = !result;
|
||||||
|
} else if (fullRequest.contains("/hyprsunset"))
|
||||||
exitStatus = requestHyprsunset(fullRequest);
|
exitStatus = requestHyprsunset(fullRequest);
|
||||||
else if (fullRequest.contains("/switchxkblayout"))
|
else if (fullRequest.contains("/switchxkblayout"))
|
||||||
exitStatus = request(fullRequest, 2);
|
exitStatus = request(fullRequest, 2);
|
||||||
|
|
@ -4,4 +4,5 @@ Name: Hyprland
|
||||||
URL: https://github.com/hyprwm/Hyprland
|
URL: https://github.com/hyprwm/Hyprland
|
||||||
Description: Hyprland header files
|
Description: Hyprland header files
|
||||||
Version: @HYPRLAND_VERSION@
|
Version: @HYPRLAND_VERSION@
|
||||||
|
Requires: aquamarine >= @AQUAMARINE_MINIMUM_VERSION@, hyprcursor >= @HYPRCURSOR_MINIMUM_VERSION@, hyprgraphics >= @HYPRGRAPHICS_MINIMUM_VERSION@, hyprlang >= @HYPRLANG_MINIMUM_VERSION@, hyprutils >= @HYPRUTILS_MINIMUM_VERSION@, libdrm, egl, cairo, xkbcommon >= @XKBCOMMON_MINIMUM_VERSION@, libinput >= @LIBINPUT_MINIMUM_VERSION@, wayland-server >= @WAYLAND_SERVER_MINIMUM_VERSION@@PKGCONFIG_XWAYLAND_DEPENDENCIES@
|
||||||
Cflags: -I${prefix} -I${prefix}/hyprland/protocols -I${prefix}/hyprland
|
Cflags: -I${prefix} -I${prefix}/hyprland/protocols -I${prefix}/hyprland
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,9 @@ set(CMAKE_CXX_STANDARD 23)
|
||||||
|
|
||||||
pkg_check_modules(hyprpm_deps REQUIRED IMPORTED_TARGET tomlplusplus hyprutils>=0.7.0)
|
pkg_check_modules(hyprpm_deps REQUIRED IMPORTED_TARGET tomlplusplus hyprutils>=0.7.0)
|
||||||
|
|
||||||
find_package(glaze QUIET)
|
find_package(glaze 6.0.0 QUIET)
|
||||||
if (NOT glaze_FOUND)
|
if (NOT glaze_FOUND)
|
||||||
set(GLAZE_VERSION v5.1.1)
|
set(GLAZE_VERSION v6.1.0)
|
||||||
message(STATUS "glaze dependency not found, retrieving ${GLAZE_VERSION} with FetchContent")
|
message(STATUS "glaze dependency not found, retrieving ${GLAZE_VERSION} with FetchContent")
|
||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@ function _hyprpm
|
||||||
set descriptions[6] "Show help menu"
|
set descriptions[6] "Show help menu"
|
||||||
set descriptions[7] "Check and update all plugins if needed"
|
set descriptions[7] "Check and update all plugins if needed"
|
||||||
set descriptions[8] "Install a new plugin repository from git"
|
set descriptions[8] "Install a new plugin repository from git"
|
||||||
set descriptions[9] "Enable too much loggin"
|
set descriptions[9] "Enable too much logging"
|
||||||
set descriptions[10] "Enable too much loggin"
|
set descriptions[10] "Enable too much logging"
|
||||||
set descriptions[11] "Force an operation ignoring checks (e.g. update -f)"
|
set descriptions[11] "Force an operation ignoring checks (e.g. update -f)"
|
||||||
set descriptions[12] "Disable shallow cloning of Hyprland sources"
|
set descriptions[12] "Disable shallow cloning of Hyprland sources"
|
||||||
set descriptions[13] "Remove a plugin repository"
|
set descriptions[13] "Remove a plugin repository"
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ hyprpm [<FLAGS>]... <ARGUMENT>
|
||||||
|
|
||||||
<FLAGS> ::= (--notify | -n) "Send a hyprland notification for important events (e.g. load fail)"
|
<FLAGS> ::= (--notify | -n) "Send a hyprland notification for important events (e.g. load fail)"
|
||||||
| (--help | -h) "Show help menu"
|
| (--help | -h) "Show help menu"
|
||||||
| (--verbose | -v) "Enable too much loggin"
|
| (--verbose | -v) "Enable too much logging"
|
||||||
| (--force | -f) "Force an operation ignoring checks (e.g. update -f)"
|
| (--force | -f) "Force an operation ignoring checks (e.g. update -f)"
|
||||||
| (--no-shallow | -s) "Disable shallow cloning of Hyprland sources"
|
| (--no-shallow | -s) "Disable shallow cloning of Hyprland sources"
|
||||||
;
|
;
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ _hyprpm () {
|
||||||
descriptions[6]="Show help menu"
|
descriptions[6]="Show help menu"
|
||||||
descriptions[7]="Check and update all plugins if needed"
|
descriptions[7]="Check and update all plugins if needed"
|
||||||
descriptions[8]="Install a new plugin repository from git"
|
descriptions[8]="Install a new plugin repository from git"
|
||||||
descriptions[9]="Enable too much loggin"
|
descriptions[9]="Enable too much logging"
|
||||||
descriptions[10]="Enable too much loggin"
|
descriptions[10]="Enable too much logging"
|
||||||
descriptions[11]="Force an operation ignoring checks (e.g. update -f)"
|
descriptions[11]="Force an operation ignoring checks (e.g. update -f)"
|
||||||
descriptions[12]="Disable shallow cloning of Hyprland sources"
|
descriptions[12]="Disable shallow cloning of Hyprland sources"
|
||||||
descriptions[13]="Remove a plugin repository"
|
descriptions[13]="Remove a plugin repository"
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,9 @@ static std::string getTempRoot() {
|
||||||
|
|
||||||
const auto STR = ENV + std::string{"/hyprpm/"};
|
const auto STR = ENV + std::string{"/hyprpm/"};
|
||||||
|
|
||||||
|
if (!std::filesystem::exists(STR))
|
||||||
|
mkdir(STR.c_str(), S_IRWXU);
|
||||||
|
|
||||||
return STR;
|
return STR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,6 +93,7 @@ void DataState::addNewPluginRepo(const SPluginRepository& repo) {
|
||||||
auto DATA = toml::table{
|
auto DATA = toml::table{
|
||||||
{"repository", toml::table{
|
{"repository", toml::table{
|
||||||
{"name", repo.name},
|
{"name", repo.name},
|
||||||
|
{"author", repo.author},
|
||||||
{"hash", repo.hash},
|
{"hash", repo.hash},
|
||||||
{"url", repo.url},
|
{"url", repo.url},
|
||||||
{"rev", repo.rev}
|
{"rev", repo.rev}
|
||||||
|
|
@ -119,31 +123,32 @@ void DataState::addNewPluginRepo(const SPluginRepository& repo) {
|
||||||
Debug::die("{}", failureString("Failed to write plugin state"));
|
Debug::die("{}", failureString("Failed to write plugin state"));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DataState::pluginRepoExists(const std::string& urlOrName) {
|
bool DataState::pluginRepoExists(const SPluginRepoIdentifier identifier) {
|
||||||
ensureStateStoreExists();
|
ensureStateStoreExists();
|
||||||
|
|
||||||
for (const auto& stateFile : getPluginStates()) {
|
for (const auto& stateFile : getPluginStates()) {
|
||||||
const auto STATE = toml::parse_file(stateFile.c_str());
|
const auto STATE = toml::parse_file(stateFile.c_str());
|
||||||
const auto NAME = STATE["repository"]["name"].value_or("");
|
const auto NAME = STATE["repository"]["name"].value_or("");
|
||||||
|
const auto AUTHOR = STATE["repository"]["author"].value_or("");
|
||||||
const auto URL = STATE["repository"]["url"].value_or("");
|
const auto URL = STATE["repository"]["url"].value_or("");
|
||||||
|
|
||||||
if (URL == urlOrName || NAME == urlOrName)
|
if (identifier.matches(URL, NAME, AUTHOR))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataState::removePluginRepo(const std::string& urlOrName) {
|
void DataState::removePluginRepo(const SPluginRepoIdentifier identifier) {
|
||||||
ensureStateStoreExists();
|
ensureStateStoreExists();
|
||||||
|
|
||||||
for (const auto& stateFile : getPluginStates()) {
|
for (const auto& stateFile : getPluginStates()) {
|
||||||
const auto STATE = toml::parse_file(stateFile.c_str());
|
const auto STATE = toml::parse_file(stateFile.c_str());
|
||||||
const auto NAME = STATE["repository"]["name"].value_or("");
|
const auto NAME = STATE["repository"]["name"].value_or("");
|
||||||
|
const auto AUTHOR = STATE["repository"]["author"].value_or("");
|
||||||
const auto URL = STATE["repository"]["url"].value_or("");
|
const auto URL = STATE["repository"]["url"].value_or("");
|
||||||
|
|
||||||
if (URL == urlOrName || NAME == urlOrName) {
|
if (identifier.matches(URL, NAME, AUTHOR)) {
|
||||||
|
|
||||||
// unload the plugins!!
|
// unload the plugins!!
|
||||||
for (const auto& file : std::filesystem::directory_iterator(stateFile.parent_path())) {
|
for (const auto& file : std::filesystem::directory_iterator(stateFile.parent_path())) {
|
||||||
if (!file.path().string().ends_with(".so"))
|
if (!file.path().string().ends_with(".so"))
|
||||||
|
|
@ -178,7 +183,7 @@ void DataState::updateGlobalState(const SGlobalState& state) {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
auto DATA = toml::table{
|
auto DATA = toml::table{
|
||||||
{"state", toml::table{
|
{"state", toml::table{
|
||||||
{"hash", state.headersHashCompiled},
|
{"hash", state.headersAbiCompiled},
|
||||||
{"dont_warn_install", state.dontWarnInstall}
|
{"dont_warn_install", state.dontWarnInstall}
|
||||||
}}
|
}}
|
||||||
};
|
};
|
||||||
|
|
@ -203,7 +208,7 @@ SGlobalState DataState::getGlobalState() {
|
||||||
auto DATA = toml::parse_file(stateFile.c_str());
|
auto DATA = toml::parse_file(stateFile.c_str());
|
||||||
|
|
||||||
SGlobalState state;
|
SGlobalState state;
|
||||||
state.headersHashCompiled = DATA["state"]["hash"].value_or("");
|
state.headersAbiCompiled = DATA["state"]["hash"].value_or("");
|
||||||
state.dontWarnInstall = DATA["state"]["dont_warn_install"].value_or(false);
|
state.dontWarnInstall = DATA["state"]["dont_warn_install"].value_or(false);
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
|
|
@ -217,6 +222,7 @@ std::vector<SPluginRepository> DataState::getAllRepositories() {
|
||||||
const auto STATE = toml::parse_file(stateFile.c_str());
|
const auto STATE = toml::parse_file(stateFile.c_str());
|
||||||
|
|
||||||
const auto NAME = STATE["repository"]["name"].value_or("");
|
const auto NAME = STATE["repository"]["name"].value_or("");
|
||||||
|
const auto AUTHOR = STATE["repository"]["author"].value_or("");
|
||||||
const auto URL = STATE["repository"]["url"].value_or("");
|
const auto URL = STATE["repository"]["url"].value_or("");
|
||||||
const auto REV = STATE["repository"]["rev"].value_or("");
|
const auto REV = STATE["repository"]["rev"].value_or("");
|
||||||
const auto HASH = STATE["repository"]["hash"].value_or("");
|
const auto HASH = STATE["repository"]["hash"].value_or("");
|
||||||
|
|
@ -224,6 +230,7 @@ std::vector<SPluginRepository> DataState::getAllRepositories() {
|
||||||
SPluginRepository repo;
|
SPluginRepository repo;
|
||||||
repo.hash = HASH;
|
repo.hash = HASH;
|
||||||
repo.name = NAME;
|
repo.name = NAME;
|
||||||
|
repo.author = AUTHOR;
|
||||||
repo.url = URL;
|
repo.url = URL;
|
||||||
repo.rev = REV;
|
repo.rev = REV;
|
||||||
|
|
||||||
|
|
@ -244,7 +251,7 @@ std::vector<SPluginRepository> DataState::getAllRepositories() {
|
||||||
return repos;
|
return repos;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DataState::setPluginEnabled(const std::string& name, bool enabled) {
|
bool DataState::setPluginEnabled(const SPluginRepoIdentifier identifier, bool enabled) {
|
||||||
ensureStateStoreExists();
|
ensureStateStoreExists();
|
||||||
|
|
||||||
for (const auto& stateFile : getPluginStates()) {
|
for (const auto& stateFile : getPluginStates()) {
|
||||||
|
|
@ -253,8 +260,17 @@ bool DataState::setPluginEnabled(const std::string& name, bool enabled) {
|
||||||
if (key == "repository")
|
if (key == "repository")
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (key.str() != name)
|
switch (identifier.type) {
|
||||||
|
case IDENTIFIER_NAME:
|
||||||
|
if (key.str() != identifier.name)
|
||||||
continue;
|
continue;
|
||||||
|
break;
|
||||||
|
case IDENTIFIER_AUTHOR_NAME:
|
||||||
|
if (STATE["repository"]["author"] != identifier.author || key.str() != identifier.name)
|
||||||
|
continue;
|
||||||
|
break;
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
|
||||||
const auto FAILED = STATE[key]["failed"].value_or(false);
|
const auto FAILED = STATE[key]["failed"].value_or(false);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
#include "Plugin.hpp"
|
#include "Plugin.hpp"
|
||||||
|
|
||||||
struct SGlobalState {
|
struct SGlobalState {
|
||||||
std::string headersHashCompiled = "";
|
std::string headersAbiCompiled = "";
|
||||||
bool dontWarnInstall = false;
|
bool dontWarnInstall = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -15,11 +15,11 @@ namespace DataState {
|
||||||
std::vector<std::filesystem::path> getPluginStates();
|
std::vector<std::filesystem::path> getPluginStates();
|
||||||
void ensureStateStoreExists();
|
void ensureStateStoreExists();
|
||||||
void addNewPluginRepo(const SPluginRepository& repo);
|
void addNewPluginRepo(const SPluginRepository& repo);
|
||||||
void removePluginRepo(const std::string& urlOrName);
|
void removePluginRepo(const SPluginRepoIdentifier identifier);
|
||||||
bool pluginRepoExists(const std::string& urlOrName);
|
bool pluginRepoExists(const SPluginRepoIdentifier identifier);
|
||||||
void updateGlobalState(const SGlobalState& state);
|
void updateGlobalState(const SGlobalState& state);
|
||||||
void purgeAllCache();
|
void purgeAllCache();
|
||||||
SGlobalState getGlobalState();
|
SGlobalState getGlobalState();
|
||||||
bool setPluginEnabled(const std::string& name, bool enabled);
|
bool setPluginEnabled(const SPluginRepoIdentifier identifier, bool enabled);
|
||||||
std::vector<SPluginRepository> getAllRepositories();
|
std::vector<SPluginRepository> getAllRepositories();
|
||||||
};
|
};
|
||||||
|
|
@ -6,6 +6,9 @@
|
||||||
#include <sys/un.h>
|
#include <sys/un.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <hyprutils/memory/Casts.hpp>
|
||||||
|
|
||||||
|
using namespace Hyprutils::Memory;
|
||||||
|
|
||||||
static int getUID() {
|
static int getUID() {
|
||||||
const auto UID = getuid();
|
const auto UID = getuid();
|
||||||
|
|
@ -46,7 +49,7 @@ std::string NHyprlandSocket::send(const std::string& cmd) {
|
||||||
|
|
||||||
strncpy(serverAddress.sun_path, socketPath.c_str(), sizeof(serverAddress.sun_path) - 1);
|
strncpy(serverAddress.sun_path, socketPath.c_str(), sizeof(serverAddress.sun_path) - 1);
|
||||||
|
|
||||||
if (connect(SERVERSOCKET, (sockaddr*)&serverAddress, SUN_LEN(&serverAddress)) < 0) {
|
if (connect(SERVERSOCKET, rc<sockaddr*>(&serverAddress), SUN_LEN(&serverAddress)) < 0) {
|
||||||
std::println("{}", failureString("Couldn't connect to " + socketPath + ". (4)"));
|
std::println("{}", failureString("Couldn't connect to " + socketPath + ". (4)"));
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
48
hyprpm/src/core/Plugin.cpp
Normal file
48
hyprpm/src/core/Plugin.cpp
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
#include "Plugin.hpp"
|
||||||
|
|
||||||
|
SPluginRepoIdentifier SPluginRepoIdentifier::fromUrl(const std::string& url) {
|
||||||
|
return SPluginRepoIdentifier{.type = IDENTIFIER_URL, .url = url};
|
||||||
|
}
|
||||||
|
|
||||||
|
SPluginRepoIdentifier SPluginRepoIdentifier::fromName(const std::string& name) {
|
||||||
|
return SPluginRepoIdentifier{.type = IDENTIFIER_NAME, .name = name};
|
||||||
|
}
|
||||||
|
|
||||||
|
SPluginRepoIdentifier SPluginRepoIdentifier::fromAuthorName(const std::string& author, const std::string& name) {
|
||||||
|
return SPluginRepoIdentifier{.type = IDENTIFIER_AUTHOR_NAME, .name = name, .author = author};
|
||||||
|
}
|
||||||
|
|
||||||
|
SPluginRepoIdentifier SPluginRepoIdentifier::fromString(const std::string& string) {
|
||||||
|
if (string.find(':') != std::string::npos) {
|
||||||
|
return SPluginRepoIdentifier{.type = IDENTIFIER_URL, .url = string};
|
||||||
|
} else {
|
||||||
|
auto slashPos = string.find('/');
|
||||||
|
if (slashPos != std::string::npos) {
|
||||||
|
std::string author = string.substr(0, slashPos);
|
||||||
|
std::string name = string.substr(slashPos + 1, string.size() - slashPos - 1);
|
||||||
|
return SPluginRepoIdentifier{.type = IDENTIFIER_AUTHOR_NAME, .name = name, .author = author};
|
||||||
|
} else {
|
||||||
|
return SPluginRepoIdentifier{.type = IDENTIFIER_NAME, .name = string};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string SPluginRepoIdentifier::toString() const {
|
||||||
|
switch (type) {
|
||||||
|
case IDENTIFIER_NAME: return name;
|
||||||
|
case IDENTIFIER_AUTHOR_NAME: return author + '/' + name;
|
||||||
|
case IDENTIFIER_URL: return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SPluginRepoIdentifier::matches(const std::string& url, const std::string& name, const std::string& author) const {
|
||||||
|
switch (type) {
|
||||||
|
case IDENTIFIER_URL: return this->url == url;
|
||||||
|
case IDENTIFIER_NAME: return this->name == name;
|
||||||
|
case IDENTIFIER_AUTHOR_NAME: return this->author == author && this->name == name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,27 @@ struct SPluginRepository {
|
||||||
std::string url;
|
std::string url;
|
||||||
std::string rev;
|
std::string rev;
|
||||||
std::string name;
|
std::string name;
|
||||||
|
std::string author;
|
||||||
std::vector<SPlugin> plugins;
|
std::vector<SPlugin> plugins;
|
||||||
std::string hash;
|
std::string hash;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum ePluginRepoIdentifierType {
|
||||||
|
IDENTIFIER_URL,
|
||||||
|
IDENTIFIER_NAME,
|
||||||
|
IDENTIFIER_AUTHOR_NAME
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SPluginRepoIdentifier {
|
||||||
|
ePluginRepoIdentifierType type;
|
||||||
|
std::string url = "";
|
||||||
|
std::string name = "";
|
||||||
|
std::string author = "";
|
||||||
|
|
||||||
|
static SPluginRepoIdentifier fromString(const std::string& string);
|
||||||
|
static SPluginRepoIdentifier fromUrl(const std::string& Url);
|
||||||
|
static SPluginRepoIdentifier fromName(const std::string& name);
|
||||||
|
static SPluginRepoIdentifier fromAuthorName(const std::string& author, const std::string& name);
|
||||||
|
std::string toString() const;
|
||||||
|
bool matches(const std::string& url, const std::string& name, const std::string& author) const;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
#include <print>
|
#include <print>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
@ -26,8 +27,10 @@
|
||||||
|
|
||||||
#include <hyprutils/string/String.hpp>
|
#include <hyprutils/string/String.hpp>
|
||||||
#include <hyprutils/os/Process.hpp>
|
#include <hyprutils/os/Process.hpp>
|
||||||
|
#include <hyprutils/memory/Casts.hpp>
|
||||||
using namespace Hyprutils::String;
|
using namespace Hyprutils::String;
|
||||||
using namespace Hyprutils::OS;
|
using namespace Hyprutils::OS;
|
||||||
|
using namespace Hyprutils::Memory;
|
||||||
|
|
||||||
static std::string execAndGet(std::string cmd) {
|
static std::string execAndGet(std::string cmd) {
|
||||||
cmd += " 2>&1";
|
cmd += " 2>&1";
|
||||||
|
|
@ -76,40 +79,30 @@ SHyprlandVersion CPluginManager::getHyprlandVersion(bool running) {
|
||||||
else
|
else
|
||||||
onceInstalled = true;
|
onceInstalled = true;
|
||||||
|
|
||||||
const auto HLVERCALL = running ? NHyprlandSocket::send("/version") : execAndGet("Hyprland --version");
|
const auto HLVERCALL = running ? NHyprlandSocket::send("j/version") : execAndGet("Hyprland --version-json");
|
||||||
if (m_bVerbose)
|
|
||||||
std::println("{}", verboseString("{} version returned: {}", running ? "running" : "installed", HLVERCALL));
|
|
||||||
|
|
||||||
if (!HLVERCALL.contains("Tag:")) {
|
auto jsonQuery = glz::read_json<glz::generic>(HLVERCALL);
|
||||||
std::println(stderr, "\n{}", failureString("You don't seem to be running Hyprland."));
|
|
||||||
|
if (!jsonQuery) {
|
||||||
|
std::println("{}", failureString("failed to get the current hyprland version. Are you running hyprland?"));
|
||||||
return SHyprlandVersion{};
|
return SHyprlandVersion{};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string hlcommit = HLVERCALL.substr(HLVERCALL.find("at commit") + 10);
|
auto hlbranch = (*jsonQuery)["branch"].get_string();
|
||||||
hlcommit = hlcommit.substr(0, hlcommit.find_first_of(' '));
|
auto hlcommit = (*jsonQuery)["commit"].get_string();
|
||||||
|
auto abiHash = (*jsonQuery)["abiHash"].get_string();
|
||||||
|
auto hldate = (*jsonQuery)["commit_date"].get_string();
|
||||||
|
auto hlcommits = (*jsonQuery)["commits"].get_string();
|
||||||
|
|
||||||
std::string hlbranch = HLVERCALL.substr(HLVERCALL.find("from branch") + 12);
|
size_t commits = 0;
|
||||||
hlbranch = hlbranch.substr(0, hlbranch.find(" at commit "));
|
|
||||||
|
|
||||||
std::string hldate = HLVERCALL.substr(HLVERCALL.find("Date: ") + 6);
|
|
||||||
hldate = hldate.substr(0, hldate.find('\n'));
|
|
||||||
|
|
||||||
std::string hlcommits;
|
|
||||||
|
|
||||||
if (HLVERCALL.contains("commits:")) {
|
|
||||||
hlcommits = HLVERCALL.substr(HLVERCALL.find("commits:") + 9);
|
|
||||||
hlcommits = hlcommits.substr(0, hlcommits.find(' '));
|
|
||||||
}
|
|
||||||
|
|
||||||
int commits = 0;
|
|
||||||
try {
|
try {
|
||||||
commits = std::stoi(hlcommits);
|
commits = std::stoull(hlcommits);
|
||||||
} catch (...) { ; }
|
} catch (...) { ; }
|
||||||
|
|
||||||
if (m_bVerbose)
|
if (m_bVerbose)
|
||||||
std::println("{}", verboseString("parsed commit {} at branch {} on {}, commits {}", hlcommit, hlbranch, hldate, commits));
|
std::println("{}", verboseString("parsed commit {} at branch {} on {}, commits {}", hlcommit, hlbranch, hldate, commits));
|
||||||
|
|
||||||
auto ver = SHyprlandVersion{hlbranch, hlcommit, hldate, commits};
|
auto ver = SHyprlandVersion{hlbranch, hlcommit, hldate, abiHash, commits};
|
||||||
|
|
||||||
if (running)
|
if (running)
|
||||||
verRunning = ver;
|
verRunning = ver;
|
||||||
|
|
@ -144,7 +137,7 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DataState::pluginRepoExists(url)) {
|
if (DataState::pluginRepoExists(SPluginRepoIdentifier::fromUrl(url))) {
|
||||||
std::println(stderr, "\n{}", failureString("Could not clone the plugin repository. Repository already installed."));
|
std::println(stderr, "\n{}", failureString("Could not clone the plugin repository. Repository already installed."));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -152,14 +145,14 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
|
||||||
auto GLOBALSTATE = DataState::getGlobalState();
|
auto GLOBALSTATE = DataState::getGlobalState();
|
||||||
if (!GLOBALSTATE.dontWarnInstall) {
|
if (!GLOBALSTATE.dontWarnInstall) {
|
||||||
std::println("{}!{} Disclaimer: {}", Colors::YELLOW, Colors::RED, Colors::RESET);
|
std::println("{}!{} Disclaimer: {}", Colors::YELLOW, Colors::RED, Colors::RESET);
|
||||||
std::println("plugins, especially not official, have no guarantee of stability, availablity or security.\n"
|
std::println("plugins, especially not official, have no guarantee of stability, availability or security.\n"
|
||||||
"Run them at your own risk.\n"
|
"Run them at your own risk.\n"
|
||||||
"This message will not appear again.");
|
"This message will not appear again.");
|
||||||
GLOBALSTATE.dontWarnInstall = true;
|
GLOBALSTATE.dontWarnInstall = true;
|
||||||
DataState::updateGlobalState(GLOBALSTATE);
|
DataState::updateGlobalState(GLOBALSTATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GLOBALSTATE.headersHashCompiled.empty()) {
|
if (GLOBALSTATE.headersAbiCompiled.empty()) {
|
||||||
std::println("\n{}", failureString("Cannot find headers in the global state. Try running hyprpm update first."));
|
std::println("\n{}", failureString("Cannot find headers in the global state. Try running hyprpm update first."));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -341,7 +334,10 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
|
||||||
std::string repohash = execAndGet("cd " + m_szWorkingPluginDirectory + " && git rev-parse HEAD");
|
std::string repohash = execAndGet("cd " + m_szWorkingPluginDirectory + " && git rev-parse HEAD");
|
||||||
if (repohash.length() > 0)
|
if (repohash.length() > 0)
|
||||||
repohash.pop_back();
|
repohash.pop_back();
|
||||||
repo.name = pManifest->m_repository.name.empty() ? url.substr(url.find_last_of('/') + 1) : pManifest->m_repository.name;
|
auto lastSlash = url.find_last_of('/');
|
||||||
|
auto secondLastSlash = url.find_last_of('/', lastSlash - 1);
|
||||||
|
repo.name = pManifest->m_repository.name.empty() ? url.substr(lastSlash + 1) : pManifest->m_repository.name;
|
||||||
|
repo.author = url.substr(secondLastSlash + 1, lastSlash - secondLastSlash - 1);
|
||||||
repo.url = url;
|
repo.url = url;
|
||||||
repo.rev = rev;
|
repo.rev = rev;
|
||||||
repo.hash = repohash;
|
repo.hash = repohash;
|
||||||
|
|
@ -364,13 +360,13 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CPluginManager::removePluginRepo(const std::string& urlOrName) {
|
bool CPluginManager::removePluginRepo(const SPluginRepoIdentifier identifier) {
|
||||||
if (!DataState::pluginRepoExists(urlOrName)) {
|
if (!DataState::pluginRepoExists(identifier)) {
|
||||||
std::println(stderr, "\n{}", failureString("Could not remove the repository. Repository is not installed."));
|
std::println(stderr, "\n{}", failureString("Could not remove the repository. Repository is not installed."));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << Colors::YELLOW << "!" << Colors::RESET << Colors::RED << " removing a plugin repository: " << Colors::RESET << urlOrName << "\n "
|
std::cout << Colors::YELLOW << "!" << Colors::RESET << Colors::RED << " removing a plugin repository: " << Colors::RESET << identifier.toString() << "\n "
|
||||||
<< "Are you sure? [Y/n] ";
|
<< "Are you sure? [Y/n] ";
|
||||||
std::fflush(stdout);
|
std::fflush(stdout);
|
||||||
std::string input;
|
std::string input;
|
||||||
|
|
@ -381,7 +377,7 @@ bool CPluginManager::removePluginRepo(const std::string& urlOrName) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
DataState::removePluginRepo(urlOrName);
|
DataState::removePluginRepo(identifier);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -442,11 +438,16 @@ eHeadersErrors CPluginManager::headersValid() {
|
||||||
if (hash != HLVER.hash)
|
if (hash != HLVER.hash)
|
||||||
return HEADERS_MISMATCHED;
|
return HEADERS_MISMATCHED;
|
||||||
|
|
||||||
|
// check ABI hash too
|
||||||
|
const auto GLOBALSTATE = DataState::getGlobalState();
|
||||||
|
|
||||||
|
if (GLOBALSTATE.headersAbiCompiled != HLVER.abiHash)
|
||||||
|
return HEADERS_ABI_MISMATCH;
|
||||||
|
|
||||||
return HEADERS_OK;
|
return HEADERS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CPluginManager::updateHeaders(bool force) {
|
bool CPluginManager::updateHeaders(bool force) {
|
||||||
|
|
||||||
DataState::ensureStateStoreExists();
|
DataState::ensureStateStoreExists();
|
||||||
|
|
||||||
const auto HLVER = getHyprlandVersion(false);
|
const auto HLVER = getHyprlandVersion(false);
|
||||||
|
|
@ -587,19 +588,20 @@ bool CPluginManager::updateHeaders(bool force) {
|
||||||
std::filesystem::remove_all(WORKINGDIR);
|
std::filesystem::remove_all(WORKINGDIR);
|
||||||
|
|
||||||
auto HEADERSVALID = headersValid();
|
auto HEADERSVALID = headersValid();
|
||||||
if (HEADERSVALID == HEADERS_OK) {
|
|
||||||
|
if (HEADERSVALID == HEADERS_OK || HEADERSVALID == HEADERS_MISMATCHED || HEADERSVALID == HEADERS_ABI_MISMATCH) {
|
||||||
progress.printMessageAbove(successString("installed headers"));
|
progress.printMessageAbove(successString("installed headers"));
|
||||||
progress.m_iSteps = 5;
|
progress.m_iSteps = 5;
|
||||||
progress.m_szCurrentMessage = "Done!";
|
progress.m_szCurrentMessage = "Done!";
|
||||||
progress.print();
|
progress.print();
|
||||||
|
|
||||||
auto GLOBALSTATE = DataState::getGlobalState();
|
auto GLOBALSTATE = DataState::getGlobalState();
|
||||||
GLOBALSTATE.headersHashCompiled = HLVER.hash;
|
GLOBALSTATE.headersAbiCompiled = HLVER.abiHash;
|
||||||
DataState::updateGlobalState(GLOBALSTATE);
|
DataState::updateGlobalState(GLOBALSTATE);
|
||||||
|
|
||||||
std::print("\n");
|
std::print("\n");
|
||||||
} else {
|
} else {
|
||||||
progress.printMessageAbove(failureString("failed to install headers with error code {} ({})", (int)HEADERSVALID, headerErrorShort(HEADERSVALID)));
|
progress.printMessageAbove(failureString("failed to install headers with error code {} ({})", sc<int>(HEADERSVALID), headerErrorShort(HEADERSVALID)));
|
||||||
progress.printMessageAbove(infoString("if the problem persists, try running hyprpm purge-cache."));
|
progress.printMessageAbove(infoString("if the problem persists, try running hyprpm purge-cache."));
|
||||||
progress.m_iSteps = 5;
|
progress.m_iSteps = 5;
|
||||||
progress.m_szCurrentMessage = "Failed";
|
progress.m_szCurrentMessage = "Failed";
|
||||||
|
|
@ -773,7 +775,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
|
||||||
const auto OLDPLUGINIT = std::find_if(repo.plugins.begin(), repo.plugins.end(), [&](const auto& other) { return other.name == p.name; });
|
const auto OLDPLUGINIT = std::find_if(repo.plugins.begin(), repo.plugins.end(), [&](const auto& other) { return other.name == p.name; });
|
||||||
newrepo.plugins.push_back(SPlugin{p.name, m_szWorkingPluginDirectory + "/" + p.output, OLDPLUGINIT != repo.plugins.end() ? OLDPLUGINIT->enabled : false});
|
newrepo.plugins.push_back(SPlugin{p.name, m_szWorkingPluginDirectory + "/" + p.output, OLDPLUGINIT != repo.plugins.end() ? OLDPLUGINIT->enabled : false});
|
||||||
}
|
}
|
||||||
DataState::removePluginRepo(newrepo.name);
|
DataState::removePluginRepo(SPluginRepoIdentifier::fromName(newrepo.name));
|
||||||
DataState::addNewPluginRepo(newrepo);
|
DataState::addNewPluginRepo(newrepo);
|
||||||
|
|
||||||
std::filesystem::remove_all(m_szWorkingPluginDirectory);
|
std::filesystem::remove_all(m_szWorkingPluginDirectory);
|
||||||
|
|
@ -786,7 +788,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
|
||||||
progress.print();
|
progress.print();
|
||||||
|
|
||||||
auto GLOBALSTATE = DataState::getGlobalState();
|
auto GLOBALSTATE = DataState::getGlobalState();
|
||||||
GLOBALSTATE.headersHashCompiled = HLVER.hash;
|
GLOBALSTATE.headersAbiCompiled = HLVER.abiHash;
|
||||||
DataState::updateGlobalState(GLOBALSTATE);
|
DataState::updateGlobalState(GLOBALSTATE);
|
||||||
|
|
||||||
progress.m_iSteps++;
|
progress.m_iSteps++;
|
||||||
|
|
@ -798,17 +800,23 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CPluginManager::enablePlugin(const std::string& name) {
|
bool CPluginManager::enablePlugin(const SPluginRepoIdentifier identifier) {
|
||||||
bool ret = DataState::setPluginEnabled(name, true);
|
bool ret = false;
|
||||||
|
|
||||||
|
switch (identifier.type) {
|
||||||
|
case IDENTIFIER_NAME:
|
||||||
|
case IDENTIFIER_AUTHOR_NAME: ret = DataState::setPluginEnabled(identifier, true); break;
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
if (ret)
|
if (ret)
|
||||||
std::println("{}", successString("Enabled {}", name));
|
std::println("{}", successString("Enabled {}", identifier.name));
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CPluginManager::disablePlugin(const std::string& name) {
|
bool CPluginManager::disablePlugin(const SPluginRepoIdentifier identifier) {
|
||||||
bool ret = DataState::setPluginEnabled(name, false);
|
bool ret = DataState::setPluginEnabled(identifier, false);
|
||||||
if (ret)
|
if (ret)
|
||||||
std::println("{}", successString("Disabled {}", name));
|
std::println("{}", successString("Disabled {}", identifier.name));
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -826,7 +834,7 @@ ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState(bool forceReload)
|
||||||
}
|
}
|
||||||
const auto HYPRPMPATH = DataState::getDataStatePath();
|
const auto HYPRPMPATH = DataState::getDataStatePath();
|
||||||
|
|
||||||
const auto json = glz::read_json<glz::json_t::array_t>(NHyprlandSocket::send("j/plugins list"));
|
const auto json = glz::read_json<glz::generic::array_t>(NHyprlandSocket::send("j/plugins list"));
|
||||||
if (!json) {
|
if (!json) {
|
||||||
std::println(stderr, "PluginManager: couldn't parse plugin list output");
|
std::println(stderr, "PluginManager: couldn't parse plugin list output");
|
||||||
return LOADSTATE_FAIL;
|
return LOADSTATE_FAIL;
|
||||||
|
|
@ -911,9 +919,9 @@ bool CPluginManager::loadUnloadPlugin(const std::string& path, bool load) {
|
||||||
auto state = DataState::getGlobalState();
|
auto state = DataState::getGlobalState();
|
||||||
auto HLVER = getHyprlandVersion(true);
|
auto HLVER = getHyprlandVersion(true);
|
||||||
|
|
||||||
if (state.headersHashCompiled != HLVER.hash) {
|
if (state.headersAbiCompiled != HLVER.abiHash) {
|
||||||
if (load)
|
if (load)
|
||||||
std::println("{}", infoString("Running Hyprland version ({}) differs from plugin state ({}), please restart Hyprland.", HLVER.hash, state.headersHashCompiled));
|
std::println("{}", infoString("Running Hyprland version ({}) differs from plugin state ({}), please restart Hyprland.", HLVER.hash, state.headersAbiCompiled));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -929,7 +937,7 @@ void CPluginManager::listAllPlugins() {
|
||||||
const auto REPOS = DataState::getAllRepositories();
|
const auto REPOS = DataState::getAllRepositories();
|
||||||
|
|
||||||
for (auto const& r : REPOS) {
|
for (auto const& r : REPOS) {
|
||||||
std::println("{}", infoString("Repository {}:", r.name));
|
std::println("{}", infoString("Repository {} (by {}):", r.name, r.author));
|
||||||
|
|
||||||
for (auto const& p : r.plugins) {
|
for (auto const& p : r.plugins) {
|
||||||
std::println(" │ Plugin {}", p.name);
|
std::println(" │ Plugin {}", p.name);
|
||||||
|
|
@ -945,7 +953,7 @@ void CPluginManager::listAllPlugins() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPluginManager::notify(const eNotifyIcons icon, uint32_t color, int durationMs, const std::string& message) {
|
void CPluginManager::notify(const eNotifyIcons icon, uint32_t color, int durationMs, const std::string& message) {
|
||||||
NHyprlandSocket::send("/notify " + std::to_string((int)icon) + " " + std::to_string(durationMs) + " " + std::to_string(color) + " " + message);
|
NHyprlandSocket::send("/notify " + std::to_string(icon) + " " + std::to_string(durationMs) + " " + std::to_string(color) + " " + message);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string CPluginManager::headerError(const eHeadersErrors err) {
|
std::string CPluginManager::headerError(const eHeadersErrors err) {
|
||||||
|
|
@ -954,6 +962,7 @@ std::string CPluginManager::headerError(const eHeadersErrors err) {
|
||||||
case HEADERS_MISMATCHED: return failureString("Headers version mismatch. Please run hyprpm update to fix those.\n");
|
case HEADERS_MISMATCHED: return failureString("Headers version mismatch. Please run hyprpm update to fix those.\n");
|
||||||
case HEADERS_NOT_HYPRLAND: return failureString("It doesn't seem you are running on hyprland.\n");
|
case HEADERS_NOT_HYPRLAND: return failureString("It doesn't seem you are running on hyprland.\n");
|
||||||
case HEADERS_MISSING: return failureString("Headers missing. Please run hyprpm update to fix those.\n");
|
case HEADERS_MISSING: return failureString("Headers missing. Please run hyprpm update to fix those.\n");
|
||||||
|
case HEADERS_ABI_MISMATCH: return failureString("ABI is mismatched. Please run hyprpm update to fix that.\n");
|
||||||
case HEADERS_DUPLICATED: {
|
case HEADERS_DUPLICATED: {
|
||||||
return failureString("Headers duplicated!!! This is a very bad sign.\n"
|
return failureString("Headers duplicated!!! This is a very bad sign.\n"
|
||||||
"This could be due to e.g. installing hyprland manually while a system package of hyprland is also installed.\n"
|
"This could be due to e.g. installing hyprland manually while a system package of hyprland is also installed.\n"
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#include "Plugin.hpp"
|
||||||
|
|
||||||
enum eHeadersErrors {
|
enum eHeadersErrors {
|
||||||
HEADERS_OK = 0,
|
HEADERS_OK = 0,
|
||||||
|
|
@ -10,6 +13,7 @@ enum eHeadersErrors {
|
||||||
HEADERS_MISSING,
|
HEADERS_MISSING,
|
||||||
HEADERS_CORRUPTED,
|
HEADERS_CORRUPTED,
|
||||||
HEADERS_MISMATCHED,
|
HEADERS_MISMATCHED,
|
||||||
|
HEADERS_ABI_MISMATCH,
|
||||||
HEADERS_DUPLICATED
|
HEADERS_DUPLICATED
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -35,6 +39,7 @@ struct SHyprlandVersion {
|
||||||
std::string branch;
|
std::string branch;
|
||||||
std::string hash;
|
std::string hash;
|
||||||
std::string date;
|
std::string date;
|
||||||
|
std::string abiHash;
|
||||||
int commits = 0;
|
int commits = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -43,7 +48,7 @@ class CPluginManager {
|
||||||
CPluginManager();
|
CPluginManager();
|
||||||
|
|
||||||
bool addNewPluginRepo(const std::string& url, const std::string& rev);
|
bool addNewPluginRepo(const std::string& url, const std::string& rev);
|
||||||
bool removePluginRepo(const std::string& urlOrName);
|
bool removePluginRepo(const SPluginRepoIdentifier identifier);
|
||||||
|
|
||||||
eHeadersErrors headersValid();
|
eHeadersErrors headersValid();
|
||||||
bool updateHeaders(bool force = false);
|
bool updateHeaders(bool force = false);
|
||||||
|
|
@ -51,8 +56,8 @@ class CPluginManager {
|
||||||
|
|
||||||
void listAllPlugins();
|
void listAllPlugins();
|
||||||
|
|
||||||
bool enablePlugin(const std::string& name);
|
bool enablePlugin(const SPluginRepoIdentifier identifier);
|
||||||
bool disablePlugin(const std::string& name);
|
bool disablePlugin(const SPluginRepoIdentifier identifier);
|
||||||
ePluginLoadStateReturn ensurePluginsLoadState(bool forceReload = false);
|
ePluginLoadStateReturn ensurePluginsLoadState(bool forceReload = false);
|
||||||
|
|
||||||
bool loadUnloadPlugin(const std::string& path, bool load);
|
bool loadUnloadPlugin(const std::string& path, bool load);
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
#include <print>
|
#include <print>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
#include <hyprutils/os/Process.hpp>
|
#include <hyprutils/os/Process.hpp>
|
||||||
#include <hyprutils/string/VarList.hpp>
|
#include <hyprutils/string/VarList.hpp>
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,11 @@ using namespace Hyprutils::Utils;
|
||||||
|
|
||||||
constexpr std::string_view HELP = R"#(┏ hyprpm, a Hyprland Plugin Manager
|
constexpr std::string_view HELP = R"#(┏ hyprpm, a Hyprland Plugin Manager
|
||||||
┃
|
┃
|
||||||
┣ add [url] [git rev] → Install a new plugin repository from git. Git revision
|
┣ add <url> [git rev] → Install a new plugin repository from git. Git revision
|
||||||
┃ is optional, when set, commit locks are ignored.
|
┃ is optional, when set, commit locks are ignored.
|
||||||
┣ remove [url/name] → Remove an installed plugin repository.
|
┣ remove <url|name|author/name> → Remove an installed plugin repository.
|
||||||
┣ enable [name] → Enable a plugin.
|
┣ enable <name|author/name> → Enable a plugin.
|
||||||
┣ disable [name] → Disable a plugin.
|
┣ disable <name|author/name> → Disable a plugin.
|
||||||
┣ update → Check and update all plugins if needed.
|
┣ update → Check and update all plugins if needed.
|
||||||
┣ reload → Reload hyprpm state. Ensure all enabled plugins are loaded.
|
┣ reload → Reload hyprpm state. Ensure all enabled plugins are loaded.
|
||||||
┣ list → List all installed plugins.
|
┣ list → List all installed plugins.
|
||||||
|
|
@ -25,8 +25,8 @@ constexpr std::string_view HELP = R"#(┏ hyprpm, a Hyprland Plugin Manager
|
||||||
┃
|
┃
|
||||||
┣ Flags:
|
┣ Flags:
|
||||||
┃
|
┃
|
||||||
┣ --notify | -n → Send a hyprland notification for important events (including both successes and fail events).
|
┣ --notify | -n → Send a hyprland notification confirming successful plugin load.
|
||||||
┣ --notify-fail | -nn → Send a hyprland notification for fail events only.
|
┃ Warnings/Errors trigger notifications regardless of this flag.
|
||||||
┣ --help | -h → Show this menu.
|
┣ --help | -h → Show this menu.
|
||||||
┣ --verbose | -v → Enable too much logging.
|
┣ --verbose | -v → Enable too much logging.
|
||||||
┣ --force | -f → Force an operation ignoring checks (e.g. update -f).
|
┣ --force | -f → Force an operation ignoring checks (e.g. update -f).
|
||||||
|
|
@ -47,7 +47,7 @@ int main(int argc, char** argv, char** envp) {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> command;
|
std::vector<std::string> command;
|
||||||
bool notify = false, notifyFail = false, verbose = false, force = false, noShallow = false;
|
bool notify = false, verbose = false, force = false, noShallow = false;
|
||||||
std::string customHlUrl;
|
std::string customHlUrl;
|
||||||
|
|
||||||
for (int i = 1; i < argc; ++i) {
|
for (int i = 1; i < argc; ++i) {
|
||||||
|
|
@ -58,7 +58,9 @@ int main(int argc, char** argv, char** envp) {
|
||||||
} else if (ARGS[i] == "--notify" || ARGS[i] == "-n") {
|
} else if (ARGS[i] == "--notify" || ARGS[i] == "-n") {
|
||||||
notify = true;
|
notify = true;
|
||||||
} else if (ARGS[i] == "--notify-fail" || ARGS[i] == "-nn") {
|
} else if (ARGS[i] == "--notify-fail" || ARGS[i] == "-nn") {
|
||||||
notifyFail = notify = true;
|
// TODO: Deprecated since v.053.0. Remove in version>0.56.0
|
||||||
|
std::println(stderr, "{}", failureString("Deprececated flag."));
|
||||||
|
g_pPluginManager->notify(ICON_INFO, 0, 10000, "[hyprpm] -n flag is deprecated, see hyprpm --help.");
|
||||||
} else if (ARGS[i] == "--verbose" || ARGS[i] == "-v") {
|
} else if (ARGS[i] == "--verbose" || ARGS[i] == "-v") {
|
||||||
verbose = true;
|
verbose = true;
|
||||||
} else if (ARGS[i] == "--no-shallow" || ARGS[i] == "-s") {
|
} else if (ARGS[i] == "--no-shallow" || ARGS[i] == "-s") {
|
||||||
|
|
@ -104,7 +106,7 @@ int main(int argc, char** argv, char** envp) {
|
||||||
const auto HLVER = g_pPluginManager->getHyprlandVersion();
|
const auto HLVER = g_pPluginManager->getHyprlandVersion();
|
||||||
auto GLOBALSTATE = DataState::getGlobalState();
|
auto GLOBALSTATE = DataState::getGlobalState();
|
||||||
|
|
||||||
if (GLOBALSTATE.headersHashCompiled != HLVER.hash) {
|
if (GLOBALSTATE.headersAbiCompiled != HLVER.abiHash) {
|
||||||
std::println(stderr, "{}", failureString("Headers outdated, please run hyprpm update."));
|
std::println(stderr, "{}", failureString("Headers outdated, please run hyprpm update."));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
@ -124,7 +126,7 @@ int main(int argc, char** argv, char** envp) {
|
||||||
NSys::root::cacheSudo();
|
NSys::root::cacheSudo();
|
||||||
CScopeGuard x([] { NSys::root::dropSudo(); });
|
CScopeGuard x([] { NSys::root::dropSudo(); });
|
||||||
|
|
||||||
return g_pPluginManager->removePluginRepo(command[1]) ? 0 : 1;
|
return g_pPluginManager->removePluginRepo(SPluginRepoIdentifier::fromString(command[1])) ? 0 : 1;
|
||||||
} else if (command[0] == "update") {
|
} else if (command[0] == "update") {
|
||||||
NSys::root::cacheSudo();
|
NSys::root::cacheSudo();
|
||||||
CScopeGuard x([] { NSys::root::dropSudo(); });
|
CScopeGuard x([] { NSys::root::dropSudo(); });
|
||||||
|
|
@ -135,7 +137,7 @@ int main(int argc, char** argv, char** envp) {
|
||||||
if (headers) {
|
if (headers) {
|
||||||
const auto HLVER = g_pPluginManager->getHyprlandVersion(false);
|
const auto HLVER = g_pPluginManager->getHyprlandVersion(false);
|
||||||
auto GLOBALSTATE = DataState::getGlobalState();
|
auto GLOBALSTATE = DataState::getGlobalState();
|
||||||
const auto COMPILEDOUTDATED = HLVER.hash != GLOBALSTATE.headersHashCompiled;
|
const auto COMPILEDOUTDATED = HLVER.abiHash != GLOBALSTATE.headersAbiCompiled;
|
||||||
|
|
||||||
bool ret1 = g_pPluginManager->updatePlugins(!headersValid || force || COMPILEDOUTDATED);
|
bool ret1 = g_pPluginManager->updatePlugins(!headersValid || force || COMPILEDOUTDATED);
|
||||||
|
|
||||||
|
|
@ -149,15 +151,16 @@ int main(int argc, char** argv, char** envp) {
|
||||||
|
|
||||||
if (ret2 != LOADSTATE_OK)
|
if (ret2 != LOADSTATE_OK)
|
||||||
return 1;
|
return 1;
|
||||||
} else if (notify)
|
} else {
|
||||||
g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Couldn't update headers");
|
g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Couldn't update headers");
|
||||||
|
}
|
||||||
} else if (command[0] == "enable") {
|
} else if (command[0] == "enable") {
|
||||||
if (command.size() < 2) {
|
if (command.size() < 2) {
|
||||||
std::println(stderr, "{}", failureString("Not enough args for enable."));
|
std::println(stderr, "{}", failureString("Not enough args for enable."));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!g_pPluginManager->enablePlugin(command[1])) {
|
if (!g_pPluginManager->enablePlugin(SPluginRepoIdentifier::fromString(command[1]))) {
|
||||||
std::println(stderr, "{}", failureString("Couldn't enable plugin (missing?)"));
|
std::println(stderr, "{}", failureString("Couldn't enable plugin (missing?)"));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
@ -178,7 +181,7 @@ int main(int argc, char** argv, char** envp) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!g_pPluginManager->disablePlugin(command[1])) {
|
if (!g_pPluginManager->disablePlugin(SPluginRepoIdentifier::fromString(command[1]))) {
|
||||||
std::println(stderr, "{}", failureString("Couldn't disable plugin (missing?)"));
|
std::println(stderr, "{}", failureString("Couldn't disable plugin (missing?)"));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
@ -194,7 +197,6 @@ int main(int argc, char** argv, char** envp) {
|
||||||
auto ret = g_pPluginManager->ensurePluginsLoadState(force);
|
auto ret = g_pPluginManager->ensurePluginsLoadState(force);
|
||||||
|
|
||||||
if (ret != LOADSTATE_OK) {
|
if (ret != LOADSTATE_OK) {
|
||||||
if (notify) {
|
|
||||||
switch (ret) {
|
switch (ret) {
|
||||||
case LOADSTATE_FAIL:
|
case LOADSTATE_FAIL:
|
||||||
case LOADSTATE_PARTIAL_FAIL: g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Failed to load plugins"); break;
|
case LOADSTATE_PARTIAL_FAIL: g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Failed to load plugins"); break;
|
||||||
|
|
@ -203,10 +205,9 @@ int main(int argc, char** argv, char** envp) {
|
||||||
break;
|
break;
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
} else if (notify && !notifyFail) {
|
} else if (notify) {
|
||||||
g_pPluginManager->notify(ICON_OK, 0, 4000, "[hyprpm] Loaded plugins");
|
g_pPluginManager->notify(ICON_OK, 0, 4000, "[hyprpm] Loaded plugins");
|
||||||
}
|
}
|
||||||
} else if (command[0] == "purge-cache") {
|
} else if (command[0] == "purge-cache") {
|
||||||
|
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
globber = run_command('sh', '-c', 'find . -name "*.cpp" | sort', check: true)
|
|
||||||
src = globber.stdout().strip().split('\n')
|
|
||||||
|
|
||||||
executable(
|
|
||||||
'hyprpm',
|
|
||||||
src,
|
|
||||||
dependencies: [
|
|
||||||
dependency('hyprutils', version: '>= 0.1.1'),
|
|
||||||
dependency('threads'),
|
|
||||||
dependency('tomlplusplus'),
|
|
||||||
dependency('glaze', method: 'cmake'),
|
|
||||||
],
|
|
||||||
install: true,
|
|
||||||
)
|
|
||||||
|
|
||||||
install_data(
|
|
||||||
'../hyprpm.bash',
|
|
||||||
install_dir: join_paths(get_option('datadir'), 'bash-completion/completions'),
|
|
||||||
install_tag: 'runtime',
|
|
||||||
rename: 'hyprpm',
|
|
||||||
)
|
|
||||||
install_data(
|
|
||||||
'../hyprpm.fish',
|
|
||||||
install_dir: join_paths(get_option('datadir'), 'fish/vendor_completions.d'),
|
|
||||||
install_tag: 'runtime',
|
|
||||||
)
|
|
||||||
install_data(
|
|
||||||
'../hyprpm.zsh',
|
|
||||||
install_dir: join_paths(get_option('datadir'), 'zsh/site-functions'),
|
|
||||||
install_tag: 'runtime',
|
|
||||||
rename: '_hyprpm',
|
|
||||||
)
|
|
||||||
|
|
@ -9,9 +9,11 @@
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <hyprutils/memory/Casts.hpp>
|
||||||
#include "../helpers/Colors.hpp"
|
#include "../helpers/Colors.hpp"
|
||||||
|
|
||||||
|
using namespace Hyprutils::Memory;
|
||||||
|
|
||||||
static winsize getTerminalSize() {
|
static winsize getTerminalSize() {
|
||||||
winsize w{};
|
winsize w{};
|
||||||
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
|
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
|
||||||
|
|
@ -44,7 +46,7 @@ void CProgressBar::print() {
|
||||||
percentDone = m_fPercentage;
|
percentDone = m_fPercentage;
|
||||||
else {
|
else {
|
||||||
// check for divide-by-zero
|
// check for divide-by-zero
|
||||||
percentDone = m_iMaxSteps > 0 ? static_cast<float>(m_iSteps) / m_iMaxSteps : 0.0f;
|
percentDone = m_iMaxSteps > 0 ? sc<float>(m_iSteps) / m_iMaxSteps : 0.0f;
|
||||||
}
|
}
|
||||||
// clamp to ensure no overflows (sanity check)
|
// clamp to ensure no overflows (sanity check)
|
||||||
percentDone = std::clamp(percentDone, 0.0f, 1.0f);
|
percentDone = std::clamp(percentDone, 0.0f, 1.0f);
|
||||||
|
|
@ -54,7 +56,7 @@ void CProgressBar::print() {
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
oss << ' ' << Colors::GREEN;
|
oss << ' ' << Colors::GREEN;
|
||||||
|
|
||||||
size_t filled = static_cast<size_t>(std::floor(percentDone * BARWIDTH));
|
size_t filled = std::floor(percentDone * BARWIDTH);
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
|
|
||||||
for (; i < filled; ++i)
|
for (; i < filled; ++i)
|
||||||
|
|
@ -69,7 +71,7 @@ void CProgressBar::print() {
|
||||||
oss << Colors::RESET;
|
oss << Colors::RESET;
|
||||||
|
|
||||||
if (m_fPercentage >= 0.0f)
|
if (m_fPercentage >= 0.0f)
|
||||||
oss << " " << std::format("{}%", static_cast<int>(percentDone * 100.0)) << ' ';
|
oss << " " << std::format("{}%", sc<int>(percentDone * 100.0)) << ' ';
|
||||||
else
|
else
|
||||||
oss << " " << std::format("{} / {}", m_iSteps, m_iMaxSteps) << ' ';
|
oss << " " << std::format("{} / {}", m_iSteps, m_iMaxSteps) << ' ';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ project(hyprtester DESCRIPTION "Hyprland test suite")
|
||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 26)
|
set(CMAKE_CXX_STANDARD 26)
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
|
||||||
|
|
||||||
find_package(PkgConfig REQUIRED)
|
find_package(PkgConfig REQUIRED)
|
||||||
|
|
||||||
|
|
@ -28,3 +29,73 @@ install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/test.conf
|
||||||
|
|
||||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/plugin/hyprtestplugin.so
|
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/plugin/hyprtestplugin.so
|
||||||
DESTINATION ${CMAKE_INSTALL_PREFIX}/lib)
|
DESTINATION ${CMAKE_INSTALL_PREFIX}/lib)
|
||||||
|
|
||||||
|
file(WRITE ${CMAKE_CURRENT_SOURCE_DIR}/src/tests/clients/build.hpp
|
||||||
|
"#include <string>\n"
|
||||||
|
"static const std::string binaryDir = \"${CMAKE_CURRENT_BINARY_DIR}\";"
|
||||||
|
)
|
||||||
|
|
||||||
|
######## wayland protocols testing stuff
|
||||||
|
|
||||||
|
if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
|
||||||
|
set(CMAKE_EXECUTABLE_ENABLE_EXPORTS TRUE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
find_package(hyprwayland-scanner 0.4.0 REQUIRED)
|
||||||
|
pkg_check_modules(
|
||||||
|
protocols_deps
|
||||||
|
REQUIRED
|
||||||
|
IMPORTED_TARGET
|
||||||
|
hyprutils>=0.8.0
|
||||||
|
wayland-client
|
||||||
|
wayland-protocols
|
||||||
|
)
|
||||||
|
|
||||||
|
pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir)
|
||||||
|
message(STATUS "Found wayland-protocols at ${WAYLAND_PROTOCOLS_DIR}")
|
||||||
|
pkg_get_variable(WAYLAND_SCANNER_PKGDATA_DIR wayland-scanner pkgdatadir)
|
||||||
|
message(STATUS "Found wayland-scanner pkgdatadir at ${WAYLAND_SCANNER_PKGDATA_DIR}")
|
||||||
|
|
||||||
|
# gen core wayland stuff
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/protocols/wayland.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/protocols/wayland.hpp
|
||||||
|
COMMAND hyprwayland-scanner --wayland-enums --client
|
||||||
|
${WAYLAND_SCANNER_PKGDATA_DIR}/wayland.xml ${CMAKE_CURRENT_SOURCE_DIR}/protocols/
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|
||||||
|
function(protocolNew protoPath protoName external)
|
||||||
|
if(external)
|
||||||
|
set(path ${CMAKE_CURRENT_SOURCE_DIR}/${protoPath})
|
||||||
|
else()
|
||||||
|
set(path ${WAYLAND_PROTOCOLS_DIR}/${protoPath})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/protocols/${protoName}.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/protocols/${protoName}.hpp
|
||||||
|
COMMAND hyprwayland-scanner --client ${path}/${protoName}.xml
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/protocols/
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
endfunction()
|
||||||
|
function(clientNew sourceName)
|
||||||
|
cmake_parse_arguments(PARSE_ARGV 1 ARG "" "" "PROTOS")
|
||||||
|
|
||||||
|
add_executable(${sourceName} clients/${sourceName}.cpp)
|
||||||
|
|
||||||
|
target_include_directories(${sourceName} BEFORE PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/protocols")
|
||||||
|
target_link_libraries(${sourceName} PUBLIC PkgConfig::protocols_deps)
|
||||||
|
|
||||||
|
target_sources(${sourceName} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/protocols/wayland.cpp ${CMAKE_CURRENT_SOURCE_DIR}/protocols/wayland.hpp)
|
||||||
|
|
||||||
|
foreach(protoName IN LISTS ARG_PROTOS)
|
||||||
|
target_sources(${sourceName} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/protocols/${protoName}.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/protocols/${protoName}.hpp)
|
||||||
|
endforeach()
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
protocolnew("staging/pointer-warp" "pointer-warp-v1" false)
|
||||||
|
protocolnew("stable/xdg-shell" "xdg-shell" false)
|
||||||
|
|
||||||
|
clientNew("pointer-warp" PROTOS "pointer-warp-v1" "xdg-shell")
|
||||||
|
clientNew("pointer-scroll" PROTOS "xdg-shell")
|
||||||
|
|
|
||||||
318
hyprtester/clients/pointer-scroll.cpp
Normal file
318
hyprtester/clients/pointer-scroll.cpp
Normal file
|
|
@ -0,0 +1,318 @@
|
||||||
|
#include <cstring>
|
||||||
|
#include <sys/poll.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <print>
|
||||||
|
#include <format>
|
||||||
|
#include <string>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
#include <wayland-client.h>
|
||||||
|
#include <wayland.hpp>
|
||||||
|
#include <xdg-shell.hpp>
|
||||||
|
#include <pointer-warp-v1.hpp>
|
||||||
|
|
||||||
|
#include <hyprutils/memory/SharedPtr.hpp>
|
||||||
|
#include <hyprutils/math/Vector2D.hpp>
|
||||||
|
#include <hyprutils/os/FileDescriptor.hpp>
|
||||||
|
|
||||||
|
using Hyprutils::Math::Vector2D;
|
||||||
|
using namespace Hyprutils::Memory;
|
||||||
|
|
||||||
|
struct SWlState {
|
||||||
|
wl_display* display;
|
||||||
|
CSharedPointer<CCWlRegistry> registry;
|
||||||
|
|
||||||
|
// protocols
|
||||||
|
CSharedPointer<CCWlCompositor> wlCompositor;
|
||||||
|
CSharedPointer<CCWlSeat> wlSeat;
|
||||||
|
CSharedPointer<CCWlShm> wlShm;
|
||||||
|
CSharedPointer<CCXdgWmBase> xdgShell;
|
||||||
|
|
||||||
|
// shm/buffer stuff
|
||||||
|
CSharedPointer<CCWlShmPool> shmPool;
|
||||||
|
CSharedPointer<CCWlBuffer> shmBuf;
|
||||||
|
int shmFd;
|
||||||
|
size_t shmBufSize;
|
||||||
|
bool xrgb8888_support = false;
|
||||||
|
|
||||||
|
// surface/toplevel stuff
|
||||||
|
CSharedPointer<CCWlSurface> surf;
|
||||||
|
CSharedPointer<CCXdgSurface> xdgSurf;
|
||||||
|
CSharedPointer<CCXdgToplevel> xdgToplevel;
|
||||||
|
Vector2D geom;
|
||||||
|
|
||||||
|
// pointer
|
||||||
|
CSharedPointer<CCWlPointer> pointer;
|
||||||
|
uint32_t enterSerial;
|
||||||
|
|
||||||
|
// last delta
|
||||||
|
float lastScrollDelta = -1.F;
|
||||||
|
bool writeDelta = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::ofstream logfile;
|
||||||
|
|
||||||
|
static bool debug, started, shouldExit;
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
//NOLINTNEXTLINE
|
||||||
|
static void clientLog(std::format_string<Args...> fmt, Args&&... args) {
|
||||||
|
std::string text = std::vformat(fmt.get(), std::make_format_args(args...));
|
||||||
|
std::println("{}", text);
|
||||||
|
logfile << text << std::endl;
|
||||||
|
std::fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
//NOLINTNEXTLINE
|
||||||
|
static void debugLog(std::format_string<Args...> fmt, Args&&... args) {
|
||||||
|
std::string text = std::vformat(fmt.get(), std::make_format_args(args...));
|
||||||
|
logfile << text << std::endl;
|
||||||
|
if (!debug)
|
||||||
|
return;
|
||||||
|
std::println("{}", text);
|
||||||
|
std::fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool bindRegistry(SWlState& state) {
|
||||||
|
state.registry = makeShared<CCWlRegistry>((wl_proxy*)wl_display_get_registry(state.display));
|
||||||
|
|
||||||
|
state.registry->setGlobal([&](CCWlRegistry* r, uint32_t id, const char* name, uint32_t version) {
|
||||||
|
const std::string NAME = name;
|
||||||
|
if (NAME == "wl_compositor") {
|
||||||
|
debugLog(" > binding to global: {} (version {}) with id {}", name, version, id);
|
||||||
|
state.wlCompositor = makeShared<CCWlCompositor>((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wl_compositor_interface, 6));
|
||||||
|
} else if (NAME == "wl_shm") {
|
||||||
|
debugLog(" > binding to global: {} (version {}) with id {}", name, version, id);
|
||||||
|
state.wlShm = makeShared<CCWlShm>((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wl_shm_interface, 1));
|
||||||
|
} else if (NAME == "wl_seat") {
|
||||||
|
debugLog(" > binding to global: {} (version {}) with id {}", name, version, id);
|
||||||
|
state.wlSeat = makeShared<CCWlSeat>((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wl_seat_interface, 9));
|
||||||
|
} else if (NAME == "xdg_wm_base") {
|
||||||
|
debugLog(" > binding to global: {} (version {}) with id {}", name, version, id);
|
||||||
|
state.xdgShell = makeShared<CCXdgWmBase>((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &xdg_wm_base_interface, 1));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
state.registry->setGlobalRemove([](CCWlRegistry* r, uint32_t id) { debugLog("Global {} removed", id); });
|
||||||
|
|
||||||
|
wl_display_roundtrip(state.display);
|
||||||
|
|
||||||
|
if (!state.wlCompositor || !state.wlShm || !state.wlSeat || !state.xdgShell) {
|
||||||
|
clientLog("Failed to get protocols from Hyprland");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool createShm(SWlState& state, Vector2D geom) {
|
||||||
|
if (!state.xrgb8888_support)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
size_t stride = geom.x * 4;
|
||||||
|
size_t size = geom.y * stride;
|
||||||
|
if (!state.shmPool) {
|
||||||
|
const char* name = "/wl-shm-pointer-scroll";
|
||||||
|
state.shmFd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
|
||||||
|
if (state.shmFd < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (shm_unlink(name) < 0 || ftruncate(state.shmFd, size) < 0) {
|
||||||
|
close(state.shmFd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.shmPool = makeShared<CCWlShmPool>(state.wlShm->sendCreatePool(state.shmFd, size));
|
||||||
|
if (!state.shmPool->resource()) {
|
||||||
|
close(state.shmFd);
|
||||||
|
state.shmFd = -1;
|
||||||
|
state.shmPool.reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
state.shmBufSize = size;
|
||||||
|
} else if (size > state.shmBufSize) {
|
||||||
|
if (ftruncate(state.shmFd, size) < 0) {
|
||||||
|
close(state.shmFd);
|
||||||
|
state.shmFd = -1;
|
||||||
|
state.shmPool.reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.shmPool->sendResize(size);
|
||||||
|
state.shmBufSize = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto buf = makeShared<CCWlBuffer>(state.shmPool->sendCreateBuffer(0, geom.x, geom.y, stride, WL_SHM_FORMAT_XRGB8888));
|
||||||
|
if (!buf->resource())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (state.shmBuf) {
|
||||||
|
state.shmBuf->sendDestroy();
|
||||||
|
state.shmBuf.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
state.shmBuf = buf;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool setupToplevel(SWlState& state) {
|
||||||
|
state.wlShm->setFormat([&](CCWlShm* p, uint32_t format) {
|
||||||
|
if (format == WL_SHM_FORMAT_XRGB8888)
|
||||||
|
state.xrgb8888_support = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
state.xdgShell->setPing([&](CCXdgWmBase* p, uint32_t serial) { state.xdgShell->sendPong(serial); });
|
||||||
|
|
||||||
|
state.surf = makeShared<CCWlSurface>(state.wlCompositor->sendCreateSurface());
|
||||||
|
if (!state.surf->resource())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
state.xdgSurf = makeShared<CCXdgSurface>(state.xdgShell->sendGetXdgSurface(state.surf->resource()));
|
||||||
|
if (!state.xdgSurf->resource())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
state.xdgToplevel = makeShared<CCXdgToplevel>(state.xdgSurf->sendGetToplevel());
|
||||||
|
if (!state.xdgToplevel->resource())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
state.xdgToplevel->setClose([&](CCXdgToplevel* p) { exit(0); });
|
||||||
|
|
||||||
|
state.xdgToplevel->setConfigure([&](CCXdgToplevel* p, int32_t w, int32_t h, wl_array* arr) {
|
||||||
|
state.geom = {1280, 720};
|
||||||
|
|
||||||
|
if (!createShm(state, state.geom))
|
||||||
|
exit(-1);
|
||||||
|
});
|
||||||
|
|
||||||
|
state.xdgSurf->setConfigure([&](CCXdgSurface* p, uint32_t serial) {
|
||||||
|
if (!state.shmBuf)
|
||||||
|
debugLog("xdgSurf configure but no buf made yet?");
|
||||||
|
|
||||||
|
state.xdgSurf->sendSetWindowGeometry(0, 0, state.geom.x, state.geom.y);
|
||||||
|
state.surf->sendAttach(state.shmBuf.get(), 0, 0);
|
||||||
|
state.surf->sendCommit();
|
||||||
|
|
||||||
|
state.xdgSurf->sendAckConfigure(serial);
|
||||||
|
|
||||||
|
if (!started) {
|
||||||
|
started = true;
|
||||||
|
clientLog("started");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
state.xdgToplevel->sendSetTitle("pointer-scroll test client");
|
||||||
|
state.xdgToplevel->sendSetAppId("pointer-scroll");
|
||||||
|
|
||||||
|
state.surf->sendAttach(nullptr, 0, 0);
|
||||||
|
state.surf->sendCommit();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool setupSeat(SWlState& state) {
|
||||||
|
state.pointer = makeShared<CCWlPointer>(state.wlSeat->sendGetPointer());
|
||||||
|
if (!state.pointer->resource())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
state.pointer->setEnter([&](CCWlPointer* p, uint32_t serial, wl_proxy* surf, wl_fixed_t x, wl_fixed_t y) {
|
||||||
|
debugLog("Got pointer enter event, serial {}, x {}, y {}", serial, x, y);
|
||||||
|
state.enterSerial = serial;
|
||||||
|
});
|
||||||
|
|
||||||
|
state.pointer->setAxis([&](CCWlPointer* p, uint32_t time, wl_pointer_axis axis, wl_fixed_t delta) {
|
||||||
|
debugLog("axis: ax {} delta {}", (int)axis, wl_fixed_to_double(delta));
|
||||||
|
|
||||||
|
if (state.writeDelta) {
|
||||||
|
clientLog("{:.2f}", wl_fixed_to_double(delta));
|
||||||
|
state.writeDelta = false;
|
||||||
|
state.lastScrollDelta = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.lastScrollDelta = wl_fixed_to_double(delta);
|
||||||
|
state.writeDelta = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
state.pointer->setLeave([&](CCWlPointer* p, uint32_t serial, wl_proxy* surf) { debugLog("Got pointer leave event, serial {}", serial); });
|
||||||
|
|
||||||
|
state.pointer->setMotion([&](CCWlPointer* p, uint32_t serial, wl_fixed_t x, wl_fixed_t y) { debugLog("Got pointer motion event, serial {}, x {}, y {}", serial, x, y); });
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return last delta after axis
|
||||||
|
static void parseRequest(SWlState& state, std::string req) {
|
||||||
|
if (!state.writeDelta) {
|
||||||
|
state.writeDelta = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clientLog("{:.2f}", state.lastScrollDelta);
|
||||||
|
state.writeDelta = false;
|
||||||
|
state.lastScrollDelta = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
logfile.open("pointer-scroll.txt", std::ios::trunc);
|
||||||
|
|
||||||
|
if (argc != 1 && argc != 2)
|
||||||
|
clientLog("Only the \"--debug\" switch is allowed, it turns on debug logs.");
|
||||||
|
|
||||||
|
if (argc == 2 && std::string{argv[1]} == "--debug")
|
||||||
|
debug = true;
|
||||||
|
|
||||||
|
SWlState state;
|
||||||
|
|
||||||
|
// WAYLAND_DISPLAY env should be set to the correct one
|
||||||
|
state.display = wl_display_connect(nullptr);
|
||||||
|
if (!state.display) {
|
||||||
|
clientLog("Failed to connect to wayland display");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bindRegistry(state) || !setupSeat(state) || !setupToplevel(state))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
std::array<char, 1024> readBuf;
|
||||||
|
readBuf.fill(0);
|
||||||
|
|
||||||
|
wl_display_flush(state.display);
|
||||||
|
|
||||||
|
struct pollfd fds[2] = {{.fd = wl_display_get_fd(state.display), .events = POLLIN | POLLOUT}, {.fd = STDIN_FILENO, .events = POLLIN}};
|
||||||
|
while (!shouldExit && poll(fds, 2, 0) != -1) {
|
||||||
|
if (fds[0].revents & POLLIN) {
|
||||||
|
wl_display_flush(state.display);
|
||||||
|
|
||||||
|
if (wl_display_prepare_read(state.display) == 0) {
|
||||||
|
wl_display_read_events(state.display);
|
||||||
|
wl_display_dispatch_pending(state.display);
|
||||||
|
} else
|
||||||
|
wl_display_dispatch(state.display);
|
||||||
|
|
||||||
|
int ret = 0;
|
||||||
|
do {
|
||||||
|
ret = wl_display_dispatch_pending(state.display);
|
||||||
|
wl_display_flush(state.display);
|
||||||
|
} while (ret > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fds[1].revents & POLLIN) {
|
||||||
|
ssize_t bytesRead = read(fds[1].fd, readBuf.data(), 1023);
|
||||||
|
if (bytesRead == -1)
|
||||||
|
continue;
|
||||||
|
readBuf[bytesRead] = 0;
|
||||||
|
|
||||||
|
parseRequest(state, std::string{readBuf.data()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wl_display* display = state.display;
|
||||||
|
state = {};
|
||||||
|
|
||||||
|
wl_display_disconnect(display);
|
||||||
|
logfile.flush();
|
||||||
|
logfile.close();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
317
hyprtester/clients/pointer-warp.cpp
Normal file
317
hyprtester/clients/pointer-warp.cpp
Normal file
|
|
@ -0,0 +1,317 @@
|
||||||
|
#include <cstring>
|
||||||
|
#include <sys/poll.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <print>
|
||||||
|
#include <format>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <wayland-client.h>
|
||||||
|
#include <wayland.hpp>
|
||||||
|
#include <xdg-shell.hpp>
|
||||||
|
#include <pointer-warp-v1.hpp>
|
||||||
|
|
||||||
|
#include <hyprutils/memory/SharedPtr.hpp>
|
||||||
|
#include <hyprutils/math/Vector2D.hpp>
|
||||||
|
#include <hyprutils/os/FileDescriptor.hpp>
|
||||||
|
|
||||||
|
using Hyprutils::Math::Vector2D;
|
||||||
|
using namespace Hyprutils::Memory;
|
||||||
|
|
||||||
|
struct SWlState {
|
||||||
|
wl_display* display;
|
||||||
|
CSharedPointer<CCWlRegistry> registry;
|
||||||
|
|
||||||
|
// protocols
|
||||||
|
CSharedPointer<CCWlCompositor> wlCompositor;
|
||||||
|
CSharedPointer<CCWlSeat> wlSeat;
|
||||||
|
CSharedPointer<CCWlShm> wlShm;
|
||||||
|
CSharedPointer<CCXdgWmBase> xdgShell;
|
||||||
|
CSharedPointer<CCWpPointerWarpV1> pointerWarp;
|
||||||
|
|
||||||
|
// shm/buffer stuff
|
||||||
|
CSharedPointer<CCWlShmPool> shmPool;
|
||||||
|
CSharedPointer<CCWlBuffer> shmBuf;
|
||||||
|
int shmFd;
|
||||||
|
size_t shmBufSize;
|
||||||
|
bool xrgb8888_support = false;
|
||||||
|
|
||||||
|
// surface/toplevel stuff
|
||||||
|
CSharedPointer<CCWlSurface> surf;
|
||||||
|
CSharedPointer<CCXdgSurface> xdgSurf;
|
||||||
|
CSharedPointer<CCXdgToplevel> xdgToplevel;
|
||||||
|
Vector2D geom;
|
||||||
|
|
||||||
|
// pointer
|
||||||
|
CSharedPointer<CCWlPointer> pointer;
|
||||||
|
uint32_t enterSerial;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool debug, started, shouldExit;
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
//NOLINTNEXTLINE
|
||||||
|
static void clientLog(std::format_string<Args...> fmt, Args&&... args) {
|
||||||
|
std::println("{}", std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||||
|
std::fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
//NOLINTNEXTLINE
|
||||||
|
static void debugLog(std::format_string<Args...> fmt, Args&&... args) {
|
||||||
|
if (!debug)
|
||||||
|
return;
|
||||||
|
std::println("{}", std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||||
|
std::fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool bindRegistry(SWlState& state) {
|
||||||
|
state.registry = makeShared<CCWlRegistry>((wl_proxy*)wl_display_get_registry(state.display));
|
||||||
|
|
||||||
|
state.registry->setGlobal([&](CCWlRegistry* r, uint32_t id, const char* name, uint32_t version) {
|
||||||
|
const std::string NAME = name;
|
||||||
|
if (NAME == "wl_compositor") {
|
||||||
|
debugLog(" > binding to global: {} (version {}) with id {}", name, version, id);
|
||||||
|
state.wlCompositor = makeShared<CCWlCompositor>((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wl_compositor_interface, 6));
|
||||||
|
} else if (NAME == "wl_shm") {
|
||||||
|
debugLog(" > binding to global: {} (version {}) with id {}", name, version, id);
|
||||||
|
state.wlShm = makeShared<CCWlShm>((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wl_shm_interface, 1));
|
||||||
|
} else if (NAME == "wl_seat") {
|
||||||
|
debugLog(" > binding to global: {} (version {}) with id {}", name, version, id);
|
||||||
|
state.wlSeat = makeShared<CCWlSeat>((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wl_seat_interface, 9));
|
||||||
|
} else if (NAME == "xdg_wm_base") {
|
||||||
|
debugLog(" > binding to global: {} (version {}) with id {}", name, version, id);
|
||||||
|
state.xdgShell = makeShared<CCXdgWmBase>((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &xdg_wm_base_interface, 1));
|
||||||
|
} else if (NAME == "wp_pointer_warp_v1") {
|
||||||
|
debugLog(" > binding to global: {} (version {}) with id {}", name, version, id);
|
||||||
|
state.pointerWarp = makeShared<CCWpPointerWarpV1>((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wp_pointer_warp_v1_interface, 1));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
state.registry->setGlobalRemove([](CCWlRegistry* r, uint32_t id) { debugLog("Global {} removed", id); });
|
||||||
|
|
||||||
|
wl_display_roundtrip(state.display);
|
||||||
|
|
||||||
|
if (!state.wlCompositor || !state.wlShm || !state.wlSeat || !state.xdgShell || !state.pointerWarp) {
|
||||||
|
clientLog("Failed to get protocols from Hyprland");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool createShm(SWlState& state, Vector2D geom) {
|
||||||
|
if (!state.xrgb8888_support)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
size_t stride = geom.x * 4;
|
||||||
|
size_t size = geom.y * stride;
|
||||||
|
if (!state.shmPool) {
|
||||||
|
const char* name = "/wl-shm-pointer-warp";
|
||||||
|
state.shmFd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
|
||||||
|
if (state.shmFd < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (shm_unlink(name) < 0 || ftruncate(state.shmFd, size) < 0) {
|
||||||
|
close(state.shmFd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.shmPool = makeShared<CCWlShmPool>(state.wlShm->sendCreatePool(state.shmFd, size));
|
||||||
|
if (!state.shmPool->resource()) {
|
||||||
|
close(state.shmFd);
|
||||||
|
state.shmFd = -1;
|
||||||
|
state.shmPool.reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
state.shmBufSize = size;
|
||||||
|
} else if (size > state.shmBufSize) {
|
||||||
|
if (ftruncate(state.shmFd, size) < 0) {
|
||||||
|
close(state.shmFd);
|
||||||
|
state.shmFd = -1;
|
||||||
|
state.shmPool.reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.shmPool->sendResize(size);
|
||||||
|
state.shmBufSize = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto buf = makeShared<CCWlBuffer>(state.shmPool->sendCreateBuffer(0, geom.x, geom.y, stride, WL_SHM_FORMAT_XRGB8888));
|
||||||
|
if (!buf->resource())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (state.shmBuf) {
|
||||||
|
state.shmBuf->sendDestroy();
|
||||||
|
state.shmBuf.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
state.shmBuf = buf;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool setupToplevel(SWlState& state) {
|
||||||
|
state.wlShm->setFormat([&](CCWlShm* p, uint32_t format) {
|
||||||
|
if (format == WL_SHM_FORMAT_XRGB8888)
|
||||||
|
state.xrgb8888_support = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
state.xdgShell->setPing([&](CCXdgWmBase* p, uint32_t serial) { state.xdgShell->sendPong(serial); });
|
||||||
|
|
||||||
|
state.surf = makeShared<CCWlSurface>(state.wlCompositor->sendCreateSurface());
|
||||||
|
if (!state.surf->resource())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
state.xdgSurf = makeShared<CCXdgSurface>(state.xdgShell->sendGetXdgSurface(state.surf->resource()));
|
||||||
|
if (!state.xdgSurf->resource())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
state.xdgToplevel = makeShared<CCXdgToplevel>(state.xdgSurf->sendGetToplevel());
|
||||||
|
if (!state.xdgToplevel->resource())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
state.xdgToplevel->setClose([&](CCXdgToplevel* p) { exit(0); });
|
||||||
|
|
||||||
|
state.xdgToplevel->setConfigure([&](CCXdgToplevel* p, int32_t w, int32_t h, wl_array* arr) {
|
||||||
|
state.geom = {1280, 720};
|
||||||
|
|
||||||
|
if (!createShm(state, state.geom))
|
||||||
|
exit(-1);
|
||||||
|
});
|
||||||
|
|
||||||
|
state.xdgSurf->setConfigure([&](CCXdgSurface* p, uint32_t serial) {
|
||||||
|
if (!state.shmBuf)
|
||||||
|
debugLog("xdgSurf configure but no buf made yet?");
|
||||||
|
|
||||||
|
state.xdgSurf->sendSetWindowGeometry(0, 0, state.geom.x, state.geom.y);
|
||||||
|
state.surf->sendAttach(state.shmBuf.get(), 0, 0);
|
||||||
|
state.surf->sendCommit();
|
||||||
|
|
||||||
|
state.xdgSurf->sendAckConfigure(serial);
|
||||||
|
|
||||||
|
if (!started) {
|
||||||
|
started = true;
|
||||||
|
clientLog("started");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
state.xdgToplevel->sendSetTitle("pointer-warp test client");
|
||||||
|
state.xdgToplevel->sendSetAppId("pointer-warp");
|
||||||
|
|
||||||
|
state.surf->sendAttach(nullptr, 0, 0);
|
||||||
|
state.surf->sendCommit();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool setupSeat(SWlState& state) {
|
||||||
|
state.pointer = makeShared<CCWlPointer>(state.wlSeat->sendGetPointer());
|
||||||
|
if (!state.pointer->resource())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
state.pointer->setEnter([&](CCWlPointer* p, uint32_t serial, wl_proxy* surf, wl_fixed_t x, wl_fixed_t y) {
|
||||||
|
debugLog("Got pointer enter event, serial {}, x {}, y {}", serial, x, y);
|
||||||
|
state.enterSerial = serial;
|
||||||
|
});
|
||||||
|
|
||||||
|
state.pointer->setLeave([&](CCWlPointer* p, uint32_t serial, wl_proxy* surf) { debugLog("Got pointer leave event, serial {}", serial); });
|
||||||
|
|
||||||
|
state.pointer->setMotion([&](CCWlPointer* p, uint32_t serial, wl_fixed_t x, wl_fixed_t y) { debugLog("Got pointer motion event, serial {}, x {}, y {}", serial, x, y); });
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// format is like below
|
||||||
|
// "warp 20 20\n" would ask to warp cursor to x=20,y=20 in surface local coords
|
||||||
|
static void parseRequest(SWlState& state, std::string req) {
|
||||||
|
if (req.contains("exit")) {
|
||||||
|
shouldExit = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!req.starts_with("warp "))
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto it = req.find_first_of('\n');
|
||||||
|
if (it == std::string::npos)
|
||||||
|
return;
|
||||||
|
|
||||||
|
req = req.substr(0, it);
|
||||||
|
|
||||||
|
it = req.find_first_of(' ');
|
||||||
|
if (it == std::string::npos)
|
||||||
|
return;
|
||||||
|
|
||||||
|
req = req.substr(it + 1);
|
||||||
|
|
||||||
|
it = req.find_first_of(' ');
|
||||||
|
|
||||||
|
int x = std::stoi(req.substr(0, it));
|
||||||
|
int y = std::stoi(req.substr(it + 1));
|
||||||
|
|
||||||
|
state.pointerWarp->sendWarpPointer(state.surf->resource(), state.pointer->resource(), wl_fixed_from_int(x), wl_fixed_from_int(y), state.enterSerial);
|
||||||
|
|
||||||
|
// sync the request then reply
|
||||||
|
wl_display_roundtrip(state.display);
|
||||||
|
|
||||||
|
clientLog("parsed request to move to x:{}, y:{}", x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
if (argc != 1 && argc != 2)
|
||||||
|
clientLog("Only the \"--debug\" switch is allowed, it turns on debug logs.");
|
||||||
|
|
||||||
|
if (argc == 2 && std::string{argv[1]} == "--debug")
|
||||||
|
debug = true;
|
||||||
|
|
||||||
|
SWlState state;
|
||||||
|
|
||||||
|
// WAYLAND_DISPLAY env should be set to the correct one
|
||||||
|
state.display = wl_display_connect(nullptr);
|
||||||
|
if (!state.display) {
|
||||||
|
clientLog("Failed to connect to wayland display");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bindRegistry(state) || !setupSeat(state) || !setupToplevel(state))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
std::array<char, 1024> readBuf;
|
||||||
|
readBuf.fill(0);
|
||||||
|
|
||||||
|
wl_display_flush(state.display);
|
||||||
|
|
||||||
|
struct pollfd fds[2] = {{.fd = wl_display_get_fd(state.display), .events = POLLIN | POLLOUT}, {.fd = STDIN_FILENO, .events = POLLIN}};
|
||||||
|
while (!shouldExit && poll(fds, 2, 0) != -1) {
|
||||||
|
if (fds[0].revents & POLLIN) {
|
||||||
|
wl_display_flush(state.display);
|
||||||
|
|
||||||
|
if (wl_display_prepare_read(state.display) == 0) {
|
||||||
|
wl_display_read_events(state.display);
|
||||||
|
wl_display_dispatch_pending(state.display);
|
||||||
|
} else
|
||||||
|
wl_display_dispatch(state.display);
|
||||||
|
|
||||||
|
int ret = 0;
|
||||||
|
do {
|
||||||
|
ret = wl_display_dispatch_pending(state.display);
|
||||||
|
wl_display_flush(state.display);
|
||||||
|
} while (ret > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fds[1].revents & POLLIN) {
|
||||||
|
ssize_t bytesRead = read(fds[1].fd, readBuf.data(), 1023);
|
||||||
|
if (bytesRead == -1)
|
||||||
|
continue;
|
||||||
|
readBuf[bytesRead] = 0;
|
||||||
|
|
||||||
|
parseRequest(state, std::string{readBuf.data()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wl_display* display = state.display;
|
||||||
|
state = {};
|
||||||
|
|
||||||
|
wl_display_disconnect(display);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
CXXFLAGS = -shared -fPIC --no-gnu-unique -g -std=c++2b -Wno-c++11-narrowing
|
CXXFLAGS = -shared -fPIC -g -std=c++2b -Wno-c++11-narrowing
|
||||||
INCLUDES = `pkg-config --cflags pixman-1 libdrm pangocairo libinput libudev wayland-server xkbcommon`
|
INCLUDES = `pkg-config --cflags pixman-1 libdrm pangocairo libinput libudev wayland-server xkbcommon`
|
||||||
LIBS = `pkg-config --libs pangocairo`
|
LIBS = `pkg-config --libs pangocairo`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,20 @@
|
||||||
#include <src/config/ConfigDescriptions.hpp>
|
#include <src/config/ConfigDescriptions.hpp>
|
||||||
#include <src/layout/IHyprLayout.hpp>
|
#include <src/layout/IHyprLayout.hpp>
|
||||||
#include <src/managers/LayoutManager.hpp>
|
#include <src/managers/LayoutManager.hpp>
|
||||||
|
#include <src/managers/input/InputManager.hpp>
|
||||||
|
#include <src/managers/PointerManager.hpp>
|
||||||
|
#include <src/managers/input/trackpad/TrackpadGestures.hpp>
|
||||||
|
#include <src/desktop/rule/windowRule/WindowRuleEffectContainer.hpp>
|
||||||
|
#include <src/desktop/rule/windowRule/WindowRuleApplicator.hpp>
|
||||||
#include <src/Compositor.hpp>
|
#include <src/Compositor.hpp>
|
||||||
|
#include <src/desktop/state/FocusState.hpp>
|
||||||
#undef private
|
#undef private
|
||||||
|
|
||||||
|
#include <hyprutils/utils/ScopeGuard.hpp>
|
||||||
|
#include <hyprutils/string/VarList.hpp>
|
||||||
|
using namespace Hyprutils::Utils;
|
||||||
|
using namespace Hyprutils::String;
|
||||||
|
|
||||||
#include "globals.hpp"
|
#include "globals.hpp"
|
||||||
|
|
||||||
// Do NOT change this function.
|
// Do NOT change this function.
|
||||||
|
|
@ -35,7 +46,7 @@ static SDispatchResult test(std::string in) {
|
||||||
|
|
||||||
// Trigger a snap move event for the active window
|
// Trigger a snap move event for the active window
|
||||||
static SDispatchResult snapMove(std::string in) {
|
static SDispatchResult snapMove(std::string in) {
|
||||||
const auto PLASTWINDOW = g_pCompositor->m_lastWindow.lock();
|
const auto PLASTWINDOW = Desktop::focusState()->window();
|
||||||
if (!PLASTWINDOW->m_isFloating)
|
if (!PLASTWINDOW->m_isFloating)
|
||||||
return {.success = false, .error = "Window must be floating"};
|
return {.success = false, .error = "Window must be floating"};
|
||||||
|
|
||||||
|
|
@ -48,15 +59,248 @@ static SDispatchResult snapMove(std::string in) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CTestKeyboard : public IKeyboard {
|
||||||
|
public:
|
||||||
|
static SP<CTestKeyboard> create(bool isVirtual) {
|
||||||
|
auto keeb = SP<CTestKeyboard>(new CTestKeyboard());
|
||||||
|
keeb->m_self = keeb;
|
||||||
|
keeb->m_isVirtual = isVirtual;
|
||||||
|
keeb->m_shareStates = !isVirtual;
|
||||||
|
return keeb;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool isVirtual() {
|
||||||
|
return m_isVirtual;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual SP<Aquamarine::IKeyboard> aq() {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendKey(uint32_t key, bool pressed) {
|
||||||
|
auto event = IKeyboard::SKeyEvent{
|
||||||
|
.timeMs = sc<uint32_t>(Time::millis(Time::steadyNow())),
|
||||||
|
.keycode = key,
|
||||||
|
.state = pressed ? WL_KEYBOARD_KEY_STATE_PRESSED : WL_KEYBOARD_KEY_STATE_RELEASED,
|
||||||
|
};
|
||||||
|
updatePressed(event.keycode, pressed);
|
||||||
|
m_keyboardEvents.key.emit(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy() {
|
||||||
|
m_events.destroy.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool m_isVirtual = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CTestMouse : public IPointer {
|
||||||
|
public:
|
||||||
|
static SP<CTestMouse> create(bool isVirtual) {
|
||||||
|
auto maus = SP<CTestMouse>(new CTestMouse());
|
||||||
|
maus->m_self = maus;
|
||||||
|
maus->m_isVirtual = isVirtual;
|
||||||
|
maus->m_deviceName = "test-mouse";
|
||||||
|
maus->m_hlName = "test-mouse";
|
||||||
|
return maus;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool isVirtual() {
|
||||||
|
return m_isVirtual;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual SP<Aquamarine::IPointer> aq() {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy() {
|
||||||
|
m_events.destroy.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool m_isVirtual = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
SP<CTestMouse> g_mouse;
|
||||||
|
SP<CTestKeyboard> g_keyboard;
|
||||||
|
|
||||||
|
static SDispatchResult pressAlt(std::string in) {
|
||||||
|
g_pInputManager->m_lastMods = in == "1" ? HL_MODIFIER_ALT : 0;
|
||||||
|
|
||||||
|
return {.success = true};
|
||||||
|
}
|
||||||
|
|
||||||
|
static SDispatchResult simulateGesture(std::string in) {
|
||||||
|
CVarList data(in);
|
||||||
|
|
||||||
|
uint32_t fingers = 3;
|
||||||
|
try {
|
||||||
|
fingers = std::stoul(data[1]);
|
||||||
|
} catch (...) { return {.success = false}; }
|
||||||
|
|
||||||
|
if (data[0] == "down") {
|
||||||
|
g_pTrackpadGestures->gestureBegin(IPointer::SSwipeBeginEvent{});
|
||||||
|
g_pTrackpadGestures->gestureUpdate(IPointer::SSwipeUpdateEvent{.fingers = fingers, .delta = {0, 300}});
|
||||||
|
g_pTrackpadGestures->gestureEnd(IPointer::SSwipeEndEvent{});
|
||||||
|
} else if (data[0] == "up") {
|
||||||
|
g_pTrackpadGestures->gestureBegin(IPointer::SSwipeBeginEvent{});
|
||||||
|
g_pTrackpadGestures->gestureUpdate(IPointer::SSwipeUpdateEvent{.fingers = fingers, .delta = {0, -300}});
|
||||||
|
g_pTrackpadGestures->gestureEnd(IPointer::SSwipeEndEvent{});
|
||||||
|
} else if (data[0] == "left") {
|
||||||
|
g_pTrackpadGestures->gestureBegin(IPointer::SSwipeBeginEvent{});
|
||||||
|
g_pTrackpadGestures->gestureUpdate(IPointer::SSwipeUpdateEvent{.fingers = fingers, .delta = {-300, 0}});
|
||||||
|
g_pTrackpadGestures->gestureEnd(IPointer::SSwipeEndEvent{});
|
||||||
|
} else {
|
||||||
|
g_pTrackpadGestures->gestureBegin(IPointer::SSwipeBeginEvent{});
|
||||||
|
g_pTrackpadGestures->gestureUpdate(IPointer::SSwipeUpdateEvent{.fingers = fingers, .delta = {300, 0}});
|
||||||
|
g_pTrackpadGestures->gestureEnd(IPointer::SSwipeEndEvent{});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {.success = true};
|
||||||
|
}
|
||||||
|
|
||||||
|
static SDispatchResult vkb(std::string in) {
|
||||||
|
auto tkb0 = CTestKeyboard::create(false);
|
||||||
|
auto tkb1 = CTestKeyboard::create(false);
|
||||||
|
auto vkb0 = CTestKeyboard::create(true);
|
||||||
|
|
||||||
|
g_pInputManager->newKeyboard(tkb0);
|
||||||
|
g_pInputManager->newKeyboard(tkb1);
|
||||||
|
g_pInputManager->newKeyboard(vkb0);
|
||||||
|
|
||||||
|
CScopeGuard x([&] {
|
||||||
|
tkb0->destroy();
|
||||||
|
tkb1->destroy();
|
||||||
|
vkb0->destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
const auto& PRESSED = g_pInputManager->getKeysFromAllKBs();
|
||||||
|
const uint32_t TESTKEY = 1;
|
||||||
|
|
||||||
|
tkb0->sendKey(TESTKEY, true);
|
||||||
|
if (!std::ranges::contains(PRESSED, TESTKEY)) {
|
||||||
|
return {
|
||||||
|
.success = false,
|
||||||
|
.error = "Expected pressed key not found",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
tkb1->sendKey(TESTKEY, true);
|
||||||
|
tkb0->sendKey(TESTKEY, false);
|
||||||
|
if (!std::ranges::contains(PRESSED, TESTKEY)) {
|
||||||
|
return {
|
||||||
|
.success = false,
|
||||||
|
.error = "Expected pressed key not found (kb share state)",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
vkb0->sendKey(TESTKEY, true);
|
||||||
|
tkb1->sendKey(TESTKEY, false);
|
||||||
|
if (std::ranges::contains(PRESSED, TESTKEY)) {
|
||||||
|
return {
|
||||||
|
.success = false,
|
||||||
|
.error = "Expected released key found in pressed (vkb no share state)",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
static SDispatchResult scroll(std::string in) {
|
||||||
|
double by;
|
||||||
|
try {
|
||||||
|
by = std::stod(in);
|
||||||
|
} catch (...) { return SDispatchResult{.success = false, .error = "invalid input"}; }
|
||||||
|
|
||||||
|
Log::logger->log(Log::DEBUG, "tester: scrolling by {}", by);
|
||||||
|
|
||||||
|
g_mouse->m_pointerEvents.axis.emit(IPointer::SAxisEvent{
|
||||||
|
.delta = by,
|
||||||
|
.deltaDiscrete = 120,
|
||||||
|
.mouse = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
static SDispatchResult keybind(std::string in) {
|
||||||
|
CVarList2 data(std::move(in));
|
||||||
|
// 0 = release, 1 = press
|
||||||
|
bool press;
|
||||||
|
// See src/devices/IKeyboard.hpp : eKeyboardModifiers for modifier bitmasks
|
||||||
|
// 0 = none, eKeyboardModifiers is shifted to start at 1
|
||||||
|
uint32_t modifier;
|
||||||
|
// keycode
|
||||||
|
uint32_t key;
|
||||||
|
try {
|
||||||
|
press = std::stoul(std::string{data[0]}) == 1;
|
||||||
|
modifier = std::stoul(std::string{data[1]});
|
||||||
|
key = std::stoul(std::string{data[2]}) - 8; // xkb offset
|
||||||
|
} catch (...) { return {.success = false, .error = "invalid input"}; }
|
||||||
|
|
||||||
|
uint32_t modifierMask = 0;
|
||||||
|
if (modifier > 0)
|
||||||
|
modifierMask = 1 << (modifier - 1);
|
||||||
|
g_pInputManager->m_lastMods = modifierMask;
|
||||||
|
g_keyboard->sendKey(key, press);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
static Desktop::Rule::CWindowRuleEffectContainer::storageType ruleIDX = 0;
|
||||||
|
|
||||||
|
//
|
||||||
|
static SDispatchResult addRule(std::string in) {
|
||||||
|
ruleIDX = Desktop::Rule::windowEffects()->registerEffect("plugin_rule");
|
||||||
|
|
||||||
|
if (Desktop::Rule::windowEffects()->registerEffect("plugin_rule") != ruleIDX)
|
||||||
|
return {.success = false, .error = "re-registering returned a different id?"};
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
static SDispatchResult checkRule(std::string in) {
|
||||||
|
const auto PLASTWINDOW = Desktop::focusState()->window();
|
||||||
|
|
||||||
|
if (!PLASTWINDOW)
|
||||||
|
return {.success = false, .error = "No window"};
|
||||||
|
|
||||||
|
if (!PLASTWINDOW->m_ruleApplicator->m_otherProps.props.contains(ruleIDX))
|
||||||
|
return {.success = false, .error = "No rule"};
|
||||||
|
|
||||||
|
if (PLASTWINDOW->m_ruleApplicator->m_otherProps.props[ruleIDX]->effect != "effect")
|
||||||
|
return {.success = false, .error = "Effect isn't \"effect\""};
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
|
APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
|
||||||
PHANDLE = handle;
|
PHANDLE = handle;
|
||||||
|
|
||||||
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:test", ::test);
|
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:test", ::test);
|
||||||
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:snapmove", ::snapMove);
|
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:snapmove", ::snapMove);
|
||||||
|
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:vkb", ::vkb);
|
||||||
|
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:alt", ::pressAlt);
|
||||||
|
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:gesture", ::simulateGesture);
|
||||||
|
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:scroll", ::scroll);
|
||||||
|
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:keybind", ::keybind);
|
||||||
|
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:add_rule", ::addRule);
|
||||||
|
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:check_rule", ::checkRule);
|
||||||
|
|
||||||
|
// init mouse
|
||||||
|
g_mouse = CTestMouse::create(false);
|
||||||
|
g_pInputManager->newMouse(g_mouse);
|
||||||
|
|
||||||
|
// init keyboard
|
||||||
|
g_keyboard = CTestKeyboard::create(false);
|
||||||
|
g_pInputManager->newKeyboard(g_keyboard);
|
||||||
|
|
||||||
return {"hyprtestplugin", "hyprtestplugin", "Vaxry", "1.0"};
|
return {"hyprtestplugin", "hyprtestplugin", "Vaxry", "1.0"};
|
||||||
}
|
}
|
||||||
|
|
||||||
APICALL EXPORT void PLUGIN_EXIT() {
|
APICALL EXPORT void PLUGIN_EXIT() {
|
||||||
;
|
g_mouse->destroy();
|
||||||
|
g_mouse.reset();
|
||||||
|
g_keyboard->destroy();
|
||||||
|
g_keyboard.reset();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
hyprtester/protocols/.gitignore
vendored
Normal file
2
hyprtester/protocols/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
*
|
||||||
|
!.gitignore
|
||||||
|
|
@ -14,6 +14,8 @@
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <print>
|
#include <print>
|
||||||
|
#include <hyprutils/memory/Casts.hpp>
|
||||||
|
using namespace Hyprutils::Memory;
|
||||||
|
|
||||||
static int getUID() {
|
static int getUID() {
|
||||||
const auto UID = getuid();
|
const auto UID = getuid();
|
||||||
|
|
@ -95,7 +97,7 @@ std::string getFromSocket(const std::string& cmd) {
|
||||||
|
|
||||||
strncpy(serverAddress.sun_path, socketPath.c_str(), sizeof(serverAddress.sun_path) - 1);
|
strncpy(serverAddress.sun_path, socketPath.c_str(), sizeof(serverAddress.sun_path) - 1);
|
||||||
|
|
||||||
if (connect(SERVERSOCKET, (sockaddr*)&serverAddress, SUN_LEN(&serverAddress)) < 0) {
|
if (connect(SERVERSOCKET, rc<sockaddr*>(&serverAddress), SUN_LEN(&serverAddress)) < 0) {
|
||||||
std::println("Couldn't connect to {}. (3)", socketPath);
|
std::println("Couldn't connect to {}. (3)", socketPath);
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,16 +17,22 @@
|
||||||
#include "shared.hpp"
|
#include "shared.hpp"
|
||||||
#include "hyprctlCompat.hpp"
|
#include "hyprctlCompat.hpp"
|
||||||
#include "tests/main/tests.hpp"
|
#include "tests/main/tests.hpp"
|
||||||
|
#include "tests/clients/tests.hpp"
|
||||||
#include "tests/plugin/plugin.hpp"
|
#include "tests/plugin/plugin.hpp"
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <hyprutils/os/Process.hpp>
|
#include <hyprutils/os/Process.hpp>
|
||||||
#include <hyprutils/memory/WeakPtr.hpp>
|
#include <hyprutils/memory/WeakPtr.hpp>
|
||||||
|
#include <hyprutils/memory/Casts.hpp>
|
||||||
|
using namespace Hyprutils::Memory;
|
||||||
|
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <print>
|
#include <print>
|
||||||
|
#include <string_view>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
#include "Log.hpp"
|
#include "Log.hpp"
|
||||||
|
|
||||||
|
|
@ -91,10 +97,13 @@ int main(int argc, char** argv, char** envp) {
|
||||||
std::string binaryPath = "";
|
std::string binaryPath = "";
|
||||||
std::string pluginPath = std::filesystem::current_path().string();
|
std::string pluginPath = std::filesystem::current_path().string();
|
||||||
|
|
||||||
std::vector<std::string> args{argv + 1, argv + argc};
|
if (argc > 1) {
|
||||||
|
std::span<char*> args{argv + 1, sc<std::size_t>(argc - 1)};
|
||||||
|
|
||||||
for (auto it = args.begin(); it != args.end(); it++) {
|
for (auto it = args.begin(); it != args.end(); it++) {
|
||||||
if (*it == "--config" || *it == "-c") {
|
std::string_view value = *it;
|
||||||
|
|
||||||
|
if (value == "--config" || value == "-c") {
|
||||||
if (std::next(it) == args.end()) {
|
if (std::next(it) == args.end()) {
|
||||||
help();
|
help();
|
||||||
|
|
||||||
|
|
@ -119,7 +128,7 @@ int main(int argc, char** argv, char** envp) {
|
||||||
it++;
|
it++;
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
} else if (*it == "--binary" || *it == "-b") {
|
} else if (value == "--binary" || value == "-b") {
|
||||||
if (std::next(it) == args.end()) {
|
if (std::next(it) == args.end()) {
|
||||||
help();
|
help();
|
||||||
|
|
||||||
|
|
@ -144,7 +153,7 @@ int main(int argc, char** argv, char** envp) {
|
||||||
it++;
|
it++;
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
} else if (*it == "--plugin" || *it == "-p") {
|
} else if (value == "--plugin" || value == "-p") {
|
||||||
if (std::next(it) == args.end()) {
|
if (std::next(it) == args.end()) {
|
||||||
help();
|
help();
|
||||||
|
|
||||||
|
|
@ -169,7 +178,7 @@ int main(int argc, char** argv, char** envp) {
|
||||||
it++;
|
it++;
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
} else if (*it == "--help" || *it == "-h") {
|
} else if (value == "--help" || value == "-h") {
|
||||||
help();
|
help();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -180,6 +189,7 @@ int main(int argc, char** argv, char** envp) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
NLog::log("{}launching hl", Colors::YELLOW);
|
NLog::log("{}launching hl", Colors::YELLOW);
|
||||||
if (!launchHyprland(configPath, binaryPath)) {
|
if (!launchHyprland(configPath, binaryPath)) {
|
||||||
|
|
@ -218,13 +228,24 @@ int main(int argc, char** argv, char** envp) {
|
||||||
|
|
||||||
NLog::log("{}Loaded plugin", Colors::YELLOW);
|
NLog::log("{}Loaded plugin", Colors::YELLOW);
|
||||||
|
|
||||||
|
NLog::log("{}Running main tests", Colors::YELLOW);
|
||||||
|
|
||||||
for (const auto& fn : testFns) {
|
for (const auto& fn : testFns) {
|
||||||
EXPECT(fn(), true);
|
EXPECT(fn(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NLog::log("{}Running protocol client tests", Colors::YELLOW);
|
||||||
|
|
||||||
|
for (const auto& fn : clientTestFns) {
|
||||||
|
EXPECT(fn(), true);
|
||||||
|
}
|
||||||
|
|
||||||
NLog::log("{}running plugin test", Colors::YELLOW);
|
NLog::log("{}running plugin test", Colors::YELLOW);
|
||||||
EXPECT(testPlugin(), true);
|
EXPECT(testPlugin(), true);
|
||||||
|
|
||||||
|
NLog::log("{}running vkb test from plugin", Colors::YELLOW);
|
||||||
|
EXPECT(testVkb(), true);
|
||||||
|
|
||||||
// kill hyprland
|
// kill hyprland
|
||||||
NLog::log("{}dispatching exit", Colors::YELLOW);
|
NLog::log("{}dispatching exit", Colors::YELLOW);
|
||||||
getFromSocket("/dispatch exit");
|
getFromSocket("/dispatch exit");
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,17 @@ namespace Colors {
|
||||||
constexpr const char* RESET = "\x1b[0m";
|
constexpr const char* RESET = "\x1b[0m";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define EXPECT_MAX_DELTA(expr, desired, delta) \
|
||||||
|
if (const auto RESULT = expr; std::abs(RESULT - (desired)) > delta) { \
|
||||||
|
NLog::log("{}Failed: {}{}, expected max delta of {}, got delta {} ({} - {}). Source: {}@{}.", Colors::RED, Colors::RESET, #expr, delta, (RESULT - (desired)), RESULT, \
|
||||||
|
desired, __FILE__, __LINE__); \
|
||||||
|
ret = 1; \
|
||||||
|
TESTS_FAILED++; \
|
||||||
|
} else { \
|
||||||
|
NLog::log("{}Passed: {}{}. Got {}", Colors::GREEN, Colors::RESET, #expr, (RESULT - (desired))); \
|
||||||
|
TESTS_PASSED++; \
|
||||||
|
}
|
||||||
|
|
||||||
#define EXPECT(expr, val) \
|
#define EXPECT(expr, val) \
|
||||||
if (const auto RESULT = expr; RESULT != (val)) { \
|
if (const auto RESULT = expr; RESULT != (val)) { \
|
||||||
NLog::log("{}Failed: {}{}, expected {}, got {}. Source: {}@{}.", Colors::RED, Colors::RESET, #expr, val, RESULT, __FILE__, __LINE__); \
|
NLog::log("{}Failed: {}{}, expected {}, got {}. Source: {}@{}.", Colors::RED, Colors::RESET, #expr, val, RESULT, __FILE__, __LINE__); \
|
||||||
|
|
@ -45,7 +56,7 @@ namespace Colors {
|
||||||
|
|
||||||
#define EXPECT_CONTAINS(haystack, needle) \
|
#define EXPECT_CONTAINS(haystack, needle) \
|
||||||
if (const auto EXPECTED = needle; !std::string{haystack}.contains(EXPECTED)) { \
|
if (const auto EXPECTED = needle; !std::string{haystack}.contains(EXPECTED)) { \
|
||||||
NLog::log("{}Failed: {}{} should contain {} but doesn't. Source: {}@{}. Haystack is:\n{}", Colors::RED, Colors::RESET, #haystack, EXPECTED, __FILE__, __LINE__, \
|
NLog::log("{}Failed: {}{} should contain {} but doesn't. Source: {}@{}. Haystack is:\n{}", Colors::RED, Colors::RESET, #haystack, #needle, __FILE__, __LINE__, \
|
||||||
std::string{haystack}); \
|
std::string{haystack}); \
|
||||||
ret = 1; \
|
ret = 1; \
|
||||||
TESTS_FAILED++; \
|
TESTS_FAILED++; \
|
||||||
|
|
|
||||||
1
hyprtester/src/tests/clients/.gitignore
vendored
Normal file
1
hyprtester/src/tests/clients/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
build.hpp
|
||||||
145
hyprtester/src/tests/clients/pointer-scroll.cpp
Normal file
145
hyprtester/src/tests/clients/pointer-scroll.cpp
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
#include "../../shared.hpp"
|
||||||
|
#include "../../hyprctlCompat.hpp"
|
||||||
|
#include "../shared.hpp"
|
||||||
|
#include "tests.hpp"
|
||||||
|
#include "build.hpp"
|
||||||
|
|
||||||
|
#include <hyprutils/os/FileDescriptor.hpp>
|
||||||
|
#include <hyprutils/os/Process.hpp>
|
||||||
|
|
||||||
|
#include <sys/poll.h>
|
||||||
|
#include <csignal>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
using namespace Hyprutils::OS;
|
||||||
|
using namespace Hyprutils::Memory;
|
||||||
|
|
||||||
|
#define SP CSharedPointer
|
||||||
|
|
||||||
|
struct SClient {
|
||||||
|
SP<CProcess> proc;
|
||||||
|
std::array<char, 1024> readBuf;
|
||||||
|
CFileDescriptor readFd, writeFd;
|
||||||
|
struct pollfd fds;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int ret = 0;
|
||||||
|
|
||||||
|
static bool startClient(SClient& client) {
|
||||||
|
client.proc = makeShared<CProcess>(binaryDir + "/pointer-scroll", std::vector<std::string>{});
|
||||||
|
|
||||||
|
client.proc->addEnv("WAYLAND_DISPLAY", WLDISPLAY);
|
||||||
|
|
||||||
|
int pipeFds1[2], pipeFds2[2];
|
||||||
|
if (pipe(pipeFds1) != 0 || pipe(pipeFds2) != 0) {
|
||||||
|
NLog::log("{}Unable to open pipe to client", Colors::RED);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.writeFd = CFileDescriptor(pipeFds1[1]);
|
||||||
|
client.proc->setStdinFD(pipeFds1[0]);
|
||||||
|
|
||||||
|
client.readFd = CFileDescriptor(pipeFds2[0]);
|
||||||
|
client.proc->setStdoutFD(pipeFds2[1]);
|
||||||
|
|
||||||
|
client.proc->runAsync();
|
||||||
|
|
||||||
|
close(pipeFds1[0]);
|
||||||
|
close(pipeFds2[1]);
|
||||||
|
|
||||||
|
client.fds = {.fd = client.readFd.get(), .events = POLLIN};
|
||||||
|
if (poll(&client.fds, 1, 1000) != 1 || !(client.fds.revents & POLLIN))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
client.readBuf.fill(0);
|
||||||
|
if (read(client.readFd.get(), client.readBuf.data(), client.readBuf.size() - 1) == -1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::string ret = std::string{client.readBuf.data()};
|
||||||
|
if (ret.find("started") == std::string::npos) {
|
||||||
|
NLog::log("{}Failed to start pointer-scroll client, read {}", Colors::RED, ret);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for window to appear
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
|
||||||
|
|
||||||
|
if (getFromSocket(std::format("/dispatch setprop pid:{} no_anim 1", client.proc->pid())) != "ok") {
|
||||||
|
NLog::log("{}Failed to disable animations for client window", Colors::RED, ret);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getFromSocket(std::format("/dispatch focuswindow pid:{}", client.proc->pid())) != "ok") {
|
||||||
|
NLog::log("{}Failed to focus pointer-scroll client", Colors::RED, ret);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
NLog::log("{}Started pointer-scroll client", Colors::YELLOW);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void stopClient(SClient& client) {
|
||||||
|
std::string cmd = "exit\n";
|
||||||
|
write(client.writeFd.get(), cmd.c_str(), cmd.length());
|
||||||
|
|
||||||
|
kill(client.proc->pid(), SIGKILL);
|
||||||
|
client.proc.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
static int getLastDelta(SClient& client) {
|
||||||
|
std::string cmd = "hypr";
|
||||||
|
if ((size_t)write(client.writeFd.get(), cmd.c_str(), cmd.length()) != cmd.length())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (poll(&client.fds, 1, 1500) != 1 || !(client.fds.revents & POLLIN))
|
||||||
|
return false;
|
||||||
|
ssize_t bytesRead = read(client.fds.fd, client.readBuf.data(), 1023);
|
||||||
|
if (bytesRead == -1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
client.readBuf[bytesRead] = 0;
|
||||||
|
std::string received = std::string{client.readBuf.data()};
|
||||||
|
received.pop_back();
|
||||||
|
|
||||||
|
try {
|
||||||
|
return std::stoi(received);
|
||||||
|
} catch (...) { return -1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool sendScroll(int delta) {
|
||||||
|
return getFromSocket(std::format("/dispatch plugin:test:scroll {}", delta)) == "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool test() {
|
||||||
|
SClient client;
|
||||||
|
|
||||||
|
if (!startClient(client))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
EXPECT(getFromSocket("/keyword input:emulate_discrete_scroll 0"), "ok");
|
||||||
|
|
||||||
|
EXPECT(sendScroll(10), true);
|
||||||
|
EXPECT(getLastDelta(client), 10);
|
||||||
|
|
||||||
|
EXPECT(getFromSocket("/keyword input:scroll_factor 2"), "ok");
|
||||||
|
EXPECT(sendScroll(10), true);
|
||||||
|
EXPECT(getLastDelta(client), 20);
|
||||||
|
|
||||||
|
EXPECT(getFromSocket("r/keyword device[test-mouse-1]:scroll_factor 3"), "ok");
|
||||||
|
EXPECT(sendScroll(10), true);
|
||||||
|
EXPECT(getLastDelta(client), 30);
|
||||||
|
|
||||||
|
EXPECT(getFromSocket("r/dispatch setprop active scroll_mouse 4"), "ok");
|
||||||
|
EXPECT(sendScroll(10), true);
|
||||||
|
EXPECT(getLastDelta(client), 40);
|
||||||
|
|
||||||
|
stopClient(client);
|
||||||
|
|
||||||
|
NLog::log("{}Reloading the config", Colors::YELLOW);
|
||||||
|
OK(getFromSocket("/reload"));
|
||||||
|
|
||||||
|
return !ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
REGISTER_CLIENT_TEST_FN(test);
|
||||||
183
hyprtester/src/tests/clients/pointer-warp.cpp
Normal file
183
hyprtester/src/tests/clients/pointer-warp.cpp
Normal file
|
|
@ -0,0 +1,183 @@
|
||||||
|
#include "../../shared.hpp"
|
||||||
|
#include "../../hyprctlCompat.hpp"
|
||||||
|
#include "../shared.hpp"
|
||||||
|
#include "tests.hpp"
|
||||||
|
#include "build.hpp"
|
||||||
|
|
||||||
|
#include <hyprutils/os/FileDescriptor.hpp>
|
||||||
|
#include <hyprutils/os/Process.hpp>
|
||||||
|
|
||||||
|
#include <sys/poll.h>
|
||||||
|
#include <csignal>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
using namespace Hyprutils::OS;
|
||||||
|
using namespace Hyprutils::Memory;
|
||||||
|
|
||||||
|
#define SP CSharedPointer
|
||||||
|
|
||||||
|
struct SClient {
|
||||||
|
SP<CProcess> proc;
|
||||||
|
std::array<char, 1024> readBuf;
|
||||||
|
CFileDescriptor readFd, writeFd;
|
||||||
|
struct pollfd fds;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int ret = 0;
|
||||||
|
|
||||||
|
static bool startClient(SClient& client) {
|
||||||
|
client.proc = makeShared<CProcess>(binaryDir + "/pointer-warp", std::vector<std::string>{});
|
||||||
|
|
||||||
|
client.proc->addEnv("WAYLAND_DISPLAY", WLDISPLAY);
|
||||||
|
|
||||||
|
int pipeFds1[2], pipeFds2[2];
|
||||||
|
if (pipe(pipeFds1) != 0 || pipe(pipeFds2) != 0) {
|
||||||
|
NLog::log("{}Unable to open pipe to client", Colors::RED);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.writeFd = CFileDescriptor(pipeFds1[1]);
|
||||||
|
client.proc->setStdinFD(pipeFds1[0]);
|
||||||
|
|
||||||
|
client.readFd = CFileDescriptor(pipeFds2[0]);
|
||||||
|
client.proc->setStdoutFD(pipeFds2[1]);
|
||||||
|
|
||||||
|
client.proc->runAsync();
|
||||||
|
|
||||||
|
close(pipeFds1[0]);
|
||||||
|
close(pipeFds2[1]);
|
||||||
|
|
||||||
|
client.fds = {.fd = client.readFd.get(), .events = POLLIN};
|
||||||
|
if (poll(&client.fds, 1, 1000) != 1 || !(client.fds.revents & POLLIN))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
client.readBuf.fill(0);
|
||||||
|
if (read(client.readFd.get(), client.readBuf.data(), client.readBuf.size() - 1) == -1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::string ret = std::string{client.readBuf.data()};
|
||||||
|
if (ret.find("started") == std::string::npos) {
|
||||||
|
NLog::log("{}Failed to start pointer-warp client, read {}", Colors::RED, ret);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for window to appear
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
|
||||||
|
|
||||||
|
if (getFromSocket(std::format("/dispatch setprop pid:{} no_anim 1", client.proc->pid())) != "ok") {
|
||||||
|
NLog::log("{}Failed to disable animations for client window", Colors::RED, ret);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getFromSocket(std::format("/dispatch focuswindow pid:{}", client.proc->pid())) != "ok") {
|
||||||
|
NLog::log("{}Failed to focus pointer-warp client", Colors::RED, ret);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
NLog::log("{}Started pointer-warp client", Colors::YELLOW);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void stopClient(SClient& client) {
|
||||||
|
std::string cmd = "exit\n";
|
||||||
|
write(client.writeFd.get(), cmd.c_str(), cmd.length());
|
||||||
|
|
||||||
|
kill(client.proc->pid(), SIGKILL);
|
||||||
|
client.proc.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// format is like below
|
||||||
|
// "warp 20 20\n" would ask to warp cursor to x=20,y=20 in surface local coords
|
||||||
|
static bool sendWarp(SClient& client, int x, int y) {
|
||||||
|
std::string cmd = std::format("warp {} {}\n", x, y);
|
||||||
|
if ((size_t)write(client.writeFd.get(), cmd.c_str(), cmd.length()) != cmd.length())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (poll(&client.fds, 1, 1500) != 1 || !(client.fds.revents & POLLIN))
|
||||||
|
return false;
|
||||||
|
ssize_t bytesRead = read(client.fds.fd, client.readBuf.data(), 1023);
|
||||||
|
if (bytesRead == -1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
client.readBuf[bytesRead] = 0;
|
||||||
|
std::string recieved = std::string{client.readBuf.data()};
|
||||||
|
recieved.pop_back();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isCursorPos(int x, int y) {
|
||||||
|
// TODO: add a better way to do this using test plugin?
|
||||||
|
std::string res = getFromSocket("/cursorpos");
|
||||||
|
if (res == "error") {
|
||||||
|
NLog::log("{}Cursorpos err'd: {}", Colors::RED, res);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = res.find_first_of(' ');
|
||||||
|
if (res.at(it - 1) != ',') {
|
||||||
|
NLog::log("{}Cursorpos err'd: {}", Colors::RED, res);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int cursorX = std::stoi(res.substr(0, it - 1));
|
||||||
|
int cursorY = std::stoi(res.substr(it + 1));
|
||||||
|
|
||||||
|
// somehow this is always gives 1 less than surfbox->pos()??
|
||||||
|
res = getFromSocket("/activewindow");
|
||||||
|
it = res.find("at: ") + 4;
|
||||||
|
res = res.substr(it, res.find_first_of('\n', it) - it);
|
||||||
|
|
||||||
|
it = res.find_first_of(',');
|
||||||
|
int clientX = cursorX - std::stoi(res.substr(0, it)) + 1;
|
||||||
|
int clientY = cursorY - std::stoi(res.substr(it + 1)) + 1;
|
||||||
|
|
||||||
|
return clientX == x && clientY == y;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool test() {
|
||||||
|
SClient client;
|
||||||
|
|
||||||
|
if (!startClient(client))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
EXPECT(sendWarp(client, 100, 100), true);
|
||||||
|
EXPECT(isCursorPos(100, 100), true);
|
||||||
|
|
||||||
|
EXPECT(sendWarp(client, 0, 0), true);
|
||||||
|
EXPECT(isCursorPos(0, 0), true);
|
||||||
|
|
||||||
|
EXPECT(sendWarp(client, 200, 200), true);
|
||||||
|
EXPECT(isCursorPos(200, 200), true);
|
||||||
|
|
||||||
|
EXPECT(sendWarp(client, 100, -100), true);
|
||||||
|
EXPECT(isCursorPos(200, 200), true);
|
||||||
|
|
||||||
|
EXPECT(sendWarp(client, 234, 345), true);
|
||||||
|
EXPECT(isCursorPos(234, 345), true);
|
||||||
|
|
||||||
|
EXPECT(sendWarp(client, -1, -1), true);
|
||||||
|
EXPECT(isCursorPos(234, 345), true);
|
||||||
|
|
||||||
|
EXPECT(sendWarp(client, 1, -1), true);
|
||||||
|
EXPECT(isCursorPos(234, 345), true);
|
||||||
|
|
||||||
|
EXPECT(sendWarp(client, 13, 37), true);
|
||||||
|
EXPECT(isCursorPos(13, 37), true);
|
||||||
|
|
||||||
|
EXPECT(sendWarp(client, -100, 100), true);
|
||||||
|
EXPECT(isCursorPos(13, 37), true);
|
||||||
|
|
||||||
|
EXPECT(sendWarp(client, -1, 1), true);
|
||||||
|
EXPECT(isCursorPos(13, 37), true);
|
||||||
|
|
||||||
|
stopClient(client);
|
||||||
|
|
||||||
|
NLog::log("{}Reloading the config", Colors::YELLOW);
|
||||||
|
OK(getFromSocket("/reload"));
|
||||||
|
|
||||||
|
return !ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
REGISTER_CLIENT_TEST_FN(test);
|
||||||
12
hyprtester/src/tests/clients/tests.hpp
Normal file
12
hyprtester/src/tests/clients/tests.hpp
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
inline std::vector<std::function<bool()>> clientTestFns;
|
||||||
|
|
||||||
|
#define REGISTER_CLIENT_TEST_FN(fn) \
|
||||||
|
static auto _register_fn = [] { \
|
||||||
|
clientTestFns.emplace_back(fn); \
|
||||||
|
return 1; \
|
||||||
|
}();
|
||||||
22
hyprtester/src/tests/main/animations.cpp
Normal file
22
hyprtester/src/tests/main/animations.cpp
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
#include "../../Log.hpp"
|
||||||
|
#include "tests.hpp"
|
||||||
|
#include "../../shared.hpp"
|
||||||
|
#include "../../hyprctlCompat.hpp"
|
||||||
|
#include <hyprutils/os/Process.hpp>
|
||||||
|
#include <hyprutils/memory/WeakPtr.hpp>
|
||||||
|
|
||||||
|
static int ret = 0;
|
||||||
|
|
||||||
|
using namespace Hyprutils::OS;
|
||||||
|
using namespace Hyprutils::Memory;
|
||||||
|
|
||||||
|
static bool test() {
|
||||||
|
NLog::log("{}Testing animations", Colors::GREEN);
|
||||||
|
|
||||||
|
auto str = getFromSocket("/animations");
|
||||||
|
NLog::log("{}Testing bezier curve output from `hyprctl animations`", Colors::YELLOW);
|
||||||
|
{EXPECT_CONTAINS(str, std::format("beziers:\n\n\tname: quick\n\t\tX0: 0.15\n\t\tY0: 0.00\n\t\tX1: 0.10\n\t\tY1: 1.00"))};
|
||||||
|
return !ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
REGISTER_TEST_FN(test)
|
||||||
27
hyprtester/src/tests/main/colors.cpp
Normal file
27
hyprtester/src/tests/main/colors.cpp
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
#include "tests.hpp"
|
||||||
|
#include "../../shared.hpp"
|
||||||
|
#include "../../hyprctlCompat.hpp"
|
||||||
|
#include "../shared.hpp"
|
||||||
|
|
||||||
|
static int ret = 0;
|
||||||
|
|
||||||
|
static bool test() {
|
||||||
|
NLog::log("{}Testing hyprctl monitors", Colors::GREEN);
|
||||||
|
|
||||||
|
std::string monitorsSpec = getFromSocket("j/monitors");
|
||||||
|
EXPECT_CONTAINS(monitorsSpec, R"("colorManagementPreset": "srgb")");
|
||||||
|
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/keyword monitor HEADLESS-2,1920x1080x60.00000,0x0,1.0,bitdepth,10,cm,wide"), "ok")
|
||||||
|
monitorsSpec = getFromSocket("j/monitors");
|
||||||
|
EXPECT_CONTAINS(monitorsSpec, R"("colorManagementPreset": "wide")");
|
||||||
|
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/keyword monitor HEADLESS-2,1920x1080x60.00000,0x0,1.0,bitdepth,10,cm,srgb,sdrbrightness,1.2,sdrsaturation,0.98"), "ok")
|
||||||
|
monitorsSpec = getFromSocket("j/monitors");
|
||||||
|
EXPECT_CONTAINS(monitorsSpec, R"("colorManagementPreset": "srgb")");
|
||||||
|
EXPECT_CONTAINS(monitorsSpec, R"("sdrBrightness": 1.20)");
|
||||||
|
EXPECT_CONTAINS(monitorsSpec, R"("sdrSaturation": 0.98)");
|
||||||
|
|
||||||
|
return !ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
REGISTER_TEST_FN(test)
|
||||||
54
hyprtester/src/tests/main/dwindle.cpp
Normal file
54
hyprtester/src/tests/main/dwindle.cpp
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
#include "../shared.hpp"
|
||||||
|
#include "../../shared.hpp"
|
||||||
|
#include "../../hyprctlCompat.hpp"
|
||||||
|
#include "tests.hpp"
|
||||||
|
|
||||||
|
static int ret = 0;
|
||||||
|
|
||||||
|
static void testFloatClamp() {
|
||||||
|
for (auto const& win : {"a", "b", "c"}) {
|
||||||
|
if (!Tests::spawnKitty(win)) {
|
||||||
|
NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win);
|
||||||
|
++TESTS_FAILED;
|
||||||
|
ret = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/keyword dwindle:force_split 2"));
|
||||||
|
OK(getFromSocket("/keyword monitor HEADLESS-2, addreserved, 0, 20, 0, 20"));
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:c"));
|
||||||
|
OK(getFromSocket("/dispatch setfloating class:c"));
|
||||||
|
OK(getFromSocket("/dispatch resizewindowpixel exact 1200 900,class:c"));
|
||||||
|
OK(getFromSocket("/dispatch settiled class:c"));
|
||||||
|
OK(getFromSocket("/dispatch setfloating class:c"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/clients");
|
||||||
|
EXPECT_CONTAINS(str, "at: 698,158");
|
||||||
|
EXPECT_CONTAINS(str, "size: 1200,900");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/keyword dwindle:force_split 0"));
|
||||||
|
|
||||||
|
// clean up
|
||||||
|
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||||
|
Tests::killAllWindows();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool test() {
|
||||||
|
NLog::log("{}Testing Dwindle layout", Colors::GREEN);
|
||||||
|
|
||||||
|
// test
|
||||||
|
NLog::log("{}Testing float clamp", Colors::GREEN);
|
||||||
|
testFloatClamp();
|
||||||
|
|
||||||
|
// clean up
|
||||||
|
NLog::log("Cleaning up", Colors::YELLOW);
|
||||||
|
getFromSocket("/dispatch workspace 1");
|
||||||
|
OK(getFromSocket("/reload"));
|
||||||
|
|
||||||
|
return !ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
REGISTER_TEST_FN(test);
|
||||||
54
hyprtester/src/tests/main/exec.cpp
Normal file
54
hyprtester/src/tests/main/exec.cpp
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
#include "tests.hpp"
|
||||||
|
#include "../../shared.hpp"
|
||||||
|
#include "../../hyprctlCompat.hpp"
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
#include <hyprutils/os/Process.hpp>
|
||||||
|
#include <hyprutils/memory/WeakPtr.hpp>
|
||||||
|
#include "../shared.hpp"
|
||||||
|
|
||||||
|
static int ret = 0;
|
||||||
|
|
||||||
|
using namespace Hyprutils::OS;
|
||||||
|
using namespace Hyprutils::Memory;
|
||||||
|
|
||||||
|
#define UP CUniquePointer
|
||||||
|
#define SP CSharedPointer
|
||||||
|
|
||||||
|
static bool test() {
|
||||||
|
NLog::log("{}Testing process spawning", Colors::GREEN);
|
||||||
|
|
||||||
|
// Note: POSIX sleep does not support fractional seconds, so
|
||||||
|
// can't sleep for less than 1 second.
|
||||||
|
OK(getFromSocket("/dispatch exec sleep 1"));
|
||||||
|
|
||||||
|
// Ensure that sleep is our child
|
||||||
|
const std::string sleepPidS = Tests::execAndGet("pgrep sleep");
|
||||||
|
pid_t sleepPid;
|
||||||
|
try {
|
||||||
|
sleepPid = std::stoull(sleepPidS);
|
||||||
|
} catch (...) {
|
||||||
|
NLog::log("{}Sleep was not spawned or several sleeps are running: pgrep returned '{}'", Colors::RED, sleepPidS);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string sleepParentComm = Tests::execAndGet("cat \"/proc/$(ps -o ppid:1= -p " + sleepPidS + ")/comm\"");
|
||||||
|
NLog::log("{}Expecting that sleep's parent is Hyprland", Colors::YELLOW);
|
||||||
|
EXPECT_CONTAINS(sleepParentComm, "Hyprland");
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||||
|
|
||||||
|
// Ensure that sleep did not become a zombie
|
||||||
|
EXPECT(Tests::processAlive(sleepPid), false);
|
||||||
|
|
||||||
|
// kill all
|
||||||
|
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||||
|
Tests::killAllWindows();
|
||||||
|
|
||||||
|
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
|
||||||
|
EXPECT(Tests::windowCount(), 0);
|
||||||
|
|
||||||
|
return !ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
REGISTER_TEST_FN(test)
|
||||||
203
hyprtester/src/tests/main/gestures.cpp
Normal file
203
hyprtester/src/tests/main/gestures.cpp
Normal file
|
|
@ -0,0 +1,203 @@
|
||||||
|
#include "tests.hpp"
|
||||||
|
#include "../../shared.hpp"
|
||||||
|
#include "../../hyprctlCompat.hpp"
|
||||||
|
#include <print>
|
||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
#include <hyprutils/os/Process.hpp>
|
||||||
|
#include <hyprutils/memory/WeakPtr.hpp>
|
||||||
|
#include <csignal>
|
||||||
|
#include <cerrno>
|
||||||
|
#include "../shared.hpp"
|
||||||
|
|
||||||
|
static int ret = 0;
|
||||||
|
|
||||||
|
using namespace Hyprutils::OS;
|
||||||
|
using namespace Hyprutils::Memory;
|
||||||
|
|
||||||
|
#define UP CUniquePointer
|
||||||
|
#define SP CSharedPointer
|
||||||
|
|
||||||
|
static bool waitForWindowCount(int expectedWindowCnt, std::string_view expectation, int waitMillis = 100, int maxWaitCnt = 50) {
|
||||||
|
int counter = 0;
|
||||||
|
while (Tests::windowCount() != expectedWindowCnt) {
|
||||||
|
counter++;
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(waitMillis));
|
||||||
|
|
||||||
|
if (counter > maxWaitCnt) {
|
||||||
|
NLog::log("{}Unmet expectation: {}", Colors::RED, expectation);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool test() {
|
||||||
|
NLog::log("{}Testing gestures", Colors::GREEN);
|
||||||
|
|
||||||
|
EXPECT(Tests::windowCount(), 0);
|
||||||
|
|
||||||
|
// test on workspace "window"
|
||||||
|
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
|
||||||
|
getFromSocket("/dispatch workspace 1"); // no OK: we might be on 1 already
|
||||||
|
|
||||||
|
Tests::spawnKitty();
|
||||||
|
EXPECT(Tests::windowCount(), 1);
|
||||||
|
|
||||||
|
// Give the shell a moment to initialize
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:gesture up,5"));
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:gesture down,5"));
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:gesture left,5"));
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:gesture right,5"));
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:gesture right,4"));
|
||||||
|
|
||||||
|
EXPECT(waitForWindowCount(0, "Gesture sent paste exit + enter to kitty"), true);
|
||||||
|
|
||||||
|
EXPECT(Tests::windowCount(), 0);
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:gesture left,3"));
|
||||||
|
|
||||||
|
EXPECT(waitForWindowCount(1, "Gesture spawned kitty"), true);
|
||||||
|
|
||||||
|
EXPECT(Tests::windowCount(), 1);
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:gesture right,3"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/clients");
|
||||||
|
EXPECT_CONTAINS(str, "floating: 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:gesture down,3"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/clients");
|
||||||
|
EXPECT_CONTAINS(str, "fullscreen: 2");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:gesture down,3"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/clients");
|
||||||
|
EXPECT_CONTAINS(str, "fullscreen: 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:alt 1"));
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:gesture left,3"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/workspaces");
|
||||||
|
EXPECT_CONTAINS(str, "ID 2 (2)");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:gesture right,3"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/workspaces");
|
||||||
|
EXPECT_NOT_CONTAINS(str, "ID 2 (2)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for crashes
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:gesture right,3"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/workspaces");
|
||||||
|
EXPECT_NOT_CONTAINS(str, "ID 2 (2)");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/keyword gestures:workspace_swipe_invert 0"));
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:gesture right,3"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/workspaces");
|
||||||
|
EXPECT_CONTAINS(str, "ID 2 (2)");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:gesture left,3"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/workspaces");
|
||||||
|
EXPECT_NOT_CONTAINS(str, "ID 2 (2)");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/keyword gestures:workspace_swipe_invert 1"));
|
||||||
|
OK(getFromSocket("/keyword gestures:workspace_swipe_create_new 0"));
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:gesture left,3"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/workspaces");
|
||||||
|
EXPECT_NOT_CONTAINS(str, "ID 2 (2)");
|
||||||
|
EXPECT_CONTAINS(str, "ID 1 (1)");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:gesture down,3"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/clients");
|
||||||
|
EXPECT_CONTAINS(str, "floating: 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:alt 0"));
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:gesture up,3"));
|
||||||
|
|
||||||
|
EXPECT(waitForWindowCount(0, "Gesture closed kitty"), true);
|
||||||
|
|
||||||
|
EXPECT(Tests::windowCount(), 0);
|
||||||
|
|
||||||
|
// This test ensures that `movecursortocorner`, which expects
|
||||||
|
// a single-character direction argument, is parsed correctly.
|
||||||
|
Tests::spawnKitty();
|
||||||
|
OK(getFromSocket("/dispatch movecursortocorner 0"));
|
||||||
|
const std::string cursorPos1 = getFromSocket("/cursorpos");
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:gesture left,4"));
|
||||||
|
const std::string cursorPos2 = getFromSocket("/cursorpos");
|
||||||
|
// The cursor should have moved because of the gesture
|
||||||
|
EXPECT(cursorPos1 != cursorPos2, true);
|
||||||
|
|
||||||
|
// Test that `workspace previous` works correctly after a workspace gesture.
|
||||||
|
{
|
||||||
|
OK(getFromSocket("/keyword gestures:workspace_swipe_invert 0"));
|
||||||
|
OK(getFromSocket("/keyword gestures:workspace_swipe_create_new 1"));
|
||||||
|
OK(getFromSocket("/dispatch workspace 3"));
|
||||||
|
|
||||||
|
// Come to workspace 5 from workspace 3: 5 will remember that.
|
||||||
|
OK(getFromSocket("/dispatch workspace 5"));
|
||||||
|
Tests::spawnKitty(); // Keep workspace 5 open
|
||||||
|
|
||||||
|
// Swipe from 1 to 5: 5 shall remember that.
|
||||||
|
OK(getFromSocket("/dispatch workspace 1"));
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:alt 1"));
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:gesture right,3"));
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:alt 0"));
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/activeworkspace"), "ID 5 (5)");
|
||||||
|
|
||||||
|
// Must return to 1 rather than 3
|
||||||
|
OK(getFromSocket("/dispatch workspace previous"));
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/activeworkspace"), "ID 1 (1)");
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch workspace previous"));
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/activeworkspace"), "ID 5 (5)");
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch workspace 1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// kill all
|
||||||
|
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||||
|
Tests::killAllWindows();
|
||||||
|
|
||||||
|
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
|
||||||
|
EXPECT(Tests::windowCount(), 0);
|
||||||
|
|
||||||
|
// reload cfg
|
||||||
|
OK(getFromSocket("/reload"));
|
||||||
|
|
||||||
|
return !ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
REGISTER_TEST_FN(test)
|
||||||
184
hyprtester/src/tests/main/hyprctl.cpp
Normal file
184
hyprtester/src/tests/main/hyprctl.cpp
Normal file
|
|
@ -0,0 +1,184 @@
|
||||||
|
#include "tests.hpp"
|
||||||
|
#include "../../shared.hpp"
|
||||||
|
#include "../../hyprctlCompat.hpp"
|
||||||
|
#include <cstdint>
|
||||||
|
#include <print>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
#include <hyprutils/os/Process.hpp>
|
||||||
|
#include <hyprutils/memory/WeakPtr.hpp>
|
||||||
|
#include <csignal>
|
||||||
|
#include <cerrno>
|
||||||
|
#include "../shared.hpp"
|
||||||
|
|
||||||
|
static int ret = 0;
|
||||||
|
|
||||||
|
using namespace Hyprutils::OS;
|
||||||
|
using namespace Hyprutils::Memory;
|
||||||
|
|
||||||
|
#define UP CUniquePointer
|
||||||
|
#define SP CSharedPointer
|
||||||
|
|
||||||
|
static std::string getCommandStdOut(std::string command) {
|
||||||
|
CProcess process("bash", {"-c", command});
|
||||||
|
process.addEnv("HYPRLAND_INSTANCE_SIGNATURE", HIS);
|
||||||
|
process.runSync();
|
||||||
|
|
||||||
|
const std::string& stdOut = process.stdOut();
|
||||||
|
|
||||||
|
// Remove trailing new line
|
||||||
|
return stdOut.substr(0, stdOut.length() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool testDevicesActiveLayoutIndex() {
|
||||||
|
NLog::log("{}Testing hyprctl devices active_layout_index", Colors::GREEN);
|
||||||
|
|
||||||
|
// configure layouts
|
||||||
|
getFromSocket("/keyword input:kb_layout us,pl,ua");
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < 3; i++) {
|
||||||
|
// set layout
|
||||||
|
getFromSocket("/switchxkblayout all " + std::to_string(i));
|
||||||
|
std::string devicesJson = getFromSocket("j/devices");
|
||||||
|
std::string expected = R"("active_layout_index": )" + std::to_string(i);
|
||||||
|
// check layout index
|
||||||
|
EXPECT_CONTAINS(devicesJson, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool testGetprop() {
|
||||||
|
NLog::log("{}Testing hyprctl getprop", Colors::GREEN);
|
||||||
|
if (!Tests::spawnKitty()) {
|
||||||
|
NLog::log("{}Error: kitty did not spawn", Colors::RED);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// animation
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty animation"), "(unset)");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty animation -j"), R"({"animation": ""})");
|
||||||
|
getFromSocket("/dispatch setprop class:kitty animation teststyle");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty animation"), "teststyle");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty animation -j"), R"({"animation": "teststyle"})");
|
||||||
|
|
||||||
|
// max_size
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size"), "inf inf");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size -j"), R"({"max_size": [null,null]})");
|
||||||
|
getFromSocket("/dispatch setprop class:kitty max_size 200 150");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size"), "200 150");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size -j"), R"({"max_size": [200,150]})");
|
||||||
|
|
||||||
|
// min_size
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size"), "20 20");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size -j"), R"({"min_size": [20,20]})");
|
||||||
|
getFromSocket("/dispatch setprop class:kitty min_size 100 50");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size"), "100 50");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size -j"), R"({"min_size": [100,50]})");
|
||||||
|
|
||||||
|
// opacity
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity"), "1");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity -j"), R"({"opacity": 1})");
|
||||||
|
getFromSocket("/dispatch setprop class:kitty opacity 0.3");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity"), "0.3");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity -j"), R"({"opacity": 0.3})");
|
||||||
|
|
||||||
|
// opacity_inactive
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive"), "1");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive -j"), R"({"opacity_inactive": 1})");
|
||||||
|
getFromSocket("/dispatch setprop class:kitty opacity_inactive 0.5");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive"), "0.5");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive -j"), R"({"opacity_inactive": 0.5})");
|
||||||
|
|
||||||
|
// opacity_fullscreen
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen"), "1");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen -j"), R"({"opacity_fullscreen": 1})");
|
||||||
|
getFromSocket("/dispatch setprop class:kitty opacity_fullscreen 0.75");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen"), "0.75");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen -j"), R"({"opacity_fullscreen": 0.75})");
|
||||||
|
|
||||||
|
// opacity_override
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_override"), "false");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_override -j"), R"({"opacity_override": false})");
|
||||||
|
getFromSocket("/dispatch setprop class:kitty opacity_override true");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_override"), "true");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_override -j"), R"({"opacity_override": true})");
|
||||||
|
|
||||||
|
// opacity_inactive_override
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive_override"), "false");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive_override -j"), R"({"opacity_inactive_override": false})");
|
||||||
|
getFromSocket("/dispatch setprop class:kitty opacity_inactive_override true");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive_override"), "true");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive_override -j"), R"({"opacity_inactive_override": true})");
|
||||||
|
|
||||||
|
// opacity_fullscreen_override
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen_override"), "false");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen_override -j"), R"({"opacity_fullscreen_override": false})");
|
||||||
|
getFromSocket("/dispatch setprop class:kitty opacity_fullscreen_override true");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen_override"), "true");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen_override -j"), R"({"opacity_fullscreen_override": true})");
|
||||||
|
|
||||||
|
// active_border_color
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty active_border_color"), "ee33ccff ee00ff99 45deg");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty active_border_color -j"), R"({"active_border_color": "ee33ccff ee00ff99 45deg"})");
|
||||||
|
getFromSocket("/dispatch setprop class:kitty active_border_color rgb(abcdef)");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty active_border_color"), "ffabcdef 0deg");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty active_border_color -j"), R"({"active_border_color": "ffabcdef 0deg"})");
|
||||||
|
|
||||||
|
// bool window properties
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty allows_input"), "false");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty allows_input -j"), R"({"allows_input": false})");
|
||||||
|
getFromSocket("/dispatch setprop class:kitty allows_input true");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty allows_input"), "true");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty allows_input -j"), R"({"allows_input": true})");
|
||||||
|
|
||||||
|
// int window properties
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding"), "10");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding -j"), R"({"rounding": 10})");
|
||||||
|
getFromSocket("/dispatch setprop class:kitty rounding 4");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding"), "4");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding -j"), R"({"rounding": 4})");
|
||||||
|
|
||||||
|
// float window properties
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding_power"), "2");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding_power -j"), R"({"rounding_power": 2})");
|
||||||
|
getFromSocket("/dispatch setprop class:kitty rounding_power 1.25");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding_power"), "1.25");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding_power -j"), R"({"rounding_power": 1.25})");
|
||||||
|
|
||||||
|
// errors
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop"), "not enough args");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty"), "not enough args");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:nonexistantclass animation"), "window not found");
|
||||||
|
EXPECT(getCommandStdOut("hyprctl getprop class:kitty nonexistantprop"), "prop not found");
|
||||||
|
|
||||||
|
// kill all
|
||||||
|
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||||
|
Tests::killAllWindows();
|
||||||
|
|
||||||
|
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
|
||||||
|
EXPECT(Tests::windowCount(), 0);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool test() {
|
||||||
|
NLog::log("{}Testing hyprctl", Colors::GREEN);
|
||||||
|
|
||||||
|
{
|
||||||
|
NLog::log("{}Testing hyprctl descriptions for any json errors", Colors::GREEN);
|
||||||
|
CProcess jqProc("bash", {"-c", "hyprctl descriptions | jq"});
|
||||||
|
jqProc.addEnv("HYPRLAND_INSTANCE_SIGNATURE", HIS);
|
||||||
|
jqProc.runSync();
|
||||||
|
EXPECT(jqProc.exitCode(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
testGetprop();
|
||||||
|
testDevicesActiveLayoutIndex();
|
||||||
|
getFromSocket("/reload");
|
||||||
|
|
||||||
|
return !ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
REGISTER_TEST_FN(test);
|
||||||
515
hyprtester/src/tests/main/keybinds.cpp
Normal file
515
hyprtester/src/tests/main/keybinds.cpp
Normal file
|
|
@ -0,0 +1,515 @@
|
||||||
|
#include <filesystem>
|
||||||
|
#include <linux/input-event-codes.h>
|
||||||
|
#include <thread>
|
||||||
|
#include "../../shared.hpp"
|
||||||
|
#include "../../hyprctlCompat.hpp"
|
||||||
|
#include "../shared.hpp"
|
||||||
|
#include "tests.hpp"
|
||||||
|
|
||||||
|
using namespace Hyprutils::OS;
|
||||||
|
using namespace Hyprutils::Memory;
|
||||||
|
|
||||||
|
static int ret = 0;
|
||||||
|
static std::string flagFile = "/tmp/hyprtester-keybinds.txt";
|
||||||
|
|
||||||
|
// Because i don't feel like changing someone elses code.
|
||||||
|
enum eKeyboardModifierIndex : uint8_t {
|
||||||
|
MOD_SHIFT = 1,
|
||||||
|
MOD_CAPS,
|
||||||
|
MOD_CTRL,
|
||||||
|
MOD_ALT,
|
||||||
|
MOD_MOD2,
|
||||||
|
MOD_MOD3,
|
||||||
|
MOD_META,
|
||||||
|
MOD_MOD5
|
||||||
|
};
|
||||||
|
|
||||||
|
static void clearFlag() {
|
||||||
|
std::filesystem::remove(flagFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool checkFlag() {
|
||||||
|
bool exists = std::filesystem::exists(flagFile);
|
||||||
|
clearFlag();
|
||||||
|
return exists;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool attemptCheckFlag(int attempts, int intervalMs) {
|
||||||
|
for (int i = 0; i < attempts; i++) {
|
||||||
|
if (checkFlag())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(intervalMs));
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string readKittyOutput() {
|
||||||
|
std::string output = Tests::execAndGet("kitten @ --to unix:/tmp/hyprtester-kitty.sock get-text --extent all");
|
||||||
|
// chop off shell prompt
|
||||||
|
std::size_t pos = output.rfind("$");
|
||||||
|
if (pos != std::string::npos) {
|
||||||
|
pos += 1;
|
||||||
|
if (pos < output.size())
|
||||||
|
output.erase(0, pos);
|
||||||
|
}
|
||||||
|
// NLog::log("Kitty output: '{}'", output);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void awaitKittyPrompt() {
|
||||||
|
// wait until we see the shell prompt, meaning it's ready for test inputs
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
std::string output = Tests::execAndGet("kitten @ --to unix:/tmp/hyprtester-kitty.sock get-text --extent all");
|
||||||
|
if (output.rfind("$") == std::string::npos) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
NLog::log("{}Error: timed out waiting for kitty prompt", Colors::RED);
|
||||||
|
}
|
||||||
|
|
||||||
|
static CUniquePointer<CProcess> spawnRemoteControlKitty() {
|
||||||
|
auto kittyProc = Tests::spawnKitty("keybinds_test", {"-o", "allow_remote_control=yes", "--listen-on", "unix:/tmp/hyprtester-kitty.sock", "--config", "NONE", "/bin/sh"});
|
||||||
|
// wait a bit to ensure shell prompt is sent, we are going to read the text after it
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
if (kittyProc)
|
||||||
|
awaitKittyPrompt();
|
||||||
|
return kittyProc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testBind() {
|
||||||
|
EXPECT(checkFlag(), false);
|
||||||
|
EXPECT(getFromSocket("/keyword bind SUPER,Y,exec,touch " + flagFile), "ok");
|
||||||
|
// press keybind
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
|
||||||
|
// await flag
|
||||||
|
EXPECT(attemptCheckFlag(20, 50), true);
|
||||||
|
// release keybind
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
|
||||||
|
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testBindKey() {
|
||||||
|
EXPECT(checkFlag(), false);
|
||||||
|
EXPECT(getFromSocket("/keyword bind ,Y,exec,touch " + flagFile), "ok");
|
||||||
|
// press keybind
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 1,0,29"));
|
||||||
|
// await flag
|
||||||
|
EXPECT(attemptCheckFlag(20, 50), true);
|
||||||
|
// release keybind
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
|
||||||
|
EXPECT(getFromSocket("/keyword unbind ,Y"), "ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testLongPress() {
|
||||||
|
EXPECT(checkFlag(), false);
|
||||||
|
EXPECT(getFromSocket("/keyword bindo SUPER,Y,exec,touch " + flagFile), "ok");
|
||||||
|
EXPECT(getFromSocket("/keyword input:repeat_delay 100"), "ok");
|
||||||
|
// press keybind
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
|
||||||
|
// check no flag on short press
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
|
EXPECT(checkFlag(), false);
|
||||||
|
// await repeat delay
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
EXPECT(checkFlag(), true);
|
||||||
|
// release keybind
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
|
||||||
|
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testKeyLongPress() {
|
||||||
|
EXPECT(checkFlag(), false);
|
||||||
|
EXPECT(getFromSocket("/keyword bindo ,Y,exec,touch " + flagFile), "ok");
|
||||||
|
EXPECT(getFromSocket("/keyword input:repeat_delay 100"), "ok");
|
||||||
|
// press keybind
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 1,0,29"));
|
||||||
|
// check no flag on short press
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
|
EXPECT(checkFlag(), false);
|
||||||
|
// await repeat delay
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
EXPECT(checkFlag(), true);
|
||||||
|
// release keybind
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
|
||||||
|
EXPECT(getFromSocket("/keyword unbind ,Y"), "ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testLongPressRelease() {
|
||||||
|
EXPECT(checkFlag(), false);
|
||||||
|
EXPECT(getFromSocket("/keyword bindo SUPER,Y,exec,touch " + flagFile), "ok");
|
||||||
|
EXPECT(getFromSocket("/keyword input:repeat_delay 100"), "ok");
|
||||||
|
// press keybind
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
|
||||||
|
// check no flag on short press
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
|
EXPECT(checkFlag(), false);
|
||||||
|
// release keybind
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
|
||||||
|
// await repeat delay
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
EXPECT(checkFlag(), false);
|
||||||
|
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testLongPressOnlyKeyRelease() {
|
||||||
|
EXPECT(checkFlag(), false);
|
||||||
|
EXPECT(getFromSocket("/keyword bindo SUPER,Y,exec,touch " + flagFile), "ok");
|
||||||
|
EXPECT(getFromSocket("/keyword input:repeat_delay 100"), "ok");
|
||||||
|
// press keybind
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
|
||||||
|
// check no flag on short press
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
|
EXPECT(checkFlag(), false);
|
||||||
|
// release key, keep modifier
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 0,7,29"));
|
||||||
|
// await repeat delay
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
EXPECT(checkFlag(), false);
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
|
||||||
|
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testRepeat() {
|
||||||
|
EXPECT(checkFlag(), false);
|
||||||
|
EXPECT(getFromSocket("/keyword binde SUPER,Y,exec,touch " + flagFile), "ok");
|
||||||
|
EXPECT(getFromSocket("/keyword input:repeat_delay 100"), "ok");
|
||||||
|
// press keybind
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
|
||||||
|
// await flag
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
EXPECT(checkFlag(), true);
|
||||||
|
// await repeat delay
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
EXPECT(checkFlag(), true);
|
||||||
|
// check that it continues repeating
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
EXPECT(checkFlag(), true);
|
||||||
|
// release keybind
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
|
||||||
|
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testKeyRepeat() {
|
||||||
|
EXPECT(checkFlag(), false);
|
||||||
|
EXPECT(getFromSocket("/keyword binde ,Y,exec,touch " + flagFile), "ok");
|
||||||
|
EXPECT(getFromSocket("/keyword input:repeat_delay 100"), "ok");
|
||||||
|
// press keybind
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 1,0,29"));
|
||||||
|
// await flag
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
|
EXPECT(checkFlag(), true);
|
||||||
|
// await repeat delay
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
EXPECT(checkFlag(), true);
|
||||||
|
// check that it continues repeating
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
EXPECT(checkFlag(), true);
|
||||||
|
// release keybind
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
|
||||||
|
EXPECT(getFromSocket("/keyword unbind ,Y"), "ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testRepeatRelease() {
|
||||||
|
EXPECT(checkFlag(), false);
|
||||||
|
EXPECT(getFromSocket("/keyword binde SUPER,Y,exec,touch " + flagFile), "ok");
|
||||||
|
EXPECT(getFromSocket("/keyword input:repeat_delay 100"), "ok");
|
||||||
|
// press keybind
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
|
||||||
|
// await flag
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
|
EXPECT(checkFlag(), true);
|
||||||
|
// release keybind
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
|
||||||
|
// await repeat delay
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
clearFlag();
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
EXPECT(checkFlag(), false);
|
||||||
|
// check that it is not repeating
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
EXPECT(checkFlag(), false);
|
||||||
|
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testRepeatOnlyKeyRelease() {
|
||||||
|
EXPECT(checkFlag(), false);
|
||||||
|
EXPECT(getFromSocket("/keyword binde SUPER,Y,exec,touch " + flagFile), "ok");
|
||||||
|
EXPECT(getFromSocket("/keyword input:repeat_delay 100"), "ok");
|
||||||
|
// press keybind
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
|
||||||
|
// await flag
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
EXPECT(checkFlag(), true);
|
||||||
|
// release key, keep modifier
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 0,7,29"));
|
||||||
|
// await repeat delay
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
clearFlag();
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
EXPECT(checkFlag(), false);
|
||||||
|
// check that it is not repeating
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
EXPECT(checkFlag(), false);
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
|
||||||
|
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testShortcutBind() {
|
||||||
|
auto kittyProc = spawnRemoteControlKitty();
|
||||||
|
if (!kittyProc) {
|
||||||
|
NLog::log("{}Error: kitty did not spawn", Colors::RED);
|
||||||
|
ret = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
EXPECT(getFromSocket("/dispatch focuswindow class:keybinds_test"), "ok");
|
||||||
|
EXPECT(getFromSocket("/keyword bind SUPER,Y,sendshortcut,,q,"), "ok");
|
||||||
|
// press keybind
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
|
||||||
|
// release keybind
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
|
const std::string output = readKittyOutput();
|
||||||
|
EXPECT_COUNT_STRING(output, "y", 0);
|
||||||
|
EXPECT_COUNT_STRING(output, "q", 1);
|
||||||
|
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
|
||||||
|
Tests::killAllWindows();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testShortcutBindKey() {
|
||||||
|
auto kittyProc = spawnRemoteControlKitty();
|
||||||
|
if (!kittyProc) {
|
||||||
|
NLog::log("{}Error: kitty did not spawn", Colors::RED);
|
||||||
|
ret = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
EXPECT(getFromSocket("/dispatch focuswindow class:keybinds_test"), "ok");
|
||||||
|
EXPECT(getFromSocket("/keyword bind ,Y,sendshortcut,,q,"), "ok");
|
||||||
|
// press keybind
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 1,0,29"));
|
||||||
|
// release keybind
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
|
const std::string output = readKittyOutput();
|
||||||
|
EXPECT_COUNT_STRING(output, "y", 0);
|
||||||
|
// disabled: doesn't work in CI
|
||||||
|
// EXPECT_COUNT_STRING(output, "q", 1);
|
||||||
|
EXPECT(getFromSocket("/keyword unbind ,Y"), "ok");
|
||||||
|
Tests::killAllWindows();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testShortcutLongPress() {
|
||||||
|
auto kittyProc = spawnRemoteControlKitty();
|
||||||
|
if (!kittyProc) {
|
||||||
|
NLog::log("{}Error: kitty did not spawn", Colors::RED);
|
||||||
|
ret = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
EXPECT(getFromSocket("/dispatch focuswindow class:keybinds_test"), "ok");
|
||||||
|
EXPECT(getFromSocket("/keyword bindo SUPER,Y,sendshortcut,,q,"), "ok");
|
||||||
|
EXPECT(getFromSocket("/keyword input:repeat_delay 100"), "ok");
|
||||||
|
EXPECT(getFromSocket("/keyword input:repeat_rate 10"), "ok");
|
||||||
|
// press keybind
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
|
||||||
|
// await repeat delay
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
const std::string output = readKittyOutput();
|
||||||
|
int yCount = Tests::countOccurrences(output, "y");
|
||||||
|
// sometimes 1, sometimes 2, not sure why
|
||||||
|
// keybind press sends 1 y immediately
|
||||||
|
// then repeat triggers, sending 1 y
|
||||||
|
// final release stop repeats, and shouldn't send any more
|
||||||
|
EXPECT(true, yCount == 1 || yCount == 2);
|
||||||
|
EXPECT_COUNT_STRING(output, "q", 1);
|
||||||
|
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
|
||||||
|
Tests::killAllWindows();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testShortcutLongPressKeyRelease() {
|
||||||
|
auto kittyProc = spawnRemoteControlKitty();
|
||||||
|
if (!kittyProc) {
|
||||||
|
NLog::log("{}Error: kitty did not spawn", Colors::RED);
|
||||||
|
ret = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
EXPECT(getFromSocket("/dispatch focuswindow class:keybinds_test"), "ok");
|
||||||
|
EXPECT(getFromSocket("/keyword bindo SUPER,Y,sendshortcut,,q,"), "ok");
|
||||||
|
EXPECT(getFromSocket("/keyword input:repeat_delay 100"), "ok");
|
||||||
|
EXPECT(getFromSocket("/keyword input:repeat_rate 10"), "ok");
|
||||||
|
// press keybind
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
// release key, keep modifier
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 0,7,29"));
|
||||||
|
// await repeat delay
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
const std::string output = readKittyOutput();
|
||||||
|
// disabled: doesn't work on CI
|
||||||
|
// EXPECT_COUNT_STRING(output, "y", 1);
|
||||||
|
EXPECT_COUNT_STRING(output, "q", 0);
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
|
||||||
|
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
|
||||||
|
Tests::killAllWindows();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testShortcutRepeat() {
|
||||||
|
auto kittyProc = spawnRemoteControlKitty();
|
||||||
|
if (!kittyProc) {
|
||||||
|
NLog::log("{}Error: kitty did not spawn", Colors::RED);
|
||||||
|
ret = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
EXPECT(getFromSocket("/dispatch focuswindow class:keybinds_test"), "ok");
|
||||||
|
EXPECT(getFromSocket("/keyword binde SUPER,Y,sendshortcut,,q,"), "ok");
|
||||||
|
EXPECT(getFromSocket("/keyword input:repeat_rate 5"), "ok");
|
||||||
|
EXPECT(getFromSocket("/keyword input:repeat_delay 200"), "ok");
|
||||||
|
// press keybind
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
|
||||||
|
// await repeat
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(210));
|
||||||
|
// release keybind
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(450));
|
||||||
|
const std::string output = readKittyOutput();
|
||||||
|
EXPECT_COUNT_STRING(output, "y", 0);
|
||||||
|
int qCount = Tests::countOccurrences(output, "q");
|
||||||
|
// sometimes 2, sometimes 3, not sure why
|
||||||
|
// keybind press sends 1 q immediately
|
||||||
|
// then repeat triggers, sending 1 q
|
||||||
|
// final release stop repeats, and shouldn't send any more
|
||||||
|
EXPECT(true, qCount == 2 || qCount == 3);
|
||||||
|
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
|
||||||
|
Tests::killAllWindows();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testShortcutRepeatKeyRelease() {
|
||||||
|
auto kittyProc = spawnRemoteControlKitty();
|
||||||
|
if (!kittyProc) {
|
||||||
|
NLog::log("{}Error: kitty did not spawn", Colors::RED);
|
||||||
|
ret = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
EXPECT(getFromSocket("/dispatch focuswindow class:keybinds_test"), "ok");
|
||||||
|
EXPECT(getFromSocket("/keyword binde SUPER,Y,sendshortcut,,q,"), "ok");
|
||||||
|
EXPECT(getFromSocket("/keyword input:repeat_rate 5"), "ok");
|
||||||
|
EXPECT(getFromSocket("/keyword input:repeat_delay 200"), "ok");
|
||||||
|
// press keybind
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(210));
|
||||||
|
// release key, keep modifier
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 0,7,29"));
|
||||||
|
// if repeat was still active, we'd get 2 more q's here
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(450));
|
||||||
|
// release modifier
|
||||||
|
const std::string output = readKittyOutput();
|
||||||
|
EXPECT_COUNT_STRING(output, "y", 0);
|
||||||
|
int qCount = Tests::countOccurrences(output, "q");
|
||||||
|
// sometimes 2, sometimes 3, not sure why
|
||||||
|
// keybind press sends 1 q immediately
|
||||||
|
// then repeat triggers, sending 1 q
|
||||||
|
// final release stop repeats, and shouldn't send any more
|
||||||
|
EXPECT(true, qCount == 2 || qCount == 3);
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
|
||||||
|
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
|
||||||
|
Tests::killAllWindows();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testSubmap() {
|
||||||
|
const auto press = [](const uint32_t key, const uint32_t mod = 0) {
|
||||||
|
// +8 because udev -> XKB keycode.
|
||||||
|
getFromSocket("/dispatch plugin:test:keybind 1," + std::to_string(mod) + "," + std::to_string(key + 8));
|
||||||
|
getFromSocket("/dispatch plugin:test:keybind 0," + std::to_string(mod) + "," + std::to_string(key + 8));
|
||||||
|
};
|
||||||
|
|
||||||
|
NLog::log("{}Testing submaps", Colors::GREEN);
|
||||||
|
// submap 1 no resets
|
||||||
|
press(KEY_U, MOD_META);
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/submap"), "submap1");
|
||||||
|
press(KEY_O);
|
||||||
|
Tests::waitUntilWindowsN(1);
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/submap"), "submap1");
|
||||||
|
// submap 2 resets to submap 1
|
||||||
|
press(KEY_U);
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/submap"), "submap2");
|
||||||
|
press(KEY_O);
|
||||||
|
Tests::waitUntilWindowsN(2);
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/submap"), "submap1");
|
||||||
|
// submap 3 resets to default
|
||||||
|
press(KEY_I);
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/submap"), "submap3");
|
||||||
|
press(KEY_O);
|
||||||
|
Tests::waitUntilWindowsN(3);
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/submap"), "default");
|
||||||
|
// submap 1 reset via keybind
|
||||||
|
press(KEY_U, MOD_META);
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/submap"), "submap1");
|
||||||
|
press(KEY_P);
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/submap"), "default");
|
||||||
|
|
||||||
|
Tests::killAllWindows();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testSubmapUniversal() {
|
||||||
|
NLog::log("{}Testing submap universal", Colors::GREEN);
|
||||||
|
|
||||||
|
EXPECT(checkFlag(), false);
|
||||||
|
EXPECT(getFromSocket("/keyword bindu SUPER,Y,exec,touch " + flagFile), "ok");
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/submap"), "default");
|
||||||
|
|
||||||
|
// keybind works on default submap
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 0,7,29"));
|
||||||
|
EXPECT(attemptCheckFlag(30, 5), true);
|
||||||
|
|
||||||
|
// keybind works on submap1
|
||||||
|
getFromSocket("/dispatch plugin:test:keybind 1,7,30");
|
||||||
|
getFromSocket("/dispatch plugin:test:keybind 0,7,30");
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/submap"), "submap1");
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:keybind 0,7,29"));
|
||||||
|
EXPECT(attemptCheckFlag(30, 5), true);
|
||||||
|
|
||||||
|
// reset to default submap
|
||||||
|
getFromSocket("/dispatch plugin:test:keybind 1,0,33");
|
||||||
|
getFromSocket("/dispatch plugin:test:keybind 0,0,33");
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/submap"), "default");
|
||||||
|
|
||||||
|
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool test() {
|
||||||
|
NLog::log("{}Testing keybinds", Colors::GREEN);
|
||||||
|
|
||||||
|
clearFlag();
|
||||||
|
|
||||||
|
testBind();
|
||||||
|
testBindKey();
|
||||||
|
testLongPress();
|
||||||
|
testKeyLongPress();
|
||||||
|
testLongPressRelease();
|
||||||
|
testLongPressOnlyKeyRelease();
|
||||||
|
testRepeat();
|
||||||
|
testKeyRepeat();
|
||||||
|
testRepeatRelease();
|
||||||
|
testRepeatOnlyKeyRelease();
|
||||||
|
testShortcutBind();
|
||||||
|
testShortcutBindKey();
|
||||||
|
testShortcutLongPress();
|
||||||
|
testShortcutLongPressKeyRelease();
|
||||||
|
testShortcutRepeat();
|
||||||
|
testShortcutRepeatKeyRelease();
|
||||||
|
testSubmap();
|
||||||
|
testSubmapUniversal();
|
||||||
|
|
||||||
|
clearFlag();
|
||||||
|
return !ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
REGISTER_TEST_FN(test)
|
||||||
71
hyprtester/src/tests/main/master.cpp
Normal file
71
hyprtester/src/tests/main/master.cpp
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
#include "../shared.hpp"
|
||||||
|
#include "../../shared.hpp"
|
||||||
|
#include "../../hyprctlCompat.hpp"
|
||||||
|
#include "tests.hpp"
|
||||||
|
|
||||||
|
static int ret = 0;
|
||||||
|
|
||||||
|
static void focusMasterPrevious() {
|
||||||
|
// setup
|
||||||
|
NLog::log("{}Spawning 1 master and 3 slave windows", Colors::YELLOW);
|
||||||
|
// order of windows set according to new_status = master (set in test.conf)
|
||||||
|
for (auto const& win : {"slave1", "slave2", "slave3", "master"}) {
|
||||||
|
if (!Tests::spawnKitty(win)) {
|
||||||
|
NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win);
|
||||||
|
++TESTS_FAILED;
|
||||||
|
ret = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NLog::log("{}Ensuring focus is on master before testing", Colors::YELLOW);
|
||||||
|
OK(getFromSocket("/dispatch layoutmsg focusmaster master"));
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: master");
|
||||||
|
|
||||||
|
// test
|
||||||
|
NLog::log("{}Testing fallback to focusmaster auto", Colors::YELLOW);
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch layoutmsg focusmaster previous"));
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: slave1");
|
||||||
|
|
||||||
|
NLog::log("{}Testing focusing from slave to master", Colors::YELLOW);
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch layoutmsg cyclenext noloop"));
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: slave2");
|
||||||
|
OK(getFromSocket("/dispatch layoutmsg focusmaster previous"));
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: master");
|
||||||
|
|
||||||
|
NLog::log("{}Testing focusing on previous window", Colors::YELLOW);
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch layoutmsg focusmaster previous"));
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: slave2");
|
||||||
|
|
||||||
|
NLog::log("{}Testing focusing back to master", Colors::YELLOW);
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch layoutmsg focusmaster previous"));
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: master");
|
||||||
|
|
||||||
|
// clean up
|
||||||
|
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||||
|
Tests::killAllWindows();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool test() {
|
||||||
|
NLog::log("{}Testing Master layout", Colors::GREEN);
|
||||||
|
|
||||||
|
// setup
|
||||||
|
OK(getFromSocket("/dispatch workspace name:master"));
|
||||||
|
OK(getFromSocket("/keyword general:layout master"));
|
||||||
|
|
||||||
|
// test
|
||||||
|
NLog::log("{}Testing `focusmaster previous` layoutmsg", Colors::GREEN);
|
||||||
|
focusMasterPrevious();
|
||||||
|
|
||||||
|
// clean up
|
||||||
|
NLog::log("Cleaning up", Colors::YELLOW);
|
||||||
|
OK(getFromSocket("/dispatch workspace 1"));
|
||||||
|
OK(getFromSocket("/reload"));
|
||||||
|
|
||||||
|
return !ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
REGISTER_TEST_FN(test);
|
||||||
|
|
@ -53,7 +53,7 @@ static bool test() {
|
||||||
|
|
||||||
NLog::log("{}Testing new_window_takes_over_fullscreen", Colors::YELLOW);
|
NLog::log("{}Testing new_window_takes_over_fullscreen", Colors::YELLOW);
|
||||||
|
|
||||||
OK(getFromSocket("/keyword misc:new_window_takes_over_fullscreen 0"));
|
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 0"));
|
||||||
|
|
||||||
Tests::spawnKitty("kitty_A");
|
Tests::spawnKitty("kitty_A");
|
||||||
|
|
||||||
|
|
@ -73,7 +73,16 @@ static bool test() {
|
||||||
EXPECT_CONTAINS(str, "kitty_A");
|
EXPECT_CONTAINS(str, "kitty_A");
|
||||||
}
|
}
|
||||||
|
|
||||||
OK(getFromSocket("/keyword misc:new_window_takes_over_fullscreen 1"));
|
OK(getFromSocket("/dispatch focuswindow class:kitty_B"));
|
||||||
|
|
||||||
|
{
|
||||||
|
// should be ignored as per focus_under_fullscreen 0
|
||||||
|
auto str = getFromSocket("/activewindow");
|
||||||
|
EXPECT_CONTAINS(str, "fullscreen: 2");
|
||||||
|
EXPECT_CONTAINS(str, "kitty_A");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 1"));
|
||||||
|
|
||||||
Tests::spawnKitty("kitty_C");
|
Tests::spawnKitty("kitty_C");
|
||||||
|
|
||||||
|
|
@ -83,7 +92,7 @@ static bool test() {
|
||||||
EXPECT_CONTAINS(str, "kitty_C");
|
EXPECT_CONTAINS(str, "kitty_C");
|
||||||
}
|
}
|
||||||
|
|
||||||
OK(getFromSocket("/keyword misc:new_window_takes_over_fullscreen 2"));
|
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 2"));
|
||||||
|
|
||||||
Tests::spawnKitty("kitty_D");
|
Tests::spawnKitty("kitty_D");
|
||||||
|
|
||||||
|
|
@ -93,7 +102,7 @@ static bool test() {
|
||||||
EXPECT_CONTAINS(str, "kitty_D");
|
EXPECT_CONTAINS(str, "kitty_D");
|
||||||
}
|
}
|
||||||
|
|
||||||
OK(getFromSocket("/keyword misc:new_window_takes_over_fullscreen 0"));
|
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 0"));
|
||||||
|
|
||||||
Tests::killAllWindows();
|
Tests::killAllWindows();
|
||||||
|
|
||||||
|
|
@ -131,6 +140,74 @@ static bool test() {
|
||||||
EXPECT_CONTAINS(str, "fullscreen: 2");
|
EXPECT_CONTAINS(str, "fullscreen: 2");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Tests::killAllWindows();
|
||||||
|
|
||||||
|
NLog::log("{}Testing fullscreen and fullscreenstate dispatcher", Colors::YELLOW);
|
||||||
|
|
||||||
|
Tests::spawnKitty("kitty_A");
|
||||||
|
Tests::spawnKitty("kitty_B");
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
|
||||||
|
OK(getFromSocket("/dispatch fullscreen 0 set"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/activewindow");
|
||||||
|
EXPECT_CONTAINS(str, "fullscreen: 2");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch fullscreen 0 unset"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/activewindow");
|
||||||
|
EXPECT_CONTAINS(str, "fullscreen: 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch fullscreen 1 toggle"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/activewindow");
|
||||||
|
EXPECT_CONTAINS(str, "fullscreen: 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch fullscreen 1 toggle"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/activewindow");
|
||||||
|
EXPECT_CONTAINS(str, "fullscreen: 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch fullscreenstate 2 2 set"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/activewindow");
|
||||||
|
EXPECT_CONTAINS(str, "fullscreen: 2");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch fullscreenstate 2 2 set"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/activewindow");
|
||||||
|
EXPECT_CONTAINS(str, "fullscreen: 2");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch fullscreenstate 2 2 toggle"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/activewindow");
|
||||||
|
EXPECT_CONTAINS(str, "fullscreen: 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch fullscreenstate 2 2 toggle"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/activewindow");
|
||||||
|
EXPECT_CONTAINS(str, "fullscreen: 2");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the process autostarted in the config does not
|
||||||
|
// become a zombie even if it terminates very quickly.
|
||||||
|
EXPECT(Tests::execAndGet("pgrep -f 'sleep 0'").empty(), true);
|
||||||
|
|
||||||
// kill all
|
// kill all
|
||||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||||
Tests::killAllWindows();
|
Tests::killAllWindows();
|
||||||
|
|
|
||||||
85
hyprtester/src/tests/main/persistent.cpp
Normal file
85
hyprtester/src/tests/main/persistent.cpp
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
#include "tests.hpp"
|
||||||
|
#include "../../shared.hpp"
|
||||||
|
#include "../../hyprctlCompat.hpp"
|
||||||
|
#include <print>
|
||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
#include <hyprutils/os/Process.hpp>
|
||||||
|
#include <hyprutils/memory/WeakPtr.hpp>
|
||||||
|
#include <csignal>
|
||||||
|
#include <cerrno>
|
||||||
|
#include "../shared.hpp"
|
||||||
|
|
||||||
|
static int ret = 0;
|
||||||
|
|
||||||
|
using namespace Hyprutils::OS;
|
||||||
|
using namespace Hyprutils::Memory;
|
||||||
|
|
||||||
|
#define UP CUniquePointer
|
||||||
|
#define SP CSharedPointer
|
||||||
|
|
||||||
|
static bool test() {
|
||||||
|
NLog::log("{}Testing persistent workspaces", Colors::GREEN);
|
||||||
|
|
||||||
|
EXPECT(Tests::windowCount(), 0);
|
||||||
|
|
||||||
|
// test on workspace "window"
|
||||||
|
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
|
||||||
|
getFromSocket("/dispatch workspace 1"); // no OK: we might be on 1 already
|
||||||
|
|
||||||
|
OK(getFromSocket("/keyword workspace 5, monitor:HEADLESS-2, persistent:1"));
|
||||||
|
OK(getFromSocket("/keyword workspace 6, monitor:HEADLESS-PERSISTENT-TEST, persistent:1"));
|
||||||
|
OK(getFromSocket("/keyword workspace name:PERSIST, monitor:HEADLESS-PERSISTENT-TEST, persistent:1"));
|
||||||
|
OK(getFromSocket("/keyword workspace name:PERSIST-2, monitor:HEADLESS-PERSISTENT-TEST, persistent:1"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/workspaces");
|
||||||
|
EXPECT_CONTAINS(str, "ID 5 (5)");
|
||||||
|
EXPECT_COUNT_STRING(str, "workspace ID ", 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/output create headless HEADLESS-PERSISTENT-TEST"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/monitors");
|
||||||
|
EXPECT_CONTAINS(str, "HEADLESS-PERSISTENT-TEST");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch focusmonitor HEADLESS-PERSISTENT-TEST"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/workspaces");
|
||||||
|
EXPECT_CONTAINS(str, "ID 2 (2)"); // this should be automatically generated by hl
|
||||||
|
EXPECT_CONTAINS(str, "ID 5 (5)");
|
||||||
|
EXPECT_CONTAINS(str, "ID 6 (6)");
|
||||||
|
EXPECT_CONTAINS(str, "(PERSIST) on monitor");
|
||||||
|
EXPECT_CONTAINS(str, "(PERSIST-2) on monitor");
|
||||||
|
EXPECT_COUNT_STRING(str, "workspace ID ", 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/reload"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/workspaces");
|
||||||
|
EXPECT_NOT_CONTAINS(str, "ID 5 (5)");
|
||||||
|
EXPECT_NOT_CONTAINS(str, "ID 6 (6)");
|
||||||
|
EXPECT_NOT_CONTAINS(str, "(PERSIST) on monitor");
|
||||||
|
EXPECT_COUNT_STRING(str, "workspace ID ", 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/output remove HEADLESS-PERSISTENT-TEST"));
|
||||||
|
|
||||||
|
// kill all
|
||||||
|
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||||
|
Tests::killAllWindows();
|
||||||
|
|
||||||
|
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
|
||||||
|
EXPECT(Tests::windowCount(), 0);
|
||||||
|
|
||||||
|
// reload cfg
|
||||||
|
OK(getFromSocket("/reload"));
|
||||||
|
|
||||||
|
return !ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
REGISTER_TEST_FN(test)
|
||||||
|
|
@ -45,19 +45,17 @@ static void expectSnapMove(const Vector2D FROM, const Vector2D* TO) {
|
||||||
EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("at: {},{}", B.x, B.y));
|
EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("at: {},{}", B.x, B.y));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void testSnap(const bool OVERLAP, const bool RESPECT) {
|
static void testWindowSnap(const bool RESPECTGAPS) {
|
||||||
const double BORDERSIZE = 2;
|
const int BORDERSIZE = 2;
|
||||||
const double WINDOWSIZE = 100;
|
const int WINDOWSIZE = 100;
|
||||||
|
|
||||||
// test window snapping
|
const int OTHER = 500;
|
||||||
{
|
const int WINDOWGAP = 8;
|
||||||
const double OTHER = 500;
|
const int GAPSIN = 5;
|
||||||
const double WINDOWGAP = 8;
|
const int GAP = (RESPECTGAPS ? 2 * GAPSIN : 0) + (2 * BORDERSIZE);
|
||||||
const double GAPSIN = 5;
|
const int END = GAP + WINDOWSIZE;
|
||||||
const double GAP = (RESPECT ? GAPSIN : 0) + BORDERSIZE + (OVERLAP ? 0 : BORDERSIZE);
|
|
||||||
const double END = GAP + WINDOWSIZE;
|
|
||||||
|
|
||||||
double x;
|
int x;
|
||||||
Vector2D predict;
|
Vector2D predict;
|
||||||
|
|
||||||
x = WINDOWGAP + END;
|
x = WINDOWGAP + END;
|
||||||
|
|
@ -70,17 +68,19 @@ static void testSnap(const bool OVERLAP, const bool RESPECT) {
|
||||||
expectSnapMove({OTHER - x, OTHER}, &(predict = {OTHER - END, OTHER}));
|
expectSnapMove({OTHER - x, OTHER}, &(predict = {OTHER - END, OTHER}));
|
||||||
expectSnapMove({OTHER, OTHER + x}, &(predict = {OTHER, OTHER + END}));
|
expectSnapMove({OTHER, OTHER + x}, &(predict = {OTHER, OTHER + END}));
|
||||||
expectSnapMove({OTHER, OTHER - x}, &(predict = {OTHER, OTHER - END}));
|
expectSnapMove({OTHER, OTHER - x}, &(predict = {OTHER, OTHER - END}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// test monitor snapping
|
static void testMonitorSnap(const bool RESPECTGAPS, const bool OVERLAP) {
|
||||||
{
|
const int BORDERSIZE = 2;
|
||||||
const double MONITORGAP = 10;
|
const int WINDOWSIZE = 100;
|
||||||
const double GAPSOUT = 20;
|
|
||||||
const double RESP = (RESPECT ? GAPSOUT : 0);
|
|
||||||
const double GAP = RESP + (OVERLAP ? 0 : BORDERSIZE);
|
|
||||||
const double END = GAP + WINDOWSIZE;
|
|
||||||
|
|
||||||
double x;
|
const int MONITORGAP = 10;
|
||||||
|
const int GAPSOUT = 20;
|
||||||
|
const int RESP = (RESPECTGAPS ? GAPSOUT : 0);
|
||||||
|
const int GAP = RESP + (OVERLAP ? 0 : BORDERSIZE);
|
||||||
|
const int END = GAP + WINDOWSIZE;
|
||||||
|
|
||||||
|
int x;
|
||||||
Vector2D predict;
|
Vector2D predict;
|
||||||
|
|
||||||
x = MONITORGAP + GAP;
|
x = MONITORGAP + GAP;
|
||||||
|
|
@ -94,9 +94,9 @@ static void testSnap(const bool OVERLAP, const bool RESPECT) {
|
||||||
expectSnapMove({1920 - x, 1080 - x}, &(predict = {1920 - END, 1080 - END}));
|
expectSnapMove({1920 - x, 1080 - x}, &(predict = {1920 - END, 1080 - END}));
|
||||||
|
|
||||||
// test reserved area
|
// test reserved area
|
||||||
const double RESERVED = 200;
|
const int RESERVED = 200;
|
||||||
const double RGAP = RESERVED + RESP + BORDERSIZE;
|
const int RGAP = RESERVED + RESP + BORDERSIZE;
|
||||||
const double REND = RGAP + WINDOWSIZE;
|
const int REND = RGAP + WINDOWSIZE;
|
||||||
|
|
||||||
x = MONITORGAP + RGAP;
|
x = MONITORGAP + RGAP;
|
||||||
expectSnapMove({x, x}, nullptr);
|
expectSnapMove({x, x}, nullptr);
|
||||||
|
|
@ -107,7 +107,6 @@ static void testSnap(const bool OVERLAP, const bool RESPECT) {
|
||||||
expectSnapMove({1920 - x, 1080 - x}, nullptr);
|
expectSnapMove({1920 - x, 1080 - x}, nullptr);
|
||||||
x -= 1;
|
x -= 1;
|
||||||
expectSnapMove({1920 - x, 1080 - x}, &(predict = {1920 - REND, 1080 - REND}));
|
expectSnapMove({1920 - x, 1080 - x}, &(predict = {1920 - REND, 1080 - REND}));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool test() {
|
static bool test() {
|
||||||
|
|
@ -143,20 +142,22 @@ static bool test() {
|
||||||
EXPECT(Tests::windowCount(), 2);
|
EXPECT(Tests::windowCount(), 2);
|
||||||
|
|
||||||
NLog::log("");
|
NLog::log("");
|
||||||
testSnap(false, false);
|
testWindowSnap(false);
|
||||||
|
testMonitorSnap(false, false);
|
||||||
NLog::log("\n{}Turning on border_overlap", Colors::YELLOW);
|
|
||||||
OK(getFromSocket("/keyword general:snap:border_overlap true"));
|
|
||||||
testSnap(true, false);
|
|
||||||
|
|
||||||
NLog::log("\n{}Turning on respect_gaps", Colors::YELLOW);
|
NLog::log("\n{}Turning on respect_gaps", Colors::YELLOW);
|
||||||
OK(getFromSocket("/keyword general:snap:border_overlap false"));
|
|
||||||
OK(getFromSocket("/keyword general:snap:respect_gaps true"));
|
OK(getFromSocket("/keyword general:snap:respect_gaps true"));
|
||||||
testSnap(false, true);
|
testWindowSnap(true);
|
||||||
|
testMonitorSnap(true, false);
|
||||||
|
|
||||||
|
NLog::log("\n{}Turning on border_overlap", Colors::YELLOW);
|
||||||
|
OK(getFromSocket("/keyword general:snap:respect_gaps false"));
|
||||||
|
OK(getFromSocket("/keyword general:snap:border_overlap true"));
|
||||||
|
testMonitorSnap(false, true);
|
||||||
|
|
||||||
NLog::log("\n{}Turning on both border_overlap and respect_gaps", Colors::YELLOW);
|
NLog::log("\n{}Turning on both border_overlap and respect_gaps", Colors::YELLOW);
|
||||||
OK(getFromSocket("/keyword general:snap:border_overlap true"));
|
OK(getFromSocket("/keyword general:snap:respect_gaps true"));
|
||||||
testSnap(true, true);
|
testMonitorSnap(true, true);
|
||||||
|
|
||||||
// kill all
|
// kill all
|
||||||
NLog::log("\n{}Killing all windows", Colors::YELLOW);
|
NLog::log("\n{}Killing all windows", Colors::YELLOW);
|
||||||
|
|
@ -167,6 +168,7 @@ static bool test() {
|
||||||
|
|
||||||
NLog::log("{}Reloading the config", Colors::YELLOW);
|
NLog::log("{}Reloading the config", Colors::YELLOW);
|
||||||
OK(getFromSocket("/reload"));
|
OK(getFromSocket("/reload"));
|
||||||
|
OK(getFromSocket("/dispatch workspace 1"));
|
||||||
|
|
||||||
return !ret;
|
return !ret;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
76
hyprtester/src/tests/main/solitary.cpp
Normal file
76
hyprtester/src/tests/main/solitary.cpp
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
#include "tests.hpp"
|
||||||
|
#include "../../shared.hpp"
|
||||||
|
#include "../../hyprctlCompat.hpp"
|
||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
#include <hyprutils/os/Process.hpp>
|
||||||
|
#include <hyprutils/memory/WeakPtr.hpp>
|
||||||
|
#include "../shared.hpp"
|
||||||
|
|
||||||
|
static int ret = 0;
|
||||||
|
|
||||||
|
using namespace Hyprutils::OS;
|
||||||
|
using namespace Hyprutils::Memory;
|
||||||
|
|
||||||
|
#define UP CUniquePointer
|
||||||
|
#define SP CSharedPointer
|
||||||
|
|
||||||
|
static bool test() {
|
||||||
|
NLog::log("{}Testing solitary clients", Colors::GREEN);
|
||||||
|
|
||||||
|
OK(getFromSocket("/keyword general:allow_tearing false"));
|
||||||
|
OK(getFromSocket("/keyword render:direct_scanout 0"));
|
||||||
|
OK(getFromSocket("/keyword cursor:no_hardware_cursors 1"));
|
||||||
|
NLog::log("{}Expecting blocked solitary/DS/tearing", Colors::YELLOW);
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/monitors");
|
||||||
|
EXPECT_CONTAINS(str, "solitary: 0\n");
|
||||||
|
EXPECT_CONTAINS(str, "solitaryBlockedBy: windowed mode,missing candidate");
|
||||||
|
EXPECT_CONTAINS(str, "activelyTearing: false");
|
||||||
|
EXPECT_CONTAINS(str, "tearingBlockedBy: next frame is not torn,user settings,not supported by monitor,missing candidate");
|
||||||
|
EXPECT_CONTAINS(str, "directScanoutTo: 0\n");
|
||||||
|
EXPECT_CONTAINS(str, "directScanoutBlockedBy: user settings,software renders/cursors,missing candidate");
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: need a reliable client with solitary opaque surface in fullscreen. kitty doesn't work all the time
|
||||||
|
// NLog::log("{}Spawning kittyProcA", Colors::YELLOW);
|
||||||
|
// auto kittyProcA = Tests::spawnKitty();
|
||||||
|
|
||||||
|
// if (!kittyProcA) {
|
||||||
|
// NLog::log("{}Error: kitty did not spawn", Colors::RED);
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// OK(getFromSocket("/keyword general:allow_tearing true"));
|
||||||
|
// OK(getFromSocket("/keyword render:direct_scanout 1"));
|
||||||
|
// NLog::log("{}", getFromSocket("/clients"));
|
||||||
|
// OK(getFromSocket("/dispatch fullscreen"));
|
||||||
|
// NLog::log("{}", getFromSocket("/clients"));
|
||||||
|
// std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
// NLog::log("{}Expecting kitty to almost pass for solitary/DS/tearing", Colors::YELLOW);
|
||||||
|
// {
|
||||||
|
// auto str = getFromSocket("/monitors");
|
||||||
|
// EXPECT_NOT_CONTAINS(str, "solitary: 0\n");
|
||||||
|
// EXPECT_CONTAINS(str, "solitaryBlockedBy: null");
|
||||||
|
// EXPECT_CONTAINS(str, "activelyTearing: false");
|
||||||
|
// EXPECT_CONTAINS(str, "tearingBlockedBy: next frame is not torn,not supported by monitor,window settings");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// OK(getFromSocket("/dispatch setprop active immediate 1"));
|
||||||
|
// NLog::log("{}Expecting kitty to almost pass for tearing", Colors::YELLOW);
|
||||||
|
// {
|
||||||
|
// auto str = getFromSocket("/monitors");
|
||||||
|
// EXPECT_CONTAINS(str, "tearingBlockedBy: next frame is not torn,not supported by monitor\n");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // kill all
|
||||||
|
// NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||||
|
// Tests::killAllWindows();
|
||||||
|
|
||||||
|
NLog::log("{}Reloading the config", Colors::YELLOW);
|
||||||
|
OK(getFromSocket("/reload"));
|
||||||
|
|
||||||
|
return !ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
REGISTER_TEST_FN(test)
|
||||||
51
hyprtester/src/tests/main/tags.cpp
Normal file
51
hyprtester/src/tests/main/tags.cpp
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
#include "../../shared.hpp"
|
||||||
|
#include "../../hyprctlCompat.hpp"
|
||||||
|
#include "../shared.hpp"
|
||||||
|
#include "tests.hpp"
|
||||||
|
|
||||||
|
static int ret = 0;
|
||||||
|
|
||||||
|
static bool testTags() {
|
||||||
|
NLog::log("{}Testing tags", Colors::GREEN);
|
||||||
|
|
||||||
|
EXPECT(Tests::windowCount(), 0);
|
||||||
|
|
||||||
|
NLog::log("{}Spawning kittyProcA&B on ws 1", Colors::YELLOW);
|
||||||
|
auto kittyProcA = Tests::spawnKitty("tagged");
|
||||||
|
auto kittyProcB = Tests::spawnKitty("untagged");
|
||||||
|
|
||||||
|
if (!kittyProcA || !kittyProcB) {
|
||||||
|
NLog::log("{}Error: kitty did not spawn", Colors::RED);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
NLog::log("{}Testing testTag tags", Colors::YELLOW);
|
||||||
|
|
||||||
|
OK(getFromSocket("/keyword windowrule[tag-test-1]:tag +testTag"));
|
||||||
|
OK(getFromSocket("/keyword windowrule[tag-test-1]:match:class tagged"));
|
||||||
|
OK(getFromSocket("/keyword windowrule[tag-test-2]:match:tag negative:testTag"));
|
||||||
|
OK(getFromSocket("/keyword windowrule[tag-test-2]:no_shadow true"));
|
||||||
|
OK(getFromSocket("/keyword windowrule[tag-test-3]:match:tag testTag"));
|
||||||
|
OK(getFromSocket("/keyword windowrule[tag-test-3]:no_dim true"));
|
||||||
|
|
||||||
|
EXPECT(Tests::windowCount(), 2);
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:tagged"));
|
||||||
|
NLog::log("{}Testing tagged window for no_dim 0 & no_shadow", Colors::YELLOW);
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/activewindow"), "testTag");
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/getprop activewindow no_dim"), "true");
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/getprop activewindow no_shadow"), "false");
|
||||||
|
NLog::log("{}Testing untagged window for no_dim & no_shadow", Colors::YELLOW);
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:untagged"));
|
||||||
|
EXPECT_NOT_CONTAINS(getFromSocket("/activewindow"), "testTag");
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/getprop activewindow no_shadow"), "true");
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/getprop activewindow no_dim"), "false");
|
||||||
|
|
||||||
|
Tests::killAllWindows();
|
||||||
|
EXPECT(Tests::windowCount(), 0);
|
||||||
|
|
||||||
|
OK(getFromSocket("/reload"));
|
||||||
|
|
||||||
|
return ret == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
REGISTER_TEST_FN(testTags)
|
||||||
|
|
@ -1,22 +1,377 @@
|
||||||
#include "tests.hpp"
|
#include <cmath>
|
||||||
#include "../../shared.hpp"
|
|
||||||
#include "../../hyprctlCompat.hpp"
|
|
||||||
#include <print>
|
|
||||||
#include <thread>
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <thread>
|
||||||
#include <hyprutils/os/Process.hpp>
|
#include <hyprutils/os/Process.hpp>
|
||||||
#include <hyprutils/memory/WeakPtr.hpp>
|
#include <hyprutils/memory/WeakPtr.hpp>
|
||||||
#include <csignal>
|
#include <hyprutils/string/VarList2.hpp>
|
||||||
#include <cerrno>
|
|
||||||
|
#include "../../shared.hpp"
|
||||||
|
#include "../../hyprctlCompat.hpp"
|
||||||
#include "../shared.hpp"
|
#include "../shared.hpp"
|
||||||
|
#include "tests.hpp"
|
||||||
|
|
||||||
static int ret = 0;
|
static int ret = 0;
|
||||||
|
|
||||||
using namespace Hyprutils::OS;
|
static bool spawnKitty(const std::string& class_, const std::vector<std::string>& args = {}) {
|
||||||
using namespace Hyprutils::Memory;
|
NLog::log("{}Spawning {}", Colors::YELLOW, class_);
|
||||||
|
if (!Tests::spawnKitty(class_, args)) {
|
||||||
|
NLog::log("{}Error: {} did not spawn", Colors::RED, class_);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
#define UP CUniquePointer
|
static std::string getWindowAttribute(const std::string& winInfo, const std::string& attr) {
|
||||||
#define SP CSharedPointer
|
auto pos = winInfo.find(attr);
|
||||||
|
if (pos == std::string::npos) {
|
||||||
|
NLog::log("{}Wrong window attribute", Colors::RED);
|
||||||
|
ret = 1;
|
||||||
|
return "Wrong window attribute";
|
||||||
|
}
|
||||||
|
auto pos2 = winInfo.find('\n', pos);
|
||||||
|
return winInfo.substr(pos, pos2 - pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string getWindowAddress(const std::string& winInfo) {
|
||||||
|
auto pos = winInfo.find("Window ");
|
||||||
|
auto pos2 = winInfo.find(" -> ");
|
||||||
|
if (pos == std::string::npos || pos2 == std::string::npos) {
|
||||||
|
NLog::log("{}Wrong window info", Colors::RED);
|
||||||
|
ret = 1;
|
||||||
|
return "Wrong window info";
|
||||||
|
}
|
||||||
|
return winInfo.substr(pos + 7, pos2 - pos - 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testSwapWindow() {
|
||||||
|
NLog::log("{}Testing swapwindow", Colors::GREEN);
|
||||||
|
|
||||||
|
// test on workspace "swapwindow"
|
||||||
|
NLog::log("{}Switching to workspace \"swapwindow\"", Colors::YELLOW);
|
||||||
|
getFromSocket("/dispatch workspace name:swapwindow");
|
||||||
|
|
||||||
|
if (!Tests::spawnKitty("kitty_A")) {
|
||||||
|
ret = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Tests::spawnKitty("kitty_B")) {
|
||||||
|
ret = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NLog::log("{}Expecting 2 windows", Colors::YELLOW);
|
||||||
|
EXPECT(Tests::windowCount(), 2);
|
||||||
|
|
||||||
|
// Test swapwindow by direction
|
||||||
|
{
|
||||||
|
getFromSocket("/dispatch focuswindow class:kitty_A");
|
||||||
|
auto pos = getWindowAttribute(getFromSocket("/activewindow"), "at:");
|
||||||
|
NLog::log("{}Testing kitty_A {}, swapwindow with direction 'l'", Colors::YELLOW, pos);
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch swapwindow l"));
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:kitty_B"));
|
||||||
|
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("{}", pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test swapwindow by class
|
||||||
|
{
|
||||||
|
getFromSocket("/dispatch focuswindow class:kitty_A");
|
||||||
|
auto pos = getWindowAttribute(getFromSocket("/activewindow"), "at:");
|
||||||
|
NLog::log("{}Testing kitty_A {}, swapwindow with class:kitty_B", Colors::YELLOW, pos);
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch swapwindow class:kitty_B"));
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:kitty_B"));
|
||||||
|
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("{}", pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test swapwindow by address
|
||||||
|
{
|
||||||
|
getFromSocket("/dispatch focuswindow class:kitty_B");
|
||||||
|
auto addr = getWindowAddress(getFromSocket("/activewindow"));
|
||||||
|
getFromSocket("/dispatch focuswindow class:kitty_A");
|
||||||
|
auto pos = getWindowAttribute(getFromSocket("/activewindow"), "at:");
|
||||||
|
NLog::log("{}Testing kitty_A {}, swapwindow with address:0x{}(kitty_B)", Colors::YELLOW, pos, addr);
|
||||||
|
|
||||||
|
OK(getFromSocket(std::format("/dispatch swapwindow address:0x{}", addr)));
|
||||||
|
OK(getFromSocket(std::format("/dispatch focuswindow address:0x{}", addr)));
|
||||||
|
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("{}", pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
NLog::log("{}Testing swapwindow with fullscreen. Expecting to fail", Colors::YELLOW);
|
||||||
|
{
|
||||||
|
OK(getFromSocket("/dispatch fullscreen"));
|
||||||
|
|
||||||
|
auto str = getFromSocket("/dispatch swapwindow l");
|
||||||
|
EXPECT_CONTAINS(str, "Can't swap fullscreen window");
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch fullscreen"));
|
||||||
|
}
|
||||||
|
|
||||||
|
NLog::log("{}Testing swapwindow with different workspace", Colors::YELLOW);
|
||||||
|
{
|
||||||
|
getFromSocket("/dispatch focuswindow class:kitty_B");
|
||||||
|
auto addr = getWindowAddress(getFromSocket("/activewindow"));
|
||||||
|
auto ws = getWindowAttribute(getFromSocket("/activewindow"), "workspace:");
|
||||||
|
NLog::log("{}Sending address:0x{}(kitty_B) to workspace \"swapwindow2\"", Colors::YELLOW, addr);
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch movetoworkspacesilent name:swapwindow2"));
|
||||||
|
OK(getFromSocket(std::format("/dispatch swapwindow address:0x{}", addr)));
|
||||||
|
getFromSocket("/dispatch focuswindow class:kitty_B");
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("{}", ws));
|
||||||
|
}
|
||||||
|
|
||||||
|
// kill all
|
||||||
|
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||||
|
Tests::killAllWindows();
|
||||||
|
|
||||||
|
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
|
||||||
|
EXPECT(Tests::windowCount(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testGroupRules() {
|
||||||
|
NLog::log("{}Testing group window rules", Colors::YELLOW);
|
||||||
|
|
||||||
|
OK(getFromSocket("/keyword general:border_size 8"));
|
||||||
|
OK(getFromSocket("/keyword workspace w[tv1], bordersize:0"));
|
||||||
|
OK(getFromSocket("/keyword workspace f[1], bordersize:0"));
|
||||||
|
OK(getFromSocket("/keyword windowrule match:workspace w[tv1], border_size 0"));
|
||||||
|
OK(getFromSocket("/keyword windowrule match:workspace f[1], border_size 0"));
|
||||||
|
|
||||||
|
if (!Tests::spawnKitty("kitty_A")) {
|
||||||
|
ret = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/getprop active border_size");
|
||||||
|
EXPECT_CONTAINS(str, "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Tests::spawnKitty("kitty_B")) {
|
||||||
|
ret = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/getprop active border_size");
|
||||||
|
EXPECT_CONTAINS(str, "8");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
|
||||||
|
OK(getFromSocket("/dispatch togglegroup"));
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:kitty_B"));
|
||||||
|
OK(getFromSocket("/dispatch moveintogroup l"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/getprop active border_size");
|
||||||
|
EXPECT_CONTAINS(str, "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch changegroupactive f"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/getprop active border_size");
|
||||||
|
EXPECT_CONTAINS(str, "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Tests::spawnKitty("kitty_C")) {
|
||||||
|
ret = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch moveoutofgroup r"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/getprop active border_size");
|
||||||
|
EXPECT_CONTAINS(str, "8");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/reload"));
|
||||||
|
Tests::killAllWindows();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isActiveWindow(const std::string& class_, char fullscreen, bool log = true) {
|
||||||
|
std::string activeWin = getFromSocket("/activewindow");
|
||||||
|
auto winClass = getWindowAttribute(activeWin, "class:");
|
||||||
|
auto winFullscreen = getWindowAttribute(activeWin, "fullscreen:").back();
|
||||||
|
if (winClass.substr(strlen("class: ")) == class_ && winFullscreen == fullscreen)
|
||||||
|
return true;
|
||||||
|
else {
|
||||||
|
if (log)
|
||||||
|
NLog::log("{}Wrong active window: expected class {} fullscreen '{}', found class {}, fullscreen '{}'", Colors::RED, class_, fullscreen, winClass, winFullscreen);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool waitForActiveWindow(const std::string& class_, char fullscreen, int maxTries = 50) {
|
||||||
|
int cnt = 0;
|
||||||
|
while (!isActiveWindow(class_, fullscreen, false)) {
|
||||||
|
++cnt;
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
if (cnt > maxTries) {
|
||||||
|
return isActiveWindow(class_, fullscreen, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests behavior of a window being focused when on that window's workspace
|
||||||
|
/// another fullscreen window exists.
|
||||||
|
static bool testWindowFocusOnFullscreenConflict() {
|
||||||
|
if (!spawnKitty("kitty_A"))
|
||||||
|
return false;
|
||||||
|
if (!spawnKitty("kitty_B"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
OK(getFromSocket("/keyword misc:focus_on_activate true"));
|
||||||
|
|
||||||
|
auto spawnKittyActivating = [] -> std::string {
|
||||||
|
// `XXXXXX` is what `mkstemp` expects to find in the string
|
||||||
|
std::string tmpFilename = (std::filesystem::temp_directory_path() / "XXXXXX").string();
|
||||||
|
int fd = mkstemp(tmpFilename.data());
|
||||||
|
if (fd < 0) {
|
||||||
|
NLog::log("{}Error: could not create tmp file: errno {}", Colors::RED, errno);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
(void)close(fd);
|
||||||
|
bool ok = spawnKitty("kitty_activating",
|
||||||
|
{"-o", "allow_remote_control=yes", "--", "/bin/sh", "-c", "while [ -f \"" + tmpFilename + "\" ]; do :; done; kitten @ focus-window; sleep infinity"});
|
||||||
|
if (!ok) {
|
||||||
|
NLog::log("{}Error: failed to spawn kitty", Colors::RED);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return tmpFilename;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Unfullscreen on conflict
|
||||||
|
{
|
||||||
|
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 2"));
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
|
||||||
|
OK(getFromSocket("/dispatch fullscreen 0 set"));
|
||||||
|
EXPECT(isActiveWindow("kitty_A", '2'), true);
|
||||||
|
|
||||||
|
// Dispatch-focus the same window
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
|
||||||
|
EXPECT(isActiveWindow("kitty_A", '2'), true);
|
||||||
|
|
||||||
|
// Dispatch-focus a different window
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:kitty_B"));
|
||||||
|
EXPECT(isActiveWindow("kitty_B", '0'), true);
|
||||||
|
|
||||||
|
// Make a window that will request focus
|
||||||
|
const std::string removeToActivate = spawnKittyActivating();
|
||||||
|
if (removeToActivate.empty())
|
||||||
|
return false;
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
|
||||||
|
OK(getFromSocket("/dispatch fullscreen 0 set"));
|
||||||
|
EXPECT(isActiveWindow("kitty_A", '2'), true);
|
||||||
|
std::filesystem::remove(removeToActivate);
|
||||||
|
EXPECT(waitForActiveWindow("kitty_activating", '0'), true);
|
||||||
|
OK(getFromSocket("/dispatch forcekillactive"));
|
||||||
|
Tests::waitUntilWindowsN(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take over on conflict
|
||||||
|
{
|
||||||
|
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 1"));
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
|
||||||
|
OK(getFromSocket("/dispatch fullscreen 0 set"));
|
||||||
|
EXPECT(isActiveWindow("kitty_A", '2'), true);
|
||||||
|
|
||||||
|
// Dispatch-focus the same window
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
|
||||||
|
EXPECT(isActiveWindow("kitty_A", '2'), true);
|
||||||
|
|
||||||
|
// Dispatch-focus a different window
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:kitty_B"));
|
||||||
|
EXPECT(isActiveWindow("kitty_B", '2'), true);
|
||||||
|
OK(getFromSocket("/dispatch fullscreenstate 0 0"));
|
||||||
|
|
||||||
|
// Make a window that will request focus
|
||||||
|
const std::string removeToActivate = spawnKittyActivating();
|
||||||
|
if (removeToActivate.empty())
|
||||||
|
return false;
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
|
||||||
|
OK(getFromSocket("/dispatch fullscreen 0 set"));
|
||||||
|
EXPECT(isActiveWindow("kitty_A", '2'), true);
|
||||||
|
std::filesystem::remove(removeToActivate);
|
||||||
|
EXPECT(waitForActiveWindow("kitty_activating", '2'), true);
|
||||||
|
OK(getFromSocket("/dispatch forcekillactive"));
|
||||||
|
Tests::waitUntilWindowsN(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep the old focus on conflict
|
||||||
|
{
|
||||||
|
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 0"));
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
|
||||||
|
OK(getFromSocket("/dispatch fullscreen 0 set"));
|
||||||
|
EXPECT(isActiveWindow("kitty_A", '2'), true);
|
||||||
|
|
||||||
|
// Dispatch-focus the same window
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
|
||||||
|
EXPECT(isActiveWindow("kitty_A", '2'), true);
|
||||||
|
|
||||||
|
// Make a window that will request focus - the setting is treated normally
|
||||||
|
const std::string removeToActivate = spawnKittyActivating();
|
||||||
|
if (removeToActivate.empty())
|
||||||
|
return false;
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
|
||||||
|
OK(getFromSocket("/dispatch fullscreen 0 set"));
|
||||||
|
EXPECT(isActiveWindow("kitty_A", '2'), true);
|
||||||
|
std::filesystem::remove(removeToActivate);
|
||||||
|
EXPECT(waitForActiveWindow("kitty_A", '2'), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
NLog::log("{}Reloading config", Colors::YELLOW);
|
||||||
|
OK(getFromSocket("/reload"));
|
||||||
|
|
||||||
|
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||||
|
Tests::killAllWindows();
|
||||||
|
|
||||||
|
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
|
||||||
|
EXPECT(Tests::windowCount(), 0);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testMaximizeSize() {
|
||||||
|
NLog::log("{}Testing maximize size", Colors::GREEN);
|
||||||
|
|
||||||
|
EXPECT(spawnKitty("kitty_A"), true);
|
||||||
|
|
||||||
|
// check kitty properties. Maximizing shouldnt change its size
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/clients");
|
||||||
|
EXPECT(str.contains("at: 22,22"), true);
|
||||||
|
EXPECT(str.contains("size: 1876,1036"), true);
|
||||||
|
EXPECT(str.contains("fullscreen: 0"), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch fullscreen 1"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/clients");
|
||||||
|
EXPECT(str.contains("at: 22,22"), true);
|
||||||
|
EXPECT(str.contains("size: 1876,1036"), true);
|
||||||
|
EXPECT(str.contains("fullscreen: 1"), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||||
|
Tests::killAllWindows();
|
||||||
|
|
||||||
|
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
|
||||||
|
EXPECT(Tests::windowCount(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
static bool test() {
|
static bool test() {
|
||||||
NLog::log("{}Testing windows", Colors::GREEN);
|
NLog::log("{}Testing windows", Colors::GREEN);
|
||||||
|
|
@ -25,19 +380,11 @@ static bool test() {
|
||||||
NLog::log("{}Switching to workspace `window`", Colors::YELLOW);
|
NLog::log("{}Switching to workspace `window`", Colors::YELLOW);
|
||||||
getFromSocket("/dispatch workspace name:window");
|
getFromSocket("/dispatch workspace name:window");
|
||||||
|
|
||||||
NLog::log("{}Spawning kittyProcA", Colors::YELLOW);
|
if (!spawnKitty("kitty_A"))
|
||||||
auto kittyProcA = Tests::spawnKitty();
|
|
||||||
|
|
||||||
if (!kittyProcA) {
|
|
||||||
NLog::log("{}Error: kitty did not spawn", Colors::RED);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
NLog::log("{}Expecting 1 window", Colors::YELLOW);
|
|
||||||
EXPECT(Tests::windowCount(), 1);
|
|
||||||
|
|
||||||
// check kitty properties. One kitty should take the entire screen, as this is smart gaps
|
// check kitty properties. One kitty should take the entire screen, as this is smart gaps
|
||||||
NLog::log("{}Expecting kitty to take up the whole screen", Colors::YELLOW);
|
NLog::log("{}Expecting kitty_A to take up the whole screen", Colors::YELLOW);
|
||||||
{
|
{
|
||||||
auto str = getFromSocket("/clients");
|
auto str = getFromSocket("/clients");
|
||||||
EXPECT(str.contains("at: 0,0"), true);
|
EXPECT(str.contains("at: 0,0"), true);
|
||||||
|
|
@ -45,31 +392,95 @@ static bool test() {
|
||||||
EXPECT(str.contains("fullscreen: 0"), true);
|
EXPECT(str.contains("fullscreen: 0"), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
NLog::log("{}Spawning kittyProcB", Colors::YELLOW);
|
NLog::log("{}Testing window split ratios", Colors::YELLOW);
|
||||||
auto kittyProcB = Tests::spawnKitty();
|
{
|
||||||
if (!kittyProcB) {
|
const double INITIAL_RATIO = 1.25;
|
||||||
NLog::log("{}Error: kitty did not spawn", Colors::RED);
|
const int GAPSIN = 5;
|
||||||
|
const int GAPSOUT = 20;
|
||||||
|
const int BORDERSIZE = 2;
|
||||||
|
const int BORDERS = BORDERSIZE * 2;
|
||||||
|
const int MONITOR_W = 1920;
|
||||||
|
const int MONITOR_H = 1080;
|
||||||
|
|
||||||
|
const float totalAvailableHeight = MONITOR_H - (GAPSOUT * 2);
|
||||||
|
const int HEIGHT = std::floor(totalAvailableHeight) - BORDERS;
|
||||||
|
const float availableWidthForSplit = MONITOR_W - (GAPSOUT * 2) - GAPSIN;
|
||||||
|
|
||||||
|
auto calculateFinalWidth = [&](double boxWidth, bool isLeftWindow) {
|
||||||
|
double gapLeft = isLeftWindow ? GAPSOUT : GAPSIN;
|
||||||
|
double gapRight = isLeftWindow ? GAPSIN : GAPSOUT;
|
||||||
|
return std::floor(boxWidth - gapLeft - gapRight - BORDERS);
|
||||||
|
};
|
||||||
|
|
||||||
|
double geomBoxWidthA_R1 = (availableWidthForSplit * INITIAL_RATIO / 2.0) + GAPSOUT + (GAPSIN / 2.0);
|
||||||
|
double geomBoxWidthB_R1 = MONITOR_W - geomBoxWidthA_R1;
|
||||||
|
const int WIDTH1 = calculateFinalWidth(geomBoxWidthB_R1, false);
|
||||||
|
|
||||||
|
const double INVERTED_RATIO = 0.75;
|
||||||
|
double geomBoxWidthA_R2 = (availableWidthForSplit * INVERTED_RATIO / 2.0) + GAPSOUT + (GAPSIN / 2.0);
|
||||||
|
double geomBoxWidthB_R2 = MONITOR_W - geomBoxWidthA_R2;
|
||||||
|
const int WIDTH2 = calculateFinalWidth(geomBoxWidthB_R2, false);
|
||||||
|
const int WIDTH_A_FINAL = calculateFinalWidth(geomBoxWidthA_R2, true);
|
||||||
|
|
||||||
|
OK(getFromSocket("/keyword dwindle:default_split_ratio 1.25"));
|
||||||
|
|
||||||
|
if (!spawnKitty("kitty_B"))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
NLog::log("{}Expecting kitty_B size: {},{}", Colors::YELLOW, WIDTH1, HEIGHT);
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("size: {},{}", WIDTH1, HEIGHT));
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch killwindow activewindow"));
|
||||||
|
Tests::waitUntilWindowsN(1);
|
||||||
|
|
||||||
|
NLog::log("{}Inverting the split ratio", Colors::YELLOW);
|
||||||
|
OK(getFromSocket("/keyword dwindle:default_split_ratio 0.75"));
|
||||||
|
|
||||||
|
if (!spawnKitty("kitty_B"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
NLog::log("{}Expecting kitty_B size: {},{}", Colors::YELLOW, WIDTH2, HEIGHT);
|
||||||
|
|
||||||
|
{
|
||||||
|
auto data = getFromSocket("/activewindow");
|
||||||
|
data = data.substr(data.find("size:") + 5);
|
||||||
|
data = data.substr(0, data.find('\n'));
|
||||||
|
|
||||||
|
Hyprutils::String::CVarList2 sizes(std::move(data), 0, ',');
|
||||||
|
|
||||||
|
EXPECT_MAX_DELTA(std::stoi(std::string{sizes[0]}), WIDTH2, 2);
|
||||||
|
EXPECT_MAX_DELTA(std::stoi(std::string{sizes[1]}), HEIGHT, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
NLog::log("{}Expecting 2 windows", Colors::YELLOW);
|
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
|
||||||
EXPECT(Tests::windowCount(), 2);
|
NLog::log("{}Expecting kitty_A size: {},{}", Colors::YELLOW, WIDTH_A_FINAL, HEIGHT);
|
||||||
|
|
||||||
|
{
|
||||||
|
auto data = getFromSocket("/activewindow");
|
||||||
|
data = data.substr(data.find("size:") + 5);
|
||||||
|
data = data.substr(0, data.find('\n'));
|
||||||
|
|
||||||
|
Hyprutils::String::CVarList2 sizes(std::move(data), 0, ',');
|
||||||
|
|
||||||
|
EXPECT_MAX_DELTA(std::stoi(std::string{sizes[0]}), WIDTH_A_FINAL, 2);
|
||||||
|
EXPECT_MAX_DELTA(std::stoi(std::string{sizes[1]}), HEIGHT, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (...) {
|
||||||
|
NLog::log("{}Exception thrown", Colors::RED);
|
||||||
|
EXPECT(false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/keyword dwindle:default_split_ratio 1"));
|
||||||
|
}
|
||||||
|
|
||||||
// open xeyes
|
// open xeyes
|
||||||
NLog::log("{}Spawning xeyes", Colors::YELLOW);
|
NLog::log("{}Spawning xeyes", Colors::YELLOW);
|
||||||
getFromSocket("/dispatch exec xeyes");
|
getFromSocket("/dispatch exec xeyes");
|
||||||
|
|
||||||
NLog::log("{}Keep checking if xeyes spawned", Colors::YELLOW);
|
NLog::log("{}Keep checking if xeyes spawned", Colors::YELLOW);
|
||||||
int counter = 0;
|
Tests::waitUntilWindowsN(3);
|
||||||
while (Tests::windowCount() != 3) {
|
|
||||||
counter++;
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
||||||
|
|
||||||
if (counter > 50) {
|
|
||||||
EXPECT(Tests::windowCount(), 3);
|
|
||||||
return !ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NLog::log("{}Expecting 3 windows", Colors::YELLOW);
|
NLog::log("{}Expecting 3 windows", Colors::YELLOW);
|
||||||
EXPECT(Tests::windowCount(), 3);
|
EXPECT(Tests::windowCount(), 3);
|
||||||
|
|
@ -92,6 +503,257 @@ static bool test() {
|
||||||
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
|
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
|
||||||
EXPECT(Tests::windowCount(), 0);
|
EXPECT(Tests::windowCount(), 0);
|
||||||
|
|
||||||
|
testSwapWindow();
|
||||||
|
|
||||||
|
getFromSocket("/dispatch workspace 1");
|
||||||
|
|
||||||
|
if (!testWindowFocusOnFullscreenConflict()) {
|
||||||
|
ret = 1;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
NLog::log("{}Testing spawning a floating window over a fullscreen window", Colors::YELLOW);
|
||||||
|
{
|
||||||
|
if (!spawnKitty("kitty_A"))
|
||||||
|
return false;
|
||||||
|
OK(getFromSocket("/dispatch fullscreen 0 set"));
|
||||||
|
EXPECT(Tests::windowCount(), 1);
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch exec [float] kitty"));
|
||||||
|
Tests::waitUntilWindowsN(2);
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:^kitty$"));
|
||||||
|
const auto focused1 = getFromSocket("/activewindow");
|
||||||
|
EXPECT_CONTAINS(focused1, "class: kitty\n");
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch killwindow activewindow"));
|
||||||
|
Tests::waitUntilWindowsN(1);
|
||||||
|
|
||||||
|
// The old window should be focused again
|
||||||
|
const auto focused2 = getFromSocket("/activewindow");
|
||||||
|
EXPECT_CONTAINS(focused2, "class: kitty_A\n");
|
||||||
|
|
||||||
|
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||||
|
Tests::killAllWindows();
|
||||||
|
}
|
||||||
|
|
||||||
|
NLog::log("{}Testing minsize/maxsize rules for tiled windows", Colors::YELLOW);
|
||||||
|
{
|
||||||
|
// Enable the config for testing, test max/minsize for tiled windows and centering
|
||||||
|
OK(getFromSocket("/keyword misc:size_limits_tiled 1"));
|
||||||
|
OK(getFromSocket("/keyword windowrule[kitty-max-rule]:match:class kitty_maxsize"));
|
||||||
|
OK(getFromSocket("/keyword windowrule[kitty-max-rule]:max_size 1500 500"));
|
||||||
|
OK(getFromSocket("r/keyword windowrule[kitty-max-rule]:min_size 1200 500"));
|
||||||
|
if (!spawnKitty("kitty_maxsize"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto dwindle = getFromSocket("/activewindow");
|
||||||
|
EXPECT_CONTAINS(dwindle, "size: 1500,500");
|
||||||
|
EXPECT_CONTAINS(dwindle, "at: 210,290");
|
||||||
|
|
||||||
|
if (!spawnKitty("kitty_maxsize"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/activewindow"), "size: 1200,500");
|
||||||
|
|
||||||
|
Tests::killAllWindows();
|
||||||
|
EXPECT(Tests::windowCount(), 0);
|
||||||
|
|
||||||
|
OK(getFromSocket("/keyword general:layout master"));
|
||||||
|
|
||||||
|
if (!spawnKitty("kitty_maxsize"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto master = getFromSocket("/activewindow");
|
||||||
|
EXPECT_CONTAINS(master, "size: 1500,500");
|
||||||
|
EXPECT_CONTAINS(master, "at: 210,290");
|
||||||
|
|
||||||
|
if (!spawnKitty("kitty_maxsize"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:kitty_maxsize"));
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/activewindow"), "size: 1200,500")
|
||||||
|
|
||||||
|
NLog::log("{}Reloading config", Colors::YELLOW);
|
||||||
|
OK(getFromSocket("/reload"));
|
||||||
|
Tests::killAllWindows();
|
||||||
|
EXPECT(Tests::windowCount(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
NLog::log("{}Testing window rules", Colors::YELLOW);
|
||||||
|
if (!spawnKitty("wr_kitty"))
|
||||||
|
return false;
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/activewindow");
|
||||||
|
const int SIZE = 200;
|
||||||
|
EXPECT_CONTAINS(str, "floating: 1");
|
||||||
|
EXPECT_CONTAINS(str, std::format("size: {},{}", SIZE, SIZE));
|
||||||
|
EXPECT_NOT_CONTAINS(str, "pinned: 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/keyword windowrule[wr-kitty-stuff]:opacity 0.5 0.5 override"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/getprop active opacity");
|
||||||
|
EXPECT_CONTAINS(str, "0.5");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/keyword windowrule[special-magic-kitty]:match:class magic_kitty"));
|
||||||
|
OK(getFromSocket("/keyword windowrule[special-magic-kitty]:workspace special:magic"));
|
||||||
|
|
||||||
|
if (!spawnKitty("magic_kitty"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/activewindow");
|
||||||
|
EXPECT_CONTAINS(str, "special:magic");
|
||||||
|
EXPECT_NOT_CONTAINS(str, "workspace: 9");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto str = getFromSocket("/monitors"); str.contains("magic)")) {
|
||||||
|
OK(getFromSocket("/dispatch togglespecialworkspace magic"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Tests::killAllWindows();
|
||||||
|
|
||||||
|
if (!spawnKitty("tag_kitty"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/activewindow");
|
||||||
|
EXPECT_CONTAINS(str, "floating: 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/reload"));
|
||||||
|
Tests::killAllWindows();
|
||||||
|
|
||||||
|
// test rules that overlap effects but don't overlap props
|
||||||
|
OK(getFromSocket("/keyword windowrule match:class overlap_kitty, border_size 0"));
|
||||||
|
OK(getFromSocket("/keyword windowrule match:fullscreen false, border_size 10"));
|
||||||
|
|
||||||
|
if (!spawnKitty("overlap_kitty"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/getprop active border_size");
|
||||||
|
EXPECT_CONTAINS(str, "10");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/reload"));
|
||||||
|
Tests::killAllWindows();
|
||||||
|
|
||||||
|
// test persistent_size between floating window launches
|
||||||
|
OK(getFromSocket("/keyword windowrule match:class persistent_size_kitty, persistent_size true, float true"));
|
||||||
|
|
||||||
|
if (!spawnKitty("persistent_size_kitty"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch resizeactive exact 600 400"))
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/activewindow");
|
||||||
|
EXPECT_CONTAINS(str, "size: 600,400");
|
||||||
|
EXPECT_CONTAINS(str, "floating: 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
Tests::killAllWindows();
|
||||||
|
|
||||||
|
if (!spawnKitty("persistent_size_kitty"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/activewindow");
|
||||||
|
EXPECT_CONTAINS(str, "size: 600,400");
|
||||||
|
EXPECT_CONTAINS(str, "floating: 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/reload"));
|
||||||
|
Tests::killAllWindows();
|
||||||
|
|
||||||
|
OK(getFromSocket("/keyword general:border_size 0"));
|
||||||
|
OK(getFromSocket("/keyword windowrule match:float true, border_size 10"));
|
||||||
|
|
||||||
|
if (!spawnKitty("border_kitty"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/getprop active border_size");
|
||||||
|
EXPECT_CONTAINS(str, "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch togglefloating"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/getprop active border_size");
|
||||||
|
EXPECT_CONTAINS(str, "10");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch togglefloating"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/getprop active border_size");
|
||||||
|
EXPECT_CONTAINS(str, "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/reload"));
|
||||||
|
Tests::killAllWindows();
|
||||||
|
|
||||||
|
// test expression rules
|
||||||
|
OK(getFromSocket("/keyword windowrule match:class expr_kitty, float yes, size monitor_w*0.5 monitor_h*0.5, move 20+(monitor_w*0.1) monitor_h*0.5"));
|
||||||
|
|
||||||
|
if (!spawnKitty("expr_kitty"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/activewindow");
|
||||||
|
EXPECT_CONTAINS(str, "floating: 1");
|
||||||
|
EXPECT_CONTAINS(str, "at: 212,540");
|
||||||
|
EXPECT_CONTAINS(str, "size: 960,540");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/reload"));
|
||||||
|
Tests::killAllWindows();
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:add_rule"));
|
||||||
|
OK(getFromSocket("/reload"));
|
||||||
|
|
||||||
|
OK(getFromSocket("/keyword windowrule match:class plugin_kitty, plugin_rule effect"));
|
||||||
|
|
||||||
|
if (!spawnKitty("plugin_kitty"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:check_rule"));
|
||||||
|
|
||||||
|
OK(getFromSocket("/reload"));
|
||||||
|
Tests::killAllWindows();
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:add_rule"));
|
||||||
|
OK(getFromSocket("/reload"));
|
||||||
|
|
||||||
|
OK(getFromSocket("/keyword windowrule[test-plugin-rule]:match:class plugin_kitty"));
|
||||||
|
OK(getFromSocket("/keyword windowrule[test-plugin-rule]:plugin_rule effect"));
|
||||||
|
|
||||||
|
if (!spawnKitty("plugin_kitty"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch plugin:test:check_rule"));
|
||||||
|
|
||||||
|
OK(getFromSocket("/reload"));
|
||||||
|
Tests::killAllWindows();
|
||||||
|
|
||||||
|
testGroupRules();
|
||||||
|
|
||||||
|
testMaximizeSize();
|
||||||
|
|
||||||
|
NLog::log("{}Reloading config", Colors::YELLOW);
|
||||||
|
OK(getFromSocket("/reload"));
|
||||||
|
|
||||||
|
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||||
|
Tests::killAllWindows();
|
||||||
|
|
||||||
|
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
|
||||||
|
EXPECT(Tests::windowCount(), 0);
|
||||||
|
|
||||||
return !ret;
|
return !ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <hyprutils/os/Process.hpp>
|
#include <hyprutils/os/Process.hpp>
|
||||||
#include <hyprutils/memory/WeakPtr.hpp>
|
#include <hyprutils/memory/WeakPtr.hpp>
|
||||||
|
#include <hyprutils/utils/ScopeGuard.hpp>
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include "../shared.hpp"
|
#include "../shared.hpp"
|
||||||
|
|
@ -14,16 +15,117 @@ static int ret = 0;
|
||||||
|
|
||||||
using namespace Hyprutils::OS;
|
using namespace Hyprutils::OS;
|
||||||
using namespace Hyprutils::Memory;
|
using namespace Hyprutils::Memory;
|
||||||
|
using namespace Hyprutils::Utils;
|
||||||
|
|
||||||
#define UP CUniquePointer
|
#define UP CUniquePointer
|
||||||
#define SP CSharedPointer
|
#define SP CSharedPointer
|
||||||
|
|
||||||
|
static bool testAsymmetricGaps() {
|
||||||
|
NLog::log("{}Testing asymmetric gap splits", Colors::YELLOW);
|
||||||
|
{
|
||||||
|
|
||||||
|
CScopeGuard guard = {[&]() {
|
||||||
|
NLog::log("{}Cleaning up asymmetric gap test", Colors::YELLOW);
|
||||||
|
Tests::killAllWindows();
|
||||||
|
OK(getFromSocket("/reload"));
|
||||||
|
}};
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch workspace name:gap_split_test"));
|
||||||
|
OK(getFromSocket("r/keyword general:gaps_in 0"));
|
||||||
|
OK(getFromSocket("r/keyword general:border_size 0"));
|
||||||
|
OK(getFromSocket("r/keyword dwindle:split_width_multiplier 1.0"));
|
||||||
|
OK(getFromSocket("r/keyword workspace name:gap_split_test,gapsout:0 1000 0 0"));
|
||||||
|
|
||||||
|
NLog::log("{}Testing default split (force_split = 0)", Colors::YELLOW);
|
||||||
|
OK(getFromSocket("r/keyword dwindle:force_split 0"));
|
||||||
|
|
||||||
|
if (!Tests::spawnKitty("gaps_kitty_A") || !Tests::spawnKitty("gaps_kitty_B"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
NLog::log("{}Expecting vertical split (B below A)", Colors::YELLOW);
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_A"));
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0");
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_B"));
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,540");
|
||||||
|
|
||||||
|
Tests::killAllWindows();
|
||||||
|
EXPECT(Tests::windowCount(), 0);
|
||||||
|
|
||||||
|
NLog::log("{}Testing force_split = 1", Colors::YELLOW);
|
||||||
|
OK(getFromSocket("r/keyword dwindle:force_split 1"));
|
||||||
|
|
||||||
|
if (!Tests::spawnKitty("gaps_kitty_A") || !Tests::spawnKitty("gaps_kitty_B"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
NLog::log("{}Expecting vertical split (B above A)", Colors::YELLOW);
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_B"));
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0");
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_A"));
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,540");
|
||||||
|
|
||||||
|
NLog::log("{}Expecting horizontal split (C left of B)", Colors::YELLOW);
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_B"));
|
||||||
|
|
||||||
|
if (!Tests::spawnKitty("gaps_kitty_C"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_C"));
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0");
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_B"));
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 460,0");
|
||||||
|
|
||||||
|
Tests::killAllWindows();
|
||||||
|
EXPECT(Tests::windowCount(), 0);
|
||||||
|
|
||||||
|
NLog::log("{}Testing force_split = 2", Colors::YELLOW);
|
||||||
|
OK(getFromSocket("r/keyword dwindle:force_split 2"));
|
||||||
|
|
||||||
|
if (!Tests::spawnKitty("gaps_kitty_A") || !Tests::spawnKitty("gaps_kitty_B"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
NLog::log("{}Expecting vertical split (B below A)", Colors::YELLOW);
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_A"));
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0");
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_B"));
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,540");
|
||||||
|
|
||||||
|
NLog::log("{}Expecting horizontal split (C right of A)", Colors::YELLOW);
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_A"));
|
||||||
|
|
||||||
|
if (!Tests::spawnKitty("gaps_kitty_C"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_A"));
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0");
|
||||||
|
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_C"));
|
||||||
|
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 460,0");
|
||||||
|
}
|
||||||
|
|
||||||
|
// kill all
|
||||||
|
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||||
|
Tests::killAllWindows();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static bool test() {
|
static bool test() {
|
||||||
NLog::log("{}Testing workspaces", Colors::GREEN);
|
NLog::log("{}Testing workspaces", Colors::GREEN);
|
||||||
|
|
||||||
|
EXPECT(Tests::windowCount(), 0);
|
||||||
|
|
||||||
// test on workspace "window"
|
// test on workspace "window"
|
||||||
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
|
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
|
||||||
OK(getFromSocket("/dispatch workspace 1"));
|
getFromSocket("/dispatch workspace 1");
|
||||||
|
|
||||||
|
NLog::log("{}Checking persistent no-mon", Colors::YELLOW);
|
||||||
|
OK(getFromSocket("r/keyword workspace 966,persistent:1"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/workspaces");
|
||||||
|
EXPECT_CONTAINS(str, "workspace ID 966 (966)");
|
||||||
|
}
|
||||||
|
|
||||||
|
OK(getFromSocket("/reload"));
|
||||||
|
|
||||||
NLog::log("{}Spawning kittyProc on ws 1", Colors::YELLOW);
|
NLog::log("{}Spawning kittyProc on ws 1", Colors::YELLOW);
|
||||||
auto kittyProcA = Tests::spawnKitty();
|
auto kittyProcA = Tests::spawnKitty();
|
||||||
|
|
@ -65,6 +167,11 @@ static bool test() {
|
||||||
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
|
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
|
||||||
OK(getFromSocket("/dispatch workspace 1"));
|
OK(getFromSocket("/dispatch workspace 1"));
|
||||||
|
|
||||||
|
{
|
||||||
|
auto str = getFromSocket("/workspaces");
|
||||||
|
EXPECT_NOT_CONTAINS(str, "workspace ID 2 (2)");
|
||||||
|
}
|
||||||
|
|
||||||
NLog::log("{}Switching to workspace m+1", Colors::YELLOW);
|
NLog::log("{}Switching to workspace m+1", Colors::YELLOW);
|
||||||
OK(getFromSocket("/dispatch workspace m+1"));
|
OK(getFromSocket("/dispatch workspace m+1"));
|
||||||
|
|
||||||
|
|
@ -210,7 +317,7 @@ static bool test() {
|
||||||
EXPECT_CONTAINS(str, "special:HELLO");
|
EXPECT_CONTAINS(str, "special:HELLO");
|
||||||
}
|
}
|
||||||
|
|
||||||
// no OK: will err (it shouldnt prolly but oh well)
|
// no OK: will err (it shouldn't prolly but oh well)
|
||||||
getFromSocket("/dispatch workspace 3");
|
getFromSocket("/dispatch workspace 3");
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
@ -342,6 +449,8 @@ static bool test() {
|
||||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||||
Tests::killAllWindows();
|
Tests::killAllWindows();
|
||||||
|
|
||||||
|
testAsymmetricGaps();
|
||||||
|
|
||||||
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
|
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
|
||||||
EXPECT(Tests::windowCount(), 0);
|
EXPECT(Tests::windowCount(), 0);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,3 +19,13 @@ bool testPlugin() {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool testVkb() {
|
||||||
|
const auto RESPONSE = getFromSocket("/dispatch plugin:test:vkb");
|
||||||
|
|
||||||
|
if (RESPONSE != "ok") {
|
||||||
|
NLog::log("{}Vkb tests failed, tests returned:\n{}{}", Colors::RED, Colors::RESET, RESPONSE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
bool testPlugin();
|
bool testPlugin();
|
||||||
|
bool testVkb();
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,15 @@
|
||||||
using namespace Hyprutils::OS;
|
using namespace Hyprutils::OS;
|
||||||
using namespace Hyprutils::Memory;
|
using namespace Hyprutils::Memory;
|
||||||
|
|
||||||
CUniquePointer<CProcess> Tests::spawnKitty(const std::string& class_) {
|
CUniquePointer<CProcess> Tests::spawnKitty(const std::string& class_, const std::vector<std::string> args) {
|
||||||
const auto COUNT_BEFORE = windowCount();
|
const auto COUNT_BEFORE = windowCount();
|
||||||
|
|
||||||
CUniquePointer<CProcess> kitty = makeUnique<CProcess>("kitty", class_.empty() ? std::vector<std::string>{} : std::vector<std::string>{"--class", class_});
|
std::vector<std::string> programArgs = args;
|
||||||
|
if (!class_.empty()) {
|
||||||
|
programArgs.insert(programArgs.begin(), "--class");
|
||||||
|
programArgs.insert(programArgs.begin() + 1, class_);
|
||||||
|
}
|
||||||
|
CUniquePointer<CProcess> kitty = makeUnique<CProcess>("kitty", programArgs);
|
||||||
kitty->addEnv("WAYLAND_DISPLAY", WLDISPLAY);
|
kitty->addEnv("WAYLAND_DISPLAY", WLDISPLAY);
|
||||||
kitty->runAsync();
|
kitty->runAsync();
|
||||||
|
|
||||||
|
|
@ -49,7 +54,7 @@ int Tests::countOccurrences(const std::string& in, const std::string& what) {
|
||||||
auto pos = in.find(what);
|
auto pos = in.find(what);
|
||||||
while (pos != std::string::npos) {
|
while (pos != std::string::npos) {
|
||||||
cnt++;
|
cnt++;
|
||||||
pos = in.find(what, pos + what.length() - 1);
|
pos = in.find(what, pos + what.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
return cnt;
|
return cnt;
|
||||||
|
|
@ -90,3 +95,13 @@ void Tests::waitUntilWindowsN(int n) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string Tests::execAndGet(const std::string& cmd) {
|
||||||
|
CProcess proc("/bin/sh", {"-c", cmd});
|
||||||
|
|
||||||
|
if (!proc.runSync()) {
|
||||||
|
return "error";
|
||||||
|
}
|
||||||
|
|
||||||
|
return proc.stdOut();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,11 @@
|
||||||
|
|
||||||
//NOLINTNEXTLINE
|
//NOLINTNEXTLINE
|
||||||
namespace Tests {
|
namespace Tests {
|
||||||
Hyprutils::Memory::CUniquePointer<Hyprutils::OS::CProcess> spawnKitty(const std::string& class_ = "");
|
Hyprutils::Memory::CUniquePointer<Hyprutils::OS::CProcess> spawnKitty(const std::string& class_ = "", const std::vector<std::string> args = {});
|
||||||
bool processAlive(pid_t pid);
|
bool processAlive(pid_t pid);
|
||||||
int windowCount();
|
int windowCount();
|
||||||
int countOccurrences(const std::string& in, const std::string& what);
|
int countOccurrences(const std::string& in, const std::string& what);
|
||||||
bool killAllWindows();
|
bool killAllWindows();
|
||||||
void waitUntilWindowsN(int n);
|
void waitUntilWindowsN(int n);
|
||||||
|
std::string execAndGet(const std::string& cmd);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ monitor=HEADLESS-3,1920x1080@60,auto-right,1
|
||||||
monitor=HEADLESS-4,1920x1080@60,auto-right,1
|
monitor=HEADLESS-4,1920x1080@60,auto-right,1
|
||||||
monitor=HEADLESS-5,1920x1080@60,auto-right,1
|
monitor=HEADLESS-5,1920x1080@60,auto-right,1
|
||||||
monitor=HEADLESS-6,1920x1080@60,auto-right,1
|
monitor=HEADLESS-6,1920x1080@60,auto-right,1
|
||||||
|
monitor=HEADLESS-PERSISTENT-TEST,1920x1080@60,auto-right,1
|
||||||
|
|
||||||
monitor=,disabled
|
monitor=,disabled
|
||||||
|
|
||||||
|
|
@ -45,6 +46,7 @@ $menu = wofi --show drun
|
||||||
# Autostart necessary processes (like notifications daemons, status bars, etc.)
|
# Autostart necessary processes (like notifications daemons, status bars, etc.)
|
||||||
# Or execute your favorite apps at launch like this:
|
# Or execute your favorite apps at launch like this:
|
||||||
|
|
||||||
|
exec-once = sleep 0 # Terminates very quickly
|
||||||
# exec-once = $terminal
|
# exec-once = $terminal
|
||||||
# exec-once = nm-applet &
|
# exec-once = nm-applet &
|
||||||
# exec-once = waybar & hyprpaper & firefox
|
# exec-once = waybar & hyprpaper & firefox
|
||||||
|
|
@ -150,6 +152,11 @@ animations {
|
||||||
animation = workspacesOut, 1, 1.94, almostLinear, fade
|
animation = workspacesOut, 1, 1.94, almostLinear, fade
|
||||||
}
|
}
|
||||||
|
|
||||||
|
device {
|
||||||
|
name = test-mouse-1
|
||||||
|
enabled = true
|
||||||
|
}
|
||||||
|
|
||||||
# Ref https://wiki.hyprland.org/Configuring/Workspace-Rules/
|
# Ref https://wiki.hyprland.org/Configuring/Workspace-Rules/
|
||||||
# "Smart gaps" / "No gaps when only"
|
# "Smart gaps" / "No gaps when only"
|
||||||
# uncomment all if you wish to use that.
|
# uncomment all if you wish to use that.
|
||||||
|
|
@ -164,6 +171,7 @@ animations {
|
||||||
dwindle {
|
dwindle {
|
||||||
pseudotile = true # Master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below
|
pseudotile = true # Master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below
|
||||||
preserve_split = true # You probably want this
|
preserve_split = true # You probably want this
|
||||||
|
split_bias = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# See https://wiki.hyprland.org/Configuring/Master-Layout/ for more
|
# See https://wiki.hyprland.org/Configuring/Master-Layout/ for more
|
||||||
|
|
@ -201,7 +209,7 @@ input {
|
||||||
|
|
||||||
# https://wiki.hyprland.org/Configuring/Variables/#gestures
|
# https://wiki.hyprland.org/Configuring/Variables/#gestures
|
||||||
gestures {
|
gestures {
|
||||||
workspace_swipe = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Example per-device config
|
# Example per-device config
|
||||||
|
|
@ -211,6 +219,10 @@ device {
|
||||||
sensitivity = -0.5
|
sensitivity = -0.5
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug {
|
||||||
|
disable_logs = false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
###################
|
###################
|
||||||
### KEYBINDINGS ###
|
### KEYBINDINGS ###
|
||||||
|
|
@ -285,29 +297,104 @@ bindl = , XF86AudioPause, exec, playerctl play-pause
|
||||||
bindl = , XF86AudioPlay, exec, playerctl play-pause
|
bindl = , XF86AudioPlay, exec, playerctl play-pause
|
||||||
bindl = , XF86AudioPrev, exec, playerctl previous
|
bindl = , XF86AudioPrev, exec, playerctl previous
|
||||||
|
|
||||||
|
bind = $mainMod, u, submap, submap1
|
||||||
|
|
||||||
|
submap = submap1
|
||||||
|
bind = , u, submap, submap2
|
||||||
|
bind = , i, submap, submap3
|
||||||
|
bind = , o, exec, $terminal
|
||||||
|
bind = , p, submap, reset
|
||||||
|
|
||||||
|
submap = submap2, submap1
|
||||||
|
bind = , o, exec, $terminal
|
||||||
|
|
||||||
|
submap = submap3, reset
|
||||||
|
bind = , o, exec, $terminal
|
||||||
|
|
||||||
|
submap = reset
|
||||||
|
|
||||||
|
|
||||||
##############################
|
##############################
|
||||||
### WINDOWS AND WORKSPACES ###
|
### WINDOWS AND WORKSPACES ###
|
||||||
##############################
|
##############################
|
||||||
|
|
||||||
# See https://wiki.hyprland.org/Configuring/Window-Rules/ for more
|
windowrule {
|
||||||
# See https://wiki.hyprland.org/Configuring/Workspace-Rules/ for workspace rules
|
# Ignore maximize requests from apps. You'll probably like this.
|
||||||
|
name = suppress-maximize-events
|
||||||
|
match:class = .*
|
||||||
|
|
||||||
# Example windowrule v1
|
suppress_event = maximize
|
||||||
# windowrule = float, ^(kitty)$
|
}
|
||||||
|
|
||||||
# Example windowrule v2
|
windowrule {
|
||||||
# windowrulev2 = float,class:^(kitty)$,title:^(kitty)$
|
# Fix some dragging issues with XWayland
|
||||||
|
name = fix-xwayland-drags
|
||||||
|
match:class = ^$
|
||||||
|
match:title = ^$
|
||||||
|
match:xwayland = true
|
||||||
|
match:float = true
|
||||||
|
match:fullscreen = false
|
||||||
|
match:pin = false
|
||||||
|
|
||||||
# Ignore maximize requests from apps. You'll probably like this.
|
no_focus = true
|
||||||
windowrulev2 = suppressevent maximize, class:.*
|
}
|
||||||
|
|
||||||
# Fix some dragging issues with XWayland
|
|
||||||
windowrulev2 = nofocus,class:^$,title:^$,xwayland:1,floating:1,fullscreen:0,pinned:0
|
|
||||||
|
|
||||||
# Workspace "windows" is a smart gaps one
|
|
||||||
workspace = n[s:window] w[tv1], gapsout:0, gapsin:0
|
workspace = n[s:window] w[tv1], gapsout:0, gapsin:0
|
||||||
workspace = n[s:window] f[1], gapsout:0, gapsin:0
|
workspace = n[s:window] f[1], gapsout:0, gapsin:0
|
||||||
windowrulev2 = bordersize 0, floating:0, onworkspace:n[s:window] w[tv1]
|
|
||||||
windowrulev2 = rounding 0, floating:0, onworkspace:n[s:window] w[tv1]
|
windowrule {
|
||||||
windowrulev2 = bordersize 0, floating:0, onworkspace:n[s:window] f[1]
|
name = smart-gaps-1
|
||||||
windowrulev2 = rounding 0, floating:0, onworkspace:n[s:window] f[1]
|
match:float = false
|
||||||
|
match:workspace = n[s:window] w[tv1]
|
||||||
|
|
||||||
|
border_size = 0
|
||||||
|
rounding = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
windowrule {
|
||||||
|
name = smart-gaps-2
|
||||||
|
match:float = false
|
||||||
|
match:workspace = n[s:window] f[1]
|
||||||
|
|
||||||
|
border_size = 0
|
||||||
|
rounding = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
windowrule {
|
||||||
|
name = wr-kitty-stuff
|
||||||
|
match:class = wr_kitty
|
||||||
|
|
||||||
|
float = true
|
||||||
|
size = 200 200
|
||||||
|
pin = false
|
||||||
|
}
|
||||||
|
|
||||||
|
windowrule {
|
||||||
|
name = tagged-kitty-floats
|
||||||
|
match:tag = tag_kitty
|
||||||
|
|
||||||
|
float = true
|
||||||
|
}
|
||||||
|
|
||||||
|
windowrule {
|
||||||
|
name = static-kitty-tag
|
||||||
|
match:class = tag_kitty
|
||||||
|
|
||||||
|
tag = +tag_kitty
|
||||||
|
}
|
||||||
|
|
||||||
|
gesture = 3, left, dispatcher, exec, kitty
|
||||||
|
gesture = 3, right, float
|
||||||
|
gesture = 3, up, close
|
||||||
|
gesture = 3, down, fullscreen
|
||||||
|
|
||||||
|
gesture = 3, down, mod:ALT, float
|
||||||
|
|
||||||
|
gesture = 3, horizontal, mod:ALT, workspace
|
||||||
|
|
||||||
|
gesture = 5, up, dispatcher, sendshortcut, , e, activewindow
|
||||||
|
gesture = 5, down, dispatcher, sendshortcut, , x, activewindow
|
||||||
|
gesture = 5, left, dispatcher, sendshortcut, , i, activewindow
|
||||||
|
gesture = 5, right, dispatcher, sendshortcut, , t, activewindow
|
||||||
|
gesture = 4, right, dispatcher, sendshortcut, , return, activewindow
|
||||||
|
gesture = 4, left, dispatcher, movecursortocorner, 1
|
||||||
|
|
|
||||||
125
meson.build
125
meson.build
|
|
@ -1,125 +0,0 @@
|
||||||
project(
|
|
||||||
'Hyprland',
|
|
||||||
'cpp',
|
|
||||||
'c',
|
|
||||||
version: run_command('cat', join_paths(meson.project_source_root(), 'VERSION'), check: true).stdout().strip(),
|
|
||||||
default_options: [
|
|
||||||
'warning_level=2',
|
|
||||||
'default_library=static',
|
|
||||||
'optimization=3',
|
|
||||||
'buildtype=release',
|
|
||||||
'debug=false',
|
|
||||||
'cpp_std=c++26',
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
datarootdir = '-DDATAROOTDIR="' + get_option('prefix') / get_option('datadir') + '"'
|
|
||||||
add_project_arguments(
|
|
||||||
[
|
|
||||||
'-Wno-unused-parameter',
|
|
||||||
'-Wno-unused-value',
|
|
||||||
'-Wno-missing-field-initializers',
|
|
||||||
'-Wno-narrowing',
|
|
||||||
'-Wno-pointer-arith', datarootdir,
|
|
||||||
'-DHYPRLAND_VERSION="' + meson.project_version() + '"',
|
|
||||||
],
|
|
||||||
language: 'cpp',
|
|
||||||
)
|
|
||||||
|
|
||||||
cpp_compiler = meson.get_compiler('cpp')
|
|
||||||
if cpp_compiler.check_header('execinfo.h')
|
|
||||||
add_project_arguments('-DHAS_EXECINFO', language: 'cpp')
|
|
||||||
endif
|
|
||||||
|
|
||||||
aquamarine = dependency('aquamarine', version: '>=0.9.0')
|
|
||||||
hyprcursor = dependency('hyprcursor', version: '>=0.1.7')
|
|
||||||
hyprgraphics = dependency('hyprgraphics', version: '>= 0.1.3')
|
|
||||||
hyprlang = dependency('hyprlang', version: '>= 0.3.2')
|
|
||||||
hyprutils = dependency('hyprutils', version: '>= 0.8.1')
|
|
||||||
aquamarine_version_list = aquamarine.version().split('.')
|
|
||||||
add_project_arguments(['-DAQUAMARINE_VERSION="@0@"'.format(aquamarine.version())], language: 'cpp')
|
|
||||||
add_project_arguments(['-DAQUAMARINE_VERSION_MAJOR=@0@'.format(aquamarine_version_list.get(0))], language: 'cpp')
|
|
||||||
add_project_arguments(['-DAQUAMARINE_VERSION_MINOR=@0@'.format(aquamarine_version_list.get(1))], language: 'cpp')
|
|
||||||
add_project_arguments(['-DAQUAMARINE_VERSION_PATCH=@0@'.format(aquamarine_version_list.get(2))], language: 'cpp')
|
|
||||||
add_project_arguments(['-DHYPRCURSOR_VERSION="@0@"'.format(hyprcursor.version())], language: 'cpp')
|
|
||||||
add_project_arguments(['-DHYPRGRAPHICS_VERSION="@0@"'.format(hyprgraphics.version())], language: 'cpp')
|
|
||||||
add_project_arguments(['-DHYPRLANG_VERSION="@0@"'.format(hyprlang.version())], language: 'cpp')
|
|
||||||
add_project_arguments(['-DHYPRUTILS_VERSION="@0@"'.format(hyprutils.version())], language: 'cpp')
|
|
||||||
|
|
||||||
xcb_dep = dependency('xcb', required: get_option('xwayland'))
|
|
||||||
xcb_composite_dep = dependency('xcb-composite', required: get_option('xwayland'))
|
|
||||||
xcb_errors_dep = dependency('xcb-errors', required: get_option('xwayland'))
|
|
||||||
xcb_icccm_dep = dependency('xcb-icccm', required: get_option('xwayland'))
|
|
||||||
xcb_render_dep = dependency('xcb-render', required: get_option('xwayland'))
|
|
||||||
xcb_res_dep = dependency('xcb-res', required: get_option('xwayland'))
|
|
||||||
xcb_xfixes_dep = dependency('xcb-xfixes', required: get_option('xwayland'))
|
|
||||||
|
|
||||||
gio_dep = dependency('gio-2.0', required: true)
|
|
||||||
|
|
||||||
if not xcb_dep.found()
|
|
||||||
add_project_arguments('-DNO_XWAYLAND', language: 'cpp')
|
|
||||||
endif
|
|
||||||
|
|
||||||
backtrace_dep = cpp_compiler.find_library('execinfo', required: false)
|
|
||||||
epoll_dep = dependency('epoll-shim', required: false) # timerfd on BSDs
|
|
||||||
inotify_dep = dependency('libinotify', required: false) # inotify on BSDs
|
|
||||||
|
|
||||||
re2 = dependency('re2', required: true)
|
|
||||||
|
|
||||||
# Handle options
|
|
||||||
systemd_option = get_option('systemd')
|
|
||||||
systemd = dependency('systemd', required: systemd_option)
|
|
||||||
systemd_option.enable_auto_if(systemd.found())
|
|
||||||
|
|
||||||
if (systemd_option.enabled())
|
|
||||||
message('Enabling systemd integration')
|
|
||||||
add_project_arguments('-DUSES_SYSTEMD', language: 'cpp')
|
|
||||||
subdir('systemd')
|
|
||||||
endif
|
|
||||||
|
|
||||||
if get_option('buildtype') == 'debug'
|
|
||||||
add_project_arguments('-DHYPRLAND_DEBUG', language: 'cpp')
|
|
||||||
endif
|
|
||||||
|
|
||||||
# Generate hyprland version and populate version.h
|
|
||||||
run_command('sh', '-c', 'scripts/generateVersion.sh', check: true)
|
|
||||||
# Make shader files includable
|
|
||||||
run_command('sh', '-c', 'scripts/generateShaderIncludes.sh', check: true)
|
|
||||||
|
|
||||||
# Install headers
|
|
||||||
globber = run_command('find', 'src', '-name', '*.h*', '-o', '-name', '*.inc', check: true)
|
|
||||||
headers = globber.stdout().strip().split('\n')
|
|
||||||
foreach file : headers
|
|
||||||
install_headers(file, subdir: 'hyprland', preserve_path: true)
|
|
||||||
endforeach
|
|
||||||
|
|
||||||
tracy = dependency('tracy', static: true, required: get_option('tracy_enable'))
|
|
||||||
|
|
||||||
if get_option('tracy_enable') and get_option('buildtype') != 'debugoptimized'
|
|
||||||
warning('Profiling builds should set -- buildtype = debugoptimized')
|
|
||||||
endif
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
subdir('protocols')
|
|
||||||
subdir('src')
|
|
||||||
subdir('hyprctl')
|
|
||||||
subdir('assets')
|
|
||||||
subdir('example')
|
|
||||||
subdir('docs')
|
|
||||||
|
|
||||||
if get_option('hyprpm').enabled()
|
|
||||||
subdir('hyprpm/src')
|
|
||||||
endif
|
|
||||||
|
|
||||||
# Generate hyprland.pc
|
|
||||||
pkg_install_dir = join_paths(get_option('datadir'), 'pkgconfig')
|
|
||||||
|
|
||||||
import('pkgconfig').generate(
|
|
||||||
name: 'Hyprland',
|
|
||||||
filebase: 'hyprland',
|
|
||||||
url: 'https://github.com/hyprwm/Hyprland',
|
|
||||||
description: 'Hyprland header files',
|
|
||||||
install_dir: pkg_install_dir,
|
|
||||||
subdirs: ['', 'hyprland/protocols', 'hyprland'],
|
|
||||||
)
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
option('xwayland', type: 'feature', value: 'auto', description: 'Enable support for X11 applications')
|
|
||||||
option('systemd', type: 'feature', value: 'auto', description: 'Enable systemd integration')
|
|
||||||
option('uwsm', type: 'feature', value: 'enabled', description: 'Enable uwsm integration (only if systemd is enabled)')
|
|
||||||
option('hyprpm', type: 'feature', value: 'enabled', description: 'Enable hyprpm')
|
|
||||||
option('tracy_enable', type: 'boolean', value: false , description: 'Enable profiling')
|
|
||||||
|
|
@ -6,21 +6,21 @@
|
||||||
pkgconf,
|
pkgconf,
|
||||||
makeWrapper,
|
makeWrapper,
|
||||||
cmake,
|
cmake,
|
||||||
meson,
|
|
||||||
ninja,
|
|
||||||
aquamarine,
|
aquamarine,
|
||||||
binutils,
|
binutils,
|
||||||
cairo,
|
cairo,
|
||||||
epoll-shim,
|
epoll-shim,
|
||||||
git,
|
git,
|
||||||
glaze,
|
glaze-hyprland,
|
||||||
|
gtest,
|
||||||
hyprcursor,
|
hyprcursor,
|
||||||
hyprgraphics,
|
hyprgraphics,
|
||||||
hyprland-protocols,
|
hyprland-protocols,
|
||||||
hyprland-qtutils,
|
hyprland-guiutils,
|
||||||
hyprlang,
|
hyprlang,
|
||||||
hyprutils,
|
hyprutils,
|
||||||
hyprwayland-scanner,
|
hyprwayland-scanner,
|
||||||
|
hyprwire,
|
||||||
libGL,
|
libGL,
|
||||||
libdrm,
|
libdrm,
|
||||||
libexecinfo,
|
libexecinfo,
|
||||||
|
|
@ -28,6 +28,7 @@
|
||||||
libxkbcommon,
|
libxkbcommon,
|
||||||
libuuid,
|
libuuid,
|
||||||
libgbm,
|
libgbm,
|
||||||
|
muparser,
|
||||||
pango,
|
pango,
|
||||||
pciutils,
|
pciutils,
|
||||||
re2,
|
re2,
|
||||||
|
|
@ -40,6 +41,7 @@
|
||||||
xorg,
|
xorg,
|
||||||
xwayland,
|
xwayland,
|
||||||
debug ? false,
|
debug ? false,
|
||||||
|
withTests ? false,
|
||||||
enableXWayland ? true,
|
enableXWayland ? true,
|
||||||
withSystemd ? lib.meta.availableOn stdenv.hostPlatform systemd,
|
withSystemd ? lib.meta.availableOn stdenv.hostPlatform systemd,
|
||||||
wrapRuntimeDeps ? true,
|
wrapRuntimeDeps ? true,
|
||||||
|
|
@ -52,12 +54,13 @@
|
||||||
nvidiaPatches ? false,
|
nvidiaPatches ? false,
|
||||||
hidpiXWayland ? false,
|
hidpiXWayland ? false,
|
||||||
legacyRenderer ? false,
|
legacyRenderer ? false,
|
||||||
|
withHyprtester ? false,
|
||||||
}: let
|
}: let
|
||||||
inherit (builtins) foldl' readFile;
|
inherit (builtins) foldl' readFile;
|
||||||
inherit (lib.asserts) assertMsg;
|
inherit (lib.asserts) assertMsg;
|
||||||
inherit (lib.attrsets) mapAttrsToList;
|
inherit (lib.attrsets) mapAttrsToList;
|
||||||
inherit (lib.lists) flatten concatLists optional optionals;
|
inherit (lib.lists) flatten concatLists optional optionals;
|
||||||
inherit (lib.strings) makeBinPath optionalString mesonBool mesonEnable trim;
|
inherit (lib.strings) makeBinPath optionalString cmakeBool trim;
|
||||||
fs = lib.fileset;
|
fs = lib.fileset;
|
||||||
|
|
||||||
adapters = flatten [
|
adapters = flatten [
|
||||||
|
|
@ -71,9 +74,10 @@ in
|
||||||
assert assertMsg (!enableNvidiaPatches) "The option `enableNvidiaPatches` has been removed.";
|
assert assertMsg (!enableNvidiaPatches) "The option `enableNvidiaPatches` has been removed.";
|
||||||
assert assertMsg (!hidpiXWayland) "The option `hidpiXWayland` has been removed. Please refer https://wiki.hypr.land/Configuring/XWayland";
|
assert assertMsg (!hidpiXWayland) "The option `hidpiXWayland` has been removed. Please refer https://wiki.hypr.land/Configuring/XWayland";
|
||||||
assert assertMsg (!legacyRenderer) "The option `legacyRenderer` has been removed. Legacy renderer is no longer supported.";
|
assert assertMsg (!legacyRenderer) "The option `legacyRenderer` has been removed. Legacy renderer is no longer supported.";
|
||||||
|
assert assertMsg (!withHyprtester) "The option `withHyprtester` has been removed. Hyprtester is always built now.";
|
||||||
customStdenv.mkDerivation (finalAttrs: {
|
customStdenv.mkDerivation (finalAttrs: {
|
||||||
pname = "hyprland${optionalString debug "-debug"}";
|
pname = "hyprland${optionalString debug "-debug"}";
|
||||||
inherit version;
|
inherit version withTests;
|
||||||
|
|
||||||
src = fs.toSource {
|
src = fs.toSource {
|
||||||
root = ../.;
|
root = ../.;
|
||||||
|
|
@ -81,22 +85,23 @@ in
|
||||||
fs.intersection
|
fs.intersection
|
||||||
# allows non-flake builds to only include files tracked by git
|
# allows non-flake builds to only include files tracked by git
|
||||||
(fs.gitTracked ../.)
|
(fs.gitTracked ../.)
|
||||||
(fs.unions [
|
(fs.unions (flatten [
|
||||||
../assets/hyprland-portals.conf
|
../assets/hyprland-portals.conf
|
||||||
../assets/install
|
../assets/install
|
||||||
../hyprctl
|
../hyprctl
|
||||||
../hyprland.pc.in
|
../hyprland.pc.in
|
||||||
../LICENSE
|
../LICENSE
|
||||||
../meson_options.txt
|
|
||||||
../protocols
|
../protocols
|
||||||
../src
|
../src
|
||||||
|
../start
|
||||||
../systemd
|
../systemd
|
||||||
../VERSION
|
../VERSION
|
||||||
(fs.fileFilter (file: file.hasExt "1") ../docs)
|
(fs.fileFilter (file: file.hasExt "1") ../docs)
|
||||||
(fs.fileFilter (file: file.hasExt "conf" || file.hasExt "desktop") ../example)
|
(fs.fileFilter (file: file.hasExt "conf" || file.hasExt "desktop") ../example)
|
||||||
(fs.fileFilter (file: file.hasExt "sh") ../scripts)
|
(fs.fileFilter (file: file.hasExt "sh") ../scripts)
|
||||||
(fs.fileFilter (file: file.name == "meson.build") ../.)
|
(fs.fileFilter (file: file.name == "CMakeLists.txt") ../.)
|
||||||
]);
|
(optional withTests [../tests ../hyprtester])
|
||||||
|
]));
|
||||||
};
|
};
|
||||||
|
|
||||||
postPatch = ''
|
postPatch = ''
|
||||||
|
|
@ -107,11 +112,13 @@ in
|
||||||
sed -i "s#@PREFIX@/##g" hyprland.pc.in
|
sed -i "s#@PREFIX@/##g" hyprland.pc.in
|
||||||
'';
|
'';
|
||||||
|
|
||||||
COMMITS = revCount;
|
env = {
|
||||||
DATE = date;
|
GIT_COMMITS = revCount;
|
||||||
DIRTY = optionalString (commit == "") "dirty";
|
GIT_COMMIT_DATE = date;
|
||||||
HASH = commit;
|
GIT_COMMIT_HASH = commit;
|
||||||
TAG = "v${trim (readFile "${finalAttrs.src}/VERSION")}";
|
GIT_DIRTY = if (commit == "") then "clean" else "dirty";
|
||||||
|
GIT_TAG = "v${trim (readFile "${finalAttrs.src}/VERSION")}";
|
||||||
|
};
|
||||||
|
|
||||||
depsBuildBuild = [
|
depsBuildBuild = [
|
||||||
pkg-config
|
pkg-config
|
||||||
|
|
@ -119,10 +126,9 @@ in
|
||||||
|
|
||||||
nativeBuildInputs = [
|
nativeBuildInputs = [
|
||||||
hyprwayland-scanner
|
hyprwayland-scanner
|
||||||
|
hyprwire
|
||||||
makeWrapper
|
makeWrapper
|
||||||
meson
|
cmake
|
||||||
ninja
|
|
||||||
cmake # needed for glaze
|
|
||||||
pkg-config
|
pkg-config
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -137,18 +143,21 @@ in
|
||||||
aquamarine
|
aquamarine
|
||||||
cairo
|
cairo
|
||||||
git
|
git
|
||||||
glaze
|
glaze-hyprland
|
||||||
|
gtest
|
||||||
hyprcursor
|
hyprcursor
|
||||||
hyprgraphics
|
hyprgraphics
|
||||||
hyprland-protocols
|
hyprland-protocols
|
||||||
hyprlang
|
hyprlang
|
||||||
hyprutils
|
hyprutils
|
||||||
|
hyprwire
|
||||||
libdrm
|
libdrm
|
||||||
libGL
|
libGL
|
||||||
libinput
|
libinput
|
||||||
libuuid
|
libuuid
|
||||||
libxkbcommon
|
libxkbcommon
|
||||||
libgbm
|
libgbm
|
||||||
|
muparser
|
||||||
pango
|
pango
|
||||||
pciutils
|
pciutils
|
||||||
re2
|
re2
|
||||||
|
|
@ -174,34 +183,48 @@ in
|
||||||
|
|
||||||
strictDeps = true;
|
strictDeps = true;
|
||||||
|
|
||||||
mesonBuildType =
|
cmakeBuildType =
|
||||||
if debug
|
if debug
|
||||||
then "debug"
|
then "Debug"
|
||||||
else "release";
|
else "RelWithDebInfo";
|
||||||
|
|
||||||
mesonFlags = flatten [
|
# we want as much debug info as possible
|
||||||
(mapAttrsToList mesonEnable {
|
dontStrip = debug;
|
||||||
"xwayland" = enableXWayland;
|
|
||||||
"systemd" = withSystemd;
|
cmakeFlags = mapAttrsToList cmakeBool {
|
||||||
"uwsm" = false;
|
"BUILT_WITH_NIX" = true;
|
||||||
"hyprpm" = false;
|
"NO_XWAYLAND" = !enableXWayland;
|
||||||
})
|
"LEGACY_RENDERER" = legacyRenderer;
|
||||||
(mapAttrsToList mesonBool {
|
"NO_SYSTEMD" = !withSystemd;
|
||||||
"b_pch" = false;
|
"CMAKE_DISABLE_PRECOMPILE_HEADERS" = true;
|
||||||
"tracy_enable" = false;
|
"NO_UWSM" = true;
|
||||||
})
|
"NO_HYPRPM" = true;
|
||||||
];
|
"TRACY_ENABLE" = false;
|
||||||
|
"WITH_TESTS" = withTests;
|
||||||
|
};
|
||||||
|
|
||||||
|
preConfigure = ''
|
||||||
|
substituteInPlace hyprtester/CMakeLists.txt --replace-fail \
|
||||||
|
"\''${CMAKE_CURRENT_BINARY_DIR}" \
|
||||||
|
"${placeholder "out"}/bin"
|
||||||
|
'';
|
||||||
|
|
||||||
postInstall = ''
|
postInstall = ''
|
||||||
${optionalString wrapRuntimeDeps ''
|
${optionalString wrapRuntimeDeps ''
|
||||||
wrapProgram $out/bin/Hyprland \
|
wrapProgram $out/bin/Hyprland \
|
||||||
--suffix PATH : ${makeBinPath [
|
--suffix PATH : ${makeBinPath [
|
||||||
binutils
|
binutils
|
||||||
hyprland-qtutils
|
hyprland-guiutils
|
||||||
pciutils
|
pciutils
|
||||||
pkgconf
|
pkgconf
|
||||||
]}
|
]}
|
||||||
''}
|
''}
|
||||||
|
|
||||||
|
${optionalString withTests ''
|
||||||
|
install hyprtester/pointer-warp -t $out/bin
|
||||||
|
install hyprtester/pointer-scroll -t $out/bin
|
||||||
|
install hyprland_gtests -t $out/bin
|
||||||
|
''}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
passthru.providedSessions = ["hyprland"];
|
passthru.providedSessions = ["hyprland"];
|
||||||
|
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
{
|
|
||||||
lib,
|
|
||||||
stdenv,
|
|
||||||
stdenvAdapters,
|
|
||||||
cmake,
|
|
||||||
pkg-config,
|
|
||||||
hyprland,
|
|
||||||
hyprwayland-scanner,
|
|
||||||
version ? "git",
|
|
||||||
}: let
|
|
||||||
inherit (lib.lists) flatten foldl';
|
|
||||||
inherit (lib.sources) cleanSourceWith cleanSource;
|
|
||||||
inherit (lib.strings) hasSuffix cmakeBool;
|
|
||||||
|
|
||||||
adapters = flatten [
|
|
||||||
stdenvAdapters.useMoldLinker
|
|
||||||
stdenvAdapters.keepDebugInfo
|
|
||||||
];
|
|
||||||
|
|
||||||
customStdenv = foldl' (acc: adapter: adapter acc) stdenv adapters;
|
|
||||||
in
|
|
||||||
customStdenv.mkDerivation (finalAttrs: {
|
|
||||||
pname = "hyprtester";
|
|
||||||
inherit version;
|
|
||||||
|
|
||||||
src = cleanSourceWith {
|
|
||||||
filter = name: _type: let
|
|
||||||
baseName = baseNameOf (toString name);
|
|
||||||
in
|
|
||||||
! (hasSuffix ".nix" baseName);
|
|
||||||
src = cleanSource ../.;
|
|
||||||
};
|
|
||||||
|
|
||||||
nativeBuildInputs = [
|
|
||||||
cmake
|
|
||||||
pkg-config
|
|
||||||
hyprwayland-scanner
|
|
||||||
];
|
|
||||||
|
|
||||||
buildInputs = hyprland.buildInputs;
|
|
||||||
|
|
||||||
preConfigure = ''
|
|
||||||
cmake -S . -B .
|
|
||||||
cmake --build . --target generate-protocol-headers -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
|
|
||||||
|
|
||||||
cd hyprtester
|
|
||||||
'';
|
|
||||||
|
|
||||||
cmakeBuildType = "Debug";
|
|
||||||
|
|
||||||
cmakeFlags = [(cmakeBool "TESTS" true)];
|
|
||||||
|
|
||||||
meta = {
|
|
||||||
homepage = "https://github.com/hyprwm/Hyprland";
|
|
||||||
description = "Hyprland testing framework";
|
|
||||||
license = lib.licenses.bsd3;
|
|
||||||
platforms = hyprland.meta.platforms;
|
|
||||||
mainProgram = "hyprtester";
|
|
||||||
};
|
|
||||||
})
|
|
||||||
|
|
@ -82,7 +82,7 @@ lib: let
|
||||||
:::
|
:::
|
||||||
*/
|
*/
|
||||||
toHyprlang = {
|
toHyprlang = {
|
||||||
topCommandsPrefixes ? ["$"],
|
topCommandsPrefixes ? ["$" "bezier"],
|
||||||
bottomCommandsPrefixes ? [],
|
bottomCommandsPrefixes ? [],
|
||||||
}: attrs: let
|
}: attrs: let
|
||||||
toHyprlang' = attrs: let
|
toHyprlang' = attrs: let
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,13 @@ in {
|
||||||
inputs.hyprcursor.overlays.default
|
inputs.hyprcursor.overlays.default
|
||||||
inputs.hyprgraphics.overlays.default
|
inputs.hyprgraphics.overlays.default
|
||||||
inputs.hyprland-protocols.overlays.default
|
inputs.hyprland-protocols.overlays.default
|
||||||
inputs.hyprland-qtutils.overlays.default
|
inputs.hyprland-guiutils.overlays.default
|
||||||
inputs.hyprlang.overlays.default
|
inputs.hyprlang.overlays.default
|
||||||
inputs.hyprutils.overlays.default
|
inputs.hyprutils.overlays.default
|
||||||
inputs.hyprwayland-scanner.overlays.default
|
inputs.hyprwayland-scanner.overlays.default
|
||||||
|
inputs.hyprwire.overlays.default
|
||||||
self.overlays.udis86
|
self.overlays.udis86
|
||||||
|
self.overlays.glaze
|
||||||
|
|
||||||
# Hyprland packages themselves
|
# Hyprland packages themselves
|
||||||
(final: _prev: let
|
(final: _prev: let
|
||||||
|
|
@ -43,9 +45,14 @@ in {
|
||||||
};
|
};
|
||||||
hyprland-unwrapped = final.hyprland.override {wrapRuntimeDeps = false;};
|
hyprland-unwrapped = final.hyprland.override {wrapRuntimeDeps = false;};
|
||||||
|
|
||||||
hyprtester = final.callPackage ./hyprtester.nix {
|
hyprland-with-tests = final.hyprland.override {withTests = true;};
|
||||||
inherit version;
|
|
||||||
};
|
hyprland-with-hyprtester =
|
||||||
|
builtins.trace ''
|
||||||
|
hyprland-with-hyprtester was removed. Please use the hyprland package.
|
||||||
|
Hyprtester is always built now.
|
||||||
|
''
|
||||||
|
final.hyprland;
|
||||||
|
|
||||||
# deprecated packages
|
# deprecated packages
|
||||||
hyprland-legacy-renderer =
|
hyprland-legacy-renderer =
|
||||||
|
|
@ -104,4 +111,13 @@ in {
|
||||||
patches = [];
|
patches = [];
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Even though glaze itself disables it by default, nixpkgs sets ENABLE_SSL set to true.
|
||||||
|
# Since we don't include openssl, the build failes without the `enableSSL = false;` override
|
||||||
|
glaze = final: prev: {
|
||||||
|
glaze-hyprland = prev.glaze.override {
|
||||||
|
enableSSL = false;
|
||||||
|
enableInterop = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
inputs: pkgs: let
|
inputs: pkgs: let
|
||||||
flake = inputs.self.packages.${pkgs.stdenv.hostPlatform.system};
|
flake = inputs.self.packages.${pkgs.stdenv.hostPlatform.system};
|
||||||
hyprland = flake.hyprland;
|
hyprland = flake.hyprland-with-tests;
|
||||||
in {
|
in {
|
||||||
tests = pkgs.testers.runNixOSTest {
|
tests = pkgs.testers.runNixOSTest {
|
||||||
name = "hyprland-tests";
|
name = "hyprland-tests";
|
||||||
|
|
||||||
nodes.machine = {pkgs, ...}: {
|
nodes.machine = {pkgs, ...}: {
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
flake.hyprtester
|
|
||||||
|
|
||||||
# Programs needed for tests
|
# Programs needed for tests
|
||||||
|
jq
|
||||||
kitty
|
kitty
|
||||||
|
wl-clipboard
|
||||||
xorg.xeyes
|
xorg.xeyes
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -22,8 +22,13 @@ in {
|
||||||
"HYPRLAND_TRACE" = "1";
|
"HYPRLAND_TRACE" = "1";
|
||||||
"XDG_RUNTIME_DIR" = "/tmp";
|
"XDG_RUNTIME_DIR" = "/tmp";
|
||||||
"XDG_CACHE_HOME" = "/tmp";
|
"XDG_CACHE_HOME" = "/tmp";
|
||||||
|
"KITTY_CONFIG_DIRECTORY" = "/etc/kitty";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
environment.etc."kitty/kitty.conf".text = ''
|
||||||
|
confirm_os_window_close 0
|
||||||
|
'';
|
||||||
|
|
||||||
programs.hyprland = {
|
programs.hyprland = {
|
||||||
enable = true;
|
enable = true;
|
||||||
package = hyprland;
|
package = hyprland;
|
||||||
|
|
@ -31,7 +36,7 @@ in {
|
||||||
};
|
};
|
||||||
|
|
||||||
# Test configuration
|
# Test configuration
|
||||||
environment.etc."test.conf".source = "${flake.hyprtester}/share/hypr/test.conf";
|
environment.etc."test.conf".source = "${hyprland}/share/hypr/test.conf";
|
||||||
|
|
||||||
# Disable portals
|
# Disable portals
|
||||||
xdg.portal.enable = pkgs.lib.mkForce false;
|
xdg.portal.enable = pkgs.lib.mkForce false;
|
||||||
|
|
@ -63,14 +68,21 @@ in {
|
||||||
# Wait for tty to be up
|
# Wait for tty to be up
|
||||||
machine.wait_for_unit("multi-user.target")
|
machine.wait_for_unit("multi-user.target")
|
||||||
|
|
||||||
|
|
||||||
|
# Run gtests
|
||||||
|
print("Running gtests")
|
||||||
|
exit_status, _out = machine.execute("su - alice -c 'hyprland_gtests 2>&1 | tee /tmp/gtestslog; exit ''${PIPESTATUS[0]}'")
|
||||||
|
machine.execute(f'echo {exit_status} > /tmp/exit_status_gtests')
|
||||||
|
|
||||||
# Run hyprtester testing framework/suite
|
# Run hyprtester testing framework/suite
|
||||||
print("Running hyprtester")
|
print("Running hyprtester")
|
||||||
exit_status, _out = machine.execute("su - alice -c 'hyprtester -b ${hyprland}/bin/Hyprland -c /etc/test.conf -p ${flake.hyprtester}/lib/hyprtestplugin.so 2>&1 | tee /tmp/testerlog; exit ''${PIPESTATUS[0]}'")
|
exit_status, _out = machine.execute("su - alice -c 'hyprtester -b ${hyprland}/bin/Hyprland -c /etc/test.conf -p ${hyprland}/lib/hyprtestplugin.so 2>&1 | tee /tmp/testerlog; exit ''${PIPESTATUS[0]}'")
|
||||||
print(f"Hyprtester exited with {exit_status}")
|
print(f"Hyprtester exited with {exit_status}")
|
||||||
|
|
||||||
# Copy logs to host
|
# Copy logs to host
|
||||||
machine.execute('cp "$(find /tmp/hypr -name *.log | head -1)" /tmp/hyprlog')
|
machine.execute('cp "$(find /tmp/hypr -name *.log | head -1)" /tmp/hyprlog')
|
||||||
machine.execute(f'echo {exit_status} > /tmp/exit_status')
|
machine.execute(f'echo {exit_status} > /tmp/exit_status')
|
||||||
|
machine.copy_from_vm("/tmp/gtestslog")
|
||||||
machine.copy_from_vm("/tmp/testerlog")
|
machine.copy_from_vm("/tmp/testerlog")
|
||||||
machine.copy_from_vm("/tmp/hyprlog")
|
machine.copy_from_vm("/tmp/hyprlog")
|
||||||
machine.copy_from_vm("/tmp/exit_status")
|
machine.copy_from_vm("/tmp/exit_status")
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,25 @@
|
||||||
#!/usr/bin/env -S nix shell nixpkgs#jq -c bash
|
#!/usr/bin/env -S nix shell nixpkgs#jq -c bash
|
||||||
|
|
||||||
# Update inputs when the Mesa version is outdated. We don't want
|
# Update inputs when the Mesa or QT version is outdated. We don't want
|
||||||
# incompatibilities between the user's system and Hyprland.
|
# incompatibilities between the user's system and Hyprland.
|
||||||
|
|
||||||
# get the current Nixpkgs revision
|
# get the current Nixpkgs revision
|
||||||
REV=$(jq <flake.lock '.nodes.nixpkgs.locked.rev' -r)
|
REV=$(jq <flake.lock '.nodes.nixpkgs.locked.rev' -r)
|
||||||
# check versions for current and remote nixpkgs' mesa
|
|
||||||
CRT_VER=$(nix eval --raw github:nixos/nixpkgs/"$REV"#mesa.version)
|
|
||||||
NEW_VER=$(nix eval --raw github:nixos/nixpkgs/nixos-unstable#mesa.version)
|
|
||||||
|
|
||||||
if [ "$CRT_VER" != "$NEW_VER" ]; then
|
get_ver() {
|
||||||
echo "Updating Mesa $CRT_VER -> $NEW_VER and flake inputs"
|
nix eval --raw "github:nixos/nixpkgs/$1#$2"
|
||||||
|
}
|
||||||
|
|
||||||
|
# check versions for current and remote nixpkgs'
|
||||||
|
MESA_OLD=$(get_ver "$REV" mesa.version)
|
||||||
|
MESA_NEW=$(get_ver nixos-unstable mesa.version)
|
||||||
|
QT_OLD=$(get_ver "$REV" kdePackages.qtbase.version)
|
||||||
|
QT_NEW=$(get_ver nixos-unstable kdePackages.qtbase.version)
|
||||||
|
|
||||||
|
if [ "$MESA_OLD" != "$MESA_NEW" ] || [ "$QT_OLD" != "$QT_NEW" ]; then
|
||||||
|
echo "Updating flake inputs..."
|
||||||
|
echo "Mesa: $MESA_OLD -> $MESA_NEW"
|
||||||
|
echo "Qt: $QT_OLD -> $QT_NEW"
|
||||||
|
|
||||||
# update inputs to latest versions
|
# update inputs to latest versions
|
||||||
nix flake update
|
nix flake update
|
||||||
|
|
|
||||||
|
|
@ -1,115 +0,0 @@
|
||||||
wayland_protos = dependency(
|
|
||||||
'wayland-protocols',
|
|
||||||
version: '>=1.43',
|
|
||||||
fallback: 'wayland-protocols',
|
|
||||||
default_options: ['tests=false'],
|
|
||||||
)
|
|
||||||
|
|
||||||
hyprland_protos = dependency(
|
|
||||||
'hyprland-protocols',
|
|
||||||
version: '>=0.6.4',
|
|
||||||
fallback: 'hyprland-protocols',
|
|
||||||
)
|
|
||||||
|
|
||||||
wayland_protocol_dir = wayland_protos.get_variable('pkgdatadir')
|
|
||||||
hyprland_protocol_dir = hyprland_protos.get_variable('pkgdatadir')
|
|
||||||
|
|
||||||
hyprwayland_scanner_dep = dependency('hyprwayland-scanner', version: '>=0.3.10', native: true)
|
|
||||||
hyprwayland_scanner = find_program(
|
|
||||||
hyprwayland_scanner_dep.get_variable('hyprwayland_scanner'),
|
|
||||||
native: true,
|
|
||||||
)
|
|
||||||
|
|
||||||
protocols = [
|
|
||||||
'wlr-gamma-control-unstable-v1.xml',
|
|
||||||
'wlr-foreign-toplevel-management-unstable-v1.xml',
|
|
||||||
'wlr-output-power-management-unstable-v1.xml',
|
|
||||||
'input-method-unstable-v2.xml',
|
|
||||||
'virtual-keyboard-unstable-v1.xml',
|
|
||||||
'wlr-virtual-pointer-unstable-v1.xml',
|
|
||||||
'wlr-output-management-unstable-v1.xml',
|
|
||||||
'kde-server-decoration.xml',
|
|
||||||
'wlr-layer-shell-unstable-v1.xml',
|
|
||||||
'wayland-drm.xml',
|
|
||||||
'wlr-data-control-unstable-v1.xml',
|
|
||||||
'wlr-screencopy-unstable-v1.xml',
|
|
||||||
'xx-color-management-v4.xml',
|
|
||||||
'frog-color-management-v1.xml',
|
|
||||||
hyprland_protocol_dir / 'protocols/hyprland-global-shortcuts-v1.xml',
|
|
||||||
hyprland_protocol_dir / 'protocols/hyprland-toplevel-export-v1.xml',
|
|
||||||
hyprland_protocol_dir / 'protocols/hyprland-toplevel-mapping-v1.xml',
|
|
||||||
hyprland_protocol_dir / 'protocols/hyprland-focus-grab-v1.xml',
|
|
||||||
hyprland_protocol_dir / 'protocols/hyprland-ctm-control-v1.xml',
|
|
||||||
hyprland_protocol_dir / 'protocols/hyprland-surface-v1.xml',
|
|
||||||
hyprland_protocol_dir / 'protocols/hyprland-lock-notify-v1.xml',
|
|
||||||
wayland_protocol_dir / 'staging/tearing-control/tearing-control-v1.xml',
|
|
||||||
wayland_protocol_dir / 'staging/fractional-scale/fractional-scale-v1.xml',
|
|
||||||
wayland_protocol_dir / 'unstable/xdg-output/xdg-output-unstable-v1.xml',
|
|
||||||
wayland_protocol_dir / 'staging/cursor-shape/cursor-shape-v1.xml',
|
|
||||||
wayland_protocol_dir / 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml',
|
|
||||||
wayland_protocol_dir / 'unstable/relative-pointer/relative-pointer-unstable-v1.xml',
|
|
||||||
wayland_protocol_dir / 'unstable/xdg-decoration/xdg-decoration-unstable-v1.xml',
|
|
||||||
wayland_protocol_dir / 'staging/alpha-modifier/alpha-modifier-v1.xml',
|
|
||||||
wayland_protocol_dir / 'staging/ext-foreign-toplevel-list/ext-foreign-toplevel-list-v1.xml',
|
|
||||||
wayland_protocol_dir / 'unstable/pointer-gestures/pointer-gestures-unstable-v1.xml',
|
|
||||||
wayland_protocol_dir / 'unstable/keyboard-shortcuts-inhibit/keyboard-shortcuts-inhibit-unstable-v1.xml',
|
|
||||||
wayland_protocol_dir / 'unstable/text-input/text-input-unstable-v3.xml',
|
|
||||||
wayland_protocol_dir / 'unstable/text-input/text-input-unstable-v1.xml',
|
|
||||||
wayland_protocol_dir / 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml',
|
|
||||||
wayland_protocol_dir / 'staging/xdg-activation/xdg-activation-v1.xml',
|
|
||||||
wayland_protocol_dir / 'staging/ext-idle-notify/ext-idle-notify-v1.xml',
|
|
||||||
wayland_protocol_dir / 'staging/ext-session-lock/ext-session-lock-v1.xml',
|
|
||||||
wayland_protocol_dir / 'stable/tablet/tablet-v2.xml',
|
|
||||||
wayland_protocol_dir / 'stable/presentation-time/presentation-time.xml',
|
|
||||||
wayland_protocol_dir / 'stable/xdg-shell/xdg-shell.xml',
|
|
||||||
wayland_protocol_dir / 'unstable/primary-selection/primary-selection-unstable-v1.xml',
|
|
||||||
wayland_protocol_dir / 'staging/xwayland-shell/xwayland-shell-v1.xml',
|
|
||||||
wayland_protocol_dir / 'stable/viewporter/viewporter.xml',
|
|
||||||
wayland_protocol_dir / 'stable/linux-dmabuf/linux-dmabuf-v1.xml',
|
|
||||||
wayland_protocol_dir / 'staging/drm-lease/drm-lease-v1.xml',
|
|
||||||
wayland_protocol_dir / 'staging/linux-drm-syncobj/linux-drm-syncobj-v1.xml',
|
|
||||||
wayland_protocol_dir / 'staging/xdg-dialog/xdg-dialog-v1.xml',
|
|
||||||
wayland_protocol_dir / 'staging/single-pixel-buffer/single-pixel-buffer-v1.xml',
|
|
||||||
wayland_protocol_dir / 'staging/security-context/security-context-v1.xml',
|
|
||||||
wayland_protocol_dir / 'staging/content-type/content-type-v1.xml',
|
|
||||||
wayland_protocol_dir / 'staging/color-management/color-management-v1.xml',
|
|
||||||
wayland_protocol_dir / 'staging/xdg-toplevel-tag/xdg-toplevel-tag-v1.xml',
|
|
||||||
wayland_protocol_dir / 'staging/xdg-system-bell/xdg-system-bell-v1.xml',
|
|
||||||
wayland_protocol_dir / 'staging/ext-workspace/ext-workspace-v1.xml',
|
|
||||||
]
|
|
||||||
|
|
||||||
wl_protocols = []
|
|
||||||
foreach protocol : protocols
|
|
||||||
wl_protocols += custom_target(
|
|
||||||
protocol.underscorify(),
|
|
||||||
input: protocol,
|
|
||||||
install: true,
|
|
||||||
install_dir: [false, join_paths(get_option('includedir'), 'hyprland/protocols')],
|
|
||||||
output: ['@BASENAME@.cpp', '@BASENAME@.hpp'],
|
|
||||||
command: [hyprwayland_scanner, '@INPUT@', '@OUTDIR@'],
|
|
||||||
)
|
|
||||||
endforeach
|
|
||||||
|
|
||||||
# wayland.xml generation
|
|
||||||
wayland_scanner = dependency('wayland-scanner', native: true)
|
|
||||||
wayland_scanner_datadir = wayland_scanner.get_variable('pkgdatadir')
|
|
||||||
|
|
||||||
wayland_xml = wayland_scanner_datadir / 'wayland.xml'
|
|
||||||
wayland_protocol = custom_target(
|
|
||||||
wayland_xml.underscorify(),
|
|
||||||
input: wayland_xml,
|
|
||||||
install: true,
|
|
||||||
install_dir: [false, join_paths(get_option('includedir'), 'hyprland/protocols')],
|
|
||||||
output: ['@BASENAME@.cpp', '@BASENAME@.hpp'],
|
|
||||||
command: [hyprwayland_scanner, '--wayland-enums', '@INPUT@', '@OUTDIR@'],
|
|
||||||
)
|
|
||||||
|
|
||||||
lib_server_protos = static_library(
|
|
||||||
'server_protos',
|
|
||||||
wl_protocols + wayland_protocol,
|
|
||||||
)
|
|
||||||
|
|
||||||
server_protos = declare_dependency(
|
|
||||||
link_with: lib_server_protos,
|
|
||||||
sources: wl_protocols + wayland_protocol,
|
|
||||||
)
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# if the git directory doesn't exist, don't gather data to avoid overwriting, unless
|
|
||||||
# the version file is missing altogether (otherwise compiling will fail)
|
|
||||||
if [ ! -d ./.git ]; then
|
|
||||||
if [ -f ./src/version.h ]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
cp -fr ./src/version.h.in ./src/version.h
|
|
||||||
|
|
||||||
HASH=${HASH-$(git rev-parse HEAD)}
|
|
||||||
BRANCH=${BRANCH-$(git branch --show-current)}
|
|
||||||
MESSAGE=${MESSAGE-$(git show | head -n 5 | tail -n 1 | sed -e 's/#//g' -e 's/\"//g')}
|
|
||||||
DATE=${DATE-$(git show --no-patch --format=%cd --date=local)}
|
|
||||||
DIRTY=${DIRTY-$(git diff-index --quiet HEAD -- || echo dirty)}
|
|
||||||
TAG=${TAG-$(git describe --tags)}
|
|
||||||
COMMITS=${COMMITS-$(git rev-list --count HEAD)}
|
|
||||||
|
|
||||||
sed -i -e "s#@HASH@#${HASH}#" ./src/version.h
|
|
||||||
sed -i -e "s#@BRANCH@#${BRANCH}#" ./src/version.h
|
|
||||||
sed -i -e "s#@MESSAGE@#${MESSAGE}#" ./src/version.h
|
|
||||||
sed -i -e "s#@DATE@#${DATE}#" ./src/version.h
|
|
||||||
sed -i -e "s#@DIRTY@#${DIRTY}#" ./src/version.h
|
|
||||||
sed -i -e "s#@TAG@#${TAG}#" ./src/version.h
|
|
||||||
sed -i -e "s#@COMMITS@#${COMMITS}#" ./src/version.h
|
|
||||||
1177
src/Compositor.cpp
1177
src/Compositor.cpp
File diff suppressed because it is too large
Load diff
|
|
@ -2,10 +2,12 @@
|
||||||
|
|
||||||
#include <sys/resource.h>
|
#include <sys/resource.h>
|
||||||
|
|
||||||
|
#include <ranges>
|
||||||
|
|
||||||
#include "managers/XWaylandManager.hpp"
|
#include "managers/XWaylandManager.hpp"
|
||||||
#include "managers/KeybindManager.hpp"
|
#include "managers/KeybindManager.hpp"
|
||||||
#include "managers/SessionLockManager.hpp"
|
#include "managers/SessionLockManager.hpp"
|
||||||
#include "desktop/Window.hpp"
|
#include "desktop/view/Window.hpp"
|
||||||
#include "protocols/types/ColorManagement.hpp"
|
#include "protocols/types/ColorManagement.hpp"
|
||||||
|
|
||||||
#include <aquamarine/backend/Backend.hpp>
|
#include <aquamarine/backend/Backend.hpp>
|
||||||
|
|
@ -27,8 +29,18 @@ class CCompositor {
|
||||||
|
|
||||||
wl_display* m_wlDisplay = nullptr;
|
wl_display* m_wlDisplay = nullptr;
|
||||||
wl_event_loop* m_wlEventLoop = nullptr;
|
wl_event_loop* m_wlEventLoop = nullptr;
|
||||||
int m_drmFD = -1;
|
struct {
|
||||||
|
int fd = -1;
|
||||||
|
bool syncobjSupport = false;
|
||||||
|
} m_drm;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
int fd = -1;
|
||||||
|
bool syncObjSupport = false;
|
||||||
|
} m_drmRenderNode;
|
||||||
|
|
||||||
bool m_initialized = false;
|
bool m_initialized = false;
|
||||||
|
bool m_safeMode = false;
|
||||||
SP<Aquamarine::CBackend> m_aqBackend;
|
SP<Aquamarine::CBackend> m_aqBackend;
|
||||||
|
|
||||||
std::string m_hyprTempDataRoot = "";
|
std::string m_hyprTempDataRoot = "";
|
||||||
|
|
@ -42,9 +54,9 @@ class CCompositor {
|
||||||
std::vector<PHLMONITOR> m_realMonitors; // for all monitors, even those turned off
|
std::vector<PHLMONITOR> m_realMonitors; // for all monitors, even those turned off
|
||||||
std::vector<PHLWINDOW> m_windows;
|
std::vector<PHLWINDOW> m_windows;
|
||||||
std::vector<PHLLS> m_layers;
|
std::vector<PHLLS> m_layers;
|
||||||
std::vector<PHLWORKSPACE> m_workspaces;
|
|
||||||
std::vector<PHLWINDOWREF> m_windowsFadingOut;
|
std::vector<PHLWINDOWREF> m_windowsFadingOut;
|
||||||
std::vector<PHLLSREF> m_surfacesFadingOut;
|
std::vector<PHLLSREF> m_surfacesFadingOut;
|
||||||
|
std::vector<SP<Desktop::View::IView>> m_otherViews;
|
||||||
|
|
||||||
std::unordered_map<std::string, MONITORID> m_monitorIDMap;
|
std::unordered_map<std::string, MONITORID> m_monitorIDMap;
|
||||||
std::unordered_map<std::string, WORKSPACEID> m_seenMonitorWorkspaceMap; // map of seen monitor names to workspace IDs
|
std::unordered_map<std::string, WORKSPACEID> m_seenMonitorWorkspaceMap; // map of seen monitor names to workspace IDs
|
||||||
|
|
@ -55,12 +67,7 @@ class CCompositor {
|
||||||
void cleanup();
|
void cleanup();
|
||||||
void bumpNofile();
|
void bumpNofile();
|
||||||
void restoreNofile();
|
void restoreNofile();
|
||||||
|
bool setWatchdogFd(int fd);
|
||||||
WP<CWLSurfaceResource> m_lastFocus;
|
|
||||||
PHLWINDOWREF m_lastWindow;
|
|
||||||
PHLMONITORREF m_lastMonitor;
|
|
||||||
|
|
||||||
std::vector<PHLWINDOWREF> m_windowFocusHistory; // first element is the most recently focused
|
|
||||||
|
|
||||||
bool m_readyToProcess = false;
|
bool m_readyToProcess = false;
|
||||||
bool m_sessionActive = true;
|
bool m_sessionActive = true;
|
||||||
|
|
@ -75,14 +82,20 @@ class CCompositor {
|
||||||
|
|
||||||
// ------------------------------------------------- //
|
// ------------------------------------------------- //
|
||||||
|
|
||||||
|
auto getWorkspaces() {
|
||||||
|
return std::views::filter(m_workspaces, [](const auto& e) { return e; });
|
||||||
|
}
|
||||||
|
std::vector<PHLWORKSPACE> getWorkspacesCopy();
|
||||||
|
void registerWorkspace(PHLWORKSPACE w);
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
PHLMONITOR getMonitorFromID(const MONITORID&);
|
PHLMONITOR getMonitorFromID(const MONITORID&);
|
||||||
PHLMONITOR getMonitorFromName(const std::string&);
|
PHLMONITOR getMonitorFromName(const std::string&);
|
||||||
PHLMONITOR getMonitorFromDesc(const std::string&);
|
PHLMONITOR getMonitorFromDesc(const std::string&);
|
||||||
PHLMONITOR getMonitorFromCursor();
|
PHLMONITOR getMonitorFromCursor();
|
||||||
PHLMONITOR getMonitorFromVector(const Vector2D&);
|
PHLMONITOR getMonitorFromVector(const Vector2D&);
|
||||||
void removeWindowFromVectorSafe(PHLWINDOW);
|
void removeWindowFromVectorSafe(PHLWINDOW);
|
||||||
void focusWindow(PHLWINDOW, SP<CWLSurfaceResource> pSurface = nullptr, bool preserveFocusHistory = false);
|
|
||||||
void focusSurface(SP<CWLSurfaceResource>, PHLWINDOW pWindowOwner = nullptr);
|
|
||||||
bool monitorExists(PHLMONITOR);
|
bool monitorExists(PHLMONITOR);
|
||||||
PHLWINDOW vectorToWindowUnified(const Vector2D&, uint8_t properties, PHLWINDOW pIgnoreWindow = nullptr);
|
PHLWINDOW vectorToWindowUnified(const Vector2D&, uint8_t properties, PHLWINDOW pIgnoreWindow = nullptr);
|
||||||
SP<CWLSurfaceResource> vectorToLayerSurface(const Vector2D&, std::vector<PHLLSREF>*, Vector2D*, PHLLS*, bool aboveLockscreen = false);
|
SP<CWLSurfaceResource> vectorToLayerSurface(const Vector2D&, std::vector<PHLLSREF>*, Vector2D*, PHLLS*, bool aboveLockscreen = false);
|
||||||
|
|
@ -96,7 +109,6 @@ class CCompositor {
|
||||||
PHLWORKSPACE getWorkspaceByID(const WORKSPACEID&);
|
PHLWORKSPACE getWorkspaceByID(const WORKSPACEID&);
|
||||||
PHLWORKSPACE getWorkspaceByName(const std::string&);
|
PHLWORKSPACE getWorkspaceByName(const std::string&);
|
||||||
PHLWORKSPACE getWorkspaceByString(const std::string&);
|
PHLWORKSPACE getWorkspaceByString(const std::string&);
|
||||||
void sanityCheckWorkspaces();
|
|
||||||
PHLWINDOW getUrgentWindow();
|
PHLWINDOW getUrgentWindow();
|
||||||
bool isWindowActive(PHLWINDOW);
|
bool isWindowActive(PHLWINDOW);
|
||||||
void changeWindowZOrder(PHLWINDOW, bool);
|
void changeWindowZOrder(PHLWINDOW, bool);
|
||||||
|
|
@ -108,10 +120,10 @@ class CCompositor {
|
||||||
WORKSPACEID getNextAvailableNamedWorkspace();
|
WORKSPACEID getNextAvailableNamedWorkspace();
|
||||||
bool isPointOnAnyMonitor(const Vector2D&);
|
bool isPointOnAnyMonitor(const Vector2D&);
|
||||||
bool isPointOnReservedArea(const Vector2D& point, const PHLMONITOR monitor = nullptr);
|
bool isPointOnReservedArea(const Vector2D& point, const PHLMONITOR monitor = nullptr);
|
||||||
|
CBox calculateX11WorkArea();
|
||||||
PHLMONITOR getMonitorInDirection(const char&);
|
PHLMONITOR getMonitorInDirection(const char&);
|
||||||
PHLMONITOR getMonitorInDirection(PHLMONITOR, const char&);
|
PHLMONITOR getMonitorInDirection(PHLMONITOR, const char&);
|
||||||
void updateAllWindowsAnimatedDecorationValues();
|
void updateAllWindowsAnimatedDecorationValues();
|
||||||
void updateWindowAnimatedDecorationValues(PHLWINDOW);
|
|
||||||
MONITORID getNextAvailableMonitorID(std::string const& name);
|
MONITORID getNextAvailableMonitorID(std::string const& name);
|
||||||
void moveWorkspaceToMonitor(PHLWORKSPACE, PHLMONITOR, bool noWarpCursor = false);
|
void moveWorkspaceToMonitor(PHLWORKSPACE, PHLMONITOR, bool noWarpCursor = false);
|
||||||
void swapActiveWorkspaces(PHLMONITOR, PHLMONITOR);
|
void swapActiveWorkspaces(PHLMONITOR, PHLMONITOR);
|
||||||
|
|
@ -119,9 +131,8 @@ class CCompositor {
|
||||||
bool workspaceIDOutOfBounds(const WORKSPACEID&);
|
bool workspaceIDOutOfBounds(const WORKSPACEID&);
|
||||||
void setWindowFullscreenInternal(const PHLWINDOW PWINDOW, const eFullscreenMode MODE);
|
void setWindowFullscreenInternal(const PHLWINDOW PWINDOW, const eFullscreenMode MODE);
|
||||||
void setWindowFullscreenClient(const PHLWINDOW PWINDOW, const eFullscreenMode MODE);
|
void setWindowFullscreenClient(const PHLWINDOW PWINDOW, const eFullscreenMode MODE);
|
||||||
void setWindowFullscreenState(const PHLWINDOW PWINDOW, const SFullscreenState state);
|
void setWindowFullscreenState(const PHLWINDOW PWINDOW, const Desktop::View::SFullscreenState state);
|
||||||
void changeWindowFullscreenModeClient(const PHLWINDOW PWINDOW, const eFullscreenMode MODE, const bool ON);
|
void changeWindowFullscreenModeClient(const PHLWINDOW PWINDOW, const eFullscreenMode MODE, const bool ON);
|
||||||
void updateFullscreenFadeOnWorkspace(PHLWORKSPACE);
|
|
||||||
PHLWINDOW getX11Parent(PHLWINDOW);
|
PHLWINDOW getX11Parent(PHLWINDOW);
|
||||||
void scheduleFrameForMonitor(PHLMONITOR, Aquamarine::IOutput::scheduleFrameReason reason = Aquamarine::IOutput::AQ_SCHEDULE_CLIENT_UNKNOWN);
|
void scheduleFrameForMonitor(PHLMONITOR, Aquamarine::IOutput::scheduleFrameReason reason = Aquamarine::IOutput::AQ_SCHEDULE_CLIENT_UNKNOWN);
|
||||||
void addToFadingOutSafe(PHLLS);
|
void addToFadingOutSafe(PHLLS);
|
||||||
|
|
@ -132,15 +143,16 @@ class CCompositor {
|
||||||
PHLLS getLayerSurfaceFromSurface(SP<CWLSurfaceResource>);
|
PHLLS getLayerSurfaceFromSurface(SP<CWLSurfaceResource>);
|
||||||
void closeWindow(PHLWINDOW);
|
void closeWindow(PHLWINDOW);
|
||||||
Vector2D parseWindowVectorArgsRelative(const std::string&, const Vector2D&);
|
Vector2D parseWindowVectorArgsRelative(const std::string&, const Vector2D&);
|
||||||
PHLWORKSPACE createNewWorkspace(const WORKSPACEID&, const MONITORID&, const std::string& name = "",
|
[[nodiscard]] PHLWORKSPACE createNewWorkspace(const WORKSPACEID&, const MONITORID&, const std::string& name = "",
|
||||||
bool isEmpty = true); // will be deleted next frame if left empty and unfocused!
|
bool isEmpty = true); // will be deleted next frame if left empty and unfocused!
|
||||||
void setActiveMonitor(PHLMONITOR);
|
|
||||||
bool isWorkspaceSpecial(const WORKSPACEID&);
|
bool isWorkspaceSpecial(const WORKSPACEID&);
|
||||||
WORKSPACEID getNewSpecialID();
|
WORKSPACEID getNewSpecialID();
|
||||||
void performUserChecks();
|
void performUserChecks();
|
||||||
void moveWindowToWorkspaceSafe(PHLWINDOW pWindow, PHLWORKSPACE pWorkspace);
|
void moveWindowToWorkspaceSafe(PHLWINDOW pWindow, PHLWORKSPACE pWorkspace);
|
||||||
PHLWINDOW getForceFocus();
|
PHLWINDOW getForceFocus();
|
||||||
|
void scheduleMonitorStateRecheck();
|
||||||
void arrangeMonitors();
|
void arrangeMonitors();
|
||||||
|
void checkMonitorOverlaps();
|
||||||
void enterUnsafeState();
|
void enterUnsafeState();
|
||||||
void leaveUnsafeState();
|
void leaveUnsafeState();
|
||||||
void setPreferredScaleForSurface(SP<CWLSurfaceResource> pSurface, double scale);
|
void setPreferredScaleForSurface(SP<CWLSurfaceResource> pSurface, double scale);
|
||||||
|
|
@ -148,10 +160,13 @@ class CCompositor {
|
||||||
void updateSuspendedStates();
|
void updateSuspendedStates();
|
||||||
void onNewMonitor(SP<Aquamarine::IOutput> output);
|
void onNewMonitor(SP<Aquamarine::IOutput> output);
|
||||||
void ensurePersistentWorkspacesPresent(const std::vector<SWorkspaceRule>& rules, PHLWORKSPACE pWorkspace = nullptr);
|
void ensurePersistentWorkspacesPresent(const std::vector<SWorkspaceRule>& rules, PHLWORKSPACE pWorkspace = nullptr);
|
||||||
|
std::optional<unsigned int> getVTNr();
|
||||||
|
|
||||||
NColorManagement::SImageDescription getPreferredImageDescription();
|
NColorManagement::SImageDescription getPreferredImageDescription();
|
||||||
|
NColorManagement::SImageDescription getHDRImageDescription();
|
||||||
bool shouldChangePreferredImageDescription();
|
bool shouldChangePreferredImageDescription();
|
||||||
|
|
||||||
|
bool supportsDrmSyncobjTimeline() const;
|
||||||
std::string m_explicitConfigPath;
|
std::string m_explicitConfigPath;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
@ -164,10 +179,14 @@ class CCompositor {
|
||||||
void createLockFile();
|
void createLockFile();
|
||||||
void removeLockFile();
|
void removeLockFile();
|
||||||
void setMallocThreshold();
|
void setMallocThreshold();
|
||||||
|
void openSafeModeBox();
|
||||||
|
|
||||||
uint64_t m_hyprlandPID = 0;
|
uint64_t m_hyprlandPID = 0;
|
||||||
wl_event_source* m_critSigSource = nullptr;
|
wl_event_source* m_critSigSource = nullptr;
|
||||||
rlimit m_originalNofile = {};
|
rlimit m_originalNofile = {};
|
||||||
|
Hyprutils::OS::CFileDescriptor m_watchdogWriteFd;
|
||||||
|
|
||||||
|
std::vector<PHLWORKSPACEREF> m_workspaces;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline UP<CCompositor> g_pCompositor;
|
inline UP<CCompositor> g_pCompositor;
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ enum eIcons : uint8_t {
|
||||||
enum eRenderStage : uint8_t {
|
enum eRenderStage : uint8_t {
|
||||||
RENDER_PRE = 0, /* Before binding the gl context */
|
RENDER_PRE = 0, /* Before binding the gl context */
|
||||||
RENDER_BEGIN, /* Just when the rendering begins, nothing has been rendered yet. Damage, current render data in opengl valid. */
|
RENDER_BEGIN, /* Just when the rendering begins, nothing has been rendered yet. Damage, current render data in opengl valid. */
|
||||||
|
RENDER_POST_WALLPAPER, /* After background layer, but before bottom and overlay layers */
|
||||||
RENDER_PRE_WINDOWS, /* Pre windows, post bottom and overlay layers */
|
RENDER_PRE_WINDOWS, /* Pre windows, post bottom and overlay layers */
|
||||||
RENDER_POST_WINDOWS, /* Post windows, pre top/overlay layers, etc */
|
RENDER_POST_WINDOWS, /* Post windows, pre top/overlay layers, etc */
|
||||||
RENDER_LAST_MOMENT, /* Last moment to render with the gl context */
|
RENDER_LAST_MOMENT, /* Last moment to render with the gl context */
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue