Compare commits
243 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b41882c169 | |||
| bcab43b181 | |||
| 08e215c9bf | |||
|
|
4152ac76d0 | ||
|
|
a5858018d8 | ||
|
|
8685fd7b0c | ||
|
|
1fa157cf6d | ||
|
|
e0c5710059 | ||
|
|
42f0a6005b | ||
|
|
ae9ca17b40 | ||
|
|
972f23efe8 | ||
|
|
4c60d9df70 | ||
|
|
b7dfb47566 | ||
|
|
3284dd729b | ||
|
|
803e81ac39 | ||
|
|
34c7cc7d38 | ||
|
|
c47ae950f4 | ||
|
|
3f169ee5de | ||
|
|
10754745a9 | ||
|
|
8271cfc97b | ||
|
|
c11cadd8d6 | ||
|
|
dc4b082ee8 | ||
|
|
edf7098345 | ||
|
|
7299a3b0d5 | ||
|
|
b06a4b5e13 | ||
|
|
3faddf40d0 | ||
|
|
a6e3a2478c | ||
|
|
ff0b706ea3 | ||
|
|
4f44df7b17 | ||
|
|
be03497b82 | ||
|
|
ff20cbf89c | ||
|
|
fe0a202137 | ||
|
|
75a815fbf2 | ||
|
|
3b7401b065 | ||
|
|
d4d17d5d52 | ||
|
|
52ece2b017 | ||
|
|
d98f7ffaf5 | ||
|
|
5f650f8ed9 | ||
|
|
9f98f7440b | ||
|
|
5cb1281035 | ||
|
|
743dffd638 | ||
|
|
5c370c3333 | ||
|
|
cf0d256c13 | ||
|
|
6ebafcf107 | ||
|
|
8ad96a95d6 | ||
|
|
f41e3c2203 | ||
|
|
f0a80ce5e0 | ||
|
|
2928d6af0a | ||
|
|
93aacfc0dc | ||
|
|
19c263e53c | ||
|
|
a032090098 | ||
|
|
0b55c55f4a | ||
|
|
85c2764f5e | ||
|
|
c2bed4103c | ||
|
|
82729db330 | ||
|
|
f12904e641 | ||
|
|
b90c61c04f | ||
|
|
e333a330c0 | ||
|
|
1c64ef06d9 | ||
|
|
6b2c08d3e8 | ||
|
|
db8509dfe2 | ||
|
|
d2b9957fab | ||
|
|
362ea7b0f3 | ||
|
|
f7114016c6 | ||
|
|
0002f148c9 | ||
|
|
ffec41c426 | ||
|
|
f624449c12 | ||
|
|
70cdd819e4 | ||
|
|
cc14dd1baf | ||
|
|
c71fbd854d | ||
|
|
0e9196867b | ||
|
|
1e06ab464f | ||
|
|
623185170b | ||
|
|
d0583e1761 | ||
|
|
5b2efe54b1 | ||
|
|
457617b5a3 | ||
|
|
c60b3cb2ed | ||
|
|
fbf67ef050 | ||
|
|
be893a81b4 | ||
|
|
5a80bc120a | ||
|
|
a248805132 | ||
|
|
8ab4d1dc06 | ||
|
|
ae82a55400 | ||
|
|
bc09504ea5 | ||
|
|
f4bc8c3a64 | ||
|
|
b88813c7ef | ||
|
|
b4ee4674f9 | ||
|
|
93dbf88426 | ||
|
|
0eb4755a3e | ||
|
|
723870337f | ||
|
|
51f8849e54 | ||
|
|
b9b1eda2ef | ||
|
|
13dab66b1d | ||
|
|
9f59ed7868 | ||
|
|
a20142bcce | ||
|
|
d91952c555 | ||
|
|
8b17a7404b | ||
|
|
9ea6d0e15f | ||
|
|
a1e62dcb12 | ||
|
|
7a566942d5 | ||
|
|
68456a5d9a | ||
|
|
184af52f24 | ||
|
|
1af260ecbe | ||
|
|
0de216e783 | ||
|
|
661314e134 | ||
|
|
17fc159ae2 | ||
|
|
6716b8a0e3 | ||
|
|
59f19e465b | ||
|
|
e6ca141364 | ||
|
|
48176160ab | ||
|
|
e5a2b9e5b0 | ||
|
|
e80f705d76 | ||
|
|
1bf410e1fc | ||
|
|
1c767de9da | ||
|
|
a8a8929bb4 | ||
|
|
eb0d3f9f01 | ||
|
|
81a029e504 | ||
|
|
380d14998e | ||
|
|
fd48d102e1 | ||
|
|
531fc43203 | ||
|
|
5b6c42ca70 | ||
|
|
857a78ce4e | ||
|
|
339661229d | ||
|
|
ff061d177e | ||
|
|
407a623801 | ||
|
|
171ad7d338 | ||
|
|
f16ebef003 | ||
|
|
6507445787 | ||
|
|
f68ac7ef75 | ||
|
|
60f1c61323 | ||
|
|
9f9dbb0dc5 | ||
|
|
cfbbfb591a | ||
|
|
63eb6b3bda | ||
|
|
8606bc255b | ||
|
|
562171ab66 | ||
|
|
9ce9ef2705 | ||
|
|
02ff413002 | ||
|
|
1bc857b12c | ||
|
|
cd7bdc7a43 | ||
|
|
e123fd3e66 | ||
|
|
30756d8718 | ||
|
|
9433060760 | ||
|
|
a0ec2e4daf | ||
|
|
d9d9d9358f | ||
|
|
95c8f8b299 | ||
|
|
beeca9dacb | ||
|
|
47f9035601 | ||
|
|
db6114c6c5 | ||
|
|
cbeb6984e7 | ||
|
|
4330b49a84 | ||
|
|
2ad7f6edd4 | ||
|
|
ec120d5732 | ||
|
|
fe6c213024 | ||
|
|
b8fc0def97 | ||
|
|
c92fb5e85f | ||
|
|
7d209b2941 | ||
|
|
e92b20292b | ||
|
|
22e53345ba | ||
|
|
116537b494 | ||
|
|
c8b5023bb0 | ||
|
|
bcb34275ea | ||
|
|
21325f9385 | ||
|
|
50454c6d17 | ||
|
|
c65c7614bc | ||
|
|
891e029ba3 | ||
|
|
b1d1c9843f | ||
|
|
2a2c2b0e28 | ||
|
|
64db62d7e2 | ||
|
|
82de66a030 | ||
|
|
22fc8136a2 | ||
|
|
e7985ca4c4 | ||
|
|
6c3ebed76e | ||
|
|
f9fb24577a | ||
|
|
55f40ecc95 | ||
|
|
441a8714c7 | ||
|
|
57e6a57e6b | ||
|
|
c44292c723 | ||
|
|
f0b6714539 | ||
|
|
8f547c6fa0 | ||
|
|
d6e2ae0247 | ||
|
|
eb0480ba0d | ||
|
|
0896775f1b | ||
|
|
c99eb23869 | ||
|
|
92a3b91999 | ||
|
|
36aa465a21 | ||
|
|
fec17e5e79 | ||
|
|
eff484b96c | ||
|
|
2e697ce2bf | ||
|
|
0b13d398fe | ||
|
|
ac9df44788 | ||
|
|
e0cf88809d | ||
|
|
e43f949f8a | ||
|
|
8d03fcc8d7 | ||
|
|
5e18111121 | ||
|
|
fbf421df88 | ||
|
|
8f8b31e7a6 | ||
|
|
81e7498ec2 | ||
|
|
fa41c8229d | ||
|
|
5b1b79c29c | ||
|
|
eb623bd91d | ||
|
|
3dcaadbdf5 | ||
|
|
a649dbe4c4 | ||
|
|
f767782e3f | ||
|
|
f54dd4da4a | ||
|
|
3aa4e02720 | ||
|
|
8368566044 | ||
|
|
918e2bb9be | ||
|
|
a383ca1866 | ||
|
|
f1652b2951 | ||
|
|
cbfbd9712a | ||
|
|
9817553c66 | ||
|
|
6fce2d7288 | ||
|
|
107275238c | ||
|
|
3b77c784e2 | ||
|
|
d46df728fd | ||
|
|
8eb3ecc755 | ||
|
|
97c8a2f1cf | ||
|
|
a492fa3866 | ||
|
|
e165f84184 | ||
|
|
686eda9d48 | ||
|
|
70c5fe5cd8 | ||
|
|
32978176b1 | ||
|
|
1761909bca | ||
|
|
7d8f57083e | ||
|
|
a3c8533d74 | ||
|
|
0b3b012817 | ||
|
|
583c4074a5 | ||
|
|
922e53c68c | ||
|
|
17bc3b83db | ||
|
|
fab3370254 | ||
|
|
ee67278038 | ||
|
|
b9bd9d147f | ||
|
|
ec4beb1b39 | ||
|
|
31d3181e1e | ||
|
|
9b93d621b1 | ||
|
|
bd7f9aad05 | ||
|
|
48a024e032 | ||
|
|
bd02178e96 | ||
|
|
214fdb099c | ||
|
|
d622c09d09 | ||
|
|
529559712b | ||
|
|
293d3e5de9 | ||
|
|
f8464866eb |
321 changed files with 22049 additions and 11645 deletions
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
|
|
@ -1,6 +1,8 @@
|
|||
<!--
|
||||
BEFORE you submit your PR, please check out the PR guidelines
|
||||
on our wiki: https://wiki.hyprland.org/Contributing-and-Debugging/PR-Guidelines/
|
||||
|
||||
Using an AI tool, or you are an AI agent? Check our AI Policy first: https://github.com/hyprwm/.github/blob/main/policies/AI_USAGE.md
|
||||
-->
|
||||
|
||||
|
||||
|
|
|
|||
33
.github/workflows/ci.yaml
vendored
33
.github/workflows/ci.yaml
vendored
|
|
@ -46,11 +46,38 @@ jobs:
|
|||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork
|
||||
name: "Code Style"
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: archlinux
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# - name: clang-format check
|
||||
# uses: jidicula/clang-format-action@v4.16.0
|
||||
# with:
|
||||
# exclude-regex: ^subprojects$
|
||||
|
||||
- name: Install clang-format
|
||||
run: |
|
||||
pacman --noconfirm --noprogressbar -Syyu
|
||||
pacman --noconfirm --noprogressbar -Sy clang
|
||||
|
||||
- name: clang-format check
|
||||
uses: jidicula/clang-format-action@v4.16.0
|
||||
with:
|
||||
exclude-regex: ^subprojects$
|
||||
run: .github/workflows/clang-format-check.sh "." "llvm" "^subprojects$" ""
|
||||
|
||||
- name: Save PR head commit SHA
|
||||
if: failure() && github.event_name == 'pull_request'
|
||||
shell: bash
|
||||
run: |
|
||||
SHA="${{ github.event.pull_request.head.sha }}"
|
||||
echo "SHA=$SHA" >> $GITHUB_ENV
|
||||
- name: Save latest commit SHA if not PR
|
||||
if: failure() && github.event_name != 'pull_request'
|
||||
shell: bash
|
||||
run: echo "SHA=${{ github.sha }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Report failure in job summary
|
||||
if: failure()
|
||||
run: |
|
||||
DEEPLINK="${{ github.server_url }}/${{ github.repository }}/commit/${{ env.SHA }}"
|
||||
echo -e "Format check failed on commit [${GITHUB_SHA:0:8}]($DEEPLINK) with files:\n$(<$GITHUB_WORKSPACE/failing-files.txt)" >> $GITHUB_STEP_SUMMARY
|
||||
|
|
|
|||
92
.github/workflows/clang-format-check.sh
vendored
Executable file
92
.github/workflows/clang-format-check.sh
vendored
Executable file
|
|
@ -0,0 +1,92 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Adapted from https://github.com/jidicula/clang-format-action
|
||||
|
||||
###############################################################################
|
||||
# check.sh #
|
||||
###############################################################################
|
||||
# USAGE: ./entrypoint.sh [<path>] [<fallback style>]
|
||||
#
|
||||
# Checks all C/C++/Protobuf/CUDA files (.h, .H, .hpp, .hh, .h++, .hxx and .c,
|
||||
# .C, .cpp, .cc, .c++, .cxx, .proto, .cu) in the provided GitHub repository path
|
||||
# (arg1) for conforming to clang-format. If no path is provided or provided path
|
||||
# is not a directory, all C/C++/Protobuf/CUDA files are checked. If any files
|
||||
# are incorrectly formatted, the script lists them and exits with 1.
|
||||
#
|
||||
# Define your own formatting rules in a .clang-format file at your repository
|
||||
# root. Otherwise, the provided style guide (arg2) is used as a fallback.
|
||||
|
||||
# format_diff function
|
||||
# Accepts a filepath argument. The filepath passed to this function must point
|
||||
# to a C/C++/Protobuf/CUDA file.
|
||||
format_diff() {
|
||||
local filepath="$1"
|
||||
|
||||
# Invoke clang-format with dry run and formatting error output
|
||||
local_format="$(clang-format \
|
||||
--dry-run \
|
||||
--Werror \
|
||||
--style=file \
|
||||
--fallback-style="$FALLBACK_STYLE" \
|
||||
"${filepath}")"
|
||||
|
||||
local format_status="$?"
|
||||
if [[ ${format_status} -ne 0 ]]; then
|
||||
# Append Markdown-bulleted monospaced filepath of failing file to
|
||||
# summary file.
|
||||
echo "* \`$filepath\`" >>failing-files.txt
|
||||
|
||||
echo "Failed on file: $filepath" >&2
|
||||
echo "$local_format" >&2
|
||||
exit_code=1 # flip the global exit code
|
||||
return "${format_status}"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
CHECK_PATH="$1"
|
||||
FALLBACK_STYLE="$2"
|
||||
EXCLUDE_REGEX="$3"
|
||||
INCLUDE_REGEX="$4"
|
||||
|
||||
# Set the regex to an empty string regex if nothing was provided
|
||||
if [[ -z $EXCLUDE_REGEX ]]; then
|
||||
EXCLUDE_REGEX="^$"
|
||||
fi
|
||||
|
||||
# Set the filetype regex if nothing was provided.
|
||||
# Find all C/C++/Protobuf/CUDA files:
|
||||
# h, H, hpp, hh, h++, hxx
|
||||
# c, C, cpp, cc, c++, cxx
|
||||
# ino, pde
|
||||
# proto
|
||||
# cu
|
||||
if [[ -z $INCLUDE_REGEX ]]; then
|
||||
INCLUDE_REGEX='^.*\.((((c|C)(c|pp|xx|\+\+)?$)|((h|H)h?(pp|xx|\+\+)?$))|(ino|pde|proto|cu))$'
|
||||
fi
|
||||
|
||||
cd "$GITHUB_WORKSPACE" || exit 2
|
||||
|
||||
if [[ ! -d $CHECK_PATH ]]; then
|
||||
echo "Not a directory in the workspace, fallback to all files." >&2
|
||||
CHECK_PATH="."
|
||||
fi
|
||||
|
||||
# initialize exit code
|
||||
exit_code=0
|
||||
|
||||
# All files improperly formatted will be printed to the output.
|
||||
src_files=$(find "$CHECK_PATH" -name .git -prune -o -regextype posix-egrep -regex "$INCLUDE_REGEX" -print)
|
||||
|
||||
# check formatting in each source file
|
||||
IFS=$'\n' # Loop below should separate on new lines, not spaces.
|
||||
for file in $src_files; do
|
||||
# Only check formatting if the path doesn't match the regex
|
||||
if ! [[ ${file} =~ $EXCLUDE_REGEX ]]; then
|
||||
format_diff "${file}"
|
||||
fi
|
||||
done
|
||||
|
||||
# global exit code is flipped to nonzero if any invocation of `format_diff` has
|
||||
# a formatting difference.
|
||||
exit "$exit_code"
|
||||
|
|
@ -110,6 +110,7 @@ add_compile_options(
|
|||
-Wno-narrowing
|
||||
-Wno-pointer-arith
|
||||
-Wno-clobbered
|
||||
-frtti
|
||||
-fmacro-prefix-map=${CMAKE_SOURCE_DIR}/=)
|
||||
|
||||
# disable lto as it may break plugins
|
||||
|
|
@ -124,6 +125,7 @@ find_package(Threads REQUIRED)
|
|||
|
||||
set(GLES_VERSION "GLES3")
|
||||
find_package(OpenGL REQUIRED COMPONENTS ${GLES_VERSION})
|
||||
find_package(glslang CONFIG REQUIRED)
|
||||
|
||||
set(AQUAMARINE_MINIMUM_VERSION 0.9.3)
|
||||
set(HYPRLANG_MINIMUM_VERSION 0.6.7)
|
||||
|
|
@ -243,7 +245,7 @@ configure_file(
|
|||
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_MINIMUM_VERSION 1.22.91)
|
||||
set(WAYLAND_SERVER_PROTOCOLS_MINIMUM_VERSION 1.45)
|
||||
set(LIBINPUT_MINIMUM_VERSION 1.28)
|
||||
|
||||
|
|
@ -265,7 +267,8 @@ pkg_check_modules(
|
|||
gbm
|
||||
gio-2.0
|
||||
re2
|
||||
muparser)
|
||||
muparser
|
||||
lcms2)
|
||||
|
||||
find_package(hyprwayland-scanner 0.3.10 REQUIRED)
|
||||
|
||||
|
|
@ -298,21 +301,6 @@ if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
|
|||
target_compile_options(hyprland_lib PUBLIC -fsanitize=address)
|
||||
endif()
|
||||
|
||||
if(USE_TRACY)
|
||||
message(STATUS "Tracy is turned on")
|
||||
|
||||
option(TRACY_ENABLE "" ON)
|
||||
option(TRACY_ON_DEMAND "" ON)
|
||||
add_subdirectory(subprojects/tracy)
|
||||
|
||||
target_link_libraries(hyprland_lib PUBLIC Tracy::TracyClient)
|
||||
|
||||
if(USE_TRACY_GPU)
|
||||
message(STATUS "Tracy GPU Profiling is turned on")
|
||||
add_compile_definitions(USE_TRACY_GPU)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_compile_options(-fno-pie -fno-builtin)
|
||||
add_link_options(-no-pie -fno-builtin)
|
||||
if(USE_GPROF)
|
||||
|
|
@ -321,6 +309,23 @@ if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
|
|||
endif()
|
||||
endif()
|
||||
|
||||
if(USE_TRACY)
|
||||
message(STATUS "Tracy is turned on")
|
||||
|
||||
option(TRACY_ENABLE "" ON)
|
||||
option(TRACY_ON_DEMAND "" ON)
|
||||
add_subdirectory(subprojects/tracy)
|
||||
|
||||
add_compile_options(-fno-omit-frame-pointer)
|
||||
|
||||
target_link_libraries(hyprland_lib PUBLIC Tracy::TracyClient)
|
||||
|
||||
if(USE_TRACY_GPU)
|
||||
message(STATUS "Tracy GPU Profiling is turned on")
|
||||
add_compile_definitions(USE_TRACY_GPU)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(BUILT_WITH_NIX)
|
||||
add_compile_definitions(BUILT_WITH_NIX)
|
||||
endif()
|
||||
|
|
@ -474,7 +479,11 @@ function(protocolWayland)
|
|||
set(PROTOCOL_SOURCES "${PROTOCOL_SOURCES}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
target_link_libraries(hyprland_lib PUBLIC OpenGL::EGL OpenGL::GL Threads::Threads)
|
||||
if(TARGET OpenGL::GL)
|
||||
target_link_libraries(hyprland_lib PUBLIC OpenGL::EGL OpenGL::GL glslang::glslang glslang::glslang-default-resource-limits glslang::SPIRV Threads::Threads)
|
||||
else()
|
||||
target_link_libraries(hyprland_lib PUBLIC OpenGL::EGL OpenGL::GLES3 glslang::glslang glslang::glslang-default-resource-limits glslang::SPIRV Threads::Threads)
|
||||
endif()
|
||||
|
||||
pkg_check_modules(hyprland_protocols_dep hyprland-protocols>=0.6.4)
|
||||
if(hyprland_protocols_dep_FOUND)
|
||||
|
|
@ -549,6 +558,8 @@ 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)
|
||||
protocolnew("staging/ext-image-capture-source" "ext-image-capture-source-v1" false)
|
||||
protocolnew("staging/ext-image-copy-capture" "ext-image-copy-capture-v1" false)
|
||||
|
||||
protocolwayland()
|
||||
|
||||
|
|
|
|||
2
LICENSE
2
LICENSE
|
|
@ -1,6 +1,6 @@
|
|||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2022-2025, vaxerski
|
||||
Copyright (c) 2022-2026, vaxerski
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
|
|
|||
3
Makefile
3
Makefile
|
|
@ -18,6 +18,7 @@ nopch:
|
|||
clear:
|
||||
rm -rf build
|
||||
rm -f ./protocols/*.h ./protocols/*.c ./protocols/*.cpp ./protocols/*.hpp
|
||||
rm -f ./hyprctl/hw-protocols/*.cpp ./hyprctl/hw-protocols/*.hpp
|
||||
|
||||
all:
|
||||
$(MAKE) clear
|
||||
|
|
@ -87,7 +88,7 @@ asan:
|
|||
@echo "Wayland done"
|
||||
|
||||
patch -p1 < ./scripts/hyprlandStaticAsan.diff
|
||||
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -DWITH_ASAN:STRING=True -DUSE_TRACY:STRING=False -DUSE_TRACY_GPU:STRING=False -S . -B ./build -G Ninja
|
||||
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -DWITH_ASAN:STRING=True -DUSE_TRACY:STRING=False -DUSE_TRACY_GPU:STRING=False -S . -B ./build
|
||||
cmake --build ./build --config Debug --target all
|
||||
@echo "Hyprland done"
|
||||
|
||||
|
|
|
|||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
0.53.0
|
||||
0.54.0
|
||||
|
|
|
|||
|
|
@ -242,7 +242,7 @@ bind = $mainMod, E, exec, $fileManager
|
|||
bind = $mainMod, V, togglefloating,
|
||||
bind = $mainMod, R, exec, $menu
|
||||
bind = $mainMod, P, pseudo, # dwindle
|
||||
bind = $mainMod, J, togglesplit, # dwindle
|
||||
bind = $mainMod, J, layoutmsg, togglesplit # dwindle
|
||||
|
||||
# Move focus with mainMod + arrow keys
|
||||
bind = $mainMod, left, movefocus, l
|
||||
|
|
|
|||
64
flake.lock
generated
64
flake.lock
generated
|
|
@ -16,11 +16,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1765900596,
|
||||
"narHash": "sha256-+hn8v9jkkLP9m+o0Nm5SiEq10W0iWDSotH2XfjU45fA=",
|
||||
"lastModified": 1772292445,
|
||||
"narHash": "sha256-4F1Q7U313TKUDDovCC96m/Za4wZcJ3yqtu4eSrj8lk8=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "aquamarine",
|
||||
"rev": "d83c97f8f5c0aae553c1489c7d9eff3eadcadace",
|
||||
"rev": "1dbbba659c1cef0b0202ce92cadfe13bae550e8f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -32,15 +32,15 @@
|
|||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1761588595,
|
||||
"narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=",
|
||||
"owner": "edolstra",
|
||||
"lastModified": 1767039857,
|
||||
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
|
||||
"owner": "NixOS",
|
||||
"repo": "flake-compat",
|
||||
"rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5",
|
||||
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"owner": "NixOS",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
|
|
@ -105,11 +105,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1763733840,
|
||||
"narHash": "sha256-JnET78yl5RvpGuDQy3rCycOCkiKoLr5DN1fPhRNNMco=",
|
||||
"lastModified": 1770511807,
|
||||
"narHash": "sha256-suKmSbSk34uPOJDTg/GbPrKEJutzK08vj0VoTvAFBCA=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprgraphics",
|
||||
"rev": "8f1bec691b2d198c60cccabca7a94add2df4ed1a",
|
||||
"rev": "7c75487edd43a71b61adb01cae8326d277aab683",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -144,11 +144,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1765643131,
|
||||
"narHash": "sha256-CCGohW5EBIRy4B7vTyBMqPgsNcaNenVad/wszfddET0=",
|
||||
"lastModified": 1767023960,
|
||||
"narHash": "sha256-R2HgtVS1G3KSIKAQ77aOZ+Q0HituOmPgXW9nBNkpp3Q=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprland-guiutils",
|
||||
"rev": "e50ae912813bdfa8372d62daf454f48d6df02297",
|
||||
"rev": "c2e906261142f5dd1ee0bfc44abba23e2754c660",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -193,11 +193,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1764612430,
|
||||
"narHash": "sha256-54ltTSbI6W+qYGMchAgCR6QnC1kOdKXN6X6pJhOWxFg=",
|
||||
"lastModified": 1771866172,
|
||||
"narHash": "sha256-fYFoXhQLrm1rD8vSFKQBOEX4OGCuJdLt1amKfHd5GAw=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprlang",
|
||||
"rev": "0d00dc118981531aa731150b6ea551ef037acddd",
|
||||
"rev": "0b219224910e7642eb0ed49f0db5ec3d008e3e41",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -261,11 +261,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1766160771,
|
||||
"narHash": "sha256-roINUGikWRqqgKrD4iotKbGj3ZKJl3hjMz5l/SyKrHw=",
|
||||
"lastModified": 1771271487,
|
||||
"narHash": "sha256-41gEiUS0Pyw3L/ge1l8MXn61cK14VAhgWB/JV8s/oNI=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprutils",
|
||||
"rev": "5ac060bfcf2f12b3a6381156ebbc13826a05b09f",
|
||||
"rev": "340a792e3b3d482c4ae5f66d27a9096bdee6d76d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -284,11 +284,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1763640274,
|
||||
"narHash": "sha256-Uan1Nl9i4TF/kyFoHnTq1bd/rsWh4GAK/9/jDqLbY5A=",
|
||||
"lastModified": 1770501770,
|
||||
"narHash": "sha256-NWRM6+YxTRv+bT9yvlhhJ2iLae1B1pNH3mAL5wi2rlQ=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprwayland-scanner",
|
||||
"rev": "f6cf414ca0e16a4d30198fd670ec86df3c89f671",
|
||||
"rev": "0bd8b6cde9ec27d48aad9e5b4deefb3746909d40",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -310,11 +310,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1766253200,
|
||||
"narHash": "sha256-26qPwrd3od+xoYVywSB7hC2cz9ivN46VPLlrsXyGxvE=",
|
||||
"lastModified": 1771606233,
|
||||
"narHash": "sha256-F3PLUqQ/TwgR70U+UeOqJnihJZ2EuunzojYC4g5xHr0=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprwire",
|
||||
"rev": "1079777525b30a947c8d657fac158e00ae85de9d",
|
||||
"rev": "06c7f1f8c4194786c8400653c4efc49dc14c0f3a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -325,11 +325,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1766070988,
|
||||
"narHash": "sha256-G/WVghka6c4bAzMhTwT2vjLccg/awmHkdKSd2JrycLc=",
|
||||
"lastModified": 1772198003,
|
||||
"narHash": "sha256-I45esRSssFtJ8p/gLHUZ1OUaaTaVLluNkABkk6arQwE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "c6245e83d836d0433170a16eb185cefe0572f8b8",
|
||||
"rev": "dd9b079222d43e1943b6ebd802f04fd959dc8e61",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -348,11 +348,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1765911976,
|
||||
"narHash": "sha256-t3T/xm8zstHRLx+pIHxVpQTiySbKqcQbK+r+01XVKc0=",
|
||||
"lastModified": 1772024342,
|
||||
"narHash": "sha256-+eXlIc4/7dE6EcPs9a2DaSY3fTA9AE526hGqkNID3Wg=",
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"rev": "b68b780b69702a090c8bb1b973bab13756cc7a27",
|
||||
"rev": "6e34e97ed9788b17796ee43ccdbaf871a5c2b476",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
|||
206
flake.nix
206
flake.nix
|
|
@ -88,108 +88,122 @@
|
|||
};
|
||||
};
|
||||
|
||||
outputs = inputs @ {
|
||||
self,
|
||||
nixpkgs,
|
||||
systems,
|
||||
...
|
||||
}: let
|
||||
inherit (nixpkgs) lib;
|
||||
eachSystem = lib.genAttrs (import systems);
|
||||
pkgsFor = eachSystem (system:
|
||||
import nixpkgs {
|
||||
localSystem = system;
|
||||
overlays = with self.overlays; [
|
||||
hyprland-packages
|
||||
hyprland-extras
|
||||
];
|
||||
});
|
||||
pkgsCrossFor = eachSystem (system: crossSystem:
|
||||
import nixpkgs {
|
||||
localSystem = system;
|
||||
inherit crossSystem;
|
||||
overlays = with self.overlays; [
|
||||
hyprland-packages
|
||||
hyprland-extras
|
||||
];
|
||||
});
|
||||
pkgsDebugFor = eachSystem (system:
|
||||
import nixpkgs {
|
||||
localSystem = system;
|
||||
overlays = with self.overlays; [
|
||||
hyprland-debug
|
||||
];
|
||||
});
|
||||
pkgsDebugCrossFor = eachSystem (system: crossSystem:
|
||||
import nixpkgs {
|
||||
localSystem = system;
|
||||
inherit crossSystem;
|
||||
overlays = with self.overlays; [
|
||||
hyprland-debug
|
||||
];
|
||||
});
|
||||
in {
|
||||
overlays = import ./nix/overlays.nix {inherit self lib inputs;};
|
||||
outputs =
|
||||
inputs@{
|
||||
self,
|
||||
nixpkgs,
|
||||
systems,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (nixpkgs) lib;
|
||||
eachSystem = lib.genAttrs (import systems);
|
||||
pkgsFor = eachSystem (
|
||||
system:
|
||||
import nixpkgs {
|
||||
localSystem = system;
|
||||
overlays = with self.overlays; [
|
||||
hyprland-packages
|
||||
hyprland-extras
|
||||
];
|
||||
}
|
||||
);
|
||||
pkgsCrossFor = eachSystem (
|
||||
system: crossSystem:
|
||||
import nixpkgs {
|
||||
localSystem = system;
|
||||
inherit crossSystem;
|
||||
overlays = with self.overlays; [
|
||||
hyprland-packages
|
||||
hyprland-extras
|
||||
];
|
||||
}
|
||||
);
|
||||
pkgsDebugFor = eachSystem (
|
||||
system:
|
||||
import nixpkgs {
|
||||
localSystem = system;
|
||||
overlays = with self.overlays; [
|
||||
hyprland-debug
|
||||
];
|
||||
}
|
||||
);
|
||||
pkgsDebugCrossFor = eachSystem (
|
||||
system: crossSystem:
|
||||
import nixpkgs {
|
||||
localSystem = system;
|
||||
inherit crossSystem;
|
||||
overlays = with self.overlays; [
|
||||
hyprland-debug
|
||||
];
|
||||
}
|
||||
);
|
||||
in
|
||||
{
|
||||
overlays = import ./nix/overlays.nix { inherit self lib inputs; };
|
||||
|
||||
checks = eachSystem (system:
|
||||
(lib.filterAttrs
|
||||
(n: _: (lib.hasPrefix "hyprland" n) && !(lib.hasSuffix "debug" n))
|
||||
self.packages.${system})
|
||||
// {
|
||||
inherit (self.packages.${system}) xdg-desktop-portal-hyprland;
|
||||
pre-commit-check = inputs.pre-commit-hooks.lib.${system}.run {
|
||||
src = ./.;
|
||||
hooks = {
|
||||
hyprland-treewide-formatter = {
|
||||
enable = true;
|
||||
entry = "${self.formatter.${system}}/bin/hyprland-treewide-formatter";
|
||||
pass_filenames = false;
|
||||
excludes = ["subprojects"];
|
||||
always_run = true;
|
||||
checks = eachSystem (
|
||||
system:
|
||||
(lib.filterAttrs (
|
||||
n: _: (lib.hasPrefix "hyprland" n) && !(lib.hasSuffix "debug" n)
|
||||
) self.packages.${system})
|
||||
// {
|
||||
inherit (self.packages.${system}) xdg-desktop-portal-hyprland;
|
||||
pre-commit-check = inputs.pre-commit-hooks.lib.${system}.run {
|
||||
src = ./.;
|
||||
hooks = {
|
||||
hyprland-treewide-formatter = {
|
||||
enable = true;
|
||||
entry = "${self.formatter.${system}}/bin/hyprland-treewide-formatter";
|
||||
pass_filenames = false;
|
||||
excludes = [ "subprojects" ];
|
||||
always_run = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
// (import ./nix/tests inputs pkgsFor.${system}));
|
||||
}
|
||||
// (import ./nix/tests inputs pkgsFor.${system})
|
||||
);
|
||||
|
||||
packages = eachSystem (system: {
|
||||
default = self.packages.${system}.hyprland;
|
||||
inherit
|
||||
(pkgsFor.${system})
|
||||
# hyprland-packages
|
||||
hyprland
|
||||
hyprland-unwrapped
|
||||
hyprland-with-tests
|
||||
# hyprland-extras
|
||||
xdg-desktop-portal-hyprland
|
||||
;
|
||||
inherit (pkgsDebugFor.${system}) hyprland-debug;
|
||||
hyprland-cross = (pkgsCrossFor.${system} "aarch64-linux").hyprland;
|
||||
hyprland-debug-cross = (pkgsDebugCrossFor.${system} "aarch64-linux").hyprland-debug;
|
||||
});
|
||||
packages = eachSystem (system: {
|
||||
default = self.packages.${system}.hyprland;
|
||||
inherit (pkgsFor.${system})
|
||||
# hyprland-packages
|
||||
hyprland
|
||||
hyprland-unwrapped
|
||||
hyprland-with-tests
|
||||
# hyprland-extras
|
||||
xdg-desktop-portal-hyprland
|
||||
;
|
||||
inherit (pkgsDebugFor.${system}) hyprland-debug;
|
||||
hyprland-cross = (pkgsCrossFor.${system} "aarch64-linux").hyprland;
|
||||
hyprland-debug-cross = (pkgsDebugCrossFor.${system} "aarch64-linux").hyprland-debug;
|
||||
});
|
||||
|
||||
devShells = eachSystem (system: {
|
||||
default =
|
||||
pkgsFor.${system}.mkShell.override {
|
||||
inherit (self.packages.${system}.default) stdenv;
|
||||
} {
|
||||
name = "hyprland-shell";
|
||||
hardeningDisable = ["fortify"];
|
||||
inputsFrom = [pkgsFor.${system}.hyprland];
|
||||
packages = [pkgsFor.${system}.clang-tools];
|
||||
inherit (self.checks.${system}.pre-commit-check) shellHook;
|
||||
};
|
||||
});
|
||||
devShells = eachSystem (system: {
|
||||
default =
|
||||
pkgsFor.${system}.mkShell.override
|
||||
{
|
||||
inherit (self.packages.${system}.default) stdenv;
|
||||
}
|
||||
{
|
||||
name = "hyprland-shell";
|
||||
hardeningDisable = [ "fortify" ];
|
||||
inputsFrom = [ pkgsFor.${system}.hyprland ];
|
||||
packages = [ pkgsFor.${system}.clang-tools ];
|
||||
inherit (self.checks.${system}.pre-commit-check) shellHook;
|
||||
};
|
||||
});
|
||||
|
||||
formatter = eachSystem (system: pkgsFor.${system}.callPackage ./nix/formatter.nix {});
|
||||
formatter = eachSystem (system: pkgsFor.${system}.callPackage ./nix/formatter.nix { });
|
||||
|
||||
nixosModules.default = import ./nix/module.nix inputs;
|
||||
homeManagerModules.default = import ./nix/hm-module.nix self;
|
||||
nixosModules.default = import ./nix/module.nix inputs;
|
||||
homeManagerModules.default = import ./nix/hm-module.nix self;
|
||||
|
||||
# Hydra build jobs
|
||||
# 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
|
||||
# be added by merging, e.g., self.packages // self.devShells.
|
||||
hydraJobs = self.packages;
|
||||
};
|
||||
# Hydra build jobs
|
||||
# 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
|
||||
# be added by merging, e.g., self.packages // self.devShells.
|
||||
hydraJobs = self.packages;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="hyprpaper_core" version="1">
|
||||
<protocol name="hyprpaper_core" version="2">
|
||||
<copyright>
|
||||
BSD 3-Clause License
|
||||
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
</copyright>
|
||||
|
||||
<object name="hyprpaper_core_manager" version="1">
|
||||
<object name="hyprpaper_core_manager" version="2">
|
||||
<description summary="manager object">
|
||||
This is the core manager object for hyprpaper operations
|
||||
</description>
|
||||
|
|
@ -62,6 +62,13 @@
|
|||
Destroys this object. Children remain alive until destroyed.
|
||||
</description>
|
||||
</c2s>
|
||||
|
||||
<c2s name="get_status_object" since="2">
|
||||
<description summary="Get a status object">
|
||||
Creates a status object
|
||||
</description>
|
||||
<returns iface="hyprpaper_status"/>
|
||||
</c2s>
|
||||
</object>
|
||||
|
||||
<enum name="wallpaper_fit_mode">
|
||||
|
|
@ -141,4 +148,25 @@
|
|||
</description>
|
||||
</c2s>
|
||||
</object>
|
||||
|
||||
<object name="hyprpaper_status" version="2">
|
||||
<description summary="status object">
|
||||
This is an object which will emit various status updates.
|
||||
</description>
|
||||
|
||||
<s2c name="active_wallpaper">
|
||||
<description summary="Active wallpaper state">
|
||||
Sends the active wallpaper for a given monitor. This will be emitted
|
||||
immediately after binding, and then every time the path changes.
|
||||
</description>
|
||||
<arg name="monitor" type="varchar" summary="monitor name"/>
|
||||
<arg name="path" type="varchar" summary="wallpaper path"/>
|
||||
</s2c>
|
||||
|
||||
<c2s name="destroy" destructor="true">
|
||||
<description summary="Destroy this object">
|
||||
Destroys this object.
|
||||
</description>
|
||||
</c2s>
|
||||
</object>
|
||||
</protocol>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include <optional>
|
||||
#include <format>
|
||||
#include <filesystem>
|
||||
#include <print>
|
||||
|
||||
#include <hyprpaper_core-client.hpp>
|
||||
|
||||
|
|
@ -15,7 +16,7 @@ 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;
|
||||
constexpr const uint32_t PROTOCOL_VERSION_SUPPORTED = 2;
|
||||
|
||||
//
|
||||
static hyprpaperCoreWallpaperFitMode fitFromString(const std::string_view& sv) {
|
||||
|
|
@ -53,21 +54,7 @@ static std::expected<std::string, std::string> getFullPath(const std::string_vie
|
|||
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");
|
||||
|
||||
static std::expected<void, std::string> doWallpaper(const std::string_view& RHS) {
|
||||
CVarList2 args(std::string{RHS}, 0, ',');
|
||||
|
||||
const std::string MONITOR = std::string{args[0]};
|
||||
|
|
@ -99,7 +86,7 @@ std::expected<void, std::string> Hyprpaper::makeHyprpaperRequest(const std::stri
|
|||
if (!socket)
|
||||
return std::unexpected("can't send: failed to connect to hyprpaper (is it running?)");
|
||||
|
||||
g_coreImpl = makeShared<CCHyprpaperCoreImpl>(1);
|
||||
g_coreImpl = makeShared<CCHyprpaperCoreImpl>(PROTOCOL_VERSION_SUPPORTED);
|
||||
|
||||
socket->addImplementation(g_coreImpl);
|
||||
|
||||
|
|
@ -111,7 +98,7 @@ std::expected<void, std::string> Hyprpaper::makeHyprpaperRequest(const std::stri
|
|||
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));
|
||||
auto manager = makeShared<CCHyprpaperCoreManagerObject>(socket->bindProtocol(g_coreImpl->protocol(), std::min(PROTOCOL_VERSION_SUPPORTED, spec->specVer())));
|
||||
|
||||
if (!manager)
|
||||
return std::unexpected("wire error: couldn't create manager");
|
||||
|
|
@ -126,7 +113,11 @@ std::expected<void, std::string> Hyprpaper::makeHyprpaperRequest(const std::stri
|
|||
|
||||
wallpaper->setFailed([&canExit, &err](uint32_t code) {
|
||||
canExit = true;
|
||||
err = std::format("failed to set wallpaper, code {}", code);
|
||||
switch (code) {
|
||||
case HYPRPAPER_CORE_APPLYING_ERROR_INVALID_PATH: err = std::format("failed to set wallpaper: Invalid path", code); break;
|
||||
case HYPRPAPER_CORE_APPLYING_ERROR_INVALID_MONITOR: err = std::format("failed to set wallpaper: Invalid monitor", code); break;
|
||||
default: err = std::format("failed to set wallpaper: unknown error, code {}", code); break;
|
||||
}
|
||||
});
|
||||
wallpaper->setSuccess([&canExit]() { canExit = true; });
|
||||
|
||||
|
|
@ -145,4 +136,73 @@ std::expected<void, std::string> Hyprpaper::makeHyprpaperRequest(const std::stri
|
|||
return std::unexpected(*err);
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
static std::expected<void, std::string> doListActive() {
|
||||
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)");
|
||||
|
||||
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>(PROTOCOL_VERSION_SUPPORTED);
|
||||
|
||||
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?!");
|
||||
|
||||
if (spec->specVer() < 2)
|
||||
return std::unexpected("can't send: hyprpaper protocol version too low (hyprpaper too old)");
|
||||
|
||||
auto manager = makeShared<CCHyprpaperCoreManagerObject>(socket->bindProtocol(g_coreImpl->protocol(), std::min(PROTOCOL_VERSION_SUPPORTED, spec->specVer())));
|
||||
|
||||
if (!manager)
|
||||
return std::unexpected("wire error: couldn't create manager");
|
||||
|
||||
auto status = makeShared<CCHyprpaperStatusObject>(manager->sendGetStatusObject());
|
||||
|
||||
status->setActiveWallpaper([](const char* mon, const char* wp) { std::println("{}: {}", mon, wp); });
|
||||
|
||||
socket->roundtrip();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
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 doWallpaper(RHS);
|
||||
else if (LHS == "listactive")
|
||||
return doListActive();
|
||||
else
|
||||
return std::unexpected("invalid hyprpaper request");
|
||||
|
||||
return {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -228,23 +228,23 @@ int request(std::string_view arg, int minArgs = 0, bool needRoll = false) {
|
|||
constexpr size_t BUFFER_SIZE = 8192;
|
||||
char buffer[BUFFER_SIZE] = {0};
|
||||
|
||||
sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE);
|
||||
|
||||
if (sizeWritten < 0) {
|
||||
if (errno == EWOULDBLOCK)
|
||||
log("Hyprland IPC didn't respond in time\n");
|
||||
log("Couldn't read (6)");
|
||||
return 6;
|
||||
}
|
||||
|
||||
reply += std::string(buffer, sizeWritten);
|
||||
|
||||
while (sizeWritten == BUFFER_SIZE) {
|
||||
// read all data until server closes the connection
|
||||
// this handles partial writes on the server side under high load
|
||||
while (true) {
|
||||
sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE);
|
||||
|
||||
if (sizeWritten < 0) {
|
||||
if (errno == EWOULDBLOCK)
|
||||
log("Hyprland IPC didn't respond in time\n");
|
||||
log("Couldn't read (6)");
|
||||
return 6;
|
||||
}
|
||||
|
||||
if (sizeWritten == 0) {
|
||||
// server closed connection, we're done
|
||||
break;
|
||||
}
|
||||
|
||||
reply += std::string(buffer, sizeWritten);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ set(CMAKE_CXX_STANDARD 23)
|
|||
|
||||
pkg_check_modules(hyprpm_deps REQUIRED IMPORTED_TARGET tomlplusplus hyprutils>=0.7.0)
|
||||
|
||||
find_package(glaze 6.0.0 QUIET)
|
||||
find_package(glaze 6.0.1 QUIET)
|
||||
if (NOT glaze_FOUND)
|
||||
set(GLAZE_VERSION v6.1.0)
|
||||
set(GLAZE_VERSION v6.0.1)
|
||||
message(STATUS "glaze dependency not found, retrieving ${GLAZE_VERSION} with FetchContent")
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
|
|
@ -21,6 +21,7 @@ if (NOT glaze_FOUND)
|
|||
GIT_REPOSITORY https://github.com/stephenberry/glaze.git
|
||||
GIT_TAG ${GLAZE_VERSION}
|
||||
GIT_SHALLOW TRUE
|
||||
EXCLUDE_FROM_ALL
|
||||
)
|
||||
FetchContent_MakeAvailable(glaze)
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -94,15 +94,18 @@ SHyprlandVersion CPluginManager::getHyprlandVersion(bool running) {
|
|||
auto hldate = (*jsonQuery)["commit_date"].get_string();
|
||||
auto hlcommits = (*jsonQuery)["commits"].get_string();
|
||||
|
||||
auto flags = (*jsonQuery)["flags"].get_array();
|
||||
bool isNix = std::ranges::any_of(flags, [](const auto& f) { return f.is_string() && f.get_string() == std::string_view{"nix"}; });
|
||||
|
||||
size_t commits = 0;
|
||||
try {
|
||||
commits = std::stoull(hlcommits);
|
||||
} catch (...) { ; }
|
||||
|
||||
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 {}, nix: {}", hlcommit, hlbranch, hldate, commits, isNix));
|
||||
|
||||
auto ver = SHyprlandVersion{hlbranch, hlcommit, hldate, abiHash, commits};
|
||||
auto ver = SHyprlandVersion{hlbranch, hlcommit, hldate, abiHash, commits, isNix};
|
||||
|
||||
if (running)
|
||||
verRunning = ver;
|
||||
|
|
@ -128,12 +131,20 @@ bool CPluginManager::createSafeDirectory(const std::string& path) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool CPluginManager::validArg(const std::string& s) {
|
||||
return !s.contains("'") && !s.ends_with("\\") && !s.starts_with("\\");
|
||||
}
|
||||
|
||||
bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string& rev) {
|
||||
const auto HLVER = getHyprlandVersion();
|
||||
|
||||
if (!validArg(url) || !validArg(rev)) {
|
||||
std::println(stderr, "\n{}", failureString("url or rev invalid"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!hasDeps()) {
|
||||
std::println(stderr, "\n{}",
|
||||
failureString("Could not clone the plugin repository. Dependencies not satisfied. Hyprpm requires: cmake, meson, cpio, pkg-config, git, g++, gcc"));
|
||||
std::println(stderr, "\n{}", failureString("Could not clone the plugin repository. Dependencies not satisfied. Hyprpm requires: cmake, cpio, pkg-config, git, g++, gcc"));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -196,7 +207,7 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
|
|||
|
||||
progress.printMessageAbove(infoString("Cloning {}", url));
|
||||
|
||||
std::string ret = execAndGet(std::format("cd {} && git clone --recursive {} {}", getTempRoot(), url, USERNAME));
|
||||
std::string ret = execAndGet(std::format("cd {} && git clone --recursive '{}' {}", getTempRoot(), url, USERNAME));
|
||||
|
||||
if (!std::filesystem::exists(m_szWorkingPluginDirectory + "/.git")) {
|
||||
std::println(stderr, "\n{}", failureString("Could not clone the plugin repository. shell returned:\n{}", ret));
|
||||
|
|
@ -303,8 +314,14 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
|
|||
progress.printMessageAbove(infoString("Building {}", p.name));
|
||||
|
||||
for (auto const& bs : p.buildSteps) {
|
||||
const std::string& cmd = std::format("cd {} && PKG_CONFIG_PATH=\"{}/share/pkgconfig\" {}", m_szWorkingPluginDirectory, DataState::getHeadersPath(), bs);
|
||||
out += " -> " + cmd + "\n" + execAndGet(cmd) + "\n";
|
||||
const auto CMD_RAW = nixDevelopIfNeeded(std::format("cd {} && PKG_CONFIG_PATH=\"{}\" {}", m_szWorkingPluginDirectory, getPkgConfigPath(), bs), HLVER);
|
||||
|
||||
if (!CMD_RAW) {
|
||||
progress.printMessageAbove(failureString("Failed to build {}: {}", p.name, CMD_RAW.error()));
|
||||
break;
|
||||
}
|
||||
|
||||
out += " -> " + *CMD_RAW + "\n" + execAndGet(*CMD_RAW) + "\n";
|
||||
}
|
||||
|
||||
if (m_bVerbose)
|
||||
|
|
@ -389,7 +406,7 @@ eHeadersErrors CPluginManager::headersValid() {
|
|||
return HEADERS_MISSING;
|
||||
|
||||
// find headers commit
|
||||
const std::string& cmd = std::format("PKG_CONFIG_PATH=\"{}/share/pkgconfig\" pkgconf --cflags --keep-system-cflags hyprland", DataState::getHeadersPath());
|
||||
const std::string& cmd = std::format("PKG_CONFIG_PATH=\"{}\" pkgconf --cflags --keep-system-cflags hyprland", getPkgConfigPath());
|
||||
auto headers = execAndGet(cmd);
|
||||
|
||||
if (!headers.contains("-I/"))
|
||||
|
|
@ -453,7 +470,7 @@ bool CPluginManager::updateHeaders(bool force) {
|
|||
const auto HLVER = getHyprlandVersion(false);
|
||||
|
||||
if (!hasDeps()) {
|
||||
std::println("\n{}", failureString("Could not update. Dependencies not satisfied. Hyprpm requires: cmake, meson, cpio, pkg-config, git, g++, gcc"));
|
||||
std::println("\n{}", failureString("Could not update. Dependencies not satisfied. Hyprpm requires: cmake, cpio, pkg-config, git, g++, gcc"));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -495,11 +512,11 @@ bool CPluginManager::updateHeaders(bool force) {
|
|||
progress.printMessageAbove(verboseString("will shallow since: {}", SHALLOW_DATE));
|
||||
|
||||
std::string ret =
|
||||
execAndGet(std::format("cd {} && git clone --recursive {} hyprland-{}{}", getTempRoot(), HL_URL, USERNAME, (bShallow ? " --shallow-since='" + SHALLOW_DATE + "'" : "")));
|
||||
execAndGet(std::format("cd {} && git clone --recursive '{}' hyprland-{}{}", getTempRoot(), HL_URL, USERNAME, (bShallow ? " --shallow-since='" + SHALLOW_DATE + "'" : "")));
|
||||
|
||||
if (!std::filesystem::exists(WORKINGDIR)) {
|
||||
progress.printMessageAbove(failureString("Clone failed. Retrying without shallow."));
|
||||
ret = execAndGet(std::format("cd {} && git clone --recursive {} hyprland-{}", getTempRoot(), HL_URL, USERNAME));
|
||||
ret = execAndGet(std::format("cd {} && git clone --recursive '{}' hyprland-{}", getTempRoot(), HL_URL, USERNAME));
|
||||
}
|
||||
|
||||
if (!std::filesystem::exists(WORKINGDIR + "/.git")) {
|
||||
|
|
@ -542,8 +559,17 @@ bool CPluginManager::updateHeaders(bool force) {
|
|||
if (m_bVerbose)
|
||||
progress.printMessageAbove(verboseString("setting PREFIX for cmake to {}", DataState::getHeadersPath()));
|
||||
|
||||
ret = execAndGet(std::format("cd {} && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:STRING=\"{}\" -S . -B ./build -G Ninja", WORKINGDIR,
|
||||
DataState::getHeadersPath()));
|
||||
const auto CONFIGURE_CMD =
|
||||
nixDevelopIfNeeded(std::format("cd {} && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:STRING=\"{}\" -S . -B ./build", WORKINGDIR,
|
||||
DataState::getHeadersPath()),
|
||||
HLVER);
|
||||
|
||||
if (!CONFIGURE_CMD) {
|
||||
std::println(stderr, "\n{}", failureString("Could not configure hyprland: {}", CONFIGURE_CMD.error()));
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = execAndGet(*CONFIGURE_CMD);
|
||||
if (m_bVerbose)
|
||||
progress.printMessageAbove(verboseString("cmake returned: {}", ret));
|
||||
|
||||
|
|
@ -631,7 +657,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
|
|||
const auto HLVER = getHyprlandVersion(false);
|
||||
|
||||
CProgressBar progress;
|
||||
progress.m_iMaxSteps = REPOS.size() * 2 + 2;
|
||||
progress.m_iMaxSteps = (REPOS.size() * 2) + 2;
|
||||
progress.m_iSteps = 0;
|
||||
progress.m_szCurrentMessage = "Updating repositories";
|
||||
progress.print();
|
||||
|
|
@ -652,7 +678,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
|
|||
|
||||
progress.printMessageAbove(infoString("Cloning {}", repo.url));
|
||||
|
||||
std::string ret = execAndGet(std::format("cd {} && git clone --recursive {} {}", getTempRoot(), repo.url, USERNAME));
|
||||
std::string ret = execAndGet(std::format("cd {} && git clone --recursive '{}' {}", getTempRoot(), repo.url, USERNAME));
|
||||
|
||||
if (!std::filesystem::exists(m_szWorkingPluginDirectory + "/.git")) {
|
||||
std::println("{}", failureString("could not clone repo: shell returned: {}", ret));
|
||||
|
|
@ -662,7 +688,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
|
|||
if (!repo.rev.empty()) {
|
||||
progress.printMessageAbove(infoString("Plugin has revision set, resetting: {}", repo.rev));
|
||||
|
||||
std::string ret = execAndGet("git -C " + m_szWorkingPluginDirectory + " reset --hard --recurse-submodules " + repo.rev);
|
||||
std::string ret = execAndGet("git -C " + m_szWorkingPluginDirectory + " reset --hard --recurse-submodules \'" + repo.rev + "\'");
|
||||
if (ret.compare(0, 6, "fatal:") == 0) {
|
||||
std::println(stderr, "\n{}", failureString("could not check out revision {}: shell returned:\n{}", repo.rev, ret));
|
||||
|
||||
|
|
@ -741,8 +767,14 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
|
|||
progress.printMessageAbove(infoString("Building {}", p.name));
|
||||
|
||||
for (auto const& bs : p.buildSteps) {
|
||||
const std::string& cmd = std::format("cd {} && PKG_CONFIG_PATH=\"{}/share/pkgconfig\" {}", m_szWorkingPluginDirectory, DataState::getHeadersPath(), bs);
|
||||
out += " -> " + cmd + "\n" + execAndGet(cmd) + "\n";
|
||||
const auto CMD_RAW = nixDevelopIfNeeded(std::format("cd {} && PKG_CONFIG_PATH=\"{}\" {}", m_szWorkingPluginDirectory, getPkgConfigPath(), bs), HLVER);
|
||||
|
||||
if (!CMD_RAW) {
|
||||
progress.printMessageAbove(failureString("Failed to build {}: {}", p.name, CMD_RAW.error()));
|
||||
break;
|
||||
}
|
||||
|
||||
out += " -> " + *CMD_RAW + "\n" + execAndGet(*CMD_RAW) + "\n";
|
||||
}
|
||||
|
||||
if (m_bVerbose)
|
||||
|
|
@ -772,8 +804,8 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
|
|||
repohash.pop_back();
|
||||
newrepo.hash = repohash;
|
||||
for (auto const& p : pManifest->m_plugins) {
|
||||
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});
|
||||
const auto OLDPLUGINIT = std::ranges::find_if(repo.plugins, [&](const auto& other) { return other.name == p.name; });
|
||||
newrepo.plugins.emplace_back(SPlugin{p.name, m_szWorkingPluginDirectory + "/" + p.output, OLDPLUGINIT != repo.plugins.end() ? OLDPLUGINIT->enabled : false});
|
||||
}
|
||||
DataState::removePluginRepo(SPluginRepoIdentifier::fromName(newrepo.name));
|
||||
DataState::addNewPluginRepo(newrepo);
|
||||
|
|
@ -899,7 +931,7 @@ ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState(bool forceReload)
|
|||
if (!p.enabled)
|
||||
continue;
|
||||
|
||||
if (!forceReload && std::find_if(loadedPlugins.begin(), loadedPlugins.end(), [&](const auto& other) { return other == p.name; }) != loadedPlugins.end())
|
||||
if (!forceReload && std::ranges::find_if(loadedPlugins, [&](const auto& other) { return other == p.name; }) != loadedPlugins.end())
|
||||
continue;
|
||||
|
||||
if (!loadUnloadPlugin(HYPRPMPATH / repoForName(p.name) / p.filename, true)) {
|
||||
|
|
@ -987,8 +1019,11 @@ std::string CPluginManager::headerErrorShort(const eHeadersErrors err) {
|
|||
}
|
||||
|
||||
bool CPluginManager::hasDeps() {
|
||||
if (!m_bNoNix && getHyprlandVersion().isNix)
|
||||
return true; // dep check not needed if we are on nix
|
||||
|
||||
bool hasAllDeps = true;
|
||||
std::vector<std::string> deps = {"meson", "cpio", "cmake", "pkg-config", "g++", "gcc", "git"};
|
||||
std::vector<std::string> deps = {"cpio", "cmake", "pkg-config", "g++", "gcc", "git"};
|
||||
|
||||
for (auto const& d : deps) {
|
||||
if (!execAndGet("command -v " + d).contains("/")) {
|
||||
|
|
@ -999,3 +1034,92 @@ bool CPluginManager::hasDeps() {
|
|||
|
||||
return hasAllDeps;
|
||||
}
|
||||
|
||||
const std::string& CPluginManager::getPkgConfigPath() {
|
||||
static const auto str = std::format("{}/share/pkgconfig:$PKG_CONFIG_PATH", DataState::getHeadersPath());
|
||||
return str;
|
||||
}
|
||||
|
||||
static std::expected<std::string, std::string> getNixDevelopFromPath(const std::string& argv0) {
|
||||
std::string fullStorePath;
|
||||
|
||||
if (argv0.starts_with("/")) {
|
||||
// we can use this directly
|
||||
fullStorePath = argv0;
|
||||
} else {
|
||||
// use hyprpm, find in path
|
||||
auto exe = NSys::findInPath("hyprpm");
|
||||
if (!exe)
|
||||
return std::unexpected("hyprpm not found in PATH");
|
||||
|
||||
fullStorePath = *exe;
|
||||
}
|
||||
|
||||
if (fullStorePath.empty() || !fullStorePath.ends_with("/bin/hyprpm"))
|
||||
return std::unexpected("couldn't get a real path for hyprpm (1)");
|
||||
|
||||
// canonicalize to get the real nix-store path
|
||||
std::error_code ec;
|
||||
fullStorePath = std::filesystem::canonical(fullStorePath, ec);
|
||||
|
||||
if (ec || fullStorePath.empty() || !fullStorePath.starts_with("/nix"))
|
||||
return std::unexpected("couldn't get a real path for hyprpm");
|
||||
|
||||
fullStorePath = fullStorePath.substr(0, fullStorePath.length() - std::string_view{"/bin/hyprpm"}.length());
|
||||
|
||||
auto deriver = trim(execAndGet(std::format("echo \"$(nix-store --query --deriver '{}')\"", fullStorePath)));
|
||||
|
||||
if (deriver.starts_with("unknown"))
|
||||
return std::unexpected("couldn't nix deriver");
|
||||
|
||||
return deriver;
|
||||
}
|
||||
|
||||
static std::expected<std::string, std::string> getNixDevelopFromProfile() {
|
||||
const auto NIX_PROFILE_STR = execAndGet("nix profile list --json");
|
||||
|
||||
auto rawJson = glz::read_json<glz::generic>(NIX_PROFILE_STR);
|
||||
|
||||
if (!rawJson)
|
||||
return std::unexpected("failed to parse nix profile list --json");
|
||||
|
||||
auto& json = *rawJson;
|
||||
|
||||
if (!json.contains("elements") || !json["elements"].is_object())
|
||||
return std::unexpected("nix profile list --json returned a wonky json");
|
||||
|
||||
if (!json["elements"].contains("hyprland") && !json["elements"].contains("Hyprland"))
|
||||
return std::unexpected("nix profile list --json doesn't contain Hyprland (did you uninstall?)");
|
||||
|
||||
auto& hyprlandJson = json["elements"].contains("hyprland") ? json["elements"]["hyprland"] : json["elements"]["Hyprland"];
|
||||
|
||||
if (!hyprlandJson.contains("originalUrl"))
|
||||
return std::unexpected("nix profile list --json's hyprland doesn't contain originalUrl?");
|
||||
|
||||
return hyprlandJson["originalUrl"].get_string();
|
||||
}
|
||||
|
||||
std::expected<std::string, std::string> CPluginManager::nixDevelopIfNeeded(const std::string& cmd, const SHyprlandVersion& ver) {
|
||||
if (m_bNoNix || !ver.isNix)
|
||||
return cmd;
|
||||
|
||||
// Escape single quotes
|
||||
std::string newCmd = cmd;
|
||||
replaceInString(newCmd, "'", "\\'");
|
||||
|
||||
auto NIX_DEVELOP = getNixDevelopFromPath(m_szArgv0);
|
||||
|
||||
if (NIX_DEVELOP)
|
||||
return std::format("nix develop '{}' --command bash -c $'{}'", *NIX_DEVELOP, newCmd);
|
||||
else if (m_bVerbose)
|
||||
std::println("{}", verboseString("Failed nix from path: {}", NIX_DEVELOP.error()));
|
||||
|
||||
NIX_DEVELOP = getNixDevelopFromProfile();
|
||||
|
||||
if (NIX_DEVELOP)
|
||||
return std::format("nix develop '{}' --command bash -c $'{}'", *NIX_DEVELOP, newCmd);
|
||||
else if (m_bVerbose)
|
||||
std::println("{}", verboseString("Failed nix from profile: {}", NIX_DEVELOP.error()));
|
||||
|
||||
return std::unexpected("hyprland is nix, but hyprpm failed to obtain a nix develop shell for build cmd");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <expected>
|
||||
#include "Plugin.hpp"
|
||||
|
||||
enum eHeadersErrors {
|
||||
|
|
@ -41,6 +41,7 @@ struct SHyprlandVersion {
|
|||
std::string date;
|
||||
std::string abiHash;
|
||||
int commits = 0;
|
||||
bool isNix = false;
|
||||
};
|
||||
|
||||
class CPluginManager {
|
||||
|
|
@ -65,20 +66,26 @@ class CPluginManager {
|
|||
|
||||
void notify(const eNotifyIcons icon, uint32_t color, int durationMs, const std::string& message);
|
||||
|
||||
const std::string& getPkgConfigPath();
|
||||
|
||||
bool hasDeps();
|
||||
|
||||
bool m_bVerbose = false;
|
||||
bool m_bNoShallow = false;
|
||||
std::string m_szCustomHlUrl, m_szUsername;
|
||||
bool m_bNoNix = false;
|
||||
std::string m_szCustomHlUrl, m_szUsername, m_szArgv0;
|
||||
|
||||
// will delete recursively if exists!!
|
||||
bool createSafeDirectory(const std::string& path);
|
||||
|
||||
private:
|
||||
std::string headerError(const eHeadersErrors err);
|
||||
std::string headerErrorShort(const eHeadersErrors err);
|
||||
std::string headerError(const eHeadersErrors err);
|
||||
std::string headerErrorShort(const eHeadersErrors err);
|
||||
bool validArg(const std::string& s);
|
||||
|
||||
std::string m_szWorkingPluginDirectory;
|
||||
std::expected<std::string, std::string> nixDevelopIfNeeded(const std::string& cmd, const SHyprlandVersion& ver);
|
||||
|
||||
std::string m_szWorkingPluginDirectory;
|
||||
};
|
||||
|
||||
inline std::unique_ptr<CPluginManager> g_pPluginManager;
|
||||
|
|
|
|||
|
|
@ -35,9 +35,13 @@ static std::string validSubinsAsStr() {
|
|||
}
|
||||
|
||||
static bool executableExistsInPath(const std::string& exe) {
|
||||
return NSys::findInPath(exe).has_value();
|
||||
}
|
||||
|
||||
std::optional<std::string> NSys::findInPath(const std::string& exe) {
|
||||
const char* PATHENV = std::getenv("PATH");
|
||||
if (!PATHENV)
|
||||
return false;
|
||||
return std::nullopt;
|
||||
|
||||
CVarList paths(PATHENV, 0, ':', true);
|
||||
std::error_code ec;
|
||||
|
|
@ -52,10 +56,10 @@ static bool executableExistsInPath(const std::string& exe) {
|
|||
if (ec)
|
||||
continue;
|
||||
if ((perms & std::filesystem::perms::others_exec) != std::filesystem::perms::none)
|
||||
return true;
|
||||
return candidate.string();
|
||||
}
|
||||
|
||||
return false;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static std::string subin() {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
namespace NSys {
|
||||
bool isSuperuser();
|
||||
int getUID();
|
||||
int getEUID();
|
||||
bool isSuperuser();
|
||||
int getUID();
|
||||
int getEUID();
|
||||
std::optional<std::string> findInPath(const std::string& exe);
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
namespace root {
|
||||
|
|
@ -20,4 +22,4 @@ namespace NSys {
|
|||
// Do not use this unless absolutely necessary!
|
||||
std::string runAsSuperuserUnsafe(const std::string& cmd);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ constexpr std::string_view HELP = R"#(┏ hyprpm, a Hyprland Plugin Manager
|
|||
┃
|
||||
┣ Flags:
|
||||
┃
|
||||
┣ --no-nix | → Disable `nix develop` for build commands, even if Hyprland is nix.
|
||||
┣ --notify | -n → Send a hyprland notification confirming successful plugin load.
|
||||
┃ Warnings/Errors trigger notifications regardless of this flag.
|
||||
┣ --help | -h → Show this menu.
|
||||
|
|
@ -47,7 +48,7 @@ int main(int argc, char** argv, char** envp) {
|
|||
}
|
||||
|
||||
std::vector<std::string> command;
|
||||
bool notify = false, verbose = false, force = false, noShallow = false;
|
||||
bool notify = false, verbose = false, force = false, noShallow = false, noNix = false;
|
||||
std::string customHlUrl;
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
|
|
@ -63,6 +64,8 @@ int main(int argc, char** argv, char** envp) {
|
|||
g_pPluginManager->notify(ICON_INFO, 0, 10000, "[hyprpm] -n flag is deprecated, see hyprpm --help.");
|
||||
} else if (ARGS[i] == "--verbose" || ARGS[i] == "-v") {
|
||||
verbose = true;
|
||||
} else if (ARGS[i] == "--no-nix") {
|
||||
noNix = true;
|
||||
} else if (ARGS[i] == "--no-shallow" || ARGS[i] == "-s") {
|
||||
noShallow = true;
|
||||
} else if (ARGS[i] == "--hl-url") {
|
||||
|
|
@ -91,7 +94,9 @@ int main(int argc, char** argv, char** envp) {
|
|||
g_pPluginManager = std::make_unique<CPluginManager>();
|
||||
g_pPluginManager->m_bVerbose = verbose;
|
||||
g_pPluginManager->m_bNoShallow = noShallow;
|
||||
g_pPluginManager->m_bNoNix = noNix;
|
||||
g_pPluginManager->m_szCustomHlUrl = customHlUrl;
|
||||
g_pPluginManager->m_szArgv0 = argv[0];
|
||||
|
||||
if (command[0] == "add") {
|
||||
if (command.size() < 2) {
|
||||
|
|
|
|||
|
|
@ -96,7 +96,9 @@ endfunction()
|
|||
|
||||
protocolnew("staging/pointer-warp" "pointer-warp-v1" false)
|
||||
protocolnew("stable/xdg-shell" "xdg-shell" false)
|
||||
protocolnew("unstable/keyboard-shortcuts-inhibit" "keyboard-shortcuts-inhibit-unstable-v1" false)
|
||||
|
||||
clientNew("pointer-warp" PROTOS "pointer-warp-v1" "xdg-shell")
|
||||
clientNew("pointer-scroll" PROTOS "xdg-shell")
|
||||
clientNew("child-window" PROTOS "xdg-shell")
|
||||
clientNew("child-window" PROTOS "xdg-shell")
|
||||
clientNew("shortcut-inhibitor" PROTOS "xdg-shell" "keyboard-shortcuts-inhibit-unstable-v1")
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include <sys/mman.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <wayland-client.h>
|
||||
#include <wayland.hpp>
|
||||
|
|
@ -332,4 +333,4 @@ int main(int argc, char** argv) {
|
|||
|
||||
wl_display_disconnect(display);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include <sys/poll.h>
|
||||
#include <sys/mman.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <print>
|
||||
#include <format>
|
||||
#include <string>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include <sys/poll.h>
|
||||
#include <sys/mman.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <print>
|
||||
#include <format>
|
||||
#include <string>
|
||||
|
|
|
|||
297
hyprtester/clients/shortcut-inhibitor.cpp
Normal file
297
hyprtester/clients/shortcut-inhibitor.cpp
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
#include <sys/poll.h>
|
||||
#include <sys/mman.h>
|
||||
#include <fcntl.h>
|
||||
#include <print>
|
||||
|
||||
#include <wayland-client.h>
|
||||
#include <wayland.hpp>
|
||||
#include <xdg-shell.hpp>
|
||||
#include <keyboard-shortcuts-inhibit-unstable-v1.hpp>
|
||||
|
||||
#include <hyprutils/memory/SharedPtr.hpp>
|
||||
#include <hyprutils/math/Vector2D.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<CCZwpKeyboardShortcutsInhibitManagerV1> inhibitManager;
|
||||
|
||||
// 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;
|
||||
|
||||
// shortcut inhibiting
|
||||
CSharedPointer<CCZwpKeyboardShortcutsInhibitorV1> inhibitor;
|
||||
};
|
||||
|
||||
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);
|
||||
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...));
|
||||
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));
|
||||
} else if (NAME == "zwp_keyboard_shortcuts_inhibit_manager_v1") {
|
||||
debugLog(" > binding to global: {} (version {}) with id {}", name, version, id);
|
||||
state.inhibitManager = makeShared<CCZwpKeyboardShortcutsInhibitManagerV1>(
|
||||
(wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &zwp_keyboard_shortcuts_inhibit_manager_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.inhibitManager) {
|
||||
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-shortcut-inhibitor";
|
||||
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("shortcut-inhibitor test client");
|
||||
state.xdgToplevel->sendSetAppId("shortcut-inhibitor");
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static void parseRequest(SWlState& state, std::string req) {
|
||||
if (req.starts_with("on")) {
|
||||
state.inhibitor = makeShared<CCZwpKeyboardShortcutsInhibitorV1>(state.inhibitManager->sendInhibitShortcuts(state.surf->resource(), state.wlSeat->resource()));
|
||||
|
||||
state.inhibitor->setActive([&](CCZwpKeyboardShortcutsInhibitorV1* inhibitorV1) { clientLog("inhibiting"); });
|
||||
state.inhibitor->setInactive([&](CCZwpKeyboardShortcutsInhibitorV1* inhibitorV1) { clientLog("inhibit disabled by compositor"); });
|
||||
} else if (req.starts_with("off")) {
|
||||
state.inhibitor->sendDestroy();
|
||||
state.inhibitor.reset();
|
||||
shouldExit = true;
|
||||
clientLog("inhibit disabled by request");
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
@ -6,15 +6,16 @@
|
|||
#define private public
|
||||
#include <src/config/ConfigManager.hpp>
|
||||
#include <src/config/ConfigDescriptions.hpp>
|
||||
#include <src/layout/IHyprLayout.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/layerRule/LayerRuleEffectContainer.hpp>
|
||||
#include <src/desktop/rule/windowRule/WindowRuleApplicator.hpp>
|
||||
#include <src/desktop/view/LayerSurface.hpp>
|
||||
#include <src/Compositor.hpp>
|
||||
#include <src/desktop/state/FocusState.hpp>
|
||||
#include <src/layout/LayoutManager.hpp>
|
||||
#undef private
|
||||
|
||||
#include <hyprutils/utils/ScopeGuard.hpp>
|
||||
|
|
@ -53,8 +54,9 @@ static SDispatchResult snapMove(std::string in) {
|
|||
Vector2D pos = PLASTWINDOW->m_realPosition->goal();
|
||||
Vector2D size = PLASTWINDOW->m_realSize->goal();
|
||||
|
||||
g_pLayoutManager->getCurrentLayout()->performSnap(pos, size, PLASTWINDOW, MBIND_MOVE, -1, size);
|
||||
*PLASTWINDOW->m_realPosition = pos.round();
|
||||
g_layoutManager->performSnap(pos, size, PLASTWINDOW->layoutTarget(), MBIND_MOVE, -1, size);
|
||||
|
||||
PLASTWINDOW->layoutTarget()->setPositionGlobal(CBox{pos, size});
|
||||
|
||||
return {};
|
||||
}
|
||||
|
|
@ -270,32 +272,85 @@ static SDispatchResult keybind(std::string in) {
|
|||
return {};
|
||||
}
|
||||
|
||||
static Desktop::Rule::CWindowRuleEffectContainer::storageType ruleIDX = 0;
|
||||
static Desktop::Rule::CWindowRuleEffectContainer::storageType windowRuleIDX = 0;
|
||||
|
||||
//
|
||||
static SDispatchResult addRule(std::string in) {
|
||||
ruleIDX = Desktop::Rule::windowEffects()->registerEffect("plugin_rule");
|
||||
static SDispatchResult addWindowRule(std::string in) {
|
||||
windowRuleIDX = Desktop::Rule::windowEffects()->registerEffect("plugin_rule");
|
||||
|
||||
if (Desktop::Rule::windowEffects()->registerEffect("plugin_rule") != ruleIDX)
|
||||
if (Desktop::Rule::windowEffects()->registerEffect("plugin_rule") != windowRuleIDX)
|
||||
return {.success = false, .error = "re-registering returned a different id?"};
|
||||
return {};
|
||||
}
|
||||
|
||||
static SDispatchResult checkRule(std::string in) {
|
||||
static SDispatchResult checkWindowRule(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))
|
||||
if (!PLASTWINDOW->m_ruleApplicator->m_otherProps.props.contains(windowRuleIDX))
|
||||
return {.success = false, .error = "No rule"};
|
||||
|
||||
if (PLASTWINDOW->m_ruleApplicator->m_otherProps.props[ruleIDX]->effect != "effect")
|
||||
if (PLASTWINDOW->m_ruleApplicator->m_otherProps.props[windowRuleIDX]->effect != "effect")
|
||||
return {.success = false, .error = "Effect isn't \"effect\""};
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static Desktop::Rule::CLayerRuleEffectContainer::storageType layerRuleIDX = 0;
|
||||
|
||||
static SDispatchResult addLayerRule(std::string in) {
|
||||
layerRuleIDX = Desktop::Rule::layerEffects()->registerEffect("plugin_rule");
|
||||
|
||||
if (Desktop::Rule::layerEffects()->registerEffect("plugin_rule") != layerRuleIDX)
|
||||
return {.success = false, .error = "re-registering returned a different id?"};
|
||||
return {};
|
||||
}
|
||||
|
||||
static SDispatchResult checkLayerRule(std::string in) {
|
||||
if (g_pCompositor->m_layers.size() != 3)
|
||||
return {.success = false, .error = "Layers under test not here"};
|
||||
|
||||
for (const auto& layer : g_pCompositor->m_layers) {
|
||||
if (layer->m_namespace == "rule-layer") {
|
||||
|
||||
if (!layer->m_ruleApplicator->m_otherProps.props.contains(layerRuleIDX))
|
||||
return {.success = false, .error = "No rule"};
|
||||
|
||||
if (layer->m_ruleApplicator->m_otherProps.props[layerRuleIDX]->effect != "effect")
|
||||
return {.success = false, .error = "Effect isn't \"effect\""};
|
||||
|
||||
} else if (layer->m_namespace == "norule-layer") {
|
||||
|
||||
if (layer->m_ruleApplicator->m_otherProps.props.contains(layerRuleIDX))
|
||||
return {.success = false, .error = "Rule even though it shouldn't"};
|
||||
|
||||
} else
|
||||
return {.success = false, .error = "Unrecognized layer"};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static SDispatchResult floatingFocusOnFullscreen(std::string in) {
|
||||
const auto PLASTWINDOW = Desktop::focusState()->window();
|
||||
|
||||
if (!PLASTWINDOW)
|
||||
return {.success = false, .error = "No window"};
|
||||
|
||||
if (!PLASTWINDOW->m_isFloating)
|
||||
return {.success = false, .error = "Window must be floating"};
|
||||
|
||||
if (PLASTWINDOW->m_alpha != 1.f)
|
||||
return {.success = false, .error = "floating window doesnt restore it opacity when focused on fullscreen workspace"};
|
||||
|
||||
if (!PLASTWINDOW->m_createdOverFullscreen)
|
||||
return {.success = false, .error = "floating window doesnt get flagged as createdOverFullscreen"};
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
|
||||
PHANDLE = handle;
|
||||
|
||||
|
|
@ -307,8 +362,11 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
|
|||
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:scroll", ::scroll);
|
||||
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:click", ::click);
|
||||
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:keybind", ::keybind);
|
||||
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:add_rule", ::addRule);
|
||||
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:check_rule", ::checkRule);
|
||||
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:add_window_rule", ::addWindowRule);
|
||||
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:check_window_rule", ::checkWindowRule);
|
||||
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:add_layer_rule", ::addLayerRule);
|
||||
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:check_layer_rule", ::checkLayerRule);
|
||||
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:floating_focus_on_fullscreen", ::floatingFocusOnFullscreen);
|
||||
|
||||
// init mouse
|
||||
g_mouse = CTestMouse::create(false);
|
||||
|
|
|
|||
|
|
@ -39,6 +39,16 @@ namespace Colors {
|
|||
TESTS_PASSED++; \
|
||||
}
|
||||
|
||||
#define EXPECT_NOT(expr, val) \
|
||||
if (const auto RESULT = expr; RESULT == (val)) { \
|
||||
NLog::log("{}Failed: {}{}, expected not {}, got {}. Source: {}@{}.", Colors::RED, Colors::RESET, #expr, val, RESULT, __FILE__, __LINE__); \
|
||||
ret = 1; \
|
||||
TESTS_FAILED++; \
|
||||
} else { \
|
||||
NLog::log("{}Passed: {}{}. Got {}", Colors::GREEN, Colors::RESET, #expr, val); \
|
||||
TESTS_PASSED++; \
|
||||
}
|
||||
|
||||
#define EXPECT_VECTOR2D(expr, val) \
|
||||
do { \
|
||||
const auto& RESULT = expr; \
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include <hyprutils/os/Process.hpp>
|
||||
|
||||
#include <sys/poll.h>
|
||||
#include <unistd.h>
|
||||
#include <csignal>
|
||||
#include <thread>
|
||||
|
||||
|
|
@ -117,7 +118,34 @@ static bool test() {
|
|||
Tests::killAllWindows();
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
|
||||
// test that child windows (shouldBeFloated) are not auto-grouped
|
||||
NLog::log("{}Test child windows are not auto-grouped", Colors::GREEN);
|
||||
auto kitty = Tests::spawnKitty();
|
||||
if (!kitty) {
|
||||
NLog::log("{}Error: kitty did not spawn", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
// create group and enable auto-grouping
|
||||
OK(getFromSocket("/dispatch togglegroup"));
|
||||
OK(getFromSocket("/keyword group:auto_group true"));
|
||||
|
||||
SClient client2;
|
||||
if (!startClient(client2))
|
||||
return false;
|
||||
|
||||
EXPECT(Tests::windowCount(), 2);
|
||||
createChild(client2);
|
||||
EXPECT(Tests::windowCount(), 3);
|
||||
|
||||
// child has set_parent so shouldBeFloated returns true, it should not be auto-grouped
|
||||
EXPECT_COUNT_STRING(getFromSocket("/clients"), "grouped: 0", 1);
|
||||
|
||||
stopClient(client2);
|
||||
Tests::killAllWindows();
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
|
||||
return !ret;
|
||||
}
|
||||
|
||||
REGISTER_CLIENT_TEST_FN(test);
|
||||
REGISTER_CLIENT_TEST_FN(test);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include <hyprutils/os/Process.hpp>
|
||||
|
||||
#include <sys/poll.h>
|
||||
#include <unistd.h>
|
||||
#include <csignal>
|
||||
#include <thread>
|
||||
|
||||
|
|
@ -42,6 +43,7 @@ static bool startClient(SClient& client) {
|
|||
client.readFd = CFileDescriptor(pipeFds2[0]);
|
||||
client.proc->setStdoutFD(pipeFds2[1]);
|
||||
|
||||
const int COUNT_BEFORE = Tests::windowCount();
|
||||
client.proc->runAsync();
|
||||
|
||||
close(pipeFds1[0]);
|
||||
|
|
@ -62,7 +64,16 @@ static bool startClient(SClient& client) {
|
|||
}
|
||||
|
||||
// wait for window to appear
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
|
||||
int counter = 0;
|
||||
while (Tests::processAlive(client.proc->pid()) && Tests::windowCount() == COUNT_BEFORE) {
|
||||
counter++;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
if (counter > 50) {
|
||||
NLog::log("{}pointer-scroll client took too long to open", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include <hyprutils/os/Process.hpp>
|
||||
|
||||
#include <sys/poll.h>
|
||||
#include <unistd.h>
|
||||
#include <csignal>
|
||||
#include <thread>
|
||||
|
||||
|
|
@ -42,6 +43,7 @@ static bool startClient(SClient& client) {
|
|||
client.readFd = CFileDescriptor(pipeFds2[0]);
|
||||
client.proc->setStdoutFD(pipeFds2[1]);
|
||||
|
||||
const int COUNT_BEFORE = Tests::windowCount();
|
||||
client.proc->runAsync();
|
||||
|
||||
close(pipeFds1[0]);
|
||||
|
|
@ -62,7 +64,16 @@ static bool startClient(SClient& client) {
|
|||
}
|
||||
|
||||
// wait for window to appear
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
|
||||
int counter = 0;
|
||||
while (Tests::processAlive(client.proc->pid()) && Tests::windowCount() == COUNT_BEFORE) {
|
||||
counter++;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
if (counter > 50) {
|
||||
NLog::log("{}pointer-warp client took too long to open", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
|
|||
180
hyprtester/src/tests/clients/shortcut-inhibitor.cpp
Normal file
180
hyprtester/src/tests/clients/shortcut-inhibitor.cpp
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
#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>
|
||||
#include <filesystem>
|
||||
|
||||
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) {
|
||||
Tests::killAllWindows();
|
||||
client.proc = makeShared<CProcess>(binaryDir + "/shortcut-inhibitor", 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]);
|
||||
|
||||
const int COUNT_BEFORE = Tests::windowCount();
|
||||
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)) {
|
||||
NLog::log("{}shortcut-inhibitor client failed poll", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
client.readBuf.fill(0);
|
||||
if (read(client.readFd.get(), client.readBuf.data(), client.readBuf.size() - 1) == -1) {
|
||||
NLog::log("{}shortcut-inhibitor client read failed", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string ret = std::string{client.readBuf.data()};
|
||||
if (ret.find("started") == std::string::npos) {
|
||||
NLog::log("{}Failed to start shortcut-inhibitor client, read {}", Colors::RED, ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
// wait for window to appear
|
||||
int counter = 0;
|
||||
while (Tests::processAlive(client.proc->pid()) && Tests::windowCount() == COUNT_BEFORE) {
|
||||
counter++;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
if (counter > 50) {
|
||||
NLog::log("{}shortcut-inhibitor client took too long to open", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Tests::processAlive(client.proc->pid())) {
|
||||
NLog::log("{}shortcut-inhibitor client not alive", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (getFromSocket(std::format("/dispatch focuswindow pid:{}", client.proc->pid())) != "ok") {
|
||||
NLog::log("{}Failed to focus shortcut-inhibitor client", Colors::RED, ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string command = "on\n";
|
||||
if (write(client.writeFd.get(), command.c_str(), command.length()) == -1) {
|
||||
NLog::log("{}shortcut-inhibitor client write failed", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
client.readBuf.fill(0);
|
||||
if (read(client.readFd.get(), client.readBuf.data(), client.readBuf.size() - 1) == -1)
|
||||
return false;
|
||||
|
||||
ret = std::string{client.readBuf.data()};
|
||||
if (ret.find("inhibiting") == std::string::npos) {
|
||||
NLog::log("{}shortcut-inhibitor client didn't return inhibiting", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
NLog::log("{}Started shortcut-inhibitor client", Colors::YELLOW);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void stopClient(SClient& client) {
|
||||
std::string cmd = "off\n";
|
||||
write(client.writeFd.get(), cmd.c_str(), cmd.length());
|
||||
|
||||
kill(client.proc->pid(), SIGKILL);
|
||||
client.proc.reset();
|
||||
}
|
||||
|
||||
static std::string flagFile = "/tmp/hyprtester-keybinds.txt";
|
||||
|
||||
static bool checkFlag() {
|
||||
bool exists = std::filesystem::exists(flagFile);
|
||||
std::filesystem::remove(flagFile);
|
||||
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 bool test() {
|
||||
SClient client;
|
||||
if (!startClient(client))
|
||||
return false;
|
||||
|
||||
NLog::log("{}Testing keybinds", Colors::GREEN);
|
||||
//basic keybind test
|
||||
EXPECT(checkFlag(), false);
|
||||
EXPECT(getFromSocket("/keyword bind SUPER,Y,exec,touch " + flagFile), "ok");
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
|
||||
EXPECT(attemptCheckFlag(20, 50), false);
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
|
||||
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
|
||||
|
||||
//keybind bypass flag test
|
||||
EXPECT(checkFlag(), false);
|
||||
EXPECT(getFromSocket("/keyword bindp SUPER,Y,exec,touch " + flagFile), "ok");
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
|
||||
EXPECT(attemptCheckFlag(20, 50), true);
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
|
||||
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
|
||||
|
||||
NLog::log("{}Testing gestures", Colors::GREEN);
|
||||
//basic gesture test
|
||||
OK(getFromSocket("/dispatch plugin:test:gesture right,3"));
|
||||
EXPECT_NOT_CONTAINS(getFromSocket("/activewindow"), "floating: 1");
|
||||
|
||||
//gesture bypass flag test
|
||||
OK(getFromSocket("/dispatch plugin:test:gesture right,2"));
|
||||
EXPECT_CONTAINS(getFromSocket("/activewindow"), "floating: 1");
|
||||
|
||||
stopClient(client);
|
||||
|
||||
NLog::log("{}Reloading the config", Colors::YELLOW);
|
||||
OK(getFromSocket("/reload"));
|
||||
|
||||
return !ret;
|
||||
}
|
||||
|
||||
REGISTER_CLIENT_TEST_FN(test);
|
||||
|
|
@ -34,6 +34,197 @@ static void testFloatClamp() {
|
|||
// clean up
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
|
||||
OK(getFromSocket("/reload"));
|
||||
}
|
||||
|
||||
static void test13349() {
|
||||
|
||||
// Test if dwindle properly uses a focal point to place a new window.
|
||||
// exposed by #13349 as a regression from #12890
|
||||
|
||||
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("/dispatch focuswindow class:c"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 967,547");
|
||||
EXPECT_CONTAINS(str, "size: 931,511");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch movewindow l"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 497,22");
|
||||
EXPECT_CONTAINS(str, "size: 456,1036");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch movewindow r"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 967,22");
|
||||
EXPECT_CONTAINS(str, "size: 456,1036");
|
||||
}
|
||||
|
||||
// clean up
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
}
|
||||
|
||||
static void testSplit() {
|
||||
// Test various split methods
|
||||
|
||||
Tests::spawnKitty("a");
|
||||
|
||||
// these must not crash
|
||||
EXPECT_NOT(getFromSocket("/dispatch layoutmsg swapsplit"), "ok");
|
||||
EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio 1 exact"), "ok");
|
||||
|
||||
Tests::spawnKitty("b");
|
||||
|
||||
OK(getFromSocket("/dispatch focuswindow class:a"));
|
||||
OK(getFromSocket("/dispatch layoutmsg splitratio -0.2"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 22,22");
|
||||
EXPECT_CONTAINS(str, "size: 743,1036");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch layoutmsg splitratio 1.6 exact"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 22,22");
|
||||
EXPECT_CONTAINS(str, "size: 1495,1036");
|
||||
}
|
||||
|
||||
EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio fhne exact"), "ok");
|
||||
EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio exact"), "ok");
|
||||
EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio -....9"), "ok");
|
||||
EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio ..9"), "ok");
|
||||
EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio"), "ok");
|
||||
|
||||
OK(getFromSocket("/dispatch layoutmsg togglesplit"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 22,22");
|
||||
EXPECT_CONTAINS(str, "size: 1876,823");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch layoutmsg swapsplit"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 22,859");
|
||||
EXPECT_CONTAINS(str, "size: 1876,199");
|
||||
}
|
||||
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
}
|
||||
|
||||
static void testRotatesplit() {
|
||||
OK(getFromSocket("r/keyword general:gaps_in 0"));
|
||||
OK(getFromSocket("r/keyword general:gaps_out 0"));
|
||||
OK(getFromSocket("r/keyword general:border_size 0"));
|
||||
|
||||
for (auto const& win : {"a", "b"}) {
|
||||
if (!Tests::spawnKitty(win)) {
|
||||
NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win);
|
||||
++TESTS_FAILED;
|
||||
ret = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT_CONTAINS(str, "at: 0,0");
|
||||
EXPECT_CONTAINS(str, "size: 960,1080");
|
||||
}
|
||||
|
||||
// test 4 repeated rotations by 90 degrees
|
||||
OK(getFromSocket("/dispatch layoutmsg rotatesplit"));
|
||||
{
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT_CONTAINS(str, "at: 0,0");
|
||||
EXPECT_CONTAINS(str, "size: 1920,540");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch layoutmsg rotatesplit"));
|
||||
{
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT_CONTAINS(str, "at: 960,0");
|
||||
EXPECT_CONTAINS(str, "size: 960,1080");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch layoutmsg rotatesplit"));
|
||||
{
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT_CONTAINS(str, "at: 0,540");
|
||||
EXPECT_CONTAINS(str, "size: 1920,540");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch layoutmsg rotatesplit"));
|
||||
{
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT_CONTAINS(str, "at: 0,0");
|
||||
EXPECT_CONTAINS(str, "size: 960,1080");
|
||||
}
|
||||
|
||||
// test different angles
|
||||
OK(getFromSocket("/dispatch layoutmsg rotatesplit 180"));
|
||||
{
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT_CONTAINS(str, "at: 960,0");
|
||||
EXPECT_CONTAINS(str, "size: 960,1080");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch layoutmsg rotatesplit 270"));
|
||||
{
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT_CONTAINS(str, "at: 0,540");
|
||||
EXPECT_CONTAINS(str, "size: 1920,540");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch layoutmsg rotatesplit 360"));
|
||||
{
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT_CONTAINS(str, "at: 0,0");
|
||||
EXPECT_CONTAINS(str, "size: 1920,540");
|
||||
}
|
||||
|
||||
// test negative angles
|
||||
OK(getFromSocket("/dispatch layoutmsg rotatesplit -90"));
|
||||
{
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT_CONTAINS(str, "at: 0,0");
|
||||
EXPECT_CONTAINS(str, "size: 960,1080");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch layoutmsg rotatesplit -180"));
|
||||
{
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT_CONTAINS(str, "at: 960,0");
|
||||
EXPECT_CONTAINS(str, "size: 960,1080");
|
||||
}
|
||||
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
|
||||
OK(getFromSocket("/reload"));
|
||||
}
|
||||
|
||||
static bool test() {
|
||||
|
|
@ -43,6 +234,15 @@ static bool test() {
|
|||
NLog::log("{}Testing float clamp", Colors::GREEN);
|
||||
testFloatClamp();
|
||||
|
||||
NLog::log("{}Testing #13349", Colors::GREEN);
|
||||
test13349();
|
||||
|
||||
NLog::log("{}Testing splits", Colors::GREEN);
|
||||
testSplit();
|
||||
|
||||
NLog::log("{}Testing rotatesplit", Colors::GREEN);
|
||||
testRotatesplit();
|
||||
|
||||
// clean up
|
||||
NLog::log("Cleaning up", Colors::YELLOW);
|
||||
getFromSocket("/dispatch workspace 1");
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include "../../shared.hpp"
|
||||
#include "../../hyprctlCompat.hpp"
|
||||
#include <chrono>
|
||||
#include <format>
|
||||
#include <thread>
|
||||
#include <hyprutils/os/Process.hpp>
|
||||
#include <hyprutils/memory/WeakPtr.hpp>
|
||||
|
|
@ -15,40 +16,46 @@ using namespace Hyprutils::Memory;
|
|||
#define UP CUniquePointer
|
||||
#define SP CSharedPointer
|
||||
|
||||
static bool test() {
|
||||
const static auto SLEEP_DURATIONS = std::array{1, 10};
|
||||
|
||||
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"));
|
||||
for (const auto duration : SLEEP_DURATIONS) {
|
||||
// Note: POSIX sleep does not support fractional seconds, so
|
||||
// can't sleep for less than 1 second.
|
||||
OK(getFromSocket(std::format("/dispatch exec sleep {}", duration)));
|
||||
|
||||
// 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;
|
||||
// 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);
|
||||
continue;
|
||||
}
|
||||
|
||||
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(duration));
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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;
|
||||
return false;
|
||||
}
|
||||
|
||||
REGISTER_TEST_FN(test)
|
||||
|
|
|
|||
|
|
@ -127,6 +127,34 @@ static bool test() {
|
|||
ret = 1;
|
||||
}
|
||||
|
||||
// test movegroupwindow: focus should follow the moved window
|
||||
NLog::log("{}Test movegroupwindow focus follows window", Colors::YELLOW);
|
||||
try {
|
||||
auto str = getFromSocket("/activewindow");
|
||||
auto activeBeforeMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16);
|
||||
OK(getFromSocket("/dispatch movegroupwindow f"));
|
||||
str = getFromSocket("/activewindow");
|
||||
auto activeAfterMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16);
|
||||
EXPECT(activeAfterMove, activeBeforeMove);
|
||||
} catch (...) {
|
||||
NLog::log("{}Fail at getting prop", Colors::RED);
|
||||
ret = 1;
|
||||
}
|
||||
|
||||
// and backwards
|
||||
NLog::log("{}Test movegroupwindow backwards", Colors::YELLOW);
|
||||
try {
|
||||
auto str = getFromSocket("/activewindow");
|
||||
auto activeBeforeMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16);
|
||||
OK(getFromSocket("/dispatch movegroupwindow b"));
|
||||
str = getFromSocket("/activewindow");
|
||||
auto activeAfterMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16);
|
||||
EXPECT(activeAfterMove, activeBeforeMove);
|
||||
} catch (...) {
|
||||
NLog::log("{}Fail at getting prop", Colors::RED);
|
||||
ret = 1;
|
||||
}
|
||||
|
||||
NLog::log("{}Disable autogrouping", Colors::YELLOW);
|
||||
OK(getFromSocket("/keyword group:auto_group false"));
|
||||
|
||||
|
|
@ -173,6 +201,99 @@ static bool test() {
|
|||
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
|
||||
// test movewindoworgroup: direction should be respected when extracting from group
|
||||
NLog::log("{}Test movewindoworgroup respects direction out of group", Colors::YELLOW);
|
||||
OK(getFromSocket("/keyword group:groupbar:enabled 0"));
|
||||
{
|
||||
auto kittyE = Tests::spawnKitty();
|
||||
if (!kittyE) {
|
||||
NLog::log("{}Error: kitty did not spawn", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
// group kitty, and new windows should be auto-grouped
|
||||
OK(getFromSocket("/dispatch togglegroup"));
|
||||
|
||||
auto kittyF = Tests::spawnKitty();
|
||||
if (!kittyF) {
|
||||
NLog::log("{}Error: kitty did not spawn", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
EXPECT(Tests::windowCount(), 2);
|
||||
|
||||
// both windows should be grouped at the same position
|
||||
{
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT_COUNT_STRING(str, "at: 22,22", 2);
|
||||
}
|
||||
|
||||
// move active window out of group to the right
|
||||
NLog::log("{}Test movewindoworgroup r", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch movewindoworgroup r"));
|
||||
|
||||
// the group should stay at x=22, the extracted window should be to the right
|
||||
{
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT_COUNT_STRING(str, "at: 22,22", 1);
|
||||
}
|
||||
|
||||
// move it back into the group
|
||||
OK(getFromSocket("/dispatch moveintogroup l"));
|
||||
|
||||
// move active window out of group downward
|
||||
NLog::log("{}Test movewindoworgroup d", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch movewindoworgroup d"));
|
||||
|
||||
// the group should stay at y=22, the extracted window should be below
|
||||
{
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT_COUNT_STRING(str, "at: 22,22", 1);
|
||||
}
|
||||
|
||||
Tests::killAllWindows();
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
}
|
||||
|
||||
// test that we deny a floated window getting auto-grouped into a tiled group.
|
||||
NLog::log("{}Test that we deny a floated window getting auto-grouped into a tiled group.", Colors::GREEN);
|
||||
|
||||
OK(getFromSocket("/keyword windowrule[kitty-tiled]:match:class kitty_tiled"));
|
||||
OK(getFromSocket("/keyword windowrule[kitty-tiled]:tile yes"));
|
||||
auto kittyProcE = Tests::spawnKitty("kitty_tiled");
|
||||
if (!kittyProcE) {
|
||||
NLog::log("{}Error: kitty did not spawn", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
OK(getFromSocket("/dispatch togglegroup"));
|
||||
|
||||
OK(getFromSocket("/keyword windowrule[kitty-floated]:match:class kitty_floated"));
|
||||
OK(getFromSocket("/keyword windowrule[kitty-floated]:float yes"));
|
||||
auto kittyProcF = Tests::spawnKitty("kitty_floated");
|
||||
if (!kittyProcF) {
|
||||
NLog::log("{}Error: kitty did not spawn", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
EXPECT(Tests::windowCount(), 2);
|
||||
|
||||
{
|
||||
auto clients = getFromSocket("/clients");
|
||||
auto classPos = clients.find("class: kitty_floated");
|
||||
if (classPos == std::string::npos) {
|
||||
NLog::log("{}Could not find kitty_floated in clients output", Colors::RED);
|
||||
ret = 1;
|
||||
} else {
|
||||
auto entryStart = clients.rfind("Window ", classPos);
|
||||
auto entryEnd = clients.find("\n\n", classPos);
|
||||
auto windowEntry = clients.substr(entryStart, entryEnd - entryStart);
|
||||
EXPECT_CONTAINS(windowEntry, "floating: 1");
|
||||
EXPECT_CONTAINS(windowEntry, "grouped: 0");
|
||||
}
|
||||
}
|
||||
|
||||
Tests::killAllWindows();
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
|
||||
return !ret;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,6 +77,16 @@ static bool testGetprop() {
|
|||
EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size"), "100 50");
|
||||
EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size -j"), R"({"min_size": [100,50]})");
|
||||
|
||||
// expr-based min/max _size
|
||||
getFromSocket("/dispatch setfloating class:kitty"); // need to set floating for tests below
|
||||
getFromSocket("/dispatch setprop class:kitty max_size 90+10 25*2"); // set max to the same as min above, forcing window to 100*50
|
||||
EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size"), "100 50");
|
||||
EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size -j"), R"({"max_size": [100,50]})");
|
||||
getFromSocket("/dispatch setprop class:kitty min_size window_w*0.5 window_h-10");
|
||||
EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size"), "50 40");
|
||||
EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size -j"), R"({"min_size": [50,40]})");
|
||||
getFromSocket("/dispatch settiled class:kitty"); // go back to tiled for consistency
|
||||
|
||||
// opacity
|
||||
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity"), "1");
|
||||
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity -j"), R"({"opacity": 1})");
|
||||
|
|
|
|||
53
hyprtester/src/tests/main/layer.cpp
Normal file
53
hyprtester/src/tests/main/layer.cpp
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
#include "../../Log.hpp"
|
||||
#include "../shared.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 spawnLayer(const std::string& namespace_) {
|
||||
NLog::log("{}Spawning kitty layer {}", Colors::YELLOW, namespace_);
|
||||
if (!Tests::spawnLayerKitty(namespace_)) {
|
||||
NLog::log("{}Error: {} layer did not spawn", Colors::RED, namespace_);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool test() {
|
||||
NLog::log("{}Testing plugin layerrules", Colors::GREEN);
|
||||
|
||||
if (!spawnLayer("rule-layer"))
|
||||
return false;
|
||||
|
||||
OK(getFromSocket("/dispatch plugin:test:add_layer_rule"));
|
||||
OK(getFromSocket("/reload"));
|
||||
|
||||
OK(getFromSocket("/keyword layerrule match:namespace rule-layer, plugin_rule effect"));
|
||||
|
||||
if (!spawnLayer("rule-layer"))
|
||||
return false;
|
||||
|
||||
if (!spawnLayer("norule-layer"))
|
||||
return false;
|
||||
|
||||
OK(getFromSocket("/dispatch plugin:test:check_layer_rule"));
|
||||
|
||||
OK(getFromSocket("/reload"));
|
||||
|
||||
NLog::log("{}Killing all layers", Colors::YELLOW);
|
||||
Tests::killAllLayers();
|
||||
|
||||
NLog::log("{}Expecting 0 layers", Colors::YELLOW);
|
||||
EXPECT(Tests::layerCount(), 0);
|
||||
|
||||
return !ret;
|
||||
}
|
||||
|
||||
REGISTER_TEST_FN(test)
|
||||
127
hyprtester/src/tests/main/layout.cpp
Normal file
127
hyprtester/src/tests/main/layout.cpp
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
#include "../shared.hpp"
|
||||
#include "../../shared.hpp"
|
||||
#include "../../hyprctlCompat.hpp"
|
||||
#include "tests.hpp"
|
||||
|
||||
static int ret = 0;
|
||||
|
||||
static void swar() {
|
||||
OK(getFromSocket("/keyword layout:single_window_aspect_ratio 1 1"));
|
||||
|
||||
Tests::spawnKitty();
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 442,22");
|
||||
EXPECT_CONTAINS(str, "size: 1036,1036");
|
||||
}
|
||||
|
||||
Tests::spawnKitty();
|
||||
|
||||
OK(getFromSocket("/dispatch killwindow activewindow"));
|
||||
|
||||
Tests::waitUntilWindowsN(1);
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 442,22");
|
||||
EXPECT_CONTAINS(str, "size: 1036,1036");
|
||||
}
|
||||
|
||||
// don't use swar on maximized
|
||||
OK(getFromSocket("/dispatch fullscreen 1"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 22,22");
|
||||
EXPECT_CONTAINS(str, "size: 1876,1036");
|
||||
}
|
||||
|
||||
// clean up
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
}
|
||||
|
||||
// Don't crash when focus after global geometry changes
|
||||
static void testCrashOnGeomUpdate() {
|
||||
Tests::spawnKitty();
|
||||
Tests::spawnKitty();
|
||||
Tests::spawnKitty();
|
||||
|
||||
// move the layout
|
||||
OK(getFromSocket("/keyword monitor HEADLESS-2,1920x1080@60,1000x0,1"));
|
||||
|
||||
// shouldnt crash
|
||||
OK(getFromSocket("/dispatch movefocus r"));
|
||||
|
||||
OK(getFromSocket("/reload"));
|
||||
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
}
|
||||
|
||||
// Test if size + pos is preserved after fs cycle
|
||||
static void testPosPreserve() {
|
||||
Tests::spawnKitty();
|
||||
|
||||
OK(getFromSocket("/dispatch setfloating class:kitty"));
|
||||
OK(getFromSocket("/dispatch resizewindowpixel exact 1337 69, class:kitty"));
|
||||
OK(getFromSocket("/dispatch movewindowpixel exact 420 420, class:kitty"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 420,420");
|
||||
EXPECT_CONTAINS(str, "size: 1337,69");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch fullscreen"));
|
||||
OK(getFromSocket("/dispatch fullscreen"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "size: 1337,69");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch movewindow r"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 581,420");
|
||||
EXPECT_CONTAINS(str, "size: 1337,69");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch fullscreen"));
|
||||
OK(getFromSocket("/dispatch fullscreen"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 581,420");
|
||||
EXPECT_CONTAINS(str, "size: 1337,69");
|
||||
}
|
||||
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
}
|
||||
|
||||
static bool test() {
|
||||
NLog::log("{}Testing layout generic", Colors::GREEN);
|
||||
|
||||
// setup
|
||||
OK(getFromSocket("/dispatch workspace 10"));
|
||||
|
||||
// test
|
||||
NLog::log("{}Testing `single_window_aspect_ratio`", Colors::GREEN);
|
||||
swar();
|
||||
|
||||
testCrashOnGeomUpdate();
|
||||
testPosPreserve();
|
||||
|
||||
// clean up
|
||||
NLog::log("Cleaning up", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch workspace 1"));
|
||||
OK(getFromSocket("/reload"));
|
||||
|
||||
return !ret;
|
||||
}
|
||||
|
||||
REGISTER_TEST_FN(test);
|
||||
|
|
@ -3,7 +3,53 @@
|
|||
#include "../../hyprctlCompat.hpp"
|
||||
#include "tests.hpp"
|
||||
|
||||
static int ret = 0;
|
||||
static int ret = 0;
|
||||
|
||||
// reqs 1 master 3 slaves
|
||||
static void testOrientations() {
|
||||
OK(getFromSocket("/keyword master:orientation top"));
|
||||
|
||||
// top
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 22,22");
|
||||
EXPECT_CONTAINS(str, "size: 1876");
|
||||
}
|
||||
|
||||
// cycle = top, right, bottom, center, left
|
||||
|
||||
// right
|
||||
OK(getFromSocket("/dispatch layoutmsg orientationnext"));
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 873,22");
|
||||
EXPECT_CONTAINS(str, "size: 1025,1036");
|
||||
}
|
||||
|
||||
// bottom
|
||||
OK(getFromSocket("/dispatch layoutmsg orientationnext"));
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 22,495");
|
||||
EXPECT_CONTAINS(str, "size: 1876");
|
||||
}
|
||||
|
||||
// center
|
||||
OK(getFromSocket("/dispatch layoutmsg orientationnext"));
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 450,22");
|
||||
EXPECT_CONTAINS(str, "size: 1020,1036");
|
||||
}
|
||||
|
||||
// left
|
||||
OK(getFromSocket("/dispatch layoutmsg orientationnext"));
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 22,22");
|
||||
EXPECT_CONTAINS(str, "size: 1025,1036");
|
||||
}
|
||||
}
|
||||
|
||||
static void focusMasterPrevious() {
|
||||
// setup
|
||||
|
|
@ -44,11 +90,74 @@ static void focusMasterPrevious() {
|
|||
OK(getFromSocket("/dispatch layoutmsg focusmaster previous"));
|
||||
EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: master");
|
||||
|
||||
testOrientations();
|
||||
|
||||
// clean up
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
}
|
||||
|
||||
static void testFsBehavior() {
|
||||
// Master will re-send data to fullscreen / maximized windows, which can interfere with misc:on_focus_under_fullscreen
|
||||
// check that it doesn't.
|
||||
|
||||
for (auto const& win : {"master", "slave1", "slave2"}) {
|
||||
if (!Tests::spawnKitty(win)) {
|
||||
NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win);
|
||||
++TESTS_FAILED;
|
||||
ret = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch focuswindow class:master"));
|
||||
OK(getFromSocket("/dispatch fullscreen 1"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 22,22");
|
||||
EXPECT_CONTAINS(str, "size: 1876,1036");
|
||||
EXPECT_CONTAINS(str, "class: master");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 1"));
|
||||
|
||||
Tests::spawnKitty("new_master");
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 22,22");
|
||||
EXPECT_CONTAINS(str, "size: 1876,1036");
|
||||
EXPECT_CONTAINS(str, "class: new_master");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 1");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 0"));
|
||||
|
||||
Tests::spawnKitty("ignored");
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 22,22");
|
||||
EXPECT_CONTAINS(str, "size: 1876,1036");
|
||||
EXPECT_CONTAINS(str, "class: new_master");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 1");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 2"));
|
||||
|
||||
Tests::spawnKitty("vaxwashere");
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: vaxwashere");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 0");
|
||||
}
|
||||
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
}
|
||||
|
||||
static bool test() {
|
||||
NLog::log("{}Testing Master layout", Colors::GREEN);
|
||||
|
||||
|
|
@ -60,6 +169,9 @@ static bool test() {
|
|||
NLog::log("{}Testing `focusmaster previous` layoutmsg", Colors::GREEN);
|
||||
focusMasterPrevious();
|
||||
|
||||
NLog::log("{}Testing fs behavior", Colors::GREEN);
|
||||
testFsBehavior();
|
||||
|
||||
// clean up
|
||||
NLog::log("Cleaning up", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch workspace 1"));
|
||||
|
|
|
|||
|
|
@ -18,6 +18,121 @@ using namespace Hyprutils::Memory;
|
|||
#define UP CUniquePointer
|
||||
#define SP CSharedPointer
|
||||
|
||||
// Uncomment once test vm can run hyprland-dialog
|
||||
// static void testAnrDialogs() {
|
||||
// NLog::log("{}Testing ANR dialogs", Colors::YELLOW);
|
||||
//
|
||||
// OK(getFromSocket("/keyword misc:enable_anr_dialog true"));
|
||||
// OK(getFromSocket("/keyword misc:anr_missed_pings 1"));
|
||||
//
|
||||
// NLog::log("{}ANR dialog: regular workspaces", Colors::YELLOW);
|
||||
// {
|
||||
// OK(getFromSocket("/dispatch workspace 2"));
|
||||
//
|
||||
// auto kitty = Tests::spawnKitty("bad_kitty");
|
||||
//
|
||||
// if (!kitty) {
|
||||
// ret = 1;
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// auto str = getFromSocket("/activewindow");
|
||||
// EXPECT_CONTAINS(str, "workspace: 2");
|
||||
// }
|
||||
//
|
||||
// OK(getFromSocket("/dispatch workspace 1"));
|
||||
//
|
||||
// ::kill(kitty->pid(), SIGSTOP);
|
||||
// Tests::waitUntilWindowsN(2);
|
||||
//
|
||||
// {
|
||||
// auto str = getFromSocket("/activeworkspace");
|
||||
// EXPECT_CONTAINS(str, "windows: 0");
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// OK(getFromSocket("/dispatch focuswindow class:hyprland-dialog"))
|
||||
// auto str = getFromSocket("/activewindow");
|
||||
// EXPECT_CONTAINS(str, "workspace: 2");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Tests::killAllWindows();
|
||||
//
|
||||
// NLog::log("{}ANR dialog: named workspaces", Colors::YELLOW);
|
||||
// {
|
||||
// OK(getFromSocket("/dispatch workspace name:yummy"));
|
||||
//
|
||||
// auto kitty = Tests::spawnKitty("bad_kitty");
|
||||
//
|
||||
// if (!kitty) {
|
||||
// ret = 1;
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// auto str = getFromSocket("/activewindow");
|
||||
// EXPECT_CONTAINS(str, "yummy");
|
||||
// }
|
||||
//
|
||||
// OK(getFromSocket("/dispatch workspace 1"));
|
||||
//
|
||||
// ::kill(kitty->pid(), SIGSTOP);
|
||||
// Tests::waitUntilWindowsN(2);
|
||||
//
|
||||
// {
|
||||
// auto str = getFromSocket("/activeworkspace");
|
||||
// EXPECT_CONTAINS(str, "windows: 0");
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// OK(getFromSocket("/dispatch focuswindow class:hyprland-dialog"))
|
||||
// auto str = getFromSocket("/activewindow");
|
||||
// EXPECT_CONTAINS(str, "yummy");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Tests::killAllWindows();
|
||||
//
|
||||
// NLog::log("{}ANR dialog: special workspaces", Colors::YELLOW);
|
||||
// {
|
||||
// OK(getFromSocket("/dispatch workspace special:apple"));
|
||||
//
|
||||
// auto kitty = Tests::spawnKitty("bad_kitty");
|
||||
//
|
||||
// if (!kitty) {
|
||||
// ret = 1;
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// auto str = getFromSocket("/activewindow");
|
||||
// EXPECT_CONTAINS(str, "special:apple");
|
||||
// }
|
||||
//
|
||||
// OK(getFromSocket("/dispatch togglespecialworkspace apple"));
|
||||
// OK(getFromSocket("/dispatch workspace 1"));
|
||||
//
|
||||
// ::kill(kitty->pid(), SIGSTOP);
|
||||
// Tests::waitUntilWindowsN(2);
|
||||
//
|
||||
// {
|
||||
// auto str = getFromSocket("/activeworkspace");
|
||||
// EXPECT_CONTAINS(str, "windows: 0");
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// OK(getFromSocket("/dispatch focuswindow class:hyprland-dialog"))
|
||||
// auto str = getFromSocket("/activewindow");
|
||||
// EXPECT_CONTAINS(str, "special:apple");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// OK(getFromSocket("/reload"));
|
||||
// Tests::killAllWindows();
|
||||
// }
|
||||
|
||||
static bool test() {
|
||||
NLog::log("{}Testing config: misc:", Colors::GREEN);
|
||||
|
||||
|
|
|
|||
228
hyprtester/src/tests/main/scroll.cpp
Normal file
228
hyprtester/src/tests/main/scroll.cpp
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
#include "../shared.hpp"
|
||||
#include "../../shared.hpp"
|
||||
#include "../../hyprctlCompat.hpp"
|
||||
#include "tests.hpp"
|
||||
|
||||
static int ret = 0;
|
||||
|
||||
static void testFocusCycling() {
|
||||
for (auto const& win : {"a", "b", "c", "d"}) {
|
||||
if (!Tests::spawnKitty(win)) {
|
||||
NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win);
|
||||
++TESTS_FAILED;
|
||||
ret = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch focuswindow class:a"));
|
||||
|
||||
OK(getFromSocket("/dispatch movefocus r"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: b");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch movefocus r"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: c");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch movefocus r"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: d");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch movewindow l"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: d");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch movefocus u"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: c");
|
||||
}
|
||||
|
||||
// clean up
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
}
|
||||
|
||||
static void testFocusWrapping() {
|
||||
for (auto const& win : {"a", "b", "c", "d"}) {
|
||||
if (!Tests::spawnKitty(win)) {
|
||||
NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win);
|
||||
++TESTS_FAILED;
|
||||
ret = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// set wrap_focus to true
|
||||
OK(getFromSocket("/keyword scrolling:wrap_focus true"));
|
||||
|
||||
OK(getFromSocket("/dispatch focuswindow class:a"));
|
||||
|
||||
OK(getFromSocket("/dispatch layoutmsg focus l"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: d");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch layoutmsg focus r"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: a");
|
||||
}
|
||||
|
||||
// set wrap_focus to false
|
||||
OK(getFromSocket("/keyword scrolling:wrap_focus false"));
|
||||
|
||||
OK(getFromSocket("/dispatch focuswindow class:a"));
|
||||
|
||||
OK(getFromSocket("/dispatch layoutmsg focus l"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: a");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch focuswindow class:d"));
|
||||
|
||||
OK(getFromSocket("/dispatch layoutmsg focus r"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: d");
|
||||
}
|
||||
|
||||
// clean up
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
}
|
||||
|
||||
static void testSwapcolWrapping() {
|
||||
for (auto const& win : {"a", "b", "c", "d"}) {
|
||||
if (!Tests::spawnKitty(win)) {
|
||||
NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win);
|
||||
++TESTS_FAILED;
|
||||
ret = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// set wrap_swapcol to true
|
||||
OK(getFromSocket("/keyword scrolling:wrap_swapcol true"));
|
||||
|
||||
OK(getFromSocket("/dispatch focuswindow class:a"));
|
||||
|
||||
OK(getFromSocket("/dispatch layoutmsg swapcol l"));
|
||||
OK(getFromSocket("/dispatch layoutmsg focus l"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: c");
|
||||
}
|
||||
|
||||
// clean up
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
|
||||
for (auto const& win : {"a", "b", "c", "d"}) {
|
||||
if (!Tests::spawnKitty(win)) {
|
||||
NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win);
|
||||
++TESTS_FAILED;
|
||||
ret = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch focuswindow class:d"));
|
||||
OK(getFromSocket("/dispatch layoutmsg swapcol r"));
|
||||
OK(getFromSocket("/dispatch layoutmsg focus r"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: b");
|
||||
}
|
||||
|
||||
// clean up
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
|
||||
for (auto const& win : {"a", "b", "c", "d"}) {
|
||||
if (!Tests::spawnKitty(win)) {
|
||||
NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win);
|
||||
++TESTS_FAILED;
|
||||
ret = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// set wrap_swapcol to false
|
||||
OK(getFromSocket("/keyword scrolling:wrap_swapcol false"));
|
||||
|
||||
OK(getFromSocket("/dispatch focuswindow class:a"));
|
||||
|
||||
OK(getFromSocket("/dispatch layoutmsg swapcol l"));
|
||||
OK(getFromSocket("/dispatch layoutmsg focus r"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: b");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch focuswindow class:d"));
|
||||
|
||||
OK(getFromSocket("/dispatch layoutmsg swapcol r"));
|
||||
OK(getFromSocket("/dispatch layoutmsg focus l"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: c");
|
||||
}
|
||||
|
||||
// clean up
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
}
|
||||
|
||||
static bool test() {
|
||||
NLog::log("{}Testing Scroll layout", Colors::GREEN);
|
||||
|
||||
// setup
|
||||
OK(getFromSocket("/dispatch workspace name:scroll"));
|
||||
OK(getFromSocket("/keyword general:layout scrolling"));
|
||||
|
||||
// test
|
||||
NLog::log("{}Testing focus cycling", Colors::GREEN);
|
||||
testFocusCycling();
|
||||
|
||||
// test
|
||||
NLog::log("{}Testing focus wrap", Colors::GREEN);
|
||||
testFocusWrapping();
|
||||
|
||||
// test
|
||||
NLog::log("{}Testing swapcol wrap", Colors::GREEN);
|
||||
testSwapcolWrapping();
|
||||
|
||||
// clean up
|
||||
NLog::log("Cleaning up", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch workspace 1"));
|
||||
OK(getFromSocket("/reload"));
|
||||
|
||||
return !ret;
|
||||
}
|
||||
|
||||
REGISTER_TEST_FN(test);
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
#include <unistd.h>
|
||||
#include <cmath>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
|
|
@ -24,6 +25,28 @@ static bool spawnKitty(const std::string& class_, const std::vector<std::string>
|
|||
return true;
|
||||
}
|
||||
|
||||
/// Spawns a kitty and creates a file and returns its name. The removal of the file triggers
|
||||
/// activation of the spawned kitty window.
|
||||
///
|
||||
/// On failure, returns an empty string, possibly leaving a temporary file.
|
||||
static std::string spawnKittyActivating(const std::string& class_ = "kitty_activating") {
|
||||
// `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(class_, {"-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;
|
||||
}
|
||||
|
||||
static std::string getWindowAttribute(const std::string& winInfo, const std::string& attr) {
|
||||
auto pos = winInfo.find(attr);
|
||||
if (pos == std::string::npos) {
|
||||
|
|
@ -70,9 +93,9 @@ static void testSwapWindow() {
|
|||
{
|
||||
getFromSocket("/dispatch focuswindow class:kitty_A");
|
||||
auto pos = getWindowAttribute(getFromSocket("/activewindow"), "at:");
|
||||
NLog::log("{}Testing kitty_A {}, swapwindow with direction 'l'", Colors::YELLOW, pos);
|
||||
NLog::log("{}Testing kitty_A {}, swapwindow with direction 'r'", Colors::YELLOW, pos);
|
||||
|
||||
OK(getFromSocket("/dispatch swapwindow l"));
|
||||
OK(getFromSocket("/dispatch swapwindow r"));
|
||||
OK(getFromSocket("/dispatch focuswindow class:kitty_B"));
|
||||
|
||||
EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("{}", pos));
|
||||
|
|
@ -197,7 +220,7 @@ static void testGroupRules() {
|
|||
Tests::killAllWindows();
|
||||
}
|
||||
|
||||
static bool isActiveWindow(const std::string& class_, char fullscreen, bool log = true) {
|
||||
static bool isActiveWindow(const std::string& class_, char fullscreen = '0', bool log = true) {
|
||||
std::string activeWin = getFromSocket("/activewindow");
|
||||
auto winClass = getWindowAttribute(activeWin, "class:");
|
||||
auto winFullscreen = getWindowAttribute(activeWin, "fullscreen:").back();
|
||||
|
|
@ -210,13 +233,13 @@ static bool isActiveWindow(const std::string& class_, char fullscreen, bool log
|
|||
}
|
||||
}
|
||||
|
||||
static bool waitForActiveWindow(const std::string& class_, char fullscreen, int maxTries = 50) {
|
||||
static bool waitForActiveWindow(const std::string& class_, char fullscreen = '0', bool logLastCheck = true, 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 isActiveWindow(class_, fullscreen, logLastCheck);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
|
@ -232,24 +255,6 @@ static bool testWindowFocusOnFullscreenConflict() {
|
|||
|
||||
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"));
|
||||
|
|
@ -373,6 +378,61 @@ static void testMaximizeSize() {
|
|||
EXPECT(Tests::windowCount(), 0);
|
||||
}
|
||||
|
||||
static void testFloatingFocusOnFullscreen() {
|
||||
NLog::log("{}Testing floating focus on fullscreen", Colors::GREEN);
|
||||
|
||||
EXPECT(spawnKitty("kitty_A"), true);
|
||||
OK(getFromSocket("/dispatch togglefloating"));
|
||||
|
||||
EXPECT(spawnKitty("kitty_B"), true);
|
||||
OK(getFromSocket("/dispatch fullscreen 1"));
|
||||
|
||||
OK(getFromSocket("/dispatch cyclenext"));
|
||||
|
||||
OK(getFromSocket("/dispatch plugin:test:floating_focus_on_fullscreen"));
|
||||
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
|
||||
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
}
|
||||
|
||||
static void testGroupFallbackFocus() {
|
||||
NLog::log("{}Testing group fallback focus", Colors::GREEN);
|
||||
|
||||
EXPECT(spawnKitty("kitty_A"), true);
|
||||
|
||||
OK(getFromSocket("/dispatch togglegroup"));
|
||||
|
||||
EXPECT(spawnKitty("kitty_B"), true);
|
||||
EXPECT(spawnKitty("kitty_C"), true);
|
||||
EXPECT(spawnKitty("kitty_D"), true);
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT(str.contains("class: kitty_D"), true);
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch focuswindow class:kitty_B"));
|
||||
OK(getFromSocket("/dispatch focuswindow class:kitty_D"));
|
||||
OK(getFromSocket("/dispatch killactive"));
|
||||
|
||||
Tests::waitUntilWindowsN(3);
|
||||
|
||||
// Focus must return to the last focus, in this case B.
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT(str.contains("class: kitty_B"), true);
|
||||
}
|
||||
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
|
||||
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
}
|
||||
|
||||
static void testBringActiveToTopMouseMovement() {
|
||||
NLog::log("{}Testing bringactivetotop mouse movement", Colors::GREEN);
|
||||
|
||||
|
|
@ -412,6 +472,192 @@ static void testBringActiveToTopMouseMovement() {
|
|||
Tests::killAllWindows();
|
||||
}
|
||||
|
||||
static void testInitialFloatSize() {
|
||||
NLog::log("{}Testing initial float size", Colors::GREEN);
|
||||
|
||||
Tests::killAllWindows();
|
||||
OK(getFromSocket("/keyword windowrule match:class kitty, float yes"));
|
||||
OK(getFromSocket("/keyword input:float_switch_override_focus 0"));
|
||||
|
||||
EXPECT(spawnKitty("kitty"), true);
|
||||
|
||||
{
|
||||
// Kitty by default opens as 640x400, if this changes this test will break
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT(str.contains("size: 640,400"), true);
|
||||
}
|
||||
|
||||
OK(getFromSocket("/reload"));
|
||||
|
||||
Tests::killAllWindows();
|
||||
|
||||
OK(getFromSocket("/dispatch exec [float yes]kitty"));
|
||||
|
||||
Tests::waitUntilWindowsN(1);
|
||||
|
||||
{
|
||||
// Kitty by default opens as 640x400, if this changes this test will break
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT(str.contains("size: 640,400"), true);
|
||||
EXPECT(str.contains("floating: 1"), true);
|
||||
}
|
||||
|
||||
Tests::killAllWindows();
|
||||
}
|
||||
|
||||
/// Tests that the `focus_on_activate` effect of window rules always overrides
|
||||
/// the `misc:focus_on_activate` variable.
|
||||
static bool testWindowRuleFocusOnActivate() {
|
||||
OK(getFromSocket("/reload"));
|
||||
|
||||
if (!spawnKitty("kitty_default")) {
|
||||
NLog::log("{}Error: failed to spawn kitty", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do not focus anyone automatically
|
||||
///////////OK(getFromSocket("/keyword windowrule match:class .*, no_initial_focus true"));
|
||||
|
||||
// `focus_on_activate off` takes over
|
||||
{
|
||||
OK(getFromSocket("/keyword misc:focus_on_activate true"));
|
||||
OK(getFromSocket("/keyword windowrule match:class kitty_antifocus, focus_on_activate off"));
|
||||
|
||||
const std::string removeToActivate = spawnKittyActivating("kitty_antifocus");
|
||||
if (removeToActivate.empty()) {
|
||||
return false;
|
||||
}
|
||||
EXPECT(waitForActiveWindow("kitty_antifocus"), true);
|
||||
OK(getFromSocket("/dispatch focuswindow class:kitty_default"));
|
||||
EXPECT(isActiveWindow("kitty_default"), true);
|
||||
|
||||
std::filesystem::remove(removeToActivate);
|
||||
// The focus should NOT transition, since the window rule explicitly forbids that
|
||||
EXPECT(waitForActiveWindow("kitty_antifocus", '0', false), false);
|
||||
}
|
||||
|
||||
// `focus_on_activate on` takes over
|
||||
{
|
||||
OK(getFromSocket("/keyword misc:focus_on_activate false"));
|
||||
OK(getFromSocket("/keyword windowrule match:class kitty_superfocus, focus_on_activate on"));
|
||||
|
||||
const std::string removeToActivate = spawnKittyActivating("kitty_superfocus");
|
||||
if (removeToActivate.empty()) {
|
||||
return false;
|
||||
}
|
||||
EXPECT(waitForActiveWindow("kitty_superfocus"), true);
|
||||
OK(getFromSocket("/dispatch focuswindow class:kitty_default"));
|
||||
EXPECT(isActiveWindow("kitty_default"), true);
|
||||
|
||||
std::filesystem::remove(removeToActivate);
|
||||
// Now that we requested activation, the focus SHOULD transition to kitty_superfocus, according to the window rule
|
||||
EXPECT(waitForActiveWindow("kitty_superfocus"), 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;
|
||||
}
|
||||
|
||||
// tests if a pinned window contains the valid workspace after change
|
||||
static bool testPinnedWorkspacesValid() {
|
||||
OK(getFromSocket("/reload"));
|
||||
getFromSocket("/dispatch workspace 1337");
|
||||
|
||||
if (!spawnKitty("kitty")) {
|
||||
NLog::log("{}Error: failed to spawn kitty", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch setfloating class:kitty"));
|
||||
OK(getFromSocket("/dispatch pin class:kitty"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT(str.contains("workspace: 1337"), true);
|
||||
EXPECT(str.contains("pinned: 1"), true);
|
||||
}
|
||||
|
||||
getFromSocket("/dispatch workspace 1338");
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT(str.contains("workspace: 1338"), true);
|
||||
EXPECT(str.contains("pinned: 1"), true);
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch settiled class:kitty"))
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT(str.contains("workspace: 1338"), true);
|
||||
EXPECT(str.contains("pinned: 0"), 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 bool testWindowRuleWorkspaceEmpty() {
|
||||
NLog::log("{}Testing windowrule workspace empty", Colors::YELLOW);
|
||||
OK(getFromSocket("/reload"));
|
||||
|
||||
OK(getFromSocket("/keyword windowrule match:class kitty_A, workspace empty"));
|
||||
OK(getFromSocket("/keyword windowrule match:class kitty_B, workspace emptyn"));
|
||||
|
||||
getFromSocket("/dispatch workspace 3");
|
||||
|
||||
if (!spawnKitty("kitty")) {
|
||||
NLog::log("{}Error: failed to spawn kitty", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT(str.contains("workspace: 3"), true);
|
||||
}
|
||||
|
||||
if (!spawnKitty("kitty_A")) {
|
||||
NLog::log("{}Error: failed to spawn kitty", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT(str.contains("workspace: 1"), true);
|
||||
}
|
||||
|
||||
getFromSocket("/dispatch workspace 3");
|
||||
if (!spawnKitty("kitty_B")) {
|
||||
NLog::log("{}Error: failed to spawn kitty", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT(str.contains("workspace: 4"), true);
|
||||
}
|
||||
|
||||
Tests::killAllWindows();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool test() {
|
||||
NLog::log("{}Testing windows", Colors::GREEN);
|
||||
|
||||
|
|
@ -525,14 +771,14 @@ static bool test() {
|
|||
EXPECT(Tests::windowCount(), 3);
|
||||
|
||||
NLog::log("{}Checking props of xeyes", Colors::YELLOW);
|
||||
// check some window props of xeyes, try to tile them
|
||||
// check some window props of xeyes, try to float it
|
||||
{
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT_CONTAINS(str, "floating: 1");
|
||||
getFromSocket("/dispatch settiled class:XEyes");
|
||||
EXPECT_NOT_CONTAINS(str, "floating: 1");
|
||||
getFromSocket("/dispatch setfloating class:XEyes");
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
str = getFromSocket("/clients");
|
||||
EXPECT_NOT_CONTAINS(str, "floating: 1");
|
||||
EXPECT_CONTAINS(str, "floating: 1");
|
||||
}
|
||||
|
||||
// kill all
|
||||
|
|
@ -716,6 +962,23 @@ static bool test() {
|
|||
|
||||
Tests::killAllWindows();
|
||||
|
||||
OK(getFromSocket("/keyword windowrule[border-magic-kitty]:match:class border_kitty"));
|
||||
OK(getFromSocket("/keyword windowrule[border-magic-kitty]:border_color rgba(c6ff00ff) rgba(ff0000ee) 45deg"));
|
||||
|
||||
if (!spawnKitty("border_kitty"))
|
||||
return false;
|
||||
|
||||
OK(getFromSocket("/dispatch focuswindow class:border_kitty"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/getprop active active_border_color");
|
||||
EXPECT_CONTAINS(str, "ffc6ff00");
|
||||
EXPECT_CONTAINS(str, "eeff0000");
|
||||
EXPECT_CONTAINS(str, "45deg");
|
||||
}
|
||||
|
||||
Tests::killAllWindows();
|
||||
|
||||
if (!spawnKitty("tag_kitty"))
|
||||
return false;
|
||||
|
||||
|
|
@ -799,7 +1062,8 @@ static bool test() {
|
|||
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"));
|
||||
OK(getFromSocket("/keyword windowrule match:class expr_kitty, float yes, size monitor_w*0.5 monitor_h*0.5, min_size monitor_w*0.25 monitor_h*0.25, "
|
||||
"max_size monitor_w*0.75 monitor_h*0.75, move 20+(monitor_w*0.1) monitor_h*0.5"));
|
||||
|
||||
if (!spawnKitty("expr_kitty"))
|
||||
return false;
|
||||
|
|
@ -809,12 +1073,20 @@ static bool test() {
|
|||
EXPECT_CONTAINS(str, "floating: 1");
|
||||
EXPECT_CONTAINS(str, "at: 212,540");
|
||||
EXPECT_CONTAINS(str, "size: 960,540");
|
||||
|
||||
auto min = getFromSocket("/getprop active min_size");
|
||||
EXPECT_CONTAINS(min, "480");
|
||||
EXPECT_CONTAINS(min, "270");
|
||||
|
||||
auto max = getFromSocket("/getprop active max_size");
|
||||
EXPECT_CONTAINS(max, "1440");
|
||||
EXPECT_CONTAINS(max, "810");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/reload"));
|
||||
Tests::killAllWindows();
|
||||
|
||||
OK(getFromSocket("/dispatch plugin:test:add_rule"));
|
||||
OK(getFromSocket("/dispatch plugin:test:add_window_rule"));
|
||||
OK(getFromSocket("/reload"));
|
||||
|
||||
OK(getFromSocket("/keyword windowrule match:class plugin_kitty, plugin_rule effect"));
|
||||
|
|
@ -822,12 +1094,12 @@ static bool test() {
|
|||
if (!spawnKitty("plugin_kitty"))
|
||||
return false;
|
||||
|
||||
OK(getFromSocket("/dispatch plugin:test:check_rule"));
|
||||
OK(getFromSocket("/dispatch plugin:test:check_window_rule"));
|
||||
|
||||
OK(getFromSocket("/reload"));
|
||||
Tests::killAllWindows();
|
||||
|
||||
OK(getFromSocket("/dispatch plugin:test:add_rule"));
|
||||
OK(getFromSocket("/dispatch plugin:test:add_window_rule"));
|
||||
OK(getFromSocket("/reload"));
|
||||
|
||||
OK(getFromSocket("/keyword windowrule[test-plugin-rule]:match:class plugin_kitty"));
|
||||
|
|
@ -836,16 +1108,20 @@ static bool test() {
|
|||
if (!spawnKitty("plugin_kitty"))
|
||||
return false;
|
||||
|
||||
OK(getFromSocket("/dispatch plugin:test:check_rule"));
|
||||
OK(getFromSocket("/dispatch plugin:test:check_window_rule"));
|
||||
|
||||
OK(getFromSocket("/reload"));
|
||||
Tests::killAllWindows();
|
||||
|
||||
testGroupRules();
|
||||
|
||||
testMaximizeSize();
|
||||
|
||||
testFloatingFocusOnFullscreen();
|
||||
testBringActiveToTopMouseMovement();
|
||||
testGroupFallbackFocus();
|
||||
testInitialFloatSize();
|
||||
testWindowRuleFocusOnActivate();
|
||||
testPinnedWorkspacesValid();
|
||||
testWindowRuleWorkspaceEmpty();
|
||||
|
||||
NLog::log("{}Reloading config", Colors::YELLOW);
|
||||
OK(getFromSocket("/reload"));
|
||||
|
|
|
|||
|
|
@ -20,6 +20,91 @@ using namespace Hyprutils::Utils;
|
|||
#define UP CUniquePointer
|
||||
#define SP CSharedPointer
|
||||
|
||||
static bool testSpecialWorkspaceFullscreen() {
|
||||
NLog::log("{}Testing special workspace fullscreen detection", Colors::YELLOW);
|
||||
|
||||
CScopeGuard guard = {[&]() {
|
||||
NLog::log("{}Cleaning up special workspace fullscreen test", Colors::YELLOW);
|
||||
// Close special workspace if open
|
||||
auto monitors = getFromSocket("/monitors");
|
||||
if (monitors.contains("(special:test_fs_special)") && !monitors.contains("special workspace: 0 ()"))
|
||||
getFromSocket("/dispatch togglespecialworkspace test_fs_special");
|
||||
Tests::killAllWindows();
|
||||
OK(getFromSocket("/reload"));
|
||||
}};
|
||||
|
||||
getFromSocket("/dispatch workspace 1");
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
|
||||
NLog::log("{}Test 1: Fullscreen detection on special workspace", Colors::YELLOW);
|
||||
|
||||
OK(getFromSocket("/dispatch workspace special:test_fs_special"));
|
||||
|
||||
if (!Tests::spawnKitty("kitty_special"))
|
||||
return false;
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: kitty_special");
|
||||
EXPECT_CONTAINS(str, "(special:test_fs_special)");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch fullscreen 0"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 2");
|
||||
}
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/monitors");
|
||||
EXPECT_CONTAINS(str, "(special:test_fs_special)");
|
||||
}
|
||||
|
||||
NLog::log("{}Test 2: Special workspace fullscreen precedence", Colors::YELLOW);
|
||||
|
||||
// Close special workspace before spawning on regular workspace
|
||||
OK(getFromSocket("/dispatch togglespecialworkspace test_fs_special"));
|
||||
getFromSocket("/dispatch workspace 1");
|
||||
|
||||
if (!Tests::spawnKitty("kitty_regular"))
|
||||
return false;
|
||||
|
||||
OK(getFromSocket("/dispatch fullscreen 0"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: kitty_regular");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 2");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch togglespecialworkspace test_fs_special"));
|
||||
OK(getFromSocket("/dispatch focuswindow class:kitty_special"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: kitty_special");
|
||||
}
|
||||
|
||||
NLog::log("{}Test 3: Toggle special workspace hides it", Colors::YELLOW);
|
||||
|
||||
OK(getFromSocket("/dispatch togglespecialworkspace test_fs_special"));
|
||||
OK(getFromSocket("/dispatch focuswindow class:kitty_regular"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: kitty_regular");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 2");
|
||||
}
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/monitors");
|
||||
EXPECT_CONTAINS(str, "special workspace: 0 ()");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool testAsymmetricGaps() {
|
||||
NLog::log("{}Testing asymmetric gap splits", Colors::YELLOW);
|
||||
{
|
||||
|
|
@ -108,6 +193,206 @@ static bool testAsymmetricGaps() {
|
|||
return true;
|
||||
}
|
||||
|
||||
static void testMultimonBAF() {
|
||||
NLog::log("{}Testing multimon back and forth", Colors::YELLOW);
|
||||
|
||||
OK(getFromSocket("/keyword binds:workspace_back_and_forth 1"));
|
||||
|
||||
OK(getFromSocket("/dispatch focusmonitor HEADLESS-2"));
|
||||
OK(getFromSocket("/dispatch workspace 1"));
|
||||
|
||||
Tests::spawnKitty();
|
||||
|
||||
OK(getFromSocket("/dispatch workspace 2"));
|
||||
|
||||
Tests::spawnKitty();
|
||||
|
||||
OK(getFromSocket("/dispatch focusmonitor HEADLESS-3"));
|
||||
OK(getFromSocket("/dispatch workspace 3"));
|
||||
|
||||
Tests::spawnKitty();
|
||||
|
||||
OK(getFromSocket("/dispatch workspace 3"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_CONTAINS(str, "workspace ID 2 ");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch workspace 4"));
|
||||
OK(getFromSocket("/dispatch workspace 4"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_CONTAINS(str, "workspace ID 2 ");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch workspace 2"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_CONTAINS(str, "workspace ID 4 ");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch workspace 3"));
|
||||
OK(getFromSocket("/dispatch workspace 3"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_CONTAINS(str, "workspace ID 4 ");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch workspace 2"));
|
||||
OK(getFromSocket("/dispatch workspace 3"));
|
||||
OK(getFromSocket("/dispatch workspace 1"));
|
||||
OK(getFromSocket("/dispatch workspace 1"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_CONTAINS(str, "workspace ID 3 ");
|
||||
}
|
||||
|
||||
Tests::killAllWindows();
|
||||
}
|
||||
|
||||
static void testMultimonFocus() {
|
||||
NLog::log("{}Testing multimon focus and move", Colors::YELLOW);
|
||||
|
||||
OK(getFromSocket("/keyword input:follow_mouse 0"));
|
||||
OK(getFromSocket("/dispatch focusmonitor HEADLESS-3"));
|
||||
OK(getFromSocket("/dispatch workspace 8"));
|
||||
OK(getFromSocket("/dispatch focusmonitor HEADLESS-2"));
|
||||
OK(getFromSocket("/dispatch workspace 7"));
|
||||
|
||||
for (auto const& win : {"a", "b"}) {
|
||||
if (!Tests::spawnKitty(win)) {
|
||||
NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win);
|
||||
++TESTS_FAILED;
|
||||
ret = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch focuswindow class:a"));
|
||||
OK(getFromSocket("/dispatch movefocus r"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_CONTAINS(str, "workspace ID 7 ");
|
||||
}
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: b");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch movefocus r"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_CONTAINS(str, "workspace ID 8 ");
|
||||
}
|
||||
|
||||
Tests::spawnKitty("c");
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: c");
|
||||
}
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_CONTAINS(str, "workspace ID 8 ");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch movefocus l"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: b");
|
||||
}
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_CONTAINS(str, "workspace ID 7 ");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch movewindow r"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: b");
|
||||
}
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_CONTAINS(str, "workspace ID 8 ");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch movefocus r"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: c");
|
||||
}
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_CONTAINS(str, "workspace ID 8 ");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch movefocus l"));
|
||||
|
||||
OK(getFromSocket("/keyword general:no_focus_fallback true"));
|
||||
OK(getFromSocket("/keyword binds:window_direction_monitor_fallback false"));
|
||||
|
||||
EXPECT_NOT(getFromSocket("/dispatch movefocus l"), "ok");
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: b");
|
||||
}
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_CONTAINS(str, "workspace ID 8 ");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch movewindow l"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: b");
|
||||
}
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_CONTAINS(str, "workspace ID 8 ");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/reload"));
|
||||
|
||||
Tests::killAllWindows();
|
||||
}
|
||||
|
||||
static void testDynamicWsEffects() {
|
||||
// test dynamic workspace effects, they shouldn't lag
|
||||
|
||||
OK(getFromSocket("/dispatch workspace 69"));
|
||||
|
||||
Tests::spawnKitty("bitch");
|
||||
|
||||
OK(getFromSocket("r/keyword workspace 69,bordersize:20"));
|
||||
OK(getFromSocket("r/keyword workspace 69,rounding:false"));
|
||||
|
||||
EXPECT(getFromSocket("/getprop class:bitch border_size"), "20");
|
||||
EXPECT(getFromSocket("/getprop class:bitch rounding"), "0");
|
||||
|
||||
OK(getFromSocket("/reload"));
|
||||
|
||||
Tests::killAllWindows();
|
||||
}
|
||||
|
||||
static bool test() {
|
||||
NLog::log("{}Testing workspaces", Colors::GREEN);
|
||||
|
||||
|
|
@ -442,14 +727,19 @@ static bool test() {
|
|||
EXPECT_CONTAINS(str, "class: kitty_B");
|
||||
}
|
||||
|
||||
// destroy the headless output
|
||||
OK(getFromSocket("/output remove HEADLESS-3"));
|
||||
|
||||
// kill all
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
|
||||
testMultimonBAF();
|
||||
testMultimonFocus();
|
||||
|
||||
// destroy the headless output
|
||||
OK(getFromSocket("/output remove HEADLESS-3"));
|
||||
|
||||
testSpecialWorkspaceFullscreen();
|
||||
testAsymmetricGaps();
|
||||
testDynamicWsEffects();
|
||||
|
||||
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include <cerrno>
|
||||
#include <thread>
|
||||
#include <print>
|
||||
#include <fstream>
|
||||
#include "../shared.hpp"
|
||||
#include "../hyprctlCompat.hpp"
|
||||
|
||||
|
|
@ -39,6 +40,38 @@ CUniquePointer<CProcess> Tests::spawnKitty(const std::string& class_, const std:
|
|||
return kitty;
|
||||
}
|
||||
|
||||
CUniquePointer<CProcess> Tests::spawnLayerKitty(const std::string& namespace_, const std::vector<std::string> args) {
|
||||
std::vector<std::string> programArgs = args;
|
||||
if (!namespace_.empty()) {
|
||||
programArgs.insert(programArgs.begin(), "--class");
|
||||
programArgs.insert(programArgs.begin() + 1, namespace_);
|
||||
}
|
||||
|
||||
programArgs.insert(programArgs.begin(), "+kitten");
|
||||
programArgs.insert(programArgs.begin() + 1, "panel");
|
||||
|
||||
CUniquePointer<CProcess> kitty = makeUnique<CProcess>("kitty", programArgs);
|
||||
kitty->addEnv("WAYLAND_DISPLAY", WLDISPLAY);
|
||||
kitty->runAsync();
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
|
||||
// wait while the layer spawns
|
||||
int counter = 0;
|
||||
while (processAlive(kitty->pid()) && countOccurrences(getFromSocket("/layers"), std::format("pid: {}", kitty->pid())) == 0) {
|
||||
counter++;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
if (counter > 50)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!processAlive(kitty->pid()))
|
||||
return nullptr;
|
||||
|
||||
return kitty;
|
||||
}
|
||||
|
||||
bool Tests::processAlive(pid_t pid) {
|
||||
errno = 0;
|
||||
int ret = kill(pid, 0);
|
||||
|
|
@ -96,6 +129,38 @@ void Tests::waitUntilWindowsN(int n) {
|
|||
}
|
||||
}
|
||||
|
||||
int Tests::layerCount() {
|
||||
return countOccurrences(getFromSocket("/layers"), "namespace: ");
|
||||
}
|
||||
|
||||
bool Tests::killAllLayers() {
|
||||
auto str = getFromSocket("/layers");
|
||||
auto pos = str.find("pid: ");
|
||||
while (pos != std::string::npos) {
|
||||
auto pid = stoi(str.substr(pos + 5, str.find('\n', pos)));
|
||||
kill(pid, 15);
|
||||
|
||||
// we need to wait for a bit because for some reason otherwise we'll end up
|
||||
// with layers with pid -1 if they are all removed at the same time
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
pos = str.find("pid: ", pos + 5);
|
||||
}
|
||||
|
||||
int counter = 0;
|
||||
while (Tests::layerCount() != 0) {
|
||||
counter++;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
if (counter > 50) {
|
||||
std::println("{}Timed out waiting for layers to close", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string Tests::execAndGet(const std::string& cmd) {
|
||||
CProcess proc("/bin/sh", {"-c", cmd});
|
||||
|
||||
|
|
@ -105,3 +170,14 @@ std::string Tests::execAndGet(const std::string& cmd) {
|
|||
|
||||
return proc.stdOut();
|
||||
}
|
||||
|
||||
bool Tests::writeFile(const std::string& name, const std::string& contents) {
|
||||
std::ofstream of(name, std::ios::trunc);
|
||||
if (!of.good())
|
||||
return false;
|
||||
|
||||
of << contents;
|
||||
of.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,10 +9,14 @@
|
|||
//NOLINTNEXTLINE
|
||||
namespace Tests {
|
||||
Hyprutils::Memory::CUniquePointer<Hyprutils::OS::CProcess> spawnKitty(const std::string& class_ = "", const std::vector<std::string> args = {});
|
||||
Hyprutils::Memory::CUniquePointer<Hyprutils::OS::CProcess> spawnLayerKitty(const std::string& namespace_ = "", const std::vector<std::string> args = {});
|
||||
bool processAlive(pid_t pid);
|
||||
int windowCount();
|
||||
int countOccurrences(const std::string& in, const std::string& what);
|
||||
bool killAllWindows();
|
||||
void waitUntilWindowsN(int n);
|
||||
int layerCount();
|
||||
bool killAllLayers();
|
||||
std::string execAndGet(const std::string& cmd);
|
||||
bool writeFile(const std::string& name, const std::string& contents);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -179,6 +179,17 @@ master {
|
|||
new_status = master
|
||||
}
|
||||
|
||||
scrolling {
|
||||
fullscreen_on_one_column = true
|
||||
column_width = 0.5
|
||||
focus_fit_method = 1
|
||||
follow_focus = true
|
||||
follow_min_visible = 1
|
||||
explicit_column_widths = 0.25, 0.333, 0.5, 0.667, 0.75, 1.0
|
||||
wrap_focus = true
|
||||
wrap_swapcol = true
|
||||
}
|
||||
|
||||
# https://wiki.hyprland.org/Configuring/Variables/#misc
|
||||
misc {
|
||||
force_default_wallpaper = -1 # Set to 0 or 1 to disable the anime mascot wallpapers
|
||||
|
|
@ -239,7 +250,7 @@ bind = $mainMod, E, exec, $fileManager
|
|||
bind = $mainMod, V, togglefloating,
|
||||
bind = $mainMod, R, exec, $menu
|
||||
bind = $mainMod, P, pseudo, # dwindle
|
||||
bind = $mainMod, J, togglesplit, # dwindle
|
||||
bind = $mainMod, J, layoutmsg, togglesplit, # dwindle
|
||||
|
||||
# Move focus with mainMod + arrow keys
|
||||
bind = $mainMod, left, movefocus, l
|
||||
|
|
@ -398,3 +409,5 @@ 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
|
||||
|
||||
gesturep = 2, right, float
|
||||
|
|
|
|||
107
nix/default.nix
107
nix/default.nix
|
|
@ -12,6 +12,7 @@
|
|||
epoll-shim,
|
||||
git,
|
||||
glaze-hyprland,
|
||||
glslang,
|
||||
gtest,
|
||||
hyprcursor,
|
||||
hyprgraphics,
|
||||
|
|
@ -21,10 +22,17 @@
|
|||
hyprutils,
|
||||
hyprwayland-scanner,
|
||||
hyprwire,
|
||||
lcms2,
|
||||
libGL,
|
||||
libdrm,
|
||||
libexecinfo,
|
||||
libinput,
|
||||
libxcb,
|
||||
libxcb-errors,
|
||||
libxcb-render-util,
|
||||
libxcb-wm,
|
||||
libxdmcp,
|
||||
libxcursor,
|
||||
libxkbcommon,
|
||||
libuuid,
|
||||
libgbm,
|
||||
|
|
@ -38,7 +46,6 @@
|
|||
wayland,
|
||||
wayland-protocols,
|
||||
wayland-scanner,
|
||||
xorg,
|
||||
xwayland,
|
||||
debug ? false,
|
||||
withTests ? false,
|
||||
|
|
@ -59,8 +66,20 @@
|
|||
inherit (builtins) foldl' readFile;
|
||||
inherit (lib.asserts) assertMsg;
|
||||
inherit (lib.attrsets) mapAttrsToList;
|
||||
inherit (lib.lists) flatten concatLists optional optionals;
|
||||
inherit (lib.strings) makeBinPath optionalString cmakeBool trim;
|
||||
inherit
|
||||
(lib.lists)
|
||||
flatten
|
||||
concatLists
|
||||
optional
|
||||
optionals
|
||||
;
|
||||
inherit
|
||||
(lib.strings)
|
||||
makeBinPath
|
||||
optionalString
|
||||
cmakeBool
|
||||
trim
|
||||
;
|
||||
fs = lib.fileset;
|
||||
|
||||
adapters = flatten [
|
||||
|
|
@ -72,7 +91,8 @@
|
|||
in
|
||||
assert assertMsg (!nvidiaPatches) "The option `nvidiaPatches` 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 (!withHyprtester) "The option `withHyprtester` has been removed. Hyprtester is always built now.";
|
||||
customStdenv.mkDerivation (finalAttrs: {
|
||||
|
|
@ -85,23 +105,29 @@ in
|
|||
fs.intersection
|
||||
# allows non-flake builds to only include files tracked by git
|
||||
(fs.gitTracked ../.)
|
||||
(fs.unions (flatten [
|
||||
../assets/hyprland-portals.conf
|
||||
../assets/install
|
||||
../hyprctl
|
||||
../hyprland.pc.in
|
||||
../LICENSE
|
||||
../protocols
|
||||
../src
|
||||
../start
|
||||
../systemd
|
||||
../VERSION
|
||||
(fs.fileFilter (file: file.hasExt "1") ../docs)
|
||||
(fs.fileFilter (file: file.hasExt "conf" || file.hasExt "in") ../example)
|
||||
(fs.fileFilter (file: file.hasExt "sh") ../scripts)
|
||||
(fs.fileFilter (file: file.name == "CMakeLists.txt") ../.)
|
||||
(optional withTests [../tests ../hyprtester])
|
||||
]));
|
||||
(
|
||||
fs.unions (flatten [
|
||||
../assets/hyprland-portals.conf
|
||||
../assets/install
|
||||
../hyprctl
|
||||
../hyprland.pc.in
|
||||
../hyprpm
|
||||
../LICENSE
|
||||
../protocols
|
||||
../src
|
||||
../start
|
||||
../systemd
|
||||
../VERSION
|
||||
(fs.fileFilter (file: file.hasExt "1") ../docs)
|
||||
(fs.fileFilter (file: file.hasExt "conf" || file.hasExt "in") ../example)
|
||||
(fs.fileFilter (file: file.hasExt "sh") ../scripts)
|
||||
(fs.fileFilter (file: file.name == "CMakeLists.txt") ../.)
|
||||
(optional withTests [
|
||||
../tests
|
||||
../hyprtester
|
||||
])
|
||||
])
|
||||
);
|
||||
};
|
||||
|
||||
postPatch = ''
|
||||
|
|
@ -117,7 +143,10 @@ in
|
|||
GIT_COMMITS = revCount;
|
||||
GIT_COMMIT_DATE = date;
|
||||
GIT_COMMIT_HASH = commit;
|
||||
GIT_DIRTY = if (commit == "") then "clean" else "dirty";
|
||||
GIT_DIRTY =
|
||||
if (commit == "")
|
||||
then "clean"
|
||||
else "dirty";
|
||||
GIT_TAG = "v${trim (readFile "${finalAttrs.src}/VERSION")}";
|
||||
};
|
||||
|
||||
|
|
@ -145,6 +174,7 @@ in
|
|||
cairo
|
||||
git
|
||||
glaze-hyprland
|
||||
glslang
|
||||
gtest
|
||||
hyprcursor
|
||||
hyprgraphics
|
||||
|
|
@ -152,12 +182,14 @@ in
|
|||
hyprlang
|
||||
hyprutils
|
||||
hyprwire
|
||||
lcms2
|
||||
libdrm
|
||||
libgbm
|
||||
libGL
|
||||
libinput
|
||||
libuuid
|
||||
libxcursor
|
||||
libxkbcommon
|
||||
libgbm
|
||||
muparser
|
||||
pango
|
||||
pciutils
|
||||
|
|
@ -167,16 +199,15 @@ in
|
|||
wayland
|
||||
wayland-protocols
|
||||
wayland-scanner
|
||||
xorg.libXcursor
|
||||
]
|
||||
(optionals customStdenv.hostPlatform.isBSD [epoll-shim])
|
||||
(optionals customStdenv.hostPlatform.isMusl [libexecinfo])
|
||||
(optionals enableXWayland [
|
||||
xorg.libxcb
|
||||
xorg.libXdmcp
|
||||
xorg.xcbutilerrors
|
||||
xorg.xcbutilrenderutil
|
||||
xorg.xcbutilwm
|
||||
libxcb
|
||||
libxcb-errors
|
||||
libxcb-render-util
|
||||
libxcb-wm
|
||||
libxdmcp
|
||||
xwayland
|
||||
])
|
||||
(optional withSystemd systemd)
|
||||
|
|
@ -199,7 +230,6 @@ in
|
|||
"NO_SYSTEMD" = !withSystemd;
|
||||
"CMAKE_DISABLE_PRECOMPILE_HEADERS" = true;
|
||||
"NO_UWSM" = !withSystemd;
|
||||
"NO_HYPRPM" = true;
|
||||
"TRACY_ENABLE" = false;
|
||||
"WITH_TESTS" = withTests;
|
||||
};
|
||||
|
|
@ -213,23 +243,26 @@ in
|
|||
postInstall = ''
|
||||
${optionalString wrapRuntimeDeps ''
|
||||
wrapProgram $out/bin/Hyprland \
|
||||
--suffix PATH : ${makeBinPath [
|
||||
binutils
|
||||
hyprland-guiutils
|
||||
pciutils
|
||||
pkgconf
|
||||
]}
|
||||
--suffix PATH : ${
|
||||
makeBinPath [
|
||||
binutils
|
||||
hyprland-guiutils
|
||||
pciutils
|
||||
pkgconf
|
||||
]
|
||||
}
|
||||
''}
|
||||
|
||||
${optionalString withTests ''
|
||||
install hyprtester/pointer-warp -t $out/bin
|
||||
install hyprtester/pointer-scroll -t $out/bin
|
||||
install hyprtester/shortcut-inhibitor -t $out/bin
|
||||
install hyprland_gtests -t $out/bin
|
||||
install hyprtester/child-window -t $out/bin
|
||||
''}
|
||||
'';
|
||||
|
||||
passthru.providedSessions = ["hyprland"];
|
||||
passthru.providedSessions = ["hyprland"] ++ optionals withSystemd ["hyprland-uwsm"];
|
||||
|
||||
meta = {
|
||||
homepage = "https://github.com/hyprwm/Hyprland";
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
writeShellApplication,
|
||||
deadnix,
|
||||
statix,
|
||||
alejandra,
|
||||
nixfmt,
|
||||
llvmPackages_19,
|
||||
fd,
|
||||
}:
|
||||
|
|
@ -11,7 +11,7 @@ writeShellApplication {
|
|||
runtimeInputs = [
|
||||
deadnix
|
||||
statix
|
||||
alejandra
|
||||
nixfmt
|
||||
llvmPackages_19.clang-tools
|
||||
fd
|
||||
];
|
||||
|
|
@ -24,14 +24,14 @@ writeShellApplication {
|
|||
nix_format() {
|
||||
if [ "$*" = 0 ]; then
|
||||
fd '.*\.nix' . -E "$excludes" -x statix fix -- {} \;
|
||||
fd '.*\.nix' . -E "$excludes" -X deadnix -e -- {} \; -X alejandra {} \;
|
||||
fd '.*\.nix' . -E "$excludes" -X deadnix -e -- {} \; -X nixfmt {} \;
|
||||
elif [ -d "$1" ]; then
|
||||
fd '.*\.nix' "$1" -E "$excludes" -i -x statix fix -- {} \;
|
||||
fd '.*\.nix' "$1" -E "$excludes" -i -X deadnix -e -- {} \; -X alejandra {} \;
|
||||
fd '.*\.nix' "$1" -E "$excludes" -i -X deadnix -e -- {} \; -X nixfmt {} \;
|
||||
else
|
||||
statix fix -- "$1"
|
||||
deadnix -e "$1"
|
||||
alejandra "$1"
|
||||
nixfmt "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
self: {
|
||||
config,
|
||||
self:
|
||||
{
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
}:
|
||||
let
|
||||
inherit (pkgs.stdenv.hostPlatform) system;
|
||||
|
||||
package = self.packages.${system}.default;
|
||||
in {
|
||||
in
|
||||
{
|
||||
config = {
|
||||
wayland.windowManager.hyprland.package = lib.mkDefault package;
|
||||
};
|
||||
|
|
|
|||
115
nix/lib.nix
115
nix/lib.nix
|
|
@ -1,4 +1,5 @@
|
|||
lib: let
|
||||
lib:
|
||||
let
|
||||
inherit (lib)
|
||||
attrNames
|
||||
filterAttrs
|
||||
|
|
@ -17,7 +18,7 @@ lib: let
|
|||
|
||||
This function takes a nested attribute set and converts it into Hyprland-compatible
|
||||
configuration syntax, supporting top, bottom, and regular command sections.
|
||||
|
||||
|
||||
Commands are flattened using the `flattenAttrs` function, and attributes are formatted as
|
||||
`key = value` pairs. Lists are expanded as duplicate keys to match Hyprland's expected format.
|
||||
|
||||
|
|
@ -81,44 +82,51 @@ lib: let
|
|||
|
||||
:::
|
||||
*/
|
||||
toHyprlang = {
|
||||
topCommandsPrefixes ? ["$" "bezier"],
|
||||
bottomCommandsPrefixes ? [],
|
||||
}: attrs: let
|
||||
toHyprlang' = attrs: let
|
||||
# Specially configured `toKeyValue` generator with support for duplicate keys
|
||||
# and a legible key-value separator.
|
||||
mkCommands = generators.toKeyValue {
|
||||
mkKeyValue = generators.mkKeyValueDefault {} " = ";
|
||||
listsAsDuplicateKeys = true;
|
||||
indent = ""; # No indent, since we don't have nesting
|
||||
};
|
||||
toHyprlang =
|
||||
{
|
||||
topCommandsPrefixes ? [
|
||||
"$"
|
||||
"bezier"
|
||||
],
|
||||
bottomCommandsPrefixes ? [ ],
|
||||
}:
|
||||
attrs:
|
||||
let
|
||||
toHyprlang' =
|
||||
attrs:
|
||||
let
|
||||
# Specially configured `toKeyValue` generator with support for duplicate keys
|
||||
# and a legible key-value separator.
|
||||
mkCommands = generators.toKeyValue {
|
||||
mkKeyValue = generators.mkKeyValueDefault { } " = ";
|
||||
listsAsDuplicateKeys = true;
|
||||
indent = ""; # No indent, since we don't have nesting
|
||||
};
|
||||
|
||||
# Flatten the attrset, combining keys in a "path" like `"a:b:c" = "x"`.
|
||||
# Uses `flattenAttrs` with a colon separator.
|
||||
commands = flattenAttrs (p: k: "${p}:${k}") attrs;
|
||||
# Flatten the attrset, combining keys in a "path" like `"a:b:c" = "x"`.
|
||||
# Uses `flattenAttrs` with a colon separator.
|
||||
commands = flattenAttrs (p: k: "${p}:${k}") attrs;
|
||||
|
||||
# General filtering function to check if a key starts with any prefix in a given list.
|
||||
filterCommands = list: n:
|
||||
foldl (acc: prefix: acc || hasPrefix prefix n) false list;
|
||||
# General filtering function to check if a key starts with any prefix in a given list.
|
||||
filterCommands = list: n: foldl (acc: prefix: acc || hasPrefix prefix n) false list;
|
||||
|
||||
# Partition keys into top commands and the rest
|
||||
result = partition (filterCommands topCommandsPrefixes) (attrNames commands);
|
||||
topCommands = filterAttrs (n: _: builtins.elem n result.right) commands;
|
||||
remainingCommands = removeAttrs commands result.right;
|
||||
# Partition keys into top commands and the rest
|
||||
result = partition (filterCommands topCommandsPrefixes) (attrNames commands);
|
||||
topCommands = filterAttrs (n: _: builtins.elem n result.right) commands;
|
||||
remainingCommands = removeAttrs commands result.right;
|
||||
|
||||
# Partition remaining commands into bottom commands and regular commands
|
||||
result2 = partition (filterCommands bottomCommandsPrefixes) result.wrong;
|
||||
bottomCommands = filterAttrs (n: _: builtins.elem n result2.right) remainingCommands;
|
||||
regularCommands = removeAttrs remainingCommands result2.right;
|
||||
# Partition remaining commands into bottom commands and regular commands
|
||||
result2 = partition (filterCommands bottomCommandsPrefixes) result.wrong;
|
||||
bottomCommands = filterAttrs (n: _: builtins.elem n result2.right) remainingCommands;
|
||||
regularCommands = removeAttrs remainingCommands result2.right;
|
||||
in
|
||||
# Concatenate strings from mapping `mkCommands` over top, regular, and bottom commands.
|
||||
concatMapStrings mkCommands [
|
||||
topCommands
|
||||
regularCommands
|
||||
bottomCommands
|
||||
];
|
||||
in
|
||||
# Concatenate strings from mapping `mkCommands` over top, regular, and bottom commands.
|
||||
concatMapStrings mkCommands [
|
||||
topCommands
|
||||
regularCommands
|
||||
bottomCommands
|
||||
];
|
||||
in
|
||||
toHyprlang' attrs;
|
||||
|
||||
/**
|
||||
|
|
@ -131,7 +139,7 @@ lib: let
|
|||
Configuration:
|
||||
|
||||
* `pred` - A function `(string -> string -> string)` defining how keys should be concatenated.
|
||||
|
||||
|
||||
# Inputs
|
||||
|
||||
Structured function argument:
|
||||
|
|
@ -139,7 +147,7 @@ lib: let
|
|||
: pred (required)
|
||||
: A function that determines how parent and child keys should be combined into a single key.
|
||||
It takes a `prefix` (parent key) and `key` (current key) and returns the joined key.
|
||||
|
||||
|
||||
Value:
|
||||
|
||||
: The nested attribute set to be flattened.
|
||||
|
|
@ -174,26 +182,21 @@ lib: let
|
|||
```
|
||||
|
||||
:::
|
||||
|
||||
*/
|
||||
flattenAttrs = pred: attrs: let
|
||||
flattenAttrs' = prefix: attrs:
|
||||
builtins.foldl' (
|
||||
acc: key: let
|
||||
value = attrs.${key};
|
||||
newKey =
|
||||
if prefix == ""
|
||||
then key
|
||||
else pred prefix key;
|
||||
in
|
||||
acc
|
||||
// (
|
||||
if builtins.isAttrs value
|
||||
then flattenAttrs' newKey value
|
||||
else {"${newKey}" = value;}
|
||||
)
|
||||
) {} (builtins.attrNames attrs);
|
||||
in
|
||||
flattenAttrs =
|
||||
pred: attrs:
|
||||
let
|
||||
flattenAttrs' =
|
||||
prefix: attrs:
|
||||
builtins.foldl' (
|
||||
acc: key:
|
||||
let
|
||||
value = attrs.${key};
|
||||
newKey = if prefix == "" then key else pred prefix key;
|
||||
in
|
||||
acc // (if builtins.isAttrs value then flattenAttrs' newKey value else { "${newKey}" = value; })
|
||||
) { } (builtins.attrNames attrs);
|
||||
in
|
||||
flattenAttrs' "" attrs;
|
||||
in
|
||||
{
|
||||
|
|
|
|||
111
nix/module.nix
111
nix/module.nix
|
|
@ -1,18 +1,21 @@
|
|||
inputs: {
|
||||
inputs:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
}:
|
||||
let
|
||||
inherit (pkgs.stdenv.hostPlatform) system;
|
||||
selflib = import ./lib.nix lib;
|
||||
cfg = config.programs.hyprland;
|
||||
in {
|
||||
in
|
||||
{
|
||||
options = {
|
||||
programs.hyprland = {
|
||||
plugins = lib.mkOption {
|
||||
type = with lib.types; listOf (either package path);
|
||||
default = [];
|
||||
default = [ ];
|
||||
description = ''
|
||||
List of Hyprland plugins to use. Can either be packages or
|
||||
absolute plugin paths.
|
||||
|
|
@ -20,23 +23,25 @@ in {
|
|||
};
|
||||
|
||||
settings = lib.mkOption {
|
||||
type = with lib.types; let
|
||||
valueType =
|
||||
nullOr (oneOf [
|
||||
bool
|
||||
int
|
||||
float
|
||||
str
|
||||
path
|
||||
(attrsOf valueType)
|
||||
(listOf valueType)
|
||||
])
|
||||
// {
|
||||
description = "Hyprland configuration value";
|
||||
};
|
||||
in
|
||||
type =
|
||||
with lib.types;
|
||||
let
|
||||
valueType =
|
||||
nullOr (oneOf [
|
||||
bool
|
||||
int
|
||||
float
|
||||
str
|
||||
path
|
||||
(attrsOf valueType)
|
||||
(listOf valueType)
|
||||
])
|
||||
// {
|
||||
description = "Hyprland configuration value";
|
||||
};
|
||||
in
|
||||
valueType;
|
||||
default = {};
|
||||
default = { };
|
||||
description = ''
|
||||
Hyprland configuration written in Nix. Entries with the same key
|
||||
should be written as lists. Variables' and colors' names should be
|
||||
|
|
@ -92,8 +97,15 @@ in {
|
|||
|
||||
topPrefixes = lib.mkOption {
|
||||
type = with lib.types; listOf str;
|
||||
default = ["$" "bezier"];
|
||||
example = ["$" "bezier" "source"];
|
||||
default = [
|
||||
"$"
|
||||
"bezier"
|
||||
];
|
||||
example = [
|
||||
"$"
|
||||
"bezier"
|
||||
"source"
|
||||
];
|
||||
description = ''
|
||||
List of prefix of attributes to put at the top of the config.
|
||||
'';
|
||||
|
|
@ -101,8 +113,8 @@ in {
|
|||
|
||||
bottomPrefixes = lib.mkOption {
|
||||
type = with lib.types; listOf str;
|
||||
default = [];
|
||||
example = ["source"];
|
||||
default = [ ];
|
||||
example = [ "source" ];
|
||||
description = ''
|
||||
List of prefix of attributes to put at the bottom of the config.
|
||||
'';
|
||||
|
|
@ -117,35 +129,36 @@ in {
|
|||
};
|
||||
}
|
||||
(lib.mkIf cfg.enable {
|
||||
environment.etc."xdg/hypr/hyprland.conf" = let
|
||||
shouldGenerate = cfg.extraConfig != "" || cfg.settings != {} || cfg.plugins != [];
|
||||
environment.etc."xdg/hypr/hyprland.conf" =
|
||||
let
|
||||
shouldGenerate = cfg.extraConfig != "" || cfg.settings != { } || cfg.plugins != [ ];
|
||||
|
||||
pluginsToHyprlang = plugins:
|
||||
selflib.toHyprlang {
|
||||
topCommandsPrefixes = cfg.topPrefixes;
|
||||
bottomCommandsPrefixes = cfg.bottomPrefixes;
|
||||
}
|
||||
{
|
||||
"exec-once" = let
|
||||
mkEntry = entry:
|
||||
if lib.types.package.check entry
|
||||
then "${entry}/lib/lib${entry.pname}.so"
|
||||
else entry;
|
||||
hyprctl = lib.getExe' config.programs.hyprland.package "hyprctl";
|
||||
in
|
||||
map (p: "${hyprctl} plugin load ${mkEntry p}") cfg.plugins;
|
||||
};
|
||||
in
|
||||
lib.mkIf shouldGenerate {
|
||||
text =
|
||||
lib.optionalString (cfg.plugins != [])
|
||||
(pluginsToHyprlang cfg.plugins)
|
||||
+ lib.optionalString (cfg.settings != {})
|
||||
(selflib.toHyprlang {
|
||||
pluginsToHyprlang =
|
||||
_plugins:
|
||||
selflib.toHyprlang
|
||||
{
|
||||
topCommandsPrefixes = cfg.topPrefixes;
|
||||
bottomCommandsPrefixes = cfg.bottomPrefixes;
|
||||
}
|
||||
cfg.settings)
|
||||
{
|
||||
"exec-once" =
|
||||
let
|
||||
mkEntry =
|
||||
entry: if lib.types.package.check entry then "${entry}/lib/lib${entry.pname}.so" else entry;
|
||||
hyprctl = lib.getExe' config.programs.hyprland.package "hyprctl";
|
||||
in
|
||||
map (p: "${hyprctl} plugin load ${mkEntry p}") cfg.plugins;
|
||||
};
|
||||
in
|
||||
lib.mkIf shouldGenerate {
|
||||
text =
|
||||
lib.optionalString (cfg.plugins != [ ]) (pluginsToHyprlang cfg.plugins)
|
||||
+ lib.optionalString (cfg.settings != { }) (
|
||||
selflib.toHyprlang {
|
||||
topCommandsPrefixes = cfg.topPrefixes;
|
||||
bottomCommandsPrefixes = cfg.bottomPrefixes;
|
||||
} cfg.settings
|
||||
)
|
||||
+ lib.optionalString (cfg.extraConfig != "") cfg.extraConfig;
|
||||
};
|
||||
})
|
||||
|
|
|
|||
115
nix/overlays.nix
115
nix/overlays.nix
|
|
@ -2,20 +2,27 @@
|
|||
self,
|
||||
lib,
|
||||
inputs,
|
||||
}: let
|
||||
mkDate = longDate: (lib.concatStringsSep "-" [
|
||||
(builtins.substring 0 4 longDate)
|
||||
(builtins.substring 4 2 longDate)
|
||||
(builtins.substring 6 2 longDate)
|
||||
]);
|
||||
}:
|
||||
let
|
||||
mkDate =
|
||||
longDate:
|
||||
(lib.concatStringsSep "-" [
|
||||
(builtins.substring 0 4 longDate)
|
||||
(builtins.substring 4 2 longDate)
|
||||
(builtins.substring 6 2 longDate)
|
||||
]);
|
||||
ver = lib.removeSuffix "\n" (builtins.readFile ../VERSION);
|
||||
in {
|
||||
in
|
||||
{
|
||||
# Contains what a user is most likely to care about:
|
||||
# Hyprland itself, XDPH and the Share Picker.
|
||||
default = lib.composeManyExtensions (with self.overlays; [
|
||||
hyprland-packages
|
||||
hyprland-extras
|
||||
]);
|
||||
default = lib.composeManyExtensions (
|
||||
with self.overlays;
|
||||
[
|
||||
hyprland-packages
|
||||
hyprland-extras
|
||||
]
|
||||
);
|
||||
|
||||
# Packages for variations of Hyprland, dependencies included.
|
||||
hyprland-packages = lib.composeManyExtensions [
|
||||
|
|
@ -33,49 +40,45 @@ in {
|
|||
self.overlays.glaze
|
||||
|
||||
# Hyprland packages themselves
|
||||
(final: _prev: let
|
||||
date = mkDate (self.lastModifiedDate or "19700101");
|
||||
version = "${ver}+date=${date}_${self.shortRev or "dirty"}";
|
||||
in {
|
||||
hyprland = final.callPackage ./default.nix {
|
||||
stdenv = final.gcc15Stdenv;
|
||||
commit = self.rev or "";
|
||||
revCount = self.sourceInfo.revCount or "";
|
||||
inherit date version;
|
||||
};
|
||||
hyprland-unwrapped = final.hyprland.override {wrapRuntimeDeps = false;};
|
||||
(
|
||||
final: _prev:
|
||||
let
|
||||
date = mkDate (self.lastModifiedDate or "19700101");
|
||||
version = "${ver}+date=${date}_${self.shortRev or "dirty"}";
|
||||
in
|
||||
{
|
||||
hyprland = final.callPackage ./default.nix {
|
||||
stdenv = final.gcc15Stdenv;
|
||||
commit = self.rev or "";
|
||||
revCount = self.sourceInfo.revCount or "";
|
||||
inherit date version;
|
||||
};
|
||||
hyprland-unwrapped = final.hyprland.override { wrapRuntimeDeps = false; };
|
||||
|
||||
hyprland-with-tests = final.hyprland.override {withTests = true;};
|
||||
hyprland-with-tests = final.hyprland.override { withTests = true; };
|
||||
|
||||
hyprland-with-hyprtester =
|
||||
builtins.trace ''
|
||||
hyprland-with-hyprtester = builtins.trace ''
|
||||
hyprland-with-hyprtester was removed. Please use the hyprland package.
|
||||
Hyprtester is always built now.
|
||||
''
|
||||
final.hyprland;
|
||||
'' final.hyprland;
|
||||
|
||||
# deprecated packages
|
||||
hyprland-legacy-renderer =
|
||||
builtins.trace ''
|
||||
# deprecated packages
|
||||
hyprland-legacy-renderer = builtins.trace ''
|
||||
hyprland-legacy-renderer was removed. Please use the hyprland package.
|
||||
Legacy renderer is no longer supported.
|
||||
''
|
||||
final.hyprland;
|
||||
'' final.hyprland;
|
||||
|
||||
hyprland-nvidia =
|
||||
builtins.trace ''
|
||||
hyprland-nvidia = builtins.trace ''
|
||||
hyprland-nvidia was removed. Please use the hyprland package.
|
||||
Nvidia patches are no longer needed.
|
||||
''
|
||||
final.hyprland;
|
||||
'' final.hyprland;
|
||||
|
||||
hyprland-hidpi =
|
||||
builtins.trace ''
|
||||
hyprland-hidpi = builtins.trace ''
|
||||
hyprland-hidpi was removed. Please use the hyprland package.
|
||||
For more information, refer to https://wiki.hypr.land/Configuring/XWayland.
|
||||
''
|
||||
final.hyprland;
|
||||
})
|
||||
'' final.hyprland;
|
||||
}
|
||||
)
|
||||
];
|
||||
|
||||
# Debug
|
||||
|
|
@ -83,10 +86,10 @@ in {
|
|||
# Dependencies
|
||||
self.overlays.hyprland-packages
|
||||
|
||||
(final: prev: {
|
||||
aquamarine = prev.aquamarine.override {debug = true;};
|
||||
hyprutils = prev.hyprutils.override {debug = true;};
|
||||
hyprland-debug = prev.hyprland.override {debug = true;};
|
||||
(_final: prev: {
|
||||
aquamarine = prev.aquamarine.override { debug = true; };
|
||||
hyprutils = prev.hyprutils.override { debug = true; };
|
||||
hyprland-debug = prev.hyprland.override { debug = true; };
|
||||
})
|
||||
];
|
||||
|
||||
|
|
@ -100,21 +103,23 @@ in {
|
|||
# this version is the one used in the git submodule, and allows us to
|
||||
# fetch the source without '?submodules=1'
|
||||
udis86 = final: prev: {
|
||||
udis86-hyprland = prev.udis86.overrideAttrs (_self: _super: {
|
||||
src = final.fetchFromGitHub {
|
||||
owner = "canihavesomecoffee";
|
||||
repo = "udis86";
|
||||
rev = "5336633af70f3917760a6d441ff02d93477b0c86";
|
||||
hash = "sha256-HifdUQPGsKQKQprByeIznvRLONdOXeolOsU5nkwIv3g=";
|
||||
};
|
||||
udis86-hyprland = prev.udis86.overrideAttrs (
|
||||
_self: _super: {
|
||||
src = final.fetchFromGitHub {
|
||||
owner = "canihavesomecoffee";
|
||||
repo = "udis86";
|
||||
rev = "5336633af70f3917760a6d441ff02d93477b0c86";
|
||||
hash = "sha256-HifdUQPGsKQKQprByeIznvRLONdOXeolOsU5nkwIv3g=";
|
||||
};
|
||||
|
||||
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 = _final: prev: {
|
||||
glaze-hyprland = prev.glaze.override {
|
||||
enableSSL = false;
|
||||
enableInterop = false;
|
||||
|
|
|
|||
|
|
@ -1,68 +1,75 @@
|
|||
inputs: pkgs: let
|
||||
inputs: pkgs:
|
||||
let
|
||||
flake = inputs.self.packages.${pkgs.stdenv.hostPlatform.system};
|
||||
hyprland = flake.hyprland-with-tests;
|
||||
in {
|
||||
in
|
||||
{
|
||||
tests = pkgs.testers.runNixOSTest {
|
||||
name = "hyprland-tests";
|
||||
|
||||
nodes.machine = {pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
# Programs needed for tests
|
||||
jq
|
||||
kitty
|
||||
wl-clipboard
|
||||
xorg.xeyes
|
||||
];
|
||||
nodes.machine =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
environment.systemPackages = with pkgs; [
|
||||
# Programs needed for tests
|
||||
jq
|
||||
kitty
|
||||
wl-clipboard
|
||||
xeyes
|
||||
];
|
||||
|
||||
# Enabled by default for some reason
|
||||
services.speechd.enable = false;
|
||||
# Enabled by default for some reason
|
||||
services.speechd.enable = false;
|
||||
|
||||
environment.variables = {
|
||||
"AQ_TRACE" = "1";
|
||||
"HYPRLAND_TRACE" = "1";
|
||||
"XDG_RUNTIME_DIR" = "/tmp";
|
||||
"XDG_CACHE_HOME" = "/tmp";
|
||||
"KITTY_CONFIG_DIRECTORY" = "/etc/kitty";
|
||||
};
|
||||
|
||||
environment.etc."kitty/kitty.conf".text = ''
|
||||
confirm_os_window_close 0
|
||||
'';
|
||||
|
||||
programs.hyprland = {
|
||||
enable = true;
|
||||
package = hyprland;
|
||||
# We don't need portals in this test, so we don't set portalPackage
|
||||
};
|
||||
|
||||
# Test configuration
|
||||
environment.etc."test.conf".source = "${hyprland}/share/hypr/test.conf";
|
||||
|
||||
# Disable portals
|
||||
xdg.portal.enable = pkgs.lib.mkForce false;
|
||||
|
||||
# Autologin root into tty
|
||||
services.getty.autologinUser = "alice";
|
||||
|
||||
system.stateVersion = "24.11";
|
||||
|
||||
users.users.alice = {
|
||||
isNormalUser = true;
|
||||
};
|
||||
|
||||
virtualisation = {
|
||||
cores = 4;
|
||||
# Might crash with less
|
||||
memorySize = 8192;
|
||||
resolution = {
|
||||
x = 1920;
|
||||
y = 1080;
|
||||
environment.variables = {
|
||||
"AQ_TRACE" = "1";
|
||||
"HYPRLAND_TRACE" = "1";
|
||||
"XDG_RUNTIME_DIR" = "/tmp";
|
||||
"XDG_CACHE_HOME" = "/tmp";
|
||||
"KITTY_CONFIG_DIRECTORY" = "/etc/kitty";
|
||||
};
|
||||
|
||||
# Doesn't seem to do much, thought it would fix XWayland crashing
|
||||
qemu.options = ["-vga none -device virtio-gpu-pci"];
|
||||
environment.etc."kitty/kitty.conf".text = ''
|
||||
confirm_os_window_close 0
|
||||
remember_window_size no
|
||||
initial_window_width 640
|
||||
initial_window_height 400
|
||||
'';
|
||||
|
||||
programs.hyprland = {
|
||||
enable = true;
|
||||
package = hyprland;
|
||||
# We don't need portals in this test, so we don't set portalPackage
|
||||
};
|
||||
|
||||
# Test configuration
|
||||
environment.etc."test.conf".source = "${hyprland}/share/hypr/test.conf";
|
||||
|
||||
# Disable portals
|
||||
xdg.portal.enable = pkgs.lib.mkForce false;
|
||||
|
||||
# Autologin root into tty
|
||||
services.getty.autologinUser = "alice";
|
||||
|
||||
system.stateVersion = "24.11";
|
||||
|
||||
users.users.alice = {
|
||||
isNormalUser = true;
|
||||
};
|
||||
|
||||
virtualisation = {
|
||||
cores = 4;
|
||||
# Might crash with less
|
||||
memorySize = 8192;
|
||||
resolution = {
|
||||
x = 1920;
|
||||
y = 1080;
|
||||
};
|
||||
|
||||
# Doesn't seem to do much, thought it would fix XWayland crashing
|
||||
qemu.options = [ "-vga none -device virtio-gpu-pci" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
# Wait for tty to be up
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ echo 'static const std::map<std::string, std::string> SHADERS = {' >> ./src/rend
|
|||
for filename in `ls ${SHADERS_SRC}`; do
|
||||
echo "-- ${filename}"
|
||||
|
||||
{ echo 'R"#('; cat ${SHADERS_SRC}/${filename}; echo ')#"'; } > ./src/render/shaders/${filename}.inc
|
||||
{ echo -n 'R"#('; cat ${SHADERS_SRC}/${filename}; echo ')#"'; } > ./src/render/shaders/${filename}.inc
|
||||
echo "{\"${filename}\"," >> ./src/render/shaders/Shaders.hpp
|
||||
echo "#include \"./${filename}.inc\"" >> ./src/render/shaders/Shaders.hpp
|
||||
echo "}," >> ./src/render/shaders/Shaders.hpp
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include "desktop/state/FocusState.hpp"
|
||||
#include "desktop/history/WindowHistoryTracker.hpp"
|
||||
#include "desktop/history/WorkspaceHistoryTracker.hpp"
|
||||
#include "desktop/view/Group.hpp"
|
||||
#include "helpers/Splashes.hpp"
|
||||
#include "config/ConfigValue.hpp"
|
||||
#include "config/ConfigWatcher.hpp"
|
||||
|
|
@ -19,6 +20,7 @@
|
|||
#include "managers/ANRManager.hpp"
|
||||
#include "managers/eventLoop/EventLoopManager.hpp"
|
||||
#include "managers/permissions/DynamicPermissionManager.hpp"
|
||||
#include "managers/screenshare/ScreenshareManager.hpp"
|
||||
#include <algorithm>
|
||||
#include <aquamarine/output/Output.hpp>
|
||||
#include <bit>
|
||||
|
|
@ -45,6 +47,7 @@
|
|||
#include "protocols/core/Compositor.hpp"
|
||||
#include "protocols/core/Subcompositor.hpp"
|
||||
#include "desktop/view/LayerSurface.hpp"
|
||||
#include "layout/space/Space.hpp"
|
||||
#include "render/Renderer.hpp"
|
||||
#include "xwayland/XWayland.hpp"
|
||||
#include "helpers/ByteOperations.hpp"
|
||||
|
|
@ -60,9 +63,7 @@
|
|||
#include "managers/animation/AnimationManager.hpp"
|
||||
#include "managers/animation/DesktopAnimationManager.hpp"
|
||||
#include "managers/EventManager.hpp"
|
||||
#include "managers/HookSystemManager.hpp"
|
||||
#include "managers/ProtocolManager.hpp"
|
||||
#include "managers/LayoutManager.hpp"
|
||||
#include "managers/WelcomeManager.hpp"
|
||||
#include "render/AsyncResourceGatherer.hpp"
|
||||
#include "plugins/PluginSystem.hpp"
|
||||
|
|
@ -71,6 +72,9 @@
|
|||
#include "debug/HyprDebugOverlay.hpp"
|
||||
#include "helpers/MonitorFrameScheduler.hpp"
|
||||
#include "i18n/Engine.hpp"
|
||||
#include "layout/LayoutManager.hpp"
|
||||
#include "layout/target/WindowTarget.hpp"
|
||||
#include "event/EventBus.hpp"
|
||||
|
||||
#include <hyprutils/string/String.hpp>
|
||||
#include <aquamarine/input/Input.hpp>
|
||||
|
|
@ -103,11 +107,6 @@ static void handleUnrecoverableSignal(int sig) {
|
|||
signal(SIGABRT, SIG_DFL);
|
||||
signal(SIGSEGV, SIG_DFL);
|
||||
|
||||
if (g_pHookSystem && g_pHookSystem->m_currentEventPlugin) {
|
||||
longjmp(g_pHookSystem->m_hookFaultJumpBuf, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Kill the program if the crash-reporter is caught in a deadlock.
|
||||
signal(SIGALRM, [](int _) {
|
||||
char const* msg = "\nCrashReporter exceeded timeout, forcefully exiting\n";
|
||||
|
|
@ -283,7 +282,6 @@ static bool filterGlobals(const wl_client* client, const wl_global* global, void
|
|||
//
|
||||
void CCompositor::initServer(std::string socketName, int socketFd) {
|
||||
if (m_onlyConfigVerification) {
|
||||
g_pHookSystem = makeUnique<CHookSystemManager>();
|
||||
g_pKeybindManager = makeUnique<CKeybindManager>();
|
||||
g_pAnimationManager = makeUnique<CHyprAnimationManager>();
|
||||
g_pConfigManager = makeUnique<CConfigManager>();
|
||||
|
|
@ -372,11 +370,11 @@ void CCompositor::initServer(std::string socketName, int socketFd) {
|
|||
return ret == 0 && cap != 0;
|
||||
};
|
||||
|
||||
if ((m_drm.syncobjSupport = syncObjSupport(m_drm.fd)))
|
||||
Log::logger->log(Log::DEBUG, "DRM DisplayNode syncobj timeline support: {}", m_drm.syncobjSupport ? "yes" : "no");
|
||||
m_drm.syncobjSupport = syncObjSupport(m_drm.fd);
|
||||
Log::logger->log(Log::DEBUG, "DRM DisplayNode syncobj timeline support: {}", m_drm.syncobjSupport ? "yes" : "no");
|
||||
|
||||
if ((m_drmRenderNode.syncObjSupport = syncObjSupport(m_drmRenderNode.fd)))
|
||||
Log::logger->log(Log::DEBUG, "DRM RenderNode syncobj timeline support: {}", m_drmRenderNode.syncObjSupport ? "yes" : "no");
|
||||
m_drmRenderNode.syncObjSupport = syncObjSupport(m_drmRenderNode.fd);
|
||||
Log::logger->log(Log::DEBUG, "DRM RenderNode syncobj timeline support: {}", m_drmRenderNode.syncObjSupport ? "yes" : "no");
|
||||
|
||||
if (!m_drm.syncobjSupport && !m_drmRenderNode.syncObjSupport)
|
||||
Log::logger->log(Log::DEBUG, "DRM no syncobj support, disabling explicit sync");
|
||||
|
|
@ -483,6 +481,10 @@ void CCompositor::initAllSignals() {
|
|||
|
||||
m_sessionActive = true;
|
||||
|
||||
// Reset animation tick state to avoid stale timer issues after suspend/wake
|
||||
if (g_pAnimationManager)
|
||||
g_pAnimationManager->resetTickState();
|
||||
|
||||
for (auto const& m : m_monitors) {
|
||||
scheduleFrameForMonitor(m);
|
||||
m->applyMonitorRule(&m->m_activeMonitorRule, true);
|
||||
|
|
@ -586,11 +588,10 @@ void CCompositor::cleanup() {
|
|||
g_pHyprRenderer.reset();
|
||||
g_pHyprOpenGL.reset();
|
||||
g_pConfigManager.reset();
|
||||
g_pLayoutManager.reset();
|
||||
g_layoutManager.reset();
|
||||
g_pHyprError.reset();
|
||||
g_pConfigManager.reset();
|
||||
g_pKeybindManager.reset();
|
||||
g_pHookSystem.reset();
|
||||
g_pXWaylandManager.reset();
|
||||
g_pPointerManager.reset();
|
||||
g_pSeatManager.reset();
|
||||
|
|
@ -619,9 +620,6 @@ void CCompositor::initManagers(eManagersInitStage stage) {
|
|||
Log::logger->log(Log::DEBUG, "Creating the EventLoopManager!");
|
||||
g_pEventLoopManager = makeUnique<CEventLoopManager>(m_wlDisplay, m_wlEventLoop);
|
||||
|
||||
Log::logger->log(Log::DEBUG, "Creating the HookSystem!");
|
||||
g_pHookSystem = makeUnique<CHookSystemManager>();
|
||||
|
||||
Log::logger->log(Log::DEBUG, "Creating the KeybindManager!");
|
||||
g_pKeybindManager = makeUnique<CKeybindManager>();
|
||||
|
||||
|
|
@ -638,7 +636,7 @@ void CCompositor::initManagers(eManagersInitStage stage) {
|
|||
g_pHyprError = makeUnique<CHyprError>();
|
||||
|
||||
Log::logger->log(Log::DEBUG, "Creating the LayoutManager!");
|
||||
g_pLayoutManager = makeUnique<CLayoutManager>();
|
||||
g_layoutManager = makeUnique<Layout::CLayoutManager>();
|
||||
|
||||
Log::logger->log(Log::DEBUG, "Creating the TokenManager!");
|
||||
g_pTokenManager = makeUnique<CTokenManager>();
|
||||
|
|
@ -792,7 +790,8 @@ void CCompositor::startCompositor() {
|
|||
|
||||
createLockFile();
|
||||
|
||||
EMIT_HOOK_EVENT("ready", nullptr);
|
||||
Event::bus()->m_events.ready.emit();
|
||||
|
||||
if (m_watchdogWriteFd.isValid())
|
||||
write(m_watchdogWriteFd.get(), "vax", 3);
|
||||
|
||||
|
|
@ -872,7 +871,7 @@ PHLMONITOR CCompositor::getMonitorFromVector(const Vector2D& point) {
|
|||
|
||||
void CCompositor::removeWindowFromVectorSafe(PHLWINDOW pWindow) {
|
||||
if (!pWindow->m_fadingOut) {
|
||||
EMIT_HOOK_EVENT("destroyWindow", pWindow);
|
||||
Event::bus()->m_events.window.destroy.emit(pWindow);
|
||||
|
||||
std::erase_if(m_windows, [&](SP<Desktop::View::CWindow>& el) { return el == pWindow; });
|
||||
std::erase_if(m_windowsFadingOut, [&](PHLWINDOWREF el) { return el.lock() == pWindow; });
|
||||
|
|
@ -1034,6 +1033,8 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t proper
|
|||
if (!w->m_isFloating && w->m_isMapped && w->workspaceID() == WSPID && !w->isHidden() && !w->m_X11ShouldntFocus && !w->m_ruleApplicator->noFocus().valueOrDefault() &&
|
||||
w != pIgnoreWindow && !isShadowedByModal(w)) {
|
||||
CBox box = (properties & Desktop::View::USE_PROP_TILED) ? w->getWindowBoxUnified(properties) : CBox{w->m_position, w->m_size};
|
||||
if ((properties & Desktop::View::INPUT_EXTENTS) && BORDER_GRAB_AREA > 0 && !w->isX11OverrideRedirect())
|
||||
box.expand(BORDER_GRAB_AREA);
|
||||
if (box.containsPoint(pos))
|
||||
return w;
|
||||
}
|
||||
|
|
@ -1179,7 +1180,7 @@ PHLWINDOW CCompositor::getWindowFromSurface(SP<CWLSurfaceResource> pSurface) {
|
|||
|
||||
const auto VIEW = pSurface->m_hlSurface->view();
|
||||
|
||||
if (VIEW->type() != Desktop::View::VIEW_TYPE_WINDOW)
|
||||
if (!VIEW || VIEW->type() != Desktop::View::VIEW_TYPE_WINDOW)
|
||||
return nullptr;
|
||||
|
||||
return dynamicPointerCast<Desktop::View::CWindow>(VIEW);
|
||||
|
|
@ -1371,8 +1372,8 @@ void CCompositor::addToFadingOutSafe(PHLWINDOW pWindow) {
|
|||
m_windowsFadingOut.emplace_back(pWindow);
|
||||
}
|
||||
|
||||
PHLWINDOW CCompositor::getWindowInDirection(PHLWINDOW pWindow, char dir) {
|
||||
if (!isDirection(dir))
|
||||
PHLWINDOW CCompositor::getWindowInDirection(PHLWINDOW pWindow, Math::eDirection dir) {
|
||||
if (dir == Math::DIRECTION_DEFAULT)
|
||||
return nullptr;
|
||||
|
||||
const auto PMONITOR = pWindow->m_monitor.lock();
|
||||
|
|
@ -1389,8 +1390,8 @@ PHLWINDOW CCompositor::getWindowInDirection(PHLWINDOW pWindow, char dir) {
|
|||
return getWindowInDirection(WINDOWIDEALBB, PWORKSPACE, dir, pWindow, pWindow->m_isFloating);
|
||||
}
|
||||
|
||||
PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, char dir, PHLWINDOW ignoreWindow, bool useVectorAngles) {
|
||||
if (!isDirection(dir))
|
||||
PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, Math::eDirection dir, PHLWINDOW ignoreWindow, bool useVectorAngles) {
|
||||
if (dir == Math::DIRECTION_DEFAULT)
|
||||
return nullptr;
|
||||
|
||||
// 0 -> history, 1 -> shared length
|
||||
|
|
@ -1404,6 +1405,35 @@ PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorks
|
|||
PHLWINDOW leaderWindow = nullptr;
|
||||
|
||||
if (!useVectorAngles) {
|
||||
// helper to check if two rectangles are adjacent along an axis, considering slight overlaps.
|
||||
// returns true if: STICKS (delta <= 2) OR rectangles overlap but no more than 50% of the smaller dimension.
|
||||
static auto isAdjacent = [](const double aMin, const double aMax, const double bMin, const double bMax) -> bool {
|
||||
constexpr double STICK_THRESHOLD = 2.0;
|
||||
constexpr double MAX_OVERLAP_RATIO = 0.5;
|
||||
|
||||
const double aEdge = aMin;
|
||||
const double bEdge = bMax;
|
||||
const double delta = aEdge - bEdge;
|
||||
|
||||
// old STICKS check for 2px
|
||||
if (std::abs(delta) < STICK_THRESHOLD)
|
||||
return true;
|
||||
|
||||
if (delta >= 0)
|
||||
return false;
|
||||
|
||||
const double overlap = -delta;
|
||||
const double sizeA = aMax - aMin;
|
||||
const double sizeB = bMax - bMin;
|
||||
|
||||
// reject if one rectangle fully contains the other
|
||||
if ((bMin <= aMin && bMax >= aMax) || (aMin <= bMin && aMax >= bMax))
|
||||
return false;
|
||||
|
||||
// accept if overlap is at most 50% of the smaller dimension
|
||||
return overlap <= std::min(sizeA, sizeB) * MAX_OVERLAP_RATIO;
|
||||
};
|
||||
|
||||
for (auto const& w : m_windows) {
|
||||
if (w == ignoreWindow || !w->m_workspace || !w->m_isMapped || w->isHidden() || (!w->isFullscreen() && w->m_isFloating) || !w->m_workspace->isVisible())
|
||||
continue;
|
||||
|
|
@ -1425,28 +1455,23 @@ PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorks
|
|||
double intersectLength = -1;
|
||||
|
||||
switch (dir) {
|
||||
case 'l':
|
||||
if (STICKS(POSA.x, POSB.x + SIZEB.x)) {
|
||||
case Math::DIRECTION_LEFT:
|
||||
if (isAdjacent(POSA.x, POSA.x + SIZEA.x, POSB.x, POSB.x + SIZEB.x))
|
||||
intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y));
|
||||
}
|
||||
break;
|
||||
case 'r':
|
||||
if (STICKS(POSA.x + SIZEA.x, POSB.x)) {
|
||||
case Math::DIRECTION_RIGHT:
|
||||
if (isAdjacent(POSB.x, POSB.x + SIZEB.x, POSA.x, POSA.x + SIZEA.x))
|
||||
intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y));
|
||||
}
|
||||
break;
|
||||
case 't':
|
||||
case 'u':
|
||||
if (STICKS(POSA.y, POSB.y + SIZEB.y)) {
|
||||
case Math::DIRECTION_UP:
|
||||
if (isAdjacent(POSA.y, POSA.y + SIZEA.y, POSB.y, POSB.y + SIZEB.y))
|
||||
intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x));
|
||||
}
|
||||
break;
|
||||
case 'b':
|
||||
case 'd':
|
||||
if (STICKS(POSA.y + SIZEA.y, POSB.y)) {
|
||||
case Math::DIRECTION_DOWN:
|
||||
if (isAdjacent(POSB.y, POSB.y + SIZEB.y, POSA.y, POSA.y + SIZEA.y))
|
||||
intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x));
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (*PMETHOD == 0 /* history */) {
|
||||
|
|
@ -1475,12 +1500,8 @@ PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorks
|
|||
}
|
||||
}
|
||||
} else {
|
||||
if (dir == 'u')
|
||||
dir = 't';
|
||||
if (dir == 'd')
|
||||
dir = 'b';
|
||||
|
||||
static const std::unordered_map<char, Vector2D> VECTORS = {{'r', {1, 0}}, {'t', {0, -1}}, {'b', {0, 1}}, {'l', {-1, 0}}};
|
||||
static const std::unordered_map<Math::eDirection, Vector2D> VECTORS = {
|
||||
{Math::DIRECTION_RIGHT, {1, 0}}, {Math::DIRECTION_UP, {0, -1}}, {Math::DIRECTION_DOWN, {0, 1}}, {Math::DIRECTION_LEFT, {-1, 0}}};
|
||||
|
||||
//
|
||||
auto vectorAngles = [](const Vector2D& a, const Vector2D& b) -> double {
|
||||
|
|
@ -1632,38 +1653,28 @@ bool CCompositor::isPointOnReservedArea(const Vector2D& point, const PHLMONITOR
|
|||
return VECNOTINRECT(point, box.x, box.y, box.x + box.w, box.y + box.h);
|
||||
}
|
||||
|
||||
CBox CCompositor::calculateX11WorkArea() {
|
||||
std::optional<CBox> CCompositor::calculateX11WorkArea() {
|
||||
static auto PXWLFORCESCALEZERO = CConfigValue<Hyprlang::INT>("xwayland:force_zero_scaling");
|
||||
CBox workbox = {0, 0, 0, 0};
|
||||
bool firstMonitor = true;
|
||||
// We more than likely won't be able to calculate one
|
||||
// and even if we could this is minor
|
||||
if (m_monitors.size() > 1 || m_monitors.empty())
|
||||
return std::nullopt;
|
||||
|
||||
for (const auto& monitor : m_monitors) {
|
||||
// we ignore monitor->m_position on purpose
|
||||
CBox box = monitor->logicalBoxMinusReserved().translate(-monitor->m_position);
|
||||
if ((*PXWLFORCESCALEZERO))
|
||||
box.scale(monitor->m_scale);
|
||||
const auto M = m_monitors.front();
|
||||
|
||||
if (firstMonitor) {
|
||||
firstMonitor = false;
|
||||
workbox = box;
|
||||
} else {
|
||||
// if this monitor creates a different workbox than previous monitor, we remove the _NET_WORKAREA property all together
|
||||
if ((std::abs(box.x - workbox.x) > 3) || (std::abs(box.y - workbox.y) > 3) || (std::abs(box.w - workbox.w) > 3) || (std::abs(box.h - workbox.h) > 3)) {
|
||||
workbox = {0, 0, 0, 0};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// we ignore monitor->m_position on purpose
|
||||
CBox box = M->logicalBoxMinusReserved().translate(-M->m_position);
|
||||
if ((*PXWLFORCESCALEZERO))
|
||||
box.scale(M->m_scale);
|
||||
|
||||
// returning 0, 0 will remove the _NET_WORKAREA property
|
||||
return workbox;
|
||||
return box.translate(M->m_xwaylandPosition);
|
||||
}
|
||||
|
||||
PHLMONITOR CCompositor::getMonitorInDirection(const char& dir) {
|
||||
PHLMONITOR CCompositor::getMonitorInDirection(Math::eDirection dir) {
|
||||
return getMonitorInDirection(Desktop::focusState()->monitor(), dir);
|
||||
}
|
||||
|
||||
PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const char& dir) {
|
||||
PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, Math::eDirection dir) {
|
||||
if (!pSourceMonitor)
|
||||
return nullptr;
|
||||
|
||||
|
|
@ -1680,7 +1691,7 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const c
|
|||
const auto POSB = m->m_position;
|
||||
const auto SIZEB = m->m_size;
|
||||
switch (dir) {
|
||||
case 'l':
|
||||
case Math::DIRECTION_LEFT:
|
||||
if (STICKS(POSA.x, POSB.x + SIZEB.x)) {
|
||||
const auto INTERSECTLEN = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y));
|
||||
if (INTERSECTLEN > longestIntersect) {
|
||||
|
|
@ -1689,7 +1700,7 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const c
|
|||
}
|
||||
}
|
||||
break;
|
||||
case 'r':
|
||||
case Math::DIRECTION_RIGHT:
|
||||
if (STICKS(POSA.x + SIZEA.x, POSB.x)) {
|
||||
const auto INTERSECTLEN = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y));
|
||||
if (INTERSECTLEN > longestIntersect) {
|
||||
|
|
@ -1698,8 +1709,7 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const c
|
|||
}
|
||||
}
|
||||
break;
|
||||
case 't':
|
||||
case 'u':
|
||||
case Math::DIRECTION_UP:
|
||||
if (STICKS(POSA.y, POSB.y + SIZEB.y)) {
|
||||
const auto INTERSECTLEN = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x));
|
||||
if (INTERSECTLEN > longestIntersect) {
|
||||
|
|
@ -1708,8 +1718,7 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const c
|
|||
}
|
||||
}
|
||||
break;
|
||||
case 'b':
|
||||
case 'd':
|
||||
case Math::DIRECTION_DOWN:
|
||||
if (STICKS(POSA.y + SIZEA.y, POSB.y)) {
|
||||
const auto INTERSECTLEN = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x));
|
||||
if (INTERSECTLEN > longestIntersect) {
|
||||
|
|
@ -1718,6 +1727,7 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const c
|
|||
}
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1773,7 +1783,7 @@ void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitor
|
|||
|
||||
// additionally, move floating and fs windows manually
|
||||
if (w->m_isFloating)
|
||||
*w->m_realPosition = w->m_realPosition->goal() - pMonitorA->m_position + pMonitorB->m_position;
|
||||
w->layoutTarget()->setPositionGlobal(w->layoutTarget()->position().translate(-pMonitorA->m_position + pMonitorB->m_position));
|
||||
|
||||
if (w->isFullscreen()) {
|
||||
*w->m_realPosition = pMonitorB->m_position;
|
||||
|
|
@ -1798,7 +1808,7 @@ void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitor
|
|||
|
||||
// additionally, move floating and fs windows manually
|
||||
if (w->m_isFloating)
|
||||
*w->m_realPosition = w->m_realPosition->goal() - pMonitorB->m_position + pMonitorA->m_position;
|
||||
w->layoutTarget()->setPositionGlobal(w->layoutTarget()->position().translate(-pMonitorB->m_position + pMonitorA->m_position));
|
||||
|
||||
if (w->isFullscreen()) {
|
||||
*w->m_realPosition = pMonitorA->m_position;
|
||||
|
|
@ -1812,8 +1822,11 @@ void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitor
|
|||
pMonitorA->m_activeWorkspace = PWORKSPACEB;
|
||||
pMonitorB->m_activeWorkspace = PWORKSPACEA;
|
||||
|
||||
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitorA->m_id);
|
||||
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitorB->m_id);
|
||||
g_layoutManager->recalculateMonitor(pMonitorA);
|
||||
g_layoutManager->recalculateMonitor(pMonitorB);
|
||||
|
||||
g_pHyprRenderer->damageMonitor(pMonitorB);
|
||||
g_pHyprRenderer->damageMonitor(pMonitorA);
|
||||
|
||||
g_pDesktopAnimationManager->setFullscreenFadeAnimation(
|
||||
PWORKSPACEB, PWORKSPACEB->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : CDesktopAnimationManager::ANIMATION_TYPE_OUT);
|
||||
|
|
@ -1825,29 +1838,30 @@ void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitor
|
|||
Desktop::focusState()->fullWindowFocus(
|
||||
LASTWIN ? LASTWIN :
|
||||
(g_pCompositor->vectorToWindowUnified(g_pInputManager->getMouseCoordsInternal(),
|
||||
Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING)));
|
||||
Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING)),
|
||||
Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE);
|
||||
|
||||
const auto PNEWWORKSPACE = pMonitorA->m_id == Desktop::focusState()->monitor()->m_id ? PWORKSPACEB : PWORKSPACEA;
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{.event = "workspace", .data = PNEWWORKSPACE->m_name});
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{.event = "workspacev2", .data = std::format("{},{}", PNEWWORKSPACE->m_id, PNEWWORKSPACE->m_name)});
|
||||
EMIT_HOOK_EVENT("workspace", PNEWWORKSPACE);
|
||||
Event::bus()->m_events.workspace.active.emit(PNEWWORKSPACE);
|
||||
}
|
||||
|
||||
// event
|
||||
// events
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspace", .data = PWORKSPACEA->m_name + "," + pMonitorB->m_name});
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspacev2", .data = std::format("{},{},{}", PWORKSPACEA->m_id, PWORKSPACEA->m_name, pMonitorB->m_name)});
|
||||
EMIT_HOOK_EVENT("moveWorkspace", (std::vector<std::any>{PWORKSPACEA, pMonitorB}));
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspace", .data = PWORKSPACEB->m_name + "," + pMonitorA->m_name});
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspacev2", .data = std::format("{},{},{}", PWORKSPACEB->m_id, PWORKSPACEB->m_name, pMonitorA->m_name)});
|
||||
|
||||
EMIT_HOOK_EVENT("moveWorkspace", (std::vector<std::any>{PWORKSPACEB, pMonitorA}));
|
||||
Event::bus()->m_events.workspace.moveToMonitor.emit(PWORKSPACEA, pMonitorB);
|
||||
Event::bus()->m_events.workspace.moveToMonitor.emit(PWORKSPACEB, pMonitorA);
|
||||
}
|
||||
|
||||
PHLMONITOR CCompositor::getMonitorFromString(const std::string& name) {
|
||||
if (name == "current")
|
||||
return Desktop::focusState()->monitor();
|
||||
else if (isDirection(name))
|
||||
return getMonitorInDirection(name[0]);
|
||||
return getMonitorInDirection(Math::fromChar(name[0]));
|
||||
else if (name[0] == '+' || name[0] == '-') {
|
||||
// relative
|
||||
|
||||
|
|
@ -1968,6 +1982,7 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo
|
|||
|
||||
// move the workspace
|
||||
pWorkspace->m_monitor = pMonitor;
|
||||
pWorkspace->m_space->recheckWorkArea();
|
||||
pWorkspace->m_events.monitorChanged.emit();
|
||||
|
||||
for (auto const& w : m_windows) {
|
||||
|
|
@ -1983,17 +1998,18 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo
|
|||
if (w->m_isMapped && !w->isHidden()) {
|
||||
if (POLDMON) {
|
||||
if (w->m_isFloating)
|
||||
*w->m_realPosition = w->m_realPosition->goal() - POLDMON->m_position + pMonitor->m_position;
|
||||
w->layoutTarget()->setPositionGlobal(w->layoutTarget()->position().translate(-POLDMON->m_position + pMonitor->m_position));
|
||||
|
||||
if (w->isFullscreen()) {
|
||||
*w->m_realPosition = pMonitor->m_position;
|
||||
*w->m_realSize = pMonitor->m_size;
|
||||
}
|
||||
} else
|
||||
*w->m_realPosition = Vector2D{
|
||||
(pMonitor->m_size.x != 0) ? sc<int>(w->m_realPosition->goal().x) % sc<int>(pMonitor->m_size.x) : 0,
|
||||
(pMonitor->m_size.y != 0) ? sc<int>(w->m_realPosition->goal().y) % sc<int>(pMonitor->m_size.y) : 0,
|
||||
};
|
||||
w->layoutTarget()->setPositionGlobal(CBox{Vector2D{
|
||||
(pMonitor->m_size.x != 0) ? sc<int>(w->m_realPosition->goal().x) % sc<int>(pMonitor->m_size.x) : 0,
|
||||
(pMonitor->m_size.y != 0) ? sc<int>(w->m_realPosition->goal().y) % sc<int>(pMonitor->m_size.y) : 0,
|
||||
},
|
||||
w->layoutTarget()->position().size()});
|
||||
}
|
||||
|
||||
w->updateToplevel();
|
||||
|
|
@ -2021,7 +2037,8 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo
|
|||
|
||||
pWorkspace->m_events.activeChanged.emit();
|
||||
|
||||
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitor->m_id);
|
||||
g_layoutManager->recalculateMonitor(pMonitor);
|
||||
g_pHyprRenderer->damageMonitor(pMonitor);
|
||||
|
||||
g_pDesktopAnimationManager->startAnimation(pWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true);
|
||||
pWorkspace->m_visible = true;
|
||||
|
|
@ -2034,7 +2051,7 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo
|
|||
|
||||
// finalize
|
||||
if (POLDMON) {
|
||||
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(POLDMON->m_id);
|
||||
g_layoutManager->recalculateMonitor(POLDMON);
|
||||
if (valid(POLDMON->m_activeWorkspace))
|
||||
g_pDesktopAnimationManager->setFullscreenFadeAnimation(POLDMON->m_activeWorkspace,
|
||||
POLDMON->m_activeWorkspace->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN :
|
||||
|
|
@ -2049,7 +2066,8 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo
|
|||
// event
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspace", .data = pWorkspace->m_name + "," + pMonitor->m_name});
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspacev2", .data = std::format("{},{},{}", pWorkspace->m_id, pWorkspace->m_name, pMonitor->m_name)});
|
||||
EMIT_HOOK_EVENT("moveWorkspace", (std::vector<std::any>{pWorkspace, pMonitor}));
|
||||
|
||||
Event::bus()->m_events.workspace.moveToMonitor.emit(pWorkspace, pMonitor);
|
||||
}
|
||||
|
||||
bool CCompositor::workspaceIDOutOfBounds(const WORKSPACEID& id) {
|
||||
|
|
@ -2132,24 +2150,25 @@ void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, Desktop::Vie
|
|||
PWINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_FULLSCREEN | Desktop::Rule::RULE_PROP_FULLSCREENSTATE_CLIENT |
|
||||
Desktop::Rule::RULE_PROP_FULLSCREENSTATE_INTERNAL | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
|
||||
PWINDOW->updateDecorationValues();
|
||||
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PWINDOW->monitorID());
|
||||
g_layoutManager->recalculateMonitor(PMONITOR);
|
||||
return;
|
||||
}
|
||||
|
||||
g_pLayoutManager->getCurrentLayout()->fullscreenRequestForWindow(PWINDOW, CURRENT_EFFECTIVE_MODE, EFFECTIVE_MODE);
|
||||
PWORKSPACE->m_fullscreenMode = EFFECTIVE_MODE;
|
||||
PWORKSPACE->m_hasFullscreenWindow = EFFECTIVE_MODE != FSMODE_NONE;
|
||||
|
||||
g_layoutManager->fullscreenRequestForTarget(PWINDOW->layoutTarget(), CURRENT_EFFECTIVE_MODE, EFFECTIVE_MODE);
|
||||
|
||||
PWINDOW->m_fullscreenState.internal = state.internal;
|
||||
PWORKSPACE->m_fullscreenMode = EFFECTIVE_MODE;
|
||||
PWORKSPACE->m_hasFullscreenWindow = EFFECTIVE_MODE != FSMODE_NONE;
|
||||
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{.event = "fullscreen", .data = std::to_string(sc<int>(EFFECTIVE_MODE) != FSMODE_NONE)});
|
||||
EMIT_HOOK_EVENT("fullscreen", PWINDOW);
|
||||
Event::bus()->m_events.window.fullscreen.emit(PWINDOW);
|
||||
|
||||
PWINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_FULLSCREEN | Desktop::Rule::RULE_PROP_FULLSCREENSTATE_CLIENT |
|
||||
Desktop::Rule::RULE_PROP_FULLSCREENSTATE_INTERNAL | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
|
||||
|
||||
PWINDOW->updateDecorationValues();
|
||||
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PWINDOW->monitorID());
|
||||
g_layoutManager->recalculateMonitor(PMONITOR);
|
||||
|
||||
// make all windows and layers on the same workspace under the fullscreen window
|
||||
for (auto const& w : m_windows) {
|
||||
|
|
@ -2262,7 +2281,7 @@ PHLWINDOW CCompositor::getWindowByRegex(const std::string& regexp_) {
|
|||
}
|
||||
|
||||
for (auto const& w : g_pCompositor->m_windows) {
|
||||
if (!w->m_isMapped || (w->isHidden() && !g_pLayoutManager->getCurrentLayout()->isWindowReachable(w)))
|
||||
if (!w->m_isMapped)
|
||||
continue;
|
||||
|
||||
switch (mode) {
|
||||
|
|
@ -2567,59 +2586,30 @@ void CCompositor::moveWindowToWorkspaceSafe(PHLWINDOW pWindow, PHLWORKSPACE pWor
|
|||
setWindowFullscreenInternal(pWindow, FSMODE_NONE);
|
||||
|
||||
const PHLWINDOW pFirstWindowOnWorkspace = pWorkspace->getFirstWindow();
|
||||
const int visibleWindowsOnWorkspace = pWorkspace->getWindows(std::nullopt, true);
|
||||
const int visibleWindowsOnWorkspace = pWorkspace->getWindows(true, std::nullopt, true);
|
||||
const auto POSTOMON = pWindow->m_realPosition->goal() - (pWindow->m_monitor ? pWindow->m_monitor->m_position : Vector2D{});
|
||||
const auto PWORKSPACEMONITOR = pWorkspace->m_monitor.lock();
|
||||
|
||||
if (!pWindow->m_isFloating)
|
||||
g_pLayoutManager->getCurrentLayout()->onWindowRemovedTiling(pWindow);
|
||||
|
||||
pWindow->moveToWorkspace(pWorkspace);
|
||||
pWindow->m_monitor = pWorkspace->m_monitor;
|
||||
|
||||
static auto PGROUPONMOVETOWORKSPACE = CConfigValue<Hyprlang::INT>("group:group_on_movetoworkspace");
|
||||
if (*PGROUPONMOVETOWORKSPACE && visibleWindowsOnWorkspace == 1 && pFirstWindowOnWorkspace && pFirstWindowOnWorkspace != pWindow &&
|
||||
pFirstWindowOnWorkspace->m_groupData.pNextWindow.lock() && pWindow->canBeGroupedInto(pFirstWindowOnWorkspace)) {
|
||||
|
||||
pWindow->m_isFloating = pFirstWindowOnWorkspace->m_isFloating; // match the floating state. Needed to group tiled into floated and vice versa.
|
||||
if (!pWindow->m_groupData.pNextWindow.expired()) {
|
||||
PHLWINDOW next = pWindow->m_groupData.pNextWindow.lock();
|
||||
while (next != pWindow) {
|
||||
next->m_isFloating = pFirstWindowOnWorkspace->m_isFloating; // match the floating state of group members
|
||||
next = next->m_groupData.pNextWindow.lock();
|
||||
}
|
||||
}
|
||||
|
||||
static auto USECURRPOS = CConfigValue<Hyprlang::INT>("group:insert_after_current");
|
||||
(*USECURRPOS ? pFirstWindowOnWorkspace : pFirstWindowOnWorkspace->getGroupTail())->insertWindowToGroup(pWindow);
|
||||
|
||||
pFirstWindowOnWorkspace->setGroupCurrent(pWindow);
|
||||
pWindow->updateWindowDecos();
|
||||
g_pLayoutManager->getCurrentLayout()->recalculateWindow(pWindow);
|
||||
|
||||
if (!pWindow->getDecorationByType(DECORATION_GROUPBAR))
|
||||
pWindow->addWindowDeco(makeUnique<CHyprGroupBarDecoration>(pWindow));
|
||||
|
||||
if (*PGROUPONMOVETOWORKSPACE && visibleWindowsOnWorkspace == 1 && pFirstWindowOnWorkspace && pFirstWindowOnWorkspace != pWindow && pFirstWindowOnWorkspace->m_group &&
|
||||
pWindow->canBeGroupedInto(pFirstWindowOnWorkspace->m_group)) {
|
||||
pFirstWindowOnWorkspace->m_group->add(pWindow);
|
||||
} else {
|
||||
if (!pWindow->m_isFloating)
|
||||
g_pLayoutManager->getCurrentLayout()->onWindowCreatedTiling(pWindow);
|
||||
|
||||
if (pWindow->m_isFloating)
|
||||
*pWindow->m_realPosition = POSTOMON + PWORKSPACEMONITOR->m_position;
|
||||
pWindow->layoutTarget()->setPositionGlobal(CBox{POSTOMON + PWORKSPACEMONITOR->m_position, pWindow->layoutTarget()->position().size()});
|
||||
}
|
||||
|
||||
pWindow->updateToplevel();
|
||||
pWindow->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE);
|
||||
pWindow->uncacheWindowDecos();
|
||||
pWindow->updateGroupOutputs();
|
||||
|
||||
if (!pWindow->m_groupData.pNextWindow.expired()) {
|
||||
PHLWINDOW next = pWindow->m_groupData.pNextWindow.lock();
|
||||
while (next != pWindow) {
|
||||
next->updateToplevel();
|
||||
next = next->m_groupData.pNextWindow.lock();
|
||||
}
|
||||
}
|
||||
if (pWindow->m_group)
|
||||
pWindow->m_group->updateWorkspace(pWorkspace);
|
||||
|
||||
g_layoutManager->newTarget(pWindow->layoutTarget(), pWorkspace->m_space);
|
||||
|
||||
if (FULLSCREEN)
|
||||
setWindowFullscreenInternal(pWindow, FULLSCREENMODE);
|
||||
|
|
@ -2788,12 +2778,17 @@ void CCompositor::arrangeMonitors() {
|
|||
}
|
||||
|
||||
PROTO::xdgOutput->updateAllOutputs();
|
||||
Event::bus()->m_events.monitor.layoutChanged.emit();
|
||||
|
||||
#ifndef NO_XWAYLAND
|
||||
CBox box = g_pCompositor->calculateX11WorkArea();
|
||||
if (!g_pXWayland || !g_pXWayland->m_wm)
|
||||
return;
|
||||
g_pXWayland->m_wm->updateWorkArea(box.x, box.y, box.w, box.h);
|
||||
const auto box = g_pCompositor->calculateX11WorkArea();
|
||||
if (g_pXWayland && g_pXWayland->m_wm) {
|
||||
if (box)
|
||||
g_pXWayland->m_wm->updateWorkArea(box->x, box->y, box->w, box->h);
|
||||
else
|
||||
g_pXWayland->m_wm->updateWorkArea(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
@ -2920,7 +2915,7 @@ void CCompositor::onNewMonitor(SP<Aquamarine::IOutput> output) {
|
|||
PNEWMONITOR->m_id = FALLBACK ? MONITOR_INVALID : g_pCompositor->getNextAvailableMonitorID(output->name);
|
||||
PNEWMONITOR->m_isUnsafeFallback = FALLBACK;
|
||||
|
||||
EMIT_HOOK_EVENT("newMonitor", PNEWMONITOR);
|
||||
Event::bus()->m_events.monitor.newMon.emit(PNEWMONITOR);
|
||||
|
||||
if (!FALLBACK)
|
||||
PNEWMONITOR->onConnect(false);
|
||||
|
|
@ -2973,13 +2968,16 @@ PImageDescription CCompositor::getHDRImageDescription() {
|
|||
}
|
||||
|
||||
return m_monitors.size() == 1 && m_monitors[0]->m_output && m_monitors[0]->m_output->parsedEDID.hdrMetadata.has_value() ?
|
||||
CImageDescription::from(SImageDescription{.transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ,
|
||||
.primariesNameSet = true,
|
||||
.primariesNamed = NColorManagement::CM_PRIMARIES_BT2020,
|
||||
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020),
|
||||
.luminances = {.min = m_monitors[0]->m_output->parsedEDID.hdrMetadata->desiredContentMinLuminance,
|
||||
.max = m_monitors[0]->m_output->parsedEDID.hdrMetadata->desiredContentMaxLuminance,
|
||||
.reference = m_monitors[0]->m_output->parsedEDID.hdrMetadata->desiredMaxFrameAverageLuminance}}) :
|
||||
CImageDescription::from(SImageDescription{
|
||||
.transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ,
|
||||
.primariesNameSet = true,
|
||||
.primariesNamed = NColorManagement::CM_PRIMARIES_BT2020,
|
||||
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020),
|
||||
.masteringPrimaries = m_monitors[0]->getMasteringPrimaries(),
|
||||
.luminances = {.min = m_monitors[0]->minLuminance(HDR_MIN_LUMINANCE), .max = m_monitors[0]->maxLuminance(HDR_MAX_LUMINANCE), .reference = HDR_REF_LUMINANCE},
|
||||
.masteringLuminances = m_monitors[0]->getMasteringLuminances(),
|
||||
.maxCLL = m_monitors[0]->maxCLL(),
|
||||
.maxFALL = m_monitors[0]->maxFALL()}) :
|
||||
DEFAULT_HDR_IMAGE_DESCRIPTION;
|
||||
}
|
||||
|
||||
|
|
@ -3076,6 +3074,27 @@ void CCompositor::ensurePersistentWorkspacesPresent(const std::vector<SWorkspace
|
|||
}
|
||||
}
|
||||
|
||||
void CCompositor::ensureWorkspacesOnAssignedMonitors() {
|
||||
for (auto const& ws : getWorkspacesCopy()) {
|
||||
if (!valid(ws) || ws->m_isSpecialWorkspace)
|
||||
continue;
|
||||
|
||||
const auto RULE = g_pConfigManager->getWorkspaceRuleFor(ws);
|
||||
if (RULE.monitor.empty())
|
||||
continue;
|
||||
|
||||
const auto PMONITOR = getMonitorFromString(RULE.monitor);
|
||||
if (!PMONITOR)
|
||||
continue;
|
||||
|
||||
if (ws->m_monitor == PMONITOR)
|
||||
continue;
|
||||
|
||||
Log::logger->log(Log::DEBUG, "ensureWorkspacesOnAssignedMonitors: moving workspace {} to {}", ws->m_name, PMONITOR->m_name);
|
||||
moveWorkspaceToMonitor(ws, PMONITOR, true);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<unsigned int> CCompositor::getVTNr() {
|
||||
if (!m_aqBackend->hasSession())
|
||||
return std::nullopt;
|
||||
|
|
@ -3096,3 +3115,7 @@ std::optional<unsigned int> CCompositor::getVTNr() {
|
|||
|
||||
return ttynum;
|
||||
}
|
||||
|
||||
bool CCompositor::isVRRActiveOnAnyMonitor() const {
|
||||
return std::ranges::any_of(m_monitors, [](const PHLMONITOR& m) { return m->m_vrrActive; });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,12 @@
|
|||
|
||||
#include <ranges>
|
||||
|
||||
#include "helpers/math/Direction.hpp"
|
||||
#include "managers/XWaylandManager.hpp"
|
||||
#include "managers/KeybindManager.hpp"
|
||||
#include "managers/SessionLockManager.hpp"
|
||||
#include "desktop/view/Window.hpp"
|
||||
#include "protocols/types/ColorManagement.hpp"
|
||||
#include "helpers/cm/ColorManagement.hpp"
|
||||
|
||||
#include <aquamarine/backend/Backend.hpp>
|
||||
#include <aquamarine/output/Output.hpp>
|
||||
|
|
@ -113,16 +114,16 @@ class CCompositor {
|
|||
bool isWindowActive(PHLWINDOW);
|
||||
void changeWindowZOrder(PHLWINDOW, bool);
|
||||
void cleanupFadingOut(const MONITORID& monid);
|
||||
PHLWINDOW getWindowInDirection(PHLWINDOW, char);
|
||||
PHLWINDOW getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, char dir, PHLWINDOW ignoreWindow = nullptr, bool useVectorAngles = false);
|
||||
PHLWINDOW getWindowInDirection(PHLWINDOW, Math::eDirection);
|
||||
PHLWINDOW getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, Math::eDirection dir, PHLWINDOW ignoreWindow = nullptr, bool useVectorAngles = false);
|
||||
PHLWINDOW getWindowCycle(PHLWINDOW cur, bool focusableOnly = false, std::optional<bool> floating = std::nullopt, bool visible = false, bool prev = false);
|
||||
PHLWINDOW getWindowCycleHist(PHLWINDOWREF cur, bool focusableOnly = false, std::optional<bool> floating = std::nullopt, bool visible = false, bool next = false);
|
||||
WORKSPACEID getNextAvailableNamedWorkspace();
|
||||
bool isPointOnAnyMonitor(const Vector2D&);
|
||||
bool isPointOnReservedArea(const Vector2D& point, const PHLMONITOR monitor = nullptr);
|
||||
CBox calculateX11WorkArea();
|
||||
PHLMONITOR getMonitorInDirection(const char&);
|
||||
PHLMONITOR getMonitorInDirection(PHLMONITOR, const char&);
|
||||
std::optional<CBox> calculateX11WorkArea();
|
||||
PHLMONITOR getMonitorInDirection(Math::eDirection);
|
||||
PHLMONITOR getMonitorInDirection(PHLMONITOR, Math::eDirection);
|
||||
void updateAllWindowsAnimatedDecorationValues();
|
||||
MONITORID getNextAvailableMonitorID(std::string const& name);
|
||||
void moveWorkspaceToMonitor(PHLWORKSPACE, PHLMONITOR, bool noWarpCursor = false);
|
||||
|
|
@ -160,7 +161,9 @@ class CCompositor {
|
|||
void updateSuspendedStates();
|
||||
void onNewMonitor(SP<Aquamarine::IOutput> output);
|
||||
void ensurePersistentWorkspacesPresent(const std::vector<SWorkspaceRule>& rules, PHLWORKSPACE pWorkspace = nullptr);
|
||||
void ensureWorkspacesOnAssignedMonitors();
|
||||
std::optional<unsigned int> getVTNr();
|
||||
bool isVRRActiveOnAnyMonitor() const;
|
||||
|
||||
NColorManagement::PImageDescription getPreferredImageDescription();
|
||||
NColorManagement::PImageDescription getHDRImageDescription();
|
||||
|
|
|
|||
|
|
@ -38,10 +38,6 @@ enum eInputType : uint8_t {
|
|||
INPUT_TYPE_MOTION
|
||||
};
|
||||
|
||||
struct SCallbackInfo {
|
||||
bool cancelled = false; /* on cancellable events, will cancel the event. */
|
||||
};
|
||||
|
||||
enum eHyprCtlOutputFormat : uint8_t {
|
||||
FORMAT_NORMAL = 0,
|
||||
FORMAT_JSON
|
||||
|
|
@ -62,5 +58,3 @@ struct SDispatchResult {
|
|||
using WINDOWID = int64_t;
|
||||
using MONITORID = int64_t;
|
||||
using WORKSPACEID = int64_t;
|
||||
|
||||
using HOOK_CALLBACK_FN = std::function<void(void*, SCallbackInfo&, std::any)>;
|
||||
|
|
|
|||
|
|
@ -1115,6 +1115,12 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
|
|||
.type = CONFIG_OPTION_BOOL,
|
||||
.data = SConfigOptionDescription::SRangeData{0, -20, 20},
|
||||
},
|
||||
SConfigOptionDescription{
|
||||
.value = "group:groupbar:text_padding",
|
||||
.description = "set horizontal padding for a text",
|
||||
.type = CONFIG_OPTION_BOOL,
|
||||
.data = SConfigOptionDescription::SRangeData{0, 0, 22},
|
||||
},
|
||||
SConfigOptionDescription{
|
||||
.value = "group:groupbar:blur",
|
||||
.description = "enable background blur for groupbars",
|
||||
|
|
@ -1562,10 +1568,28 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
|
|||
},
|
||||
SConfigOptionDescription{
|
||||
.value = "render:cm_sdr_eotf",
|
||||
.description = "Default transfer function for displaying SDR apps. 0 - Use default value (Gamma 2.2), 1 - Treat unspecified as Gamma 2.2, 2 - Treat "
|
||||
"unspecified and sRGB as Gamma 2.2, 3 - Treat unspecified as sRGB",
|
||||
.type = CONFIG_OPTION_CHOICE,
|
||||
.data = SConfigOptionDescription::SChoiceData{0, "default,gamma22,gamma22force,srgb"},
|
||||
.description = "Default transfer function for displaying SDR apps. default - Use default value (Gamma 2.2), gamma22 - Treat unspecified as Gamma 2.2, gamma22force - Treat "
|
||||
"unspecified and sRGB as Gamma 2.2, srgb - Treat unspecified as sRGB",
|
||||
.type = CONFIG_OPTION_STRING_SHORT,
|
||||
.data = SConfigOptionDescription::SStringData{"default"},
|
||||
},
|
||||
SConfigOptionDescription{
|
||||
.value = "render:commit_timing_enabled",
|
||||
.description = "Enable commit timing proto. Requires restart",
|
||||
.type = CONFIG_OPTION_BOOL,
|
||||
.data = SConfigOptionDescription::SBoolData{true},
|
||||
},
|
||||
SConfigOptionDescription{
|
||||
.value = "render:icc_vcgt_enabled",
|
||||
.description = "Enable sending VCGT ramps to KMS with ICC profiles",
|
||||
.type = CONFIG_OPTION_BOOL,
|
||||
.data = SConfigOptionDescription::SBoolData{true},
|
||||
},
|
||||
{
|
||||
.value = "render:use_shader_blur_blend",
|
||||
.description = "Use experimental blurred bg blending",
|
||||
.type = CONFIG_OPTION_BOOL,
|
||||
.data = SConfigOptionDescription::SBoolData{false},
|
||||
},
|
||||
|
||||
/*
|
||||
|
|
@ -1690,9 +1714,9 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
|
|||
},
|
||||
SConfigOptionDescription{
|
||||
.value = "cursor:use_cpu_buffer",
|
||||
.description = "Makes HW cursors use a CPU buffer. Required on Nvidia to have HW cursors. Experimental",
|
||||
.type = CONFIG_OPTION_BOOL,
|
||||
.data = SConfigOptionDescription::SBoolData{false},
|
||||
.description = "Makes HW cursors use a CPU buffer. Required on Nvidia to have HW cursors. 0 - off, 1 - on, 2 - auto (nvidia only)",
|
||||
.type = CONFIG_OPTION_INT,
|
||||
.data = SConfigOptionDescription::SRangeData{.value = 2, .min = 0, .max = 2},
|
||||
},
|
||||
SConfigOptionDescription{
|
||||
.value = "cursor:sync_gsettings_theme",
|
||||
|
|
@ -1746,6 +1770,12 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
|
|||
.type = CONFIG_OPTION_BOOL,
|
||||
.data = SConfigOptionDescription::SBoolData{false},
|
||||
},
|
||||
SConfigOptionDescription{
|
||||
.value = "debug:gl_debugging",
|
||||
.description = "enable OpenGL debugging and error checking, they hurt performance.",
|
||||
.type = CONFIG_OPTION_BOOL,
|
||||
.data = SConfigOptionDescription::SBoolData{false},
|
||||
},
|
||||
SConfigOptionDescription{
|
||||
.value = "debug:disable_logs",
|
||||
.description = "disable logging to a file",
|
||||
|
|
@ -1824,6 +1854,46 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
|
|||
.type = CONFIG_OPTION_BOOL,
|
||||
.data = SConfigOptionDescription::SBoolData{false},
|
||||
},
|
||||
SConfigOptionDescription{
|
||||
.value = "debug:ds_handle_same_buffer",
|
||||
.description = "Special case for DS with unmodified buffer",
|
||||
.type = CONFIG_OPTION_BOOL,
|
||||
.data = SConfigOptionDescription::SBoolData{true},
|
||||
},
|
||||
SConfigOptionDescription{
|
||||
.value = "debug:ds_handle_same_buffer_fifo",
|
||||
.description = "Special case for DS with unmodified buffer unlocks fifo",
|
||||
.type = CONFIG_OPTION_BOOL,
|
||||
.data = SConfigOptionDescription::SBoolData{true},
|
||||
},
|
||||
SConfigOptionDescription{
|
||||
.value = "debug:fifo_pending_workaround",
|
||||
.description = "Fifo workaround for empty pending list",
|
||||
.type = CONFIG_OPTION_BOOL,
|
||||
.data = SConfigOptionDescription::SBoolData{false},
|
||||
},
|
||||
SConfigOptionDescription{
|
||||
.value = "debug:render_solitary_wo_damage",
|
||||
.description = "Render solitary window with empty damage",
|
||||
.type = CONFIG_OPTION_BOOL,
|
||||
.data = SConfigOptionDescription::SBoolData{false},
|
||||
},
|
||||
|
||||
/*
|
||||
* layout:
|
||||
*/
|
||||
SConfigOptionDescription{
|
||||
.value = "layout:single_window_aspect_ratio",
|
||||
.description = "If specified, whenever only a single window is open, it will be coerced into the specified aspect ratio. Ignored if the y-value is zero.",
|
||||
.type = CONFIG_OPTION_VECTOR,
|
||||
.data = SConfigOptionDescription::SVectorData{{0, 0}, {0, 0}, {1000., 1000.}},
|
||||
},
|
||||
SConfigOptionDescription{
|
||||
.value = "layout:single_window_aspect_ratio_tolerance",
|
||||
.description = "Minimum distance for single_window_aspect_ratio to take effect, in fractions of the monitor's size.",
|
||||
.type = CONFIG_OPTION_FLOAT,
|
||||
.data = SConfigOptionDescription::SFloatData{0.1f, 0.f, 1.f},
|
||||
},
|
||||
|
||||
/*
|
||||
* dwindle:
|
||||
|
|
@ -1904,18 +1974,6 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
|
|||
.type = CONFIG_OPTION_BOOL,
|
||||
.data = SConfigOptionDescription::SBoolData{false},
|
||||
},
|
||||
SConfigOptionDescription{
|
||||
.value = "dwindle:single_window_aspect_ratio",
|
||||
.description = "If specified, whenever only a single window is open, it will be coerced into the specified aspect ratio. Ignored if the y-value is zero.",
|
||||
.type = CONFIG_OPTION_VECTOR,
|
||||
.data = SConfigOptionDescription::SVectorData{{0, 0}, {0, 0}, {1000., 1000.}},
|
||||
},
|
||||
SConfigOptionDescription{
|
||||
.value = "dwindle:single_window_aspect_ratio_tolerance",
|
||||
.description = "Minimum distance for single_window_aspect_ratio to take effect, in fractions of the monitor's size.",
|
||||
.type = CONFIG_OPTION_FLOAT,
|
||||
.data = SConfigOptionDescription::SFloatData{0.1f, 0.f, 1.f},
|
||||
},
|
||||
|
||||
/*
|
||||
* master:
|
||||
|
|
@ -2001,6 +2059,65 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
|
|||
.data = SConfigOptionDescription::SBoolData{false},
|
||||
},
|
||||
|
||||
/*
|
||||
* scrolling:
|
||||
*/
|
||||
|
||||
SConfigOptionDescription{
|
||||
.value = "scrolling:fullscreen_on_one_column",
|
||||
.description = "when enabled, a single column on a workspace will always span the entire screen.",
|
||||
.type = CONFIG_OPTION_BOOL,
|
||||
.data = SConfigOptionDescription::SBoolData{true},
|
||||
},
|
||||
SConfigOptionDescription{
|
||||
.value = "scrolling:column_width",
|
||||
.description = "the default width of a column, [0.1 - 1.0].",
|
||||
.type = CONFIG_OPTION_FLOAT,
|
||||
.data = SConfigOptionDescription::SFloatData{.value = 0.5, .min = 0.1, .max = 1.0},
|
||||
},
|
||||
SConfigOptionDescription{
|
||||
.value = "scrolling:focus_fit_method",
|
||||
.description = "When a column is focused, what method should be used to bring it into view",
|
||||
.type = CONFIG_OPTION_CHOICE,
|
||||
.data = SConfigOptionDescription::SChoiceData{.firstIndex = 0, .choices = "center,fit"},
|
||||
},
|
||||
SConfigOptionDescription{
|
||||
.value = "scrolling:follow_focus",
|
||||
.description = "when a window is focused, should the layout move to bring it into view automatically",
|
||||
.type = CONFIG_OPTION_BOOL,
|
||||
.data = SConfigOptionDescription::SBoolData{.value = true},
|
||||
},
|
||||
SConfigOptionDescription{
|
||||
.value = "scrolling:follow_min_visible",
|
||||
.description = "when a window is focused, require that at least a given fraction of it is visible for focus to follow",
|
||||
.type = CONFIG_OPTION_FLOAT,
|
||||
.data = SConfigOptionDescription::SFloatData{.value = 0.4, .min = 0.0, .max = 1.0},
|
||||
},
|
||||
SConfigOptionDescription{
|
||||
.value = "scrolling:explicit_column_widths",
|
||||
.description = "A comma-separated list of preconfigured widths for colresize +conf/-conf",
|
||||
.type = CONFIG_OPTION_STRING_SHORT,
|
||||
.data = SConfigOptionDescription::SStringData{"0.333, 0.5, 0.667, 1.0"},
|
||||
},
|
||||
SConfigOptionDescription{
|
||||
.value = "scrolling:direction",
|
||||
.description = "Direction in which new windows appear and the layout scrolls",
|
||||
.type = CONFIG_OPTION_CHOICE,
|
||||
.data = SConfigOptionDescription::SChoiceData{.firstIndex = 0, .choices = "right,left,down,up"},
|
||||
},
|
||||
SConfigOptionDescription{
|
||||
.value = "scrolling:wrap_focus",
|
||||
.description = "Determines if column focus wraps around when going before the first column or past the last column",
|
||||
.type = CONFIG_OPTION_BOOL,
|
||||
.data = SConfigOptionDescription::SBoolData{.value = true},
|
||||
},
|
||||
SConfigOptionDescription{
|
||||
.value = "scrolling:wrap_swapcol",
|
||||
.description = "Determines if column movement wraps around when moving to before the first column or past the last column",
|
||||
.type = CONFIG_OPTION_BOOL,
|
||||
.data = SConfigOptionDescription::SBoolData{.value = true},
|
||||
},
|
||||
|
||||
/*
|
||||
* Quirks
|
||||
*/
|
||||
|
|
@ -2011,5 +2128,11 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
|
|||
.type = CONFIG_OPTION_INT,
|
||||
.data = SConfigOptionDescription::SRangeData{.value = 0, .min = 0, .max = 2},
|
||||
},
|
||||
SConfigOptionDescription{
|
||||
.value = "quirks:skip_non_kms_dmabuf_formats",
|
||||
.description = "Do not report dmabuf formats which cannot be imported into KMS",
|
||||
.type = CONFIG_OPTION_BOOL,
|
||||
.data = SConfigOptionDescription::SBoolData{true},
|
||||
},
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -18,13 +18,14 @@
|
|||
#include "../desktop/rule/layerRule/LayerRule.hpp"
|
||||
#include "../debug/HyprCtl.hpp"
|
||||
#include "../desktop/state/FocusState.hpp"
|
||||
#include "../layout/space/Space.hpp"
|
||||
#include "../layout/supplementary/WorkspaceAlgoMatcher.hpp"
|
||||
#include "defaultConfig.hpp"
|
||||
|
||||
#include "../render/Renderer.hpp"
|
||||
#include "../hyprerror/HyprError.hpp"
|
||||
#include "../managers/input/InputManager.hpp"
|
||||
#include "../managers/eventLoop/EventLoopManager.hpp"
|
||||
#include "../managers/LayoutManager.hpp"
|
||||
#include "../managers/EventManager.hpp"
|
||||
#include "../managers/permissions/DynamicPermissionManager.hpp"
|
||||
#include "../debug/HyprNotificationOverlay.hpp"
|
||||
|
|
@ -39,8 +40,10 @@
|
|||
#include "../managers/input/trackpad/gestures/CloseGesture.hpp"
|
||||
#include "../managers/input/trackpad/gestures/FloatGesture.hpp"
|
||||
#include "../managers/input/trackpad/gestures/FullscreenGesture.hpp"
|
||||
#include "../managers/input/trackpad/gestures/CursorZoomGesture.hpp"
|
||||
|
||||
#include "../event/EventBus.hpp"
|
||||
|
||||
#include "../managers/HookSystemManager.hpp"
|
||||
#include "../protocols/types/ContentType.hpp"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
|
@ -399,6 +402,12 @@ static Hyprlang::CParseResult handleWindowrule(const char* c, const char* v) {
|
|||
return result;
|
||||
}
|
||||
|
||||
static Hyprlang::CParseResult handleWindowrulev2(const char* c, const char* v) {
|
||||
Hyprlang::CParseResult res;
|
||||
res.setError("windowrulev2 is deprecated. Correct syntax can be found on the wiki.");
|
||||
return res;
|
||||
}
|
||||
|
||||
static Hyprlang::CParseResult handleLayerrule(const char* c, const char* v) {
|
||||
const std::string VALUE = v;
|
||||
const std::string COMMAND = c;
|
||||
|
|
@ -411,6 +420,12 @@ static Hyprlang::CParseResult handleLayerrule(const char* c, const char* v) {
|
|||
return result;
|
||||
}
|
||||
|
||||
static Hyprlang::CParseResult handleLayerrulev2(const char* c, const char* v) {
|
||||
Hyprlang::CParseResult res;
|
||||
res.setError("layerrulev2 doesn't exist. Correct syntax can be found on the wiki.");
|
||||
return res;
|
||||
}
|
||||
|
||||
void CConfigManager::registerConfigVar(const char* name, const Hyprlang::INT& val) {
|
||||
m_configValueNumber++;
|
||||
m_config->addConfigValue(name, val);
|
||||
|
|
@ -541,12 +556,14 @@ CConfigManager::CConfigManager() {
|
|||
registerConfigVar("group:groupbar:gaps_in", Hyprlang::INT{2});
|
||||
registerConfigVar("group:groupbar:keep_upper_gap", Hyprlang::INT{1});
|
||||
registerConfigVar("group:groupbar:text_offset", Hyprlang::INT{0});
|
||||
registerConfigVar("group:groupbar:text_padding", Hyprlang::INT{0});
|
||||
registerConfigVar("group:groupbar:blur", Hyprlang::INT{0});
|
||||
|
||||
registerConfigVar("debug:log_damage", Hyprlang::INT{0});
|
||||
registerConfigVar("debug:overlay", Hyprlang::INT{0});
|
||||
registerConfigVar("debug:damage_blink", Hyprlang::INT{0});
|
||||
registerConfigVar("debug:pass", Hyprlang::INT{0});
|
||||
registerConfigVar("debug:gl_debugging", Hyprlang::INT{0});
|
||||
registerConfigVar("debug:disable_logs", Hyprlang::INT{1});
|
||||
registerConfigVar("debug:disable_time", Hyprlang::INT{1});
|
||||
registerConfigVar("debug:enable_stdout_logs", Hyprlang::INT{0});
|
||||
|
|
@ -558,6 +575,10 @@ CConfigManager::CConfigManager() {
|
|||
registerConfigVar("debug:disable_scale_checks", Hyprlang::INT{0});
|
||||
registerConfigVar("debug:colored_stdout_logs", Hyprlang::INT{1});
|
||||
registerConfigVar("debug:full_cm_proto", Hyprlang::INT{0});
|
||||
registerConfigVar("debug:ds_handle_same_buffer", Hyprlang::INT{1});
|
||||
registerConfigVar("debug:ds_handle_same_buffer_fifo", Hyprlang::INT{1});
|
||||
registerConfigVar("debug:fifo_pending_workaround", Hyprlang::INT{0});
|
||||
registerConfigVar("debug:render_solitary_wo_damage", Hyprlang::INT{0});
|
||||
|
||||
registerConfigVar("decoration:rounding", Hyprlang::INT{0});
|
||||
registerConfigVar("decoration:rounding_power", {2.F});
|
||||
|
|
@ -597,6 +618,9 @@ CConfigManager::CConfigManager() {
|
|||
registerConfigVar("decoration:screen_shader", {STRVAL_EMPTY});
|
||||
registerConfigVar("decoration:border_part_of_window", Hyprlang::INT{1});
|
||||
|
||||
registerConfigVar("layout:single_window_aspect_ratio", Hyprlang::VEC2{0, 0});
|
||||
registerConfigVar("layout:single_window_aspect_ratio_tolerance", {0.1f});
|
||||
|
||||
registerConfigVar("dwindle:pseudotile", Hyprlang::INT{0});
|
||||
registerConfigVar("dwindle:force_split", Hyprlang::INT{0});
|
||||
registerConfigVar("dwindle:permanent_direction_override", Hyprlang::INT{0});
|
||||
|
|
@ -609,8 +633,6 @@ CConfigManager::CConfigManager() {
|
|||
registerConfigVar("dwindle:smart_split", Hyprlang::INT{0});
|
||||
registerConfigVar("dwindle:smart_resizing", Hyprlang::INT{1});
|
||||
registerConfigVar("dwindle:precise_mouse_move", Hyprlang::INT{0});
|
||||
registerConfigVar("dwindle:single_window_aspect_ratio", Hyprlang::VEC2{0, 0});
|
||||
registerConfigVar("dwindle:single_window_aspect_ratio_tolerance", {0.1f});
|
||||
|
||||
registerConfigVar("master:special_scale_factor", {1.f});
|
||||
registerConfigVar("master:mfact", {0.55f});
|
||||
|
|
@ -626,6 +648,16 @@ CConfigManager::CConfigManager() {
|
|||
registerConfigVar("master:drop_at_cursor", Hyprlang::INT{1});
|
||||
registerConfigVar("master:always_keep_position", Hyprlang::INT{0});
|
||||
|
||||
registerConfigVar("scrolling:fullscreen_on_one_column", Hyprlang::INT{1});
|
||||
registerConfigVar("scrolling:column_width", Hyprlang::FLOAT{0.5F});
|
||||
registerConfigVar("scrolling:focus_fit_method", Hyprlang::INT{1});
|
||||
registerConfigVar("scrolling:follow_focus", Hyprlang::INT{1});
|
||||
registerConfigVar("scrolling:follow_min_visible", Hyprlang::FLOAT{0.4});
|
||||
registerConfigVar("scrolling:explicit_column_widths", Hyprlang::STRING{"0.333, 0.5, 0.667, 1.0"});
|
||||
registerConfigVar("scrolling:direction", Hyprlang::STRING{"right"});
|
||||
registerConfigVar("scrolling:wrap_focus", Hyprlang::INT{1});
|
||||
registerConfigVar("scrolling:wrap_swapcol", Hyprlang::INT{1});
|
||||
|
||||
registerConfigVar("animations:enabled", Hyprlang::INT{1});
|
||||
registerConfigVar("animations:workspace_wraparound", Hyprlang::INT{0});
|
||||
|
||||
|
|
@ -696,9 +728,9 @@ CConfigManager::CConfigManager() {
|
|||
registerConfigVar("binds:movefocus_cycles_fullscreen", Hyprlang::INT{0});
|
||||
registerConfigVar("binds:movefocus_cycles_groupfirst", Hyprlang::INT{0});
|
||||
registerConfigVar("binds:disable_keybind_grabbing", Hyprlang::INT{0});
|
||||
registerConfigVar("binds:window_direction_monitor_fallback", Hyprlang::INT{1});
|
||||
registerConfigVar("binds:allow_pin_fullscreen", Hyprlang::INT{0});
|
||||
registerConfigVar("binds:drag_threshold", Hyprlang::INT{0});
|
||||
registerConfigVar("binds:window_direction_monitor_fallback", Hyprlang::INT{1});
|
||||
|
||||
registerConfigVar("gestures:workspace_swipe_distance", Hyprlang::INT{300});
|
||||
registerConfigVar("gestures:workspace_swipe_invert", Hyprlang::INT{1});
|
||||
|
|
@ -765,13 +797,17 @@ CConfigManager::CConfigManager() {
|
|||
registerConfigVar("render:cm_auto_hdr", Hyprlang::INT{1});
|
||||
registerConfigVar("render:new_render_scheduling", Hyprlang::INT{0});
|
||||
registerConfigVar("render:non_shader_cm", Hyprlang::INT{3});
|
||||
registerConfigVar("render:cm_sdr_eotf", Hyprlang::INT{0});
|
||||
registerConfigVar("render:cm_sdr_eotf", {"default"});
|
||||
registerConfigVar("render:commit_timing_enabled", Hyprlang::INT{1});
|
||||
registerConfigVar("render:icc_vcgt_enabled", Hyprlang::INT{1});
|
||||
registerConfigVar("render:use_shader_blur_blend", Hyprlang::INT{0});
|
||||
|
||||
registerConfigVar("ecosystem:no_update_news", Hyprlang::INT{0});
|
||||
registerConfigVar("ecosystem:no_donation_nag", Hyprlang::INT{0});
|
||||
registerConfigVar("ecosystem:enforce_permissions", Hyprlang::INT{0});
|
||||
|
||||
registerConfigVar("quirks:prefer_hdr", Hyprlang::INT{0});
|
||||
registerConfigVar("quirks:skip_non_kms_dmabuf_formats", Hyprlang::INT{0});
|
||||
|
||||
// devices
|
||||
m_config->addSpecialCategory("device", {"name"});
|
||||
|
|
@ -827,7 +863,7 @@ CConfigManager::CConfigManager() {
|
|||
m_config->addSpecialConfigValue("monitorv2", "mirror", {STRVAL_EMPTY});
|
||||
m_config->addSpecialConfigValue("monitorv2", "bitdepth", {STRVAL_EMPTY}); // TODO use correct type
|
||||
m_config->addSpecialConfigValue("monitorv2", "cm", {"auto"});
|
||||
m_config->addSpecialConfigValue("monitorv2", "sdr_eotf", Hyprlang::INT{0});
|
||||
m_config->addSpecialConfigValue("monitorv2", "sdr_eotf", {"default"});
|
||||
m_config->addSpecialConfigValue("monitorv2", "sdrbrightness", Hyprlang::FLOAT{1.0});
|
||||
m_config->addSpecialConfigValue("monitorv2", "sdrsaturation", Hyprlang::FLOAT{1.0});
|
||||
m_config->addSpecialConfigValue("monitorv2", "vrr", Hyprlang::INT{0});
|
||||
|
|
@ -839,6 +875,7 @@ CConfigManager::CConfigManager() {
|
|||
m_config->addSpecialConfigValue("monitorv2", "min_luminance", Hyprlang::FLOAT{-1.0});
|
||||
m_config->addSpecialConfigValue("monitorv2", "max_luminance", Hyprlang::INT{-1});
|
||||
m_config->addSpecialConfigValue("monitorv2", "max_avg_luminance", Hyprlang::INT{-1});
|
||||
m_config->addSpecialConfigValue("monitorv2", "icc", Hyprlang::STRING{""});
|
||||
|
||||
// windowrule v3
|
||||
m_config->addSpecialCategory("windowrule", {.key = "name"});
|
||||
|
|
@ -868,9 +905,13 @@ CConfigManager::CConfigManager() {
|
|||
m_config->registerHandler(&::handleSubmap, "submap", {false});
|
||||
m_config->registerHandler(&::handlePlugin, "plugin", {false});
|
||||
m_config->registerHandler(&::handlePermission, "permission", {false});
|
||||
m_config->registerHandler(&::handleGesture, "gesture", {false});
|
||||
m_config->registerHandler(&::handleGesture, "gesture", {true});
|
||||
m_config->registerHandler(&::handleEnv, "env", {true});
|
||||
|
||||
// windowrulev2 and layerrulev2 errors
|
||||
m_config->registerHandler(&::handleWindowrulev2, "windowrulev2", {false});
|
||||
m_config->registerHandler(&::handleLayerrulev2, "layerrulev2", {false});
|
||||
|
||||
// pluginza
|
||||
m_config->addSpecialCategory("plugin", {nullptr, true});
|
||||
|
||||
|
|
@ -1031,7 +1072,7 @@ static void clearHlVersionVars() {
|
|||
}
|
||||
|
||||
void CConfigManager::reload() {
|
||||
EMIT_HOOK_EVENT("preConfigReload", nullptr);
|
||||
Event::bus()->m_events.config.preReload.emit();
|
||||
setDefaultAnimationVars();
|
||||
resetHLConfig();
|
||||
m_configCurrentPath = getMainConfigPath();
|
||||
|
|
@ -1173,8 +1214,18 @@ std::optional<std::string> CConfigManager::handleMonitorv2(const std::string& ou
|
|||
if (VAL && VAL->m_bSetByUser)
|
||||
parser.parseCM(std::any_cast<Hyprlang::STRING>(VAL->getValue()));
|
||||
VAL = m_config->getSpecialConfigValuePtr("monitorv2", "sdr_eotf", output.c_str());
|
||||
if (VAL && VAL->m_bSetByUser)
|
||||
parser.rule().sdrEotf = std::any_cast<Hyprlang::INT>(VAL->getValue());
|
||||
if (VAL && VAL->m_bSetByUser) {
|
||||
const std::string value = std::any_cast<Hyprlang::STRING>(VAL->getValue());
|
||||
// remap legacy
|
||||
if (value == "0")
|
||||
parser.rule().sdrEotf = NTransferFunction::TF_AUTO;
|
||||
else if (value == "1")
|
||||
parser.rule().sdrEotf = NTransferFunction::TF_SRGB;
|
||||
else if (value == "2")
|
||||
parser.rule().sdrEotf = NTransferFunction::TF_GAMMA22;
|
||||
else
|
||||
parser.rule().sdrEotf = NTransferFunction::fromString(value);
|
||||
}
|
||||
VAL = m_config->getSpecialConfigValuePtr("monitorv2", "sdrbrightness", output.c_str());
|
||||
if (VAL && VAL->m_bSetByUser)
|
||||
parser.rule().sdrBrightness = std::any_cast<Hyprlang::FLOAT>(VAL->getValue());
|
||||
|
|
@ -1211,6 +1262,10 @@ std::optional<std::string> CConfigManager::handleMonitorv2(const std::string& ou
|
|||
if (VAL && VAL->m_bSetByUser)
|
||||
parser.rule().maxAvgLuminance = std::any_cast<Hyprlang::INT>(VAL->getValue());
|
||||
|
||||
VAL = m_config->getSpecialConfigValuePtr("monitorv2", "icc", output.c_str());
|
||||
if (VAL && VAL->m_bSetByUser)
|
||||
parser.rule().iccFile = std::any_cast<Hyprlang::STRING>(VAL->getValue());
|
||||
|
||||
auto newrule = parser.rule();
|
||||
|
||||
std::erase_if(m_monitorRules, [&](const auto& other) { return other.name == newrule.name; });
|
||||
|
|
@ -1313,7 +1368,8 @@ void CConfigManager::postConfigReload(const Hyprlang::CParseResult& result) {
|
|||
static auto PZOOMFACTOR = CConfigValue<Hyprlang::FLOAT>("cursor:zoom_factor");
|
||||
for (auto const& m : g_pCompositor->m_monitors) {
|
||||
*(m->m_cursorZoom) = *PZOOMFACTOR;
|
||||
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->m_id);
|
||||
if (m->m_activeWorkspace)
|
||||
m->m_activeWorkspace->m_space->recalculate();
|
||||
}
|
||||
|
||||
// Update the keyboard layout to the cfg'd one if this is not the first launch
|
||||
|
|
@ -1381,9 +1437,6 @@ void CConfigManager::postConfigReload(const Hyprlang::CParseResult& result) {
|
|||
// Update window border colors
|
||||
g_pCompositor->updateAllWindowsAnimatedDecorationValues();
|
||||
|
||||
// update layout
|
||||
g_pLayoutManager->switchToLayout(std::any_cast<Hyprlang::STRING>(m_config->getConfigValue("general:layout")));
|
||||
|
||||
// manual crash
|
||||
if (std::any_cast<Hyprlang::INT>(m_config->getConfigValue("debug:manual_crash")) && !m_manualCrashInitiated) {
|
||||
m_manualCrashInitiated = true;
|
||||
|
|
@ -1422,7 +1475,10 @@ void CConfigManager::postConfigReload(const Hyprlang::CParseResult& result) {
|
|||
if (!m_isFirstLaunch)
|
||||
ensurePersistentWorkspacesPresent();
|
||||
|
||||
EMIT_HOOK_EVENT("configReloaded", nullptr);
|
||||
// update layouts
|
||||
Layout::Supplementary::algoMatcher()->updateWorkspaceLayouts();
|
||||
|
||||
Event::bus()->m_events.config.reloaded.emit();
|
||||
if (g_pEventManager)
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{"configreloaded", ""});
|
||||
}
|
||||
|
|
@ -1444,8 +1500,9 @@ std::string CConfigManager::parseKeyword(const std::string& COMMAND, const std::
|
|||
|
||||
// invalidate layouts if they changed
|
||||
if (COMMAND == "monitor" || COMMAND.contains("gaps_") || COMMAND.starts_with("dwindle:") || COMMAND.starts_with("master:")) {
|
||||
for (auto const& m : g_pCompositor->m_monitors)
|
||||
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->m_id);
|
||||
for (auto const& m : g_pCompositor->m_monitors) {
|
||||
g_layoutManager->recalculateMonitor(m);
|
||||
}
|
||||
}
|
||||
|
||||
// Update window border colors
|
||||
|
|
@ -1617,6 +1674,8 @@ SWorkspaceRule CConfigManager::mergeWorkspaceRules(const SWorkspaceRule& rule1,
|
|||
mergedRule.onCreatedEmptyRunCmd = rule2.onCreatedEmptyRunCmd;
|
||||
if (rule2.defaultName.has_value())
|
||||
mergedRule.defaultName = rule2.defaultName;
|
||||
if (rule2.layout.has_value())
|
||||
mergedRule.layout = rule2.layout;
|
||||
if (!rule2.layoutopts.empty()) {
|
||||
for (const auto& layoutopt : rule2.layoutopts) {
|
||||
mergedRule.layoutopts[layoutopt.first] = layoutopt.second;
|
||||
|
|
@ -1708,7 +1767,7 @@ void CConfigManager::performMonitorReload() {
|
|||
|
||||
m_wantsMonitorReload = false;
|
||||
|
||||
EMIT_HOOK_EVENT("monitorLayoutChanged", nullptr);
|
||||
Event::bus()->m_events.monitor.layoutChanged.emit();
|
||||
}
|
||||
|
||||
void* const* CConfigManager::getConfigValuePtr(const std::string& val) {
|
||||
|
|
@ -1766,24 +1825,41 @@ void CConfigManager::ensureVRR(PHLMONITOR pMonitor) {
|
|||
}
|
||||
m->m_vrrActive = false;
|
||||
return;
|
||||
} else if (USEVRR == 1) {
|
||||
if (!m->m_vrrActive) {
|
||||
m->m_output->state->resetExplicitFences();
|
||||
m->m_output->state->setAdaptiveSync(true);
|
||||
}
|
||||
|
||||
if (!m->m_state.test()) {
|
||||
Log::logger->log(Log::DEBUG, "Pending output {} does not accept VRR.", m->m_output->name);
|
||||
m->m_output->state->setAdaptiveSync(false);
|
||||
const auto PWORKSPACE = m->m_activeWorkspace;
|
||||
|
||||
if (USEVRR == 1) {
|
||||
bool wantVRR = true;
|
||||
if (PWORKSPACE && PWORKSPACE->m_hasFullscreenWindow && (PWORKSPACE->m_fullscreenMode & FSMODE_FULLSCREEN))
|
||||
wantVRR = !PWORKSPACE->getFullscreenWindow()->m_ruleApplicator->noVRR().valueOrDefault();
|
||||
|
||||
if (wantVRR) {
|
||||
if (!m->m_vrrActive) {
|
||||
m->m_output->state->resetExplicitFences();
|
||||
m->m_output->state->setAdaptiveSync(true);
|
||||
|
||||
if (!m->m_state.test()) {
|
||||
Log::logger->log(Log::DEBUG, "Pending output {} does not accept VRR.", m->m_output->name);
|
||||
m->m_output->state->setAdaptiveSync(false);
|
||||
}
|
||||
|
||||
if (!m->m_state.commit())
|
||||
Log::logger->log(Log::ERR, "Couldn't commit output {} in ensureVRR -> true", m->m_output->name);
|
||||
}
|
||||
m->m_vrrActive = true;
|
||||
} else {
|
||||
if (m->m_vrrActive) {
|
||||
m->m_output->state->resetExplicitFences();
|
||||
m->m_output->state->setAdaptiveSync(false);
|
||||
|
||||
if (!m->m_state.commit())
|
||||
Log::logger->log(Log::ERR, "Couldn't commit output {} in ensureVRR -> true", m->m_output->name);
|
||||
if (!m->m_state.commit())
|
||||
Log::logger->log(Log::ERR, "Couldn't commit output {} in ensureVRR -> false", m->m_output->name);
|
||||
}
|
||||
m->m_vrrActive = false;
|
||||
}
|
||||
m->m_vrrActive = true;
|
||||
return;
|
||||
} else if (USEVRR == 2 || USEVRR == 3) {
|
||||
const auto PWORKSPACE = m->m_activeWorkspace;
|
||||
|
||||
if (!PWORKSPACE)
|
||||
return; // ???
|
||||
|
||||
|
|
@ -2208,6 +2284,15 @@ bool CMonitorRuleParser::parseVRR(const std::string& value) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool CMonitorRuleParser::parseICC(const std::string& val) {
|
||||
if (val.empty()) {
|
||||
m_error += "invalid icc ";
|
||||
return false;
|
||||
}
|
||||
m_rule.iccFile = val;
|
||||
return true;
|
||||
}
|
||||
|
||||
void CMonitorRuleParser::setDisabled() {
|
||||
m_rule.disabled = true;
|
||||
}
|
||||
|
|
@ -2302,6 +2387,9 @@ std::optional<std::string> CConfigManager::handleMonitor(const std::string& comm
|
|||
} else if (ARGS[argno] == "vrr") {
|
||||
parser.parseVRR(std::string(ARGS[argno + 1]));
|
||||
argno++;
|
||||
} else if (ARGS[argno] == "icc") {
|
||||
parser.parseICC(std::string(ARGS[argno + 1]));
|
||||
argno++;
|
||||
} else if (ARGS[argno] == "workspace") {
|
||||
const auto& [id, name, isAutoID] = getWorkspaceIDNameFromString(std::string(ARGS[argno + 1]));
|
||||
|
||||
|
|
@ -2668,6 +2756,9 @@ std::optional<std::string> CConfigManager::handleWorkspaceRules(const std::strin
|
|||
opt = opt.substr(0, opt.find(':'));
|
||||
|
||||
wsRule.layoutopts[opt] = val;
|
||||
} else if ((delim = rule.find("layout:")) != std::string::npos) {
|
||||
std::string layout = rule.substr(delim + 7);
|
||||
wsRule.layout = std::move(layout);
|
||||
}
|
||||
|
||||
return {};
|
||||
|
|
@ -2804,6 +2895,8 @@ std::optional<std::string> CConfigManager::handlePermission(const std::string& c
|
|||
|
||||
if (data[1] == "screencopy")
|
||||
type = PERMISSION_TYPE_SCREENCOPY;
|
||||
else if (data[1] == "cursorpos")
|
||||
type = PERMISSION_TYPE_CURSOR_POS;
|
||||
else if (data[1] == "plugin")
|
||||
type = PERMISSION_TYPE_PLUGIN;
|
||||
else if (data[1] == "keyboard" || data[1] == "keeb")
|
||||
|
|
@ -2821,7 +2914,7 @@ std::optional<std::string> CConfigManager::handlePermission(const std::string& c
|
|||
if (mode == PERMISSION_RULE_ALLOW_MODE_UNKNOWN)
|
||||
return "unknown permission allow mode";
|
||||
|
||||
if (m_isFirstLaunch)
|
||||
if (m_isFirstLaunch && g_pDynamicPermissionManager)
|
||||
g_pDynamicPermissionManager->addConfigPermissionRule(std::string(data[0]), type, mode);
|
||||
|
||||
return {};
|
||||
|
|
@ -2845,9 +2938,17 @@ std::optional<std::string> CConfigManager::handleGesture(const std::string& comm
|
|||
if (direction == TRACKPAD_GESTURE_DIR_NONE)
|
||||
return std::format("Invalid direction: {}", data[1]);
|
||||
|
||||
int startDataIdx = 2;
|
||||
uint32_t modMask = 0;
|
||||
float deltaScale = 1.F;
|
||||
int startDataIdx = 2;
|
||||
uint32_t modMask = 0;
|
||||
float deltaScale = 1.F;
|
||||
bool disableInhibit = false;
|
||||
|
||||
for (const auto arg : command.substr(7)) {
|
||||
switch (arg) {
|
||||
case 'p': disableInhibit = true; break;
|
||||
default: return "gesture: invalid flag";
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
|
||||
|
|
@ -2870,23 +2971,29 @@ std::optional<std::string> CConfigManager::handleGesture(const std::string& comm
|
|||
|
||||
if (data[startDataIdx] == "dispatcher")
|
||||
result = g_pTrackpadGestures->addGesture(makeUnique<CDispatcherTrackpadGesture>(std::string{data[startDataIdx + 1]}, data.join(",", startDataIdx + 2)), fingerCount,
|
||||
direction, modMask, deltaScale);
|
||||
direction, modMask, deltaScale, disableInhibit);
|
||||
else if (data[startDataIdx] == "workspace")
|
||||
result = g_pTrackpadGestures->addGesture(makeUnique<CWorkspaceSwipeGesture>(), fingerCount, direction, modMask, deltaScale);
|
||||
result = g_pTrackpadGestures->addGesture(makeUnique<CWorkspaceSwipeGesture>(), fingerCount, direction, modMask, deltaScale, disableInhibit);
|
||||
else if (data[startDataIdx] == "resize")
|
||||
result = g_pTrackpadGestures->addGesture(makeUnique<CResizeTrackpadGesture>(), fingerCount, direction, modMask, deltaScale);
|
||||
result = g_pTrackpadGestures->addGesture(makeUnique<CResizeTrackpadGesture>(), fingerCount, direction, modMask, deltaScale, disableInhibit);
|
||||
else if (data[startDataIdx] == "move")
|
||||
result = g_pTrackpadGestures->addGesture(makeUnique<CMoveTrackpadGesture>(), fingerCount, direction, modMask, deltaScale);
|
||||
result = g_pTrackpadGestures->addGesture(makeUnique<CMoveTrackpadGesture>(), fingerCount, direction, modMask, deltaScale, disableInhibit);
|
||||
else if (data[startDataIdx] == "special")
|
||||
result = g_pTrackpadGestures->addGesture(makeUnique<CSpecialWorkspaceGesture>(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale);
|
||||
result =
|
||||
g_pTrackpadGestures->addGesture(makeUnique<CSpecialWorkspaceGesture>(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale, disableInhibit);
|
||||
else if (data[startDataIdx] == "close")
|
||||
result = g_pTrackpadGestures->addGesture(makeUnique<CCloseTrackpadGesture>(), fingerCount, direction, modMask, deltaScale);
|
||||
result = g_pTrackpadGestures->addGesture(makeUnique<CCloseTrackpadGesture>(), fingerCount, direction, modMask, deltaScale, disableInhibit);
|
||||
else if (data[startDataIdx] == "float")
|
||||
result = g_pTrackpadGestures->addGesture(makeUnique<CFloatTrackpadGesture>(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale);
|
||||
result =
|
||||
g_pTrackpadGestures->addGesture(makeUnique<CFloatTrackpadGesture>(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale, disableInhibit);
|
||||
else if (data[startDataIdx] == "fullscreen")
|
||||
result = g_pTrackpadGestures->addGesture(makeUnique<CFullscreenTrackpadGesture>(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale);
|
||||
else if (data[startDataIdx] == "unset")
|
||||
result = g_pTrackpadGestures->removeGesture(fingerCount, direction, modMask, deltaScale);
|
||||
result = g_pTrackpadGestures->addGesture(makeUnique<CFullscreenTrackpadGesture>(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale,
|
||||
disableInhibit);
|
||||
else if (data[startDataIdx] == "cursorZoom") {
|
||||
result = g_pTrackpadGestures->addGesture(makeUnique<CCursorZoomTrackpadGesture>(std::string{data[startDataIdx + 1]}, std::string{data[startDataIdx + 2]}), fingerCount,
|
||||
direction, modMask, deltaScale, disableInhibit);
|
||||
} else if (data[startDataIdx] == "unset")
|
||||
result = g_pTrackpadGestures->removeGesture(fingerCount, direction, modMask, deltaScale, disableInhibit);
|
||||
else
|
||||
return std::format("Invalid gesture: {}", data[startDataIdx]);
|
||||
|
||||
|
|
@ -2989,7 +3096,7 @@ bool CConfigManager::shouldUseSoftwareCursors(PHLMONITOR pMonitor) {
|
|||
switch (*PNOHW) {
|
||||
case 0: return false;
|
||||
case 1: return true;
|
||||
case 2: return g_pHyprRenderer->isNvidia() && g_pHyprRenderer->isMgpu();
|
||||
case 2: return g_pHyprRenderer->isNvidia() && (g_pHyprRenderer->isMgpu() || g_pCompositor->isVRRActiveOnAnyMonitor());
|
||||
default: break;
|
||||
}
|
||||
|
||||
|
|
@ -3016,20 +3123,20 @@ std::string SConfigOptionDescription::jsonify() const {
|
|||
else if (typeid(Hyprlang::FLOAT) == std::type_index(CONFIGVALUE.type()))
|
||||
currentValue = std::format("{:.2f}", std::any_cast<Hyprlang::FLOAT>(CONFIGVALUE));
|
||||
else if (typeid(Hyprlang::STRING) == std::type_index(CONFIGVALUE.type()))
|
||||
currentValue = std::any_cast<Hyprlang::STRING>(CONFIGVALUE);
|
||||
currentValue = std::format("\"{}\"", std::any_cast<Hyprlang::STRING>(CONFIGVALUE));
|
||||
else if (typeid(Hyprlang::VEC2) == std::type_index(CONFIGVALUE.type())) {
|
||||
const auto V = std::any_cast<Hyprlang::VEC2>(CONFIGVALUE);
|
||||
currentValue = std::format("{}, {}", V.x, V.y);
|
||||
currentValue = std::format("\"{}, {}\"", V.x, V.y);
|
||||
} else if (typeid(void*) == std::type_index(CONFIGVALUE.type())) {
|
||||
const auto DATA = sc<ICustomConfigValueData*>(std::any_cast<void*>(CONFIGVALUE));
|
||||
currentValue = DATA->toString();
|
||||
currentValue = std::format("\"{}\"", DATA->toString());
|
||||
}
|
||||
|
||||
try {
|
||||
using T = std::decay_t<decltype(val)>;
|
||||
if constexpr (std::is_same_v<T, SStringData>) {
|
||||
return std::format(R"#( "value": "{}",
|
||||
"current": "{}",
|
||||
"current": {},
|
||||
"explicit": {})#",
|
||||
val.value, currentValue, EXPLICIT);
|
||||
} else if constexpr (std::is_same_v<T, SRangeData>) {
|
||||
|
|
@ -3048,7 +3155,7 @@ std::string SConfigOptionDescription::jsonify() const {
|
|||
val.value, val.min, val.max, currentValue, EXPLICIT);
|
||||
} else if constexpr (std::is_same_v<T, SColorData>) {
|
||||
return std::format(R"#( "value": "{}",
|
||||
"current": "{}",
|
||||
"current": {},
|
||||
"explicit": {})#",
|
||||
val.color.getAsHex(), currentValue, EXPLICIT);
|
||||
} else if constexpr (std::is_same_v<T, SBoolData>) {
|
||||
|
|
@ -3069,12 +3176,12 @@ std::string SConfigOptionDescription::jsonify() const {
|
|||
"min_y": {},
|
||||
"max_x": {},
|
||||
"max_y": {},
|
||||
"current": "{}",
|
||||
"current": {},
|
||||
"explicit": {})#",
|
||||
val.vec.x, val.vec.y, val.min.x, val.min.y, val.max.x, val.max.y, currentValue, EXPLICIT);
|
||||
} else if constexpr (std::is_same_v<T, SGradientData>) {
|
||||
return std::format(R"#( "value": "{}",
|
||||
"current": "{}",
|
||||
"current": {},
|
||||
"explicit": {})#",
|
||||
val.gradient, currentValue, EXPLICIT);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ struct SWorkspaceRule {
|
|||
std::optional<bool> noShadow;
|
||||
std::optional<std::string> onCreatedEmptyRunCmd;
|
||||
std::optional<std::string> defaultName;
|
||||
std::optional<std::string> layout;
|
||||
std::map<std::string, std::string> layoutopts;
|
||||
};
|
||||
|
||||
|
|
@ -176,6 +177,7 @@ class CMonitorRuleParser {
|
|||
bool parseSDRBrightness(const std::string& value);
|
||||
bool parseSDRSaturation(const std::string& value);
|
||||
bool parseVRR(const std::string& value);
|
||||
bool parseICC(const std::string& value);
|
||||
|
||||
void setDisabled();
|
||||
void setMirror(const std::string& value);
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ using namespace Hyprutils::OS;
|
|||
#include "config/ConfigManager.hpp"
|
||||
#include "helpers/MiscFunctions.hpp"
|
||||
#include "../desktop/view/LayerSurface.hpp"
|
||||
#include "../desktop/view/Group.hpp"
|
||||
#include "../desktop/rule/Engine.hpp"
|
||||
#include "../desktop/history/WindowHistoryTracker.hpp"
|
||||
#include "../desktop/state/FocusState.hpp"
|
||||
|
|
@ -52,12 +53,15 @@ using namespace Hyprutils::OS;
|
|||
#include "../Compositor.hpp"
|
||||
#include "../managers/input/InputManager.hpp"
|
||||
#include "../managers/XWaylandManager.hpp"
|
||||
#include "../managers/LayoutManager.hpp"
|
||||
#include "../plugins/PluginSystem.hpp"
|
||||
#include "../managers/animation/AnimationManager.hpp"
|
||||
#include "../debug/HyprNotificationOverlay.hpp"
|
||||
#include "../render/Renderer.hpp"
|
||||
#include "../render/OpenGL.hpp"
|
||||
#include "../layout/space/Space.hpp"
|
||||
#include "../layout/algorithm/Algorithm.hpp"
|
||||
#include "../layout/algorithm/TiledAlgorithm.hpp"
|
||||
#include "../layout/supplementary/WorkspaceAlgoMatcher.hpp"
|
||||
|
||||
#if defined(__DragonFly__) || defined(__FreeBSD__)
|
||||
#include <sys/ucred.h>
|
||||
|
|
@ -144,13 +148,13 @@ std::string CHyprCtl::getSolitaryBlockedReason(Hyprutils::Memory::CSharedPointer
|
|||
}
|
||||
|
||||
const std::array<const char*, CMonitor::DS_CHECKS_COUNT> DS_REASONS_JSON = {
|
||||
"\"UNKNOWN\"", "\"USER\"", "\"WINDOWED\"", "\"CONTENT\"", "\"MIRROR\"", "\"RECORD\"", "\"SW\"",
|
||||
"\"CANDIDATE\"", "\"SURFACE\"", "\"TRANSFORM\"", "\"DMA\"", "\"TEARING\"", "\"FAILED\"", "\"CM\"",
|
||||
"\"UNKNOWN\"", "\"USER\"", "\"WINDOWED\"", "\"CONTENT\"", "\"MIRROR\"", "\"RECORD\"", "\"SW\"",
|
||||
"\"CANDIDATE\"", "\"SURFACE\"", "\"TRANSFORM\"", "\"DMA\"", "\"FAILED\"", "\"CM\"",
|
||||
};
|
||||
|
||||
const std::array<const char*, CMonitor::DS_CHECKS_COUNT> DS_REASONS_TEXT = {
|
||||
"unknown reason", "user settings", "windowed mode", "content type", "monitor mirrors", "screen record/screenshot", "software renders/cursors",
|
||||
"missing candidate", "invalid surface", "surface transformations", "invalid buffer", "tearing", "activation failed", "color management",
|
||||
"unknown reason", "user settings", "windowed mode", "content type", "monitor mirrors", "screen record/screenshot", "software renders/cursors",
|
||||
"missing candidate", "invalid surface", "surface transformations", "invalid buffer", "activation failed", "color management",
|
||||
};
|
||||
|
||||
std::string CHyprCtl::getDSBlockedReason(Hyprutils::Memory::CSharedPointer<CMonitor> m, eHyprCtlOutputFormat format) {
|
||||
|
|
@ -173,14 +177,13 @@ std::string CHyprCtl::getDSBlockedReason(Hyprutils::Memory::CSharedPointer<CMoni
|
|||
}
|
||||
|
||||
const std::array<const char*, CMonitor::TC_CHECKS_COUNT> TEARING_REASONS_JSON = {
|
||||
"\"UNKNOWN\"", "\"NOT_TORN\"", "\"USER\"", "\"ZOOM\"", "\"SUPPORT\"", "\"CANDIDATE\"", "\"WINDOW\"",
|
||||
"\"UNKNOWN\"", "\"NOT_TORN\"", "\"USER\"", "\"ZOOM\"", "\"SUPPORT\"", "\"CANDIDATE\"", "\"WINDOW\"", "\"HW_CURSOR\"",
|
||||
};
|
||||
|
||||
const std::array<const char*, CMonitor::TC_CHECKS_COUNT> TEARING_REASONS_TEXT = {
|
||||
"unknown reason", "next frame is not torn", "user settings", "zoom", "not supported by monitor", "missing candidate", "window settings",
|
||||
};
|
||||
const std::array<const char*, CMonitor::TC_CHECKS_COUNT> TEARING_REASONS_TEXT = {"unknown reason", "next frame is not torn", "user settings", "zoom",
|
||||
"not supported by monitor", "missing candidate", "window settings", "hw cursor"};
|
||||
|
||||
std::string CHyprCtl::getTearingBlockedReason(Hyprutils::Memory::CSharedPointer<CMonitor> m, eHyprCtlOutputFormat format) {
|
||||
std::string CHyprCtl::getTearingBlockedReason(Hyprutils::Memory::CSharedPointer<CMonitor> m, eHyprCtlOutputFormat format) {
|
||||
const auto reasons = m->isTearingBlocked(true);
|
||||
if (!reasons || (reasons == CMonitor::TC_NOT_TORN && m->m_tearingState.activelyTearing))
|
||||
return "null";
|
||||
|
|
@ -331,23 +334,19 @@ static std::string getTagsData(PHLWINDOW w, eHyprCtlOutputFormat format) {
|
|||
|
||||
static std::string getGroupedData(PHLWINDOW w, eHyprCtlOutputFormat format) {
|
||||
const bool isJson = format == eHyprCtlOutputFormat::FORMAT_JSON;
|
||||
if (w->m_groupData.pNextWindow.expired())
|
||||
if (!w->m_group)
|
||||
return isJson ? "" : "0";
|
||||
|
||||
std::ostringstream result;
|
||||
|
||||
PHLWINDOW head = w->getGroupHead();
|
||||
PHLWINDOW curr = head;
|
||||
while (true) {
|
||||
for (const auto& curr : w->m_group->windows()) {
|
||||
if (isJson)
|
||||
result << std::format("\"0x{:x}\"", rc<uintptr_t>(curr.get()));
|
||||
else
|
||||
result << std::format("{:x}", rc<uintptr_t>(curr.get()));
|
||||
curr = curr->m_groupData.pNextWindow.lock();
|
||||
// We've wrapped around to the start, break out without trailing comma
|
||||
if (curr == head)
|
||||
break;
|
||||
result << (isJson ? ", " : ",");
|
||||
|
||||
if (curr != w->m_group->windows().back())
|
||||
result << (isJson ? ", " : ",");
|
||||
}
|
||||
|
||||
return result.str();
|
||||
|
|
@ -376,7 +375,6 @@ std::string CHyprCtl::getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format) {
|
|||
"name": "{}"
|
||||
}},
|
||||
"floating": {},
|
||||
"pseudo": {},
|
||||
"monitor": {},
|
||||
"class": "{}",
|
||||
"title": "{}",
|
||||
|
|
@ -387,6 +385,7 @@ std::string CHyprCtl::getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format) {
|
|||
"pinned": {},
|
||||
"fullscreen": {},
|
||||
"fullscreenClient": {},
|
||||
"overFullscreen": {},
|
||||
"grouped": [{}],
|
||||
"tags": [{}],
|
||||
"swallowing": "0x{:x}",
|
||||
|
|
@ -394,29 +393,31 @@ std::string CHyprCtl::getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format) {
|
|||
"inhibitingIdle": {},
|
||||
"xdgTag": "{}",
|
||||
"xdgDescription": "{}",
|
||||
"contentType": "{}"
|
||||
"contentType": "{}",
|
||||
"stableId": "{:x}"
|
||||
}},)#",
|
||||
rc<uintptr_t>(w.get()), (w->m_isMapped ? "true" : "false"), (w->isHidden() ? "true" : "false"), sc<int>(w->m_realPosition->goal().x),
|
||||
sc<int>(w->m_realPosition->goal().y), sc<int>(w->m_realSize->goal().x), sc<int>(w->m_realSize->goal().y), w->m_workspace ? w->workspaceID() : WORKSPACE_INVALID,
|
||||
escapeJSONStrings(!w->m_workspace ? "" : w->m_workspace->m_name), (sc<int>(w->m_isFloating) == 1 ? "true" : "false"), (w->m_isPseudotiled ? "true" : "false"),
|
||||
w->monitorID(), escapeJSONStrings(w->m_class), escapeJSONStrings(w->m_title), escapeJSONStrings(w->m_initialClass), escapeJSONStrings(w->m_initialTitle), w->getPID(),
|
||||
(sc<int>(w->m_isX11) == 1 ? "true" : "false"), (w->m_pinned ? "true" : "false"), sc<uint8_t>(w->m_fullscreenState.internal), sc<uint8_t>(w->m_fullscreenState.client),
|
||||
escapeJSONStrings(!w->m_workspace ? "" : w->m_workspace->m_name), (sc<int>(w->m_isFloating) == 1 ? "true" : "false"), w->monitorID(), escapeJSONStrings(w->m_class),
|
||||
escapeJSONStrings(w->m_title), escapeJSONStrings(w->m_initialClass), escapeJSONStrings(w->m_initialTitle), w->getPID(), (sc<int>(w->m_isX11) == 1 ? "true" : "false"),
|
||||
(w->m_pinned ? "true" : "false"), sc<uint8_t>(w->m_fullscreenState.internal), sc<uint8_t>(w->m_fullscreenState.client), (w->m_createdOverFullscreen ? "true" : "false"),
|
||||
getGroupedData(w, format), getTagsData(w, format), rc<uintptr_t>(w->m_swallowed.get()), getFocusHistoryID(w),
|
||||
(g_pInputManager->isWindowInhibiting(w, false) ? "true" : "false"), escapeJSONStrings(w->xdgTag().value_or("")), escapeJSONStrings(w->xdgDescription().value_or("")),
|
||||
escapeJSONStrings(NContentType::toString(w->getContentType())));
|
||||
escapeJSONStrings(NContentType::toString(w->getContentType())), w->m_stableID);
|
||||
} else {
|
||||
return std::format(
|
||||
"Window {:x} -> {}:\n\tmapped: {}\n\thidden: {}\n\tat: {},{}\n\tsize: {},{}\n\tworkspace: {} ({})\n\tfloating: {}\n\tpseudo: {}\n\tmonitor: {}\n\tclass: {}\n\ttitle: "
|
||||
"Window {:x} -> {}:\n\tmapped: {}\n\thidden: {}\n\tat: {},{}\n\tsize: {},{}\n\tworkspace: {} ({})\n\tfloating: {}\n\tmonitor: {}\n\tclass: {}\n\ttitle: "
|
||||
"{}\n\tinitialClass: {}\n\tinitialTitle: {}\n\tpid: "
|
||||
"{}\n\txwayland: {}\n\tpinned: "
|
||||
"{}\n\tfullscreen: {}\n\tfullscreenClient: {}\n\tgrouped: {}\n\ttags: {}\n\tswallowing: {:x}\n\tfocusHistoryID: {}\n\tinhibitingIdle: {}\n\txdgTag: "
|
||||
"{}\n\txdgDescription: {}\n\tcontentType: {}\n\n",
|
||||
"{}\n\tfullscreen: {}\n\tfullscreenClient: {}\n\toverFullscreen: {}\n\tgrouped: {}\n\ttags: {}\n\tswallowing: {:x}\n\tfocusHistoryID: {}\n\tinhibitingIdle: "
|
||||
"{}\n\txdgTag: "
|
||||
"{}\n\txdgDescription: {}\n\tcontentType: {}\n\tstableID: {:x}\n\n",
|
||||
rc<uintptr_t>(w.get()), w->m_title, sc<int>(w->m_isMapped), sc<int>(w->isHidden()), sc<int>(w->m_realPosition->goal().x), sc<int>(w->m_realPosition->goal().y),
|
||||
sc<int>(w->m_realSize->goal().x), sc<int>(w->m_realSize->goal().y), w->m_workspace ? w->workspaceID() : WORKSPACE_INVALID,
|
||||
(!w->m_workspace ? "" : w->m_workspace->m_name), sc<int>(w->m_isFloating), sc<int>(w->m_isPseudotiled), w->monitorID(), w->m_class, w->m_title, w->m_initialClass,
|
||||
w->m_initialTitle, w->getPID(), sc<int>(w->m_isX11), sc<int>(w->m_pinned), sc<uint8_t>(w->m_fullscreenState.internal), sc<uint8_t>(w->m_fullscreenState.client),
|
||||
(!w->m_workspace ? "" : w->m_workspace->m_name), sc<int>(w->m_isFloating), w->monitorID(), w->m_class, w->m_title, w->m_initialClass, w->m_initialTitle, w->getPID(),
|
||||
sc<int>(w->m_isX11), sc<int>(w->m_pinned), sc<uint8_t>(w->m_fullscreenState.internal), sc<uint8_t>(w->m_fullscreenState.client), sc<int>(w->m_createdOverFullscreen),
|
||||
getGroupedData(w, format), getTagsData(w, format), rc<uintptr_t>(w->m_swallowed.get()), getFocusHistoryID(w), sc<int>(g_pInputManager->isWindowInhibiting(w, false)),
|
||||
w->xdgTag().value_or(""), w->xdgDescription().value_or(""), NContentType::toString(w->getContentType()));
|
||||
w->xdgTag().value_or(""), w->xdgDescription().value_or(""), NContentType::toString(w->getContentType()), w->m_stableID);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -450,8 +451,15 @@ static std::string clientsRequest(eHyprCtlOutputFormat format, std::string reque
|
|||
}
|
||||
|
||||
std::string CHyprCtl::getWorkspaceData(PHLWORKSPACE w, eHyprCtlOutputFormat format) {
|
||||
const auto PLASTW = w->getLastFocusedWindow();
|
||||
const auto PMONITOR = w->m_monitor.lock();
|
||||
const auto PLASTW = w->getLastFocusedWindow();
|
||||
const auto PMONITOR = w->m_monitor.lock();
|
||||
|
||||
std::string layoutName = "unknown";
|
||||
if (w->m_space && w->m_space->algorithm() && w->m_space->algorithm()->tiledAlgo()) {
|
||||
const auto& TILED_ALGO = w->m_space->algorithm()->tiledAlgo();
|
||||
layoutName = Layout::Supplementary::algoMatcher()->getNameForTiledAlgo(&typeid(*TILED_ALGO.get()));
|
||||
}
|
||||
|
||||
if (format == eHyprCtlOutputFormat::FORMAT_JSON) {
|
||||
return std::format(R"#({{
|
||||
"id": {},
|
||||
|
|
@ -462,16 +470,17 @@ std::string CHyprCtl::getWorkspaceData(PHLWORKSPACE w, eHyprCtlOutputFormat form
|
|||
"hasfullscreen": {},
|
||||
"lastwindow": "0x{:x}",
|
||||
"lastwindowtitle": "{}",
|
||||
"ispersistent": {}
|
||||
"ispersistent": {},
|
||||
"tiledLayout": "{}"
|
||||
}})#",
|
||||
w->m_id, escapeJSONStrings(w->m_name), escapeJSONStrings(PMONITOR ? PMONITOR->m_name : "?"),
|
||||
escapeJSONStrings(PMONITOR ? std::to_string(PMONITOR->m_id) : "null"), w->getWindows(), w->m_hasFullscreenWindow ? "true" : "false",
|
||||
rc<uintptr_t>(PLASTW.get()), PLASTW ? escapeJSONStrings(PLASTW->m_title) : "", w->isPersistent() ? "true" : "false");
|
||||
rc<uintptr_t>(PLASTW.get()), PLASTW ? escapeJSONStrings(PLASTW->m_title) : "", w->isPersistent() ? "true" : "false", escapeJSONStrings(layoutName));
|
||||
} else {
|
||||
return std::format(
|
||||
"workspace ID {} ({}) on monitor {}:\n\tmonitorID: {}\n\twindows: {}\n\thasfullscreen: {}\n\tlastwindow: 0x{:x}\n\tlastwindowtitle: {}\n\tispersistent: {}\n\n",
|
||||
w->m_id, w->m_name, PMONITOR ? PMONITOR->m_name : "?", PMONITOR ? std::to_string(PMONITOR->m_id) : "null", w->getWindows(), sc<int>(w->m_hasFullscreenWindow),
|
||||
rc<uintptr_t>(PLASTW.get()), PLASTW ? PLASTW->m_title : "", sc<int>(w->isPersistent()));
|
||||
return std::format("workspace ID {} ({}) on monitor {}:\n\tmonitorID: {}\n\twindows: {}\n\thasfullscreen: {}\n\tlastwindow: 0x{:x}\n\tlastwindowtitle: {}\n\tispersistent: "
|
||||
"{}\n\ttiledLayout: {}\n\n",
|
||||
w->m_id, w->m_name, PMONITOR ? PMONITOR->m_name : "?", PMONITOR ? std::to_string(PMONITOR->m_id) : "null", w->getWindows(),
|
||||
sc<int>(w->m_hasFullscreenWindow), rc<uintptr_t>(PLASTW.get()), PLASTW ? PLASTW->m_title : "", sc<int>(w->isPersistent()), layoutName);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -670,28 +679,6 @@ static std::string layersRequest(eHyprCtlOutputFormat format, std::string reques
|
|||
return result;
|
||||
}
|
||||
|
||||
static std::string layoutsRequest(eHyprCtlOutputFormat format, std::string request) {
|
||||
std::string result = "";
|
||||
if (format == eHyprCtlOutputFormat::FORMAT_JSON) {
|
||||
result += "[";
|
||||
|
||||
for (auto const& m : g_pLayoutManager->getAllLayoutNames()) {
|
||||
result += std::format(
|
||||
R"#(
|
||||
"{}",)#",
|
||||
m);
|
||||
}
|
||||
trimTrailingComma(result);
|
||||
|
||||
result += "\n]\n";
|
||||
} else {
|
||||
for (auto const& m : g_pLayoutManager->getAllLayoutNames()) {
|
||||
result += std::format("{}\n", m);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::string configErrorsRequest(eHyprCtlOutputFormat format, std::string request) {
|
||||
std::string result = "";
|
||||
std::string currErrors = g_pConfigManager->getErrors();
|
||||
|
|
@ -802,7 +789,7 @@ static std::string devicesRequest(eHyprCtlOutputFormat format, std::string reque
|
|||
result += std::format(
|
||||
R"#( {{
|
||||
"address": "0x{:x}",
|
||||
"type": "tabletTool",
|
||||
"type": "tabletTool"
|
||||
}},)#",
|
||||
rc<uintptr_t>(d.get()));
|
||||
}
|
||||
|
|
@ -1294,8 +1281,13 @@ static std::string dispatchKeyword(eHyprCtlOutputFormat format, std::string in)
|
|||
|
||||
g_pHyprCtl->m_currentRequestParams.isDynamicKeyword = false;
|
||||
|
||||
if (COMMAND == "source") {
|
||||
g_pConfigManager->m_wantsMonitorReload = true;
|
||||
g_pEventLoopManager->doLater([] { g_pConfigManager->reloadRules(); });
|
||||
}
|
||||
|
||||
// if we are executing a dynamic source we have to reload everything, so every if will have a check for source.
|
||||
if (COMMAND == "monitor" || COMMAND == "source")
|
||||
if (COMMAND == "monitor")
|
||||
g_pConfigManager->m_wantsMonitorReload = true; // for monitor keywords
|
||||
|
||||
if (COMMAND.contains("monitorv2"))
|
||||
|
|
@ -1308,10 +1300,8 @@ static std::string dispatchKeyword(eHyprCtlOutputFormat format, std::string in)
|
|||
g_pInputManager->setTabletConfigs(); // update tablets
|
||||
}
|
||||
|
||||
static auto PLAYOUT = CConfigValue<std::string>("general:layout");
|
||||
|
||||
if (COMMAND.contains("general:layout"))
|
||||
g_pLayoutManager->switchToLayout(*PLAYOUT); // update layout
|
||||
if (COMMAND.contains("general:layout") || (COMMAND.contains("workspace") && VALUE.contains("layout:")))
|
||||
Layout::Supplementary::algoMatcher()->updateWorkspaceLayouts();
|
||||
|
||||
if (COMMAND.contains("decoration:screen_shader") || COMMAND == "source")
|
||||
g_pHyprOpenGL->m_reloadScreenShader = true;
|
||||
|
|
@ -1331,13 +1321,22 @@ static std::string dispatchKeyword(eHyprCtlOutputFormat format, std::string in)
|
|||
for (auto const& m : g_pCompositor->m_monitors) {
|
||||
*(m->m_cursorZoom) = *PZOOMFACTOR;
|
||||
g_pHyprRenderer->damageMonitor(m);
|
||||
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->m_id);
|
||||
if (m->m_activeWorkspace)
|
||||
m->m_activeWorkspace->m_space->recalculate();
|
||||
}
|
||||
}
|
||||
|
||||
if (COMMAND.contains("windowrule ") || COMMAND.contains("windowrule["))
|
||||
g_pConfigManager->reloadRules();
|
||||
|
||||
if (COMMAND.contains("layerrule") || COMMAND.contains("layerrule[")) {
|
||||
g_pConfigManager->reloadRules();
|
||||
// Damage all monitors to redraw static layers.
|
||||
for (auto const& m : g_pCompositor->m_monitors) {
|
||||
g_pHyprRenderer->damageMonitor(m);
|
||||
}
|
||||
}
|
||||
|
||||
if (COMMAND.contains("workspace"))
|
||||
g_pConfigManager->ensurePersistentWorkspacesPresent();
|
||||
|
||||
|
|
@ -1597,7 +1596,7 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ
|
|||
static auto PGROUPACTIVELOCKEDCOL = CConfigValue<Hyprlang::CUSTOMTYPE>("group:col.border_locked_active");
|
||||
static auto PGROUPINACTIVELOCKEDCOL = CConfigValue<Hyprlang::CUSTOMTYPE>("group:col.border_locked_inactive");
|
||||
|
||||
const bool GROUPLOCKED = PWINDOW->m_groupData.pNextWindow.lock() ? PWINDOW->getGroupHead()->m_groupData.locked : false;
|
||||
const bool GROUPLOCKED = PWINDOW->m_group ? PWINDOW->m_group->locked() : false;
|
||||
|
||||
if (active) {
|
||||
auto* const ACTIVECOL = (CGradientValueData*)(PACTIVECOL.ptr())->getData();
|
||||
|
|
@ -1605,7 +1604,7 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ
|
|||
auto* const GROUPACTIVECOL = (CGradientValueData*)(PGROUPACTIVECOL.ptr())->getData();
|
||||
auto* const GROUPACTIVELOCKEDCOL = (CGradientValueData*)(PGROUPACTIVELOCKEDCOL.ptr())->getData();
|
||||
const auto* const ACTIVECOLOR =
|
||||
!PWINDOW->m_groupData.pNextWindow.lock() ? (!PWINDOW->m_groupData.deny ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL);
|
||||
!PWINDOW->m_group ? (!(PWINDOW->m_groupRules & Desktop::View::GROUP_DENY) ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL);
|
||||
|
||||
std::string borderColorString = PWINDOW->m_ruleApplicator->activeBorderColor().valueOr(*ACTIVECOLOR).toString();
|
||||
if (FORMNORM)
|
||||
|
|
@ -1617,8 +1616,8 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ
|
|||
auto* const NOGROUPINACTIVECOL = (CGradientValueData*)(PNOGROUPINACTIVECOL.ptr())->getData();
|
||||
auto* const GROUPINACTIVECOL = (CGradientValueData*)(PGROUPINACTIVECOL.ptr())->getData();
|
||||
auto* const GROUPINACTIVELOCKEDCOL = (CGradientValueData*)(PGROUPINACTIVELOCKEDCOL.ptr())->getData();
|
||||
const auto* const INACTIVECOLOR = !PWINDOW->m_groupData.pNextWindow.lock() ? (!PWINDOW->m_groupData.deny ? INACTIVECOL : NOGROUPINACTIVECOL) :
|
||||
(GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL);
|
||||
const auto* const INACTIVECOLOR = !PWINDOW->m_group ? (!(PWINDOW->m_groupRules & Desktop::View::GROUP_DENY) ? INACTIVECOL : NOGROUPINACTIVECOL) :
|
||||
(GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL);
|
||||
|
||||
std::string borderColorString = PWINDOW->m_ruleApplicator->inactiveBorderColor().valueOr(*INACTIVECOLOR).toString();
|
||||
if (FORMNORM)
|
||||
|
|
@ -2050,7 +2049,12 @@ static std::string submapRequest(eHyprCtlOutputFormat format, std::string reques
|
|||
}
|
||||
|
||||
static std::string reloadShaders(eHyprCtlOutputFormat format, std::string request) {
|
||||
if (g_pHyprOpenGL->initShaders())
|
||||
CVarList vars(request, 0, ' ');
|
||||
|
||||
if (vars.size() > 2)
|
||||
return "too many args";
|
||||
|
||||
if (g_pHyprOpenGL && g_pHyprRenderer->reloadShaders(vars.size() == 2 ? vars[1] : ""))
|
||||
return format == FORMAT_JSON ? "{\"ok\": true}" : "ok";
|
||||
else
|
||||
return format == FORMAT_JSON ? "{\"ok\": false}" : "error";
|
||||
|
|
@ -2073,13 +2077,12 @@ CHyprCtl::CHyprCtl() {
|
|||
registerCommand(SHyprCtlCommand{"systeminfo", true, systemInfoRequest});
|
||||
registerCommand(SHyprCtlCommand{"animations", true, animationsRequest});
|
||||
registerCommand(SHyprCtlCommand{"rollinglog", true, rollinglogRequest});
|
||||
registerCommand(SHyprCtlCommand{"layouts", true, layoutsRequest});
|
||||
registerCommand(SHyprCtlCommand{"configerrors", true, configErrorsRequest});
|
||||
registerCommand(SHyprCtlCommand{"locked", true, getIsLocked});
|
||||
registerCommand(SHyprCtlCommand{"descriptions", true, getDescriptions});
|
||||
registerCommand(SHyprCtlCommand{"submap", true, submapRequest});
|
||||
registerCommand(SHyprCtlCommand{.name = "reloadshaders", .exact = true, .fn = reloadShaders});
|
||||
|
||||
registerCommand(SHyprCtlCommand{.name = "reloadshaders", .exact = false, .fn = reloadShaders});
|
||||
registerCommand(SHyprCtlCommand{"monitors", false, monitorsRequest});
|
||||
registerCommand(SHyprCtlCommand{"reload", false, reloadRequest});
|
||||
registerCommand(SHyprCtlCommand{"plugin", false, dispatchPlugin});
|
||||
|
|
@ -2186,10 +2189,6 @@ std::string CHyprCtl::getReply(std::string request) {
|
|||
g_pInputManager->setTouchDeviceConfigs(); // update touch device cfgs
|
||||
g_pInputManager->setTabletConfigs(); // update tablets
|
||||
|
||||
static auto PLAYOUT = CConfigValue<std::string>("general:layout");
|
||||
|
||||
g_pLayoutManager->switchToLayout(*PLAYOUT); // update layout
|
||||
|
||||
g_pHyprOpenGL->m_reloadScreenShader = true;
|
||||
|
||||
for (auto& [m, rd] : g_pHyprOpenGL->m_monitorRenderResources) {
|
||||
|
|
@ -2203,9 +2202,17 @@ std::string CHyprCtl::getReply(std::string request) {
|
|||
Desktop::Rule::ruleEngine()->updateAllRules();
|
||||
}
|
||||
|
||||
for (const auto& ws : g_pCompositor->getWorkspaces()) {
|
||||
if (!ws)
|
||||
continue;
|
||||
|
||||
ws->updateWindows();
|
||||
ws->updateWindowData();
|
||||
ws->updateWindowDecos();
|
||||
}
|
||||
|
||||
for (auto const& m : g_pCompositor->m_monitors) {
|
||||
g_pHyprRenderer->damageMonitor(m);
|
||||
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->m_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2217,16 +2224,38 @@ std::string CHyprCtl::makeDynamicCall(const std::string& input) {
|
|||
}
|
||||
|
||||
static bool successWrite(int fd, const std::string& data, bool needLog = true) {
|
||||
if (write(fd, data.c_str(), data.length()) > 0)
|
||||
return true;
|
||||
size_t totalWritten = 0;
|
||||
size_t remaining = data.length();
|
||||
size_t waitsDone = 0;
|
||||
constexpr const size_t MAX_WAITS = 20; // 2000µs = 2ms
|
||||
|
||||
if (errno == EAGAIN)
|
||||
return true;
|
||||
while (totalWritten < data.length()) {
|
||||
ssize_t written = write(fd, data.c_str() + totalWritten, remaining);
|
||||
|
||||
if (needLog)
|
||||
Log::logger->log(Log::ERR, "Couldn't write to socket. Error: " + std::string(strerror(errno)));
|
||||
if (waitsDone > MAX_WAITS) {
|
||||
Log::logger->log(Log::ERR, "Couldn't write to socket. Buffer was full and the client couldn't read in time.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
if (written < 0) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
// socket buffer full, wait a bit and retry
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(100));
|
||||
waitsDone++;
|
||||
continue;
|
||||
}
|
||||
if (needLog)
|
||||
Log::logger->log(Log::ERR, "Couldn't write to socket. Error: {}", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
waitsDone = 0;
|
||||
|
||||
totalWritten += written;
|
||||
remaining -= written;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void runWritingDebugLogThread(const int conn) {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
#include "../desktop/state/FocusState.hpp"
|
||||
|
||||
CHyprDebugOverlay::CHyprDebugOverlay() {
|
||||
m_texture = makeShared<CTexture>();
|
||||
m_texture = g_pHyprRenderer->createTexture();
|
||||
}
|
||||
|
||||
void CHyprMonitorDebugOverlay::renderData(PHLMONITOR pMonitor, float durationUs) {
|
||||
|
|
@ -259,15 +259,7 @@ void CHyprDebugOverlay::draw() {
|
|||
cairo_surface_flush(m_cairoSurface);
|
||||
|
||||
// copy the data to an OpenGL texture we have
|
||||
const auto DATA = cairo_image_surface_get_data(m_cairoSurface);
|
||||
m_texture->allocate();
|
||||
m_texture->bind();
|
||||
m_texture->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
m_texture->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
m_texture->setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE);
|
||||
m_texture->setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED);
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA);
|
||||
m_texture = g_pHyprRenderer->createTexture(m_cairoSurface);
|
||||
|
||||
CTexPassElement::SRenderData data;
|
||||
data.tex = m_texture;
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ class CHyprDebugOverlay {
|
|||
cairo_surface_t* m_cairoSurface = nullptr;
|
||||
cairo_t* m_cairo = nullptr;
|
||||
|
||||
SP<CTexture> m_texture;
|
||||
SP<ITexture> m_texture;
|
||||
|
||||
friend class CHyprMonitorDebugOverlay;
|
||||
friend class CHyprRenderer;
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
#include "../Compositor.hpp"
|
||||
#include "../config/ConfigValue.hpp"
|
||||
#include "../render/pass/TexPassElement.hpp"
|
||||
#include "../event/EventBus.hpp"
|
||||
|
||||
#include "../managers/animation/AnimationManager.hpp"
|
||||
#include "../managers/HookSystemManager.hpp"
|
||||
#include "../render/Renderer.hpp"
|
||||
|
||||
static inline auto iconBackendFromLayout(PangoLayout* layout) {
|
||||
|
|
@ -22,14 +22,12 @@ static inline auto iconBackendFromLayout(PangoLayout* layout) {
|
|||
}
|
||||
|
||||
CHyprNotificationOverlay::CHyprNotificationOverlay() {
|
||||
static auto P = g_pHookSystem->hookDynamic("focusedMon", [&](void* self, SCallbackInfo& info, std::any param) {
|
||||
static auto P = Event::bus()->m_events.monitor.focused.listen([&](PHLMONITOR mon) {
|
||||
if (m_notifications.empty())
|
||||
return;
|
||||
|
||||
g_pHyprRenderer->damageBox(m_lastDamage);
|
||||
});
|
||||
|
||||
m_texture = makeShared<CTexture>();
|
||||
}
|
||||
|
||||
CHyprNotificationOverlay::~CHyprNotificationOverlay() {
|
||||
|
|
@ -232,16 +230,7 @@ void CHyprNotificationOverlay::draw(PHLMONITOR pMonitor) {
|
|||
|
||||
m_lastDamage = damage;
|
||||
|
||||
// copy the data to an OpenGL texture we have
|
||||
const auto DATA = cairo_image_surface_get_data(m_cairoSurface);
|
||||
m_texture->allocate();
|
||||
m_texture->bind();
|
||||
m_texture->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
m_texture->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
m_texture->setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE);
|
||||
m_texture->setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED);
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, MONSIZE.x, MONSIZE.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA);
|
||||
m_texture = g_pHyprRenderer->createTexture(m_cairoSurface);
|
||||
|
||||
CTexPassElement::SRenderData data;
|
||||
data.tex = m_texture;
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ enum eIconBackend : uint8_t {
|
|||
static const std::array<std::array<std::string, ICON_NONE + 1>, 3 /* backends */> ICONS_ARRAY = {
|
||||
std::array<std::string, ICON_NONE + 1>{"[!]", "[i]", "[Hint]", "[Err]", "[?]", "[ok]", ""},
|
||||
std::array<std::string, ICON_NONE + 1>{"", "", "", "", "", "", ""}, std::array<std::string, ICON_NONE + 1>{"", "", "", "", "", ""}};
|
||||
static const std::array<CHyprColor, ICON_NONE + 1> ICONS_COLORS = {CHyprColor{255.0 / 255.0, 204 / 255.0, 102 / 255.0, 1.0},
|
||||
static const std::array<CHyprColor, ICON_NONE + 1> ICONS_COLORS = {CHyprColor{1.0, 204 / 255.0, 102 / 255.0, 1.0},
|
||||
CHyprColor{128 / 255.0, 255 / 255.0, 255 / 255.0, 1.0},
|
||||
CHyprColor{179 / 255.0, 255 / 255.0, 204 / 255.0, 1.0},
|
||||
CHyprColor{255 / 255.0, 77 / 255.0, 77 / 255.0, 1.0},
|
||||
|
|
@ -57,7 +57,7 @@ class CHyprNotificationOverlay {
|
|||
PHLMONITORREF m_lastMonitor;
|
||||
Vector2D m_lastSize = Vector2D(-1, -1);
|
||||
|
||||
SP<CTexture> m_texture;
|
||||
SP<ITexture> m_texture;
|
||||
};
|
||||
|
||||
inline UP<CHyprNotificationOverlay> g_pHyprNotificationOverlay;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
#include "Logger.hpp"
|
||||
#include "RollingLogFollow.hpp"
|
||||
|
||||
#include "../../defines.hpp"
|
||||
#include "../../event/EventBus.hpp"
|
||||
|
||||
#include "../../managers/HookSystemManager.hpp"
|
||||
#include "../../config/ConfigValue.hpp"
|
||||
|
||||
using namespace Log;
|
||||
|
|
@ -39,7 +38,7 @@ void CLogger::initIS(const std::string_view& IS) {
|
|||
}
|
||||
|
||||
void CLogger::initCallbacks() {
|
||||
static auto P = g_pHookSystem->hookDynamic("configReloaded", [this](void* hk, SCallbackInfo& info, std::any param) { recheckCfg(); });
|
||||
static auto P = Event::bus()->m_events.config.reloaded.listen([this]() { recheckCfg(); });
|
||||
recheckCfg();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,3 +5,7 @@
|
|||
#include "helpers/Color.hpp"
|
||||
#include "macros.hpp"
|
||||
#include "desktop/DesktopTypes.hpp"
|
||||
|
||||
#if !defined(__GXX_RTTI)
|
||||
#error "Hyprland requires C++ RTTI. Shit will hit the fan otherwise. Do not even try."
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
#include "Workspace.hpp"
|
||||
#include "view/Group.hpp"
|
||||
#include "../Compositor.hpp"
|
||||
#include "../config/ConfigValue.hpp"
|
||||
#include "config/ConfigManager.hpp"
|
||||
#include "managers/animation/AnimationManager.hpp"
|
||||
#include "../managers/EventManager.hpp"
|
||||
#include "../managers/HookSystemManager.hpp"
|
||||
#include "../layout/space/Space.hpp"
|
||||
#include "../layout/supplementary/WorkspaceAlgoMatcher.hpp"
|
||||
#include "../event/EventBus.hpp"
|
||||
|
||||
#include <hyprutils/animation/AnimatedVariable.hpp>
|
||||
#include <hyprutils/string/String.hpp>
|
||||
|
|
@ -34,13 +37,14 @@ void CWorkspace::init(PHLWORKSPACE self) {
|
|||
if (RULEFORTHIS.defaultName.has_value())
|
||||
m_name = RULEFORTHIS.defaultName.value();
|
||||
|
||||
m_focusedWindowHook = g_pHookSystem->hookDynamic("closeWindow", [this](void* self, SCallbackInfo& info, std::any param) {
|
||||
const auto PWINDOW = std::any_cast<PHLWINDOW>(param);
|
||||
|
||||
if (PWINDOW == m_lastFocusedWindow.lock())
|
||||
m_focusedWindowHook = Event::bus()->m_events.window.close.listen([this](PHLWINDOW pWindow) {
|
||||
if (pWindow == m_lastFocusedWindow.lock())
|
||||
m_lastFocusedWindow.reset();
|
||||
});
|
||||
|
||||
m_space = Layout::CSpace::create(m_self.lock());
|
||||
m_space->setAlgorithmProvider(Layout::Supplementary::algoMatcher()->createAlgorithmForWorkspace(m_self.lock()));
|
||||
|
||||
m_inert = false;
|
||||
|
||||
const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(self);
|
||||
|
|
@ -52,22 +56,19 @@ void CWorkspace::init(PHLWORKSPACE self) {
|
|||
|
||||
g_pEventManager->postEvent({.event = "createworkspace", .data = m_name});
|
||||
g_pEventManager->postEvent({.event = "createworkspacev2", .data = std::format("{},{}", m_id, m_name)});
|
||||
EMIT_HOOK_EVENT("createWorkspace", this);
|
||||
Event::bus()->m_events.workspace.created.emit(self);
|
||||
}
|
||||
|
||||
CWorkspace::~CWorkspace() {
|
||||
Log::logger->log(Log::DEBUG, "Destroying workspace ID {}", m_id);
|
||||
|
||||
// check if g_pHookSystem and g_pEventManager exist, they might be destroyed as in when the compositor is closing.
|
||||
if (g_pHookSystem)
|
||||
g_pHookSystem->unhook(m_focusedWindowHook);
|
||||
|
||||
if (g_pEventManager) {
|
||||
g_pEventManager->postEvent({.event = "destroyworkspace", .data = m_name});
|
||||
g_pEventManager->postEvent({.event = "destroyworkspacev2", .data = std::format("{},{}", m_id, m_name)});
|
||||
EMIT_HOOK_EVENT("destroyWorkspace", this);
|
||||
}
|
||||
|
||||
Event::bus()->m_events.workspace.removed.emit(m_self);
|
||||
|
||||
m_events.destroy.emit();
|
||||
}
|
||||
|
||||
|
|
@ -406,14 +407,19 @@ bool CWorkspace::isVisibleNotCovered() {
|
|||
|
||||
int CWorkspace::getWindows(std::optional<bool> onlyTiled, std::optional<bool> onlyPinned, std::optional<bool> onlyVisible) {
|
||||
int no = 0;
|
||||
for (auto const& w : g_pCompositor->m_windows) {
|
||||
if (w->workspaceID() != m_id || !w->m_isMapped)
|
||||
|
||||
if (!m_space)
|
||||
return 0;
|
||||
|
||||
for (auto const& t : m_space->targets()) {
|
||||
if (!t)
|
||||
continue;
|
||||
if (onlyTiled.has_value() && w->m_isFloating == onlyTiled.value())
|
||||
|
||||
if (onlyTiled.has_value() && t->floating() == onlyTiled.value())
|
||||
continue;
|
||||
if (onlyPinned.has_value() && w->m_pinned != onlyPinned.value())
|
||||
if (onlyPinned.has_value() && (!t->window() || t->window()->m_pinned != onlyPinned.value()))
|
||||
continue;
|
||||
if (onlyVisible.has_value() && w->isHidden() == onlyVisible.value())
|
||||
if (onlyVisible.has_value() && (!t->window() || t->window()->isHidden() == onlyVisible.value()))
|
||||
continue;
|
||||
no++;
|
||||
}
|
||||
|
|
@ -423,16 +429,16 @@ int CWorkspace::getWindows(std::optional<bool> onlyTiled, std::optional<bool> on
|
|||
|
||||
int CWorkspace::getGroups(std::optional<bool> onlyTiled, std::optional<bool> onlyPinned, std::optional<bool> onlyVisible) {
|
||||
int no = 0;
|
||||
for (auto const& w : g_pCompositor->m_windows) {
|
||||
if (w->workspaceID() != m_id || !w->m_isMapped)
|
||||
for (auto const& g : Desktop::View::groups()) {
|
||||
const auto HEAD = g->head();
|
||||
|
||||
if (HEAD->workspaceID() != m_id || !HEAD->m_isMapped)
|
||||
continue;
|
||||
if (!w->m_groupData.head)
|
||||
if (onlyTiled.has_value() && HEAD->m_isFloating == onlyTiled.value())
|
||||
continue;
|
||||
if (onlyTiled.has_value() && w->m_isFloating == onlyTiled.value())
|
||||
if (onlyPinned.has_value() && HEAD->m_pinned != onlyPinned.value())
|
||||
continue;
|
||||
if (onlyPinned.has_value() && w->m_pinned != onlyPinned.value())
|
||||
continue;
|
||||
if (onlyVisible.has_value() && w->isHidden() == onlyVisible.value())
|
||||
if (onlyVisible.has_value() && g->current()->isHidden() == onlyVisible.value())
|
||||
continue;
|
||||
no++;
|
||||
}
|
||||
|
|
@ -514,13 +520,11 @@ void CWorkspace::rename(const std::string& name) {
|
|||
}
|
||||
|
||||
void CWorkspace::updateWindows() {
|
||||
m_hasFullscreenWindow = std::ranges::any_of(g_pCompositor->m_windows, [this](const auto& w) { return w->m_isMapped && w->m_workspace == m_self && w->isFullscreen(); });
|
||||
m_hasFullscreenWindow = std::ranges::any_of(m_space->targets(), [](const auto& t) { return t->fullscreenMode() != FSMODE_NONE; });
|
||||
|
||||
for (auto const& w : g_pCompositor->m_windows) {
|
||||
if (!w->m_isMapped || w->m_workspace != m_self)
|
||||
continue;
|
||||
|
||||
w->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE);
|
||||
for (auto const& t : m_space->targets()) {
|
||||
if (t->window())
|
||||
t->window()->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@
|
|||
#include "../helpers/MiscFunctions.hpp"
|
||||
#include "../helpers/signal/Signal.hpp"
|
||||
|
||||
namespace Layout {
|
||||
class CSpace;
|
||||
};
|
||||
|
||||
enum eFullscreenMode : int8_t {
|
||||
FSMODE_NONE = 0,
|
||||
FSMODE_MAXIMIZED = 1 << 0,
|
||||
|
|
@ -20,7 +24,9 @@ class CWorkspace {
|
|||
CWorkspace(WORKSPACEID id, PHLMONITOR monitor, std::string name, bool special = false, bool isEmpty = true);
|
||||
~CWorkspace();
|
||||
|
||||
WP<CWorkspace> m_self;
|
||||
WP<CWorkspace> m_self;
|
||||
|
||||
SP<Layout::CSpace> m_space;
|
||||
|
||||
// Workspaces ID-based have IDs > 0
|
||||
// and workspaces name-based have IDs starting with -1337
|
||||
|
|
@ -87,13 +93,13 @@ class CWorkspace {
|
|||
} m_events;
|
||||
|
||||
private:
|
||||
void init(PHLWORKSPACE self);
|
||||
void init(PHLWORKSPACE self);
|
||||
|
||||
SP<HOOK_CALLBACK_FN> m_focusedWindowHook;
|
||||
bool m_inert = true;
|
||||
CHyprSignalListener m_focusedWindowHook;
|
||||
bool m_inert = true;
|
||||
|
||||
SP<CWorkspace> m_selfPersistent; // for persistent workspaces.
|
||||
bool m_persistent = false;
|
||||
SP<CWorkspace> m_selfPersistent; // for persistent workspaces.
|
||||
bool m_persistent = false;
|
||||
};
|
||||
|
||||
inline bool valid(const PHLWORKSPACE& ref) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#include "WindowHistoryTracker.hpp"
|
||||
|
||||
#include "../../managers/HookSystemManager.hpp"
|
||||
#include "../view/Window.hpp"
|
||||
#include "../../event/EventBus.hpp"
|
||||
|
||||
using namespace Desktop;
|
||||
using namespace Desktop::History;
|
||||
|
|
@ -12,18 +12,12 @@ SP<CWindowHistoryTracker> History::windowTracker() {
|
|||
}
|
||||
|
||||
CWindowHistoryTracker::CWindowHistoryTracker() {
|
||||
static auto P = g_pHookSystem->hookDynamic("openWindowEarly", [this](void* self, SCallbackInfo& info, std::any data) {
|
||||
auto window = std::any_cast<PHLWINDOW>(data);
|
||||
|
||||
static auto P = Event::bus()->m_events.window.openEarly.listen([this](PHLWINDOW pWindow) {
|
||||
// add a last track
|
||||
m_history.insert(m_history.begin(), window);
|
||||
m_history.insert(m_history.begin(), pWindow);
|
||||
});
|
||||
|
||||
static auto P1 = g_pHookSystem->hookDynamic("activeWindow", [this](void* self, SCallbackInfo& info, std::any data) {
|
||||
auto window = std::any_cast<PHLWINDOW>(data);
|
||||
|
||||
track(window);
|
||||
});
|
||||
static auto P1 = Event::bus()->m_events.window.active.listen([this](PHLWINDOW window, uint8_t reason) { track(window); });
|
||||
}
|
||||
|
||||
void CWindowHistoryTracker::track(PHLWINDOW w) {
|
||||
|
|
|
|||
|
|
@ -2,9 +2,13 @@
|
|||
|
||||
#include "../../helpers/Monitor.hpp"
|
||||
#include "../Workspace.hpp"
|
||||
#include "../../managers/HookSystemManager.hpp"
|
||||
#include "../state/FocusState.hpp"
|
||||
#include "../../managers/eventLoop/EventLoopManager.hpp"
|
||||
#include "../../event/EventBus.hpp"
|
||||
#include "../../config/ConfigValue.hpp"
|
||||
|
||||
#include <hyprutils/utils/ScopeGuard.hpp>
|
||||
|
||||
using namespace Desktop;
|
||||
using namespace Desktop::History;
|
||||
|
||||
|
|
@ -14,27 +18,16 @@ SP<CWorkspaceHistoryTracker> History::workspaceTracker() {
|
|||
}
|
||||
|
||||
CWorkspaceHistoryTracker::CWorkspaceHistoryTracker() {
|
||||
static auto P = g_pHookSystem->hookDynamic("workspace", [this](void* self, SCallbackInfo& info, std::any data) {
|
||||
auto workspace = std::any_cast<PHLWORKSPACE>(data);
|
||||
track(workspace);
|
||||
});
|
||||
static auto P = Event::bus()->m_events.workspace.active.listen([this](PHLWORKSPACE workspace) { track(workspace); });
|
||||
|
||||
static auto P1 = g_pHookSystem->hookDynamic("monitorAdded", [this](void* self, SCallbackInfo& info, std::any data) {
|
||||
auto mon = std::any_cast<PHLMONITOR>(data);
|
||||
track(mon);
|
||||
});
|
||||
}
|
||||
|
||||
CWorkspaceHistoryTracker::SMonitorData& CWorkspaceHistoryTracker::dataFor(PHLMONITOR mon) {
|
||||
for (auto& ref : m_monitorDatas) {
|
||||
if (ref.monitor != mon)
|
||||
continue;
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
return m_monitorDatas.emplace_back(SMonitorData{
|
||||
.monitor = mon,
|
||||
static auto P1 = Event::bus()->m_events.monitor.focused.listen([this](PHLMONITOR mon) {
|
||||
// This sucks ASS, but we have to do this because switching to a workspace on another mon will trigger a workspace event right afterwards and we don't
|
||||
// want to remember the workspace that was not visible there
|
||||
// TODO: do something about this
|
||||
g_pEventLoopManager->doLater([this, mon = PHLMONITORREF{mon}] {
|
||||
if (mon)
|
||||
track(mon->m_activeWorkspace);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -52,44 +45,32 @@ CWorkspaceHistoryTracker::SWorkspacePreviousData& CWorkspaceHistoryTracker::data
|
|||
}
|
||||
|
||||
void CWorkspaceHistoryTracker::track(PHLWORKSPACE w) {
|
||||
if (!w->m_monitor)
|
||||
if (!w || !w->m_monitor || w == m_lastWorkspaceData.workspace)
|
||||
return;
|
||||
|
||||
static auto PALLOWWORKSPACECYCLES = CConfigValue<Hyprlang::INT>("binds:allow_workspace_cycles");
|
||||
static auto PALLOWWORKSPACECYCLES = CConfigValue<Hyprlang::INT>("binds:allow_workspace_cycles");
|
||||
|
||||
auto& data = dataFor(w);
|
||||
auto& monData = dataFor(w->m_monitor.lock());
|
||||
auto& data = dataFor(w);
|
||||
|
||||
if (!monData.workspace) {
|
||||
data.previous.reset();
|
||||
data.previousID = WORKSPACE_INVALID;
|
||||
data.previousName = "";
|
||||
Hyprutils::Utils::CScopeGuard x([&] { setLastWorkspaceData(w); });
|
||||
|
||||
if (m_lastWorkspaceData.workspace == w && !*PALLOWWORKSPACECYCLES)
|
||||
return;
|
||||
|
||||
data.previous = m_lastWorkspaceData.workspace;
|
||||
if (m_lastWorkspaceData.workspace) {
|
||||
data.previousName = m_lastWorkspaceData.workspace->m_name;
|
||||
data.previousID = m_lastWorkspaceData.workspace->m_id;
|
||||
data.previousMon = m_lastWorkspaceData.workspace->m_monitor;
|
||||
} else {
|
||||
data.previousName = m_lastWorkspaceData.workspaceName;
|
||||
data.previousID = m_lastWorkspaceData.workspaceID;
|
||||
data.previousMon = m_lastWorkspaceData.monitor;
|
||||
}
|
||||
|
||||
if (monData.workspace == w && !*PALLOWWORKSPACECYCLES) {
|
||||
track(w->m_monitor.lock());
|
||||
return;
|
||||
}
|
||||
|
||||
data.previous = monData.workspace;
|
||||
data.previousName = monData.workspace->m_name;
|
||||
data.previousID = monData.workspace->m_id;
|
||||
data.previousMon = monData.workspace->m_monitor;
|
||||
|
||||
track(w->m_monitor.lock());
|
||||
}
|
||||
|
||||
void CWorkspaceHistoryTracker::track(PHLMONITOR mon) {
|
||||
auto& data = dataFor(mon);
|
||||
data.workspace = mon->m_activeWorkspace;
|
||||
data.workspaceName = mon->m_activeWorkspace ? mon->m_activeWorkspace->m_name : "";
|
||||
data.workspaceID = mon->activeWorkspaceID();
|
||||
}
|
||||
|
||||
void CWorkspaceHistoryTracker::gc() {
|
||||
std::erase_if(m_datas, [](const auto& e) { return !e.workspace; });
|
||||
std::erase_if(m_monitorDatas, [](const auto& e) { return !e.monitor; });
|
||||
}
|
||||
|
||||
const CWorkspaceHistoryTracker::SWorkspacePreviousData* CWorkspaceHistoryTracker::previousWorkspace(PHLWORKSPACE ws) {
|
||||
|
|
@ -156,3 +137,15 @@ SWorkspaceIDName CWorkspaceHistoryTracker::previousWorkspaceIDName(PHLWORKSPACE
|
|||
|
||||
return SWorkspaceIDName{.id = DATA->previousID, .name = DATA->previousName, .isAutoIDd = DATA->previousID <= 0};
|
||||
}
|
||||
|
||||
void CWorkspaceHistoryTracker::setLastWorkspaceData(PHLWORKSPACE w) {
|
||||
if (!w) {
|
||||
m_lastWorkspaceData = {};
|
||||
return;
|
||||
}
|
||||
|
||||
m_lastWorkspaceData.workspace = w;
|
||||
m_lastWorkspaceData.workspaceID = w->m_id;
|
||||
m_lastWorkspaceData.workspaceName = w->m_name;
|
||||
m_lastWorkspaceData.monitor = w->m_monitor;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,21 +32,19 @@ namespace Desktop::History {
|
|||
SWorkspaceIDName previousWorkspaceIDName(PHLWORKSPACE ws, PHLMONITOR restrict);
|
||||
|
||||
private:
|
||||
struct SMonitorData {
|
||||
struct SLastWorkspaceData {
|
||||
PHLMONITORREF monitor;
|
||||
PHLWORKSPACEREF workspace;
|
||||
std::string workspaceName = "";
|
||||
WORKSPACEID workspaceID = WORKSPACE_INVALID;
|
||||
};
|
||||
} m_lastWorkspaceData;
|
||||
|
||||
std::vector<SWorkspacePreviousData> m_datas;
|
||||
std::vector<SMonitorData> m_monitorDatas;
|
||||
|
||||
void track(PHLWORKSPACE w);
|
||||
void track(PHLMONITOR mon);
|
||||
void gc();
|
||||
void setLastWorkspaceData(PHLWORKSPACE w);
|
||||
|
||||
SMonitorData& dataFor(PHLMONITOR mon);
|
||||
SWorkspacePreviousData& dataFor(PHLWORKSPACE ws);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -6,16 +6,18 @@ using namespace Desktop;
|
|||
// fuck me. Writing this at 11pm, and I have an in-class test tomorrow.
|
||||
// I am failing that bitch
|
||||
|
||||
CReservedArea::CReservedArea(const Vector2D& tl, const Vector2D& br) : m_initialTopLeft(tl), m_initialBottomRight(br) {
|
||||
CReservedArea::CReservedArea(const Vector2D& tl, const Vector2D& br) : m_initialTopLeft(tl.clamp({0, 0})), m_initialBottomRight(br.clamp({0, 0})) {
|
||||
calculate();
|
||||
}
|
||||
|
||||
CReservedArea::CReservedArea(double top, double right, double bottom, double left) : m_initialTopLeft(left, top), m_initialBottomRight(right, bottom) {
|
||||
CReservedArea::CReservedArea(double top, double right, double bottom, double left) :
|
||||
m_initialTopLeft(std::max(left, 0.0), std::max(top, 0.0)), m_initialBottomRight(std::max(right, 0.0), std::max(bottom, 0.0)) {
|
||||
calculate();
|
||||
}
|
||||
|
||||
CReservedArea::CReservedArea(const CBox& parent, const CBox& child) {
|
||||
ASSERT(!parent.empty() && !child.empty());
|
||||
if (parent.empty() || child.empty())
|
||||
return; // empty reserved area
|
||||
|
||||
ASSERT(parent.containsPoint(child.pos() + Vector2D{0.0001, 0.0001}));
|
||||
ASSERT(parent.containsPoint(child.pos() + child.size() - Vector2D{0.0001, 0.0001}));
|
||||
|
|
@ -81,6 +83,8 @@ void CReservedArea::addType(eReservedDynamicType t, const Vector2D& topLeft, con
|
|||
auto& ref = m_dynamicReserved[t];
|
||||
ref.topLeft += topLeft;
|
||||
ref.bottomRight += bottomRight;
|
||||
ref.topLeft = ref.topLeft.clamp({0, 0});
|
||||
ref.bottomRight = ref.bottomRight.clamp({0, 0});
|
||||
calculate();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ static const std::unordered_map<eRuleProperty, eRuleMatchEngine> RULE_ENGINES =
|
|||
{RULE_PROP_XDG_TAG, RULE_MATCH_ENGINE_REGEX}, //
|
||||
{RULE_PROP_NAMESPACE, RULE_MATCH_ENGINE_REGEX}, //
|
||||
{RULE_PROP_EXEC_TOKEN, RULE_MATCH_ENGINE_REGEX}, //
|
||||
{RULE_PROP_EXEC_PID, RULE_MATCH_ENGINE_INT}, //
|
||||
};
|
||||
|
||||
const std::vector<std::string>& Rule::allMatchPropStrings() {
|
||||
|
|
@ -125,10 +126,11 @@ const std::string& IRule::name() {
|
|||
return m_name;
|
||||
}
|
||||
|
||||
void IRule::markAsExecRule(const std::string& token, bool persistent) {
|
||||
void IRule::markAsExecRule(const std::string& token, uint64_t pid, bool persistent) {
|
||||
m_execData.isExecRule = true;
|
||||
m_execData.isExecPersistent = persistent;
|
||||
m_execData.token = token;
|
||||
m_execData.pid = pid;
|
||||
m_execData.expiresAt = Time::steadyNow() + std::chrono::minutes(1);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@
|
|||
#include "../../helpers/time/Time.hpp"
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
namespace Desktop::Rule {
|
||||
|
|
@ -31,6 +30,7 @@ namespace Desktop::Rule {
|
|||
RULE_PROP_XDG_TAG = (1 << 16),
|
||||
RULE_PROP_NAMESPACE = (1 << 17),
|
||||
RULE_PROP_EXEC_TOKEN = (1 << 18),
|
||||
RULE_PROP_EXEC_PID = (1 << 19),
|
||||
|
||||
RULE_PROP_ALL = std::numeric_limits<std::underlying_type_t<eRuleProperty>>::max(),
|
||||
};
|
||||
|
|
@ -52,7 +52,7 @@ namespace Desktop::Rule {
|
|||
virtual std::underlying_type_t<eRuleProperty> getPropertiesMask();
|
||||
|
||||
void registerMatch(eRuleProperty, const std::string&);
|
||||
void markAsExecRule(const std::string& token, bool persistent = false);
|
||||
void markAsExecRule(const std::string& token, uint64_t pid, bool persistent = false);
|
||||
bool isExecRule();
|
||||
bool isExecPersistent();
|
||||
bool execExpired();
|
||||
|
|
@ -78,7 +78,8 @@ namespace Desktop::Rule {
|
|||
bool isExecRule = false;
|
||||
bool isExecPersistent = false;
|
||||
std::string token;
|
||||
uint64_t pid = 0;
|
||||
Time::steady_tp expiresAt;
|
||||
} m_execData;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "../../view/LayerSurface.hpp"
|
||||
#include "../../types/OverridableVar.hpp"
|
||||
#include "../../../helpers/MiscFunctions.hpp"
|
||||
#include "../../../event/EventBus.hpp"
|
||||
|
||||
using namespace Desktop;
|
||||
using namespace Desktop::Rule;
|
||||
|
|
@ -32,11 +33,38 @@ void CLayerRuleApplicator::resetProps(std::underlying_type_t<eRuleProperty> prop
|
|||
UNSET(aboveLock)
|
||||
UNSET(ignoreAlpha)
|
||||
UNSET(animationStyle)
|
||||
|
||||
#undef UNSET
|
||||
|
||||
if (prio == Types::PRIORITY_WINDOW_RULE)
|
||||
std::erase_if(m_otherProps.props, [props](const auto& el) { return !el.second || el.second->propMask & props; });
|
||||
}
|
||||
|
||||
void CLayerRuleApplicator::applyDynamicRule(const SP<CLayerRule>& rule) {
|
||||
for (const auto& [key, effect] : rule->effects()) {
|
||||
switch (key) {
|
||||
default: {
|
||||
if (key <= LAYER_RULE_EFFECT_LAST_STATIC) {
|
||||
Log::logger->log(Log::TRACE, "CLayerRuleApplicator::applyDynamicRule: Skipping effect {}, not dynamic", sc<std::underlying_type_t<eLayerRuleEffect>>(key));
|
||||
break;
|
||||
}
|
||||
|
||||
// custom type, add to our vec
|
||||
if (!m_otherProps.props.contains(key)) {
|
||||
m_otherProps.props.emplace(key,
|
||||
makeUnique<SCustomPropContainer>(SCustomPropContainer{
|
||||
.idx = key,
|
||||
.propMask = rule->getPropertiesMask(),
|
||||
.effect = effect,
|
||||
}));
|
||||
} else {
|
||||
auto& e = m_otherProps.props[key];
|
||||
e->propMask |= rule->getPropertiesMask();
|
||||
e->effect = effect;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case LAYER_RULE_EFFECT_NONE: {
|
||||
Log::logger->log(Log::ERR, "CLayerRuleApplicator::applyDynamicRule: BUG THIS: LAYER_RULE_EFFECT_NONE??");
|
||||
break;
|
||||
|
|
@ -73,8 +101,8 @@ void CLayerRuleApplicator::applyDynamicRule(const SP<CLayerRule>& rule) {
|
|||
}
|
||||
case LAYER_RULE_EFFECT_ORDER: {
|
||||
try {
|
||||
m_noScreenShare.first.set(std::stoi(effect), Types::PRIORITY_WINDOW_RULE);
|
||||
m_noScreenShare.second |= rule->getPropertiesMask();
|
||||
m_order.first.set(std::stoi(effect), Types::PRIORITY_WINDOW_RULE);
|
||||
m_order.second |= rule->getPropertiesMask();
|
||||
} catch (...) { Log::logger->log(Log::ERR, "CLayerRuleApplicator::applyDynamicRule: invalid order {}", effect); }
|
||||
break;
|
||||
}
|
||||
|
|
@ -125,4 +153,7 @@ void CLayerRuleApplicator::propertiesChanged(std::underlying_type_t<eRulePropert
|
|||
|
||||
applyDynamicRule(wr);
|
||||
}
|
||||
|
||||
// for plugins
|
||||
Event::bus()->m_events.layer.updateRules.emit(m_ls.lock());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "LayerRuleEffectContainer.hpp"
|
||||
#include "../../DesktopTypes.hpp"
|
||||
#include "../Rule.hpp"
|
||||
#include "../../types/OverridableVar.hpp"
|
||||
|
|
@ -21,6 +22,17 @@ namespace Desktop::Rule {
|
|||
void propertiesChanged(std::underlying_type_t<eRuleProperty> props);
|
||||
void resetProps(std::underlying_type_t<eRuleProperty> props, Types::eOverridePriority prio = Types::PRIORITY_WINDOW_RULE);
|
||||
|
||||
struct SCustomPropContainer {
|
||||
CLayerRuleEffectContainer::storageType idx = LAYER_RULE_EFFECT_NONE;
|
||||
std::underlying_type_t<eRuleProperty> propMask = RULE_PROP_NONE;
|
||||
std::string effect;
|
||||
};
|
||||
|
||||
// This struct holds props that were dynamically registered. Plugins may read this.
|
||||
struct {
|
||||
std::unordered_map<CLayerRuleEffectContainer::storageType, UP<SCustomPropContainer>> props;
|
||||
} m_otherProps;
|
||||
|
||||
#define COMMA ,
|
||||
#define DEFINE_PROP(type, name, def) \
|
||||
private: \
|
||||
|
|
|
|||
|
|
@ -8,5 +8,5 @@ CWorkspaceMatchEngine::CWorkspaceMatchEngine(const std::string& s) : m_value(s)
|
|||
}
|
||||
|
||||
bool CWorkspaceMatchEngine::match(PHLWORKSPACE ws) {
|
||||
return ws->matchesStaticSelector(m_value);
|
||||
return ws && ws->matchesStaticSelector(m_value);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ bool CWindowRule::matches(PHLWINDOW w, bool allowEnvLookup) {
|
|||
return false;
|
||||
break;
|
||||
case RULE_PROP_GROUP:
|
||||
if (!engine->match(w->m_groupData.pNextWindow))
|
||||
if (!engine->match(!!w->m_group))
|
||||
return false;
|
||||
break;
|
||||
case RULE_PROP_MODAL:
|
||||
|
|
@ -97,27 +97,31 @@ bool CWindowRule::matches(PHLWINDOW w, bool allowEnvLookup) {
|
|||
return false;
|
||||
break;
|
||||
case RULE_PROP_CONTENT:
|
||||
if (!engine->match(NContentType::toString(w->getContentType())))
|
||||
if (!engine->match(w->getContentType()) && !engine->match(NContentType::toString(w->getContentType())))
|
||||
return false;
|
||||
break;
|
||||
case RULE_PROP_XDG_TAG:
|
||||
if (!w->xdgTag().has_value() || !engine->match(*w->xdgTag()))
|
||||
return false;
|
||||
break;
|
||||
|
||||
case RULE_PROP_EXEC_TOKEN:
|
||||
// this is only allowed on static rules, we don't need it on dynamic plus it's expensive
|
||||
if (!allowEnvLookup)
|
||||
break;
|
||||
|
||||
const auto ENV = w->getEnv();
|
||||
if (ENV.contains(EXEC_RULE_ENV_NAME)) {
|
||||
const auto TKN = ENV.at(EXEC_RULE_ENV_NAME);
|
||||
if (!engine->match(TKN))
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
const auto ENV = w->getEnv();
|
||||
bool match = false;
|
||||
|
||||
return false;
|
||||
if (ENV.contains(EXEC_RULE_ENV_NAME)) {
|
||||
if (engine->match(ENV.at(EXEC_RULE_ENV_NAME)))
|
||||
match = true;
|
||||
} else if (m_matchEngines.contains(RULE_PROP_EXEC_PID)) {
|
||||
if (m_matchEngines.at(RULE_PROP_EXEC_PID)->match(w->getPID()))
|
||||
match = true;
|
||||
}
|
||||
if (!match)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -153,11 +157,6 @@ SP<CWindowRule> CWindowRule::buildFromExecString(std::string&& s) {
|
|||
wr->addEffect(*EFFECT, std::string{"1"});
|
||||
}
|
||||
|
||||
const auto TOKEN = g_pTokenManager->registerNewToken(nullptr, std::chrono::seconds(1));
|
||||
|
||||
wr->markAsExecRule(TOKEN, false /* TODO: could be nice. */);
|
||||
wr->registerMatch(RULE_PROP_EXEC_TOKEN, TOKEN);
|
||||
|
||||
return wr;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,7 @@
|
|||
#include "../utils/SetUtils.hpp"
|
||||
#include "../../view/Window.hpp"
|
||||
#include "../../types/OverridableVar.hpp"
|
||||
#include "../../../managers/LayoutManager.hpp"
|
||||
#include "../../../managers/HookSystemManager.hpp"
|
||||
#include "../../../event/EventBus.hpp"
|
||||
|
||||
#include <hyprutils/string/String.hpp>
|
||||
|
||||
|
|
@ -151,7 +150,7 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const
|
|||
CGradientValueData activeBorderGradient = {};
|
||||
CGradientValueData inactiveBorderGradient = {};
|
||||
bool active = true;
|
||||
CVarList colorsAndAngles = CVarList(trim(effect.substr(effect.find_first_of(' ') + 1)), 0, 's', true);
|
||||
CVarList colorsAndAngles = CVarList(trim(effect), 0, 's', true);
|
||||
|
||||
// Basic form has only two colors, everything else can be parsed as a gradient
|
||||
if (colorsAndAngles.size() == 2 && !colorsAndAngles[1].contains("deg")) {
|
||||
|
|
@ -159,6 +158,8 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const
|
|||
Types::COverridableVar(CGradientValueData(CHyprColor(configStringToInt(colorsAndAngles[0]).value_or(0))), Types::PRIORITY_WINDOW_RULE);
|
||||
m_inactiveBorderColor.first =
|
||||
Types::COverridableVar(CGradientValueData(CHyprColor(configStringToInt(colorsAndAngles[1]).value_or(0))), Types::PRIORITY_WINDOW_RULE);
|
||||
m_activeBorderColor.second |= rule->getPropertiesMask();
|
||||
m_inactiveBorderColor.second |= rule->getPropertiesMask();
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -265,13 +266,17 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const
|
|||
if (!m_window)
|
||||
break;
|
||||
|
||||
const auto VEC = configStringToVector2D(effect);
|
||||
if (VEC.x < 1 || VEC.y < 1) {
|
||||
const auto VEC = m_window->calculateExpression(effect);
|
||||
if (!VEC) {
|
||||
Log::logger->log(Log::ERR, "failed to parse {} as an expression", effect);
|
||||
break;
|
||||
}
|
||||
if (VEC->x < 1 || VEC->y < 1) {
|
||||
Log::logger->log(Log::ERR, "Invalid size for maxsize");
|
||||
break;
|
||||
}
|
||||
|
||||
m_maxSize.first = Types::COverridableVar(VEC, Types::PRIORITY_WINDOW_RULE);
|
||||
m_maxSize.first = Types::COverridableVar(*VEC, Types::PRIORITY_WINDOW_RULE);
|
||||
|
||||
if (*PCLAMP_TILED || m_window->m_isFloating)
|
||||
m_window->clampWindowSize(std::nullopt, m_maxSize.first.value());
|
||||
|
|
@ -286,13 +291,18 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const
|
|||
if (!m_window)
|
||||
break;
|
||||
|
||||
const auto VEC = configStringToVector2D(effect);
|
||||
if (VEC.x < 1 || VEC.y < 1) {
|
||||
const auto VEC = m_window->calculateExpression(effect);
|
||||
if (!VEC) {
|
||||
Log::logger->log(Log::ERR, "failed to parse {} as an expression", effect);
|
||||
break;
|
||||
}
|
||||
|
||||
if (VEC->x < 1 || VEC->y < 1) {
|
||||
Log::logger->log(Log::ERR, "Invalid size for maxsize");
|
||||
break;
|
||||
}
|
||||
|
||||
m_minSize.first = Types::COverridableVar(VEC, Types::PRIORITY_WINDOW_RULE);
|
||||
m_minSize.first = Types::COverridableVar(*VEC, Types::PRIORITY_WINDOW_RULE);
|
||||
if (*PCLAMP_TILED || m_window->m_isFloating)
|
||||
m_window->clampWindowSize(m_minSize.first.value(), std::nullopt);
|
||||
} catch (std::exception& e) { Log::logger->log(Log::ERR, "minsize rule \"{}\" failed with: {}", effect, e.what()); }
|
||||
|
|
@ -537,7 +547,7 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyStaticRule(const
|
|||
return SRuleResult{};
|
||||
}
|
||||
|
||||
void CWindowRuleApplicator::readStaticRules() {
|
||||
void CWindowRuleApplicator::readStaticRules(bool preRead) {
|
||||
if (!m_window)
|
||||
return;
|
||||
|
||||
|
|
@ -592,7 +602,8 @@ void CWindowRuleApplicator::readStaticRules() {
|
|||
for (const auto& wr : execRules) {
|
||||
applyStaticRule(wr);
|
||||
applyDynamicRule(wr);
|
||||
ruleEngine()->unregisterRule(wr);
|
||||
if (!preRead)
|
||||
ruleEngine()->unregisterRule(wr);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -619,11 +630,13 @@ void CWindowRuleApplicator::propertiesChanged(std::underlying_type_t<eRuleProper
|
|||
needsRelayout = needsRelayout || RES.needsRelayout;
|
||||
}
|
||||
|
||||
m_window->updateWindowData();
|
||||
m_window->updateWindowDecos();
|
||||
m_window->updateDecorationValues();
|
||||
|
||||
if (needsRelayout)
|
||||
g_pDecorationPositioner->forceRecalcFor(m_window.lock());
|
||||
|
||||
// for plugins
|
||||
EMIT_HOOK_EVENT("windowUpdateRules", m_window.lock());
|
||||
Event::bus()->m_events.window.updateRules.emit(m_window.lock());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,8 +33,7 @@ namespace Desktop::Rule {
|
|||
void propertiesChanged(std::underlying_type_t<eRuleProperty> props);
|
||||
std::unordered_set<CWindowRuleEffectContainer::storageType> resetProps(std::underlying_type_t<eRuleProperty> props,
|
||||
Types::eOverridePriority prio = Types::PRIORITY_WINDOW_RULE);
|
||||
void readStaticRules();
|
||||
void applyStaticRules();
|
||||
void readStaticRules(bool preRead = false);
|
||||
|
||||
// static props
|
||||
struct {
|
||||
|
|
|
|||
|
|
@ -3,14 +3,19 @@
|
|||
#include "../../Compositor.hpp"
|
||||
#include "../../protocols/XDGShell.hpp"
|
||||
#include "../../render/Renderer.hpp"
|
||||
#include "../../managers/LayoutManager.hpp"
|
||||
#include "../../managers/EventManager.hpp"
|
||||
#include "../../managers/HookSystemManager.hpp"
|
||||
#include "../../managers/input/InputManager.hpp"
|
||||
#include "../../managers/SeatManager.hpp"
|
||||
#include "../../xwayland/XSurface.hpp"
|
||||
#include "../../protocols/PointerConstraints.hpp"
|
||||
#include "managers/animation/DesktopAnimationManager.hpp"
|
||||
#include "../../layout/LayoutManager.hpp"
|
||||
#include "../../event/EventBus.hpp"
|
||||
|
||||
using namespace Desktop;
|
||||
|
||||
#define COMMA ,
|
||||
|
||||
SP<CFocusState> Desktop::focusState() {
|
||||
static SP<CFocusState> state = makeShared<CFocusState>();
|
||||
return state;
|
||||
|
|
@ -32,6 +37,7 @@ static SFullscreenWorkspaceFocusResult onFullscreenWorkspaceFocusWindow(PHLWINDO
|
|||
if (pWindow->m_isFloating) {
|
||||
// if the window is floating, just bring it to the top
|
||||
pWindow->m_createdOverFullscreen = true;
|
||||
g_pDesktopAnimationManager->setFullscreenFloatingFade(pWindow, 1.f);
|
||||
g_pHyprRenderer->damageWindow(pWindow);
|
||||
return {};
|
||||
}
|
||||
|
|
@ -61,7 +67,7 @@ static SFullscreenWorkspaceFocusResult onFullscreenWorkspaceFocusWindow(PHLWINDO
|
|||
return {};
|
||||
}
|
||||
|
||||
void CFocusState::fullWindowFocus(PHLWINDOW pWindow, SP<CWLSurfaceResource> surface, bool forceFSCycle) {
|
||||
void CFocusState::fullWindowFocus(PHLWINDOW pWindow, eFocusReason reason, SP<CWLSurfaceResource> surface, bool forceFSCycle) {
|
||||
if (pWindow) {
|
||||
if (!pWindow->m_workspace)
|
||||
return;
|
||||
|
|
@ -81,10 +87,10 @@ void CFocusState::fullWindowFocus(PHLWINDOW pWindow, SP<CWLSurfaceResource> surf
|
|||
return;
|
||||
}
|
||||
|
||||
rawWindowFocus(pWindow, surface);
|
||||
rawWindowFocus(pWindow, reason, surface);
|
||||
}
|
||||
|
||||
void CFocusState::rawWindowFocus(PHLWINDOW pWindow, SP<CWLSurfaceResource> surface) {
|
||||
void CFocusState::rawWindowFocus(PHLWINDOW pWindow, eFocusReason reason, SP<CWLSurfaceResource> surface) {
|
||||
static auto PFOLLOWMOUSE = CConfigValue<Hyprlang::INT>("input:follow_mouse");
|
||||
static auto PSPECIALFALLTHROUGH = CConfigValue<Hyprlang::INT>("input:special_fallthrough");
|
||||
|
||||
|
|
@ -103,7 +109,9 @@ void CFocusState::rawWindowFocus(PHLWINDOW pWindow, SP<CWLSurfaceResource> surfa
|
|||
if (pWindow && pWindow->m_isX11 && pWindow->isX11OverrideRedirect() && !pWindow->m_xwaylandSurface->wantsFocus())
|
||||
return;
|
||||
|
||||
g_pLayoutManager->getCurrentLayout()->bringWindowToTop(pWindow);
|
||||
// m_target on purpose, this avoids the group
|
||||
if (pWindow)
|
||||
g_layoutManager->bringTargetToTop(pWindow->m_target);
|
||||
|
||||
if (!pWindow || !validMapped(pWindow)) {
|
||||
|
||||
|
|
@ -125,9 +133,7 @@ void CFocusState::rawWindowFocus(PHLWINDOW pWindow, SP<CWLSurfaceResource> surfa
|
|||
g_pEventManager->postEvent(SHyprIPCEvent{"activewindow", ","});
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{"activewindowv2", ""});
|
||||
|
||||
EMIT_HOOK_EVENT("activeWindow", PHLWINDOW{nullptr});
|
||||
|
||||
g_pLayoutManager->getCurrentLayout()->onWindowFocusChange(nullptr);
|
||||
Event::bus()->m_events.window.active.emit(nullptr, reason);
|
||||
|
||||
m_focusSurface.reset();
|
||||
|
||||
|
|
@ -194,16 +200,14 @@ void CFocusState::rawWindowFocus(PHLWINDOW pWindow, SP<CWLSurfaceResource> surfa
|
|||
g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindow", .data = pWindow->m_class + "," + pWindow->m_title});
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindowv2", .data = std::format("{:x}", rc<uintptr_t>(pWindow.get()))});
|
||||
|
||||
EMIT_HOOK_EVENT("activeWindow", pWindow);
|
||||
|
||||
g_pLayoutManager->getCurrentLayout()->onWindowFocusChange(pWindow);
|
||||
Event::bus()->m_events.window.active.emit(pWindow, reason);
|
||||
|
||||
g_pInputManager->recheckIdleInhibitorStatus();
|
||||
|
||||
if (*PFOLLOWMOUSE == 0)
|
||||
g_pInputManager->sendMotionEventsToFocused();
|
||||
|
||||
if (pWindow->m_groupData.pNextWindow)
|
||||
if (pWindow->m_group)
|
||||
pWindow->deactivateGroupMembers();
|
||||
}
|
||||
|
||||
|
|
@ -229,7 +233,7 @@ void CFocusState::rawSurfaceFocus(SP<CWLSurfaceResource> pSurface, PHLWINDOW pWi
|
|||
g_pSeatManager->setKeyboardFocus(nullptr);
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindow", .data = ","});
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindowv2", .data = ""});
|
||||
EMIT_HOOK_EVENT("keyboardFocus", SP<CWLSurfaceResource>{nullptr});
|
||||
Event::bus()->m_events.input.keyboard.focus.emit(nullptr);
|
||||
m_focusSurface.reset();
|
||||
return;
|
||||
}
|
||||
|
|
@ -245,7 +249,7 @@ void CFocusState::rawSurfaceFocus(SP<CWLSurfaceResource> pSurface, PHLWINDOW pWi
|
|||
g_pXWaylandManager->activateSurface(pSurface, true);
|
||||
m_focusSurface = pSurface;
|
||||
|
||||
EMIT_HOOK_EVENT("keyboardFocus", pSurface);
|
||||
Event::bus()->m_events.input.keyboard.focus.emit(pSurface);
|
||||
|
||||
const auto SURF = Desktop::View::CWLSurface::fromResource(pSurface);
|
||||
const auto OLDSURF = Desktop::View::CWLSurface::fromResource(PLASTSURF);
|
||||
|
|
@ -274,7 +278,7 @@ void CFocusState::rawMonitorFocus(PHLMONITOR pMonitor) {
|
|||
g_pEventManager->postEvent(SHyprIPCEvent{.event = "focusedmon", .data = pMonitor->m_name + "," + WORKSPACE_NAME});
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{.event = "focusedmonv2", .data = pMonitor->m_name + "," + WORKSPACE_ID});
|
||||
|
||||
EMIT_HOOK_EVENT("focusedMon", pMonitor);
|
||||
Event::bus()->m_events.monitor.focused.emit(pMonitor);
|
||||
m_focusMonitor = pMonitor;
|
||||
}
|
||||
|
||||
|
|
@ -294,3 +298,7 @@ void CFocusState::resetWindowFocus() {
|
|||
m_focusWindow.reset();
|
||||
m_focusSurface.reset();
|
||||
}
|
||||
|
||||
bool Desktop::isHardInputFocusReason(eFocusReason r) {
|
||||
return r == FOCUS_REASON_NEW_WINDOW || r == FOCUS_REASON_KEYBIND || r == FOCUS_REASON_GHOSTS || r == FOCUS_REASON_CLICK || r == FOCUS_REASON_DESKTOP_STATE_CHANGE;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include "../DesktopTypes.hpp"
|
||||
#include "../../SharedDefs.hpp"
|
||||
#include "../../helpers/signal/Signal.hpp"
|
||||
|
||||
class CWLSurfaceResource;
|
||||
|
||||
namespace Desktop {
|
||||
enum eFocusReason : uint8_t {
|
||||
FOCUS_REASON_UNKNOWN = 0,
|
||||
FOCUS_REASON_FFM,
|
||||
FOCUS_REASON_KEYBIND,
|
||||
FOCUS_REASON_CLICK,
|
||||
FOCUS_REASON_OTHER,
|
||||
FOCUS_REASON_DESKTOP_STATE_CHANGE,
|
||||
FOCUS_REASON_NEW_WINDOW,
|
||||
FOCUS_REASON_GHOSTS,
|
||||
};
|
||||
|
||||
bool isHardInputFocusReason(eFocusReason r);
|
||||
|
||||
class CFocusState {
|
||||
public:
|
||||
CFocusState();
|
||||
|
|
@ -15,8 +28,8 @@ namespace Desktop {
|
|||
CFocusState(CFocusState&) = delete;
|
||||
CFocusState(const CFocusState&) = delete;
|
||||
|
||||
void fullWindowFocus(PHLWINDOW w, SP<CWLSurfaceResource> surface = nullptr, bool forceFSCycle = false);
|
||||
void rawWindowFocus(PHLWINDOW w, SP<CWLSurfaceResource> surface = nullptr);
|
||||
void fullWindowFocus(PHLWINDOW w, eFocusReason reason, SP<CWLSurfaceResource> surface = nullptr, bool forceFSCycle = false);
|
||||
void rawWindowFocus(PHLWINDOW w, eFocusReason reason, SP<CWLSurfaceResource> surface = nullptr);
|
||||
void rawSurfaceFocus(SP<CWLSurfaceResource> s, PHLWINDOW pWindowOwner = nullptr);
|
||||
void rawMonitorFocus(PHLMONITOR m);
|
||||
|
||||
|
|
@ -31,7 +44,7 @@ namespace Desktop {
|
|||
PHLWINDOWREF m_focusWindow;
|
||||
PHLMONITORREF m_focusMonitor;
|
||||
|
||||
SP<HOOK_CALLBACK_FN> m_windowOpen, m_windowClose;
|
||||
CHyprSignalListener m_windowOpen, m_windowClose;
|
||||
};
|
||||
|
||||
SP<CFocusState> focusState();
|
||||
|
|
|
|||
351
src/desktop/view/Group.cpp
Normal file
351
src/desktop/view/Group.cpp
Normal file
|
|
@ -0,0 +1,351 @@
|
|||
#include "Group.hpp"
|
||||
#include "Window.hpp"
|
||||
|
||||
#include "../../render/decorations/CHyprGroupBarDecoration.hpp"
|
||||
#include "../../layout/target/WindowGroupTarget.hpp"
|
||||
#include "../../layout/target/WindowTarget.hpp"
|
||||
#include "../../layout/target/Target.hpp"
|
||||
#include "../../layout/space/Space.hpp"
|
||||
#include "../../layout/LayoutManager.hpp"
|
||||
#include "../../desktop/state/FocusState.hpp"
|
||||
#include "../../Compositor.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace Desktop;
|
||||
using namespace Desktop::View;
|
||||
|
||||
std::vector<WP<CGroup>>& View::groups() {
|
||||
static std::vector<WP<CGroup>> g;
|
||||
return g;
|
||||
}
|
||||
|
||||
SP<CGroup> CGroup::create(std::vector<PHLWINDOWREF>&& windows) {
|
||||
auto x = SP<CGroup>(new CGroup(std::move(windows)));
|
||||
x->m_self = x;
|
||||
x->m_target = Layout::CWindowGroupTarget::create(x);
|
||||
groups().emplace_back(x);
|
||||
|
||||
x->init();
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
CGroup::CGroup(std::vector<PHLWINDOWREF>&& windows) : m_windows(std::move(windows)) {
|
||||
;
|
||||
}
|
||||
|
||||
void CGroup::init() {
|
||||
// for proper group logic:
|
||||
// - add all windows to us
|
||||
// - replace the first window with our target
|
||||
// - remove all window targets from layout
|
||||
// - apply updates
|
||||
|
||||
// FIXME: what if some windows are grouped? For now we only do 1-window but YNK
|
||||
for (const auto& w : m_windows) {
|
||||
RASSERT(!w->m_group, "CGroup: windows cannot contain grouped in init, this will explode");
|
||||
w->m_group = m_self.lock();
|
||||
}
|
||||
|
||||
g_layoutManager->switchTargets(m_windows.at(0)->m_target, m_target);
|
||||
|
||||
for (const auto& w : m_windows) {
|
||||
w->m_target->setSpaceGhost(m_target->space());
|
||||
}
|
||||
|
||||
for (const auto& w : m_windows) {
|
||||
applyWindowDecosAndUpdates(w.lock());
|
||||
}
|
||||
|
||||
updateWindowVisibility();
|
||||
}
|
||||
|
||||
void CGroup::destroy() {
|
||||
while (true) {
|
||||
if (m_windows.size() == 1) {
|
||||
remove(m_windows.at(0).lock());
|
||||
break;
|
||||
}
|
||||
|
||||
remove(m_windows.at(0).lock());
|
||||
}
|
||||
}
|
||||
|
||||
CGroup::~CGroup() {
|
||||
if (m_target->space())
|
||||
m_target->assignToSpace(nullptr);
|
||||
std::erase_if(groups(), [this](const auto& e) { return !e || e == m_self; });
|
||||
}
|
||||
|
||||
bool CGroup::has(PHLWINDOW w) const {
|
||||
return std::ranges::contains(m_windows, w);
|
||||
}
|
||||
|
||||
void CGroup::add(PHLWINDOW w) {
|
||||
static auto INSERT_AFTER_CURRENT = CConfigValue<Hyprlang::INT>("group:insert_after_current");
|
||||
|
||||
if (w->m_group) {
|
||||
if (w->m_group == m_self)
|
||||
return;
|
||||
|
||||
const auto WINDOWS = w->m_group->windows();
|
||||
for (const auto& w : WINDOWS) {
|
||||
w->m_group->remove(w.lock());
|
||||
add(w.lock());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (w->layoutTarget()->space()) {
|
||||
// remove the target from a space if it is in one
|
||||
g_layoutManager->removeTarget(w->layoutTarget());
|
||||
}
|
||||
|
||||
w->m_group = m_self.lock();
|
||||
w->m_target->setSpaceGhost(m_target->space());
|
||||
w->m_target->setFloating(m_target->floating());
|
||||
|
||||
if (*INSERT_AFTER_CURRENT) {
|
||||
m_windows.insert(m_windows.begin() + m_current + 1, w);
|
||||
m_current++;
|
||||
} else {
|
||||
m_windows.emplace_back(w);
|
||||
m_current = m_windows.size() - 1;
|
||||
}
|
||||
|
||||
applyWindowDecosAndUpdates(w);
|
||||
updateWindowVisibility();
|
||||
m_target->recalc();
|
||||
}
|
||||
|
||||
void CGroup::remove(PHLWINDOW w, Math::eDirection dir) {
|
||||
std::optional<size_t> idx;
|
||||
for (size_t i = 0; i < m_windows.size(); ++i) {
|
||||
if (m_windows.at(i) == w) {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!idx)
|
||||
return;
|
||||
|
||||
if ((m_current >= *idx && idx != 0) || (m_current >= m_windows.size() - 1 && m_current > 0))
|
||||
m_current--;
|
||||
|
||||
auto g = m_self.lock(); // keep ref to avoid uaf after w->m_group.reset()
|
||||
|
||||
w->m_group.reset();
|
||||
removeWindowDecos(w);
|
||||
|
||||
w->setHidden(false);
|
||||
|
||||
const bool REMOVING_GROUP = m_windows.size() <= 1;
|
||||
|
||||
if (REMOVING_GROUP) {
|
||||
w->m_target->assignToSpace(nullptr);
|
||||
g_layoutManager->switchTargets(m_target, w->m_target);
|
||||
}
|
||||
|
||||
// we do it after the above because switchTargets expects this to be a valid group
|
||||
m_windows.erase(m_windows.begin() + *idx);
|
||||
|
||||
if (!m_windows.empty())
|
||||
updateWindowVisibility();
|
||||
|
||||
// do this here: otherwise the new current is hidden and workspace rules get wrong data
|
||||
if (!REMOVING_GROUP) {
|
||||
std::optional<Vector2D> focalPoint;
|
||||
if (dir != Math::DIRECTION_DEFAULT) {
|
||||
const auto box = m_target->position();
|
||||
switch (dir) {
|
||||
case Math::DIRECTION_RIGHT: focalPoint = Vector2D(box.x + box.w, box.y + box.h / 2.0); break;
|
||||
case Math::DIRECTION_LEFT: focalPoint = Vector2D(box.x, box.y + box.h / 2.0); break;
|
||||
case Math::DIRECTION_DOWN: focalPoint = Vector2D(box.x + box.w / 2.0, box.y + box.h); break;
|
||||
case Math::DIRECTION_UP: focalPoint = Vector2D(box.x + box.w / 2.0, box.y); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
w->m_target->assignToSpace(m_target->space(), focalPoint);
|
||||
}
|
||||
}
|
||||
|
||||
void CGroup::moveCurrent(bool next) {
|
||||
size_t idx = m_current;
|
||||
|
||||
if (next) {
|
||||
idx++;
|
||||
if (idx >= m_windows.size())
|
||||
idx = 0;
|
||||
} else {
|
||||
if (idx == 0)
|
||||
idx = m_windows.size() - 1;
|
||||
else
|
||||
idx--;
|
||||
}
|
||||
|
||||
setCurrent(idx);
|
||||
}
|
||||
|
||||
void CGroup::setCurrent(size_t idx) {
|
||||
if (idx == m_current)
|
||||
return;
|
||||
|
||||
const auto FS_STATE = m_target->fullscreenMode();
|
||||
const auto WASFOCUS = Desktop::focusState()->window() == current();
|
||||
auto oldWindow = m_windows.at(m_current).lock();
|
||||
|
||||
if (FS_STATE != FSMODE_NONE)
|
||||
g_pCompositor->setWindowFullscreenInternal(oldWindow, FSMODE_NONE);
|
||||
|
||||
m_current = std::clamp(idx, sc<size_t>(0), m_windows.size() - 1);
|
||||
updateWindowVisibility();
|
||||
|
||||
auto newWindow = m_windows.at(m_current).lock();
|
||||
|
||||
if (FS_STATE != FSMODE_NONE) {
|
||||
g_pCompositor->setWindowFullscreenInternal(newWindow, FS_STATE);
|
||||
newWindow->m_target->warpPositionSize();
|
||||
oldWindow->m_target->setPositionGlobal(newWindow->m_target->position()); // TODO: this is a hack and sucks
|
||||
}
|
||||
|
||||
if (WASFOCUS)
|
||||
Desktop::focusState()->rawWindowFocus(current(), FOCUS_REASON_DESKTOP_STATE_CHANGE);
|
||||
}
|
||||
|
||||
void CGroup::setCurrent(PHLWINDOW w) {
|
||||
if (w == current())
|
||||
return;
|
||||
|
||||
for (size_t i = 0; i < m_windows.size(); ++i) {
|
||||
if (m_windows.at(i) == w) {
|
||||
setCurrent(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t CGroup::getCurrentIdx() const {
|
||||
return m_current;
|
||||
}
|
||||
|
||||
PHLWINDOW CGroup::head() const {
|
||||
return m_windows.front().lock();
|
||||
}
|
||||
|
||||
PHLWINDOW CGroup::tail() const {
|
||||
return m_windows.back().lock();
|
||||
}
|
||||
|
||||
PHLWINDOW CGroup::current() const {
|
||||
return m_windows.at(m_current).lock();
|
||||
}
|
||||
|
||||
PHLWINDOW CGroup::next() const {
|
||||
return (m_current >= m_windows.size() - 1 ? m_windows.front() : m_windows.at(m_current + 1)).lock();
|
||||
}
|
||||
|
||||
PHLWINDOW CGroup::fromIndex(size_t idx) const {
|
||||
if (idx >= m_windows.size())
|
||||
return nullptr;
|
||||
|
||||
return m_windows.at(idx).lock();
|
||||
}
|
||||
|
||||
const std::vector<PHLWINDOWREF>& CGroup::windows() const {
|
||||
return m_windows;
|
||||
}
|
||||
|
||||
void CGroup::applyWindowDecosAndUpdates(PHLWINDOW x) {
|
||||
x->addWindowDeco(makeUnique<CHyprGroupBarDecoration>(x));
|
||||
|
||||
x->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
|
||||
x->updateWindowDecos();
|
||||
x->updateDecorationValues();
|
||||
}
|
||||
|
||||
void CGroup::removeWindowDecos(PHLWINDOW x) {
|
||||
x->removeWindowDeco(x->getDecorationByType(DECORATION_GROUPBAR));
|
||||
|
||||
x->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
|
||||
x->updateWindowDecos();
|
||||
x->updateDecorationValues();
|
||||
}
|
||||
|
||||
void CGroup::updateWindowVisibility() {
|
||||
for (size_t i = 0; i < m_windows.size(); ++i) {
|
||||
if (i == m_current) {
|
||||
auto& x = m_windows.at(i);
|
||||
x->setHidden(false);
|
||||
x->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
|
||||
x->updateWindowDecos();
|
||||
x->updateDecorationValues();
|
||||
} else
|
||||
m_windows.at(i)->setHidden(true);
|
||||
}
|
||||
|
||||
m_target->recalc();
|
||||
|
||||
m_target->damageEntire();
|
||||
}
|
||||
|
||||
size_t CGroup::size() const {
|
||||
return m_windows.size();
|
||||
}
|
||||
|
||||
bool CGroup::locked() const {
|
||||
return m_locked;
|
||||
}
|
||||
|
||||
void CGroup::setLocked(bool x) {
|
||||
m_locked = x;
|
||||
}
|
||||
|
||||
bool CGroup::denied() const {
|
||||
return m_deny;
|
||||
}
|
||||
|
||||
void CGroup::setDenied(bool x) {
|
||||
m_deny = x;
|
||||
}
|
||||
|
||||
void CGroup::updateWorkspace(PHLWORKSPACE ws) {
|
||||
if (!ws)
|
||||
return;
|
||||
|
||||
for (const auto& w : windows()) {
|
||||
w->m_monitor = ws->m_monitor;
|
||||
w->moveToWorkspace(ws);
|
||||
w->updateToplevel();
|
||||
w->updateWindowDecos();
|
||||
w->m_target->setSpaceGhost(ws->m_space);
|
||||
}
|
||||
}
|
||||
|
||||
void CGroup::swapWithNext() {
|
||||
const bool HAD_FOCUS = Desktop::focusState()->window() == m_windows.at(m_current);
|
||||
|
||||
size_t idx = m_current + 1 >= m_windows.size() ? 0 : m_current + 1;
|
||||
std::iter_swap(m_windows.begin() + m_current, m_windows.begin() + idx);
|
||||
m_current = idx;
|
||||
|
||||
updateWindowVisibility();
|
||||
|
||||
if (HAD_FOCUS)
|
||||
Desktop::focusState()->fullWindowFocus(m_windows.at(m_current).lock(), FOCUS_REASON_DESKTOP_STATE_CHANGE);
|
||||
}
|
||||
|
||||
void CGroup::swapWithLast() {
|
||||
const bool HAD_FOCUS = Desktop::focusState()->window() == m_windows.at(m_current);
|
||||
|
||||
size_t idx = m_current == 0 ? m_windows.size() - 1 : m_current - 1;
|
||||
std::iter_swap(m_windows.begin() + m_current, m_windows.begin() + idx);
|
||||
m_current = idx;
|
||||
|
||||
updateWindowVisibility();
|
||||
|
||||
if (HAD_FOCUS)
|
||||
Desktop::focusState()->fullWindowFocus(m_windows.at(m_current).lock(), FOCUS_REASON_DESKTOP_STATE_CHANGE);
|
||||
}
|
||||
69
src/desktop/view/Group.hpp
Normal file
69
src/desktop/view/Group.hpp
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#pragma once
|
||||
|
||||
#include "../DesktopTypes.hpp"
|
||||
#include "../../helpers/math/Direction.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Layout {
|
||||
class CWindowGroupTarget;
|
||||
};
|
||||
|
||||
namespace Desktop::View {
|
||||
class CGroup {
|
||||
public:
|
||||
static SP<CGroup> create(std::vector<PHLWINDOWREF>&& windows);
|
||||
~CGroup();
|
||||
|
||||
bool has(PHLWINDOW w) const;
|
||||
|
||||
void add(PHLWINDOW w);
|
||||
void remove(PHLWINDOW w, Math::eDirection dir = Math::DIRECTION_DEFAULT);
|
||||
void moveCurrent(bool next);
|
||||
void setCurrent(size_t idx);
|
||||
void setCurrent(PHLWINDOW w);
|
||||
size_t getCurrentIdx() const;
|
||||
size_t size() const;
|
||||
void destroy();
|
||||
void updateWorkspace(PHLWORKSPACE);
|
||||
|
||||
void swapWithNext();
|
||||
void swapWithLast();
|
||||
|
||||
PHLWINDOW head() const;
|
||||
PHLWINDOW tail() const;
|
||||
PHLWINDOW current() const;
|
||||
PHLWINDOW next() const;
|
||||
|
||||
PHLWINDOW fromIndex(size_t idx) const;
|
||||
|
||||
bool locked() const;
|
||||
void setLocked(bool x);
|
||||
|
||||
bool denied() const;
|
||||
void setDenied(bool x);
|
||||
|
||||
const std::vector<PHLWINDOWREF>& windows() const;
|
||||
|
||||
SP<Layout::CWindowGroupTarget> m_target;
|
||||
|
||||
private:
|
||||
CGroup(std::vector<PHLWINDOWREF>&& windows);
|
||||
|
||||
void applyWindowDecosAndUpdates(PHLWINDOW x);
|
||||
void removeWindowDecos(PHLWINDOW x);
|
||||
void init();
|
||||
void updateWindowVisibility();
|
||||
|
||||
WP<CGroup> m_self;
|
||||
|
||||
std::vector<PHLWINDOWREF> m_windows;
|
||||
|
||||
bool m_locked = false;
|
||||
bool m_deny = false;
|
||||
|
||||
size_t m_current = 0;
|
||||
};
|
||||
|
||||
std::vector<WP<CGroup>>& groups();
|
||||
};
|
||||
|
|
@ -10,8 +10,8 @@
|
|||
#include "../../config/ConfigManager.hpp"
|
||||
#include "../../helpers/Monitor.hpp"
|
||||
#include "../../managers/input/InputManager.hpp"
|
||||
#include "../../managers/HookSystemManager.hpp"
|
||||
#include "../../managers/EventManager.hpp"
|
||||
#include "../../event/EventBus.hpp"
|
||||
|
||||
using namespace Desktop;
|
||||
using namespace Desktop::View;
|
||||
|
|
@ -23,24 +23,11 @@ PHLLS CLayerSurface::create(SP<CLayerShellResource> resource) {
|
|||
|
||||
pLS->m_wlSurface->assign(resource->m_surface.lock(), pLS);
|
||||
|
||||
if (!pMonitor) {
|
||||
Log::logger->log(Log::ERR, "New LS has no monitor??");
|
||||
return pLS;
|
||||
}
|
||||
|
||||
if (pMonitor->m_mirrorOf)
|
||||
pMonitor = g_pCompositor->m_monitors.front();
|
||||
|
||||
pLS->m_self = pLS;
|
||||
|
||||
pLS->m_namespace = resource->m_layerNamespace;
|
||||
|
||||
pLS->m_layer = resource->m_current.layer;
|
||||
pLS->m_popupHead = CPopup::create(pLS);
|
||||
pLS->m_monitor = pMonitor;
|
||||
pMonitor->m_layerSurfaceLayers[resource->m_current.layer].emplace_back(pLS);
|
||||
|
||||
pLS->m_ruleApplicator = makeUnique<Desktop::Rule::CLayerRuleApplicator>(pLS);
|
||||
pLS->m_self = pLS;
|
||||
pLS->m_namespace = resource->m_layerNamespace;
|
||||
pLS->m_layer = std::clamp(resource->m_current.layer, ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY);
|
||||
pLS->m_popupHead = CPopup::create(pLS);
|
||||
|
||||
g_pAnimationManager->createAnimation(0.f, pLS->m_alpha, g_pConfigManager->getAnimationPropertyConfig("fadeLayersIn"), pLS, AVARDAMAGE_ENTIRE);
|
||||
g_pAnimationManager->createAnimation(Vector2D(0, 0), pLS->m_realPosition, g_pConfigManager->getAnimationPropertyConfig("layersIn"), pLS, AVARDAMAGE_ENTIRE);
|
||||
|
|
@ -50,6 +37,19 @@ PHLLS CLayerSurface::create(SP<CLayerShellResource> resource) {
|
|||
|
||||
pLS->m_alpha->setValueAndWarp(0.f);
|
||||
|
||||
if (!pMonitor) {
|
||||
Log::logger->log(Log::DEBUG, "LayerSurface {:x} (namespace {} layer {}) created on NO MONITOR ?!", rc<uintptr_t>(resource.get()), resource->m_layerNamespace,
|
||||
sc<int>(pLS->m_layer));
|
||||
|
||||
return pLS;
|
||||
}
|
||||
|
||||
if (pMonitor->m_mirrorOf)
|
||||
pMonitor = g_pCompositor->m_monitors.front();
|
||||
|
||||
pLS->m_monitor = pMonitor;
|
||||
pMonitor->m_layerSurfaceLayers[resource->m_current.layer].emplace_back(pLS);
|
||||
|
||||
Log::logger->log(Log::DEBUG, "LayerSurface {:x} (namespace {} layer {}) created on monitor {}", rc<uintptr_t>(resource.get()), resource->m_layerNamespace,
|
||||
sc<int>(pLS->m_layer), pMonitor->m_name);
|
||||
|
||||
|
|
@ -222,7 +222,7 @@ void CLayerSurface::onMap() {
|
|||
m_fadingOut = false;
|
||||
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{.event = "openlayer", .data = m_namespace});
|
||||
EMIT_HOOK_EVENT("openLayer", m_self.lock());
|
||||
Event::bus()->m_events.layer.opened.emit(m_self.lock());
|
||||
|
||||
g_pCompositor->setPreferredScaleForSurface(m_wlSurface->resource(), PMONITOR->m_scale);
|
||||
g_pCompositor->setPreferredTransformForSurface(m_wlSurface->resource(), PMONITOR->m_transform);
|
||||
|
|
@ -232,7 +232,7 @@ void CLayerSurface::onUnmap() {
|
|||
Log::logger->log(Log::DEBUG, "LayerSurface {:x} unmapped", rc<uintptr_t>(m_layerSurface.get()));
|
||||
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{.event = "closelayer", .data = m_layerSurface->m_layerNamespace});
|
||||
EMIT_HOOK_EVENT("closeLayer", m_self.lock());
|
||||
Event::bus()->m_events.layer.closed.emit(m_self.lock());
|
||||
|
||||
std::erase_if(g_pInputManager->m_exclusiveLSes, [this](const auto& other) { return !other || other == m_self; });
|
||||
|
||||
|
|
@ -323,16 +323,18 @@ void CLayerSurface::onCommit() {
|
|||
if (m_layerSurface->m_current.committed != 0) {
|
||||
if (m_layerSurface->m_current.committed & CLayerShellResource::eCommittedState::STATE_LAYER && m_layerSurface->m_current.layer != m_layer) {
|
||||
|
||||
const auto NEW_LAYER = std::clamp(m_layerSurface->m_current.layer, ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY);
|
||||
|
||||
for (auto it = PMONITOR->m_layerSurfaceLayers[m_layer].begin(); it != PMONITOR->m_layerSurfaceLayers[m_layer].end(); it++) {
|
||||
if (*it == m_self) {
|
||||
PMONITOR->m_layerSurfaceLayers[m_layerSurface->m_current.layer].emplace_back(*it);
|
||||
PMONITOR->m_layerSurfaceLayers[NEW_LAYER].emplace_back(*it);
|
||||
PMONITOR->m_layerSurfaceLayers[m_layer].erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_layer = m_layerSurface->m_current.layer;
|
||||
m_aboveFullscreen = m_layerSurface->m_current.layer >= ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY;
|
||||
m_layer = NEW_LAYER;
|
||||
m_aboveFullscreen = NEW_LAYER >= ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY;
|
||||
|
||||
// if in fullscreen, only overlay can be above.
|
||||
*m_alpha = PMONITOR->inFullscreenMode() ? (m_layer >= ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY ? 1.F : 0.F) : 1.F;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#include "../../managers/animation/AnimationManager.hpp"
|
||||
#include "LayerSurface.hpp"
|
||||
#include "../../managers/input/InputManager.hpp"
|
||||
#include "../../managers/eventLoop/EventLoopManager.hpp"
|
||||
#include "../../render/Renderer.hpp"
|
||||
#include "../../render/OpenGL.hpp"
|
||||
#include <ranges>
|
||||
|
|
@ -108,8 +109,12 @@ void CPopup::initAllSignals() {
|
|||
m_alpha->setCallbackOnEnd(
|
||||
[this](auto) {
|
||||
if (inert()) {
|
||||
g_pHyprRenderer->damageBox(CBox{coordsGlobal(), size()});
|
||||
fullyDestroy();
|
||||
g_pEventLoopManager->doLater([p = m_self] {
|
||||
if (!p)
|
||||
return;
|
||||
g_pHyprRenderer->damageBox(CBox{p->coordsGlobal(), p->size()});
|
||||
p->fullyDestroy();
|
||||
});
|
||||
}
|
||||
},
|
||||
false);
|
||||
|
|
@ -194,7 +199,7 @@ void CPopup::onMap() {
|
|||
|
||||
//unconstrain();
|
||||
sendScale();
|
||||
m_resource->m_surface->m_surface->enter(PMONITOR->m_self.lock());
|
||||
m_wlSurface->resource()->breadthfirst([PMONITOR](SP<CWLSurfaceResource> s, const Vector2D& offset, void* d) { s->enter(PMONITOR->m_self.lock()); }, nullptr);
|
||||
|
||||
if (!m_layerOwner.expired() && m_layerOwner->m_layer < ZWLR_LAYER_SHELL_V1_LAYER_TOP)
|
||||
g_pHyprOpenGL->markBlurDirtyForMonitor(g_pCompositor->getMonitorFromID(m_layerOwner->m_layer));
|
||||
|
|
@ -334,8 +339,7 @@ void CPopup::reposition() {
|
|||
if (!PMONITOR)
|
||||
return;
|
||||
|
||||
CBox box = {PMONITOR->m_position.x, PMONITOR->m_position.y, PMONITOR->m_size.x, PMONITOR->m_size.y};
|
||||
m_resource->applyPositioning(box, COORDS);
|
||||
m_resource->applyPositioning(m_windowOwner ? PMONITOR->logicalBoxMinusReserved() : PMONITOR->logicalBox(), COORDS);
|
||||
}
|
||||
|
||||
SP<Desktop::View::CWLSurface> CPopup::getT1Owner() const {
|
||||
|
|
@ -399,10 +403,14 @@ void CPopup::recheckChildrenRecursive() {
|
|||
std::vector<WP<CPopup>> cpy;
|
||||
std::ranges::for_each(m_children, [&cpy](const auto& el) { cpy.emplace_back(el); });
|
||||
for (auto const& c : cpy) {
|
||||
if (!c->visible())
|
||||
if (!c || !c->visible())
|
||||
continue;
|
||||
c->onCommit(true);
|
||||
c->recheckChildrenRecursive();
|
||||
|
||||
// keep ref, onCommit can call onDestroy
|
||||
auto x = c.lock();
|
||||
|
||||
x->onCommit(true);
|
||||
x->recheckChildrenRecursive();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -26,8 +26,19 @@ struct SWorkspaceRule;
|
|||
|
||||
class IWindowTransformer;
|
||||
|
||||
namespace Layout {
|
||||
class ITarget;
|
||||
class CWindowTarget;
|
||||
}
|
||||
|
||||
namespace Desktop {
|
||||
enum eFocusReason : uint8_t;
|
||||
}
|
||||
|
||||
namespace Desktop::View {
|
||||
|
||||
class CGroup;
|
||||
|
||||
enum eGroupRules : uint8_t {
|
||||
// effective only during first map, except for _ALWAYS variant
|
||||
GROUP_NONE = 0,
|
||||
|
|
@ -38,6 +49,7 @@ namespace Desktop::View {
|
|||
GROUP_LOCK_ALWAYS = 1 << 4,
|
||||
GROUP_INVADE = 1 << 5, // Force enter a group, event if lock is engaged
|
||||
GROUP_OVERRIDE = 1 << 6, // Override other rules
|
||||
GROUP_DENY = 1 << 7, // deny
|
||||
};
|
||||
|
||||
enum eGetWindowProperties : uint8_t {
|
||||
|
|
@ -61,6 +73,11 @@ namespace Desktop::View {
|
|||
SUPPRESS_FULLSCREEN_OUTPUT = 1 << 4,
|
||||
};
|
||||
|
||||
struct SWindowActiveEvent {
|
||||
PHLWINDOW window = nullptr;
|
||||
eFocusReason reason = sc<eFocusReason>(0) /* unknown */;
|
||||
};
|
||||
|
||||
struct SInitialWorkspaceToken {
|
||||
PHLWINDOWREF primaryOwner;
|
||||
std::string workspace;
|
||||
|
|
@ -92,11 +109,17 @@ namespace Desktop::View {
|
|||
|
||||
struct {
|
||||
CSignalT<> destroy;
|
||||
CSignalT<> unmap;
|
||||
CSignalT<> hide;
|
||||
CSignalT<> resize;
|
||||
CSignalT<> monitorChanged;
|
||||
} m_events;
|
||||
|
||||
WP<CXDGSurfaceResource> m_xdgSurface;
|
||||
WP<CXWaylandSurface> m_xwaylandSurface;
|
||||
|
||||
SP<Layout::ITarget> m_target;
|
||||
|
||||
// this is the position and size of the "bounding box"
|
||||
Vector2D m_position = Vector2D(0, 0);
|
||||
Vector2D m_size = Vector2D(0, 0);
|
||||
|
|
@ -112,30 +135,21 @@ namespace Desktop::View {
|
|||
std::optional<std::pair<uint32_t, Vector2D>> m_pendingSizeAck;
|
||||
std::vector<std::pair<uint32_t, Vector2D>> m_pendingSizeAcks;
|
||||
|
||||
// for restoring floating statuses
|
||||
Vector2D m_lastFloatingSize;
|
||||
Vector2D m_lastFloatingPosition;
|
||||
|
||||
// for floating window offset in workspace animations
|
||||
Vector2D m_floatingOffset = Vector2D(0, 0);
|
||||
|
||||
// this is used for pseudotiling
|
||||
bool m_isPseudotiled = false;
|
||||
Vector2D m_pseudoSize = Vector2D(1280, 720);
|
||||
|
||||
// for recovering relative cursor position
|
||||
Vector2D m_relativeCursorCoordsOnLastWarp = Vector2D(-1, -1);
|
||||
|
||||
bool m_firstMap = false; // for layouts
|
||||
bool m_isFloating = false;
|
||||
bool m_draggingTiled = false; // for dragging around tiled windows
|
||||
SFullscreenState m_fullscreenState = {.internal = FSMODE_NONE, .client = FSMODE_NONE};
|
||||
std::string m_title = "";
|
||||
std::string m_class = "";
|
||||
std::string m_initialTitle = "";
|
||||
std::string m_initialClass = "";
|
||||
PHLWORKSPACE m_workspace;
|
||||
PHLMONITORREF m_monitor;
|
||||
PHLMONITORREF m_monitor, m_prevMonitor;
|
||||
|
||||
bool m_isMapped = false;
|
||||
|
||||
|
|
@ -229,15 +243,13 @@ namespace Desktop::View {
|
|||
std::string m_initialWorkspaceToken = "";
|
||||
|
||||
// for groups
|
||||
struct SGroupData {
|
||||
PHLWINDOWREF pNextWindow; // nullptr means no grouping. Self means single group.
|
||||
bool head = false;
|
||||
bool locked = false; // per group lock
|
||||
bool deny = false; // deny window from enter a group or made a group
|
||||
} m_groupData;
|
||||
uint16_t m_groupRules = Desktop::View::GROUP_NONE;
|
||||
SP<CGroup> m_group;
|
||||
uint16_t m_groupRules = Desktop::View::GROUP_NONE;
|
||||
|
||||
bool m_tearingHint = false;
|
||||
bool m_tearingHint = false;
|
||||
|
||||
// Stable ID for ext_foreign_toplevel_list
|
||||
const uint64_t m_stableID = 0x2137;
|
||||
|
||||
// ANR
|
||||
PHLANIMVAR<float> m_notRespondingTint;
|
||||
|
|
@ -300,21 +312,6 @@ namespace Desktop::View {
|
|||
bool isInCurvedCorner(double x, double y);
|
||||
bool hasPopupAt(const Vector2D& pos);
|
||||
int popupsCount();
|
||||
void applyGroupRules();
|
||||
void createGroup();
|
||||
void destroyGroup();
|
||||
PHLWINDOW getGroupHead();
|
||||
PHLWINDOW getGroupTail();
|
||||
PHLWINDOW getGroupCurrent();
|
||||
PHLWINDOW getGroupPrevious();
|
||||
PHLWINDOW getGroupWindowByIndex(int);
|
||||
bool hasInGroup(PHLWINDOW);
|
||||
int getGroupSize();
|
||||
bool canBeGroupedInto(PHLWINDOW pWindow);
|
||||
void setGroupCurrent(PHLWINDOW pWindow);
|
||||
void insertWindowToGroup(PHLWINDOW pWindow);
|
||||
void updateGroupOutputs();
|
||||
void switchWithWindowInGroup(PHLWINDOW pWindow);
|
||||
void setAnimationsToMove();
|
||||
void onWorkspaceAnimUpdate();
|
||||
void onFocusAnimUpdate();
|
||||
|
|
@ -328,8 +325,6 @@ namespace Desktop::View {
|
|||
PHLWINDOW getSwallower();
|
||||
bool isX11OverrideRedirect();
|
||||
bool isModal();
|
||||
Vector2D requestedMinSize();
|
||||
Vector2D requestedMaxSize();
|
||||
Vector2D realToReportSize();
|
||||
Vector2D realToReportPosition();
|
||||
Vector2D xwaylandSizeToReal(Vector2D size);
|
||||
|
|
@ -349,6 +344,8 @@ namespace Desktop::View {
|
|||
std::optional<Vector2D> calculateExpression(const std::string& s);
|
||||
std::optional<Vector2D> minSize();
|
||||
std::optional<Vector2D> maxSize();
|
||||
SP<Layout::ITarget> layoutTarget();
|
||||
bool canBeGroupedInto(SP<CGroup> group);
|
||||
|
||||
CBox getWindowMainSurfaceBox() const {
|
||||
return {m_realPosition->value().x, m_realPosition->value().y, m_realSize->value().x, m_realSize->value().y};
|
||||
|
|
|
|||
8
src/event/EventBus.cpp
Normal file
8
src/event/EventBus.cpp
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#include "EventBus.hpp"
|
||||
|
||||
using namespace Event;
|
||||
|
||||
UP<CEventBus>& Event::bus() {
|
||||
static UP<CEventBus> p = makeUnique<CEventBus>();
|
||||
return p;
|
||||
}
|
||||
144
src/event/EventBus.hpp
Normal file
144
src/event/EventBus.hpp
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
#pragma once
|
||||
|
||||
#include "../helpers/memory/Memory.hpp"
|
||||
#include "../helpers/signal/Signal.hpp"
|
||||
#include "../helpers/math/Math.hpp"
|
||||
|
||||
#include "../devices/IPointer.hpp"
|
||||
#include "../devices/IKeyboard.hpp"
|
||||
#include "../devices/Tablet.hpp"
|
||||
#include "../devices/ITouch.hpp"
|
||||
|
||||
#include "../desktop/DesktopTypes.hpp"
|
||||
|
||||
#include "../SharedDefs.hpp"
|
||||
|
||||
namespace Desktop {
|
||||
enum eFocusReason : uint8_t;
|
||||
}
|
||||
namespace Event {
|
||||
struct SCallbackInfo {
|
||||
bool cancelled = false; /* on cancellable events, will cancel the event. */
|
||||
};
|
||||
|
||||
class CEventBus {
|
||||
public:
|
||||
CEventBus() = default;
|
||||
~CEventBus() = default;
|
||||
|
||||
template <typename... Args>
|
||||
using Event = CSignalT<Args...>;
|
||||
|
||||
template <typename... Args>
|
||||
using Cancellable = CSignalT<Args..., SCallbackInfo&>;
|
||||
|
||||
struct {
|
||||
Event<> ready;
|
||||
Event<> tick;
|
||||
|
||||
struct {
|
||||
Event<PHLWINDOW> open;
|
||||
Event<PHLWINDOW> openEarly;
|
||||
Event<PHLWINDOW> destroy;
|
||||
Event<PHLWINDOW> close;
|
||||
Event<PHLWINDOW> kill;
|
||||
Event<PHLWINDOW, Desktop::eFocusReason> active;
|
||||
Event<PHLWINDOW> urgent;
|
||||
Event<PHLWINDOW> title;
|
||||
Event<PHLWINDOW> class_;
|
||||
Event<PHLWINDOW> pin;
|
||||
Event<PHLWINDOW> fullscreen;
|
||||
Event<PHLWINDOW> updateRules;
|
||||
Event<PHLWINDOW, PHLWORKSPACE> moveToWorkspace;
|
||||
} window;
|
||||
|
||||
struct {
|
||||
Event<PHLLS> opened;
|
||||
Event<PHLLS> closed;
|
||||
Event<PHLLS> updateRules;
|
||||
} layer;
|
||||
|
||||
struct {
|
||||
struct {
|
||||
Cancellable<Vector2D> move;
|
||||
Cancellable<IPointer::SButtonEvent> button;
|
||||
Cancellable<IPointer::SAxisEvent> axis;
|
||||
} mouse;
|
||||
|
||||
struct {
|
||||
Cancellable<IKeyboard::SKeyEvent> key;
|
||||
Event<SP<IKeyboard>, const std::string&> layout;
|
||||
Event<SP<CWLSurfaceResource>> focus;
|
||||
} keyboard;
|
||||
|
||||
struct {
|
||||
Cancellable<CTablet::SAxisEvent> axis;
|
||||
Cancellable<CTablet::SButtonEvent> button;
|
||||
Cancellable<CTablet::SProximityEvent> proximity;
|
||||
Cancellable<CTablet::STipEvent> tip;
|
||||
} tablet;
|
||||
|
||||
struct {
|
||||
Cancellable<ITouch::SCancelEvent> cancel;
|
||||
Cancellable<ITouch::SDownEvent> down;
|
||||
Cancellable<ITouch::SUpEvent> up;
|
||||
Cancellable<ITouch::SMotionEvent> motion;
|
||||
} touch;
|
||||
} input;
|
||||
|
||||
struct {
|
||||
Event<PHLMONITOR> pre;
|
||||
Event<eRenderStage> stage;
|
||||
} render;
|
||||
|
||||
struct {
|
||||
Event<bool /* state start/stop */, uint8_t /* eScreenshareType */, const std::string& /* name */> state;
|
||||
} screenshare;
|
||||
|
||||
struct {
|
||||
struct {
|
||||
Cancellable<IPointer::SSwipeBeginEvent> begin;
|
||||
Cancellable<IPointer::SSwipeEndEvent> end;
|
||||
Cancellable<IPointer::SSwipeUpdateEvent> update;
|
||||
} swipe;
|
||||
|
||||
struct {
|
||||
Cancellable<IPointer::SPinchBeginEvent> begin;
|
||||
Cancellable<IPointer::SPinchEndEvent> end;
|
||||
Cancellable<IPointer::SPinchUpdateEvent> update;
|
||||
} pinch;
|
||||
} gesture;
|
||||
|
||||
struct {
|
||||
Event<PHLMONITOR> newMon;
|
||||
Event<PHLMONITOR> preAdded;
|
||||
Event<PHLMONITOR> added;
|
||||
Event<PHLMONITOR> preRemoved;
|
||||
Event<PHLMONITOR> removed;
|
||||
Event<PHLMONITOR> preCommit;
|
||||
Event<PHLMONITOR> focused;
|
||||
|
||||
Event<> layoutChanged;
|
||||
} monitor;
|
||||
|
||||
struct {
|
||||
Event<PHLWORKSPACE, PHLMONITOR> moveToMonitor;
|
||||
Event<PHLWORKSPACE> active;
|
||||
Event<PHLWORKSPACEREF> created;
|
||||
Event<PHLWORKSPACEREF> removed;
|
||||
} workspace;
|
||||
|
||||
struct {
|
||||
Event<> preReload;
|
||||
Event<> reloaded;
|
||||
} config;
|
||||
|
||||
struct {
|
||||
Event<const std::string&> submap;
|
||||
} keybinds;
|
||||
|
||||
} m_events;
|
||||
};
|
||||
|
||||
UP<CEventBus>& bus();
|
||||
};
|
||||
|
|
@ -4,6 +4,8 @@
|
|||
#include <algorithm>
|
||||
#include <unistd.h>
|
||||
#include "../managers/eventLoop/EventLoopManager.hpp"
|
||||
#include "../desktop/rule/windowRule/WindowRule.hpp"
|
||||
#include "../desktop/rule/Engine.hpp"
|
||||
|
||||
using namespace Hyprutils::OS;
|
||||
|
||||
|
|
@ -119,6 +121,9 @@ SP<CPromise<std::string>> CAsyncDialogBox::open() {
|
|||
|
||||
m_selfReference = m_selfWeakReference.lock();
|
||||
|
||||
if (!m_execRuleToken.empty())
|
||||
proc.addEnv(Desktop::Rule::EXEC_RULE_ENV_NAME, m_execRuleToken);
|
||||
|
||||
if (!proc.runAsync()) {
|
||||
Log::logger->log(Log::ERR, "CAsyncDialogBox::open: failed to run async");
|
||||
wl_event_source_remove(m_readEventSource);
|
||||
|
|
@ -154,3 +159,9 @@ pid_t CAsyncDialogBox::getPID() const {
|
|||
SP<CAsyncDialogBox> CAsyncDialogBox::lockSelf() {
|
||||
return m_selfWeakReference.lock();
|
||||
}
|
||||
|
||||
void CAsyncDialogBox::setExecRule(std::string&& s) {
|
||||
auto rule = Desktop::Rule::CWindowRule::buildFromExecString(std::move(s));
|
||||
m_execRuleToken = rule->execToken();
|
||||
Desktop::Rule::ruleEngine()->registerRule(std::move(rule));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ class CAsyncDialogBox {
|
|||
void kill();
|
||||
bool isRunning() const;
|
||||
pid_t getPID() const;
|
||||
void setExecRule(std::string&& s);
|
||||
|
||||
SP<CAsyncDialogBox> lockSelf();
|
||||
|
||||
|
|
@ -41,7 +42,8 @@ class CAsyncDialogBox {
|
|||
pid_t m_dialogPid = 0;
|
||||
wl_event_source* m_readEventSource = nullptr;
|
||||
Hyprutils::OS::CFileDescriptor m_pipeReadFd;
|
||||
std::string m_stdout = "";
|
||||
std::string m_stdout = "";
|
||||
std::string m_execRuleToken = "";
|
||||
|
||||
const std::string m_title;
|
||||
const std::string m_description;
|
||||
|
|
|
|||
20
src/helpers/Drm.cpp
Normal file
20
src/helpers/Drm.cpp
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#include <xf86drm.h>
|
||||
#include "Drm.hpp"
|
||||
|
||||
bool DRM::sameGpu(int fd1, int fd2) {
|
||||
drmDevice* devA = nullptr;
|
||||
drmDevice* devB = nullptr;
|
||||
|
||||
if (drmGetDevice2(fd1, 0, &devA) != 0)
|
||||
return false;
|
||||
if (drmGetDevice2(fd2, 0, &devB) != 0) {
|
||||
drmFreeDevice(&devA);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool same = drmDevicesEqual(devA, devB);
|
||||
|
||||
drmFreeDevice(&devA);
|
||||
drmFreeDevice(&devB);
|
||||
return same;
|
||||
}
|
||||
5
src/helpers/Drm.hpp
Normal file
5
src/helpers/Drm.hpp
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
namespace DRM {
|
||||
bool sameGpu(int fd1, int fd2);
|
||||
}
|
||||
|
|
@ -6,158 +6,186 @@
|
|||
#include <xf86drm.h>
|
||||
#include <drm_fourcc.h>
|
||||
|
||||
/*
|
||||
DRM formats are LE, while OGL is BE. The two primary formats
|
||||
will be flipped, so we will set flipRB which will later use swizzle
|
||||
to flip the red and blue channels.
|
||||
This will not work on GLES2, but I want to drop support for it one day anyways.
|
||||
*/
|
||||
inline const std::vector<SPixelFormat> GLES3_FORMATS = {
|
||||
{
|
||||
.drmFormat = DRM_FORMAT_ARGB8888,
|
||||
.flipRB = true,
|
||||
.glFormat = GL_RGBA,
|
||||
.glType = GL_UNSIGNED_BYTE,
|
||||
.withAlpha = true,
|
||||
.alphaStripped = DRM_FORMAT_XRGB8888,
|
||||
.bytesPerBlock = 4,
|
||||
.drmFormat = DRM_FORMAT_ARGB8888,
|
||||
.glInternalFormat = GL_RGBA8,
|
||||
.glFormat = GL_RGBA,
|
||||
.glType = GL_UNSIGNED_BYTE,
|
||||
.withAlpha = true,
|
||||
.alphaStripped = DRM_FORMAT_XRGB8888,
|
||||
.bytesPerBlock = 4,
|
||||
.swizzle = {SWIZZLE_BGRA},
|
||||
},
|
||||
{
|
||||
.drmFormat = DRM_FORMAT_XRGB8888,
|
||||
.flipRB = true,
|
||||
.glFormat = GL_RGBA,
|
||||
.glType = GL_UNSIGNED_BYTE,
|
||||
.withAlpha = false,
|
||||
.alphaStripped = DRM_FORMAT_XRGB8888,
|
||||
.bytesPerBlock = 4,
|
||||
.drmFormat = DRM_FORMAT_XRGB8888,
|
||||
.glInternalFormat = GL_RGBA8,
|
||||
.glFormat = GL_RGBA,
|
||||
.glType = GL_UNSIGNED_BYTE,
|
||||
.withAlpha = false,
|
||||
.alphaStripped = DRM_FORMAT_XRGB8888,
|
||||
.bytesPerBlock = 4,
|
||||
.swizzle = {SWIZZLE_BGR1},
|
||||
},
|
||||
{
|
||||
.drmFormat = DRM_FORMAT_XBGR8888,
|
||||
.glFormat = GL_RGBA,
|
||||
.glType = GL_UNSIGNED_BYTE,
|
||||
.withAlpha = false,
|
||||
.alphaStripped = DRM_FORMAT_XBGR8888,
|
||||
.bytesPerBlock = 4,
|
||||
.drmFormat = DRM_FORMAT_XBGR8888,
|
||||
.glInternalFormat = GL_RGBA8,
|
||||
.glFormat = GL_RGBA,
|
||||
.glType = GL_UNSIGNED_BYTE,
|
||||
.withAlpha = false,
|
||||
.alphaStripped = DRM_FORMAT_XBGR8888,
|
||||
.bytesPerBlock = 4,
|
||||
.swizzle = {SWIZZLE_RGB1},
|
||||
},
|
||||
{
|
||||
.drmFormat = DRM_FORMAT_ABGR8888,
|
||||
.glFormat = GL_RGBA,
|
||||
.glType = GL_UNSIGNED_BYTE,
|
||||
.withAlpha = true,
|
||||
.alphaStripped = DRM_FORMAT_XBGR8888,
|
||||
.bytesPerBlock = 4,
|
||||
.drmFormat = DRM_FORMAT_ABGR8888,
|
||||
.glInternalFormat = GL_RGBA8,
|
||||
.glFormat = GL_RGBA,
|
||||
.glType = GL_UNSIGNED_BYTE,
|
||||
.withAlpha = true,
|
||||
.alphaStripped = DRM_FORMAT_XBGR8888,
|
||||
.bytesPerBlock = 4,
|
||||
.swizzle = {SWIZZLE_RGBA},
|
||||
},
|
||||
{
|
||||
.drmFormat = DRM_FORMAT_BGR888,
|
||||
.glFormat = GL_RGB,
|
||||
.glType = GL_UNSIGNED_BYTE,
|
||||
.withAlpha = false,
|
||||
.alphaStripped = DRM_FORMAT_BGR888,
|
||||
.bytesPerBlock = 3,
|
||||
.drmFormat = DRM_FORMAT_BGR888,
|
||||
.glInternalFormat = GL_RGB8,
|
||||
.glFormat = GL_RGB,
|
||||
.glType = GL_UNSIGNED_BYTE,
|
||||
.withAlpha = false,
|
||||
.alphaStripped = DRM_FORMAT_BGR888,
|
||||
.bytesPerBlock = 3,
|
||||
.swizzle = {SWIZZLE_RGB1},
|
||||
},
|
||||
{
|
||||
.drmFormat = DRM_FORMAT_RGBX4444,
|
||||
.glFormat = GL_RGBA,
|
||||
.glType = GL_UNSIGNED_SHORT_4_4_4_4,
|
||||
.withAlpha = false,
|
||||
.alphaStripped = DRM_FORMAT_RGBX4444,
|
||||
.bytesPerBlock = 2,
|
||||
.drmFormat = DRM_FORMAT_RGBX4444,
|
||||
.glInternalFormat = GL_RGBA4,
|
||||
.glFormat = GL_RGBA,
|
||||
.glType = GL_UNSIGNED_SHORT_4_4_4_4,
|
||||
.withAlpha = false,
|
||||
.alphaStripped = DRM_FORMAT_RGBX4444,
|
||||
.bytesPerBlock = 2,
|
||||
.swizzle = {SWIZZLE_RGB1},
|
||||
},
|
||||
{
|
||||
.drmFormat = DRM_FORMAT_RGBA4444,
|
||||
.glFormat = GL_RGBA,
|
||||
.glType = GL_UNSIGNED_SHORT_4_4_4_4,
|
||||
.withAlpha = true,
|
||||
.alphaStripped = DRM_FORMAT_RGBX4444,
|
||||
.bytesPerBlock = 2,
|
||||
.drmFormat = DRM_FORMAT_RGBA4444,
|
||||
.glInternalFormat = GL_RGBA4,
|
||||
.glFormat = GL_RGBA,
|
||||
.glType = GL_UNSIGNED_SHORT_4_4_4_4,
|
||||
.withAlpha = true,
|
||||
.alphaStripped = DRM_FORMAT_RGBX4444,
|
||||
.bytesPerBlock = 2,
|
||||
.swizzle = {SWIZZLE_RGBA},
|
||||
},
|
||||
{
|
||||
.drmFormat = DRM_FORMAT_RGBX5551,
|
||||
.glFormat = GL_RGBA,
|
||||
.glType = GL_UNSIGNED_SHORT_5_5_5_1,
|
||||
.withAlpha = false,
|
||||
.alphaStripped = DRM_FORMAT_RGBX5551,
|
||||
.bytesPerBlock = 2,
|
||||
.drmFormat = DRM_FORMAT_RGBX5551,
|
||||
.glInternalFormat = GL_RGB5_A1,
|
||||
.glFormat = GL_RGBA,
|
||||
.glType = GL_UNSIGNED_SHORT_5_5_5_1,
|
||||
.withAlpha = false,
|
||||
.alphaStripped = DRM_FORMAT_RGBX5551,
|
||||
.bytesPerBlock = 2,
|
||||
.swizzle = {SWIZZLE_RGB1},
|
||||
},
|
||||
{
|
||||
.drmFormat = DRM_FORMAT_RGBA5551,
|
||||
.glFormat = GL_RGBA,
|
||||
.glType = GL_UNSIGNED_SHORT_5_5_5_1,
|
||||
.withAlpha = true,
|
||||
.alphaStripped = DRM_FORMAT_RGBX5551,
|
||||
.bytesPerBlock = 2,
|
||||
.drmFormat = DRM_FORMAT_RGBA5551,
|
||||
.glInternalFormat = GL_RGB5_A1,
|
||||
.glFormat = GL_RGBA,
|
||||
.glType = GL_UNSIGNED_SHORT_5_5_5_1,
|
||||
.withAlpha = true,
|
||||
.alphaStripped = DRM_FORMAT_RGBX5551,
|
||||
.bytesPerBlock = 2,
|
||||
.swizzle = {SWIZZLE_RGBA},
|
||||
},
|
||||
{
|
||||
.drmFormat = DRM_FORMAT_RGB565,
|
||||
.glFormat = GL_RGB,
|
||||
.glType = GL_UNSIGNED_SHORT_5_6_5,
|
||||
.withAlpha = false,
|
||||
.alphaStripped = DRM_FORMAT_RGB565,
|
||||
.bytesPerBlock = 2,
|
||||
.drmFormat = DRM_FORMAT_RGB565,
|
||||
.glInternalFormat = GL_RGB565,
|
||||
.glFormat = GL_RGB,
|
||||
.glType = GL_UNSIGNED_SHORT_5_6_5,
|
||||
.withAlpha = false,
|
||||
.alphaStripped = DRM_FORMAT_RGB565,
|
||||
.bytesPerBlock = 2,
|
||||
.swizzle = {SWIZZLE_RGB1},
|
||||
},
|
||||
{
|
||||
.drmFormat = DRM_FORMAT_XBGR2101010,
|
||||
.glFormat = GL_RGBA,
|
||||
.glType = GL_UNSIGNED_INT_2_10_10_10_REV,
|
||||
.withAlpha = false,
|
||||
.alphaStripped = DRM_FORMAT_XBGR2101010,
|
||||
.bytesPerBlock = 4,
|
||||
.drmFormat = DRM_FORMAT_XBGR2101010,
|
||||
.glInternalFormat = GL_RGB10_A2,
|
||||
.glFormat = GL_RGBA,
|
||||
.glType = GL_UNSIGNED_INT_2_10_10_10_REV,
|
||||
.withAlpha = false,
|
||||
.alphaStripped = DRM_FORMAT_XBGR2101010,
|
||||
.bytesPerBlock = 4,
|
||||
.swizzle = {SWIZZLE_RGB1},
|
||||
},
|
||||
{
|
||||
.drmFormat = DRM_FORMAT_ABGR2101010,
|
||||
.glFormat = GL_RGBA,
|
||||
.glType = GL_UNSIGNED_INT_2_10_10_10_REV,
|
||||
.withAlpha = true,
|
||||
.alphaStripped = DRM_FORMAT_XBGR2101010,
|
||||
.bytesPerBlock = 4,
|
||||
.drmFormat = DRM_FORMAT_ABGR2101010,
|
||||
.glInternalFormat = GL_RGB10_A2,
|
||||
.glFormat = GL_RGBA,
|
||||
.glType = GL_UNSIGNED_INT_2_10_10_10_REV,
|
||||
.withAlpha = true,
|
||||
.alphaStripped = DRM_FORMAT_XBGR2101010,
|
||||
.bytesPerBlock = 4,
|
||||
.swizzle = {SWIZZLE_RGBA},
|
||||
},
|
||||
{
|
||||
.drmFormat = DRM_FORMAT_XRGB2101010,
|
||||
.glFormat = GL_RGBA,
|
||||
.glType = GL_UNSIGNED_INT_2_10_10_10_REV,
|
||||
.withAlpha = false,
|
||||
.alphaStripped = DRM_FORMAT_XRGB2101010,
|
||||
.bytesPerBlock = 4,
|
||||
.drmFormat = DRM_FORMAT_XRGB2101010,
|
||||
.glInternalFormat = GL_RGB10_A2,
|
||||
.glFormat = GL_RGBA,
|
||||
.glType = GL_UNSIGNED_INT_2_10_10_10_REV,
|
||||
.withAlpha = false,
|
||||
.alphaStripped = DRM_FORMAT_XRGB2101010,
|
||||
.bytesPerBlock = 4,
|
||||
.swizzle = {SWIZZLE_BGR1},
|
||||
},
|
||||
{
|
||||
.drmFormat = DRM_FORMAT_ARGB2101010,
|
||||
.glFormat = GL_RGBA,
|
||||
.glType = GL_UNSIGNED_INT_2_10_10_10_REV,
|
||||
.withAlpha = true,
|
||||
.alphaStripped = DRM_FORMAT_XRGB2101010,
|
||||
.bytesPerBlock = 4,
|
||||
.drmFormat = DRM_FORMAT_ARGB2101010,
|
||||
.glInternalFormat = GL_RGB10_A2,
|
||||
.glFormat = GL_RGBA,
|
||||
.glType = GL_UNSIGNED_INT_2_10_10_10_REV,
|
||||
.withAlpha = true,
|
||||
.alphaStripped = DRM_FORMAT_XRGB2101010,
|
||||
.bytesPerBlock = 4,
|
||||
.swizzle = {SWIZZLE_BGRA},
|
||||
},
|
||||
{
|
||||
.drmFormat = DRM_FORMAT_XBGR16161616F,
|
||||
.glFormat = GL_RGBA,
|
||||
.glType = GL_HALF_FLOAT,
|
||||
.withAlpha = false,
|
||||
.alphaStripped = DRM_FORMAT_XBGR16161616F,
|
||||
.bytesPerBlock = 8,
|
||||
.drmFormat = DRM_FORMAT_XBGR16161616F,
|
||||
.glInternalFormat = GL_RGBA16F,
|
||||
.glFormat = GL_RGBA,
|
||||
.glType = GL_HALF_FLOAT,
|
||||
.withAlpha = false,
|
||||
.alphaStripped = DRM_FORMAT_XBGR16161616F,
|
||||
.bytesPerBlock = 8,
|
||||
.swizzle = {SWIZZLE_RGB1},
|
||||
},
|
||||
{
|
||||
.drmFormat = DRM_FORMAT_ABGR16161616F,
|
||||
.glFormat = GL_RGBA,
|
||||
.glType = GL_HALF_FLOAT,
|
||||
.withAlpha = true,
|
||||
.alphaStripped = DRM_FORMAT_XBGR16161616F,
|
||||
.bytesPerBlock = 8,
|
||||
.drmFormat = DRM_FORMAT_ABGR16161616F,
|
||||
.glInternalFormat = GL_RGBA16F,
|
||||
.glFormat = GL_RGBA,
|
||||
.glType = GL_HALF_FLOAT,
|
||||
.withAlpha = true,
|
||||
.alphaStripped = DRM_FORMAT_XBGR16161616F,
|
||||
.bytesPerBlock = 8,
|
||||
.swizzle = {SWIZZLE_RGBA},
|
||||
},
|
||||
{
|
||||
.drmFormat = DRM_FORMAT_XBGR16161616,
|
||||
.glFormat = GL_RGBA16UI,
|
||||
.glType = GL_UNSIGNED_SHORT,
|
||||
.withAlpha = false,
|
||||
.alphaStripped = DRM_FORMAT_XBGR16161616,
|
||||
.bytesPerBlock = 8,
|
||||
.drmFormat = DRM_FORMAT_XBGR16161616,
|
||||
.glInternalFormat = GL_RGBA16UI,
|
||||
.glFormat = GL_RGBA_INTEGER,
|
||||
.glType = GL_UNSIGNED_SHORT,
|
||||
.withAlpha = false,
|
||||
.alphaStripped = DRM_FORMAT_XBGR16161616,
|
||||
.bytesPerBlock = 8,
|
||||
.swizzle = {SWIZZLE_RGBA},
|
||||
},
|
||||
{
|
||||
.drmFormat = DRM_FORMAT_ABGR16161616,
|
||||
.glFormat = GL_RGBA16UI,
|
||||
.glType = GL_UNSIGNED_SHORT,
|
||||
.withAlpha = true,
|
||||
.alphaStripped = DRM_FORMAT_XBGR16161616,
|
||||
.bytesPerBlock = 8,
|
||||
.drmFormat = DRM_FORMAT_ABGR16161616,
|
||||
.glInternalFormat = GL_RGBA16UI,
|
||||
.glFormat = GL_RGBA_INTEGER,
|
||||
.glType = GL_UNSIGNED_SHORT,
|
||||
.withAlpha = true,
|
||||
.alphaStripped = DRM_FORMAT_XBGR16161616,
|
||||
.bytesPerBlock = 8,
|
||||
.swizzle = {SWIZZLE_RGBA},
|
||||
},
|
||||
{
|
||||
.drmFormat = DRM_FORMAT_YVYU,
|
||||
|
|
@ -170,24 +198,28 @@ inline const std::vector<SPixelFormat> GLES3_FORMATS = {
|
|||
.blockSize = {2, 1},
|
||||
},
|
||||
{
|
||||
.drmFormat = DRM_FORMAT_R8,
|
||||
.bytesPerBlock = 1,
|
||||
.drmFormat = DRM_FORMAT_R8,
|
||||
.glInternalFormat = GL_R8,
|
||||
.glFormat = GL_RED,
|
||||
.glType = GL_UNSIGNED_BYTE,
|
||||
.bytesPerBlock = 1,
|
||||
.swizzle = {SWIZZLE_R001},
|
||||
},
|
||||
{
|
||||
.drmFormat = DRM_FORMAT_GR88,
|
||||
.bytesPerBlock = 2,
|
||||
.drmFormat = DRM_FORMAT_GR88,
|
||||
.glInternalFormat = GL_RG8,
|
||||
.glFormat = GL_RG,
|
||||
.glType = GL_UNSIGNED_BYTE,
|
||||
.bytesPerBlock = 2,
|
||||
.swizzle = {SWIZZLE_RG01},
|
||||
},
|
||||
{
|
||||
.drmFormat = DRM_FORMAT_RGB888,
|
||||
.bytesPerBlock = 3,
|
||||
},
|
||||
{
|
||||
.drmFormat = DRM_FORMAT_BGR888,
|
||||
.bytesPerBlock = 3,
|
||||
},
|
||||
{
|
||||
.drmFormat = DRM_FORMAT_RGBX4444,
|
||||
.bytesPerBlock = 2,
|
||||
.drmFormat = DRM_FORMAT_RGB888,
|
||||
.glInternalFormat = GL_RGB8,
|
||||
.glFormat = GL_RGB,
|
||||
.glType = GL_UNSIGNED_BYTE,
|
||||
.bytesPerBlock = 3,
|
||||
.swizzle = {SWIZZLE_BGR1},
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -229,6 +261,26 @@ const SPixelFormat* NFormatUtils::getPixelFormatFromGL(uint32_t glFormat, uint32
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
bool NFormatUtils::isFormatYUV(uint32_t drmFormat) {
|
||||
switch (drmFormat) {
|
||||
case DRM_FORMAT_YUYV:
|
||||
case DRM_FORMAT_YVYU:
|
||||
case DRM_FORMAT_UYVY:
|
||||
case DRM_FORMAT_VYUY:
|
||||
case DRM_FORMAT_AYUV:
|
||||
case DRM_FORMAT_NV12:
|
||||
case DRM_FORMAT_NV21:
|
||||
case DRM_FORMAT_NV16:
|
||||
case DRM_FORMAT_NV61:
|
||||
case DRM_FORMAT_YUV410:
|
||||
case DRM_FORMAT_YUV411:
|
||||
case DRM_FORMAT_YUV420:
|
||||
case DRM_FORMAT_YUV422:
|
||||
case DRM_FORMAT_YUV444: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool NFormatUtils::isFormatOpaque(DRMFormat drm) {
|
||||
const auto FMT = NFormatUtils::getPixelFormatFromDRM(drm);
|
||||
if (!FMT)
|
||||
|
|
@ -245,32 +297,38 @@ int NFormatUtils::minStride(const SPixelFormat* const fmt, int32_t width) {
|
|||
return std::ceil((width * fmt->bytesPerBlock) / pixelsPerBlock(fmt));
|
||||
}
|
||||
|
||||
uint32_t NFormatUtils::drmFormatToGL(DRMFormat drm) {
|
||||
switch (drm) {
|
||||
case DRM_FORMAT_XRGB8888:
|
||||
case DRM_FORMAT_XBGR8888: return GL_RGBA; // doesn't matter, opengl is gucci in this case.
|
||||
case DRM_FORMAT_XRGB2101010:
|
||||
case DRM_FORMAT_XBGR2101010: return GL_RGB10_A2;
|
||||
default: return GL_RGBA;
|
||||
}
|
||||
UNREACHABLE();
|
||||
return GL_RGBA;
|
||||
}
|
||||
|
||||
uint32_t NFormatUtils::glFormatToType(uint32_t gl) {
|
||||
return gl != GL_RGBA ? GL_UNSIGNED_INT_2_10_10_10_REV : GL_UNSIGNED_BYTE;
|
||||
}
|
||||
|
||||
std::string NFormatUtils::drmFormatName(DRMFormat drm) {
|
||||
auto n = drmGetFormatName(drm);
|
||||
auto n = drmGetFormatName(drm);
|
||||
|
||||
if (!n)
|
||||
return "unknown";
|
||||
|
||||
std::string name = n;
|
||||
free(n); // NOLINT(cppcoreguidelines-no-malloc,-warnings-as-errors)
|
||||
return name;
|
||||
}
|
||||
|
||||
std::string NFormatUtils::drmModifierName(uint64_t mod) {
|
||||
auto n = drmGetFormatModifierName(mod);
|
||||
auto n = drmGetFormatModifierName(mod);
|
||||
|
||||
if (!n)
|
||||
return "unknown";
|
||||
|
||||
std::string name = n;
|
||||
free(n); // NOLINT(cppcoreguidelines-no-malloc,-warnings-as-errors)
|
||||
return name;
|
||||
}
|
||||
|
||||
DRMFormat NFormatUtils::alphaFormat(DRMFormat prevFormat) {
|
||||
switch (prevFormat) {
|
||||
case DRM_FORMAT_XRGB8888: return DRM_FORMAT_ARGB8888;
|
||||
case DRM_FORMAT_XBGR8888: return DRM_FORMAT_ABGR8888;
|
||||
case DRM_FORMAT_BGRX8888: return DRM_FORMAT_BGRA8888;
|
||||
case DRM_FORMAT_RGBX8888: return DRM_FORMAT_RGBA8888;
|
||||
case DRM_FORMAT_XRGB2101010: return DRM_FORMAT_ARGB2101010;
|
||||
case DRM_FORMAT_XBGR2101010: return DRM_FORMAT_ABGR2101010;
|
||||
case DRM_FORMAT_RGBX1010102: return DRM_FORMAT_RGBA1010102;
|
||||
case DRM_FORMAT_BGRX1010102: return DRM_FORMAT_BGRA1010102;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,22 +2,43 @@
|
|||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <GLES3/gl32.h>
|
||||
#include "math/Math.hpp"
|
||||
#include <aquamarine/backend/Misc.hpp>
|
||||
|
||||
using DRMFormat = uint32_t;
|
||||
using SHMFormat = uint32_t;
|
||||
|
||||
#define SWIZZLE_A1GB {GL_ALPHA, GL_ONE, GL_GREEN, GL_BLUE}
|
||||
#define SWIZZLE_ABG1 {GL_ALPHA, GL_BLUE, GL_GREEN, GL_ONE}
|
||||
#define SWIZZLE_ABGR {GL_ALPHA, GL_BLUE, GL_GREEN, GL_RED}
|
||||
#define SWIZZLE_ARGB {GL_ALPHA, GL_RED, GL_GREEN, GL_BLUE}
|
||||
#define SWIZZLE_B1RG {GL_BLUE, GL_ONE, GL_RED, GL_GREEN}
|
||||
#define SWIZZLE_BARG {GL_BLUE, GL_ALPHA, GL_RED, GL_GREEN}
|
||||
#define SWIZZLE_BGR1 {GL_BLUE, GL_GREEN, GL_RED, GL_ONE}
|
||||
#define SWIZZLE_BGRA {GL_BLUE, GL_GREEN, GL_RED, GL_ALPHA}
|
||||
#define SWIZZLE_G1AB {GL_GREEN, GL_ONE, GL_ALPHA, GL_BLUE}
|
||||
#define SWIZZLE_GBA1 {GL_GREEN, GL_BLUE, GL_ALPHA, GL_ONE}
|
||||
#define SWIZZLE_GBAR {GL_GREEN, GL_BLUE, GL_ALPHA, GL_RED}
|
||||
#define SWIZZLE_GRAB {GL_GREEN, GL_RED, GL_ALPHA, GL_BLUE}
|
||||
#define SWIZZLE_R001 {GL_RED, GL_ZERO, GL_ZERO, GL_ONE}
|
||||
#define SWIZZLE_R1BG {GL_RED, GL_ONE, GL_BLUE, GL_GREEN}
|
||||
#define SWIZZLE_RABG {GL_RED, GL_ALPHA, GL_BLUE, GL_GREEN}
|
||||
#define SWIZZLE_RG01 {GL_RED, GL_GREEN, GL_ZERO, GL_ONE}
|
||||
#define SWIZZLE_GR01 {GL_GREEN, GL_RED, GL_ZERO, GL_ONE}
|
||||
#define SWIZZLE_RGB1 {GL_RED, GL_GREEN, GL_BLUE, GL_ONE}
|
||||
#define SWIZZLE_RGBA {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA}
|
||||
|
||||
struct SPixelFormat {
|
||||
DRMFormat drmFormat = 0; /* DRM_FORMAT_INVALID */
|
||||
bool flipRB = false;
|
||||
int glInternalFormat = 0;
|
||||
int glFormat = 0;
|
||||
int glType = 0;
|
||||
bool withAlpha = true;
|
||||
DRMFormat alphaStripped = 0; /* DRM_FORMAT_INVALID */
|
||||
uint32_t bytesPerBlock = 0;
|
||||
Vector2D blockSize;
|
||||
DRMFormat drmFormat = 0; /* DRM_FORMAT_INVALID */
|
||||
int glInternalFormat = 0;
|
||||
int glFormat = 0;
|
||||
int glType = 0;
|
||||
bool withAlpha = true;
|
||||
DRMFormat alphaStripped = 0; /* DRM_FORMAT_INVALID */
|
||||
uint32_t bytesPerBlock = 0;
|
||||
Vector2D blockSize;
|
||||
std::optional<std::array<GLint, 4>> swizzle = std::nullopt;
|
||||
};
|
||||
|
||||
using SDRMFormat = Aquamarine::SDRMFormat;
|
||||
|
|
@ -28,11 +49,11 @@ namespace NFormatUtils {
|
|||
|
||||
const SPixelFormat* getPixelFormatFromDRM(DRMFormat drm);
|
||||
const SPixelFormat* getPixelFormatFromGL(uint32_t glFormat, uint32_t glType, bool alpha);
|
||||
bool isFormatYUV(uint32_t drmFormat);
|
||||
bool isFormatOpaque(DRMFormat drm);
|
||||
int pixelsPerBlock(const SPixelFormat* const fmt);
|
||||
int minStride(const SPixelFormat* const fmt, int32_t width);
|
||||
uint32_t drmFormatToGL(DRMFormat drm);
|
||||
uint32_t glFormatToType(uint32_t gl);
|
||||
std::string drmFormatName(DRMFormat drm);
|
||||
std::string drmModifierName(uint64_t mod);
|
||||
DRMFormat alphaFormat(DRMFormat prevFormat);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,10 +11,7 @@ static int onDataRead(int fd, uint32_t mask, void* data) {
|
|||
CMainLoopExecutor::CMainLoopExecutor(std::function<void()>&& callback) : m_fn(std::move(callback)) {
|
||||
|
||||
int fds[2];
|
||||
pipe(fds);
|
||||
|
||||
RASSERT(fds[0] != 0, "CMainLoopExecutor: failed to open a pipe");
|
||||
RASSERT(fds[1] != 0, "CMainLoopExecutor: failed to open a pipe");
|
||||
RASSERT(pipe(fds) == 0, "CMainLoopExecutor: failed to open a pipe");
|
||||
|
||||
m_event = wl_event_loop_add_fd(g_pEventLoopManager->m_wayland.loop, fds[0], WL_EVENT_READABLE, ::onDataRead, this);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include "MiscFunctions.hpp"
|
||||
#include "../macros.hpp"
|
||||
#include "SharedDefs.hpp"
|
||||
#include "../helpers/TransferFunction.hpp"
|
||||
#include "math/Math.hpp"
|
||||
#include "../protocols/ColorManagement.hpp"
|
||||
#include "../Compositor.hpp"
|
||||
|
|
@ -22,16 +23,20 @@
|
|||
#include "../protocols/core/DataDevice.hpp"
|
||||
#include "../render/Renderer.hpp"
|
||||
#include "../managers/EventManager.hpp"
|
||||
#include "../managers/LayoutManager.hpp"
|
||||
#include "../managers/screenshare/ScreenshareManager.hpp"
|
||||
#include "../managers/animation/AnimationManager.hpp"
|
||||
#include "../managers/animation/DesktopAnimationManager.hpp"
|
||||
#include "../managers/input/InputManager.hpp"
|
||||
#include "../hyprerror/HyprError.hpp"
|
||||
#include "../layout/LayoutManager.hpp"
|
||||
#include "../i18n/Engine.hpp"
|
||||
#include "../helpers/cm/ColorManagement.hpp"
|
||||
#include "sync/SyncTimeline.hpp"
|
||||
#include "time/Time.hpp"
|
||||
#include "../desktop/view/LayerSurface.hpp"
|
||||
#include "../desktop/state/FocusState.hpp"
|
||||
#include "../event/EventBus.hpp"
|
||||
#include "Drm.hpp"
|
||||
#include <aquamarine/output/Output.hpp>
|
||||
#include "debug/log/Logger.hpp"
|
||||
#include "debug/HyprNotificationOverlay.hpp"
|
||||
|
|
@ -50,7 +55,7 @@ using namespace Hyprutils::OS;
|
|||
using enum NContentType::eContentType;
|
||||
using namespace NColorManagement;
|
||||
|
||||
CMonitor::CMonitor(SP<Aquamarine::IOutput> output_) : m_state(this), m_output(output_) {
|
||||
CMonitor::CMonitor(SP<Aquamarine::IOutput> output_) : m_state(this), m_output(output_), m_imageDescription(DEFAULT_IMAGE_DESCRIPTION) {
|
||||
g_pAnimationManager->createAnimation(0.f, m_specialFade, g_pConfigManager->getAnimationPropertyConfig("specialWorkspaceIn"), AVARDAMAGE_NONE);
|
||||
m_specialFade->setUpdateCallback([this](auto) { g_pHyprRenderer->damageMonitor(m_self.lock()); });
|
||||
static auto PZOOMFACTOR = CConfigValue<Hyprlang::FLOAT>("cursor:zoom_factor");
|
||||
|
|
@ -71,23 +76,27 @@ CMonitor::~CMonitor() {
|
|||
}
|
||||
|
||||
void CMonitor::onConnect(bool noRule) {
|
||||
EMIT_HOOK_EVENT("preMonitorAdded", m_self.lock());
|
||||
Event::bus()->m_events.monitor.preAdded.emit(m_self.lock());
|
||||
CScopeGuard x = {[]() { g_pCompositor->arrangeMonitors(); }};
|
||||
|
||||
m_zoomAnimProgress->setValueAndWarp(0.F);
|
||||
m_zoomAnimFrameCounter = 0;
|
||||
|
||||
g_pEventLoopManager->doLater([] { g_pConfigManager->ensurePersistentWorkspacesPresent(); });
|
||||
g_pEventLoopManager->doLater([] {
|
||||
g_pConfigManager->ensurePersistentWorkspacesPresent();
|
||||
g_pCompositor->ensureWorkspacesOnAssignedMonitors();
|
||||
});
|
||||
|
||||
m_listeners.frame = m_output->events.frame.listen([this] {
|
||||
if (m_frameScheduler)
|
||||
m_frameScheduler->onFrame();
|
||||
});
|
||||
m_listeners.commit = m_output->events.commit.listen([this] {
|
||||
if (true) { // FIXME: E->state->committed & WLR_OUTPUT_STATE_BUFFER
|
||||
PROTO::screencopy->onOutputCommit(m_self.lock());
|
||||
PROTO::toplevelExport->onOutputCommit(m_self.lock());
|
||||
}
|
||||
m_events.commit.emit();
|
||||
|
||||
// FIXME: E->state->committed & WLR_OUTPUT_STATE_BUFFER
|
||||
if (true && Screenshare::mgr())
|
||||
Screenshare::mgr()->onOutputCommit(m_self.lock());
|
||||
});
|
||||
m_listeners.needsFrame = m_output->events.needsFrame.listen([this] { g_pCompositor->scheduleFrameForMonitor(m_self.lock(), Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME); });
|
||||
|
||||
|
|
@ -114,10 +123,12 @@ void CMonitor::onConnect(bool noRule) {
|
|||
ts = nullptr;
|
||||
}
|
||||
|
||||
if (!ts)
|
||||
PROTO::presentation->onPresented(m_self.lock(), Time::steadyNow(), event.refresh, event.seq, event.flags);
|
||||
else
|
||||
PROTO::presentation->onPresented(m_self.lock(), Time::fromTimespec(event.when), event.refresh, event.seq, event.flags);
|
||||
if (!ts) {
|
||||
timespec mono{};
|
||||
clock_gettime(CLOCK_MONOTONIC, &mono);
|
||||
PROTO::presentation->onPresented(m_self.lock(), mono, event.refresh, event.seq, event.flags & ~Aquamarine::IOutput::AQ_OUTPUT_PRESENT_HW_CLOCK);
|
||||
} else
|
||||
PROTO::presentation->onPresented(m_self.lock(), *ts, event.refresh, event.seq, event.flags);
|
||||
|
||||
if (m_zoomAnimFrameCounter < 5) {
|
||||
m_zoomAnimFrameCounter++;
|
||||
|
|
@ -282,10 +293,16 @@ void CMonitor::onConnect(bool noRule) {
|
|||
if (!valid(ws))
|
||||
continue;
|
||||
|
||||
if (ws->m_lastMonitor == m_name || g_pCompositor->m_monitors.size() == 1 /* avoid lost workspaces on recover */) {
|
||||
const auto CURRENTMON = ws->m_monitor.lock();
|
||||
const bool ORPHANED = !CURRENTMON || std::ranges::none_of(g_pCompositor->m_monitors, [&](const auto& mon) { return mon == CURRENTMON; });
|
||||
const bool RETURNING = ws->m_lastMonitor == m_name;
|
||||
const bool RECOVERY = g_pCompositor->m_monitors.size() == 1 && ORPHANED; // temporarily recover orphaned workspaces
|
||||
|
||||
if (RETURNING || RECOVERY) {
|
||||
g_pCompositor->moveWorkspaceToMonitor(ws, m_self.lock());
|
||||
g_pDesktopAnimationManager->startAnimation(ws, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true);
|
||||
ws->m_lastMonitor = "";
|
||||
if (RETURNING)
|
||||
ws->m_lastMonitor = "";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -303,7 +320,7 @@ void CMonitor::onConnect(bool noRule) {
|
|||
Desktop::focusState()->rawMonitorFocus(m_self.lock());
|
||||
|
||||
g_pHyprRenderer->arrangeLayersForMonitor(m_id);
|
||||
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id);
|
||||
g_layoutManager->recalculateMonitor(m_self.lock());
|
||||
|
||||
// ensure VRR (will enable if necessary)
|
||||
g_pConfigManager->ensureVRR(m_self.lock());
|
||||
|
|
@ -341,17 +358,17 @@ void CMonitor::onConnect(bool noRule) {
|
|||
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{"monitoradded", m_name});
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{"monitoraddedv2", std::format("{},{},{}", m_id, m_name, m_shortDescription)});
|
||||
EMIT_HOOK_EVENT("monitorAdded", m_self.lock());
|
||||
Event::bus()->m_events.monitor.added.emit(m_self.lock());
|
||||
}
|
||||
|
||||
void CMonitor::onDisconnect(bool destroy) {
|
||||
EMIT_HOOK_EVENT("preMonitorRemoved", m_self.lock());
|
||||
Event::bus()->m_events.monitor.preRemoved.emit(m_self.lock());
|
||||
CScopeGuard x = {[this]() {
|
||||
if (g_pCompositor->m_isShuttingDown)
|
||||
return;
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{"monitorremoved", m_name});
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{"monitorremovedv2", std::format("{},{},{}", m_id, m_name, m_shortDescription)});
|
||||
EMIT_HOOK_EVENT("monitorRemoved", m_self.lock());
|
||||
Event::bus()->m_events.monitor.removed.emit(m_self.lock());
|
||||
g_pCompositor->scheduleMonitorStateRecheck();
|
||||
}};
|
||||
|
||||
|
|
@ -411,7 +428,6 @@ void CMonitor::onDisconnect(bool destroy) {
|
|||
m_layerSurfaceLayers[i].clear();
|
||||
}
|
||||
|
||||
std::erase_if(g_pCompositor->m_monitors, [&](PHLMONITOR& el) { return el.get() == this; });
|
||||
Log::logger->log(Log::DEBUG, "Removed monitor {}!", m_name);
|
||||
|
||||
if (!BACKUPMON) {
|
||||
|
|
@ -422,19 +438,24 @@ void CMonitor::onDisconnect(bool destroy) {
|
|||
m_enabled = false;
|
||||
m_renderingInitPassed = false;
|
||||
|
||||
std::vector<PHLWORKSPACE> wspToMove;
|
||||
for (auto const& w : g_pCompositor->getWorkspaces()) {
|
||||
if (w->m_monitor == m_self || !w->m_monitor)
|
||||
wspToMove.emplace_back(w.lock());
|
||||
}
|
||||
|
||||
// Preserve ownership across cascaded monitor disconnects.
|
||||
// The first disconnected monitor "owns" where a workspace should return.
|
||||
for (auto const& w : wspToMove) {
|
||||
if (w && w->m_lastMonitor.empty())
|
||||
w->m_lastMonitor = m_name;
|
||||
}
|
||||
|
||||
if (BACKUPMON) {
|
||||
// snap cursor
|
||||
g_pCompositor->warpCursorTo(BACKUPMON->m_position + BACKUPMON->m_transformedSize / 2.F, true);
|
||||
|
||||
// move workspaces
|
||||
std::vector<PHLWORKSPACE> wspToMove;
|
||||
for (auto const& w : g_pCompositor->getWorkspaces()) {
|
||||
if (w->m_monitor == m_self || !w->m_monitor)
|
||||
wspToMove.emplace_back(w.lock());
|
||||
}
|
||||
|
||||
for (auto const& w : wspToMove) {
|
||||
w->m_lastMonitor = m_name;
|
||||
g_pCompositor->moveWorkspaceToMonitor(w, BACKUPMON);
|
||||
g_pDesktopAnimationManager->startAnimation(w, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true);
|
||||
}
|
||||
|
|
@ -463,7 +484,7 @@ void CMonitor::onDisconnect(bool destroy) {
|
|||
PHLMONITOR pMonitorMostHz = nullptr;
|
||||
|
||||
for (auto const& m : g_pCompositor->m_monitors) {
|
||||
if (m->m_refreshRate > mostHz) {
|
||||
if (m->m_refreshRate > mostHz && m != m_self) {
|
||||
pMonitorMostHz = m;
|
||||
mostHz = m->m_refreshRate;
|
||||
}
|
||||
|
|
@ -471,78 +492,118 @@ void CMonitor::onDisconnect(bool destroy) {
|
|||
|
||||
g_pHyprRenderer->m_mostHzMonitor = pMonitorMostHz;
|
||||
}
|
||||
|
||||
std::erase_if(g_pCompositor->m_monitors, [&](PHLMONITOR& el) { return el.get() == this; });
|
||||
}
|
||||
|
||||
void CMonitor::applyCMType(NCMType::eCMType cmType, int cmSdrEotf) {
|
||||
auto oldImageDescription = m_imageDescription;
|
||||
static auto PSDREOTF = CConfigValue<Hyprlang::INT>("render:cm_sdr_eotf");
|
||||
auto chosenSdrEotf = cmSdrEotf == 0 ? (*PSDREOTF != 3 ? NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22 : NColorManagement::CM_TRANSFER_FUNCTION_SRGB) :
|
||||
(cmSdrEotf == 1 ? NColorManagement::CM_TRANSFER_FUNCTION_SRGB : NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22);
|
||||
static NColorManagement::eTransferFunction chooseTF(NTransferFunction::eTF tf) {
|
||||
const auto sdrEOTF = NTransferFunction::fromConfig();
|
||||
|
||||
switch (tf) {
|
||||
case NTransferFunction::TF_DEFAULT:
|
||||
case NTransferFunction::TF_GAMMA22:
|
||||
case NTransferFunction::TF_FORCED_GAMMA22: return NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22;
|
||||
case NTransferFunction::TF_SRGB: return NColorManagement::CM_TRANSFER_FUNCTION_SRGB;
|
||||
|
||||
case NTransferFunction::TF_AUTO: // use global setting
|
||||
switch (sdrEOTF) {
|
||||
case NTransferFunction::TF_AUTO: return NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22;
|
||||
default: return chooseTF(sdrEOTF);
|
||||
}
|
||||
|
||||
default: UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
void CMonitor::applyCMType(NCMType::eCMType cmType, NTransferFunction::eTF cmSdrEotf) {
|
||||
auto oldImageDescription = m_imageDescription;
|
||||
const auto chosenSdrEotf = chooseTF(cmSdrEotf);
|
||||
|
||||
const auto masteringPrimaries = getMasteringPrimaries();
|
||||
const NColorManagement::SImageDescription::SPCMasteringLuminances masteringLuminances = getMasteringLuminances();
|
||||
|
||||
const auto maxFALL = this->maxFALL();
|
||||
const auto maxCLL = this->maxCLL();
|
||||
|
||||
switch (cmType) {
|
||||
case NCMType::CM_SRGB: m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf}); break; // assumes SImageDescription defaults to sRGB
|
||||
case NCMType::CM_WIDE:
|
||||
case NCMType::CM_SRGB:
|
||||
m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf,
|
||||
.primariesNameSet = true,
|
||||
.primariesNamed = NColorManagement::CM_PRIMARIES_BT2020,
|
||||
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020)});
|
||||
.primariesNamed = NColorManagement::CM_PRIMARIES_SRGB,
|
||||
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_SRGB)});
|
||||
break; // assumes SImageDescription defaults to sRGB
|
||||
case NCMType::CM_WIDE:
|
||||
m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf,
|
||||
.primariesNameSet = true,
|
||||
.primariesNamed = NColorManagement::CM_PRIMARIES_BT2020,
|
||||
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020),
|
||||
.masteringPrimaries = masteringPrimaries,
|
||||
.masteringLuminances = masteringLuminances,
|
||||
.maxCLL = maxCLL,
|
||||
.maxFALL = maxFALL});
|
||||
break;
|
||||
case NCMType::CM_DCIP3:
|
||||
m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf,
|
||||
.primariesNameSet = true,
|
||||
.primariesNamed = NColorManagement::CM_PRIMARIES_DCI_P3,
|
||||
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_DCI_P3)});
|
||||
m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf,
|
||||
.primariesNameSet = true,
|
||||
.primariesNamed = NColorManagement::CM_PRIMARIES_DCI_P3,
|
||||
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_DCI_P3),
|
||||
.masteringPrimaries = masteringPrimaries,
|
||||
.masteringLuminances = masteringLuminances,
|
||||
.maxCLL = maxCLL,
|
||||
.maxFALL = maxFALL});
|
||||
break;
|
||||
case NCMType::CM_DP3:
|
||||
m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf,
|
||||
.primariesNameSet = true,
|
||||
.primariesNamed = NColorManagement::CM_PRIMARIES_DISPLAY_P3,
|
||||
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_DISPLAY_P3)});
|
||||
m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf,
|
||||
.primariesNameSet = true,
|
||||
.primariesNamed = NColorManagement::CM_PRIMARIES_DISPLAY_P3,
|
||||
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_DISPLAY_P3),
|
||||
.masteringPrimaries = masteringPrimaries,
|
||||
.masteringLuminances = masteringLuminances,
|
||||
.maxCLL = maxCLL,
|
||||
.maxFALL = maxFALL});
|
||||
break;
|
||||
case NCMType::CM_ADOBE:
|
||||
m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf,
|
||||
.primariesNameSet = true,
|
||||
.primariesNamed = NColorManagement::CM_PRIMARIES_ADOBE_RGB,
|
||||
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_ADOBE_RGB)});
|
||||
m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf,
|
||||
.primariesNameSet = true,
|
||||
.primariesNamed = NColorManagement::CM_PRIMARIES_ADOBE_RGB,
|
||||
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_ADOBE_RGB),
|
||||
.masteringPrimaries = masteringPrimaries,
|
||||
.masteringLuminances = masteringLuminances,
|
||||
.maxCLL = maxCLL,
|
||||
.maxFALL = maxFALL});
|
||||
break;
|
||||
case NCMType::CM_EDID:
|
||||
m_imageDescription =
|
||||
CImageDescription::from({.transferFunction = chosenSdrEotf,
|
||||
.primariesNameSet = false,
|
||||
.primariesNamed = NColorManagement::CM_PRIMARIES_BT2020,
|
||||
.primaries = {
|
||||
.red = {.x = m_output->parsedEDID.chromaticityCoords->red.x, .y = m_output->parsedEDID.chromaticityCoords->red.y},
|
||||
.green = {.x = m_output->parsedEDID.chromaticityCoords->green.x, .y = m_output->parsedEDID.chromaticityCoords->green.y},
|
||||
.blue = {.x = m_output->parsedEDID.chromaticityCoords->blue.x, .y = m_output->parsedEDID.chromaticityCoords->blue.y},
|
||||
.white = {.x = m_output->parsedEDID.chromaticityCoords->white.x, .y = m_output->parsedEDID.chromaticityCoords->white.y},
|
||||
}});
|
||||
m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf,
|
||||
.primariesNameSet = false,
|
||||
.primariesNamed = NColorManagement::CM_PRIMARIES_BT2020,
|
||||
.primaries = masteringPrimaries,
|
||||
.masteringPrimaries = masteringPrimaries,
|
||||
.masteringLuminances = masteringLuminances,
|
||||
.maxCLL = maxCLL,
|
||||
.maxFALL = maxFALL});
|
||||
break;
|
||||
case NCMType::CM_HDR: m_imageDescription = DEFAULT_HDR_IMAGE_DESCRIPTION; break;
|
||||
case NCMType::CM_HDR_EDID:
|
||||
m_imageDescription =
|
||||
CImageDescription::from({.transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ,
|
||||
.primariesNameSet = false,
|
||||
.primariesNamed = NColorManagement::CM_PRIMARIES_BT2020,
|
||||
.primaries = m_output->parsedEDID.chromaticityCoords.has_value() ?
|
||||
NColorManagement::SPCPRimaries{
|
||||
.red = {.x = m_output->parsedEDID.chromaticityCoords->red.x, .y = m_output->parsedEDID.chromaticityCoords->red.y},
|
||||
.green = {.x = m_output->parsedEDID.chromaticityCoords->green.x, .y = m_output->parsedEDID.chromaticityCoords->green.y},
|
||||
.blue = {.x = m_output->parsedEDID.chromaticityCoords->blue.x, .y = m_output->parsedEDID.chromaticityCoords->blue.y},
|
||||
.white = {.x = m_output->parsedEDID.chromaticityCoords->white.x, .y = m_output->parsedEDID.chromaticityCoords->white.y},
|
||||
} :
|
||||
NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020),
|
||||
.luminances = {.min = m_output->parsedEDID.hdrMetadata->desiredContentMinLuminance,
|
||||
.max = m_output->parsedEDID.hdrMetadata->desiredContentMaxLuminance,
|
||||
.reference = m_output->parsedEDID.hdrMetadata->desiredMaxFrameAverageLuminance}});
|
||||
m_imageDescription = CImageDescription::from(
|
||||
{.transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ,
|
||||
.primariesNameSet = false,
|
||||
.primariesNamed = NColorManagement::CM_PRIMARIES_BT2020,
|
||||
.primaries = m_output->parsedEDID.chromaticityCoords.has_value() ? masteringPrimaries : NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020),
|
||||
.masteringPrimaries = masteringPrimaries,
|
||||
.luminances = {.min = DEFAULT_HDR_IMAGE_DESCRIPTION->value().getTFMinLuminance(),
|
||||
.max = DEFAULT_HDR_IMAGE_DESCRIPTION->value().getTFMaxLuminance(),
|
||||
.reference = DEFAULT_HDR_IMAGE_DESCRIPTION->value().getTFRefLuminance()},
|
||||
.masteringLuminances = masteringLuminances,
|
||||
.maxCLL = maxCLL,
|
||||
.maxFALL = maxFALL});
|
||||
|
||||
break;
|
||||
default: UNREACHABLE();
|
||||
}
|
||||
if (m_minLuminance >= 0 || m_maxLuminance >= 0 || m_maxAvgLuminance >= 0)
|
||||
if ((m_minLuminance >= 0 || m_maxLuminance >= 0 || m_maxAvgLuminance >= 0) && (cmType == NCMType::CM_HDR || cmType == NCMType::CM_HDR_EDID))
|
||||
m_imageDescription = m_imageDescription->with({
|
||||
.min = m_minLuminance >= 0 ? m_minLuminance : m_imageDescription->value().luminances.min, //
|
||||
.max = m_maxLuminance >= 0 ? m_maxLuminance : m_imageDescription->value().luminances.max, //
|
||||
.reference = m_maxAvgLuminance >= 0 ? m_maxAvgLuminance : m_imageDescription->value().luminances.reference //
|
||||
.min = m_minLuminance >= 0 ? m_minLuminance : m_imageDescription->value().luminances.min, //
|
||||
.max = m_maxLuminance >= 0 ? m_maxLuminance : m_imageDescription->value().luminances.max, //
|
||||
.reference = m_imageDescription->value().luminances.reference //
|
||||
});
|
||||
|
||||
if (oldImageDescription != m_imageDescription) {
|
||||
|
|
@ -872,29 +933,46 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) {
|
|||
m_supportsWideColor = RULE->supportsHDR;
|
||||
m_supportsHDR = RULE->supportsHDR;
|
||||
|
||||
m_cmType = RULE->cmType;
|
||||
switch (m_cmType) {
|
||||
case NCMType::CM_AUTO: m_cmType = m_enabled10bit && supportsWideColor() ? NCMType::CM_WIDE : NCMType::CM_SRGB; break;
|
||||
case NCMType::CM_EDID: m_cmType = m_output->parsedEDID.chromaticityCoords.has_value() ? NCMType::CM_EDID : NCMType::CM_SRGB; break;
|
||||
case NCMType::CM_HDR:
|
||||
case NCMType::CM_HDR_EDID: m_cmType = supportsHDR() ? m_cmType : NCMType::CM_SRGB; break;
|
||||
default: break;
|
||||
if (RULE->iccFile.empty()) {
|
||||
// only apply explicit cm settings if we have no icc file
|
||||
|
||||
m_cmType = RULE->cmType;
|
||||
switch (m_cmType) {
|
||||
case NCMType::CM_AUTO: m_cmType = m_enabled10bit && supportsWideColor() ? NCMType::CM_WIDE : NCMType::CM_SRGB; break;
|
||||
case NCMType::CM_EDID: m_cmType = m_output->parsedEDID.chromaticityCoords.has_value() ? NCMType::CM_EDID : NCMType::CM_SRGB; break;
|
||||
case NCMType::CM_HDR:
|
||||
case NCMType::CM_HDR_EDID: m_cmType = supportsHDR() ? m_cmType : NCMType::CM_SRGB; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
m_sdrEotf = RULE->sdrEotf;
|
||||
|
||||
m_sdrMinLuminance = RULE->sdrMinLuminance;
|
||||
m_sdrMaxLuminance = RULE->sdrMaxLuminance;
|
||||
|
||||
m_minLuminance = RULE->minLuminance;
|
||||
m_maxLuminance = RULE->maxLuminance;
|
||||
m_maxAvgLuminance = RULE->maxAvgLuminance;
|
||||
|
||||
applyCMType(m_cmType, m_sdrEotf);
|
||||
|
||||
m_sdrSaturation = RULE->sdrSaturation;
|
||||
m_sdrBrightness = RULE->sdrBrightness;
|
||||
} else {
|
||||
auto image = NColorManagement::SImageDescription::fromICC(RULE->iccFile);
|
||||
if (!image) {
|
||||
Log::logger->log(Log::ERR, "icc for {} ({}) failed: {}", m_name, RULE->iccFile, image.error());
|
||||
g_pConfigManager->addParseError(std::format("failed to apply icc {} to {}: {}", RULE->iccFile, m_name, image.error()));
|
||||
} else {
|
||||
m_imageDescription = CImageDescription::from(*image);
|
||||
if (!m_imageDescription) {
|
||||
Log::logger->log(Log::ERR, "icc for {} ({}) failed 2: {}", m_name, RULE->iccFile, image.error());
|
||||
g_pConfigManager->addParseError(std::format("failed to apply icc {} to {}: {}", RULE->iccFile, m_name, image.error()));
|
||||
m_imageDescription = CImageDescription::from(SImageDescription{});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_sdrEotf = RULE->sdrEotf;
|
||||
|
||||
m_sdrMinLuminance = RULE->sdrMinLuminance;
|
||||
m_sdrMaxLuminance = RULE->sdrMaxLuminance;
|
||||
|
||||
m_minLuminance = RULE->minLuminance;
|
||||
m_maxLuminance = RULE->maxLuminance;
|
||||
m_maxAvgLuminance = RULE->maxAvgLuminance;
|
||||
|
||||
applyCMType(m_cmType, m_sdrEotf);
|
||||
|
||||
m_sdrSaturation = RULE->sdrSaturation;
|
||||
m_sdrBrightness = RULE->sdrBrightness;
|
||||
|
||||
Vector2D logicalSize = m_pixelSize / m_scale;
|
||||
if (!*PDISABLESCALECHECKS && (logicalSize.x != std::round(logicalSize.x) || logicalSize.y != std::round(logicalSize.y))) {
|
||||
// invalid scale, will produce fractional pixels.
|
||||
|
|
@ -976,6 +1054,8 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) {
|
|||
|
||||
m_damage.setSize(m_transformedSize);
|
||||
|
||||
updateVCGTRamps();
|
||||
|
||||
// Set scale for all surfaces on this monitor, needed for some clients
|
||||
// but not on unsafe state to avoid crashes
|
||||
if (!g_pCompositor->m_unsafeState) {
|
||||
|
|
@ -992,7 +1072,7 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) {
|
|||
Log::logger->log(Log::DEBUG, "Monitor {} data dump: res {:X}@{:.2f}Hz, scale {:.2f}, transform {}, pos {:X}, 10b {}", m_name, m_pixelSize, m_refreshRate, m_scale,
|
||||
sc<int>(m_transform), m_position, sc<int>(m_enabled10bit));
|
||||
|
||||
EMIT_HOOK_EVENT("monitorLayoutChanged", nullptr);
|
||||
Event::bus()->m_events.monitor.layoutChanged.emit();
|
||||
|
||||
m_events.modeChanged.emit();
|
||||
|
||||
|
|
@ -1026,8 +1106,10 @@ bool CMonitor::shouldSkipScheduleFrameOnMouseEvent() {
|
|||
static auto PMINRR = CConfigValue<Hyprlang::INT>("cursor:min_refresh_rate");
|
||||
|
||||
// skip scheduling extra frames for fullsreen apps with vrr
|
||||
const bool shouldSkip = inFullscreenMode() && (*PNOBREAK == 1 || (*PNOBREAK == 2 && m_activeWorkspace->getFullscreenWindow()->getContentType() == CONTENT_TYPE_GAME)) &&
|
||||
m_output->state->state().adaptiveSync;
|
||||
const auto FS_WINDOW = getFullscreenWindow();
|
||||
const bool shouldRenderCursor = g_pHyprRenderer->shouldRenderCursor();
|
||||
const bool noBreak = FS_WINDOW && (*PNOBREAK == 1 || (*PNOBREAK == 2 && FS_WINDOW->getContentType() == CONTENT_TYPE_GAME));
|
||||
const bool shouldSkip = (!shouldRenderCursor || noBreak) && m_output->state->state().adaptiveSync;
|
||||
|
||||
// keep requested minimum refresh rate
|
||||
if (shouldSkip && *PMINRR && m_lastPresentationTimer.getMillis() > 1000.0f / *PMINRR) {
|
||||
|
|
@ -1096,7 +1178,7 @@ void CMonitor::setupDefaultWS(const SMonitorRule& monitorRule) {
|
|||
// workspace exists, move it to the newly connected monitor
|
||||
g_pCompositor->moveWorkspaceToMonitor(PNEWWORKSPACE, m_self.lock());
|
||||
m_activeWorkspace = PNEWWORKSPACE;
|
||||
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id);
|
||||
g_layoutManager->recalculateMonitor(m_self.lock());
|
||||
g_pDesktopAnimationManager->startAnimation(PNEWWORKSPACE, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true);
|
||||
} else {
|
||||
if (newDefaultWorkspaceName.empty())
|
||||
|
|
@ -1280,7 +1362,7 @@ void CMonitor::changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal, bo
|
|||
// move pinned windows
|
||||
for (auto const& w : g_pCompositor->m_windows) {
|
||||
if (w->m_workspace == POLDWORKSPACE && w->m_pinned)
|
||||
w->moveToWorkspace(pWorkspace);
|
||||
w->layoutTarget()->assignToSpace(pWorkspace->m_space);
|
||||
}
|
||||
|
||||
if (!noFocus && !Desktop::focusState()->monitor()->m_activeSpecialWorkspace &&
|
||||
|
|
@ -1300,17 +1382,17 @@ void CMonitor::changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal, bo
|
|||
pWindow = pWorkspace->getFirstWindow();
|
||||
}
|
||||
|
||||
Desktop::focusState()->fullWindowFocus(pWindow);
|
||||
Desktop::focusState()->fullWindowFocus(pWindow, Desktop::FOCUS_REASON_KEYBIND);
|
||||
}
|
||||
|
||||
if (!noMouseMove)
|
||||
g_pInputManager->simulateMouseMovement();
|
||||
|
||||
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id);
|
||||
g_layoutManager->recalculateMonitor(m_self.lock());
|
||||
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{"workspace", pWorkspace->m_name});
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{"workspacev2", std::format("{},{}", pWorkspace->m_id, pWorkspace->m_name)});
|
||||
EMIT_HOOK_EVENT("workspace", pWorkspace);
|
||||
Event::bus()->m_events.workspace.active.emit(pWorkspace);
|
||||
}
|
||||
|
||||
// set all LSes as not above fullscreen on workspace changes
|
||||
|
|
@ -1357,17 +1439,23 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) {
|
|||
g_pDesktopAnimationManager->startAnimation(m_activeSpecialWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_OUT, false);
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{"activespecial", "," + m_name});
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{"activespecialv2", ",," + m_name});
|
||||
|
||||
// Reset layer surface state when closing special workspace
|
||||
for (auto const& ls : g_pCompositor->m_layers) {
|
||||
if (ls->m_monitor == m_self)
|
||||
ls->m_aboveFullscreen = false;
|
||||
}
|
||||
}
|
||||
m_activeSpecialWorkspace.reset();
|
||||
|
||||
if (POLDSPECIAL)
|
||||
POLDSPECIAL->m_events.activeChanged.emit();
|
||||
|
||||
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id);
|
||||
g_layoutManager->recalculateMonitor(m_self.lock());
|
||||
|
||||
if (!(Desktop::focusState()->window() && Desktop::focusState()->window()->m_pinned && Desktop::focusState()->window()->m_monitor == m_self)) {
|
||||
if (const auto PLAST = m_activeWorkspace->getLastFocusedWindow(); PLAST)
|
||||
Desktop::focusState()->fullWindowFocus(PLAST);
|
||||
Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_KEYBIND);
|
||||
else
|
||||
g_pInputManager->refocus();
|
||||
}
|
||||
|
|
@ -1392,10 +1480,17 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) {
|
|||
const auto PMONITORWORKSPACEOWNER = pWorkspace->m_monitor.lock();
|
||||
if (const auto PMWSOWNER = pWorkspace->m_monitor.lock(); PMWSOWNER && PMWSOWNER->m_activeSpecialWorkspace == pWorkspace) {
|
||||
PMWSOWNER->m_activeSpecialWorkspace.reset();
|
||||
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PMWSOWNER->m_id);
|
||||
g_layoutManager->recalculateMonitor(PMWSOWNER);
|
||||
g_pHyprRenderer->damageMonitor(PMWSOWNER);
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{"activespecial", "," + PMWSOWNER->m_name});
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{"activespecialv2", ",," + PMWSOWNER->m_name});
|
||||
|
||||
// Reset layer surfaces on the old monitor when special workspace is stolen
|
||||
for (auto const& ls : g_pCompositor->m_layers) {
|
||||
if (ls->m_monitor == PMWSOWNER)
|
||||
ls->m_aboveFullscreen = false;
|
||||
}
|
||||
|
||||
const auto PACTIVEWORKSPACE = PMWSOWNER->m_activeWorkspace;
|
||||
g_pDesktopAnimationManager->setFullscreenFadeAnimation(PACTIVEWORKSPACE,
|
||||
PACTIVEWORKSPACE && PACTIVEWORKSPACE->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN :
|
||||
|
|
@ -1409,6 +1504,12 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) {
|
|||
m_activeSpecialWorkspace = pWorkspace;
|
||||
m_activeSpecialWorkspace->m_visible = true;
|
||||
|
||||
// Reset layer surface state when opening special workspace
|
||||
for (auto const& ls : g_pCompositor->m_layers) {
|
||||
if (ls->m_monitor == m_self)
|
||||
ls->m_aboveFullscreen = false;
|
||||
}
|
||||
|
||||
if (POLDSPECIAL)
|
||||
POLDSPECIAL->m_events.activeChanged.emit();
|
||||
|
||||
|
|
@ -1439,17 +1540,16 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) {
|
|||
} else
|
||||
pos = pos - PMONFROMMIDDLE->m_position + m_position;
|
||||
|
||||
*w->m_realPosition = pos;
|
||||
w->m_position = pos;
|
||||
w->layoutTarget()->setPositionGlobal(CBox{pos, w->layoutTarget()->position().size()});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id);
|
||||
g_layoutManager->recalculateMonitor(m_self.lock());
|
||||
|
||||
if (!(Desktop::focusState()->window() && Desktop::focusState()->window()->m_pinned && Desktop::focusState()->window()->m_monitor == m_self)) {
|
||||
if (const auto PLAST = pWorkspace->getLastFocusedWindow(); PLAST)
|
||||
Desktop::focusState()->fullWindowFocus(PLAST);
|
||||
Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_KEYBIND);
|
||||
else
|
||||
g_pInputManager->refocus();
|
||||
}
|
||||
|
|
@ -1479,10 +1579,20 @@ Vector2D CMonitor::middle() {
|
|||
return m_position + m_size / 2.f;
|
||||
}
|
||||
|
||||
const Mat3x3& CMonitor::getTransformMatrix() {
|
||||
return m_projMatrix;
|
||||
}
|
||||
|
||||
const Mat3x3& CMonitor::getScaleMatrix() {
|
||||
return m_projOutputMatrix;
|
||||
}
|
||||
|
||||
void CMonitor::updateMatrix() {
|
||||
m_projMatrix = Mat3x3::identity();
|
||||
if (m_transform != WL_OUTPUT_TRANSFORM_NORMAL)
|
||||
m_projMatrix.translate(m_pixelSize / 2.0).transform(Math::wlTransformToHyprutils(m_transform)).translate(-m_transformedSize / 2.0);
|
||||
|
||||
m_projOutputMatrix = Mat3x3::outputProjection(m_pixelSize, HYPRUTILS_TRANSFORM_NORMAL);
|
||||
}
|
||||
|
||||
WORKSPACEID CMonitor::activeWorkspaceID() {
|
||||
|
|
@ -1686,6 +1796,10 @@ uint8_t CMonitor::isTearingBlocked(bool full) {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: remove this when kernel allows tearing + hw cursor updated
|
||||
if (g_pPointerManager->hasVisibleHWCursor(m_self.lock()))
|
||||
reasons |= TC_HW_CURSOR;
|
||||
|
||||
if (m_solitaryClient.expired()) {
|
||||
reasons |= TC_CANDIDATE;
|
||||
return reasons;
|
||||
|
|
@ -1727,12 +1841,6 @@ uint16_t CMonitor::isDSBlocked(bool full) {
|
|||
}
|
||||
}
|
||||
|
||||
if (m_tearingState.activelyTearing) {
|
||||
reasons |= DS_BLOCK_TEARING;
|
||||
if (!full)
|
||||
return reasons;
|
||||
}
|
||||
|
||||
if (!m_mirrors.empty() || isMirror()) {
|
||||
reasons |= DS_BLOCK_MIRROR;
|
||||
if (!full)
|
||||
|
|
@ -1771,21 +1879,27 @@ uint16_t CMonitor::isDSBlocked(bool full) {
|
|||
|
||||
// we can't scanout shm buffers.
|
||||
const auto params = PSURFACE->m_current.buffer->dmabuf();
|
||||
if (!params.success || !PSURFACE->m_current.texture->m_eglImage /* dmabuf */) {
|
||||
if (!params.success || !PSURFACE->m_current.texture->isDMA() /* dmabuf */) {
|
||||
reasons |= DS_BLOCK_DMA;
|
||||
if (!full)
|
||||
return reasons;
|
||||
}
|
||||
|
||||
const bool surfaceIsHDR = PSURFACE->m_colorManagement.valid() && (PSURFACE->m_colorManagement->isHDR() || PSURFACE->m_colorManagement->isWindowsScRGB());
|
||||
if (needsCM() && *PNONSHADER != CM_NS_IGNORE && !canNoShaderCM() && ((inHDR() && (*PPASS == 0 || !surfaceIsHDR)) || (!inHDR() && (*PPASS != 1 || surfaceIsHDR))))
|
||||
const bool surfaceIsHDR = PSURFACE->m_colorManagement.valid() && PSURFACE->m_colorManagement->isHDR();
|
||||
const bool surfaceIsScRGB = surfaceIsHDR && PSURFACE->m_colorManagement->isWindowsScRGB();
|
||||
|
||||
if (needsCM() && (*PNONSHADER != CM_NS_IGNORE || surfaceIsScRGB) && !canNoShaderCM() &&
|
||||
((inHDR() && (*PPASS == 0 || !surfaceIsHDR || surfaceIsScRGB)) || (!inHDR() && (*PPASS != 1 || surfaceIsHDR))))
|
||||
reasons |= DS_BLOCK_CM;
|
||||
|
||||
return reasons;
|
||||
}
|
||||
|
||||
bool CMonitor::attemptDirectScanout() {
|
||||
const auto blockedReason = isDSBlocked();
|
||||
static const auto PSAME = CConfigValue<Hyprlang::INT>("debug:ds_handle_same_buffer");
|
||||
static const auto PSAMEFIFO = CConfigValue<Hyprlang::INT>("debug:ds_handle_same_buffer_fifo");
|
||||
|
||||
const auto blockedReason = isDSBlocked();
|
||||
if (blockedReason)
|
||||
return false;
|
||||
|
||||
|
|
@ -1799,7 +1913,7 @@ bool CMonitor::attemptDirectScanout() {
|
|||
auto PBUFFER = PSURFACE->m_current.buffer.m_buffer;
|
||||
|
||||
// #TODO this entire bit needs figuring out, vrr goes down the drain without it
|
||||
if (PBUFFER == m_output->state->state().buffer) {
|
||||
if (PBUFFER == m_output->state->state().buffer && *PSAME) {
|
||||
PSURFACE->presentFeedback(Time::steadyNow(), m_self.lock());
|
||||
|
||||
if (m_scanoutNeedsCursorUpdate) {
|
||||
|
|
@ -1818,7 +1932,7 @@ bool CMonitor::attemptDirectScanout() {
|
|||
}
|
||||
|
||||
//#TODO this entire bit is bootleg deluxe, above bit is to not make vrr go down the drain, returning early here means fifo gets forever locked.
|
||||
if (PSURFACE->m_fifo)
|
||||
if (PSURFACE->m_fifo && !m_tearingState.activelyTearing && *PSAMEFIFO)
|
||||
PSURFACE->m_stateQueue.unlockFirst(LOCK_REASON_FIFO);
|
||||
|
||||
return true;
|
||||
|
|
@ -1849,7 +1963,18 @@ bool CMonitor::attemptDirectScanout() {
|
|||
PSURFACE->presentFeedback(Time::steadyNow(), m_self.lock());
|
||||
|
||||
m_output->state->addDamage(PSURFACE->m_current.accumulateBufferDamage());
|
||||
m_output->state->resetExplicitFences();
|
||||
|
||||
// multigpu needs a fence to trigger fence syncing blits and also committing with the recreated dgpu fence
|
||||
if (!DRM::sameGpu(m_output->getBackend()->preferredAllocator()->drmFD(), g_pCompositor->m_drm.fd) && g_pHyprOpenGL->explicitSyncSupported()) {
|
||||
auto sync = CEGLSync::create();
|
||||
|
||||
if (sync->fd().isValid()) {
|
||||
m_inFence = sync->takeFd();
|
||||
m_output->state->setExplicitInFence(m_inFence.get());
|
||||
} else
|
||||
m_output->state->resetExplicitFences(); // good luck.
|
||||
} else
|
||||
m_output->state->resetExplicitFences();
|
||||
|
||||
// no need to do explicit sync here as surface current can only ever be ready to read
|
||||
|
||||
|
|
@ -2004,6 +2129,14 @@ int CMonitor::maxAvgLuminance(int defaultValue) {
|
|||
(m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->desiredMaxFrameAverageLuminance : defaultValue);
|
||||
}
|
||||
|
||||
float CMonitor::maxFALL() {
|
||||
return m_maxAvgLuminance >= 0 ? m_maxAvgLuminance : (m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->desiredMaxFrameAverageLuminance : 0);
|
||||
}
|
||||
|
||||
float CMonitor::maxCLL() {
|
||||
return m_maxLuminance >= 0 ? m_maxLuminance : (m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->desiredContentMaxLuminance : 0);
|
||||
}
|
||||
|
||||
bool CMonitor::wantsWideColor() {
|
||||
return supportsWideColor() && (wantsHDR() || m_imageDescription->value().primariesNamed == CM_PRIMARIES_BT2020);
|
||||
}
|
||||
|
|
@ -2017,22 +2150,52 @@ bool CMonitor::inHDR() {
|
|||
}
|
||||
|
||||
bool CMonitor::inFullscreenMode() {
|
||||
// Check special workspace first since it renders on top of regular workspaces
|
||||
if (m_activeSpecialWorkspace && m_activeSpecialWorkspace->m_hasFullscreenWindow && m_activeSpecialWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN)
|
||||
return true;
|
||||
return m_activeWorkspace && m_activeWorkspace->m_hasFullscreenWindow && m_activeWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN;
|
||||
}
|
||||
|
||||
PHLWINDOW CMonitor::getFullscreenWindow() {
|
||||
// Check special workspace first since it renders on top of regular workspaces
|
||||
if (m_activeSpecialWorkspace && m_activeSpecialWorkspace->m_hasFullscreenWindow && m_activeSpecialWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN)
|
||||
return m_activeSpecialWorkspace->getFullscreenWindow();
|
||||
if (m_activeWorkspace && m_activeWorkspace->m_hasFullscreenWindow && m_activeWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN)
|
||||
return m_activeWorkspace->getFullscreenWindow();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::optional<NColorManagement::PImageDescription> CMonitor::getFSImageDescription() {
|
||||
if (!inFullscreenMode())
|
||||
return {};
|
||||
|
||||
const auto FS_WINDOW = m_activeWorkspace->getFullscreenWindow();
|
||||
const auto FS_WINDOW = getFullscreenWindow();
|
||||
if (!FS_WINDOW)
|
||||
return {}; // should be unreachable
|
||||
return {};
|
||||
|
||||
const auto ROOT_SURF = FS_WINDOW->wlSurface()->resource();
|
||||
const auto SURF = ROOT_SURF->findWithCM();
|
||||
return SURF ? NColorManagement::CImageDescription::from(SURF->m_colorManagement->imageDescription()) : DEFAULT_IMAGE_DESCRIPTION;
|
||||
}
|
||||
|
||||
NColorManagement::SPCPRimaries CMonitor::getMasteringPrimaries() {
|
||||
return m_output->parsedEDID.chromaticityCoords.has_value() ?
|
||||
NColorManagement::SPCPRimaries{
|
||||
.red = {.x = m_output->parsedEDID.chromaticityCoords->red.x, .y = m_output->parsedEDID.chromaticityCoords->red.y},
|
||||
.green = {.x = m_output->parsedEDID.chromaticityCoords->green.x, .y = m_output->parsedEDID.chromaticityCoords->green.y},
|
||||
.blue = {.x = m_output->parsedEDID.chromaticityCoords->blue.x, .y = m_output->parsedEDID.chromaticityCoords->blue.y},
|
||||
.white = {.x = m_output->parsedEDID.chromaticityCoords->white.x, .y = m_output->parsedEDID.chromaticityCoords->white.y},
|
||||
} :
|
||||
NColorManagement::SPCPRimaries{};
|
||||
}
|
||||
|
||||
NColorManagement::SImageDescription::SPCMasteringLuminances CMonitor::getMasteringLuminances() {
|
||||
return {
|
||||
.min = m_minLuminance >= 0 ? m_minLuminance : (m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->desiredContentMinLuminance : 0),
|
||||
.max = m_maxLuminance >= 0 ? m_maxLuminance : (m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->desiredContentMaxLuminance : 0),
|
||||
};
|
||||
}
|
||||
|
||||
bool CMonitor::needsCM() {
|
||||
const auto SRC_DESC = getFSImageDescription();
|
||||
return SRC_DESC.has_value() && SRC_DESC.value() != m_imageDescription;
|
||||
|
|
@ -2053,24 +2216,91 @@ bool CMonitor::canNoShaderCM() {
|
|||
|
||||
const auto SRC_DESC_VALUE = SRC_DESC.value()->value();
|
||||
|
||||
if (SRC_DESC_VALUE.icc.fd >= 0 || m_imageDescription->value().icc.fd >= 0)
|
||||
return false; // no ICC support
|
||||
if (m_imageDescription->value().icc.present)
|
||||
return false;
|
||||
|
||||
static auto PSDREOTF = CConfigValue<Hyprlang::INT>("render:cm_sdr_eotf");
|
||||
const auto sdrEOTF = NTransferFunction::fromConfig();
|
||||
// only primaries differ
|
||||
return ((SRC_DESC_VALUE.transferFunction == m_imageDescription->value().transferFunction ||
|
||||
(*PSDREOTF == 2 && SRC_DESC_VALUE.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_SRGB &&
|
||||
m_imageDescription->value().transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22)) &&
|
||||
SRC_DESC_VALUE.transferFunctionPower == m_imageDescription->value().transferFunctionPower &&
|
||||
(!inHDR() || SRC_DESC_VALUE.luminances == m_imageDescription->value().luminances) &&
|
||||
SRC_DESC_VALUE.masteringLuminances == m_imageDescription->value().masteringLuminances && SRC_DESC_VALUE.maxCLL == m_imageDescription->value().maxCLL &&
|
||||
SRC_DESC_VALUE.maxFALL == m_imageDescription->value().maxFALL);
|
||||
return (
|
||||
(SRC_DESC_VALUE.transferFunction == m_imageDescription->value().transferFunction ||
|
||||
(sdrEOTF == NTransferFunction::TF_FORCED_GAMMA22 && SRC_DESC_VALUE.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_SRGB &&
|
||||
m_imageDescription->value().transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22)) &&
|
||||
SRC_DESC_VALUE.transferFunctionPower == m_imageDescription->value().transferFunctionPower &&
|
||||
(!inHDR() || SRC_DESC_VALUE.luminances == m_imageDescription->value().luminances)
|
||||
// not used by shaders atm
|
||||
// && SRC_DESC_VALUE.masteringLuminances == m_imageDescription->value().masteringLuminances && SRC_DESC_VALUE.maxCLL == m_imageDescription->value().maxCLL && SRC_DESC_VALUE.maxFALL == m_imageDescription->value().maxFALL
|
||||
);
|
||||
}
|
||||
|
||||
bool CMonitor::doesNoShaderCM() {
|
||||
return m_noShaderCTM;
|
||||
}
|
||||
|
||||
static std::vector<uint16_t> resampleInterleavedToKms(const SVCGTTable16& t, size_t gammaSize) {
|
||||
std::vector<uint16_t> out;
|
||||
out.resize(gammaSize * 3);
|
||||
|
||||
//
|
||||
auto sample = [&](int c, float x) -> uint16_t {
|
||||
const float maxX = t.entries - 1;
|
||||
x = std::clamp(x, 0.F, maxX);
|
||||
|
||||
const size_t i0 = (size_t)std::floor(x);
|
||||
const size_t i1 = std::min(i0 + 1, (size_t)t.entries - 1);
|
||||
const float f = x - sc<float>(i0);
|
||||
|
||||
const float v0 = sc<float>(t.ch[c][i0]);
|
||||
const float v1 = sc<float>(t.ch[c][i1]);
|
||||
const float v = v0 + ((v1 - v0) * f);
|
||||
|
||||
int64_t vi = std::round(v);
|
||||
vi = std::clamp(vi, sc<int64_t>(0), sc<int64_t>(65535));
|
||||
return sc<uint16_t>(vi);
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < gammaSize; ++i) {
|
||||
float x = sc<float>(i) * sc<float>(t.entries - 1) / sc<float>(gammaSize - 1);
|
||||
|
||||
const uint16_t r = sample(0, x);
|
||||
const uint16_t g = sample(1, x);
|
||||
const uint16_t b = sample(2, x);
|
||||
|
||||
out[i * 3 + 0] = r;
|
||||
out[i * 3 + 1] = g;
|
||||
out[i * 3 + 2] = b;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void CMonitor::updateVCGTRamps() {
|
||||
auto gammaSize = m_output->getGammaSize();
|
||||
|
||||
if (gammaSize <= 10) {
|
||||
Log::logger->log(Log::DEBUG, "CMonitor::updateVCGTRamps: skipping, no gamma ramp for output");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_imageDescription->value().icc.vcgt) {
|
||||
if (m_vcgtRampsSet)
|
||||
m_output->state->setGammaLut({});
|
||||
|
||||
m_vcgtRampsSet = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// build table
|
||||
auto table = resampleInterleavedToKms(*m_imageDescription->value().icc.vcgt, gammaSize);
|
||||
|
||||
m_output->state->setGammaLut(table);
|
||||
|
||||
m_vcgtRampsSet = true;
|
||||
}
|
||||
|
||||
bool CMonitor::gammaRampsInUse() {
|
||||
return m_vcgtRampsSet;
|
||||
}
|
||||
|
||||
CMonitorState::CMonitorState(CMonitor* owner) : m_owner(owner) {
|
||||
;
|
||||
}
|
||||
|
|
@ -2098,7 +2328,7 @@ bool CMonitorState::commit() {
|
|||
if (!updateSwapchain())
|
||||
return false;
|
||||
|
||||
EMIT_HOOK_EVENT("preMonitorCommit", m_owner->m_self.lock());
|
||||
Event::bus()->m_events.monitor.preCommit.emit(m_owner->m_self.lock());
|
||||
|
||||
ensureBufferPresent();
|
||||
|
||||
|
|
|
|||
|
|
@ -16,13 +16,15 @@
|
|||
#include "math/Math.hpp"
|
||||
#include "../desktop/reserved/ReservedArea.hpp"
|
||||
#include <optional>
|
||||
#include "../protocols/types/ColorManagement.hpp"
|
||||
#include "cm/ColorManagement.hpp"
|
||||
#include "signal/Signal.hpp"
|
||||
#include "DamageRing.hpp"
|
||||
#include <aquamarine/output/Output.hpp>
|
||||
#include <aquamarine/allocator/Swapchain.hpp>
|
||||
#include <hyprutils/os/FileDescriptor.hpp>
|
||||
|
||||
#include "../helpers/TransferFunction.hpp"
|
||||
|
||||
class CMonitorFrameScheduler;
|
||||
|
||||
// Enum for the different types of auto directions, e.g. auto-left, auto-up.
|
||||
|
|
@ -50,10 +52,11 @@ struct SMonitorRule {
|
|||
std::string mirrorOf = "";
|
||||
bool enable10bit = false;
|
||||
NCMType::eCMType cmType = NCMType::CM_SRGB;
|
||||
int sdrEotf = 0;
|
||||
NTransferFunction::eTF sdrEotf = NTransferFunction::TF_DEFAULT;
|
||||
float sdrSaturation = 1.0f; // SDR -> HDR
|
||||
float sdrBrightness = 1.0f; // SDR -> HDR
|
||||
Desktop::CReservedArea reservedArea;
|
||||
std::string iccFile;
|
||||
|
||||
int supportsWideColor = 0; // 0 - auto, 1 - force enable, -1 - force disable
|
||||
int supportsHDR = 0; // 0 - auto, 1 - force enable, -1 - force disable
|
||||
|
|
@ -124,7 +127,7 @@ class CMonitor {
|
|||
bool m_scheduledRecalc = false;
|
||||
wl_output_transform m_transform = WL_OUTPUT_TRANSFORM_NORMAL;
|
||||
float m_xwaylandScale = 1.f;
|
||||
Mat3x3 m_projMatrix;
|
||||
|
||||
std::optional<Vector2D> m_forceSize;
|
||||
SP<Aquamarine::SOutputMode> m_currentMode;
|
||||
SP<Aquamarine::CSwapchain> m_cursorSwapchain;
|
||||
|
|
@ -137,7 +140,7 @@ class CMonitor {
|
|||
bool m_vrrActive = false; // this can be TRUE even if VRR is not active in the case that this display does not support it.
|
||||
bool m_enabled10bit = false; // as above, this can be TRUE even if 10 bit failed.
|
||||
NCMType::eCMType m_cmType = NCMType::CM_SRGB;
|
||||
int m_sdrEotf = 0;
|
||||
NTransferFunction::eTF m_sdrEotf = NTransferFunction::TF_DEFAULT;
|
||||
float m_sdrSaturation = 1.0f;
|
||||
float m_sdrBrightness = 1.0f;
|
||||
float m_sdrMinLuminance = 0.2f;
|
||||
|
|
@ -177,6 +180,7 @@ class CMonitor {
|
|||
|
||||
// for direct scanout
|
||||
PHLWINDOWREF m_lastScanout;
|
||||
bool m_directScanoutIsActive = false; // for cleanup logic. m_lastScanout.expired() can become true before the DS cleanup if client crashes/exits while DS is active.
|
||||
bool m_scanoutNeedsCursorUpdate = false;
|
||||
|
||||
// for special fade/blur
|
||||
|
|
@ -207,6 +211,7 @@ class CMonitor {
|
|||
} m_tearingState;
|
||||
|
||||
struct {
|
||||
CSignalT<> commit;
|
||||
CSignalT<> destroy;
|
||||
CSignalT<> connect;
|
||||
CSignalT<> disconnect;
|
||||
|
|
@ -232,9 +237,8 @@ class CMonitor {
|
|||
DS_BLOCK_SURFACE = (1 << 8),
|
||||
DS_BLOCK_TRANSFORM = (1 << 9),
|
||||
DS_BLOCK_DMA = (1 << 10),
|
||||
DS_BLOCK_TEARING = (1 << 11),
|
||||
DS_BLOCK_FAILED = (1 << 12),
|
||||
DS_BLOCK_CM = (1 << 13),
|
||||
DS_BLOCK_FAILED = (1 << 11),
|
||||
DS_BLOCK_CM = (1 << 12),
|
||||
|
||||
DS_CHECKS_COUNT = 14,
|
||||
};
|
||||
|
|
@ -275,14 +279,15 @@ class CMonitor {
|
|||
TC_SUPPORT = (1 << 4),
|
||||
TC_CANDIDATE = (1 << 5),
|
||||
TC_WINDOW = (1 << 6),
|
||||
TC_HW_CURSOR = (1 << 7),
|
||||
|
||||
TC_CHECKS_COUNT = 7,
|
||||
TC_CHECKS_COUNT = 8,
|
||||
};
|
||||
|
||||
// methods
|
||||
void onConnect(bool noRule);
|
||||
void onDisconnect(bool destroy = false);
|
||||
void applyCMType(NCMType::eCMType cmType, int cmSdrEotf);
|
||||
void applyCMType(NCMType::eCMType cmType, NTransferFunction::eTF cmSdrEotf);
|
||||
bool applyMonitorRule(SMonitorRule* pMonitorRule, bool force = false);
|
||||
void addDamage(const pixman_region32_t* rg);
|
||||
void addDamage(const CRegion& rg);
|
||||
|
|
@ -298,7 +303,6 @@ class CMonitor {
|
|||
void setSpecialWorkspace(const WORKSPACEID& id);
|
||||
void moveTo(const Vector2D& pos);
|
||||
Vector2D middle();
|
||||
void updateMatrix();
|
||||
WORKSPACEID activeWorkspaceID();
|
||||
WORKSPACEID activeSpecialWorkspaceID();
|
||||
CBox logicalBox();
|
||||
|
|
@ -321,17 +325,29 @@ class CMonitor {
|
|||
float minLuminance(float defaultValue = 0);
|
||||
int maxLuminance(int defaultValue = 80);
|
||||
int maxAvgLuminance(int defaultValue = 80);
|
||||
float maxFALL();
|
||||
float maxCLL();
|
||||
|
||||
bool wantsWideColor();
|
||||
bool wantsHDR();
|
||||
|
||||
bool inHDR();
|
||||
bool gammaRampsInUse();
|
||||
|
||||
/// Has an active workspace with a real fullscreen window
|
||||
bool inFullscreenMode();
|
||||
std::optional<NColorManagement::PImageDescription> getFSImageDescription();
|
||||
//
|
||||
const Mat3x3& getTransformMatrix();
|
||||
const Mat3x3& getScaleMatrix();
|
||||
|
||||
bool needsCM();
|
||||
/// Has an active workspace with a real fullscreen window (includes special workspace)
|
||||
bool inFullscreenMode();
|
||||
/// Get fullscreen window from active or special workspace
|
||||
PHLWINDOW getFullscreenWindow();
|
||||
std::optional<NColorManagement::PImageDescription> getFSImageDescription();
|
||||
|
||||
NColorManagement::SPCPRimaries getMasteringPrimaries();
|
||||
NColorManagement::SImageDescription::SPCMasteringLuminances getMasteringLuminances();
|
||||
|
||||
bool needsCM();
|
||||
/// Can do CM without shader
|
||||
bool canNoShaderCM();
|
||||
bool doesNoShaderCM();
|
||||
|
|
@ -342,8 +358,8 @@ class CMonitor {
|
|||
PHLWINDOWREF m_previousFSWindow;
|
||||
bool m_needsHDRupdate = false;
|
||||
|
||||
NColorManagement::PImageDescription m_imageDescription;
|
||||
bool m_noShaderCTM = false; // sets drm CTM, restore needed
|
||||
NColorManagement::PImageDescription m_imageDescription = NColorManagement::CImageDescription::from(NColorManagement::SImageDescription{});
|
||||
bool m_noShaderCTM = false; // sets drm CTM, restore needed
|
||||
|
||||
// For the list lookup
|
||||
|
||||
|
|
@ -351,12 +367,19 @@ class CMonitor {
|
|||
return m_position == rhs.m_position && m_size == rhs.m_size && m_name == rhs.m_name;
|
||||
}
|
||||
|
||||
Mat3x3 m_projMatrix;
|
||||
|
||||
private:
|
||||
void updateMatrix();
|
||||
Mat3x3 m_projOutputMatrix;
|
||||
|
||||
void setupDefaultWS(const SMonitorRule&);
|
||||
WORKSPACEID findAvailableDefaultWS();
|
||||
void commitDPMSState(bool state);
|
||||
void updateVCGTRamps();
|
||||
|
||||
bool m_doneScheduled = false;
|
||||
bool m_vcgtRampsSet = false;
|
||||
std::stack<WORKSPACEID> m_prevWorkSpaces;
|
||||
|
||||
struct {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ CMonitorFrameScheduler::CMonitorFrameScheduler(PHLMONITOR m) : m_monitor(m) {
|
|||
bool CMonitorFrameScheduler::newSchedulingEnabled() {
|
||||
static auto PENABLENEW = CConfigValue<Hyprlang::INT>("render:new_render_scheduling");
|
||||
|
||||
return *PENABLENEW && g_pHyprOpenGL->explicitSyncSupported();
|
||||
return *PENABLENEW && g_pHyprOpenGL->explicitSyncSupported() && m_monitor && !m_monitor->m_directScanoutIsActive;
|
||||
}
|
||||
|
||||
void CMonitorFrameScheduler::onSyncFired() {
|
||||
|
|
|
|||
|
|
@ -38,10 +38,10 @@ int NSystemd::sdNotify(int unsetEnvironment, const char* state) {
|
|||
if (!addr)
|
||||
return 0;
|
||||
|
||||
// address length must be at most this; see man 7 unix
|
||||
size_t addrLen = strnlen(addr, 107);
|
||||
struct sockaddr_un unixAddr = {0};
|
||||
|
||||
size_t addrLen = strnlen(addr, sizeof(unixAddr.sun_path) - 1);
|
||||
|
||||
struct sockaddr_un unixAddr;
|
||||
unixAddr.sun_family = AF_UNIX;
|
||||
strncpy(unixAddr.sun_path, addr, addrLen);
|
||||
if (unixAddr.sun_path[0] == '@')
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue