diff --git a/.clang-format-ignore b/.clang-format-ignore new file mode 100644 index 00000000..c6bf34ed --- /dev/null +++ b/.clang-format-ignore @@ -0,0 +1 @@ +subprojects/**/* diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 90bb9286..b6582979 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -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: "
+ System/Version info + + + ```sh + + + + ``` + + +
" validations: required: true diff --git a/.github/actions/setup_base/action.yml b/.github/actions/setup_base/action.yml new file mode 100644 index 00000000..2985ce9a --- /dev/null +++ b/.github/actions/setup_base/action.yml @@ -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 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2c9b60a6..01bb43d1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -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 diff --git a/.github/workflows/nix-build.yml b/.github/workflows/nix-build.yml index fb98f099..2c05d5e2 100644 --- a/.github/workflows/nix-build.yml +++ b/.github/workflows/nix-build.yml @@ -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 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 2c1517c1..cbf99a02 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -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 diff --git a/.github/workflows/security-checks.yml b/.github/workflows/security-checks.yml index 6b7d71e5..6a86f70e 100644 --- a/.github/workflows/security-checks.yml +++ b/.github/workflows/security-checks.yml @@ -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: diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 00000000..51f6b91e --- /dev/null +++ b/.github/workflows/stale.yml @@ -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 diff --git a/.gitignore b/.gitignore index 96fd6c87..dfee530e 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,5 @@ gmon.out *.tar.gz PKGBUILD + +src/version.h diff --git a/CMakeLists.txt b/CMakeLists.txt old mode 100644 new mode 100755 index a52c798a..f894212c --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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=$,disabled,enabled> -Dexamples=false -Drenderers=gles2 $,-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) diff --git a/Makefile b/Makefile index 518df327..e3224970 100644 --- a/Makefile +++ b/Makefile @@ -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 \ diff --git a/README.md b/README.md index b16019f0..9f4aa847 100644 --- a/README.md +++ b/README.md @@ -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... [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 diff --git a/assets/hyprland-portals.conf b/assets/hyprland-portals.conf index 2ec1f0de..da8b5db0 100644 --- a/assets/hyprland-portals.conf +++ b/assets/hyprland-portals.conf @@ -1,2 +1,2 @@ [preferred] -default=hyprland \ No newline at end of file +default=hyprland;gtk \ No newline at end of file diff --git a/example/examplePlugin/Makefile b/example/examplePlugin/Makefile deleted file mode 100644 index bb79532a..00000000 --- a/example/examplePlugin/Makefile +++ /dev/null @@ -1,8 +0,0 @@ -# compile with HYPRLAND_HEADERS= 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 diff --git a/example/examplePlugin/customDecoration.cpp b/example/examplePlugin/customDecoration.cpp deleted file mode 100644 index 52b8b715..00000000 --- a/example/examplePlugin/customDecoration.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#include "customDecoration.hpp" -#include -#include -#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); -} \ No newline at end of file diff --git a/example/examplePlugin/customDecoration.hpp b/example/examplePlugin/customDecoration.hpp deleted file mode 100644 index dbb0c0e2..00000000 --- a/example/examplePlugin/customDecoration.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#define WLR_USE_UNSTABLE - -#include - -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; -}; \ No newline at end of file diff --git a/example/examplePlugin/customLayout.cpp b/example/examplePlugin/customLayout.cpp deleted file mode 100644 index 8001c72d..00000000 --- a/example/examplePlugin/customLayout.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include "customLayout.hpp" -#include -#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(); -} \ No newline at end of file diff --git a/example/examplePlugin/customLayout.hpp b/example/examplePlugin/customLayout.hpp deleted file mode 100644 index 44797ccc..00000000 --- a/example/examplePlugin/customLayout.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#define WLR_USE_UNSTABLE - -#include - -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 m_vWindowData; -}; \ No newline at end of file diff --git a/example/examplePlugin/globals.hpp b/example/examplePlugin/globals.hpp deleted file mode 100644 index 22574754..00000000 --- a/example/examplePlugin/globals.hpp +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include - -inline HANDLE PHANDLE = nullptr; \ No newline at end of file diff --git a/example/examplePlugin/main.cpp b/example/examplePlugin/main.cpp deleted file mode 100644 index 560af387..00000000 --- a/example/examplePlugin/main.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#define WLR_USE_UNSTABLE - -#include "globals.hpp" - -#include -#include -#include "customLayout.hpp" -#include "customDecoration.hpp" - -#include -#include - -// Methods -inline std::unique_ptr 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(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(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(); - - 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"); -} \ No newline at end of file diff --git a/example/hyprland.conf b/example/hyprland.conf index 895a44f2..62b6e6e5 100644 --- a/example/hyprland.conf +++ b/example/hyprland.conf @@ -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 diff --git a/flake.lock b/flake.lock index 8a52cc43..d7db1bca 100644 --- a/flake.lock +++ b/flake.lock @@ -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": { diff --git a/flake.nix b/flake.nix index 91135803..7f9a958b 100644 --- a/flake.nix +++ b/flake.nix @@ -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); diff --git a/hyprctl/CMakeLists.txt b/hyprctl/CMakeLists.txt new file mode 100644 index 00000000..99964b50 --- /dev/null +++ b/hyprctl/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.19) + +project( + hyprctl + DESCRIPTION "Control utility for Hyprland" +) + +add_executable(hyprctl "main.cpp") \ No newline at end of file diff --git a/hyprctl/Makefile b/hyprctl/Makefile index ec819918..9798320c 100644 --- a/hyprctl/Makefile +++ b/hyprctl/Makefile @@ -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 diff --git a/hyprctl/main.cpp b/hyprctl/main.cpp index aebc1cbb..281a74ac 100644 --- a/hyprctl/main.cpp +++ b/hyprctl/main.cpp @@ -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(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 { diff --git a/hyprpm/CMakeLists.txt b/hyprpm/CMakeLists.txt new file mode 100644 index 00000000..a6bd1eca --- /dev/null +++ b/hyprpm/CMakeLists.txt @@ -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) diff --git a/hyprpm/src/core/DataState.cpp b/hyprpm/src/core/DataState.cpp new file mode 100644 index 00000000..724c40ac --- /dev/null +++ b/hyprpm/src/core/DataState.cpp @@ -0,0 +1,222 @@ +#include "DataState.hpp" +#include +#include +#include +#include +#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 DataState::getAllRepositories() { + ensureStateStoreExists(); + + const auto PATH = getDataStatePath(); + + std::vector 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; +} \ No newline at end of file diff --git a/hyprpm/src/core/DataState.hpp b/hyprpm/src/core/DataState.hpp new file mode 100644 index 00000000..ac81dae1 --- /dev/null +++ b/hyprpm/src/core/DataState.hpp @@ -0,0 +1,21 @@ +#pragma once +#include +#include +#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 getAllRepositories(); +}; \ No newline at end of file diff --git a/hyprpm/src/core/Manifest.cpp b/hyprpm/src/core/Manifest.cpp new file mode 100644 index 00000000..27ad058a --- /dev/null +++ b/hyprpm/src/core/Manifest.cpp @@ -0,0 +1,104 @@ +#include "Manifest.hpp" +#include +#include + +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; + } +} \ No newline at end of file diff --git a/hyprpm/src/core/Manifest.hpp b/hyprpm/src/core/Manifest.hpp new file mode 100644 index 00000000..55b4a7f0 --- /dev/null +++ b/hyprpm/src/core/Manifest.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +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 authors; + std::vector buildSteps; + std::string output; + bool failed = false; + }; + + struct { + std::string name; + std::vector authors; + std::vector> commitPins; + } m_sRepository; + + std::vector m_vPlugins; + bool m_bGood = true; +}; \ No newline at end of file diff --git a/hyprpm/src/core/Plugin.hpp b/hyprpm/src/core/Plugin.hpp new file mode 100644 index 00000000..a69478d1 --- /dev/null +++ b/hyprpm/src/core/Plugin.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +struct SPlugin { + std::string name; + std::string filename; + bool enabled = false; + bool failed = false; +}; + +struct SPluginRepository { + std::string url; + std::string name; + std::vector plugins; + std::string hash; +}; \ No newline at end of file diff --git a/hyprpm/src/core/PluginManager.cpp b/hyprpm/src/core/PluginManager.cpp new file mode 100644 index 00000000..4cc93d4b --- /dev/null +++ b/hyprpm/src/core/PluginManager.cpp @@ -0,0 +1,731 @@ +#include "PluginManager.hpp" +#include "../helpers/Colors.hpp" +#include "../progress/CProgressBar.hpp" +#include "Manifest.hpp" +#include "DataState.hpp" + +#include +#include +#include +#include +#include +#include + +#include + +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 buffer; + std::string result; + const std::unique_ptr 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 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(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(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(ifs)), (std::istreambuf_iterator())); + 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 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(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(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 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"; +} diff --git a/hyprpm/src/core/PluginManager.hpp b/hyprpm/src/core/PluginManager.hpp new file mode 100644 index 00000000..3c5c7c5c --- /dev/null +++ b/hyprpm/src/core/PluginManager.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include +#include + +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 g_pPluginManager; \ No newline at end of file diff --git a/hyprpm/src/helpers/Colors.hpp b/hyprpm/src/helpers/Colors.hpp new file mode 100644 index 00000000..f3298bdd --- /dev/null +++ b/hyprpm/src/helpers/Colors.hpp @@ -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"; +}; \ No newline at end of file diff --git a/hyprpm/src/main.cpp b/hyprpm/src/main.cpp new file mode 100644 index 00000000..7bc44141 --- /dev/null +++ b/hyprpm/src/main.cpp @@ -0,0 +1,149 @@ +#include "progress/CProgressBar.hpp" +#include "helpers/Colors.hpp" +#include "core/PluginManager.hpp" + +#include +#include +#include +#include +#include + +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 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 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(); + 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; +} \ No newline at end of file diff --git a/hyprpm/src/meson.build b/hyprpm/src/meson.build new file mode 100644 index 00000000..0adae7aa --- /dev/null +++ b/hyprpm/src/meson.build @@ -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 +) diff --git a/hyprpm/src/progress/CProgressBar.cpp b/hyprpm/src/progress/CProgressBar.cpp new file mode 100644 index 00000000..45602f82 --- /dev/null +++ b/hyprpm/src/progress/CProgressBar.cpp @@ -0,0 +1,80 @@ +#include "CProgressBar.hpp" + +#include +#include +#include +#include + +#include +#include +#include + +#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(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(percentDone * 100.0)) + " "; + else + message += " " + std::format("{} / {}", m_iSteps, m_iMaxSteps) + " "; + + // draw message + std::cout << message + " " + m_szCurrentMessage; + + std::fflush(stdout); +} \ No newline at end of file diff --git a/hyprpm/src/progress/CProgressBar.hpp b/hyprpm/src/progress/CProgressBar.hpp new file mode 100644 index 00000000..6ac18f21 --- /dev/null +++ b/hyprpm/src/progress/CProgressBar.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include + +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; +}; \ No newline at end of file diff --git a/meson.build b/meson.build index d515621e..81d6e403 100644 --- a/meson.build +++ b/meson.build @@ -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') diff --git a/nix/default.nix b/nix/default.nix index 96fa12d7..751457ca 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -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 + ]} ''} ''; diff --git a/nix/hm-module.nix b/nix/hm-module.nix index 123bceb3..e3c788d0 100644 --- a/nix/hm-module.nix +++ b/nix/hm-module.nix @@ -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 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") - ]; } diff --git a/nix/module.nix b/nix/module.nix index fcb29837..6f553893 100644 --- a/nix/module.nix +++ b/nix/module.nix @@ -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 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"] - ) - ]; } diff --git a/nix/overlays.nix b/nix/overlays.nix index 3c38d740..5eb7b082 100644 --- a/nix/overlays.nix +++ b/nix/overlays.nix @@ -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"]; - }); }; }; } diff --git a/nix/patches/meson-build.patch b/nix/patches/meson-build.patch index d0cba18a..844eacae 100644 --- a/nix/patches/meson-build.patch +++ b/nix/patches/meson-build.patch @@ -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'), diff --git a/nix/patches/wlroots-nvidia.patch b/nix/patches/wlroots-nvidia.patch deleted file mode 100644 index 01bdd342..00000000 --- a/nix/patches/wlroots-nvidia.patch +++ /dev/null @@ -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, diff --git a/nix/wlroots.nix b/nix/wlroots.nix index c1aa27de..bd3af688 100644 --- a/nix/wlroots.nix +++ b/nix/wlroots.nix @@ -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 }) diff --git a/props.json b/props.json index 2c6747a2..93c30160 100644 --- a/props.json +++ b/props.json @@ -1,3 +1,3 @@ { - "version": "0.30.0" + "version": "0.34.0" } \ No newline at end of file diff --git a/protocols/meson.build b/protocols/meson.build index 60ae20cb..cc111e98 100644 --- a/protocols/meson.build +++ b/protocols/meson.build @@ -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'], diff --git a/scripts/generateVersion.sh b/scripts/generateVersion.sh new file mode 100755 index 00000000..443c5c54 --- /dev/null +++ b/scripts/generateVersion.sh @@ -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 diff --git a/src/Compositor.cpp b/src/Compositor.cpp index 4ddac879..8d16f22d 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -24,7 +24,7 @@ void handleUnrecoverableSignal(int sig) { signal(SIGABRT, SIG_DFL); signal(SIGSEGV, SIG_DFL); - if (g_pHookSystem->m_bCurrentEventPlugin) { + if (g_pHookSystem && g_pHookSystem->m_bCurrentEventPlugin) { longjmp(g_pHookSystem->m_jbHookFaultJumpBuf, 1); return; } @@ -34,6 +34,13 @@ void handleUnrecoverableSignal(int sig) { abort(); } +void handleUserSignal(int sig) { + if (sig == SIGUSR1) { + // means we have to unwind a timed out event + throw std::exception(); + } +} + CCompositor::CCompositor() { m_iHyprlandPID = getpid(); @@ -43,7 +50,7 @@ CCompositor::CCompositor() { if (!std::filesystem::exists("/tmp/hypr")) { std::filesystem::create_directory("/tmp/hypr"); - std::filesystem::permissions("/tmp/hypr", std::filesystem::perms::all, std::filesystem::perm_options::replace); + std::filesystem::permissions("/tmp/hypr", std::filesystem::perms::all | std::filesystem::perms::sticky_bit, std::filesystem::perm_options::replace); } const auto INSTANCEPATH = "/tmp/hypr/" + m_szInstanceSignature; @@ -74,6 +81,7 @@ CCompositor::CCompositor() { CCompositor::~CCompositor() { cleanup(); + g_pDecorationPositioner.reset(); g_pPluginSystem.reset(); g_pHyprNotificationOverlay.reset(); g_pDebugOverlay.reset(); @@ -92,6 +100,7 @@ CCompositor::~CCompositor() { g_pAnimationManager.reset(); g_pKeybindManager.reset(); g_pHookSystem.reset(); + g_pWatchdog.reset(); } void CCompositor::setRandomSplash() { @@ -112,18 +121,19 @@ void CCompositor::initServer() { wl_event_loop_add_signal(m_sWLEventLoop, SIGTERM, handleCritSignal, nullptr); signal(SIGSEGV, handleUnrecoverableSignal); signal(SIGABRT, handleUnrecoverableSignal); - //wl_event_loop_add_signal(m_sWLEventLoop, SIGINT, handleCritSignal, nullptr); + signal(SIGUSR1, handleUserSignal); initManagers(STAGE_PRIORITY); - if (const auto ENV = getenv("HYPRLAND_TRACE"); ENV && std::string(ENV) == "1") + if (envEnabled("HYPRLAND_TRACE")) Debug::trace = true; wlr_log_init(WLR_INFO, NULL); - const auto LOGWLR = getenv("HYPRLAND_LOG_WLR"); - if (LOGWLR && std::string(LOGWLR) == "1") + if (envEnabled("HYPRLAND_LOG_WLR")) wlr_log_init(WLR_DEBUG, Debug::wlrLog); + else + wlr_log_init(WLR_ERROR, Debug::wlrLog); m_sWLRBackend = wlr_backend_autocreate(m_sWLDisplay, &m_sWLRSession); @@ -179,11 +189,11 @@ void CCompositor::initServer() { m_sWLRGammaCtrlMgr = wlr_gamma_control_manager_v1_create(m_sWLDisplay); - m_sWLROutputLayout = wlr_output_layout_create(); + m_sWLROutputLayout = wlr_output_layout_create(m_sWLDisplay); m_sWLROutputPowerMgr = wlr_output_power_manager_v1_create(m_sWLDisplay); - m_sWLRXDGShell = wlr_xdg_shell_create(m_sWLDisplay, 5); + m_sWLRXDGShell = wlr_xdg_shell_create(m_sWLDisplay, 6); m_sWLRCursor = wlr_cursor_create(); wlr_cursor_attach_output_layout(m_sWLRCursor, m_sWLROutputLayout); @@ -204,7 +214,6 @@ void CCompositor::initServer() { m_sWLRPresentation = wlr_presentation_create(m_sWLDisplay, m_sWLRBackend); - m_sWLRIdle = wlr_idle_create(m_sWLDisplay); m_sWLRIdleNotifier = wlr_idle_notifier_v1_create(m_sWLDisplay); m_sWLRLayerShell = wlr_layer_shell_v1_create(m_sWLDisplay, 4); @@ -215,7 +224,6 @@ void CCompositor::initServer() { m_sWLROutputMgr = wlr_output_manager_v1_create(m_sWLDisplay); - m_sWLRInhibitMgr = wlr_input_inhibit_manager_create(m_sWLDisplay); m_sWLRKbShInhibitMgr = wlr_keyboard_shortcuts_inhibit_v1_create(m_sWLDisplay); m_sWLRPointerConstraints = wlr_pointer_constraints_v1_create(m_sWLDisplay); @@ -257,6 +265,8 @@ void CCompositor::initServer() { m_sWLRCursorShapeMgr = wlr_cursor_shape_manager_v1_create(m_sWLDisplay, 1); + m_sWLRTearingControlMgr = wlr_tearing_control_manager_v1_create(m_sWLDisplay, 1); + if (!m_sWLRHeadlessBackend) { Debug::log(CRIT, "Couldn't create the headless backend"); throwError("wlr_headless_backend_create() failed!"); @@ -271,7 +281,7 @@ void CCompositor::initServer() { void CCompositor::initAllSignals() { addWLSignal(&m_sWLRBackend->events.new_output, &Events::listen_newOutput, m_sWLRBackend, "Backend"); - addWLSignal(&m_sWLRXDGShell->events.new_surface, &Events::listen_newXDGSurface, m_sWLRXDGShell, "XDG Shell"); + addWLSignal(&m_sWLRXDGShell->events.new_toplevel, &Events::listen_newXDGToplevel, m_sWLRXDGShell, "XDG Shell"); addWLSignal(&m_sWLRCursor->events.motion, &Events::listen_mouseMove, m_sWLRCursor, "WLRCursor"); addWLSignal(&m_sWLRCursor->events.motion_absolute, &Events::listen_mouseMoveAbsolute, m_sWLRCursor, "WLRCursor"); addWLSignal(&m_sWLRCursor->events.button, &Events::listen_mouseButton, m_sWLRCursor, "WLRCursor"); @@ -300,8 +310,6 @@ void CCompositor::initAllSignals() { addWLSignal(&m_sWLROutputLayout->events.change, &Events::listen_change, m_sWLROutputLayout, "OutputLayout"); addWLSignal(&m_sWLROutputMgr->events.apply, &Events::listen_outputMgrApply, m_sWLROutputMgr, "OutputMgr"); addWLSignal(&m_sWLROutputMgr->events.test, &Events::listen_outputMgrTest, m_sWLROutputMgr, "OutputMgr"); - addWLSignal(&m_sWLRInhibitMgr->events.activate, &Events::listen_InhibitActivate, m_sWLRInhibitMgr, "InhibitMgr"); - addWLSignal(&m_sWLRInhibitMgr->events.deactivate, &Events::listen_InhibitDeactivate, m_sWLRInhibitMgr, "InhibitMgr"); addWLSignal(&m_sWLRPointerConstraints->events.new_constraint, &Events::listen_newConstraint, m_sWLRPointerConstraints, "PointerConstraints"); addWLSignal(&m_sWLRXDGDecoMgr->events.new_toplevel_decoration, &Events::listen_NewXDGDeco, m_sWLRXDGDecoMgr, "XDGDecoMgr"); addWLSignal(&m_sWLRVirtPtrMgr->events.new_virtual_pointer, &Events::listen_newVirtPtr, m_sWLRVirtPtrMgr, "VirtPtrMgr"); @@ -315,6 +323,7 @@ void CCompositor::initAllSignals() { addWLSignal(&m_sWLRSessionLockMgr->events.new_lock, &Events::listen_newSessionLock, m_sWLRSessionLockMgr, "SessionLockMgr"); addWLSignal(&m_sWLRGammaCtrlMgr->events.set_gamma, &Events::listen_setGamma, m_sWLRGammaCtrlMgr, "GammaCtrlMgr"); addWLSignal(&m_sWLRCursorShapeMgr->events.request_set_shape, &Events::listen_setCursorShape, m_sWLRCursorShapeMgr, "CursorShapeMgr"); + addWLSignal(&m_sWLRTearingControlMgr->events.new_object, &Events::listen_newTearingHint, m_sWLRTearingControlMgr, "TearingControlMgr"); if (m_sWRLDRMLeaseMgr) addWLSignal(&m_sWRLDRMLeaseMgr->events.request, &Events::listen_leaseRequest, &m_sWRLDRMLeaseMgr, "DRM"); @@ -329,10 +338,11 @@ void CCompositor::cleanup() { removeLockFile(); - m_bIsShuttingDown = true; + m_bIsShuttingDown = true; + Debug::shuttingDown = true; #ifdef USES_SYSTEMD - if (sd_booted() > 0) + if (sd_booted() > 0 && !envEnabled("HYPRLAND_NO_SD_NOTIFY")) sd_notify(0, "STOPPING=1"); #endif @@ -392,6 +402,7 @@ void CCompositor::initManagers(eManagersInitStage stage) { g_pLayoutManager = std::make_unique(); g_pConfigManager->init(); + g_pWatchdog = std::make_unique(); // requires config } break; case STAGE_LATE: { Debug::log(LOG, "Creating the ThreadManager!"); @@ -428,6 +439,9 @@ void CCompositor::initManagers(eManagersInitStage stage) { Debug::log(LOG, "Creating the PluginSystem!"); g_pPluginSystem = std::make_unique(); g_pConfigManager->handlePluginLoads(); + + Debug::log(LOG, "Creating the DecorationPositioner!"); + g_pDecorationPositioner = std::make_unique(); } break; default: UNREACHABLE(); } @@ -450,6 +464,25 @@ void CCompositor::removeLockFile() { std::filesystem::remove(PATH); } +void CCompositor::prepareFallbackOutput() { + // create a backup monitor + wlr_backend* headless = nullptr; + wlr_multi_for_each_backend( + m_sWLRBackend, + [](wlr_backend* b, void* data) { + if (wlr_backend_is_headless(b)) + *((wlr_backend**)data) = b; + }, + &headless); + + if (!headless) { + Debug::log(WARN, "Unsafe state will be ineffective, no fallback output"); + return; + } + + wlr_headless_add_output(headless, 1920, 1080); +} + void CCompositor::startCompositor() { initAllSignals(); @@ -483,14 +516,15 @@ void CCompositor::startCompositor() { signal(SIGPIPE, SIG_IGN); - if (m_sWLRSession /* Session-less Hyprland usually means a nest, don't update the env in that case */ && fork() == 0) - execl( - "/bin/sh", "/bin/sh", "-c", + if (m_sWLRSession /* Session-less Hyprland usually means a nest, don't update the env in that case */) { + const auto CMD = #ifdef USES_SYSTEMD - "systemctl --user import-environment DISPLAY WAYLAND_DISPLAY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP && hash dbus-update-activation-environment 2>/dev/null && " + "systemctl --user import-environment DISPLAY WAYLAND_DISPLAY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP QT_QPA_PLATFORMTHEME && hash " + "dbus-update-activation-environment 2>/dev/null && " #endif - "dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP HYPRLAND_INSTANCE_SIGNATURE", - nullptr); + "dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP HYPRLAND_INSTANCE_SIGNATURE QT_QPA_PLATFORMTHEME"; + g_pKeybindManager->spawn(CMD); + } Debug::log(LOG, "Running on WAYLAND_DISPLAY: {}", m_szWLDisplaySocket); @@ -501,13 +535,16 @@ void CCompositor::startCompositor() { throwError("The backend could not start!"); } - wlr_cursor_set_xcursor(m_sWLRCursor, m_sWLRXCursorMgr, "left_ptr"); + prepareFallbackOutput(); + + g_pHyprRenderer->setCursorFromName("left_ptr"); #ifdef USES_SYSTEMD - if (sd_booted() > 0) + if (sd_booted() > 0) { // tell systemd that we are ready so it can start other bond, following, related units - sd_notify(0, "READY=1"); - else + if (!envEnabled("HYPRLAND_NO_SD_NOTIFY")) + sd_notify(0, "READY=1"); + } else Debug::log(LOG, "systemd integration is baked in but system itself is not booted à la systemd!"); #endif @@ -539,7 +576,7 @@ CMonitor* CCompositor::getMonitorFromName(const std::string& name) { CMonitor* CCompositor::getMonitorFromDesc(const std::string& desc) { for (auto& m : m_vMonitors) { - if (m->output->description && std::string(m->output->description).find(desc) == 0) + if (m->szDescription.starts_with(desc)) return m.get(); } return nullptr; @@ -592,100 +629,40 @@ bool CCompositor::windowExists(CWindow* pWindow) { return false; } -CWindow* CCompositor::vectorToWindow(const Vector2D& pos) { - const auto PMONITOR = getMonitorFromVector(pos); - - if (PMONITOR->specialWorkspaceID) { - for (auto& w : m_vWindows | std::views::reverse) { - wlr_box box = {w->m_vRealPosition.vec().x, w->m_vRealPosition.vec().y, w->m_vRealSize.vec().x, w->m_vRealSize.vec().y}; - if (w->m_bIsFloating && w->m_iWorkspaceID == PMONITOR->specialWorkspaceID && w->m_bIsMapped && wlr_box_contains_point(&box, pos.x, pos.y) && !w->isHidden() && - !w->m_bNoFocus) - return w.get(); - } - - for (auto& w : m_vWindows) { - wlr_box box = {w->m_vRealPosition.vec().x, w->m_vRealPosition.vec().y, w->m_vRealSize.vec().x, w->m_vRealSize.vec().y}; - if (w->m_iWorkspaceID == PMONITOR->specialWorkspaceID && wlr_box_contains_point(&box, pos.x, pos.y) && w->m_bIsMapped && !w->m_bIsFloating && !w->isHidden() && - !w->m_bNoFocus) - return w.get(); - } - } - - // pinned - for (auto& w : m_vWindows | std::views::reverse) { - wlr_box box = {w->m_vRealPosition.vec().x, w->m_vRealPosition.vec().y, w->m_vRealSize.vec().x, w->m_vRealSize.vec().y}; - if (wlr_box_contains_point(&box, pos.x, pos.y) && w->m_bIsMapped && w->m_bIsFloating && !w->isHidden() && w->m_bPinned && !w->m_bNoFocus) - return w.get(); - } - - // first loop over floating cuz they're above, m_vWindows should be sorted bottom->top, for tiled it doesn't matter. - for (auto& w : m_vWindows | std::views::reverse) { - wlr_box box = {w->m_vRealPosition.vec().x, w->m_vRealPosition.vec().y, w->m_vRealSize.vec().x, w->m_vRealSize.vec().y}; - if (wlr_box_contains_point(&box, pos.x, pos.y) && w->m_bIsMapped && w->m_bIsFloating && isWorkspaceVisible(w->m_iWorkspaceID) && !w->isHidden() && !w->m_bPinned && - !w->m_bNoFocus) - return w.get(); - } - - for (auto& w : m_vWindows) { - wlr_box box = {w->m_vRealPosition.vec().x, w->m_vRealPosition.vec().y, w->m_vRealSize.vec().x, w->m_vRealSize.vec().y}; - if (wlr_box_contains_point(&box, pos.x, pos.y) && w->m_bIsMapped && !w->m_bIsFloating && PMONITOR->activeWorkspace == w->m_iWorkspaceID && !w->isHidden() && !w->m_bNoFocus) - return w.get(); - } - - return nullptr; -} - CWindow* CCompositor::vectorToWindowTiled(const Vector2D& pos) { const auto PMONITOR = getMonitorFromVector(pos); if (PMONITOR->specialWorkspaceID) { for (auto& w : m_vWindows) { - wlr_box box = {w->m_vPosition.x, w->m_vPosition.y, w->m_vSize.x, w->m_vSize.y}; - if (w->m_iWorkspaceID == PMONITOR->specialWorkspaceID && wlr_box_contains_point(&box, pos.x, pos.y) && !w->m_bIsFloating && !w->isHidden() && !w->m_bNoFocus) + CBox box = {w->m_vPosition.x, w->m_vPosition.y, w->m_vSize.x, w->m_vSize.y}; + if (w->m_iWorkspaceID == PMONITOR->specialWorkspaceID && box.containsPoint(pos) && !w->m_bIsFloating && !w->isHidden() && !w->m_bNoFocus) return w.get(); } } for (auto& w : m_vWindows) { - wlr_box box = {w->m_vPosition.x, w->m_vPosition.y, w->m_vSize.x, w->m_vSize.y}; - if (w->m_bIsMapped && wlr_box_contains_point(&box, pos.x, pos.y) && w->m_iWorkspaceID == PMONITOR->activeWorkspace && !w->m_bIsFloating && !w->isHidden() && !w->m_bNoFocus) + CBox box = {w->m_vPosition.x, w->m_vPosition.y, w->m_vSize.x, w->m_vSize.y}; + if (w->m_bIsMapped && box.containsPoint(pos) && w->m_iWorkspaceID == PMONITOR->activeWorkspace && !w->m_bIsFloating && !w->isHidden() && !w->m_bNoFocus) return w.get(); } return nullptr; } -CWindow* CCompositor::vectorToWindowIdeal(const Vector2D& pos) { +CWindow* CCompositor::vectorToWindowIdeal(const Vector2D& pos, CWindow* pIgnoreWindow) { const auto PMONITOR = getMonitorFromVector(pos); static auto* const PRESIZEONBORDER = &g_pConfigManager->getConfigValuePtr("general:resize_on_border")->intValue; static auto* const PBORDERSIZE = &g_pConfigManager->getConfigValuePtr("general:border_size")->intValue; static auto* const PBORDERGRABEXTEND = &g_pConfigManager->getConfigValuePtr("general:extend_border_grab_area")->intValue; + static auto* const PSPECIALFALLTHRU = &g_pConfigManager->getConfigValuePtr("input:special_fallthrough")->intValue; const auto BORDER_GRAB_AREA = *PRESIZEONBORDER ? *PBORDERSIZE + *PBORDERGRABEXTEND : 0; - // special workspace - if (PMONITOR->specialWorkspaceID) { - for (auto& w : m_vWindows | std::views::reverse) { - const auto BB = w->getWindowInputBox(); - wlr_box box = {BB.x - BORDER_GRAB_AREA, BB.y - BORDER_GRAB_AREA, BB.width + 2 * BORDER_GRAB_AREA, BB.height + 2 * BORDER_GRAB_AREA}; - if (w->m_bIsFloating && w->m_iWorkspaceID == PMONITOR->specialWorkspaceID && w->m_bIsMapped && wlr_box_contains_point(&box, pos.x, pos.y) && !w->isHidden() && - !w->m_bX11ShouldntFocus && !w->m_bNoFocus) - return w.get(); - } - - for (auto& w : m_vWindows) { - wlr_box box = {w->m_vPosition.x, w->m_vPosition.y, w->m_vSize.x, w->m_vSize.y}; - if (!w->m_bIsFloating && w->m_iWorkspaceID == PMONITOR->specialWorkspaceID && w->m_bIsMapped && wlr_box_contains_point(&box, pos.x, pos.y) && !w->isHidden() && - !w->m_bX11ShouldntFocus && !w->m_bNoFocus) - return w.get(); - } - } - // pinned windows on top of floating regardless for (auto& w : m_vWindows | std::views::reverse) { const auto BB = w->getWindowInputBox(); - wlr_box box = {BB.x - BORDER_GRAB_AREA, BB.y - BORDER_GRAB_AREA, BB.width + 2 * BORDER_GRAB_AREA, BB.height + 2 * BORDER_GRAB_AREA}; - if (w->m_bIsFloating && w->m_bIsMapped && !w->isHidden() && !w->m_bX11ShouldntFocus && w->m_bPinned && !w->m_bNoFocus) { - if (wlr_box_contains_point(&box, m_sWLRCursor->x, m_sWLRCursor->y)) + CBox box = {BB.x - BORDER_GRAB_AREA, BB.y - BORDER_GRAB_AREA, BB.width + 2 * BORDER_GRAB_AREA, BB.height + 2 * BORDER_GRAB_AREA}; + if (w->m_bIsFloating && w->m_bIsMapped && !w->isHidden() && !w->m_bX11ShouldntFocus && w->m_bPinned && !w->m_bNoFocus && w.get() != pIgnoreWindow) { + if (box.containsPoint({m_sWLRCursor->x, m_sWLRCursor->y})) return w.get(); if (!w->m_bIsX11) { @@ -695,48 +672,81 @@ CWindow* CCompositor::vectorToWindowIdeal(const Vector2D& pos) { } } - // first loop over floating cuz they're above, m_lWindows should be sorted bottom->top, for tiled it doesn't matter. - for (auto& w : m_vWindows | std::views::reverse) { - const auto BB = w->getWindowInputBox(); - wlr_box box = {BB.x - BORDER_GRAB_AREA, BB.y - BORDER_GRAB_AREA, BB.width + 2 * BORDER_GRAB_AREA, BB.height + 2 * BORDER_GRAB_AREA}; - if (w->m_bIsFloating && w->m_bIsMapped && isWorkspaceVisible(w->m_iWorkspaceID) && !w->isHidden() && !w->m_bPinned && !w->m_bNoFocus) { - // OR windows should add focus to parent - if (w->m_bX11ShouldntFocus && w->m_iX11Type != 2) + auto windowForWorkspace = [&](bool special) -> CWindow* { + // first loop over floating cuz they're above, m_lWindows should be sorted bottom->top, for tiled it doesn't matter. + for (auto& w : m_vWindows | std::views::reverse) { + + if (special && !isWorkspaceSpecial(w->m_iWorkspaceID)) // because special floating may creep up into regular continue; - if (wlr_box_contains_point(&box, m_sWLRCursor->x, m_sWLRCursor->y)) { + const auto BB = w->getWindowInputBox(); + CBox box = {BB.x - BORDER_GRAB_AREA, BB.y - BORDER_GRAB_AREA, BB.width + 2 * BORDER_GRAB_AREA, BB.height + 2 * BORDER_GRAB_AREA}; + if (w->m_bIsFloating && w->m_bIsMapped && isWorkspaceVisible(w->m_iWorkspaceID) && !w->isHidden() && !w->m_bPinned && !w->m_bNoFocus && w.get() != pIgnoreWindow) { + // OR windows should add focus to parent + if (w->m_bX11ShouldntFocus && w->m_iX11Type != 2) + continue; - if (w->m_bIsX11 && w->m_iX11Type == 2 && !wlr_xwayland_or_surface_wants_focus(w->m_uSurface.xwayland)) { - // Override Redirect - return g_pCompositor->m_pLastWindow; // we kinda trick everything here. - // TODO: this is wrong, we should focus the parent, but idk how to get it considering it's nullptr in most cases. + if (box.containsPoint({m_sWLRCursor->x, m_sWLRCursor->y})) { + + if (w->m_bIsX11 && w->m_iX11Type == 2 && !wlr_xwayland_or_surface_wants_focus(w->m_uSurface.xwayland)) { + // Override Redirect + return g_pCompositor->m_pLastWindow; // we kinda trick everything here. + // TODO: this is wrong, we should focus the parent, but idk how to get it considering it's nullptr in most cases. + } + + return w.get(); } - return w.get(); + if (!w->m_bIsX11) { + if (w->hasPopupAt(pos)) + return w.get(); + } } + } - if (!w->m_bIsX11) { + const int64_t WORKSPACEID = special ? PMONITOR->specialWorkspaceID : PMONITOR->activeWorkspace; + const auto PWORKSPACE = getWorkspaceByID(WORKSPACEID); + + if (PWORKSPACE->m_bHasFullscreenWindow) + return getFullscreenWindowOnWorkspace(PWORKSPACE->m_iID); + + // for windows, we need to check their extensions too, first. + for (auto& w : m_vWindows) { + if (special != isWorkspaceSpecial(w->m_iWorkspaceID)) + continue; + + if (!w->m_bIsX11 && !w->m_bIsFloating && w->m_bIsMapped && w->m_iWorkspaceID == WORKSPACEID && !w->isHidden() && !w->m_bX11ShouldntFocus && !w->m_bNoFocus && + w.get() != pIgnoreWindow) { if (w->hasPopupAt(pos)) return w.get(); } } - } - // for windows, we need to check their extensions too, first. - for (auto& w : m_vWindows) { - if (!w->m_bIsX11 && !w->m_bIsFloating && w->m_bIsMapped && w->m_iWorkspaceID == PMONITOR->activeWorkspace && !w->isHidden() && !w->m_bX11ShouldntFocus && !w->m_bNoFocus) { - if ((w)->hasPopupAt(pos)) + for (auto& w : m_vWindows) { + if (special != isWorkspaceSpecial(w->m_iWorkspaceID)) + continue; + + CBox box = {w->m_vPosition, w->m_vSize}; + if (!w->m_bIsFloating && w->m_bIsMapped && box.containsPoint(pos) && w->m_iWorkspaceID == WORKSPACEID && !w->isHidden() && !w->m_bX11ShouldntFocus && !w->m_bNoFocus && + w.get() != pIgnoreWindow) return w.get(); } - } - for (auto& w : m_vWindows) { - wlr_box box = {w->m_vPosition.x, w->m_vPosition.y, w->m_vSize.x, w->m_vSize.y}; - if (!w->m_bIsFloating && w->m_bIsMapped && wlr_box_contains_point(&box, pos.x, pos.y) && w->m_iWorkspaceID == PMONITOR->activeWorkspace && !w->isHidden() && - !w->m_bX11ShouldntFocus && !w->m_bNoFocus) - return w.get(); + + return nullptr; + }; + + // special workspace + if (PMONITOR->specialWorkspaceID && !*PSPECIALFALLTHRU) + return windowForWorkspace(true); + + if (PMONITOR->specialWorkspaceID) { + const auto PWINDOW = windowForWorkspace(true); + + if (PWINDOW) + return PWINDOW; } - return nullptr; + return windowForWorkspace(false); } CWindow* CCompositor::windowFromCursor() { @@ -744,37 +754,36 @@ CWindow* CCompositor::windowFromCursor() { if (PMONITOR->specialWorkspaceID) { for (auto& w : m_vWindows | std::views::reverse) { - wlr_box box = {w->m_vRealPosition.vec().x, w->m_vRealPosition.vec().y, w->m_vRealSize.vec().x, w->m_vRealSize.vec().y}; - if (w->m_bIsFloating && w->m_iWorkspaceID == PMONITOR->specialWorkspaceID && w->m_bIsMapped && wlr_box_contains_point(&box, m_sWLRCursor->x, m_sWLRCursor->y) && + CBox box = {w->m_vRealPosition.vec().x, w->m_vRealPosition.vec().y, w->m_vRealSize.vec().x, w->m_vRealSize.vec().y}; + if (w->m_bIsFloating && w->m_iWorkspaceID == PMONITOR->specialWorkspaceID && w->m_bIsMapped && box.containsPoint({m_sWLRCursor->x, m_sWLRCursor->y}) && !w->isHidden() && !w->m_bNoFocus) return w.get(); } for (auto& w : m_vWindows) { - wlr_box box = {w->m_vPosition.x, w->m_vPosition.y, w->m_vSize.x, w->m_vSize.y}; - if (w->m_iWorkspaceID == PMONITOR->specialWorkspaceID && wlr_box_contains_point(&box, m_sWLRCursor->x, m_sWLRCursor->y) && w->m_bIsMapped && !w->m_bNoFocus) + CBox box = {w->m_vPosition.x, w->m_vPosition.y, w->m_vSize.x, w->m_vSize.y}; + if (w->m_iWorkspaceID == PMONITOR->specialWorkspaceID && box.containsPoint({m_sWLRCursor->x, m_sWLRCursor->y}) && w->m_bIsMapped && !w->m_bNoFocus) return w.get(); } } // pinned for (auto& w : m_vWindows | std::views::reverse) { - wlr_box box = {w->m_vRealPosition.vec().x, w->m_vRealPosition.vec().y, w->m_vRealSize.vec().x, w->m_vRealSize.vec().y}; - if (wlr_box_contains_point(&box, m_sWLRCursor->x, m_sWLRCursor->y) && w->m_bIsMapped && w->m_bIsFloating && w->m_bPinned && !w->m_bNoFocus) + CBox box = {w->m_vRealPosition.vec().x, w->m_vRealPosition.vec().y, w->m_vRealSize.vec().x, w->m_vRealSize.vec().y}; + if (box.containsPoint({m_sWLRCursor->x, m_sWLRCursor->y}) && w->m_bIsMapped && w->m_bIsFloating && w->m_bPinned && !w->m_bNoFocus) return w.get(); } // first loop over floating cuz they're above, m_lWindows should be sorted bottom->top, for tiled it doesn't matter. for (auto& w : m_vWindows | std::views::reverse) { - wlr_box box = {w->m_vRealPosition.vec().x, w->m_vRealPosition.vec().y, w->m_vRealSize.vec().x, w->m_vRealSize.vec().y}; - if (wlr_box_contains_point(&box, m_sWLRCursor->x, m_sWLRCursor->y) && w->m_bIsMapped && w->m_bIsFloating && isWorkspaceVisible(w->m_iWorkspaceID) && !w->m_bPinned && - !w->m_bNoFocus) + CBox box = {w->m_vRealPosition.vec().x, w->m_vRealPosition.vec().y, w->m_vRealSize.vec().x, w->m_vRealSize.vec().y}; + if (box.containsPoint({m_sWLRCursor->x, m_sWLRCursor->y}) && w->m_bIsMapped && w->m_bIsFloating && isWorkspaceVisible(w->m_iWorkspaceID) && !w->m_bPinned && !w->m_bNoFocus) return w.get(); } for (auto& w : m_vWindows) { - wlr_box box = {w->m_vPosition.x, w->m_vPosition.y, w->m_vSize.x, w->m_vSize.y}; - if (wlr_box_contains_point(&box, m_sWLRCursor->x, m_sWLRCursor->y) && w->m_bIsMapped && w->m_iWorkspaceID == PMONITOR->activeWorkspace && !w->m_bNoFocus) + CBox box = {w->m_vPosition.x, w->m_vPosition.y, w->m_vSize.x, w->m_vSize.y}; + if (box.containsPoint({m_sWLRCursor->x, m_sWLRCursor->y}) && w->m_bIsMapped && w->m_iWorkspaceID == PMONITOR->activeWorkspace && !w->m_bNoFocus) return w.get(); } @@ -783,14 +792,14 @@ CWindow* CCompositor::windowFromCursor() { CWindow* CCompositor::windowFloatingFromCursor() { for (auto& w : m_vWindows | std::views::reverse) { - wlr_box box = {w->m_vRealPosition.vec().x, w->m_vRealPosition.vec().y, w->m_vRealSize.vec().x, w->m_vRealSize.vec().y}; - if (wlr_box_contains_point(&box, m_sWLRCursor->x, m_sWLRCursor->y) && w->m_bIsMapped && w->m_bIsFloating && !w->isHidden() && w->m_bPinned && !w->m_bNoFocus) + CBox box = {w->m_vRealPosition.vec().x, w->m_vRealPosition.vec().y, w->m_vRealSize.vec().x, w->m_vRealSize.vec().y}; + if (box.containsPoint({m_sWLRCursor->x, m_sWLRCursor->y}) && w->m_bIsMapped && w->m_bIsFloating && !w->isHidden() && w->m_bPinned && !w->m_bNoFocus) return w.get(); } for (auto& w : m_vWindows | std::views::reverse) { - wlr_box box = {w->m_vRealPosition.vec().x, w->m_vRealPosition.vec().y, w->m_vRealSize.vec().x, w->m_vRealSize.vec().y}; - if (wlr_box_contains_point(&box, m_sWLRCursor->x, m_sWLRCursor->y) && w->m_bIsMapped && w->m_bIsFloating && isWorkspaceVisible(w->m_iWorkspaceID) && !w->isHidden() && + CBox box = {w->m_vRealPosition.vec().x, w->m_vRealPosition.vec().y, w->m_vRealSize.vec().x, w->m_vRealSize.vec().y}; + if (box.containsPoint({m_sWLRCursor->x, m_sWLRCursor->y}) && w->m_bIsMapped && w->m_bIsFloating && isWorkspaceVisible(w->m_iWorkspaceID) && !w->isHidden() && !w->m_bPinned && !w->m_bNoFocus) return w.get(); } @@ -810,8 +819,9 @@ wlr_surface* CCompositor::vectorWindowToSurface(const Vector2D& pos, CWindow* pW double subx, suby; // calc for oversized windows... fucking bullshit, again. - wlr_box geom; - wlr_xdg_surface_get_geometry(pWindow->m_uSurface.xdg, &geom); + CBox geom; + wlr_xdg_surface_get_geometry(pWindow->m_uSurface.xdg, geom.pWlr()); + geom.applyFromWlr(); const auto PFOUND = wlr_xdg_surface_surface_at(PSURFACE, pos.x - pWindow->m_vRealPosition.vec().x + geom.x, pos.y - pWindow->m_vRealPosition.vec().y + geom.y, &subx, &suby); @@ -852,10 +862,14 @@ Vector2D CCompositor::vectorToSurfaceLocal(const Vector2D& vec, CWindow* pWindow }, &iterData); + CBox geom = {}; + wlr_xdg_surface_get_geometry(PSURFACE, geom.pWlr()); + geom.applyFromWlr(); + if (std::get<1>(iterData) == -1337 && std::get<2>(iterData) == -1337) return vec - pWindow->m_vRealPosition.goalv(); - return vec - pWindow->m_vRealPosition.goalv() - Vector2D{std::get<1>(iterData), std::get<2>(iterData)}; + return vec - pWindow->m_vRealPosition.goalv() - Vector2D{std::get<1>(iterData), std::get<2>(iterData)} + Vector2D{geom.x, geom.y}; } CMonitor* CCompositor::getMonitorFromOutput(wlr_output* out) { @@ -870,6 +884,9 @@ CMonitor* CCompositor::getMonitorFromOutput(wlr_output* out) { void CCompositor::focusWindow(CWindow* pWindow, wlr_surface* pSurface) { + static auto* const PFOLLOWMOUSE = &g_pConfigManager->getConfigValuePtr("input:follow_mouse")->intValue; + static auto* const PSPECIALFALLTHROUGH = &g_pConfigManager->getConfigValuePtr("input:special_fallthrough")->intValue; + if (g_pCompositor->m_sSeat.exclusiveClient) { Debug::log(LOG, "Disallowing setting focus to a window due to there being an active input inhibitor layer."); return; @@ -878,6 +895,10 @@ void CCompositor::focusWindow(CWindow* pWindow, wlr_surface* pSurface) { g_pLayoutManager->getCurrentLayout()->bringWindowToTop(pWindow); if (!pWindow || !windowValidMapped(pWindow)) { + + if (!m_pLastWindow && !pWindow) + return; + const auto PLASTWINDOW = m_pLastWindow; m_pLastWindow = nullptr; @@ -916,12 +937,13 @@ void CCompositor::focusWindow(CWindow* pWindow, wlr_surface* pSurface) { if (pWindow->m_bPinned) pWindow->m_iWorkspaceID = m_pLastMonitor->activeWorkspace; + const auto PMONITOR = getMonitorFromID(pWindow->m_iMonitorID); + if (!isWorkspaceVisible(pWindow->m_iWorkspaceID)) { + const auto PWORKSPACE = getWorkspaceByID(pWindow->m_iWorkspaceID); // This is to fix incorrect feedback on the focus history. - const auto PWORKSPACE = getWorkspaceByID(pWindow->m_iWorkspaceID); PWORKSPACE->m_pLastFocusedWindow = pWindow; PWORKSPACE->rememberPrevWorkspace(getWorkspaceByID(m_pLastMonitor->activeWorkspace)); - const auto PMONITOR = getMonitorFromID(PWORKSPACE->m_iMonitorID); PMONITOR->changeWorkspace(PWORKSPACE, false, true); // changeworkspace already calls focusWindow return; @@ -930,8 +952,15 @@ void CCompositor::focusWindow(CWindow* pWindow, wlr_surface* pSurface) { const auto PLASTWINDOW = m_pLastWindow; m_pLastWindow = pWindow; + /* If special fallthrough is enabled, this behavior will be disabled, as I have no better idea of nicely tracking which + window focuses are "via keybinds" and which ones aren't. */ + if (PMONITOR->specialWorkspaceID && PMONITOR->specialWorkspaceID != pWindow->m_iWorkspaceID && !*PSPECIALFALLTHROUGH) + PMONITOR->setSpecialWorkspace(nullptr); + // we need to make the PLASTWINDOW not equal to m_pLastWindow so that RENDERDATA is correct for an unfocused window if (windowValidMapped(PLASTWINDOW)) { + PLASTWINDOW->updateDynamicRules(); + updateWindowAnimatedDecorationValues(PLASTWINDOW); if (!pWindow->m_bIsX11 || pWindow->m_iX11Type == 1) @@ -949,6 +978,8 @@ void CCompositor::focusWindow(CWindow* pWindow, wlr_surface* pSurface) { g_pXWaylandManager->activateWindow(pWindow, true); // sets the m_pLastWindow + pWindow->updateDynamicRules(); + updateWindowAnimatedDecorationValues(pWindow); if (pWindow->m_bIsUrgent) @@ -989,6 +1020,9 @@ void CCompositor::focusWindow(CWindow* pWindow, wlr_surface* pSurface) { } else { std::rotate(m_vWindowFocusHistory.begin(), HISTORYPIVOT, HISTORYPIVOT + 1); } + + if (*PFOLLOWMOUSE == 0) + g_pInputManager->sendMotionEventsToFocused(); } void CCompositor::focusSurface(wlr_surface* pSurface, CWindow* pWindowOwner) { @@ -996,10 +1030,8 @@ void CCompositor::focusSurface(wlr_surface* pSurface, CWindow* pWindowOwner) { if (m_sSeat.seat->keyboard_state.focused_surface == pSurface || (pWindowOwner && m_sSeat.seat->keyboard_state.focused_surface == pWindowOwner->m_pWLSurface.wlr())) return; // Don't focus when already focused on this. - if (g_pSessionLockManager->isSessionLocked()) { - wlr_seat_keyboard_clear_focus(m_sSeat.seat); - m_pLastFocus = nullptr; - } + if (g_pSessionLockManager->isSessionLocked() && !g_pSessionLockManager->isSurfaceSessionLock(pSurface)) + return; // Unfocus last surface if should if (m_pLastFocus && !pWindowOwner) @@ -1014,20 +1046,17 @@ void CCompositor::focusSurface(wlr_surface* pSurface, CWindow* pWindowOwner) { return; } - const auto KEYBOARD = wlr_seat_get_keyboard(m_sSeat.seat); + if (const auto KEYBOARD = wlr_seat_get_keyboard(m_sSeat.seat); KEYBOARD) { + uint32_t keycodes[WLR_KEYBOARD_KEYS_CAP] = {0}; // TODO: maybe send valid, non-keybind codes? + wlr_seat_keyboard_notify_enter(m_sSeat.seat, pSurface, keycodes, 0, &KEYBOARD->modifiers); - if (!KEYBOARD) - return; - - uint32_t keycodes[WLR_KEYBOARD_KEYS_CAP] = {0}; // TODO: maybe send valid, non-keybind codes? - wlr_seat_keyboard_notify_enter(m_sSeat.seat, pSurface, keycodes, 0, &KEYBOARD->modifiers); - - wlr_seat_keyboard_focus_change_event event = { - .seat = m_sSeat.seat, - .old_surface = m_pLastFocus, - .new_surface = pSurface, - }; - wl_signal_emit_mutable(&m_sSeat.seat->keyboard_state.events.focus_change, &event); + wlr_seat_keyboard_focus_change_event event = { + .seat = m_sSeat.seat, + .old_surface = m_pLastFocus, + .new_surface = pSurface, + }; + wl_signal_emit_mutable(&m_sSeat.seat->keyboard_state.events.focus_change, &event); + } if (pWindowOwner) Debug::log(LOG, "Set keyboard focus to surface {:x}, with {}", (uintptr_t)pSurface, pWindowOwner); @@ -1047,9 +1076,6 @@ bool CCompositor::windowValidMapped(CWindow* pWindow) { if (!windowExists(pWindow)) return false; - if (pWindow->m_bIsX11 && !pWindow->m_bMappedX11) - return false; - if (!pWindow->m_bIsMapped) return false; @@ -1076,14 +1102,6 @@ wlr_surface* CCompositor::vectorToLayerSurface(const Vector2D& pos, std::vector< auto SURFACEAT = wlr_layer_surface_v1_surface_at(ls->layerSurface, pos.x - ls->geometry.x, pos.y - ls->geometry.y, &sCoords->x, &sCoords->y); - if (ls->layerSurface->current.keyboard_interactive && ls->layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP) { - if (!SURFACEAT) - SURFACEAT = ls->layerSurface->surface; - - *ppLayerSurfaceFound = ls.get(); - return SURFACEAT; - } - if (SURFACEAT) { if (!pixman_region32_not_empty(&SURFACEAT->input_region)) continue; @@ -1096,9 +1114,24 @@ wlr_surface* CCompositor::vectorToLayerSurface(const Vector2D& pos, std::vector< return nullptr; } +SIMEPopup* CCompositor::vectorToIMEPopup(const Vector2D& pos, std::list& popups) { + for (auto& popup : popups) { + auto surface = popup.pSurface->surface; + CBox box{ + popup.realX, + popup.realY, + surface->current.width, + surface->current.height, + }; + if (box.containsPoint(pos)) + return &popup; + } + return nullptr; +} + CWindow* CCompositor::getWindowFromSurface(wlr_surface* pSurface) { for (auto& w : m_vWindows) { - if (!w->m_bIsMapped || w->m_bFadingOut || !w->m_bMappedX11) + if (!w->m_bIsMapped || w->m_bFadingOut) continue; if (w->m_pWLSurface.wlr() == pSurface) @@ -1169,27 +1202,39 @@ void CCompositor::sanityCheckWorkspaces() { auto it = m_vWorkspaces.begin(); while (it != m_vWorkspaces.end()) { - if ((*it)->m_bIndestructible) + const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(it->get()); + if (WORKSPACERULE.isPersistent) { + ++it; continue; + } - const auto WINDOWSONWORKSPACE = getWindowsOnWorkspace((*it)->m_iID); + const auto& WORKSPACE = *it; + const auto WINDOWSONWORKSPACE = getWindowsOnWorkspace(WORKSPACE->m_iID); - if ((WINDOWSONWORKSPACE == 0 && !isWorkspaceVisible((*it)->m_iID))) { + if (WINDOWSONWORKSPACE == 0) { + if (!isWorkspaceVisible(WORKSPACE->m_iID)) { - if ((*it)->m_bIsSpecialWorkspace) { - if ((*it)->m_fAlpha.fl() > 0.f /* don't abruptly end the fadeout */) { - ++it; - continue; + if (WORKSPACE->m_bIsSpecialWorkspace) { + if (WORKSPACE->m_fAlpha.fl() > 0.f /* don't abruptly end the fadeout */) { + ++it; + continue; + } + + const auto PMONITOR = getMonitorFromID(WORKSPACE->m_iMonitorID); + + if (PMONITOR && PMONITOR->specialWorkspaceID == WORKSPACE->m_iID) + PMONITOR->setSpecialWorkspace(nullptr); } - const auto PMONITOR = getMonitorFromID((*it)->m_iMonitorID); - - if (PMONITOR && PMONITOR->specialWorkspaceID == (*it)->m_iID) - PMONITOR->setSpecialWorkspace(nullptr); + it = m_vWorkspaces.erase(it); + continue; } + if (!WORKSPACE->m_bOnCreatedEmptyExecuted) { + if (auto cmd = WORKSPACERULE.onCreatedEmptyRunCmd) + g_pKeybindManager->spawn(*cmd); - it = m_vWorkspaces.erase(it); - continue; + WORKSPACE->m_bOnCreatedEmptyExecuted = true; + } } ++it; @@ -1329,7 +1374,7 @@ void CCompositor::changeWindowZOrder(CWindow* pWindow, bool top) { toMove.emplace_front(pw); for (auto& w : m_vWindows) { - if (w->m_bIsMapped && w->m_bMappedX11 && !w->isHidden() && w->m_bIsX11 && w->X11TransientFor() == pw) { + if (w->m_bIsMapped && !w->isHidden() && w->m_bIsX11 && w->X11TransientFor() == pw) { x11Stack(w.get(), top, x11Stack); } } @@ -1355,14 +1400,11 @@ void CCompositor::cleanupFadingOut(const int& monid) { if (valid && !w->m_bReadyToDelete) continue; - std::erase_if(g_pHyprOpenGL->m_mWindowFramebuffers, [&](const auto& other) { return other.first == w; }); w->m_bFadingOut = false; removeWindowFromVectorSafe(w); std::erase(m_vWindowsFadingOut, w); Debug::log(LOG, "Cleanup: destroyed a window"); - - glFlush(); // to free mem NOW. return; } } @@ -1404,9 +1446,6 @@ void CCompositor::cleanupFadingOut(const int& monid) { g_pHyprOpenGL->markBlurDirtyForMonitor(getMonitorFromID(monid)); if (ls->fadingOut && ls->readyToDelete && !ls->alpha.isBeingAnimated()) { - g_pHyprOpenGL->m_mLayerFramebuffers[ls].release(); - g_pHyprOpenGL->m_mLayerFramebuffers.erase(ls); - for (auto& m : m_vMonitors) { for (auto& lsl : m->m_aLayerSurfaceLayers) { if (!lsl.empty() && std::find_if(lsl.begin(), lsl.end(), [&](std::unique_ptr& other) { return other.get() == ls; }) != lsl.end()) { @@ -1445,82 +1484,144 @@ void CCompositor::addToFadingOutSafe(CWindow* pWindow) { CWindow* CCompositor::getWindowInDirection(CWindow* pWindow, char dir) { + if (!isDirection(dir)) + return nullptr; + // 0 -> history, 1 -> shared length static auto* const PMETHOD = &g_pConfigManager->getConfigValuePtr("binds:focus_preferred_method")->intValue; - const auto WINDOWIDEALBB = pWindow->getWindowIdealBoundingBoxIgnoreReserved(); + const auto PMONITOR = g_pCompositor->getMonitorFromID(pWindow->m_iMonitorID); - const auto POSA = Vector2D(WINDOWIDEALBB.x, WINDOWIDEALBB.y); - const auto SIZEA = Vector2D(WINDOWIDEALBB.width, WINDOWIDEALBB.height); + if (!PMONITOR) + return nullptr; // ?? - auto leaderValue = -1; - CWindow* leaderWindow = nullptr; + const auto WINDOWIDEALBB = pWindow->m_bIsFullscreen ? wlr_box{(int)PMONITOR->vecPosition.x, (int)PMONITOR->vecPosition.y, (int)PMONITOR->vecSize.x, (int)PMONITOR->vecSize.y} : + pWindow->getWindowIdealBoundingBoxIgnoreReserved(); - for (auto& w : m_vWindows) { - if (w.get() == pWindow || !w->m_bIsMapped || w->isHidden() || w->m_bIsFloating || !isWorkspaceVisible(w->m_iWorkspaceID)) - continue; + const auto POSA = Vector2D(WINDOWIDEALBB.x, WINDOWIDEALBB.y); + const auto SIZEA = Vector2D(WINDOWIDEALBB.width, WINDOWIDEALBB.height); - const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(w->m_iWorkspaceID); - if (PWORKSPACE->m_bHasFullscreenWindow && !w->m_bIsFullscreen && !w->m_bCreatedOverFullscreen) - continue; + const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(pWindow->m_iWorkspaceID); + auto leaderValue = -1; + CWindow* leaderWindow = nullptr; - const auto BWINDOWIDEALBB = w->getWindowIdealBoundingBoxIgnoreReserved(); + if (!pWindow->m_bIsFloating) { - const auto POSB = Vector2D(BWINDOWIDEALBB.x, BWINDOWIDEALBB.y); - const auto SIZEB = Vector2D(BWINDOWIDEALBB.width, BWINDOWIDEALBB.height); + // for tiled windows, we calc edges + for (auto& w : m_vWindows) { + if (w.get() == pWindow || !w->m_bIsMapped || w->isHidden() || w->m_bIsFloating || !isWorkspaceVisible(w->m_iWorkspaceID)) + continue; - double intersectLength = -1; + if (pWindow->m_iMonitorID == w->m_iMonitorID && pWindow->m_iWorkspaceID != w->m_iWorkspaceID) + continue; - switch (dir) { - case 'l': - if (STICKS(POSA.x, POSB.x + SIZEB.x)) { - intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y)); - } - break; - case 'r': - if (STICKS(POSA.x + SIZEA.x, POSB.x)) { - intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y)); - } - break; - case 't': - case 'u': - if (STICKS(POSA.y, POSB.y + SIZEB.y)) { - intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x)); - } - break; - case 'b': - case 'd': - if (STICKS(POSA.y + SIZEA.y, POSB.y)) { - intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x)); - } - break; - } + if (PWORKSPACE->m_bHasFullscreenWindow && !w->m_bIsFullscreen && !w->m_bCreatedOverFullscreen) + continue; - if (*PMETHOD == 0 /* history */) { - if (intersectLength > 0) { + const auto BWINDOWIDEALBB = w->getWindowIdealBoundingBoxIgnoreReserved(); - // get idx - int windowIDX = -1; - for (size_t i = 0; i < g_pCompositor->m_vWindowFocusHistory.size(); ++i) { - if (g_pCompositor->m_vWindowFocusHistory[i] == w.get()) { - windowIDX = i; - break; + const auto POSB = Vector2D(BWINDOWIDEALBB.x, BWINDOWIDEALBB.y); + const auto SIZEB = Vector2D(BWINDOWIDEALBB.width, BWINDOWIDEALBB.height); + + double intersectLength = -1; + + switch (dir) { + case 'l': + if (STICKS(POSA.x, POSB.x + SIZEB.x)) { + intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y)); + } + break; + case 'r': + if (STICKS(POSA.x + SIZEA.x, POSB.x)) { + intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y)); + } + break; + case 't': + case 'u': + if (STICKS(POSA.y, POSB.y + SIZEB.y)) { + intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x)); + } + break; + case 'b': + case 'd': + if (STICKS(POSA.y + SIZEA.y, POSB.y)) { + intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x)); + } + break; + } + + if (*PMETHOD == 0 /* history */) { + if (intersectLength > 0) { + + // get idx + int windowIDX = -1; + for (size_t i = 0; i < g_pCompositor->m_vWindowFocusHistory.size(); ++i) { + if (g_pCompositor->m_vWindowFocusHistory[i] == w.get()) { + windowIDX = i; + break; + } + } + + windowIDX = g_pCompositor->m_vWindowFocusHistory.size() - windowIDX; + + if (windowIDX > leaderValue) { + leaderValue = windowIDX; + leaderWindow = w.get(); } } - - windowIDX = g_pCompositor->m_vWindowFocusHistory.size() - windowIDX; - - if (windowIDX > leaderValue) { - leaderValue = windowIDX; + } else /* length */ { + if (intersectLength > leaderValue) { + leaderValue = intersectLength; leaderWindow = w.get(); } } - } else /* length */ { - if (intersectLength > leaderValue) { - leaderValue = intersectLength; + } + } else { + // for floating windows, we calculate best distance and angle. + // if there is a window with angle better than THRESHOLD, only distance counts + + if (dir == 'u') + dir = 't'; + if (dir == 'd') + dir = 'b'; + + static const std::unordered_map VECTORS = {{'r', {1, 0}}, {'t', {0, -1}}, {'b', {0, 1}}, {'l', {-1, 0}}}; + + // + auto vectorAngles = [](Vector2D a, Vector2D b) -> double { + double dot = a.x * b.x + a.y * b.y; + double ang = std::acos(dot / (a.size() * b.size())); + return ang; + }; + + float bestAngleAbs = 2.0 * M_PI; + constexpr float THRESHOLD = 0.3 * M_PI; + + for (auto& w : m_vWindows) { + if (w.get() == pWindow || !w->m_bIsMapped || w->isHidden() || !w->m_bIsFloating || !isWorkspaceVisible(w->m_iWorkspaceID)) + continue; + + if (pWindow->m_iMonitorID == w->m_iMonitorID && pWindow->m_iWorkspaceID != w->m_iWorkspaceID) + continue; + + if (PWORKSPACE->m_bHasFullscreenWindow && !w->m_bIsFullscreen && !w->m_bCreatedOverFullscreen) + continue; + + const auto DIST = w->middle().distance(pWindow->middle()); + const auto ANGLE = vectorAngles(Vector2D{w->middle() - pWindow->middle()}, VECTORS.at(dir)); + + if (ANGLE > M_PI_2) + continue; // if the angle is over 90 degrees, ignore. Wrong direction entirely. + + if ((bestAngleAbs < THRESHOLD && DIST < leaderValue && ANGLE < THRESHOLD) || (ANGLE < bestAngleAbs && bestAngleAbs > THRESHOLD) || leaderValue == -1) { + leaderValue = DIST; + bestAngleAbs = ANGLE; leaderWindow = w.get(); } } + + if (!leaderWindow && PWORKSPACE->m_bHasFullscreenWindow) + leaderWindow = g_pCompositor->getFullscreenWindowOnWorkspace(PWORKSPACE->m_iID); } if (leaderValue != -1) @@ -1529,7 +1630,7 @@ CWindow* CCompositor::getWindowInDirection(CWindow* pWindow, char dir) { return nullptr; } -CWindow* CCompositor::getNextWindowOnWorkspace(CWindow* pWindow, bool focusableOnly) { +CWindow* CCompositor::getNextWindowOnWorkspace(CWindow* pWindow, bool focusableOnly, std::optional floating) { bool gotToWindow = false; for (auto& w : m_vWindows) { if (w.get() != pWindow && !gotToWindow) @@ -1540,11 +1641,17 @@ CWindow* CCompositor::getNextWindowOnWorkspace(CWindow* pWindow, bool focusableO continue; } + if (floating.has_value() && w->m_bIsFloating != floating.value()) + continue; + if (w->m_iWorkspaceID == pWindow->m_iWorkspaceID && w->m_bIsMapped && !w->isHidden() && (!focusableOnly || !w->m_bNoFocus)) return w.get(); } for (auto& w : m_vWindows) { + if (floating.has_value() && w->m_bIsFloating != floating.value()) + continue; + if (w.get() != pWindow && w->m_iWorkspaceID == pWindow->m_iWorkspaceID && w->m_bIsMapped && !w->isHidden() && (!focusableOnly || !w->m_bNoFocus)) return w.get(); } @@ -1552,7 +1659,7 @@ CWindow* CCompositor::getNextWindowOnWorkspace(CWindow* pWindow, bool focusableO return nullptr; } -CWindow* CCompositor::getPrevWindowOnWorkspace(CWindow* pWindow, bool focusableOnly) { +CWindow* CCompositor::getPrevWindowOnWorkspace(CWindow* pWindow, bool focusableOnly, std::optional floating) { bool gotToWindow = false; for (auto& w : m_vWindows | std::views::reverse) { if (w.get() != pWindow && !gotToWindow) @@ -1563,11 +1670,17 @@ CWindow* CCompositor::getPrevWindowOnWorkspace(CWindow* pWindow, bool focusableO continue; } + if (floating.has_value() && w->m_bIsFloating != floating.value()) + continue; + if (w->m_iWorkspaceID == pWindow->m_iWorkspaceID && w->m_bIsMapped && !w->isHidden() && (!focusableOnly || !w->m_bNoFocus)) return w.get(); } for (auto& w : m_vWindows | std::views::reverse) { + if (floating.has_value() && w->m_bIsFloating != floating.value()) + continue; + if (w.get() != pWindow && w->m_iWorkspaceID == pWindow->m_iWorkspaceID && w->m_bIsMapped && !w->isHidden() && (!focusableOnly || !w->m_bNoFocus)) return w.get(); } @@ -1595,7 +1708,7 @@ CWorkspace* CCompositor::getWorkspaceByName(const std::string& name) { } CWorkspace* CCompositor::getWorkspaceByString(const std::string& str) { - if (str.find("name:") == 0) { + if (str.starts_with("name:")) { return getWorkspaceByName(str.substr(str.find_first_of(':') + 1)); } @@ -1616,6 +1729,15 @@ bool CCompositor::isPointOnAnyMonitor(const Vector2D& point) { return false; } +bool CCompositor::isPointOnReservedArea(const Vector2D& point, const CMonitor* pMonitor) { + const auto PMONITOR = pMonitor ? pMonitor : getMonitorFromVector(point); + + const auto XY1 = PMONITOR->vecPosition + PMONITOR->vecReservedTopLeft; + const auto XY2 = PMONITOR->vecPosition + PMONITOR->vecSize - PMONITOR->vecReservedBottomRight; + + return !VECINRECT(point, XY1.x, XY1.y, XY2.x, XY2.y); +} + void checkFocusSurfaceIter(wlr_surface* pSurface, int x, int y, void* data) { auto pair = (std::pair*)data; pair->second = pair->second || pSurface == pair->first; @@ -1628,7 +1750,7 @@ CWindow* CCompositor::getConstraintWindow(SMouse* pMouse) { const auto PSURFACE = pMouse->currentConstraint->surface; for (auto& w : m_vWindows) { - if (w->isHidden() || !w->m_bMappedX11 || !w->m_bIsMapped || !w->m_pWLSurface.exists()) + if (w->isHidden() || !w->m_bIsMapped || !w->m_pWLSurface.exists()) continue; if (w->m_bIsX11) { @@ -1716,16 +1838,25 @@ void CCompositor::updateAllWindowsAnimatedDecorationValues() { } } +void CCompositor::updateWorkspaceWindows(const int64_t& id) { + for (auto& w : m_vWindows) { + if (!w->m_bIsMapped || w->m_iWorkspaceID != id) + continue; + + w->updateDynamicRules(); + } +} + void CCompositor::updateWindowAnimatedDecorationValues(CWindow* pWindow) { // optimization static auto* const ACTIVECOL = (CGradientValueData*)g_pConfigManager->getConfigValuePtr("general:col.active_border")->data.get(); static auto* const INACTIVECOL = (CGradientValueData*)g_pConfigManager->getConfigValuePtr("general:col.inactive_border")->data.get(); static auto* const NOGROUPACTIVECOL = (CGradientValueData*)g_pConfigManager->getConfigValuePtr("general:col.nogroup_border_active")->data.get(); static auto* const NOGROUPINACTIVECOL = (CGradientValueData*)g_pConfigManager->getConfigValuePtr("general:col.nogroup_border")->data.get(); - static auto* const GROUPACTIVECOL = (CGradientValueData*)g_pConfigManager->getConfigValuePtr("general:col.group_border_active")->data.get(); - static auto* const GROUPINACTIVECOL = (CGradientValueData*)g_pConfigManager->getConfigValuePtr("general:col.group_border")->data.get(); - static auto* const GROUPACTIVELOCKEDCOL = (CGradientValueData*)g_pConfigManager->getConfigValuePtr("general:col.group_border_locked_active")->data.get(); - static auto* const GROUPINACTIVELOCKEDCOL = (CGradientValueData*)g_pConfigManager->getConfigValuePtr("general:col.group_border_locked")->data.get(); + static auto* const GROUPACTIVECOL = (CGradientValueData*)g_pConfigManager->getConfigValuePtr("group:col.border_active")->data.get(); + static auto* const GROUPINACTIVECOL = (CGradientValueData*)g_pConfigManager->getConfigValuePtr("group:col.border_inactive")->data.get(); + static auto* const GROUPACTIVELOCKEDCOL = (CGradientValueData*)g_pConfigManager->getConfigValuePtr("group:col.border_locked_active")->data.get(); + static auto* const GROUPINACTIVELOCKEDCOL = (CGradientValueData*)g_pConfigManager->getConfigValuePtr("group:col.border_locked_inactive")->data.get(); static auto* const PINACTIVEALPHA = &g_pConfigManager->getConfigValuePtr("decoration:inactive_opacity")->floatValue; static auto* const PACTIVEALPHA = &g_pConfigManager->getConfigValuePtr("decoration:active_opacity")->floatValue; static auto* const PFULLSCREENALPHA = &g_pConfigManager->getConfigValuePtr("decoration:fullscreen_opacity")->floatValue; @@ -1753,15 +1884,13 @@ void CCompositor::updateWindowAnimatedDecorationValues(CWindow* pWindow) { if (pWindow == m_pLastWindow) { const auto* const ACTIVECOLOR = !pWindow->m_sGroupData.pNextWindow ? (!pWindow->m_sGroupData.deny ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL); - setBorderColor(pWindow->m_sSpecialRenderData.activeBorderColor.toUnderlying() >= 0 ? - CGradientValueData(CColor(pWindow->m_sSpecialRenderData.activeBorderColor.toUnderlying())) : - *ACTIVECOLOR); + setBorderColor(pWindow->m_sSpecialRenderData.activeBorderColor.toUnderlying().m_vColors.empty() ? *ACTIVECOLOR : + pWindow->m_sSpecialRenderData.activeBorderColor.toUnderlying()); } else { const auto* const INACTIVECOLOR = !pWindow->m_sGroupData.pNextWindow ? (!pWindow->m_sGroupData.deny ? INACTIVECOL : NOGROUPINACTIVECOL) : (GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL); - setBorderColor(pWindow->m_sSpecialRenderData.inactiveBorderColor.toUnderlying() >= 0 ? - CGradientValueData(CColor(pWindow->m_sSpecialRenderData.inactiveBorderColor.toUnderlying())) : - *INACTIVECOLOR); + setBorderColor(pWindow->m_sSpecialRenderData.inactiveBorderColor.toUnderlying().m_vColors.empty() ? *INACTIVECOLOR : + pWindow->m_sSpecialRenderData.inactiveBorderColor.toUnderlying()); } } @@ -1802,8 +1931,7 @@ void CCompositor::updateWindowAnimatedDecorationValues(CWindow* pWindow) { pWindow->m_cRealShadowColor.setValueAndWarp(CColor(0, 0, 0, 0)); // no shadow } - for (auto& d : pWindow->m_dWindowDecorations) - d->updateWindow(pWindow); + pWindow->updateWindowDecos(); } int CCompositor::getNextAvailableMonitorID(std::string const& name) { @@ -1889,7 +2017,7 @@ void CCompositor::swapActiveWorkspaces(CMonitor* pMonitorA, CMonitor* pMonitorB) updateFullscreenFadeOnWorkspace(PWORKSPACEB); updateFullscreenFadeOnWorkspace(PWORKSPACEA); - g_pInputManager->simulateMouseMovement(); + g_pInputManager->sendMotionEventsToFocused(); // event g_pEventManager->postEvent(SHyprIPCEvent{"moveworkspace", PWORKSPACEA->m_szName + "," + pMonitorB->szName}); @@ -1954,14 +2082,14 @@ CMonitor* CCompositor::getMonitorFromString(const std::string& name) { Debug::log(ERR, "Error in getMonitorFromString: invalid arg 1"); return nullptr; } - } else if (name.find("desc:") == 0) { + } else if (name.starts_with("desc:")) { const auto DESCRIPTION = name.substr(5); for (auto& m : m_vMonitors) { if (!m->output) continue; - if (m->output->description && std::string(m->output->description).find(DESCRIPTION) == 0) { + if (m->szDescription.starts_with(DESCRIPTION)) { return m.get(); } } @@ -1986,7 +2114,7 @@ CMonitor* CCompositor::getMonitorFromString(const std::string& name) { return nullptr; } -void CCompositor::moveWorkspaceToMonitor(CWorkspace* pWorkspace, CMonitor* pMonitor) { +void CCompositor::moveWorkspaceToMonitor(CWorkspace* pWorkspace, CMonitor* pMonitor, bool noWarpCursor) { // We trust the workspace and monitor to be correct. @@ -2072,9 +2200,10 @@ void CCompositor::moveWorkspaceToMonitor(CWorkspace* pWorkspace, CMonitor* pMoni pWorkspace->startAnim(true, true, true); - wlr_cursor_warp(m_sWLRCursor, nullptr, pMonitor->vecPosition.x + pMonitor->vecTransformedSize.x / 2, pMonitor->vecPosition.y + pMonitor->vecTransformedSize.y / 2); + if (!noWarpCursor) + wlr_cursor_warp(m_sWLRCursor, nullptr, pMonitor->vecPosition.x + pMonitor->vecTransformedSize.x / 2, pMonitor->vecPosition.y + pMonitor->vecTransformedSize.y / 2); - g_pInputManager->simulateMouseMovement(); + g_pInputManager->sendMotionEventsToFocused(); } // finalize @@ -2136,7 +2265,7 @@ void CCompositor::updateFullscreenFadeOnWorkspace(CWorkspace* pWorkspace) { } void CCompositor::setWindowFullscreen(CWindow* pWindow, bool on, eFullscreenMode mode) { - if (!windowValidMapped(pWindow)) + if (!windowValidMapped(pWindow) || g_pCompositor->m_bUnsafeState) return; if (pWindow->m_bPinned) { @@ -2148,16 +2277,17 @@ void CCompositor::setWindowFullscreen(CWindow* pWindow, bool on, eFullscreenMode const auto PWORKSPACE = getWorkspaceByID(pWindow->m_iWorkspaceID); + const auto MODE = mode == FULLSCREEN_INVALID ? PWORKSPACE->m_efFullscreenMode : mode; + if (PWORKSPACE->m_bHasFullscreenWindow && on) { Debug::log(LOG, "Rejecting fullscreen ON on a fullscreen workspace"); return; } - g_pLayoutManager->getCurrentLayout()->fullscreenRequestForWindow(pWindow, mode, on); + g_pLayoutManager->getCurrentLayout()->fullscreenRequestForWindow(pWindow, MODE, on); - g_pXWaylandManager->setWindowFullscreen(pWindow, pWindow->m_bIsFullscreen && mode == FULLSCREEN_FULL); + g_pXWaylandManager->setWindowFullscreen(pWindow, pWindow->shouldSendFullscreenState()); - pWindow->updateDynamicRules(); updateWindowAnimatedDecorationValues(pWindow); // make all windows on the same workspace under the fullscreen window @@ -2221,15 +2351,30 @@ CWindow* CCompositor::getWindowByRegex(const std::string& regexp) { std::regex regexCheck(regexp); std::string matchCheck; - if (regexp.find("title:") == 0) { + if (regexp.starts_with("title:")) { mode = MODE_TITLE_REGEX; regexCheck = std::regex(regexp.substr(6)); - } else if (regexp.find("address:") == 0) { + } else if (regexp.starts_with("address:")) { mode = MODE_ADDRESS; matchCheck = regexp.substr(8); - } else if (regexp.find("pid:") == 0) { + } else if (regexp.starts_with("pid:")) { mode = MODE_PID; matchCheck = regexp.substr(4); + } else if (regexp.starts_with("floating") || regexp.starts_with("tiled")) { + // first floating on the current ws + if (!m_pLastWindow) + return nullptr; + + const bool FLOAT = regexp.starts_with("floating"); + + for (auto& w : m_vWindows) { + if (!w->m_bIsMapped || w->m_bIsFloating != FLOAT || w->m_iWorkspaceID != m_pLastWindow->m_iWorkspaceID || w->isHidden()) + continue; + + return w.get(); + } + + return nullptr; } for (auto& w : g_pCompositor->m_vWindows) { @@ -2396,17 +2541,6 @@ void CCompositor::forceReportSizesToWindowsOnWorkspace(const int& wid) { } } -bool CCompositor::cursorOnReservedArea() { - const auto PMONITOR = getMonitorFromCursor(); - - const auto XY1 = PMONITOR->vecPosition + PMONITOR->vecReservedTopLeft; - const auto XY2 = PMONITOR->vecPosition + PMONITOR->vecSize - PMONITOR->vecReservedBottomRight; - - const auto CURSORPOS = g_pInputManager->getMouseCoordsInternal(); - - return !VECINRECT(CURSORPOS, XY1.x, XY1.y, XY2.x, XY2.y); -} - CWorkspace* CCompositor::createNewWorkspace(const int& id, const int& monid, const std::string& name) { const auto NAME = name == "" ? std::to_string(id) : name; auto monID = monid; @@ -2423,6 +2557,8 @@ CWorkspace* CCompositor::createNewWorkspace(const int& id, const int& monid, con PWORKSPACE->m_iID = id; PWORKSPACE->m_iMonitorID = monID; + PWORKSPACE->m_fAlpha.setValueAndWarp(0); + return PWORKSPACE; } @@ -2473,7 +2609,11 @@ int CCompositor::getNewSpecialID() { } void CCompositor::performUserChecks() { - // empty + if (g_pConfigManager->getInt("general:allow_tearing") == 1 && !envEnabled("WLR_DRM_NO_ATOMIC")) { + g_pHyprNotificationOverlay->addNotification("You have enabled tearing, but immediate presentations are not available on your configuration. Try adding " + "env = WLR_DRM_NO_ATOMIC,1 to your config.", + CColor(0), 15000, ICON_WARNING); + } } void CCompositor::moveWindowToWorkspaceSafe(CWindow* pWindow, CWorkspace* pWorkspace) { @@ -2492,6 +2632,7 @@ void CCompositor::moveWindowToWorkspaceSafe(CWindow* pWindow, CWorkspace* pWorks pWindow->moveToWorkspace(pWorkspace->m_iID); pWindow->updateToplevel(); pWindow->updateDynamicRules(); + pWindow->uncacheWindowDecos(); if (!pWindow->m_bIsFloating) { g_pLayoutManager->getCurrentLayout()->onWindowRemovedTiling(pWindow); @@ -2521,6 +2662,9 @@ void CCompositor::moveWindowToWorkspaceSafe(CWindow* pWindow, CWorkspace* pWorks if (FULLSCREEN) setWindowFullscreen(pWindow, true, FULLSCREENMODE); + + g_pCompositor->updateWorkspaceWindows(pWorkspace->m_iID); + g_pCompositor->updateWorkspaceWindows(pWindow->m_iWorkspaceID); } CWindow* CCompositor::getForceFocus() { @@ -2538,12 +2682,10 @@ CWindow* CCompositor::getForceFocus() { } void CCompositor::notifyIdleActivity() { - wlr_idle_notify_activity(g_pCompositor->m_sWLRIdle, g_pCompositor->m_sSeat.seat); wlr_idle_notifier_v1_notify_activity(g_pCompositor->m_sWLRIdleNotifier, g_pCompositor->m_sSeat.seat); } void CCompositor::setIdleActivityInhibit(bool enabled) { - wlr_idle_set_enabled(g_pCompositor->m_sWLRIdle, g_pCompositor->m_sSeat.seat, enabled); wlr_idle_notifier_v1_set_inhibited(g_pCompositor->m_sWLRIdleNotifier, !enabled); } void CCompositor::arrangeMonitors() { @@ -2611,24 +2753,10 @@ void CCompositor::enterUnsafeState() { Debug::log(LOG, "Entering unsafe state"); + if (!m_pUnsafeOutput->m_bEnabled) + m_pUnsafeOutput->onConnect(false); + m_bUnsafeState = true; - - // create a backup monitor - wlr_backend* headless = nullptr; - wlr_multi_for_each_backend( - m_sWLRBackend, - [](wlr_backend* b, void* data) { - if (wlr_backend_is_headless(b)) - *((wlr_backend**)data) = b; - }, - &headless); - - if (!headless) { - Debug::log(WARN, "Entering an unsafe state without a headless backend"); - return; - } - - m_pUnsafeOutput = wlr_headless_add_output(headless, 1920, 1080); } void CCompositor::leaveUnsafeState() { @@ -2639,8 +2767,55 @@ void CCompositor::leaveUnsafeState() { m_bUnsafeState = false; - if (m_pUnsafeOutput) - wlr_output_destroy(m_pUnsafeOutput); + CMonitor* pNewMonitor = nullptr; + for (auto& pMonitor : m_vMonitors) { + if (pMonitor->output != m_pUnsafeOutput->output) { + pNewMonitor = pMonitor.get(); + break; + } + } - m_pUnsafeOutput = nullptr; + RASSERT(pNewMonitor, "Tried to leave unsafe without a monitor"); + + if (m_pUnsafeOutput->m_bEnabled) + m_pUnsafeOutput->onDisconnect(); + + for (auto& m : m_vMonitors) { + scheduleFrameForMonitor(m.get()); + } +} + +void CCompositor::setPreferredScaleForSurface(wlr_surface* pSurface, double scale) { + g_pProtocolManager->m_pFractionalScaleProtocolManager->setPreferredScaleForSurface(pSurface, scale); + wlr_surface_set_preferred_buffer_scale(pSurface, static_cast(std::ceil(scale))); + + const auto PSURFACE = CWLSurface::surfaceFromWlr(pSurface); + if (!PSURFACE) { + Debug::log(WARN, "Orphaned wlr_surface {:x} in setPreferredScaleForSurface", (uintptr_t)pSurface); + return; + } + + PSURFACE->m_fLastScale = scale; + PSURFACE->m_iLastScale = static_cast(std::ceil(scale)); +} + +void CCompositor::setPreferredTransformForSurface(wlr_surface* pSurface, wl_output_transform transform) { + wlr_surface_set_preferred_buffer_transform(pSurface, transform); + + const auto PSURFACE = CWLSurface::surfaceFromWlr(pSurface); + if (!PSURFACE) { + Debug::log(WARN, "Orphaned wlr_surface {:x} in setPreferredTransformForSurface", (uintptr_t)pSurface); + return; + } + + PSURFACE->m_eLastTransform = transform; +} + +void CCompositor::updateSuspendedStates() { + for (auto& w : g_pCompositor->m_vWindows) { + if (!w->m_bIsMapped) + continue; + + w->setSuspended(w->isHidden() || !isWorkspaceVisible(w->m_iWorkspaceID)); + } } diff --git a/src/Compositor.hpp b/src/Compositor.hpp index 06d15c3c..22b94dbb 100644 --- a/src/Compositor.hpp +++ b/src/Compositor.hpp @@ -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>*, Vector2D*, SLayerSurface**); + SIMEPopup* vectorToIMEPopup(const Vector2D& pos, std::list& 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 floating = {}); + CWindow* getPrevWindowOnWorkspace(CWindow*, bool focusableOnly = false, std::optional 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; }; diff --git a/src/SharedDefs.hpp b/src/SharedDefs.hpp index 53a9850a..da7f6938 100644 --- a/src/SharedDefs.hpp +++ b/src/SharedDefs.hpp @@ -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; + } }; \ No newline at end of file diff --git a/src/Window.cpp b/src/Window.cpp index 4ee40dff..5a50713b 100644 --- a/src/Window.cpp +++ b/src/Window.cpp @@ -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(this)); // put the shadow so it's the first deco (has to be rendered first) + addWindowDeco(std::make_unique(this)); + addWindowDeco(std::make_unique(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 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(this)); - updateWindowDecos(); + addWindowDeco(std::make_unique(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(pWindow)); + pWindow->addWindowDeco(std::make_unique(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; +} diff --git a/src/Window.hpp b/src/Window.hpp index 81b6156f..bbe7dc07 100644 --- a/src/Window.hpp +++ b/src/Window.hpp @@ -30,6 +30,8 @@ enum eGroupRules { GROUP_OVERRIDE = 1 << 6, // Override other rules }; +class IWindowTransformer; + template class CWindowOverridableVar { public: @@ -104,13 +106,13 @@ class CWindowOverridableVar { }; struct SWindowSpecialRenderData { - CWindowOverridableVar alphaOverride = false; - CWindowOverridableVar alpha = 1.f; - CWindowOverridableVar alphaInactiveOverride = false; - CWindowOverridableVar alphaInactive = -1.f; // -1 means unset + CWindowOverridableVar alphaOverride = false; + CWindowOverridableVar alpha = 1.f; + CWindowOverridableVar alphaInactiveOverride = false; + CWindowOverridableVar alphaInactive = -1.f; // -1 means unset - CWindowOverridableVar activeBorderColor = -1; // -1 means unset - CWindowOverridableVar inactiveBorderColor = -1; // -1 means unset + CWindowOverridableVar activeBorderColor = CGradientValueData(); // empty color vector means unset + CWindowOverridableVar inactiveBorderColor = CGradientValueData(); // empty color vector means unset // set by the layout CWindowOverridableVar borderSize = -1; // -1 means unset @@ -138,6 +140,8 @@ struct SWindowAdditionalConfigData { CWindowOverridableVar keepAspectRatio = false; CWindowOverridableVar xray = -1; // -1 means unset, takes precedence over the renderdata one CWindowOverridableVar borderSize = -1; // -1 means unset, takes precedence over the renderdata one + CWindowOverridableVar forceTearing = false; + CWindowOverridableVar 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> m_pPendingSizeAck; + std::vector> 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> 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 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 : std::formatter { std::format_to(out, ", class: {}", g_pXWaylandManager->getAppIDClass(w)); return std::format_to(out, "]"); } -}; \ No newline at end of file +}; diff --git a/src/config/ConfigDataValues.hpp b/src/config/ConfigDataValues.hpp index f6bf9881..14b5e51d 100644 --- a/src/config/ConfigDataValues.hpp +++ b/src/config/ConfigDataValues.hpp @@ -16,6 +16,7 @@ class ICustomConfigValueData { class CGradientValueData : public ICustomConfigValueData { public: + CGradientValueData(){}; CGradientValueData(CColor col) { m_vColors.push_back(col); }; diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 2eaee318..402d879e 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -1,7 +1,10 @@ #include "ConfigManager.hpp" #include "../managers/KeybindManager.hpp" +#include "../render/decorations/CHyprGroupBarDecoration.hpp" + #include +#include #include #include #include @@ -15,14 +18,22 @@ extern "C" char** environ; CConfigManager::CConfigManager() { - configValues["general:col.active_border"].data = std::make_shared(0xffffffff); - configValues["general:col.inactive_border"].data = std::make_shared(0xff444444); - configValues["general:col.nogroup_border"].data = std::make_shared(0xffffaaff); - configValues["general:col.nogroup_border_active"].data = std::make_shared(0xffff00ff); - configValues["general:col.group_border"].data = std::make_shared(0x66777700); - configValues["general:col.group_border_active"].data = std::make_shared(0x66ffff00); - configValues["general:col.group_border_locked"].data = std::make_shared(0x66775500); - configValues["general:col.group_border_locked_active"].data = std::make_shared(0x66ff5500); + configValues["general:col.active_border"].data = std::make_shared(0xffffffff); + configValues["general:col.inactive_border"].data = std::make_shared(0xff444444); + configValues["general:col.nogroup_border"].data = std::make_shared(0xffffaaff); + configValues["general:col.nogroup_border_active"].data = std::make_shared(0xffff00ff); + + configValues["group:col.border_active"].data = std::make_shared(0x66ffff00); + configValues["group:col.border_inactive"].data = std::make_shared(0x66777700); + configValues["group:col.border_locked_active"].data = std::make_shared(0x66ff5500); + configValues["group:col.border_locked_inactive"].data = std::make_shared(0x66775500); + + configValues["group:groupbar:col.active"].data = std::make_shared(0x66ffff00); + configValues["group:groupbar:col.inactive"].data = std::make_shared(0x66777700); + configValues["group:groupbar:col.locked_active"].data = std::make_shared(0x66ff5500); + configValues["group:groupbar:col.locked_inactive"].data = std::make_shared(0x66775500); + + Debug::log(LOG, "NOTE: further logs to stdout / logfile are disabled by default. Use debug:disable_logs and debug:enable_stdout_logs to override this."); setDefaultVars(); setDefaultAnimationVars(); @@ -37,12 +48,11 @@ CConfigManager::CConfigManager() { std::string CConfigManager::getConfigDir() { static const char* xdgConfigHome = getenv("XDG_CONFIG_HOME"); - std::string configPath; - if (!xdgConfigHome) - configPath = getenv("HOME") + std::string("/.config"); - else - configPath = xdgConfigHome; - return configPath; + + if (xdgConfigHome && std::filesystem::path(xdgConfigHome).is_absolute()) + return xdgConfigHome; + + return getenv("HOME") + std::string("/.config"); } std::string CConfigManager::getMainConfigPath() { @@ -70,16 +80,14 @@ void CConfigManager::setDefaultVars() { configValues["general:apply_sens_to_raw"].intValue = 0; configValues["general:border_size"].intValue = 1; configValues["general:no_border_on_floating"].intValue = 0; + configValues["general:border_part_of_window"].intValue = 1; configValues["general:gaps_in"].intValue = 5; configValues["general:gaps_out"].intValue = 20; + configValues["general:gaps_workspaces"].intValue = 0; ((CGradientValueData*)configValues["general:col.active_border"].data.get())->reset(0xffffffff); ((CGradientValueData*)configValues["general:col.inactive_border"].data.get())->reset(0xff444444); ((CGradientValueData*)configValues["general:col.nogroup_border"].data.get())->reset(0xff444444); ((CGradientValueData*)configValues["general:col.nogroup_border_active"].data.get())->reset(0xffff00ff); - ((CGradientValueData*)configValues["general:col.group_border"].data.get())->reset(0x66777700); - ((CGradientValueData*)configValues["general:col.group_border_active"].data.get())->reset(0x66ffff00); - ((CGradientValueData*)configValues["general:col.group_border_locked"].data.get())->reset(0x66775500); - ((CGradientValueData*)configValues["general:col.group_border_locked_active"].data.get())->reset(0x66ff5500); configValues["general:cursor_inactive_timeout"].intValue = 0; configValues["general:no_cursor_warps"].intValue = 0; configValues["general:no_focus_fallback"].intValue = 0; @@ -87,88 +95,111 @@ void CConfigManager::setDefaultVars() { configValues["general:extend_border_grab_area"].intValue = 15; configValues["general:hover_icon_on_border"].intValue = 1; configValues["general:layout"].strValue = "dwindle"; + configValues["general:allow_tearing"].intValue = 0; - configValues["misc:disable_hyprland_logo"].intValue = 0; - configValues["misc:disable_splash_rendering"].intValue = 0; - configValues["misc:disable_hypr_chan"].intValue = 0; - configValues["misc:force_hypr_chan"].intValue = 0; - configValues["misc:vfr"].intValue = 1; - configValues["misc:vrr"].intValue = 0; - configValues["misc:mouse_move_enables_dpms"].intValue = 0; - configValues["misc:key_press_enables_dpms"].intValue = 0; - configValues["misc:always_follow_on_dnd"].intValue = 1; - configValues["misc:layers_hog_keyboard_focus"].intValue = 1; - configValues["misc:animate_manual_resizes"].intValue = 0; - configValues["misc:animate_mouse_windowdragging"].intValue = 0; - configValues["misc:disable_autoreload"].intValue = 0; - configValues["misc:enable_swallow"].intValue = 0; - configValues["misc:swallow_regex"].strValue = STRVAL_EMPTY; - configValues["misc:swallow_exception_regex"].strValue = STRVAL_EMPTY; - configValues["misc:focus_on_activate"].intValue = 0; - configValues["misc:no_direct_scanout"].intValue = 1; - configValues["misc:hide_cursor_on_touch"].intValue = 1; - configValues["misc:mouse_move_focuses_monitor"].intValue = 1; - configValues["misc:render_ahead_of_time"].intValue = 0; - configValues["misc:render_ahead_safezone"].intValue = 1; - configValues["misc:cursor_zoom_factor"].floatValue = 1.f; - configValues["misc:cursor_zoom_rigid"].intValue = 0; - configValues["misc:allow_session_lock_restore"].intValue = 0; - configValues["misc:groupbar_scrolling"].intValue = 1; - configValues["misc:group_insert_after_current"].intValue = 1; - configValues["misc:group_focus_removed_window"].intValue = 1; - configValues["misc:render_titles_in_groupbar"].intValue = 1; - configValues["misc:groupbar_titles_font_size"].intValue = 8; - configValues["misc:groupbar_gradients"].intValue = 1; - configValues["misc:close_special_on_empty"].intValue = 1; - configValues["misc:groupbar_text_color"].intValue = 0xffffffff; - configValues["misc:background_color"].intValue = 0xff111111; + configValues["misc:disable_hyprland_logo"].intValue = 0; + configValues["misc:disable_splash_rendering"].intValue = 0; + configValues["misc:force_hypr_chan"].intValue = 0; + configValues["misc:force_default_wallpaper"].intValue = -1; + configValues["misc:vfr"].intValue = 1; + configValues["misc:vrr"].intValue = 0; + configValues["misc:mouse_move_enables_dpms"].intValue = 0; + configValues["misc:key_press_enables_dpms"].intValue = 0; + configValues["misc:always_follow_on_dnd"].intValue = 1; + configValues["misc:layers_hog_keyboard_focus"].intValue = 1; + configValues["misc:animate_manual_resizes"].intValue = 0; + configValues["misc:animate_mouse_windowdragging"].intValue = 0; + configValues["misc:disable_autoreload"].intValue = 0; + configValues["misc:enable_swallow"].intValue = 0; + configValues["misc:swallow_regex"].strValue = STRVAL_EMPTY; + configValues["misc:swallow_exception_regex"].strValue = STRVAL_EMPTY; + configValues["misc:focus_on_activate"].intValue = 0; + configValues["misc:no_direct_scanout"].intValue = 1; + configValues["misc:hide_cursor_on_touch"].intValue = 1; + configValues["misc:mouse_move_focuses_monitor"].intValue = 1; + configValues["misc:render_ahead_of_time"].intValue = 0; + configValues["misc:render_ahead_safezone"].intValue = 1; + configValues["misc:cursor_zoom_factor"].floatValue = 1.f; + configValues["misc:cursor_zoom_rigid"].intValue = 0; + configValues["misc:allow_session_lock_restore"].intValue = 0; + configValues["misc:close_special_on_empty"].intValue = 1; + configValues["misc:background_color"].intValue = 0xff111111; + configValues["misc:new_window_takes_over_fullscreen"].intValue = 0; - configValues["debug:int"].intValue = 0; - configValues["debug:log_damage"].intValue = 0; - configValues["debug:overlay"].intValue = 0; - configValues["debug:damage_blink"].intValue = 0; - configValues["debug:disable_logs"].intValue = 0; - configValues["debug:disable_time"].intValue = 1; - configValues["debug:enable_stdout_logs"].intValue = 0; - configValues["debug:damage_tracking"].intValue = DAMAGE_TRACKING_FULL; - configValues["debug:manual_crash"].intValue = 0; - configValues["debug:suppress_errors"].intValue = 0; + ((CGradientValueData*)configValues["group:col.border_active"].data.get())->reset(0x66ffff00); + ((CGradientValueData*)configValues["group:col.border_inactive"].data.get())->reset(0x66777700); + ((CGradientValueData*)configValues["group:col.border_locked_active"].data.get())->reset(0x66ff5500); + ((CGradientValueData*)configValues["group:col.border_locked_inactive"].data.get())->reset(0x66775500); - configValues["decoration:rounding"].intValue = 0; - configValues["decoration:blur:enabled"].intValue = 1; - configValues["decoration:blur:size"].intValue = 8; - configValues["decoration:blur:passes"].intValue = 1; - configValues["decoration:blur:ignore_opacity"].intValue = 0; - configValues["decoration:blur:new_optimizations"].intValue = 1; - configValues["decoration:blur:xray"].intValue = 0; - configValues["decoration:blur:noise"].floatValue = 0.0117; - configValues["decoration:blur:contrast"].floatValue = 0.8916; - configValues["decoration:blur:brightness"].floatValue = 0.8172; - configValues["decoration:blur:special"].intValue = 0; - configValues["decoration:active_opacity"].floatValue = 1; - configValues["decoration:inactive_opacity"].floatValue = 1; - configValues["decoration:fullscreen_opacity"].floatValue = 1; - configValues["decoration:multisample_edges"].intValue = 1; - configValues["decoration:no_blur_on_oversized"].intValue = 0; - configValues["decoration:drop_shadow"].intValue = 1; - configValues["decoration:shadow_range"].intValue = 4; - configValues["decoration:shadow_render_power"].intValue = 3; - configValues["decoration:shadow_ignore_window"].intValue = 1; - configValues["decoration:shadow_offset"].vecValue = Vector2D(); - configValues["decoration:shadow_scale"].floatValue = 1.f; - configValues["decoration:col.shadow"].intValue = 0xee1a1a1a; - configValues["decoration:col.shadow_inactive"].intValue = INT_MAX; - configValues["decoration:dim_inactive"].intValue = 0; - configValues["decoration:dim_strength"].floatValue = 0.5f; - configValues["decoration:dim_special"].floatValue = 0.2f; - configValues["decoration:dim_around"].floatValue = 0.4f; - configValues["decoration:screen_shader"].strValue = STRVAL_EMPTY; + configValues["group:insert_after_current"].intValue = 1; + configValues["group:focus_removed_window"].intValue = 1; + + configValues["group:groupbar:enabled"].intValue = 1; + configValues["group:groupbar:font_family"].strValue = "Sans"; + configValues["group:groupbar:font_size"].intValue = 8; + configValues["group:groupbar:gradients"].intValue = 1; + configValues["group:groupbar:height"].intValue = 14; + configValues["group:groupbar:priority"].intValue = 3; + configValues["group:groupbar:render_titles"].intValue = 1; + configValues["group:groupbar:scrolling"].intValue = 1; + configValues["group:groupbar:text_color"].intValue = 0xffffffff; + + ((CGradientValueData*)configValues["group:groupbar:col.active"].data.get())->reset(0x66ffff00); + ((CGradientValueData*)configValues["group:groupbar:col.inactive"].data.get())->reset(0x66777700); + ((CGradientValueData*)configValues["group:groupbar:col.locked_active"].data.get())->reset(0x66ff5500); + ((CGradientValueData*)configValues["group:groupbar:col.locked_inactive"].data.get())->reset(0x66775500); + + configValues["debug:int"].intValue = 0; + configValues["debug:log_damage"].intValue = 0; + configValues["debug:overlay"].intValue = 0; + configValues["debug:damage_blink"].intValue = 0; + configValues["debug:disable_logs"].intValue = 1; + configValues["debug:disable_time"].intValue = 1; + configValues["debug:enable_stdout_logs"].intValue = 0; + configValues["debug:damage_tracking"].intValue = DAMAGE_TRACKING_FULL; + configValues["debug:manual_crash"].intValue = 0; + configValues["debug:suppress_errors"].intValue = 0; + configValues["debug:watchdog_timeout"].intValue = 5; + configValues["debug:disable_scale_checks"].intValue = 0; + + configValues["decoration:rounding"].intValue = 0; + configValues["decoration:blur:enabled"].intValue = 1; + configValues["decoration:blur:size"].intValue = 8; + configValues["decoration:blur:passes"].intValue = 1; + configValues["decoration:blur:ignore_opacity"].intValue = 0; + configValues["decoration:blur:new_optimizations"].intValue = 1; + configValues["decoration:blur:xray"].intValue = 0; + configValues["decoration:blur:contrast"].floatValue = 0.8916; + configValues["decoration:blur:brightness"].floatValue = 1.0; + configValues["decoration:blur:vibrancy"].floatValue = 0.1696; + configValues["decoration:blur:vibrancy_darkness"].floatValue = 0.0; + configValues["decoration:blur:noise"].floatValue = 0.0117; + configValues["decoration:blur:special"].intValue = 0; + configValues["decoration:blur:popups"].intValue = 0; + configValues["decoration:blur:popups_ignorealpha"].floatValue = 0.2; + configValues["decoration:active_opacity"].floatValue = 1; + configValues["decoration:inactive_opacity"].floatValue = 1; + configValues["decoration:fullscreen_opacity"].floatValue = 1; + configValues["decoration:no_blur_on_oversized"].intValue = 0; + configValues["decoration:drop_shadow"].intValue = 1; + configValues["decoration:shadow_range"].intValue = 4; + configValues["decoration:shadow_render_power"].intValue = 3; + configValues["decoration:shadow_ignore_window"].intValue = 1; + configValues["decoration:shadow_offset"].vecValue = Vector2D(); + configValues["decoration:shadow_scale"].floatValue = 1.f; + configValues["decoration:col.shadow"].intValue = 0xee1a1a1a; + configValues["decoration:col.shadow_inactive"].intValue = INT_MAX; + configValues["decoration:dim_inactive"].intValue = 0; + configValues["decoration:dim_strength"].floatValue = 0.5f; + configValues["decoration:dim_special"].floatValue = 0.2f; + configValues["decoration:dim_around"].floatValue = 0.4f; + configValues["decoration:screen_shader"].strValue = STRVAL_EMPTY; configValues["dwindle:pseudotile"].intValue = 0; configValues["dwindle:force_split"].intValue = 0; configValues["dwindle:permanent_direction_override"].intValue = 0; configValues["dwindle:preserve_split"].intValue = 0; - configValues["dwindle:special_scale_factor"].floatValue = 0.8f; + configValues["dwindle:special_scale_factor"].floatValue = 1.f; configValues["dwindle:split_width_multiplier"].floatValue = 1.0f; configValues["dwindle:no_gaps_when_only"].intValue = 0; configValues["dwindle:use_active_for_splits"].intValue = 1; @@ -176,7 +207,7 @@ void CConfigManager::setDefaultVars() { configValues["dwindle:smart_split"].intValue = 0; configValues["dwindle:smart_resizing"].intValue = 1; - configValues["master:special_scale_factor"].floatValue = 0.8f; + configValues["master:special_scale_factor"].floatValue = 1.f; configValues["master:mfact"].floatValue = 0.55f; configValues["master:new_is_master"].intValue = 1; configValues["master:always_center_master"].intValue = 0; @@ -186,11 +217,14 @@ void CConfigManager::setDefaultVars() { configValues["master:inherit_fullscreen"].intValue = 1; configValues["master:allow_small_split"].intValue = 0; configValues["master:smart_resizing"].intValue = 1; + configValues["master:drop_at_cursor"].intValue = 1; - configValues["animations:enabled"].intValue = 1; + configValues["animations:enabled"].intValue = 1; + configValues["animations:first_launch_animation"].intValue = 1; configValues["input:follow_mouse"].intValue = 1; configValues["input:mouse_refocus"].intValue = 1; + configValues["input:special_fallthrough"].intValue = 0; configValues["input:sensitivity"].floatValue = 0.f; configValues["input:accel_profile"].strValue = STRVAL_EMPTY; configValues["input:kb_file"].strValue = STRVAL_EMPTY; @@ -209,6 +243,7 @@ void CConfigManager::setDefaultVars() { configValues["input:scroll_method"].strValue = STRVAL_EMPTY; configValues["input:scroll_button"].intValue = 0; configValues["input:scroll_button_lock"].intValue = 0; + configValues["input:scroll_points"].strValue = STRVAL_EMPTY; configValues["input:touchpad:natural_scroll"].intValue = 0; configValues["input:touchpad:disable_while_typing"].intValue = 1; configValues["input:touchpad:clickfinger_behavior"].intValue = 0; @@ -222,13 +257,18 @@ void CConfigManager::setDefaultVars() { configValues["input:touchdevice:output"].strValue = STRVAL_EMPTY; configValues["input:tablet:transform"].intValue = 0; configValues["input:tablet:output"].strValue = STRVAL_EMPTY; + configValues["input:tablet:region_position"].vecValue = Vector2D(); + configValues["input:tablet:region_size"].vecValue = Vector2D(); + configValues["input:tablet:relative_input"].intValue = 0; - configValues["binds:pass_mouse_when_bound"].intValue = 0; - configValues["binds:scroll_event_delay"].intValue = 300; - configValues["binds:workspace_back_and_forth"].intValue = 0; - configValues["binds:allow_workspace_cycles"].intValue = 0; - configValues["binds:focus_preferred_method"].intValue = 0; - configValues["binds:ignore_group_lock"].intValue = 0; + configValues["binds:pass_mouse_when_bound"].intValue = 0; + configValues["binds:scroll_event_delay"].intValue = 300; + configValues["binds:workspace_back_and_forth"].intValue = 0; + configValues["binds:allow_workspace_cycles"].intValue = 0; + configValues["binds:workspace_center_on"].intValue = 1; + configValues["binds:focus_preferred_method"].intValue = 0; + configValues["binds:ignore_group_lock"].intValue = 0; + configValues["binds:movefocus_cycles_fullscreen"].intValue = 1; configValues["gestures:workspace_swipe"].intValue = 0; configValues["gestures:workspace_swipe_fingers"].intValue = 3; @@ -246,6 +286,8 @@ void CConfigManager::setDefaultVars() { configValues["xwayland:use_nearest_neighbor"].intValue = 1; configValues["xwayland:force_zero_scaling"].intValue = 0; + configValues["opengl:nvidia_anti_flicker"].intValue = 1; + configValues["autogenerated"].intValue = 0; } @@ -275,9 +317,13 @@ void CConfigManager::setDeviceDefaultVars(const std::string& dev) { cfgValues["scroll_method"].strValue = STRVAL_EMPTY; cfgValues["scroll_button"].intValue = 0; cfgValues["scroll_button_lock"].intValue = 0; + cfgValues["scroll_points"].strValue = STRVAL_EMPTY; cfgValues["transform"].intValue = 0; cfgValues["output"].strValue = STRVAL_EMPTY; - cfgValues["enabled"].intValue = 1; // only for mice / touchpads + cfgValues["enabled"].intValue = 1; // only for mice / touchpads + cfgValues["region_position"].vecValue = Vector2D(); // only for tablets + cfgValues["region_size"].vecValue = Vector2D(); // only for tablets + cfgValues["relative_input"].intValue = 0; // only for tablets } void CConfigManager::setDefaultAnimationVars() { @@ -347,14 +393,24 @@ void CConfigManager::init() { } void CConfigManager::configSetValueSafe(const std::string& COMMAND, const std::string& VALUE) { - if (configValues.find(COMMAND) == configValues.end()) { - if (COMMAND.find("device:") != 0 /* devices parsed later */ && COMMAND.find("plugin:") != 0 /* plugins parsed later */) { + if (!configValues.contains(COMMAND)) { + if (!COMMAND.starts_with("device:") /* devices parsed later */ && !COMMAND.starts_with("plugin:") /* plugins parsed later */) { if (COMMAND[0] == '$') { // register a dynamic var - Debug::log(LOG, "Registered dynamic var \"{}\" -> {}", COMMAND, VALUE); - configDynamicVars.emplace_back(std::make_pair<>(COMMAND.substr(1), VALUE)); + bool found = false; + for (auto& [var, val] : configDynamicVars) { + if (var == COMMAND.substr(1)) { + Debug::log(LOG, "Registered new value for dynamic var \"{}\" -> {}", COMMAND, VALUE); + val = VALUE; + found = true; + } + } - std::sort(configDynamicVars.begin(), configDynamicVars.end(), [&](const auto& a, const auto& b) { return a.first.length() > b.first.length(); }); + if (!found) { + Debug::log(LOG, "Registered dynamic var \"{}\" -> {}", COMMAND, VALUE); + configDynamicVars.emplace_back(std::make_pair<>(COMMAND.substr(1), VALUE)); + std::sort(configDynamicVars.begin(), configDynamicVars.end(), [&](const auto& a, const auto& b) { return a.first.length() > b.first.length(); }); + } } else { parseError = "Error setting value <" + VALUE + "> for field <" + COMMAND + ">: No such field."; } @@ -365,7 +421,7 @@ void CConfigManager::configSetValueSafe(const std::string& COMMAND, const std::s SConfigValue* CONFIGENTRY = nullptr; - if (COMMAND.find("device:") == 0) { + if (COMMAND.starts_with("device:")) { const auto DEVICE = COMMAND.substr(7).substr(0, COMMAND.find_last_of(':') - 7); const auto CONFIGVAR = COMMAND.substr(COMMAND.find_last_of(':') + 1); @@ -385,7 +441,7 @@ void CConfigManager::configSetValueSafe(const std::string& COMMAND, const std::s } CONFIGENTRY = &it->second.at(CONFIGVAR); - } else if (COMMAND.find("plugin:") == 0) { + } else if (COMMAND.starts_with("plugin:")) { for (auto& [handle, pMap] : pluginConfigs) { auto it = std::find_if(pMap->begin(), pMap->end(), [&](const auto& other) { return other.first == COMMAND; }); if (it == pMap->end()) { @@ -495,7 +551,7 @@ void CConfigManager::configSetValueSafe(const std::string& COMMAND, const std::s } } - if (COMMAND == "decoration:screen_shader") { + if (COMMAND == "decoration:screen_shader" && VALUE != STRVAL_EMPTY) { const auto PATH = absolutePath(VALUE, configCurrentPath); configPaths.push_back(PATH); @@ -623,11 +679,11 @@ void CConfigManager::handleMonitor(const std::string& command, const std::string return; } - if (ARGS[1].find("pref") == 0) { + if (ARGS[1].starts_with("pref")) { newrule.resolution = Vector2D(); - } else if (ARGS[1].find("highrr") == 0) { + } else if (ARGS[1].starts_with("highrr")) { newrule.resolution = Vector2D(-1, -1); - } else if (ARGS[1].find("highres") == 0) { + } else if (ARGS[1].starts_with("highres")) { newrule.resolution = Vector2D(-1, -2); } else if (parseModeLine(ARGS[1], newrule.drmMode)) { newrule.resolution = Vector2D(newrule.drmMode.hdisplay, newrule.drmMode.vdisplay); @@ -640,14 +696,14 @@ void CConfigManager::handleMonitor(const std::string& command, const std::string newrule.refreshRate = stof(ARGS[1].substr(ARGS[1].find_first_of('@') + 1)); } - if (ARGS[2].find("auto") == 0) { + if (ARGS[2].starts_with("auto")) { newrule.offset = Vector2D(-INT32_MAX, -INT32_MAX); } else { newrule.offset.x = stoi(ARGS[2].substr(0, ARGS[2].find_first_of('x'))); newrule.offset.y = stoi(ARGS[2].substr(ARGS[2].find_first_of('x') + 1)); } - if (ARGS[3].find("auto") == 0) { + if (ARGS[3].starts_with("auto")) { newrule.scale = -1; } else { newrule.scale = stof(ARGS[3]); @@ -810,6 +866,7 @@ void CConfigManager::handleBind(const std::string& command, const std::string& v bool mouse = false; bool nonConsuming = false; bool transparent = false; + bool ignoreMods = false; const auto BINDARGS = command.substr(4); for (auto& arg : BINDARGS) { @@ -825,6 +882,8 @@ void CConfigManager::handleBind(const std::string& command, const std::string& v nonConsuming = true; } else if (arg == 't') { transparent = true; + } else if (arg == 'i') { + ignoreMods = true; } else { parseError = "bind: invalid flag"; return; @@ -882,12 +941,13 @@ void CConfigManager::handleBind(const std::string& command, const std::string& v if (KEY != "") { if (isNumber(KEY) && std::stoi(KEY) > 9) - g_pKeybindManager->addKeybind(SKeybind{"", std::stoi(KEY), MOD, HANDLER, COMMAND, locked, m_szCurrentSubmap, release, repeat, mouse, nonConsuming, transparent}); - else if (KEY.find("code:") == 0 && isNumber(KEY.substr(5))) g_pKeybindManager->addKeybind( - SKeybind{"", std::stoi(KEY.substr(5)), MOD, HANDLER, COMMAND, locked, m_szCurrentSubmap, release, repeat, mouse, nonConsuming, transparent}); + SKeybind{"", std::stoi(KEY), MOD, HANDLER, COMMAND, locked, m_szCurrentSubmap, release, repeat, mouse, nonConsuming, transparent, ignoreMods}); + else if (KEY.starts_with("code:") && isNumber(KEY.substr(5))) + g_pKeybindManager->addKeybind( + SKeybind{"", std::stoi(KEY.substr(5)), MOD, HANDLER, COMMAND, locked, m_szCurrentSubmap, release, repeat, mouse, nonConsuming, transparent, ignoreMods}); else - g_pKeybindManager->addKeybind(SKeybind{KEY, -1, MOD, HANDLER, COMMAND, locked, m_szCurrentSubmap, release, repeat, mouse, nonConsuming, transparent}); + g_pKeybindManager->addKeybind(SKeybind{KEY, 0, MOD, HANDLER, COMMAND, locked, m_szCurrentSubmap, release, repeat, mouse, nonConsuming, transparent, ignoreMods}); } } @@ -902,17 +962,17 @@ void CConfigManager::handleUnbind(const std::string& command, const std::string& } bool windowRuleValid(const std::string& RULE) { - return !(RULE != "float" && RULE != "tile" && RULE.find("opacity") != 0 && RULE.find("move") != 0 && RULE.find("size") != 0 && RULE.find("minsize") != 0 && - RULE.find("maxsize") != 0 && RULE.find("pseudo") != 0 && RULE.find("monitor") != 0 && RULE.find("idleinhibit") != 0 && RULE != "nofocus" && RULE != "noblur" && - RULE != "noshadow" && RULE != "nodim" && RULE != "noborder" && RULE != "opaque" && RULE != "forceinput" && RULE != "fullscreen" && RULE != "nofullscreenrequest" && - RULE != "nomaximizerequest" && RULE != "fakefullscreen" && RULE != "nomaxsize" && RULE != "pin" && RULE != "noanim" && RULE != "dimaround" && RULE != "windowdance" && - RULE != "maximize" && RULE != "keepaspectratio" && RULE.find("animation") != 0 && RULE.find("rounding") != 0 && RULE.find("workspace") != 0 && - RULE.find("bordercolor") != 0 && RULE != "forcergbx" && RULE != "noinitialfocus" && RULE != "stayfocused" && RULE.find("bordersize") != 0 && RULE.find("xray") != 0 && - RULE.find("center") != 0 && RULE.find("group") != 0); + return RULE == "float" || RULE == "tile" || RULE.starts_with("opacity") || RULE.starts_with("move") || RULE.starts_with("size") || RULE.starts_with("minsize") || + RULE.starts_with("maxsize") || RULE.starts_with("pseudo") || RULE.starts_with("monitor") || RULE.starts_with("idleinhibit") || RULE == "nofocus" || RULE == "noblur" || + RULE == "noshadow" || RULE == "nodim" || RULE == "noborder" || RULE == "opaque" || RULE == "forceinput" || RULE == "fullscreen" || RULE == "nofullscreenrequest" || + RULE == "nomaximizerequest" || RULE == "fakefullscreen" || RULE == "nomaxsize" || RULE == "pin" || RULE == "noanim" || RULE == "dimaround" || RULE == "windowdance" || + RULE == "maximize" || RULE == "keepaspectratio" || RULE.starts_with("animation") || RULE.starts_with("rounding") || RULE.starts_with("workspace") || + RULE.starts_with("bordercolor") || RULE == "forcergbx" || RULE == "noinitialfocus" || RULE == "stayfocused" || RULE.starts_with("bordersize") || RULE.starts_with("xray") || + RULE.starts_with("center") || RULE.starts_with("group") || RULE == "immediate" || RULE == "nearestneighbor"; } bool layerRuleValid(const std::string& RULE) { - return !(RULE != "noanim" && RULE != "blur" && RULE.find("ignorealpha") != 0 && RULE.find("ignorezero") != 0 && RULE.find("xray") != 0); + return RULE == "noanim" || RULE == "blur" || RULE.starts_with("ignorealpha") || RULE.starts_with("ignorezero") || RULE.starts_with("xray"); } void CConfigManager::handleWindowRule(const std::string& command, const std::string& value) { @@ -936,7 +996,7 @@ void CConfigManager::handleWindowRule(const std::string& command, const std::str return; } - if (RULE.find("size") == 0 || RULE.find("maxsize") == 0 || RULE.find("minsize") == 0) + if (RULE.starts_with("size") || RULE.starts_with("maxsize") || RULE.starts_with("minsize")) m_dWindowRules.push_front({RULE, VALUE}); else m_dWindowRules.push_back({RULE, VALUE}); @@ -985,16 +1045,31 @@ void CConfigManager::handleWindowRuleV2(const std::string& command, const std::s rule.szRule = RULE; rule.szValue = VALUE; - const auto TITLEPOS = VALUE.find("title:"); - const auto CLASSPOS = VALUE.find("class:"); - const auto X11POS = VALUE.find("xwayland:"); - const auto FLOATPOS = VALUE.find("floating:"); - const auto FULLSCREENPOS = VALUE.find("fullscreen:"); - const auto PINNEDPOS = VALUE.find("pinned:"); - const auto WORKSPACEPOS = VALUE.find("workspace:"); + const auto TITLEPOS = VALUE.find("title:"); + const auto CLASSPOS = VALUE.find("class:"); + const auto INITIALTITLEPOS = VALUE.find("initialTitle:"); + const auto INITIALCLASSPOS = VALUE.find("initialClass:"); + const auto X11POS = VALUE.find("xwayland:"); + const auto FLOATPOS = VALUE.find("floating:"); + const auto FULLSCREENPOS = VALUE.find("fullscreen:"); + const auto PINNEDPOS = VALUE.find("pinned:"); + const auto FOCUSPOS = VALUE.find("focus:"); + const auto ONWORKSPACEPOS = VALUE.find("onworkspace:"); - if (TITLEPOS == std::string::npos && CLASSPOS == std::string::npos && X11POS == std::string::npos && FLOATPOS == std::string::npos && FULLSCREENPOS == std::string::npos && - PINNEDPOS == std::string::npos && WORKSPACEPOS == std::string::npos) { + // find workspacepos that isn't onworkspacepos + size_t WORKSPACEPOS = std::string::npos; + size_t currentPos = VALUE.find("workspace:"); + while (currentPos != std::string::npos) { + if (currentPos == 0 || VALUE[currentPos - 1] != 'n') { + WORKSPACEPOS = currentPos; + break; + } + currentPos = VALUE.find("workspace:", currentPos + 1); + } + + if (TITLEPOS == std::string::npos && CLASSPOS == std::string::npos && INITIALTITLEPOS == std::string::npos && INITIALCLASSPOS == std::string::npos && + X11POS == std::string::npos && FLOATPOS == std::string::npos && FULLSCREENPOS == std::string::npos && PINNEDPOS == std::string::npos && WORKSPACEPOS == std::string::npos && + FOCUSPOS == std::string::npos && ONWORKSPACEPOS == std::string::npos) { Debug::log(ERR, "Invalid rulev2 syntax: {}", VALUE); parseError = "Invalid rulev2 syntax: " + VALUE; return; @@ -1009,6 +1084,10 @@ void CConfigManager::handleWindowRuleV2(const std::string& command, const std::s min = TITLEPOS; if (CLASSPOS > pos && CLASSPOS < min) min = CLASSPOS; + if (INITIALTITLEPOS > pos && INITIALTITLEPOS < min) + min = INITIALTITLEPOS; + if (INITIALCLASSPOS > pos && INITIALCLASSPOS < min) + min = INITIALCLASSPOS; if (X11POS > pos && X11POS < min) min = X11POS; if (FLOATPOS > pos && FLOATPOS < min) @@ -1017,8 +1096,12 @@ void CConfigManager::handleWindowRuleV2(const std::string& command, const std::s min = FULLSCREENPOS; if (PINNEDPOS > pos && PINNEDPOS < min) min = PINNEDPOS; + if (ONWORKSPACEPOS > pos && ONWORKSPACEPOS < min) + min = ONWORKSPACEPOS; if (WORKSPACEPOS > pos && WORKSPACEPOS < min) - min = PINNEDPOS; + min = WORKSPACEPOS; + if (FOCUSPOS > pos && FOCUSPOS < min) + min = FOCUSPOS; result = result.substr(0, min - pos); @@ -1036,6 +1119,12 @@ void CConfigManager::handleWindowRuleV2(const std::string& command, const std::s if (TITLEPOS != std::string::npos) rule.szTitle = extract(TITLEPOS + 6); + if (INITIALCLASSPOS != std::string::npos) + rule.szInitialClass = extract(INITIALCLASSPOS + 13); + + if (INITIALTITLEPOS != std::string::npos) + rule.szInitialTitle = extract(INITIALTITLEPOS + 13); + if (X11POS != std::string::npos) rule.bX11 = extract(X11POS + 9) == "1" ? 1 : 0; @@ -1051,6 +1140,12 @@ void CConfigManager::handleWindowRuleV2(const std::string& command, const std::s if (WORKSPACEPOS != std::string::npos) rule.szWorkspace = extract(WORKSPACEPOS + 10); + if (FOCUSPOS != std::string::npos) + rule.bFocus = extract(FOCUSPOS + 6) == "1" ? 1 : 0; + + if (ONWORKSPACEPOS != std::string::npos) + rule.iOnWorkspace = configStringToInt(extract(ONWORKSPACEPOS + 12)); + if (RULE == "unset") { std::erase_if(m_dWindowRules, [&](const SWindowRule& other) { if (!other.v2) { @@ -1062,6 +1157,12 @@ void CConfigManager::handleWindowRuleV2(const std::string& command, const std::s if (!rule.szTitle.empty() && rule.szTitle != other.szTitle) return false; + if (!rule.szInitialClass.empty() && rule.szInitialClass != other.szInitialClass) + return false; + + if (!rule.szInitialTitle.empty() && rule.szInitialTitle != other.szInitialTitle) + return false; + if (rule.bX11 != -1 && rule.bX11 != other.bX11) return false; @@ -1077,20 +1178,26 @@ void CConfigManager::handleWindowRuleV2(const std::string& command, const std::s if (!rule.szWorkspace.empty() && rule.szWorkspace != other.szWorkspace) return false; + if (rule.bFocus != -1 && rule.bFocus != other.bFocus) + return false; + + if (rule.iOnWorkspace != -1 && rule.iOnWorkspace != other.iOnWorkspace) + return false; + return true; } }); return; } - if (RULE.find("size") == 0 || RULE.find("maxsize") == 0 || RULE.find("minsize") == 0) + if (RULE.starts_with("size") || RULE.starts_with("maxsize") || RULE.starts_with("minsize")) m_dWindowRules.push_front(rule); else m_dWindowRules.push_back(rule); } void CConfigManager::updateBlurredLS(const std::string& name, const bool forceBlur) { - const bool BYADDRESS = name.find("address:") == 0; + const bool BYADDRESS = name.starts_with("address:"); std::string matchName = name; if (BYADDRESS) { @@ -1111,7 +1218,7 @@ void CConfigManager::updateBlurredLS(const std::string& name, const bool forceBl } void CConfigManager::handleBlurLS(const std::string& command, const std::string& value) { - if (value.find("remove,") == 0) { + if (value.starts_with("remove,")) { const auto TOREMOVE = removeBeginEndSpacesTabs(value.substr(7)); if (std::erase_if(m_dBlurLSNamespaces, [&](const auto& other) { return other == TOREMOVE; })) updateBlurredLS(TOREMOVE, false); @@ -1133,13 +1240,13 @@ void CConfigManager::handleWorkspaceRules(const std::string& command, const std: auto rules = value.substr(FIRST_DELIM + 1); SWorkspaceRule wsRule; wsRule.workspaceString = first_ident; - if (id == INT_MAX) { + if (id == WORKSPACE_INVALID) { // it could be the monitor. If so, second value MUST be // the workspace. const auto WORKSPACE_DELIM = value.find_first_of(',', FIRST_DELIM + 1); auto wsIdent = removeBeginEndSpacesTabs(value.substr(FIRST_DELIM + 1, (WORKSPACE_DELIM - FIRST_DELIM - 1))); id = getWorkspaceIDFromString(wsIdent, name); - if (id == INT_MAX) { + if (id == WORKSPACE_INVALID) { Debug::log(ERR, "Invalid workspace identifier found: {}", wsIdent); parseError = "Invalid workspace identifier found: " + wsIdent; return; @@ -1150,7 +1257,10 @@ void CConfigManager::handleWorkspaceRules(const std::string& command, const std: rules = value.substr(WORKSPACE_DELIM + 1); } - auto assignRule = [&](std::string rule) { + const static std::string ruleOnCreatedEmtpy = "on-created-empty:"; + const static int ruleOnCreatedEmtpyLen = ruleOnCreatedEmtpy.length(); + + auto assignRule = [&](std::string rule) { size_t delim = std::string::npos; if ((delim = rule.find("gapsin:")) != std::string::npos) wsRule.gapsIn = std::stoi(rule.substr(delim + 7)); @@ -1170,6 +1280,24 @@ void CConfigManager::handleWorkspaceRules(const std::string& command, const std: wsRule.monitor = rule.substr(delim + 8); else if ((delim = rule.find("default:")) != std::string::npos) wsRule.isDefault = configStringToInt(rule.substr(delim + 8)); + else if ((delim = rule.find("persistent:")) != std::string::npos) + wsRule.isPersistent = configStringToInt(rule.substr(delim + 11)); + else if ((delim = rule.find(ruleOnCreatedEmtpy)) != std::string::npos) + wsRule.onCreatedEmptyRunCmd = cleanCmdForWorkspace(name, rule.substr(delim + ruleOnCreatedEmtpyLen)); + else if ((delim = rule.find("layoutopt:")) != std::string::npos) { + std::string opt = rule.substr(delim + 10); + if (!opt.contains(":")) { + // invalid + Debug::log(ERR, "Invalid workspace rule found: {}", rule); + parseError = "Invalid workspace rule found: " + rule; + return; + } + + std::string val = opt.substr(opt.find(":") + 1); + opt = opt.substr(0, opt.find(":")); + + wsRule.layoutopts[opt] = val; + } }; size_t pos = 0; @@ -1217,7 +1345,12 @@ void CConfigManager::handleSource(const std::string& command, const std::string& for (size_t i = 0; i < glob_buf->gl_pathc; i++) { auto value = absolutePath(glob_buf->gl_pathv[i], configCurrentPath); - if (!std::filesystem::exists(value)) { + if (!std::filesystem::is_regular_file(value)) { + if (std::filesystem::exists(value)) { + Debug::log(WARN, "source= skipping non-file {}", value); + continue; + } + Debug::log(ERR, "source= file doesnt exist"); parseError = "source file " + value + " doesn't exist!"; return; @@ -1240,7 +1373,7 @@ void CConfigManager::handleSource(const std::string& command, const std::string& int linenum = 1; if (ifs.is_open()) { auto configCurrentPathBackup = configCurrentPath; - + while (std::getline(ifs, line)) { // Read line by line. try { @@ -1253,7 +1386,7 @@ void CConfigManager::handleSource(const std::string& command, const std::string& parseError += "Config error at line " + std::to_string(linenum) + " (" + configCurrentPath + "): Line parsing error."; } - if (parseError != "" && parseError.find("Config error at line") != 0) { + if (parseError != "" && !parseError.starts_with("Config error at line")) { parseError = "Config error at line " + std::to_string(linenum) + " (" + configCurrentPath + "): " + parseError; } @@ -1261,7 +1394,7 @@ void CConfigManager::handleSource(const std::string& command, const std::string& } ifs.close(); - + configCurrentPath = configCurrentPathBackup; } } @@ -1326,7 +1459,7 @@ std::string CConfigManager::parseKeyword(const std::string& COMMAND, const std:: } } else if (COMMAND == "monitor") handleMonitor(COMMAND, VALUE); - else if (COMMAND.find("bind") == 0) + else if (COMMAND.starts_with("bind")) handleBind(COMMAND, VALUE); else if (COMMAND == "unbind") handleUnbind(COMMAND, VALUE); @@ -1350,13 +1483,20 @@ std::string CConfigManager::parseKeyword(const std::string& COMMAND, const std:: handleBlurLS(COMMAND, VALUE); else if (COMMAND == "wsbind") handleBindWS(COMMAND, VALUE); - else if (COMMAND.find("env") == 0) - handleEnv(COMMAND, VALUE); - else if (COMMAND.find("plugin") == 0) + else if (COMMAND == "plugin") handlePlugin(COMMAND, VALUE); + else if (COMMAND.starts_with("env")) + handleEnv(COMMAND, VALUE); else { - configSetValueSafe(currentCategory + (currentCategory == "" ? "" : ":") + COMMAND, VALUE); - needsLayoutRecalc = 2; + // try config + const auto IT = std::find_if(pluginKeywords.begin(), pluginKeywords.end(), [&](const auto& other) { return other.name == COMMAND; }); + + if (IT != pluginKeywords.end()) { + IT->fn(COMMAND, VALUE); + } else { + configSetValueSafe(currentCategory + (currentCategory == "" ? "" : ":") + COMMAND, VALUE); + needsLayoutRecalc = 2; + } } if (dynamic) { @@ -1365,7 +1505,7 @@ std::string CConfigManager::parseKeyword(const std::string& COMMAND, const std:: // invalidate layouts if they changed if (needsLayoutRecalc) { - if (needsLayoutRecalc == 1 || COMMAND.contains("gaps_") || COMMAND.find("dwindle:") == 0 || COMMAND.find("master:") == 0) { + if (needsLayoutRecalc == 1 || COMMAND.contains("gaps_") || COMMAND.starts_with("dwindle:") || COMMAND.starts_with("master:")) { for (auto& m : g_pCompositor->m_vMonitors) g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->ID); } @@ -1395,12 +1535,15 @@ std::string CConfigManager::parseKeyword(const std::string& COMMAND, const std:: void CConfigManager::applyUserDefinedVars(std::string& line, const size_t equalsPlace) { auto dollarPlace = line.find_first_of('$', equalsPlace); + int times = 0; + while (dollarPlace != std::string::npos) { + times++; const auto STRAFTERDOLLAR = line.substr(dollarPlace + 1); bool found = false; for (auto& [var, value] : configDynamicVars) { - if (STRAFTERDOLLAR.find(var) == 0) { + if (STRAFTERDOLLAR.starts_with(var)) { line.replace(dollarPlace, var.length() + 1, value); found = true; break; @@ -1410,7 +1553,7 @@ void CConfigManager::applyUserDefinedVars(std::string& line, const size_t equals if (!found) { // maybe env? for (auto& [var, value] : environmentVariables) { - if (STRAFTERDOLLAR.find(var) == 0) { + if (STRAFTERDOLLAR.starts_with(var)) { line.replace(dollarPlace, var.length() + 1, value); break; } @@ -1418,6 +1561,13 @@ void CConfigManager::applyUserDefinedVars(std::string& line, const size_t equals } dollarPlace = line.find_first_of('$', dollarPlace + 1); + + if (times > 256 /* arbitrary limit */) { + line = ""; + parseError = "Maximum variable recursion limit hit. Evaluating the line led to too many variable substitutions."; + Debug::log(ERR, "Variable recursion limit hit in configmanager"); + break; + } } } @@ -1464,7 +1614,7 @@ void CConfigManager::parseLine(std::string& line) { const auto LASTSEP = currentCategory.find_last_of(':'); - if (LASTSEP == std::string::npos || currentCategory.contains("device")) + if (LASTSEP == std::string::npos || currentCategory.starts_with("device")) currentCategory = ""; else currentCategory = currentCategory.substr(0, LASTSEP); @@ -1490,6 +1640,8 @@ void CConfigManager::parseLine(std::string& line) { } void CConfigManager::loadConfigLoadVars() { + EMIT_HOOK_EVENT("preConfigReload", nullptr); + Debug::log(LOG, "Reloading the config!"); parseError = ""; // reset the error currentCategory = ""; // reset the category @@ -1515,20 +1667,20 @@ void CConfigManager::loadConfigLoadVars() { std::string mainConfigPath = getMainConfigPath(); Debug::log(LOG, "Using config: {}", mainConfigPath); configPaths.push_back(mainConfigPath); - std::string configPath = mainConfigPath.substr(0, mainConfigPath.find_last_of('/')); - // find_last_of never returns npos since main_config at least has /hypr/ - if (!std::filesystem::is_directory(configPath)) { - Debug::log(WARN, "Creating config home directory"); - try { - std::filesystem::create_directories(configPath); - } catch (...) { - parseError = "Broken config file! (Could not create config directory)"; - return; + if (g_pCompositor->explicitConfigPath.empty() && !std::filesystem::exists(mainConfigPath)) { + std::string configPath = std::filesystem::path(mainConfigPath).parent_path(); + + if (!std::filesystem::is_directory(configPath)) { + Debug::log(WARN, "Creating config home directory"); + try { + std::filesystem::create_directories(configPath); + } catch (...) { + parseError = "Broken config file! (Could not create config directory)"; + return; + } } - } - if (!std::filesystem::exists(mainConfigPath)) { Debug::log(WARN, "No config file found; attempting to generate."); std::ofstream ofs; ofs.open(mainConfigPath, std::ios::trunc); @@ -1540,10 +1692,16 @@ void CConfigManager::loadConfigLoadVars() { ifs.open(mainConfigPath); if (!ifs.good()) { - Debug::log(WARN, "Config reading error. Attempting to generate, backing up old one if exists"); - ifs.close(); + if (!g_pCompositor->explicitConfigPath.empty()) { + Debug::log(WARN, "Config reading error!"); + parseError = "Broken config file! (Could not read)"; + return; + } + + Debug::log(WARN, "Config reading error. Attempting to generate, backing up old one if exists"); + if (std::filesystem::exists(mainConfigPath)) std::filesystem::rename(mainConfigPath, mainConfigPath + ".backup"); @@ -1576,7 +1734,7 @@ void CConfigManager::loadConfigLoadVars() { parseError += "Config error at line " + std::to_string(linenum) + " (" + mainConfigPath + "): Line parsing error."; } - if (parseError != "" && parseError.find("Config error at line") != 0) { + if (parseError != "" && !parseError.starts_with("Config error at line")) { parseError = "Config error at line " + std::to_string(linenum) + " (" + mainConfigPath + "): " + parseError; } @@ -1586,6 +1744,10 @@ void CConfigManager::loadConfigLoadVars() { ifs.close(); } + for (auto& w : g_pCompositor->m_vWindows) { + w->uncacheWindowDecos(); + } + for (auto& m : g_pCompositor->m_vMonitors) g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->ID); @@ -1620,6 +1782,9 @@ void CConfigManager::loadConfigLoadVars() { ensureVRR(); } + if (!isFirstLaunch && !g_pCompositor->m_bUnsafeState) + refreshGroupBarGradients(); + // Updates dynamic window and workspace rules for (auto& w : g_pCompositor->m_vWindows) { if (!w->m_bIsMapped) @@ -1665,6 +1830,8 @@ void CConfigManager::loadConfigLoadVars() { handlePluginLoads(); EMIT_HOOK_EVENT("configReloaded", nullptr); + if (g_pEventManager) + g_pEventManager->postEvent(SHyprIPCEvent{"configreloaded", ""}); } void CConfigManager::tick() { @@ -1737,6 +1904,10 @@ float CConfigManager::getFloat(const std::string& v) { return getConfigValueSafe(v).floatValue; } +Vector2D CConfigManager::getVec(const std::string& v) { + return getConfigValueSafe(v).vecValue; +} + std::string CConfigManager::getString(const std::string& v) { auto VAL = getConfigValueSafe(v).strValue; @@ -1754,6 +1925,10 @@ float CConfigManager::getDeviceFloat(const std::string& dev, const std::string& return getConfigValueSafeDevice(dev, v, fallback).floatValue; } +Vector2D CConfigManager::getDeviceVec(const std::string& dev, const std::string& v, const std::string& fallback) { + return getConfigValueSafeDevice(dev, v, fallback).vecValue; +} + std::string CConfigManager::getDeviceString(const std::string& dev, const std::string& v, const std::string& fallback) { auto VAL = getConfigValueSafeDevice(dev, v, fallback).strValue; @@ -1771,6 +1946,10 @@ void CConfigManager::setFloat(const std::string& v, float val) { configValues[v].floatValue = val; } +void CConfigManager::setVec(const std::string& v, Vector2D val) { + configValues[v].vecValue = val; +} + void CConfigManager::setString(const std::string& v, const std::string& val) { configValues[v].strValue = val; } @@ -1780,7 +1959,7 @@ SMonitorRule CConfigManager::getMonitorRuleFor(const std::string& name, const st for (auto& r : m_dMonitorRules) { if (r.name == name || - (r.name.find("desc:") == 0 && + (r.name.starts_with("desc:") && (r.name.substr(5) == displayName || r.name.substr(5) == removeBeginEndSpacesTabs(displayName.substr(0, displayName.find_first_of('(')))))) { found = &r; break; @@ -1808,7 +1987,13 @@ SMonitorRule CConfigManager::getMonitorRuleFor(const std::string& name, const st } SWorkspaceRule CConfigManager::getWorkspaceRuleFor(CWorkspace* pWorkspace) { - const auto IT = std::find_if(m_dWorkspaceRules.begin(), m_dWorkspaceRules.end(), [&](const auto& other) { return other.workspaceName == pWorkspace->m_szName; }); + const auto WORKSPACEIDSTR = std::to_string(pWorkspace->m_iID); + const auto IT = std::find_if(m_dWorkspaceRules.begin(), m_dWorkspaceRules.end(), [&](const auto& other) { + return other.workspaceName == pWorkspace->m_szName /* name matches */ + || (pWorkspace->m_bIsSpecialWorkspace && other.workspaceName.starts_with("special:") && + other.workspaceName.substr(8) == pWorkspace->m_szName) /* special and special:name */ + || (pWorkspace->m_iID > 0 && WORKSPACEIDSTR == other.workspaceName); /* id matches and workspace is numerical */ + }); if (IT == m_dWorkspaceRules.end()) return SWorkspaceRule{}; return *IT; @@ -1833,7 +2018,7 @@ std::vector CConfigManager::getMatchingRules(CWindow* pWindow) { // check if we have a matching rule if (!rule.v2) { try { - if (rule.szValue.find("title:") == 0) { + if (rule.szValue.starts_with("title:")) { // we have a title rule. std::regex RULECHECK(rule.szValue.substr(6)); @@ -1865,6 +2050,20 @@ std::vector CConfigManager::getMatchingRules(CWindow* pWindow) { continue; } + if (rule.szInitialTitle != "") { + std::regex RULECHECK(rule.szInitialTitle); + + if (!std::regex_search(pWindow->m_szInitialTitle, RULECHECK)) + continue; + } + + if (rule.szInitialClass != "") { + std::regex RULECHECK(rule.szInitialClass); + + if (!std::regex_search(pWindow->m_szInitialClass, RULECHECK)) + continue; + } + if (rule.bX11 != -1) { if (pWindow->m_bIsX11 != rule.bX11) continue; @@ -1885,13 +2084,23 @@ std::vector CConfigManager::getMatchingRules(CWindow* pWindow) { continue; } + if (rule.bFocus != -1) { + if (rule.bFocus != (g_pCompositor->m_pLastWindow == pWindow)) + continue; + } + + if (rule.iOnWorkspace != -1) { + if (rule.iOnWorkspace != g_pCompositor->getWindowsOnWorkspace(pWindow->m_iWorkspaceID)) + continue; + } + if (!rule.szWorkspace.empty()) { const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(pWindow->m_iWorkspaceID); if (!PWORKSPACE) continue; - if (rule.szWorkspace.find("name:") == 0) { + if (rule.szWorkspace.starts_with("name:")) { if (PWORKSPACE->m_szName != rule.szWorkspace.substr(5)) continue; } else { @@ -1949,7 +2158,7 @@ std::vector CConfigManager::getMatchingRules(SLayerSurface* pLS) { return returns; for (auto& lr : m_dLayerRules) { - if (lr.targetNamespace.find("address:0x") == 0) { + if (lr.targetNamespace.starts_with("address:0x")) { if (std::format("address:0x{:x}", (uintptr_t)pLS) != lr.targetNamespace) continue; } else { @@ -1975,12 +2184,12 @@ void CConfigManager::dispatchExecOnce() { // update dbus env if (g_pCompositor->m_sWLRSession) - handleRawExec( - "", + handleRawExec("", #ifdef USES_SYSTEMD - "systemctl --user import-environment DISPLAY WAYLAND_DISPLAY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP && hash dbus-update-activation-environment 2>/dev/null && " + "systemctl --user import-environment DISPLAY WAYLAND_DISPLAY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP QT_QPA_PLATFORMTHEME && hash " + "dbus-update-activation-environment 2>/dev/null && " #endif - "dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP HYPRLAND_INSTANCE_SIGNATURE"); + "dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP HYPRLAND_INSTANCE_SIGNATURE QT_QPA_PLATFORMTHEME"); firstExecDispatched = true; @@ -2005,10 +2214,10 @@ void CConfigManager::performMonitorReload() { bool overAgain = false; for (auto& m : g_pCompositor->m_vRealMonitors) { - if (!m->output) + if (!m->output || m->isUnsafeFallback) continue; - auto rule = getMonitorRuleFor(m->szName, m->output->description ? m->output->description : ""); + auto rule = getMonitorRuleFor(m->szName, m->szDescription); if (!g_pHyprRenderer->applyMonitorRule(m.get(), &rule)) { overAgain = true; @@ -2071,9 +2280,7 @@ bool CConfigManager::deviceConfigExists(const std::string& dev) { auto copy = dev; std::replace(copy.begin(), copy.end(), ' ', '-'); - const auto it = deviceConfigs.find(copy); - - return it != deviceConfigs.end(); + return deviceConfigs.contains(copy); } bool CConfigManager::shouldBlurLS(const std::string& ns) { @@ -2088,15 +2295,13 @@ bool CConfigManager::shouldBlurLS(const std::string& ns) { void CConfigManager::ensureMonitorStatus() { for (auto& rm : g_pCompositor->m_vRealMonitors) { - if (!rm->output) + if (!rm->output || rm->isUnsafeFallback) continue; - auto rule = getMonitorRuleFor(rm->szName, rm->output->description ? rm->output->description : ""); + auto rule = getMonitorRuleFor(rm->szName, rm->szDescription); - if (rule.disabled == rm->m_bEnabled) { - rm->m_pThisWrap = &rm; + if (rule.disabled == rm->m_bEnabled) g_pHyprRenderer->applyMonitorRule(rm.get(), &rule); - } } } @@ -2194,7 +2399,7 @@ CMonitor* CConfigManager::getBoundMonitorForWS(const std::string& wsname) { std::string CConfigManager::getBoundMonitorStringForWS(const std::string& wsname) { for (auto& wr : m_dWorkspaceRules) { - const auto WSNAME = wr.workspaceName.find("name:") == 0 ? wr.workspaceName.substr(5) : wr.workspaceName; + const auto WSNAME = wr.workspaceName.starts_with("name:") ? wr.workspaceName.substr(5) : wr.workspaceName; if (WSNAME == wsname) { return wr.monitor; @@ -2262,8 +2467,13 @@ void CConfigManager::addPluginConfigVar(HANDLE handle, const std::string& name, } } +void CConfigManager::addPluginKeyword(HANDLE handle, const std::string& name, std::function fn) { + pluginKeywords.emplace_back(SPluginKeyword{handle, name, fn}); +} + void CConfigManager::removePluginConfig(HANDLE handle) { std::erase_if(pluginConfigs, [&](const auto& other) { return other.first == handle; }); + std::erase_if(pluginKeywords, [&](const auto& other) { return other.handle == handle; }); } std::string CConfigManager::getDefaultWorkspaceFor(const std::string& name) { diff --git a/src/config/ConfigManager.hpp b/src/config/ConfigManager.hpp index 3d545bc4..d5957cd5 100644 --- a/src/config/ConfigManager.hpp +++ b/src/config/ConfigManager.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #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 gapsIn; - std::optional gapsOut; - std::optional borderSize; - std::optional border; - std::optional rounding; - std::optional decorate; - std::optional shadow; + std::string monitor = ""; + std::string workspaceString = ""; + std::string workspaceName = ""; + int workspaceId = -1; + bool isDefault = false; + bool isPersistent = false; + std::optional gapsIn; + std::optional gapsOut; + std::optional borderSize; + std::optional border; + std::optional rounding; + std::optional decorate; + std::optional shadow; + std::optional onCreatedEmptyRunCmd; + std::map layoutopts; }; struct SMonitorAdditionalReservedArea { @@ -69,6 +73,12 @@ struct SAnimationPropertyConfig { SAnimationPropertyConfig* pParentAnimation = nullptr; }; +struct SPluginKeyword { + HANDLE handle = 0; + std::string name = ""; + std::function 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 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 fun); + void removePluginConfig(HANDLE handle); // no-op when done. void dispatchExecOnce(); @@ -158,6 +172,7 @@ class CConfigManager { std::vector m_vDeclaredPlugins; std::unordered_map>> pluginConfigs; // stores plugin configs + std::vector pluginKeywords; bool isFirstLaunch = true; // For exec-once diff --git a/src/config/defaultConfig.hpp b/src/config/defaultConfig.hpp index 1793b4a1..1e149a7a 100644 --- a/src/config/defaultConfig.hpp +++ b/src/config/defaultConfig.hpp @@ -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 diff --git a/src/debug/CrashReporter.cpp b/src/debug/CrashReporter.cpp index ed4357d2..65b31399 100644 --- a/src/debug/CrashReporter.cpp +++ b/src/debug/CrashReporter.cpp @@ -1,7 +1,6 @@ #include "CrashReporter.hpp" #include #include -#include #include #include @@ -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; diff --git a/src/debug/HyprCtl.cpp b/src/debug/HyprCtl.cpp index f37b24e6..79855d27 100644 --- a/src/debug/HyprCtl.cpp +++ b/src/debug/HyprCtl.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -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 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() ? "" : escapeJSONStrings(r.monitor)); + const std::string default_ = std::format("\tdefault: {}\n", (bool)(r.isDefault) ? boolToString(r.isDefault) : ""); + const std::string persistent = std::format("\tpersistent: {}\n", (bool)(r.isPersistent) ? boolToString(r.isPersistent) : ""); + const std::string gapsIn = std::format("\tgapsIn: {}\n", (bool)(r.gapsIn) ? std::to_string(r.gapsIn.value()) : ""); + const std::string gapsOut = std::format("\tgapsOut: {}\n", (bool)(r.gapsOut) ? std::to_string(r.gapsOut.value()) : ""); + const std::string borderSize = std::format("\tborderSize: {}\n", (bool)(r.borderSize) ? std::to_string(r.borderSize.value()) : ""); + const std::string border = std::format("\tborder: {}\n", (bool)(r.border) ? boolToString(r.border.value()) : ""); + const std::string rounding = std::format("\trounding: {}\n", (bool)(r.rounding) ? boolToString(r.rounding.value()) : ""); + const std::string decorate = std::format("\tdecorate: {}\n", (bool)(r.decorate) ? boolToString(r.decorate.value()) : ""); + const std::string shadow = std::format("\tshadow: {}\n", (bool)(r.shadow) ? boolToString(r.shadow.value()) : ""); + + 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*)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 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 = ""; diff --git a/src/debug/HyprDebugOverlay.cpp b/src/debug/HyprDebugOverlay.cpp index 741f6e18..b5f7e1f2 100644 --- a/src/debug/HyprDebugOverlay.cpp +++ b/src/debug/HyprDebugOverlay.cpp @@ -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); } diff --git a/src/debug/HyprDebugOverlay.hpp b/src/debug/HyprDebugOverlay.hpp index 03027018..f3beab45 100644 --- a/src/debug/HyprDebugOverlay.hpp +++ b/src/debug/HyprDebugOverlay.hpp @@ -24,7 +24,7 @@ class CHyprMonitorDebugOverlay { std::deque 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; }; diff --git a/src/debug/HyprNotificationOverlay.cpp b/src/debug/HyprNotificationOverlay.cpp index b19f56fa..12f7a575 100644 --- a/src/debug/HyprNotificationOverlay.cpp +++ b/src/debug/HyprNotificationOverlay.cpp @@ -3,7 +3,7 @@ #include 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); } \ No newline at end of file diff --git a/src/debug/HyprNotificationOverlay.hpp b/src/debug/HyprNotificationOverlay.hpp index 462bbde1..84e61f80 100644 --- a/src/debug/HyprNotificationOverlay.hpp +++ b/src/debug/HyprNotificationOverlay.hpp @@ -10,16 +10,15 @@ #include -enum eIconBackend -{ +enum eIconBackend { ICONS_BACKEND_NONE = 0, ICONS_BACKEND_NF, ICONS_BACKEND_FA }; static const std::array, 3 /* backends */> ICONS_ARRAY = { - std::array{"[!]", "[i]", "[Hint]", "[Err]", "[?]", "[ok]", ""}, std::array{"", "", "", "", "", "󰸞", ""}, - std::array{"", "", "", "", "", ""}}; + std::array{"[!]", "[i]", "[Hint]", "[Err]", "[?]", "[ok]", ""}, + std::array{"", "", "", "", "", "󰸞", ""}, std::array{"", "", "", "", "", ""}}; static const std::array 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> m_dNotifications; diff --git a/src/debug/Log.cpp b/src/debug/Log.cpp index 8850a0af..404904a2 100644 --- a/src/debug/Log.cpp +++ b/src/debug/Log.cpp @@ -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"; diff --git a/src/debug/Log.hpp b/src/debug/Log.hpp index 125ed7f4..3d2ed48c 100644 --- a/src/debug/Log.hpp +++ b/src/debug/Log.hpp @@ -1,13 +1,14 @@ #pragma once #include -#include #include #include #include #include +#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 void log(LogLevel level, std::format_string 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); -}; \ No newline at end of file +}; diff --git a/src/debug/TracyDefines.hpp b/src/debug/TracyDefines.hpp index 767b1a20..49d296f6 100644 --- a/src/debug/TracyDefines.hpp +++ b/src/debug/TracyDefines.hpp @@ -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 diff --git a/src/events/Events.hpp b/src/events/Events.hpp index f89032ea..777bdf9f 100644 --- a/src/events/Events.hpp +++ b/src/events/Events.hpp @@ -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); }; diff --git a/src/events/Layers.cpp b/src/events/Layers.cpp index cb99d0b3..d6e21d9d 100644 --- a/src/events/Layers.cpp +++ b/src/events/Layers.cpp @@ -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); } diff --git a/src/events/Misc.cpp b/src/events/Misc.cpp index d4e7bad9..6b77d9c5 100644 --- a/src/events/Misc.cpp +++ b/src/events/Misc.cpp @@ -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()).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"); +} diff --git a/src/events/Monitors.cpp b/src/events/Monitors.cpp index 947f9e7f..56f1d757 100644 --- a/src/events/Monitors.cpp +++ b/src/events/Monitors.cpp @@ -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* PNEWMONITORWRAP = nullptr; PNEWMONITORWRAP = &g_pCompositor->m_vRealMonitors.emplace_back(std::make_shared()); + 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 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& 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); } diff --git a/src/events/Popups.cpp b/src/events/Popups.cpp index b8d1dffa..bcdb1808 100644 --- a/src/events/Popups.cpp +++ b/src/events/Popups.cpp @@ -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); diff --git a/src/events/Windows.cpp b/src/events/Windows.cpp index d0ae4ae8..cb7a6aaf 100644 --- a/src/events/Windows.cpp +++ b/src/events/Windows.cpp @@ -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()).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; diff --git a/src/helpers/AnimatedVariable.hpp b/src/helpers/AnimatedVariable.hpp index 57a772ae..d87273b3 100644 --- a/src/helpers/AnimatedVariable.hpp +++ b/src/helpers/AnimatedVariable.hpp @@ -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(); diff --git a/src/helpers/Box.cpp b/src/helpers/Box.cpp new file mode 100644 index 00000000..3d57bc79 --- /dev/null +++ b/src/helpers/Box.cpp @@ -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)}}; +} diff --git a/src/helpers/Box.hpp b/src/helpers/Box.hpp new file mode 100644 index 00000000..a1ed83be --- /dev/null +++ b/src/helpers/Box.hpp @@ -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; +}; diff --git a/src/helpers/Color.hpp b/src/helpers/Color.hpp index ffd6d2d8..98ffa476 100644 --- a/src/helpers/Color.hpp +++ b/src/helpers/Color.hpp @@ -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}; + } }; diff --git a/src/helpers/MiscFunctions.cpp b/src/helpers/MiscFunctions.cpp index e54e93ac..5eb99bfd 100644 --- a/src/helpers/MiscFunctions.cpp +++ b/src/helpers/MiscFunctions.cpp @@ -2,11 +2,14 @@ #include "../defines.hpp" #include #include "../Compositor.hpp" +#include #include #include #include #include +#ifdef HAS_EXECINFO #include +#endif #if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) #include @@ -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 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 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(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 getBacktrace() { std::vector 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 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 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"; } \ No newline at end of file diff --git a/src/helpers/MiscFunctions.hpp b/src/helpers/MiscFunctions.hpp index 429f381f..1ccbdc0e 100644 --- a/src/helpers/MiscFunctions.hpp +++ b/src/helpers/MiscFunctions.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -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 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 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 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 [[deprecated("use std::format instead")]] std::string getFormat(std::format_string fmt, Args&&... args) { diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index dd9580d1..e62d9d49 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -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* 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 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& el) { return el.get() == this; }); } @@ -313,14 +321,14 @@ void CMonitor::addDamage(const CRegion* rg) { addDamage(const_cast(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(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* 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; -} \ No newline at end of file +} + +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); + } +} diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp index 93f345ba..943b929d 100644 --- a/src/helpers/Monitor.hpp +++ b/src/helpers/Monitor.hpp @@ -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 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* m_pThisWrap = nullptr; - bool m_bEnabled = false; - bool m_bRenderingInitPassed = false; + bool m_bEnabled = false; + bool m_bRenderingInitPassed = false; // For the list lookup diff --git a/src/helpers/Region.cpp b/src/helpers/Region.cpp index 5515e71e..8f180416 100644 --- a/src/helpers/Region.cpp +++ b/src/helpers/Region.cpp @@ -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(&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 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}; } diff --git a/src/helpers/Region.hpp b/src/helpers/Region.hpp index cc745f9e..972a2ba7 100644 --- a/src/helpers/Region.hpp +++ b/src/helpers/Region.hpp @@ -2,6 +2,7 @@ #include #include #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 getRects() const; pixman_region32_t* pixman() { - return &m_rRegion; + return &m_rRegion; } private: diff --git a/src/helpers/SubsurfaceTree.cpp b/src/helpers/SubsurfaceTree.cpp index 5e3e9e67..41b968e4 100644 --- a/src/helpers/SubsurfaceTree.cpp +++ b/src/helpers/SubsurfaceTree.cpp @@ -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) { diff --git a/src/helpers/SubsurfaceTree.hpp b/src/helpers/SubsurfaceTree.hpp index d6f04a58..7a8b8fb8 100644 --- a/src/helpers/SubsurfaceTree.hpp +++ b/src/helpers/SubsurfaceTree.hpp @@ -26,6 +26,8 @@ struct SSurfaceTreeNode { void* globalOffsetData; CWindow* pWindowOwner = nullptr; + Vector2D lastSize; + // bool operator==(const SSurfaceTreeNode& rhs) const { return pSurface == rhs.pSurface; diff --git a/src/helpers/VarList.hpp b/src/helpers/VarList.hpp index 60e9a551..d70f2c3f 100644 --- a/src/helpers/VarList.hpp +++ b/src/helpers/VarList.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include #include #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 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 m_vArgs; }; \ No newline at end of file diff --git a/src/helpers/Vector2D.cpp b/src/helpers/Vector2D.cpp index 118ba9d9..7bc6412d 100644 --- a/src/helpers/Vector2D.cpp +++ b/src/helpers/Vector2D.cpp @@ -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); +} diff --git a/src/helpers/Vector2D.hpp b/src/helpers/Vector2D.hpp index 3c5d4a2c..34b06862 100644 --- a/src/helpers/Vector2D.hpp +++ b/src/helpers/Vector2D.hpp @@ -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; }; diff --git a/src/helpers/WLClasses.cpp b/src/helpers/WLClasses.cpp index 1fa807fa..960145b9 100644 --- a/src/helpers/WLClasses.cpp +++ b/src/helpers/WLClasses.cpp @@ -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]); diff --git a/src/helpers/WLClasses.hpp b/src/helpers/WLClasses.hpp index d66ebdfd..be0fd005 100644 --- a/src/helpers/WLClasses.hpp +++ b/src/helpers/WLClasses.hpp @@ -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; + } +}; diff --git a/src/helpers/WLListener.cpp b/src/helpers/WLListener.cpp index 3cb051b7..402c999c 100644 --- a/src/helpers/WLListener.cpp +++ b/src/helpers/WLListener.cpp @@ -2,11 +2,18 @@ #include "MiscFunctions.hpp" #include #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 callback, void* pOwner) { @@ -50,4 +57,4 @@ void CHyprWLListener::initCallback(wl_signal* pSignal, std::functionm_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; diff --git a/src/helpers/WLSurface.hpp b/src/helpers/WLSurface.hpp index c5cdd37f..03714bbf 100644 --- a/src/helpers/WLSurface.hpp +++ b/src/helpers/WLSurface.hpp @@ -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; diff --git a/src/helpers/Watchdog.cpp b/src/helpers/Watchdog.cpp new file mode 100644 index 00000000..2a26ee29 --- /dev/null +++ b/src/helpers/Watchdog.cpp @@ -0,0 +1,59 @@ +#include "Watchdog.hpp" +#include +#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([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(); +} \ No newline at end of file diff --git a/src/helpers/Watchdog.hpp b/src/helpers/Watchdog.hpp new file mode 100644 index 00000000..7bb499d6 --- /dev/null +++ b/src/helpers/Watchdog.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include + +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 m_pWatchdog; + std::mutex m_mWatchdogMutex; + bool m_bNotified = false; + bool m_bExitThread = false; + std::condition_variable m_cvWatchdogCondition; +}; + +inline std::unique_ptr g_pWatchdog; \ No newline at end of file diff --git a/src/helpers/Workspace.cpp b/src/helpers/Workspace.cpp index 51d29e5e..213607d0 100644 --- a/src/helpers/Workspace.cpp +++ b/src/helpers/Workspace.cpp @@ -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); } } diff --git a/src/helpers/Workspace.hpp b/src/helpers/Workspace.hpp index 07b5694c..a0bbb815 100644 --- a/src/helpers/Workspace.hpp +++ b/src/helpers/Workspace.hpp @@ -4,9 +4,9 @@ #include #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); diff --git a/src/helpers/XWaylandStubs.hpp b/src/helpers/XWaylandStubs.hpp index 904867cf..89e0b41f 100644 --- a/src/helpers/XWaylandStubs.hpp +++ b/src/helpers/XWaylandStubs.hpp @@ -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, diff --git a/src/hyprerror/HyprError.cpp b/src/hyprerror/HyprError.cpp index b72f0589..6a91ba87 100644 --- a/src/hyprerror/HyprError.cpp +++ b/src/hyprerror/HyprError.cpp @@ -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; diff --git a/src/hyprerror/HyprError.hpp b/src/hyprerror/HyprError.hpp index ff4e8577..cee648e2 100644 --- a/src/hyprerror/HyprError.hpp +++ b/src/hyprerror/HyprError.hpp @@ -23,7 +23,7 @@ class CHyprError { bool m_bIsCreated = false; CTexture m_tTexture; CAnimatedVariable m_fFadeOpacity; - wlr_box m_bDamageBox = {0, 0, 0, 0}; + CBox m_bDamageBox = {0, 0, 0, 0}; bool m_bMonitorChanged = false; }; diff --git a/src/includes.hpp b/src/includes.hpp index c81ee9fe..961729ad 100644 --- a/src/includes.hpp +++ b/src/includes.hpp @@ -27,7 +27,7 @@ // pthread first because it uses class in a C++ way and XWayland includes that... #include -#define class _class +#define class _class #define namespace _namespace #define static #define delete delete_ @@ -47,7 +47,6 @@ extern "C" { #include #include #include -#include #include #include #include @@ -71,7 +70,6 @@ extern "C" { #include #include #include -#include #include #include #include @@ -105,6 +103,11 @@ extern "C" { #include #include #include +#include +#include +#include +#include +#include #include @@ -145,3 +148,5 @@ extern "C" { #endif #include "helpers/Vector2D.hpp" +#include "helpers/Box.hpp" +#include "SharedDefs.hpp" diff --git a/src/init/initHelpers.cpp b/src/init/initHelpers.cpp index 2a296d2e..2cc491d0 100644 --- a/src/init/initHelpers.cpp +++ b/src/init/initHelpers.cpp @@ -5,8 +5,8 @@ bool Init::isSudo() { } void Init::gainRealTime() { - const int minPrio = sched_get_priority_min(SCHED_RR); - int old_policy; + const int minPrio = sched_get_priority_min(SCHED_RR); + int old_policy; struct sched_param param; if (pthread_getschedparam(pthread_self(), &old_policy, ¶m)) { diff --git a/src/layout/DwindleLayout.cpp b/src/layout/DwindleLayout.cpp index fae4af52..76b9aeb4 100644 --- a/src/layout/DwindleLayout.cpp +++ b/src/layout/DwindleLayout.cpp @@ -4,15 +4,12 @@ void SDwindleNodeData::recalcSizePosRecursive(bool force, bool horizontalOverride, bool verticalOverride) { if (children[0]) { - - const auto REVERSESPLITRATIO = 2.f - splitRatio; - static auto* const PSMARTSPLIT = &g_pConfigManager->getConfigValuePtr("dwindle:smart_split")->intValue; static auto* const PPRESERVESPLIT = &g_pConfigManager->getConfigValuePtr("dwindle:preserve_split")->intValue; static auto* const PFLMULT = &g_pConfigManager->getConfigValuePtr("dwindle:split_width_multiplier")->floatValue; if (*PPRESERVESPLIT == 0 && *PSMARTSPLIT == 0) { - splitTop = size.y * *PFLMULT > size.x; + splitTop = box.h * *PFLMULT > box.w; } if (verticalOverride == true) @@ -24,16 +21,14 @@ void SDwindleNodeData::recalcSizePosRecursive(bool force, bool horizontalOverrid if (SPLITSIDE) { // split left/right - children[0]->position = position; - children[0]->size = Vector2D(size.x / 2.f * splitRatio, size.y); - children[1]->position = Vector2D(position.x + size.x / 2.f * splitRatio, position.y); - children[1]->size = Vector2D(size.x / 2.f * REVERSESPLITRATIO, size.y); + const float FIRSTSIZE = box.w / 2.0 * splitRatio; + children[0]->box = CBox{box.x, box.y, FIRSTSIZE, box.h}; + children[1]->box = CBox{box.x + FIRSTSIZE, box.y, box.w - FIRSTSIZE, box.h}; } else { // split top/bottom - children[0]->position = position; - children[0]->size = Vector2D(size.x, size.y / 2.f * splitRatio); - children[1]->position = Vector2D(position.x, position.y + size.y / 2.f * splitRatio); - children[1]->size = Vector2D(size.x, size.y / 2.f * REVERSESPLITRATIO); + const float FIRSTSIZE = box.h / 2.0 * splitRatio; + children[0]->box = CBox{box.x, box.y, box.w, FIRSTSIZE}; + children[1]->box = CBox{box.x, box.y + FIRSTSIZE, box.w, box.h - FIRSTSIZE}; } children[0]->recalcSizePosRecursive(force); @@ -110,16 +105,19 @@ void CHyprDwindleLayout::applyNodeDataToWindow(SDwindleNodeData* pNode, bool for } // for gaps outer - const bool DISPLAYLEFT = STICKS(pNode->position.x, PMONITOR->vecPosition.x + PMONITOR->vecReservedTopLeft.x); - const bool DISPLAYRIGHT = STICKS(pNode->position.x + pNode->size.x, PMONITOR->vecPosition.x + PMONITOR->vecSize.x - PMONITOR->vecReservedBottomRight.x); - const bool DISPLAYTOP = STICKS(pNode->position.y, PMONITOR->vecPosition.y + PMONITOR->vecReservedTopLeft.y); - const bool DISPLAYBOTTOM = STICKS(pNode->position.y + pNode->size.y, PMONITOR->vecPosition.y + PMONITOR->vecSize.y - PMONITOR->vecReservedBottomRight.y); + const bool DISPLAYLEFT = STICKS(pNode->box.x, PMONITOR->vecPosition.x + PMONITOR->vecReservedTopLeft.x); + const bool DISPLAYRIGHT = STICKS(pNode->box.x + pNode->box.w, PMONITOR->vecPosition.x + PMONITOR->vecSize.x - PMONITOR->vecReservedBottomRight.x); + const bool DISPLAYTOP = STICKS(pNode->box.y, PMONITOR->vecPosition.y + PMONITOR->vecReservedTopLeft.y); + const bool DISPLAYBOTTOM = STICKS(pNode->box.y + pNode->box.h, PMONITOR->vecPosition.y + PMONITOR->vecSize.y - PMONITOR->vecReservedBottomRight.y); const auto PWINDOW = pNode->pWindow; // get specific gaps and rules for this workspace, // if user specified them in config const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(g_pCompositor->getWorkspaceByID(PWINDOW->m_iWorkspaceID)); + if (PWINDOW->m_bIsFullscreen && !pNode->ignoreFullscreenChecks) + return; + PWINDOW->updateSpecialRenderData(); static auto* const PGAPSIN = &g_pConfigManager->getConfigValuePtr("general:gaps_in")->intValue; @@ -135,8 +133,11 @@ void CHyprDwindleLayout::applyNodeDataToWindow(SDwindleNodeData* pNode, bool for return; } - PWINDOW->m_vSize = pNode->size; - PWINDOW->m_vPosition = pNode->position; + CBox nodeBox = pNode->box; + nodeBox.round(); + + PWINDOW->m_vSize = nodeBox.size(); + PWINDOW->m_vPosition = nodeBox.pos(); const auto NODESONWORKSPACE = getNodesOnWorkspace(PWINDOW->m_iWorkspaceID); @@ -148,22 +149,20 @@ void CHyprDwindleLayout::applyNodeDataToWindow(SDwindleNodeData* pNode, bool for PWINDOW->m_sSpecialRenderData.rounding = false; PWINDOW->m_sSpecialRenderData.shadow = false; + PWINDOW->updateWindowDecos(); + const auto RESERVED = PWINDOW->getFullWindowReservedArea(); - const int BORDERSIZE = PWINDOW->getRealBorderSize(); + PWINDOW->m_vRealPosition = PWINDOW->m_vPosition + RESERVED.topLeft; + PWINDOW->m_vRealSize = PWINDOW->m_vSize - (RESERVED.topLeft + RESERVED.bottomRight); - PWINDOW->m_vRealPosition = PWINDOW->m_vPosition + Vector2D(BORDERSIZE, BORDERSIZE) + RESERVED.topLeft; - PWINDOW->m_vRealSize = PWINDOW->m_vSize - Vector2D(2 * BORDERSIZE, 2 * BORDERSIZE) - (RESERVED.topLeft + RESERVED.bottomRight); - - PWINDOW->updateWindowDecos(); + g_pXWaylandManager->setWindowSize(PWINDOW, PWINDOW->m_vRealSize.goalv()); return; } - const int BORDERSIZE = PWINDOW->getRealBorderSize(); - - auto calcPos = PWINDOW->m_vPosition + Vector2D(BORDERSIZE, BORDERSIZE); - auto calcSize = PWINDOW->m_vSize - Vector2D(2 * BORDERSIZE, 2 * BORDERSIZE); + auto calcPos = PWINDOW->m_vPosition; + auto calcSize = PWINDOW->m_vSize; const auto OFFSETTOPLEFT = Vector2D(DISPLAYLEFT ? gapsOut : gapsIn, DISPLAYTOP ? gapsOut : gapsIn); @@ -204,10 +203,13 @@ void CHyprDwindleLayout::applyNodeDataToWindow(SDwindleNodeData* pNode, bool for // if special, we adjust the coords a bit static auto* const PSCALEFACTOR = &g_pConfigManager->getConfigValuePtr("dwindle:special_scale_factor")->floatValue; - PWINDOW->m_vRealPosition = calcPos + (calcSize - calcSize * *PSCALEFACTOR) / 2.f; - PWINDOW->m_vRealSize = calcSize * *PSCALEFACTOR; + CBox wb = {calcPos + (calcSize - calcSize * *PSCALEFACTOR) / 2.f, calcSize * *PSCALEFACTOR}; + wb.round(); // avoid rounding mess - g_pXWaylandManager->setWindowSize(PWINDOW, calcSize * *PSCALEFACTOR); + PWINDOW->m_vRealPosition = wb.pos(); + PWINDOW->m_vRealSize = wb.size(); + + g_pXWaylandManager->setWindowSize(PWINDOW, wb.size()); } else { PWINDOW->m_vRealSize = calcSize; PWINDOW->m_vRealPosition = calcPos; @@ -252,10 +254,11 @@ void CHyprDwindleLayout::onWindowCreatedTiling(CWindow* pWindow, eDirection dire const auto MOUSECOORDS = m_vOverrideFocalPoint.value_or(g_pInputManager->getMouseCoordsInternal()); const auto MONFROMCURSOR = g_pCompositor->getMonitorFromVector(MOUSECOORDS); + const auto TARGETCOORDS = PMONITOR->ID == MONFROMCURSOR->ID && g_pCompositor->isPointOnReservedArea(MOUSECOORDS, PMONITOR) ? pWindow->middle() : MOUSECOORDS; if (PMONITOR->ID == MONFROMCURSOR->ID && (PNODE->workspaceID == PMONITOR->activeWorkspace || (g_pCompositor->isWorkspaceSpecial(PNODE->workspaceID) && PMONITOR->specialWorkspaceID)) && !*PUSEACTIVE) { - OPENINGON = getNodeFromWindow(g_pCompositor->vectorToWindowTiled(MOUSECOORDS)); + OPENINGON = getNodeFromWindow(g_pCompositor->vectorToWindowTiled(TARGETCOORDS)); // happens on reserved area if (!OPENINGON && g_pCompositor->getWindowsOnWorkspace(PNODE->workspaceID) > 0) @@ -266,7 +269,7 @@ void CHyprDwindleLayout::onWindowCreatedTiling(CWindow* pWindow, eDirection dire g_pCompositor->m_pLastWindow->m_iWorkspaceID == pWindow->m_iWorkspaceID && g_pCompositor->m_pLastWindow->m_bIsMapped) { OPENINGON = getNodeFromWindow(g_pCompositor->m_pLastWindow); } else { - OPENINGON = getNodeFromWindow(g_pCompositor->vectorToWindowTiled(MOUSECOORDS)); + OPENINGON = getNodeFromWindow(g_pCompositor->vectorToWindowTiled(TARGETCOORDS)); } if (!OPENINGON && g_pCompositor->getWindowsOnWorkspace(PNODE->workspaceID) > 0) @@ -282,7 +285,7 @@ void CHyprDwindleLayout::onWindowCreatedTiling(CWindow* pWindow, eDirection dire } // first, check if OPENINGON isn't too big. - const auto PREDSIZEMAX = OPENINGON ? Vector2D(OPENINGON->size.x, OPENINGON->size.y) : PMONITOR->vecSize; + const auto PREDSIZEMAX = OPENINGON ? Vector2D(OPENINGON->box.w, OPENINGON->box.h) : PMONITOR->vecSize; if (const auto MAXSIZE = g_pXWaylandManager->getMaxSizeForWindow(pWindow); MAXSIZE.x < PREDSIZEMAX.x || MAXSIZE.y < PREDSIZEMAX.y) { // we can't continue. make it floating. pWindow->m_bIsFloating = true; @@ -291,13 +294,6 @@ void CHyprDwindleLayout::onWindowCreatedTiling(CWindow* pWindow, eDirection dire return; } - const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(pWindow->m_iWorkspaceID); - - if (PWORKSPACE->m_bHasFullscreenWindow) { - const auto PFULLWINDOW = g_pCompositor->getFullscreenWindowOnWorkspace(PWORKSPACE->m_iID); - g_pCompositor->setWindowFullscreen(PFULLWINDOW, false, FULLSCREEN_FULL); - } - // last fail-safe to avoid duplicate fullscreens if ((!OPENINGON || OPENINGON->pWindow == pWindow) && getNodesOnWorkspace(PNODE->workspaceID) > 1) { for (auto& node : m_lDwindleNodesData) { @@ -310,8 +306,7 @@ void CHyprDwindleLayout::onWindowCreatedTiling(CWindow* pWindow, eDirection dire // if it's the first, it's easy. Make it fullscreen. if (!OPENINGON || OPENINGON->pWindow == pWindow) { - PNODE->position = PMONITOR->vecPosition + PMONITOR->vecReservedTopLeft; - PNODE->size = PMONITOR->vecSize - PMONITOR->vecReservedTopLeft - PMONITOR->vecReservedBottomRight; + PNODE->box = CBox{PMONITOR->vecPosition + PMONITOR->vecReservedTopLeft, PMONITOR->vecSize - PMONITOR->vecReservedTopLeft - PMONITOR->vecReservedBottomRight}; applyNodeDataToWindow(PNODE); @@ -320,39 +315,27 @@ void CHyprDwindleLayout::onWindowCreatedTiling(CWindow* pWindow, eDirection dire return; } - // if it's a group, add the window - if (OPENINGON->pWindow->m_sGroupData.pNextWindow // target is group - && !g_pKeybindManager->m_bGroupsLocked // global group lock disengaged - && ((pWindow->m_eGroupRules & GROUP_INVADE && pWindow->m_bFirstMap) // window ignore local group locks, or - || (!OPENINGON->pWindow->getGroupHead()->m_sGroupData.locked // target unlocked - && !(pWindow->m_sGroupData.pNextWindow && pWindow->getGroupHead()->m_sGroupData.locked))) // source unlocked or isn't group - && !pWindow->m_sGroupData.deny // source is not denied entry - && !(pWindow->m_eGroupRules & GROUP_BARRED && pWindow->m_bFirstMap) // group rule doesn't prevent adding window - && !m_vOverrideFocalPoint // we are not moving window - ) { - if (!pWindow->m_sGroupData.pNextWindow) - pWindow->m_dWindowDecorations.emplace_back(std::make_unique(pWindow)); + if (!m_vOverrideFocalPoint && g_pInputManager->m_bWasDraggingWindow) { + if (OPENINGON->pWindow->checkInputOnDecos(INPUT_TYPE_DRAG_END, MOUSECOORDS, pWindow)) + return; + } + // if it's a group, add the window + if (OPENINGON->pWindow->m_sGroupData.pNextWindow // target is group + && pWindow->canBeGroupedInto(OPENINGON->pWindow) && !m_vOverrideFocalPoint) { // we are not moving window m_lDwindleNodesData.remove(*PNODE); - const wlr_box box = OPENINGON->pWindow->getDecorationByType(DECORATION_GROUPBAR)->getWindowDecorationRegion().getExtents(); - if (wlr_box_contains_point(&box, MOUSECOORDS.x, MOUSECOORDS.y)) { // TODO: Deny when not using mouse - const int SIZE = OPENINGON->pWindow->getGroupSize(); - const int INDEX = (int)((MOUSECOORDS.x - box.x) * 2 * SIZE / box.width + 1) / 2 - 1; - CWindow* pWindowInsertAfter = OPENINGON->pWindow->getGroupWindowByIndex(INDEX); - pWindowInsertAfter->insertWindowToGroup(pWindow); - if (INDEX == -1) - std::swap(pWindow->m_sGroupData.pNextWindow->m_sGroupData.head, pWindow->m_sGroupData.head); - } else { - static const auto* USECURRPOS = &g_pConfigManager->getConfigValuePtr("misc:group_insert_after_current")->intValue; - (*USECURRPOS ? OPENINGON->pWindow : OPENINGON->pWindow->getGroupTail())->insertWindowToGroup(pWindow); - } + static const auto* USECURRPOS = &g_pConfigManager->getConfigValuePtr("group:insert_after_current")->intValue; + (*USECURRPOS ? OPENINGON->pWindow : OPENINGON->pWindow->getGroupTail())->insertWindowToGroup(pWindow); OPENINGON->pWindow->setGroupCurrent(pWindow); pWindow->applyGroupRules(); pWindow->updateWindowDecos(); recalculateWindow(pWindow); + if (!pWindow->getDecorationByType(DECORATION_GROUPBAR)) + pWindow->addWindowDeco(std::make_unique(pWindow)); + return; } @@ -362,8 +345,7 @@ void CHyprDwindleLayout::onWindowCreatedTiling(CWindow* pWindow, eDirection dire const auto NEWPARENT = &m_lDwindleNodesData.back(); // make the parent have the OPENINGON's stats - NEWPARENT->position = OPENINGON->position; - NEWPARENT->size = OPENINGON->size; + NEWPARENT->box = OPENINGON->box; NEWPARENT->workspaceID = OPENINGON->workspaceID; NEWPARENT->pParent = OPENINGON->pParent; NEWPARENT->isNode = true; // it is a node @@ -372,7 +354,7 @@ void CHyprDwindleLayout::onWindowCreatedTiling(CWindow* pWindow, eDirection dire const auto PWIDTHMULTIPLIER = &g_pConfigManager->getConfigValuePtr("dwindle:split_width_multiplier")->floatValue; // if cursor over first child, make it first, etc - const auto SIDEBYSIDE = NEWPARENT->size.x > NEWPARENT->size.y * *PWIDTHMULTIPLIER; + const auto SIDEBYSIDE = NEWPARENT->box.w > NEWPARENT->box.h * *PWIDTHMULTIPLIER; NEWPARENT->splitTop = !SIDEBYSIDE; static auto* const PFORCESPLIT = &g_pConfigManager->getConfigValuePtr("dwindle:force_split")->intValue; @@ -404,21 +386,21 @@ void CHyprDwindleLayout::onWindowCreatedTiling(CWindow* pWindow, eDirection dire if (*PERMANENTDIRECTIONOVERRIDE == 0) overrideDirection = DIRECTION_DEFAULT; } else if (*PSMARTSPLIT == 1) { - const auto tl = NEWPARENT->position; - const auto tr = NEWPARENT->position + Vector2D(NEWPARENT->size.x, 0); - const auto bl = NEWPARENT->position + Vector2D(0, NEWPARENT->size.y); - const auto br = NEWPARENT->position + NEWPARENT->size; - const auto cc = NEWPARENT->position + NEWPARENT->size / 2; + const auto tl = NEWPARENT->box.pos(); + const auto tr = NEWPARENT->box.pos() + Vector2D(NEWPARENT->box.w, 0); + const auto bl = NEWPARENT->box.pos() + Vector2D(0, NEWPARENT->box.h); + const auto br = NEWPARENT->box.pos() + NEWPARENT->box.size(); + const auto cc = NEWPARENT->box.pos() + NEWPARENT->box.size() / 2; - if (MOUSECOORDS.inTriangle(tl, tr, cc)) { + if (TARGETCOORDS.inTriangle(tl, tr, cc)) { NEWPARENT->splitTop = true; NEWPARENT->children[0] = PNODE; NEWPARENT->children[1] = OPENINGON; - } else if (MOUSECOORDS.inTriangle(tr, cc, br)) { + } else if (TARGETCOORDS.inTriangle(tr, cc, br)) { NEWPARENT->splitTop = false; NEWPARENT->children[0] = OPENINGON; NEWPARENT->children[1] = PNODE; - } else if (MOUSECOORDS.inTriangle(br, bl, cc)) { + } else if (TARGETCOORDS.inTriangle(br, bl, cc)) { NEWPARENT->splitTop = true; NEWPARENT->children[0] = OPENINGON; NEWPARENT->children[1] = PNODE; @@ -429,11 +411,9 @@ void CHyprDwindleLayout::onWindowCreatedTiling(CWindow* pWindow, eDirection dire } } else if (*PFORCESPLIT == 0 || !pWindow->m_bFirstMap) { if ((SIDEBYSIDE && - VECINRECT(MOUSECOORDS, NEWPARENT->position.x, NEWPARENT->position.y / *PWIDTHMULTIPLIER, NEWPARENT->position.x + NEWPARENT->size.x / 2.f, - NEWPARENT->position.y + NEWPARENT->size.y)) || + VECINRECT(TARGETCOORDS, NEWPARENT->box.x, NEWPARENT->box.y / *PWIDTHMULTIPLIER, NEWPARENT->box.x + NEWPARENT->box.w / 2.f, NEWPARENT->box.y + NEWPARENT->box.h)) || (!SIDEBYSIDE && - VECINRECT(MOUSECOORDS, NEWPARENT->position.x, NEWPARENT->position.y / *PWIDTHMULTIPLIER, NEWPARENT->position.x + NEWPARENT->size.x, - NEWPARENT->position.y + NEWPARENT->size.y / 2.f))) { + VECINRECT(TARGETCOORDS, NEWPARENT->box.x, NEWPARENT->box.y / *PWIDTHMULTIPLIER, NEWPARENT->box.x + NEWPARENT->box.w, NEWPARENT->box.y + NEWPARENT->box.h / 2.f))) { // we are hovering over the first node, make PNODE first. NEWPARENT->children[1] = OPENINGON; NEWPARENT->children[0] = PNODE; @@ -462,18 +442,14 @@ void CHyprDwindleLayout::onWindowCreatedTiling(CWindow* pWindow, eDirection dire } // Update the children - if (!verticalOverride && (NEWPARENT->size.x * *PWIDTHMULTIPLIER > NEWPARENT->size.y || horizontalOverride)) { + if (!verticalOverride && (NEWPARENT->box.w * *PWIDTHMULTIPLIER > NEWPARENT->box.h || horizontalOverride)) { // split left/right -> forced - OPENINGON->position = NEWPARENT->position; - OPENINGON->size = Vector2D(NEWPARENT->size.x / 2.f, NEWPARENT->size.y); - PNODE->position = Vector2D(NEWPARENT->position.x + NEWPARENT->size.x / 2.f, NEWPARENT->position.y); - PNODE->size = Vector2D(NEWPARENT->size.x / 2.f, NEWPARENT->size.y); + OPENINGON->box = {NEWPARENT->box.pos(), Vector2D(NEWPARENT->box.w / 2.f, NEWPARENT->box.h)}; + PNODE->box = {Vector2D(NEWPARENT->box.x + NEWPARENT->box.w / 2.f, NEWPARENT->box.y), Vector2D(NEWPARENT->box.w / 2.f, NEWPARENT->box.h)}; } else { // split top/bottom - OPENINGON->position = NEWPARENT->position; - OPENINGON->size = Vector2D(NEWPARENT->size.x, NEWPARENT->size.y / 2.f); - PNODE->position = Vector2D(NEWPARENT->position.x, NEWPARENT->position.y + NEWPARENT->size.y / 2.f); - PNODE->size = Vector2D(NEWPARENT->size.x, NEWPARENT->size.y / 2.f); + OPENINGON->box = {NEWPARENT->box.pos(), Vector2D(NEWPARENT->box.w, NEWPARENT->box.h / 2.f)}; + PNODE->box = {Vector2D(NEWPARENT->box.x, NEWPARENT->box.y + NEWPARENT->box.h / 2.f), Vector2D(NEWPARENT->box.w, NEWPARENT->box.h / 2.f)}; } OPENINGON->pParent = NEWPARENT; @@ -481,8 +457,8 @@ void CHyprDwindleLayout::onWindowCreatedTiling(CWindow* pWindow, eDirection dire NEWPARENT->recalcSizePosRecursive(false, horizontalOverride, verticalOverride); - applyNodeDataToWindow(PNODE); - applyNodeDataToWindow(OPENINGON); + recalculateMonitor(pWindow->m_iMonitorID); + pWindow->applyGroupRules(); } @@ -510,9 +486,8 @@ void CHyprDwindleLayout::onWindowRemovedTiling(CWindow* pWindow) { const auto PSIBLING = PPARENT->children[0] == PNODE ? PPARENT->children[1] : PPARENT->children[0]; - PSIBLING->position = PPARENT->position; - PSIBLING->size = PPARENT->size; - PSIBLING->pParent = PPARENT->pParent; + PSIBLING->box = PPARENT->box; + PSIBLING->pParent = PPARENT->pParent; if (PPARENT->pParent != nullptr) { if (PPARENT->pParent->children[0] == PPARENT) { @@ -551,8 +526,7 @@ void CHyprDwindleLayout::recalculateMonitor(const int& monid) { const auto TOPNODE = getMasterNodeOnWorkspace(PMONITOR->specialWorkspaceID); if (TOPNODE && PMONITOR) { - TOPNODE->position = PMONITOR->vecPosition + PMONITOR->vecReservedTopLeft; - TOPNODE->size = PMONITOR->vecSize - PMONITOR->vecReservedTopLeft - PMONITOR->vecReservedBottomRight; + TOPNODE->box = {PMONITOR->vecPosition + PMONITOR->vecReservedTopLeft, PMONITOR->vecSize - PMONITOR->vecReservedTopLeft - PMONITOR->vecReservedBottomRight}; TOPNODE->recalcSizePosRecursive(); } } @@ -567,11 +541,11 @@ void CHyprDwindleLayout::recalculateMonitor(const int& monid) { } else if (PWORKSPACE->m_efFullscreenMode == FULLSCREEN_MAXIMIZED) { SDwindleNodeData fakeNode; fakeNode.pWindow = PFULLWINDOW; - fakeNode.position = PMONITOR->vecPosition + PMONITOR->vecReservedTopLeft; - fakeNode.size = PMONITOR->vecSize - PMONITOR->vecReservedTopLeft - PMONITOR->vecReservedBottomRight; + fakeNode.box = {PMONITOR->vecPosition + PMONITOR->vecReservedTopLeft, PMONITOR->vecSize - PMONITOR->vecReservedTopLeft - PMONITOR->vecReservedBottomRight}; fakeNode.workspaceID = PWORKSPACE->m_iID; - PFULLWINDOW->m_vPosition = fakeNode.position; - PFULLWINDOW->m_vSize = fakeNode.size; + PFULLWINDOW->m_vPosition = fakeNode.box.pos(); + PFULLWINDOW->m_vSize = fakeNode.box.size(); + fakeNode.ignoreFullscreenChecks = true; applyNodeDataToWindow(&fakeNode); } @@ -582,8 +556,7 @@ void CHyprDwindleLayout::recalculateMonitor(const int& monid) { const auto TOPNODE = getMasterNodeOnWorkspace(PMONITOR->activeWorkspace); if (TOPNODE && PMONITOR) { - TOPNODE->position = PMONITOR->vecPosition + PMONITOR->vecReservedTopLeft; - TOPNODE->size = PMONITOR->vecSize - PMONITOR->vecReservedTopLeft - PMONITOR->vecReservedBottomRight; + TOPNODE->box = {PMONITOR->vecPosition + PMONITOR->vecReservedTopLeft, PMONITOR->vecSize - PMONITOR->vecReservedTopLeft - PMONITOR->vecReservedBottomRight}; TOPNODE->recalcSizePosRecursive(); } } @@ -628,7 +601,7 @@ void CHyprDwindleLayout::resizeActiveWindow(const Vector2D& pixResize, eRectCorn m_PseudoDragFlags.started = true; const auto pseudoSize = PWINDOW->m_vRealSize.goalv(); - const auto mouseOffset = g_pInputManager->getMouseCoordsInternal() - (PNODE->position + ((PNODE->size / 2) - (pseudoSize / 2))); + const auto mouseOffset = g_pInputManager->getMouseCoordsInternal() - (PNODE->box.pos() + ((PNODE->box.size() / 2) - (pseudoSize / 2))); if (mouseOffset.x > 0 && mouseOffset.x < pseudoSize.x && mouseOffset.y > 0 && mouseOffset.y < pseudoSize.y) { m_PseudoDragFlags.pseudo = true; @@ -651,8 +624,8 @@ void CHyprDwindleLayout::resizeActiveWindow(const Vector2D& pixResize, eRectCorn else PWINDOW->m_vPseudoSize.y -= pixResize.y * 2; - PWINDOW->m_vPseudoSize.x = std::clamp(PWINDOW->m_vPseudoSize.x, 30.0, PNODE->size.x); - PWINDOW->m_vPseudoSize.y = std::clamp(PWINDOW->m_vPseudoSize.y, 30.0, PNODE->size.y); + PWINDOW->m_vPseudoSize.x = std::clamp(PWINDOW->m_vPseudoSize.x, 30.0, PNODE->box.w); + PWINDOW->m_vPseudoSize.y = std::clamp(PWINDOW->m_vPseudoSize.y, 30.0, PNODE->box.h); PWINDOW->m_vLastFloatingSize = PWINDOW->m_vPseudoSize; PNODE->recalcSizePosRecursive(*PANIMATE == 0); @@ -699,30 +672,30 @@ void CHyprDwindleLayout::resizeActiveWindow(const Vector2D& pixResize, eRectCorn } if (PHOUTER) { - PHOUTER->pParent->splitRatio = std::clamp(PHOUTER->pParent->splitRatio + allowedMovement.x * 2.f / PHOUTER->pParent->size.x, 0.1, 1.9); + PHOUTER->pParent->splitRatio = std::clamp(PHOUTER->pParent->splitRatio + allowedMovement.x * 2.f / PHOUTER->pParent->box.w, 0.1, 1.9); if (PHINNER) { - const auto ORIGINAL = PHINNER->size.x; + const auto ORIGINAL = PHINNER->box.w; PHOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0); if (PHINNER->pParent->children[0] == PHINNER) - PHINNER->pParent->splitRatio = std::clamp((ORIGINAL - allowedMovement.x) / PHINNER->pParent->size.x * 2.f, 0.1, 1.9); + PHINNER->pParent->splitRatio = std::clamp((ORIGINAL - allowedMovement.x) / PHINNER->pParent->box.w * 2.f, 0.1, 1.9); else - PHINNER->pParent->splitRatio = std::clamp(2 - (ORIGINAL + allowedMovement.x) / PHINNER->pParent->size.x * 2.f, 0.1, 1.9); + PHINNER->pParent->splitRatio = std::clamp(2 - (ORIGINAL + allowedMovement.x) / PHINNER->pParent->box.w * 2.f, 0.1, 1.9); PHINNER->pParent->recalcSizePosRecursive(*PANIMATE == 0); } else PHOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0); } if (PVOUTER) { - PVOUTER->pParent->splitRatio = std::clamp(PVOUTER->pParent->splitRatio + allowedMovement.y * 2.f / PVOUTER->pParent->size.y, 0.1, 1.9); + PVOUTER->pParent->splitRatio = std::clamp(PVOUTER->pParent->splitRatio + allowedMovement.y * 2.f / PVOUTER->pParent->box.h, 0.1, 1.9); if (PVINNER) { - const auto ORIGINAL = PVINNER->size.y; + const auto ORIGINAL = PVINNER->box.h; PVOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0); if (PVINNER->pParent->children[0] == PVINNER) - PVINNER->pParent->splitRatio = std::clamp((ORIGINAL - allowedMovement.y) / PVINNER->pParent->size.y * 2.f, 0.1, 1.9); + PVINNER->pParent->splitRatio = std::clamp((ORIGINAL - allowedMovement.y) / PVINNER->pParent->box.h * 2.f, 0.1, 1.9); else - PVINNER->pParent->splitRatio = std::clamp(2 - (ORIGINAL + allowedMovement.y) / PVINNER->pParent->size.y * 2.f, 0.1, 1.9); + PVINNER->pParent->splitRatio = std::clamp(2 - (ORIGINAL + allowedMovement.y) / PVINNER->pParent->box.h * 2.f, 0.1, 1.9); PVINNER->pParent->recalcSizePosRecursive(*PANIMATE == 0); } else PVOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0); @@ -742,11 +715,11 @@ void CHyprDwindleLayout::resizeActiveWindow(const Vector2D& pixResize, eRectCorn // No parent means we have only 2 windows, and thus one axis of freedom if (!PPARENT2) { if (PARENTSIDEBYSIDE) { - allowedMovement.x *= 2.f / PPARENT->size.x; + allowedMovement.x *= 2.f / PPARENT->box.w; PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.x, 0.1, 1.9); PPARENT->recalcSizePosRecursive(*PANIMATE == 0); } else { - allowedMovement.y *= 2.f / PPARENT->size.y; + allowedMovement.y *= 2.f / PPARENT->box.h; PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.y, 0.1, 1.9); PPARENT->recalcSizePosRecursive(*PANIMATE == 0); } @@ -761,11 +734,11 @@ void CHyprDwindleLayout::resizeActiveWindow(const Vector2D& pixResize, eRectCorn // no parent, one axis of freedom if (!PPARENT2) { if (PARENTSIDEBYSIDE) { - allowedMovement.x *= 2.f / PPARENT->size.x; + allowedMovement.x *= 2.f / PPARENT->box.w; PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.x, 0.1, 1.9); PPARENT->recalcSizePosRecursive(*PANIMATE == 0); } else { - allowedMovement.y *= 2.f / PPARENT->size.y; + allowedMovement.y *= 2.f / PPARENT->box.h; PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.y, 0.1, 1.9); PPARENT->recalcSizePosRecursive(*PANIMATE == 0); } @@ -777,8 +750,8 @@ void CHyprDwindleLayout::resizeActiveWindow(const Vector2D& pixResize, eRectCorn const auto SIDECONTAINER = PARENTSIDEBYSIDE ? PPARENT : PPARENT2; const auto TOPCONTAINER = PARENTSIDEBYSIDE ? PPARENT2 : PPARENT; - allowedMovement.x *= 2.f / SIDECONTAINER->size.x; - allowedMovement.y *= 2.f / TOPCONTAINER->size.y; + allowedMovement.x *= 2.f / SIDECONTAINER->box.w; + allowedMovement.y *= 2.f / TOPCONTAINER->box.h; SIDECONTAINER->splitRatio = std::clamp(SIDECONTAINER->splitRatio + allowedMovement.x, 0.1, 1.9); TOPCONTAINER->splitRatio = std::clamp(TOPCONTAINER->splitRatio + allowedMovement.y, 0.1, 1.9); @@ -807,6 +780,9 @@ void CHyprDwindleLayout::fullscreenRequestForWindow(CWindow* pWindow, eFullscree pWindow->m_bIsFullscreen = on; PWORKSPACE->m_bHasFullscreenWindow = !PWORKSPACE->m_bHasFullscreenWindow; + pWindow->updateDynamicRules(); + pWindow->updateWindowDecos(); + g_pEventManager->postEvent(SHyprIPCEvent{"fullscreen", std::to_string((int)on)}); EMIT_HOOK_EVENT("fullscreen", pWindow); @@ -846,13 +822,12 @@ void CHyprDwindleLayout::fullscreenRequestForWindow(CWindow* pWindow, eFullscree SDwindleNodeData fakeNode; fakeNode.pWindow = pWindow; - fakeNode.position = PMONITOR->vecPosition + PMONITOR->vecReservedTopLeft; - fakeNode.size = PMONITOR->vecSize - PMONITOR->vecReservedTopLeft - PMONITOR->vecReservedBottomRight; + fakeNode.box = {PMONITOR->vecPosition + PMONITOR->vecReservedTopLeft, PMONITOR->vecSize - PMONITOR->vecReservedTopLeft - PMONITOR->vecReservedBottomRight}; fakeNode.workspaceID = pWindow->m_iWorkspaceID; - pWindow->m_vPosition = fakeNode.position; - pWindow->m_vSize = fakeNode.size; + pWindow->m_vPosition = fakeNode.box.pos(); + pWindow->m_vSize = fakeNode.box.size(); - applyNodeDataToWindow(&fakeNode); + applyNodeDataToWindow(&fakeNode, true); } } @@ -962,17 +937,15 @@ void CHyprDwindleLayout::switchWindows(CWindow* pWindow, CWindow* pWindow2) { } if (ACTIVE1) { - ACTIVE1->position = PNODE->position; - ACTIVE1->size = PNODE->size; - ACTIVE1->pWindow->m_vPosition = ACTIVE1->position; - ACTIVE1->pWindow->m_vSize = ACTIVE1->size; + ACTIVE1->box = PNODE->box; + ACTIVE1->pWindow->m_vPosition = ACTIVE1->box.pos(); + ACTIVE1->pWindow->m_vSize = ACTIVE1->box.size(); } if (ACTIVE2) { - ACTIVE2->position = PNODE2->position; - ACTIVE2->size = PNODE2->size; - ACTIVE2->pWindow->m_vPosition = ACTIVE2->position; - ACTIVE2->pWindow->m_vSize = ACTIVE2->size; + ACTIVE2->box = PNODE2->box; + ACTIVE2->pWindow->m_vPosition = ACTIVE2->box.pos(); + ACTIVE2->pWindow->m_vSize = ACTIVE2->box.size(); } g_pHyprRenderer->damageWindow(pWindow); @@ -1067,7 +1040,7 @@ std::string CHyprDwindleLayout::getLayoutName() { void CHyprDwindleLayout::onEnable() { for (auto& w : g_pCompositor->m_vWindows) { - if (w->m_bIsFloating || !w->m_bMappedX11 || !w->m_bIsMapped || w->isHidden()) + if (w->m_bIsFloating || !w->m_bIsMapped || w->isHidden()) continue; onWindowCreatedTiling(w.get()); diff --git a/src/layout/DwindleLayout.hpp b/src/layout/DwindleLayout.hpp index 993f8cb7..c2d47d88 100644 --- a/src/layout/DwindleLayout.hpp +++ b/src/layout/DwindleLayout.hpp @@ -9,7 +9,7 @@ #include class CHyprDwindleLayout; -enum eFullscreenMode : uint8_t; +enum eFullscreenMode : int8_t; struct SDwindleNodeData { SDwindleNodeData* pParent = nullptr; @@ -21,8 +21,7 @@ struct SDwindleNodeData { bool splitTop = false; // for preserve_split - Vector2D position; - Vector2D size; + CBox box = {0}; int workspaceID = -1; @@ -30,10 +29,12 @@ struct SDwindleNodeData { bool valid = true; + bool ignoreFullscreenChecks = false; + // For list lookup bool operator==(const SDwindleNodeData& rhs) const { - return pWindow == rhs.pWindow && workspaceID == rhs.workspaceID && position == rhs.position && size == rhs.size && pParent == rhs.pParent && - children[0] == rhs.children[0] && children[1] == rhs.children[1]; + return pWindow == rhs.pWindow && workspaceID == rhs.workspaceID && box == rhs.box && pParent == rhs.pParent && children[0] == rhs.children[0] && + children[1] == rhs.children[1]; } void recalcSizePosRecursive(bool force = false, bool horizontalOverride = false, bool verticalOverride = false); @@ -94,7 +95,7 @@ struct std::formatter : std::formatter { auto out = ctx.out(); if (!node) return std::format_to(out, "[Node nullptr]"); - std::format_to(out, "[Node {:x}: workspace: {}, pos: {:j2}, size: {:j2}", (uintptr_t)node, node->workspaceID, node->position, node->size); + std::format_to(out, "[Node {:x}: workspace: {}, pos: {:j2}, size: {:j2}", (uintptr_t)node, node->workspaceID, node->box.pos(), node->box.size()); if (!node->isNode && node->pWindow) std::format_to(out, ", window: {:x}", node->pWindow); return std::format_to(out, "]"); diff --git a/src/layout/IHyprLayout.cpp b/src/layout/IHyprLayout.cpp index 61bc2266..2a8fdf53 100644 --- a/src/layout/IHyprLayout.cpp +++ b/src/layout/IHyprLayout.cpp @@ -1,12 +1,13 @@ #include "IHyprLayout.hpp" #include "../defines.hpp" #include "../Compositor.hpp" +#include "../render/decorations/CHyprGroupBarDecoration.hpp" void IHyprLayout::onWindowCreated(CWindow* pWindow, eDirection direction) { if (pWindow->m_bIsFloating) { onWindowCreatedFloating(pWindow); } else { - wlr_box desiredGeometry = {0}; + CBox desiredGeometry = {}; g_pXWaylandManager->getGeometryForWindow(pWindow, &desiredGeometry); if (desiredGeometry.width <= 5 || desiredGeometry.height <= 5) { @@ -35,15 +36,15 @@ void IHyprLayout::onWindowRemoved(CWindow* pWindow) { const auto WINDOWISVISIBLE = pWindow->getGroupCurrent() == pWindow; if (WINDOWISVISIBLE) - PWINDOWPREV->setGroupCurrent(PWINDOWPREV); + PWINDOWPREV->setGroupCurrent(pWindow->m_sGroupData.head ? pWindow->m_sGroupData.pNextWindow : PWINDOWPREV); PWINDOWPREV->m_sGroupData.pNextWindow = pWindow->m_sGroupData.pNextWindow; pWindow->m_sGroupData.pNextWindow = nullptr; if (pWindow->m_sGroupData.head) { - std::swap(PWINDOWPREV->m_sGroupData.head, pWindow->m_sGroupData.head); - std::swap(PWINDOWPREV->m_sGroupData.locked, pWindow->m_sGroupData.locked); + std::swap(PWINDOWPREV->m_sGroupData.pNextWindow->m_sGroupData.head, pWindow->m_sGroupData.head); + std::swap(PWINDOWPREV->m_sGroupData.pNextWindow->m_sGroupData.locked, pWindow->m_sGroupData.locked); } if (pWindow == m_pLastTiledWindow) @@ -75,7 +76,7 @@ void IHyprLayout::onWindowRemovedFloating(CWindow* pWindow) { void IHyprLayout::onWindowCreatedFloating(CWindow* pWindow) { - wlr_box desiredGeometry = {0}; + CBox desiredGeometry = {0}; g_pXWaylandManager->getGeometryForWindow(pWindow, &desiredGeometry); const auto PMONITOR = g_pCompositor->getMonitorFromID(pWindow->m_iMonitorID); @@ -162,6 +163,9 @@ void IHyprLayout::onWindowCreatedFloating(CWindow* pWindow) { g_pXWaylandManager->setWindowSize(pWindow, pWindow->m_vRealSize.goalv()); g_pCompositor->changeWindowZOrder(pWindow, true); + } else { + pWindow->m_vPendingReportedSize = pWindow->m_vRealSize.goalv(); + pWindow->m_vReportedSize = pWindow->m_vPendingReportedSize; } } @@ -254,17 +258,38 @@ void IHyprLayout::onEndDragWindow() { g_pInputManager->unsetCursorImage(); g_pInputManager->currentlyDraggedWindow = nullptr; + g_pInputManager->m_bWasDraggingWindow = true; if (DRAGGINGWINDOW->m_bDraggingTiled) { DRAGGINGWINDOW->m_bIsFloating = false; g_pInputManager->refocus(); changeWindowFloatingMode(DRAGGINGWINDOW); DRAGGINGWINDOW->m_vLastFloatingSize = m_vDraggingWindowOriginalFloatSize; + } else if (g_pInputManager->dragMode == MBIND_MOVE) { + g_pHyprRenderer->damageWindow(DRAGGINGWINDOW); + const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal(); + CWindow* pWindow = g_pCompositor->vectorToWindowIdeal(MOUSECOORDS, DRAGGINGWINDOW); + + if (pWindow && pWindow->m_bIsFloating) { + if (pWindow->checkInputOnDecos(INPUT_TYPE_DRAG_END, MOUSECOORDS, DRAGGINGWINDOW)) + return; + + if (pWindow->m_sGroupData.pNextWindow && DRAGGINGWINDOW->canBeGroupedInto(pWindow)) { + static const auto* USECURRPOS = &g_pConfigManager->getConfigValuePtr("group:insert_after_current")->intValue; + (*USECURRPOS ? pWindow : pWindow->getGroupTail())->insertWindowToGroup(DRAGGINGWINDOW); + pWindow->setGroupCurrent(DRAGGINGWINDOW); + DRAGGINGWINDOW->updateWindowDecos(); + + if (!DRAGGINGWINDOW->getDecorationByType(DECORATION_GROUPBAR)) + DRAGGINGWINDOW->addWindowDeco(std::make_unique(DRAGGINGWINDOW)); + } + } } g_pHyprRenderer->damageWindow(DRAGGINGWINDOW); - g_pCompositor->focusWindow(DRAGGINGWINDOW); + + g_pInputManager->m_bWasDraggingWindow = false; } void IHyprLayout::onMouseMove(const Vector2D& mousePos) { @@ -301,11 +326,13 @@ void IHyprLayout::onMouseMove(const Vector2D& mousePos) { if (g_pInputManager->dragMode == MBIND_MOVE) { - if (*PANIMATEMOUSE) { - DRAGGINGWINDOW->m_vRealPosition = m_vBeginDragPositionXY + DELTA; - } else { - DRAGGINGWINDOW->m_vRealPosition.setValueAndWarp(m_vBeginDragPositionXY + DELTA); - } + CBox wb = {m_vBeginDragPositionXY + DELTA, DRAGGINGWINDOW->m_vRealSize.goalv()}; + wb.round(); + + if (*PANIMATEMOUSE) + DRAGGINGWINDOW->m_vRealPosition = wb.pos(); + else + DRAGGINGWINDOW->m_vRealPosition.setValueAndWarp(wb.pos()); g_pXWaylandManager->setWindowSize(DRAGGINGWINDOW, DRAGGINGWINDOW->m_vRealSize.goalv()); } else if (g_pInputManager->dragMode == MBIND_RESIZE || g_pInputManager->dragMode == MBIND_RESIZE_FORCE_RATIO || g_pInputManager->dragMode == MBIND_RESIZE_BLOCK_RATIO) { @@ -357,12 +384,15 @@ void IHyprLayout::onMouseMove(const Vector2D& mousePos) { else if (m_eGrabbedCorner == CORNER_BOTTOMLEFT) newPos = newPos + Vector2D((m_vBeginDragSizeXY - newSize).x, 0); + CBox wb = {newPos, newSize}; + wb.round(); + if (*PANIMATE) { - DRAGGINGWINDOW->m_vRealSize = newSize; - DRAGGINGWINDOW->m_vRealPosition = newPos; + DRAGGINGWINDOW->m_vRealSize = wb.size(); + DRAGGINGWINDOW->m_vRealPosition = wb.pos(); } else { - DRAGGINGWINDOW->m_vRealSize.setValueAndWarp(newSize); - DRAGGINGWINDOW->m_vRealPosition.setValueAndWarp(newPos); + DRAGGINGWINDOW->m_vRealSize.setValueAndWarp(wb.size()); + DRAGGINGWINDOW->m_vRealPosition.setValueAndWarp(wb.pos()); } g_pXWaylandManager->setWindowSize(DRAGGINGWINDOW, DRAGGINGWINDOW->m_vRealSize.goalv()); @@ -416,7 +446,8 @@ void IHyprLayout::changeWindowFloatingMode(CWindow* pWindow) { const auto PSAVEDSIZE = pWindow->m_vRealSize.goalv(); // if the window is pseudo, update its size - pWindow->m_vPseudoSize = pWindow->m_vRealSize.goalv(); + if (!pWindow->m_bDraggingTiled) + pWindow->m_vPseudoSize = pWindow->m_vRealSize.goalv(); pWindow->m_vLastFloatingSize = PSAVEDSIZE; @@ -438,16 +469,18 @@ void IHyprLayout::changeWindowFloatingMode(CWindow* pWindow) { g_pCompositor->changeWindowZOrder(pWindow, true); + CBox wb = {pWindow->m_vRealPosition.goalv() + (pWindow->m_vRealSize.goalv() - pWindow->m_vLastFloatingSize) / 2.f, pWindow->m_vLastFloatingSize}; + wb.round(); + if (DELTALESSTHAN(pWindow->m_vRealSize.vec().x, pWindow->m_vLastFloatingSize.x, 10) && DELTALESSTHAN(pWindow->m_vRealSize.vec().y, pWindow->m_vLastFloatingSize.y, 10)) { - pWindow->m_vRealPosition = pWindow->m_vRealPosition.goalv() + (pWindow->m_vRealSize.goalv() - pWindow->m_vLastFloatingSize) / 2.f + Vector2D{10, 10}; - pWindow->m_vRealSize = pWindow->m_vLastFloatingSize - Vector2D{20, 20}; + wb = {wb.pos() + Vector2D{10, 10}, wb.size() - Vector2D{20, 20}}; } - pWindow->m_vRealPosition = pWindow->m_vRealPosition.goalv() + (pWindow->m_vRealSize.goalv() - pWindow->m_vLastFloatingSize) / 2.f; - pWindow->m_vRealSize = pWindow->m_vLastFloatingSize; + pWindow->m_vRealPosition = wb.pos(); + pWindow->m_vRealSize = wb.size(); - pWindow->m_vSize = pWindow->m_vRealSize.goalv(); - pWindow->m_vPosition = pWindow->m_vRealPosition.goalv(); + pWindow->m_vSize = wb.pos(); + pWindow->m_vPosition = wb.size(); g_pHyprRenderer->damageMonitor(g_pCompositor->getMonitorFromID(pWindow->m_iMonitorID)); @@ -526,13 +559,19 @@ CWindow* IHyprLayout::getNextWindowCandidate(CWindow* pWindow) { } // if it was a tiled window, we first try to find the window that will replace it. - const auto PWINDOWCANDIDATE = g_pCompositor->vectorToWindowIdeal(pWindow->middle()); + auto pWindowCandidate = g_pCompositor->vectorToWindowIdeal(pWindow->middle()); - if (!PWINDOWCANDIDATE || pWindow == PWINDOWCANDIDATE || !PWINDOWCANDIDATE->m_bIsMapped || PWINDOWCANDIDATE->isHidden() || PWINDOWCANDIDATE->m_bX11ShouldntFocus || - PWINDOWCANDIDATE->m_iX11Type == 2 || PWINDOWCANDIDATE->m_iMonitorID != g_pCompositor->m_pLastMonitor->ID) + if (!pWindowCandidate) + pWindowCandidate = g_pCompositor->getTopLeftWindowOnWorkspace(pWindow->m_iWorkspaceID); + + if (!pWindowCandidate) + pWindowCandidate = g_pCompositor->getFirstWindowOnWorkspace(pWindow->m_iWorkspaceID); + + if (!pWindowCandidate || pWindow == pWindowCandidate || !pWindowCandidate->m_bIsMapped || pWindowCandidate->isHidden() || pWindowCandidate->m_bX11ShouldntFocus || + pWindowCandidate->m_iX11Type == 2 || pWindowCandidate->m_iMonitorID != g_pCompositor->m_pLastMonitor->ID) return nullptr; - return PWINDOWCANDIDATE; + return pWindowCandidate; } bool IHyprLayout::isWindowReachable(CWindow* pWindow) { diff --git a/src/layout/IHyprLayout.hpp b/src/layout/IHyprLayout.hpp index 15acbc1d..5c74078e 100644 --- a/src/layout/IHyprLayout.hpp +++ b/src/layout/IHyprLayout.hpp @@ -13,7 +13,7 @@ struct SLayoutMessageHeader { CWindow* pWindow = nullptr; }; -enum eFullscreenMode : uint8_t; +enum eFullscreenMode : int8_t; enum eRectCorner { CORNER_NONE = 0, @@ -25,7 +25,7 @@ enum eRectCorner { enum eDirection { DIRECTION_DEFAULT = -1, - DIRECTION_UP = 0, + DIRECTION_UP = 0, DIRECTION_RIGHT, DIRECTION_DOWN, DIRECTION_LEFT diff --git a/src/layout/MasterLayout.cpp b/src/layout/MasterLayout.cpp index c1dd37dc..9a76801c 100644 --- a/src/layout/MasterLayout.cpp +++ b/src/layout/MasterLayout.cpp @@ -42,17 +42,24 @@ SMasterWorkspaceData* CHyprMasterLayout::getMasterWorkspaceData(const int& ws) { const auto PWORKSPACEDATA = &m_lMasterWorkspacesData.emplace_back(); PWORKSPACEDATA->workspaceID = ws; const auto orientation = &g_pConfigManager->getConfigValuePtr("master:orientation")->strValue; - if (*orientation == "top") { + const auto layoutoptsForWs = g_pConfigManager->getWorkspaceRuleFor(g_pCompositor->getWorkspaceByID(ws)).layoutopts; + auto orientationForWs = *orientation; + + if (layoutoptsForWs.contains("orientation")) + orientationForWs = layoutoptsForWs.at("orientation"); + + if (orientationForWs == "top") { PWORKSPACEDATA->orientation = ORIENTATION_TOP; - } else if (*orientation == "right") { + } else if (orientationForWs == "right") { PWORKSPACEDATA->orientation = ORIENTATION_RIGHT; - } else if (*orientation == "bottom") { + } else if (orientationForWs == "bottom") { PWORKSPACEDATA->orientation = ORIENTATION_BOTTOM; - } else if (*orientation == "left") { - PWORKSPACEDATA->orientation = ORIENTATION_LEFT; - } else { + } else if (orientationForWs == "center") { PWORKSPACEDATA->orientation = ORIENTATION_CENTER; + } else { + PWORKSPACEDATA->orientation = ORIENTATION_LEFT; } + return PWORKSPACEDATA; } @@ -94,45 +101,98 @@ void CHyprMasterLayout::onWindowCreatedTiling(CWindow* pWindow, eDirection direc const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal(); - // if it's a group, add the window - if (OPENINGON && OPENINGON != PNODE && OPENINGON->pWindow->m_sGroupData.pNextWindow // target is group - && !g_pKeybindManager->m_bGroupsLocked // global group lock disengaged - && ((pWindow->m_eGroupRules & GROUP_INVADE && pWindow->m_bFirstMap) // window ignore local group locks, or - || (!OPENINGON->pWindow->getGroupHead()->m_sGroupData.locked // target unlocked - && !(pWindow->m_sGroupData.pNextWindow && pWindow->getGroupHead()->m_sGroupData.locked))) // source unlocked or isn't group - && !pWindow->m_sGroupData.deny // source is not denied entry - && !(pWindow->m_eGroupRules & GROUP_BARRED) // group rule doesn't prevent adding window - ) { + if (g_pInputManager->m_bWasDraggingWindow && OPENINGON) { + if (OPENINGON->pWindow->checkInputOnDecos(INPUT_TYPE_DRAG_END, MOUSECOORDS, pWindow)) + return; + } - if (!pWindow->m_sGroupData.pNextWindow) - pWindow->m_dWindowDecorations.emplace_back(std::make_unique(pWindow)); + // if it's a group, add the window + if (OPENINGON && OPENINGON != PNODE && OPENINGON->pWindow->m_sGroupData.pNextWindow // target is group + && pWindow->canBeGroupedInto(OPENINGON->pWindow)) { m_lMasterNodesData.remove(*PNODE); - const wlr_box box = OPENINGON->pWindow->getDecorationByType(DECORATION_GROUPBAR)->getWindowDecorationRegion().getExtents(); - if (wlr_box_contains_point(&box, MOUSECOORDS.x, MOUSECOORDS.y)) { // TODO: Deny when not using mouse - const int SIZE = OPENINGON->pWindow->getGroupSize(); - const int INDEX = (int)((MOUSECOORDS.x - box.x) * 2 * SIZE / box.width + 1) / 2 - 1; - CWindow* pWindowInsertAfter = OPENINGON->pWindow->getGroupWindowByIndex(INDEX); - pWindowInsertAfter->insertWindowToGroup(pWindow); - if (INDEX == -1) - std::swap(pWindow->m_sGroupData.pNextWindow->m_sGroupData.head, pWindow->m_sGroupData.head); - } else { - static const auto* USECURRPOS = &g_pConfigManager->getConfigValuePtr("misc:group_insert_after_current")->intValue; - (*USECURRPOS ? OPENINGON->pWindow : OPENINGON->pWindow->getGroupTail())->insertWindowToGroup(pWindow); - } + static const auto* USECURRPOS = &g_pConfigManager->getConfigValuePtr("group:insert_after_current")->intValue; + (*USECURRPOS ? OPENINGON->pWindow : OPENINGON->pWindow->getGroupTail())->insertWindowToGroup(pWindow); OPENINGON->pWindow->setGroupCurrent(pWindow); pWindow->applyGroupRules(); pWindow->updateWindowDecos(); recalculateWindow(pWindow); + if (!pWindow->getDecorationByType(DECORATION_GROUPBAR)) + pWindow->addWindowDeco(std::make_unique(pWindow)); + return; } pWindow->applyGroupRules(); - if (*PNEWISMASTER || WINDOWSONWORKSPACE == 1 || (!pWindow->m_bFirstMap && OPENINGON->isMaster)) { + static auto* const PDROPATCURSOR = &g_pConfigManager->getConfigValuePtr("master:drop_at_cursor")->intValue; + const auto PWORKSPACEDATA = getMasterWorkspaceData(pWindow->m_iWorkspaceID); + eOrientation orientation = PWORKSPACEDATA->orientation; + const auto NODEIT = std::find(m_lMasterNodesData.begin(), m_lMasterNodesData.end(), *PNODE); + + bool forceDropAsMaster = false; + // if dragging window to move, drop it at the cursor position instead of bottom/top of stack + if (*PDROPATCURSOR && g_pInputManager->dragMode == MBIND_MOVE) { + if (WINDOWSONWORKSPACE > 2) { + for (auto it = m_lMasterNodesData.begin(); it != m_lMasterNodesData.end(); ++it) { + if (it->workspaceID != pWindow->m_iWorkspaceID) + continue; + const CBox box = it->pWindow->getWindowIdealBoundingBoxIgnoreReserved(); + if (box.containsPoint(MOUSECOORDS)) { + switch (orientation) { + case ORIENTATION_LEFT: + case ORIENTATION_RIGHT: + if (MOUSECOORDS.y > it->pWindow->middle().y) + ++it; + break; + case ORIENTATION_TOP: + case ORIENTATION_BOTTOM: + if (MOUSECOORDS.x > it->pWindow->middle().x) + ++it; + break; + case ORIENTATION_CENTER: break; + default: UNREACHABLE(); + } + m_lMasterNodesData.splice(it, m_lMasterNodesData, NODEIT); + break; + } + } + } else if (WINDOWSONWORKSPACE == 2) { + // when dropping as the second tiled window in the workspace, + // make it the master only if the cursor is on the master side of the screen + for (auto& nd : m_lMasterNodesData) { + if (nd.isMaster && nd.workspaceID == PNODE->workspaceID) { + switch (orientation) { + case ORIENTATION_LEFT: + case ORIENTATION_CENTER: + if (MOUSECOORDS.x < nd.pWindow->middle().x) + forceDropAsMaster = true; + break; + case ORIENTATION_RIGHT: + if (MOUSECOORDS.x > nd.pWindow->middle().x) + forceDropAsMaster = true; + break; + case ORIENTATION_TOP: + if (MOUSECOORDS.y < nd.pWindow->middle().y) + forceDropAsMaster = true; + break; + case ORIENTATION_BOTTOM: + if (MOUSECOORDS.y > nd.pWindow->middle().y) + forceDropAsMaster = true; + break; + default: UNREACHABLE(); + } + break; + } + } + } + } + + if ((*PNEWISMASTER && g_pInputManager->dragMode != MBIND_MOVE) || WINDOWSONWORKSPACE == 1 || (WINDOWSONWORKSPACE > 2 && !pWindow->m_bFirstMap && OPENINGON->isMaster) || + forceDropAsMaster) { for (auto& nd : m_lMasterNodesData) { if (nd.isMaster && nd.workspaceID == PNODE->workspaceID) { nd.isMaster = false; @@ -167,13 +227,6 @@ void CHyprMasterLayout::onWindowCreatedTiling(CWindow* pWindow, eDirection direc } } - const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(pWindow->m_iWorkspaceID); - - if (PWORKSPACE->m_bHasFullscreenWindow) { - const auto PFULLWINDOW = g_pCompositor->getFullscreenWindowOnWorkspace(PWORKSPACE->m_iID); - g_pCompositor->setWindowFullscreen(PFULLWINDOW, false, FULLSCREEN_FULL); - } - // recalc recalculateMonitor(pWindow->m_iMonitorID); } @@ -575,6 +628,9 @@ void CHyprMasterLayout::applyNodeDataToWindow(SMasterNodeData* pNode) { // if user specified them in config const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(g_pCompositor->getWorkspaceByID(PWINDOW->m_iWorkspaceID)); + if (PWINDOW->m_bIsFullscreen && !pNode->ignoreFullscreenChecks) + return; + PWINDOW->updateSpecialRenderData(); static auto* const PGAPSIN = &g_pConfigManager->getConfigValuePtr("general:gaps_in")->intValue; @@ -602,22 +658,20 @@ void CHyprMasterLayout::applyNodeDataToWindow(SMasterNodeData* pNode) { PWINDOW->m_sSpecialRenderData.rounding = false; PWINDOW->m_sSpecialRenderData.shadow = false; + PWINDOW->updateWindowDecos(); + const auto RESERVED = PWINDOW->getFullWindowReservedArea(); - const int BORDERSIZE = PWINDOW->getRealBorderSize(); + PWINDOW->m_vRealPosition = PWINDOW->m_vPosition + RESERVED.topLeft; + PWINDOW->m_vRealSize = PWINDOW->m_vSize - (RESERVED.topLeft + RESERVED.bottomRight); - PWINDOW->m_vRealPosition = PWINDOW->m_vPosition + Vector2D(BORDERSIZE, BORDERSIZE) + RESERVED.topLeft; - PWINDOW->m_vRealSize = PWINDOW->m_vSize - Vector2D(2 * BORDERSIZE, 2 * BORDERSIZE) - (RESERVED.topLeft + RESERVED.bottomRight); - - PWINDOW->updateWindowDecos(); + g_pXWaylandManager->setWindowSize(PWINDOW, PWINDOW->m_vRealSize.goalv()); return; } - const int BORDERSIZE = PWINDOW->getRealBorderSize(); - - auto calcPos = PWINDOW->m_vPosition + Vector2D(BORDERSIZE, BORDERSIZE); - auto calcSize = PWINDOW->m_vSize - Vector2D(2 * BORDERSIZE, 2 * BORDERSIZE); + auto calcPos = PWINDOW->m_vPosition; + auto calcSize = PWINDOW->m_vSize; const auto OFFSETTOPLEFT = Vector2D(DISPLAYLEFT ? gapsOut : gapsIn, DISPLAYTOP ? gapsOut : gapsIn); @@ -633,15 +687,21 @@ void CHyprMasterLayout::applyNodeDataToWindow(SMasterNodeData* pNode) { if (g_pCompositor->isWorkspaceSpecial(PWINDOW->m_iWorkspaceID)) { static auto* const PSCALEFACTOR = &g_pConfigManager->getConfigValuePtr("master:special_scale_factor")->floatValue; - PWINDOW->m_vRealPosition = calcPos + (calcSize - calcSize * *PSCALEFACTOR) / 2.f; - PWINDOW->m_vRealSize = calcSize * *PSCALEFACTOR; + CBox wb = {calcPos + (calcSize - calcSize * *PSCALEFACTOR) / 2.f, calcSize * *PSCALEFACTOR}; + wb.round(); // avoid rounding mess - g_pXWaylandManager->setWindowSize(PWINDOW, calcSize * *PSCALEFACTOR); + PWINDOW->m_vRealPosition = wb.pos(); + PWINDOW->m_vRealSize = wb.size(); + + g_pXWaylandManager->setWindowSize(PWINDOW, wb.size()); } else { - PWINDOW->m_vRealSize = calcSize; - PWINDOW->m_vRealPosition = calcPos; + CBox wb = {calcPos, calcSize}; + wb.round(); // avoid rounding mess - g_pXWaylandManager->setWindowSize(PWINDOW, calcSize); + PWINDOW->m_vRealPosition = wb.pos(); + PWINDOW->m_vRealSize = wb.size(); + + g_pXWaylandManager->setWindowSize(PWINDOW, wb.size()); } if (m_bForceWarps && !*PANIMATE) { @@ -718,8 +778,9 @@ void CHyprMasterLayout::resizeActiveWindow(const Vector2D& pixResize, eRectCorne default: UNREACHABLE(); } + const auto workspaceIdForResizing = PMONITOR->specialWorkspaceID == 0 ? PMONITOR->activeWorkspace : PMONITOR->specialWorkspaceID; for (auto& n : m_lMasterNodesData) { - if (n.isMaster && n.workspaceID == PMONITOR->activeWorkspace) + if (n.isMaster && n.workspaceID == workspaceIdForResizing) n.percMaster = std::clamp(n.percMaster + delta, 0.05, 0.95); } @@ -822,6 +883,9 @@ void CHyprMasterLayout::fullscreenRequestForWindow(CWindow* pWindow, eFullscreen pWindow->m_bIsFullscreen = on; PWORKSPACE->m_bHasFullscreenWindow = !PWORKSPACE->m_bHasFullscreenWindow; + pWindow->updateDynamicRules(); + pWindow->updateWindowDecos(); + g_pEventManager->postEvent(SHyprIPCEvent{"fullscreen", std::to_string((int)on)}); EMIT_HOOK_EVENT("fullscreen", pWindow); @@ -860,12 +924,13 @@ void CHyprMasterLayout::fullscreenRequestForWindow(CWindow* pWindow, eFullscreen // To keep consistent with the settings without C+P code SMasterNodeData fakeNode; - fakeNode.pWindow = pWindow; - fakeNode.position = PMONITOR->vecPosition + PMONITOR->vecReservedTopLeft; - fakeNode.size = PMONITOR->vecSize - PMONITOR->vecReservedTopLeft - PMONITOR->vecReservedBottomRight; - fakeNode.workspaceID = pWindow->m_iWorkspaceID; - pWindow->m_vPosition = fakeNode.position; - pWindow->m_vSize = fakeNode.size; + fakeNode.pWindow = pWindow; + fakeNode.position = PMONITOR->vecPosition + PMONITOR->vecReservedTopLeft; + fakeNode.size = PMONITOR->vecSize - PMONITOR->vecReservedTopLeft - PMONITOR->vecReservedBottomRight; + fakeNode.workspaceID = pWindow->m_iWorkspaceID; + pWindow->m_vPosition = fakeNode.position; + pWindow->m_vSize = fakeNode.size; + fakeNode.ignoreFullscreenChecks = true; applyNodeDataToWindow(&fakeNode); } @@ -903,7 +968,16 @@ void CHyprMasterLayout::moveWindowTo(CWindow* pWindow, const std::string& dir) { const auto PWINDOW2 = g_pCompositor->getWindowInDirection(pWindow, dir[0]); - switchWindows(pWindow, PWINDOW2); + if (pWindow->m_iWorkspaceID != PWINDOW2->m_iWorkspaceID) { + // if different monitors, send to monitor + onWindowRemovedTiling(pWindow); + pWindow->moveToWorkspace(PWINDOW2->m_iWorkspaceID); + pWindow->m_iMonitorID = PWINDOW2->m_iMonitorID; + onWindowCreatedTiling(pWindow); + } else { + // if same monitor, switch windows + switchWindows(pWindow, PWINDOW2); + } } void CHyprMasterLayout::switchWindows(CWindow* pWindow, CWindow* pWindow2) { @@ -1253,6 +1327,62 @@ std::any CHyprMasterLayout::layoutMessage(SLayoutMessageHeader header, std::stri nd.percMaster = std::clamp(newMfact, 0.05f, 0.95f); } } + } else if (command == "rollnext") { + const auto PWINDOW = header.pWindow; + const auto PNODE = getNodeFromWindow(PWINDOW); + + if (!PNODE) + return 0; + + const auto OLDMASTER = PNODE->isMaster ? PNODE : getMasterNodeOnWorkspace(PNODE->workspaceID); + if (!OLDMASTER) + return 0; + + const auto OLDMASTERIT = std::find(m_lMasterNodesData.begin(), m_lMasterNodesData.end(), *OLDMASTER); + + for (auto& nd : m_lMasterNodesData) { + if (nd.workspaceID == PNODE->workspaceID && !nd.isMaster) { + nd.isMaster = true; + const auto NEWMASTERIT = std::find(m_lMasterNodesData.begin(), m_lMasterNodesData.end(), nd); + m_lMasterNodesData.splice(OLDMASTERIT, m_lMasterNodesData, NEWMASTERIT); + const bool inheritFullscreen = prepareLoseFocus(PWINDOW); + switchToWindow(nd.pWindow); + prepareNewFocus(nd.pWindow, inheritFullscreen); + OLDMASTER->isMaster = false; + m_lMasterNodesData.splice(m_lMasterNodesData.end(), m_lMasterNodesData, OLDMASTERIT); + break; + } + } + + recalculateMonitor(PWINDOW->m_iMonitorID); + } else if (command == "rollprev") { + const auto PWINDOW = header.pWindow; + const auto PNODE = getNodeFromWindow(PWINDOW); + + if (!PNODE) + return 0; + + const auto OLDMASTER = PNODE->isMaster ? PNODE : getMasterNodeOnWorkspace(PNODE->workspaceID); + if (!OLDMASTER) + return 0; + + const auto OLDMASTERIT = std::find(m_lMasterNodesData.begin(), m_lMasterNodesData.end(), *OLDMASTER); + + for (auto& nd : m_lMasterNodesData | std::views::reverse) { + if (nd.workspaceID == PNODE->workspaceID && !nd.isMaster) { + nd.isMaster = true; + const auto NEWMASTERIT = std::find(m_lMasterNodesData.begin(), m_lMasterNodesData.end(), nd); + m_lMasterNodesData.splice(OLDMASTERIT, m_lMasterNodesData, NEWMASTERIT); + const bool inheritFullscreen = prepareLoseFocus(PWINDOW); + switchToWindow(nd.pWindow); + prepareNewFocus(nd.pWindow, inheritFullscreen); + OLDMASTER->isMaster = false; + m_lMasterNodesData.splice(m_lMasterNodesData.begin(), m_lMasterNodesData, OLDMASTERIT); + break; + } + } + + recalculateMonitor(PWINDOW->m_iMonitorID); } return 0; @@ -1328,7 +1458,7 @@ void CHyprMasterLayout::replaceWindowDataWith(CWindow* from, CWindow* to) { void CHyprMasterLayout::onEnable() { for (auto& w : g_pCompositor->m_vWindows) { - if (w->m_bIsFloating || !w->m_bMappedX11 || !w->m_bIsMapped || w->isHidden()) + if (w->m_bIsFloating || !w->m_bIsMapped || w->isHidden()) continue; onWindowCreatedTiling(w.get()); diff --git a/src/layout/MasterLayout.hpp b/src/layout/MasterLayout.hpp index aa201505..e316556a 100644 --- a/src/layout/MasterLayout.hpp +++ b/src/layout/MasterLayout.hpp @@ -7,7 +7,7 @@ #include #include -enum eFullscreenMode : uint8_t; +enum eFullscreenMode : int8_t; //orientation determines which side of the screen the master area resides enum eOrientation : uint8_t { @@ -31,7 +31,10 @@ struct SMasterNodeData { int workspaceID = -1; - bool operator==(const SMasterNodeData& rhs) const { + bool ignoreFullscreenChecks = false; + + // + bool operator==(const SMasterNodeData& rhs) const { return pWindow == rhs.pWindow; } }; @@ -40,7 +43,8 @@ struct SMasterWorkspaceData { int workspaceID = -1; eOrientation orientation = ORIENTATION_LEFT; - bool operator==(const SMasterWorkspaceData& rhs) const { + // + bool operator==(const SMasterWorkspaceData& rhs) const { return workspaceID == rhs.workspaceID; } }; diff --git a/src/macros.hpp b/src/macros.hpp index 8874ad3e..19b4cbc6 100644 --- a/src/macros.hpp +++ b/src/macros.hpp @@ -14,22 +14,7 @@ #define ISDEBUG false #endif -// git stuff -#ifndef GIT_COMMIT_HASH -#define GIT_COMMIT_HASH "?" -#endif -#ifndef GIT_BRANCH -#define GIT_BRANCH "?" -#endif -#ifndef GIT_COMMIT_MESSAGE -#define GIT_COMMIT_MESSAGE "?" -#endif -#ifndef GIT_DIRTY -#define GIT_DIRTY "?" -#endif -#ifndef GIT_TAG -#define GIT_TAG "?" -#endif +#include "version.h" #define SPECIAL_WORKSPACE_START (-99) @@ -37,6 +22,8 @@ #define STRVAL_EMPTY "[[EMPTY]]" +#define WORKSPACE_INVALID -1L + #define LISTENER(name) \ void listener_##name(wl_listener*, void*); \ inline wl_listener listen_##name = {.notify = listener_##name} diff --git a/src/main.cpp b/src/main.cpp index 9f8d61fc..e1f43ad0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -52,16 +52,21 @@ int main(int argc, char** argv) { return 1; } - std::string next_arg = std::next(it)->c_str(); + configPath = std::next(it)->c_str(); - if (!std::filesystem::exists(next_arg)) { - std::cerr << "[ ERROR ] Config path '" << next_arg << "' doesn't exist!\n"; + try { + configPath = std::filesystem::canonical(configPath); + + if (!std::filesystem::is_regular_file(configPath)) { + throw std::exception(); + } + } catch (...) { + std::cerr << "[ ERROR ] Config file '" << configPath << "' doesn't exist!\n"; help(); return 1; } - configPath = next_arg; Debug::log(LOG, "User-specified config location: '{}'", configPath); it++; @@ -80,7 +85,7 @@ int main(int argc, char** argv) { } if (!ignoreSudo && Init::isSudo()) { - std::cerr << "[ ERROR ] Hyprland was launched with superuser priveleges, but the privileges check is not omitted.\n"; + std::cerr << "[ ERROR ] Hyprland was launched with superuser privileges, but the privileges check is not omitted.\n"; std::cerr << " Hint: Use the --i-am-really-stupid flag to omit that check.\n"; return 1; @@ -97,13 +102,16 @@ int main(int argc, char** argv) { g_pCompositor->initServer(); - Init::gainRealTime(); + if (!envEnabled("HYPRLAND_NO_RT")) + Init::gainRealTime(); Debug::log(LOG, "Hyprland init finished."); // If all's good to go, start. g_pCompositor->startCompositor(); + g_pCompositor->m_bIsShuttingDown = true; + // If we are here it means we got yote. Debug::log(LOG, "Hyprland reached the end."); g_pCompositor.reset(); diff --git a/src/managers/AnimationManager.cpp b/src/managers/AnimationManager.cpp index 64cb1c0c..120e6f49 100644 --- a/src/managers/AnimationManager.cpp +++ b/src/managers/AnimationManager.cpp @@ -68,6 +68,7 @@ void CAnimationManager::tick() { if (av->m_eDamagePolicy == AVARDAMAGE_SHADOW && !*PSHADOWSENABLED) { av->warp(false); + animationEndedVars.push_back(av); continue; } @@ -81,7 +82,7 @@ void CAnimationManager::tick() { CMonitor* PMONITOR = nullptr; bool animationsDisabled = animGlobalDisabled; - wlr_box WLRBOXPREV = {0, 0, 0, 0}; + CBox WLRBOXPREV = {0, 0, 0, 0}; if (PWINDOW) { WLRBOXPREV = PWINDOW->getFullWindowBoundingBox(); PMONITOR = g_pCompositor->getMonitorFromID(PWINDOW->m_iMonitorID); @@ -93,6 +94,12 @@ void CAnimationManager::tick() { if (!PMONITOR) continue; WLRBOXPREV = {(int)PMONITOR->vecPosition.x, (int)PMONITOR->vecPosition.y, (int)PMONITOR->vecSize.x, (int)PMONITOR->vecSize.y}; + + // TODO: just make this into a damn callback already vax... + for (auto& w : g_pCompositor->m_vWindows) { + if (!w->isHidden() && w->m_bIsMapped && w->m_bIsFloating) + g_pHyprRenderer->damageWindow(w.get()); + } } else if (PLAYER) { WLRBOXPREV = PLAYER->geometry; PMONITOR = g_pCompositor->getMonitorFromVector(Vector2D(PLAYER->geometry.x, PLAYER->geometry.y) + Vector2D(PLAYER->geometry.width, PLAYER->geometry.height) / 2.f); @@ -206,6 +213,12 @@ void CAnimationManager::tick() { continue; w->updateWindowDecos(); + + if (w->m_bIsFloating) { + auto bb = w->getFullWindowBoundingBox(); + bb.translate(PWORKSPACE->m_vRenderOffset.vec()); + g_pHyprRenderer->damageBox(&bb); + } } } else if (PLAYER) { if (PLAYER->layer == ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND || PLAYER->layer == ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM) @@ -216,6 +229,8 @@ void CAnimationManager::tick() { case AVARDAMAGE_BORDER: { RASSERT(PWINDOW, "Tried to AVARDAMAGE_BORDER a non-window AVAR!"); + // TODO: move this to the border class + // damage only the border. static auto* const PROUNDING = &g_pConfigManager->getConfigValuePtr("decoration:rounding")->intValue; const auto ROUNDINGSIZE = *PROUNDING + 1; @@ -230,7 +245,7 @@ void CAnimationManager::tick() { BORDERSIZE + ROUNDINGSIZE); // bottom // damage for new box - const wlr_box WLRBOXNEW = {PWINDOW->m_vRealPosition.vec().x, PWINDOW->m_vRealPosition.vec().y, PWINDOW->m_vRealSize.vec().x, PWINDOW->m_vRealSize.vec().y}; + const CBox WLRBOXNEW = {PWINDOW->m_vRealPosition.vec().x, PWINDOW->m_vRealPosition.vec().y, PWINDOW->m_vRealSize.vec().x, PWINDOW->m_vRealSize.vec().y}; g_pHyprRenderer->damageBox(WLRBOXNEW.x - BORDERSIZE, WLRBOXNEW.y - BORDERSIZE, WLRBOXNEW.width + 2 * BORDERSIZE, BORDERSIZE + ROUNDINGSIZE); // top g_pHyprRenderer->damageBox(WLRBOXNEW.x - BORDERSIZE, WLRBOXNEW.y - BORDERSIZE, BORDERSIZE + ROUNDINGSIZE, WLRBOXNEW.height + 2 * BORDERSIZE); // left g_pHyprRenderer->damageBox(WLRBOXNEW.x + WLRBOXNEW.width - ROUNDINGSIZE, WLRBOXNEW.y - BORDERSIZE, BORDERSIZE + ROUNDINGSIZE, @@ -242,29 +257,9 @@ void CAnimationManager::tick() { case AVARDAMAGE_SHADOW: { RASSERT(PWINDOW, "Tried to AVARDAMAGE_SHADOW a non-window AVAR!"); - static auto* const PSHADOWIGNOREWINDOW = &g_pConfigManager->getConfigValuePtr("decoration:shadow_ignore_window")->intValue; + const auto PDECO = PWINDOW->getDecorationByType(DECORATION_SHADOW); - const auto PDECO = PWINDOW->getDecorationByType(DECORATION_SHADOW); - - if (PDECO) { - const auto EXTENTS = PDECO->getWindowDecorationExtents(); - - wlr_box dmg = {PWINDOW->m_vRealPosition.vec().x - EXTENTS.topLeft.x, PWINDOW->m_vRealPosition.vec().y - EXTENTS.topLeft.y, - PWINDOW->m_vRealSize.vec().x + EXTENTS.topLeft.x + EXTENTS.bottomRight.x, - PWINDOW->m_vRealSize.vec().y + EXTENTS.topLeft.y + EXTENTS.bottomRight.y}; - - if (!*PSHADOWIGNOREWINDOW) { - // easy, damage the entire box - g_pHyprRenderer->damageBox(&dmg); - } else { - CRegion rg{dmg.x, dmg.y, dmg.width, dmg.height}; - CRegion wb{PWINDOW->m_vRealPosition.vec().x, PWINDOW->m_vRealPosition.vec().y, PWINDOW->m_vRealSize.vec().x, PWINDOW->m_vRealSize.vec().y}; - - rg.subtract(wb); - - g_pHyprRenderer->damageRegion(rg); - } - } + PDECO->damageEntire(); break; } @@ -423,7 +418,7 @@ void CAnimationManager::onWindowPostCreateClose(CWindow* pWindow, bool close) { if (pWindow->m_sAdditionalConfigData.animationStyle != "") { // the window has config'd special anim - if (pWindow->m_sAdditionalConfigData.animationStyle.find("slide") == 0) { + if (pWindow->m_sAdditionalConfigData.animationStyle.starts_with("slide")) { if (pWindow->m_sAdditionalConfigData.animationStyle.contains(' ')) { // has a direction animationSlide(pWindow, pWindow->m_sAdditionalConfigData.animationStyle.substr(pWindow->m_sAdditionalConfigData.animationStyle.find(' ') + 1), close); @@ -452,7 +447,7 @@ void CAnimationManager::onWindowPostCreateClose(CWindow* pWindow, bool close) { // anim popin, fallback float minPerc = 0.f; - if (ANIMSTYLE.find("%") != 0) { + if (!ANIMSTYLE.starts_with("%")) { try { auto percstr = ANIMSTYLE.substr(ANIMSTYLE.find_last_of(' ')); minPerc = std::stoi(percstr.substr(0, percstr.length() - 1)); @@ -467,10 +462,10 @@ void CAnimationManager::onWindowPostCreateClose(CWindow* pWindow, bool close) { } std::string CAnimationManager::styleValidInConfigVar(const std::string& config, const std::string& style) { - if (config.find("window") == 0) { + if (config.starts_with("window")) { if (style == "slide") { return ""; - } else if (style.find("popin") == 0) { + } else if (style.starts_with("popin")) { // try parsing float minPerc = 0.f; if (style.find("%") != std::string::npos) { @@ -491,7 +486,7 @@ std::string CAnimationManager::styleValidInConfigVar(const std::string& config, } else if (config == "workspaces" || config == "specialWorkspace") { if (style == "slide" || style == "slidevert" || style == "fade") return ""; - else if (style.find("slidefade") == 0) { + else if (style.starts_with("slidefade")) { // try parsing float movePerc = 0.f; if (style.find("%") != std::string::npos) { diff --git a/src/managers/EventManager.hpp b/src/managers/EventManager.hpp index be2bf2d5..68cda1ee 100644 --- a/src/managers/EventManager.hpp +++ b/src/managers/EventManager.hpp @@ -20,6 +20,7 @@ class CEventManager { void startThread(); std::thread m_tThread; + private: void flushEvents(); diff --git a/src/managers/HookSystemManager.cpp b/src/managers/HookSystemManager.cpp index 567286d6..48c300cb 100644 --- a/src/managers/HookSystemManager.cpp +++ b/src/managers/HookSystemManager.cpp @@ -26,7 +26,7 @@ void CHookSystemManager::unhook(HOOK_CALLBACK_FN* fn) { } } -void CHookSystemManager::emit(const std::vector* callbacks, std::any data) { +void CHookSystemManager::emit(const std::vector* callbacks, SCallbackInfo& info, std::any data) { if (callbacks->empty()) return; @@ -38,7 +38,7 @@ void CHookSystemManager::emit(const std::vector* callbacks, std: if (!cb.handle) { // we don't guard hl hooks - (*cb.fn)(cb.fn, data); + (*cb.fn)(cb.fn, info, data); continue; } @@ -49,7 +49,7 @@ void CHookSystemManager::emit(const std::vector* callbacks, std: try { if (!setjmp(m_jbHookFaultJumpBuf)) - (*cb.fn)(cb.fn, data); + (*cb.fn)(cb.fn, info, data); else { // this module crashed. throw std::exception(); diff --git a/src/managers/HookSystemManager.hpp b/src/managers/HookSystemManager.hpp index 66edd1c0..ca8e9006 100644 --- a/src/managers/HookSystemManager.hpp +++ b/src/managers/HookSystemManager.hpp @@ -12,7 +12,8 @@ #include "../plugins/PluginAPI.hpp" // global typedef for hooked functions. Passes itself as a ptr when called, and `data` additionally. -typedef std::function HOOK_CALLBACK_FN; + +typedef std::function HOOK_CALLBACK_FN; struct SCallbackFNPtr { HOOK_CALLBACK_FN* fn = nullptr; @@ -22,7 +23,17 @@ struct SCallbackFNPtr { #define EMIT_HOOK_EVENT(name, param) \ { \ static auto* const PEVENTVEC = g_pHookSystem->getVecForEvent(name); \ - g_pHookSystem->emit(PEVENTVEC, param); \ + SCallbackInfo info; \ + g_pHookSystem->emit(PEVENTVEC, info, param); \ + } + +#define EMIT_HOOK_EVENT_CANCELLABLE(name, param) \ + { \ + static auto* const PEVENTVEC = g_pHookSystem->getVecForEvent(name); \ + SCallbackInfo info; \ + g_pHookSystem->emit(PEVENTVEC, info, param); \ + if (info.cancelled) \ + return; \ } class CHookSystemManager { @@ -34,7 +45,7 @@ class CHookSystemManager { void hookStatic(const std::string& event, HOOK_CALLBACK_FN* fn, HANDLE handle = nullptr); void unhook(HOOK_CALLBACK_FN* fn); - void emit(const std::vector* callbacks, std::any data = 0); + void emit(const std::vector* callbacks, SCallbackInfo& info, std::any data = 0); std::vector* getVecForEvent(const std::string& event); bool m_bCurrentEventPlugin = false; diff --git a/src/managers/KeybindManager.cpp b/src/managers/KeybindManager.cpp index ddfcfa1b..72f92804 100644 --- a/src/managers/KeybindManager.cpp +++ b/src/managers/KeybindManager.cpp @@ -1,11 +1,14 @@ #include "KeybindManager.hpp" #include "./directions/GroupActiveDirection.hpp" #include "../render/decorations/CHyprGroupBarDecoration.hpp" +#include "debug/Log.hpp" +#include "helpers/VarList.hpp" #include #include #include +#include #if defined(__linux__) #include #elif defined(__NetBSD__) || defined(__OpenBSD__) @@ -17,69 +20,70 @@ CKeybindManager::CKeybindManager() { // initialize all dispatchers - m_mDispatchers["exec"] = spawn; - m_mDispatchers["execr"] = spawnRaw; - m_mDispatchers["killactive"] = killActive; - m_mDispatchers["closewindow"] = kill; - m_mDispatchers["togglefloating"] = toggleActiveFloating; - m_mDispatchers["workspace"] = changeworkspace; - m_mDispatchers["renameworkspace"] = renameWorkspace; - m_mDispatchers["fullscreen"] = fullscreenActive; - m_mDispatchers["fakefullscreen"] = fakeFullscreenActive; - m_mDispatchers["movetoworkspace"] = moveActiveToWorkspace; - m_mDispatchers["movetoworkspacesilent"] = moveActiveToWorkspaceSilent; - m_mDispatchers["pseudo"] = toggleActivePseudo; + m_mDispatchers["exec"] = spawn; + m_mDispatchers["execr"] = spawnRaw; + m_mDispatchers["killactive"] = killActive; + m_mDispatchers["closewindow"] = kill; + m_mDispatchers["togglefloating"] = toggleActiveFloating; + m_mDispatchers["workspace"] = changeworkspace; + m_mDispatchers["renameworkspace"] = renameWorkspace; + m_mDispatchers["fullscreen"] = fullscreenActive; + m_mDispatchers["fakefullscreen"] = fakeFullscreenActive; + m_mDispatchers["movetoworkspace"] = moveActiveToWorkspace; + m_mDispatchers["movetoworkspacesilent"] = moveActiveToWorkspaceSilent; + m_mDispatchers["pseudo"] = toggleActivePseudo; m_mDispatchers["movegroupfocus"] = moveGroupFocusTo; - m_mDispatchers["movefocus"] = moveFocusTo; - m_mDispatchers["movewindow"] = moveActiveTo; - m_mDispatchers["swapwindow"] = swapActive; - m_mDispatchers["centerwindow"] = centerWindow; - m_mDispatchers["togglegroup"] = toggleGroup; - m_mDispatchers["changegroupactive"] = changeGroupActive; - m_mDispatchers["movegroupwindow"] = moveGroupWindow; - m_mDispatchers["togglesplit"] = toggleSplit; - m_mDispatchers["splitratio"] = alterSplitRatio; - m_mDispatchers["focusmonitor"] = focusMonitor; - m_mDispatchers["movecursortocorner"] = moveCursorToCorner; - m_mDispatchers["movecursor"] = moveCursor; - m_mDispatchers["workspaceopt"] = workspaceOpt; - m_mDispatchers["exit"] = exitHyprland; - m_mDispatchers["movecurrentworkspacetomonitor"] = moveCurrentWorkspaceToMonitor; - m_mDispatchers["moveworkspacetomonitor"] = moveWorkspaceToMonitor; - m_mDispatchers["togglespecialworkspace"] = toggleSpecialWorkspace; - m_mDispatchers["forcerendererreload"] = forceRendererReload; - m_mDispatchers["resizeactive"] = resizeActive; - m_mDispatchers["moveactive"] = moveActive; - m_mDispatchers["cyclenext"] = circleNext; - m_mDispatchers["focuswindowbyclass"] = focusWindow; - m_mDispatchers["focuswindow"] = focusWindow; - m_mDispatchers["submap"] = setSubmap; - m_mDispatchers["pass"] = pass; - m_mDispatchers["layoutmsg"] = layoutmsg; - m_mDispatchers["toggleopaque"] = toggleOpaque; - m_mDispatchers["dpms"] = dpms; - m_mDispatchers["movewindowpixel"] = moveWindow; - m_mDispatchers["resizewindowpixel"] = resizeWindow; - m_mDispatchers["swapnext"] = swapnext; - m_mDispatchers["swapactiveworkspaces"] = swapActiveWorkspaces; - m_mDispatchers["pin"] = pinActive; - m_mDispatchers["mouse"] = mouse; - m_mDispatchers["bringactivetotop"] = bringActiveToTop; - m_mDispatchers["alterzorder"] = alterZOrder; - m_mDispatchers["focusurgentorlast"] = focusUrgentOrLast; - m_mDispatchers["focuscurrentorlast"] = focusCurrentOrLast; - m_mDispatchers["lockgroups"] = lockGroups; - m_mDispatchers["lockactivegroup"] = lockActiveGroup; - m_mDispatchers["moveintogroup"] = moveIntoGroup; - m_mDispatchers["moveoutofgroup"] = moveOutOfGroup; - m_mDispatchers["movewindoworgroup"] = moveWindowOrGroup; - m_mDispatchers["setignoregrouplock"] = setIgnoreGroupLock; - m_mDispatchers["denywindowfromgroup"] = denyWindowFromGroup; - m_mDispatchers["global"] = global; + m_mDispatchers["movefocus"] = moveFocusTo; + m_mDispatchers["movewindow"] = moveActiveTo; + m_mDispatchers["swapwindow"] = swapActive; + m_mDispatchers["centerwindow"] = centerWindow; + m_mDispatchers["togglegroup"] = toggleGroup; + m_mDispatchers["changegroupactive"] = changeGroupActive; + m_mDispatchers["movegroupwindow"] = moveGroupWindow; + m_mDispatchers["togglesplit"] = toggleSplit; + m_mDispatchers["splitratio"] = alterSplitRatio; + m_mDispatchers["focusmonitor"] = focusMonitor; + m_mDispatchers["movecursortocorner"] = moveCursorToCorner; + m_mDispatchers["movecursor"] = moveCursor; + m_mDispatchers["workspaceopt"] = workspaceOpt; + m_mDispatchers["exit"] = exitHyprland; + m_mDispatchers["movecurrentworkspacetomonitor"] = moveCurrentWorkspaceToMonitor; + m_mDispatchers["focusworkspaceoncurrentmonitor"] = focusWorkspaceOnCurrentMonitor; + m_mDispatchers["moveworkspacetomonitor"] = moveWorkspaceToMonitor; + m_mDispatchers["togglespecialworkspace"] = toggleSpecialWorkspace; + m_mDispatchers["forcerendererreload"] = forceRendererReload; + m_mDispatchers["resizeactive"] = resizeActive; + m_mDispatchers["moveactive"] = moveActive; + m_mDispatchers["cyclenext"] = circleNext; + m_mDispatchers["focuswindowbyclass"] = focusWindow; + m_mDispatchers["focuswindow"] = focusWindow; + m_mDispatchers["submap"] = setSubmap; + m_mDispatchers["pass"] = pass; + m_mDispatchers["layoutmsg"] = layoutmsg; + m_mDispatchers["toggleopaque"] = toggleOpaque; + m_mDispatchers["dpms"] = dpms; + m_mDispatchers["movewindowpixel"] = moveWindow; + m_mDispatchers["resizewindowpixel"] = resizeWindow; + m_mDispatchers["swapnext"] = swapnext; + m_mDispatchers["swapactiveworkspaces"] = swapActiveWorkspaces; + m_mDispatchers["pin"] = pinActive; + m_mDispatchers["mouse"] = mouse; + m_mDispatchers["bringactivetotop"] = bringActiveToTop; + m_mDispatchers["alterzorder"] = alterZOrder; + m_mDispatchers["focusurgentorlast"] = focusUrgentOrLast; + m_mDispatchers["focuscurrentorlast"] = focusCurrentOrLast; + m_mDispatchers["lockgroups"] = lockGroups; + m_mDispatchers["lockactivegroup"] = lockActiveGroup; + m_mDispatchers["moveintogroup"] = moveIntoGroup; + m_mDispatchers["moveoutofgroup"] = moveOutOfGroup; + m_mDispatchers["movewindoworgroup"] = moveWindowOrGroup; + m_mDispatchers["setignoregrouplock"] = setIgnoreGroupLock; + m_mDispatchers["denywindowfromgroup"] = denyWindowFromGroup; + m_mDispatchers["global"] = global; m_tScrollTimer.reset(); - g_pHookSystem->hookDynamic("configReloaded", [this](void* hk, std::any param) { + g_pHookSystem->hookDynamic("configReloaded", [this](void* hk, SCallbackInfo& info, std::any param) { // clear cuz realloc'd m_pActiveKeybind = nullptr; m_vPressedSpecialBinds.clear(); @@ -95,7 +99,7 @@ void CKeybindManager::addKeybind(SKeybind kb) { void CKeybindManager::removeKeybind(uint32_t mod, const std::string& key) { for (auto it = m_lKeybinds.begin(); it != m_lKeybinds.end(); ++it) { if (isNumber(key) && std::stoi(key) > 9) { - const auto KEYNUM = std::stoi(key); + const uint32_t KEYNUM = std::stoi(key); if (it->modmask == mod && it->keycode == KEYNUM) { it = m_lKeybinds.erase(it); @@ -137,6 +141,22 @@ uint32_t CKeybindManager::stringToModMask(std::string mods) { return modMask; } +uint32_t CKeybindManager::keycodeToModifier(xkb_keycode_t keycode) { + switch (keycode - 8) { + case KEY_LEFTMETA: return WLR_MODIFIER_LOGO; + case KEY_RIGHTMETA: return WLR_MODIFIER_LOGO; + case KEY_LEFTSHIFT: return WLR_MODIFIER_SHIFT; + case KEY_RIGHTSHIFT: return WLR_MODIFIER_SHIFT; + case KEY_LEFTCTRL: return WLR_MODIFIER_CTRL; + case KEY_RIGHTCTRL: return WLR_MODIFIER_CTRL; + case KEY_LEFTALT: return WLR_MODIFIER_ALT; + case KEY_RIGHTALT: return WLR_MODIFIER_ALT; + case KEY_CAPSLOCK: return WLR_MODIFIER_CAPS; + case KEY_NUMLOCK: return WLR_MODIFIER_MOD2; + default: return 0; + } +} + void CKeybindManager::updateXKBTranslationState() { if (m_pXKBTranslationState) { xkb_keymap_unref(xkb_state_get_keymap(m_pXKBTranslationState)); @@ -270,7 +290,7 @@ CWindow *CKeybindManager::groupActiveInDirection(std::string args) { if (PWINDOW->m_sGroupData.pNextWindow == PWINDOW) return nullptr; - + if (isNumber(args, false)) { // index starts from '1'; '0' means last window const int INDEX = std::stoi(args); @@ -278,7 +298,7 @@ CWindow *CKeybindManager::groupActiveInDirection(std::string args) { return nullptr; if (INDEX == 0) return PWINDOW->getGroupTail(); - + return PWINDOW->getGroupWindowByIndex(INDEX - 1); } @@ -292,8 +312,7 @@ CWindow *CKeybindManager::groupActiveInDirection(std::string args) { bool CKeybindManager::onKeyEvent(wlr_keyboard_key_event* e, SKeyboard* pKeyboard) { if (!g_pCompositor->m_bSessionActive || g_pCompositor->m_bUnsafeState) { - m_dPressedKeycodes.clear(); - m_dPressedKeysyms.clear(); + m_dPressedKeys.clear(); return true; } @@ -322,9 +341,16 @@ bool CKeybindManager::onKeyEvent(wlr_keyboard_key_event* e, SKeyboard* pKeyboard m_uLastCode = KEYCODE; m_uLastMouseCode = 0; - bool mouseBindWasActive = ensureMouseBindState(); + bool mouseBindWasActive = ensureMouseBindState(); - bool found = false; + const auto KEY = SPressedKeyWithMods{ + .keysym = keysym, + .keycode = KEYCODE, + .modmaskAtPressTime = MODS, + .sent = true, + }; + + bool suppressEvent = false; if (e->state == WL_KEYBOARD_KEY_STATE_PRESSED) { // clean repeat if (m_pActiveKeybindEventSource) { @@ -333,16 +359,15 @@ bool CKeybindManager::onKeyEvent(wlr_keyboard_key_event* e, SKeyboard* pKeyboard m_pActiveKeybind = nullptr; } - m_dPressedKeycodes.push_back(KEYCODE); - m_dPressedKeysyms.push_back(keysym); + m_dPressedKeys.push_back(KEY); - found = handleKeybinds(MODS, "", keysym, 0, true, e->time_msec) || found; + suppressEvent = handleKeybinds(MODS, KEY, true); - found = handleKeybinds(MODS, "", 0, KEYCODE, true, e->time_msec) || found; - - if (found) + if (suppressEvent) shadowKeybinds(keysym, KEYCODE); - } else if (e->state == WL_KEYBOARD_KEY_STATE_RELEASED) { + + m_dPressedKeys.back().sent = !suppressEvent; + } else { // key release // clean repeat if (m_pActiveKeybindEventSource) { wl_event_source_remove(m_pActiveKeybindEventSource); @@ -350,17 +375,28 @@ bool CKeybindManager::onKeyEvent(wlr_keyboard_key_event* e, SKeyboard* pKeyboard m_pActiveKeybind = nullptr; } - m_dPressedKeycodes.erase(std::remove(m_dPressedKeycodes.begin(), m_dPressedKeycodes.end(), KEYCODE), m_dPressedKeycodes.end()); - m_dPressedKeysyms.erase(std::remove(m_dPressedKeysyms.begin(), m_dPressedKeysyms.end(), keysym), m_dPressedKeysyms.end()); - - found = handleKeybinds(MODS, "", keysym, 0, false, e->time_msec) || found; - - found = handleKeybinds(MODS, "", 0, KEYCODE, false, e->time_msec) || found; + bool foundInPressedKeys = false; + for (auto it = m_dPressedKeys.begin(); it != m_dPressedKeys.end();) { + if (it->keycode == KEYCODE) { + suppressEvent = handleKeybinds(MODS, *it, false); + foundInPressedKeys = true; + suppressEvent = !it->sent; + it = m_dPressedKeys.erase(it); + break; + } else { + ++it; + } + } + if (!foundInPressedKeys) { + Debug::log(ERR, "BUG THIS: key not found in m_dPressedKeys"); + // fallback with wrong `KEY.modmaskAtPressTime`, this can be buggy + suppressEvent = handleKeybinds(MODS, KEY, false); + } shadowKeybinds(); } - return !found && !mouseBindWasActive; + return !suppressEvent && !mouseBindWasActive; } bool CKeybindManager::onAxisEvent(wlr_pointer_axis_event* e) { @@ -378,14 +414,14 @@ bool CKeybindManager::onAxisEvent(wlr_pointer_axis_event* e) { bool found = false; if (e->source == WLR_AXIS_SOURCE_WHEEL && e->orientation == WLR_AXIS_ORIENTATION_VERTICAL) { if (e->delta < 0) - found = handleKeybinds(MODS, "mouse_down", 0, 0, true, 0); + found = handleKeybinds(MODS, SPressedKeyWithMods{.keyName = "mouse_down"}, true); else - found = handleKeybinds(MODS, "mouse_up", 0, 0, true, 0); + found = handleKeybinds(MODS, SPressedKeyWithMods{.keyName = "mouse_up"}, true); } else if (e->source == WLR_AXIS_SOURCE_WHEEL && e->orientation == WLR_AXIS_ORIENTATION_HORIZONTAL) { if (e->delta < 0) - found = handleKeybinds(MODS, "mouse_left", 0, 0, true, 0); + found = handleKeybinds(MODS, SPressedKeyWithMods{.keyName = "mouse_left"}, true); else - found = handleKeybinds(MODS, "mouse_right", 0, 0, true, 0); + found = handleKeybinds(MODS, SPressedKeyWithMods{.keyName = "mouse_right"}, true); } if (found) @@ -397,26 +433,53 @@ bool CKeybindManager::onAxisEvent(wlr_pointer_axis_event* e) { bool CKeybindManager::onMouseEvent(wlr_pointer_button_event* e) { const auto MODS = g_pInputManager->accumulateModsFromAllKBs(); - bool found = false; + bool suppressEvent = false; m_uLastMouseCode = e->button; m_uLastCode = 0; m_uTimeLastMs = e->time_msec; - bool mouseBindWasActive = ensureMouseBindState(); + bool mouseBindWasActive = ensureMouseBindState(); + + const auto KEY_NAME = "mouse:" + std::to_string(e->button); + + const auto KEY = SPressedKeyWithMods{ + .keyName = KEY_NAME, + .modmaskAtPressTime = MODS, + }; if (e->state == WLR_BUTTON_PRESSED) { - found = handleKeybinds(MODS, "mouse:" + std::to_string(e->button), 0, 0, true, 0); + m_dPressedKeys.push_back(KEY); - if (found) + suppressEvent = handleKeybinds(MODS, KEY, true); + + if (suppressEvent) shadowKeybinds(); + + m_dPressedKeys.back().sent = !suppressEvent; } else { - found = handleKeybinds(MODS, "mouse:" + std::to_string(e->button), 0, 0, false, 0); + bool foundInPressedKeys = false; + for (auto it = m_dPressedKeys.begin(); it != m_dPressedKeys.end();) { + if (it->keyName == KEY_NAME) { + suppressEvent = handleKeybinds(MODS, *it, false); + foundInPressedKeys = true; + suppressEvent = !it->sent; + it = m_dPressedKeys.erase(it); + break; + } else { + ++it; + } + } + if (!foundInPressedKeys) { + Debug::log(ERR, "BUG THIS: key not found in m_dPressedKeys (2)"); + // fallback with wrong `KEY.modmaskAtPressTime`, this can be buggy + suppressEvent = handleKeybinds(MODS, KEY, false); + } shadowKeybinds(); } - return !found && !mouseBindWasActive; + return !suppressEvent && !mouseBindWasActive; } void CKeybindManager::resizeWithBorder(wlr_pointer_button_event* e) { @@ -428,15 +491,15 @@ void CKeybindManager::resizeWithBorder(wlr_pointer_button_event* e) { } void CKeybindManager::onSwitchEvent(const std::string& switchName) { - handleKeybinds(0, "switch:" + switchName, 0, 0, true, 0); + handleKeybinds(0, SPressedKeyWithMods{.keyName = "switch:" + switchName}, true); } void CKeybindManager::onSwitchOnEvent(const std::string& switchName) { - handleKeybinds(0, "switch:on:" + switchName, 0, 0, true, 0); + handleKeybinds(0, SPressedKeyWithMods{.keyName = "switch:on:" + switchName}, true); } void CKeybindManager::onSwitchOffEvent(const std::string& switchName) { - handleKeybinds(0, "switch:off:" + switchName, 0, 0, true, 0); + handleKeybinds(0, SPressedKeyWithMods{.keyName = "switch:off:" + switchName}, true); } int repeatKeyHandler(void* data) { @@ -455,7 +518,7 @@ int repeatKeyHandler(void* data) { return 0; } -bool CKeybindManager::handleKeybinds(const uint32_t& modmask, const std::string& key, const xkb_keysym_t& keysym, const int& keycode, bool pressed, uint32_t time) { +bool CKeybindManager::handleKeybinds(const uint32_t modmask, const SPressedKeyWithMods& key, bool pressed) { bool found = false; if (g_pCompositor->m_sSeat.exclusiveClient) @@ -468,25 +531,23 @@ bool CKeybindManager::handleKeybinds(const uint32_t& modmask, const std::string& const bool IGNORECONDITIONS = SPECIALDISPATCHER && !pressed && SPECIALTRIGGERED; // ignore mods. Pass, global dispatchers should be released immediately once the key is released. - if (!IGNORECONDITIONS && (modmask != k.modmask || (g_pCompositor->m_sSeat.exclusiveClient && !k.locked) || k.submap != m_szCurrentSelectedSubmap || k.shadowed)) + if (!IGNORECONDITIONS && + ((modmask != k.modmask && !k.ignoreMods) || (g_pCompositor->m_sSeat.exclusiveClient && !k.locked) || k.submap != m_szCurrentSelectedSubmap || k.shadowed)) continue; - if (!key.empty()) { - if (key != k.key) + if (!key.keyName.empty()) { + if (key.keyName != k.key) continue; - } else if (k.keycode != -1) { - if (keycode != k.keycode) + } else if (k.keycode != 0) { + if (key.keycode != k.keycode) continue; } else { - if (keysym == 0) - continue; // this is a keycode check run - // oMg such performance hit!!11! // this little maneouver is gonna cost us 4µs const auto KBKEY = xkb_keysym_from_name(k.key.c_str(), XKB_KEYSYM_CASE_INSENSITIVE); const auto KBKEYUPPER = xkb_keysym_to_upper(KBKEY); - if (keysym != KBKEY && keysym != KBKEYUPPER) + if (key.keysym != KBKEY && key.keysym != KBKEYUPPER) continue; } @@ -498,12 +559,24 @@ bool CKeybindManager::handleKeybinds(const uint32_t& modmask, const std::string& continue; } - if (!pressed && !k.release && !SPECIALDISPATCHER) { - if (k.nonConsuming) - continue; + if (!pressed) { + // Require mods to be matching when the key was first pressed. + if (key.modmaskAtPressTime != modmask) { + // Handle properly `bindr` where a key is itself a bind mod for example: + // "bindr = SUPER, SUPER_L, exec, $launcher". + // This needs to be handled separately for the above case, because `key.modmaskAtPressTime` is set + // from currently pressed keys as programs see them, but it doesn't yet include the currently + // pressed mod key, which is still being handled internally. + if (keycodeToModifier(key.keycode) == key.modmaskAtPressTime) + continue; - found = true; // suppress the event - continue; + } else if (!k.release && !SPECIALDISPATCHER) { + if (k.nonConsuming) + continue; + + found = true; // suppress the event + continue; + } } const auto DISPATCHER = m_mDispatchers.find(k.mouse ? "mouse" : k.handler); @@ -518,7 +591,7 @@ bool CKeybindManager::handleKeybinds(const uint32_t& modmask, const std::string& Debug::log(ERR, "Invalid handler in a keybind! (handler {} does not exist)", k.handler); } else { // call the dispatcher - Debug::log(LOG, "Keybind triggered, calling dispatcher ({}, {}, {})", modmask, key, keysym); + Debug::log(LOG, "Keybind triggered, calling dispatcher ({}, {}, {})", modmask, key.keyName, key.keysym); m_iPassPressed = (int)pressed; @@ -551,7 +624,7 @@ bool CKeybindManager::handleKeybinds(const uint32_t& modmask, const std::string& return found; } -void CKeybindManager::shadowKeybinds(const xkb_keysym_t& doesntHave, const int& doesntHaveCode) { +void CKeybindManager::shadowKeybinds(const xkb_keysym_t& doesntHave, const uint32_t doesntHaveCode) { // shadow disables keybinds after one has been triggered for (auto& k : m_lKeybinds) { @@ -564,22 +637,20 @@ void CKeybindManager::shadowKeybinds(const xkb_keysym_t& doesntHave, const int& const auto KBKEY = xkb_keysym_from_name(k.key.c_str(), XKB_KEYSYM_CASE_INSENSITIVE); const auto KBKEYUPPER = xkb_keysym_to_upper(KBKEY); - for (auto& pk : m_dPressedKeysyms) { - if ((pk == KBKEY || pk == KBKEYUPPER)) { + for (auto& pk : m_dPressedKeys) { + if ((pk.keysym != 0 && (pk.keysym == KBKEY || pk.keysym == KBKEYUPPER))) { shadow = true; - if (pk == doesntHave && doesntHave != 0) { + if (pk.keysym == doesntHave && doesntHave != 0) { shadow = false; break; } } - } - for (auto& pk : m_dPressedKeycodes) { - if (pk == k.keycode) { + if (pk.keycode != 0 && pk.keycode == k.keycode) { shadow = true; - if (pk == doesntHaveCode && doesntHaveCode != 0 && doesntHaveCode != -1) { + if (pk.keycode == doesntHaveCode && doesntHaveCode != 0) { shadow = false; break; } @@ -671,11 +742,6 @@ void CKeybindManager::spawn(std::string args) { args = args.substr(args.find_first_of(']') + 1); } - if (g_pXWaylandManager->m_sWLRXWayland) - args = "WAYLAND_DISPLAY=" + std::string(g_pCompositor->m_szWLDisplaySocket) + " DISPLAY=" + std::string(g_pXWaylandManager->m_sWLRXWayland->display_name) + " " + args; - else - args = "WAYLAND_DISPLAY=" + std::string(g_pCompositor->m_szWLDisplaySocket) + " " + args; - const uint64_t PROC = spawnRaw(args); if (!RULES.empty()) { @@ -765,11 +831,10 @@ void CKeybindManager::clearKeybinds() { void CKeybindManager::toggleActiveFloating(std::string args) { CWindow* PWINDOW = nullptr; - if (args != "" && args != "active" && args.length() > 1) { + if (args != "" && args != "active" && args.length() > 1) PWINDOW = g_pCompositor->getWindowByRegex(args); - } else { + else PWINDOW = g_pCompositor->m_pLastWindow; - } if (!PWINDOW) return; @@ -777,9 +842,6 @@ void CKeybindManager::toggleActiveFloating(std::string args) { // remove drag status g_pInputManager->currentlyDraggedWindow = nullptr; - if (g_pCompositor->isWorkspaceSpecial(PWINDOW->m_iWorkspaceID)) - return; - if (PWINDOW->m_sGroupData.pNextWindow && PWINDOW->m_sGroupData.pNextWindow != PWINDOW) { const auto PCURRENT = PWINDOW->getGroupCurrent(); @@ -790,6 +852,7 @@ void CKeybindManager::toggleActiveFloating(std::string args) { while (curr != PCURRENT) { curr->m_bIsFloating = PCURRENT->m_bIsFloating; curr->updateDynamicRules(); + curr->updateSpecialRenderData(); curr = curr->m_sGroupData.pNextWindow; } } else { @@ -837,12 +900,17 @@ void CKeybindManager::changeworkspace(std::string args) { // the current workspace will instead switch to the previous. static auto* const PBACKANDFORTH = &g_pConfigManager->getConfigValuePtr("binds:workspace_back_and_forth")->intValue; static auto* const PALLOWWORKSPACECYCLES = &g_pConfigManager->getConfigValuePtr("binds:allow_workspace_cycles")->intValue; + static auto* const PWORKSPACECENTERON = &g_pConfigManager->getConfigValuePtr("binds:workspace_center_on")->intValue; - const auto PMONITOR = g_pCompositor->m_pLastMonitor; - const auto PCURRENTWORKSPACE = g_pCompositor->getWorkspaceByID(PMONITOR->activeWorkspace); - const bool EXPLICITPREVIOUS = args.find("previous") == 0; + const auto PMONITOR = g_pCompositor->m_pLastMonitor; - if (args.find("previous") == 0) { + if (!PMONITOR) + return; + + const auto PCURRENTWORKSPACE = g_pCompositor->getWorkspaceByID(PMONITOR->activeWorkspace); + const bool EXPLICITPREVIOUS = args.starts_with("previous"); + + if (args.starts_with("previous")) { // Do nothing if there's no previous workspace, otherwise switch to it. if (PCURRENTWORKSPACE->m_sPrevWorkspace.iID == -1) { Debug::log(LOG, "No previous workspace to change to"); @@ -860,7 +928,7 @@ void CKeybindManager::changeworkspace(std::string args) { workspaceToChangeTo = getWorkspaceIDFromString(args, workspaceName); } - if (workspaceToChangeTo == INT_MAX) { + if (workspaceToChangeTo == WORKSPACE_INVALID) { Debug::log(ERR, "Error in changeworkspace, invalid value"); return; } @@ -896,9 +964,13 @@ void CKeybindManager::changeworkspace(std::string args) { PMONITORWORKSPACEOWNER->changeWorkspace(pWorkspaceToChangeTo, false, true); if (PMONITOR != PMONITORWORKSPACEOWNER) { - g_pCompositor->warpCursorTo(PMONITORWORKSPACEOWNER->middle()); - if (const auto PLAST = pWorkspaceToChangeTo->getLastFocusedWindow(); PLAST) + Vector2D middle = PMONITORWORKSPACEOWNER->middle(); + if (const auto PLAST = pWorkspaceToChangeTo->getLastFocusedWindow(); PLAST) { g_pCompositor->focusWindow(PLAST); + if (*PWORKSPACECENTERON == 1) + middle = PLAST->middle(); + } + g_pCompositor->warpCursorTo(middle); } if (BISWORKSPACECURRENT) { @@ -909,10 +981,12 @@ void CKeybindManager::changeworkspace(std::string args) { } else pWorkspaceToChangeTo->rememberPrevWorkspace(PCURRENTWORKSPACE); - if (!g_pCompositor->m_pLastFocus) - g_pInputManager->simulateMouseMovement(); - else - g_pInputManager->sendMotionEventsToFocused(); + if (!g_pInputManager->m_bLastFocusOnLS) { + if (g_pCompositor->m_pLastFocus) + g_pInputManager->sendMotionEventsToFocused(); + else + g_pInputManager->simulateMouseMovement(); + } } void CKeybindManager::fullscreenActive(std::string args) { @@ -945,7 +1019,7 @@ void CKeybindManager::moveActiveToWorkspace(std::string args) { std::string workspaceName; const auto WORKSPACEID = getWorkspaceIDFromString(args, workspaceName); - if (WORKSPACEID == INT_MAX) { + if (WORKSPACEID == WORKSPACE_INVALID) { Debug::log(LOG, "Invalid workspace in moveActiveToWorkspace"); return; } @@ -974,6 +1048,11 @@ void CKeybindManager::moveActiveToWorkspace(std::string args) { POLDWS->m_pLastFocusedWindow = g_pCompositor->getFirstWindowOnWorkspace(POLDWS->m_iID); + if (pWorkspace->m_bIsSpecialWorkspace) + pMonitor->setSpecialWorkspace(pWorkspace); + else if (POLDWS->m_bIsSpecialWorkspace) + g_pCompositor->getMonitorFromID(POLDWS->m_iMonitorID)->setSpecialWorkspace(nullptr); + pMonitor->changeWorkspace(pWorkspace); g_pCompositor->focusWindow(PWINDOW); @@ -1002,7 +1081,7 @@ void CKeybindManager::moveActiveToWorkspaceSilent(std::string args) { const int WORKSPACEID = getWorkspaceIDFromString(args, workspaceName); - if (WORKSPACEID == INT_MAX) { + if (WORKSPACEID == WORKSPACE_INVALID) { Debug::log(ERR, "Error in moveActiveToWorkspaceSilent, invalid value"); return; } @@ -1022,10 +1101,12 @@ void CKeybindManager::moveActiveToWorkspaceSilent(std::string args) { g_pCompositor->moveWindowToWorkspaceSafe(PWINDOW, pWorkspace); } - if (const auto PATCOORDS = g_pCompositor->vectorToWindowIdeal(OLDMIDDLE); PATCOORDS && PATCOORDS != PWINDOW) - g_pCompositor->focusWindow(PATCOORDS); - else - g_pInputManager->refocus(); + if (PWINDOW == g_pCompositor->m_pLastWindow) { + if (const auto PATCOORDS = g_pCompositor->vectorToWindowIdeal(OLDMIDDLE, PWINDOW); PATCOORDS) + g_pCompositor->focusWindow(PATCOORDS); + else + g_pInputManager->refocus(); + } } void CKeybindManager::moveGroupFocusTo(std::string args) { @@ -1033,7 +1114,7 @@ void CKeybindManager::moveGroupFocusTo(std::string args) { GroupActiveDirection direction = GroupActiveDirection::fromFocusDirection(args); CWindow *pNextWindow = groupActiveInDirection(direction.asString()); - + if( pNextWindow == nullptr || pNextWindow == PWINDOW @@ -1048,7 +1129,8 @@ void CKeybindManager::moveGroupFocusTo(std::string args) { } void CKeybindManager::moveFocusTo(std::string args) { - char arg = args[0]; + static auto* const PFULLCYCLE = &g_pConfigManager->getConfigValuePtr("binds:movefocus_cycles_fullscreen")->intValue; + char arg = args[0]; if (!isDirection(args)) { Debug::log(ERR, "Cannot move focus in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg); @@ -1064,7 +1146,7 @@ void CKeybindManager::moveFocusTo(std::string args) { // remove constraints g_pInputManager->unconstrainMouse(); - const auto PWINDOWTOCHANGETO = PLASTWINDOW->m_bIsFullscreen ? + const auto PWINDOWTOCHANGETO = *PFULLCYCLE && PLASTWINDOW->m_bIsFullscreen ? (arg == 'd' || arg == 'b' || arg == 'r' ? g_pCompositor->getNextWindowOnWorkspace(PLASTWINDOW, true) : g_pCompositor->getPrevWindowOnWorkspace(PLASTWINDOW, true)) : g_pCompositor->getWindowInDirection(PLASTWINDOW, arg); @@ -1135,13 +1217,13 @@ void CKeybindManager::swapActive(std::string args) { return; g_pLayoutManager->getCurrentLayout()->switchWindows(PLASTWINDOW, PWINDOWTOCHANGETO); - g_pCompositor->warpCursorTo(PLASTWINDOW->m_vRealPosition.vec() + PLASTWINDOW->m_vRealSize.vec() / 2.0); + g_pCompositor->warpCursorTo(PLASTWINDOW->middle()); } void CKeybindManager::moveActiveTo(std::string args) { char arg = args[0]; - if (args.find("mon:") == 0) { + if (args.starts_with("mon:")) { const auto PNEWMONITOR = g_pCompositor->getMonitorFromString(args.substr(4)); if (!PNEWMONITOR) return; @@ -1161,20 +1243,20 @@ void CKeybindManager::moveActiveTo(std::string args) { return; if (PLASTWINDOW->m_bIsFloating) { - auto vPos = PLASTWINDOW->m_vRealPosition.goalv(); + Vector2D vPos; const auto PMONITOR = g_pCompositor->getMonitorFromID(PLASTWINDOW->m_iMonitorID); const auto BORDERSIZE = PLASTWINDOW->getRealBorderSize(); switch (arg) { - case 'l': vPos.x = PMONITOR->vecReservedTopLeft.x + BORDERSIZE; break; - case 'r': vPos.x = PMONITOR->vecSize.x - PMONITOR->vecReservedBottomRight.x - PLASTWINDOW->m_vRealSize.goalv().x - BORDERSIZE; break; + case 'l': vPos.x = PMONITOR->vecReservedTopLeft.x + BORDERSIZE + PMONITOR->vecPosition.x; break; + case 'r': vPos.x = PMONITOR->vecSize.x - PMONITOR->vecReservedBottomRight.x - PLASTWINDOW->m_vRealSize.goalv().x - BORDERSIZE + PMONITOR->vecPosition.x; break; case 't': - case 'u': vPos.y = PMONITOR->vecReservedTopLeft.y + BORDERSIZE; break; + case 'u': vPos.y = PMONITOR->vecReservedTopLeft.y + BORDERSIZE + PMONITOR->vecPosition.y; break; case 'b': - case 'd': vPos.y = PMONITOR->vecSize.y - PMONITOR->vecReservedBottomRight.y - PLASTWINDOW->m_vRealSize.goalv().y - BORDERSIZE; break; + case 'd': vPos.y = PMONITOR->vecSize.y - PMONITOR->vecReservedBottomRight.y - PLASTWINDOW->m_vRealSize.goalv().y - BORDERSIZE + PMONITOR->vecPosition.y; break; } - PLASTWINDOW->m_vRealPosition = vPos + PMONITOR->vecPosition; + PLASTWINDOW->m_vRealPosition = Vector2D(vPos.x != 0 ? vPos.x : PLASTWINDOW->m_vRealPosition.goalv().x, vPos.y != 0 ? vPos.y : PLASTWINDOW->m_vRealPosition.goalv().y); return; } @@ -1244,24 +1326,16 @@ void CKeybindManager::toggleSplit(std::string args) { } void CKeybindManager::alterSplitRatio(std::string args) { - float splitratio = 0; - bool exact = false; + std::optional splitResult; + bool exact = false; - if (args == "+" || args == "-") { - Debug::log(LOG, "alterSplitRatio: using LEGACY +/-, consider switching to the Hyprland syntax."); - splitratio = (args == "+" ? 0.05f : -0.05f); - } + if (args.starts_with("exact")) { + exact = true; + splitResult = getPlusMinusKeywordResult(args.substr(5), 0); + } else + splitResult = getPlusMinusKeywordResult(args, 0); - if (splitratio == 0) { - if (args.find("exact") == 0) { - exact = true; - splitratio = getPlusMinusKeywordResult(args.substr(5), 0); - } else { - splitratio = getPlusMinusKeywordResult(args, 0); - } - } - - if (splitratio == INT_MAX) { + if (!splitResult.has_value()) { Debug::log(ERR, "Splitratio invalid in alterSplitRatio!"); return; } @@ -1271,7 +1345,7 @@ void CKeybindManager::alterSplitRatio(std::string args) { if (!PLASTWINDOW) return; - g_pLayoutManager->getCurrentLayout()->alterSplitRatio(PLASTWINDOW, splitratio, exact); + g_pLayoutManager->getCurrentLayout()->alterSplitRatio(PLASTWINDOW, splitResult.value(), exact); } void CKeybindManager::focusMonitor(std::string arg) { @@ -1453,7 +1527,7 @@ void CKeybindManager::moveWorkspaceToMonitor(std::string args) { std::string workspaceName; const int WORKSPACEID = getWorkspaceIDFromString(workspace, workspaceName); - if (WORKSPACEID == INT_MAX) { + if (WORKSPACEID == WORKSPACE_INVALID) { Debug::log(ERR, "moveWorkspaceToMonitor invalid workspace!"); return; } @@ -1468,6 +1542,48 @@ void CKeybindManager::moveWorkspaceToMonitor(std::string args) { g_pCompositor->moveWorkspaceToMonitor(PWORKSPACE, PMONITOR); } +void CKeybindManager::focusWorkspaceOnCurrentMonitor(std::string args) { + std::string workspaceName; + const int WORKSPACEID = getWorkspaceIDFromString(args, workspaceName); + + if (WORKSPACEID == WORKSPACE_INVALID) { + Debug::log(ERR, "focusWorkspaceOnCurrentMonitor invalid workspace!"); + return; + } + + const auto PCURRMONITOR = g_pCompositor->getMonitorFromCursor(); + + if (!PCURRMONITOR) { + Debug::log(ERR, "focusWorkspaceOnCurrentMonitor monitor doesn't exist!"); + return; + } + + auto PWORKSPACE = g_pCompositor->getWorkspaceByID(WORKSPACEID); + + if (!PWORKSPACE) { + PWORKSPACE = g_pCompositor->createNewWorkspace(WORKSPACEID, PCURRMONITOR->ID); + // we can skip the moving, since it's already on the current monitor + changeworkspace(PWORKSPACE->getConfigName()); + return; + } + + if (PWORKSPACE->m_iMonitorID != PCURRMONITOR->ID) { + const auto POLDMONITOR = g_pCompositor->getMonitorFromID(PWORKSPACE->m_iMonitorID); + if (!POLDMONITOR) { // wat + Debug::log(ERR, "focusWorkspaceOnCurrentMonitor old monitor doesn't exist!"); + return; + } + if (POLDMONITOR->activeWorkspace == WORKSPACEID) { + g_pCompositor->swapActiveWorkspaces(POLDMONITOR, PCURRMONITOR); + return; + } else { + g_pCompositor->moveWorkspaceToMonitor(PWORKSPACE, PCURRMONITOR, true); + } + } + + changeworkspace(PWORKSPACE->getConfigName()); +} + void CKeybindManager::toggleSpecialWorkspace(std::string args) { static auto* const PFOLLOWMOUSE = &g_pConfigManager->getConfigValuePtr("input:follow_mouse")->intValue; @@ -1475,7 +1591,7 @@ void CKeybindManager::toggleSpecialWorkspace(std::string args) { std::string workspaceName = ""; int workspaceID = getWorkspaceIDFromString("special:" + args, workspaceName); - if (workspaceID == INT_MAX || !g_pCompositor->isWorkspaceSpecial(workspaceID)) { + if (workspaceID == WORKSPACE_INVALID || !g_pCompositor->isWorkspaceSpecial(workspaceID)) { Debug::log(ERR, "Invalid workspace passed to special"); return; } @@ -1513,7 +1629,7 @@ void CKeybindManager::forceRendererReload(std::string args) { if (!m->output) continue; - auto rule = g_pConfigManager->getMonitorRuleFor(m->szName, m->output->description ? m->output->description : ""); + auto rule = g_pConfigManager->getMonitorRuleFor(m->szName, m->szDescription); if (!g_pHyprRenderer->applyMonitorRule(m.get(), &rule, true)) { overAgain = true; break; @@ -1607,10 +1723,18 @@ void CKeybindManager::circleNext(std::string arg) { return; } - if (arg == "last" || arg == "l" || arg == "prev" || arg == "p") - switchToWindow(g_pCompositor->getPrevWindowOnWorkspace(g_pCompositor->m_pLastWindow, true)); + CVarList args{arg, 0, 's', true}; + + std::optional floatStatus = {}; + if (args.contains("tile") || args.contains("tiled")) + floatStatus = false; + else if (args.contains("float") || args.contains("floating")) + floatStatus = true; + + if (args.contains("prev") || args.contains("p") || args.contains("last") || args.contains("l")) + switchToWindow(g_pCompositor->getPrevWindowOnWorkspace(g_pCompositor->m_pLastWindow, true, floatStatus)); else - switchToWindow(g_pCompositor->getNextWindowOnWorkspace(g_pCompositor->m_pLastWindow, true)); + switchToWindow(g_pCompositor->getNextWindowOnWorkspace(g_pCompositor->m_pLastWindow, true, floatStatus)); } void CKeybindManager::focusWindow(std::string regexp) { @@ -1753,10 +1877,10 @@ void CKeybindManager::toggleOpaque(std::string unused) { } void CKeybindManager::dpms(std::string arg) { - bool enable = arg.find("on") == 0; + bool enable = arg.starts_with("on"); std::string port = ""; - if (arg.find("toggle") == 0) + if (arg.starts_with("toggle")) enable = !std::any_of(g_pCompositor->m_vMonitors.begin(), g_pCompositor->m_vMonitors.end(), [&](const auto& other) { return !other->dpmsStatus; }); // enable if any is off if (arg.find_first_of(' ') != std::string::npos) @@ -1868,25 +1992,13 @@ void CKeybindManager::mouse(std::string args) { const auto mouseCoords = g_pInputManager->getMouseCoordsInternal(); CWindow* pWindow = g_pCompositor->vectorToWindowIdeal(mouseCoords); - if (pWindow && !pWindow->m_bIsFullscreen && !pWindow->hasPopupAt(mouseCoords) && pWindow->m_sGroupData.pNextWindow) { - const wlr_box box = pWindow->getDecorationByType(DECORATION_GROUPBAR)->getWindowDecorationRegion().getExtents(); - if (wlr_box_contains_point(&box, mouseCoords.x, mouseCoords.y)) { - const int SIZE = pWindow->getGroupSize(); - pWindow = pWindow->getGroupWindowByIndex((mouseCoords.x - box.x) * SIZE / box.width); + if (pWindow && !pWindow->m_bIsFullscreen) + pWindow->checkInputOnDecos(INPUT_TYPE_DRAG_START, mouseCoords); - // hack - g_pLayoutManager->getCurrentLayout()->onWindowRemoved(pWindow); - if (!pWindow->m_bIsFloating) { - const bool GROUPSLOCKEDPREV = g_pKeybindManager->m_bGroupsLocked; - g_pKeybindManager->m_bGroupsLocked = true; - g_pLayoutManager->getCurrentLayout()->onWindowCreated(pWindow); - g_pKeybindManager->m_bGroupsLocked = GROUPSLOCKEDPREV; - } - } - } + if (!g_pInputManager->currentlyDraggedWindow) + g_pInputManager->currentlyDraggedWindow = pWindow; - g_pInputManager->currentlyDraggedWindow = pWindow; - g_pInputManager->dragMode = MBIND_MOVE; + g_pInputManager->dragMode = MBIND_MOVE; g_pLayoutManager->getCurrentLayout()->onBeginDragWindow(); } else { g_pKeybindManager->m_bIsMouseBindActive = false; @@ -1946,7 +2058,7 @@ void CKeybindManager::alterZOrder(std::string args) { else if (POSITION == "bottom") g_pCompositor->changeWindowZOrder(PWINDOW, 0); else { - Debug::log(ERR, "alterZOrder: bad position: %s", POSITION); + Debug::log(ERR, "alterZOrder: bad position: {}", POSITION); return; } @@ -1957,8 +2069,7 @@ void CKeybindManager::fakeFullscreenActive(std::string args) { if (g_pCompositor->m_pLastWindow) { // will also set the flag g_pCompositor->m_pLastWindow->m_bFakeFullscreenState = !g_pCompositor->m_pLastWindow->m_bFakeFullscreenState; - g_pXWaylandManager->setWindowFullscreen(g_pCompositor->m_pLastWindow, - g_pCompositor->m_pLastWindow->m_bFakeFullscreenState || g_pCompositor->m_pLastWindow->m_bIsFullscreen); + g_pXWaylandManager->setWindowFullscreen(g_pCompositor->m_pLastWindow, g_pCompositor->m_pLastWindow->shouldSendFullscreenState()); } } @@ -1995,12 +2106,9 @@ void CKeybindManager::moveWindowIntoGroup(CWindow* pWindow, CWindow* pWindowInDi if (pWindow->m_sGroupData.deny) return; - if (!pWindow->m_sGroupData.pNextWindow) - pWindow->m_dWindowDecorations.emplace_back(std::make_unique(pWindow)); - g_pLayoutManager->getCurrentLayout()->onWindowRemoved(pWindow); // This removes groupped property! - static const auto* USECURRPOS = &g_pConfigManager->getConfigValuePtr("misc:group_insert_after_current")->intValue; + static const auto* USECURRPOS = &g_pConfigManager->getConfigValuePtr("group:insert_after_current")->intValue; pWindowInDirection = *USECURRPOS ? pWindowInDirection : pWindowInDirection->getGroupTail(); pWindowInDirection->insertWindowToGroup(pWindow); @@ -2009,11 +2117,13 @@ void CKeybindManager::moveWindowIntoGroup(CWindow* pWindow, CWindow* pWindowInDi g_pLayoutManager->getCurrentLayout()->recalculateWindow(pWindow); g_pCompositor->focusWindow(pWindow); g_pCompositor->warpCursorTo(pWindow->middle()); + + if (!pWindow->getDecorationByType(DECORATION_GROUPBAR)) + pWindow->addWindowDeco(std::make_unique(pWindow)); } void CKeybindManager::moveWindowOutOfGroup(CWindow* pWindow, const std::string& dir) { - - static auto* const BFOCUSREMOVEDWINDOW = &g_pConfigManager->getConfigValuePtr("misc:group_focus_removed_window")->intValue; + static auto* const BFOCUSREMOVEDWINDOW = &g_pConfigManager->getConfigValuePtr("group:focus_removed_window")->intValue; const auto PWINDOWPREV = pWindow->getGroupPrevious(); eDirection direction; @@ -2052,7 +2162,10 @@ void CKeybindManager::moveWindowOutOfGroup(CWindow* pWindow, const std::string& void CKeybindManager::moveIntoGroup(std::string args) { char arg = args[0]; - static auto* const BIGNOREGROUPLOCK = &g_pConfigManager->getConfigValuePtr("binds:ignore_group_lock")->intValue; + static auto* const PIGNOREGROUPLOCK = &g_pConfigManager->getConfigValuePtr("binds:ignore_group_lock")->intValue; + + if (!*PIGNOREGROUPLOCK && g_pKeybindManager->m_bGroupsLocked) + return; if (!isDirection(args)) { Debug::log(ERR, "Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg); @@ -2070,13 +2183,18 @@ void CKeybindManager::moveIntoGroup(std::string args) { return; // Do not move window into locked group if binds:ignore_group_lock is false - if (!*BIGNOREGROUPLOCK && (PWINDOWINDIR->getGroupHead()->m_sGroupData.locked || (PWINDOW->m_sGroupData.pNextWindow && PWINDOW->getGroupHead()->m_sGroupData.locked))) + if (!*PIGNOREGROUPLOCK && (PWINDOWINDIR->getGroupHead()->m_sGroupData.locked || (PWINDOW->m_sGroupData.pNextWindow && PWINDOW->getGroupHead()->m_sGroupData.locked))) return; moveWindowIntoGroup(PWINDOW, PWINDOWINDIR); } void CKeybindManager::moveOutOfGroup(std::string args) { + static auto* const PIGNOREGROUPLOCK = &g_pConfigManager->getConfigValuePtr("binds:ignore_group_lock")->intValue; + + if (!*PIGNOREGROUPLOCK && g_pKeybindManager->m_bGroupsLocked) + return; + const auto PWINDOW = g_pCompositor->m_pLastWindow; if (!PWINDOW || !PWINDOW->m_sGroupData.pNextWindow) @@ -2091,13 +2209,19 @@ void CKeybindManager::moveWindowOrGroup(std::string args) { static auto* const PIGNOREGROUPLOCK = &g_pConfigManager->getConfigValuePtr("binds:ignore_group_lock")->intValue; if (!isDirection(args)) { - Debug::log(ERR, "Cannot move into group in direction %c, unsupported direction. Supported: l,r,u/t,d/b", arg); + Debug::log(ERR, "Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg); return; } const auto PWINDOW = g_pCompositor->m_pLastWindow; if (!PWINDOW || PWINDOW->m_bIsFullscreen) return; + + if (!*PIGNOREGROUPLOCK && g_pKeybindManager->m_bGroupsLocked) { + g_pLayoutManager->getCurrentLayout()->moveWindowTo(PWINDOW, args); + return; + } + const auto PWINDOWINDIR = g_pCompositor->getWindowInDirection(PWINDOW, arg); const bool ISWINDOWGROUP = PWINDOW->m_sGroupData.pNextWindow; diff --git a/src/managers/KeybindManager.hpp b/src/managers/KeybindManager.hpp index 009cb08f..4c97be03 100644 --- a/src/managers/KeybindManager.hpp +++ b/src/managers/KeybindManager.hpp @@ -12,7 +12,7 @@ class CPluginSystem; struct SKeybind { std::string key = ""; - int keycode = -1; + uint32_t keycode = 0; uint32_t modmask = 0; std::string handler = ""; std::string arg = ""; @@ -23,6 +23,7 @@ struct SKeybind { bool mouse = false; bool nonConsuming = false; bool transparent = false; + bool ignoreMods = false; // DO NOT INITIALIZE bool shadowed = false; @@ -35,6 +36,14 @@ enum eFocusWindowMode { MODE_PID }; +struct SPressedKeyWithMods { + std::string keyName = ""; + xkb_keysym_t keysym = 0; + uint32_t keycode = 0; + uint32_t modmaskAtPressTime = 0; + bool sent = false; +}; + class CKeybindManager { public: CKeybindManager(); @@ -50,8 +59,9 @@ class CKeybindManager { void addKeybind(SKeybind); void removeKeybind(uint32_t, const std::string&); uint32_t stringToModMask(std::string); + uint32_t keycodeToModifier(xkb_keycode_t); void clearKeybinds(); - void shadowKeybinds(const xkb_keysym_t& doesntHave = 0, const int& doesntHaveCode = 0); + void shadowKeybinds(const xkb_keysym_t& doesntHave = 0, const uint32_t doesntHaveCode = 0); std::unordered_map> m_mDispatchers; @@ -62,39 +72,38 @@ class CKeybindManager { std::list m_lKeybinds; private: - std::deque m_dPressedKeysyms; - std::deque m_dPressedKeycodes; + std::deque m_dPressedKeys; - inline static std::string m_szCurrentSelectedSubmap = ""; + inline static std::string m_szCurrentSelectedSubmap = ""; - SKeybind* m_pActiveKeybind = nullptr; + SKeybind* m_pActiveKeybind = nullptr; - uint32_t m_uTimeLastMs = 0; - uint32_t m_uLastCode = 0; - uint32_t m_uLastMouseCode = 0; + uint32_t m_uTimeLastMs = 0; + uint32_t m_uLastCode = 0; + uint32_t m_uLastMouseCode = 0; - bool m_bIsMouseBindActive = false; - std::vector m_vPressedSpecialBinds; + bool m_bIsMouseBindActive = false; + std::vector m_vPressedSpecialBinds; - int m_iPassPressed = -1; // used for pass + int m_iPassPressed = -1; // used for pass - CTimer m_tScrollTimer; + CTimer m_tScrollTimer; - bool handleKeybinds(const uint32_t&, const std::string&, const xkb_keysym_t&, const int&, bool, uint32_t); + bool handleKeybinds(const uint32_t, const SPressedKeyWithMods&, bool); - bool handleInternalKeybinds(xkb_keysym_t); - bool handleVT(xkb_keysym_t); + bool handleInternalKeybinds(xkb_keysym_t); + bool handleVT(xkb_keysym_t); - xkb_state* m_pXKBTranslationState = nullptr; + xkb_state* m_pXKBTranslationState = nullptr; - void updateXKBTranslationState(); - bool ensureMouseBindState(); + void updateXKBTranslationState(); + bool ensureMouseBindState(); - static bool tryMoveFocusToMonitor(CMonitor* monitor); - static void moveWindowOutOfGroup(CWindow* pWindow, const std::string& dir = ""); - static void moveWindowIntoGroup(CWindow* pWindow, CWindow* pWindowInDirection); - static void switchToWindow(CWindow* PWINDOWTOCHANGETO); - static CWindow* groupActiveInDirection(std::string args); + static bool tryMoveFocusToMonitor(CMonitor* monitor); + static void moveWindowOutOfGroup(CWindow* pWindow, const std::string& dir = ""); + static void moveWindowIntoGroup(CWindow* pWindow, CWindow* pWindowInDirection); + static void switchToWindow(CWindow* PWINDOWTOCHANGETO); + static CWindow* groupActiveInDirection(std::string args); // -------------- Dispatchers -------------- // static void killActive(std::string); @@ -127,6 +136,7 @@ class CKeybindManager { static void exitHyprland(std::string); static void moveCurrentWorkspaceToMonitor(std::string); static void moveWorkspaceToMonitor(std::string); + static void focusWorkspaceOnCurrentMonitor(std::string); static void toggleSpecialWorkspace(std::string); static void forceRendererReload(std::string); static void resizeActive(std::string); diff --git a/src/managers/LayoutManager.cpp b/src/managers/LayoutManager.cpp index bf4ca8bd..70c2d2a3 100644 --- a/src/managers/LayoutManager.cpp +++ b/src/managers/LayoutManager.cpp @@ -51,3 +51,10 @@ bool CLayoutManager::removeLayout(IHyprLayout* layout) { return true; } + +std::vector CLayoutManager::getAllLayoutNames() { + std::vector results(m_vLayouts.size()); + for (size_t i = 0; i < m_vLayouts.size(); ++i) + results[i] = m_vLayouts[i].first; + return results; +} diff --git a/src/managers/LayoutManager.hpp b/src/managers/LayoutManager.hpp index 1ebe5711..78d86797 100644 --- a/src/managers/LayoutManager.hpp +++ b/src/managers/LayoutManager.hpp @@ -7,16 +7,16 @@ class CLayoutManager { public: CLayoutManager(); - IHyprLayout* getCurrentLayout(); + IHyprLayout* getCurrentLayout(); - void switchToLayout(std::string); + void switchToLayout(std::string); - bool addLayout(const std::string& name, IHyprLayout* layout); - bool removeLayout(IHyprLayout* layout); + bool addLayout(const std::string& name, IHyprLayout* layout); + bool removeLayout(IHyprLayout* layout); + std::vector getAllLayoutNames(); private: - enum HYPRLAYOUTS - { + enum HYPRLAYOUTS { LAYOUT_DWINDLE = 0, LAYOUT_MASTER }; @@ -25,8 +25,7 @@ class CLayoutManager { CHyprDwindleLayout m_cDwindleLayout; CHyprMasterLayout m_cMasterLayout; - std::vector> m_vLayouts; }; -inline std::unique_ptr g_pLayoutManager; \ No newline at end of file +inline std::unique_ptr g_pLayoutManager; diff --git a/src/managers/XWaylandManager.cpp b/src/managers/XWaylandManager.cpp index 39331e9c..63b8cca0 100644 --- a/src/managers/XWaylandManager.cpp +++ b/src/managers/XWaylandManager.cpp @@ -45,10 +45,11 @@ void CHyprXWaylandManager::activateSurface(wlr_surface* pSurface, bool activate) wlr_xdg_toplevel_set_activated(PSURF->toplevel, activate); } else if (wlr_xwayland_surface_try_from_wlr_surface(pSurface)) { - wlr_xwayland_surface_activate(wlr_xwayland_surface_try_from_wlr_surface(pSurface), activate); + const auto XSURF = wlr_xwayland_surface_try_from_wlr_surface(pSurface); + wlr_xwayland_surface_activate(XSURF, activate); - if (activate) - wlr_xwayland_surface_restack(wlr_xwayland_surface_try_from_wlr_surface(pSurface), nullptr, XCB_STACK_MODE_ABOVE); + if (activate && !XSURF->override_redirect) + wlr_xwayland_surface_restack(XSURF, nullptr, XCB_STACK_MODE_ABOVE); } } @@ -58,7 +59,8 @@ void CHyprXWaylandManager::activateWindow(CWindow* pWindow, bool activate) { if (activate) { wlr_xwayland_surface_set_minimized(pWindow->m_uSurface.xwayland, false); - wlr_xwayland_surface_restack(pWindow->m_uSurface.xwayland, nullptr, XCB_STACK_MODE_ABOVE); + if (!pWindow->m_uSurface.xwayland->override_redirect) + wlr_xwayland_surface_restack(pWindow->m_uSurface.xwayland, nullptr, XCB_STACK_MODE_ABOVE); } wlr_xwayland_surface_activate(pWindow->m_uSurface.xwayland, activate); @@ -74,7 +76,7 @@ void CHyprXWaylandManager::activateWindow(CWindow* pWindow, bool activate) { g_pCompositor->getWorkspaceByID(pWindow->m_iWorkspaceID)->m_pLastFocusedWindow = pWindow; } -void CHyprXWaylandManager::getGeometryForWindow(CWindow* pWindow, wlr_box* pbox) { +void CHyprXWaylandManager::getGeometryForWindow(CWindow* pWindow, CBox* pbox) { if (pWindow->m_bIsX11) { const auto SIZEHINTS = pWindow->m_uSurface.xwayland->size_hints; @@ -89,8 +91,10 @@ void CHyprXWaylandManager::getGeometryForWindow(CWindow* pWindow, wlr_box* pbox) pbox->width = pWindow->m_uSurface.xwayland->width; pbox->height = pWindow->m_uSurface.xwayland->height; } - } else - wlr_xdg_surface_get_geometry(pWindow->m_uSurface.xdg, pbox); + } else { + wlr_xdg_surface_get_geometry(pWindow->m_uSurface.xdg, pbox->pWlr()); + pbox->applyFromWlr(); + } } std::string CHyprXWaylandManager::getTitle(CWindow* pWindow) { @@ -115,7 +119,7 @@ std::string CHyprXWaylandManager::getTitle(CWindow* pWindow) { } std::string CHyprXWaylandManager::getAppIDClass(CWindow* pWindow) { - if (!pWindow->m_bMappedX11 || !pWindow->m_bIsMapped) + if (!pWindow->m_bIsMapped) return ""; try { @@ -146,44 +150,43 @@ void CHyprXWaylandManager::setWindowSize(CWindow* pWindow, Vector2D size, bool f static auto* const PXWLFORCESCALEZERO = &g_pConfigManager->getConfigValuePtr("xwayland:force_zero_scaling")->intValue; const auto PMONITOR = g_pCompositor->getMonitorFromID(pWindow->m_iMonitorID); - if (!PMONITOR) - return; // calculate pos // TODO: this should be decoupled from setWindowSize IMO Vector2D windowPos = pWindow->m_vRealPosition.vec(); - if (pWindow->m_bIsX11) { + if (pWindow->m_bIsX11 && PMONITOR) { windowPos = windowPos - PMONITOR->vecPosition; // normalize to monitor if (*PXWLFORCESCALEZERO) windowPos = windowPos * PMONITOR->scale; // scale if applicable windowPos = windowPos + PMONITOR->vecXWaylandPosition; // move to correct position for xwayland } - if (!force && ((pWindow->m_vReportedSize == size && windowPos == pWindow->m_vReportedPosition) || (pWindow->m_vReportedSize == size && !pWindow->m_bIsX11))) + if (!force && ((pWindow->m_vPendingReportedSize == size && windowPos == pWindow->m_vReportedPosition) || (pWindow->m_vPendingReportedSize == size && !pWindow->m_bIsX11))) return; - pWindow->m_vReportedPosition = windowPos; - pWindow->m_vReportedSize = size; + pWindow->m_vReportedPosition = windowPos; + pWindow->m_vPendingReportedSize = size; pWindow->m_fX11SurfaceScaledBy = 1.f; - if (*PXWLFORCESCALEZERO && pWindow->m_bIsX11) { - if (const auto PMONITOR = g_pCompositor->getMonitorFromID(pWindow->m_iMonitorID); PMONITOR) { - size = size * PMONITOR->scale; - pWindow->m_fX11SurfaceScaledBy = PMONITOR->scale; - } + if (*PXWLFORCESCALEZERO && pWindow->m_bIsX11 && PMONITOR) { + size = size * PMONITOR->scale; + pWindow->m_fX11SurfaceScaledBy = PMONITOR->scale; } if (pWindow->m_bIsX11) wlr_xwayland_surface_configure(pWindow->m_uSurface.xwayland, windowPos.x, windowPos.y, size.x, size.y); else - wlr_xdg_toplevel_set_size(pWindow->m_uSurface.xdg->toplevel, size.x, size.y); + pWindow->m_vPendingSizeAcks.push_back(std::make_pair<>(wlr_xdg_toplevel_set_size(pWindow->m_uSurface.xdg->toplevel, size.x, size.y), size.floor())); } void CHyprXWaylandManager::setWindowStyleTiled(CWindow* pWindow, uint32_t edgez) { - if (!pWindow->m_bIsX11) - wlr_xdg_toplevel_set_tiled(pWindow->m_uSurface.xdg->toplevel, edgez); + if (pWindow->m_bIsX11) + return; + + wlr_xdg_toplevel_set_tiled(pWindow->m_uSurface.xdg->toplevel, edgez); + wlr_xdg_toplevel_set_maximized(pWindow->m_uSurface.xdg->toplevel, true); } wlr_surface* CHyprXWaylandManager::surfaceAt(CWindow* pWindow, const Vector2D& client, Vector2D& surface) { diff --git a/src/managers/XWaylandManager.hpp b/src/managers/XWaylandManager.hpp index 807f0593..e41313e0 100644 --- a/src/managers/XWaylandManager.hpp +++ b/src/managers/XWaylandManager.hpp @@ -15,7 +15,7 @@ class CHyprXWaylandManager { wlr_surface* getWindowSurface(CWindow*); void activateSurface(wlr_surface*, bool); void activateWindow(CWindow*, bool); - void getGeometryForWindow(CWindow*, wlr_box*); + void getGeometryForWindow(CWindow*, CBox*); std::string getTitle(CWindow*); std::string getAppIDClass(CWindow*); void sendCloseWindow(CWindow*); diff --git a/src/managers/input/InputManager.cpp b/src/managers/input/InputManager.cpp index ab633bc5..8bfdb65c 100644 --- a/src/managers/input/InputManager.cpp +++ b/src/managers/input/InputManager.cpp @@ -3,6 +3,18 @@ #include "wlr/types/wlr_switch.h" #include +CInputManager::~CInputManager() { + m_lConstraints.clear(); + m_lKeyboards.clear(); + m_lMice.clear(); + m_lTablets.clear(); + m_lTabletTools.clear(); + m_lTabletPads.clear(); + m_lIdleInhibitors.clear(); + m_lTouchDevices.clear(); + m_lSwitches.clear(); +} + void CInputManager::onMouseMoved(wlr_pointer_motion_event* e) { static auto* const PSENS = &g_pConfigManager->getConfigValuePtr("general:sensitivity")->floatValue; static auto* const PNOACCEL = &g_pConfigManager->getConfigValuePtr("input:force_no_accel")->intValue; @@ -41,8 +53,6 @@ void CInputManager::simulateMouseMovement() { clock_gettime(CLOCK_MONOTONIC, &now); m_vLastCursorPosFloored = m_vLastCursorPosFloored - Vector2D(1, 1); // hack: force the mouseMoveUnified to report without making this a refocus. mouseMoveUnified(now.tv_sec * 1000 + now.tv_nsec / 10000000); - - m_tmrLastCursorMovement.reset(); } void CInputManager::sendMotionEventsToFocused() { @@ -67,15 +77,11 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { static auto* const PMOUSEREFOCUS = &g_pConfigManager->getConfigValuePtr("input:mouse_refocus")->intValue; static auto* const PMOUSEDPMS = &g_pConfigManager->getConfigValuePtr("misc:mouse_move_enables_dpms")->intValue; static auto* const PFOLLOWONDND = &g_pConfigManager->getConfigValuePtr("misc:always_follow_on_dnd")->intValue; - static auto* const PHOGFOCUS = &g_pConfigManager->getConfigValuePtr("misc:layers_hog_keyboard_focus")->intValue; static auto* const PFLOATBEHAVIOR = &g_pConfigManager->getConfigValuePtr("input:float_switch_override_focus")->intValue; static auto* const PMOUSEFOCUSMON = &g_pConfigManager->getConfigValuePtr("misc:mouse_move_focuses_monitor")->intValue; static auto* const PRESIZEONBORDER = &g_pConfigManager->getConfigValuePtr("general:resize_on_border")->intValue; - static auto* const PBORDERSIZE = &g_pConfigManager->getConfigValuePtr("general:border_size")->intValue; - static auto* const PBORDERGRABEXTEND = &g_pConfigManager->getConfigValuePtr("general:extend_border_grab_area")->intValue; static auto* const PRESIZECURSORICON = &g_pConfigManager->getConfigValuePtr("general:hover_icon_on_border")->intValue; static auto* const PZOOMFACTOR = &g_pConfigManager->getConfigValuePtr("misc:cursor_zoom_factor")->floatValue; - const auto BORDER_GRAB_AREA = *PRESIZEONBORDER ? *PBORDERSIZE + *PBORDERGRABEXTEND : 0; const auto FOLLOWMOUSE = *PFOLLOWONDND && m_sDrag.drag ? 1 : *PFOLLOWMOUSE; @@ -102,11 +108,11 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { if (MOUSECOORDSFLOORED == m_vLastCursorPosFloored && !refocus) return; + EMIT_HOOK_EVENT_CANCELLABLE("mouseMove", MOUSECOORDSFLOORED); + if (time) g_pCompositor->notifyIdleActivity(); - EMIT_HOOK_EVENT("mouseMove", MOUSECOORDSFLOORED); - m_vLastCursorPosFloored = MOUSECOORDSFLOORED; const auto PMONITOR = g_pCompositor->getMonitorFromCursor(); @@ -118,7 +124,8 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { if (*PZOOMFACTOR != 1.f) g_pHyprRenderer->damageMonitor(PMONITOR); - g_pCompositor->scheduleFrameForMonitor(PMONITOR); + if (!PMONITOR->solitaryClient && g_pHyprRenderer->shouldRenderCursor() && PMONITOR->output->software_cursor_locks > 0) + g_pCompositor->scheduleFrameForMonitor(PMONITOR); CWindow* forcedFocus = m_pForcedFocus; @@ -187,7 +194,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { // update stuff updateDragIcon(); - if (!m_sDrag.drag && !m_lCurrentlyHeldButtons.empty() && g_pCompositor->m_pLastFocus) { + if (!m_sDrag.drag && !m_lCurrentlyHeldButtons.empty() && g_pCompositor->m_pLastFocus && m_pLastMouseSurface) { if (m_bLastFocusOnLS) { foundSurface = m_pLastMouseSurface; pFoundLayerSurface = g_pCompositor->getLayerSurfaceFromSurface(foundSurface); @@ -226,10 +233,20 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { surfacePos = PMONITOR->vecPosition; } - // overlay is above fullscreen + // overlays are above fullscreen if (!foundSurface) foundSurface = g_pCompositor->vectorToLayerSurface(mouseCoords, &PMONITOR->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], &surfaceCoords, &pFoundLayerSurface); + // also IME popups + if (!foundSurface) { + auto popup = g_pCompositor->vectorToIMEPopup(mouseCoords, m_sIMERelay.m_lIMEPopups); + if (popup) { + foundSurface = popup->pSurface->surface; + surfacePos = Vector2D(popup->realX, popup->realY); + } + } + + // also top layers if (!foundSurface) foundSurface = g_pCompositor->vectorToLayerSurface(mouseCoords, &PMONITOR->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], &surfaceCoords, &pFoundLayerSurface); @@ -302,23 +319,25 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { if (!foundSurface) { if (!m_bEmptyFocusCursorSet) { - m_eBorderIconDirection = BORDERICON_NONE; - if (g_pHyprRenderer->m_bHasARenderedCursor) { - // TODO: maybe wrap? - if (m_ecbClickBehavior == CLICKMODE_KILL) - wlr_cursor_set_xcursor(g_pCompositor->m_sWLRCursor, g_pCompositor->m_sWLRXCursorMgr, "crosshair"); - else - wlr_cursor_set_xcursor(g_pCompositor->m_sWLRCursor, g_pCompositor->m_sWLRXCursorMgr, "left_ptr"); + if (*PRESIZEONBORDER && *PRESIZECURSORICON && m_eBorderIconDirection != BORDERICON_NONE) { + m_eBorderIconDirection = BORDERICON_NONE; + unsetCursorImage(); } + // TODO: maybe wrap? + if (m_ecbClickBehavior == CLICKMODE_KILL) + setCursorImageOverride("crosshair"); + else + setCursorImageOverride("left_ptr"); + m_bEmptyFocusCursorSet = true; } wlr_seat_pointer_clear_focus(g_pCompositor->m_sSeat.seat); + m_pLastMouseSurface = nullptr; - if (refocus) { // if we are forcing a refocus, and we don't find a surface, clear the kb focus too! + if (refocus || !g_pCompositor->m_pLastWindow) // if we are forcing a refocus, and we don't find a surface, clear the kb focus too! g_pCompositor->focusWindow(nullptr); - } return; } @@ -340,12 +359,11 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { bool allowKeyboardRefocus = true; - if (*PHOGFOCUS && !refocus && g_pCompositor->m_pLastFocus) { + if (g_pCompositor->m_pLastFocus) { const auto PLS = g_pCompositor->getLayerSurfaceFromSurface(g_pCompositor->m_pLastFocus); - if (PLS && PLS->layerSurface->current.keyboard_interactive) { + if (PLS && PLS->layerSurface->current.keyboard_interactive == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE) allowKeyboardRefocus = false; - } } // set the values for use @@ -361,6 +379,14 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { return; } + if (pFoundWindow && foundSurface == pFoundWindow->m_pWLSurface.wlr() && !m_bCursorImageOverridden) { + const auto BOX = pFoundWindow->getWindowMainSurfaceBox(); + if (!VECINRECT(mouseCoords, BOX.x, BOX.y, BOX.x + BOX.width, BOX.y + BOX.height)) + setCursorImageOverride("left_ptr"); + else + restoreCursorIconToApp(); + } + if (pFoundWindow) { // change cursor icon if hovering over border if (*PRESIZEONBORDER && *PRESIZECURSORICON) { @@ -371,18 +397,6 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { } } - // if we're on an input deco, reset cursor. Don't on overridden - // if (!m_bCursorImageOverridden) { - // if (!VECINRECT(m_vLastCursorPosFloored, pFoundWindow->m_vRealPosition.vec().x, pFoundWindow->m_vRealPosition.vec().y, - // pFoundWindow->m_vRealPosition.vec().x + pFoundWindow->m_vRealSize.vec().x, pFoundWindow->m_vRealPosition.vec().y + pFoundWindow->m_vRealSize.vec().y)) { - // wlr_cursor_set_xcursor(g_pCompositor->m_sWLRCursor, g_pCompositor->m_sWLRXCursorMgr, "left_ptr"); - // cursorSurfaceInfo.bUsed = false; - // } else if (!cursorSurfaceInfo.bUsed) { - // cursorSurfaceInfo.bUsed = true; - // wlr_cursor_set_surface(g_pCompositor->m_sWLRCursor, cursorSurfaceInfo.pSurface, cursorSurfaceInfo.vHotspot.x, cursorSurfaceInfo.vHotspot.y); - // } - // } - if (FOLLOWMOUSE != 1 && !refocus) { if (pFoundWindow != g_pCompositor->m_pLastWindow && g_pCompositor->m_pLastWindow && ((pFoundWindow->m_bIsFloating && *PFLOATBEHAVIOR == 2) || (g_pCompositor->m_pLastWindow->m_bIsFloating != pFoundWindow->m_bIsFloating && *PFLOATBEHAVIOR != 0))) { @@ -397,11 +411,8 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { } if (pFoundWindow == g_pCompositor->m_pLastWindow) { - if (foundSurface != g_pCompositor->m_pLastFocus || m_bLastFocusOnLS) { - // ^^^ changed the subsurface ^^^ came back from a LS - m_pLastMouseSurface = foundSurface; - wlr_seat_pointer_notify_enter(g_pCompositor->m_sSeat.seat, foundSurface, surfaceLocal.x, surfaceLocal.y); - } + m_pLastMouseSurface = foundSurface; + wlr_seat_pointer_notify_enter(g_pCompositor->m_sSeat.seat, foundSurface, surfaceLocal.x, surfaceLocal.y); } if (FOLLOWMOUSE != 0 || pFoundWindow == g_pCompositor->m_pLastWindow) @@ -410,7 +421,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { m_bLastFocusOnLS = false; return; // don't enter any new surfaces } else { - if (((FOLLOWMOUSE != 3 && allowKeyboardRefocus) && (*PMOUSEREFOCUS || m_pLastMouseFocus != pFoundWindow)) || refocus) { + if (allowKeyboardRefocus && ((FOLLOWMOUSE != 3 && (*PMOUSEREFOCUS || m_pLastMouseFocus != pFoundWindow)) || refocus)) { m_pLastMouseFocus = pFoundWindow; g_pCompositor->focusWindow(pFoundWindow, foundSurface); } @@ -424,7 +435,8 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { } if (pFoundLayerSurface && - (pFoundLayerSurface->layerSurface->current.keyboard_interactive || (pFoundLayerSurface->layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP && !g_pCompositor->m_pLastWindow)) && + (pFoundLayerSurface->layerSurface->current.keyboard_interactive != ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE || + (pFoundLayerSurface->layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP && !g_pCompositor->m_pLastWindow)) && FOLLOWMOUSE != 3 && allowKeyboardRefocus) { g_pCompositor->focusSurface(foundSurface); } @@ -439,9 +451,9 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { } void CInputManager::onMouseButton(wlr_pointer_button_event* e) { - g_pCompositor->notifyIdleActivity(); + EMIT_HOOK_EVENT_CANCELLABLE("mouseButton", e); - EMIT_HOOK_EVENT("mouseButton", e); + g_pCompositor->notifyIdleActivity(); m_tmrLastCursorMovement.reset(); @@ -471,40 +483,71 @@ void CInputManager::onMouseButton(wlr_pointer_button_event* e) { } void CInputManager::processMouseRequest(wlr_seat_pointer_request_set_cursor_event* e) { - - if (!e->surface) - g_pHyprRenderer->m_bWindowRequestedCursorHide = true; - else - g_pHyprRenderer->m_bWindowRequestedCursorHide = false; - - if (!cursorImageUnlocked() || !g_pHyprRenderer->shouldRenderCursor()) + if (!cursorImageUnlocked()) return; - // cursorSurfaceInfo.pSurface = e->surface; + if (e->seat_client == g_pCompositor->m_sSeat.seat->pointer_state.focused_client) { + m_sCursorSurfaceInfo.wlSurface.unassign(); - // if (e->surface) { - // hyprListener_CursorSurfaceDestroy.removeCallback(); - // hyprListener_CursorSurfaceDestroy.initCallback( - // &e->surface->events.destroy, [&](void* owner, void* data) { cursorSurfaceInfo.pSurface = nullptr; }, this, "InputManager"); - // cursorSurfaceInfo.vHotspot = {e->hotspot_x, e->hotspot_y}; - // } + if (e->surface) { + m_sCursorSurfaceInfo.wlSurface.assign(e->surface); + m_sCursorSurfaceInfo.vHotspot = {e->hotspot_x, e->hotspot_y}; + m_sCursorSurfaceInfo.hidden = false; + } else { + m_sCursorSurfaceInfo.vHotspot = {}; + m_sCursorSurfaceInfo.hidden = true; + } - if (e->seat_client == g_pCompositor->m_sSeat.seat->pointer_state.focused_client) - wlr_cursor_set_surface(g_pCompositor->m_sWLRCursor, e->surface, e->hotspot_x, e->hotspot_y); + m_sCursorSurfaceInfo.name = ""; + + m_sCursorSurfaceInfo.inUse = true; + g_pHyprRenderer->setCursorSurface(e->surface, e->hotspot_x, e->hotspot_y); + } } void CInputManager::processMouseRequest(wlr_cursor_shape_manager_v1_request_set_shape_event* e) { - if (!g_pHyprRenderer->shouldRenderCursor() || !cursorImageUnlocked()) + if (!cursorImageUnlocked()) return; - if (e->seat_client == g_pCompositor->m_sSeat.seat->pointer_state.focused_client) - wlr_cursor_set_xcursor(g_pCompositor->m_sWLRCursor, g_pCompositor->m_sWLRXCursorMgr, wlr_cursor_shape_v1_name(e->shape)); + if (e->seat_client == g_pCompositor->m_sSeat.seat->pointer_state.focused_client) { + m_sCursorSurfaceInfo.wlSurface.unassign(); + m_sCursorSurfaceInfo.vHotspot = {}; + m_sCursorSurfaceInfo.name = wlr_cursor_shape_v1_name(e->shape); + m_sCursorSurfaceInfo.hidden = false; + + m_sCursorSurfaceInfo.inUse = true; + g_pHyprRenderer->setCursorFromName(m_sCursorSurfaceInfo.name); + } +} + +void CInputManager::restoreCursorIconToApp() { + if (m_sCursorSurfaceInfo.inUse) + return; + + if (m_sCursorSurfaceInfo.hidden) { + g_pHyprRenderer->setCursorSurface(nullptr, 0, 0); + return; + } + + if (m_sCursorSurfaceInfo.name.empty()) { + if (m_sCursorSurfaceInfo.wlSurface.exists()) + g_pHyprRenderer->setCursorSurface(m_sCursorSurfaceInfo.wlSurface.wlr(), m_sCursorSurfaceInfo.vHotspot.x, m_sCursorSurfaceInfo.vHotspot.y); + } else { + g_pHyprRenderer->setCursorFromName(m_sCursorSurfaceInfo.name); + } + + m_sCursorSurfaceInfo.inUse = true; +} + +void CInputManager::setCursorImageOverride(const std::string& name) { + if (m_bCursorImageOverridden) + return; + + m_sCursorSurfaceInfo.inUse = false; + g_pHyprRenderer->setCursorFromName(name); } bool CInputManager::cursorImageUnlocked() { - if (!g_pHyprRenderer->shouldRenderCursor()) - return false; - if (m_ecbClickBehavior == CLICKMODE_KILL) return false; @@ -523,7 +566,7 @@ void CInputManager::setClickMode(eClickBehaviorMode mode) { case CLICKMODE_DEFAULT: Debug::log(LOG, "SetClickMode: DEFAULT"); m_ecbClickBehavior = CLICKMODE_DEFAULT; - wlr_cursor_set_xcursor(g_pCompositor->m_sWLRCursor, g_pCompositor->m_sWLRXCursorMgr, "left_ptr"); + g_pHyprRenderer->setCursorFromName("left_ptr"); break; case CLICKMODE_KILL: @@ -535,7 +578,7 @@ void CInputManager::setClickMode(eClickBehaviorMode mode) { refocus(); // set cursor - wlr_cursor_set_xcursor(g_pCompositor->m_sWLRCursor, g_pCompositor->m_sWLRXCursorMgr, "crosshair"); + g_pHyprRenderer->setCursorFromName("crosshair"); break; default: break; } @@ -544,10 +587,13 @@ void CInputManager::setClickMode(eClickBehaviorMode mode) { void CInputManager::processMouseDownNormal(wlr_pointer_button_event* e) { // notify the keybind manager - static auto* const PPASSMOUSE = &g_pConfigManager->getConfigValuePtr("binds:pass_mouse_when_bound")->intValue; - const auto PASS = g_pKeybindManager->onMouseEvent(e); - static auto* const PFOLLOWMOUSE = &g_pConfigManager->getConfigValuePtr("input:follow_mouse")->intValue; - static auto* const PRESIZEONBORDER = &g_pConfigManager->getConfigValuePtr("general:resize_on_border")->intValue; + static auto* const PPASSMOUSE = &g_pConfigManager->getConfigValuePtr("binds:pass_mouse_when_bound")->intValue; + const auto PASS = g_pKeybindManager->onMouseEvent(e); + static auto* const PFOLLOWMOUSE = &g_pConfigManager->getConfigValuePtr("input:follow_mouse")->intValue; + static auto* const PRESIZEONBORDER = &g_pConfigManager->getConfigValuePtr("general:resize_on_border")->intValue; + static auto* const PBORDERSIZE = &g_pConfigManager->getConfigValuePtr("general:border_size")->intValue; + static auto* const PBORDERGRABEXTEND = &g_pConfigManager->getConfigValuePtr("general:extend_border_grab_area")->intValue; + const auto BORDER_GRAB_AREA = *PRESIZEONBORDER ? *PBORDERSIZE + *PBORDERGRABEXTEND : 0; if (!PASS && !*PPASSMOUSE) return; @@ -555,25 +601,17 @@ void CInputManager::processMouseDownNormal(wlr_pointer_button_event* e) { const auto mouseCoords = g_pInputManager->getMouseCoordsInternal(); const auto w = g_pCompositor->vectorToWindowIdeal(mouseCoords); - if (w && !w->m_bIsFullscreen && !w->hasPopupAt(mouseCoords) && w->m_sGroupData.pNextWindow) { - const wlr_box box = w->getDecorationByType(DECORATION_GROUPBAR)->getWindowDecorationRegion().getExtents(); - if (wlr_box_contains_point(&box, mouseCoords.x, mouseCoords.y)) { - if (e->state == WLR_BUTTON_PRESSED) { - const int SIZE = w->getGroupSize(); - CWindow* pWindow = w->getGroupWindowByIndex((mouseCoords.x - box.x) * SIZE / box.width); - if (w != pWindow) - w->setGroupCurrent(pWindow); - } - return; - } - } + if (w && !m_bLastFocusOnLS && w->checkInputOnDecos(INPUT_TYPE_BUTTON, mouseCoords, e)) + return; // clicking on border triggers resize // TODO detect click on LS properly - if (*PRESIZEONBORDER && !m_bLastFocusOnLS) { + if (*PRESIZEONBORDER && !m_bLastFocusOnLS && e->state == WLR_BUTTON_PRESSED) { if (w && !w->m_bIsFullscreen) { - const wlr_box real = {w->m_vRealPosition.vec().x, w->m_vRealPosition.vec().y, w->m_vRealSize.vec().x, w->m_vRealSize.vec().y}; - if ((!wlr_box_contains_point(&real, mouseCoords.x, mouseCoords.y) || w->isInCurvedCorner(mouseCoords.x, mouseCoords.y)) && !w->hasPopupAt(mouseCoords)) { + const CBox real = {w->m_vRealPosition.vec().x, w->m_vRealPosition.vec().y, w->m_vRealSize.vec().x, w->m_vRealSize.vec().y}; + const CBox grab = {real.x - BORDER_GRAB_AREA, real.y - BORDER_GRAB_AREA, real.width + 2 * BORDER_GRAB_AREA, real.height + 2 * BORDER_GRAB_AREA}; + + if ((grab.containsPoint(mouseCoords) && (!real.containsPoint(mouseCoords) || w->isInCurvedCorner(mouseCoords.x, mouseCoords.y))) && !w->hasPopupAt(mouseCoords)) { g_pKeybindManager->resizeWithBorder(e); return; } @@ -635,31 +673,29 @@ void CInputManager::processMouseDownKill(wlr_pointer_button_event* e) { } void CInputManager::onMouseWheel(wlr_pointer_axis_event* e) { - static auto* const PSCROLLFACTOR = &g_pConfigManager->getConfigValuePtr("input:touchpad:scroll_factor")->floatValue; - static auto* const PGROUPBARSCROLLING = &g_pConfigManager->getConfigValuePtr("misc:groupbar_scrolling")->intValue; + static auto* const PSCROLLFACTOR = &g_pConfigManager->getConfigValuePtr("input:touchpad:scroll_factor")->floatValue; auto factor = (*PSCROLLFACTOR <= 0.f || e->source != WLR_AXIS_SOURCE_FINGER ? 1.f : *PSCROLLFACTOR); - bool passEvent = g_pKeybindManager->onAxisEvent(e); + const auto EMAP = std::unordered_map{{"event", e}}; + EMIT_HOOK_EVENT_CANCELLABLE("mouseAxis", EMAP); + + bool passEvent = g_pKeybindManager->onAxisEvent(e); g_pCompositor->notifyIdleActivity(); - const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal(); - const auto pWindow = g_pCompositor->vectorToWindowIdeal(MOUSECOORDS); + if (!passEvent) + return; - if (*PGROUPBARSCROLLING && pWindow && !pWindow->m_bIsFullscreen && !pWindow->hasPopupAt(MOUSECOORDS) && pWindow->m_sGroupData.pNextWindow) { - const wlr_box box = pWindow->getDecorationByType(DECORATION_GROUPBAR)->getWindowDecorationRegion().getExtents(); - if (wlr_box_contains_point(&box, MOUSECOORDS.x, MOUSECOORDS.y)) { - if (e->delta > 0) - pWindow->setGroupCurrent(pWindow->m_sGroupData.pNextWindow); - else - pWindow->setGroupCurrent(pWindow->getGroupPrevious()); + if (!m_bLastFocusOnLS) { + const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal(); + const auto PWINDOW = g_pCompositor->vectorToWindowIdeal(MOUSECOORDS); + + if (PWINDOW && PWINDOW->checkInputOnDecos(INPUT_TYPE_AXIS, MOUSECOORDS, e)) return; - } } - if (passEvent) - wlr_seat_pointer_notify_axis(g_pCompositor->m_sSeat.seat, e->time_msec, e->orientation, factor * e->delta, std::round(factor * e->delta_discrete), e->source); + wlr_seat_pointer_notify_axis(g_pCompositor->m_sSeat.seat, e->time_msec, e->orientation, factor * e->delta, std::round(factor * e->delta_discrete), e->source); } Vector2D CInputManager::getMouseCoordsInternal() { @@ -796,7 +832,7 @@ void CInputManager::applyConfigToKeyboard(SKeyboard* pKeyboard) { pKeyboard->repeatDelay = REPEATDELAY; pKeyboard->repeatRate = REPEATRATE; pKeyboard->numlockOn = NUMLOCKON; - pKeyboard->xkbFilePath = FILEPATH.c_str(); + pKeyboard->xkbFilePath = FILEPATH; xkb_rule_names rules = {.rules = RULES.c_str(), .model = MODEL.c_str(), .layout = LAYOUT.c_str(), .variant = VARIANT.c_str(), .options = OPTIONS.c_str()}; @@ -844,7 +880,7 @@ void CInputManager::applyConfigToKeyboard(SKeyboard* pKeyboard) { pKeyboard->currentRules.model = ""; pKeyboard->currentRules.variant = ""; pKeyboard->currentRules.options = ""; - pKeyboard->currentRules.layout = ""; + pKeyboard->currentRules.layout = "us"; KEYMAP = xkb_keymap_new_from_names(CONTEXT, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS); } @@ -872,7 +908,8 @@ void CInputManager::applyConfigToKeyboard(SKeyboard* pKeyboard) { g_pEventManager->postEvent(SHyprIPCEvent{"activelayout", pKeyboard->name + "," + LAYOUTSTR}); EMIT_HOOK_EVENT("activeLayout", (std::vector{pKeyboard, (void*)&LAYOUTSTR})); - Debug::log(LOG, "Set the keyboard layout to {} and variant to {} for keyboard \"{}\"", rules.layout, rules.variant, pKeyboard->keyboard->name); + Debug::log(LOG, "Set the keyboard layout to {} and variant to {} for keyboard \"{}\"", pKeyboard->currentRules.layout, pKeyboard->currentRules.variant, + pKeyboard->keyboard->name); } void CInputManager::newMouse(wlr_input_device* mouse, bool virt) { @@ -1009,24 +1046,41 @@ void CInputManager::setPointerConfigs() { libinput_device_config_accel_set_speed(LIBINPUTDEV, LIBINPUTSENS); const auto ACCELPROFILE = g_pConfigManager->getDeviceString(devname, "accel_profile", "input:accel_profile"); + const auto SCROLLPOINTS = g_pConfigManager->getDeviceString(devname, "scroll_points", "input:scroll_points"); - if (ACCELPROFILE == "") { + if (ACCELPROFILE.empty()) { libinput_device_config_accel_set_profile(LIBINPUTDEV, libinput_device_config_accel_get_default_profile(LIBINPUTDEV)); } else if (ACCELPROFILE == "adaptive") { libinput_device_config_accel_set_profile(LIBINPUTDEV, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE); } else if (ACCELPROFILE == "flat") { libinput_device_config_accel_set_profile(LIBINPUTDEV, LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT); - } else if (ACCELPROFILE.find("custom") == 0) { - CVarList args = {ACCELPROFILE, 0, ' '}; + } else if (ACCELPROFILE.starts_with("custom")) { + CVarList accelValues = {ACCELPROFILE, 0, ' '}; + try { - double step = std::stod(args[1]); - std::vector points; - for (size_t i = 2; i < args.size(); ++i) - points.push_back(std::stod(args[i])); + double accelStep = std::stod(accelValues[1]); + std::vector accelPoints; + for (size_t i = 2; i < accelValues.size(); ++i) { + accelPoints.push_back(std::stod(accelValues[i])); + } const auto CONFIG = libinput_config_accel_create(LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM); - libinput_config_accel_set_points(CONFIG, LIBINPUT_ACCEL_TYPE_MOTION, step, points.size(), points.data()); - libinput_device_config_accel_set_profile(LIBINPUTDEV, LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM); + + if (!SCROLLPOINTS.empty()) { + CVarList scrollValues = {SCROLLPOINTS, 0, ' '}; + try { + double scrollStep = std::stod(scrollValues[0]); + std::vector scrollPoints; + for (size_t i = 1; i < scrollValues.size(); ++i) { + scrollPoints.push_back(std::stod(scrollValues[i])); + } + + libinput_config_accel_set_points(CONFIG, LIBINPUT_ACCEL_TYPE_SCROLL, scrollStep, scrollPoints.size(), scrollPoints.data()); + } catch (std::exception& e) { Debug::log(ERR, "Invalid values in scroll_points"); } + } + + libinput_config_accel_set_points(CONFIG, LIBINPUT_ACCEL_TYPE_MOTION, accelStep, accelPoints.size(), accelPoints.data()); + libinput_device_config_accel_apply(LIBINPUTDEV, CONFIG); libinput_config_accel_destroy(CONFIG); } catch (std::exception& e) { Debug::log(ERR, "Invalid values in custom accel profile"); } } else { @@ -1104,6 +1158,9 @@ void CInputManager::onKeyboardKey(wlr_keyboard_key_event* e, SKeyboard* pKeyboar if (!pKeyboard->enabled) return; + const auto EMAP = std::unordered_map{{"keyboard", pKeyboard}, {"event", e}}; + EMIT_HOOK_EVENT_CANCELLABLE("keyPress", EMAP); + static auto* const PDPMS = &g_pConfigManager->getConfigValuePtr("misc:key_press_enables_dpms")->intValue; if (*PDPMS && !g_pCompositor->m_bDPMSStateON) { // enable dpms @@ -1180,7 +1237,7 @@ void CInputManager::updateDragIcon() { switch (m_sDrag.dragIcon->drag->grab_type) { case WLR_DRAG_GRAB_KEYBOARD: break; case WLR_DRAG_GRAB_KEYBOARD_POINTER: { - wlr_box box = {m_sDrag.pos.x - 2, m_sDrag.pos.y - 2, m_sDrag.dragIcon->surface->current.width + 4, m_sDrag.dragIcon->surface->current.height + 4}; + CBox box = {m_sDrag.pos.x - 2, m_sDrag.pos.y - 2, m_sDrag.dragIcon->surface->current.width + 4, m_sDrag.dragIcon->surface->current.height + 4}; g_pHyprRenderer->damageBox(&box); m_sDrag.pos = getMouseCoordsInternal(); break; @@ -1207,24 +1264,30 @@ void CInputManager::constrainMouse(SMouse* pMouse, wlr_pointer_constraint_v1* co return; const auto MOUSECOORDS = getMouseCoordsInternal(); + const auto PCONSTRAINT = constraintFromWlr(constraint); pMouse->hyprListener_commitConstraint.removeCallback(); - if (pMouse->currentConstraint) { - if (constraint) { - const auto PCONSTRAINT = constraintFromWlr(constraint); - if (constraint->current.committed & WLR_POINTER_CONSTRAINT_V1_STATE_CURSOR_HINT) { - PCONSTRAINT->hintSet = true; - PCONSTRAINT->positionHint = {constraint->current.cursor_hint.x, constraint->current.cursor_hint.y}; - } - - if (constraint->current.committed & WLR_POINTER_CONSTRAINT_V1_STATE_CURSOR_HINT && constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED) - warpMouseToConstraintMiddle(PCONSTRAINT); - } - + if (pMouse->currentConstraint) wlr_pointer_constraint_v1_send_deactivated(pMouse->currentConstraint); + + if (const auto PWINDOW = g_pCompositor->getWindowFromSurface(constraint->surface); PWINDOW) { + const auto RELATIVETO = PWINDOW->m_bIsX11 ? + (PWINDOW->m_bIsMapped ? PWINDOW->m_vRealPosition.goalv() : + g_pXWaylandManager->xwaylandToWaylandCoords({PWINDOW->m_uSurface.xwayland->x, PWINDOW->m_uSurface.xwayland->y})) : + PWINDOW->m_vRealPosition.goalv(); + + PCONSTRAINT->cursorPosOnActivate = (MOUSECOORDS - RELATIVETO) * PWINDOW->m_fX11SurfaceScaledBy; } + if (constraint->current.committed & WLR_POINTER_CONSTRAINT_V1_STATE_CURSOR_HINT) { + PCONSTRAINT->hintSet = true; + PCONSTRAINT->positionHint = {constraint->current.cursor_hint.x, constraint->current.cursor_hint.y}; + } + + if (constraint->current.committed & WLR_POINTER_CONSTRAINT_V1_STATE_CURSOR_HINT && constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED) + warpMouseToConstraintMiddle(PCONSTRAINT); + pMouse->currentConstraint = constraint; pMouse->constraintActive = true; @@ -1236,7 +1299,7 @@ void CInputManager::constrainMouse(SMouse* pMouse, wlr_pointer_constraint_v1* co // warp to the constraint recheckConstraint(pMouse); - constraintFromWlr(constraint)->active = true; + PCONSTRAINT->active = true; wlr_pointer_constraint_v1_send_activated(pMouse->currentConstraint); @@ -1253,23 +1316,17 @@ void CInputManager::warpMouseToConstraintMiddle(SConstraint* pConstraint) { const auto PWINDOW = g_pCompositor->getWindowFromSurface(pConstraint->constraint->surface); if (PWINDOW) { - const auto RELATIVETO = PWINDOW->m_bIsX11 ? - (PWINDOW->m_bIsMapped ? PWINDOW->m_vRealPosition.goalv() : - g_pXWaylandManager->xwaylandToWaylandCoords({PWINDOW->m_uSurface.xwayland->x, PWINDOW->m_uSurface.xwayland->y})) : - PWINDOW->m_vRealPosition.goalv(); + const auto RELATIVETO = pConstraint->getLogicConstraintPos(); const auto HINTSCALE = PWINDOW->m_fX11SurfaceScaledBy; - if (pConstraint->hintSet) { - wlr_cursor_warp(g_pCompositor->m_sWLRCursor, nullptr, RELATIVETO.x + pConstraint->positionHint.x / HINTSCALE, RELATIVETO.y + pConstraint->positionHint.y / HINTSCALE); - wlr_seat_pointer_warp(pConstraint->constraint->seat, pConstraint->constraint->current.cursor_hint.x, pConstraint->constraint->current.cursor_hint.y); - } else { - const auto RELATIVESIZE = PWINDOW->m_bIsX11 ? - (PWINDOW->m_bIsMapped ? PWINDOW->m_vRealSize.goalv() : - g_pXWaylandManager->xwaylandToWaylandCoords({PWINDOW->m_uSurface.xwayland->width, PWINDOW->m_uSurface.xwayland->height})) : - PWINDOW->m_vRealSize.goalv(); + auto HINT = pConstraint->hintSet ? pConstraint->positionHint : pConstraint->cursorPosOnActivate; - wlr_cursor_warp(g_pCompositor->m_sWLRCursor, nullptr, RELATIVETO.x + RELATIVESIZE.x / 2.f, RELATIVETO.y + RELATIVESIZE.y / 2.f); - wlr_seat_pointer_warp(pConstraint->constraint->seat, RELATIVESIZE.x / 2.f, RELATIVESIZE.y / 2.f); + if (HINT == Vector2D{-1, -1}) + HINT = pConstraint->getLogicConstraintSize() / 2.f; + + if (HINT != Vector2D{-1, -1}) { + wlr_cursor_warp(g_pCompositor->m_sWLRCursor, nullptr, RELATIVETO.x + HINT.x / HINTSCALE, RELATIVETO.y + HINT.y / HINTSCALE); + wlr_seat_pointer_warp(pConstraint->constraint->seat, pConstraint->constraint->current.cursor_hint.x, pConstraint->constraint->current.cursor_hint.y); } } } @@ -1434,7 +1491,10 @@ void CInputManager::setTabletConfigs() { if (wlr_input_device_is_libinput(t.wlrDevice)) { const auto LIBINPUTDEV = (libinput_device*)wlr_libinput_get_device_handle(t.wlrDevice); - const int ROTATION = std::clamp(g_pConfigManager->getDeviceInt(t.name, "transform", "input:tablet:transform"), 0, 7); + const auto RELINPUT = g_pConfigManager->getDeviceInt(t.name, "relative_input", "input:tablet:relative_input"); + t.relativeInput = RELINPUT; + + const int ROTATION = std::clamp(g_pConfigManager->getDeviceInt(t.name, "transform", "input:tablet:transform"), 0, 7); Debug::log(LOG, "Setting calibration matrix for device {}", t.name); libinput_device_config_calibration_set_matrix(LIBINPUTDEV, MATRICES[ROTATION]); @@ -1444,6 +1504,12 @@ void CInputManager::setTabletConfigs() { wlr_cursor_map_input_to_output(g_pCompositor->m_sWLRCursor, t.wlrDevice, PMONITOR->output); wlr_cursor_map_input_to_region(g_pCompositor->m_sWLRCursor, t.wlrDevice, nullptr); } + + const auto REGION_POS = g_pConfigManager->getDeviceVec(t.name, "region_position", "input:tablet:region_position"); + const auto REGION_SIZE = g_pConfigManager->getDeviceVec(t.name, "region_size", "input:tablet:region_size"); + auto regionBox = CBox{REGION_POS.x, REGION_POS.y, REGION_SIZE.x, REGION_SIZE.y}; + if (!regionBox.empty()) + wlr_cursor_map_input_to_region(g_pCompositor->m_sWLRCursor, t.wlrDevice, regionBox.pWlr()); } } } @@ -1500,8 +1566,9 @@ void CInputManager::destroySwitch(SSwitchDevice* pDevice) { } void CInputManager::setCursorImageUntilUnset(std::string name) { - wlr_cursor_set_xcursor(g_pCompositor->m_sWLRCursor, g_pCompositor->m_sWLRXCursorMgr, name.c_str()); - m_bCursorImageOverridden = true; + g_pHyprRenderer->setCursorFromName(name); + m_bCursorImageOverridden = true; + m_sCursorSurfaceInfo.inUse = false; } void CInputManager::unsetCursorImage() { @@ -1509,8 +1576,7 @@ void CInputManager::unsetCursorImage() { return; m_bCursorImageOverridden = false; - if (!g_pHyprRenderer->m_bWindowRequestedCursorHide) - wlr_cursor_set_xcursor(g_pCompositor->m_sWLRCursor, g_pCompositor->m_sWLRXCursorMgr, "left_ptr"); + restoreCursorIconToApp(); } std::string CInputManager::deviceNameToInternalString(std::string in) { @@ -1588,49 +1654,70 @@ void CInputManager::setCursorIconOnBorder(CWindow* w) { // give a small leeway (10 px) for corner icon const auto CORNER = *PROUNDING + BORDERSIZE + 10; const auto mouseCoords = getMouseCoordsInternal(); - wlr_box box = {w->m_vRealPosition.vec().x, w->m_vRealPosition.vec().y, w->m_vRealSize.vec().x, w->m_vRealSize.vec().y}; + CBox box = w->getWindowMainSurfaceBox(); eBorderIconDirection direction = BORDERICON_NONE; - wlr_box boxFullGrabInput = {box.x - *PEXTENDBORDERGRAB - BORDERSIZE, box.y - *PEXTENDBORDERGRAB - BORDERSIZE, box.width + 2 * (*PEXTENDBORDERGRAB + BORDERSIZE), - box.height + 2 * (*PEXTENDBORDERGRAB + BORDERSIZE)}; + CBox boxFullGrabInput = {box.x - *PEXTENDBORDERGRAB - BORDERSIZE, box.y - *PEXTENDBORDERGRAB - BORDERSIZE, box.width + 2 * (*PEXTENDBORDERGRAB + BORDERSIZE), + box.height + 2 * (*PEXTENDBORDERGRAB + BORDERSIZE)}; - if (!wlr_box_contains_point(&boxFullGrabInput, mouseCoords.x, mouseCoords.y) || (!m_lCurrentlyHeldButtons.empty() && !currentlyDraggedWindow)) { + if (w->hasPopupAt(mouseCoords)) direction = BORDERICON_NONE; - } else if (wlr_box_contains_point(&box, mouseCoords.x, mouseCoords.y)) { - if (!w->isInCurvedCorner(mouseCoords.x, mouseCoords.y)) { - direction = BORDERICON_NONE; - } else { - if (mouseCoords.y < box.y + CORNER) { - if (mouseCoords.x < box.x + CORNER) - direction = BORDERICON_UP_LEFT; - else - direction = BORDERICON_UP_RIGHT; - } else { - if (mouseCoords.x < box.x + CORNER) - direction = BORDERICON_DOWN_LEFT; - else - direction = BORDERICON_DOWN_RIGHT; + else if (!boxFullGrabInput.containsPoint(mouseCoords) || (!m_lCurrentlyHeldButtons.empty() && !currentlyDraggedWindow)) + direction = BORDERICON_NONE; + else { + + bool onDeco = false; + + for (auto& wd : w->m_dWindowDecorations) { + if (!(wd->getDecorationFlags() & DECORATION_ALLOWS_MOUSE_INPUT)) + continue; + + if (g_pDecorationPositioner->getWindowDecorationBox(wd.get()).containsPoint(mouseCoords)) { + onDeco = true; + break; } } - } else { - if (mouseCoords.y < box.y + CORNER) { - if (mouseCoords.x < box.x + CORNER) - direction = BORDERICON_UP_LEFT; - else if (mouseCoords.x > box.x + box.width - CORNER) - direction = BORDERICON_UP_RIGHT; - else - direction = BORDERICON_UP; - } else if (mouseCoords.y > box.y + box.height - CORNER) { - if (mouseCoords.x < box.x + CORNER) - direction = BORDERICON_DOWN_LEFT; - else if (mouseCoords.x > box.x + box.width - CORNER) - direction = BORDERICON_DOWN_RIGHT; - else - direction = BORDERICON_DOWN; - } else { - if (mouseCoords.x < box.x + CORNER) - direction = BORDERICON_LEFT; - else if (mouseCoords.x > box.x + box.width - CORNER) - direction = BORDERICON_RIGHT; + + if (onDeco) + direction = BORDERICON_NONE; + else { + if (box.containsPoint(mouseCoords)) { + if (!w->isInCurvedCorner(mouseCoords.x, mouseCoords.y)) { + direction = BORDERICON_NONE; + } else { + if (mouseCoords.y < box.y + CORNER) { + if (mouseCoords.x < box.x + CORNER) + direction = BORDERICON_UP_LEFT; + else + direction = BORDERICON_UP_RIGHT; + } else { + if (mouseCoords.x < box.x + CORNER) + direction = BORDERICON_DOWN_LEFT; + else + direction = BORDERICON_DOWN_RIGHT; + } + } + } else { + if (mouseCoords.y < box.y + CORNER) { + if (mouseCoords.x < box.x + CORNER) + direction = BORDERICON_UP_LEFT; + else if (mouseCoords.x > box.x + box.width - CORNER) + direction = BORDERICON_UP_RIGHT; + else + direction = BORDERICON_UP; + } else if (mouseCoords.y > box.y + box.height - CORNER) { + if (mouseCoords.x < box.x + CORNER) + direction = BORDERICON_DOWN_LEFT; + else if (mouseCoords.x > box.x + box.width - CORNER) + direction = BORDERICON_DOWN_RIGHT; + else + direction = BORDERICON_DOWN; + } else { + if (mouseCoords.x < box.x + CORNER) + direction = BORDERICON_LEFT; + else if (mouseCoords.x > box.x + box.width - CORNER) + direction = BORDERICON_RIGHT; + } + } } } diff --git a/src/managers/input/InputManager.hpp b/src/managers/input/InputManager.hpp index 670bc4da..5fa5824e 100644 --- a/src/managers/input/InputManager.hpp +++ b/src/managers/input/InputManager.hpp @@ -7,14 +7,12 @@ #include "../../helpers/Timer.hpp" #include "InputMethodRelay.hpp" -enum eClickBehaviorMode -{ +enum eClickBehaviorMode { CLICKMODE_DEFAULT = 0, CLICKMODE_KILL }; -enum eMouseBindMode -{ +enum eMouseBindMode { MBIND_INVALID = -1, MBIND_MOVE = 0, MBIND_RESIZE = 1, @@ -22,8 +20,7 @@ enum eMouseBindMode MBIND_RESIZE_FORCE_RATIO = 3 }; -enum eBorderIconDirection -{ +enum eBorderIconDirection { BORDERICON_NONE, BORDERICON_UP, BORDERICON_DOWN, @@ -64,6 +61,8 @@ class CKeybindManager; class CInputManager { public: + ~CInputManager(); + void onMouseMoved(wlr_pointer_motion_event*); void onMouseWarp(wlr_pointer_motion_absolute_event*); void onMouseButton(wlr_pointer_button_event*); @@ -118,6 +117,7 @@ class CInputManager { // for dragging floating windows CWindow* currentlyDraggedWindow = nullptr; eMouseBindMode dragMode = MBIND_INVALID; + bool m_bWasDraggingWindow = false; // for refocus to be forced CWindow* m_pForcedFocus = nullptr; @@ -229,15 +229,22 @@ class CInputManager { void setBorderCursorIcon(eBorderIconDirection); void setCursorIconOnBorder(CWindow* w); + // temporary. Obeys setUntilUnset. + void setCursorImageOverride(const std::string& name); + // cursor surface - // struct cursorSI { - // wlr_surface* pSurface = nullptr; - // Vector2D vHotspot; - // bool bUsed = false; - // } cursorSurfaceInfo; - // DYNLISTENER(CursorSurfaceDestroy); + struct cursorSI { + bool hidden = false; // null surface = hidden + CWLSurface wlSurface; + Vector2D vHotspot; + std::string name; // if not empty, means set by name. + bool inUse = false; + } m_sCursorSurfaceInfo; + + void restoreCursorIconToApp(); // no-op if restored friend class CKeybindManager; + friend class CWLSurface; }; inline std::unique_ptr g_pInputManager; diff --git a/src/managers/input/InputMethodRelay.cpp b/src/managers/input/InputMethodRelay.cpp index 58ad2a2d..5b6df08d 100644 --- a/src/managers/input/InputMethodRelay.cpp +++ b/src/managers/input/InputMethodRelay.cpp @@ -3,7 +3,7 @@ #include "../../Compositor.hpp" CInputMethodRelay::CInputMethodRelay() { - g_pHookSystem->hookDynamic("keyboardFocus", [&](void* self, std::any param) { onKeyboardFocus(std::any_cast(param)); }); + g_pHookSystem->hookDynamic("keyboardFocus", [&](void* self, SCallbackInfo& info, std::any param) { onKeyboardFocus(std::any_cast(param)); }); } void CInputMethodRelay::onNewIME(wlr_input_method_v2* pIME) { @@ -178,7 +178,7 @@ void CInputMethodRelay::updateInputPopup(SIMEPopup* pPopup) { bool cursorRect = PFOCUSEDTI->pWlrInput ? PFOCUSEDTI->pWlrInput->current.features & WLR_TEXT_INPUT_V3_FEATURE_CURSOR_RECTANGLE : true; const auto PFOCUSEDSURFACE = focusedSurface(PFOCUSEDTI); - auto cursorBox = PFOCUSEDTI->pWlrInput ? PFOCUSEDTI->pWlrInput->current.cursor_rectangle : PFOCUSEDTI->pV1Input->cursorRectangle; + CBox cursorBox = PFOCUSEDTI->pWlrInput ? PFOCUSEDTI->pWlrInput->current.cursor_rectangle : PFOCUSEDTI->pV1Input->cursorRectangle; CMonitor* pMonitor = nullptr; Vector2D parentPos; @@ -209,7 +209,7 @@ void CInputMethodRelay::updateInputPopup(SIMEPopup* pPopup) { if (!pMonitor) return; - wlr_box finalBox = cursorBox; + CBox finalBox = cursorBox; if (cursorBox.y + parentPos.y + pPopup->pSurface->surface->current.height + finalBox.height > pMonitor->vecPosition.y + pMonitor->vecSize.y) finalBox.y -= pPopup->pSurface->surface->current.height + finalBox.height; @@ -225,7 +225,7 @@ void CInputMethodRelay::updateInputPopup(SIMEPopup* pPopup) { pPopup->lastSize = Vector2D(pPopup->pSurface->surface->current.width, pPopup->pSurface->surface->current.height); - wlr_input_popup_surface_v2_send_text_input_rectangle(pPopup->pSurface, &finalBox); + wlr_input_popup_surface_v2_send_text_input_rectangle(pPopup->pSurface, finalBox.pWlr()); damagePopup(pPopup); } diff --git a/src/managers/input/Swipe.cpp b/src/managers/input/Swipe.cpp index ebed67cd..38ec8aa2 100644 --- a/src/managers/input/Swipe.cpp +++ b/src/managers/input/Swipe.cpp @@ -44,14 +44,15 @@ void CInputManager::onSwipeEnd(wlr_pointer_swipe_end_event* e) { if (!m_sActiveSwipe.pWorkspaceBegin) return; // no valid swipe - static auto* const PSWIPEPERC = &g_pConfigManager->getConfigValuePtr("gestures:workspace_swipe_cancel_ratio")->floatValue; - static auto* const PSWIPEDIST = &g_pConfigManager->getConfigValuePtr("gestures:workspace_swipe_distance")->intValue; - static auto* const PSWIPEFORC = &g_pConfigManager->getConfigValuePtr("gestures:workspace_swipe_min_speed_to_force")->intValue; - static auto* const PSWIPENEW = &g_pConfigManager->getConfigValuePtr("gestures:workspace_swipe_create_new")->intValue; - static auto* const PSWIPENUMBER = &g_pConfigManager->getConfigValuePtr("gestures:workspace_swipe_numbered")->intValue; - static auto* const PSWIPEUSER = &g_pConfigManager->getConfigValuePtr("gestures:workspace_swipe_use_r")->intValue; - const bool VERTANIMS = m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset.getConfig()->pValues->internalStyle == "slidevert" || - m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset.getConfig()->pValues->internalStyle.find("slidefadevert") == 0; + static auto* const PSWIPEPERC = &g_pConfigManager->getConfigValuePtr("gestures:workspace_swipe_cancel_ratio")->floatValue; + static auto* const PSWIPEDIST = &g_pConfigManager->getConfigValuePtr("gestures:workspace_swipe_distance")->intValue; + static auto* const PSWIPEFORC = &g_pConfigManager->getConfigValuePtr("gestures:workspace_swipe_min_speed_to_force")->intValue; + static auto* const PSWIPENEW = &g_pConfigManager->getConfigValuePtr("gestures:workspace_swipe_create_new")->intValue; + static auto* const PSWIPENUMBER = &g_pConfigManager->getConfigValuePtr("gestures:workspace_swipe_numbered")->intValue; + static auto* const PSWIPEUSER = &g_pConfigManager->getConfigValuePtr("gestures:workspace_swipe_use_r")->intValue; + static auto* const PWORKSPACEGAP = &g_pConfigManager->getConfigValuePtr("general:gaps_workspaces")->intValue; + const bool VERTANIMS = m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset.getConfig()->pValues->internalStyle == "slidevert" || + m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset.getConfig()->pValues->internalStyle.starts_with("slidefadevert"); // commit std::string wsname = ""; @@ -79,6 +80,8 @@ void CInputManager::onSwipeEnd(wlr_pointer_swipe_end_event* e) { auto PWORKSPACEL = g_pCompositor->getWorkspaceByID(workspaceIDLeft); // not guaranteed if PSWIPENUMBER const auto RENDEROFFSETMIDDLE = m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset.vec(); + const auto XDISTANCE = m_sActiveSwipe.pMonitor->vecSize.x + *PWORKSPACEGAP; + const auto YDISTANCE = m_sActiveSwipe.pMonitor->vecSize.y + *PWORKSPACEGAP; CWorkspace* pSwitchedTo = nullptr; @@ -94,18 +97,19 @@ void CInputManager::onSwipeEnd(wlr_pointer_swipe_end_event* e) { } else { if (m_sActiveSwipe.delta < 0) { // to left + if (PWORKSPACEL) { if (VERTANIMS) - PWORKSPACEL->m_vRenderOffset = Vector2D({0, -m_sActiveSwipe.pMonitor->vecSize.y}); + PWORKSPACEL->m_vRenderOffset = Vector2D({0, -YDISTANCE}); else - PWORKSPACEL->m_vRenderOffset = Vector2D({-m_sActiveSwipe.pMonitor->vecSize.x, 0}); + PWORKSPACEL->m_vRenderOffset = Vector2D({-XDISTANCE, 0}); } } else if (PWORKSPACER) { // to right if (VERTANIMS) - PWORKSPACER->m_vRenderOffset = Vector2D({0, m_sActiveSwipe.pMonitor->vecSize.y}); + PWORKSPACER->m_vRenderOffset = Vector2D({0, YDISTANCE}); else - PWORKSPACER->m_vRenderOffset = Vector2D({m_sActiveSwipe.pMonitor->vecSize.x, 0}); + PWORKSPACER->m_vRenderOffset = Vector2D({XDISTANCE, 0}); } m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset = Vector2D(); @@ -128,9 +132,9 @@ void CInputManager::onSwipeEnd(wlr_pointer_swipe_end_event* e) { m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset.setValue(RENDEROFFSETMIDDLE); if (VERTANIMS) - m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset = Vector2D(0, m_sActiveSwipe.pMonitor->vecSize.y); + m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset = Vector2D(0, YDISTANCE); else - m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset = Vector2D(m_sActiveSwipe.pMonitor->vecSize.x, 0); + m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset = Vector2D(XDISTANCE, 0); m_sActiveSwipe.pWorkspaceBegin->m_fAlpha.setValueAndWarp(1.f); g_pInputManager->unconstrainMouse(); @@ -154,9 +158,9 @@ void CInputManager::onSwipeEnd(wlr_pointer_swipe_end_event* e) { m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset.setValue(RENDEROFFSETMIDDLE); if (VERTANIMS) - m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset = Vector2D(0, -m_sActiveSwipe.pMonitor->vecSize.y); + m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset = Vector2D(0, -YDISTANCE); else - m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset = Vector2D(-m_sActiveSwipe.pMonitor->vecSize.x, 0); + m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset = Vector2D(-XDISTANCE, 0); m_sActiveSwipe.pWorkspaceBegin->m_fAlpha.setValueAndWarp(1.f); g_pInputManager->unconstrainMouse(); @@ -198,9 +202,12 @@ void CInputManager::onSwipeUpdate(wlr_pointer_swipe_update_event* e) { static auto* const PSWIPEFOREVER = &g_pConfigManager->getConfigValuePtr("gestures:workspace_swipe_forever")->intValue; static auto* const PSWIPENUMBER = &g_pConfigManager->getConfigValuePtr("gestures:workspace_swipe_numbered")->intValue; static auto* const PSWIPEUSER = &g_pConfigManager->getConfigValuePtr("gestures:workspace_swipe_use_r")->intValue; + static auto* const PWORKSPACEGAP = &g_pConfigManager->getConfigValuePtr("general:gaps_workspaces")->intValue; + const auto XDISTANCE = m_sActiveSwipe.pMonitor->vecSize.x + *PWORKSPACEGAP; + const auto YDISTANCE = m_sActiveSwipe.pMonitor->vecSize.y + *PWORKSPACEGAP; const bool VERTANIMS = m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset.getConfig()->pValues->internalStyle == "slidevert" || - m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset.getConfig()->pValues->internalStyle.find("slidefadevert") == 0; + m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset.getConfig()->pValues->internalStyle.starts_with("slidefadevert"); m_sActiveSwipe.delta += VERTANIMS ? (*PSWIPEINVR ? -e->dy : e->dy) : (*PSWIPEINVR ? -e->dx : e->dx); @@ -211,7 +218,7 @@ void CInputManager::onSwipeUpdate(wlr_pointer_swipe_update_event* e) { auto workspaceIDLeft = getWorkspaceIDFromString(*PSWIPENUMBER ? "-1" : (*PSWIPEUSER ? "r-1" : "m-1"), wsname); auto workspaceIDRight = getWorkspaceIDFromString(*PSWIPENUMBER ? "+1" : (*PSWIPEUSER ? "r+1" : "m+1"), wsname); - if ((workspaceIDLeft == INT_MAX || workspaceIDRight == INT_MAX || workspaceIDLeft == m_sActiveSwipe.pWorkspaceBegin->m_iID) && !*PSWIPENEW) { + if ((workspaceIDLeft == WORKSPACE_INVALID || workspaceIDRight == WORKSPACE_INVALID || workspaceIDLeft == m_sActiveSwipe.pWorkspaceBegin->m_iID) && !*PSWIPENEW) { m_sActiveSwipe.pWorkspaceBegin = nullptr; // invalidate the swipe return; } @@ -244,9 +251,9 @@ void CInputManager::onSwipeUpdate(wlr_pointer_swipe_update_event* e) { g_pHyprRenderer->damageMonitor(m_sActiveSwipe.pMonitor); if (VERTANIMS) - m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset.setValueAndWarp(Vector2D(0, ((-m_sActiveSwipe.delta) / *PSWIPEDIST) * m_sActiveSwipe.pMonitor->vecSize.y)); + m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset.setValueAndWarp(Vector2D(0, ((-m_sActiveSwipe.delta) / *PSWIPEDIST) * YDISTANCE)); else - m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset.setValueAndWarp(Vector2D(((-m_sActiveSwipe.delta) / *PSWIPEDIST) * m_sActiveSwipe.pMonitor->vecSize.x, 0)); + m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset.setValueAndWarp(Vector2D(((-m_sActiveSwipe.delta) / *PSWIPEDIST) * XDISTANCE, 0)); g_pCompositor->updateWorkspaceWindowDecos(m_sActiveSwipe.pWorkspaceBegin->m_iID); return; @@ -258,7 +265,7 @@ void CInputManager::onSwipeUpdate(wlr_pointer_swipe_update_event* e) { PWORKSPACE->m_bForceRendering = true; PWORKSPACE->m_fAlpha.setValueAndWarp(1.f); - if (workspaceIDLeft != workspaceIDRight) { + if (workspaceIDLeft != workspaceIDRight && workspaceIDRight != m_sActiveSwipe.pWorkspaceBegin->m_iID) { const auto PWORKSPACER = g_pCompositor->getWorkspaceByID(workspaceIDRight); if (PWORKSPACER) { @@ -268,13 +275,11 @@ void CInputManager::onSwipeUpdate(wlr_pointer_swipe_update_event* e) { } if (VERTANIMS) { - PWORKSPACE->m_vRenderOffset.setValueAndWarp( - Vector2D(0, ((-m_sActiveSwipe.delta) / *PSWIPEDIST) * m_sActiveSwipe.pMonitor->vecSize.y - m_sActiveSwipe.pMonitor->vecSize.y)); - m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset.setValueAndWarp(Vector2D(0, ((-m_sActiveSwipe.delta) / *PSWIPEDIST) * m_sActiveSwipe.pMonitor->vecSize.y)); + PWORKSPACE->m_vRenderOffset.setValueAndWarp(Vector2D(0, ((-m_sActiveSwipe.delta) / *PSWIPEDIST) * YDISTANCE - YDISTANCE)); + m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset.setValueAndWarp(Vector2D(0, ((-m_sActiveSwipe.delta) / *PSWIPEDIST) * YDISTANCE)); } else { - PWORKSPACE->m_vRenderOffset.setValueAndWarp( - Vector2D(((-m_sActiveSwipe.delta) / *PSWIPEDIST) * m_sActiveSwipe.pMonitor->vecSize.x - m_sActiveSwipe.pMonitor->vecSize.x, 0)); - m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset.setValueAndWarp(Vector2D(((-m_sActiveSwipe.delta) / *PSWIPEDIST) * m_sActiveSwipe.pMonitor->vecSize.x, 0)); + PWORKSPACE->m_vRenderOffset.setValueAndWarp(Vector2D(((-m_sActiveSwipe.delta) / *PSWIPEDIST) * XDISTANCE - XDISTANCE, 0)); + m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset.setValueAndWarp(Vector2D(((-m_sActiveSwipe.delta) / *PSWIPEDIST) * XDISTANCE, 0)); } g_pCompositor->updateWorkspaceWindowDecos(workspaceIDLeft); @@ -286,9 +291,9 @@ void CInputManager::onSwipeUpdate(wlr_pointer_swipe_update_event* e) { g_pHyprRenderer->damageMonitor(m_sActiveSwipe.pMonitor); if (VERTANIMS) - m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset.setValueAndWarp(Vector2D(0, ((-m_sActiveSwipe.delta) / *PSWIPEDIST) * m_sActiveSwipe.pMonitor->vecSize.y)); + m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset.setValueAndWarp(Vector2D(0, ((-m_sActiveSwipe.delta) / *PSWIPEDIST) * YDISTANCE)); else - m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset.setValueAndWarp(Vector2D(((-m_sActiveSwipe.delta) / *PSWIPEDIST) * m_sActiveSwipe.pMonitor->vecSize.x, 0)); + m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset.setValueAndWarp(Vector2D(((-m_sActiveSwipe.delta) / *PSWIPEDIST) * XDISTANCE, 0)); g_pCompositor->updateWorkspaceWindowDecos(m_sActiveSwipe.pWorkspaceBegin->m_iID); return; @@ -300,7 +305,7 @@ void CInputManager::onSwipeUpdate(wlr_pointer_swipe_update_event* e) { PWORKSPACE->m_bForceRendering = true; PWORKSPACE->m_fAlpha.setValueAndWarp(1.f); - if (workspaceIDLeft != workspaceIDRight) { + if (workspaceIDLeft != workspaceIDRight && workspaceIDLeft != m_sActiveSwipe.pWorkspaceBegin->m_iID) { const auto PWORKSPACEL = g_pCompositor->getWorkspaceByID(workspaceIDLeft); if (PWORKSPACEL) { @@ -310,13 +315,11 @@ void CInputManager::onSwipeUpdate(wlr_pointer_swipe_update_event* e) { } if (VERTANIMS) { - PWORKSPACE->m_vRenderOffset.setValueAndWarp( - Vector2D(0, ((-m_sActiveSwipe.delta) / *PSWIPEDIST) * m_sActiveSwipe.pMonitor->vecSize.y + m_sActiveSwipe.pMonitor->vecSize.y)); - m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset.setValueAndWarp(Vector2D(0, ((-m_sActiveSwipe.delta) / *PSWIPEDIST) * m_sActiveSwipe.pMonitor->vecSize.y)); + PWORKSPACE->m_vRenderOffset.setValueAndWarp(Vector2D(0, ((-m_sActiveSwipe.delta) / *PSWIPEDIST) * YDISTANCE + YDISTANCE)); + m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset.setValueAndWarp(Vector2D(0, ((-m_sActiveSwipe.delta) / *PSWIPEDIST) * YDISTANCE)); } else { - PWORKSPACE->m_vRenderOffset.setValueAndWarp( - Vector2D(((-m_sActiveSwipe.delta) / *PSWIPEDIST) * m_sActiveSwipe.pMonitor->vecSize.x + m_sActiveSwipe.pMonitor->vecSize.x, 0)); - m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset.setValueAndWarp(Vector2D(((-m_sActiveSwipe.delta) / *PSWIPEDIST) * m_sActiveSwipe.pMonitor->vecSize.x, 0)); + PWORKSPACE->m_vRenderOffset.setValueAndWarp(Vector2D(((-m_sActiveSwipe.delta) / *PSWIPEDIST) * XDISTANCE + XDISTANCE, 0)); + m_sActiveSwipe.pWorkspaceBegin->m_vRenderOffset.setValueAndWarp(Vector2D(((-m_sActiveSwipe.delta) / *PSWIPEDIST) * XDISTANCE, 0)); } g_pCompositor->updateWorkspaceWindowDecos(workspaceIDRight); diff --git a/src/managers/input/Tablets.cpp b/src/managers/input/Tablets.cpp index 77dd737c..959ce432 100644 --- a/src/managers/input/Tablets.cpp +++ b/src/managers/input/Tablets.cpp @@ -43,9 +43,16 @@ void CInputManager::newTabletTool(wlr_input_device* pDevice) { g_pInputManager->m_tmrLastCursorMovement.reset(); break; default: - double x = (EVENT->updated_axes & WLR_TABLET_TOOL_AXIS_X) ? EVENT->x : NAN; - double y = (EVENT->updated_axes & WLR_TABLET_TOOL_AXIS_Y) ? EVENT->y : NAN; - wlr_cursor_warp_absolute(g_pCompositor->m_sWLRCursor, PTAB->wlrDevice, x, y); + double x = (EVENT->updated_axes & WLR_TABLET_TOOL_AXIS_X) ? EVENT->x : NAN; + double dx = (EVENT->updated_axes & WLR_TABLET_TOOL_AXIS_X) ? EVENT->dx : NAN; + double y = (EVENT->updated_axes & WLR_TABLET_TOOL_AXIS_Y) ? EVENT->y : NAN; + double dy = (EVENT->updated_axes & WLR_TABLET_TOOL_AXIS_Y) ? EVENT->dy : NAN; + + if (PTAB->relativeInput) + wlr_cursor_move(g_pCompositor->m_sWLRCursor, PTAB->wlrDevice, dx, dy); + else + wlr_cursor_warp_absolute(g_pCompositor->m_sWLRCursor, PTAB->wlrDevice, x, y); + g_pInputManager->refocus(); g_pInputManager->m_tmrLastCursorMovement.reset(); break; @@ -83,6 +90,8 @@ void CInputManager::newTabletTool(wlr_input_device* pDevice) { if (EVENT->updated_axes & (WLR_TABLET_TOOL_AXIS_TILT_X | WLR_TABLET_TOOL_AXIS_TILT_Y)) wlr_tablet_v2_tablet_tool_notify_tilt(PTOOL->wlrTabletToolV2, PTOOL->tiltX, PTOOL->tiltY); + + g_pCompositor->notifyIdleActivity(); }, PNEWTABLET, "Tablet"); @@ -102,6 +111,8 @@ void CInputManager::newTabletTool(wlr_input_device* pDevice) { } else { wlr_send_tablet_v2_tablet_tool_up(PTOOL->wlrTabletToolV2); } + + g_pCompositor->notifyIdleActivity(); }, PNEWTABLET, "Tablet"); @@ -113,6 +124,7 @@ void CInputManager::newTabletTool(wlr_input_device* pDevice) { const auto PTOOL = g_pInputManager->ensureTabletToolPresent(EVENT->tool); wlr_tablet_v2_tablet_tool_notify_button(PTOOL->wlrTabletToolV2, (zwp_tablet_pad_v2_button_state)EVENT->button, (zwp_tablet_pad_v2_button_state)EVENT->state); + g_pCompositor->notifyIdleActivity(); }, PNEWTABLET, "Tablet"); @@ -137,6 +149,8 @@ void CInputManager::newTabletTool(wlr_input_device* pDevice) { g_pInputManager->refocus(); g_pInputManager->focusTablet(PTAB, EVENT->tool); } + + g_pCompositor->notifyIdleActivity(); }, PNEWTABLET, "Tablet"); @@ -244,8 +258,6 @@ void CInputManager::focusTablet(STablet* pTab, wlr_tablet_tool* pTool, bool moti if (const auto PWINDOW = g_pCompositor->m_pLastWindow; PWINDOW) { const auto CURSORPOS = g_pInputManager->getMouseCoordsInternal(); - const auto LOCAL = CURSORPOS - PWINDOW->m_vRealPosition.goalv(); - if (PTOOL->pSurface != g_pCompositor->m_pLastFocus) wlr_tablet_v2_tablet_tool_notify_proximity_out(PTOOL->wlrTabletToolV2); @@ -254,8 +266,14 @@ void CInputManager::focusTablet(STablet* pTab, wlr_tablet_tool* pTool, bool moti wlr_tablet_v2_tablet_tool_notify_proximity_in(PTOOL->wlrTabletToolV2, pTab->wlrTabletV2, g_pCompositor->m_pLastFocus); } - if (motion) - wlr_tablet_v2_tablet_tool_notify_motion(PTOOL->wlrTabletToolV2, LOCAL.x, LOCAL.y); + if (motion) { + auto local = CURSORPOS - PWINDOW->m_vRealPosition.goalv(); + + if (PWINDOW->m_bIsX11) + local = local * PWINDOW->m_fX11SurfaceScaledBy; + + wlr_tablet_v2_tablet_tool_notify_motion(PTOOL->wlrTabletToolV2, local.x, local.y); + } } else { if (PTOOL->pSurface) wlr_tablet_v2_tablet_tool_notify_proximity_out(PTOOL->wlrTabletToolV2); diff --git a/src/managers/input/Touch.cpp b/src/managers/input/Touch.cpp index 19e29c1b..600df302 100644 --- a/src/managers/input/Touch.cpp +++ b/src/managers/input/Touch.cpp @@ -2,6 +2,8 @@ #include "../../Compositor.hpp" void CInputManager::onTouchDown(wlr_touch_down_event* e) { + EMIT_HOOK_EVENT_CANCELLABLE("touchDown", e); + auto PMONITOR = g_pCompositor->getMonitorFromName(e->touch->output_name ? e->touch->output_name : ""); const auto PDEVIT = std::find_if(m_lTouchDevices.begin(), m_lTouchDevices.end(), [&](const STouchDevice& other) { return other.pWlrDevice == &e->touch->base; }); @@ -32,12 +34,12 @@ void CInputManager::onTouchDown(wlr_touch_down_event* e) { if (m_sTouchData.touchFocusWindow) { if (m_sTouchData.touchFocusWindow->m_bIsX11) { - local = g_pInputManager->getMouseCoordsInternal() - m_sTouchData.touchFocusWindow->m_vRealPosition.goalv(); + local = (g_pInputManager->getMouseCoordsInternal() - m_sTouchData.touchFocusWindow->m_vRealPosition.goalv()) * m_sTouchData.touchFocusWindow->m_fX11SurfaceScaledBy; + m_sTouchData.touchSurfaceOrigin = m_sTouchData.touchFocusWindow->m_vRealPosition.goalv(); } else { g_pCompositor->vectorWindowToSurface(g_pInputManager->getMouseCoordsInternal(), m_sTouchData.touchFocusWindow, local); + m_sTouchData.touchSurfaceOrigin = g_pInputManager->getMouseCoordsInternal() - local; } - - m_sTouchData.touchSurfaceOrigin = g_pInputManager->getMouseCoordsInternal() - local; } else if (m_sTouchData.touchFocusLS) { local = g_pInputManager->getMouseCoordsInternal() - Vector2D(m_sTouchData.touchFocusLS->geometry.x, m_sTouchData.touchFocusLS->geometry.y); @@ -52,21 +54,25 @@ void CInputManager::onTouchDown(wlr_touch_down_event* e) { } void CInputManager::onTouchUp(wlr_touch_up_event* e) { + EMIT_HOOK_EVENT_CANCELLABLE("touchUp", e); if (m_sTouchData.touchFocusSurface) { wlr_seat_touch_notify_up(g_pCompositor->m_sSeat.seat, e->time_msec, e->touch_id); } } void CInputManager::onTouchMove(wlr_touch_motion_event* e) { + EMIT_HOOK_EVENT_CANCELLABLE("touchMove", e); if (m_sTouchData.touchFocusWindow && g_pCompositor->windowValidMapped(m_sTouchData.touchFocusWindow)) { const auto PMONITOR = g_pCompositor->getMonitorFromID(m_sTouchData.touchFocusWindow->m_iMonitorID); wlr_cursor_warp(g_pCompositor->m_sWLRCursor, nullptr, PMONITOR->vecPosition.x + e->x * PMONITOR->vecSize.x, PMONITOR->vecPosition.y + e->y * PMONITOR->vecSize.y); - const auto local = g_pInputManager->getMouseCoordsInternal() - m_sTouchData.touchSurfaceOrigin; + auto local = g_pInputManager->getMouseCoordsInternal() - m_sTouchData.touchSurfaceOrigin; + if (m_sTouchData.touchFocusWindow->m_bIsX11) + local = local * m_sTouchData.touchFocusWindow->m_fX11SurfaceScaledBy; wlr_seat_touch_notify_motion(g_pCompositor->m_sSeat.seat, e->time_msec, e->touch_id, local.x, local.y); - wlr_seat_pointer_notify_motion(g_pCompositor->m_sSeat.seat, e->time_msec, local.x, local.y); + // wlr_seat_pointer_notify_motion(g_pCompositor->m_sSeat.seat, e->time_msec, local.x, local.y); } else if (m_sTouchData.touchFocusLS) { const auto PMONITOR = g_pCompositor->getMonitorFromID(m_sTouchData.touchFocusLS->monitorID); @@ -75,7 +81,7 @@ void CInputManager::onTouchMove(wlr_touch_motion_event* e) { const auto local = g_pInputManager->getMouseCoordsInternal() - m_sTouchData.touchSurfaceOrigin; wlr_seat_touch_notify_motion(g_pCompositor->m_sSeat.seat, e->time_msec, e->touch_id, local.x, local.y); - wlr_seat_pointer_notify_motion(g_pCompositor->m_sSeat.seat, e->time_msec, local.x, local.y); + // wlr_seat_pointer_notify_motion(g_pCompositor->m_sSeat.seat, e->time_msec, local.x, local.y); } } diff --git a/src/meson.build b/src/meson.build index 2065c6f5..0af864b9 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,4 +1,4 @@ -globber = run_command('find', '.', '-name', '*.cpp', check: true) +globber = run_command('sh', '-c', 'find . -name "*.cpp" | sort', check: true) src = globber.stdout().strip().split('\n') executable('Hyprland', src, diff --git a/src/plugins/HookSystem.cpp b/src/plugins/HookSystem.cpp index e6bd354b..a7625ffe 100644 --- a/src/plugins/HookSystem.cpp +++ b/src/plugins/HookSystem.cpp @@ -1,4 +1,6 @@ #include "HookSystem.hpp" +#include "../debug/Log.hpp" +#include "../helpers/VarList.hpp" #define register #include @@ -18,12 +20,12 @@ CFunctionHook::~CFunctionHook() { unhook(); } -size_t CFunctionHook::getInstructionLenAt(void* start) { +CFunctionHook::SInstructionProbe CFunctionHook::getInstructionLenAt(void* start) { ud_t udis; ud_init(&udis); ud_set_mode(&udis, 64); - ud_set_syntax(&udis, UD_SYN_INTEL); + ud_set_syntax(&udis, UD_SYN_ATT); size_t curOffset = 1; size_t insSize = 0; @@ -40,27 +42,101 @@ size_t CFunctionHook::getInstructionLenAt(void* start) { if (const auto CINS = ud_insn_asm(&udis); CINS) ins = std::string(CINS); - if (!ins.empty() && ins.find("rip") != std::string::npos) { - // todo: support something besides call qword ptr [rip + 0xdeadbeef] - // I don't have an assembler. I don't think udis provides one. Besides, variables might be tricky. - if (((uint8_t*)start)[0] == 0xFF && ((uint8_t*)start)[1] == 0x15) - m_vTrampolineRIPUses.emplace_back(std::make_pair<>((uint64_t)start - (uint64_t)m_pSource, ins)); - } - - return insSize; + return {insSize, ins}; } -size_t CFunctionHook::probeMinimumJumpSize(void* start, size_t min) { +CFunctionHook::SInstructionProbe CFunctionHook::probeMinimumJumpSize(void* start, size_t min) { - size_t size = 0; + size_t size = 0; + + std::string instrs = ""; + std::vector sizes; while (size <= min) { // find info about this instruction - size_t insLen = getInstructionLenAt((uint8_t*)start + size); - size += insLen; + auto probe = getInstructionLenAt((uint8_t*)start + size); + sizes.push_back(probe.len); + size += probe.len; + instrs += probe.assembly + "\n"; } - return size; + return {size, instrs, sizes}; +} + +CFunctionHook::SAssembly CFunctionHook::fixInstructionProbeRIPCalls(const SInstructionProbe& probe) { + // analyze the code and fix what we know how to. + uint64_t currentAddress = (uint64_t)m_pSource; + // actually newline + 1 + size_t lastAsmNewline = 0; + std::string assemblyBuilder; + for (auto& len : probe.insSizes) { + + std::string code = probe.assembly.substr(lastAsmNewline, probe.assembly.find("\n", lastAsmNewline) - lastAsmNewline); + if (code.contains("%rip")) { + CVarList tokens{code, 0, 's'}; + size_t plusPresent = tokens[1][0] == '+' ? 1 : 0; + std::string addr = tokens[1].substr(plusPresent, tokens[1].find("(%rip)") - plusPresent); + const uint64_t OFFSET = configStringToInt(addr); + if (OFFSET == 0) + return {}; + const uint64_t DESTINATION = currentAddress + OFFSET + len; + + if (code.starts_with("mov")) { + // mov +0xdeadbeef(%rip), %rax + assemblyBuilder += std::format("movabs $0x{:x}, {}\n", DESTINATION, tokens[2]); + } else if (code.starts_with("call")) { + // call +0xdeadbeef(%rip) + assemblyBuilder += std::format("pushq %rax\nmovabs $0x{:x}, %rax\ncallq *%rax\npopq %rax\n", DESTINATION); + } else if (code.starts_with("lea")) { + // lea 0xdeadbeef(%rip), %rax + assemblyBuilder += std::format("movabs $0x{:x}, {}\n", DESTINATION, tokens[2]); + } else { + return {}; + } + } else if (code.contains("invalid")) { + std::vector bytes; + bytes.resize(len); + memcpy(bytes.data(), (std::byte*)currentAddress, len); + if (len == 4 && bytes[0] == 0xF3 && bytes[1] == 0x0F && bytes[2] == 0x1E && bytes[3] == 0xFA) { + // F3 0F 1E FA = endbr64, udis doesn't understand that one + assemblyBuilder += "endbr64\n"; + } else { + // raise error, unknown op + std::string strBytes; + for (auto& b : bytes) { + strBytes += std::format("{:x} ", b); + } + Debug::log(ERR, "[functionhook] unknown bytes: {}", strBytes); + return {}; + } + } else { + assemblyBuilder += code + "\n"; + } + + lastAsmNewline = probe.assembly.find("\n", lastAsmNewline) + 1; + currentAddress += len; + } + + std::ofstream ofs("/tmp/hypr/.hookcode.asm", std::ios::trunc); + ofs << assemblyBuilder; + ofs.close(); + std::string ret = execAndGet( + "cc -x assembler -c /tmp/hypr/.hookcode.asm -o /tmp/hypr/.hookbinary.o 2>&1 && objcopy -O binary -j .text /tmp/hypr/.hookbinary.o /tmp/hypr/.hookbinary2.o 2>&1"); + Debug::log(LOG, "[functionhook] assembler returned:\n{}", ret); + if (!std::filesystem::exists("/tmp/hypr/.hookbinary2.o")) { + std::filesystem::remove("/tmp/hypr/.hookcode.asm"); + std::filesystem::remove("/tmp/hypr/.hookbinary.asm"); + return {}; + } + + std::ifstream ifs("/tmp/hypr/.hookbinary2.o", std::ios::binary); + SAssembly returns = {std::vector(std::istreambuf_iterator(ifs), {})}; + ifs.close(); + std::filesystem::remove("/tmp/hypr/.hookcode.asm"); + std::filesystem::remove("/tmp/hypr/.hookbinary.o"); + std::filesystem::remove("/tmp/hypr/.hookbinary2.o"); + + return returns; } bool CFunctionHook::hook() { @@ -80,65 +156,62 @@ bool CFunctionHook::hook() { static constexpr uint8_t POP_RAX[] = {0x58}; // nop static constexpr uint8_t NOP = 0x90; - /* - movabs $0,%rax - callq *%rax - offset for addr: 3 - */ - static constexpr uint8_t CALL_WITH_RAX[] = {0x48, 0xB8, 0xEF, 0xBE, 0xAD, 0xDE, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x10}; - static constexpr size_t CALL_WITH_RAX_ADDRESS_OFFSET = 2; + // probe instructions to be trampolin'd + SInstructionProbe probe; + try { + probe = probeMinimumJumpSize(m_pSource, sizeof(ABSOLUTE_JMP_ADDRESS) + sizeof(PUSH_RAX) + sizeof(POP_RAX)); + } catch (std::exception& e) { return false; } - // get minimum size to overwrite - const auto HOOKSIZE = probeMinimumJumpSize(m_pSource, sizeof(ABSOLUTE_JMP_ADDRESS) + sizeof(PUSH_RAX) + sizeof(POP_RAX)); + const auto PROBEFIXEDASM = fixInstructionProbeRIPCalls(probe); + + if (PROBEFIXEDASM.bytes.size() == 0) { + Debug::log(ERR, "[functionhook] failed, unsupported asm / failed assembling:\n{}", probe.assembly); + return false; + } + + const size_t HOOKSIZE = PROBEFIXEDASM.bytes.size(); + const size_t ORIGSIZE = probe.len; // alloc trampoline - const auto TRAMPOLINE_SIZE = sizeof(ABSOLUTE_JMP_ADDRESS) + HOOKSIZE + sizeof(PUSH_RAX) + m_vTrampolineRIPUses.size() * (sizeof(CALL_WITH_RAX) - 6); + const auto TRAMPOLINE_SIZE = sizeof(ABSOLUTE_JMP_ADDRESS) + HOOKSIZE + sizeof(PUSH_RAX); m_pTrampolineAddr = mmap(NULL, TRAMPOLINE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - m_pOriginalBytes = malloc(HOOKSIZE); - memcpy(m_pOriginalBytes, m_pSource, HOOKSIZE); + m_pOriginalBytes = malloc(ORIGSIZE); + memcpy(m_pOriginalBytes, m_pSource, ORIGSIZE); // populate trampoline - memcpy(m_pTrampolineAddr, m_pSource, HOOKSIZE); // first, original func bytes + memcpy(m_pTrampolineAddr, PROBEFIXEDASM.bytes.data(), HOOKSIZE); // first, original but fixed func bytes memcpy((uint8_t*)m_pTrampolineAddr + HOOKSIZE, PUSH_RAX, sizeof(PUSH_RAX)); // then, pushq %rax memcpy((uint8_t*)m_pTrampolineAddr + HOOKSIZE + sizeof(PUSH_RAX), ABSOLUTE_JMP_ADDRESS, sizeof(ABSOLUTE_JMP_ADDRESS)); // then, jump to source - // fix trampoline %rip calls - for (size_t i = 0; i < m_vTrampolineRIPUses.size(); ++i) { - size_t callOffset = i * (sizeof(CALL_WITH_RAX) - 6 /* callq [rip + x] */) + m_vTrampolineRIPUses[i].first; - size_t realCallAddress = (uint64_t)m_pSource + callOffset + 6 + *((uint32_t*)((uint8_t*)m_pSource + callOffset + 2)); - - memmove((uint8_t*)m_pTrampolineAddr + callOffset + sizeof(CALL_WITH_RAX), (uint8_t*)m_pTrampolineAddr + callOffset + 6, TRAMPOLINE_SIZE - callOffset - 6); - memcpy((uint8_t*)m_pTrampolineAddr + callOffset, CALL_WITH_RAX, sizeof(CALL_WITH_RAX)); - - *(uint64_t*)((uint8_t*)m_pTrampolineAddr + callOffset + CALL_WITH_RAX_ADDRESS_OFFSET) = (uint64_t)realCallAddress; - } - // fixup trampoline addr *(uint64_t*)((uint8_t*)m_pTrampolineAddr + TRAMPOLINE_SIZE - sizeof(ABSOLUTE_JMP_ADDRESS) + ABSOLUTE_JMP_ADDRESS_OFFSET) = (uint64_t)((uint8_t*)m_pSource + sizeof(ABSOLUTE_JMP_ADDRESS)); // make jump to hk - mprotect((uint8_t*)m_pSource - ((uint64_t)m_pSource) % sysconf(_SC_PAGE_SIZE), sysconf(_SC_PAGE_SIZE), PROT_READ | PROT_WRITE | PROT_EXEC); - memcpy(m_pSource, ABSOLUTE_JMP_ADDRESS, sizeof(ABSOLUTE_JMP_ADDRESS)); + const auto PAGESIZE_VAR = sysconf(_SC_PAGE_SIZE); + const uint8_t* PROTSTART = (uint8_t*)m_pSource - ((uint64_t)m_pSource % PAGESIZE_VAR); + const size_t PROTLEN = std::ceil((float)(ORIGSIZE + ((uint64_t)m_pSource - (uint64_t)PROTSTART)) / (float)PAGESIZE_VAR) * PAGESIZE_VAR; + mprotect((uint8_t*)PROTSTART, PROTLEN, PROT_READ | PROT_WRITE | PROT_EXEC); + memcpy((uint8_t*)m_pSource, ABSOLUTE_JMP_ADDRESS, sizeof(ABSOLUTE_JMP_ADDRESS)); // make popq %rax and NOP all remaining memcpy((uint8_t*)m_pSource + sizeof(ABSOLUTE_JMP_ADDRESS), POP_RAX, sizeof(POP_RAX)); size_t currentOp = sizeof(ABSOLUTE_JMP_ADDRESS) + sizeof(POP_RAX); - memset((uint8_t*)m_pSource + currentOp, NOP, HOOKSIZE - currentOp); + memset((uint8_t*)m_pSource + currentOp, NOP, ORIGSIZE - currentOp); // fixup jump addr *(uint64_t*)((uint8_t*)m_pSource + ABSOLUTE_JMP_ADDRESS_OFFSET) = (uint64_t)(m_pDestination); // revert mprot - mprotect((uint8_t*)m_pSource - ((uint64_t)m_pSource) % sysconf(_SC_PAGE_SIZE), sysconf(_SC_PAGE_SIZE), PROT_READ | PROT_EXEC); + mprotect((uint8_t*)PROTSTART, PROTLEN, PROT_READ | PROT_EXEC); // set original addr to trampo addr m_pOriginal = m_pTrampolineAddr; m_bActive = true; - m_iHookLen = HOOKSIZE; + m_iHookLen = ORIGSIZE; m_iTrampoLen = TRAMPOLINE_SIZE; return true; diff --git a/src/plugins/HookSystem.hpp b/src/plugins/HookSystem.hpp index b871a379..8714ac32 100644 --- a/src/plugins/HookSystem.hpp +++ b/src/plugins/HookSystem.hpp @@ -14,29 +14,39 @@ class CFunctionHook { bool hook(); bool unhook(); - CFunctionHook(const CFunctionHook&) = delete; - CFunctionHook(CFunctionHook&&) = delete; + CFunctionHook(const CFunctionHook&) = delete; + CFunctionHook(CFunctionHook&&) = delete; CFunctionHook& operator=(const CFunctionHook&) = delete; - CFunctionHook& operator=(CFunctionHook&&) = delete; + CFunctionHook& operator=(CFunctionHook&&) = delete; void* m_pOriginal = nullptr; private: - void* m_pSource = nullptr; - void* m_pFunctionAddr = nullptr; - void* m_pTrampolineAddr = nullptr; - void* m_pDestination = nullptr; - size_t m_iHookLen = 0; - size_t m_iTrampoLen = 0; - HANDLE m_pOwner = nullptr; - bool m_bActive = false; + void* m_pSource = nullptr; + void* m_pFunctionAddr = nullptr; + void* m_pTrampolineAddr = nullptr; + void* m_pDestination = nullptr; + size_t m_iHookLen = 0; + size_t m_iTrampoLen = 0; + HANDLE m_pOwner = nullptr; + bool m_bActive = false; - std::vector> m_vTrampolineRIPUses; + void* m_pOriginalBytes = nullptr; - void* m_pOriginalBytes = nullptr; + struct SInstructionProbe { + size_t len = 0; + std::string assembly = ""; + std::vector insSizes; + }; - size_t probeMinimumJumpSize(void* start, size_t min); - size_t getInstructionLenAt(void* start); + struct SAssembly { + std::vector bytes; + }; + + SInstructionProbe probeMinimumJumpSize(void* start, size_t min); + SInstructionProbe getInstructionLenAt(void* start); + + SAssembly fixInstructionProbeRIPCalls(const SInstructionProbe& probe); friend class CHookSystem; }; diff --git a/src/plugins/PluginAPI.cpp b/src/plugins/PluginAPI.cpp index a133de38..283472fa 100644 --- a/src/plugins/PluginAPI.cpp +++ b/src/plugins/PluginAPI.cpp @@ -9,6 +9,10 @@ #include +APICALL const char* __hyprland_api_get_hash() { + return GIT_COMMIT_HASH; +} + APICALL bool HyprlandAPI::registerCallbackStatic(HANDLE handle, const std::string& event, HOOK_CALLBACK_FN* fn) { auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle); @@ -107,7 +111,7 @@ APICALL bool HyprlandAPI::removeFunctionHook(HANDLE handle, CFunctionHook* hook) return g_pFunctionHookSystem->removeHook(hook); } -APICALL bool HyprlandAPI::addWindowDecoration(HANDLE handle, CWindow* pWindow, IHyprWindowDecoration* pDecoration) { +APICALL bool HyprlandAPI::addWindowDecoration(HANDLE handle, CWindow* pWindow, std::unique_ptr pDecoration) { auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle); if (!PLUGIN) @@ -116,11 +120,10 @@ APICALL bool HyprlandAPI::addWindowDecoration(HANDLE handle, CWindow* pWindow, I if (!g_pCompositor->windowValidMapped(pWindow)) return false; - PLUGIN->registeredDecorations.push_back(pDecoration); + PLUGIN->registeredDecorations.push_back(pDecoration.get()); - pWindow->m_dWindowDecorations.emplace_back(pDecoration); + pWindow->addWindowDeco(std::move(pDecoration)); - pWindow->updateWindowDecos(); g_pLayoutManager->getCurrentLayout()->recalculateWindow(pWindow); return true; @@ -135,7 +138,7 @@ APICALL bool HyprlandAPI::removeWindowDecoration(HANDLE handle, IHyprWindowDecor for (auto& w : g_pCompositor->m_vWindows) { for (auto& d : w->m_dWindowDecorations) { if (d.get() == pDecoration) { - std::erase(w->m_dWindowDecorations, d); + w->removeWindowDeco(pDecoration); return true; } } @@ -153,13 +156,26 @@ APICALL bool HyprlandAPI::addConfigValue(HANDLE handle, const std::string& name, if (!PLUGIN) return false; - if (name.find("plugin:") != 0) + if (!name.starts_with("plugin:")) return false; g_pConfigManager->addPluginConfigVar(handle, name, value); return true; } +APICALL bool HyprlandAPI::addConfigKeyword(HANDLE handle, const std::string& name, std::function fn) { + auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle); + + if (!g_pPluginSystem->m_bAllowConfigVars) + return false; + + if (!PLUGIN) + return false; + + g_pConfigManager->addPluginKeyword(handle, name, fn); + return true; +} + APICALL SConfigValue* HyprlandAPI::getConfigValue(HANDLE handle, const std::string& name) { auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle); @@ -277,7 +293,7 @@ APICALL std::vector HyprlandAPI::findFunctionsByName(HANDLE hand const auto FPATH = std::filesystem::canonical(exe); #elif defined(__OpenBSD__) // Neither KERN_PROC_PATHNAME nor /proc are supported - const auto FPATH = std::filesystem::canonical("/usr/local/bin/Hyprland"); + const auto FPATH = std::filesystem::canonical("/usr/local/bin/Hyprland"); #else const auto FPATH = std::filesystem::canonical("/proc/self/exe"); #endif @@ -301,7 +317,11 @@ APICALL std::vector HyprlandAPI::findFunctionsByName(HANDLE hand count++; } - return SYMBOLSDEMANGLED.substr(pos, SYMBOLSDEMANGLED.find('\n', pos + 1) - pos); + // Skip the newline char itself + if (pos != 0) + pos++; + + return SYMBOLSDEMANGLED.substr(pos, SYMBOLSDEMANGLED.find('\n', pos) - pos); }; if (SYMBOLS.empty()) { @@ -334,3 +354,12 @@ APICALL std::vector HyprlandAPI::findFunctionsByName(HANDLE hand return matches; } + +APICALL SVersionInfo HyprlandAPI::getHyprlandVersion(HANDLE handle) { + auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle); + + if (!PLUGIN) + return {}; + + return {GIT_COMMIT_HASH, GIT_TAG, GIT_DIRTY != std::string(""), GIT_BRANCH, GIT_COMMIT_MESSAGE}; +} \ No newline at end of file diff --git a/src/plugins/PluginAPI.hpp b/src/plugins/PluginAPI.hpp index c14c820a..12e7c128 100644 --- a/src/plugins/PluginAPI.hpp +++ b/src/plugins/PluginAPI.hpp @@ -23,12 +23,13 @@ Feel like the API is missing something you'd like to use in your plugin? Open an #include "../helpers/Color.hpp" #include "HookSystem.hpp" #include "../SharedDefs.hpp" +#include "../version.h" #include #include #include -typedef std::function HOOK_CALLBACK_FN; +typedef std::function HOOK_CALLBACK_FN; typedef struct { std::string name; std::string description; @@ -42,6 +43,14 @@ struct SFunctionMatch { std::string demangled; }; +struct SVersionInfo { + std::string hash; + std::string tag; + bool dirty = false; + std::string branch; + std::string message; +}; + #define APICALL extern "C" #define EXPORT __attribute__((visibility("default"))) #define REQUIRED @@ -105,6 +114,14 @@ namespace HyprlandAPI { */ APICALL bool addConfigValue(HANDLE handle, const std::string& name, const SConfigValue& value); + /* + Add a config keyword. + This method may only be called in "pluginInit" + + returns: true on success, false on fail + */ + APICALL bool addConfigKeyword(HANDLE handle, const std::string& name, std::function fn); + /* Get a config value. @@ -204,7 +221,7 @@ namespace HyprlandAPI { returns: true on success. False otherwise. */ - APICALL bool addWindowDecoration(HANDLE handle, CWindow* pWindow, IHyprWindowDecoration* pDecoration); + APICALL bool addWindowDecoration(HANDLE handle, CWindow* pWindow, std::unique_ptr pDecoration); /* Removes a window decoration @@ -250,4 +267,24 @@ namespace HyprlandAPI { Empty means either none found or handle was invalid */ APICALL std::vector findFunctionsByName(HANDLE handle, const std::string& name); -}; \ No newline at end of file + + /* + Returns the hyprland version data. It's highly advised to not run plugins compiled + for a different hash. + */ + APICALL SVersionInfo getHyprlandVersion(HANDLE handle); +}; + +/* + Get the hash this plugin/server was compiled with. + + This function will end up in both hyprland and any/all plugins, + and can be found by a simple dlsym() + + _get_hash() is server, + _get_client_hash() is client. +*/ +APICALL EXPORT const char* __hyprland_api_get_hash(); +APICALL inline EXPORT const char* __hyprland_api_get_client_hash() { + return GIT_COMMIT_HASH; +} diff --git a/src/plugins/PluginSystem.cpp b/src/plugins/PluginSystem.cpp index a8ab904c..1468cb79 100644 --- a/src/plugins/PluginSystem.cpp +++ b/src/plugins/PluginSystem.cpp @@ -10,7 +10,10 @@ CPluginSystem::CPluginSystem() { CPlugin* CPluginSystem::loadPlugin(const std::string& path) { + m_szLastError = ""; + if (getPluginByPath(path)) { + m_szLastError = "Cannot load a plugin twice!"; Debug::log(ERR, " [PluginSystem] Cannot load a plugin twice!"); return nullptr; } @@ -22,7 +25,9 @@ CPlugin* CPluginSystem::loadPlugin(const std::string& path) { HANDLE MODULE = dlopen(path.c_str(), RTLD_LAZY); if (!MODULE) { - Debug::log(ERR, " [PluginSystem] Plugin {} could not be loaded: {}", path, dlerror()); + std::string strerr = dlerror(); + m_szLastError = std::format("Plugin {} could not be loaded: {}", path, strerr); + Debug::log(ERR, " [PluginSystem] Plugin {} could not be loaded: {}", path, strerr); m_vLoadedPlugins.pop_back(); return nullptr; } @@ -33,6 +38,7 @@ CPlugin* CPluginSystem::loadPlugin(const std::string& path) { PPLUGIN_INIT_FUNC initFunc = (PPLUGIN_INIT_FUNC)dlsym(MODULE, PLUGIN_INIT_FUNC_STR); if (!apiVerFunc || !initFunc) { + m_szLastError = std::format("Plugin {} could not be loaded: {}", path, "missing apiver/init func"); Debug::log(ERR, " [PluginSystem] Plugin {} could not be loaded. (No apiver/init func)", path); dlclose(MODULE); m_vLoadedPlugins.pop_back(); @@ -42,6 +48,7 @@ CPlugin* CPluginSystem::loadPlugin(const std::string& path) { const std::string PLUGINAPIVER = apiVerFunc(); if (PLUGINAPIVER != HYPRLAND_API_VERSION) { + m_szLastError = std::format("Plugin {} could not be loaded: {}", path, "API version mismatch"); Debug::log(ERR, " [PluginSystem] Plugin {} could not be loaded. (API version mismatch)", path); dlclose(MODULE); m_vLoadedPlugins.pop_back(); @@ -56,10 +63,11 @@ CPlugin* CPluginSystem::loadPlugin(const std::string& path) { PLUGINDATA = initFunc(MODULE); } else { // this module crashed. - throw std::exception(); + throw std::runtime_error("received a fatal signal"); } } catch (std::exception& e) { m_bAllowConfigVars = false; + m_szLastError = std::format("Plugin {} could not be loaded: plugin crashed/threw in main: {}", path, e.what()); Debug::log(ERR, " [PluginSystem] Plugin {} (Handle {:x}) crashed in init. Unloading.", path, (uintptr_t)MODULE); unloadPlugin(PLUGIN, true); // Plugin could've already hooked/done something return nullptr; diff --git a/src/plugins/PluginSystem.hpp b/src/plugins/PluginSystem.hpp index 0ef30bab..f0c9c9ad 100644 --- a/src/plugins/PluginSystem.hpp +++ b/src/plugins/PluginSystem.hpp @@ -38,6 +38,7 @@ class CPluginSystem { std::vector getAllPlugins(); bool m_bAllowConfigVars = false; + std::string m_szLastError = ""; private: std::vector> m_vLoadedPlugins; diff --git a/src/protocols/FractionalScale.cpp b/src/protocols/FractionalScale.cpp index f659a63a..86b5afbc 100644 --- a/src/protocols/FractionalScale.cpp +++ b/src/protocols/FractionalScale.cpp @@ -53,7 +53,9 @@ void CFractionalScaleProtocolManager::bindManager(wl_client* client, void* data, static void handleDestroyScaleAddon(wl_client* client, wl_resource* resource); // -static const struct wp_fractional_scale_v1_interface fractionalScaleAddonImpl { .destroy = handleDestroyScaleAddon }; +static const struct wp_fractional_scale_v1_interface fractionalScaleAddonImpl { + .destroy = handleDestroyScaleAddon +}; // SFractionalScaleAddon* addonFromResource(wl_resource* resource) { @@ -78,6 +80,11 @@ void CFractionalScaleProtocolManager::getFractionalScale(wl_client* client, wl_r const auto PSURFACE = wlr_surface_from_resource(surface); const auto PADDON = getAddonForSurface(PSURFACE); + if (PADDON->pResource) { + wl_resource_post_error(resource, WP_FRACTIONAL_SCALE_MANAGER_V1_ERROR_FRACTIONAL_SCALE_EXISTS, "Fractional scale exists."); + return; + } + PADDON->pResource = wl_resource_create(client, &wp_fractional_scale_v1_interface, wl_resource_get_version(resource), id); wl_resource_set_implementation(PADDON->pResource, &fractionalScaleAddonImpl, PADDON, handleAddonDestroy); diff --git a/src/protocols/Screencopy.cpp b/src/protocols/Screencopy.cpp index 61f76285..18fe09f4 100644 --- a/src/protocols/Screencopy.cpp +++ b/src/protocols/Screencopy.cpp @@ -119,7 +119,7 @@ CScreencopyClient::~CScreencopyClient() { CScreencopyClient::CScreencopyClient() { lastMeasure.reset(); lastFrame.reset(); - tickCallback = g_pHookSystem->hookDynamic("tick", [&](void* self, std::any data) { onTick(); }); + tickCallback = g_pHookSystem->hookDynamic("tick", [&](void* self, SCallbackInfo& info, std::any data) { onTick(); }); } void CScreencopyClient::onTick() { @@ -183,7 +183,7 @@ void CScreencopyProtocolManager::removeFrame(SScreencopyFrame* frame, bool force m_lFrames.remove(*frame); } -void CScreencopyProtocolManager::captureOutput(wl_client* client, wl_resource* resource, uint32_t frame, int32_t overlay_cursor, wl_resource* output, wlr_box box) { +void CScreencopyProtocolManager::captureOutput(wl_client* client, wl_resource* resource, uint32_t frame, int32_t overlay_cursor, wl_resource* output, CBox box) { const auto PCLIENT = clientFromResource(resource); const auto PFRAME = &m_lFrames.emplace_back(); @@ -210,7 +210,8 @@ void CScreencopyProtocolManager::captureOutput(wl_client* client, wl_resource* r PFRAME->client = PCLIENT; PCLIENT->ref++; - PFRAME->shmFormat = wlr_output_preferred_read_format(PFRAME->pMonitor->output); + g_pHyprRenderer->makeEGLCurrent(); + PFRAME->shmFormat = g_pHyprOpenGL->getPreferredReadFormat(PFRAME->pMonitor); if (PFRAME->shmFormat == DRM_FORMAT_INVALID) { Debug::log(ERR, "No format supported by renderer in capture output"); zwlr_screencopy_frame_v1_send_failed(PFRAME->resource); @@ -239,10 +240,9 @@ void CScreencopyProtocolManager::captureOutput(wl_client* client, wl_resource* r } int ow, oh; wlr_output_effective_resolution(PFRAME->pMonitor->output, &ow, &oh); - wlr_box_transform(&PFRAME->box, &PFRAME->box, PFRAME->pMonitor->transform, ow, oh); - scaleBox(&PFRAME->box, PFRAME->pMonitor->scale); + PFRAME->box.transform(PFRAME->pMonitor->transform, ow, oh).scale(PFRAME->pMonitor->scale).round(); - PFRAME->shmStride = (PSHMINFO->bpp / 8) * PFRAME->box.width; + PFRAME->shmStride = pixel_format_info_min_stride(PSHMINFO, PFRAME->box.w); zwlr_screencopy_frame_v1_send_buffer(PFRAME->resource, convert_drm_format_to_wl_shm(PFRAME->shmFormat), PFRAME->box.width, PFRAME->box.height, PFRAME->shmStride); @@ -263,20 +263,23 @@ void CScreencopyProtocolManager::copyFrame(wl_client* client, wl_resource* resou return; } - const auto PBUFFER = wlr_buffer_from_resource(buffer); + const auto PBUFFER = wlr_buffer_try_from_resource(buffer); if (!PBUFFER) { + Debug::log(ERR, "[sc] invalid buffer in {:x}", (uintptr_t)PFRAME); wl_resource_post_error(PFRAME->resource, ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer"); removeFrame(PFRAME); return; } if (PBUFFER->width != PFRAME->box.width || PBUFFER->height != PFRAME->box.height) { + Debug::log(ERR, "[sc] invalid dimensions in {:x}", (uintptr_t)PFRAME); wl_resource_post_error(PFRAME->resource, ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer dimensions"); removeFrame(PFRAME); return; } if (PFRAME->buffer) { + Debug::log(ERR, "[sc] buffer used in {:x}", (uintptr_t)PFRAME); wl_resource_post_error(PFRAME->resource, ZWLR_SCREENCOPY_FRAME_V1_ERROR_ALREADY_USED, "frame already used"); removeFrame(PFRAME); return; @@ -290,6 +293,7 @@ void CScreencopyProtocolManager::copyFrame(wl_client* client, wl_resource* resou PFRAME->bufferCap = WLR_BUFFER_CAP_DMABUF; if (dmabufAttrs.format != PFRAME->dmabufFormat) { + Debug::log(ERR, "[sc] invalid buffer dma format in {:x}", (uintptr_t)PFRAME); wl_resource_post_error(PFRAME->resource, ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer format"); removeFrame(PFRAME); return; @@ -298,15 +302,18 @@ void CScreencopyProtocolManager::copyFrame(wl_client* client, wl_resource* resou wlr_buffer_end_data_ptr_access(PBUFFER); if (wlrBufferAccessFormat != PFRAME->shmFormat) { + Debug::log(ERR, "[sc] invalid buffer shm format in {:x}", (uintptr_t)PFRAME); wl_resource_post_error(PFRAME->resource, ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer format"); removeFrame(PFRAME); return; } else if ((int)wlrBufferAccessStride != PFRAME->shmStride) { + Debug::log(ERR, "[sc] invalid buffer shm stride in {:x}", (uintptr_t)PFRAME); wl_resource_post_error(PFRAME->resource, ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer stride"); removeFrame(PFRAME); return; } } else { + Debug::log(ERR, "[sc] invalid buffer type in {:x}", (uintptr_t)PFRAME); wl_resource_post_error(PFRAME->resource, ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer type"); removeFrame(PFRAME); return; @@ -325,7 +332,7 @@ void CScreencopyProtocolManager::copyFrame(wl_client* client, wl_resource* resou } void CScreencopyProtocolManager::onOutputCommit(CMonitor* pMonitor, wlr_output_event_commit* e) { - m_pLastMonitorBackBuffer = e->buffer; + m_pLastMonitorBackBuffer = e->state->buffer; shareAllFrames(pMonitor); m_pLastMonitorBackBuffer = nullptr; } @@ -382,11 +389,13 @@ void CScreencopyProtocolManager::shareFrame(SScreencopyFrame* frame) { uint32_t flags = 0; if (frame->bufferCap == WLR_BUFFER_CAP_DMABUF) { if (!copyFrameDmabuf(frame)) { + Debug::log(ERR, "[sc] dmabuf copy failed in {:x}", (uintptr_t)frame); zwlr_screencopy_frame_v1_send_failed(frame->resource); return; } } else { if (!copyFrameShm(frame, &now)) { + Debug::log(ERR, "[sc] shm copy failed in {:x}", (uintptr_t)frame); zwlr_screencopy_frame_v1_send_failed(frame->resource); return; } @@ -416,23 +425,76 @@ void CScreencopyProtocolManager::sendFrameDamage(SScreencopyFrame* frame) { } bool CScreencopyProtocolManager::copyFrameShm(SScreencopyFrame* frame, timespec* now) { + wlr_texture* sourceTex = wlr_texture_from_buffer(g_pCompositor->m_sWLRRenderer, m_pLastMonitorBackBuffer); + if (!sourceTex) + return false; + void* data; uint32_t format; size_t stride; - if (!wlr_buffer_begin_data_ptr_access(frame->buffer, WLR_BUFFER_DATA_PTR_ACCESS_WRITE, &data, &format, &stride)) + if (!wlr_buffer_begin_data_ptr_access(frame->buffer, WLR_BUFFER_DATA_PTR_ACCESS_WRITE, &data, &format, &stride)) { + wlr_texture_destroy(sourceTex); return false; + } - if (!wlr_renderer_begin_with_buffer(g_pCompositor->m_sWLRRenderer, m_pLastMonitorBackBuffer)) { - Debug::log(ERR, "[sc] shm: Client requested a copy to a buffer that failed to pass wlr_renderer_begin_with_buffer"); + CRegion fakeDamage = {0, 0, INT16_MAX, INT16_MAX}; + + g_pHyprRenderer->makeEGLCurrent(); + + CFramebuffer fb; + fb.alloc(frame->box.w, frame->box.h, g_pHyprRenderer->isNvidia() ? DRM_FORMAT_XBGR8888 : frame->pMonitor->drmFormat); + + if (!g_pHyprRenderer->beginRender(frame->pMonitor, fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, &fb)) { + wlr_texture_destroy(sourceTex); wlr_buffer_end_data_ptr_access(frame->buffer); return false; } - bool success = wlr_renderer_read_pixels(g_pCompositor->m_sWLRRenderer, format, stride, frame->box.width, frame->box.height, frame->box.x, frame->box.y, 0, 0, data); - wlr_renderer_end(g_pCompositor->m_sWLRRenderer); - wlr_buffer_end_data_ptr_access(frame->buffer); + CBox monbox = CBox{0, 0, frame->pMonitor->vecTransformedSize.x, frame->pMonitor->vecTransformedSize.y}.translate({-frame->box.x, -frame->box.y}); + g_pHyprOpenGL->setMonitorTransformEnabled(false); + g_pHyprOpenGL->renderTexture(sourceTex, &monbox, 1); + g_pHyprOpenGL->setMonitorTransformEnabled(true); - return success; +#ifndef GLES2 + glBindFramebuffer(GL_READ_FRAMEBUFFER, fb.m_iFb); +#else + glBindFramebuffer(GL_FRAMEBUFFER, fb.m_iFb); +#endif + + const auto PFORMAT = g_pHyprOpenGL->getPixelFormatFromDRM(format); + if (!PFORMAT) { + g_pHyprRenderer->endRender(); + wlr_texture_destroy(sourceTex); + wlr_buffer_end_data_ptr_access(frame->buffer); + return false; + } + + g_pHyprRenderer->endRender(); + + g_pHyprRenderer->makeEGLCurrent(); + g_pHyprOpenGL->m_RenderData.pMonitor = frame->pMonitor; + fb.bind(); + + glPixelStorei(GL_PACK_ALIGNMENT, 1); + + const wlr_pixel_format_info* drmFmtWlr = drm_get_pixel_format_info(format); + uint32_t packStride = pixel_format_info_min_stride(drmFmtWlr, frame->box.w); + + if (packStride == stride) { + glReadPixels(0, 0, frame->box.w, frame->box.h, PFORMAT->glFormat, PFORMAT->glType, data); + } else { + for (size_t i = 0; i < frame->box.h; ++i) { + uint32_t y = i; + glReadPixels(0, y, frame->box.w, 1, PFORMAT->glFormat, PFORMAT->glType, ((unsigned char*)data) + i * stride); + } + } + + g_pHyprOpenGL->m_RenderData.pMonitor = nullptr; + + wlr_buffer_end_data_ptr_access(frame->buffer); + wlr_texture_destroy(sourceTex); + + return true; } bool CScreencopyProtocolManager::copyFrameDmabuf(SScreencopyFrame* frame) { @@ -440,25 +502,19 @@ bool CScreencopyProtocolManager::copyFrameDmabuf(SScreencopyFrame* frame) { if (!sourceTex) return false; - float glMatrix[9]; - wlr_matrix_identity(glMatrix); - wlr_matrix_translate(glMatrix, -frame->box.x, -frame->box.y); - wlr_matrix_scale(glMatrix, frame->pMonitor->vecPixelSize.x, frame->pMonitor->vecPixelSize.y); + CRegion fakeDamage = {0, 0, frame->box.width, frame->box.height}; - if (!wlr_renderer_begin_with_buffer(g_pCompositor->m_sWLRRenderer, frame->buffer)) { - Debug::log(ERR, "[sc] dmabuf: Client requested a copy to a buffer that failed to pass wlr_renderer_begin_with_buffer"); - wlr_texture_destroy(sourceTex); + if (!g_pHyprRenderer->beginRender(frame->pMonitor, fakeDamage, RENDER_MODE_TO_BUFFER, frame->buffer)) return false; - } - float color[] = {0, 0, 0, 0}; - wlr_renderer_clear(g_pCompositor->m_sWLRRenderer, color); - // TODO: use hl render methods to use damage - wlr_render_texture_with_matrix(g_pCompositor->m_sWLRRenderer, sourceTex, glMatrix, 1.0f); + CBox monbox = CBox{0, 0, frame->pMonitor->vecPixelSize.x, frame->pMonitor->vecPixelSize.y}.translate({-frame->box.x, -frame->box.y}); + g_pHyprOpenGL->setMonitorTransformEnabled(false); + g_pHyprOpenGL->renderTexture(sourceTex, &monbox, 1); + g_pHyprOpenGL->setMonitorTransformEnabled(true); + + g_pHyprRenderer->endRender(); wlr_texture_destroy(sourceTex); - wlr_renderer_end(g_pCompositor->m_sWLRRenderer); - return true; } diff --git a/src/protocols/Screencopy.hpp b/src/protocols/Screencopy.hpp index 3af75544..0c709c7b 100644 --- a/src/protocols/Screencopy.hpp +++ b/src/protocols/Screencopy.hpp @@ -10,8 +10,7 @@ class CMonitor; -enum eClientOwners -{ +enum eClientOwners { CLIENT_SCREENCOPY = 0, CLIENT_TOPLEVEL_EXPORT }; @@ -46,7 +45,7 @@ struct SScreencopyFrame { uint32_t shmFormat = 0; uint32_t dmabufFormat = 0; - wlr_box box = {0}; + CBox box = {}; int shmStride = 0; bool overlayCursor = false; @@ -73,7 +72,7 @@ class CScreencopyProtocolManager { void removeFrame(SScreencopyFrame* frame, bool force = false); void displayDestroy(); - void captureOutput(wl_client* client, wl_resource* resource, uint32_t frame, int32_t overlay_cursor, wl_resource* output, wlr_box box = {0, 0, 0, 0}); + void captureOutput(wl_client* client, wl_resource* resource, uint32_t frame, int32_t overlay_cursor, wl_resource* output, CBox box = {0, 0, 0, 0}); void copyFrame(wl_client* client, wl_resource* resource, wl_resource* buffer); diff --git a/src/protocols/TextInputV1.cpp b/src/protocols/TextInputV1.cpp index 9156da12..d69a79ca 100644 --- a/src/protocols/TextInputV1.cpp +++ b/src/protocols/TextInputV1.cpp @@ -200,7 +200,7 @@ void CTextInputV1ProtocolManager::handleSetContentType(wl_client* client, wl_res void CTextInputV1ProtocolManager::handleSetCursorRectangle(wl_client* client, wl_resource* resource, int32_t x, int32_t y, int32_t width, int32_t height) { const auto PTI = tiFromResource(resource); - PTI->cursorRectangle = wlr_box{x, y, width, height}; + PTI->cursorRectangle = CBox{x, y, width, height}; } void CTextInputV1ProtocolManager::handleSetPreferredLanguage(wl_client* client, wl_resource* resource, const char* language) { diff --git a/src/protocols/TextInputV1.hpp b/src/protocols/TextInputV1.hpp index f714b24c..b279763d 100644 --- a/src/protocols/TextInputV1.hpp +++ b/src/protocols/TextInputV1.hpp @@ -39,9 +39,9 @@ struct STextInputV1 { uint32_t purpose = 0; } pendingContentType; - wlr_box cursorRectangle = {0, 0, 0, 0}; + CBox cursorRectangle = {0, 0, 0, 0}; - bool operator==(const STextInputV1& other) { + bool operator==(const STextInputV1& other) { return other.client == client && other.resourceCaller == resourceCaller && other.resourceImpl == resourceImpl; } }; diff --git a/src/protocols/ToplevelExport.cpp b/src/protocols/ToplevelExport.cpp index 828ad35f..425572eb 100644 --- a/src/protocols/ToplevelExport.cpp +++ b/src/protocols/ToplevelExport.cpp @@ -134,7 +134,8 @@ void CToplevelExportProtocolManager::removeFrame(SScreencopyFrame* frame, bool f std::erase_if(m_vFramesAwaitingWrite, [&](const auto& other) { return other == frame; }); wl_resource_set_user_data(frame->resource, nullptr); - wlr_buffer_unlock(frame->buffer); + if (frame->buffer && frame->buffer->n_locks > 0) + wlr_buffer_unlock(frame->buffer); removeClient(frame->client, force); m_lFrames.remove(*frame); } @@ -176,7 +177,8 @@ void CToplevelExportProtocolManager::captureToplevel(wl_client* client, wl_resou const auto PMONITOR = g_pCompositor->getMonitorFromID(PFRAME->pWindow->m_iMonitorID); - PFRAME->shmFormat = wlr_output_preferred_read_format(PMONITOR->output); + g_pHyprRenderer->makeEGLCurrent(); + PFRAME->shmFormat = g_pHyprOpenGL->getPreferredReadFormat(PMONITOR); if (PFRAME->shmFormat == DRM_FORMAT_INVALID) { Debug::log(ERR, "No format supported by renderer in capture toplevel"); hyprland_toplevel_export_frame_v1_send_failed(resource); @@ -201,9 +203,9 @@ void CToplevelExportProtocolManager::captureToplevel(wl_client* client, wl_resou PFRAME->box = {0, 0, (int)(PFRAME->pWindow->m_vRealSize.vec().x * PMONITOR->scale), (int)(PFRAME->pWindow->m_vRealSize.vec().y * PMONITOR->scale)}; int ow, oh; wlr_output_effective_resolution(PMONITOR->output, &ow, &oh); - wlr_box_transform(&PFRAME->box, &PFRAME->box, PMONITOR->transform, ow, oh); + PFRAME->box.transform(PMONITOR->transform, ow, oh).round(); - PFRAME->shmStride = (PSHMINFO->bpp / 8) * PFRAME->box.width; + PFRAME->shmStride = pixel_format_info_min_stride(PSHMINFO, PFRAME->box.w); hyprland_toplevel_export_frame_v1_send_buffer(PFRAME->resource, convert_drm_format_to_wl_shm(PFRAME->shmFormat), PFRAME->box.width, PFRAME->box.height, PFRAME->shmStride); @@ -229,7 +231,7 @@ void CToplevelExportProtocolManager::copyFrame(wl_client* client, wl_resource* r return; } - const auto PBUFFER = wlr_buffer_from_resource(buffer); + const auto PBUFFER = wlr_buffer_try_from_resource(buffer); if (!PBUFFER) { wl_resource_post_error(PFRAME->resource, HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer"); removeFrame(PFRAME); @@ -301,9 +303,9 @@ void CToplevelExportProtocolManager::onOutputCommit(CMonitor* pMonitor, wlr_outp if (PMONITOR != g_pCompositor->getMonitorFromID(f->pWindow->m_iMonitorID)) continue; - wlr_box geometry = {f->pWindow->m_vRealPosition.vec().x, f->pWindow->m_vRealPosition.vec().y, f->pWindow->m_vRealSize.vec().x, f->pWindow->m_vRealSize.vec().y}; + CBox geometry = {f->pWindow->m_vRealPosition.vec().x, f->pWindow->m_vRealPosition.vec().y, f->pWindow->m_vRealSize.vec().x, f->pWindow->m_vRealSize.vec().y}; - if (!wlr_output_layout_intersects(g_pCompositor->m_sWLROutputLayout, pMonitor->output, &geometry)) + if (!wlr_output_layout_intersects(g_pCompositor->m_sWLROutputLayout, pMonitor->output, geometry.pWlr())) continue; shareFrame(f); @@ -362,18 +364,19 @@ bool CToplevelExportProtocolManager::copyFrameShm(SScreencopyFrame* frame, times const auto PMONITOR = g_pCompositor->getMonitorFromID(frame->pWindow->m_iMonitorID); CRegion fakeDamage{0, 0, PMONITOR->vecPixelSize.x * 10, PMONITOR->vecPixelSize.y * 10}; - if (frame->overlayCursor) - wlr_output_lock_software_cursors(PMONITOR->output, true); + g_pHyprRenderer->makeEGLCurrent(); - if (!wlr_output_attach_render(PMONITOR->output, nullptr)) { - Debug::log(ERR, "[toplevel_export] Couldn't attach render"); + CFramebuffer outFB; + outFB.alloc(PMONITOR->vecPixelSize.x, PMONITOR->vecPixelSize.y, g_pHyprRenderer->isNvidia() ? DRM_FORMAT_XBGR8888 : PMONITOR->drmFormat); + + if (!g_pHyprRenderer->beginRender(PMONITOR, fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, &outFB)) { wlr_buffer_end_data_ptr_access(frame->buffer); - if (frame->overlayCursor) - wlr_output_lock_software_cursors(PMONITOR->output, false); return false; } - g_pHyprOpenGL->begin(PMONITOR, &fakeDamage, true); + if (frame->overlayCursor) + wlr_output_lock_software_cursors(PMONITOR->output, true); + g_pHyprOpenGL->clear(CColor(0, 0, 0, 1.0)); // render client at 0,0 @@ -381,46 +384,29 @@ bool CToplevelExportProtocolManager::copyFrameShm(SScreencopyFrame* frame, times g_pHyprRenderer->renderWindow(frame->pWindow, PMONITOR, now, false, RENDER_PASS_ALL, true, true); g_pHyprRenderer->m_bBlockSurfaceFeedback = false; - if (frame->overlayCursor && wlr_renderer_begin(g_pCompositor->m_sWLRRenderer, PMONITOR->vecPixelSize.x, PMONITOR->vecPixelSize.y)) { - // hack le massive - wlr_output_cursor* cursor; - const auto OFFSET = frame->pWindow->m_vRealPosition.vec() - PMONITOR->vecPosition; - wl_list_for_each(cursor, &PMONITOR->output->cursors, link) { - if (!cursor->enabled || !cursor->visible || PMONITOR->output->hardware_cursor == cursor) { - continue; - } - cursor->x -= OFFSET.x; - cursor->y -= OFFSET.y; - } - wlr_output_render_software_cursors(PMONITOR->output, NULL); - wl_list_for_each(cursor, &PMONITOR->output->cursors, link) { - if (!cursor->enabled || !cursor->visible || PMONITOR->output->hardware_cursor == cursor) { - continue; - } - cursor->x += OFFSET.x; - cursor->y += OFFSET.y; - } - wlr_renderer_end(g_pCompositor->m_sWLRRenderer); - } + if (frame->overlayCursor) + g_pHyprRenderer->renderSoftwareCursors(PMONITOR, fakeDamage, g_pInputManager->getMouseCoordsInternal() - frame->pWindow->m_vRealPosition.vec()); - // copy pixels - const auto PFORMAT = gles2FromDRM(format); + const auto PFORMAT = g_pHyprOpenGL->getPixelFormatFromDRM(format); if (!PFORMAT) { - Debug::log(ERR, "[toplevel_export] Cannot read pixels, unsupported format {:x}", (uintptr_t)PFORMAT); - g_pHyprOpenGL->end(); + g_pHyprRenderer->endRender(); wlr_buffer_end_data_ptr_access(frame->buffer); - if (frame->overlayCursor) - wlr_output_lock_software_cursors(PMONITOR->output, false); return false; } - glBindFramebuffer(GL_FRAMEBUFFER, g_pHyprOpenGL->m_RenderData.pCurrentMonData->primaryFB.m_iFb); + g_pHyprRenderer->endRender(); - glReadPixels(0, 0, frame->box.width, frame->box.height, PFORMAT->gl_format, PFORMAT->gl_type, data); + g_pHyprRenderer->makeEGLCurrent(); + g_pHyprOpenGL->m_RenderData.pMonitor = PMONITOR; + outFB.bind(); - g_pHyprOpenGL->end(); +#ifndef GLES2 + glBindFramebuffer(GL_READ_FRAMEBUFFER, outFB.m_iFb); +#endif - wlr_output_rollback(PMONITOR->output); + glPixelStorei(GL_PACK_ALIGNMENT, 1); + + glReadPixels(0, 0, frame->box.width, frame->box.height, PFORMAT->glFormat, PFORMAT->glType, data); wlr_buffer_end_data_ptr_access(frame->buffer); @@ -431,14 +417,12 @@ bool CToplevelExportProtocolManager::copyFrameShm(SScreencopyFrame* frame, times } bool CToplevelExportProtocolManager::copyFrameDmabuf(SScreencopyFrame* frame, timespec* now) { - if (!wlr_renderer_begin_with_buffer(g_pCompositor->m_sWLRRenderer, frame->buffer)) - return false; - const auto PMONITOR = g_pCompositor->getMonitorFromID(frame->pWindow->m_iMonitorID); CRegion fakeDamage{0, 0, INT16_MAX, INT16_MAX}; - g_pHyprOpenGL->begin(PMONITOR, &fakeDamage, true); + if (!g_pHyprRenderer->beginRender(PMONITOR, fakeDamage, RENDER_MODE_TO_BUFFER, frame->buffer)) + return false; g_pHyprOpenGL->clear(CColor(0, 0, 0, 1.0)); @@ -446,14 +430,10 @@ bool CToplevelExportProtocolManager::copyFrameDmabuf(SScreencopyFrame* frame, ti g_pHyprRenderer->renderWindow(frame->pWindow, PMONITOR, now, false, RENDER_PASS_ALL, true, true); g_pHyprRenderer->m_bBlockSurfaceFeedback = false; - g_pHyprOpenGL->bindWlrOutputFb(); + if (frame->overlayCursor) + g_pHyprRenderer->renderSoftwareCursors(PMONITOR, fakeDamage, g_pInputManager->getMouseCoordsInternal() - frame->pWindow->m_vRealPosition.vec()); - wlr_box monbox = {0, 0, PMONITOR->vecPixelSize.x, PMONITOR->vecPixelSize.y}; - g_pHyprOpenGL->renderTexture(g_pHyprOpenGL->m_RenderData.pCurrentMonData->primaryFB.m_cTex, &monbox, 1.f); - - g_pHyprOpenGL->end(); - - wlr_renderer_end(g_pCompositor->m_sWLRRenderer); + g_pHyprRenderer->endRender(); return true; } diff --git a/src/protocols/ToplevelExportWlrFuncs.hpp b/src/protocols/ToplevelExportWlrFuncs.hpp index b580d3f0..b6f387e8 100644 --- a/src/protocols/ToplevelExportWlrFuncs.hpp +++ b/src/protocols/ToplevelExportWlrFuncs.hpp @@ -11,8 +11,10 @@ struct wlr_pixel_format_info { */ uint32_t opaque_substitute; - /* Bits per pixels */ - uint32_t bpp; + /* Bytes per block (including padding) */ + uint32_t bytes_per_block; + /* Size of a block in pixels (zero for 1×1) */ + uint32_t block_width, block_height; /* True if the format has an alpha channel */ bool has_alpha; @@ -20,143 +22,171 @@ struct wlr_pixel_format_info { static const struct wlr_pixel_format_info pixel_format_info[] = { { - .drm_format = DRM_FORMAT_XRGB8888, - .opaque_substitute = DRM_FORMAT_INVALID, - .bpp = 32, - .has_alpha = false, + .drm_format = DRM_FORMAT_XRGB8888, + .bytes_per_block = 4, }, { .drm_format = DRM_FORMAT_ARGB8888, .opaque_substitute = DRM_FORMAT_XRGB8888, - .bpp = 32, + .bytes_per_block = 4, .has_alpha = true, }, { - .drm_format = DRM_FORMAT_XBGR8888, - .opaque_substitute = DRM_FORMAT_INVALID, - .bpp = 32, - .has_alpha = false, + .drm_format = DRM_FORMAT_XBGR8888, + .bytes_per_block = 4, }, { .drm_format = DRM_FORMAT_ABGR8888, .opaque_substitute = DRM_FORMAT_XBGR8888, - .bpp = 32, + .bytes_per_block = 4, .has_alpha = true, }, { - .drm_format = DRM_FORMAT_RGBX8888, - .opaque_substitute = DRM_FORMAT_INVALID, - .bpp = 32, - .has_alpha = false, + .drm_format = DRM_FORMAT_RGBX8888, + .bytes_per_block = 4, }, { .drm_format = DRM_FORMAT_RGBA8888, .opaque_substitute = DRM_FORMAT_RGBX8888, - .bpp = 32, + .bytes_per_block = 4, .has_alpha = true, }, { - .drm_format = DRM_FORMAT_BGRX8888, - .opaque_substitute = DRM_FORMAT_INVALID, - .bpp = 32, - .has_alpha = false, + .drm_format = DRM_FORMAT_BGRX8888, + .bytes_per_block = 4, }, { .drm_format = DRM_FORMAT_BGRA8888, .opaque_substitute = DRM_FORMAT_BGRX8888, - .bpp = 32, + .bytes_per_block = 4, .has_alpha = true, }, { - .drm_format = DRM_FORMAT_BGR888, - .opaque_substitute = DRM_FORMAT_INVALID, - .bpp = 24, - .has_alpha = false, + .drm_format = DRM_FORMAT_R8, + .bytes_per_block = 1, }, { - .drm_format = DRM_FORMAT_RGBX4444, - .opaque_substitute = DRM_FORMAT_INVALID, - .bpp = 16, - .has_alpha = false, + .drm_format = DRM_FORMAT_GR88, + .bytes_per_block = 2, + }, + { + .drm_format = DRM_FORMAT_RGB888, + .bytes_per_block = 3, + }, + { + .drm_format = DRM_FORMAT_BGR888, + .bytes_per_block = 3, + }, + { + .drm_format = DRM_FORMAT_RGBX4444, + .bytes_per_block = 2, }, { .drm_format = DRM_FORMAT_RGBA4444, .opaque_substitute = DRM_FORMAT_RGBX4444, - .bpp = 16, + .bytes_per_block = 2, .has_alpha = true, }, { - .drm_format = DRM_FORMAT_RGBX5551, - .opaque_substitute = DRM_FORMAT_INVALID, - .bpp = 16, - .has_alpha = false, + .drm_format = DRM_FORMAT_BGRX4444, + .bytes_per_block = 2, + }, + { + .drm_format = DRM_FORMAT_BGRA4444, + .opaque_substitute = DRM_FORMAT_BGRX4444, + .bytes_per_block = 2, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_RGBX5551, + .bytes_per_block = 2, }, { .drm_format = DRM_FORMAT_RGBA5551, .opaque_substitute = DRM_FORMAT_RGBX5551, - .bpp = 16, + .bytes_per_block = 2, .has_alpha = true, }, { - .drm_format = DRM_FORMAT_RGB565, - .opaque_substitute = DRM_FORMAT_INVALID, - .bpp = 16, - .has_alpha = false, + .drm_format = DRM_FORMAT_BGRX5551, + .bytes_per_block = 2, }, { - .drm_format = DRM_FORMAT_BGR565, - .opaque_substitute = DRM_FORMAT_INVALID, - .bpp = 16, - .has_alpha = false, + .drm_format = DRM_FORMAT_BGRA5551, + .opaque_substitute = DRM_FORMAT_BGRX5551, + .bytes_per_block = 2, + .has_alpha = true, }, { - .drm_format = DRM_FORMAT_XRGB2101010, - .opaque_substitute = DRM_FORMAT_INVALID, - .bpp = 32, - .has_alpha = false, + .drm_format = DRM_FORMAT_XRGB1555, + .bytes_per_block = 2, + }, + { + .drm_format = DRM_FORMAT_ARGB1555, + .opaque_substitute = DRM_FORMAT_XRGB1555, + .bytes_per_block = 2, + .has_alpha = true, + }, + { + .drm_format = DRM_FORMAT_RGB565, + .bytes_per_block = 2, + }, + { + .drm_format = DRM_FORMAT_BGR565, + .bytes_per_block = 2, + }, + { + .drm_format = DRM_FORMAT_XRGB2101010, + .bytes_per_block = 4, }, { .drm_format = DRM_FORMAT_ARGB2101010, .opaque_substitute = DRM_FORMAT_XRGB2101010, - .bpp = 32, + .bytes_per_block = 4, .has_alpha = true, }, { - .drm_format = DRM_FORMAT_XBGR2101010, - .opaque_substitute = DRM_FORMAT_INVALID, - .bpp = 32, - .has_alpha = false, + .drm_format = DRM_FORMAT_XBGR2101010, + .bytes_per_block = 4, }, { .drm_format = DRM_FORMAT_ABGR2101010, .opaque_substitute = DRM_FORMAT_XBGR2101010, - .bpp = 32, + .bytes_per_block = 4, .has_alpha = true, }, { - .drm_format = DRM_FORMAT_XBGR16161616F, - .opaque_substitute = DRM_FORMAT_INVALID, - .bpp = 64, - .has_alpha = false, + .drm_format = DRM_FORMAT_XBGR16161616F, + .bytes_per_block = 8, }, { .drm_format = DRM_FORMAT_ABGR16161616F, .opaque_substitute = DRM_FORMAT_XBGR16161616F, - .bpp = 64, + .bytes_per_block = 8, .has_alpha = true, }, { - .drm_format = DRM_FORMAT_XBGR16161616, - .opaque_substitute = DRM_FORMAT_INVALID, - .bpp = 64, - .has_alpha = false, + .drm_format = DRM_FORMAT_XBGR16161616, + .bytes_per_block = 8, }, { .drm_format = DRM_FORMAT_ABGR16161616, .opaque_substitute = DRM_FORMAT_XBGR16161616, - .bpp = 64, + .bytes_per_block = 8, .has_alpha = true, }, + { + .drm_format = DRM_FORMAT_YVYU, + .bytes_per_block = 4, + .block_width = 2, + .block_height = 1, + }, + { + .drm_format = DRM_FORMAT_VYUY, + .bytes_per_block = 4, + .block_width = 2, + .block_height = 1, + }, }; static const size_t pixel_format_info_size = sizeof(pixel_format_info) / sizeof(pixel_format_info[0]); @@ -187,124 +217,27 @@ static enum wl_shm_format convert_drm_format_to_wl_shm(uint32_t fmt) { } } -struct wlr_gles2_pixel_format { - uint32_t drm_format; - // optional field, if empty then internalformat = format - GLint gl_internalformat; - GLint gl_format, gl_type; - bool has_alpha; -}; +static uint32_t pixel_format_info_pixels_per_block(const struct wlr_pixel_format_info* info) { + uint32_t pixels = info->block_width * info->block_height; + return pixels > 0 ? pixels : 1; +} -static const struct wlr_gles2_pixel_format formats[] = { - { - .drm_format = DRM_FORMAT_ARGB8888, - .gl_format = GL_BGRA_EXT, - .gl_type = GL_UNSIGNED_BYTE, - .has_alpha = true, - }, - { - .drm_format = DRM_FORMAT_XRGB8888, - .gl_format = GL_BGRA_EXT, - .gl_type = GL_UNSIGNED_BYTE, - .has_alpha = false, - }, - { - .drm_format = DRM_FORMAT_XBGR8888, - .gl_format = GL_RGBA, - .gl_type = GL_UNSIGNED_BYTE, - .has_alpha = false, - }, - { - .drm_format = DRM_FORMAT_ABGR8888, - .gl_format = GL_RGBA, - .gl_type = GL_UNSIGNED_BYTE, - .has_alpha = true, - }, - { - .drm_format = DRM_FORMAT_BGR888, - .gl_format = GL_RGB, - .gl_type = GL_UNSIGNED_BYTE, - .has_alpha = false, - }, -#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ - { - .drm_format = DRM_FORMAT_RGBX4444, - .gl_format = GL_RGBA, - .gl_type = GL_UNSIGNED_SHORT_4_4_4_4, - .has_alpha = false, - }, - { - .drm_format = DRM_FORMAT_RGBA4444, - .gl_format = GL_RGBA, - .gl_type = GL_UNSIGNED_SHORT_4_4_4_4, - .has_alpha = true, - }, - { - .drm_format = DRM_FORMAT_RGBX5551, - .gl_format = GL_RGBA, - .gl_type = GL_UNSIGNED_SHORT_5_5_5_1, - .has_alpha = false, - }, - { - .drm_format = DRM_FORMAT_RGBA5551, - .gl_format = GL_RGBA, - .gl_type = GL_UNSIGNED_SHORT_5_5_5_1, - .has_alpha = true, - }, - { - .drm_format = DRM_FORMAT_RGB565, - .gl_format = GL_RGB, - .gl_type = GL_UNSIGNED_SHORT_5_6_5, - .has_alpha = false, - }, - { - .drm_format = DRM_FORMAT_XBGR2101010, - .gl_format = GL_RGBA, - .gl_type = GL_UNSIGNED_INT_2_10_10_10_REV_EXT, - .has_alpha = false, - }, - { - .drm_format = DRM_FORMAT_ABGR2101010, - .gl_format = GL_RGBA, - .gl_type = GL_UNSIGNED_INT_2_10_10_10_REV_EXT, - .has_alpha = true, - }, - { - .drm_format = DRM_FORMAT_XBGR16161616F, - .gl_format = GL_RGBA, - .gl_type = GL_HALF_FLOAT_OES, - .has_alpha = false, - }, - { - .drm_format = DRM_FORMAT_ABGR16161616F, - .gl_format = GL_RGBA, - .gl_type = GL_HALF_FLOAT_OES, - .has_alpha = true, - }, - { - .drm_format = DRM_FORMAT_XBGR16161616, - .gl_internalformat = GL_RGBA16_EXT, - .gl_format = GL_RGBA, - .gl_type = GL_UNSIGNED_SHORT, - .has_alpha = false, - }, - { - .drm_format = DRM_FORMAT_ABGR16161616, - .gl_internalformat = GL_RGBA16_EXT, - .gl_format = GL_RGBA, - .gl_type = GL_UNSIGNED_SHORT, - .has_alpha = true, - }, -#endif -}; - -inline const struct wlr_gles2_pixel_format* gles2FromDRM(uint32_t fmt) { - for (size_t i = 0; i < sizeof(formats) / sizeof(*formats); ++i) { - if (formats[i].drm_format == fmt) { - return &formats[i]; - } +static int32_t div_round_up(int32_t dividend, int32_t divisor) { + int32_t quotient = dividend / divisor; + if (dividend % divisor != 0) { + quotient++; } - return NULL; + return quotient; +} + +static int32_t pixel_format_info_min_stride(const wlr_pixel_format_info* fmt, int32_t width) { + int32_t pixels_per_block = (int32_t)pixel_format_info_pixels_per_block(fmt); + int32_t bytes_per_block = (int32_t)fmt->bytes_per_block; + if (width > INT32_MAX / bytes_per_block) { + wlr_log(WLR_DEBUG, "Invalid width %d (overflow)", width); + return 0; + } + return div_round_up(width * bytes_per_block, pixels_per_block); } #endif \ No newline at end of file diff --git a/src/protocols/XDGOutput.cpp b/src/protocols/XDGOutput.cpp index 94839e20..49845b94 100644 --- a/src/protocols/XDGOutput.cpp +++ b/src/protocols/XDGOutput.cpp @@ -58,9 +58,9 @@ void CXDGOutputProtocol::bindManager(wl_client* client, void* data, uint32_t ver } CXDGOutputProtocol::CXDGOutputProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { - g_pHookSystem->hookDynamic("monitorLayoutChanged", [this](void* self, std::any param) { this->updateAllOutputs(); }); - g_pHookSystem->hookDynamic("configReloaded", [this](void* self, std::any param) { this->updateAllOutputs(); }); - g_pHookSystem->hookDynamic("monitorRemoved", [this](void* self, std::any param) { + g_pHookSystem->hookDynamic("monitorLayoutChanged", [this](void* self, SCallbackInfo& info, std::any param) { this->updateAllOutputs(); }); + g_pHookSystem->hookDynamic("configReloaded", [this](void* self, SCallbackInfo& info, std::any param) { this->updateAllOutputs(); }); + g_pHookSystem->hookDynamic("monitorRemoved", [this](void* self, SCallbackInfo& info, std::any param) { const auto PMONITOR = std::any_cast(param); std::erase_if(m_vXDGOutputs, [&](const auto& other) { const auto REMOVE = other->monitor == PMONITOR; @@ -72,15 +72,9 @@ CXDGOutputProtocol::CXDGOutputProtocol(const wl_interface* iface, const int& ver } void CXDGOutputProtocol::onManagerGetXDGOutput(wl_client* client, wl_resource* resource, uint32_t id, wl_resource* outputResource) { - const auto OUTPUT = wlr_output_from_resource(outputResource); + const auto OUTPUT = wlr_output_from_resource(outputResource); - if (!OUTPUT) - return; - - const auto PMONITOR = g_pCompositor->getMonitorFromOutput(OUTPUT); - - if (!PMONITOR) - return; + const auto PMONITOR = g_pCompositor->getMonitorFromOutput(OUTPUT); SXDGOutput* pXDGOutput = m_vXDGOutputs.emplace_back(std::make_unique(PMONITOR)).get(); #ifndef NO_XWAYLAND @@ -99,6 +93,10 @@ void CXDGOutputProtocol::onManagerGetXDGOutput(wl_client* client, wl_resource* r pXDGOutput->resource->setImplementation(&OUTPUT_IMPL, nullptr); pXDGOutput->resource->setData(this); + + if (!PMONITOR) + return; + const auto XDGVER = pXDGOutput->resource->version(); if (XDGVER >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) @@ -116,7 +114,7 @@ void CXDGOutputProtocol::onManagerGetXDGOutput(wl_client* client, wl_resource* r void CXDGOutputProtocol::updateOutputDetails(SXDGOutput* pOutput) { static auto* const PXWLFORCESCALEZERO = &g_pConfigManager->getConfigValuePtr("xwayland:force_zero_scaling")->intValue; - if (!pOutput->resource->good()) + if (!pOutput->resource->good() || !pOutput->monitor) return; const auto POS = pOutput->isXWayland ? pOutput->monitor->vecXWaylandPosition : pOutput->monitor->vecPosition; @@ -133,6 +131,10 @@ void CXDGOutputProtocol::updateOutputDetails(SXDGOutput* pOutput) { void CXDGOutputProtocol::updateAllOutputs() { for (auto& o : m_vXDGOutputs) { + + if (!o->monitor) + continue; + updateOutputDetails(o.get()); wlr_output_schedule_done(o->monitor->output); diff --git a/src/render/Framebuffer.cpp b/src/render/Framebuffer.cpp index a62ea18a..d35e5119 100644 --- a/src/render/Framebuffer.cpp +++ b/src/render/Framebuffer.cpp @@ -1,10 +1,13 @@ #include "Framebuffer.hpp" #include "OpenGL.hpp" -bool CFramebuffer::alloc(int w, int h) { +bool CFramebuffer::alloc(int w, int h, uint32_t drmFormat) { bool firstAlloc = false; RASSERT((w > 1 && h > 1), "cannot alloc a FB with negative / zero size! (attempted {}x{})", w, h); + uint32_t glFormat = drmFormatToGL(drmFormat); + uint32_t glType = glFormatToType(glFormat); + if (m_iFb == (uint32_t)-1) { firstAlloc = true; glGenFramebuffers(1, &m_iFb); @@ -22,7 +25,7 @@ bool CFramebuffer::alloc(int w, int h) { if (firstAlloc || m_vSize != Vector2D(w, h)) { glBindTexture(GL_TEXTURE_2D, m_cTex.m_iTexID); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glTexImage2D(GL_TEXTURE_2D, 0, glFormat, w, h, 0, GL_RGBA, glType, 0); glBindFramebuffer(GL_FRAMEBUFFER, m_iFb); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_cTex.m_iTexID, 0); @@ -53,6 +56,24 @@ bool CFramebuffer::alloc(int w, int h) { return true; } +void CFramebuffer::addStencil() { + // TODO: Allow this with gles2 +#ifndef GLES2 + glBindTexture(GL_TEXTURE_2D, m_pStencilTex->m_iTexID); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, m_vSize.x, m_vSize.y, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, 0); + + glBindFramebuffer(GL_FRAMEBUFFER, m_iFb); + + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_pStencilTex->m_iTexID, 0); + + auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + RASSERT((status == GL_FRAMEBUFFER_COMPLETE), "Failed adding a stencil to fbo!", status); + + glBindTexture(GL_TEXTURE_2D, 0); + glBindFramebuffer(GL_FRAMEBUFFER, g_pHyprOpenGL->m_iCurrentOutputFb); +#endif +} + void CFramebuffer::bind() { #ifndef GLES2 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_iFb); diff --git a/src/render/Framebuffer.hpp b/src/render/Framebuffer.hpp index 9ba3ac05..22809158 100644 --- a/src/render/Framebuffer.hpp +++ b/src/render/Framebuffer.hpp @@ -7,7 +7,8 @@ class CFramebuffer { public: ~CFramebuffer(); - bool alloc(int w, int h); + bool alloc(int w, int h, uint32_t format = GL_RGBA); + void addStencil(); void bind(); void release(); void reset(); diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp index f5a39ca7..55a48a46 100644 --- a/src/render/OpenGL.cpp +++ b/src/render/OpenGL.cpp @@ -5,6 +5,15 @@ #include "Shaders.hpp" #include +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; +} + CHyprOpenGLImpl::CHyprOpenGLImpl() { RASSERT(eglMakeCurrent(wlr_egl_get_display(g_pCompositor->m_sWLREGL), EGL_NO_SURFACE, EGL_NO_SURFACE, wlr_egl_get_context(g_pCompositor->m_sWLREGL)), "Couldn't unset current EGL!"); @@ -23,6 +32,11 @@ CHyprOpenGLImpl::CHyprOpenGLImpl() { Debug::log(LOG, "Renderer: {}", (char*)glGetString(GL_RENDERER)); Debug::log(LOG, "Supported extensions size: {}", std::count(m_szExtensions.begin(), m_szExtensions.end(), ' ')); + loadGLProc(&m_sProc.glEGLImageTargetRenderbufferStorageOES, "glEGLImageTargetRenderbufferStorageOES"); + loadGLProc(&m_sProc.eglDestroyImageKHR, "eglDestroyImageKHR"); + + m_sExts.EXT_read_format_bgra = m_szExtensions.contains("GL_EXT_read_format_bgra"); + #ifdef USE_TRACY_GPU loadGLProc(&glQueryCounter, "glQueryCounterEXT"); @@ -37,7 +51,7 @@ CHyprOpenGLImpl::CHyprOpenGLImpl() { Debug::log(WARN, "!RENDERER: Using the legacy GLES2 renderer!"); #endif - g_pHookSystem->hookDynamic("preRender", [&](void* self, std::any data) { preRender(std::any_cast(data)); }); + g_pHookSystem->hookDynamic("preRender", [&](void* self, SCallbackInfo& info, std::any data) { preRender(std::any_cast(data)); }); RASSERT(eglMakeCurrent(wlr_egl_get_display(g_pCompositor->m_sWLREGL), EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT), "Couldn't unset current EGL!"); @@ -103,35 +117,99 @@ GLuint CHyprOpenGLImpl::compileShader(const GLuint& type, std::string src, bool return shader; } -void CHyprOpenGLImpl::begin(CMonitor* pMonitor, CRegion* pDamage, bool fake) { +bool CHyprOpenGLImpl::passRequiresIntrospection(CMonitor* pMonitor) { + // passes requiring introspection are the ones that need to render blur. + + static auto* const PBLUR = &g_pConfigManager->getConfigValuePtr("decoration:blur:enabled")->intValue; + static auto* const PXRAY = &g_pConfigManager->getConfigValuePtr("decoration:blur:xray")->intValue; + static auto* const POPTIM = &g_pConfigManager->getConfigValuePtr("decoration:blur:new_optimizations")->intValue; + static auto* const PBLURSPECIAL = &g_pConfigManager->getConfigValuePtr("decoration:blur:special")->intValue; + + if (m_RenderData.mouseZoomFactor != 1.0) + return true; + + if (!pMonitor->mirrors.empty()) + return true; + + if (*PBLUR == 0) + return false; + + if (m_RenderData.pCurrentMonData->blurFBShouldRender) + return true; + + if (pMonitor->solitaryClient) + return false; + + for (auto& ls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]) { + const auto XRAYMODE = ls->xray == -1 ? *PXRAY : ls->xray; + if (ls->forceBlur && !XRAYMODE) + return true; + } + + for (auto& ls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) { + const auto XRAYMODE = ls->xray == -1 ? *PXRAY : ls->xray; + if (ls->forceBlur && !XRAYMODE) + return true; + } + + if (*PBLURSPECIAL) { + for (auto& ws : g_pCompositor->m_vWorkspaces) { + if (!ws->m_bIsSpecialWorkspace || ws->m_iMonitorID != pMonitor->ID) + continue; + + if (ws->m_fAlpha.fl() == 0) + continue; + + return true; + } + } + + if (*PXRAY) + return false; + + for (auto& w : g_pCompositor->m_vWindows) { + if (!w->m_bIsMapped || w->isHidden() || (!w->m_bIsFloating && *POPTIM && !g_pCompositor->isWorkspaceSpecial(w->m_iWorkspaceID))) + continue; + + if (!g_pHyprRenderer->shouldRenderWindow(w.get())) + continue; + + if (w->m_sAdditionalConfigData.forceNoBlur.toUnderlying() == true || w->m_sAdditionalConfigData.xray.toUnderlying() == true) + continue; + + if (w->opaque()) + continue; + + return true; + } + + return false; +} + +void CHyprOpenGLImpl::begin(CMonitor* pMonitor, CRegion* pDamage, CFramebuffer* fb) { m_RenderData.pMonitor = pMonitor; TRACY_GPU_ZONE("RenderBegin"); - if (eglGetCurrentContext() != wlr_egl_get_context(g_pCompositor->m_sWLREGL)) { - eglMakeCurrent(wlr_egl_get_display(g_pCompositor->m_sWLREGL), EGL_NO_SURFACE, EGL_NO_SURFACE, wlr_egl_get_context(g_pCompositor->m_sWLREGL)); - } - glViewport(0, 0, pMonitor->vecPixelSize.x, pMonitor->vecPixelSize.y); matrixProjection(m_RenderData.projection, pMonitor->vecPixelSize.x, pMonitor->vecPixelSize.y, WL_OUTPUT_TRANSFORM_NORMAL); m_RenderData.pCurrentMonData = &m_mMonitorRenderResources[pMonitor]; - glGetIntegerv(GL_FRAMEBUFFER_BINDING, &m_iCurrentOutputFb); - m_iWLROutputFb = m_iCurrentOutputFb; - // ensure a framebuffer for the monitor exists - if (m_mMonitorRenderResources.find(pMonitor) == m_mMonitorRenderResources.end() || m_RenderData.pCurrentMonData->primaryFB.m_vSize != pMonitor->vecPixelSize) { + if (!m_mMonitorRenderResources.contains(pMonitor) || m_RenderData.pCurrentMonData->offloadFB.m_vSize != pMonitor->vecPixelSize) { m_RenderData.pCurrentMonData->stencilTex.allocate(); - m_RenderData.pCurrentMonData->primaryFB.m_pStencilTex = &m_RenderData.pCurrentMonData->stencilTex; + m_RenderData.pCurrentMonData->offloadFB.m_pStencilTex = &m_RenderData.pCurrentMonData->stencilTex; m_RenderData.pCurrentMonData->mirrorFB.m_pStencilTex = &m_RenderData.pCurrentMonData->stencilTex; m_RenderData.pCurrentMonData->mirrorSwapFB.m_pStencilTex = &m_RenderData.pCurrentMonData->stencilTex; + m_RenderData.pCurrentMonData->offMainFB.m_pStencilTex = &m_RenderData.pCurrentMonData->stencilTex; - m_RenderData.pCurrentMonData->primaryFB.alloc(pMonitor->vecPixelSize.x, pMonitor->vecPixelSize.y); - m_RenderData.pCurrentMonData->mirrorFB.alloc(pMonitor->vecPixelSize.x, pMonitor->vecPixelSize.y); - m_RenderData.pCurrentMonData->mirrorSwapFB.alloc(pMonitor->vecPixelSize.x, pMonitor->vecPixelSize.y); + m_RenderData.pCurrentMonData->offloadFB.alloc(pMonitor->vecPixelSize.x, pMonitor->vecPixelSize.y, pMonitor->drmFormat); + m_RenderData.pCurrentMonData->mirrorFB.alloc(pMonitor->vecPixelSize.x, pMonitor->vecPixelSize.y, pMonitor->drmFormat); + m_RenderData.pCurrentMonData->mirrorSwapFB.alloc(pMonitor->vecPixelSize.x, pMonitor->vecPixelSize.y, pMonitor->drmFormat); + m_RenderData.pCurrentMonData->offMainFB.alloc(pMonitor->vecPixelSize.x, pMonitor->vecPixelSize.y, pMonitor->drmFormat); createBGTextureForMonitor(pMonitor); } @@ -142,17 +220,39 @@ void CHyprOpenGLImpl::begin(CMonitor* pMonitor, CRegion* pDamage, bool fake) { if (!m_RenderData.pCurrentMonData->m_bShadersInitialized) initShaders(); - // bind the primary Hypr Framebuffer - m_RenderData.pCurrentMonData->primaryFB.bind(); - m_RenderData.damage.set(*pDamage); - m_bFakeFrame = fake; + m_bFakeFrame = fb; if (m_bReloadScreenShader) { m_bReloadScreenShader = false; applyScreenShader(g_pConfigManager->getString("decoration:screen_shader")); } + + const auto PRBO = g_pHyprRenderer->getCurrentRBO(); + const bool FBPROPERSIZE = fb && fb->m_vSize == pMonitor->vecPixelSize; + + if (m_RenderData.forceIntrospection || !FBPROPERSIZE || m_sFinalScreenShader.program > 0 || (PRBO && pMonitor->vecPixelSize != PRBO->getFB()->m_vSize) || + passRequiresIntrospection(pMonitor)) { + // we have to offload + // bind the offload Hypr Framebuffer + m_RenderData.pCurrentMonData->offloadFB.bind(); + m_RenderData.currentFB = &m_RenderData.pCurrentMonData->offloadFB; + m_bOffloadedFramebuffer = true; + } else { + // we can render to the rbo / fbo (fake) directly + const auto PFBO = fb ? fb : PRBO->getFB(); + m_RenderData.currentFB = PFBO; + if (PFBO->m_pStencilTex != &m_RenderData.pCurrentMonData->stencilTex) { + PFBO->m_pStencilTex = &m_RenderData.pCurrentMonData->stencilTex; + PFBO->addStencil(); + } + PFBO->bind(); + m_bOffloadedFramebuffer = false; + } + + m_RenderData.mainFB = m_RenderData.currentFB; + m_RenderData.outFB = fb ? fb : PRBO->getFB(); } void CHyprOpenGLImpl::end() { @@ -160,25 +260,23 @@ void CHyprOpenGLImpl::end() { TRACY_GPU_ZONE("RenderEnd"); + if (!m_RenderData.pMonitor->mirrors.empty() && !m_bFakeFrame) + saveBufferForMirror(); // save with original damage region + // end the render, copy the data to the WLR framebuffer - if (!m_bFakeFrame) { + if (m_bOffloadedFramebuffer) { m_RenderData.damage = m_RenderData.pMonitor->lastFrameDamage; - if (!m_RenderData.pMonitor->mirrors.empty()) - saveBufferForMirror(); // save with original damage region + m_RenderData.outFB->bind(); - glBindFramebuffer(GL_FRAMEBUFFER, m_iWLROutputFb); - wlr_box monbox = {0, 0, m_RenderData.pMonitor->vecTransformedSize.x, m_RenderData.pMonitor->vecTransformedSize.y}; + CBox monbox = {0, 0, m_RenderData.pMonitor->vecTransformedSize.x, m_RenderData.pMonitor->vecTransformedSize.y}; if (m_RenderData.mouseZoomFactor != 1.f) { const auto ZOOMCENTER = m_RenderData.mouseZoomUseMouse ? (g_pInputManager->getMouseCoordsInternal() - m_RenderData.pMonitor->vecPosition) * m_RenderData.pMonitor->scale : m_RenderData.pMonitor->vecTransformedSize / 2.f; - monbox.x -= ZOOMCENTER.x; - monbox.y -= ZOOMCENTER.y; - scaleBox(&monbox, m_RenderData.mouseZoomFactor); - monbox.x += *PZOOMRIGID ? m_RenderData.pMonitor->vecTransformedSize.x / 2 : ZOOMCENTER.x; - monbox.y += *PZOOMRIGID ? m_RenderData.pMonitor->vecTransformedSize.y / 2 : ZOOMCENTER.y; + + monbox.translate(-ZOOMCENTER).scale(m_RenderData.mouseZoomFactor).translate(*PZOOMRIGID ? m_RenderData.pMonitor->vecTransformedSize / 2.0 : ZOOMCENTER); if (monbox.x > 0) monbox.x = 0; @@ -198,9 +296,9 @@ void CHyprOpenGLImpl::end() { blend(false); if (m_sFinalScreenShader.program < 1) - renderTexturePrimitive(m_RenderData.pCurrentMonData->primaryFB.m_cTex, &monbox); + renderTexturePrimitive(m_RenderData.pCurrentMonData->offloadFB.m_cTex, &monbox); else - renderTexture(m_RenderData.pCurrentMonData->primaryFB.m_cTex, &monbox, 1.f); + renderTexture(m_RenderData.pCurrentMonData->offloadFB.m_cTex, &monbox, 1.f); blend(true); @@ -210,43 +308,40 @@ void CHyprOpenGLImpl::end() { } // reset our data - m_RenderData.pMonitor = nullptr; - m_iWLROutputFb = 0; - m_RenderData.mouseZoomFactor = 1.f; - m_RenderData.mouseZoomUseMouse = true; -} - -void CHyprOpenGLImpl::bindWlrOutputFb() { - glBindFramebuffer(GL_FRAMEBUFFER, m_iWLROutputFb); + m_RenderData.pMonitor = nullptr; + m_RenderData.mouseZoomFactor = 1.f; + m_RenderData.mouseZoomUseMouse = true; + m_RenderData.forceIntrospection = false; } void CHyprOpenGLImpl::initShaders() { - GLuint prog = createProgram(QUADVERTSRC, QUADFRAGSRC); - m_RenderData.pCurrentMonData->m_shQUAD.program = prog; - m_RenderData.pCurrentMonData->m_shQUAD.proj = glGetUniformLocation(prog, "proj"); - m_RenderData.pCurrentMonData->m_shQUAD.color = glGetUniformLocation(prog, "color"); - m_RenderData.pCurrentMonData->m_shQUAD.posAttrib = glGetAttribLocation(prog, "pos"); - m_RenderData.pCurrentMonData->m_shQUAD.topLeft = glGetUniformLocation(prog, "topLeft"); - m_RenderData.pCurrentMonData->m_shQUAD.fullSize = glGetUniformLocation(prog, "fullSize"); - m_RenderData.pCurrentMonData->m_shQUAD.radius = glGetUniformLocation(prog, "radius"); - m_RenderData.pCurrentMonData->m_shQUAD.primitiveMultisample = glGetUniformLocation(prog, "primitiveMultisample"); + GLuint prog = createProgram(QUADVERTSRC, QUADFRAGSRC); + m_RenderData.pCurrentMonData->m_shQUAD.program = prog; + m_RenderData.pCurrentMonData->m_shQUAD.proj = glGetUniformLocation(prog, "proj"); + m_RenderData.pCurrentMonData->m_shQUAD.color = glGetUniformLocation(prog, "color"); + m_RenderData.pCurrentMonData->m_shQUAD.posAttrib = glGetAttribLocation(prog, "pos"); + m_RenderData.pCurrentMonData->m_shQUAD.topLeft = glGetUniformLocation(prog, "topLeft"); + m_RenderData.pCurrentMonData->m_shQUAD.fullSize = glGetUniformLocation(prog, "fullSize"); + m_RenderData.pCurrentMonData->m_shQUAD.radius = glGetUniformLocation(prog, "radius"); - prog = createProgram(TEXVERTSRC, TEXFRAGSRCRGBA); - m_RenderData.pCurrentMonData->m_shRGBA.program = prog; - m_RenderData.pCurrentMonData->m_shRGBA.proj = glGetUniformLocation(prog, "proj"); - m_RenderData.pCurrentMonData->m_shRGBA.tex = glGetUniformLocation(prog, "tex"); - m_RenderData.pCurrentMonData->m_shRGBA.alpha = glGetUniformLocation(prog, "alpha"); - m_RenderData.pCurrentMonData->m_shRGBA.texAttrib = glGetAttribLocation(prog, "texcoord"); - m_RenderData.pCurrentMonData->m_shRGBA.posAttrib = glGetAttribLocation(prog, "pos"); - m_RenderData.pCurrentMonData->m_shRGBA.discardOpaque = glGetUniformLocation(prog, "discardOpaque"); - m_RenderData.pCurrentMonData->m_shRGBA.discardAlpha = glGetUniformLocation(prog, "discardAlpha"); - m_RenderData.pCurrentMonData->m_shRGBA.discardAlphaValue = glGetUniformLocation(prog, "discardAlphaValue"); - m_RenderData.pCurrentMonData->m_shRGBA.topLeft = glGetUniformLocation(prog, "topLeft"); - m_RenderData.pCurrentMonData->m_shRGBA.fullSize = glGetUniformLocation(prog, "fullSize"); - m_RenderData.pCurrentMonData->m_shRGBA.radius = glGetUniformLocation(prog, "radius"); - m_RenderData.pCurrentMonData->m_shRGBA.primitiveMultisample = glGetUniformLocation(prog, "primitiveMultisample"); - m_RenderData.pCurrentMonData->m_shRGBA.applyTint = glGetUniformLocation(prog, "applyTint"); - m_RenderData.pCurrentMonData->m_shRGBA.tint = glGetUniformLocation(prog, "tint"); + prog = createProgram(TEXVERTSRC, TEXFRAGSRCRGBA); + m_RenderData.pCurrentMonData->m_shRGBA.program = prog; + m_RenderData.pCurrentMonData->m_shRGBA.proj = glGetUniformLocation(prog, "proj"); + m_RenderData.pCurrentMonData->m_shRGBA.tex = glGetUniformLocation(prog, "tex"); + m_RenderData.pCurrentMonData->m_shRGBA.alphaMatte = glGetUniformLocation(prog, "texMatte"); + m_RenderData.pCurrentMonData->m_shRGBA.alpha = glGetUniformLocation(prog, "alpha"); + m_RenderData.pCurrentMonData->m_shRGBA.texAttrib = glGetAttribLocation(prog, "texcoord"); + m_RenderData.pCurrentMonData->m_shRGBA.matteTexAttrib = glGetAttribLocation(prog, "texcoordMatte"); + m_RenderData.pCurrentMonData->m_shRGBA.posAttrib = glGetAttribLocation(prog, "pos"); + m_RenderData.pCurrentMonData->m_shRGBA.discardOpaque = glGetUniformLocation(prog, "discardOpaque"); + m_RenderData.pCurrentMonData->m_shRGBA.discardAlpha = glGetUniformLocation(prog, "discardAlpha"); + m_RenderData.pCurrentMonData->m_shRGBA.discardAlphaValue = glGetUniformLocation(prog, "discardAlphaValue"); + m_RenderData.pCurrentMonData->m_shRGBA.topLeft = glGetUniformLocation(prog, "topLeft"); + m_RenderData.pCurrentMonData->m_shRGBA.fullSize = glGetUniformLocation(prog, "fullSize"); + m_RenderData.pCurrentMonData->m_shRGBA.radius = glGetUniformLocation(prog, "radius"); + m_RenderData.pCurrentMonData->m_shRGBA.applyTint = glGetUniformLocation(prog, "applyTint"); + m_RenderData.pCurrentMonData->m_shRGBA.tint = glGetUniformLocation(prog, "tint"); + m_RenderData.pCurrentMonData->m_shRGBA.useAlphaMatte = glGetUniformLocation(prog, "useAlphaMatte"); prog = createProgram(TEXVERTSRC, TEXFRAGSRCRGBAPASSTHRU); m_RenderData.pCurrentMonData->m_shPASSTHRURGBA.program = prog; @@ -255,6 +350,14 @@ void CHyprOpenGLImpl::initShaders() { m_RenderData.pCurrentMonData->m_shPASSTHRURGBA.texAttrib = glGetAttribLocation(prog, "texcoord"); m_RenderData.pCurrentMonData->m_shPASSTHRURGBA.posAttrib = glGetAttribLocation(prog, "pos"); + prog = createProgram(TEXVERTSRC, TEXFRAGSRCRGBAMATTE); + m_RenderData.pCurrentMonData->m_shMATTE.program = prog; + m_RenderData.pCurrentMonData->m_shMATTE.proj = glGetUniformLocation(prog, "proj"); + m_RenderData.pCurrentMonData->m_shMATTE.tex = glGetUniformLocation(prog, "tex"); + m_RenderData.pCurrentMonData->m_shMATTE.alphaMatte = glGetUniformLocation(prog, "texMatte"); + m_RenderData.pCurrentMonData->m_shMATTE.texAttrib = glGetAttribLocation(prog, "texcoord"); + m_RenderData.pCurrentMonData->m_shMATTE.posAttrib = glGetAttribLocation(prog, "pos"); + prog = createProgram(TEXVERTSRC, FRAGGLITCH); m_RenderData.pCurrentMonData->m_shGLITCH.program = prog; m_RenderData.pCurrentMonData->m_shGLITCH.proj = glGetUniformLocation(prog, "proj"); @@ -265,49 +368,50 @@ void CHyprOpenGLImpl::initShaders() { m_RenderData.pCurrentMonData->m_shGLITCH.time = glGetUniformLocation(prog, "time"); m_RenderData.pCurrentMonData->m_shGLITCH.fullSize = glGetUniformLocation(prog, "screenSize"); - prog = createProgram(TEXVERTSRC, TEXFRAGSRCRGBX); - m_RenderData.pCurrentMonData->m_shRGBX.program = prog; - m_RenderData.pCurrentMonData->m_shRGBX.tex = glGetUniformLocation(prog, "tex"); - m_RenderData.pCurrentMonData->m_shRGBX.proj = glGetUniformLocation(prog, "proj"); - m_RenderData.pCurrentMonData->m_shRGBX.alpha = glGetUniformLocation(prog, "alpha"); - m_RenderData.pCurrentMonData->m_shRGBX.texAttrib = glGetAttribLocation(prog, "texcoord"); - m_RenderData.pCurrentMonData->m_shRGBX.posAttrib = glGetAttribLocation(prog, "pos"); - m_RenderData.pCurrentMonData->m_shRGBX.discardOpaque = glGetUniformLocation(prog, "discardOpaque"); - m_RenderData.pCurrentMonData->m_shRGBX.discardAlpha = glGetUniformLocation(prog, "discardAlpha"); - m_RenderData.pCurrentMonData->m_shRGBX.discardAlphaValue = glGetUniformLocation(prog, "discardAlphaValue"); - m_RenderData.pCurrentMonData->m_shRGBX.topLeft = glGetUniformLocation(prog, "topLeft"); - m_RenderData.pCurrentMonData->m_shRGBX.fullSize = glGetUniformLocation(prog, "fullSize"); - m_RenderData.pCurrentMonData->m_shRGBX.radius = glGetUniformLocation(prog, "radius"); - m_RenderData.pCurrentMonData->m_shRGBX.primitiveMultisample = glGetUniformLocation(prog, "primitiveMultisample"); - m_RenderData.pCurrentMonData->m_shRGBX.applyTint = glGetUniformLocation(prog, "applyTint"); - m_RenderData.pCurrentMonData->m_shRGBX.tint = glGetUniformLocation(prog, "tint"); + prog = createProgram(TEXVERTSRC, TEXFRAGSRCRGBX); + m_RenderData.pCurrentMonData->m_shRGBX.program = prog; + m_RenderData.pCurrentMonData->m_shRGBX.tex = glGetUniformLocation(prog, "tex"); + m_RenderData.pCurrentMonData->m_shRGBX.proj = glGetUniformLocation(prog, "proj"); + m_RenderData.pCurrentMonData->m_shRGBX.alpha = glGetUniformLocation(prog, "alpha"); + m_RenderData.pCurrentMonData->m_shRGBX.texAttrib = glGetAttribLocation(prog, "texcoord"); + m_RenderData.pCurrentMonData->m_shRGBX.posAttrib = glGetAttribLocation(prog, "pos"); + m_RenderData.pCurrentMonData->m_shRGBX.discardOpaque = glGetUniformLocation(prog, "discardOpaque"); + m_RenderData.pCurrentMonData->m_shRGBX.discardAlpha = glGetUniformLocation(prog, "discardAlpha"); + m_RenderData.pCurrentMonData->m_shRGBX.discardAlphaValue = glGetUniformLocation(prog, "discardAlphaValue"); + m_RenderData.pCurrentMonData->m_shRGBX.topLeft = glGetUniformLocation(prog, "topLeft"); + m_RenderData.pCurrentMonData->m_shRGBX.fullSize = glGetUniformLocation(prog, "fullSize"); + m_RenderData.pCurrentMonData->m_shRGBX.radius = glGetUniformLocation(prog, "radius"); + m_RenderData.pCurrentMonData->m_shRGBX.applyTint = glGetUniformLocation(prog, "applyTint"); + m_RenderData.pCurrentMonData->m_shRGBX.tint = glGetUniformLocation(prog, "tint"); - prog = createProgram(TEXVERTSRC, TEXFRAGSRCEXT); - m_RenderData.pCurrentMonData->m_shEXT.program = prog; - m_RenderData.pCurrentMonData->m_shEXT.tex = glGetUniformLocation(prog, "tex"); - m_RenderData.pCurrentMonData->m_shEXT.proj = glGetUniformLocation(prog, "proj"); - m_RenderData.pCurrentMonData->m_shEXT.alpha = glGetUniformLocation(prog, "alpha"); - m_RenderData.pCurrentMonData->m_shEXT.posAttrib = glGetAttribLocation(prog, "pos"); - m_RenderData.pCurrentMonData->m_shEXT.texAttrib = glGetAttribLocation(prog, "texcoord"); - m_RenderData.pCurrentMonData->m_shEXT.discardOpaque = glGetUniformLocation(prog, "discardOpaque"); - m_RenderData.pCurrentMonData->m_shEXT.discardAlpha = glGetUniformLocation(prog, "discardAlpha"); - m_RenderData.pCurrentMonData->m_shEXT.discardAlphaValue = glGetUniformLocation(prog, "discardAlphaValue"); - m_RenderData.pCurrentMonData->m_shEXT.topLeft = glGetUniformLocation(prog, "topLeft"); - m_RenderData.pCurrentMonData->m_shEXT.fullSize = glGetUniformLocation(prog, "fullSize"); - m_RenderData.pCurrentMonData->m_shEXT.radius = glGetUniformLocation(prog, "radius"); - m_RenderData.pCurrentMonData->m_shEXT.primitiveMultisample = glGetUniformLocation(prog, "primitiveMultisample"); - m_RenderData.pCurrentMonData->m_shEXT.applyTint = glGetUniformLocation(prog, "applyTint"); - m_RenderData.pCurrentMonData->m_shEXT.tint = glGetUniformLocation(prog, "tint"); + prog = createProgram(TEXVERTSRC, TEXFRAGSRCEXT); + m_RenderData.pCurrentMonData->m_shEXT.program = prog; + m_RenderData.pCurrentMonData->m_shEXT.tex = glGetUniformLocation(prog, "tex"); + m_RenderData.pCurrentMonData->m_shEXT.proj = glGetUniformLocation(prog, "proj"); + m_RenderData.pCurrentMonData->m_shEXT.alpha = glGetUniformLocation(prog, "alpha"); + m_RenderData.pCurrentMonData->m_shEXT.posAttrib = glGetAttribLocation(prog, "pos"); + m_RenderData.pCurrentMonData->m_shEXT.texAttrib = glGetAttribLocation(prog, "texcoord"); + m_RenderData.pCurrentMonData->m_shEXT.discardOpaque = glGetUniformLocation(prog, "discardOpaque"); + m_RenderData.pCurrentMonData->m_shEXT.discardAlpha = glGetUniformLocation(prog, "discardAlpha"); + m_RenderData.pCurrentMonData->m_shEXT.discardAlphaValue = glGetUniformLocation(prog, "discardAlphaValue"); + m_RenderData.pCurrentMonData->m_shEXT.topLeft = glGetUniformLocation(prog, "topLeft"); + m_RenderData.pCurrentMonData->m_shEXT.fullSize = glGetUniformLocation(prog, "fullSize"); + m_RenderData.pCurrentMonData->m_shEXT.radius = glGetUniformLocation(prog, "radius"); + m_RenderData.pCurrentMonData->m_shEXT.applyTint = glGetUniformLocation(prog, "applyTint"); + m_RenderData.pCurrentMonData->m_shEXT.tint = glGetUniformLocation(prog, "tint"); - prog = createProgram(TEXVERTSRC, FRAGBLUR1); - m_RenderData.pCurrentMonData->m_shBLUR1.program = prog; - m_RenderData.pCurrentMonData->m_shBLUR1.tex = glGetUniformLocation(prog, "tex"); - m_RenderData.pCurrentMonData->m_shBLUR1.alpha = glGetUniformLocation(prog, "alpha"); - m_RenderData.pCurrentMonData->m_shBLUR1.proj = glGetUniformLocation(prog, "proj"); - m_RenderData.pCurrentMonData->m_shBLUR1.posAttrib = glGetAttribLocation(prog, "pos"); - m_RenderData.pCurrentMonData->m_shBLUR1.texAttrib = glGetAttribLocation(prog, "texcoord"); - m_RenderData.pCurrentMonData->m_shBLUR1.radius = glGetUniformLocation(prog, "radius"); - m_RenderData.pCurrentMonData->m_shBLUR1.halfpixel = glGetUniformLocation(prog, "halfpixel"); + prog = createProgram(TEXVERTSRC, FRAGBLUR1); + m_RenderData.pCurrentMonData->m_shBLUR1.program = prog; + m_RenderData.pCurrentMonData->m_shBLUR1.tex = glGetUniformLocation(prog, "tex"); + m_RenderData.pCurrentMonData->m_shBLUR1.alpha = glGetUniformLocation(prog, "alpha"); + m_RenderData.pCurrentMonData->m_shBLUR1.proj = glGetUniformLocation(prog, "proj"); + m_RenderData.pCurrentMonData->m_shBLUR1.posAttrib = glGetAttribLocation(prog, "pos"); + m_RenderData.pCurrentMonData->m_shBLUR1.texAttrib = glGetAttribLocation(prog, "texcoord"); + m_RenderData.pCurrentMonData->m_shBLUR1.radius = glGetUniformLocation(prog, "radius"); + m_RenderData.pCurrentMonData->m_shBLUR1.halfpixel = glGetUniformLocation(prog, "halfpixel"); + m_RenderData.pCurrentMonData->m_shBLUR1.passes = glGetUniformLocation(prog, "passes"); + m_RenderData.pCurrentMonData->m_shBLUR1.vibrancy = glGetUniformLocation(prog, "vibrancy"); + m_RenderData.pCurrentMonData->m_shBLUR1.vibrancy_darkness = glGetUniformLocation(prog, "vibrancy_darkness"); prog = createProgram(TEXVERTSRC, FRAGBLUR2); m_RenderData.pCurrentMonData->m_shBLUR2.program = prog; @@ -319,15 +423,23 @@ void CHyprOpenGLImpl::initShaders() { m_RenderData.pCurrentMonData->m_shBLUR2.radius = glGetUniformLocation(prog, "radius"); m_RenderData.pCurrentMonData->m_shBLUR2.halfpixel = glGetUniformLocation(prog, "halfpixel"); + prog = createProgram(TEXVERTSRC, FRAGBLURPREPARE); + m_RenderData.pCurrentMonData->m_shBLURPREPARE.program = prog; + m_RenderData.pCurrentMonData->m_shBLURPREPARE.tex = glGetUniformLocation(prog, "tex"); + m_RenderData.pCurrentMonData->m_shBLURPREPARE.proj = glGetUniformLocation(prog, "proj"); + m_RenderData.pCurrentMonData->m_shBLURPREPARE.posAttrib = glGetAttribLocation(prog, "pos"); + m_RenderData.pCurrentMonData->m_shBLURPREPARE.texAttrib = glGetAttribLocation(prog, "texcoord"); + m_RenderData.pCurrentMonData->m_shBLURPREPARE.contrast = glGetUniformLocation(prog, "contrast"); + m_RenderData.pCurrentMonData->m_shBLURPREPARE.brightness = glGetUniformLocation(prog, "brightness"); + prog = createProgram(TEXVERTSRC, FRAGBLURFINISH); m_RenderData.pCurrentMonData->m_shBLURFINISH.program = prog; m_RenderData.pCurrentMonData->m_shBLURFINISH.tex = glGetUniformLocation(prog, "tex"); m_RenderData.pCurrentMonData->m_shBLURFINISH.proj = glGetUniformLocation(prog, "proj"); m_RenderData.pCurrentMonData->m_shBLURFINISH.posAttrib = glGetAttribLocation(prog, "pos"); m_RenderData.pCurrentMonData->m_shBLURFINISH.texAttrib = glGetAttribLocation(prog, "texcoord"); - m_RenderData.pCurrentMonData->m_shBLURFINISH.noise = glGetUniformLocation(prog, "noise"); - m_RenderData.pCurrentMonData->m_shBLURFINISH.contrast = glGetUniformLocation(prog, "contrast"); m_RenderData.pCurrentMonData->m_shBLURFINISH.brightness = glGetUniformLocation(prog, "brightness"); + m_RenderData.pCurrentMonData->m_shBLURFINISH.noise = glGetUniformLocation(prog, "noise"); prog = createProgram(QUADVERTSRC, FRAGSHADOW); m_RenderData.pCurrentMonData->m_shSHADOW.program = prog; @@ -353,7 +465,7 @@ void CHyprOpenGLImpl::initShaders() { m_RenderData.pCurrentMonData->m_shBORDER1.fullSize = glGetUniformLocation(prog, "fullSize"); m_RenderData.pCurrentMonData->m_shBORDER1.fullSizeUntransformed = glGetUniformLocation(prog, "fullSizeUntransformed"); m_RenderData.pCurrentMonData->m_shBORDER1.radius = glGetUniformLocation(prog, "radius"); - m_RenderData.pCurrentMonData->m_shBORDER1.primitiveMultisample = glGetUniformLocation(prog, "primitiveMultisample"); + m_RenderData.pCurrentMonData->m_shBORDER1.radiusOuter = glGetUniformLocation(prog, "radiusOuter"); m_RenderData.pCurrentMonData->m_shBORDER1.gradient = glGetUniformLocation(prog, "gradient"); m_RenderData.pCurrentMonData->m_shBORDER1.gradientLength = glGetUniformLocation(prog, "gradientLength"); m_RenderData.pCurrentMonData->m_shBORDER1.angle = glGetUniformLocation(prog, "angle"); @@ -415,7 +527,7 @@ void CHyprOpenGLImpl::clear(const CColor& color) { } } - scissor((wlr_box*)nullptr); + scissor((CBox*)nullptr); } void CHyprOpenGLImpl::blend(bool enabled) { @@ -428,7 +540,7 @@ void CHyprOpenGLImpl::blend(bool enabled) { m_bBlend = enabled; } -void CHyprOpenGLImpl::scissor(const wlr_box* pBox, bool transform) { +void CHyprOpenGLImpl::scissor(const CBox* pBox, bool transform) { RASSERT(m_RenderData.pMonitor, "Tried to scissor without begin()!"); if (!pBox) { @@ -436,14 +548,14 @@ void CHyprOpenGLImpl::scissor(const wlr_box* pBox, bool transform) { return; } - wlr_box newBox = *pBox; + CBox newBox = *pBox; if (transform) { int w, h; wlr_output_transformed_resolution(m_RenderData.pMonitor->output, &w, &h); const auto TR = wlr_output_transform_invert(m_RenderData.pMonitor->transform); - wlr_box_transform(&newBox, &newBox, TR, w, h); + newBox.transform(TR, w, h); } glScissor(newBox.x, newBox.y, newBox.width, newBox.height); @@ -458,35 +570,34 @@ void CHyprOpenGLImpl::scissor(const pixman_box32* pBox, bool transform) { return; } - wlr_box newBox = {pBox->x1, pBox->y1, pBox->x2 - pBox->x1, pBox->y2 - pBox->y1}; + CBox newBox = {pBox->x1, pBox->y1, pBox->x2 - pBox->x1, pBox->y2 - pBox->y1}; scissor(&newBox, transform); } void CHyprOpenGLImpl::scissor(const int x, const int y, const int w, const int h, bool transform) { - wlr_box box = {x, y, w, h}; + CBox box = {x, y, w, h}; scissor(&box, transform); } -void CHyprOpenGLImpl::renderRect(wlr_box* box, const CColor& col, int round) { +void CHyprOpenGLImpl::renderRect(CBox* box, const CColor& col, int round) { if (!m_RenderData.damage.empty()) renderRectWithDamage(box, col, &m_RenderData.damage, round); } -void CHyprOpenGLImpl::renderRectWithBlur(wlr_box* box, const CColor& col, int round, float blurA) { +void CHyprOpenGLImpl::renderRectWithBlur(CBox* box, const CColor& col, int round, float blurA, bool xray) { if (m_RenderData.damage.empty()) return; CRegion damage{m_RenderData.damage}; - damage.intersect(box); + damage.intersect(*box); - CFramebuffer* POUTFB = blurMainFramebufferWithDamage(blurA, &damage); + CFramebuffer* POUTFB = xray ? &m_RenderData.pCurrentMonData->blurFB : blurMainFramebufferWithDamage(blurA, &damage); - // bind primary - m_RenderData.pCurrentMonData->primaryFB.bind(); + m_RenderData.currentFB->bind(); // make a stencil for rounded corners to work with blur - scissor((wlr_box*)nullptr); // allow the entire window and stencil to render + scissor((CBox*)nullptr); // allow the entire window and stencil to render glClearStencil(0); glClear(GL_STENCIL_BUFFER_BIT); @@ -503,7 +614,7 @@ void CHyprOpenGLImpl::renderRectWithBlur(wlr_box* box, const CColor& col, int ro glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); scissor(box); - wlr_box MONITORBOX = {0, 0, m_RenderData.pMonitor->vecTransformedSize.x, m_RenderData.pMonitor->vecTransformedSize.y}; + CBox MONITORBOX = {0, 0, m_RenderData.pMonitor->vecTransformedSize.x, m_RenderData.pMonitor->vecTransformedSize.y}; m_bEndFrame = true; // fix transformed const auto SAVEDRENDERMODIF = m_RenderData.renderModif; m_RenderData.renderModif = {}; // fix shit @@ -516,27 +627,25 @@ void CHyprOpenGLImpl::renderRectWithBlur(wlr_box* box, const CColor& col, int ro glDisable(GL_STENCIL_TEST); glStencilMask(-1); glStencilFunc(GL_ALWAYS, 1, 0xFF); - scissor((wlr_box*)nullptr); + scissor((CBox*)nullptr); renderRectWithDamage(box, col, &m_RenderData.damage, round); } -void CHyprOpenGLImpl::renderRectWithDamage(wlr_box* box, const CColor& col, CRegion* damage, int round) { +void CHyprOpenGLImpl::renderRectWithDamage(CBox* box, const CColor& col, CRegion* damage, int round) { RASSERT((box->width > 0 && box->height > 0), "Tried to render rect with width/height < 0!"); RASSERT(m_RenderData.pMonitor, "Tried to render rect without begin()!"); TRACY_GPU_ZONE("RenderRectWithDamage"); - wlr_box newBox = *box; - scaleBox(&newBox, m_RenderData.renderModif.scale); - newBox.x += m_RenderData.renderModif.translate.x; - newBox.y += m_RenderData.renderModif.translate.y; + CBox newBox = *box; + m_RenderData.renderModif.applyToBox(newBox); box = &newBox; float matrix[9]; - wlr_matrix_project_box(matrix, box, wlr_output_transform_invert(!m_bEndFrame ? WL_OUTPUT_TRANSFORM_NORMAL : m_RenderData.pMonitor->transform), 0, - m_RenderData.pMonitor->output->transform_matrix); // TODO: write own, don't use WLR here + wlr_matrix_project_box(matrix, box->pWlr(), wlr_output_transform_invert(!m_bEndFrame ? WL_OUTPUT_TRANSFORM_NORMAL : m_RenderData.pMonitor->transform), newBox.rot, + m_RenderData.pMonitor->projMatrix.data()); // TODO: write own, don't use WLR here float glMatrix[9]; wlr_matrix_multiply(glMatrix, m_RenderData.projection, matrix); @@ -553,20 +662,17 @@ void CHyprOpenGLImpl::renderRectWithDamage(wlr_box* box, const CColor& col, CReg // premultiply the color as well as we don't work with straight alpha glUniform4f(m_RenderData.pCurrentMonData->m_shQUAD.color, col.r * col.a, col.g * col.a, col.b * col.a, col.a); - wlr_box transformedBox; - wlr_box_transform(&transformedBox, box, wlr_output_transform_invert(m_RenderData.pMonitor->transform), m_RenderData.pMonitor->vecTransformedSize.x, - m_RenderData.pMonitor->vecTransformedSize.y); + CBox transformedBox = *box; + transformedBox.transform(wlr_output_transform_invert(m_RenderData.pMonitor->transform), m_RenderData.pMonitor->vecTransformedSize.x, + m_RenderData.pMonitor->vecTransformedSize.y); - const auto TOPLEFT = Vector2D(transformedBox.x, transformedBox.y); - const auto FULLSIZE = Vector2D(transformedBox.width, transformedBox.height); - - static auto* const PMULTISAMPLEEDGES = &g_pConfigManager->getConfigValuePtr("decoration:multisample_edges")->intValue; + const auto TOPLEFT = Vector2D(transformedBox.x, transformedBox.y); + const auto FULLSIZE = Vector2D(transformedBox.width, transformedBox.height); // Rounded corners glUniform2f(m_RenderData.pCurrentMonData->m_shQUAD.topLeft, (float)TOPLEFT.x, (float)TOPLEFT.y); glUniform2f(m_RenderData.pCurrentMonData->m_shQUAD.fullSize, (float)FULLSIZE.x, (float)FULLSIZE.y); glUniform1f(m_RenderData.pCurrentMonData->m_shQUAD.radius, round); - glUniform1i(m_RenderData.pCurrentMonData->m_shQUAD.primitiveMultisample, (int)(*PMULTISAMPLEEDGES == 1 && round != 0)); glVertexAttribPointer(m_RenderData.pCurrentMonData->m_shQUAD.posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); @@ -592,22 +698,22 @@ void CHyprOpenGLImpl::renderRectWithDamage(wlr_box* box, const CColor& col, CReg glDisableVertexAttribArray(m_RenderData.pCurrentMonData->m_shQUAD.posAttrib); } -void CHyprOpenGLImpl::renderTexture(wlr_texture* tex, wlr_box* pBox, float alpha, int round, bool allowCustomUV) { +void CHyprOpenGLImpl::renderTexture(wlr_texture* tex, CBox* pBox, float alpha, int round, bool allowCustomUV) { RASSERT(m_RenderData.pMonitor, "Tried to render texture without begin()!"); renderTexture(CTexture(tex), pBox, alpha, round, false, allowCustomUV); } -void CHyprOpenGLImpl::renderTexture(const CTexture& tex, wlr_box* pBox, float alpha, int round, bool discardActive, bool allowCustomUV) { +void CHyprOpenGLImpl::renderTexture(const CTexture& tex, CBox* pBox, float alpha, int round, bool discardActive, bool allowCustomUV) { RASSERT(m_RenderData.pMonitor, "Tried to render texture without begin()!"); renderTextureInternalWithDamage(tex, pBox, alpha, &m_RenderData.damage, round, discardActive, false, allowCustomUV, true); - scissor((wlr_box*)nullptr); + scissor((CBox*)nullptr); } -void CHyprOpenGLImpl::renderTextureInternalWithDamage(const CTexture& tex, wlr_box* pBox, float alpha, CRegion* damage, int round, bool discardActive, bool noAA, - bool allowCustomUV, bool allowDim) { +void CHyprOpenGLImpl::renderTextureInternalWithDamage(const CTexture& tex, CBox* pBox, float alpha, CRegion* damage, int round, bool discardActive, bool noAA, bool allowCustomUV, + bool allowDim) { RASSERT(m_RenderData.pMonitor, "Tried to render texture without begin()!"); RASSERT((tex.m_iTexID > 0), "Attempted to draw NULL texture!"); @@ -618,17 +724,15 @@ void CHyprOpenGLImpl::renderTextureInternalWithDamage(const CTexture& tex, wlr_b if (m_RenderData.damage.empty()) return; - wlr_box newBox = *pBox; - scaleBox(&newBox, m_RenderData.renderModif.scale); - newBox.x += m_RenderData.renderModif.translate.x; - newBox.y += m_RenderData.renderModif.translate.y; + CBox newBox = *pBox; + m_RenderData.renderModif.applyToBox(newBox); static auto* const PDIMINACTIVE = &g_pConfigManager->getConfigValuePtr("decoration:dim_inactive")->intValue; // get transform const auto TRANSFORM = wlr_output_transform_invert(!m_bEndFrame ? WL_OUTPUT_TRANSFORM_NORMAL : m_RenderData.pMonitor->transform); float matrix[9]; - wlr_matrix_project_box(matrix, &newBox, TRANSFORM, 0, m_RenderData.pMonitor->output->transform_matrix); + wlr_matrix_project_box(matrix, newBox.pWlr(), TRANSFORM, newBox.rot, m_RenderData.pMonitor->projMatrix.data()); float glMatrix[9]; wlr_matrix_multiply(glMatrix, m_RenderData.projection, matrix); @@ -711,20 +815,18 @@ void CHyprOpenGLImpl::renderTextureInternalWithDamage(const CTexture& tex, wlr_b } } - wlr_box transformedBox; - wlr_box_transform(&transformedBox, &newBox, wlr_output_transform_invert(m_RenderData.pMonitor->transform), m_RenderData.pMonitor->vecTransformedSize.x, - m_RenderData.pMonitor->vecTransformedSize.y); + CBox transformedBox = newBox; + transformedBox.transform(wlr_output_transform_invert(m_RenderData.pMonitor->transform), m_RenderData.pMonitor->vecTransformedSize.x, + m_RenderData.pMonitor->vecTransformedSize.y); - const auto TOPLEFT = Vector2D(transformedBox.x, transformedBox.y); - const auto FULLSIZE = Vector2D(transformedBox.width, transformedBox.height); - static auto* const PMULTISAMPLEEDGES = &g_pConfigManager->getConfigValuePtr("decoration:multisample_edges")->intValue; + const auto TOPLEFT = Vector2D(transformedBox.x, transformedBox.y); + const auto FULLSIZE = Vector2D(transformedBox.width, transformedBox.height); if (!usingFinalShader) { // Rounded corners glUniform2f(shader->topLeft, TOPLEFT.x, TOPLEFT.y); glUniform2f(shader->fullSize, FULLSIZE.x, FULLSIZE.y); glUniform1f(shader->radius, round); - glUniform1i(shader->primitiveMultisample, (int)(*PMULTISAMPLEEDGES == 1 && round != 0 && !noAA)); if (allowDim && m_pCurrentWindow && *PDIMINACTIVE) { glUniform1i(shader->applyTint, 1); @@ -776,7 +878,7 @@ void CHyprOpenGLImpl::renderTextureInternalWithDamage(const CTexture& tex, wlr_b glBindTexture(tex.m_iTarget, 0); } -void CHyprOpenGLImpl::renderTexturePrimitive(const CTexture& tex, wlr_box* pBox) { +void CHyprOpenGLImpl::renderTexturePrimitive(const CTexture& tex, CBox* pBox) { RASSERT(m_RenderData.pMonitor, "Tried to render texture without begin()!"); RASSERT((tex.m_iTexID > 0), "Attempted to draw NULL texture!"); @@ -785,15 +887,13 @@ void CHyprOpenGLImpl::renderTexturePrimitive(const CTexture& tex, wlr_box* pBox) if (m_RenderData.damage.empty()) return; - wlr_box newBox = *pBox; - scaleBox(&newBox, m_RenderData.renderModif.scale); - newBox.x += m_RenderData.renderModif.translate.x; - newBox.y += m_RenderData.renderModif.translate.y; + CBox newBox = *pBox; + m_RenderData.renderModif.applyToBox(newBox); // get transform const auto TRANSFORM = wlr_output_transform_invert(!m_bEndFrame ? WL_OUTPUT_TRANSFORM_NORMAL : m_RenderData.pMonitor->transform); float matrix[9]; - wlr_matrix_project_box(matrix, &newBox, TRANSFORM, 0, m_RenderData.pMonitor->output->transform_matrix); + wlr_matrix_project_box(matrix, newBox.pWlr(), TRANSFORM, newBox.rot, m_RenderData.pMonitor->projMatrix.data()); float glMatrix[9]; wlr_matrix_multiply(glMatrix, m_RenderData.projection, matrix); @@ -824,7 +924,65 @@ void CHyprOpenGLImpl::renderTexturePrimitive(const CTexture& tex, wlr_box* pBox) glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } - scissor((wlr_box*)nullptr); + scissor((CBox*)nullptr); + + glDisableVertexAttribArray(shader->posAttrib); + glDisableVertexAttribArray(shader->texAttrib); + + glBindTexture(tex.m_iTarget, 0); +} + +void CHyprOpenGLImpl::renderTextureMatte(const CTexture& tex, CBox* pBox, CFramebuffer& matte) { + RASSERT(m_RenderData.pMonitor, "Tried to render texture without begin()!"); + RASSERT((tex.m_iTexID > 0), "Attempted to draw NULL texture!"); + + TRACY_GPU_ZONE("RenderTextureMatte"); + + if (m_RenderData.damage.empty()) + return; + + CBox newBox = *pBox; + m_RenderData.renderModif.applyToBox(newBox); + + // get transform + const auto TRANSFORM = wlr_output_transform_invert(!m_bEndFrame ? WL_OUTPUT_TRANSFORM_NORMAL : m_RenderData.pMonitor->transform); + float matrix[9]; + wlr_matrix_project_box(matrix, newBox.pWlr(), TRANSFORM, newBox.rot, m_RenderData.pMonitor->projMatrix.data()); + + float glMatrix[9]; + wlr_matrix_multiply(glMatrix, m_RenderData.projection, matrix); + + CShader* shader = &m_RenderData.pCurrentMonData->m_shMATTE; + + glUseProgram(shader->program); + +#ifndef GLES2 + glUniformMatrix3fv(shader->proj, 1, GL_TRUE, glMatrix); +#else + wlr_matrix_transpose(glMatrix, glMatrix); + glUniformMatrix3fv(shader->proj, 1, GL_FALSE, glMatrix); +#endif + glUniform1i(shader->tex, 0); + glUniform1i(shader->alphaMatte, 1); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(tex.m_iTarget, tex.m_iTexID); + + glActiveTexture(GL_TEXTURE0 + 1); + glBindTexture(matte.m_cTex.m_iTarget, matte.m_cTex.m_iTexID); + + glVertexAttribPointer(shader->posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); + glVertexAttribPointer(shader->texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); + + glEnableVertexAttribArray(shader->posAttrib); + glEnableVertexAttribArray(shader->texAttrib); + + for (auto& RECT : m_RenderData.damage.getRects()) { + scissor(&RECT); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + } + + scissor((CBox*)nullptr); glDisableVertexAttribArray(shader->posAttrib); glDisableVertexAttribArray(shader->texAttrib); @@ -847,15 +1005,17 @@ CFramebuffer* CHyprOpenGLImpl::blurMainFramebufferWithDamage(float a, CRegion* o // get transforms for the full monitor const auto TRANSFORM = wlr_output_transform_invert(m_RenderData.pMonitor->transform); float matrix[9]; - wlr_box MONITORBOX = {0, 0, m_RenderData.pMonitor->vecTransformedSize.x, m_RenderData.pMonitor->vecTransformedSize.y}; - wlr_matrix_project_box(matrix, &MONITORBOX, TRANSFORM, 0, m_RenderData.pMonitor->output->transform_matrix); + CBox MONITORBOX = {0, 0, m_RenderData.pMonitor->vecTransformedSize.x, m_RenderData.pMonitor->vecTransformedSize.y}; + wlr_matrix_project_box(matrix, MONITORBOX.pWlr(), TRANSFORM, 0, m_RenderData.pMonitor->projMatrix.data()); float glMatrix[9]; wlr_matrix_multiply(glMatrix, m_RenderData.projection, matrix); // get the config settings - static auto* const PBLURSIZE = &g_pConfigManager->getConfigValuePtr("decoration:blur:size")->intValue; - static auto* const PBLURPASSES = &g_pConfigManager->getConfigValuePtr("decoration:blur:passes")->intValue; + static auto* const PBLURSIZE = &g_pConfigManager->getConfigValuePtr("decoration:blur:size")->intValue; + static auto* const PBLURPASSES = &g_pConfigManager->getConfigValuePtr("decoration:blur:passes")->intValue; + static auto* const PBLURVIBRANCY = &g_pConfigManager->getConfigValuePtr("decoration:blur:vibrancy")->floatValue; + static auto* const PBLURVIBRANCYDARKNESS = &g_pConfigManager->getConfigValuePtr("decoration:blur:vibrancy_darkness")->floatValue; // prep damage CRegion damage{*originalDamage}; @@ -869,7 +1029,7 @@ CFramebuffer* CHyprOpenGLImpl::blurMainFramebufferWithDamage(float a, CRegion* o CFramebuffer* currentRenderToFB = PMIRRORFB; - // begin with color adjustments + // Begin with base color adjustments - global brightness and contrast // TODO: make this a part of the first pass maybe to save on a drawcall? { static auto* const PBLURCONTRAST = &g_pConfigManager->getConfigValuePtr("decoration:blur:contrast")->floatValue; @@ -879,28 +1039,27 @@ CFramebuffer* CHyprOpenGLImpl::blurMainFramebufferWithDamage(float a, CRegion* o glActiveTexture(GL_TEXTURE0); - glBindTexture(m_RenderData.pCurrentMonData->primaryFB.m_cTex.m_iTarget, m_RenderData.pCurrentMonData->primaryFB.m_cTex.m_iTexID); + glBindTexture(m_RenderData.currentFB->m_cTex.m_iTarget, m_RenderData.currentFB->m_cTex.m_iTexID); - glTexParameteri(m_RenderData.pCurrentMonData->primaryFB.m_cTex.m_iTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(m_RenderData.currentFB->m_cTex.m_iTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glUseProgram(m_RenderData.pCurrentMonData->m_shBLURFINISH.program); + glUseProgram(m_RenderData.pCurrentMonData->m_shBLURPREPARE.program); #ifndef GLES2 - glUniformMatrix3fv(m_RenderData.pCurrentMonData->m_shBLURFINISH.proj, 1, GL_TRUE, glMatrix); + glUniformMatrix3fv(m_RenderData.pCurrentMonData->m_shBLURPREPARE.proj, 1, GL_TRUE, glMatrix); #else wlr_matrix_transpose(glMatrix, glMatrix); - glUniformMatrix3fv(m_RenderData.pCurrentMonData->m_shBLURFINISH.proj, 1, GL_FALSE, glMatrix); + glUniformMatrix3fv(m_RenderData.pCurrentMonData->m_shBLURPREPARE.proj, 1, GL_FALSE, glMatrix); #endif - glUniform1f(m_RenderData.pCurrentMonData->m_shBLURFINISH.contrast, *PBLURCONTRAST); - glUniform1f(m_RenderData.pCurrentMonData->m_shBLURFINISH.brightness, *PBLURBRIGHTNESS); + glUniform1f(m_RenderData.pCurrentMonData->m_shBLURPREPARE.contrast, *PBLURCONTRAST); + glUniform1f(m_RenderData.pCurrentMonData->m_shBLURPREPARE.brightness, *PBLURBRIGHTNESS); + glUniform1i(m_RenderData.pCurrentMonData->m_shBLURPREPARE.tex, 0); - glUniform1i(m_RenderData.pCurrentMonData->m_shBLURFINISH.tex, 0); + glVertexAttribPointer(m_RenderData.pCurrentMonData->m_shBLURPREPARE.posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); + glVertexAttribPointer(m_RenderData.pCurrentMonData->m_shBLURPREPARE.texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); - glVertexAttribPointer(m_RenderData.pCurrentMonData->m_shBLURFINISH.posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); - glVertexAttribPointer(m_RenderData.pCurrentMonData->m_shBLURFINISH.texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); - - glEnableVertexAttribArray(m_RenderData.pCurrentMonData->m_shBLURFINISH.posAttrib); - glEnableVertexAttribArray(m_RenderData.pCurrentMonData->m_shBLURFINISH.texAttrib); + glEnableVertexAttribArray(m_RenderData.pCurrentMonData->m_shBLURPREPARE.posAttrib); + glEnableVertexAttribArray(m_RenderData.pCurrentMonData->m_shBLURPREPARE.texAttrib); if (!damage.empty()) { for (auto& RECT : damage.getRects()) { @@ -909,8 +1068,8 @@ CFramebuffer* CHyprOpenGLImpl::blurMainFramebufferWithDamage(float a, CRegion* o } } - glDisableVertexAttribArray(m_RenderData.pCurrentMonData->m_shBLURFINISH.posAttrib); - glDisableVertexAttribArray(m_RenderData.pCurrentMonData->m_shBLURFINISH.texAttrib); + glDisableVertexAttribArray(m_RenderData.pCurrentMonData->m_shBLURPREPARE.posAttrib); + glDisableVertexAttribArray(m_RenderData.pCurrentMonData->m_shBLURPREPARE.texAttrib); currentRenderToFB = PMIRRORSWAPFB; } @@ -938,10 +1097,13 @@ CFramebuffer* CHyprOpenGLImpl::blurMainFramebufferWithDamage(float a, CRegion* o glUniformMatrix3fv(pShader->proj, 1, GL_FALSE, glMatrix); #endif glUniform1f(pShader->radius, *PBLURSIZE * a); // this makes the blursize change with a - if (pShader == &m_RenderData.pCurrentMonData->m_shBLUR1) + if (pShader == &m_RenderData.pCurrentMonData->m_shBLUR1) { glUniform2f(m_RenderData.pCurrentMonData->m_shBLUR1.halfpixel, 0.5f / (m_RenderData.pMonitor->vecPixelSize.x / 2.f), 0.5f / (m_RenderData.pMonitor->vecPixelSize.y / 2.f)); - else + glUniform1i(m_RenderData.pCurrentMonData->m_shBLUR1.passes, *PBLURPASSES); + glUniform1f(m_RenderData.pCurrentMonData->m_shBLUR1.vibrancy, *PBLURVIBRANCY); + glUniform1f(m_RenderData.pCurrentMonData->m_shBLUR1.vibrancy_darkness, *PBLURVIBRANCYDARKNESS); + } else glUniform2f(m_RenderData.pCurrentMonData->m_shBLUR2.halfpixel, 0.5f / (m_RenderData.pMonitor->vecPixelSize.x * 2.f), 0.5f / (m_RenderData.pMonitor->vecPixelSize.y * 2.f)); glUniform1i(pShader->tex, 0); @@ -987,9 +1149,10 @@ CFramebuffer* CHyprOpenGLImpl::blurMainFramebufferWithDamage(float a, CRegion* o drawPass(&m_RenderData.pCurrentMonData->m_shBLUR2, &tempDamage); // up } - // finalize with noise + // finalize the image { - static auto* const PBLURNOISE = &g_pConfigManager->getConfigValuePtr("decoration:blur:noise")->floatValue; + static auto* const PBLURNOISE = &g_pConfigManager->getConfigValuePtr("decoration:blur:noise")->floatValue; + static auto* const PBLURBRIGHTNESS = &g_pConfigManager->getConfigValuePtr("decoration:blur:brightness")->floatValue; if (currentRenderToFB == PMIRRORFB) PMIRRORSWAPFB->bind(); @@ -1011,6 +1174,7 @@ CFramebuffer* CHyprOpenGLImpl::blurMainFramebufferWithDamage(float a, CRegion* o glUniformMatrix3fv(m_RenderData.pCurrentMonData->m_shBLURFINISH.proj, 1, GL_FALSE, glMatrix); #endif glUniform1f(m_RenderData.pCurrentMonData->m_shBLURFINISH.noise, *PBLURNOISE); + glUniform1f(m_RenderData.pCurrentMonData->m_shBLURFINISH.brightness, *PBLURBRIGHTNESS); glUniform1i(m_RenderData.pCurrentMonData->m_shBLURFINISH.tex, 0); @@ -1045,13 +1209,6 @@ CFramebuffer* CHyprOpenGLImpl::blurMainFramebufferWithDamage(float a, CRegion* o } void CHyprOpenGLImpl::markBlurDirtyForMonitor(CMonitor* pMonitor) { - const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(pMonitor->activeWorkspace); - const auto PFULLWINDOW = g_pCompositor->getFullscreenWindowOnWorkspace(pMonitor->activeWorkspace); - - if (PWORKSPACE->m_bHasFullscreenWindow && PWORKSPACE->m_efFullscreenMode == FULLSCREEN_FULL && PFULLWINDOW && !PFULLWINDOW->m_vRealSize.isBeingAnimated() && - PFULLWINDOW->opaque()) - return; - m_mMonitorRenderResources[pMonitor].blurFBDirty = true; } @@ -1063,6 +1220,10 @@ void CHyprOpenGLImpl::preRender(CMonitor* pMonitor) { if (!*PBLURNEWOPTIMIZE || !m_mMonitorRenderResources[pMonitor].blurFBDirty || !*PBLUR) return; + // ignore if solitary present, nothing to blur + if (pMonitor->solitaryClient) + return; + // check if we need to update the blur fb // if there are no windows that would benefit from it, // we will ignore that the blur FB is dirty. @@ -1074,6 +1235,9 @@ void CHyprOpenGLImpl::preRender(CMonitor* pMonitor) { if (pWindow->m_sAdditionalConfigData.forceNoBlur) return false; + if (pWindow->m_pWLSurface.small() && !pWindow->m_pWLSurface.m_bFillIgnoreSmall) + return true; + const auto PSURFACE = pWindow->m_pWLSurface.wlr(); const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(pWindow->m_iWorkspaceID); @@ -1140,11 +1304,11 @@ void CHyprOpenGLImpl::preBlurForCurrentMonitor() { // make the fake dmg CRegion fakeDamage{0, 0, m_RenderData.pMonitor->vecTransformedSize.x, m_RenderData.pMonitor->vecTransformedSize.y}; - wlr_box wholeMonitor = {0, 0, m_RenderData.pMonitor->vecTransformedSize.x, m_RenderData.pMonitor->vecTransformedSize.y}; + CBox wholeMonitor = {0, 0, m_RenderData.pMonitor->vecTransformedSize.x, m_RenderData.pMonitor->vecTransformedSize.y}; const auto POUTFB = blurMainFramebufferWithDamage(1, &fakeDamage); // render onto blurFB - m_RenderData.pCurrentMonData->blurFB.alloc(m_RenderData.pMonitor->vecPixelSize.x, m_RenderData.pMonitor->vecPixelSize.y); + m_RenderData.pCurrentMonData->blurFB.alloc(m_RenderData.pMonitor->vecPixelSize.x, m_RenderData.pMonitor->vecPixelSize.y, m_RenderData.pMonitor->drmFormat); m_RenderData.pCurrentMonData->blurFB.bind(); clear(CColor(0, 0, 0, 0)); @@ -1153,7 +1317,7 @@ void CHyprOpenGLImpl::preBlurForCurrentMonitor() { renderTextureInternalWithDamage(POUTFB->m_cTex, &wholeMonitor, 1, &fakeDamage, 0, false, true, false); m_bEndFrame = false; - m_RenderData.pCurrentMonData->primaryFB.bind(); + m_RenderData.currentFB->bind(); m_RenderData.pCurrentMonData->blurFBDirty = false; @@ -1199,7 +1363,7 @@ bool CHyprOpenGLImpl::shouldUseNewBlurOptimizations(SLayerSurface* pLayer, CWind return false; } -void CHyprOpenGLImpl::renderTextureWithBlur(const CTexture& tex, wlr_box* pBox, float a, wlr_surface* pSurface, int round, bool blockBlurOptimization, float blurA) { +void CHyprOpenGLImpl::renderTextureWithBlur(const CTexture& tex, CBox* pBox, float a, wlr_surface* pSurface, int round, bool blockBlurOptimization, float blurA) { RASSERT(m_RenderData.pMonitor, "Tried to render texture with blur without begin()!"); static auto* const PBLURENABLED = &g_pConfigManager->getConfigValuePtr("decoration:blur:enabled")->intValue; @@ -1222,7 +1386,8 @@ void CHyprOpenGLImpl::renderTextureWithBlur(const CTexture& tex, wlr_box* pBox, // amazing hack: the surface has an opaque region! CRegion inverseOpaque; - if (a >= 1.f) { + if (a >= 1.f && std::round(pSurface->current.width * m_RenderData.pMonitor->scale) == pBox->w && + std::round(pSurface->current.height * m_RenderData.pMonitor->scale) == pBox->h) { pixman_box32_t surfbox = {0, 0, pSurface->current.width * pSurface->current.scale, pSurface->current.height * pSurface->current.scale}; inverseOpaque = &pSurface->current.opaque; inverseOpaque.invert(&surfbox).intersect(0, 0, pSurface->current.width * pSurface->current.scale, pSurface->current.height * pSurface->current.scale); @@ -1249,11 +1414,10 @@ void CHyprOpenGLImpl::renderTextureWithBlur(const CTexture& tex, wlr_box* pBox, POUTFB = &m_RenderData.pCurrentMonData->blurFB; } - // bind primary - m_RenderData.pCurrentMonData->primaryFB.bind(); + m_RenderData.currentFB->bind(); // make a stencil for rounded corners to work with blur - scissor((wlr_box*)nullptr); // allow the entire window and stencil to render + scissor((CBox*)nullptr); // allow the entire window and stencil to render glClearStencil(0); glClear(GL_STENCIL_BUFFER_BIT); @@ -1273,7 +1437,7 @@ void CHyprOpenGLImpl::renderTextureWithBlur(const CTexture& tex, wlr_box* pBox, glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); // stencil done. Render everything. - wlr_box MONITORBOX = {0, 0, m_RenderData.pMonitor->vecTransformedSize.x, m_RenderData.pMonitor->vecTransformedSize.y}; + CBox MONITORBOX = {0, 0, m_RenderData.pMonitor->vecTransformedSize.x, m_RenderData.pMonitor->vecTransformedSize.y}; // render our great blurred FB static auto* const PBLURIGNOREOPACITY = &g_pConfigManager->getConfigValuePtr("decoration:blur:ignore_opacity")->intValue; m_bEndFrame = true; // fix transformed @@ -1293,17 +1457,17 @@ void CHyprOpenGLImpl::renderTextureWithBlur(const CTexture& tex, wlr_box* pBox, glStencilMask(-1); glStencilFunc(GL_ALWAYS, 1, 0xFF); - scissor((wlr_box*)nullptr); + scissor((CBox*)nullptr); } -void pushVert2D(float x, float y, float* arr, int& counter, wlr_box* box) { +void pushVert2D(float x, float y, float* arr, int& counter, CBox* box) { // 0-1 space god damnit arr[counter * 2 + 0] = x / box->width; arr[counter * 2 + 1] = y / box->height; counter++; } -void CHyprOpenGLImpl::renderBorder(wlr_box* box, const CGradientValueData& grad, int round, int borderSize, float a) { +void CHyprOpenGLImpl::renderBorder(CBox* box, const CGradientValueData& grad, int round, int borderSize, float a, int outerRound) { RASSERT((box->width > 0 && box->height > 0), "Tried to render rect with width/height < 0!"); RASSERT(m_RenderData.pMonitor, "Tried to render rect without begin()!"); @@ -1312,19 +1476,15 @@ void CHyprOpenGLImpl::renderBorder(wlr_box* box, const CGradientValueData& grad, if (m_RenderData.damage.empty() || (m_pCurrentWindow && m_pCurrentWindow->m_sAdditionalConfigData.forceNoBorder)) return; - static auto* const PMULTISAMPLE = &g_pConfigManager->getConfigValuePtr("decoration:multisample_edges")->intValue; - - wlr_box newBox = *box; - scaleBox(&newBox, m_RenderData.renderModif.scale); - newBox.x += m_RenderData.renderModif.translate.x; - newBox.y += m_RenderData.renderModif.translate.y; + CBox newBox = *box; + m_RenderData.renderModif.applyToBox(newBox); box = &newBox; if (borderSize < 1) return; - int scaledBorderSize = borderSize * m_RenderData.pMonitor->scale * m_RenderData.renderModif.scale; + int scaledBorderSize = std::round(borderSize * m_RenderData.pMonitor->scale); // adjust box box->x -= scaledBorderSize; @@ -1335,8 +1495,8 @@ void CHyprOpenGLImpl::renderBorder(wlr_box* box, const CGradientValueData& grad, round += round == 0 ? 0 : scaledBorderSize; float matrix[9]; - wlr_matrix_project_box(matrix, box, wlr_output_transform_invert(!m_bEndFrame ? WL_OUTPUT_TRANSFORM_NORMAL : m_RenderData.pMonitor->transform), 0, - m_RenderData.pMonitor->output->transform_matrix); // TODO: write own, don't use WLR here + wlr_matrix_project_box(matrix, box->pWlr(), wlr_output_transform_invert(!m_bEndFrame ? WL_OUTPUT_TRANSFORM_NORMAL : m_RenderData.pMonitor->transform), newBox.rot, + m_RenderData.pMonitor->projMatrix.data()); // TODO: write own, don't use WLR here float glMatrix[9]; wlr_matrix_multiply(glMatrix, m_RenderData.projection, matrix); @@ -1360,9 +1520,9 @@ void CHyprOpenGLImpl::renderBorder(wlr_box* box, const CGradientValueData& grad, glUniform1f(m_RenderData.pCurrentMonData->m_shBORDER1.angle, (int)(grad.m_fAngle / (PI / 180.0)) % 360 * (PI / 180.0)); glUniform1f(m_RenderData.pCurrentMonData->m_shBORDER1.alpha, a); - wlr_box transformedBox; - wlr_box_transform(&transformedBox, box, wlr_output_transform_invert(m_RenderData.pMonitor->transform), m_RenderData.pMonitor->vecTransformedSize.x, - m_RenderData.pMonitor->vecTransformedSize.y); + CBox transformedBox = *box; + transformedBox.transform(wlr_output_transform_invert(m_RenderData.pMonitor->transform), m_RenderData.pMonitor->vecTransformedSize.x, + m_RenderData.pMonitor->vecTransformedSize.y); const auto TOPLEFT = Vector2D(transformedBox.x, transformedBox.y); const auto FULLSIZE = Vector2D(transformedBox.width, transformedBox.height); @@ -1371,8 +1531,8 @@ void CHyprOpenGLImpl::renderBorder(wlr_box* box, const CGradientValueData& grad, glUniform2f(m_RenderData.pCurrentMonData->m_shBORDER1.fullSize, (float)FULLSIZE.x, (float)FULLSIZE.y); glUniform2f(m_RenderData.pCurrentMonData->m_shBORDER1.fullSizeUntransformed, (float)box->width, (float)box->height); glUniform1f(m_RenderData.pCurrentMonData->m_shBORDER1.radius, round); + glUniform1f(m_RenderData.pCurrentMonData->m_shBORDER1.radiusOuter, outerRound == -1 ? round : outerRound); glUniform1f(m_RenderData.pCurrentMonData->m_shBORDER1.thick, scaledBorderSize); - glUniform1i(m_RenderData.pCurrentMonData->m_shBORDER1.primitiveMultisample, *PMULTISAMPLE); glVertexAttribPointer(m_RenderData.pCurrentMonData->m_shBORDER1.posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); glVertexAttribPointer(m_RenderData.pCurrentMonData->m_shBORDER1.texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); @@ -1410,14 +1570,18 @@ void CHyprOpenGLImpl::makeRawWindowSnapshot(CWindow* pWindow, CFramebuffer* pFra if (!PMONITOR || !PMONITOR->output || PMONITOR->vecPixelSize.x <= 0 || PMONITOR->vecPixelSize.y <= 0) return; - wlr_output_attach_render(PMONITOR->output, nullptr); - // we need to "damage" the entire monitor // so that we render the entire window // this is temporary, doesnt mess with the actual wlr damage CRegion fakeDamage{0, 0, (int)PMONITOR->vecTransformedSize.x, (int)PMONITOR->vecTransformedSize.y}; - begin(PMONITOR, &fakeDamage, true); + g_pHyprRenderer->makeEGLCurrent(); + + pFramebuffer->m_pStencilTex = &m_RenderData.pCurrentMonData->stencilTex; + + pFramebuffer->alloc(PMONITOR->vecPixelSize.x, PMONITOR->vecPixelSize.y, PMONITOR->drmFormat); + + g_pHyprRenderer->beginRender(PMONITOR, fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, pFramebuffer); clear(CColor(0, 0, 0, 0)); // JIC @@ -1435,11 +1599,7 @@ void CHyprOpenGLImpl::makeRawWindowSnapshot(CWindow* pWindow, CFramebuffer* pFra // TODO: how can we make this the size of the window? setting it to window's size makes the entire screen render with the wrong res forever more. odd. glViewport(0, 0, PMONITOR->vecPixelSize.x, PMONITOR->vecPixelSize.y); - pFramebuffer->m_pStencilTex = &m_RenderData.pCurrentMonData->stencilTex; - - pFramebuffer->alloc(PMONITOR->vecPixelSize.x, PMONITOR->vecPixelSize.y); - - pFramebuffer->bind(); + m_RenderData.currentFB = pFramebuffer; clear(CColor(0, 0, 0, 0)); // JIC @@ -1447,15 +1607,7 @@ void CHyprOpenGLImpl::makeRawWindowSnapshot(CWindow* pWindow, CFramebuffer* pFra g_pConfigManager->setInt("decoration:blur:enabled", BLURVAL); -// restore original fb -#ifndef GLES2 - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_iCurrentOutputFb); -#else - glBindFramebuffer(GL_FRAMEBUFFER, m_iCurrentOutputFb); -#endif - end(); - - wlr_output_rollback(PMONITOR->output); + g_pHyprRenderer->endRender(); } void CHyprOpenGLImpl::makeWindowSnapshot(CWindow* pWindow) { @@ -1465,14 +1617,21 @@ void CHyprOpenGLImpl::makeWindowSnapshot(CWindow* pWindow) { if (!PMONITOR || !PMONITOR->output || PMONITOR->vecPixelSize.x <= 0 || PMONITOR->vecPixelSize.y <= 0) return; - wlr_output_attach_render(PMONITOR->output, nullptr); + if (!g_pHyprRenderer->shouldRenderWindow(pWindow)) + return; // ignore, window is not being rendered // we need to "damage" the entire monitor // so that we render the entire window // this is temporary, doesnt mess with the actual wlr damage CRegion fakeDamage{0, 0, (int)PMONITOR->vecTransformedSize.x, (int)PMONITOR->vecTransformedSize.y}; - begin(PMONITOR, &fakeDamage, true); + g_pHyprRenderer->makeEGLCurrent(); + + const auto PFRAMEBUFFER = &m_mWindowFramebuffers[pWindow]; + + PFRAMEBUFFER->alloc(PMONITOR->vecPixelSize.x, PMONITOR->vecPixelSize.y, PMONITOR->drmFormat); + + g_pHyprRenderer->beginRender(PMONITOR, fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, PFRAMEBUFFER); g_pHyprRenderer->m_bRenderingSnapshot = true; @@ -1489,33 +1648,15 @@ void CHyprOpenGLImpl::makeWindowSnapshot(CWindow* pWindow) { const auto BLURVAL = g_pConfigManager->getInt("decoration:blur:enabled"); g_pConfigManager->setInt("decoration:blur:enabled", 0); - glViewport(0, 0, m_RenderData.pMonitor->vecPixelSize.x, m_RenderData.pMonitor->vecPixelSize.y); - - const auto PFRAMEBUFFER = &m_mWindowFramebuffers[pWindow]; - - PFRAMEBUFFER->m_pStencilTex = &m_RenderData.pCurrentMonData->stencilTex; - - PFRAMEBUFFER->alloc(PMONITOR->vecPixelSize.x, PMONITOR->vecPixelSize.y); - - PFRAMEBUFFER->bind(); - clear(CColor(0, 0, 0, 0)); // JIC g_pHyprRenderer->renderWindow(pWindow, PMONITOR, &now, !pWindow->m_bX11DoesntWantBorders, RENDER_PASS_ALL); g_pConfigManager->setInt("decoration:blur:enabled", BLURVAL); -// restore original fb -#ifndef GLES2 - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_iCurrentOutputFb); -#else - glBindFramebuffer(GL_FRAMEBUFFER, m_iCurrentOutputFb); -#endif - end(); + g_pHyprRenderer->endRender(); g_pHyprRenderer->m_bRenderingSnapshot = false; - - wlr_output_rollback(PMONITOR->output); } void CHyprOpenGLImpl::makeLayerSnapshot(SLayerSurface* pLayer) { @@ -1525,24 +1666,20 @@ void CHyprOpenGLImpl::makeLayerSnapshot(SLayerSurface* pLayer) { if (!PMONITOR || !PMONITOR->output || PMONITOR->vecPixelSize.x <= 0 || PMONITOR->vecPixelSize.y <= 0) return; - wlr_output_attach_render(PMONITOR->output, nullptr); - // we need to "damage" the entire monitor // so that we render the entire window // this is temporary, doesnt mess with the actual wlr damage CRegion fakeDamage{0, 0, (int)PMONITOR->vecTransformedSize.x, (int)PMONITOR->vecTransformedSize.y}; - begin(PMONITOR, &fakeDamage, true); - - g_pHyprRenderer->m_bRenderingSnapshot = true; + g_pHyprRenderer->makeEGLCurrent(); const auto PFRAMEBUFFER = &m_mLayerFramebuffers[pLayer]; - glViewport(0, 0, g_pHyprOpenGL->m_RenderData.pMonitor->vecPixelSize.x, g_pHyprOpenGL->m_RenderData.pMonitor->vecPixelSize.y); + PFRAMEBUFFER->alloc(PMONITOR->vecPixelSize.x, PMONITOR->vecPixelSize.y, PMONITOR->drmFormat); - PFRAMEBUFFER->alloc(PMONITOR->vecPixelSize.x, PMONITOR->vecPixelSize.y); + g_pHyprRenderer->beginRender(PMONITOR, fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, PFRAMEBUFFER); - PFRAMEBUFFER->bind(); + g_pHyprRenderer->m_bRenderingSnapshot = true; clear(CColor(0, 0, 0, 0)); // JIC @@ -1557,20 +1694,9 @@ void CHyprOpenGLImpl::makeLayerSnapshot(SLayerSurface* pLayer) { pLayer->forceBlur = BLURLSSTATUS; - // TODO: WARN: - // revise if any stencil-requiring rendering is done to the layers. - -// restore original fb -#ifndef GLES2 - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_iCurrentOutputFb); -#else - glBindFramebuffer(GL_FRAMEBUFFER, m_iCurrentOutputFb); -#endif - end(); + g_pHyprRenderer->endRender(); g_pHyprRenderer->m_bRenderingSnapshot = false; - - wlr_output_rollback(PMONITOR->output); } void CHyprOpenGLImpl::renderSnapshot(CWindow** pWindow) { @@ -1591,7 +1717,7 @@ void CHyprOpenGLImpl::renderSnapshot(CWindow** pWindow) { const auto PMONITOR = g_pCompositor->getMonitorFromID(PWINDOW->m_iMonitorID); - wlr_box windowBox; + CBox windowBox; // some mafs to figure out the correct box // the originalClosedPos is relative to the monitor's pos Vector2D scaleXY = Vector2D((PMONITOR->scale * PWINDOW->m_vRealSize.vec().x / (PWINDOW->m_vOriginalClosedSize.x * PMONITOR->scale)), @@ -1605,7 +1731,7 @@ void CHyprOpenGLImpl::renderSnapshot(CWindow** pWindow) { CRegion fakeDamage{0, 0, PMONITOR->vecTransformedSize.x, PMONITOR->vecTransformedSize.y}; if (*PDIMAROUND && (*pWindow)->m_sAdditionalConfigData.dimAround) { - wlr_box monbox = {0, 0, g_pHyprOpenGL->m_RenderData.pMonitor->vecPixelSize.x, g_pHyprOpenGL->m_RenderData.pMonitor->vecPixelSize.y}; + CBox monbox = {0, 0, g_pHyprOpenGL->m_RenderData.pMonitor->vecPixelSize.x, g_pHyprOpenGL->m_RenderData.pMonitor->vecPixelSize.y}; g_pHyprOpenGL->renderRect(&monbox, CColor(0, 0, 0, *PDIMAROUND * PWINDOW->m_fAlpha.fl())); g_pHyprRenderer->damageMonitor(PMONITOR); } @@ -1633,7 +1759,7 @@ void CHyprOpenGLImpl::renderSnapshot(SLayerSurface** pLayer) { const auto PMONITOR = g_pCompositor->getMonitorFromID(PLAYER->monitorID); - wlr_box monbox = {0, 0, PMONITOR->vecTransformedSize.x, PMONITOR->vecTransformedSize.y}; + CBox monbox = {0, 0, PMONITOR->vecTransformedSize.x, PMONITOR->vecTransformedSize.y}; CRegion fakeDamage{0, 0, PMONITOR->vecTransformedSize.x, PMONITOR->vecTransformedSize.y}; @@ -1644,7 +1770,7 @@ void CHyprOpenGLImpl::renderSnapshot(SLayerSurface** pLayer) { m_bEndFrame = false; } -void CHyprOpenGLImpl::renderRoundedShadow(wlr_box* box, int round, int range, float a) { +void CHyprOpenGLImpl::renderRoundedShadow(CBox* box, int round, int range, const CColor& color, float a) { RASSERT(m_RenderData.pMonitor, "Tried to render shadow without begin()!"); RASSERT((box->width > 0 && box->height > 0), "Tried to render shadow with width/height < 0!"); RASSERT(m_pCurrentWindow, "Tried to render shadow without a window!"); @@ -1654,10 +1780,8 @@ void CHyprOpenGLImpl::renderRoundedShadow(wlr_box* box, int round, int range, fl TRACY_GPU_ZONE("RenderShadow"); - wlr_box newBox = *box; - scaleBox(&newBox, m_RenderData.renderModif.scale); - newBox.x += m_RenderData.renderModif.translate.x; - newBox.y += m_RenderData.renderModif.translate.y; + CBox newBox = *box; + m_RenderData.renderModif.applyToBox(newBox); box = &newBox; @@ -1665,11 +1789,11 @@ void CHyprOpenGLImpl::renderRoundedShadow(wlr_box* box, int round, int range, fl const auto SHADOWPOWER = std::clamp((int)*PSHADOWPOWER, 1, 4); - const auto col = m_pCurrentWindow->m_cRealShadowColor.col(); + const auto col = color; float matrix[9]; - wlr_matrix_project_box(matrix, box, wlr_output_transform_invert(!m_bEndFrame ? WL_OUTPUT_TRANSFORM_NORMAL : m_RenderData.pMonitor->transform), 0, - m_RenderData.pMonitor->output->transform_matrix); // TODO: write own, don't use WLR here + wlr_matrix_project_box(matrix, box->pWlr(), wlr_output_transform_invert(!m_bEndFrame ? WL_OUTPUT_TRANSFORM_NORMAL : m_RenderData.pMonitor->transform), newBox.rot, + m_RenderData.pMonitor->projMatrix.data()); // TODO: write own, don't use WLR here float glMatrix[9]; wlr_matrix_multiply(glMatrix, m_RenderData.projection, matrix); @@ -1728,23 +1852,23 @@ void CHyprOpenGLImpl::renderRoundedShadow(wlr_box* box, int round, int range, fl void CHyprOpenGLImpl::saveBufferForMirror() { if (!m_RenderData.pCurrentMonData->monitorMirrorFB.isAllocated()) - m_RenderData.pCurrentMonData->monitorMirrorFB.alloc(m_RenderData.pMonitor->vecPixelSize.x, m_RenderData.pMonitor->vecPixelSize.y); + m_RenderData.pCurrentMonData->monitorMirrorFB.alloc(m_RenderData.pMonitor->vecPixelSize.x, m_RenderData.pMonitor->vecPixelSize.y, m_RenderData.pMonitor->drmFormat); m_RenderData.pCurrentMonData->monitorMirrorFB.bind(); - wlr_box monbox = {0, 0, m_RenderData.pMonitor->vecPixelSize.x, m_RenderData.pMonitor->vecPixelSize.y}; + CBox monbox = {0, 0, m_RenderData.pMonitor->vecPixelSize.x, m_RenderData.pMonitor->vecPixelSize.y}; blend(false); - renderTexture(m_RenderData.pCurrentMonData->primaryFB.m_cTex, &monbox, 1.f, 0, false, false); + renderTexture(m_RenderData.currentFB->m_cTex, &monbox, 1.f, 0, false, false); blend(true); - m_RenderData.pCurrentMonData->primaryFB.bind(); + m_RenderData.currentFB->bind(); } void CHyprOpenGLImpl::renderMirrored() { - wlr_box monbox = {0, 0, m_RenderData.pMonitor->vecPixelSize.x, m_RenderData.pMonitor->vecPixelSize.y}; + CBox monbox = {0, 0, m_RenderData.pMonitor->vecPixelSize.x, m_RenderData.pMonitor->vecPixelSize.y}; const auto PFB = &m_mMonitorRenderResources[m_RenderData.pMonitor->pMirrorOf].monitorMirrorFB; @@ -1775,34 +1899,41 @@ void CHyprOpenGLImpl::renderSplash(cairo_t* const CAIRO, cairo_surface_t* const void CHyprOpenGLImpl::createBGTextureForMonitor(CMonitor* pMonitor) { RASSERT(m_RenderData.pMonitor, "Tried to createBGTex without begin()!"); - static auto* const PNOSPLASH = &g_pConfigManager->getConfigValuePtr("misc:disable_splash_rendering")->intValue; - static auto* const PDISABLEHYPRCHAN = &g_pConfigManager->getConfigValuePtr("misc:disable_hypr_chan")->intValue; - static auto* const PFORCEHYPRCHAN = &g_pConfigManager->getConfigValuePtr("misc:force_hypr_chan")->intValue; + static auto* const PRENDERTEX = &g_pConfigManager->getConfigValuePtr("misc:disable_hyprland_logo")->intValue; + static auto* const PNOSPLASH = &g_pConfigManager->getConfigValuePtr("misc:disable_splash_rendering")->intValue; + static auto* const PFORCEHYPRCHAN = &g_pConfigManager->getConfigValuePtr("misc:force_hypr_chan")->intValue; + static auto* const PFORCEWALLPAPER = &g_pConfigManager->getConfigValuePtr("misc:force_default_wallpaper")->intValue; - std::random_device dev; - std::mt19937 engine(dev()); - std::uniform_int_distribution<> distribution(0, 2); - std::uniform_int_distribution<> distribution2(0, 1); + const auto FORCEWALLPAPER = std::clamp(*PFORCEWALLPAPER, static_cast(-1L), static_cast(2L)); - const bool USEANIME = *PFORCEHYPRCHAN || distribution(engine) == 0; // 66% for anime + if (*PRENDERTEX) + return; // release the last tex if exists const auto PTEX = &m_mMonitorBGTextures[pMonitor]; PTEX->destroyTexture(); PTEX->allocate(); - Debug::log(LOG, "Allocated texture for BGTex"); // TODO: use relative paths to the installation // or configure the paths at build time + std::string texPath = "/usr/share/hyprland/wall_"; + std::string prefixes[] = {"", "anime_", "anime2_"}; // get the adequate tex - std::string texPath = "/usr/share/hyprland/wall_"; - if (!*PDISABLEHYPRCHAN) - texPath += std::string(USEANIME ? (distribution2(engine) == 0 ? "anime_" : "anime2_") : ""); + if (FORCEWALLPAPER == -1) { + std::random_device dev; + std::mt19937 engine(dev()); + std::uniform_int_distribution<> distribution(0, 2); + std::uniform_int_distribution<> distribution_anime(1, 2); - // check if wallpapers exist + if (PFORCEHYPRCHAN) + texPath += prefixes[distribution_anime(engine)]; + else + texPath += prefixes[distribution(engine)]; + } else + texPath += prefixes[FORCEWALLPAPER]; Vector2D textureSize; if (pMonitor->vecTransformedSize.x > 3850) { @@ -1816,6 +1947,7 @@ void CHyprOpenGLImpl::createBGTextureForMonitor(CMonitor* pMonitor) { texPath += "2K.png"; } + // check if wallpapers exist if (!std::filesystem::exists(texPath)) { // try local texPath = texPath.substr(0, 5) + "local/" + texPath.substr(5); @@ -1843,7 +1975,7 @@ void CHyprOpenGLImpl::createBGTextureForMonitor(CMonitor* pMonitor) { origin.x = (m_RenderData.pMonitor->vecTransformedSize.x - PTEX->m_vSize.x * scale) / 2.0; } - wlr_box box = {origin.x, origin.y, PTEX->m_vSize.x * scale, PTEX->m_vSize.y * scale}; + CBox box = {origin.x, origin.y, PTEX->m_vSize.x * scale, PTEX->m_vSize.y * scale}; m_mMonitorRenderResources[pMonitor].backgroundTexBox = box; @@ -1887,26 +2019,34 @@ void CHyprOpenGLImpl::clearWithTex() { TEXIT = m_mMonitorBGTextures.find(m_RenderData.pMonitor); } + CBox box = {0, 0, m_RenderData.pMonitor->vecPixelSize.x, m_RenderData.pMonitor->vecPixelSize.y}; + if (TEXIT != m_mMonitorBGTextures.end()) - renderTexturePrimitive(TEXIT->second, &m_mMonitorRenderResources[m_RenderData.pMonitor].backgroundTexBox); + renderTexture(TEXIT->second, &box, 1); } void CHyprOpenGLImpl::destroyMonitorResources(CMonitor* pMonitor) { - wlr_output_attach_render(pMonitor->output, nullptr); + g_pHyprRenderer->makeEGLCurrent(); - g_pHyprOpenGL->m_mMonitorRenderResources[pMonitor].mirrorFB.release(); - g_pHyprOpenGL->m_mMonitorRenderResources[pMonitor].primaryFB.release(); - g_pHyprOpenGL->m_mMonitorRenderResources[pMonitor].mirrorSwapFB.release(); - g_pHyprOpenGL->m_mMonitorRenderResources[pMonitor].monitorMirrorFB.release(); - g_pHyprOpenGL->m_mMonitorRenderResources[pMonitor].blurFB.release(); - g_pHyprOpenGL->m_mMonitorRenderResources[pMonitor].stencilTex.destroyTexture(); - g_pHyprOpenGL->m_mMonitorBGTextures[pMonitor].destroyTexture(); - g_pHyprOpenGL->m_mMonitorRenderResources.erase(pMonitor); - g_pHyprOpenGL->m_mMonitorBGTextures.erase(pMonitor); + auto RESIT = g_pHyprOpenGL->m_mMonitorRenderResources.find(pMonitor); + if (RESIT != g_pHyprOpenGL->m_mMonitorRenderResources.end()) { + RESIT->second.mirrorFB.release(); + RESIT->second.offloadFB.release(); + RESIT->second.mirrorSwapFB.release(); + RESIT->second.monitorMirrorFB.release(); + RESIT->second.blurFB.release(); + RESIT->second.offMainFB.release(); + RESIT->second.stencilTex.destroyTexture(); + g_pHyprOpenGL->m_mMonitorRenderResources.erase(RESIT); + } + + auto TEXIT = g_pHyprOpenGL->m_mMonitorBGTextures.find(pMonitor); + if (TEXIT != g_pHyprOpenGL->m_mMonitorBGTextures.end()) { + TEXIT->second.destroyTexture(); + g_pHyprOpenGL->m_mMonitorBGTextures.erase(TEXIT); + } Debug::log(LOG, "Monitor {} -> destroyed all render data", pMonitor->szName); - - wlr_output_rollback(pMonitor->output); } void CHyprOpenGLImpl::saveMatrix() { @@ -1921,3 +2061,179 @@ void CHyprOpenGLImpl::setMatrixScaleTranslate(const Vector2D& translate, const f void CHyprOpenGLImpl::restoreMatrix() { memcpy(m_RenderData.projection, m_RenderData.savedProjection, 9 * sizeof(float)); } + +void CHyprOpenGLImpl::bindOffMain() { + m_RenderData.pCurrentMonData->offMainFB.bind(); + clear(CColor(0, 0, 0, 0)); + m_RenderData.currentFB = &m_RenderData.pCurrentMonData->offMainFB; +} + +void CHyprOpenGLImpl::renderOffToMain(CFramebuffer* off) { + CBox monbox = {0, 0, m_RenderData.pMonitor->vecTransformedSize.x, m_RenderData.pMonitor->vecTransformedSize.y}; + renderTexturePrimitive(off->m_cTex, &monbox); +} + +void CHyprOpenGLImpl::bindBackOnMain() { + m_RenderData.mainFB->bind(); + m_RenderData.currentFB = m_RenderData.mainFB; +} + +void CHyprOpenGLImpl::setMonitorTransformEnabled(bool enabled) { + m_bEndFrame = !enabled; +} + +inline const SGLPixelFormat GLES2_FORMATS[] = { + { + .drmFormat = DRM_FORMAT_ARGB8888, + .glFormat = GL_BGRA_EXT, + .glType = GL_UNSIGNED_BYTE, + .withAlpha = true, + }, + { + .drmFormat = DRM_FORMAT_XRGB8888, + .glFormat = GL_BGRA_EXT, + .glType = GL_UNSIGNED_BYTE, + .withAlpha = false, + }, + { + .drmFormat = DRM_FORMAT_XBGR8888, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_BYTE, + .withAlpha = false, + }, + { + .drmFormat = DRM_FORMAT_ABGR8888, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_BYTE, + .withAlpha = true, + }, + { + .drmFormat = DRM_FORMAT_BGR888, + .glFormat = GL_RGB, + .glType = GL_UNSIGNED_BYTE, + .withAlpha = false, + }, +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + { + .drmFormat = DRM_FORMAT_RGBX4444, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_SHORT_4_4_4_4, + .withAlpha = false, + }, + { + .drmFormat = DRM_FORMAT_RGBA4444, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_SHORT_4_4_4_4, + .withAlpha = true, + }, + { + .drmFormat = DRM_FORMAT_RGBX5551, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_SHORT_5_5_5_1, + .withAlpha = false, + }, + { + .drmFormat = DRM_FORMAT_RGBA5551, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_SHORT_5_5_5_1, + .withAlpha = true, + }, + { + .drmFormat = DRM_FORMAT_RGB565, + .glFormat = GL_RGB, + .glType = GL_UNSIGNED_SHORT_5_6_5, + .withAlpha = false, + }, + { + .drmFormat = DRM_FORMAT_XBGR2101010, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_INT_2_10_10_10_REV_EXT, + .withAlpha = false, + }, + { + .drmFormat = DRM_FORMAT_ABGR2101010, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_INT_2_10_10_10_REV_EXT, + .withAlpha = true, + }, + { + .drmFormat = DRM_FORMAT_XBGR16161616F, + .glFormat = GL_RGBA, + .glType = GL_HALF_FLOAT_OES, + .withAlpha = false, + }, + { + .drmFormat = DRM_FORMAT_ABGR16161616F, + .glFormat = GL_RGBA, + .glType = GL_HALF_FLOAT_OES, + .withAlpha = true, + }, + { + .drmFormat = DRM_FORMAT_XBGR16161616, + .glInternalFormat = GL_RGBA16_EXT, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_SHORT, + .withAlpha = false, + }, + { + .drmFormat = DRM_FORMAT_ABGR16161616, + .glInternalFormat = GL_RGBA16_EXT, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_SHORT, + .withAlpha = true, + }, +#endif +}; + +uint32_t CHyprOpenGLImpl::getPreferredReadFormat(CMonitor* pMonitor) { + GLint glf = -1, glt = -1, as = -1; + glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &glf); + glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &glt); + glGetIntegerv(GL_ALPHA_BITS, &as); + + if (glf == 0 || glt == 0) { + glf = drmFormatToGL(pMonitor->drmFormat); + glt = glFormatToType(glf); + } + + for (auto& fmt : GLES2_FORMATS) { + if (fmt.glFormat == glf && fmt.glType == glt && fmt.withAlpha == (as > 0)) + return fmt.drmFormat; + } + + if (m_sExts.EXT_read_format_bgra) + return DRM_FORMAT_XRGB8888; + + return DRM_FORMAT_XBGR8888; +} + +const SGLPixelFormat* CHyprOpenGLImpl::getPixelFormatFromDRM(uint32_t drmFormat) { + for (auto& fmt : GLES2_FORMATS) { + if (fmt.drmFormat == drmFormat) + return &fmt; + } + + return nullptr; +} + +void SRenderModifData::applyToBox(CBox& box) { + for (auto& [type, val] : modifs) { + try { + switch (type) { + case RMOD_TYPE_SCALE: box.scale(std::any_cast(val)); break; + case RMOD_TYPE_SCALECENTER: box.scaleFromCenter(std::any_cast(val)); break; + case RMOD_TYPE_TRANSLATE: box.translate(std::any_cast(val)); break; + case RMOD_TYPE_ROTATE: box.rot += std::any_cast(val); break; + case RMOD_TYPE_ROTATECENTER: { + const auto THETA = std::any_cast(val); + const double COS = std::cos(THETA); + const double SIN = std::sin(THETA); + box.rot += THETA; + const auto OLDPOS = box.pos(); + box.x = OLDPOS.x * COS - OLDPOS.y * SIN; + box.y = OLDPOS.y * COS + OLDPOS.x * SIN; + } + } + } catch (std::bad_any_cast& e) { Debug::log(ERR, "BUG THIS OR PLUGIN ERROR: caught a bad_any_cast in SRenderModifData::applyToBox!"); } + } +} diff --git a/src/render/OpenGL.hpp b/src/render/OpenGL.hpp index c04e8c65..9fd6d197 100644 --- a/src/render/OpenGL.hpp +++ b/src/render/OpenGL.hpp @@ -13,6 +13,10 @@ #include "Shader.hpp" #include "Texture.hpp" #include "Framebuffer.hpp" +#include "Transformer.hpp" +#include "Renderbuffer.hpp" + +#include #include "../debug/TracyDefines.hpp" @@ -26,21 +30,38 @@ inline const float fullVerts[] = { }; inline const float fanVertsFull[] = {-1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f}; -enum eDiscardMode -{ +enum eDiscardMode { DISCARD_OPAQUE = 1, DISCARD_ALPHA = 1 << 1 }; struct SRenderModifData { - Vector2D translate = {}; - float scale = 1.f; + enum eRenderModifType { + RMOD_TYPE_SCALE, /* scale by a float */ + RMOD_TYPE_SCALECENTER, /* scale by a float from the center */ + RMOD_TYPE_TRANSLATE, /* translate by a Vector2D */ + RMOD_TYPE_ROTATE, /* rotate by a float in rad from top left */ + RMOD_TYPE_ROTATECENTER, /* rotate by a float in rad from center */ + }; + + std::vector> modifs; + + void applyToBox(CBox& box); +}; + +struct SGLPixelFormat { + uint32_t drmFormat = DRM_FORMAT_INVALID; + GLint glInternalFormat = 0; + GLint glFormat = 0; + GLint glType = 0; + bool withAlpha = false; }; struct SMonitorRenderData { - CFramebuffer primaryFB; + CFramebuffer offloadFB; CFramebuffer mirrorFB; // these are used for some effects, CFramebuffer mirrorSwapFB; // etc + CFramebuffer offMainFB; CFramebuffer monitorMirrorFB; // used for mirroring outputs @@ -50,17 +71,19 @@ struct SMonitorRenderData { bool blurFBDirty = true; bool blurFBShouldRender = false; - wlr_box backgroundTexBox; + CBox backgroundTexBox; // Shaders bool m_bShadersInitialized = false; CShader m_shQUAD; CShader m_shRGBA; CShader m_shPASSTHRURGBA; + CShader m_shMATTE; CShader m_shRGBX; CShader m_shEXT; CShader m_shBLUR1; CShader m_shBLUR2; + CShader m_shBLURPREPARE; CShader m_shBLURFINISH; CShader m_shSHADOW; CShader m_shBORDER1; @@ -75,6 +98,9 @@ struct SCurrentRenderData { float savedProjection[9]; SMonitorRenderData* pCurrentMonData = nullptr; + CFramebuffer* currentFB = nullptr; // current rendering to + CFramebuffer* mainFB = nullptr; // main to render to + CFramebuffer* outFB = nullptr; // out to render to (if offloaded, etc) CRegion damage; @@ -82,11 +108,12 @@ struct SCurrentRenderData { float mouseZoomFactor = 1.f; bool mouseZoomUseMouse = true; // true by default bool useNearestNeighbor = false; + bool forceIntrospection = false; // cleaned in ::end() Vector2D primarySurfaceUVTopLeft = Vector2D(-1, -1); Vector2D primarySurfaceUVBottomRight = Vector2D(-1, -1); - wlr_box clipBox = {}; + CBox clipBox = {}; uint32_t discardMode = DISCARD_OPAQUE; float discardOpacity = 0.f; @@ -98,65 +125,83 @@ class CHyprOpenGLImpl { public: CHyprOpenGLImpl(); - void begin(CMonitor*, CRegion*, bool fake = false); - void end(); - void bindWlrOutputFb(); + void begin(CMonitor*, CRegion*, CFramebuffer* fb = nullptr /* if provided, it's not a real frame */); + void end(); - void renderRect(wlr_box*, const CColor&, int round = 0); - void renderRectWithBlur(wlr_box*, const CColor&, int round = 0, float blurA = 1.f); - void renderRectWithDamage(wlr_box*, const CColor&, CRegion* damage, int round = 0); - void renderTexture(wlr_texture*, wlr_box*, float a, int round = 0, bool allowCustomUV = false); - void renderTexture(const CTexture&, wlr_box*, float a, int round = 0, bool discardActive = false, bool allowCustomUV = false); - void renderTextureWithBlur(const CTexture&, wlr_box*, float a, wlr_surface* pSurface, int round = 0, bool blockBlurOptimization = false, float blurA = 1.f); - void renderRoundedShadow(wlr_box*, int round, int range, float a = 1.0); - void renderBorder(wlr_box*, const CGradientValueData&, int round, int borderSize, float a = 1.0); + void renderRect(CBox*, const CColor&, int round = 0); + void renderRectWithBlur(CBox*, const CColor&, int round = 0, float blurA = 1.f, bool xray = false); + void renderRectWithDamage(CBox*, const CColor&, CRegion* damage, int round = 0); + void renderTexture(wlr_texture*, CBox*, float a, int round = 0, bool allowCustomUV = false); + void renderTexture(const CTexture&, CBox*, float a, int round = 0, bool discardActive = false, bool allowCustomUV = false); + void renderTextureWithBlur(const CTexture&, CBox*, float a, wlr_surface* pSurface, int round = 0, bool blockBlurOptimization = false, float blurA = 1.f); + void renderRoundedShadow(CBox*, int round, int range, const CColor& color, float a = 1.0); + void renderBorder(CBox*, const CGradientValueData&, int round, int borderSize, float a = 1.0, int outerRound = -1 /* use round */); + void renderTextureMatte(const CTexture& tex, CBox* pBox, CFramebuffer& matte); - void saveMatrix(); - void setMatrixScaleTranslate(const Vector2D& translate, const float& scale); - void restoreMatrix(); + void setMonitorTransformEnabled(bool enabled); - void blend(bool enabled); + void saveMatrix(); + void setMatrixScaleTranslate(const Vector2D& translate, const float& scale); + void restoreMatrix(); - void makeWindowSnapshot(CWindow*); - void makeRawWindowSnapshot(CWindow*, CFramebuffer*); - void makeLayerSnapshot(SLayerSurface*); - void renderSnapshot(CWindow**); - void renderSnapshot(SLayerSurface**); + void blend(bool enabled); - void clear(const CColor&); - void clearWithTex(); - void scissor(const wlr_box*, bool transform = true); - void scissor(const pixman_box32*, bool transform = true); - void scissor(const int x, const int y, const int w, const int h, bool transform = true); + void makeWindowSnapshot(CWindow*); + void makeRawWindowSnapshot(CWindow*, CFramebuffer*); + void makeLayerSnapshot(SLayerSurface*); + void renderSnapshot(CWindow**); + void renderSnapshot(SLayerSurface**); + bool shouldUseNewBlurOptimizations(SLayerSurface* pLayer, CWindow* pWindow); - void destroyMonitorResources(CMonitor*); + void clear(const CColor&); + void clearWithTex(); + void scissor(const CBox*, bool transform = true); + void scissor(const pixman_box32*, bool transform = true); + void scissor(const int x, const int y, const int w, const int h, bool transform = true); - void markBlurDirtyForMonitor(CMonitor*); + void destroyMonitorResources(CMonitor*); - void preWindowPass(); - bool preBlurQueued(); - void preRender(CMonitor*); + void markBlurDirtyForMonitor(CMonitor*); - void saveBufferForMirror(); - void renderMirrored(); + void preWindowPass(); + bool preBlurQueued(); + void preRender(CMonitor*); - void applyScreenShader(const std::string& path); + void saveBufferForMirror(); + void renderMirrored(); - SCurrentRenderData m_RenderData; + void applyScreenShader(const std::string& path); - GLint m_iCurrentOutputFb = 0; - GLint m_iWLROutputFb = 0; + void bindOffMain(); + void renderOffToMain(CFramebuffer* off); + void bindBackOnMain(); - bool m_bReloadScreenShader = true; // at launch it can be set + uint32_t getPreferredReadFormat(CMonitor* pMonitor); + const SGLPixelFormat* getPixelFormatFromDRM(uint32_t drmFormat); - CWindow* m_pCurrentWindow = nullptr; // hack to get the current rendered window - SLayerSurface* m_pCurrentLayer = nullptr; // hack to get the current rendered layer + SCurrentRenderData m_RenderData; + + GLint m_iCurrentOutputFb = 0; + + bool m_bReloadScreenShader = true; // at launch it can be set + + CWindow* m_pCurrentWindow = nullptr; // hack to get the current rendered window + SLayerSurface* m_pCurrentLayer = nullptr; // hack to get the current rendered layer std::unordered_map m_mWindowFramebuffers; std::unordered_map m_mLayerFramebuffers; std::unordered_map m_mMonitorRenderResources; std::unordered_map m_mMonitorBGTextures; + struct { + PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC glEGLImageTargetRenderbufferStorageOES = nullptr; + PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR = nullptr; + } m_sProc; + + struct { + bool EXT_read_format_bgra = false; + } m_sExts; + private: std::list m_lBuffers; std::list m_lTextures; @@ -164,10 +209,11 @@ class CHyprOpenGLImpl { int m_iDRMFD; std::string m_szExtensions; - bool m_bFakeFrame = false; - bool m_bEndFrame = false; - bool m_bApplyFinalShader = false; - bool m_bBlend = false; + bool m_bFakeFrame = false; + bool m_bEndFrame = false; + bool m_bApplyFinalShader = false; + bool m_bBlend = false; + bool m_bOffloadedFramebuffer = false; CShader m_sFinalScreenShader; CTimer m_tGlobalTimer; @@ -180,14 +226,14 @@ class CHyprOpenGLImpl { // returns the out FB, can be either Mirror or MirrorSwap CFramebuffer* blurMainFramebufferWithDamage(float a, CRegion* damage); - void renderTextureInternalWithDamage(const CTexture&, wlr_box* pBox, float a, CRegion* damage, int round = 0, bool discardOpaque = false, bool noAA = false, + void renderTextureInternalWithDamage(const CTexture&, CBox* pBox, float a, CRegion* damage, int round = 0, bool discardOpaque = false, bool noAA = false, bool allowCustomUV = false, bool allowDim = false); - void renderTexturePrimitive(const CTexture& tex, wlr_box* pBox); + void renderTexturePrimitive(const CTexture& tex, CBox* pBox); void renderSplash(cairo_t* const, cairo_surface_t* const, double); void preBlurForCurrentMonitor(); - bool shouldUseNewBlurOptimizations(SLayerSurface* pLayer, CWindow* pWindow); + bool passRequiresIntrospection(CMonitor* pMonitor); friend class CHyprRenderer; }; diff --git a/src/render/Renderbuffer.cpp b/src/render/Renderbuffer.cpp new file mode 100644 index 00000000..9434c3f0 --- /dev/null +++ b/src/render/Renderbuffer.cpp @@ -0,0 +1,84 @@ +#include "Renderbuffer.hpp" +#include "OpenGL.hpp" +#include "../Compositor.hpp" + +#include + +CRenderbuffer::~CRenderbuffer() { + if (!g_pCompositor) + return; + + if (eglGetCurrentContext() != wlr_egl_get_context(g_pCompositor->m_sWLREGL)) + eglMakeCurrent(wlr_egl_get_display(g_pCompositor->m_sWLREGL), EGL_NO_SURFACE, EGL_NO_SURFACE, wlr_egl_get_context(g_pCompositor->m_sWLREGL)); + + m_sFramebuffer.release(); + glDeleteRenderbuffers(1, &m_iRBO); + + g_pHyprOpenGL->m_sProc.eglDestroyImageKHR(wlr_egl_get_display(g_pCompositor->m_sWLREGL), m_iImage); +} + +CRenderbuffer::CRenderbuffer(wlr_buffer* buffer, uint32_t format) : m_pWlrBuffer(buffer) { + + // EVIL, but we can't include a hidden header because nixos is fucking special + static EGLImageKHR (*PWLREGLCREATEIMAGEFROMDMABUF)(wlr_egl*, wlr_dmabuf_attributes*, bool*); + static bool symbolFound = false; + if (!symbolFound) { + PWLREGLCREATEIMAGEFROMDMABUF = reinterpret_cast(dlsym(RTLD_DEFAULT, "wlr_egl_create_image_from_dmabuf")); + + symbolFound = true; + + RASSERT(PWLREGLCREATEIMAGEFROMDMABUF, "wlr_egl_create_image_from_dmabuf was not found in wlroots!"); + + Debug::log(LOG, "CRenderbuffer: wlr_egl_create_image_from_dmabuf found at {:x}", (uintptr_t)PWLREGLCREATEIMAGEFROMDMABUF); + } + // end evil hack + + struct wlr_dmabuf_attributes dmabuf = {0}; + if (!wlr_buffer_get_dmabuf(buffer, &dmabuf)) + throw std::runtime_error("wlr_buffer_get_dmabuf failed"); + + bool externalOnly; + m_iImage = PWLREGLCREATEIMAGEFROMDMABUF(g_pCompositor->m_sWLREGL, &dmabuf, &externalOnly); + if (m_iImage == EGL_NO_IMAGE_KHR) + throw std::runtime_error("wlr_egl_create_image_from_dmabuf failed"); + + glGenRenderbuffers(1, &m_iRBO); + glBindRenderbuffer(GL_RENDERBUFFER, m_iRBO); + g_pHyprOpenGL->m_sProc.glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, (GLeglImageOES)m_iImage); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + + glGenFramebuffers(1, &m_sFramebuffer.m_iFb); + m_sFramebuffer.m_vSize = {buffer->width, buffer->height}; + m_sFramebuffer.bind(); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_iRBO); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + throw std::runtime_error("rbo: glCheckFramebufferStatus failed"); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + hyprListener_destroyBuffer.initCallback( + &buffer->events.destroy, [](void* owner, void* data) { g_pHyprRenderer->onRenderbufferDestroy((CRenderbuffer*)owner); }, this, "CRenderbuffer"); +} + +void CRenderbuffer::bind() { + glBindRenderbuffer(GL_RENDERBUFFER, m_iRBO); + bindFB(); +} + +void CRenderbuffer::bindFB() { + m_sFramebuffer.bind(); +} + +void CRenderbuffer::unbind() { + glBindRenderbuffer(GL_RENDERBUFFER, 0); +#ifndef GLES2 + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); +#else + glBindFramebuffer(GL_FRAMEBUFFER, 0); +#endif +} + +CFramebuffer* CRenderbuffer::getFB() { + return &m_sFramebuffer; +} \ No newline at end of file diff --git a/src/render/Renderbuffer.hpp b/src/render/Renderbuffer.hpp new file mode 100644 index 00000000..d8df8ca5 --- /dev/null +++ b/src/render/Renderbuffer.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "Framebuffer.hpp" + +class CMonitor; + +class CRenderbuffer { + public: + CRenderbuffer(wlr_buffer* buffer, uint32_t format); + ~CRenderbuffer(); + + void bind(); + void bindFB(); + void unbind(); + CFramebuffer* getFB(); + + wlr_buffer* m_pWlrBuffer = nullptr; + + DYNLISTENER(destroyBuffer); + + private: + EGLImageKHR m_iImage = 0; + GLuint m_iRBO = 0; + CFramebuffer m_sFramebuffer; +}; \ No newline at end of file diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index 6dcf0b44..cf246c7c 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -2,10 +2,60 @@ #include "../Compositor.hpp" #include "linux-dmabuf-unstable-v1-protocol.h" #include "../helpers/Region.hpp" +#include -void renderSurface(struct wlr_surface* surface, int x, int y, void* data) { - const auto TEXTURE = wlr_surface_get_texture(surface); - const auto RDATA = (SRenderData*)data; +extern "C" { +#include +} + +CHyprRenderer::CHyprRenderer() { + if (envEnabled("WLR_DRM_NO_ATOMIC")) + m_bTearingEnvSatisfied = true; + + if (g_pCompositor->m_sWLRSession) { + wlr_device* dev; + wl_list_for_each(dev, &g_pCompositor->m_sWLRSession->devices, link) { + const auto DRMV = drmGetVersion(dev->fd); + + std::string name = std::string{DRMV->name, DRMV->name_len}; + std::transform(name.begin(), name.end(), name.begin(), tolower); + + if (name.contains("nvidia")) + m_bNvidia = true; + + Debug::log(LOG, "DRM driver information: {} v{}.{}.{} from {} description {}", name, DRMV->version_major, DRMV->version_minor, DRMV->version_patchlevel, + std::string{DRMV->date, DRMV->date_len}, std::string{DRMV->desc, DRMV->desc_len}); + + drmFreeVersion(DRMV); + } + } else { + Debug::log(LOG, "m_sWLRSession is null, omitting full DRM node checks"); + + const auto DRMV = drmGetVersion(g_pCompositor->m_iDRMFD); + + std::string name = std::string{DRMV->name, DRMV->name_len}; + std::transform(name.begin(), name.end(), name.begin(), tolower); + + if (name.contains("nvidia")) + m_bNvidia = true; + + Debug::log(LOG, "Primary DRM driver information: {} v{}.{}.{} from {} description {}", name, DRMV->version_major, DRMV->version_minor, DRMV->version_patchlevel, + std::string{DRMV->date, DRMV->date_len}, std::string{DRMV->desc, DRMV->desc_len}); + + drmFreeVersion(DRMV); + } + + if (m_bNvidia) + Debug::log(WARN, "NVIDIA detected, please remember to follow nvidia instructions on the wiki"); +} + +static void renderSurface(struct wlr_surface* surface, int x, int y, void* data) { + static auto* const PBLURPOPUPS = &g_pConfigManager->getConfigValuePtr("decoration:blur:popups")->intValue; + static auto* const PBLURPOPUPSIGNOREALPHA = &g_pConfigManager->getConfigValuePtr("decoration:blur:popups_ignorealpha")->floatValue; + + const auto TEXTURE = wlr_surface_get_texture(surface); + const auto RDATA = (SRenderData*)data; + const auto INTERACTIVERESIZEINPROGRESS = RDATA->pWindow && g_pInputManager->currentlyDraggedWindow == RDATA->pWindow && g_pInputManager->dragMode == MBIND_RESIZE; if (!TEXTURE) return; @@ -15,10 +65,30 @@ void renderSurface(struct wlr_surface* surface, int x, int y, void* data) { double outputX = 0, outputY = 0; wlr_output_layout_output_coords(g_pCompositor->m_sWLROutputLayout, RDATA->pMonitor->output, &outputX, &outputY); - wlr_box windowBox; - if (RDATA->surface && surface == RDATA->surface) + CBox windowBox; + if (RDATA->surface && surface == RDATA->surface) { windowBox = {(int)outputX + RDATA->x + x, (int)outputY + RDATA->y + y, RDATA->w, RDATA->h}; - else { // here we clamp to 2, these might be some tiny specks + + // however, if surface buffer w / h < box, we need to adjust them + auto* const PSURFACE = CWLSurface::surfaceFromWlr(surface); + + if (PSURFACE && !PSURFACE->m_bFillIgnoreSmall && PSURFACE->small() /* guarantees m_pOwner */) { + const auto CORRECT = PSURFACE->correctSmallVec(); + const auto SIZE = PSURFACE->getViewporterCorrectedSize(); + + if (!INTERACTIVERESIZEINPROGRESS) { + windowBox.x += CORRECT.x; + windowBox.y += CORRECT.y; + + windowBox.width = SIZE.x * (PSURFACE->m_pOwner->m_vRealSize.vec().x / PSURFACE->m_pOwner->m_vReportedSize.x); + windowBox.height = SIZE.y * (PSURFACE->m_pOwner->m_vRealSize.vec().y / PSURFACE->m_pOwner->m_vReportedSize.y); + } else { + windowBox.width = SIZE.x; + windowBox.height = SIZE.y; + } + } + + } else { // here we clamp to 2, these might be some tiny specks windowBox = {(int)outputX + RDATA->x + x, (int)outputY + RDATA->y + y, std::max(surface->current.width, 2), std::max(surface->current.height, 2)}; if (RDATA->pWindow && RDATA->pWindow->m_vRealSize.isBeingAnimated() && RDATA->surface && RDATA->surface != surface && RDATA->squishOversized /* subsurface */) { // adjust subsurfaces to the window @@ -34,9 +104,23 @@ void renderSurface(struct wlr_surface* surface, int x, int y, void* data) { windowBox.height = RDATA->h - y; } - g_pHyprRenderer->calculateUVForSurface(RDATA->pWindow, surface, RDATA->squishOversized); + if (windowBox.width <= 1 || windowBox.height <= 1) + return; // invisible - scaleBox(&windowBox, RDATA->pMonitor->scale); + g_pHyprRenderer->calculateUVForSurface(RDATA->pWindow, surface, RDATA->surface == surface); + + windowBox.scale(RDATA->pMonitor->scale); + windowBox.round(); + + // check for fractional scale surfaces misaligning the buffer size + // in those cases it's better to just force nearest neighbor + // as long as the window is not animated. During those it'd look weird + const auto NEARESTNEIGHBORSET = g_pHyprOpenGL->m_RenderData.useNearestNeighbor; + if (std::floor(RDATA->pMonitor->scale) != RDATA->pMonitor->scale /* Fractional */ && surface->current.scale == 1 /* fs protocol */ && + windowBox.size() != Vector2D{surface->current.buffer_width, surface->current.buffer_height} /* misaligned */ && + DELTALESSTHAN(windowBox.width, surface->current.buffer_width, 3) && DELTALESSTHAN(windowBox.height, surface->current.buffer_height, 3) /* off by one-or-two */ && + (!RDATA->pWindow || (!RDATA->pWindow->m_vRealSize.isBeingAnimated() && !INTERACTIVERESIZEINPROGRESS)) /* not window or not animated/resizing */) + g_pHyprOpenGL->m_RenderData.useNearestNeighbor = true; float rounding = RDATA->rounding; @@ -45,7 +129,8 @@ void renderSurface(struct wlr_surface* surface, int x, int y, void* data) { if (RDATA->dontRound) rounding = 0; - const bool CANDISABLEBLEND = RDATA->alpha >= 1.f && rounding == 0 && surface->opaque; + const bool WINDOWOPAQUE = RDATA->pWindow && RDATA->pWindow->m_pWLSurface.wlr() == surface ? RDATA->pWindow->opaque() : false; + const bool CANDISABLEBLEND = RDATA->alpha * RDATA->fadeAlpha >= 1.f && rounding == 0 && (WINDOWOPAQUE || surface->opaque); if (CANDISABLEBLEND) g_pHyprOpenGL->blend(false); @@ -62,25 +147,36 @@ void renderSurface(struct wlr_surface* surface, int x, int y, void* data) { g_pHyprOpenGL->renderTexture(TEXTURE, &windowBox, RDATA->fadeAlpha * RDATA->alpha, rounding, true); } } else { - g_pHyprOpenGL->renderTexture(TEXTURE, &windowBox, RDATA->fadeAlpha * RDATA->alpha, rounding, true); + if (RDATA->blur && RDATA->popup && *PBLURPOPUPS) { + + if (*PBLURPOPUPSIGNOREALPHA != 1.f) { + g_pHyprOpenGL->m_RenderData.discardMode |= DISCARD_ALPHA; + g_pHyprOpenGL->m_RenderData.discardOpacity = *PBLURPOPUPSIGNOREALPHA; + } + + g_pHyprOpenGL->renderTextureWithBlur(TEXTURE, &windowBox, RDATA->fadeAlpha * RDATA->alpha, surface, rounding, true); + g_pHyprOpenGL->m_RenderData.discardMode &= ~DISCARD_ALPHA; + } else + g_pHyprOpenGL->renderTexture(TEXTURE, &windowBox, RDATA->fadeAlpha * RDATA->alpha, rounding, true); } if (!g_pHyprRenderer->m_bBlockSurfaceFeedback) { wlr_surface_send_frame_done(surface, RDATA->when); - wlr_presentation_surface_textured_on_output(g_pCompositor->m_sWLRPresentation, surface, RDATA->pMonitor->output); + wlr_presentation_surface_textured_on_output(surface, RDATA->pMonitor->output); } g_pHyprOpenGL->blend(true); - // reset the UV, we might've set it above + // reset props g_pHyprOpenGL->m_RenderData.primarySurfaceUVTopLeft = Vector2D(-1, -1); g_pHyprOpenGL->m_RenderData.primarySurfaceUVBottomRight = Vector2D(-1, -1); + g_pHyprOpenGL->m_RenderData.useNearestNeighbor = NEARESTNEIGHBORSET; } bool CHyprRenderer::shouldRenderWindow(CWindow* pWindow, CMonitor* pMonitor, CWorkspace* pWorkspace) { - wlr_box geometry = pWindow->getFullWindowBoundingBox(); + CBox geometry = pWindow->getFullWindowBoundingBox(); - if (!wlr_output_layout_intersects(g_pCompositor->m_sWLROutputLayout, pMonitor->output, &geometry)) + if (!wlr_output_layout_intersects(g_pCompositor->m_sWLROutputLayout, pMonitor->output, geometry.pWlr())) return false; if (pWindow->m_iWorkspaceID == -1) @@ -94,7 +190,8 @@ bool CHyprRenderer::shouldRenderWindow(CWindow* pWindow, CMonitor* pMonitor, CWo if (PWINDOWWORKSPACE->m_vRenderOffset.isBeingAnimated() || PWINDOWWORKSPACE->m_fAlpha.isBeingAnimated() || PWINDOWWORKSPACE->m_bForceRendering) { return true; } else { - if (!(!PWINDOWWORKSPACE->m_bHasFullscreenWindow || pWindow->m_bIsFullscreen || (pWindow->m_bIsFloating && pWindow->m_bCreatedOverFullscreen))) + if (PWINDOWWORKSPACE->m_bHasFullscreenWindow && !pWindow->m_bIsFullscreen && !pWindow->m_bIsFloating && !pWindow->m_bCreatedOverFullscreen && + pWindow->m_fAlpha.fl() == 0) return false; } } @@ -146,7 +243,7 @@ void CHyprRenderer::renderWorkspaceWindowsFullscreen(CMonitor* pMonitor, CWorksp // loop over the tiled windows that are fading out for (auto& w : g_pCompositor->m_vWindows) { - if (w->m_iWorkspaceID != pMonitor->activeWorkspace) + if (!shouldRenderWindow(w.get(), pMonitor, pWorkspace)) continue; if (w->m_fAlpha.fl() == 0.f) @@ -155,12 +252,15 @@ void CHyprRenderer::renderWorkspaceWindowsFullscreen(CMonitor* pMonitor, CWorksp if (w->m_bIsFullscreen || w->m_bIsFloating) continue; + if (pWorkspace->m_bIsSpecialWorkspace != g_pCompositor->isWorkspaceSpecial(w->m_iWorkspaceID)) + continue; + renderWindow(w.get(), pMonitor, time, true, RENDER_PASS_ALL); } // and floating ones too for (auto& w : g_pCompositor->m_vWindows) { - if (w->m_iWorkspaceID != pMonitor->activeWorkspace) + if (!shouldRenderWindow(w.get(), pMonitor, pWorkspace)) continue; if (w->m_fAlpha.fl() == 0.f) @@ -169,9 +269,16 @@ void CHyprRenderer::renderWorkspaceWindowsFullscreen(CMonitor* pMonitor, CWorksp if (w->m_bIsFullscreen || !w->m_bIsFloating) continue; + if (w->m_iMonitorID == pWorkspace->m_iMonitorID && pWorkspace->m_bIsSpecialWorkspace != g_pCompositor->isWorkspaceSpecial(w->m_iWorkspaceID)) + continue; + + if (pWorkspace->m_bIsSpecialWorkspace && w->m_iMonitorID != pWorkspace->m_iMonitorID) + continue; // special on another are rendered as a part of the base pass + renderWindow(w.get(), pMonitor, time, true, RENDER_PASS_ALL); } + // TODO: this pass sucks for (auto& w : g_pCompositor->m_vWindows) { const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(w->m_iWorkspaceID); @@ -183,11 +290,14 @@ void CHyprRenderer::renderWorkspaceWindowsFullscreen(CMonitor* pMonitor, CWorksp continue; } - if (w->m_iWorkspaceID == pMonitor->activeWorkspace && !w->m_bIsFullscreen) + if (!w->m_bIsFullscreen) continue; renderWindow(w.get(), pMonitor, time, pWorkspace->m_efFullscreenMode != FULLSCREEN_FULL, RENDER_PASS_ALL); + if (w->m_iWorkspaceID != pWorkspace->m_iID) + continue; + pWorkspaceWindow = w.get(); } @@ -202,6 +312,12 @@ void CHyprRenderer::renderWorkspaceWindowsFullscreen(CMonitor* pMonitor, CWorksp if (w->m_iWorkspaceID != pWorkspaceWindow->m_iWorkspaceID || (!w->m_bCreatedOverFullscreen && !w->m_bPinned) || (!w->m_bIsMapped && !w->m_bFadingOut) || w->m_bIsFullscreen) continue; + if (w->m_iMonitorID == pWorkspace->m_iMonitorID && pWorkspace->m_bIsSpecialWorkspace != g_pCompositor->isWorkspaceSpecial(w->m_iWorkspaceID)) + continue; + + if (pWorkspace->m_bIsSpecialWorkspace && w->m_iMonitorID != pWorkspace->m_iMonitorID) + continue; // special on another are rendered as a part of the base pass + renderWindow(w.get(), pMonitor, time, true, RENDER_PASS_ALL); } } @@ -219,14 +335,14 @@ void CHyprRenderer::renderWorkspaceWindows(CMonitor* pMonitor, CWorkspace* pWork if (w->m_bIsFloating) continue; // floating are in the second pass - if (g_pCompositor->isWorkspaceSpecial(w->m_iWorkspaceID)) - continue; // special are in the third pass - if (!shouldRenderWindow(w.get(), pMonitor, pWorkspace)) continue; + if (pWorkspace->m_bIsSpecialWorkspace != g_pCompositor->isWorkspaceSpecial(w->m_iWorkspaceID)) + continue; + // render active window after all others of this pass - if (w.get() == g_pCompositor->m_pLastWindow) { + if (w.get() == g_pCompositor->m_pLastWindow && w->m_iWorkspaceID == pWorkspace->m_iID) { lastWindow = w.get(); continue; } @@ -246,8 +362,8 @@ void CHyprRenderer::renderWorkspaceWindows(CMonitor* pMonitor, CWorkspace* pWork if (w->m_bIsFloating) continue; // floating are in the second pass - if (g_pCompositor->isWorkspaceSpecial(w->m_iWorkspaceID)) - continue; // special are in the third pass + if (pWorkspace->m_bIsSpecialWorkspace != g_pCompositor->isWorkspaceSpecial(w->m_iWorkspaceID)) + continue; if (!shouldRenderWindow(w.get(), pMonitor, pWorkspace)) continue; @@ -264,29 +380,14 @@ void CHyprRenderer::renderWorkspaceWindows(CMonitor* pMonitor, CWorkspace* pWork if (!w->m_bIsFloating || w->m_bPinned) continue; - if (g_pCompositor->isWorkspaceSpecial(w->m_iWorkspaceID)) - continue; - if (!shouldRenderWindow(w.get(), pMonitor, pWorkspace)) continue; - // render the bad boy - renderWindow(w.get(), pMonitor, time, true, RENDER_PASS_ALL); - } - - // pinned always above - for (auto& w : g_pCompositor->m_vWindows) { - if (w->isHidden() && !w->m_bIsMapped && !w->m_bFadingOut) + if (w->m_iMonitorID == pWorkspace->m_iMonitorID && pWorkspace->m_bIsSpecialWorkspace != g_pCompositor->isWorkspaceSpecial(w->m_iWorkspaceID)) continue; - if (!w->m_bPinned || !w->m_bIsFloating) - continue; - - if (g_pCompositor->isWorkspaceSpecial(w->m_iWorkspaceID)) - continue; - - if (!shouldRenderWindow(w.get(), pMonitor, pWorkspace)) - continue; + if (pWorkspace->m_bIsSpecialWorkspace && w->m_iMonitorID != pWorkspace->m_iMonitorID) + continue; // special on another are rendered as a part of the base pass // render the bad boy renderWindow(w.get(), pMonitor, time, true, RENDER_PASS_ALL); @@ -305,12 +406,19 @@ void CHyprRenderer::renderWindow(CWindow* pWindow, CMonitor* pMonitor, timespec* TRACY_GPU_ZONE("RenderWindow"); - const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(pWindow->m_iWorkspaceID); - const auto REALPOS = pWindow->m_vRealPosition.vec() + (pWindow->m_bPinned ? Vector2D{} : PWORKSPACE->m_vRenderOffset.vec()); - static auto* const PDIMAROUND = &g_pConfigManager->getConfigValuePtr("decoration:dim_around")->floatValue; - static auto* const PBORDERSIZE = &g_pConfigManager->getConfigValuePtr("general:border_size")->intValue; + const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(pWindow->m_iWorkspaceID); + const auto REALPOS = pWindow->m_vRealPosition.vec() + (pWindow->m_bPinned ? Vector2D{} : PWORKSPACE->m_vRenderOffset.vec()); + static auto* const PDIMAROUND = &g_pConfigManager->getConfigValuePtr("decoration:dim_around")->floatValue; + static auto* const PBLUR = &g_pConfigManager->getConfigValuePtr("decoration:blur:enabled")->intValue; + + SRenderData renderdata = {pMonitor, time}; + CBox textureBox = {REALPOS.x, REALPOS.y, std::max(pWindow->m_vRealSize.vec().x, 5.0), std::max(pWindow->m_vRealSize.vec().y, 5.0)}; + + renderdata.x = textureBox.x; + renderdata.y = textureBox.y; + renderdata.w = textureBox.w; + renderdata.h = textureBox.h; - SRenderData renderdata = {pMonitor, time, REALPOS.x, REALPOS.y}; if (ignorePosition) { renderdata.x = pMonitor->vecPosition.x; renderdata.y = pMonitor->vecPosition.y; @@ -320,8 +428,6 @@ void CHyprRenderer::renderWindow(CWindow* pWindow, CMonitor* pMonitor, timespec* decorate = false; renderdata.surface = pWindow->m_pWLSurface.wlr(); - renderdata.w = std::max(pWindow->m_vRealSize.vec().x, 5.0); // clamp the size to min 5, - renderdata.h = std::max(pWindow->m_vRealSize.vec().y, 5.0); // otherwise we'll have issues later with invalid boxes renderdata.dontRound = (pWindow->m_bIsFullscreen && PWORKSPACE->m_efFullscreenMode == FULLSCREEN_FULL) || (!pWindow->m_sSpecialRenderData.rounding); renderdata.fadeAlpha = pWindow->m_fAlpha.fl() * (pWindow->m_bPinned ? 1.f : PWORKSPACE->m_fAlpha.fl()); renderdata.alpha = pWindow->m_fActiveInactiveAlpha.fl(); @@ -344,13 +450,15 @@ void CHyprRenderer::renderWindow(CWindow* pWindow, CMonitor* pMonitor, timespec* EMIT_HOOK_EVENT("render", RENDER_PRE_WINDOW); if (*PDIMAROUND && pWindow->m_sAdditionalConfigData.dimAround && !m_bRenderingSnapshot && mode != RENDER_PASS_POPUP) { - wlr_box monbox = {0, 0, g_pHyprOpenGL->m_RenderData.pMonitor->vecTransformedSize.x, g_pHyprOpenGL->m_RenderData.pMonitor->vecTransformedSize.y}; + CBox monbox = {0, 0, g_pHyprOpenGL->m_RenderData.pMonitor->vecTransformedSize.x, g_pHyprOpenGL->m_RenderData.pMonitor->vecTransformedSize.y}; g_pHyprOpenGL->renderRect(&monbox, CColor(0, 0, 0, *PDIMAROUND * renderdata.alpha * renderdata.fadeAlpha)); } // clip box for animated offsets - Vector2D offset; - if (!ignorePosition && pWindow->m_bIsFloating && !pWindow->m_bPinned) { + const Vector2D PREOFFSETPOS = {renderdata.x, renderdata.y}; + if (!ignorePosition && pWindow->m_bIsFloating && !pWindow->m_bPinned && !pWindow->m_bIsFullscreen) { + Vector2D offset; + if (PWORKSPACE->m_vRenderOffset.vec().x != 0) { const auto PWSMON = g_pCompositor->getMonitorFromID(PWORKSPACE->m_iMonitorID); const auto PROGRESS = PWORKSPACE->m_vRenderOffset.vec().x / PWSMON->vecSize.x; @@ -377,51 +485,85 @@ void CHyprRenderer::renderWindow(CWindow* pWindow, CMonitor* pMonitor, timespec* renderdata.y += offset.y; } + // if window is floating and we have a slide animation, clip it to its full bb + if (!ignorePosition && pWindow->m_bIsFloating && !pWindow->m_bIsFullscreen && PWORKSPACE->m_vRenderOffset.isBeingAnimated() && !pWindow->m_bPinned) { + CRegion rg = pWindow->getFullWindowBoundingBox().translate(-pMonitor->vecPosition + PWORKSPACE->m_vRenderOffset.vec()).scale(pMonitor->scale); + g_pHyprOpenGL->m_RenderData.clipBox = rg.getExtents(); + } + // render window decorations first, if not fullscreen full if (mode == RENDER_PASS_ALL || mode == RENDER_PASS_MAIN) { - if (!pWindow->m_bIsFullscreen || PWORKSPACE->m_efFullscreenMode != FULLSCREEN_FULL) - for (auto& wd : pWindow->m_dWindowDecorations) - wd->draw(pMonitor, renderdata.alpha * renderdata.fadeAlpha, offset); + + const bool TRANSFORMERSPRESENT = !pWindow->m_vTransformers.empty(); + + if (TRANSFORMERSPRESENT) { + g_pHyprOpenGL->bindOffMain(); + + for (auto& t : pWindow->m_vTransformers) { + t->preWindowRender(&renderdata); + } + } + + if (decorate) { + for (auto& wd : pWindow->m_dWindowDecorations) { + if (wd->getDecorationLayer() != DECORATION_LAYER_BOTTOM) + continue; + + wd->draw(pMonitor, renderdata.alpha * renderdata.fadeAlpha, Vector2D{renderdata.x, renderdata.y} - PREOFFSETPOS); + } + + for (auto& wd : pWindow->m_dWindowDecorations) { + if (wd->getDecorationLayer() != DECORATION_LAYER_UNDER) + continue; + + wd->draw(pMonitor, renderdata.alpha * renderdata.fadeAlpha, Vector2D{renderdata.x, renderdata.y} - PREOFFSETPOS); + } + } static auto* const PXWLUSENN = &g_pConfigManager->getConfigValuePtr("xwayland:use_nearest_neighbor")->intValue; - if (pWindow->m_bIsX11 && *PXWLUSENN) + if ((pWindow->m_bIsX11 && *PXWLUSENN) || pWindow->m_sAdditionalConfigData.nearestNeighbor.toUnderlying()) g_pHyprOpenGL->m_RenderData.useNearestNeighbor = true; + if (pWindow->m_pWLSurface.small() && !pWindow->m_pWLSurface.m_bFillIgnoreSmall && renderdata.blur && *PBLUR) { + CBox wb = {renderdata.x - pMonitor->vecPosition.x, renderdata.y - pMonitor->vecPosition.y, renderdata.w, renderdata.h}; + wb.scale(pMonitor->scale).round(); + g_pHyprOpenGL->renderRectWithBlur(&wb, CColor(0, 0, 0, 0), renderdata.dontRound ? 0 : renderdata.rounding - 1, renderdata.fadeAlpha, + g_pHyprOpenGL->shouldUseNewBlurOptimizations(nullptr, pWindow)); + renderdata.blur = false; + } + wlr_surface_for_each_surface(pWindow->m_pWLSurface.wlr(), renderSurface, &renderdata); g_pHyprOpenGL->m_RenderData.useNearestNeighbor = false; - if (renderdata.decorate && pWindow->m_sSpecialRenderData.border) { - auto grad = g_pHyprOpenGL->m_pCurrentWindow->m_cRealBorderColor; - const bool ANIMATED = g_pHyprOpenGL->m_pCurrentWindow->m_fBorderFadeAnimationProgress.isBeingAnimated(); - float a1 = renderdata.fadeAlpha * renderdata.alpha * (ANIMATED ? g_pHyprOpenGL->m_pCurrentWindow->m_fBorderFadeAnimationProgress.fl() : 1.f); + if (decorate) { + for (auto& wd : pWindow->m_dWindowDecorations) { + if (wd->getDecorationLayer() != DECORATION_LAYER_OVER) + continue; - if (g_pHyprOpenGL->m_pCurrentWindow->m_fBorderAngleAnimationProgress.getConfig()->pValues->internalEnabled) { - grad.m_fAngle += g_pHyprOpenGL->m_pCurrentWindow->m_fBorderAngleAnimationProgress.fl() * M_PI * 2; - grad.m_fAngle = normalizeAngleRad(grad.m_fAngle); + wd->draw(pMonitor, renderdata.alpha * renderdata.fadeAlpha, Vector2D{renderdata.x, renderdata.y} - PREOFFSETPOS); + } + } + + if (TRANSFORMERSPRESENT) { + + CFramebuffer* last = g_pHyprOpenGL->m_RenderData.currentFB; + for (auto& t : pWindow->m_vTransformers) { + last = t->transform(last); } - wlr_box windowBox = {renderdata.x - pMonitor->vecPosition.x, renderdata.y - pMonitor->vecPosition.y, renderdata.w, renderdata.h}; - - scaleBox(&windowBox, pMonitor->scale); - - int borderSize = pWindow->m_sSpecialRenderData.borderSize.toUnderlying() == -1 ? *PBORDERSIZE : pWindow->m_sSpecialRenderData.borderSize.toUnderlying(); - if (pWindow->m_sAdditionalConfigData.borderSize.toUnderlying() != -1) - borderSize = pWindow->m_sAdditionalConfigData.borderSize.toUnderlying(); - - g_pHyprOpenGL->renderBorder(&windowBox, grad, renderdata.rounding, borderSize, a1); - - if (ANIMATED) { - float a2 = renderdata.fadeAlpha * renderdata.alpha * (1.f - g_pHyprOpenGL->m_pCurrentWindow->m_fBorderFadeAnimationProgress.fl()); - g_pHyprOpenGL->renderBorder(&windowBox, g_pHyprOpenGL->m_pCurrentWindow->m_cRealBorderColorPrevious, renderdata.rounding, borderSize, a2); - } + g_pHyprOpenGL->bindBackOnMain(); + g_pHyprOpenGL->renderOffToMain(last); } } + g_pHyprOpenGL->m_RenderData.clipBox = CBox(); + if (mode == RENDER_PASS_ALL || mode == RENDER_PASS_POPUP) { if (!pWindow->m_bIsX11) { - wlr_box geom; - wlr_xdg_surface_get_geometry(pWindow->m_uSurface.xdg, &geom); + CBox geom; + wlr_xdg_surface_get_geometry(pWindow->m_uSurface.xdg, geom.pWlr()); + geom.applyFromWlr(); renderdata.x -= geom.x; renderdata.y -= geom.y; @@ -429,14 +571,30 @@ void CHyprRenderer::renderWindow(CWindow* pWindow, CMonitor* pMonitor, timespec* renderdata.dontRound = true; // don't round popups renderdata.pMonitor = pMonitor; renderdata.squishOversized = false; // don't squish popups + renderdata.popup = true; + + if (pWindow->m_sAdditionalConfigData.nearestNeighbor.toUnderlying()) + g_pHyprOpenGL->m_RenderData.useNearestNeighbor = true; + wlr_xdg_surface_for_each_popup_surface(pWindow->m_uSurface.xdg, renderSurface, &renderdata); + + g_pHyprOpenGL->m_RenderData.useNearestNeighbor = false; + } + + if (decorate) { + for (auto& wd : pWindow->m_dWindowDecorations) { + if (wd->getDecorationLayer() != DECORATION_LAYER_OVERLAY) + continue; + + wd->draw(pMonitor, renderdata.alpha * renderdata.fadeAlpha, Vector2D{renderdata.x, renderdata.y} - PREOFFSETPOS); + } } } EMIT_HOOK_EVENT("render", RENDER_POST_WINDOW); g_pHyprOpenGL->m_pCurrentWindow = nullptr; - g_pHyprOpenGL->m_RenderData.clipBox = {0, 0, 0, 0}; + g_pHyprOpenGL->m_RenderData.clipBox = CBox(); } void CHyprRenderer::renderLayer(SLayerSurface* pLayer, CMonitor* pMonitor, timespec* time) { @@ -467,6 +625,7 @@ void CHyprRenderer::renderLayer(SLayerSurface* pLayer, CMonitor* pMonitor, times renderdata.squishOversized = false; // don't squish popups renderdata.dontRound = true; + renderdata.popup = true; wlr_layer_surface_v1_for_each_popup_surface(pLayer->layerSurface, renderSurface, &renderdata); g_pHyprOpenGL->m_pCurrentLayer = nullptr; @@ -497,18 +656,24 @@ void CHyprRenderer::renderSessionLockSurface(SSessionLockSurface* pSurface, CMon } void CHyprRenderer::renderAllClientsForWorkspace(CMonitor* pMonitor, CWorkspace* pWorkspace, timespec* time, const Vector2D& translate, const float& scale) { - static auto* const PDIMSPECIAL = &g_pConfigManager->getConfigValuePtr("decoration:dim_special")->floatValue; - static auto* const PBLURSPECIAL = &g_pConfigManager->getConfigValuePtr("decoration:blur:special")->intValue; - static auto* const PBLUR = &g_pConfigManager->getConfigValuePtr("decoration:blur:enabled")->intValue; + static auto* const PDIMSPECIAL = &g_pConfigManager->getConfigValuePtr("decoration:dim_special")->floatValue; + static auto* const PBLURSPECIAL = &g_pConfigManager->getConfigValuePtr("decoration:blur:special")->intValue; + static auto* const PBLUR = &g_pConfigManager->getConfigValuePtr("decoration:blur:enabled")->intValue; + static auto* const PRENDERTEX = &g_pConfigManager->getConfigValuePtr("misc:disable_hyprland_logo")->intValue; + static auto* const PBACKGROUNDCOLOR = &g_pConfigManager->getConfigValuePtr("misc:background_color")->intValue; - const SRenderModifData RENDERMODIFDATA = {translate, scale}; + SRenderModifData RENDERMODIFDATA; + if (translate != Vector2D{0, 0}) + RENDERMODIFDATA.modifs.push_back({SRenderModifData::eRenderModifType::RMOD_TYPE_TRANSLATE, translate}); + if (scale != 1.f) + RENDERMODIFDATA.modifs.push_back({SRenderModifData::eRenderModifType::RMOD_TYPE_SCALE, scale}); if (!pMonitor) return; if (!g_pCompositor->m_sSeat.exclusiveClient && g_pSessionLockManager->isSessionLocked()) { // locked with no exclusive, draw only red - wlr_box boxe = {0, 0, INT16_MAX, INT16_MAX}; + CBox boxe = {0, 0, INT16_MAX, INT16_MAX}; g_pHyprOpenGL->renderRect(&boxe, CColor(1.0, 0.2, 0.2, 1.0)); return; } @@ -531,6 +696,15 @@ void CHyprRenderer::renderAllClientsForWorkspace(CMonitor* pMonitor, CWorkspace* if (!g_pHyprOpenGL->m_RenderData.pCurrentMonData->blurFBShouldRender) setOccludedForBackLayers(g_pHyprOpenGL->m_RenderData.damage, pWorkspace); + g_pHyprOpenGL->blend(false); + if (!canSkipBackBufferClear(pMonitor)) { + if (*PRENDERTEX /* inverted cfg flag */) + g_pHyprOpenGL->clear(CColor(*PBACKGROUNDCOLOR)); + else + g_pHyprOpenGL->clearWithTex(); // will apply the hypr "wallpaper" + } + g_pHyprOpenGL->blend(true); + for (auto& ls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]) { renderLayer(ls.get(), pMonitor, time); } @@ -544,11 +718,15 @@ void CHyprRenderer::renderAllClientsForWorkspace(CMonitor* pMonitor, CWorkspace* // pre window pass g_pHyprOpenGL->preWindowPass(); + setOccludedForMainWorkspace(g_pHyprOpenGL->m_RenderData.damage, pWorkspace); + if (pWorkspace->m_bHasFullscreenWindow) renderWorkspaceWindowsFullscreen(pMonitor, pWorkspace, time); else renderWorkspaceWindows(pMonitor, pWorkspace, time); + g_pHyprOpenGL->m_RenderData.damage = preOccludedDamage; + g_pHyprOpenGL->m_RenderData.renderModif = {}; // and then special @@ -558,12 +736,12 @@ void CHyprRenderer::renderAllClientsForWorkspace(CMonitor* pMonitor, CWorkspace* const bool ANIMOUT = !pMonitor->specialWorkspaceID; if (*PDIMSPECIAL != 0.f) { - wlr_box monbox = {translate.x, translate.y, pMonitor->vecTransformedSize.x * scale, pMonitor->vecTransformedSize.y * scale}; + CBox monbox = {translate.x, translate.y, pMonitor->vecTransformedSize.x * scale, pMonitor->vecTransformedSize.y * scale}; g_pHyprOpenGL->renderRect(&monbox, CColor(0, 0, 0, *PDIMSPECIAL * (ANIMOUT ? (1.0 - SPECIALANIMPROGRS) : SPECIALANIMPROGRS))); } if (*PBLURSPECIAL && *PBLUR) { - wlr_box monbox = {translate.x, translate.y, pMonitor->vecTransformedSize.x * scale, pMonitor->vecTransformedSize.y * scale}; + CBox monbox = {translate.x, translate.y, pMonitor->vecTransformedSize.x * scale, pMonitor->vecTransformedSize.y * scale}; g_pHyprOpenGL->renderRectWithBlur(&monbox, CColor(0, 0, 0, 0), 0, (ANIMOUT ? (1.0 - SPECIALANIMPROGRS) : SPECIALANIMPROGRS)); } @@ -571,11 +749,18 @@ void CHyprRenderer::renderAllClientsForWorkspace(CMonitor* pMonitor, CWorkspace* } } + // special + for (auto& ws : g_pCompositor->m_vWorkspaces) { + if (ws->m_iMonitorID == pMonitor->ID && ws->m_fAlpha.fl() > 0.f && ws->m_bIsSpecialWorkspace) + renderWorkspaceWindows(pMonitor, ws.get(), time); + } + + // pinned always above for (auto& w : g_pCompositor->m_vWindows) { if (w->isHidden() && !w->m_bIsMapped && !w->m_bFadingOut) continue; - if (!g_pCompositor->isWorkspaceSpecial(w->m_iWorkspaceID)) + if (!w->m_bPinned || !w->m_bIsFloating) continue; if (!shouldRenderWindow(w.get(), pMonitor, pWorkspace)) @@ -615,7 +800,7 @@ void CHyprRenderer::renderLockscreen(CMonitor* pMonitor, timespec* now) { if (!PSLS) { // locked with no surface, fill with red - wlr_box boxe = {0, 0, INT16_MAX, INT16_MAX}; + CBox boxe = {0, 0, INT16_MAX, INT16_MAX}; g_pHyprOpenGL->renderRect(&boxe, CColor(1.0, 0.2, 0.2, 1.0)); } else { renderSessionLockSurface(PSLS, pMonitor, now); @@ -657,11 +842,12 @@ void CHyprRenderer::calculateUVForSurface(CWindow* pWindow, wlr_surface* pSurfac if (!main || !pWindow) return; - wlr_box geom; - wlr_xdg_surface_get_geometry(pWindow->m_uSurface.xdg, &geom); + CBox geom; + wlr_xdg_surface_get_geometry(pWindow->m_uSurface.xdg, geom.pWlr()); + geom.applyFromWlr(); // ignore X and Y, adjust uv - if (geom.x != 0 || geom.y != 0 || geom.width > pWindow->m_vRealSize.goalv().x || geom.height > pWindow->m_vRealSize.goalv().y) { + if (geom.x != 0 || geom.y != 0 || geom.width > pWindow->m_vRealSize.vec().x || geom.height > pWindow->m_vRealSize.vec().y) { const auto XPERC = (double)geom.x / (double)pSurface->current.width; const auto YPERC = (double)geom.y / (double)pSurface->current.height; const auto WPERC = (double)(geom.x + geom.width) / (double)pSurface->current.width; @@ -671,10 +857,16 @@ void CHyprRenderer::calculateUVForSurface(CWindow* pWindow, wlr_surface* pSurfac uvBR = uvBR - Vector2D(1.0 - WPERC * (uvBR.x - uvTL.x), 1.0 - HPERC * (uvBR.y - uvTL.y)); uvTL = uvTL + TOADDTL; - if (geom.width > pWindow->m_vRealSize.goalv().x || geom.height > pWindow->m_vRealSize.goalv().y) { - uvBR.x = uvBR.x * (pWindow->m_vRealSize.goalv().x / geom.width); - uvBR.y = uvBR.y * (pWindow->m_vRealSize.goalv().y / geom.height); - } + // TODO: make this passed to the func. Might break in the future. + auto maxSize = pWindow->m_vRealSize.vec(); + + if (pWindow->m_pWLSurface.small() && !pWindow->m_pWLSurface.m_bFillIgnoreSmall) + maxSize = pWindow->m_pWLSurface.getViewporterCorrectedSize(); + + if (geom.width > maxSize.x) + uvBR.x = uvBR.x * (maxSize.x / geom.width); + if (geom.height > maxSize.y) + uvBR.y = uvBR.y * (maxSize.y / geom.height); } g_pHyprOpenGL->m_RenderData.primarySurfaceUVTopLeft = uvTL; @@ -702,51 +894,12 @@ bool CHyprRenderer::attemptDirectScanout(CMonitor* pMonitor) { if (!wlr_output_is_direct_scanout_allowed(pMonitor->output)) return false; - const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(pMonitor->activeWorkspace); - - if (!PWORKSPACE || !PWORKSPACE->m_bHasFullscreenWindow || g_pInputManager->m_sDrag.drag || g_pCompositor->m_sSeat.exclusiveClient || pMonitor->specialWorkspaceID) - return false; - - const auto PCANDIDATE = g_pCompositor->getFullscreenWindowOnWorkspace(PWORKSPACE->m_iID); + const auto PCANDIDATE = pMonitor->solitaryClient; if (!PCANDIDATE) - return false; // ???? - - if (PCANDIDATE->m_fAlpha.fl() != 1.f || PCANDIDATE->m_fActiveInactiveAlpha.fl() != 1.f || PWORKSPACE->m_fAlpha.fl() != 1.f) return false; - if (PCANDIDATE->m_vRealSize.vec() != pMonitor->vecSize || PCANDIDATE->m_vRealPosition.vec() != pMonitor->vecPosition || PCANDIDATE->m_vRealPosition.isBeingAnimated() || - PCANDIDATE->m_vRealSize.isBeingAnimated()) - return false; - - if (!pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY].empty()) - return false; - - for (auto& topls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) { - if (topls->alpha.fl() != 0.f) - return false; - } - - // check if it did not open any subsurfaces or shit - int surfaceCount = 0; - if (PCANDIDATE->m_bIsX11) { - surfaceCount = 1; - - // check opaque - if (PCANDIDATE->m_uSurface.xwayland->has_alpha) - return false; - } else { - wlr_xdg_surface_for_each_surface(PCANDIDATE->m_uSurface.xdg, countSubsurfacesIter, &surfaceCount); - wlr_xdg_surface_for_each_popup_surface(PCANDIDATE->m_uSurface.xdg, countSubsurfacesIter, &surfaceCount); - - if (!PCANDIDATE->m_uSurface.xdg->surface->opaque) - return false; - } - - if (surfaceCount != 1) - return false; - - const auto PSURFACE = PCANDIDATE->m_pWLSurface.wlr(); + const auto PSURFACE = g_pXWaylandManager->getWindowSurface(PCANDIDATE); if (!PSURFACE || PSURFACE->current.scale != pMonitor->output->scale || PSURFACE->current.transform != pMonitor->output->transform) return false; @@ -754,14 +907,13 @@ bool CHyprRenderer::attemptDirectScanout(CMonitor* pMonitor) { // finally, we should be GTG. wlr_output_attach_buffer(pMonitor->output, &PSURFACE->buffer->base); - if (!wlr_output_test(pMonitor->output)) { + if (!wlr_output_test(pMonitor->output)) return false; - } timespec now; clock_gettime(CLOCK_MONOTONIC, &now); wlr_surface_send_frame_done(PSURFACE, &now); - wlr_presentation_surface_scanned_out_on_output(g_pCompositor->m_sWLRPresentation, PSURFACE, pMonitor->output); + wlr_presentation_surface_scanned_out_on_output(PSURFACE, pMonitor->output); if (wlr_output_commit(pMonitor->output)) { if (!m_pLastScanout) { @@ -777,8 +929,8 @@ bool CHyprRenderer::attemptDirectScanout(CMonitor* pMonitor) { } void CHyprRenderer::renderMonitor(CMonitor* pMonitor) { - static std::chrono::high_resolution_clock::time_point startRender = std::chrono::high_resolution_clock::now(); - static std::chrono::high_resolution_clock::time_point startRenderOverlay = std::chrono::high_resolution_clock::now(); + static std::chrono::high_resolution_clock::time_point renderStart = std::chrono::high_resolution_clock::now(); + static std::chrono::high_resolution_clock::time_point renderStartOverlay = std::chrono::high_resolution_clock::now(); static std::chrono::high_resolution_clock::time_point endRenderOverlay = std::chrono::high_resolution_clock::now(); static auto* const PDEBUGOVERLAY = &g_pConfigManager->getConfigValuePtr("debug:overlay")->intValue; @@ -787,9 +939,9 @@ void CHyprRenderer::renderMonitor(CMonitor* pMonitor) { static auto* const PNODIRECTSCANOUT = &g_pConfigManager->getConfigValuePtr("misc:no_direct_scanout")->intValue; static auto* const PVFR = &g_pConfigManager->getConfigValuePtr("misc:vfr")->intValue; static auto* const PZOOMFACTOR = &g_pConfigManager->getConfigValuePtr("misc:cursor_zoom_factor")->floatValue; - static auto* const PRENDERTEX = &g_pConfigManager->getConfigValuePtr("misc:disable_hyprland_logo")->intValue; - static auto* const PBACKGROUNDCOLOR = &g_pConfigManager->getConfigValuePtr("misc:background_color")->intValue; static auto* const PANIMENABLED = &g_pConfigManager->getConfigValuePtr("animations:enabled")->intValue; + static auto* const PFIRSTLAUNCHANIM = &g_pConfigManager->getConfigValuePtr("animations:first_launch_animation")->intValue; + static auto* const PTEARINGENABLED = &g_pConfigManager->getConfigValuePtr("general:allow_tearing")->intValue; static int damageBlinkCleanup = 0; // because double-buffered @@ -797,7 +949,7 @@ void CHyprRenderer::renderMonitor(CMonitor* pMonitor) { damageBlinkCleanup = 0; static bool firstLaunch = true; - static bool firstLaunchAnimActive = true; + static bool firstLaunchAnimActive = *PFIRSTLAUNCHANIM; float zoomInFactorFirstLaunch = 1.f; @@ -818,7 +970,7 @@ void CHyprRenderer::renderMonitor(CMonitor* pMonitor) { firstLaunchAnimActive = false; } - startRender = std::chrono::high_resolution_clock::now(); + renderStart = std::chrono::high_resolution_clock::now(); if (*PDEBUGOVERLAY == 1) { g_pDebugOverlay->frameData(pMonitor); @@ -876,8 +1028,31 @@ void CHyprRenderer::renderMonitor(CMonitor* pMonitor) { } } - // Direct scanout first - if (!*PNODIRECTSCANOUT) { + // tearing and DS first + bool shouldTear = false; + if (pMonitor->tearingState.nextRenderTorn) { + pMonitor->tearingState.nextRenderTorn = false; + + if (!*PTEARINGENABLED) { + Debug::log(WARN, "Tearing commit requested but the master switch general:allow_tearing is off, ignoring"); + return; + } + + if (g_pHyprOpenGL->m_RenderData.mouseZoomFactor != 1.0) { + Debug::log(WARN, "Tearing commit requested but scale factor is not 1, ignoring"); + return; + } + + if (!pMonitor->tearingState.canTear) { + Debug::log(WARN, "Tearing commit requested but monitor doesn't support it, ignoring"); + return; + } + + if (pMonitor->solitaryClient) + shouldTear = true; + } + + if (!*PNODIRECTSCANOUT && !shouldTear) { if (attemptDirectScanout(pMonitor)) { return; } else if (m_pLastScanout) { @@ -886,6 +1061,11 @@ void CHyprRenderer::renderMonitor(CMonitor* pMonitor) { } } + if (pMonitor->tearingState.activelyTearing != shouldTear) { + // change of state + pMonitor->tearingState.activelyTearing = shouldTear; + } + EMIT_HOOK_EVENT("preRender", pMonitor); timespec now; @@ -894,7 +1074,6 @@ void CHyprRenderer::renderMonitor(CMonitor* pMonitor) { // check the damage CRegion damage; bool hasChanged = pMonitor->output->needs_frame || pixman_region32_not_empty(&pMonitor->damage.current); - int bufferAge; if (!hasChanged && *PDAMAGETRACKINGMODE != DAMAGE_TRACKING_NONE && pMonitor->forceFullFrames == 0 && damageBlinkCleanup == 0) return; @@ -910,16 +1089,7 @@ void CHyprRenderer::renderMonitor(CMonitor* pMonitor) { if (UNLOCK_SC) wlr_output_lock_software_cursors(pMonitor->output, true); - if (!wlr_output_attach_render(pMonitor->output, &bufferAge)) { - Debug::log(ERR, "Couldn't attach render to display {} ???", pMonitor->szName); - - if (UNLOCK_SC) - wlr_output_lock_software_cursors(pMonitor->output, false); - - return; - } - - wlr_damage_ring_get_buffer_damage(&pMonitor->damage, bufferAge, damage.pixman()); + wlr_damage_ring_get_buffer_damage(&pMonitor->damage, m_iLastBufferAge, damage.pixman()); pMonitor->renderingActive = true; @@ -965,64 +1135,6 @@ void CHyprRenderer::renderMonitor(CMonitor* pMonitor) { TRACY_GPU_ZONE("Render"); - g_pHyprOpenGL->begin(pMonitor, &damage); - - EMIT_HOOK_EVENT("render", RENDER_BEGIN); - - if (pMonitor->isMirror()) { - g_pHyprOpenGL->blend(false); - g_pHyprOpenGL->renderMirrored(); - g_pHyprOpenGL->blend(true); - EMIT_HOOK_EVENT("render", RENDER_POST_MIRROR); - } else { - g_pHyprOpenGL->blend(false); - if (!canSkipBackBufferClear(pMonitor)) { - if (*PRENDERTEX /* inverted cfg flag */) - g_pHyprOpenGL->clear(CColor(*PBACKGROUNDCOLOR)); - else - g_pHyprOpenGL->clearWithTex(); // will apply the hypr "wallpaper" - } - g_pHyprOpenGL->blend(true); - - wlr_box renderBox = {0, 0, (int)pMonitor->vecPixelSize.x, (int)pMonitor->vecPixelSize.y}; - renderWorkspace(pMonitor, g_pCompositor->getWorkspaceByID(pMonitor->activeWorkspace), &now, renderBox); - - renderLockscreen(pMonitor, &now); - - if (pMonitor == g_pCompositor->m_pLastMonitor) { - g_pHyprNotificationOverlay->draw(pMonitor); - g_pHyprError->draw(); - } - - // for drawing the debug overlay - if (pMonitor == g_pCompositor->m_vMonitors.front().get() && *PDEBUGOVERLAY == 1) { - startRenderOverlay = std::chrono::high_resolution_clock::now(); - g_pDebugOverlay->draw(); - endRenderOverlay = std::chrono::high_resolution_clock::now(); - } - - if (*PDAMAGEBLINK && damageBlinkCleanup == 0) { - wlr_box monrect = {0, 0, pMonitor->vecTransformedSize.x, pMonitor->vecTransformedSize.y}; - g_pHyprOpenGL->renderRect(&monrect, CColor(1.0, 0.0, 1.0, 100.0 / 255.0), 0); - damageBlinkCleanup = 1; - } else if (*PDAMAGEBLINK) { - damageBlinkCleanup++; - if (damageBlinkCleanup > 3) - damageBlinkCleanup = 0; - } - - if (wlr_renderer_begin(g_pCompositor->m_sWLRRenderer, pMonitor->vecPixelSize.x, pMonitor->vecPixelSize.y)) { - TRACY_GPU_ZONE("RenderCursor"); - if (pMonitor == g_pCompositor->getMonitorFromCursor() && *PZOOMFACTOR != 1.f) { - wlr_output_lock_software_cursors(pMonitor->output, true); - wlr_output_render_software_cursors(pMonitor->output, NULL); - wlr_output_lock_software_cursors(pMonitor->output, false); - } else - wlr_output_render_software_cursors(pMonitor->output, NULL); - wlr_renderer_end(g_pCompositor->m_sWLRRenderer); - } - } - if (pMonitor == g_pCompositor->getMonitorFromCursor()) g_pHyprOpenGL->m_RenderData.mouseZoomFactor = std::clamp(*PZOOMFACTOR, 1.f, INFINITY); else @@ -1034,9 +1146,76 @@ void CHyprRenderer::renderMonitor(CMonitor* pMonitor) { g_pHyprOpenGL->m_RenderData.useNearestNeighbor = false; } + if (!beginRender(pMonitor, damage, RENDER_MODE_NORMAL)) { + Debug::log(ERR, "renderer: couldn't beginRender()!"); + + if (UNLOCK_SC) + wlr_output_lock_software_cursors(pMonitor->output, false); + + return; + } + + EMIT_HOOK_EVENT("render", RENDER_BEGIN); + + bool renderCursor = true; + + if (!pMonitor->solitaryClient) { + if (pMonitor->isMirror()) { + g_pHyprOpenGL->blend(false); + g_pHyprOpenGL->renderMirrored(); + g_pHyprOpenGL->blend(true); + EMIT_HOOK_EVENT("render", RENDER_POST_MIRROR); + renderCursor = false; + } else { + CBox renderBox = {0, 0, (int)pMonitor->vecPixelSize.x, (int)pMonitor->vecPixelSize.y}; + renderWorkspace(pMonitor, g_pCompositor->getWorkspaceByID(pMonitor->activeWorkspace), &now, renderBox); + + renderLockscreen(pMonitor, &now); + + if (pMonitor == g_pCompositor->m_pLastMonitor) { + g_pHyprNotificationOverlay->draw(pMonitor); + g_pHyprError->draw(); + } + + // for drawing the debug overlay + if (pMonitor == g_pCompositor->m_vMonitors.front().get() && *PDEBUGOVERLAY == 1) { + renderStartOverlay = std::chrono::high_resolution_clock::now(); + g_pDebugOverlay->draw(); + endRenderOverlay = std::chrono::high_resolution_clock::now(); + } + + if (*PDAMAGEBLINK && damageBlinkCleanup == 0) { + CBox monrect = {0, 0, pMonitor->vecTransformedSize.x, pMonitor->vecTransformedSize.y}; + g_pHyprOpenGL->renderRect(&monrect, CColor(1.0, 0.0, 1.0, 100.0 / 255.0), 0); + damageBlinkCleanup = 1; + } else if (*PDAMAGEBLINK) { + damageBlinkCleanup++; + if (damageBlinkCleanup > 3) + damageBlinkCleanup = 0; + } + } + } else { + g_pHyprRenderer->renderWindow(pMonitor->solitaryClient, pMonitor, &now, false, RENDER_PASS_MAIN /* solitary = no popups */); + } + + renderCursor = renderCursor && shouldRenderCursor(); + + if (renderCursor) { + TRACY_GPU_ZONE("RenderCursor"); + + bool lockSoftware = pMonitor == g_pCompositor->getMonitorFromCursor() && *PZOOMFACTOR != 1.f; + + if (lockSoftware) { + wlr_output_lock_software_cursors(pMonitor->output, true); + g_pHyprRenderer->renderSoftwareCursors(pMonitor, g_pHyprOpenGL->m_RenderData.damage); + wlr_output_lock_software_cursors(pMonitor->output, false); + } else + g_pHyprRenderer->renderSoftwareCursors(pMonitor, g_pHyprOpenGL->m_RenderData.damage); + } + EMIT_HOOK_EVENT("render", RENDER_LAST_MOMENT); - g_pHyprOpenGL->end(); + endRender(); TRACY_GPU_COLLECT; @@ -1052,8 +1231,6 @@ void CHyprRenderer::renderMonitor(CMonitor* pMonitor) { if (*PDAMAGEBLINK) frameDamage.add(damage); - //wlr_output_set_damage(pMonitor->output, frameDamage.pixman()); - if (!pMonitor->mirrors.empty()) g_pHyprRenderer->damageMirrorsWith(pMonitor, frameDamage); @@ -1061,13 +1238,19 @@ void CHyprRenderer::renderMonitor(CMonitor* pMonitor) { EMIT_HOOK_EVENT("render", RENDER_POST); + pMonitor->output->pending.tearing_page_flip = shouldTear; + if (!wlr_output_commit(pMonitor->output)) { + if (UNLOCK_SC) wlr_output_lock_software_cursors(pMonitor->output, false); return; } + if (shouldTear) + pMonitor->tearingState.busy = true; + wlr_damage_ring_rotate(&pMonitor->damage); if (UNLOCK_SC) @@ -1078,12 +1261,12 @@ void CHyprRenderer::renderMonitor(CMonitor* pMonitor) { pMonitor->pendingFrame = false; - const float µs = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - startRender).count() / 1000.f; + const float µs = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - renderStart).count() / 1000.f; g_pDebugOverlay->renderData(pMonitor, µs); if (*PDEBUGOVERLAY == 1) { if (pMonitor == g_pCompositor->m_vMonitors.front().get()) { - const float µsNoOverlay = µs - std::chrono::duration_cast(endRenderOverlay - startRenderOverlay).count() / 1000.f; + const float µsNoOverlay = µs - std::chrono::duration_cast(endRenderOverlay - renderStartOverlay).count() / 1000.f; g_pDebugOverlay->renderDataNoOverlay(pMonitor, µsNoOverlay); } else { g_pDebugOverlay->renderDataNoOverlay(pMonitor, µs); @@ -1091,7 +1274,7 @@ void CHyprRenderer::renderMonitor(CMonitor* pMonitor) { } } -void CHyprRenderer::renderWorkspace(CMonitor* pMonitor, CWorkspace* pWorkspace, timespec* now, const wlr_box& geometry) { +void CHyprRenderer::renderWorkspace(CMonitor* pMonitor, CWorkspace* pWorkspace, timespec* now, const CBox& geometry) { Vector2D translate = {geometry.x, geometry.y}; float scale = (float)geometry.width / pMonitor->vecPixelSize.x; @@ -1177,13 +1360,22 @@ void CHyprRenderer::outputMgrApplyTest(wlr_output_configuration_v1* config, bool break; } - if (!test) + if (!test) { g_pConfigManager->m_bWantsMonitorReload = true; // for monitor keywords + // if everything is disabled, performMonitorReload won't be called from renderMonitor + bool allDisabled = std::all_of(g_pCompositor->m_vMonitors.begin(), g_pCompositor->m_vMonitors.end(), + [](const auto m) { return !m->m_bEnabled || g_pCompositor->m_pUnsafeOutput == m.get(); }); + if (allDisabled) { + Debug::log(LOG, "OutputMgr apply: All monitors disabled; performing monitor reload."); + g_pConfigManager->performMonitorReload(); + } + } if (ok) wlr_output_configuration_v1_send_succeeded(config); else wlr_output_configuration_v1_send_failed(config); + wlr_output_configuration_v1_destroy(config); Debug::log(LOG, "OutputMgr Applied/Tested."); @@ -1191,7 +1383,7 @@ void CHyprRenderer::outputMgrApplyTest(wlr_output_configuration_v1* config, bool // taken from Sway. // this is just too much of a spaghetti for me to understand -void apply_exclusive(struct wlr_box* usable_area, uint32_t anchor, int32_t exclusive, int32_t margin_top, int32_t margin_right, int32_t margin_bottom, int32_t margin_left) { +static void applyExclusive(wlr_box& usableArea, uint32_t anchor, int32_t exclusive, int32_t marginTop, int32_t marginRight, int32_t marginBottom, int32_t marginLeft) { if (exclusive <= 0) { return; } @@ -1206,33 +1398,33 @@ void apply_exclusive(struct wlr_box* usable_area, uint32_t anchor, int32_t exclu { .singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP, .anchor_triplet = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP, - .positive_axis = &usable_area->y, - .negative_axis = &usable_area->height, - .margin = margin_top, + .positive_axis = &usableArea.y, + .negative_axis = &usableArea.height, + .margin = marginTop, }, // Bottom { .singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM, .anchor_triplet = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM, .positive_axis = NULL, - .negative_axis = &usable_area->height, - .margin = margin_bottom, + .negative_axis = &usableArea.height, + .margin = marginBottom, }, // Left { .singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT, .anchor_triplet = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM, - .positive_axis = &usable_area->x, - .negative_axis = &usable_area->width, - .margin = margin_left, + .positive_axis = &usableArea.x, + .negative_axis = &usableArea.width, + .margin = marginLeft, }, // Right { .singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT, .anchor_triplet = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM, .positive_axis = NULL, - .negative_axis = &usable_area->width, - .margin = margin_right, + .negative_axis = &usableArea.width, + .margin = marginRight, }, }; for (size_t i = 0; i < sizeof(edges) / sizeof(edges[0]); ++i) { @@ -1248,8 +1440,8 @@ void apply_exclusive(struct wlr_box* usable_area, uint32_t anchor, int32_t exclu } } -void CHyprRenderer::arrangeLayerArray(CMonitor* pMonitor, const std::vector>& layerSurfaces, bool exclusiveZone, wlr_box* usableArea) { - wlr_box full_area = {pMonitor->vecPosition.x, pMonitor->vecPosition.y, pMonitor->vecSize.x, pMonitor->vecSize.y}; +void CHyprRenderer::arrangeLayerArray(CMonitor* pMonitor, const std::vector>& layerSurfaces, bool exclusiveZone, CBox* usableArea) { + CBox full_area = {pMonitor->vecPosition.x, pMonitor->vecPosition.y, pMonitor->vecSize.x, pMonitor->vecSize.y}; for (auto& ls : layerSurfaces) { if (ls->fadingOut || ls->readyToDelete || !ls->layerSurface || ls->noProcess) @@ -1257,18 +1449,18 @@ void CHyprRenderer::arrangeLayerArray(CMonitor* pMonitor, const std::vectorlayerSurface; const auto PSTATE = &PLAYER->current; - if (exclusiveZone != (PSTATE->exclusive_zone > 0)) { + if (exclusiveZone != (PSTATE->exclusive_zone > 0)) continue; - } - wlr_box bounds; - if (PSTATE->exclusive_zone == -1) { + CBox bounds; + if (PSTATE->exclusive_zone == -1) bounds = full_area; - } else { + else bounds = *usableArea; - } - wlr_box box = {.width = PSTATE->desired_width, .height = PSTATE->desired_height}; + const Vector2D OLDSIZE = {ls->geometry.width, ls->geometry.height}; + + CBox box = {0, 0, PSTATE->desired_width, PSTATE->desired_height}; // Horizontal axis const uint32_t both_horiz = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; if (box.width == 0) { @@ -1323,12 +1515,12 @@ void CHyprRenderer::arrangeLayerArray(CMonitor* pMonitor, const std::vectorgeometry = box; - apply_exclusive(usableArea, PSTATE->anchor, PSTATE->exclusive_zone, PSTATE->margin.top, PSTATE->margin.right, PSTATE->margin.bottom, PSTATE->margin.left); + applyExclusive(*usableArea->pWlr(), PSTATE->anchor, PSTATE->exclusive_zone, PSTATE->margin.top, PSTATE->margin.right, PSTATE->margin.bottom, PSTATE->margin.left); - wlr_layer_surface_v1_configure(ls->layerSurface, box.width, box.height); + usableArea->applyFromWlr(); - Debug::log(LOG, "LayerSurface {:x} arranged: x: {} y: {} w: {} h: {} with margins: t: {} l: {} r: {} b: {}", (uintptr_t)&ls, box.x, box.y, box.width, box.height, - PSTATE->margin.top, PSTATE->margin.left, PSTATE->margin.right, PSTATE->margin.bottom); + if (Vector2D{box.width, box.height} != OLDSIZE) + wlr_layer_surface_v1_configure(ls->layerSurface, box.width, box.height); } } @@ -1342,7 +1534,7 @@ void CHyprRenderer::arrangeLayersForMonitor(const int& monitor) { PMONITOR->vecReservedBottomRight = Vector2D(); PMONITOR->vecReservedTopLeft = Vector2D(); - wlr_box usableArea = {PMONITOR->vecPosition.x, PMONITOR->vecPosition.y, PMONITOR->vecSize.x, PMONITOR->vecSize.y}; + CBox usableArea = {PMONITOR->vecPosition.x, PMONITOR->vecPosition.y, PMONITOR->vecSize.x, PMONITOR->vecSize.y}; for (auto& la : PMONITOR->m_aLayerSurfaceLayers) arrangeLayerArray(PMONITOR, la, true, &usableArea); @@ -1367,8 +1559,6 @@ void CHyprRenderer::arrangeLayersForMonitor(const int& monitor) { damageMonitor(PMONITOR); g_pLayoutManager->getCurrentLayout()->recalculateMonitor(monitor); - - Debug::log(LOG, "Monitor {} layers arranged: reserved: {:5j} {:5j}", PMONITOR->szName, PMONITOR->vecReservedTopLeft, PMONITOR->vecReservedBottomRight); } void CHyprRenderer::damageSurface(wlr_surface* pSurface, double x, double y, double scale) { @@ -1378,6 +1568,13 @@ void CHyprRenderer::damageSurface(wlr_surface* pSurface, double x, double y, dou if (g_pCompositor->m_bUnsafeState) return; + auto* const PSURFACE = CWLSurface::surfaceFromWlr(pSurface); + if (PSURFACE && PSURFACE->m_pOwner && PSURFACE->small()) { + const auto CORRECTION = PSURFACE->correctSmallVec(); + x += CORRECTION.x; + y += CORRECTION.y; + } + CRegion damageBox; wlr_surface_get_effective_damage(pSurface, damageBox.pixman()); if (scale != 1.0) @@ -1419,10 +1616,10 @@ void CHyprRenderer::damageWindow(CWindow* pWindow) { if (g_pCompositor->m_bUnsafeState) return; - wlr_box damageBox = pWindow->getFullWindowBoundingBox(); + CBox damageBox = pWindow->getFullWindowBoundingBox(); for (auto& m : g_pCompositor->m_vMonitors) { - wlr_box fixedDamageBox = {damageBox.x - m->vecPosition.x, damageBox.y - m->vecPosition.y, damageBox.width, damageBox.height}; - scaleBox(&fixedDamageBox, m->scale); + CBox fixedDamageBox = {damageBox.x - m->vecPosition.x, damageBox.y - m->vecPosition.y, damageBox.width, damageBox.height}; + fixedDamageBox.scale(m->scale); m->addDamage(&fixedDamageBox); } @@ -1439,7 +1636,7 @@ void CHyprRenderer::damageMonitor(CMonitor* pMonitor) { if (g_pCompositor->m_bUnsafeState || pMonitor->isMirror()) return; - wlr_box damageBox = {0, 0, INT16_MAX, INT16_MAX}; + CBox damageBox = {0, 0, INT16_MAX, INT16_MAX}; pMonitor->addDamage(&damageBox); static auto* const PLOGDAMAGE = &g_pConfigManager->getConfigValuePtr("debug:log_damage")->intValue; @@ -1448,7 +1645,7 @@ void CHyprRenderer::damageMonitor(CMonitor* pMonitor) { Debug::log(LOG, "Damage: Monitor {}", pMonitor->szName); } -void CHyprRenderer::damageBox(wlr_box* pBox) { +void CHyprRenderer::damageBox(CBox* pBox) { if (g_pCompositor->m_bUnsafeState) return; @@ -1456,8 +1653,8 @@ void CHyprRenderer::damageBox(wlr_box* pBox) { if (m->isMirror()) continue; // don't damage mirrors traditionally - wlr_box damageBox = {pBox->x - m->vecPosition.x, pBox->y - m->vecPosition.y, pBox->width, pBox->height}; - scaleBox(&damageBox, m->scale); + CBox damageBox = {pBox->x - m->vecPosition.x, pBox->y - m->vecPosition.y, pBox->width, pBox->height}; + damageBox.scale(m->scale); m->addDamage(&damageBox); } @@ -1468,7 +1665,7 @@ void CHyprRenderer::damageBox(wlr_box* pBox) { } void CHyprRenderer::damageBox(const int& x, const int& y, const int& w, const int& h) { - wlr_box box = {x, y, w, h}; + CBox box = {x, y, w, h}; damageBox(&box); } @@ -1501,8 +1698,8 @@ void CHyprRenderer::renderDragIcon(CMonitor* pMonitor, timespec* time) { wlr_surface_for_each_surface(g_pInputManager->m_sDrag.dragIcon->surface, renderSurface, &renderdata); - wlr_box box = {g_pInputManager->m_sDrag.pos.x - 2, g_pInputManager->m_sDrag.pos.y - 2, g_pInputManager->m_sDrag.dragIcon->surface->current.width + 4, - g_pInputManager->m_sDrag.dragIcon->surface->current.height + 4}; + CBox box = {g_pInputManager->m_sDrag.pos.x - 2, g_pInputManager->m_sDrag.pos.y - 2, g_pInputManager->m_sDrag.dragIcon->surface->current.width + 4, + g_pInputManager->m_sDrag.dragIcon->surface->current.height + 4}; g_pHyprRenderer->damageBox(&box); } @@ -1519,13 +1716,14 @@ DAMAGETRACKINGMODES CHyprRenderer::damageTrackingModeFromStr(const std::string& bool CHyprRenderer::applyMonitorRule(CMonitor* pMonitor, SMonitorRule* pMonitorRule, bool force) { + static auto* const PDISABLESCALECHECKS = &g_pConfigManager->getConfigValuePtr("debug:disable_scale_checks")->intValue; + Debug::log(LOG, "Applying monitor rule for {}", pMonitor->szName); pMonitor->activeMonitorRule = *pMonitorRule; // if it's disabled, disable and ignore if (pMonitorRule->disabled) { - if (pMonitor->m_bEnabled) pMonitor->onDisconnect(); @@ -1545,7 +1743,7 @@ bool CHyprRenderer::applyMonitorRule(CMonitor* pMonitor, SMonitorRule* pMonitorR // Check if the rule isn't already applied // TODO: clean this up lol if (!force && DELTALESSTHAN(pMonitor->vecPixelSize.x, pMonitorRule->resolution.x, 1) && DELTALESSTHAN(pMonitor->vecPixelSize.y, pMonitorRule->resolution.y, 1) && - DELTALESSTHAN(pMonitor->refreshRate, pMonitorRule->refreshRate, 1) && pMonitor->scale == pMonitorRule->scale && + DELTALESSTHAN(pMonitor->refreshRate, pMonitorRule->refreshRate, 1) && pMonitor->setScale == pMonitorRule->scale && ((DELTALESSTHAN(pMonitor->vecPosition.x, pMonitorRule->offset.x, 1) && DELTALESSTHAN(pMonitor->vecPosition.y, pMonitorRule->offset.y, 1)) || pMonitorRule->offset == Vector2D(-INT32_MAX, -INT32_MAX)) && pMonitor->transform == pMonitorRule->transform && pMonitorRule->enable10bit == pMonitor->enabled10bit && @@ -1557,16 +1755,19 @@ bool CHyprRenderer::applyMonitorRule(CMonitor* pMonitor, SMonitorRule* pMonitorR // Needed in case we are switching from a custom modeline to a standard mode pMonitor->customDrmMode = {}; + bool autoScale = false; if (pMonitorRule->scale > 0.1) { - wlr_output_set_scale(pMonitor->output, pMonitorRule->scale); pMonitor->scale = pMonitorRule->scale; } else { + autoScale = true; const auto DEFAULTSCALE = pMonitor->getDefaultScale(); - wlr_output_set_scale(pMonitor->output, DEFAULTSCALE); - pMonitor->scale = DEFAULTSCALE; + pMonitor->scale = DEFAULTSCALE; } + wlr_output_set_scale(pMonitor->output, pMonitor->scale); + pMonitor->setScale = pMonitor->scale; + wlr_output_set_transform(pMonitor->output, pMonitorRule->transform); pMonitor->transform = pMonitorRule->transform; @@ -1783,6 +1984,66 @@ bool CHyprRenderer::applyMonitorRule(CMonitor* pMonitor, SMonitorRule* pMonitorR pMonitor->vecPixelSize = pMonitor->vecSize; + Vector2D logicalSize = pMonitor->vecPixelSize / pMonitor->scale; + if (!*PDISABLESCALECHECKS && (logicalSize.x != std::round(logicalSize.x) || logicalSize.y != std::round(logicalSize.y))) { + // invalid scale, will produce fractional pixels. + // find the nearest valid. + + float searchScale = std::round(pMonitor->scale * 120.0); + bool found = false; + + double scaleZero = searchScale / 120.0; + + Vector2D logicalZero = pMonitor->vecPixelSize / scaleZero; + if (logicalZero == logicalZero.round()) { + pMonitor->scale = scaleZero; + wlr_output_set_scale(pMonitor->output, pMonitor->scale); + } else { + for (size_t i = 1; i < 90; ++i) { + double scaleUp = (searchScale + i) / 120.0; + double scaleDown = (searchScale - i) / 120.0; + + Vector2D logicalUp = pMonitor->vecPixelSize / scaleUp; + Vector2D logicalDown = pMonitor->vecPixelSize / scaleDown; + + if (logicalUp == logicalUp.round()) { + found = true; + searchScale = scaleUp; + break; + } + if (logicalDown == logicalDown.round()) { + found = true; + searchScale = scaleDown; + break; + } + } + + if (!found) { + if (autoScale) + pMonitor->scale = std::round(scaleZero); + else { + Debug::log(ERR, "Invalid scale passed to monitor, {} failed to find a clean divisor", pMonitor->scale); + g_pConfigManager->addParseError("Invalid scale passed to monitor " + pMonitor->szName + ", failed to find a clean divisor"); + pMonitor->scale = pMonitor->getDefaultScale(); + } + } else { + if (!autoScale) { + Debug::log(ERR, "Invalid scale passed to monitor, {} found suggestion {}", pMonitor->scale, searchScale); + g_pConfigManager->addParseError( + std::format("Invalid scale passed to monitor {}, failed to find a clean divisor. Suggested nearest scale: {:5f}", pMonitor->szName, searchScale)); + pMonitor->scale = pMonitor->getDefaultScale(); + } else + pMonitor->scale = searchScale; + } + + // for wlroots, that likes flooring, we have to do this. + double logicalX = std::round(pMonitor->vecPixelSize.x / pMonitor->scale); + logicalX += 0.1; + + wlr_output_set_scale(pMonitor->output, pMonitor->vecPixelSize.x / logicalX); + } + } + // clang-format off static const std::array>, 2> formats{ std::vector>{ /* 10-bit */ @@ -1794,7 +2055,8 @@ bool CHyprRenderer::applyMonitorRule(CMonitor* pMonitor, SMonitorRule* pMonitorR }; // clang-format on - bool set10bit = false; + bool set10bit = false; + pMonitor->drmFormat = DRM_FORMAT_INVALID; for (auto& fmt : formats[(int)!pMonitorRule->enable10bit]) { wlr_output_set_render_format(pMonitor->output, fmt.second); @@ -1805,6 +2067,8 @@ bool CHyprRenderer::applyMonitorRule(CMonitor* pMonitor, SMonitorRule* pMonitorR Debug::log(LOG, "output {} succeeded basic test on format {}", pMonitor->szName, fmt.first); if (pMonitorRule->enable10bit && fmt.first.contains("101010")) set10bit = true; + + pMonitor->drmFormat = fmt.second; break; } } @@ -1821,13 +2085,14 @@ bool CHyprRenderer::applyMonitorRule(CMonitor* pMonitor, SMonitorRule* pMonitorR pMonitor->vecTransformedSize = Vector2D(x, y); if (pMonitor->createdByUser) { - wlr_box transformedBox = {0, 0, (int)pMonitor->vecTransformedSize.x, (int)pMonitor->vecTransformedSize.y}; - wlr_box_transform(&transformedBox, &transformedBox, wlr_output_transform_invert(pMonitor->output->transform), (int)pMonitor->vecTransformedSize.x, - (int)pMonitor->vecTransformedSize.y); + CBox transformedBox = {0, 0, pMonitor->vecTransformedSize.x, pMonitor->vecTransformedSize.y}; + transformedBox.transform(wlr_output_transform_invert(pMonitor->output->transform), pMonitor->vecTransformedSize.x, pMonitor->vecTransformedSize.y); pMonitor->vecPixelSize = Vector2D(transformedBox.width, transformedBox.height); } + pMonitor->updateMatrix(); + // update renderer (here because it will call rollback, so we cannot do this before committing) g_pHyprOpenGL->destroyMonitorResources(pMonitor); @@ -1836,6 +2101,13 @@ bool CHyprRenderer::applyMonitorRule(CMonitor* pMonitor, SMonitorRule* pMonitorR wlr_damage_ring_set_bounds(&pMonitor->damage, pMonitor->vecTransformedSize.x, pMonitor->vecTransformedSize.y); + // Set scale for all surfaces on this monitor, needed for some clients + // but not on unsafe state to avoid crashes + if (!g_pCompositor->m_bUnsafeState) { + for (auto& w : g_pCompositor->m_vWindows) { + w->updateSurfaceScaleTransformDetails(); + } + } // updato us arrangeLayersForMonitor(pMonitor->ID); @@ -1855,6 +2127,32 @@ bool CHyprRenderer::applyMonitorRule(CMonitor* pMonitor, SMonitorRule* pMonitorR return true; } +void CHyprRenderer::setCursorSurface(wlr_surface* surf, int hotspotX, int hotspotY, bool force) { + m_bCursorHasSurface = surf; + + if ((surf == m_sLastCursorData.surf || m_bCursorHidden) && !force) + return; + + m_sLastCursorData.name = ""; + m_sLastCursorData.surf = surf; + m_sLastCursorData.hotspotX = hotspotX; + m_sLastCursorData.hotspotY = hotspotY; + + wlr_cursor_set_surface(g_pCompositor->m_sWLRCursor, surf, hotspotX, hotspotY); +} + +void CHyprRenderer::setCursorFromName(const std::string& name, bool force) { + m_bCursorHasSurface = true; + + if ((name == m_sLastCursorData.name || m_bCursorHidden) && !force) + return; + + m_sLastCursorData.name = name; + m_sLastCursorData.surf.reset(); + + wlr_cursor_set_xcursor(g_pCompositor->m_sWLRCursor, g_pCompositor->m_sWLRXCursorMgr, name.c_str()); +} + void CHyprRenderer::ensureCursorRenderingMode() { static auto* const PCURSORTIMEOUT = &g_pConfigManager->getConfigValuePtr("general:cursor_inactive_timeout")->intValue; static auto* const PHIDEONTOUCH = &g_pConfigManager->getConfigValuePtr("misc:hide_cursor_on_touch")->intValue; @@ -1864,33 +2162,49 @@ void CHyprRenderer::ensureCursorRenderingMode() { if (*PCURSORTIMEOUT > 0 || *PHIDEONTOUCH) { const bool HIDE = (*PCURSORTIMEOUT > 0 && *PCURSORTIMEOUT < PASSEDCURSORSECONDS) || (g_pInputManager->m_bLastInputTouch && *PHIDEONTOUCH); - if (HIDE && m_bHasARenderedCursor) { - m_bHasARenderedCursor = false; - - wlr_cursor_set_surface(g_pCompositor->m_sWLRCursor, nullptr, 0, 0); // hide - + if (HIDE && !m_bCursorHidden) { Debug::log(LOG, "Hiding the cursor (timeout)"); for (auto& m : g_pCompositor->m_vMonitors) g_pHyprRenderer->damageMonitor(m.get()); // TODO: maybe just damage the cursor area? - } else if (!HIDE && !m_bHasARenderedCursor) { - m_bHasARenderedCursor = true; - if (!m_bWindowRequestedCursorHide) - wlr_cursor_set_xcursor(g_pCompositor->m_sWLRCursor, g_pCompositor->m_sWLRXCursorMgr, "left_ptr"); + setCursorHidden(true); + } else if (!HIDE && m_bCursorHidden) { Debug::log(LOG, "Showing the cursor (timeout)"); for (auto& m : g_pCompositor->m_vMonitors) g_pHyprRenderer->damageMonitor(m.get()); // TODO: maybe just damage the cursor area? + + setCursorHidden(false); } } else { - m_bHasARenderedCursor = true; + setCursorHidden(false); } } +void CHyprRenderer::setCursorHidden(bool hide) { + + if (hide == m_bCursorHidden) + return; + + m_bCursorHidden = hide; + + if (hide) { + wlr_cursor_unset_image(g_pCompositor->m_sWLRCursor); + return; + } + + if (m_sLastCursorData.surf.has_value()) + setCursorSurface(m_sLastCursorData.surf.value(), m_sLastCursorData.hotspotX, m_sLastCursorData.hotspotY, true); + else if (!m_sLastCursorData.name.empty()) + setCursorFromName(m_sLastCursorData.name, true); + else + setCursorFromName("left_ptr", true); +} + bool CHyprRenderer::shouldRenderCursor() { - return m_bHasARenderedCursor; + return !m_bCursorHidden && m_bCursorHasSurface; } std::tuple CHyprRenderer::getRenderTimes(CMonitor* pMonitor) { @@ -1940,6 +2254,35 @@ void CHyprRenderer::initiateManualCrash() { g_pConfigManager->setInt("debug:damage_tracking", 0); } +void CHyprRenderer::setOccludedForMainWorkspace(CRegion& region, CWorkspace* pWorkspace) { + CRegion rg; + + const auto PMONITOR = g_pCompositor->getMonitorFromID(pWorkspace->m_iMonitorID); + + if (!PMONITOR->specialWorkspaceID) + return; + + for (auto& w : g_pCompositor->m_vWindows) { + if (!w->m_bIsMapped || w->isHidden() || w->m_iWorkspaceID != PMONITOR->specialWorkspaceID) + continue; + + if (!w->opaque()) + continue; + + const auto ROUNDING = w->rounding() * PMONITOR->scale; + const Vector2D POS = w->m_vRealPosition.vec() + Vector2D{ROUNDING, ROUNDING} - PMONITOR->vecPosition + (w->m_bPinned ? Vector2D{} : pWorkspace->m_vRenderOffset.vec()); + const Vector2D SIZE = w->m_vRealSize.vec() - Vector2D{ROUNDING * 2, ROUNDING * 2}; + + CBox box = {POS.x, POS.y, SIZE.x, SIZE.y}; + + box.scale(PMONITOR->scale); + + rg.add(box); + } + + region.subtract(rg); +} + void CHyprRenderer::setOccludedForBackLayers(CRegion& region, CWorkspace* pWorkspace) { CRegion rg; @@ -1956,11 +2299,11 @@ void CHyprRenderer::setOccludedForBackLayers(CRegion& region, CWorkspace* pWorks const Vector2D POS = w->m_vRealPosition.vec() + Vector2D{ROUNDING, ROUNDING} - PMONITOR->vecPosition + (w->m_bPinned ? Vector2D{} : pWorkspace->m_vRenderOffset.vec()); const Vector2D SIZE = w->m_vRealSize.vec() - Vector2D{ROUNDING * 2, ROUNDING * 2}; - wlr_box box = {POS.x, POS.y, SIZE.x, SIZE.y}; + CBox box = {POS.x, POS.y, SIZE.x, SIZE.y}; - scaleBox(&box, PMONITOR->scale); + box.scale(PMONITOR->scale); - rg.add(&box); + rg.add(box); } region.subtract(rg); @@ -1978,7 +2321,12 @@ bool CHyprRenderer::canSkipBackBufferClear(CMonitor* pMonitor) { ls->geometry.height != pMonitor->vecSize.y) continue; - if (!ls->layerSurface->surface->opaque) + // TODO: cache maybe? + CRegion opaque = &ls->layerSurface->surface->opaque_region; + CBox lsbox = {0, 0, ls->layerSurface->surface->current.buffer_width, ls->layerSurface->surface->current.buffer_height}; + opaque.invert(lsbox); + + if (!opaque.empty()) continue; return true; @@ -1986,3 +2334,179 @@ bool CHyprRenderer::canSkipBackBufferClear(CMonitor* pMonitor) { return false; } + +void CHyprRenderer::recheckSolitaryForMonitor(CMonitor* pMonitor) { + pMonitor->solitaryClient = nullptr; // reset it, if we find one it will be set. + + const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(pMonitor->activeWorkspace); + + if (!PWORKSPACE || !PWORKSPACE->m_bHasFullscreenWindow || g_pInputManager->m_sDrag.drag || g_pCompositor->m_sSeat.exclusiveClient || pMonitor->specialWorkspaceID || + PWORKSPACE->m_fAlpha.fl() != 1.f || PWORKSPACE->m_vRenderOffset.vec() != Vector2D{}) + return; + + const auto PCANDIDATE = g_pCompositor->getFullscreenWindowOnWorkspace(PWORKSPACE->m_iID); + + if (!PCANDIDATE) + return; // ???? + + if (!PCANDIDATE->opaque()) + return; + + if (PCANDIDATE->m_vRealSize.vec() != pMonitor->vecSize || PCANDIDATE->m_vRealPosition.vec() != pMonitor->vecPosition || PCANDIDATE->m_vRealPosition.isBeingAnimated() || + PCANDIDATE->m_vRealSize.isBeingAnimated()) + return; + + if (!pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY].empty()) + return; + + for (auto& topls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) { + if (topls->alpha.fl() != 0.f) + return; + } + + for (auto& w : g_pCompositor->m_vWindows) { + if (w->m_iWorkspaceID == PCANDIDATE->m_iWorkspaceID && w->m_bIsFloating && w->m_bCreatedOverFullscreen && !w->isHidden() && w->m_bIsMapped && w.get() != PCANDIDATE) + return; + } + + if (pMonitor->specialWorkspaceID != 0) + return; + + // check if it did not open any subsurfaces or shit + int surfaceCount = 0; + if (PCANDIDATE->m_bIsX11) { + surfaceCount = 1; + } else { + wlr_xdg_surface_for_each_surface(PCANDIDATE->m_uSurface.xdg, countSubsurfacesIter, &surfaceCount); + wlr_xdg_surface_for_each_popup_surface(PCANDIDATE->m_uSurface.xdg, countSubsurfacesIter, &surfaceCount); + } + + if (surfaceCount > 1) + return; + + // found one! + pMonitor->solitaryClient = PCANDIDATE; +} + +void CHyprRenderer::renderSoftwareCursors(CMonitor* pMonitor, const CRegion& damage, std::optional overridePos) { + const auto CURSORPOS = overridePos.value_or(g_pInputManager->getMouseCoordsInternal() - pMonitor->vecPosition) * pMonitor->scale; + wlr_output_cursor* cursor; + wl_list_for_each(cursor, &pMonitor->output->cursors, link) { + if (!cursor->enabled || !cursor->visible || pMonitor->output->hardware_cursor == cursor) + continue; + + if (!cursor->texture) + continue; + + CBox cursorBox = CBox{CURSORPOS.x, CURSORPOS.y, cursor->width, cursor->height}.translate({-cursor->hotspot_x, -cursor->hotspot_y}); + + // TODO: NVIDIA doesn't like if we use renderTexturePrimitive here. Why? + g_pHyprOpenGL->renderTexture(cursor->texture, &cursorBox, 1.0); + } +} + +CRenderbuffer* CHyprRenderer::getOrCreateRenderbuffer(wlr_buffer* buffer, uint32_t fmt) { + auto it = std::find_if(m_vRenderbuffers.begin(), m_vRenderbuffers.end(), [&](const auto& other) { return other->m_pWlrBuffer == buffer; }); + + if (it != m_vRenderbuffers.end()) + return it->get(); + + return m_vRenderbuffers.emplace_back(std::make_unique(buffer, fmt)).get(); +} + +void CHyprRenderer::makeEGLCurrent() { + if (!g_pCompositor) + return; + + if (eglGetCurrentContext() != wlr_egl_get_context(g_pCompositor->m_sWLREGL)) + eglMakeCurrent(wlr_egl_get_display(g_pCompositor->m_sWLREGL), EGL_NO_SURFACE, EGL_NO_SURFACE, wlr_egl_get_context(g_pCompositor->m_sWLREGL)); +} + +void CHyprRenderer::unsetEGL() { + eglMakeCurrent(wlr_egl_get_display(g_pCompositor->m_sWLREGL), EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); +} + +bool CHyprRenderer::beginRender(CMonitor* pMonitor, CRegion& damage, eRenderMode mode, wlr_buffer* buffer, CFramebuffer* fb) { + + makeEGLCurrent(); + + m_eRenderMode = mode; + + g_pHyprOpenGL->m_RenderData.pMonitor = pMonitor; // has to be set cuz allocs + + if (mode == RENDER_MODE_FULL_FAKE) { + RASSERT(fb, "Cannot render FULL_FAKE without a provided fb!"); + fb->bind(); + g_pHyprOpenGL->begin(pMonitor, &damage, fb); + return true; + } + + if (!buffer) { + if (!wlr_output_configure_primary_swapchain(pMonitor->output, &pMonitor->output->pending, &pMonitor->output->swapchain)) + return false; + + m_pCurrentWlrBuffer = wlr_swapchain_acquire(pMonitor->output->swapchain, &m_iLastBufferAge); + if (!m_pCurrentWlrBuffer) + return false; + } else { + m_pCurrentWlrBuffer = wlr_buffer_lock(buffer); + } + + try { + m_pCurrentRenderbuffer = getOrCreateRenderbuffer(m_pCurrentWlrBuffer, pMonitor->drmFormat); + } catch (std::exception& e) { + wlr_buffer_unlock(m_pCurrentWlrBuffer); + return false; + } + + m_pCurrentRenderbuffer->bind(); + g_pHyprOpenGL->begin(pMonitor, &damage); + + return true; +} + +void CHyprRenderer::endRender() { + const auto PMONITOR = g_pHyprOpenGL->m_RenderData.pMonitor; + static auto* const PNVIDIAANTIFLICKER = &g_pConfigManager->getConfigValuePtr("opengl:nvidia_anti_flicker")->intValue; + + if (m_eRenderMode != RENDER_MODE_TO_BUFFER_READ_ONLY) + g_pHyprOpenGL->end(); + else { + g_pHyprOpenGL->m_RenderData.pMonitor = nullptr; + g_pHyprOpenGL->m_RenderData.mouseZoomFactor = 1.f; + g_pHyprOpenGL->m_RenderData.mouseZoomUseMouse = true; + } + + if (m_eRenderMode == RENDER_MODE_FULL_FAKE) + return; + + if (isNvidia() && *PNVIDIAANTIFLICKER) + glFinish(); + else + glFlush(); + + if (m_eRenderMode == RENDER_MODE_NORMAL) { + wlr_output_state_set_buffer(&PMONITOR->output->pending, m_pCurrentWlrBuffer); + unsetEGL(); // flush the context + } + + wlr_buffer_unlock(m_pCurrentWlrBuffer); + + m_pCurrentRenderbuffer->unbind(); + + m_pCurrentRenderbuffer = nullptr; + m_pCurrentWlrBuffer = nullptr; + m_iLastBufferAge = 0; +} + +void CHyprRenderer::onRenderbufferDestroy(CRenderbuffer* rb) { + std::erase_if(m_vRenderbuffers, [&](const auto& rbo) { return rbo.get() == rb; }); +} + +CRenderbuffer* CHyprRenderer::getCurrentRBO() { + return m_pCurrentRenderbuffer; +} + +bool CHyprRenderer::isNvidia() { + return m_bNvidia; +} diff --git a/src/render/Renderer.hpp b/src/render/Renderer.hpp index f0e19c38..bc914558 100644 --- a/src/render/Renderer.hpp +++ b/src/render/Renderer.hpp @@ -6,39 +6,47 @@ #include "../helpers/Workspace.hpp" #include "../Window.hpp" #include "OpenGL.hpp" +#include "Renderbuffer.hpp" #include "../helpers/Timer.hpp" #include "../helpers/Region.hpp" struct SMonitorRule; // TODO: add fuller damage tracking for updating only parts of a window -enum DAMAGETRACKINGMODES -{ +enum DAMAGETRACKINGMODES { DAMAGE_TRACKING_INVALID = -1, DAMAGE_TRACKING_NONE = 0, DAMAGE_TRACKING_MONITOR, DAMAGE_TRACKING_FULL }; -enum eRenderPassMode -{ +enum eRenderPassMode { RENDER_PASS_ALL = 0, RENDER_PASS_MAIN, RENDER_PASS_POPUP }; +enum eRenderMode { + RENDER_MODE_NORMAL = 0, + RENDER_MODE_FULL_FAKE = 1, + RENDER_MODE_TO_BUFFER = 2, + RENDER_MODE_TO_BUFFER_READ_ONLY = 3, +}; + class CToplevelExportProtocolManager; class CInputManager; struct SSessionLockSurface; class CHyprRenderer { public: + CHyprRenderer(); + void renderMonitor(CMonitor* pMonitor); void outputMgrApplyTest(wlr_output_configuration_v1*, bool); void arrangeLayersForMonitor(const int&); void damageSurface(wlr_surface*, double, double, double scale = 1.0); void damageWindow(CWindow*); - void damageBox(wlr_box*); + void damageBox(CBox*); void damageBox(const int& x, const int& y, const int& w, const int& h); void damageRegion(const CRegion&); void damageMonitor(CMonitor*); @@ -48,46 +56,79 @@ class CHyprRenderer { bool shouldRenderWindow(CWindow*); void ensureCursorRenderingMode(); bool shouldRenderCursor(); + void setCursorHidden(bool hide); void calculateUVForSurface(CWindow*, wlr_surface*, bool main = false); std::tuple getRenderTimes(CMonitor* pMonitor); // avg max min void renderLockscreen(CMonitor* pMonitor, timespec* now); void setOccludedForBackLayers(CRegion& region, CWorkspace* pWorkspace); + void setOccludedForMainWorkspace(CRegion& region, CWorkspace* pWorkspace); // TODO: merge occlusion methods bool canSkipBackBufferClear(CMonitor* pMonitor); + void recheckSolitaryForMonitor(CMonitor* pMonitor); + void setCursorSurface(wlr_surface* surf, int hotspotX, int hotspotY, bool force = false); + void setCursorFromName(const std::string& name, bool force = false); + void renderSoftwareCursors(CMonitor* pMonitor, const CRegion& damage, std::optional overridePos = {}); + void onRenderbufferDestroy(CRenderbuffer* rb); + CRenderbuffer* getCurrentRBO(); + bool isNvidia(); + void makeEGLCurrent(); + void unsetEGL(); - bool m_bWindowRequestedCursorHide = false; - bool m_bBlockSurfaceFeedback = false; - bool m_bRenderingSnapshot = false; - CWindow* m_pLastScanout = nullptr; - CMonitor* m_pMostHzMonitor = nullptr; - bool m_bDirectScanoutBlocked = false; - bool m_bSoftwareCursorsLocked = false; + bool beginRender(CMonitor* pMonitor, CRegion& damage, eRenderMode mode = RENDER_MODE_NORMAL, wlr_buffer* buffer = nullptr, CFramebuffer* fb = nullptr); + void endRender(); + + bool m_bBlockSurfaceFeedback = false; + bool m_bRenderingSnapshot = false; + CWindow* m_pLastScanout = nullptr; + CMonitor* m_pMostHzMonitor = nullptr; + bool m_bDirectScanoutBlocked = false; + bool m_bSoftwareCursorsLocked = false; + bool m_bTearingEnvSatisfied = false; DAMAGETRACKINGMODES damageTrackingModeFromStr(const std::string&); - bool attemptDirectScanout(CMonitor*); - void setWindowScanoutMode(CWindow*); - void initiateManualCrash(); + bool attemptDirectScanout(CMonitor*); + void setWindowScanoutMode(CWindow*); + void initiateManualCrash(); - bool m_bCrashingInProgress = false; - float m_fCrashingDistort = 0.5f; - wl_event_source* m_pCrashingLoop = nullptr; + bool m_bCrashingInProgress = false; + float m_fCrashingDistort = 0.5f; + wl_event_source* m_pCrashingLoop = nullptr; - CTimer m_tRenderTimer; + std::vector> m_vTearingControllers; + + CTimer m_tRenderTimer; + + struct { + int hotspotX; + int hotspotY; + std::optional surf = nullptr; + std::string name; + } m_sLastCursorData; private: - void arrangeLayerArray(CMonitor*, const std::vector>&, bool, wlr_box*); - void renderWorkspaceWindowsFullscreen(CMonitor*, CWorkspace*, timespec*); // renders workspace windows (fullscreen) (tiled, floating, pinned, but no special) - void renderWorkspaceWindows(CMonitor*, CWorkspace*, timespec*); // renders workspace windows (no fullscreen) (tiled, floating, pinned, but no special) - void renderWindow(CWindow*, CMonitor*, timespec*, bool, eRenderPassMode, bool ignorePosition = false, bool ignoreAllGeometry = false); - void renderLayer(SLayerSurface*, CMonitor*, timespec*); - void renderSessionLockSurface(SSessionLockSurface*, CMonitor*, timespec*); - void renderDragIcon(CMonitor*, timespec*); - void renderIMEPopup(SIMEPopup*, CMonitor*, timespec*); - void renderWorkspace(CMonitor* pMonitor, CWorkspace* pWorkspace, timespec* now, const wlr_box& geometry); - void renderAllClientsForWorkspace(CMonitor* pMonitor, CWorkspace* pWorkspace, timespec* now, const Vector2D& translate = {0, 0}, const float& scale = 1.f); + void arrangeLayerArray(CMonitor*, const std::vector>&, bool, CBox*); + void renderWorkspaceWindowsFullscreen(CMonitor*, CWorkspace*, timespec*); // renders workspace windows (fullscreen) (tiled, floating, pinned, but no special) + void renderWorkspaceWindows(CMonitor*, CWorkspace*, timespec*); // renders workspace windows (no fullscreen) (tiled, floating, pinned, but no special) + void renderWindow(CWindow*, CMonitor*, timespec*, bool, eRenderPassMode, bool ignorePosition = false, bool ignoreAllGeometry = false); + void renderLayer(SLayerSurface*, CMonitor*, timespec*); + void renderSessionLockSurface(SSessionLockSurface*, CMonitor*, timespec*); + void renderDragIcon(CMonitor*, timespec*); + void renderIMEPopup(SIMEPopup*, CMonitor*, timespec*); + void renderWorkspace(CMonitor* pMonitor, CWorkspace* pWorkspace, timespec* now, const CBox& geometry); + void renderAllClientsForWorkspace(CMonitor* pMonitor, CWorkspace* pWorkspace, timespec* now, const Vector2D& translate = {0, 0}, const float& scale = 1.f); - bool m_bHasARenderedCursor = true; + bool m_bCursorHidden = false; + bool m_bCursorHasSurface = false; + CRenderbuffer* m_pCurrentRenderbuffer = nullptr; + wlr_buffer* m_pCurrentWlrBuffer = nullptr; + eRenderMode m_eRenderMode = RENDER_MODE_NORMAL; + int m_iLastBufferAge = 0; + + bool m_bNvidia = false; + + CRenderbuffer* getOrCreateRenderbuffer(wlr_buffer* buffer, uint32_t fmt); + std::vector> m_vRenderbuffers; friend class CHyprOpenGLImpl; friend class CToplevelExportProtocolManager; diff --git a/src/render/Shader.hpp b/src/render/Shader.hpp index 01d35209..70fe468c 100644 --- a/src/render/Shader.hpp +++ b/src/render/Shader.hpp @@ -10,10 +10,12 @@ class CShader { GLuint program = 0; GLint proj = -1; GLint color = -1; + GLint alphaMatte = -1; GLint tex = -1; GLint alpha = -1; GLint posAttrib = -1; GLint texAttrib = -1; + GLint matteTexAttrib = -1; GLint discardOpaque = -1; GLint discardAlpha = -1; GLfloat discardAlphaValue = -1; @@ -23,14 +25,15 @@ class CShader { GLint fullSize = -1; GLint fullSizeUntransformed = -1; GLint radius = -1; - GLint primitiveMultisample = -1; + GLint radiusOuter = -1; GLint thick = -1; GLint halfpixel = -1; - GLint range = -1; - GLint shadowPower = -1; + GLint range = -1; + GLint shadowPower = -1; + GLint useAlphaMatte = -1; // always inverted GLint applyTint = -1; GLint tint = -1; @@ -43,13 +46,21 @@ class CShader { GLint distort = -1; GLint output = -1; - GLint noise = -1; - GLint contrast = -1; - GLint brightness = -1; + // Blur prepare + GLint contrast = -1; - GLint getUniformLocation(const std::string&); + // Blur + GLint passes = -1; // Used by `vibrancy` + GLint vibrancy = -1; + GLint vibrancy_darkness = -1; - void destroy(); + // Blur finish + GLint brightness = -1; + GLint noise = -1; + + GLint getUniformLocation(const std::string&); + + void destroy(); private: std::unordered_map m_muUniforms; diff --git a/src/render/Texture.hpp b/src/render/Texture.hpp index f287089b..93a6aa5f 100644 --- a/src/render/Texture.hpp +++ b/src/render/Texture.hpp @@ -2,8 +2,7 @@ #include "../defines.hpp" -enum TEXTURETYPE -{ +enum TEXTURETYPE { TEXTURE_INVALID, // Invalid TEXTURE_RGBA, // 4 channels TEXTURE_RGBX, // discard A diff --git a/src/render/Transformer.cpp b/src/render/Transformer.cpp new file mode 100644 index 00000000..c6989d2c --- /dev/null +++ b/src/render/Transformer.cpp @@ -0,0 +1,5 @@ +#include "Transformer.hpp" + +void IWindowTransformer::preWindowRender(SRenderData* pRenderData) { + ; +} \ No newline at end of file diff --git a/src/render/Transformer.hpp b/src/render/Transformer.hpp new file mode 100644 index 00000000..90fdeba9 --- /dev/null +++ b/src/render/Transformer.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "Framebuffer.hpp" + +struct SRenderData; + +// A window transformer can be attached to a window. +// If any is attached, Hyprland will render the window to a separate fb, then call the transform() func with it, +// and finally render it back to the main fb after all transformers pass. +// +// Worth noting transformers for now only affect the main pass (not popups) +class IWindowTransformer { + public: + // called by Hyprland. For more data about what is being rendered, inspect render data. + // returns the out fb. + virtual CFramebuffer* transform(CFramebuffer* in) = 0; + + // called by Hyprland before a window main pass is started. + virtual void preWindowRender(SRenderData* pRenderData); +}; \ No newline at end of file diff --git a/src/render/decorations/CHyprBorderDecoration.cpp b/src/render/decorations/CHyprBorderDecoration.cpp new file mode 100644 index 00000000..77ebdd57 --- /dev/null +++ b/src/render/decorations/CHyprBorderDecoration.cpp @@ -0,0 +1,108 @@ +#include "CHyprBorderDecoration.hpp" +#include "../../Compositor.hpp" + +CHyprBorderDecoration::CHyprBorderDecoration(CWindow* pWindow) : IHyprWindowDecoration(pWindow) { + m_pWindow = pWindow; +} + +CHyprBorderDecoration::~CHyprBorderDecoration() { + ; +} + +SDecorationPositioningInfo CHyprBorderDecoration::getPositioningInfo() { + const auto BORDERSIZE = m_pWindow->getRealBorderSize(); + m_seExtents = {{BORDERSIZE, BORDERSIZE}, {BORDERSIZE, BORDERSIZE}}; + + if (doesntWantBorders()) + m_seExtents = {{}, {}}; + + SDecorationPositioningInfo info; + info.priority = 10000; + info.policy = DECORATION_POSITION_STICKY; + info.desiredExtents = m_seExtents; + info.reserved = true; + info.edges = DECORATION_EDGE_BOTTOM | DECORATION_EDGE_LEFT | DECORATION_EDGE_RIGHT | DECORATION_EDGE_TOP; + + m_seReportedExtents = m_seExtents; + return info; +} + +void CHyprBorderDecoration::onPositioningReply(const SDecorationPositioningReply& reply) { + m_bAssignedGeometry = reply.assignedGeometry; +} + +CBox CHyprBorderDecoration::assignedBoxGlobal() { + CBox box = m_bAssignedGeometry; + box.translate(g_pDecorationPositioner->getEdgeDefinedPoint(DECORATION_EDGE_BOTTOM | DECORATION_EDGE_LEFT | DECORATION_EDGE_RIGHT | DECORATION_EDGE_TOP, m_pWindow)); + + const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(m_pWindow->m_iWorkspaceID); + + if (!PWORKSPACE) + return box; + + const auto WORKSPACEOFFSET = PWORKSPACE && !m_pWindow->m_bPinned ? PWORKSPACE->m_vRenderOffset.vec() : Vector2D(); + return box.translate(WORKSPACEOFFSET); +} + +void CHyprBorderDecoration::draw(CMonitor* pMonitor, float a, const Vector2D& offset) { + if (doesntWantBorders()) + return; + + if (m_bAssignedGeometry.width < m_seExtents.topLeft.x + 1 || m_bAssignedGeometry.height < m_seExtents.topLeft.y + 1) + return; + + CBox windowBox = assignedBoxGlobal().translate(-pMonitor->vecPosition + offset).expand(-m_pWindow->getRealBorderSize()).scale(pMonitor->scale).round(); + + if (windowBox.width < 1 || windowBox.height < 1) + return; + + auto grad = m_pWindow->m_cRealBorderColor; + const bool ANIMATED = m_pWindow->m_fBorderFadeAnimationProgress.isBeingAnimated(); + float a1 = a * (ANIMATED ? m_pWindow->m_fBorderFadeAnimationProgress.fl() : 1.f); + + if (m_pWindow->m_fBorderAngleAnimationProgress.getConfig()->pValues->internalEnabled) { + grad.m_fAngle += m_pWindow->m_fBorderAngleAnimationProgress.fl() * M_PI * 2; + grad.m_fAngle = normalizeAngleRad(grad.m_fAngle); + } + + int borderSize = m_pWindow->getRealBorderSize(); + const auto ROUNDING = m_pWindow->rounding() * pMonitor->scale; + + g_pHyprOpenGL->renderBorder(&windowBox, grad, ROUNDING, borderSize, a1); + + if (ANIMATED) { + float a2 = a * (1.f - m_pWindow->m_fBorderFadeAnimationProgress.fl()); + g_pHyprOpenGL->renderBorder(&windowBox, m_pWindow->m_cRealBorderColorPrevious, ROUNDING, borderSize, a2); + } +} + +eDecorationType CHyprBorderDecoration::getDecorationType() { + return DECORATION_BORDER; +} + +void CHyprBorderDecoration::updateWindow(CWindow*) { + if (m_pWindow->getRealBorderSize() != m_seExtents.topLeft.x) + g_pDecorationPositioner->repositionDeco(this); +} + +void CHyprBorderDecoration::damageEntire() { + ; // ignored, done in animationManager. todo, move. +} + +eDecorationLayer CHyprBorderDecoration::getDecorationLayer() { + return DECORATION_LAYER_OVER; +} + +uint64_t CHyprBorderDecoration::getDecorationFlags() { + static auto* const PPARTOFWINDOW = &g_pConfigManager->getConfigValuePtr("general:border_part_of_window")->intValue; + + return *PPARTOFWINDOW && !doesntWantBorders() ? DECORATION_PART_OF_MAIN_WINDOW : 0; +} + +std::string CHyprBorderDecoration::getDisplayName() { + return "Border"; +} + +bool CHyprBorderDecoration::doesntWantBorders() { + return !m_pWindow->m_sSpecialRenderData.border || m_pWindow->m_bX11DoesntWantBorders || m_pWindow->getRealBorderSize() == 0; +} diff --git a/src/render/decorations/CHyprBorderDecoration.hpp b/src/render/decorations/CHyprBorderDecoration.hpp new file mode 100644 index 00000000..9c0bcc33 --- /dev/null +++ b/src/render/decorations/CHyprBorderDecoration.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include "IHyprWindowDecoration.hpp" + +class CHyprBorderDecoration : public IHyprWindowDecoration { + public: + CHyprBorderDecoration(CWindow*); + virtual ~CHyprBorderDecoration(); + + virtual SDecorationPositioningInfo getPositioningInfo(); + + virtual void onPositioningReply(const SDecorationPositioningReply& reply); + + virtual void draw(CMonitor*, float a, const Vector2D& offset); + + virtual eDecorationType getDecorationType(); + + virtual void updateWindow(CWindow*); + + virtual void damageEntire(); + + virtual eDecorationLayer getDecorationLayer(); + + virtual uint64_t getDecorationFlags(); + + virtual std::string getDisplayName(); + + private: + SWindowDecorationExtents m_seExtents; + SWindowDecorationExtents m_seReportedExtents; + + CWindow* m_pWindow = nullptr; + + Vector2D m_vLastWindowPos; + Vector2D m_vLastWindowSize; + + CBox m_bAssignedGeometry = {0}; + + CBox assignedBoxGlobal(); + bool doesntWantBorders(); +}; diff --git a/src/render/decorations/CHyprDropShadowDecoration.cpp b/src/render/decorations/CHyprDropShadowDecoration.cpp index ee1a65a5..0e5e9d94 100644 --- a/src/render/decorations/CHyprDropShadowDecoration.cpp +++ b/src/render/decorations/CHyprDropShadowDecoration.cpp @@ -6,46 +6,51 @@ CHyprDropShadowDecoration::CHyprDropShadowDecoration(CWindow* pWindow) : IHyprWi m_pWindow = pWindow; } -CHyprDropShadowDecoration::~CHyprDropShadowDecoration() { - updateWindow(m_pWindow); -} - -SWindowDecorationExtents CHyprDropShadowDecoration::getWindowDecorationExtents() { - static auto* const PSHADOWS = &g_pConfigManager->getConfigValuePtr("decoration:drop_shadow")->intValue; - - if (*PSHADOWS != 1) - return {{}, {}}; - - return m_seExtents; -} +CHyprDropShadowDecoration::~CHyprDropShadowDecoration() {} eDecorationType CHyprDropShadowDecoration::getDecorationType() { return DECORATION_SHADOW; } +SDecorationPositioningInfo CHyprDropShadowDecoration::getPositioningInfo() { + SDecorationPositioningInfo info; + info.policy = DECORATION_POSITION_ABSOLUTE; + info.desiredExtents = m_seExtents; + info.edges = DECORATION_EDGE_BOTTOM | DECORATION_EDGE_LEFT | DECORATION_EDGE_RIGHT | DECORATION_EDGE_TOP; + + m_seReportedExtents = m_seExtents; + return info; +} + +void CHyprDropShadowDecoration::onPositioningReply(const SDecorationPositioningReply& reply) { + updateWindow(m_pWindow); +} + +uint64_t CHyprDropShadowDecoration::getDecorationFlags() { + return DECORATION_NON_SOLID; +} + +std::string CHyprDropShadowDecoration::getDisplayName() { + return "Drop Shadow"; +} + void CHyprDropShadowDecoration::damageEntire() { static auto* const PSHADOWS = &g_pConfigManager->getConfigValuePtr("decoration:drop_shadow")->intValue; if (*PSHADOWS != 1) return; // disabled - wlr_box dm = {m_vLastWindowPos.x - m_seExtents.topLeft.x, m_vLastWindowPos.y - m_seExtents.topLeft.y, m_vLastWindowSize.x + m_seExtents.topLeft.x + m_seExtents.bottomRight.x, - m_vLastWindowSize.y + m_seExtents.topLeft.y + m_seExtents.bottomRight.y}; + CBox dm = {m_vLastWindowPos.x - m_seExtents.topLeft.x, m_vLastWindowPos.y - m_seExtents.topLeft.y, m_vLastWindowSize.x + m_seExtents.topLeft.x + m_seExtents.bottomRight.x, + m_vLastWindowSize.y + m_seExtents.topLeft.y + m_seExtents.bottomRight.y}; g_pHyprRenderer->damageBox(&dm); } void CHyprDropShadowDecoration::updateWindow(CWindow* pWindow) { + m_vLastWindowPos = m_pWindow->m_vRealPosition.vec(); + m_vLastWindowSize = m_pWindow->m_vRealSize.vec(); - const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(pWindow->m_iWorkspaceID); - - const auto WORKSPACEOFFSET = PWORKSPACE && !pWindow->m_bPinned ? PWORKSPACE->m_vRenderOffset.vec() : Vector2D(); - - if (pWindow->m_vRealPosition.vec() + WORKSPACEOFFSET != m_vLastWindowPos || pWindow->m_vRealSize.vec() != m_vLastWindowSize) { - m_vLastWindowPos = pWindow->m_vRealPosition.vec() + WORKSPACEOFFSET; - m_vLastWindowSize = pWindow->m_vRealSize.vec(); - - damageEntire(); - } + m_bLastWindowBox = {m_vLastWindowPos.x, m_vLastWindowPos.y, m_vLastWindowSize.x, m_vLastWindowSize.y}; + m_bLastWindowBoxWithDecos = g_pDecorationPositioner->getBoxWithIncludedDecos(pWindow); } void CHyprDropShadowDecoration::draw(CMonitor* pMonitor, float a, const Vector2D& offset) { @@ -67,7 +72,6 @@ void CHyprDropShadowDecoration::draw(CMonitor* pMonitor, float a, const Vector2D static auto* const PSHADOWS = &g_pConfigManager->getConfigValuePtr("decoration:drop_shadow")->intValue; static auto* const PSHADOWSIZE = &g_pConfigManager->getConfigValuePtr("decoration:shadow_range")->intValue; - static auto* const PROUNDING = &g_pConfigManager->getConfigValuePtr("decoration:rounding")->intValue; static auto* const PSHADOWIGNOREWINDOW = &g_pConfigManager->getConfigValuePtr("decoration:shadow_ignore_window")->intValue; static auto* const PSHADOWSCALE = &g_pConfigManager->getConfigValuePtr("decoration:shadow_scale")->floatValue; static auto* const PSHADOWOFFSET = &g_pConfigManager->getConfigValuePtr("decoration:shadow_offset")->vecValue; @@ -75,84 +79,100 @@ void CHyprDropShadowDecoration::draw(CMonitor* pMonitor, float a, const Vector2D if (*PSHADOWS != 1) return; // disabled - const auto ROUNDING = !m_pWindow->m_sSpecialRenderData.rounding ? - 0 : - (m_pWindow->m_sAdditionalConfigData.rounding.toUnderlying() == -1 ? *PROUNDING : m_pWindow->m_sAdditionalConfigData.rounding.toUnderlying()); + const auto ROUNDINGBASE = m_pWindow->rounding(); + const auto ROUNDING = ROUNDINGBASE > 0 ? ROUNDINGBASE + m_pWindow->getRealBorderSize() : 0; + const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(m_pWindow->m_iWorkspaceID); + const auto WORKSPACEOFFSET = PWORKSPACE && !m_pWindow->m_bPinned ? PWORKSPACE->m_vRenderOffset.vec() : Vector2D(); // draw the shadow - wlr_box fullBox = {m_vLastWindowPos.x - *PSHADOWSIZE, m_vLastWindowPos.y - *PSHADOWSIZE, m_vLastWindowSize.x + 2.0 * *PSHADOWSIZE, m_vLastWindowSize.y + 2.0 * *PSHADOWSIZE}; - - fullBox.x -= pMonitor->vecPosition.x; - fullBox.y -= pMonitor->vecPosition.y; + CBox fullBox = m_bLastWindowBoxWithDecos; + fullBox.translate(-pMonitor->vecPosition + WORKSPACEOFFSET); + fullBox.x -= *PSHADOWSIZE; + fullBox.y -= *PSHADOWSIZE; + fullBox.w += 2 * *PSHADOWSIZE; + fullBox.h += 2 * *PSHADOWSIZE; const float SHADOWSCALE = std::clamp(*PSHADOWSCALE, 0.f, 1.f); // scale the box in relation to the center of the box - const Vector2D NEWSIZE = Vector2D{fullBox.width, fullBox.height} * SHADOWSCALE; - fullBox.width = NEWSIZE.x; - fullBox.height = NEWSIZE.y; - - if (PSHADOWOFFSET->x < 0) { - fullBox.x += PSHADOWOFFSET->x; - } else if (PSHADOWOFFSET->x > 0) { - fullBox.x = m_vLastWindowPos.x + m_vLastWindowSize.x - fullBox.width + (SHADOWSCALE * *PSHADOWSIZE) + PSHADOWOFFSET->x - pMonitor->vecPosition.x; - } else { - fullBox.x += ((m_vLastWindowSize.x + 2.0 * *PSHADOWSIZE) - NEWSIZE.x) / 2.0; - } - - if (PSHADOWOFFSET->y < 0) { - fullBox.y += PSHADOWOFFSET->y; - } else if (PSHADOWOFFSET->y > 0) { - fullBox.y = m_vLastWindowPos.y + m_vLastWindowSize.y - fullBox.height + (SHADOWSCALE * *PSHADOWSIZE) + PSHADOWOFFSET->y - pMonitor->vecPosition.y; - } else { - fullBox.y += ((m_vLastWindowSize.y + 2.0 * *PSHADOWSIZE) - NEWSIZE.y) / 2.0; - } + fullBox.scaleFromCenter(SHADOWSCALE).translate(*PSHADOWOFFSET); + m_vLastWindowPos += WORKSPACEOFFSET; 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; + fullBox.translate(offset); if (fullBox.width < 1 || fullBox.height < 1) return; // don't draw invisible shadows - g_pHyprOpenGL->scissor((wlr_box*)nullptr); + g_pHyprOpenGL->scissor((CBox*)nullptr); + + // we'll take the liberty of using this as it should not be used rn + CFramebuffer& alphaFB = g_pHyprOpenGL->m_RenderData.pCurrentMonData->mirrorFB; + CFramebuffer& alphaSwapFB = g_pHyprOpenGL->m_RenderData.pCurrentMonData->mirrorSwapFB; + auto* LASTFB = g_pHyprOpenGL->m_RenderData.currentFB; + + fullBox.scale(pMonitor->scale).round(); if (*PSHADOWIGNOREWINDOW) { - glEnable(GL_STENCIL_TEST); + CBox windowBox = m_bLastWindowBox; + CBox withDecos = m_bLastWindowBoxWithDecos; - glClearStencil(0); - glClear(GL_STENCIL_BUFFER_BIT); + // get window box + windowBox.translate(-pMonitor->vecPosition + WORKSPACEOFFSET); + withDecos.translate(-pMonitor->vecPosition + WORKSPACEOFFSET); - glStencilFunc(GL_ALWAYS, 1, -1); - glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + auto scaledExtentss = withDecos.extentsFrom(windowBox); + scaledExtentss = scaledExtentss * pMonitor->scale; + scaledExtentss = scaledExtentss.round(); - wlr_box windowBox = {m_vLastWindowPos.x - pMonitor->vecPosition.x, m_vLastWindowPos.y - pMonitor->vecPosition.y, m_vLastWindowSize.x, m_vLastWindowSize.y}; + // add extents + windowBox.scale(pMonitor->scale).round().addExtents(scaledExtentss); - scaleBox(&windowBox, pMonitor->scale); - - if (windowBox.width < 1 || windowBox.height < 1) { - glClearStencil(0); - glClear(GL_STENCIL_BUFFER_BIT); - glDisable(GL_STENCIL_TEST); + if (windowBox.width < 1 || windowBox.height < 1) return; // prevent assert failed - } - g_pHyprOpenGL->renderRect(&windowBox, CColor(0, 0, 0, 0), ROUNDING * pMonitor->scale); + CRegion saveDamage = g_pHyprOpenGL->m_RenderData.damage; - glStencilFunc(GL_NOTEQUAL, 1, -1); - glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + g_pHyprOpenGL->m_RenderData.damage = fullBox; + g_pHyprOpenGL->m_RenderData.damage.subtract(windowBox.copy().expand(-ROUNDING * pMonitor->scale)).intersect(saveDamage); + + alphaFB.bind(); + + // build the matte + // 10-bit formats have dogshit alpha channels, so we have to use the matte to its fullest. + // first, clear region of interest with black (fully transparent) + g_pHyprOpenGL->renderRect(&fullBox, CColor(0, 0, 0, 1), 0); + + // render white shadow with the alpha of the shadow color (otherwise we clear with alpha later and shit it to 2 bit) + g_pHyprOpenGL->renderRoundedShadow(&fullBox, ROUNDING * pMonitor->scale, *PSHADOWSIZE * pMonitor->scale, CColor(1, 1, 1, m_pWindow->m_cRealShadowColor.col().a), a); + + // render black window box ("clip") + g_pHyprOpenGL->renderRect(&windowBox, CColor(0, 0, 0, 1.0), ROUNDING * pMonitor->scale); + + alphaSwapFB.bind(); + + // alpha swap just has the shadow color. It will be the "texture" to render. + g_pHyprOpenGL->renderRect(&fullBox, m_pWindow->m_cRealShadowColor.col().stripA(), 0); + + LASTFB->bind(); + + CBox monbox = {0, 0, pMonitor->vecTransformedSize.x, pMonitor->vecTransformedSize.y}; + g_pHyprOpenGL->setMonitorTransformEnabled(false); + g_pHyprOpenGL->renderTextureMatte(alphaSwapFB.m_cTex, &monbox, alphaFB); + g_pHyprOpenGL->setMonitorTransformEnabled(true); + + g_pHyprOpenGL->m_RenderData.damage = saveDamage; + } else { + g_pHyprOpenGL->renderRoundedShadow(&fullBox, ROUNDING * pMonitor->scale, *PSHADOWSIZE * pMonitor->scale, m_pWindow->m_cRealShadowColor.col(), a); } - scaleBox(&fullBox, pMonitor->scale); - g_pHyprOpenGL->renderRoundedShadow(&fullBox, ROUNDING * pMonitor->scale, *PSHADOWSIZE * pMonitor->scale, a); - - if (*PSHADOWIGNOREWINDOW) { - // cleanup - glClearStencil(0); - glClear(GL_STENCIL_BUFFER_BIT); - glDisable(GL_STENCIL_TEST); - } + if (m_seExtents != m_seReportedExtents) + g_pDecorationPositioner->repositionDeco(this); +} + +eDecorationLayer CHyprDropShadowDecoration::getDecorationLayer() { + return DECORATION_LAYER_BOTTOM; } diff --git a/src/render/decorations/CHyprDropShadowDecoration.hpp b/src/render/decorations/CHyprDropShadowDecoration.hpp index 3a7ffb2b..3b389550 100644 --- a/src/render/decorations/CHyprDropShadowDecoration.hpp +++ b/src/render/decorations/CHyprDropShadowDecoration.hpp @@ -7,21 +7,33 @@ class CHyprDropShadowDecoration : public IHyprWindowDecoration { CHyprDropShadowDecoration(CWindow*); virtual ~CHyprDropShadowDecoration(); - virtual SWindowDecorationExtents getWindowDecorationExtents(); + virtual SDecorationPositioningInfo getPositioningInfo(); - virtual void draw(CMonitor*, float a, const Vector2D& offset); + virtual void onPositioningReply(const SDecorationPositioningReply& reply); - virtual eDecorationType getDecorationType(); + virtual void draw(CMonitor*, float a, const Vector2D& offset); - virtual void updateWindow(CWindow*); + virtual eDecorationType getDecorationType(); - virtual void damageEntire(); + virtual void updateWindow(CWindow*); + + virtual void damageEntire(); + + virtual eDecorationLayer getDecorationLayer(); + + virtual uint64_t getDecorationFlags(); + + virtual std::string getDisplayName(); private: SWindowDecorationExtents m_seExtents; + SWindowDecorationExtents m_seReportedExtents; CWindow* m_pWindow = nullptr; Vector2D m_vLastWindowPos; Vector2D m_vLastWindowSize; -}; \ No newline at end of file + + CBox m_bLastWindowBox = {0}; + CBox m_bLastWindowBoxWithDecos = {0}; +}; diff --git a/src/render/decorations/CHyprGroupBarDecoration.cpp b/src/render/decorations/CHyprGroupBarDecoration.cpp index 70d4ed93..9a74dcd7 100644 --- a/src/render/decorations/CHyprGroupBarDecoration.cpp +++ b/src/render/decorations/CHyprGroupBarDecoration.cpp @@ -6,53 +6,58 @@ // shared things to conserve VRAM static CTexture m_tGradientActive; static CTexture m_tGradientInactive; +static CTexture m_tGradientLockedActive; +static CTexture m_tGradientLockedInactive; + +constexpr int BAR_INDICATOR_HEIGHT = 3; +constexpr int BAR_PADDING_OUTER_VERT = 2; +constexpr int BAR_TEXT_PAD = 2; +constexpr int BAR_HORIZONTAL_PADDING = 2; CHyprGroupBarDecoration::CHyprGroupBarDecoration(CWindow* pWindow) : IHyprWindowDecoration(pWindow) { - m_pWindow = pWindow; + static auto* const PGRADIENTS = &g_pConfigManager->getConfigValuePtr("group:groupbar:enabled")->intValue; + static auto* const PENABLED = &g_pConfigManager->getConfigValuePtr("group:groupbar:gradients")->intValue; + m_pWindow = pWindow; + + if (m_tGradientActive.m_iTexID == 0 && *PENABLED && *PGRADIENTS) + refreshGroupBarGradients(); } CHyprGroupBarDecoration::~CHyprGroupBarDecoration() {} -SWindowDecorationExtents CHyprGroupBarDecoration::getWindowDecorationExtents() { - return m_seExtents; +SDecorationPositioningInfo CHyprGroupBarDecoration::getPositioningInfo() { + static auto* const PHEIGHT = &g_pConfigManager->getConfigValuePtr("group:groupbar:height")->intValue; + static auto* const PENABLED = &g_pConfigManager->getConfigValuePtr("group:groupbar:enabled")->intValue; + static auto* const PRENDERTITLES = &g_pConfigManager->getConfigValuePtr("group:groupbar:render_titles")->intValue; + static auto* const PGRADIENTS = &g_pConfigManager->getConfigValuePtr("group:groupbar:gradients")->intValue; + + SDecorationPositioningInfo info; + info.policy = DECORATION_POSITION_STICKY; + info.edges = DECORATION_EDGE_TOP; + info.priority = g_pConfigManager->getConfigValuePtr("group:groupbar:priority")->intValue; + info.reserved = true; + + if (*PENABLED && m_pWindow->m_sSpecialRenderData.decorate) + info.desiredExtents = {{0, BAR_PADDING_OUTER_VERT * 2 + BAR_INDICATOR_HEIGHT + (*PGRADIENTS || *PRENDERTITLES ? *PHEIGHT : 0) + 2}, {0, 0}}; + else + info.desiredExtents = {{0, 0}, {0, 0}}; + + return info; +} + +void CHyprGroupBarDecoration::onPositioningReply(const SDecorationPositioningReply& reply) { + m_bAssignedBox = reply.assignedGeometry; } eDecorationType CHyprGroupBarDecoration::getDecorationType() { return DECORATION_GROUPBAR; } -constexpr int BAR_INDICATOR_HEIGHT = 3; -constexpr int BAR_PADDING_OUTER_VERT = 2; -constexpr int BAR_TEXT_PAD = 2; - // void CHyprGroupBarDecoration::updateWindow(CWindow* pWindow) { - damageEntire(); - - const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(pWindow->m_iWorkspaceID); - - const auto WORKSPACEOFFSET = PWORKSPACE && !pWindow->m_bPinned ? PWORKSPACE->m_vRenderOffset.vec() : Vector2D(); - - static auto* const PRENDERTITLES = &g_pConfigManager->getConfigValuePtr("misc:render_titles_in_groupbar")->intValue; - static auto* const PTITLEFONTSIZE = &g_pConfigManager->getConfigValuePtr("misc:groupbar_titles_font_size")->intValue; - - if (pWindow->m_vRealPosition.vec() + WORKSPACEOFFSET != m_vLastWindowPos || pWindow->m_vRealSize.vec() != m_vLastWindowSize) { - // we draw 3px above the window's border with 3px - - const int BORDERSIZE = pWindow->getRealBorderSize(); - - m_seExtents.topLeft = Vector2D(0, BORDERSIZE + BAR_PADDING_OUTER_VERT * 2 + BAR_INDICATOR_HEIGHT + (*PRENDERTITLES ? *PTITLEFONTSIZE : 0) + 2); - m_seExtents.bottomRight = Vector2D(); - - m_vLastWindowPos = pWindow->m_vRealPosition.vec() + WORKSPACEOFFSET; - m_vLastWindowSize = pWindow->m_vRealSize.vec(); - - invalidateTextures(); - } - if (!m_pWindow->m_sGroupData.pNextWindow) { - m_pWindow->m_vDecosToRemove.push_back(this); + m_pWindow->removeWindowDeco(this); return; } @@ -69,49 +74,52 @@ void CHyprGroupBarDecoration::updateWindow(CWindow* pWindow) { damageEntire(); if (m_dwGroupMembers.size() == 0) { - m_pWindow->m_vDecosToRemove.push_back(this); + m_pWindow->removeWindowDeco(this); return; } } void CHyprGroupBarDecoration::damageEntire() { - wlr_box dm = {m_vLastWindowPos.x - m_seExtents.topLeft.x, m_vLastWindowPos.y - m_seExtents.topLeft.y, m_vLastWindowSize.x + m_seExtents.topLeft.x + m_seExtents.bottomRight.x, - m_seExtents.topLeft.y}; - g_pHyprRenderer->damageBox(&dm); + auto box = assignedBoxGlobal(); + g_pHyprRenderer->damageBox(&box); } void CHyprGroupBarDecoration::draw(CMonitor* pMonitor, float a, const Vector2D& offset) { // get how many bars we will draw int barsToDraw = m_dwGroupMembers.size(); - static auto* const PRENDERTITLES = &g_pConfigManager->getConfigValuePtr("misc:render_titles_in_groupbar")->intValue; - static auto* const PTITLEFONTSIZE = &g_pConfigManager->getConfigValuePtr("misc:groupbar_titles_font_size")->intValue; - static auto* const PGRADIENTS = &g_pConfigManager->getConfigValuePtr("misc:groupbar_gradients")->intValue; + static auto* const PENABLED = &g_pConfigManager->getConfigValuePtr("group:groupbar:enabled")->intValue; + static auto* const PRENDERTITLES = &g_pConfigManager->getConfigValuePtr("group:groupbar:render_titles")->intValue; + static auto* const PTITLEFONTSIZE = &g_pConfigManager->getConfigValuePtr("group:groupbar:font_size")->intValue; + static auto* const PHEIGHT = &g_pConfigManager->getConfigValuePtr("group:groupbar:height")->intValue; + static auto* const PGRADIENTS = &g_pConfigManager->getConfigValuePtr("group:groupbar:gradients")->intValue; - const int BORDERSIZE = m_pWindow->getRealBorderSize(); - - if (!m_pWindow->m_sSpecialRenderData.decorate) + if (!*PENABLED || !m_pWindow->m_sSpecialRenderData.decorate) return; - const int PAD = 2; //2px + const auto ASSIGNEDBOX = assignedBoxGlobal(); - const int BARW = (m_vLastWindowSize.x - PAD * (barsToDraw - 1)) / barsToDraw; + m_fBarWidth = (ASSIGNEDBOX.w - BAR_HORIZONTAL_PADDING * (barsToDraw - 1)) / barsToDraw; - int xoff = 0; + const auto DESIREDHEIGHT = BAR_PADDING_OUTER_VERT * 2 + BAR_INDICATOR_HEIGHT + (*PGRADIENTS || *PRENDERTITLES ? *PHEIGHT : 0) + 2; + if (DESIREDHEIGHT != ASSIGNEDBOX.h) + g_pDecorationPositioner->repositionDeco(this); + + int xoff = 0; for (int i = 0; i < barsToDraw; ++i) { - wlr_box rect = {m_vLastWindowPos.x + xoff - pMonitor->vecPosition.x + offset.x, - m_vLastWindowPos.y - BAR_PADDING_OUTER_VERT - BORDERSIZE - BAR_INDICATOR_HEIGHT - pMonitor->vecPosition.y + offset.y, BARW, BAR_INDICATOR_HEIGHT}; + CBox rect = {ASSIGNEDBOX.x + xoff - pMonitor->vecPosition.x + offset.x, + ASSIGNEDBOX.y + ASSIGNEDBOX.h - BAR_INDICATOR_HEIGHT - BAR_PADDING_OUTER_VERT - pMonitor->vecPosition.y + offset.y, m_fBarWidth, BAR_INDICATOR_HEIGHT}; if (rect.width <= 0 || rect.height <= 0) break; - scaleBox(&rect, pMonitor->scale); + rect.scale(pMonitor->scale); - static auto* const PGROUPCOLACTIVE = &g_pConfigManager->getConfigValuePtr("general:col.group_border_active")->data; - static auto* const PGROUPCOLINACTIVE = &g_pConfigManager->getConfigValuePtr("general:col.group_border")->data; - static auto* const PGROUPCOLACTIVELOCKED = &g_pConfigManager->getConfigValuePtr("general:col.group_border_locked_active")->data; - static auto* const PGROUPCOLINACTIVELOCKED = &g_pConfigManager->getConfigValuePtr("general:col.group_border_locked")->data; + static auto* const PGROUPCOLACTIVE = &g_pConfigManager->getConfigValuePtr("group:groupbar:col.active")->data; + static auto* const PGROUPCOLINACTIVE = &g_pConfigManager->getConfigValuePtr("group:groupbar:col.inactive")->data; + static auto* const PGROUPCOLACTIVELOCKED = &g_pConfigManager->getConfigValuePtr("group:groupbar:col.locked_active")->data; + static auto* const PGROUPCOLINACTIVELOCKED = &g_pConfigManager->getConfigValuePtr("group:groupbar:col.locked_inactive")->data; const bool GROUPLOCKED = m_pWindow->getGroupHead()->m_sGroupData.locked; const auto* const PCOLACTIVE = GROUPLOCKED ? PGROUPCOLACTIVELOCKED : PGROUPCOLACTIVE; @@ -122,45 +130,39 @@ void CHyprGroupBarDecoration::draw(CMonitor* pMonitor, float a, const Vector2D& color.a *= a; g_pHyprOpenGL->renderRect(&rect, color); - // render title if necessary + rect = {ASSIGNEDBOX.x + xoff - pMonitor->vecPosition.x + offset.x, ASSIGNEDBOX.y - pMonitor->vecPosition.y + offset.y + BAR_PADDING_OUTER_VERT, m_fBarWidth, + ASSIGNEDBOX.h - BAR_INDICATOR_HEIGHT - BAR_PADDING_OUTER_VERT * 2}; + rect.scale(pMonitor->scale); + + if (*PGRADIENTS) { + const auto& GRADIENTTEX = (m_dwGroupMembers[i] == g_pCompositor->m_pLastWindow ? (GROUPLOCKED ? m_tGradientLockedActive : m_tGradientActive) : + (GROUPLOCKED ? m_tGradientLockedInactive : m_tGradientInactive)); + if (GRADIENTTEX.m_iTexID != 0) + g_pHyprOpenGL->renderTexture(GRADIENTTEX, &rect, 1.0); + } + if (*PRENDERTITLES) { CTitleTex* pTitleTex = textureFromTitle(m_dwGroupMembers[i]->m_szTitle); if (!pTitleTex) - pTitleTex = - m_sTitleTexs.titleTexs - .emplace_back(std::make_unique(m_dwGroupMembers[i], Vector2D{BARW * pMonitor->scale, (*PTITLEFONTSIZE + 2 * BAR_TEXT_PAD) * pMonitor->scale})) - .get(); + pTitleTex = m_sTitleTexs.titleTexs + .emplace_back(std::make_unique(m_dwGroupMembers[i], + Vector2D{m_fBarWidth * pMonitor->scale, (*PTITLEFONTSIZE + 2 * BAR_TEXT_PAD) * pMonitor->scale})) + .get(); - rect.height = (*PTITLEFONTSIZE + 2 * BAR_TEXT_PAD) * 0.8 * pMonitor->scale; - - rect.y -= rect.height; - rect.width = BARW * pMonitor->scale; - - refreshGradients(); - - if (*PGRADIENTS) - g_pHyprOpenGL->renderTexture((m_dwGroupMembers[i] == g_pCompositor->m_pLastWindow ? m_tGradientActive : m_tGradientInactive), &rect, 1.0); - - rect.y -= (*PTITLEFONTSIZE + 2 * BAR_TEXT_PAD) * 0.2 * pMonitor->scale; + rect.y += (ASSIGNEDBOX.h / 2.0 - (*PTITLEFONTSIZE + 2 * BAR_TEXT_PAD) / 2.0) * pMonitor->scale; rect.height = (*PTITLEFONTSIZE + 2 * BAR_TEXT_PAD) * pMonitor->scale; g_pHyprOpenGL->renderTexture(pTitleTex->tex, &rect, 1.f); } - xoff += PAD + BARW; + xoff += BAR_HORIZONTAL_PADDING + m_fBarWidth; } if (*PRENDERTITLES) invalidateTextures(); } -SWindowDecorationExtents CHyprGroupBarDecoration::getWindowDecorationReservedArea() { - static auto* const PRENDERTITLES = &g_pConfigManager->getConfigValuePtr("misc:render_titles_in_groupbar")->intValue; - static auto* const PTITLEFONTSIZE = &g_pConfigManager->getConfigValuePtr("misc:groupbar_titles_font_size")->intValue; - return SWindowDecorationExtents{{0, BAR_INDICATOR_HEIGHT + BAR_PADDING_OUTER_VERT * 2 + (*PRENDERTITLES ? *PTITLEFONTSIZE : 0)}, {}}; -} - CTitleTex* CHyprGroupBarDecoration::textureFromTitle(const std::string& title) { for (auto& tex : m_sTitleTexs.titleTexs) { if (tex->szContent == title) @@ -180,8 +182,9 @@ CTitleTex::CTitleTex(CWindow* pWindow, const Vector2D& bufferSize) { const auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, bufferSize.x, bufferSize.y); const auto CAIRO = cairo_create(CAIROSURFACE); - static auto* const PTITLEFONTSIZE = &g_pConfigManager->getConfigValuePtr("misc:groupbar_titles_font_size")->intValue; - static auto* const PTEXTCOLOR = &g_pConfigManager->getConfigValuePtr("misc:groupbar_text_color")->intValue; + static auto* const PTITLEFONTFAMILY = &g_pConfigManager->getConfigValuePtr("group:groupbar:font_family")->strValue; + static auto* const PTITLEFONTSIZE = &g_pConfigManager->getConfigValuePtr("group:groupbar:font_size")->intValue; + static auto* const PTEXTCOLOR = &g_pConfigManager->getConfigValuePtr("group:groupbar:text_color")->intValue; const CColor COLOR = CColor(*PTEXTCOLOR); @@ -195,7 +198,7 @@ CTitleTex::CTitleTex(CWindow* pWindow, const Vector2D& bufferSize) { PangoLayout* layout = pango_cairo_create_layout(CAIRO); pango_layout_set_text(layout, szContent.c_str(), -1); - PangoFontDescription* fontDesc = pango_font_description_from_string("Sans"); + PangoFontDescription* fontDesc = pango_font_description_from_string(PTITLEFONTFAMILY->c_str()); pango_font_description_set_size(fontDesc, *PTITLEFONTSIZE * PANGO_SCALE); pango_layout_set_font_description(layout, fontDesc); pango_font_description_free(fontDesc); @@ -242,7 +245,10 @@ CTitleTex::~CTitleTex() { tex.destroyTexture(); } -void renderGradientTo(CTexture& tex, const CColor& grad) { +void renderGradientTo(CTexture& tex, CGradientValueData* grad) { + + if (!g_pCompositor->m_pLastMonitor) + return; const Vector2D& bufferSize = g_pCompositor->m_pLastMonitor->vecPixelSize; @@ -257,8 +263,12 @@ void renderGradientTo(CTexture& tex, const CColor& grad) { cairo_pattern_t* pattern; pattern = cairo_pattern_create_linear(0, 0, 0, bufferSize.y); - cairo_pattern_add_color_stop_rgba(pattern, 1, grad.r, grad.g, grad.b, grad.a); - cairo_pattern_add_color_stop_rgba(pattern, 0, grad.r, grad.g, grad.b, 0); + + for (unsigned long i = 0; i < grad->m_vColors.size(); i++) { + cairo_pattern_add_color_stop_rgba(pattern, 1 - (double)(i + 1) / (grad->m_vColors.size() + 1), grad->m_vColors[i].r, grad->m_vColors[i].g, grad->m_vColors[i].b, + grad->m_vColors[i].a); + } + cairo_rectangle(CAIRO, 0, 0, bufferSize.x, bufferSize.y); cairo_set_source(CAIRO, pattern); cairo_fill(CAIRO); @@ -285,23 +295,207 @@ void renderGradientTo(CTexture& tex, const CColor& grad) { cairo_surface_destroy(CAIROSURFACE); } -void CHyprGroupBarDecoration::refreshGradients() { - if (m_tGradientActive.m_iTexID > 0) +void refreshGroupBarGradients() { + static auto* const PGRADIENTS = &g_pConfigManager->getConfigValuePtr("group:groupbar:enabled")->intValue; + static auto* const PENABLED = &g_pConfigManager->getConfigValuePtr("group:groupbar:gradients")->intValue; + + CGradientValueData* PGROUPCOLACTIVE = (CGradientValueData*)g_pConfigManager->getConfigValuePtr("group:groupbar:col.active")->data.get(); + CGradientValueData* PGROUPCOLINACTIVE = (CGradientValueData*)g_pConfigManager->getConfigValuePtr("group:groupbar:col.inactive")->data.get(); + CGradientValueData* PGROUPCOLACTIVELOCKED = (CGradientValueData*)g_pConfigManager->getConfigValuePtr("group:groupbar:col.locked_active")->data.get(); + CGradientValueData* PGROUPCOLINACTIVELOCKED = (CGradientValueData*)g_pConfigManager->getConfigValuePtr("group:groupbar:col.locked_inactive")->data.get(); + + g_pHyprRenderer->makeEGLCurrent(); + + if (m_tGradientActive.m_iTexID != 0) { + m_tGradientActive.destroyTexture(); + m_tGradientInactive.destroyTexture(); + m_tGradientLockedActive.destroyTexture(); + m_tGradientLockedInactive.destroyTexture(); + } + + if (!*PENABLED || !*PGRADIENTS) return; - static auto* const PGROUPCOLACTIVE = &g_pConfigManager->getConfigValuePtr("general:col.group_border_active")->data; - static auto* const PGROUPCOLINACTIVE = &g_pConfigManager->getConfigValuePtr("general:col.group_border")->data; - static auto* const PGROUPCOLACTIVELOCKED = &g_pConfigManager->getConfigValuePtr("general:col.group_border_locked_active")->data; - static auto* const PGROUPCOLINACTIVELOCKED = &g_pConfigManager->getConfigValuePtr("general:col.group_border_locked")->data; - - const bool GROUPLOCKED = m_pWindow->getGroupHead()->m_sGroupData.locked; - const auto* const PCOLACTIVE = GROUPLOCKED ? PGROUPCOLACTIVELOCKED : PGROUPCOLACTIVE; - const auto* const PCOLINACTIVE = GROUPLOCKED ? PGROUPCOLINACTIVELOCKED : PGROUPCOLINACTIVE; - - renderGradientTo(m_tGradientActive, ((CGradientValueData*)PCOLACTIVE->get())->m_vColors[0]); - renderGradientTo(m_tGradientInactive, ((CGradientValueData*)PCOLINACTIVE->get())->m_vColors[0]); + renderGradientTo(m_tGradientActive, PGROUPCOLACTIVE); + renderGradientTo(m_tGradientInactive, PGROUPCOLINACTIVE); + renderGradientTo(m_tGradientLockedActive, PGROUPCOLACTIVELOCKED); + renderGradientTo(m_tGradientLockedInactive, PGROUPCOLINACTIVELOCKED); } -bool CHyprGroupBarDecoration::allowsInput() { +bool CHyprGroupBarDecoration::onBeginWindowDragOnDeco(const Vector2D& pos) { + if (m_pWindow == m_pWindow->m_sGroupData.pNextWindow) + return false; + + const float BARRELATIVEX = pos.x - assignedBoxGlobal().x; + const int WINDOWINDEX = (BARRELATIVEX) / (m_fBarWidth + BAR_HORIZONTAL_PADDING); + + if (BARRELATIVEX - (m_fBarWidth + BAR_HORIZONTAL_PADDING) * WINDOWINDEX > m_fBarWidth) + return false; + + CWindow* pWindow = m_pWindow->getGroupWindowByIndex(WINDOWINDEX); + + // hack + g_pLayoutManager->getCurrentLayout()->onWindowRemoved(pWindow); + if (!pWindow->m_bIsFloating) { + const bool GROUPSLOCKEDPREV = g_pKeybindManager->m_bGroupsLocked; + g_pKeybindManager->m_bGroupsLocked = true; + g_pLayoutManager->getCurrentLayout()->onWindowCreated(pWindow); + g_pKeybindManager->m_bGroupsLocked = GROUPSLOCKEDPREV; + } + + g_pInputManager->currentlyDraggedWindow = pWindow; + + if (!g_pCompositor->isWindowActive(pWindow)) + g_pCompositor->focusWindow(pWindow); + return true; } + +bool CHyprGroupBarDecoration::onEndWindowDragOnDeco(const Vector2D& pos, CWindow* pDraggedWindow) { + if (!pDraggedWindow->canBeGroupedInto(m_pWindow)) + return false; + + const float BARRELATIVEX = pos.x - assignedBoxGlobal().x - m_fBarWidth / 2; + const int WINDOWINDEX = BARRELATIVEX < 0 ? -1 : (BARRELATIVEX) / (m_fBarWidth + BAR_HORIZONTAL_PADDING); + + CWindow* pWindowInsertAfter = m_pWindow->getGroupWindowByIndex(WINDOWINDEX); + CWindow* pWindowInsertEnd = pWindowInsertAfter->m_sGroupData.pNextWindow; + CWindow* pDraggedHead = pDraggedWindow->m_sGroupData.pNextWindow ? pDraggedWindow->getGroupHead() : pDraggedWindow; + + if (pDraggedWindow->m_sGroupData.pNextWindow) { + + // stores group data + std::vector members; + CWindow* curr = pDraggedHead; + const bool WASLOCKED = pDraggedHead->m_sGroupData.locked; + do { + members.push_back(curr); + curr = curr->m_sGroupData.pNextWindow; + } while (curr != members[0]); + + // removes all windows + for (CWindow* w : members) { + w->m_sGroupData.pNextWindow = nullptr; + w->m_sGroupData.head = false; + w->m_sGroupData.locked = false; + g_pLayoutManager->getCurrentLayout()->onWindowRemoved(w); + } + + // restores the group + for (auto it = members.begin(); it != members.end(); ++it) { + if (std::next(it) != members.end()) + (*it)->m_sGroupData.pNextWindow = *std::next(it); + else + (*it)->m_sGroupData.pNextWindow = members[0]; + } + members[0]->m_sGroupData.head = true; + members[0]->m_sGroupData.locked = WASLOCKED; + } else { + g_pLayoutManager->getCurrentLayout()->onWindowRemoved(pDraggedWindow); + } + + pWindowInsertAfter->insertWindowToGroup(pDraggedWindow); + + if (WINDOWINDEX == -1) + std::swap(pDraggedHead->m_sGroupData.head, pWindowInsertEnd->m_sGroupData.head); + + m_pWindow->setGroupCurrent(pDraggedWindow); + pDraggedWindow->applyGroupRules(); + pDraggedWindow->updateWindowDecos(); + g_pLayoutManager->getCurrentLayout()->recalculateWindow(pDraggedWindow); + + if (!pDraggedWindow->getDecorationByType(DECORATION_GROUPBAR)) + pDraggedWindow->addWindowDeco(std::make_unique(pDraggedWindow)); + + return true; +} + +bool CHyprGroupBarDecoration::onMouseButtonOnDeco(const Vector2D& pos, wlr_pointer_button_event* e) { + if (m_pWindow->m_bIsFullscreen && g_pCompositor->getWorkspaceByID(m_pWindow->m_iWorkspaceID)->m_efFullscreenMode == FULLSCREEN_FULL) + return true; + + const float BARRELATIVEX = pos.x - assignedBoxGlobal().x; + const int WINDOWINDEX = (BARRELATIVEX) / (m_fBarWidth + BAR_HORIZONTAL_PADDING); + + // close window on middle click + if (e->button == 274) { + static Vector2D pressedCursorPos; + + if (e->state == WLR_BUTTON_PRESSED) + pressedCursorPos = pos; + else if (e->state == WLR_BUTTON_RELEASED && pressedCursorPos == pos) + g_pXWaylandManager->sendCloseWindow(m_pWindow->getGroupWindowByIndex(WINDOWINDEX)); + + return true; + } + + if (e->state != WLR_BUTTON_PRESSED) + return true; + + // click on padding + if (BARRELATIVEX - (m_fBarWidth + BAR_HORIZONTAL_PADDING) * WINDOWINDEX > m_fBarWidth) { + if (!g_pCompositor->isWindowActive(m_pWindow)) + g_pCompositor->focusWindow(m_pWindow); + return true; + } + + CWindow* pWindow = m_pWindow->getGroupWindowByIndex(WINDOWINDEX); + + if (pWindow != m_pWindow) + pWindow->setGroupCurrent(pWindow); + + if (pWindow->m_bIsFloating) + g_pCompositor->changeWindowZOrder(pWindow, 1); + + return true; +} + +bool CHyprGroupBarDecoration::onScrollOnDeco(const Vector2D& pos, wlr_pointer_axis_event* e) { + static auto* const PGROUPBARSCROLLING = &g_pConfigManager->getConfigValuePtr("group:groupbar:scrolling")->intValue; + + if (!*PGROUPBARSCROLLING || !m_pWindow->m_sGroupData.pNextWindow) { + return false; + } + + if (e->delta > 0) + m_pWindow->setGroupCurrent(m_pWindow->m_sGroupData.pNextWindow); + else + m_pWindow->setGroupCurrent(m_pWindow->getGroupPrevious()); + + return true; +} + +bool CHyprGroupBarDecoration::onInputOnDeco(const eInputType type, const Vector2D& mouseCoords, std::any data) { + switch (type) { + case INPUT_TYPE_AXIS: return onScrollOnDeco(mouseCoords, std::any_cast(data)); + case INPUT_TYPE_BUTTON: return onMouseButtonOnDeco(mouseCoords, std::any_cast(data)); + case INPUT_TYPE_DRAG_START: return onBeginWindowDragOnDeco(mouseCoords); + case INPUT_TYPE_DRAG_END: return onEndWindowDragOnDeco(mouseCoords, std::any_cast(data)); + default: return false; + } +} + +eDecorationLayer CHyprGroupBarDecoration::getDecorationLayer() { + return DECORATION_LAYER_OVER; +} + +uint64_t CHyprGroupBarDecoration::getDecorationFlags() { + return DECORATION_ALLOWS_MOUSE_INPUT; +} + +std::string CHyprGroupBarDecoration::getDisplayName() { + return "GroupBar"; +} + +CBox CHyprGroupBarDecoration::assignedBoxGlobal() { + CBox box = m_bAssignedBox; + box.translate(g_pDecorationPositioner->getEdgeDefinedPoint(DECORATION_EDGE_TOP, m_pWindow)); + + const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(m_pWindow->m_iWorkspaceID); + + if (!PWORKSPACE) + return box; + + const auto WORKSPACEOFFSET = PWORKSPACE && !m_pWindow->m_bPinned ? PWORKSPACE->m_vRenderOffset.vec() : Vector2D(); + return box.translate(WORKSPACEOFFSET); +} diff --git a/src/render/decorations/CHyprGroupBarDecoration.hpp b/src/render/decorations/CHyprGroupBarDecoration.hpp index 118c7a64..04f5123b 100644 --- a/src/render/decorations/CHyprGroupBarDecoration.hpp +++ b/src/render/decorations/CHyprGroupBarDecoration.hpp @@ -16,39 +16,53 @@ class CTitleTex { CWindow* pWindowOwner = nullptr; }; +void refreshGroupBarGradients(); + class CHyprGroupBarDecoration : public IHyprWindowDecoration { public: CHyprGroupBarDecoration(CWindow*); virtual ~CHyprGroupBarDecoration(); - virtual SWindowDecorationExtents getWindowDecorationExtents(); + virtual SDecorationPositioningInfo getPositioningInfo(); - virtual void draw(CMonitor*, float a, const Vector2D& offset); + virtual void onPositioningReply(const SDecorationPositioningReply& reply); - virtual eDecorationType getDecorationType(); + virtual void draw(CMonitor*, float a, const Vector2D& offset); - virtual void updateWindow(CWindow*); + virtual eDecorationType getDecorationType(); - virtual void damageEntire(); + virtual void updateWindow(CWindow*); - virtual SWindowDecorationExtents getWindowDecorationReservedArea(); + virtual void damageEntire(); - virtual bool allowsInput(); + virtual bool onInputOnDeco(const eInputType, const Vector2D&, std::any = {}); + + virtual eDecorationLayer getDecorationLayer(); + + virtual uint64_t getDecorationFlags(); + + virtual std::string getDisplayName(); private: SWindowDecorationExtents m_seExtents; + CBox m_bAssignedBox = {0}; + CWindow* m_pWindow = nullptr; - Vector2D m_vLastWindowPos; - Vector2D m_vLastWindowSize; - std::deque m_dwGroupMembers; + float m_fBarWidth; + CTitleTex* textureFromTitle(const std::string&); void invalidateTextures(); - void refreshGradients(); + CBox assignedBoxGlobal(); + + bool onBeginWindowDragOnDeco(const Vector2D&); + bool onEndWindowDragOnDeco(const Vector2D&, CWindow*); + bool onMouseButtonOnDeco(const Vector2D&, wlr_pointer_button_event*); + bool onScrollOnDeco(const Vector2D&, wlr_pointer_axis_event*); struct STitleTexs { // STitleTexs* overriden = nullptr; // TODO: make shit shared in-group to decrease VRAM usage. diff --git a/src/render/decorations/DecorationPositioner.cpp b/src/render/decorations/DecorationPositioner.cpp new file mode 100644 index 00000000..e19c361b --- /dev/null +++ b/src/render/decorations/DecorationPositioner.cpp @@ -0,0 +1,373 @@ +#include "DecorationPositioner.hpp" +#include "../../Compositor.hpp" + +CDecorationPositioner::CDecorationPositioner() { + g_pHookSystem->hookDynamic("closeWindow", [this](void* call, SCallbackInfo& info, std::any data) { + auto* const PWINDOW = std::any_cast(data); + this->onWindowUnmap(PWINDOW); + }); + + g_pHookSystem->hookDynamic("openWindow", [this](void* call, SCallbackInfo& info, std::any data) { + auto* const PWINDOW = std::any_cast(data); + this->onWindowMap(PWINDOW); + }); +} + +Vector2D CDecorationPositioner::getEdgeDefinedPoint(uint32_t edges, CWindow* pWindow) { + const bool TOP = edges & DECORATION_EDGE_TOP; + const bool BOTTOM = edges & DECORATION_EDGE_BOTTOM; + const bool LEFT = edges & DECORATION_EDGE_LEFT; + const bool RIGHT = edges & DECORATION_EDGE_RIGHT; + + const int EDGESNO = TOP + BOTTOM + LEFT + RIGHT; + + if (EDGESNO == 0 || EDGESNO == 3 || EDGESNO > 4) { + Debug::log(ERR, "getEdgeDefinedPoint: invalid number of edges"); + return {}; + } + + CBox wb = pWindow->getWindowMainSurfaceBox(); + + if (EDGESNO == 4) + return wb.pos(); + + if (EDGESNO == 1) { + if (TOP) + return wb.pos() + Vector2D{wb.size().x / 2.0, 0}; + else if (BOTTOM) + return wb.pos() + Vector2D{wb.size().x / 2.0, wb.size().y}; + else if (LEFT) + return wb.pos() + Vector2D{0, wb.size().y / 2.0}; + else if (RIGHT) + return wb.pos() + Vector2D{wb.size().x, wb.size().y / 2.0}; + UNREACHABLE(); + } else { + if (TOP && LEFT) + return wb.pos(); + if (TOP && RIGHT) + return wb.pos() + Vector2D{wb.size().x, 0}; + if (BOTTOM && RIGHT) + return wb.pos() + wb.size(); + if (BOTTOM && LEFT) + return wb.pos() + Vector2D{0, wb.size().y}; + UNREACHABLE(); + } + UNREACHABLE(); + return {}; +} + +void CDecorationPositioner::uncacheDecoration(IHyprWindowDecoration* deco) { + std::erase_if(m_vWindowPositioningDatas, [&](const auto& data) { return data->pDecoration == deco; }); + + const auto WIT = std::find_if(m_mWindowDatas.begin(), m_mWindowDatas.end(), [&](const auto& other) { return other.first == deco->m_pWindow; }); + if (WIT == m_mWindowDatas.end()) + return; + + WIT->second.needsRecalc = true; +} + +void CDecorationPositioner::repositionDeco(IHyprWindowDecoration* deco) { + uncacheDecoration(deco); + onWindowUpdate(deco->m_pWindow); +} + +CDecorationPositioner::SWindowPositioningData* CDecorationPositioner::getDataFor(IHyprWindowDecoration* pDecoration, CWindow* pWindow) { + auto it = std::find_if(m_vWindowPositioningDatas.begin(), m_vWindowPositioningDatas.end(), [&](const auto& el) { return el->pDecoration == pDecoration; }); + + if (it != m_vWindowPositioningDatas.end()) + return it->get(); + + const auto DATA = m_vWindowPositioningDatas.emplace_back(std::make_unique(pWindow, pDecoration)).get(); + + DATA->positioningInfo = pDecoration->getPositioningInfo(); + + return DATA; +} + +void CDecorationPositioner::sanitizeDatas() { + std::erase_if(m_mWindowDatas, [](const auto& other) { return !g_pCompositor->windowExists(other.first); }); + std::erase_if(m_vWindowPositioningDatas, [](const auto& other) { + if (!g_pCompositor->windowExists(other->pWindow)) + return true; + if (std::find_if(other->pWindow->m_dWindowDecorations.begin(), other->pWindow->m_dWindowDecorations.end(), + [&](const auto& el) { return el.get() == other->pDecoration; }) == other->pWindow->m_dWindowDecorations.end()) + return true; + return false; + }); +} + +void CDecorationPositioner::forceRecalcFor(CWindow* pWindow) { + const auto WIT = std::find_if(m_mWindowDatas.begin(), m_mWindowDatas.end(), [&](const auto& other) { return other.first == pWindow; }); + if (WIT == m_mWindowDatas.end()) + return; + + const auto WINDOWDATA = &WIT->second; + + WINDOWDATA->needsRecalc = true; +} + +void CDecorationPositioner::onWindowUpdate(CWindow* pWindow) { + if (!g_pCompositor->windowExists(pWindow) || !pWindow->m_bIsMapped) + return; + + const auto WIT = std::find_if(m_mWindowDatas.begin(), m_mWindowDatas.end(), [&](const auto& other) { return other.first == pWindow; }); + if (WIT == m_mWindowDatas.end()) + return; + + const auto WINDOWDATA = &WIT->second; + + sanitizeDatas(); + + // + std::vector datas; + for (auto& wd : pWindow->m_dWindowDecorations) { + datas.push_back(getDataFor(wd.get(), pWindow)); + } + + if (WINDOWDATA->lastWindowSize == pWindow->m_vRealSize.vec() /* position not changed */ + && + std::all_of(m_vWindowPositioningDatas.begin(), m_vWindowPositioningDatas.end(), [pWindow](const auto& data) { return pWindow != data->pWindow || !data->needsReposition; }) + /* all window datas are either not for this window or don't need a reposition */ + && !WINDOWDATA->needsRecalc /* window doesn't need recalc */ + ) + return; + + WINDOWDATA->lastWindowSize = pWindow->m_vRealSize.vec(); + WINDOWDATA->needsRecalc = false; + const bool EPHEMERAL = pWindow->m_vRealSize.isBeingAnimated(); + + std::sort(datas.begin(), datas.end(), [](const auto& a, const auto& b) { return a->positioningInfo.priority > b->positioningInfo.priority; }); + + CBox wb = pWindow->getWindowMainSurfaceBox(); + + // calc reserved + float reservedXL = 0, reservedYT = 0, reservedXR = 0, reservedYB = 0; + for (size_t i = 0; i < datas.size(); ++i) { + auto* const wd = datas[i]; + + if (!wd->positioningInfo.reserved) + continue; + + const bool TOP = wd->positioningInfo.edges & DECORATION_EDGE_TOP; + const bool BOTTOM = wd->positioningInfo.edges & DECORATION_EDGE_BOTTOM; + const bool LEFT = wd->positioningInfo.edges & DECORATION_EDGE_LEFT; + const bool RIGHT = wd->positioningInfo.edges & DECORATION_EDGE_RIGHT; + + if (LEFT) + reservedXL += wd->positioningInfo.desiredExtents.topLeft.x; + if (RIGHT) + reservedXR += wd->positioningInfo.desiredExtents.bottomRight.x; + if (TOP) + reservedYT += wd->positioningInfo.desiredExtents.topLeft.y; + if (BOTTOM) + reservedYB += wd->positioningInfo.desiredExtents.bottomRight.y; + } + + WINDOWDATA->reserved = {{reservedXL, reservedYT}, {reservedXR, reservedYB}}; + + float stickyOffsetXL = 0, stickyOffsetYT = 0, stickyOffsetXR = 0, stickyOffsetYB = 0; + + for (size_t i = 0; i < datas.size(); ++i) { + auto* const wd = datas[i]; + + wd->needsReposition = false; + + const bool TOP = wd->positioningInfo.edges & DECORATION_EDGE_TOP; + const bool BOTTOM = wd->positioningInfo.edges & DECORATION_EDGE_BOTTOM; + const bool LEFT = wd->positioningInfo.edges & DECORATION_EDGE_LEFT; + const bool RIGHT = wd->positioningInfo.edges & DECORATION_EDGE_RIGHT; + const int EDGESNO = TOP + BOTTOM + LEFT + RIGHT; + const bool SOLID = !(wd->pDecoration->getDecorationFlags() & DECORATION_NON_SOLID); + + if (wd->positioningInfo.policy == DECORATION_POSITION_ABSOLUTE) { + + if (SOLID) { + if (LEFT) + stickyOffsetXL += wd->positioningInfo.desiredExtents.topLeft.x; + if (RIGHT) + stickyOffsetXR += wd->positioningInfo.desiredExtents.bottomRight.x; + if (TOP) + stickyOffsetYT += wd->positioningInfo.desiredExtents.topLeft.y; + if (BOTTOM) + stickyOffsetYB += wd->positioningInfo.desiredExtents.bottomRight.y; + } + + wd->lastReply = {}; + wd->pDecoration->onPositioningReply({}); + continue; + } + + if (wd->positioningInfo.policy == DECORATION_POSITION_STICKY) { + if (EDGESNO != 1 && EDGESNO != 4) { + wd->lastReply = {}; + wd->pDecoration->onPositioningReply({}); + continue; + } + + auto desiredSize = 0; + if (LEFT) + desiredSize = wd->positioningInfo.desiredExtents.topLeft.x; + else if (RIGHT) + desiredSize = wd->positioningInfo.desiredExtents.bottomRight.x; + else if (TOP) + desiredSize = wd->positioningInfo.desiredExtents.topLeft.y; + else + desiredSize = wd->positioningInfo.desiredExtents.bottomRight.y; + + const auto EDGEPOINT = getEdgeDefinedPoint(wd->positioningInfo.edges, pWindow); + + Vector2D pos, size; + + if (EDGESNO == 4) { + pos = wb.pos() - EDGEPOINT - Vector2D{stickyOffsetXL + desiredSize, stickyOffsetYT + desiredSize}; + size = wb.size() + Vector2D{stickyOffsetXL + stickyOffsetXR + desiredSize * 2, stickyOffsetYB + stickyOffsetYT + desiredSize * 2}; + + stickyOffsetXL += desiredSize; + stickyOffsetXR += desiredSize; + stickyOffsetYT += desiredSize; + stickyOffsetYB += desiredSize; + } else if (LEFT) { + pos = wb.pos() - EDGEPOINT - Vector2D{stickyOffsetXL, -stickyOffsetYT}; + pos.x -= desiredSize; + size = {desiredSize, wb.size().y + stickyOffsetYB + stickyOffsetYT}; + + if (SOLID) + stickyOffsetXL += desiredSize; + } else if (RIGHT) { + pos = wb.pos() + Vector2D{wb.size().x, 0} - EDGEPOINT + Vector2D{stickyOffsetXR, -stickyOffsetYT}; + size = {desiredSize, wb.size().y + stickyOffsetYB + stickyOffsetYT}; + + if (SOLID) + stickyOffsetXR += desiredSize; + } else if (TOP) { + pos = wb.pos() - EDGEPOINT - Vector2D{stickyOffsetXL, stickyOffsetYT}; + pos.y -= desiredSize; + size = {wb.size().x + stickyOffsetXL + stickyOffsetXR, desiredSize}; + + if (SOLID) + stickyOffsetYT += desiredSize; + } else { + pos = wb.pos() + Vector2D{0, wb.size().y} - EDGEPOINT - Vector2D{stickyOffsetXL, stickyOffsetYB}; + size = {wb.size().x + stickyOffsetXL + stickyOffsetXR, desiredSize}; + + if (SOLID) + stickyOffsetYB += desiredSize; + } + + wd->lastReply = {{pos, size}, EPHEMERAL}; + wd->pDecoration->onPositioningReply(wd->lastReply); + + continue; + } else { + // invalid + wd->lastReply = {}; + wd->pDecoration->onPositioningReply({}); + continue; + } + } + + WINDOWDATA->extents = {{stickyOffsetXL + reservedXL, stickyOffsetYT + reservedYT}, {stickyOffsetXR + reservedXR, stickyOffsetYB + reservedYB}}; +} + +void CDecorationPositioner::onWindowUnmap(CWindow* pWindow) { + std::erase_if(m_vWindowPositioningDatas, [&](const auto& data) { return data->pWindow == pWindow; }); + m_mWindowDatas.erase(pWindow); +} + +void CDecorationPositioner::onWindowMap(CWindow* pWindow) { + m_mWindowDatas[pWindow] = {}; +} + +SWindowDecorationExtents CDecorationPositioner::getWindowDecorationReserved(CWindow* pWindow) { + try { + const auto E = m_mWindowDatas.at(pWindow); + return E.reserved; + } catch (std::out_of_range& e) { return {}; } +} + +SWindowDecorationExtents CDecorationPositioner::getWindowDecorationExtents(CWindow* pWindow, bool inputOnly) { + CBox accum = pWindow->getWindowMainSurfaceBox(); + + for (auto& data : m_vWindowPositioningDatas) { + if (data->pWindow != pWindow) + continue; + + if (!data->pWindow || !data->pDecoration) + continue; + + if (!(data->pDecoration->getDecorationFlags() & DECORATION_ALLOWS_MOUSE_INPUT) && inputOnly) + continue; + + CBox decoBox; + + if (data->positioningInfo.policy == DECORATION_POSITION_ABSOLUTE) { + decoBox = data->pWindow->getWindowMainSurfaceBox(); + decoBox.addExtents(data->positioningInfo.desiredExtents); + } else { + decoBox = data->lastReply.assignedGeometry; + const auto EDGEPOINT = getEdgeDefinedPoint(data->positioningInfo.edges, pWindow); + decoBox.translate(EDGEPOINT); + } + + SWindowDecorationExtents extentsToAdd; + + if (decoBox.x < accum.x) + extentsToAdd.topLeft.x = accum.x - decoBox.x; + if (decoBox.y < accum.y) + extentsToAdd.topLeft.y = accum.y - decoBox.y; + if (decoBox.x + decoBox.w > accum.x + accum.w) + extentsToAdd.bottomRight.x = (decoBox.x + decoBox.w) - (accum.x + accum.w); + if (decoBox.y + decoBox.h > accum.y + accum.h) + extentsToAdd.bottomRight.y = (decoBox.y + decoBox.h) - (accum.y + accum.h); + + accum.addExtents(extentsToAdd); + } + + return accum.extentsFrom(pWindow->getWindowMainSurfaceBox()); +} + +CBox CDecorationPositioner::getBoxWithIncludedDecos(CWindow* pWindow) { + CBox accum = pWindow->getWindowMainSurfaceBox(); + + for (auto& data : m_vWindowPositioningDatas) { + if (data->pWindow != pWindow) + continue; + + if (!(data->pDecoration->getDecorationFlags() & DECORATION_PART_OF_MAIN_WINDOW)) + continue; + + CBox decoBox; + + if (data->positioningInfo.policy == DECORATION_POSITION_ABSOLUTE) { + decoBox = data->pWindow->getWindowMainSurfaceBox(); + decoBox.addExtents(data->positioningInfo.desiredExtents); + } else { + decoBox = data->lastReply.assignedGeometry; + const auto EDGEPOINT = getEdgeDefinedPoint(data->positioningInfo.edges, pWindow); + decoBox.translate(EDGEPOINT); + } + + SWindowDecorationExtents extentsToAdd; + + if (decoBox.x < accum.x) + extentsToAdd.topLeft.x = accum.x - decoBox.x; + if (decoBox.y < accum.y) + extentsToAdd.topLeft.y = accum.y - decoBox.y; + if (decoBox.x + decoBox.w > accum.x + accum.w) + extentsToAdd.bottomRight.x = (decoBox.x + decoBox.w) - (accum.x + accum.w); + if (decoBox.y + decoBox.h > accum.y + accum.h) + extentsToAdd.bottomRight.y = (decoBox.y + decoBox.h) - (accum.y + accum.h); + + accum.addExtents(extentsToAdd); + } + + return accum; +} + +CBox CDecorationPositioner::getWindowDecorationBox(IHyprWindowDecoration* deco) { + const auto DATA = getDataFor(deco, deco->m_pWindow); + + CBox box = DATA->lastReply.assignedGeometry; + box.translate(getEdgeDefinedPoint(DATA->positioningInfo.edges, deco->m_pWindow)); + return box; +} diff --git a/src/render/decorations/DecorationPositioner.hpp b/src/render/decorations/DecorationPositioner.hpp new file mode 100644 index 00000000..140ecace --- /dev/null +++ b/src/render/decorations/DecorationPositioner.hpp @@ -0,0 +1,99 @@ +#pragma once + +#include +#include +#include +#include +#include "../../helpers/Box.hpp" + +class CWindow; +class IHyprWindowDecoration; + +enum eDecorationPositioningPolicy { + DECORATION_POSITION_ABSOLUTE = 0, /* Decoration wants absolute positioning */ + DECORATION_POSITION_STICKY, /* Decoration is stuck to some edge of a window */ +}; + +enum eDecorationEdges { + DECORATION_EDGE_TOP = 1 << 0, + DECORATION_EDGE_BOTTOM = 1 << 1, + DECORATION_EDGE_LEFT = 1 << 2, + DECORATION_EDGE_RIGHT = 1 << 3 +}; + +/* +Request the positioner to position a decoration + +DECORATION_POSITION_ABSOLUTE: + - desiredExtents has to contain the extents. Edges has to have the edges used. + - reserved allowed +DECORATION_POSITION_STICKY: + - one edge allowed + - priority allowed + - desiredExtents contains the desired extents. Any other edge than the one selected is ignored. + - reserved is allowed +*/ +struct SDecorationPositioningInfo { + eDecorationPositioningPolicy policy = DECORATION_POSITION_ABSOLUTE; + uint32_t edges = 0; // enum eDecorationEdges + uint32_t priority = 10; // priority, decos will be evaluated high -> low + SWindowDecorationExtents desiredExtents; + bool reserved = false; // if true, geometry will use reserved area +}; + +/* +A reply from the positioner. This may be sent multiple times, if anything changes. + +DECORATION_POSITION_ABSOLUTE: + - assignedGeometry is empty +DECORATION_POSITION_STICKY: + - assignedGeometry is relative to the edge's center point + - ephemeral is sent +*/ +struct SDecorationPositioningReply { + CBox assignedGeometry; + bool ephemeral = false; // if true, means it's a result of an animation and will change soon. +}; + +class CDecorationPositioner { + public: + CDecorationPositioner(); + + Vector2D getEdgeDefinedPoint(uint32_t edges, CWindow* pWindow); + + // called on resize, or insert/removal of a new deco + void onWindowUpdate(CWindow* pWindow); + void uncacheDecoration(IHyprWindowDecoration* deco); + SWindowDecorationExtents getWindowDecorationReserved(CWindow* pWindow); + SWindowDecorationExtents getWindowDecorationExtents(CWindow* pWindow, bool inputOnly = false); + CBox getBoxWithIncludedDecos(CWindow* pWindow); + void repositionDeco(IHyprWindowDecoration* deco); + CBox getWindowDecorationBox(IHyprWindowDecoration* deco); + void forceRecalcFor(CWindow* pWindow); + + private: + struct SWindowPositioningData { + CWindow* pWindow = nullptr; + IHyprWindowDecoration* pDecoration = nullptr; + SDecorationPositioningInfo positioningInfo; + SDecorationPositioningReply lastReply; + bool needsReposition = true; + }; + + struct SWindowData { + Vector2D lastWindowSize = {}; + SWindowDecorationExtents reserved = {}; + SWindowDecorationExtents extents = {}; + bool needsRecalc = false; + }; + + std::unordered_map m_mWindowDatas; + std::vector> m_vWindowPositioningDatas; + + SWindowPositioningData* getDataFor(IHyprWindowDecoration* pDecoration, CWindow* pWindow); + void onWindowUnmap(CWindow* pWindow); + void onWindowMap(CWindow* pWindow); + void sanitizeDatas(); +}; + +inline std::unique_ptr g_pDecorationPositioner; \ No newline at end of file diff --git a/src/render/decorations/IHyprWindowDecoration.cpp b/src/render/decorations/IHyprWindowDecoration.cpp index a87c334a..973f2700 100644 --- a/src/render/decorations/IHyprWindowDecoration.cpp +++ b/src/render/decorations/IHyprWindowDecoration.cpp @@ -8,23 +8,18 @@ IHyprWindowDecoration::IHyprWindowDecoration(CWindow* pWindow) { IHyprWindowDecoration::~IHyprWindowDecoration() {} -SWindowDecorationExtents IHyprWindowDecoration::getWindowDecorationReservedArea() { - return SWindowDecorationExtents{}; -} - -CRegion IHyprWindowDecoration::getWindowDecorationRegion() { - const SWindowDecorationExtents RESERVED = getWindowDecorationReservedArea(); - const int BORDERSIZE = m_pWindow->getRealBorderSize(); - return CRegion(m_pWindow->m_vRealPosition.vec().x - (BORDERSIZE + RESERVED.topLeft.x) * (int)(RESERVED.topLeft.x != 0), - m_pWindow->m_vRealPosition.vec().y - (BORDERSIZE + RESERVED.topLeft.y) * (int)(RESERVED.topLeft.y != 0), - m_pWindow->m_vRealSize.vec().x + (BORDERSIZE + RESERVED.topLeft.x) * (int)(RESERVED.topLeft.x != 0) + - (BORDERSIZE + RESERVED.bottomRight.x) * (int)(RESERVED.bottomRight.x != 0), - m_pWindow->m_vRealSize.vec().y + (BORDERSIZE + RESERVED.topLeft.y) * (int)(RESERVED.topLeft.y != 0) + - (BORDERSIZE + RESERVED.bottomRight.y) * (int)(RESERVED.bottomRight.y != 0)) - .subtract(CRegion(m_pWindow->m_vRealPosition.vec().x - BORDERSIZE, m_pWindow->m_vRealPosition.vec().y - BORDERSIZE, m_pWindow->m_vRealSize.vec().x + 2 * BORDERSIZE, - m_pWindow->m_vRealSize.vec().y + 2 * BORDERSIZE)); -} - -bool IHyprWindowDecoration::allowsInput() { +bool IHyprWindowDecoration::onInputOnDeco(const eInputType, const Vector2D&, std::any) { return false; } + +eDecorationLayer IHyprWindowDecoration::getDecorationLayer() { + return DECORATION_LAYER_UNDER; +} + +uint64_t IHyprWindowDecoration::getDecorationFlags() { + return 0; +} + +std::string IHyprWindowDecoration::getDisplayName() { + return "Unknown Decoration"; +} diff --git a/src/render/decorations/IHyprWindowDecoration.hpp b/src/render/decorations/IHyprWindowDecoration.hpp index fec7305d..d3d1a5dd 100644 --- a/src/render/decorations/IHyprWindowDecoration.hpp +++ b/src/render/decorations/IHyprWindowDecoration.hpp @@ -1,44 +1,62 @@ #pragma once +#include #include "../../defines.hpp" #include "../../helpers/Region.hpp" +#include "DecorationPositioner.hpp" enum eDecorationType { DECORATION_NONE = -1, DECORATION_GROUPBAR, DECORATION_SHADOW, + DECORATION_BORDER, DECORATION_CUSTOM }; -struct SWindowDecorationExtents { - Vector2D topLeft; - Vector2D bottomRight; +enum eDecorationLayer { + DECORATION_LAYER_BOTTOM = 0, /* lowest. */ + DECORATION_LAYER_UNDER, /* under the window, but above BOTTOM */ + DECORATION_LAYER_OVER, /* above the window, but below its popups */ + DECORATION_LAYER_OVERLAY /* above everything of the window, including popups */ +}; + +enum eDecorationFlags { + DECORATION_ALLOWS_MOUSE_INPUT = 1 << 0, /* this decoration accepts mouse input */ + DECORATION_PART_OF_MAIN_WINDOW = 1 << 1, /* this decoration is a *seamless* part of the main window, so stuff like shadows will include it */ + DECORATION_NON_SOLID = 1 << 2, /* this decoration is not solid. Other decorations should draw on top of it. Example: shadow */ }; class CWindow; class CMonitor; +class CDecorationPositioner; class IHyprWindowDecoration { public: IHyprWindowDecoration(CWindow*); virtual ~IHyprWindowDecoration() = 0; - virtual SWindowDecorationExtents getWindowDecorationExtents() = 0; + virtual SDecorationPositioningInfo getPositioningInfo() = 0; - virtual void draw(CMonitor*, float a, const Vector2D& offset = Vector2D()) = 0; + virtual void onPositioningReply(const SDecorationPositioningReply& reply) = 0; - virtual eDecorationType getDecorationType() = 0; + virtual void draw(CMonitor*, float a, const Vector2D& offset = Vector2D()) = 0; - virtual void updateWindow(CWindow*) = 0; + virtual eDecorationType getDecorationType() = 0; - virtual void damageEntire() = 0; + virtual void updateWindow(CWindow*) = 0; - virtual SWindowDecorationExtents getWindowDecorationReservedArea(); + virtual void damageEntire() = 0; // should be ignored by non-absolute decos - virtual CRegion getWindowDecorationRegion(); + virtual bool onInputOnDeco(const eInputType, const Vector2D&, std::any = {}); - virtual bool allowsInput(); + virtual eDecorationLayer getDecorationLayer(); + + virtual uint64_t getDecorationFlags(); + + virtual std::string getDisplayName(); private: CWindow* m_pWindow = nullptr; + + friend class CDecorationPositioner; }; diff --git a/src/render/shaders/Border.hpp b/src/render/shaders/Border.hpp index 4e2d485d..79493da9 100644 --- a/src/render/shaders/Border.hpp +++ b/src/render/shaders/Border.hpp @@ -12,8 +12,8 @@ uniform vec2 topLeft; uniform vec2 fullSize; uniform vec2 fullSizeUntransformed; uniform float radius; +uniform float radiusOuter; uniform float thick; -uniform int primitiveMultisample; uniform vec4 gradient[10]; uniform int gradientLength; @@ -52,6 +52,7 @@ vec4 getColorForCoord(vec2 normalizedCoord) { void main() { highp vec2 pixCoord = vec2(gl_FragCoord); + highp vec2 pixCoordOuter = pixCoord; highp vec2 originalPixCoord = v_texcoord; originalPixCoord *= fullSizeUntransformed; float additionalAlpha = 1.0; @@ -62,36 +63,33 @@ void main() { pixCoord -= topLeft + fullSize * 0.5; pixCoord *= vec2(lessThan(pixCoord, vec2(0.0))) * -2.0 + 1.0; + pixCoordOuter = pixCoord; pixCoord -= fullSize * 0.5 - radius; + pixCoordOuter -= fullSize * 0.5 - radiusOuter; + + // center the pixes dont make it top-left + pixCoord += vec2(1.0, 1.0) / fullSize; + pixCoordOuter += vec2(1.0, 1.0) / fullSize; if (min(pixCoord.x, pixCoord.y) > 0.0 && radius > 0.0) { - float dist = length(pixCoord); + float distOuter = length(pixCoordOuter); + float h = (thick / 2.0); - if (dist > radius + 1.0 || dist < radius - thick - 1.0) - discard; - - if (primitiveMultisample == 1 && (dist > radius - 1.0 || dist < radius - thick + 1.0)) { - float distances = 0.0; - float len = length(pixCoord + vec2(0.25, 0.25)); - distances += float(len < radius && len > radius - thick); - len = length(pixCoord + vec2(0.75, 0.25)); - distances += float(len < radius && len > radius - thick); - len = length(pixCoord + vec2(0.25, 0.75)); - distances += float(len < radius && len > radius - thick); - len = length(pixCoord + vec2(0.75, 0.75)); - distances += float(len < radius && len > radius - thick); - - if (distances == 0.0) - discard; - - distances /= 4.0; - - additionalAlpha *= distances; - } else if (dist > radius || dist < radius - thick) - discard; - - done = true; + if (dist < radius - h) { + // lower + float normalized = smoothstep(0.0, 1.0, dist - radius + thick + 0.5); + additionalAlpha *= normalized; + done = true; + } else if (min(pixCoordOuter.x, pixCoordOuter.y) > 0.0) { + // higher + float normalized = 1.0 - smoothstep(0.0, 1.0, distOuter - radiusOuter + 0.5); + additionalAlpha *= normalized; + done = true; + } else if (distOuter < radiusOuter - h) { + additionalAlpha = 1.0; + done = true; + } } // now check for other shit diff --git a/src/render/shaders/Textures.hpp b/src/render/shaders/Textures.hpp index 98cd8907..e78f39fc 100644 --- a/src/render/shaders/Textures.hpp +++ b/src/render/shaders/Textures.hpp @@ -10,6 +10,7 @@ inline static constexpr auto ROUNDED_SHADER_FUNC = [](const std::string colorVar pixCoord -= topLeft + fullSize * 0.5; pixCoord *= vec2(lessThan(pixCoord, vec2(0.0))) * -2.0 + 1.0; pixCoord -= fullSize * 0.5 - radius; + pixCoord += vec2(1.0, 1.0) / fullSize; // center the pix dont make it top-left if (pixCoord.x + pixCoord.y > radius) { @@ -18,20 +19,13 @@ inline static constexpr auto ROUNDED_SHADER_FUNC = [](const std::string colorVar if (dist > radius) discard; - if (primitiveMultisample == 1 && dist > radius - 1.0) { - float distances = 0.0; - distances += float(length(pixCoord + vec2(0.25, 0.25)) < radius); - distances += float(length(pixCoord + vec2(0.75, 0.25)) < radius); - distances += float(length(pixCoord + vec2(0.25, 0.75)) < radius); - distances += float(length(pixCoord + vec2(0.75, 0.75)) < radius); + if (dist > radius - 1.0) { + float dist = length(pixCoord); - if (distances == 0.0) - discard; - - distances /= 4.0; + float normalized = 1.0 - smoothstep(0.0, 1.0, dist - radius + 0.5); )#" + - colorVarName + R"#( = )#" + colorVarName + R"#( * distances; + colorVarName + R"#( = )#" + colorVarName + R"#( * normalized; } } @@ -43,13 +37,16 @@ uniform mat3 proj; uniform vec4 color; attribute vec2 pos; attribute vec2 texcoord; +attribute vec2 texcoordMatte; varying vec4 v_color; varying vec2 v_texcoord; +varying vec2 v_texcoordMatte; void main() { gl_Position = vec4(proj * vec3(pos, 1.0), 1.0); v_color = color; v_texcoord = texcoord; + v_texcoordMatte = texcoordMatte; })#"; inline const std::string QUADFRAGSRC = R"#( @@ -60,8 +57,6 @@ uniform vec2 topLeft; uniform vec2 fullSize; uniform float radius; -uniform int primitiveMultisample; - void main() { vec4 pixColor = v_color; @@ -102,15 +97,13 @@ uniform float discardAlphaValue; uniform int applyTint; uniform vec3 tint; -uniform int primitiveMultisample; - void main() { vec4 pixColor = texture2D(tex, v_texcoord); if (discardOpaque == 1 && pixColor[3] * alpha == 1.0) discard; - + if (discardAlpha == 1 && pixColor[3] <= discardAlphaValue) discard; @@ -137,6 +130,16 @@ void main() { gl_FragColor = texture2D(tex, v_texcoord); })#"; +inline const std::string TEXFRAGSRCRGBAMATTE = R"#( +precision mediump float; +varying vec2 v_texcoord; // is in 0-1 +uniform sampler2D tex; +uniform sampler2D texMatte; + +void main() { + gl_FragColor = texture2D(tex, v_texcoord) * texture2D(texMatte, v_texcoord)[0]; // I know it only uses R, but matte should be black/white anyways. +})#"; + inline const std::string TEXFRAGSRCRGBX = R"#( precision mediump float; varying vec2 v_texcoord; @@ -154,8 +157,6 @@ uniform int discardAlphaValue; uniform int applyTint; uniform vec3 tint; -uniform int primitiveMultisample; - void main() { if (discardOpaque == 1 && alpha == 1.0) @@ -179,12 +180,113 @@ void main() { inline const std::string FRAGBLUR1 = R"#( #version 100 -precision mediump float; +precision mediump float; varying mediump vec2 v_texcoord; // is in 0-1 -uniform sampler2D tex; +uniform sampler2D tex; -uniform float radius; -uniform vec2 halfpixel; +uniform float radius; +uniform vec2 halfpixel; +uniform int passes; +uniform float vibrancy; +uniform float vibrancy_darkness; + +// see http://alienryderflex.com/hsp.html +const float Pr = 0.299; +const float Pg = 0.587; +const float Pb = 0.114; + +// Y is "v" ( brightness ). X is "s" ( saturation ) +// see https://www.desmos.com/3d/a88652b9a4 +// Determines if high brightness or high saturation is more important +const float a = 0.93; +const float b = 0.11; +const float c = 0.66; // Determines the smoothness of the transition of unboosted to boosted colors +// + +// http://www.flong.com/archive/texts/code/shapers_circ/ +float doubleCircleSigmoid(float x, float a) { + a = clamp(a, 0.0, 1.0); + + float y = .0; + if (x <= a) { + y = a - sqrt(a * a - x * x); + } else { + y = a + sqrt(pow(1. - a, 2.) - pow(x - 1., 2.)); + } + return y; +} + +vec3 rgb2hsl(vec3 col) { + float red = col.r; + float green = col.g; + float blue = col.b; + + float minc = min(col.r, min(col.g, col.b)); + float maxc = max(col.r, max(col.g, col.b)); + float delta = maxc - minc; + + float lum = (minc + maxc) * 0.5; + float sat = 0.0; + float hue = 0.0; + + if (lum > 0.0 && lum < 1.0) { + float mul = (lum < 0.5) ? (lum) : (1.0 - lum); + sat = delta / (mul * 2.0); + } + + if (delta > 0.0) { + vec3 maxcVec = vec3(maxc); + vec3 masks = vec3(equal(maxcVec, col)) * vec3(notEqual(maxcVec, vec3(green, blue, red))); + vec3 adds = vec3(0.0, 2.0, 4.0) + vec3(green - blue, blue - red, red - green) / delta; + + hue += dot(adds, masks); + hue /= 6.0; + + if (hue < 0.0) + hue += 1.0; + } + + return vec3(hue, sat, lum); +} + +vec3 hsl2rgb(vec3 col) { + const float onethird = 1.0 / 3.0; + const float twothird = 2.0 / 3.0; + const float rcpsixth = 6.0; + + float hue = col.x; + float sat = col.y; + float lum = col.z; + + vec3 xt = vec3(0.0); + + if (hue < onethird) { + xt.r = rcpsixth * (onethird - hue); + xt.g = rcpsixth * hue; + xt.b = 0.0; + } else if (hue < twothird) { + xt.r = 0.0; + xt.g = rcpsixth * (twothird - hue); + xt.b = rcpsixth * (hue - onethird); + } else + xt = vec3(rcpsixth * (hue - twothird), 0.0, rcpsixth * (1.0 - hue)); + + xt = min(xt, 1.0); + + float sat2 = 2.0 * sat; + float satinv = 1.0 - sat; + float luminv = 1.0 - lum; + float lum2m1 = (2.0 * lum) - 1.0; + vec3 ct = (sat2 * xt) + satinv; + + vec3 rgb; + if (lum >= 0.5) + rgb = (luminv * ct) + lum2m1; + else + rgb = lum * ct; + + return rgb; +} void main() { vec2 uv = v_texcoord * 2.0; @@ -195,7 +297,28 @@ void main() { sum += texture2D(tex, uv + vec2(halfpixel.x, -halfpixel.y) * radius); sum += texture2D(tex, uv - vec2(halfpixel.x, -halfpixel.y) * radius); - gl_FragColor = sum / 8.0; + vec4 color = sum / 8.0; + + if (vibrancy == 0.0) { + gl_FragColor = color; + } else { + // Invert it so that it correctly maps to the config setting + float vibrancy_darkness1 = 1.0 - vibrancy_darkness; + + // Decrease the RGB components based on their perceived brightness, to prevent visually dark colors from overblowing the rest. + vec3 hsl = rgb2hsl(color.rgb); + // Calculate perceived brightness, as not boost visually dark colors like deep blue as much as equally saturated yellow + float perceivedBrightness = doubleCircleSigmoid(sqrt(color.r * color.r * Pr + color.g * color.g * Pg + color.b * color.b * Pb), 0.8 * vibrancy_darkness1); + + float b1 = b * vibrancy_darkness1; + float boostBase = hsl[1] > 0.0 ? smoothstep(b1 - c * 0.5, b1 + c * 0.5, 1.0 - (pow(1.0 - hsl[1] * cos(a), 2.0) + pow(1.0 - perceivedBrightness * sin(a), 2.0))) : 0.0; + + float saturation = clamp(hsl[1] + (boostBase * vibrancy) / float(passes), 0.0, 1.0); + + vec3 newColor = hsl2rgb(vec3(hsl[0], saturation, hsl[2])); + + gl_FragColor = vec4(newColor, color[3]); + } } )#"; @@ -225,35 +348,66 @@ void main() { } )#"; -inline const std::string FRAGBLURFINISH = R"#( -precision mediump float; -varying vec2 v_texcoord; // is in 0-1 +inline const std::string FRAGBLURPREPARE = R"#( +precision mediump float; +varying vec2 v_texcoord; // is in 0-1 uniform sampler2D tex; -uniform float contrast; -uniform float noise; -uniform float brightness; +uniform float contrast; +uniform float brightness; -float hash(vec2 p) { - return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453); +float gain(float x, float k) { + float a = 0.5 * pow(2.0 * ((x < 0.5) ? x : 1.0 - x), k); + return (x < 0.5) ? a : 1.0 - a; } void main() { vec4 pixColor = texture2D(tex, v_texcoord); // contrast - pixColor.rgb = (pixColor.rgb - 0.5) * contrast + 0.5; + if (contrast != 1.0) { + pixColor.r = gain(pixColor.r, contrast); + pixColor.g = gain(pixColor.g, contrast); + pixColor.b = gain(pixColor.b, contrast); + } // brightness - pixColor.rgb *= brightness; + if (brightness > 1.0) { + pixColor.rgb *= brightness; + } + + gl_FragColor = pixColor; +} +)#"; + +inline const std::string FRAGBLURFINISH = R"#( +precision mediump float; +varying vec2 v_texcoord; // is in 0-1 +uniform sampler2D tex; + +uniform float noise; +uniform float brightness; + +float hash(vec2 p) { + return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453); +} + +void main() { + vec4 pixColor = texture2D(tex, v_texcoord); // noise - float noiseHash = hash(v_texcoord); + float noiseHash = hash(v_texcoord); float noiseAmount = (mod(noiseHash, 1.0) - 0.5); pixColor.rgb += noiseAmount * noise; + // brightness + if (brightness < 1.0) { + pixColor.rgb *= brightness; + } + gl_FragColor = pixColor; -})#"; +} +)#"; inline const std::string TEXFRAGSRCEXT = R"#( #extension GL_OES_EGL_image_external : require @@ -274,8 +428,6 @@ uniform int discardAlphaValue; uniform int applyTint; uniform vec3 tint; -uniform int primitiveMultisample; - void main() { vec4 pixColor = texture2D(texture0, v_texcoord); @@ -363,4 +515,4 @@ void main() { gl_FragColor = pixColor; } -)#"; \ No newline at end of file +)#"; diff --git a/src/version.h.in b/src/version.h.in new file mode 100644 index 00000000..e0dd8a83 --- /dev/null +++ b/src/version.h.in @@ -0,0 +1,7 @@ +#pragma once +#define GIT_COMMIT_HASH "@HASH@" +#define GIT_BRANCH "@BRANCH@" +#define GIT_COMMIT_MESSAGE "@MESSAGE@" +#define GIT_COMMIT_DATE "@DATE@" +#define GIT_DIRTY "@DIRTY@" +#define GIT_TAG "@TAG@" diff --git a/subprojects/packagefiles/wlroots-meson-build.patch b/subprojects/packagefiles/wlroots-meson-build.patch index 5e7565a3..a184eb77 100644 --- a/subprojects/packagefiles/wlroots-meson-build.patch +++ b/subprojects/packagefiles/wlroots-meson-build.patch @@ -4,7 +4,7 @@ index e669800..687786b 100644 +++ b/include/meson.build @@ -1,4 +1,5 @@ -subdir('wlr') -+run_command('ln', '-s', join_paths(meson.project_source_root(), 'include', 'wlr'), join_paths(meson.project_source_root(), 'include', 'wlroots'), check: true) ++run_command('ln', '-sf', join_paths(meson.project_source_root(), 'include', 'wlr'), join_paths(meson.project_source_root(), 'include', 'wlroots'), check: true) +subdir('wlroots') exclude_files = ['meson.build', 'config.h.in', 'version.h.in'] @@ -35,11 +35,11 @@ index 29b103a..0b6e5a4 100644 --- a/meson.build +++ b/meson.build @@ -15,7 +15,7 @@ project( - # necessary for bugfix releases. Increasing soversion is required because - # wlroots never guarantees ABI stability -- only API stability is guaranteed - # between minor releases. --soversion = 12 -+soversion = 12032 + version_major = version.split('.')[0] + version_minor = version.split('.')[1] + assert(version_major == '0') +-soversion = version_minor.to_int() - 5 ++soversion = 13032 little_endian = target_machine.endian() == 'little' big_endian = target_machine.endian() == 'big' diff --git a/subprojects/tracy b/subprojects/tracy index 897aec5b..37aff70d 160000 --- a/subprojects/tracy +++ b/subprojects/tracy @@ -1 +1 @@ -Subproject commit 897aec5b062664d2485f4f9a213715d2e527e0ca +Subproject commit 37aff70dfa50cf6307b3fee6074d627dc2929143 diff --git a/subprojects/wlroots b/subprojects/wlroots index 98a745d9..f81c3d93 160000 --- a/subprojects/wlroots +++ b/subprojects/wlroots @@ -1 +1 @@ -Subproject commit 98a745d926d8048bc30aef11b421df207a01c279 +Subproject commit f81c3d93cd6f61b20ae784297679283438def8df diff --git a/subprojects/wlroots.wrap b/subprojects/wlroots.wrap index 31fddb18..915385df 100644 --- a/subprojects/wlroots.wrap +++ b/subprojects/wlroots.wrap @@ -1,7 +1,7 @@ [wrap-git] directory = wlroots url = https://gitlab.freedesktop.org/wlroots/wlroots.git -revision = 98a745d926d8048bc30aef11b421df207a01c279 +revision = f81c3d93cd6f61b20ae784297679283438def8df depth = 1 diff_files = wlroots-meson-build.patch