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