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
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
name: "Code Style"
runs-on: ubuntu-latest
container:
image: archlinux
steps:
- name: Checkout repository
uses: actions/checkout@v4
# - name: clang-format check
# uses: jidicula/clang-format-action@v4.16.0
# with:
# exclude-regex: ^subprojects$
- name: Install clang-format
run: |
pacman --noconfirm --noprogressbar -Syyu
pacman --noconfirm --noprogressbar -Sy clang
- name: clang-format check
run: .github/workflows/clang-format-check.sh "." "llvm" "^subprojects$" ""
- name: Save PR head commit SHA
if: failure() && github.event_name == 'pull_request'
shell: bash
run: |
SHA="${{ github.event.pull_request.head.sha }}"
echo "SHA=$SHA" >> $GITHUB_ENV
- name: Save latest commit SHA if not PR
if: failure() && github.event_name != 'pull_request'
shell: bash
run: echo "SHA=${{ github.sha }}" >> $GITHUB_ENV
- name: Report failure in job summary
if: failure()
run: |
DEEPLINK="${{ github.server_url }}/${{ github.repository }}/commit/${{ env.SHA }}"
echo -e "Format check failed on commit [${GITHUB_SHA:0:8}]($DEEPLINK) with files:\n$(<$GITHUB_WORKSPACE/failing-files.txt)" >> $GITHUB_STEP_SUMMARY
uses: jidicula/clang-format-action@v4.16.0
with:
exclude-regex: ^subprojects$

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

View file

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

View file

@ -18,7 +18,6 @@ nopch:
clear:
rm -rf build
rm -f ./protocols/*.h ./protocols/*.c ./protocols/*.cpp ./protocols/*.hpp
rm -f ./hyprctl/hw-protocols/*.cpp ./hyprctl/hw-protocols/*.hpp
all:
$(MAKE) clear
@ -88,7 +87,7 @@ asan:
@echo "Wayland done"
patch -p1 < ./scripts/hyprlandStaticAsan.diff
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -DWITH_ASAN:STRING=True -DUSE_TRACY:STRING=False -DUSE_TRACY_GPU:STRING=False -S . -B ./build
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
@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, R, exec, $menu
bind = $mainMod, P, pseudo, # dwindle
bind = $mainMod, J, layoutmsg, togglesplit # dwindle
bind = $mainMod, J, togglesplit, # dwindle
# Move focus with mainMod + arrow keys
bind = $mainMod, left, movefocus, l

64
flake.lock generated
View file

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

206
flake.nix
View file

@ -88,122 +88,108 @@
};
};
outputs =
inputs@{
self,
nixpkgs,
systems,
...
}:
let
inherit (nixpkgs) lib;
eachSystem = lib.genAttrs (import systems);
pkgsFor = eachSystem (
system:
import nixpkgs {
localSystem = system;
overlays = with self.overlays; [
hyprland-packages
hyprland-extras
];
}
);
pkgsCrossFor = eachSystem (
system: crossSystem:
import nixpkgs {
localSystem = system;
inherit crossSystem;
overlays = with self.overlays; [
hyprland-packages
hyprland-extras
];
}
);
pkgsDebugFor = eachSystem (
system:
import nixpkgs {
localSystem = system;
overlays = with self.overlays; [
hyprland-debug
];
}
);
pkgsDebugCrossFor = eachSystem (
system: crossSystem:
import nixpkgs {
localSystem = system;
inherit crossSystem;
overlays = with self.overlays; [
hyprland-debug
];
}
);
in
{
overlays = import ./nix/overlays.nix { inherit self lib inputs; };
outputs = inputs @ {
self,
nixpkgs,
systems,
...
}: let
inherit (nixpkgs) lib;
eachSystem = lib.genAttrs (import systems);
pkgsFor = eachSystem (system:
import nixpkgs {
localSystem = system;
overlays = with self.overlays; [
hyprland-packages
hyprland-extras
];
});
pkgsCrossFor = eachSystem (system: crossSystem:
import nixpkgs {
localSystem = system;
inherit crossSystem;
overlays = with self.overlays; [
hyprland-packages
hyprland-extras
];
});
pkgsDebugFor = eachSystem (system:
import nixpkgs {
localSystem = system;
overlays = with self.overlays; [
hyprland-debug
];
});
pkgsDebugCrossFor = eachSystem (system: crossSystem:
import nixpkgs {
localSystem = system;
inherit crossSystem;
overlays = with self.overlays; [
hyprland-debug
];
});
in {
overlays = import ./nix/overlays.nix {inherit self lib inputs;};
checks = eachSystem (
system:
(lib.filterAttrs (
n: _: (lib.hasPrefix "hyprland" n) && !(lib.hasSuffix "debug" n)
) self.packages.${system})
// {
inherit (self.packages.${system}) xdg-desktop-portal-hyprland;
pre-commit-check = inputs.pre-commit-hooks.lib.${system}.run {
src = ./.;
hooks = {
hyprland-treewide-formatter = {
enable = true;
entry = "${self.formatter.${system}}/bin/hyprland-treewide-formatter";
pass_filenames = false;
excludes = [ "subprojects" ];
always_run = true;
};
checks = eachSystem (system:
(lib.filterAttrs
(n: _: (lib.hasPrefix "hyprland" n) && !(lib.hasSuffix "debug" n))
self.packages.${system})
// {
inherit (self.packages.${system}) xdg-desktop-portal-hyprland;
pre-commit-check = inputs.pre-commit-hooks.lib.${system}.run {
src = ./.;
hooks = {
hyprland-treewide-formatter = {
enable = true;
entry = "${self.formatter.${system}}/bin/hyprland-treewide-formatter";
pass_filenames = false;
excludes = ["subprojects"];
always_run = true;
};
};
}
// (import ./nix/tests inputs pkgsFor.${system})
);
};
}
// (import ./nix/tests inputs pkgsFor.${system}));
packages = eachSystem (system: {
default = self.packages.${system}.hyprland;
inherit (pkgsFor.${system})
# hyprland-packages
hyprland
hyprland-unwrapped
hyprland-with-tests
# hyprland-extras
xdg-desktop-portal-hyprland
;
inherit (pkgsDebugFor.${system}) hyprland-debug;
hyprland-cross = (pkgsCrossFor.${system} "aarch64-linux").hyprland;
hyprland-debug-cross = (pkgsDebugCrossFor.${system} "aarch64-linux").hyprland-debug;
});
packages = eachSystem (system: {
default = self.packages.${system}.hyprland;
inherit
(pkgsFor.${system})
# hyprland-packages
hyprland
hyprland-unwrapped
hyprland-with-tests
# hyprland-extras
xdg-desktop-portal-hyprland
;
inherit (pkgsDebugFor.${system}) hyprland-debug;
hyprland-cross = (pkgsCrossFor.${system} "aarch64-linux").hyprland;
hyprland-debug-cross = (pkgsDebugCrossFor.${system} "aarch64-linux").hyprland-debug;
});
devShells = eachSystem (system: {
default =
pkgsFor.${system}.mkShell.override
{
inherit (self.packages.${system}.default) stdenv;
}
{
name = "hyprland-shell";
hardeningDisable = [ "fortify" ];
inputsFrom = [ pkgsFor.${system}.hyprland ];
packages = [ pkgsFor.${system}.clang-tools ];
inherit (self.checks.${system}.pre-commit-check) shellHook;
};
});
devShells = eachSystem (system: {
default =
pkgsFor.${system}.mkShell.override {
inherit (self.packages.${system}.default) stdenv;
} {
name = "hyprland-shell";
hardeningDisable = ["fortify"];
inputsFrom = [pkgsFor.${system}.hyprland];
packages = [pkgsFor.${system}.clang-tools];
inherit (self.checks.${system}.pre-commit-check) shellHook;
};
});
formatter = eachSystem (system: pkgsFor.${system}.callPackage ./nix/formatter.nix { });
formatter = eachSystem (system: pkgsFor.${system}.callPackage ./nix/formatter.nix {});
nixosModules.default = import ./nix/module.nix inputs;
homeManagerModules.default = import ./nix/hm-module.nix self;
nixosModules.default = import ./nix/module.nix inputs;
homeManagerModules.default = import ./nix/hm-module.nix self;
# Hydra build jobs
# Recent versions of Hydra can aggregate jobsets from 'hydraJobs' instead of a release.nix
# or similar. Remember to filter large or incompatible attributes here. More eval jobs can
# be added by merging, e.g., self.packages // self.devShells.
hydraJobs = self.packages;
};
# Hydra build jobs
# Recent versions of Hydra can aggregate jobsets from 'hydraJobs' instead of a release.nix
# or similar. Remember to filter large or incompatible attributes here. More eval jobs can
# be added by merging, e.g., self.packages // self.devShells.
hydraJobs = self.packages;
};
}

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="hyprpaper_core" version="2">
<protocol name="hyprpaper_core" version="1">
<copyright>
BSD 3-Clause License
@ -31,7 +31,7 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</copyright>
<object name="hyprpaper_core_manager" version="2">
<object name="hyprpaper_core_manager" version="1">
<description summary="manager object">
This is the core manager object for hyprpaper operations
</description>
@ -62,13 +62,6 @@
Destroys this object. Children remain alive until destroyed.
</description>
</c2s>
<c2s name="get_status_object" since="2">
<description summary="Get a status object">
Creates a status object
</description>
<returns iface="hyprpaper_status"/>
</c2s>
</object>
<enum name="wallpaper_fit_mode">
@ -148,25 +141,4 @@
</description>
</c2s>
</object>
<object name="hyprpaper_status" version="2">
<description summary="status object">
This is an object which will emit various status updates.
</description>
<s2c name="active_wallpaper">
<description summary="Active wallpaper state">
Sends the active wallpaper for a given monitor. This will be emitted
immediately after binding, and then every time the path changes.
</description>
<arg name="monitor" type="varchar" summary="monitor name"/>
<arg name="path" type="varchar" summary="wallpaper path"/>
</s2c>
<c2s name="destroy" destructor="true">
<description summary="Destroy this object">
Destroys this object.
</description>
</c2s>
</object>
</protocol>

View file

@ -4,7 +4,6 @@
#include <optional>
#include <format>
#include <filesystem>
#include <print>
#include <hyprpaper_core-client.hpp>
@ -16,7 +15,7 @@ using namespace std::string_literals;
constexpr const char* SOCKET_NAME = ".hyprpaper.sock";
static SP<CCHyprpaperCoreImpl> g_coreImpl;
constexpr const uint32_t PROTOCOL_VERSION_SUPPORTED = 2;
constexpr const uint32_t PROTOCOL_VERSION_SUPPORTED = 1;
//
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);
}
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, ',');
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)
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);
@ -98,7 +111,7 @@ static std::expected<void, std::string> doWallpaper(const std::string_view& RHS)
if (!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)
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) {
canExit = true;
switch (code) {
case HYPRPAPER_CORE_APPLYING_ERROR_INVALID_PATH: err = std::format("failed to set wallpaper: Invalid path", code); break;
case HYPRPAPER_CORE_APPLYING_ERROR_INVALID_MONITOR: err = std::format("failed to set wallpaper: Invalid monitor", code); break;
default: err = std::format("failed to set wallpaper: unknown error, code {}", code); break;
}
err = std::format("failed to set wallpaper, code {}", code);
});
wallpaper->setSuccess([&canExit]() { canExit = true; });
@ -136,73 +145,4 @@ static std::expected<void, std::string> doWallpaper(const std::string_view& RHS)
return std::unexpected(*err);
return {};
}
static std::expected<void, std::string> doListActive() {
const auto RTDIR = getenv("XDG_RUNTIME_DIR");
if (!RTDIR || RTDIR[0] == '\0')
return std::unexpected("can't send: no XDG_RUNTIME_DIR");
const auto HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE");
if (!HIS || HIS[0] == '\0')
return std::unexpected("can't send: no HYPRLAND_INSTANCE_SIGNATURE (not running under hyprland)");
auto socketPath = RTDIR + "/hypr/"s + HIS + "/"s + SOCKET_NAME;
auto socket = Hyprwire::IClientSocket::open(socketPath);
if (!socket)
return std::unexpected("can't send: failed to connect to hyprpaper (is it running?)");
g_coreImpl = makeShared<CCHyprpaperCoreImpl>(PROTOCOL_VERSION_SUPPORTED);
socket->addImplementation(g_coreImpl);
if (!socket->waitForHandshake())
return std::unexpected("can't send: wire handshake failed");
auto spec = socket->getSpec(g_coreImpl->protocol()->specName());
if (!spec)
return std::unexpected("can't send: hyprpaper doesn't have the spec?!");
if (spec->specVer() < 2)
return std::unexpected("can't send: hyprpaper protocol version too low (hyprpaper too old)");
auto manager = makeShared<CCHyprpaperCoreManagerObject>(socket->bindProtocol(g_coreImpl->protocol(), std::min(PROTOCOL_VERSION_SUPPORTED, spec->specVer())));
if (!manager)
return std::unexpected("wire error: couldn't create manager");
auto status = makeShared<CCHyprpaperStatusObject>(manager->sendGetStatusObject());
status->setActiveWallpaper([](const char* mon, const char* wp) { std::println("{}: {}", mon, wp); });
socket->roundtrip();
return {};
}
std::expected<void, std::string> Hyprpaper::makeHyprpaperRequest(const std::string_view& rq) {
if (!rq.contains(' '))
return std::unexpected("Invalid request");
if (!rq.starts_with("/hyprpaper "))
return std::unexpected("Invalid request");
std::string_view LHS, RHS;
auto spacePos = rq.find(' ', 12);
LHS = rq.substr(11, spacePos - 11);
RHS = rq.substr(spacePos + 1);
if (LHS == "wallpaper")
return doWallpaper(RHS);
else if (LHS == "listactive")
return doListActive();
else
return std::unexpected("invalid hyprpaper request");
return {};
}
}

View file

@ -228,23 +228,23 @@ int request(std::string_view arg, int minArgs = 0, bool needRoll = false) {
constexpr size_t BUFFER_SIZE = 8192;
char buffer[BUFFER_SIZE] = {0};
// 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 (errno == EWOULDBLOCK)
log("Hyprland IPC didn't respond in time\n");
log("Couldn't read (6)");
return 6;
}
reply += std::string(buffer, sizeWritten);
while (sizeWritten == BUFFER_SIZE) {
sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE);
if (sizeWritten < 0) {
if (errno == EWOULDBLOCK)
log("Hyprland IPC didn't respond in time\n");
log("Couldn't read (6)");
return 6;
}
if (sizeWritten == 0) {
// server closed connection, we're done
break;
}
reply += std::string(buffer, sizeWritten);
}

View file

@ -11,9 +11,9 @@ set(CMAKE_CXX_STANDARD 23)
pkg_check_modules(hyprpm_deps REQUIRED IMPORTED_TARGET tomlplusplus hyprutils>=0.7.0)
find_package(glaze 6.0.1 QUIET)
find_package(glaze 6.0.0 QUIET)
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")
include(FetchContent)
FetchContent_Declare(
@ -21,7 +21,6 @@ if (NOT glaze_FOUND)
GIT_REPOSITORY https://github.com/stephenberry/glaze.git
GIT_TAG ${GLAZE_VERSION}
GIT_SHALLOW TRUE
EXCLUDE_FROM_ALL
)
FetchContent_MakeAvailable(glaze)
endif()

View file

@ -94,18 +94,15 @@ SHyprlandVersion CPluginManager::getHyprlandVersion(bool running) {
auto hldate = (*jsonQuery)["commit_date"].get_string();
auto hlcommits = (*jsonQuery)["commits"].get_string();
auto flags = (*jsonQuery)["flags"].get_array();
bool isNix = std::ranges::any_of(flags, [](const auto& f) { return f.is_string() && f.get_string() == std::string_view{"nix"}; });
size_t commits = 0;
try {
commits = std::stoull(hlcommits);
} catch (...) { ; }
if (m_bVerbose)
std::println("{}", verboseString("parsed commit {} at branch {} on {}, commits {}, 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)
verRunning = ver;
@ -131,20 +128,12 @@ bool CPluginManager::createSafeDirectory(const std::string& path) {
return true;
}
bool CPluginManager::validArg(const std::string& s) {
return !s.contains("'") && !s.ends_with("\\") && !s.starts_with("\\");
}
bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string& rev) {
const auto HLVER = getHyprlandVersion();
if (!validArg(url) || !validArg(rev)) {
std::println(stderr, "\n{}", failureString("url or rev invalid"));
return false;
}
if (!hasDeps()) {
std::println(stderr, "\n{}", failureString("Could not clone the plugin repository. Dependencies not satisfied. Hyprpm requires: cmake, 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;
}
@ -207,7 +196,7 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
progress.printMessageAbove(infoString("Cloning {}", url));
std::string ret = execAndGet(std::format("cd {} && git clone --recursive '{}' {}", getTempRoot(), url, USERNAME));
std::string ret = execAndGet(std::format("cd {} && git clone --recursive {} {}", getTempRoot(), url, USERNAME));
if (!std::filesystem::exists(m_szWorkingPluginDirectory + "/.git")) {
std::println(stderr, "\n{}", failureString("Could not clone the plugin repository. shell returned:\n{}", ret));
@ -314,14 +303,8 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
progress.printMessageAbove(infoString("Building {}", p.name));
for (auto const& bs : p.buildSteps) {
const auto CMD_RAW = nixDevelopIfNeeded(std::format("cd {} && PKG_CONFIG_PATH=\"{}\" {}", m_szWorkingPluginDirectory, getPkgConfigPath(), bs), HLVER);
if (!CMD_RAW) {
progress.printMessageAbove(failureString("Failed to build {}: {}", p.name, CMD_RAW.error()));
break;
}
out += " -> " + *CMD_RAW + "\n" + execAndGet(*CMD_RAW) + "\n";
const std::string& cmd = std::format("cd {} && PKG_CONFIG_PATH=\"{}/share/pkgconfig\" {}", m_szWorkingPluginDirectory, DataState::getHeadersPath(), bs);
out += " -> " + cmd + "\n" + execAndGet(cmd) + "\n";
}
if (m_bVerbose)
@ -406,7 +389,7 @@ eHeadersErrors CPluginManager::headersValid() {
return HEADERS_MISSING;
// 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);
if (!headers.contains("-I/"))
@ -470,7 +453,7 @@ bool CPluginManager::updateHeaders(bool force) {
const auto HLVER = getHyprlandVersion(false);
if (!hasDeps()) {
std::println("\n{}", failureString("Could not update. Dependencies not satisfied. Hyprpm requires: cmake, 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;
}
@ -512,11 +495,11 @@ bool CPluginManager::updateHeaders(bool force) {
progress.printMessageAbove(verboseString("will shallow since: {}", SHALLOW_DATE));
std::string ret =
execAndGet(std::format("cd {} && git clone --recursive '{}' hyprland-{}{}", getTempRoot(), HL_URL, USERNAME, (bShallow ? " --shallow-since='" + SHALLOW_DATE + "'" : "")));
execAndGet(std::format("cd {} && git clone --recursive {} hyprland-{}{}", getTempRoot(), HL_URL, USERNAME, (bShallow ? " --shallow-since='" + SHALLOW_DATE + "'" : "")));
if (!std::filesystem::exists(WORKINGDIR)) {
progress.printMessageAbove(failureString("Clone failed. Retrying without shallow."));
ret = execAndGet(std::format("cd {} && git clone --recursive '{}' hyprland-{}", getTempRoot(), HL_URL, USERNAME));
ret = execAndGet(std::format("cd {} && git clone --recursive {} hyprland-{}", getTempRoot(), HL_URL, USERNAME));
}
if (!std::filesystem::exists(WORKINGDIR + "/.git")) {
@ -559,17 +542,8 @@ bool CPluginManager::updateHeaders(bool force) {
if (m_bVerbose)
progress.printMessageAbove(verboseString("setting PREFIX for cmake to {}", DataState::getHeadersPath()));
const auto CONFIGURE_CMD =
nixDevelopIfNeeded(std::format("cd {} && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:STRING=\"{}\" -S . -B ./build", WORKINGDIR,
DataState::getHeadersPath()),
HLVER);
if (!CONFIGURE_CMD) {
std::println(stderr, "\n{}", failureString("Could not configure hyprland: {}", CONFIGURE_CMD.error()));
return false;
}
ret = execAndGet(*CONFIGURE_CMD);
ret = execAndGet(std::format("cd {} && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:STRING=\"{}\" -S . -B ./build -G Ninja", WORKINGDIR,
DataState::getHeadersPath()));
if (m_bVerbose)
progress.printMessageAbove(verboseString("cmake returned: {}", ret));
@ -657,7 +631,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
const auto HLVER = getHyprlandVersion(false);
CProgressBar progress;
progress.m_iMaxSteps = (REPOS.size() * 2) + 2;
progress.m_iMaxSteps = REPOS.size() * 2 + 2;
progress.m_iSteps = 0;
progress.m_szCurrentMessage = "Updating repositories";
progress.print();
@ -678,7 +652,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
progress.printMessageAbove(infoString("Cloning {}", repo.url));
std::string ret = execAndGet(std::format("cd {} && git clone --recursive '{}' {}", getTempRoot(), repo.url, USERNAME));
std::string ret = execAndGet(std::format("cd {} && git clone --recursive {} {}", getTempRoot(), repo.url, USERNAME));
if (!std::filesystem::exists(m_szWorkingPluginDirectory + "/.git")) {
std::println("{}", failureString("could not clone repo: shell returned: {}", ret));
@ -688,7 +662,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
if (!repo.rev.empty()) {
progress.printMessageAbove(infoString("Plugin has revision set, resetting: {}", repo.rev));
std::string ret = execAndGet("git -C " + m_szWorkingPluginDirectory + " reset --hard --recurse-submodules \'" + repo.rev + "\'");
std::string ret = execAndGet("git -C " + m_szWorkingPluginDirectory + " reset --hard --recurse-submodules " + repo.rev);
if (ret.compare(0, 6, "fatal:") == 0) {
std::println(stderr, "\n{}", failureString("could not check out revision {}: shell returned:\n{}", repo.rev, ret));
@ -767,14 +741,8 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
progress.printMessageAbove(infoString("Building {}", p.name));
for (auto const& bs : p.buildSteps) {
const auto CMD_RAW = nixDevelopIfNeeded(std::format("cd {} && PKG_CONFIG_PATH=\"{}\" {}", m_szWorkingPluginDirectory, getPkgConfigPath(), bs), HLVER);
if (!CMD_RAW) {
progress.printMessageAbove(failureString("Failed to build {}: {}", p.name, CMD_RAW.error()));
break;
}
out += " -> " + *CMD_RAW + "\n" + execAndGet(*CMD_RAW) + "\n";
const std::string& cmd = std::format("cd {} && PKG_CONFIG_PATH=\"{}/share/pkgconfig\" {}", m_szWorkingPluginDirectory, DataState::getHeadersPath(), bs);
out += " -> " + cmd + "\n" + execAndGet(cmd) + "\n";
}
if (m_bVerbose)
@ -804,8 +772,8 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
repohash.pop_back();
newrepo.hash = repohash;
for (auto const& p : pManifest->m_plugins) {
const auto OLDPLUGINIT = std::ranges::find_if(repo.plugins, [&](const auto& other) { return other.name == p.name; });
newrepo.plugins.emplace_back(SPlugin{p.name, m_szWorkingPluginDirectory + "/" + p.output, OLDPLUGINIT != repo.plugins.end() ? OLDPLUGINIT->enabled : false});
const auto OLDPLUGINIT = std::find_if(repo.plugins.begin(), repo.plugins.end(), [&](const auto& other) { return other.name == p.name; });
newrepo.plugins.push_back(SPlugin{p.name, m_szWorkingPluginDirectory + "/" + p.output, OLDPLUGINIT != repo.plugins.end() ? OLDPLUGINIT->enabled : false});
}
DataState::removePluginRepo(SPluginRepoIdentifier::fromName(newrepo.name));
DataState::addNewPluginRepo(newrepo);
@ -931,7 +899,7 @@ ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState(bool forceReload)
if (!p.enabled)
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;
if (!loadUnloadPlugin(HYPRPMPATH / repoForName(p.name) / p.filename, true)) {
@ -1019,11 +987,8 @@ std::string CPluginManager::headerErrorShort(const eHeadersErrors err) {
}
bool CPluginManager::hasDeps() {
if (!m_bNoNix && getHyprlandVersion().isNix)
return true; // dep check not needed if we are on nix
bool hasAllDeps = true;
std::vector<std::string> deps = {"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) {
if (!execAndGet("command -v " + d).contains("/")) {
@ -1034,92 +999,3 @@ bool CPluginManager::hasDeps() {
return hasAllDeps;
}
const std::string& CPluginManager::getPkgConfigPath() {
static const auto str = std::format("{}/share/pkgconfig:$PKG_CONFIG_PATH", DataState::getHeadersPath());
return str;
}
static std::expected<std::string, std::string> getNixDevelopFromPath(const std::string& argv0) {
std::string fullStorePath;
if (argv0.starts_with("/")) {
// we can use this directly
fullStorePath = argv0;
} else {
// use hyprpm, find in path
auto exe = NSys::findInPath("hyprpm");
if (!exe)
return std::unexpected("hyprpm not found in PATH");
fullStorePath = *exe;
}
if (fullStorePath.empty() || !fullStorePath.ends_with("/bin/hyprpm"))
return std::unexpected("couldn't get a real path for hyprpm (1)");
// canonicalize to get the real nix-store path
std::error_code ec;
fullStorePath = std::filesystem::canonical(fullStorePath, ec);
if (ec || fullStorePath.empty() || !fullStorePath.starts_with("/nix"))
return std::unexpected("couldn't get a real path for hyprpm");
fullStorePath = fullStorePath.substr(0, fullStorePath.length() - std::string_view{"/bin/hyprpm"}.length());
auto deriver = trim(execAndGet(std::format("echo \"$(nix-store --query --deriver '{}')\"", fullStorePath)));
if (deriver.starts_with("unknown"))
return std::unexpected("couldn't nix deriver");
return deriver;
}
static std::expected<std::string, std::string> getNixDevelopFromProfile() {
const auto NIX_PROFILE_STR = execAndGet("nix profile list --json");
auto rawJson = glz::read_json<glz::generic>(NIX_PROFILE_STR);
if (!rawJson)
return std::unexpected("failed to parse nix profile list --json");
auto& json = *rawJson;
if (!json.contains("elements") || !json["elements"].is_object())
return std::unexpected("nix profile list --json returned a wonky json");
if (!json["elements"].contains("hyprland") && !json["elements"].contains("Hyprland"))
return std::unexpected("nix profile list --json doesn't contain Hyprland (did you uninstall?)");
auto& hyprlandJson = json["elements"].contains("hyprland") ? json["elements"]["hyprland"] : json["elements"]["Hyprland"];
if (!hyprlandJson.contains("originalUrl"))
return std::unexpected("nix profile list --json's hyprland doesn't contain originalUrl?");
return hyprlandJson["originalUrl"].get_string();
}
std::expected<std::string, std::string> CPluginManager::nixDevelopIfNeeded(const std::string& cmd, const SHyprlandVersion& ver) {
if (m_bNoNix || !ver.isNix)
return cmd;
// Escape single quotes
std::string newCmd = cmd;
replaceInString(newCmd, "'", "\\'");
auto NIX_DEVELOP = getNixDevelopFromPath(m_szArgv0);
if (NIX_DEVELOP)
return std::format("nix develop '{}' --command bash -c $'{}'", *NIX_DEVELOP, newCmd);
else if (m_bVerbose)
std::println("{}", verboseString("Failed nix from path: {}", NIX_DEVELOP.error()));
NIX_DEVELOP = getNixDevelopFromProfile();
if (NIX_DEVELOP)
return std::format("nix develop '{}' --command bash -c $'{}'", *NIX_DEVELOP, newCmd);
else if (m_bVerbose)
std::println("{}", verboseString("Failed nix from profile: {}", NIX_DEVELOP.error()));
return std::unexpected("hyprland is nix, but hyprpm failed to obtain a nix develop shell for build cmd");
}

View file

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

View file

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

View file

@ -1,13 +1,11 @@
#pragma once
#include <string>
#include <optional>
namespace NSys {
bool isSuperuser();
int getUID();
int getEUID();
std::optional<std::string> findInPath(const std::string& exe);
bool isSuperuser();
int getUID();
int getEUID();
// NOLINTNEXTLINE
namespace root {
@ -22,4 +20,4 @@ namespace NSys {
// Do not use this unless absolutely necessary!
std::string runAsSuperuserUnsafe(const std::string& cmd);
};
};
};

View file

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

View file

@ -96,9 +96,7 @@ endfunction()
protocolnew("staging/pointer-warp" "pointer-warp-v1" false)
protocolnew("stable/xdg-shell" "xdg-shell" false)
protocolnew("unstable/keyboard-shortcuts-inhibit" "keyboard-shortcuts-inhibit-unstable-v1" false)
clientNew("pointer-warp" PROTOS "pointer-warp-v1" "xdg-shell")
clientNew("pointer-scroll" PROTOS "xdg-shell")
clientNew("child-window" PROTOS "xdg-shell")
clientNew("shortcut-inhibitor" PROTOS "xdg-shell" "keyboard-shortcuts-inhibit-unstable-v1")
clientNew("child-window" PROTOS "xdg-shell")

View file

@ -3,7 +3,6 @@
#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <wayland-client.h>
#include <wayland.hpp>
@ -333,4 +332,4 @@ int main(int argc, char** argv) {
wl_display_disconnect(display);
return 0;
}
}

View file

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

View file

@ -2,7 +2,6 @@
#include <sys/poll.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <print>
#include <format>
#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
#include <src/config/ConfigManager.hpp>
#include <src/config/ConfigDescriptions.hpp>
#include <src/layout/IHyprLayout.hpp>
#include <src/managers/LayoutManager.hpp>
#include <src/managers/input/InputManager.hpp>
#include <src/managers/PointerManager.hpp>
#include <src/managers/input/trackpad/TrackpadGestures.hpp>
#include <src/desktop/rule/windowRule/WindowRuleEffectContainer.hpp>
#include <src/desktop/rule/layerRule/LayerRuleEffectContainer.hpp>
#include <src/desktop/rule/windowRule/WindowRuleApplicator.hpp>
#include <src/desktop/view/LayerSurface.hpp>
#include <src/Compositor.hpp>
#include <src/desktop/state/FocusState.hpp>
#include <src/layout/LayoutManager.hpp>
#undef private
#include <hyprutils/utils/ScopeGuard.hpp>
@ -54,9 +53,8 @@ static SDispatchResult snapMove(std::string in) {
Vector2D pos = PLASTWINDOW->m_realPosition->goal();
Vector2D size = PLASTWINDOW->m_realSize->goal();
g_layoutManager->performSnap(pos, size, PLASTWINDOW->layoutTarget(), MBIND_MOVE, -1, size);
PLASTWINDOW->layoutTarget()->setPositionGlobal(CBox{pos, size});
g_pLayoutManager->getCurrentLayout()->performSnap(pos, size, PLASTWINDOW, MBIND_MOVE, -1, size);
*PLASTWINDOW->m_realPosition = pos.round();
return {};
}
@ -272,85 +270,32 @@ static SDispatchResult keybind(std::string in) {
return {};
}
static Desktop::Rule::CWindowRuleEffectContainer::storageType windowRuleIDX = 0;
static Desktop::Rule::CWindowRuleEffectContainer::storageType ruleIDX = 0;
//
static SDispatchResult addWindowRule(std::string in) {
windowRuleIDX = Desktop::Rule::windowEffects()->registerEffect("plugin_rule");
static SDispatchResult addRule(std::string in) {
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 {};
}
static SDispatchResult checkWindowRule(std::string in) {
static SDispatchResult checkRule(std::string in) {
const auto PLASTWINDOW = Desktop::focusState()->window();
if (!PLASTWINDOW)
return {.success = false, .error = "No window"};
if (!PLASTWINDOW->m_ruleApplicator->m_otherProps.props.contains(windowRuleIDX))
if (!PLASTWINDOW->m_ruleApplicator->m_otherProps.props.contains(ruleIDX))
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 {};
}
static Desktop::Rule::CLayerRuleEffectContainer::storageType layerRuleIDX = 0;
static SDispatchResult addLayerRule(std::string in) {
layerRuleIDX = Desktop::Rule::layerEffects()->registerEffect("plugin_rule");
if (Desktop::Rule::layerEffects()->registerEffect("plugin_rule") != layerRuleIDX)
return {.success = false, .error = "re-registering returned a different id?"};
return {};
}
static SDispatchResult checkLayerRule(std::string in) {
if (g_pCompositor->m_layers.size() != 3)
return {.success = false, .error = "Layers under test not here"};
for (const auto& layer : g_pCompositor->m_layers) {
if (layer->m_namespace == "rule-layer") {
if (!layer->m_ruleApplicator->m_otherProps.props.contains(layerRuleIDX))
return {.success = false, .error = "No rule"};
if (layer->m_ruleApplicator->m_otherProps.props[layerRuleIDX]->effect != "effect")
return {.success = false, .error = "Effect isn't \"effect\""};
} else if (layer->m_namespace == "norule-layer") {
if (layer->m_ruleApplicator->m_otherProps.props.contains(layerRuleIDX))
return {.success = false, .error = "Rule even though it shouldn't"};
} else
return {.success = false, .error = "Unrecognized layer"};
}
return {};
}
static SDispatchResult floatingFocusOnFullscreen(std::string in) {
const auto PLASTWINDOW = Desktop::focusState()->window();
if (!PLASTWINDOW)
return {.success = false, .error = "No window"};
if (!PLASTWINDOW->m_isFloating)
return {.success = false, .error = "Window must be floating"};
if (PLASTWINDOW->m_alpha != 1.f)
return {.success = false, .error = "floating window doesnt restore it opacity when focused on fullscreen workspace"};
if (!PLASTWINDOW->m_createdOverFullscreen)
return {.success = false, .error = "floating window doesnt get flagged as createdOverFullscreen"};
return {};
}
APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
PHANDLE = handle;
@ -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:click", ::click);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:keybind", ::keybind);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:add_window_rule", ::addWindowRule);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:check_window_rule", ::checkWindowRule);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:add_layer_rule", ::addLayerRule);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:check_layer_rule", ::checkLayerRule);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:floating_focus_on_fullscreen", ::floatingFocusOnFullscreen);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:add_rule", ::addRule);
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:check_rule", ::checkRule);
// init mouse
g_mouse = CTestMouse::create(false);

View file

@ -39,16 +39,6 @@ namespace Colors {
TESTS_PASSED++; \
}
#define EXPECT_NOT(expr, val) \
if (const auto RESULT = expr; RESULT == (val)) { \
NLog::log("{}Failed: {}{}, expected not {}, got {}. Source: {}@{}.", Colors::RED, Colors::RESET, #expr, val, RESULT, __FILE__, __LINE__); \
ret = 1; \
TESTS_FAILED++; \
} else { \
NLog::log("{}Passed: {}{}. Got {}", Colors::GREEN, Colors::RESET, #expr, val); \
TESTS_PASSED++; \
}
#define EXPECT_VECTOR2D(expr, val) \
do { \
const auto& RESULT = expr; \

View file

@ -8,7 +8,6 @@
#include <hyprutils/os/Process.hpp>
#include <sys/poll.h>
#include <unistd.h>
#include <csignal>
#include <thread>
@ -118,34 +117,7 @@ static bool test() {
Tests::killAllWindows();
EXPECT(Tests::windowCount(), 0);
// test that child windows (shouldBeFloated) are not auto-grouped
NLog::log("{}Test child windows are not auto-grouped", Colors::GREEN);
auto kitty = Tests::spawnKitty();
if (!kitty) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
// create group and enable auto-grouping
OK(getFromSocket("/dispatch togglegroup"));
OK(getFromSocket("/keyword group:auto_group true"));
SClient client2;
if (!startClient(client2))
return false;
EXPECT(Tests::windowCount(), 2);
createChild(client2);
EXPECT(Tests::windowCount(), 3);
// child has set_parent so shouldBeFloated returns true, it should not be auto-grouped
EXPECT_COUNT_STRING(getFromSocket("/clients"), "grouped: 0", 1);
stopClient(client2);
Tests::killAllWindows();
EXPECT(Tests::windowCount(), 0);
return !ret;
}
REGISTER_CLIENT_TEST_FN(test);
REGISTER_CLIENT_TEST_FN(test);

View file

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

View file

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

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
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
OK(getFromSocket("/reload"));
}
static void test13349() {
// Test if dwindle properly uses a focal point to place a new window.
// exposed by #13349 as a regression from #12890
for (auto const& win : {"a", "b", "c"}) {
if (!Tests::spawnKitty(win)) {
NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win);
++TESTS_FAILED;
ret = 1;
return;
}
}
OK(getFromSocket("/dispatch focuswindow class:c"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "at: 967,547");
EXPECT_CONTAINS(str, "size: 931,511");
}
OK(getFromSocket("/dispatch movewindow l"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "at: 497,22");
EXPECT_CONTAINS(str, "size: 456,1036");
}
OK(getFromSocket("/dispatch movewindow r"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "at: 967,22");
EXPECT_CONTAINS(str, "size: 456,1036");
}
// clean up
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
}
static void testSplit() {
// Test various split methods
Tests::spawnKitty("a");
// these must not crash
EXPECT_NOT(getFromSocket("/dispatch layoutmsg swapsplit"), "ok");
EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio 1 exact"), "ok");
Tests::spawnKitty("b");
OK(getFromSocket("/dispatch focuswindow class:a"));
OK(getFromSocket("/dispatch layoutmsg splitratio -0.2"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "at: 22,22");
EXPECT_CONTAINS(str, "size: 743,1036");
}
OK(getFromSocket("/dispatch layoutmsg splitratio 1.6 exact"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "at: 22,22");
EXPECT_CONTAINS(str, "size: 1495,1036");
}
EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio fhne exact"), "ok");
EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio exact"), "ok");
EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio -....9"), "ok");
EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio ..9"), "ok");
EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio"), "ok");
OK(getFromSocket("/dispatch layoutmsg togglesplit"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "at: 22,22");
EXPECT_CONTAINS(str, "size: 1876,823");
}
OK(getFromSocket("/dispatch layoutmsg swapsplit"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "at: 22,859");
EXPECT_CONTAINS(str, "size: 1876,199");
}
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
}
static void testRotatesplit() {
OK(getFromSocket("r/keyword general:gaps_in 0"));
OK(getFromSocket("r/keyword general:gaps_out 0"));
OK(getFromSocket("r/keyword general:border_size 0"));
for (auto const& win : {"a", "b"}) {
if (!Tests::spawnKitty(win)) {
NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win);
++TESTS_FAILED;
ret = 1;
return;
}
}
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 0,0");
EXPECT_CONTAINS(str, "size: 960,1080");
}
// test 4 repeated rotations by 90 degrees
OK(getFromSocket("/dispatch layoutmsg rotatesplit"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 0,0");
EXPECT_CONTAINS(str, "size: 1920,540");
}
OK(getFromSocket("/dispatch layoutmsg rotatesplit"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 960,0");
EXPECT_CONTAINS(str, "size: 960,1080");
}
OK(getFromSocket("/dispatch layoutmsg rotatesplit"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 0,540");
EXPECT_CONTAINS(str, "size: 1920,540");
}
OK(getFromSocket("/dispatch layoutmsg rotatesplit"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 0,0");
EXPECT_CONTAINS(str, "size: 960,1080");
}
// test different angles
OK(getFromSocket("/dispatch layoutmsg rotatesplit 180"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 960,0");
EXPECT_CONTAINS(str, "size: 960,1080");
}
OK(getFromSocket("/dispatch layoutmsg rotatesplit 270"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 0,540");
EXPECT_CONTAINS(str, "size: 1920,540");
}
OK(getFromSocket("/dispatch layoutmsg rotatesplit 360"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 0,0");
EXPECT_CONTAINS(str, "size: 1920,540");
}
// test negative angles
OK(getFromSocket("/dispatch layoutmsg rotatesplit -90"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 0,0");
EXPECT_CONTAINS(str, "size: 960,1080");
}
OK(getFromSocket("/dispatch layoutmsg rotatesplit -180"));
{
auto str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "at: 960,0");
EXPECT_CONTAINS(str, "size: 960,1080");
}
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
OK(getFromSocket("/reload"));
}
static bool test() {
@ -234,15 +43,6 @@ static bool test() {
NLog::log("{}Testing float clamp", Colors::GREEN);
testFloatClamp();
NLog::log("{}Testing #13349", Colors::GREEN);
test13349();
NLog::log("{}Testing splits", Colors::GREEN);
testSplit();
NLog::log("{}Testing rotatesplit", Colors::GREEN);
testRotatesplit();
// clean up
NLog::log("Cleaning up", Colors::YELLOW);
getFromSocket("/dispatch workspace 1");

View file

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

View file

@ -127,34 +127,6 @@ static bool test() {
ret = 1;
}
// test movegroupwindow: focus should follow the moved window
NLog::log("{}Test movegroupwindow focus follows window", Colors::YELLOW);
try {
auto str = getFromSocket("/activewindow");
auto activeBeforeMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16);
OK(getFromSocket("/dispatch movegroupwindow f"));
str = getFromSocket("/activewindow");
auto activeAfterMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16);
EXPECT(activeAfterMove, activeBeforeMove);
} catch (...) {
NLog::log("{}Fail at getting prop", Colors::RED);
ret = 1;
}
// and backwards
NLog::log("{}Test movegroupwindow backwards", Colors::YELLOW);
try {
auto str = getFromSocket("/activewindow");
auto activeBeforeMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16);
OK(getFromSocket("/dispatch movegroupwindow b"));
str = getFromSocket("/activewindow");
auto activeAfterMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16);
EXPECT(activeAfterMove, activeBeforeMove);
} catch (...) {
NLog::log("{}Fail at getting prop", Colors::RED);
ret = 1;
}
NLog::log("{}Disable autogrouping", Colors::YELLOW);
OK(getFromSocket("/keyword group:auto_group false"));
@ -201,99 +173,6 @@ static bool test() {
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 0);
// test movewindoworgroup: direction should be respected when extracting from group
NLog::log("{}Test movewindoworgroup respects direction out of group", Colors::YELLOW);
OK(getFromSocket("/keyword group:groupbar:enabled 0"));
{
auto kittyE = Tests::spawnKitty();
if (!kittyE) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
// group kitty, and new windows should be auto-grouped
OK(getFromSocket("/dispatch togglegroup"));
auto kittyF = Tests::spawnKitty();
if (!kittyF) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
EXPECT(Tests::windowCount(), 2);
// both windows should be grouped at the same position
{
auto str = getFromSocket("/clients");
EXPECT_COUNT_STRING(str, "at: 22,22", 2);
}
// move active window out of group to the right
NLog::log("{}Test movewindoworgroup r", Colors::YELLOW);
OK(getFromSocket("/dispatch movewindoworgroup r"));
// the group should stay at x=22, the extracted window should be to the right
{
auto str = getFromSocket("/clients");
EXPECT_COUNT_STRING(str, "at: 22,22", 1);
}
// move it back into the group
OK(getFromSocket("/dispatch moveintogroup l"));
// move active window out of group downward
NLog::log("{}Test movewindoworgroup d", Colors::YELLOW);
OK(getFromSocket("/dispatch movewindoworgroup d"));
// the group should stay at y=22, the extracted window should be below
{
auto str = getFromSocket("/clients");
EXPECT_COUNT_STRING(str, "at: 22,22", 1);
}
Tests::killAllWindows();
EXPECT(Tests::windowCount(), 0);
}
// test that we deny a floated window getting auto-grouped into a tiled group.
NLog::log("{}Test that we deny a floated window getting auto-grouped into a tiled group.", Colors::GREEN);
OK(getFromSocket("/keyword windowrule[kitty-tiled]:match:class kitty_tiled"));
OK(getFromSocket("/keyword windowrule[kitty-tiled]:tile yes"));
auto kittyProcE = Tests::spawnKitty("kitty_tiled");
if (!kittyProcE) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
OK(getFromSocket("/dispatch togglegroup"));
OK(getFromSocket("/keyword windowrule[kitty-floated]:match:class kitty_floated"));
OK(getFromSocket("/keyword windowrule[kitty-floated]:float yes"));
auto kittyProcF = Tests::spawnKitty("kitty_floated");
if (!kittyProcF) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
return false;
}
EXPECT(Tests::windowCount(), 2);
{
auto clients = getFromSocket("/clients");
auto classPos = clients.find("class: kitty_floated");
if (classPos == std::string::npos) {
NLog::log("{}Could not find kitty_floated in clients output", Colors::RED);
ret = 1;
} else {
auto entryStart = clients.rfind("Window ", classPos);
auto entryEnd = clients.find("\n\n", classPos);
auto windowEntry = clients.substr(entryStart, entryEnd - entryStart);
EXPECT_CONTAINS(windowEntry, "floating: 1");
EXPECT_CONTAINS(windowEntry, "grouped: 0");
}
}
Tests::killAllWindows();
EXPECT(Tests::windowCount(), 0);
return !ret;
}

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 -j"), R"({"min_size": [100,50]})");
// expr-based min/max _size
getFromSocket("/dispatch setfloating class:kitty"); // need to set floating for tests below
getFromSocket("/dispatch setprop class:kitty max_size 90+10 25*2"); // set max to the same as min above, forcing window to 100*50
EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size"), "100 50");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size -j"), R"({"max_size": [100,50]})");
getFromSocket("/dispatch setprop class:kitty min_size window_w*0.5 window_h-10");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size"), "50 40");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size -j"), R"({"min_size": [50,40]})");
getFromSocket("/dispatch settiled class:kitty"); // go back to tiled for consistency
// opacity
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity"), "1");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity -j"), R"({"opacity": 1})");

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

@ -3,53 +3,7 @@
#include "../../hyprctlCompat.hpp"
#include "tests.hpp"
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 int ret = 0;
static void focusMasterPrevious() {
// setup
@ -90,74 +44,11 @@ static void focusMasterPrevious() {
OK(getFromSocket("/dispatch layoutmsg focusmaster previous"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: master");
testOrientations();
// clean up
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
}
static void testFsBehavior() {
// Master will re-send data to fullscreen / maximized windows, which can interfere with misc:on_focus_under_fullscreen
// check that it doesn't.
for (auto const& win : {"master", "slave1", "slave2"}) {
if (!Tests::spawnKitty(win)) {
NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win);
++TESTS_FAILED;
ret = 1;
return;
}
}
OK(getFromSocket("/dispatch focuswindow class:master"));
OK(getFromSocket("/dispatch fullscreen 1"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "at: 22,22");
EXPECT_CONTAINS(str, "size: 1876,1036");
EXPECT_CONTAINS(str, "class: master");
}
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 1"));
Tests::spawnKitty("new_master");
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "at: 22,22");
EXPECT_CONTAINS(str, "size: 1876,1036");
EXPECT_CONTAINS(str, "class: new_master");
EXPECT_CONTAINS(str, "fullscreen: 1");
}
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 0"));
Tests::spawnKitty("ignored");
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "at: 22,22");
EXPECT_CONTAINS(str, "size: 1876,1036");
EXPECT_CONTAINS(str, "class: new_master");
EXPECT_CONTAINS(str, "fullscreen: 1");
}
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 2"));
Tests::spawnKitty("vaxwashere");
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: vaxwashere");
EXPECT_CONTAINS(str, "fullscreen: 0");
}
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
}
static bool test() {
NLog::log("{}Testing Master layout", Colors::GREEN);
@ -169,9 +60,6 @@ static bool test() {
NLog::log("{}Testing `focusmaster previous` layoutmsg", Colors::GREEN);
focusMasterPrevious();
NLog::log("{}Testing fs behavior", Colors::GREEN);
testFsBehavior();
// clean up
NLog::log("Cleaning up", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace 1"));

View file

@ -18,121 +18,6 @@ using namespace Hyprutils::Memory;
#define UP CUniquePointer
#define SP CSharedPointer
// Uncomment once test vm can run hyprland-dialog
// static void testAnrDialogs() {
// NLog::log("{}Testing ANR dialogs", Colors::YELLOW);
//
// OK(getFromSocket("/keyword misc:enable_anr_dialog true"));
// OK(getFromSocket("/keyword misc:anr_missed_pings 1"));
//
// NLog::log("{}ANR dialog: regular workspaces", Colors::YELLOW);
// {
// OK(getFromSocket("/dispatch workspace 2"));
//
// auto kitty = Tests::spawnKitty("bad_kitty");
//
// if (!kitty) {
// ret = 1;
// return;
// }
//
// {
// auto str = getFromSocket("/activewindow");
// EXPECT_CONTAINS(str, "workspace: 2");
// }
//
// OK(getFromSocket("/dispatch workspace 1"));
//
// ::kill(kitty->pid(), SIGSTOP);
// Tests::waitUntilWindowsN(2);
//
// {
// auto str = getFromSocket("/activeworkspace");
// EXPECT_CONTAINS(str, "windows: 0");
// }
//
// {
// OK(getFromSocket("/dispatch focuswindow class:hyprland-dialog"))
// auto str = getFromSocket("/activewindow");
// EXPECT_CONTAINS(str, "workspace: 2");
// }
// }
//
// Tests::killAllWindows();
//
// NLog::log("{}ANR dialog: named workspaces", Colors::YELLOW);
// {
// OK(getFromSocket("/dispatch workspace name:yummy"));
//
// auto kitty = Tests::spawnKitty("bad_kitty");
//
// if (!kitty) {
// ret = 1;
// return;
// }
//
// {
// auto str = getFromSocket("/activewindow");
// EXPECT_CONTAINS(str, "yummy");
// }
//
// OK(getFromSocket("/dispatch workspace 1"));
//
// ::kill(kitty->pid(), SIGSTOP);
// Tests::waitUntilWindowsN(2);
//
// {
// auto str = getFromSocket("/activeworkspace");
// EXPECT_CONTAINS(str, "windows: 0");
// }
//
// {
// OK(getFromSocket("/dispatch focuswindow class:hyprland-dialog"))
// auto str = getFromSocket("/activewindow");
// EXPECT_CONTAINS(str, "yummy");
// }
// }
//
// Tests::killAllWindows();
//
// NLog::log("{}ANR dialog: special workspaces", Colors::YELLOW);
// {
// OK(getFromSocket("/dispatch workspace special:apple"));
//
// auto kitty = Tests::spawnKitty("bad_kitty");
//
// if (!kitty) {
// ret = 1;
// return;
// }
//
// {
// auto str = getFromSocket("/activewindow");
// EXPECT_CONTAINS(str, "special:apple");
// }
//
// OK(getFromSocket("/dispatch togglespecialworkspace apple"));
// OK(getFromSocket("/dispatch workspace 1"));
//
// ::kill(kitty->pid(), SIGSTOP);
// Tests::waitUntilWindowsN(2);
//
// {
// auto str = getFromSocket("/activeworkspace");
// EXPECT_CONTAINS(str, "windows: 0");
// }
//
// {
// OK(getFromSocket("/dispatch focuswindow class:hyprland-dialog"))
// auto str = getFromSocket("/activewindow");
// EXPECT_CONTAINS(str, "special:apple");
// }
// }
//
// OK(getFromSocket("/reload"));
// Tests::killAllWindows();
// }
static bool test() {
NLog::log("{}Testing config: misc:", Colors::GREEN);

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 <chrono>
#include <cstdlib>
@ -25,28 +24,6 @@ static bool spawnKitty(const std::string& class_, const std::vector<std::string>
return true;
}
/// Spawns a kitty and creates a file and returns its name. The removal of the file triggers
/// activation of the spawned kitty window.
///
/// On failure, returns an empty string, possibly leaving a temporary file.
static std::string spawnKittyActivating(const std::string& class_ = "kitty_activating") {
// `XXXXXX` is what `mkstemp` expects to find in the string
std::string tmpFilename = (std::filesystem::temp_directory_path() / "XXXXXX").string();
int fd = mkstemp(tmpFilename.data());
if (fd < 0) {
NLog::log("{}Error: could not create tmp file: errno {}", Colors::RED, errno);
return "";
}
(void)close(fd);
bool ok =
spawnKitty(class_, {"-o", "allow_remote_control=yes", "--", "/bin/sh", "-c", "while [ -f \"" + tmpFilename + "\" ]; do :; done; kitten @ focus-window; sleep infinity"});
if (!ok) {
NLog::log("{}Error: failed to spawn kitty", Colors::RED);
return "";
}
return tmpFilename;
}
static std::string getWindowAttribute(const std::string& winInfo, const std::string& attr) {
auto pos = winInfo.find(attr);
if (pos == std::string::npos) {
@ -93,9 +70,9 @@ static void testSwapWindow() {
{
getFromSocket("/dispatch focuswindow class:kitty_A");
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"));
EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("{}", pos));
@ -220,7 +197,7 @@ static void testGroupRules() {
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");
auto winClass = getWindowAttribute(activeWin, "class:");
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;
while (!isActiveWindow(class_, fullscreen, false)) {
++cnt;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (cnt > maxTries) {
return isActiveWindow(class_, fullscreen, logLastCheck);
return isActiveWindow(class_, fullscreen, true);
}
}
return true;
@ -255,6 +232,24 @@ static bool testWindowFocusOnFullscreenConflict() {
OK(getFromSocket("/keyword misc:focus_on_activate true"));
auto spawnKittyActivating = [] -> std::string {
// `XXXXXX` is what `mkstemp` expects to find in the string
std::string tmpFilename = (std::filesystem::temp_directory_path() / "XXXXXX").string();
int fd = mkstemp(tmpFilename.data());
if (fd < 0) {
NLog::log("{}Error: could not create tmp file: errno {}", Colors::RED, errno);
return "";
}
(void)close(fd);
bool ok = spawnKitty("kitty_activating",
{"-o", "allow_remote_control=yes", "--", "/bin/sh", "-c", "while [ -f \"" + tmpFilename + "\" ]; do :; done; kitten @ focus-window; sleep infinity"});
if (!ok) {
NLog::log("{}Error: failed to spawn kitty", Colors::RED);
return "";
}
return tmpFilename;
};
// Unfullscreen on conflict
{
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 2"));
@ -378,61 +373,6 @@ static void testMaximizeSize() {
EXPECT(Tests::windowCount(), 0);
}
static void testFloatingFocusOnFullscreen() {
NLog::log("{}Testing floating focus on fullscreen", Colors::GREEN);
EXPECT(spawnKitty("kitty_A"), true);
OK(getFromSocket("/dispatch togglefloating"));
EXPECT(spawnKitty("kitty_B"), true);
OK(getFromSocket("/dispatch fullscreen 1"));
OK(getFromSocket("/dispatch cyclenext"));
OK(getFromSocket("/dispatch plugin:test:floating_focus_on_fullscreen"));
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 0);
}
static void testGroupFallbackFocus() {
NLog::log("{}Testing group fallback focus", Colors::GREEN);
EXPECT(spawnKitty("kitty_A"), true);
OK(getFromSocket("/dispatch togglegroup"));
EXPECT(spawnKitty("kitty_B"), true);
EXPECT(spawnKitty("kitty_C"), true);
EXPECT(spawnKitty("kitty_D"), true);
{
auto str = getFromSocket("/activewindow");
EXPECT(str.contains("class: kitty_D"), true);
}
OK(getFromSocket("/dispatch focuswindow class:kitty_B"));
OK(getFromSocket("/dispatch focuswindow class:kitty_D"));
OK(getFromSocket("/dispatch killactive"));
Tests::waitUntilWindowsN(3);
// Focus must return to the last focus, in this case B.
{
auto str = getFromSocket("/activewindow");
EXPECT(str.contains("class: kitty_B"), true);
}
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 0);
}
static void testBringActiveToTopMouseMovement() {
NLog::log("{}Testing bringactivetotop mouse movement", Colors::GREEN);
@ -472,192 +412,6 @@ static void testBringActiveToTopMouseMovement() {
Tests::killAllWindows();
}
static void testInitialFloatSize() {
NLog::log("{}Testing initial float size", Colors::GREEN);
Tests::killAllWindows();
OK(getFromSocket("/keyword windowrule match:class kitty, float yes"));
OK(getFromSocket("/keyword input:float_switch_override_focus 0"));
EXPECT(spawnKitty("kitty"), true);
{
// Kitty by default opens as 640x400, if this changes this test will break
auto str = getFromSocket("/clients");
EXPECT(str.contains("size: 640,400"), true);
}
OK(getFromSocket("/reload"));
Tests::killAllWindows();
OK(getFromSocket("/dispatch exec [float yes]kitty"));
Tests::waitUntilWindowsN(1);
{
// Kitty by default opens as 640x400, if this changes this test will break
auto str = getFromSocket("/clients");
EXPECT(str.contains("size: 640,400"), true);
EXPECT(str.contains("floating: 1"), true);
}
Tests::killAllWindows();
}
/// Tests that the `focus_on_activate` effect of window rules always overrides
/// the `misc:focus_on_activate` variable.
static bool testWindowRuleFocusOnActivate() {
OK(getFromSocket("/reload"));
if (!spawnKitty("kitty_default")) {
NLog::log("{}Error: failed to spawn kitty", Colors::RED);
return false;
}
// Do not focus anyone automatically
///////////OK(getFromSocket("/keyword windowrule match:class .*, no_initial_focus true"));
// `focus_on_activate off` takes over
{
OK(getFromSocket("/keyword misc:focus_on_activate true"));
OK(getFromSocket("/keyword windowrule match:class kitty_antifocus, focus_on_activate off"));
const std::string removeToActivate = spawnKittyActivating("kitty_antifocus");
if (removeToActivate.empty()) {
return false;
}
EXPECT(waitForActiveWindow("kitty_antifocus"), true);
OK(getFromSocket("/dispatch focuswindow class:kitty_default"));
EXPECT(isActiveWindow("kitty_default"), true);
std::filesystem::remove(removeToActivate);
// The focus should NOT transition, since the window rule explicitly forbids that
EXPECT(waitForActiveWindow("kitty_antifocus", '0', false), false);
}
// `focus_on_activate on` takes over
{
OK(getFromSocket("/keyword misc:focus_on_activate false"));
OK(getFromSocket("/keyword windowrule match:class kitty_superfocus, focus_on_activate on"));
const std::string removeToActivate = spawnKittyActivating("kitty_superfocus");
if (removeToActivate.empty()) {
return false;
}
EXPECT(waitForActiveWindow("kitty_superfocus"), true);
OK(getFromSocket("/dispatch focuswindow class:kitty_default"));
EXPECT(isActiveWindow("kitty_default"), true);
std::filesystem::remove(removeToActivate);
// Now that we requested activation, the focus SHOULD transition to kitty_superfocus, according to the window rule
EXPECT(waitForActiveWindow("kitty_superfocus"), true);
}
NLog::log("{}Reloading config", Colors::YELLOW);
OK(getFromSocket("/reload"));
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 0);
return true;
}
// tests if a pinned window contains the valid workspace after change
static bool testPinnedWorkspacesValid() {
OK(getFromSocket("/reload"));
getFromSocket("/dispatch workspace 1337");
if (!spawnKitty("kitty")) {
NLog::log("{}Error: failed to spawn kitty", Colors::RED);
return false;
}
OK(getFromSocket("/dispatch setfloating class:kitty"));
OK(getFromSocket("/dispatch pin class:kitty"));
{
auto str = getFromSocket("/activewindow");
EXPECT(str.contains("workspace: 1337"), true);
EXPECT(str.contains("pinned: 1"), true);
}
getFromSocket("/dispatch workspace 1338");
{
auto str = getFromSocket("/activewindow");
EXPECT(str.contains("workspace: 1338"), true);
EXPECT(str.contains("pinned: 1"), true);
}
OK(getFromSocket("/dispatch settiled class:kitty"))
{
auto str = getFromSocket("/activewindow");
EXPECT(str.contains("workspace: 1338"), true);
EXPECT(str.contains("pinned: 0"), true);
}
NLog::log("{}Reloading config", Colors::YELLOW);
OK(getFromSocket("/reload"));
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 0);
return true;
}
static bool testWindowRuleWorkspaceEmpty() {
NLog::log("{}Testing windowrule workspace empty", Colors::YELLOW);
OK(getFromSocket("/reload"));
OK(getFromSocket("/keyword windowrule match:class kitty_A, workspace empty"));
OK(getFromSocket("/keyword windowrule match:class kitty_B, workspace emptyn"));
getFromSocket("/dispatch workspace 3");
if (!spawnKitty("kitty")) {
NLog::log("{}Error: failed to spawn kitty", Colors::RED);
return false;
}
{
auto str = getFromSocket("/activewindow");
EXPECT(str.contains("workspace: 3"), true);
}
if (!spawnKitty("kitty_A")) {
NLog::log("{}Error: failed to spawn kitty", Colors::RED);
return false;
}
{
auto str = getFromSocket("/activewindow");
EXPECT(str.contains("workspace: 1"), true);
}
getFromSocket("/dispatch workspace 3");
if (!spawnKitty("kitty_B")) {
NLog::log("{}Error: failed to spawn kitty", Colors::RED);
return false;
}
{
auto str = getFromSocket("/activewindow");
EXPECT(str.contains("workspace: 4"), true);
}
Tests::killAllWindows();
return true;
}
static bool test() {
NLog::log("{}Testing windows", Colors::GREEN);
@ -771,14 +525,14 @@ static bool test() {
EXPECT(Tests::windowCount(), 3);
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");
EXPECT_NOT_CONTAINS(str, "floating: 1");
getFromSocket("/dispatch setfloating class:XEyes");
EXPECT_CONTAINS(str, "floating: 1");
getFromSocket("/dispatch settiled class:XEyes");
std::this_thread::sleep_for(std::chrono::milliseconds(200));
str = getFromSocket("/clients");
EXPECT_CONTAINS(str, "floating: 1");
EXPECT_NOT_CONTAINS(str, "floating: 1");
}
// kill all
@ -962,23 +716,6 @@ static bool test() {
Tests::killAllWindows();
OK(getFromSocket("/keyword windowrule[border-magic-kitty]:match:class border_kitty"));
OK(getFromSocket("/keyword windowrule[border-magic-kitty]:border_color rgba(c6ff00ff) rgba(ff0000ee) 45deg"));
if (!spawnKitty("border_kitty"))
return false;
OK(getFromSocket("/dispatch focuswindow class:border_kitty"));
{
auto str = getFromSocket("/getprop active active_border_color");
EXPECT_CONTAINS(str, "ffc6ff00");
EXPECT_CONTAINS(str, "eeff0000");
EXPECT_CONTAINS(str, "45deg");
}
Tests::killAllWindows();
if (!spawnKitty("tag_kitty"))
return false;
@ -1062,8 +799,7 @@ static bool test() {
Tests::killAllWindows();
// test expression rules
OK(getFromSocket("/keyword windowrule match:class expr_kitty, float yes, size monitor_w*0.5 monitor_h*0.5, min_size monitor_w*0.25 monitor_h*0.25, "
"max_size monitor_w*0.75 monitor_h*0.75, move 20+(monitor_w*0.1) monitor_h*0.5"));
OK(getFromSocket("/keyword windowrule match:class expr_kitty, float yes, size monitor_w*0.5 monitor_h*0.5, move 20+(monitor_w*0.1) monitor_h*0.5"));
if (!spawnKitty("expr_kitty"))
return false;
@ -1073,20 +809,12 @@ static bool test() {
EXPECT_CONTAINS(str, "floating: 1");
EXPECT_CONTAINS(str, "at: 212,540");
EXPECT_CONTAINS(str, "size: 960,540");
auto min = getFromSocket("/getprop active min_size");
EXPECT_CONTAINS(min, "480");
EXPECT_CONTAINS(min, "270");
auto max = getFromSocket("/getprop active max_size");
EXPECT_CONTAINS(max, "1440");
EXPECT_CONTAINS(max, "810");
}
OK(getFromSocket("/reload"));
Tests::killAllWindows();
OK(getFromSocket("/dispatch plugin:test:add_window_rule"));
OK(getFromSocket("/dispatch plugin:test:add_rule"));
OK(getFromSocket("/reload"));
OK(getFromSocket("/keyword windowrule match:class plugin_kitty, plugin_rule effect"));
@ -1094,12 +822,12 @@ static bool test() {
if (!spawnKitty("plugin_kitty"))
return false;
OK(getFromSocket("/dispatch plugin:test:check_window_rule"));
OK(getFromSocket("/dispatch plugin:test:check_rule"));
OK(getFromSocket("/reload"));
Tests::killAllWindows();
OK(getFromSocket("/dispatch plugin:test:add_window_rule"));
OK(getFromSocket("/dispatch plugin:test:add_rule"));
OK(getFromSocket("/reload"));
OK(getFromSocket("/keyword windowrule[test-plugin-rule]:match:class plugin_kitty"));
@ -1108,20 +836,16 @@ static bool test() {
if (!spawnKitty("plugin_kitty"))
return false;
OK(getFromSocket("/dispatch plugin:test:check_window_rule"));
OK(getFromSocket("/dispatch plugin:test:check_rule"));
OK(getFromSocket("/reload"));
Tests::killAllWindows();
testGroupRules();
testMaximizeSize();
testFloatingFocusOnFullscreen();
testBringActiveToTopMouseMovement();
testGroupFallbackFocus();
testInitialFloatSize();
testWindowRuleFocusOnActivate();
testPinnedWorkspacesValid();
testWindowRuleWorkspaceEmpty();
NLog::log("{}Reloading config", Colors::YELLOW);
OK(getFromSocket("/reload"));

View file

@ -20,91 +20,6 @@ using namespace Hyprutils::Utils;
#define UP CUniquePointer
#define SP CSharedPointer
static bool testSpecialWorkspaceFullscreen() {
NLog::log("{}Testing special workspace fullscreen detection", Colors::YELLOW);
CScopeGuard guard = {[&]() {
NLog::log("{}Cleaning up special workspace fullscreen test", Colors::YELLOW);
// Close special workspace if open
auto monitors = getFromSocket("/monitors");
if (monitors.contains("(special:test_fs_special)") && !monitors.contains("special workspace: 0 ()"))
getFromSocket("/dispatch togglespecialworkspace test_fs_special");
Tests::killAllWindows();
OK(getFromSocket("/reload"));
}};
getFromSocket("/dispatch workspace 1");
EXPECT(Tests::windowCount(), 0);
NLog::log("{}Test 1: Fullscreen detection on special workspace", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace special:test_fs_special"));
if (!Tests::spawnKitty("kitty_special"))
return false;
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitty_special");
EXPECT_CONTAINS(str, "(special:test_fs_special)");
}
OK(getFromSocket("/dispatch fullscreen 0"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "fullscreen: 2");
}
{
auto str = getFromSocket("/monitors");
EXPECT_CONTAINS(str, "(special:test_fs_special)");
}
NLog::log("{}Test 2: Special workspace fullscreen precedence", Colors::YELLOW);
// Close special workspace before spawning on regular workspace
OK(getFromSocket("/dispatch togglespecialworkspace test_fs_special"));
getFromSocket("/dispatch workspace 1");
if (!Tests::spawnKitty("kitty_regular"))
return false;
OK(getFromSocket("/dispatch fullscreen 0"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitty_regular");
EXPECT_CONTAINS(str, "fullscreen: 2");
}
OK(getFromSocket("/dispatch togglespecialworkspace test_fs_special"));
OK(getFromSocket("/dispatch focuswindow class:kitty_special"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitty_special");
}
NLog::log("{}Test 3: Toggle special workspace hides it", Colors::YELLOW);
OK(getFromSocket("/dispatch togglespecialworkspace test_fs_special"));
OK(getFromSocket("/dispatch focuswindow class:kitty_regular"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitty_regular");
EXPECT_CONTAINS(str, "fullscreen: 2");
}
{
auto str = getFromSocket("/monitors");
EXPECT_CONTAINS(str, "special workspace: 0 ()");
}
return true;
}
static bool testAsymmetricGaps() {
NLog::log("{}Testing asymmetric gap splits", Colors::YELLOW);
{
@ -193,206 +108,6 @@ static bool testAsymmetricGaps() {
return true;
}
static void testMultimonBAF() {
NLog::log("{}Testing multimon back and forth", Colors::YELLOW);
OK(getFromSocket("/keyword binds:workspace_back_and_forth 1"));
OK(getFromSocket("/dispatch focusmonitor HEADLESS-2"));
OK(getFromSocket("/dispatch workspace 1"));
Tests::spawnKitty();
OK(getFromSocket("/dispatch workspace 2"));
Tests::spawnKitty();
OK(getFromSocket("/dispatch focusmonitor HEADLESS-3"));
OK(getFromSocket("/dispatch workspace 3"));
Tests::spawnKitty();
OK(getFromSocket("/dispatch workspace 3"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_CONTAINS(str, "workspace ID 2 ");
}
OK(getFromSocket("/dispatch workspace 4"));
OK(getFromSocket("/dispatch workspace 4"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_CONTAINS(str, "workspace ID 2 ");
}
OK(getFromSocket("/dispatch workspace 2"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_CONTAINS(str, "workspace ID 4 ");
}
OK(getFromSocket("/dispatch workspace 3"));
OK(getFromSocket("/dispatch workspace 3"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_CONTAINS(str, "workspace ID 4 ");
}
OK(getFromSocket("/dispatch workspace 2"));
OK(getFromSocket("/dispatch workspace 3"));
OK(getFromSocket("/dispatch workspace 1"));
OK(getFromSocket("/dispatch workspace 1"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_CONTAINS(str, "workspace ID 3 ");
}
Tests::killAllWindows();
}
static void testMultimonFocus() {
NLog::log("{}Testing multimon focus and move", Colors::YELLOW);
OK(getFromSocket("/keyword input:follow_mouse 0"));
OK(getFromSocket("/dispatch focusmonitor HEADLESS-3"));
OK(getFromSocket("/dispatch workspace 8"));
OK(getFromSocket("/dispatch focusmonitor HEADLESS-2"));
OK(getFromSocket("/dispatch workspace 7"));
for (auto const& win : {"a", "b"}) {
if (!Tests::spawnKitty(win)) {
NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win);
++TESTS_FAILED;
ret = 1;
return;
}
}
OK(getFromSocket("/dispatch focuswindow class:a"));
OK(getFromSocket("/dispatch movefocus r"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_CONTAINS(str, "workspace ID 7 ");
}
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: b");
}
OK(getFromSocket("/dispatch movefocus r"));
{
auto str = getFromSocket("/activeworkspace");
EXPECT_CONTAINS(str, "workspace ID 8 ");
}
Tests::spawnKitty("c");
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: c");
}
{
auto str = getFromSocket("/activeworkspace");
EXPECT_CONTAINS(str, "workspace ID 8 ");
}
OK(getFromSocket("/dispatch movefocus l"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: b");
}
{
auto str = getFromSocket("/activeworkspace");
EXPECT_CONTAINS(str, "workspace ID 7 ");
}
OK(getFromSocket("/dispatch movewindow r"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: b");
}
{
auto str = getFromSocket("/activeworkspace");
EXPECT_CONTAINS(str, "workspace ID 8 ");
}
OK(getFromSocket("/dispatch movefocus r"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: c");
}
{
auto str = getFromSocket("/activeworkspace");
EXPECT_CONTAINS(str, "workspace ID 8 ");
}
OK(getFromSocket("/dispatch movefocus l"));
OK(getFromSocket("/keyword general:no_focus_fallback true"));
OK(getFromSocket("/keyword binds:window_direction_monitor_fallback false"));
EXPECT_NOT(getFromSocket("/dispatch movefocus l"), "ok");
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: b");
}
{
auto str = getFromSocket("/activeworkspace");
EXPECT_CONTAINS(str, "workspace ID 8 ");
}
OK(getFromSocket("/dispatch movewindow l"));
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: b");
}
{
auto str = getFromSocket("/activeworkspace");
EXPECT_CONTAINS(str, "workspace ID 8 ");
}
OK(getFromSocket("/reload"));
Tests::killAllWindows();
}
static void testDynamicWsEffects() {
// test dynamic workspace effects, they shouldn't lag
OK(getFromSocket("/dispatch workspace 69"));
Tests::spawnKitty("bitch");
OK(getFromSocket("r/keyword workspace 69,bordersize:20"));
OK(getFromSocket("r/keyword workspace 69,rounding:false"));
EXPECT(getFromSocket("/getprop class:bitch border_size"), "20");
EXPECT(getFromSocket("/getprop class:bitch rounding"), "0");
OK(getFromSocket("/reload"));
Tests::killAllWindows();
}
static bool test() {
NLog::log("{}Testing workspaces", Colors::GREEN);
@ -727,19 +442,14 @@ static bool test() {
EXPECT_CONTAINS(str, "class: kitty_B");
}
// destroy the headless output
OK(getFromSocket("/output remove HEADLESS-3"));
// kill all
NLog::log("{}Killing all windows", Colors::YELLOW);
Tests::killAllWindows();
testMultimonBAF();
testMultimonFocus();
// destroy the headless output
OK(getFromSocket("/output remove HEADLESS-3"));
testSpecialWorkspaceFullscreen();
testAsymmetricGaps();
testDynamicWsEffects();
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
EXPECT(Tests::windowCount(), 0);

View file

@ -3,7 +3,6 @@
#include <cerrno>
#include <thread>
#include <print>
#include <fstream>
#include "../shared.hpp"
#include "../hyprctlCompat.hpp"
@ -40,38 +39,6 @@ CUniquePointer<CProcess> Tests::spawnKitty(const std::string& class_, const std:
return kitty;
}
CUniquePointer<CProcess> Tests::spawnLayerKitty(const std::string& namespace_, const std::vector<std::string> args) {
std::vector<std::string> programArgs = args;
if (!namespace_.empty()) {
programArgs.insert(programArgs.begin(), "--class");
programArgs.insert(programArgs.begin() + 1, namespace_);
}
programArgs.insert(programArgs.begin(), "+kitten");
programArgs.insert(programArgs.begin() + 1, "panel");
CUniquePointer<CProcess> kitty = makeUnique<CProcess>("kitty", programArgs);
kitty->addEnv("WAYLAND_DISPLAY", WLDISPLAY);
kitty->runAsync();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
// wait while the layer spawns
int counter = 0;
while (processAlive(kitty->pid()) && countOccurrences(getFromSocket("/layers"), std::format("pid: {}", kitty->pid())) == 0) {
counter++;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (counter > 50)
return nullptr;
}
if (!processAlive(kitty->pid()))
return nullptr;
return kitty;
}
bool Tests::processAlive(pid_t pid) {
errno = 0;
int ret = kill(pid, 0);
@ -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) {
CProcess proc("/bin/sh", {"-c", cmd});
@ -170,14 +105,3 @@ std::string Tests::execAndGet(const std::string& cmd) {
return proc.stdOut();
}
bool Tests::writeFile(const std::string& name, const std::string& contents) {
std::ofstream of(name, std::ios::trunc);
if (!of.good())
return false;
of << contents;
of.close();
return true;
}

View file

@ -9,14 +9,10 @@
//NOLINTNEXTLINE
namespace Tests {
Hyprutils::Memory::CUniquePointer<Hyprutils::OS::CProcess> spawnKitty(const std::string& class_ = "", const std::vector<std::string> args = {});
Hyprutils::Memory::CUniquePointer<Hyprutils::OS::CProcess> spawnLayerKitty(const std::string& namespace_ = "", const std::vector<std::string> args = {});
bool processAlive(pid_t pid);
int windowCount();
int countOccurrences(const std::string& in, const std::string& what);
bool killAllWindows();
void waitUntilWindowsN(int n);
int layerCount();
bool killAllLayers();
std::string execAndGet(const std::string& cmd);
bool writeFile(const std::string& name, const std::string& contents);
};

View file

@ -179,17 +179,6 @@ master {
new_status = master
}
scrolling {
fullscreen_on_one_column = true
column_width = 0.5
focus_fit_method = 1
follow_focus = true
follow_min_visible = 1
explicit_column_widths = 0.25, 0.333, 0.5, 0.667, 0.75, 1.0
wrap_focus = true
wrap_swapcol = true
}
# https://wiki.hyprland.org/Configuring/Variables/#misc
misc {
force_default_wallpaper = -1 # Set to 0 or 1 to disable the anime mascot wallpapers
@ -250,7 +239,7 @@ bind = $mainMod, E, exec, $fileManager
bind = $mainMod, V, togglefloating,
bind = $mainMod, R, exec, $menu
bind = $mainMod, P, pseudo, # dwindle
bind = $mainMod, J, layoutmsg, togglesplit, # dwindle
bind = $mainMod, J, togglesplit, # dwindle
# Move focus with mainMod + arrow keys
bind = $mainMod, left, movefocus, l
@ -409,5 +398,3 @@ gesture = 5, left, dispatcher, sendshortcut, , i, activewindow
gesture = 5, right, dispatcher, sendshortcut, , t, activewindow
gesture = 4, right, dispatcher, sendshortcut, , return, activewindow
gesture = 4, left, dispatcher, movecursortocorner, 1
gesturep = 2, right, float

View file

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

View file

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

View file

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

View file

@ -1,5 +1,4 @@
lib:
let
lib: let
inherit (lib)
attrNames
filterAttrs
@ -18,7 +17,7 @@ let
This function takes a nested attribute set and converts it into Hyprland-compatible
configuration syntax, supporting top, bottom, and regular command sections.
Commands are flattened using the `flattenAttrs` function, and attributes are formatted as
`key = value` pairs. Lists are expanded as duplicate keys to match Hyprland's expected format.
@ -82,51 +81,44 @@ let
:::
*/
toHyprlang =
{
topCommandsPrefixes ? [
"$"
"bezier"
],
bottomCommandsPrefixes ? [ ],
}:
attrs:
let
toHyprlang' =
attrs:
let
# Specially configured `toKeyValue` generator with support for duplicate keys
# and a legible key-value separator.
mkCommands = generators.toKeyValue {
mkKeyValue = generators.mkKeyValueDefault { } " = ";
listsAsDuplicateKeys = true;
indent = ""; # No indent, since we don't have nesting
};
toHyprlang = {
topCommandsPrefixes ? ["$" "bezier"],
bottomCommandsPrefixes ? [],
}: attrs: let
toHyprlang' = attrs: let
# Specially configured `toKeyValue` generator with support for duplicate keys
# and a legible key-value separator.
mkCommands = generators.toKeyValue {
mkKeyValue = generators.mkKeyValueDefault {} " = ";
listsAsDuplicateKeys = true;
indent = ""; # No indent, since we don't have nesting
};
# Flatten the attrset, combining keys in a "path" like `"a:b:c" = "x"`.
# Uses `flattenAttrs` with a colon separator.
commands = flattenAttrs (p: k: "${p}:${k}") attrs;
# Flatten the attrset, combining keys in a "path" like `"a:b:c" = "x"`.
# Uses `flattenAttrs` with a colon separator.
commands = flattenAttrs (p: k: "${p}:${k}") attrs;
# General filtering function to check if a key starts with any prefix in a given list.
filterCommands = list: n: foldl (acc: prefix: acc || hasPrefix prefix n) false list;
# General filtering function to check if a key starts with any prefix in a given list.
filterCommands = list: n:
foldl (acc: prefix: acc || hasPrefix prefix n) false list;
# Partition keys into top commands and the rest
result = partition (filterCommands topCommandsPrefixes) (attrNames commands);
topCommands = filterAttrs (n: _: builtins.elem n result.right) commands;
remainingCommands = removeAttrs commands result.right;
# Partition keys into top commands and the rest
result = partition (filterCommands topCommandsPrefixes) (attrNames commands);
topCommands = filterAttrs (n: _: builtins.elem n result.right) commands;
remainingCommands = removeAttrs commands result.right;
# Partition remaining commands into bottom commands and regular commands
result2 = partition (filterCommands bottomCommandsPrefixes) result.wrong;
bottomCommands = filterAttrs (n: _: builtins.elem n result2.right) remainingCommands;
regularCommands = removeAttrs remainingCommands result2.right;
in
# Concatenate strings from mapping `mkCommands` over top, regular, and bottom commands.
concatMapStrings mkCommands [
topCommands
regularCommands
bottomCommands
];
# Partition remaining commands into bottom commands and regular commands
result2 = partition (filterCommands bottomCommandsPrefixes) result.wrong;
bottomCommands = filterAttrs (n: _: builtins.elem n result2.right) remainingCommands;
regularCommands = removeAttrs remainingCommands result2.right;
in
# Concatenate strings from mapping `mkCommands` over top, regular, and bottom commands.
concatMapStrings mkCommands [
topCommands
regularCommands
bottomCommands
];
in
toHyprlang' attrs;
/**
@ -139,7 +131,7 @@ let
Configuration:
* `pred` - A function `(string -> string -> string)` defining how keys should be concatenated.
# Inputs
Structured function argument:
@ -147,7 +139,7 @@ let
: pred (required)
: A function that determines how parent and child keys should be combined into a single key.
It takes a `prefix` (parent key) and `key` (current key) and returns the joined key.
Value:
: The nested attribute set to be flattened.
@ -182,21 +174,26 @@ let
```
:::
*/
flattenAttrs =
pred: attrs:
let
flattenAttrs' =
prefix: attrs:
builtins.foldl' (
acc: key:
let
value = attrs.${key};
newKey = if prefix == "" then key else pred prefix key;
in
acc // (if builtins.isAttrs value then flattenAttrs' newKey value else { "${newKey}" = value; })
) { } (builtins.attrNames attrs);
in
flattenAttrs = pred: attrs: let
flattenAttrs' = prefix: attrs:
builtins.foldl' (
acc: key: let
value = attrs.${key};
newKey =
if prefix == ""
then key
else pred prefix key;
in
acc
// (
if builtins.isAttrs value
then flattenAttrs' newKey value
else {"${newKey}" = value;}
)
) {} (builtins.attrNames attrs);
in
flattenAttrs' "" attrs;
in
{

View file

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

View file

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

View file

@ -1,76 +1,69 @@
inputs: pkgs:
let
inputs: pkgs: let
flake = inputs.self.packages.${pkgs.stdenv.hostPlatform.system};
hyprland = flake.hyprland-with-tests;
in
{
in {
tests = pkgs.testers.runNixOSTest {
name = "hyprland-tests";
nodes.machine =
{ pkgs, ... }:
{
environment.systemPackages = with pkgs; [
# Programs needed for tests
jq
kitty
wl-clipboard
xeyes
];
nodes.machine = {pkgs, ...}: {
environment.systemPackages = with pkgs; [
# Programs needed for tests
jq
kitty
wl-clipboard
xorg.xeyes
];
# Enabled by default for some reason
services.speechd.enable = false;
# Enabled by default for some reason
services.speechd.enable = false;
environment.variables = {
"AQ_TRACE" = "1";
"HYPRLAND_TRACE" = "1";
"XDG_RUNTIME_DIR" = "/tmp";
"XDG_CACHE_HOME" = "/tmp";
"KITTY_CONFIG_DIRECTORY" = "/etc/kitty";
};
environment.etc."kitty/kitty.conf".text = ''
confirm_os_window_close 0
remember_window_size no
initial_window_width 640
initial_window_height 400
'';
programs.hyprland = {
enable = true;
package = hyprland;
# We don't need portals in this test, so we don't set portalPackage
};
# Test configuration
environment.etc."test.conf".source = "${hyprland}/share/hypr/test.conf";
# Disable portals
xdg.portal.enable = pkgs.lib.mkForce false;
# Autologin root into tty
services.getty.autologinUser = "alice";
system.stateVersion = "24.11";
users.users.alice = {
isNormalUser = true;
};
virtualisation = {
cores = 4;
# Might crash with less
memorySize = 8192;
resolution = {
x = 1920;
y = 1080;
};
# Doesn't seem to do much, thought it would fix XWayland crashing
qemu.options = [ "-vga none -device virtio-gpu-pci" ];
};
environment.variables = {
"AQ_TRACE" = "1";
"HYPRLAND_TRACE" = "1";
"XDG_RUNTIME_DIR" = "/tmp";
"XDG_CACHE_HOME" = "/tmp";
"KITTY_CONFIG_DIRECTORY" = "/etc/kitty";
};
environment.etc."kitty/kitty.conf".text = ''
confirm_os_window_close 0
'';
programs.hyprland = {
enable = true;
package = hyprland;
# We don't need portals in this test, so we don't set portalPackage
};
# Test configuration
environment.etc."test.conf".source = "${hyprland}/share/hypr/test.conf";
# Disable portals
xdg.portal.enable = pkgs.lib.mkForce false;
# Autologin root into tty
services.getty.autologinUser = "alice";
system.stateVersion = "24.11";
users.users.alice = {
isNormalUser = true;
};
virtualisation = {
cores = 4;
# Might crash with less
memorySize = 8192;
resolution = {
x = 1920;
y = 1080;
};
# Doesn't seem to do much, thought it would fix XWayland crashing
qemu.options = ["-vga none -device virtio-gpu-pci"];
};
};
testScript = ''
# Wait for tty to be up
machine.wait_for_unit("multi-user.target")

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
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 "#include \"./${filename}.inc\"" >> ./src/render/shaders/Shaders.hpp
echo "}," >> ./src/render/shaders/Shaders.hpp

View file

@ -7,7 +7,6 @@
#include "desktop/state/FocusState.hpp"
#include "desktop/history/WindowHistoryTracker.hpp"
#include "desktop/history/WorkspaceHistoryTracker.hpp"
#include "desktop/view/Group.hpp"
#include "helpers/Splashes.hpp"
#include "config/ConfigValue.hpp"
#include "config/ConfigWatcher.hpp"
@ -20,7 +19,6 @@
#include "managers/ANRManager.hpp"
#include "managers/eventLoop/EventLoopManager.hpp"
#include "managers/permissions/DynamicPermissionManager.hpp"
#include "managers/screenshare/ScreenshareManager.hpp"
#include <algorithm>
#include <aquamarine/output/Output.hpp>
#include <bit>
@ -47,7 +45,6 @@
#include "protocols/core/Compositor.hpp"
#include "protocols/core/Subcompositor.hpp"
#include "desktop/view/LayerSurface.hpp"
#include "layout/space/Space.hpp"
#include "render/Renderer.hpp"
#include "xwayland/XWayland.hpp"
#include "helpers/ByteOperations.hpp"
@ -63,7 +60,9 @@
#include "managers/animation/AnimationManager.hpp"
#include "managers/animation/DesktopAnimationManager.hpp"
#include "managers/EventManager.hpp"
#include "managers/HookSystemManager.hpp"
#include "managers/ProtocolManager.hpp"
#include "managers/LayoutManager.hpp"
#include "managers/WelcomeManager.hpp"
#include "render/AsyncResourceGatherer.hpp"
#include "plugins/PluginSystem.hpp"
@ -72,9 +71,6 @@
#include "debug/HyprDebugOverlay.hpp"
#include "helpers/MonitorFrameScheduler.hpp"
#include "i18n/Engine.hpp"
#include "layout/LayoutManager.hpp"
#include "layout/target/WindowTarget.hpp"
#include "event/EventBus.hpp"
#include <hyprutils/string/String.hpp>
#include <aquamarine/input/Input.hpp>
@ -107,6 +103,11 @@ static void handleUnrecoverableSignal(int sig) {
signal(SIGABRT, SIG_DFL);
signal(SIGSEGV, SIG_DFL);
if (g_pHookSystem && g_pHookSystem->m_currentEventPlugin) {
longjmp(g_pHookSystem->m_hookFaultJumpBuf, 1);
return;
}
// Kill the program if the crash-reporter is caught in a deadlock.
signal(SIGALRM, [](int _) {
char const* msg = "\nCrashReporter exceeded timeout, forcefully exiting\n";
@ -282,6 +283,7 @@ static bool filterGlobals(const wl_client* client, const wl_global* global, void
//
void CCompositor::initServer(std::string socketName, int socketFd) {
if (m_onlyConfigVerification) {
g_pHookSystem = makeUnique<CHookSystemManager>();
g_pKeybindManager = makeUnique<CKeybindManager>();
g_pAnimationManager = makeUnique<CHyprAnimationManager>();
g_pConfigManager = makeUnique<CConfigManager>();
@ -370,11 +372,11 @@ void CCompositor::initServer(std::string socketName, int socketFd) {
return ret == 0 && cap != 0;
};
m_drm.syncobjSupport = syncObjSupport(m_drm.fd);
Log::logger->log(Log::DEBUG, "DRM DisplayNode syncobj timeline support: {}", m_drm.syncobjSupport ? "yes" : "no");
if ((m_drm.syncobjSupport = syncObjSupport(m_drm.fd)))
Log::logger->log(Log::DEBUG, "DRM DisplayNode syncobj timeline support: {}", m_drm.syncobjSupport ? "yes" : "no");
m_drmRenderNode.syncObjSupport = syncObjSupport(m_drmRenderNode.fd);
Log::logger->log(Log::DEBUG, "DRM RenderNode syncobj timeline support: {}", m_drmRenderNode.syncObjSupport ? "yes" : "no");
if ((m_drmRenderNode.syncObjSupport = syncObjSupport(m_drmRenderNode.fd)))
Log::logger->log(Log::DEBUG, "DRM RenderNode syncobj timeline support: {}", m_drmRenderNode.syncObjSupport ? "yes" : "no");
if (!m_drm.syncobjSupport && !m_drmRenderNode.syncObjSupport)
Log::logger->log(Log::DEBUG, "DRM no syncobj support, disabling explicit sync");
@ -481,10 +483,6 @@ void CCompositor::initAllSignals() {
m_sessionActive = true;
// Reset animation tick state to avoid stale timer issues after suspend/wake
if (g_pAnimationManager)
g_pAnimationManager->resetTickState();
for (auto const& m : m_monitors) {
scheduleFrameForMonitor(m);
m->applyMonitorRule(&m->m_activeMonitorRule, true);
@ -588,10 +586,11 @@ void CCompositor::cleanup() {
g_pHyprRenderer.reset();
g_pHyprOpenGL.reset();
g_pConfigManager.reset();
g_layoutManager.reset();
g_pLayoutManager.reset();
g_pHyprError.reset();
g_pConfigManager.reset();
g_pKeybindManager.reset();
g_pHookSystem.reset();
g_pXWaylandManager.reset();
g_pPointerManager.reset();
g_pSeatManager.reset();
@ -620,6 +619,9 @@ void CCompositor::initManagers(eManagersInitStage stage) {
Log::logger->log(Log::DEBUG, "Creating the EventLoopManager!");
g_pEventLoopManager = makeUnique<CEventLoopManager>(m_wlDisplay, m_wlEventLoop);
Log::logger->log(Log::DEBUG, "Creating the HookSystem!");
g_pHookSystem = makeUnique<CHookSystemManager>();
Log::logger->log(Log::DEBUG, "Creating the KeybindManager!");
g_pKeybindManager = makeUnique<CKeybindManager>();
@ -636,7 +638,7 @@ void CCompositor::initManagers(eManagersInitStage stage) {
g_pHyprError = makeUnique<CHyprError>();
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!");
g_pTokenManager = makeUnique<CTokenManager>();
@ -790,8 +792,7 @@ void CCompositor::startCompositor() {
createLockFile();
Event::bus()->m_events.ready.emit();
EMIT_HOOK_EVENT("ready", nullptr);
if (m_watchdogWriteFd.isValid())
write(m_watchdogWriteFd.get(), "vax", 3);
@ -871,7 +872,7 @@ PHLMONITOR CCompositor::getMonitorFromVector(const Vector2D& point) {
void CCompositor::removeWindowFromVectorSafe(PHLWINDOW pWindow) {
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_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() &&
w != pIgnoreWindow && !isShadowedByModal(w)) {
CBox box = (properties & Desktop::View::USE_PROP_TILED) ? w->getWindowBoxUnified(properties) : CBox{w->m_position, w->m_size};
if ((properties & Desktop::View::INPUT_EXTENTS) && BORDER_GRAB_AREA > 0 && !w->isX11OverrideRedirect())
box.expand(BORDER_GRAB_AREA);
if (box.containsPoint(pos))
return w;
}
@ -1180,7 +1179,7 @@ PHLWINDOW CCompositor::getWindowFromSurface(SP<CWLSurfaceResource> pSurface) {
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 dynamicPointerCast<Desktop::View::CWindow>(VIEW);
@ -1372,8 +1371,8 @@ void CCompositor::addToFadingOutSafe(PHLWINDOW pWindow) {
m_windowsFadingOut.emplace_back(pWindow);
}
PHLWINDOW CCompositor::getWindowInDirection(PHLWINDOW pWindow, Math::eDirection dir) {
if (dir == Math::DIRECTION_DEFAULT)
PHLWINDOW CCompositor::getWindowInDirection(PHLWINDOW pWindow, char dir) {
if (!isDirection(dir))
return nullptr;
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);
}
PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, Math::eDirection dir, PHLWINDOW ignoreWindow, bool useVectorAngles) {
if (dir == Math::DIRECTION_DEFAULT)
PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, char dir, PHLWINDOW ignoreWindow, bool useVectorAngles) {
if (!isDirection(dir))
return nullptr;
// 0 -> history, 1 -> shared length
@ -1405,35 +1404,6 @@ PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorks
PHLWINDOW leaderWindow = nullptr;
if (!useVectorAngles) {
// helper to check if two rectangles are adjacent along an axis, considering slight overlaps.
// returns true if: STICKS (delta <= 2) OR rectangles overlap but no more than 50% of the smaller dimension.
static auto isAdjacent = [](const double aMin, const double aMax, const double bMin, const double bMax) -> bool {
constexpr double STICK_THRESHOLD = 2.0;
constexpr double MAX_OVERLAP_RATIO = 0.5;
const double aEdge = aMin;
const double bEdge = bMax;
const double delta = aEdge - bEdge;
// old STICKS check for 2px
if (std::abs(delta) < STICK_THRESHOLD)
return true;
if (delta >= 0)
return false;
const double overlap = -delta;
const double sizeA = aMax - aMin;
const double sizeB = bMax - bMin;
// reject if one rectangle fully contains the other
if ((bMin <= aMin && bMax >= aMax) || (aMin <= bMin && aMax >= bMax))
return false;
// accept if overlap is at most 50% of the smaller dimension
return overlap <= std::min(sizeA, sizeB) * MAX_OVERLAP_RATIO;
};
for (auto const& w : m_windows) {
if (w == ignoreWindow || !w->m_workspace || !w->m_isMapped || w->isHidden() || (!w->isFullscreen() && w->m_isFloating) || !w->m_workspace->isVisible())
continue;
@ -1455,23 +1425,28 @@ PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorks
double intersectLength = -1;
switch (dir) {
case Math::DIRECTION_LEFT:
if (isAdjacent(POSA.x, POSA.x + SIZEA.x, POSB.x, POSB.x + SIZEB.x))
case 'l':
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));
}
break;
case Math::DIRECTION_RIGHT:
if (isAdjacent(POSB.x, POSB.x + SIZEB.x, POSA.x, POSA.x + SIZEA.x))
case 'r':
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));
}
break;
case Math::DIRECTION_UP:
if (isAdjacent(POSA.y, POSA.y + SIZEA.y, POSB.y, POSB.y + SIZEB.y))
case 't':
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));
}
break;
case Math::DIRECTION_DOWN:
if (isAdjacent(POSB.y, POSB.y + SIZEB.y, POSA.y, POSA.y + SIZEA.y))
case 'b':
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));
}
break;
default: break;
}
if (*PMETHOD == 0 /* history */) {
@ -1500,8 +1475,12 @@ PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorks
}
}
} else {
static const std::unordered_map<Math::eDirection, Vector2D> VECTORS = {
{Math::DIRECTION_RIGHT, {1, 0}}, {Math::DIRECTION_UP, {0, -1}}, {Math::DIRECTION_DOWN, {0, 1}}, {Math::DIRECTION_LEFT, {-1, 0}}};
if (dir == 'u')
dir = 't';
if (dir == 'd')
dir = 'b';
static const std::unordered_map<char, Vector2D> VECTORS = {{'r', {1, 0}}, {'t', {0, -1}}, {'b', {0, 1}}, {'l', {-1, 0}}};
//
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);
}
std::optional<CBox> CCompositor::calculateX11WorkArea() {
CBox CCompositor::calculateX11WorkArea() {
static auto PXWLFORCESCALEZERO = CConfigValue<Hyprlang::INT>("xwayland:force_zero_scaling");
// We more than likely won't be able to calculate one
// and even if we could this is minor
if (m_monitors.size() > 1 || m_monitors.empty())
return std::nullopt;
CBox workbox = {0, 0, 0, 0};
bool firstMonitor = true;
const auto M = m_monitors.front();
for (const auto& monitor : m_monitors) {
// we ignore monitor->m_position on purpose
CBox box = monitor->logicalBoxMinusReserved().translate(-monitor->m_position);
if ((*PXWLFORCESCALEZERO))
box.scale(monitor->m_scale);
// we ignore monitor->m_position on purpose
CBox box = M->logicalBoxMinusReserved().translate(-M->m_position);
if ((*PXWLFORCESCALEZERO))
box.scale(M->m_scale);
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;
}
}
}
return box.translate(M->m_xwaylandPosition);
// 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);
}
PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, Math::eDirection dir) {
PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const char& dir) {
if (!pSourceMonitor)
return nullptr;
@ -1691,7 +1680,7 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, Math::e
const auto POSB = m->m_position;
const auto SIZEB = m->m_size;
switch (dir) {
case Math::DIRECTION_LEFT:
case 'l':
if (STICKS(POSA.x, POSB.x + SIZEB.x)) {
const auto INTERSECTLEN = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y));
if (INTERSECTLEN > longestIntersect) {
@ -1700,7 +1689,7 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, Math::e
}
}
break;
case Math::DIRECTION_RIGHT:
case 'r':
if (STICKS(POSA.x + SIZEA.x, POSB.x)) {
const auto INTERSECTLEN = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y));
if (INTERSECTLEN > longestIntersect) {
@ -1709,7 +1698,8 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, Math::e
}
}
break;
case Math::DIRECTION_UP:
case 't':
case 'u':
if (STICKS(POSA.y, POSB.y + SIZEB.y)) {
const auto INTERSECTLEN = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x));
if (INTERSECTLEN > longestIntersect) {
@ -1718,7 +1708,8 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, Math::e
}
}
break;
case Math::DIRECTION_DOWN:
case 'b':
case 'd':
if (STICKS(POSA.y + SIZEA.y, POSB.y)) {
const auto INTERSECTLEN = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x));
if (INTERSECTLEN > longestIntersect) {
@ -1727,7 +1718,6 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, Math::e
}
}
break;
default: break;
}
}
@ -1783,7 +1773,7 @@ void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitor
// additionally, move floating and fs windows manually
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()) {
*w->m_realPosition = pMonitorB->m_position;
@ -1808,7 +1798,7 @@ void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitor
// additionally, move floating and fs windows manually
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()) {
*w->m_realPosition = pMonitorA->m_position;
@ -1822,11 +1812,8 @@ void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitor
pMonitorA->m_activeWorkspace = PWORKSPACEB;
pMonitorB->m_activeWorkspace = PWORKSPACEA;
g_layoutManager->recalculateMonitor(pMonitorA);
g_layoutManager->recalculateMonitor(pMonitorB);
g_pHyprRenderer->damageMonitor(pMonitorB);
g_pHyprRenderer->damageMonitor(pMonitorA);
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitorA->m_id);
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitorB->m_id);
g_pDesktopAnimationManager->setFullscreenFadeAnimation(
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(
LASTWIN ? LASTWIN :
(g_pCompositor->vectorToWindowUnified(g_pInputManager->getMouseCoordsInternal(),
Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING)),
Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE);
Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING)));
const auto PNEWWORKSPACE = pMonitorA->m_id == Desktop::focusState()->monitor()->m_id ? PWORKSPACEB : PWORKSPACEA;
g_pEventManager->postEvent(SHyprIPCEvent{.event = "workspace", .data = PNEWWORKSPACE->m_name});
g_pEventManager->postEvent(SHyprIPCEvent{.event = "workspacev2", .data = std::format("{},{}", PNEWWORKSPACE->m_id, PNEWWORKSPACE->m_name)});
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 = "moveworkspacev2", .data = std::format("{},{},{}", PWORKSPACEA->m_id, PWORKSPACEA->m_name, pMonitorB->m_name)});
EMIT_HOOK_EVENT("moveWorkspace", (std::vector<std::any>{PWORKSPACEA, pMonitorB}));
g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspace", .data = PWORKSPACEB->m_name + "," + pMonitorA->m_name});
g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspacev2", .data = std::format("{},{},{}", PWORKSPACEB->m_id, PWORKSPACEB->m_name, pMonitorA->m_name)});
Event::bus()->m_events.workspace.moveToMonitor.emit(PWORKSPACEA, pMonitorB);
Event::bus()->m_events.workspace.moveToMonitor.emit(PWORKSPACEB, pMonitorA);
EMIT_HOOK_EVENT("moveWorkspace", (std::vector<std::any>{PWORKSPACEB, pMonitorA}));
}
PHLMONITOR CCompositor::getMonitorFromString(const std::string& name) {
if (name == "current")
return Desktop::focusState()->monitor();
else if (isDirection(name))
return getMonitorInDirection(Math::fromChar(name[0]));
return getMonitorInDirection(name[0]);
else if (name[0] == '+' || name[0] == '-') {
// relative
@ -1982,7 +1968,6 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo
// move the workspace
pWorkspace->m_monitor = pMonitor;
pWorkspace->m_space->recheckWorkArea();
pWorkspace->m_events.monitorChanged.emit();
for (auto const& w : m_windows) {
@ -1998,18 +1983,17 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo
if (w->m_isMapped && !w->isHidden()) {
if (POLDMON) {
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()) {
*w->m_realPosition = pMonitor->m_position;
*w->m_realSize = pMonitor->m_size;
}
} else
w->layoutTarget()->setPositionGlobal(CBox{Vector2D{
(pMonitor->m_size.x != 0) ? sc<int>(w->m_realPosition->goal().x) % sc<int>(pMonitor->m_size.x) : 0,
(pMonitor->m_size.y != 0) ? sc<int>(w->m_realPosition->goal().y) % sc<int>(pMonitor->m_size.y) : 0,
},
w->layoutTarget()->position().size()});
*w->m_realPosition = Vector2D{
(pMonitor->m_size.x != 0) ? sc<int>(w->m_realPosition->goal().x) % sc<int>(pMonitor->m_size.x) : 0,
(pMonitor->m_size.y != 0) ? sc<int>(w->m_realPosition->goal().y) % sc<int>(pMonitor->m_size.y) : 0,
};
}
w->updateToplevel();
@ -2037,8 +2021,7 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo
pWorkspace->m_events.activeChanged.emit();
g_layoutManager->recalculateMonitor(pMonitor);
g_pHyprRenderer->damageMonitor(pMonitor);
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitor->m_id);
g_pDesktopAnimationManager->startAnimation(pWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true);
pWorkspace->m_visible = true;
@ -2051,7 +2034,7 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo
// finalize
if (POLDMON) {
g_layoutManager->recalculateMonitor(POLDMON);
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(POLDMON->m_id);
if (valid(POLDMON->m_activeWorkspace))
g_pDesktopAnimationManager->setFullscreenFadeAnimation(POLDMON->m_activeWorkspace,
POLDMON->m_activeWorkspace->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN :
@ -2066,8 +2049,7 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo
// event
g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspace", .data = pWorkspace->m_name + "," + pMonitor->m_name});
g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspacev2", .data = std::format("{},{},{}", pWorkspace->m_id, pWorkspace->m_name, pMonitor->m_name)});
Event::bus()->m_events.workspace.moveToMonitor.emit(pWorkspace, pMonitor);
EMIT_HOOK_EVENT("moveWorkspace", (std::vector<std::any>{pWorkspace, pMonitor}));
}
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 |
Desktop::Rule::RULE_PROP_FULLSCREENSTATE_INTERNAL | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
PWINDOW->updateDecorationValues();
g_layoutManager->recalculateMonitor(PMONITOR);
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PWINDOW->monitorID());
return;
}
PWORKSPACE->m_fullscreenMode = EFFECTIVE_MODE;
PWORKSPACE->m_hasFullscreenWindow = EFFECTIVE_MODE != FSMODE_NONE;
g_layoutManager->fullscreenRequestForTarget(PWINDOW->layoutTarget(), CURRENT_EFFECTIVE_MODE, EFFECTIVE_MODE);
g_pLayoutManager->getCurrentLayout()->fullscreenRequestForWindow(PWINDOW, CURRENT_EFFECTIVE_MODE, EFFECTIVE_MODE);
PWINDOW->m_fullscreenState.internal = state.internal;
PWORKSPACE->m_fullscreenMode = EFFECTIVE_MODE;
PWORKSPACE->m_hasFullscreenWindow = EFFECTIVE_MODE != FSMODE_NONE;
g_pEventManager->postEvent(SHyprIPCEvent{.event = "fullscreen", .data = std::to_string(sc<int>(EFFECTIVE_MODE) != FSMODE_NONE)});
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 |
Desktop::Rule::RULE_PROP_FULLSCREENSTATE_INTERNAL | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
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
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) {
if (!w->m_isMapped)
if (!w->m_isMapped || (w->isHidden() && !g_pLayoutManager->getCurrentLayout()->isWindowReachable(w)))
continue;
switch (mode) {
@ -2586,30 +2567,59 @@ void CCompositor::moveWindowToWorkspaceSafe(PHLWINDOW pWindow, PHLWORKSPACE pWor
setWindowFullscreenInternal(pWindow, FSMODE_NONE);
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 PWORKSPACEMONITOR = pWorkspace->m_monitor.lock();
if (!pWindow->m_isFloating)
g_pLayoutManager->getCurrentLayout()->onWindowRemovedTiling(pWindow);
pWindow->moveToWorkspace(pWorkspace);
pWindow->m_monitor = pWorkspace->m_monitor;
static auto PGROUPONMOVETOWORKSPACE = CConfigValue<Hyprlang::INT>("group:group_on_movetoworkspace");
if (*PGROUPONMOVETOWORKSPACE && visibleWindowsOnWorkspace == 1 && pFirstWindowOnWorkspace && pFirstWindowOnWorkspace != pWindow && pFirstWindowOnWorkspace->m_group &&
pWindow->canBeGroupedInto(pFirstWindowOnWorkspace->m_group)) {
pFirstWindowOnWorkspace->m_group->add(pWindow);
if (*PGROUPONMOVETOWORKSPACE && visibleWindowsOnWorkspace == 1 && pFirstWindowOnWorkspace && pFirstWindowOnWorkspace != pWindow &&
pFirstWindowOnWorkspace->m_groupData.pNextWindow.lock() && pWindow->canBeGroupedInto(pFirstWindowOnWorkspace)) {
pWindow->m_isFloating = pFirstWindowOnWorkspace->m_isFloating; // match the floating state. Needed to group tiled into floated and vice versa.
if (!pWindow->m_groupData.pNextWindow.expired()) {
PHLWINDOW next = pWindow->m_groupData.pNextWindow.lock();
while (next != pWindow) {
next->m_isFloating = pFirstWindowOnWorkspace->m_isFloating; // match the floating state of group members
next = next->m_groupData.pNextWindow.lock();
}
}
static auto USECURRPOS = CConfigValue<Hyprlang::INT>("group:insert_after_current");
(*USECURRPOS ? pFirstWindowOnWorkspace : pFirstWindowOnWorkspace->getGroupTail())->insertWindowToGroup(pWindow);
pFirstWindowOnWorkspace->setGroupCurrent(pWindow);
pWindow->updateWindowDecos();
g_pLayoutManager->getCurrentLayout()->recalculateWindow(pWindow);
if (!pWindow->getDecorationByType(DECORATION_GROUPBAR))
pWindow->addWindowDeco(makeUnique<CHyprGroupBarDecoration>(pWindow));
} else {
if (!pWindow->m_isFloating)
g_pLayoutManager->getCurrentLayout()->onWindowCreatedTiling(pWindow);
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->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE);
pWindow->uncacheWindowDecos();
pWindow->updateGroupOutputs();
if (pWindow->m_group)
pWindow->m_group->updateWorkspace(pWorkspace);
g_layoutManager->newTarget(pWindow->layoutTarget(), pWorkspace->m_space);
if (!pWindow->m_groupData.pNextWindow.expired()) {
PHLWINDOW next = pWindow->m_groupData.pNextWindow.lock();
while (next != pWindow) {
next->updateToplevel();
next = next->m_groupData.pNextWindow.lock();
}
}
if (FULLSCREEN)
setWindowFullscreenInternal(pWindow, FULLSCREENMODE);
@ -2778,17 +2788,12 @@ void CCompositor::arrangeMonitors() {
}
PROTO::xdgOutput->updateAllOutputs();
Event::bus()->m_events.monitor.layoutChanged.emit();
#ifndef NO_XWAYLAND
const auto box = g_pCompositor->calculateX11WorkArea();
if (g_pXWayland && g_pXWayland->m_wm) {
if (box)
g_pXWayland->m_wm->updateWorkArea(box->x, box->y, box->w, box->h);
else
g_pXWayland->m_wm->updateWorkArea(0, 0, 0, 0);
}
CBox box = g_pCompositor->calculateX11WorkArea();
if (!g_pXWayland || !g_pXWayland->m_wm)
return;
g_pXWayland->m_wm->updateWorkArea(box.x, box.y, box.w, box.h);
#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_isUnsafeFallback = FALLBACK;
Event::bus()->m_events.monitor.newMon.emit(PNEWMONITOR);
EMIT_HOOK_EVENT("newMonitor", PNEWMONITOR);
if (!FALLBACK)
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() ?
CImageDescription::from(SImageDescription{
.transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ,
.primariesNameSet = true,
.primariesNamed = NColorManagement::CM_PRIMARIES_BT2020,
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020),
.masteringPrimaries = m_monitors[0]->getMasteringPrimaries(),
.luminances = {.min = m_monitors[0]->minLuminance(HDR_MIN_LUMINANCE), .max = m_monitors[0]->maxLuminance(HDR_MAX_LUMINANCE), .reference = HDR_REF_LUMINANCE},
.masteringLuminances = m_monitors[0]->getMasteringLuminances(),
.maxCLL = m_monitors[0]->maxCLL(),
.maxFALL = m_monitors[0]->maxFALL()}) :
CImageDescription::from(SImageDescription{.transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ,
.primariesNameSet = true,
.primariesNamed = NColorManagement::CM_PRIMARIES_BT2020,
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020),
.luminances = {.min = m_monitors[0]->m_output->parsedEDID.hdrMetadata->desiredContentMinLuminance,
.max = m_monitors[0]->m_output->parsedEDID.hdrMetadata->desiredContentMaxLuminance,
.reference = m_monitors[0]->m_output->parsedEDID.hdrMetadata->desiredMaxFrameAverageLuminance}}) :
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() {
if (!m_aqBackend->hasSession())
return std::nullopt;
@ -3115,7 +3096,3 @@ std::optional<unsigned int> CCompositor::getVTNr() {
return ttynum;
}
bool CCompositor::isVRRActiveOnAnyMonitor() const {
return std::ranges::any_of(m_monitors, [](const PHLMONITOR& m) { return m->m_vrrActive; });
}

View file

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

View file

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

View file

@ -1115,12 +1115,6 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SRangeData{0, -20, 20},
},
SConfigOptionDescription{
.value = "group:groupbar:text_padding",
.description = "set horizontal padding for a text",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SRangeData{0, 0, 22},
},
SConfigOptionDescription{
.value = "group:groupbar:blur",
.description = "enable background blur for groupbars",
@ -1568,28 +1562,10 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
},
SConfigOptionDescription{
.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 "
"unspecified and sRGB as Gamma 2.2, srgb - Treat unspecified as sRGB",
.type = CONFIG_OPTION_STRING_SHORT,
.data = SConfigOptionDescription::SStringData{"default"},
},
SConfigOptionDescription{
.value = "render:commit_timing_enabled",
.description = "Enable commit timing proto. Requires restart",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "render:icc_vcgt_enabled",
.description = "Enable sending VCGT ramps to KMS with ICC profiles",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
{
.value = "render:use_shader_blur_blend",
.description = "Use experimental blurred bg blending",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
.description = "Default transfer function for displaying SDR apps. 0 - Use default value (Gamma 2.2), 1 - Treat unspecified as Gamma 2.2, 2 - Treat "
"unspecified and sRGB as Gamma 2.2, 3 - Treat unspecified as sRGB",
.type = CONFIG_OPTION_CHOICE,
.data = SConfigOptionDescription::SChoiceData{0, "default,gamma22,gamma22force,srgb"},
},
/*
@ -1714,9 +1690,9 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
},
SConfigOptionDescription{
.value = "cursor:use_cpu_buffer",
.description = "Makes HW cursors use a CPU buffer. Required on Nvidia to have HW cursors. 0 - off, 1 - on, 2 - auto (nvidia only)",
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{.value = 2, .min = 0, .max = 2},
.description = "Makes HW cursors use a CPU buffer. Required on Nvidia to have HW cursors. Experimental",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "cursor:sync_gsettings_theme",
@ -1770,12 +1746,6 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "debug:gl_debugging",
.description = "enable OpenGL debugging and error checking, they hurt performance.",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "debug:disable_logs",
.description = "disable logging to a file",
@ -1854,46 +1824,6 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "debug:ds_handle_same_buffer",
.description = "Special case for DS with unmodified buffer",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "debug:ds_handle_same_buffer_fifo",
.description = "Special case for DS with unmodified buffer unlocks fifo",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "debug:fifo_pending_workaround",
.description = "Fifo workaround for empty pending list",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "debug:render_solitary_wo_damage",
.description = "Render solitary window with empty damage",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
/*
* layout:
*/
SConfigOptionDescription{
.value = "layout:single_window_aspect_ratio",
.description = "If specified, whenever only a single window is open, it will be coerced into the specified aspect ratio. Ignored if the y-value is zero.",
.type = CONFIG_OPTION_VECTOR,
.data = SConfigOptionDescription::SVectorData{{0, 0}, {0, 0}, {1000., 1000.}},
},
SConfigOptionDescription{
.value = "layout:single_window_aspect_ratio_tolerance",
.description = "Minimum distance for single_window_aspect_ratio to take effect, in fractions of the monitor's size.",
.type = CONFIG_OPTION_FLOAT,
.data = SConfigOptionDescription::SFloatData{0.1f, 0.f, 1.f},
},
/*
* dwindle:
@ -1974,6 +1904,18 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false},
},
SConfigOptionDescription{
.value = "dwindle:single_window_aspect_ratio",
.description = "If specified, whenever only a single window is open, it will be coerced into the specified aspect ratio. Ignored if the y-value is zero.",
.type = CONFIG_OPTION_VECTOR,
.data = SConfigOptionDescription::SVectorData{{0, 0}, {0, 0}, {1000., 1000.}},
},
SConfigOptionDescription{
.value = "dwindle:single_window_aspect_ratio_tolerance",
.description = "Minimum distance for single_window_aspect_ratio to take effect, in fractions of the monitor's size.",
.type = CONFIG_OPTION_FLOAT,
.data = SConfigOptionDescription::SFloatData{0.1f, 0.f, 1.f},
},
/*
* master:
@ -2059,65 +2001,6 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.data = SConfigOptionDescription::SBoolData{false},
},
/*
* scrolling:
*/
SConfigOptionDescription{
.value = "scrolling:fullscreen_on_one_column",
.description = "when enabled, a single column on a workspace will always span the entire screen.",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
SConfigOptionDescription{
.value = "scrolling:column_width",
.description = "the default width of a column, [0.1 - 1.0].",
.type = CONFIG_OPTION_FLOAT,
.data = SConfigOptionDescription::SFloatData{.value = 0.5, .min = 0.1, .max = 1.0},
},
SConfigOptionDescription{
.value = "scrolling:focus_fit_method",
.description = "When a column is focused, what method should be used to bring it into view",
.type = CONFIG_OPTION_CHOICE,
.data = SConfigOptionDescription::SChoiceData{.firstIndex = 0, .choices = "center,fit"},
},
SConfigOptionDescription{
.value = "scrolling:follow_focus",
.description = "when a window is focused, should the layout move to bring it into view automatically",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{.value = true},
},
SConfigOptionDescription{
.value = "scrolling:follow_min_visible",
.description = "when a window is focused, require that at least a given fraction of it is visible for focus to follow",
.type = CONFIG_OPTION_FLOAT,
.data = SConfigOptionDescription::SFloatData{.value = 0.4, .min = 0.0, .max = 1.0},
},
SConfigOptionDescription{
.value = "scrolling:explicit_column_widths",
.description = "A comma-separated list of preconfigured widths for colresize +conf/-conf",
.type = CONFIG_OPTION_STRING_SHORT,
.data = SConfigOptionDescription::SStringData{"0.333, 0.5, 0.667, 1.0"},
},
SConfigOptionDescription{
.value = "scrolling:direction",
.description = "Direction in which new windows appear and the layout scrolls",
.type = CONFIG_OPTION_CHOICE,
.data = SConfigOptionDescription::SChoiceData{.firstIndex = 0, .choices = "right,left,down,up"},
},
SConfigOptionDescription{
.value = "scrolling:wrap_focus",
.description = "Determines if column focus wraps around when going before the first column or past the last column",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{.value = true},
},
SConfigOptionDescription{
.value = "scrolling:wrap_swapcol",
.description = "Determines if column movement wraps around when moving to before the first column or past the last column",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{.value = true},
},
/*
* Quirks
*/
@ -2128,11 +2011,5 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
.type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{.value = 0, .min = 0, .max = 2},
},
SConfigOptionDescription{
.value = "quirks:skip_non_kms_dmabuf_formats",
.description = "Do not report dmabuf formats which cannot be imported into KMS",
.type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{true},
},
};

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,9 +4,9 @@
#include "../Compositor.hpp"
#include "../config/ConfigValue.hpp"
#include "../render/pass/TexPassElement.hpp"
#include "../event/EventBus.hpp"
#include "../managers/animation/AnimationManager.hpp"
#include "../managers/HookSystemManager.hpp"
#include "../render/Renderer.hpp"
static inline auto iconBackendFromLayout(PangoLayout* layout) {
@ -22,12 +22,14 @@ static inline auto iconBackendFromLayout(PangoLayout* layout) {
}
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())
return;
g_pHyprRenderer->damageBox(m_lastDamage);
});
m_texture = makeShared<CTexture>();
}
CHyprNotificationOverlay::~CHyprNotificationOverlay() {
@ -230,7 +232,16 @@ void CHyprNotificationOverlay::draw(PHLMONITOR pMonitor) {
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;
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 = {
std::array<std::string, ICON_NONE + 1>{"[!]", "[i]", "[Hint]", "[Err]", "[?]", "[ok]", ""},
std::array<std::string, ICON_NONE + 1>{"", "", "", "", "", "󰸞", ""}, std::array<std::string, ICON_NONE + 1>{"", "", "", "", "", ""}};
static const std::array<CHyprColor, ICON_NONE + 1> ICONS_COLORS = {CHyprColor{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{179 / 255.0, 255 / 255.0, 204 / 255.0, 1.0},
CHyprColor{255 / 255.0, 77 / 255.0, 77 / 255.0, 1.0},
@ -57,7 +57,7 @@ class CHyprNotificationOverlay {
PHLMONITORREF m_lastMonitor;
Vector2D m_lastSize = Vector2D(-1, -1);
SP<ITexture> m_texture;
SP<CTexture> m_texture;
};
inline UP<CHyprNotificationOverlay> g_pHyprNotificationOverlay;

View file

@ -1,8 +1,9 @@
#include "Logger.hpp"
#include "RollingLogFollow.hpp"
#include "../../event/EventBus.hpp"
#include "../../defines.hpp"
#include "../../managers/HookSystemManager.hpp"
#include "../../config/ConfigValue.hpp"
using namespace Log;
@ -38,7 +39,7 @@ void CLogger::initIS(const std::string_view& IS) {
}
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();
}

View file

@ -5,7 +5,3 @@
#include "helpers/Color.hpp"
#include "macros.hpp"
#include "desktop/DesktopTypes.hpp"
#if !defined(__GXX_RTTI)
#error "Hyprland requires C++ RTTI. Shit will hit the fan otherwise. Do not even try."
#endif

View file

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

View file

@ -6,10 +6,6 @@
#include "../helpers/MiscFunctions.hpp"
#include "../helpers/signal/Signal.hpp"
namespace Layout {
class CSpace;
};
enum eFullscreenMode : int8_t {
FSMODE_NONE = 0,
FSMODE_MAXIMIZED = 1 << 0,
@ -24,9 +20,7 @@ class CWorkspace {
CWorkspace(WORKSPACEID id, PHLMONITOR monitor, std::string name, bool special = false, bool isEmpty = true);
~CWorkspace();
WP<CWorkspace> m_self;
SP<Layout::CSpace> m_space;
WP<CWorkspace> m_self;
// Workspaces ID-based have IDs > 0
// and workspaces name-based have IDs starting with -1337
@ -93,13 +87,13 @@ class CWorkspace {
} m_events;
private:
void init(PHLWORKSPACE self);
void init(PHLWORKSPACE self);
CHyprSignalListener m_focusedWindowHook;
bool m_inert = true;
SP<HOOK_CALLBACK_FN> m_focusedWindowHook;
bool m_inert = true;
SP<CWorkspace> m_selfPersistent; // for persistent workspaces.
bool m_persistent = false;
SP<CWorkspace> m_selfPersistent; // for persistent workspaces.
bool m_persistent = false;
};
inline bool valid(const PHLWORKSPACE& ref) {

View file

@ -1,7 +1,7 @@
#include "WindowHistoryTracker.hpp"
#include "../../managers/HookSystemManager.hpp"
#include "../view/Window.hpp"
#include "../../event/EventBus.hpp"
using namespace Desktop;
using namespace Desktop::History;
@ -12,12 +12,18 @@ SP<CWindowHistoryTracker> History::windowTracker() {
}
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
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) {

View file

@ -2,13 +2,9 @@
#include "../../helpers/Monitor.hpp"
#include "../Workspace.hpp"
#include "../state/FocusState.hpp"
#include "../../managers/eventLoop/EventLoopManager.hpp"
#include "../../event/EventBus.hpp"
#include "../../managers/HookSystemManager.hpp"
#include "../../config/ConfigValue.hpp"
#include <hyprutils/utils/ScopeGuard.hpp>
using namespace Desktop;
using namespace Desktop::History;
@ -18,16 +14,27 @@ SP<CWorkspaceHistoryTracker> History::workspaceTracker() {
}
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);
track(workspace);
});
static auto P1 = Event::bus()->m_events.monitor.focused.listen([this](PHLMONITOR mon) {
// This sucks ASS, but we have to do this because switching to a workspace on another mon will trigger a workspace event right afterwards and we don't
// want to remember the workspace that was not visible there
// TODO: do something about this
g_pEventLoopManager->doLater([this, mon = PHLMONITORREF{mon}] {
if (mon)
track(mon->m_activeWorkspace);
});
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) {
if (!w || !w->m_monitor || w == m_lastWorkspaceData.workspace)
if (!w->m_monitor)
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 (m_lastWorkspaceData.workspace == w && !*PALLOWWORKSPACECYCLES)
if (!monData.workspace) {
data.previous.reset();
data.previousID = WORKSPACE_INVALID;
data.previousName = "";
return;
data.previous = m_lastWorkspaceData.workspace;
if (m_lastWorkspaceData.workspace) {
data.previousName = m_lastWorkspaceData.workspace->m_name;
data.previousID = m_lastWorkspaceData.workspace->m_id;
data.previousMon = m_lastWorkspaceData.workspace->m_monitor;
} else {
data.previousName = m_lastWorkspaceData.workspaceName;
data.previousID = m_lastWorkspaceData.workspaceID;
data.previousMon = m_lastWorkspaceData.monitor;
}
if (monData.workspace == w && !*PALLOWWORKSPACECYCLES) {
track(w->m_monitor.lock());
return;
}
data.previous = monData.workspace;
data.previousName = monData.workspace->m_name;
data.previousID = monData.workspace->m_id;
data.previousMon = monData.workspace->m_monitor;
track(w->m_monitor.lock());
}
void CWorkspaceHistoryTracker::track(PHLMONITOR mon) {
auto& data = dataFor(mon);
data.workspace = mon->m_activeWorkspace;
data.workspaceName = mon->m_activeWorkspace ? mon->m_activeWorkspace->m_name : "";
data.workspaceID = mon->activeWorkspaceID();
}
void CWorkspaceHistoryTracker::gc() {
std::erase_if(m_datas, [](const auto& e) { return !e.workspace; });
std::erase_if(m_monitorDatas, [](const auto& e) { return !e.monitor; });
}
const CWorkspaceHistoryTracker::SWorkspacePreviousData* CWorkspaceHistoryTracker::previousWorkspace(PHLWORKSPACE ws) {
@ -137,15 +156,3 @@ SWorkspaceIDName CWorkspaceHistoryTracker::previousWorkspaceIDName(PHLWORKSPACE
return SWorkspaceIDName{.id = DATA->previousID, .name = DATA->previousName, .isAutoIDd = DATA->previousID <= 0};
}
void CWorkspaceHistoryTracker::setLastWorkspaceData(PHLWORKSPACE w) {
if (!w) {
m_lastWorkspaceData = {};
return;
}
m_lastWorkspaceData.workspace = w;
m_lastWorkspaceData.workspaceID = w->m_id;
m_lastWorkspaceData.workspaceName = w->m_name;
m_lastWorkspaceData.monitor = w->m_monitor;
}

View file

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

View file

@ -6,18 +6,16 @@ using namespace Desktop;
// fuck me. Writing this at 11pm, and I have an in-class test tomorrow.
// I am failing that bitch
CReservedArea::CReservedArea(const Vector2D& tl, const Vector2D& br) : m_initialTopLeft(tl.clamp({0, 0})), m_initialBottomRight(br.clamp({0, 0})) {
CReservedArea::CReservedArea(const Vector2D& tl, const Vector2D& br) : m_initialTopLeft(tl), m_initialBottomRight(br) {
calculate();
}
CReservedArea::CReservedArea(double top, double right, double bottom, double left) :
m_initialTopLeft(std::max(left, 0.0), std::max(top, 0.0)), m_initialBottomRight(std::max(right, 0.0), std::max(bottom, 0.0)) {
CReservedArea::CReservedArea(double top, double right, double bottom, double left) : m_initialTopLeft(left, top), m_initialBottomRight(right, bottom) {
calculate();
}
CReservedArea::CReservedArea(const CBox& parent, const CBox& child) {
if (parent.empty() || child.empty())
return; // empty reserved area
ASSERT(!parent.empty() && !child.empty());
ASSERT(parent.containsPoint(child.pos() + 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];
ref.topLeft += topLeft;
ref.bottomRight += bottomRight;
ref.topLeft = ref.topLeft.clamp({0, 0});
ref.bottomRight = ref.bottomRight.clamp({0, 0});
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_NAMESPACE, RULE_MATCH_ENGINE_REGEX}, //
{RULE_PROP_EXEC_TOKEN, RULE_MATCH_ENGINE_REGEX}, //
{RULE_PROP_EXEC_PID, RULE_MATCH_ENGINE_INT}, //
};
const std::vector<std::string>& Rule::allMatchPropStrings() {
@ -126,11 +125,10 @@ const std::string& IRule::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.isExecPersistent = persistent;
m_execData.token = token;
m_execData.pid = pid;
m_execData.expiresAt = Time::steadyNow() + std::chrono::minutes(1);
}

View file

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

View file

@ -4,7 +4,6 @@
#include "../../view/LayerSurface.hpp"
#include "../../types/OverridableVar.hpp"
#include "../../../helpers/MiscFunctions.hpp"
#include "../../../event/EventBus.hpp"
using namespace Desktop;
using namespace Desktop::Rule;
@ -33,38 +32,11 @@ void CLayerRuleApplicator::resetProps(std::underlying_type_t<eRuleProperty> prop
UNSET(aboveLock)
UNSET(ignoreAlpha)
UNSET(animationStyle)
#undef UNSET
if (prio == Types::PRIORITY_WINDOW_RULE)
std::erase_if(m_otherProps.props, [props](const auto& el) { return !el.second || el.second->propMask & props; });
}
void CLayerRuleApplicator::applyDynamicRule(const SP<CLayerRule>& rule) {
for (const auto& [key, effect] : rule->effects()) {
switch (key) {
default: {
if (key <= LAYER_RULE_EFFECT_LAST_STATIC) {
Log::logger->log(Log::TRACE, "CLayerRuleApplicator::applyDynamicRule: Skipping effect {}, not dynamic", sc<std::underlying_type_t<eLayerRuleEffect>>(key));
break;
}
// custom type, add to our vec
if (!m_otherProps.props.contains(key)) {
m_otherProps.props.emplace(key,
makeUnique<SCustomPropContainer>(SCustomPropContainer{
.idx = key,
.propMask = rule->getPropertiesMask(),
.effect = effect,
}));
} else {
auto& e = m_otherProps.props[key];
e->propMask |= rule->getPropertiesMask();
e->effect = effect;
}
break;
}
case LAYER_RULE_EFFECT_NONE: {
Log::logger->log(Log::ERR, "CLayerRuleApplicator::applyDynamicRule: BUG THIS: LAYER_RULE_EFFECT_NONE??");
break;
@ -101,8 +73,8 @@ void CLayerRuleApplicator::applyDynamicRule(const SP<CLayerRule>& rule) {
}
case LAYER_RULE_EFFECT_ORDER: {
try {
m_order.first.set(std::stoi(effect), Types::PRIORITY_WINDOW_RULE);
m_order.second |= rule->getPropertiesMask();
m_noScreenShare.first.set(std::stoi(effect), Types::PRIORITY_WINDOW_RULE);
m_noScreenShare.second |= rule->getPropertiesMask();
} catch (...) { Log::logger->log(Log::ERR, "CLayerRuleApplicator::applyDynamicRule: invalid order {}", effect); }
break;
}
@ -153,7 +125,4 @@ void CLayerRuleApplicator::propertiesChanged(std::underlying_type_t<eRulePropert
applyDynamicRule(wr);
}
// for plugins
Event::bus()->m_events.layer.updateRules.emit(m_ls.lock());
}

View file

@ -1,6 +1,5 @@
#pragma once
#include "LayerRuleEffectContainer.hpp"
#include "../../DesktopTypes.hpp"
#include "../Rule.hpp"
#include "../../types/OverridableVar.hpp"
@ -22,17 +21,6 @@ namespace Desktop::Rule {
void propertiesChanged(std::underlying_type_t<eRuleProperty> props);
void resetProps(std::underlying_type_t<eRuleProperty> props, Types::eOverridePriority prio = Types::PRIORITY_WINDOW_RULE);
struct SCustomPropContainer {
CLayerRuleEffectContainer::storageType idx = LAYER_RULE_EFFECT_NONE;
std::underlying_type_t<eRuleProperty> propMask = RULE_PROP_NONE;
std::string effect;
};
// This struct holds props that were dynamically registered. Plugins may read this.
struct {
std::unordered_map<CLayerRuleEffectContainer::storageType, UP<SCustomPropContainer>> props;
} m_otherProps;
#define COMMA ,
#define DEFINE_PROP(type, name, def) \
private: \

View file

@ -8,5 +8,5 @@ CWorkspaceMatchEngine::CWorkspaceMatchEngine(const std::string& s) : m_value(s)
}
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;
break;
case RULE_PROP_GROUP:
if (!engine->match(!!w->m_group))
if (!engine->match(w->m_groupData.pNextWindow))
return false;
break;
case RULE_PROP_MODAL:
@ -97,31 +97,27 @@ bool CWindowRule::matches(PHLWINDOW w, bool allowEnvLookup) {
return false;
break;
case RULE_PROP_CONTENT:
if (!engine->match(w->getContentType()) && !engine->match(NContentType::toString(w->getContentType())))
if (!engine->match(NContentType::toString(w->getContentType())))
return false;
break;
case RULE_PROP_XDG_TAG:
if (!w->xdgTag().has_value() || !engine->match(*w->xdgTag()))
return false;
break;
case RULE_PROP_EXEC_TOKEN:
// this is only allowed on static rules, we don't need it on dynamic plus it's expensive
if (!allowEnvLookup)
break;
const auto ENV = w->getEnv();
bool match = false;
const auto ENV = w->getEnv();
if (ENV.contains(EXEC_RULE_ENV_NAME)) {
if (engine->match(ENV.at(EXEC_RULE_ENV_NAME)))
match = true;
} else if (m_matchEngines.contains(RULE_PROP_EXEC_PID)) {
if (m_matchEngines.at(RULE_PROP_EXEC_PID)->match(w->getPID()))
match = true;
const auto TKN = ENV.at(EXEC_RULE_ENV_NAME);
if (!engine->match(TKN))
return false;
break;
}
if (!match)
return false;
break;
return false;
}
}
@ -157,6 +153,11 @@ SP<CWindowRule> CWindowRule::buildFromExecString(std::string&& s) {
wr->addEffect(*EFFECT, std::string{"1"});
}
const auto TOKEN = g_pTokenManager->registerNewToken(nullptr, std::chrono::seconds(1));
wr->markAsExecRule(TOKEN, false /* TODO: could be nice. */);
wr->registerMatch(RULE_PROP_EXEC_TOKEN, TOKEN);
return wr;
}

View file

@ -4,7 +4,8 @@
#include "../utils/SetUtils.hpp"
#include "../../view/Window.hpp"
#include "../../types/OverridableVar.hpp"
#include "../../../event/EventBus.hpp"
#include "../../../managers/LayoutManager.hpp"
#include "../../../managers/HookSystemManager.hpp"
#include <hyprutils/string/String.hpp>
@ -150,7 +151,7 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const
CGradientValueData activeBorderGradient = {};
CGradientValueData inactiveBorderGradient = {};
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
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);
m_inactiveBorderColor.first =
Types::COverridableVar(CGradientValueData(CHyprColor(configStringToInt(colorsAndAngles[1]).value_or(0))), Types::PRIORITY_WINDOW_RULE);
m_activeBorderColor.second |= rule->getPropertiesMask();
m_inactiveBorderColor.second |= rule->getPropertiesMask();
break;
}
@ -266,17 +265,13 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const
if (!m_window)
break;
const auto VEC = m_window->calculateExpression(effect);
if (!VEC) {
Log::logger->log(Log::ERR, "failed to parse {} as an expression", effect);
break;
}
if (VEC->x < 1 || VEC->y < 1) {
const auto VEC = configStringToVector2D(effect);
if (VEC.x < 1 || VEC.y < 1) {
Log::logger->log(Log::ERR, "Invalid size for maxsize");
break;
}
m_maxSize.first = Types::COverridableVar(*VEC, Types::PRIORITY_WINDOW_RULE);
m_maxSize.first = Types::COverridableVar(VEC, Types::PRIORITY_WINDOW_RULE);
if (*PCLAMP_TILED || m_window->m_isFloating)
m_window->clampWindowSize(std::nullopt, m_maxSize.first.value());
@ -291,18 +286,13 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const
if (!m_window)
break;
const auto VEC = m_window->calculateExpression(effect);
if (!VEC) {
Log::logger->log(Log::ERR, "failed to parse {} as an expression", effect);
break;
}
if (VEC->x < 1 || VEC->y < 1) {
const auto VEC = configStringToVector2D(effect);
if (VEC.x < 1 || VEC.y < 1) {
Log::logger->log(Log::ERR, "Invalid size for maxsize");
break;
}
m_minSize.first = Types::COverridableVar(*VEC, Types::PRIORITY_WINDOW_RULE);
m_minSize.first = Types::COverridableVar(VEC, Types::PRIORITY_WINDOW_RULE);
if (*PCLAMP_TILED || m_window->m_isFloating)
m_window->clampWindowSize(m_minSize.first.value(), std::nullopt);
} catch (std::exception& e) { Log::logger->log(Log::ERR, "minsize rule \"{}\" failed with: {}", effect, e.what()); }
@ -547,7 +537,7 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyStaticRule(const
return SRuleResult{};
}
void CWindowRuleApplicator::readStaticRules(bool preRead) {
void CWindowRuleApplicator::readStaticRules() {
if (!m_window)
return;
@ -602,8 +592,7 @@ void CWindowRuleApplicator::readStaticRules(bool preRead) {
for (const auto& wr : execRules) {
applyStaticRule(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;
}
m_window->updateWindowData();
m_window->updateWindowDecos();
m_window->updateDecorationValues();
if (needsRelayout)
g_pDecorationPositioner->forceRecalcFor(m_window.lock());
// 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);
std::unordered_set<CWindowRuleEffectContainer::storageType> resetProps(std::underlying_type_t<eRuleProperty> props,
Types::eOverridePriority prio = Types::PRIORITY_WINDOW_RULE);
void readStaticRules(bool preRead = false);
void readStaticRules();
void applyStaticRules();
// static props
struct {

View file

@ -3,19 +3,14 @@
#include "../../Compositor.hpp"
#include "../../protocols/XDGShell.hpp"
#include "../../render/Renderer.hpp"
#include "../../managers/LayoutManager.hpp"
#include "../../managers/EventManager.hpp"
#include "../../managers/input/InputManager.hpp"
#include "../../managers/SeatManager.hpp"
#include "../../managers/HookSystemManager.hpp"
#include "../../xwayland/XSurface.hpp"
#include "../../protocols/PointerConstraints.hpp"
#include "managers/animation/DesktopAnimationManager.hpp"
#include "../../layout/LayoutManager.hpp"
#include "../../event/EventBus.hpp"
using namespace Desktop;
#define COMMA ,
SP<CFocusState> Desktop::focusState() {
static SP<CFocusState> state = makeShared<CFocusState>();
return state;
@ -37,7 +32,6 @@ static SFullscreenWorkspaceFocusResult onFullscreenWorkspaceFocusWindow(PHLWINDO
if (pWindow->m_isFloating) {
// if the window is floating, just bring it to the top
pWindow->m_createdOverFullscreen = true;
g_pDesktopAnimationManager->setFullscreenFloatingFade(pWindow, 1.f);
g_pHyprRenderer->damageWindow(pWindow);
return {};
}
@ -67,7 +61,7 @@ static SFullscreenWorkspaceFocusResult onFullscreenWorkspaceFocusWindow(PHLWINDO
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->m_workspace)
return;
@ -87,10 +81,10 @@ void CFocusState::fullWindowFocus(PHLWINDOW pWindow, eFocusReason reason, SP<CWL
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 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())
return;
// m_target on purpose, this avoids the group
if (pWindow)
g_layoutManager->bringTargetToTop(pWindow->m_target);
g_pLayoutManager->getCurrentLayout()->bringWindowToTop(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{"activewindowv2", ""});
Event::bus()->m_events.window.active.emit(nullptr, reason);
EMIT_HOOK_EVENT("activeWindow", PHLWINDOW{nullptr});
g_pLayoutManager->getCurrentLayout()->onWindowFocusChange(nullptr);
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 = "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();
if (*PFOLLOWMOUSE == 0)
g_pInputManager->sendMotionEventsToFocused();
if (pWindow->m_group)
if (pWindow->m_groupData.pNextWindow)
pWindow->deactivateGroupMembers();
}
@ -233,7 +229,7 @@ void CFocusState::rawSurfaceFocus(SP<CWLSurfaceResource> pSurface, PHLWINDOW pWi
g_pSeatManager->setKeyboardFocus(nullptr);
g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindow", .data = ","});
g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindowv2", .data = ""});
Event::bus()->m_events.input.keyboard.focus.emit(nullptr);
EMIT_HOOK_EVENT("keyboardFocus", SP<CWLSurfaceResource>{nullptr});
m_focusSurface.reset();
return;
}
@ -249,7 +245,7 @@ void CFocusState::rawSurfaceFocus(SP<CWLSurfaceResource> pSurface, PHLWINDOW pWi
g_pXWaylandManager->activateSurface(pSurface, true);
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 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 = "focusedmonv2", .data = pMonitor->m_name + "," + WORKSPACE_ID});
Event::bus()->m_events.monitor.focused.emit(pMonitor);
EMIT_HOOK_EVENT("focusedMon", pMonitor);
m_focusMonitor = pMonitor;
}
@ -298,7 +294,3 @@ void CFocusState::resetWindowFocus() {
m_focusWindow.reset();
m_focusSurface.reset();
}
bool Desktop::isHardInputFocusReason(eFocusReason r) {
return r == FOCUS_REASON_NEW_WINDOW || r == FOCUS_REASON_KEYBIND || r == FOCUS_REASON_GHOSTS || r == FOCUS_REASON_CLICK || r == FOCUS_REASON_DESKTOP_STATE_CHANGE;
}

View file

@ -1,24 +1,11 @@
#pragma once
#include "../DesktopTypes.hpp"
#include "../../helpers/signal/Signal.hpp"
#include "../../SharedDefs.hpp"
class CWLSurfaceResource;
namespace Desktop {
enum eFocusReason : uint8_t {
FOCUS_REASON_UNKNOWN = 0,
FOCUS_REASON_FFM,
FOCUS_REASON_KEYBIND,
FOCUS_REASON_CLICK,
FOCUS_REASON_OTHER,
FOCUS_REASON_DESKTOP_STATE_CHANGE,
FOCUS_REASON_NEW_WINDOW,
FOCUS_REASON_GHOSTS,
};
bool isHardInputFocusReason(eFocusReason r);
class CFocusState {
public:
CFocusState();
@ -28,8 +15,8 @@ namespace Desktop {
CFocusState(CFocusState&) = delete;
CFocusState(const CFocusState&) = delete;
void fullWindowFocus(PHLWINDOW w, eFocusReason reason, SP<CWLSurfaceResource> surface = nullptr, bool forceFSCycle = false);
void rawWindowFocus(PHLWINDOW w, eFocusReason reason, SP<CWLSurfaceResource> surface = nullptr);
void fullWindowFocus(PHLWINDOW w, SP<CWLSurfaceResource> surface = nullptr, bool forceFSCycle = false);
void rawWindowFocus(PHLWINDOW w, SP<CWLSurfaceResource> surface = nullptr);
void rawSurfaceFocus(SP<CWLSurfaceResource> s, PHLWINDOW pWindowOwner = nullptr);
void rawMonitorFocus(PHLMONITOR m);
@ -44,7 +31,7 @@ namespace Desktop {
PHLWINDOWREF m_focusWindow;
PHLMONITORREF m_focusMonitor;
CHyprSignalListener m_windowOpen, m_windowClose;
SP<HOOK_CALLBACK_FN> m_windowOpen, m_windowClose;
};
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 "../../helpers/Monitor.hpp"
#include "../../managers/input/InputManager.hpp"
#include "../../managers/HookSystemManager.hpp"
#include "../../managers/EventManager.hpp"
#include "../../event/EventBus.hpp"
using namespace Desktop;
using namespace Desktop::View;
@ -23,11 +23,24 @@ PHLLS CLayerSurface::create(SP<CLayerShellResource> resource) {
pLS->m_wlSurface->assign(resource->m_surface.lock(), pLS);
if (!pMonitor) {
Log::logger->log(Log::ERR, "New LS has no monitor??");
return pLS;
}
if (pMonitor->m_mirrorOf)
pMonitor = g_pCompositor->m_monitors.front();
pLS->m_self = pLS;
pLS->m_namespace = resource->m_layerNamespace;
pLS->m_layer = resource->m_current.layer;
pLS->m_popupHead = CPopup::create(pLS);
pLS->m_monitor = pMonitor;
pMonitor->m_layerSurfaceLayers[resource->m_current.layer].emplace_back(pLS);
pLS->m_ruleApplicator = makeUnique<Desktop::Rule::CLayerRuleApplicator>(pLS);
pLS->m_self = pLS;
pLS->m_namespace = resource->m_layerNamespace;
pLS->m_layer = std::clamp(resource->m_current.layer, ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY);
pLS->m_popupHead = CPopup::create(pLS);
g_pAnimationManager->createAnimation(0.f, pLS->m_alpha, g_pConfigManager->getAnimationPropertyConfig("fadeLayersIn"), pLS, AVARDAMAGE_ENTIRE);
g_pAnimationManager->createAnimation(Vector2D(0, 0), pLS->m_realPosition, g_pConfigManager->getAnimationPropertyConfig("layersIn"), pLS, AVARDAMAGE_ENTIRE);
@ -37,19 +50,6 @@ PHLLS CLayerSurface::create(SP<CLayerShellResource> resource) {
pLS->m_alpha->setValueAndWarp(0.f);
if (!pMonitor) {
Log::logger->log(Log::DEBUG, "LayerSurface {:x} (namespace {} layer {}) created on NO MONITOR ?!", rc<uintptr_t>(resource.get()), resource->m_layerNamespace,
sc<int>(pLS->m_layer));
return pLS;
}
if (pMonitor->m_mirrorOf)
pMonitor = g_pCompositor->m_monitors.front();
pLS->m_monitor = pMonitor;
pMonitor->m_layerSurfaceLayers[resource->m_current.layer].emplace_back(pLS);
Log::logger->log(Log::DEBUG, "LayerSurface {:x} (namespace {} layer {}) created on monitor {}", rc<uintptr_t>(resource.get()), resource->m_layerNamespace,
sc<int>(pLS->m_layer), pMonitor->m_name);
@ -222,7 +222,7 @@ void CLayerSurface::onMap() {
m_fadingOut = false;
g_pEventManager->postEvent(SHyprIPCEvent{.event = "openlayer", .data = m_namespace});
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->setPreferredTransformForSurface(m_wlSurface->resource(), PMONITOR->m_transform);
@ -232,7 +232,7 @@ void CLayerSurface::onUnmap() {
Log::logger->log(Log::DEBUG, "LayerSurface {:x} unmapped", rc<uintptr_t>(m_layerSurface.get()));
g_pEventManager->postEvent(SHyprIPCEvent{.event = "closelayer", .data = m_layerSurface->m_layerNamespace});
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; });
@ -323,18 +323,16 @@ void CLayerSurface::onCommit() {
if (m_layerSurface->m_current.committed != 0) {
if (m_layerSurface->m_current.committed & CLayerShellResource::eCommittedState::STATE_LAYER && m_layerSurface->m_current.layer != m_layer) {
const auto NEW_LAYER = std::clamp(m_layerSurface->m_current.layer, ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY);
for (auto it = PMONITOR->m_layerSurfaceLayers[m_layer].begin(); it != PMONITOR->m_layerSurfaceLayers[m_layer].end(); it++) {
if (*it == m_self) {
PMONITOR->m_layerSurfaceLayers[NEW_LAYER].emplace_back(*it);
PMONITOR->m_layerSurfaceLayers[m_layerSurface->m_current.layer].emplace_back(*it);
PMONITOR->m_layerSurfaceLayers[m_layer].erase(it);
break;
}
}
m_layer = NEW_LAYER;
m_aboveFullscreen = NEW_LAYER >= ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY;
m_layer = m_layerSurface->m_current.layer;
m_aboveFullscreen = m_layerSurface->m_current.layer >= ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY;
// if in fullscreen, only overlay can be above.
*m_alpha = PMONITOR->inFullscreenMode() ? (m_layer >= ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY ? 1.F : 0.F) : 1.F;

View file

@ -9,7 +9,6 @@
#include "../../managers/animation/AnimationManager.hpp"
#include "LayerSurface.hpp"
#include "../../managers/input/InputManager.hpp"
#include "../../managers/eventLoop/EventLoopManager.hpp"
#include "../../render/Renderer.hpp"
#include "../../render/OpenGL.hpp"
#include <ranges>
@ -109,12 +108,8 @@ void CPopup::initAllSignals() {
m_alpha->setCallbackOnEnd(
[this](auto) {
if (inert()) {
g_pEventLoopManager->doLater([p = m_self] {
if (!p)
return;
g_pHyprRenderer->damageBox(CBox{p->coordsGlobal(), p->size()});
p->fullyDestroy();
});
g_pHyprRenderer->damageBox(CBox{coordsGlobal(), size()});
fullyDestroy();
}
},
false);
@ -199,7 +194,7 @@ void CPopup::onMap() {
//unconstrain();
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)
g_pHyprOpenGL->markBlurDirtyForMonitor(g_pCompositor->getMonitorFromID(m_layerOwner->m_layer));
@ -339,7 +334,8 @@ void CPopup::reposition() {
if (!PMONITOR)
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 {
@ -403,14 +399,10 @@ void CPopup::recheckChildrenRecursive() {
std::vector<WP<CPopup>> cpy;
std::ranges::for_each(m_children, [&cpy](const auto& el) { cpy.emplace_back(el); });
for (auto const& c : cpy) {
if (!c || !c->visible())
if (!c->visible())
continue;
// keep ref, onCommit can call onDestroy
auto x = c.lock();
x->onCommit(true);
x->recheckChildrenRecursive();
c->onCommit(true);
c->recheckChildrenRecursive();
}
}

File diff suppressed because it is too large Load diff

View file

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

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

View file

@ -27,7 +27,6 @@ class CAsyncDialogBox {
void kill();
bool isRunning() const;
pid_t getPID() const;
void setExecRule(std::string&& s);
SP<CAsyncDialogBox> lockSelf();
@ -42,8 +41,7 @@ class CAsyncDialogBox {
pid_t m_dialogPid = 0;
wl_event_source* m_readEventSource = nullptr;
Hyprutils::OS::CFileDescriptor m_pipeReadFd;
std::string m_stdout = "";
std::string m_execRuleToken = "";
std::string m_stdout = "";
const std::string m_title;
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 <drm_fourcc.h>
/*
DRM formats are LE, while OGL is BE. The two primary formats
will be flipped, so we will set flipRB which will later use swizzle
to flip the red and blue channels.
This will not work on GLES2, but I want to drop support for it one day anyways.
*/
inline const std::vector<SPixelFormat> GLES3_FORMATS = {
{
.drmFormat = DRM_FORMAT_ARGB8888,
.glInternalFormat = GL_RGBA8,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_BYTE,
.withAlpha = true,
.alphaStripped = DRM_FORMAT_XRGB8888,
.bytesPerBlock = 4,
.swizzle = {SWIZZLE_BGRA},
.drmFormat = DRM_FORMAT_ARGB8888,
.flipRB = true,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_BYTE,
.withAlpha = true,
.alphaStripped = DRM_FORMAT_XRGB8888,
.bytesPerBlock = 4,
},
{
.drmFormat = DRM_FORMAT_XRGB8888,
.glInternalFormat = GL_RGBA8,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_BYTE,
.withAlpha = false,
.alphaStripped = DRM_FORMAT_XRGB8888,
.bytesPerBlock = 4,
.swizzle = {SWIZZLE_BGR1},
.drmFormat = DRM_FORMAT_XRGB8888,
.flipRB = true,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_BYTE,
.withAlpha = false,
.alphaStripped = DRM_FORMAT_XRGB8888,
.bytesPerBlock = 4,
},
{
.drmFormat = DRM_FORMAT_XBGR8888,
.glInternalFormat = GL_RGBA8,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_BYTE,
.withAlpha = false,
.alphaStripped = DRM_FORMAT_XBGR8888,
.bytesPerBlock = 4,
.swizzle = {SWIZZLE_RGB1},
.drmFormat = DRM_FORMAT_XBGR8888,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_BYTE,
.withAlpha = false,
.alphaStripped = DRM_FORMAT_XBGR8888,
.bytesPerBlock = 4,
},
{
.drmFormat = DRM_FORMAT_ABGR8888,
.glInternalFormat = GL_RGBA8,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_BYTE,
.withAlpha = true,
.alphaStripped = DRM_FORMAT_XBGR8888,
.bytesPerBlock = 4,
.swizzle = {SWIZZLE_RGBA},
.drmFormat = DRM_FORMAT_ABGR8888,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_BYTE,
.withAlpha = true,
.alphaStripped = DRM_FORMAT_XBGR8888,
.bytesPerBlock = 4,
},
{
.drmFormat = DRM_FORMAT_BGR888,
.glInternalFormat = GL_RGB8,
.glFormat = GL_RGB,
.glType = GL_UNSIGNED_BYTE,
.withAlpha = false,
.alphaStripped = DRM_FORMAT_BGR888,
.bytesPerBlock = 3,
.swizzle = {SWIZZLE_RGB1},
.drmFormat = DRM_FORMAT_BGR888,
.glFormat = GL_RGB,
.glType = GL_UNSIGNED_BYTE,
.withAlpha = false,
.alphaStripped = DRM_FORMAT_BGR888,
.bytesPerBlock = 3,
},
{
.drmFormat = DRM_FORMAT_RGBX4444,
.glInternalFormat = GL_RGBA4,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_SHORT_4_4_4_4,
.withAlpha = false,
.alphaStripped = DRM_FORMAT_RGBX4444,
.bytesPerBlock = 2,
.swizzle = {SWIZZLE_RGB1},
.drmFormat = DRM_FORMAT_RGBX4444,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_SHORT_4_4_4_4,
.withAlpha = false,
.alphaStripped = DRM_FORMAT_RGBX4444,
.bytesPerBlock = 2,
},
{
.drmFormat = DRM_FORMAT_RGBA4444,
.glInternalFormat = GL_RGBA4,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_SHORT_4_4_4_4,
.withAlpha = true,
.alphaStripped = DRM_FORMAT_RGBX4444,
.bytesPerBlock = 2,
.swizzle = {SWIZZLE_RGBA},
.drmFormat = DRM_FORMAT_RGBA4444,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_SHORT_4_4_4_4,
.withAlpha = true,
.alphaStripped = DRM_FORMAT_RGBX4444,
.bytesPerBlock = 2,
},
{
.drmFormat = DRM_FORMAT_RGBX5551,
.glInternalFormat = GL_RGB5_A1,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_SHORT_5_5_5_1,
.withAlpha = false,
.alphaStripped = DRM_FORMAT_RGBX5551,
.bytesPerBlock = 2,
.swizzle = {SWIZZLE_RGB1},
.drmFormat = DRM_FORMAT_RGBX5551,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_SHORT_5_5_5_1,
.withAlpha = false,
.alphaStripped = DRM_FORMAT_RGBX5551,
.bytesPerBlock = 2,
},
{
.drmFormat = DRM_FORMAT_RGBA5551,
.glInternalFormat = GL_RGB5_A1,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_SHORT_5_5_5_1,
.withAlpha = true,
.alphaStripped = DRM_FORMAT_RGBX5551,
.bytesPerBlock = 2,
.swizzle = {SWIZZLE_RGBA},
.drmFormat = DRM_FORMAT_RGBA5551,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_SHORT_5_5_5_1,
.withAlpha = true,
.alphaStripped = DRM_FORMAT_RGBX5551,
.bytesPerBlock = 2,
},
{
.drmFormat = DRM_FORMAT_RGB565,
.glInternalFormat = GL_RGB565,
.glFormat = GL_RGB,
.glType = GL_UNSIGNED_SHORT_5_6_5,
.withAlpha = false,
.alphaStripped = DRM_FORMAT_RGB565,
.bytesPerBlock = 2,
.swizzle = {SWIZZLE_RGB1},
.drmFormat = DRM_FORMAT_RGB565,
.glFormat = GL_RGB,
.glType = GL_UNSIGNED_SHORT_5_6_5,
.withAlpha = false,
.alphaStripped = DRM_FORMAT_RGB565,
.bytesPerBlock = 2,
},
{
.drmFormat = DRM_FORMAT_XBGR2101010,
.glInternalFormat = GL_RGB10_A2,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_INT_2_10_10_10_REV,
.withAlpha = false,
.alphaStripped = DRM_FORMAT_XBGR2101010,
.bytesPerBlock = 4,
.swizzle = {SWIZZLE_RGB1},
.drmFormat = DRM_FORMAT_XBGR2101010,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_INT_2_10_10_10_REV,
.withAlpha = false,
.alphaStripped = DRM_FORMAT_XBGR2101010,
.bytesPerBlock = 4,
},
{
.drmFormat = DRM_FORMAT_ABGR2101010,
.glInternalFormat = GL_RGB10_A2,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_INT_2_10_10_10_REV,
.withAlpha = true,
.alphaStripped = DRM_FORMAT_XBGR2101010,
.bytesPerBlock = 4,
.swizzle = {SWIZZLE_RGBA},
.drmFormat = DRM_FORMAT_ABGR2101010,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_INT_2_10_10_10_REV,
.withAlpha = true,
.alphaStripped = DRM_FORMAT_XBGR2101010,
.bytesPerBlock = 4,
},
{
.drmFormat = DRM_FORMAT_XRGB2101010,
.glInternalFormat = GL_RGB10_A2,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_INT_2_10_10_10_REV,
.withAlpha = false,
.alphaStripped = DRM_FORMAT_XRGB2101010,
.bytesPerBlock = 4,
.swizzle = {SWIZZLE_BGR1},
.drmFormat = DRM_FORMAT_XRGB2101010,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_INT_2_10_10_10_REV,
.withAlpha = false,
.alphaStripped = DRM_FORMAT_XRGB2101010,
.bytesPerBlock = 4,
},
{
.drmFormat = DRM_FORMAT_ARGB2101010,
.glInternalFormat = GL_RGB10_A2,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_INT_2_10_10_10_REV,
.withAlpha = true,
.alphaStripped = DRM_FORMAT_XRGB2101010,
.bytesPerBlock = 4,
.swizzle = {SWIZZLE_BGRA},
.drmFormat = DRM_FORMAT_ARGB2101010,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_INT_2_10_10_10_REV,
.withAlpha = true,
.alphaStripped = DRM_FORMAT_XRGB2101010,
.bytesPerBlock = 4,
},
{
.drmFormat = DRM_FORMAT_XBGR16161616F,
.glInternalFormat = GL_RGBA16F,
.glFormat = GL_RGBA,
.glType = GL_HALF_FLOAT,
.withAlpha = false,
.alphaStripped = DRM_FORMAT_XBGR16161616F,
.bytesPerBlock = 8,
.swizzle = {SWIZZLE_RGB1},
.drmFormat = DRM_FORMAT_XBGR16161616F,
.glFormat = GL_RGBA,
.glType = GL_HALF_FLOAT,
.withAlpha = false,
.alphaStripped = DRM_FORMAT_XBGR16161616F,
.bytesPerBlock = 8,
},
{
.drmFormat = DRM_FORMAT_ABGR16161616F,
.glInternalFormat = GL_RGBA16F,
.glFormat = GL_RGBA,
.glType = GL_HALF_FLOAT,
.withAlpha = true,
.alphaStripped = DRM_FORMAT_XBGR16161616F,
.bytesPerBlock = 8,
.swizzle = {SWIZZLE_RGBA},
.drmFormat = DRM_FORMAT_ABGR16161616F,
.glFormat = GL_RGBA,
.glType = GL_HALF_FLOAT,
.withAlpha = true,
.alphaStripped = DRM_FORMAT_XBGR16161616F,
.bytesPerBlock = 8,
},
{
.drmFormat = DRM_FORMAT_XBGR16161616,
.glInternalFormat = GL_RGBA16UI,
.glFormat = GL_RGBA_INTEGER,
.glType = GL_UNSIGNED_SHORT,
.withAlpha = false,
.alphaStripped = DRM_FORMAT_XBGR16161616,
.bytesPerBlock = 8,
.swizzle = {SWIZZLE_RGBA},
.drmFormat = DRM_FORMAT_XBGR16161616,
.glFormat = GL_RGBA16UI,
.glType = GL_UNSIGNED_SHORT,
.withAlpha = false,
.alphaStripped = DRM_FORMAT_XBGR16161616,
.bytesPerBlock = 8,
},
{
.drmFormat = DRM_FORMAT_ABGR16161616,
.glInternalFormat = GL_RGBA16UI,
.glFormat = GL_RGBA_INTEGER,
.glType = GL_UNSIGNED_SHORT,
.withAlpha = true,
.alphaStripped = DRM_FORMAT_XBGR16161616,
.bytesPerBlock = 8,
.swizzle = {SWIZZLE_RGBA},
.drmFormat = DRM_FORMAT_ABGR16161616,
.glFormat = GL_RGBA16UI,
.glType = GL_UNSIGNED_SHORT,
.withAlpha = true,
.alphaStripped = DRM_FORMAT_XBGR16161616,
.bytesPerBlock = 8,
},
{
.drmFormat = DRM_FORMAT_YVYU,
@ -198,28 +170,24 @@ inline const std::vector<SPixelFormat> GLES3_FORMATS = {
.blockSize = {2, 1},
},
{
.drmFormat = DRM_FORMAT_R8,
.glInternalFormat = GL_R8,
.glFormat = GL_RED,
.glType = GL_UNSIGNED_BYTE,
.bytesPerBlock = 1,
.swizzle = {SWIZZLE_R001},
.drmFormat = DRM_FORMAT_R8,
.bytesPerBlock = 1,
},
{
.drmFormat = DRM_FORMAT_GR88,
.glInternalFormat = GL_RG8,
.glFormat = GL_RG,
.glType = GL_UNSIGNED_BYTE,
.bytesPerBlock = 2,
.swizzle = {SWIZZLE_RG01},
.drmFormat = DRM_FORMAT_GR88,
.bytesPerBlock = 2,
},
{
.drmFormat = DRM_FORMAT_RGB888,
.glInternalFormat = GL_RGB8,
.glFormat = GL_RGB,
.glType = GL_UNSIGNED_BYTE,
.bytesPerBlock = 3,
.swizzle = {SWIZZLE_BGR1},
.drmFormat = DRM_FORMAT_RGB888,
.bytesPerBlock = 3,
},
{
.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;
}
bool NFormatUtils::isFormatYUV(uint32_t drmFormat) {
switch (drmFormat) {
case DRM_FORMAT_YUYV:
case DRM_FORMAT_YVYU:
case DRM_FORMAT_UYVY:
case DRM_FORMAT_VYUY:
case DRM_FORMAT_AYUV:
case DRM_FORMAT_NV12:
case DRM_FORMAT_NV21:
case DRM_FORMAT_NV16:
case DRM_FORMAT_NV61:
case DRM_FORMAT_YUV410:
case DRM_FORMAT_YUV411:
case DRM_FORMAT_YUV420:
case DRM_FORMAT_YUV422:
case DRM_FORMAT_YUV444: return true;
default: return false;
}
}
bool NFormatUtils::isFormatOpaque(DRMFormat drm) {
const auto FMT = NFormatUtils::getPixelFormatFromDRM(drm);
if (!FMT)
@ -297,38 +245,32 @@ int NFormatUtils::minStride(const SPixelFormat* const fmt, int32_t width) {
return std::ceil((width * fmt->bytesPerBlock) / pixelsPerBlock(fmt));
}
uint32_t NFormatUtils::drmFormatToGL(DRMFormat drm) {
switch (drm) {
case DRM_FORMAT_XRGB8888:
case DRM_FORMAT_XBGR8888: return GL_RGBA; // doesn't matter, opengl is gucci in this case.
case DRM_FORMAT_XRGB2101010:
case DRM_FORMAT_XBGR2101010: return GL_RGB10_A2;
default: return GL_RGBA;
}
UNREACHABLE();
return GL_RGBA;
}
uint32_t NFormatUtils::glFormatToType(uint32_t gl) {
return gl != GL_RGBA ? GL_UNSIGNED_INT_2_10_10_10_REV : GL_UNSIGNED_BYTE;
}
std::string NFormatUtils::drmFormatName(DRMFormat drm) {
auto n = drmGetFormatName(drm);
if (!n)
return "unknown";
auto n = drmGetFormatName(drm);
std::string name = n;
free(n); // NOLINT(cppcoreguidelines-no-malloc,-warnings-as-errors)
return name;
}
std::string NFormatUtils::drmModifierName(uint64_t mod) {
auto n = drmGetFormatModifierName(mod);
if (!n)
return "unknown";
auto n = drmGetFormatModifierName(mod);
std::string name = n;
free(n); // NOLINT(cppcoreguidelines-no-malloc,-warnings-as-errors)
return name;
}
DRMFormat NFormatUtils::alphaFormat(DRMFormat prevFormat) {
switch (prevFormat) {
case DRM_FORMAT_XRGB8888: return DRM_FORMAT_ARGB8888;
case DRM_FORMAT_XBGR8888: return DRM_FORMAT_ABGR8888;
case DRM_FORMAT_BGRX8888: return DRM_FORMAT_BGRA8888;
case DRM_FORMAT_RGBX8888: return DRM_FORMAT_RGBA8888;
case DRM_FORMAT_XRGB2101010: return DRM_FORMAT_ARGB2101010;
case DRM_FORMAT_XBGR2101010: return DRM_FORMAT_ABGR2101010;
case DRM_FORMAT_RGBX1010102: return DRM_FORMAT_RGBA1010102;
case DRM_FORMAT_BGRX1010102: return DRM_FORMAT_BGRA1010102;
default: return 0;
}
}

View file

@ -2,43 +2,22 @@
#include <cstdint>
#include <string>
#include <GLES3/gl32.h>
#include "math/Math.hpp"
#include <aquamarine/backend/Misc.hpp>
using DRMFormat = uint32_t;
using SHMFormat = uint32_t;
#define SWIZZLE_A1GB {GL_ALPHA, GL_ONE, GL_GREEN, GL_BLUE}
#define SWIZZLE_ABG1 {GL_ALPHA, GL_BLUE, GL_GREEN, GL_ONE}
#define SWIZZLE_ABGR {GL_ALPHA, GL_BLUE, GL_GREEN, GL_RED}
#define SWIZZLE_ARGB {GL_ALPHA, GL_RED, GL_GREEN, GL_BLUE}
#define SWIZZLE_B1RG {GL_BLUE, GL_ONE, GL_RED, GL_GREEN}
#define SWIZZLE_BARG {GL_BLUE, GL_ALPHA, GL_RED, GL_GREEN}
#define SWIZZLE_BGR1 {GL_BLUE, GL_GREEN, GL_RED, GL_ONE}
#define SWIZZLE_BGRA {GL_BLUE, GL_GREEN, GL_RED, GL_ALPHA}
#define SWIZZLE_G1AB {GL_GREEN, GL_ONE, GL_ALPHA, GL_BLUE}
#define SWIZZLE_GBA1 {GL_GREEN, GL_BLUE, GL_ALPHA, GL_ONE}
#define SWIZZLE_GBAR {GL_GREEN, GL_BLUE, GL_ALPHA, GL_RED}
#define SWIZZLE_GRAB {GL_GREEN, GL_RED, GL_ALPHA, GL_BLUE}
#define SWIZZLE_R001 {GL_RED, GL_ZERO, GL_ZERO, GL_ONE}
#define SWIZZLE_R1BG {GL_RED, GL_ONE, GL_BLUE, GL_GREEN}
#define SWIZZLE_RABG {GL_RED, GL_ALPHA, GL_BLUE, GL_GREEN}
#define SWIZZLE_RG01 {GL_RED, GL_GREEN, GL_ZERO, GL_ONE}
#define SWIZZLE_GR01 {GL_GREEN, GL_RED, GL_ZERO, GL_ONE}
#define SWIZZLE_RGB1 {GL_RED, GL_GREEN, GL_BLUE, GL_ONE}
#define SWIZZLE_RGBA {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA}
struct SPixelFormat {
DRMFormat drmFormat = 0; /* DRM_FORMAT_INVALID */
int glInternalFormat = 0;
int glFormat = 0;
int glType = 0;
bool withAlpha = true;
DRMFormat alphaStripped = 0; /* DRM_FORMAT_INVALID */
uint32_t bytesPerBlock = 0;
Vector2D blockSize;
std::optional<std::array<GLint, 4>> swizzle = std::nullopt;
DRMFormat drmFormat = 0; /* DRM_FORMAT_INVALID */
bool flipRB = false;
int glInternalFormat = 0;
int glFormat = 0;
int glType = 0;
bool withAlpha = true;
DRMFormat alphaStripped = 0; /* DRM_FORMAT_INVALID */
uint32_t bytesPerBlock = 0;
Vector2D blockSize;
};
using SDRMFormat = Aquamarine::SDRMFormat;
@ -49,11 +28,11 @@ namespace NFormatUtils {
const SPixelFormat* getPixelFormatFromDRM(DRMFormat drm);
const SPixelFormat* getPixelFormatFromGL(uint32_t glFormat, uint32_t glType, bool alpha);
bool isFormatYUV(uint32_t drmFormat);
bool isFormatOpaque(DRMFormat drm);
int pixelsPerBlock(const SPixelFormat* const fmt);
int minStride(const SPixelFormat* const fmt, int32_t width);
uint32_t drmFormatToGL(DRMFormat drm);
uint32_t glFormatToType(uint32_t gl);
std::string drmFormatName(DRMFormat drm);
std::string drmModifierName(uint64_t mod);
DRMFormat alphaFormat(DRMFormat prevFormat);
};

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)) {
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);

View file

@ -2,7 +2,6 @@
#include "MiscFunctions.hpp"
#include "../macros.hpp"
#include "SharedDefs.hpp"
#include "../helpers/TransferFunction.hpp"
#include "math/Math.hpp"
#include "../protocols/ColorManagement.hpp"
#include "../Compositor.hpp"
@ -23,20 +22,16 @@
#include "../protocols/core/DataDevice.hpp"
#include "../render/Renderer.hpp"
#include "../managers/EventManager.hpp"
#include "../managers/screenshare/ScreenshareManager.hpp"
#include "../managers/LayoutManager.hpp"
#include "../managers/animation/AnimationManager.hpp"
#include "../managers/animation/DesktopAnimationManager.hpp"
#include "../managers/input/InputManager.hpp"
#include "../hyprerror/HyprError.hpp"
#include "../layout/LayoutManager.hpp"
#include "../i18n/Engine.hpp"
#include "../helpers/cm/ColorManagement.hpp"
#include "sync/SyncTimeline.hpp"
#include "time/Time.hpp"
#include "../desktop/view/LayerSurface.hpp"
#include "../desktop/state/FocusState.hpp"
#include "../event/EventBus.hpp"
#include "Drm.hpp"
#include <aquamarine/output/Output.hpp>
#include "debug/log/Logger.hpp"
#include "debug/HyprNotificationOverlay.hpp"
@ -55,7 +50,7 @@ using namespace Hyprutils::OS;
using enum NContentType::eContentType;
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);
m_specialFade->setUpdateCallback([this](auto) { g_pHyprRenderer->damageMonitor(m_self.lock()); });
static auto PZOOMFACTOR = CConfigValue<Hyprlang::FLOAT>("cursor:zoom_factor");
@ -76,27 +71,23 @@ CMonitor::~CMonitor() {
}
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(); }};
m_zoomAnimProgress->setValueAndWarp(0.F);
m_zoomAnimFrameCounter = 0;
g_pEventLoopManager->doLater([] {
g_pConfigManager->ensurePersistentWorkspacesPresent();
g_pCompositor->ensureWorkspacesOnAssignedMonitors();
});
g_pEventLoopManager->doLater([] { g_pConfigManager->ensurePersistentWorkspacesPresent(); });
m_listeners.frame = m_output->events.frame.listen([this] {
if (m_frameScheduler)
m_frameScheduler->onFrame();
});
m_listeners.commit = m_output->events.commit.listen([this] {
m_events.commit.emit();
// FIXME: E->state->committed & WLR_OUTPUT_STATE_BUFFER
if (true && Screenshare::mgr())
Screenshare::mgr()->onOutputCommit(m_self.lock());
if (true) { // FIXME: E->state->committed & WLR_OUTPUT_STATE_BUFFER
PROTO::screencopy->onOutputCommit(m_self.lock());
PROTO::toplevelExport->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); });
@ -123,12 +114,10 @@ void CMonitor::onConnect(bool noRule) {
ts = nullptr;
}
if (!ts) {
timespec mono{};
clock_gettime(CLOCK_MONOTONIC, &mono);
PROTO::presentation->onPresented(m_self.lock(), mono, event.refresh, event.seq, event.flags & ~Aquamarine::IOutput::AQ_OUTPUT_PRESENT_HW_CLOCK);
} else
PROTO::presentation->onPresented(m_self.lock(), *ts, event.refresh, event.seq, event.flags);
if (!ts)
PROTO::presentation->onPresented(m_self.lock(), Time::steadyNow(), event.refresh, event.seq, event.flags);
else
PROTO::presentation->onPresented(m_self.lock(), Time::fromTimespec(event.when), event.refresh, event.seq, event.flags);
if (m_zoomAnimFrameCounter < 5) {
m_zoomAnimFrameCounter++;
@ -293,16 +282,10 @@ void CMonitor::onConnect(bool noRule) {
if (!valid(ws))
continue;
const auto CURRENTMON = ws->m_monitor.lock();
const bool ORPHANED = !CURRENTMON || std::ranges::none_of(g_pCompositor->m_monitors, [&](const auto& mon) { return mon == CURRENTMON; });
const bool RETURNING = ws->m_lastMonitor == m_name;
const bool RECOVERY = g_pCompositor->m_monitors.size() == 1 && ORPHANED; // temporarily recover orphaned workspaces
if (RETURNING || RECOVERY) {
if (ws->m_lastMonitor == m_name || g_pCompositor->m_monitors.size() == 1 /* avoid lost workspaces on recover */) {
g_pCompositor->moveWorkspaceToMonitor(ws, m_self.lock());
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());
g_pHyprRenderer->arrangeLayersForMonitor(m_id);
g_layoutManager->recalculateMonitor(m_self.lock());
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id);
// ensure VRR (will enable if necessary)
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{"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) {
Event::bus()->m_events.monitor.preRemoved.emit(m_self.lock());
EMIT_HOOK_EVENT("preMonitorRemoved", m_self.lock());
CScopeGuard x = {[this]() {
if (g_pCompositor->m_isShuttingDown)
return;
g_pEventManager->postEvent(SHyprIPCEvent{"monitorremoved", m_name});
g_pEventManager->postEvent(SHyprIPCEvent{"monitorremovedv2", std::format("{},{},{}", m_id, m_name, m_shortDescription)});
Event::bus()->m_events.monitor.removed.emit(m_self.lock());
EMIT_HOOK_EVENT("monitorRemoved", m_self.lock());
g_pCompositor->scheduleMonitorStateRecheck();
}};
@ -428,6 +411,7 @@ void CMonitor::onDisconnect(bool destroy) {
m_layerSurfaceLayers[i].clear();
}
std::erase_if(g_pCompositor->m_monitors, [&](PHLMONITOR& el) { return el.get() == this; });
Log::logger->log(Log::DEBUG, "Removed monitor {}!", m_name);
if (!BACKUPMON) {
@ -438,24 +422,19 @@ void CMonitor::onDisconnect(bool destroy) {
m_enabled = false;
m_renderingInitPassed = false;
std::vector<PHLWORKSPACE> wspToMove;
for (auto const& w : g_pCompositor->getWorkspaces()) {
if (w->m_monitor == m_self || !w->m_monitor)
wspToMove.emplace_back(w.lock());
}
// Preserve ownership across cascaded monitor disconnects.
// The first disconnected monitor "owns" where a workspace should return.
for (auto const& w : wspToMove) {
if (w && w->m_lastMonitor.empty())
w->m_lastMonitor = m_name;
}
if (BACKUPMON) {
// snap cursor
g_pCompositor->warpCursorTo(BACKUPMON->m_position + BACKUPMON->m_transformedSize / 2.F, true);
// move workspaces
std::vector<PHLWORKSPACE> wspToMove;
for (auto const& w : g_pCompositor->getWorkspaces()) {
if (w->m_monitor == m_self || !w->m_monitor)
wspToMove.emplace_back(w.lock());
}
for (auto const& w : wspToMove) {
w->m_lastMonitor = m_name;
g_pCompositor->moveWorkspaceToMonitor(w, BACKUPMON);
g_pDesktopAnimationManager->startAnimation(w, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true);
}
@ -484,7 +463,7 @@ void CMonitor::onDisconnect(bool destroy) {
PHLMONITOR pMonitorMostHz = nullptr;
for (auto const& m : g_pCompositor->m_monitors) {
if (m->m_refreshRate > mostHz && m != m_self) {
if (m->m_refreshRate > mostHz) {
pMonitorMostHz = m;
mostHz = m->m_refreshRate;
}
@ -492,118 +471,78 @@ void CMonitor::onDisconnect(bool destroy) {
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) {
const auto sdrEOTF = NTransferFunction::fromConfig();
switch (tf) {
case NTransferFunction::TF_DEFAULT:
case NTransferFunction::TF_GAMMA22:
case NTransferFunction::TF_FORCED_GAMMA22: return NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22;
case NTransferFunction::TF_SRGB: return NColorManagement::CM_TRANSFER_FUNCTION_SRGB;
case NTransferFunction::TF_AUTO: // use global setting
switch (sdrEOTF) {
case NTransferFunction::TF_AUTO: return NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22;
default: return chooseTF(sdrEOTF);
}
default: UNREACHABLE();
}
}
void CMonitor::applyCMType(NCMType::eCMType cmType, NTransferFunction::eTF cmSdrEotf) {
auto oldImageDescription = m_imageDescription;
const auto chosenSdrEotf = chooseTF(cmSdrEotf);
const auto masteringPrimaries = getMasteringPrimaries();
const NColorManagement::SImageDescription::SPCMasteringLuminances masteringLuminances = getMasteringLuminances();
const auto maxFALL = this->maxFALL();
const auto maxCLL = this->maxCLL();
void CMonitor::applyCMType(NCMType::eCMType cmType, int cmSdrEotf) {
auto oldImageDescription = m_imageDescription;
static auto PSDREOTF = CConfigValue<Hyprlang::INT>("render:cm_sdr_eotf");
auto chosenSdrEotf = cmSdrEotf == 0 ? (*PSDREOTF != 3 ? NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22 : NColorManagement::CM_TRANSFER_FUNCTION_SRGB) :
(cmSdrEotf == 1 ? NColorManagement::CM_TRANSFER_FUNCTION_SRGB : NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22);
switch (cmType) {
case NCMType::CM_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_SRGB: m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf}); break; // assumes SImageDescription defaults to sRGB
case NCMType::CM_WIDE:
m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf,
.primariesNameSet = true,
.primariesNamed = NColorManagement::CM_PRIMARIES_BT2020,
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020),
.masteringPrimaries = masteringPrimaries,
.masteringLuminances = masteringLuminances,
.maxCLL = maxCLL,
.maxFALL = maxFALL});
m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf,
.primariesNameSet = true,
.primariesNamed = NColorManagement::CM_PRIMARIES_BT2020,
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020)});
break;
case NCMType::CM_DCIP3:
m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf,
.primariesNameSet = true,
.primariesNamed = NColorManagement::CM_PRIMARIES_DCI_P3,
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_DCI_P3),
.masteringPrimaries = masteringPrimaries,
.masteringLuminances = masteringLuminances,
.maxCLL = maxCLL,
.maxFALL = maxFALL});
m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf,
.primariesNameSet = true,
.primariesNamed = NColorManagement::CM_PRIMARIES_DCI_P3,
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_DCI_P3)});
break;
case NCMType::CM_DP3:
m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf,
.primariesNameSet = true,
.primariesNamed = NColorManagement::CM_PRIMARIES_DISPLAY_P3,
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_DISPLAY_P3),
.masteringPrimaries = masteringPrimaries,
.masteringLuminances = masteringLuminances,
.maxCLL = maxCLL,
.maxFALL = maxFALL});
m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf,
.primariesNameSet = true,
.primariesNamed = NColorManagement::CM_PRIMARIES_DISPLAY_P3,
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_DISPLAY_P3)});
break;
case NCMType::CM_ADOBE:
m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf,
.primariesNameSet = true,
.primariesNamed = NColorManagement::CM_PRIMARIES_ADOBE_RGB,
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_ADOBE_RGB),
.masteringPrimaries = masteringPrimaries,
.masteringLuminances = masteringLuminances,
.maxCLL = maxCLL,
.maxFALL = maxFALL});
m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf,
.primariesNameSet = true,
.primariesNamed = NColorManagement::CM_PRIMARIES_ADOBE_RGB,
.primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_ADOBE_RGB)});
break;
case NCMType::CM_EDID:
m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf,
.primariesNameSet = false,
.primariesNamed = NColorManagement::CM_PRIMARIES_BT2020,
.primaries = masteringPrimaries,
.masteringPrimaries = masteringPrimaries,
.masteringLuminances = masteringLuminances,
.maxCLL = maxCLL,
.maxFALL = maxFALL});
m_imageDescription =
CImageDescription::from({.transferFunction = chosenSdrEotf,
.primariesNameSet = false,
.primariesNamed = NColorManagement::CM_PRIMARIES_BT2020,
.primaries = {
.red = {.x = m_output->parsedEDID.chromaticityCoords->red.x, .y = m_output->parsedEDID.chromaticityCoords->red.y},
.green = {.x = m_output->parsedEDID.chromaticityCoords->green.x, .y = m_output->parsedEDID.chromaticityCoords->green.y},
.blue = {.x = m_output->parsedEDID.chromaticityCoords->blue.x, .y = m_output->parsedEDID.chromaticityCoords->blue.y},
.white = {.x = m_output->parsedEDID.chromaticityCoords->white.x, .y = m_output->parsedEDID.chromaticityCoords->white.y},
}});
break;
case NCMType::CM_HDR: m_imageDescription = DEFAULT_HDR_IMAGE_DESCRIPTION; break;
case NCMType::CM_HDR_EDID:
m_imageDescription = CImageDescription::from(
{.transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ,
.primariesNameSet = false,
.primariesNamed = NColorManagement::CM_PRIMARIES_BT2020,
.primaries = m_output->parsedEDID.chromaticityCoords.has_value() ? masteringPrimaries : NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020),
.masteringPrimaries = masteringPrimaries,
.luminances = {.min = DEFAULT_HDR_IMAGE_DESCRIPTION->value().getTFMinLuminance(),
.max = DEFAULT_HDR_IMAGE_DESCRIPTION->value().getTFMaxLuminance(),
.reference = DEFAULT_HDR_IMAGE_DESCRIPTION->value().getTFRefLuminance()},
.masteringLuminances = masteringLuminances,
.maxCLL = maxCLL,
.maxFALL = maxFALL});
m_imageDescription =
CImageDescription::from({.transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ,
.primariesNameSet = false,
.primariesNamed = NColorManagement::CM_PRIMARIES_BT2020,
.primaries = m_output->parsedEDID.chromaticityCoords.has_value() ?
NColorManagement::SPCPRimaries{
.red = {.x = m_output->parsedEDID.chromaticityCoords->red.x, .y = m_output->parsedEDID.chromaticityCoords->red.y},
.green = {.x = m_output->parsedEDID.chromaticityCoords->green.x, .y = m_output->parsedEDID.chromaticityCoords->green.y},
.blue = {.x = m_output->parsedEDID.chromaticityCoords->blue.x, .y = m_output->parsedEDID.chromaticityCoords->blue.y},
.white = {.x = m_output->parsedEDID.chromaticityCoords->white.x, .y = m_output->parsedEDID.chromaticityCoords->white.y},
} :
NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020),
.luminances = {.min = m_output->parsedEDID.hdrMetadata->desiredContentMinLuminance,
.max = m_output->parsedEDID.hdrMetadata->desiredContentMaxLuminance,
.reference = m_output->parsedEDID.hdrMetadata->desiredMaxFrameAverageLuminance}});
break;
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({
.min = m_minLuminance >= 0 ? m_minLuminance : m_imageDescription->value().luminances.min, //
.max = m_maxLuminance >= 0 ? m_maxLuminance : m_imageDescription->value().luminances.max, //
.reference = m_imageDescription->value().luminances.reference //
.min = m_minLuminance >= 0 ? m_minLuminance : m_imageDescription->value().luminances.min, //
.max = m_maxLuminance >= 0 ? m_maxLuminance : m_imageDescription->value().luminances.max, //
.reference = m_maxAvgLuminance >= 0 ? m_maxAvgLuminance : m_imageDescription->value().luminances.reference //
});
if (oldImageDescription != m_imageDescription) {
@ -933,46 +872,29 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) {
m_supportsWideColor = 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;
switch (m_cmType) {
case NCMType::CM_AUTO: m_cmType = m_enabled10bit && supportsWideColor() ? NCMType::CM_WIDE : NCMType::CM_SRGB; break;
case NCMType::CM_EDID: m_cmType = m_output->parsedEDID.chromaticityCoords.has_value() ? NCMType::CM_EDID : NCMType::CM_SRGB; break;
case NCMType::CM_HDR:
case NCMType::CM_HDR_EDID: m_cmType = supportsHDR() ? m_cmType : NCMType::CM_SRGB; break;
default: break;
}
m_sdrEotf = RULE->sdrEotf;
m_sdrMinLuminance = RULE->sdrMinLuminance;
m_sdrMaxLuminance = RULE->sdrMaxLuminance;
m_minLuminance = RULE->minLuminance;
m_maxLuminance = RULE->maxLuminance;
m_maxAvgLuminance = RULE->maxAvgLuminance;
applyCMType(m_cmType, m_sdrEotf);
m_sdrSaturation = RULE->sdrSaturation;
m_sdrBrightness = RULE->sdrBrightness;
} else {
auto image = NColorManagement::SImageDescription::fromICC(RULE->iccFile);
if (!image) {
Log::logger->log(Log::ERR, "icc for {} ({}) failed: {}", m_name, RULE->iccFile, image.error());
g_pConfigManager->addParseError(std::format("failed to apply icc {} to {}: {}", RULE->iccFile, m_name, image.error()));
} else {
m_imageDescription = CImageDescription::from(*image);
if (!m_imageDescription) {
Log::logger->log(Log::ERR, "icc for {} ({}) failed 2: {}", m_name, RULE->iccFile, image.error());
g_pConfigManager->addParseError(std::format("failed to apply icc {} to {}: {}", RULE->iccFile, m_name, image.error()));
m_imageDescription = CImageDescription::from(SImageDescription{});
}
}
m_cmType = RULE->cmType;
switch (m_cmType) {
case NCMType::CM_AUTO: m_cmType = m_enabled10bit && supportsWideColor() ? NCMType::CM_WIDE : NCMType::CM_SRGB; break;
case NCMType::CM_EDID: m_cmType = m_output->parsedEDID.chromaticityCoords.has_value() ? NCMType::CM_EDID : NCMType::CM_SRGB; break;
case NCMType::CM_HDR:
case NCMType::CM_HDR_EDID: m_cmType = supportsHDR() ? m_cmType : NCMType::CM_SRGB; break;
default: break;
}
m_sdrEotf = RULE->sdrEotf;
m_sdrMinLuminance = RULE->sdrMinLuminance;
m_sdrMaxLuminance = RULE->sdrMaxLuminance;
m_minLuminance = RULE->minLuminance;
m_maxLuminance = RULE->maxLuminance;
m_maxAvgLuminance = RULE->maxAvgLuminance;
applyCMType(m_cmType, m_sdrEotf);
m_sdrSaturation = RULE->sdrSaturation;
m_sdrBrightness = RULE->sdrBrightness;
Vector2D logicalSize = m_pixelSize / m_scale;
if (!*PDISABLESCALECHECKS && (logicalSize.x != std::round(logicalSize.x) || logicalSize.y != std::round(logicalSize.y))) {
// invalid scale, will produce fractional pixels.
@ -1054,8 +976,6 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) {
m_damage.setSize(m_transformedSize);
updateVCGTRamps();
// Set scale for all surfaces on this monitor, needed for some clients
// but not on unsafe state to avoid crashes
if (!g_pCompositor->m_unsafeState) {
@ -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,
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();
@ -1106,10 +1026,8 @@ bool CMonitor::shouldSkipScheduleFrameOnMouseEvent() {
static auto PMINRR = CConfigValue<Hyprlang::INT>("cursor:min_refresh_rate");
// skip scheduling extra frames for fullsreen apps with vrr
const auto FS_WINDOW = getFullscreenWindow();
const bool shouldRenderCursor = g_pHyprRenderer->shouldRenderCursor();
const bool noBreak = FS_WINDOW && (*PNOBREAK == 1 || (*PNOBREAK == 2 && FS_WINDOW->getContentType() == CONTENT_TYPE_GAME));
const bool shouldSkip = (!shouldRenderCursor || noBreak) && m_output->state->state().adaptiveSync;
const bool shouldSkip = inFullscreenMode() && (*PNOBREAK == 1 || (*PNOBREAK == 2 && m_activeWorkspace->getFullscreenWindow()->getContentType() == CONTENT_TYPE_GAME)) &&
m_output->state->state().adaptiveSync;
// keep requested minimum refresh rate
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
g_pCompositor->moveWorkspaceToMonitor(PNEWWORKSPACE, m_self.lock());
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);
} else {
if (newDefaultWorkspaceName.empty())
@ -1362,7 +1280,7 @@ void CMonitor::changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal, bo
// move pinned windows
for (auto const& w : g_pCompositor->m_windows) {
if (w->m_workspace == POLDWORKSPACE && w->m_pinned)
w->layoutTarget()->assignToSpace(pWorkspace->m_space);
w->moveToWorkspace(pWorkspace);
}
if (!noFocus && !Desktop::focusState()->monitor()->m_activeSpecialWorkspace &&
@ -1382,17 +1300,17 @@ void CMonitor::changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal, bo
pWindow = pWorkspace->getFirstWindow();
}
Desktop::focusState()->fullWindowFocus(pWindow, Desktop::FOCUS_REASON_KEYBIND);
Desktop::focusState()->fullWindowFocus(pWindow);
}
if (!noMouseMove)
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{"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
@ -1439,23 +1357,17 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) {
g_pDesktopAnimationManager->startAnimation(m_activeSpecialWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_OUT, false);
g_pEventManager->postEvent(SHyprIPCEvent{"activespecial", "," + m_name});
g_pEventManager->postEvent(SHyprIPCEvent{"activespecialv2", ",," + m_name});
// Reset layer surface state when closing special workspace
for (auto const& ls : g_pCompositor->m_layers) {
if (ls->m_monitor == m_self)
ls->m_aboveFullscreen = false;
}
}
m_activeSpecialWorkspace.reset();
if (POLDSPECIAL)
POLDSPECIAL->m_events.activeChanged.emit();
g_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 (const auto PLAST = m_activeWorkspace->getLastFocusedWindow(); PLAST)
Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_KEYBIND);
Desktop::focusState()->fullWindowFocus(PLAST);
else
g_pInputManager->refocus();
}
@ -1480,17 +1392,10 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) {
const auto PMONITORWORKSPACEOWNER = pWorkspace->m_monitor.lock();
if (const auto PMWSOWNER = pWorkspace->m_monitor.lock(); PMWSOWNER && PMWSOWNER->m_activeSpecialWorkspace == pWorkspace) {
PMWSOWNER->m_activeSpecialWorkspace.reset();
g_layoutManager->recalculateMonitor(PMWSOWNER);
g_pHyprRenderer->damageMonitor(PMWSOWNER);
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PMWSOWNER->m_id);
g_pEventManager->postEvent(SHyprIPCEvent{"activespecial", "," + PMWSOWNER->m_name});
g_pEventManager->postEvent(SHyprIPCEvent{"activespecialv2", ",," + PMWSOWNER->m_name});
// Reset layer surfaces on the old monitor when special workspace is stolen
for (auto const& ls : g_pCompositor->m_layers) {
if (ls->m_monitor == PMWSOWNER)
ls->m_aboveFullscreen = false;
}
const auto PACTIVEWORKSPACE = PMWSOWNER->m_activeWorkspace;
g_pDesktopAnimationManager->setFullscreenFadeAnimation(PACTIVEWORKSPACE,
PACTIVEWORKSPACE && PACTIVEWORKSPACE->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN :
@ -1504,12 +1409,6 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) {
m_activeSpecialWorkspace = pWorkspace;
m_activeSpecialWorkspace->m_visible = true;
// Reset layer surface state when opening special workspace
for (auto const& ls : g_pCompositor->m_layers) {
if (ls->m_monitor == m_self)
ls->m_aboveFullscreen = false;
}
if (POLDSPECIAL)
POLDSPECIAL->m_events.activeChanged.emit();
@ -1540,16 +1439,17 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) {
} else
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 (const auto PLAST = pWorkspace->getLastFocusedWindow(); PLAST)
Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_KEYBIND);
Desktop::focusState()->fullWindowFocus(PLAST);
else
g_pInputManager->refocus();
}
@ -1579,20 +1479,10 @@ Vector2D CMonitor::middle() {
return m_position + m_size / 2.f;
}
const Mat3x3& CMonitor::getTransformMatrix() {
return m_projMatrix;
}
const Mat3x3& CMonitor::getScaleMatrix() {
return m_projOutputMatrix;
}
void CMonitor::updateMatrix() {
m_projMatrix = Mat3x3::identity();
if (m_transform != WL_OUTPUT_TRANSFORM_NORMAL)
m_projMatrix.translate(m_pixelSize / 2.0).transform(Math::wlTransformToHyprutils(m_transform)).translate(-m_transformedSize / 2.0);
m_projOutputMatrix = Mat3x3::outputProjection(m_pixelSize, HYPRUTILS_TRANSFORM_NORMAL);
}
WORKSPACEID CMonitor::activeWorkspaceID() {
@ -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()) {
reasons |= TC_CANDIDATE;
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()) {
reasons |= DS_BLOCK_MIRROR;
if (!full)
@ -1879,27 +1771,21 @@ uint16_t CMonitor::isDSBlocked(bool full) {
// we can't scanout shm buffers.
const auto params = PSURFACE->m_current.buffer->dmabuf();
if (!params.success || !PSURFACE->m_current.texture->isDMA() /* dmabuf */) {
if (!params.success || !PSURFACE->m_current.texture->m_eglImage /* dmabuf */) {
reasons |= DS_BLOCK_DMA;
if (!full)
return reasons;
}
const bool surfaceIsHDR = PSURFACE->m_colorManagement.valid() && PSURFACE->m_colorManagement->isHDR();
const bool surfaceIsScRGB = surfaceIsHDR && PSURFACE->m_colorManagement->isWindowsScRGB();
if (needsCM() && (*PNONSHADER != CM_NS_IGNORE || surfaceIsScRGB) && !canNoShaderCM() &&
((inHDR() && (*PPASS == 0 || !surfaceIsHDR || surfaceIsScRGB)) || (!inHDR() && (*PPASS != 1 || surfaceIsHDR))))
const bool surfaceIsHDR = PSURFACE->m_colorManagement.valid() && (PSURFACE->m_colorManagement->isHDR() || PSURFACE->m_colorManagement->isWindowsScRGB());
if (needsCM() && *PNONSHADER != CM_NS_IGNORE && !canNoShaderCM() && ((inHDR() && (*PPASS == 0 || !surfaceIsHDR)) || (!inHDR() && (*PPASS != 1 || surfaceIsHDR))))
reasons |= DS_BLOCK_CM;
return reasons;
}
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)
return false;
@ -1913,7 +1799,7 @@ bool CMonitor::attemptDirectScanout() {
auto PBUFFER = PSURFACE->m_current.buffer.m_buffer;
// #TODO this entire bit needs figuring out, vrr goes down the drain without it
if (PBUFFER == m_output->state->state().buffer && *PSAME) {
if (PBUFFER == m_output->state->state().buffer) {
PSURFACE->presentFeedback(Time::steadyNow(), m_self.lock());
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.
if (PSURFACE->m_fifo && !m_tearingState.activelyTearing && *PSAMEFIFO)
if (PSURFACE->m_fifo)
PSURFACE->m_stateQueue.unlockFirst(LOCK_REASON_FIFO);
return true;
@ -1963,18 +1849,7 @@ bool CMonitor::attemptDirectScanout() {
PSURFACE->presentFeedback(Time::steadyNow(), m_self.lock());
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
@ -2129,14 +2004,6 @@ int CMonitor::maxAvgLuminance(int defaultValue) {
(m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->desiredMaxFrameAverageLuminance : defaultValue);
}
float CMonitor::maxFALL() {
return m_maxAvgLuminance >= 0 ? m_maxAvgLuminance : (m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->desiredMaxFrameAverageLuminance : 0);
}
float CMonitor::maxCLL() {
return m_maxLuminance >= 0 ? m_maxLuminance : (m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->desiredContentMaxLuminance : 0);
}
bool CMonitor::wantsWideColor() {
return supportsWideColor() && (wantsHDR() || m_imageDescription->value().primariesNamed == CM_PRIMARIES_BT2020);
}
@ -2150,52 +2017,22 @@ bool CMonitor::inHDR() {
}
bool CMonitor::inFullscreenMode() {
// Check special workspace first since it renders on top of regular workspaces
if (m_activeSpecialWorkspace && m_activeSpecialWorkspace->m_hasFullscreenWindow && m_activeSpecialWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN)
return true;
return m_activeWorkspace && m_activeWorkspace->m_hasFullscreenWindow && m_activeWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN;
}
PHLWINDOW CMonitor::getFullscreenWindow() {
// Check special workspace first since it renders on top of regular workspaces
if (m_activeSpecialWorkspace && m_activeSpecialWorkspace->m_hasFullscreenWindow && m_activeSpecialWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN)
return m_activeSpecialWorkspace->getFullscreenWindow();
if (m_activeWorkspace && m_activeWorkspace->m_hasFullscreenWindow && m_activeWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN)
return m_activeWorkspace->getFullscreenWindow();
return nullptr;
}
std::optional<NColorManagement::PImageDescription> CMonitor::getFSImageDescription() {
if (!inFullscreenMode())
return {};
const auto FS_WINDOW = getFullscreenWindow();
const auto FS_WINDOW = m_activeWorkspace->getFullscreenWindow();
if (!FS_WINDOW)
return {};
return {}; // should be unreachable
const auto ROOT_SURF = FS_WINDOW->wlSurface()->resource();
const auto SURF = ROOT_SURF->findWithCM();
return SURF ? NColorManagement::CImageDescription::from(SURF->m_colorManagement->imageDescription()) : DEFAULT_IMAGE_DESCRIPTION;
}
NColorManagement::SPCPRimaries CMonitor::getMasteringPrimaries() {
return m_output->parsedEDID.chromaticityCoords.has_value() ?
NColorManagement::SPCPRimaries{
.red = {.x = m_output->parsedEDID.chromaticityCoords->red.x, .y = m_output->parsedEDID.chromaticityCoords->red.y},
.green = {.x = m_output->parsedEDID.chromaticityCoords->green.x, .y = m_output->parsedEDID.chromaticityCoords->green.y},
.blue = {.x = m_output->parsedEDID.chromaticityCoords->blue.x, .y = m_output->parsedEDID.chromaticityCoords->blue.y},
.white = {.x = m_output->parsedEDID.chromaticityCoords->white.x, .y = m_output->parsedEDID.chromaticityCoords->white.y},
} :
NColorManagement::SPCPRimaries{};
}
NColorManagement::SImageDescription::SPCMasteringLuminances CMonitor::getMasteringLuminances() {
return {
.min = m_minLuminance >= 0 ? m_minLuminance : (m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->desiredContentMinLuminance : 0),
.max = m_maxLuminance >= 0 ? m_maxLuminance : (m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->desiredContentMaxLuminance : 0),
};
}
bool CMonitor::needsCM() {
const auto SRC_DESC = getFSImageDescription();
return SRC_DESC.has_value() && SRC_DESC.value() != m_imageDescription;
@ -2216,91 +2053,24 @@ bool CMonitor::canNoShaderCM() {
const auto SRC_DESC_VALUE = SRC_DESC.value()->value();
if (m_imageDescription->value().icc.present)
return false;
if (SRC_DESC_VALUE.icc.fd >= 0 || m_imageDescription->value().icc.fd >= 0)
return false; // no ICC support
const auto sdrEOTF = NTransferFunction::fromConfig();
static auto PSDREOTF = CConfigValue<Hyprlang::INT>("render:cm_sdr_eotf");
// only primaries differ
return (
(SRC_DESC_VALUE.transferFunction == m_imageDescription->value().transferFunction ||
(sdrEOTF == NTransferFunction::TF_FORCED_GAMMA22 && SRC_DESC_VALUE.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_SRGB &&
m_imageDescription->value().transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22)) &&
SRC_DESC_VALUE.transferFunctionPower == m_imageDescription->value().transferFunctionPower &&
(!inHDR() || SRC_DESC_VALUE.luminances == m_imageDescription->value().luminances)
// not used by shaders atm
// && SRC_DESC_VALUE.masteringLuminances == m_imageDescription->value().masteringLuminances && SRC_DESC_VALUE.maxCLL == m_imageDescription->value().maxCLL && SRC_DESC_VALUE.maxFALL == m_imageDescription->value().maxFALL
);
return ((SRC_DESC_VALUE.transferFunction == m_imageDescription->value().transferFunction ||
(*PSDREOTF == 2 && SRC_DESC_VALUE.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_SRGB &&
m_imageDescription->value().transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22)) &&
SRC_DESC_VALUE.transferFunctionPower == m_imageDescription->value().transferFunctionPower &&
(!inHDR() || SRC_DESC_VALUE.luminances == m_imageDescription->value().luminances) &&
SRC_DESC_VALUE.masteringLuminances == m_imageDescription->value().masteringLuminances && SRC_DESC_VALUE.maxCLL == m_imageDescription->value().maxCLL &&
SRC_DESC_VALUE.maxFALL == m_imageDescription->value().maxFALL);
}
bool CMonitor::doesNoShaderCM() {
return m_noShaderCTM;
}
static std::vector<uint16_t> resampleInterleavedToKms(const SVCGTTable16& t, size_t gammaSize) {
std::vector<uint16_t> out;
out.resize(gammaSize * 3);
//
auto sample = [&](int c, float x) -> uint16_t {
const float maxX = t.entries - 1;
x = std::clamp(x, 0.F, maxX);
const size_t i0 = (size_t)std::floor(x);
const size_t i1 = std::min(i0 + 1, (size_t)t.entries - 1);
const float f = x - sc<float>(i0);
const float v0 = sc<float>(t.ch[c][i0]);
const float v1 = sc<float>(t.ch[c][i1]);
const float v = v0 + ((v1 - v0) * f);
int64_t vi = std::round(v);
vi = std::clamp(vi, sc<int64_t>(0), sc<int64_t>(65535));
return sc<uint16_t>(vi);
};
for (size_t i = 0; i < gammaSize; ++i) {
float x = sc<float>(i) * sc<float>(t.entries - 1) / sc<float>(gammaSize - 1);
const uint16_t r = sample(0, x);
const uint16_t g = sample(1, x);
const uint16_t b = sample(2, x);
out[i * 3 + 0] = r;
out[i * 3 + 1] = g;
out[i * 3 + 2] = b;
}
return out;
}
void CMonitor::updateVCGTRamps() {
auto gammaSize = m_output->getGammaSize();
if (gammaSize <= 10) {
Log::logger->log(Log::DEBUG, "CMonitor::updateVCGTRamps: skipping, no gamma ramp for output");
return;
}
if (!m_imageDescription->value().icc.vcgt) {
if (m_vcgtRampsSet)
m_output->state->setGammaLut({});
m_vcgtRampsSet = false;
return;
}
// build table
auto table = resampleInterleavedToKms(*m_imageDescription->value().icc.vcgt, gammaSize);
m_output->state->setGammaLut(table);
m_vcgtRampsSet = true;
}
bool CMonitor::gammaRampsInUse() {
return m_vcgtRampsSet;
}
CMonitorState::CMonitorState(CMonitor* owner) : m_owner(owner) {
;
}
@ -2328,7 +2098,7 @@ bool CMonitorState::commit() {
if (!updateSwapchain())
return false;
Event::bus()->m_events.monitor.preCommit.emit(m_owner->m_self.lock());
EMIT_HOOK_EVENT("preMonitorCommit", m_owner->m_self.lock());
ensureBufferPresent();

View file

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

View file

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

View file

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

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