Merge branch 'main' into movegroupfocus

This commit is contained in:
speckmaier 2024-01-17 09:09:02 +01:00
commit 31f08aab4e
171 changed files with 9560 additions and 4241 deletions

1
.clang-format-ignore Normal file
View file

@ -0,0 +1 @@
subprojects/**/*

View file

@ -9,12 +9,23 @@ body:
---
- type: input
- type: textarea
id: ver
attributes:
label: Hyprland Version
description: "Paste here the output of `hyprctl version`."
placeholder: Hyprland, built from branch main at commit...
description: "Paste here the output of `hyprctl version`. For hyprland after 0.34.0, paste `hyprctl systeminfo` instead."
value: "<details>
<summary>System/Version info</summary>
```sh
<Paste the output of the command here>
```
</details>"
validations:
required: true

75
.github/actions/setup_base/action.yml vendored Normal file
View file

@ -0,0 +1,75 @@
name: "Setup base"
inputs:
INSTALL_XORG_PKGS:
description: 'Install xorg dependencies'
required: false
default: false
runs:
using: "composite"
steps:
- name: Get required pacman pkgs
shell: bash
run: |
sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf
pacman --noconfirm --noprogressbar -Syyu
pacman --noconfirm --noprogressbar -Sy \
base-devel \
cairo \
clang \
cmake \
git \
glm \
glslang \
go \
jq \
libc++ \
libdisplay-info \
libdrm \
libepoxy \
libfontenc \
libglvnd \
libinput \
libliftoff \
libxcvt \
libxfont2 \
libxkbcommon \
libxkbfile \
lld \
meson \
ninja \
pango \
pixman \
pkgconf \
scdoc \
seatd \
systemd \
tomlplusplus \
wayland \
wayland-protocols \
xcb-util-errors \
xcb-util-renderutil \
xcb-util-wm
- name: Get Xorg pacman pkgs
shell: bash
if: inputs.INSTALL_XORG_PKGS == 'true'
run: |
pacman --noconfirm --noprogressbar -Sy \
xorg-fonts-encodings \
xorg-server-common \
xorg-setxkbmap \
xorg-xkbcomp \
xorg-xwayland
- name: Checkout Hyprland
uses: actions/checkout@v4
with:
submodules: recursive
# Fix an issue with actions/checkout where the checkout repo is not mark as safe
- name: Mark directory as safe for git
shell: bash
run: |
git config --global --add safe.directory /__w/Hyprland/Hyprland

View file

@ -8,29 +8,20 @@ jobs:
container:
image: archlinux
steps:
- name: Get required pacman pkgs
run: |
sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf
pacman --noconfirm --noprogressbar -Syyu
pacman --noconfirm --noprogressbar -Sy glslang libepoxy libfontenc libxcvt libxfont2 libxkbfile vulkan-headers vulkan-validation-layers xcb-util-errors xcb-util-renderutil xcb-util-wm xorg-fonts-encodings xorg-server-common xorg-setxkbmap xorg-xkbcomp xorg-xwayland git cmake go clang lld libc++ pkgconf meson ninja wayland wayland-protocols libinput libxkbcommon pixman glm libdrm libglvnd cairo pango systemd scdoc base-devel seatd python libliftoff
- name: Set up user
run: |
useradd -m githubuser
echo -e "root ALL=(ALL:ALL) ALL\ngithubuser ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers
- name: Install libdisplay-info from the AUR
run: |
su githubuser -c "cd ~ && git clone https://aur.archlinux.org/libdisplay-info.git && cd ./libdisplay-info && makepkg -si --skippgpcheck --noconfirm --noprogressbar"
- name: Fix permissions for git
run: |
git config --global --add safe.directory /__w/Hyprland/Hyprland
- name: Checkout Hyprland
uses: actions/checkout@v3
- name: Checkout repository actions
uses: actions/checkout@v4
with:
submodules: recursive
sparse-checkout: .github/actions
- name: Setup base
uses: ./.github/actions/setup_base
with:
INSTALL_XORG_PKGS: true
- name: Build Hyprland
run: |
git submodule sync --recursive && git submodule update --init --force --recursive
make all
- name: Compress and package artifacts
run: |
mkdir x86_64-pc-linux-gnu
@ -39,12 +30,13 @@ jobs:
mkdir hyprland/assets
cp ./LICENSE hyprland/
cp build/Hyprland hyprland/
cp hyprctl/hyprctl hyprland/
cp subprojects/wlroots/build/libwlroots.so.12032 hyprland/
cp build/hyprctl/hyprctl hyprland/
cp subprojects/wlroots/build/libwlroots.so.13032 hyprland/
cp build/Hyprland hyprland/
cp -r example/ hyprland/
cp -r assets/ hyprland/
tar -cvf Hyprland.tar.xz hyprland
- name: Release
uses: actions/upload-artifact@v3
with:
@ -57,28 +49,19 @@ jobs:
container:
image: archlinux
steps:
- name: Download dependencies
run: |
sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf
pacman --noconfirm --noprogressbar -Syyu
pacman --noconfirm --noprogressbar -Sy glslang libepoxy libfontenc libxcvt libxfont2 libxkbfile vulkan-headers vulkan-validation-layers git go clang lld libc++ pkgconf meson ninja wayland wayland-protocols libinput libxkbcommon pixman glm libdrm libglvnd cairo pango systemd scdoc base-devel seatd cmake jq python libliftoff
- name: Set up user
run: |
useradd -m githubuser
echo -e "root ALL=(ALL:ALL) ALL\ngithubuser ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers
- name: Install libdisplay-info from the AUR
run: |
su githubuser -c "cd ~ && git clone https://aur.archlinux.org/libdisplay-info.git && cd ./libdisplay-info && makepkg -si --skippgpcheck --noconfirm --noprogressbar"
- name: Checkout Hyprland
uses: actions/checkout@v3
- name: Checkout repository actions
uses: actions/checkout@v4
with:
submodules: true
sparse-checkout: .github/actions
- name: Setup base
uses: ./.github/actions/setup_base
- name: Configure
run: |
meson obj-x86_64-pc-linux-gnu \
-Ddefault_library=static
run: meson setup build -Ddefault_library=static
- name: Compile
run: ninja -C obj-x86_64-pc-linux-gnu
run: ninja -C build
noxwayland:
name: "Build Hyprland in pure Wayland (Arch)"
@ -86,23 +69,36 @@ jobs:
container:
image: archlinux
steps:
- name: Download dependencies
run: |
sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf
pacman --noconfirm --noprogressbar -Syyu
pacman --noconfirm --noprogressbar -Sy glslang libepoxy libfontenc libxcvt libxfont2 libxkbfile vulkan-headers vulkan-validation-layers git cmake go clang lld libc++ pkgconf meson ninja wayland wayland-protocols libinput libxkbcommon pixman glm libdrm libglvnd cairo pango systemd scdoc base-devel seatd libliftoff
- name: Set up user
run: |
useradd -m githubuser
echo -e "root ALL=(ALL:ALL) ALL\ngithubuser ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers
- name: Install libdisplay-info from the AUR
run: |
su githubuser -c "cd ~ && git clone https://aur.archlinux.org/libdisplay-info.git && cd ./libdisplay-info && makepkg -si --skippgpcheck --noconfirm --noprogressbar"
- name: Checkout Hyprland
uses: actions/checkout@v3
- name: Checkout repository actions
uses: actions/checkout@v4
with:
submodules: true
sparse-checkout: .github/actions
- name: Setup base
uses: ./.github/actions/setup_base
- name: Configure
run: mkdir -p build && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DNO_XWAYLAND:STRING=true -H./ -B./build -G Ninja
run: mkdir -p build && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DNO_XWAYLAND:STRING=true -H./ -B./build -G Ninja
- name: Compile
run: make config && make release
run: make release
clang-format:
name: "Code Style (Arch)"
runs-on: ubuntu-latest
container:
image: archlinux
steps:
- name: Checkout repository actions
uses: actions/checkout@v4
with:
sparse-checkout: .github/actions
- name: Setup base
uses: ./.github/actions/setup_base
- name: Configure
run: meson setup build -Ddefault_library=static
- name: clang-format check
run: ninja -C build clang-format-check

View file

@ -10,7 +10,6 @@ jobs:
matrix:
package:
- hyprland
- hyprland-nvidia
- xdg-desktop-portal-hyprland
runs-on: ubuntu-latest
@ -27,4 +26,4 @@ jobs:
name: hyprland
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- run: nix build -L ${{ matrix.command }}
- run: nix build .#${{ matrix.package }} -L

View file

@ -15,10 +15,15 @@ jobs:
with:
submodules: recursive
- name: Generate version
id: genversion
run: |
bash -c scripts/generateVersion.sh
mv scripts/generateVersion.sh scripts/generateVersion.sh.bak
- name: Create tarball with submodules
id: tar
run: |
sed -i "1s/^/#define GIT_COMMIT_HASH \"$(git rev-parse HEAD)\"\n#define GIT_TAG \"$(git describe --tags)\"\n/" ./src/macros.hpp
mkdir hyprland-source; mv ./* ./hyprland-source || tar -czv --owner=0 --group=0 --no-same-owner --no-same-permissions -f source.tar.gz *
- id: whatrelease

View file

@ -24,13 +24,13 @@ jobs:
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: ${{github.workspace}}/flawfinder_results.sarif
codeql:
name: CodeQL
runs-on: ubuntu-latest
container:
image: archlinux
permissions:
actions: read
contents: read
@ -42,34 +42,25 @@ jobs:
language: [ 'cpp' ]
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Checkout repository actions
uses: actions/checkout@v4
with:
sparse-checkout: .github/actions
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
- name: Init Hyprland build
run: |
sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf
pacman --noconfirm --noprogressbar -Syyu
pacman --noconfirm --noprogressbar -Sy glslang libepoxy libfontenc libxcvt libxfont2 libxkbfile vulkan-headers vulkan-validation-layers xcb-util-errors xcb-util-renderutil xcb-util-wm xorg-fonts-encodings xorg-server-common xorg-setxkbmap xorg-xkbcomp xorg-xwayland git cmake go clang lld libc++ pkgconf meson ninja wayland wayland-protocols libinput libxkbcommon pixman glm libdrm libglvnd cairo pango systemd scdoc base-devel seatd python libliftoff
useradd -m githubuser
echo -e "root ALL=(ALL:ALL) ALL\ngithubuser ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers
su githubuser -c "cd ~ && git clone https://aur.archlinux.org/libdisplay-info.git && cd ./libdisplay-info && makepkg -si --skippgpcheck --noconfirm --noprogressbar"
git config --global --add safe.directory /__w/Hyprland/Hyprland
- name: Checkout Hyprland
uses: actions/checkout@v3
- name: Setup base
uses: ./.github/actions/setup_base
with:
submodules: recursive
INSTALL_XORG_PKGS: true
- name: Build Hyprland
run: |
git submodule sync --recursive && git submodule update --init --force --recursive
make all
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:

28
.github/workflows/stale.yml vendored Normal file
View file

@ -0,0 +1,28 @@
# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time.
#
# You can adjust the behavior by modifying this file.
# For more information, see:
# https://github.com/actions/stale
name: Mark stale issues and pull requests
on:
schedule:
- cron: '7 */4 * * *'
workflow_dispatch:
jobs:
stale:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v5
with:
repo-token: ${{ secrets.STALEBOT_PAT }}
stale-issue-label: 'stale'
stale-pr-label: 'stale'
operations-per-run: 40
days-before-close: -1

2
.gitignore vendored
View file

@ -29,3 +29,5 @@ gmon.out
*.tar.gz
PKGBUILD
src/version.h

120
CMakeLists.txt Normal file → Executable file
View file

@ -21,37 +21,51 @@ message(STATUS "Gathering git info")
# Get git info
# hash and branch
execute_process(
COMMAND git rev-parse --abbrev-ref HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_BRANCH
OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(
COMMAND git rev-parse HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_COMMIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(
COMMAND sh -c "git show ${GIT_COMMIT_HASH} | head -n 5 | tail -n 1 | sed -e 's/#//g' -e 's/\"//g'"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_COMMIT_MESSAGE
OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(
COMMAND sh -c "git diff-index --quiet HEAD -- || echo \"dirty\""
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_DIRTY
OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(
COMMAND sh -c "git describe --tags"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_TAG
OUTPUT_STRIP_TRAILING_WHITESPACE)
COMMAND ./scripts/generateVersion.sh
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
#
#
# udis
add_subdirectory("subprojects/udis86")
# wlroots
message(STATUS "Setting up wlroots")
include(ExternalProject)
if(CMAKE_BUILD_TYPE)
string(TOLOWER ${CMAKE_BUILD_TYPE} BUILDTYPE_LOWER)
if(BUILDTYPE_LOWER STREQUAL "release")
# Pass.
elseif(BUILDTYPE_LOWER STREQUAL "debug")
# Pass.
elseif(BUILDTYPE_LOWER STREQUAL "relwithdebinfo")
set(BUILDTYPE_LOWER "debugoptimized")
elseif(BUILDTYPE_LOWER STREQUAL "minsizerel")
set(BUILDTYPE_LOWER "minsize")
elseif(BUILDTYPE_LOWER STREQUAL "none")
set(BUILDTYPE_LOWER "plain")
else()
set(BUILDTYPE_LOWER "release")
endif()
else()
set(BUILDTYPE_LOWER "release")
endif()
ExternalProject_Add(
wlroots
PREFIX ${CMAKE_SOURCE_DIR}/subprojects/wlroots
SOURCE_DIR ${CMAKE_SOURCE_DIR}/subprojects/wlroots
PATCH_COMMAND sed -E -i -e "s/(soversion = .*$)/soversion = 13032/g" meson.build
CONFIGURE_COMMAND meson setup --reconfigure build --buildtype=${BUILDTYPE_LOWER} -Dwerror=false -Dxwayland=$<IF:$<BOOL:${NO_XWAYLAND}>,disabled,enabled> -Dexamples=false -Drenderers=gles2 $<IF:$<BOOL:${WITH_ASAN}>,-Db_sanitize=address,-Db_sanitize=none>
BUILD_COMMAND ninja -C build
BUILD_ALWAYS true
BUILD_IN_SOURCE true
BUILD_BYPRODUCTS ${CMAKE_SOURCE_DIR}/subprojects/wlroots/build/libwlroots.so.13032
INSTALL_COMMAND echo "wlroots: install not needed"
)
find_program(WaylandScanner NAMES wayland-scanner)
message(STATUS "Found WaylandScanner at ${WaylandScanner}")
execute_process(
@ -85,14 +99,20 @@ set(CMAKE_ENABLE_EXPORTS TRUE)
message(STATUS "Checking deps...")
find_package(Threads REQUIRED)
find_package(PkgConfig REQUIRED)
find_package(OpenGL REQUIRED)
pkg_check_modules(deps REQUIRED IMPORTED_TARGET wayland-server wayland-client wayland-cursor wayland-protocols cairo libdrm xkbcommon libinput pango pangocairo pixman-1) # we do not check for wlroots, as we provide it ourselves
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp")
add_executable(Hyprland ${SRCFILES})
set(TRACY_CPP_FILES "")
if(USE_TRACY)
set(TRACY_CPP_FILES "subprojects/tracy/public/TracyClient.cpp")
message(STATUS "Tracy enabled, TRACY_CPP_FILES: " ${TRACY_CPP_FILES})
endif()
add_executable(Hyprland ${SRCFILES} ${TRACY_CPP_FILES})
add_dependencies(Hyprland wlroots)
if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
message(STATUS "Setting debug flags")
@ -101,7 +121,7 @@ if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
message(STATUS "Enabling ASan")
target_link_libraries(Hyprland asan)
add_compile_options(-fsanitize=address)
target_compile_options(Hyprland PUBLIC -fsanitize=address)
endif()
if(USE_TRACY)
@ -123,6 +143,12 @@ if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
add_link_options(-pg -no-pie -fno-builtin)
endif()
check_include_file("execinfo.h" EXECINFOH)
if(EXECINFOH)
message(STATUS "Configuration supports execinfo")
add_compile_definitions(HAS_EXECINFO)
endif()
include(CheckLibraryExists)
check_library_exists(execinfo backtrace "" HAVE_LIBEXECINFO)
if(HAVE_LIBEXECINFO)
@ -147,24 +173,21 @@ if(NO_SYSTEMD)
message(STATUS "SYSTEMD support is disabled...")
else()
message(STATUS "SYSTEMD support is requested (NO_SYSTEMD not defined) checking deps...")
pkg_check_modules(LIBSYSTEMD libsystemd)
check_include_file("systemd/sd-daemon.h" SYSTEMDH)
if(LIBSYSTEMD_FOUND AND SYSTEMDH)
add_compile_definitions(USES_SYSTEMD)
target_link_libraries(Hyprland "${LIBSYSTEMD_LIBRARIES}")
if(SYSTEMDH)
pkg_check_modules(LIBSYSTEMD libsystemd)
if (LIBSYSTEMD_FOUND)
add_compile_definitions(USES_SYSTEMD)
target_link_libraries(Hyprland "${LIBSYSTEMD_LIBRARIES}")
message(STATUS "Systemd found")
else()
message(WARNING "Systemd support requested but systemd libraries were not found")
endif()
else()
message(WARNING "Systemd support requested but libsystemd or systemd headers were not found")
message(WARNING "Systemd support requested but systemd headers were not found")
endif()
endif()
target_compile_definitions(Hyprland
PRIVATE
"GIT_COMMIT_HASH=\"${GIT_COMMIT_HASH}\""
"GIT_BRANCH=\"${GIT_BRANCH}\""
"GIT_COMMIT_MESSAGE=\"${GIT_COMMIT_MESSAGE}\""
"GIT_DIRTY=\"${GIT_DIRTY}\""
"GIT_TAG=\"${GIT_TAG}\"")
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)
@ -198,11 +221,11 @@ function(protocol protoPath protoName external)
endfunction()
target_link_libraries(Hyprland
${CMAKE_SOURCE_DIR}/subprojects/wlroots/build/libwlroots.so.12032 # wlroots is provided by us
${CMAKE_SOURCE_DIR}/subprojects/wlroots/build/libwlroots.so.13032 # wlroots is provided by us
OpenGL::EGL
OpenGL::GL
Threads::Threads
${CMAKE_SOURCE_DIR}/subprojects/udis86/build/libudis86/liblibudis86.a
libudis86
)
protocol("protocols/idle.xml" "idle" true)
@ -218,5 +241,10 @@ protocol("stable/xdg-shell/xdg-shell.xml" "xdg-shell" false)
protocol("unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml" "linux-dmabuf-unstable-v1" false)
protocol("unstable/xdg-output/xdg-output-unstable-v1.xml" "xdg-output-unstable-v1" false)
protocol("staging/fractional-scale/fractional-scale-v1.xml" "fractional-scale-v1" false)
protocol("staging/tearing-control/tearing-control-v1.xml" "tearing-control-v1" false)
protocol("unstable/text-input/text-input-unstable-v1.xml" "text-input-unstable-v1" false)
protocol("staging/cursor-shape/cursor-shape-v1.xml" "cursor-shape-v1" false)
# tools
add_subdirectory(hyprctl)
add_subdirectory(hyprpm)

View file

@ -3,103 +3,76 @@ PREFIX = /usr/local
legacyrenderer:
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DLEGACY_RENDERER:BOOL=true -S . -B ./build -G Ninja
cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
chmod -R 777 ./build
legacyrendererdebug:
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -DLEGACY_RENDERER:BOOL=true -S . -B ./build -G Ninja
cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
chmod -R 777 ./build
release:
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -S . -B ./build -G Ninja
cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
chmod -R 777 ./build
debug:
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -S . -B ./build -G Ninja
cmake --build ./build --config Debug --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
chmod -R 777 ./build
clear:
rm -rf build
rm -f ./protocols/*-protocol.h ./protocols/*-protocol.c
rm -f ./hyprctl/hyprctl
rm -rf ./subprojects/wlroots/build
all:
@if [[ "$EUID" = 0 ]]; then echo -en "Avoid running $(MAKE) all as sudo.\n"; fi
$(MAKE) clear
$(MAKE) fixwlr
cd ./subprojects/wlroots && meson setup build/ --buildtype=release && ninja -C build/ && mkdir -p ${PREFIX}/lib/ && cp ./build/libwlroots.so.12032 ${PREFIX}/lib/ || echo "Could not install libwlroots to ${PREFIX}/lib/libwlroots.so.12032"
cd subprojects/udis86 && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -S . -B./build -G Ninja && cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
$(MAKE) release
$(MAKE) -C hyprctl all
install:
$(MAKE) clear
$(MAKE) fixwlr
cd ./subprojects/wlroots && meson setup build/ --buildtype=release && ninja -C build/ && mkdir -p ${PREFIX}/lib/ && cp ./build/libwlroots.so.12032 ${PREFIX}/lib/ || echo "Could not install libwlroots to ${PREFIX}/lib/libwlroots.so.12032"
cd subprojects/udis86 && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -S . -B./build -G Ninja && cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF` && cd ../..
$(MAKE) release
$(MAKE) -C hyprctl all
@if [ ! -f ./build/Hyprland ]; then echo -en "You need to run $(MAKE) all first.\n" && exit 1; fi
@echo -en "!NOTE: Please note make install does not compile Hyprland and only installs the already built files."
mkdir -p ${PREFIX}/share/wayland-sessions
mkdir -p ${PREFIX}/bin
cp -f ./build/Hyprland ${PREFIX}/bin
cp -f ./hyprctl/hyprctl ${PREFIX}/bin
cp -f ./build/hyprctl/hyprctl ${PREFIX}/bin
cp -f ./build/hyprpm/hyprpm ${PREFIX}/bin
chmod 755 ${PREFIX}/bin/Hyprland
chmod 755 ${PREFIX}/bin/hyprctl
chmod 755 ${PREFIX}/bin/hyprpm
cd ${PREFIX}/bin && ln -sf Hyprland hyprland
if [ ! -f ${PREFIX}/share/wayland-sessions/hyprland.desktop ]; then cp ./example/hyprland.desktop ${PREFIX}/share/wayland-sessions; fi
mkdir -p ${PREFIX}/share/hyprland
cp ./assets/wall_* ${PREFIX}/share/hyprland
mkdir -p ${PREFIX}/share/xdg-desktop-portal
cp ./assets/hyprland-portals.conf ${PREFIX}/share/xdg-desktop-portal
mkdir -p ${PREFIX}/share/man/man1
install -m644 ./docs/*.1 ${PREFIX}/share/man/man1
mkdir -p ${PREFIX}/include/hyprland
mkdir -p ${PREFIX}/include/hyprland/protocols
mkdir -p ${PREFIX}/include/hyprland/wlroots
mkdir -p ${PREFIX}/share/pkgconfig
mkdir -p ${PREFIX}/share/xdg-desktop-portal
find src -name '*.h*' -print0 | cpio --quiet -0dump ${PREFIX}/include/hyprland
cd subprojects/wlroots/include && find . -name '*.h*' -print0 | cpio --quiet -0dump ${PREFIX}/include/hyprland/wlroots && cd ../../..
cd subprojects/wlroots/build/include && find . -name '*.h*' -print0 | cpio --quiet -0dump ${PREFIX}/include/hyprland/wlroots && cd ../../../..
cp ./protocols/*-protocol.h ${PREFIX}/include/hyprland/protocols
cp ./build/hyprland.pc ${PREFIX}/share/pkgconfig
cp ./assets/hyprland-portals.conf ${PREFIX}/share/xdg-desktop-portal/
if [ -d /usr/share/pkgconfig ]; then cp ./build/hyprland.pc /usr/share/pkgconfig 2>/dev/null || true; fi
mkdir -p ${PREFIX}/lib/
cp ./subprojects/wlroots/build/libwlroots.so.13032 ${PREFIX}/lib/
cleaninstall:
echo -en "$(MAKE) cleaninstall has been DEPRECATED, you should avoid using it in the future.\nRunning $(MAKE) install instead...\n"
$(MAKE) install
$(MAKE) installheaders
uninstall:
rm -f ${PREFIX}/share/wayland-sessions/hyprland.desktop
rm -f ${PREFIX}/bin/Hyprland
rm -f ${PREFIX}/bin/hyprctl
rm -f ${PREFIX}/lib/libwlroots.so.12032
rm -f ${PREFIX}/bin/hyprpm
rm -f ${PREFIX}/lib/libwlroots.so.13032
rm -rf ${PREFIX}/share/hyprland
rm -f ${PREFIX}/share/man/man1/Hyprland.1
rm -f ${PREFIX}/share/man/man1/hyprctl.1
fixwlr:
sed -E -i -e 's/(soversion = 12)([^032]|$$)/soversion = 12032/g' subprojects/wlroots/meson.build
rm -rf ./subprojects/wlroots/build
config:
$(MAKE) fixwlr
meson setup subprojects/wlroots/build subprojects/wlroots --prefix=${PREFIX} --buildtype=release -Dwerror=false -Dexamples=false -Drenderers="gles2"
ninja -C subprojects/wlroots/build/
ninja -C subprojects/wlroots/build/ install
cd subprojects/udis86 && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -S . -B ./build -G Ninja && cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
pluginenv:
cd subprojects/udis86 && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -S . -B ./build -G Ninja && cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
$(MAKE) fixwlr
meson setup subprojects/wlroots/build subprojects/wlroots --prefix=${PREFIX} --buildtype=release -Dwerror=false -Dexamples=false
ninja -C subprojects/wlroots/build/
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -S . -B ./build -G Ninja
@echo -en "$(MAKE) pluginenv has been deprecated.\nPlease run $(MAKE) all && sudo $(MAKE) installheaders\n"
@exit 1
installheaders:
@if [ ! -f ./src/version.h ]; then echo -en "You need to run $(MAKE) all first.\n" && exit 1; fi
mkdir -p ${PREFIX}/include/hyprland
mkdir -p ${PREFIX}/include/hyprland/protocols
@ -113,13 +86,8 @@ pluginenv:
cp ./build/hyprland.pc ${PREFIX}/share/pkgconfig
if [ -d /usr/share/pkgconfig ]; then cp ./build/hyprland.pc /usr/share/pkgconfig 2>/dev/null || true; fi
configdebug:
$(MAKE) fixwlr
meson setup subprojects/wlroots/build subprojects/wlroots --prefix=${PREFIX} --buildtype=debug -Dwerror=false -Dexamples=false -Drenderers="gles2" -Db_sanitize=address
ninja -C subprojects/wlroots/build/
ninja -C subprojects/wlroots/build/ install
chmod -R 755 ${PREFIX}/include/hyprland
chmod 755 ${PREFIX}/share/pkgconfig
man:
pandoc ./docs/Hyprland.1.rst \

View file

@ -41,6 +41,8 @@ easy IPC, much more QoL stuff than other wlr-based compositors and more...
- Much more QoL stuff than other wlr-based compositors
- Custom bezier curves for the best animations
- Powerful plugin support
- Built-in plugin manager
- Tearing support for better gaming performance
- Easily expandable and readable codebase
- Fast and active development
- Not scared to provide bleeding-edge features
@ -127,8 +129,8 @@ easy IPC, much more QoL stuff than other wlr-based compositors and more...
<!----------------------------------{ Images }--------------------------------->
[Stars Preview]: https://starchart.cc/vaxerski/Hyprland.svg
[Preview A]: https://cdn.discordapp.com/attachments/1091569872535814185/1107675866101723277/screenshot-summer.png
[Preview B]: https://i.ibb.co/SX7GbYR/winter-rice.png
[Preview A]: https://i.ibb.co/C1yTb0r/falf.png
[Preview B]: https://cdn.discordapp.com/attachments/1091569872535814185/1107675866101723277/screenshot-summer.png
[Preview C]: https://i.ibb.co/B3GJg28/20221126-20h53m26s-grim.png

View file

@ -1,2 +1,2 @@
[preferred]
default=hyprland
default=hyprland;gtk

View file

@ -1,8 +0,0 @@
# compile with HYPRLAND_HEADERS=<path_to_hl> make all
# make sure that the path above is to the root hl repo directory, NOT src/
# and that you have ran `make protocols` in the hl dir.
all:
$(CXX) -shared -fPIC --no-gnu-unique main.cpp customLayout.cpp customDecoration.cpp -o examplePlugin.so -g `pkg-config --cflags pixman-1 libdrm hyprland` -std=c++2b
clean:
rm ./examplePlugin.so

View file

@ -1,74 +0,0 @@
#include "customDecoration.hpp"
#include <hyprland/src/Window.hpp>
#include <hyprland/src/Compositor.hpp>
#include "globals.hpp"
CCustomDecoration::CCustomDecoration(CWindow* pWindow) {
m_pWindow = pWindow;
m_vLastWindowPos = pWindow->m_vRealPosition.vec();
m_vLastWindowSize = pWindow->m_vRealSize.vec();
}
CCustomDecoration::~CCustomDecoration() {
damageEntire();
}
SWindowDecorationExtents CCustomDecoration::getWindowDecorationExtents() {
return m_seExtents;
}
void CCustomDecoration::draw(CMonitor* pMonitor, float a, const Vector2D& offset) {
if (!g_pCompositor->windowValidMapped(m_pWindow))
return;
if (!m_pWindow->m_sSpecialRenderData.decorate)
return;
static auto* const PCOLOR = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:example:border_color")->intValue;
static auto* const PROUNDING = &HyprlandAPI::getConfigValue(PHANDLE, "decoration:rounding")->intValue;
static auto* const PBORDERSIZE = &HyprlandAPI::getConfigValue(PHANDLE, "general:border_size")->intValue;
const auto ROUNDING = !m_pWindow->m_sSpecialRenderData.rounding ?
0 :
(m_pWindow->m_sAdditionalConfigData.rounding.toUnderlying() == -1 ? *PROUNDING : m_pWindow->m_sAdditionalConfigData.rounding.toUnderlying());
// draw the border
wlr_box fullBox = {(int)(m_vLastWindowPos.x - *PBORDERSIZE), (int)(m_vLastWindowPos.y - *PBORDERSIZE), (int)(m_vLastWindowSize.x + 2.0 * *PBORDERSIZE),
(int)(m_vLastWindowSize.y + 2.0 * *PBORDERSIZE)};
fullBox.x -= pMonitor->vecPosition.x;
fullBox.y -= pMonitor->vecPosition.y;
m_seExtents = {{m_vLastWindowPos.x - fullBox.x - pMonitor->vecPosition.x + 2, m_vLastWindowPos.y - fullBox.y - pMonitor->vecPosition.y + 2},
{fullBox.x + fullBox.width + pMonitor->vecPosition.x - m_vLastWindowPos.x - m_vLastWindowSize.x + 2,
fullBox.y + fullBox.height + pMonitor->vecPosition.y - m_vLastWindowPos.y - m_vLastWindowSize.y + 2}};
fullBox.x += offset.x;
fullBox.y += offset.y;
if (fullBox.width < 1 || fullBox.height < 1)
return; // don't draw invisible shadows
g_pHyprOpenGL->scissor((wlr_box*)nullptr);
scaleBox(&fullBox, pMonitor->scale);
g_pHyprOpenGL->renderBorder(&fullBox, CColor(*PCOLOR), *PROUNDING * pMonitor->scale + *PBORDERSIZE * 2, a);
}
eDecorationType CCustomDecoration::getDecorationType() {
return DECORATION_CUSTOM;
}
void CCustomDecoration::updateWindow(CWindow* pWindow) {
m_vLastWindowPos = pWindow->m_vRealPosition.vec();
m_vLastWindowSize = pWindow->m_vRealSize.vec();
damageEntire();
}
void CCustomDecoration::damageEntire() {
wlr_box dm = {(int)(m_vLastWindowPos.x - m_seExtents.topLeft.x), (int)(m_vLastWindowPos.y - m_seExtents.topLeft.y),
(int)(m_vLastWindowSize.x + m_seExtents.topLeft.x + m_seExtents.bottomRight.x), (int)m_seExtents.topLeft.y};
g_pHyprRenderer->damageBox(&dm);
}

View file

@ -1,29 +0,0 @@
#pragma once
#define WLR_USE_UNSTABLE
#include <hyprland/src/render/decorations/IHyprWindowDecoration.hpp>
class CCustomDecoration : public IHyprWindowDecoration {
public:
CCustomDecoration(CWindow*);
virtual ~CCustomDecoration();
virtual SWindowDecorationExtents getWindowDecorationExtents();
virtual void draw(CMonitor*, float a, const Vector2D& offset);
virtual eDecorationType getDecorationType();
virtual void updateWindow(CWindow*);
virtual void damageEntire();
private:
SWindowDecorationExtents m_seExtents;
CWindow* m_pWindow = nullptr;
Vector2D m_vLastWindowPos;
Vector2D m_vLastWindowSize;
};

View file

@ -1,80 +0,0 @@
#include "customLayout.hpp"
#include <hyprland/src/Compositor.hpp>
#include "globals.hpp"
void CHyprCustomLayout::onWindowCreatedTiling(CWindow* pWindow) {
const auto PMONITOR = g_pCompositor->getMonitorFromID(pWindow->m_iMonitorID);
const auto SIZE = PMONITOR->vecSize;
// these are used for focus and move calculations, and are *required* to touch for moving focus to work properly.
pWindow->m_vPosition = Vector2D{(SIZE.x / 2.0) * (m_vWindowData.size() % 2), (SIZE.y / 2.0) * (int)(m_vWindowData.size() > 1)};
pWindow->m_vSize = SIZE / 2.0;
// this is the actual pos and size of the window (where it's rendered)
pWindow->m_vRealPosition = pWindow->m_vPosition + Vector2D{10, 10};
pWindow->m_vRealSize = pWindow->m_vSize - Vector2D{20, 20};
const auto PDATA = &m_vWindowData.emplace_back();
PDATA->pWindow = pWindow;
}
void CHyprCustomLayout::onWindowRemovedTiling(CWindow* pWindow) {
std::erase_if(m_vWindowData, [&](const auto& other) { return other.pWindow == pWindow; });
}
bool CHyprCustomLayout::isWindowTiled(CWindow* pWindow) {
return std::find_if(m_vWindowData.begin(), m_vWindowData.end(), [&](const auto& other) { return other.pWindow == pWindow; }) != m_vWindowData.end();
}
void CHyprCustomLayout::recalculateMonitor(const int& eIdleInhibitMode) {
; // empty
}
void CHyprCustomLayout::recalculateWindow(CWindow* pWindow) {
; // empty
}
void CHyprCustomLayout::resizeActiveWindow(const Vector2D& delta, CWindow* pWindow) {
; // empty
}
void CHyprCustomLayout::fullscreenRequestForWindow(CWindow* pWindow, eFullscreenMode mode, bool on) {
; // empty
}
std::any CHyprCustomLayout::layoutMessage(SLayoutMessageHeader header, std::string content) {
return "";
}
SWindowRenderLayoutHints CHyprCustomLayout::requestRenderHints(CWindow* pWindow) {
return {};
}
void CHyprCustomLayout::switchWindows(CWindow* pWindowA, CWindow* pWindowB) {
; // empty
}
void CHyprCustomLayout::alterSplitRatio(CWindow* pWindow, float delta, bool exact) {
; // empty
}
std::string CHyprCustomLayout::getLayoutName() {
return "custom";
}
void CHyprCustomLayout::replaceWindowDataWith(CWindow* from, CWindow* to) {
; // empty
}
void CHyprCustomLayout::onEnable() {
for (auto& w : g_pCompositor->m_vWindows) {
if (w->isHidden() || !w->m_bIsMapped || w->m_bFadingOut || w->m_bIsFloating)
continue;
onWindowCreatedTiling(w.get());
}
}
void CHyprCustomLayout::onDisable() {
m_vWindowData.clear();
}

View file

@ -1,32 +0,0 @@
#pragma once
#define WLR_USE_UNSTABLE
#include <hyprland/src/layout/IHyprLayout.hpp>
struct SWindowData {
CWindow* pWindow = nullptr;
};
class CHyprCustomLayout : public IHyprLayout {
public:
virtual void onWindowCreatedTiling(CWindow*);
virtual void onWindowRemovedTiling(CWindow*);
virtual bool isWindowTiled(CWindow*);
virtual void recalculateMonitor(const int&);
virtual void recalculateWindow(CWindow*);
virtual void resizeActiveWindow(const Vector2D&, CWindow* pWindow = nullptr);
virtual void fullscreenRequestForWindow(CWindow*, eFullscreenMode, bool);
virtual std::any layoutMessage(SLayoutMessageHeader, std::string);
virtual SWindowRenderLayoutHints requestRenderHints(CWindow*);
virtual void switchWindows(CWindow*, CWindow*);
virtual void alterSplitRatio(CWindow*, float, bool);
virtual std::string getLayoutName();
virtual void replaceWindowDataWith(CWindow* from, CWindow* to);
virtual void onEnable();
virtual void onDisable();
private:
std::vector<SWindowData> m_vWindowData;
};

View file

@ -1,5 +0,0 @@
#pragma once
#include <hyprland/src/plugins/PluginAPI.hpp>
inline HANDLE PHANDLE = nullptr;

View file

@ -1,99 +0,0 @@
#define WLR_USE_UNSTABLE
#include "globals.hpp"
#include <hyprland/src/Window.hpp>
#include <hyprland/src/Compositor.hpp>
#include "customLayout.hpp"
#include "customDecoration.hpp"
#include <unistd.h>
#include <thread>
// Methods
inline std::unique_ptr<CHyprCustomLayout> g_pCustomLayout;
inline CFunctionHook* g_pFocusHook = nullptr;
inline CFunctionHook* g_pMotionHook = nullptr;
inline CFunctionHook* g_pMouseDownHook = nullptr;
typedef void (*origFocusWindow)(void*, CWindow*, wlr_surface*);
typedef void (*origMotion)(wlr_seat*, uint32_t, double, double);
typedef void (*origMouseDownNormal)(void*, wlr_pointer_button_event*);
// Do NOT change this function.
APICALL EXPORT std::string PLUGIN_API_VERSION() {
return HYPRLAND_API_VERSION;
}
static void onActiveWindowChange(void* self, std::any data) {
try {
auto* const PWINDOW = std::any_cast<CWindow*>(data);
HyprlandAPI::addNotification(PHANDLE, "[ExamplePlugin] Active window: " + (PWINDOW ? PWINDOW->m_szTitle : "None"), CColor{0.f, 0.5f, 1.f, 1.f}, 5000);
} catch (std::bad_any_cast& e) { HyprlandAPI::addNotification(PHANDLE, "[ExamplePlugin] Active window: None", CColor{0.f, 0.5f, 1.f, 1.f}, 5000); }
}
static void onNewWindow(void* self, std::any data) {
auto* const PWINDOW = std::any_cast<CWindow*>(data);
HyprlandAPI::addWindowDecoration(PHANDLE, PWINDOW, new CCustomDecoration(PWINDOW));
}
void hkFocusWindow(void* thisptr, CWindow* pWindow, wlr_surface* pSurface) {
// HyprlandAPI::addNotification(PHANDLE, getFormat("FocusWindow with %lx %lx", pWindow, pSurface), CColor{0.f, 1.f, 1.f, 1.f}, 5000);
(*(origFocusWindow)g_pFocusHook->m_pOriginal)(thisptr, pWindow, pSurface);
}
void hkNotifyMotion(wlr_seat* wlr_seat, uint32_t time_msec, double sx, double sy) {
// HyprlandAPI::addNotification(PHANDLE, getFormat("NotifyMotion with %lf %lf", sx, sy), CColor{0.f, 1.f, 1.f, 1.f}, 5000);
(*(origMotion)g_pMotionHook->m_pOriginal)(wlr_seat, time_msec, sx, sy);
}
void hkProcessMouseDownNormal(void* thisptr, wlr_pointer_button_event* e) {
// HyprlandAPI::addNotification(PHANDLE, "Mouse down normal!", CColor{0.8f, 0.2f, 0.5f, 1.0f}, 5000);
(*(origMouseDownNormal)g_pMouseDownHook->m_pOriginal)(thisptr, e);
}
APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
PHANDLE = handle;
HyprlandAPI::addNotification(PHANDLE, "Hello World from an example plugin!", CColor{0.f, 1.f, 1.f, 1.f}, 5000);
HyprlandAPI::registerCallbackDynamic(PHANDLE, "activeWindow", [&](void* self, std::any data) { onActiveWindowChange(self, data); });
HyprlandAPI::registerCallbackDynamic(PHANDLE, "openWindow", [&](void* self, std::any data) { onNewWindow(self, data); });
g_pCustomLayout = std::make_unique<CHyprCustomLayout>();
HyprlandAPI::addLayout(PHANDLE, "custom", g_pCustomLayout.get());
HyprlandAPI::addConfigValue(PHANDLE, "plugin:example:border_color", SConfigValue{.intValue = configStringToInt("rgb(44ee44)")});
HyprlandAPI::addDispatcher(PHANDLE, "example", [](std::string arg) { HyprlandAPI::addNotification(PHANDLE, "Arg passed: " + arg, CColor{0.5f, 0.5f, 0.7f, 1.0f}, 5000); });
// Hook a public member
g_pFocusHook = HyprlandAPI::createFunctionHook(PHANDLE, (void*)&CCompositor::focusWindow, (void*)&hkFocusWindow);
// Hook a public non-member
g_pMotionHook = HyprlandAPI::createFunctionHook(PHANDLE, (void*)&wlr_seat_pointer_notify_motion, (void*)&hkNotifyMotion);
// Hook a private member
static const auto METHODS = HyprlandAPI::findFunctionsByName(PHANDLE, "processMouseDownNormal");
g_pMouseDownHook = HyprlandAPI::createFunctionHook(PHANDLE, METHODS[0].address, (void*)&hkProcessMouseDownNormal);
static auto* const PBORDERCOLOR = HyprlandAPI::getConfigValue(PHANDLE, "plugin:example:border_color");
// fancy notifications
HyprlandAPI::addNotificationV2(
PHANDLE,
{{"text", "Example hint, color " + std::to_string(PBORDERCOLOR->intValue)}, {"time", (uint64_t)10000}, {"color", CColor{PBORDERCOLOR->intValue}}, {"icon", ICON_HINT}});
// Enable our hooks
g_pFocusHook->hook();
g_pMotionHook->hook();
g_pMouseDownHook->hook();
HyprlandAPI::reloadConfig();
return {"ExamplePlugin", "An example plugin", "Vaxry", "1.0"};
}
APICALL EXPORT void PLUGIN_EXIT() {
HyprlandAPI::invokeHyprctlCommand("seterror", "disable");
}

View file

@ -19,8 +19,14 @@ monitor=,preferred,auto,auto
# Source a file (multi-file configs)
# source = ~/.config/hypr/myColors.conf
# Set programs that you use
$terminal = kitty
$fileManager = dolphin
$menu = wofi --show drun
# Some default env vars.
env = XCURSOR_SIZE,24
env = QT_QPA_PLATFORMTHEME,qt5ct # change to qt6ct if you have that
# For all categories, see https://wiki.hyprland.org/Configuring/Variables/
input {
@ -49,6 +55,9 @@ general {
col.inactive_border = rgba(595959aa)
layout = dwindle
# Please see https://wiki.hyprland.org/Configuring/Tearing/ before you turn this on
allow_tearing = false
}
decoration {
@ -60,6 +69,8 @@ decoration {
enabled = true
size = 3
passes = 1
vibrancy = 0.1696
}
drop_shadow = true
@ -99,6 +110,11 @@ gestures {
workspace_swipe = false
}
misc {
# See https://wiki.hyprland.org/Configuring/Variables/ for more
force_default_wallpaper = -1 # Set to 0 to disable the anime mascot wallpapers
}
# Example per-device config
# See https://wiki.hyprland.org/Configuring/Keywords/#per-device-input-configs for more
device:epic-mouse-v1 {
@ -110,18 +126,19 @@ device:epic-mouse-v1 {
# Example windowrule v2
# windowrulev2 = float,class:^(kitty)$,title:^(kitty)$
# See https://wiki.hyprland.org/Configuring/Window-Rules/ for more
windowrulev2 = nomaximizerequest, class:.* # You'll probably like this.
# See https://wiki.hyprland.org/Configuring/Keywords/ for more
$mainMod = SUPER
# Example binds, see https://wiki.hyprland.org/Configuring/Binds/ for more
bind = $mainMod, Q, exec, kitty
bind = $mainMod, Q, exec, $terminal
bind = $mainMod, C, killactive,
bind = $mainMod, M, exit,
bind = $mainMod, E, exec, dolphin
bind = $mainMod, E, exec, $fileManager
bind = $mainMod, V, togglefloating,
bind = $mainMod, R, exec, wofi --show drun
bind = $mainMod, R, exec, $menu
bind = $mainMod, P, pseudo, # dwindle
bind = $mainMod, J, togglesplit, # dwindle
@ -155,6 +172,10 @@ bind = $mainMod SHIFT, 8, movetoworkspace, 8
bind = $mainMod SHIFT, 9, movetoworkspace, 9
bind = $mainMod SHIFT, 0, movetoworkspace, 10
# Example special workspace (scratchpad)
bind = $mainMod, S, togglespecialworkspace, magic
bind = $mainMod SHIFT, S, movetoworkspace, special:magic
# Scroll through existing workspaces with mainMod + scroll
bind = $mainMod, mouse_down, workspace, e+1
bind = $mainMod, mouse_up, workspace, e-1

View file

@ -23,13 +23,34 @@
"type": "github"
}
},
"hyprlang": {
"inputs": {
"nixpkgs": [
"xdph",
"nixpkgs"
]
},
"locked": {
"lastModified": 1704287638,
"narHash": "sha256-TuRXJGwtK440AXQNl5eiqmQqY4LZ/9+z/R7xC0ie3iA=",
"owner": "hyprwm",
"repo": "hyprlang",
"rev": "6624f2bb66d4d27975766e81f77174adbe58ec97",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprlang",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1694767346,
"narHash": "sha256-5uH27SiVFUwsTsqC5rs3kS7pBoNhtoy9QfTP9BmknGk=",
"lastModified": 1705133751,
"narHash": "sha256-rCIsyE80jgiOU78gCWN3A0wE0tR2GI5nH6MlS+HaaSQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "ace5093e36ab1e95cb9463863491bee90d5a4183",
"rev": "9b19f5e77dd906cb52dade0b7bd280339d2a1f3d",
"type": "github"
},
"original": {
@ -67,18 +88,18 @@
"flake": false,
"locked": {
"host": "gitlab.freedesktop.org",
"lastModified": 1695277534,
"narHash": "sha256-LEIUGXvKR5DYFQUTavC3yifcObvG4XZUUHfxXmu8nEM=",
"lastModified": 1703963193,
"narHash": "sha256-ke8drv6PTrdQDruWbajrRJffP9A9PU6FRyjJGNZRTs4=",
"owner": "wlroots",
"repo": "wlroots",
"rev": "98a745d926d8048bc30aef11b421df207a01c279",
"rev": "f81c3d93cd6f61b20ae784297679283438def8df",
"type": "gitlab"
},
"original": {
"host": "gitlab.freedesktop.org",
"owner": "wlroots",
"repo": "wlroots",
"rev": "98a745d926d8048bc30aef11b421df207a01c279",
"rev": "f81c3d93cd6f61b20ae784297679283438def8df",
"type": "gitlab"
}
},
@ -87,6 +108,7 @@
"hyprland-protocols": [
"hyprland-protocols"
],
"hyprlang": "hyprlang",
"nixpkgs": [
"nixpkgs"
],
@ -95,11 +117,11 @@
]
},
"locked": {
"lastModified": 1694628480,
"narHash": "sha256-Qg9hstRw0pvjGu5hStkr2UX1D73RYcQ9Ns/KnZMIm9w=",
"lastModified": 1704659450,
"narHash": "sha256-3lyoUVtUWz1LuxbltAtkJSK2IlVXmKhxCRU2/0PYCms=",
"owner": "hyprwm",
"repo": "xdg-desktop-portal-hyprland",
"rev": "8f45a6435069b9e24ebd3160eda736d7a391cbf2",
"rev": "6a5de92769d5b7038134044053f90e7458f6a197",
"type": "github"
},
"original": {

View file

@ -12,7 +12,7 @@
host = "gitlab.freedesktop.org";
owner = "wlroots";
repo = "wlroots";
rev = "98a745d926d8048bc30aef11b421df207a01c279";
rev = "f81c3d93cd6f61b20ae784297679283438def8df";
flake = false;
};
@ -62,13 +62,16 @@
inherit
(pkgsFor.${system})
# hyprland-packages
hyprland
hyprland-unwrapped
hyprland-debug
hyprland-nvidia
hyprland-legacy-renderer
# hyprland-extras
xdg-desktop-portal-hyprland
# dependencies
hyprland-protocols
wlroots-hyprland
udis86
@ -76,17 +79,18 @@
});
devShells = eachSystem (system: {
default = pkgsFor.${system}.mkShell.override {
stdenv = pkgsFor.${system}.gcc13Stdenv;
} {
name = "hyprland-shell";
nativeBuildInputs = with pkgsFor.${system}; [cmake python3];
buildInputs = [self.packages.${system}.wlroots-hyprland];
inputsFrom = [
self.packages.${system}.wlroots-hyprland
self.packages.${system}.hyprland
];
};
default =
pkgsFor.${system}.mkShell.override {
stdenv = pkgsFor.${system}.gcc13Stdenv;
} {
name = "hyprland-shell";
nativeBuildInputs = with pkgsFor.${system}; [cmake python3];
buildInputs = [self.packages.${system}.wlroots-hyprland];
inputsFrom = [
self.packages.${system}.wlroots-hyprland
self.packages.${system}.hyprland
];
};
});
formatter = eachSystem (system: nixpkgs.legacyPackages.${system}.alejandra);

8
hyprctl/CMakeLists.txt Normal file
View file

@ -0,0 +1,8 @@
cmake_minimum_required(VERSION 3.19)
project(
hyprctl
DESCRIPTION "Control utility for Hyprland"
)
add_executable(hyprctl "main.cpp")

View file

@ -1,4 +1,4 @@
all:
$(CXX) -std=c++2b ./main.cpp -o ./hyprctl
$(CXX) $(CXXFLAGS) -std=c++2b ./main.cpp -o ./hyprctl
clean:
rm ./hyprctl

View file

@ -26,31 +26,35 @@
const std::string USAGE = R"#(usage: hyprctl [(opt)flags] [command] [(opt)args]
commands:
monitors
workspaces
activeworkspace
clients
activewindow
layers
devices
activeworkspace
binds
clients
cursorpos
decorations
devices
dispatch
keyword
version
kill
splash
getoption
globalshortcuts
hyprpaper
instances
keyword
kill
layers
layouts
monitors
notify
plugin
reload
setcursor
getoption
cursorpos
switchxkblayout
seterror
setprop
plugin
notify
globalshortcuts
instances
splash
switchxkblayout
systeminfo
version
workspacerules
workspaces
flags:
-j -> output in JSON
@ -273,7 +277,6 @@ bool isNumber(const std::string& str, bool allowfloat) {
}
int main(int argc, char** argv) {
int bflag = 0, sflag = 0, index, c;
bool parseArgs = true;
if (argc < 2) {
@ -287,7 +290,7 @@ int main(int argc, char** argv) {
bool json = false;
std::string overrideInstance = "";
for (auto i = 0; i < ARGS.size(); ++i) {
for (std::size_t i = 0; i < ARGS.size(); ++i) {
if (ARGS[i] == "--") {
// Stop parsing arguments after --
parseArgs = false;
@ -329,6 +332,12 @@ int main(int argc, char** argv) {
fullRequest = fullArgs + "/" + fullRequest;
// instances is HIS-independent
if (fullRequest.contains("/instances")) {
instancesRequest(json);
return 0;
}
if (overrideInstance.contains("_"))
instanceSignature = overrideInstance;
else if (!overrideInstance.empty()) {
@ -341,7 +350,7 @@ int main(int argc, char** argv) {
const auto INSTANCES = instances();
if (INSTANCENO < 0 || INSTANCENO >= INSTANCES.size()) {
if (INSTANCENO < 0 || static_cast<std::size_t>(INSTANCENO) >= INSTANCES.size()) {
std::cout << "no such instance\n";
return 1;
}
@ -370,6 +379,8 @@ int main(int argc, char** argv) {
request(fullRequest);
else if (fullRequest.contains("/activeworkspace"))
request(fullRequest);
else if (fullRequest.contains("/workspacerules"))
request(fullRequest);
else if (fullRequest.contains("/activewindow"))
request(fullRequest);
else if (fullRequest.contains("/layers"))
@ -378,6 +389,8 @@ int main(int argc, char** argv) {
request(fullRequest);
else if (fullRequest.contains("/kill"))
request(fullRequest);
else if (fullRequest.contains("/systeminfo"))
request(fullRequest);
else if (fullRequest.contains("/splash"))
request(fullRequest);
else if (fullRequest.contains("/devices"))
@ -394,8 +407,8 @@ int main(int argc, char** argv) {
request(fullRequest);
else if (fullRequest.contains("/globalshortcuts"))
request(fullRequest);
else if (fullRequest.contains("/instances"))
instancesRequest(json);
else if (fullRequest.contains("/rollinglog"))
request(fullRequest);
else if (fullRequest.contains("/switchxkblayout"))
request(fullRequest, 2);
else if (fullRequest.contains("/seterror"))
@ -414,8 +427,12 @@ int main(int argc, char** argv) {
request(fullRequest, 1);
else if (fullRequest.contains("/keyword"))
request(fullRequest, 2);
else if (fullRequest.contains("/decorations"))
request(fullRequest, 1);
else if (fullRequest.contains("/hyprpaper"))
requestHyprpaper(fullRequest);
else if (fullRequest.contains("/layouts"))
request(fullRequest);
else if (fullRequest.contains("/--help"))
printf("%s", USAGE.c_str());
else {

16
hyprpm/CMakeLists.txt Normal file
View file

@ -0,0 +1,16 @@
cmake_minimum_required(VERSION 3.19)
project(
hyprpm
DESCRIPTION "A Hyprland Plugin Manager"
)
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp")
set(CMAKE_CXX_STANDARD 23)
pkg_check_modules(tomlplusplus REQUIRED IMPORTED_TARGET tomlplusplus)
add_executable(hyprpm ${SRCFILES})
target_link_libraries(hyprpm PUBLIC PkgConfig::tomlplusplus)

View file

@ -0,0 +1,222 @@
#include "DataState.hpp"
#include <toml++/toml.hpp>
#include <iostream>
#include <filesystem>
#include <fstream>
#include "PluginManager.hpp"
std::string DataState::getDataStatePath() {
const auto HOME = getenv("HOME");
if (!HOME) {
std::cerr << "DataState: no $HOME\n";
throw std::runtime_error("no $HOME");
return "";
}
const auto XDG_DATA_HOME = getenv("XDG_DATA_HOME");
if (XDG_DATA_HOME)
return std::string{XDG_DATA_HOME} + "/hyprpm";
return std::string{HOME} + "/.local/share/hyprpm";
}
void DataState::ensureStateStoreExists() {
const auto PATH = getDataStatePath();
if (!std::filesystem::exists(PATH))
std::filesystem::create_directories(PATH);
}
void DataState::addNewPluginRepo(const SPluginRepository& repo) {
ensureStateStoreExists();
const auto PATH = getDataStatePath() + "/" + repo.name;
std::filesystem::create_directories(PATH);
// clang-format off
auto DATA = toml::table{
{"repository", toml::table{
{"name", repo.name},
{"hash", repo.hash},
{"url", repo.url}
}}
};
for (auto& p : repo.plugins) {
// copy .so to the good place
std::filesystem::copy_file(p.filename, PATH + "/" + p.name + ".so");
DATA.emplace(p.name, toml::table{
{"filename", p.name + ".so"},
{"enabled", p.enabled},
{"failed", p.failed}
});
}
// clang-format on
std::ofstream ofs(PATH + "/state.toml", std::ios::trunc);
ofs << DATA;
ofs.close();
}
bool DataState::pluginRepoExists(const std::string& urlOrName) {
ensureStateStoreExists();
const auto PATH = getDataStatePath();
for (const auto& entry : std::filesystem::directory_iterator(PATH)) {
if (!entry.is_directory())
continue;
auto STATE = toml::parse_file(entry.path().string() + "/state.toml");
const auto NAME = STATE["repository"]["name"].value_or("");
const auto URL = STATE["repository"]["url"].value_or("");
if (URL == urlOrName || NAME == urlOrName)
return true;
}
return false;
}
void DataState::removePluginRepo(const std::string& urlOrName) {
ensureStateStoreExists();
const auto PATH = getDataStatePath();
for (const auto& entry : std::filesystem::directory_iterator(PATH)) {
if (!entry.is_directory())
continue;
auto STATE = toml::parse_file(entry.path().string() + "/state.toml");
const auto NAME = STATE["repository"]["name"].value_or("");
const auto URL = STATE["repository"]["url"].value_or("");
if (URL == urlOrName || NAME == urlOrName) {
// unload the plugins!!
for (const auto& file : std::filesystem::directory_iterator(entry.path())) {
if (!file.path().string().ends_with(".so"))
continue;
g_pPluginManager->loadUnloadPlugin(std::filesystem::absolute(file.path()), false);
}
std::filesystem::remove_all(entry.path());
return;
}
}
}
void DataState::updateGlobalState(const SGlobalState& state) {
ensureStateStoreExists();
const auto PATH = getDataStatePath();
std::filesystem::create_directories(PATH);
// clang-format off
auto DATA = toml::table{
{"state", toml::table{
{"hash", state.headersHashCompiled},
{"dont_warn_install", state.dontWarnInstall}
}}
};
// clang-format on
std::ofstream ofs(PATH + "/state.toml", std::ios::trunc);
ofs << DATA;
ofs.close();
}
SGlobalState DataState::getGlobalState() {
ensureStateStoreExists();
const auto PATH = getDataStatePath();
if (!std::filesystem::exists(PATH + "/state.toml"))
return SGlobalState{};
auto DATA = toml::parse_file(PATH + "/state.toml");
SGlobalState state;
state.headersHashCompiled = DATA["state"]["hash"].value_or("");
state.dontWarnInstall = DATA["state"]["dont_warn_install"].value_or(false);
return state;
}
std::vector<SPluginRepository> DataState::getAllRepositories() {
ensureStateStoreExists();
const auto PATH = getDataStatePath();
std::vector<SPluginRepository> repos;
for (const auto& entry : std::filesystem::directory_iterator(PATH)) {
if (!entry.is_directory())
continue;
auto STATE = toml::parse_file(entry.path().string() + "/state.toml");
const auto NAME = STATE["repository"]["name"].value_or("");
const auto URL = STATE["repository"]["url"].value_or("");
const auto HASH = STATE["repository"]["hash"].value_or("");
SPluginRepository repo;
repo.hash = HASH;
repo.name = NAME;
repo.url = URL;
for (const auto& [key, val] : STATE) {
if (key == "repository")
continue;
const auto ENABLED = STATE[key]["enabled"].value_or(false);
const auto FAILED = STATE[key]["failed"].value_or(false);
const auto FILENAME = STATE[key]["filename"].value_or("");
repo.plugins.push_back(SPlugin{std::string{key.str()}, FILENAME, ENABLED, FAILED});
}
repos.push_back(repo);
}
return repos;
}
bool DataState::setPluginEnabled(const std::string& name, bool enabled) {
ensureStateStoreExists();
const auto PATH = getDataStatePath();
for (const auto& entry : std::filesystem::directory_iterator(PATH)) {
if (!entry.is_directory())
continue;
auto STATE = toml::parse_file(entry.path().string() + "/state.toml");
for (const auto& [key, val] : STATE) {
if (key == "repository")
continue;
if (key.str() != name)
continue;
const auto FAILED = STATE[key]["failed"].value_or(false);
if (FAILED)
return false;
(*STATE[key].as_table()).insert_or_assign("enabled", enabled);
std::ofstream state(entry.path().string() + "/state.toml", std::ios::trunc);
state << STATE;
state.close();
return true;
}
}
return false;
}

View file

@ -0,0 +1,21 @@
#pragma once
#include <string>
#include <vector>
#include "Plugin.hpp"
struct SGlobalState {
std::string headersHashCompiled = "";
bool dontWarnInstall = false;
};
namespace DataState {
std::string getDataStatePath();
void ensureStateStoreExists();
void addNewPluginRepo(const SPluginRepository& repo);
void removePluginRepo(const std::string& urlOrName);
bool pluginRepoExists(const std::string& urlOrName);
void updateGlobalState(const SGlobalState& state);
SGlobalState getGlobalState();
bool setPluginEnabled(const std::string& name, bool enabled);
std::vector<SPluginRepository> getAllRepositories();
};

View file

@ -0,0 +1,104 @@
#include "Manifest.hpp"
#include <toml++/toml.hpp>
#include <iostream>
CManifest::CManifest(const eManifestType type, const std::string& path) {
auto manifest = toml::parse_file(path);
if (type == MANIFEST_HYPRLOAD) {
for (auto& [key, val] : manifest) {
if (key.str().ends_with(".build"))
continue;
CManifest::SManifestPlugin plugin;
plugin.name = key;
m_vPlugins.push_back(plugin);
}
for (auto& plugin : m_vPlugins) {
plugin.description = manifest[plugin.name]["description"].value_or("?");
plugin.version = manifest[plugin.name]["version"].value_or("?");
plugin.output = manifest[plugin.name]["build"]["output"].value_or("?");
auto authors = manifest[plugin.name]["authors"].as_array();
if (authors) {
for (auto&& a : *authors) {
plugin.authors.push_back(a.as_string()->value_or("?"));
}
} else {
auto author = manifest[plugin.name]["author"].value_or("");
if (!std::string{author}.empty())
plugin.authors.push_back(author);
}
auto buildSteps = manifest[plugin.name]["build"]["steps"].as_array();
if (buildSteps) {
for (auto&& s : *buildSteps) {
plugin.buildSteps.push_back(s.as_string()->value_or("?"));
}
}
if (plugin.output.empty() || plugin.buildSteps.empty()) {
m_bGood = false;
return;
}
}
} else if (type == MANIFEST_HYPRPM) {
m_sRepository.name = manifest["repository"]["name"].value_or("");
auto authors = manifest["repository"]["authors"].as_array();
if (authors) {
for (auto&& a : *authors) {
m_sRepository.authors.push_back(a.as_string()->value_or("?"));
}
} else {
auto author = manifest["repository"]["author"].value_or("");
if (!std::string{author}.empty())
m_sRepository.authors.push_back(author);
}
auto pins = manifest["repository"]["commit_pins"].as_array();
if (pins) {
for (auto&& pin : *pins) {
auto pinArr = pin.as_array();
if (pinArr && pinArr->get(1))
m_sRepository.commitPins.push_back(std::make_pair<>(pinArr->get(0)->as_string()->get(), pinArr->get(1)->as_string()->get()));
}
}
for (auto& [key, val] : manifest) {
if (key.str() == "repository")
continue;
CManifest::SManifestPlugin plugin;
plugin.name = key;
m_vPlugins.push_back(plugin);
}
for (auto& plugin : m_vPlugins) {
plugin.description = manifest[plugin.name]["description"].value_or("?");
plugin.output = manifest[plugin.name]["output"].value_or("?");
auto authors = manifest[plugin.name]["authors"].as_array();
if (authors) {
for (auto&& a : *authors) {
plugin.authors.push_back(a.as_string()->value_or("?"));
}
} else {
auto author = manifest[plugin.name]["author"].value_or("");
if (!std::string{author}.empty())
plugin.authors.push_back(author);
}
auto buildSteps = manifest[plugin.name]["build"].as_array();
if (buildSteps) {
for (auto&& s : *buildSteps) {
plugin.buildSteps.push_back(s.as_string()->value_or("?"));
}
}
if (plugin.output.empty() || plugin.buildSteps.empty()) {
m_bGood = false;
return;
}
}
} else {
// ???
m_bGood = false;
}
}

View file

@ -0,0 +1,33 @@
#pragma once
#include <string>
#include <vector>
enum eManifestType {
MANIFEST_HYPRLOAD,
MANIFEST_HYPRPM
};
class CManifest {
public:
CManifest(const eManifestType type, const std::string& path);
struct SManifestPlugin {
std::string name;
std::string description;
std::string version;
std::vector<std::string> authors;
std::vector<std::string> buildSteps;
std::string output;
bool failed = false;
};
struct {
std::string name;
std::vector<std::string> authors;
std::vector<std::pair<std::string, std::string>> commitPins;
} m_sRepository;
std::vector<SManifestPlugin> m_vPlugins;
bool m_bGood = true;
};

View file

@ -0,0 +1,18 @@
#pragma once
#include <string>
#include <vector>
struct SPlugin {
std::string name;
std::string filename;
bool enabled = false;
bool failed = false;
};
struct SPluginRepository {
std::string url;
std::string name;
std::vector<SPlugin> plugins;
std::string hash;
};

View file

@ -0,0 +1,731 @@
#include "PluginManager.hpp"
#include "../helpers/Colors.hpp"
#include "../progress/CProgressBar.hpp"
#include "Manifest.hpp"
#include "DataState.hpp"
#include <iostream>
#include <array>
#include <filesystem>
#include <thread>
#include <fstream>
#include <algorithm>
#include <toml++/toml.hpp>
static std::string removeBeginEndSpacesTabs(std::string str) {
if (str.empty())
return str;
int countBefore = 0;
while (str[countBefore] == ' ' || str[countBefore] == '\t') {
countBefore++;
}
int countAfter = 0;
while ((int)str.length() - countAfter - 1 >= 0 && (str[str.length() - countAfter - 1] == ' ' || str[str.length() - 1 - countAfter] == '\t')) {
countAfter++;
}
str = str.substr(countBefore, str.length() - countBefore - countAfter);
return str;
}
static std::string execAndGet(std::string cmd) {
cmd += " 2>&1";
std::array<char, 128> buffer;
std::string result;
const std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd.c_str(), "r"), pclose);
if (!pipe)
return "";
while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
result += buffer.data();
}
return result;
}
SHyprlandVersion CPluginManager::getHyprlandVersion() {
static SHyprlandVersion ver;
static bool once = false;
if (once)
return ver;
once = true;
const auto HLVERCALL = execAndGet("hyprctl version");
if (m_bVerbose)
std::cout << Colors::BLUE << "[v] " << Colors::RESET << "version returned: " << HLVERCALL << "\n";
if (!HLVERCALL.contains("Tag:")) {
std::cerr << "\n" << Colors::RED << "" << Colors::RESET << " You don't seem to be running Hyprland.";
return SHyprlandVersion{};
}
std::string hlcommit = HLVERCALL.substr(HLVERCALL.find("at commit") + 10);
hlcommit = hlcommit.substr(0, hlcommit.find_first_of(' '));
std::string hlbranch = HLVERCALL.substr(HLVERCALL.find("from branch") + 12);
hlbranch = hlbranch.substr(0, hlbranch.find(" at commit "));
if (m_bVerbose)
std::cout << Colors::BLUE << "[v] " << Colors::RESET << "parsed commit " << hlcommit << " at branch " << hlbranch << "\n";
ver = SHyprlandVersion{hlbranch, hlcommit};
return ver;
}
bool CPluginManager::addNewPluginRepo(const std::string& url) {
if (DataState::pluginRepoExists(url)) {
std::cerr << "\n" << Colors::RED << "" << Colors::RESET << " Could not clone the plugin repository. Repository already installed.\n";
return false;
}
auto GLOBALSTATE = DataState::getGlobalState();
if (!GLOBALSTATE.dontWarnInstall) {
std::cout << Colors::YELLOW << "!" << Colors::RED << " Disclaimer:\n " << Colors::RESET
<< "plugins, especially not official, have no guarantee of stability, availablity or security.\n Run them at your own risk.\n "
<< "This message will not appear again.\n";
GLOBALSTATE.dontWarnInstall = true;
DataState::updateGlobalState(GLOBALSTATE);
}
std::cout << Colors::GREEN << "" << Colors::RESET << Colors::RED << " adding a new plugin repository " << Colors::RESET << "from " << url << "\n " << Colors::RED
<< "MAKE SURE" << Colors::RESET << " that you trust the authors. " << Colors::RED << "DO NOT" << Colors::RESET
<< " install random plugins without verifying the code and author.\n "
<< "Are you sure? [Y/n] ";
std::fflush(stdout);
std::string input;
std::getline(std::cin, input);
if (input.size() > 0 && input[0] != 'Y' && input[0] != 'y') {
std::cout << "Aborting.\n";
return false;
}
CProgressBar progress;
progress.m_iMaxSteps = 5;
progress.m_iSteps = 0;
progress.m_szCurrentMessage = "Cloning the plugin repository";
progress.print();
if (!std::filesystem::exists("/tmp/hyprpm")) {
std::filesystem::create_directory("/tmp/hyprpm");
std::filesystem::permissions("/tmp/hyprpm", std::filesystem::perms::all, std::filesystem::perm_options::replace);
}
if (std::filesystem::exists("/tmp/hyprpm/new")) {
progress.printMessageAbove(std::string{Colors::YELLOW} + "!" + Colors::RESET + " old plugin repo build files found in temp directory, removing.");
std::filesystem::remove_all("/tmp/hyprpm/new");
}
progress.printMessageAbove(std::string{Colors::RESET} + " → Cloning " + url);
std::string ret = execAndGet("cd /tmp/hyprpm && git clone --recursive " + url + " new");
if (!std::filesystem::exists("/tmp/hyprpm/new")) {
std::cerr << "\n" << Colors::RED << "" << Colors::RESET << " Could not clone the plugin repository. shell returned:\n" << ret << "\n";
return false;
}
progress.m_iSteps = 1;
progress.printMessageAbove(std::string{Colors::GREEN} + "" + Colors::RESET + " cloned");
progress.m_szCurrentMessage = "Reading the manifest";
progress.print();
std::unique_ptr<CManifest> pManifest;
if (std::filesystem::exists("/tmp/hyprpm/new/hyprpm.toml")) {
progress.printMessageAbove(std::string{Colors::GREEN} + "" + Colors::RESET + " found hyprpm manifest");
pManifest = std::make_unique<CManifest>(MANIFEST_HYPRPM, "/tmp/hyprpm/new/hyprpm.toml");
} else if (std::filesystem::exists("/tmp/hyprpm/new/hyprload.toml")) {
progress.printMessageAbove(std::string{Colors::GREEN} + "" + Colors::RESET + " found hyprload manifest");
pManifest = std::make_unique<CManifest>(MANIFEST_HYPRLOAD, "/tmp/hyprpm/new/hyprload.toml");
}
if (!pManifest) {
std::cerr << "\n" << Colors::RED << "" << Colors::RESET << " The provided plugin repository does not have a valid manifest\n";
return false;
}
if (!pManifest->m_bGood) {
std::cerr << "\n" << Colors::RED << "" << Colors::RESET << " The provided plugin repository has a corrupted manifest\n";
return false;
}
progress.m_iSteps = 2;
progress.printMessageAbove(std::string{Colors::GREEN} + "" + Colors::RESET + " parsed manifest, found " + std::to_string(pManifest->m_vPlugins.size()) + " plugins:");
for (auto& pl : pManifest->m_vPlugins) {
std::string message = std::string{Colors::RESET} + "" + pl.name + " by ";
for (auto& a : pl.authors) {
message += a + ", ";
}
if (pl.authors.size() > 0) {
message.pop_back();
message.pop_back();
}
message += " version " + pl.version;
progress.printMessageAbove(message);
}
progress.m_szCurrentMessage = "Verifying headers";
progress.print();
const auto HEADERSSTATUS = headersValid();
if (HEADERSSTATUS != HEADERS_OK) {
std::cerr << "\n" << headerError(HEADERSSTATUS);
return false;
}
progress.m_iSteps = 3;
progress.printMessageAbove(std::string{Colors::GREEN} + "" + Colors::RESET + " Hyprland headers OK");
progress.m_szCurrentMessage = "Building plugin(s)";
progress.print();
for (auto& p : pManifest->m_vPlugins) {
std::string out;
progress.printMessageAbove(std::string{Colors::RESET} + " → Building " + p.name);
for (auto& bs : p.buildSteps) {
out += execAndGet("cd /tmp/hyprpm/new && " + bs) + "\n";
}
if (!std::filesystem::exists("/tmp/hyprpm/new/" + p.output)) {
progress.printMessageAbove(std::string{Colors::RED} + "" + Colors::RESET + " Plugin " + p.name + " failed to build.\n");
if (m_bVerbose)
std::cout << Colors::BLUE << "[v] " << Colors::RESET << "shell returned: " << out << "\n";
p.failed = true;
continue;
}
progress.printMessageAbove(std::string{Colors::GREEN} + "" + Colors::RESET + " built " + p.name + " into " + p.output);
}
progress.printMessageAbove(std::string{Colors::GREEN} + "" + Colors::RESET + " all plugins built");
progress.m_iSteps = 4;
progress.m_szCurrentMessage = "Installing repository";
progress.print();
// add repo toml to DataState
SPluginRepository repo;
std::string repohash = execAndGet("cd /tmp/hyprpm/new/ && git rev-parse HEAD");
if (repohash.length() > 0)
repohash.pop_back();
repo.name = pManifest->m_sRepository.name.empty() ? url.substr(url.find_last_of('/') + 1) : pManifest->m_sRepository.name;
repo.url = url;
repo.hash = repohash;
for (auto& p : pManifest->m_vPlugins) {
repo.plugins.push_back(SPlugin{p.name, "/tmp/hyprpm/new/" + p.output, false, p.failed});
}
DataState::addNewPluginRepo(repo);
progress.printMessageAbove(std::string{Colors::GREEN} + "" + Colors::RESET + " installed repository");
progress.printMessageAbove(std::string{Colors::GREEN} + "" + Colors::RESET + " you can now enable the plugin(s) with hyprpm enable");
progress.m_iSteps = 5;
progress.m_szCurrentMessage = "Done!";
progress.print();
std::cout << "\n";
// remove build files
std::filesystem::remove_all("/tmp/hyprpm/new");
return true;
}
bool CPluginManager::removePluginRepo(const std::string& urlOrName) {
if (!DataState::pluginRepoExists(urlOrName)) {
std::cerr << "\n" << Colors::RED << "" << Colors::RESET << " Could not remove the repository. Repository is not installed.\n";
return false;
}
std::cout << Colors::YELLOW << "!" << Colors::RESET << Colors::RED << " removing a plugin repository: " << Colors::RESET << urlOrName << "\n "
<< "Are you sure? [Y/n] ";
std::fflush(stdout);
std::string input;
std::getline(std::cin, input);
if (input.size() > 0 && input[0] != 'Y' && input[0] != 'y') {
std::cout << "Aborting.\n";
return false;
}
DataState::removePluginRepo(urlOrName);
return true;
}
eHeadersErrors CPluginManager::headersValid() {
const auto HLVER = getHyprlandVersion();
// find headers commit
auto headers = execAndGet("pkg-config --cflags --keep-system-cflags hyprland");
if (!headers.contains("-I/"))
return HEADERS_MISSING;
headers.pop_back(); // pop newline
std::string verHeader = "";
while (!headers.empty()) {
const auto PATH = headers.substr(0, headers.find(" -I/", 3));
if (headers.find(" -I/", 3) != std::string::npos)
headers = headers.substr(headers.find("-I/", 3));
else
headers = "";
if (PATH.ends_with("protocols") || PATH.ends_with("wlroots"))
continue;
verHeader = removeBeginEndSpacesTabs(PATH.substr(2)) + "/hyprland/src/version.h";
break;
}
if (verHeader.empty())
return HEADERS_CORRUPTED;
// read header
std::ifstream ifs(verHeader);
if (!ifs.good())
return HEADERS_CORRUPTED;
if ((std::filesystem::exists("/usr/include/hyprland/src/version.h") && verHeader != "/usr/include/hyprland/src/version.h") ||
(std::filesystem::exists("/usr/local/include/hyprland/src/version.h") && verHeader != "/usr/local/include/hyprland/src/version.h"))
return HEADERS_DUPLICATED;
std::string verHeaderContent((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
ifs.close();
std::string hash = verHeaderContent.substr(verHeaderContent.find("#define GIT_COMMIT_HASH") + 23);
hash = hash.substr(0, hash.find_first_of('\n'));
hash = hash.substr(hash.find_first_of('"') + 1);
hash = hash.substr(0, hash.find_first_of('"'));
if (hash != HLVER.hash)
return HEADERS_MISMATCHED;
return HEADERS_OK;
}
bool CPluginManager::updateHeaders() {
const auto HLVER = getHyprlandVersion();
if (!std::filesystem::exists("/tmp/hyprpm")) {
std::filesystem::create_directory("/tmp/hyprpm");
std::filesystem::permissions("/tmp/hyprpm", std::filesystem::perms::all, std::filesystem::perm_options::replace);
}
if (headersValid() == HEADERS_OK) {
std::cout << "\n" << std::string{Colors::GREEN} + "" + Colors::RESET + " Your headers are already up-to-date.\n";
auto GLOBALSTATE = DataState::getGlobalState();
GLOBALSTATE.headersHashCompiled = HLVER.hash;
DataState::updateGlobalState(GLOBALSTATE);
return true;
}
CProgressBar progress;
progress.m_iMaxSteps = 5;
progress.m_iSteps = 0;
progress.m_szCurrentMessage = "Cloning the hyprland repository";
progress.print();
if (std::filesystem::exists("/tmp/hyprpm/hyprland")) {
progress.printMessageAbove(std::string{Colors::YELLOW} + "!" + Colors::RESET + " old hyprland source files found in temp directory, removing.");
std::filesystem::remove_all("/tmp/hyprpm/hyprland");
}
progress.printMessageAbove(std::string{Colors::YELLOW} + "!" + Colors::RESET + " Cloning https://github.com/hyprwm/hyprland, this might take a moment.");
std::string ret = execAndGet("cd /tmp/hyprpm && git clone --recursive https://github.com/hyprwm/hyprland hyprland");
if (!std::filesystem::exists("/tmp/hyprpm/hyprland")) {
std::cerr << "\n" << Colors::RED << "" << Colors::RESET << " Could not clone the hyprland repository. shell returned:\n" << ret << "\n";
return false;
}
progress.printMessageAbove(std::string{Colors::GREEN} + "" + Colors::RESET + " cloned");
progress.m_iSteps = 2;
progress.m_szCurrentMessage = "Checking out sources";
progress.print();
ret =
execAndGet("cd /tmp/hyprpm/hyprland && git checkout " + HLVER.branch + " 2>&1 && git submodule update --init 2>&1 && git reset --hard --recurse-submodules " + HLVER.hash);
if (m_bVerbose)
progress.printMessageAbove(std::string{Colors::BLUE} + "[v] " + Colors::RESET + "git returned: " + ret);
progress.printMessageAbove(std::string{Colors::GREEN} + "" + Colors::RESET + " checked out to running ver");
progress.m_iSteps = 3;
progress.m_szCurrentMessage = "Building Hyprland";
progress.print();
progress.printMessageAbove(std::string{Colors::YELLOW} + "!" + Colors::RESET + " configuring Hyprland");
ret = execAndGet("cd /tmp/hyprpm/hyprland && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -S . -B ./build -G Ninja");
// le hack. Wlroots has to generate its build/include
ret = execAndGet("cd /tmp/hyprpm/hyprland/subprojects/wlroots && meson setup -Drenderers=gles2 -Dexamples=false build");
progress.printMessageAbove(std::string{Colors::GREEN} + "" + Colors::RESET + " configured Hyprland");
progress.m_iSteps = 4;
progress.m_szCurrentMessage = "Installing sources";
progress.print();
progress.printMessageAbove(
std::string{Colors::YELLOW} + "!" + Colors::RESET +
" in order to install the sources, you will need to input your password.\n If nothing pops up, make sure you have polkit and an authentication daemon running.");
ret = execAndGet("pkexec sh \"-c\" \"cd /tmp/hyprpm/hyprland && make installheaders\"");
if (m_bVerbose)
std::cout << Colors::BLUE << "[v] " << Colors::RESET << "pkexec returned: " << ret << "\n";
// remove build files
std::filesystem::remove_all("/tmp/hyprpm/hyprland");
auto HEADERSVALID = headersValid();
if (HEADERSVALID == HEADERS_OK) {
progress.printMessageAbove(std::string{Colors::GREEN} + "" + Colors::RESET + " installed headers");
progress.m_iSteps = 5;
progress.m_szCurrentMessage = "Done!";
progress.print();
auto GLOBALSTATE = DataState::getGlobalState();
GLOBALSTATE.headersHashCompiled = HLVER.hash;
DataState::updateGlobalState(GLOBALSTATE);
std::cout << "\n";
} else {
progress.printMessageAbove(std::string{Colors::RED} + "" + Colors::RESET + " failed to install headers with error code " + std::to_string((int)HEADERSVALID));
progress.m_iSteps = 5;
progress.m_szCurrentMessage = "Failed";
progress.print();
std::cout << "\n";
std::cerr << "\n" << headerError(HEADERSVALID);
return false;
}
return true;
}
bool CPluginManager::updatePlugins(bool forceUpdateAll) {
if (headersValid() != HEADERS_OK) {
std::cout << "\n" << std::string{Colors::RED} + "" + Colors::RESET + " headers are not up-to-date, please run hyprpm update.\n";
return false;
}
const auto REPOS = DataState::getAllRepositories();
if (REPOS.size() < 1) {
std::cout << "\n" << std::string{Colors::RED} + "" + Colors::RESET + " No repos to update.\n";
return true;
}
const auto HLVER = getHyprlandVersion();
CProgressBar progress;
progress.m_iMaxSteps = REPOS.size() * 2 + 1;
progress.m_iSteps = 0;
progress.m_szCurrentMessage = "Updating repositories";
progress.print();
for (auto& repo : REPOS) {
bool update = forceUpdateAll;
progress.m_iSteps++;
progress.m_szCurrentMessage = "Updating " + repo.name;
progress.print();
progress.printMessageAbove(std::string{Colors::RESET} + " → checking for updates for " + repo.name);
if (std::filesystem::exists("/tmp/hyprpm/update")) {
progress.printMessageAbove(std::string{Colors::YELLOW} + "!" + Colors::RESET + " old update build files found in temp directory, removing.");
std::filesystem::remove_all("/tmp/hyprpm/update");
}
progress.printMessageAbove(std::string{Colors::RESET} + " → Cloning " + repo.url);
std::string ret = execAndGet("cd /tmp/hyprpm && git clone --recursive " + repo.url + " update");
if (!std::filesystem::exists("/tmp/hyprpm/update")) {
std::cout << "\n" << std::string{Colors::RED} + "" + Colors::RESET + " could not clone repo: shell returned:\n" + ret;
return false;
}
if (!update) {
// check if git has updates
std::string hash = execAndGet("cd /tmp/hyprpm/update && git rev-parse HEAD");
if (!hash.empty())
hash.pop_back();
update = update || hash != repo.hash;
}
if (!update) {
std::filesystem::remove_all("/tmp/hyprpm/update");
progress.printMessageAbove(std::string{Colors::GREEN} + "" + Colors::RESET + " repository " + repo.name + " is up-to-date.");
progress.m_iSteps++;
progress.print();
continue;
}
// we need to update
progress.printMessageAbove(std::string{Colors::GREEN} + "" + Colors::RESET + " repository " + repo.name + " has updates.");
progress.printMessageAbove(std::string{Colors::RESET} + " → Building " + repo.name);
progress.m_iSteps++;
progress.print();
std::unique_ptr<CManifest> pManifest;
if (std::filesystem::exists("/tmp/hyprpm/update/hyprpm.toml")) {
progress.printMessageAbove(std::string{Colors::GREEN} + "" + Colors::RESET + " found hyprpm manifest");
pManifest = std::make_unique<CManifest>(MANIFEST_HYPRPM, "/tmp/hyprpm/update/hyprpm.toml");
} else if (std::filesystem::exists("/tmp/hyprpm/update/hyprload.toml")) {
progress.printMessageAbove(std::string{Colors::GREEN} + "" + Colors::RESET + " found hyprload manifest");
pManifest = std::make_unique<CManifest>(MANIFEST_HYPRLOAD, "/tmp/hyprpm/update/hyprload.toml");
}
if (!pManifest) {
std::cerr << "\n" << Colors::RED << "" << Colors::RESET << " The provided plugin repository does not have a valid manifest\n";
continue;
}
if (!pManifest->m_bGood) {
std::cerr << "\n" << Colors::RED << "" << Colors::RESET << " The provided plugin repository has a corrupted manifest\n";
continue;
}
if (!pManifest->m_sRepository.commitPins.empty()) {
// check commit pins
progress.printMessageAbove(std::string{Colors::RESET} + " → Manifest has " + std::to_string(pManifest->m_sRepository.commitPins.size()) + " pins, checking");
for (auto& [hl, plugin] : pManifest->m_sRepository.commitPins) {
if (hl != HLVER.hash)
continue;
progress.printMessageAbove(std::string{Colors::GREEN} + "" + Colors::RESET + " commit pin " + plugin + " matched hl, resetting");
execAndGet("cd /tmp/hyprpm/update/ && git reset --hard --recurse-submodules " + plugin);
}
}
bool failed = false;
for (auto& p : pManifest->m_vPlugins) {
std::string out;
progress.printMessageAbove(std::string{Colors::RESET} + " → Building " + p.name);
for (auto& bs : p.buildSteps) {
out += execAndGet("cd /tmp/hyprpm/update && " + bs) + "\n";
}
if (!std::filesystem::exists("/tmp/hyprpm/update/" + p.output)) {
std::cerr << "\n" << Colors::RED << "" << Colors::RESET << " Plugin " << p.name << " failed to build.\n";
failed = true;
if (m_bVerbose)
std::cout << Colors::BLUE << "[v] " << Colors::RESET << "shell returned: " << out << "\n";
break;
}
progress.printMessageAbove(std::string{Colors::GREEN} + "" + Colors::RESET + " built " + p.name + " into " + p.output);
}
if (failed)
continue;
// add repo toml to DataState
SPluginRepository newrepo = repo;
newrepo.plugins.clear();
execAndGet(
"cd /tmp/hyprpm/update/ && git pull --recurse-submodules && git reset --hard --recurse-submodules"); // repo hash in the state.toml has to match head and not any pin
std::string repohash = execAndGet("cd /tmp/hyprpm/update && git rev-parse HEAD");
if (repohash.length() > 0)
repohash.pop_back();
newrepo.hash = repohash;
for (auto& p : pManifest->m_vPlugins) {
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, "/tmp/hyprpm/update/" + p.output, OLDPLUGINIT != repo.plugins.end() ? OLDPLUGINIT->enabled : false});
}
DataState::removePluginRepo(newrepo.name);
DataState::addNewPluginRepo(newrepo);
std::filesystem::remove_all("/tmp/hyprpm/update");
progress.printMessageAbove(std::string{Colors::GREEN} + "" + Colors::RESET + " updated " + repo.name);
}
progress.m_iSteps++;
progress.m_szCurrentMessage = "Done!";
progress.print();
std::cout << "\n";
return true;
}
bool CPluginManager::enablePlugin(const std::string& name) {
bool ret = DataState::setPluginEnabled(name, true);
if (ret)
std::cout << Colors::GREEN << "" << Colors::RESET << " Enabled " << name << "\n";
return ret;
}
bool CPluginManager::disablePlugin(const std::string& name) {
bool ret = DataState::setPluginEnabled(name, false);
if (ret)
std::cout << Colors::GREEN << "" << Colors::RESET << " Disabled " << name << "\n";
return ret;
}
ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState() {
if (headersValid() != HEADERS_OK) {
std::cerr << "\n" << std::string{Colors::RED} + "" + Colors::RESET + " headers are not up-to-date, please run hyprpm update.\n";
return LOADSTATE_HEADERS_OUTDATED;
}
const auto HOME = getenv("HOME");
const auto HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE");
if (!HOME || !HIS) {
std::cerr << "PluginManager: no $HOME or HIS\n";
return LOADSTATE_FAIL;
}
const auto HYPRPMPATH = DataState::getDataStatePath() + "/";
auto pluginLines = execAndGet("hyprctl plugins list | grep Plugin");
std::vector<std::string> loadedPlugins;
std::cout << Colors::GREEN << "" << Colors::RESET << " Ensuring plugin load state\n";
// iterate line by line
while (!pluginLines.empty()) {
auto plLine = pluginLines.substr(0, pluginLines.find("\n"));
if (pluginLines.find("\n") != std::string::npos)
pluginLines = pluginLines.substr(pluginLines.find("\n") + 1);
else
pluginLines = "";
if (plLine.back() != ':')
continue;
plLine = plLine.substr(7);
plLine = plLine.substr(0, plLine.find(" by "));
loadedPlugins.push_back(plLine);
}
// get state
const auto REPOS = DataState::getAllRepositories();
auto enabled = [REPOS](const std::string& plugin) -> bool {
for (auto& r : REPOS) {
for (auto& p : r.plugins) {
if (p.name == plugin && p.enabled)
return true;
}
}
return false;
};
auto repoForName = [REPOS](const std::string& name) -> std::string {
for (auto& r : REPOS) {
for (auto& p : r.plugins) {
if (p.name == name)
return r.name;
}
}
return "";
};
// unload disabled plugins
for (auto& p : loadedPlugins) {
if (!enabled(p)) {
// unload
loadUnloadPlugin(HYPRPMPATH + repoForName(p) + "/" + p + ".so", false);
std::cout << Colors::GREEN << "" << Colors::RESET << " Unloaded " << p << "\n";
}
}
// load enabled plugins
for (auto& r : REPOS) {
for (auto& p : r.plugins) {
if (!p.enabled)
continue;
if (std::find_if(loadedPlugins.begin(), loadedPlugins.end(), [&](const auto& other) { return other == p.name; }) != loadedPlugins.end())
continue;
loadUnloadPlugin(HYPRPMPATH + repoForName(p.name) + "/" + p.filename, true);
std::cout << Colors::GREEN << "" << Colors::RESET << " Loaded " << p.name << "\n";
}
}
std::cout << Colors::GREEN << "" << Colors::RESET << " Plugin load state ensured\n";
return LOADSTATE_OK;
}
bool CPluginManager::loadUnloadPlugin(const std::string& path, bool load) {
if (load)
execAndGet("hyprctl plugin load " + path);
else
execAndGet("hyprctl plugin unload " + path);
return true;
}
void CPluginManager::listAllPlugins() {
const auto REPOS = DataState::getAllRepositories();
for (auto& r : REPOS) {
std::cout << std::string{Colors::RESET} + " → Repository " + r.name + ":\n";
for (auto& p : r.plugins) {
std::cout << std::string{Colors::RESET} + " │ Plugin " + p.name;
if (!p.failed)
std::cout << "\n └─ enabled: " << (p.enabled ? Colors::GREEN : Colors::RED) << (p.enabled ? "true" : "false") << Colors::RESET << "\n";
else
std::cout << "\n └─ enabled: " << Colors::RED << "Plugin failed to build" << Colors::RESET << "\n";
}
}
}
void CPluginManager::notify(const eNotifyIcons icon, uint32_t color, int durationMs, const std::string& message) {
execAndGet("hyprctl notify " + std::to_string((int)icon) + " " + std::to_string(durationMs) + " " + std::to_string(color) + " " + message);
}
std::string CPluginManager::headerError(const eHeadersErrors err) {
switch (err) {
case HEADERS_CORRUPTED: return std::string{Colors::RED} + "" + Colors::RESET + " Headers corrupted. Please run hyprpm update to fix those.\n";
case HEADERS_MISMATCHED: return std::string{Colors::RED} + "" + Colors::RESET + " Headers version mismatch. Please run hyprpm update to fix those.\n";
case HEADERS_NOT_HYPRLAND: return std::string{Colors::RED} + "" + Colors::RESET + " It doesn't seem you are running on hyprland.\n";
case HEADERS_MISSING: return std::string{Colors::RED} + "" + Colors::RESET + " Headers missing. Please run hyprpm update to fix those.\n";
case HEADERS_DUPLICATED: {
return std::string{Colors::RED} + "" + Colors::RESET + " 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" +
" If the above doesn't apply, check your /usr/include and /usr/local/include directories\n and remove all the hyprland headers.\n";
}
default: break;
}
return std::string{Colors::RED} + "" + Colors::RESET + " Unknown header error. Please run hyprpm update to fix those.\n";
}

View file

@ -0,0 +1,63 @@
#pragma once
#include <memory>
#include <string>
enum eHeadersErrors {
HEADERS_OK = 0,
HEADERS_NOT_HYPRLAND,
HEADERS_MISSING,
HEADERS_CORRUPTED,
HEADERS_MISMATCHED,
HEADERS_DUPLICATED
};
enum eNotifyIcons {
ICON_WARNING = 0,
ICON_INFO,
ICON_HINT,
ICON_ERROR,
ICON_CONFUSED,
ICON_OK,
ICON_NONE
};
enum ePluginLoadStateReturn {
LOADSTATE_OK = 0,
LOADSTATE_FAIL,
LOADSTATE_PARTIAL_FAIL,
LOADSTATE_HEADERS_OUTDATED
};
struct SHyprlandVersion {
std::string branch;
std::string hash;
};
class CPluginManager {
public:
bool addNewPluginRepo(const std::string& url);
bool removePluginRepo(const std::string& urlOrName);
eHeadersErrors headersValid();
bool updateHeaders();
bool updatePlugins(bool forceUpdateAll);
void listAllPlugins();
bool enablePlugin(const std::string& name);
bool disablePlugin(const std::string& name);
ePluginLoadStateReturn ensurePluginsLoadState();
bool loadUnloadPlugin(const std::string& path, bool load);
SHyprlandVersion getHyprlandVersion();
void notify(const eNotifyIcons icon, uint32_t color, int durationMs, const std::string& message);
bool m_bVerbose = false;
private:
std::string headerError(const eHeadersErrors err);
};
inline std::unique_ptr<CPluginManager> g_pPluginManager;

View file

@ -0,0 +1,11 @@
#pragma once
namespace Colors {
constexpr const char* RED = "\x1b[31m";
constexpr const char* GREEN = "\x1b[32m";
constexpr const char* YELLOW = "\x1b[33m";
constexpr const char* BLUE = "\x1b[34m";
constexpr const char* MAGENTA = "\x1b[35m";
constexpr const char* CYAN = "\x1b[36m";
constexpr const char* RESET = "\x1b[0m";
};

149
hyprpm/src/main.cpp Normal file
View file

@ -0,0 +1,149 @@
#include "progress/CProgressBar.hpp"
#include "helpers/Colors.hpp"
#include "core/PluginManager.hpp"
#include <iostream>
#include <vector>
#include <string>
#include <chrono>
#include <thread>
const std::string HELP = R"#(┏ hyprpm, a Hyprland Plugin Manager
add [url] Install a new plugin repository from git
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
Flags:
--notify | -n Send a hyprland notification for important events (e.g. load fail)
--help | -h Show this menu
--verbose | -v Enable too much logging
)#";
int main(int argc, char** argv, char** envp) {
std::vector<std::string> ARGS{argc};
for (int i = 0; i < argc; ++i) {
ARGS[i] = std::string{argv[i]};
}
if (ARGS.size() < 2) {
std::cout << HELP;
return 1;
}
std::vector<std::string> command;
bool notify = false, verbose = false;
for (int i = 1; i < argc; ++i) {
if (ARGS[i].starts_with("-")) {
if (ARGS[i] == "--help" || ARGS[i] == "-h") {
std::cout << HELP;
return 0;
} else if (ARGS[i] == "--notify" || ARGS[i] == "-n") {
notify = true;
} else if (ARGS[i] == "--verbose" || ARGS[i] == "-v") {
verbose = true;
} else {
std::cerr << "Unrecognized option " << ARGS[i];
return 1;
}
} else {
command.push_back(ARGS[i]);
}
}
if (command.empty()) {
std::cout << HELP;
return 0;
}
g_pPluginManager = std::make_unique<CPluginManager>();
g_pPluginManager->m_bVerbose = verbose;
if (command[0] == "add") {
if (command.size() < 2) {
std::cerr << Colors::RED << "" << Colors::RESET << " Not enough args for add.\n";
return 1;
}
return g_pPluginManager->addNewPluginRepo(command[1]) ? 0 : 1;
} else if (command[0] == "remove") {
if (ARGS.size() < 2) {
std::cerr << Colors::RED << "" << Colors::RESET << " Not enough args for remove.\n";
return 1;
}
return g_pPluginManager->removePluginRepo(command[1]) ? 0 : 1;
} else if (command[0] == "update") {
bool headersValid = g_pPluginManager->headersValid() == HEADERS_OK;
bool headers = g_pPluginManager->updateHeaders();
if (headers) {
bool ret1 = g_pPluginManager->updatePlugins(!headersValid);
if (!ret1)
return 1;
auto ret2 = g_pPluginManager->ensurePluginsLoadState();
if (ret2 != LOADSTATE_OK)
return 1;
} else if (notify)
g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Couldn't update headers");
} else if (command[0] == "enable") {
if (ARGS.size() < 2) {
std::cerr << Colors::RED << "" << Colors::RESET << " Not enough args for enable.\n";
return 1;
}
if (!g_pPluginManager->enablePlugin(command[1])) {
std::cerr << Colors::RED << "" << Colors::RESET << " Couldn't enable plugin (missing?)\n";
return 1;
}
auto ret = g_pPluginManager->ensurePluginsLoadState();
if (ret != LOADSTATE_OK)
return 1;
} else if (command[0] == "disable") {
if (command.size() < 2) {
std::cerr << Colors::RED << "" << Colors::RESET << " Not enough args for disable.\n";
return 1;
}
if (!g_pPluginManager->disablePlugin(command[1])) {
std::cerr << Colors::RED << "" << Colors::RESET << " Couldn't disable plugin (missing?)\n";
return 1;
}
auto ret = g_pPluginManager->ensurePluginsLoadState();
if (ret != LOADSTATE_OK)
return 1;
} else if (command[0] == "reload") {
auto ret = g_pPluginManager->ensurePluginsLoadState();
if (ret != LOADSTATE_OK && notify) {
switch (ret) {
case LOADSTATE_FAIL:
case LOADSTATE_PARTIAL_FAIL: g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Failed to load plugins"); break;
case LOADSTATE_HEADERS_OUTDATED:
g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Failed to load plugins: Outdated headers. Please run hyprpm update manually.");
break;
default: break;
}
} else if (notify) {
g_pPluginManager->notify(ICON_OK, 0, 4000, "[hyprpm] Loaded plugins");
}
} else if (command[0] == "list") {
g_pPluginManager->listAllPlugins();
} else {
std::cout << HELP;
return 1;
}
return 0;
}

10
hyprpm/src/meson.build Normal file
View file

@ -0,0 +1,10 @@
globber = run_command('sh', '-c', 'find . -name "*.cpp" | sort', check: true)
src = globber.stdout().strip().split('\n')
executable('hyprpm', src,
dependencies: [
dependency('threads'),
dependency('tomlplusplus')
],
install : true
)

View file

@ -0,0 +1,80 @@
#include "CProgressBar.hpp"
#include <iostream>
#include <algorithm>
#include <cmath>
#include <format>
#include <sys/ioctl.h>
#include <stdio.h>
#include <unistd.h>
#include "../helpers/Colors.hpp"
void CProgressBar::printMessageAbove(const std::string& msg) {
struct winsize w;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
std::string spaces;
for (size_t i = 0; i < w.ws_col; ++i) {
spaces += ' ';
}
std::cout << "\r" << spaces << "\r" << msg << "\n";
print();
}
void CProgressBar::print() {
struct winsize w;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
if (m_bFirstPrint)
std::cout << "\n";
m_bFirstPrint = false;
std::string spaces;
for (size_t i = 0; i < w.ws_col; ++i) {
spaces += ' ';
}
std::cout << "\r" << spaces << "\r";
std::string message = "";
float percentDone = 0;
if (m_fPercentage >= 0)
percentDone = m_fPercentage;
else
percentDone = (float)m_iSteps / (float)m_iMaxSteps;
const auto BARWIDTH = std::clamp(w.ws_col - static_cast<unsigned long>(m_szCurrentMessage.length()) - 2, 0UL, 50UL);
// draw bar
message += std::string{" "} + Colors::GREEN;
size_t i = 0;
for (; i < std::floor(percentDone * BARWIDTH); ++i) {
message += "";
}
if (i < BARWIDTH) {
i++;
message += std::string{""} + Colors::RESET;
for (; i < BARWIDTH; ++i) {
message += "";
}
} else
message += Colors::RESET;
// draw progress
if (m_fPercentage >= 0)
message += " " + std::format("{}%", static_cast<int>(percentDone * 100.0)) + " ";
else
message += " " + std::format("{} / {}", m_iSteps, m_iMaxSteps) + " ";
// draw message
std::cout << message + " " + m_szCurrentMessage;
std::fflush(stdout);
}

View file

@ -0,0 +1,17 @@
#pragma once
#include <string>
class CProgressBar {
public:
void print();
void printMessageAbove(const std::string& msg);
std::string m_szCurrentMessage = "";
size_t m_iSteps = 0;
size_t m_iMaxSteps = 0;
float m_fPercentage = -1; // if != -1, use percentage
private:
bool m_bFirstPrint = true;
};

View file

@ -20,25 +20,19 @@ else
error('Could not configure current C++ compiler (' + cpp_compiler.get_id() + ' ' + cpp_compiler.version() + ') with required C++ standard (C++23)')
endif
GIT_BRANCH = run_command('git', 'rev-parse', '--abbrev-ref', 'HEAD', check: false).stdout().strip()
GIT_COMMIT_HASH = run_command('git', 'rev-parse', 'HEAD', check: false).stdout().strip()
GIT_COMMIT_MESSAGE = run_command('sh', '-c', 'git show | head -n 5 | tail -n 1', check: false).stdout().strip()
GIT_DIRTY = run_command('sh', '-c', 'git diff-index --quiet HEAD -- || echo "dirty"', check: false).stdout().strip()
add_project_arguments(
[
'-Wno-unused-parameter',
'-Wno-unused-value',
'-Wno-missing-field-initializers',
'-Wno-narrowing',
f'-DGIT_BRANCH="@GIT_BRANCH@"',
f'-DGIT_COMMIT_HASH="@GIT_COMMIT_HASH@"',
f'-DGIT_COMMIT_MESSAGE="@GIT_COMMIT_MESSAGE@"',
f'-DGIT_DIRTY="@GIT_DIRTY@"',
],
language: 'cpp')
if cpp_compiler.check_header('execinfo.h')
add_project_arguments('-DHAS_EXECINFO', language: 'cpp')
endif
wlroots = subproject('wlroots', default_options: ['examples=false', 'renderers=gles2'])
have_xwlr = wlroots.get_variable('features').get('xwayland')
xcb_dep = dependency('xcb', required: get_option('xwayland'))
@ -75,6 +69,8 @@ if get_option('buildtype') == 'debug'
add_project_arguments('-DHYPRLAND_DEBUG', language: 'cpp')
endif
version_h = run_command('sh', '-c', 'scripts/generateVersion.sh')
globber = run_command('find', 'src', '-name', '*.h*', check: true)
headers = globber.stdout().strip().split('\n')
foreach file : headers
@ -84,6 +80,7 @@ endforeach
subdir('protocols')
subdir('src')
subdir('hyprctl')
subdir('hyprpm/src')
subdir('assets')
subdir('example')
subdir('docs')

View file

@ -10,6 +10,7 @@
git,
hyprland-protocols,
jq,
libGL,
libdrm,
libinput,
libxcb,
@ -18,6 +19,7 @@
pango,
pciutils,
systemd,
tomlplusplus,
udis86,
wayland,
wayland-protocols,
@ -26,21 +28,23 @@
xcbutilwm,
xwayland,
debug ? false,
enableNvidiaPatches ? false,
enableXWayland ? true,
legacyRenderer ? false,
withSystemd ? lib.meta.availableOn stdenv.hostPlatform systemd,
wrapRuntimeDeps ? true,
version ? "git",
commit,
date,
# deprecated flags
enableNvidiaPatches ? false,
nvidiaPatches ? false,
hidpiXWayland ? false,
}:
assert lib.assertMsg (!nvidiaPatches) "The option `nvidiaPatches` has been renamed `enableNvidiaPatches`";
assert lib.assertMsg (!nvidiaPatches) "The option `nvidiaPatches` has been removed.";
assert lib.assertMsg (!enableNvidiaPatches) "The option `enableNvidiaPatches` has been removed.";
assert lib.assertMsg (!hidpiXWayland) "The option `hidpiXWayland` has been removed. Please refer https://wiki.hyprland.org/Configuring/XWayland";
stdenv.mkDerivation {
pname = "hyprland${lib.optionalString enableNvidiaPatches "-nvidia"}${lib.optionalString debug "-debug"}";
pname = "hyprland${lib.optionalString debug "-debug"}";
inherit version;
src = lib.cleanSourceWith {
@ -68,19 +72,21 @@ assert lib.assertMsg (!hidpiXWayland) "The option `hidpiXWayland` has been remov
buildInputs =
[
git
cairo
git
hyprland-protocols
libdrm
libGL
libinput
libxkbcommon
mesa
pango
pciutils
tomlplusplus
udis86
wayland
wayland-protocols
pciutils
(wlroots.override {inherit enableNvidiaPatches;})
wlroots
]
++ lib.optionals enableXWayland [libxcb xcbutilwm xwayland]
++ lib.optionals withSystemd [systemd];
@ -90,8 +96,9 @@ assert lib.assertMsg (!hidpiXWayland) "The option `hidpiXWayland` has been remov
then "debug"
else "release";
mesonAutoFeatures = "disabled";
mesonFlags = builtins.concatLists [
["-Dauto_features=disabled"]
(lib.optional enableXWayland "-Dxwayland=enabled")
(lib.optional legacyRenderer "-Dlegacy_renderer=enabled")
(lib.optional withSystemd "-Dsystemd=enabled")
@ -105,9 +112,16 @@ assert lib.assertMsg (!hidpiXWayland) "The option `hidpiXWayland` has been remov
postPatch = ''
# Fix hardcoded paths to /usr installation
sed -i "s#/usr#$out#" src/render/OpenGL.cpp
substituteInPlace meson.build \
--replace "@GIT_COMMIT_HASH@" '${commit}' \
--replace "@GIT_DIRTY@" '${
# Generate version.h
cp src/version.h.in src/version.h
substituteInPlace src/version.h \
--replace "@HASH@" '${commit}' \
--replace "@BRANCH@" "" \
--replace "@MESSAGE@" "" \
--replace "@DATE@" "${date}" \
--replace "@TAG@" "" \
--replace "@DIRTY@" '${
if commit == ""
then "dirty"
else ""
@ -118,7 +132,11 @@ assert lib.assertMsg (!hidpiXWayland) "The option `hidpiXWayland` has been remov
ln -s ${wlroots}/include/wlr $dev/include/hyprland/wlroots
${lib.optionalString wrapRuntimeDeps ''
wrapProgram $out/bin/Hyprland \
--suffix PATH : ${lib.makeBinPath [binutils pciutils]}
--suffix PATH : ${lib.makeBinPath [
stdenv.cc
binutils
pciutils
]}
''}
'';

View file

@ -4,174 +4,11 @@ self: {
pkgs,
...
}: let
cfg = config.wayland.windowManager.hyprland;
defaultHyprlandPackage = self.packages.${pkgs.stdenv.hostPlatform.system}.default.override {
enableXWayland = cfg.xwayland.enable;
inherit (cfg) enableNvidiaPatches;
};
inherit (pkgs.stdenv.hostPlatform) system;
package = self.packages.${system}.default;
in {
disabledModules = ["services/window-managers/hyprland.nix"];
meta.maintainers = [lib.maintainers.fufexan];
options.wayland.windowManager.hyprland = {
enable =
lib.mkEnableOption null
// {
description = lib.mdDoc ''
Whether to enable Hyprland, the dynamic tiling Wayland compositor
that doesn't sacrifice on its looks.
You can manually launch Hyprland by executing {command}`Hyprland` on
a TTY.
See <https://wiki.hyprland.org> for more information.
'';
};
package = lib.mkOption {
type = with lib.types; nullOr package;
default = defaultHyprlandPackage;
defaultText = lib.literalExpression ''
hyprland.packages.''${pkgs.stdenv.hostPlatform.system}.default.override {
enableXWayland = config.wayland.windowManager.hyprland.xwayland.enable;
inherit (config.wayland.windowManager.hyprland) enableNvidiaPatches;
}
'';
description = lib.mdDoc ''
Hyprland package to use. Will override the 'xwayland' and
'enableNvidiaPatches' options.
Defaults to the one provided by the flake. Set it to
{package}`pkgs.hyprland` to use the one provided by nixpkgs or
if you have an overlay.
Set to null to not add any Hyprland package to your path. This should
be done if you want to use the NixOS module to install Hyprland.
'';
};
plugins = lib.mkOption {
type = with lib.types; listOf (either package path);
default = [];
description = lib.mdDoc ''
List of Hyprland plugins to use. Can either be packages or
absolute plugin paths.
'';
};
systemdIntegration = lib.mkOption {
type = lib.types.bool;
default = pkgs.stdenv.isLinux;
description = lib.mdDoc ''
Whether to enable {file}`hyprland-session.target` on
Hyprland startup. This links to {file}`graphical-session.target`.
Some important environment variables will be imported to systemd
and dbus user environment before reaching the target, including
- {env}`DISPLAY`
- {env}`HYPRLAND_INSTANCE_SIGNATURE`
- {env}`WAYLAND_DISPLAY`
- {env}`XDG_CURRENT_DESKTOP`
'';
};
disableAutoreload =
lib.mkEnableOption null
// {
description = lib.mdDoc ''
Whether to disable automatically reloading Hyprland's configuration when
rebuilding the Home Manager profile.
'';
};
xwayland.enable = lib.mkEnableOption (lib.mdDoc "XWayland") // {default = true;};
enableNvidiaPatches = lib.mkEnableOption (lib.mdDoc "patching wlroots for better Nvidia support.");
extraConfig = lib.mkOption {
type = lib.types.nullOr lib.types.lines;
default = "";
description = lib.mdDoc ''
Extra configuration lines to add to {file}`~/.config/hypr/hyprland.conf`.
'';
};
recommendedEnvironment =
lib.mkEnableOption null
// {
description = lib.mdDoc ''
Whether to set the recommended environment variables.
'';
};
config = {
wayland.windowManager.hyprland.package = lib.mkDefault package;
};
config = lib.mkIf cfg.enable {
warnings =
if (cfg.systemdIntegration || cfg.plugins != []) && cfg.extraConfig == null
then [
''
You have enabled hyprland.systemdIntegration or listed plugins in hyprland.plugins.
Your Hyprland config will be linked by home manager.
Set hyprland.extraConfig or unset hyprland.systemdIntegration and hyprland.plugins to remove this warning.
''
]
else [];
home.packages =
lib.optional (cfg.package != null) cfg.package
++ lib.optional cfg.xwayland.enable pkgs.xwayland;
home.sessionVariables =
lib.mkIf cfg.recommendedEnvironment {NIXOS_OZONE_WL = "1";};
xdg.configFile."hypr/hyprland.conf" = lib.mkIf (cfg.systemdIntegration || cfg.extraConfig != null || cfg.plugins != []) {
text =
(lib.optionalString cfg.systemdIntegration ''
exec-once=${pkgs.dbus}/bin/dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP && systemctl --user start hyprland-session.target
'')
+ lib.concatStrings (builtins.map (entry: let
plugin =
if lib.types.package.check entry
then "${entry}/lib/lib${entry.pname}.so"
else entry;
in "plugin = ${plugin}\n")
cfg.plugins)
+ (
if cfg.extraConfig != null
then cfg.extraConfig
else ""
);
onChange = let
hyprlandPackage =
if cfg.package == null
then defaultHyprlandPackage
else cfg.package;
in
lib.mkIf (!cfg.disableAutoreload) ''
( # execute in subshell so that `shopt` won't affect other scripts
shopt -s nullglob # so that nothing is done if /tmp/hypr/ does not exist or is empty
for instance in /tmp/hypr/*; do
HYPRLAND_INSTANCE_SIGNATURE=''${instance##*/} ${hyprlandPackage}/bin/hyprctl reload config-only \
|| true # ignore dead instance(s)
done
)
'';
};
systemd.user.targets.hyprland-session = lib.mkIf cfg.systemdIntegration {
Unit = {
Description = "Hyprland compositor session";
Documentation = ["man:systemd.special(7)"];
BindsTo = ["graphical-session.target"];
Wants = ["graphical-session-pre.target"];
After = ["graphical-session-pre.target"];
};
};
};
imports = [
(lib.mkRemovedOptionModule ["wayland" "windowManager" "hyprland" "xwayland" "hidpi"]
"Support for this option has been removed. Refer to https://wiki.hyprland.org/Configuring/XWayland for more info")
];
}

View file

@ -2,98 +2,20 @@ inputs: {
config,
lib,
pkgs,
options,
...
}:
with lib; let
cfg = config.programs.hyprland;
}: let
inherit (pkgs.stdenv.hostPlatform) system;
cfg = config.programs.hyprland;
finalPortalPackage = cfg.portalPackage.override {
package = inputs.self.packages.${system}.hyprland;
portalPackage = inputs.self.packages.${system}.xdg-desktop-portal-hyprland.override {
hyprland = cfg.finalPackage;
};
in {
# disables Nixpkgs Hyprland module to avoid conflicts
disabledModules = ["programs/hyprland.nix"];
options.programs.hyprland = {
enable =
mkEnableOption null
// {
description = mdDoc ''
Hyprland, the dynamic tiling Wayland compositor that doesn't sacrifice on its looks.
You can manually launch Hyprland by executing {command}`Hyprland` on a TTY.
A configuration file will be generated in {file}`~/.config/hypr/hyprland.conf`.
See <https://wiki.hyprland.org> for more information.
'';
};
package = mkPackageOptionMD inputs.self.packages.${system} "hyprland" { };
finalPackage = mkOption {
type = types.package;
readOnly = true;
default = cfg.package.override {
enableXWayland = cfg.xwayland.enable;
enableNvidiaPatches = cfg.enableNvidiaPatches;
};
defaultText =
literalExpression
"`programs.hyprland.package` with applied configuration";
description = mdDoc ''
The Hyprland package after applying configuration.
'';
};
portalPackage = mkPackageOptionMD inputs.xdph.packages.${system} "xdg-desktop-portal-hyprland" {};
xwayland.enable = mkEnableOption (mdDoc "support for XWayland") // {default = true;};
enableNvidiaPatches =
mkEnableOption null
// {
description = mdDoc "Whether to apply patches to wlroots for better Nvidia support.";
};
};
config = mkIf cfg.enable {
environment.systemPackages = [cfg.finalPackage];
# NixOS changed the name of this attribute between NixOS 23.05 and
# 23.11
fonts = if builtins.hasAttr "enableDefaultPackages" options.fonts
then {enableDefaultPackages = mkDefault true;}
else {enableDefaultFonts = mkDefault true;};
hardware.opengl.enable = mkDefault true;
programs = {
dconf.enable = mkDefault true;
xwayland.enable = mkDefault cfg.xwayland.enable;
};
security.polkit.enable = true;
services.xserver.displayManager.sessionPackages = [cfg.finalPackage];
xdg.portal = {
enable = mkDefault true;
extraPortals = [finalPortalPackage];
config = {
programs.hyprland = {
package = lib.mkDefault package;
portalPackage = lib.mkDefault portalPackage;
};
};
imports = with lib; [
(
mkRemovedOptionModule
["programs" "hyprland" "xwayland" "hidpi"]
"XWayland patches are deprecated. Refer to https://wiki.hyprland.org/Configuring/XWayland"
)
(
mkRenamedOptionModule
["programs" "hyprland" "nvidiaPatches"]
["programs" "hyprland" "enableNvidiaPatches"]
)
];
}

View file

@ -28,17 +28,26 @@ in {
self.overlays.wlroots-hyprland
self.overlays.udis86
# Hyprland packages themselves
(final: prev: {
(final: prev: let
date = mkDate (self.lastModifiedDate or "19700101");
in {
hyprland = final.callPackage ./default.nix {
stdenv = final.gcc13Stdenv;
version = "${props.version}+date=${mkDate (self.lastModifiedDate or "19700101")}_${self.shortRev or "dirty"}";
version = "${props.version}+date=${date}_${self.shortRev or "dirty"}";
wlroots = final.wlroots-hyprland;
commit = self.rev or "";
inherit (final) udis86 hyprland-protocols;
inherit date;
};
hyprland-unwrapped = final.hyprland.override {wrapRuntimeDeps = false;};
hyprland-debug = final.hyprland.override {debug = true;};
hyprland-nvidia = final.hyprland.override {enableNvidiaPatches = true;};
hyprland-legacy-renderer = final.hyprland.override {legacyRenderer = true;};
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.
@ -65,30 +74,6 @@ in {
wlroots-hyprland = final.callPackage ./wlroots.nix {
version = "${mkDate (inputs.wlroots.lastModifiedDate or "19700101")}_${inputs.wlroots.shortRev or "dirty"}";
src = inputs.wlroots;
libdisplay-info = prev.libdisplay-info.overrideAttrs (old: {
version = "0.1.1+date=2023-03-02";
src = final.fetchFromGitLab {
domain = "gitlab.freedesktop.org";
owner = "emersion";
repo = old.pname;
rev = "147d6611a64a6ab04611b923e30efacaca6fc678";
sha256 = "sha256-/q79o13Zvu7x02SBGu0W5yQznQ+p7ltZ9L6cMW5t/o4=";
};
});
libliftoff = prev.libliftoff.overrideAttrs (old: {
version = "0.5.0-dev";
src = final.fetchFromGitLab {
domain = "gitlab.freedesktop.org";
owner = "emersion";
repo = old.pname;
rev = "d98ae243280074b0ba44bff92215ae8d785658c0";
sha256 = "sha256-DjwlS8rXE7srs7A8+tHqXyUsFGtucYSeq6X0T/pVOc8=";
};
NIX_CFLAGS_COMPILE = toString ["-Wno-error=sign-conversion"];
});
};
};
}

View file

@ -1,23 +1,11 @@
diff --git a/meson.build b/meson.build
index f3802553..6a924a79 100644
index 1d2c7f9f..c5ef4e67 100644
--- a/meson.build
+++ b/meson.build
@@ -21,9 +21,9 @@ else
@@ -33,20 +33,7 @@ if cpp_compiler.check_header('execinfo.h')
add_project_arguments('-DHAS_EXECINFO', language: 'cpp')
endif
GIT_BRANCH = run_command('git', 'rev-parse', '--abbrev-ref', 'HEAD', check: false).stdout().strip()
-GIT_COMMIT_HASH = run_command('git', 'rev-parse', 'HEAD', check: false).stdout().strip()
+GIT_COMMIT_HASH = '@GIT_COMMIT_HASH@'
GIT_COMMIT_MESSAGE = run_command('sh', '-c', 'git show | head -n 5 | tail -n 1', check: false).stdout().strip()
-GIT_DIRTY = run_command('sh', '-c', 'git diff-index --quiet HEAD -- || echo "dirty"', check: false).stdout().strip()
+GIT_DIRTY = '@GIT_DIRTY@'
add_project_arguments(
[
@@ -39,21 +39,8 @@ add_project_arguments(
],
language: 'cpp')
-wlroots = subproject('wlroots', default_options: ['examples=false', 'renderers=gles2'])
-have_xwlr = wlroots.get_variable('features').get('xwayland')
-xcb_dep = dependency('xcb', required: get_option('xwayland'))
@ -32,17 +20,24 @@ index f3802553..6a924a79 100644
-have_xwayland = xcb_dep.found() and have_xwlr
-
-if not have_xwayland
- add_project_arguments('-DNO_XWAYLAND', language: 'cpp')
+if get_option('xwayland').disabled()
+ add_project_arguments('-DNO_XWAYLAND', language: 'cpp')
add_project_arguments('-DNO_XWAYLAND', language: 'cpp')
endif
backtrace_dep = cpp_compiler.find_library('execinfo', required: false)
@@ -69,8 +56,6 @@ if get_option('buildtype') == 'debug'
add_project_arguments('-DHYPRLAND_DEBUG', language: 'cpp')
endif
-version_h = run_command('sh', '-c', 'scripts/generateVersion.sh')
-
globber = run_command('find', 'src', '-name', '*.h*', check: true)
headers = globber.stdout().strip().split('\n')
foreach file : headers
diff --git a/src/meson.build b/src/meson.build
index 7b658d31..60aa4057 100644
index 0af864b9..38723b8c 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -7,16 +7,16 @@ executable('Hyprland', src,
@@ -9,16 +9,16 @@ executable('Hyprland', src,
server_protos,
dependency('wayland-server'),
dependency('wayland-client'),

View file

@ -1,41 +0,0 @@
diff --git a/render/gles2/renderer.c b/render/gles2/renderer.c
index 9fe934f7..9662d4ee 100644
--- a/render/gles2/renderer.c
+++ b/render/gles2/renderer.c
@@ -176,7 +176,7 @@ static bool gles2_bind_buffer(struct wlr_renderer *wlr_renderer,
assert(wlr_egl_is_current(renderer->egl));
push_gles2_debug(renderer);
- glFlush();
+ glFinish();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
pop_gles2_debug(renderer);
diff --git a/types/output/render.c b/types/output/render.c
index 2e38919a..97f78608 100644
--- a/types/output/render.c
+++ b/types/output/render.c
@@ -240,22 +240,7 @@ bool output_pick_format(struct wlr_output *output,
}
uint32_t wlr_output_preferred_read_format(struct wlr_output *output) {
- struct wlr_renderer *renderer = output->renderer;
- assert(renderer != NULL);
-
- if (!renderer->impl->preferred_read_format || !renderer->impl->read_pixels) {
- return DRM_FORMAT_INVALID;
- }
-
- if (!wlr_output_attach_render(output, NULL)) {
- return false;
- }
-
- uint32_t fmt = renderer->impl->preferred_read_format(renderer);
-
- output_clear_back_buffer(output);
-
- return fmt;
+ return DRM_FORMAT_XRGB8888;
}
struct wlr_render_pass *wlr_output_begin_render_pass(struct wlr_output *output,

View file

@ -1,28 +1,13 @@
{
lib,
version,
src,
wlroots,
hwdata,
libdisplay-info,
libliftoff,
enableXWayland ? true,
enableNvidiaPatches ? false,
}:
wlroots.overrideAttrs (old: {
inherit version src enableXWayland;
pname = "${old.pname}-hyprland${lib.optionalString enableNvidiaPatches "-nvidia"}";
pname = "${old.pname}-hyprland";
patches =
(old.patches or [])
++ (lib.optionals enableNvidiaPatches [
./patches/wlroots-nvidia.patch
]);
buildInputs = old.buildInputs ++ [hwdata libliftoff libdisplay-info];
NIX_CFLAGS_COMPILE = toString [
"-Wno-error=maybe-uninitialized"
];
patches = [ ]; # don't inherit old.patches
})

View file

@ -1,3 +1,3 @@
{
"version": "0.30.0"
"version": "0.34.0"
}

View file

@ -25,6 +25,7 @@ protocols = [
[wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'],
[wl_protocol_dir, 'staging/fractional-scale/fractional-scale-v1.xml'],
[wl_protocol_dir, 'staging/cursor-shape/cursor-shape-v1.xml'],
[wl_protocol_dir, 'staging/tearing-control/tearing-control-v1.xml'],
['wlr-foreign-toplevel-management-unstable-v1.xml'],
['wlr-layer-shell-unstable-v1.xml'],
['wlr-output-power-management-unstable-v1.xml'],

16
scripts/generateVersion.sh Executable file
View file

@ -0,0 +1,16 @@
#!/bin/sh
cp -fr ./src/version.h.in ./src/version.h
HASH=$(git rev-parse HEAD)
BRANCH=$(git rev-parse --abbrev-ref HEAD)
MESSAGE=$(git show ${GIT_COMMIT_HASH} | head -n 5 | tail -n 1 | sed -e 's/#//g' -e 's/\"//g')
DATE=$(git show ${GIT_COMMIT_HASH} --no-patch --format=%cd --date=local)
DIRTY=$(git diff-index --quiet HEAD -- || echo dirty)
TAG=$(git describe --tags)
sed -i -e "s#@HASH@#${HASH}#" ./src/version.h
sed -i -e "s#@BRANCH@#${BRANCH}#" ./src/version.h
sed -i -e "s#@MESSAGE@#${MESSAGE}#" ./src/version.h
sed -i -e "s#@DATE@#${DATE}#" ./src/version.h
sed -i -e "s#@DIRTY@#${DIRTY}#" ./src/version.h
sed -i -e "s#@TAG@#${TAG}#" ./src/version.h

File diff suppressed because it is too large Load diff

View file

@ -27,9 +27,9 @@
#include "render/OpenGL.hpp"
#include "hyprerror/HyprError.hpp"
#include "plugins/PluginSystem.hpp"
#include "helpers/Watchdog.hpp"
enum eManagersInitStage
{
enum eManagersInitStage {
STAGE_PRIORITY = 0,
STAGE_LATE
};
@ -53,7 +53,6 @@ class CCompositor {
wlr_drm_lease_v1_manager* m_sWRLDRMLeaseMgr;
wlr_xdg_activation_v1* m_sWLRXDGActivation;
wlr_output_layout* m_sWLROutputLayout;
wlr_idle* m_sWLRIdle;
wlr_idle_notifier_v1* m_sWLRIdleNotifier;
wlr_layer_shell_v1* m_sWLRLayerShell;
wlr_xdg_shell* m_sWLRXDGShell;
@ -62,7 +61,6 @@ class CCompositor {
wlr_virtual_keyboard_manager_v1* m_sWLRVKeyboardMgr;
wlr_output_manager_v1* m_sWLROutputMgr;
wlr_presentation* m_sWLRPresentation;
wlr_input_inhibit_manager* m_sWLRInhibitMgr;
wlr_keyboard_shortcuts_inhibit_manager_v1* m_sWLRKbShInhibitMgr;
wlr_egl* m_sWLREGL;
int m_iDRMFD;
@ -85,6 +83,7 @@ class CCompositor {
wlr_session_lock_manager_v1* m_sWLRSessionLockMgr;
wlr_gamma_control_manager_v1* m_sWLRGammaCtrlMgr;
wlr_cursor_shape_manager_v1* m_sWLRCursorShapeMgr;
wlr_tearing_control_manager_v1* m_sWLRTearingControlMgr;
// ------------------------------------------------- //
std::string m_szWLDisplaySocket = "";
@ -120,7 +119,8 @@ class CCompositor {
bool m_bSessionActive = true;
bool m_bDPMSStateON = true;
bool m_bUnsafeState = false; // unsafe state is when there is no monitors.
wlr_output* m_pUnsafeOutput = nullptr; // fallback output for the unsafe state
bool m_bNextIsUnsafe = false; // because wlroots
CMonitor* m_pUnsafeOutput = nullptr; // fallback output for the unsafe state
bool m_bIsShuttingDown = false;
// ------------------------------------------------- //
@ -135,10 +135,10 @@ class CCompositor {
void focusSurface(wlr_surface*, CWindow* pWindowOwner = nullptr);
bool windowExists(CWindow*);
bool windowValidMapped(CWindow*);
CWindow* vectorToWindow(const Vector2D&);
CWindow* vectorToWindowIdeal(const Vector2D&); // used only for finding a window to focus on, basically a "findFocusableWindow"
CWindow* vectorToWindowIdeal(const Vector2D&, CWindow* pIgnoreWindow = nullptr); // used only for finding a window to focus on, basically a "findFocusableWindow"
CWindow* vectorToWindowTiled(const Vector2D&);
wlr_surface* vectorToLayerSurface(const Vector2D&, std::vector<std::unique_ptr<SLayerSurface>>*, Vector2D*, SLayerSurface**);
SIMEPopup* vectorToIMEPopup(const Vector2D& pos, std::list<SIMEPopup>& popups);
wlr_surface* vectorWindowToSurface(const Vector2D&, CWindow*, Vector2D& sl);
Vector2D vectorToSurfaceLocal(const Vector2D&, CWindow*, wlr_surface*);
CWindow* windowFromCursor();
@ -165,16 +165,18 @@ class CCompositor {
void changeWindowZOrder(CWindow*, bool);
void cleanupFadingOut(const int& monid);
CWindow* getWindowInDirection(CWindow*, char);
CWindow* getNextWindowOnWorkspace(CWindow*, bool focusableOnly = false);
CWindow* getPrevWindowOnWorkspace(CWindow*, bool focusableOnly = false);
CWindow* getNextWindowOnWorkspace(CWindow*, bool focusableOnly = false, std::optional<bool> floating = {});
CWindow* getPrevWindowOnWorkspace(CWindow*, bool focusableOnly = false, std::optional<bool> floating = {});
int getNextAvailableNamedWorkspace();
bool isPointOnAnyMonitor(const Vector2D&);
bool isPointOnReservedArea(const Vector2D& point, const CMonitor* monitor = nullptr);
CWindow* getConstraintWindow(SMouse*);
CMonitor* getMonitorInDirection(const char&);
void updateAllWindowsAnimatedDecorationValues();
void updateWorkspaceWindows(const int64_t& id);
void updateWindowAnimatedDecorationValues(CWindow*);
int getNextAvailableMonitorID(std::string const& name);
void moveWorkspaceToMonitor(CWorkspace*, CMonitor*);
void moveWorkspaceToMonitor(CWorkspace*, CMonitor*, bool noWarpCursor = false);
void swapActiveWorkspaces(CMonitor*, CMonitor*);
CMonitor* getMonitorFromString(const std::string&);
bool workspaceIDOutOfBounds(const int64_t&);
@ -191,7 +193,6 @@ class CCompositor {
void closeWindow(CWindow*);
Vector2D parseWindowVectorArgsRelative(const std::string&, const Vector2D&);
void forceReportSizesToWindowsOnWorkspace(const int&);
bool cursorOnReservedArea();
CWorkspace* createNewWorkspace(const int&, const int&, const std::string& name = ""); // will be deleted next frame if left empty and unfocused!
void renameWorkspace(const int&, const std::string& name = "");
void setActiveMonitor(CMonitor*);
@ -205,6 +206,9 @@ class CCompositor {
void arrangeMonitors();
void enterUnsafeState();
void leaveUnsafeState();
void setPreferredScaleForSurface(wlr_surface* pSurface, double scale);
void setPreferredTransformForSurface(wlr_surface* pSurface, wl_output_transform transform);
void updateSuspendedStates();
std::string explicitConfigPath;
@ -212,6 +216,7 @@ class CCompositor {
void initAllSignals();
void setRandomSplash();
void initManagers(eManagersInitStage stage);
void prepareFallbackOutput();
uint64_t m_iHyprlandPID = 0;
};

View file

@ -1,7 +1,8 @@
#pragma once
enum eIcons
{
#include "helpers/Vector2D.hpp"
enum eIcons {
ICON_WARNING = 0,
ICON_INFO,
ICON_HINT,
@ -11,8 +12,7 @@ enum eIcons
ICON_NONE
};
enum eRenderStage
{
enum eRenderStage {
RENDER_PRE = 0, /* Before binding the gl context */
RENDER_BEGIN, /* Just when the rendering begins, nothing has been rendered yet. Damage, current render data in opengl valid. */
RENDER_PRE_WINDOWS, /* Pre windows, post bottom and overlay layers */
@ -22,4 +22,34 @@ enum eRenderStage
RENDER_POST_MIRROR, /* After rendering a mirror */
RENDER_PRE_WINDOW, /* Before rendering a window (any pass) Note some windows (e.g. tiled) may have 2 passes (main & popup) */
RENDER_POST_WINDOW, /* After rendering a window (any pass) */
};
enum eInputType {
INPUT_TYPE_AXIS = 0,
INPUT_TYPE_BUTTON,
INPUT_TYPE_DRAG_START,
INPUT_TYPE_DRAG_END,
INPUT_TYPE_MOTION
};
struct SCallbackInfo {
bool cancelled = false; /* on cancellable events, will cancel the event. */
};
struct SWindowDecorationExtents {
Vector2D topLeft;
Vector2D bottomRight;
//
SWindowDecorationExtents operator*(const double& scale) const {
return SWindowDecorationExtents{topLeft * scale, bottomRight * scale};
}
SWindowDecorationExtents round() {
return {topLeft.round(), bottomRight.round()};
}
bool operator==(const SWindowDecorationExtents& other) const {
return topLeft == other.topLeft && bottomRight == other.bottomRight;
}
};

View file

@ -2,6 +2,7 @@
#include "Compositor.hpp"
#include "render/decorations/CHyprDropShadowDecoration.hpp"
#include "render/decorations/CHyprGroupBarDecoration.hpp"
#include "render/decorations/CHyprBorderDecoration.hpp"
CWindow::CWindow() {
m_vRealPosition.create(AVARTYPE_VECTOR, g_pConfigManager->getAnimationPropertyConfig("windowsIn"), (void*)this, AVARDAMAGE_ENTIRE);
@ -13,7 +14,8 @@ CWindow::CWindow() {
m_cRealShadowColor.create(AVARTYPE_COLOR, g_pConfigManager->getAnimationPropertyConfig("fadeShadow"), (void*)this, AVARDAMAGE_SHADOW);
m_fDimPercent.create(AVARTYPE_FLOAT, g_pConfigManager->getAnimationPropertyConfig("fadeDim"), (void*)this, AVARDAMAGE_ENTIRE);
m_dWindowDecorations.emplace_back(std::make_unique<CHyprDropShadowDecoration>(this)); // put the shadow so it's the first deco (has to be rendered first)
addWindowDeco(std::make_unique<CHyprDropShadowDecoration>(this));
addWindowDeco(std::make_unique<CHyprBorderDecoration>(this));
}
CWindow::~CWindow() {
@ -21,6 +23,12 @@ CWindow::~CWindow() {
g_pCompositor->m_pLastFocus = nullptr;
g_pCompositor->m_pLastWindow = nullptr;
}
if (!g_pHyprOpenGL)
return;
g_pHyprRenderer->makeEGLCurrent();
std::erase_if(g_pHyprOpenGL->m_mWindowFramebuffers, [&](const auto& other) { return other.first == this; });
}
SWindowDecorationExtents CWindow::getFullWindowExtents() {
@ -37,30 +45,27 @@ SWindowDecorationExtents CWindow::getFullWindowExtents() {
SWindowDecorationExtents maxExtents = {{BORDERSIZE + 2, BORDERSIZE + 2}, {BORDERSIZE + 2, BORDERSIZE + 2}};
for (auto& wd : m_dWindowDecorations) {
const auto EXTENTS = g_pDecorationPositioner->getWindowDecorationExtents(this);
const auto EXTENTS = wd->getWindowDecorationExtents();
if (EXTENTS.topLeft.x > maxExtents.topLeft.x)
maxExtents.topLeft.x = EXTENTS.topLeft.x;
if (EXTENTS.topLeft.x > maxExtents.topLeft.x)
maxExtents.topLeft.x = EXTENTS.topLeft.x;
if (EXTENTS.topLeft.y > maxExtents.topLeft.y)
maxExtents.topLeft.y = EXTENTS.topLeft.y;
if (EXTENTS.topLeft.y > maxExtents.topLeft.y)
maxExtents.topLeft.y = EXTENTS.topLeft.y;
if (EXTENTS.bottomRight.x > maxExtents.bottomRight.x)
maxExtents.bottomRight.x = EXTENTS.bottomRight.x;
if (EXTENTS.bottomRight.x > maxExtents.bottomRight.x)
maxExtents.bottomRight.x = EXTENTS.bottomRight.x;
if (EXTENTS.bottomRight.y > maxExtents.bottomRight.y)
maxExtents.bottomRight.y = EXTENTS.bottomRight.y;
}
if (EXTENTS.bottomRight.y > maxExtents.bottomRight.y)
maxExtents.bottomRight.y = EXTENTS.bottomRight.y;
if (m_pWLSurface.exists() && !m_bIsX11) {
wlr_box surfaceExtents = {0, 0, 0, 0};
CBox surfaceExtents = {0, 0, 0, 0};
// TODO: this could be better, perhaps make a getFullWindowRegion?
wlr_xdg_surface_for_each_popup_surface(
m_uSurface.xdg,
[](wlr_surface* surf, int sx, int sy, void* data) {
wlr_box* pSurfaceExtents = (wlr_box*)data;
CBox* pSurfaceExtents = (CBox*)data;
if (sx < pSurfaceExtents->x)
pSurfaceExtents->x = sx;
if (sy < pSurfaceExtents->y)
@ -88,21 +93,21 @@ SWindowDecorationExtents CWindow::getFullWindowExtents() {
return maxExtents;
}
wlr_box CWindow::getFullWindowBoundingBox() {
CBox CWindow::getFullWindowBoundingBox() {
if (m_sAdditionalConfigData.dimAround) {
const auto PMONITOR = g_pCompositor->getMonitorFromID(m_iMonitorID);
return {PMONITOR->vecPosition.x, PMONITOR->vecPosition.y, PMONITOR->vecSize.x, PMONITOR->vecSize.y};
}
auto maxExtents = getFullWindowExtents();
auto maxExtents = getFullWindowExtents();
wlr_box finalBox = {m_vRealPosition.vec().x - maxExtents.topLeft.x, m_vRealPosition.vec().y - maxExtents.topLeft.y,
m_vRealSize.vec().x + maxExtents.topLeft.x + maxExtents.bottomRight.x, m_vRealSize.vec().y + maxExtents.topLeft.y + maxExtents.bottomRight.y};
CBox finalBox = {m_vRealPosition.vec().x - maxExtents.topLeft.x, m_vRealPosition.vec().y - maxExtents.topLeft.y,
m_vRealSize.vec().x + maxExtents.topLeft.x + maxExtents.bottomRight.x, m_vRealSize.vec().y + maxExtents.topLeft.y + maxExtents.bottomRight.y};
return finalBox;
}
wlr_box CWindow::getWindowIdealBoundingBoxIgnoreReserved() {
CBox CWindow::getWindowIdealBoundingBoxIgnoreReserved() {
const auto PMONITOR = g_pCompositor->getMonitorFromID(m_iMonitorID);
@ -113,7 +118,7 @@ wlr_box CWindow::getWindowIdealBoundingBoxIgnoreReserved() {
POS = PMONITOR->vecPosition;
SIZE = PMONITOR->vecSize;
return wlr_box{(int)POS.x, (int)POS.y, (int)SIZE.x, (int)SIZE.y};
return CBox{(int)POS.x, (int)POS.y, (int)SIZE.x, (int)SIZE.y};
}
if (DELTALESSTHAN(POS.y - PMONITOR->vecPosition.y, PMONITOR->vecReservedTopLeft.y, 1)) {
@ -131,10 +136,10 @@ wlr_box CWindow::getWindowIdealBoundingBoxIgnoreReserved() {
SIZE.y += PMONITOR->vecReservedBottomRight.y;
}
return wlr_box{(int)POS.x, (int)POS.y, (int)SIZE.x, (int)SIZE.y};
return CBox{(int)POS.x, (int)POS.y, (int)SIZE.x, (int)SIZE.y};
}
wlr_box CWindow::getWindowInputBox() {
CBox CWindow::getWindowInputBox() {
const int BORDERSIZE = getRealBorderSize();
if (m_sAdditionalConfigData.dimAround) {
@ -144,58 +149,45 @@ wlr_box CWindow::getWindowInputBox() {
SWindowDecorationExtents maxExtents = {{BORDERSIZE + 2, BORDERSIZE + 2}, {BORDERSIZE + 2, BORDERSIZE + 2}};
for (auto& wd : m_dWindowDecorations) {
const auto EXTENTS = g_pDecorationPositioner->getWindowDecorationExtents(this, true);
if (!wd->allowsInput())
continue;
if (EXTENTS.topLeft.x > maxExtents.topLeft.x)
maxExtents.topLeft.x = EXTENTS.topLeft.x;
const auto EXTENTS = wd->getWindowDecorationExtents();
if (EXTENTS.topLeft.y > maxExtents.topLeft.y)
maxExtents.topLeft.y = EXTENTS.topLeft.y;
if (EXTENTS.topLeft.x > maxExtents.topLeft.x)
maxExtents.topLeft.x = EXTENTS.topLeft.x;
if (EXTENTS.bottomRight.x > maxExtents.bottomRight.x)
maxExtents.bottomRight.x = EXTENTS.bottomRight.x;
if (EXTENTS.topLeft.y > maxExtents.topLeft.y)
maxExtents.topLeft.y = EXTENTS.topLeft.y;
if (EXTENTS.bottomRight.x > maxExtents.bottomRight.x)
maxExtents.bottomRight.x = EXTENTS.bottomRight.x;
if (EXTENTS.bottomRight.y > maxExtents.bottomRight.y)
maxExtents.bottomRight.y = EXTENTS.bottomRight.y;
}
if (EXTENTS.bottomRight.y > maxExtents.bottomRight.y)
maxExtents.bottomRight.y = EXTENTS.bottomRight.y;
// Add extents to the real base BB and return
wlr_box finalBox = {m_vRealPosition.vec().x - maxExtents.topLeft.x, m_vRealPosition.vec().y - maxExtents.topLeft.y,
m_vRealSize.vec().x + maxExtents.topLeft.x + maxExtents.bottomRight.x, m_vRealSize.vec().y + maxExtents.topLeft.y + maxExtents.bottomRight.y};
CBox finalBox = {m_vRealPosition.vec().x - maxExtents.topLeft.x, m_vRealPosition.vec().y - maxExtents.topLeft.y,
m_vRealSize.vec().x + maxExtents.topLeft.x + maxExtents.bottomRight.x, m_vRealSize.vec().y + maxExtents.topLeft.y + maxExtents.bottomRight.y};
return finalBox;
}
CBox CWindow::getWindowMainSurfaceBox() {
return {m_vRealPosition.vec().x, m_vRealPosition.vec().y, m_vRealSize.vec().x, m_vRealSize.vec().y};
}
SWindowDecorationExtents CWindow::getFullWindowReservedArea() {
SWindowDecorationExtents extents;
for (auto& wd : m_dWindowDecorations) {
const auto RESERVED = wd->getWindowDecorationReservedArea();
if (RESERVED.bottomRight == Vector2D{} && RESERVED.topLeft == Vector2D{})
continue;
extents.topLeft = extents.topLeft + RESERVED.topLeft;
extents.bottomRight = extents.bottomRight + RESERVED.bottomRight;
}
return extents;
return g_pDecorationPositioner->getWindowDecorationReserved(this);
}
void CWindow::updateWindowDecos() {
for (auto& wd : m_dWindowDecorations)
wd->updateWindow(this);
bool recalc = false;
if (!m_bIsMapped || isHidden())
return;
for (auto& wd : m_vDecosToRemove) {
for (auto it = m_dWindowDecorations.begin(); it != m_dWindowDecorations.end(); it++) {
if (it->get() == wd) {
g_pDecorationPositioner->uncacheDecoration(it->get());
it = m_dWindowDecorations.erase(it);
recalc = true;
if (it == m_dWindowDecorations.end())
@ -204,10 +196,54 @@ void CWindow::updateWindowDecos() {
}
}
g_pDecorationPositioner->onWindowUpdate(this);
if (recalc)
g_pLayoutManager->getCurrentLayout()->recalculateWindow(this);
m_vDecosToRemove.clear();
for (auto& wd : m_dWindowDecorations) {
wd->updateWindow(this);
}
}
void CWindow::addWindowDeco(std::unique_ptr<IHyprWindowDecoration> deco) {
m_dWindowDecorations.emplace_back(std::move(deco));
g_pDecorationPositioner->forceRecalcFor(this);
updateWindowDecos();
g_pLayoutManager->getCurrentLayout()->recalculateWindow(this);
}
void CWindow::removeWindowDeco(IHyprWindowDecoration* deco) {
m_vDecosToRemove.push_back(deco);
g_pDecorationPositioner->forceRecalcFor(this);
updateWindowDecos();
g_pLayoutManager->getCurrentLayout()->recalculateWindow(this);
}
void CWindow::uncacheWindowDecos() {
for (auto& wd : m_dWindowDecorations) {
g_pDecorationPositioner->uncacheDecoration(wd.get());
}
}
bool CWindow::checkInputOnDecos(const eInputType type, const Vector2D& mouseCoords, std::any data) {
if (type != INPUT_TYPE_DRAG_END && hasPopupAt(mouseCoords))
return false;
for (auto& wd : m_dWindowDecorations) {
if (!(wd->getDecorationFlags() & DECORATION_ALLOWS_MOUSE_INPUT))
continue;
if (!g_pDecorationPositioner->getWindowDecorationBox(wd.get()).containsPoint(mouseCoords))
continue;
if (wd->onInputOnDeco(type, mouseCoords, data))
return true;
}
return false;
}
pid_t CWindow::getPID() {
@ -219,6 +255,9 @@ pid_t CWindow::getPID() {
wl_client_get_credentials(wl_resource_get_client(m_uSurface.xdg->resource), &PID, nullptr, nullptr);
} else {
if (!m_bIsMapped)
return -1;
PID = m_uSurface.xwayland->pid;
}
@ -279,7 +318,7 @@ void CWindow::destroyToplevelHandle() {
}
void CWindow::updateToplevel() {
updateSurfaceOutputs();
updateSurfaceScaleTransformDetails();
if (!m_phForeignToplevel)
return;
@ -306,8 +345,8 @@ void sendLeaveIter(wlr_surface* pSurface, int x, int y, void* data) {
wlr_surface_send_leave(pSurface, OUTPUT);
}
void CWindow::updateSurfaceOutputs() {
if (m_iLastSurfaceMonitorID == m_iMonitorID || !m_bIsMapped || m_bHidden || !m_bMappedX11)
void CWindow::updateSurfaceScaleTransformDetails() {
if (!m_bIsMapped || m_bHidden)
return;
const auto PLASTMONITOR = g_pCompositor->getMonitorFromID(m_iLastSurfaceMonitorID);
@ -316,16 +355,24 @@ void CWindow::updateSurfaceOutputs() {
const auto PNEWMONITOR = g_pCompositor->getMonitorFromID(m_iMonitorID);
if (PLASTMONITOR && PLASTMONITOR->m_bEnabled)
wlr_surface_for_each_surface(m_pWLSurface.wlr(), sendLeaveIter, PLASTMONITOR->output);
if (PNEWMONITOR != PLASTMONITOR) {
if (PLASTMONITOR && PLASTMONITOR->m_bEnabled)
wlr_surface_for_each_surface(m_pWLSurface.wlr(), sendLeaveIter, PLASTMONITOR->output);
wlr_surface_for_each_surface(m_pWLSurface.wlr(), sendEnterIter, PNEWMONITOR->output);
wlr_surface_for_each_surface(m_pWLSurface.wlr(), sendEnterIter, PNEWMONITOR->output);
}
wlr_surface_for_each_surface(
m_pWLSurface.wlr(),
[](wlr_surface* surf, int x, int y, void* data) {
const auto PMONITOR = g_pCompositor->getMonitorFromID(((CWindow*)data)->m_iMonitorID);
g_pProtocolManager->m_pFractionalScaleProtocolManager->setPreferredScaleForSurface(surf, PMONITOR ? PMONITOR->scale : 1.f);
const auto PSURFACE = CWLSurface::surfaceFromWlr(surf);
if (PSURFACE && PSURFACE->m_fLastScale == PMONITOR->scale)
return;
g_pCompositor->setPreferredScaleForSurface(surf, PMONITOR->scale);
g_pCompositor->setPreferredTransformForSurface(surf, PMONITOR->transform);
},
this);
}
@ -334,6 +381,10 @@ void CWindow::moveToWorkspace(int workspaceID) {
if (m_iWorkspaceID == workspaceID)
return;
static auto* const PCLOSEONLASTSPECIAL = &g_pConfigManager->getConfigValuePtr("misc:close_special_on_empty")->intValue;
const int OLDWORKSPACE = m_iWorkspaceID;
m_iWorkspaceID = workspaceID;
const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(m_iWorkspaceID);
@ -352,6 +403,15 @@ void CWindow::moveToWorkspace(int workspaceID) {
// update xwayland coords
g_pXWaylandManager->setWindowSize(this, m_vRealSize.vec());
if (g_pCompositor->isWorkspaceSpecial(OLDWORKSPACE) && g_pCompositor->getWindowsOnWorkspace(OLDWORKSPACE) == 0 && *PCLOSEONLASTSPECIAL) {
const auto PWS = g_pCompositor->getWorkspaceByID(OLDWORKSPACE);
if (PWS) {
if (const auto PMONITOR = g_pCompositor->getMonitorFromID(PWS->m_iMonitorID); PMONITOR)
PMONITOR->setSpecialWorkspace(nullptr);
}
}
}
CWindow* CWindow::X11TransientFor() {
@ -414,11 +474,19 @@ void CWindow::onUnmap() {
if (PMONITOR && PMONITOR->specialWorkspaceID == m_iWorkspaceID)
PMONITOR->setSpecialWorkspace(nullptr);
}
const auto PMONITOR = g_pCompositor->getMonitorFromID(m_iMonitorID);
if (PMONITOR && PMONITOR->solitaryClient == this)
PMONITOR->solitaryClient = nullptr;
g_pCompositor->updateWorkspaceWindows(m_iWorkspaceID);
}
void CWindow::onMap() {
m_pWLSurface.assign(g_pXWaylandManager->getWindowSurface(this));
m_pWLSurface.m_pOwner = this;
// JIC, reset the callbacks. If any are set, we'll make sure they are cleared so we don't accidentally unset them. (In case a window got remapped)
m_vRealPosition.resetAllCallbacks();
@ -448,6 +516,8 @@ void CWindow::onMap() {
hyprListener_unmapWindow.initCallback(m_bIsX11 ? &m_uSurface.xwayland->surface->events.unmap : &m_uSurface.xdg->surface->events.unmap, &Events::listener_unmapWindow, this,
"CWindow");
m_vReportedSize = m_vPendingReportedSize;
}
void CWindow::onBorderAngleAnimEnd(void* ptr) {
@ -472,6 +542,8 @@ void CWindow::setHidden(bool hidden) {
if (hidden && g_pCompositor->m_pLastWindow == this) {
g_pCompositor->m_pLastWindow = nullptr;
}
setSuspended(hidden);
}
bool CWindow::isHidden() {
@ -492,15 +564,19 @@ void CWindow::applyDynamicRule(const SWindowRule& r) {
} else if (r.szRule == "opaque") {
if (!m_sAdditionalConfigData.forceOpaqueOverridden)
m_sAdditionalConfigData.forceOpaque = true;
} else if (r.szRule.find("rounding") == 0) {
} else if (r.szRule == "immediate") {
m_sAdditionalConfigData.forceTearing = true;
} else if (r.szRule == "nearestneighbor") {
m_sAdditionalConfigData.nearestNeighbor = true;
} else if (r.szRule.starts_with("rounding")) {
try {
m_sAdditionalConfigData.rounding = std::stoi(r.szRule.substr(r.szRule.find_first_of(' ') + 1));
} catch (std::exception& e) { Debug::log(ERR, "Rounding rule \"{}\" failed with: {}", r.szRule, e.what()); }
} else if (r.szRule.find("bordersize") == 0) {
} else if (r.szRule.starts_with("bordersize")) {
try {
m_sAdditionalConfigData.borderSize = std::stoi(r.szRule.substr(r.szRule.find_first_of(' ') + 1));
} catch (std::exception& e) { Debug::log(ERR, "Bordersize rule \"{}\" failed with: {}", r.szRule, e.what()); }
} else if (r.szRule.find("opacity") == 0) {
} else if (r.szRule.starts_with("opacity")) {
try {
CVarList vars(r.szRule, 0, ' ');
@ -533,37 +609,78 @@ void CWindow::applyDynamicRule(const SWindowRule& r) {
} catch (std::exception& e) { Debug::log(ERR, "Opacity rule \"{}\" failed with: {}", r.szRule, e.what()); }
} else if (r.szRule == "noanim") {
m_sAdditionalConfigData.forceNoAnims = true;
} else if (r.szRule.find("animation") == 0) {
} else if (r.szRule.starts_with("animation")) {
auto STYLE = r.szRule.substr(r.szRule.find_first_of(' ') + 1);
m_sAdditionalConfigData.animationStyle = STYLE;
} else if (r.szRule.find("bordercolor") == 0) {
} else if (r.szRule.starts_with("bordercolor")) {
try {
std::string colorPart = removeBeginEndSpacesTabs(r.szRule.substr(r.szRule.find_first_of(' ') + 1));
// Each vector will only get used if it has at least one color
CGradientValueData activeBorderGradient = {};
CGradientValueData inactiveBorderGradient = {};
bool active = true;
CVarList colorsAndAngles = CVarList(removeBeginEndSpacesTabs(r.szRule.substr(r.szRule.find_first_of(' ') + 1)), 0, 's', true);
if (colorPart.contains(' ')) {
// we have a space, 2 values
m_sSpecialRenderData.activeBorderColor = configStringToInt(colorPart.substr(0, colorPart.find_first_of(' ')));
m_sSpecialRenderData.inactiveBorderColor = configStringToInt(colorPart.substr(colorPart.find_first_of(' ') + 1));
} else {
m_sSpecialRenderData.activeBorderColor = configStringToInt(colorPart);
// Basic form has only two colors, everything else can be parsed as a gradient
if (colorsAndAngles.size() == 2 && !colorsAndAngles[1].contains("deg")) {
m_sSpecialRenderData.activeBorderColor = CGradientValueData(CColor(configStringToInt(colorsAndAngles[0])));
m_sSpecialRenderData.inactiveBorderColor = CGradientValueData(CColor(configStringToInt(colorsAndAngles[1])));
return;
}
for (auto& token : colorsAndAngles) {
// The first angle, or an explicit "0deg", splits the two gradients
if (active && token.contains("deg")) {
activeBorderGradient.m_fAngle = std::stoi(token.substr(0, token.size() - 3)) * (PI / 180.0);
active = false;
} else if (token.contains("deg"))
inactiveBorderGradient.m_fAngle = std::stoi(token.substr(0, token.size() - 3)) * (PI / 180.0);
else if (active)
activeBorderGradient.m_vColors.push_back(configStringToInt(token));
else
inactiveBorderGradient.m_vColors.push_back(configStringToInt(token));
}
// Includes sanity checks for the number of colors in each gradient
if (activeBorderGradient.m_vColors.size() > 10 || inactiveBorderGradient.m_vColors.size() > 10)
Debug::log(WARN, "Bordercolor rule \"{}\" has more than 10 colors in one gradient, ignoring", r.szRule);
else if (activeBorderGradient.m_vColors.empty())
Debug::log(WARN, "Bordercolor rule \"{}\" has no colors, ignoring", r.szRule);
else if (inactiveBorderGradient.m_vColors.empty())
m_sSpecialRenderData.activeBorderColor = activeBorderGradient;
else {
m_sSpecialRenderData.activeBorderColor = activeBorderGradient;
m_sSpecialRenderData.inactiveBorderColor = inactiveBorderGradient;
}
} catch (std::exception& e) { Debug::log(ERR, "BorderColor rule \"{}\" failed with: {}", r.szRule, e.what()); }
} else if (r.szRule == "dimaround") {
m_sAdditionalConfigData.dimAround = true;
} else if (r.szRule == "keepaspectratio") {
m_sAdditionalConfigData.keepAspectRatio = true;
} else if (r.szRule.find("xray") == 0) {
} else if (r.szRule.starts_with("xray")) {
CVarList vars(r.szRule, 0, ' ');
try {
m_sAdditionalConfigData.xray = configStringToInt(vars[1]);
} catch (...) {}
} else if (r.szRule.starts_with("idleinhibit")) {
auto IDLERULE = r.szRule.substr(r.szRule.find_first_of(' ') + 1);
if (IDLERULE == "none")
m_eIdleInhibitMode = IDLEINHIBIT_NONE;
else if (IDLERULE == "always")
m_eIdleInhibitMode = IDLEINHIBIT_ALWAYS;
else if (IDLERULE == "focus")
m_eIdleInhibitMode = IDLEINHIBIT_FOCUS;
else if (IDLERULE == "fullscreen")
m_eIdleInhibitMode = IDLEINHIBIT_FULLSCREEN;
else
Debug::log(ERR, "Rule idleinhibit: unknown mode {}", IDLERULE);
}
}
void CWindow::updateDynamicRules() {
m_sSpecialRenderData.activeBorderColor = -1;
m_sSpecialRenderData.inactiveBorderColor = -1;
m_sSpecialRenderData.activeBorderColor = CGradientValueData();
m_sSpecialRenderData.inactiveBorderColor = CGradientValueData();
m_sSpecialRenderData.alpha = 1.f;
m_sSpecialRenderData.alphaInactive = -1.f;
m_sAdditionalConfigData.forceNoBlur = false;
@ -580,6 +697,9 @@ void CWindow::updateDynamicRules() {
m_sAdditionalConfigData.borderSize = -1;
m_sAdditionalConfigData.keepAspectRatio = false;
m_sAdditionalConfigData.xray = -1;
m_sAdditionalConfigData.forceTearing = false;
m_sAdditionalConfigData.nearestNeighbor = false;
m_eIdleInhibitMode = IDLEINHIBIT_NONE;
const auto WINDOWRULES = g_pConfigManager->getMatchingRules(this);
for (auto& r : WINDOWRULES) {
@ -622,9 +742,9 @@ bool CWindow::isInCurvedCorner(double x, double y) {
void findExtensionForVector2D(wlr_surface* surface, int x, int y, void* data) {
const auto DATA = (SExtensionFindingData*)data;
wlr_box box = {DATA->origin.x + x, DATA->origin.y + y, surface->current.width, surface->current.height};
CBox box = {DATA->origin.x + x, DATA->origin.y + y, surface->current.width, surface->current.height};
if (wlr_box_contains_point(&box, DATA->vec.x, DATA->vec.y))
if (box.containsPoint(DATA->vec))
*DATA->found = surface;
}
@ -654,14 +774,14 @@ void CWindow::createGroup() {
Debug::log(LOG, "createGroup: window:{:x},title:{} is denied as a group, ignored", (uintptr_t)this, this->m_szTitle);
return;
}
if (!m_sGroupData.pNextWindow) {
m_sGroupData.pNextWindow = this;
m_sGroupData.head = true;
m_sGroupData.locked = false;
m_sGroupData.deny = false;
m_dWindowDecorations.emplace_back(std::make_unique<CHyprGroupBarDecoration>(this));
updateWindowDecos();
addWindowDeco(std::make_unique<CHyprGroupBarDecoration>(this));
g_pLayoutManager->getCurrentLayout()->recalculateWindow(this);
g_pCompositor->updateAllWindowsAnimatedDecorationValues();
@ -735,6 +855,15 @@ int CWindow::getGroupSize() {
return size;
}
bool CWindow::canBeGroupedInto(CWindow* pWindow) {
return !g_pKeybindManager->m_bGroupsLocked // global group lock disengaged
&& ((m_eGroupRules & GROUP_INVADE && m_bFirstMap) // window ignore local group locks, or
|| (!pWindow->getGroupHead()->m_sGroupData.locked // target unlocked
&& !(m_sGroupData.pNextWindow && getGroupHead()->m_sGroupData.locked))) // source unlocked or isn't group
&& !m_sGroupData.deny // source is not denied entry
&& !(m_eGroupRules & GROUP_BARRED && m_bFirstMap); // group rule doesn't prevent adding window
}
CWindow* CWindow::getGroupWindowByIndex(int index) {
const int SIZE = getGroupSize();
index = ((index % SIZE) + SIZE) % SIZE;
@ -791,6 +920,8 @@ void CWindow::setGroupCurrent(CWindow* pWindow) {
g_pCompositor->setWindowFullscreen(pWindow, true, WORKSPACE->m_efFullscreenMode);
g_pHyprRenderer->damageWindow(pWindow);
pWindow->updateWindowDecos();
}
void CWindow::insertWindowToGroup(CWindow* pWindow) {
@ -798,7 +929,7 @@ void CWindow::insertWindowToGroup(CWindow* pWindow) {
const auto ENDAT = m_sGroupData.pNextWindow;
if (!pWindow->getDecorationByType(DECORATION_GROUPBAR))
pWindow->m_dWindowDecorations.emplace_back(std::make_unique<CHyprGroupBarDecoration>(pWindow));
pWindow->addWindowDeco(std::make_unique<CHyprGroupBarDecoration>(pWindow));
if (!pWindow->m_sGroupData.pNextWindow) {
BEGINAT->m_sGroupData.pNextWindow = pWindow;
@ -874,6 +1005,9 @@ bool CWindow::opaque() {
const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(m_iWorkspaceID);
if (m_pWLSurface.small() && !m_pWLSurface.m_bFillIgnoreSmall)
return false;
if (PWORKSPACE->m_fAlpha.fl() != 1.f)
return false;
@ -895,7 +1029,7 @@ float CWindow::rounding() {
float rounding = m_sAdditionalConfigData.rounding.toUnderlying() == -1 ? *PROUNDING : m_sAdditionalConfigData.rounding.toUnderlying();
return rounding;
return m_sSpecialRenderData.rounding ? rounding : 0;
}
void CWindow::updateSpecialRenderData() {
@ -925,3 +1059,23 @@ int CWindow::getRealBorderSize() {
return g_pConfigManager->getConfigValuePtr("general:border_size")->intValue;
}
bool CWindow::canBeTorn() {
return (m_sAdditionalConfigData.forceTearing.toUnderlying() || m_bTearingHint) && g_pHyprRenderer->m_bTearingEnvSatisfied;
}
bool CWindow::shouldSendFullscreenState() {
const auto MODE = g_pCompositor->getWorkspaceByID(m_iWorkspaceID)->m_efFullscreenMode;
return m_bFakeFullscreenState || (m_bIsFullscreen && (MODE == FULLSCREEN_FULL));
}
void CWindow::setSuspended(bool suspend) {
if (suspend == m_bSuspended)
return;
if (m_bIsX11)
return;
wlr_xdg_toplevel_set_suspended(m_uSurface.xdg->toplevel, suspend);
m_bSuspended = suspend;
}

View file

@ -30,6 +30,8 @@ enum eGroupRules {
GROUP_OVERRIDE = 1 << 6, // Override other rules
};
class IWindowTransformer;
template <typename T>
class CWindowOverridableVar {
public:
@ -104,13 +106,13 @@ class CWindowOverridableVar {
};
struct SWindowSpecialRenderData {
CWindowOverridableVar<bool> alphaOverride = false;
CWindowOverridableVar<float> alpha = 1.f;
CWindowOverridableVar<bool> alphaInactiveOverride = false;
CWindowOverridableVar<float> alphaInactive = -1.f; // -1 means unset
CWindowOverridableVar<bool> alphaOverride = false;
CWindowOverridableVar<float> alpha = 1.f;
CWindowOverridableVar<bool> alphaInactiveOverride = false;
CWindowOverridableVar<float> alphaInactive = -1.f; // -1 means unset
CWindowOverridableVar<int64_t> activeBorderColor = -1; // -1 means unset
CWindowOverridableVar<int64_t> inactiveBorderColor = -1; // -1 means unset
CWindowOverridableVar<CGradientValueData> activeBorderColor = CGradientValueData(); // empty color vector means unset
CWindowOverridableVar<CGradientValueData> inactiveBorderColor = CGradientValueData(); // empty color vector means unset
// set by the layout
CWindowOverridableVar<int> borderSize = -1; // -1 means unset
@ -138,6 +140,8 @@ struct SWindowAdditionalConfigData {
CWindowOverridableVar<bool> keepAspectRatio = false;
CWindowOverridableVar<int> xray = -1; // -1 means unset, takes precedence over the renderdata one
CWindowOverridableVar<int> borderSize = -1; // -1 means unset, takes precedence over the renderdata one
CWindowOverridableVar<bool> forceTearing = false;
CWindowOverridableVar<bool> nearestNeighbor = false;
};
struct SWindowRule {
@ -147,11 +151,15 @@ struct SWindowRule {
bool v2 = false;
std::string szTitle;
std::string szClass;
int bX11 = -1; // -1 means "ANY"
int bFloating = -1;
int bFullscreen = -1;
int bPinned = -1;
std::string szWorkspace = ""; // empty means any
std::string szInitialTitle;
std::string szInitialClass;
int bX11 = -1; // -1 means "ANY"
int bFloating = -1;
int bFullscreen = -1;
int bPinned = -1;
int bFocus = -1;
int iOnWorkspace = -1;
std::string szWorkspace = ""; // empty means any
};
class CWindow {
@ -179,6 +187,7 @@ class CWindow {
DYNLISTENER(setOverrideRedirect);
DYNLISTENER(associateX11);
DYNLISTENER(dissociateX11);
DYNLISTENER(ackConfigure);
// DYNLISTENER(newSubsurfaceWindow);
CWLSurface m_pWLSurface;
@ -198,8 +207,11 @@ class CWindow {
CAnimatedVariable m_vRealSize;
// for not spamming the protocols
Vector2D m_vReportedPosition;
Vector2D m_vReportedSize;
Vector2D m_vReportedPosition;
Vector2D m_vReportedSize;
Vector2D m_vPendingReportedSize;
std::optional<std::pair<uint32_t, Vector2D>> m_pPendingSizeAck;
std::vector<std::pair<uint32_t, Vector2D>> m_vPendingSizeAcks;
// for restoring floating statuses
Vector2D m_vLastFloatingSize;
@ -229,7 +241,6 @@ class CWindow {
// XWayland stuff
bool m_bIsX11 = false;
bool m_bMappedX11 = false;
CWindow* m_pX11Parent = nullptr;
uint64_t m_iX11Type = 0;
bool m_bIsModal = false;
@ -286,6 +297,9 @@ class CWindow {
SWindowSpecialRenderData m_sSpecialRenderData;
SWindowAdditionalConfigData m_sAdditionalConfigData;
// Transformers
std::vector<std::unique_ptr<IWindowTransformer>> m_vTransformers;
// for alpha
CAnimatedVariable m_fActiveInactiveAlpha;
@ -317,6 +331,8 @@ class CWindow {
} m_sGroupData;
uint16_t m_eGroupRules = GROUP_NONE;
bool m_bTearingHint = false;
// For the list lookup
bool operator==(const CWindow& rhs) {
return m_uSurface.xdg == rhs.m_uSurface.xdg && m_uSurface.xwayland == rhs.m_uSurface.xwayland && m_vPosition == rhs.m_vPosition && m_vSize == rhs.m_vSize &&
@ -324,18 +340,23 @@ class CWindow {
}
// methods
wlr_box getFullWindowBoundingBox();
CBox getFullWindowBoundingBox();
SWindowDecorationExtents getFullWindowExtents();
wlr_box getWindowInputBox();
wlr_box getWindowIdealBoundingBoxIgnoreReserved();
CBox getWindowInputBox();
CBox getWindowMainSurfaceBox();
CBox getWindowIdealBoundingBoxIgnoreReserved();
void addWindowDeco(std::unique_ptr<IHyprWindowDecoration> deco);
void updateWindowDecos();
void removeWindowDeco(IHyprWindowDecoration* deco);
void uncacheWindowDecos();
bool checkInputOnDecos(const eInputType, const Vector2D&, std::any = {});
pid_t getPID();
IHyprWindowDecoration* getDecorationByType(eDecorationType);
void removeDecorationByType(eDecorationType);
void createToplevelHandle();
void destroyToplevelHandle();
void updateToplevel();
void updateSurfaceOutputs();
void updateSurfaceScaleTransformDetails();
void moveToWorkspace(int);
CWindow* X11TransientFor();
void onUnmap();
@ -348,6 +369,9 @@ class CWindow {
Vector2D middle();
bool opaque();
float rounding();
bool canBeTorn();
bool shouldSendFullscreenState();
void setSuspended(bool suspend);
int getRealBorderSize();
void updateSpecialRenderData();
@ -365,6 +389,7 @@ class CWindow {
CWindow* getGroupPrevious();
CWindow* getGroupWindowByIndex(int);
int getGroupSize();
bool canBeGroupedInto(CWindow* pWindow);
void setGroupCurrent(CWindow* pWindow);
void insertWindowToGroup(CWindow* pWindow);
void updateGroupOutputs();
@ -372,7 +397,8 @@ class CWindow {
private:
// For hidden windows and stuff
bool m_bHidden = false;
bool m_bHidden = false;
bool m_bSuspended = false;
};
/**
@ -414,4 +440,4 @@ struct std::formatter<CWindow*, CharT> : std::formatter<CharT> {
std::format_to(out, ", class: {}", g_pXWaylandManager->getAppIDClass(w));
return std::format_to(out, "]");
}
};
};

View file

@ -16,6 +16,7 @@ class ICustomConfigValueData {
class CGradientValueData : public ICustomConfigValueData {
public:
CGradientValueData(){};
CGradientValueData(CColor col) {
m_vColors.push_back(col);
};

File diff suppressed because it is too large Load diff

View file

@ -11,6 +11,7 @@
#include <algorithm>
#include <regex>
#include <optional>
#include <functional>
#include <xf86drmMode.h>
#include "../Window.hpp"
#include "../helpers/WLClasses.hpp"
@ -36,18 +37,21 @@ struct SConfigValue {
};
struct SWorkspaceRule {
std::string monitor = "";
std::string workspaceString = "";
std::string workspaceName = "";
int workspaceId = -1;
bool isDefault = false;
std::optional<int64_t> gapsIn;
std::optional<int64_t> gapsOut;
std::optional<int64_t> borderSize;
std::optional<int> border;
std::optional<int> rounding;
std::optional<int> decorate;
std::optional<int> shadow;
std::string monitor = "";
std::string workspaceString = "";
std::string workspaceName = "";
int workspaceId = -1;
bool isDefault = false;
bool isPersistent = false;
std::optional<int64_t> gapsIn;
std::optional<int64_t> gapsOut;
std::optional<int64_t> borderSize;
std::optional<int> border;
std::optional<int> rounding;
std::optional<int> decorate;
std::optional<int> shadow;
std::optional<std::string> onCreatedEmptyRunCmd;
std::map<std::string, std::string> layoutopts;
};
struct SMonitorAdditionalReservedArea {
@ -69,6 +73,12 @@ struct SAnimationPropertyConfig {
SAnimationPropertyConfig* pParentAnimation = nullptr;
};
struct SPluginKeyword {
HANDLE handle = 0;
std::string name = "";
std::function<void(const std::string&, const std::string&)> fn;
};
struct SExecRequestedRule {
std::string szRule = "";
uint64_t iPid = 0;
@ -83,13 +93,16 @@ class CConfigManager {
int getInt(const std::string&);
float getFloat(const std::string&);
Vector2D getVec(const std::string&);
std::string getString(const std::string&);
void setFloat(const std::string&, float);
void setInt(const std::string&, int);
void setVec(const std::string&, Vector2D);
void setString(const std::string&, const std::string&);
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 deviceConfigExists(const std::string&);
bool shouldBlurLS(const std::string&);
@ -115,7 +128,8 @@ class CConfigManager {
std::unordered_map<std::string, SAnimationPropertyConfig> getAnimationConfig();
void addPluginConfigVar(HANDLE handle, const std::string& name, const SConfigValue& value);
void removePluginConfig(HANDLE handle);
void addPluginKeyword(HANDLE handle, const std::string& name, std::function<void(const std::string& cmd, const std::string& val)> fun);
void removePluginConfig(HANDLE handle);
// no-op when done.
void dispatchExecOnce();
@ -158,6 +172,7 @@ class CConfigManager {
std::vector<std::string> m_vDeclaredPlugins;
std::unordered_map<HANDLE, std::unique_ptr<std::unordered_map<std::string, SConfigValue>>> pluginConfigs; // stores plugin configs
std::vector<SPluginKeyword> pluginKeywords;
bool isFirstLaunch = true; // For exec-once

View file

@ -28,8 +28,14 @@ monitor=,preferred,auto,auto
# Source a file (multi-file configs)
# source = ~/.config/hypr/myColors.conf
# Set programs that you use
$terminal = kitty
$fileManager = dolphin
$menu = wofi --show drun
# Some default env vars.
env = XCURSOR_SIZE,24
env = QT_QPA_PLATFORMTHEME,qt5ct # change to qt6ct if you have that
# For all categories, see https://wiki.hyprland.org/Configuring/Variables/
input {
@ -58,6 +64,9 @@ general {
col.inactive_border = rgba(595959aa)
layout = dwindle
# Please see https://wiki.hyprland.org/Configuring/Tearing/ before you turn this on
allow_tearing = false
}
decoration {
@ -108,6 +117,11 @@ gestures {
workspace_swipe = off
}
misc {
# See https://wiki.hyprland.org/Configuring/Variables/ for more
force_default_wallpaper = -1 # Set to 0 to disable the anime mascot wallpapers
}
# Example per-device config
# See https://wiki.hyprland.org/Configuring/Keywords/#executing for more
device:epic-mouse-v1 {
@ -119,18 +133,19 @@ device:epic-mouse-v1 {
# Example windowrule v2
# windowrulev2 = float,class:^(kitty)$,title:^(kitty)$
# See https://wiki.hyprland.org/Configuring/Window-Rules/ for more
windowrulev2 = nomaximizerequest, class:.* # You'll probably like this.
# See https://wiki.hyprland.org/Configuring/Keywords/ for more
$mainMod = SUPER
# Example binds, see https://wiki.hyprland.org/Configuring/Binds/ for more
bind = $mainMod, Q, exec, kitty
bind = $mainMod, Q, exec, $terminal
bind = $mainMod, C, killactive,
bind = $mainMod, M, exit,
bind = $mainMod, E, exec, dolphin
bind = $mainMod, E, exec, $fileManager
bind = $mainMod, V, togglefloating,
bind = $mainMod, R, exec, wofi --show drun
bind = $mainMod, R, exec, $menu
bind = $mainMod, P, pseudo, # dwindle
bind = $mainMod, J, togglesplit, # dwindle
@ -164,6 +179,10 @@ bind = $mainMod SHIFT, 8, movetoworkspace, 8
bind = $mainMod SHIFT, 9, movetoworkspace, 9
bind = $mainMod SHIFT, 0, movetoworkspace, 10
# Example special workspace (scratchpad)
bind = $mainMod, S, togglespecialworkspace, magic
bind = $mainMod SHIFT, S, movetoworkspace, special:magic
# Scroll through existing workspaces with mainMod + scroll
bind = $mainMod, mouse_down, workspace, e+1
bind = $mainMod, mouse_up, workspace, e-1

View file

@ -1,7 +1,6 @@
#include "CrashReporter.hpp"
#include <random>
#include <sys/utsname.h>
#include <execinfo.h>
#include <fstream>
#include <signal.h>
@ -64,8 +63,8 @@ void CrashReporter::createAndSaveCrash(int sig) {
struct utsname unameInfo;
uname(&unameInfo);
finalCrashReport +=
std::format("\tSystem name: {}\n\tNode name: {}\n\tRelease: {}\n\tVersion: {}\n\n", unameInfo.sysname, unameInfo.nodename, unameInfo.release, unameInfo.version);
finalCrashReport += std::format("\tSystem name: {}\n\tNode name: {}\n\tRelease: {}\n\tVersion: {}\n\n", std::string{unameInfo.sysname}, std::string{unameInfo.nodename},
std::string{unameInfo.release}, std::string{unameInfo.version});
#if defined(__DragonFly__) || defined(__FreeBSD__)
const std::string GPUINFO = execAndGet("pciconf -lv | fgrep -A4 vga");
@ -120,7 +119,7 @@ void CrashReporter::createAndSaveCrash(int sig) {
finalCrashReport += "\n\nLog tail:\n";
finalCrashReport += execAndGet(("cat \"" + Debug::logFile + "\" | tail -n 50").c_str());
finalCrashReport += Debug::rollingLog;
const auto HOME = getenv("HOME");
const auto CACHE_HOME = getenv("XDG_CACHE_HOME");
@ -130,25 +129,19 @@ void CrashReporter::createAndSaveCrash(int sig) {
std::ofstream ofs;
std::string path;
if (!CACHE_HOME) {
if (!std::filesystem::exists(std::string(HOME) + "/.hyprland")) {
if (!CACHE_HOME || std::string(CACHE_HOME).empty()) {
if (!std::filesystem::exists(std::string(HOME) + "/.hyprland"))
std::filesystem::create_directory(std::string(HOME) + "/.hyprland");
std::filesystem::permissions(std::string(HOME) + "/.hyprland", std::filesystem::perms::all, std::filesystem::perm_options::replace);
}
path = std::string(HOME) + "/.hyprland/hyprlandCrashReport" + std::to_string(PID) + ".txt";
ofs.open(path, std::ios::trunc);
} else if (CACHE_HOME) {
if (!std::filesystem::exists(std::string(CACHE_HOME) + "/hyprland")) {
} else {
if (!std::filesystem::exists(std::string(CACHE_HOME) + "/hyprland"))
std::filesystem::create_directory(std::string(CACHE_HOME) + "/hyprland");
std::filesystem::permissions(std::string(CACHE_HOME) + "/hyprland", std::filesystem::perms::all, std::filesystem::perm_options::replace);
}
path = std::string(CACHE_HOME) + "/hyprland/hyprlandCrashReport" + std::to_string(PID) + ".txt";
ofs.open(path, std::ios::trunc);
} else {
return;
}
ofs << finalCrashReport;

View file

@ -7,6 +7,7 @@
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <sys/un.h>
#include <unistd.h>
#include <errno.h>
@ -28,13 +29,22 @@ static std::string getWorkspaceNameFromSpecialID(const int workspaceID) {
return workspace->m_szName;
}
std::string monitorsRequest(HyprCtl::eHyprCtlOutputFormat format) {
std::string monitorsRequest(std::string request, HyprCtl::eHyprCtlOutputFormat format) {
CVarList vars(request, 0, ' ');
auto allMonitors = false;
if (vars.size() > 2)
return "too many args";
if (vars.size() == 2 && vars[1] == "all")
allMonitors = true;
std::string result = "";
if (format == HyprCtl::FORMAT_JSON) {
result += "[";
for (auto& m : g_pCompositor->m_vMonitors) {
if (!m->output)
for (auto& m : allMonitors ? g_pCompositor->m_vRealMonitors : g_pCompositor->m_vMonitors) {
if (!m->output || m->ID == -1ull)
continue;
result += std::format(
@ -63,35 +73,37 @@ std::string monitorsRequest(HyprCtl::eHyprCtlOutputFormat format) {
"transform": {},
"focused": {},
"dpmsStatus": {},
"vrr": {}
"vrr": {},
"activelyTearing": {}
}},)#",
m->ID, escapeJSONStrings(m->szName), escapeJSONStrings(m->output->description ? m->output->description : ""), (m->output->make ? m->output->make : ""),
(m->output->model ? m->output->model : ""), (m->output->serial ? m->output->serial : ""), (int)m->vecPixelSize.x, (int)m->vecPixelSize.y, m->refreshRate,
(int)m->vecPosition.x, (int)m->vecPosition.y, m->activeWorkspace, escapeJSONStrings(g_pCompositor->getWorkspaceByID(m->activeWorkspace)->m_szName),
m->specialWorkspaceID, escapeJSONStrings(getWorkspaceNameFromSpecialID(m->specialWorkspaceID)), (int)m->vecReservedTopLeft.x, (int)m->vecReservedTopLeft.y,
m->ID, escapeJSONStrings(m->szName), escapeJSONStrings(m->szDescription), (m->output->make ? m->output->make : ""), (m->output->model ? m->output->model : ""),
(m->output->serial ? m->output->serial : ""), (int)m->vecPixelSize.x, (int)m->vecPixelSize.y, m->refreshRate, (int)m->vecPosition.x, (int)m->vecPosition.y,
m->activeWorkspace, (m->activeWorkspace == -1 ? "" : escapeJSONStrings(g_pCompositor->getWorkspaceByID(m->activeWorkspace)->m_szName)), m->specialWorkspaceID,
escapeJSONStrings(getWorkspaceNameFromSpecialID(m->specialWorkspaceID)), (int)m->vecReservedTopLeft.x, (int)m->vecReservedTopLeft.y,
(int)m->vecReservedBottomRight.x, (int)m->vecReservedBottomRight.y, m->scale, (int)m->transform, (m.get() == g_pCompositor->m_pLastMonitor ? "true" : "false"),
(m->dpmsStatus ? "true" : "false"), (m->output->adaptive_sync_status == WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED ? "true" : "false"));
(m->dpmsStatus ? "true" : "false"), (m->output->adaptive_sync_status == WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED ? "true" : "false"),
m->tearingState.activelyTearing ? "true" : "false");
}
trimTrailingComma(result);
result += "]";
} else {
for (auto& m : g_pCompositor->m_vMonitors) {
if (!m->output)
for (auto& m : allMonitors ? g_pCompositor->m_vRealMonitors : g_pCompositor->m_vMonitors) {
if (!m->output || m->ID == -1ull)
continue;
result +=
std::format("Monitor {} (ID {}):\n\t{}x{}@{:.5f} at {}x{}\n\tdescription: {}\n\tmake: {}\n\tmodel: {}\n\tserial: {}\n\tactive workspace: {} ({})\n\tspecial "
"workspace: {} ({})\n\treserved: {} "
"{} {} {}\n\tscale: {:.2f}\n\ttransform: "
"{}\n\tfocused: {}\n\tdpmsStatus: {}\n\tvrr: {}\n\n",
m->szName, m->ID, (int)m->vecPixelSize.x, (int)m->vecPixelSize.y, m->refreshRate, (int)m->vecPosition.x, (int)m->vecPosition.y,
(m->output->description ? m->output->description : ""), (m->output->make ? m->output->make : ""), (m->output->model ? m->output->model : ""),
(m->output->serial ? m->output->serial : ""), m->activeWorkspace, g_pCompositor->getWorkspaceByID(m->activeWorkspace)->m_szName, m->specialWorkspaceID,
"{}\n\tfocused: {}\n\tdpmsStatus: {}\n\tvrr: {}\n\tactivelyTearing: {}\n\n",
m->szName, m->ID, (int)m->vecPixelSize.x, (int)m->vecPixelSize.y, m->refreshRate, (int)m->vecPosition.x, (int)m->vecPosition.y, m->szDescription,
(m->output->make ? m->output->make : ""), (m->output->model ? m->output->model : ""), (m->output->serial ? m->output->serial : ""), m->activeWorkspace,
(m->activeWorkspace == -1 ? "" : g_pCompositor->getWorkspaceByID(m->activeWorkspace)->m_szName), m->specialWorkspaceID,
getWorkspaceNameFromSpecialID(m->specialWorkspaceID), (int)m->vecReservedTopLeft.x, (int)m->vecReservedTopLeft.y, (int)m->vecReservedBottomRight.x,
(int)m->vecReservedBottomRight.y, m->scale, (int)m->transform, (m.get() == g_pCompositor->m_pLastMonitor ? "yes" : "no"), (int)m->dpmsStatus,
(int)(m->output->adaptive_sync_status == WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED));
(int)(m->output->adaptive_sync_status == WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED), m->tearingState.activelyTearing);
}
}
@ -103,33 +115,34 @@ static std::string getGroupedData(CWindow* w, HyprCtl::eHyprCtlOutputFormat form
if (!w->m_sGroupData.pNextWindow)
return isJson ? "" : "0";
std::vector<CWindow*> groupMembers;
CWindow* curr = w;
do {
groupMembers.push_back(curr);
curr = curr->m_sGroupData.pNextWindow;
} while (curr != w);
const auto comma = isJson ? ", " : ",";
std::ostringstream result;
bool first = true;
for (auto& gw : groupMembers) {
if (first)
first = false;
else
result << comma;
CWindow* head = w->getGroupHead();
CWindow* curr = head;
while (true) {
if (isJson)
result << std::format("\"0x{:x}\"", (uintptr_t)gw);
result << std::format("\"0x{:x}\"", (uintptr_t)curr);
else
result << std::format("{:x}", (uintptr_t)gw);
result << std::format("{:x}", (uintptr_t)curr);
curr = curr->m_sGroupData.pNextWindow;
// We've wrapped around to the start, break out without trailing comma
if (curr == head)
break;
result << (isJson ? ", " : ",");
}
return result.str();
}
static std::string getWindowData(CWindow* w, HyprCtl::eHyprCtlOutputFormat format) {
auto getFocusHistoryID = [](CWindow* wnd) -> int {
for (size_t i = 0; i < g_pCompositor->m_vWindowFocusHistory.size(); ++i) {
if (g_pCompositor->m_vWindowFocusHistory[i] == wnd)
return i;
}
return -1;
};
if (format == HyprCtl::FORMAT_JSON) {
return std::format(
R"#({{
@ -155,7 +168,8 @@ static std::string getWindowData(CWindow* w, HyprCtl::eHyprCtlOutputFormat forma
"fullscreenMode": {},
"fakeFullscreen": {},
"grouped": [{}],
"swallowing": "0x{:x}"
"swallowing": "0x{:x}",
"focusHistoryID": {}
}},)#",
(uintptr_t)w, (w->m_bIsMapped ? "true" : "false"), (w->isHidden() ? "true" : "false"), (int)w->m_vRealPosition.goalv().x, (int)w->m_vRealPosition.goalv().y,
(int)w->m_vRealSize.goalv().x, (int)w->m_vRealSize.goalv().y, w->m_iWorkspaceID,
@ -166,13 +180,13 @@ static std::string getWindowData(CWindow* w, HyprCtl::eHyprCtlOutputFormat forma
escapeJSONStrings(g_pXWaylandManager->getTitle(w)), escapeJSONStrings(w->m_szInitialClass), escapeJSONStrings(w->m_szInitialTitle), w->getPID(),
((int)w->m_bIsX11 == 1 ? "true" : "false"), (w->m_bPinned ? "true" : "false"), (w->m_bIsFullscreen ? "true" : "false"),
(w->m_bIsFullscreen ? (g_pCompositor->getWorkspaceByID(w->m_iWorkspaceID) ? (int)g_pCompositor->getWorkspaceByID(w->m_iWorkspaceID)->m_efFullscreenMode : 0) : 0),
w->m_bFakeFullscreenState ? "true" : "false", getGroupedData(w, format), (uintptr_t)w->m_pSwallowed);
w->m_bFakeFullscreenState ? "true" : "false", getGroupedData(w, format), (uintptr_t)w->m_pSwallowed, getFocusHistoryID(w));
} else {
return std::format(
"Window {:x} -> {}:\n\tmapped: {}\n\thidden: {}\n\tat: {},{}\n\tsize: {},{}\n\tworkspace: {} ({})\n\tfloating: {}\n\tmonitor: {}\n\tclass: {}\n\ttitle: "
"{}\n\tinitialClass: {}\n\tinitialTitle: {}\n\tpid: "
"{}\n\txwayland: {}\n\tpinned: "
"{}\n\tfullscreen: {}\n\tfullscreenmode: {}\n\tfakefullscreen: {}\n\tgrouped: {}\n\tswallowing: {:x}\n\n",
"{}\n\tfullscreen: {}\n\tfullscreenmode: {}\n\tfakefullscreen: {}\n\tgrouped: {}\n\tswallowing: {:x}\n\tfocusHistoryID: {}\n\n",
(uintptr_t)w, w->m_szTitle, (int)w->m_bIsMapped, (int)w->isHidden(), (int)w->m_vRealPosition.goalv().x, (int)w->m_vRealPosition.goalv().y,
(int)w->m_vRealSize.goalv().x, (int)w->m_vRealSize.goalv().y, w->m_iWorkspaceID,
(w->m_iWorkspaceID == -1 ? "" :
@ -181,7 +195,7 @@ static std::string getWindowData(CWindow* w, HyprCtl::eHyprCtlOutputFormat forma
(int)w->m_bIsFloating, (int64_t)w->m_iMonitorID, g_pXWaylandManager->getAppIDClass(w), g_pXWaylandManager->getTitle(w), w->m_szInitialClass, w->m_szInitialTitle,
w->getPID(), (int)w->m_bIsX11, (int)w->m_bPinned, (int)w->m_bIsFullscreen,
(w->m_bIsFullscreen ? (g_pCompositor->getWorkspaceByID(w->m_iWorkspaceID) ? g_pCompositor->getWorkspaceByID(w->m_iWorkspaceID)->m_efFullscreenMode : 0) : 0),
(int)w->m_bFakeFullscreenState, getGroupedData(w, format), (uintptr_t)w->m_pSwallowed);
(int)w->m_bFakeFullscreenState, getGroupedData(w, format), (uintptr_t)w->m_pSwallowed, getFocusHistoryID(w));
}
}
@ -213,23 +227,70 @@ static std::string getWorkspaceData(CWorkspace* w, HyprCtl::eHyprCtlOutputFormat
"id": {},
"name": "{}",
"monitor": "{}",
"monitorID": {},
"windows": {},
"hasfullscreen": {},
"lastwindow": "0x{:x}",
"lastwindowtitle": "{}"
}})#",
w->m_iID, escapeJSONStrings(w->m_szName), escapeJSONStrings(PMONITOR ? PMONITOR->szName : "?"), g_pCompositor->getWindowsOnWorkspace(w->m_iID),
w->m_iID, escapeJSONStrings(w->m_szName), escapeJSONStrings(PMONITOR ? PMONITOR->szName : "?"),
escapeJSONStrings(PMONITOR ? std::to_string(PMONITOR->ID) : "null"), g_pCompositor->getWindowsOnWorkspace(w->m_iID),
((int)w->m_bHasFullscreenWindow == 1 ? "true" : "false"), (uintptr_t)PLASTW, PLASTW ? escapeJSONStrings(PLASTW->m_szTitle) : "");
} else {
return std::format("workspace ID {} ({}) on monitor {}:\n\twindows: {}\n\thasfullscreen: {}\n\tlastwindow: 0x{:x}\n\tlastwindowtitle: {}\n\n", w->m_iID, w->m_szName,
PMONITOR ? PMONITOR->szName : "?", g_pCompositor->getWindowsOnWorkspace(w->m_iID), (int)w->m_bHasFullscreenWindow, (uintptr_t)PLASTW,
PLASTW ? PLASTW->m_szTitle : "");
return std::format("workspace ID {} ({}) on monitor {}:\n\tmonitorID: {}\n\twindows: {}\n\thasfullscreen: {}\n\tlastwindow: 0x{:x}\n\tlastwindowtitle: {}\n\n", w->m_iID,
w->m_szName, PMONITOR ? PMONITOR->szName : "?", PMONITOR ? std::to_string(PMONITOR->ID) : "null", g_pCompositor->getWindowsOnWorkspace(w->m_iID),
(int)w->m_bHasFullscreenWindow, (uintptr_t)PLASTW, PLASTW ? PLASTW->m_szTitle : "");
}
}
static std::string getWorkspaceRuleData(const SWorkspaceRule& r, HyprCtl::eHyprCtlOutputFormat format) {
const auto boolToString = [](const bool b) -> std::string { return b ? "true" : "false"; };
if (format == HyprCtl::FORMAT_JSON) {
const std::string monitor = r.monitor.empty() ? "" : std::format(",\n \"monitor\": \"{}\"", escapeJSONStrings(r.monitor));
const std::string default_ = (bool)(r.isDefault) ? std::format(",\n \"default\": {}", boolToString(r.isDefault)) : "";
const std::string persistent = (bool)(r.isPersistent) ? std::format(",\n \"persistent\": {}", boolToString(r.isPersistent)) : "";
const std::string gapsIn = (bool)(r.gapsIn) ? std::format(",\n \"gapsIn\": {}", r.gapsIn.value()) : "";
const std::string gapsOut = (bool)(r.gapsOut) ? std::format(",\n \"gapsOut\": {}", r.gapsOut.value()) : "";
const std::string borderSize = (bool)(r.borderSize) ? std::format(",\n \"borderSize\": {}", r.borderSize.value()) : "";
const std::string border = (bool)(r.border) ? std::format(",\n \"border\": {}", boolToString(r.border.value())) : "";
const std::string rounding = (bool)(r.rounding) ? std::format(",\n \"rounding\": {}", boolToString(r.rounding.value())) : "";
const std::string decorate = (bool)(r.decorate) ? std::format(",\n \"decorate\": {}", boolToString(r.decorate.value())) : "";
const std::string shadow = (bool)(r.shadow) ? std::format(",\n \"shadow\": {}", boolToString(r.shadow.value())) : "";
std::string result = std::format(R"#({{
"workspaceString": "{}"{}{}{}{}{}{}{}{}
}})#",
escapeJSONStrings(r.workspaceString), monitor, default_, persistent, gapsIn, gapsOut, borderSize, border, rounding, decorate, shadow);
return result;
} else {
const std::string monitor = std::format("\tmonitor: {}\n", r.monitor.empty() ? "<unset>" : escapeJSONStrings(r.monitor));
const std::string default_ = std::format("\tdefault: {}\n", (bool)(r.isDefault) ? boolToString(r.isDefault) : "<unset>");
const std::string persistent = std::format("\tpersistent: {}\n", (bool)(r.isPersistent) ? boolToString(r.isPersistent) : "<unset>");
const std::string gapsIn = std::format("\tgapsIn: {}\n", (bool)(r.gapsIn) ? std::to_string(r.gapsIn.value()) : "<unset>");
const std::string gapsOut = std::format("\tgapsOut: {}\n", (bool)(r.gapsOut) ? std::to_string(r.gapsOut.value()) : "<unset>");
const std::string borderSize = std::format("\tborderSize: {}\n", (bool)(r.borderSize) ? std::to_string(r.borderSize.value()) : "<unset>");
const std::string border = std::format("\tborder: {}\n", (bool)(r.border) ? boolToString(r.border.value()) : "<unset>");
const std::string rounding = std::format("\trounding: {}\n", (bool)(r.rounding) ? boolToString(r.rounding.value()) : "<unset>");
const std::string decorate = std::format("\tdecorate: {}\n", (bool)(r.decorate) ? boolToString(r.decorate.value()) : "<unset>");
const std::string shadow = std::format("\tshadow: {}\n", (bool)(r.shadow) ? boolToString(r.shadow.value()) : "<unset>");
std::string result = std::format("Workspace rule {}:\n{}{}{}{}{}{}{}{}{}{}\n", escapeJSONStrings(r.workspaceString), monitor, default_, persistent, gapsIn, gapsOut,
borderSize, border, rounding, decorate, shadow);
return result;
}
}
std::string activeWorkspaceRequest(HyprCtl::eHyprCtlOutputFormat format) {
if (!g_pCompositor->m_pLastMonitor)
return "unsafe state";
std::string result = "";
auto w = g_pCompositor->getWorkspaceByID(g_pCompositor->m_pLastMonitor->activeWorkspace);
if (!w)
return "internal error";
return getWorkspaceData(w, format);
}
@ -254,6 +315,26 @@ std::string workspacesRequest(HyprCtl::eHyprCtlOutputFormat format) {
return result;
}
std::string workspaceRulesRequest(HyprCtl::eHyprCtlOutputFormat format) {
std::string result = "";
if (format == HyprCtl::FORMAT_JSON) {
result += "[";
for (auto& r : g_pConfigManager->getAllWorkspaceRules()) {
result += getWorkspaceRuleData(r, format);
result += ",";
}
trimTrailingComma(result);
result += "]";
} else {
for (auto& r : g_pConfigManager->getAllWorkspaceRules()) {
result += getWorkspaceRuleData(r, format);
}
}
return result;
}
std::string activeWindowRequest(HyprCtl::eHyprCtlOutputFormat format) {
const auto PWINDOW = g_pCompositor->m_pLastWindow;
@ -342,6 +423,28 @@ std::string layersRequest(HyprCtl::eHyprCtlOutputFormat format) {
return result;
}
std::string layoutsRequest(HyprCtl::eHyprCtlOutputFormat format) {
std::string result = "";
if (format == HyprCtl::FORMAT_JSON) {
result += "[";
for (auto& m : g_pLayoutManager->getAllLayoutNames()) {
result += std::format(
R"#(
"{}",)#",
m);
}
trimTrailingComma(result);
result += "\n]\n";
} else {
for (auto& m : g_pLayoutManager->getAllLayoutNames()) {
result += std::format("{}\n", m);
}
}
return result;
}
std::string devicesRequest(HyprCtl::eHyprCtlOutputFormat format) {
std::string result = "";
@ -554,6 +657,20 @@ std::string animationsRequest(HyprCtl::eHyprCtlOutputFormat format) {
return ret;
}
std::string rollinglogRequest(HyprCtl::eHyprCtlOutputFormat format) {
std::string result = "";
if (format == HyprCtl::FORMAT_JSON) {
result += "[\n\"log\":\"";
result += escapeJSONStrings(Debug::rollingLog);
result += "\"]";
} else {
result = Debug::rollingLog;
}
return result;
}
std::string globalShortcutsRequest(HyprCtl::eHyprCtlOutputFormat format) {
std::string ret = "";
const auto SHORTCUTS = g_pProtocolManager->m_pGlobalShortcutsProtocolManager->getAllShortcuts();
@ -632,15 +749,12 @@ std::string versionRequest(HyprCtl::eHyprCtlOutputFormat format) {
if (format == HyprCtl::eHyprCtlOutputFormat::FORMAT_NORMAL) {
std::string result = "Hyprland, built from branch " + std::string(GIT_BRANCH) + " at commit " + GIT_COMMIT_HASH + " " + GIT_DIRTY + " (" + commitMsg +
").\nTag: " + GIT_TAG + "\n\nflags: (if any)\n";
").\nDate: " + GIT_COMMIT_DATE + "\nTag: " + GIT_TAG + "\n\nflags: (if any)\n";
#ifdef LEGACY_RENDERER
result += "legacyrenderer\n";
#endif
#ifndef NDEBUG
result += "debug\n";
#endif
#ifdef HYPRLAND_DEBUG
#ifndef ISDEBUG
result += "debug\n";
#endif
#ifdef NO_XWAYLAND
@ -655,17 +769,15 @@ std::string versionRequest(HyprCtl::eHyprCtlOutputFormat format) {
"commit": "{}",
"dirty": {},
"commit_message": "{}",
"commit_date": "{}",
"tag": "{}",
"flags": [)#",
GIT_BRANCH, GIT_COMMIT_HASH, (strcmp(GIT_DIRTY, "dirty") == 0 ? "true" : "false"), escapeJSONStrings(commitMsg), GIT_TAG);
GIT_BRANCH, GIT_COMMIT_HASH, (strcmp(GIT_DIRTY, "dirty") == 0 ? "true" : "false"), escapeJSONStrings(commitMsg), GIT_COMMIT_DATE, GIT_TAG);
#ifdef LEGACY_RENDERER
result += "\"legacyrenderer\",";
#endif
#ifndef NDEBUG
result += "\"debug\",";
#endif
#ifdef HYPRLAND_DEBUG
#ifndef ISDEBUG
result += "\"debug\",";
#endif
#ifdef NO_XWAYLAND
@ -682,6 +794,39 @@ std::string versionRequest(HyprCtl::eHyprCtlOutputFormat format) {
return ""; // make the compiler happy
}
std::string systemInfoRequest() {
std::string result = versionRequest(HyprCtl::eHyprCtlOutputFormat::FORMAT_NORMAL);
result += "\n\nSystem Information:\n";
struct utsname unameInfo;
uname(&unameInfo);
result += "System name: " + std::string{unameInfo.sysname} + "\n";
result += "Node name: " + std::string{unameInfo.nodename} + "\n";
result += "Release: " + std::string{unameInfo.release} + "\n";
result += "Version: " + std::string{unameInfo.version} + "\n";
result += "\n\n";
#if defined(__DragonFly__) || defined(__FreeBSD__)
const std::string GPUINFO = execAndGet("pciconf -lv | fgrep -A4 vga");
#else
const std::string GPUINFO = execAndGet("lspci -vnn | grep VGA");
#endif
result += "GPU information: \n" + GPUINFO + "\n\n";
result += "os-release: " + execAndGet("cat /etc/os-release") + "\n\n";
result += "plugins:\n";
for (auto& pl : g_pPluginSystem->getAllPlugins()) {
result += std::format(" {} by {} ver {}\n", pl->name, pl->author, pl->version);
}
return result;
}
std::string dispatchRequest(std::string in) {
// get rid of the dispatch keyword
in = in.substr(in.find_first_of(' ') + 1);
@ -952,7 +1097,7 @@ std::string dispatchSetProp(std::string request) {
bool lock = false;
if (vars.size() > 4) {
if (vars[4].find("lock") == 0) {
if (vars[4].starts_with("lock")) {
lock = true;
}
}
@ -993,15 +1138,19 @@ std::string dispatchSetProp(std::string request) {
} else if (PROP == "alphainactive") {
PWINDOW->m_sSpecialRenderData.alphaInactive.forceSetIgnoreLocked(std::stof(VAL), lock);
} else if (PROP == "activebordercolor") {
PWINDOW->m_sSpecialRenderData.activeBorderColor.forceSetIgnoreLocked(configStringToInt(VAL), lock);
PWINDOW->m_sSpecialRenderData.activeBorderColor.forceSetIgnoreLocked(CGradientValueData(CColor(configStringToInt(VAL))), lock);
} else if (PROP == "inactivebordercolor") {
PWINDOW->m_sSpecialRenderData.inactiveBorderColor.forceSetIgnoreLocked(configStringToInt(VAL), lock);
PWINDOW->m_sSpecialRenderData.inactiveBorderColor.forceSetIgnoreLocked(CGradientValueData(CColor(configStringToInt(VAL))), lock);
} else if (PROP == "forcergbx") {
PWINDOW->m_sAdditionalConfigData.forceRGBX.forceSetIgnoreLocked(configStringToInt(VAL), lock);
} else if (PROP == "bordersize") {
PWINDOW->m_sSpecialRenderData.borderSize.forceSetIgnoreLocked(configStringToInt(VAL), lock);
} else if (PROP == "keepaspectratio") {
PWINDOW->m_sAdditionalConfigData.keepAspectRatio.forceSetIgnoreLocked(configStringToInt(VAL), lock);
} else if (PROP == "immediate") {
PWINDOW->m_sAdditionalConfigData.forceTearing.forceSetIgnoreLocked(configStringToInt(VAL), lock);
} else if (PROP == "nearestneighbor") {
PWINDOW->m_sAdditionalConfigData.nearestNeighbor.forceSetIgnoreLocked(configStringToInt(VAL), lock);
} else {
return "prop not found";
}
@ -1059,6 +1208,32 @@ std::string dispatchGetOption(std::string request, HyprCtl::eHyprCtlOutputFormat
}
}
std::string decorationRequest(std::string request, HyprCtl::eHyprCtlOutputFormat format) {
CVarList vars(request, 0, ' ');
const auto PWINDOW = g_pCompositor->getWindowByRegex(vars[1]);
if (!PWINDOW)
return "none";
std::string result = "";
if (format == HyprCtl::FORMAT_JSON) {
result += "[";
for (auto& wd : PWINDOW->m_dWindowDecorations) {
result += "{\n\"decorationName\": \"" + wd->getDisplayName() + "\",\n\"priority\": " + std::to_string(wd->getPositioningInfo().priority) + "\n},";
}
trimTrailingComma(result);
result += "]";
} else {
result = +"Decoration\tPriority\n";
for (auto& wd : PWINDOW->m_dWindowDecorations) {
result += wd->getDisplayName() + "\t" + std::to_string(wd->getPositioningInfo().priority) + "\n";
}
}
return result;
}
void createOutputIter(wlr_backend* backend, void* data) {
const auto DATA = (std::pair<std::string, bool>*)data;
@ -1155,7 +1330,7 @@ std::string dispatchPlugin(std::string request) {
const auto PLUGIN = g_pPluginSystem->loadPlugin(PATH);
if (!PLUGIN)
return "error in loading plugin";
return "error in loading plugin, last error: " + g_pPluginSystem->m_szLastError;
} else if (OPERATION == "unload") {
if (vars.size() < 3)
return "not enough args";
@ -1227,13 +1402,20 @@ std::string getReply(std::string request) {
auto format = HyprCtl::FORMAT_NORMAL;
// process flags for non-batch requests
if (!request.contains("[[BATCH]]") && request.contains("/")) {
if (!request.starts_with("[[BATCH]]") && request.contains("/")) {
long unsigned int sepIndex = 0;
for (const auto& c : request) {
if (c == '/') { // stop at separator
break;
}
// after whitespace assume the first word as a keyword,
// so its value can have slashes (e.g., a path)
if (c == ' ') {
sepIndex = request.size();
break;
}
sepIndex++;
if (c == 'j')
@ -1244,10 +1426,12 @@ std::string getReply(std::string request) {
request = request.substr(sepIndex + 1); // remove flags and separator so we can compare the rest of the string
}
if (request == "monitors")
return monitorsRequest(format);
if (request.starts_with("monitors"))
return monitorsRequest(request, format);
else if (request == "workspaces")
return workspacesRequest(format);
else if (request == "workspacerules")
return workspaceRulesRequest(format);
else if (request == "activeworkspace")
return activeWorkspaceRequest(format);
else if (request == "clients")
@ -1260,7 +1444,7 @@ std::string getReply(std::string request) {
return layersRequest(format);
else if (request == "version")
return versionRequest(format);
else if (request.find("reload") == 0)
else if (request.starts_with("reload"))
return reloadRequest(request);
else if (request == "devices")
return devicesRequest(format);
@ -1272,29 +1456,37 @@ std::string getReply(std::string request) {
return bindsRequest(format);
else if (request == "globalshortcuts")
return globalShortcutsRequest(format);
else if (request == "systeminfo")
return systemInfoRequest();
else if (request == "animations")
return animationsRequest(format);
else if (request.find("plugin") == 0)
else if (request == "rollinglog")
return rollinglogRequest(format);
else if (request == "layouts")
return layoutsRequest(format);
else if (request.starts_with("plugin"))
return dispatchPlugin(request);
else if (request.find("notify") == 0)
else if (request.starts_with("notify"))
return dispatchNotify(request);
else if (request.find("setprop") == 0)
else if (request.starts_with("setprop"))
return dispatchSetProp(request);
else if (request.find("seterror") == 0)
else if (request.starts_with("seterror"))
return dispatchSeterror(request);
else if (request.find("switchxkblayout") == 0)
else if (request.starts_with("switchxkblayout"))
return switchXKBLayoutRequest(request);
else if (request.find("output") == 0)
else if (request.starts_with("output"))
return dispatchOutput(request);
else if (request.find("dispatch") == 0)
else if (request.starts_with("dispatch"))
return dispatchRequest(request);
else if (request.find("keyword") == 0)
else if (request.starts_with("keyword"))
return dispatchKeyword(request);
else if (request.find("setcursor") == 0)
else if (request.starts_with("setcursor"))
return dispatchSetCursor(request);
else if (request.find("getoption") == 0)
else if (request.starts_with("getoption"))
return dispatchGetOption(request, format);
else if (request.find("[[BATCH]]") == 0)
else if (request.starts_with("decorations"))
return decorationRequest(request, format);
else if (request.starts_with("[[BATCH]]"))
return dispatchBatch(request);
return "unknown request";
@ -1308,14 +1500,14 @@ int hyprCtlFDTick(int fd, uint32_t mask, void* data) {
if (mask & WL_EVENT_ERROR || mask & WL_EVENT_HANGUP)
return 0;
sockaddr_in clientAddress;
socklen_t clientSize = sizeof(clientAddress);
sockaddr_in clientAddress;
socklen_t clientSize = sizeof(clientAddress);
const auto ACCEPTEDCONNECTION = accept4(HyprCtl::iSocketFD, (sockaddr*)&clientAddress, &clientSize, SOCK_CLOEXEC);
const auto ACCEPTEDCONNECTION = accept4(HyprCtl::iSocketFD, (sockaddr*)&clientAddress, &clientSize, SOCK_CLOEXEC);
char readBuffer[1024];
std::array<char, 1024> readBuffer;
fd_set fdset;
fd_set fdset;
FD_ZERO(&fdset);
FD_SET(ACCEPTEDCONNECTION, &fdset);
timeval timeout = {.tv_sec = 0, .tv_usec = 5000};
@ -1326,10 +1518,17 @@ int hyprCtlFDTick(int fd, uint32_t mask, void* data) {
return 0;
}
auto messageSize = read(ACCEPTEDCONNECTION, readBuffer, 1024);
readBuffer[messageSize == 1024 ? 1023 : messageSize] = '\0';
std::string request(readBuffer);
std::string request;
while (true) {
readBuffer.fill(0);
auto messageSize = read(ACCEPTEDCONNECTION, readBuffer.data(), 1023);
if (messageSize < 1)
break;
std::string recvd = readBuffer.data();
request += recvd;
if (messageSize < 1023)
break;
}
std::string reply = "";

View file

@ -233,6 +233,6 @@ void CHyprDebugOverlay::draw() {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, PMONITOR->vecPixelSize.x, PMONITOR->vecPixelSize.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA);
wlr_box pMonBox = {0, 0, PMONITOR->vecPixelSize.x, PMONITOR->vecPixelSize.y};
CBox pMonBox = {0, 0, PMONITOR->vecPixelSize.x, PMONITOR->vecPixelSize.y};
g_pHyprOpenGL->renderTexture(m_tTexture, &pMonBox, 1.f);
}

View file

@ -24,7 +24,7 @@ class CHyprMonitorDebugOverlay {
std::deque<float> m_dLastAnimationTicks;
std::chrono::high_resolution_clock::time_point m_tpLastFrame;
CMonitor* m_pMonitor = nullptr;
wlr_box m_wbLastDrawnBox;
CBox m_wbLastDrawnBox;
friend class CHyprRenderer;
};

View file

@ -3,7 +3,7 @@
#include <pango/pangocairo.h>
CHyprNotificationOverlay::CHyprNotificationOverlay() {
g_pHookSystem->hookDynamic("focusedMon", [&](void* self, std::any param) {
g_pHookSystem->hookDynamic("focusedMon", [&](void* self, SCallbackInfo& info, std::any param) {
if (m_dNotifications.size() == 0)
return;
@ -44,9 +44,13 @@ void CHyprNotificationOverlay::addNotification(const std::string& text, const CC
PNOTIF->started.reset();
PNOTIF->timeMs = timeMs;
PNOTIF->icon = icon;
for (auto& m : g_pCompositor->m_vMonitors) {
g_pCompositor->scheduleFrameForMonitor(m.get());
}
}
wlr_box CHyprNotificationOverlay::drawNotifications(CMonitor* pMonitor) {
CBox CHyprNotificationOverlay::drawNotifications(CMonitor* pMonitor) {
static constexpr auto ANIM_DURATION_MS = 600.0;
static constexpr auto ANIM_LAG_MS = 100.0;
static constexpr auto NOTIF_LEFTBAR_SIZE = 5.0;
@ -166,7 +170,7 @@ wlr_box CHyprNotificationOverlay::drawNotifications(CMonitor* pMonitor) {
// cleanup notifs
std::erase_if(m_dNotifications, [](const auto& notif) { return notif->started.getMillis() > notif->timeMs; });
return wlr_box{(int)(pMonitor->vecPosition.x + pMonitor->vecSize.x - maxWidth - 20), (int)pMonitor->vecPosition.y, (int)maxWidth + 20, (int)offsetY + 10};
return CBox{(int)(pMonitor->vecPosition.x + pMonitor->vecSize.x - maxWidth - 20), (int)pMonitor->vecPosition.y, (int)maxWidth + 20, (int)offsetY + 10};
}
void CHyprNotificationOverlay::draw(CMonitor* pMonitor) {
@ -197,7 +201,7 @@ void CHyprNotificationOverlay::draw(CMonitor* pMonitor) {
cairo_surface_flush(m_pCairoSurface);
wlr_box damage = drawNotifications(pMonitor);
CBox damage = drawNotifications(pMonitor);
g_pHyprRenderer->damageBox(&damage);
g_pHyprRenderer->damageBox(&m_bLastDamage);
@ -220,6 +224,6 @@ void CHyprNotificationOverlay::draw(CMonitor* pMonitor) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pMonitor->vecPixelSize.x, pMonitor->vecPixelSize.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA);
wlr_box pMonBox = {0, 0, pMonitor->vecPixelSize.x, pMonitor->vecPixelSize.y};
CBox pMonBox = {0, 0, pMonitor->vecPixelSize.x, pMonitor->vecPixelSize.y};
g_pHyprOpenGL->renderTexture(m_tTexture, &pMonBox, 1.f);
}

View file

@ -10,16 +10,15 @@
#include <cairo/cairo.h>
enum eIconBackend
{
enum eIconBackend {
ICONS_BACKEND_NONE = 0,
ICONS_BACKEND_NF,
ICONS_BACKEND_FA
};
static const std::array<std::array<std::string, ICON_NONE + 1>, 3 /* backends */> ICONS_ARRAY = {
std::array<std::string, ICON_NONE + 1>{"[!]", "[i]", "[Hint]", "[Err]", "[?]", "[ok]", ""}, std::array<std::string, ICON_NONE + 1>{"", "", "", "", "", "󰸞", ""},
std::array<std::string, ICON_NONE + 1>{"", "", "", "", "", ""}};
std::array<std::string, ICON_NONE + 1>{"[!]", "[i]", "[Hint]", "[Err]", "[?]", "[ok]", ""},
std::array<std::string, ICON_NONE + 1>{"", "", "", "", "", "󰸞", ""}, std::array<std::string, ICON_NONE + 1>{"", "", "", "", "", ""}};
static const std::array<CColor, ICON_NONE + 1> ICONS_COLORS = {CColor{255.0 / 255.0, 204 / 255.0, 102 / 255.0, 1.0},
CColor{128 / 255.0, 255 / 255.0, 255 / 255.0, 1.0},
CColor{179 / 255.0, 255 / 255.0, 204 / 255.0, 1.0},
@ -44,8 +43,8 @@ class CHyprNotificationOverlay {
void addNotification(const std::string& text, const CColor& color, const float timeMs, const eIcons icon = ICON_NONE);
private:
wlr_box drawNotifications(CMonitor* pMonitor);
wlr_box m_bLastDamage;
CBox drawNotifications(CMonitor* pMonitor);
CBox m_bLastDamage;
std::deque<std::unique_ptr<SNotification>> m_dNotifications;

View file

@ -10,19 +10,24 @@ void Debug::init(const std::string& IS) {
}
void Debug::wlrLog(wlr_log_importance level, const char* fmt, va_list args) {
char* outputStr = nullptr;
if (level > wlr_log_get_verbosity())
return;
std::ofstream ofs;
ofs.open(logFile, std::ios::out | std::ios::app);
char* outputStr = nullptr;
vasprintf(&outputStr, fmt, args);
std::string output = std::string(outputStr);
free(outputStr);
ofs << "[wlr] " << output << "\n";
rollingLog += output + "\n";
ofs.close();
if (!disableLogs || !*disableLogs) {
std::ofstream ofs;
ofs.open(logFile, std::ios::out | std::ios::app);
ofs << "[wlr] " << output << "\n";
ofs.close();
}
if (!disableStdout)
std::cout << output << "\n";

View file

@ -1,13 +1,14 @@
#pragma once
#include <string>
#include <wlr/util/log.h>
#include <format>
#include <iostream>
#include <fstream>
#include <chrono>
#include "../includes.hpp"
#include "../helpers/MiscFunctions.hpp"
#define LOGMESSAGESIZE 1024
#define LOGMESSAGESIZE 1024
#define ROLLING_LOG_SIZE 4096
enum LogLevel {
NONE = -1,
@ -25,14 +26,17 @@ namespace Debug {
inline int64_t* disableTime = nullptr;
inline bool disableStdout = false;
inline bool trace = false;
inline bool shuttingDown = false;
inline std::string rollingLog = ""; // rolling log contains the ROLLING_LOG_SIZE tail of the log
void init(const std::string& IS);
template <typename... Args>
void log(LogLevel level, std::format_string<Args...> fmt, Args&&... args) {
if (disableLogs && *disableLogs)
if (level == TRACE && !trace)
return;
if (level == TRACE && !trace)
if (shuttingDown)
return;
std::string logMsg = "";
@ -47,10 +51,6 @@ namespace Debug {
default: break;
}
// log to a file
std::ofstream ofs;
ofs.open(logFile, std::ios::out | std::ios::app);
// print date and time to the ofs
if (disableTime && !*disableTime) {
#ifndef _LIBCPP_VERSION
@ -69,9 +69,18 @@ namespace Debug {
// 3. this is actually what std::format in stdlib does
logMsg += std::vformat(fmt.get(), std::make_format_args(args...));
ofs << logMsg << "\n";
rollingLog += logMsg + "\n";
if (rollingLog.size() > ROLLING_LOG_SIZE)
rollingLog = rollingLog.substr(rollingLog.size() - ROLLING_LOG_SIZE);
ofs.close();
if (!disableLogs || !*disableLogs) {
// log to a file
std::ofstream ofs;
ofs.open(logFile, std::ios::out | std::ios::app);
ofs << logMsg << "\n";
ofs.close();
}
// log it to the stdout too.
if (!disableStdout)
@ -79,4 +88,4 @@ namespace Debug {
}
void wlrLog(wlr_log_importance level, const char* fmt, va_list args);
};
};

View file

@ -13,15 +13,6 @@ inline PFNGLGETQUERYOBJECTUI64VEXTPROC glGetQueryObjectui64v;
#include "../../subprojects/tracy/public/tracy/TracyOpenGL.hpp"
inline void loadGLProc(void* pProc, const char* name) {
void* proc = (void*)eglGetProcAddress(name);
if (proc == NULL) {
Debug::log(CRIT, "[Tracy GPU Profiling] eglGetProcAddress({}) failed", name);
abort();
}
*(void**)pProc = proc;
}
#define TRACY_GPU_CONTEXT TracyGpuContext
#define TRACY_GPU_ZONE(e) TracyGpuZone(e)
#define TRACY_GPU_COLLECT TracyGpuCollect

View file

@ -42,7 +42,7 @@ namespace Events {
DYNLISTENFUNC(repositionPopupXDG);
// Surface XDG (window)
LISTENER(newXDGSurface);
LISTENER(newXDGToplevel);
LISTENER(activateXDG);
// Window events
@ -62,6 +62,7 @@ namespace Events {
DYNLISTENFUNC(setOverrideRedirect);
DYNLISTENFUNC(associateX11);
DYNLISTENFUNC(dissociateX11);
DYNLISTENFUNC(ackConfigure);
// Window subsurfaces
// LISTENER(newSubsurfaceWindow);
@ -120,10 +121,6 @@ namespace Events {
DYNLISTENFUNC(destroyDragIcon);
DYNLISTENFUNC(commitDragIcon);
// Inhibit
LISTENER(InhibitActivate);
LISTENER(InhibitDeactivate);
// Deco XDG
LISTENER(NewXDGDeco);
@ -174,4 +171,7 @@ namespace Events {
// Cursor shape
LISTENER(setCursorShape);
// Tearing hints
LISTENER(newTearingHint);
};

View file

@ -95,8 +95,8 @@ void Events::listener_destroyLayerSurface(void* owner, void* data) {
PMONITOR->scheduledRecalc = true;
// and damage
wlr_box geomFixed = {layersurface->geometry.x + PMONITOR->vecPosition.x, layersurface->geometry.y + PMONITOR->vecPosition.y, layersurface->geometry.width,
layersurface->geometry.height};
CBox geomFixed = {layersurface->geometry.x + PMONITOR->vecPosition.x, layersurface->geometry.y + PMONITOR->vecPosition.y, layersurface->geometry.width,
layersurface->geometry.height};
g_pHyprRenderer->damageBox(&geomFixed);
}
@ -142,7 +142,7 @@ void Events::listener_mapLayerSurface(void* owner, void* data) {
wlr_surface_send_enter(layersurface->layerSurface->surface, layersurface->layerSurface->output);
const bool GRABSFOCUS = layersurface->layerSurface->current.keyboard_interactive &&
const bool GRABSFOCUS = layersurface->layerSurface->current.keyboard_interactive != ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE &&
// don't focus if constrained
(!g_pCompositor->m_sSeat.mouse || !g_pCompositor->m_sSeat.mouse->currentConstraint);
@ -157,8 +157,8 @@ void Events::listener_mapLayerSurface(void* owner, void* data) {
layersurface->position = Vector2D(layersurface->geometry.x, layersurface->geometry.y);
wlr_box geomFixed = {layersurface->geometry.x + PMONITOR->vecPosition.x, layersurface->geometry.y + PMONITOR->vecPosition.y, layersurface->geometry.width,
layersurface->geometry.height};
CBox geomFixed = {layersurface->geometry.x + PMONITOR->vecPosition.x, layersurface->geometry.y + PMONITOR->vecPosition.y, layersurface->geometry.width,
layersurface->geometry.height};
g_pHyprRenderer->damageBox(&geomFixed);
const auto WORKSPACE = g_pCompositor->getWorkspaceByID(PMONITOR->activeWorkspace);
const bool FULLSCREEN = WORKSPACE->m_bHasFullscreenWindow && WORKSPACE->m_efFullscreenMode == FULLSCREEN_FULL;
@ -171,7 +171,8 @@ void Events::listener_mapLayerSurface(void* owner, void* data) {
g_pEventManager->postEvent(SHyprIPCEvent{"openlayer", std::string(layersurface->layerSurface->_namespace ? layersurface->layerSurface->_namespace : "")});
EMIT_HOOK_EVENT("openLayer", layersurface);
g_pProtocolManager->m_pFractionalScaleProtocolManager->setPreferredScaleForSurface(layersurface->layerSurface->surface, PMONITOR->scale);
g_pCompositor->setPreferredScaleForSurface(layersurface->layerSurface->surface, PMONITOR->scale);
g_pCompositor->setPreferredTransformForSurface(layersurface->layerSurface->surface, PMONITOR->transform);
}
void Events::listener_unmapLayerSurface(void* owner, void* data) {
@ -246,13 +247,15 @@ void Events::listener_unmapLayerSurface(void* owner, void* data) {
}
}
wlr_box geomFixed = {layersurface->geometry.x + PMONITOR->vecPosition.x, layersurface->geometry.y + PMONITOR->vecPosition.y, layersurface->geometry.width,
layersurface->geometry.height};
CBox geomFixed = {layersurface->geometry.x + PMONITOR->vecPosition.x, layersurface->geometry.y + PMONITOR->vecPosition.y, layersurface->geometry.width,
layersurface->geometry.height};
g_pHyprRenderer->damageBox(&geomFixed);
geomFixed = {layersurface->geometry.x + (int)PMONITOR->vecPosition.x, layersurface->geometry.y + (int)PMONITOR->vecPosition.y,
(int)layersurface->layerSurface->surface->current.width, (int)layersurface->layerSurface->surface->current.height};
g_pHyprRenderer->damageBox(&geomFixed);
g_pInputManager->sendMotionEventsToFocused();
}
void Events::listener_commitLayerSurface(void* owner, void* data) {
@ -269,7 +272,7 @@ void Events::listener_commitLayerSurface(void* owner, void* data) {
if (layersurface->layer == ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND || layersurface->layer == ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM)
g_pHyprOpenGL->markBlurDirtyForMonitor(PMONITOR); // so that blur is recalc'd
wlr_box geomFixed = {layersurface->geometry.x, layersurface->geometry.y, layersurface->geometry.width, layersurface->geometry.height};
CBox geomFixed = {layersurface->geometry.x, layersurface->geometry.y, layersurface->geometry.width, layersurface->geometry.height};
g_pHyprRenderer->damageBox(&geomFixed);
// fix if it changed its mon
@ -342,5 +345,6 @@ void Events::listener_commitLayerSurface(void* owner, void* data) {
g_pHyprRenderer->damageSurface(layersurface->layerSurface->surface, layersurface->position.x, layersurface->position.y);
g_pProtocolManager->m_pFractionalScaleProtocolManager->setPreferredScaleForSurface(layersurface->layerSurface->surface, PMONITOR->scale);
g_pCompositor->setPreferredScaleForSurface(layersurface->layerSurface->surface, PMONITOR->scale);
g_pCompositor->setPreferredTransformForSurface(layersurface->layerSurface->surface, PMONITOR->transform);
}

View file

@ -156,20 +156,6 @@ void Events::listener_commitDragIcon(void* owner, void* data) {
Debug::log(LOG, "Drag icon committed.");
}
void Events::listener_InhibitActivate(wl_listener* listener, void* data) {
Debug::log(LOG, "Activated exclusive for {:x}.", (uintptr_t)g_pCompositor->m_sSeat.exclusiveClient);
g_pInputManager->refocus();
g_pCompositor->m_sSeat.exclusiveClient = g_pCompositor->m_sWLRInhibitMgr->active_client;
}
void Events::listener_InhibitDeactivate(wl_listener* listener, void* data) {
Debug::log(LOG, "Deactivated exclusive.");
g_pCompositor->m_sSeat.exclusiveClient = nullptr;
g_pInputManager->refocus();
}
void Events::listener_RendererDestroy(wl_listener* listener, void* data) {
Debug::log(LOG, "!!Renderer destroyed!!");
}
@ -237,3 +223,43 @@ void Events::listener_setCursorShape(wl_listener* listener, void* data) {
g_pInputManager->processMouseRequest(E);
}
void Events::listener_newTearingHint(wl_listener* listener, void* data) {
const auto TCTL = (wlr_tearing_control_v1*)data;
const auto PWINDOW = g_pCompositor->getWindowFromSurface(TCTL->surface);
if (!PWINDOW) {
Debug::log(ERR, "Tearing hint {} was attached to an unknown surface", (uintptr_t)data);
return;
}
Debug::log(LOG, "New tearing hint for window {} at {}", PWINDOW, (uintptr_t)data);
const auto NEWCTRL = g_pHyprRenderer->m_vTearingControllers.emplace_back(std::make_unique<STearingController>()).get();
NEWCTRL->pWlrHint = (wlr_tearing_control_v1*)data;
NEWCTRL->hyprListener_destroy.initCallback(
&NEWCTRL->pWlrHint->events.destroy,
[&](void* owner, void* data) {
Debug::log(LOG, "Destroyed {} tearing hint", (uintptr_t)((STearingController*)owner)->pWlrHint);
std::erase_if(g_pHyprRenderer->m_vTearingControllers, [&](const auto& other) { return other.get() == owner; });
},
NEWCTRL, "TearingController");
NEWCTRL->hyprListener_set.initCallback(
&NEWCTRL->pWlrHint->events.set_hint,
[&](void* owner, void* data) {
const auto TEARINGHINT = (STearingController*)owner;
const auto PWINDOW = g_pCompositor->getWindowFromSurface(TEARINGHINT->pWlrHint->surface);
if (PWINDOW) {
PWINDOW->m_bTearingHint = (bool)TEARINGHINT->pWlrHint->current;
Debug::log(LOG, "Hint {} (window {}) set tearing hint to {}", (uintptr_t)TEARINGHINT->pWlrHint, PWINDOW, (uint32_t)TEARINGHINT->pWlrHint->current);
}
},
NEWCTRL, "TearingController");
}

View file

@ -22,14 +22,18 @@ void Events::listener_change(wl_listener* listener, void* data) {
if (!CONFIG)
return;
for (auto& m : g_pCompositor->m_vMonitors) {
for (auto& m : g_pCompositor->m_vRealMonitors) {
if (!m->output)
continue;
if (g_pCompositor->m_pUnsafeOutput == m.get())
continue;
const auto CONFIGHEAD = wlr_output_configuration_head_v1_create(CONFIG, m->output);
wlr_box BOX;
wlr_output_layout_get_box(g_pCompositor->m_sWLROutputLayout, m->output, &BOX);
CBox BOX;
wlr_output_layout_get_box(g_pCompositor->m_sWLROutputLayout, m->output, BOX.pWlr());
BOX.applyFromWlr();
//m->vecSize.x = BOX.width;
// m->vecSize.y = BOX.height;
@ -67,52 +71,31 @@ void Events::listener_newOutput(wl_listener* listener, void* data) {
return;
}
if (g_pCompositor->m_bUnsafeState)
Debug::log(WARN, "Recovering from an unsafe state. May you be lucky.");
// add it to real
std::shared_ptr<CMonitor>* PNEWMONITORWRAP = nullptr;
PNEWMONITORWRAP = &g_pCompositor->m_vRealMonitors.emplace_back(std::make_shared<CMonitor>());
if (std::string("HEADLESS-1") == OUTPUT->name)
g_pCompositor->m_pUnsafeOutput = PNEWMONITORWRAP->get();
(*PNEWMONITORWRAP)->ID = g_pCompositor->getNextAvailableMonitorID(OUTPUT->name);
(*PNEWMONITORWRAP)->output = OUTPUT;
const bool FALLBACK = g_pCompositor->m_pUnsafeOutput ? OUTPUT == g_pCompositor->m_pUnsafeOutput->output : false;
(*PNEWMONITORWRAP)->ID = FALLBACK ? -1 : g_pCompositor->getNextAvailableMonitorID(OUTPUT->name);
const auto PNEWMONITOR = PNEWMONITORWRAP->get();
PNEWMONITOR->isUnsafeFallback = FALLBACK;
const auto PNEWMONITOR = PNEWMONITORWRAP->get();
if (!FALLBACK)
PNEWMONITOR->onConnect(false);
PNEWMONITOR->output = OUTPUT;
PNEWMONITOR->m_pThisWrap = PNEWMONITORWRAP;
if (!PNEWMONITOR->m_bEnabled || FALLBACK)
return;
PNEWMONITOR->onConnect(false);
// ready to process if we have a real monitor
if ((!g_pHyprRenderer->m_pMostHzMonitor || PNEWMONITOR->refreshRate > g_pHyprRenderer->m_pMostHzMonitor->refreshRate) && PNEWMONITOR->m_bEnabled)
g_pHyprRenderer->m_pMostHzMonitor = PNEWMONITOR;
// wlroots will instantly call this handler before we get a return to the wlr_output* in CCompositor::enterUnsafeState
const bool PROBABLYFALLBACK = (g_pCompositor->m_bUnsafeState && !g_pCompositor->m_pUnsafeOutput) || OUTPUT == g_pCompositor->m_pUnsafeOutput;
// ready to process if we have a real monitor
if (PNEWMONITOR->m_bEnabled && !PROBABLYFALLBACK) {
// leave unsafe state
if (g_pCompositor->m_bUnsafeState) {
// recover workspaces
std::vector<CWorkspace*> wsp;
for (auto& ws : g_pCompositor->m_vWorkspaces) {
wsp.push_back(ws.get());
}
for (auto& ws : wsp) {
// because this can realloc the vec
g_pCompositor->moveWorkspaceToMonitor(ws, PNEWMONITOR);
}
g_pHyprRenderer->m_pMostHzMonitor = PNEWMONITOR;
const auto POS = PNEWMONITOR->middle();
if (g_pCompositor->m_sSeat.mouse)
wlr_cursor_warp(g_pCompositor->m_sWLRCursor, g_pCompositor->m_sSeat.mouse->mouse, POS.x, POS.y);
}
g_pCompositor->m_bReadyToProcess = true;
}
g_pCompositor->m_bReadyToProcess = true;
g_pConfigManager->m_bWantsMonitorReload = true;
g_pCompositor->scheduleFrameForMonitor(PNEWMONITOR);
@ -126,7 +109,7 @@ void Events::listener_newOutput(wl_listener* listener, void* data) {
for (auto& w : g_pCompositor->m_vWindows) {
if (w->m_iMonitorID == PNEWMONITOR->ID) {
w->m_iLastSurfaceMonitorID = -1;
w->updateSurfaceOutputs();
w->updateSurfaceScaleTransformDetails();
}
}
}
@ -138,7 +121,9 @@ void Events::listener_monitorFrame(void* owner, void* data) {
if ((g_pCompositor->m_sWLRSession && !g_pCompositor->m_sWLRSession->active) || !g_pCompositor->m_bSessionActive || g_pCompositor->m_bUnsafeState) {
Debug::log(WARN, "Attempted to render frame on inactive session!");
if (g_pCompositor->m_bUnsafeState && PMONITOR->output != g_pCompositor->m_pUnsafeOutput) {
if (g_pCompositor->m_bUnsafeState && std::ranges::any_of(g_pCompositor->m_vMonitors.begin(), g_pCompositor->m_vMonitors.end(), [&](auto& m) {
return m->output != g_pCompositor->m_pUnsafeOutput->output;
})) {
// restore from unsafe state
g_pCompositor->leaveUnsafeState();
}
@ -149,12 +134,25 @@ void Events::listener_monitorFrame(void* owner, void* data) {
if (!PMONITOR->m_bEnabled)
return;
g_pHyprRenderer->recheckSolitaryForMonitor(PMONITOR);
PMONITOR->tearingState.busy = false;
if (PMONITOR->tearingState.activelyTearing && PMONITOR->solitaryClient /* can be invalidated by a recheck */) {
if (!PMONITOR->tearingState.frameScheduledWhileBusy)
return; // we did not schedule a frame yet to be displayed, but we are tearing. Why render?
PMONITOR->tearingState.nextRenderTorn = true;
PMONITOR->tearingState.frameScheduledWhileBusy = false;
}
static auto* const PENABLERAT = &g_pConfigManager->getConfigValuePtr("misc:render_ahead_of_time")->intValue;
static auto* const PRATSAFE = &g_pConfigManager->getConfigValuePtr("misc:render_ahead_safezone")->intValue;
PMONITOR->lastPresentationTimer.reset();
if (*PENABLERAT) {
if (*PENABLERAT && !PMONITOR->tearingState.nextRenderTorn) {
if (!PMONITOR->RATScheduled) {
// render
g_pHyprRenderer->renderMonitor(PMONITOR);
@ -200,14 +198,11 @@ void Events::listener_monitorDestroy(void* owner, void* data) {
Debug::log(LOG, "Destroy called for monitor {}", pMonitor->output->name);
pMonitor->onDisconnect();
pMonitor->onDisconnect(true);
pMonitor->output = nullptr;
pMonitor->m_bRenderingInitPassed = false;
if (g_pCompositor->m_pUnsafeOutput == OUTPUT)
g_pCompositor->m_pUnsafeOutput = nullptr;
Debug::log(LOG, "Removing monitor {} from realMonitors", pMonitor->szName);
std::erase_if(g_pCompositor->m_vRealMonitors, [&](std::shared_ptr<CMonitor>& el) { return el.get() == pMonitor; });
@ -238,7 +233,7 @@ void Events::listener_monitorCommit(void* owner, void* data) {
const auto E = (wlr_output_event_commit*)data;
if (E->committed & WLR_OUTPUT_STATE_BUFFER) {
if (E->state->committed & WLR_OUTPUT_STATE_BUFFER) {
g_pProtocolManager->m_pScreencopyProtocolManager->onOutputCommit(PMONITOR, E);
g_pProtocolManager->m_pToplevelExportProtocolManager->onOutputCommit(PMONITOR, E);
}

View file

@ -64,9 +64,9 @@ void createNewPopup(wlr_xdg_popup* popup, SXDGPopup* pHyprPopup) {
const auto PMONITOR = g_pCompositor->m_pLastMonitor;
wlr_box box = {.x = PMONITOR->vecPosition.x - pHyprPopup->lx, .y = PMONITOR->vecPosition.y - pHyprPopup->ly, .width = PMONITOR->vecSize.x, .height = PMONITOR->vecSize.y};
CBox box = {PMONITOR->vecPosition.x - pHyprPopup->lx, PMONITOR->vecPosition.y - pHyprPopup->ly, PMONITOR->vecSize.x, PMONITOR->vecSize.y};
wlr_xdg_popup_unconstrain_from_box(popup, &box);
wlr_xdg_popup_unconstrain_from_box(popup, box.pWlr());
pHyprPopup->monitor = PMONITOR;
@ -159,13 +159,16 @@ void Events::listener_mapPopupXDG(void* owner, void* data) {
int lx = 0, ly = 0;
addPopupGlobalCoords(PPOPUP, &lx, &ly);
wlr_box extents;
wlr_surface_get_extends(PPOPUP->popup->base->surface, &extents);
CBox extents;
wlr_surface_get_extends(PPOPUP->popup->base->surface, extents.pWlr());
extents.applyFromWlr();
g_pHyprRenderer->damageBox(lx - extents.x, ly - extents.y, extents.width + 2, extents.height + 2);
if (PPOPUP->monitor)
g_pProtocolManager->m_pFractionalScaleProtocolManager->setPreferredScaleForSurface(PPOPUP->popup->base->surface, PPOPUP->monitor->scale);
if (PPOPUP->monitor) {
g_pCompositor->setPreferredScaleForSurface(PPOPUP->popup->base->surface, PPOPUP->monitor->scale);
g_pCompositor->setPreferredTransformForSurface(PPOPUP->popup->base->surface, PPOPUP->monitor->transform);
}
Debug::log(LOG, "XDG Popup got assigned a surfaceTreeNode {:x}", (uintptr_t)PPOPUP->pSurfaceTree);
}
@ -178,11 +181,18 @@ void Events::listener_repositionPopupXDG(void* owner, void* data) {
int lx = 0, ly = 0;
addPopupGlobalCoords(PPOPUP, &lx, &ly);
wlr_box extents;
wlr_surface_get_extends(PPOPUP->popup->base->surface, &extents);
CBox extents;
wlr_surface_get_extends(PPOPUP->popup->base->surface, extents.pWlr());
extents.applyFromWlr();
PPOPUP->lastPos = {lx - extents.x, ly - extents.y};
PPOPUP->repositionRequested = true;
const auto PMONITOR = g_pCompositor->m_pLastMonitor;
CBox box = {PMONITOR->vecPosition.x - lx + PPOPUP->popup->current.geometry.x, PMONITOR->vecPosition.y - ly + PPOPUP->popup->current.geometry.y, PMONITOR->vecSize.x,
PMONITOR->vecSize.y};
wlr_xdg_popup_unconstrain_from_box(PPOPUP->popup, box.pWlr());
}
void Events::listener_unmapPopupXDG(void* owner, void* data) {
@ -199,8 +209,9 @@ void Events::listener_unmapPopupXDG(void* owner, void* data) {
int lx = 0, ly = 0;
addPopupGlobalCoords(PPOPUP, &lx, &ly);
wlr_box extents;
wlr_surface_get_extends(PPOPUP->popup->base->surface, &extents);
CBox extents;
wlr_surface_get_extends(PPOPUP->popup->base->surface, extents.pWlr());
extents.applyFromWlr();
g_pHyprRenderer->damageBox(lx - extents.x, ly - extents.y, extents.width + 2, extents.height + 2);
@ -225,8 +236,9 @@ void Events::listener_commitPopupXDG(void* owner, void* data) {
int lx = 0, ly = 0;
addPopupGlobalCoords(PPOPUP, &lx, &ly);
wlr_box extents;
wlr_surface_get_extends(PPOPUP->popup->base->surface, &extents);
CBox extents;
wlr_surface_get_extends(PPOPUP->popup->base->surface, extents.pWlr());
extents.applyFromWlr();
if (PPOPUP->repositionRequested)
g_pHyprRenderer->damageBox(PPOPUP->lastPos.x, PPOPUP->lastPos.y, extents.width + 2, extents.height + 2);

View file

@ -46,12 +46,12 @@ void Events::listener_mapWindow(void* owner, void* data) {
static auto* const PSWALLOW = &g_pConfigManager->getConfigValuePtr("misc:enable_swallow")->intValue;
static auto* const PSWALLOWREGEX = &g_pConfigManager->getConfigValuePtr("misc:swallow_regex")->strValue;
static auto* const PSWALLOWEXREGEX = &g_pConfigManager->getConfigValuePtr("misc:swallow_exception_regex")->strValue;
static auto* const PNEWTAKESOVERFS = &g_pConfigManager->getConfigValuePtr("misc:new_window_takes_over_fullscreen")->intValue;
auto PMONITOR = g_pCompositor->m_pLastMonitor;
const auto PWORKSPACE =
PMONITOR->specialWorkspaceID ? g_pCompositor->getWorkspaceByID(PMONITOR->specialWorkspaceID) : g_pCompositor->getWorkspaceByID(PMONITOR->activeWorkspace);
PWINDOW->m_iMonitorID = PMONITOR->ID;
PWINDOW->m_bMappedX11 = true;
PWINDOW->m_iWorkspaceID = PMONITOR->specialWorkspaceID ? PMONITOR->specialWorkspaceID : PMONITOR->activeWorkspace;
PWINDOW->m_bIsMapped = true;
PWINDOW->m_bReadyToDelete = false;
@ -95,7 +95,7 @@ void Events::listener_mapWindow(void* owner, void* data) {
if (PWORKSPACE->m_bDefaultPseudo) {
PWINDOW->m_bIsPseudotiled = true;
wlr_box desiredGeometry = {0};
CBox desiredGeometry = {0};
g_pXWaylandManager->getGeometryForWindow(PWINDOW, &desiredGeometry);
PWINDOW->m_vPseudoSize = Vector2D(desiredGeometry.width, desiredGeometry.height);
}
@ -116,7 +116,7 @@ void Events::listener_mapWindow(void* owner, void* data) {
PWINDOW->m_szInitialClass = g_pXWaylandManager->getAppIDClass(PWINDOW);
for (auto& r : WINDOWRULES) {
if (r.szRule.find("monitor") == 0) {
if (r.szRule.starts_with("monitor")) {
try {
const auto MONITORSTR = removeBeginEndSpacesTabs(r.szRule.substr(r.szRule.find(' ')));
@ -150,7 +150,7 @@ void Events::listener_mapWindow(void* owner, void* data) {
Debug::log(LOG, "Rule monitor, applying to {:mw}", PWINDOW);
} catch (std::exception& e) { Debug::log(ERR, "Rule monitor failed, rule: {} -> {} | err: {}", r.szRule, r.szValue, e.what()); }
} else if (r.szRule.find("workspace") == 0) {
} else if (r.szRule.starts_with("workspace")) {
// check if it isnt unset
const auto WORKSPACERQ = r.szRule.substr(r.szRule.find_first_of(' ') + 1);
@ -166,19 +166,19 @@ void Events::listener_mapWindow(void* owner, void* data) {
requestedWorkspace = "";
Debug::log(LOG, "Rule workspace matched by {}, {} applied.", PWINDOW, r.szValue);
} else if (r.szRule.find("float") == 0) {
} else if (r.szRule.starts_with("float")) {
PWINDOW->m_bIsFloating = true;
} else if (r.szRule.find("tile") == 0) {
} else if (r.szRule.starts_with("tile")) {
PWINDOW->m_bIsFloating = false;
} else if (r.szRule.find("pseudo") == 0) {
} else if (r.szRule.starts_with("pseudo")) {
PWINDOW->m_bIsPseudotiled = true;
} else if (r.szRule.find("nofocus") == 0) {
} else if (r.szRule.starts_with("nofocus")) {
PWINDOW->m_bNoFocus = true;
} else if (r.szRule.find("noinitialfocus") == 0) {
} else if (r.szRule.starts_with("noinitialfocus")) {
PWINDOW->m_bNoInitialFocus = true;
} else if (r.szRule.find("nofullscreenrequest") == 0) {
} else if (r.szRule.starts_with("nofullscreenrequest")) {
PWINDOW->m_bNoFullscreenRequest = true;
} else if (r.szRule.find("nomaximizerequest") == 0) {
} else if (r.szRule.starts_with("nomaximizerequest")) {
PWINDOW->m_bNoMaximizeRequest = true;
} else if (r.szRule == "fullscreen") {
requestsFullscreen = true;
@ -198,7 +198,7 @@ void Events::listener_mapWindow(void* owner, void* data) {
overridingNoMaximize = true;
} else if (r.szRule == "stayfocused") {
PWINDOW->m_bStayFocused = true;
} else if (r.szRule.find("group") == 0) {
} else if (r.szRule.starts_with("group")) {
if (PWINDOW->m_eGroupRules & GROUP_OVERRIDE)
continue;
@ -245,31 +245,10 @@ void Events::listener_mapWindow(void* owner, void* data) {
}
vPrev = v;
}
} else if (r.szRule.find("idleinhibit") == 0) {
auto IDLERULE = r.szRule.substr(r.szRule.find_first_of(' ') + 1);
if (IDLERULE == "none") {
PWINDOW->m_eIdleInhibitMode = IDLEINHIBIT_NONE;
} else if (IDLERULE == "always") {
PWINDOW->m_eIdleInhibitMode = IDLEINHIBIT_ALWAYS;
} else if (IDLERULE == "focus") {
PWINDOW->m_eIdleInhibitMode = IDLEINHIBIT_FOCUS;
} else if (IDLERULE == "fullscreen") {
PWINDOW->m_eIdleInhibitMode = IDLEINHIBIT_FULLSCREEN;
} else {
Debug::log(ERR, "Rule idleinhibit: unknown mode {}", IDLERULE);
}
}
PWINDOW->applyDynamicRule(r);
}
CWindow* pFullscreenWindow = nullptr;
if (PWORKSPACE->m_bHasFullscreenWindow && !PWINDOW->m_bIsFloating) {
const auto PFULLWINDOW = g_pCompositor->getFullscreenWindowOnWorkspace(PWORKSPACE->m_iID);
pFullscreenWindow = PFULLWINDOW;
g_pCompositor->setWindowFullscreen(PFULLWINDOW, false, PWORKSPACE->m_efFullscreenMode);
}
PWINDOW->updateSpecialRenderData();
// disallow tiled pinned
@ -279,13 +258,13 @@ void Events::listener_mapWindow(void* owner, void* data) {
const CVarList WORKSPACEARGS = CVarList(requestedWorkspace, 0, ' ');
if (!WORKSPACEARGS[0].empty()) {
if (WORKSPACEARGS[WORKSPACEARGS.size() - 1].find("silent") == 0)
if (WORKSPACEARGS[WORKSPACEARGS.size() - 1].starts_with("silent"))
workspaceSilent = true;
std::string requestedWorkspaceName;
const int REQUESTEDWORKSPACEID = getWorkspaceIDFromString(WORKSPACEARGS.join(" ", 0, workspaceSilent ? WORKSPACEARGS.size() - 1 : 0), requestedWorkspaceName);
if (REQUESTEDWORKSPACEID != INT_MAX) {
if (REQUESTEDWORKSPACEID != WORKSPACE_INVALID) {
auto pWorkspace = g_pCompositor->getWorkspaceByID(REQUESTEDWORKSPACEID);
if (!pWorkspace)
@ -315,7 +294,7 @@ void Events::listener_mapWindow(void* owner, void* data) {
// size and move rules
for (auto& r : WINDOWRULES) {
if (r.szRule.find("size") == 0) {
if (r.szRule.starts_with("size")) {
try {
const auto VALUE = r.szRule.substr(r.szRule.find(' ') + 1);
const auto SIZEXSTR = VALUE.substr(0, VALUE.find(' '));
@ -337,7 +316,7 @@ void Events::listener_mapWindow(void* owner, void* data) {
PWINDOW->setHidden(false);
} catch (...) { Debug::log(LOG, "Rule size failed, rule: {} -> {}", r.szRule, r.szValue); }
} else if (r.szRule.find("minsize") == 0) {
} else if (r.szRule.starts_with("minsize")) {
try {
const auto VALUE = r.szRule.substr(r.szRule.find(' ') + 1);
const auto SIZEXSTR = VALUE.substr(0, VALUE.find(' '));
@ -351,7 +330,7 @@ void Events::listener_mapWindow(void* owner, void* data) {
PWINDOW->setHidden(false);
} catch (...) { Debug::log(LOG, "Rule minsize failed, rule: {} -> {}", r.szRule, r.szValue); }
} else if (r.szRule.find("maxsize") == 0) {
} else if (r.szRule.starts_with("maxsize")) {
try {
const auto VALUE = r.szRule.substr(r.szRule.find(' ') + 1);
const auto SIZEXSTR = VALUE.substr(0, VALUE.find(' '));
@ -365,16 +344,16 @@ void Events::listener_mapWindow(void* owner, void* data) {
PWINDOW->setHidden(false);
} catch (...) { Debug::log(LOG, "Rule maxsize failed, rule: {} -> {}", r.szRule, r.szValue); }
} else if (r.szRule.find("move") == 0) {
} else if (r.szRule.starts_with("move")) {
try {
auto value = r.szRule.substr(r.szRule.find(' ') + 1);
const bool ONSCREEN = value.find("onscreen") == 0;
const bool ONSCREEN = value.starts_with("onscreen");
if (ONSCREEN)
value = value.substr(value.find_first_of(' ') + 1);
const bool CURSOR = value.find("cursor") == 0;
const bool CURSOR = value.starts_with("cursor");
if (CURSOR)
value = value.substr(value.find_first_of(' ') + 1);
@ -385,7 +364,7 @@ void Events::listener_mapWindow(void* owner, void* data) {
int posX = 0;
int posY = 0;
if (POSXSTR.find("100%-") == 0) {
if (POSXSTR.starts_with("100%-")) {
const auto POSXRAW = POSXSTR.substr(5);
posX =
PMONITOR->vecSize.x - (!POSXRAW.contains('%') ? std::stoi(POSXRAW) : std::stof(POSXRAW.substr(0, POSXRAW.length() - 1)) * 0.01 * PMONITOR->vecSize.x);
@ -404,7 +383,7 @@ void Events::listener_mapWindow(void* owner, void* data) {
}
}
if (POSYSTR.find("100%-") == 0) {
if (POSYSTR.starts_with("100%-")) {
const auto POSYRAW = POSYSTR.substr(5);
posY =
PMONITOR->vecSize.y - (!POSYRAW.contains('%') ? std::stoi(POSYRAW) : std::stof(POSYRAW.substr(0, POSYRAW.length() - 1)) * 0.01 * PMONITOR->vecSize.y);
@ -439,7 +418,7 @@ void Events::listener_mapWindow(void* owner, void* data) {
PWINDOW->setHidden(false);
} catch (...) { Debug::log(LOG, "Rule move failed, rule: {} -> {}", r.szRule, r.szValue); }
} else if (r.szRule.find("center") == 0) {
} else if (r.szRule.starts_with("center")) {
auto RESERVEDOFFSET = Vector2D();
const auto ARGS = CVarList(r.szRule, 2, ' ');
if (ARGS[1] == "1")
@ -474,6 +453,16 @@ void Events::listener_mapWindow(void* owner, void* data) {
const auto PLSFROMFOCUS = g_pCompositor->getLayerSurfaceFromSurface(g_pCompositor->m_pLastFocus);
if (PLSFROMFOCUS && PLSFROMFOCUS->layerSurface->current.keyboard_interactive)
PWINDOW->m_bNoInitialFocus = true;
if (PWORKSPACE->m_bHasFullscreenWindow && !requestsFullscreen && !PWINDOW->m_bIsFloating) {
if (*PNEWTAKESOVERFS == 0)
PWINDOW->m_bNoInitialFocus = true;
else if (*PNEWTAKESOVERFS == 2)
g_pCompositor->setWindowFullscreen(g_pCompositor->getFullscreenWindowOnWorkspace(PWORKSPACE->m_iID), false, FULLSCREEN_INVALID);
else if (PWORKSPACE->m_efFullscreenMode == FULLSCREEN_MAXIMIZED)
requestsMaximize = true;
else
requestsFullscreen = true;
}
if (!PWINDOW->m_bNoFocus && !PWINDOW->m_bNoInitialFocus &&
(PWINDOW->m_iX11Type != 2 || (PWINDOW->m_bIsX11 && wlr_xwayland_or_surface_wants_focus(PWINDOW->m_uSurface.xwayland))) && !workspaceSilent &&
@ -489,7 +478,6 @@ void Events::listener_mapWindow(void* owner, void* data) {
Debug::log(LOG, "Window got assigned a surfaceTreeNode {:x}", (uintptr_t)PWINDOW->m_pSurfaceTree);
if (!PWINDOW->m_bIsX11) {
PWINDOW->hyprListener_commitWindow.initCallback(&PWINDOW->m_uSurface.xdg->surface->events.commit, &Events::listener_commitWindow, PWINDOW, "XDG Window Late");
PWINDOW->hyprListener_setTitleWindow.initCallback(&PWINDOW->m_uSurface.xdg->toplevel->events.set_title, &Events::listener_setTitleWindow, PWINDOW, "XDG Window Late");
PWINDOW->hyprListener_newPopupXDG.initCallback(&PWINDOW->m_uSurface.xdg->events.new_popup, &Events::listener_newPopupXDG, PWINDOW, "XDG Window Late");
PWINDOW->hyprListener_requestMaximize.initCallback(&PWINDOW->m_uSurface.xdg->toplevel->events.request_maximize, &Events::listener_requestMaximize, PWINDOW,
@ -500,6 +488,7 @@ void Events::listener_mapWindow(void* owner, void* data) {
PWINDOW->hyprListener_requestResize.initCallback(&PWINDOW->m_uSurface.xdg->toplevel->events.request_resize, &Events::listener_requestResize, PWINDOW, "XDG Window Late");
PWINDOW->hyprListener_fullscreenWindow.initCallback(&PWINDOW->m_uSurface.xdg->toplevel->events.request_fullscreen, &Events::listener_fullscreenWindow, PWINDOW,
"XDG Window Late");
PWINDOW->hyprListener_ackConfigure.initCallback(&PWINDOW->m_uSurface.xdg->events.ack_configure, &Events::listener_ackConfigure, PWINDOW, "XDG Window Late");
} else {
PWINDOW->hyprListener_fullscreenWindow.initCallback(&PWINDOW->m_uSurface.xwayland->events.request_fullscreen, &Events::listener_fullscreenWindow, PWINDOW,
"XWayland Window Late");
@ -515,14 +504,6 @@ void Events::listener_mapWindow(void* owner, void* data) {
"XWayland Window Late");
}
// do the animation thing
g_pAnimationManager->onWindowPostCreateClose(PWINDOW, false);
PWINDOW->m_fAlpha.setValueAndWarp(0.f);
PWINDOW->m_fAlpha = 1.f;
PWINDOW->m_vRealPosition.setCallbackOnEnd(setAnimToMove);
PWINDOW->m_vRealSize.setCallbackOnEnd(setAnimToMove);
if ((requestsFullscreen && (!PWINDOW->m_bNoFullscreenRequest || overridingNoFullscreen)) || (requestsMaximize && (!PWINDOW->m_bNoMaximizeRequest || overridingNoMaximize)) ||
requestsFakeFullscreen) {
// fix fullscreen on requested (basically do a switcheroo)
@ -543,10 +524,6 @@ void Events::listener_mapWindow(void* owner, void* data) {
}
}
if (pFullscreenWindow && workspaceSilent) {
g_pCompositor->setWindowFullscreen(pFullscreenWindow, true, PWORKSPACE->m_efFullscreenMode);
}
// recheck idle inhibitors
g_pInputManager->recheckIdleInhibitorStatus();
@ -640,10 +617,34 @@ void Events::listener_mapWindow(void* owner, void* data) {
g_pEventManager->postEvent(SHyprIPCEvent{"openwindow", std::format("{:x},{},{},{}", PWINDOW, workspaceID, g_pXWaylandManager->getAppIDClass(PWINDOW), PWINDOW->m_szTitle)});
EMIT_HOOK_EVENT("openWindow", PWINDOW);
// apply data from default decos. Borders, shadows.
g_pDecorationPositioner->forceRecalcFor(PWINDOW);
PWINDOW->updateWindowDecos();
g_pLayoutManager->getCurrentLayout()->recalculateWindow(PWINDOW);
// do animations
g_pAnimationManager->onWindowPostCreateClose(PWINDOW, false);
PWINDOW->m_fAlpha.setValueAndWarp(0.f);
PWINDOW->m_fAlpha = 1.f;
PWINDOW->m_vRealPosition.setCallbackOnEnd(setAnimToMove);
PWINDOW->m_vRealSize.setCallbackOnEnd(setAnimToMove);
// recalc the values for this window
g_pCompositor->updateWindowAnimatedDecorationValues(PWINDOW);
// avoid this window being visible
if (PWORKSPACE->m_bHasFullscreenWindow && !PWINDOW->m_bIsFullscreen && !PWINDOW->m_bIsFloating)
PWINDOW->m_fAlpha.setValueAndWarp(0.f);
g_pProtocolManager->m_pFractionalScaleProtocolManager->setPreferredScaleForSurface(PWINDOW->m_pWLSurface.wlr(), PMONITOR->scale);
g_pCompositor->setPreferredScaleForSurface(PWINDOW->m_pWLSurface.wlr(), PMONITOR->scale);
g_pCompositor->setPreferredTransformForSurface(PWINDOW->m_pWLSurface.wlr(), PMONITOR->transform);
g_pInputManager->sendMotionEventsToFocused();
// fix some xwayland apps that don't behave nicely
PWINDOW->m_vReportedSize = PWINDOW->m_vPendingReportedSize;
g_pCompositor->updateWorkspaceWindows(PWINDOW->m_iWorkspaceID);
}
void Events::listener_unmapWindow(void* owner, void* data) {
@ -657,6 +658,13 @@ void Events::listener_unmapWindow(void* owner, void* data) {
return;
}
const auto PMONITOR = g_pCompositor->getMonitorFromID(PWINDOW->m_iMonitorID);
if (PMONITOR) {
PWINDOW->m_vOriginalClosedPos = PWINDOW->m_vRealPosition.vec() - PMONITOR->vecPosition;
PWINDOW->m_vOriginalClosedSize = PWINDOW->m_vRealSize.vec();
PWINDOW->m_eOriginalClosedExtents = PWINDOW->getFullWindowExtents();
}
g_pEventManager->postEvent(SHyprIPCEvent{"closewindow", std::format("{:x}", PWINDOW)});
EMIT_HOOK_EVENT("closeWindow", PWINDOW);
@ -664,7 +672,6 @@ void Events::listener_unmapWindow(void* owner, void* data) {
if (!PWINDOW->m_bIsX11) {
Debug::log(LOG, "Unregistered late callbacks XDG");
PWINDOW->hyprListener_commitWindow.removeCallback();
PWINDOW->hyprListener_setTitleWindow.removeCallback();
PWINDOW->hyprListener_newPopupXDG.removeCallback();
PWINDOW->hyprListener_requestMaximize.removeCallback();
@ -672,6 +679,7 @@ void Events::listener_unmapWindow(void* owner, void* data) {
PWINDOW->hyprListener_requestMove.removeCallback();
PWINDOW->hyprListener_requestResize.removeCallback();
PWINDOW->hyprListener_fullscreenWindow.removeCallback();
PWINDOW->hyprListener_ackConfigure.removeCallback();
} else {
Debug::log(LOG, "Unregistered late callbacks XWL");
PWINDOW->hyprListener_fullscreenWindow.removeCallback();
@ -685,13 +693,6 @@ void Events::listener_unmapWindow(void* owner, void* data) {
if (PWINDOW->m_bIsFullscreen)
g_pCompositor->setWindowFullscreen(PWINDOW, false, FULLSCREEN_FULL);
const auto PMONITOR = g_pCompositor->getMonitorFromID(PWINDOW->m_iMonitorID);
if (PMONITOR) {
PWINDOW->m_vOriginalClosedPos = PWINDOW->m_vRealPosition.vec() - PMONITOR->vecPosition;
PWINDOW->m_vOriginalClosedSize = PWINDOW->m_vRealSize.vec();
PWINDOW->m_eOriginalClosedExtents = PWINDOW->getFullWindowExtents();
}
// Allow the renderer to catch the last frame.
g_pHyprOpenGL->makeWindowSnapshot(PWINDOW);
@ -712,8 +713,6 @@ void Events::listener_unmapWindow(void* owner, void* data) {
g_pInputManager->releaseAllMouseButtons();
}
PWINDOW->m_bMappedX11 = false;
// remove the fullscreen window status from workspace if we closed it
const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(PWINDOW->m_iWorkspaceID);
@ -731,13 +730,16 @@ void Events::listener_unmapWindow(void* owner, void* data) {
Debug::log(LOG, "On closed window, new focused candidate is {}", PWINDOWCANDIDATE);
if (PWINDOWCANDIDATE != g_pCompositor->m_pLastWindow) {
if (!PWINDOWCANDIDATE)
g_pInputManager->simulateMouseMovement();
else
g_pCompositor->focusWindow(PWINDOWCANDIDATE);
} else {
g_pInputManager->simulateMouseMovement();
if (PWINDOWCANDIDATE != g_pCompositor->m_pLastWindow && PWINDOWCANDIDATE)
g_pCompositor->focusWindow(PWINDOWCANDIDATE);
g_pInputManager->sendMotionEventsToFocused();
// CWindow::onUnmap will remove this window's active status, but we can't really do it above.
if (PWINDOW == g_pCompositor->m_pLastWindow || !g_pCompositor->m_pLastWindow) {
g_pEventManager->postEvent(SHyprIPCEvent{"activewindow", ","});
g_pEventManager->postEvent(SHyprIPCEvent{"activewindowv2", ","});
EMIT_HOOK_EVENT("activeWindow", (CWindow*)nullptr);
}
} else {
Debug::log(LOG, "Unmapped was not focused, ignoring a refocus.");
@ -774,13 +776,32 @@ void Events::listener_unmapWindow(void* owner, void* data) {
PWINDOW->onUnmap();
}
void Events::listener_ackConfigure(void* owner, void* data) {
CWindow* PWINDOW = (CWindow*)owner;
const auto E = (wlr_xdg_surface_configure*)data;
// find last matching serial
const auto SERIAL = std::find_if(PWINDOW->m_vPendingSizeAcks.rbegin(), PWINDOW->m_vPendingSizeAcks.rend(), [&](const auto& e) { return e.first == E->serial; });
if (SERIAL == PWINDOW->m_vPendingSizeAcks.rend())
return;
PWINDOW->m_pPendingSizeAck = *SERIAL;
std::erase_if(PWINDOW->m_vPendingSizeAcks, [&](const auto& el) { return el.first == SERIAL->first; });
}
void Events::listener_commitWindow(void* owner, void* data) {
CWindow* PWINDOW = (CWindow*)owner;
if (!PWINDOW->m_bMappedX11 || PWINDOW->isHidden() || (PWINDOW->m_bIsX11 && !PWINDOW->m_bMappedX11))
if (!PWINDOW->m_bIsMapped || PWINDOW->isHidden())
return;
PWINDOW->updateSurfaceOutputs();
if (PWINDOW->m_bIsX11)
PWINDOW->m_vReportedSize = PWINDOW->m_vPendingReportedSize; // apply pending size. We pinged, the window ponged.
else if (PWINDOW->m_pPendingSizeAck.has_value()) {
PWINDOW->m_vReportedSize = PWINDOW->m_pPendingSizeAck->second;
PWINDOW->m_pPendingSizeAck.reset();
}
g_pHyprRenderer->damageSurface(PWINDOW->m_pWLSurface.wlr(), PWINDOW->m_vRealPosition.goalv().x, PWINDOW->m_vRealPosition.goalv().y,
PWINDOW->m_bIsX11 ? 1.0 / PWINDOW->m_fX11SurfaceScaledBy : 1.0);
@ -788,22 +809,29 @@ void Events::listener_commitWindow(void* owner, void* data) {
if (PWINDOW->m_bIsX11 || !PWINDOW->m_bIsFloating || PWINDOW->m_bIsFullscreen)
return;
const auto ISRIGID = PWINDOW->m_uSurface.xdg->toplevel->current.max_height == PWINDOW->m_uSurface.xdg->toplevel->current.min_height &&
PWINDOW->m_uSurface.xdg->toplevel->current.max_width == PWINDOW->m_uSurface.xdg->toplevel->current.min_width;
const auto MINSIZE = Vector2D{PWINDOW->m_uSurface.xdg->toplevel->current.min_width, PWINDOW->m_uSurface.xdg->toplevel->current.min_height};
const auto MAXSIZE = Vector2D{PWINDOW->m_uSurface.xdg->toplevel->current.max_width, PWINDOW->m_uSurface.xdg->toplevel->current.max_height};
if (!ISRIGID)
if (MAXSIZE < Vector2D{1, 1})
return;
const Vector2D REQUESTEDSIZE = {PWINDOW->m_uSurface.xdg->toplevel->current.max_width, PWINDOW->m_uSurface.xdg->toplevel->current.max_height};
const auto REALSIZE = PWINDOW->m_vRealSize.goalv();
Vector2D newSize = REALSIZE;
if (REQUESTEDSIZE == PWINDOW->m_vReportedSize || REQUESTEDSIZE.x < 5 || REQUESTEDSIZE.y < 5)
return;
if (MAXSIZE.x < newSize.x)
newSize.x = MAXSIZE.x;
if (MAXSIZE.y < newSize.y)
newSize.y = MAXSIZE.y;
if (MINSIZE.x > newSize.x)
newSize.x = MINSIZE.x;
if (MINSIZE.y > newSize.y)
newSize.y = MINSIZE.y;
const Vector2D DELTA = PWINDOW->m_vReportedSize - REQUESTEDSIZE;
const Vector2D DELTA = REALSIZE - newSize;
PWINDOW->m_vRealPosition = PWINDOW->m_vRealPosition.goalv() + DELTA / 2.0;
PWINDOW->m_vRealSize = REQUESTEDSIZE;
g_pXWaylandManager->setWindowSize(PWINDOW, REQUESTEDSIZE, true);
PWINDOW->m_vRealSize = newSize;
g_pXWaylandManager->setWindowSize(PWINDOW, newSize, true);
g_pHyprRenderer->damageWindow(PWINDOW);
}
@ -820,6 +848,7 @@ void Events::listener_destroyWindow(void* owner, void* data) {
g_pCompositor->m_pLastFocus = nullptr;
}
PWINDOW->hyprListener_commitWindow.removeCallback();
PWINDOW->hyprListener_mapWindow.removeCallback();
PWINDOW->hyprListener_unmapWindow.removeCallback();
PWINDOW->hyprListener_destroyWindow.removeCallback();
@ -839,8 +868,8 @@ void Events::listener_destroyWindow(void* owner, void* data) {
PWINDOW->m_bReadyToDelete = true;
if (!PWINDOW->m_bFadingOut) {
g_pCompositor->removeWindowFromVectorSafe(PWINDOW); // most likely X11 unmanaged or sumn
Debug::log(LOG, "Unmapped {} removed instantly", PWINDOW);
g_pCompositor->removeWindowFromVectorSafe(PWINDOW); // most likely X11 unmanaged or sumn
}
}
@ -995,8 +1024,10 @@ void Events::listener_configureX11(void* owner, void* data) {
const auto E = (wlr_xwayland_surface_configure_event*)data;
if (!PWINDOW->m_uSurface.xwayland->surface || !PWINDOW->m_uSurface.xwayland->surface->mapped || !PWINDOW->m_bMappedX11) {
if (!PWINDOW->m_uSurface.xwayland->surface || !PWINDOW->m_uSurface.xwayland->surface->mapped || !PWINDOW->m_bIsMapped) {
wlr_xwayland_surface_configure(PWINDOW->m_uSurface.xwayland, E->x, E->y, E->width, E->height);
PWINDOW->m_vPendingReportedSize = {E->width, E->height};
PWINDOW->m_vReportedSize = {E->width, E->height};
return;
}
@ -1021,8 +1052,11 @@ void Events::listener_configureX11(void* owner, void* data) {
static auto* const PXWLFORCESCALEZERO = &g_pConfigManager->getConfigValuePtr("xwayland:force_zero_scaling")->intValue;
if (*PXWLFORCESCALEZERO) {
if (const auto PMONITOR = g_pCompositor->getMonitorFromID(PWINDOW->m_iMonitorID); PMONITOR)
if (const auto PMONITOR = g_pCompositor->getMonitorFromID(PWINDOW->m_iMonitorID); PMONITOR) {
const Vector2D DELTA = PWINDOW->m_vRealSize.goalv() - PWINDOW->m_vRealSize.goalv() / PMONITOR->scale;
PWINDOW->m_vRealSize.setValueAndWarp(PWINDOW->m_vRealSize.goalv() / PMONITOR->scale);
PWINDOW->m_vRealPosition.setValueAndWarp(PWINDOW->m_vRealPosition.goalv() + DELTA / 2.0);
}
}
PWINDOW->m_vPosition = PWINDOW->m_vRealPosition.vec();
@ -1030,6 +1064,14 @@ void Events::listener_configureX11(void* owner, void* data) {
wlr_xwayland_surface_configure(PWINDOW->m_uSurface.xwayland, E->x, E->y, E->width, E->height);
PWINDOW->m_vPendingReportedSize = {E->width, E->height};
PWINDOW->m_vReportedSize = {E->width, E->height};
PWINDOW->updateWindowDecos();
if (!g_pCompositor->isWorkspaceVisible(PWINDOW->m_iWorkspaceID))
return; // further things are only for visible windows
PWINDOW->m_iWorkspaceID = g_pCompositor->getMonitorFromVector(PWINDOW->m_vRealPosition.vec() + PWINDOW->m_vRealSize.vec() / 2.f)->activeWorkspace;
g_pCompositor->changeWindowZOrder(PWINDOW, true);
@ -1040,14 +1082,12 @@ void Events::listener_configureX11(void* owner, void* data) {
g_pInputManager->refocus();
g_pHyprRenderer->damageWindow(PWINDOW);
PWINDOW->updateWindowDecos();
}
void Events::listener_unmanagedSetGeometry(void* owner, void* data) {
CWindow* PWINDOW = (CWindow*)owner;
if (!PWINDOW->m_bMappedX11)
if (!PWINDOW->m_bIsMapped)
return;
const auto POS = PWINDOW->m_vRealPosition.goalv();
@ -1080,8 +1120,11 @@ void Events::listener_unmanagedSetGeometry(void* owner, void* data) {
PWINDOW->m_vRealSize.setValueAndWarp(Vector2D(PWINDOW->m_uSurface.xwayland->width, PWINDOW->m_uSurface.xwayland->height));
if (*PXWLFORCESCALEZERO) {
if (const auto PMONITOR = g_pCompositor->getMonitorFromID(PWINDOW->m_iMonitorID); PMONITOR)
if (const auto PMONITOR = g_pCompositor->getMonitorFromID(PWINDOW->m_iMonitorID); PMONITOR) {
const Vector2D DELTA = PWINDOW->m_vRealSize.goalv() - PWINDOW->m_vRealSize.goalv() / PMONITOR->scale;
PWINDOW->m_vRealSize.setValueAndWarp(PWINDOW->m_vRealSize.goalv() / PMONITOR->scale);
PWINDOW->m_vRealPosition.setValueAndWarp(PWINDOW->m_vRealPosition.goalv() + DELTA / 2.0);
}
}
PWINDOW->m_vPosition = PWINDOW->m_vRealPosition.goalv();
@ -1092,6 +1135,10 @@ void Events::listener_unmanagedSetGeometry(void* owner, void* data) {
g_pCompositor->changeWindowZOrder(PWINDOW, true);
PWINDOW->updateWindowDecos();
g_pHyprRenderer->damageWindow(PWINDOW);
PWINDOW->m_vReportedPosition = PWINDOW->m_vRealPosition.goalv();
PWINDOW->m_vReportedSize = PWINDOW->m_vRealSize.goalv();
PWINDOW->m_vPendingReportedSize = PWINDOW->m_vReportedSize;
}
}
@ -1107,12 +1154,14 @@ void Events::listener_associateX11(void* owner, void* data) {
const auto PWINDOW = (CWindow*)owner;
PWINDOW->hyprListener_mapWindow.initCallback(&PWINDOW->m_uSurface.xwayland->surface->events.map, &Events::listener_mapWindow, PWINDOW, "XWayland Window");
PWINDOW->hyprListener_commitWindow.initCallback(&PWINDOW->m_uSurface.xwayland->surface->events.commit, &Events::listener_commitWindow, PWINDOW, "XWayland Window");
}
void Events::listener_dissociateX11(void* owner, void* data) {
const auto PWINDOW = (CWindow*)owner;
PWINDOW->hyprListener_mapWindow.removeCallback();
PWINDOW->hyprListener_commitWindow.removeCallback();
}
void Events::listener_surfaceXWayland(wl_listener* listener, void* data) {
@ -1137,20 +1186,19 @@ void Events::listener_surfaceXWayland(wl_listener* listener, void* data) {
PNEWWINDOW->hyprListener_configureX11.initCallback(&XWSURFACE->events.request_configure, &Events::listener_configureX11, PNEWWINDOW, "XWayland Window");
}
void Events::listener_newXDGSurface(wl_listener* listener, void* data) {
void Events::listener_newXDGToplevel(wl_listener* listener, void* data) {
// A window got opened
const auto XDGSURFACE = (wlr_xdg_surface*)data;
const auto XDGTOPLEVEL = (wlr_xdg_toplevel*)data;
const auto XDGSURFACE = XDGTOPLEVEL->base;
if (XDGSURFACE->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL)
return;
Debug::log(LOG, "New XDG Surface created. (class: {})", XDGSURFACE->toplevel->app_id ? XDGSURFACE->toplevel->app_id : "null");
Debug::log(LOG, "New XDG Toplevel created. (class: {})", XDGSURFACE->toplevel->app_id ? XDGSURFACE->toplevel->app_id : "null");
const auto PNEWWINDOW = g_pCompositor->m_vWindows.emplace_back(std::make_unique<CWindow>()).get();
PNEWWINDOW->m_uSurface.xdg = XDGSURFACE;
PNEWWINDOW->hyprListener_mapWindow.initCallback(&XDGSURFACE->surface->events.map, &Events::listener_mapWindow, PNEWWINDOW, "XDG Window");
PNEWWINDOW->hyprListener_destroyWindow.initCallback(&XDGSURFACE->events.destroy, &Events::listener_destroyWindow, PNEWWINDOW, "XDG Window");
PNEWWINDOW->hyprListener_commitWindow.initCallback(&XDGSURFACE->surface->events.commit, &Events::listener_commitWindow, PNEWWINDOW, "XDG Window");
}
void Events::listener_NewXDGDeco(wl_listener* listener, void* data) {
@ -1173,7 +1221,7 @@ void Events::listener_requestMaximize(void* owner, void* data) {
wlr_xdg_surface_schedule_configure(PWINDOW->m_uSurface.xdg);
} else {
if (!PWINDOW->m_bMappedX11 || PWINDOW->m_iX11Type != 1)
if (!PWINDOW->m_bIsMapped || PWINDOW->m_iX11Type != 1)
return;
g_pCompositor->setWindowFullscreen(PWINDOW, !PWINDOW->m_bIsFullscreen, FULLSCREEN_MAXIMIZED);
@ -1186,7 +1234,7 @@ void Events::listener_requestMinimize(void* owner, void* data) {
Debug::log(LOG, "Minimize request for {}", PWINDOW);
if (PWINDOW->m_bIsX11) {
if (!PWINDOW->m_bMappedX11 || PWINDOW->m_iX11Type != 1)
if (!PWINDOW->m_bIsMapped || PWINDOW->m_iX11Type != 1)
return;
const auto E = (wlr_xwayland_minimize_event*)data;

View file

@ -6,17 +6,16 @@
#include "Vector2D.hpp"
#include "Color.hpp"
#include "../macros.hpp"
#include "../debug/Log.hpp"
enum ANIMATEDVARTYPE
{
enum ANIMATEDVARTYPE {
AVARTYPE_INVALID = -1,
AVARTYPE_FLOAT,
AVARTYPE_VECTOR,
AVARTYPE_COLOR
};
enum AVARDAMAGEPOLICY
{
enum AVARDAMAGEPOLICY {
AVARDAMAGE_NONE = -1,
AVARDAMAGE_ENTIRE = 0,
AVARDAMAGE_BORDER,
@ -36,10 +35,10 @@ class CAnimatedVariable {
void create(ANIMATEDVARTYPE, SAnimationPropertyConfig*, void* pWindow, AVARDAMAGEPOLICY);
void create(ANIMATEDVARTYPE, std::any val, SAnimationPropertyConfig*, void* pWindow, AVARDAMAGEPOLICY);
CAnimatedVariable(const CAnimatedVariable&) = delete;
CAnimatedVariable(CAnimatedVariable&&) = delete;
CAnimatedVariable(const CAnimatedVariable&) = delete;
CAnimatedVariable(CAnimatedVariable&&) = delete;
CAnimatedVariable& operator=(const CAnimatedVariable&) = delete;
CAnimatedVariable& operator=(CAnimatedVariable&&) = delete;
CAnimatedVariable& operator=(CAnimatedVariable&&) = delete;
~CAnimatedVariable();

129
src/helpers/Box.cpp Normal file
View file

@ -0,0 +1,129 @@
#include "Box.hpp"
wlr_box CBox::wlr() {
CBox rounded = roundInternal();
m_bWlrBox = wlr_box{(int)rounded.x, (int)rounded.y, (int)rounded.w, (int)rounded.h};
return m_bWlrBox;
}
wlr_box* CBox::pWlr() {
CBox rounded = roundInternal();
m_bWlrBox = wlr_box{(int)rounded.x, (int)rounded.y, (int)rounded.w, (int)rounded.h};
return &m_bWlrBox;
}
CBox& CBox::scale(double scale) {
x *= scale;
y *= scale;
w *= scale;
h *= scale;
return *this;
}
CBox& CBox::scale(const Vector2D& scale) {
x *= scale.x;
y *= scale.y;
w *= scale.x;
h *= scale.y;
return *this;
}
CBox& CBox::translate(const Vector2D& vec) {
x += vec.x;
y += vec.y;
return *this;
}
Vector2D CBox::middle() const {
return Vector2D{x + w / 2.0, y + h / 2.0};
}
bool CBox::containsPoint(const Vector2D& vec) const {
return VECINRECT(vec, x, y, x + w, y + h);
}
bool CBox::empty() const {
return w == 0 || h == 0;
}
CBox& CBox::applyFromWlr() {
x = m_bWlrBox.x;
y = m_bWlrBox.y;
w = m_bWlrBox.width;
h = m_bWlrBox.height;
return *this;
}
CBox& CBox::round() {
float newW = x + w - std::round(x);
float newH = y + h - std::round(y);
x = std::round(x);
y = std::round(y);
w = std::round(newW);
h = std::round(newH);
return *this;
}
CBox& CBox::transform(const wl_output_transform t, double w, double h) {
wlr_box_transform(&m_bWlrBox, pWlr(), t, w, h);
applyFromWlr();
return *this;
}
CBox& CBox::addExtents(const SWindowDecorationExtents& e) {
x -= e.topLeft.x;
y -= e.topLeft.y;
w += e.topLeft.x + e.bottomRight.x;
h += e.topLeft.y + e.bottomRight.y;
return *this;
}
CBox& CBox::scaleFromCenter(double scale) {
double oldW = w, oldH = h;
w *= scale;
h *= scale;
x -= (w - oldW) / 2.0;
y -= (h - oldH) / 2.0;
return *this;
}
CBox& CBox::expand(const double& value) {
x -= value;
y -= value;
w += value * 2.0;
h += value * 2.0;
return *this;
}
CBox CBox::roundInternal() {
float newW = x + w - std::floor(x);
float newH = y + h - std::floor(y);
return CBox{std::floor(x), std::floor(y), std::floor(newW), std::floor(newH)};
}
CBox CBox::copy() const {
return CBox{*this};
}
Vector2D CBox::pos() const {
return {x, y};
}
Vector2D CBox::size() const {
return {w, h};
}
SWindowDecorationExtents CBox::extentsFrom(const CBox& small) {
return {{small.x - x, small.y - y}, {w - small.w - (small.x - x), h - small.h - (small.y - y)}};
}

87
src/helpers/Box.hpp Normal file
View file

@ -0,0 +1,87 @@
#pragma once
#include "Vector2D.hpp"
#include "../SharedDefs.hpp"
#include "../includes.hpp"
class CBox {
public:
CBox(double x_, double y_, double w_, double h_) {
x = x_;
y = y_;
w = w_;
h = h_;
}
CBox() {
w = 0;
h = 0;
}
CBox(const wlr_box& box) {
x = box.x;
y = box.y;
w = box.width;
h = box.height;
}
CBox(const double d) {
x = d;
y = d;
w = d;
h = d;
}
CBox(const Vector2D& pos, const Vector2D& size) {
x = pos.x;
y = pos.y;
w = size.x;
h = size.y;
}
wlr_box wlr();
wlr_box* pWlr();
CBox& applyFromWlr();
CBox& scale(double scale);
CBox& scaleFromCenter(double scale);
CBox& scale(const Vector2D& scale);
CBox& translate(const Vector2D& vec);
CBox& round();
CBox& transform(const wl_output_transform t, double w, double h);
CBox& addExtents(const SWindowDecorationExtents& e);
CBox& expand(const double& value);
CBox copy() const;
SWindowDecorationExtents extentsFrom(const CBox&); // this is the big box
Vector2D middle() const;
Vector2D pos() const;
Vector2D size() const;
bool containsPoint(const Vector2D& vec) const;
bool empty() const;
double x = 0, y = 0;
union {
double w;
double width;
};
union {
double h;
double height;
};
double rot = 0; /* rad, ccw */
//
bool operator==(const CBox& rhs) const {
return x == rhs.x && y == rhs.y && w == rhs.w && h == rhs.h;
}
private:
CBox roundInternal();
wlr_box m_bWlrBox;
};

View file

@ -5,7 +5,7 @@
class CColor {
public:
CColor();
CColor(float, float, float, float);
CColor(float r, float g, float b, float a);
CColor(uint64_t);
float r = 0, g = 0, b = 0, a = 1.f;
@ -27,4 +27,8 @@ class CColor {
bool operator==(const CColor& c2) const {
return r == c2.r && g == c2.g && b == c2.b && a == c2.a;
}
CColor stripA() const {
return {r, g, b, 1};
}
};

View file

@ -2,11 +2,14 @@
#include "../defines.hpp"
#include <algorithm>
#include "../Compositor.hpp"
#include <optional>
#include <set>
#include <sys/utsname.h>
#include <iomanip>
#include <sstream>
#ifdef HAS_EXECINFO
#include <execinfo.h>
#endif
#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
#include <sys/sysctl.h>
@ -182,13 +185,6 @@ std::string escapeJSONStrings(const std::string& str) {
return oss.str();
}
void scaleBox(wlr_box* box, float scale) {
box->width = std::round(box->width * scale);
box->height = std::round(box->height * scale);
box->x = std::round(box->x * scale);
box->y = std::round(box->y * scale);
}
std::string removeBeginEndSpacesTabs(std::string str) {
if (str.empty())
return str;
@ -208,12 +204,12 @@ std::string removeBeginEndSpacesTabs(std::string str) {
return str;
}
float getPlusMinusKeywordResult(std::string source, float relative) {
std::optional<float> getPlusMinusKeywordResult(std::string source, float relative) {
try {
return relative + stof(source);
} catch (...) {
Debug::log(ERR, "Invalid arg \"{}\" in getPlusMinusKeywordResult!", source);
return INT_MAX;
return {};
}
}
@ -246,9 +242,13 @@ bool isDirection(const std::string& arg) {
return arg == "l" || arg == "r" || arg == "u" || arg == "d" || arg == "t" || arg == "b";
}
bool isDirection(const char& arg) {
return arg == 'l' || arg == 'r' || arg == 'u' || arg == 'd' || arg == 't' || arg == 'b';
}
int getWorkspaceIDFromString(const std::string& in, std::string& outName) {
int result = INT_MAX;
if (in.find("special") == 0) {
int result = WORKSPACE_INVALID;
if (in.starts_with("special")) {
outName = "special";
if (in.length() > 8) {
@ -262,7 +262,7 @@ int getWorkspaceIDFromString(const std::string& in, std::string& outName) {
}
return SPECIAL_WORKSPACE_START;
} else if (in.find("name:") == 0) {
} else if (in.starts_with("name:")) {
const auto WORKSPACENAME = in.substr(in.find_first_of(':') + 1);
const auto WORKSPACE = g_pCompositor->getWorkspaceByName(WORKSPACENAME);
if (!WORKSPACE) {
@ -271,26 +271,26 @@ int getWorkspaceIDFromString(const std::string& in, std::string& outName) {
result = WORKSPACE->m_iID;
}
outName = WORKSPACENAME;
} else if (in.find("empty") == 0) {
} else if (in.starts_with("empty")) {
int id = 0;
while (++id < INT_MAX) {
const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(id);
if (!PWORKSPACE || (g_pCompositor->getWindowsOnWorkspace(id) == 0))
return id;
}
} else if (in.find("prev") == 0) {
} else if (in.starts_with("prev")) {
if (!g_pCompositor->m_pLastMonitor)
return INT_MAX;
return WORKSPACE_INVALID;
const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(g_pCompositor->m_pLastMonitor->activeWorkspace);
if (!PWORKSPACE)
return INT_MAX;
return WORKSPACE_INVALID;
const auto PLASTWORKSPACE = g_pCompositor->getWorkspaceByID(PWORKSPACE->m_sPrevWorkspace.iID);
if (!PLASTWORKSPACE)
return INT_MAX;
return WORKSPACE_INVALID;
outName = PLASTWORKSPACE->m_szName;
return PLASTWORKSPACE->m_iID;
@ -298,10 +298,15 @@ int getWorkspaceIDFromString(const std::string& in, std::string& outName) {
if (in[0] == 'r' && (in[1] == '-' || in[1] == '+') && isNumber(in.substr(2))) {
if (!g_pCompositor->m_pLastMonitor) {
Debug::log(ERR, "Relative monitor workspace on monitor null!");
result = INT_MAX;
return result;
return WORKSPACE_INVALID;
}
result = (int)getPlusMinusKeywordResult(in.substr(1), 0);
const auto PLUSMINUSRESULT = getPlusMinusKeywordResult(in.substr(1), 0);
if (!PLUSMINUSRESULT.has_value())
return WORKSPACE_INVALID;
result = (int)PLUSMINUSRESULT.value();
int remains = (int)result;
@ -389,12 +394,12 @@ int getWorkspaceIDFromString(const std::string& in, std::string& outName) {
int beginID = finalWSID;
int curID = finalWSID;
while (--curID > 0 && remainingWSes > 0) {
if (invalidWSes.find(curID) == invalidWSes.end()) {
if (!invalidWSes.contains(curID)) {
remainingWSes--;
}
finalWSID = curID;
}
if (finalWSID <= 0 || invalidWSes.find(finalWSID) != invalidWSes.end()) {
if (finalWSID <= 0 || invalidWSes.contains(finalWSID)) {
if (namedWSes.size()) {
// Go to the named workspaces
// Need remainingWSes more
@ -414,7 +419,7 @@ int getWorkspaceIDFromString(const std::string& in, std::string& outName) {
if (walkDir == '+') {
int curID = finalWSID;
while (++curID < INT32_MAX && remainingWSes > 0) {
if (invalidWSes.find(curID) == invalidWSes.end()) {
if (!invalidWSes.contains(curID)) {
remainingWSes--;
}
finalWSID = curID;
@ -433,12 +438,16 @@ int getWorkspaceIDFromString(const std::string& in, std::string& outName) {
if (!g_pCompositor->m_pLastMonitor) {
Debug::log(ERR, "Relative monitor workspace on monitor null!");
result = INT_MAX;
return result;
return WORKSPACE_INVALID;
}
// monitor relative
result = (int)getPlusMinusKeywordResult(in.substr(1), 0);
const auto PLUSMINUSRESULT = getPlusMinusKeywordResult(in.substr(1), 0);
if (!PLUSMINUSRESULT.has_value())
return WORKSPACE_INVALID;
result = (int)PLUSMINUSRESULT.value();
// result now has +/- what we should move on mon
int remains = (int)result;
@ -479,11 +488,15 @@ int getWorkspaceIDFromString(const std::string& in, std::string& outName) {
outName = g_pCompositor->getWorkspaceByID(validWSes[currentItem])->m_szName;
} else {
if (in[0] == '+' || in[0] == '-') {
if (g_pCompositor->m_pLastMonitor)
result = std::max((int)getPlusMinusKeywordResult(in, g_pCompositor->m_pLastMonitor->activeWorkspace), 1);
else {
if (g_pCompositor->m_pLastMonitor) {
const auto PLUSMINUSRESULT = getPlusMinusKeywordResult(in, g_pCompositor->m_pLastMonitor->activeWorkspace);
if (!PLUSMINUSRESULT.has_value())
return WORKSPACE_INVALID;
result = std::max((int)PLUSMINUSRESULT.value(), 1);
} else {
Debug::log(ERR, "Relative workspace on no mon!");
result = INT_MAX;
return WORKSPACE_INVALID;
}
} else if (isNumber(in))
result = std::max(std::stoi(in), 1);
@ -501,6 +514,43 @@ int getWorkspaceIDFromString(const std::string& in, std::string& outName) {
return result;
}
std::optional<std::string> cleanCmdForWorkspace(const std::string& inWorkspaceName, std::string dirtyCmd) {
std::string cmd = removeBeginEndSpacesTabs(dirtyCmd);
if (!cmd.empty()) {
std::string rules;
const std::string workspaceRule = "workspace " + inWorkspaceName;
if (cmd[0] == '[') {
const int closingBracketIdx = cmd.find_last_of(']');
auto tmpRules = cmd.substr(1, closingBracketIdx - 1);
cmd = cmd.substr(closingBracketIdx + 1);
auto rulesList = CVarList(tmpRules, 0, ';');
bool hadWorkspaceRule = false;
rulesList.map([&](std::string& rule) {
if (rule.find("workspace") == 0) {
rule = workspaceRule;
hadWorkspaceRule = true;
}
});
if (!hadWorkspaceRule)
rulesList.append(workspaceRule);
rules = "[" + rulesList.join(";") + "]";
} else {
rules = "[" + workspaceRule + "]";
}
return std::optional<std::string>(rules + " " + cmd);
}
return std::nullopt;
}
float vecToRectDistanceSquared(const Vector2D& vec, const Vector2D& p1, const Vector2D& p2) {
const float DX = std::max({0.0, p1.x - vec.x, vec.x - p2.x});
const float DY = std::max({0.0, p1.y - vec.y, vec.y - p2.y});
@ -527,10 +577,10 @@ void logSystemInfo() {
uname(&unameInfo);
Debug::log(LOG, "System name: {}", unameInfo.sysname);
Debug::log(LOG, "Node name: {}", unameInfo.nodename);
Debug::log(LOG, "Release: {}", unameInfo.release);
Debug::log(LOG, "Version: {}", unameInfo.version);
Debug::log(LOG, "System name: {}", std::string{unameInfo.sysname});
Debug::log(LOG, "Node name: {}", std::string{unameInfo.nodename});
Debug::log(LOG, "Release: {}", std::string{unameInfo.release});
Debug::log(LOG, "Version: {}", std::string{unameInfo.version});
Debug::log(NONE, "\n");
@ -626,11 +676,11 @@ int64_t getPPIDof(int64_t pid) {
}
int64_t configStringToInt(const std::string& VALUE) {
if (VALUE.find("0x") == 0) {
if (VALUE.starts_with("0x")) {
// Values with 0x are hex
const auto VALUEWITHOUTHEX = VALUE.substr(2);
return stol(VALUEWITHOUTHEX, nullptr, 16);
} else if (VALUE.find("rgba(") == 0 && VALUE.find(')') == VALUE.length() - 1) {
} else if (VALUE.starts_with("rgba(") && VALUE.ends_with(')')) {
const auto VALUEWITHOUTFUNC = VALUE.substr(5, VALUE.length() - 6);
if (removeBeginEndSpacesTabs(VALUEWITHOUTFUNC).length() != 8) {
@ -642,7 +692,7 @@ int64_t configStringToInt(const std::string& VALUE) {
// now we need to RGBA -> ARGB. The config holds ARGB only.
return (RGBA >> 8) + 0x1000000 * (RGBA & 0xFF);
} else if (VALUE.find("rgb(") == 0 && VALUE.find(')') == VALUE.length() - 1) {
} else if (VALUE.starts_with("rgb(") && VALUE.ends_with(')')) {
const auto VALUEWITHOUTFUNC = VALUE.substr(4, VALUE.length() - 5);
if (removeBeginEndSpacesTabs(VALUEWITHOUTFUNC).length() != 6) {
@ -653,11 +703,15 @@ int64_t configStringToInt(const std::string& VALUE) {
const auto RGB = std::stol(VALUEWITHOUTFUNC, nullptr, 16);
return RGB + 0xFF000000; // 0xFF for opaque
} else if (VALUE.find("true") == 0 || VALUE.find("on") == 0 || VALUE.find("yes") == 0) {
} else if (VALUE.starts_with("true") || VALUE.starts_with("on") || VALUE.starts_with("yes")) {
return 1;
} else if (VALUE.find("false") == 0 || VALUE.find("off") == 0 || VALUE.find("no") == 0) {
} else if (VALUE.starts_with("false") || VALUE.starts_with("off") || VALUE.starts_with("no")) {
return 0;
}
if (VALUE.empty() || !isNumber(VALUE))
return 0;
return std::stoll(VALUE);
}
@ -689,9 +743,10 @@ std::string replaceInString(std::string subject, const std::string& search, cons
std::vector<SCallstackFrameInfo> getBacktrace() {
std::vector<SCallstackFrameInfo> callstack;
void* bt[1024];
size_t btSize;
char** btSymbols;
#ifdef HAS_EXECINFO
void* bt[1024];
size_t btSize;
char** btSymbols;
btSize = backtrace(bt, 1024);
btSymbols = backtrace_symbols(bt, btSize);
@ -699,6 +754,9 @@ std::vector<SCallstackFrameInfo> getBacktrace() {
for (size_t i = 0; i < btSize; ++i) {
callstack.emplace_back(SCallstackFrameInfo{bt[i], std::string{btSymbols[i]}});
}
#else
callstack.emplace_back(SCallstackFrameInfo{nullptr, "configuration does not support execinfo.h"});
#endif
return callstack;
}
@ -706,4 +764,38 @@ std::vector<SCallstackFrameInfo> getBacktrace() {
void throwError(const std::string& err) {
Debug::log(CRIT, "Critical error thrown: {}", err);
throw std::runtime_error(err);
}
uint32_t drmFormatToGL(uint32_t drm) {
switch (drm) {
case DRM_FORMAT_XRGB8888:
case DRM_FORMAT_XBGR8888: return GL_RGBA; // doesn't matter, opengl is gucci in this case.
case DRM_FORMAT_XRGB2101010:
case DRM_FORMAT_XBGR2101010:
#ifdef GLES2
return GL_RGB10_A2_EXT;
#else
return GL_RGB10_A2;
#endif
default: return GL_RGBA;
}
UNREACHABLE();
return GL_RGBA;
}
uint32_t glFormatToType(uint32_t gl) {
return gl != GL_RGBA ?
#ifdef GLES2
GL_UNSIGNED_INT_2_10_10_10_REV_EXT :
#else
GL_UNSIGNED_INT_2_10_10_10_REV :
#endif
GL_UNSIGNED_BYTE;
}
bool envEnabled(const std::string& env) {
const auto ENV = getenv(env.c_str());
if (!ENV)
return false;
return std::string(ENV) == "1";
}

View file

@ -1,5 +1,6 @@
#pragma once
#include <optional>
#include <string>
#include <wayland-server.h>
#include <wlr/util/box.h>
@ -15,22 +16,26 @@ struct SCallstackFrameInfo {
std::string absolutePath(const std::string&, const std::string&);
void addWLSignal(wl_signal*, wl_listener*, void* pOwner, const std::string& ownerString);
std::string escapeJSONStrings(const std::string& str);
void scaleBox(wlr_box*, float);
std::string removeBeginEndSpacesTabs(std::string);
bool isNumber(const std::string&, bool allowfloat = false);
bool isDirection(const std::string&);
bool isDirection(const char&);
int getWorkspaceIDFromString(const std::string&, std::string&);
std::optional<std::string> cleanCmdForWorkspace(const std::string&, std::string);
float vecToRectDistanceSquared(const Vector2D& vec, const Vector2D& p1, const Vector2D& p2);
void logSystemInfo();
std::string execAndGet(const char*);
int64_t getPPIDof(int64_t pid);
int64_t configStringToInt(const std::string&);
float getPlusMinusKeywordResult(std::string in, float relative);
std::optional<float> getPlusMinusKeywordResult(std::string in, float relative);
void matrixProjection(float mat[9], int w, int h, wl_output_transform tr);
double normalizeAngleRad(double ang);
std::string replaceInString(std::string subject, const std::string& search, const std::string& replace);
std::vector<SCallstackFrameInfo> getBacktrace();
void throwError(const std::string& err);
uint32_t drmFormatToGL(uint32_t drm);
uint32_t glFormatToType(uint32_t gl);
bool envEnabled(const std::string& env);
template <typename... Args>
[[deprecated("use std::format instead")]] std::string getFormat(std::format_string<Args...> fmt, Args&&... args) {

View file

@ -40,6 +40,8 @@ void CMonitor::onConnect(bool noRule) {
hyprListener_monitorCommit.initCallback(&output->events.commit, &Events::listener_monitorCommit, this);
hyprListener_monitorBind.initCallback(&output->events.bind, &Events::listener_monitorBind, this);
tearingState.canTear = wlr_backend_is_drm(output->backend); // tearing only works on drm
if (m_bEnabled) {
wlr_output_enable(output, 1);
wlr_output_commit(output);
@ -48,6 +50,10 @@ void CMonitor::onConnect(bool noRule) {
szName = output->name;
szDescription = output->description ? output->description : "";
// remove comma character from description. This allow monitor specific rules to work on monitor with comma on their description
szDescription.erase(std::remove(szDescription.begin(), szDescription.end(), ','), szDescription.end());
if (!wlr_backend_is_drm(output->backend))
createdByUser = true; // should be true. WL, X11 and Headless backends should be addable / removable
@ -107,20 +113,20 @@ void CMonitor::onConnect(bool noRule) {
m_bRenderingInitPassed = true;
}
if (!m_pThisWrap) {
std::shared_ptr<CMonitor>* thisWrapper = nullptr;
// find the wrap
for (auto& m : g_pCompositor->m_vRealMonitors) {
if (m->ID == ID) {
m_pThisWrap = &m;
break;
}
// find the wrap
for (auto& m : g_pCompositor->m_vRealMonitors) {
if (m->ID == ID) {
thisWrapper = &m;
break;
}
}
if (std::find_if(g_pCompositor->m_vMonitors.begin(), g_pCompositor->m_vMonitors.end(), [&](auto& other) { return other.get() == this; }) == g_pCompositor->m_vMonitors.end()) {
g_pCompositor->m_vMonitors.push_back(*m_pThisWrap);
}
RASSERT(thisWrapper->get(), "CMonitor::onConnect: Had no wrapper???");
if (std::find_if(g_pCompositor->m_vMonitors.begin(), g_pCompositor->m_vMonitors.end(), [&](auto& other) { return other.get() == this; }) == g_pCompositor->m_vMonitors.end())
g_pCompositor->m_vMonitors.push_back(*thisWrapper);
m_bEnabled = true;
@ -130,6 +136,8 @@ void CMonitor::onConnect(bool noRule) {
if (!noRule)
g_pHyprRenderer->applyMonitorRule(this, &monitorRule, true);
wlr_output_commit(output);
wlr_damage_ring_set_bounds(&damage, vecTransformedSize.x, vecTransformedSize.y);
wlr_xcursor_manager_load(g_pCompositor->m_sWLRXCursorMgr, scale);
@ -150,8 +158,6 @@ void CMonitor::onConnect(bool noRule) {
if (scale < 0.1)
scale = getDefaultScale();
m_pThisWrap = nullptr;
forceFullFrames = 3; // force 3 full frames to make sure there is no blinking due to double-buffering.
//
@ -182,9 +188,11 @@ void CMonitor::onConnect(bool noRule) {
g_pCompositor->setActiveMonitor(this);
renderTimer = wl_event_loop_add_timer(g_pCompositor->m_sWLEventLoop, ratHandler, this);
g_pCompositor->scheduleFrameForMonitor(this);
}
void CMonitor::onDisconnect() {
void CMonitor::onDisconnect(bool destroy) {
if (renderTimer) {
wl_event_source_remove(renderTimer);
@ -219,9 +227,6 @@ void CMonitor::onDisconnect() {
g_pConfigManager->m_bWantsMonitorReload = true;
}
m_bEnabled = false;
m_bRenderingInitPassed = false;
hyprListener_monitorFrame.removeCallback();
hyprListener_monitorDamage.removeCallback();
hyprListener_monitorNeedsFrame.removeCallback();
@ -246,6 +251,9 @@ void CMonitor::onDisconnect() {
g_pCompositor->enterUnsafeState();
}
m_bEnabled = false;
m_bRenderingInitPassed = false;
if (BACKUPMON) {
// snap cursor
wlr_cursor_warp(g_pCompositor->m_sWLRCursor, nullptr, BACKUPMON->vecPosition.x + BACKUPMON->vecTransformedSize.x / 2.f,
@ -254,7 +262,7 @@ void CMonitor::onDisconnect() {
// move workspaces
std::deque<CWorkspace*> wspToMove;
for (auto& w : g_pCompositor->m_vWorkspaces) {
if (w->m_iMonitorID == ID) {
if (w->m_iMonitorID == ID || !g_pCompositor->getMonitorFromID(w->m_iMonitorID)) {
wspToMove.push_back(w.get());
}
}
@ -272,7 +280,8 @@ void CMonitor::onDisconnect() {
activeWorkspace = -1;
wlr_output_layout_remove(g_pCompositor->m_sWLROutputLayout, output);
if (!destroy)
wlr_output_layout_remove(g_pCompositor->m_sWLROutputLayout, output);
wlr_output_enable(output, false);
@ -294,7 +303,6 @@ void CMonitor::onDisconnect() {
g_pHyprRenderer->m_pMostHzMonitor = pMonitorMostHz;
}
std::erase_if(g_pCompositor->m_vMonitors, [&](std::shared_ptr<CMonitor>& el) { return el.get() == this; });
}
@ -313,14 +321,14 @@ void CMonitor::addDamage(const CRegion* rg) {
addDamage(const_cast<CRegion*>(rg)->pixman());
}
void CMonitor::addDamage(const wlr_box* box) {
void CMonitor::addDamage(const CBox* box) {
static auto* const PZOOMFACTOR = &g_pConfigManager->getConfigValuePtr("misc:cursor_zoom_factor")->floatValue;
if (*PZOOMFACTOR != 1.f && g_pCompositor->getMonitorFromCursor() == this) {
wlr_damage_ring_add_whole(&damage);
g_pCompositor->scheduleFrameForMonitor(this);
}
if (wlr_damage_ring_add_box(&damage, box))
if (wlr_damage_ring_add_box(&damage, const_cast<CBox*>(box)->pWlr()))
g_pCompositor->scheduleFrameForMonitor(this);
}
@ -349,7 +357,7 @@ void CMonitor::setupDefaultWS(const SMonitorRule& monitorRule) {
findAvailableDefaultWS() :
getWorkspaceIDFromString(g_pConfigManager->getDefaultWorkspaceFor(szName), newDefaultWorkspaceName);
if (WORKSPACEID == INT_MAX || (WORKSPACEID >= SPECIAL_WORKSPACE_START && WORKSPACEID <= -2)) {
if (WORKSPACEID == WORKSPACE_INVALID || (WORKSPACEID >= SPECIAL_WORKSPACE_START && WORKSPACEID <= -2)) {
WORKSPACEID = g_pCompositor->m_vWorkspaces.size() + 1;
newDefaultWorkspaceName = std::to_string(WORKSPACEID);
@ -412,19 +420,22 @@ void CMonitor::setMirror(const std::string& mirrorOf) {
vecPosition = RULE.offset;
// push to mvmonitors
if (!m_pThisWrap) {
// find the wrap
for (auto& m : g_pCompositor->m_vRealMonitors) {
if (m->ID == ID) {
m_pThisWrap = &m;
break;
}
std::shared_ptr<CMonitor>* thisWrapper = nullptr;
// find the wrap
for (auto& m : g_pCompositor->m_vRealMonitors) {
if (m->ID == ID) {
thisWrapper = &m;
break;
}
}
RASSERT(thisWrapper->get(), "CMonitor::setMirror: Had no wrapper???");
if (std::find_if(g_pCompositor->m_vMonitors.begin(), g_pCompositor->m_vMonitors.end(), [&](auto& other) { return other.get() == this; }) ==
g_pCompositor->m_vMonitors.end()) {
g_pCompositor->m_vMonitors.push_back(*m_pThisWrap);
g_pCompositor->m_vMonitors.push_back(*thisWrapper);
}
setupDefaultWS(RULE);
@ -491,7 +502,7 @@ float CMonitor::getDefaultScale() {
return 1;
}
void CMonitor::changeWorkspace(CWorkspace* const pWorkspace, bool internal, bool noMouseMove) {
void CMonitor::changeWorkspace(CWorkspace* const pWorkspace, bool internal, bool noMouseMove, bool noFocus) {
if (!pWorkspace)
return;
@ -522,24 +533,24 @@ void CMonitor::changeWorkspace(CWorkspace* const pWorkspace, bool internal, bool
}
}
static auto* const PFOLLOWMOUSE = &g_pConfigManager->getConfigValuePtr("input:follow_mouse")->intValue;
if (!noFocus && !g_pCompositor->m_pLastMonitor->specialWorkspaceID) {
static auto* const PFOLLOWMOUSE = &g_pConfigManager->getConfigValuePtr("input:follow_mouse")->intValue;
CWindow* pWindow = pWorkspace->getLastFocusedWindow();
if (const auto PLASTWINDOW = pWorkspace->getLastFocusedWindow(); PLASTWINDOW)
g_pCompositor->focusWindow(PLASTWINDOW);
else {
CWindow* pWindow = nullptr;
if (!pWindow) {
if (*PFOLLOWMOUSE == 1)
pWindow = g_pCompositor->vectorToWindowIdeal(g_pInputManager->getMouseCoordsInternal());
if (*PFOLLOWMOUSE == 1)
pWindow = g_pCompositor->vectorToWindowIdeal(g_pInputManager->getMouseCoordsInternal());
if (!pWindow)
pWindow = g_pCompositor->getTopLeftWindowOnWorkspace(pWorkspace->m_iID);
if (!pWindow)
pWindow = g_pCompositor->getTopLeftWindowOnWorkspace(pWorkspace->m_iID);
if (!pWindow)
pWindow = g_pCompositor->getFirstWindowOnWorkspace(pWorkspace->m_iID);
if (!pWindow)
pWindow = g_pCompositor->getFirstWindowOnWorkspace(pWorkspace->m_iID);
}
g_pCompositor->focusWindow(pWindow);
}
if (!noMouseMove)
g_pInputManager->simulateMouseMovement();
@ -552,6 +563,10 @@ void CMonitor::changeWorkspace(CWorkspace* const pWorkspace, bool internal, bool
g_pHyprRenderer->damageMonitor(this);
g_pCompositor->updateFullscreenFadeOnWorkspace(pWorkspace);
g_pConfigManager->ensureVRR(this);
g_pCompositor->updateSuspendedStates();
}
void CMonitor::changeWorkspace(const int& id, bool internal) {
@ -577,6 +592,8 @@ void CMonitor::setSpecialWorkspace(CWorkspace* const pWorkspace) {
else
g_pInputManager->refocus();
g_pCompositor->updateSuspendedStates();
return;
}
@ -604,7 +621,23 @@ void CMonitor::setSpecialWorkspace(CWorkspace* const pWorkspace) {
for (auto& w : g_pCompositor->m_vWindows) {
if (w->m_iWorkspaceID == pWorkspace->m_iID) {
w->m_iMonitorID = ID;
w->updateSurfaceOutputs();
w->updateSurfaceScaleTransformDetails();
const auto MIDDLE = w->middle();
if (w->m_bIsFloating && !VECINRECT(MIDDLE, vecPosition.x, vecPosition.y, vecPosition.x + vecSize.x, vecPosition.y + vecSize.y) && w->m_iX11Type != 2) {
// if it's floating and the middle isnt on the current mon, move it to the center
const auto PMONFROMMIDDLE = g_pCompositor->getMonitorFromVector(MIDDLE);
Vector2D pos = w->m_vRealPosition.goalv();
if (!VECINRECT(MIDDLE, PMONFROMMIDDLE->vecPosition.x, PMONFROMMIDDLE->vecPosition.y, PMONFROMMIDDLE->vecPosition.x + PMONFROMMIDDLE->vecSize.x,
PMONFROMMIDDLE->vecPosition.y + PMONFROMMIDDLE->vecSize.y)) {
// not on any monitor, center
pos = middle() / 2.f - w->m_vRealSize.goalv() / 2.f;
} else
pos = pos - PMONFROMMIDDLE->vecPosition + vecPosition;
w->m_vRealPosition = pos;
w->m_vPosition = pos;
}
}
}
@ -618,6 +651,8 @@ void CMonitor::setSpecialWorkspace(CWorkspace* const pWorkspace) {
g_pEventManager->postEvent(SHyprIPCEvent{"activespecial", pWorkspace->m_szName + "," + szName});
g_pHyprRenderer->damageMonitor(this);
g_pCompositor->updateSuspendedStates();
}
void CMonitor::setSpecialWorkspace(const int& id) {
@ -633,4 +668,13 @@ void CMonitor::moveTo(const Vector2D& pos) {
Vector2D CMonitor::middle() {
return vecPosition + vecSize / 2.f;
}
}
void CMonitor::updateMatrix() {
wlr_matrix_identity(projMatrix.data());
if (transform != WL_OUTPUT_TRANSFORM_NORMAL) {
wlr_matrix_translate(projMatrix.data(), vecPixelSize.x / 2.0, vecPixelSize.y / 2.0);
wlr_matrix_transform(projMatrix.data(), transform);
wlr_matrix_translate(projMatrix.data(), -vecTransformedSize.x / 2.0, -vecTransformedSize.y / 2.0);
}
}

View file

@ -40,9 +40,11 @@ class CMonitor {
uint64_t ID = -1;
int activeWorkspace = -1;
float scale = 1;
float setScale = 1; // scale set by cfg
float scale = 1; // real scale
std::string szName = "";
std::string szName = "";
std::string szDescription = "";
Vector2D vecReservedTopLeft = Vector2D(0, 0);
Vector2D vecReservedBottomRight = Vector2D(0, 0);
@ -50,30 +52,33 @@ class CMonitor {
drmModeModeInfo customDrmMode = {};
// WLR stuff
wlr_damage_ring damage;
wlr_output* output = nullptr;
float refreshRate = 60;
int framesToSkip = 0;
int forceFullFrames = 0;
bool noFrameSchedule = false;
bool scheduledRecalc = false;
wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL;
bool gammaChanged = false;
float xwaylandScale = 1.f;
wlr_damage_ring damage;
wlr_output* output = nullptr;
float refreshRate = 60;
int framesToSkip = 0;
int forceFullFrames = 0;
bool noFrameSchedule = false;
bool scheduledRecalc = false;
wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL;
bool gammaChanged = false;
float xwaylandScale = 1.f;
std::array<float, 9> projMatrix = {0};
bool dpmsStatus = true;
bool vrrActive = false; // this can be TRUE even if VRR is not active in the case that this display does not support it.
bool enabled10bit = false; // as above, this can be TRUE even if 10 bit failed.
bool createdByUser = false;
bool dpmsStatus = true;
bool vrrActive = false; // this can be TRUE even if VRR is not active in the case that this display does not support it.
bool enabled10bit = false; // as above, this can be TRUE even if 10 bit failed.
bool createdByUser = false;
uint32_t drmFormat = DRM_FORMAT_INVALID;
bool isUnsafeFallback = false;
bool pendingFrame = false; // if we schedule a frame during rendering, reschedule it after
bool renderingActive = false;
bool pendingFrame = false; // if we schedule a frame during rendering, reschedule it after
bool renderingActive = false;
wl_event_source* renderTimer = nullptr; // for RAT
bool RATScheduled = false;
CTimer lastPresentationTimer;
wl_event_source* renderTimer = nullptr; // for RAT
bool RATScheduled = false;
CTimer lastPresentationTimer;
SMonitorRule activeMonitorRule;
SMonitorRule activeMonitorRule;
// mirroring
CMonitor* pMirrorOf = nullptr;
@ -81,6 +86,18 @@ class CMonitor {
CRegion lastFrameDamage; // stores last frame damage
// for tearing
CWindow* solitaryClient = nullptr;
struct {
bool canTear = false;
bool nextRenderTorn = false;
bool activelyTearing = false;
bool busy = false;
bool frameScheduledWhileBusy = false;
} tearingState;
// for the special workspace. 0 means not open.
int specialWorkspaceID = 0;
@ -95,24 +112,24 @@ class CMonitor {
DYNLISTENER(monitorBind);
// methods
void onConnect(bool noRule);
void onDisconnect();
void addDamage(const pixman_region32_t* rg);
void addDamage(const CRegion* rg);
void addDamage(const wlr_box* box);
void setMirror(const std::string&);
bool isMirror();
float getDefaultScale();
void changeWorkspace(CWorkspace* const pWorkspace, bool internal = false, bool noMouseMove = false);
void changeWorkspace(const int& id, bool internal = false);
void setSpecialWorkspace(CWorkspace* const pWorkspace);
void setSpecialWorkspace(const int& id);
void moveTo(const Vector2D& pos);
Vector2D middle();
void onConnect(bool noRule);
void onDisconnect(bool destroy = false);
void addDamage(const pixman_region32_t* rg);
void addDamage(const CRegion* rg);
void addDamage(const CBox* box);
void setMirror(const std::string&);
bool isMirror();
float getDefaultScale();
void changeWorkspace(CWorkspace* const pWorkspace, bool internal = false, bool noMouseMove = false, bool noFocus = false);
void changeWorkspace(const int& id, bool internal = false);
void setSpecialWorkspace(CWorkspace* const pWorkspace);
void setSpecialWorkspace(const int& id);
void moveTo(const Vector2D& pos);
Vector2D middle();
void updateMatrix();
std::shared_ptr<CMonitor>* m_pThisWrap = nullptr;
bool m_bEnabled = false;
bool m_bRenderingInitPassed = false;
bool m_bEnabled = false;
bool m_bRenderingInitPassed = false;
// For the list lookup

View file

@ -21,6 +21,10 @@ CRegion::CRegion(wlr_box* box) {
pixman_region32_init_rect(&m_rRegion, box->x, box->y, box->width, box->height);
}
CRegion::CRegion(const CBox& box) {
pixman_region32_init_rect(&m_rRegion, box.x, box.y, box.w, box.h);
}
CRegion::CRegion(pixman_box32_t* box) {
pixman_region32_init_rect(&m_rRegion, box->x1, box->y1, box->x2 - box->x1, box->y2 - box->y1);
}
@ -59,6 +63,11 @@ CRegion& CRegion::add(double x, double y, double w, double h) {
return *this;
}
CRegion& CRegion::add(const CBox& other) {
pixman_region32_union_rect(&m_rRegion, &m_rRegion, other.x, other.y, other.w, other.h);
return *this;
}
CRegion& CRegion::subtract(const CRegion& other) {
pixman_region32_subtract(&m_rRegion, &m_rRegion, const_cast<CRegion*>(&other)->pixman());
return *this;
@ -79,11 +88,25 @@ CRegion& CRegion::invert(pixman_box32_t* box) {
return *this;
}
CRegion& CRegion::invert(const CBox& box) {
pixman_box32 pixmanBox = {box.x, box.y, box.w + box.x, box.h + box.y};
return this->invert(&pixmanBox);
}
CRegion& CRegion::translate(const Vector2D& vec) {
pixman_region32_translate(&m_rRegion, vec.x, vec.y);
return *this;
}
CRegion& CRegion::transform(const wl_output_transform t, double w, double h) {
wlr_region_transform(&m_rRegion, &m_rRegion, t, w, h);
return *this;
}
CRegion CRegion::copy() const {
return CRegion(*this);
}
CRegion& CRegion::scale(float scale) {
wlr_region_scale(&m_rRegion, &m_rRegion, scale);
return *this;
@ -100,7 +123,7 @@ std::vector<pixman_box32_t> CRegion::getRects() const {
return result;
}
wlr_box CRegion::getExtents() {
CBox CRegion::getExtents() {
pixman_box32_t* box = pixman_region32_extents(&m_rRegion);
return {box->x1, box->y1, box->x2 - box->x1, box->y2 - box->y1};
}

View file

@ -2,6 +2,7 @@
#include <pixman.h>
#include <vector>
#include "Vector2D.hpp"
#include "Box.hpp"
struct wlr_box;
@ -15,6 +16,8 @@ class CRegion {
CRegion(double x, double y, double w, double h);
/* Create from a wlr_box */
CRegion(wlr_box* box);
/* Create from a CBox */
CRegion(const CBox& box);
/* Create from a pixman_box32_t */
CRegion(pixman_box32_t* box);
@ -37,21 +40,25 @@ class CRegion {
CRegion& set(const CRegion& other);
CRegion& add(const CRegion& other);
CRegion& add(double x, double y, double w, double h);
CRegion& add(const CBox& other);
CRegion& subtract(const CRegion& other);
CRegion& intersect(const CRegion& other);
CRegion& intersect(double x, double y, double w, double h);
CRegion& translate(const Vector2D& vec);
CRegion& transform(const wl_output_transform t, double w, double h);
CRegion& invert(pixman_box32_t* box);
CRegion& invert(const CBox& box);
CRegion& scale(float scale);
wlr_box getExtents();
CBox getExtents();
bool containsPoint(const Vector2D& vec) const;
bool empty() const;
Vector2D closestPoint(const Vector2D& vec) const;
CRegion copy() const;
std::vector<pixman_box32_t> getRects() const;
pixman_region32_t* pixman() {
return &m_rRegion;
return &m_rRegion;
}
private:

View file

@ -98,8 +98,9 @@ void SubsurfaceTree::destroySurfaceTree(SSurfaceTreeNode* pNode) {
// damage
if (pNode->pSurface && pNode->pSurface->exists()) {
wlr_box extents = {};
wlr_surface_get_extends(pNode->pSurface->wlr(), &extents);
CBox extents = {};
wlr_surface_get_extends(pNode->pSurface->wlr(), extents.pWlr());
extents.applyFromWlr();
int lx = 0, ly = 0;
addSurfaceGlobalOffset(pNode, &lx, &ly);
@ -177,6 +178,9 @@ void Events::listener_mapSubsurface(void* owner, void* data) {
Debug::log(LOG, "Subsurface {:x} mapped", (uintptr_t)subsurface->pSubsurface);
subsurface->pChild = createSubsurfaceNode(subsurface->pParent, subsurface, subsurface->pSubsurface->surface, subsurface->pWindowOwner);
if (subsurface->pWindowOwner)
subsurface->pWindowOwner->updateSurfaceScaleTransformDetails();
}
void Events::listener_unmapSubsurface(void* owner, void* data) {
@ -198,7 +202,7 @@ void Events::listener_unmapSubsurface(void* owner, void* data) {
int lx = 0, ly = 0;
addSurfaceGlobalOffset(PNODE, &lx, &ly);
wlr_box extents = {lx, ly, 0, 0};
CBox extents = {lx, ly, 0, 0};
extents.width = PNODE->pSurface->wlr()->current.width;
extents.height = PNODE->pSurface->wlr()->current.height;
@ -219,6 +223,8 @@ void Events::listener_commitSubsurface(void* owner, void* data) {
// no damaging if it's not visible
if (!g_pHyprRenderer->shouldRenderWindow(pNode->pWindowOwner)) {
pNode->lastSize = pNode->pSurface->exists() ? Vector2D{pNode->pSurface->wlr()->current.width, pNode->pSurface->wlr()->current.height} : Vector2D{};
static auto* const PLOGDAMAGE = &g_pConfigManager->getConfigValuePtr("debug:log_damage")->intValue;
if (*PLOGDAMAGE)
Debug::log(LOG, "Refusing to commit damage from {} because it's invisible.", pNode->pWindowOwner);
@ -243,8 +249,37 @@ void Events::listener_commitSubsurface(void* owner, void* data) {
}
}
if (pNode->pSurface && pNode->pSurface->exists())
if (pNode->pSurface && pNode->pSurface->exists()) {
g_pHyprRenderer->damageSurface(pNode->pSurface->wlr(), lx, ly, SCALE);
if (pNode->lastSize != Vector2D{pNode->pSurface->wlr()->current.width, pNode->pSurface->wlr()->current.height} && pNode->pWindowOwner)
g_pHyprRenderer->damageWindow(pNode->pWindowOwner);
}
if (pNode->pWindowOwner) {
if (pNode->pWindowOwner->m_bIsX11)
pNode->pWindowOwner->m_vReportedSize = pNode->pWindowOwner->m_vPendingReportedSize; // apply pending size. We pinged, the window ponged.
// tearing: if solitary, redraw it. This still might be a single surface window
const auto PMONITOR = g_pCompositor->getMonitorFromID(pNode->pWindowOwner->m_iMonitorID);
if (PMONITOR->solitaryClient == pNode->pWindowOwner && pNode->pWindowOwner->canBeTorn() && PMONITOR->tearingState.canTear &&
pNode->pSurface->wlr()->current.committed & WLR_SURFACE_STATE_BUFFER) {
CRegion damageBox;
wlr_surface_get_effective_damage(pNode->pSurface->wlr(), damageBox.pixman());
if (!damageBox.empty()) {
if (PMONITOR->tearingState.busy) {
PMONITOR->tearingState.frameScheduledWhileBusy = true;
} else {
PMONITOR->tearingState.nextRenderTorn = true;
g_pHyprRenderer->renderMonitor(PMONITOR);
}
}
}
}
pNode->lastSize = pNode->pSurface->exists() ? Vector2D{pNode->pSurface->wlr()->current.width, pNode->pSurface->wlr()->current.height} : Vector2D{};
}
void Events::listener_destroySubsurface(void* owner, void* data) {

View file

@ -26,6 +26,8 @@ struct SSurfaceTreeNode {
void* globalOffsetData;
CWindow* pWindowOwner = nullptr;
Vector2D lastSize;
//
bool operator==(const SSurfaceTreeNode& rhs) const {
return pSurface == rhs.pSurface;

View file

@ -1,4 +1,5 @@
#pragma once
#include <functional>
#include <vector>
#include <string>
#include "../macros.hpp"
@ -20,6 +21,15 @@ class CVarList {
std::string join(const std::string& joiner, size_t from = 0, size_t to = 0) const;
void map(std::function<void(std::string&)> func) {
for (auto& s : m_vArgs)
func(s);
}
void append(const std::string arg) {
m_vArgs.emplace_back(arg);
}
std::string operator[](const size_t& idx) const {
if (idx >= m_vArgs.size())
return "";
@ -40,6 +50,15 @@ class CVarList {
return m_vArgs.end();
}
bool contains(const std::string& el) {
for (auto& a : m_vArgs) {
if (a == el)
return true;
}
return false;
}
private:
std::vector<std::string> m_vArgs;
};

View file

@ -28,6 +28,10 @@ Vector2D Vector2D::floor() const {
return Vector2D(std::floor(x), std::floor(y));
}
Vector2D Vector2D::round() const {
return Vector2D(std::round(x), std::round(y));
}
Vector2D Vector2D::clamp(const Vector2D& min, const Vector2D& max) const {
return Vector2D(std::clamp(this->x, min.x, max.x < min.x ? INFINITY : max.x), std::clamp(this->y, min.y, max.y < min.y ? INFINITY : max.y));
}
@ -45,3 +49,7 @@ bool Vector2D::inTriangle(const Vector2D& p1, const Vector2D& p2, const Vector2D
return 0 <= a && a <= 1 && 0 <= b && b <= 1 && 0 <= c && c <= 1;
}
double Vector2D::size() const {
return std::sqrt(x * x + y * y);
}

View file

@ -22,10 +22,13 @@ class Vector2D {
Vector2D operator-(const Vector2D& a) const {
return Vector2D(this->x - a.x, this->y - a.y);
}
Vector2D operator*(const float& a) const {
Vector2D operator-() const {
return Vector2D(-this->x, -this->y);
}
Vector2D operator*(const double& a) const {
return Vector2D(this->x * a, this->y * a);
}
Vector2D operator/(const float& a) const {
Vector2D operator/(const double& a) const {
return Vector2D(this->x / a, this->y / a);
}
@ -52,12 +55,43 @@ class Vector2D {
bool operator<(const Vector2D& a) const {
return this->x < a.x && this->y < a.y;
}
Vector2D& operator+=(const Vector2D& a) {
this->x += a.x;
this->y += a.y;
return *this;
}
Vector2D& operator-=(const Vector2D& a) {
this->x -= a.x;
this->y -= a.y;
return *this;
}
Vector2D& operator*=(const Vector2D& a) {
this->x *= a.x;
this->y *= a.y;
return *this;
}
Vector2D& operator/=(const Vector2D& a) {
this->x /= a.x;
this->y /= a.y;
return *this;
}
Vector2D& operator*=(const double& a) {
this->x *= a;
this->y *= a;
return *this;
}
Vector2D& operator/=(const double& a) {
this->x /= a;
this->y /= a;
return *this;
}
double distance(const Vector2D& other) const;
double size() const;
Vector2D clamp(const Vector2D& min, const Vector2D& max = Vector2D()) const;
Vector2D floor() const;
Vector2D round() const;
bool inTriangle(const Vector2D& p1, const Vector2D& p2, const Vector2D& p3) const;
};

View file

@ -8,6 +8,14 @@ SLayerSurface::SLayerSurface() {
alpha.registerVar();
}
SLayerSurface::~SLayerSurface() {
if (!g_pHyprOpenGL)
return;
g_pHyprRenderer->makeEGLCurrent();
std::erase_if(g_pHyprOpenGL->m_mLayerFramebuffers, [&](const auto& other) { return other.first == this; });
}
void SLayerSurface::applyRules() {
noAnimations = false;
forceBlur = false;
@ -20,7 +28,7 @@ void SLayerSurface::applyRules() {
noAnimations = true;
else if (rule.rule == "blur")
forceBlur = true;
else if (rule.rule.find("ignorealpha") == 0 || rule.rule.find("ignorezero") == 0) {
else if (rule.rule.starts_with("ignorealpha") || rule.rule.starts_with("ignorezero")) {
const auto FIRST_SPACE_POS = rule.rule.find_first_of(' ');
std::string alphaValue = "";
if (FIRST_SPACE_POS != std::string::npos)
@ -31,7 +39,7 @@ void SLayerSurface::applyRules() {
if (!alphaValue.empty())
ignoreAlphaValue = std::stof(alphaValue);
} catch (...) { Debug::log(ERR, "Invalid value passed to ignoreAlpha"); }
} else if (rule.rule.find("xray") == 0) {
} else if (rule.rule.starts_with("xray")) {
CVarList vars{rule.rule, 0, ' '};
try {
xray = configStringToInt(vars[1]);

View file

@ -16,6 +16,7 @@ struct SLayerRule {
struct SLayerSurface {
SLayerSurface();
~SLayerSurface();
void applyRules();
@ -33,7 +34,7 @@ struct SLayerSurface {
DYNLISTENER(commitLayerSurface);
DYNLISTENER(newPopup);
wlr_box geometry = {0, 0, 0, 0};
CBox geometry = {0, 0, 0, 0};
Vector2D position;
zwlr_layer_shell_v1_layer layer;
@ -65,12 +66,12 @@ class CMonitor;
struct SRenderData {
CMonitor* pMonitor;
timespec* when;
int x, y;
double x, y;
// for iters
void* data = nullptr;
wlr_surface* surface = nullptr;
int w, h;
double w, h;
// for rounding
bool dontRound = true;
@ -96,6 +97,8 @@ struct SRenderData {
// for calculating UV
CWindow* pWindow = nullptr;
bool popup = false;
};
struct SExtensionFindingData {
@ -168,18 +171,20 @@ struct SConstraint {
bool active = false;
bool hintSet = false;
Vector2D positionHint = {-1, -1}; // the position hint, but will be set to the current cursor pos if not set.
bool hintSet = false;
Vector2D positionHint = {-1, -1}; // the position hint, but will use cursorPosOnActivate if unset
Vector2D cursorPosOnActivate = {-1, -1};
DYNLISTENER(setConstraintRegion);
DYNLISTENER(destroyConstraint);
CRegion getLogicCoordsRegion();
CRegion getLogicCoordsRegion();
Vector2D getLogicConstraintPos();
Vector2D getLogicConstraintSize();
bool operator==(const SConstraint& b) const {
return constraint == b.constraint;
//
bool operator==(const SConstraint& b) const {
return constraint == b.constraint;
}
};
@ -250,6 +255,8 @@ struct STablet {
wlr_tablet_v2_tablet* wlrTabletV2 = nullptr;
wlr_input_device* wlrDevice = nullptr;
bool relativeInput = false;
std::string name = "";
//
@ -392,3 +399,14 @@ struct SSwitchDevice {
return pWlrDevice == other.pWlrDevice;
}
};
struct STearingController {
wlr_tearing_control_v1* pWlrHint = nullptr;
DYNLISTENER(set);
DYNLISTENER(destroy);
bool operator==(const STearingController& other) {
return pWlrHint == other.pWlrHint;
}
};

View file

@ -2,11 +2,18 @@
#include "MiscFunctions.hpp"
#include <string>
#include "../debug/Log.hpp"
#include "Watchdog.hpp"
void handleWrapped(wl_listener* listener, void* data) {
CHyprWLListener::SWrapper* pWrap = wl_container_of(listener, pWrap, m_sListener);
pWrap->m_pSelf->emit(data);
g_pWatchdog->startWatching();
try {
pWrap->m_pSelf->emit(data);
} catch (std::exception& e) { Debug::log(ERR, "Listener {} timed out and was killed by Watchdog!!!", (uintptr_t)listener); }
g_pWatchdog->endWatching();
}
CHyprWLListener::CHyprWLListener(wl_signal* pSignal, std::function<void(void*, void*)> callback, void* pOwner) {
@ -50,4 +57,4 @@ void CHyprWLListener::initCallback(wl_signal* pSignal, std::function<void(void*,
void CHyprWLListener::emit(void* data) {
m_pCallback(m_pOwner, data);
}
}

View file

@ -27,17 +27,45 @@ wlr_surface* CWLSurface::wlr() const {
return m_pWLRSurface;
}
bool CWLSurface::small() const {
if (!m_pOwner || !exists())
return false;
return m_pOwner->m_vReportedSize.x > m_pWLRSurface->current.buffer_width + 1 || m_pOwner->m_vReportedSize.y > m_pWLRSurface->current.buffer_height + 1;
}
Vector2D CWLSurface::correctSmallVec() const {
if (!m_pOwner || !exists() || !small() || m_bFillIgnoreSmall)
return {};
const auto SIZE = getViewporterCorrectedSize();
return Vector2D{(m_pOwner->m_vReportedSize.x - SIZE.x) / 2, (m_pOwner->m_vReportedSize.y - SIZE.y) / 2}.clamp({}, {INFINITY, INFINITY}) *
(m_pOwner->m_vRealSize.vec() / m_pOwner->m_vReportedSize);
}
Vector2D CWLSurface::getViewporterCorrectedSize() const {
if (!exists())
return {};
return m_pWLRSurface->current.viewport.has_dst ? Vector2D{m_pWLRSurface->current.viewport.dst_width, m_pWLRSurface->current.viewport.dst_height} :
Vector2D{m_pWLRSurface->current.buffer_width, m_pWLRSurface->current.buffer_height};
}
void CWLSurface::destroy() {
if (!m_pWLRSurface)
return;
hyprListener_destroy.removeCallback();
m_pWLRSurface->data = nullptr;
m_pOwner = nullptr;
if (g_pCompositor->m_pLastFocus == m_pWLRSurface)
g_pCompositor->m_pLastFocus = nullptr;
if (g_pInputManager->m_pLastMouseSurface == m_pWLRSurface)
g_pInputManager->m_pLastMouseSurface = nullptr;
if (g_pHyprRenderer->m_sLastCursorData.surf == m_pWLRSurface)
g_pHyprRenderer->m_sLastCursorData.surf.reset();
m_pWLRSurface = nullptr;

View file

@ -1,6 +1,9 @@
#pragma once
#include "../defines.hpp"
class CWindow;
class CWLSurface {
public:
CWLSurface() = default;
@ -17,13 +20,28 @@ class CWLSurface {
wlr_surface* wlr() const;
bool exists() const;
bool small() const; // means surface is smaller than the requested size
Vector2D correctSmallVec() const; // returns a corrective vector for small() surfaces
Vector2D getViewporterCorrectedSize() const;
CWLSurface& operator=(wlr_surface* pSurface) {
destroy();
m_pWLRSurface = pSurface;
init();
// allow stretching. Useful for plugins.
bool m_bFillIgnoreSmall = false;
return *this;
// if present, means this is a base surface of a window. Cleaned on unassign()
CWindow* m_pOwner = nullptr;
// track surface data and avoid dupes
float m_fLastScale = 0;
int m_iLastScale = 0;
wl_output_transform m_eLastTransform = (wl_output_transform)-1;
//
CWLSurface& operator=(wlr_surface* pSurface) {
destroy();
m_pWLRSurface = pSurface;
init();
return *this;
}
bool operator==(const CWLSurface& other) const {
@ -38,6 +56,10 @@ class CWLSurface {
return exists();
}
static CWLSurface* surfaceFromWlr(wlr_surface* pSurface) {
return (CWLSurface*)pSurface->data;
}
private:
wlr_surface* m_pWLRSurface = nullptr;

59
src/helpers/Watchdog.cpp Normal file
View file

@ -0,0 +1,59 @@
#include "Watchdog.hpp"
#include <signal.h>
#include "config/ConfigManager.hpp"
CWatchdog::~CWatchdog() {
m_bExitThread = true;
m_bNotified = true;
m_cvWatchdogCondition.notify_all();
m_pWatchdog.reset();
}
CWatchdog::CWatchdog() {
m_iMainThreadPID = pthread_self();
m_pWatchdog = std::make_unique<std::thread>([this] {
static auto* const PTIMEOUT = &g_pConfigManager->getConfigValuePtr("debug:watchdog_timeout")->intValue;
while (1337) {
std::unique_lock lk(m_mWatchdogMutex);
if (!m_bWillWatch)
m_cvWatchdogCondition.wait(lk, [this] { return m_bNotified; });
else {
if (m_cvWatchdogCondition.wait_for(lk, std::chrono::milliseconds((int)(*PTIMEOUT * 1000.0)), [this] { return m_bNotified; }) == false)
pthread_kill(m_iMainThreadPID, SIGUSR1);
}
if (m_bExitThread)
break;
m_bWatching = false;
m_bNotified = false;
}
});
m_pWatchdog->detach();
}
void CWatchdog::startWatching() {
static auto* const PTIMEOUT = &g_pConfigManager->getConfigValuePtr("debug:watchdog_timeout")->intValue;
if (*PTIMEOUT == 0)
return;
m_tTriggered = std::chrono::high_resolution_clock::now();
m_bWillWatch = true;
m_bWatching = true;
m_bNotified = true;
m_cvWatchdogCondition.notify_all();
}
void CWatchdog::endWatching() {
m_bWatching = false;
m_bWillWatch = false;
m_bNotified = true;
m_cvWatchdogCondition.notify_all();
}

32
src/helpers/Watchdog.hpp Normal file
View file

@ -0,0 +1,32 @@
#pragma once
#include <memory>
#include <chrono>
#include <thread>
#include <condition_variable>
class CWatchdog {
public:
// must be called from the main thread
CWatchdog();
~CWatchdog();
void startWatching();
void endWatching();
private:
std::chrono::high_resolution_clock::time_point m_tTriggered;
pthread_t m_iMainThreadPID = 0;
bool m_bWatching = false;
bool m_bWillWatch = false;
std::unique_ptr<std::thread> m_pWatchdog;
std::mutex m_mWatchdogMutex;
bool m_bNotified = false;
bool m_bExitThread = false;
std::condition_variable m_cvWatchdogCondition;
};
inline std::unique_ptr<CWatchdog> g_pWatchdog;

View file

@ -38,9 +38,10 @@ CWorkspace::~CWorkspace() {
}
void CWorkspace::startAnim(bool in, bool left, bool instant) {
const auto ANIMSTYLE = m_fAlpha.m_pConfig->pValues->internalStyle;
const auto ANIMSTYLE = m_fAlpha.m_pConfig->pValues->internalStyle;
const auto PWORKSPACEGAP = &g_pConfigManager->getConfigValuePtr("general:gaps_workspaces")->intValue;
if (ANIMSTYLE.find("slidefade") == 0) {
if (ANIMSTYLE.starts_with("slidefade")) {
const auto PMONITOR = g_pCompositor->getMonitorFromID(m_iMonitorID);
float movePerc = 100.f;
@ -54,7 +55,7 @@ void CWorkspace::startAnim(bool in, bool left, bool instant) {
m_fAlpha.setValueAndWarp(1.f);
m_vRenderOffset.setValueAndWarp(Vector2D(0, 0));
if (ANIMSTYLE.find("slidefadevert") == 0) {
if (ANIMSTYLE.starts_with("slidefadevert")) {
if (in) {
m_fAlpha.setValueAndWarp(0.f);
m_vRenderOffset.setValueAndWarp(Vector2D(0, (left ? PMONITOR->vecSize.y : -PMONITOR->vecSize.y) * (movePerc / 100.f)));
@ -89,27 +90,29 @@ void CWorkspace::startAnim(bool in, bool left, bool instant) {
}
} else if (ANIMSTYLE == "slidevert") {
// fallback is slide
const auto PMONITOR = g_pCompositor->getMonitorFromID(m_iMonitorID);
const auto PMONITOR = g_pCompositor->getMonitorFromID(m_iMonitorID);
const auto YDISTANCE = PMONITOR->vecSize.y + *PWORKSPACEGAP;
m_fAlpha.setValueAndWarp(1.f); // fix a bug, if switching from fade -> slide.
if (in) {
m_vRenderOffset.setValueAndWarp(Vector2D(0, left ? PMONITOR->vecSize.y : -PMONITOR->vecSize.y));
m_vRenderOffset.setValueAndWarp(Vector2D(0, left ? YDISTANCE : -YDISTANCE));
m_vRenderOffset = Vector2D(0, 0);
} else {
m_vRenderOffset = Vector2D(0, left ? -PMONITOR->vecSize.y : PMONITOR->vecSize.y);
m_vRenderOffset = Vector2D(0, left ? -YDISTANCE : YDISTANCE);
}
} else {
// fallback is slide
const auto PMONITOR = g_pCompositor->getMonitorFromID(m_iMonitorID);
const auto PMONITOR = g_pCompositor->getMonitorFromID(m_iMonitorID);
const auto XDISTANCE = PMONITOR->vecSize.x + *PWORKSPACEGAP;
m_fAlpha.setValueAndWarp(1.f); // fix a bug, if switching from fade -> slide.
if (in) {
m_vRenderOffset.setValueAndWarp(Vector2D(left ? PMONITOR->vecSize.x : -PMONITOR->vecSize.x, 0));
m_vRenderOffset.setValueAndWarp(Vector2D(left ? XDISTANCE : -XDISTANCE, 0));
m_vRenderOffset = Vector2D(0, 0);
} else {
m_vRenderOffset = Vector2D(left ? -PMONITOR->vecSize.x : PMONITOR->vecSize.x, 0);
m_vRenderOffset = Vector2D(left ? -XDISTANCE : XDISTANCE, 0);
}
}

View file

@ -4,9 +4,9 @@
#include <string>
#include "../defines.hpp"
enum eFullscreenMode : uint8_t
{
FULLSCREEN_FULL = 0,
enum eFullscreenMode : int8_t {
FULLSCREEN_INVALID = -1,
FULLSCREEN_FULL = 0,
FULLSCREEN_MAXIMIZED
};
@ -49,12 +49,12 @@ class CWorkspace {
bool m_bDefaultFloating = false;
bool m_bDefaultPseudo = false;
// don't destroy in sanity check
bool m_bIndestructible = false;
// last monitor (used on reconnect)
std::string m_szLastMonitor = "";
// Whether the user configured command for on-created-empty has been executed, if any
bool m_bOnCreatedEmptyExecuted = false;
void startAnim(bool in, bool left, bool instant = false);
void setActive(bool on);

View file

@ -28,8 +28,7 @@ typedef struct {
} xcb_size_hints_t;
typedef unsigned int xcb_window_t;
typedef enum xcb_stack_mode_t
{
typedef enum xcb_stack_mode_t {
XCB_STACK_MODE_ABOVE = 0,
XCB_STACK_MODE_BELOW = 1,
XCB_STACK_MODE_TOP_IF = 2,

View file

@ -5,7 +5,7 @@ CHyprError::CHyprError() {
m_fFadeOpacity.create(AVARTYPE_FLOAT, g_pConfigManager->getAnimationPropertyConfig("fadeIn"), nullptr, AVARDAMAGE_NONE);
m_fFadeOpacity.registerVar();
g_pHookSystem->hookDynamic("focusedMon", [&](void* self, std::any param) {
g_pHookSystem->hookDynamic("focusedMon", [&](void* self, SCallbackInfo& info, std::any param) {
if (!m_bIsCreated)
return;
@ -13,7 +13,7 @@ CHyprError::CHyprError() {
m_bMonitorChanged = true;
});
g_pHookSystem->hookDynamic("preRender", [&](void* self, std::any param) {
g_pHookSystem->hookDynamic("preRender", [&](void* self, SCallbackInfo& info, std::any param) {
if (!m_bIsCreated)
return;
@ -154,7 +154,7 @@ void CHyprError::draw() {
const auto PMONITOR = g_pHyprOpenGL->m_RenderData.pMonitor;
wlr_box monbox = {0, 0, PMONITOR->vecPixelSize.x, PMONITOR->vecPixelSize.y};
CBox monbox = {0, 0, PMONITOR->vecPixelSize.x, PMONITOR->vecPixelSize.y};
m_bDamageBox.x = (int)PMONITOR->vecPosition.x;
m_bDamageBox.y = (int)PMONITOR->vecPosition.y;

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