Compare commits

..

No commits in common. "main" and "v0.53.0" have entirely different histories.

321 changed files with 11659 additions and 22063 deletions

View file

@ -1,8 +1,6 @@
<!-- <!--
BEFORE you submit your PR, please check out the PR guidelines BEFORE you submit your PR, please check out the PR guidelines
on our wiki: https://wiki.hyprland.org/Contributing-and-Debugging/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
--> -->

View file

@ -46,38 +46,11 @@ jobs:
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork
name: "Code Style" name: "Code Style"
runs-on: ubuntu-latest runs-on: ubuntu-latest
container:
image: archlinux
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 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 - name: clang-format check
run: .github/workflows/clang-format-check.sh "." "llvm" "^subprojects$" "" uses: jidicula/clang-format-action@v4.16.0
with:
- name: Save PR head commit SHA exclude-regex: ^subprojects$
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

View file

@ -1,92 +0,0 @@
#!/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"

View file

@ -110,7 +110,6 @@ add_compile_options(
-Wno-narrowing -Wno-narrowing
-Wno-pointer-arith -Wno-pointer-arith
-Wno-clobbered -Wno-clobbered
-frtti
-fmacro-prefix-map=${CMAKE_SOURCE_DIR}/=) -fmacro-prefix-map=${CMAKE_SOURCE_DIR}/=)
# disable lto as it may break plugins # disable lto as it may break plugins
@ -125,7 +124,6 @@ find_package(Threads REQUIRED)
set(GLES_VERSION "GLES3") set(GLES_VERSION "GLES3")
find_package(OpenGL REQUIRED COMPONENTS ${GLES_VERSION}) find_package(OpenGL REQUIRED COMPONENTS ${GLES_VERSION})
find_package(glslang CONFIG REQUIRED)
set(AQUAMARINE_MINIMUM_VERSION 0.9.3) set(AQUAMARINE_MINIMUM_VERSION 0.9.3)
set(HYPRLANG_MINIMUM_VERSION 0.6.7) set(HYPRLANG_MINIMUM_VERSION 0.6.7)
@ -245,7 +243,7 @@ configure_file(
set_source_files_properties(${CMAKE_SOURCE_DIR}/src/version.h PROPERTIES GENERATED TRUE) set_source_files_properties(${CMAKE_SOURCE_DIR}/src/version.h PROPERTIES GENERATED TRUE)
set(XKBCOMMON_MINIMUM_VERSION 1.11.0) set(XKBCOMMON_MINIMUM_VERSION 1.11.0)
set(WAYLAND_SERVER_MINIMUM_VERSION 1.22.91) set(WAYLAND_SERVER_MINIMUM_VERSION 1.22.90)
set(WAYLAND_SERVER_PROTOCOLS_MINIMUM_VERSION 1.45) set(WAYLAND_SERVER_PROTOCOLS_MINIMUM_VERSION 1.45)
set(LIBINPUT_MINIMUM_VERSION 1.28) set(LIBINPUT_MINIMUM_VERSION 1.28)
@ -267,8 +265,7 @@ pkg_check_modules(
gbm gbm
gio-2.0 gio-2.0
re2 re2
muparser muparser)
lcms2)
find_package(hyprwayland-scanner 0.3.10 REQUIRED) find_package(hyprwayland-scanner 0.3.10 REQUIRED)
@ -301,29 +298,27 @@ if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
target_compile_options(hyprland_lib PUBLIC -fsanitize=address) target_compile_options(hyprland_lib PUBLIC -fsanitize=address)
endif() endif()
add_compile_options(-fno-pie -fno-builtin) if(USE_TRACY)
add_link_options(-no-pie -fno-builtin)
if(USE_GPROF)
add_compile_options(-pg)
add_link_options(-pg)
endif()
endif()
if(USE_TRACY)
message(STATUS "Tracy is turned on") message(STATUS "Tracy is turned on")
option(TRACY_ENABLE "" ON) option(TRACY_ENABLE "" ON)
option(TRACY_ON_DEMAND "" ON) option(TRACY_ON_DEMAND "" ON)
add_subdirectory(subprojects/tracy) add_subdirectory(subprojects/tracy)
add_compile_options(-fno-omit-frame-pointer)
target_link_libraries(hyprland_lib PUBLIC Tracy::TracyClient) target_link_libraries(hyprland_lib PUBLIC Tracy::TracyClient)
if(USE_TRACY_GPU) if(USE_TRACY_GPU)
message(STATUS "Tracy GPU Profiling is turned on") message(STATUS "Tracy GPU Profiling is turned on")
add_compile_definitions(USE_TRACY_GPU) add_compile_definitions(USE_TRACY_GPU)
endif() endif()
endif()
add_compile_options(-fno-pie -fno-builtin)
add_link_options(-no-pie -fno-builtin)
if(USE_GPROF)
add_compile_options(-pg)
add_link_options(-pg)
endif()
endif() endif()
if(BUILT_WITH_NIX) if(BUILT_WITH_NIX)
@ -479,11 +474,7 @@ function(protocolWayland)
set(PROTOCOL_SOURCES "${PROTOCOL_SOURCES}" PARENT_SCOPE) set(PROTOCOL_SOURCES "${PROTOCOL_SOURCES}" PARENT_SCOPE)
endfunction() endfunction()
if(TARGET OpenGL::GL) target_link_libraries(hyprland_lib PUBLIC OpenGL::EGL OpenGL::GL Threads::Threads)
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) pkg_check_modules(hyprland_protocols_dep hyprland-protocols>=0.6.4)
if(hyprland_protocols_dep_FOUND) if(hyprland_protocols_dep_FOUND)
@ -558,8 +549,6 @@ protocolnew("staging/ext-data-control" "ext-data-control-v1" false)
protocolnew("staging/pointer-warp" "pointer-warp-v1" false) protocolnew("staging/pointer-warp" "pointer-warp-v1" false)
protocolnew("staging/fifo" "fifo-v1" false) protocolnew("staging/fifo" "fifo-v1" false)
protocolnew("staging/commit-timing" "commit-timing-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() protocolwayland()

View file

@ -1,6 +1,6 @@
BSD 3-Clause License BSD 3-Clause License
Copyright (c) 2022-2026, vaxerski Copyright (c) 2022-2025, vaxerski
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without

View file

@ -18,7 +18,6 @@ nopch:
clear: clear:
rm -rf build rm -rf build
rm -f ./protocols/*.h ./protocols/*.c ./protocols/*.cpp ./protocols/*.hpp rm -f ./protocols/*.h ./protocols/*.c ./protocols/*.cpp ./protocols/*.hpp
rm -f ./hyprctl/hw-protocols/*.cpp ./hyprctl/hw-protocols/*.hpp
all: all:
$(MAKE) clear $(MAKE) clear
@ -88,7 +87,7 @@ asan:
@echo "Wayland done" @echo "Wayland done"
patch -p1 < ./scripts/hyprlandStaticAsan.diff 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 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 --build ./build --config Debug --target all cmake --build ./build --config Debug --target all
@echo "Hyprland done" @echo "Hyprland done"

View file

@ -1 +1 @@
0.54.0 0.53.0

View file

@ -242,7 +242,7 @@ bind = $mainMod, E, exec, $fileManager
bind = $mainMod, V, togglefloating, bind = $mainMod, V, togglefloating,
bind = $mainMod, R, exec, $menu bind = $mainMod, R, exec, $menu
bind = $mainMod, P, pseudo, # dwindle bind = $mainMod, P, pseudo, # dwindle
bind = $mainMod, J, layoutmsg, togglesplit # dwindle bind = $mainMod, J, togglesplit, # dwindle
# Move focus with mainMod + arrow keys # Move focus with mainMod + arrow keys
bind = $mainMod, left, movefocus, l bind = $mainMod, left, movefocus, l

64
flake.lock generated
View file

@ -16,11 +16,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1772292445, "lastModified": 1765900596,
"narHash": "sha256-4F1Q7U313TKUDDovCC96m/Za4wZcJ3yqtu4eSrj8lk8=", "narHash": "sha256-+hn8v9jkkLP9m+o0Nm5SiEq10W0iWDSotH2XfjU45fA=",
"owner": "hyprwm", "owner": "hyprwm",
"repo": "aquamarine", "repo": "aquamarine",
"rev": "1dbbba659c1cef0b0202ce92cadfe13bae550e8f", "rev": "d83c97f8f5c0aae553c1489c7d9eff3eadcadace",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -32,15 +32,15 @@
"flake-compat": { "flake-compat": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1767039857, "lastModified": 1761588595,
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", "narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=",
"owner": "NixOS", "owner": "edolstra",
"repo": "flake-compat", "repo": "flake-compat",
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", "rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "edolstra",
"repo": "flake-compat", "repo": "flake-compat",
"type": "github" "type": "github"
} }
@ -105,11 +105,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1770511807, "lastModified": 1763733840,
"narHash": "sha256-suKmSbSk34uPOJDTg/GbPrKEJutzK08vj0VoTvAFBCA=", "narHash": "sha256-JnET78yl5RvpGuDQy3rCycOCkiKoLr5DN1fPhRNNMco=",
"owner": "hyprwm", "owner": "hyprwm",
"repo": "hyprgraphics", "repo": "hyprgraphics",
"rev": "7c75487edd43a71b61adb01cae8326d277aab683", "rev": "8f1bec691b2d198c60cccabca7a94add2df4ed1a",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -144,11 +144,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1767023960, "lastModified": 1765643131,
"narHash": "sha256-R2HgtVS1G3KSIKAQ77aOZ+Q0HituOmPgXW9nBNkpp3Q=", "narHash": "sha256-CCGohW5EBIRy4B7vTyBMqPgsNcaNenVad/wszfddET0=",
"owner": "hyprwm", "owner": "hyprwm",
"repo": "hyprland-guiutils", "repo": "hyprland-guiutils",
"rev": "c2e906261142f5dd1ee0bfc44abba23e2754c660", "rev": "e50ae912813bdfa8372d62daf454f48d6df02297",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -193,11 +193,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1771866172, "lastModified": 1764612430,
"narHash": "sha256-fYFoXhQLrm1rD8vSFKQBOEX4OGCuJdLt1amKfHd5GAw=", "narHash": "sha256-54ltTSbI6W+qYGMchAgCR6QnC1kOdKXN6X6pJhOWxFg=",
"owner": "hyprwm", "owner": "hyprwm",
"repo": "hyprlang", "repo": "hyprlang",
"rev": "0b219224910e7642eb0ed49f0db5ec3d008e3e41", "rev": "0d00dc118981531aa731150b6ea551ef037acddd",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -261,11 +261,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1771271487, "lastModified": 1766160771,
"narHash": "sha256-41gEiUS0Pyw3L/ge1l8MXn61cK14VAhgWB/JV8s/oNI=", "narHash": "sha256-roINUGikWRqqgKrD4iotKbGj3ZKJl3hjMz5l/SyKrHw=",
"owner": "hyprwm", "owner": "hyprwm",
"repo": "hyprutils", "repo": "hyprutils",
"rev": "340a792e3b3d482c4ae5f66d27a9096bdee6d76d", "rev": "5ac060bfcf2f12b3a6381156ebbc13826a05b09f",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -284,11 +284,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1770501770, "lastModified": 1763640274,
"narHash": "sha256-NWRM6+YxTRv+bT9yvlhhJ2iLae1B1pNH3mAL5wi2rlQ=", "narHash": "sha256-Uan1Nl9i4TF/kyFoHnTq1bd/rsWh4GAK/9/jDqLbY5A=",
"owner": "hyprwm", "owner": "hyprwm",
"repo": "hyprwayland-scanner", "repo": "hyprwayland-scanner",
"rev": "0bd8b6cde9ec27d48aad9e5b4deefb3746909d40", "rev": "f6cf414ca0e16a4d30198fd670ec86df3c89f671",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -310,11 +310,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1771606233, "lastModified": 1766253200,
"narHash": "sha256-F3PLUqQ/TwgR70U+UeOqJnihJZ2EuunzojYC4g5xHr0=", "narHash": "sha256-26qPwrd3od+xoYVywSB7hC2cz9ivN46VPLlrsXyGxvE=",
"owner": "hyprwm", "owner": "hyprwm",
"repo": "hyprwire", "repo": "hyprwire",
"rev": "06c7f1f8c4194786c8400653c4efc49dc14c0f3a", "rev": "1079777525b30a947c8d657fac158e00ae85de9d",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -325,11 +325,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1772198003, "lastModified": 1766070988,
"narHash": "sha256-I45esRSssFtJ8p/gLHUZ1OUaaTaVLluNkABkk6arQwE=", "narHash": "sha256-G/WVghka6c4bAzMhTwT2vjLccg/awmHkdKSd2JrycLc=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "dd9b079222d43e1943b6ebd802f04fd959dc8e61", "rev": "c6245e83d836d0433170a16eb185cefe0572f8b8",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -348,11 +348,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1772024342, "lastModified": 1765911976,
"narHash": "sha256-+eXlIc4/7dE6EcPs9a2DaSY3fTA9AE526hGqkNID3Wg=", "narHash": "sha256-t3T/xm8zstHRLx+pIHxVpQTiySbKqcQbK+r+01XVKc0=",
"owner": "cachix", "owner": "cachix",
"repo": "git-hooks.nix", "repo": "git-hooks.nix",
"rev": "6e34e97ed9788b17796ee43ccdbaf871a5c2b476", "rev": "b68b780b69702a090c8bb1b973bab13756cc7a27",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -88,28 +88,23 @@
}; };
}; };
outputs = outputs = inputs @ {
inputs@{
self, self,
nixpkgs, nixpkgs,
systems, systems,
... ...
}: }: let
let
inherit (nixpkgs) lib; inherit (nixpkgs) lib;
eachSystem = lib.genAttrs (import systems); eachSystem = lib.genAttrs (import systems);
pkgsFor = eachSystem ( pkgsFor = eachSystem (system:
system:
import nixpkgs { import nixpkgs {
localSystem = system; localSystem = system;
overlays = with self.overlays; [ overlays = with self.overlays; [
hyprland-packages hyprland-packages
hyprland-extras hyprland-extras
]; ];
} });
); pkgsCrossFor = eachSystem (system: crossSystem:
pkgsCrossFor = eachSystem (
system: crossSystem:
import nixpkgs { import nixpkgs {
localSystem = system; localSystem = system;
inherit crossSystem; inherit crossSystem;
@ -117,36 +112,29 @@
hyprland-packages hyprland-packages
hyprland-extras hyprland-extras
]; ];
} });
); pkgsDebugFor = eachSystem (system:
pkgsDebugFor = eachSystem (
system:
import nixpkgs { import nixpkgs {
localSystem = system; localSystem = system;
overlays = with self.overlays; [ overlays = with self.overlays; [
hyprland-debug hyprland-debug
]; ];
} });
); pkgsDebugCrossFor = eachSystem (system: crossSystem:
pkgsDebugCrossFor = eachSystem (
system: crossSystem:
import nixpkgs { import nixpkgs {
localSystem = system; localSystem = system;
inherit crossSystem; inherit crossSystem;
overlays = with self.overlays; [ overlays = with self.overlays; [
hyprland-debug hyprland-debug
]; ];
} });
); in {
in overlays = import ./nix/overlays.nix {inherit self lib inputs;};
{
overlays = import ./nix/overlays.nix { inherit self lib inputs; };
checks = eachSystem ( checks = eachSystem (system:
system: (lib.filterAttrs
(lib.filterAttrs ( (n: _: (lib.hasPrefix "hyprland" n) && !(lib.hasSuffix "debug" n))
n: _: (lib.hasPrefix "hyprland" n) && !(lib.hasSuffix "debug" n) self.packages.${system})
) self.packages.${system})
// { // {
inherit (self.packages.${system}) xdg-desktop-portal-hyprland; inherit (self.packages.${system}) xdg-desktop-portal-hyprland;
pre-commit-check = inputs.pre-commit-hooks.lib.${system}.run { pre-commit-check = inputs.pre-commit-hooks.lib.${system}.run {
@ -156,18 +144,18 @@
enable = true; enable = true;
entry = "${self.formatter.${system}}/bin/hyprland-treewide-formatter"; entry = "${self.formatter.${system}}/bin/hyprland-treewide-formatter";
pass_filenames = false; pass_filenames = false;
excludes = [ "subprojects" ]; excludes = ["subprojects"];
always_run = true; always_run = true;
}; };
}; };
}; };
} }
// (import ./nix/tests inputs pkgsFor.${system}) // (import ./nix/tests inputs pkgsFor.${system}));
);
packages = eachSystem (system: { packages = eachSystem (system: {
default = self.packages.${system}.hyprland; default = self.packages.${system}.hyprland;
inherit (pkgsFor.${system}) inherit
(pkgsFor.${system})
# hyprland-packages # hyprland-packages
hyprland hyprland
hyprland-unwrapped hyprland-unwrapped
@ -182,20 +170,18 @@
devShells = eachSystem (system: { devShells = eachSystem (system: {
default = default =
pkgsFor.${system}.mkShell.override pkgsFor.${system}.mkShell.override {
{
inherit (self.packages.${system}.default) stdenv; inherit (self.packages.${system}.default) stdenv;
} } {
{
name = "hyprland-shell"; name = "hyprland-shell";
hardeningDisable = [ "fortify" ]; hardeningDisable = ["fortify"];
inputsFrom = [ pkgsFor.${system}.hyprland ]; inputsFrom = [pkgsFor.${system}.hyprland];
packages = [ pkgsFor.${system}.clang-tools ]; packages = [pkgsFor.${system}.clang-tools];
inherit (self.checks.${system}.pre-commit-check) shellHook; 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; nixosModules.default = import ./nix/module.nix inputs;
homeManagerModules.default = import ./nix/hm-module.nix self; homeManagerModules.default = import ./nix/hm-module.nix self;

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<protocol name="hyprpaper_core" version="2"> <protocol name="hyprpaper_core" version="1">
<copyright> <copyright>
BSD 3-Clause License BSD 3-Clause License
@ -31,7 +31,7 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</copyright> </copyright>
<object name="hyprpaper_core_manager" version="2"> <object name="hyprpaper_core_manager" version="1">
<description summary="manager object"> <description summary="manager object">
This is the core manager object for hyprpaper operations This is the core manager object for hyprpaper operations
</description> </description>
@ -62,13 +62,6 @@
Destroys this object. Children remain alive until destroyed. Destroys this object. Children remain alive until destroyed.
</description> </description>
</c2s> </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> </object>
<enum name="wallpaper_fit_mode"> <enum name="wallpaper_fit_mode">
@ -148,25 +141,4 @@
</description> </description>
</c2s> </c2s>
</object> </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> </protocol>

View file

@ -4,7 +4,6 @@
#include <optional> #include <optional>
#include <format> #include <format>
#include <filesystem> #include <filesystem>
#include <print>
#include <hyprpaper_core-client.hpp> #include <hyprpaper_core-client.hpp>
@ -16,7 +15,7 @@ using namespace std::string_literals;
constexpr const char* SOCKET_NAME = ".hyprpaper.sock"; constexpr const char* SOCKET_NAME = ".hyprpaper.sock";
static SP<CCHyprpaperCoreImpl> g_coreImpl; static SP<CCHyprpaperCoreImpl> g_coreImpl;
constexpr const uint32_t PROTOCOL_VERSION_SUPPORTED = 2; constexpr const uint32_t PROTOCOL_VERSION_SUPPORTED = 1;
// //
static hyprpaperCoreWallpaperFitMode fitFromString(const std::string_view& sv) { static hyprpaperCoreWallpaperFitMode fitFromString(const std::string_view& sv) {
@ -54,7 +53,21 @@ static std::expected<std::string, std::string> getFullPath(const std::string_vie
return resolvePath(sv); return resolvePath(sv);
} }
static std::expected<void, std::string> doWallpaper(const std::string_view& RHS) { std::expected<void, std::string> Hyprpaper::makeHyprpaperRequest(const std::string_view& rq) {
if (!rq.contains(' '))
return std::unexpected("Invalid request");
if (!rq.starts_with("/hyprpaper "))
return std::unexpected("Invalid request");
std::string_view LHS, RHS;
auto spacePos = rq.find(' ', 12);
LHS = rq.substr(11, spacePos - 11);
RHS = rq.substr(spacePos + 1);
if (LHS != "wallpaper")
return std::unexpected("Unknown hyprpaper request");
CVarList2 args(std::string{RHS}, 0, ','); CVarList2 args(std::string{RHS}, 0, ',');
const std::string MONITOR = std::string{args[0]}; const std::string MONITOR = std::string{args[0]};
@ -86,7 +99,7 @@ static std::expected<void, std::string> doWallpaper(const std::string_view& RHS)
if (!socket) if (!socket)
return std::unexpected("can't send: failed to connect to hyprpaper (is it running?)"); return std::unexpected("can't send: failed to connect to hyprpaper (is it running?)");
g_coreImpl = makeShared<CCHyprpaperCoreImpl>(PROTOCOL_VERSION_SUPPORTED); g_coreImpl = makeShared<CCHyprpaperCoreImpl>(1);
socket->addImplementation(g_coreImpl); socket->addImplementation(g_coreImpl);
@ -98,7 +111,7 @@ static std::expected<void, std::string> doWallpaper(const std::string_view& RHS)
if (!spec) if (!spec)
return std::unexpected("can't send: hyprpaper doesn't have the spec?!"); return std::unexpected("can't send: hyprpaper doesn't have the spec?!");
auto manager = makeShared<CCHyprpaperCoreManagerObject>(socket->bindProtocol(g_coreImpl->protocol(), std::min(PROTOCOL_VERSION_SUPPORTED, spec->specVer()))); auto manager = makeShared<CCHyprpaperCoreManagerObject>(socket->bindProtocol(g_coreImpl->protocol(), PROTOCOL_VERSION_SUPPORTED));
if (!manager) if (!manager)
return std::unexpected("wire error: couldn't create manager"); return std::unexpected("wire error: couldn't create manager");
@ -113,11 +126,7 @@ static std::expected<void, std::string> doWallpaper(const std::string_view& RHS)
wallpaper->setFailed([&canExit, &err](uint32_t code) { wallpaper->setFailed([&canExit, &err](uint32_t code) {
canExit = true; canExit = true;
switch (code) { err = std::format("failed to set wallpaper, code {}", 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; }); wallpaper->setSuccess([&canExit]() { canExit = true; });
@ -137,72 +146,3 @@ static std::expected<void, std::string> doWallpaper(const std::string_view& RHS)
return {}; 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 {};
}

View file

@ -228,9 +228,6 @@ int request(std::string_view arg, int minArgs = 0, bool needRoll = false) {
constexpr size_t BUFFER_SIZE = 8192; constexpr size_t BUFFER_SIZE = 8192;
char buffer[BUFFER_SIZE] = {0}; char buffer[BUFFER_SIZE] = {0};
// 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); sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE);
if (sizeWritten < 0) { if (sizeWritten < 0) {
@ -240,11 +237,14 @@ int request(std::string_view arg, int minArgs = 0, bool needRoll = false) {
return 6; return 6;
} }
if (sizeWritten == 0) { reply += std::string(buffer, sizeWritten);
// server closed connection, we're done
break;
}
while (sizeWritten == BUFFER_SIZE) {
sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE);
if (sizeWritten < 0) {
log("Couldn't read (6)");
return 6;
}
reply += std::string(buffer, sizeWritten); reply += std::string(buffer, sizeWritten);
} }

View file

@ -11,9 +11,9 @@ set(CMAKE_CXX_STANDARD 23)
pkg_check_modules(hyprpm_deps REQUIRED IMPORTED_TARGET tomlplusplus hyprutils>=0.7.0) pkg_check_modules(hyprpm_deps REQUIRED IMPORTED_TARGET tomlplusplus hyprutils>=0.7.0)
find_package(glaze 6.0.1 QUIET) find_package(glaze 6.0.0 QUIET)
if (NOT glaze_FOUND) if (NOT glaze_FOUND)
set(GLAZE_VERSION v6.0.1) set(GLAZE_VERSION v6.1.0)
message(STATUS "glaze dependency not found, retrieving ${GLAZE_VERSION} with FetchContent") message(STATUS "glaze dependency not found, retrieving ${GLAZE_VERSION} with FetchContent")
include(FetchContent) include(FetchContent)
FetchContent_Declare( FetchContent_Declare(
@ -21,7 +21,6 @@ if (NOT glaze_FOUND)
GIT_REPOSITORY https://github.com/stephenberry/glaze.git GIT_REPOSITORY https://github.com/stephenberry/glaze.git
GIT_TAG ${GLAZE_VERSION} GIT_TAG ${GLAZE_VERSION}
GIT_SHALLOW TRUE GIT_SHALLOW TRUE
EXCLUDE_FROM_ALL
) )
FetchContent_MakeAvailable(glaze) FetchContent_MakeAvailable(glaze)
endif() endif()

View file

@ -94,18 +94,15 @@ SHyprlandVersion CPluginManager::getHyprlandVersion(bool running) {
auto hldate = (*jsonQuery)["commit_date"].get_string(); auto hldate = (*jsonQuery)["commit_date"].get_string();
auto hlcommits = (*jsonQuery)["commits"].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; size_t commits = 0;
try { try {
commits = std::stoull(hlcommits); commits = std::stoull(hlcommits);
} catch (...) { ; } } catch (...) { ; }
if (m_bVerbose) if (m_bVerbose)
std::println("{}", verboseString("parsed commit {} at branch {} on {}, commits {}, nix: {}", hlcommit, hlbranch, hldate, commits, isNix)); std::println("{}", verboseString("parsed commit {} at branch {} on {}, commits {}", hlcommit, hlbranch, hldate, commits));
auto ver = SHyprlandVersion{hlbranch, hlcommit, hldate, abiHash, commits, isNix}; auto ver = SHyprlandVersion{hlbranch, hlcommit, hldate, abiHash, commits};
if (running) if (running)
verRunning = ver; verRunning = ver;
@ -131,20 +128,12 @@ bool CPluginManager::createSafeDirectory(const std::string& path) {
return true; 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) { bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string& rev) {
const auto HLVER = getHyprlandVersion(); const auto HLVER = getHyprlandVersion();
if (!validArg(url) || !validArg(rev)) {
std::println(stderr, "\n{}", failureString("url or rev invalid"));
return false;
}
if (!hasDeps()) { if (!hasDeps()) {
std::println(stderr, "\n{}", failureString("Could not clone the plugin repository. Dependencies not satisfied. Hyprpm requires: cmake, cpio, pkg-config, git, g++, gcc")); std::println(stderr, "\n{}",
failureString("Could not clone the plugin repository. Dependencies not satisfied. Hyprpm requires: cmake, meson, cpio, pkg-config, git, g++, gcc"));
return false; return false;
} }
@ -207,7 +196,7 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
progress.printMessageAbove(infoString("Cloning {}", url)); 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")) { if (!std::filesystem::exists(m_szWorkingPluginDirectory + "/.git")) {
std::println(stderr, "\n{}", failureString("Could not clone the plugin repository. shell returned:\n{}", ret)); std::println(stderr, "\n{}", failureString("Could not clone the plugin repository. shell returned:\n{}", ret));
@ -314,14 +303,8 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
progress.printMessageAbove(infoString("Building {}", p.name)); progress.printMessageAbove(infoString("Building {}", p.name));
for (auto const& bs : p.buildSteps) { for (auto const& bs : p.buildSteps) {
const auto CMD_RAW = nixDevelopIfNeeded(std::format("cd {} && PKG_CONFIG_PATH=\"{}\" {}", m_szWorkingPluginDirectory, getPkgConfigPath(), bs), HLVER); const std::string& cmd = std::format("cd {} && PKG_CONFIG_PATH=\"{}/share/pkgconfig\" {}", m_szWorkingPluginDirectory, DataState::getHeadersPath(), bs);
out += " -> " + cmd + "\n" + execAndGet(cmd) + "\n";
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) if (m_bVerbose)
@ -406,7 +389,7 @@ eHeadersErrors CPluginManager::headersValid() {
return HEADERS_MISSING; return HEADERS_MISSING;
// find headers commit // find headers commit
const std::string& cmd = std::format("PKG_CONFIG_PATH=\"{}\" pkgconf --cflags --keep-system-cflags hyprland", getPkgConfigPath()); const std::string& cmd = std::format("PKG_CONFIG_PATH=\"{}/share/pkgconfig\" pkgconf --cflags --keep-system-cflags hyprland", DataState::getHeadersPath());
auto headers = execAndGet(cmd); auto headers = execAndGet(cmd);
if (!headers.contains("-I/")) if (!headers.contains("-I/"))
@ -470,7 +453,7 @@ bool CPluginManager::updateHeaders(bool force) {
const auto HLVER = getHyprlandVersion(false); const auto HLVER = getHyprlandVersion(false);
if (!hasDeps()) { if (!hasDeps()) {
std::println("\n{}", failureString("Could not update. Dependencies not satisfied. Hyprpm requires: cmake, cpio, pkg-config, git, g++, gcc")); std::println("\n{}", failureString("Could not update. Dependencies not satisfied. Hyprpm requires: cmake, meson, cpio, pkg-config, git, g++, gcc"));
return false; return false;
} }
@ -512,11 +495,11 @@ bool CPluginManager::updateHeaders(bool force) {
progress.printMessageAbove(verboseString("will shallow since: {}", SHALLOW_DATE)); progress.printMessageAbove(verboseString("will shallow since: {}", SHALLOW_DATE));
std::string ret = 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)) { if (!std::filesystem::exists(WORKINGDIR)) {
progress.printMessageAbove(failureString("Clone failed. Retrying without shallow.")); 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")) { if (!std::filesystem::exists(WORKINGDIR + "/.git")) {
@ -559,17 +542,8 @@ bool CPluginManager::updateHeaders(bool force) {
if (m_bVerbose) if (m_bVerbose)
progress.printMessageAbove(verboseString("setting PREFIX for cmake to {}", DataState::getHeadersPath())); progress.printMessageAbove(verboseString("setting PREFIX for cmake to {}", DataState::getHeadersPath()));
const auto CONFIGURE_CMD = 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,
nixDevelopIfNeeded(std::format("cd {} && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:STRING=\"{}\" -S . -B ./build", WORKINGDIR, DataState::getHeadersPath()));
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) if (m_bVerbose)
progress.printMessageAbove(verboseString("cmake returned: {}", ret)); progress.printMessageAbove(verboseString("cmake returned: {}", ret));
@ -657,7 +631,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
const auto HLVER = getHyprlandVersion(false); const auto HLVER = getHyprlandVersion(false);
CProgressBar progress; CProgressBar progress;
progress.m_iMaxSteps = (REPOS.size() * 2) + 2; progress.m_iMaxSteps = REPOS.size() * 2 + 2;
progress.m_iSteps = 0; progress.m_iSteps = 0;
progress.m_szCurrentMessage = "Updating repositories"; progress.m_szCurrentMessage = "Updating repositories";
progress.print(); progress.print();
@ -678,7 +652,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
progress.printMessageAbove(infoString("Cloning {}", repo.url)); 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")) { if (!std::filesystem::exists(m_szWorkingPluginDirectory + "/.git")) {
std::println("{}", failureString("could not clone repo: shell returned: {}", ret)); std::println("{}", failureString("could not clone repo: shell returned: {}", ret));
@ -688,7 +662,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
if (!repo.rev.empty()) { if (!repo.rev.empty()) {
progress.printMessageAbove(infoString("Plugin has revision set, resetting: {}", repo.rev)); 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) { if (ret.compare(0, 6, "fatal:") == 0) {
std::println(stderr, "\n{}", failureString("could not check out revision {}: shell returned:\n{}", repo.rev, ret)); std::println(stderr, "\n{}", failureString("could not check out revision {}: shell returned:\n{}", repo.rev, ret));
@ -767,14 +741,8 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
progress.printMessageAbove(infoString("Building {}", p.name)); progress.printMessageAbove(infoString("Building {}", p.name));
for (auto const& bs : p.buildSteps) { for (auto const& bs : p.buildSteps) {
const auto CMD_RAW = nixDevelopIfNeeded(std::format("cd {} && PKG_CONFIG_PATH=\"{}\" {}", m_szWorkingPluginDirectory, getPkgConfigPath(), bs), HLVER); const std::string& cmd = std::format("cd {} && PKG_CONFIG_PATH=\"{}/share/pkgconfig\" {}", m_szWorkingPluginDirectory, DataState::getHeadersPath(), bs);
out += " -> " + cmd + "\n" + execAndGet(cmd) + "\n";
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) if (m_bVerbose)
@ -804,8 +772,8 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
repohash.pop_back(); repohash.pop_back();
newrepo.hash = repohash; newrepo.hash = repohash;
for (auto const& p : pManifest->m_plugins) { for (auto const& p : pManifest->m_plugins) {
const auto OLDPLUGINIT = std::ranges::find_if(repo.plugins, [&](const auto& other) { return other.name == p.name; }); const auto OLDPLUGINIT = std::find_if(repo.plugins.begin(), repo.plugins.end(), [&](const auto& other) { return other.name == p.name; });
newrepo.plugins.emplace_back(SPlugin{p.name, m_szWorkingPluginDirectory + "/" + p.output, OLDPLUGINIT != repo.plugins.end() ? OLDPLUGINIT->enabled : false}); newrepo.plugins.push_back(SPlugin{p.name, m_szWorkingPluginDirectory + "/" + p.output, OLDPLUGINIT != repo.plugins.end() ? OLDPLUGINIT->enabled : false});
} }
DataState::removePluginRepo(SPluginRepoIdentifier::fromName(newrepo.name)); DataState::removePluginRepo(SPluginRepoIdentifier::fromName(newrepo.name));
DataState::addNewPluginRepo(newrepo); DataState::addNewPluginRepo(newrepo);
@ -931,7 +899,7 @@ ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState(bool forceReload)
if (!p.enabled) if (!p.enabled)
continue; continue;
if (!forceReload && std::ranges::find_if(loadedPlugins, [&](const auto& other) { return other == p.name; }) != loadedPlugins.end()) if (!forceReload && std::find_if(loadedPlugins.begin(), loadedPlugins.end(), [&](const auto& other) { return other == p.name; }) != loadedPlugins.end())
continue; continue;
if (!loadUnloadPlugin(HYPRPMPATH / repoForName(p.name) / p.filename, true)) { if (!loadUnloadPlugin(HYPRPMPATH / repoForName(p.name) / p.filename, true)) {
@ -1019,11 +987,8 @@ std::string CPluginManager::headerErrorShort(const eHeadersErrors err) {
} }
bool CPluginManager::hasDeps() { bool CPluginManager::hasDeps() {
if (!m_bNoNix && getHyprlandVersion().isNix)
return true; // dep check not needed if we are on nix
bool hasAllDeps = true; bool hasAllDeps = true;
std::vector<std::string> deps = {"cpio", "cmake", "pkg-config", "g++", "gcc", "git"}; std::vector<std::string> deps = {"meson", "cpio", "cmake", "pkg-config", "g++", "gcc", "git"};
for (auto const& d : deps) { for (auto const& d : deps) {
if (!execAndGet("command -v " + d).contains("/")) { if (!execAndGet("command -v " + d).contains("/")) {
@ -1034,92 +999,3 @@ bool CPluginManager::hasDeps() {
return hasAllDeps; 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");
}

View file

@ -4,7 +4,7 @@
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <string> #include <string>
#include <expected> #include <utility>
#include "Plugin.hpp" #include "Plugin.hpp"
enum eHeadersErrors { enum eHeadersErrors {
@ -41,7 +41,6 @@ struct SHyprlandVersion {
std::string date; std::string date;
std::string abiHash; std::string abiHash;
int commits = 0; int commits = 0;
bool isNix = false;
}; };
class CPluginManager { class CPluginManager {
@ -66,14 +65,11 @@ class CPluginManager {
void notify(const eNotifyIcons icon, uint32_t color, int durationMs, const std::string& message); void notify(const eNotifyIcons icon, uint32_t color, int durationMs, const std::string& message);
const std::string& getPkgConfigPath();
bool hasDeps(); bool hasDeps();
bool m_bVerbose = false; bool m_bVerbose = false;
bool m_bNoShallow = false; bool m_bNoShallow = false;
bool m_bNoNix = false; std::string m_szCustomHlUrl, m_szUsername;
std::string m_szCustomHlUrl, m_szUsername, m_szArgv0;
// will delete recursively if exists!! // will delete recursively if exists!!
bool createSafeDirectory(const std::string& path); bool createSafeDirectory(const std::string& path);
@ -81,9 +77,6 @@ class CPluginManager {
private: private:
std::string headerError(const eHeadersErrors err); std::string headerError(const eHeadersErrors err);
std::string headerErrorShort(const eHeadersErrors err); std::string headerErrorShort(const eHeadersErrors err);
bool validArg(const std::string& s);
std::expected<std::string, std::string> nixDevelopIfNeeded(const std::string& cmd, const SHyprlandVersion& ver);
std::string m_szWorkingPluginDirectory; std::string m_szWorkingPluginDirectory;
}; };

View file

@ -35,13 +35,9 @@ static std::string validSubinsAsStr() {
} }
static bool executableExistsInPath(const std::string& exe) { 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"); const char* PATHENV = std::getenv("PATH");
if (!PATHENV) if (!PATHENV)
return std::nullopt; return false;
CVarList paths(PATHENV, 0, ':', true); CVarList paths(PATHENV, 0, ':', true);
std::error_code ec; std::error_code ec;
@ -56,10 +52,10 @@ std::optional<std::string> NSys::findInPath(const std::string& exe) {
if (ec) if (ec)
continue; continue;
if ((perms & std::filesystem::perms::others_exec) != std::filesystem::perms::none) if ((perms & std::filesystem::perms::others_exec) != std::filesystem::perms::none)
return candidate.string(); return true;
} }
return std::nullopt; return false;
} }
static std::string subin() { static std::string subin() {

View file

@ -1,13 +1,11 @@
#pragma once #pragma once
#include <string> #include <string>
#include <optional>
namespace NSys { namespace NSys {
bool isSuperuser(); bool isSuperuser();
int getUID(); int getUID();
int getEUID(); int getEUID();
std::optional<std::string> findInPath(const std::string& exe);
// NOLINTNEXTLINE // NOLINTNEXTLINE
namespace root { namespace root {

View file

@ -25,7 +25,6 @@ constexpr std::string_view HELP = R"#(┏ hyprpm, a Hyprland Plugin Manager
Flags: Flags:
--no-nix | Disable `nix develop` for build commands, even if Hyprland is nix.
--notify | -n Send a hyprland notification confirming successful plugin load. --notify | -n Send a hyprland notification confirming successful plugin load.
Warnings/Errors trigger notifications regardless of this flag. Warnings/Errors trigger notifications regardless of this flag.
--help | -h Show this menu. --help | -h Show this menu.
@ -48,7 +47,7 @@ int main(int argc, char** argv, char** envp) {
} }
std::vector<std::string> command; std::vector<std::string> command;
bool notify = false, verbose = false, force = false, noShallow = false, noNix = false; bool notify = false, verbose = false, force = false, noShallow = false;
std::string customHlUrl; std::string customHlUrl;
for (int i = 1; i < argc; ++i) { for (int i = 1; i < argc; ++i) {
@ -64,8 +63,6 @@ int main(int argc, char** argv, char** envp) {
g_pPluginManager->notify(ICON_INFO, 0, 10000, "[hyprpm] -n flag is deprecated, see hyprpm --help."); g_pPluginManager->notify(ICON_INFO, 0, 10000, "[hyprpm] -n flag is deprecated, see hyprpm --help.");
} else if (ARGS[i] == "--verbose" || ARGS[i] == "-v") { } else if (ARGS[i] == "--verbose" || ARGS[i] == "-v") {
verbose = true; verbose = true;
} else if (ARGS[i] == "--no-nix") {
noNix = true;
} else if (ARGS[i] == "--no-shallow" || ARGS[i] == "-s") { } else if (ARGS[i] == "--no-shallow" || ARGS[i] == "-s") {
noShallow = true; noShallow = true;
} else if (ARGS[i] == "--hl-url") { } else if (ARGS[i] == "--hl-url") {
@ -94,9 +91,7 @@ int main(int argc, char** argv, char** envp) {
g_pPluginManager = std::make_unique<CPluginManager>(); g_pPluginManager = std::make_unique<CPluginManager>();
g_pPluginManager->m_bVerbose = verbose; g_pPluginManager->m_bVerbose = verbose;
g_pPluginManager->m_bNoShallow = noShallow; g_pPluginManager->m_bNoShallow = noShallow;
g_pPluginManager->m_bNoNix = noNix;
g_pPluginManager->m_szCustomHlUrl = customHlUrl; g_pPluginManager->m_szCustomHlUrl = customHlUrl;
g_pPluginManager->m_szArgv0 = argv[0];
if (command[0] == "add") { if (command[0] == "add") {
if (command.size() < 2) { if (command.size() < 2) {

View file

@ -96,9 +96,7 @@ endfunction()
protocolnew("staging/pointer-warp" "pointer-warp-v1" false) protocolnew("staging/pointer-warp" "pointer-warp-v1" false)
protocolnew("stable/xdg-shell" "xdg-shell" 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-warp" PROTOS "pointer-warp-v1" "xdg-shell")
clientNew("pointer-scroll" PROTOS "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")

View file

@ -3,7 +3,6 @@
#include <sys/mman.h> #include <sys/mman.h>
#include <fcntl.h> #include <fcntl.h>
#include <stdio.h> #include <stdio.h>
#include <unistd.h>
#include <wayland-client.h> #include <wayland-client.h>
#include <wayland.hpp> #include <wayland.hpp>

View file

@ -2,7 +2,6 @@
#include <sys/poll.h> #include <sys/poll.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <fcntl.h> #include <fcntl.h>
#include <unistd.h>
#include <print> #include <print>
#include <format> #include <format>
#include <string> #include <string>

View file

@ -2,7 +2,6 @@
#include <sys/poll.h> #include <sys/poll.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <fcntl.h> #include <fcntl.h>
#include <unistd.h>
#include <print> #include <print>
#include <format> #include <format>
#include <string> #include <string>

View file

@ -1,297 +0,0 @@
#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;
}

View file

@ -6,16 +6,15 @@
#define private public #define private public
#include <src/config/ConfigManager.hpp> #include <src/config/ConfigManager.hpp>
#include <src/config/ConfigDescriptions.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/input/InputManager.hpp>
#include <src/managers/PointerManager.hpp> #include <src/managers/PointerManager.hpp>
#include <src/managers/input/trackpad/TrackpadGestures.hpp> #include <src/managers/input/trackpad/TrackpadGestures.hpp>
#include <src/desktop/rule/windowRule/WindowRuleEffectContainer.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/rule/windowRule/WindowRuleApplicator.hpp>
#include <src/desktop/view/LayerSurface.hpp>
#include <src/Compositor.hpp> #include <src/Compositor.hpp>
#include <src/desktop/state/FocusState.hpp> #include <src/desktop/state/FocusState.hpp>
#include <src/layout/LayoutManager.hpp>
#undef private #undef private
#include <hyprutils/utils/ScopeGuard.hpp> #include <hyprutils/utils/ScopeGuard.hpp>
@ -54,9 +53,8 @@ static SDispatchResult snapMove(std::string in) {
Vector2D pos = PLASTWINDOW->m_realPosition->goal(); Vector2D pos = PLASTWINDOW->m_realPosition->goal();
Vector2D size = PLASTWINDOW->m_realSize->goal(); Vector2D size = PLASTWINDOW->m_realSize->goal();
g_layoutManager->performSnap(pos, size, PLASTWINDOW->layoutTarget(), MBIND_MOVE, -1, size); g_pLayoutManager->getCurrentLayout()->performSnap(pos, size, PLASTWINDOW, MBIND_MOVE, -1, size);
*PLASTWINDOW->m_realPosition = pos.round();
PLASTWINDOW->layoutTarget()->setPositionGlobal(CBox{pos, size});
return {}; return {};
} }
@ -272,85 +270,32 @@ static SDispatchResult keybind(std::string in) {
return {}; return {};
} }
static Desktop::Rule::CWindowRuleEffectContainer::storageType windowRuleIDX = 0; static Desktop::Rule::CWindowRuleEffectContainer::storageType ruleIDX = 0;
// //
static SDispatchResult addWindowRule(std::string in) { static SDispatchResult addRule(std::string in) {
windowRuleIDX = Desktop::Rule::windowEffects()->registerEffect("plugin_rule"); ruleIDX = Desktop::Rule::windowEffects()->registerEffect("plugin_rule");
if (Desktop::Rule::windowEffects()->registerEffect("plugin_rule") != windowRuleIDX) if (Desktop::Rule::windowEffects()->registerEffect("plugin_rule") != ruleIDX)
return {.success = false, .error = "re-registering returned a different id?"}; return {.success = false, .error = "re-registering returned a different id?"};
return {}; return {};
} }
static SDispatchResult checkWindowRule(std::string in) { static SDispatchResult checkRule(std::string in) {
const auto PLASTWINDOW = Desktop::focusState()->window(); const auto PLASTWINDOW = Desktop::focusState()->window();
if (!PLASTWINDOW) if (!PLASTWINDOW)
return {.success = false, .error = "No window"}; return {.success = false, .error = "No window"};
if (!PLASTWINDOW->m_ruleApplicator->m_otherProps.props.contains(windowRuleIDX)) if (!PLASTWINDOW->m_ruleApplicator->m_otherProps.props.contains(ruleIDX))
return {.success = false, .error = "No rule"}; return {.success = false, .error = "No rule"};
if (PLASTWINDOW->m_ruleApplicator->m_otherProps.props[windowRuleIDX]->effect != "effect") if (PLASTWINDOW->m_ruleApplicator->m_otherProps.props[ruleIDX]->effect != "effect")
return {.success = false, .error = "Effect isn't \"effect\""}; return {.success = false, .error = "Effect isn't \"effect\""};
return {}; 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) { APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
PHANDLE = handle; PHANDLE = handle;
@ -362,11 +307,8 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:scroll", ::scroll); HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:scroll", ::scroll);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:click", ::click); HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:click", ::click);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:keybind", ::keybind); HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:keybind", ::keybind);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:add_window_rule", ::addWindowRule); HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:add_rule", ::addRule);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:check_window_rule", ::checkWindowRule); HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:check_rule", ::checkRule);
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 // init mouse
g_mouse = CTestMouse::create(false); g_mouse = CTestMouse::create(false);

View file

@ -39,16 +39,6 @@ namespace Colors {
TESTS_PASSED++; \ 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) \ #define EXPECT_VECTOR2D(expr, val) \
do { \ do { \
const auto& RESULT = expr; \ const auto& RESULT = expr; \

View file

@ -8,7 +8,6 @@
#include <hyprutils/os/Process.hpp> #include <hyprutils/os/Process.hpp>
#include <sys/poll.h> #include <sys/poll.h>
#include <unistd.h>
#include <csignal> #include <csignal>
#include <thread> #include <thread>
@ -118,33 +117,6 @@ static bool test() {
Tests::killAllWindows(); Tests::killAllWindows();
EXPECT(Tests::windowCount(), 0); 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; return !ret;
} }

View file

@ -8,7 +8,6 @@
#include <hyprutils/os/Process.hpp> #include <hyprutils/os/Process.hpp>
#include <sys/poll.h> #include <sys/poll.h>
#include <unistd.h>
#include <csignal> #include <csignal>
#include <thread> #include <thread>
@ -43,7 +42,6 @@ static bool startClient(SClient& client) {
client.readFd = CFileDescriptor(pipeFds2[0]); client.readFd = CFileDescriptor(pipeFds2[0]);
client.proc->setStdoutFD(pipeFds2[1]); client.proc->setStdoutFD(pipeFds2[1]);
const int COUNT_BEFORE = Tests::windowCount();
client.proc->runAsync(); client.proc->runAsync();
close(pipeFds1[0]); close(pipeFds1[0]);
@ -64,16 +62,7 @@ static bool startClient(SClient& client) {
} }
// wait for window to appear // wait for window to appear
int counter = 0; std::this_thread::sleep_for(std::chrono::milliseconds(5000));
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") { 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); NLog::log("{}Failed to disable animations for client window", Colors::RED, ret);

View file

@ -8,7 +8,6 @@
#include <hyprutils/os/Process.hpp> #include <hyprutils/os/Process.hpp>
#include <sys/poll.h> #include <sys/poll.h>
#include <unistd.h>
#include <csignal> #include <csignal>
#include <thread> #include <thread>
@ -43,7 +42,6 @@ static bool startClient(SClient& client) {
client.readFd = CFileDescriptor(pipeFds2[0]); client.readFd = CFileDescriptor(pipeFds2[0]);
client.proc->setStdoutFD(pipeFds2[1]); client.proc->setStdoutFD(pipeFds2[1]);
const int COUNT_BEFORE = Tests::windowCount();
client.proc->runAsync(); client.proc->runAsync();
close(pipeFds1[0]); close(pipeFds1[0]);
@ -64,16 +62,7 @@ static bool startClient(SClient& client) {
} }
// wait for window to appear // wait for window to appear
int counter = 0; std::this_thread::sleep_for(std::chrono::milliseconds(5000));
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") { 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); NLog::log("{}Failed to disable animations for client window", Colors::RED, ret);

View file

@ -1,180 +0,0 @@
#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);

View file

@ -34,197 +34,6 @@ static void testFloatClamp() {
// clean up // clean up
NLog::log("{}Killing all windows", Colors::YELLOW); NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows(); 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() { static bool test() {
@ -234,15 +43,6 @@ static bool test() {
NLog::log("{}Testing float clamp", Colors::GREEN); NLog::log("{}Testing float clamp", Colors::GREEN);
testFloatClamp(); testFloatClamp();
NLog::log("{}Testing #13349", Colors::GREEN);
test13349();
NLog::log("{}Testing splits", Colors::GREEN);
testSplit();
NLog::log("{}Testing rotatesplit", Colors::GREEN);
testRotatesplit();
// clean up // clean up
NLog::log("Cleaning up", Colors::YELLOW); NLog::log("Cleaning up", Colors::YELLOW);
getFromSocket("/dispatch workspace 1"); getFromSocket("/dispatch workspace 1");

View file

@ -2,7 +2,6 @@
#include "../../shared.hpp" #include "../../shared.hpp"
#include "../../hyprctlCompat.hpp" #include "../../hyprctlCompat.hpp"
#include <chrono> #include <chrono>
#include <format>
#include <thread> #include <thread>
#include <hyprutils/os/Process.hpp> #include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/WeakPtr.hpp> #include <hyprutils/memory/WeakPtr.hpp>
@ -16,15 +15,12 @@ using namespace Hyprutils::Memory;
#define UP CUniquePointer #define UP CUniquePointer
#define SP CSharedPointer #define SP CSharedPointer
const static auto SLEEP_DURATIONS = std::array{1, 10};
static bool test() { static bool test() {
NLog::log("{}Testing process spawning", Colors::GREEN); NLog::log("{}Testing process spawning", Colors::GREEN);
for (const auto duration : SLEEP_DURATIONS) {
// Note: POSIX sleep does not support fractional seconds, so // Note: POSIX sleep does not support fractional seconds, so
// can't sleep for less than 1 second. // can't sleep for less than 1 second.
OK(getFromSocket(std::format("/dispatch exec sleep {}", duration))); OK(getFromSocket("/dispatch exec sleep 1"));
// Ensure that sleep is our child // Ensure that sleep is our child
const std::string sleepPidS = Tests::execAndGet("pgrep sleep"); const std::string sleepPidS = Tests::execAndGet("pgrep sleep");
@ -33,14 +29,14 @@ static bool test() {
sleepPid = std::stoull(sleepPidS); sleepPid = std::stoull(sleepPidS);
} catch (...) { } catch (...) {
NLog::log("{}Sleep was not spawned or several sleeps are running: pgrep returned '{}'", Colors::RED, sleepPidS); NLog::log("{}Sleep was not spawned or several sleeps are running: pgrep returned '{}'", Colors::RED, sleepPidS);
continue; return false;
} }
const std::string sleepParentComm = Tests::execAndGet("cat \"/proc/$(ps -o ppid:1= -p " + sleepPidS + ")/comm\""); 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); NLog::log("{}Expecting that sleep's parent is Hyprland", Colors::YELLOW);
EXPECT_CONTAINS(sleepParentComm, "Hyprland"); EXPECT_CONTAINS(sleepParentComm, "Hyprland");
std::this_thread::sleep_for(std::chrono::seconds(duration)); std::this_thread::sleep_for(std::chrono::seconds(1));
// Ensure that sleep did not become a zombie // Ensure that sleep did not become a zombie
EXPECT(Tests::processAlive(sleepPid), false); EXPECT(Tests::processAlive(sleepPid), false);
@ -53,9 +49,6 @@ static bool test() {
EXPECT(Tests::windowCount(), 0); EXPECT(Tests::windowCount(), 0);
return !ret; return !ret;
}
return false;
} }
REGISTER_TEST_FN(test) REGISTER_TEST_FN(test)

View file

@ -127,34 +127,6 @@ static bool test() {
ret = 1; 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); NLog::log("{}Disable autogrouping", Colors::YELLOW);
OK(getFromSocket("/keyword group:auto_group false")); OK(getFromSocket("/keyword group:auto_group false"));
@ -201,99 +173,6 @@ static bool test() {
NLog::log("{}Expecting 0 windows", Colors::YELLOW); NLog::log("{}Expecting 0 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 0); EXPECT(Tests::windowCount(), 0);
// 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; return !ret;
} }

View file

@ -77,16 +77,6 @@ static bool testGetprop() {
EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size"), "100 50"); EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size"), "100 50");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size -j"), R"({"min_size": [100,50]})"); 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 // opacity
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity"), "1"); EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity"), "1");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity -j"), R"({"opacity": 1})"); EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity -j"), R"({"opacity": 1})");

View file

@ -1,53 +0,0 @@
#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)

View file

@ -1,127 +0,0 @@
#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);

View file

@ -5,52 +5,6 @@
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() { static void focusMasterPrevious() {
// setup // setup
NLog::log("{}Spawning 1 master and 3 slave windows", Colors::YELLOW); NLog::log("{}Spawning 1 master and 3 slave windows", Colors::YELLOW);
@ -90,74 +44,11 @@ static void focusMasterPrevious() {
OK(getFromSocket("/dispatch layoutmsg focusmaster previous")); OK(getFromSocket("/dispatch layoutmsg focusmaster previous"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: master"); EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: master");
testOrientations();
// clean up // clean up
NLog::log("{}Killing all windows", Colors::YELLOW); NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows(); 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() { static bool test() {
NLog::log("{}Testing Master layout", Colors::GREEN); NLog::log("{}Testing Master layout", Colors::GREEN);
@ -169,9 +60,6 @@ static bool test() {
NLog::log("{}Testing `focusmaster previous` layoutmsg", Colors::GREEN); NLog::log("{}Testing `focusmaster previous` layoutmsg", Colors::GREEN);
focusMasterPrevious(); focusMasterPrevious();
NLog::log("{}Testing fs behavior", Colors::GREEN);
testFsBehavior();
// clean up // clean up
NLog::log("Cleaning up", Colors::YELLOW); NLog::log("Cleaning up", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace 1")); OK(getFromSocket("/dispatch workspace 1"));

View file

@ -18,121 +18,6 @@ using namespace Hyprutils::Memory;
#define UP CUniquePointer #define UP CUniquePointer
#define SP CSharedPointer #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() { static bool test() {
NLog::log("{}Testing config: misc:", Colors::GREEN); NLog::log("{}Testing config: misc:", Colors::GREEN);

View file

@ -1,228 +0,0 @@
#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);

View file

@ -1,4 +1,3 @@
#include <unistd.h>
#include <cmath> #include <cmath>
#include <chrono> #include <chrono>
#include <cstdlib> #include <cstdlib>
@ -25,28 +24,6 @@ static bool spawnKitty(const std::string& class_, const std::vector<std::string>
return true; 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) { static std::string getWindowAttribute(const std::string& winInfo, const std::string& attr) {
auto pos = winInfo.find(attr); auto pos = winInfo.find(attr);
if (pos == std::string::npos) { if (pos == std::string::npos) {
@ -93,9 +70,9 @@ static void testSwapWindow() {
{ {
getFromSocket("/dispatch focuswindow class:kitty_A"); getFromSocket("/dispatch focuswindow class:kitty_A");
auto pos = getWindowAttribute(getFromSocket("/activewindow"), "at:"); auto pos = getWindowAttribute(getFromSocket("/activewindow"), "at:");
NLog::log("{}Testing kitty_A {}, swapwindow with direction 'r'", Colors::YELLOW, pos); NLog::log("{}Testing kitty_A {}, swapwindow with direction 'l'", Colors::YELLOW, pos);
OK(getFromSocket("/dispatch swapwindow r")); OK(getFromSocket("/dispatch swapwindow l"));
OK(getFromSocket("/dispatch focuswindow class:kitty_B")); OK(getFromSocket("/dispatch focuswindow class:kitty_B"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("{}", pos)); EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("{}", pos));
@ -220,7 +197,7 @@ static void testGroupRules() {
Tests::killAllWindows(); Tests::killAllWindows();
} }
static bool isActiveWindow(const std::string& class_, char fullscreen = '0', bool log = true) { static bool isActiveWindow(const std::string& class_, char fullscreen, bool log = true) {
std::string activeWin = getFromSocket("/activewindow"); std::string activeWin = getFromSocket("/activewindow");
auto winClass = getWindowAttribute(activeWin, "class:"); auto winClass = getWindowAttribute(activeWin, "class:");
auto winFullscreen = getWindowAttribute(activeWin, "fullscreen:").back(); auto winFullscreen = getWindowAttribute(activeWin, "fullscreen:").back();
@ -233,13 +210,13 @@ static bool isActiveWindow(const std::string& class_, char fullscreen = '0', boo
} }
} }
static bool waitForActiveWindow(const std::string& class_, char fullscreen = '0', bool logLastCheck = true, int maxTries = 50) { static bool waitForActiveWindow(const std::string& class_, char fullscreen, int maxTries = 50) {
int cnt = 0; int cnt = 0;
while (!isActiveWindow(class_, fullscreen, false)) { while (!isActiveWindow(class_, fullscreen, false)) {
++cnt; ++cnt;
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (cnt > maxTries) { if (cnt > maxTries) {
return isActiveWindow(class_, fullscreen, logLastCheck); return isActiveWindow(class_, fullscreen, true);
} }
} }
return true; return true;
@ -255,6 +232,24 @@ static bool testWindowFocusOnFullscreenConflict() {
OK(getFromSocket("/keyword misc:focus_on_activate true")); 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 // Unfullscreen on conflict
{ {
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 2")); OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 2"));
@ -378,61 +373,6 @@ static void testMaximizeSize() {
EXPECT(Tests::windowCount(), 0); 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() { static void testBringActiveToTopMouseMovement() {
NLog::log("{}Testing bringactivetotop mouse movement", Colors::GREEN); NLog::log("{}Testing bringactivetotop mouse movement", Colors::GREEN);
@ -472,192 +412,6 @@ static void testBringActiveToTopMouseMovement() {
Tests::killAllWindows(); 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() { static bool test() {
NLog::log("{}Testing windows", Colors::GREEN); NLog::log("{}Testing windows", Colors::GREEN);
@ -771,14 +525,14 @@ static bool test() {
EXPECT(Tests::windowCount(), 3); EXPECT(Tests::windowCount(), 3);
NLog::log("{}Checking props of xeyes", Colors::YELLOW); NLog::log("{}Checking props of xeyes", Colors::YELLOW);
// check some window props of xeyes, try to float it // check some window props of xeyes, try to tile them
{ {
auto str = getFromSocket("/clients"); auto str = getFromSocket("/clients");
EXPECT_NOT_CONTAINS(str, "floating: 1"); EXPECT_CONTAINS(str, "floating: 1");
getFromSocket("/dispatch setfloating class:XEyes"); getFromSocket("/dispatch settiled class:XEyes");
std::this_thread::sleep_for(std::chrono::milliseconds(200)); std::this_thread::sleep_for(std::chrono::milliseconds(200));
str = getFromSocket("/clients"); str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "floating: 1"); EXPECT_NOT_CONTAINS(str, "floating: 1");
} }
// kill all // kill all
@ -962,23 +716,6 @@ static bool test() {
Tests::killAllWindows(); 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")) if (!spawnKitty("tag_kitty"))
return false; return false;
@ -1062,8 +799,7 @@ static bool test() {
Tests::killAllWindows(); Tests::killAllWindows();
// test expression rules // test expression rules
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, " 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"));
"max_size monitor_w*0.75 monitor_h*0.75, move 20+(monitor_w*0.1) monitor_h*0.5"));
if (!spawnKitty("expr_kitty")) if (!spawnKitty("expr_kitty"))
return false; return false;
@ -1073,20 +809,12 @@ static bool test() {
EXPECT_CONTAINS(str, "floating: 1"); EXPECT_CONTAINS(str, "floating: 1");
EXPECT_CONTAINS(str, "at: 212,540"); EXPECT_CONTAINS(str, "at: 212,540");
EXPECT_CONTAINS(str, "size: 960,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")); OK(getFromSocket("/reload"));
Tests::killAllWindows(); Tests::killAllWindows();
OK(getFromSocket("/dispatch plugin:test:add_window_rule")); OK(getFromSocket("/dispatch plugin:test:add_rule"));
OK(getFromSocket("/reload")); OK(getFromSocket("/reload"));
OK(getFromSocket("/keyword windowrule match:class plugin_kitty, plugin_rule effect")); OK(getFromSocket("/keyword windowrule match:class plugin_kitty, plugin_rule effect"));
@ -1094,12 +822,12 @@ static bool test() {
if (!spawnKitty("plugin_kitty")) if (!spawnKitty("plugin_kitty"))
return false; return false;
OK(getFromSocket("/dispatch plugin:test:check_window_rule")); OK(getFromSocket("/dispatch plugin:test:check_rule"));
OK(getFromSocket("/reload")); OK(getFromSocket("/reload"));
Tests::killAllWindows(); Tests::killAllWindows();
OK(getFromSocket("/dispatch plugin:test:add_window_rule")); OK(getFromSocket("/dispatch plugin:test:add_rule"));
OK(getFromSocket("/reload")); OK(getFromSocket("/reload"));
OK(getFromSocket("/keyword windowrule[test-plugin-rule]:match:class plugin_kitty")); OK(getFromSocket("/keyword windowrule[test-plugin-rule]:match:class plugin_kitty"));
@ -1108,20 +836,16 @@ static bool test() {
if (!spawnKitty("plugin_kitty")) if (!spawnKitty("plugin_kitty"))
return false; return false;
OK(getFromSocket("/dispatch plugin:test:check_window_rule")); OK(getFromSocket("/dispatch plugin:test:check_rule"));
OK(getFromSocket("/reload")); OK(getFromSocket("/reload"));
Tests::killAllWindows(); Tests::killAllWindows();
testGroupRules(); testGroupRules();
testMaximizeSize(); testMaximizeSize();
testFloatingFocusOnFullscreen();
testBringActiveToTopMouseMovement(); testBringActiveToTopMouseMovement();
testGroupFallbackFocus();
testInitialFloatSize();
testWindowRuleFocusOnActivate();
testPinnedWorkspacesValid();
testWindowRuleWorkspaceEmpty();
NLog::log("{}Reloading config", Colors::YELLOW); NLog::log("{}Reloading config", Colors::YELLOW);
OK(getFromSocket("/reload")); OK(getFromSocket("/reload"));

View file

@ -20,91 +20,6 @@ using namespace Hyprutils::Utils;
#define UP CUniquePointer #define UP CUniquePointer
#define SP CSharedPointer #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() { static bool testAsymmetricGaps() {
NLog::log("{}Testing asymmetric gap splits", Colors::YELLOW); NLog::log("{}Testing asymmetric gap splits", Colors::YELLOW);
{ {
@ -193,206 +108,6 @@ static bool testAsymmetricGaps() {
return true; 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() { static bool test() {
NLog::log("{}Testing workspaces", Colors::GREEN); NLog::log("{}Testing workspaces", Colors::GREEN);
@ -727,19 +442,14 @@ static bool test() {
EXPECT_CONTAINS(str, "class: kitty_B"); EXPECT_CONTAINS(str, "class: kitty_B");
} }
// destroy the headless output
OK(getFromSocket("/output remove HEADLESS-3"));
// kill all // kill all
NLog::log("{}Killing all windows", Colors::YELLOW); NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows(); Tests::killAllWindows();
testMultimonBAF();
testMultimonFocus();
// destroy the headless output
OK(getFromSocket("/output remove HEADLESS-3"));
testSpecialWorkspaceFullscreen();
testAsymmetricGaps(); testAsymmetricGaps();
testDynamicWsEffects();
NLog::log("{}Expecting 0 windows", Colors::YELLOW); NLog::log("{}Expecting 0 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 0); EXPECT(Tests::windowCount(), 0);

View file

@ -3,7 +3,6 @@
#include <cerrno> #include <cerrno>
#include <thread> #include <thread>
#include <print> #include <print>
#include <fstream>
#include "../shared.hpp" #include "../shared.hpp"
#include "../hyprctlCompat.hpp" #include "../hyprctlCompat.hpp"
@ -40,38 +39,6 @@ CUniquePointer<CProcess> Tests::spawnKitty(const std::string& class_, const std:
return kitty; 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) { bool Tests::processAlive(pid_t pid) {
errno = 0; errno = 0;
int ret = kill(pid, 0); int ret = kill(pid, 0);
@ -129,38 +96,6 @@ 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) { std::string Tests::execAndGet(const std::string& cmd) {
CProcess proc("/bin/sh", {"-c", cmd}); CProcess proc("/bin/sh", {"-c", cmd});
@ -170,14 +105,3 @@ std::string Tests::execAndGet(const std::string& cmd) {
return proc.stdOut(); 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;
}

View file

@ -9,14 +9,10 @@
//NOLINTNEXTLINE //NOLINTNEXTLINE
namespace Tests { 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> 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); bool processAlive(pid_t pid);
int windowCount(); int windowCount();
int countOccurrences(const std::string& in, const std::string& what); int countOccurrences(const std::string& in, const std::string& what);
bool killAllWindows(); bool killAllWindows();
void waitUntilWindowsN(int n); void waitUntilWindowsN(int n);
int layerCount();
bool killAllLayers();
std::string execAndGet(const std::string& cmd); std::string execAndGet(const std::string& cmd);
bool writeFile(const std::string& name, const std::string& contents);
}; };

View file

@ -179,17 +179,6 @@ master {
new_status = 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 # https://wiki.hyprland.org/Configuring/Variables/#misc
misc { misc {
force_default_wallpaper = -1 # Set to 0 or 1 to disable the anime mascot wallpapers force_default_wallpaper = -1 # Set to 0 or 1 to disable the anime mascot wallpapers
@ -250,7 +239,7 @@ bind = $mainMod, E, exec, $fileManager
bind = $mainMod, V, togglefloating, bind = $mainMod, V, togglefloating,
bind = $mainMod, R, exec, $menu bind = $mainMod, R, exec, $menu
bind = $mainMod, P, pseudo, # dwindle bind = $mainMod, P, pseudo, # dwindle
bind = $mainMod, J, layoutmsg, togglesplit, # dwindle bind = $mainMod, J, togglesplit, # dwindle
# Move focus with mainMod + arrow keys # Move focus with mainMod + arrow keys
bind = $mainMod, left, movefocus, l bind = $mainMod, left, movefocus, l
@ -409,5 +398,3 @@ gesture = 5, left, dispatcher, sendshortcut, , i, activewindow
gesture = 5, right, dispatcher, sendshortcut, , t, activewindow gesture = 5, right, dispatcher, sendshortcut, , t, activewindow
gesture = 4, right, dispatcher, sendshortcut, , return, activewindow gesture = 4, right, dispatcher, sendshortcut, , return, activewindow
gesture = 4, left, dispatcher, movecursortocorner, 1 gesture = 4, left, dispatcher, movecursortocorner, 1
gesturep = 2, right, float

View file

@ -12,7 +12,6 @@
epoll-shim, epoll-shim,
git, git,
glaze-hyprland, glaze-hyprland,
glslang,
gtest, gtest,
hyprcursor, hyprcursor,
hyprgraphics, hyprgraphics,
@ -22,17 +21,10 @@
hyprutils, hyprutils,
hyprwayland-scanner, hyprwayland-scanner,
hyprwire, hyprwire,
lcms2,
libGL, libGL,
libdrm, libdrm,
libexecinfo, libexecinfo,
libinput, libinput,
libxcb,
libxcb-errors,
libxcb-render-util,
libxcb-wm,
libxdmcp,
libxcursor,
libxkbcommon, libxkbcommon,
libuuid, libuuid,
libgbm, libgbm,
@ -46,6 +38,7 @@
wayland, wayland,
wayland-protocols, wayland-protocols,
wayland-scanner, wayland-scanner,
xorg,
xwayland, xwayland,
debug ? false, debug ? false,
withTests ? false, withTests ? false,
@ -66,20 +59,8 @@
inherit (builtins) foldl' readFile; inherit (builtins) foldl' readFile;
inherit (lib.asserts) assertMsg; inherit (lib.asserts) assertMsg;
inherit (lib.attrsets) mapAttrsToList; inherit (lib.attrsets) mapAttrsToList;
inherit inherit (lib.lists) flatten concatLists optional optionals;
(lib.lists) inherit (lib.strings) makeBinPath optionalString cmakeBool trim;
flatten
concatLists
optional
optionals
;
inherit
(lib.strings)
makeBinPath
optionalString
cmakeBool
trim
;
fs = lib.fileset; fs = lib.fileset;
adapters = flatten [ adapters = flatten [
@ -91,8 +72,7 @@
in in
assert assertMsg (!nvidiaPatches) "The option `nvidiaPatches` has been removed."; assert assertMsg (!nvidiaPatches) "The option `nvidiaPatches` has been removed.";
assert assertMsg (!enableNvidiaPatches) "The option `enableNvidiaPatches` has been removed."; assert assertMsg (!enableNvidiaPatches) "The option `enableNvidiaPatches` has been removed.";
assert assertMsg (!hidpiXWayland) assert assertMsg (!hidpiXWayland) "The option `hidpiXWayland` has been removed. Please refer https://wiki.hypr.land/Configuring/XWayland";
"The option `hidpiXWayland` has been removed. Please refer https://wiki.hypr.land/Configuring/XWayland";
assert assertMsg (!legacyRenderer) "The option `legacyRenderer` has been removed. Legacy renderer is no longer supported."; assert assertMsg (!legacyRenderer) "The option `legacyRenderer` has been removed. Legacy renderer is no longer supported.";
assert assertMsg (!withHyprtester) "The option `withHyprtester` has been removed. Hyprtester is always built now."; assert assertMsg (!withHyprtester) "The option `withHyprtester` has been removed. Hyprtester is always built now.";
customStdenv.mkDerivation (finalAttrs: { customStdenv.mkDerivation (finalAttrs: {
@ -105,13 +85,11 @@ in
fs.intersection fs.intersection
# allows non-flake builds to only include files tracked by git # allows non-flake builds to only include files tracked by git
(fs.gitTracked ../.) (fs.gitTracked ../.)
( (fs.unions (flatten [
fs.unions (flatten [
../assets/hyprland-portals.conf ../assets/hyprland-portals.conf
../assets/install ../assets/install
../hyprctl ../hyprctl
../hyprland.pc.in ../hyprland.pc.in
../hyprpm
../LICENSE ../LICENSE
../protocols ../protocols
../src ../src
@ -122,12 +100,8 @@ in
(fs.fileFilter (file: file.hasExt "conf" || file.hasExt "in") ../example) (fs.fileFilter (file: file.hasExt "conf" || file.hasExt "in") ../example)
(fs.fileFilter (file: file.hasExt "sh") ../scripts) (fs.fileFilter (file: file.hasExt "sh") ../scripts)
(fs.fileFilter (file: file.name == "CMakeLists.txt") ../.) (fs.fileFilter (file: file.name == "CMakeLists.txt") ../.)
(optional withTests [ (optional withTests [../tests ../hyprtester])
../tests ]));
../hyprtester
])
])
);
}; };
postPatch = '' postPatch = ''
@ -143,10 +117,7 @@ in
GIT_COMMITS = revCount; GIT_COMMITS = revCount;
GIT_COMMIT_DATE = date; GIT_COMMIT_DATE = date;
GIT_COMMIT_HASH = commit; GIT_COMMIT_HASH = commit;
GIT_DIRTY = GIT_DIRTY = if (commit == "") then "clean" else "dirty";
if (commit == "")
then "clean"
else "dirty";
GIT_TAG = "v${trim (readFile "${finalAttrs.src}/VERSION")}"; GIT_TAG = "v${trim (readFile "${finalAttrs.src}/VERSION")}";
}; };
@ -174,7 +145,6 @@ in
cairo cairo
git git
glaze-hyprland glaze-hyprland
glslang
gtest gtest
hyprcursor hyprcursor
hyprgraphics hyprgraphics
@ -182,14 +152,12 @@ in
hyprlang hyprlang
hyprutils hyprutils
hyprwire hyprwire
lcms2
libdrm libdrm
libgbm
libGL libGL
libinput libinput
libuuid libuuid
libxcursor
libxkbcommon libxkbcommon
libgbm
muparser muparser
pango pango
pciutils pciutils
@ -199,15 +167,16 @@ in
wayland wayland
wayland-protocols wayland-protocols
wayland-scanner wayland-scanner
xorg.libXcursor
] ]
(optionals customStdenv.hostPlatform.isBSD [epoll-shim]) (optionals customStdenv.hostPlatform.isBSD [epoll-shim])
(optionals customStdenv.hostPlatform.isMusl [libexecinfo]) (optionals customStdenv.hostPlatform.isMusl [libexecinfo])
(optionals enableXWayland [ (optionals enableXWayland [
libxcb xorg.libxcb
libxcb-errors xorg.libXdmcp
libxcb-render-util xorg.xcbutilerrors
libxcb-wm xorg.xcbutilrenderutil
libxdmcp xorg.xcbutilwm
xwayland xwayland
]) ])
(optional withSystemd systemd) (optional withSystemd systemd)
@ -230,6 +199,7 @@ in
"NO_SYSTEMD" = !withSystemd; "NO_SYSTEMD" = !withSystemd;
"CMAKE_DISABLE_PRECOMPILE_HEADERS" = true; "CMAKE_DISABLE_PRECOMPILE_HEADERS" = true;
"NO_UWSM" = !withSystemd; "NO_UWSM" = !withSystemd;
"NO_HYPRPM" = true;
"TRACY_ENABLE" = false; "TRACY_ENABLE" = false;
"WITH_TESTS" = withTests; "WITH_TESTS" = withTests;
}; };
@ -243,26 +213,23 @@ in
postInstall = '' postInstall = ''
${optionalString wrapRuntimeDeps '' ${optionalString wrapRuntimeDeps ''
wrapProgram $out/bin/Hyprland \ wrapProgram $out/bin/Hyprland \
--suffix PATH : ${ --suffix PATH : ${makeBinPath [
makeBinPath [
binutils binutils
hyprland-guiutils hyprland-guiutils
pciutils pciutils
pkgconf pkgconf
] ]}
}
''} ''}
${optionalString withTests '' ${optionalString withTests ''
install hyprtester/pointer-warp -t $out/bin install hyprtester/pointer-warp -t $out/bin
install hyprtester/pointer-scroll -t $out/bin install hyprtester/pointer-scroll -t $out/bin
install hyprtester/shortcut-inhibitor -t $out/bin
install hyprland_gtests -t $out/bin install hyprland_gtests -t $out/bin
install hyprtester/child-window -t $out/bin install hyprtester/child-window -t $out/bin
''} ''}
''; '';
passthru.providedSessions = ["hyprland"] ++ optionals withSystemd ["hyprland-uwsm"]; passthru.providedSessions = ["hyprland"];
meta = { meta = {
homepage = "https://github.com/hyprwm/Hyprland"; homepage = "https://github.com/hyprwm/Hyprland";

View file

@ -2,7 +2,7 @@
writeShellApplication, writeShellApplication,
deadnix, deadnix,
statix, statix,
nixfmt, alejandra,
llvmPackages_19, llvmPackages_19,
fd, fd,
}: }:
@ -11,7 +11,7 @@ writeShellApplication {
runtimeInputs = [ runtimeInputs = [
deadnix deadnix
statix statix
nixfmt alejandra
llvmPackages_19.clang-tools llvmPackages_19.clang-tools
fd fd
]; ];
@ -24,14 +24,14 @@ writeShellApplication {
nix_format() { nix_format() {
if [ "$*" = 0 ]; then if [ "$*" = 0 ]; then
fd '.*\.nix' . -E "$excludes" -x statix fix -- {} \; fd '.*\.nix' . -E "$excludes" -x statix fix -- {} \;
fd '.*\.nix' . -E "$excludes" -X deadnix -e -- {} \; -X nixfmt {} \; fd '.*\.nix' . -E "$excludes" -X deadnix -e -- {} \; -X alejandra {} \;
elif [ -d "$1" ]; then elif [ -d "$1" ]; then
fd '.*\.nix' "$1" -E "$excludes" -i -x statix fix -- {} \; fd '.*\.nix' "$1" -E "$excludes" -i -x statix fix -- {} \;
fd '.*\.nix' "$1" -E "$excludes" -i -X deadnix -e -- {} \; -X nixfmt {} \; fd '.*\.nix' "$1" -E "$excludes" -i -X deadnix -e -- {} \; -X alejandra {} \;
else else
statix fix -- "$1" statix fix -- "$1"
deadnix -e "$1" deadnix -e "$1"
nixfmt "$1" alejandra "$1"
fi fi
} }

View file

@ -1,15 +1,13 @@
self: self: {
{ config,
lib, lib,
pkgs, pkgs,
... ...
}: }: let
let
inherit (pkgs.stdenv.hostPlatform) system; inherit (pkgs.stdenv.hostPlatform) system;
package = self.packages.${system}.default; package = self.packages.${system}.default;
in in {
{
config = { config = {
wayland.windowManager.hyprland.package = lib.mkDefault package; wayland.windowManager.hyprland.package = lib.mkDefault package;
}; };

View file

@ -1,5 +1,4 @@
lib: lib: let
let
inherit (lib) inherit (lib)
attrNames attrNames
filterAttrs filterAttrs
@ -82,23 +81,15 @@ let
::: :::
*/ */
toHyprlang = toHyprlang = {
{ topCommandsPrefixes ? ["$" "bezier"],
topCommandsPrefixes ? [ bottomCommandsPrefixes ? [],
"$" }: attrs: let
"bezier" toHyprlang' = attrs: let
],
bottomCommandsPrefixes ? [ ],
}:
attrs:
let
toHyprlang' =
attrs:
let
# Specially configured `toKeyValue` generator with support for duplicate keys # Specially configured `toKeyValue` generator with support for duplicate keys
# and a legible key-value separator. # and a legible key-value separator.
mkCommands = generators.toKeyValue { mkCommands = generators.toKeyValue {
mkKeyValue = generators.mkKeyValueDefault { } " = "; mkKeyValue = generators.mkKeyValueDefault {} " = ";
listsAsDuplicateKeys = true; listsAsDuplicateKeys = true;
indent = ""; # No indent, since we don't have nesting indent = ""; # No indent, since we don't have nesting
}; };
@ -108,7 +99,8 @@ let
commands = flattenAttrs (p: k: "${p}:${k}") attrs; commands = flattenAttrs (p: k: "${p}:${k}") attrs;
# General filtering function to check if a key starts with any prefix in a given 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; filterCommands = list: n:
foldl (acc: prefix: acc || hasPrefix prefix n) false list;
# Partition keys into top commands and the rest # Partition keys into top commands and the rest
result = partition (filterCommands topCommandsPrefixes) (attrNames commands); result = partition (filterCommands topCommandsPrefixes) (attrNames commands);
@ -182,20 +174,25 @@ let
``` ```
::: :::
*/ */
flattenAttrs = flattenAttrs = pred: attrs: let
pred: attrs: flattenAttrs' = prefix: attrs:
let
flattenAttrs' =
prefix: attrs:
builtins.foldl' ( builtins.foldl' (
acc: key: acc: key: let
let
value = attrs.${key}; value = attrs.${key};
newKey = if prefix == "" then key else pred prefix key; newKey =
if prefix == ""
then key
else pred prefix key;
in in
acc // (if builtins.isAttrs value then flattenAttrs' newKey value else { "${newKey}" = value; }) acc
) { } (builtins.attrNames attrs); // (
if builtins.isAttrs value
then flattenAttrs' newKey value
else {"${newKey}" = value;}
)
) {} (builtins.attrNames attrs);
in in
flattenAttrs' "" attrs; flattenAttrs' "" attrs;
in in

View file

@ -1,21 +1,18 @@
inputs: inputs: {
{
config, config,
lib, lib,
pkgs, pkgs,
... ...
}: }: let
let
inherit (pkgs.stdenv.hostPlatform) system; inherit (pkgs.stdenv.hostPlatform) system;
selflib = import ./lib.nix lib; selflib = import ./lib.nix lib;
cfg = config.programs.hyprland; cfg = config.programs.hyprland;
in in {
{
options = { options = {
programs.hyprland = { programs.hyprland = {
plugins = lib.mkOption { plugins = lib.mkOption {
type = with lib.types; listOf (either package path); type = with lib.types; listOf (either package path);
default = [ ]; default = [];
description = '' description = ''
List of Hyprland plugins to use. Can either be packages or List of Hyprland plugins to use. Can either be packages or
absolute plugin paths. absolute plugin paths.
@ -23,9 +20,7 @@ in
}; };
settings = lib.mkOption { settings = lib.mkOption {
type = type = with lib.types; let
with lib.types;
let
valueType = valueType =
nullOr (oneOf [ nullOr (oneOf [
bool bool
@ -41,7 +36,7 @@ in
}; };
in in
valueType; valueType;
default = { }; default = {};
description = '' description = ''
Hyprland configuration written in Nix. Entries with the same key Hyprland configuration written in Nix. Entries with the same key
should be written as lists. Variables' and colors' names should be should be written as lists. Variables' and colors' names should be
@ -97,15 +92,8 @@ in
topPrefixes = lib.mkOption { topPrefixes = lib.mkOption {
type = with lib.types; listOf str; type = with lib.types; listOf str;
default = [ default = ["$" "bezier"];
"$" example = ["$" "bezier" "source"];
"bezier"
];
example = [
"$"
"bezier"
"source"
];
description = '' description = ''
List of prefix of attributes to put at the top of the config. List of prefix of attributes to put at the top of the config.
''; '';
@ -113,8 +101,8 @@ in
bottomPrefixes = lib.mkOption { bottomPrefixes = lib.mkOption {
type = with lib.types; listOf str; type = with lib.types; listOf str;
default = [ ]; default = [];
example = [ "source" ]; example = ["source"];
description = '' description = ''
List of prefix of attributes to put at the bottom of the config. List of prefix of attributes to put at the bottom of the config.
''; '';
@ -129,22 +117,20 @@ in
}; };
} }
(lib.mkIf cfg.enable { (lib.mkIf cfg.enable {
environment.etc."xdg/hypr/hyprland.conf" = environment.etc."xdg/hypr/hyprland.conf" = let
let shouldGenerate = cfg.extraConfig != "" || cfg.settings != {} || cfg.plugins != [];
shouldGenerate = cfg.extraConfig != "" || cfg.settings != { } || cfg.plugins != [ ];
pluginsToHyprlang = pluginsToHyprlang = plugins:
_plugins: selflib.toHyprlang {
selflib.toHyprlang
{
topCommandsPrefixes = cfg.topPrefixes; topCommandsPrefixes = cfg.topPrefixes;
bottomCommandsPrefixes = cfg.bottomPrefixes; bottomCommandsPrefixes = cfg.bottomPrefixes;
} }
{ {
"exec-once" = "exec-once" = let
let mkEntry = entry:
mkEntry = if lib.types.package.check entry
entry: if lib.types.package.check entry then "${entry}/lib/lib${entry.pname}.so" else entry; then "${entry}/lib/lib${entry.pname}.so"
else entry;
hyprctl = lib.getExe' config.programs.hyprland.package "hyprctl"; hyprctl = lib.getExe' config.programs.hyprland.package "hyprctl";
in in
map (p: "${hyprctl} plugin load ${mkEntry p}") cfg.plugins; map (p: "${hyprctl} plugin load ${mkEntry p}") cfg.plugins;
@ -152,13 +138,14 @@ in
in in
lib.mkIf shouldGenerate { lib.mkIf shouldGenerate {
text = text =
lib.optionalString (cfg.plugins != [ ]) (pluginsToHyprlang cfg.plugins) lib.optionalString (cfg.plugins != [])
+ lib.optionalString (cfg.settings != { }) ( (pluginsToHyprlang cfg.plugins)
selflib.toHyprlang { + lib.optionalString (cfg.settings != {})
(selflib.toHyprlang {
topCommandsPrefixes = cfg.topPrefixes; topCommandsPrefixes = cfg.topPrefixes;
bottomCommandsPrefixes = cfg.bottomPrefixes; bottomCommandsPrefixes = cfg.bottomPrefixes;
} cfg.settings }
) cfg.settings)
+ lib.optionalString (cfg.extraConfig != "") cfg.extraConfig; + lib.optionalString (cfg.extraConfig != "") cfg.extraConfig;
}; };
}) })

View file

@ -2,27 +2,20 @@
self, self,
lib, lib,
inputs, inputs,
}: }: let
let mkDate = longDate: (lib.concatStringsSep "-" [
mkDate =
longDate:
(lib.concatStringsSep "-" [
(builtins.substring 0 4 longDate) (builtins.substring 0 4 longDate)
(builtins.substring 4 2 longDate) (builtins.substring 4 2 longDate)
(builtins.substring 6 2 longDate) (builtins.substring 6 2 longDate)
]); ]);
ver = lib.removeSuffix "\n" (builtins.readFile ../VERSION); ver = lib.removeSuffix "\n" (builtins.readFile ../VERSION);
in in {
{
# Contains what a user is most likely to care about: # Contains what a user is most likely to care about:
# Hyprland itself, XDPH and the Share Picker. # Hyprland itself, XDPH and the Share Picker.
default = lib.composeManyExtensions ( default = lib.composeManyExtensions (with self.overlays; [
with self.overlays;
[
hyprland-packages hyprland-packages
hyprland-extras hyprland-extras
] ]);
);
# Packages for variations of Hyprland, dependencies included. # Packages for variations of Hyprland, dependencies included.
hyprland-packages = lib.composeManyExtensions [ hyprland-packages = lib.composeManyExtensions [
@ -40,45 +33,49 @@ in
self.overlays.glaze self.overlays.glaze
# Hyprland packages themselves # Hyprland packages themselves
( (final: _prev: let
final: _prev:
let
date = mkDate (self.lastModifiedDate or "19700101"); date = mkDate (self.lastModifiedDate or "19700101");
version = "${ver}+date=${date}_${self.shortRev or "dirty"}"; version = "${ver}+date=${date}_${self.shortRev or "dirty"}";
in in {
{
hyprland = final.callPackage ./default.nix { hyprland = final.callPackage ./default.nix {
stdenv = final.gcc15Stdenv; stdenv = final.gcc15Stdenv;
commit = self.rev or ""; commit = self.rev or "";
revCount = self.sourceInfo.revCount or ""; revCount = self.sourceInfo.revCount or "";
inherit date version; inherit date version;
}; };
hyprland-unwrapped = final.hyprland.override { wrapRuntimeDeps = false; }; 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. hyprland-with-hyprtester was removed. Please use the hyprland package.
Hyprtester is always built now. Hyprtester is always built now.
'' final.hyprland; ''
final.hyprland;
# deprecated packages # deprecated packages
hyprland-legacy-renderer = builtins.trace '' hyprland-legacy-renderer =
builtins.trace ''
hyprland-legacy-renderer was removed. Please use the hyprland package. hyprland-legacy-renderer was removed. Please use the hyprland package.
Legacy renderer is no longer supported. 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. hyprland-nvidia was removed. Please use the hyprland package.
Nvidia patches are no longer needed. 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. hyprland-hidpi was removed. Please use the hyprland package.
For more information, refer to https://wiki.hypr.land/Configuring/XWayland. For more information, refer to https://wiki.hypr.land/Configuring/XWayland.
'' final.hyprland; ''
} final.hyprland;
) })
]; ];
# Debug # Debug
@ -86,10 +83,10 @@ in
# Dependencies # Dependencies
self.overlays.hyprland-packages self.overlays.hyprland-packages
(_final: prev: { (final: prev: {
aquamarine = prev.aquamarine.override { debug = true; }; aquamarine = prev.aquamarine.override {debug = true;};
hyprutils = prev.hyprutils.override { debug = true; }; hyprutils = prev.hyprutils.override {debug = true;};
hyprland-debug = prev.hyprland.override { debug = true; }; hyprland-debug = prev.hyprland.override {debug = true;};
}) })
]; ];
@ -103,8 +100,7 @@ in
# this version is the one used in the git submodule, and allows us to # this version is the one used in the git submodule, and allows us to
# fetch the source without '?submodules=1' # fetch the source without '?submodules=1'
udis86 = final: prev: { udis86 = final: prev: {
udis86-hyprland = prev.udis86.overrideAttrs ( udis86-hyprland = prev.udis86.overrideAttrs (_self: _super: {
_self: _super: {
src = final.fetchFromGitHub { src = final.fetchFromGitHub {
owner = "canihavesomecoffee"; owner = "canihavesomecoffee";
repo = "udis86"; repo = "udis86";
@ -112,14 +108,13 @@ in
hash = "sha256-HifdUQPGsKQKQprByeIznvRLONdOXeolOsU5nkwIv3g="; hash = "sha256-HifdUQPGsKQKQprByeIznvRLONdOXeolOsU5nkwIv3g=";
}; };
patches = [ ]; patches = [];
} });
);
}; };
# Even though glaze itself disables it by default, nixpkgs sets ENABLE_SSL set to true. # 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 # 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 { glaze-hyprland = prev.glaze.override {
enableSSL = false; enableSSL = false;
enableInterop = false; enableInterop = false;

View file

@ -1,21 +1,17 @@
inputs: pkgs: inputs: pkgs: let
let
flake = inputs.self.packages.${pkgs.stdenv.hostPlatform.system}; flake = inputs.self.packages.${pkgs.stdenv.hostPlatform.system};
hyprland = flake.hyprland-with-tests; hyprland = flake.hyprland-with-tests;
in in {
{
tests = pkgs.testers.runNixOSTest { tests = pkgs.testers.runNixOSTest {
name = "hyprland-tests"; name = "hyprland-tests";
nodes.machine = nodes.machine = {pkgs, ...}: {
{ pkgs, ... }:
{
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
# Programs needed for tests # Programs needed for tests
jq jq
kitty kitty
wl-clipboard wl-clipboard
xeyes xorg.xeyes
]; ];
# Enabled by default for some reason # Enabled by default for some reason
@ -31,9 +27,6 @@ in
environment.etc."kitty/kitty.conf".text = '' environment.etc."kitty/kitty.conf".text = ''
confirm_os_window_close 0 confirm_os_window_close 0
remember_window_size no
initial_window_width 640
initial_window_height 400
''; '';
programs.hyprland = { programs.hyprland = {
@ -67,7 +60,7 @@ in
}; };
# Doesn't seem to do much, thought it would fix XWayland crashing # Doesn't seem to do much, thought it would fix XWayland crashing
qemu.options = [ "-vga none -device virtio-gpu-pci" ]; qemu.options = ["-vga none -device virtio-gpu-pci"];
}; };
}; };

View file

@ -15,7 +15,7 @@ echo 'static const std::map<std::string, std::string> SHADERS = {' >> ./src/rend
for filename in `ls ${SHADERS_SRC}`; do for filename in `ls ${SHADERS_SRC}`; do
echo "-- ${filename}" echo "-- ${filename}"
{ echo -n 'R"#('; cat ${SHADERS_SRC}/${filename}; echo ')#"'; } > ./src/render/shaders/${filename}.inc { echo 'R"#('; cat ${SHADERS_SRC}/${filename}; echo ')#"'; } > ./src/render/shaders/${filename}.inc
echo "{\"${filename}\"," >> ./src/render/shaders/Shaders.hpp echo "{\"${filename}\"," >> ./src/render/shaders/Shaders.hpp
echo "#include \"./${filename}.inc\"" >> ./src/render/shaders/Shaders.hpp echo "#include \"./${filename}.inc\"" >> ./src/render/shaders/Shaders.hpp
echo "}," >> ./src/render/shaders/Shaders.hpp echo "}," >> ./src/render/shaders/Shaders.hpp

View file

@ -7,7 +7,6 @@
#include "desktop/state/FocusState.hpp" #include "desktop/state/FocusState.hpp"
#include "desktop/history/WindowHistoryTracker.hpp" #include "desktop/history/WindowHistoryTracker.hpp"
#include "desktop/history/WorkspaceHistoryTracker.hpp" #include "desktop/history/WorkspaceHistoryTracker.hpp"
#include "desktop/view/Group.hpp"
#include "helpers/Splashes.hpp" #include "helpers/Splashes.hpp"
#include "config/ConfigValue.hpp" #include "config/ConfigValue.hpp"
#include "config/ConfigWatcher.hpp" #include "config/ConfigWatcher.hpp"
@ -20,7 +19,6 @@
#include "managers/ANRManager.hpp" #include "managers/ANRManager.hpp"
#include "managers/eventLoop/EventLoopManager.hpp" #include "managers/eventLoop/EventLoopManager.hpp"
#include "managers/permissions/DynamicPermissionManager.hpp" #include "managers/permissions/DynamicPermissionManager.hpp"
#include "managers/screenshare/ScreenshareManager.hpp"
#include <algorithm> #include <algorithm>
#include <aquamarine/output/Output.hpp> #include <aquamarine/output/Output.hpp>
#include <bit> #include <bit>
@ -47,7 +45,6 @@
#include "protocols/core/Compositor.hpp" #include "protocols/core/Compositor.hpp"
#include "protocols/core/Subcompositor.hpp" #include "protocols/core/Subcompositor.hpp"
#include "desktop/view/LayerSurface.hpp" #include "desktop/view/LayerSurface.hpp"
#include "layout/space/Space.hpp"
#include "render/Renderer.hpp" #include "render/Renderer.hpp"
#include "xwayland/XWayland.hpp" #include "xwayland/XWayland.hpp"
#include "helpers/ByteOperations.hpp" #include "helpers/ByteOperations.hpp"
@ -63,7 +60,9 @@
#include "managers/animation/AnimationManager.hpp" #include "managers/animation/AnimationManager.hpp"
#include "managers/animation/DesktopAnimationManager.hpp" #include "managers/animation/DesktopAnimationManager.hpp"
#include "managers/EventManager.hpp" #include "managers/EventManager.hpp"
#include "managers/HookSystemManager.hpp"
#include "managers/ProtocolManager.hpp" #include "managers/ProtocolManager.hpp"
#include "managers/LayoutManager.hpp"
#include "managers/WelcomeManager.hpp" #include "managers/WelcomeManager.hpp"
#include "render/AsyncResourceGatherer.hpp" #include "render/AsyncResourceGatherer.hpp"
#include "plugins/PluginSystem.hpp" #include "plugins/PluginSystem.hpp"
@ -72,9 +71,6 @@
#include "debug/HyprDebugOverlay.hpp" #include "debug/HyprDebugOverlay.hpp"
#include "helpers/MonitorFrameScheduler.hpp" #include "helpers/MonitorFrameScheduler.hpp"
#include "i18n/Engine.hpp" #include "i18n/Engine.hpp"
#include "layout/LayoutManager.hpp"
#include "layout/target/WindowTarget.hpp"
#include "event/EventBus.hpp"
#include <hyprutils/string/String.hpp> #include <hyprutils/string/String.hpp>
#include <aquamarine/input/Input.hpp> #include <aquamarine/input/Input.hpp>
@ -107,6 +103,11 @@ static void handleUnrecoverableSignal(int sig) {
signal(SIGABRT, SIG_DFL); signal(SIGABRT, SIG_DFL);
signal(SIGSEGV, 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. // Kill the program if the crash-reporter is caught in a deadlock.
signal(SIGALRM, [](int _) { signal(SIGALRM, [](int _) {
char const* msg = "\nCrashReporter exceeded timeout, forcefully exiting\n"; char const* msg = "\nCrashReporter exceeded timeout, forcefully exiting\n";
@ -282,6 +283,7 @@ static bool filterGlobals(const wl_client* client, const wl_global* global, void
// //
void CCompositor::initServer(std::string socketName, int socketFd) { void CCompositor::initServer(std::string socketName, int socketFd) {
if (m_onlyConfigVerification) { if (m_onlyConfigVerification) {
g_pHookSystem = makeUnique<CHookSystemManager>();
g_pKeybindManager = makeUnique<CKeybindManager>(); g_pKeybindManager = makeUnique<CKeybindManager>();
g_pAnimationManager = makeUnique<CHyprAnimationManager>(); g_pAnimationManager = makeUnique<CHyprAnimationManager>();
g_pConfigManager = makeUnique<CConfigManager>(); g_pConfigManager = makeUnique<CConfigManager>();
@ -370,10 +372,10 @@ void CCompositor::initServer(std::string socketName, int socketFd) {
return ret == 0 && cap != 0; return ret == 0 && cap != 0;
}; };
m_drm.syncobjSupport = syncObjSupport(m_drm.fd); if ((m_drm.syncobjSupport = syncObjSupport(m_drm.fd)))
Log::logger->log(Log::DEBUG, "DRM DisplayNode syncobj timeline support: {}", m_drm.syncobjSupport ? "yes" : "no"); Log::logger->log(Log::DEBUG, "DRM DisplayNode syncobj timeline support: {}", m_drm.syncobjSupport ? "yes" : "no");
m_drmRenderNode.syncObjSupport = syncObjSupport(m_drmRenderNode.fd); if ((m_drmRenderNode.syncObjSupport = syncObjSupport(m_drmRenderNode.fd)))
Log::logger->log(Log::DEBUG, "DRM RenderNode syncobj timeline support: {}", m_drmRenderNode.syncObjSupport ? "yes" : "no"); Log::logger->log(Log::DEBUG, "DRM RenderNode syncobj timeline support: {}", m_drmRenderNode.syncObjSupport ? "yes" : "no");
if (!m_drm.syncobjSupport && !m_drmRenderNode.syncObjSupport) if (!m_drm.syncobjSupport && !m_drmRenderNode.syncObjSupport)
@ -481,10 +483,6 @@ void CCompositor::initAllSignals() {
m_sessionActive = true; 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) { for (auto const& m : m_monitors) {
scheduleFrameForMonitor(m); scheduleFrameForMonitor(m);
m->applyMonitorRule(&m->m_activeMonitorRule, true); m->applyMonitorRule(&m->m_activeMonitorRule, true);
@ -588,10 +586,11 @@ void CCompositor::cleanup() {
g_pHyprRenderer.reset(); g_pHyprRenderer.reset();
g_pHyprOpenGL.reset(); g_pHyprOpenGL.reset();
g_pConfigManager.reset(); g_pConfigManager.reset();
g_layoutManager.reset(); g_pLayoutManager.reset();
g_pHyprError.reset(); g_pHyprError.reset();
g_pConfigManager.reset(); g_pConfigManager.reset();
g_pKeybindManager.reset(); g_pKeybindManager.reset();
g_pHookSystem.reset();
g_pXWaylandManager.reset(); g_pXWaylandManager.reset();
g_pPointerManager.reset(); g_pPointerManager.reset();
g_pSeatManager.reset(); g_pSeatManager.reset();
@ -620,6 +619,9 @@ void CCompositor::initManagers(eManagersInitStage stage) {
Log::logger->log(Log::DEBUG, "Creating the EventLoopManager!"); Log::logger->log(Log::DEBUG, "Creating the EventLoopManager!");
g_pEventLoopManager = makeUnique<CEventLoopManager>(m_wlDisplay, m_wlEventLoop); 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!"); Log::logger->log(Log::DEBUG, "Creating the KeybindManager!");
g_pKeybindManager = makeUnique<CKeybindManager>(); g_pKeybindManager = makeUnique<CKeybindManager>();
@ -636,7 +638,7 @@ void CCompositor::initManagers(eManagersInitStage stage) {
g_pHyprError = makeUnique<CHyprError>(); g_pHyprError = makeUnique<CHyprError>();
Log::logger->log(Log::DEBUG, "Creating the LayoutManager!"); Log::logger->log(Log::DEBUG, "Creating the LayoutManager!");
g_layoutManager = makeUnique<Layout::CLayoutManager>(); g_pLayoutManager = makeUnique<CLayoutManager>();
Log::logger->log(Log::DEBUG, "Creating the TokenManager!"); Log::logger->log(Log::DEBUG, "Creating the TokenManager!");
g_pTokenManager = makeUnique<CTokenManager>(); g_pTokenManager = makeUnique<CTokenManager>();
@ -790,8 +792,7 @@ void CCompositor::startCompositor() {
createLockFile(); createLockFile();
Event::bus()->m_events.ready.emit(); EMIT_HOOK_EVENT("ready", nullptr);
if (m_watchdogWriteFd.isValid()) if (m_watchdogWriteFd.isValid())
write(m_watchdogWriteFd.get(), "vax", 3); write(m_watchdogWriteFd.get(), "vax", 3);
@ -871,7 +872,7 @@ PHLMONITOR CCompositor::getMonitorFromVector(const Vector2D& point) {
void CCompositor::removeWindowFromVectorSafe(PHLWINDOW pWindow) { void CCompositor::removeWindowFromVectorSafe(PHLWINDOW pWindow) {
if (!pWindow->m_fadingOut) { if (!pWindow->m_fadingOut) {
Event::bus()->m_events.window.destroy.emit(pWindow); EMIT_HOOK_EVENT("destroyWindow", pWindow);
std::erase_if(m_windows, [&](SP<Desktop::View::CWindow>& el) { return el == 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; }); std::erase_if(m_windowsFadingOut, [&](PHLWINDOWREF el) { return el.lock() == pWindow; });
@ -1033,8 +1034,6 @@ 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() && if (!w->m_isFloating && w->m_isMapped && w->workspaceID() == WSPID && !w->isHidden() && !w->m_X11ShouldntFocus && !w->m_ruleApplicator->noFocus().valueOrDefault() &&
w != pIgnoreWindow && !isShadowedByModal(w)) { w != pIgnoreWindow && !isShadowedByModal(w)) {
CBox box = (properties & Desktop::View::USE_PROP_TILED) ? w->getWindowBoxUnified(properties) : CBox{w->m_position, w->m_size}; 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)) if (box.containsPoint(pos))
return w; return w;
} }
@ -1180,7 +1179,7 @@ PHLWINDOW CCompositor::getWindowFromSurface(SP<CWLSurfaceResource> pSurface) {
const auto VIEW = pSurface->m_hlSurface->view(); const auto VIEW = pSurface->m_hlSurface->view();
if (!VIEW || VIEW->type() != Desktop::View::VIEW_TYPE_WINDOW) if (VIEW->type() != Desktop::View::VIEW_TYPE_WINDOW)
return nullptr; return nullptr;
return dynamicPointerCast<Desktop::View::CWindow>(VIEW); return dynamicPointerCast<Desktop::View::CWindow>(VIEW);
@ -1372,8 +1371,8 @@ void CCompositor::addToFadingOutSafe(PHLWINDOW pWindow) {
m_windowsFadingOut.emplace_back(pWindow); m_windowsFadingOut.emplace_back(pWindow);
} }
PHLWINDOW CCompositor::getWindowInDirection(PHLWINDOW pWindow, Math::eDirection dir) { PHLWINDOW CCompositor::getWindowInDirection(PHLWINDOW pWindow, char dir) {
if (dir == Math::DIRECTION_DEFAULT) if (!isDirection(dir))
return nullptr; return nullptr;
const auto PMONITOR = pWindow->m_monitor.lock(); const auto PMONITOR = pWindow->m_monitor.lock();
@ -1390,8 +1389,8 @@ PHLWINDOW CCompositor::getWindowInDirection(PHLWINDOW pWindow, Math::eDirection
return getWindowInDirection(WINDOWIDEALBB, PWORKSPACE, dir, pWindow, pWindow->m_isFloating); return getWindowInDirection(WINDOWIDEALBB, PWORKSPACE, dir, pWindow, pWindow->m_isFloating);
} }
PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, Math::eDirection dir, PHLWINDOW ignoreWindow, bool useVectorAngles) { PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, char dir, PHLWINDOW ignoreWindow, bool useVectorAngles) {
if (dir == Math::DIRECTION_DEFAULT) if (!isDirection(dir))
return nullptr; return nullptr;
// 0 -> history, 1 -> shared length // 0 -> history, 1 -> shared length
@ -1405,35 +1404,6 @@ PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorks
PHLWINDOW leaderWindow = nullptr; PHLWINDOW leaderWindow = nullptr;
if (!useVectorAngles) { 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) { 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()) if (w == ignoreWindow || !w->m_workspace || !w->m_isMapped || w->isHidden() || (!w->isFullscreen() && w->m_isFloating) || !w->m_workspace->isVisible())
continue; continue;
@ -1455,23 +1425,28 @@ PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorks
double intersectLength = -1; double intersectLength = -1;
switch (dir) { switch (dir) {
case Math::DIRECTION_LEFT: case 'l':
if (isAdjacent(POSA.x, POSA.x + SIZEA.x, POSB.x, POSB.x + SIZEB.x)) if (STICKS(POSA.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)); intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y));
}
break; break;
case Math::DIRECTION_RIGHT: case 'r':
if (isAdjacent(POSB.x, POSB.x + SIZEB.x, POSA.x, POSA.x + SIZEA.x)) if (STICKS(POSA.x + SIZEA.x, POSB.x)) {
intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y)); intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y));
}
break; break;
case Math::DIRECTION_UP: case 't':
if (isAdjacent(POSA.y, POSA.y + SIZEA.y, POSB.y, POSB.y + SIZEB.y)) case 'u':
if (STICKS(POSA.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)); intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x));
}
break; break;
case Math::DIRECTION_DOWN: case 'b':
if (isAdjacent(POSB.y, POSB.y + SIZEB.y, POSA.y, POSA.y + SIZEA.y)) case 'd':
if (STICKS(POSA.y + SIZEA.y, POSB.y)) {
intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x)); intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x));
}
break; break;
default: break;
} }
if (*PMETHOD == 0 /* history */) { if (*PMETHOD == 0 /* history */) {
@ -1500,8 +1475,12 @@ PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorks
} }
} }
} else { } else {
static const std::unordered_map<Math::eDirection, Vector2D> VECTORS = { if (dir == 'u')
{Math::DIRECTION_RIGHT, {1, 0}}, {Math::DIRECTION_UP, {0, -1}}, {Math::DIRECTION_DOWN, {0, 1}}, {Math::DIRECTION_LEFT, {-1, 0}}}; 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}}};
// //
auto vectorAngles = [](const Vector2D& a, const Vector2D& b) -> double { auto vectorAngles = [](const Vector2D& a, const Vector2D& b) -> double {
@ -1653,28 +1632,38 @@ bool CCompositor::isPointOnReservedArea(const Vector2D& point, const PHLMONITOR
return VECNOTINRECT(point, box.x, box.y, box.x + box.w, box.y + box.h); return VECNOTINRECT(point, box.x, box.y, box.x + box.w, box.y + box.h);
} }
std::optional<CBox> CCompositor::calculateX11WorkArea() { CBox CCompositor::calculateX11WorkArea() {
static auto PXWLFORCESCALEZERO = CConfigValue<Hyprlang::INT>("xwayland:force_zero_scaling"); static auto PXWLFORCESCALEZERO = CConfigValue<Hyprlang::INT>("xwayland:force_zero_scaling");
// We more than likely won't be able to calculate one CBox workbox = {0, 0, 0, 0};
// and even if we could this is minor bool firstMonitor = true;
if (m_monitors.size() > 1 || m_monitors.empty())
return std::nullopt;
const auto M = m_monitors.front();
for (const auto& monitor : m_monitors) {
// we ignore monitor->m_position on purpose // we ignore monitor->m_position on purpose
CBox box = M->logicalBoxMinusReserved().translate(-M->m_position); CBox box = monitor->logicalBoxMinusReserved().translate(-monitor->m_position);
if ((*PXWLFORCESCALEZERO)) if ((*PXWLFORCESCALEZERO))
box.scale(M->m_scale); box.scale(monitor->m_scale);
return box.translate(M->m_xwaylandPosition); 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;
}
}
}
// returning 0, 0 will remove the _NET_WORKAREA property
return workbox;
} }
PHLMONITOR CCompositor::getMonitorInDirection(Math::eDirection dir) { PHLMONITOR CCompositor::getMonitorInDirection(const char& dir) {
return getMonitorInDirection(Desktop::focusState()->monitor(), dir); return getMonitorInDirection(Desktop::focusState()->monitor(), dir);
} }
PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, Math::eDirection dir) { PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const char& dir) {
if (!pSourceMonitor) if (!pSourceMonitor)
return nullptr; return nullptr;
@ -1691,7 +1680,7 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, Math::e
const auto POSB = m->m_position; const auto POSB = m->m_position;
const auto SIZEB = m->m_size; const auto SIZEB = m->m_size;
switch (dir) { switch (dir) {
case Math::DIRECTION_LEFT: case 'l':
if (STICKS(POSA.x, POSB.x + SIZEB.x)) { 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)); 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) { if (INTERSECTLEN > longestIntersect) {
@ -1700,7 +1689,7 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, Math::e
} }
} }
break; break;
case Math::DIRECTION_RIGHT: case 'r':
if (STICKS(POSA.x + SIZEA.x, POSB.x)) { 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)); 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) { if (INTERSECTLEN > longestIntersect) {
@ -1709,7 +1698,8 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, Math::e
} }
} }
break; break;
case Math::DIRECTION_UP: case 't':
case 'u':
if (STICKS(POSA.y, POSB.y + SIZEB.y)) { 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)); 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) { if (INTERSECTLEN > longestIntersect) {
@ -1718,7 +1708,8 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, Math::e
} }
} }
break; break;
case Math::DIRECTION_DOWN: case 'b':
case 'd':
if (STICKS(POSA.y + SIZEA.y, POSB.y)) { 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)); 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) { if (INTERSECTLEN > longestIntersect) {
@ -1727,7 +1718,6 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, Math::e
} }
} }
break; break;
default: break;
} }
} }
@ -1783,7 +1773,7 @@ void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitor
// additionally, move floating and fs windows manually // additionally, move floating and fs windows manually
if (w->m_isFloating) if (w->m_isFloating)
w->layoutTarget()->setPositionGlobal(w->layoutTarget()->position().translate(-pMonitorA->m_position + pMonitorB->m_position)); *w->m_realPosition = w->m_realPosition->goal() - pMonitorA->m_position + pMonitorB->m_position;
if (w->isFullscreen()) { if (w->isFullscreen()) {
*w->m_realPosition = pMonitorB->m_position; *w->m_realPosition = pMonitorB->m_position;
@ -1808,7 +1798,7 @@ void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitor
// additionally, move floating and fs windows manually // additionally, move floating and fs windows manually
if (w->m_isFloating) if (w->m_isFloating)
w->layoutTarget()->setPositionGlobal(w->layoutTarget()->position().translate(-pMonitorB->m_position + pMonitorA->m_position)); *w->m_realPosition = w->m_realPosition->goal() - pMonitorB->m_position + pMonitorA->m_position;
if (w->isFullscreen()) { if (w->isFullscreen()) {
*w->m_realPosition = pMonitorA->m_position; *w->m_realPosition = pMonitorA->m_position;
@ -1822,11 +1812,8 @@ void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitor
pMonitorA->m_activeWorkspace = PWORKSPACEB; pMonitorA->m_activeWorkspace = PWORKSPACEB;
pMonitorB->m_activeWorkspace = PWORKSPACEA; pMonitorB->m_activeWorkspace = PWORKSPACEA;
g_layoutManager->recalculateMonitor(pMonitorA); g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitorA->m_id);
g_layoutManager->recalculateMonitor(pMonitorB); g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitorB->m_id);
g_pHyprRenderer->damageMonitor(pMonitorB);
g_pHyprRenderer->damageMonitor(pMonitorA);
g_pDesktopAnimationManager->setFullscreenFadeAnimation( g_pDesktopAnimationManager->setFullscreenFadeAnimation(
PWORKSPACEB, PWORKSPACEB->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : CDesktopAnimationManager::ANIMATION_TYPE_OUT); PWORKSPACEB, PWORKSPACEB->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : CDesktopAnimationManager::ANIMATION_TYPE_OUT);
@ -1838,30 +1825,29 @@ void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitor
Desktop::focusState()->fullWindowFocus( Desktop::focusState()->fullWindowFocus(
LASTWIN ? LASTWIN : LASTWIN ? LASTWIN :
(g_pCompositor->vectorToWindowUnified(g_pInputManager->getMouseCoordsInternal(), (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; 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 = "workspace", .data = PNEWWORKSPACE->m_name});
g_pEventManager->postEvent(SHyprIPCEvent{.event = "workspacev2", .data = std::format("{},{}", PNEWWORKSPACE->m_id, PNEWWORKSPACE->m_name)}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "workspacev2", .data = std::format("{},{}", PNEWWORKSPACE->m_id, PNEWWORKSPACE->m_name)});
Event::bus()->m_events.workspace.active.emit(PNEWWORKSPACE); EMIT_HOOK_EVENT("workspace", PNEWWORKSPACE);
} }
// events // event
g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspace", .data = PWORKSPACEA->m_name + "," + pMonitorB->m_name}); 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)}); 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 = "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)}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspacev2", .data = std::format("{},{},{}", PWORKSPACEB->m_id, PWORKSPACEB->m_name, pMonitorA->m_name)});
Event::bus()->m_events.workspace.moveToMonitor.emit(PWORKSPACEA, pMonitorB); EMIT_HOOK_EVENT("moveWorkspace", (std::vector<std::any>{PWORKSPACEB, pMonitorA}));
Event::bus()->m_events.workspace.moveToMonitor.emit(PWORKSPACEB, pMonitorA);
} }
PHLMONITOR CCompositor::getMonitorFromString(const std::string& name) { PHLMONITOR CCompositor::getMonitorFromString(const std::string& name) {
if (name == "current") if (name == "current")
return Desktop::focusState()->monitor(); return Desktop::focusState()->monitor();
else if (isDirection(name)) else if (isDirection(name))
return getMonitorInDirection(Math::fromChar(name[0])); return getMonitorInDirection(name[0]);
else if (name[0] == '+' || name[0] == '-') { else if (name[0] == '+' || name[0] == '-') {
// relative // relative
@ -1982,7 +1968,6 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo
// move the workspace // move the workspace
pWorkspace->m_monitor = pMonitor; pWorkspace->m_monitor = pMonitor;
pWorkspace->m_space->recheckWorkArea();
pWorkspace->m_events.monitorChanged.emit(); pWorkspace->m_events.monitorChanged.emit();
for (auto const& w : m_windows) { for (auto const& w : m_windows) {
@ -1998,18 +1983,17 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo
if (w->m_isMapped && !w->isHidden()) { if (w->m_isMapped && !w->isHidden()) {
if (POLDMON) { if (POLDMON) {
if (w->m_isFloating) if (w->m_isFloating)
w->layoutTarget()->setPositionGlobal(w->layoutTarget()->position().translate(-POLDMON->m_position + pMonitor->m_position)); *w->m_realPosition = w->m_realPosition->goal() - POLDMON->m_position + pMonitor->m_position;
if (w->isFullscreen()) { if (w->isFullscreen()) {
*w->m_realPosition = pMonitor->m_position; *w->m_realPosition = pMonitor->m_position;
*w->m_realSize = pMonitor->m_size; *w->m_realSize = pMonitor->m_size;
} }
} else } else
w->layoutTarget()->setPositionGlobal(CBox{Vector2D{ *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.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, (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(); w->updateToplevel();
@ -2037,8 +2021,7 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo
pWorkspace->m_events.activeChanged.emit(); pWorkspace->m_events.activeChanged.emit();
g_layoutManager->recalculateMonitor(pMonitor); g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitor->m_id);
g_pHyprRenderer->damageMonitor(pMonitor);
g_pDesktopAnimationManager->startAnimation(pWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true); g_pDesktopAnimationManager->startAnimation(pWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true);
pWorkspace->m_visible = true; pWorkspace->m_visible = true;
@ -2051,7 +2034,7 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo
// finalize // finalize
if (POLDMON) { if (POLDMON) {
g_layoutManager->recalculateMonitor(POLDMON); g_pLayoutManager->getCurrentLayout()->recalculateMonitor(POLDMON->m_id);
if (valid(POLDMON->m_activeWorkspace)) if (valid(POLDMON->m_activeWorkspace))
g_pDesktopAnimationManager->setFullscreenFadeAnimation(POLDMON->m_activeWorkspace, g_pDesktopAnimationManager->setFullscreenFadeAnimation(POLDMON->m_activeWorkspace,
POLDMON->m_activeWorkspace->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : POLDMON->m_activeWorkspace->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN :
@ -2066,8 +2049,7 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo
// event // event
g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspace", .data = pWorkspace->m_name + "," + pMonitor->m_name}); 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)}); 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) { bool CCompositor::workspaceIDOutOfBounds(const WORKSPACEID& id) {
@ -2150,25 +2132,24 @@ void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, Desktop::Vie
PWINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_FULLSCREEN | Desktop::Rule::RULE_PROP_FULLSCREENSTATE_CLIENT | 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); Desktop::Rule::RULE_PROP_FULLSCREENSTATE_INTERNAL | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
PWINDOW->updateDecorationValues(); PWINDOW->updateDecorationValues();
g_layoutManager->recalculateMonitor(PMONITOR); g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PWINDOW->monitorID());
return; return;
} }
g_pLayoutManager->getCurrentLayout()->fullscreenRequestForWindow(PWINDOW, CURRENT_EFFECTIVE_MODE, EFFECTIVE_MODE);
PWINDOW->m_fullscreenState.internal = state.internal;
PWORKSPACE->m_fullscreenMode = EFFECTIVE_MODE; PWORKSPACE->m_fullscreenMode = EFFECTIVE_MODE;
PWORKSPACE->m_hasFullscreenWindow = EFFECTIVE_MODE != FSMODE_NONE; PWORKSPACE->m_hasFullscreenWindow = EFFECTIVE_MODE != FSMODE_NONE;
g_layoutManager->fullscreenRequestForTarget(PWINDOW->layoutTarget(), CURRENT_EFFECTIVE_MODE, EFFECTIVE_MODE);
PWINDOW->m_fullscreenState.internal = state.internal;
g_pEventManager->postEvent(SHyprIPCEvent{.event = "fullscreen", .data = std::to_string(sc<int>(EFFECTIVE_MODE) != FSMODE_NONE)}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "fullscreen", .data = std::to_string(sc<int>(EFFECTIVE_MODE) != FSMODE_NONE)});
Event::bus()->m_events.window.fullscreen.emit(PWINDOW); EMIT_HOOK_EVENT("fullscreen", PWINDOW);
PWINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_FULLSCREEN | Desktop::Rule::RULE_PROP_FULLSCREENSTATE_CLIENT | 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); Desktop::Rule::RULE_PROP_FULLSCREENSTATE_INTERNAL | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
PWINDOW->updateDecorationValues(); PWINDOW->updateDecorationValues();
g_layoutManager->recalculateMonitor(PMONITOR); g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PWINDOW->monitorID());
// make all windows and layers on the same workspace under the fullscreen window // make all windows and layers on the same workspace under the fullscreen window
for (auto const& w : m_windows) { for (auto const& w : m_windows) {
@ -2281,7 +2262,7 @@ PHLWINDOW CCompositor::getWindowByRegex(const std::string& regexp_) {
} }
for (auto const& w : g_pCompositor->m_windows) { for (auto const& w : g_pCompositor->m_windows) {
if (!w->m_isMapped) if (!w->m_isMapped || (w->isHidden() && !g_pLayoutManager->getCurrentLayout()->isWindowReachable(w)))
continue; continue;
switch (mode) { switch (mode) {
@ -2586,30 +2567,59 @@ void CCompositor::moveWindowToWorkspaceSafe(PHLWINDOW pWindow, PHLWORKSPACE pWor
setWindowFullscreenInternal(pWindow, FSMODE_NONE); setWindowFullscreenInternal(pWindow, FSMODE_NONE);
const PHLWINDOW pFirstWindowOnWorkspace = pWorkspace->getFirstWindow(); const PHLWINDOW pFirstWindowOnWorkspace = pWorkspace->getFirstWindow();
const int visibleWindowsOnWorkspace = pWorkspace->getWindows(true, std::nullopt, true); const int visibleWindowsOnWorkspace = pWorkspace->getWindows(std::nullopt, true);
const auto POSTOMON = pWindow->m_realPosition->goal() - (pWindow->m_monitor ? pWindow->m_monitor->m_position : Vector2D{}); const auto POSTOMON = pWindow->m_realPosition->goal() - (pWindow->m_monitor ? pWindow->m_monitor->m_position : Vector2D{});
const auto PWORKSPACEMONITOR = pWorkspace->m_monitor.lock(); const auto PWORKSPACEMONITOR = pWorkspace->m_monitor.lock();
if (!pWindow->m_isFloating)
g_pLayoutManager->getCurrentLayout()->onWindowRemovedTiling(pWindow);
pWindow->moveToWorkspace(pWorkspace); pWindow->moveToWorkspace(pWorkspace);
pWindow->m_monitor = pWorkspace->m_monitor; pWindow->m_monitor = pWorkspace->m_monitor;
static auto PGROUPONMOVETOWORKSPACE = CConfigValue<Hyprlang::INT>("group:group_on_movetoworkspace"); static auto PGROUPONMOVETOWORKSPACE = CConfigValue<Hyprlang::INT>("group:group_on_movetoworkspace");
if (*PGROUPONMOVETOWORKSPACE && visibleWindowsOnWorkspace == 1 && pFirstWindowOnWorkspace && pFirstWindowOnWorkspace != pWindow && pFirstWindowOnWorkspace->m_group && if (*PGROUPONMOVETOWORKSPACE && visibleWindowsOnWorkspace == 1 && pFirstWindowOnWorkspace && pFirstWindowOnWorkspace != pWindow &&
pWindow->canBeGroupedInto(pFirstWindowOnWorkspace->m_group)) { pFirstWindowOnWorkspace->m_groupData.pNextWindow.lock() && pWindow->canBeGroupedInto(pFirstWindowOnWorkspace)) {
pFirstWindowOnWorkspace->m_group->add(pWindow);
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));
} else { } else {
if (!pWindow->m_isFloating)
g_pLayoutManager->getCurrentLayout()->onWindowCreatedTiling(pWindow);
if (pWindow->m_isFloating) if (pWindow->m_isFloating)
pWindow->layoutTarget()->setPositionGlobal(CBox{POSTOMON + PWORKSPACEMONITOR->m_position, pWindow->layoutTarget()->position().size()}); *pWindow->m_realPosition = POSTOMON + PWORKSPACEMONITOR->m_position;
} }
pWindow->updateToplevel(); pWindow->updateToplevel();
pWindow->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE); pWindow->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE);
pWindow->uncacheWindowDecos(); pWindow->uncacheWindowDecos();
pWindow->updateGroupOutputs();
if (pWindow->m_group) if (!pWindow->m_groupData.pNextWindow.expired()) {
pWindow->m_group->updateWorkspace(pWorkspace); PHLWINDOW next = pWindow->m_groupData.pNextWindow.lock();
while (next != pWindow) {
g_layoutManager->newTarget(pWindow->layoutTarget(), pWorkspace->m_space); next->updateToplevel();
next = next->m_groupData.pNextWindow.lock();
}
}
if (FULLSCREEN) if (FULLSCREEN)
setWindowFullscreenInternal(pWindow, FULLSCREENMODE); setWindowFullscreenInternal(pWindow, FULLSCREENMODE);
@ -2778,17 +2788,12 @@ void CCompositor::arrangeMonitors() {
} }
PROTO::xdgOutput->updateAllOutputs(); PROTO::xdgOutput->updateAllOutputs();
Event::bus()->m_events.monitor.layoutChanged.emit();
#ifndef NO_XWAYLAND #ifndef NO_XWAYLAND
const auto box = g_pCompositor->calculateX11WorkArea(); CBox box = g_pCompositor->calculateX11WorkArea();
if (g_pXWayland && g_pXWayland->m_wm) { if (!g_pXWayland || !g_pXWayland->m_wm)
if (box) return;
g_pXWayland->m_wm->updateWorkArea(box->x, box->y, box->w, box->h); g_pXWayland->m_wm->updateWorkArea(box.x, box.y, box.w, box.h);
else
g_pXWayland->m_wm->updateWorkArea(0, 0, 0, 0);
}
#endif #endif
} }
@ -2915,7 +2920,7 @@ void CCompositor::onNewMonitor(SP<Aquamarine::IOutput> output) {
PNEWMONITOR->m_id = FALLBACK ? MONITOR_INVALID : g_pCompositor->getNextAvailableMonitorID(output->name); PNEWMONITOR->m_id = FALLBACK ? MONITOR_INVALID : g_pCompositor->getNextAvailableMonitorID(output->name);
PNEWMONITOR->m_isUnsafeFallback = FALLBACK; PNEWMONITOR->m_isUnsafeFallback = FALLBACK;
Event::bus()->m_events.monitor.newMon.emit(PNEWMONITOR); EMIT_HOOK_EVENT("newMonitor", PNEWMONITOR);
if (!FALLBACK) if (!FALLBACK)
PNEWMONITOR->onConnect(false); PNEWMONITOR->onConnect(false);
@ -2968,16 +2973,13 @@ PImageDescription CCompositor::getHDRImageDescription() {
} }
return m_monitors.size() == 1 && m_monitors[0]->m_output && m_monitors[0]->m_output->parsedEDID.hdrMetadata.has_value() ? return m_monitors.size() == 1 && m_monitors[0]->m_output && m_monitors[0]->m_output->parsedEDID.hdrMetadata.has_value() ?
CImageDescription::from(SImageDescription{ CImageDescription::from(SImageDescription{.transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ,
.transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ,
.primariesNameSet = true, .primariesNameSet = true,
.primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020,
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020), .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020),
.masteringPrimaries = m_monitors[0]->getMasteringPrimaries(), .luminances = {.min = m_monitors[0]->m_output->parsedEDID.hdrMetadata->desiredContentMinLuminance,
.luminances = {.min = m_monitors[0]->minLuminance(HDR_MIN_LUMINANCE), .max = m_monitors[0]->maxLuminance(HDR_MAX_LUMINANCE), .reference = HDR_REF_LUMINANCE}, .max = m_monitors[0]->m_output->parsedEDID.hdrMetadata->desiredContentMaxLuminance,
.masteringLuminances = m_monitors[0]->getMasteringLuminances(), .reference = m_monitors[0]->m_output->parsedEDID.hdrMetadata->desiredMaxFrameAverageLuminance}}) :
.maxCLL = m_monitors[0]->maxCLL(),
.maxFALL = m_monitors[0]->maxFALL()}) :
DEFAULT_HDR_IMAGE_DESCRIPTION; DEFAULT_HDR_IMAGE_DESCRIPTION;
} }
@ -3074,27 +3076,6 @@ 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() { std::optional<unsigned int> CCompositor::getVTNr() {
if (!m_aqBackend->hasSession()) if (!m_aqBackend->hasSession())
return std::nullopt; return std::nullopt;
@ -3115,7 +3096,3 @@ std::optional<unsigned int> CCompositor::getVTNr() {
return ttynum; return ttynum;
} }
bool CCompositor::isVRRActiveOnAnyMonitor() const {
return std::ranges::any_of(m_monitors, [](const PHLMONITOR& m) { return m->m_vrrActive; });
}

View file

@ -4,12 +4,11 @@
#include <ranges> #include <ranges>
#include "helpers/math/Direction.hpp"
#include "managers/XWaylandManager.hpp" #include "managers/XWaylandManager.hpp"
#include "managers/KeybindManager.hpp" #include "managers/KeybindManager.hpp"
#include "managers/SessionLockManager.hpp" #include "managers/SessionLockManager.hpp"
#include "desktop/view/Window.hpp" #include "desktop/view/Window.hpp"
#include "helpers/cm/ColorManagement.hpp" #include "protocols/types/ColorManagement.hpp"
#include <aquamarine/backend/Backend.hpp> #include <aquamarine/backend/Backend.hpp>
#include <aquamarine/output/Output.hpp> #include <aquamarine/output/Output.hpp>
@ -114,16 +113,16 @@ class CCompositor {
bool isWindowActive(PHLWINDOW); bool isWindowActive(PHLWINDOW);
void changeWindowZOrder(PHLWINDOW, bool); void changeWindowZOrder(PHLWINDOW, bool);
void cleanupFadingOut(const MONITORID& monid); void cleanupFadingOut(const MONITORID& monid);
PHLWINDOW getWindowInDirection(PHLWINDOW, Math::eDirection); PHLWINDOW getWindowInDirection(PHLWINDOW, char);
PHLWINDOW getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, Math::eDirection dir, PHLWINDOW ignoreWindow = nullptr, bool useVectorAngles = false); PHLWINDOW getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, char 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 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); PHLWINDOW getWindowCycleHist(PHLWINDOWREF cur, bool focusableOnly = false, std::optional<bool> floating = std::nullopt, bool visible = false, bool next = false);
WORKSPACEID getNextAvailableNamedWorkspace(); WORKSPACEID getNextAvailableNamedWorkspace();
bool isPointOnAnyMonitor(const Vector2D&); bool isPointOnAnyMonitor(const Vector2D&);
bool isPointOnReservedArea(const Vector2D& point, const PHLMONITOR monitor = nullptr); bool isPointOnReservedArea(const Vector2D& point, const PHLMONITOR monitor = nullptr);
std::optional<CBox> calculateX11WorkArea(); CBox calculateX11WorkArea();
PHLMONITOR getMonitorInDirection(Math::eDirection); PHLMONITOR getMonitorInDirection(const char&);
PHLMONITOR getMonitorInDirection(PHLMONITOR, Math::eDirection); PHLMONITOR getMonitorInDirection(PHLMONITOR, const char&);
void updateAllWindowsAnimatedDecorationValues(); void updateAllWindowsAnimatedDecorationValues();
MONITORID getNextAvailableMonitorID(std::string const& name); MONITORID getNextAvailableMonitorID(std::string const& name);
void moveWorkspaceToMonitor(PHLWORKSPACE, PHLMONITOR, bool noWarpCursor = false); void moveWorkspaceToMonitor(PHLWORKSPACE, PHLMONITOR, bool noWarpCursor = false);
@ -161,9 +160,7 @@ class CCompositor {
void updateSuspendedStates(); void updateSuspendedStates();
void onNewMonitor(SP<Aquamarine::IOutput> output); void onNewMonitor(SP<Aquamarine::IOutput> output);
void ensurePersistentWorkspacesPresent(const std::vector<SWorkspaceRule>& rules, PHLWORKSPACE pWorkspace = nullptr); void ensurePersistentWorkspacesPresent(const std::vector<SWorkspaceRule>& rules, PHLWORKSPACE pWorkspace = nullptr);
void ensureWorkspacesOnAssignedMonitors();
std::optional<unsigned int> getVTNr(); std::optional<unsigned int> getVTNr();
bool isVRRActiveOnAnyMonitor() const;
NColorManagement::PImageDescription getPreferredImageDescription(); NColorManagement::PImageDescription getPreferredImageDescription();
NColorManagement::PImageDescription getHDRImageDescription(); NColorManagement::PImageDescription getHDRImageDescription();

View file

@ -38,6 +38,10 @@ enum eInputType : uint8_t {
INPUT_TYPE_MOTION INPUT_TYPE_MOTION
}; };
struct SCallbackInfo {
bool cancelled = false; /* on cancellable events, will cancel the event. */
};
enum eHyprCtlOutputFormat : uint8_t { enum eHyprCtlOutputFormat : uint8_t {
FORMAT_NORMAL = 0, FORMAT_NORMAL = 0,
FORMAT_JSON FORMAT_JSON
@ -58,3 +62,5 @@ struct SDispatchResult {
using WINDOWID = int64_t; using WINDOWID = int64_t;
using MONITORID = int64_t; using MONITORID = int64_t;
using WORKSPACEID = int64_t; using WORKSPACEID = int64_t;
using HOOK_CALLBACK_FN = std::function<void(void*, SCallbackInfo&, std::any)>;

View file

@ -1115,12 +1115,6 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL, .type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SRangeData{0, -20, 20}, .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{ SConfigOptionDescription{
.value = "group:groupbar:blur", .value = "group:groupbar:blur",
.description = "enable background blur for groupbars", .description = "enable background blur for groupbars",
@ -1568,28 +1562,10 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
}, },
SConfigOptionDescription{ SConfigOptionDescription{
.value = "render:cm_sdr_eotf", .value = "render:cm_sdr_eotf",
.description = "Default transfer function for displaying SDR apps. default - Use default value (Gamma 2.2), gamma22 - Treat unspecified as Gamma 2.2, gamma22force - Treat " .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, srgb - Treat unspecified as sRGB", "unspecified and sRGB as Gamma 2.2, 3 - Treat unspecified as sRGB",
.type = CONFIG_OPTION_STRING_SHORT, .type = CONFIG_OPTION_CHOICE,
.data = SConfigOptionDescription::SStringData{"default"}, .data = SConfigOptionDescription::SChoiceData{0, "default,gamma22,gamma22force,srgb"},
},
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},
}, },
/* /*
@ -1714,9 +1690,9 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
}, },
SConfigOptionDescription{ SConfigOptionDescription{
.value = "cursor:use_cpu_buffer", .value = "cursor:use_cpu_buffer",
.description = "Makes HW cursors use a CPU buffer. Required on Nvidia to have HW cursors. 0 - off, 1 - on, 2 - auto (nvidia only)", .description = "Makes HW cursors use a CPU buffer. Required on Nvidia to have HW cursors. Experimental",
.type = CONFIG_OPTION_INT, .type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SRangeData{.value = 2, .min = 0, .max = 2}, .data = SConfigOptionDescription::SBoolData{false},
}, },
SConfigOptionDescription{ SConfigOptionDescription{
.value = "cursor:sync_gsettings_theme", .value = "cursor:sync_gsettings_theme",
@ -1770,12 +1746,6 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL, .type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false}, .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{ SConfigOptionDescription{
.value = "debug:disable_logs", .value = "debug:disable_logs",
.description = "disable logging to a file", .description = "disable logging to a file",
@ -1854,46 +1824,6 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL, .type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false}, .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: * dwindle:
@ -1974,6 +1904,18 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL, .type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false}, .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: * master:
@ -2059,65 +2001,6 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.data = SConfigOptionDescription::SBoolData{false}, .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 * Quirks
*/ */
@ -2128,11 +2011,5 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_INT, .type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{.value = 0, .min = 0, .max = 2}, .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},
},
}; };

View file

@ -18,14 +18,13 @@
#include "../desktop/rule/layerRule/LayerRule.hpp" #include "../desktop/rule/layerRule/LayerRule.hpp"
#include "../debug/HyprCtl.hpp" #include "../debug/HyprCtl.hpp"
#include "../desktop/state/FocusState.hpp" #include "../desktop/state/FocusState.hpp"
#include "../layout/space/Space.hpp"
#include "../layout/supplementary/WorkspaceAlgoMatcher.hpp"
#include "defaultConfig.hpp" #include "defaultConfig.hpp"
#include "../render/Renderer.hpp" #include "../render/Renderer.hpp"
#include "../hyprerror/HyprError.hpp" #include "../hyprerror/HyprError.hpp"
#include "../managers/input/InputManager.hpp" #include "../managers/input/InputManager.hpp"
#include "../managers/eventLoop/EventLoopManager.hpp" #include "../managers/eventLoop/EventLoopManager.hpp"
#include "../managers/LayoutManager.hpp"
#include "../managers/EventManager.hpp" #include "../managers/EventManager.hpp"
#include "../managers/permissions/DynamicPermissionManager.hpp" #include "../managers/permissions/DynamicPermissionManager.hpp"
#include "../debug/HyprNotificationOverlay.hpp" #include "../debug/HyprNotificationOverlay.hpp"
@ -40,10 +39,8 @@
#include "../managers/input/trackpad/gestures/CloseGesture.hpp" #include "../managers/input/trackpad/gestures/CloseGesture.hpp"
#include "../managers/input/trackpad/gestures/FloatGesture.hpp" #include "../managers/input/trackpad/gestures/FloatGesture.hpp"
#include "../managers/input/trackpad/gestures/FullscreenGesture.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 "../protocols/types/ContentType.hpp"
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
@ -402,12 +399,6 @@ static Hyprlang::CParseResult handleWindowrule(const char* c, const char* v) {
return result; 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) { static Hyprlang::CParseResult handleLayerrule(const char* c, const char* v) {
const std::string VALUE = v; const std::string VALUE = v;
const std::string COMMAND = c; const std::string COMMAND = c;
@ -420,12 +411,6 @@ static Hyprlang::CParseResult handleLayerrule(const char* c, const char* v) {
return result; 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) { void CConfigManager::registerConfigVar(const char* name, const Hyprlang::INT& val) {
m_configValueNumber++; m_configValueNumber++;
m_config->addConfigValue(name, val); m_config->addConfigValue(name, val);
@ -556,14 +541,12 @@ CConfigManager::CConfigManager() {
registerConfigVar("group:groupbar:gaps_in", Hyprlang::INT{2}); registerConfigVar("group:groupbar:gaps_in", Hyprlang::INT{2});
registerConfigVar("group:groupbar:keep_upper_gap", Hyprlang::INT{1}); registerConfigVar("group:groupbar:keep_upper_gap", Hyprlang::INT{1});
registerConfigVar("group:groupbar:text_offset", Hyprlang::INT{0}); registerConfigVar("group:groupbar:text_offset", Hyprlang::INT{0});
registerConfigVar("group:groupbar:text_padding", Hyprlang::INT{0});
registerConfigVar("group:groupbar:blur", Hyprlang::INT{0}); registerConfigVar("group:groupbar:blur", Hyprlang::INT{0});
registerConfigVar("debug:log_damage", Hyprlang::INT{0}); registerConfigVar("debug:log_damage", Hyprlang::INT{0});
registerConfigVar("debug:overlay", Hyprlang::INT{0}); registerConfigVar("debug:overlay", Hyprlang::INT{0});
registerConfigVar("debug:damage_blink", Hyprlang::INT{0}); registerConfigVar("debug:damage_blink", Hyprlang::INT{0});
registerConfigVar("debug:pass", 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_logs", Hyprlang::INT{1});
registerConfigVar("debug:disable_time", Hyprlang::INT{1}); registerConfigVar("debug:disable_time", Hyprlang::INT{1});
registerConfigVar("debug:enable_stdout_logs", Hyprlang::INT{0}); registerConfigVar("debug:enable_stdout_logs", Hyprlang::INT{0});
@ -575,10 +558,6 @@ CConfigManager::CConfigManager() {
registerConfigVar("debug:disable_scale_checks", Hyprlang::INT{0}); registerConfigVar("debug:disable_scale_checks", Hyprlang::INT{0});
registerConfigVar("debug:colored_stdout_logs", Hyprlang::INT{1}); registerConfigVar("debug:colored_stdout_logs", Hyprlang::INT{1});
registerConfigVar("debug:full_cm_proto", Hyprlang::INT{0}); 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", Hyprlang::INT{0});
registerConfigVar("decoration:rounding_power", {2.F}); registerConfigVar("decoration:rounding_power", {2.F});
@ -618,9 +597,6 @@ CConfigManager::CConfigManager() {
registerConfigVar("decoration:screen_shader", {STRVAL_EMPTY}); registerConfigVar("decoration:screen_shader", {STRVAL_EMPTY});
registerConfigVar("decoration:border_part_of_window", Hyprlang::INT{1}); 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:pseudotile", Hyprlang::INT{0});
registerConfigVar("dwindle:force_split", Hyprlang::INT{0}); registerConfigVar("dwindle:force_split", Hyprlang::INT{0});
registerConfigVar("dwindle:permanent_direction_override", Hyprlang::INT{0}); registerConfigVar("dwindle:permanent_direction_override", Hyprlang::INT{0});
@ -633,6 +609,8 @@ CConfigManager::CConfigManager() {
registerConfigVar("dwindle:smart_split", Hyprlang::INT{0}); registerConfigVar("dwindle:smart_split", Hyprlang::INT{0});
registerConfigVar("dwindle:smart_resizing", Hyprlang::INT{1}); registerConfigVar("dwindle:smart_resizing", Hyprlang::INT{1});
registerConfigVar("dwindle:precise_mouse_move", Hyprlang::INT{0}); 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:special_scale_factor", {1.f});
registerConfigVar("master:mfact", {0.55f}); registerConfigVar("master:mfact", {0.55f});
@ -648,16 +626,6 @@ CConfigManager::CConfigManager() {
registerConfigVar("master:drop_at_cursor", Hyprlang::INT{1}); registerConfigVar("master:drop_at_cursor", Hyprlang::INT{1});
registerConfigVar("master:always_keep_position", Hyprlang::INT{0}); 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:enabled", Hyprlang::INT{1});
registerConfigVar("animations:workspace_wraparound", Hyprlang::INT{0}); registerConfigVar("animations:workspace_wraparound", Hyprlang::INT{0});
@ -728,9 +696,9 @@ CConfigManager::CConfigManager() {
registerConfigVar("binds:movefocus_cycles_fullscreen", Hyprlang::INT{0}); registerConfigVar("binds:movefocus_cycles_fullscreen", Hyprlang::INT{0});
registerConfigVar("binds:movefocus_cycles_groupfirst", Hyprlang::INT{0}); registerConfigVar("binds:movefocus_cycles_groupfirst", Hyprlang::INT{0});
registerConfigVar("binds:disable_keybind_grabbing", 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:allow_pin_fullscreen", Hyprlang::INT{0});
registerConfigVar("binds:drag_threshold", 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_distance", Hyprlang::INT{300});
registerConfigVar("gestures:workspace_swipe_invert", Hyprlang::INT{1}); registerConfigVar("gestures:workspace_swipe_invert", Hyprlang::INT{1});
@ -797,17 +765,13 @@ CConfigManager::CConfigManager() {
registerConfigVar("render:cm_auto_hdr", Hyprlang::INT{1}); registerConfigVar("render:cm_auto_hdr", Hyprlang::INT{1});
registerConfigVar("render:new_render_scheduling", Hyprlang::INT{0}); registerConfigVar("render:new_render_scheduling", Hyprlang::INT{0});
registerConfigVar("render:non_shader_cm", Hyprlang::INT{3}); registerConfigVar("render:non_shader_cm", Hyprlang::INT{3});
registerConfigVar("render:cm_sdr_eotf", {"default"}); registerConfigVar("render:cm_sdr_eotf", Hyprlang::INT{0});
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_update_news", Hyprlang::INT{0});
registerConfigVar("ecosystem:no_donation_nag", Hyprlang::INT{0}); registerConfigVar("ecosystem:no_donation_nag", Hyprlang::INT{0});
registerConfigVar("ecosystem:enforce_permissions", Hyprlang::INT{0}); registerConfigVar("ecosystem:enforce_permissions", Hyprlang::INT{0});
registerConfigVar("quirks:prefer_hdr", Hyprlang::INT{0}); registerConfigVar("quirks:prefer_hdr", Hyprlang::INT{0});
registerConfigVar("quirks:skip_non_kms_dmabuf_formats", Hyprlang::INT{0});
// devices // devices
m_config->addSpecialCategory("device", {"name"}); m_config->addSpecialCategory("device", {"name"});
@ -863,7 +827,7 @@ CConfigManager::CConfigManager() {
m_config->addSpecialConfigValue("monitorv2", "mirror", {STRVAL_EMPTY}); m_config->addSpecialConfigValue("monitorv2", "mirror", {STRVAL_EMPTY});
m_config->addSpecialConfigValue("monitorv2", "bitdepth", {STRVAL_EMPTY}); // TODO use correct type m_config->addSpecialConfigValue("monitorv2", "bitdepth", {STRVAL_EMPTY}); // TODO use correct type
m_config->addSpecialConfigValue("monitorv2", "cm", {"auto"}); m_config->addSpecialConfigValue("monitorv2", "cm", {"auto"});
m_config->addSpecialConfigValue("monitorv2", "sdr_eotf", {"default"}); m_config->addSpecialConfigValue("monitorv2", "sdr_eotf", Hyprlang::INT{0});
m_config->addSpecialConfigValue("monitorv2", "sdrbrightness", Hyprlang::FLOAT{1.0}); m_config->addSpecialConfigValue("monitorv2", "sdrbrightness", Hyprlang::FLOAT{1.0});
m_config->addSpecialConfigValue("monitorv2", "sdrsaturation", Hyprlang::FLOAT{1.0}); m_config->addSpecialConfigValue("monitorv2", "sdrsaturation", Hyprlang::FLOAT{1.0});
m_config->addSpecialConfigValue("monitorv2", "vrr", Hyprlang::INT{0}); m_config->addSpecialConfigValue("monitorv2", "vrr", Hyprlang::INT{0});
@ -875,7 +839,6 @@ CConfigManager::CConfigManager() {
m_config->addSpecialConfigValue("monitorv2", "min_luminance", Hyprlang::FLOAT{-1.0}); m_config->addSpecialConfigValue("monitorv2", "min_luminance", Hyprlang::FLOAT{-1.0});
m_config->addSpecialConfigValue("monitorv2", "max_luminance", Hyprlang::INT{-1}); m_config->addSpecialConfigValue("monitorv2", "max_luminance", Hyprlang::INT{-1});
m_config->addSpecialConfigValue("monitorv2", "max_avg_luminance", Hyprlang::INT{-1}); m_config->addSpecialConfigValue("monitorv2", "max_avg_luminance", Hyprlang::INT{-1});
m_config->addSpecialConfigValue("monitorv2", "icc", Hyprlang::STRING{""});
// windowrule v3 // windowrule v3
m_config->addSpecialCategory("windowrule", {.key = "name"}); m_config->addSpecialCategory("windowrule", {.key = "name"});
@ -905,13 +868,9 @@ CConfigManager::CConfigManager() {
m_config->registerHandler(&::handleSubmap, "submap", {false}); m_config->registerHandler(&::handleSubmap, "submap", {false});
m_config->registerHandler(&::handlePlugin, "plugin", {false}); m_config->registerHandler(&::handlePlugin, "plugin", {false});
m_config->registerHandler(&::handlePermission, "permission", {false}); m_config->registerHandler(&::handlePermission, "permission", {false});
m_config->registerHandler(&::handleGesture, "gesture", {true}); m_config->registerHandler(&::handleGesture, "gesture", {false});
m_config->registerHandler(&::handleEnv, "env", {true}); m_config->registerHandler(&::handleEnv, "env", {true});
// windowrulev2 and layerrulev2 errors
m_config->registerHandler(&::handleWindowrulev2, "windowrulev2", {false});
m_config->registerHandler(&::handleLayerrulev2, "layerrulev2", {false});
// pluginza // pluginza
m_config->addSpecialCategory("plugin", {nullptr, true}); m_config->addSpecialCategory("plugin", {nullptr, true});
@ -1072,7 +1031,7 @@ static void clearHlVersionVars() {
} }
void CConfigManager::reload() { void CConfigManager::reload() {
Event::bus()->m_events.config.preReload.emit(); EMIT_HOOK_EVENT("preConfigReload", nullptr);
setDefaultAnimationVars(); setDefaultAnimationVars();
resetHLConfig(); resetHLConfig();
m_configCurrentPath = getMainConfigPath(); m_configCurrentPath = getMainConfigPath();
@ -1214,18 +1173,8 @@ std::optional<std::string> CConfigManager::handleMonitorv2(const std::string& ou
if (VAL && VAL->m_bSetByUser) if (VAL && VAL->m_bSetByUser)
parser.parseCM(std::any_cast<Hyprlang::STRING>(VAL->getValue())); parser.parseCM(std::any_cast<Hyprlang::STRING>(VAL->getValue()));
VAL = m_config->getSpecialConfigValuePtr("monitorv2", "sdr_eotf", output.c_str()); VAL = m_config->getSpecialConfigValuePtr("monitorv2", "sdr_eotf", output.c_str());
if (VAL && VAL->m_bSetByUser) { if (VAL && VAL->m_bSetByUser)
const std::string value = std::any_cast<Hyprlang::STRING>(VAL->getValue()); parser.rule().sdrEotf = std::any_cast<Hyprlang::INT>(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()); VAL = m_config->getSpecialConfigValuePtr("monitorv2", "sdrbrightness", output.c_str());
if (VAL && VAL->m_bSetByUser) if (VAL && VAL->m_bSetByUser)
parser.rule().sdrBrightness = std::any_cast<Hyprlang::FLOAT>(VAL->getValue()); parser.rule().sdrBrightness = std::any_cast<Hyprlang::FLOAT>(VAL->getValue());
@ -1262,10 +1211,6 @@ std::optional<std::string> CConfigManager::handleMonitorv2(const std::string& ou
if (VAL && VAL->m_bSetByUser) if (VAL && VAL->m_bSetByUser)
parser.rule().maxAvgLuminance = std::any_cast<Hyprlang::INT>(VAL->getValue()); 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(); auto newrule = parser.rule();
std::erase_if(m_monitorRules, [&](const auto& other) { return other.name == newrule.name; }); std::erase_if(m_monitorRules, [&](const auto& other) { return other.name == newrule.name; });
@ -1368,8 +1313,7 @@ void CConfigManager::postConfigReload(const Hyprlang::CParseResult& result) {
static auto PZOOMFACTOR = CConfigValue<Hyprlang::FLOAT>("cursor:zoom_factor"); static auto PZOOMFACTOR = CConfigValue<Hyprlang::FLOAT>("cursor:zoom_factor");
for (auto const& m : g_pCompositor->m_monitors) { for (auto const& m : g_pCompositor->m_monitors) {
*(m->m_cursorZoom) = *PZOOMFACTOR; *(m->m_cursorZoom) = *PZOOMFACTOR;
if (m->m_activeWorkspace) g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->m_id);
m->m_activeWorkspace->m_space->recalculate();
} }
// Update the keyboard layout to the cfg'd one if this is not the first launch // Update the keyboard layout to the cfg'd one if this is not the first launch
@ -1437,6 +1381,9 @@ void CConfigManager::postConfigReload(const Hyprlang::CParseResult& result) {
// Update window border colors // Update window border colors
g_pCompositor->updateAllWindowsAnimatedDecorationValues(); g_pCompositor->updateAllWindowsAnimatedDecorationValues();
// update layout
g_pLayoutManager->switchToLayout(std::any_cast<Hyprlang::STRING>(m_config->getConfigValue("general:layout")));
// manual crash // manual crash
if (std::any_cast<Hyprlang::INT>(m_config->getConfigValue("debug:manual_crash")) && !m_manualCrashInitiated) { if (std::any_cast<Hyprlang::INT>(m_config->getConfigValue("debug:manual_crash")) && !m_manualCrashInitiated) {
m_manualCrashInitiated = true; m_manualCrashInitiated = true;
@ -1475,10 +1422,7 @@ void CConfigManager::postConfigReload(const Hyprlang::CParseResult& result) {
if (!m_isFirstLaunch) if (!m_isFirstLaunch)
ensurePersistentWorkspacesPresent(); ensurePersistentWorkspacesPresent();
// update layouts EMIT_HOOK_EVENT("configReloaded", nullptr);
Layout::Supplementary::algoMatcher()->updateWorkspaceLayouts();
Event::bus()->m_events.config.reloaded.emit();
if (g_pEventManager) if (g_pEventManager)
g_pEventManager->postEvent(SHyprIPCEvent{"configreloaded", ""}); g_pEventManager->postEvent(SHyprIPCEvent{"configreloaded", ""});
} }
@ -1500,9 +1444,8 @@ std::string CConfigManager::parseKeyword(const std::string& COMMAND, const std::
// invalidate layouts if they changed // invalidate layouts if they changed
if (COMMAND == "monitor" || COMMAND.contains("gaps_") || COMMAND.starts_with("dwindle:") || COMMAND.starts_with("master:")) { if (COMMAND == "monitor" || COMMAND.contains("gaps_") || COMMAND.starts_with("dwindle:") || COMMAND.starts_with("master:")) {
for (auto const& m : g_pCompositor->m_monitors) { for (auto const& m : g_pCompositor->m_monitors)
g_layoutManager->recalculateMonitor(m); g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->m_id);
}
} }
// Update window border colors // Update window border colors
@ -1674,8 +1617,6 @@ SWorkspaceRule CConfigManager::mergeWorkspaceRules(const SWorkspaceRule& rule1,
mergedRule.onCreatedEmptyRunCmd = rule2.onCreatedEmptyRunCmd; mergedRule.onCreatedEmptyRunCmd = rule2.onCreatedEmptyRunCmd;
if (rule2.defaultName.has_value()) if (rule2.defaultName.has_value())
mergedRule.defaultName = rule2.defaultName; mergedRule.defaultName = rule2.defaultName;
if (rule2.layout.has_value())
mergedRule.layout = rule2.layout;
if (!rule2.layoutopts.empty()) { if (!rule2.layoutopts.empty()) {
for (const auto& layoutopt : rule2.layoutopts) { for (const auto& layoutopt : rule2.layoutopts) {
mergedRule.layoutopts[layoutopt.first] = layoutopt.second; mergedRule.layoutopts[layoutopt.first] = layoutopt.second;
@ -1767,7 +1708,7 @@ void CConfigManager::performMonitorReload() {
m_wantsMonitorReload = false; m_wantsMonitorReload = false;
Event::bus()->m_events.monitor.layoutChanged.emit(); EMIT_HOOK_EVENT("monitorLayoutChanged", nullptr);
} }
void* const* CConfigManager::getConfigValuePtr(const std::string& val) { void* const* CConfigManager::getConfigValuePtr(const std::string& val) {
@ -1825,16 +1766,7 @@ void CConfigManager::ensureVRR(PHLMONITOR pMonitor) {
} }
m->m_vrrActive = false; m->m_vrrActive = false;
return; return;
} } else if (USEVRR == 1) {
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) { if (!m->m_vrrActive) {
m->m_output->state->resetExplicitFences(); m->m_output->state->resetExplicitFences();
m->m_output->state->setAdaptiveSync(true); m->m_output->state->setAdaptiveSync(true);
@ -1848,18 +1780,10 @@ void CConfigManager::ensureVRR(PHLMONITOR pMonitor) {
Log::logger->log(Log::ERR, "Couldn't commit output {} in ensureVRR -> true", m->m_output->name); Log::logger->log(Log::ERR, "Couldn't commit output {} in ensureVRR -> true", m->m_output->name);
} }
m->m_vrrActive = true; 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 -> false", m->m_output->name);
}
m->m_vrrActive = false;
}
return; return;
} else if (USEVRR == 2 || USEVRR == 3) { } else if (USEVRR == 2 || USEVRR == 3) {
const auto PWORKSPACE = m->m_activeWorkspace;
if (!PWORKSPACE) if (!PWORKSPACE)
return; // ??? return; // ???
@ -2284,15 +2208,6 @@ bool CMonitorRuleParser::parseVRR(const std::string& value) {
return true; 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() { void CMonitorRuleParser::setDisabled() {
m_rule.disabled = true; m_rule.disabled = true;
} }
@ -2387,9 +2302,6 @@ std::optional<std::string> CConfigManager::handleMonitor(const std::string& comm
} else if (ARGS[argno] == "vrr") { } else if (ARGS[argno] == "vrr") {
parser.parseVRR(std::string(ARGS[argno + 1])); parser.parseVRR(std::string(ARGS[argno + 1]));
argno++; argno++;
} else if (ARGS[argno] == "icc") {
parser.parseICC(std::string(ARGS[argno + 1]));
argno++;
} else if (ARGS[argno] == "workspace") { } else if (ARGS[argno] == "workspace") {
const auto& [id, name, isAutoID] = getWorkspaceIDNameFromString(std::string(ARGS[argno + 1])); const auto& [id, name, isAutoID] = getWorkspaceIDNameFromString(std::string(ARGS[argno + 1]));
@ -2756,9 +2668,6 @@ std::optional<std::string> CConfigManager::handleWorkspaceRules(const std::strin
opt = opt.substr(0, opt.find(':')); opt = opt.substr(0, opt.find(':'));
wsRule.layoutopts[opt] = val; 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 {}; return {};
@ -2895,8 +2804,6 @@ std::optional<std::string> CConfigManager::handlePermission(const std::string& c
if (data[1] == "screencopy") if (data[1] == "screencopy")
type = PERMISSION_TYPE_SCREENCOPY; type = PERMISSION_TYPE_SCREENCOPY;
else if (data[1] == "cursorpos")
type = PERMISSION_TYPE_CURSOR_POS;
else if (data[1] == "plugin") else if (data[1] == "plugin")
type = PERMISSION_TYPE_PLUGIN; type = PERMISSION_TYPE_PLUGIN;
else if (data[1] == "keyboard" || data[1] == "keeb") else if (data[1] == "keyboard" || data[1] == "keeb")
@ -2914,7 +2821,7 @@ std::optional<std::string> CConfigManager::handlePermission(const std::string& c
if (mode == PERMISSION_RULE_ALLOW_MODE_UNKNOWN) if (mode == PERMISSION_RULE_ALLOW_MODE_UNKNOWN)
return "unknown permission allow mode"; return "unknown permission allow mode";
if (m_isFirstLaunch && g_pDynamicPermissionManager) if (m_isFirstLaunch)
g_pDynamicPermissionManager->addConfigPermissionRule(std::string(data[0]), type, mode); g_pDynamicPermissionManager->addConfigPermissionRule(std::string(data[0]), type, mode);
return {}; return {};
@ -2941,14 +2848,6 @@ std::optional<std::string> CConfigManager::handleGesture(const std::string& comm
int startDataIdx = 2; int startDataIdx = 2;
uint32_t modMask = 0; uint32_t modMask = 0;
float deltaScale = 1.F; 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) { while (true) {
@ -2971,29 +2870,23 @@ std::optional<std::string> CConfigManager::handleGesture(const std::string& comm
if (data[startDataIdx] == "dispatcher") if (data[startDataIdx] == "dispatcher")
result = g_pTrackpadGestures->addGesture(makeUnique<CDispatcherTrackpadGesture>(std::string{data[startDataIdx + 1]}, data.join(",", startDataIdx + 2)), fingerCount, result = g_pTrackpadGestures->addGesture(makeUnique<CDispatcherTrackpadGesture>(std::string{data[startDataIdx + 1]}, data.join(",", startDataIdx + 2)), fingerCount,
direction, modMask, deltaScale, disableInhibit); direction, modMask, deltaScale);
else if (data[startDataIdx] == "workspace") else if (data[startDataIdx] == "workspace")
result = g_pTrackpadGestures->addGesture(makeUnique<CWorkspaceSwipeGesture>(), fingerCount, direction, modMask, deltaScale, disableInhibit); result = g_pTrackpadGestures->addGesture(makeUnique<CWorkspaceSwipeGesture>(), fingerCount, direction, modMask, deltaScale);
else if (data[startDataIdx] == "resize") else if (data[startDataIdx] == "resize")
result = g_pTrackpadGestures->addGesture(makeUnique<CResizeTrackpadGesture>(), fingerCount, direction, modMask, deltaScale, disableInhibit); result = g_pTrackpadGestures->addGesture(makeUnique<CResizeTrackpadGesture>(), fingerCount, direction, modMask, deltaScale);
else if (data[startDataIdx] == "move") else if (data[startDataIdx] == "move")
result = g_pTrackpadGestures->addGesture(makeUnique<CMoveTrackpadGesture>(), fingerCount, direction, modMask, deltaScale, disableInhibit); result = g_pTrackpadGestures->addGesture(makeUnique<CMoveTrackpadGesture>(), fingerCount, direction, modMask, deltaScale);
else if (data[startDataIdx] == "special") else if (data[startDataIdx] == "special")
result = result = g_pTrackpadGestures->addGesture(makeUnique<CSpecialWorkspaceGesture>(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale);
g_pTrackpadGestures->addGesture(makeUnique<CSpecialWorkspaceGesture>(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale, disableInhibit);
else if (data[startDataIdx] == "close") else if (data[startDataIdx] == "close")
result = g_pTrackpadGestures->addGesture(makeUnique<CCloseTrackpadGesture>(), fingerCount, direction, modMask, deltaScale, disableInhibit); result = g_pTrackpadGestures->addGesture(makeUnique<CCloseTrackpadGesture>(), fingerCount, direction, modMask, deltaScale);
else if (data[startDataIdx] == "float") else if (data[startDataIdx] == "float")
result = result = g_pTrackpadGestures->addGesture(makeUnique<CFloatTrackpadGesture>(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale);
g_pTrackpadGestures->addGesture(makeUnique<CFloatTrackpadGesture>(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale, disableInhibit);
else if (data[startDataIdx] == "fullscreen") else if (data[startDataIdx] == "fullscreen")
result = g_pTrackpadGestures->addGesture(makeUnique<CFullscreenTrackpadGesture>(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale, result = g_pTrackpadGestures->addGesture(makeUnique<CFullscreenTrackpadGesture>(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale);
disableInhibit); else if (data[startDataIdx] == "unset")
else if (data[startDataIdx] == "cursorZoom") { result = g_pTrackpadGestures->removeGesture(fingerCount, direction, modMask, deltaScale);
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 else
return std::format("Invalid gesture: {}", data[startDataIdx]); return std::format("Invalid gesture: {}", data[startDataIdx]);
@ -3096,7 +2989,7 @@ bool CConfigManager::shouldUseSoftwareCursors(PHLMONITOR pMonitor) {
switch (*PNOHW) { switch (*PNOHW) {
case 0: return false; case 0: return false;
case 1: return true; case 1: return true;
case 2: return g_pHyprRenderer->isNvidia() && (g_pHyprRenderer->isMgpu() || g_pCompositor->isVRRActiveOnAnyMonitor()); case 2: return g_pHyprRenderer->isNvidia() && g_pHyprRenderer->isMgpu();
default: break; default: break;
} }
@ -3123,20 +3016,20 @@ std::string SConfigOptionDescription::jsonify() const {
else if (typeid(Hyprlang::FLOAT) == std::type_index(CONFIGVALUE.type())) else if (typeid(Hyprlang::FLOAT) == std::type_index(CONFIGVALUE.type()))
currentValue = std::format("{:.2f}", std::any_cast<Hyprlang::FLOAT>(CONFIGVALUE)); currentValue = std::format("{:.2f}", std::any_cast<Hyprlang::FLOAT>(CONFIGVALUE));
else if (typeid(Hyprlang::STRING) == std::type_index(CONFIGVALUE.type())) else if (typeid(Hyprlang::STRING) == std::type_index(CONFIGVALUE.type()))
currentValue = std::format("\"{}\"", std::any_cast<Hyprlang::STRING>(CONFIGVALUE)); currentValue = std::any_cast<Hyprlang::STRING>(CONFIGVALUE);
else if (typeid(Hyprlang::VEC2) == std::type_index(CONFIGVALUE.type())) { else if (typeid(Hyprlang::VEC2) == std::type_index(CONFIGVALUE.type())) {
const auto V = std::any_cast<Hyprlang::VEC2>(CONFIGVALUE); 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())) { } else if (typeid(void*) == std::type_index(CONFIGVALUE.type())) {
const auto DATA = sc<ICustomConfigValueData*>(std::any_cast<void*>(CONFIGVALUE)); const auto DATA = sc<ICustomConfigValueData*>(std::any_cast<void*>(CONFIGVALUE));
currentValue = std::format("\"{}\"", DATA->toString()); currentValue = DATA->toString();
} }
try { try {
using T = std::decay_t<decltype(val)>; using T = std::decay_t<decltype(val)>;
if constexpr (std::is_same_v<T, SStringData>) { if constexpr (std::is_same_v<T, SStringData>) {
return std::format(R"#( "value": "{}", return std::format(R"#( "value": "{}",
"current": {}, "current": "{}",
"explicit": {})#", "explicit": {})#",
val.value, currentValue, EXPLICIT); val.value, currentValue, EXPLICIT);
} else if constexpr (std::is_same_v<T, SRangeData>) { } else if constexpr (std::is_same_v<T, SRangeData>) {
@ -3155,7 +3048,7 @@ std::string SConfigOptionDescription::jsonify() const {
val.value, val.min, val.max, currentValue, EXPLICIT); val.value, val.min, val.max, currentValue, EXPLICIT);
} else if constexpr (std::is_same_v<T, SColorData>) { } else if constexpr (std::is_same_v<T, SColorData>) {
return std::format(R"#( "value": "{}", return std::format(R"#( "value": "{}",
"current": {}, "current": "{}",
"explicit": {})#", "explicit": {})#",
val.color.getAsHex(), currentValue, EXPLICIT); val.color.getAsHex(), currentValue, EXPLICIT);
} else if constexpr (std::is_same_v<T, SBoolData>) { } else if constexpr (std::is_same_v<T, SBoolData>) {
@ -3176,12 +3069,12 @@ std::string SConfigOptionDescription::jsonify() const {
"min_y": {}, "min_y": {},
"max_x": {}, "max_x": {},
"max_y": {}, "max_y": {},
"current": {}, "current": "{}",
"explicit": {})#", "explicit": {})#",
val.vec.x, val.vec.y, val.min.x, val.min.y, val.max.x, val.max.y, currentValue, 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>) { } else if constexpr (std::is_same_v<T, SGradientData>) {
return std::format(R"#( "value": "{}", return std::format(R"#( "value": "{}",
"current": {}, "current": "{}",
"explicit": {})#", "explicit": {})#",
val.gradient, currentValue, EXPLICIT); val.gradient, currentValue, EXPLICIT);
} }

View file

@ -46,7 +46,6 @@ struct SWorkspaceRule {
std::optional<bool> noShadow; std::optional<bool> noShadow;
std::optional<std::string> onCreatedEmptyRunCmd; std::optional<std::string> onCreatedEmptyRunCmd;
std::optional<std::string> defaultName; std::optional<std::string> defaultName;
std::optional<std::string> layout;
std::map<std::string, std::string> layoutopts; std::map<std::string, std::string> layoutopts;
}; };
@ -177,7 +176,6 @@ class CMonitorRuleParser {
bool parseSDRBrightness(const std::string& value); bool parseSDRBrightness(const std::string& value);
bool parseSDRSaturation(const std::string& value); bool parseSDRSaturation(const std::string& value);
bool parseVRR(const std::string& value); bool parseVRR(const std::string& value);
bool parseICC(const std::string& value);
void setDisabled(); void setDisabled();
void setMirror(const std::string& value); void setMirror(const std::string& value);

View file

@ -44,7 +44,6 @@ using namespace Hyprutils::OS;
#include "config/ConfigManager.hpp" #include "config/ConfigManager.hpp"
#include "helpers/MiscFunctions.hpp" #include "helpers/MiscFunctions.hpp"
#include "../desktop/view/LayerSurface.hpp" #include "../desktop/view/LayerSurface.hpp"
#include "../desktop/view/Group.hpp"
#include "../desktop/rule/Engine.hpp" #include "../desktop/rule/Engine.hpp"
#include "../desktop/history/WindowHistoryTracker.hpp" #include "../desktop/history/WindowHistoryTracker.hpp"
#include "../desktop/state/FocusState.hpp" #include "../desktop/state/FocusState.hpp"
@ -53,15 +52,12 @@ using namespace Hyprutils::OS;
#include "../Compositor.hpp" #include "../Compositor.hpp"
#include "../managers/input/InputManager.hpp" #include "../managers/input/InputManager.hpp"
#include "../managers/XWaylandManager.hpp" #include "../managers/XWaylandManager.hpp"
#include "../managers/LayoutManager.hpp"
#include "../plugins/PluginSystem.hpp" #include "../plugins/PluginSystem.hpp"
#include "../managers/animation/AnimationManager.hpp" #include "../managers/animation/AnimationManager.hpp"
#include "../debug/HyprNotificationOverlay.hpp" #include "../debug/HyprNotificationOverlay.hpp"
#include "../render/Renderer.hpp" #include "../render/Renderer.hpp"
#include "../render/OpenGL.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__) #if defined(__DragonFly__) || defined(__FreeBSD__)
#include <sys/ucred.h> #include <sys/ucred.h>
@ -149,12 +145,12 @@ std::string CHyprCtl::getSolitaryBlockedReason(Hyprutils::Memory::CSharedPointer
const std::array<const char*, CMonitor::DS_CHECKS_COUNT> DS_REASONS_JSON = { const std::array<const char*, CMonitor::DS_CHECKS_COUNT> DS_REASONS_JSON = {
"\"UNKNOWN\"", "\"USER\"", "\"WINDOWED\"", "\"CONTENT\"", "\"MIRROR\"", "\"RECORD\"", "\"SW\"", "\"UNKNOWN\"", "\"USER\"", "\"WINDOWED\"", "\"CONTENT\"", "\"MIRROR\"", "\"RECORD\"", "\"SW\"",
"\"CANDIDATE\"", "\"SURFACE\"", "\"TRANSFORM\"", "\"DMA\"", "\"FAILED\"", "\"CM\"", "\"CANDIDATE\"", "\"SURFACE\"", "\"TRANSFORM\"", "\"DMA\"", "\"TEARING\"", "\"FAILED\"", "\"CM\"",
}; };
const std::array<const char*, CMonitor::DS_CHECKS_COUNT> DS_REASONS_TEXT = { 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", "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", "missing candidate", "invalid surface", "surface transformations", "invalid buffer", "tearing", "activation failed", "color management",
}; };
std::string CHyprCtl::getDSBlockedReason(Hyprutils::Memory::CSharedPointer<CMonitor> m, eHyprCtlOutputFormat format) { std::string CHyprCtl::getDSBlockedReason(Hyprutils::Memory::CSharedPointer<CMonitor> m, eHyprCtlOutputFormat format) {
@ -177,11 +173,12 @@ std::string CHyprCtl::getDSBlockedReason(Hyprutils::Memory::CSharedPointer<CMoni
} }
const std::array<const char*, CMonitor::TC_CHECKS_COUNT> TEARING_REASONS_JSON = { const std::array<const char*, CMonitor::TC_CHECKS_COUNT> TEARING_REASONS_JSON = {
"\"UNKNOWN\"", "\"NOT_TORN\"", "\"USER\"", "\"ZOOM\"", "\"SUPPORT\"", "\"CANDIDATE\"", "\"WINDOW\"", "\"HW_CURSOR\"", "\"UNKNOWN\"", "\"NOT_TORN\"", "\"USER\"", "\"ZOOM\"", "\"SUPPORT\"", "\"CANDIDATE\"", "\"WINDOW\"",
}; };
const std::array<const char*, CMonitor::TC_CHECKS_COUNT> TEARING_REASONS_TEXT = {"unknown reason", "next frame is not torn", "user settings", "zoom", const std::array<const char*, CMonitor::TC_CHECKS_COUNT> TEARING_REASONS_TEXT = {
"not supported by monitor", "missing candidate", "window settings", "hw cursor"}; "unknown reason", "next frame is not torn", "user settings", "zoom", "not supported by monitor", "missing candidate", "window settings",
};
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); const auto reasons = m->isTearingBlocked(true);
@ -334,18 +331,22 @@ static std::string getTagsData(PHLWINDOW w, eHyprCtlOutputFormat format) {
static std::string getGroupedData(PHLWINDOW w, eHyprCtlOutputFormat format) { static std::string getGroupedData(PHLWINDOW w, eHyprCtlOutputFormat format) {
const bool isJson = format == eHyprCtlOutputFormat::FORMAT_JSON; const bool isJson = format == eHyprCtlOutputFormat::FORMAT_JSON;
if (!w->m_group) if (w->m_groupData.pNextWindow.expired())
return isJson ? "" : "0"; return isJson ? "" : "0";
std::ostringstream result; std::ostringstream result;
for (const auto& curr : w->m_group->windows()) { PHLWINDOW head = w->getGroupHead();
PHLWINDOW curr = head;
while (true) {
if (isJson) if (isJson)
result << std::format("\"0x{:x}\"", rc<uintptr_t>(curr.get())); result << std::format("\"0x{:x}\"", rc<uintptr_t>(curr.get()));
else else
result << std::format("{:x}", rc<uintptr_t>(curr.get())); result << std::format("{:x}", rc<uintptr_t>(curr.get()));
curr = curr->m_groupData.pNextWindow.lock();
if (curr != w->m_group->windows().back()) // We've wrapped around to the start, break out without trailing comma
if (curr == head)
break;
result << (isJson ? ", " : ","); result << (isJson ? ", " : ",");
} }
@ -375,6 +376,7 @@ std::string CHyprCtl::getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format) {
"name": "{}" "name": "{}"
}}, }},
"floating": {}, "floating": {},
"pseudo": {},
"monitor": {}, "monitor": {},
"class": "{}", "class": "{}",
"title": "{}", "title": "{}",
@ -385,7 +387,6 @@ std::string CHyprCtl::getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format) {
"pinned": {}, "pinned": {},
"fullscreen": {}, "fullscreen": {},
"fullscreenClient": {}, "fullscreenClient": {},
"overFullscreen": {},
"grouped": [{}], "grouped": [{}],
"tags": [{}], "tags": [{}],
"swallowing": "0x{:x}", "swallowing": "0x{:x}",
@ -393,31 +394,29 @@ std::string CHyprCtl::getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format) {
"inhibitingIdle": {}, "inhibitingIdle": {},
"xdgTag": "{}", "xdgTag": "{}",
"xdgDescription": "{}", "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), 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, 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->monitorID(), escapeJSONStrings(w->m_class), escapeJSONStrings(!w->m_workspace ? "" : w->m_workspace->m_name), (sc<int>(w->m_isFloating) == 1 ? "true" : "false"), (w->m_isPseudotiled ? "true" : "false"),
escapeJSONStrings(w->m_title), escapeJSONStrings(w->m_initialClass), escapeJSONStrings(w->m_initialTitle), w->getPID(), (sc<int>(w->m_isX11) == 1 ? "true" : "false"), w->monitorID(), escapeJSONStrings(w->m_class), escapeJSONStrings(w->m_title), escapeJSONStrings(w->m_initialClass), escapeJSONStrings(w->m_initialTitle), w->getPID(),
(w->m_pinned ? "true" : "false"), sc<uint8_t>(w->m_fullscreenState.internal), sc<uint8_t>(w->m_fullscreenState.client), (w->m_createdOverFullscreen ? "true" : "false"), (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),
getGroupedData(w, format), getTagsData(w, format), rc<uintptr_t>(w->m_swallowed.get()), getFocusHistoryID(w), 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("")), (g_pInputManager->isWindowInhibiting(w, false) ? "true" : "false"), escapeJSONStrings(w->xdgTag().value_or("")), escapeJSONStrings(w->xdgDescription().value_or("")),
escapeJSONStrings(NContentType::toString(w->getContentType())), w->m_stableID); escapeJSONStrings(NContentType::toString(w->getContentType())));
} else { } else {
return std::format( return std::format(
"Window {:x} -> {}:\n\tmapped: {}\n\thidden: {}\n\tat: {},{}\n\tsize: {},{}\n\tworkspace: {} ({})\n\tfloating: {}\n\tmonitor: {}\n\tclass: {}\n\ttitle: " "Window {:x} -> {}:\n\tmapped: {}\n\thidden: {}\n\tat: {},{}\n\tsize: {},{}\n\tworkspace: {} ({})\n\tfloating: {}\n\tpseudo: {}\n\tmonitor: {}\n\tclass: {}\n\ttitle: "
"{}\n\tinitialClass: {}\n\tinitialTitle: {}\n\tpid: " "{}\n\tinitialClass: {}\n\tinitialTitle: {}\n\tpid: "
"{}\n\txwayland: {}\n\tpinned: " "{}\n\txwayland: {}\n\tpinned: "
"{}\n\tfullscreen: {}\n\tfullscreenClient: {}\n\toverFullscreen: {}\n\tgrouped: {}\n\ttags: {}\n\tswallowing: {:x}\n\tfocusHistoryID: {}\n\tinhibitingIdle: " "{}\n\tfullscreen: {}\n\tfullscreenClient: {}\n\tgrouped: {}\n\ttags: {}\n\tswallowing: {:x}\n\tfocusHistoryID: {}\n\tinhibitingIdle: {}\n\txdgTag: "
"{}\n\txdgTag: " "{}\n\txdgDescription: {}\n\tcontentType: {}\n\n",
"{}\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), 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, 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), w->monitorID(), w->m_class, w->m_title, w->m_initialClass, w->m_initialTitle, w->getPID(), (!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,
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), 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),
getGroupedData(w, format), getTagsData(w, format), rc<uintptr_t>(w->m_swallowed.get()), getFocusHistoryID(w), sc<int>(g_pInputManager->isWindowInhibiting(w, false)), 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->m_stableID); w->xdgTag().value_or(""), w->xdgDescription().value_or(""), NContentType::toString(w->getContentType()));
} }
} }
@ -453,13 +452,6 @@ static std::string clientsRequest(eHyprCtlOutputFormat format, std::string reque
std::string CHyprCtl::getWorkspaceData(PHLWORKSPACE w, eHyprCtlOutputFormat format) { std::string CHyprCtl::getWorkspaceData(PHLWORKSPACE w, eHyprCtlOutputFormat format) {
const auto PLASTW = w->getLastFocusedWindow(); const auto PLASTW = w->getLastFocusedWindow();
const auto PMONITOR = w->m_monitor.lock(); 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) { if (format == eHyprCtlOutputFormat::FORMAT_JSON) {
return std::format(R"#({{ return std::format(R"#({{
"id": {}, "id": {},
@ -470,17 +462,16 @@ std::string CHyprCtl::getWorkspaceData(PHLWORKSPACE w, eHyprCtlOutputFormat form
"hasfullscreen": {}, "hasfullscreen": {},
"lastwindow": "0x{:x}", "lastwindow": "0x{:x}",
"lastwindowtitle": "{}", "lastwindowtitle": "{}",
"ispersistent": {}, "ispersistent": {}
"tiledLayout": "{}"
}})#", }})#",
w->m_id, escapeJSONStrings(w->m_name), escapeJSONStrings(PMONITOR ? PMONITOR->m_name : "?"), 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", 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", escapeJSONStrings(layoutName)); rc<uintptr_t>(PLASTW.get()), PLASTW ? escapeJSONStrings(PLASTW->m_title) : "", w->isPersistent() ? "true" : "false");
} else { } else {
return std::format("workspace ID {} ({}) on monitor {}:\n\tmonitorID: {}\n\twindows: {}\n\thasfullscreen: {}\n\tlastwindow: 0x{:x}\n\tlastwindowtitle: {}\n\tispersistent: " return std::format(
"{}\n\ttiledLayout: {}\n\n", "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(), 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),
sc<int>(w->m_hasFullscreenWindow), rc<uintptr_t>(PLASTW.get()), PLASTW ? PLASTW->m_title : "", sc<int>(w->isPersistent()), layoutName); rc<uintptr_t>(PLASTW.get()), PLASTW ? PLASTW->m_title : "", sc<int>(w->isPersistent()));
} }
} }
@ -679,6 +670,28 @@ static std::string layersRequest(eHyprCtlOutputFormat format, std::string reques
return result; 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) { static std::string configErrorsRequest(eHyprCtlOutputFormat format, std::string request) {
std::string result = ""; std::string result = "";
std::string currErrors = g_pConfigManager->getErrors(); std::string currErrors = g_pConfigManager->getErrors();
@ -789,7 +802,7 @@ static std::string devicesRequest(eHyprCtlOutputFormat format, std::string reque
result += std::format( result += std::format(
R"#( {{ R"#( {{
"address": "0x{:x}", "address": "0x{:x}",
"type": "tabletTool" "type": "tabletTool",
}},)#", }},)#",
rc<uintptr_t>(d.get())); rc<uintptr_t>(d.get()));
} }
@ -1281,13 +1294,8 @@ static std::string dispatchKeyword(eHyprCtlOutputFormat format, std::string in)
g_pHyprCtl->m_currentRequestParams.isDynamicKeyword = false; 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 we are executing a dynamic source we have to reload everything, so every if will have a check for source.
if (COMMAND == "monitor") if (COMMAND == "monitor" || COMMAND == "source")
g_pConfigManager->m_wantsMonitorReload = true; // for monitor keywords g_pConfigManager->m_wantsMonitorReload = true; // for monitor keywords
if (COMMAND.contains("monitorv2")) if (COMMAND.contains("monitorv2"))
@ -1300,8 +1308,10 @@ static std::string dispatchKeyword(eHyprCtlOutputFormat format, std::string in)
g_pInputManager->setTabletConfigs(); // update tablets g_pInputManager->setTabletConfigs(); // update tablets
} }
if (COMMAND.contains("general:layout") || (COMMAND.contains("workspace") && VALUE.contains("layout:"))) static auto PLAYOUT = CConfigValue<std::string>("general:layout");
Layout::Supplementary::algoMatcher()->updateWorkspaceLayouts();
if (COMMAND.contains("general:layout"))
g_pLayoutManager->switchToLayout(*PLAYOUT); // update layout
if (COMMAND.contains("decoration:screen_shader") || COMMAND == "source") if (COMMAND.contains("decoration:screen_shader") || COMMAND == "source")
g_pHyprOpenGL->m_reloadScreenShader = true; g_pHyprOpenGL->m_reloadScreenShader = true;
@ -1321,22 +1331,13 @@ static std::string dispatchKeyword(eHyprCtlOutputFormat format, std::string in)
for (auto const& m : g_pCompositor->m_monitors) { for (auto const& m : g_pCompositor->m_monitors) {
*(m->m_cursorZoom) = *PZOOMFACTOR; *(m->m_cursorZoom) = *PZOOMFACTOR;
g_pHyprRenderer->damageMonitor(m); g_pHyprRenderer->damageMonitor(m);
if (m->m_activeWorkspace) g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->m_id);
m->m_activeWorkspace->m_space->recalculate();
} }
} }
if (COMMAND.contains("windowrule ") || COMMAND.contains("windowrule[")) if (COMMAND.contains("windowrule ") || COMMAND.contains("windowrule["))
g_pConfigManager->reloadRules(); 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")) if (COMMAND.contains("workspace"))
g_pConfigManager->ensurePersistentWorkspacesPresent(); g_pConfigManager->ensurePersistentWorkspacesPresent();
@ -1596,7 +1597,7 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ
static auto PGROUPACTIVELOCKEDCOL = CConfigValue<Hyprlang::CUSTOMTYPE>("group:col.border_locked_active"); static auto PGROUPACTIVELOCKEDCOL = CConfigValue<Hyprlang::CUSTOMTYPE>("group:col.border_locked_active");
static auto PGROUPINACTIVELOCKEDCOL = CConfigValue<Hyprlang::CUSTOMTYPE>("group:col.border_locked_inactive"); static auto PGROUPINACTIVELOCKEDCOL = CConfigValue<Hyprlang::CUSTOMTYPE>("group:col.border_locked_inactive");
const bool GROUPLOCKED = PWINDOW->m_group ? PWINDOW->m_group->locked() : false; const bool GROUPLOCKED = PWINDOW->m_groupData.pNextWindow.lock() ? PWINDOW->getGroupHead()->m_groupData.locked : false;
if (active) { if (active) {
auto* const ACTIVECOL = (CGradientValueData*)(PACTIVECOL.ptr())->getData(); auto* const ACTIVECOL = (CGradientValueData*)(PACTIVECOL.ptr())->getData();
@ -1604,7 +1605,7 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ
auto* const GROUPACTIVECOL = (CGradientValueData*)(PGROUPACTIVECOL.ptr())->getData(); auto* const GROUPACTIVECOL = (CGradientValueData*)(PGROUPACTIVECOL.ptr())->getData();
auto* const GROUPACTIVELOCKEDCOL = (CGradientValueData*)(PGROUPACTIVELOCKEDCOL.ptr())->getData(); auto* const GROUPACTIVELOCKEDCOL = (CGradientValueData*)(PGROUPACTIVELOCKEDCOL.ptr())->getData();
const auto* const ACTIVECOLOR = const auto* const ACTIVECOLOR =
!PWINDOW->m_group ? (!(PWINDOW->m_groupRules & Desktop::View::GROUP_DENY) ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL); !PWINDOW->m_groupData.pNextWindow.lock() ? (!PWINDOW->m_groupData.deny ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL);
std::string borderColorString = PWINDOW->m_ruleApplicator->activeBorderColor().valueOr(*ACTIVECOLOR).toString(); std::string borderColorString = PWINDOW->m_ruleApplicator->activeBorderColor().valueOr(*ACTIVECOLOR).toString();
if (FORMNORM) if (FORMNORM)
@ -1616,7 +1617,7 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ
auto* const NOGROUPINACTIVECOL = (CGradientValueData*)(PNOGROUPINACTIVECOL.ptr())->getData(); auto* const NOGROUPINACTIVECOL = (CGradientValueData*)(PNOGROUPINACTIVECOL.ptr())->getData();
auto* const GROUPINACTIVECOL = (CGradientValueData*)(PGROUPINACTIVECOL.ptr())->getData(); auto* const GROUPINACTIVECOL = (CGradientValueData*)(PGROUPINACTIVECOL.ptr())->getData();
auto* const GROUPINACTIVELOCKEDCOL = (CGradientValueData*)(PGROUPINACTIVELOCKEDCOL.ptr())->getData(); auto* const GROUPINACTIVELOCKEDCOL = (CGradientValueData*)(PGROUPINACTIVELOCKEDCOL.ptr())->getData();
const auto* const INACTIVECOLOR = !PWINDOW->m_group ? (!(PWINDOW->m_groupRules & Desktop::View::GROUP_DENY) ? INACTIVECOL : NOGROUPINACTIVECOL) : const auto* const INACTIVECOLOR = !PWINDOW->m_groupData.pNextWindow.lock() ? (!PWINDOW->m_groupData.deny ? INACTIVECOL : NOGROUPINACTIVECOL) :
(GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL); (GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL);
std::string borderColorString = PWINDOW->m_ruleApplicator->inactiveBorderColor().valueOr(*INACTIVECOLOR).toString(); std::string borderColorString = PWINDOW->m_ruleApplicator->inactiveBorderColor().valueOr(*INACTIVECOLOR).toString();
@ -2049,12 +2050,7 @@ static std::string submapRequest(eHyprCtlOutputFormat format, std::string reques
} }
static std::string reloadShaders(eHyprCtlOutputFormat format, std::string request) { static std::string reloadShaders(eHyprCtlOutputFormat format, std::string request) {
CVarList vars(request, 0, ' '); if (g_pHyprOpenGL->initShaders())
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"; return format == FORMAT_JSON ? "{\"ok\": true}" : "ok";
else else
return format == FORMAT_JSON ? "{\"ok\": false}" : "error"; return format == FORMAT_JSON ? "{\"ok\": false}" : "error";
@ -2077,12 +2073,13 @@ CHyprCtl::CHyprCtl() {
registerCommand(SHyprCtlCommand{"systeminfo", true, systemInfoRequest}); registerCommand(SHyprCtlCommand{"systeminfo", true, systemInfoRequest});
registerCommand(SHyprCtlCommand{"animations", true, animationsRequest}); registerCommand(SHyprCtlCommand{"animations", true, animationsRequest});
registerCommand(SHyprCtlCommand{"rollinglog", true, rollinglogRequest}); registerCommand(SHyprCtlCommand{"rollinglog", true, rollinglogRequest});
registerCommand(SHyprCtlCommand{"layouts", true, layoutsRequest});
registerCommand(SHyprCtlCommand{"configerrors", true, configErrorsRequest}); registerCommand(SHyprCtlCommand{"configerrors", true, configErrorsRequest});
registerCommand(SHyprCtlCommand{"locked", true, getIsLocked}); registerCommand(SHyprCtlCommand{"locked", true, getIsLocked});
registerCommand(SHyprCtlCommand{"descriptions", true, getDescriptions}); registerCommand(SHyprCtlCommand{"descriptions", true, getDescriptions});
registerCommand(SHyprCtlCommand{"submap", true, submapRequest}); 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{"monitors", false, monitorsRequest});
registerCommand(SHyprCtlCommand{"reload", false, reloadRequest}); registerCommand(SHyprCtlCommand{"reload", false, reloadRequest});
registerCommand(SHyprCtlCommand{"plugin", false, dispatchPlugin}); registerCommand(SHyprCtlCommand{"plugin", false, dispatchPlugin});
@ -2189,6 +2186,10 @@ std::string CHyprCtl::getReply(std::string request) {
g_pInputManager->setTouchDeviceConfigs(); // update touch device cfgs g_pInputManager->setTouchDeviceConfigs(); // update touch device cfgs
g_pInputManager->setTabletConfigs(); // update tablets g_pInputManager->setTabletConfigs(); // update tablets
static auto PLAYOUT = CConfigValue<std::string>("general:layout");
g_pLayoutManager->switchToLayout(*PLAYOUT); // update layout
g_pHyprOpenGL->m_reloadScreenShader = true; g_pHyprOpenGL->m_reloadScreenShader = true;
for (auto& [m, rd] : g_pHyprOpenGL->m_monitorRenderResources) { for (auto& [m, rd] : g_pHyprOpenGL->m_monitorRenderResources) {
@ -2202,17 +2203,9 @@ std::string CHyprCtl::getReply(std::string request) {
Desktop::Rule::ruleEngine()->updateAllRules(); 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) { for (auto const& m : g_pCompositor->m_monitors) {
g_pHyprRenderer->damageMonitor(m); g_pHyprRenderer->damageMonitor(m);
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->m_id);
} }
} }
@ -2224,38 +2217,16 @@ std::string CHyprCtl::makeDynamicCall(const std::string& input) {
} }
static bool successWrite(int fd, const std::string& data, bool needLog = true) { static bool successWrite(int fd, const std::string& data, bool needLog = true) {
size_t totalWritten = 0; if (write(fd, data.c_str(), data.length()) > 0)
size_t remaining = data.length();
size_t waitsDone = 0;
constexpr const size_t MAX_WAITS = 20; // 2000µs = 2ms
while (totalWritten < data.length()) {
ssize_t written = write(fd, data.c_str() + totalWritten, remaining);
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;
}
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; return true;
if (errno == EAGAIN)
return true;
if (needLog)
Log::logger->log(Log::ERR, "Couldn't write to socket. Error: " + std::string(strerror(errno)));
return false;
} }
static void runWritingDebugLogThread(const int conn) { static void runWritingDebugLogThread(const int conn) {

View file

@ -8,7 +8,7 @@
#include "../desktop/state/FocusState.hpp" #include "../desktop/state/FocusState.hpp"
CHyprDebugOverlay::CHyprDebugOverlay() { CHyprDebugOverlay::CHyprDebugOverlay() {
m_texture = g_pHyprRenderer->createTexture(); m_texture = makeShared<CTexture>();
} }
void CHyprMonitorDebugOverlay::renderData(PHLMONITOR pMonitor, float durationUs) { void CHyprMonitorDebugOverlay::renderData(PHLMONITOR pMonitor, float durationUs) {
@ -259,7 +259,15 @@ void CHyprDebugOverlay::draw() {
cairo_surface_flush(m_cairoSurface); cairo_surface_flush(m_cairoSurface);
// copy the data to an OpenGL texture we have // copy the data to an OpenGL texture we have
m_texture = g_pHyprRenderer->createTexture(m_cairoSurface); 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);
CTexPassElement::SRenderData data; CTexPassElement::SRenderData data;
data.tex = m_texture; data.tex = m_texture;

View file

@ -42,7 +42,7 @@ class CHyprDebugOverlay {
cairo_surface_t* m_cairoSurface = nullptr; cairo_surface_t* m_cairoSurface = nullptr;
cairo_t* m_cairo = nullptr; cairo_t* m_cairo = nullptr;
SP<ITexture> m_texture; SP<CTexture> m_texture;
friend class CHyprMonitorDebugOverlay; friend class CHyprMonitorDebugOverlay;
friend class CHyprRenderer; friend class CHyprRenderer;

View file

@ -4,9 +4,9 @@
#include "../Compositor.hpp" #include "../Compositor.hpp"
#include "../config/ConfigValue.hpp" #include "../config/ConfigValue.hpp"
#include "../render/pass/TexPassElement.hpp" #include "../render/pass/TexPassElement.hpp"
#include "../event/EventBus.hpp"
#include "../managers/animation/AnimationManager.hpp" #include "../managers/animation/AnimationManager.hpp"
#include "../managers/HookSystemManager.hpp"
#include "../render/Renderer.hpp" #include "../render/Renderer.hpp"
static inline auto iconBackendFromLayout(PangoLayout* layout) { static inline auto iconBackendFromLayout(PangoLayout* layout) {
@ -22,12 +22,14 @@ static inline auto iconBackendFromLayout(PangoLayout* layout) {
} }
CHyprNotificationOverlay::CHyprNotificationOverlay() { CHyprNotificationOverlay::CHyprNotificationOverlay() {
static auto P = Event::bus()->m_events.monitor.focused.listen([&](PHLMONITOR mon) { static auto P = g_pHookSystem->hookDynamic("focusedMon", [&](void* self, SCallbackInfo& info, std::any param) {
if (m_notifications.empty()) if (m_notifications.empty())
return; return;
g_pHyprRenderer->damageBox(m_lastDamage); g_pHyprRenderer->damageBox(m_lastDamage);
}); });
m_texture = makeShared<CTexture>();
} }
CHyprNotificationOverlay::~CHyprNotificationOverlay() { CHyprNotificationOverlay::~CHyprNotificationOverlay() {
@ -230,7 +232,16 @@ void CHyprNotificationOverlay::draw(PHLMONITOR pMonitor) {
m_lastDamage = damage; m_lastDamage = damage;
m_texture = g_pHyprRenderer->createTexture(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, MONSIZE.x, MONSIZE.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA);
CTexPassElement::SRenderData data; CTexPassElement::SRenderData data;
data.tex = m_texture; data.tex = m_texture;

View file

@ -18,7 +18,7 @@ enum eIconBackend : uint8_t {
static const std::array<std::array<std::string, ICON_NONE + 1>, 3 /* backends */> ICONS_ARRAY = { 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>{"[!]", "[i]", "[Hint]", "[Err]", "[?]", "[ok]", ""},
std::array<std::string, ICON_NONE + 1>{"", "", "", "", "", "󰸞", ""}, std::array<std::string, ICON_NONE + 1>{"", "", "", "", "", ""}}; 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{1.0, 204 / 255.0, 102 / 255.0, 1.0}, static const std::array<CHyprColor, ICON_NONE + 1> ICONS_COLORS = {CHyprColor{255.0 / 255.0, 204 / 255.0, 102 / 255.0, 1.0},
CHyprColor{128 / 255.0, 255 / 255.0, 255 / 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{179 / 255.0, 255 / 255.0, 204 / 255.0, 1.0},
CHyprColor{255 / 255.0, 77 / 255.0, 77 / 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; PHLMONITORREF m_lastMonitor;
Vector2D m_lastSize = Vector2D(-1, -1); Vector2D m_lastSize = Vector2D(-1, -1);
SP<ITexture> m_texture; SP<CTexture> m_texture;
}; };
inline UP<CHyprNotificationOverlay> g_pHyprNotificationOverlay; inline UP<CHyprNotificationOverlay> g_pHyprNotificationOverlay;

View file

@ -1,8 +1,9 @@
#include "Logger.hpp" #include "Logger.hpp"
#include "RollingLogFollow.hpp" #include "RollingLogFollow.hpp"
#include "../../event/EventBus.hpp" #include "../../defines.hpp"
#include "../../managers/HookSystemManager.hpp"
#include "../../config/ConfigValue.hpp" #include "../../config/ConfigValue.hpp"
using namespace Log; using namespace Log;
@ -38,7 +39,7 @@ void CLogger::initIS(const std::string_view& IS) {
} }
void CLogger::initCallbacks() { void CLogger::initCallbacks() {
static auto P = Event::bus()->m_events.config.reloaded.listen([this]() { recheckCfg(); }); static auto P = g_pHookSystem->hookDynamic("configReloaded", [this](void* hk, SCallbackInfo& info, std::any param) { recheckCfg(); });
recheckCfg(); recheckCfg();
} }

View file

@ -5,7 +5,3 @@
#include "helpers/Color.hpp" #include "helpers/Color.hpp"
#include "macros.hpp" #include "macros.hpp"
#include "desktop/DesktopTypes.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

View file

@ -1,13 +1,10 @@
#include "Workspace.hpp" #include "Workspace.hpp"
#include "view/Group.hpp"
#include "../Compositor.hpp" #include "../Compositor.hpp"
#include "../config/ConfigValue.hpp" #include "../config/ConfigValue.hpp"
#include "config/ConfigManager.hpp" #include "config/ConfigManager.hpp"
#include "managers/animation/AnimationManager.hpp" #include "managers/animation/AnimationManager.hpp"
#include "../managers/EventManager.hpp" #include "../managers/EventManager.hpp"
#include "../layout/space/Space.hpp" #include "../managers/HookSystemManager.hpp"
#include "../layout/supplementary/WorkspaceAlgoMatcher.hpp"
#include "../event/EventBus.hpp"
#include <hyprutils/animation/AnimatedVariable.hpp> #include <hyprutils/animation/AnimatedVariable.hpp>
#include <hyprutils/string/String.hpp> #include <hyprutils/string/String.hpp>
@ -37,14 +34,13 @@ void CWorkspace::init(PHLWORKSPACE self) {
if (RULEFORTHIS.defaultName.has_value()) if (RULEFORTHIS.defaultName.has_value())
m_name = RULEFORTHIS.defaultName.value(); m_name = RULEFORTHIS.defaultName.value();
m_focusedWindowHook = Event::bus()->m_events.window.close.listen([this](PHLWINDOW pWindow) { m_focusedWindowHook = g_pHookSystem->hookDynamic("closeWindow", [this](void* self, SCallbackInfo& info, std::any param) {
if (pWindow == m_lastFocusedWindow.lock()) const auto PWINDOW = std::any_cast<PHLWINDOW>(param);
if (PWINDOW == m_lastFocusedWindow.lock())
m_lastFocusedWindow.reset(); m_lastFocusedWindow.reset();
}); });
m_space = Layout::CSpace::create(m_self.lock());
m_space->setAlgorithmProvider(Layout::Supplementary::algoMatcher()->createAlgorithmForWorkspace(m_self.lock()));
m_inert = false; m_inert = false;
const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(self); const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(self);
@ -56,19 +52,22 @@ void CWorkspace::init(PHLWORKSPACE self) {
g_pEventManager->postEvent({.event = "createworkspace", .data = m_name}); g_pEventManager->postEvent({.event = "createworkspace", .data = m_name});
g_pEventManager->postEvent({.event = "createworkspacev2", .data = std::format("{},{}", m_id, m_name)}); g_pEventManager->postEvent({.event = "createworkspacev2", .data = std::format("{},{}", m_id, m_name)});
Event::bus()->m_events.workspace.created.emit(self); EMIT_HOOK_EVENT("createWorkspace", this);
} }
CWorkspace::~CWorkspace() { CWorkspace::~CWorkspace() {
Log::logger->log(Log::DEBUG, "Destroying workspace ID {}", m_id); 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) { if (g_pEventManager) {
g_pEventManager->postEvent({.event = "destroyworkspace", .data = m_name}); g_pEventManager->postEvent({.event = "destroyworkspace", .data = m_name});
g_pEventManager->postEvent({.event = "destroyworkspacev2", .data = std::format("{},{}", m_id, 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(); m_events.destroy.emit();
} }
@ -407,19 +406,14 @@ bool CWorkspace::isVisibleNotCovered() {
int CWorkspace::getWindows(std::optional<bool> onlyTiled, std::optional<bool> onlyPinned, std::optional<bool> onlyVisible) { int CWorkspace::getWindows(std::optional<bool> onlyTiled, std::optional<bool> onlyPinned, std::optional<bool> onlyVisible) {
int no = 0; int no = 0;
for (auto const& w : g_pCompositor->m_windows) {
if (!m_space) if (w->workspaceID() != m_id || !w->m_isMapped)
return 0;
for (auto const& t : m_space->targets()) {
if (!t)
continue; continue;
if (onlyTiled.has_value() && w->m_isFloating == onlyTiled.value())
if (onlyTiled.has_value() && t->floating() == onlyTiled.value())
continue; continue;
if (onlyPinned.has_value() && (!t->window() || t->window()->m_pinned != onlyPinned.value())) if (onlyPinned.has_value() && w->m_pinned != onlyPinned.value())
continue; continue;
if (onlyVisible.has_value() && (!t->window() || t->window()->isHidden() == onlyVisible.value())) if (onlyVisible.has_value() && w->isHidden() == onlyVisible.value())
continue; continue;
no++; no++;
} }
@ -429,16 +423,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 CWorkspace::getGroups(std::optional<bool> onlyTiled, std::optional<bool> onlyPinned, std::optional<bool> onlyVisible) {
int no = 0; int no = 0;
for (auto const& g : Desktop::View::groups()) { for (auto const& w : g_pCompositor->m_windows) {
const auto HEAD = g->head(); if (w->workspaceID() != m_id || !w->m_isMapped)
if (HEAD->workspaceID() != m_id || !HEAD->m_isMapped)
continue; continue;
if (onlyTiled.has_value() && HEAD->m_isFloating == onlyTiled.value()) if (!w->m_groupData.head)
continue; continue;
if (onlyPinned.has_value() && HEAD->m_pinned != onlyPinned.value()) if (onlyTiled.has_value() && w->m_isFloating == onlyTiled.value())
continue; continue;
if (onlyVisible.has_value() && g->current()->isHidden() == onlyVisible.value()) if (onlyPinned.has_value() && w->m_pinned != onlyPinned.value())
continue;
if (onlyVisible.has_value() && w->isHidden() == onlyVisible.value())
continue; continue;
no++; no++;
} }
@ -520,11 +514,13 @@ void CWorkspace::rename(const std::string& name) {
} }
void CWorkspace::updateWindows() { void CWorkspace::updateWindows() {
m_hasFullscreenWindow = std::ranges::any_of(m_space->targets(), [](const auto& t) { return t->fullscreenMode() != FSMODE_NONE; }); 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(); });
for (auto const& t : m_space->targets()) { for (auto const& w : g_pCompositor->m_windows) {
if (t->window()) if (!w->m_isMapped || w->m_workspace != m_self)
t->window()->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE); continue;
w->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE);
} }
} }

View file

@ -6,10 +6,6 @@
#include "../helpers/MiscFunctions.hpp" #include "../helpers/MiscFunctions.hpp"
#include "../helpers/signal/Signal.hpp" #include "../helpers/signal/Signal.hpp"
namespace Layout {
class CSpace;
};
enum eFullscreenMode : int8_t { enum eFullscreenMode : int8_t {
FSMODE_NONE = 0, FSMODE_NONE = 0,
FSMODE_MAXIMIZED = 1 << 0, FSMODE_MAXIMIZED = 1 << 0,
@ -26,8 +22,6 @@ class CWorkspace {
WP<CWorkspace> m_self; WP<CWorkspace> m_self;
SP<Layout::CSpace> m_space;
// Workspaces ID-based have IDs > 0 // Workspaces ID-based have IDs > 0
// and workspaces name-based have IDs starting with -1337 // and workspaces name-based have IDs starting with -1337
WORKSPACEID m_id = WORKSPACE_INVALID; WORKSPACEID m_id = WORKSPACE_INVALID;
@ -95,7 +89,7 @@ class CWorkspace {
private: private:
void init(PHLWORKSPACE self); void init(PHLWORKSPACE self);
CHyprSignalListener m_focusedWindowHook; SP<HOOK_CALLBACK_FN> m_focusedWindowHook;
bool m_inert = true; bool m_inert = true;
SP<CWorkspace> m_selfPersistent; // for persistent workspaces. SP<CWorkspace> m_selfPersistent; // for persistent workspaces.

View file

@ -1,7 +1,7 @@
#include "WindowHistoryTracker.hpp" #include "WindowHistoryTracker.hpp"
#include "../../managers/HookSystemManager.hpp"
#include "../view/Window.hpp" #include "../view/Window.hpp"
#include "../../event/EventBus.hpp"
using namespace Desktop; using namespace Desktop;
using namespace Desktop::History; using namespace Desktop::History;
@ -12,12 +12,18 @@ SP<CWindowHistoryTracker> History::windowTracker() {
} }
CWindowHistoryTracker::CWindowHistoryTracker() { CWindowHistoryTracker::CWindowHistoryTracker() {
static auto P = Event::bus()->m_events.window.openEarly.listen([this](PHLWINDOW pWindow) { static auto P = g_pHookSystem->hookDynamic("openWindowEarly", [this](void* self, SCallbackInfo& info, std::any data) {
auto window = std::any_cast<PHLWINDOW>(data);
// add a last track // add a last track
m_history.insert(m_history.begin(), pWindow); m_history.insert(m_history.begin(), window);
}); });
static auto P1 = Event::bus()->m_events.window.active.listen([this](PHLWINDOW window, uint8_t reason) { track(window); }); static auto P1 = g_pHookSystem->hookDynamic("activeWindow", [this](void* self, SCallbackInfo& info, std::any data) {
auto window = std::any_cast<PHLWINDOW>(data);
track(window);
});
} }
void CWindowHistoryTracker::track(PHLWINDOW w) { void CWindowHistoryTracker::track(PHLWINDOW w) {

View file

@ -2,13 +2,9 @@
#include "../../helpers/Monitor.hpp" #include "../../helpers/Monitor.hpp"
#include "../Workspace.hpp" #include "../Workspace.hpp"
#include "../state/FocusState.hpp" #include "../../managers/HookSystemManager.hpp"
#include "../../managers/eventLoop/EventLoopManager.hpp"
#include "../../event/EventBus.hpp"
#include "../../config/ConfigValue.hpp" #include "../../config/ConfigValue.hpp"
#include <hyprutils/utils/ScopeGuard.hpp>
using namespace Desktop; using namespace Desktop;
using namespace Desktop::History; using namespace Desktop::History;
@ -18,16 +14,27 @@ SP<CWorkspaceHistoryTracker> History::workspaceTracker() {
} }
CWorkspaceHistoryTracker::CWorkspaceHistoryTracker() { CWorkspaceHistoryTracker::CWorkspaceHistoryTracker() {
static auto P = Event::bus()->m_events.workspace.active.listen([this](PHLWORKSPACE workspace) { track(workspace); }); static auto P = g_pHookSystem->hookDynamic("workspace", [this](void* self, SCallbackInfo& info, std::any data) {
auto workspace = std::any_cast<PHLWORKSPACE>(data);
static auto P1 = Event::bus()->m_events.monitor.focused.listen([this](PHLMONITOR mon) { track(workspace);
// 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);
}); });
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,
}); });
} }
@ -45,32 +52,44 @@ CWorkspaceHistoryTracker::SWorkspacePreviousData& CWorkspaceHistoryTracker::data
} }
void CWorkspaceHistoryTracker::track(PHLWORKSPACE w) { void CWorkspaceHistoryTracker::track(PHLWORKSPACE w) {
if (!w || !w->m_monitor || w == m_lastWorkspaceData.workspace) if (!w->m_monitor)
return; 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& data = dataFor(w);
auto& monData = dataFor(w->m_monitor.lock());
Hyprutils::Utils::CScopeGuard x([&] { setLastWorkspaceData(w); }); if (!monData.workspace) {
data.previous.reset();
if (m_lastWorkspaceData.workspace == w && !*PALLOWWORKSPACECYCLES) data.previousID = WORKSPACE_INVALID;
data.previousName = "";
return; 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() { void CWorkspaceHistoryTracker::gc() {
std::erase_if(m_datas, [](const auto& e) { return !e.workspace; }); 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) { const CWorkspaceHistoryTracker::SWorkspacePreviousData* CWorkspaceHistoryTracker::previousWorkspace(PHLWORKSPACE ws) {
@ -137,15 +156,3 @@ SWorkspaceIDName CWorkspaceHistoryTracker::previousWorkspaceIDName(PHLWORKSPACE
return SWorkspaceIDName{.id = DATA->previousID, .name = DATA->previousName, .isAutoIDd = DATA->previousID <= 0}; 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;
}

View file

@ -32,19 +32,21 @@ namespace Desktop::History {
SWorkspaceIDName previousWorkspaceIDName(PHLWORKSPACE ws, PHLMONITOR restrict); SWorkspaceIDName previousWorkspaceIDName(PHLWORKSPACE ws, PHLMONITOR restrict);
private: private:
struct SLastWorkspaceData { struct SMonitorData {
PHLMONITORREF monitor; PHLMONITORREF monitor;
PHLWORKSPACEREF workspace; PHLWORKSPACEREF workspace;
std::string workspaceName = ""; std::string workspaceName = "";
WORKSPACEID workspaceID = WORKSPACE_INVALID; WORKSPACEID workspaceID = WORKSPACE_INVALID;
} m_lastWorkspaceData; };
std::vector<SWorkspacePreviousData> m_datas; std::vector<SWorkspacePreviousData> m_datas;
std::vector<SMonitorData> m_monitorDatas;
void track(PHLWORKSPACE w); void track(PHLWORKSPACE w);
void track(PHLMONITOR mon);
void gc(); void gc();
void setLastWorkspaceData(PHLWORKSPACE w);
SMonitorData& dataFor(PHLMONITOR mon);
SWorkspacePreviousData& dataFor(PHLWORKSPACE ws); SWorkspacePreviousData& dataFor(PHLWORKSPACE ws);
}; };

View file

@ -6,18 +6,16 @@ using namespace Desktop;
// fuck me. Writing this at 11pm, and I have an in-class test tomorrow. // fuck me. Writing this at 11pm, and I have an in-class test tomorrow.
// I am failing that bitch // I am failing that bitch
CReservedArea::CReservedArea(const Vector2D& tl, const Vector2D& br) : m_initialTopLeft(tl.clamp({0, 0})), m_initialBottomRight(br.clamp({0, 0})) { CReservedArea::CReservedArea(const Vector2D& tl, const Vector2D& br) : m_initialTopLeft(tl), m_initialBottomRight(br) {
calculate(); calculate();
} }
CReservedArea::CReservedArea(double top, double right, double bottom, double left) : CReservedArea::CReservedArea(double top, double right, double bottom, double left) : m_initialTopLeft(left, top), m_initialBottomRight(right, bottom) {
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(); calculate();
} }
CReservedArea::CReservedArea(const CBox& parent, const CBox& child) { CReservedArea::CReservedArea(const CBox& parent, const CBox& child) {
if (parent.empty() || child.empty()) ASSERT(!parent.empty() && !child.empty());
return; // empty reserved area
ASSERT(parent.containsPoint(child.pos() + Vector2D{0.0001, 0.0001})); ASSERT(parent.containsPoint(child.pos() + Vector2D{0.0001, 0.0001}));
ASSERT(parent.containsPoint(child.pos() + child.size() - Vector2D{0.0001, 0.0001})); ASSERT(parent.containsPoint(child.pos() + child.size() - Vector2D{0.0001, 0.0001}));
@ -83,8 +81,6 @@ void CReservedArea::addType(eReservedDynamicType t, const Vector2D& topLeft, con
auto& ref = m_dynamicReserved[t]; auto& ref = m_dynamicReserved[t];
ref.topLeft += topLeft; ref.topLeft += topLeft;
ref.bottomRight += bottomRight; ref.bottomRight += bottomRight;
ref.topLeft = ref.topLeft.clamp({0, 0});
ref.bottomRight = ref.bottomRight.clamp({0, 0});
calculate(); calculate();
} }

View file

@ -52,7 +52,6 @@ static const std::unordered_map<eRuleProperty, eRuleMatchEngine> RULE_ENGINES =
{RULE_PROP_XDG_TAG, RULE_MATCH_ENGINE_REGEX}, // {RULE_PROP_XDG_TAG, RULE_MATCH_ENGINE_REGEX}, //
{RULE_PROP_NAMESPACE, RULE_MATCH_ENGINE_REGEX}, // {RULE_PROP_NAMESPACE, RULE_MATCH_ENGINE_REGEX}, //
{RULE_PROP_EXEC_TOKEN, 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() { const std::vector<std::string>& Rule::allMatchPropStrings() {
@ -126,11 +125,10 @@ const std::string& IRule::name() {
return m_name; return m_name;
} }
void IRule::markAsExecRule(const std::string& token, uint64_t pid, bool persistent) { void IRule::markAsExecRule(const std::string& token, bool persistent) {
m_execData.isExecRule = true; m_execData.isExecRule = true;
m_execData.isExecPersistent = persistent; m_execData.isExecPersistent = persistent;
m_execData.token = token; m_execData.token = token;
m_execData.pid = pid;
m_execData.expiresAt = Time::steadyNow() + std::chrono::minutes(1); m_execData.expiresAt = Time::steadyNow() + std::chrono::minutes(1);
} }

View file

@ -6,6 +6,7 @@
#include "../../helpers/time/Time.hpp" #include "../../helpers/time/Time.hpp"
#include <vector> #include <vector>
#include <unordered_map> #include <unordered_map>
#include <cstdint>
#include <optional> #include <optional>
namespace Desktop::Rule { namespace Desktop::Rule {
@ -30,7 +31,6 @@ namespace Desktop::Rule {
RULE_PROP_XDG_TAG = (1 << 16), RULE_PROP_XDG_TAG = (1 << 16),
RULE_PROP_NAMESPACE = (1 << 17), RULE_PROP_NAMESPACE = (1 << 17),
RULE_PROP_EXEC_TOKEN = (1 << 18), RULE_PROP_EXEC_TOKEN = (1 << 18),
RULE_PROP_EXEC_PID = (1 << 19),
RULE_PROP_ALL = std::numeric_limits<std::underlying_type_t<eRuleProperty>>::max(), 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(); virtual std::underlying_type_t<eRuleProperty> getPropertiesMask();
void registerMatch(eRuleProperty, const std::string&); void registerMatch(eRuleProperty, const std::string&);
void markAsExecRule(const std::string& token, uint64_t pid, bool persistent = false); void markAsExecRule(const std::string& token, bool persistent = false);
bool isExecRule(); bool isExecRule();
bool isExecPersistent(); bool isExecPersistent();
bool execExpired(); bool execExpired();
@ -78,7 +78,6 @@ namespace Desktop::Rule {
bool isExecRule = false; bool isExecRule = false;
bool isExecPersistent = false; bool isExecPersistent = false;
std::string token; std::string token;
uint64_t pid = 0;
Time::steady_tp expiresAt; Time::steady_tp expiresAt;
} m_execData; } m_execData;
}; };

View file

@ -4,7 +4,6 @@
#include "../../view/LayerSurface.hpp" #include "../../view/LayerSurface.hpp"
#include "../../types/OverridableVar.hpp" #include "../../types/OverridableVar.hpp"
#include "../../../helpers/MiscFunctions.hpp" #include "../../../helpers/MiscFunctions.hpp"
#include "../../../event/EventBus.hpp"
using namespace Desktop; using namespace Desktop;
using namespace Desktop::Rule; using namespace Desktop::Rule;
@ -33,38 +32,11 @@ void CLayerRuleApplicator::resetProps(std::underlying_type_t<eRuleProperty> prop
UNSET(aboveLock) UNSET(aboveLock)
UNSET(ignoreAlpha) UNSET(ignoreAlpha)
UNSET(animationStyle) 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) { void CLayerRuleApplicator::applyDynamicRule(const SP<CLayerRule>& rule) {
for (const auto& [key, effect] : rule->effects()) { for (const auto& [key, effect] : rule->effects()) {
switch (key) { 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: { case LAYER_RULE_EFFECT_NONE: {
Log::logger->log(Log::ERR, "CLayerRuleApplicator::applyDynamicRule: BUG THIS: LAYER_RULE_EFFECT_NONE??"); Log::logger->log(Log::ERR, "CLayerRuleApplicator::applyDynamicRule: BUG THIS: LAYER_RULE_EFFECT_NONE??");
break; break;
@ -101,8 +73,8 @@ void CLayerRuleApplicator::applyDynamicRule(const SP<CLayerRule>& rule) {
} }
case LAYER_RULE_EFFECT_ORDER: { case LAYER_RULE_EFFECT_ORDER: {
try { try {
m_order.first.set(std::stoi(effect), Types::PRIORITY_WINDOW_RULE); m_noScreenShare.first.set(std::stoi(effect), Types::PRIORITY_WINDOW_RULE);
m_order.second |= rule->getPropertiesMask(); m_noScreenShare.second |= rule->getPropertiesMask();
} catch (...) { Log::logger->log(Log::ERR, "CLayerRuleApplicator::applyDynamicRule: invalid order {}", effect); } } catch (...) { Log::logger->log(Log::ERR, "CLayerRuleApplicator::applyDynamicRule: invalid order {}", effect); }
break; break;
} }
@ -153,7 +125,4 @@ void CLayerRuleApplicator::propertiesChanged(std::underlying_type_t<eRulePropert
applyDynamicRule(wr); applyDynamicRule(wr);
} }
// for plugins
Event::bus()->m_events.layer.updateRules.emit(m_ls.lock());
} }

View file

@ -1,6 +1,5 @@
#pragma once #pragma once
#include "LayerRuleEffectContainer.hpp"
#include "../../DesktopTypes.hpp" #include "../../DesktopTypes.hpp"
#include "../Rule.hpp" #include "../Rule.hpp"
#include "../../types/OverridableVar.hpp" #include "../../types/OverridableVar.hpp"
@ -22,17 +21,6 @@ namespace Desktop::Rule {
void propertiesChanged(std::underlying_type_t<eRuleProperty> props); void propertiesChanged(std::underlying_type_t<eRuleProperty> props);
void resetProps(std::underlying_type_t<eRuleProperty> props, Types::eOverridePriority prio = Types::PRIORITY_WINDOW_RULE); 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 COMMA ,
#define DEFINE_PROP(type, name, def) \ #define DEFINE_PROP(type, name, def) \
private: \ private: \

View file

@ -8,5 +8,5 @@ CWorkspaceMatchEngine::CWorkspaceMatchEngine(const std::string& s) : m_value(s)
} }
bool CWorkspaceMatchEngine::match(PHLWORKSPACE ws) { bool CWorkspaceMatchEngine::match(PHLWORKSPACE ws) {
return ws && ws->matchesStaticSelector(m_value); return ws->matchesStaticSelector(m_value);
} }

View file

@ -77,7 +77,7 @@ bool CWindowRule::matches(PHLWINDOW w, bool allowEnvLookup) {
return false; return false;
break; break;
case RULE_PROP_GROUP: case RULE_PROP_GROUP:
if (!engine->match(!!w->m_group)) if (!engine->match(w->m_groupData.pNextWindow))
return false; return false;
break; break;
case RULE_PROP_MODAL: case RULE_PROP_MODAL:
@ -97,32 +97,28 @@ bool CWindowRule::matches(PHLWINDOW w, bool allowEnvLookup) {
return false; return false;
break; break;
case RULE_PROP_CONTENT: case RULE_PROP_CONTENT:
if (!engine->match(w->getContentType()) && !engine->match(NContentType::toString(w->getContentType()))) if (!engine->match(NContentType::toString(w->getContentType())))
return false; return false;
break; break;
case RULE_PROP_XDG_TAG: case RULE_PROP_XDG_TAG:
if (!w->xdgTag().has_value() || !engine->match(*w->xdgTag())) if (!w->xdgTag().has_value() || !engine->match(*w->xdgTag()))
return false; return false;
break; break;
case RULE_PROP_EXEC_TOKEN: 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) if (!allowEnvLookup)
break; break;
const auto ENV = w->getEnv(); const auto ENV = w->getEnv();
bool match = false;
if (ENV.contains(EXEC_RULE_ENV_NAME)) { if (ENV.contains(EXEC_RULE_ENV_NAME)) {
if (engine->match(ENV.at(EXEC_RULE_ENV_NAME))) const auto TKN = ENV.at(EXEC_RULE_ENV_NAME);
match = true; if (!engine->match(TKN))
} 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; return false;
break; break;
} }
return false;
}
} }
return true; return true;
@ -157,6 +153,11 @@ SP<CWindowRule> CWindowRule::buildFromExecString(std::string&& s) {
wr->addEffect(*EFFECT, std::string{"1"}); 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; return wr;
} }

View file

@ -4,7 +4,8 @@
#include "../utils/SetUtils.hpp" #include "../utils/SetUtils.hpp"
#include "../../view/Window.hpp" #include "../../view/Window.hpp"
#include "../../types/OverridableVar.hpp" #include "../../types/OverridableVar.hpp"
#include "../../../event/EventBus.hpp" #include "../../../managers/LayoutManager.hpp"
#include "../../../managers/HookSystemManager.hpp"
#include <hyprutils/string/String.hpp> #include <hyprutils/string/String.hpp>
@ -150,7 +151,7 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const
CGradientValueData activeBorderGradient = {}; CGradientValueData activeBorderGradient = {};
CGradientValueData inactiveBorderGradient = {}; CGradientValueData inactiveBorderGradient = {};
bool active = true; bool active = true;
CVarList colorsAndAngles = CVarList(trim(effect), 0, 's', true); CVarList colorsAndAngles = CVarList(trim(effect.substr(effect.find_first_of(' ') + 1)), 0, 's', true);
// Basic form has only two colors, everything else can be parsed as a gradient // Basic form has only two colors, everything else can be parsed as a gradient
if (colorsAndAngles.size() == 2 && !colorsAndAngles[1].contains("deg")) { if (colorsAndAngles.size() == 2 && !colorsAndAngles[1].contains("deg")) {
@ -158,8 +159,6 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const
Types::COverridableVar(CGradientValueData(CHyprColor(configStringToInt(colorsAndAngles[0]).value_or(0))), Types::PRIORITY_WINDOW_RULE); Types::COverridableVar(CGradientValueData(CHyprColor(configStringToInt(colorsAndAngles[0]).value_or(0))), Types::PRIORITY_WINDOW_RULE);
m_inactiveBorderColor.first = m_inactiveBorderColor.first =
Types::COverridableVar(CGradientValueData(CHyprColor(configStringToInt(colorsAndAngles[1]).value_or(0))), Types::PRIORITY_WINDOW_RULE); 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; break;
} }
@ -266,17 +265,13 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const
if (!m_window) if (!m_window)
break; break;
const auto VEC = m_window->calculateExpression(effect); const auto VEC = configStringToVector2D(effect);
if (!VEC) { if (VEC.x < 1 || VEC.y < 1) {
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"); Log::logger->log(Log::ERR, "Invalid size for maxsize");
break; 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) if (*PCLAMP_TILED || m_window->m_isFloating)
m_window->clampWindowSize(std::nullopt, m_maxSize.first.value()); m_window->clampWindowSize(std::nullopt, m_maxSize.first.value());
@ -291,18 +286,13 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const
if (!m_window) if (!m_window)
break; break;
const auto VEC = m_window->calculateExpression(effect); const auto VEC = configStringToVector2D(effect);
if (!VEC) { if (VEC.x < 1 || VEC.y < 1) {
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"); Log::logger->log(Log::ERR, "Invalid size for maxsize");
break; 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) if (*PCLAMP_TILED || m_window->m_isFloating)
m_window->clampWindowSize(m_minSize.first.value(), std::nullopt); 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()); } } catch (std::exception& e) { Log::logger->log(Log::ERR, "minsize rule \"{}\" failed with: {}", effect, e.what()); }
@ -547,7 +537,7 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyStaticRule(const
return SRuleResult{}; return SRuleResult{};
} }
void CWindowRuleApplicator::readStaticRules(bool preRead) { void CWindowRuleApplicator::readStaticRules() {
if (!m_window) if (!m_window)
return; return;
@ -602,7 +592,6 @@ void CWindowRuleApplicator::readStaticRules(bool preRead) {
for (const auto& wr : execRules) { for (const auto& wr : execRules) {
applyStaticRule(wr); applyStaticRule(wr);
applyDynamicRule(wr); applyDynamicRule(wr);
if (!preRead)
ruleEngine()->unregisterRule(wr); ruleEngine()->unregisterRule(wr);
} }
} }
@ -630,13 +619,11 @@ void CWindowRuleApplicator::propertiesChanged(std::underlying_type_t<eRuleProper
needsRelayout = needsRelayout || RES.needsRelayout; needsRelayout = needsRelayout || RES.needsRelayout;
} }
m_window->updateWindowData();
m_window->updateWindowDecos();
m_window->updateDecorationValues(); m_window->updateDecorationValues();
if (needsRelayout) if (needsRelayout)
g_pDecorationPositioner->forceRecalcFor(m_window.lock()); g_pDecorationPositioner->forceRecalcFor(m_window.lock());
// for plugins // for plugins
Event::bus()->m_events.window.updateRules.emit(m_window.lock()); EMIT_HOOK_EVENT("windowUpdateRules", m_window.lock());
} }

View file

@ -33,7 +33,8 @@ namespace Desktop::Rule {
void propertiesChanged(std::underlying_type_t<eRuleProperty> props); void propertiesChanged(std::underlying_type_t<eRuleProperty> props);
std::unordered_set<CWindowRuleEffectContainer::storageType> resetProps(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); Types::eOverridePriority prio = Types::PRIORITY_WINDOW_RULE);
void readStaticRules(bool preRead = false); void readStaticRules();
void applyStaticRules();
// static props // static props
struct { struct {

View file

@ -3,19 +3,14 @@
#include "../../Compositor.hpp" #include "../../Compositor.hpp"
#include "../../protocols/XDGShell.hpp" #include "../../protocols/XDGShell.hpp"
#include "../../render/Renderer.hpp" #include "../../render/Renderer.hpp"
#include "../../managers/LayoutManager.hpp"
#include "../../managers/EventManager.hpp" #include "../../managers/EventManager.hpp"
#include "../../managers/input/InputManager.hpp" #include "../../managers/HookSystemManager.hpp"
#include "../../managers/SeatManager.hpp"
#include "../../xwayland/XSurface.hpp" #include "../../xwayland/XSurface.hpp"
#include "../../protocols/PointerConstraints.hpp" #include "../../protocols/PointerConstraints.hpp"
#include "managers/animation/DesktopAnimationManager.hpp"
#include "../../layout/LayoutManager.hpp"
#include "../../event/EventBus.hpp"
using namespace Desktop; using namespace Desktop;
#define COMMA ,
SP<CFocusState> Desktop::focusState() { SP<CFocusState> Desktop::focusState() {
static SP<CFocusState> state = makeShared<CFocusState>(); static SP<CFocusState> state = makeShared<CFocusState>();
return state; return state;
@ -37,7 +32,6 @@ static SFullscreenWorkspaceFocusResult onFullscreenWorkspaceFocusWindow(PHLWINDO
if (pWindow->m_isFloating) { if (pWindow->m_isFloating) {
// if the window is floating, just bring it to the top // if the window is floating, just bring it to the top
pWindow->m_createdOverFullscreen = true; pWindow->m_createdOverFullscreen = true;
g_pDesktopAnimationManager->setFullscreenFloatingFade(pWindow, 1.f);
g_pHyprRenderer->damageWindow(pWindow); g_pHyprRenderer->damageWindow(pWindow);
return {}; return {};
} }
@ -67,7 +61,7 @@ static SFullscreenWorkspaceFocusResult onFullscreenWorkspaceFocusWindow(PHLWINDO
return {}; return {};
} }
void CFocusState::fullWindowFocus(PHLWINDOW pWindow, eFocusReason reason, SP<CWLSurfaceResource> surface, bool forceFSCycle) { void CFocusState::fullWindowFocus(PHLWINDOW pWindow, SP<CWLSurfaceResource> surface, bool forceFSCycle) {
if (pWindow) { if (pWindow) {
if (!pWindow->m_workspace) if (!pWindow->m_workspace)
return; return;
@ -87,10 +81,10 @@ void CFocusState::fullWindowFocus(PHLWINDOW pWindow, eFocusReason reason, SP<CWL
return; return;
} }
rawWindowFocus(pWindow, reason, surface); rawWindowFocus(pWindow, surface);
} }
void CFocusState::rawWindowFocus(PHLWINDOW pWindow, eFocusReason reason, SP<CWLSurfaceResource> surface) { void CFocusState::rawWindowFocus(PHLWINDOW pWindow, SP<CWLSurfaceResource> surface) {
static auto PFOLLOWMOUSE = CConfigValue<Hyprlang::INT>("input:follow_mouse"); static auto PFOLLOWMOUSE = CConfigValue<Hyprlang::INT>("input:follow_mouse");
static auto PSPECIALFALLTHROUGH = CConfigValue<Hyprlang::INT>("input:special_fallthrough"); static auto PSPECIALFALLTHROUGH = CConfigValue<Hyprlang::INT>("input:special_fallthrough");
@ -109,9 +103,7 @@ void CFocusState::rawWindowFocus(PHLWINDOW pWindow, eFocusReason reason, SP<CWLS
if (pWindow && pWindow->m_isX11 && pWindow->isX11OverrideRedirect() && !pWindow->m_xwaylandSurface->wantsFocus()) if (pWindow && pWindow->m_isX11 && pWindow->isX11OverrideRedirect() && !pWindow->m_xwaylandSurface->wantsFocus())
return; return;
// m_target on purpose, this avoids the group g_pLayoutManager->getCurrentLayout()->bringWindowToTop(pWindow);
if (pWindow)
g_layoutManager->bringTargetToTop(pWindow->m_target);
if (!pWindow || !validMapped(pWindow)) { if (!pWindow || !validMapped(pWindow)) {
@ -133,7 +125,9 @@ void CFocusState::rawWindowFocus(PHLWINDOW pWindow, eFocusReason reason, SP<CWLS
g_pEventManager->postEvent(SHyprIPCEvent{"activewindow", ","}); g_pEventManager->postEvent(SHyprIPCEvent{"activewindow", ","});
g_pEventManager->postEvent(SHyprIPCEvent{"activewindowv2", ""}); g_pEventManager->postEvent(SHyprIPCEvent{"activewindowv2", ""});
Event::bus()->m_events.window.active.emit(nullptr, reason); EMIT_HOOK_EVENT("activeWindow", PHLWINDOW{nullptr});
g_pLayoutManager->getCurrentLayout()->onWindowFocusChange(nullptr);
m_focusSurface.reset(); m_focusSurface.reset();
@ -200,14 +194,16 @@ void CFocusState::rawWindowFocus(PHLWINDOW pWindow, eFocusReason reason, SP<CWLS
g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindow", .data = pWindow->m_class + "," + pWindow->m_title}); 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()))}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindowv2", .data = std::format("{:x}", rc<uintptr_t>(pWindow.get()))});
Event::bus()->m_events.window.active.emit(pWindow, reason); EMIT_HOOK_EVENT("activeWindow", pWindow);
g_pLayoutManager->getCurrentLayout()->onWindowFocusChange(pWindow);
g_pInputManager->recheckIdleInhibitorStatus(); g_pInputManager->recheckIdleInhibitorStatus();
if (*PFOLLOWMOUSE == 0) if (*PFOLLOWMOUSE == 0)
g_pInputManager->sendMotionEventsToFocused(); g_pInputManager->sendMotionEventsToFocused();
if (pWindow->m_group) if (pWindow->m_groupData.pNextWindow)
pWindow->deactivateGroupMembers(); pWindow->deactivateGroupMembers();
} }
@ -233,7 +229,7 @@ void CFocusState::rawSurfaceFocus(SP<CWLSurfaceResource> pSurface, PHLWINDOW pWi
g_pSeatManager->setKeyboardFocus(nullptr); g_pSeatManager->setKeyboardFocus(nullptr);
g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindow", .data = ","}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindow", .data = ","});
g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindowv2", .data = ""}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindowv2", .data = ""});
Event::bus()->m_events.input.keyboard.focus.emit(nullptr); EMIT_HOOK_EVENT("keyboardFocus", SP<CWLSurfaceResource>{nullptr});
m_focusSurface.reset(); m_focusSurface.reset();
return; return;
} }
@ -249,7 +245,7 @@ void CFocusState::rawSurfaceFocus(SP<CWLSurfaceResource> pSurface, PHLWINDOW pWi
g_pXWaylandManager->activateSurface(pSurface, true); g_pXWaylandManager->activateSurface(pSurface, true);
m_focusSurface = pSurface; m_focusSurface = pSurface;
Event::bus()->m_events.input.keyboard.focus.emit(pSurface); EMIT_HOOK_EVENT("keyboardFocus", pSurface);
const auto SURF = Desktop::View::CWLSurface::fromResource(pSurface); const auto SURF = Desktop::View::CWLSurface::fromResource(pSurface);
const auto OLDSURF = Desktop::View::CWLSurface::fromResource(PLASTSURF); const auto OLDSURF = Desktop::View::CWLSurface::fromResource(PLASTSURF);
@ -278,7 +274,7 @@ void CFocusState::rawMonitorFocus(PHLMONITOR pMonitor) {
g_pEventManager->postEvent(SHyprIPCEvent{.event = "focusedmon", .data = pMonitor->m_name + "," + WORKSPACE_NAME}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "focusedmon", .data = pMonitor->m_name + "," + WORKSPACE_NAME});
g_pEventManager->postEvent(SHyprIPCEvent{.event = "focusedmonv2", .data = pMonitor->m_name + "," + WORKSPACE_ID}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "focusedmonv2", .data = pMonitor->m_name + "," + WORKSPACE_ID});
Event::bus()->m_events.monitor.focused.emit(pMonitor); EMIT_HOOK_EVENT("focusedMon", pMonitor);
m_focusMonitor = pMonitor; m_focusMonitor = pMonitor;
} }
@ -298,7 +294,3 @@ void CFocusState::resetWindowFocus() {
m_focusWindow.reset(); m_focusWindow.reset();
m_focusSurface.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;
}

View file

@ -1,24 +1,11 @@
#pragma once #pragma once
#include "../DesktopTypes.hpp" #include "../DesktopTypes.hpp"
#include "../../helpers/signal/Signal.hpp" #include "../../SharedDefs.hpp"
class CWLSurfaceResource; class CWLSurfaceResource;
namespace Desktop { 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 { class CFocusState {
public: public:
CFocusState(); CFocusState();
@ -28,8 +15,8 @@ namespace Desktop {
CFocusState(CFocusState&) = delete; CFocusState(CFocusState&) = delete;
CFocusState(const CFocusState&) = delete; CFocusState(const CFocusState&) = delete;
void fullWindowFocus(PHLWINDOW w, eFocusReason reason, SP<CWLSurfaceResource> surface = nullptr, bool forceFSCycle = false); void fullWindowFocus(PHLWINDOW w, SP<CWLSurfaceResource> surface = nullptr, bool forceFSCycle = false);
void rawWindowFocus(PHLWINDOW w, eFocusReason reason, SP<CWLSurfaceResource> surface = nullptr); void rawWindowFocus(PHLWINDOW w, SP<CWLSurfaceResource> surface = nullptr);
void rawSurfaceFocus(SP<CWLSurfaceResource> s, PHLWINDOW pWindowOwner = nullptr); void rawSurfaceFocus(SP<CWLSurfaceResource> s, PHLWINDOW pWindowOwner = nullptr);
void rawMonitorFocus(PHLMONITOR m); void rawMonitorFocus(PHLMONITOR m);
@ -44,7 +31,7 @@ namespace Desktop {
PHLWINDOWREF m_focusWindow; PHLWINDOWREF m_focusWindow;
PHLMONITORREF m_focusMonitor; PHLMONITORREF m_focusMonitor;
CHyprSignalListener m_windowOpen, m_windowClose; SP<HOOK_CALLBACK_FN> m_windowOpen, m_windowClose;
}; };
SP<CFocusState> focusState(); SP<CFocusState> focusState();

View file

@ -1,351 +0,0 @@
#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);
}

View file

@ -1,69 +0,0 @@
#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();
};

View file

@ -10,8 +10,8 @@
#include "../../config/ConfigManager.hpp" #include "../../config/ConfigManager.hpp"
#include "../../helpers/Monitor.hpp" #include "../../helpers/Monitor.hpp"
#include "../../managers/input/InputManager.hpp" #include "../../managers/input/InputManager.hpp"
#include "../../managers/HookSystemManager.hpp"
#include "../../managers/EventManager.hpp" #include "../../managers/EventManager.hpp"
#include "../../event/EventBus.hpp"
using namespace Desktop; using namespace Desktop;
using namespace Desktop::View; using namespace Desktop::View;
@ -23,11 +23,24 @@ PHLLS CLayerSurface::create(SP<CLayerShellResource> resource) {
pLS->m_wlSurface->assign(resource->m_surface.lock(), pLS); pLS->m_wlSurface->assign(resource->m_surface.lock(), pLS);
pLS->m_ruleApplicator = makeUnique<Desktop::Rule::CLayerRuleApplicator>(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_self = pLS;
pLS->m_namespace = resource->m_layerNamespace; 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_layer = resource->m_current.layer;
pLS->m_popupHead = CPopup::create(pLS); 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);
g_pAnimationManager->createAnimation(0.f, pLS->m_alpha, g_pConfigManager->getAnimationPropertyConfig("fadeLayersIn"), pLS, AVARDAMAGE_ENTIRE); 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); g_pAnimationManager->createAnimation(Vector2D(0, 0), pLS->m_realPosition, g_pConfigManager->getAnimationPropertyConfig("layersIn"), pLS, AVARDAMAGE_ENTIRE);
@ -37,19 +50,6 @@ PHLLS CLayerSurface::create(SP<CLayerShellResource> resource) {
pLS->m_alpha->setValueAndWarp(0.f); 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, 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); sc<int>(pLS->m_layer), pMonitor->m_name);
@ -222,7 +222,7 @@ void CLayerSurface::onMap() {
m_fadingOut = false; m_fadingOut = false;
g_pEventManager->postEvent(SHyprIPCEvent{.event = "openlayer", .data = m_namespace}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "openlayer", .data = m_namespace});
Event::bus()->m_events.layer.opened.emit(m_self.lock()); EMIT_HOOK_EVENT("openLayer", m_self.lock());
g_pCompositor->setPreferredScaleForSurface(m_wlSurface->resource(), PMONITOR->m_scale); g_pCompositor->setPreferredScaleForSurface(m_wlSurface->resource(), PMONITOR->m_scale);
g_pCompositor->setPreferredTransformForSurface(m_wlSurface->resource(), PMONITOR->m_transform); 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())); 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}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "closelayer", .data = m_layerSurface->m_layerNamespace});
Event::bus()->m_events.layer.closed.emit(m_self.lock()); EMIT_HOOK_EVENT("closeLayer", m_self.lock());
std::erase_if(g_pInputManager->m_exclusiveLSes, [this](const auto& other) { return !other || other == m_self; }); std::erase_if(g_pInputManager->m_exclusiveLSes, [this](const auto& other) { return !other || other == m_self; });
@ -323,18 +323,16 @@ void CLayerSurface::onCommit() {
if (m_layerSurface->m_current.committed != 0) { if (m_layerSurface->m_current.committed != 0) {
if (m_layerSurface->m_current.committed & CLayerShellResource::eCommittedState::STATE_LAYER && m_layerSurface->m_current.layer != m_layer) { 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++) { for (auto it = PMONITOR->m_layerSurfaceLayers[m_layer].begin(); it != PMONITOR->m_layerSurfaceLayers[m_layer].end(); it++) {
if (*it == m_self) { if (*it == m_self) {
PMONITOR->m_layerSurfaceLayers[NEW_LAYER].emplace_back(*it); PMONITOR->m_layerSurfaceLayers[m_layerSurface->m_current.layer].emplace_back(*it);
PMONITOR->m_layerSurfaceLayers[m_layer].erase(it); PMONITOR->m_layerSurfaceLayers[m_layer].erase(it);
break; break;
} }
} }
m_layer = NEW_LAYER; m_layer = m_layerSurface->m_current.layer;
m_aboveFullscreen = NEW_LAYER >= ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY; m_aboveFullscreen = m_layerSurface->m_current.layer >= ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY;
// if in fullscreen, only overlay can be above. // 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; *m_alpha = PMONITOR->inFullscreenMode() ? (m_layer >= ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY ? 1.F : 0.F) : 1.F;

View file

@ -9,7 +9,6 @@
#include "../../managers/animation/AnimationManager.hpp" #include "../../managers/animation/AnimationManager.hpp"
#include "LayerSurface.hpp" #include "LayerSurface.hpp"
#include "../../managers/input/InputManager.hpp" #include "../../managers/input/InputManager.hpp"
#include "../../managers/eventLoop/EventLoopManager.hpp"
#include "../../render/Renderer.hpp" #include "../../render/Renderer.hpp"
#include "../../render/OpenGL.hpp" #include "../../render/OpenGL.hpp"
#include <ranges> #include <ranges>
@ -109,12 +108,8 @@ void CPopup::initAllSignals() {
m_alpha->setCallbackOnEnd( m_alpha->setCallbackOnEnd(
[this](auto) { [this](auto) {
if (inert()) { if (inert()) {
g_pEventLoopManager->doLater([p = m_self] { g_pHyprRenderer->damageBox(CBox{coordsGlobal(), size()});
if (!p) fullyDestroy();
return;
g_pHyprRenderer->damageBox(CBox{p->coordsGlobal(), p->size()});
p->fullyDestroy();
});
} }
}, },
false); false);
@ -199,7 +194,7 @@ void CPopup::onMap() {
//unconstrain(); //unconstrain();
sendScale(); sendScale();
m_wlSurface->resource()->breadthfirst([PMONITOR](SP<CWLSurfaceResource> s, const Vector2D& offset, void* d) { s->enter(PMONITOR->m_self.lock()); }, nullptr); m_resource->m_surface->m_surface->enter(PMONITOR->m_self.lock());
if (!m_layerOwner.expired() && m_layerOwner->m_layer < ZWLR_LAYER_SHELL_V1_LAYER_TOP) if (!m_layerOwner.expired() && m_layerOwner->m_layer < ZWLR_LAYER_SHELL_V1_LAYER_TOP)
g_pHyprOpenGL->markBlurDirtyForMonitor(g_pCompositor->getMonitorFromID(m_layerOwner->m_layer)); g_pHyprOpenGL->markBlurDirtyForMonitor(g_pCompositor->getMonitorFromID(m_layerOwner->m_layer));
@ -339,7 +334,8 @@ void CPopup::reposition() {
if (!PMONITOR) if (!PMONITOR)
return; return;
m_resource->applyPositioning(m_windowOwner ? PMONITOR->logicalBoxMinusReserved() : PMONITOR->logicalBox(), COORDS); CBox box = {PMONITOR->m_position.x, PMONITOR->m_position.y, PMONITOR->m_size.x, PMONITOR->m_size.y};
m_resource->applyPositioning(box, COORDS);
} }
SP<Desktop::View::CWLSurface> CPopup::getT1Owner() const { SP<Desktop::View::CWLSurface> CPopup::getT1Owner() const {
@ -403,14 +399,10 @@ void CPopup::recheckChildrenRecursive() {
std::vector<WP<CPopup>> cpy; std::vector<WP<CPopup>> cpy;
std::ranges::for_each(m_children, [&cpy](const auto& el) { cpy.emplace_back(el); }); std::ranges::for_each(m_children, [&cpy](const auto& el) { cpy.emplace_back(el); });
for (auto const& c : cpy) { for (auto const& c : cpy) {
if (!c || !c->visible()) if (!c->visible())
continue; continue;
c->onCommit(true);
// keep ref, onCommit can call onDestroy c->recheckChildrenRecursive();
auto x = c.lock();
x->onCommit(true);
x->recheckChildrenRecursive();
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -26,19 +26,8 @@ struct SWorkspaceRule;
class IWindowTransformer; class IWindowTransformer;
namespace Layout {
class ITarget;
class CWindowTarget;
}
namespace Desktop {
enum eFocusReason : uint8_t;
}
namespace Desktop::View { namespace Desktop::View {
class CGroup;
enum eGroupRules : uint8_t { enum eGroupRules : uint8_t {
// effective only during first map, except for _ALWAYS variant // effective only during first map, except for _ALWAYS variant
GROUP_NONE = 0, GROUP_NONE = 0,
@ -49,7 +38,6 @@ namespace Desktop::View {
GROUP_LOCK_ALWAYS = 1 << 4, GROUP_LOCK_ALWAYS = 1 << 4,
GROUP_INVADE = 1 << 5, // Force enter a group, event if lock is engaged GROUP_INVADE = 1 << 5, // Force enter a group, event if lock is engaged
GROUP_OVERRIDE = 1 << 6, // Override other rules GROUP_OVERRIDE = 1 << 6, // Override other rules
GROUP_DENY = 1 << 7, // deny
}; };
enum eGetWindowProperties : uint8_t { enum eGetWindowProperties : uint8_t {
@ -73,11 +61,6 @@ namespace Desktop::View {
SUPPRESS_FULLSCREEN_OUTPUT = 1 << 4, SUPPRESS_FULLSCREEN_OUTPUT = 1 << 4,
}; };
struct SWindowActiveEvent {
PHLWINDOW window = nullptr;
eFocusReason reason = sc<eFocusReason>(0) /* unknown */;
};
struct SInitialWorkspaceToken { struct SInitialWorkspaceToken {
PHLWINDOWREF primaryOwner; PHLWINDOWREF primaryOwner;
std::string workspace; std::string workspace;
@ -109,17 +92,11 @@ namespace Desktop::View {
struct { struct {
CSignalT<> destroy; CSignalT<> destroy;
CSignalT<> unmap;
CSignalT<> hide;
CSignalT<> resize;
CSignalT<> monitorChanged;
} m_events; } m_events;
WP<CXDGSurfaceResource> m_xdgSurface; WP<CXDGSurfaceResource> m_xdgSurface;
WP<CXWaylandSurface> m_xwaylandSurface; WP<CXWaylandSurface> m_xwaylandSurface;
SP<Layout::ITarget> m_target;
// this is the position and size of the "bounding box" // this is the position and size of the "bounding box"
Vector2D m_position = Vector2D(0, 0); Vector2D m_position = Vector2D(0, 0);
Vector2D m_size = Vector2D(0, 0); Vector2D m_size = Vector2D(0, 0);
@ -135,21 +112,30 @@ namespace Desktop::View {
std::optional<std::pair<uint32_t, Vector2D>> m_pendingSizeAck; std::optional<std::pair<uint32_t, Vector2D>> m_pendingSizeAck;
std::vector<std::pair<uint32_t, Vector2D>> m_pendingSizeAcks; 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 // for floating window offset in workspace animations
Vector2D m_floatingOffset = Vector2D(0, 0); 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 // for recovering relative cursor position
Vector2D m_relativeCursorCoordsOnLastWarp = Vector2D(-1, -1); Vector2D m_relativeCursorCoordsOnLastWarp = Vector2D(-1, -1);
bool m_firstMap = false; // for layouts bool m_firstMap = false; // for layouts
bool m_isFloating = false; bool m_isFloating = false;
bool m_draggingTiled = false; // for dragging around tiled windows
SFullscreenState m_fullscreenState = {.internal = FSMODE_NONE, .client = FSMODE_NONE}; SFullscreenState m_fullscreenState = {.internal = FSMODE_NONE, .client = FSMODE_NONE};
std::string m_title = ""; std::string m_title = "";
std::string m_class = ""; std::string m_class = "";
std::string m_initialTitle = ""; std::string m_initialTitle = "";
std::string m_initialClass = ""; std::string m_initialClass = "";
PHLWORKSPACE m_workspace; PHLWORKSPACE m_workspace;
PHLMONITORREF m_monitor, m_prevMonitor; PHLMONITORREF m_monitor;
bool m_isMapped = false; bool m_isMapped = false;
@ -243,14 +229,16 @@ namespace Desktop::View {
std::string m_initialWorkspaceToken = ""; std::string m_initialWorkspaceToken = "";
// for groups // for groups
SP<CGroup> m_group; 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; 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 // ANR
PHLANIMVAR<float> m_notRespondingTint; PHLANIMVAR<float> m_notRespondingTint;
@ -312,6 +300,21 @@ namespace Desktop::View {
bool isInCurvedCorner(double x, double y); bool isInCurvedCorner(double x, double y);
bool hasPopupAt(const Vector2D& pos); bool hasPopupAt(const Vector2D& pos);
int popupsCount(); 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 setAnimationsToMove();
void onWorkspaceAnimUpdate(); void onWorkspaceAnimUpdate();
void onFocusAnimUpdate(); void onFocusAnimUpdate();
@ -325,6 +328,8 @@ namespace Desktop::View {
PHLWINDOW getSwallower(); PHLWINDOW getSwallower();
bool isX11OverrideRedirect(); bool isX11OverrideRedirect();
bool isModal(); bool isModal();
Vector2D requestedMinSize();
Vector2D requestedMaxSize();
Vector2D realToReportSize(); Vector2D realToReportSize();
Vector2D realToReportPosition(); Vector2D realToReportPosition();
Vector2D xwaylandSizeToReal(Vector2D size); Vector2D xwaylandSizeToReal(Vector2D size);
@ -344,8 +349,6 @@ namespace Desktop::View {
std::optional<Vector2D> calculateExpression(const std::string& s); std::optional<Vector2D> calculateExpression(const std::string& s);
std::optional<Vector2D> minSize(); std::optional<Vector2D> minSize();
std::optional<Vector2D> maxSize(); std::optional<Vector2D> maxSize();
SP<Layout::ITarget> layoutTarget();
bool canBeGroupedInto(SP<CGroup> group);
CBox getWindowMainSurfaceBox() const { CBox getWindowMainSurfaceBox() const {
return {m_realPosition->value().x, m_realPosition->value().y, m_realSize->value().x, m_realSize->value().y}; return {m_realPosition->value().x, m_realPosition->value().y, m_realSize->value().x, m_realSize->value().y};

View file

@ -1,8 +0,0 @@
#include "EventBus.hpp"
using namespace Event;
UP<CEventBus>& Event::bus() {
static UP<CEventBus> p = makeUnique<CEventBus>();
return p;
}

View file

@ -1,144 +0,0 @@
#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();
};

View file

@ -4,8 +4,6 @@
#include <algorithm> #include <algorithm>
#include <unistd.h> #include <unistd.h>
#include "../managers/eventLoop/EventLoopManager.hpp" #include "../managers/eventLoop/EventLoopManager.hpp"
#include "../desktop/rule/windowRule/WindowRule.hpp"
#include "../desktop/rule/Engine.hpp"
using namespace Hyprutils::OS; using namespace Hyprutils::OS;
@ -121,9 +119,6 @@ SP<CPromise<std::string>> CAsyncDialogBox::open() {
m_selfReference = m_selfWeakReference.lock(); m_selfReference = m_selfWeakReference.lock();
if (!m_execRuleToken.empty())
proc.addEnv(Desktop::Rule::EXEC_RULE_ENV_NAME, m_execRuleToken);
if (!proc.runAsync()) { if (!proc.runAsync()) {
Log::logger->log(Log::ERR, "CAsyncDialogBox::open: failed to run async"); Log::logger->log(Log::ERR, "CAsyncDialogBox::open: failed to run async");
wl_event_source_remove(m_readEventSource); wl_event_source_remove(m_readEventSource);
@ -159,9 +154,3 @@ pid_t CAsyncDialogBox::getPID() const {
SP<CAsyncDialogBox> CAsyncDialogBox::lockSelf() { SP<CAsyncDialogBox> CAsyncDialogBox::lockSelf() {
return m_selfWeakReference.lock(); 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));
}

View file

@ -27,7 +27,6 @@ class CAsyncDialogBox {
void kill(); void kill();
bool isRunning() const; bool isRunning() const;
pid_t getPID() const; pid_t getPID() const;
void setExecRule(std::string&& s);
SP<CAsyncDialogBox> lockSelf(); SP<CAsyncDialogBox> lockSelf();
@ -43,7 +42,6 @@ class CAsyncDialogBox {
wl_event_source* m_readEventSource = nullptr; wl_event_source* m_readEventSource = nullptr;
Hyprutils::OS::CFileDescriptor m_pipeReadFd; 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_title;
const std::string m_description; const std::string m_description;

View file

@ -1,20 +0,0 @@
#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;
}

View file

@ -1,5 +0,0 @@
#pragma once
namespace DRM {
bool sameGpu(int fd1, int fd2);
}

View file

@ -6,186 +6,158 @@
#include <xf86drm.h> #include <xf86drm.h>
#include <drm_fourcc.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 = { inline const std::vector<SPixelFormat> GLES3_FORMATS = {
{ {
.drmFormat = DRM_FORMAT_ARGB8888, .drmFormat = DRM_FORMAT_ARGB8888,
.glInternalFormat = GL_RGBA8, .flipRB = true,
.glFormat = GL_RGBA, .glFormat = GL_RGBA,
.glType = GL_UNSIGNED_BYTE, .glType = GL_UNSIGNED_BYTE,
.withAlpha = true, .withAlpha = true,
.alphaStripped = DRM_FORMAT_XRGB8888, .alphaStripped = DRM_FORMAT_XRGB8888,
.bytesPerBlock = 4, .bytesPerBlock = 4,
.swizzle = {SWIZZLE_BGRA},
}, },
{ {
.drmFormat = DRM_FORMAT_XRGB8888, .drmFormat = DRM_FORMAT_XRGB8888,
.glInternalFormat = GL_RGBA8, .flipRB = true,
.glFormat = GL_RGBA, .glFormat = GL_RGBA,
.glType = GL_UNSIGNED_BYTE, .glType = GL_UNSIGNED_BYTE,
.withAlpha = false, .withAlpha = false,
.alphaStripped = DRM_FORMAT_XRGB8888, .alphaStripped = DRM_FORMAT_XRGB8888,
.bytesPerBlock = 4, .bytesPerBlock = 4,
.swizzle = {SWIZZLE_BGR1},
}, },
{ {
.drmFormat = DRM_FORMAT_XBGR8888, .drmFormat = DRM_FORMAT_XBGR8888,
.glInternalFormat = GL_RGBA8,
.glFormat = GL_RGBA, .glFormat = GL_RGBA,
.glType = GL_UNSIGNED_BYTE, .glType = GL_UNSIGNED_BYTE,
.withAlpha = false, .withAlpha = false,
.alphaStripped = DRM_FORMAT_XBGR8888, .alphaStripped = DRM_FORMAT_XBGR8888,
.bytesPerBlock = 4, .bytesPerBlock = 4,
.swizzle = {SWIZZLE_RGB1},
}, },
{ {
.drmFormat = DRM_FORMAT_ABGR8888, .drmFormat = DRM_FORMAT_ABGR8888,
.glInternalFormat = GL_RGBA8,
.glFormat = GL_RGBA, .glFormat = GL_RGBA,
.glType = GL_UNSIGNED_BYTE, .glType = GL_UNSIGNED_BYTE,
.withAlpha = true, .withAlpha = true,
.alphaStripped = DRM_FORMAT_XBGR8888, .alphaStripped = DRM_FORMAT_XBGR8888,
.bytesPerBlock = 4, .bytesPerBlock = 4,
.swizzle = {SWIZZLE_RGBA},
}, },
{ {
.drmFormat = DRM_FORMAT_BGR888, .drmFormat = DRM_FORMAT_BGR888,
.glInternalFormat = GL_RGB8,
.glFormat = GL_RGB, .glFormat = GL_RGB,
.glType = GL_UNSIGNED_BYTE, .glType = GL_UNSIGNED_BYTE,
.withAlpha = false, .withAlpha = false,
.alphaStripped = DRM_FORMAT_BGR888, .alphaStripped = DRM_FORMAT_BGR888,
.bytesPerBlock = 3, .bytesPerBlock = 3,
.swizzle = {SWIZZLE_RGB1},
}, },
{ {
.drmFormat = DRM_FORMAT_RGBX4444, .drmFormat = DRM_FORMAT_RGBX4444,
.glInternalFormat = GL_RGBA4,
.glFormat = GL_RGBA, .glFormat = GL_RGBA,
.glType = GL_UNSIGNED_SHORT_4_4_4_4, .glType = GL_UNSIGNED_SHORT_4_4_4_4,
.withAlpha = false, .withAlpha = false,
.alphaStripped = DRM_FORMAT_RGBX4444, .alphaStripped = DRM_FORMAT_RGBX4444,
.bytesPerBlock = 2, .bytesPerBlock = 2,
.swizzle = {SWIZZLE_RGB1},
}, },
{ {
.drmFormat = DRM_FORMAT_RGBA4444, .drmFormat = DRM_FORMAT_RGBA4444,
.glInternalFormat = GL_RGBA4,
.glFormat = GL_RGBA, .glFormat = GL_RGBA,
.glType = GL_UNSIGNED_SHORT_4_4_4_4, .glType = GL_UNSIGNED_SHORT_4_4_4_4,
.withAlpha = true, .withAlpha = true,
.alphaStripped = DRM_FORMAT_RGBX4444, .alphaStripped = DRM_FORMAT_RGBX4444,
.bytesPerBlock = 2, .bytesPerBlock = 2,
.swizzle = {SWIZZLE_RGBA},
}, },
{ {
.drmFormat = DRM_FORMAT_RGBX5551, .drmFormat = DRM_FORMAT_RGBX5551,
.glInternalFormat = GL_RGB5_A1,
.glFormat = GL_RGBA, .glFormat = GL_RGBA,
.glType = GL_UNSIGNED_SHORT_5_5_5_1, .glType = GL_UNSIGNED_SHORT_5_5_5_1,
.withAlpha = false, .withAlpha = false,
.alphaStripped = DRM_FORMAT_RGBX5551, .alphaStripped = DRM_FORMAT_RGBX5551,
.bytesPerBlock = 2, .bytesPerBlock = 2,
.swizzle = {SWIZZLE_RGB1},
}, },
{ {
.drmFormat = DRM_FORMAT_RGBA5551, .drmFormat = DRM_FORMAT_RGBA5551,
.glInternalFormat = GL_RGB5_A1,
.glFormat = GL_RGBA, .glFormat = GL_RGBA,
.glType = GL_UNSIGNED_SHORT_5_5_5_1, .glType = GL_UNSIGNED_SHORT_5_5_5_1,
.withAlpha = true, .withAlpha = true,
.alphaStripped = DRM_FORMAT_RGBX5551, .alphaStripped = DRM_FORMAT_RGBX5551,
.bytesPerBlock = 2, .bytesPerBlock = 2,
.swizzle = {SWIZZLE_RGBA},
}, },
{ {
.drmFormat = DRM_FORMAT_RGB565, .drmFormat = DRM_FORMAT_RGB565,
.glInternalFormat = GL_RGB565,
.glFormat = GL_RGB, .glFormat = GL_RGB,
.glType = GL_UNSIGNED_SHORT_5_6_5, .glType = GL_UNSIGNED_SHORT_5_6_5,
.withAlpha = false, .withAlpha = false,
.alphaStripped = DRM_FORMAT_RGB565, .alphaStripped = DRM_FORMAT_RGB565,
.bytesPerBlock = 2, .bytesPerBlock = 2,
.swizzle = {SWIZZLE_RGB1},
}, },
{ {
.drmFormat = DRM_FORMAT_XBGR2101010, .drmFormat = DRM_FORMAT_XBGR2101010,
.glInternalFormat = GL_RGB10_A2,
.glFormat = GL_RGBA, .glFormat = GL_RGBA,
.glType = GL_UNSIGNED_INT_2_10_10_10_REV, .glType = GL_UNSIGNED_INT_2_10_10_10_REV,
.withAlpha = false, .withAlpha = false,
.alphaStripped = DRM_FORMAT_XBGR2101010, .alphaStripped = DRM_FORMAT_XBGR2101010,
.bytesPerBlock = 4, .bytesPerBlock = 4,
.swizzle = {SWIZZLE_RGB1},
}, },
{ {
.drmFormat = DRM_FORMAT_ABGR2101010, .drmFormat = DRM_FORMAT_ABGR2101010,
.glInternalFormat = GL_RGB10_A2,
.glFormat = GL_RGBA, .glFormat = GL_RGBA,
.glType = GL_UNSIGNED_INT_2_10_10_10_REV, .glType = GL_UNSIGNED_INT_2_10_10_10_REV,
.withAlpha = true, .withAlpha = true,
.alphaStripped = DRM_FORMAT_XBGR2101010, .alphaStripped = DRM_FORMAT_XBGR2101010,
.bytesPerBlock = 4, .bytesPerBlock = 4,
.swizzle = {SWIZZLE_RGBA},
}, },
{ {
.drmFormat = DRM_FORMAT_XRGB2101010, .drmFormat = DRM_FORMAT_XRGB2101010,
.glInternalFormat = GL_RGB10_A2,
.glFormat = GL_RGBA, .glFormat = GL_RGBA,
.glType = GL_UNSIGNED_INT_2_10_10_10_REV, .glType = GL_UNSIGNED_INT_2_10_10_10_REV,
.withAlpha = false, .withAlpha = false,
.alphaStripped = DRM_FORMAT_XRGB2101010, .alphaStripped = DRM_FORMAT_XRGB2101010,
.bytesPerBlock = 4, .bytesPerBlock = 4,
.swizzle = {SWIZZLE_BGR1},
}, },
{ {
.drmFormat = DRM_FORMAT_ARGB2101010, .drmFormat = DRM_FORMAT_ARGB2101010,
.glInternalFormat = GL_RGB10_A2,
.glFormat = GL_RGBA, .glFormat = GL_RGBA,
.glType = GL_UNSIGNED_INT_2_10_10_10_REV, .glType = GL_UNSIGNED_INT_2_10_10_10_REV,
.withAlpha = true, .withAlpha = true,
.alphaStripped = DRM_FORMAT_XRGB2101010, .alphaStripped = DRM_FORMAT_XRGB2101010,
.bytesPerBlock = 4, .bytesPerBlock = 4,
.swizzle = {SWIZZLE_BGRA},
}, },
{ {
.drmFormat = DRM_FORMAT_XBGR16161616F, .drmFormat = DRM_FORMAT_XBGR16161616F,
.glInternalFormat = GL_RGBA16F,
.glFormat = GL_RGBA, .glFormat = GL_RGBA,
.glType = GL_HALF_FLOAT, .glType = GL_HALF_FLOAT,
.withAlpha = false, .withAlpha = false,
.alphaStripped = DRM_FORMAT_XBGR16161616F, .alphaStripped = DRM_FORMAT_XBGR16161616F,
.bytesPerBlock = 8, .bytesPerBlock = 8,
.swizzle = {SWIZZLE_RGB1},
}, },
{ {
.drmFormat = DRM_FORMAT_ABGR16161616F, .drmFormat = DRM_FORMAT_ABGR16161616F,
.glInternalFormat = GL_RGBA16F,
.glFormat = GL_RGBA, .glFormat = GL_RGBA,
.glType = GL_HALF_FLOAT, .glType = GL_HALF_FLOAT,
.withAlpha = true, .withAlpha = true,
.alphaStripped = DRM_FORMAT_XBGR16161616F, .alphaStripped = DRM_FORMAT_XBGR16161616F,
.bytesPerBlock = 8, .bytesPerBlock = 8,
.swizzle = {SWIZZLE_RGBA},
}, },
{ {
.drmFormat = DRM_FORMAT_XBGR16161616, .drmFormat = DRM_FORMAT_XBGR16161616,
.glInternalFormat = GL_RGBA16UI, .glFormat = GL_RGBA16UI,
.glFormat = GL_RGBA_INTEGER,
.glType = GL_UNSIGNED_SHORT, .glType = GL_UNSIGNED_SHORT,
.withAlpha = false, .withAlpha = false,
.alphaStripped = DRM_FORMAT_XBGR16161616, .alphaStripped = DRM_FORMAT_XBGR16161616,
.bytesPerBlock = 8, .bytesPerBlock = 8,
.swizzle = {SWIZZLE_RGBA},
}, },
{ {
.drmFormat = DRM_FORMAT_ABGR16161616, .drmFormat = DRM_FORMAT_ABGR16161616,
.glInternalFormat = GL_RGBA16UI, .glFormat = GL_RGBA16UI,
.glFormat = GL_RGBA_INTEGER,
.glType = GL_UNSIGNED_SHORT, .glType = GL_UNSIGNED_SHORT,
.withAlpha = true, .withAlpha = true,
.alphaStripped = DRM_FORMAT_XBGR16161616, .alphaStripped = DRM_FORMAT_XBGR16161616,
.bytesPerBlock = 8, .bytesPerBlock = 8,
.swizzle = {SWIZZLE_RGBA},
}, },
{ {
.drmFormat = DRM_FORMAT_YVYU, .drmFormat = DRM_FORMAT_YVYU,
@ -199,27 +171,23 @@ inline const std::vector<SPixelFormat> GLES3_FORMATS = {
}, },
{ {
.drmFormat = DRM_FORMAT_R8, .drmFormat = DRM_FORMAT_R8,
.glInternalFormat = GL_R8,
.glFormat = GL_RED,
.glType = GL_UNSIGNED_BYTE,
.bytesPerBlock = 1, .bytesPerBlock = 1,
.swizzle = {SWIZZLE_R001},
}, },
{ {
.drmFormat = DRM_FORMAT_GR88, .drmFormat = DRM_FORMAT_GR88,
.glInternalFormat = GL_RG8,
.glFormat = GL_RG,
.glType = GL_UNSIGNED_BYTE,
.bytesPerBlock = 2, .bytesPerBlock = 2,
.swizzle = {SWIZZLE_RG01},
}, },
{ {
.drmFormat = DRM_FORMAT_RGB888, .drmFormat = DRM_FORMAT_RGB888,
.glInternalFormat = GL_RGB8,
.glFormat = GL_RGB,
.glType = GL_UNSIGNED_BYTE,
.bytesPerBlock = 3, .bytesPerBlock = 3,
.swizzle = {SWIZZLE_BGR1}, },
{
.drmFormat = DRM_FORMAT_BGR888,
.bytesPerBlock = 3,
},
{
.drmFormat = DRM_FORMAT_RGBX4444,
.bytesPerBlock = 2,
}, },
}; };
@ -261,26 +229,6 @@ const SPixelFormat* NFormatUtils::getPixelFormatFromGL(uint32_t glFormat, uint32
return nullptr; 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) { bool NFormatUtils::isFormatOpaque(DRMFormat drm) {
const auto FMT = NFormatUtils::getPixelFormatFromDRM(drm); const auto FMT = NFormatUtils::getPixelFormatFromDRM(drm);
if (!FMT) if (!FMT)
@ -297,12 +245,24 @@ int NFormatUtils::minStride(const SPixelFormat* const fmt, int32_t width) {
return std::ceil((width * fmt->bytesPerBlock) / pixelsPerBlock(fmt)); 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) { std::string NFormatUtils::drmFormatName(DRMFormat drm) {
auto n = drmGetFormatName(drm); auto n = drmGetFormatName(drm);
if (!n)
return "unknown";
std::string name = n; std::string name = n;
free(n); // NOLINT(cppcoreguidelines-no-malloc,-warnings-as-errors) free(n); // NOLINT(cppcoreguidelines-no-malloc,-warnings-as-errors)
return name; return name;
@ -310,25 +270,7 @@ std::string NFormatUtils::drmFormatName(DRMFormat drm) {
std::string NFormatUtils::drmModifierName(uint64_t mod) { std::string NFormatUtils::drmModifierName(uint64_t mod) {
auto n = drmGetFormatModifierName(mod); auto n = drmGetFormatModifierName(mod);
if (!n)
return "unknown";
std::string name = n; std::string name = n;
free(n); // NOLINT(cppcoreguidelines-no-malloc,-warnings-as-errors) free(n); // NOLINT(cppcoreguidelines-no-malloc,-warnings-as-errors)
return name; 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;
}
}

View file

@ -2,35 +2,15 @@
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#include <GLES3/gl32.h>
#include "math/Math.hpp" #include "math/Math.hpp"
#include <aquamarine/backend/Misc.hpp> #include <aquamarine/backend/Misc.hpp>
using DRMFormat = uint32_t; using DRMFormat = uint32_t;
using SHMFormat = 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 { struct SPixelFormat {
DRMFormat drmFormat = 0; /* DRM_FORMAT_INVALID */ DRMFormat drmFormat = 0; /* DRM_FORMAT_INVALID */
bool flipRB = false;
int glInternalFormat = 0; int glInternalFormat = 0;
int glFormat = 0; int glFormat = 0;
int glType = 0; int glType = 0;
@ -38,7 +18,6 @@ struct SPixelFormat {
DRMFormat alphaStripped = 0; /* DRM_FORMAT_INVALID */ DRMFormat alphaStripped = 0; /* DRM_FORMAT_INVALID */
uint32_t bytesPerBlock = 0; uint32_t bytesPerBlock = 0;
Vector2D blockSize; Vector2D blockSize;
std::optional<std::array<GLint, 4>> swizzle = std::nullopt;
}; };
using SDRMFormat = Aquamarine::SDRMFormat; using SDRMFormat = Aquamarine::SDRMFormat;
@ -49,11 +28,11 @@ namespace NFormatUtils {
const SPixelFormat* getPixelFormatFromDRM(DRMFormat drm); const SPixelFormat* getPixelFormatFromDRM(DRMFormat drm);
const SPixelFormat* getPixelFormatFromGL(uint32_t glFormat, uint32_t glType, bool alpha); const SPixelFormat* getPixelFormatFromGL(uint32_t glFormat, uint32_t glType, bool alpha);
bool isFormatYUV(uint32_t drmFormat);
bool isFormatOpaque(DRMFormat drm); bool isFormatOpaque(DRMFormat drm);
int pixelsPerBlock(const SPixelFormat* const fmt); int pixelsPerBlock(const SPixelFormat* const fmt);
int minStride(const SPixelFormat* const fmt, int32_t width); 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 drmFormatName(DRMFormat drm);
std::string drmModifierName(uint64_t mod); std::string drmModifierName(uint64_t mod);
DRMFormat alphaFormat(DRMFormat prevFormat);
}; };

View file

@ -11,7 +11,10 @@ static int onDataRead(int fd, uint32_t mask, void* data) {
CMainLoopExecutor::CMainLoopExecutor(std::function<void()>&& callback) : m_fn(std::move(callback)) { CMainLoopExecutor::CMainLoopExecutor(std::function<void()>&& callback) : m_fn(std::move(callback)) {
int fds[2]; int fds[2];
RASSERT(pipe(fds) == 0, "CMainLoopExecutor: failed to open a pipe"); pipe(fds);
RASSERT(fds[0] != 0, "CMainLoopExecutor: failed to open a pipe");
RASSERT(fds[1] != 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); m_event = wl_event_loop_add_fd(g_pEventLoopManager->m_wayland.loop, fds[0], WL_EVENT_READABLE, ::onDataRead, this);

View file

@ -2,7 +2,6 @@
#include "MiscFunctions.hpp" #include "MiscFunctions.hpp"
#include "../macros.hpp" #include "../macros.hpp"
#include "SharedDefs.hpp" #include "SharedDefs.hpp"
#include "../helpers/TransferFunction.hpp"
#include "math/Math.hpp" #include "math/Math.hpp"
#include "../protocols/ColorManagement.hpp" #include "../protocols/ColorManagement.hpp"
#include "../Compositor.hpp" #include "../Compositor.hpp"
@ -23,20 +22,16 @@
#include "../protocols/core/DataDevice.hpp" #include "../protocols/core/DataDevice.hpp"
#include "../render/Renderer.hpp" #include "../render/Renderer.hpp"
#include "../managers/EventManager.hpp" #include "../managers/EventManager.hpp"
#include "../managers/screenshare/ScreenshareManager.hpp" #include "../managers/LayoutManager.hpp"
#include "../managers/animation/AnimationManager.hpp" #include "../managers/animation/AnimationManager.hpp"
#include "../managers/animation/DesktopAnimationManager.hpp" #include "../managers/animation/DesktopAnimationManager.hpp"
#include "../managers/input/InputManager.hpp" #include "../managers/input/InputManager.hpp"
#include "../hyprerror/HyprError.hpp" #include "../hyprerror/HyprError.hpp"
#include "../layout/LayoutManager.hpp"
#include "../i18n/Engine.hpp" #include "../i18n/Engine.hpp"
#include "../helpers/cm/ColorManagement.hpp"
#include "sync/SyncTimeline.hpp" #include "sync/SyncTimeline.hpp"
#include "time/Time.hpp" #include "time/Time.hpp"
#include "../desktop/view/LayerSurface.hpp" #include "../desktop/view/LayerSurface.hpp"
#include "../desktop/state/FocusState.hpp" #include "../desktop/state/FocusState.hpp"
#include "../event/EventBus.hpp"
#include "Drm.hpp"
#include <aquamarine/output/Output.hpp> #include <aquamarine/output/Output.hpp>
#include "debug/log/Logger.hpp" #include "debug/log/Logger.hpp"
#include "debug/HyprNotificationOverlay.hpp" #include "debug/HyprNotificationOverlay.hpp"
@ -55,7 +50,7 @@ using namespace Hyprutils::OS;
using enum NContentType::eContentType; using enum NContentType::eContentType;
using namespace NColorManagement; using namespace NColorManagement;
CMonitor::CMonitor(SP<Aquamarine::IOutput> output_) : m_state(this), m_output(output_), m_imageDescription(DEFAULT_IMAGE_DESCRIPTION) { CMonitor::CMonitor(SP<Aquamarine::IOutput> output_) : m_state(this), m_output(output_) {
g_pAnimationManager->createAnimation(0.f, m_specialFade, g_pConfigManager->getAnimationPropertyConfig("specialWorkspaceIn"), AVARDAMAGE_NONE); g_pAnimationManager->createAnimation(0.f, m_specialFade, g_pConfigManager->getAnimationPropertyConfig("specialWorkspaceIn"), AVARDAMAGE_NONE);
m_specialFade->setUpdateCallback([this](auto) { g_pHyprRenderer->damageMonitor(m_self.lock()); }); m_specialFade->setUpdateCallback([this](auto) { g_pHyprRenderer->damageMonitor(m_self.lock()); });
static auto PZOOMFACTOR = CConfigValue<Hyprlang::FLOAT>("cursor:zoom_factor"); static auto PZOOMFACTOR = CConfigValue<Hyprlang::FLOAT>("cursor:zoom_factor");
@ -76,27 +71,23 @@ CMonitor::~CMonitor() {
} }
void CMonitor::onConnect(bool noRule) { void CMonitor::onConnect(bool noRule) {
Event::bus()->m_events.monitor.preAdded.emit(m_self.lock()); EMIT_HOOK_EVENT("preMonitorAdded", m_self.lock());
CScopeGuard x = {[]() { g_pCompositor->arrangeMonitors(); }}; CScopeGuard x = {[]() { g_pCompositor->arrangeMonitors(); }};
m_zoomAnimProgress->setValueAndWarp(0.F); m_zoomAnimProgress->setValueAndWarp(0.F);
m_zoomAnimFrameCounter = 0; m_zoomAnimFrameCounter = 0;
g_pEventLoopManager->doLater([] { g_pEventLoopManager->doLater([] { g_pConfigManager->ensurePersistentWorkspacesPresent(); });
g_pConfigManager->ensurePersistentWorkspacesPresent();
g_pCompositor->ensureWorkspacesOnAssignedMonitors();
});
m_listeners.frame = m_output->events.frame.listen([this] { m_listeners.frame = m_output->events.frame.listen([this] {
if (m_frameScheduler) if (m_frameScheduler)
m_frameScheduler->onFrame(); m_frameScheduler->onFrame();
}); });
m_listeners.commit = m_output->events.commit.listen([this] { m_listeners.commit = m_output->events.commit.listen([this] {
m_events.commit.emit(); if (true) { // FIXME: E->state->committed & WLR_OUTPUT_STATE_BUFFER
PROTO::screencopy->onOutputCommit(m_self.lock());
// FIXME: E->state->committed & WLR_OUTPUT_STATE_BUFFER PROTO::toplevelExport->onOutputCommit(m_self.lock());
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); }); m_listeners.needsFrame = m_output->events.needsFrame.listen([this] { g_pCompositor->scheduleFrameForMonitor(m_self.lock(), Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME); });
@ -123,12 +114,10 @@ void CMonitor::onConnect(bool noRule) {
ts = nullptr; ts = nullptr;
} }
if (!ts) { if (!ts)
timespec mono{}; PROTO::presentation->onPresented(m_self.lock(), Time::steadyNow(), event.refresh, event.seq, event.flags);
clock_gettime(CLOCK_MONOTONIC, &mono); else
PROTO::presentation->onPresented(m_self.lock(), mono, event.refresh, event.seq, event.flags & ~Aquamarine::IOutput::AQ_OUTPUT_PRESENT_HW_CLOCK); PROTO::presentation->onPresented(m_self.lock(), Time::fromTimespec(event.when), event.refresh, event.seq, event.flags);
} else
PROTO::presentation->onPresented(m_self.lock(), *ts, event.refresh, event.seq, event.flags);
if (m_zoomAnimFrameCounter < 5) { if (m_zoomAnimFrameCounter < 5) {
m_zoomAnimFrameCounter++; m_zoomAnimFrameCounter++;
@ -293,15 +282,9 @@ void CMonitor::onConnect(bool noRule) {
if (!valid(ws)) if (!valid(ws))
continue; continue;
const auto CURRENTMON = ws->m_monitor.lock(); if (ws->m_lastMonitor == m_name || g_pCompositor->m_monitors.size() == 1 /* avoid lost workspaces on recover */) {
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_pCompositor->moveWorkspaceToMonitor(ws, m_self.lock());
g_pDesktopAnimationManager->startAnimation(ws, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true); g_pDesktopAnimationManager->startAnimation(ws, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true);
if (RETURNING)
ws->m_lastMonitor = ""; ws->m_lastMonitor = "";
} }
} }
@ -320,7 +303,7 @@ void CMonitor::onConnect(bool noRule) {
Desktop::focusState()->rawMonitorFocus(m_self.lock()); Desktop::focusState()->rawMonitorFocus(m_self.lock());
g_pHyprRenderer->arrangeLayersForMonitor(m_id); g_pHyprRenderer->arrangeLayersForMonitor(m_id);
g_layoutManager->recalculateMonitor(m_self.lock()); g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id);
// ensure VRR (will enable if necessary) // ensure VRR (will enable if necessary)
g_pConfigManager->ensureVRR(m_self.lock()); g_pConfigManager->ensureVRR(m_self.lock());
@ -358,17 +341,17 @@ void CMonitor::onConnect(bool noRule) {
g_pEventManager->postEvent(SHyprIPCEvent{"monitoradded", m_name}); g_pEventManager->postEvent(SHyprIPCEvent{"monitoradded", m_name});
g_pEventManager->postEvent(SHyprIPCEvent{"monitoraddedv2", std::format("{},{},{}", m_id, m_name, m_shortDescription)}); g_pEventManager->postEvent(SHyprIPCEvent{"monitoraddedv2", std::format("{},{},{}", m_id, m_name, m_shortDescription)});
Event::bus()->m_events.monitor.added.emit(m_self.lock()); EMIT_HOOK_EVENT("monitorAdded", m_self.lock());
} }
void CMonitor::onDisconnect(bool destroy) { void CMonitor::onDisconnect(bool destroy) {
Event::bus()->m_events.monitor.preRemoved.emit(m_self.lock()); EMIT_HOOK_EVENT("preMonitorRemoved", m_self.lock());
CScopeGuard x = {[this]() { CScopeGuard x = {[this]() {
if (g_pCompositor->m_isShuttingDown) if (g_pCompositor->m_isShuttingDown)
return; return;
g_pEventManager->postEvent(SHyprIPCEvent{"monitorremoved", m_name}); g_pEventManager->postEvent(SHyprIPCEvent{"monitorremoved", m_name});
g_pEventManager->postEvent(SHyprIPCEvent{"monitorremovedv2", std::format("{},{},{}", m_id, m_name, m_shortDescription)}); g_pEventManager->postEvent(SHyprIPCEvent{"monitorremovedv2", std::format("{},{},{}", m_id, m_name, m_shortDescription)});
Event::bus()->m_events.monitor.removed.emit(m_self.lock()); EMIT_HOOK_EVENT("monitorRemoved", m_self.lock());
g_pCompositor->scheduleMonitorStateRecheck(); g_pCompositor->scheduleMonitorStateRecheck();
}}; }};
@ -428,6 +411,7 @@ void CMonitor::onDisconnect(bool destroy) {
m_layerSurfaceLayers[i].clear(); 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); Log::logger->log(Log::DEBUG, "Removed monitor {}!", m_name);
if (!BACKUPMON) { if (!BACKUPMON) {
@ -438,24 +422,19 @@ void CMonitor::onDisconnect(bool destroy) {
m_enabled = false; m_enabled = false;
m_renderingInitPassed = false; m_renderingInitPassed = false;
if (BACKUPMON) {
// snap cursor
g_pCompositor->warpCursorTo(BACKUPMON->m_position + BACKUPMON->m_transformedSize / 2.F, true);
// move workspaces
std::vector<PHLWORKSPACE> wspToMove; std::vector<PHLWORKSPACE> wspToMove;
for (auto const& w : g_pCompositor->getWorkspaces()) { for (auto const& w : g_pCompositor->getWorkspaces()) {
if (w->m_monitor == m_self || !w->m_monitor) if (w->m_monitor == m_self || !w->m_monitor)
wspToMove.emplace_back(w.lock()); 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) { for (auto const& w : wspToMove) {
if (w && w->m_lastMonitor.empty())
w->m_lastMonitor = m_name; w->m_lastMonitor = m_name;
}
if (BACKUPMON) {
// snap cursor
g_pCompositor->warpCursorTo(BACKUPMON->m_position + BACKUPMON->m_transformedSize / 2.F, true);
for (auto const& w : wspToMove) {
g_pCompositor->moveWorkspaceToMonitor(w, BACKUPMON); g_pCompositor->moveWorkspaceToMonitor(w, BACKUPMON);
g_pDesktopAnimationManager->startAnimation(w, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true); g_pDesktopAnimationManager->startAnimation(w, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true);
} }
@ -484,7 +463,7 @@ void CMonitor::onDisconnect(bool destroy) {
PHLMONITOR pMonitorMostHz = nullptr; PHLMONITOR pMonitorMostHz = nullptr;
for (auto const& m : g_pCompositor->m_monitors) { for (auto const& m : g_pCompositor->m_monitors) {
if (m->m_refreshRate > mostHz && m != m_self) { if (m->m_refreshRate > mostHz) {
pMonitorMostHz = m; pMonitorMostHz = m;
mostHz = m->m_refreshRate; mostHz = m->m_refreshRate;
} }
@ -492,118 +471,78 @@ void CMonitor::onDisconnect(bool destroy) {
g_pHyprRenderer->m_mostHzMonitor = pMonitorMostHz; g_pHyprRenderer->m_mostHzMonitor = pMonitorMostHz;
} }
std::erase_if(g_pCompositor->m_monitors, [&](PHLMONITOR& el) { return el.get() == this; });
} }
static NColorManagement::eTransferFunction chooseTF(NTransferFunction::eTF tf) { void CMonitor::applyCMType(NCMType::eCMType cmType, int cmSdrEotf) {
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; auto oldImageDescription = m_imageDescription;
const auto chosenSdrEotf = chooseTF(cmSdrEotf); 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) :
const auto masteringPrimaries = getMasteringPrimaries(); (cmSdrEotf == 1 ? NColorManagement::CM_TRANSFER_FUNCTION_SRGB : NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22);
const NColorManagement::SImageDescription::SPCMasteringLuminances masteringLuminances = getMasteringLuminances();
const auto maxFALL = this->maxFALL();
const auto maxCLL = this->maxCLL();
switch (cmType) { switch (cmType) {
case NCMType::CM_SRGB: case NCMType::CM_SRGB: m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf}); break; // assumes SImageDescription defaults to sRGB
m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf,
.primariesNamed = NColorManagement::CM_PRIMARIES_SRGB,
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_SRGB)});
break; // assumes SImageDescription defaults to sRGB
case NCMType::CM_WIDE: case NCMType::CM_WIDE:
m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf, m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf,
.primariesNameSet = true, .primariesNameSet = true,
.primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020,
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020), .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020)});
.masteringPrimaries = masteringPrimaries,
.masteringLuminances = masteringLuminances,
.maxCLL = maxCLL,
.maxFALL = maxFALL});
break; break;
case NCMType::CM_DCIP3: case NCMType::CM_DCIP3:
m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf, m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf,
.primariesNameSet = true, .primariesNameSet = true,
.primariesNamed = NColorManagement::CM_PRIMARIES_DCI_P3, .primariesNamed = NColorManagement::CM_PRIMARIES_DCI_P3,
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_DCI_P3), .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_DCI_P3)});
.masteringPrimaries = masteringPrimaries,
.masteringLuminances = masteringLuminances,
.maxCLL = maxCLL,
.maxFALL = maxFALL});
break; break;
case NCMType::CM_DP3: case NCMType::CM_DP3:
m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf, m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf,
.primariesNameSet = true, .primariesNameSet = true,
.primariesNamed = NColorManagement::CM_PRIMARIES_DISPLAY_P3, .primariesNamed = NColorManagement::CM_PRIMARIES_DISPLAY_P3,
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_DISPLAY_P3), .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_DISPLAY_P3)});
.masteringPrimaries = masteringPrimaries,
.masteringLuminances = masteringLuminances,
.maxCLL = maxCLL,
.maxFALL = maxFALL});
break; break;
case NCMType::CM_ADOBE: case NCMType::CM_ADOBE:
m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf, m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf,
.primariesNameSet = true, .primariesNameSet = true,
.primariesNamed = NColorManagement::CM_PRIMARIES_ADOBE_RGB, .primariesNamed = NColorManagement::CM_PRIMARIES_ADOBE_RGB,
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_ADOBE_RGB), .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_ADOBE_RGB)});
.masteringPrimaries = masteringPrimaries,
.masteringLuminances = masteringLuminances,
.maxCLL = maxCLL,
.maxFALL = maxFALL});
break; break;
case NCMType::CM_EDID: case NCMType::CM_EDID:
m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf, m_imageDescription =
CImageDescription::from({.transferFunction = chosenSdrEotf,
.primariesNameSet = false, .primariesNameSet = false,
.primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020,
.primaries = masteringPrimaries, .primaries = {
.masteringPrimaries = masteringPrimaries, .red = {.x = m_output->parsedEDID.chromaticityCoords->red.x, .y = m_output->parsedEDID.chromaticityCoords->red.y},
.masteringLuminances = masteringLuminances, .green = {.x = m_output->parsedEDID.chromaticityCoords->green.x, .y = m_output->parsedEDID.chromaticityCoords->green.y},
.maxCLL = maxCLL, .blue = {.x = m_output->parsedEDID.chromaticityCoords->blue.x, .y = m_output->parsedEDID.chromaticityCoords->blue.y},
.maxFALL = maxFALL}); .white = {.x = m_output->parsedEDID.chromaticityCoords->white.x, .y = m_output->parsedEDID.chromaticityCoords->white.y},
}});
break; break;
case NCMType::CM_HDR: m_imageDescription = DEFAULT_HDR_IMAGE_DESCRIPTION; break; case NCMType::CM_HDR: m_imageDescription = DEFAULT_HDR_IMAGE_DESCRIPTION; break;
case NCMType::CM_HDR_EDID: case NCMType::CM_HDR_EDID:
m_imageDescription = CImageDescription::from( m_imageDescription =
{.transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ, CImageDescription::from({.transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ,
.primariesNameSet = false, .primariesNameSet = false,
.primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020,
.primaries = m_output->parsedEDID.chromaticityCoords.has_value() ? masteringPrimaries : NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020), .primaries = m_output->parsedEDID.chromaticityCoords.has_value() ?
.masteringPrimaries = masteringPrimaries, NColorManagement::SPCPRimaries{
.luminances = {.min = DEFAULT_HDR_IMAGE_DESCRIPTION->value().getTFMinLuminance(), .red = {.x = m_output->parsedEDID.chromaticityCoords->red.x, .y = m_output->parsedEDID.chromaticityCoords->red.y},
.max = DEFAULT_HDR_IMAGE_DESCRIPTION->value().getTFMaxLuminance(), .green = {.x = m_output->parsedEDID.chromaticityCoords->green.x, .y = m_output->parsedEDID.chromaticityCoords->green.y},
.reference = DEFAULT_HDR_IMAGE_DESCRIPTION->value().getTFRefLuminance()}, .blue = {.x = m_output->parsedEDID.chromaticityCoords->blue.x, .y = m_output->parsedEDID.chromaticityCoords->blue.y},
.masteringLuminances = masteringLuminances, .white = {.x = m_output->parsedEDID.chromaticityCoords->white.x, .y = m_output->parsedEDID.chromaticityCoords->white.y},
.maxCLL = maxCLL, } :
.maxFALL = maxFALL}); 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}});
break; break;
default: UNREACHABLE(); default: UNREACHABLE();
} }
if ((m_minLuminance >= 0 || m_maxLuminance >= 0 || m_maxAvgLuminance >= 0) && (cmType == NCMType::CM_HDR || cmType == NCMType::CM_HDR_EDID)) if (m_minLuminance >= 0 || m_maxLuminance >= 0 || m_maxAvgLuminance >= 0)
m_imageDescription = m_imageDescription->with({ m_imageDescription = m_imageDescription->with({
.min = m_minLuminance >= 0 ? m_minLuminance : m_imageDescription->value().luminances.min, // .min = m_minLuminance >= 0 ? m_minLuminance : m_imageDescription->value().luminances.min, //
.max = m_maxLuminance >= 0 ? m_maxLuminance : m_imageDescription->value().luminances.max, // .max = m_maxLuminance >= 0 ? m_maxLuminance : m_imageDescription->value().luminances.max, //
.reference = m_imageDescription->value().luminances.reference // .reference = m_maxAvgLuminance >= 0 ? m_maxAvgLuminance : m_imageDescription->value().luminances.reference //
}); });
if (oldImageDescription != m_imageDescription) { if (oldImageDescription != m_imageDescription) {
@ -933,9 +872,6 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) {
m_supportsWideColor = RULE->supportsHDR; m_supportsWideColor = RULE->supportsHDR;
m_supportsHDR = RULE->supportsHDR; m_supportsHDR = RULE->supportsHDR;
if (RULE->iccFile.empty()) {
// only apply explicit cm settings if we have no icc file
m_cmType = RULE->cmType; m_cmType = RULE->cmType;
switch (m_cmType) { switch (m_cmType) {
case NCMType::CM_AUTO: m_cmType = m_enabled10bit && supportsWideColor() ? NCMType::CM_WIDE : NCMType::CM_SRGB; break; case NCMType::CM_AUTO: m_cmType = m_enabled10bit && supportsWideColor() ? NCMType::CM_WIDE : NCMType::CM_SRGB; break;
@ -958,20 +894,6 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) {
m_sdrSaturation = RULE->sdrSaturation; m_sdrSaturation = RULE->sdrSaturation;
m_sdrBrightness = RULE->sdrBrightness; 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{});
}
}
}
Vector2D logicalSize = m_pixelSize / m_scale; Vector2D logicalSize = m_pixelSize / m_scale;
if (!*PDISABLESCALECHECKS && (logicalSize.x != std::round(logicalSize.x) || logicalSize.y != std::round(logicalSize.y))) { if (!*PDISABLESCALECHECKS && (logicalSize.x != std::round(logicalSize.x) || logicalSize.y != std::round(logicalSize.y))) {
@ -1054,8 +976,6 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) {
m_damage.setSize(m_transformedSize); m_damage.setSize(m_transformedSize);
updateVCGTRamps();
// Set scale for all surfaces on this monitor, needed for some clients // Set scale for all surfaces on this monitor, needed for some clients
// but not on unsafe state to avoid crashes // but not on unsafe state to avoid crashes
if (!g_pCompositor->m_unsafeState) { if (!g_pCompositor->m_unsafeState) {
@ -1072,7 +992,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, 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)); sc<int>(m_transform), m_position, sc<int>(m_enabled10bit));
Event::bus()->m_events.monitor.layoutChanged.emit(); EMIT_HOOK_EVENT("monitorLayoutChanged", nullptr);
m_events.modeChanged.emit(); m_events.modeChanged.emit();
@ -1106,10 +1026,8 @@ bool CMonitor::shouldSkipScheduleFrameOnMouseEvent() {
static auto PMINRR = CConfigValue<Hyprlang::INT>("cursor:min_refresh_rate"); static auto PMINRR = CConfigValue<Hyprlang::INT>("cursor:min_refresh_rate");
// skip scheduling extra frames for fullsreen apps with vrr // skip scheduling extra frames for fullsreen apps with vrr
const auto FS_WINDOW = getFullscreenWindow(); const bool shouldSkip = inFullscreenMode() && (*PNOBREAK == 1 || (*PNOBREAK == 2 && m_activeWorkspace->getFullscreenWindow()->getContentType() == CONTENT_TYPE_GAME)) &&
const bool shouldRenderCursor = g_pHyprRenderer->shouldRenderCursor(); m_output->state->state().adaptiveSync;
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 // keep requested minimum refresh rate
if (shouldSkip && *PMINRR && m_lastPresentationTimer.getMillis() > 1000.0f / *PMINRR) { if (shouldSkip && *PMINRR && m_lastPresentationTimer.getMillis() > 1000.0f / *PMINRR) {
@ -1178,7 +1096,7 @@ void CMonitor::setupDefaultWS(const SMonitorRule& monitorRule) {
// workspace exists, move it to the newly connected monitor // workspace exists, move it to the newly connected monitor
g_pCompositor->moveWorkspaceToMonitor(PNEWWORKSPACE, m_self.lock()); g_pCompositor->moveWorkspaceToMonitor(PNEWWORKSPACE, m_self.lock());
m_activeWorkspace = PNEWWORKSPACE; m_activeWorkspace = PNEWWORKSPACE;
g_layoutManager->recalculateMonitor(m_self.lock()); g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id);
g_pDesktopAnimationManager->startAnimation(PNEWWORKSPACE, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true); g_pDesktopAnimationManager->startAnimation(PNEWWORKSPACE, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true);
} else { } else {
if (newDefaultWorkspaceName.empty()) if (newDefaultWorkspaceName.empty())
@ -1362,7 +1280,7 @@ void CMonitor::changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal, bo
// move pinned windows // move pinned windows
for (auto const& w : g_pCompositor->m_windows) { for (auto const& w : g_pCompositor->m_windows) {
if (w->m_workspace == POLDWORKSPACE && w->m_pinned) if (w->m_workspace == POLDWORKSPACE && w->m_pinned)
w->layoutTarget()->assignToSpace(pWorkspace->m_space); w->moveToWorkspace(pWorkspace);
} }
if (!noFocus && !Desktop::focusState()->monitor()->m_activeSpecialWorkspace && if (!noFocus && !Desktop::focusState()->monitor()->m_activeSpecialWorkspace &&
@ -1382,17 +1300,17 @@ void CMonitor::changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal, bo
pWindow = pWorkspace->getFirstWindow(); pWindow = pWorkspace->getFirstWindow();
} }
Desktop::focusState()->fullWindowFocus(pWindow, Desktop::FOCUS_REASON_KEYBIND); Desktop::focusState()->fullWindowFocus(pWindow);
} }
if (!noMouseMove) if (!noMouseMove)
g_pInputManager->simulateMouseMovement(); g_pInputManager->simulateMouseMovement();
g_layoutManager->recalculateMonitor(m_self.lock()); g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id);
g_pEventManager->postEvent(SHyprIPCEvent{"workspace", pWorkspace->m_name}); g_pEventManager->postEvent(SHyprIPCEvent{"workspace", pWorkspace->m_name});
g_pEventManager->postEvent(SHyprIPCEvent{"workspacev2", std::format("{},{}", pWorkspace->m_id, pWorkspace->m_name)}); g_pEventManager->postEvent(SHyprIPCEvent{"workspacev2", std::format("{},{}", pWorkspace->m_id, pWorkspace->m_name)});
Event::bus()->m_events.workspace.active.emit(pWorkspace); EMIT_HOOK_EVENT("workspace", pWorkspace);
} }
// set all LSes as not above fullscreen on workspace changes // set all LSes as not above fullscreen on workspace changes
@ -1439,23 +1357,17 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) {
g_pDesktopAnimationManager->startAnimation(m_activeSpecialWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_OUT, false); g_pDesktopAnimationManager->startAnimation(m_activeSpecialWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_OUT, false);
g_pEventManager->postEvent(SHyprIPCEvent{"activespecial", "," + m_name}); g_pEventManager->postEvent(SHyprIPCEvent{"activespecial", "," + m_name});
g_pEventManager->postEvent(SHyprIPCEvent{"activespecialv2", ",," + 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(); m_activeSpecialWorkspace.reset();
if (POLDSPECIAL) if (POLDSPECIAL)
POLDSPECIAL->m_events.activeChanged.emit(); POLDSPECIAL->m_events.activeChanged.emit();
g_layoutManager->recalculateMonitor(m_self.lock()); g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id);
if (!(Desktop::focusState()->window() && Desktop::focusState()->window()->m_pinned && Desktop::focusState()->window()->m_monitor == m_self)) { if (!(Desktop::focusState()->window() && Desktop::focusState()->window()->m_pinned && Desktop::focusState()->window()->m_monitor == m_self)) {
if (const auto PLAST = m_activeWorkspace->getLastFocusedWindow(); PLAST) if (const auto PLAST = m_activeWorkspace->getLastFocusedWindow(); PLAST)
Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_KEYBIND); Desktop::focusState()->fullWindowFocus(PLAST);
else else
g_pInputManager->refocus(); g_pInputManager->refocus();
} }
@ -1480,17 +1392,10 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) {
const auto PMONITORWORKSPACEOWNER = pWorkspace->m_monitor.lock(); const auto PMONITORWORKSPACEOWNER = pWorkspace->m_monitor.lock();
if (const auto PMWSOWNER = pWorkspace->m_monitor.lock(); PMWSOWNER && PMWSOWNER->m_activeSpecialWorkspace == pWorkspace) { if (const auto PMWSOWNER = pWorkspace->m_monitor.lock(); PMWSOWNER && PMWSOWNER->m_activeSpecialWorkspace == pWorkspace) {
PMWSOWNER->m_activeSpecialWorkspace.reset(); PMWSOWNER->m_activeSpecialWorkspace.reset();
g_layoutManager->recalculateMonitor(PMWSOWNER); g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PMWSOWNER->m_id);
g_pHyprRenderer->damageMonitor(PMWSOWNER);
g_pEventManager->postEvent(SHyprIPCEvent{"activespecial", "," + PMWSOWNER->m_name}); g_pEventManager->postEvent(SHyprIPCEvent{"activespecial", "," + PMWSOWNER->m_name});
g_pEventManager->postEvent(SHyprIPCEvent{"activespecialv2", ",," + 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; const auto PACTIVEWORKSPACE = PMWSOWNER->m_activeWorkspace;
g_pDesktopAnimationManager->setFullscreenFadeAnimation(PACTIVEWORKSPACE, g_pDesktopAnimationManager->setFullscreenFadeAnimation(PACTIVEWORKSPACE,
PACTIVEWORKSPACE && PACTIVEWORKSPACE->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : PACTIVEWORKSPACE && PACTIVEWORKSPACE->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN :
@ -1504,12 +1409,6 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) {
m_activeSpecialWorkspace = pWorkspace; m_activeSpecialWorkspace = pWorkspace;
m_activeSpecialWorkspace->m_visible = true; 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) if (POLDSPECIAL)
POLDSPECIAL->m_events.activeChanged.emit(); POLDSPECIAL->m_events.activeChanged.emit();
@ -1540,16 +1439,17 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) {
} else } else
pos = pos - PMONFROMMIDDLE->m_position + m_position; pos = pos - PMONFROMMIDDLE->m_position + m_position;
w->layoutTarget()->setPositionGlobal(CBox{pos, w->layoutTarget()->position().size()}); *w->m_realPosition = pos;
w->m_position = pos;
} }
} }
} }
g_layoutManager->recalculateMonitor(m_self.lock()); g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id);
if (!(Desktop::focusState()->window() && Desktop::focusState()->window()->m_pinned && Desktop::focusState()->window()->m_monitor == m_self)) { if (!(Desktop::focusState()->window() && Desktop::focusState()->window()->m_pinned && Desktop::focusState()->window()->m_monitor == m_self)) {
if (const auto PLAST = pWorkspace->getLastFocusedWindow(); PLAST) if (const auto PLAST = pWorkspace->getLastFocusedWindow(); PLAST)
Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_KEYBIND); Desktop::focusState()->fullWindowFocus(PLAST);
else else
g_pInputManager->refocus(); g_pInputManager->refocus();
} }
@ -1579,20 +1479,10 @@ Vector2D CMonitor::middle() {
return m_position + m_size / 2.f; return m_position + m_size / 2.f;
} }
const Mat3x3& CMonitor::getTransformMatrix() {
return m_projMatrix;
}
const Mat3x3& CMonitor::getScaleMatrix() {
return m_projOutputMatrix;
}
void CMonitor::updateMatrix() { void CMonitor::updateMatrix() {
m_projMatrix = Mat3x3::identity(); m_projMatrix = Mat3x3::identity();
if (m_transform != WL_OUTPUT_TRANSFORM_NORMAL) 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_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() { WORKSPACEID CMonitor::activeWorkspaceID() {
@ -1796,10 +1686,6 @@ 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()) { if (m_solitaryClient.expired()) {
reasons |= TC_CANDIDATE; reasons |= TC_CANDIDATE;
return reasons; return reasons;
@ -1841,6 +1727,12 @@ uint16_t CMonitor::isDSBlocked(bool full) {
} }
} }
if (m_tearingState.activelyTearing) {
reasons |= DS_BLOCK_TEARING;
if (!full)
return reasons;
}
if (!m_mirrors.empty() || isMirror()) { if (!m_mirrors.empty() || isMirror()) {
reasons |= DS_BLOCK_MIRROR; reasons |= DS_BLOCK_MIRROR;
if (!full) if (!full)
@ -1879,26 +1771,20 @@ uint16_t CMonitor::isDSBlocked(bool full) {
// we can't scanout shm buffers. // we can't scanout shm buffers.
const auto params = PSURFACE->m_current.buffer->dmabuf(); const auto params = PSURFACE->m_current.buffer->dmabuf();
if (!params.success || !PSURFACE->m_current.texture->isDMA() /* dmabuf */) { if (!params.success || !PSURFACE->m_current.texture->m_eglImage /* dmabuf */) {
reasons |= DS_BLOCK_DMA; reasons |= DS_BLOCK_DMA;
if (!full) if (!full)
return reasons; return reasons;
} }
const bool surfaceIsHDR = PSURFACE->m_colorManagement.valid() && PSURFACE->m_colorManagement->isHDR(); const bool surfaceIsHDR = PSURFACE->m_colorManagement.valid() && (PSURFACE->m_colorManagement->isHDR() || PSURFACE->m_colorManagement->isWindowsScRGB());
const bool surfaceIsScRGB = surfaceIsHDR && PSURFACE->m_colorManagement->isWindowsScRGB(); if (needsCM() && *PNONSHADER != CM_NS_IGNORE && !canNoShaderCM() && ((inHDR() && (*PPASS == 0 || !surfaceIsHDR)) || (!inHDR() && (*PPASS != 1 || surfaceIsHDR))))
if (needsCM() && (*PNONSHADER != CM_NS_IGNORE || surfaceIsScRGB) && !canNoShaderCM() &&
((inHDR() && (*PPASS == 0 || !surfaceIsHDR || surfaceIsScRGB)) || (!inHDR() && (*PPASS != 1 || surfaceIsHDR))))
reasons |= DS_BLOCK_CM; reasons |= DS_BLOCK_CM;
return reasons; return reasons;
} }
bool CMonitor::attemptDirectScanout() { bool CMonitor::attemptDirectScanout() {
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(); const auto blockedReason = isDSBlocked();
if (blockedReason) if (blockedReason)
return false; return false;
@ -1913,7 +1799,7 @@ bool CMonitor::attemptDirectScanout() {
auto PBUFFER = PSURFACE->m_current.buffer.m_buffer; auto PBUFFER = PSURFACE->m_current.buffer.m_buffer;
// #TODO this entire bit needs figuring out, vrr goes down the drain without it // #TODO this entire bit needs figuring out, vrr goes down the drain without it
if (PBUFFER == m_output->state->state().buffer && *PSAME) { if (PBUFFER == m_output->state->state().buffer) {
PSURFACE->presentFeedback(Time::steadyNow(), m_self.lock()); PSURFACE->presentFeedback(Time::steadyNow(), m_self.lock());
if (m_scanoutNeedsCursorUpdate) { if (m_scanoutNeedsCursorUpdate) {
@ -1932,7 +1818,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. //#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 && !m_tearingState.activelyTearing && *PSAMEFIFO) if (PSURFACE->m_fifo)
PSURFACE->m_stateQueue.unlockFirst(LOCK_REASON_FIFO); PSURFACE->m_stateQueue.unlockFirst(LOCK_REASON_FIFO);
return true; return true;
@ -1963,17 +1849,6 @@ bool CMonitor::attemptDirectScanout() {
PSURFACE->presentFeedback(Time::steadyNow(), m_self.lock()); PSURFACE->presentFeedback(Time::steadyNow(), m_self.lock());
m_output->state->addDamage(PSURFACE->m_current.accumulateBufferDamage()); m_output->state->addDamage(PSURFACE->m_current.accumulateBufferDamage());
// 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(); m_output->state->resetExplicitFences();
// no need to do explicit sync here as surface current can only ever be ready to read // no need to do explicit sync here as surface current can only ever be ready to read
@ -2129,14 +2004,6 @@ int CMonitor::maxAvgLuminance(int defaultValue) {
(m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->desiredMaxFrameAverageLuminance : 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() { bool CMonitor::wantsWideColor() {
return supportsWideColor() && (wantsHDR() || m_imageDescription->value().primariesNamed == CM_PRIMARIES_BT2020); return supportsWideColor() && (wantsHDR() || m_imageDescription->value().primariesNamed == CM_PRIMARIES_BT2020);
} }
@ -2150,52 +2017,22 @@ bool CMonitor::inHDR() {
} }
bool CMonitor::inFullscreenMode() { 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; 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() { std::optional<NColorManagement::PImageDescription> CMonitor::getFSImageDescription() {
if (!inFullscreenMode()) if (!inFullscreenMode())
return {}; return {};
const auto FS_WINDOW = getFullscreenWindow(); const auto FS_WINDOW = m_activeWorkspace->getFullscreenWindow();
if (!FS_WINDOW) if (!FS_WINDOW)
return {}; return {}; // should be unreachable
const auto ROOT_SURF = FS_WINDOW->wlSurface()->resource(); const auto ROOT_SURF = FS_WINDOW->wlSurface()->resource();
const auto SURF = ROOT_SURF->findWithCM(); const auto SURF = ROOT_SURF->findWithCM();
return SURF ? NColorManagement::CImageDescription::from(SURF->m_colorManagement->imageDescription()) : DEFAULT_IMAGE_DESCRIPTION; 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() { bool CMonitor::needsCM() {
const auto SRC_DESC = getFSImageDescription(); const auto SRC_DESC = getFSImageDescription();
return SRC_DESC.has_value() && SRC_DESC.value() != m_imageDescription; return SRC_DESC.has_value() && SRC_DESC.value() != m_imageDescription;
@ -2216,91 +2053,24 @@ bool CMonitor::canNoShaderCM() {
const auto SRC_DESC_VALUE = SRC_DESC.value()->value(); const auto SRC_DESC_VALUE = SRC_DESC.value()->value();
if (m_imageDescription->value().icc.present) if (SRC_DESC_VALUE.icc.fd >= 0 || m_imageDescription->value().icc.fd >= 0)
return false; return false; // no ICC support
const auto sdrEOTF = NTransferFunction::fromConfig(); static auto PSDREOTF = CConfigValue<Hyprlang::INT>("render:cm_sdr_eotf");
// only primaries differ // only primaries differ
return ( return ((SRC_DESC_VALUE.transferFunction == m_imageDescription->value().transferFunction ||
(SRC_DESC_VALUE.transferFunction == m_imageDescription->value().transferFunction || (*PSDREOTF == 2 && SRC_DESC_VALUE.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_SRGB &&
(sdrEOTF == NTransferFunction::TF_FORCED_GAMMA22 && SRC_DESC_VALUE.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_SRGB &&
m_imageDescription->value().transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22)) && m_imageDescription->value().transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22)) &&
SRC_DESC_VALUE.transferFunctionPower == m_imageDescription->value().transferFunctionPower && SRC_DESC_VALUE.transferFunctionPower == m_imageDescription->value().transferFunctionPower &&
(!inHDR() || SRC_DESC_VALUE.luminances == m_imageDescription->value().luminances) (!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.masteringLuminances == m_imageDescription->value().masteringLuminances && SRC_DESC_VALUE.maxCLL == m_imageDescription->value().maxCLL && SRC_DESC_VALUE.maxFALL == m_imageDescription->value().maxFALL SRC_DESC_VALUE.maxFALL == m_imageDescription->value().maxFALL);
);
} }
bool CMonitor::doesNoShaderCM() { bool CMonitor::doesNoShaderCM() {
return m_noShaderCTM; 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) { CMonitorState::CMonitorState(CMonitor* owner) : m_owner(owner) {
; ;
} }
@ -2328,7 +2098,7 @@ bool CMonitorState::commit() {
if (!updateSwapchain()) if (!updateSwapchain())
return false; return false;
Event::bus()->m_events.monitor.preCommit.emit(m_owner->m_self.lock()); EMIT_HOOK_EVENT("preMonitorCommit", m_owner->m_self.lock());
ensureBufferPresent(); ensureBufferPresent();

View file

@ -16,15 +16,13 @@
#include "math/Math.hpp" #include "math/Math.hpp"
#include "../desktop/reserved/ReservedArea.hpp" #include "../desktop/reserved/ReservedArea.hpp"
#include <optional> #include <optional>
#include "cm/ColorManagement.hpp" #include "../protocols/types/ColorManagement.hpp"
#include "signal/Signal.hpp" #include "signal/Signal.hpp"
#include "DamageRing.hpp" #include "DamageRing.hpp"
#include <aquamarine/output/Output.hpp> #include <aquamarine/output/Output.hpp>
#include <aquamarine/allocator/Swapchain.hpp> #include <aquamarine/allocator/Swapchain.hpp>
#include <hyprutils/os/FileDescriptor.hpp> #include <hyprutils/os/FileDescriptor.hpp>
#include "../helpers/TransferFunction.hpp"
class CMonitorFrameScheduler; class CMonitorFrameScheduler;
// Enum for the different types of auto directions, e.g. auto-left, auto-up. // Enum for the different types of auto directions, e.g. auto-left, auto-up.
@ -52,11 +50,10 @@ struct SMonitorRule {
std::string mirrorOf = ""; std::string mirrorOf = "";
bool enable10bit = false; bool enable10bit = false;
NCMType::eCMType cmType = NCMType::CM_SRGB; NCMType::eCMType cmType = NCMType::CM_SRGB;
NTransferFunction::eTF sdrEotf = NTransferFunction::TF_DEFAULT; int sdrEotf = 0;
float sdrSaturation = 1.0f; // SDR -> HDR float sdrSaturation = 1.0f; // SDR -> HDR
float sdrBrightness = 1.0f; // SDR -> HDR float sdrBrightness = 1.0f; // SDR -> HDR
Desktop::CReservedArea reservedArea; Desktop::CReservedArea reservedArea;
std::string iccFile;
int supportsWideColor = 0; // 0 - auto, 1 - force enable, -1 - force disable int supportsWideColor = 0; // 0 - auto, 1 - force enable, -1 - force disable
int supportsHDR = 0; // 0 - auto, 1 - force enable, -1 - force disable int supportsHDR = 0; // 0 - auto, 1 - force enable, -1 - force disable
@ -127,7 +124,7 @@ class CMonitor {
bool m_scheduledRecalc = false; bool m_scheduledRecalc = false;
wl_output_transform m_transform = WL_OUTPUT_TRANSFORM_NORMAL; wl_output_transform m_transform = WL_OUTPUT_TRANSFORM_NORMAL;
float m_xwaylandScale = 1.f; float m_xwaylandScale = 1.f;
Mat3x3 m_projMatrix;
std::optional<Vector2D> m_forceSize; std::optional<Vector2D> m_forceSize;
SP<Aquamarine::SOutputMode> m_currentMode; SP<Aquamarine::SOutputMode> m_currentMode;
SP<Aquamarine::CSwapchain> m_cursorSwapchain; SP<Aquamarine::CSwapchain> m_cursorSwapchain;
@ -140,7 +137,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_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. bool m_enabled10bit = false; // as above, this can be TRUE even if 10 bit failed.
NCMType::eCMType m_cmType = NCMType::CM_SRGB; NCMType::eCMType m_cmType = NCMType::CM_SRGB;
NTransferFunction::eTF m_sdrEotf = NTransferFunction::TF_DEFAULT; int m_sdrEotf = 0;
float m_sdrSaturation = 1.0f; float m_sdrSaturation = 1.0f;
float m_sdrBrightness = 1.0f; float m_sdrBrightness = 1.0f;
float m_sdrMinLuminance = 0.2f; float m_sdrMinLuminance = 0.2f;
@ -180,7 +177,6 @@ class CMonitor {
// for direct scanout // for direct scanout
PHLWINDOWREF m_lastScanout; 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; bool m_scanoutNeedsCursorUpdate = false;
// for special fade/blur // for special fade/blur
@ -211,7 +207,6 @@ class CMonitor {
} m_tearingState; } m_tearingState;
struct { struct {
CSignalT<> commit;
CSignalT<> destroy; CSignalT<> destroy;
CSignalT<> connect; CSignalT<> connect;
CSignalT<> disconnect; CSignalT<> disconnect;
@ -237,8 +232,9 @@ class CMonitor {
DS_BLOCK_SURFACE = (1 << 8), DS_BLOCK_SURFACE = (1 << 8),
DS_BLOCK_TRANSFORM = (1 << 9), DS_BLOCK_TRANSFORM = (1 << 9),
DS_BLOCK_DMA = (1 << 10), DS_BLOCK_DMA = (1 << 10),
DS_BLOCK_FAILED = (1 << 11), DS_BLOCK_TEARING = (1 << 11),
DS_BLOCK_CM = (1 << 12), DS_BLOCK_FAILED = (1 << 12),
DS_BLOCK_CM = (1 << 13),
DS_CHECKS_COUNT = 14, DS_CHECKS_COUNT = 14,
}; };
@ -279,15 +275,14 @@ class CMonitor {
TC_SUPPORT = (1 << 4), TC_SUPPORT = (1 << 4),
TC_CANDIDATE = (1 << 5), TC_CANDIDATE = (1 << 5),
TC_WINDOW = (1 << 6), TC_WINDOW = (1 << 6),
TC_HW_CURSOR = (1 << 7),
TC_CHECKS_COUNT = 8, TC_CHECKS_COUNT = 7,
}; };
// methods // methods
void onConnect(bool noRule); void onConnect(bool noRule);
void onDisconnect(bool destroy = false); void onDisconnect(bool destroy = false);
void applyCMType(NCMType::eCMType cmType, NTransferFunction::eTF cmSdrEotf); void applyCMType(NCMType::eCMType cmType, int cmSdrEotf);
bool applyMonitorRule(SMonitorRule* pMonitorRule, bool force = false); bool applyMonitorRule(SMonitorRule* pMonitorRule, bool force = false);
void addDamage(const pixman_region32_t* rg); void addDamage(const pixman_region32_t* rg);
void addDamage(const CRegion& rg); void addDamage(const CRegion& rg);
@ -303,6 +298,7 @@ class CMonitor {
void setSpecialWorkspace(const WORKSPACEID& id); void setSpecialWorkspace(const WORKSPACEID& id);
void moveTo(const Vector2D& pos); void moveTo(const Vector2D& pos);
Vector2D middle(); Vector2D middle();
void updateMatrix();
WORKSPACEID activeWorkspaceID(); WORKSPACEID activeWorkspaceID();
WORKSPACEID activeSpecialWorkspaceID(); WORKSPACEID activeSpecialWorkspaceID();
CBox logicalBox(); CBox logicalBox();
@ -325,28 +321,16 @@ class CMonitor {
float minLuminance(float defaultValue = 0); float minLuminance(float defaultValue = 0);
int maxLuminance(int defaultValue = 80); int maxLuminance(int defaultValue = 80);
int maxAvgLuminance(int defaultValue = 80); int maxAvgLuminance(int defaultValue = 80);
float maxFALL();
float maxCLL();
bool wantsWideColor(); bool wantsWideColor();
bool wantsHDR(); bool wantsHDR();
bool inHDR(); bool inHDR();
bool gammaRampsInUse();
// /// Has an active workspace with a real fullscreen window
const Mat3x3& getTransformMatrix();
const Mat3x3& getScaleMatrix();
/// Has an active workspace with a real fullscreen window (includes special workspace)
bool inFullscreenMode(); bool inFullscreenMode();
/// Get fullscreen window from active or special workspace
PHLWINDOW getFullscreenWindow();
std::optional<NColorManagement::PImageDescription> getFSImageDescription(); std::optional<NColorManagement::PImageDescription> getFSImageDescription();
NColorManagement::SPCPRimaries getMasteringPrimaries();
NColorManagement::SImageDescription::SPCMasteringLuminances getMasteringLuminances();
bool needsCM(); bool needsCM();
/// Can do CM without shader /// Can do CM without shader
bool canNoShaderCM(); bool canNoShaderCM();
@ -358,7 +342,7 @@ class CMonitor {
PHLWINDOWREF m_previousFSWindow; PHLWINDOWREF m_previousFSWindow;
bool m_needsHDRupdate = false; bool m_needsHDRupdate = false;
NColorManagement::PImageDescription m_imageDescription = NColorManagement::CImageDescription::from(NColorManagement::SImageDescription{}); NColorManagement::PImageDescription m_imageDescription;
bool m_noShaderCTM = false; // sets drm CTM, restore needed bool m_noShaderCTM = false; // sets drm CTM, restore needed
// For the list lookup // For the list lookup
@ -367,19 +351,12 @@ class CMonitor {
return m_position == rhs.m_position && m_size == rhs.m_size && m_name == rhs.m_name; return m_position == rhs.m_position && m_size == rhs.m_size && m_name == rhs.m_name;
} }
Mat3x3 m_projMatrix;
private: private:
void updateMatrix();
Mat3x3 m_projOutputMatrix;
void setupDefaultWS(const SMonitorRule&); void setupDefaultWS(const SMonitorRule&);
WORKSPACEID findAvailableDefaultWS(); WORKSPACEID findAvailableDefaultWS();
void commitDPMSState(bool state); void commitDPMSState(bool state);
void updateVCGTRamps();
bool m_doneScheduled = false; bool m_doneScheduled = false;
bool m_vcgtRampsSet = false;
std::stack<WORKSPACEID> m_prevWorkSpaces; std::stack<WORKSPACEID> m_prevWorkSpaces;
struct { struct {

View file

@ -11,7 +11,7 @@ CMonitorFrameScheduler::CMonitorFrameScheduler(PHLMONITOR m) : m_monitor(m) {
bool CMonitorFrameScheduler::newSchedulingEnabled() { bool CMonitorFrameScheduler::newSchedulingEnabled() {
static auto PENABLENEW = CConfigValue<Hyprlang::INT>("render:new_render_scheduling"); static auto PENABLENEW = CConfigValue<Hyprlang::INT>("render:new_render_scheduling");
return *PENABLENEW && g_pHyprOpenGL->explicitSyncSupported() && m_monitor && !m_monitor->m_directScanoutIsActive; return *PENABLENEW && g_pHyprOpenGL->explicitSyncSupported();
} }
void CMonitorFrameScheduler::onSyncFired() { void CMonitorFrameScheduler::onSyncFired() {

View file

@ -38,10 +38,10 @@ int NSystemd::sdNotify(int unsetEnvironment, const char* state) {
if (!addr) if (!addr)
return 0; return 0;
struct sockaddr_un unixAddr = {0}; // address length must be at most this; see man 7 unix
size_t addrLen = strnlen(addr, 107);
size_t addrLen = strnlen(addr, sizeof(unixAddr.sun_path) - 1);
struct sockaddr_un unixAddr;
unixAddr.sun_family = AF_UNIX; unixAddr.sun_family = AF_UNIX;
strncpy(unixAddr.sun_path, addr, addrLen); strncpy(unixAddr.sun_path, addr, addrLen);
if (unixAddr.sun_path[0] == '@') if (unixAddr.sun_path[0] == '@')

Some files were not shown because too many files have changed in this diff Show more