mirror of
https://github.com/hyprwm/Hyprland
synced 2026-05-07 14:58:02 +02:00
Merge branch 'hyprwm:main' into main
This commit is contained in:
commit
92b1069b3e
577 changed files with 47912 additions and 30710 deletions
|
|
@ -9,7 +9,6 @@ WarningsAsErrors: >
|
|||
-bugprone-unused-local-non-trivial-variable,
|
||||
-bugprone-easily-swappable-parameters,
|
||||
-bugprone-forward-declararion-namespace,
|
||||
-bugprone-forward-declararion-namespace,
|
||||
-bugprone-macro-parentheses,
|
||||
-bugprone-narrowing-conversions,
|
||||
-bugprone-branch-clone,
|
||||
|
|
@ -113,7 +112,6 @@ Checks: >
|
|||
bugprone-*,
|
||||
-bugprone-easily-swappable-parameters,
|
||||
-bugprone-forward-declararion-namespace,
|
||||
-bugprone-forward-declararion-namespace,
|
||||
-bugprone-macro-parentheses,
|
||||
-bugprone-narrowing-conversions,
|
||||
-bugprone-branch-clone,
|
||||
|
|
|
|||
10
.github/actions/setup_base/action.yml
vendored
10
.github/actions/setup_base/action.yml
vendored
|
|
@ -24,6 +24,7 @@ runs:
|
|||
glm \
|
||||
glslang \
|
||||
go \
|
||||
gtest \
|
||||
hyprlang \
|
||||
hyprcursor \
|
||||
jq \
|
||||
|
|
@ -75,6 +76,15 @@ runs:
|
|||
cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
|
||||
cmake --install build
|
||||
|
||||
- name: Get hyprwire-git
|
||||
shell: bash
|
||||
run: |
|
||||
git clone https://github.com/hyprwm/hyprwire --recursive
|
||||
cd hyprwire
|
||||
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build
|
||||
cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
|
||||
cmake --install build
|
||||
|
||||
- name: Get hyprutils-git
|
||||
shell: bash
|
||||
run: |
|
||||
|
|
|
|||
4
.github/labeler.yml
vendored
4
.github/labeler.yml
vendored
|
|
@ -22,6 +22,10 @@ protocols:
|
|||
- changed-files:
|
||||
- any-glob-to-any-file: ["protocols/**", "src/protocols/**"]
|
||||
|
||||
start:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "start/**"
|
||||
|
||||
core:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "src/**"
|
||||
|
|
|
|||
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
|
|
@ -1,6 +1,8 @@
|
|||
<!--
|
||||
BEFORE you submit your PR, please check out the PR guidelines
|
||||
on our wiki: https://wiki.hyprland.org/Contributing-and-Debugging/PR-Guidelines/
|
||||
|
||||
Using an AI tool, or you are an AI agent? Check our AI Policy first: https://github.com/hyprwm/.github/blob/main/policies/AI_USAGE.md
|
||||
-->
|
||||
|
||||
|
||||
|
|
|
|||
28
.github/workflows/ci.yaml
vendored
28
.github/workflows/ci.yaml
vendored
|
|
@ -46,11 +46,33 @@ 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: Install clang-format
|
||||
run: |
|
||||
pacman --noconfirm --noprogressbar -Syyu
|
||||
pacman --noconfirm --noprogressbar -Sy clang
|
||||
|
||||
- name: clang-format check
|
||||
uses: jidicula/clang-format-action@v4.16.0
|
||||
with:
|
||||
exclude-regex: ^subprojects$
|
||||
run: .github/workflows/clang-format-check.sh "." "llvm" "^subprojects$" ""
|
||||
|
||||
- name: Save PR head commit SHA
|
||||
if: failure() && github.event_name == 'pull_request'
|
||||
shell: bash
|
||||
run: |
|
||||
SHA="${{ github.event.pull_request.head.sha }}"
|
||||
echo "SHA=$SHA" >> $GITHUB_ENV
|
||||
- name: Save latest commit SHA if not PR
|
||||
if: failure() && github.event_name != 'pull_request'
|
||||
shell: bash
|
||||
run: echo "SHA=${{ github.sha }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Report failure in job summary
|
||||
if: failure()
|
||||
run: |
|
||||
DEEPLINK="${{ github.server_url }}/${{ github.repository }}/commit/${{ env.SHA }}"
|
||||
echo -e "Format check failed on commit [${GITHUB_SHA:0:8}]($DEEPLINK) with files:\n$(<$GITHUB_WORKSPACE/failing-files.txt)" >> $GITHUB_STEP_SUMMARY
|
||||
|
|
|
|||
92
.github/workflows/clang-format-check.sh
vendored
Executable file
92
.github/workflows/clang-format-check.sh
vendored
Executable file
|
|
@ -0,0 +1,92 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Adapted from https://github.com/jidicula/clang-format-action
|
||||
|
||||
###############################################################################
|
||||
# check.sh #
|
||||
###############################################################################
|
||||
# USAGE: ./entrypoint.sh [<path>] [<fallback style>]
|
||||
#
|
||||
# Checks all C/C++/Protobuf/CUDA files (.h, .H, .hpp, .hh, .h++, .hxx and .c,
|
||||
# .C, .cpp, .cc, .c++, .cxx, .proto, .cu) in the provided GitHub repository path
|
||||
# (arg1) for conforming to clang-format. If no path is provided or provided path
|
||||
# is not a directory, all C/C++/Protobuf/CUDA files are checked. If any files
|
||||
# are incorrectly formatted, the script lists them and exits with 1.
|
||||
#
|
||||
# Define your own formatting rules in a .clang-format file at your repository
|
||||
# root. Otherwise, the provided style guide (arg2) is used as a fallback.
|
||||
|
||||
# format_diff function
|
||||
# Accepts a filepath argument. The filepath passed to this function must point
|
||||
# to a C/C++/Protobuf/CUDA file.
|
||||
format_diff() {
|
||||
local filepath="$1"
|
||||
|
||||
# Invoke clang-format with dry run and formatting error output
|
||||
local_format="$(clang-format \
|
||||
--dry-run \
|
||||
--Werror \
|
||||
--style=file \
|
||||
--fallback-style="$FALLBACK_STYLE" \
|
||||
"${filepath}")"
|
||||
|
||||
local format_status="$?"
|
||||
if [[ ${format_status} -ne 0 ]]; then
|
||||
# Append Markdown-bulleted monospaced filepath of failing file to
|
||||
# summary file.
|
||||
echo "* \`$filepath\`" >>failing-files.txt
|
||||
|
||||
echo "Failed on file: $filepath" >&2
|
||||
echo "$local_format" >&2
|
||||
exit_code=1 # flip the global exit code
|
||||
return "${format_status}"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
CHECK_PATH="$1"
|
||||
FALLBACK_STYLE="$2"
|
||||
EXCLUDE_REGEX="$3"
|
||||
INCLUDE_REGEX="$4"
|
||||
|
||||
# Set the regex to an empty string regex if nothing was provided
|
||||
if [[ -z $EXCLUDE_REGEX ]]; then
|
||||
EXCLUDE_REGEX="^$"
|
||||
fi
|
||||
|
||||
# Set the filetype regex if nothing was provided.
|
||||
# Find all C/C++/Protobuf/CUDA files:
|
||||
# h, H, hpp, hh, h++, hxx
|
||||
# c, C, cpp, cc, c++, cxx
|
||||
# ino, pde
|
||||
# proto
|
||||
# cu
|
||||
if [[ -z $INCLUDE_REGEX ]]; then
|
||||
INCLUDE_REGEX='^.*\.((((c|C)(c|pp|xx|\+\+)?$)|((h|H)h?(pp|xx|\+\+)?$))|(ino|pde|proto|cu))$'
|
||||
fi
|
||||
|
||||
cd "$GITHUB_WORKSPACE" || exit 2
|
||||
|
||||
if [[ ! -d $CHECK_PATH ]]; then
|
||||
echo "Not a directory in the workspace, fallback to all files." >&2
|
||||
CHECK_PATH="."
|
||||
fi
|
||||
|
||||
# initialize exit code
|
||||
exit_code=0
|
||||
|
||||
# All files improperly formatted will be printed to the output.
|
||||
src_files=$(find "$CHECK_PATH" -name .git -prune -o -regextype posix-egrep -regex "$INCLUDE_REGEX" -print)
|
||||
|
||||
# check formatting in each source file
|
||||
IFS=$'\n' # Loop below should separate on new lines, not spaces.
|
||||
for file in $src_files; do
|
||||
# Only check formatting if the path doesn't match the regex
|
||||
if ! [[ ${file} =~ $EXCLUDE_REGEX ]]; then
|
||||
format_diff "${file}"
|
||||
fi
|
||||
done
|
||||
|
||||
# global exit code is flipped to nonzero if any invocation of `format_diff` has
|
||||
# a formatting difference.
|
||||
exit "$exit_code"
|
||||
28
.github/workflows/clang-format.yml
vendored
28
.github/workflows/clang-format.yml
vendored
|
|
@ -1,28 +0,0 @@
|
|||
name: clang-format
|
||||
on: pull_request_target
|
||||
jobs:
|
||||
clang-format:
|
||||
permissions: write-all
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork
|
||||
name: "Code Style"
|
||||
runs-on: ubuntu-latest
|
||||
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: Create comment
|
||||
if: ${{ failure() && github.event_name == 'pull_request' }}
|
||||
run: |
|
||||
echo 'Please fix the formatting issues by running [`clang-format`](https://wiki.hyprland.org/Contributing-and-Debugging/PR-Guidelines/#code-style).' > clang-format.patch
|
||||
|
||||
- name: Post comment
|
||||
if: ${{ failure() && github.event_name == 'pull_request' }}
|
||||
uses: mshick/add-pr-comment@v2
|
||||
with:
|
||||
message-path: |
|
||||
clang-format.patch
|
||||
45
.github/workflows/new-pr-comment.yml
vendored
Normal file
45
.github/workflows/new-pr-comment.yml
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
name: "New MR welcome comment"
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
|
||||
jobs:
|
||||
comment:
|
||||
if: >
|
||||
github.event.pull_request.user.login != 'vaxerski' &&
|
||||
github.event.pull_request.user.login != 'fufexan' &&
|
||||
github.event.pull_request.user.login != 'gulafaran' &&
|
||||
github.event.pull_request.user.login != 'ujint34' &&
|
||||
github.event.pull_request.user.login != 'paideiadilemma' &&
|
||||
github.event.pull_request.user.login != 'notashelf'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
env:
|
||||
PR_COMMENT: |
|
||||
Hello and thank you for making a PR to Hyprland!
|
||||
|
||||
Please check the [PR Guidelines](https://wiki.hypr.land/Contributing-and-Debugging/PR-Guidelines/) and make sure your PR follows them.
|
||||
It will make the entire review process faster. :)
|
||||
|
||||
If your code can be tested, please always add tests. See more [here](https://wiki.hypr.land/Contributing-and-Debugging/Tests/).
|
||||
|
||||
_beep boop, I'm just a bot. A real human will review your PR soon._
|
||||
|
||||
steps:
|
||||
- name: Add comment to PR
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
body: process.env.PR_COMMENT,
|
||||
});
|
||||
7
.github/workflows/nix-ci.yml
vendored
7
.github/workflows/nix-ci.yml
vendored
|
|
@ -10,21 +10,22 @@ jobs:
|
|||
|
||||
hyprland:
|
||||
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork)
|
||||
uses: ./.github/workflows/nix.yml
|
||||
uses: hyprwm/actions/.github/workflows/nix.yml@main
|
||||
secrets: inherit
|
||||
with:
|
||||
checkout: false
|
||||
command: nix build 'github:${{ github.repository }}?ref=${{ github.ref }}' -L --extra-substituters "https://hyprland.cachix.org"
|
||||
|
||||
xdph:
|
||||
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork)
|
||||
needs: hyprland
|
||||
uses: ./.github/workflows/nix.yml
|
||||
uses: hyprwm/actions/.github/workflows/nix.yml@main
|
||||
secrets: inherit
|
||||
with:
|
||||
checkout: false
|
||||
command: nix build 'github:${{ github.repository }}?ref=${{ github.ref }}#xdg-desktop-portal-hyprland' -L --extra-substituters "https://hyprland.cachix.org"
|
||||
|
||||
test:
|
||||
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork)
|
||||
needs: hyprland
|
||||
uses: ./.github/workflows/nix-test.yml
|
||||
secrets: inherit
|
||||
|
|
|
|||
23
.github/workflows/nix-test.yml
vendored
23
.github/workflows/nix-test.yml
vendored
|
|
@ -10,28 +10,9 @@ jobs:
|
|||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Nix
|
||||
uses: nixbuild/nix-quick-install-action@v31
|
||||
- uses: hyprwm/actions/nix-setup@main
|
||||
with:
|
||||
nix_conf: |
|
||||
keep-env-derivations = true
|
||||
keep-outputs = true
|
||||
|
||||
- name: Restore and save Nix store
|
||||
uses: nix-community/cache-nix-action@v6
|
||||
with:
|
||||
# restore and save a cache using this key (per job)
|
||||
primary-key: nix-${{ runner.os }}-${{ github.job }}
|
||||
# if there's no cache hit, restore a cache by this prefix
|
||||
restore-prefixes-first-match: nix-${{ runner.os }}
|
||||
# collect garbage until the Nix store size (in bytes) is at most this number
|
||||
# before trying to save a new cache
|
||||
gc-max-store-size-linux: 5G
|
||||
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: hyprland
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
|
||||
- name: Run test VM
|
||||
run: nix build 'github:${{ github.repository }}?ref=${{ github.ref }}#checks.x86_64-linux.tests' -L --extra-substituters "https://hyprland.cachix.org"
|
||||
|
|
|
|||
18
.github/workflows/nix-update-inputs.yml
vendored
18
.github/workflows/nix-update-inputs.yml
vendored
|
|
@ -17,23 +17,7 @@ jobs:
|
|||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
|
||||
- name: Install Nix
|
||||
uses: nixbuild/nix-quick-install-action@v31
|
||||
with:
|
||||
nix_conf: |
|
||||
keep-env-derivations = true
|
||||
keep-outputs = true
|
||||
|
||||
- name: Restore and save Nix store
|
||||
uses: nix-community/cache-nix-action@v6
|
||||
with:
|
||||
# restore and save a cache using this key (per job)
|
||||
primary-key: nix-${{ runner.os }}-${{ github.job }}
|
||||
# if there's no cache hit, restore a cache by this prefix
|
||||
restore-prefixes-first-match: nix-${{ runner.os }}
|
||||
# collect garbage until the Nix store size (in bytes) is at most this number
|
||||
# before trying to save a new cache
|
||||
gc-max-store-size-linux: 5G
|
||||
- uses: hyprwm/actions/nix-setup@main
|
||||
|
||||
- name: Update inputs
|
||||
run: nix/update-inputs.sh
|
||||
|
|
|
|||
41
.github/workflows/nix.yml
vendored
41
.github/workflows/nix.yml
vendored
|
|
@ -1,41 +0,0 @@
|
|||
name: Build
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
command:
|
||||
required: true
|
||||
type: string
|
||||
description: Command to run
|
||||
secrets:
|
||||
CACHIX_AUTH_TOKEN:
|
||||
required: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Nix
|
||||
uses: nixbuild/nix-quick-install-action@v31
|
||||
with:
|
||||
nix_conf: |
|
||||
keep-env-derivations = true
|
||||
keep-outputs = true
|
||||
|
||||
- name: Restore and save Nix store
|
||||
uses: nix-community/cache-nix-action@v6
|
||||
with:
|
||||
# restore and save a cache using this key (per job)
|
||||
primary-key: nix-${{ runner.os }}-${{ github.job }}
|
||||
# if there's no cache hit, restore a cache by this prefix
|
||||
restore-prefixes-first-match: nix-${{ runner.os }}
|
||||
# collect garbage until the Nix store size (in bytes) is at most this number
|
||||
# before trying to save a new cache
|
||||
gc-max-store-size-linux: 5G
|
||||
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: hyprland
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
|
||||
- run: ${{ inputs.command }}
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
|
|
@ -32,16 +32,23 @@ src/render/shaders/*.inc
|
|||
src/render/shaders/Shaders.hpp
|
||||
|
||||
hyprctl/hyprctl
|
||||
hyprctl/hw-protocols/*.c*
|
||||
hyprctl/hw-protocols/*.h*
|
||||
|
||||
gmon.out
|
||||
*.out
|
||||
*.tar.gz
|
||||
perf.data
|
||||
flame.svg
|
||||
|
||||
PKGBUILD
|
||||
|
||||
src/version.h
|
||||
hyprpm/Makefile
|
||||
hyprctl/Makefile
|
||||
example/hyprland.desktop
|
||||
|
||||
**/.#*.*
|
||||
**/#*.*#
|
||||
|
||||
debug-tools/flamegraph
|
||||
213
CMakeLists.txt
213
CMakeLists.txt
|
|
@ -17,7 +17,6 @@ set(HYPRLAND_VERSION ${VER})
|
|||
set(PREFIX ${CMAKE_INSTALL_PREFIX})
|
||||
set(INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
set(BINDIR ${CMAKE_INSTALL_BINDIR})
|
||||
configure_file(hyprland.pc.in hyprland.pc @ONLY)
|
||||
|
||||
set(CMAKE_MESSAGE_LOG_LEVEL "STATUS")
|
||||
|
||||
|
|
@ -25,7 +24,14 @@ message(STATUS "Gathering git info")
|
|||
|
||||
# Make shader files includable
|
||||
execute_process(COMMAND ./scripts/generateShaderIncludes.sh
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
RESULT_VARIABLE HYPR_SHADER_GEN_RESULT)
|
||||
if(NOT HYPR_SHADER_GEN_RESULT EQUAL 0)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"Failed to generate shader includes (scripts/generateShaderIncludes.sh), exit code: ${HYPR_SHADER_GEN_RESULT}"
|
||||
)
|
||||
endif()
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
|
||||
|
|
@ -80,9 +86,11 @@ message(
|
|||
if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
|
||||
message(STATUS "Configuring Hyprland in Debug with CMake")
|
||||
add_compile_definitions(HYPRLAND_DEBUG)
|
||||
set(BUILD_TESTING ON)
|
||||
else()
|
||||
add_compile_options(-O3)
|
||||
message(STATUS "Configuring Hyprland in Release with CMake")
|
||||
set(BUILD_TESTING OFF)
|
||||
endif()
|
||||
|
||||
add_compile_definitions(HYPRLAND_VERSION="${HYPRLAND_VERSION}")
|
||||
|
|
@ -102,6 +110,7 @@ add_compile_options(
|
|||
-Wno-narrowing
|
||||
-Wno-pointer-arith
|
||||
-Wno-clobbered
|
||||
-frtti
|
||||
-fmacro-prefix-map=${CMAKE_SOURCE_DIR}/=)
|
||||
|
||||
# disable lto as it may break plugins
|
||||
|
|
@ -116,12 +125,19 @@ find_package(Threads REQUIRED)
|
|||
|
||||
set(GLES_VERSION "GLES3")
|
||||
find_package(OpenGL REQUIRED COMPONENTS ${GLES_VERSION})
|
||||
find_package(glslang CONFIG REQUIRED)
|
||||
|
||||
pkg_check_modules(aquamarine_dep REQUIRED IMPORTED_TARGET aquamarine>=0.9.3)
|
||||
pkg_check_modules(hyprlang_dep REQUIRED IMPORTED_TARGET hyprlang>=0.3.2)
|
||||
pkg_check_modules(hyprcursor_dep REQUIRED IMPORTED_TARGET hyprcursor>=0.1.7)
|
||||
pkg_check_modules(hyprutils_dep REQUIRED IMPORTED_TARGET hyprutils>=0.10.2)
|
||||
pkg_check_modules(hyprgraphics_dep REQUIRED IMPORTED_TARGET hyprgraphics>=0.1.6)
|
||||
set(AQUAMARINE_MINIMUM_VERSION 0.9.3)
|
||||
set(HYPRLANG_MINIMUM_VERSION 0.6.7)
|
||||
set(HYPRCURSOR_MINIMUM_VERSION 0.1.7)
|
||||
set(HYPRUTILS_MINIMUM_VERSION 0.11.1)
|
||||
set(HYPRGRAPHICS_MINIMUM_VERSION 0.5.1)
|
||||
|
||||
pkg_check_modules(aquamarine_dep REQUIRED IMPORTED_TARGET aquamarine>=${AQUAMARINE_MINIMUM_VERSION})
|
||||
pkg_check_modules(hyprlang_dep REQUIRED IMPORTED_TARGET hyprlang>=${HYPRLANG_MINIMUM_VERSION})
|
||||
pkg_check_modules(hyprcursor_dep REQUIRED IMPORTED_TARGET hyprcursor>=${HYPRCURSOR_MINIMUM_VERSION})
|
||||
pkg_check_modules(hyprutils_dep REQUIRED IMPORTED_TARGET hyprutils>=${HYPRUTILS_MINIMUM_VERSION})
|
||||
pkg_check_modules(hyprgraphics_dep REQUIRED IMPORTED_TARGET hyprgraphics>=${HYPRGRAPHICS_MINIMUM_VERSION})
|
||||
|
||||
string(REPLACE "." ";" AQ_VERSION_LIST ${aquamarine_dep_VERSION})
|
||||
list(GET AQ_VERSION_LIST 0 AQ_VERSION_MAJOR)
|
||||
|
|
@ -228,29 +244,37 @@ 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_PROTOCOLS_MINIMUM_VERSION 1.47)
|
||||
set(LIBINPUT_MINIMUM_VERSION 1.28)
|
||||
|
||||
pkg_check_modules(
|
||||
deps
|
||||
REQUIRED
|
||||
IMPORTED_TARGET
|
||||
xkbcommon>=1.11.0
|
||||
IMPORTED_TARGET GLOBAL
|
||||
xkbcommon>=${XKBCOMMON_MINIMUM_VERSION}
|
||||
uuid
|
||||
wayland-server>=1.22.90
|
||||
wayland-protocols>=1.45
|
||||
wayland-server>=${WAYLAND_SERVER_MINIMUM_VERSION}
|
||||
wayland-protocols>=${WAYLAND_SERVER_PROTOCOLS_MINIMUM_VERSION}
|
||||
cairo
|
||||
pango
|
||||
pangocairo
|
||||
pixman-1
|
||||
xcursor
|
||||
libdrm
|
||||
libinput>=1.28
|
||||
libinput>=${LIBINPUT_MINIMUM_VERSION}
|
||||
gbm
|
||||
gio-2.0
|
||||
re2
|
||||
muparser)
|
||||
muparser
|
||||
lcms2)
|
||||
|
||||
find_package(hyprwayland-scanner 0.3.10 REQUIRED)
|
||||
|
||||
file(GLOB_RECURSE SRCFILES "src/*.cpp")
|
||||
get_filename_component(FULL_MAIN_PATH ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp ABSOLUTE)
|
||||
list(REMOVE_ITEM SRCFILES "${FULL_MAIN_PATH}")
|
||||
|
||||
set(TRACY_CPP_FILES "")
|
||||
if(USE_TRACY)
|
||||
|
|
@ -258,7 +282,12 @@ if(USE_TRACY)
|
|||
message(STATUS "Tracy enabled, TRACY_CPP_FILES: " ${TRACY_CPP_FILES})
|
||||
endif()
|
||||
|
||||
add_executable(Hyprland ${SRCFILES} ${TRACY_CPP_FILES})
|
||||
add_library(hyprland_lib STATIC ${SRCFILES})
|
||||
add_executable(Hyprland src/main.cpp ${TRACY_CPP_FILES})
|
||||
target_link_libraries(Hyprland hyprland_lib)
|
||||
|
||||
target_include_directories(hyprland_lib PUBLIC ${deps_INCLUDE_DIRS})
|
||||
target_include_directories(Hyprland PUBLIC ${deps_INCLUDE_DIRS})
|
||||
|
||||
set(USE_GPROF OFF)
|
||||
|
||||
|
|
@ -268,26 +297,11 @@ if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
|
|||
if(WITH_ASAN)
|
||||
message(STATUS "Enabling ASan")
|
||||
|
||||
target_link_libraries(Hyprland asan)
|
||||
target_compile_options(Hyprland PUBLIC -fsanitize=address)
|
||||
target_link_libraries(hyprland_lib PUBLIC asan)
|
||||
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 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_compile_options(-fno-pie -fno-builtin -fno-omit-frame-pointer)
|
||||
add_link_options(-no-pie -fno-builtin)
|
||||
if(USE_GPROF)
|
||||
add_compile_options(-pg)
|
||||
|
|
@ -295,6 +309,27 @@ 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()
|
||||
|
||||
check_include_file("execinfo.h" EXECINFOH)
|
||||
if(EXECINFOH)
|
||||
message(STATUS "Configuration supports execinfo")
|
||||
|
|
@ -304,19 +339,19 @@ endif()
|
|||
include(CheckLibraryExists)
|
||||
check_library_exists(execinfo backtrace "" HAVE_LIBEXECINFO)
|
||||
if(HAVE_LIBEXECINFO)
|
||||
target_link_libraries(Hyprland execinfo)
|
||||
target_link_libraries(hyprland_lib PUBLIC execinfo)
|
||||
endif()
|
||||
|
||||
check_include_file("sys/timerfd.h" HAS_TIMERFD)
|
||||
pkg_check_modules(epoll IMPORTED_TARGET epoll-shim)
|
||||
if(NOT HAS_TIMERFD AND epoll_FOUND)
|
||||
target_link_libraries(Hyprland PkgConfig::epoll)
|
||||
target_link_libraries(hyprland_lib PUBLIC PkgConfig::epoll)
|
||||
endif()
|
||||
|
||||
check_include_file("sys/inotify.h" HAS_INOTIFY)
|
||||
pkg_check_modules(inotify IMPORTED_TARGET libinotify)
|
||||
if(NOT HAS_INOTIFY AND inotify_FOUND)
|
||||
target_link_libraries(Hyprland PkgConfig::inotify)
|
||||
target_link_libraries(hyprland_lib PUBLIC PkgConfig::inotify)
|
||||
endif()
|
||||
|
||||
if(NO_XWAYLAND)
|
||||
|
|
@ -324,10 +359,7 @@ if(NO_XWAYLAND)
|
|||
add_compile_definitions(NO_XWAYLAND)
|
||||
else()
|
||||
message(STATUS "XWAYLAND Enabled (NO_XWAYLAND not defined) checking deps...")
|
||||
pkg_check_modules(
|
||||
xdeps
|
||||
REQUIRED
|
||||
IMPORTED_TARGET
|
||||
set(XWAYLAND_DEPENDENCIES
|
||||
xcb
|
||||
xcb-render
|
||||
xcb-xfixes
|
||||
|
|
@ -335,9 +367,21 @@ else()
|
|||
xcb-composite
|
||||
xcb-res
|
||||
xcb-errors)
|
||||
target_link_libraries(Hyprland PkgConfig::xdeps)
|
||||
|
||||
pkg_check_modules(
|
||||
xdeps
|
||||
REQUIRED
|
||||
IMPORTED_TARGET
|
||||
${XWAYLAND_DEPENDENCIES})
|
||||
|
||||
string(JOIN ", " PKGCONFIG_XWAYLAND_DEPENDENCIES ${XWAYLAND_DEPENDENCIES})
|
||||
string(PREPEND PKGCONFIG_XWAYLAND_DEPENDENCIES ", ")
|
||||
|
||||
target_link_libraries(hyprland_lib PUBLIC PkgConfig::xdeps)
|
||||
endif()
|
||||
|
||||
configure_file(hyprland.pc.in hyprland.pc @ONLY)
|
||||
|
||||
if(NO_SYSTEMD)
|
||||
message(STATUS "SYSTEMD support is disabled...")
|
||||
else()
|
||||
|
|
@ -362,31 +406,38 @@ if(CMAKE_DISABLE_PRECOMPILE_HEADERS)
|
|||
message(STATUS "Not using precompiled headers")
|
||||
else()
|
||||
message(STATUS "Setting precompiled headers")
|
||||
target_precompile_headers(Hyprland PRIVATE
|
||||
target_precompile_headers(hyprland_lib PRIVATE
|
||||
$<$<COMPILE_LANGUAGE:CXX>:src/pch/pch.hpp>)
|
||||
endif()
|
||||
|
||||
message(STATUS "Setting link libraries")
|
||||
|
||||
target_link_libraries(
|
||||
Hyprland
|
||||
${LIBRT}
|
||||
hyprland_lib
|
||||
PUBLIC
|
||||
PkgConfig::aquamarine_dep
|
||||
PkgConfig::hyprlang_dep
|
||||
PkgConfig::hyprutils_dep
|
||||
PkgConfig::hyprcursor_dep
|
||||
PkgConfig::hyprgraphics_dep
|
||||
PkgConfig::deps)
|
||||
PkgConfig::deps
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
Hyprland
|
||||
${LIBRT}
|
||||
hyprland_lib)
|
||||
if(udis_dep_FOUND)
|
||||
target_link_libraries(Hyprland PkgConfig::udis_dep)
|
||||
target_link_libraries(hyprland_lib PUBLIC PkgConfig::udis_dep)
|
||||
elseif(NOT("${udis_nopc}" MATCHES "udis_nopc-NOTFOUND"))
|
||||
target_link_libraries(Hyprland ${udis_nopc})
|
||||
target_link_libraries(hyprland_lib PUBLIC ${udis_nopc})
|
||||
else()
|
||||
target_link_libraries(Hyprland libudis86)
|
||||
target_link_libraries(hyprland_lib PUBLIC libudis86)
|
||||
endif()
|
||||
|
||||
# used by `make installheaders`, to ensure the headers are generated
|
||||
add_custom_target(generate-protocol-headers)
|
||||
set(PROTOCOL_SOURCES "")
|
||||
|
||||
function(protocolnew protoPath protoName external)
|
||||
if(external)
|
||||
|
|
@ -400,10 +451,15 @@ function(protocolnew protoPath protoName external)
|
|||
COMMAND hyprwayland-scanner ${path}/${protoName}.xml
|
||||
${CMAKE_SOURCE_DIR}/protocols/
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
|
||||
target_sources(Hyprland PRIVATE protocols/${protoName}.cpp
|
||||
target_sources(hyprland_lib PRIVATE protocols/${protoName}.cpp
|
||||
protocols/${protoName}.hpp)
|
||||
target_sources(generate-protocol-headers
|
||||
PRIVATE ${CMAKE_SOURCE_DIR}/protocols/${protoName}.hpp)
|
||||
|
||||
list(APPEND PROTOCOL_SOURCES "${CMAKE_SOURCE_DIR}/protocols/${protoName}.cpp")
|
||||
set(PROTOCOL_SOURCES "${PROTOCOL_SOURCES}" PARENT_SCOPE)
|
||||
list(APPEND PROTOCOL_SOURCES "${CMAKE_SOURCE_DIR}/protocols/${protoName}.hpp")
|
||||
set(PROTOCOL_SOURCES "${PROTOCOL_SOURCES}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
function(protocolWayland)
|
||||
add_custom_command(
|
||||
|
|
@ -413,12 +469,21 @@ function(protocolWayland)
|
|||
hyprwayland-scanner --wayland-enums
|
||||
${WAYLAND_SCANNER_PKGDATA_DIR}/wayland.xml ${CMAKE_SOURCE_DIR}/protocols/
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
|
||||
target_sources(Hyprland PRIVATE protocols/wayland.cpp protocols/wayland.hpp)
|
||||
target_sources(hyprland_lib PRIVATE protocols/wayland.cpp protocols/wayland.hpp)
|
||||
target_sources(generate-protocol-headers
|
||||
PRIVATE ${CMAKE_SOURCE_DIR}/protocols/wayland.hpp)
|
||||
|
||||
list(APPEND PROTOCOL_SOURCES "${CMAKE_SOURCE_DIR}/protocols/wayland.hpp")
|
||||
set(PROTOCOL_SOURCES "${PROTOCOL_SOURCES}" PARENT_SCOPE)
|
||||
list(APPEND PROTOCOL_SOURCES "${CMAKE_SOURCE_DIR}/protocols/wayland.cpp")
|
||||
set(PROTOCOL_SOURCES "${PROTOCOL_SOURCES}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
target_link_libraries(Hyprland OpenGL::EGL OpenGL::GL Threads::Threads)
|
||||
if(TARGET OpenGL::GL)
|
||||
target_link_libraries(hyprland_lib PUBLIC OpenGL::EGL OpenGL::GL glslang::glslang glslang::glslang-default-resource-limits glslang::SPIRV Threads::Threads)
|
||||
else()
|
||||
target_link_libraries(hyprland_lib PUBLIC OpenGL::EGL OpenGL::GLES3 glslang::glslang glslang::glslang-default-resource-limits glslang::SPIRV Threads::Threads)
|
||||
endif()
|
||||
|
||||
pkg_check_modules(hyprland_protocols_dep hyprland-protocols>=0.6.4)
|
||||
if(hyprland_protocols_dep_FOUND)
|
||||
|
|
@ -446,8 +511,6 @@ protocolnew("protocols" "kde-server-decoration" true)
|
|||
protocolnew("protocols" "wlr-data-control-unstable-v1" true)
|
||||
protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-focus-grab-v1" true)
|
||||
protocolnew("protocols" "wlr-layer-shell-unstable-v1" true)
|
||||
protocolnew("protocols" "xx-color-management-v4" true)
|
||||
protocolnew("protocols" "frog-color-management-v1" true)
|
||||
protocolnew("protocols" "wayland-drm" true)
|
||||
protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-ctm-control-v1" true)
|
||||
protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-surface-v1" true)
|
||||
|
|
@ -471,6 +534,7 @@ protocolnew("unstable/keyboard-shortcuts-inhibit"
|
|||
protocolnew("unstable/text-input" "text-input-unstable-v3" false)
|
||||
protocolnew("unstable/pointer-constraints" "pointer-constraints-unstable-v1"
|
||||
false)
|
||||
protocolnew("unstable/xdg-foreign" "xdg-foreign-unstable-v2" false)
|
||||
protocolnew("staging/xdg-activation" "xdg-activation-v1" false)
|
||||
protocolnew("staging/ext-idle-notify" "ext-idle-notify-v1" false)
|
||||
protocolnew("staging/ext-session-lock" "ext-session-lock-v1" false)
|
||||
|
|
@ -495,11 +559,14 @@ 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()
|
||||
|
||||
# tools
|
||||
add_subdirectory(hyprctl)
|
||||
add_subdirectory(start)
|
||||
|
||||
if(NO_HYPRPM)
|
||||
message(STATUS "hyprpm is disabled")
|
||||
|
|
@ -518,6 +585,11 @@ install(
|
|||
\"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_BINDIR}/hyprland\" \
|
||||
)")
|
||||
# session file
|
||||
configure_file(
|
||||
${CMAKE_SOURCE_DIR}/example/hyprland.desktop.in
|
||||
${CMAKE_SOURCE_DIR}/example/hyprland.desktop
|
||||
@ONLY
|
||||
)
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/example/hyprland.desktop
|
||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/wayland-sessions)
|
||||
|
||||
|
|
@ -563,10 +635,37 @@ install(
|
|||
PATTERN "*.hpp"
|
||||
PATTERN "*.inc")
|
||||
|
||||
if(BUILD_TESTING OR BUILD_HYPRTESTER)
|
||||
message(STATUS "Building hyprtester")
|
||||
if(BUILD_TESTING OR WITH_TESTS)
|
||||
message(STATUS "Building tests")
|
||||
|
||||
# hyprtester
|
||||
add_subdirectory(hyprtester)
|
||||
|
||||
# GTest
|
||||
find_package(GTest CONFIG REQUIRED)
|
||||
include(GoogleTest)
|
||||
file(GLOB_RECURSE TESTFILES "tests/*.cpp")
|
||||
add_executable(hyprland_gtests ${TESTFILES})
|
||||
target_compile_options(hyprland_gtests PRIVATE --coverage)
|
||||
target_link_options(hyprland_gtests PRIVATE --coverage)
|
||||
target_include_directories(
|
||||
hyprland_gtests
|
||||
PUBLIC "./include"
|
||||
PRIVATE "./src" "./src/include" "./protocols" "${CMAKE_BINARY_DIR}")
|
||||
|
||||
target_link_libraries(hyprland_gtests hyprland_lib GTest::gtest_main)
|
||||
|
||||
gtest_discover_tests(hyprland_gtests)
|
||||
|
||||
# Enable coverage in main hyprland lib
|
||||
target_compile_options(hyprland_lib PRIVATE --coverage)
|
||||
target_link_options(hyprland_lib PRIVATE --coverage)
|
||||
target_link_libraries(hyprland_lib PUBLIC gcov)
|
||||
|
||||
# Enable coverage in hyprland exe
|
||||
target_compile_options(Hyprland PRIVATE --coverage)
|
||||
target_link_options(Hyprland PRIVATE --coverage)
|
||||
target_link_libraries(Hyprland gcov)
|
||||
endif()
|
||||
|
||||
if(BUILD_TESTING)
|
||||
|
|
@ -575,12 +674,8 @@ if(BUILD_TESTING)
|
|||
enable_testing()
|
||||
add_custom_target(tests)
|
||||
|
||||
add_test(
|
||||
NAME "Main Test"
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/hyprtester
|
||||
COMMAND hyprtester)
|
||||
add_dependencies(tests hyprland_gtests)
|
||||
|
||||
add_dependencies(tests hyprtester)
|
||||
else()
|
||||
message(STATUS "Testing is disabled")
|
||||
endif()
|
||||
|
|
|
|||
2
LICENSE
2
LICENSE
|
|
@ -1,6 +1,6 @@
|
|||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2022-2025, vaxerski
|
||||
Copyright (c) 2022-2026, vaxerski
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
|
|
|||
15
Makefile
15
Makefile
|
|
@ -18,6 +18,7 @@ nopch:
|
|||
clear:
|
||||
rm -rf build
|
||||
rm -f ./protocols/*.h ./protocols/*.c ./protocols/*.cpp ./protocols/*.hpp
|
||||
rm -f ./hyprctl/hw-protocols/*.cpp ./hyprctl/hw-protocols/*.hpp
|
||||
|
||||
all:
|
||||
$(MAKE) clear
|
||||
|
|
@ -87,12 +88,24 @@ asan:
|
|||
@echo "Wayland done"
|
||||
|
||||
patch -p1 < ./scripts/hyprlandStaticAsan.diff
|
||||
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -DWITH_ASAN:STRING=True -DUSE_TRACY:STRING=False -DUSE_TRACY_GPU:STRING=False -S . -B ./build -G Ninja
|
||||
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -DWITH_ASAN:STRING=True -DUSE_TRACY:STRING=False -DUSE_TRACY_GPU:STRING=False -S . -B ./build
|
||||
cmake --build ./build --config Debug --target all
|
||||
@echo "Hyprland done"
|
||||
|
||||
ASAN_OPTIONS="detect_odr_violation=0,log_path=asan.log" HYPRLAND_NO_CRASHREPORTER=1 ./build/Hyprland -c ~/.config/hypr/hyprland.conf
|
||||
|
||||
format-check:
|
||||
@find src hyprctl hyprpm start tests -type f \( -name "*.cpp" -o -name "*.hpp" -o -name "*.h" \) \
|
||||
! -path "src/render/shaders/Shaders.hpp" \
|
||||
! -path "hyprctl/hw-protocols/*" | \
|
||||
xargs clang-format --dry-run --Werror
|
||||
|
||||
format-fix:
|
||||
@find src hyprctl hyprpm start tests -type f \( -name "*.cpp" -o -name "*.hpp" -o -name "*.h" \) \
|
||||
! -path "src/render/shaders/Shaders.hpp" \
|
||||
! -path "hyprctl/hw-protocols/*" | \
|
||||
xargs clang-format -i
|
||||
|
||||
test:
|
||||
$(MAKE) debug
|
||||
./build/hyprtester/hyprtester -c hyprtester/test.conf -b ./build/Hyprland -p hyprtester/plugin/hyprtestplugin.so
|
||||
|
|
|
|||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
0.52.0
|
||||
0.54.0
|
||||
|
|
|
|||
20
debug-tools/flamegraph.sh
Executable file
20
debug-tools/flamegraph.sh
Executable file
|
|
@ -0,0 +1,20 @@
|
|||
#!/bin/bash
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
FLAMEGRAPH_DIR="$SCRIPT_DIR/flamegraph"
|
||||
|
||||
if [ ! -d "$FLAMEGRAPH_DIR" ]; then
|
||||
echo "Cloning FlameGraph tools..."
|
||||
git clone https://github.com/brendangregg/FlameGraph "$FLAMEGRAPH_DIR"
|
||||
fi
|
||||
|
||||
if [ ! -f perf.data ]; then
|
||||
echo "No perf.data found in current directory."
|
||||
echo "Run Hyprland under perf first:"
|
||||
echo " perf record -F 99 -g -- ./build/Hyprland"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Generating flame graph..."
|
||||
perf script | "$FLAMEGRAPH_DIR/stackcollapse-perf.pl" | "$FLAMEGRAPH_DIR/flamegraph.pl" > flame.svg
|
||||
xdg-open flame.svg
|
||||
|
|
@ -27,7 +27,7 @@ monitor=,preferred,auto,auto
|
|||
# Set programs that you use
|
||||
$terminal = kitty
|
||||
$fileManager = dolphin
|
||||
$menu = wofi --show drun
|
||||
$menu = hyprlauncher
|
||||
|
||||
|
||||
#################
|
||||
|
|
@ -237,12 +237,12 @@ $mainMod = SUPER # Sets "Windows" key as main modifier
|
|||
# Example binds, see https://wiki.hypr.land/Configuring/Binds/ for more
|
||||
bind = $mainMod, Q, exec, $terminal
|
||||
bind = $mainMod, C, killactive,
|
||||
bind = $mainMod, M, exit,
|
||||
bind = $mainMod, M, exec, command -v hyprshutdown >/dev/null 2>&1 && hyprshutdown || hyprctl dispatch exit
|
||||
bind = $mainMod, E, exec, $fileManager
|
||||
bind = $mainMod, V, togglefloating,
|
||||
bind = $mainMod, R, exec, $menu
|
||||
bind = $mainMod, P, pseudo, # dwindle
|
||||
bind = $mainMod, J, togglesplit, # dwindle
|
||||
bind = $mainMod, J, layoutmsg, togglesplit # dwindle
|
||||
|
||||
# Move focus with mainMod + arrow keys
|
||||
bind = $mainMod, left, movefocus, l
|
||||
|
|
@ -329,3 +329,13 @@ windowrule {
|
|||
|
||||
no_focus = true
|
||||
}
|
||||
|
||||
# Hyprland-run windowrule
|
||||
windowrule {
|
||||
name = move-hyprland-run
|
||||
|
||||
match:class = hyprland-run
|
||||
|
||||
move = 20 monitor_h-120
|
||||
float = yes
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[Desktop Entry]
|
||||
Name=Hyprland
|
||||
Comment=An intelligent dynamic tiling Wayland compositor
|
||||
Exec=Hyprland
|
||||
Exec=@PREFIX@/@CMAKE_INSTALL_BINDIR@/start-hyprland
|
||||
Type=Application
|
||||
DesktopNames=Hyprland
|
||||
Keywords=tiling;wayland;compositor;
|
||||
109
flake.lock
generated
109
flake.lock
generated
|
|
@ -16,11 +16,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1763922789,
|
||||
"narHash": "sha256-XnkWjCpeXfip9tqYdL0b0zzBDjq+dgdISvEdSVGdVyA=",
|
||||
"lastModified": 1776702787,
|
||||
"narHash": "sha256-qc5uwEWbuubzYthmZcfCapooZGXhoYZWfTQ24TozbCQ=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "aquamarine",
|
||||
"rev": "a20a0e67a33b6848378a91b871b89588d3a12573",
|
||||
"rev": "9a1ca6b8cb4d86a599787a55b78f2ddf809bf945",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -32,15 +32,15 @@
|
|||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1761588595,
|
||||
"narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=",
|
||||
"owner": "edolstra",
|
||||
"lastModified": 1767039857,
|
||||
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
|
||||
"owner": "NixOS",
|
||||
"repo": "flake-compat",
|
||||
"rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5",
|
||||
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"owner": "NixOS",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
|
|
@ -79,11 +79,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1753964049,
|
||||
"narHash": "sha256-lIqabfBY7z/OANxHoPeIrDJrFyYy9jAM4GQLzZ2feCM=",
|
||||
"lastModified": 1776511930,
|
||||
"narHash": "sha256-fCpwFiTW0rT7oKJqr3cqHMnkwypSwQKpbtUEtxdkgrM=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprcursor",
|
||||
"rev": "44e91d467bdad8dcf8bbd2ac7cf49972540980a5",
|
||||
"rev": "39435900785d0c560c6ae8777d29f28617d031ef",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -105,11 +105,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1763733840,
|
||||
"narHash": "sha256-JnET78yl5RvpGuDQy3rCycOCkiKoLr5DN1fPhRNNMco=",
|
||||
"lastModified": 1776426399,
|
||||
"narHash": "sha256-RUESLKNikIeEq9ymGJ6nmcDXiSFQpUW1IhJ245nL3xM=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprgraphics",
|
||||
"rev": "8f1bec691b2d198c60cccabca7a94add2df4ed1a",
|
||||
"rev": "68d064434787cf1ed4a2fe257c03c5f52f33cf84",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -144,11 +144,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1763727565,
|
||||
"narHash": "sha256-vRff/2R1U1jzPBy4OODqh2kfUzmizW/nfV2ROzTDIKo=",
|
||||
"lastModified": 1776426575,
|
||||
"narHash": "sha256-KI6nIfVihn/DPaeB5Et46Xg3dkNHrrEtUd5LBBVomB0=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprland-guiutils",
|
||||
"rev": "7724d3a12a0453e7aae05f2ef39474219f05a4b4",
|
||||
"rev": "a968d211048e3ed538e47b84cb3649299578f19d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -167,11 +167,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1759610243,
|
||||
"narHash": "sha256-+KEVnKBe8wz+a6dTLq8YDcF3UrhQElwsYJaVaHXJtoI=",
|
||||
"lastModified": 1772460177,
|
||||
"narHash": "sha256-/6G/MsPvtn7bc4Y32pserBT/Z4SUUdBd4XYJpOEKVR4=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprland-protocols",
|
||||
"rev": "bd153e76f751f150a09328dbdeb5e4fab9d23622",
|
||||
"rev": "1cb6db5fd6bb8aee419f4457402fa18293ace917",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -193,11 +193,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1763819661,
|
||||
"narHash": "sha256-0jLarTR/BLWdGlboM86bPVP2zKJNI2jvo3JietnDkOM=",
|
||||
"lastModified": 1776426736,
|
||||
"narHash": "sha256-rl7i4aY+9p8LysJp7o8uRWahCkpFznCgGHXszlTw7b0=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprlang",
|
||||
"rev": "a318deec0c12409ec39c68d2be8096b636dc2a5c",
|
||||
"rev": "7833ff33b2e82d3406337b5dcf0d1cec595d83e9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -238,11 +238,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1763503177,
|
||||
"narHash": "sha256-VPoiswJBBmTLVuNncvT/8FpFR+sYcAi/LgP/zTZ+5rA=",
|
||||
"lastModified": 1772462885,
|
||||
"narHash": "sha256-5pHXrQK9zasMnIo6yME6EOXmWGFMSnCITcfKshhKJ9I=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprtoolkit",
|
||||
"rev": "f4e1e12755567ecf39090203b8f43eace8279630",
|
||||
"rev": "9af245a69fa6b286b88ddfc340afd288e00a6998",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -261,11 +261,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1763996058,
|
||||
"narHash": "sha256-DsqzFZvrEV+aDmavjaD4/bk5qxeZwhGxPWBQdpFyM9Y=",
|
||||
"lastModified": 1776428866,
|
||||
"narHash": "sha256-XfRlBolGtjvalTHJp3XvvpYLBjkMhaZLLU0WqZ91Fcg=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprutils",
|
||||
"rev": "0168583075baffa083032ed13a8bea8ea12f281a",
|
||||
"rev": "eedd60805cd96d4442586f2ba5fe51d549b12674",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -284,11 +284,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1763640274,
|
||||
"narHash": "sha256-Uan1Nl9i4TF/kyFoHnTq1bd/rsWh4GAK/9/jDqLbY5A=",
|
||||
"lastModified": 1776430932,
|
||||
"narHash": "sha256-Yv3RPiUvl7CAsJgwIVsqcj7akn1gLyJP1F/mocof5hA=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprwayland-scanner",
|
||||
"rev": "f6cf414ca0e16a4d30198fd670ec86df3c89f671",
|
||||
"rev": "4c2fcc06dc9722c97dbb54ba649c69b18ce83d2e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -297,13 +297,39 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"hyprwire": {
|
||||
"inputs": {
|
||||
"hyprutils": [
|
||||
"hyprutils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"systems": [
|
||||
"systems"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1776728575,
|
||||
"narHash": "sha256-z9eGphrArEBpl1O/GCH0wlY6z4K9vA6yWh2gAS6qytU=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprwire",
|
||||
"rev": "f3a80888783702a39691b684d099e16b83ed4702",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprwire",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1763966396,
|
||||
"narHash": "sha256-6eeL1YPcY1MV3DDStIDIdy/zZCDKgHdkCmsrLJFiZf0=",
|
||||
"lastModified": 1776548001,
|
||||
"narHash": "sha256-ZSK0NL4a1BwVbbTBoSnWgbJy9HeZFXLYQizjb2DPF24=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "5ae3b07d8d6527c42f17c876e404993199144b6a",
|
||||
"rev": "b12141ef619e0a9c1c84dc8c684040326f27cdcc",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -322,11 +348,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1763988335,
|
||||
"narHash": "sha256-QlcnByMc8KBjpU37rbq5iP7Cp97HvjRP0ucfdh+M4Qc=",
|
||||
"lastModified": 1776796298,
|
||||
"narHash": "sha256-PcRvlWayisPSjd0UcRQbhG8Oqw78AcPE6x872cPRHN8=",
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"rev": "50b9238891e388c9fdc6a5c49e49c42533a1b5ce",
|
||||
"rev": "3cfd774b0a530725a077e17354fbdb87ea1c4aad",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -345,6 +371,7 @@
|
|||
"hyprlang": "hyprlang",
|
||||
"hyprutils": "hyprutils",
|
||||
"hyprwayland-scanner": "hyprwayland-scanner",
|
||||
"hyprwire": "hyprwire",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"pre-commit-hooks": "pre-commit-hooks",
|
||||
"systems": "systems",
|
||||
|
|
@ -388,11 +415,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1761431178,
|
||||
"narHash": "sha256-xzjC1CV3+wpUQKNF+GnadnkeGUCJX+vgaWIZsnz9tzI=",
|
||||
"lastModified": 1776608502,
|
||||
"narHash": "sha256-UH8YoQxx4hFOm6qjMdjRQNRvSejFIR/wBZ8fW1p9sME=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "xdg-desktop-portal-hyprland",
|
||||
"rev": "4b8801228ff958d028f588f0c2b911dbf32297f9",
|
||||
"rev": "4a293523d36dfa367e67ec304cc718ea66a8fec2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
|||
212
flake.nix
212
flake.nix
|
|
@ -65,6 +65,13 @@
|
|||
inputs.systems.follows = "systems";
|
||||
};
|
||||
|
||||
hyprwire = {
|
||||
url = "github:hyprwm/hyprwire";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
inputs.systems.follows = "systems";
|
||||
inputs.hyprutils.follows = "hyprutils";
|
||||
};
|
||||
|
||||
xdph = {
|
||||
url = "github:hyprwm/xdg-desktop-portal-hyprland";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
|
|
@ -81,107 +88,122 @@
|
|||
};
|
||||
};
|
||||
|
||||
outputs = inputs @ {
|
||||
self,
|
||||
nixpkgs,
|
||||
systems,
|
||||
...
|
||||
}: let
|
||||
inherit (nixpkgs) lib;
|
||||
eachSystem = lib.genAttrs (import systems);
|
||||
pkgsFor = eachSystem (system:
|
||||
import nixpkgs {
|
||||
localSystem = system;
|
||||
overlays = with self.overlays; [
|
||||
hyprland-packages
|
||||
hyprland-extras
|
||||
];
|
||||
});
|
||||
pkgsCrossFor = eachSystem (system: crossSystem:
|
||||
import nixpkgs {
|
||||
localSystem = system;
|
||||
inherit crossSystem;
|
||||
overlays = with self.overlays; [
|
||||
hyprland-packages
|
||||
hyprland-extras
|
||||
];
|
||||
});
|
||||
pkgsDebugFor = eachSystem (system:
|
||||
import nixpkgs {
|
||||
localSystem = system;
|
||||
overlays = with self.overlays; [
|
||||
hyprland-debug
|
||||
];
|
||||
});
|
||||
pkgsDebugCrossFor = eachSystem (system: crossSystem:
|
||||
import nixpkgs {
|
||||
localSystem = system;
|
||||
inherit crossSystem;
|
||||
overlays = with self.overlays; [
|
||||
hyprland-debug
|
||||
];
|
||||
});
|
||||
in {
|
||||
overlays = import ./nix/overlays.nix {inherit self lib inputs;};
|
||||
outputs =
|
||||
inputs@{
|
||||
self,
|
||||
nixpkgs,
|
||||
systems,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (nixpkgs) lib;
|
||||
eachSystem = lib.genAttrs (import systems);
|
||||
pkgsFor = eachSystem (
|
||||
system:
|
||||
import nixpkgs {
|
||||
localSystem = system;
|
||||
overlays = with self.overlays; [
|
||||
hyprland-packages
|
||||
hyprland-extras
|
||||
];
|
||||
}
|
||||
);
|
||||
pkgsCrossFor = eachSystem (
|
||||
system: crossSystem:
|
||||
import nixpkgs {
|
||||
localSystem = system;
|
||||
inherit crossSystem;
|
||||
overlays = with self.overlays; [
|
||||
hyprland-packages
|
||||
hyprland-extras
|
||||
];
|
||||
}
|
||||
);
|
||||
pkgsDebugFor = eachSystem (
|
||||
system:
|
||||
import nixpkgs {
|
||||
localSystem = system;
|
||||
overlays = with self.overlays; [
|
||||
hyprland-debug
|
||||
];
|
||||
}
|
||||
);
|
||||
pkgsDebugCrossFor = eachSystem (
|
||||
system: crossSystem:
|
||||
import nixpkgs {
|
||||
localSystem = system;
|
||||
inherit crossSystem;
|
||||
overlays = with self.overlays; [
|
||||
hyprland-debug
|
||||
];
|
||||
}
|
||||
);
|
||||
in
|
||||
{
|
||||
overlays = import ./nix/overlays.nix { inherit self lib inputs; };
|
||||
|
||||
checks = eachSystem (system:
|
||||
(lib.filterAttrs
|
||||
(n: _: (lib.hasPrefix "hyprland" n) && !(lib.hasSuffix "debug" n))
|
||||
self.packages.${system})
|
||||
// {
|
||||
inherit (self.packages.${system}) xdg-desktop-portal-hyprland;
|
||||
pre-commit-check = inputs.pre-commit-hooks.lib.${system}.run {
|
||||
src = ./.;
|
||||
hooks = {
|
||||
hyprland-treewide-formatter = {
|
||||
enable = true;
|
||||
entry = "${self.formatter.${system}}/bin/hyprland-treewide-formatter";
|
||||
pass_filenames = false;
|
||||
excludes = ["subprojects"];
|
||||
always_run = true;
|
||||
checks = eachSystem (
|
||||
system:
|
||||
(lib.filterAttrs (
|
||||
n: _: (lib.hasPrefix "hyprland" n) && !(lib.hasSuffix "debug" n)
|
||||
) self.packages.${system})
|
||||
// {
|
||||
inherit (self.packages.${system}) xdg-desktop-portal-hyprland;
|
||||
pre-commit-check = inputs.pre-commit-hooks.lib.${system}.run {
|
||||
src = ./.;
|
||||
hooks = {
|
||||
hyprland-treewide-formatter = {
|
||||
enable = true;
|
||||
entry = "${self.formatter.${system}}/bin/hyprland-treewide-formatter";
|
||||
pass_filenames = false;
|
||||
excludes = [ "subprojects" ];
|
||||
always_run = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
// (import ./nix/tests inputs pkgsFor.${system}));
|
||||
}
|
||||
// (import ./nix/tests inputs pkgsFor.${system})
|
||||
);
|
||||
|
||||
packages = eachSystem (system: {
|
||||
default = self.packages.${system}.hyprland;
|
||||
inherit
|
||||
(pkgsFor.${system})
|
||||
# hyprland-packages
|
||||
hyprland
|
||||
hyprland-unwrapped
|
||||
# hyprland-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;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,32 @@ project(
|
|||
DESCRIPTION "Control utility for Hyprland"
|
||||
)
|
||||
|
||||
pkg_check_modules(hyprctl_deps REQUIRED IMPORTED_TARGET hyprutils>=0.2.4 re2)
|
||||
pkg_check_modules(hyprctl_deps REQUIRED IMPORTED_TARGET hyprutils>=0.2.4 hyprwire re2)
|
||||
|
||||
add_executable(hyprctl "main.cpp")
|
||||
file(GLOB_RECURSE HYPRCTL_SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "hw-protocols/*.cpp" "include/*.hpp")
|
||||
|
||||
add_executable(hyprctl ${HYPRCTL_SRCFILES})
|
||||
|
||||
target_link_libraries(hyprctl PUBLIC PkgConfig::hyprctl_deps)
|
||||
target_include_directories(hyprctl PRIVATE "hw-protocols")
|
||||
|
||||
# Hyprwire
|
||||
|
||||
function(hyprprotocol protoPath protoName)
|
||||
set(path ${CMAKE_CURRENT_SOURCE_DIR}/${protoPath})
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/hw-protocols/${protoName}-client.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/hw-protocols/${protoName}-client.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/hw-protocols/${protoName}-spec.hpp
|
||||
COMMAND hyprwire-scanner --client ${path}/${protoName}.xml
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/hw-protocols/
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_sources(hyprctl PRIVATE hw-protocols/${protoName}-client.cpp
|
||||
hw-protocols/${protoName}-client.hpp
|
||||
hw-protocols/${protoName}-spec.hpp)
|
||||
endfunction()
|
||||
|
||||
hyprprotocol(hw-protocols hyprpaper_core)
|
||||
|
||||
# binary
|
||||
install(TARGETS hyprctl)
|
||||
|
|
|
|||
172
hyprctl/hw-protocols/hyprpaper_core.xml
Normal file
172
hyprctl/hw-protocols/hyprpaper_core.xml
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="hyprpaper_core" version="2">
|
||||
<copyright>
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2025, Hypr Development
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
</copyright>
|
||||
|
||||
<object name="hyprpaper_core_manager" version="2">
|
||||
<description summary="manager object">
|
||||
This is the core manager object for hyprpaper operations
|
||||
</description>
|
||||
|
||||
<c2s name="get_wallpaper_object">
|
||||
<description summary="Get a wallpaper object">
|
||||
Creates a wallpaper object
|
||||
</description>
|
||||
<returns iface="hyprpaper_wallpaper"/>
|
||||
</c2s>
|
||||
|
||||
<s2c name="add_monitor">
|
||||
<description summary="New monitor added">
|
||||
Emitted when a new monitor is added.
|
||||
</description>
|
||||
<arg name="monitor_name" type="varchar" summary="the monitor's name"/>
|
||||
</s2c>
|
||||
|
||||
<s2c name="remove_monitor">
|
||||
<description summary="A monitor was removed">
|
||||
Emitted when a monitor is removed.
|
||||
</description>
|
||||
<arg name="monitor_name" type="varchar" summary="the monitor's name"/>
|
||||
</s2c>
|
||||
|
||||
<c2s name="destroy" destructor="true">
|
||||
<description summary="Destroy this object">
|
||||
Destroys this object. Children remain alive until destroyed.
|
||||
</description>
|
||||
</c2s>
|
||||
|
||||
<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">
|
||||
<value idx="0" name="stretch"/>
|
||||
<value idx="1" name="cover"/>
|
||||
<value idx="2" name="contain"/>
|
||||
<value idx="3" name="tile"/>
|
||||
</enum>
|
||||
|
||||
<enum name="wallpaper_errors">
|
||||
<value idx="0" name="inert_wallpaper_object" description="attempted to use an inert wallpaper object"/>
|
||||
</enum>
|
||||
|
||||
<enum name="applying_error">
|
||||
<value idx="0" name="invalid_path" description="path provided was invalid"/>
|
||||
<value idx="1" name="invalid_monitor" description="monitor provided was invalid"/>
|
||||
<value idx="2" name="unknown_error" description="unknown error"/>
|
||||
</enum>
|
||||
|
||||
<object name="hyprpaper_wallpaper" version="1">
|
||||
<description summary="wallpaper object">
|
||||
This is an object describing a wallpaper
|
||||
</description>
|
||||
|
||||
<c2s name="path">
|
||||
<description summary="Set a path">
|
||||
Set a file path for the wallpaper. This has to be an absolute path from the fs root.
|
||||
This is required.
|
||||
</description>
|
||||
<arg name="wallpaper" type="varchar" summary="path"/>
|
||||
</c2s>
|
||||
|
||||
<c2s name="fit_mode">
|
||||
<description summary="Set a fit mode">
|
||||
Set a fit mode for the wallpaper. This is set to cover by default.
|
||||
</description>
|
||||
<arg name="fit_mode" type="enum" interface="wallpaper_fit_mode" summary="path"/>
|
||||
</c2s>
|
||||
|
||||
<c2s name="monitor_name">
|
||||
<description summary="Set the monitor name">
|
||||
Set a monitor for the wallpaper. Setting this to empty (or not setting at all) will
|
||||
treat this as a wildcard fallback.
|
||||
|
||||
See hyprpaper_core_manager.add_monitor and hyprpaper_core_manager.remove_monitor
|
||||
for tracking monitor names.
|
||||
</description>
|
||||
<arg name="monitor_name" type="varchar" summary="monitor name"/>
|
||||
</c2s>
|
||||
|
||||
<c2s name="apply">
|
||||
<description summary="Apply this wallpaper">
|
||||
Applies this object's state to the wallpaper state. Will emit .success on success,
|
||||
and .failed on failure.
|
||||
|
||||
This object becomes inert after .succeess or .failed, the only valid operation
|
||||
is to destroy it afterwards.
|
||||
</description>
|
||||
</c2s>
|
||||
|
||||
<s2c name="success">
|
||||
<description summary="Operation succeeded">
|
||||
Wallpaper was applied successfully.
|
||||
</description>
|
||||
</s2c>
|
||||
|
||||
<s2c name="failed">
|
||||
<description summary="Operation failed">
|
||||
Wallpaper was not applied. See the error field for more information.
|
||||
</description>
|
||||
<arg name="error" type="enum" interface="hyprpaper_wallpaper_application_error" summary="path"/>
|
||||
</s2c>
|
||||
|
||||
<c2s name="destroy" destructor="true">
|
||||
<description summary="Destroy this object">
|
||||
Destroys this object.
|
||||
</description>
|
||||
</c2s>
|
||||
</object>
|
||||
|
||||
<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>
|
||||
|
|
@ -74,11 +74,8 @@ flags:
|
|||
const std::string_view HYPRPAPER_HELP = R"#(usage: hyprctl [flags] hyprpaper <request>
|
||||
|
||||
requests:
|
||||
listactive → Lists all active images
|
||||
listloaded → Lists all loaded images
|
||||
preload <path> → Preloads image
|
||||
unload <path> → Unloads image. Pass 'all' as path to unload all images
|
||||
wallpaper → Issue a wallpaper to call a config wallpaper dynamically
|
||||
wallpaper → Issue a wallpaper to call a config wallpaper dynamically.
|
||||
Arguments are [mon],[path],[fit_mode]. Fit mode is optional.
|
||||
|
||||
flags:
|
||||
See 'hyprctl --help')#";
|
||||
11
hyprctl/src/helpers/Memory.hpp
Normal file
11
hyprctl/src/helpers/Memory.hpp
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include <hyprutils/memory/SharedPtr.hpp>
|
||||
#include <hyprutils/memory/UniquePtr.hpp>
|
||||
#include <hyprutils/memory/Atomic.hpp>
|
||||
|
||||
using namespace Hyprutils::Memory;
|
||||
|
||||
#define SP CSharedPointer
|
||||
#define WP CWeakPointer
|
||||
#define UP CUniquePointer
|
||||
208
hyprctl/src/hyprpaper/Hyprpaper.cpp
Normal file
208
hyprctl/src/hyprpaper/Hyprpaper.cpp
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
#include "Hyprpaper.hpp"
|
||||
#include "../helpers/Memory.hpp"
|
||||
|
||||
#include <optional>
|
||||
#include <format>
|
||||
#include <filesystem>
|
||||
#include <print>
|
||||
|
||||
#include <hyprpaper_core-client.hpp>
|
||||
|
||||
#include <hyprutils/string/VarList2.hpp>
|
||||
using namespace Hyprutils::String;
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
constexpr const char* SOCKET_NAME = ".hyprpaper.sock";
|
||||
static SP<CCHyprpaperCoreImpl> g_coreImpl;
|
||||
|
||||
constexpr const uint32_t PROTOCOL_VERSION_SUPPORTED = 2;
|
||||
|
||||
//
|
||||
static hyprpaperCoreWallpaperFitMode fitFromString(const std::string_view& sv) {
|
||||
if (sv == "contain")
|
||||
return HYPRPAPER_CORE_WALLPAPER_FIT_MODE_CONTAIN;
|
||||
if (sv == "fit" || sv == "stretch")
|
||||
return HYPRPAPER_CORE_WALLPAPER_FIT_MODE_STRETCH;
|
||||
if (sv == "tile")
|
||||
return HYPRPAPER_CORE_WALLPAPER_FIT_MODE_TILE;
|
||||
return HYPRPAPER_CORE_WALLPAPER_FIT_MODE_COVER;
|
||||
}
|
||||
|
||||
static std::expected<std::string, std::string> resolvePath(const std::string_view& sv) {
|
||||
std::error_code ec;
|
||||
auto can = std::filesystem::canonical(sv, ec);
|
||||
|
||||
if (ec)
|
||||
return std::unexpected(std::format("invalid path: {}", ec.message()));
|
||||
|
||||
return can;
|
||||
}
|
||||
|
||||
static std::expected<std::string, std::string> getFullPath(const std::string_view& sv) {
|
||||
if (sv.empty())
|
||||
return std::unexpected("empty path");
|
||||
|
||||
if (sv[0] == '~') {
|
||||
static auto HOME = getenv("HOME");
|
||||
if (!HOME || HOME[0] == '\0')
|
||||
return std::unexpected("home path but no $HOME");
|
||||
|
||||
return resolvePath(std::string{HOME} + "/"s + std::string{sv.substr(1)});
|
||||
}
|
||||
|
||||
return resolvePath(sv);
|
||||
}
|
||||
|
||||
static std::expected<void, std::string> doWallpaper(const std::string_view& RHS) {
|
||||
CVarList2 args(std::string{RHS}, 0, ',');
|
||||
|
||||
const std::string MONITOR = std::string{args[0]};
|
||||
const auto& PATH_RAW = args[1];
|
||||
const auto& FIT = args[2];
|
||||
|
||||
if (PATH_RAW.empty())
|
||||
return std::unexpected("not enough args");
|
||||
|
||||
const auto RTDIR = getenv("XDG_RUNTIME_DIR");
|
||||
|
||||
if (!RTDIR || RTDIR[0] == '\0')
|
||||
return std::unexpected("can't send: no XDG_RUNTIME_DIR");
|
||||
|
||||
const auto HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE");
|
||||
|
||||
if (!HIS || HIS[0] == '\0')
|
||||
return std::unexpected("can't send: no HYPRLAND_INSTANCE_SIGNATURE (not running under hyprland)");
|
||||
|
||||
const auto PATH = getFullPath(PATH_RAW);
|
||||
|
||||
if (!PATH)
|
||||
return std::unexpected(std::format("bad path: {}", PATH_RAW));
|
||||
|
||||
auto socketPath = RTDIR + "/hypr/"s + HIS + "/"s + SOCKET_NAME;
|
||||
|
||||
auto socket = Hyprwire::IClientSocket::open(socketPath);
|
||||
|
||||
if (!socket)
|
||||
return std::unexpected("can't send: failed to connect to hyprpaper (is it running?)");
|
||||
|
||||
g_coreImpl = makeShared<CCHyprpaperCoreImpl>(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?!");
|
||||
|
||||
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 wallpaper = makeShared<CCHyprpaperWallpaperObject>(manager->sendGetWallpaperObject());
|
||||
|
||||
if (!wallpaper)
|
||||
return std::unexpected("wire error: couldn't create wallpaper object");
|
||||
|
||||
bool canExit = false;
|
||||
std::optional<std::string> err;
|
||||
|
||||
wallpaper->setFailed([&canExit, &err](uint32_t code) {
|
||||
canExit = true;
|
||||
switch (code) {
|
||||
case HYPRPAPER_CORE_APPLYING_ERROR_INVALID_PATH: err = std::format("failed to set wallpaper: Invalid path", code); break;
|
||||
case HYPRPAPER_CORE_APPLYING_ERROR_INVALID_MONITOR: err = std::format("failed to set wallpaper: Invalid monitor", code); break;
|
||||
default: err = std::format("failed to set wallpaper: unknown error, code {}", code); break;
|
||||
}
|
||||
});
|
||||
wallpaper->setSuccess([&canExit]() { canExit = true; });
|
||||
|
||||
wallpaper->sendPath(PATH->c_str());
|
||||
wallpaper->sendMonitorName(MONITOR.c_str());
|
||||
if (!FIT.empty())
|
||||
wallpaper->sendFitMode(fitFromString(FIT));
|
||||
|
||||
wallpaper->sendApply();
|
||||
|
||||
while (!canExit) {
|
||||
socket->dispatchEvents(true);
|
||||
}
|
||||
|
||||
if (err)
|
||||
return std::unexpected(*err);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
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 {};
|
||||
}
|
||||
8
hyprctl/src/hyprpaper/Hyprpaper.hpp
Normal file
8
hyprctl/src/hyprpaper/Hyprpaper.hpp
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <expected>
|
||||
#include <string>
|
||||
|
||||
namespace Hyprpaper {
|
||||
std::expected<void, std::string> makeHyprpaperRequest(const std::string_view& rq);
|
||||
};
|
||||
|
|
@ -31,6 +31,7 @@ using namespace Hyprutils::String;
|
|||
using namespace Hyprutils::Memory;
|
||||
|
||||
#include "Strings.hpp"
|
||||
#include "hyprpaper/Hyprpaper.hpp"
|
||||
|
||||
std::string instanceSignature;
|
||||
bool quiet = false;
|
||||
|
|
@ -227,23 +228,23 @@ int request(std::string_view arg, int minArgs = 0, bool needRoll = false) {
|
|||
constexpr size_t BUFFER_SIZE = 8192;
|
||||
char buffer[BUFFER_SIZE] = {0};
|
||||
|
||||
sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE);
|
||||
|
||||
if (sizeWritten < 0) {
|
||||
if (errno == EWOULDBLOCK)
|
||||
log("Hyprland IPC didn't respond in time\n");
|
||||
log("Couldn't read (6)");
|
||||
return 6;
|
||||
}
|
||||
|
||||
reply += std::string(buffer, sizeWritten);
|
||||
|
||||
while (sizeWritten == BUFFER_SIZE) {
|
||||
// read all data until server closes the connection
|
||||
// this handles partial writes on the server side under high load
|
||||
while (true) {
|
||||
sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE);
|
||||
|
||||
if (sizeWritten < 0) {
|
||||
if (errno == EWOULDBLOCK)
|
||||
log("Hyprland IPC didn't respond in time\n");
|
||||
log("Couldn't read (6)");
|
||||
return 6;
|
||||
}
|
||||
|
||||
if (sizeWritten == 0) {
|
||||
// server closed connection, we're done
|
||||
break;
|
||||
}
|
||||
|
||||
reply += std::string(buffer, sizeWritten);
|
||||
}
|
||||
|
||||
|
|
@ -305,10 +306,6 @@ int requestIPC(std::string_view filename, std::string_view arg) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
int requestHyprpaper(std::string_view arg) {
|
||||
return requestIPC(".hyprpaper.sock", arg);
|
||||
}
|
||||
|
||||
int requestHyprsunset(std::string_view arg) {
|
||||
return requestIPC(".hyprsunset.sock", arg);
|
||||
}
|
||||
|
|
@ -500,9 +497,12 @@ int main(int argc, char** argv) {
|
|||
|
||||
if (fullRequest.contains("/--batch"))
|
||||
batchRequest(fullRequest, json);
|
||||
else if (fullRequest.contains("/hyprpaper"))
|
||||
exitStatus = requestHyprpaper(fullRequest);
|
||||
else if (fullRequest.contains("/hyprsunset"))
|
||||
else if (fullRequest.contains("/hyprpaper")) {
|
||||
auto result = Hyprpaper::makeHyprpaperRequest(fullRequest);
|
||||
if (!result)
|
||||
log(std::format("error: {}", result.error()));
|
||||
exitStatus = !result;
|
||||
} else if (fullRequest.contains("/hyprsunset"))
|
||||
exitStatus = requestHyprsunset(fullRequest);
|
||||
else if (fullRequest.contains("/switchxkblayout"))
|
||||
exitStatus = request(fullRequest, 2);
|
||||
|
|
@ -4,4 +4,5 @@ Name: Hyprland
|
|||
URL: https://github.com/hyprwm/Hyprland
|
||||
Description: Hyprland header files
|
||||
Version: @HYPRLAND_VERSION@
|
||||
Cflags: -I${prefix} -I${prefix}/hyprland/protocols -I${prefix}/hyprland
|
||||
Requires: aquamarine >= @AQUAMARINE_MINIMUM_VERSION@, hyprcursor >= @HYPRCURSOR_MINIMUM_VERSION@, hyprgraphics >= @HYPRGRAPHICS_MINIMUM_VERSION@, hyprlang >= @HYPRLANG_MINIMUM_VERSION@, hyprutils >= @HYPRUTILS_MINIMUM_VERSION@, libdrm, egl, cairo, xkbcommon >= @XKBCOMMON_MINIMUM_VERSION@, libinput >= @LIBINPUT_MINIMUM_VERSION@, wayland-server >= @WAYLAND_SERVER_MINIMUM_VERSION@@PKGCONFIG_XWAYLAND_DEPENDENCIES@
|
||||
Cflags: -I${prefix} -I${prefix}/hyprland/protocols -I${prefix}/hyprland -I${prefix}/hyprland/src
|
||||
|
|
|
|||
|
|
@ -11,16 +11,17 @@ set(CMAKE_CXX_STANDARD 23)
|
|||
|
||||
pkg_check_modules(hyprpm_deps REQUIRED IMPORTED_TARGET tomlplusplus hyprutils>=0.7.0)
|
||||
|
||||
find_package(glaze QUIET)
|
||||
find_package(glaze 7...<8 QUIET)
|
||||
if (NOT glaze_FOUND)
|
||||
set(GLAZE_VERSION v5.1.1)
|
||||
message(STATUS "glaze dependency not found, retrieving ${GLAZE_VERSION} with FetchContent")
|
||||
set(GLAZE_VERSION v7.2.0)
|
||||
message(STATUS "hyprpm: glaze dependency not found, retrieving ${GLAZE_VERSION} with FetchContent")
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
glaze
|
||||
GIT_REPOSITORY https://github.com/stephenberry/glaze.git
|
||||
GIT_TAG ${GLAZE_VERSION}
|
||||
GIT_SHALLOW TRUE
|
||||
EXCLUDE_FROM_ALL
|
||||
)
|
||||
FetchContent_MakeAvailable(glaze)
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ void DataState::addNewPluginRepo(const SPluginRepository& repo) {
|
|||
auto DATA = toml::table{
|
||||
{"repository", toml::table{
|
||||
{"name", repo.name},
|
||||
{"author", repo.author},
|
||||
{"hash", repo.hash},
|
||||
{"url", repo.url},
|
||||
{"rev", repo.rev}
|
||||
|
|
@ -122,31 +123,32 @@ void DataState::addNewPluginRepo(const SPluginRepository& repo) {
|
|||
Debug::die("{}", failureString("Failed to write plugin state"));
|
||||
}
|
||||
|
||||
bool DataState::pluginRepoExists(const std::string& urlOrName) {
|
||||
bool DataState::pluginRepoExists(const SPluginRepoIdentifier identifier) {
|
||||
ensureStateStoreExists();
|
||||
|
||||
for (const auto& stateFile : getPluginStates()) {
|
||||
const auto STATE = toml::parse_file(stateFile.c_str());
|
||||
const auto NAME = STATE["repository"]["name"].value_or("");
|
||||
const auto URL = STATE["repository"]["url"].value_or("");
|
||||
const auto STATE = toml::parse_file(stateFile.c_str());
|
||||
const auto NAME = STATE["repository"]["name"].value_or("");
|
||||
const auto AUTHOR = STATE["repository"]["author"].value_or("");
|
||||
const auto URL = STATE["repository"]["url"].value_or("");
|
||||
|
||||
if (URL == urlOrName || NAME == urlOrName)
|
||||
if (identifier.matches(URL, NAME, AUTHOR))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void DataState::removePluginRepo(const std::string& urlOrName) {
|
||||
void DataState::removePluginRepo(const SPluginRepoIdentifier identifier) {
|
||||
ensureStateStoreExists();
|
||||
|
||||
for (const auto& stateFile : getPluginStates()) {
|
||||
const auto STATE = toml::parse_file(stateFile.c_str());
|
||||
const auto NAME = STATE["repository"]["name"].value_or("");
|
||||
const auto URL = STATE["repository"]["url"].value_or("");
|
||||
|
||||
if (URL == urlOrName || NAME == urlOrName) {
|
||||
const auto STATE = toml::parse_file(stateFile.c_str());
|
||||
const auto NAME = STATE["repository"]["name"].value_or("");
|
||||
const auto AUTHOR = STATE["repository"]["author"].value_or("");
|
||||
const auto URL = STATE["repository"]["url"].value_or("");
|
||||
|
||||
if (identifier.matches(URL, NAME, AUTHOR)) {
|
||||
// unload the plugins!!
|
||||
for (const auto& file : std::filesystem::directory_iterator(stateFile.parent_path())) {
|
||||
if (!file.path().string().ends_with(".so"))
|
||||
|
|
@ -181,7 +183,7 @@ void DataState::updateGlobalState(const SGlobalState& state) {
|
|||
// clang-format off
|
||||
auto DATA = toml::table{
|
||||
{"state", toml::table{
|
||||
{"hash", state.headersHashCompiled},
|
||||
{"hash", state.headersAbiCompiled},
|
||||
{"dont_warn_install", state.dontWarnInstall}
|
||||
}}
|
||||
};
|
||||
|
|
@ -206,8 +208,8 @@ SGlobalState DataState::getGlobalState() {
|
|||
auto DATA = toml::parse_file(stateFile.c_str());
|
||||
|
||||
SGlobalState state;
|
||||
state.headersHashCompiled = DATA["state"]["hash"].value_or("");
|
||||
state.dontWarnInstall = DATA["state"]["dont_warn_install"].value_or(false);
|
||||
state.headersAbiCompiled = DATA["state"]["hash"].value_or("");
|
||||
state.dontWarnInstall = DATA["state"]["dont_warn_install"].value_or(false);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
|
@ -219,16 +221,18 @@ std::vector<SPluginRepository> DataState::getAllRepositories() {
|
|||
for (const auto& stateFile : getPluginStates()) {
|
||||
const auto STATE = toml::parse_file(stateFile.c_str());
|
||||
|
||||
const auto NAME = STATE["repository"]["name"].value_or("");
|
||||
const auto URL = STATE["repository"]["url"].value_or("");
|
||||
const auto REV = STATE["repository"]["rev"].value_or("");
|
||||
const auto HASH = STATE["repository"]["hash"].value_or("");
|
||||
const auto NAME = STATE["repository"]["name"].value_or("");
|
||||
const auto AUTHOR = STATE["repository"]["author"].value_or("");
|
||||
const auto URL = STATE["repository"]["url"].value_or("");
|
||||
const auto REV = STATE["repository"]["rev"].value_or("");
|
||||
const auto HASH = STATE["repository"]["hash"].value_or("");
|
||||
|
||||
SPluginRepository repo;
|
||||
repo.hash = HASH;
|
||||
repo.name = NAME;
|
||||
repo.url = URL;
|
||||
repo.rev = REV;
|
||||
repo.hash = HASH;
|
||||
repo.name = NAME;
|
||||
repo.author = AUTHOR;
|
||||
repo.url = URL;
|
||||
repo.rev = REV;
|
||||
|
||||
for (const auto& [key, val] : STATE) {
|
||||
if (key == "repository")
|
||||
|
|
@ -247,7 +251,7 @@ std::vector<SPluginRepository> DataState::getAllRepositories() {
|
|||
return repos;
|
||||
}
|
||||
|
||||
bool DataState::setPluginEnabled(const std::string& name, bool enabled) {
|
||||
bool DataState::setPluginEnabled(const SPluginRepoIdentifier identifier, bool enabled) {
|
||||
ensureStateStoreExists();
|
||||
|
||||
for (const auto& stateFile : getPluginStates()) {
|
||||
|
|
@ -256,8 +260,17 @@ bool DataState::setPluginEnabled(const std::string& name, bool enabled) {
|
|||
if (key == "repository")
|
||||
continue;
|
||||
|
||||
if (key.str() != name)
|
||||
continue;
|
||||
switch (identifier.type) {
|
||||
case IDENTIFIER_NAME:
|
||||
if (key.str() != identifier.name)
|
||||
continue;
|
||||
break;
|
||||
case IDENTIFIER_AUTHOR_NAME:
|
||||
if (STATE["repository"]["author"] != identifier.author || key.str() != identifier.name)
|
||||
continue;
|
||||
break;
|
||||
default: return false;
|
||||
}
|
||||
|
||||
const auto FAILED = STATE[key]["failed"].value_or(false);
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
#include "Plugin.hpp"
|
||||
|
||||
struct SGlobalState {
|
||||
std::string headersHashCompiled = "";
|
||||
bool dontWarnInstall = false;
|
||||
std::string headersAbiCompiled = "";
|
||||
bool dontWarnInstall = false;
|
||||
};
|
||||
|
||||
namespace DataState {
|
||||
|
|
@ -15,11 +15,11 @@ namespace DataState {
|
|||
std::vector<std::filesystem::path> getPluginStates();
|
||||
void ensureStateStoreExists();
|
||||
void addNewPluginRepo(const SPluginRepository& repo);
|
||||
void removePluginRepo(const std::string& urlOrName);
|
||||
bool pluginRepoExists(const std::string& urlOrName);
|
||||
void removePluginRepo(const SPluginRepoIdentifier identifier);
|
||||
bool pluginRepoExists(const SPluginRepoIdentifier identifier);
|
||||
void updateGlobalState(const SGlobalState& state);
|
||||
void purgeAllCache();
|
||||
SGlobalState getGlobalState();
|
||||
bool setPluginEnabled(const std::string& name, bool enabled);
|
||||
bool setPluginEnabled(const SPluginRepoIdentifier identifier, bool enabled);
|
||||
std::vector<SPluginRepository> getAllRepositories();
|
||||
};
|
||||
};
|
||||
|
|
|
|||
48
hyprpm/src/core/Plugin.cpp
Normal file
48
hyprpm/src/core/Plugin.cpp
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#include "Plugin.hpp"
|
||||
|
||||
SPluginRepoIdentifier SPluginRepoIdentifier::fromUrl(const std::string& url) {
|
||||
return SPluginRepoIdentifier{.type = IDENTIFIER_URL, .url = url};
|
||||
}
|
||||
|
||||
SPluginRepoIdentifier SPluginRepoIdentifier::fromName(const std::string& name) {
|
||||
return SPluginRepoIdentifier{.type = IDENTIFIER_NAME, .name = name};
|
||||
}
|
||||
|
||||
SPluginRepoIdentifier SPluginRepoIdentifier::fromAuthorName(const std::string& author, const std::string& name) {
|
||||
return SPluginRepoIdentifier{.type = IDENTIFIER_AUTHOR_NAME, .name = name, .author = author};
|
||||
}
|
||||
|
||||
SPluginRepoIdentifier SPluginRepoIdentifier::fromString(const std::string& string) {
|
||||
if (string.find(':') != std::string::npos) {
|
||||
return SPluginRepoIdentifier{.type = IDENTIFIER_URL, .url = string};
|
||||
} else {
|
||||
auto slashPos = string.find('/');
|
||||
if (slashPos != std::string::npos) {
|
||||
std::string author = string.substr(0, slashPos);
|
||||
std::string name = string.substr(slashPos + 1, string.size() - slashPos - 1);
|
||||
return SPluginRepoIdentifier{.type = IDENTIFIER_AUTHOR_NAME, .name = name, .author = author};
|
||||
} else {
|
||||
return SPluginRepoIdentifier{.type = IDENTIFIER_NAME, .name = string};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string SPluginRepoIdentifier::toString() const {
|
||||
switch (type) {
|
||||
case IDENTIFIER_NAME: return name;
|
||||
case IDENTIFIER_AUTHOR_NAME: return author + '/' + name;
|
||||
case IDENTIFIER_URL: return url;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
bool SPluginRepoIdentifier::matches(const std::string& url, const std::string& name, const std::string& author) const {
|
||||
switch (type) {
|
||||
case IDENTIFIER_URL: return this->url == url;
|
||||
case IDENTIFIER_NAME: return this->name == name;
|
||||
case IDENTIFIER_AUTHOR_NAME: return this->author == author && this->name == name;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -14,6 +14,27 @@ struct SPluginRepository {
|
|||
std::string url;
|
||||
std::string rev;
|
||||
std::string name;
|
||||
std::string author;
|
||||
std::vector<SPlugin> plugins;
|
||||
std::string hash;
|
||||
};
|
||||
};
|
||||
|
||||
enum ePluginRepoIdentifierType {
|
||||
IDENTIFIER_URL,
|
||||
IDENTIFIER_NAME,
|
||||
IDENTIFIER_AUTHOR_NAME
|
||||
};
|
||||
|
||||
struct SPluginRepoIdentifier {
|
||||
ePluginRepoIdentifierType type;
|
||||
std::string url = "";
|
||||
std::string name = "";
|
||||
std::string author = "";
|
||||
|
||||
static SPluginRepoIdentifier fromString(const std::string& string);
|
||||
static SPluginRepoIdentifier fromUrl(const std::string& Url);
|
||||
static SPluginRepoIdentifier fromName(const std::string& name);
|
||||
static SPluginRepoIdentifier fromAuthorName(const std::string& author, const std::string& name);
|
||||
std::string toString() const;
|
||||
bool matches(const std::string& url, const std::string& name, const std::string& author) const;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <print>
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
|
|
@ -78,40 +79,33 @@ SHyprlandVersion CPluginManager::getHyprlandVersion(bool running) {
|
|||
else
|
||||
onceInstalled = true;
|
||||
|
||||
const auto HLVERCALL = running ? NHyprlandSocket::send("/version") : execAndGet("Hyprland --version");
|
||||
if (m_bVerbose)
|
||||
std::println("{}", verboseString("{} version returned: {}", running ? "running" : "installed", HLVERCALL));
|
||||
const auto HLVERCALL = running ? NHyprlandSocket::send("j/version") : execAndGet("Hyprland --version-json");
|
||||
|
||||
if (!HLVERCALL.contains("Tag:")) {
|
||||
std::println(stderr, "\n{}", failureString("You don't seem to be running Hyprland."));
|
||||
auto jsonQuery = glz::read_json<glz::generic>(HLVERCALL);
|
||||
|
||||
if (!jsonQuery) {
|
||||
std::println("{}", failureString("failed to get the current hyprland version. Are you running hyprland?"));
|
||||
return SHyprlandVersion{};
|
||||
}
|
||||
|
||||
std::string hlcommit = HLVERCALL.substr(HLVERCALL.find("at commit") + 10);
|
||||
hlcommit = hlcommit.substr(0, hlcommit.find_first_of(' '));
|
||||
auto hlbranch = (*jsonQuery)["branch"].get_string();
|
||||
auto hlcommit = (*jsonQuery)["commit"].get_string();
|
||||
auto abiHash = (*jsonQuery)["abiHash"].get_string();
|
||||
auto hldate = (*jsonQuery)["commit_date"].get_string();
|
||||
auto hlcommits = (*jsonQuery)["commits"].get_string();
|
||||
|
||||
std::string hlbranch = HLVERCALL.substr(HLVERCALL.find("from branch") + 12);
|
||||
hlbranch = hlbranch.substr(0, hlbranch.find(" at commit "));
|
||||
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"}; });
|
||||
|
||||
std::string hldate = HLVERCALL.substr(HLVERCALL.find("Date: ") + 6);
|
||||
hldate = hldate.substr(0, hldate.find('\n'));
|
||||
|
||||
std::string hlcommits;
|
||||
|
||||
if (HLVERCALL.contains("commits:")) {
|
||||
hlcommits = HLVERCALL.substr(HLVERCALL.find("commits:") + 9);
|
||||
hlcommits = hlcommits.substr(0, hlcommits.find(' '));
|
||||
}
|
||||
|
||||
int commits = 0;
|
||||
size_t commits = 0;
|
||||
try {
|
||||
commits = std::stoi(hlcommits);
|
||||
commits = std::stoull(hlcommits);
|
||||
} catch (...) { ; }
|
||||
|
||||
if (m_bVerbose)
|
||||
std::println("{}", verboseString("parsed commit {} at branch {} on {}, commits {}", hlcommit, hlbranch, hldate, commits));
|
||||
std::println("{}", verboseString("parsed commit {} at branch {} on {}, commits {}, nix: {}", hlcommit, hlbranch, hldate, commits, isNix));
|
||||
|
||||
auto ver = SHyprlandVersion{hlbranch, hlcommit, hldate, commits};
|
||||
auto ver = SHyprlandVersion{hlbranch, hlcommit, hldate, abiHash, commits, isNix};
|
||||
|
||||
if (running)
|
||||
verRunning = ver;
|
||||
|
|
@ -137,16 +131,24 @@ 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 (!hasDeps()) {
|
||||
std::println(stderr, "\n{}",
|
||||
failureString("Could not clone the plugin repository. Dependencies not satisfied. Hyprpm requires: cmake, meson, cpio, pkg-config, git, g++, gcc"));
|
||||
if (!validArg(url) || !validArg(rev)) {
|
||||
std::println(stderr, "\n{}", failureString("url or rev invalid"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (DataState::pluginRepoExists(url)) {
|
||||
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"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (DataState::pluginRepoExists(SPluginRepoIdentifier::fromUrl(url))) {
|
||||
std::println(stderr, "\n{}", failureString("Could not clone the plugin repository. Repository already installed."));
|
||||
return false;
|
||||
}
|
||||
|
|
@ -161,7 +163,7 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
|
|||
DataState::updateGlobalState(GLOBALSTATE);
|
||||
}
|
||||
|
||||
if (GLOBALSTATE.headersHashCompiled.empty()) {
|
||||
if (GLOBALSTATE.headersAbiCompiled.empty()) {
|
||||
std::println("\n{}", failureString("Cannot find headers in the global state. Try running hyprpm update first."));
|
||||
return false;
|
||||
}
|
||||
|
|
@ -205,7 +207,7 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
|
|||
|
||||
progress.printMessageAbove(infoString("Cloning {}", url));
|
||||
|
||||
std::string ret = execAndGet(std::format("cd {} && git clone --recursive {} {}", getTempRoot(), url, USERNAME));
|
||||
std::string ret = execAndGet(std::format("cd {} && git clone --recursive '{}' {}", getTempRoot(), url, USERNAME));
|
||||
|
||||
if (!std::filesystem::exists(m_szWorkingPluginDirectory + "/.git")) {
|
||||
std::println(stderr, "\n{}", failureString("Could not clone the plugin repository. shell returned:\n{}", ret));
|
||||
|
|
@ -312,8 +314,14 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
|
|||
progress.printMessageAbove(infoString("Building {}", p.name));
|
||||
|
||||
for (auto const& bs : p.buildSteps) {
|
||||
const std::string& cmd = std::format("cd {} && PKG_CONFIG_PATH=\"{}/share/pkgconfig\" {}", m_szWorkingPluginDirectory, DataState::getHeadersPath(), bs);
|
||||
out += " -> " + cmd + "\n" + execAndGet(cmd) + "\n";
|
||||
const auto CMD_RAW = nixDevelopIfNeeded(std::format("cd {} && PKG_CONFIG_PATH=\"{}\" {}", m_szWorkingPluginDirectory, getPkgConfigPath(), bs), HLVER);
|
||||
|
||||
if (!CMD_RAW) {
|
||||
progress.printMessageAbove(failureString("Failed to build {}: {}", p.name, CMD_RAW.error()));
|
||||
break;
|
||||
}
|
||||
|
||||
out += " -> " + *CMD_RAW + "\n" + execAndGet(*CMD_RAW) + "\n";
|
||||
}
|
||||
|
||||
if (m_bVerbose)
|
||||
|
|
@ -343,10 +351,13 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
|
|||
std::string repohash = execAndGet("cd " + m_szWorkingPluginDirectory + " && git rev-parse HEAD");
|
||||
if (repohash.length() > 0)
|
||||
repohash.pop_back();
|
||||
repo.name = pManifest->m_repository.name.empty() ? url.substr(url.find_last_of('/') + 1) : pManifest->m_repository.name;
|
||||
repo.url = url;
|
||||
repo.rev = rev;
|
||||
repo.hash = repohash;
|
||||
auto lastSlash = url.find_last_of('/');
|
||||
auto secondLastSlash = url.find_last_of('/', lastSlash - 1);
|
||||
repo.name = pManifest->m_repository.name.empty() ? url.substr(lastSlash + 1) : pManifest->m_repository.name;
|
||||
repo.author = url.substr(secondLastSlash + 1, lastSlash - secondLastSlash - 1);
|
||||
repo.url = url;
|
||||
repo.rev = rev;
|
||||
repo.hash = repohash;
|
||||
for (auto const& p : pManifest->m_plugins) {
|
||||
repo.plugins.push_back(SPlugin{p.name, m_szWorkingPluginDirectory + "/" + p.output, false, p.failed});
|
||||
}
|
||||
|
|
@ -366,13 +377,13 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string&
|
|||
return true;
|
||||
}
|
||||
|
||||
bool CPluginManager::removePluginRepo(const std::string& urlOrName) {
|
||||
if (!DataState::pluginRepoExists(urlOrName)) {
|
||||
bool CPluginManager::removePluginRepo(const SPluginRepoIdentifier identifier) {
|
||||
if (!DataState::pluginRepoExists(identifier)) {
|
||||
std::println(stderr, "\n{}", failureString("Could not remove the repository. Repository is not installed."));
|
||||
return false;
|
||||
}
|
||||
|
||||
std::cout << Colors::YELLOW << "!" << Colors::RESET << Colors::RED << " removing a plugin repository: " << Colors::RESET << urlOrName << "\n "
|
||||
std::cout << Colors::YELLOW << "!" << Colors::RESET << Colors::RED << " removing a plugin repository: " << Colors::RESET << identifier.toString() << "\n "
|
||||
<< "Are you sure? [Y/n] ";
|
||||
std::fflush(stdout);
|
||||
std::string input;
|
||||
|
|
@ -383,7 +394,7 @@ bool CPluginManager::removePluginRepo(const std::string& urlOrName) {
|
|||
return false;
|
||||
}
|
||||
|
||||
DataState::removePluginRepo(urlOrName);
|
||||
DataState::removePluginRepo(identifier);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -395,7 +406,7 @@ eHeadersErrors CPluginManager::headersValid() {
|
|||
return HEADERS_MISSING;
|
||||
|
||||
// find headers commit
|
||||
const std::string& cmd = std::format("PKG_CONFIG_PATH=\"{}/share/pkgconfig\" pkgconf --cflags --keep-system-cflags hyprland", DataState::getHeadersPath());
|
||||
const std::string& cmd = std::format("PKG_CONFIG_PATH=\"{}\" pkgconf --cflags --keep-system-cflags hyprland", getPkgConfigPath());
|
||||
auto headers = execAndGet(cmd);
|
||||
|
||||
if (!headers.contains("-I/"))
|
||||
|
|
@ -444,17 +455,22 @@ eHeadersErrors CPluginManager::headersValid() {
|
|||
if (hash != HLVER.hash)
|
||||
return HEADERS_MISMATCHED;
|
||||
|
||||
// check ABI hash too
|
||||
const auto GLOBALSTATE = DataState::getGlobalState();
|
||||
|
||||
if (GLOBALSTATE.headersAbiCompiled != HLVER.abiHash)
|
||||
return HEADERS_ABI_MISMATCH;
|
||||
|
||||
return HEADERS_OK;
|
||||
}
|
||||
|
||||
bool CPluginManager::updateHeaders(bool force) {
|
||||
|
||||
DataState::ensureStateStoreExists();
|
||||
|
||||
const auto HLVER = getHyprlandVersion(false);
|
||||
|
||||
if (!hasDeps()) {
|
||||
std::println("\n{}", failureString("Could not update. Dependencies not satisfied. Hyprpm requires: cmake, meson, cpio, pkg-config, git, g++, gcc"));
|
||||
std::println("\n{}", failureString("Could not update. Dependencies not satisfied. Hyprpm requires: cmake, cpio, pkg-config, git, g++, gcc"));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -496,11 +512,11 @@ bool CPluginManager::updateHeaders(bool force) {
|
|||
progress.printMessageAbove(verboseString("will shallow since: {}", SHALLOW_DATE));
|
||||
|
||||
std::string ret =
|
||||
execAndGet(std::format("cd {} && git clone --recursive {} hyprland-{}{}", getTempRoot(), HL_URL, USERNAME, (bShallow ? " --shallow-since='" + SHALLOW_DATE + "'" : "")));
|
||||
execAndGet(std::format("cd {} && git clone --recursive '{}' hyprland-{}{}", getTempRoot(), HL_URL, USERNAME, (bShallow ? " --shallow-since='" + SHALLOW_DATE + "'" : "")));
|
||||
|
||||
if (!std::filesystem::exists(WORKINGDIR)) {
|
||||
progress.printMessageAbove(failureString("Clone failed. Retrying without shallow."));
|
||||
ret = execAndGet(std::format("cd {} && git clone --recursive {} hyprland-{}", getTempRoot(), HL_URL, USERNAME));
|
||||
ret = execAndGet(std::format("cd {} && git clone --recursive '{}' hyprland-{}", getTempRoot(), HL_URL, USERNAME));
|
||||
}
|
||||
|
||||
if (!std::filesystem::exists(WORKINGDIR + "/.git")) {
|
||||
|
|
@ -543,8 +559,17 @@ bool CPluginManager::updateHeaders(bool force) {
|
|||
if (m_bVerbose)
|
||||
progress.printMessageAbove(verboseString("setting PREFIX for cmake to {}", DataState::getHeadersPath()));
|
||||
|
||||
ret = execAndGet(std::format("cd {} && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:STRING=\"{}\" -S . -B ./build -G Ninja", WORKINGDIR,
|
||||
DataState::getHeadersPath()));
|
||||
const auto CONFIGURE_CMD =
|
||||
nixDevelopIfNeeded(std::format("cd {} && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:STRING=\"{}\" -S . -B ./build", WORKINGDIR,
|
||||
DataState::getHeadersPath()),
|
||||
HLVER);
|
||||
|
||||
if (!CONFIGURE_CMD) {
|
||||
std::println(stderr, "\n{}", failureString("Could not configure hyprland: {}", CONFIGURE_CMD.error()));
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = execAndGet(*CONFIGURE_CMD);
|
||||
if (m_bVerbose)
|
||||
progress.printMessageAbove(verboseString("cmake returned: {}", ret));
|
||||
|
||||
|
|
@ -589,14 +614,15 @@ bool CPluginManager::updateHeaders(bool force) {
|
|||
std::filesystem::remove_all(WORKINGDIR);
|
||||
|
||||
auto HEADERSVALID = headersValid();
|
||||
if (HEADERSVALID == HEADERS_OK) {
|
||||
|
||||
if (HEADERSVALID == HEADERS_OK || HEADERSVALID == HEADERS_MISMATCHED || HEADERSVALID == HEADERS_ABI_MISMATCH) {
|
||||
progress.printMessageAbove(successString("installed headers"));
|
||||
progress.m_iSteps = 5;
|
||||
progress.m_szCurrentMessage = "Done!";
|
||||
progress.print();
|
||||
|
||||
auto GLOBALSTATE = DataState::getGlobalState();
|
||||
GLOBALSTATE.headersHashCompiled = HLVER.hash;
|
||||
auto GLOBALSTATE = DataState::getGlobalState();
|
||||
GLOBALSTATE.headersAbiCompiled = HLVER.abiHash;
|
||||
DataState::updateGlobalState(GLOBALSTATE);
|
||||
|
||||
std::print("\n");
|
||||
|
|
@ -631,7 +657,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
|
|||
const auto HLVER = getHyprlandVersion(false);
|
||||
|
||||
CProgressBar progress;
|
||||
progress.m_iMaxSteps = REPOS.size() * 2 + 2;
|
||||
progress.m_iMaxSteps = (REPOS.size() * 2) + 2;
|
||||
progress.m_iSteps = 0;
|
||||
progress.m_szCurrentMessage = "Updating repositories";
|
||||
progress.print();
|
||||
|
|
@ -652,7 +678,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
|
|||
|
||||
progress.printMessageAbove(infoString("Cloning {}", repo.url));
|
||||
|
||||
std::string ret = execAndGet(std::format("cd {} && git clone --recursive {} {}", getTempRoot(), repo.url, USERNAME));
|
||||
std::string ret = execAndGet(std::format("cd {} && git clone --recursive '{}' {}", getTempRoot(), repo.url, USERNAME));
|
||||
|
||||
if (!std::filesystem::exists(m_szWorkingPluginDirectory + "/.git")) {
|
||||
std::println("{}", failureString("could not clone repo: shell returned: {}", ret));
|
||||
|
|
@ -662,7 +688,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
|
|||
if (!repo.rev.empty()) {
|
||||
progress.printMessageAbove(infoString("Plugin has revision set, resetting: {}", repo.rev));
|
||||
|
||||
std::string ret = execAndGet("git -C " + m_szWorkingPluginDirectory + " reset --hard --recurse-submodules " + repo.rev);
|
||||
std::string ret = execAndGet("git -C " + m_szWorkingPluginDirectory + " reset --hard --recurse-submodules \'" + repo.rev + "\'");
|
||||
if (ret.compare(0, 6, "fatal:") == 0) {
|
||||
std::println(stderr, "\n{}", failureString("could not check out revision {}: shell returned:\n{}", repo.rev, ret));
|
||||
|
||||
|
|
@ -741,8 +767,14 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
|
|||
progress.printMessageAbove(infoString("Building {}", p.name));
|
||||
|
||||
for (auto const& bs : p.buildSteps) {
|
||||
const std::string& cmd = std::format("cd {} && PKG_CONFIG_PATH=\"{}/share/pkgconfig\" {}", m_szWorkingPluginDirectory, DataState::getHeadersPath(), bs);
|
||||
out += " -> " + cmd + "\n" + execAndGet(cmd) + "\n";
|
||||
const auto CMD_RAW = nixDevelopIfNeeded(std::format("cd {} && PKG_CONFIG_PATH=\"{}\" {}", m_szWorkingPluginDirectory, getPkgConfigPath(), bs), HLVER);
|
||||
|
||||
if (!CMD_RAW) {
|
||||
progress.printMessageAbove(failureString("Failed to build {}: {}", p.name, CMD_RAW.error()));
|
||||
break;
|
||||
}
|
||||
|
||||
out += " -> " + *CMD_RAW + "\n" + execAndGet(*CMD_RAW) + "\n";
|
||||
}
|
||||
|
||||
if (m_bVerbose)
|
||||
|
|
@ -772,10 +804,10 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
|
|||
repohash.pop_back();
|
||||
newrepo.hash = repohash;
|
||||
for (auto const& p : pManifest->m_plugins) {
|
||||
const auto OLDPLUGINIT = std::find_if(repo.plugins.begin(), repo.plugins.end(), [&](const auto& other) { return other.name == p.name; });
|
||||
newrepo.plugins.push_back(SPlugin{p.name, m_szWorkingPluginDirectory + "/" + p.output, OLDPLUGINIT != repo.plugins.end() ? OLDPLUGINIT->enabled : false});
|
||||
const auto OLDPLUGINIT = std::ranges::find_if(repo.plugins, [&](const auto& other) { return other.name == p.name; });
|
||||
newrepo.plugins.emplace_back(SPlugin{p.name, m_szWorkingPluginDirectory + "/" + p.output, OLDPLUGINIT != repo.plugins.end() ? OLDPLUGINIT->enabled : false});
|
||||
}
|
||||
DataState::removePluginRepo(newrepo.name);
|
||||
DataState::removePluginRepo(SPluginRepoIdentifier::fromName(newrepo.name));
|
||||
DataState::addNewPluginRepo(newrepo);
|
||||
|
||||
std::filesystem::remove_all(m_szWorkingPluginDirectory);
|
||||
|
|
@ -787,8 +819,8 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
|
|||
progress.m_szCurrentMessage = "Updating global state...";
|
||||
progress.print();
|
||||
|
||||
auto GLOBALSTATE = DataState::getGlobalState();
|
||||
GLOBALSTATE.headersHashCompiled = HLVER.hash;
|
||||
auto GLOBALSTATE = DataState::getGlobalState();
|
||||
GLOBALSTATE.headersAbiCompiled = HLVER.abiHash;
|
||||
DataState::updateGlobalState(GLOBALSTATE);
|
||||
|
||||
progress.m_iSteps++;
|
||||
|
|
@ -800,17 +832,23 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool CPluginManager::enablePlugin(const std::string& name) {
|
||||
bool ret = DataState::setPluginEnabled(name, true);
|
||||
bool CPluginManager::enablePlugin(const SPluginRepoIdentifier identifier) {
|
||||
bool ret = false;
|
||||
|
||||
switch (identifier.type) {
|
||||
case IDENTIFIER_NAME:
|
||||
case IDENTIFIER_AUTHOR_NAME: ret = DataState::setPluginEnabled(identifier, true); break;
|
||||
default: return false;
|
||||
}
|
||||
if (ret)
|
||||
std::println("{}", successString("Enabled {}", name));
|
||||
std::println("{}", successString("Enabled {}", identifier.name));
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool CPluginManager::disablePlugin(const std::string& name) {
|
||||
bool ret = DataState::setPluginEnabled(name, false);
|
||||
bool CPluginManager::disablePlugin(const SPluginRepoIdentifier identifier) {
|
||||
bool ret = DataState::setPluginEnabled(identifier, false);
|
||||
if (ret)
|
||||
std::println("{}", successString("Disabled {}", name));
|
||||
std::println("{}", successString("Disabled {}", identifier.name));
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -828,7 +866,7 @@ ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState(bool forceReload)
|
|||
}
|
||||
const auto HYPRPMPATH = DataState::getDataStatePath();
|
||||
|
||||
const auto json = glz::read_json<glz::json_t::array_t>(NHyprlandSocket::send("j/plugins list"));
|
||||
const auto json = glz::read_json<glz::generic::array_t>(NHyprlandSocket::send("j/plugins list"));
|
||||
if (!json) {
|
||||
std::println(stderr, "PluginManager: couldn't parse plugin list output");
|
||||
return LOADSTATE_FAIL;
|
||||
|
|
@ -893,7 +931,7 @@ ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState(bool forceReload)
|
|||
if (!p.enabled)
|
||||
continue;
|
||||
|
||||
if (!forceReload && std::find_if(loadedPlugins.begin(), loadedPlugins.end(), [&](const auto& other) { return other == p.name; }) != loadedPlugins.end())
|
||||
if (!forceReload && std::ranges::find_if(loadedPlugins, [&](const auto& other) { return other == p.name; }) != loadedPlugins.end())
|
||||
continue;
|
||||
|
||||
if (!loadUnloadPlugin(HYPRPMPATH / repoForName(p.name) / p.filename, true)) {
|
||||
|
|
@ -913,9 +951,9 @@ bool CPluginManager::loadUnloadPlugin(const std::string& path, bool load) {
|
|||
auto state = DataState::getGlobalState();
|
||||
auto HLVER = getHyprlandVersion(true);
|
||||
|
||||
if (state.headersHashCompiled != HLVER.hash) {
|
||||
if (state.headersAbiCompiled != HLVER.abiHash) {
|
||||
if (load)
|
||||
std::println("{}", infoString("Running Hyprland version ({}) differs from plugin state ({}), please restart Hyprland.", HLVER.hash, state.headersHashCompiled));
|
||||
std::println("{}", infoString("Running Hyprland version ({}) differs from plugin state ({}), please restart Hyprland.", HLVER.hash, state.headersAbiCompiled));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -931,7 +969,7 @@ void CPluginManager::listAllPlugins() {
|
|||
const auto REPOS = DataState::getAllRepositories();
|
||||
|
||||
for (auto const& r : REPOS) {
|
||||
std::println("{}", infoString("Repository {}:", r.name));
|
||||
std::println("{}", infoString("Repository {} (by {}):", r.name, r.author));
|
||||
|
||||
for (auto const& p : r.plugins) {
|
||||
std::println(" │ Plugin {}", p.name);
|
||||
|
|
@ -956,6 +994,7 @@ std::string CPluginManager::headerError(const eHeadersErrors err) {
|
|||
case HEADERS_MISMATCHED: return failureString("Headers version mismatch. Please run hyprpm update to fix those.\n");
|
||||
case HEADERS_NOT_HYPRLAND: return failureString("It doesn't seem you are running on hyprland.\n");
|
||||
case HEADERS_MISSING: return failureString("Headers missing. Please run hyprpm update to fix those.\n");
|
||||
case HEADERS_ABI_MISMATCH: return failureString("ABI is mismatched. Please run hyprpm update to fix that.\n");
|
||||
case HEADERS_DUPLICATED: {
|
||||
return failureString("Headers duplicated!!! This is a very bad sign.\n"
|
||||
"This could be due to e.g. installing hyprland manually while a system package of hyprland is also installed.\n"
|
||||
|
|
@ -980,8 +1019,11 @@ std::string CPluginManager::headerErrorShort(const eHeadersErrors err) {
|
|||
}
|
||||
|
||||
bool CPluginManager::hasDeps() {
|
||||
if (!m_bNoNix && getHyprlandVersion().isNix)
|
||||
return true; // dep check not needed if we are on nix
|
||||
|
||||
bool hasAllDeps = true;
|
||||
std::vector<std::string> deps = {"meson", "cpio", "cmake", "pkg-config", "g++", "gcc", "git"};
|
||||
std::vector<std::string> deps = {"cpio", "cmake", "pkg-config", "g++", "gcc", "git"};
|
||||
|
||||
for (auto const& d : deps) {
|
||||
if (!execAndGet("command -v " + d).contains("/")) {
|
||||
|
|
@ -992,3 +1034,92 @@ bool CPluginManager::hasDeps() {
|
|||
|
||||
return hasAllDeps;
|
||||
}
|
||||
|
||||
const std::string& CPluginManager::getPkgConfigPath() {
|
||||
static const auto str = std::format("{}/share/pkgconfig:$PKG_CONFIG_PATH", DataState::getHeadersPath());
|
||||
return str;
|
||||
}
|
||||
|
||||
static std::expected<std::string, std::string> getNixDevelopFromPath(const std::string& argv0) {
|
||||
std::string fullStorePath;
|
||||
|
||||
if (argv0.starts_with("/")) {
|
||||
// we can use this directly
|
||||
fullStorePath = argv0;
|
||||
} else {
|
||||
// use hyprpm, find in path
|
||||
auto exe = NSys::findInPath("hyprpm");
|
||||
if (!exe)
|
||||
return std::unexpected("hyprpm not found in PATH");
|
||||
|
||||
fullStorePath = *exe;
|
||||
}
|
||||
|
||||
if (fullStorePath.empty() || !fullStorePath.ends_with("/bin/hyprpm"))
|
||||
return std::unexpected("couldn't get a real path for hyprpm (1)");
|
||||
|
||||
// canonicalize to get the real nix-store path
|
||||
std::error_code ec;
|
||||
fullStorePath = std::filesystem::canonical(fullStorePath, ec);
|
||||
|
||||
if (ec || fullStorePath.empty() || !fullStorePath.starts_with("/nix"))
|
||||
return std::unexpected("couldn't get a real path for hyprpm");
|
||||
|
||||
fullStorePath = fullStorePath.substr(0, fullStorePath.length() - std::string_view{"/bin/hyprpm"}.length());
|
||||
|
||||
auto deriver = trim(execAndGet(std::format("echo \"$(nix-store --query --deriver '{}')\"", fullStorePath)));
|
||||
|
||||
if (deriver.starts_with("unknown"))
|
||||
return std::unexpected("couldn't nix deriver");
|
||||
|
||||
return deriver;
|
||||
}
|
||||
|
||||
static std::expected<std::string, std::string> getNixDevelopFromProfile() {
|
||||
const auto NIX_PROFILE_STR = execAndGet("nix profile list --json");
|
||||
|
||||
auto rawJson = glz::read_json<glz::generic>(NIX_PROFILE_STR);
|
||||
|
||||
if (!rawJson)
|
||||
return std::unexpected("failed to parse nix profile list --json");
|
||||
|
||||
auto& json = *rawJson;
|
||||
|
||||
if (!json.contains("elements") || !json["elements"].is_object())
|
||||
return std::unexpected("nix profile list --json returned a wonky json");
|
||||
|
||||
if (!json["elements"].contains("hyprland") && !json["elements"].contains("Hyprland"))
|
||||
return std::unexpected("nix profile list --json doesn't contain Hyprland (did you uninstall?)");
|
||||
|
||||
auto& hyprlandJson = json["elements"].contains("hyprland") ? json["elements"]["hyprland"] : json["elements"]["Hyprland"];
|
||||
|
||||
if (!hyprlandJson.contains("originalUrl"))
|
||||
return std::unexpected("nix profile list --json's hyprland doesn't contain originalUrl?");
|
||||
|
||||
return hyprlandJson["originalUrl"].get_string();
|
||||
}
|
||||
|
||||
std::expected<std::string, std::string> CPluginManager::nixDevelopIfNeeded(const std::string& cmd, const SHyprlandVersion& ver) {
|
||||
if (m_bNoNix || !ver.isNix)
|
||||
return cmd;
|
||||
|
||||
// Escape single quotes
|
||||
std::string newCmd = cmd;
|
||||
replaceInString(newCmd, "'", "\\'");
|
||||
|
||||
auto NIX_DEVELOP = getNixDevelopFromPath(m_szArgv0);
|
||||
|
||||
if (NIX_DEVELOP)
|
||||
return std::format("nix develop '{}' --command bash -c $'{}'", *NIX_DEVELOP, newCmd);
|
||||
else if (m_bVerbose)
|
||||
std::println("{}", verboseString("Failed nix from path: {}", NIX_DEVELOP.error()));
|
||||
|
||||
NIX_DEVELOP = getNixDevelopFromProfile();
|
||||
|
||||
if (NIX_DEVELOP)
|
||||
return std::format("nix develop '{}' --command bash -c $'{}'", *NIX_DEVELOP, newCmd);
|
||||
else if (m_bVerbose)
|
||||
std::println("{}", verboseString("Failed nix from profile: {}", NIX_DEVELOP.error()));
|
||||
|
||||
return std::unexpected("hyprland is nix, but hyprpm failed to obtain a nix develop shell for build cmd");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@
|
|||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <expected>
|
||||
#include "Plugin.hpp"
|
||||
|
||||
enum eHeadersErrors {
|
||||
HEADERS_OK = 0,
|
||||
|
|
@ -11,6 +13,7 @@ enum eHeadersErrors {
|
|||
HEADERS_MISSING,
|
||||
HEADERS_CORRUPTED,
|
||||
HEADERS_MISMATCHED,
|
||||
HEADERS_ABI_MISMATCH,
|
||||
HEADERS_DUPLICATED
|
||||
};
|
||||
|
||||
|
|
@ -36,7 +39,9 @@ struct SHyprlandVersion {
|
|||
std::string branch;
|
||||
std::string hash;
|
||||
std::string date;
|
||||
std::string abiHash;
|
||||
int commits = 0;
|
||||
bool isNix = false;
|
||||
};
|
||||
|
||||
class CPluginManager {
|
||||
|
|
@ -44,7 +49,7 @@ class CPluginManager {
|
|||
CPluginManager();
|
||||
|
||||
bool addNewPluginRepo(const std::string& url, const std::string& rev);
|
||||
bool removePluginRepo(const std::string& urlOrName);
|
||||
bool removePluginRepo(const SPluginRepoIdentifier identifier);
|
||||
|
||||
eHeadersErrors headersValid();
|
||||
bool updateHeaders(bool force = false);
|
||||
|
|
@ -52,8 +57,8 @@ class CPluginManager {
|
|||
|
||||
void listAllPlugins();
|
||||
|
||||
bool enablePlugin(const std::string& name);
|
||||
bool disablePlugin(const std::string& name);
|
||||
bool enablePlugin(const SPluginRepoIdentifier identifier);
|
||||
bool disablePlugin(const SPluginRepoIdentifier identifier);
|
||||
ePluginLoadStateReturn ensurePluginsLoadState(bool forceReload = false);
|
||||
|
||||
bool loadUnloadPlugin(const std::string& path, bool load);
|
||||
|
|
@ -61,20 +66,26 @@ class CPluginManager {
|
|||
|
||||
void notify(const eNotifyIcons icon, uint32_t color, int durationMs, const std::string& message);
|
||||
|
||||
const std::string& getPkgConfigPath();
|
||||
|
||||
bool hasDeps();
|
||||
|
||||
bool m_bVerbose = false;
|
||||
bool m_bNoShallow = false;
|
||||
std::string m_szCustomHlUrl, m_szUsername;
|
||||
bool m_bNoNix = false;
|
||||
std::string m_szCustomHlUrl, m_szUsername, m_szArgv0;
|
||||
|
||||
// will delete recursively if exists!!
|
||||
bool createSafeDirectory(const std::string& path);
|
||||
|
||||
private:
|
||||
std::string headerError(const eHeadersErrors err);
|
||||
std::string headerErrorShort(const eHeadersErrors err);
|
||||
std::string headerError(const eHeadersErrors err);
|
||||
std::string headerErrorShort(const eHeadersErrors err);
|
||||
bool validArg(const std::string& s);
|
||||
|
||||
std::string m_szWorkingPluginDirectory;
|
||||
std::expected<std::string, std::string> nixDevelopIfNeeded(const std::string& cmd, const SHyprlandVersion& ver);
|
||||
|
||||
std::string m_szWorkingPluginDirectory;
|
||||
};
|
||||
|
||||
inline std::unique_ptr<CPluginManager> g_pPluginManager;
|
||||
|
|
|
|||
|
|
@ -35,9 +35,13 @@ static std::string validSubinsAsStr() {
|
|||
}
|
||||
|
||||
static bool executableExistsInPath(const std::string& exe) {
|
||||
return NSys::findInPath(exe).has_value();
|
||||
}
|
||||
|
||||
std::optional<std::string> NSys::findInPath(const std::string& exe) {
|
||||
const char* PATHENV = std::getenv("PATH");
|
||||
if (!PATHENV)
|
||||
return false;
|
||||
return std::nullopt;
|
||||
|
||||
CVarList paths(PATHENV, 0, ':', true);
|
||||
std::error_code ec;
|
||||
|
|
@ -52,10 +56,10 @@ static bool executableExistsInPath(const std::string& exe) {
|
|||
if (ec)
|
||||
continue;
|
||||
if ((perms & std::filesystem::perms::others_exec) != std::filesystem::perms::none)
|
||||
return true;
|
||||
return candidate.string();
|
||||
}
|
||||
|
||||
return false;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static std::string subin() {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
namespace NSys {
|
||||
bool isSuperuser();
|
||||
int getUID();
|
||||
int getEUID();
|
||||
bool isSuperuser();
|
||||
int getUID();
|
||||
int getEUID();
|
||||
std::optional<std::string> findInPath(const std::string& exe);
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
namespace root {
|
||||
|
|
@ -20,4 +22,4 @@ namespace NSys {
|
|||
// Do not use this unless absolutely necessary!
|
||||
std::string runAsSuperuserUnsafe(const std::string& cmd);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,25 +13,26 @@ using namespace Hyprutils::Utils;
|
|||
|
||||
constexpr std::string_view HELP = R"#(┏ hyprpm, a Hyprland Plugin Manager
|
||||
┃
|
||||
┣ add [url] [git rev] → Install a new plugin repository from git. Git revision
|
||||
┃ is optional, when set, commit locks are ignored.
|
||||
┣ remove [url/name] → Remove an installed plugin repository.
|
||||
┣ enable [name] → Enable a plugin.
|
||||
┣ disable [name] → Disable a plugin.
|
||||
┣ update → Check and update all plugins if needed.
|
||||
┣ reload → Reload hyprpm state. Ensure all enabled plugins are loaded.
|
||||
┣ list → List all installed plugins.
|
||||
┣ purge-cache → Remove the entire hyprpm cache, built plugins, hyprpm settings and headers.
|
||||
┣ add <url> [git rev] → Install a new plugin repository from git. Git revision
|
||||
┃ is optional, when set, commit locks are ignored.
|
||||
┣ remove <url|name|author/name> → Remove an installed plugin repository.
|
||||
┣ enable <name|author/name> → Enable a plugin.
|
||||
┣ disable <name|author/name> → Disable a plugin.
|
||||
┣ update → Check and update all plugins if needed.
|
||||
┣ reload → Reload hyprpm state. Ensure all enabled plugins are loaded.
|
||||
┣ list → List all installed plugins.
|
||||
┣ purge-cache → Remove the entire hyprpm cache, built plugins, hyprpm settings and headers.
|
||||
┃
|
||||
┣ Flags:
|
||||
┃
|
||||
┣ --notify | -n → Send a hyprland notification confirming successful plugin load.
|
||||
┃ Warnings/Errors trigger notifications regardless of this flag.
|
||||
┣ --help | -h → Show this menu.
|
||||
┣ --verbose | -v → Enable too much logging.
|
||||
┣ --force | -f → Force an operation ignoring checks (e.g. update -f).
|
||||
┣ --no-shallow | -s → Disable shallow cloning of Hyprland sources.
|
||||
┣ --hl-url | → Pass a custom hyprland source url.
|
||||
┣ --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.
|
||||
┣ --verbose | -v → Enable too much logging.
|
||||
┣ --force | -f → Force an operation ignoring checks (e.g. update -f).
|
||||
┣ --no-shallow | -s → Disable shallow cloning of Hyprland sources.
|
||||
┣ --hl-url | → Pass a custom hyprland source url.
|
||||
┗
|
||||
)#";
|
||||
|
||||
|
|
@ -47,7 +48,7 @@ int main(int argc, char** argv, char** envp) {
|
|||
}
|
||||
|
||||
std::vector<std::string> command;
|
||||
bool notify = false, verbose = false, force = false, noShallow = false;
|
||||
bool notify = false, verbose = false, force = false, noShallow = false, noNix = false;
|
||||
std::string customHlUrl;
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
|
|
@ -63,6 +64,8 @@ int main(int argc, char** argv, char** envp) {
|
|||
g_pPluginManager->notify(ICON_INFO, 0, 10000, "[hyprpm] -n flag is deprecated, see hyprpm --help.");
|
||||
} else if (ARGS[i] == "--verbose" || ARGS[i] == "-v") {
|
||||
verbose = true;
|
||||
} else if (ARGS[i] == "--no-nix") {
|
||||
noNix = true;
|
||||
} else if (ARGS[i] == "--no-shallow" || ARGS[i] == "-s") {
|
||||
noShallow = true;
|
||||
} else if (ARGS[i] == "--hl-url") {
|
||||
|
|
@ -91,7 +94,9 @@ int main(int argc, char** argv, char** envp) {
|
|||
g_pPluginManager = std::make_unique<CPluginManager>();
|
||||
g_pPluginManager->m_bVerbose = verbose;
|
||||
g_pPluginManager->m_bNoShallow = noShallow;
|
||||
g_pPluginManager->m_bNoNix = noNix;
|
||||
g_pPluginManager->m_szCustomHlUrl = customHlUrl;
|
||||
g_pPluginManager->m_szArgv0 = argv[0];
|
||||
|
||||
if (command[0] == "add") {
|
||||
if (command.size() < 2) {
|
||||
|
|
@ -106,7 +111,7 @@ int main(int argc, char** argv, char** envp) {
|
|||
const auto HLVER = g_pPluginManager->getHyprlandVersion();
|
||||
auto GLOBALSTATE = DataState::getGlobalState();
|
||||
|
||||
if (GLOBALSTATE.headersHashCompiled != HLVER.hash) {
|
||||
if (GLOBALSTATE.headersAbiCompiled != HLVER.abiHash) {
|
||||
std::println(stderr, "{}", failureString("Headers outdated, please run hyprpm update."));
|
||||
return 1;
|
||||
}
|
||||
|
|
@ -126,7 +131,7 @@ int main(int argc, char** argv, char** envp) {
|
|||
NSys::root::cacheSudo();
|
||||
CScopeGuard x([] { NSys::root::dropSudo(); });
|
||||
|
||||
return g_pPluginManager->removePluginRepo(command[1]) ? 0 : 1;
|
||||
return g_pPluginManager->removePluginRepo(SPluginRepoIdentifier::fromString(command[1])) ? 0 : 1;
|
||||
} else if (command[0] == "update") {
|
||||
NSys::root::cacheSudo();
|
||||
CScopeGuard x([] { NSys::root::dropSudo(); });
|
||||
|
|
@ -137,7 +142,7 @@ int main(int argc, char** argv, char** envp) {
|
|||
if (headers) {
|
||||
const auto HLVER = g_pPluginManager->getHyprlandVersion(false);
|
||||
auto GLOBALSTATE = DataState::getGlobalState();
|
||||
const auto COMPILEDOUTDATED = HLVER.hash != GLOBALSTATE.headersHashCompiled;
|
||||
const auto COMPILEDOUTDATED = HLVER.abiHash != GLOBALSTATE.headersAbiCompiled;
|
||||
|
||||
bool ret1 = g_pPluginManager->updatePlugins(!headersValid || force || COMPILEDOUTDATED);
|
||||
|
||||
|
|
@ -160,7 +165,7 @@ int main(int argc, char** argv, char** envp) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
if (!g_pPluginManager->enablePlugin(command[1])) {
|
||||
if (!g_pPluginManager->enablePlugin(SPluginRepoIdentifier::fromString(command[1]))) {
|
||||
std::println(stderr, "{}", failureString("Couldn't enable plugin (missing?)"));
|
||||
return 1;
|
||||
}
|
||||
|
|
@ -181,7 +186,7 @@ int main(int argc, char** argv, char** envp) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
if (!g_pPluginManager->disablePlugin(command[1])) {
|
||||
if (!g_pPluginManager->disablePlugin(SPluginRepoIdentifier::fromString(command[1]))) {
|
||||
std::println(stderr, "{}", failureString("Couldn't disable plugin (missing?)"));
|
||||
return 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ pkg_check_modules(
|
|||
IMPORTED_TARGET
|
||||
hyprutils>=0.8.0
|
||||
wayland-client
|
||||
wayland-protocols
|
||||
wayland-protocols>=1.47
|
||||
)
|
||||
|
||||
pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir)
|
||||
|
|
@ -96,6 +96,9 @@ endfunction()
|
|||
|
||||
protocolnew("staging/pointer-warp" "pointer-warp-v1" false)
|
||||
protocolnew("stable/xdg-shell" "xdg-shell" false)
|
||||
protocolnew("unstable/keyboard-shortcuts-inhibit" "keyboard-shortcuts-inhibit-unstable-v1" false)
|
||||
|
||||
clientNew("pointer-warp" PROTOS "pointer-warp-v1" "xdg-shell")
|
||||
clientNew("pointer-scroll" PROTOS "xdg-shell")
|
||||
clientNew("child-window" PROTOS "xdg-shell")
|
||||
clientNew("shortcut-inhibitor" PROTOS "xdg-shell" "keyboard-shortcuts-inhibit-unstable-v1")
|
||||
|
|
|
|||
336
hyprtester/clients/child-window.cpp
Normal file
336
hyprtester/clients/child-window.cpp
Normal file
|
|
@ -0,0 +1,336 @@
|
|||
#include <print>
|
||||
#include <poll.h>
|
||||
#include <sys/mman.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <wayland-client.h>
|
||||
#include <wayland.hpp>
|
||||
#include <xdg-shell.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;
|
||||
|
||||
// shm/buffer stuff
|
||||
CSharedPointer<CCWlShmPool> shmPool;
|
||||
CSharedPointer<CCWlBuffer> shmBuf;
|
||||
CSharedPointer<CCWlBuffer> shmBuf2;
|
||||
int shmFd = 0;
|
||||
size_t shmBufSize = 0;
|
||||
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 = 0;
|
||||
};
|
||||
|
||||
bool debug, shouldExit, started;
|
||||
|
||||
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));
|
||||
}
|
||||
});
|
||||
state.registry->setGlobalRemove([](CCWlRegistry* r, uint32_t id) { debugLog("Global {} removed", id); });
|
||||
|
||||
wl_display_roundtrip(state.display);
|
||||
|
||||
if (!state.wlCompositor || !state.wlShm || !state.wlSeat || !state.xdgShell) {
|
||||
clientLog("Failed to get protocols from Hyprland");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool createShm(SWlState& state, Vector2D geom) {
|
||||
if (!state.xrgb8888_support)
|
||||
return false;
|
||||
|
||||
size_t stride = geom.x * 4;
|
||||
size_t size = geom.y * stride;
|
||||
if (!state.shmPool) {
|
||||
const char* name = "/wl-shm-pointer-warp";
|
||||
state.shmFd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
|
||||
if (state.shmFd < 0)
|
||||
return false;
|
||||
|
||||
if (shm_unlink(name) < 0 || ftruncate(state.shmFd, size * 2) < 0) {
|
||||
close(state.shmFd);
|
||||
return false;
|
||||
}
|
||||
|
||||
state.shmPool = makeShared<CCWlShmPool>(state.wlShm->sendCreatePool(state.shmFd, size * 2));
|
||||
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 * 2);
|
||||
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("child-test parent");
|
||||
state.xdgToplevel->sendSetAppId("child-test-parent");
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
struct SChildWindow {
|
||||
CSharedPointer<CCWlSurface> surface;
|
||||
CSharedPointer<CCXdgSurface> xSurface;
|
||||
CSharedPointer<CCXdgToplevel> toplevel;
|
||||
};
|
||||
|
||||
static void parseRequest(SWlState& state, std::string str, SChildWindow& window) {
|
||||
if (str.starts_with("exit")) {
|
||||
shouldExit = true;
|
||||
return;
|
||||
}
|
||||
|
||||
size_t index = str.find_first_of('\n');
|
||||
str = str.substr(0, index);
|
||||
|
||||
if (str == "toplevel") {
|
||||
window.surface = makeShared<CCWlSurface>(state.wlCompositor->sendCreateSurface());
|
||||
window.xSurface = makeShared<CCXdgSurface>(state.xdgShell->sendGetXdgSurface(window.surface->resource()));
|
||||
|
||||
window.xSurface->setConfigure([&](CCXdgSurface* p, uint32_t serial) {
|
||||
if (!state.shmBuf)
|
||||
debugLog("xdgSurf configure but no buf made yet?");
|
||||
|
||||
window.xSurface->sendSetWindowGeometry(0, 0, state.geom.x, state.geom.y);
|
||||
window.surface->sendAttach(state.shmBuf2.get(), 0, 0);
|
||||
window.surface->sendCommit();
|
||||
|
||||
window.xSurface->sendAckConfigure(serial);
|
||||
});
|
||||
|
||||
window.toplevel = makeShared<CCXdgToplevel>(window.xSurface->sendGetToplevel());
|
||||
|
||||
window.toplevel->setConfigure([&](CCXdgToplevel* p, int32_t w, int32_t h, wl_array* arr) {
|
||||
size_t stride = 1280 * 4;
|
||||
size_t size = 720 * stride;
|
||||
|
||||
auto buf = makeShared<CCWlBuffer>(state.shmPool->sendCreateBuffer(size, state.geom.x, state.geom.y, stride, WL_SHM_FORMAT_XRGB8888));
|
||||
if (!buf->resource())
|
||||
clientLog("Failed to create child buffer");
|
||||
|
||||
if (state.shmBuf2) {
|
||||
state.shmBuf2->sendDestroy();
|
||||
state.shmBuf2.reset();
|
||||
}
|
||||
state.shmBuf2 = buf;
|
||||
});
|
||||
|
||||
window.toplevel->sendSetTitle("child-test child");
|
||||
window.toplevel->sendSetAppId("child-test-child");
|
||||
window.toplevel->sendSetParent(state.xdgToplevel.get());
|
||||
|
||||
window.surface->sendAttach(nullptr, 0, 0);
|
||||
window.surface->sendCommit();
|
||||
clientLog("child started");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
SChildWindow window;
|
||||
|
||||
// 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()}, window);
|
||||
}
|
||||
}
|
||||
|
||||
wl_display* display = state.display;
|
||||
state = {};
|
||||
window = {};
|
||||
|
||||
wl_display_disconnect(display);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
#include <sys/poll.h>
|
||||
#include <sys/mman.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <print>
|
||||
#include <format>
|
||||
#include <string>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include <sys/poll.h>
|
||||
#include <sys/mman.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <print>
|
||||
#include <format>
|
||||
#include <string>
|
||||
|
|
|
|||
297
hyprtester/clients/shortcut-inhibitor.cpp
Normal file
297
hyprtester/clients/shortcut-inhibitor.cpp
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
#include <sys/poll.h>
|
||||
#include <sys/mman.h>
|
||||
#include <fcntl.h>
|
||||
#include <print>
|
||||
|
||||
#include <wayland-client.h>
|
||||
#include <wayland.hpp>
|
||||
#include <xdg-shell.hpp>
|
||||
#include <keyboard-shortcuts-inhibit-unstable-v1.hpp>
|
||||
|
||||
#include <hyprutils/memory/SharedPtr.hpp>
|
||||
#include <hyprutils/math/Vector2D.hpp>
|
||||
|
||||
using Hyprutils::Math::Vector2D;
|
||||
using namespace Hyprutils::Memory;
|
||||
|
||||
struct SWlState {
|
||||
wl_display* display;
|
||||
CSharedPointer<CCWlRegistry> registry;
|
||||
|
||||
// protocols
|
||||
CSharedPointer<CCWlCompositor> wlCompositor;
|
||||
CSharedPointer<CCWlSeat> wlSeat;
|
||||
CSharedPointer<CCWlShm> wlShm;
|
||||
CSharedPointer<CCXdgWmBase> xdgShell;
|
||||
CSharedPointer<CCZwpKeyboardShortcutsInhibitManagerV1> inhibitManager;
|
||||
|
||||
// shm/buffer stuff
|
||||
CSharedPointer<CCWlShmPool> shmPool;
|
||||
CSharedPointer<CCWlBuffer> shmBuf;
|
||||
int shmFd;
|
||||
size_t shmBufSize;
|
||||
bool xrgb8888_support = false;
|
||||
|
||||
// surface/toplevel stuff
|
||||
CSharedPointer<CCWlSurface> surf;
|
||||
CSharedPointer<CCXdgSurface> xdgSurf;
|
||||
CSharedPointer<CCXdgToplevel> xdgToplevel;
|
||||
Vector2D geom;
|
||||
|
||||
// pointer
|
||||
CSharedPointer<CCWlPointer> pointer;
|
||||
uint32_t enterSerial;
|
||||
|
||||
// shortcut inhibiting
|
||||
CSharedPointer<CCZwpKeyboardShortcutsInhibitorV1> inhibitor;
|
||||
};
|
||||
|
||||
static bool debug, started, shouldExit;
|
||||
|
||||
template <typename... Args>
|
||||
//NOLINTNEXTLINE
|
||||
static void clientLog(std::format_string<Args...> fmt, Args&&... args) {
|
||||
std::string text = std::vformat(fmt.get(), std::make_format_args(args...));
|
||||
std::println("{}", text);
|
||||
std::fflush(stdout);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
//NOLINTNEXTLINE
|
||||
static void debugLog(std::format_string<Args...> fmt, Args&&... args) {
|
||||
std::string text = std::vformat(fmt.get(), std::make_format_args(args...));
|
||||
if (!debug)
|
||||
return;
|
||||
std::println("{}", text);
|
||||
std::fflush(stdout);
|
||||
}
|
||||
|
||||
static bool bindRegistry(SWlState& state) {
|
||||
state.registry = makeShared<CCWlRegistry>((wl_proxy*)wl_display_get_registry(state.display));
|
||||
|
||||
state.registry->setGlobal([&](CCWlRegistry* r, uint32_t id, const char* name, uint32_t version) {
|
||||
const std::string NAME = name;
|
||||
if (NAME == "wl_compositor") {
|
||||
debugLog(" > binding to global: {} (version {}) with id {}", name, version, id);
|
||||
state.wlCompositor = makeShared<CCWlCompositor>((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wl_compositor_interface, 6));
|
||||
} else if (NAME == "wl_shm") {
|
||||
debugLog(" > binding to global: {} (version {}) with id {}", name, version, id);
|
||||
state.wlShm = makeShared<CCWlShm>((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wl_shm_interface, 1));
|
||||
} else if (NAME == "wl_seat") {
|
||||
debugLog(" > binding to global: {} (version {}) with id {}", name, version, id);
|
||||
state.wlSeat = makeShared<CCWlSeat>((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wl_seat_interface, 9));
|
||||
} else if (NAME == "xdg_wm_base") {
|
||||
debugLog(" > binding to global: {} (version {}) with id {}", name, version, id);
|
||||
state.xdgShell = makeShared<CCXdgWmBase>((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &xdg_wm_base_interface, 1));
|
||||
} else if (NAME == "zwp_keyboard_shortcuts_inhibit_manager_v1") {
|
||||
debugLog(" > binding to global: {} (version {}) with id {}", name, version, id);
|
||||
state.inhibitManager = makeShared<CCZwpKeyboardShortcutsInhibitManagerV1>(
|
||||
(wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1));
|
||||
}
|
||||
});
|
||||
state.registry->setGlobalRemove([](CCWlRegistry* r, uint32_t id) { debugLog("Global {} removed", id); });
|
||||
|
||||
wl_display_roundtrip(state.display);
|
||||
|
||||
if (!state.wlCompositor || !state.wlShm || !state.wlSeat || !state.xdgShell || !state.inhibitManager) {
|
||||
clientLog("Failed to get protocols from Hyprland");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool createShm(SWlState& state, Vector2D geom) {
|
||||
if (!state.xrgb8888_support)
|
||||
return false;
|
||||
|
||||
size_t stride = geom.x * 4;
|
||||
size_t size = geom.y * stride;
|
||||
if (!state.shmPool) {
|
||||
const char* name = "/wl-shm-shortcut-inhibitor";
|
||||
state.shmFd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
|
||||
if (state.shmFd < 0)
|
||||
return false;
|
||||
|
||||
if (shm_unlink(name) < 0 || ftruncate(state.shmFd, size) < 0) {
|
||||
close(state.shmFd);
|
||||
return false;
|
||||
}
|
||||
|
||||
state.shmPool = makeShared<CCWlShmPool>(state.wlShm->sendCreatePool(state.shmFd, size));
|
||||
if (!state.shmPool->resource()) {
|
||||
close(state.shmFd);
|
||||
state.shmFd = -1;
|
||||
state.shmPool.reset();
|
||||
return false;
|
||||
}
|
||||
state.shmBufSize = size;
|
||||
} else if (size > state.shmBufSize) {
|
||||
if (ftruncate(state.shmFd, size) < 0) {
|
||||
close(state.shmFd);
|
||||
state.shmFd = -1;
|
||||
state.shmPool.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
state.shmPool->sendResize(size);
|
||||
state.shmBufSize = size;
|
||||
}
|
||||
|
||||
auto buf = makeShared<CCWlBuffer>(state.shmPool->sendCreateBuffer(0, geom.x, geom.y, stride, WL_SHM_FORMAT_XRGB8888));
|
||||
if (!buf->resource())
|
||||
return false;
|
||||
|
||||
if (state.shmBuf) {
|
||||
state.shmBuf->sendDestroy();
|
||||
state.shmBuf.reset();
|
||||
}
|
||||
|
||||
state.shmBuf = buf;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool setupToplevel(SWlState& state) {
|
||||
state.wlShm->setFormat([&](CCWlShm* p, uint32_t format) {
|
||||
if (format == WL_SHM_FORMAT_XRGB8888)
|
||||
state.xrgb8888_support = true;
|
||||
});
|
||||
|
||||
state.xdgShell->setPing([&](CCXdgWmBase* p, uint32_t serial) { state.xdgShell->sendPong(serial); });
|
||||
|
||||
state.surf = makeShared<CCWlSurface>(state.wlCompositor->sendCreateSurface());
|
||||
if (!state.surf->resource())
|
||||
return false;
|
||||
|
||||
state.xdgSurf = makeShared<CCXdgSurface>(state.xdgShell->sendGetXdgSurface(state.surf->resource()));
|
||||
if (!state.xdgSurf->resource())
|
||||
return false;
|
||||
|
||||
state.xdgToplevel = makeShared<CCXdgToplevel>(state.xdgSurf->sendGetToplevel());
|
||||
if (!state.xdgToplevel->resource())
|
||||
return false;
|
||||
|
||||
state.xdgToplevel->setClose([&](CCXdgToplevel* p) { exit(0); });
|
||||
|
||||
state.xdgToplevel->setConfigure([&](CCXdgToplevel* p, int32_t w, int32_t h, wl_array* arr) {
|
||||
state.geom = {1280, 720};
|
||||
|
||||
if (!createShm(state, state.geom))
|
||||
exit(-1);
|
||||
});
|
||||
|
||||
state.xdgSurf->setConfigure([&](CCXdgSurface* p, uint32_t serial) {
|
||||
if (!state.shmBuf)
|
||||
debugLog("xdgSurf configure but no buf made yet?");
|
||||
|
||||
state.xdgSurf->sendSetWindowGeometry(0, 0, state.geom.x, state.geom.y);
|
||||
state.surf->sendAttach(state.shmBuf.get(), 0, 0);
|
||||
state.surf->sendCommit();
|
||||
|
||||
state.xdgSurf->sendAckConfigure(serial);
|
||||
|
||||
if (!started) {
|
||||
started = true;
|
||||
clientLog("started");
|
||||
}
|
||||
});
|
||||
|
||||
state.xdgToplevel->sendSetTitle("shortcut-inhibitor test client");
|
||||
state.xdgToplevel->sendSetAppId("shortcut-inhibitor");
|
||||
|
||||
state.surf->sendAttach(nullptr, 0, 0);
|
||||
state.surf->sendCommit();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool setupSeat(SWlState& state) {
|
||||
state.pointer = makeShared<CCWlPointer>(state.wlSeat->sendGetPointer());
|
||||
if (!state.pointer->resource())
|
||||
return false;
|
||||
|
||||
state.pointer->setEnter([&](CCWlPointer* p, uint32_t serial, wl_proxy* surf, wl_fixed_t x, wl_fixed_t y) {
|
||||
debugLog("Got pointer enter event, serial {}, x {}, y {}", serial, x, y);
|
||||
state.enterSerial = serial;
|
||||
});
|
||||
|
||||
state.pointer->setLeave([&](CCWlPointer* p, uint32_t serial, wl_proxy* surf) { debugLog("Got pointer leave event, serial {}", serial); });
|
||||
|
||||
state.pointer->setMotion([&](CCWlPointer* p, uint32_t serial, wl_fixed_t x, wl_fixed_t y) { debugLog("Got pointer motion event, serial {}, x {}, y {}", serial, x, y); });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void parseRequest(SWlState& state, std::string req) {
|
||||
if (req.starts_with("on")) {
|
||||
state.inhibitor = makeShared<CCZwpKeyboardShortcutsInhibitorV1>(state.inhibitManager->sendInhibitShortcuts(state.surf->resource(), state.wlSeat->resource()));
|
||||
|
||||
state.inhibitor->setActive([&](CCZwpKeyboardShortcutsInhibitorV1* inhibitorV1) { clientLog("inhibiting"); });
|
||||
state.inhibitor->setInactive([&](CCZwpKeyboardShortcutsInhibitorV1* inhibitorV1) { clientLog("inhibit disabled by compositor"); });
|
||||
} else if (req.starts_with("off")) {
|
||||
state.inhibitor->sendDestroy();
|
||||
state.inhibitor.reset();
|
||||
shouldExit = true;
|
||||
clientLog("inhibit disabled by request");
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc != 1 && argc != 2)
|
||||
clientLog("Only the \"--debug\" switch is allowed, it turns on debug logs.");
|
||||
|
||||
if (argc == 2 && std::string{argv[1]} == "--debug")
|
||||
debug = true;
|
||||
|
||||
SWlState state;
|
||||
|
||||
// WAYLAND_DISPLAY env should be set to the correct one
|
||||
state.display = wl_display_connect(nullptr);
|
||||
if (!state.display) {
|
||||
clientLog("Failed to connect to wayland display");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!bindRegistry(state) || !setupSeat(state) || !setupToplevel(state))
|
||||
return -1;
|
||||
|
||||
std::array<char, 1024> readBuf;
|
||||
readBuf.fill(0);
|
||||
|
||||
wl_display_flush(state.display);
|
||||
|
||||
struct pollfd fds[2] = {{.fd = wl_display_get_fd(state.display), .events = POLLIN | POLLOUT}, {.fd = STDIN_FILENO, .events = POLLIN}};
|
||||
while (!shouldExit && poll(fds, 2, 0) != -1) {
|
||||
if (fds[0].revents & POLLIN) {
|
||||
wl_display_flush(state.display);
|
||||
|
||||
if (wl_display_prepare_read(state.display) == 0) {
|
||||
wl_display_read_events(state.display);
|
||||
wl_display_dispatch_pending(state.display);
|
||||
} else
|
||||
wl_display_dispatch(state.display);
|
||||
|
||||
int ret = 0;
|
||||
do {
|
||||
ret = wl_display_dispatch_pending(state.display);
|
||||
wl_display_flush(state.display);
|
||||
} while (ret > 0);
|
||||
}
|
||||
|
||||
if (fds[1].revents & POLLIN) {
|
||||
ssize_t bytesRead = read(fds[1].fd, readBuf.data(), 1023);
|
||||
if (bytesRead == -1)
|
||||
continue;
|
||||
readBuf[bytesRead] = 0;
|
||||
|
||||
parseRequest(state, std::string{readBuf.data()});
|
||||
}
|
||||
}
|
||||
|
||||
wl_display* display = state.display;
|
||||
state = {};
|
||||
|
||||
wl_display_disconnect(display);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -4,17 +4,18 @@
|
|||
#include <any>
|
||||
|
||||
#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/config/legacy/ConfigManager.hpp>
|
||||
#include <src/config/supplementary/ConfigDescriptions.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>
|
||||
|
|
@ -33,7 +34,7 @@ static SDispatchResult test(std::string in) {
|
|||
bool success = true;
|
||||
std::string errors = "";
|
||||
|
||||
if (g_pConfigManager->m_configValueNumber != CONFIG_OPTIONS.size() + 1 /* autogenerated is special */) {
|
||||
if (Config::Legacy::mgr()->m_configValueNumber != Config::Supplementary::CONFIG_OPTIONS.size() + 1 /* autogenerated is special */) {
|
||||
errors += "config value number mismatches descriptions size\n";
|
||||
success = false;
|
||||
}
|
||||
|
|
@ -53,8 +54,9 @@ static SDispatchResult snapMove(std::string in) {
|
|||
Vector2D pos = PLASTWINDOW->m_realPosition->goal();
|
||||
Vector2D size = PLASTWINDOW->m_realSize->goal();
|
||||
|
||||
g_pLayoutManager->getCurrentLayout()->performSnap(pos, size, PLASTWINDOW, MBIND_MOVE, -1, size);
|
||||
*PLASTWINDOW->m_realPosition = pos.round();
|
||||
g_layoutManager->performSnap(pos, size, PLASTWINDOW->layoutTarget(), MBIND_MOVE, -1, size);
|
||||
|
||||
PLASTWINDOW->layoutTarget()->setPositionGlobal(CBox{pos, size});
|
||||
|
||||
return {};
|
||||
}
|
||||
|
|
@ -66,6 +68,8 @@ class CTestKeyboard : public IKeyboard {
|
|||
keeb->m_self = keeb;
|
||||
keeb->m_isVirtual = isVirtual;
|
||||
keeb->m_shareStates = !isVirtual;
|
||||
keeb->m_hlName = "test-keyboard";
|
||||
keeb->m_deviceName = "test-keyboard";
|
||||
return keeb;
|
||||
}
|
||||
|
||||
|
|
@ -213,7 +217,7 @@ static SDispatchResult scroll(std::string in) {
|
|||
by = std::stod(in);
|
||||
} catch (...) { return SDispatchResult{.success = false, .error = "invalid input"}; }
|
||||
|
||||
Debug::log(LOG, "tester: scrolling by {}", by);
|
||||
Log::logger->log(Log::DEBUG, "tester: scrolling by {}", by);
|
||||
|
||||
g_mouse->m_pointerEvents.axis.emit(IPointer::SAxisEvent{
|
||||
.delta = by,
|
||||
|
|
@ -224,8 +228,30 @@ static SDispatchResult scroll(std::string in) {
|
|||
return {};
|
||||
}
|
||||
|
||||
static SDispatchResult click(std::string in) {
|
||||
CVarList2 data(std::move(in));
|
||||
|
||||
uint32_t button;
|
||||
bool pressed;
|
||||
try {
|
||||
button = std::stoul(std::string{data[0]});
|
||||
pressed = std::stoul(std::string{data[1]}) == 1;
|
||||
} catch (...) { return {.success = false, .error = "invalid input"}; }
|
||||
|
||||
Log::logger->log(Log::DEBUG, "tester: mouse button {} state {}", button, pressed);
|
||||
|
||||
g_mouse->m_pointerEvents.button.emit(IPointer::SButtonEvent{
|
||||
.timeMs = sc<uint32_t>(Time::millis(Time::steadyNow())),
|
||||
.button = button,
|
||||
.state = pressed ? WL_POINTER_BUTTON_STATE_PRESSED : WL_POINTER_BUTTON_STATE_RELEASED,
|
||||
.mouse = true,
|
||||
});
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static SDispatchResult keybind(std::string in) {
|
||||
CVarList data(in);
|
||||
CVarList2 data(std::move(in));
|
||||
// 0 = release, 1 = press
|
||||
bool press;
|
||||
// See src/devices/IKeyboard.hpp : eKeyboardModifiers for modifier bitmasks
|
||||
|
|
@ -234,9 +260,9 @@ static SDispatchResult keybind(std::string in) {
|
|||
// keycode
|
||||
uint32_t key;
|
||||
try {
|
||||
press = std::stoul(data[0]) == 1;
|
||||
modifier = std::stoul(data[1]);
|
||||
key = std::stoul(data[2]) - 8; // xkb offset
|
||||
press = std::stoul(std::string{data[0]}) == 1;
|
||||
modifier = std::stoul(std::string{data[1]});
|
||||
key = std::stoul(std::string{data[2]}) - 8; // xkb offset
|
||||
} catch (...) { return {.success = false, .error = "invalid input"}; }
|
||||
|
||||
uint32_t modifierMask = 0;
|
||||
|
|
@ -248,32 +274,85 @@ static SDispatchResult keybind(std::string in) {
|
|||
return {};
|
||||
}
|
||||
|
||||
static Desktop::Rule::CWindowRuleEffectContainer::storageType ruleIDX = 0;
|
||||
static Desktop::Rule::CWindowRuleEffectContainer::storageType windowRuleIDX = 0;
|
||||
|
||||
//
|
||||
static SDispatchResult addRule(std::string in) {
|
||||
ruleIDX = Desktop::Rule::windowEffects()->registerEffect("plugin_rule");
|
||||
static SDispatchResult addWindowRule(std::string in) {
|
||||
windowRuleIDX = Desktop::Rule::windowEffects()->registerEffect("plugin_rule");
|
||||
|
||||
if (Desktop::Rule::windowEffects()->registerEffect("plugin_rule") != ruleIDX)
|
||||
if (Desktop::Rule::windowEffects()->registerEffect("plugin_rule") != windowRuleIDX)
|
||||
return {.success = false, .error = "re-registering returned a different id?"};
|
||||
return {};
|
||||
}
|
||||
|
||||
static SDispatchResult checkRule(std::string in) {
|
||||
static SDispatchResult checkWindowRule(std::string in) {
|
||||
const auto PLASTWINDOW = Desktop::focusState()->window();
|
||||
|
||||
if (!PLASTWINDOW)
|
||||
return {.success = false, .error = "No window"};
|
||||
|
||||
if (!PLASTWINDOW->m_ruleApplicator->m_otherProps.props.contains(ruleIDX))
|
||||
if (!PLASTWINDOW->m_ruleApplicator->m_otherProps.props.contains(windowRuleIDX))
|
||||
return {.success = false, .error = "No rule"};
|
||||
|
||||
if (PLASTWINDOW->m_ruleApplicator->m_otherProps.props[ruleIDX]->effect != "effect")
|
||||
if (PLASTWINDOW->m_ruleApplicator->m_otherProps.props[windowRuleIDX]->effect != "effect")
|
||||
return {.success = false, .error = "Effect isn't \"effect\""};
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static Desktop::Rule::CLayerRuleEffectContainer::storageType layerRuleIDX = 0;
|
||||
|
||||
static SDispatchResult addLayerRule(std::string in) {
|
||||
layerRuleIDX = Desktop::Rule::layerEffects()->registerEffect("plugin_rule");
|
||||
|
||||
if (Desktop::Rule::layerEffects()->registerEffect("plugin_rule") != layerRuleIDX)
|
||||
return {.success = false, .error = "re-registering returned a different id?"};
|
||||
return {};
|
||||
}
|
||||
|
||||
static SDispatchResult checkLayerRule(std::string in) {
|
||||
if (g_pCompositor->m_layers.size() != 3)
|
||||
return {.success = false, .error = "Layers under test not here"};
|
||||
|
||||
for (const auto& layer : g_pCompositor->m_layers) {
|
||||
if (layer->m_namespace == "rule-layer") {
|
||||
|
||||
if (!layer->m_ruleApplicator->m_otherProps.props.contains(layerRuleIDX))
|
||||
return {.success = false, .error = "No rule"};
|
||||
|
||||
if (layer->m_ruleApplicator->m_otherProps.props[layerRuleIDX]->effect != "effect")
|
||||
return {.success = false, .error = "Effect isn't \"effect\""};
|
||||
|
||||
} else if (layer->m_namespace == "norule-layer") {
|
||||
|
||||
if (layer->m_ruleApplicator->m_otherProps.props.contains(layerRuleIDX))
|
||||
return {.success = false, .error = "Rule even though it shouldn't"};
|
||||
|
||||
} else
|
||||
return {.success = false, .error = "Unrecognized layer"};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static SDispatchResult floatingFocusOnFullscreen(std::string in) {
|
||||
const auto PLASTWINDOW = Desktop::focusState()->window();
|
||||
|
||||
if (!PLASTWINDOW)
|
||||
return {.success = false, .error = "No window"};
|
||||
|
||||
if (!PLASTWINDOW->m_isFloating)
|
||||
return {.success = false, .error = "Window must be floating"};
|
||||
|
||||
if (PLASTWINDOW->m_alpha != 1.f)
|
||||
return {.success = false, .error = "floating window doesnt restore it opacity when focused on fullscreen workspace"};
|
||||
|
||||
if (!PLASTWINDOW->m_createdOverFullscreen)
|
||||
return {.success = false, .error = "floating window doesnt get flagged as createdOverFullscreen"};
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
|
||||
PHANDLE = handle;
|
||||
|
||||
|
|
@ -283,9 +362,13 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
|
|||
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:alt", ::pressAlt);
|
||||
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:gesture", ::simulateGesture);
|
||||
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:scroll", ::scroll);
|
||||
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:click", ::click);
|
||||
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:keybind", ::keybind);
|
||||
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:add_rule", ::addRule);
|
||||
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:check_rule", ::checkRule);
|
||||
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:add_window_rule", ::addWindowRule);
|
||||
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:check_window_rule", ::checkWindowRule);
|
||||
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:add_layer_rule", ::addLayerRule);
|
||||
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:check_layer_rule", ::checkLayerRule);
|
||||
HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:floating_focus_on_fullscreen", ::floatingFocusOnFullscreen);
|
||||
|
||||
// init mouse
|
||||
g_mouse = CTestMouse::create(false);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,17 @@ namespace Colors {
|
|||
constexpr const char* RESET = "\x1b[0m";
|
||||
};
|
||||
|
||||
#define EXPECT_MAX_DELTA(expr, desired, delta) \
|
||||
if (const auto RESULT = expr; std::abs(RESULT - (desired)) > delta) { \
|
||||
NLog::log("{}Failed: {}{}, expected max delta of {}, got delta {} ({} - {}). Source: {}@{}.", Colors::RED, Colors::RESET, #expr, delta, (RESULT - (desired)), RESULT, \
|
||||
desired, __FILE__, __LINE__); \
|
||||
ret = 1; \
|
||||
TESTS_FAILED++; \
|
||||
} else { \
|
||||
NLog::log("{}Passed: {}{}. Got {}", Colors::GREEN, Colors::RESET, #expr, (RESULT - (desired))); \
|
||||
TESTS_PASSED++; \
|
||||
}
|
||||
|
||||
#define EXPECT(expr, val) \
|
||||
if (const auto RESULT = expr; RESULT != (val)) { \
|
||||
NLog::log("{}Failed: {}{}, expected {}, got {}. Source: {}@{}.", Colors::RED, Colors::RESET, #expr, val, RESULT, __FILE__, __LINE__); \
|
||||
|
|
@ -28,6 +39,16 @@ namespace Colors {
|
|||
TESTS_PASSED++; \
|
||||
}
|
||||
|
||||
#define EXPECT_NOT(expr, val) \
|
||||
if (const auto RESULT = expr; RESULT == (val)) { \
|
||||
NLog::log("{}Failed: {}{}, expected not {}, got {}. Source: {}@{}.", Colors::RED, Colors::RESET, #expr, val, RESULT, __FILE__, __LINE__); \
|
||||
ret = 1; \
|
||||
TESTS_FAILED++; \
|
||||
} else { \
|
||||
NLog::log("{}Passed: {}{}. Got {}", Colors::GREEN, Colors::RESET, #expr, val); \
|
||||
TESTS_PASSED++; \
|
||||
}
|
||||
|
||||
#define EXPECT_VECTOR2D(expr, val) \
|
||||
do { \
|
||||
const auto& RESULT = expr; \
|
||||
|
|
|
|||
151
hyprtester/src/tests/clients/child-window.cpp
Normal file
151
hyprtester/src/tests/clients/child-window.cpp
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
#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 <unistd.h>
|
||||
#include <csignal>
|
||||
#include <thread>
|
||||
|
||||
using namespace Hyprutils::OS;
|
||||
using namespace Hyprutils::Memory;
|
||||
|
||||
#define SP CSharedPointer
|
||||
|
||||
struct SClient {
|
||||
SP<CProcess> proc;
|
||||
std::array<char, 1024> readBuf;
|
||||
CFileDescriptor readFd, writeFd;
|
||||
struct pollfd fds;
|
||||
};
|
||||
|
||||
static int ret = 0;
|
||||
|
||||
static bool waitForWindow(SP<CProcess> proc, int windowsBefore) {
|
||||
int counter = 0;
|
||||
while (Tests::processAlive(proc->pid()) && Tests::windowCount() == windowsBefore) {
|
||||
counter++;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
if (counter > 50)
|
||||
return false;
|
||||
}
|
||||
|
||||
NLog::log("{}Waited {} milliseconds for window to open", Colors::YELLOW, counter * 100);
|
||||
return Tests::processAlive(proc->pid());
|
||||
}
|
||||
|
||||
static bool startClient(SClient& client) {
|
||||
NLog::log("{}Attempting to start child-window client", Colors::YELLOW);
|
||||
|
||||
client.proc = makeShared<CProcess>(binaryDir + "/child-window", std::vector<std::string>{});
|
||||
|
||||
client.proc->addEnv("WAYLAND_DISPLAY", WLDISPLAY);
|
||||
|
||||
int procInPipeFd[2], procOutPipeFd[2];
|
||||
if (pipe(procInPipeFd) != 0 || pipe(procOutPipeFd) != 0) {
|
||||
NLog::log("{}Unable to open pipe to client", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
client.writeFd = CFileDescriptor(procInPipeFd[1]);
|
||||
client.proc->setStdinFD(procInPipeFd[0]);
|
||||
|
||||
client.readFd = CFileDescriptor(procOutPipeFd[0]);
|
||||
client.proc->setStdoutFD(procOutPipeFd[1]);
|
||||
|
||||
if (!client.proc->runAsync()) {
|
||||
NLog::log("{}Failed to run client", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
close(procInPipeFd[0]);
|
||||
close(procOutPipeFd[1]);
|
||||
|
||||
if (!waitForWindow(client.proc, Tests::windowCount())) {
|
||||
NLog::log("{}Window took too long to open", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
NLog::log("{}Started child-window client", Colors::YELLOW);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void stopClient(SClient& client) {
|
||||
std::string cmd = "exit\n";
|
||||
write(client.writeFd.get(), cmd.c_str(), cmd.length());
|
||||
|
||||
kill(client.proc->pid(), SIGKILL);
|
||||
client.proc.reset();
|
||||
}
|
||||
|
||||
static bool createChild(SClient& client) {
|
||||
std::string cmd = "toplevel\n";
|
||||
if ((size_t)write(client.writeFd.get(), cmd.c_str(), cmd.length()) != cmd.length())
|
||||
return false;
|
||||
|
||||
if (!waitForWindow(client.proc, Tests::windowCount()))
|
||||
NLog::log("{}Child window took too long to open", Colors::RED);
|
||||
|
||||
if (getFromSocket("/dispatch focuswindow class:child-test-child") != "ok") {
|
||||
NLog::log("{}Failed to focus child window", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool test() {
|
||||
SClient client;
|
||||
|
||||
if (!startClient(client))
|
||||
return false;
|
||||
OK(getFromSocket("/dispatch setfloating class:child-test-parent"));
|
||||
OK(getFromSocket("/dispatch pin class:child-test-parent"));
|
||||
|
||||
createChild(client);
|
||||
EXPECT(Tests::windowCount(), 2)
|
||||
EXPECT_COUNT_STRING(getFromSocket("/clients"), "pinned: 1", 2);
|
||||
|
||||
stopClient(client);
|
||||
NLog::log("{}Reloading config", Colors::YELLOW);
|
||||
OK(getFromSocket("/reload"));
|
||||
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);
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
#include <hyprutils/os/Process.hpp>
|
||||
|
||||
#include <sys/poll.h>
|
||||
#include <unistd.h>
|
||||
#include <csignal>
|
||||
#include <thread>
|
||||
|
||||
|
|
@ -42,6 +43,7 @@ static bool startClient(SClient& client) {
|
|||
client.readFd = CFileDescriptor(pipeFds2[0]);
|
||||
client.proc->setStdoutFD(pipeFds2[1]);
|
||||
|
||||
const int COUNT_BEFORE = Tests::windowCount();
|
||||
client.proc->runAsync();
|
||||
|
||||
close(pipeFds1[0]);
|
||||
|
|
@ -62,7 +64,16 @@ static bool startClient(SClient& client) {
|
|||
}
|
||||
|
||||
// wait for window to appear
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
|
||||
int counter = 0;
|
||||
while (Tests::processAlive(client.proc->pid()) && Tests::windowCount() == COUNT_BEFORE) {
|
||||
counter++;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
if (counter > 50) {
|
||||
NLog::log("{}pointer-scroll client took too long to open", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (getFromSocket(std::format("/dispatch setprop pid:{} no_anim 1", client.proc->pid())) != "ok") {
|
||||
NLog::log("{}Failed to disable animations for client window", Colors::RED, ret);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include <hyprutils/os/Process.hpp>
|
||||
|
||||
#include <sys/poll.h>
|
||||
#include <unistd.h>
|
||||
#include <csignal>
|
||||
#include <thread>
|
||||
|
||||
|
|
@ -42,6 +43,7 @@ static bool startClient(SClient& client) {
|
|||
client.readFd = CFileDescriptor(pipeFds2[0]);
|
||||
client.proc->setStdoutFD(pipeFds2[1]);
|
||||
|
||||
const int COUNT_BEFORE = Tests::windowCount();
|
||||
client.proc->runAsync();
|
||||
|
||||
close(pipeFds1[0]);
|
||||
|
|
@ -62,7 +64,16 @@ static bool startClient(SClient& client) {
|
|||
}
|
||||
|
||||
// wait for window to appear
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
|
||||
int counter = 0;
|
||||
while (Tests::processAlive(client.proc->pid()) && Tests::windowCount() == COUNT_BEFORE) {
|
||||
counter++;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
if (counter > 50) {
|
||||
NLog::log("{}pointer-warp client took too long to open", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (getFromSocket(std::format("/dispatch setprop pid:{} no_anim 1", client.proc->pid())) != "ok") {
|
||||
NLog::log("{}Failed to disable animations for client window", Colors::RED, ret);
|
||||
|
|
|
|||
180
hyprtester/src/tests/clients/shortcut-inhibitor.cpp
Normal file
180
hyprtester/src/tests/clients/shortcut-inhibitor.cpp
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
#include "../../shared.hpp"
|
||||
#include "../../hyprctlCompat.hpp"
|
||||
#include "../shared.hpp"
|
||||
#include "tests.hpp"
|
||||
#include "build.hpp"
|
||||
|
||||
#include <hyprutils/os/FileDescriptor.hpp>
|
||||
#include <hyprutils/os/Process.hpp>
|
||||
|
||||
#include <sys/poll.h>
|
||||
#include <csignal>
|
||||
#include <thread>
|
||||
#include <filesystem>
|
||||
|
||||
using namespace Hyprutils::OS;
|
||||
using namespace Hyprutils::Memory;
|
||||
|
||||
#define SP CSharedPointer
|
||||
|
||||
struct SClient {
|
||||
SP<CProcess> proc;
|
||||
std::array<char, 1024> readBuf;
|
||||
CFileDescriptor readFd, writeFd;
|
||||
struct pollfd fds;
|
||||
};
|
||||
|
||||
static int ret = 0;
|
||||
|
||||
static bool startClient(SClient& client) {
|
||||
Tests::killAllWindows();
|
||||
client.proc = makeShared<CProcess>(binaryDir + "/shortcut-inhibitor", std::vector<std::string>{});
|
||||
|
||||
client.proc->addEnv("WAYLAND_DISPLAY", WLDISPLAY);
|
||||
|
||||
int pipeFds1[2], pipeFds2[2];
|
||||
if (pipe(pipeFds1) != 0 || pipe(pipeFds2) != 0) {
|
||||
NLog::log("{}Unable to open pipe to client", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
client.writeFd = CFileDescriptor(pipeFds1[1]);
|
||||
client.proc->setStdinFD(pipeFds1[0]);
|
||||
|
||||
client.readFd = CFileDescriptor(pipeFds2[0]);
|
||||
client.proc->setStdoutFD(pipeFds2[1]);
|
||||
|
||||
const int COUNT_BEFORE = Tests::windowCount();
|
||||
client.proc->runAsync();
|
||||
|
||||
close(pipeFds1[0]);
|
||||
close(pipeFds2[1]);
|
||||
|
||||
client.fds = {.fd = client.readFd.get(), .events = POLLIN};
|
||||
if (poll(&client.fds, 1, 1000) != 1 || !(client.fds.revents & POLLIN)) {
|
||||
NLog::log("{}shortcut-inhibitor client failed poll", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
client.readBuf.fill(0);
|
||||
if (read(client.readFd.get(), client.readBuf.data(), client.readBuf.size() - 1) == -1) {
|
||||
NLog::log("{}shortcut-inhibitor client read failed", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string ret = std::string{client.readBuf.data()};
|
||||
if (ret.find("started") == std::string::npos) {
|
||||
NLog::log("{}Failed to start shortcut-inhibitor client, read {}", Colors::RED, ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
// wait for window to appear
|
||||
int counter = 0;
|
||||
while (Tests::processAlive(client.proc->pid()) && Tests::windowCount() == COUNT_BEFORE) {
|
||||
counter++;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
if (counter > 50) {
|
||||
NLog::log("{}shortcut-inhibitor client took too long to open", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Tests::processAlive(client.proc->pid())) {
|
||||
NLog::log("{}shortcut-inhibitor client not alive", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (getFromSocket(std::format("/dispatch focuswindow pid:{}", client.proc->pid())) != "ok") {
|
||||
NLog::log("{}Failed to focus shortcut-inhibitor client", Colors::RED, ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string command = "on\n";
|
||||
if (write(client.writeFd.get(), command.c_str(), command.length()) == -1) {
|
||||
NLog::log("{}shortcut-inhibitor client write failed", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
client.readBuf.fill(0);
|
||||
if (read(client.readFd.get(), client.readBuf.data(), client.readBuf.size() - 1) == -1)
|
||||
return false;
|
||||
|
||||
ret = std::string{client.readBuf.data()};
|
||||
if (ret.find("inhibiting") == std::string::npos) {
|
||||
NLog::log("{}shortcut-inhibitor client didn't return inhibiting", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
NLog::log("{}Started shortcut-inhibitor client", Colors::YELLOW);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void stopClient(SClient& client) {
|
||||
std::string cmd = "off\n";
|
||||
write(client.writeFd.get(), cmd.c_str(), cmd.length());
|
||||
|
||||
kill(client.proc->pid(), SIGKILL);
|
||||
client.proc.reset();
|
||||
}
|
||||
|
||||
static std::string flagFile = "/tmp/hyprtester-keybinds.txt";
|
||||
|
||||
static bool checkFlag() {
|
||||
bool exists = std::filesystem::exists(flagFile);
|
||||
std::filesystem::remove(flagFile);
|
||||
return exists;
|
||||
}
|
||||
|
||||
static bool attemptCheckFlag(int attempts, int intervalMs) {
|
||||
for (int i = 0; i < attempts; i++) {
|
||||
if (checkFlag())
|
||||
return true;
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(intervalMs));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool test() {
|
||||
SClient client;
|
||||
if (!startClient(client))
|
||||
return false;
|
||||
|
||||
NLog::log("{}Testing keybinds", Colors::GREEN);
|
||||
//basic keybind test
|
||||
EXPECT(checkFlag(), false);
|
||||
EXPECT(getFromSocket("/keyword bind SUPER,Y,exec,touch " + flagFile), "ok");
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
|
||||
EXPECT(attemptCheckFlag(20, 50), false);
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
|
||||
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
|
||||
|
||||
//keybind bypass flag test
|
||||
EXPECT(checkFlag(), false);
|
||||
EXPECT(getFromSocket("/keyword bindp SUPER,Y,exec,touch " + flagFile), "ok");
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
|
||||
EXPECT(attemptCheckFlag(20, 50), true);
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
|
||||
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
|
||||
|
||||
NLog::log("{}Testing gestures", Colors::GREEN);
|
||||
//basic gesture test
|
||||
OK(getFromSocket("/dispatch plugin:test:gesture right,3"));
|
||||
EXPECT_NOT_CONTAINS(getFromSocket("/activewindow"), "floating: 1");
|
||||
|
||||
//gesture bypass flag test
|
||||
OK(getFromSocket("/dispatch plugin:test:gesture right,2"));
|
||||
EXPECT_CONTAINS(getFromSocket("/activewindow"), "floating: 1");
|
||||
|
||||
stopClient(client);
|
||||
|
||||
NLog::log("{}Reloading the config", Colors::YELLOW);
|
||||
OK(getFromSocket("/reload"));
|
||||
|
||||
return !ret;
|
||||
}
|
||||
|
||||
REGISTER_CLIENT_TEST_FN(test);
|
||||
|
|
@ -3,23 +3,33 @@
|
|||
#include "../../hyprctlCompat.hpp"
|
||||
#include "../shared.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
static int ret = 0;
|
||||
|
||||
static bool test() {
|
||||
NLog::log("{}Testing hyprctl monitors", Colors::GREEN);
|
||||
|
||||
std::string monitorsSpec = getFromSocket("j/monitors");
|
||||
EXPECT_CONTAINS(monitorsSpec, R"("colorManagementPreset": "srgb")");
|
||||
EXPECT_CONTAINS(monitorsSpec, R"("colorManagementPreset")");
|
||||
|
||||
EXPECT_CONTAINS(getFromSocket("/keyword monitor HEADLESS-2,1920x1080x60.00000,0x0,1.0,bitdepth,10,cm,wide"), "ok")
|
||||
|
||||
// monitor settings are applied after a frame is pushed.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
|
||||
monitorsSpec = getFromSocket("j/monitors");
|
||||
EXPECT_CONTAINS(monitorsSpec, R"("colorManagementPreset": "wide")");
|
||||
|
||||
EXPECT_CONTAINS(getFromSocket("/keyword monitor HEADLESS-2,1920x1080x60.00000,0x0,1.0,bitdepth,10,cm,srgb,sdrbrightness,1.2,sdrsaturation,0.98"), "ok")
|
||||
monitorsSpec = getFromSocket("j/monitors");
|
||||
EXPECT_CONTAINS(monitorsSpec, R"("colorManagementPreset": "srgb")");
|
||||
EXPECT_CONTAINS(monitorsSpec, R"("sdrBrightness": 1.20)");
|
||||
EXPECT_CONTAINS(monitorsSpec, R"("sdrSaturation": 0.98)");
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
|
||||
EXPECT_CONTAINS(monitorsSpec, "colorManagementPreset");
|
||||
EXPECT_CONTAINS(monitorsSpec, "sdrBrightness");
|
||||
EXPECT_CONTAINS(monitorsSpec, "sdrSaturation");
|
||||
|
||||
return !ret;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ static void testFloatClamp() {
|
|||
|
||||
{
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT_CONTAINS(str, "at: 698,158");
|
||||
EXPECT_CONTAINS(str, "at:");
|
||||
EXPECT_CONTAINS(str, "size: 1200,900");
|
||||
}
|
||||
|
||||
|
|
@ -34,6 +34,219 @@ 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: 22,547");
|
||||
EXPECT_CONTAINS(str, "size: 931,511");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch movewindow r"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 967,547");
|
||||
EXPECT_CONTAINS(str, "size: 931,511");
|
||||
}
|
||||
|
||||
// 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 void testForceSplitOnMoveToWorkspace() {
|
||||
OK(getFromSocket("/dispatch workspace 2"));
|
||||
EXPECT(!!Tests::spawnKitty("kitty"), true);
|
||||
|
||||
OK(getFromSocket("/dispatch workspace 1"));
|
||||
EXPECT(!!Tests::spawnKitty("kitty"), true);
|
||||
std::string posBefore = Tests::getWindowAttribute(getFromSocket("/activewindow"), "at:");
|
||||
|
||||
OK(getFromSocket("/keyword dwindle:force_split 2"));
|
||||
OK(getFromSocket("/dispatch movecursortocorner 3")); // top left
|
||||
OK(getFromSocket("/dispatch movetoworkspace 2"));
|
||||
|
||||
// Should be moved to the right, so the position should change
|
||||
std::string activeWindow = getFromSocket("/activewindow");
|
||||
EXPECT(activeWindow.contains(posBefore), false);
|
||||
|
||||
// clean up
|
||||
OK(getFromSocket("/reload"));
|
||||
Tests::killAllWindows();
|
||||
Tests::waitUntilWindowsN(0);
|
||||
}
|
||||
|
||||
static bool test() {
|
||||
|
|
@ -43,6 +256,18 @@ 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();
|
||||
|
||||
NLog::log("{}Testing force_split on move to workspace", Colors::GREEN);
|
||||
testForceSplitOnMoveToWorkspace();
|
||||
|
||||
// clean up
|
||||
NLog::log("Cleaning up", Colors::YELLOW);
|
||||
getFromSocket("/dispatch workspace 1");
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include "../../shared.hpp"
|
||||
#include "../../hyprctlCompat.hpp"
|
||||
#include <chrono>
|
||||
#include <format>
|
||||
#include <thread>
|
||||
#include <hyprutils/os/Process.hpp>
|
||||
#include <hyprutils/memory/WeakPtr.hpp>
|
||||
|
|
@ -15,40 +16,46 @@ using namespace Hyprutils::Memory;
|
|||
#define UP CUniquePointer
|
||||
#define SP CSharedPointer
|
||||
|
||||
static bool test() {
|
||||
const static auto SLEEP_DURATIONS = std::array{1, 10};
|
||||
|
||||
static bool test() {
|
||||
NLog::log("{}Testing process spawning", Colors::GREEN);
|
||||
|
||||
// Note: POSIX sleep does not support fractional seconds, so
|
||||
// can't sleep for less than 1 second.
|
||||
OK(getFromSocket("/dispatch exec sleep 1"));
|
||||
for (const auto duration : SLEEP_DURATIONS) {
|
||||
// Note: POSIX sleep does not support fractional seconds, so
|
||||
// can't sleep for less than 1 second.
|
||||
OK(getFromSocket(std::format("/dispatch exec sleep {}", duration)));
|
||||
|
||||
// Ensure that sleep is our child
|
||||
const std::string sleepPidS = Tests::execAndGet("pgrep sleep");
|
||||
pid_t sleepPid;
|
||||
try {
|
||||
sleepPid = std::stoull(sleepPidS);
|
||||
} catch (...) {
|
||||
NLog::log("{}Sleep was not spawned or several sleeps are running: pgrep returned '{}'", Colors::RED, sleepPidS);
|
||||
return false;
|
||||
// Ensure that sleep is our child
|
||||
const std::string sleepPidS = Tests::execAndGet("pgrep sleep");
|
||||
pid_t sleepPid;
|
||||
try {
|
||||
sleepPid = std::stoull(sleepPidS);
|
||||
} catch (...) {
|
||||
NLog::log("{}Sleep was not spawned or several sleeps are running: pgrep returned '{}'", Colors::RED, sleepPidS);
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string sleepParentComm = Tests::execAndGet("cat \"/proc/$(ps -o ppid:1= -p " + sleepPidS + ")/comm\"");
|
||||
NLog::log("{}Expecting that sleep's parent is Hyprland", Colors::YELLOW);
|
||||
EXPECT_CONTAINS(sleepParentComm, "Hyprland");
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds(duration));
|
||||
|
||||
// Ensure that sleep did not become a zombie
|
||||
EXPECT(Tests::processAlive(sleepPid), false);
|
||||
|
||||
// kill all
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
|
||||
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
|
||||
return !ret;
|
||||
}
|
||||
|
||||
const std::string sleepParentComm = Tests::execAndGet("cat \"/proc/$(ps -o ppid:1= -p " + sleepPidS + ")/comm\"");
|
||||
NLog::log("{}Expecting that sleep's parent is Hyprland", Colors::YELLOW);
|
||||
EXPECT_CONTAINS(sleepParentComm, "Hyprland");
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
// Ensure that sleep did not become a zombie
|
||||
EXPECT(Tests::processAlive(sleepPid), false);
|
||||
|
||||
// kill all
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
|
||||
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
|
||||
return !ret;
|
||||
return false;
|
||||
}
|
||||
|
||||
REGISTER_TEST_FN(test)
|
||||
|
|
|
|||
131
hyprtester/src/tests/main/follow_mouse_shrink.cpp
Normal file
131
hyprtester/src/tests/main/follow_mouse_shrink.cpp
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
|
||||
#include "../../shared.hpp"
|
||||
#include "../../hyprctlCompat.hpp"
|
||||
#include "../shared.hpp"
|
||||
#include "tests.hpp"
|
||||
|
||||
static int ret = 0;
|
||||
|
||||
static bool spawnKitty(const std::string& class_) {
|
||||
NLog::log("{}Spawning {}", Colors::YELLOW, class_);
|
||||
if (!Tests::spawnKitty(class_)) {
|
||||
NLog::log("{}Error: {} did not spawn", Colors::RED, class_);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool isActiveWindow(const std::string& class_, char fullscreen = '0', bool log = true) {
|
||||
std::string activeWin = getFromSocket("/activewindow");
|
||||
auto winClass = Tests::getWindowAttribute(activeWin, "class:");
|
||||
auto winFullscreen = Tests::getWindowAttribute(activeWin, "fullscreen:").back();
|
||||
if (winClass.substr(strlen("class: ")) == class_ && winFullscreen == fullscreen)
|
||||
return true;
|
||||
else {
|
||||
if (log)
|
||||
NLog::log("{}Wrong active window: expected class {} fullscreen '{}', found class {}, fullscreen '{}'", Colors::RED, class_, fullscreen, winClass, winFullscreen);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool waitForActiveWindow(const std::string& class_, char fullscreen = '0', bool logLastCheck = true, int maxTries = 50) {
|
||||
int cnt = 0;
|
||||
while (!isActiveWindow(class_, fullscreen, false)) {
|
||||
++cnt;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
if (cnt > maxTries) {
|
||||
return isActiveWindow(class_, fullscreen, logLastCheck);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool test() {
|
||||
NLog::log("{}Testing follow_mouse_shrink", Colors::GREEN);
|
||||
|
||||
getFromSocket("/dispatch workspace name:follow_mouse_shrink");
|
||||
|
||||
// follow_mouse 2 so cursor position determines focus (mode 1's delta threshold
|
||||
// is unreliable with movecursor/simulateMouseMovement). float_switch_override_focus 2
|
||||
// enables focus switching between floating windows.
|
||||
OK(getFromSocket("/keyword input:follow_mouse 2"));
|
||||
OK(getFromSocket("/keyword input:float_switch_override_focus 2"));
|
||||
|
||||
// Spawn two floating windows with a 20px gap
|
||||
// fms_a: position (100,100), size 400x400 -> hitbox [100,499] x [100,499]
|
||||
if (!spawnKitty("fms_a"))
|
||||
return false;
|
||||
OK(getFromSocket("/dispatch setfloating"));
|
||||
OK(getFromSocket("/dispatch resizewindowpixel exact 400 400,activewindow"));
|
||||
OK(getFromSocket("/dispatch movewindowpixel exact 100 100,activewindow"));
|
||||
|
||||
// fms_b: position (520,100), size 400x400 -> hitbox [520,919] x [100,499]
|
||||
if (!spawnKitty("fms_b"))
|
||||
return false;
|
||||
OK(getFromSocket("/dispatch setfloating"));
|
||||
OK(getFromSocket("/dispatch resizewindowpixel exact 400 400,activewindow"));
|
||||
OK(getFromSocket("/dispatch movewindowpixel exact 520 100,activewindow"));
|
||||
|
||||
// --- Test 1: Baseline shrink=0, edge focus works ---
|
||||
NLog::log("{}Test 1: shrink=0, cursor at B's left edge focuses B", Colors::GREEN);
|
||||
OK(getFromSocket("/keyword input:follow_mouse_shrink 0"));
|
||||
// Focus A explicitly, then move cursor inside A so follow_mouse tracks it
|
||||
OK(getFromSocket("/dispatch focuswindow class:fms_a"));
|
||||
EXPECT(waitForActiveWindow("fms_a"), true);
|
||||
OK(getFromSocket("/dispatch movecursor 300 300"));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
// Move to just inside B's left edge
|
||||
OK(getFromSocket("/dispatch movecursor 521 300"));
|
||||
EXPECT(waitForActiveWindow("fms_b"), true);
|
||||
|
||||
// --- Test 2: Shrink=20, cursor in dead zone does NOT change focus ---
|
||||
NLog::log("{}Test 2: shrink=20, cursor in B's dead zone stays on A", Colors::GREEN);
|
||||
OK(getFromSocket("/keyword input:follow_mouse_shrink 20"));
|
||||
// Focus A explicitly
|
||||
OK(getFromSocket("/dispatch focuswindow class:fms_a"));
|
||||
EXPECT(waitForActiveWindow("fms_a"), true);
|
||||
OK(getFromSocket("/dispatch movecursor 300 300"));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
// Move to 530,300 -- 10px inside B, within 20px shrink zone (B's shrunk hitbox starts at 540)
|
||||
OK(getFromSocket("/dispatch movecursor 530 300"));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
EXPECT(isActiveWindow("fms_a"), true);
|
||||
|
||||
// --- Test 3: Shrink=20, cursor well inside inactive window DOES focus it ---
|
||||
NLog::log("{}Test 3: shrink=20, cursor at B's center focuses B", Colors::GREEN);
|
||||
// Still focused on A from test 2
|
||||
OK(getFromSocket("/dispatch movecursor 720 300"));
|
||||
EXPECT(waitForActiveWindow("fms_b"), true);
|
||||
|
||||
// --- Test 4: Focused window's hitbox is NOT shrunk ---
|
||||
NLog::log("{}Test 4a: focused window hitbox is not shrunk", Colors::GREEN);
|
||||
// Focus A explicitly, move cursor inside A, then move near A's right edge
|
||||
OK(getFromSocket("/dispatch focuswindow class:fms_a"));
|
||||
EXPECT(waitForActiveWindow("fms_a"), true);
|
||||
OK(getFromSocket("/dispatch movecursor 300 300"));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
OK(getFromSocket("/dispatch movecursor 490 300"));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
EXPECT(isActiveWindow("fms_a"), true);
|
||||
|
||||
NLog::log("{}Test 4b: inactive window hitbox IS shrunk at same position", Colors::GREEN);
|
||||
// Focus B explicitly, then move to (490,300). Now A is inactive with shrunk box ending at 479, so 490 is outside A.
|
||||
OK(getFromSocket("/dispatch focuswindow class:fms_b"));
|
||||
EXPECT(waitForActiveWindow("fms_b"), true);
|
||||
OK(getFromSocket("/dispatch movecursor 720 300"));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
OK(getFromSocket("/dispatch movecursor 490 300"));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
EXPECT(isActiveWindow("fms_b"), true);
|
||||
|
||||
// Cleanup
|
||||
OK(getFromSocket("/reload"));
|
||||
Tests::killAllWindows();
|
||||
|
||||
return ret == 0;
|
||||
}
|
||||
|
||||
REGISTER_TEST_FN(test)
|
||||
|
|
@ -160,6 +160,33 @@ static bool test() {
|
|||
// The cursor should have moved because of the gesture
|
||||
EXPECT(cursorPos1 != cursorPos2, true);
|
||||
|
||||
// Test that `workspace previous` works correctly after a workspace gesture.
|
||||
{
|
||||
OK(getFromSocket("/keyword gestures:workspace_swipe_invert 0"));
|
||||
OK(getFromSocket("/keyword gestures:workspace_swipe_create_new 1"));
|
||||
OK(getFromSocket("/dispatch workspace 3"));
|
||||
|
||||
// Come to workspace 5 from workspace 3: 5 will remember that.
|
||||
OK(getFromSocket("/dispatch workspace 5"));
|
||||
Tests::spawnKitty(); // Keep workspace 5 open
|
||||
|
||||
// Swipe from 1 to 5: 5 shall remember that.
|
||||
OK(getFromSocket("/dispatch workspace 1"));
|
||||
OK(getFromSocket("/dispatch plugin:test:alt 1"));
|
||||
OK(getFromSocket("/dispatch plugin:test:gesture right,3"));
|
||||
OK(getFromSocket("/dispatch plugin:test:alt 0"));
|
||||
EXPECT_CONTAINS(getFromSocket("/activeworkspace"), "ID 5 (5)");
|
||||
|
||||
// Must return to 1 rather than 3
|
||||
OK(getFromSocket("/dispatch workspace previous"));
|
||||
EXPECT_CONTAINS(getFromSocket("/activeworkspace"), "ID 1 (1)");
|
||||
|
||||
OK(getFromSocket("/dispatch workspace previous"));
|
||||
EXPECT_CONTAINS(getFromSocket("/activeworkspace"), "ID 5 (5)");
|
||||
|
||||
OK(getFromSocket("/dispatch workspace 1"));
|
||||
}
|
||||
|
||||
// kill all
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
|
|
|
|||
|
|
@ -25,6 +25,65 @@ static bool test() {
|
|||
NLog::log("{}Dispatching workspace `groups`", Colors::YELLOW);
|
||||
getFromSocket("/dispatch workspace name:groups");
|
||||
|
||||
NLog::log("{}Testing movewindoworgroup from group to group", Colors::YELLOW);
|
||||
auto kittyA = Tests::spawnKitty("kittyA");
|
||||
if (!kittyA) {
|
||||
NLog::log("{}Error: kitty did not spawn", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
// check kitty properties. One kitty should take the entire screen, minus the gaps.
|
||||
NLog::log("{}Check kittyA dimensions", Colors::YELLOW);
|
||||
{
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT_COUNT_STRING(str, "at: 22,22", 1);
|
||||
EXPECT_COUNT_STRING(str, "size: 1876,1036", 1);
|
||||
EXPECT_COUNT_STRING(str, "fullscreen: 0", 1);
|
||||
}
|
||||
|
||||
auto kittyB = Tests::spawnKitty("kittyB");
|
||||
if (!kittyB) {
|
||||
NLog::log("{}Error: kitty did not spawn", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch focuswindow class:kittyB"));
|
||||
OK(getFromSocket("/dispatch togglegroup"));
|
||||
OK(getFromSocket("/dispatch focuswindow class:kittyA"));
|
||||
OK(getFromSocket("/dispatch togglegroup"));
|
||||
|
||||
NLog::log("{}Check kittyB dimensions", Colors::YELLOW);
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_COUNT_STRING(str, "size: 931,1015", 1);
|
||||
EXPECT_COUNT_STRING(str, "fullscreen: 0", 1);
|
||||
}
|
||||
|
||||
auto kittyC = Tests::spawnKitty("kittyC");
|
||||
if (!kittyC) {
|
||||
NLog::log("{}Error: kitty did not spawn", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
NLog::log("{}Check kittyC dimensions", Colors::YELLOW);
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_COUNT_STRING(str, "size: 931,1015", 1);
|
||||
EXPECT_COUNT_STRING(str, "fullscreen: 0", 1);
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch movewindoworgroup r"));
|
||||
NLog::log("{}Check that dimensions remain the same after move", Colors::YELLOW);
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_COUNT_STRING(str, "size: 931,1015", 1);
|
||||
EXPECT_COUNT_STRING(str, "fullscreen: 0", 1);
|
||||
}
|
||||
|
||||
// kill all
|
||||
NLog::log("{}Kill windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
|
||||
NLog::log("{}Spawning kittyProcA", Colors::YELLOW);
|
||||
auto kittyProcA = Tests::spawnKitty();
|
||||
if (!kittyProcA) {
|
||||
|
|
@ -127,6 +186,34 @@ static bool test() {
|
|||
ret = 1;
|
||||
}
|
||||
|
||||
// test movegroupwindow: focus should follow the moved window
|
||||
NLog::log("{}Test movegroupwindow focus follows window", Colors::YELLOW);
|
||||
try {
|
||||
auto str = getFromSocket("/activewindow");
|
||||
auto activeBeforeMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16);
|
||||
OK(getFromSocket("/dispatch movegroupwindow f"));
|
||||
str = getFromSocket("/activewindow");
|
||||
auto activeAfterMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16);
|
||||
EXPECT(activeAfterMove, activeBeforeMove);
|
||||
} catch (...) {
|
||||
NLog::log("{}Fail at getting prop", Colors::RED);
|
||||
ret = 1;
|
||||
}
|
||||
|
||||
// and backwards
|
||||
NLog::log("{}Test movegroupwindow backwards", Colors::YELLOW);
|
||||
try {
|
||||
auto str = getFromSocket("/activewindow");
|
||||
auto activeBeforeMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16);
|
||||
OK(getFromSocket("/dispatch movegroupwindow b"));
|
||||
str = getFromSocket("/activewindow");
|
||||
auto activeAfterMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16);
|
||||
EXPECT(activeAfterMove, activeBeforeMove);
|
||||
} catch (...) {
|
||||
NLog::log("{}Fail at getting prop", Colors::RED);
|
||||
ret = 1;
|
||||
}
|
||||
|
||||
NLog::log("{}Disable autogrouping", Colors::YELLOW);
|
||||
OK(getFromSocket("/keyword group:auto_group false"));
|
||||
|
||||
|
|
@ -173,6 +260,182 @@ 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);
|
||||
|
||||
// Tests for grouping/merging logic
|
||||
NLog::log("{}Testing locked groups w/ invade", Colors::GREEN);
|
||||
|
||||
Tests::killAllWindows();
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
|
||||
// Test normal, unlocked groups
|
||||
{
|
||||
auto winA = Tests::spawnKitty("unlocked");
|
||||
if (!winA) {
|
||||
NLog::log("{}Error: unlocked kitty did not spawn", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
OK(getFromSocket("/dispatch togglegroup"));
|
||||
|
||||
auto winB = Tests::spawnKitty("top");
|
||||
if (!winB) {
|
||||
NLog::log("{}Error: top kitty did not spawn", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify it DID merge into a group
|
||||
{
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT_COUNT_STRING(str, "at: 22,22", 2);
|
||||
}
|
||||
}
|
||||
|
||||
Tests::killAllWindows();
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
|
||||
// Test locked groups
|
||||
{
|
||||
auto lockedWin = Tests::spawnKitty("locked");
|
||||
if (!lockedWin) {
|
||||
NLog::log("{}Error: locked kitty did not spawn", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
OK(getFromSocket("/dispatch togglegroup"));
|
||||
OK(getFromSocket(std::format("/dispatch focuswindow pid:{}", lockedWin->pid())));
|
||||
OK(getFromSocket("/dispatch lockactivegroup lock"));
|
||||
|
||||
auto winB = Tests::spawnKitty("top");
|
||||
if (!winB) {
|
||||
NLog::log("{}Error: top kitty did not spawn", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify it did NOT merge into the locked group
|
||||
{
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT_COUNT_STRING(str, "at: 22,22", 1);
|
||||
}
|
||||
}
|
||||
|
||||
Tests::killAllWindows();
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
|
||||
// Test locked groups WITH invade rule
|
||||
{
|
||||
OK(getFromSocket("/keyword windowrule[locked-im]:match:class ^locked|invade$"));
|
||||
OK(getFromSocket("/keyword windowrule[locked-im]:group set always lock invade"));
|
||||
|
||||
auto lockedWin = Tests::spawnKitty("locked");
|
||||
if (!lockedWin) {
|
||||
NLog::log("{}Error: locked kitty did not spawn", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto invadingWin = Tests::spawnKitty("invade");
|
||||
if (!invadingWin) {
|
||||
NLog::log("{}Error: invading kitty did not spawn", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify it DID merge into the locked group
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT_COUNT_STRING(str, "at: 22,22", 2);
|
||||
}
|
||||
|
||||
Tests::killAllWindows();
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
|
||||
return !ret;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,6 +77,16 @@ static bool testGetprop() {
|
|||
EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size"), "100 50");
|
||||
EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size -j"), R"({"min_size": [100,50]})");
|
||||
|
||||
// expr-based min/max _size
|
||||
getFromSocket("/dispatch setfloating class:kitty"); // need to set floating for tests below
|
||||
getFromSocket("/dispatch setprop class:kitty max_size 90+10 25*2"); // set max to the same as min above, forcing window to 100*50
|
||||
EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size"), "100 50");
|
||||
EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size -j"), R"({"max_size": [100,50]})");
|
||||
getFromSocket("/dispatch setprop class:kitty min_size window_w*0.5 window_h-10");
|
||||
EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size"), "50 40");
|
||||
EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size -j"), R"({"min_size": [50,40]})");
|
||||
getFromSocket("/dispatch settiled class:kitty"); // go back to tiled for consistency
|
||||
|
||||
// opacity
|
||||
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity"), "1");
|
||||
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity -j"), R"({"opacity": 1})");
|
||||
|
|
@ -163,6 +173,13 @@ static bool testGetprop() {
|
|||
return true;
|
||||
}
|
||||
|
||||
static void testSubmap() {
|
||||
NLog::log("{}Testing hyprctl submap", Colors::GREEN);
|
||||
|
||||
EXPECT(getCommandStdOut("hyprctl submap"), "default\n");
|
||||
EXPECT(getCommandStdOut("hyprctl submap -j | jq -r \".\""), "default");
|
||||
}
|
||||
|
||||
static bool test() {
|
||||
NLog::log("{}Testing hyprctl", Colors::GREEN);
|
||||
|
||||
|
|
@ -176,6 +193,7 @@ static bool test() {
|
|||
|
||||
testGetprop();
|
||||
testDevicesActiveLayoutIndex();
|
||||
testSubmap();
|
||||
getFromSocket("/reload");
|
||||
|
||||
return !ret;
|
||||
|
|
|
|||
|
|
@ -86,8 +86,7 @@ static void testBind() {
|
|||
// press keybind
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
|
||||
// await flag
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
EXPECT(checkFlag(), true);
|
||||
EXPECT(attemptCheckFlag(20, 50), true);
|
||||
// release keybind
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
|
||||
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
|
||||
|
|
@ -99,8 +98,7 @@ static void testBindKey() {
|
|||
// press keybind
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 1,0,29"));
|
||||
// await flag
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
EXPECT(checkFlag(), true);
|
||||
EXPECT(attemptCheckFlag(20, 50), true);
|
||||
// release keybind
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
|
||||
EXPECT(getFromSocket("/keyword unbind ,Y"), "ok");
|
||||
|
|
@ -116,7 +114,7 @@ static void testLongPress() {
|
|||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
EXPECT(checkFlag(), false);
|
||||
// await repeat delay
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
EXPECT(checkFlag(), true);
|
||||
// release keybind
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
|
||||
|
|
@ -133,7 +131,7 @@ static void testKeyLongPress() {
|
|||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
EXPECT(checkFlag(), false);
|
||||
// await repeat delay
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
EXPECT(checkFlag(), true);
|
||||
// release keybind
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
|
||||
|
|
@ -152,7 +150,7 @@ static void testLongPressRelease() {
|
|||
// release keybind
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
|
||||
// await repeat delay
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
EXPECT(checkFlag(), false);
|
||||
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
|
||||
}
|
||||
|
|
@ -169,7 +167,7 @@ static void testLongPressOnlyKeyRelease() {
|
|||
// release key, keep modifier
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 0,7,29"));
|
||||
// await repeat delay
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
EXPECT(checkFlag(), false);
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
|
||||
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
|
||||
|
|
@ -182,13 +180,13 @@ static void testRepeat() {
|
|||
// press keybind
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
|
||||
// await flag
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
EXPECT(checkFlag(), true);
|
||||
// await repeat delay
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
EXPECT(checkFlag(), true);
|
||||
// check that it continues repeating
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
EXPECT(checkFlag(), true);
|
||||
// release keybind
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
|
||||
|
|
@ -205,10 +203,10 @@ static void testKeyRepeat() {
|
|||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
EXPECT(checkFlag(), true);
|
||||
// await repeat delay
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
EXPECT(checkFlag(), true);
|
||||
// check that it continues repeating
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
EXPECT(checkFlag(), true);
|
||||
// release keybind
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
|
||||
|
|
@ -216,7 +214,17 @@ static void testKeyRepeat() {
|
|||
}
|
||||
|
||||
static void testRepeatRelease() {
|
||||
EXPECT(checkFlag(), false);
|
||||
// wait until flag becomes false (CI timing can vary)
|
||||
bool ok = false;
|
||||
for (int i = 0; i < 20; ++i) {
|
||||
if (!checkFlag()) {
|
||||
ok = true;
|
||||
break;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
|
||||
EXPECT(ok, true);
|
||||
EXPECT(getFromSocket("/keyword binde SUPER,Y,exec,touch " + flagFile), "ok");
|
||||
EXPECT(getFromSocket("/keyword input:repeat_delay 100"), "ok");
|
||||
// press keybind
|
||||
|
|
@ -227,10 +235,12 @@ static void testRepeatRelease() {
|
|||
// release keybind
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
|
||||
// await repeat delay
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
clearFlag();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
EXPECT(checkFlag(), false);
|
||||
// check that it is not repeating
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
EXPECT(checkFlag(), false);
|
||||
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
|
||||
}
|
||||
|
|
@ -242,15 +252,17 @@ static void testRepeatOnlyKeyRelease() {
|
|||
// press keybind
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
|
||||
// await flag
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
EXPECT(checkFlag(), true);
|
||||
// release key, keep modifier
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 0,7,29"));
|
||||
// await repeat delay
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
clearFlag();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
EXPECT(checkFlag(), false);
|
||||
// check that it is not repeating
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
EXPECT(checkFlag(), false);
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
|
||||
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
|
||||
|
|
@ -315,9 +327,9 @@ static void testShortcutLongPress() {
|
|||
// press keybind
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29"));
|
||||
// await repeat delay
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(150));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29"));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(150));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
const std::string output = readKittyOutput();
|
||||
int yCount = Tests::countOccurrences(output, "y");
|
||||
// sometimes 1, sometimes 2, not sure why
|
||||
|
|
@ -347,7 +359,7 @@ static void testShortcutLongPressKeyRelease() {
|
|||
// release key, keep modifier
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 0,7,29"));
|
||||
// await repeat delay
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(150));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
const std::string output = readKittyOutput();
|
||||
// disabled: doesn't work on CI
|
||||
// EXPECT_COUNT_STRING(output, "y", 1);
|
||||
|
|
@ -454,6 +466,35 @@ static void testSubmap() {
|
|||
Tests::killAllWindows();
|
||||
}
|
||||
|
||||
static void testBindsAfterScroll() {
|
||||
NLog::log("{}Testing binds after scroll", Colors::GREEN);
|
||||
|
||||
clearFlag();
|
||||
OK(getFromSocket("/keyword binds Alt_R,w,exec,touch " + flagFile));
|
||||
|
||||
// press keybind before scroll
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 1,0,108")); // Alt_R press
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 1,4,25")); // w press
|
||||
EXPECT(attemptCheckFlag(20, 50), true);
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 0,4,25")); // w release
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,108")); // Alt_R release
|
||||
|
||||
// scroll
|
||||
OK(getFromSocket("/dispatch plugin:test:scroll 120"));
|
||||
OK(getFromSocket("/dispatch plugin:test:scroll -120"));
|
||||
OK(getFromSocket("/dispatch plugin:test:scroll 120"));
|
||||
|
||||
// press keybind after scroll
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 1,0,108")); // Alt_R press
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 1,4,25")); // w press
|
||||
EXPECT(attemptCheckFlag(20, 50), true);
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 0,4,25")); // w release
|
||||
OK(getFromSocket("/dispatch plugin:test:keybind 0,0,108")); // Alt_R release
|
||||
|
||||
clearFlag();
|
||||
OK(getFromSocket("/keyword unbind Alt_R,w"));
|
||||
}
|
||||
|
||||
static void testSubmapUniversal() {
|
||||
NLog::log("{}Testing submap universal", Colors::GREEN);
|
||||
|
||||
|
|
@ -482,9 +523,39 @@ static void testSubmapUniversal() {
|
|||
EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok");
|
||||
}
|
||||
|
||||
static void testPerDeviceKeybind() {
|
||||
NLog::log("{}Testing per-device binds", Colors::GREEN);
|
||||
|
||||
// Inclusive
|
||||
EXPECT(checkFlag(), false);
|
||||
EXPECT(getFromSocket("/keyword bindk SUPER,Y,test-keyboard-1,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");
|
||||
|
||||
// Exclusive
|
||||
EXPECT(checkFlag(), false);
|
||||
EXPECT(getFromSocket("/keyword bindk SUPER,Y,!test-keyboard-1,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");
|
||||
|
||||
// With description
|
||||
EXPECT(checkFlag(), false);
|
||||
EXPECT(getFromSocket("/keyword binddk SUPER,Y,test-keyboard-1,test description,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");
|
||||
}
|
||||
|
||||
static bool test() {
|
||||
NLog::log("{}Testing keybinds", Colors::GREEN);
|
||||
|
||||
clearFlag();
|
||||
|
||||
testBind();
|
||||
testBindKey();
|
||||
testLongPress();
|
||||
|
|
@ -503,6 +574,8 @@ static bool test() {
|
|||
testShortcutRepeatKeyRelease();
|
||||
testSubmap();
|
||||
testSubmapUniversal();
|
||||
testBindsAfterScroll();
|
||||
testPerDeviceKeybind();
|
||||
|
||||
clearFlag();
|
||||
return !ret;
|
||||
|
|
|
|||
53
hyprtester/src/tests/main/layer.cpp
Normal file
53
hyprtester/src/tests/main/layer.cpp
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
#include "../../Log.hpp"
|
||||
#include "../shared.hpp"
|
||||
#include "tests.hpp"
|
||||
#include "../../shared.hpp"
|
||||
#include "../../hyprctlCompat.hpp"
|
||||
#include <hyprutils/os/Process.hpp>
|
||||
#include <hyprutils/memory/WeakPtr.hpp>
|
||||
|
||||
static int ret = 0;
|
||||
|
||||
using namespace Hyprutils::OS;
|
||||
using namespace Hyprutils::Memory;
|
||||
|
||||
static bool spawnLayer(const std::string& namespace_) {
|
||||
NLog::log("{}Spawning kitty layer {}", Colors::YELLOW, namespace_);
|
||||
if (!Tests::spawnLayerKitty(namespace_)) {
|
||||
NLog::log("{}Error: {} layer did not spawn", Colors::RED, namespace_);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool test() {
|
||||
NLog::log("{}Testing plugin layerrules", Colors::GREEN);
|
||||
|
||||
if (!spawnLayer("rule-layer"))
|
||||
return false;
|
||||
|
||||
OK(getFromSocket("/dispatch plugin:test:add_layer_rule"));
|
||||
OK(getFromSocket("/reload"));
|
||||
|
||||
OK(getFromSocket("/keyword layerrule match:namespace rule-layer, plugin_rule effect"));
|
||||
|
||||
if (!spawnLayer("rule-layer"))
|
||||
return false;
|
||||
|
||||
if (!spawnLayer("norule-layer"))
|
||||
return false;
|
||||
|
||||
OK(getFromSocket("/dispatch plugin:test:check_layer_rule"));
|
||||
|
||||
OK(getFromSocket("/reload"));
|
||||
|
||||
NLog::log("{}Killing all layers", Colors::YELLOW);
|
||||
Tests::killAllLayers();
|
||||
|
||||
NLog::log("{}Expecting 0 layers", Colors::YELLOW);
|
||||
EXPECT(Tests::layerCount(), 0);
|
||||
|
||||
return !ret;
|
||||
}
|
||||
|
||||
REGISTER_TEST_FN(test)
|
||||
191
hyprtester/src/tests/main/layout.cpp
Normal file
191
hyprtester/src/tests/main/layout.cpp
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
#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 testFocusMRUAfterClose() {
|
||||
NLog::log("{}Testing focus after close (MRU order)", Colors::GREEN);
|
||||
|
||||
OK(getFromSocket("/reload"));
|
||||
OK(getFromSocket("/keyword dwindle:default_split_ratio 1.25"));
|
||||
OK(getFromSocket("/keyword input:focus_on_close 2"));
|
||||
|
||||
EXPECT(!!Tests::spawnKitty("kitty_A"), true);
|
||||
EXPECT(!!Tests::spawnKitty("kitty_B"), true);
|
||||
EXPECT(!!Tests::spawnKitty("kitty_C"), true);
|
||||
|
||||
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
|
||||
OK(getFromSocket("/dispatch focuswindow class:kitty_B"));
|
||||
OK(getFromSocket("/dispatch focuswindow class:kitty_C"));
|
||||
|
||||
OK(getFromSocket("/dispatch killactive"));
|
||||
Tests::waitUntilWindowsN(2);
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT(str.contains("class: kitty_B"), true);
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch killactive"));
|
||||
Tests::waitUntilWindowsN(1);
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT(str.contains("class: kitty_A"), true);
|
||||
}
|
||||
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool testFocusPreservedLayoutChange() {
|
||||
NLog::log("{}Testing focus is preserved on layout change", Colors::GREEN);
|
||||
|
||||
OK(getFromSocket("/keyword general:layout master"));
|
||||
|
||||
EXPECT(!!Tests::spawnKitty("kitty_A"), true);
|
||||
EXPECT(!!Tests::spawnKitty("kitty_B"), true);
|
||||
EXPECT(!!Tests::spawnKitty("kitty_C"), true);
|
||||
EXPECT(!!Tests::spawnKitty("kitty_D"), true);
|
||||
|
||||
OK(getFromSocket("/dispatch focuswindow class:kitty_C"));
|
||||
|
||||
OK(getFromSocket("/keyword general:layout monocle"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT(str.contains("class: kitty_C"), true);
|
||||
}
|
||||
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
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();
|
||||
testFocusMRUAfterClose();
|
||||
testFocusPreservedLayoutChange();
|
||||
|
||||
// clean up
|
||||
NLog::log("Cleaning up", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch workspace 1"));
|
||||
OK(getFromSocket("/reload"));
|
||||
|
||||
return !ret;
|
||||
}
|
||||
|
||||
REGISTER_TEST_FN(test);
|
||||
|
|
@ -3,7 +3,53 @@
|
|||
#include "../../hyprctlCompat.hpp"
|
||||
#include "tests.hpp"
|
||||
|
||||
static int ret = 0;
|
||||
static int ret = 0;
|
||||
|
||||
// reqs 1 master 3 slaves
|
||||
static void testOrientations() {
|
||||
OK(getFromSocket("/keyword master:orientation top"));
|
||||
|
||||
// top
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 22,22");
|
||||
EXPECT_CONTAINS(str, "size: 1876");
|
||||
}
|
||||
|
||||
// cycle = top, right, bottom, center, left
|
||||
|
||||
// right
|
||||
OK(getFromSocket("/dispatch layoutmsg orientationnext"));
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 873,22");
|
||||
EXPECT_CONTAINS(str, "size: 1025,1036");
|
||||
}
|
||||
|
||||
// bottom
|
||||
OK(getFromSocket("/dispatch layoutmsg orientationnext"));
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 22,495");
|
||||
EXPECT_CONTAINS(str, "size: 1876");
|
||||
}
|
||||
|
||||
// center
|
||||
OK(getFromSocket("/dispatch layoutmsg orientationnext"));
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 450,22");
|
||||
EXPECT_CONTAINS(str, "size: 1020,1036");
|
||||
}
|
||||
|
||||
// left
|
||||
OK(getFromSocket("/dispatch layoutmsg orientationnext"));
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 22,22");
|
||||
EXPECT_CONTAINS(str, "size: 1025,1036");
|
||||
}
|
||||
}
|
||||
|
||||
static void focusMasterPrevious() {
|
||||
// setup
|
||||
|
|
@ -44,11 +90,74 @@ static void focusMasterPrevious() {
|
|||
OK(getFromSocket("/dispatch layoutmsg focusmaster previous"));
|
||||
EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: master");
|
||||
|
||||
testOrientations();
|
||||
|
||||
// clean up
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
}
|
||||
|
||||
static void testFsBehavior() {
|
||||
// Master will re-send data to fullscreen / maximized windows, which can interfere with misc:on_focus_under_fullscreen
|
||||
// check that it doesn't.
|
||||
|
||||
for (auto const& win : {"master", "slave1", "slave2"}) {
|
||||
if (!Tests::spawnKitty(win)) {
|
||||
NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win);
|
||||
++TESTS_FAILED;
|
||||
ret = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch focuswindow class:master"));
|
||||
OK(getFromSocket("/dispatch fullscreen 1"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 22,22");
|
||||
EXPECT_CONTAINS(str, "size: 1876,1036");
|
||||
EXPECT_CONTAINS(str, "class: master");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 1"));
|
||||
|
||||
Tests::spawnKitty("new_master");
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 22,22");
|
||||
EXPECT_CONTAINS(str, "size: 1876,1036");
|
||||
EXPECT_CONTAINS(str, "class: new_master");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 1");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 0"));
|
||||
|
||||
Tests::spawnKitty("ignored");
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "at: 22,22");
|
||||
EXPECT_CONTAINS(str, "size: 1876,1036");
|
||||
EXPECT_CONTAINS(str, "class: new_master");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 1");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 2"));
|
||||
|
||||
Tests::spawnKitty("vaxwashere");
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "class: vaxwashere");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 0");
|
||||
}
|
||||
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
}
|
||||
|
||||
static bool test() {
|
||||
NLog::log("{}Testing Master layout", Colors::GREEN);
|
||||
|
||||
|
|
@ -60,6 +169,9 @@ static bool test() {
|
|||
NLog::log("{}Testing `focusmaster previous` layoutmsg", Colors::GREEN);
|
||||
focusMasterPrevious();
|
||||
|
||||
NLog::log("{}Testing fs behavior", Colors::GREEN);
|
||||
testFsBehavior();
|
||||
|
||||
// clean up
|
||||
NLog::log("Cleaning up", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch workspace 1"));
|
||||
|
|
|
|||
|
|
@ -18,6 +18,121 @@ using namespace Hyprutils::Memory;
|
|||
#define UP CUniquePointer
|
||||
#define SP CSharedPointer
|
||||
|
||||
// Uncomment once test vm can run hyprland-dialog
|
||||
// static void testAnrDialogs() {
|
||||
// NLog::log("{}Testing ANR dialogs", Colors::YELLOW);
|
||||
//
|
||||
// OK(getFromSocket("/keyword misc:enable_anr_dialog true"));
|
||||
// OK(getFromSocket("/keyword misc:anr_missed_pings 1"));
|
||||
//
|
||||
// NLog::log("{}ANR dialog: regular workspaces", Colors::YELLOW);
|
||||
// {
|
||||
// OK(getFromSocket("/dispatch workspace 2"));
|
||||
//
|
||||
// auto kitty = Tests::spawnKitty("bad_kitty");
|
||||
//
|
||||
// if (!kitty) {
|
||||
// ret = 1;
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// auto str = getFromSocket("/activewindow");
|
||||
// EXPECT_CONTAINS(str, "workspace: 2");
|
||||
// }
|
||||
//
|
||||
// OK(getFromSocket("/dispatch workspace 1"));
|
||||
//
|
||||
// ::kill(kitty->pid(), SIGSTOP);
|
||||
// Tests::waitUntilWindowsN(2);
|
||||
//
|
||||
// {
|
||||
// auto str = getFromSocket("/activeworkspace");
|
||||
// EXPECT_CONTAINS(str, "windows: 0");
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// OK(getFromSocket("/dispatch focuswindow class:hyprland-dialog"))
|
||||
// auto str = getFromSocket("/activewindow");
|
||||
// EXPECT_CONTAINS(str, "workspace: 2");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Tests::killAllWindows();
|
||||
//
|
||||
// NLog::log("{}ANR dialog: named workspaces", Colors::YELLOW);
|
||||
// {
|
||||
// OK(getFromSocket("/dispatch workspace name:yummy"));
|
||||
//
|
||||
// auto kitty = Tests::spawnKitty("bad_kitty");
|
||||
//
|
||||
// if (!kitty) {
|
||||
// ret = 1;
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// auto str = getFromSocket("/activewindow");
|
||||
// EXPECT_CONTAINS(str, "yummy");
|
||||
// }
|
||||
//
|
||||
// OK(getFromSocket("/dispatch workspace 1"));
|
||||
//
|
||||
// ::kill(kitty->pid(), SIGSTOP);
|
||||
// Tests::waitUntilWindowsN(2);
|
||||
//
|
||||
// {
|
||||
// auto str = getFromSocket("/activeworkspace");
|
||||
// EXPECT_CONTAINS(str, "windows: 0");
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// OK(getFromSocket("/dispatch focuswindow class:hyprland-dialog"))
|
||||
// auto str = getFromSocket("/activewindow");
|
||||
// EXPECT_CONTAINS(str, "yummy");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Tests::killAllWindows();
|
||||
//
|
||||
// NLog::log("{}ANR dialog: special workspaces", Colors::YELLOW);
|
||||
// {
|
||||
// OK(getFromSocket("/dispatch workspace special:apple"));
|
||||
//
|
||||
// auto kitty = Tests::spawnKitty("bad_kitty");
|
||||
//
|
||||
// if (!kitty) {
|
||||
// ret = 1;
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// auto str = getFromSocket("/activewindow");
|
||||
// EXPECT_CONTAINS(str, "special:apple");
|
||||
// }
|
||||
//
|
||||
// OK(getFromSocket("/dispatch togglespecialworkspace apple"));
|
||||
// OK(getFromSocket("/dispatch workspace 1"));
|
||||
//
|
||||
// ::kill(kitty->pid(), SIGSTOP);
|
||||
// Tests::waitUntilWindowsN(2);
|
||||
//
|
||||
// {
|
||||
// auto str = getFromSocket("/activeworkspace");
|
||||
// EXPECT_CONTAINS(str, "windows: 0");
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// OK(getFromSocket("/dispatch focuswindow class:hyprland-dialog"))
|
||||
// auto str = getFromSocket("/activewindow");
|
||||
// EXPECT_CONTAINS(str, "special:apple");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// OK(getFromSocket("/reload"));
|
||||
// Tests::killAllWindows();
|
||||
// }
|
||||
|
||||
static bool test() {
|
||||
NLog::log("{}Testing config: misc:", Colors::GREEN);
|
||||
|
||||
|
|
@ -62,6 +177,7 @@ static bool test() {
|
|||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 2");
|
||||
EXPECT_CONTAINS(str, "fullscreenClient: 2");
|
||||
EXPECT_CONTAINS(str, "kitty_A");
|
||||
}
|
||||
|
||||
|
|
@ -70,6 +186,7 @@ static bool test() {
|
|||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 2");
|
||||
EXPECT_CONTAINS(str, "fullscreenClient: 2");
|
||||
EXPECT_CONTAINS(str, "kitty_A");
|
||||
}
|
||||
|
||||
|
|
@ -79,6 +196,7 @@ static bool test() {
|
|||
// should be ignored as per focus_under_fullscreen 0
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 2");
|
||||
EXPECT_CONTAINS(str, "fullscreenClient: 2");
|
||||
EXPECT_CONTAINS(str, "kitty_A");
|
||||
}
|
||||
|
||||
|
|
@ -89,6 +207,7 @@ static bool test() {
|
|||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 2");
|
||||
EXPECT_CONTAINS(str, "fullscreenClient: 2");
|
||||
EXPECT_CONTAINS(str, "kitty_C");
|
||||
}
|
||||
|
||||
|
|
@ -99,6 +218,7 @@ static bool test() {
|
|||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 0");
|
||||
EXPECT_CONTAINS(str, "fullscreenClient: 0");
|
||||
EXPECT_CONTAINS(str, "kitty_D");
|
||||
}
|
||||
|
||||
|
|
@ -118,6 +238,7 @@ static bool test() {
|
|||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 2");
|
||||
EXPECT_CONTAINS(str, "fullscreenClient: 2");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch killwindow activewindow"));
|
||||
|
|
@ -126,6 +247,7 @@ static bool test() {
|
|||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 0");
|
||||
EXPECT_CONTAINS(str, "fullscreenClient: 0");
|
||||
}
|
||||
|
||||
Tests::spawnKitty("kitty_B");
|
||||
|
|
@ -138,6 +260,7 @@ static bool test() {
|
|||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 2");
|
||||
EXPECT_CONTAINS(str, "fullscreenClient: 2");
|
||||
}
|
||||
|
||||
Tests::killAllWindows();
|
||||
|
|
@ -153,6 +276,7 @@ static bool test() {
|
|||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 2");
|
||||
EXPECT_CONTAINS(str, "fullscreenClient: 2");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch fullscreen 0 unset"));
|
||||
|
|
@ -160,6 +284,7 @@ static bool test() {
|
|||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 0");
|
||||
EXPECT_CONTAINS(str, "fullscreenClient: 0");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch fullscreen 1 toggle"));
|
||||
|
|
@ -167,6 +292,7 @@ static bool test() {
|
|||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 1");
|
||||
EXPECT_CONTAINS(str, "fullscreenClient: 1");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch fullscreen 1 toggle"));
|
||||
|
|
@ -174,6 +300,23 @@ static bool test() {
|
|||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 0");
|
||||
EXPECT_CONTAINS(str, "fullscreenClient: 0");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch fullscreenstate 3 3 set"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 3");
|
||||
EXPECT_CONTAINS(str, "fullscreenClient: 3");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch fullscreenstate 3 3 set"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 3");
|
||||
EXPECT_CONTAINS(str, "fullscreenClient: 3");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch fullscreenstate 2 2 set"));
|
||||
|
|
@ -181,6 +324,7 @@ static bool test() {
|
|||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 2");
|
||||
EXPECT_CONTAINS(str, "fullscreenClient: 2");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch fullscreenstate 2 2 set"));
|
||||
|
|
@ -188,6 +332,7 @@ static bool test() {
|
|||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 2");
|
||||
EXPECT_CONTAINS(str, "fullscreenClient: 2");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch fullscreenstate 2 2 toggle"));
|
||||
|
|
@ -195,6 +340,7 @@ static bool test() {
|
|||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 0");
|
||||
EXPECT_CONTAINS(str, "fullscreenClient: 0");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch fullscreenstate 2 2 toggle"));
|
||||
|
|
@ -202,6 +348,7 @@ static bool test() {
|
|||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "fullscreen: 2");
|
||||
EXPECT_CONTAINS(str, "fullscreenClient: 2");
|
||||
}
|
||||
|
||||
// Ensure that the process autostarted in the config does not
|
||||
|
|
|
|||
111
hyprtester/src/tests/main/moveintoorcreategroup.cpp
Normal file
111
hyprtester/src/tests/main/moveintoorcreategroup.cpp
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
#include "tests.hpp"
|
||||
#include "../../shared.hpp"
|
||||
#include "../../hyprctlCompat.hpp"
|
||||
#include <print>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <hyprutils/os/Process.hpp>
|
||||
#include <hyprutils/memory/WeakPtr.hpp>
|
||||
#include <csignal>
|
||||
#include <cerrno>
|
||||
#include "../shared.hpp"
|
||||
|
||||
static int ret = 0;
|
||||
|
||||
using namespace Hyprutils::OS;
|
||||
using namespace Hyprutils::Memory;
|
||||
|
||||
#define UP CUniquePointer
|
||||
#define SP CSharedPointer
|
||||
|
||||
static bool test() {
|
||||
NLog::log("{}Testing moveintoorcreategroup", Colors::GREEN);
|
||||
|
||||
NLog::log("{}Dispatching workspace `moveintoorcreategroup`", Colors::YELLOW);
|
||||
getFromSocket("/dispatch workspace name:moveintoorcreategroup");
|
||||
|
||||
OK(getFromSocket("/keyword group:auto_group false"));
|
||||
|
||||
NLog::log("{}Spawning kittyA", Colors::YELLOW);
|
||||
auto kittyA = Tests::spawnKitty("kitty_A");
|
||||
if (!kittyA) {
|
||||
NLog::log("{}Error: kittyA did not spawn", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
NLog::log("{}Spawning kittyB", Colors::YELLOW);
|
||||
auto kittyB = Tests::spawnKitty("kitty_B");
|
||||
if (!kittyB) {
|
||||
NLog::log("{}Error: kittyB did not spawn", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
NLog::log("{}Expecting 2 windows", Colors::YELLOW);
|
||||
EXPECT(Tests::windowCount(), 2);
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT_CONTAINS(str, "grouped: 0");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
|
||||
|
||||
NLog::log("{}Move kittyA into group with kittyB (creates group)", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch moveintoorcreategroup r"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT_CONTAINS(str, "grouped:");
|
||||
}
|
||||
|
||||
NLog::log("{}Verify active window is kitty_A (the moved window)", Colors::YELLOW);
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "kitty_A");
|
||||
}
|
||||
|
||||
NLog::log("{}Kill windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
|
||||
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
|
||||
NLog::log("{}Testing moveintoorcreategroup into existing group", Colors::YELLOW);
|
||||
|
||||
NLog::log("{}Spawning kittyC", Colors::YELLOW);
|
||||
auto kittyC = Tests::spawnKitty("kitty_C");
|
||||
NLog::log("{}Spawning kittyD", Colors::YELLOW);
|
||||
auto kittyD = Tests::spawnKitty("kitty_D");
|
||||
NLog::log("{}Spawning kittyE", Colors::YELLOW);
|
||||
auto kittyE = Tests::spawnKitty("kitty_E");
|
||||
|
||||
NLog::log("{}Expecting 3 windows", Colors::YELLOW);
|
||||
EXPECT(Tests::windowCount(), 3);
|
||||
|
||||
OK(getFromSocket("/dispatch focuswindow class:kitty_D"));
|
||||
OK(getFromSocket("/dispatch togglegroup"));
|
||||
|
||||
OK(getFromSocket("/dispatch focuswindow class:kitty_E"));
|
||||
|
||||
NLog::log("{}Move kittyE into existing group with kittyD", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch moveintoorcreategroup l"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT_CONTAINS(str, "grouped:");
|
||||
}
|
||||
|
||||
NLog::log("{}Verify active window is kitty_E (the moved window)", Colors::YELLOW);
|
||||
{
|
||||
auto str = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(str, "kitty_E");
|
||||
}
|
||||
|
||||
NLog::log("{}Kill windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
OK(getFromSocket("/keyword group:auto_group true"));
|
||||
|
||||
return !ret;
|
||||
}
|
||||
|
||||
REGISTER_TEST_FN(test)
|
||||
258
hyprtester/src/tests/main/scroll.cpp
Normal file
258
hyprtester/src/tests/main/scroll.cpp
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
#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 testWindowRule() {
|
||||
NLog::log("{}Testing Scrolling Width", Colors::GREEN);
|
||||
|
||||
// inject a new rule.
|
||||
OK(getFromSocket("/keyword windowrule[scrolling-width]:match:class kitty_scroll"));
|
||||
OK(getFromSocket("/keyword windowrule[scrolling-width]:scrolling_width 0.1"));
|
||||
|
||||
if (!Tests::spawnKitty("kitty_scroll")) {
|
||||
NLog::log("{}Failed to spawn kitty with win class `kitty_scroll`", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Tests::spawnKitty("kitty_scroll")) {
|
||||
NLog::log("{}Failed to spawn kitty with win class `kitty_scroll`", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
|
||||
EXPECT(Tests::windowCount(), 2);
|
||||
|
||||
// not the greatest test, but as long as res and gaps don't change, we good.
|
||||
EXPECT_CONTAINS(getFromSocket("/activewindow"), "size: 174,1036");
|
||||
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
testWindowRule();
|
||||
|
||||
// clean up
|
||||
NLog::log("Cleaning up", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch workspace 1"));
|
||||
OK(getFromSocket("/reload"));
|
||||
|
||||
return !ret;
|
||||
}
|
||||
|
||||
REGISTER_TEST_FN(test);
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
#include <unistd.h>
|
||||
#include <cmath>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
|
|
@ -6,6 +7,7 @@
|
|||
#include <thread>
|
||||
#include <hyprutils/os/Process.hpp>
|
||||
#include <hyprutils/memory/WeakPtr.hpp>
|
||||
#include <hyprutils/string/VarList2.hpp>
|
||||
|
||||
#include "../../shared.hpp"
|
||||
#include "../../hyprctlCompat.hpp"
|
||||
|
|
@ -23,15 +25,26 @@ static bool spawnKitty(const std::string& class_, const std::vector<std::string>
|
|||
return true;
|
||||
}
|
||||
|
||||
static std::string getWindowAttribute(const std::string& winInfo, const std::string& attr) {
|
||||
auto pos = winInfo.find(attr);
|
||||
if (pos == std::string::npos) {
|
||||
NLog::log("{}Wrong window attribute", Colors::RED);
|
||||
ret = 1;
|
||||
return "Wrong window attribute";
|
||||
/// 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 "";
|
||||
}
|
||||
auto pos2 = winInfo.find('\n', pos);
|
||||
return winInfo.substr(pos, pos2 - pos);
|
||||
(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 getWindowAddress(const std::string& winInfo) {
|
||||
|
|
@ -68,10 +81,10 @@ static void testSwapWindow() {
|
|||
// Test swapwindow by direction
|
||||
{
|
||||
getFromSocket("/dispatch focuswindow class:kitty_A");
|
||||
auto pos = getWindowAttribute(getFromSocket("/activewindow"), "at:");
|
||||
NLog::log("{}Testing kitty_A {}, swapwindow with direction 'l'", Colors::YELLOW, pos);
|
||||
auto pos = Tests::getWindowAttribute(getFromSocket("/activewindow"), "at:");
|
||||
NLog::log("{}Testing kitty_A {}, swapwindow with direction 'r'", Colors::YELLOW, pos);
|
||||
|
||||
OK(getFromSocket("/dispatch swapwindow l"));
|
||||
OK(getFromSocket("/dispatch swapwindow r"));
|
||||
OK(getFromSocket("/dispatch focuswindow class:kitty_B"));
|
||||
|
||||
EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("{}", pos));
|
||||
|
|
@ -80,7 +93,7 @@ static void testSwapWindow() {
|
|||
// Test swapwindow by class
|
||||
{
|
||||
getFromSocket("/dispatch focuswindow class:kitty_A");
|
||||
auto pos = getWindowAttribute(getFromSocket("/activewindow"), "at:");
|
||||
auto pos = Tests::getWindowAttribute(getFromSocket("/activewindow"), "at:");
|
||||
NLog::log("{}Testing kitty_A {}, swapwindow with class:kitty_B", Colors::YELLOW, pos);
|
||||
|
||||
OK(getFromSocket("/dispatch swapwindow class:kitty_B"));
|
||||
|
|
@ -94,7 +107,7 @@ static void testSwapWindow() {
|
|||
getFromSocket("/dispatch focuswindow class:kitty_B");
|
||||
auto addr = getWindowAddress(getFromSocket("/activewindow"));
|
||||
getFromSocket("/dispatch focuswindow class:kitty_A");
|
||||
auto pos = getWindowAttribute(getFromSocket("/activewindow"), "at:");
|
||||
auto pos = Tests::getWindowAttribute(getFromSocket("/activewindow"), "at:");
|
||||
NLog::log("{}Testing kitty_A {}, swapwindow with address:0x{}(kitty_B)", Colors::YELLOW, pos, addr);
|
||||
|
||||
OK(getFromSocket(std::format("/dispatch swapwindow address:0x{}", addr)));
|
||||
|
|
@ -117,7 +130,7 @@ static void testSwapWindow() {
|
|||
{
|
||||
getFromSocket("/dispatch focuswindow class:kitty_B");
|
||||
auto addr = getWindowAddress(getFromSocket("/activewindow"));
|
||||
auto ws = getWindowAttribute(getFromSocket("/activewindow"), "workspace:");
|
||||
auto ws = Tests::getWindowAttribute(getFromSocket("/activewindow"), "workspace:");
|
||||
NLog::log("{}Sending address:0x{}(kitty_B) to workspace \"swapwindow2\"", Colors::YELLOW, addr);
|
||||
|
||||
OK(getFromSocket("/dispatch movetoworkspacesilent name:swapwindow2"));
|
||||
|
|
@ -196,10 +209,10 @@ static void testGroupRules() {
|
|||
Tests::killAllWindows();
|
||||
}
|
||||
|
||||
static bool isActiveWindow(const std::string& class_, char fullscreen, bool log = true) {
|
||||
static bool isActiveWindow(const std::string& class_, char fullscreen = '0', bool log = true) {
|
||||
std::string activeWin = getFromSocket("/activewindow");
|
||||
auto winClass = getWindowAttribute(activeWin, "class:");
|
||||
auto winFullscreen = getWindowAttribute(activeWin, "fullscreen:").back();
|
||||
auto winClass = Tests::getWindowAttribute(activeWin, "class:");
|
||||
auto winFullscreen = Tests::getWindowAttribute(activeWin, "fullscreen:").back();
|
||||
if (winClass.substr(strlen("class: ")) == class_ && winFullscreen == fullscreen)
|
||||
return true;
|
||||
else {
|
||||
|
|
@ -209,13 +222,13 @@ static bool isActiveWindow(const std::string& class_, char fullscreen, bool log
|
|||
}
|
||||
}
|
||||
|
||||
static bool waitForActiveWindow(const std::string& class_, char fullscreen, int maxTries = 50) {
|
||||
static bool waitForActiveWindow(const std::string& class_, char fullscreen = '0', bool logLastCheck = true, int maxTries = 50) {
|
||||
int cnt = 0;
|
||||
while (!isActiveWindow(class_, fullscreen, false)) {
|
||||
++cnt;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
if (cnt > maxTries) {
|
||||
return isActiveWindow(class_, fullscreen, true);
|
||||
return isActiveWindow(class_, fullscreen, logLastCheck);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
|
@ -231,24 +244,6 @@ static bool testWindowFocusOnFullscreenConflict() {
|
|||
|
||||
OK(getFromSocket("/keyword misc:focus_on_activate true"));
|
||||
|
||||
auto spawnKittyActivating = [] -> std::string {
|
||||
// `XXXXXX` is what `mkstemp` expects to find in the string
|
||||
std::string tmpFilename = (std::filesystem::temp_directory_path() / "XXXXXX").string();
|
||||
int fd = mkstemp(tmpFilename.data());
|
||||
if (fd < 0) {
|
||||
NLog::log("{}Error: could not create tmp file: errno {}", Colors::RED, errno);
|
||||
return "";
|
||||
}
|
||||
(void)close(fd);
|
||||
bool ok = spawnKitty("kitty_activating",
|
||||
{"-o", "allow_remote_control=yes", "--", "/bin/sh", "-c", "while [ -f \"" + tmpFilename + "\" ]; do :; done; kitten @ focus-window; sleep infinity"});
|
||||
if (!ok) {
|
||||
NLog::log("{}Error: failed to spawn kitty", Colors::RED);
|
||||
return "";
|
||||
}
|
||||
return tmpFilename;
|
||||
};
|
||||
|
||||
// Unfullscreen on conflict
|
||||
{
|
||||
OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 2"));
|
||||
|
|
@ -343,6 +338,365 @@ static bool testWindowFocusOnFullscreenConflict() {
|
|||
return true;
|
||||
}
|
||||
|
||||
static void testMaximizeSize() {
|
||||
NLog::log("{}Testing maximize size", Colors::GREEN);
|
||||
|
||||
EXPECT(spawnKitty("kitty_A"), true);
|
||||
|
||||
// check kitty properties. Maximizing shouldnt change its size
|
||||
{
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT(str.contains("at: 22,22"), true);
|
||||
EXPECT(str.contains("size: 1876,1036"), true);
|
||||
EXPECT(str.contains("fullscreen: 0"), true);
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch fullscreen 1"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT(str.contains("at: 22,22"), true);
|
||||
EXPECT(str.contains("size: 1876,1036"), true);
|
||||
EXPECT(str.contains("fullscreen: 1"), true);
|
||||
}
|
||||
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
|
||||
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
}
|
||||
|
||||
static 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);
|
||||
|
||||
Tests::killAllWindows();
|
||||
OK(getFromSocket("/keyword input:follow_mouse 2"));
|
||||
OK(getFromSocket("/keyword input:float_switch_override_focus 0"));
|
||||
|
||||
EXPECT(spawnKitty("a"), true);
|
||||
OK(getFromSocket("/dispatch setfloating"));
|
||||
OK(getFromSocket("/dispatch movewindowpixel exact 500 300,activewindow"));
|
||||
OK(getFromSocket("/dispatch resizewindowpixel exact 400 400,activewindow"));
|
||||
|
||||
EXPECT(spawnKitty("b"), true);
|
||||
OK(getFromSocket("/dispatch setfloating"));
|
||||
OK(getFromSocket("/dispatch movewindowpixel exact 500 300,activewindow"));
|
||||
OK(getFromSocket("/dispatch resizewindowpixel exact 400 400,activewindow"));
|
||||
|
||||
auto getTopWindow = []() -> std::string {
|
||||
auto clients = getFromSocket("/clients");
|
||||
return (clients.rfind("class: a") > clients.rfind("class: b")) ? "a" : "b";
|
||||
};
|
||||
|
||||
EXPECT(getTopWindow(), std::string("b"));
|
||||
OK(getFromSocket("/dispatch movecursor 700 500"));
|
||||
|
||||
OK(getFromSocket("/dispatch focuswindow class:a"));
|
||||
EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: a");
|
||||
|
||||
OK(getFromSocket("/dispatch bringactivetotop"));
|
||||
EXPECT(getTopWindow(), std::string("a"));
|
||||
|
||||
OK(getFromSocket("/dispatch plugin:test:click 272,1"));
|
||||
OK(getFromSocket("/dispatch plugin:test:click 272,0"));
|
||||
|
||||
EXPECT(getTopWindow(), std::string("a"));
|
||||
|
||||
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 testContentRules() {
|
||||
NLog::log("{}Testing content window rules", Colors::YELLOW);
|
||||
|
||||
// kill me PLEASE
|
||||
|
||||
OK(getFromSocket("/keyword windowrule match:class kitty_content_string, content game"));
|
||||
OK(getFromSocket("/keyword windowrule match:class kitty_content_numbers, content 3"));
|
||||
OK(getFromSocket("/keyword windowrule match:content game, border_size 10"));
|
||||
OK(getFromSocket("/keyword windowrule match:content 3, opacity 0.5"));
|
||||
|
||||
const auto testProps = []() {
|
||||
EXPECT_CONTAINS(getFromSocket("/getprop active border_size"), "10");
|
||||
EXPECT_CONTAINS(getFromSocket("/getprop active opacity"), "0.5");
|
||||
};
|
||||
if (!spawnKitty("kitty_content_string"))
|
||||
return false;
|
||||
waitForActiveWindow("kitty_content_string");
|
||||
testProps();
|
||||
|
||||
Tests::killAllWindows();
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
|
||||
if (!spawnKitty("kitty_content_numbers"))
|
||||
return false;
|
||||
waitForActiveWindow("kitty_content_numbers");
|
||||
testProps();
|
||||
|
||||
Tests::killAllWindows();
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool test14038() {
|
||||
NLog::log("{}Testing #14038 crash", Colors::YELLOW);
|
||||
|
||||
if (!spawnKitty("kitty_14038"))
|
||||
return false;
|
||||
|
||||
OK(getFromSocket("/dispatch movetoworkspacesilent special:a,class:kitty_14038"));
|
||||
OK(getFromSocket("/dispatch togglefloating class:kitty_14038"));
|
||||
OK(getFromSocket("/dispatch pin class:kitty_14038"));
|
||||
OK(getFromSocket("/dispatch togglefloating class:kitty_14038"));
|
||||
|
||||
// this should not crash hyprland. If we are alive, we good.
|
||||
|
||||
Tests::killAllWindows();
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool test() {
|
||||
NLog::log("{}Testing windows", Colors::GREEN);
|
||||
|
||||
|
|
@ -364,22 +718,40 @@ static bool test() {
|
|||
|
||||
NLog::log("{}Testing window split ratios", Colors::YELLOW);
|
||||
{
|
||||
const double RATIO = 1.25;
|
||||
const double PERCENT = RATIO / 2.0 * 100.0;
|
||||
const int GAPSIN = 5;
|
||||
const int GAPSOUT = 20;
|
||||
const int BORDERS = 2 * 2;
|
||||
const int WTRIM = BORDERS + GAPSIN + GAPSOUT;
|
||||
const int HEIGHT = 1080 - (BORDERS + (GAPSOUT * 2));
|
||||
const int WIDTH1 = std::round(1920.0 / 2.0 * (2 - RATIO)) - WTRIM;
|
||||
const int WIDTH2 = std::round(1920.0 / 2.0 * RATIO) - WTRIM;
|
||||
const double INITIAL_RATIO = 1.25;
|
||||
const int GAPSIN = 5;
|
||||
const int GAPSOUT = 20;
|
||||
const int BORDERSIZE = 2;
|
||||
const int BORDERS = BORDERSIZE * 2;
|
||||
const int MONITOR_W = 1920;
|
||||
const int MONITOR_H = 1080;
|
||||
|
||||
const float totalAvailableHeight = MONITOR_H - (GAPSOUT * 2);
|
||||
const int HEIGHT = std::floor(totalAvailableHeight) - BORDERS;
|
||||
const float availableWidthForSplit = MONITOR_W - (GAPSOUT * 2) - GAPSIN;
|
||||
|
||||
auto calculateFinalWidth = [&](double boxWidth, bool isLeftWindow) {
|
||||
double gapLeft = isLeftWindow ? GAPSOUT : GAPSIN;
|
||||
double gapRight = isLeftWindow ? GAPSIN : GAPSOUT;
|
||||
return std::floor(boxWidth - gapLeft - gapRight - BORDERS);
|
||||
};
|
||||
|
||||
double geomBoxWidthA_R1 = (availableWidthForSplit * INITIAL_RATIO / 2.0) + GAPSOUT + (GAPSIN / 2.0);
|
||||
double geomBoxWidthB_R1 = MONITOR_W - geomBoxWidthA_R1;
|
||||
const int WIDTH1 = calculateFinalWidth(geomBoxWidthB_R1, false);
|
||||
|
||||
const double INVERTED_RATIO = 0.75;
|
||||
double geomBoxWidthA_R2 = (availableWidthForSplit * INVERTED_RATIO / 2.0) + GAPSOUT + (GAPSIN / 2.0);
|
||||
double geomBoxWidthB_R2 = MONITOR_W - geomBoxWidthA_R2;
|
||||
const int WIDTH2 = calculateFinalWidth(geomBoxWidthB_R2, false);
|
||||
const int WIDTH_A_FINAL = calculateFinalWidth(geomBoxWidthA_R2, true);
|
||||
|
||||
OK(getFromSocket("/keyword dwindle:default_split_ratio 1.25"));
|
||||
|
||||
if (!spawnKitty("kitty_B"))
|
||||
return false;
|
||||
|
||||
NLog::log("{}Expecting kitty_B to take up roughly {}% of screen width", Colors::YELLOW, 100 - PERCENT);
|
||||
NLog::log("{}Expecting kitty_B size: {},{}", Colors::YELLOW, WIDTH1, HEIGHT);
|
||||
EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("size: {},{}", WIDTH1, HEIGHT));
|
||||
|
||||
OK(getFromSocket("/dispatch killwindow activewindow"));
|
||||
|
|
@ -391,12 +763,38 @@ static bool test() {
|
|||
if (!spawnKitty("kitty_B"))
|
||||
return false;
|
||||
|
||||
NLog::log("{}Expecting kitty_B to take up roughly {}% of screen width", Colors::YELLOW, PERCENT);
|
||||
EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("size: {},{}", WIDTH2, HEIGHT));
|
||||
try {
|
||||
NLog::log("{}Expecting kitty_B size: {},{}", Colors::YELLOW, WIDTH2, HEIGHT);
|
||||
|
||||
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
|
||||
NLog::log("{}Expecting kitty_A to have the same width as the previous kitty_B", Colors::YELLOW);
|
||||
EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("size: {},{}", WIDTH1, HEIGHT));
|
||||
{
|
||||
auto data = getFromSocket("/activewindow");
|
||||
data = data.substr(data.find("size:") + 5);
|
||||
data = data.substr(0, data.find('\n'));
|
||||
|
||||
Hyprutils::String::CVarList2 sizes(std::move(data), 0, ',');
|
||||
|
||||
EXPECT_MAX_DELTA(std::stoi(std::string{sizes[0]}), WIDTH2, 2);
|
||||
EXPECT_MAX_DELTA(std::stoi(std::string{sizes[1]}), HEIGHT, 2);
|
||||
}
|
||||
|
||||
OK(getFromSocket("/dispatch focuswindow class:kitty_A"));
|
||||
NLog::log("{}Expecting kitty_A size: {},{}", Colors::YELLOW, WIDTH_A_FINAL, HEIGHT);
|
||||
|
||||
{
|
||||
auto data = getFromSocket("/activewindow");
|
||||
data = data.substr(data.find("size:") + 5);
|
||||
data = data.substr(0, data.find('\n'));
|
||||
|
||||
Hyprutils::String::CVarList2 sizes(std::move(data), 0, ',');
|
||||
|
||||
EXPECT_MAX_DELTA(std::stoi(std::string{sizes[0]}), WIDTH_A_FINAL, 2);
|
||||
EXPECT_MAX_DELTA(std::stoi(std::string{sizes[1]}), HEIGHT, 2);
|
||||
}
|
||||
|
||||
} catch (...) {
|
||||
NLog::log("{}Exception thrown", Colors::RED);
|
||||
EXPECT(false, true);
|
||||
}
|
||||
|
||||
OK(getFromSocket("/keyword dwindle:default_split_ratio 1"));
|
||||
}
|
||||
|
|
@ -412,14 +810,14 @@ static bool test() {
|
|||
EXPECT(Tests::windowCount(), 3);
|
||||
|
||||
NLog::log("{}Checking props of xeyes", Colors::YELLOW);
|
||||
// check some window props of xeyes, try to tile them
|
||||
// check some window props of xeyes, try to float it
|
||||
{
|
||||
auto str = getFromSocket("/clients");
|
||||
EXPECT_CONTAINS(str, "floating: 1");
|
||||
getFromSocket("/dispatch settiled class:XEyes");
|
||||
EXPECT_NOT_CONTAINS(str, "floating: 1");
|
||||
getFromSocket("/dispatch setfloating class:XEyes");
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
str = getFromSocket("/clients");
|
||||
EXPECT_NOT_CONTAINS(str, "floating: 1");
|
||||
EXPECT_CONTAINS(str, "floating: 1");
|
||||
}
|
||||
|
||||
// kill all
|
||||
|
|
@ -477,10 +875,10 @@ static bool test() {
|
|||
EXPECT_CONTAINS(dwindle, "size: 1500,500");
|
||||
EXPECT_CONTAINS(dwindle, "at: 210,290");
|
||||
|
||||
if (!spawnKitty("kitty_maxsize"))
|
||||
return false;
|
||||
|
||||
EXPECT_CONTAINS(getFromSocket("/activewindow"), "size: 1200,500");
|
||||
// Fuck this test, it's fucking stupid - vax
|
||||
// if (!spawnKitty("kitty_maxsize"))
|
||||
// return false;
|
||||
// EXPECT_CONTAINS(getFromSocket("/activewindow"), "size: 1200,500");
|
||||
|
||||
Tests::killAllWindows();
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
|
|
@ -497,8 +895,69 @@ static bool test() {
|
|||
if (!spawnKitty("kitty_maxsize"))
|
||||
return false;
|
||||
|
||||
// FIXME: I can't be arsed.
|
||||
OK(getFromSocket("/dispatch focuswindow class:kitty_maxsize"));
|
||||
EXPECT_CONTAINS(getFromSocket("/activewindow"), "size: 1200,500")
|
||||
// EXPECT_CONTAINS(getFromSocket("/activewindow"), "size: 1200,500")
|
||||
|
||||
NLog::log("{}Reloading config", Colors::YELLOW);
|
||||
OK(getFromSocket("/reload"));
|
||||
Tests::killAllWindows();
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
}
|
||||
|
||||
NLog::log("{}Testing minsize/maxsize rules", Colors::YELLOW);
|
||||
{
|
||||
// Disable size limits tiled and check if props are working and not getting skipped
|
||||
OK(getFromSocket("/keyword misc:size_limits_tiled 0"));
|
||||
OK(getFromSocket("/keyword windowrule[kitty-max-rule]:match:class kitty_maxsize"));
|
||||
OK(getFromSocket("/keyword windowrule[kitty-max-rule]:max_size 1500 500"));
|
||||
OK(getFromSocket("r/keyword windowrule[kitty-max-rule]:min_size 1200 500"));
|
||||
if (!spawnKitty("kitty_maxsize"))
|
||||
return false;
|
||||
|
||||
{
|
||||
auto res = getFromSocket("/getprop active max_size");
|
||||
EXPECT_CONTAINS(res, "1500");
|
||||
EXPECT_CONTAINS(res, "500");
|
||||
}
|
||||
|
||||
{
|
||||
auto res = getFromSocket("/getprop active min_size");
|
||||
EXPECT_CONTAINS(res, "1200");
|
||||
EXPECT_CONTAINS(res, "500");
|
||||
}
|
||||
|
||||
NLog::log("{}Reloading config", Colors::YELLOW);
|
||||
OK(getFromSocket("/reload"));
|
||||
Tests::killAllWindows();
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
// Set float
|
||||
OK(getFromSocket("/keyword windowrule[kitty-max-rule]:match:class kitty_maxsize"));
|
||||
OK(getFromSocket("/keyword windowrule[kitty-max-rule]:max_size 1200 500"));
|
||||
OK(getFromSocket("r/keyword windowrule[kitty-max-rule]:min_size 1200 500"));
|
||||
OK(getFromSocket("r/keyword windowrule[kitty-max-rule]:float yes"));
|
||||
if (!spawnKitty("kitty_maxsize"))
|
||||
return false;
|
||||
|
||||
{
|
||||
auto res = getFromSocket("/getprop active max_size");
|
||||
EXPECT_CONTAINS(res, "1200");
|
||||
EXPECT_CONTAINS(res, "500");
|
||||
}
|
||||
|
||||
{
|
||||
auto res = getFromSocket("/getprop active min_size");
|
||||
EXPECT_CONTAINS(res, "1200");
|
||||
EXPECT_CONTAINS(res, "500");
|
||||
}
|
||||
|
||||
{
|
||||
auto res = getFromSocket("/activewindow");
|
||||
EXPECT_CONTAINS(res, "size: 1200,500");
|
||||
}
|
||||
|
||||
NLog::log("{}Reloading config", Colors::YELLOW);
|
||||
OK(getFromSocket("/reload"));
|
||||
|
|
@ -542,6 +1001,23 @@ static bool test() {
|
|||
|
||||
Tests::killAllWindows();
|
||||
|
||||
OK(getFromSocket("/keyword windowrule[border-magic-kitty]:match:class border_kitty"));
|
||||
OK(getFromSocket("/keyword windowrule[border-magic-kitty]:border_color rgba(c6ff00ff) rgba(ff0000ee) 45deg"));
|
||||
|
||||
if (!spawnKitty("border_kitty"))
|
||||
return false;
|
||||
|
||||
OK(getFromSocket("/dispatch focuswindow class:border_kitty"));
|
||||
|
||||
{
|
||||
auto str = getFromSocket("/getprop active active_border_color");
|
||||
EXPECT_CONTAINS(str, "ffc6ff00");
|
||||
EXPECT_CONTAINS(str, "eeff0000");
|
||||
EXPECT_CONTAINS(str, "45deg");
|
||||
}
|
||||
|
||||
Tests::killAllWindows();
|
||||
|
||||
if (!spawnKitty("tag_kitty"))
|
||||
return false;
|
||||
|
||||
|
|
@ -625,7 +1101,8 @@ static bool test() {
|
|||
Tests::killAllWindows();
|
||||
|
||||
// test expression rules
|
||||
OK(getFromSocket("/keyword windowrule match:class expr_kitty, float yes, size monitor_w*0.5 monitor_h*0.5, move 20+(monitor_w*0.1) monitor_h*0.5"));
|
||||
OK(getFromSocket("/keyword windowrule match:class expr_kitty, float yes, size monitor_w*0.5 monitor_h*0.5, min_size monitor_w*0.25 monitor_h*0.25, "
|
||||
"max_size monitor_w*0.75 monitor_h*0.75, move 20+(monitor_w*0.1) monitor_h*0.5"));
|
||||
|
||||
if (!spawnKitty("expr_kitty"))
|
||||
return false;
|
||||
|
|
@ -635,12 +1112,20 @@ static bool test() {
|
|||
EXPECT_CONTAINS(str, "floating: 1");
|
||||
EXPECT_CONTAINS(str, "at: 212,540");
|
||||
EXPECT_CONTAINS(str, "size: 960,540");
|
||||
|
||||
auto min = getFromSocket("/getprop active min_size");
|
||||
EXPECT_CONTAINS(min, "480");
|
||||
EXPECT_CONTAINS(min, "270");
|
||||
|
||||
auto max = getFromSocket("/getprop active max_size");
|
||||
EXPECT_CONTAINS(max, "1440");
|
||||
EXPECT_CONTAINS(max, "810");
|
||||
}
|
||||
|
||||
OK(getFromSocket("/reload"));
|
||||
Tests::killAllWindows();
|
||||
|
||||
OK(getFromSocket("/dispatch plugin:test:add_rule"));
|
||||
OK(getFromSocket("/dispatch plugin:test:add_window_rule"));
|
||||
OK(getFromSocket("/reload"));
|
||||
|
||||
OK(getFromSocket("/keyword windowrule match:class plugin_kitty, plugin_rule effect"));
|
||||
|
|
@ -648,12 +1133,12 @@ static bool test() {
|
|||
if (!spawnKitty("plugin_kitty"))
|
||||
return false;
|
||||
|
||||
OK(getFromSocket("/dispatch plugin:test:check_rule"));
|
||||
OK(getFromSocket("/dispatch plugin:test:check_window_rule"));
|
||||
|
||||
OK(getFromSocket("/reload"));
|
||||
Tests::killAllWindows();
|
||||
|
||||
OK(getFromSocket("/dispatch plugin:test:add_rule"));
|
||||
OK(getFromSocket("/dispatch plugin:test:add_window_rule"));
|
||||
OK(getFromSocket("/reload"));
|
||||
|
||||
OK(getFromSocket("/keyword windowrule[test-plugin-rule]:match:class plugin_kitty"));
|
||||
|
|
@ -662,12 +1147,22 @@ static bool test() {
|
|||
if (!spawnKitty("plugin_kitty"))
|
||||
return false;
|
||||
|
||||
OK(getFromSocket("/dispatch plugin:test:check_rule"));
|
||||
OK(getFromSocket("/dispatch plugin:test:check_window_rule"));
|
||||
|
||||
OK(getFromSocket("/reload"));
|
||||
Tests::killAllWindows();
|
||||
|
||||
testGroupRules();
|
||||
testMaximizeSize();
|
||||
testFloatingFocusOnFullscreen();
|
||||
testBringActiveToTopMouseMovement();
|
||||
testGroupFallbackFocus();
|
||||
testInitialFloatSize();
|
||||
testWindowRuleFocusOnActivate();
|
||||
testPinnedWorkspacesValid();
|
||||
testWindowRuleWorkspaceEmpty();
|
||||
testContentRules();
|
||||
test14038();
|
||||
|
||||
NLog::log("{}Reloading config", Colors::YELLOW);
|
||||
OK(getFromSocket("/reload"));
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include <chrono>
|
||||
#include <hyprutils/os/Process.hpp>
|
||||
#include <hyprutils/memory/WeakPtr.hpp>
|
||||
#include <hyprutils/utils/ScopeGuard.hpp>
|
||||
#include <csignal>
|
||||
#include <cerrno>
|
||||
#include "../shared.hpp"
|
||||
|
|
@ -14,10 +15,413 @@ static int ret = 0;
|
|||
|
||||
using namespace Hyprutils::OS;
|
||||
using namespace Hyprutils::Memory;
|
||||
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);
|
||||
{
|
||||
|
||||
CScopeGuard guard = {[&]() {
|
||||
NLog::log("{}Cleaning up asymmetric gap test", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
OK(getFromSocket("/reload"));
|
||||
}};
|
||||
|
||||
OK(getFromSocket("/dispatch workspace name:gap_split_test"));
|
||||
OK(getFromSocket("r/keyword general:gaps_in 0"));
|
||||
OK(getFromSocket("r/keyword general:border_size 0"));
|
||||
OK(getFromSocket("r/keyword dwindle:split_width_multiplier 1.0"));
|
||||
OK(getFromSocket("r/keyword workspace name:gap_split_test,gapsout:0 1000 0 0"));
|
||||
|
||||
NLog::log("{}Testing default split (force_split = 0)", Colors::YELLOW);
|
||||
OK(getFromSocket("r/keyword dwindle:force_split 0"));
|
||||
|
||||
if (!Tests::spawnKitty("gaps_kitty_A") || !Tests::spawnKitty("gaps_kitty_B"))
|
||||
return false;
|
||||
|
||||
NLog::log("{}Expecting vertical split (B below A)", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_A"));
|
||||
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0");
|
||||
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_B"));
|
||||
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,540");
|
||||
|
||||
Tests::killAllWindows();
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
|
||||
NLog::log("{}Testing force_split = 1", Colors::YELLOW);
|
||||
OK(getFromSocket("r/keyword dwindle:force_split 1"));
|
||||
|
||||
if (!Tests::spawnKitty("gaps_kitty_A") || !Tests::spawnKitty("gaps_kitty_B"))
|
||||
return false;
|
||||
|
||||
NLog::log("{}Expecting vertical split (B above A)", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_B"));
|
||||
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0");
|
||||
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_A"));
|
||||
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,540");
|
||||
|
||||
NLog::log("{}Expecting horizontal split (C left of B)", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_B"));
|
||||
|
||||
if (!Tests::spawnKitty("gaps_kitty_C"))
|
||||
return false;
|
||||
|
||||
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_C"));
|
||||
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0");
|
||||
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_B"));
|
||||
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 460,0");
|
||||
|
||||
Tests::killAllWindows();
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
|
||||
NLog::log("{}Testing force_split = 2", Colors::YELLOW);
|
||||
OK(getFromSocket("r/keyword dwindle:force_split 2"));
|
||||
|
||||
if (!Tests::spawnKitty("gaps_kitty_A") || !Tests::spawnKitty("gaps_kitty_B"))
|
||||
return false;
|
||||
|
||||
NLog::log("{}Expecting vertical split (B below A)", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_A"));
|
||||
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0");
|
||||
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_B"));
|
||||
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,540");
|
||||
|
||||
NLog::log("{}Expecting horizontal split (C right of A)", Colors::YELLOW);
|
||||
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_A"));
|
||||
|
||||
if (!Tests::spawnKitty("gaps_kitty_C"))
|
||||
return false;
|
||||
|
||||
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_A"));
|
||||
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 0,0");
|
||||
OK(getFromSocket("/dispatch focuswindow class:gaps_kitty_C"));
|
||||
EXPECT_CONTAINS(getFromSocket("/activewindow"), "at: 460,0");
|
||||
}
|
||||
|
||||
// kill all
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void testWorkspaceHistoryMultiMon() {
|
||||
NLog::log("{}Testing multimon workspace history tracker", Colors::YELLOW);
|
||||
|
||||
// Initial state:
|
||||
OK(getFromSocket("/dispatch focusmonitor HEADLESS-2"));
|
||||
OK(getFromSocket("/dispatch workspace 10"));
|
||||
Tests::spawnKitty();
|
||||
OK(getFromSocket("/dispatch workspace 11"));
|
||||
Tests::spawnKitty();
|
||||
|
||||
OK(getFromSocket("/dispatch focusmonitor HEADLESS-3"));
|
||||
OK(getFromSocket("/dispatch workspace 12"));
|
||||
Tests::spawnKitty();
|
||||
|
||||
OK(getFromSocket("/dispatch focusmonitor HEADLESS-2"));
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_CONTAINS(str, "workspace ID 11");
|
||||
}
|
||||
OK(getFromSocket("/dispatch workspace previous_per_monitor"));
|
||||
{
|
||||
auto str = getFromSocket("/activeworkspace");
|
||||
EXPECT_CONTAINS(str, "workspace ID 10");
|
||||
}
|
||||
|
||||
NLog::log("{}Killing all windows", Colors::YELLOW);
|
||||
Tests::killAllWindows();
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
|
@ -352,13 +756,21 @@ 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();
|
||||
testWorkspaceHistoryMultiMon();
|
||||
|
||||
// destroy the headless output
|
||||
OK(getFromSocket("/output remove HEADLESS-3"));
|
||||
|
||||
testSpecialWorkspaceFullscreen();
|
||||
testAsymmetricGaps();
|
||||
testDynamicWsEffects();
|
||||
|
||||
NLog::log("{}Expecting 0 windows", Colors::YELLOW);
|
||||
EXPECT(Tests::windowCount(), 0);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include <cerrno>
|
||||
#include <thread>
|
||||
#include <print>
|
||||
#include <fstream>
|
||||
#include "../shared.hpp"
|
||||
#include "../hyprctlCompat.hpp"
|
||||
|
||||
|
|
@ -39,6 +40,38 @@ CUniquePointer<CProcess> Tests::spawnKitty(const std::string& class_, const std:
|
|||
return kitty;
|
||||
}
|
||||
|
||||
CUniquePointer<CProcess> Tests::spawnLayerKitty(const std::string& namespace_, const std::vector<std::string> args) {
|
||||
std::vector<std::string> programArgs = args;
|
||||
if (!namespace_.empty()) {
|
||||
programArgs.insert(programArgs.begin(), "--class");
|
||||
programArgs.insert(programArgs.begin() + 1, namespace_);
|
||||
}
|
||||
|
||||
programArgs.insert(programArgs.begin(), "+kitten");
|
||||
programArgs.insert(programArgs.begin() + 1, "panel");
|
||||
|
||||
CUniquePointer<CProcess> kitty = makeUnique<CProcess>("kitty", programArgs);
|
||||
kitty->addEnv("WAYLAND_DISPLAY", WLDISPLAY);
|
||||
kitty->runAsync();
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
|
||||
// wait while the layer spawns
|
||||
int counter = 0;
|
||||
while (processAlive(kitty->pid()) && countOccurrences(getFromSocket("/layers"), std::format("pid: {}", kitty->pid())) == 0) {
|
||||
counter++;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
if (counter > 50)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!processAlive(kitty->pid()))
|
||||
return nullptr;
|
||||
|
||||
return kitty;
|
||||
}
|
||||
|
||||
bool Tests::processAlive(pid_t pid) {
|
||||
errno = 0;
|
||||
int ret = kill(pid, 0);
|
||||
|
|
@ -96,6 +129,38 @@ void Tests::waitUntilWindowsN(int n) {
|
|||
}
|
||||
}
|
||||
|
||||
int Tests::layerCount() {
|
||||
return countOccurrences(getFromSocket("/layers"), "namespace: ");
|
||||
}
|
||||
|
||||
bool Tests::killAllLayers() {
|
||||
auto str = getFromSocket("/layers");
|
||||
auto pos = str.find("pid: ");
|
||||
while (pos != std::string::npos) {
|
||||
auto pid = stoi(str.substr(pos + 5, str.find('\n', pos)));
|
||||
kill(pid, 15);
|
||||
|
||||
// we need to wait for a bit because for some reason otherwise we'll end up
|
||||
// with layers with pid -1 if they are all removed at the same time
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
pos = str.find("pid: ", pos + 5);
|
||||
}
|
||||
|
||||
int counter = 0;
|
||||
while (Tests::layerCount() != 0) {
|
||||
counter++;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
if (counter > 50) {
|
||||
std::println("{}Timed out waiting for layers to close", Colors::RED);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string Tests::execAndGet(const std::string& cmd) {
|
||||
CProcess proc("/bin/sh", {"-c", cmd});
|
||||
|
||||
|
|
@ -105,3 +170,24 @@ 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;
|
||||
}
|
||||
|
||||
std::string Tests::getWindowAttribute(const std::string& winInfo, const std::string& attr) {
|
||||
auto pos = winInfo.find(attr);
|
||||
if (pos == std::string::npos) {
|
||||
NLog::log("{}Window attribute not found: '{}'", Colors::RED, attr);
|
||||
return "Wrong window attribute";
|
||||
}
|
||||
auto pos2 = winInfo.find('\n', pos);
|
||||
return winInfo.substr(pos, pos2 - pos);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,10 +9,15 @@
|
|||
//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);
|
||||
std::string getWindowAttribute(const std::string& winInfo, const std::string& attr);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -179,6 +179,17 @@ master {
|
|||
new_status = master
|
||||
}
|
||||
|
||||
scrolling {
|
||||
fullscreen_on_one_column = true
|
||||
column_width = 0.5
|
||||
focus_fit_method = 1
|
||||
follow_focus = true
|
||||
follow_min_visible = 1
|
||||
explicit_column_widths = 0.25, 0.333, 0.5, 0.667, 0.75, 1.0
|
||||
wrap_focus = true
|
||||
wrap_swapcol = true
|
||||
}
|
||||
|
||||
# https://wiki.hyprland.org/Configuring/Variables/#misc
|
||||
misc {
|
||||
force_default_wallpaper = -1 # Set to 0 or 1 to disable the anime mascot wallpapers
|
||||
|
|
@ -239,7 +250,7 @@ bind = $mainMod, E, exec, $fileManager
|
|||
bind = $mainMod, V, togglefloating,
|
||||
bind = $mainMod, R, exec, $menu
|
||||
bind = $mainMod, P, pseudo, # dwindle
|
||||
bind = $mainMod, J, togglesplit, # dwindle
|
||||
bind = $mainMod, J, layoutmsg, togglesplit, # dwindle
|
||||
|
||||
# Move focus with mainMod + arrow keys
|
||||
bind = $mainMod, left, movefocus, l
|
||||
|
|
@ -398,3 +409,5 @@ gesture = 5, left, dispatcher, sendshortcut, , i, activewindow
|
|||
gesture = 5, right, dispatcher, sendshortcut, , t, activewindow
|
||||
gesture = 4, right, dispatcher, sendshortcut, , return, activewindow
|
||||
gesture = 4, left, dispatcher, movecursortocorner, 1
|
||||
|
||||
gesturep = 2, right, float
|
||||
|
|
|
|||
305
nix/default.nix
305
nix/default.nix
|
|
@ -11,7 +11,9 @@
|
|||
cairo,
|
||||
epoll-shim,
|
||||
git,
|
||||
glaze,
|
||||
glaze-hyprland,
|
||||
glslang,
|
||||
gtest,
|
||||
hyprcursor,
|
||||
hyprgraphics,
|
||||
hyprland-protocols,
|
||||
|
|
@ -19,10 +21,18 @@
|
|||
hyprlang,
|
||||
hyprutils,
|
||||
hyprwayland-scanner,
|
||||
hyprwire,
|
||||
lcms2,
|
||||
libGL,
|
||||
libdrm,
|
||||
libexecinfo,
|
||||
libinput,
|
||||
libxcb,
|
||||
libxcb-errors,
|
||||
libxcb-render-util,
|
||||
libxcb-wm,
|
||||
libxdmcp,
|
||||
libxcursor,
|
||||
libxkbcommon,
|
||||
libuuid,
|
||||
libgbm,
|
||||
|
|
@ -36,9 +46,9 @@
|
|||
wayland,
|
||||
wayland-protocols,
|
||||
wayland-scanner,
|
||||
xorg,
|
||||
xwayland,
|
||||
debug ? false,
|
||||
withTests ? debug,
|
||||
enableXWayland ? true,
|
||||
withSystemd ? lib.meta.availableOn stdenv.hostPlatform systemd,
|
||||
wrapRuntimeDeps ? true,
|
||||
|
|
@ -52,12 +62,23 @@
|
|||
hidpiXWayland ? false,
|
||||
legacyRenderer ? false,
|
||||
withHyprtester ? false,
|
||||
}: let
|
||||
}:
|
||||
let
|
||||
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 [
|
||||
|
|
@ -67,162 +88,184 @@
|
|||
|
||||
customStdenv = foldl' (acc: adapter: adapter acc) stdenv adapters;
|
||||
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 (!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: {
|
||||
pname = "hyprland${optionalString debug "-debug"}";
|
||||
inherit version;
|
||||
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 (
|
||||
!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: {
|
||||
pname = "hyprland${optionalString debug "-debug"}";
|
||||
inherit version withTests;
|
||||
|
||||
src = fs.toSource {
|
||||
root = ../.;
|
||||
fileset =
|
||||
fs.intersection
|
||||
# allows non-flake builds to only include files tracked by git
|
||||
(fs.gitTracked ../.)
|
||||
(fs.unions (flatten [
|
||||
src = fs.toSource {
|
||||
root = ../.;
|
||||
fileset =
|
||||
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
|
||||
../hyprtester
|
||||
../hyprpm
|
||||
../LICENSE
|
||||
../protocols
|
||||
../src
|
||||
../start
|
||||
../systemd
|
||||
../VERSION
|
||||
(fs.fileFilter (file: file.hasExt "1") ../docs)
|
||||
(fs.fileFilter (file: file.hasExt "conf" || file.hasExt "desktop") ../example)
|
||||
(fs.fileFilter (file: file.hasExt "conf" || file.hasExt "in") ../example)
|
||||
(fs.fileFilter (file: file.hasExt "sh") ../scripts)
|
||||
(fs.fileFilter (file: file.name == "CMakeLists.txt") ../.)
|
||||
]));
|
||||
};
|
||||
(optional withTests [
|
||||
../tests
|
||||
../hyprtester
|
||||
])
|
||||
])
|
||||
);
|
||||
};
|
||||
|
||||
postPatch = ''
|
||||
# Fix hardcoded paths to /usr installation
|
||||
sed -i "s#/usr#$out#" src/render/OpenGL.cpp
|
||||
postPatch = ''
|
||||
# Fix hardcoded paths to /usr installation
|
||||
sed -i "s#/usr#$out#" src/render/OpenGL.cpp
|
||||
|
||||
# Remove extra @PREFIX@ to fix pkg-config paths
|
||||
sed -i "s#@PREFIX@/##g" hyprland.pc.in
|
||||
'';
|
||||
# Remove extra @PREFIX@ to fix some paths
|
||||
sed -i "s#@PREFIX@/##g" hyprland.pc.in
|
||||
sed -i "s#@PREFIX@/##g" example/hyprland.desktop.in
|
||||
'';
|
||||
|
||||
env = {
|
||||
GIT_COMMITS = revCount;
|
||||
GIT_COMMIT_DATE = date;
|
||||
GIT_COMMIT_HASH = commit;
|
||||
GIT_DIRTY = if (commit == "") then "clean" else "dirty";
|
||||
GIT_TAG = "v${trim (readFile "${finalAttrs.src}/VERSION")}";
|
||||
};
|
||||
env = {
|
||||
GIT_COMMITS = revCount;
|
||||
GIT_COMMIT_DATE = date;
|
||||
GIT_COMMIT_HASH = commit;
|
||||
GIT_DIRTY = if (commit == "") then "clean" else "dirty";
|
||||
GIT_TAG = "v${trim (readFile "${finalAttrs.src}/VERSION")}";
|
||||
};
|
||||
|
||||
depsBuildBuild = [
|
||||
pkg-config
|
||||
];
|
||||
depsBuildBuild = [
|
||||
pkg-config
|
||||
];
|
||||
|
||||
nativeBuildInputs = [
|
||||
hyprwayland-scanner
|
||||
makeWrapper
|
||||
cmake
|
||||
pkg-config
|
||||
];
|
||||
nativeBuildInputs = [
|
||||
hyprwayland-scanner
|
||||
hyprwire
|
||||
makeWrapper
|
||||
cmake
|
||||
pkg-config
|
||||
];
|
||||
|
||||
outputs = [
|
||||
"out"
|
||||
"man"
|
||||
"dev"
|
||||
];
|
||||
outputs = [
|
||||
"out"
|
||||
"man"
|
||||
"dev"
|
||||
];
|
||||
|
||||
buildInputs = concatLists [
|
||||
[
|
||||
aquamarine
|
||||
cairo
|
||||
git
|
||||
glaze
|
||||
hyprcursor
|
||||
hyprgraphics
|
||||
hyprland-protocols
|
||||
hyprlang
|
||||
hyprutils
|
||||
libdrm
|
||||
libGL
|
||||
libinput
|
||||
libuuid
|
||||
libxkbcommon
|
||||
libgbm
|
||||
muparser
|
||||
pango
|
||||
pciutils
|
||||
re2
|
||||
tomlplusplus
|
||||
udis86-hyprland
|
||||
wayland
|
||||
wayland-protocols
|
||||
wayland-scanner
|
||||
xorg.libXcursor
|
||||
]
|
||||
(optionals customStdenv.hostPlatform.isBSD [epoll-shim])
|
||||
(optionals customStdenv.hostPlatform.isMusl [libexecinfo])
|
||||
(optionals enableXWayland [
|
||||
xorg.libxcb
|
||||
xorg.libXdmcp
|
||||
xorg.xcbutilerrors
|
||||
xorg.xcbutilrenderutil
|
||||
xorg.xcbutilwm
|
||||
xwayland
|
||||
])
|
||||
(optional withSystemd systemd)
|
||||
];
|
||||
buildInputs = concatLists [
|
||||
[
|
||||
aquamarine
|
||||
cairo
|
||||
git
|
||||
glaze-hyprland
|
||||
glslang
|
||||
gtest
|
||||
hyprcursor
|
||||
hyprgraphics
|
||||
hyprland-protocols
|
||||
hyprlang
|
||||
hyprutils
|
||||
hyprwire
|
||||
lcms2
|
||||
libdrm
|
||||
libgbm
|
||||
libGL
|
||||
libinput
|
||||
libuuid
|
||||
libxcursor
|
||||
libxkbcommon
|
||||
muparser
|
||||
pango
|
||||
pciutils
|
||||
re2
|
||||
tomlplusplus
|
||||
udis86-hyprland
|
||||
wayland
|
||||
wayland-protocols
|
||||
wayland-scanner
|
||||
]
|
||||
(optionals customStdenv.hostPlatform.isBSD [ epoll-shim ])
|
||||
(optionals customStdenv.hostPlatform.isMusl [ libexecinfo ])
|
||||
(optionals enableXWayland [
|
||||
libxcb
|
||||
libxcb-errors
|
||||
libxcb-render-util
|
||||
libxcb-wm
|
||||
libxdmcp
|
||||
xwayland
|
||||
])
|
||||
(optional withSystemd systemd)
|
||||
];
|
||||
|
||||
strictDeps = true;
|
||||
strictDeps = true;
|
||||
|
||||
cmakeBuildType =
|
||||
if debug
|
||||
then "Debug"
|
||||
else "RelWithDebInfo";
|
||||
cmakeBuildType = if debug then "Debug" else "RelWithDebInfo";
|
||||
|
||||
# we want as much debug info as possible
|
||||
dontStrip = debug;
|
||||
# we want as much debug info as possible
|
||||
dontStrip = debug;
|
||||
|
||||
cmakeFlags = mapAttrsToList cmakeBool {
|
||||
"NO_XWAYLAND" = !enableXWayland;
|
||||
"LEGACY_RENDERER" = legacyRenderer;
|
||||
"NO_SYSTEMD" = !withSystemd;
|
||||
"CMAKE_DISABLE_PRECOMPILE_HEADERS" = true;
|
||||
"NO_UWSM" = true;
|
||||
"NO_HYPRPM" = true;
|
||||
"TRACY_ENABLE" = false;
|
||||
"BUILD_HYPRTESTER" = true;
|
||||
};
|
||||
cmakeFlags = mapAttrsToList cmakeBool {
|
||||
"BUILT_WITH_NIX" = true;
|
||||
"NO_XWAYLAND" = !enableXWayland;
|
||||
"LEGACY_RENDERER" = legacyRenderer;
|
||||
"NO_SYSTEMD" = !withSystemd;
|
||||
"CMAKE_DISABLE_PRECOMPILE_HEADERS" = true;
|
||||
"NO_UWSM" = !withSystemd;
|
||||
"TRACY_ENABLE" = false;
|
||||
"WITH_TESTS" = withTests;
|
||||
};
|
||||
|
||||
preConfigure = ''
|
||||
substituteInPlace hyprtester/CMakeLists.txt --replace-fail \
|
||||
"\''${CMAKE_CURRENT_BINARY_DIR}" \
|
||||
"${placeholder "out"}/bin"
|
||||
'';
|
||||
preConfigure = ''
|
||||
substituteInPlace hyprtester/CMakeLists.txt --replace-fail \
|
||||
"\''${CMAKE_CURRENT_BINARY_DIR}" \
|
||||
"${placeholder "out"}/bin"
|
||||
'';
|
||||
|
||||
postInstall = ''
|
||||
${optionalString wrapRuntimeDeps ''
|
||||
wrapProgram $out/bin/Hyprland \
|
||||
--suffix PATH : ${makeBinPath [
|
||||
postInstall = ''
|
||||
${optionalString wrapRuntimeDeps ''
|
||||
wrapProgram $out/bin/Hyprland \
|
||||
--suffix PATH : ${
|
||||
makeBinPath [
|
||||
binutils
|
||||
hyprland-guiutils
|
||||
pciutils
|
||||
pkgconf
|
||||
]}
|
||||
''}
|
||||
]
|
||||
}
|
||||
''}
|
||||
|
||||
install hyprtester/pointer-warp -t $out/bin
|
||||
install hyprtester/pointer-scroll -t $out/bin
|
||||
'';
|
||||
${optionalString withTests ''
|
||||
install hyprtester/pointer-warp -t $out/bin
|
||||
install hyprtester/pointer-scroll -t $out/bin
|
||||
install hyprtester/shortcut-inhibitor -t $out/bin
|
||||
install hyprland_gtests -t $out/bin
|
||||
install hyprtester/child-window -t $out/bin
|
||||
''}
|
||||
'';
|
||||
|
||||
passthru.providedSessions = ["hyprland"];
|
||||
passthru.providedSessions = [ "hyprland" ] ++ optionals withSystemd [ "hyprland-uwsm" ];
|
||||
|
||||
meta = {
|
||||
homepage = "https://github.com/hyprwm/Hyprland";
|
||||
description = "Dynamic tiling Wayland compositor that doesn't sacrifice on its looks";
|
||||
license = lib.licenses.bsd3;
|
||||
platforms = lib.platforms.linux;
|
||||
mainProgram = "Hyprland";
|
||||
};
|
||||
})
|
||||
meta = {
|
||||
homepage = "https://github.com/hyprwm/Hyprland";
|
||||
description = "Dynamic tiling Wayland compositor that doesn't sacrifice on its looks";
|
||||
license = lib.licenses.bsd3;
|
||||
platforms = lib.platforms.linux;
|
||||
mainProgram = "Hyprland";
|
||||
};
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
writeShellApplication,
|
||||
deadnix,
|
||||
statix,
|
||||
alejandra,
|
||||
nixfmt,
|
||||
llvmPackages_19,
|
||||
fd,
|
||||
}:
|
||||
|
|
@ -11,7 +11,7 @@ writeShellApplication {
|
|||
runtimeInputs = [
|
||||
deadnix
|
||||
statix
|
||||
alejandra
|
||||
nixfmt
|
||||
llvmPackages_19.clang-tools
|
||||
fd
|
||||
];
|
||||
|
|
@ -24,14 +24,14 @@ writeShellApplication {
|
|||
nix_format() {
|
||||
if [ "$*" = 0 ]; then
|
||||
fd '.*\.nix' . -E "$excludes" -x statix fix -- {} \;
|
||||
fd '.*\.nix' . -E "$excludes" -X deadnix -e -- {} \; -X alejandra {} \;
|
||||
fd '.*\.nix' . -E "$excludes" -X deadnix -e -- {} \; -X nixfmt {} \;
|
||||
elif [ -d "$1" ]; then
|
||||
fd '.*\.nix' "$1" -E "$excludes" -i -x statix fix -- {} \;
|
||||
fd '.*\.nix' "$1" -E "$excludes" -i -X deadnix -e -- {} \; -X alejandra {} \;
|
||||
fd '.*\.nix' "$1" -E "$excludes" -i -X deadnix -e -- {} \; -X nixfmt {} \;
|
||||
else
|
||||
statix fix -- "$1"
|
||||
deadnix -e "$1"
|
||||
alejandra "$1"
|
||||
nixfmt "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
self: {
|
||||
config,
|
||||
self:
|
||||
{
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
}:
|
||||
let
|
||||
inherit (pkgs.stdenv.hostPlatform) system;
|
||||
|
||||
package = self.packages.${system}.default;
|
||||
in {
|
||||
in
|
||||
{
|
||||
config = {
|
||||
wayland.windowManager.hyprland.package = lib.mkDefault package;
|
||||
};
|
||||
|
|
|
|||
115
nix/lib.nix
115
nix/lib.nix
|
|
@ -1,4 +1,5 @@
|
|||
lib: let
|
||||
lib:
|
||||
let
|
||||
inherit (lib)
|
||||
attrNames
|
||||
filterAttrs
|
||||
|
|
@ -17,7 +18,7 @@ lib: let
|
|||
|
||||
This function takes a nested attribute set and converts it into Hyprland-compatible
|
||||
configuration syntax, supporting top, bottom, and regular command sections.
|
||||
|
||||
|
||||
Commands are flattened using the `flattenAttrs` function, and attributes are formatted as
|
||||
`key = value` pairs. Lists are expanded as duplicate keys to match Hyprland's expected format.
|
||||
|
||||
|
|
@ -81,44 +82,51 @@ lib: let
|
|||
|
||||
:::
|
||||
*/
|
||||
toHyprlang = {
|
||||
topCommandsPrefixes ? ["$" "bezier"],
|
||||
bottomCommandsPrefixes ? [],
|
||||
}: attrs: let
|
||||
toHyprlang' = attrs: let
|
||||
# Specially configured `toKeyValue` generator with support for duplicate keys
|
||||
# and a legible key-value separator.
|
||||
mkCommands = generators.toKeyValue {
|
||||
mkKeyValue = generators.mkKeyValueDefault {} " = ";
|
||||
listsAsDuplicateKeys = true;
|
||||
indent = ""; # No indent, since we don't have nesting
|
||||
};
|
||||
toHyprlang =
|
||||
{
|
||||
topCommandsPrefixes ? [
|
||||
"$"
|
||||
"bezier"
|
||||
],
|
||||
bottomCommandsPrefixes ? [ ],
|
||||
}:
|
||||
attrs:
|
||||
let
|
||||
toHyprlang' =
|
||||
attrs:
|
||||
let
|
||||
# Specially configured `toKeyValue` generator with support for duplicate keys
|
||||
# and a legible key-value separator.
|
||||
mkCommands = generators.toKeyValue {
|
||||
mkKeyValue = generators.mkKeyValueDefault { } " = ";
|
||||
listsAsDuplicateKeys = true;
|
||||
indent = ""; # No indent, since we don't have nesting
|
||||
};
|
||||
|
||||
# Flatten the attrset, combining keys in a "path" like `"a:b:c" = "x"`.
|
||||
# Uses `flattenAttrs` with a colon separator.
|
||||
commands = flattenAttrs (p: k: "${p}:${k}") attrs;
|
||||
# Flatten the attrset, combining keys in a "path" like `"a:b:c" = "x"`.
|
||||
# Uses `flattenAttrs` with a colon separator.
|
||||
commands = flattenAttrs (p: k: "${p}:${k}") attrs;
|
||||
|
||||
# General filtering function to check if a key starts with any prefix in a given list.
|
||||
filterCommands = list: n:
|
||||
foldl (acc: prefix: acc || hasPrefix prefix n) false list;
|
||||
# General filtering function to check if a key starts with any prefix in a given list.
|
||||
filterCommands = list: n: foldl (acc: prefix: acc || hasPrefix prefix n) false list;
|
||||
|
||||
# Partition keys into top commands and the rest
|
||||
result = partition (filterCommands topCommandsPrefixes) (attrNames commands);
|
||||
topCommands = filterAttrs (n: _: builtins.elem n result.right) commands;
|
||||
remainingCommands = removeAttrs commands result.right;
|
||||
# Partition keys into top commands and the rest
|
||||
result = partition (filterCommands topCommandsPrefixes) (attrNames commands);
|
||||
topCommands = filterAttrs (n: _: builtins.elem n result.right) commands;
|
||||
remainingCommands = removeAttrs commands result.right;
|
||||
|
||||
# Partition remaining commands into bottom commands and regular commands
|
||||
result2 = partition (filterCommands bottomCommandsPrefixes) result.wrong;
|
||||
bottomCommands = filterAttrs (n: _: builtins.elem n result2.right) remainingCommands;
|
||||
regularCommands = removeAttrs remainingCommands result2.right;
|
||||
# Partition remaining commands into bottom commands and regular commands
|
||||
result2 = partition (filterCommands bottomCommandsPrefixes) result.wrong;
|
||||
bottomCommands = filterAttrs (n: _: builtins.elem n result2.right) remainingCommands;
|
||||
regularCommands = removeAttrs remainingCommands result2.right;
|
||||
in
|
||||
# Concatenate strings from mapping `mkCommands` over top, regular, and bottom commands.
|
||||
concatMapStrings mkCommands [
|
||||
topCommands
|
||||
regularCommands
|
||||
bottomCommands
|
||||
];
|
||||
in
|
||||
# Concatenate strings from mapping `mkCommands` over top, regular, and bottom commands.
|
||||
concatMapStrings mkCommands [
|
||||
topCommands
|
||||
regularCommands
|
||||
bottomCommands
|
||||
];
|
||||
in
|
||||
toHyprlang' attrs;
|
||||
|
||||
/**
|
||||
|
|
@ -131,7 +139,7 @@ lib: let
|
|||
Configuration:
|
||||
|
||||
* `pred` - A function `(string -> string -> string)` defining how keys should be concatenated.
|
||||
|
||||
|
||||
# Inputs
|
||||
|
||||
Structured function argument:
|
||||
|
|
@ -139,7 +147,7 @@ lib: let
|
|||
: pred (required)
|
||||
: A function that determines how parent and child keys should be combined into a single key.
|
||||
It takes a `prefix` (parent key) and `key` (current key) and returns the joined key.
|
||||
|
||||
|
||||
Value:
|
||||
|
||||
: The nested attribute set to be flattened.
|
||||
|
|
@ -174,26 +182,21 @@ lib: let
|
|||
```
|
||||
|
||||
:::
|
||||
|
||||
*/
|
||||
flattenAttrs = pred: attrs: let
|
||||
flattenAttrs' = prefix: attrs:
|
||||
builtins.foldl' (
|
||||
acc: key: let
|
||||
value = attrs.${key};
|
||||
newKey =
|
||||
if prefix == ""
|
||||
then key
|
||||
else pred prefix key;
|
||||
in
|
||||
acc
|
||||
// (
|
||||
if builtins.isAttrs value
|
||||
then flattenAttrs' newKey value
|
||||
else {"${newKey}" = value;}
|
||||
)
|
||||
) {} (builtins.attrNames attrs);
|
||||
in
|
||||
flattenAttrs =
|
||||
pred: attrs:
|
||||
let
|
||||
flattenAttrs' =
|
||||
prefix: attrs:
|
||||
builtins.foldl' (
|
||||
acc: key:
|
||||
let
|
||||
value = attrs.${key};
|
||||
newKey = if prefix == "" then key else pred prefix key;
|
||||
in
|
||||
acc // (if builtins.isAttrs value then flattenAttrs' newKey value else { "${newKey}" = value; })
|
||||
) { } (builtins.attrNames attrs);
|
||||
in
|
||||
flattenAttrs' "" attrs;
|
||||
in
|
||||
{
|
||||
|
|
|
|||
111
nix/module.nix
111
nix/module.nix
|
|
@ -1,18 +1,21 @@
|
|||
inputs: {
|
||||
inputs:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
}:
|
||||
let
|
||||
inherit (pkgs.stdenv.hostPlatform) system;
|
||||
selflib = import ./lib.nix lib;
|
||||
cfg = config.programs.hyprland;
|
||||
in {
|
||||
in
|
||||
{
|
||||
options = {
|
||||
programs.hyprland = {
|
||||
plugins = lib.mkOption {
|
||||
type = with lib.types; listOf (either package path);
|
||||
default = [];
|
||||
default = [ ];
|
||||
description = ''
|
||||
List of Hyprland plugins to use. Can either be packages or
|
||||
absolute plugin paths.
|
||||
|
|
@ -20,23 +23,25 @@ in {
|
|||
};
|
||||
|
||||
settings = lib.mkOption {
|
||||
type = with lib.types; let
|
||||
valueType =
|
||||
nullOr (oneOf [
|
||||
bool
|
||||
int
|
||||
float
|
||||
str
|
||||
path
|
||||
(attrsOf valueType)
|
||||
(listOf valueType)
|
||||
])
|
||||
// {
|
||||
description = "Hyprland configuration value";
|
||||
};
|
||||
in
|
||||
type =
|
||||
with lib.types;
|
||||
let
|
||||
valueType =
|
||||
nullOr (oneOf [
|
||||
bool
|
||||
int
|
||||
float
|
||||
str
|
||||
path
|
||||
(attrsOf valueType)
|
||||
(listOf valueType)
|
||||
])
|
||||
// {
|
||||
description = "Hyprland configuration value";
|
||||
};
|
||||
in
|
||||
valueType;
|
||||
default = {};
|
||||
default = { };
|
||||
description = ''
|
||||
Hyprland configuration written in Nix. Entries with the same key
|
||||
should be written as lists. Variables' and colors' names should be
|
||||
|
|
@ -92,8 +97,15 @@ in {
|
|||
|
||||
topPrefixes = lib.mkOption {
|
||||
type = with lib.types; listOf str;
|
||||
default = ["$" "bezier"];
|
||||
example = ["$" "bezier" "source"];
|
||||
default = [
|
||||
"$"
|
||||
"bezier"
|
||||
];
|
||||
example = [
|
||||
"$"
|
||||
"bezier"
|
||||
"source"
|
||||
];
|
||||
description = ''
|
||||
List of prefix of attributes to put at the top of the config.
|
||||
'';
|
||||
|
|
@ -101,8 +113,8 @@ in {
|
|||
|
||||
bottomPrefixes = lib.mkOption {
|
||||
type = with lib.types; listOf str;
|
||||
default = [];
|
||||
example = ["source"];
|
||||
default = [ ];
|
||||
example = [ "source" ];
|
||||
description = ''
|
||||
List of prefix of attributes to put at the bottom of the config.
|
||||
'';
|
||||
|
|
@ -117,35 +129,36 @@ in {
|
|||
};
|
||||
}
|
||||
(lib.mkIf cfg.enable {
|
||||
environment.etc."xdg/hypr/hyprland.conf" = let
|
||||
shouldGenerate = cfg.extraConfig != "" || cfg.settings != {} || cfg.plugins != [];
|
||||
environment.etc."xdg/hypr/hyprland.conf" =
|
||||
let
|
||||
shouldGenerate = cfg.extraConfig != "" || cfg.settings != { } || cfg.plugins != [ ];
|
||||
|
||||
pluginsToHyprlang = plugins:
|
||||
selflib.toHyprlang {
|
||||
topCommandsPrefixes = cfg.topPrefixes;
|
||||
bottomCommandsPrefixes = cfg.bottomPrefixes;
|
||||
}
|
||||
{
|
||||
"exec-once" = let
|
||||
mkEntry = entry:
|
||||
if lib.types.package.check entry
|
||||
then "${entry}/lib/lib${entry.pname}.so"
|
||||
else entry;
|
||||
hyprctl = lib.getExe' config.programs.hyprland.package "hyprctl";
|
||||
in
|
||||
map (p: "${hyprctl} plugin load ${mkEntry p}") cfg.plugins;
|
||||
};
|
||||
in
|
||||
lib.mkIf shouldGenerate {
|
||||
text =
|
||||
lib.optionalString (cfg.plugins != [])
|
||||
(pluginsToHyprlang cfg.plugins)
|
||||
+ lib.optionalString (cfg.settings != {})
|
||||
(selflib.toHyprlang {
|
||||
pluginsToHyprlang =
|
||||
_plugins:
|
||||
selflib.toHyprlang
|
||||
{
|
||||
topCommandsPrefixes = cfg.topPrefixes;
|
||||
bottomCommandsPrefixes = cfg.bottomPrefixes;
|
||||
}
|
||||
cfg.settings)
|
||||
{
|
||||
"exec-once" =
|
||||
let
|
||||
mkEntry =
|
||||
entry: if lib.types.package.check entry then "${entry}/lib/lib${entry.pname}.so" else entry;
|
||||
hyprctl = lib.getExe' config.programs.hyprland.package "hyprctl";
|
||||
in
|
||||
map (p: "${hyprctl} plugin load ${mkEntry p}") cfg.plugins;
|
||||
};
|
||||
in
|
||||
lib.mkIf shouldGenerate {
|
||||
text =
|
||||
lib.optionalString (cfg.plugins != [ ]) (pluginsToHyprlang cfg.plugins)
|
||||
+ lib.optionalString (cfg.settings != { }) (
|
||||
selflib.toHyprlang {
|
||||
topCommandsPrefixes = cfg.topPrefixes;
|
||||
bottomCommandsPrefixes = cfg.bottomPrefixes;
|
||||
} cfg.settings
|
||||
)
|
||||
+ lib.optionalString (cfg.extraConfig != "") cfg.extraConfig;
|
||||
};
|
||||
})
|
||||
|
|
|
|||
137
nix/overlays.nix
137
nix/overlays.nix
|
|
@ -2,22 +2,29 @@
|
|||
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
|
||||
hyprland-extras
|
||||
]
|
||||
);
|
||||
|
||||
# Packages for variations of Hyprland, dependencies included.
|
||||
# Packages for variations of Hyprland, all dependencies included.
|
||||
hyprland-packages = lib.composeManyExtensions [
|
||||
# Dependencies
|
||||
inputs.aquamarine.overlays.default
|
||||
|
|
@ -28,61 +35,68 @@ in {
|
|||
inputs.hyprlang.overlays.default
|
||||
inputs.hyprutils.overlays.default
|
||||
inputs.hyprwayland-scanner.overlays.default
|
||||
self.overlays.udis86
|
||||
|
||||
inputs.hyprwire.overlays.default
|
||||
# Hyprland packages themselves
|
||||
(final: _prev: let
|
||||
self.overlays.hyprland
|
||||
];
|
||||
|
||||
# Hyprland with its internal dependencies.
|
||||
hyprland = lib.composeManyExtensions (with self.overlays; [
|
||||
udis86
|
||||
glaze
|
||||
hyprland-no-deps
|
||||
]);
|
||||
|
||||
# Hyprland without any dependencies.
|
||||
hyprland-no-deps =
|
||||
final: _prev:
|
||||
let
|
||||
date = mkDate (self.lastModifiedDate or "19700101");
|
||||
version = "${ver}+date=${date}_${self.shortRev or "dirty"}";
|
||||
in {
|
||||
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-hyprtester =
|
||||
builtins.trace ''
|
||||
hyprland-with-hyprtester was removed. Please use the hyprland package.
|
||||
Hyprtester is always built now.
|
||||
''
|
||||
final.hyprland;
|
||||
hyprland-unwrapped = final.hyprland.override { wrapRuntimeDeps = false; };
|
||||
|
||||
hyprland-with-tests = final.hyprland.override { withTests = true; };
|
||||
|
||||
hyprland-with-hyprtester = builtins.trace ''
|
||||
hyprland-with-hyprtester was removed. Please use the hyprland package.
|
||||
Hyprtester is always built now.
|
||||
'' final.hyprland;
|
||||
|
||||
# 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;
|
||||
hyprland-legacy-renderer = builtins.trace ''
|
||||
hyprland-legacy-renderer was removed. Please use the hyprland package.
|
||||
Legacy renderer is no longer supported.
|
||||
'' final.hyprland;
|
||||
|
||||
hyprland-nvidia =
|
||||
builtins.trace ''
|
||||
hyprland-nvidia was removed. Please use the hyprland package.
|
||||
Nvidia patches are no longer needed.
|
||||
''
|
||||
final.hyprland;
|
||||
hyprland-nvidia = builtins.trace ''
|
||||
hyprland-nvidia was removed. Please use the hyprland package.
|
||||
Nvidia patches are no longer needed.
|
||||
'' final.hyprland;
|
||||
|
||||
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;
|
||||
})
|
||||
];
|
||||
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;
|
||||
};
|
||||
|
||||
# Debug
|
||||
hyprland-debug = lib.composeManyExtensions [
|
||||
# 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; };
|
||||
})
|
||||
];
|
||||
|
||||
|
|
@ -96,15 +110,26 @@ 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-hyprland = prev.glaze.override {
|
||||
enableSSL = false;
|
||||
enableInterop = false;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,89 +1,107 @@
|
|||
inputs: pkgs: let
|
||||
inputs: pkgs:
|
||||
let
|
||||
flake = inputs.self.packages.${pkgs.stdenv.hostPlatform.system};
|
||||
hyprland = flake.hyprland;
|
||||
in {
|
||||
hyprland = flake.hyprland-with-tests;
|
||||
in
|
||||
{
|
||||
tests = pkgs.testers.runNixOSTest {
|
||||
name = "hyprland-tests";
|
||||
|
||||
nodes.machine = {pkgs, ...}: {
|
||||
environment.systemPackages = with pkgs; [
|
||||
# Programs needed for tests
|
||||
jq
|
||||
kitty
|
||||
wl-clipboard
|
||||
xorg.xeyes
|
||||
];
|
||||
nodes.machine =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
environment.systemPackages = with pkgs; [
|
||||
# Programs needed for tests
|
||||
jq
|
||||
kitty
|
||||
wl-clipboard
|
||||
xeyes
|
||||
];
|
||||
|
||||
# Enabled by default for some reason
|
||||
services.speechd.enable = false;
|
||||
# Enabled by default for some reason
|
||||
services.speechd.enable = false;
|
||||
|
||||
environment.variables = {
|
||||
"AQ_TRACE" = "1";
|
||||
"HYPRLAND_TRACE" = "1";
|
||||
"XDG_RUNTIME_DIR" = "/tmp";
|
||||
"XDG_CACHE_HOME" = "/tmp";
|
||||
"KITTY_CONFIG_DIRECTORY" = "/etc/kitty";
|
||||
};
|
||||
|
||||
environment.etc."kitty/kitty.conf".text = ''
|
||||
confirm_os_window_close 0
|
||||
'';
|
||||
|
||||
programs.hyprland = {
|
||||
enable = true;
|
||||
package = hyprland;
|
||||
# We don't need portals in this test, so we don't set portalPackage
|
||||
};
|
||||
|
||||
# Test configuration
|
||||
environment.etc."test.conf".source = "${hyprland}/share/hypr/test.conf";
|
||||
|
||||
# Disable portals
|
||||
xdg.portal.enable = pkgs.lib.mkForce false;
|
||||
|
||||
# Autologin root into tty
|
||||
services.getty.autologinUser = "alice";
|
||||
|
||||
system.stateVersion = "24.11";
|
||||
|
||||
users.users.alice = {
|
||||
isNormalUser = true;
|
||||
};
|
||||
|
||||
virtualisation = {
|
||||
cores = 4;
|
||||
# Might crash with less
|
||||
memorySize = 8192;
|
||||
resolution = {
|
||||
x = 1920;
|
||||
y = 1080;
|
||||
environment.variables = {
|
||||
"AQ_TRACE" = "1";
|
||||
"HYPRLAND_TRACE" = "1";
|
||||
"XDG_RUNTIME_DIR" = "/tmp";
|
||||
"XDG_CACHE_HOME" = "/tmp";
|
||||
"KITTY_CONFIG_DIRECTORY" = "/etc/kitty";
|
||||
};
|
||||
|
||||
# Doesn't seem to do much, thought it would fix XWayland crashing
|
||||
qemu.options = ["-vga none -device virtio-gpu-pci"];
|
||||
environment.etc."kitty/kitty.conf".text = ''
|
||||
confirm_os_window_close 0
|
||||
remember_window_size no
|
||||
initial_window_width 640
|
||||
initial_window_height 400
|
||||
'';
|
||||
|
||||
programs.hyprland = {
|
||||
enable = true;
|
||||
package = hyprland;
|
||||
# We don't need portals in this test, so we don't set portalPackage
|
||||
};
|
||||
|
||||
# Test configuration
|
||||
environment.etc."test.conf".source = "${hyprland}/share/hypr/test.conf";
|
||||
|
||||
# Disable portals
|
||||
xdg.portal.enable = pkgs.lib.mkForce false;
|
||||
|
||||
# Autologin root into tty
|
||||
services.getty.autologinUser = "alice";
|
||||
|
||||
system.stateVersion = "24.11";
|
||||
|
||||
users.users.alice = {
|
||||
isNormalUser = true;
|
||||
};
|
||||
|
||||
virtualisation = {
|
||||
cores = 4;
|
||||
# Might crash with less
|
||||
memorySize = 8192;
|
||||
resolution = {
|
||||
x = 1920;
|
||||
y = 1080;
|
||||
};
|
||||
|
||||
# Doesn't seem to do much, thought it would fix XWayland crashing
|
||||
qemu.options = [ "-vga none -device virtio-gpu-pci" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
# Wait for tty to be up
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
|
||||
|
||||
# Run gtests
|
||||
print("Running gtests")
|
||||
exit_status, _out = machine.execute("su - alice -c 'hyprland_gtests 2>&1 | tee /tmp/gtestslog; exit ''${PIPESTATUS[0]}'")
|
||||
machine.execute(f'echo {exit_status} > /tmp/exit_status_gtests')
|
||||
|
||||
# Print logs for visibility in CI
|
||||
_, out = machine.execute("cat /tmp/gtestslog")
|
||||
print(f"gtests log:\n{out}")
|
||||
|
||||
# Run hyprtester testing framework/suite
|
||||
print("Running hyprtester")
|
||||
exit_status, _out = machine.execute("su - alice -c 'hyprtester -b ${hyprland}/bin/Hyprland -c /etc/test.conf -p ${hyprland}/lib/hyprtestplugin.so 2>&1 | tee /tmp/testerlog; exit ''${PIPESTATUS[0]}'")
|
||||
print(f"Hyprtester exited with {exit_status}")
|
||||
|
||||
# Copy logs to host
|
||||
machine.execute('cp "$(find /tmp/hypr -name *.log | head -1)" /tmp/hyprlog')
|
||||
machine.execute(f'echo {exit_status} > /tmp/exit_status')
|
||||
machine.copy_from_vm("/tmp/testerlog")
|
||||
machine.copy_from_vm("/tmp/hyprlog")
|
||||
machine.copy_from_vm("/tmp/exit_status")
|
||||
|
||||
# Print logs for visibility in CI
|
||||
_, out = machine.execute("cat /tmp/testerlog")
|
||||
print(f"Hyprtester log:\n{out}")
|
||||
|
||||
# Copy logs to host
|
||||
machine.execute('cp "$(find /tmp/hypr -name *.log | head -1)" /tmp/hyprlog')
|
||||
machine.execute(f'echo {exit_status} > /tmp/exit_status')
|
||||
machine.copy_from_vm("/tmp/gtestslog")
|
||||
machine.copy_from_vm("/tmp/testerlog")
|
||||
machine.copy_from_vm("/tmp/hyprlog")
|
||||
machine.copy_from_vm("/tmp/exit_status")
|
||||
|
||||
# Finally - shutdown
|
||||
machine.shutdown()
|
||||
'';
|
||||
|
|
|
|||
|
|
@ -1,366 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="frog_color_management_v1">
|
||||
|
||||
<copyright>
|
||||
Copyright © 2023 Joshua Ashton for Valve Software
|
||||
Copyright © 2023 Xaver Hugl
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the next
|
||||
paragraph) shall be included in all copies or substantial portions of the
|
||||
Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<description summary="experimental color management protocol">
|
||||
The aim of this color management extension is to get HDR games working quickly,
|
||||
and have an easy way to test implementations in the wild before the upstream
|
||||
protocol is ready to be merged.
|
||||
For that purpose it's intentionally limited and cut down and does not serve
|
||||
all uses cases.
|
||||
</description>
|
||||
|
||||
<interface name="frog_color_management_factory_v1" version="1">
|
||||
<description summary="color management factory">
|
||||
The color management factory singleton creates color managed surface objects.
|
||||
</description>
|
||||
|
||||
<request name="destroy" type="destructor"></request>
|
||||
|
||||
<request name="get_color_managed_surface">
|
||||
<description summary="create color management interface for surface">
|
||||
</description>
|
||||
|
||||
<arg name="surface" type="object" interface="wl_surface"
|
||||
summary="target surface" />
|
||||
<arg name="callback" type="new_id" interface="frog_color_managed_surface"
|
||||
summary="new color managed surface object" />
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="frog_color_managed_surface" version="1">
|
||||
<description summary="color managed surface">
|
||||
Interface for changing surface color management and HDR state.
|
||||
|
||||
An implementation must: support every part of the version
|
||||
of the frog_color_managed_surface interface it exposes.
|
||||
Including all known enums associated with a given version.
|
||||
</description>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy color managed surface">
|
||||
Destroying the color managed surface resets all known color
|
||||
state for the surface back to 'undefined' implementation-specific
|
||||
values.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<enum name="transfer_function">
|
||||
<description summary="known transfer functions">
|
||||
Extended information on the transfer functions described
|
||||
here can be found in the Khronos Data Format specification:
|
||||
https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html
|
||||
</description>
|
||||
<entry name="undefined" value="0"
|
||||
summary="specifies undefined, implementation-specific handling of the surface's transfer function." />
|
||||
<entry name="srgb" value="1"
|
||||
summary="specifies the sRGB non-linear EOTF. An implementation may: display this as Gamma 2.2 for the purposes of being consistent with content rendering across displays, rendering_intent and user expectations." />
|
||||
<entry name="gamma_22" value="2" summary="specifies gamma 2.2 power curve as the EOTF" />
|
||||
<entry name="st2084_pq" value="3"
|
||||
summary="specifies the SMPTE ST2084 Perceptual Quantizer (PQ) EOTF" />
|
||||
<entry name="scrgb_linear" value="4"
|
||||
summary="specifies the scRGB (extended sRGB) linear EOTF. Note: Primaries outside the gamut triangle specified can be expressed with negative values for this transfer function." />
|
||||
</enum>
|
||||
|
||||
<request name="set_known_transfer_function">
|
||||
<description summary="sets a known transfer function for a surface" />
|
||||
<arg name="transfer_function" type="uint" enum="transfer_function"
|
||||
summary="transfer function for the surface" />
|
||||
</request>
|
||||
|
||||
<enum name="primaries">
|
||||
<description summary="known primaries" />
|
||||
<entry name="undefined" value="0"
|
||||
summary="specifies undefined, implementation-specific handling" />
|
||||
<entry name="rec709" value="1" summary="specifies Rec.709/sRGB primaries with D65 white point" />
|
||||
<entry name="rec2020" value="2"
|
||||
summary="specifies Rec.2020/HDR10 primaries with D65 white point" />
|
||||
</enum>
|
||||
|
||||
<request name="set_known_container_color_volume">
|
||||
<description summary="sets the container color volume (primaries) for a surface" />
|
||||
<arg name="primaries" type="uint" enum="primaries" summary="primaries for the surface" />
|
||||
</request>
|
||||
|
||||
<enum name="render_intent">
|
||||
<description summary="known render intents">
|
||||
Extended information on render intents described
|
||||
here can be found in ICC.1:2022:
|
||||
|
||||
https://www.color.org/specification/ICC.1-2022-05.pdf
|
||||
</description>
|
||||
<entry name="perceptual" value="0" summary="perceptual" />
|
||||
</enum>
|
||||
|
||||
<request name="set_render_intent">
|
||||
<description summary="sets the render intent for a surface">
|
||||
NOTE: On a surface with "perceptual" (default) render intent, handling of the container's
|
||||
color volume
|
||||
is implementation-specific, and may differ between different transfer functions it is paired
|
||||
with:
|
||||
ie. sRGB + 709 rendering may have it's primaries widened to more of the available display's
|
||||
gamut
|
||||
to be be more pleasing for the viewer.
|
||||
Compared to scRGB Linear + 709 being treated faithfully as 709
|
||||
(including utilizing negatives out of the 709 gamut triangle)
|
||||
</description>
|
||||
<arg name="render_intent" type="uint" enum="render_intent"
|
||||
summary="render intent for the surface" />
|
||||
</request>
|
||||
|
||||
<request name="set_hdr_metadata">
|
||||
<description summary="set HDR metadata for a surface">
|
||||
Forwards HDR metadata from the client to the compositor.
|
||||
|
||||
HDR Metadata Infoframe as per CTA 861.G spec.
|
||||
|
||||
Usage of this HDR metadata is implementation specific and
|
||||
outside of the scope of this protocol.
|
||||
</description>
|
||||
<arg name="mastering_display_primary_red_x" type="uint">
|
||||
<description summary="red primary x coordinate">
|
||||
Mastering Red Color Primary X Coordinate of the Data.
|
||||
|
||||
Coded as unsigned 16-bit values in units of
|
||||
0.00002, where 0x0000 represents zero and 0xC350
|
||||
represents 1.0000.
|
||||
</description>
|
||||
</arg>
|
||||
<arg name="mastering_display_primary_red_y" type="uint">
|
||||
<description summary="red primary y coordinate">
|
||||
Mastering Red Color Primary Y Coordinate of the Data.
|
||||
|
||||
Coded as unsigned 16-bit values in units of
|
||||
0.00002, where 0x0000 represents zero and 0xC350
|
||||
represents 1.0000.
|
||||
</description>
|
||||
</arg>
|
||||
<arg name="mastering_display_primary_green_x" type="uint">
|
||||
<description summary="green primary x coordinate">
|
||||
Mastering Green Color Primary X Coordinate of the Data.
|
||||
|
||||
Coded as unsigned 16-bit values in units of
|
||||
0.00002, where 0x0000 represents zero and 0xC350
|
||||
represents 1.0000.
|
||||
</description>
|
||||
</arg>
|
||||
<arg name="mastering_display_primary_green_y" type="uint">
|
||||
<description summary="green primary y coordinate">
|
||||
Mastering Green Color Primary Y Coordinate of the Data.
|
||||
|
||||
Coded as unsigned 16-bit values in units of
|
||||
0.00002, where 0x0000 represents zero and 0xC350
|
||||
represents 1.0000.
|
||||
</description>
|
||||
</arg>
|
||||
<arg name="mastering_display_primary_blue_x" type="uint">
|
||||
<description summary="blue primary x coordinate">
|
||||
Mastering Blue Color Primary X Coordinate of the Data.
|
||||
|
||||
Coded as unsigned 16-bit values in units of
|
||||
0.00002, where 0x0000 represents zero and 0xC350
|
||||
represents 1.0000.
|
||||
</description>
|
||||
</arg>
|
||||
<arg name="mastering_display_primary_blue_y" type="uint">
|
||||
<description summary="blue primary y coordinate">
|
||||
Mastering Blue Color Primary Y Coordinate of the Data.
|
||||
|
||||
Coded as unsigned 16-bit values in units of
|
||||
0.00002, where 0x0000 represents zero and 0xC350
|
||||
represents 1.0000.
|
||||
</description>
|
||||
</arg>
|
||||
<arg name="mastering_white_point_x" type="uint">
|
||||
<description summary="white point x coordinate">
|
||||
Mastering White Point X Coordinate of the Data.
|
||||
|
||||
These are coded as unsigned 16-bit values in units of
|
||||
0.00002, where 0x0000 represents zero and 0xC350
|
||||
represents 1.0000.
|
||||
</description>
|
||||
</arg>
|
||||
<arg name="mastering_white_point_y" type="uint">
|
||||
<description summary="white point y coordinate">
|
||||
Mastering White Point Y Coordinate of the Data.
|
||||
|
||||
These are coded as unsigned 16-bit values in units of
|
||||
0.00002, where 0x0000 represents zero and 0xC350
|
||||
represents 1.0000.
|
||||
</description>
|
||||
</arg>
|
||||
<arg name="max_display_mastering_luminance" type="uint">
|
||||
<description summary="max display mastering luminance">
|
||||
Max Mastering Display Luminance.
|
||||
This value is coded as an unsigned 16-bit value in units of 1 cd/m2,
|
||||
where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2.
|
||||
</description>
|
||||
</arg>
|
||||
<arg name="min_display_mastering_luminance" type="uint">
|
||||
<description summary="min display mastering luminance">
|
||||
Min Mastering Display Luminance.
|
||||
This value is coded as an unsigned 16-bit value in units of
|
||||
0.0001 cd/m2, where 0x0001 represents 0.0001 cd/m2 and 0xFFFF
|
||||
represents 6.5535 cd/m2.
|
||||
</description>
|
||||
</arg>
|
||||
<arg name="max_cll" type="uint">
|
||||
<description summary="max content light level">
|
||||
Max Content Light Level.
|
||||
This value is coded as an unsigned 16-bit value in units of 1 cd/m2,
|
||||
where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2.
|
||||
</description>
|
||||
</arg>
|
||||
<arg name="max_fall" type="uint">
|
||||
<description summary="max frame average light level">
|
||||
Max Frame Average Light Level.
|
||||
This value is coded as an unsigned 16-bit value in units of 1 cd/m2,
|
||||
where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2.
|
||||
</description>
|
||||
</arg>
|
||||
</request>
|
||||
|
||||
<event name="preferred_metadata">
|
||||
<description summary="preferred metadata for a surface">
|
||||
Current preferred metadata for a surface.
|
||||
The application should use this information to tone-map its buffers
|
||||
to this target before committing.
|
||||
|
||||
This metadata does not necessarily correspond to any physical output, but
|
||||
rather what the compositor thinks would be best for a given surface.
|
||||
</description>
|
||||
<arg name="transfer_function" type="uint" enum="transfer_function">
|
||||
<description summary="output's current transfer function">
|
||||
Specifies a known transfer function that corresponds to the
|
||||
output the surface is targeting.
|
||||
</description>
|
||||
</arg>
|
||||
<arg name="output_display_primary_red_x" type="uint">
|
||||
<description summary="red primary x coordinate">
|
||||
Output Red Color Primary X Coordinate of the Data.
|
||||
|
||||
Coded as unsigned 16-bit values in units of
|
||||
0.00002, where 0x0000 represents zero and 0xC350
|
||||
represents 1.0000.
|
||||
</description>
|
||||
</arg>
|
||||
<arg name="output_display_primary_red_y" type="uint">
|
||||
<description summary="red primary y coordinate">
|
||||
Output Red Color Primary Y Coordinate of the Data.
|
||||
|
||||
Coded as unsigned 16-bit values in units of
|
||||
0.00002, where 0x0000 represents zero and 0xC350
|
||||
represents 1.0000.
|
||||
</description>
|
||||
</arg>
|
||||
<arg name="output_display_primary_green_x" type="uint">
|
||||
<description summary="green primary x coordinate">
|
||||
Output Green Color Primary X Coordinate of the Data.
|
||||
|
||||
Coded as unsigned 16-bit values in units of
|
||||
0.00002, where 0x0000 represents zero and 0xC350
|
||||
represents 1.0000.
|
||||
</description>
|
||||
</arg>
|
||||
<arg name="output_display_primary_green_y" type="uint">
|
||||
<description summary="green primary y coordinate">
|
||||
Output Green Color Primary Y Coordinate of the Data.
|
||||
|
||||
Coded as unsigned 16-bit values in units of
|
||||
0.00002, where 0x0000 represents zero and 0xC350
|
||||
represents 1.0000.
|
||||
</description>
|
||||
</arg>
|
||||
<arg name="output_display_primary_blue_x" type="uint">
|
||||
<description summary="blue primary x coordinate">
|
||||
Output Blue Color Primary X Coordinate of the Data.
|
||||
|
||||
Coded as unsigned 16-bit values in units of
|
||||
0.00002, where 0x0000 represents zero and 0xC350
|
||||
represents 1.0000.
|
||||
</description>
|
||||
</arg>
|
||||
<arg name="output_display_primary_blue_y" type="uint">
|
||||
<description summary="blue primary y coordinate">
|
||||
Output Blue Color Primary Y Coordinate of the Data.
|
||||
|
||||
Coded as unsigned 16-bit values in units of
|
||||
0.00002, where 0x0000 represents zero and 0xC350
|
||||
represents 1.0000.
|
||||
</description>
|
||||
</arg>
|
||||
<arg name="output_white_point_x" type="uint">
|
||||
<description summary="white point x coordinate">
|
||||
Output White Point X Coordinate of the Data.
|
||||
|
||||
These are coded as unsigned 16-bit values in units of
|
||||
0.00002, where 0x0000 represents zero and 0xC350
|
||||
represents 1.0000.
|
||||
</description>
|
||||
</arg>
|
||||
<arg name="output_white_point_y" type="uint">
|
||||
<description summary="white point y coordinate">
|
||||
Output White Point Y Coordinate of the Data.
|
||||
|
||||
These are coded as unsigned 16-bit values in units of
|
||||
0.00002, where 0x0000 represents zero and 0xC350
|
||||
represents 1.0000.
|
||||
</description>
|
||||
</arg>
|
||||
<arg name="max_luminance" type="uint">
|
||||
<description summary="maximum luminance">
|
||||
Max Output Luminance
|
||||
The max luminance in nits that the output is capable of rendering in small areas.
|
||||
Content should: not exceed this value to avoid clipping.
|
||||
|
||||
This value is coded as an unsigned 16-bit value in units of 1 cd/m2,
|
||||
where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2.
|
||||
</description>
|
||||
</arg>
|
||||
<arg name="min_luminance" type="uint">
|
||||
<description summary="minimum luminance">
|
||||
Min Output Luminance
|
||||
The min luminance that the output is capable of rendering.
|
||||
Content should: not exceed this value to avoid clipping.
|
||||
|
||||
This value is coded as an unsigned 16-bit value in units of
|
||||
0.0001 cd/m2, where 0x0001 represents 0.0001 cd/m2 and 0xFFFF
|
||||
represents 6.5535 cd/m2.
|
||||
</description>
|
||||
</arg>
|
||||
<arg name="max_full_frame_luminance" type="uint">
|
||||
<description summary="maximum full frame luminance">
|
||||
Max Full Frame Luminance
|
||||
The max luminance in nits that the output is capable of rendering for the
|
||||
full frame sustained.
|
||||
|
||||
This value is coded as an unsigned 16-bit value in units of 1 cd/m2,
|
||||
where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2.
|
||||
</description>
|
||||
</arg>
|
||||
</event>
|
||||
</interface>
|
||||
</protocol>
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -15,7 +15,7 @@ echo 'static const std::map<std::string, std::string> SHADERS = {' >> ./src/rend
|
|||
for filename in `ls ${SHADERS_SRC}`; do
|
||||
echo "-- ${filename}"
|
||||
|
||||
{ echo 'R"#('; cat ${SHADERS_SRC}/${filename}; echo ')#"'; } > ./src/render/shaders/${filename}.inc
|
||||
{ echo -n 'R"#('; cat ${SHADERS_SRC}/${filename}; echo ')#"'; } > ./src/render/shaders/${filename}.inc
|
||||
echo "{\"${filename}\"," >> ./src/render/shaders/Shaders.hpp
|
||||
echo "#include \"./${filename}.inc\"" >> ./src/render/shaders/Shaders.hpp
|
||||
echo "}," >> ./src/render/shaders/Shaders.hpp
|
||||
|
|
|
|||
1040
src/Compositor.cpp
1040
src/Compositor.cpp
File diff suppressed because it is too large
Load diff
|
|
@ -4,11 +4,13 @@
|
|||
|
||||
#include <ranges>
|
||||
|
||||
#include "helpers/math/Direction.hpp"
|
||||
#include "managers/XWaylandManager.hpp"
|
||||
#include "managers/KeybindManager.hpp"
|
||||
#include "managers/SessionLockManager.hpp"
|
||||
#include "desktop/Window.hpp"
|
||||
#include "protocols/types/ColorManagement.hpp"
|
||||
#include "desktop/view/Window.hpp"
|
||||
#include "helpers/cm/ColorManagement.hpp"
|
||||
#include "config/shared/workspace/WorkspaceRule.hpp"
|
||||
|
||||
#include <aquamarine/backend/Backend.hpp>
|
||||
#include <aquamarine/output/Output.hpp>
|
||||
|
|
@ -40,6 +42,7 @@ class CCompositor {
|
|||
} m_drmRenderNode;
|
||||
|
||||
bool m_initialized = false;
|
||||
bool m_safeMode = false;
|
||||
SP<Aquamarine::CBackend> m_aqBackend;
|
||||
|
||||
std::string m_hyprTempDataRoot = "";
|
||||
|
|
@ -55,6 +58,7 @@ class CCompositor {
|
|||
std::vector<PHLLS> m_layers;
|
||||
std::vector<PHLWINDOWREF> m_windowsFadingOut;
|
||||
std::vector<PHLLSREF> m_surfacesFadingOut;
|
||||
std::vector<SP<Desktop::View::IView>> m_otherViews;
|
||||
|
||||
std::unordered_map<std::string, MONITORID> m_monitorIDMap;
|
||||
std::unordered_map<std::string, WORKSPACEID> m_seenMonitorWorkspaceMap; // map of seen monitor names to workspace IDs
|
||||
|
|
@ -65,6 +69,7 @@ class CCompositor {
|
|||
void cleanup();
|
||||
void bumpNofile();
|
||||
void restoreNofile();
|
||||
bool setWatchdogFd(int fd);
|
||||
|
||||
bool m_readyToProcess = false;
|
||||
bool m_sessionActive = true;
|
||||
|
|
@ -94,7 +99,7 @@ class CCompositor {
|
|||
PHLMONITOR getMonitorFromVector(const Vector2D&);
|
||||
void removeWindowFromVectorSafe(PHLWINDOW);
|
||||
bool monitorExists(PHLMONITOR);
|
||||
PHLWINDOW vectorToWindowUnified(const Vector2D&, uint8_t properties, PHLWINDOW pIgnoreWindow = nullptr);
|
||||
PHLWINDOW vectorToWindowUnified(const Vector2D&, uint16_t properties, PHLWINDOW pIgnoreWindow = nullptr);
|
||||
SP<CWLSurfaceResource> vectorToLayerSurface(const Vector2D&, std::vector<PHLLSREF>*, Vector2D*, PHLLS*, bool aboveLockscreen = false);
|
||||
SP<CWLSurfaceResource> vectorToLayerPopupSurface(const Vector2D&, PHLMONITOR monitor, Vector2D*, PHLLS*);
|
||||
SP<CWLSurfaceResource> vectorWindowToSurface(const Vector2D&, PHLWINDOW, Vector2D& sl);
|
||||
|
|
@ -110,16 +115,16 @@ class CCompositor {
|
|||
bool isWindowActive(PHLWINDOW);
|
||||
void changeWindowZOrder(PHLWINDOW, bool);
|
||||
void cleanupFadingOut(const MONITORID& monid);
|
||||
PHLWINDOW getWindowInDirection(PHLWINDOW, char);
|
||||
PHLWINDOW getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, char dir, PHLWINDOW ignoreWindow = nullptr, bool useVectorAngles = false);
|
||||
PHLWINDOW getWindowInDirection(PHLWINDOW, Math::eDirection);
|
||||
PHLWINDOW getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, Math::eDirection dir, PHLWINDOW ignoreWindow = nullptr, bool useVectorAngles = false);
|
||||
PHLWINDOW getWindowCycle(PHLWINDOW cur, bool focusableOnly = false, std::optional<bool> floating = std::nullopt, bool visible = false, bool prev = false);
|
||||
PHLWINDOW getWindowCycleHist(PHLWINDOWREF cur, bool focusableOnly = false, std::optional<bool> floating = std::nullopt, bool visible = false, bool next = false);
|
||||
WORKSPACEID getNextAvailableNamedWorkspace();
|
||||
bool isPointOnAnyMonitor(const Vector2D&);
|
||||
bool isPointOnReservedArea(const Vector2D& point, const PHLMONITOR monitor = nullptr);
|
||||
CBox calculateX11WorkArea();
|
||||
PHLMONITOR getMonitorInDirection(const char&);
|
||||
PHLMONITOR getMonitorInDirection(PHLMONITOR, const char&);
|
||||
std::optional<CBox> calculateX11WorkArea();
|
||||
PHLMONITOR getMonitorInDirection(Math::eDirection);
|
||||
PHLMONITOR getMonitorInDirection(PHLMONITOR, Math::eDirection);
|
||||
void updateAllWindowsAnimatedDecorationValues();
|
||||
MONITORID getNextAvailableMonitorID(std::string const& name);
|
||||
void moveWorkspaceToMonitor(PHLWORKSPACE, PHLMONITOR, bool noWarpCursor = false);
|
||||
|
|
@ -128,7 +133,7 @@ class CCompositor {
|
|||
bool workspaceIDOutOfBounds(const WORKSPACEID&);
|
||||
void setWindowFullscreenInternal(const PHLWINDOW PWINDOW, const eFullscreenMode MODE);
|
||||
void setWindowFullscreenClient(const PHLWINDOW PWINDOW, const eFullscreenMode MODE);
|
||||
void setWindowFullscreenState(const PHLWINDOW PWINDOW, const SFullscreenState state);
|
||||
void setWindowFullscreenState(const PHLWINDOW PWINDOW, const Desktop::View::SFullscreenState state);
|
||||
void changeWindowFullscreenModeClient(const PHLWINDOW PWINDOW, const eFullscreenMode MODE, const bool ON);
|
||||
PHLWINDOW getX11Parent(PHLWINDOW);
|
||||
void scheduleFrameForMonitor(PHLMONITOR, Aquamarine::IOutput::scheduleFrameReason reason = Aquamarine::IOutput::AQ_SCHEDULE_CLIENT_UNKNOWN);
|
||||
|
|
@ -156,31 +161,37 @@ class CCompositor {
|
|||
void setPreferredTransformForSurface(SP<CWLSurfaceResource> pSurface, wl_output_transform transform);
|
||||
void updateSuspendedStates();
|
||||
void onNewMonitor(SP<Aquamarine::IOutput> output);
|
||||
void ensurePersistentWorkspacesPresent(const std::vector<SWorkspaceRule>& rules, PHLWORKSPACE pWorkspace = nullptr);
|
||||
void ensurePersistentWorkspacesPresent(const std::vector<Config::CWorkspaceRule>& rules, PHLWORKSPACE pWorkspace = nullptr);
|
||||
void ensurePersistentWorkspacesPresent(PHLWORKSPACE pWorkspace = nullptr);
|
||||
void ensureWorkspacesOnAssignedMonitors();
|
||||
std::optional<unsigned int> getVTNr();
|
||||
bool isVRRActiveOnAnyMonitor() const;
|
||||
|
||||
NColorManagement::SImageDescription getPreferredImageDescription();
|
||||
NColorManagement::PImageDescription getPreferredImageDescription();
|
||||
NColorManagement::PImageDescription getHDRImageDescription();
|
||||
bool shouldChangePreferredImageDescription();
|
||||
|
||||
bool supportsDrmSyncobjTimeline() const;
|
||||
std::string m_explicitConfigPath;
|
||||
|
||||
private:
|
||||
void initAllSignals();
|
||||
void removeAllSignals();
|
||||
void cleanEnvironment();
|
||||
void setRandomSplash();
|
||||
void initManagers(eManagersInitStage stage);
|
||||
void prepareFallbackOutput();
|
||||
void createLockFile();
|
||||
void removeLockFile();
|
||||
void setMallocThreshold();
|
||||
void initAllSignals();
|
||||
void removeAllSignals();
|
||||
void cleanEnvironment();
|
||||
void setRandomSplash();
|
||||
void initManagers(eManagersInitStage stage);
|
||||
void prepareFallbackOutput();
|
||||
void createLockFile();
|
||||
void removeLockFile();
|
||||
void setMallocThreshold();
|
||||
void openSafeModeBox();
|
||||
|
||||
uint64_t m_hyprlandPID = 0;
|
||||
wl_event_source* m_critSigSource = nullptr;
|
||||
rlimit m_originalNofile = {};
|
||||
uint64_t m_hyprlandPID = 0;
|
||||
wl_event_source* m_critSigSource = nullptr;
|
||||
rlimit m_originalNofile = {};
|
||||
Hyprutils::OS::CFileDescriptor m_watchdogWriteFd;
|
||||
|
||||
std::vector<PHLWORKSPACEREF> m_workspaces;
|
||||
std::vector<PHLWORKSPACEREF> m_workspaces;
|
||||
};
|
||||
|
||||
inline UP<CCompositor> g_pCompositor;
|
||||
|
|
|
|||
|
|
@ -38,10 +38,6 @@ enum eInputType : uint8_t {
|
|||
INPUT_TYPE_MOTION
|
||||
};
|
||||
|
||||
struct SCallbackInfo {
|
||||
bool cancelled = false; /* on cancellable events, will cancel the event. */
|
||||
};
|
||||
|
||||
enum eHyprCtlOutputFormat : uint8_t {
|
||||
FORMAT_NORMAL = 0,
|
||||
FORMAT_JSON
|
||||
|
|
@ -62,5 +58,3 @@ struct SDispatchResult {
|
|||
using WINDOWID = int64_t;
|
||||
using MONITORID = int64_t;
|
||||
using WORKSPACEID = int64_t;
|
||||
|
||||
using HOOK_CALLBACK_FN = std::function<void(void*, SCallbackInfo&, std::any)>;
|
||||
|
|
|
|||
|
|
@ -1,180 +0,0 @@
|
|||
#pragma once
|
||||
#include "../defines.hpp"
|
||||
#include "../helpers/varlist/VarList.hpp"
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
enum eConfigValueDataTypes : int8_t {
|
||||
CVD_TYPE_INVALID = -1,
|
||||
CVD_TYPE_GRADIENT = 0,
|
||||
CVD_TYPE_CSS_VALUE = 1,
|
||||
CVD_TYPE_FONT_WEIGHT = 2,
|
||||
};
|
||||
|
||||
class ICustomConfigValueData {
|
||||
public:
|
||||
virtual ~ICustomConfigValueData() = default;
|
||||
|
||||
virtual eConfigValueDataTypes getDataType() = 0;
|
||||
|
||||
virtual std::string toString() = 0;
|
||||
};
|
||||
|
||||
class CGradientValueData : public ICustomConfigValueData {
|
||||
public:
|
||||
CGradientValueData() = default;
|
||||
CGradientValueData(CHyprColor col) {
|
||||
m_colors.push_back(col);
|
||||
updateColorsOk();
|
||||
};
|
||||
virtual ~CGradientValueData() = default;
|
||||
|
||||
virtual eConfigValueDataTypes getDataType() {
|
||||
return CVD_TYPE_GRADIENT;
|
||||
}
|
||||
|
||||
void reset(CHyprColor col) {
|
||||
m_colors.clear();
|
||||
m_colors.emplace_back(col);
|
||||
m_angle = 0;
|
||||
updateColorsOk();
|
||||
}
|
||||
|
||||
void updateColorsOk() {
|
||||
m_colorsOkLabA.clear();
|
||||
for (auto& c : m_colors) {
|
||||
const auto OKLAB = c.asOkLab();
|
||||
m_colorsOkLabA.emplace_back(OKLAB.l);
|
||||
m_colorsOkLabA.emplace_back(OKLAB.a);
|
||||
m_colorsOkLabA.emplace_back(OKLAB.b);
|
||||
m_colorsOkLabA.emplace_back(c.a);
|
||||
}
|
||||
}
|
||||
|
||||
/* Vector containing the colors */
|
||||
std::vector<CHyprColor> m_colors;
|
||||
|
||||
/* Vector containing pure colors for shoving into opengl */
|
||||
std::vector<float> m_colorsOkLabA;
|
||||
|
||||
/* Float corresponding to the angle (rad) */
|
||||
float m_angle = 0;
|
||||
|
||||
//
|
||||
bool operator==(const CGradientValueData& other) const {
|
||||
if (other.m_colors.size() != m_colors.size() || m_angle != other.m_angle)
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < m_colors.size(); ++i)
|
||||
if (m_colors[i] != other.m_colors[i])
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual std::string toString() {
|
||||
std::string result;
|
||||
for (auto& c : m_colors) {
|
||||
result += std::format("{:x} ", c.getAsHex());
|
||||
}
|
||||
|
||||
result += std::format("{}deg", sc<int>(m_angle * 180.0 / M_PI));
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
class CCssGapData : public ICustomConfigValueData {
|
||||
public:
|
||||
CCssGapData() : m_top(0), m_right(0), m_bottom(0), m_left(0) {};
|
||||
CCssGapData(int64_t global) : m_top(global), m_right(global), m_bottom(global), m_left(global) {};
|
||||
CCssGapData(int64_t vertical, int64_t horizontal) : m_top(vertical), m_right(horizontal), m_bottom(vertical), m_left(horizontal) {};
|
||||
CCssGapData(int64_t top, int64_t horizontal, int64_t bottom) : m_top(top), m_right(horizontal), m_bottom(bottom), m_left(horizontal) {};
|
||||
CCssGapData(int64_t top, int64_t right, int64_t bottom, int64_t left) : m_top(top), m_right(right), m_bottom(bottom), m_left(left) {};
|
||||
|
||||
/* Css like directions */
|
||||
int64_t m_top;
|
||||
int64_t m_right;
|
||||
int64_t m_bottom;
|
||||
int64_t m_left;
|
||||
|
||||
void parseGapData(CVarList2 varlist) {
|
||||
const auto toInt = [](std::string_view string) -> int { return std::stoi(std::string(string)); };
|
||||
|
||||
switch (varlist.size()) {
|
||||
case 1: {
|
||||
*this = CCssGapData(toInt(varlist[0]));
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
*this = CCssGapData(toInt(varlist[0]), toInt(varlist[1]));
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
*this = CCssGapData(toInt(varlist[0]), toInt(varlist[1]), toInt(varlist[2]));
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
*this = CCssGapData(toInt(varlist[0]), toInt(varlist[1]), toInt(varlist[2]), toInt(varlist[3]));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Debug::log(WARN, "Too many arguments provided for gaps.");
|
||||
*this = CCssGapData(toInt(varlist[0]), toInt(varlist[1]), toInt(varlist[2]), toInt(varlist[3]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void reset(int64_t global) {
|
||||
m_top = global;
|
||||
m_right = global;
|
||||
m_bottom = global;
|
||||
m_left = global;
|
||||
}
|
||||
|
||||
virtual eConfigValueDataTypes getDataType() {
|
||||
return CVD_TYPE_CSS_VALUE;
|
||||
}
|
||||
|
||||
virtual std::string toString() {
|
||||
return std::format("{} {} {} {}", m_top, m_right, m_bottom, m_left);
|
||||
}
|
||||
};
|
||||
|
||||
class CFontWeightConfigValueData : public ICustomConfigValueData {
|
||||
public:
|
||||
CFontWeightConfigValueData() = default;
|
||||
CFontWeightConfigValueData(const char* weight) {
|
||||
parseWeight(weight);
|
||||
}
|
||||
|
||||
int64_t m_value = 400; // default to normal weight
|
||||
|
||||
virtual eConfigValueDataTypes getDataType() {
|
||||
return CVD_TYPE_FONT_WEIGHT;
|
||||
}
|
||||
|
||||
virtual std::string toString() {
|
||||
return std::format("{}", m_value);
|
||||
}
|
||||
|
||||
void parseWeight(const std::string& strWeight) {
|
||||
auto lcWeight{strWeight};
|
||||
std::ranges::transform(strWeight, lcWeight.begin(), ::tolower);
|
||||
|
||||
// values taken from Pango weight enums
|
||||
const auto WEIGHTS = std::map<std::string, int>{
|
||||
{"thin", 100}, {"ultralight", 200}, {"light", 300}, {"semilight", 350}, {"book", 380}, {"normal", 400},
|
||||
{"medium", 500}, {"semibold", 600}, {"bold", 700}, {"ultrabold", 800}, {"heavy", 900}, {"ultraheavy", 1000},
|
||||
};
|
||||
|
||||
auto weight = WEIGHTS.find(lcWeight);
|
||||
if (weight != WEIGHTS.end())
|
||||
m_value = weight->second;
|
||||
else {
|
||||
int w_i = std::stoi(strWeight);
|
||||
if (w_i < 100 || w_i > 1000)
|
||||
m_value = 400;
|
||||
}
|
||||
}
|
||||
};
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,347 +1,63 @@
|
|||
#pragma once
|
||||
|
||||
#include <hyprutils/animation/AnimationConfig.hpp>
|
||||
#define CONFIG_MANAGER_H
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
#include <expected>
|
||||
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include "../defines.hpp"
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <functional>
|
||||
#include <xf86drmMode.h>
|
||||
#include "../helpers/Monitor.hpp"
|
||||
#include "../desktop/Window.hpp"
|
||||
|
||||
#include "ConfigDataValues.hpp"
|
||||
#include "../SharedDefs.hpp"
|
||||
#include "../helpers/Color.hpp"
|
||||
#include "../desktop/DesktopTypes.hpp"
|
||||
#include "./shared/Types.hpp"
|
||||
#include "../helpers/memory/Memory.hpp"
|
||||
#include "../managers/XWaylandManager.hpp"
|
||||
#include "../managers/KeybindManager.hpp"
|
||||
|
||||
#include <hyprlang.hpp>
|
||||
namespace Config {
|
||||
|
||||
#define HANDLE void*
|
||||
|
||||
class CConfigManager;
|
||||
|
||||
struct SWorkspaceRule {
|
||||
std::string monitor = "";
|
||||
std::string workspaceString = "";
|
||||
std::string workspaceName = "";
|
||||
WORKSPACEID workspaceId = -1;
|
||||
bool isDefault = false;
|
||||
bool isPersistent = false;
|
||||
std::optional<CCssGapData> gapsIn;
|
||||
std::optional<CCssGapData> gapsOut;
|
||||
std::optional<CCssGapData> floatGaps = gapsOut;
|
||||
std::optional<int64_t> borderSize;
|
||||
std::optional<bool> decorate;
|
||||
std::optional<bool> noRounding;
|
||||
std::optional<bool> noBorder;
|
||||
std::optional<bool> noShadow;
|
||||
std::optional<std::string> onCreatedEmptyRunCmd;
|
||||
std::optional<std::string> defaultName;
|
||||
std::map<std::string, std::string> layoutopts;
|
||||
};
|
||||
|
||||
struct SMonitorAdditionalReservedArea {
|
||||
int top = 0;
|
||||
int bottom = 0;
|
||||
int left = 0;
|
||||
int right = 0;
|
||||
};
|
||||
|
||||
struct SPluginKeyword {
|
||||
HANDLE handle = nullptr;
|
||||
std::string name = "";
|
||||
Hyprlang::PCONFIGHANDLERFUNC fn = nullptr;
|
||||
};
|
||||
|
||||
struct SPluginVariable {
|
||||
HANDLE handle = nullptr;
|
||||
std::string name = "";
|
||||
};
|
||||
|
||||
enum eConfigOptionType : uint8_t {
|
||||
CONFIG_OPTION_BOOL = 0,
|
||||
CONFIG_OPTION_INT = 1, /* e.g. 0/1/2*/
|
||||
CONFIG_OPTION_FLOAT = 2,
|
||||
CONFIG_OPTION_STRING_SHORT = 3, /* e.g. "auto" */
|
||||
CONFIG_OPTION_STRING_LONG = 4, /* e.g. a command */
|
||||
CONFIG_OPTION_COLOR = 5,
|
||||
CONFIG_OPTION_CHOICE = 6, /* e.g. "one", "two", "three" */
|
||||
CONFIG_OPTION_GRADIENT = 7,
|
||||
CONFIG_OPTION_VECTOR = 8,
|
||||
};
|
||||
|
||||
enum eConfigOptionFlags : uint8_t {
|
||||
CONFIG_OPTION_FLAG_PERCENTAGE = (1 << 0),
|
||||
};
|
||||
|
||||
struct SConfigOptionDescription {
|
||||
|
||||
struct SBoolData {
|
||||
bool value = false;
|
||||
struct SConfigOptionReply {
|
||||
// <type>* const*
|
||||
void* const* dataptr = nullptr;
|
||||
const std::type_info* type = nullptr;
|
||||
bool setByUser = false;
|
||||
};
|
||||
|
||||
struct SRangeData {
|
||||
int value = 0, min = 0, max = 2;
|
||||
enum eConfigManagerType : uint8_t {
|
||||
CONFIG_LEGACY = 0,
|
||||
CONFIG_LUA
|
||||
};
|
||||
|
||||
struct SFloatData {
|
||||
float value = 0, min = 0, max = 100;
|
||||
class IConfigManager {
|
||||
protected:
|
||||
IConfigManager() = default;
|
||||
|
||||
public:
|
||||
virtual ~IConfigManager() = default;
|
||||
|
||||
virtual eConfigManagerType type() = 0;
|
||||
|
||||
virtual void init() = 0;
|
||||
virtual void reload() = 0;
|
||||
virtual std::string verify() = 0;
|
||||
|
||||
virtual int getDeviceInt(const std::string&, const std::string&, const std::string& fallback = "") = 0;
|
||||
virtual float getDeviceFloat(const std::string&, const std::string&, const std::string& fallback = "") = 0;
|
||||
virtual Vector2D getDeviceVec(const std::string&, const std::string&, const std::string& fallback = "") = 0;
|
||||
virtual std::string getDeviceString(const std::string&, const std::string&, const std::string& fallback = "") = 0;
|
||||
virtual bool deviceConfigExplicitlySet(const std::string&, const std::string&) = 0;
|
||||
virtual bool deviceConfigExists(const std::string&) = 0;
|
||||
|
||||
virtual SConfigOptionReply getConfigValue(const std::string&) = 0;
|
||||
|
||||
virtual std::string getMainConfigPath() = 0;
|
||||
virtual std::string currentConfigPath() = 0;
|
||||
virtual std::string getConfigString() = 0;
|
||||
virtual const std::vector<std::string>& getConfigPaths() = 0;
|
||||
|
||||
virtual bool configVerifPassed() = 0;
|
||||
|
||||
virtual std::string getErrors() = 0;
|
||||
|
||||
virtual std::expected<void, std::string> generateDefaultConfig(const std::filesystem::path&, bool safeMode = false) = 0;
|
||||
|
||||
virtual void handlePluginLoads() = 0;
|
||||
};
|
||||
|
||||
struct SStringData {
|
||||
std::string value;
|
||||
};
|
||||
bool initConfigManager();
|
||||
|
||||
struct SColorData {
|
||||
CHyprColor color;
|
||||
};
|
||||
|
||||
struct SChoiceData {
|
||||
int firstIndex = 0;
|
||||
std::string choices; // comma-separated
|
||||
};
|
||||
|
||||
struct SGradientData {
|
||||
std::string gradient;
|
||||
};
|
||||
|
||||
struct SVectorData {
|
||||
Vector2D vec, min, max;
|
||||
};
|
||||
|
||||
std::string value; // e.g. general:gaps_in
|
||||
std::string description;
|
||||
std::string specialCategory; // if value is special (e.g. device:abc) value will be abc and special device
|
||||
bool specialKey = false;
|
||||
eConfigOptionType type = CONFIG_OPTION_BOOL;
|
||||
uint32_t flags = 0; // eConfigOptionFlags
|
||||
|
||||
std::string jsonify() const;
|
||||
|
||||
//
|
||||
std::variant<SBoolData, SRangeData, SFloatData, SStringData, SColorData, SChoiceData, SGradientData, SVectorData> data;
|
||||
};
|
||||
|
||||
struct SFirstExecRequest {
|
||||
std::string exec = "";
|
||||
bool withRules = false;
|
||||
};
|
||||
|
||||
struct SFloatCache {
|
||||
size_t hash;
|
||||
|
||||
SFloatCache(PHLWINDOW window, bool initial) {
|
||||
// Base hash from class/title
|
||||
size_t baseHash = initial ? (std::hash<std::string>{}(window->m_initialClass) ^ (std::hash<std::string>{}(window->m_initialTitle) << 1)) :
|
||||
(std::hash<std::string>{}(window->m_class) ^ (std::hash<std::string>{}(window->m_title) << 1));
|
||||
|
||||
// Use empty string as default tag value
|
||||
std::string tagValue = "";
|
||||
if (auto xdgTag = window->xdgTag())
|
||||
tagValue = xdgTag.value();
|
||||
|
||||
// Combine hashes
|
||||
hash = baseHash ^ (std::hash<std::string>{}(tagValue) << 2);
|
||||
}
|
||||
|
||||
bool operator==(const SFloatCache& other) const {
|
||||
return hash == other.hash;
|
||||
}
|
||||
};
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<SFloatCache> {
|
||||
size_t operator()(const SFloatCache& id) const {
|
||||
return id.hash;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class CMonitorRuleParser {
|
||||
public:
|
||||
CMonitorRuleParser(const std::string& name);
|
||||
|
||||
const std::string& name();
|
||||
SMonitorRule& rule();
|
||||
std::optional<std::string> getError();
|
||||
bool parseMode(const std::string& value);
|
||||
bool parsePosition(const std::string& value, bool isFirst = false);
|
||||
bool parseScale(const std::string& value);
|
||||
bool parseTransform(const std::string& value);
|
||||
bool parseBitdepth(const std::string& value);
|
||||
bool parseCM(const std::string& value);
|
||||
bool parseSDRBrightness(const std::string& value);
|
||||
bool parseSDRSaturation(const std::string& value);
|
||||
bool parseVRR(const std::string& value);
|
||||
|
||||
void setDisabled();
|
||||
void setMirror(const std::string& value);
|
||||
bool setReserved(const SMonitorAdditionalReservedArea& value);
|
||||
|
||||
private:
|
||||
SMonitorRule m_rule;
|
||||
std::string m_error = "";
|
||||
};
|
||||
|
||||
class CConfigManager {
|
||||
public:
|
||||
CConfigManager();
|
||||
|
||||
void init();
|
||||
void reload();
|
||||
std::string verify();
|
||||
|
||||
int getDeviceInt(const std::string&, const std::string&, const std::string& fallback = "");
|
||||
float getDeviceFloat(const std::string&, const std::string&, const std::string& fallback = "");
|
||||
Vector2D getDeviceVec(const std::string&, const std::string&, const std::string& fallback = "");
|
||||
std::string getDeviceString(const std::string&, const std::string&, const std::string& fallback = "");
|
||||
bool deviceConfigExplicitlySet(const std::string&, const std::string&);
|
||||
bool deviceConfigExists(const std::string&);
|
||||
Hyprlang::CConfigValue* getConfigValueSafeDevice(const std::string& dev, const std::string& val, const std::string& fallback);
|
||||
|
||||
void* const* getConfigValuePtr(const std::string&);
|
||||
Hyprlang::CConfigValue* getHyprlangConfigValuePtr(const std::string& name, const std::string& specialCat = "");
|
||||
std::string getMainConfigPath();
|
||||
std::string getConfigString();
|
||||
|
||||
SMonitorRule getMonitorRuleFor(const PHLMONITOR);
|
||||
SWorkspaceRule getWorkspaceRuleFor(PHLWORKSPACE workspace);
|
||||
std::string getDefaultWorkspaceFor(const std::string&);
|
||||
|
||||
PHLMONITOR getBoundMonitorForWS(const std::string&);
|
||||
std::string getBoundMonitorStringForWS(const std::string&);
|
||||
const std::vector<SWorkspaceRule>& getAllWorkspaceRules();
|
||||
|
||||
void ensurePersistentWorkspacesPresent();
|
||||
|
||||
const std::vector<SConfigOptionDescription>& getAllDescriptions();
|
||||
|
||||
std::unordered_map<std::string, SMonitorAdditionalReservedArea> m_mAdditionalReservedAreas;
|
||||
|
||||
const std::unordered_map<std::string, SP<Hyprutils::Animation::SAnimationPropertyConfig>>& getAnimationConfig();
|
||||
|
||||
void addPluginConfigVar(HANDLE handle, const std::string& name, const Hyprlang::CConfigValue& value);
|
||||
void addPluginKeyword(HANDLE handle, const std::string& name, Hyprlang::PCONFIGHANDLERFUNC fun, Hyprlang::SHandlerOptions opts = {});
|
||||
void removePluginConfig(HANDLE handle);
|
||||
|
||||
// no-op when done.
|
||||
void dispatchExecOnce();
|
||||
void dispatchExecShutdown();
|
||||
|
||||
void performMonitorReload();
|
||||
void ensureMonitorStatus();
|
||||
void ensureVRR(PHLMONITOR pMonitor = nullptr);
|
||||
|
||||
bool shouldUseSoftwareCursors(PHLMONITOR pMonitor);
|
||||
void updateWatcher();
|
||||
|
||||
std::string parseKeyword(const std::string&, const std::string&);
|
||||
|
||||
void addParseError(const std::string&);
|
||||
|
||||
SP<Hyprutils::Animation::SAnimationPropertyConfig> getAnimationPropertyConfig(const std::string&);
|
||||
|
||||
void handlePluginLoads();
|
||||
std::string getErrors();
|
||||
|
||||
// keywords
|
||||
std::optional<std::string> handleRawExec(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleExec(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleExecOnce(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleExecRawOnce(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleExecShutdown(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleMonitor(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleBind(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleUnbind(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleWorkspaceRules(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleBezier(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleAnimation(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleSource(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleSubmap(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleBindWS(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleEnv(const std::string&, const std::string&);
|
||||
std::optional<std::string> handlePlugin(const std::string&, const std::string&);
|
||||
std::optional<std::string> handlePermission(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleGesture(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleWindowrule(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleLayerrule(const std::string&, const std::string&);
|
||||
|
||||
std::optional<std::string> handleMonitorv2(const std::string& output);
|
||||
Hyprlang::CParseResult handleMonitorv2();
|
||||
std::optional<std::string> addRuleFromConfigKey(const std::string& name);
|
||||
std::optional<std::string> addLayerRuleFromConfigKey(const std::string& name);
|
||||
Hyprlang::CParseResult reloadRules();
|
||||
|
||||
std::string m_configCurrentPath;
|
||||
|
||||
bool m_wantsMonitorReload = false;
|
||||
bool m_noMonitorReload = false;
|
||||
bool m_isLaunchingExecOnce = false; // For exec-once to skip initial ws tracking
|
||||
bool m_lastConfigVerificationWasSuccessful = true;
|
||||
|
||||
void storeFloatingSize(PHLWINDOW window, const Vector2D& size);
|
||||
std::optional<Vector2D> getStoredFloatingSize(PHLWINDOW window);
|
||||
|
||||
private:
|
||||
UP<Hyprlang::CConfig> m_config;
|
||||
|
||||
std::vector<std::string> m_configPaths;
|
||||
|
||||
Hyprutils::Animation::CAnimationConfigTree m_animationTree;
|
||||
|
||||
SSubmap m_currentSubmap;
|
||||
|
||||
std::vector<std::string> m_declaredPlugins;
|
||||
std::vector<SPluginKeyword> m_pluginKeywords;
|
||||
std::vector<SPluginVariable> m_pluginVariables;
|
||||
|
||||
std::vector<SP<Desktop::Rule::IRule>> m_keywordRules;
|
||||
|
||||
bool m_isFirstLaunch = true; // For exec-once
|
||||
|
||||
std::vector<SMonitorRule> m_monitorRules;
|
||||
std::vector<SWorkspaceRule> m_workspaceRules;
|
||||
|
||||
bool m_firstExecDispatched = false;
|
||||
bool m_manualCrashInitiated = false;
|
||||
|
||||
std::vector<SFirstExecRequest> m_firstExecRequests; // bool is for if with rules
|
||||
std::vector<std::string> m_finalExecRequests;
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> m_failedPluginConfigValues; // for plugin values of unloaded plugins
|
||||
std::string m_configErrors = "";
|
||||
|
||||
uint32_t m_configValueNumber = 0;
|
||||
|
||||
// internal methods
|
||||
void setDefaultAnimationVars();
|
||||
std::optional<std::string> resetHLConfig();
|
||||
std::optional<std::string> generateConfig(std::string configPath);
|
||||
std::optional<std::string> verifyConfigExists();
|
||||
void reloadRuleConfigs();
|
||||
|
||||
void postConfigReload(const Hyprlang::CParseResult& result);
|
||||
SWorkspaceRule mergeWorkspaceRules(const SWorkspaceRule&, const SWorkspaceRule&);
|
||||
|
||||
void registerConfigVar(const char* name, const Hyprlang::INT& val);
|
||||
void registerConfigVar(const char* name, const Hyprlang::FLOAT& val);
|
||||
void registerConfigVar(const char* name, const Hyprlang::VEC2& val);
|
||||
void registerConfigVar(const char* name, const Hyprlang::STRING& val);
|
||||
void registerConfigVar(const char* name, Hyprlang::CUSTOMTYPE&& val);
|
||||
|
||||
std::unordered_map<SFloatCache, Vector2D> m_mStoredFloatingSizes;
|
||||
|
||||
friend struct SConfigOptionDescription;
|
||||
friend class CMonitorRuleParser;
|
||||
};
|
||||
|
||||
inline UP<CConfigManager> g_pConfigManager;
|
||||
UP<IConfigManager>& mgr();
|
||||
};
|
||||
|
|
@ -1,15 +1,12 @@
|
|||
#include "ConfigValue.hpp"
|
||||
#include "ConfigManager.hpp"
|
||||
#include "../macros.hpp"
|
||||
|
||||
void local__configValuePopulate(void* const** p, const std::string& val) {
|
||||
const auto PVHYPRLANG = g_pConfigManager->getHyprlangConfigValuePtr(val);
|
||||
|
||||
*p = PVHYPRLANG->getDataStaticPtr();
|
||||
const auto BIGP = Config::mgr()->getConfigValue(val);
|
||||
*p = BIGP.dataptr;
|
||||
}
|
||||
|
||||
std::type_index local__configValueTypeIdx(const std::string& val) {
|
||||
const auto PVHYPRLANG = g_pConfigManager->getHyprlangConfigValuePtr(val);
|
||||
const auto ANY = PVHYPRLANG->getValue();
|
||||
return std::type_index(ANY.type());
|
||||
const auto BIGP = Config::mgr()->getConfigValue(val);
|
||||
return std::type_index(*BIGP.type);
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
#pragma once
|
||||
#include "../helpers/memory/Memory.hpp"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <hyprutils/os/FileDescriptor.hpp>
|
||||
|
||||
class CConfigWatcher {
|
||||
public:
|
||||
CConfigWatcher();
|
||||
~CConfigWatcher() = default;
|
||||
|
||||
struct SConfigWatchEvent {
|
||||
std::string file;
|
||||
};
|
||||
|
||||
Hyprutils::OS::CFileDescriptor& getInotifyFD();
|
||||
void setWatchList(const std::vector<std::string>& paths);
|
||||
void setOnChange(const std::function<void(const SConfigWatchEvent&)>& fn);
|
||||
void onInotifyEvent();
|
||||
|
||||
private:
|
||||
struct SInotifyWatch {
|
||||
int wd = -1;
|
||||
std::string file;
|
||||
};
|
||||
|
||||
std::function<void(const SConfigWatchEvent&)> m_watchCallback;
|
||||
std::vector<SInotifyWatch> m_watches;
|
||||
Hyprutils::OS::CFileDescriptor m_inotifyFd;
|
||||
};
|
||||
|
||||
inline UP<CConfigWatcher> g_pConfigWatcher = makeUnique<CConfigWatcher>();
|
||||
2414
src/config/legacy/ConfigManager.cpp
Normal file
2414
src/config/legacy/ConfigManager.cpp
Normal file
File diff suppressed because it is too large
Load diff
153
src/config/legacy/ConfigManager.hpp
Normal file
153
src/config/legacy/ConfigManager.hpp
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
#pragma once
|
||||
|
||||
#include <hyprutils/animation/AnimationConfig.hpp>
|
||||
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <xf86drmMode.h>
|
||||
|
||||
#include "../../desktop/DesktopTypes.hpp"
|
||||
#include "../../desktop/rule/Rule.hpp"
|
||||
#include "../../helpers/memory/Memory.hpp"
|
||||
#include "../../managers/KeybindManager.hpp"
|
||||
|
||||
#include "../ConfigManager.hpp"
|
||||
|
||||
#include <hyprlang.hpp>
|
||||
|
||||
#define HANDLE void*
|
||||
|
||||
namespace Config::Supplementary {
|
||||
struct SConfigOptionDescription;
|
||||
};
|
||||
|
||||
namespace Config::Legacy {
|
||||
|
||||
class CConfigManager;
|
||||
|
||||
struct SPluginKeyword {
|
||||
HANDLE handle = nullptr;
|
||||
std::string name = "";
|
||||
Hyprlang::PCONFIGHANDLERFUNC fn = nullptr;
|
||||
};
|
||||
|
||||
struct SPluginVariable {
|
||||
HANDLE handle = nullptr;
|
||||
std::string name = "";
|
||||
};
|
||||
|
||||
class CConfigManager : public Config::IConfigManager {
|
||||
public:
|
||||
CConfigManager();
|
||||
|
||||
virtual eConfigManagerType type() override;
|
||||
|
||||
virtual void init() override;
|
||||
virtual void reload() override;
|
||||
virtual std::string verify() override;
|
||||
|
||||
virtual int getDeviceInt(const std::string&, const std::string&, const std::string& fallback = "") override;
|
||||
virtual float getDeviceFloat(const std::string&, const std::string&, const std::string& fallback = "") override;
|
||||
virtual Vector2D getDeviceVec(const std::string&, const std::string&, const std::string& fallback = "") override;
|
||||
virtual std::string getDeviceString(const std::string&, const std::string&, const std::string& fallback = "") override;
|
||||
virtual bool deviceConfigExplicitlySet(const std::string&, const std::string&) override;
|
||||
virtual bool deviceConfigExists(const std::string&) override;
|
||||
|
||||
virtual SConfigOptionReply getConfigValue(const std::string&) override;
|
||||
|
||||
virtual std::string getMainConfigPath() override;
|
||||
virtual std::string getErrors() override;
|
||||
virtual std::string getConfigString() override;
|
||||
virtual std::string currentConfigPath() override;
|
||||
virtual const std::vector<std::string>& getConfigPaths() override;
|
||||
|
||||
virtual std::expected<void, std::string> generateDefaultConfig(const std::filesystem::path&, bool safeMode) override;
|
||||
|
||||
virtual void handlePluginLoads() override;
|
||||
virtual bool configVerifPassed() override;
|
||||
|
||||
void addPluginConfigVar(HANDLE handle, const std::string& name, const Hyprlang::CConfigValue& value);
|
||||
void addPluginKeyword(HANDLE handle, const std::string& name, Hyprlang::PCONFIGHANDLERFUNC fun, Hyprlang::SHandlerOptions opts = {});
|
||||
void removePluginConfig(HANDLE handle);
|
||||
|
||||
std::string parseKeyword(const std::string&, const std::string&);
|
||||
|
||||
Hyprlang::CConfigValue* getHyprlangConfigValuePtr(const std::string& name, const std::string& specialCat = "");
|
||||
|
||||
// keywords
|
||||
std::optional<std::string> handleRawExec(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleExec(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleExecOnce(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleExecRawOnce(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleExecShutdown(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleMonitor(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleBind(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleUnbind(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleWorkspaceRules(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleBezier(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleAnimation(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleSource(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleSubmap(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleBindWS(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleEnv(const std::string&, const std::string&);
|
||||
std::optional<std::string> handlePlugin(const std::string&, const std::string&);
|
||||
std::optional<std::string> handlePermission(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleGesture(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleWindowrule(const std::string&, const std::string&);
|
||||
std::optional<std::string> handleLayerrule(const std::string&, const std::string&);
|
||||
|
||||
std::optional<std::string> handleMonitorv2(const std::string& output);
|
||||
Hyprlang::CParseResult handleMonitorv2();
|
||||
std::optional<std::string> addRuleFromConfigKey(const std::string& name);
|
||||
std::optional<std::string> addLayerRuleFromConfigKey(const std::string& name);
|
||||
Hyprlang::CParseResult reloadRules();
|
||||
|
||||
std::string m_configCurrentPath;
|
||||
|
||||
bool m_isLaunchingExecOnce = false; // For exec-once to skip initial ws tracking
|
||||
bool m_lastConfigVerificationWasSuccessful = true;
|
||||
|
||||
private:
|
||||
// internal methods
|
||||
std::optional<std::string> resetHLConfig();
|
||||
std::optional<std::string> verifyConfigExists();
|
||||
void reloadRuleConfigs();
|
||||
|
||||
void postConfigReload(const Hyprlang::CParseResult& result);
|
||||
|
||||
void registerConfigVar(const char* name, const Hyprlang::INT& val);
|
||||
void registerConfigVar(const char* name, const Hyprlang::FLOAT& val);
|
||||
void registerConfigVar(const char* name, const Hyprlang::VEC2& val);
|
||||
void registerConfigVar(const char* name, const Hyprlang::STRING& val);
|
||||
void registerConfigVar(const char* name, Hyprlang::CUSTOMTYPE&& val);
|
||||
Hyprlang::CConfigValue* getConfigValueSafeDevice(const std::string& dev, const std::string& val, const std::string& fallback);
|
||||
|
||||
UP<Hyprlang::CConfig> m_config;
|
||||
|
||||
std::vector<std::string> m_configPaths;
|
||||
std::string m_mainConfigPath;
|
||||
|
||||
SSubmap m_currentSubmap;
|
||||
|
||||
std::vector<std::string> m_declaredPlugins;
|
||||
std::vector<SPluginKeyword> m_pluginKeywords;
|
||||
std::vector<SPluginVariable> m_pluginVariables;
|
||||
|
||||
std::vector<SP<Desktop::Rule::IRule>> m_keywordRules;
|
||||
|
||||
bool m_isFirstLaunch = true; // For exec-once
|
||||
|
||||
bool m_firstExecDispatched = false;
|
||||
bool m_manualCrashInitiated = false;
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> m_failedPluginConfigValues; // for plugin values of unloaded plugins
|
||||
std::string m_configErrors = "";
|
||||
|
||||
uint32_t m_configValueNumber = 0;
|
||||
|
||||
friend struct Config::Supplementary::SConfigOptionDescription;
|
||||
friend class CMonitorRuleParser;
|
||||
};
|
||||
|
||||
WP<CConfigManager> mgr();
|
||||
}
|
||||
15
src/config/shared/Types.hpp
Normal file
15
src/config/shared/Types.hpp
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#include "../../helpers/math/Math.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Config {
|
||||
class ICustomConfigValueData;
|
||||
|
||||
typedef int64_t INTEGER;
|
||||
typedef float FLOAT;
|
||||
typedef Vector2D VEC2;
|
||||
typedef std::string STRING;
|
||||
typedef ICustomConfigValueData* COMPLEX;
|
||||
};
|
||||
79
src/config/shared/animation/AnimationTree.cpp
Normal file
79
src/config/shared/animation/AnimationTree.cpp
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
#include "AnimationTree.hpp"
|
||||
|
||||
using namespace Config;
|
||||
|
||||
UP<CAnimationTreeController>& Config::animationTree() {
|
||||
static UP<CAnimationTreeController> p = makeUnique<CAnimationTreeController>();
|
||||
return p;
|
||||
}
|
||||
|
||||
CAnimationTreeController::CAnimationTreeController() {
|
||||
reset();
|
||||
}
|
||||
|
||||
void CAnimationTreeController::reset() {
|
||||
m_animationTree.createNode("__internal_fadeCTM");
|
||||
m_animationTree.createNode("global");
|
||||
|
||||
// global
|
||||
m_animationTree.createNode("windows", "global");
|
||||
m_animationTree.createNode("layers", "global");
|
||||
m_animationTree.createNode("fade", "global");
|
||||
m_animationTree.createNode("border", "global");
|
||||
m_animationTree.createNode("borderangle", "global");
|
||||
m_animationTree.createNode("workspaces", "global");
|
||||
m_animationTree.createNode("zoomFactor", "global");
|
||||
m_animationTree.createNode("monitorAdded", "global");
|
||||
|
||||
// layer
|
||||
m_animationTree.createNode("layersIn", "layers");
|
||||
m_animationTree.createNode("layersOut", "layers");
|
||||
|
||||
// windows
|
||||
m_animationTree.createNode("windowsIn", "windows");
|
||||
m_animationTree.createNode("windowsOut", "windows");
|
||||
m_animationTree.createNode("windowsMove", "windows");
|
||||
|
||||
// fade
|
||||
m_animationTree.createNode("fadeIn", "fade");
|
||||
m_animationTree.createNode("fadeOut", "fade");
|
||||
m_animationTree.createNode("fadeSwitch", "fade");
|
||||
m_animationTree.createNode("fadeShadow", "fade");
|
||||
m_animationTree.createNode("fadeGlow", "fade");
|
||||
m_animationTree.createNode("fadeDim", "fade");
|
||||
m_animationTree.createNode("fadeLayers", "fade");
|
||||
m_animationTree.createNode("fadeLayersIn", "fadeLayers");
|
||||
m_animationTree.createNode("fadeLayersOut", "fadeLayers");
|
||||
m_animationTree.createNode("fadePopups", "fade");
|
||||
m_animationTree.createNode("fadePopupsIn", "fadePopups");
|
||||
m_animationTree.createNode("fadePopupsOut", "fadePopups");
|
||||
m_animationTree.createNode("fadeDpms", "fade");
|
||||
|
||||
// workspaces
|
||||
m_animationTree.createNode("workspacesIn", "workspaces");
|
||||
m_animationTree.createNode("workspacesOut", "workspaces");
|
||||
m_animationTree.createNode("specialWorkspace", "workspaces");
|
||||
m_animationTree.createNode("specialWorkspaceIn", "specialWorkspace");
|
||||
m_animationTree.createNode("specialWorkspaceOut", "specialWorkspace");
|
||||
|
||||
// init the root nodes
|
||||
m_animationTree.setConfigForNode("global", 1, 8.f, "default");
|
||||
m_animationTree.setConfigForNode("__internal_fadeCTM", 1, 5.f, "linear");
|
||||
m_animationTree.setConfigForNode("borderangle", 0, 1, "default");
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, SP<Hyprutils::Animation::SAnimationPropertyConfig>>& CAnimationTreeController::getAnimationConfig() {
|
||||
return m_animationTree.getFullConfig();
|
||||
}
|
||||
|
||||
SP<Hyprutils::Animation::SAnimationPropertyConfig> CAnimationTreeController::getAnimationPropertyConfig(const std::string& name) {
|
||||
return m_animationTree.getConfig(name);
|
||||
}
|
||||
|
||||
void CAnimationTreeController::setConfigForNode(const std::string& name, bool enabled, float speed, const std::string& bezier, const std::string& style) {
|
||||
m_animationTree.setConfigForNode(name, enabled, speed, bezier, style);
|
||||
}
|
||||
|
||||
bool CAnimationTreeController::nodeExists(const std::string& name) {
|
||||
return m_animationTree.nodeExists(name);
|
||||
}
|
||||
29
src/config/shared/animation/AnimationTree.hpp
Normal file
29
src/config/shared/animation/AnimationTree.hpp
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "../../../helpers/memory/Memory.hpp"
|
||||
|
||||
#include <hyprutils/animation/AnimationConfig.hpp>
|
||||
|
||||
namespace Config {
|
||||
class CAnimationTreeController {
|
||||
public:
|
||||
CAnimationTreeController();
|
||||
|
||||
const std::unordered_map<std::string, SP<Hyprutils::Animation::SAnimationPropertyConfig>>& getAnimationConfig();
|
||||
SP<Hyprutils::Animation::SAnimationPropertyConfig> getAnimationPropertyConfig(const std::string&);
|
||||
|
||||
//
|
||||
void reset();
|
||||
|
||||
//
|
||||
void setConfigForNode(const std::string& name, bool enabled, float speed, const std::string& bezier, const std::string& style = "");
|
||||
bool nodeExists(const std::string& name);
|
||||
|
||||
private:
|
||||
Hyprutils::Animation::CAnimationConfigTree m_animationTree;
|
||||
};
|
||||
|
||||
UP<CAnimationTreeController>& animationTree();
|
||||
};
|
||||
22
src/config/shared/complex/ComplexDataType.hpp
Normal file
22
src/config/shared/complex/ComplexDataType.hpp
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace Config {
|
||||
enum eConfigValueDataTypes : int8_t {
|
||||
CVD_TYPE_INVALID = -1,
|
||||
CVD_TYPE_GRADIENT = 0,
|
||||
CVD_TYPE_CSS_VALUE = 1,
|
||||
CVD_TYPE_FONT_WEIGHT = 2,
|
||||
};
|
||||
|
||||
class IComplexConfigValue {
|
||||
public:
|
||||
virtual ~IComplexConfigValue() = default;
|
||||
|
||||
virtual eConfigValueDataTypes getDataType() = 0;
|
||||
|
||||
virtual std::string toString() = 0;
|
||||
};
|
||||
}
|
||||
176
src/config/shared/complex/ComplexDataTypes.hpp
Normal file
176
src/config/shared/complex/ComplexDataTypes.hpp
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
#pragma once
|
||||
#include "ComplexDataType.hpp"
|
||||
#include "../../../helpers/Color.hpp"
|
||||
|
||||
#include <hyprutils/string/VarList2.hpp>
|
||||
|
||||
#include <vector>
|
||||
#include <format>
|
||||
#include <map>
|
||||
#include <algorithm>
|
||||
|
||||
namespace Config {
|
||||
|
||||
class CGradientValueData : public IComplexConfigValue {
|
||||
public:
|
||||
CGradientValueData() = default;
|
||||
CGradientValueData(CHyprColor col) {
|
||||
m_colors.push_back(col);
|
||||
updateColorsOk();
|
||||
};
|
||||
CGradientValueData(std::vector<CHyprColor>&& cols, float ang) : m_colors(std::move(cols)), m_angle(ang) {
|
||||
updateColorsOk();
|
||||
};
|
||||
virtual ~CGradientValueData() = default;
|
||||
|
||||
virtual eConfigValueDataTypes getDataType() {
|
||||
return CVD_TYPE_GRADIENT;
|
||||
}
|
||||
|
||||
void reset(CHyprColor col) {
|
||||
m_colors.clear();
|
||||
m_colors.emplace_back(col);
|
||||
m_angle = 0;
|
||||
updateColorsOk();
|
||||
}
|
||||
|
||||
void updateColorsOk() {
|
||||
m_colorsOkLabA.clear();
|
||||
for (auto& c : m_colors) {
|
||||
const auto OKLAB = c.asOkLab();
|
||||
m_colorsOkLabA.emplace_back(OKLAB.l);
|
||||
m_colorsOkLabA.emplace_back(OKLAB.a);
|
||||
m_colorsOkLabA.emplace_back(OKLAB.b);
|
||||
m_colorsOkLabA.emplace_back(c.a);
|
||||
}
|
||||
}
|
||||
|
||||
/* Vector containing the colors */
|
||||
std::vector<CHyprColor> m_colors;
|
||||
|
||||
/* Vector containing pure colors for shoving into opengl */
|
||||
std::vector<float> m_colorsOkLabA;
|
||||
|
||||
/* Float corresponding to the angle (rad) */
|
||||
float m_angle = 0;
|
||||
|
||||
//
|
||||
bool operator==(const CGradientValueData& other) const {
|
||||
if (other.m_colors.size() != m_colors.size() || m_angle != other.m_angle)
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < m_colors.size(); ++i)
|
||||
if (m_colors[i] != other.m_colors[i])
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual std::string toString() {
|
||||
std::string result;
|
||||
for (auto& c : m_colors) {
|
||||
result += std::format("{:x} ", c.getAsHex());
|
||||
}
|
||||
|
||||
result += std::format("{}deg", sc<int>(m_angle * 180.0 / M_PI));
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
class CCssGapData : public IComplexConfigValue {
|
||||
public:
|
||||
CCssGapData() : m_top(0), m_right(0), m_bottom(0), m_left(0) {};
|
||||
CCssGapData(int64_t global) : m_top(global), m_right(global), m_bottom(global), m_left(global) {};
|
||||
CCssGapData(int64_t vertical, int64_t horizontal) : m_top(vertical), m_right(horizontal), m_bottom(vertical), m_left(horizontal) {};
|
||||
CCssGapData(int64_t top, int64_t horizontal, int64_t bottom) : m_top(top), m_right(horizontal), m_bottom(bottom), m_left(horizontal) {};
|
||||
CCssGapData(int64_t top, int64_t right, int64_t bottom, int64_t left) : m_top(top), m_right(right), m_bottom(bottom), m_left(left) {};
|
||||
|
||||
/* Css like directions */
|
||||
int64_t m_top;
|
||||
int64_t m_right;
|
||||
int64_t m_bottom;
|
||||
int64_t m_left;
|
||||
|
||||
void parseGapData(Hyprutils::String::CVarList2 varlist) {
|
||||
const auto toInt = [](std::string_view string) -> int { return std::stoi(std::string(string)); };
|
||||
|
||||
switch (varlist.size()) {
|
||||
case 1: {
|
||||
*this = CCssGapData(toInt(varlist[0]));
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
*this = CCssGapData(toInt(varlist[0]), toInt(varlist[1]));
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
*this = CCssGapData(toInt(varlist[0]), toInt(varlist[1]), toInt(varlist[2]));
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
*this = CCssGapData(toInt(varlist[0]), toInt(varlist[1]), toInt(varlist[2]), toInt(varlist[3]));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Log::logger->log(Log::WARN, "Too many arguments provided for gaps.");
|
||||
*this = CCssGapData(toInt(varlist[0]), toInt(varlist[1]), toInt(varlist[2]), toInt(varlist[3]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void reset(int64_t global) {
|
||||
m_top = global;
|
||||
m_right = global;
|
||||
m_bottom = global;
|
||||
m_left = global;
|
||||
}
|
||||
|
||||
virtual eConfigValueDataTypes getDataType() {
|
||||
return CVD_TYPE_CSS_VALUE;
|
||||
}
|
||||
|
||||
virtual std::string toString() {
|
||||
return std::format("{} {} {} {}", m_top, m_right, m_bottom, m_left);
|
||||
}
|
||||
};
|
||||
|
||||
class CFontWeightConfigValueData : public IComplexConfigValue {
|
||||
public:
|
||||
CFontWeightConfigValueData() = default;
|
||||
CFontWeightConfigValueData(const char* weight) {
|
||||
parseWeight(weight);
|
||||
}
|
||||
|
||||
int64_t m_value = 400; // default to normal weight
|
||||
|
||||
virtual eConfigValueDataTypes getDataType() {
|
||||
return CVD_TYPE_FONT_WEIGHT;
|
||||
}
|
||||
|
||||
virtual std::string toString() {
|
||||
return std::format("{}", m_value);
|
||||
}
|
||||
|
||||
void parseWeight(const std::string& strWeight) {
|
||||
auto lcWeight{strWeight};
|
||||
std::ranges::transform(strWeight, lcWeight.begin(), ::tolower);
|
||||
|
||||
// values taken from Pango weight enums
|
||||
const auto WEIGHTS = std::map<std::string, int>{
|
||||
{"thin", 100}, {"ultralight", 200}, {"light", 300}, {"semilight", 350}, {"book", 380}, {"normal", 400},
|
||||
{"medium", 500}, {"semibold", 600}, {"bold", 700}, {"ultrabold", 800}, {"heavy", 900}, {"ultraheavy", 1000},
|
||||
};
|
||||
|
||||
auto weight = WEIGHTS.find(lcWeight);
|
||||
if (weight != WEIGHTS.end())
|
||||
m_value = weight->second;
|
||||
else {
|
||||
int w_i = std::stoi(strWeight);
|
||||
if (w_i < 100 || w_i > 1000)
|
||||
m_value = 400;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -3,24 +3,32 @@
|
|||
#include <linux/limits.h>
|
||||
#endif
|
||||
#include <sys/inotify.h>
|
||||
#include "../debug/Log.hpp"
|
||||
#include "../../../debug/log/Logger.hpp"
|
||||
#include "../../ConfigValue.hpp"
|
||||
#include "../../ConfigManager.hpp"
|
||||
#include <ranges>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <filesystem>
|
||||
|
||||
using namespace Hyprutils::OS;
|
||||
using namespace Config;
|
||||
|
||||
UP<CConfigWatcher>& Config::watcher() {
|
||||
static UP<CConfigWatcher> p = makeUnique<CConfigWatcher>();
|
||||
return p;
|
||||
}
|
||||
|
||||
CConfigWatcher::CConfigWatcher() : m_inotifyFd(inotify_init()) {
|
||||
if (!m_inotifyFd.isValid()) {
|
||||
Debug::log(ERR, "CConfigWatcher couldn't open an inotify node. Config will not be automatically reloaded");
|
||||
Log::logger->log(Log::ERR, "CConfigWatcher couldn't open an inotify node. Config will not be automatically reloaded");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: make CFileDescriptor take F_GETFL, F_SETFL
|
||||
const int FLAGS = fcntl(m_inotifyFd.get(), F_GETFL, 0);
|
||||
if (fcntl(m_inotifyFd.get(), F_SETFL, FLAGS | O_NONBLOCK) < 0) {
|
||||
Debug::log(ERR, "CConfigWatcher couldn't non-block inotify node. Config will not be automatically reloaded");
|
||||
Log::logger->log(Log::ERR, "CConfigWatcher couldn't non-block inotify node. Config will not be automatically reloaded");
|
||||
m_inotifyFd.reset();
|
||||
return;
|
||||
}
|
||||
|
|
@ -30,6 +38,11 @@ CFileDescriptor& CConfigWatcher::getInotifyFD() {
|
|||
return m_inotifyFd;
|
||||
}
|
||||
|
||||
void CConfigWatcher::update() {
|
||||
static const auto PDISABLEAUTORELOAD = CConfigValue<Hyprlang::INT>("misc:disable_autoreload");
|
||||
setWatchList(*PDISABLEAUTORELOAD ? std::vector<std::string>{} : Config::mgr()->getConfigPaths());
|
||||
}
|
||||
|
||||
void CConfigWatcher::setWatchList(const std::vector<std::string>& paths) {
|
||||
|
||||
// we clear all watches first, because whichever fired is now invalid
|
||||
|
|
@ -78,19 +91,19 @@ void CConfigWatcher::onInotifyEvent() {
|
|||
const auto* ev = rc<const inotify_event*>(buffer.data() + offset);
|
||||
|
||||
if (offset + sizeof(inotify_event) > sc<size_t>(bytesRead)) {
|
||||
Debug::log(ERR, "CConfigWatcher: malformed inotify event, truncated header");
|
||||
Log::logger->log(Log::ERR, "CConfigWatcher: malformed inotify event, truncated header");
|
||||
break;
|
||||
}
|
||||
|
||||
if (offset + sizeof(inotify_event) + ev->len > sc<size_t>(bytesRead)) {
|
||||
Debug::log(ERR, "CConfigWatcher: malformed inotify event, truncated name field");
|
||||
Log::logger->log(Log::ERR, "CConfigWatcher: malformed inotify event, truncated name field");
|
||||
break;
|
||||
}
|
||||
|
||||
const auto WD = std::ranges::find_if(m_watches, [wd = ev->wd](const auto& e) { return e.wd == wd; });
|
||||
|
||||
if (WD == m_watches.end())
|
||||
Debug::log(ERR, "CConfigWatcher: got an event for wd {} which we don't have?!", ev->wd);
|
||||
Log::logger->log(Log::ERR, "CConfigWatcher: got an event for wd {} which we don't have?!", ev->wd);
|
||||
else
|
||||
m_watchCallback(SConfigWatchEvent{
|
||||
.file = WD->file,
|
||||
37
src/config/shared/inotify/ConfigWatcher.hpp
Normal file
37
src/config/shared/inotify/ConfigWatcher.hpp
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#pragma once
|
||||
#include "../../../helpers/memory/Memory.hpp"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <hyprutils/os/FileDescriptor.hpp>
|
||||
|
||||
namespace Config {
|
||||
class CConfigWatcher {
|
||||
public:
|
||||
CConfigWatcher();
|
||||
~CConfigWatcher() = default;
|
||||
|
||||
struct SConfigWatchEvent {
|
||||
std::string file;
|
||||
};
|
||||
|
||||
void update();
|
||||
|
||||
Hyprutils::OS::CFileDescriptor& getInotifyFD();
|
||||
void setWatchList(const std::vector<std::string>& paths);
|
||||
void setOnChange(const std::function<void(const SConfigWatchEvent&)>& fn);
|
||||
void onInotifyEvent();
|
||||
|
||||
private:
|
||||
struct SInotifyWatch {
|
||||
int wd = -1;
|
||||
std::string file;
|
||||
};
|
||||
|
||||
std::function<void(const SConfigWatchEvent&)> m_watchCallback;
|
||||
std::vector<SInotifyWatch> m_watches;
|
||||
Hyprutils::OS::CFileDescriptor m_inotifyFd;
|
||||
};
|
||||
|
||||
UP<CConfigWatcher>& watcher();
|
||||
}
|
||||
62
src/config/shared/monitor/MonitorRule.hpp
Normal file
62
src/config/shared/monitor/MonitorRule.hpp
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <xf86drmMode.h>
|
||||
|
||||
#include "../../../helpers/math/Math.hpp"
|
||||
#include "../../../helpers/cm/ColorManagement.hpp"
|
||||
#include "../../../helpers/CMType.hpp"
|
||||
#include "../../../helpers/TransferFunction.hpp"
|
||||
#include "../../../desktop/reserved/ReservedArea.hpp"
|
||||
|
||||
namespace Config {
|
||||
// Enum for the different types of auto directions, e.g. auto-left, auto-up.
|
||||
enum eAutoDirs : uint8_t {
|
||||
DIR_AUTO_NONE = 0, /* None will be treated as right. */
|
||||
DIR_AUTO_UP,
|
||||
DIR_AUTO_DOWN,
|
||||
DIR_AUTO_LEFT,
|
||||
DIR_AUTO_RIGHT,
|
||||
DIR_AUTO_CENTER_UP,
|
||||
DIR_AUTO_CENTER_DOWN,
|
||||
DIR_AUTO_CENTER_LEFT,
|
||||
DIR_AUTO_CENTER_RIGHT
|
||||
};
|
||||
|
||||
class CMonitorRule {
|
||||
public:
|
||||
CMonitorRule() = default;
|
||||
~CMonitorRule() = default;
|
||||
|
||||
eAutoDirs m_autoDir = DIR_AUTO_NONE;
|
||||
std::string m_name = "";
|
||||
Vector2D m_resolution = Vector2D(1280, 720);
|
||||
Vector2D m_offset = Vector2D(0, 0);
|
||||
float m_scale = 1;
|
||||
float m_refreshRate = 60; // Hz
|
||||
bool m_disabled = false;
|
||||
wl_output_transform m_transform = WL_OUTPUT_TRANSFORM_NORMAL;
|
||||
std::string m_mirrorOf = "";
|
||||
bool m_enable10bit = false;
|
||||
NCMType::eCMType m_cmType = NCMType::CM_SRGB;
|
||||
NTransferFunction::eTF m_sdrEotf = NTransferFunction::TF_DEFAULT;
|
||||
float m_sdrSaturation = 1.F; // SDR -> HDR
|
||||
float m_sdrBrightness = 1.F; // SDR -> HDR
|
||||
Desktop::CReservedArea m_reservedArea;
|
||||
std::string m_iccFile;
|
||||
|
||||
int m_supportsWideColor = 0; // 0 - auto, 1 - force enable, -1 - force disable
|
||||
int m_supportsHDR = 0; // 0 - auto, 1 - force enable, -1 - force disable
|
||||
float m_sdrMinLuminance = 0.2F; // SDR -> HDR
|
||||
int m_sdrMaxLuminance = 80; // SDR -> HDR
|
||||
|
||||
// Incorrect values will result in reduced luminance range or incorrect tonemapping. Shouldn't damage the HW. Use with care in case of a faulty monitor firmware.
|
||||
float m_minLuminance = -1.F; // >= 0 overrides EDID
|
||||
int m_maxLuminance = -1; // >= 0 overrides EDID
|
||||
int m_maxAvgLuminance = -1; // >= 0 overrides EDID
|
||||
|
||||
drmModeModeInfo m_drmMode = {};
|
||||
std::optional<int> m_vrr;
|
||||
};
|
||||
};
|
||||
252
src/config/shared/monitor/MonitorRuleManager.cpp
Normal file
252
src/config/shared/monitor/MonitorRuleManager.cpp
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
#include "MonitorRuleManager.hpp"
|
||||
|
||||
#include "../../../debug/log/Logger.hpp"
|
||||
#include "../../../protocols/OutputManagement.hpp"
|
||||
#include "../../../helpers/Monitor.hpp"
|
||||
#include "../../../Compositor.hpp"
|
||||
#include "../../../render/Renderer.hpp"
|
||||
#include "../../../event/EventBus.hpp"
|
||||
#include "../../../managers/eventLoop/EventLoopManager.hpp"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
using namespace Config;
|
||||
|
||||
UP<CMonitorRuleManager>& Config::monitorRuleMgr() {
|
||||
static UP<CMonitorRuleManager> p = makeUnique<CMonitorRuleManager>();
|
||||
return p;
|
||||
}
|
||||
|
||||
CMonitorRuleManager::CMonitorRuleManager() {
|
||||
m_listeners.preChecksRender = Event::bus()->m_events.render.preChecks.listen([this](PHLMONITOR m) {
|
||||
if (m_reloadScheduled)
|
||||
performMonitorReload();
|
||||
|
||||
m_reloadScheduled = false;
|
||||
});
|
||||
}
|
||||
|
||||
void CMonitorRuleManager::clear() {
|
||||
m_rules.clear();
|
||||
}
|
||||
|
||||
void CMonitorRuleManager::add(CMonitorRule&& x) {
|
||||
std::erase_if(m_rules, [&x](const auto& e) { return e.m_name == x.m_name; });
|
||||
m_rules.emplace_back(std::move(x));
|
||||
}
|
||||
|
||||
CMonitorRule CMonitorRuleManager::get(const PHLMONITOR PMONITOR) {
|
||||
auto applyWlrOutputConfig = [PMONITOR](CMonitorRule rule) -> CMonitorRule {
|
||||
const auto CONFIG = PROTO::outputManagement->getOutputStateFor(PMONITOR);
|
||||
|
||||
if (!CONFIG)
|
||||
return rule;
|
||||
|
||||
Log::logger->log(Log::DEBUG, "CConfigManager::getMonitorRuleFor: found a wlr_output_manager override for {}", PMONITOR->m_name);
|
||||
|
||||
Log::logger->log(Log::DEBUG, " > overriding enabled: {} -> {}", !rule.m_disabled, !CONFIG->enabled);
|
||||
rule.m_disabled = !CONFIG->enabled;
|
||||
|
||||
if ((CONFIG->committedProperties & OUTPUT_HEAD_COMMITTED_MODE) || (CONFIG->committedProperties & OUTPUT_HEAD_COMMITTED_CUSTOM_MODE)) {
|
||||
Log::logger->log(Log::DEBUG, " > overriding mode: {:.0f}x{:.0f}@{:.2f}Hz -> {:.0f}x{:.0f}@{:.2f}Hz", rule.m_resolution.x, rule.m_resolution.y, rule.m_refreshRate,
|
||||
CONFIG->resolution.x, CONFIG->resolution.y, CONFIG->refresh / 1000.F);
|
||||
rule.m_resolution = CONFIG->resolution;
|
||||
rule.m_refreshRate = CONFIG->refresh / 1000.F;
|
||||
}
|
||||
|
||||
if (CONFIG->committedProperties & OUTPUT_HEAD_COMMITTED_POSITION) {
|
||||
Log::logger->log(Log::DEBUG, " > overriding offset: {:.0f}, {:.0f} -> {:.0f}, {:.0f}", rule.m_offset.x, rule.m_offset.y, CONFIG->position.x, CONFIG->position.y);
|
||||
rule.m_offset = CONFIG->position;
|
||||
}
|
||||
|
||||
if (CONFIG->committedProperties & OUTPUT_HEAD_COMMITTED_TRANSFORM) {
|
||||
Log::logger->log(Log::DEBUG, " > overriding transform: {} -> {}", sc<uint8_t>(rule.m_transform), sc<uint8_t>(CONFIG->transform));
|
||||
rule.m_transform = CONFIG->transform;
|
||||
}
|
||||
|
||||
if (CONFIG->committedProperties & OUTPUT_HEAD_COMMITTED_SCALE) {
|
||||
Log::logger->log(Log::DEBUG, " > overriding scale: {} -> {}", sc<uint8_t>(rule.m_scale), sc<uint8_t>(CONFIG->scale));
|
||||
rule.m_scale = CONFIG->scale;
|
||||
}
|
||||
|
||||
if (CONFIG->committedProperties & OUTPUT_HEAD_COMMITTED_ADAPTIVE_SYNC) {
|
||||
Log::logger->log(Log::DEBUG, " > overriding vrr: {} -> {}", rule.m_vrr.value_or(0), CONFIG->adaptiveSync);
|
||||
rule.m_vrr = sc<int>(CONFIG->adaptiveSync);
|
||||
}
|
||||
|
||||
return rule;
|
||||
};
|
||||
|
||||
for (auto const& r : m_rules | std::views::reverse) {
|
||||
if (PMONITOR->matchesStaticSelector(r.m_name))
|
||||
return applyWlrOutputConfig(r);
|
||||
}
|
||||
|
||||
Log::logger->log(Log::WARN, "No rule found for {}, trying to use the first.", PMONITOR->m_name);
|
||||
|
||||
for (auto const& r : m_rules) {
|
||||
if (r.m_name.empty())
|
||||
return applyWlrOutputConfig(r);
|
||||
}
|
||||
|
||||
Log::logger->log(Log::WARN, "No rules configured. Using the default hardcoded one.");
|
||||
|
||||
CMonitorRule fallbackRule;
|
||||
fallbackRule.m_autoDir = eAutoDirs::DIR_AUTO_RIGHT;
|
||||
fallbackRule.m_name = "";
|
||||
fallbackRule.m_resolution = Vector2D{};
|
||||
fallbackRule.m_offset = Vector2D{-INT32_MAX, -INT32_MAX};
|
||||
fallbackRule.m_scale = -1;
|
||||
return applyWlrOutputConfig(fallbackRule);
|
||||
}
|
||||
|
||||
const std::vector<CMonitorRule>& CMonitorRuleManager::all() {
|
||||
return m_rules;
|
||||
}
|
||||
|
||||
std::vector<CMonitorRule>& CMonitorRuleManager::allMut() {
|
||||
return m_rules;
|
||||
}
|
||||
|
||||
void CMonitorRuleManager::scheduleReload() {
|
||||
if (m_reloadScheduled)
|
||||
return;
|
||||
|
||||
m_reloadScheduled = true;
|
||||
}
|
||||
|
||||
void CMonitorRuleManager::performMonitorReload() {
|
||||
bool overAgain = false;
|
||||
|
||||
for (auto const& m : g_pCompositor->m_realMonitors) {
|
||||
if (!m->m_output || m->m_isUnsafeFallback)
|
||||
continue;
|
||||
|
||||
auto rule = get(m);
|
||||
|
||||
if (!m->applyMonitorRule(Config::CMonitorRule{rule})) {
|
||||
overAgain = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// ensure mirror
|
||||
m->setMirror(rule.m_mirrorOf);
|
||||
|
||||
g_pHyprRenderer->arrangeLayersForMonitor(m->m_id);
|
||||
}
|
||||
|
||||
if (overAgain)
|
||||
performMonitorReload();
|
||||
|
||||
m_reloadScheduled = false;
|
||||
|
||||
Event::bus()->m_events.monitor.layoutChanged.emit();
|
||||
}
|
||||
|
||||
void CMonitorRuleManager::ensureMonitorStatus() {
|
||||
for (auto const& rm : g_pCompositor->m_realMonitors) {
|
||||
if (!rm->m_output || rm->m_isUnsafeFallback)
|
||||
continue;
|
||||
|
||||
auto rule = get(rm);
|
||||
|
||||
if (rule.m_disabled == rm->m_enabled)
|
||||
rm->applyMonitorRule(std::move(rule));
|
||||
}
|
||||
}
|
||||
|
||||
void CMonitorRuleManager::ensureVRR(PHLMONITOR pMonitor) {
|
||||
static auto PVRR = CConfigValue<Hyprlang::INT>("misc:vrr");
|
||||
|
||||
static auto ensureVRRForDisplay = [&](PHLMONITOR m) -> void {
|
||||
if (!m->m_output || m->m_createdByUser)
|
||||
return;
|
||||
|
||||
const auto USEVRR = m->m_activeMonitorRule.m_vrr.has_value() ? m->m_activeMonitorRule.m_vrr.value() : *PVRR;
|
||||
|
||||
if (USEVRR == 0) {
|
||||
if (m->m_vrrActive) {
|
||||
m->m_output->state->resetExplicitFences();
|
||||
m->m_output->state->setAdaptiveSync(false);
|
||||
|
||||
if (!m->m_state.commit())
|
||||
Log::logger->log(Log::ERR, "Couldn't commit output {} in ensureVRR -> false", m->m_output->name);
|
||||
}
|
||||
m->m_vrrActive = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const auto PWORKSPACE = m->m_activeWorkspace;
|
||||
|
||||
if (USEVRR == 1) {
|
||||
bool wantVRR = true;
|
||||
if (PWORKSPACE && PWORKSPACE->m_hasFullscreenWindow && (PWORKSPACE->m_fullscreenMode & FSMODE_FULLSCREEN))
|
||||
wantVRR = !PWORKSPACE->getFullscreenWindow()->m_ruleApplicator->noVRR().valueOrDefault();
|
||||
|
||||
if (wantVRR) {
|
||||
if (!m->m_vrrActive) {
|
||||
m->m_output->state->resetExplicitFences();
|
||||
m->m_output->state->setAdaptiveSync(true);
|
||||
|
||||
if (!m->m_state.test()) {
|
||||
Log::logger->log(Log::DEBUG, "Pending output {} does not accept VRR.", m->m_output->name);
|
||||
m->m_output->state->setAdaptiveSync(false);
|
||||
}
|
||||
|
||||
if (!m->m_state.commit())
|
||||
Log::logger->log(Log::ERR, "Couldn't commit output {} in ensureVRR -> true", m->m_output->name);
|
||||
}
|
||||
m->m_vrrActive = true;
|
||||
} else {
|
||||
if (m->m_vrrActive) {
|
||||
m->m_output->state->resetExplicitFences();
|
||||
m->m_output->state->setAdaptiveSync(false);
|
||||
|
||||
if (!m->m_state.commit())
|
||||
Log::logger->log(Log::ERR, "Couldn't commit output {} in ensureVRR -> false", m->m_output->name);
|
||||
}
|
||||
m->m_vrrActive = false;
|
||||
}
|
||||
return;
|
||||
} else if (USEVRR == 2 || USEVRR == 3) {
|
||||
if (!PWORKSPACE)
|
||||
return; // ???
|
||||
|
||||
bool wantVRR = PWORKSPACE->m_hasFullscreenWindow && (PWORKSPACE->m_fullscreenMode & FSMODE_FULLSCREEN);
|
||||
if (wantVRR && PWORKSPACE->getFullscreenWindow()->m_ruleApplicator->noVRR().valueOrDefault())
|
||||
wantVRR = false;
|
||||
|
||||
if (wantVRR && USEVRR == 3) {
|
||||
const auto contentType = PWORKSPACE->getFullscreenWindow()->getContentType();
|
||||
wantVRR = contentType == NContentType::CONTENT_TYPE_GAME || contentType == NContentType::CONTENT_TYPE_VIDEO;
|
||||
}
|
||||
|
||||
if (wantVRR) {
|
||||
/* fullscreen */
|
||||
m->m_vrrActive = true;
|
||||
|
||||
if (!m->m_output->state->state().adaptiveSync) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
m->m_vrrActive = false;
|
||||
|
||||
m->m_output->state->setAdaptiveSync(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (pMonitor) {
|
||||
ensureVRRForDisplay(pMonitor);
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto const& m : g_pCompositor->m_monitors) {
|
||||
ensureVRRForDisplay(m);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue