├── hyprexpo ├── globals.hpp ├── Makefile ├── default.nix ├── ExpoGesture.hpp ├── CMakeLists.txt ├── OverviewPassElement.hpp ├── OverviewPassElement.cpp ├── meson.build ├── ExpoGesture.cpp ├── README.md ├── overview.hpp ├── main.cpp └── overview.cpp ├── hyprfocus ├── globals.hpp ├── Makefile ├── default.nix ├── CMakeLists.txt ├── meson.build ├── README.md └── main.cpp ├── hyprwinwrap ├── globals.hpp ├── Makefile ├── default.nix ├── CMakeLists.txt ├── README.md ├── meson.build └── main.cpp ├── csgo-vulkan-fix ├── globals.hpp ├── Makefile ├── default.nix ├── CMakeLists.txt ├── README.md ├── meson.build └── main.cpp ├── hyprscrolling ├── globals.hpp ├── Makefile ├── default.nix ├── CMakeLists.txt ├── meson.build ├── main.cpp ├── README.md └── Scrolling.hpp ├── xtra-dispatchers ├── globals.hpp ├── Makefile ├── default.nix ├── CMakeLists.txt ├── README.md ├── meson.build └── main.cpp ├── borders-plus-plus ├── globals.hpp ├── Makefile ├── default.nix ├── BorderppPassElement.cpp ├── CMakeLists.txt ├── README.md ├── BorderppPassElement.hpp ├── meson.build ├── borderDeco.hpp ├── main.cpp └── borderDeco.cpp ├── .github └── workflows │ ├── nix-ci.yml │ └── nix-build.yml ├── hyprtrails ├── globals.hpp ├── Makefile ├── README.md ├── default.nix ├── TrailPassElement.cpp ├── CMakeLists.txt ├── TrailPassElement.hpp ├── meson.build ├── shaders.hpp ├── trail.hpp ├── main.cpp └── trail.cpp ├── hyprbars ├── default.nix ├── CMakeLists.txt ├── Makefile ├── BarPassElement.hpp ├── globals.hpp ├── meson.build ├── BarPassElement.cpp ├── README.md ├── barDeco.hpp └── main.cpp ├── .gitignore ├── CMakeLists.txt ├── hyprload.toml ├── LICENSE ├── .clang-format ├── flake.nix ├── README.md ├── hyprpm.toml └── flake.lock /hyprexpo/globals.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | inline HANDLE PHANDLE = nullptr; -------------------------------------------------------------------------------- /hyprfocus/globals.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | inline HANDLE PHANDLE = nullptr; -------------------------------------------------------------------------------- /hyprwinwrap/globals.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | inline HANDLE PHANDLE = nullptr; -------------------------------------------------------------------------------- /csgo-vulkan-fix/globals.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | inline HANDLE PHANDLE = nullptr; -------------------------------------------------------------------------------- /hyprscrolling/globals.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | inline HANDLE PHANDLE = nullptr; -------------------------------------------------------------------------------- /xtra-dispatchers/globals.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | inline HANDLE PHANDLE = nullptr; -------------------------------------------------------------------------------- /borders-plus-plus/globals.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | inline HANDLE PHANDLE = nullptr; 6 | -------------------------------------------------------------------------------- /.github/workflows/nix-ci.yml: -------------------------------------------------------------------------------- 1 | name: Nix 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | jobs: 6 | build: 7 | uses: ./.github/workflows/nix-build.yml 8 | -------------------------------------------------------------------------------- /hyprtrails/globals.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | inline HANDLE PHANDLE = nullptr; 6 | 7 | struct SGlobalState { 8 | SShader trailShader; 9 | wl_event_source* tick = nullptr; 10 | }; 11 | 12 | inline UP g_pGlobalState; 13 | -------------------------------------------------------------------------------- /hyprfocus/Makefile: -------------------------------------------------------------------------------- 1 | # Else exist specifically for clang 2 | ifeq ($(CXX),g++) 3 | EXTRA_FLAGS = --no-gnu-unique 4 | else 5 | EXTRA_FLAGS = 6 | endif 7 | 8 | all: 9 | $(CXX) -shared -fPIC $(EXTRA_FLAGS) main.cpp -o hyprfocus.so -g `pkg-config --cflags pixman-1 libdrm hyprland pangocairo libinput libudev wayland-server xkbcommon` -std=c++2b -O2 10 | clean: 11 | rm ./hyprfocus.so 12 | -------------------------------------------------------------------------------- /hyprwinwrap/Makefile: -------------------------------------------------------------------------------- 1 | # Else exist specifically for clang 2 | ifeq ($(CXX),g++) 3 | EXTRA_FLAGS = --no-gnu-unique 4 | else 5 | EXTRA_FLAGS = 6 | endif 7 | 8 | all: 9 | $(CXX) -shared -fPIC $(EXTRA_FLAGS) main.cpp -o hyprwinwrap.so -g `pkg-config --cflags pixman-1 libdrm hyprland pangocairo libinput libudev wayland-server xkbcommon` -std=c++2b -O2 10 | clean: 11 | rm ./hyprwinwrap.so 12 | -------------------------------------------------------------------------------- /csgo-vulkan-fix/Makefile: -------------------------------------------------------------------------------- 1 | # Else exist specifically for clang 2 | ifeq ($(CXX),g++) 3 | EXTRA_FLAGS = --no-gnu-unique 4 | else 5 | EXTRA_FLAGS = 6 | endif 7 | 8 | all: 9 | $(CXX) -shared -fPIC $(EXTRA_FLAGS) main.cpp -o csgo-vulkan-fix.so -g `pkg-config --cflags pixman-1 libdrm hyprland pangocairo libinput libudev wayland-server xkbcommon` -std=c++2b -O2 10 | clean: 11 | rm ./csgo-vulkan-fix.so 12 | -------------------------------------------------------------------------------- /xtra-dispatchers/Makefile: -------------------------------------------------------------------------------- 1 | # Else exist specifically for clang 2 | ifeq ($(CXX),g++) 3 | EXTRA_FLAGS = --no-gnu-unique 4 | else 5 | EXTRA_FLAGS = 6 | endif 7 | 8 | all: 9 | $(CXX) -shared -fPIC $(EXTRA_FLAGS) main.cpp -o xtra-dispatchers.so -g `pkg-config --cflags pixman-1 libdrm hyprland pangocairo libinput libudev wayland-server xkbcommon` -std=c++2b -O2 10 | clean: 11 | rm ./xtra-dispatchers.so 12 | -------------------------------------------------------------------------------- /hyprscrolling/Makefile: -------------------------------------------------------------------------------- 1 | # Else exist specifically for clang 2 | ifeq ($(CXX),g++) 3 | EXTRA_FLAGS = --no-gnu-unique 4 | else 5 | EXTRA_FLAGS = 6 | endif 7 | 8 | 9 | all: 10 | $(CXX) -shared -fPIC $(EXTRA_FLAGS) main.cpp Scrolling.cpp -o hyprscrolling.so -g `pkg-config --cflags pixman-1 libdrm hyprland pangocairo libinput libudev wayland-server xkbcommon` -std=c++2b 11 | clean: 12 | rm ./hyprscrolling.so 13 | -------------------------------------------------------------------------------- /hyprtrails/Makefile: -------------------------------------------------------------------------------- 1 | # Else exist specifically for clang 2 | ifeq ($(CXX),g++) 3 | EXTRA_FLAGS = --no-gnu-unique 4 | else 5 | EXTRA_FLAGS = 6 | endif 7 | 8 | all: 9 | $(CXX) -shared -fPIC $(EXTRA_FLAGS) main.cpp trail.cpp TrailPassElement.cpp -o hyprtrails.so -g `pkg-config --cflags pixman-1 libdrm hyprland pangocairo libinput libudev wayland-server xkbcommon` -std=c++2b -O2 10 | clean: 11 | rm ./hyprtrails.so 12 | -------------------------------------------------------------------------------- /hyprexpo/Makefile: -------------------------------------------------------------------------------- 1 | # Else exist specifically for clang 2 | ifeq ($(CXX),g++) 3 | EXTRA_FLAGS = --no-gnu-unique 4 | else 5 | EXTRA_FLAGS = 6 | endif 7 | 8 | all: 9 | $(CXX) -shared -fPIC $(EXTRA_FLAGS) main.cpp overview.cpp ExpoGesture.cpp OverviewPassElement.cpp -o hyprexpo.so -g `pkg-config --cflags pixman-1 libdrm hyprland pangocairo libinput libudev wayland-server xkbcommon` -std=c++2b -Wno-narrowing 10 | clean: 11 | rm ./hyprexpo.so 12 | -------------------------------------------------------------------------------- /borders-plus-plus/Makefile: -------------------------------------------------------------------------------- 1 | # Else exist specifically for clang 2 | ifeq ($(CXX),g++) 3 | EXTRA_FLAGS = --no-gnu-unique 4 | else 5 | EXTRA_FLAGS = 6 | endif 7 | 8 | all: 9 | $(CXX) -shared -fPIC $(EXTRA_FLAGS) main.cpp borderDeco.cpp BorderppPassElement.cpp -o borders-plus-plus.so -g `pkg-config --cflags pixman-1 libdrm hyprland pangocairo libinput libudev wayland-server xkbcommon` -std=c++2b -O2 10 | 11 | clean: 12 | rm ./borders-plus-plus.so 13 | -------------------------------------------------------------------------------- /hyprbars/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | hyprland, 4 | hyprlandPlugins, 5 | }: 6 | hyprlandPlugins.mkHyprlandPlugin { 7 | pluginName = "hyprbars"; 8 | version = "0.1"; 9 | src = ./.; 10 | 11 | inherit (hyprland) nativeBuildInputs; 12 | 13 | meta = with lib; { 14 | homepage = "https://github.com/hyprwm/hyprland-plugins/tree/main/hyprbars"; 15 | description = "Hyprland window title plugin"; 16 | license = licenses.bsd3; 17 | platforms = platforms.linux; 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /hyprfocus/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | hyprland, 4 | hyprlandPlugins, 5 | }: 6 | hyprlandPlugins.mkHyprlandPlugin { 7 | pluginName = "hyprfocus"; 8 | version = "0.1"; 9 | src = ./.; 10 | 11 | inherit (hyprland) nativeBuildInputs; 12 | 13 | meta = with lib; { 14 | homepage = "https://github.com/hyprwm/hyprland-plugins/tree/main/hyprfocus"; 15 | description = "Hyprland flashfocus plugin"; 16 | license = licenses.bsd3; 17 | platforms = platforms.linux; 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /hyprexpo/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | hyprland, 4 | hyprlandPlugins, 5 | }: 6 | hyprlandPlugins.mkHyprlandPlugin { 7 | pluginName = "hyprexpo"; 8 | version = "0.1"; 9 | src = ./.; 10 | 11 | inherit (hyprland) nativeBuildInputs; 12 | 13 | meta = with lib; { 14 | homepage = "https://github.com/hyprwm/hyprland-plugins/tree/main/hyprexpo"; 15 | description = "Hyprland workspaces overview plugin"; 16 | license = licenses.bsd3; 17 | platforms = platforms.linux; 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /hyprwinwrap/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | hyprland, 4 | hyprlandPlugins, 5 | }: 6 | hyprlandPlugins.mkHyprlandPlugin { 7 | pluginName = "hyprwinwrap"; 8 | version = "0.1"; 9 | src = ./.; 10 | 11 | inherit (hyprland) nativeBuildInputs; 12 | 13 | meta = with lib; { 14 | homepage = "https://github.com/hyprwm/hyprland-plugins/tree/main/hyprwinwrap"; 15 | description = "Hyprland version of xwinwrap"; 16 | license = licenses.bsd3; 17 | platforms = platforms.linux; 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /csgo-vulkan-fix/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | hyprland, 4 | hyprlandPlugins, 5 | }: 6 | hyprlandPlugins.mkHyprlandPlugin { 7 | pluginName = "csgo-vulkan-fix"; 8 | version = "0.1"; 9 | src = ./.; 10 | 11 | inherit (hyprland) nativeBuildInputs; 12 | 13 | meta = with lib; { 14 | homepage = "https://github.com/hyprwm/hyprland-plugins/tree/main/csgo-vulkan-fix"; 15 | description = "Hyprland CS:GO Vulkan fix"; 16 | license = licenses.bsd3; 17 | platforms = platforms.linux; 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /hyprtrails/README.md: -------------------------------------------------------------------------------- 1 | # hyprtrails 2 | 3 | A neat, but useless plugin to add trails behind windows: 4 | 5 | https://github.com/hyprwm/hyprland-plugins/assets/43317083/6c31b839-92cd-4510-bb12-110d77dd5b44 6 | 7 | Maybe isn't the most efficient. The curve-related settings are only for advanced users. 8 | Be warned they _incredibly_ impact performance. 9 | 10 | Only setting you may want to change: 11 | ```ini 12 | plugin { 13 | hyprtrails { 14 | color = rgba(ffaa00ff) 15 | } 16 | } 17 | 18 | ``` 19 | -------------------------------------------------------------------------------- /hyprscrolling/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | hyprland, 4 | hyprlandPlugins, 5 | }: 6 | hyprlandPlugins.mkHyprlandPlugin { 7 | pluginName = "hyprscrolling"; 8 | version = "0.1"; 9 | src = ./.; 10 | 11 | inherit (hyprland) nativeBuildInputs; 12 | 13 | meta = with lib; { 14 | homepage = "https://github.com/hyprwm/hyprland-plugins/tree/main/hyprscrolling"; 15 | description = "Hyprland scrolling layout plugin"; 16 | license = licenses.bsd3; 17 | platforms = platforms.linux; 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /hyprtrails/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | hyprland, 4 | hyprlandPlugins, 5 | }: 6 | hyprlandPlugins.mkHyprlandPlugin { 7 | pluginName = "hyprtrails"; 8 | version = "0.1"; 9 | src = ./.; 10 | 11 | inherit (hyprland) nativeBuildInputs; 12 | 13 | meta = with lib; { 14 | homepage = "https://github.com/hyprwm/hyprland-plugins/tree/main/hyprtrails"; 15 | description = "Smooth trails behind moving windows for Hyprland"; 16 | license = licenses.bsd3; 17 | platforms = platforms.linux; 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # Editors 35 | .vscode/ 36 | 37 | # Nix 38 | result 39 | result-man 40 | 41 | build/ 42 | .cache/ -------------------------------------------------------------------------------- /borders-plus-plus/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | hyprland, 4 | hyprlandPlugins, 5 | }: 6 | hyprlandPlugins.mkHyprlandPlugin { 7 | pluginName = "borders-plus-plus"; 8 | version = "0.1"; 9 | src = ./.; 10 | 11 | inherit (hyprland) nativeBuildInputs; 12 | 13 | meta = with lib; { 14 | homepage = "https://github.com/hyprwm/hyprland-plugins/tree/main/borders-plus-plus"; 15 | description = "Hyprland borders-plus-plus plugin"; 16 | license = licenses.bsd3; 17 | platforms = platforms.linux; 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /xtra-dispatchers/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | hyprland, 4 | hyprlandPlugins, 5 | }: 6 | hyprlandPlugins.mkHyprlandPlugin { 7 | pluginName = "xtra-dispatchers"; 8 | version = "0.1"; 9 | src = ./.; 10 | 11 | inherit (hyprland) nativeBuildInputs; 12 | 13 | meta = with lib; { 14 | homepage = "https://github.com/hyprwm/hyprland-plugins/tree/main/xtra-dispatchers"; 15 | description = "Hyprland extra dispatchers plugin"; 16 | license = licenses.bsd3; 17 | platforms = platforms.linux; 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.27) 2 | project(hyprland-plugins 3 | DESCRIPTION "Official plugins for Hyprland" 4 | LANGUAGES CXX 5 | ) 6 | 7 | set(CMAKE_CXX_STANDARD 23) 8 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 9 | 10 | add_subdirectory(borders-plus-plus) 11 | add_subdirectory(csgo-vulkan-fix) 12 | add_subdirectory(hyprbars) 13 | add_subdirectory(hyprexpo) 14 | add_subdirectory(hyprfocus) 15 | add_subdirectory(hyprscrolling) 16 | add_subdirectory(hyprtrails) 17 | add_subdirectory(hyprwinwrap) 18 | add_subdirectory(xtra-dispatchers) 19 | -------------------------------------------------------------------------------- /hyprtrails/TrailPassElement.cpp: -------------------------------------------------------------------------------- 1 | #include "TrailPassElement.hpp" 2 | #include 3 | #include "trail.hpp" 4 | 5 | CTrailPassElement::CTrailPassElement(const CTrailPassElement::STrailData& data_) : data(data_) { 6 | ; 7 | } 8 | 9 | void CTrailPassElement::draw(const CRegion& damage) { 10 | data.deco->renderPass(g_pHyprOpenGL->m_renderData.pMonitor.lock(), data.a); 11 | } 12 | 13 | bool CTrailPassElement::needsLiveBlur() { 14 | return false; 15 | } 16 | 17 | bool CTrailPassElement::needsPrecomputeBlur() { 18 | return false; 19 | } -------------------------------------------------------------------------------- /borders-plus-plus/BorderppPassElement.cpp: -------------------------------------------------------------------------------- 1 | #include "BorderppPassElement.hpp" 2 | #include 3 | #include "borderDeco.hpp" 4 | 5 | CBorderPPPassElement::CBorderPPPassElement(const CBorderPPPassElement::SBorderPPData& data_) : data(data_) { 6 | ; 7 | } 8 | 9 | void CBorderPPPassElement::draw(const CRegion& damage) { 10 | data.deco->drawPass(g_pHyprOpenGL->m_renderData.pMonitor.lock(), data.a); 11 | } 12 | 13 | bool CBorderPPPassElement::needsLiveBlur() { 14 | return false; 15 | } 16 | 17 | bool CBorderPPPassElement::needsPrecomputeBlur() { 18 | return false; 19 | } -------------------------------------------------------------------------------- /hyprexpo/ExpoGesture.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class CExpoGesture : public ITrackpadGesture { 6 | public: 7 | CExpoGesture() = default; 8 | virtual ~CExpoGesture() = default; 9 | 10 | virtual void begin(const ITrackpadGesture::STrackpadGestureBegin& e); 11 | virtual void update(const ITrackpadGesture::STrackpadGestureUpdate& e); 12 | virtual void end(const ITrackpadGesture::STrackpadGestureEnd& e); 13 | 14 | private: 15 | float m_lastDelta = 0.F; 16 | bool m_firstUpdate = false; 17 | }; 18 | -------------------------------------------------------------------------------- /hyprbars/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.27) 2 | 3 | project(hyprbars 4 | DESCRIPTION "hyprbars plugin for Hyprland" 5 | VERSION 0.1 6 | ) 7 | 8 | set(CMAKE_CXX_STANDARD 23) 9 | 10 | file(GLOB_RECURSE SRC "*.cpp") 11 | 12 | add_library(hyprbars SHARED ${SRC}) 13 | 14 | find_package(PkgConfig REQUIRED) 15 | pkg_check_modules(deps REQUIRED IMPORTED_TARGET 16 | hyprland 17 | libdrm 18 | libinput 19 | libudev 20 | pangocairo 21 | pixman-1 22 | wayland-server 23 | xkbcommon 24 | ) 25 | target_link_libraries(hyprbars PRIVATE rt PkgConfig::deps) 26 | 27 | install(TARGETS hyprbars) 28 | -------------------------------------------------------------------------------- /hyprexpo/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.27) 2 | 3 | project(hyprexpo 4 | DESCRIPTION "hyprexpo plugin for Hyprland" 5 | VERSION 0.1 6 | ) 7 | 8 | set(CMAKE_CXX_STANDARD 23) 9 | 10 | file(GLOB_RECURSE SRC "*.cpp") 11 | 12 | add_library(hyprexpo SHARED ${SRC}) 13 | 14 | find_package(PkgConfig REQUIRED) 15 | pkg_check_modules(deps REQUIRED IMPORTED_TARGET 16 | hyprland 17 | libdrm 18 | libinput 19 | libudev 20 | pangocairo 21 | pixman-1 22 | wayland-server 23 | xkbcommon 24 | ) 25 | target_link_libraries(hyprexpo PRIVATE rt PkgConfig::deps) 26 | 27 | install(TARGETS hyprexpo) 28 | -------------------------------------------------------------------------------- /hyprfocus/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.27) 2 | 3 | project(hyprfocus 4 | DESCRIPTION "flashfocus for Hyprland" 5 | VERSION 0.1 6 | ) 7 | 8 | set(CMAKE_CXX_STANDARD 23) 9 | 10 | file(GLOB_RECURSE SRC "*.cpp") 11 | 12 | add_library(hyprfocus SHARED ${SRC}) 13 | 14 | find_package(PkgConfig REQUIRED) 15 | pkg_check_modules(deps REQUIRED IMPORTED_TARGET 16 | hyprland 17 | libdrm 18 | libinput 19 | libudev 20 | pangocairo 21 | pixman-1 22 | wayland-server 23 | xkbcommon 24 | ) 25 | target_link_libraries(hyprfocus PRIVATE rt PkgConfig::deps) 26 | 27 | install(TARGETS hyprfocus) 28 | -------------------------------------------------------------------------------- /hyprtrails/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.27) 2 | 3 | project(hyprtrails 4 | DESCRIPTION "hyprtrails plugin for Hyprland" 5 | VERSION 0.1 6 | ) 7 | 8 | set(CMAKE_CXX_STANDARD 23) 9 | 10 | file(GLOB_RECURSE SRC "*.cpp") 11 | 12 | add_library(hyprtrails SHARED ${SRC}) 13 | 14 | find_package(PkgConfig REQUIRED) 15 | pkg_check_modules(deps REQUIRED IMPORTED_TARGET 16 | hyprland 17 | libdrm 18 | libinput 19 | libudev 20 | pangocairo 21 | pixman-1 22 | wayland-server 23 | xkbcommon 24 | ) 25 | target_link_libraries(hyprtrails PRIVATE rt PkgConfig::deps) 26 | 27 | install(TARGETS hyprtrails) 28 | -------------------------------------------------------------------------------- /hyprwinwrap/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.27) 2 | 3 | project(hyprwinwrap 4 | DESCRIPTION "hyprwinwrap plugin for Hyprland" 5 | VERSION 0.1 6 | ) 7 | 8 | set(CMAKE_CXX_STANDARD 23) 9 | 10 | file(GLOB_RECURSE SRC "*.cpp") 11 | 12 | add_library(hyprwinwrap SHARED ${SRC}) 13 | 14 | find_package(PkgConfig REQUIRED) 15 | pkg_check_modules(deps REQUIRED IMPORTED_TARGET 16 | hyprland 17 | libdrm 18 | libinput 19 | libudev 20 | pangocairo 21 | pixman-1 22 | wayland-server 23 | xkbcommon 24 | ) 25 | target_link_libraries(hyprwinwrap PRIVATE rt PkgConfig::deps) 26 | 27 | install(TARGETS hyprwinwrap) 28 | -------------------------------------------------------------------------------- /hyprscrolling/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.27) 2 | 3 | project(hyprscrolling 4 | DESCRIPTION "hyprscrolling plugin for Hyprland" 5 | VERSION 0.1 6 | ) 7 | 8 | set(CMAKE_CXX_STANDARD 23) 9 | 10 | file(GLOB_RECURSE SRC "*.cpp") 11 | 12 | add_library(hyprscrolling SHARED ${SRC}) 13 | 14 | find_package(PkgConfig REQUIRED) 15 | pkg_check_modules(deps REQUIRED IMPORTED_TARGET 16 | hyprland 17 | libdrm 18 | libinput 19 | libudev 20 | pangocairo 21 | pixman-1 22 | wayland-server 23 | xkbcommon 24 | ) 25 | target_link_libraries(hyprscrolling PRIVATE rt PkgConfig::deps) 26 | 27 | install(TARGETS hyprscrolling) 28 | -------------------------------------------------------------------------------- /csgo-vulkan-fix/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.27) 2 | 3 | project(csgo-vulkan-fix 4 | DESCRIPTION "csgo-vulkan-fix plugin for Hyprland" 5 | VERSION 0.1 6 | ) 7 | 8 | set(CMAKE_CXX_STANDARD 23) 9 | 10 | file(GLOB_RECURSE SRC "*.cpp") 11 | 12 | add_library(csgo-vulkan-fix SHARED ${SRC}) 13 | 14 | find_package(PkgConfig REQUIRED) 15 | pkg_check_modules(deps REQUIRED IMPORTED_TARGET 16 | hyprland 17 | libdrm 18 | libinput 19 | libudev 20 | pangocairo 21 | pixman-1 22 | wayland-server 23 | xkbcommon 24 | ) 25 | target_link_libraries(csgo-vulkan-fix PRIVATE rt PkgConfig::deps) 26 | 27 | install(TARGETS csgo-vulkan-fix) 28 | -------------------------------------------------------------------------------- /hyprexpo/OverviewPassElement.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | class COverview; 5 | 6 | class COverviewPassElement : public IPassElement { 7 | public: 8 | COverviewPassElement(); 9 | virtual ~COverviewPassElement() = default; 10 | 11 | virtual void draw(const CRegion& damage); 12 | virtual bool needsLiveBlur(); 13 | virtual bool needsPrecomputeBlur(); 14 | virtual std::optional boundingBox(); 15 | virtual CRegion opaqueRegion(); 16 | 17 | virtual const char* passName() { 18 | return "COverviewPassElement"; 19 | } 20 | }; -------------------------------------------------------------------------------- /xtra-dispatchers/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.27) 2 | 3 | project(xtra-dispatchers 4 | DESCRIPTION "xtra-dispatchers plugin for Hyprland" 5 | VERSION 0.1 6 | ) 7 | 8 | set(CMAKE_CXX_STANDARD 23) 9 | 10 | file(GLOB_RECURSE SRC "*.cpp") 11 | 12 | add_library(xtra-dispatchers SHARED ${SRC}) 13 | 14 | find_package(PkgConfig REQUIRED) 15 | pkg_check_modules(deps REQUIRED IMPORTED_TARGET 16 | hyprland 17 | libdrm 18 | libinput 19 | libudev 20 | pangocairo 21 | pixman-1 22 | wayland-server 23 | xkbcommon 24 | ) 25 | target_link_libraries(xtra-dispatchers PRIVATE rt PkgConfig::deps) 26 | 27 | install(TARGETS xtra-dispatchers) 28 | -------------------------------------------------------------------------------- /borders-plus-plus/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.27) 2 | 3 | project(borders-plus-plus 4 | DESCRIPTION "borders-plus-plus plugin for Hyprland" 5 | VERSION 0.1 6 | ) 7 | 8 | set(CMAKE_CXX_STANDARD 23) 9 | 10 | file(GLOB_RECURSE SRC "*.cpp") 11 | 12 | add_library(borders-plus-plus SHARED ${SRC}) 13 | 14 | find_package(PkgConfig REQUIRED) 15 | pkg_check_modules(deps REQUIRED IMPORTED_TARGET 16 | hyprland 17 | libdrm 18 | libinput 19 | libudev 20 | pangocairo 21 | pixman-1 22 | wayland-server 23 | xkbcommon 24 | ) 25 | target_link_libraries(borders-plus-plus PRIVATE rt PkgConfig::deps) 26 | 27 | install(TARGETS borders-plus-plus) 28 | -------------------------------------------------------------------------------- /hyprtrails/TrailPassElement.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | class CTrail; 5 | 6 | class CTrailPassElement : public IPassElement { 7 | public: 8 | struct STrailData { 9 | CTrail* deco = nullptr; 10 | float a = 1.F; 11 | }; 12 | 13 | CTrailPassElement(const STrailData& data_); 14 | virtual ~CTrailPassElement() = default; 15 | 16 | virtual void draw(const CRegion& damage); 17 | virtual bool needsLiveBlur(); 18 | virtual bool needsPrecomputeBlur(); 19 | 20 | virtual const char* passName() { 21 | return "CTrailPassElement"; 22 | } 23 | 24 | private: 25 | STrailData data; 26 | }; -------------------------------------------------------------------------------- /borders-plus-plus/README.md: -------------------------------------------------------------------------------- 1 | # borders-plus-plus 2 | 3 | Allows you to add one or two additional borders to your windows. 4 | 5 | The borders added are static. 6 | 7 | Example Config: 8 | ``` 9 | plugin { 10 | borders-plus-plus { 11 | add_borders = 1 # 0 - 9 12 | 13 | # you can add up to 9 borders 14 | col.border_1 = rgb(ffffff) 15 | col.border_2 = rgb(2222ff) 16 | 17 | # -1 means "default" as in the one defined in general:border_size 18 | border_size_1 = 10 19 | border_size_2 = -1 20 | 21 | # makes outer edges match rounding of the parent. Turn on / off to better understand. Default = on. 22 | natural_rounding = yes 23 | } 24 | } 25 | ``` -------------------------------------------------------------------------------- /xtra-dispatchers/README.md: -------------------------------------------------------------------------------- 1 | # xtra-dispatchers 2 | 3 | Adds some additional dispatchers to Hyprland. 4 | 5 | ## Dispatchers 6 | 7 | All dispatchers here are called `plugin:xtd:name` e.g. `plugin:xtd:moveorexec`. 8 | 9 | | name | description | params | 10 | | -- | -- | -- | 11 | | moveorexec | moves window to the current workspace, or executes if it's not found. `WINDOW` cannot contain commas | `WINDOW,CMD` | 12 | | throwunfocused | throws all unfocused windows on the current workspace to the given workspace | `WORKSPACE` | 13 | | bringallfrom | kinda inverse of throwunfocused. Bring all windows from a given workspace to the current one. | `WORKSPACE` | 14 | | closeunfocused | close all unfocused windows on the current workspace. | none | 15 | -------------------------------------------------------------------------------- /hyprbars/Makefile: -------------------------------------------------------------------------------- 1 | # Else exist specifically for clang 2 | ifeq ($(CXX),g++) 3 | EXTRA_FLAGS = --no-gnu-unique 4 | else 5 | EXTRA_FLAGS = 6 | endif 7 | 8 | CXXFLAGS = -shared -fPIC -g -std=c++2b -Wno-c++11-narrowing 9 | INCLUDES = `pkg-config --cflags pixman-1 libdrm hyprland pangocairo libinput libudev wayland-server xkbcommon` 10 | LIBS = `pkg-config --libs pangocairo` 11 | 12 | SRC = main.cpp barDeco.cpp BarPassElement.cpp 13 | TARGET = hyprbars.so 14 | 15 | all: $(TARGET) 16 | 17 | $(TARGET): $(SRC) 18 | $(CXX) $(CXXFLAGS) $(EXTRA_FLAGS) $(INCLUDES) $^ $> -o $@ $(LIBS) -O2 19 | 20 | clean: 21 | rm ./$(TARGET) 22 | 23 | meson-build: 24 | mkdir -p build 25 | cd build && meson .. && ninja 26 | 27 | .PHONY: all meson-build clean 28 | -------------------------------------------------------------------------------- /borders-plus-plus/BorderppPassElement.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | class CBordersPlusPlus; 5 | 6 | class CBorderPPPassElement : public IPassElement { 7 | public: 8 | struct SBorderPPData { 9 | CBordersPlusPlus* deco = nullptr; 10 | float a = 1.F; 11 | }; 12 | 13 | CBorderPPPassElement(const SBorderPPData& data_); 14 | virtual ~CBorderPPPassElement() = default; 15 | 16 | virtual void draw(const CRegion& damage); 17 | virtual bool needsLiveBlur(); 18 | virtual bool needsPrecomputeBlur(); 19 | 20 | virtual const char* passName() { 21 | return "CBorderPPPassElement"; 22 | } 23 | 24 | private: 25 | SBorderPPData data; 26 | }; -------------------------------------------------------------------------------- /hyprbars/BarPassElement.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | class CHyprBar; 5 | 6 | class CBarPassElement : public IPassElement { 7 | public: 8 | struct SBarData { 9 | CHyprBar* deco = nullptr; 10 | float a = 1.F; 11 | }; 12 | 13 | CBarPassElement(const SBarData& data_); 14 | virtual ~CBarPassElement() = default; 15 | 16 | virtual void draw(const CRegion& damage); 17 | virtual bool needsLiveBlur(); 18 | virtual bool needsPrecomputeBlur(); 19 | virtual std::optional boundingBox(); 20 | 21 | virtual const char* passName() { 22 | return "CBarPassElement"; 23 | } 24 | 25 | private: 26 | SBarData data; 27 | }; -------------------------------------------------------------------------------- /hyprwinwrap/README.md: -------------------------------------------------------------------------------- 1 | # hyprwinwrap 2 | 3 | Clone of xwinwrap for hyprland. 4 | 5 | Example config: 6 | ```ini 7 | plugin { 8 | hyprwinwrap { 9 | # class is an EXACT match and NOT a regex! 10 | class = kitty-bg 11 | # you can also use title 12 | title = kitty-bg 13 | # you can add the position of the window in a percentage 14 | pos_x = 25 15 | pos_y = 30 16 | # you can add the size of the window in a percentage 17 | size_x = 40 18 | size_y = 70 19 | } 20 | } 21 | 22 | ``` 23 | 24 | Launch `kitty -c "~/.config/hypr/kittyconfigbg.conf" --class="kitty-bg" "/home/vaxry/.config/hypr/cava.sh"` 25 | 26 | Example script for cava: 27 | 28 | ```sh 29 | #!/bin/sh 30 | sleep 1 && cava 31 | ``` 32 | 33 | _sleep required because resizing happens a few ms after open, which breaks cava_ 34 | -------------------------------------------------------------------------------- /hyprbars/globals.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | inline HANDLE PHANDLE = nullptr; 7 | 8 | struct SHyprButton { 9 | std::string cmd = ""; 10 | bool userfg = false; 11 | CHyprColor fgcol = CHyprColor(0, 0, 0, 0); 12 | CHyprColor bgcol = CHyprColor(0, 0, 0, 0); 13 | float size = 10; 14 | std::string icon = ""; 15 | SP iconTex = makeShared(); 16 | }; 17 | 18 | class CHyprBar; 19 | 20 | struct SGlobalState { 21 | std::vector buttons; 22 | std::vector> bars; 23 | uint32_t nobarRuleIdx = 0; 24 | uint32_t barColorRuleIdx = 0; 25 | uint32_t titleColorRuleIdx = 0; 26 | }; 27 | 28 | inline UP g_pGlobalState; 29 | -------------------------------------------------------------------------------- /hyprexpo/OverviewPassElement.cpp: -------------------------------------------------------------------------------- 1 | #include "OverviewPassElement.hpp" 2 | #include 3 | #include "overview.hpp" 4 | 5 | COverviewPassElement::COverviewPassElement() { 6 | ; 7 | } 8 | 9 | void COverviewPassElement::draw(const CRegion& damage) { 10 | g_pOverview->fullRender(); 11 | } 12 | 13 | bool COverviewPassElement::needsLiveBlur() { 14 | return false; 15 | } 16 | 17 | bool COverviewPassElement::needsPrecomputeBlur() { 18 | return false; 19 | } 20 | 21 | std::optional COverviewPassElement::boundingBox() { 22 | if (!g_pOverview->pMonitor) 23 | return std::nullopt; 24 | 25 | return CBox{{}, g_pOverview->pMonitor->m_size}; 26 | } 27 | 28 | CRegion COverviewPassElement::opaqueRegion() { 29 | if (!g_pOverview->pMonitor) 30 | return CRegion{}; 31 | 32 | return CBox{{}, g_pOverview->pMonitor->m_size}; 33 | } 34 | -------------------------------------------------------------------------------- /csgo-vulkan-fix/README.md: -------------------------------------------------------------------------------- 1 | # csgo-vulkan-fix 2 | 3 | Originally meant for csgo / cs2, but can work with any app, really. 4 | 5 | csgo-vulkan-fix is a way to force apps to a fake resolution without 6 | them realizing it. 7 | 8 | If you want to play CS2, you're locked to your native res. 9 | Other resolutions (especially not 16:9) are wonky. 10 | 11 | With this plugin, you aren't anymore. 12 | 13 | This is also useful for when you are scaling and want to force any game to a custom resolution. 14 | 15 | CS2 launch options: 16 | ``` 17 | -vulkan -window -w -h -vulkan 18 | ``` 19 | 20 | example plugin config: 21 | ``` 22 | plugin { 23 | csgo-vulkan-fix { 24 | # Whether to fix the mouse position. A select few apps might be wonky with this. 25 | fix_mouse = true 26 | 27 | # Add apps with vkfix-app = initialClass, width, height 28 | vkfix-app = cs2, 1650, 1050 29 | vkfix-app = myapp, 1920, 1080 30 | } 31 | } 32 | ``` 33 | 34 | fullscreen the game manually and enjoy. -------------------------------------------------------------------------------- /hyprfocus/meson.build: -------------------------------------------------------------------------------- 1 | project('hyprfocus', 'cpp', 2 | version: '0.1', 3 | default_options: ['buildtype=release'], 4 | ) 5 | 6 | cpp_compiler = meson.get_compiler('cpp') 7 | if cpp_compiler.has_argument('-std=c++23') 8 | add_global_arguments('-std=c++23', language: 'cpp') 9 | elif cpp_compiler.has_argument('-std=c++2b') 10 | add_global_arguments('-std=c++2b', language: 'cpp') 11 | else 12 | error('Could not configure current C++ compiler (' + cpp_compiler.get_id() + ' ' + cpp_compiler.version() + ') with required C++ standard (C++23)') 13 | endif 14 | 15 | globber = run_command('find', '.', '-name', '*.cpp', check: true) 16 | src = globber.stdout().strip().split('\n') 17 | 18 | shared_module(meson.project_name(), src, 19 | dependencies: [ 20 | dependency('hyprland'), 21 | dependency('libdrm'), 22 | dependency('libinput'), 23 | dependency('libudev'), 24 | dependency('pangocairo'), 25 | dependency('pixman-1'), 26 | dependency('wayland-server'), 27 | dependency('xkbcommon'), 28 | ], 29 | install: true, 30 | ) 31 | -------------------------------------------------------------------------------- /hyprscrolling/meson.build: -------------------------------------------------------------------------------- 1 | project('hyprscrolling', 'cpp', 2 | version: '0.1', 3 | default_options: ['buildtype=release'], 4 | ) 5 | 6 | cpp_compiler = meson.get_compiler('cpp') 7 | if cpp_compiler.has_argument('-std=c++23') 8 | add_global_arguments('-std=c++23', language: 'cpp') 9 | elif cpp_compiler.has_argument('-std=c++2b') 10 | add_global_arguments('-std=c++2b', language: 'cpp') 11 | else 12 | error('Could not configure current C++ compiler (' + cpp_compiler.get_id() + ' ' + cpp_compiler.version() + ') with required C++ standard (C++23)') 13 | endif 14 | 15 | globber = run_command('find', '.', '-name', '*.cpp', check: true) 16 | src = globber.stdout().strip().split('\n') 17 | 18 | shared_module(meson.project_name(), src, 19 | dependencies: [ 20 | dependency('hyprland'), 21 | dependency('pixman-1'), 22 | dependency('libdrm'), 23 | dependency('pangocairo'), 24 | dependency('libinput'), 25 | dependency('libudev'), 26 | dependency('wayland-server'), 27 | dependency('xkbcommon'), 28 | ], 29 | install: true, 30 | ) 31 | -------------------------------------------------------------------------------- /hyprwinwrap/meson.build: -------------------------------------------------------------------------------- 1 | project('hyprwinwrap', 'cpp', 2 | version: '0.1', 3 | default_options: ['buildtype=release'], 4 | ) 5 | 6 | cpp_compiler = meson.get_compiler('cpp') 7 | if cpp_compiler.has_argument('-std=c++23') 8 | add_global_arguments('-std=c++23', language: 'cpp') 9 | elif cpp_compiler.has_argument('-std=c++2b') 10 | add_global_arguments('-std=c++2b', language: 'cpp') 11 | else 12 | error('Could not configure current C++ compiler (' + cpp_compiler.get_id() + ' ' + cpp_compiler.version() + ') with required C++ standard (C++23)') 13 | endif 14 | 15 | globber = run_command('find', '.', '-name', '*.cpp', check: true) 16 | src = globber.stdout().strip().split('\n') 17 | 18 | shared_module(meson.project_name(), src, 19 | dependencies: [ 20 | dependency('hyprland'), 21 | dependency('pixman-1'), 22 | dependency('libdrm'), 23 | dependency('pangocairo'), 24 | dependency('libinput'), 25 | dependency('libudev'), 26 | dependency('wayland-server'), 27 | dependency('xkbcommon'), 28 | ], 29 | install: true, 30 | ) 31 | -------------------------------------------------------------------------------- /csgo-vulkan-fix/meson.build: -------------------------------------------------------------------------------- 1 | project('csgo-vulkan-fix', 'cpp', 2 | version: '0.1', 3 | default_options: ['buildtype=release'], 4 | ) 5 | 6 | cpp_compiler = meson.get_compiler('cpp') 7 | if cpp_compiler.has_argument('-std=c++23') 8 | add_global_arguments('-std=c++23', language: 'cpp') 9 | elif cpp_compiler.has_argument('-std=c++2b') 10 | add_global_arguments('-std=c++2b', language: 'cpp') 11 | else 12 | error('Could not configure current C++ compiler (' + cpp_compiler.get_id() + ' ' + cpp_compiler.version() + ') with required C++ standard (C++23)') 13 | endif 14 | 15 | globber = run_command('find', '.', '-name', '*.cpp', check: true) 16 | src = globber.stdout().strip().split('\n') 17 | 18 | shared_module(meson.project_name(), src, 19 | dependencies: [ 20 | dependency('hyprland'), 21 | dependency('pixman-1'), 22 | dependency('libdrm'), 23 | dependency('pangocairo'), 24 | dependency('libinput'), 25 | dependency('libudev'), 26 | dependency('wayland-server'), 27 | dependency('xkbcommon'), 28 | ], 29 | install: true, 30 | ) 31 | -------------------------------------------------------------------------------- /borders-plus-plus/meson.build: -------------------------------------------------------------------------------- 1 | project('borders-plus-plus', 'cpp', 2 | version: '0.1', 3 | default_options: ['buildtype=release'], 4 | ) 5 | 6 | cpp_compiler = meson.get_compiler('cpp') 7 | if cpp_compiler.has_argument('-std=c++23') 8 | add_global_arguments('-std=c++23', language: 'cpp') 9 | elif cpp_compiler.has_argument('-std=c++2b') 10 | add_global_arguments('-std=c++2b', language: 'cpp') 11 | else 12 | error('Could not configure current C++ compiler (' + cpp_compiler.get_id() + ' ' + cpp_compiler.version() + ') with required C++ standard (C++23)') 13 | endif 14 | 15 | globber = run_command('find', '.', '-name', '*.cpp', check: true) 16 | src = globber.stdout().strip().split('\n') 17 | 18 | shared_module(meson.project_name(), src, 19 | dependencies: [ 20 | dependency('hyprland'), 21 | dependency('pixman-1'), 22 | dependency('libdrm'), 23 | dependency('pangocairo'), 24 | dependency('libinput'), 25 | dependency('libudev'), 26 | dependency('wayland-server'), 27 | dependency('xkbcommon'), 28 | ], 29 | install: true, 30 | ) 31 | -------------------------------------------------------------------------------- /xtra-dispatchers/meson.build: -------------------------------------------------------------------------------- 1 | project('xtra-dispatchers', 'cpp', 2 | version: '0.1', 3 | default_options: ['buildtype=release'], 4 | ) 5 | 6 | cpp_compiler = meson.get_compiler('cpp') 7 | if cpp_compiler.has_argument('-std=c++23') 8 | add_global_arguments('-std=c++23', language: 'cpp') 9 | elif cpp_compiler.has_argument('-std=c++2b') 10 | add_global_arguments('-std=c++2b', language: 'cpp') 11 | else 12 | error('Could not configure current C++ compiler (' + cpp_compiler.get_id() + ' ' + cpp_compiler.version() + ') with required C++ standard (C++23)') 13 | endif 14 | 15 | globber = run_command('find', '.', '-name', '*.cpp', check: true) 16 | src = globber.stdout().strip().split('\n') 17 | 18 | shared_module(meson.project_name(), src, 19 | dependencies: [ 20 | dependency('hyprland'), 21 | dependency('pixman-1'), 22 | dependency('libdrm'), 23 | dependency('pangocairo'), 24 | dependency('libinput'), 25 | dependency('libudev'), 26 | dependency('wayland-server'), 27 | dependency('xkbcommon'), 28 | ], 29 | install: true, 30 | ) 31 | -------------------------------------------------------------------------------- /hyprbars/meson.build: -------------------------------------------------------------------------------- 1 | project('hyprbars', 'cpp', 2 | version: '0.1', 3 | default_options: ['buildtype=release'], 4 | ) 5 | 6 | cpp_compiler = meson.get_compiler('cpp') 7 | if cpp_compiler.has_argument('-std=c++23') 8 | add_global_arguments('-std=c++23', language: 'cpp') 9 | elif cpp_compiler.has_argument('-std=c++2b') 10 | add_global_arguments('-std=c++2b', language: 'cpp') 11 | else 12 | error('Could not configure current C++ compiler (' + cpp_compiler.get_id() + ' ' + cpp_compiler.version() + ') with required C++ standard (C++23)') 13 | endif 14 | 15 | add_project_arguments( 16 | [ 17 | '-Wno-narrowing', 18 | ], 19 | language: 'cpp') 20 | 21 | globber = run_command('find', '.', '-name', '*.cpp', check: true) 22 | src = globber.stdout().strip().split('\n') 23 | 24 | hyprland = dependency('hyprland') 25 | 26 | shared_module(meson.project_name(), src, 27 | dependencies: [ 28 | dependency('hyprland'), 29 | dependency('pixman-1'), 30 | dependency('libdrm'), 31 | dependency('pangocairo'), 32 | dependency('libinput'), 33 | dependency('libudev'), 34 | dependency('wayland-server'), 35 | dependency('xkbcommon'), 36 | ], 37 | install: true, 38 | ) 39 | -------------------------------------------------------------------------------- /hyprexpo/meson.build: -------------------------------------------------------------------------------- 1 | project('hyprexpo', 'cpp', 2 | version: '0.1', 3 | default_options: ['buildtype=release'], 4 | ) 5 | 6 | cpp_compiler = meson.get_compiler('cpp') 7 | if cpp_compiler.has_argument('-std=c++23') 8 | add_global_arguments('-std=c++23', language: 'cpp') 9 | elif cpp_compiler.has_argument('-std=c++2b') 10 | add_global_arguments('-std=c++2b', language: 'cpp') 11 | else 12 | error('Could not configure current C++ compiler (' + cpp_compiler.get_id() + ' ' + cpp_compiler.version() + ') with required C++ standard (C++23)') 13 | endif 14 | 15 | add_project_arguments( 16 | [ 17 | '-Wno-narrowing', 18 | ], 19 | language: 'cpp') 20 | 21 | globber = run_command('find', '.', '-name', '*.cpp', check: true) 22 | src = globber.stdout().strip().split('\n') 23 | 24 | hyprland = dependency('hyprland') 25 | 26 | shared_module(meson.project_name(), src, 27 | dependencies: [ 28 | dependency('hyprland'), 29 | dependency('pixman-1'), 30 | dependency('libdrm'), 31 | dependency('pangocairo'), 32 | dependency('libinput'), 33 | dependency('libudev'), 34 | dependency('wayland-server'), 35 | dependency('xkbcommon'), 36 | ], 37 | install: true, 38 | ) 39 | -------------------------------------------------------------------------------- /hyprfocus/README.md: -------------------------------------------------------------------------------- 1 | # hyprfocus 2 | 3 | Flashfocus for Hyprland. 4 | 5 | ## Configuring 6 | 7 | ### Animations 8 | 9 | Hyprfocus exposes two animation leaves: `hyprfocusIn` and `hyprfocusOut`: 10 | ```ini 11 | animation = hyprfocusIn, 1, 1.7, myCurve 12 | animation = hyprfocusOut, 1, 1.7, myCurve2 13 | ``` 14 | 15 | ### Variables 16 | 17 | | name | description | type | default | 18 | |--------------------------|----------------------------------------------------------------------|---------|---------| 19 | | `mode` | which mode to use (flash / bounce / slide) | str | flash | 20 | | `only_on_monitor_change` | whether to only perform the animation when changing between monitors | boolean | false | 21 | | `fade_opacity` | for flash, what opacity to flash to | float | 0.8 | 22 | | `bounce_strength` | for bounce, what fraction of the window's size to jump to | float | 0.95 | 23 | | `slide_height` | for slide, how far up to slide | float | 20 | 24 | -------------------------------------------------------------------------------- /hyprtrails/meson.build: -------------------------------------------------------------------------------- 1 | project('hyprtrails', 'cpp', 2 | version: '0.1', 3 | default_options: ['buildtype=release'], 4 | ) 5 | 6 | cpp_compiler = meson.get_compiler('cpp') 7 | if cpp_compiler.has_argument('-std=c++23') 8 | add_global_arguments('-std=c++23', language: 'cpp') 9 | elif cpp_compiler.has_argument('-std=c++2b') 10 | add_global_arguments('-std=c++2b', language: 'cpp') 11 | else 12 | error('Could not configure current C++ compiler (' + cpp_compiler.get_id() + ' ' + cpp_compiler.version() + ') with required C++ standard (C++23)') 13 | endif 14 | 15 | add_project_arguments( 16 | [ 17 | '-Wno-narrowing', 18 | ], 19 | language: 'cpp') 20 | 21 | globber = run_command('find', '.', '-name', '*.cpp', check: true) 22 | src = globber.stdout().strip().split('\n') 23 | 24 | hyprland = dependency('hyprland') 25 | 26 | shared_module(meson.project_name(), src, 27 | dependencies: [ 28 | dependency('hyprland'), 29 | dependency('pixman-1'), 30 | dependency('libdrm'), 31 | dependency('pangocairo'), 32 | dependency('libinput'), 33 | dependency('libudev'), 34 | dependency('wayland-server'), 35 | dependency('xkbcommon'), 36 | ], 37 | install: true, 38 | ) 39 | -------------------------------------------------------------------------------- /hyprexpo/ExpoGesture.cpp: -------------------------------------------------------------------------------- 1 | #include "ExpoGesture.hpp" 2 | 3 | #include "overview.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | void CExpoGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& e) { 10 | ITrackpadGesture::begin(e); 11 | 12 | m_lastDelta = 0.F; 13 | m_firstUpdate = true; 14 | 15 | if (!g_pOverview) 16 | g_pOverview = std::make_unique(Desktop::focusState()->monitor()->m_activeWorkspace); 17 | else { 18 | g_pOverview->selectHoveredWorkspace(); 19 | g_pOverview->setClosing(true); 20 | } 21 | } 22 | 23 | void CExpoGesture::update(const ITrackpadGesture::STrackpadGestureUpdate& e) { 24 | if (m_firstUpdate) { 25 | m_firstUpdate = false; 26 | return; 27 | } 28 | 29 | m_lastDelta += distance(e); 30 | 31 | if (m_lastDelta <= 0.01) // plugin will crash if swipe ends at <= 0 32 | m_lastDelta = 0.01; 33 | 34 | g_pOverview->onSwipeUpdate(m_lastDelta); 35 | } 36 | 37 | void CExpoGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e) { 38 | g_pOverview->setClosing(false); 39 | g_pOverview->onSwipeEnd(); 40 | g_pOverview->resetSwipe(); 41 | } 42 | -------------------------------------------------------------------------------- /hyprload.toml: -------------------------------------------------------------------------------- 1 | [borders-plus-plus] 2 | description ="A plugin to add more borders to windows." 3 | version = "1.0" 4 | authors = ["Vaxry"] 5 | 6 | [borders-plus-plus.build] 7 | output = "borders-plus-plus/borders-plus-plus.so" 8 | steps = [ 9 | "make -C borders-plus-plus all", 10 | ] 11 | 12 | [csgo-vulkan-fix] 13 | description = "A plugin to fix incorrect mouse offsets on csgo in Vulkan" 14 | version = "1.0" 15 | authors = ["Vaxry"] 16 | 17 | [csgo-vulkan-fix.build] 18 | output = "csgo-vulkan-fix/csgo-vulkan-fix.so" 19 | steps = [ 20 | "make -C csgo-vulkan-fix all", 21 | ] 22 | 23 | [hyprbars] 24 | description = "A plugin to add title bars to windows" 25 | version = "1.0" 26 | authors = ["Vaxry"] 27 | 28 | [hyprbars.build] 29 | output = "hyprbars/hyprbars.so" 30 | steps = [ 31 | "make -C hyprbars all", 32 | ] 33 | 34 | [hyprtrails] 35 | description = "A plugin to add trails behind moving windows" 36 | version = "1.0" 37 | authors = ["Vaxry"] 38 | 39 | [hyprtrails.build] 40 | output = "hyprtrails/hyprtrails.so" 41 | steps = [ 42 | "make -C hyprtrails all", 43 | ] 44 | 45 | [hyprwinwrap] 46 | description = "A clone of xwinwrap for Hyprland" 47 | version = "1.0" 48 | authors = ["Vaxry"] 49 | 50 | [hyprwinwrap.build] 51 | output = "hyprwinwrap/hyprwinwrap.so" 52 | steps = [ 53 | "make -C hyprwinwrap all", 54 | ] 55 | -------------------------------------------------------------------------------- /borders-plus-plus/borderDeco.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define WLR_USE_UNSTABLE 4 | 5 | #include 6 | 7 | class CBordersPlusPlus : public IHyprWindowDecoration { 8 | public: 9 | CBordersPlusPlus(PHLWINDOW); 10 | virtual ~CBordersPlusPlus(); 11 | 12 | virtual SDecorationPositioningInfo getPositioningInfo(); 13 | 14 | virtual void onPositioningReply(const SDecorationPositioningReply& reply); 15 | 16 | virtual void draw(PHLMONITOR, float const& a); 17 | 18 | virtual eDecorationType getDecorationType(); 19 | 20 | virtual void updateWindow(PHLWINDOW); 21 | 22 | virtual void damageEntire(); 23 | 24 | virtual uint64_t getDecorationFlags(); 25 | 26 | virtual eDecorationLayer getDecorationLayer(); 27 | 28 | virtual std::string getDisplayName(); 29 | 30 | private: 31 | void drawPass(PHLMONITOR, float const& a); 32 | 33 | SBoxExtents m_seExtents; 34 | 35 | PHLWINDOWREF m_pWindow; 36 | 37 | CBox m_bLastRelativeBox; 38 | CBox m_bAssignedGeometry; 39 | 40 | Vector2D m_lastWindowPos; 41 | Vector2D m_lastWindowSize; 42 | 43 | double m_fLastThickness = 0; 44 | 45 | friend class CBorderPPPassElement; 46 | }; 47 | -------------------------------------------------------------------------------- /hyprtrails/shaders.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | inline const std::string QUADTRAIL = R"#( 6 | #version 300 es 7 | precision mediump float; 8 | uniform mat3 proj; 9 | uniform vec4 color; 10 | in vec2 pos; 11 | in vec2 texcoord; 12 | in vec4 colors; 13 | out vec4 v_color; 14 | out vec2 v_texcoord; 15 | 16 | void main() { 17 | gl_Position = vec4(proj * vec3(pos, 1.0), 1.0); 18 | v_color = color; 19 | v_texcoord = texcoord; 20 | })#"; 21 | 22 | inline const std::string FRAGTRAIL = R"#( 23 | #version 300 es 24 | precision mediump float; 25 | in vec4 v_color; 26 | in vec2 v_texcoord; 27 | 28 | uniform vec4 window; 29 | 30 | layout(location = 0) out vec4 fragColor; 31 | 32 | float distToRect(vec4 rect) { 33 | float dx = max(rect[0] - v_texcoord[0], max(0.0, v_texcoord[0] - rect[2])); 34 | float dy = max(rect[1] - v_texcoord[1], max(0.0, v_texcoord[1] - rect[3])); 35 | return sqrt(dx*dx + dy*dy); 36 | } 37 | 38 | float alphaForShot(vec4 shot, float threshold) { 39 | 40 | float dist = distToRect(shot); 41 | 42 | if (dist > threshold) 43 | return 0.0; 44 | 45 | if (dist <= 0.0) 46 | return 0.0; 47 | 48 | return 1.0 - (dist * (1.0 / threshold)); 49 | } 50 | 51 | void main() { 52 | 53 | vec4 pixColor = v_color; 54 | float a = v_color[3]; // clamp(alphaForShot(window, 0.5), 0.0, 1.0); // todo 55 | 56 | pixColor.rgb *= a; 57 | 58 | fragColor = pixColor; 59 | })#"; 60 | -------------------------------------------------------------------------------- /hyprbars/BarPassElement.cpp: -------------------------------------------------------------------------------- 1 | #include "BarPassElement.hpp" 2 | #include 3 | #include "barDeco.hpp" 4 | 5 | CBarPassElement::CBarPassElement(const CBarPassElement::SBarData& data_) : data(data_) { 6 | ; 7 | } 8 | 9 | void CBarPassElement::draw(const CRegion& damage) { 10 | data.deco->renderPass(g_pHyprOpenGL->m_renderData.pMonitor.lock(), data.a); 11 | } 12 | 13 | bool CBarPassElement::needsLiveBlur() { 14 | static auto* const PCOLOR = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_color")->getDataStaticPtr(); 15 | static auto* const PENABLEBLUR = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_blur")->getDataStaticPtr(); 16 | static auto* const PENABLEBLURGLOBAL = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "decoration:blur:enabled")->getDataStaticPtr(); 17 | 18 | CHyprColor color = data.deco->m_bForcedBarColor.value_or(**PCOLOR); 19 | color.a *= data.a; 20 | const bool SHOULDBLUR = **PENABLEBLUR && **PENABLEBLURGLOBAL && color.a < 1.F; 21 | 22 | return SHOULDBLUR; 23 | } 24 | 25 | std::optional CBarPassElement::boundingBox() { 26 | // Temporary fix: expand the bar bb a bit, otherwise occlusion gets too aggressive. 27 | return data.deco->assignedBoxGlobal().translate(-g_pHyprOpenGL->m_renderData.pMonitor->m_position).expand(10); 28 | } 29 | 30 | bool CBarPassElement::needsPrecomputeBlur() { 31 | return false; 32 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, Hypr Development 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /.github/workflows/nix-build.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v3 9 | 10 | - name: Install Nix 11 | uses: nixbuild/nix-quick-install-action@v31 12 | with: 13 | nix_conf: | 14 | keep-env-derivations = true 15 | keep-outputs = true 16 | 17 | - name: Restore and save Nix store 18 | uses: nix-community/cache-nix-action@v6 19 | with: 20 | # restore and save a cache using this key 21 | primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} 22 | # if there's no cache hit, restore a cache by this prefix 23 | restore-prefixes-first-match: nix-${{ runner.os }}- 24 | # collect garbage until the Nix store size (in bytes) is at most this number 25 | # before trying to save a new cache 26 | # 1G = 1073741824 27 | gc-max-store-size-linux: 1G 28 | # do purge caches 29 | purge: true 30 | # purge all versions of the cache 31 | purge-prefixes: nix-${{ runner.os }}- 32 | # created more than this number of seconds ago 33 | purge-created: 0 34 | # or, last accessed more than this number of seconds ago 35 | # relative to the start of the `Post Restore and save Nix store` phase 36 | purge-last-accessed: 0 37 | # except any version with the key that is the same as the `primary-key` 38 | purge-primary-key: never 39 | 40 | - name: Update inputs 41 | run: nix flake update 42 | 43 | - run: nix flake check -L --keep-going 44 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: LLVM 4 | 5 | AccessModifierOffset: -2 6 | AlignAfterOpenBracket: Align 7 | AlignConsecutiveMacros: true 8 | AlignConsecutiveAssignments: true 9 | AlignEscapedNewlines: Right 10 | AlignOperands: false 11 | AlignTrailingComments: true 12 | AllowAllArgumentsOnNextLine: true 13 | AllowAllConstructorInitializersOnNextLine: true 14 | AllowAllParametersOfDeclarationOnNextLine: true 15 | AllowShortBlocksOnASingleLine: true 16 | AllowShortCaseLabelsOnASingleLine: true 17 | AllowShortFunctionsOnASingleLine: Empty 18 | AllowShortIfStatementsOnASingleLine: Never 19 | AllowShortLambdasOnASingleLine: All 20 | AllowShortLoopsOnASingleLine: false 21 | AlwaysBreakAfterDefinitionReturnType: None 22 | AlwaysBreakAfterReturnType: None 23 | AlwaysBreakBeforeMultilineStrings: false 24 | AlwaysBreakTemplateDeclarations: Yes 25 | BreakBeforeBraces: Attach 26 | BreakBeforeTernaryOperators: false 27 | BreakConstructorInitializers: AfterColon 28 | ColumnLimit: 180 29 | CompactNamespaces: false 30 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 31 | ExperimentalAutoDetectBinPacking: false 32 | FixNamespaceComments: false 33 | IncludeBlocks: Preserve 34 | IndentCaseLabels: true 35 | IndentWidth: 4 36 | PointerAlignment: Left 37 | ReflowComments: false 38 | SortIncludes: false 39 | SortUsingDeclarations: false 40 | SpaceAfterCStyleCast: false 41 | SpaceAfterLogicalNot: false 42 | SpaceAfterTemplateKeyword: true 43 | SpaceBeforeCtorInitializerColon: true 44 | SpaceBeforeInheritanceColon: true 45 | SpaceBeforeParens: ControlStatements 46 | SpaceBeforeRangeBasedForLoopColon: true 47 | SpaceInEmptyParentheses: false 48 | SpacesBeforeTrailingComments: 1 49 | SpacesInAngles: false 50 | SpacesInCStyleCastParentheses: false 51 | SpacesInContainerLiterals: false 52 | SpacesInParentheses: false 53 | SpacesInSquareBrackets: false 54 | Standard: Auto 55 | TabWidth: 4 56 | UseTab: Never 57 | 58 | AllowShortEnumsOnASingleLine: false 59 | 60 | BraceWrapping: 61 | AfterEnum: false 62 | 63 | AlignConsecutiveDeclarations: AcrossEmptyLines 64 | 65 | NamespaceIndentation: All 66 | -------------------------------------------------------------------------------- /hyprexpo/README.md: -------------------------------------------------------------------------------- 1 | # HyprExpo 2 | HyprExpo is an overview plugin like Gnome, KDE or wf. 3 | 4 | ![HyprExpo](https://github.com/user-attachments/assets/e89df9d2-9800-4268-9929-239ad9bc3a54) 5 | 6 | ## Config 7 | A great start to configure this plugin would be adding this code to the `plugin` section of your hyprland configuration file: 8 | ```ini 9 | # .config/hypr/hyprland.conf 10 | plugin { 11 | hyprexpo { 12 | columns = 3 13 | gap_size = 5 14 | bg_col = rgb(111111) 15 | workspace_method = center current # [center/first] [workspace] e.g. first 1 or center m+1 16 | 17 | gesture_distance = 300 # how far is the "max" for the gesture 18 | } 19 | } 20 | ``` 21 | 22 | ### Properties 23 | 24 | | property | type | description | default | 25 | | --- | --- | --- | --- | 26 | columns | number | how many desktops are displayed on one line | `3` 27 | gap_size | number | gap between desktops | `5` 28 | bg_col | color | color in gaps (between desktops) | `rgb(000000)` 29 | workspace_method | [center/first] [workspace] | position of the desktops | `center current` 30 | skip_empty | boolean | whether the grid displays workspaces sequentially by id using selector "r" (`false`) or skips empty workspaces using selector "m" (`true`) | `false` 31 | gesture_distance | number | how far is the max for the gesture | `300` 32 | 33 | ### Keywords 34 | 35 | | name | description | arguments | 36 | | -- | -- | -- | 37 | | hyprexpo-gesture | same as gesture, but for hyprexpo gestures. Supports: `expo`. | Same as gesture | 38 | 39 | ### Binding 40 | ```bash 41 | # hyprland.conf 42 | bind = MODIFIER, KEY, hyprexpo:expo, OPTION 43 | ``` 44 | 45 | Example: 46 | ```bash 47 | # This will toggle HyprExpo when SUPER+g is pressed 48 | bind = SUPER, g, hyprexpo:expo, toggle 49 | ``` 50 | 51 | Here are a list of options you can use: 52 | | option | description | 53 | | --- | --- | 54 | toggle | displays if hidden, hide if displayed 55 | select | selects the hovered desktop 56 | off | hides the overview 57 | disable | same as `off` 58 | on | displays the overview 59 | enable | same as `on` 60 | 61 | -------------------------------------------------------------------------------- /hyprtrails/trail.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define WLR_USE_UNSTABLE 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | struct box { 10 | float x = 0, y = 0, w = 0, h = 0; 11 | 12 | // 13 | Vector2D middle() { 14 | return Vector2D{x + w / 2.0, y + h / 2.0}; 15 | } 16 | }; 17 | struct point2 { 18 | point2(const Vector2D& v) { 19 | x = v.x; 20 | y = v.y; 21 | } 22 | 23 | point2() { 24 | x = 0; 25 | y = 0; 26 | } 27 | 28 | float x = 0, y = 0; 29 | }; 30 | 31 | class CTrail : public IHyprWindowDecoration { 32 | public: 33 | CTrail(PHLWINDOW); 34 | virtual ~CTrail(); 35 | 36 | virtual SDecorationPositioningInfo getPositioningInfo(); 37 | 38 | virtual void onPositioningReply(const SDecorationPositioningReply& reply); 39 | 40 | virtual void draw(PHLMONITOR, float const& a); 41 | 42 | virtual eDecorationType getDecorationType(); 43 | 44 | virtual void updateWindow(PHLWINDOW); 45 | 46 | virtual void damageEntire(); 47 | 48 | private: 49 | SP pTickCb; 50 | void onTick(); 51 | void renderPass(PHLMONITOR pMonitor, const float& a); 52 | 53 | std::deque> m_dLastGeoms; 54 | 55 | int m_iTimer = 0; 56 | 57 | SBoxExtents m_seExtents; 58 | 59 | PHLWINDOWREF m_pWindow; 60 | 61 | Vector2D m_lastWindowPos; 62 | Vector2D m_lastWindowSize; 63 | 64 | CBox m_bLastBox = {0}; 65 | bool m_bNeedsDamage = false; 66 | 67 | friend class CTrailPassElement; 68 | }; 69 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Official Hyprland Plugins"; 3 | 4 | inputs = { 5 | hyprland.url = "github:hyprwm/Hyprland"; 6 | nixpkgs.follows = "hyprland/nixpkgs"; 7 | systems.follows = "hyprland/systems"; 8 | }; 9 | 10 | outputs = { 11 | self, 12 | hyprland, 13 | nixpkgs, 14 | systems, 15 | ... 16 | }: let 17 | inherit (nixpkgs) lib; 18 | eachSystem = lib.genAttrs (import systems); 19 | 20 | pkgsFor = eachSystem (system: 21 | import nixpkgs { 22 | localSystem.system = system; 23 | overlays = [ 24 | self.overlays.hyprland-plugins 25 | hyprland.overlays.hyprland-packages 26 | ]; 27 | }); 28 | in { 29 | packages = eachSystem (system: { 30 | inherit 31 | (pkgsFor.${system}.hyprlandPlugins) 32 | borders-plus-plus 33 | csgo-vulkan-fix 34 | hyprbars 35 | hyprexpo 36 | hyprfocus 37 | hyprscrolling 38 | hyprtrails 39 | hyprwinwrap 40 | xtra-dispatchers 41 | ; 42 | }); 43 | 44 | overlays = { 45 | default = self.overlays.hyprland-plugins; 46 | 47 | hyprland-plugins = final: prev: let 48 | inherit (final) callPackage; 49 | in { 50 | hyprlandPlugins = 51 | (prev.hyprlandPlugins 52 | or {}) 53 | // { 54 | borders-plus-plus = callPackage ./borders-plus-plus {}; 55 | csgo-vulkan-fix = callPackage ./csgo-vulkan-fix {}; 56 | hyprbars = callPackage ./hyprbars {}; 57 | hyprexpo = callPackage ./hyprexpo {}; 58 | hyprfocus = callPackage ./hyprfocus {}; 59 | hyprscrolling = callPackage ./hyprscrolling {}; 60 | hyprtrails = callPackage ./hyprtrails {}; 61 | hyprwinwrap = callPackage ./hyprwinwrap {}; 62 | xtra-dispatchers = callPackage ./xtra-dispatchers {}; 63 | }; 64 | }; 65 | }; 66 | 67 | checks = eachSystem (system: self.packages.${system}); 68 | 69 | devShells = eachSystem (system: 70 | with pkgsFor.${system}; { 71 | default = mkShell.override {stdenv = gcc14Stdenv;} { 72 | name = "hyprland-plugins"; 73 | buildInputs = [hyprland.packages.${system}.hyprland]; 74 | inputsFrom = [hyprland.packages.${system}.hyprland]; 75 | }; 76 | }); 77 | }; 78 | } 79 | -------------------------------------------------------------------------------- /hyprexpo/overview.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define WLR_USE_UNSTABLE 4 | 5 | #include "globals.hpp" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | // saves on resources, but is a bit broken rn with blur. 13 | // hyprland's fault, but cba to fix. 14 | constexpr bool ENABLE_LOWRES = false; 15 | 16 | class CMonitor; 17 | 18 | class COverview { 19 | public: 20 | COverview(PHLWORKSPACE startedOn_, bool swipe = false); 21 | ~COverview(); 22 | 23 | void render(); 24 | void damage(); 25 | void onDamageReported(); 26 | void onPreRender(); 27 | 28 | void setClosing(bool closing); 29 | 30 | void resetSwipe(); 31 | void onSwipeUpdate(double delta); 32 | void onSwipeEnd(); 33 | 34 | // close without a selection 35 | void close(); 36 | void selectHoveredWorkspace(); 37 | 38 | bool blockOverviewRendering = false; 39 | bool blockDamageReporting = false; 40 | 41 | PHLMONITORREF pMonitor; 42 | bool m_isSwiping = false; 43 | 44 | private: 45 | void redrawID(int id, bool forcelowres = false); 46 | void redrawAll(bool forcelowres = false); 47 | void onWorkspaceChange(); 48 | void fullRender(); 49 | 50 | int SIDE_LENGTH = 3; 51 | int GAP_WIDTH = 5; 52 | CHyprColor BG_COLOR = CHyprColor{0.1, 0.1, 0.1, 1.0}; 53 | 54 | bool damageDirty = false; 55 | 56 | struct SWorkspaceImage { 57 | CFramebuffer fb; 58 | int64_t workspaceID = -1; 59 | PHLWORKSPACE pWorkspace; 60 | CBox box; 61 | }; 62 | 63 | Vector2D lastMousePosLocal = Vector2D{}; 64 | 65 | int openedID = -1; 66 | int closeOnID = -1; 67 | 68 | std::vector images; 69 | 70 | PHLWORKSPACE startedOn; 71 | 72 | PHLANIMVAR size; 73 | PHLANIMVAR pos; 74 | 75 | bool closing = false; 76 | 77 | SP mouseMoveHook; 78 | SP mouseButtonHook; 79 | SP touchMoveHook; 80 | SP touchDownHook; 81 | 82 | bool swipe = false; 83 | bool swipeWasCommenced = false; 84 | 85 | friend class COverviewPassElement; 86 | }; 87 | 88 | inline std::unique_ptr g_pOverview; 89 | -------------------------------------------------------------------------------- /borders-plus-plus/main.cpp: -------------------------------------------------------------------------------- 1 | #define WLR_USE_UNSTABLE 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "borderDeco.hpp" 12 | #include "globals.hpp" 13 | 14 | // Do NOT change this function. 15 | APICALL EXPORT std::string PLUGIN_API_VERSION() { 16 | return HYPRLAND_API_VERSION; 17 | } 18 | 19 | void onNewWindow(void* self, std::any data) { 20 | // data is guaranteed 21 | const auto PWINDOW = std::any_cast(data); 22 | 23 | HyprlandAPI::addWindowDecoration(PHANDLE, PWINDOW, makeUnique(PWINDOW)); 24 | } 25 | 26 | APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { 27 | PHANDLE = handle; 28 | 29 | const std::string HASH = __hyprland_api_get_hash(); 30 | const std::string CLIENT_HASH = __hyprland_api_get_client_hash(); 31 | 32 | if (HASH != CLIENT_HASH) { 33 | HyprlandAPI::addNotification(PHANDLE, "[borders-plus-plus] Failure in initialization: Version mismatch (headers ver is not equal to running hyprland ver)", 34 | CHyprColor{1.0, 0.2, 0.2, 1.0}, 5000); 35 | throw std::runtime_error("[bpp] Version mismatch"); 36 | } 37 | 38 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:borders-plus-plus:add_borders", Hyprlang::INT{1}); 39 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:borders-plus-plus:natural_rounding", Hyprlang::INT{1}); 40 | 41 | for (size_t i = 0; i < 9; ++i) { 42 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:borders-plus-plus:col.border_" + std::to_string(i + 1), Hyprlang::INT{*configStringToInt("rgba(000000ee)")}); 43 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:borders-plus-plus:border_size_" + std::to_string(i + 1), Hyprlang::INT{-1}); 44 | } 45 | 46 | HyprlandAPI::reloadConfig(); 47 | 48 | static auto P = HyprlandAPI::registerCallbackDynamic(PHANDLE, "openWindow", [&](void* self, SCallbackInfo& info, std::any data) { onNewWindow(self, data); }); 49 | 50 | // add deco to existing windows 51 | for (auto& w : g_pCompositor->m_windows) { 52 | if (w->isHidden() || !w->m_isMapped) 53 | continue; 54 | 55 | HyprlandAPI::addWindowDecoration(PHANDLE, w, makeUnique(w)); 56 | } 57 | 58 | HyprlandAPI::addNotification(PHANDLE, "[borders-plus-plus] Initialized successfully!", CHyprColor{0.2, 1.0, 0.2, 1.0}, 5000); 59 | 60 | return {"borders-plus-plus", "A plugin to add more borders to windows.", "Vaxry", "1.0"}; 61 | } 62 | 63 | APICALL EXPORT void PLUGIN_EXIT() { 64 | g_pHyprRenderer->m_renderPass.removeAllOfType("CBorderPPPassElement"); 65 | } 66 | -------------------------------------------------------------------------------- /hyprscrolling/main.cpp: -------------------------------------------------------------------------------- 1 | #define WLR_USE_UNSTABLE 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #define private public 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #undef private 15 | 16 | #include 17 | using namespace Hyprutils::String; 18 | 19 | #include "globals.hpp" 20 | #include "Scrolling.hpp" 21 | 22 | // Do NOT change this function. 23 | APICALL EXPORT std::string PLUGIN_API_VERSION() { 24 | return HYPRLAND_API_VERSION; 25 | } 26 | 27 | UP g_pScrollingLayout; 28 | 29 | // 30 | 31 | APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { 32 | PHANDLE = handle; 33 | 34 | const std::string HASH = __hyprland_api_get_hash(); 35 | const std::string CLIENT_HASH = __hyprland_api_get_client_hash(); 36 | 37 | if (HASH != CLIENT_HASH) { 38 | HyprlandAPI::addNotification(PHANDLE, "[hyprscrolling] Failure in initialization: Version mismatch (headers ver is not equal to running hyprland ver)", 39 | CHyprColor{1.0, 0.2, 0.2, 1.0}, 5000); 40 | throw std::runtime_error("[hs] Version mismatch"); 41 | } 42 | 43 | bool success = true; 44 | 45 | g_pScrollingLayout = makeUnique(); 46 | 47 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprscrolling:fullscreen_on_one_column", Hyprlang::INT{0}); 48 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprscrolling:column_width", Hyprlang::FLOAT{0.5F}); 49 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprscrolling:focus_fit_method", Hyprlang::INT{0}); 50 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprscrolling:follow_focus", Hyprlang::INT{1}); 51 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprscrolling:explicit_column_widths", Hyprlang::STRING{"0.333, 0.5, 0.667, 1.0"}); 52 | HyprlandAPI::addLayout(PHANDLE, "scrolling", g_pScrollingLayout.get()); 53 | 54 | if (success) 55 | HyprlandAPI::addNotification(PHANDLE, "[hyprscrolling] Initialized successfully!", CHyprColor{0.2, 1.0, 0.2, 1.0}, 5000); 56 | else { 57 | HyprlandAPI::addNotification(PHANDLE, "[hyprscrolling] Failure in initialization: failed to register dispatchers", CHyprColor{1.0, 0.2, 0.2, 1.0}, 5000); 58 | throw std::runtime_error("[hs] Dispatchers failed"); 59 | } 60 | 61 | return {"hyprscrolling", "A plugin to add a scrolling layout to hyprland", "Vaxry", "1.0"}; 62 | } 63 | 64 | APICALL EXPORT void PLUGIN_EXIT() { 65 | HyprlandAPI::removeLayout(PHANDLE, g_pScrollingLayout.get()); 66 | g_pScrollingLayout.reset(); 67 | } 68 | -------------------------------------------------------------------------------- /hyprbars/README.md: -------------------------------------------------------------------------------- 1 | # hyprbars 2 | 3 | Adds simple title bars to windows. 4 | 5 | ![image](https://github.com/user-attachments/assets/184a66b9-eb91-4f6f-8953-b265a2735939) 6 | 7 | ## Config 8 | 9 | All config options are in `plugin:hyprbars`: 10 | 11 | ``` 12 | plugin { 13 | hyprbars { 14 | # example config 15 | bar_height = 20 16 | 17 | # example buttons (R -> L) 18 | # hyprbars-button = color, size, on-click 19 | hyprbars-button = rgb(ff4040), 10, 󰖭, hyprctl dispatch killactive 20 | hyprbars-button = rgb(eeee11), 10, , hyprctl dispatch fullscreen 1 21 | 22 | # cmd to run on double click of the bar 23 | on_double_click = hyprctl dispatch fullscreen 1 24 | } 25 | } 26 | ``` 27 | 28 | | property | type | description | default | 29 | | --- | --- | --- | --- | 30 | `enabled` | bool | whether to enable the bars | 31 | `bar_color` | color | bar's background color 32 | `bar_height` | int | bar's height | `15` 33 | `bar_blur` | bool | whether to blur the bar. Also requires the global blur to be enabled. 34 | `col.text` | color | bar's title text color 35 | `bar_title_enabled` | bool | whether to render the title | `true` 36 | `bar_text_size` | int | bar's title text font size | `10` 37 | `bar_text_font` | str | bar's title text font | `Sans` 38 | `bar_text_align` | left, center | bar's title text alignment | `center` 39 | `bar_buttons_alignment` | right, left | bar's buttons alignment | `right` 40 | `bar_part_of_window` | bool | whether the bar is a part of the main window (if it is, stuff like shadows render around it) 41 | `bar_precedence_over_border` | bool | whether the bar should have a higher priority than the border (border will be around the bar) 42 | `bar_padding` | int | left / right edge padding | `7` 43 | `bar_button_padding` | int | padding between the buttons | `5` 44 | `icon_on_hover` | bool | whether the icons show on mouse hovering over the buttons | `false` 45 | `inactive_button_color` | col | buttons bg color when window isn't focused 46 | `on_double_click` | str | command to run on double click of the bar (not on a button) 47 | 48 | ## Buttons Config 49 | 50 | Use the `hyprbars-button` keyword. 51 | 52 | ```ini 53 | hyprbars-button = bgcolor, size, icon, on-click, fgcolor 54 | ``` 55 | 56 | Please note it _has_ to be inside `plugin { hyprbars { } }`. 57 | 58 | ## Window rules 59 | 60 | Hyprbars supports the following _dynamic_ [window rules](https://wiki.hypr.land/Configuring/Window-Rules/): 61 | 62 | `hyprbars:no_bar` -> disables the bar on matching windows. 63 | `hyprbars:bar_color` -> sets the bar background color on matching windows. 64 | `hyprbars:title_color` -> sets the bar title color on matching windows. 65 | 66 | Example: 67 | ```bash 68 | # Sets the bar color in red for all windows that have 'myClass' as a class 69 | windowrule = plugin:hyprbars:bar_color rgb(ff0000), class:^(myClass) 70 | ``` 71 | -------------------------------------------------------------------------------- /hyprscrolling/README.md: -------------------------------------------------------------------------------- 1 | # hyprscrolling 2 | 3 | Adds a scrolling layout to Hyprland. 4 | 5 | **This plugin is a work in progress!** 6 | 7 | ## Config 8 | 9 | Make sure to set the `general:layout` to `scrolling` to use this layout. 10 | 11 | All config values can be set in your Hyprland config file, for example: 12 | ``` 13 | plugin { 14 | hyprscrolling { 15 | column_width = 0.7 16 | fullscreen_on_one_column = false 17 | } 18 | } 19 | ``` 20 | 21 | | name | description | type | default | 22 | | -- | -- | -- | -- | 23 | | fullscreen_on_one_column | if there's only one column, should it be fullscreen | bool | false | 24 | | column_width | default column width as a fraction of the monitor width | float [0 - 1] | 0.5 | 25 | | explicit_column_widths | a comma-separated list of widths for columns to be used with `+conf` or `-conf` | string | `0.333, 0.5, 0.667, 1.0` | 26 | | focus_fit_method | when a column is focused, what method to use to bring it into view. 0 - center, 1 - fit | int | 0 | 27 | | follow_focus | when a window is focused, the layout will move to make it visible | bool | true | 28 | 29 | 30 | ## Layout messages 31 | 32 | | name | description | params | 33 | | --- | --- | --- | 34 | | move | move the layout horizontally, by either a relative logical px (`-200`, `+200`) or columns (`+col`, `-col`) | move data | 35 | | colresize | resize the current column, to either a value or by a relative value e.g. `0.5`, `+0.2`, `-0.2` or cycle the preconfigured ones with `+conf` or `-conf`. Can also be `all (number)` for resizing all columns to a specific width | relative float / relative conf | 36 | | movewindowto | same as the movewindow dispatcher but supports promotion to the right at the end | direction | 37 | | fit | executes a fit operation based on the argument. Available: `active`, `visible`, `all`, `toend`, `tobeg` | fit mode | 38 | | focus | moves the focus and centers the layout, while also wrapping instead of moving to neighbring monitors. | direction | 39 | | promote | moves a window to its own new column | none | 40 | | swapcol | Swaps the current column with its neighbor to the left (`l`) or right (`r`). The swap wraps around (e.g., swapping the first column left moves it to the end). | `l` or `r` | 41 | | movecoltoworkspace | Moves the entire current column to the specified workspace, preserving its internal layout. Works with existing, new, and special workspaces. e.g. like `1`, `2`, `-1`, `+2`, `special`, etc. | workspace identifier| 42 | | togglefit | Toggle the focus_fit_method (center, fit) | none | 43 | 44 | Example key bindings for your Hyprland config: 45 | ``` 46 | bind = $mainMod, period, layoutmsg, move +col 47 | bind = $mainMod, comma, layoutmsg, move -col 48 | bind = $mainMod SHIFT, period, layoutmsg, movewindowto r 49 | bind = $mainMod SHIFT, comma, layoutmsg, movewindowto l 50 | bind = $mainMod SHIFT, up, layoutmsg, movewindowto u 51 | bind = $mainMod SHIFT, down, layoutmsg, movewindowto d 52 | ``` 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hyprland-plugins 2 | 3 | This repo houses official plugins for Hyprland. 4 | 5 | # Plugin list 6 | - borders-plus-plus -> adds one or two additional borders to windows 7 | - csgo-vulkan-fix -> fixes custom resolutions on CS:GO with `-vulkan` 8 | - hyprbars -> adds title bars to windows 9 | - hyprexpo -> adds an expo-like workspace overview 10 | - hyprfocus -> flashfocus for hyprland 11 | - hyprscrolling -> adds a scrolling layout to hyprland 12 | - hyprtrails -> adds smooth trails behind moving windows 13 | - hyprwinwrap -> clone of xwinwrap, allows you to put any app as a wallpaper 14 | - xtra-dispatchers -> adds some new dispatchers 15 | 16 | # Install 17 | > [!IMPORTANT] 18 | > hyprland-plugins only officially supports installation via `hyprpm`. 19 | > `hyprpm` automatically detects your hyprland version & installs only 20 | > the corresponding "pinned" release of hyprland-plugins. 21 | > If you want the latest commits to hyprland-plugins, you need to use 22 | > `hyprland-git`. 23 | 24 | ## Install with `hyprpm` 25 | 26 | To install these plugins, from the command line run: 27 | ```bash 28 | hyprpm update 29 | ``` 30 | Then add this repository: 31 | ```bash 32 | hyprpm add https://github.com/hyprwm/hyprland-plugins 33 | ``` 34 | then enable the desired plugin with 35 | ```bash 36 | hyprpm enable 37 | ``` 38 | 39 | See the respective README's in the subdirectories for configuration options. 40 | 41 | See [the plugins wiki](https://wiki.hyprland.org/Plugins/Using-Plugins/#installing--using-plugins) and `hyprpm -h` for more details. 42 | 43 | ## Install on Nix 44 | 45 | To use these plugins, it's recommended that you are already using the 46 | [Hyprland flake](https://github.com/hyprwm/Hyprland). 47 | First, add this flake to your inputs: 48 | 49 | ```nix 50 | inputs = { 51 | # ... 52 | hyprland.url = "github:hyprwm/Hyprland"; 53 | hyprland-plugins = { 54 | url = "github:hyprwm/hyprland-plugins"; 55 | inputs.hyprland.follows = "hyprland"; 56 | }; 57 | 58 | # ... 59 | }; 60 | ``` 61 | 62 | The `inputs.hyprland.follows` guarantees the plugins will always be built using 63 | your locked Hyprland version, thus you will never get version mismatches that 64 | lead to errors. 65 | 66 | After that's done, you can use the plugins with the Home Manager module like 67 | this: 68 | 69 | ```nix 70 | {inputs, pkgs, ...}: { 71 | wayland.windowManager.hyprland = { 72 | enable = true; 73 | # ... 74 | plugins = [ 75 | inputs.hyprland-plugins.packages.${pkgs.system}.hyprbars 76 | # ... 77 | ]; 78 | }; 79 | } 80 | ``` 81 | 82 | If you don't use Home Manager: 83 | 84 | ```nix 85 | { lib, pkgs, inputs, ... }: 86 | with lib; let 87 | hyprPluginPkgs = inputs.hyprland-plugins.packages.${pkgs.system}; 88 | hypr-plugin-dir = pkgs.symlinkJoin { 89 | name = "hyrpland-plugins"; 90 | paths = with hyprPluginPkgs; [ 91 | hyprexpo 92 | #...plugins 93 | ]; 94 | }; 95 | in 96 | { 97 | environment.sessionVariables = { HYPR_PLUGIN_DIR = hypr-plugin-dir; }; 98 | } 99 | ``` 100 | 101 | And in `hyprland.conf` 102 | 103 | ```hyprlang 104 | # load all the plugins you installed 105 | exec-once = hyprctl plugin load "$HYPR_PLUGIN_DIR/lib/libhyprexpo.so" 106 | ``` 107 | 108 | # Contributing 109 | 110 | Feel free to open issues and MRs with fixes. 111 | 112 | If you want your plugin added here, contact vaxry beforehand. 113 | -------------------------------------------------------------------------------- /hyprbars/barDeco.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define WLR_USE_UNSTABLE 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "globals.hpp" 13 | 14 | #define private public 15 | #include 16 | #undef private 17 | 18 | class CHyprBar : public IHyprWindowDecoration { 19 | public: 20 | CHyprBar(PHLWINDOW); 21 | virtual ~CHyprBar(); 22 | 23 | virtual SDecorationPositioningInfo getPositioningInfo(); 24 | 25 | virtual void onPositioningReply(const SDecorationPositioningReply& reply); 26 | 27 | virtual void draw(PHLMONITOR, float const& a); 28 | 29 | virtual eDecorationType getDecorationType(); 30 | 31 | virtual void updateWindow(PHLWINDOW); 32 | 33 | virtual void damageEntire(); 34 | 35 | virtual eDecorationLayer getDecorationLayer(); 36 | 37 | virtual uint64_t getDecorationFlags(); 38 | 39 | bool m_bButtonsDirty = true; 40 | 41 | virtual std::string getDisplayName(); 42 | 43 | PHLWINDOW getOwner(); 44 | 45 | void updateRules(); 46 | 47 | WP m_self; 48 | 49 | private: 50 | SBoxExtents m_seExtents; 51 | 52 | PHLWINDOWREF m_pWindow; 53 | 54 | CBox m_bAssignedBox; 55 | 56 | SP m_pTextTex; 57 | SP m_pButtonsTex; 58 | 59 | bool m_bWindowSizeChanged = false; 60 | bool m_hidden = false; 61 | bool m_bTitleColorChanged = false; 62 | bool m_bButtonHovered = false; 63 | bool m_bLastEnabledState = false; 64 | bool m_bWindowHasFocus = false; 65 | std::optional m_bForcedBarColor; 66 | std::optional m_bForcedTitleColor; 67 | 68 | Time::steady_tp m_lastMouseDown = Time::steadyNow(); 69 | 70 | PHLANIMVAR m_cRealBarColor; 71 | 72 | Vector2D cursorRelativeToBar(); 73 | 74 | void renderPass(PHLMONITOR, float const& a); 75 | void renderBarTitle(const Vector2D& bufferSize, const float scale); 76 | void renderText(SP out, const std::string& text, const CHyprColor& color, const Vector2D& bufferSize, const float scale, const int fontSize); 77 | void renderBarButtons(const Vector2D& bufferSize, const float scale); 78 | void renderBarButtonsText(CBox* barBox, const float scale, const float a); 79 | void damageOnButtonHover(); 80 | 81 | bool inputIsValid(); 82 | void onMouseButton(SCallbackInfo& info, IPointer::SButtonEvent e); 83 | void onTouchDown(SCallbackInfo& info, ITouch::SDownEvent e); 84 | void onTouchUp(SCallbackInfo& info, ITouch::SUpEvent e); 85 | void onMouseMove(Vector2D coords); 86 | void onTouchMove(SCallbackInfo& info, ITouch::SMotionEvent e); 87 | 88 | void handleDownEvent(SCallbackInfo& info, std::optional touchEvent); 89 | void handleUpEvent(SCallbackInfo& info); 90 | void handleMovement(); 91 | bool doButtonPress(Hyprlang::INT* const* PBARPADDING, Hyprlang::INT* const* PBARBUTTONPADDING, Hyprlang::INT* const* PHEIGHT, Vector2D COORDS, bool BUTTONSRIGHT); 92 | 93 | CBox assignedBoxGlobal(); 94 | 95 | SP m_pMouseButtonCallback; 96 | SP m_pTouchDownCallback; 97 | SP m_pTouchUpCallback; 98 | 99 | SP m_pTouchMoveCallback; 100 | SP m_pMouseMoveCallback; 101 | 102 | std::string m_szLastTitle; 103 | 104 | bool m_bDraggingThis = false; 105 | bool m_bTouchEv = false; 106 | bool m_bDragPending = false; 107 | bool m_bCancelledDown = false; 108 | int m_touchId = 0; 109 | 110 | // store hover state for buttons as a bitfield 111 | unsigned int m_iButtonHoverState = 0; 112 | 113 | // for dynamic updates 114 | int m_iLastHeight = 0; 115 | 116 | size_t getVisibleButtonCount(Hyprlang::INT* const* PBARBUTTONPADDING, Hyprlang::INT* const* PBARPADDING, const Vector2D& bufferSize, const float scale); 117 | 118 | friend class CBarPassElement; 119 | }; 120 | -------------------------------------------------------------------------------- /hyprtrails/main.cpp: -------------------------------------------------------------------------------- 1 | #define WLR_USE_UNSTABLE 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "globals.hpp" 14 | #include "shaders.hpp" 15 | #include "trail.hpp" 16 | 17 | // Do NOT change this function. 18 | APICALL EXPORT std::string PLUGIN_API_VERSION() { 19 | return HYPRLAND_API_VERSION; 20 | } 21 | 22 | void onNewWindow(void* self, std::any data) { 23 | // data is guaranteed 24 | const auto PWINDOW = std::any_cast(data); 25 | 26 | HyprlandAPI::addWindowDecoration(PHANDLE, PWINDOW, makeUnique(PWINDOW)); 27 | } 28 | 29 | GLuint CompileShader(const GLuint& type, std::string src) { 30 | auto shader = glCreateShader(type); 31 | 32 | auto shaderSource = src.c_str(); 33 | 34 | glShaderSource(shader, 1, (const GLchar**)&shaderSource, nullptr); 35 | glCompileShader(shader); 36 | 37 | GLint ok; 38 | glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); 39 | 40 | if (ok == GL_FALSE) 41 | throw std::runtime_error("compileShader() failed!"); 42 | 43 | return shader; 44 | } 45 | 46 | GLuint CreateProgram(const std::string& vert, const std::string& frag) { 47 | auto vertCompiled = CompileShader(GL_VERTEX_SHADER, vert); 48 | 49 | if (!vertCompiled) 50 | throw std::runtime_error("Compiling vshader failed."); 51 | 52 | auto fragCompiled = CompileShader(GL_FRAGMENT_SHADER, frag); 53 | 54 | if (!fragCompiled) 55 | throw std::runtime_error("Compiling fshader failed."); 56 | 57 | auto prog = glCreateProgram(); 58 | glAttachShader(prog, vertCompiled); 59 | glAttachShader(prog, fragCompiled); 60 | glLinkProgram(prog); 61 | 62 | glDetachShader(prog, vertCompiled); 63 | glDetachShader(prog, fragCompiled); 64 | glDeleteShader(vertCompiled); 65 | glDeleteShader(fragCompiled); 66 | 67 | GLint ok; 68 | glGetProgramiv(prog, GL_LINK_STATUS, &ok); 69 | 70 | if (ok == GL_FALSE) 71 | throw std::runtime_error("createProgram() failed! GL_LINK_STATUS not OK!"); 72 | 73 | return prog; 74 | } 75 | 76 | int onTick(void* data) { 77 | EMIT_HOOK_EVENT("trailTick", nullptr); 78 | 79 | const int TIMEOUT = g_pHyprRenderer->m_mostHzMonitor ? 1000.0 / g_pHyprRenderer->m_mostHzMonitor->m_refreshRate : 16; 80 | wl_event_source_timer_update(g_pGlobalState->tick, TIMEOUT); 81 | 82 | return 0; 83 | } 84 | 85 | void initGlobal() { 86 | g_pHyprRenderer->makeEGLCurrent(); 87 | 88 | GLuint prog = CreateProgram(QUADTRAIL, FRAGTRAIL); 89 | g_pGlobalState->trailShader.program = prog; 90 | g_pGlobalState->trailShader.uniformLocations[SHADER_PROJ] = glGetUniformLocation(prog, "proj"); 91 | g_pGlobalState->trailShader.uniformLocations[SHADER_TEX] = glGetUniformLocation(prog, "tex"); 92 | g_pGlobalState->trailShader.uniformLocations[SHADER_COLOR] = glGetUniformLocation(prog, "color"); 93 | g_pGlobalState->trailShader.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(prog, "pos"); 94 | g_pGlobalState->trailShader.uniformLocations[SHADER_GRADIENT] = glGetUniformLocation(prog, "snapshots"); 95 | 96 | g_pGlobalState->tick = wl_event_loop_add_timer(g_pCompositor->m_wlEventLoop, &onTick, nullptr); 97 | wl_event_source_timer_update(g_pGlobalState->tick, 1); 98 | } 99 | 100 | APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { 101 | PHANDLE = handle; 102 | 103 | const std::string HASH = __hyprland_api_get_hash(); 104 | const std::string CLIENT_HASH = __hyprland_api_get_client_hash(); 105 | 106 | if (HASH != CLIENT_HASH) { 107 | HyprlandAPI::addNotification(PHANDLE, "[ht] Failure in initialization: Version mismatch (headers ver is not equal to running hyprland ver)", CHyprColor{1.0, 0.2, 0.2, 1.0}, 108 | 5000); 109 | throw std::runtime_error("[ht] Version mismatch"); 110 | } 111 | 112 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprtrails:bezier_step", Hyprlang::FLOAT{0.025}); 113 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprtrails:points_per_step", Hyprlang::INT{2}); 114 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprtrails:history_points", Hyprlang::INT{20}); 115 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprtrails:history_step", Hyprlang::INT{2}); 116 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprtrails:color", Hyprlang::INT{*configStringToInt("rgba(ffaa00ff)")}); 117 | 118 | static auto P = HyprlandAPI::registerCallbackDynamic(PHANDLE, "openWindow", [&](void* self, SCallbackInfo& info, std::any data) { onNewWindow(self, data); }); 119 | 120 | g_pGlobalState = makeUnique(); 121 | initGlobal(); 122 | 123 | // add deco to existing windows 124 | for (auto& w : g_pCompositor->m_windows) { 125 | if (w->isHidden() || !w->m_isMapped) 126 | continue; 127 | 128 | HyprlandAPI::addWindowDecoration(PHANDLE, w, makeUnique(w)); 129 | } 130 | 131 | HyprlandAPI::reloadConfig(); 132 | 133 | HyprlandAPI::addNotification(PHANDLE, "[hyprtrails] Initialized successfully!", CHyprColor{0.2, 1.0, 0.2, 1.0}, 5000); 134 | 135 | return {"hyprtrails", "A plugin to add trails behind moving windows", "Vaxry", "1.0"}; 136 | } 137 | 138 | APICALL EXPORT void PLUGIN_EXIT() { 139 | wl_event_source_remove(g_pGlobalState->tick); 140 | g_pHyprRenderer->m_renderPass.removeAllOfType("CTrailPassElement"); 141 | } 142 | -------------------------------------------------------------------------------- /hyprpm.toml: -------------------------------------------------------------------------------- 1 | [repository] 2 | name = "hyprland-plugins" 3 | authors = ["Vaxry"] 4 | commit_pins = [ 5 | ["3bb9c7c5cf4f2ee30bf821501499f2308d616f94", "efee74a7404495dbda70205824d6e9fc923ccdae"], 6 | ["d74607e414dcd16911089a6d4b6aeb661c880923", "efee74a7404495dbda70205824d6e9fc923ccdae"], 7 | ["03ebbe18ed8517ee22591eac82cd54322f42cb7d", "f7853b9cc6aab627b37b7be6575628e788ad6d1d"], 8 | ["84ab8d11e8951a6551d1e1bf87796a8589da6d47", "8af29f09c5b132d5087c2931fe9bd34f63923ba1"], 9 | ["1c460e98f870676b15871fe4e5bfeb1a32a3d6d8", "f99822818ec8276cfd6ec99ab60c4708c9884e3d"], 10 | ["c5e28ebcfe00a510922779b2c568cfa52a317445", "4c9d83b981ad4668b89b8a3dc24d6f3ea7ad08fd"], # 0.37.0 11 | ["19c90048d65a5660384d2fb865926a366696d6be", "4c9d83b981ad4668b89b8a3dc24d6f3ea7ad08fd"], # 0.37.1 12 | ["3875679755014997776e091ff8903acfb311dd2f", "e45066d0741a1a4b9298a4c5ec43a5e57b059a4e"], # 0.38.0 13 | ["360ede79d124ffdeebbe8401f1ac4bc0dbec2c91", "e45066d0741a1a4b9298a4c5ec43a5e57b059a4e"], # 0.38.1 14 | ["e93fbd7c4f991cb8ef03e433ccc4d43587923e15", "e9457e08ca3ff16dc5a815be62baf9e18b539197"], # 0.39.0 15 | ["fe7b748eb668136dd0558b7c8279bfcd7ab4d759", "e9457e08ca3ff16dc5a815be62baf9e18b539197"], # 0.39.1 16 | ["cba1ade848feac44b2eda677503900639581c3f4", "18daf37b7c4e6e51ca2bf8953ce4cff1c38ca725"], # 0.40.0 17 | ["ea2501d4556f84d3de86a4ae2f4b22a474555b9f", "8571aa9badf7db9c4911018a5611c038cc776256"], # 0.41.0 18 | ["9e781040d9067c2711ec2e9f5b47b76ef70762b3", "8571aa9badf7db9c4911018a5611c038cc776256"], # 0.41.1 19 | ["918d8340afd652b011b937d29d5eea0be08467f5", "135de7b88649dbe5fea8c997447bdc9d6f15ad86"], # 0.41.2 20 | ["9a09eac79b85c846e3a865a9078a3f8ff65a9259", "4fcb4038f23e5273af9a5684f3ee10b0652b3bab"], # 0.42.0 21 | ["0f594732b063a90d44df8c5d402d658f27471dfe", "b73d7b901d8cb1172dd25c7b7159f0242c625a77"], # 0.43.0 22 | ["0c7a7e2d569eeed9d6025f3eef4ea0690d90845d", "9215288eb2ded9d0c08d468ea90ba68f43162c67"], # 0.44.0 23 | ["4520b30d498daca8079365bdb909a8dea38e8d55", "9215288eb2ded9d0c08d468ea90ba68f43162c67"], # 0.44.1 24 | ["a425fbebe4cf4238e48a42f724ef2208959d66cf", "44859f877739c05d031fcab4a2991ec004fa9bc4"], # 0.45.0 25 | ["500d2a3580388afc8b620b0a3624147faa34f98b", "344a69db96fa8c6dc3b8f1f8f5a75f6eb441cbf2"], # 0.45.1 26 | ["12f9a0d0b93f691d4d9923716557154d74777b0a", "344a69db96fa8c6dc3b8f1f8f5a75f6eb441cbf2"], # 0.45.2 27 | ["788ae588979c2a1ff8a660f16e3c502ef5796755", "17ef806444fee729d00b3ba5cb8c623b7fbb699b"], # 0.46.0 28 | ["254fc2bc6000075f660b4b8ed818a6af544d1d64", "17ef806444fee729d00b3ba5cb8c623b7fbb699b"], # 0.46.1 29 | ["0bd541f2fd902dbfa04c3ea2ccf679395e316887", "17ef806444fee729d00b3ba5cb8c623b7fbb699b"], # 0.46.2 30 | ["04ac46c54357278fc68f0a95d26347ea0db99496", "3e51162d83b0cd9ee35acbd3b91e6d7ba856f5eb"], # 0.47.0 31 | ["75dff7205f6d2bd437abfb4196f700abee92581a", "3e51162d83b0cd9ee35acbd3b91e6d7ba856f5eb"], # 0.47.1 32 | ["5ee35f914f921e5696030698e74fb5566a804768", "1f332c09a2382cb23da0f69a6f504f8b33433831"], # 0.48.0 33 | ["29e2e59fdbab8ed2cc23a20e3c6043d5decb5cdc", "1f332c09a2382cb23da0f69a6f504f8b33433831"], # 0.48.1 34 | ["9958d297641b5c84dcff93f9039d80a5ad37ab00", "c491d2831448645f24a1597a17f564aa52691ac6"], # 0.49.0 35 | ["c4a4c341568944bd4fb9cd503558b2de602c0213", "bf310cda4a09b79725c2919688881959ebf3229e"], # 0.50.0 36 | ["4e242d086e20b32951fdc0ebcbfb4d41b5be8dcc", "bf310cda4a09b79725c2919688881959ebf3229e"], # 0.50.1 37 | ["46174f78b374b6cea669c48880877a8bdcf7802f", "a5a6f93d72d5fb37e78b98c756cfd8b340e71a19"], # 0.51.0 38 | ["71a1216abcc7031776630a6d88f105605c4dc1c9", "a5a6f93d72d5fb37e78b98c756cfd8b340e71a19"], # 0.51.1 39 | ["f56ec180d3a03a5aa978391249ff8f40f949fb73", "8c1212e96b81aa5f11fe21ca27defa2aad5b3cf3"], # 0.52.0 40 | ["967c3c7404d4fa00234e29c70df3e263386d2597", "8c1212e96b81aa5f11fe21ca27defa2aad5b3cf3"], # 0.52.1 41 | ["386376400119dd46a767c9f8c8791fd22c7b6e61", "8c1212e96b81aa5f11fe21ca27defa2aad5b3cf3"] # 0.52.2 42 | ] 43 | 44 | [borders-plus-plus] 45 | description = "A plugin to add more borders to windows." 46 | authors = ["Vaxry"] 47 | output = "borders-plus-plus/borders-plus-plus.so" 48 | build = [ 49 | "make -C borders-plus-plus all", 50 | ] 51 | 52 | [csgo-vulkan-fix] 53 | description = "A plugin to fix incorrect mouse offsets on csgo in Vulkan" 54 | authors = ["Vaxry"] 55 | output = "csgo-vulkan-fix/csgo-vulkan-fix.so" 56 | build = [ 57 | "make -C csgo-vulkan-fix all", 58 | ] 59 | 60 | [hyprbars] 61 | description = "A plugin to add title bars to windows" 62 | authors = ["Vaxry"] 63 | output = "hyprbars/hyprbars.so" 64 | build = [ 65 | "make -C hyprbars all", 66 | ] 67 | 68 | [hyprtrails] 69 | description = "A plugin to add trails behind moving windows" 70 | authors = ["Vaxry"] 71 | output = "hyprtrails/hyprtrails.so" 72 | build = [ 73 | "make -C hyprtrails all", 74 | ] 75 | 76 | [hyprwinwrap] 77 | description = "A clone of xwinwrap for Hyprland" 78 | authors = ["Vaxry"] 79 | output = "hyprwinwrap/hyprwinwrap.so" 80 | build = [ 81 | "make -C hyprwinwrap all", 82 | ] 83 | 84 | [hyprexpo] 85 | description = "A plugin to add expo (overlay) for workspaces" 86 | authors = ["Vaxry"] 87 | output = "hyprexpo/hyprexpo.so" 88 | build = [ 89 | "make -C hyprexpo all", 90 | ] 91 | since_hyprland = 4364 92 | 93 | [xtra-dispatchers] 94 | description = "A plugin to add some extra dispatchers" 95 | authors = ["Vaxry"] 96 | output = "xtra-dispatchers/xtra-dispatchers.so" 97 | build = [ 98 | "make -C xtra-dispatchers all", 99 | ] 100 | since_hyprland = 5573 101 | 102 | [hyprscrolling] 103 | description = "A plugin to add a scrolling layout" 104 | authors = ["Vaxry"] 105 | output = "hyprscrolling/hyprscrolling.so" 106 | build = [ 107 | "make -C hyprscrolling all", 108 | ] 109 | since_hyprland = 6066 110 | 111 | [hyprfocus] 112 | description = "Flashfocus for Hyprland" 113 | authors = ["Vaxry"] 114 | output = "hyprfocus/hyprfocus.so" 115 | build = [ 116 | "make -C hyprfocus all", 117 | ] 118 | since_hyprland = 6066 119 | -------------------------------------------------------------------------------- /hyprscrolling/Scrolling.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class CScrollingLayout; 9 | struct SColumnData; 10 | struct SWorkspaceData; 11 | 12 | struct SScrollingWindowData { 13 | SScrollingWindowData(PHLWINDOW w, SP col, float ws = 1.F) : window(w), column(col), windowSize(ws) { 14 | ; 15 | } 16 | 17 | PHLWINDOWREF window; 18 | WP column; 19 | float windowSize = 1.F; 20 | bool ignoreFullscreenChecks = false; 21 | PHLWORKSPACEREF overrideWorkspace; 22 | 23 | CBox layoutBox; 24 | }; 25 | 26 | struct SColumnData { 27 | SColumnData(SP ws) : workspace(ws) { 28 | ; 29 | } 30 | 31 | void add(PHLWINDOW w); 32 | void add(PHLWINDOW w, int after); 33 | void add(SP w); 34 | void add(SP w, int after); 35 | void remove(PHLWINDOW w); 36 | bool has(PHLWINDOW w); 37 | size_t idx(PHLWINDOW w); 38 | 39 | // index of lowest window that is above y. 40 | size_t idxForHeight(float y); 41 | 42 | void up(SP w); 43 | void down(SP w); 44 | 45 | SP next(SP w); 46 | SP prev(SP w); 47 | 48 | std::vector> windowDatas; 49 | float columnSize = 1.F; 50 | float columnWidth = 1.F; 51 | WP workspace; 52 | WP lastFocusedWindow; 53 | 54 | WP self; 55 | }; 56 | 57 | struct SWorkspaceData { 58 | SWorkspaceData(PHLWORKSPACE w, CScrollingLayout* l) : workspace(w), layout(l) { 59 | ; 60 | } 61 | 62 | PHLWORKSPACEREF workspace; 63 | std::vector> columns; 64 | float leftOffset = 0; 65 | 66 | SP add(); 67 | SP add(int after); 68 | int64_t idx(SP c); 69 | void remove(SP c); 70 | double maxWidth(); 71 | SP next(SP c); 72 | SP prev(SP c); 73 | SP atCenter(); 74 | 75 | bool visible(SP c); 76 | void centerCol(SP c); 77 | void fitCol(SP c); 78 | void centerOrFitCol(SP c); 79 | 80 | void recalculate(bool forceInstant = false); 81 | 82 | CScrollingLayout* layout = nullptr; 83 | WP self; 84 | }; 85 | 86 | class CScrollingLayout : public IHyprLayout { 87 | public: 88 | virtual void onWindowCreatedTiling(PHLWINDOW, eDirection direction = DIRECTION_DEFAULT); 89 | virtual void onWindowRemovedTiling(PHLWINDOW); 90 | virtual bool isWindowTiled(PHLWINDOW); 91 | virtual void recalculateMonitor(const MONITORID&); 92 | virtual void recalculateWindow(PHLWINDOW); 93 | virtual void onBeginDragWindow(); 94 | virtual void resizeActiveWindow(const Vector2D&, eRectCorner corner = CORNER_NONE, PHLWINDOW pWindow = nullptr); 95 | virtual void fullscreenRequestForWindow(PHLWINDOW pWindow, const eFullscreenMode CURRENT_EFFECTIVE_MODE, const eFullscreenMode EFFECTIVE_MODE); 96 | virtual std::any layoutMessage(SLayoutMessageHeader, std::string); 97 | virtual SWindowRenderLayoutHints requestRenderHints(PHLWINDOW); 98 | virtual void switchWindows(PHLWINDOW, PHLWINDOW); 99 | virtual void moveWindowTo(PHLWINDOW, const std::string& dir, bool silent); 100 | virtual void alterSplitRatio(PHLWINDOW, float, bool); 101 | virtual std::string getLayoutName(); 102 | virtual void replaceWindowDataWith(PHLWINDOW from, PHLWINDOW to); 103 | virtual Vector2D predictSizeForNewWindowTiled(); 104 | 105 | virtual void onEnable(); 106 | virtual void onDisable(); 107 | 108 | CBox usableAreaFor(PHLMONITOR m); 109 | 110 | private: 111 | std::vector> m_workspaceDatas; 112 | 113 | SP m_configCallback; 114 | SP m_focusCallback; 115 | 116 | struct { 117 | bool isMovingColumn = false; 118 | int targetWorkspaceID = -1; 119 | } m_columnMoveState; 120 | 121 | struct { 122 | std::vector configuredWidths; 123 | } m_config; 124 | 125 | SP findBestNeighbor(SP pCurrent, SP pTargetCol); 126 | SP dataFor(PHLWORKSPACE ws); 127 | SP dataFor(PHLWINDOW w); 128 | SP currentWorkspaceData(); 129 | 130 | void applyNodeDataToWindow(SP node, bool instant, bool hasWindowsRight, bool hasWindowsLeft); 131 | void focusWindowUpdate(PHLWINDOW pWindow); 132 | 133 | friend struct SWorkspaceData; 134 | }; 135 | -------------------------------------------------------------------------------- /xtra-dispatchers/main.cpp: -------------------------------------------------------------------------------- 1 | #define WLR_USE_UNSTABLE 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #define private public 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #undef private 16 | 17 | #include 18 | using namespace Hyprutils::String; 19 | 20 | #include "globals.hpp" 21 | 22 | // Do NOT change this function. 23 | APICALL EXPORT std::string PLUGIN_API_VERSION() { 24 | return HYPRLAND_API_VERSION; 25 | } 26 | 27 | // 28 | 29 | static SDispatchResult moveOrExec(std::string in) { 30 | CVarList vars(in, 0, ','); 31 | 32 | auto focusState = Desktop::focusState(); 33 | auto monitor = focusState->monitor(); 34 | 35 | if (!monitor || !monitor->m_activeWorkspace) 36 | return SDispatchResult{.success = false, .error = "No active workspace"}; 37 | 38 | const auto PWINDOW = g_pCompositor->getWindowByRegex(vars[0]); 39 | 40 | if (!PWINDOW) 41 | g_pKeybindManager->spawn(vars[1]); 42 | else { 43 | if (monitor->m_activeWorkspace != PWINDOW->m_workspace) 44 | g_pCompositor->moveWindowToWorkspaceSafe(PWINDOW, monitor->m_activeWorkspace); 45 | else 46 | g_pCompositor->warpCursorTo(PWINDOW->middle()); 47 | focusState->fullWindowFocus(PWINDOW); 48 | } 49 | 50 | return SDispatchResult{}; 51 | } 52 | 53 | static SDispatchResult throwUnfocused(std::string in) { 54 | const auto [id, name, isAutoID] = getWorkspaceIDNameFromString(in); 55 | 56 | if (id == WORKSPACE_INVALID) 57 | return SDispatchResult{.success = false, .error = "Failed to find workspace"}; 58 | 59 | auto focusState = Desktop::focusState(); 60 | auto window = focusState->window(); 61 | 62 | if (!window || !window->m_workspace) 63 | return SDispatchResult{.success = false, .error = "No valid last window"}; 64 | 65 | auto pWorkspace = g_pCompositor->getWorkspaceByID(id); 66 | if (!pWorkspace) 67 | pWorkspace = g_pCompositor->createNewWorkspace(id, window->m_workspace->monitorID(), name, false); 68 | 69 | for (const auto& w : g_pCompositor->m_windows) { 70 | if (w == window || w->m_workspace != window->m_workspace) 71 | continue; 72 | 73 | g_pCompositor->moveWindowToWorkspaceSafe(w, pWorkspace); 74 | } 75 | 76 | return SDispatchResult{}; 77 | } 78 | 79 | static SDispatchResult bringAllFrom(std::string in) { 80 | const auto [id, name, isAutoID] = getWorkspaceIDNameFromString(in); 81 | 82 | if (id == WORKSPACE_INVALID) 83 | return SDispatchResult{.success = false, .error = "Failed to find workspace"}; 84 | 85 | auto focusState = Desktop::focusState(); 86 | auto monitor = focusState->monitor(); 87 | auto window = focusState->window(); 88 | 89 | if (!monitor || !monitor->m_activeWorkspace) 90 | return SDispatchResult{.success = false, .error = "No active monitor"}; 91 | 92 | auto pWorkspace = g_pCompositor->getWorkspaceByID(id); 93 | if (!pWorkspace) 94 | return SDispatchResult{.success = false, .error = "Workspace isnt open"}; 95 | 96 | const auto PLASTWINDOW = window; 97 | 98 | for (const auto& w : g_pCompositor->m_windows) { 99 | if (w->m_workspace != pWorkspace) 100 | continue; 101 | 102 | g_pCompositor->moveWindowToWorkspaceSafe(w, monitor->m_activeWorkspace); 103 | } 104 | 105 | if (PLASTWINDOW) { 106 | Desktop::focusState()->fullWindowFocus(PLASTWINDOW); 107 | g_pCompositor->warpCursorTo(PLASTWINDOW->middle()); 108 | } 109 | 110 | return SDispatchResult{}; 111 | } 112 | 113 | static SDispatchResult closeUnfocused(std::string in) { 114 | auto focusState = Desktop::focusState(); 115 | auto monitor = focusState->monitor(); 116 | auto window = focusState->window(); 117 | 118 | if (!window) 119 | return SDispatchResult{.success = false, .error = "No focused monitor"}; 120 | 121 | for (const auto& w : g_pCompositor->m_windows) { 122 | if (w->m_workspace != monitor->m_activeWorkspace || w->m_monitor != monitor || !w->m_isMapped || w == window) 123 | continue; 124 | 125 | g_pCompositor->closeWindow(w); 126 | } 127 | 128 | return SDispatchResult{}; 129 | } 130 | 131 | APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { 132 | PHANDLE = handle; 133 | 134 | const std::string HASH = __hyprland_api_get_hash(); 135 | const std::string CLIENT_HASH = __hyprland_api_get_client_hash(); 136 | 137 | if (HASH != CLIENT_HASH) { 138 | HyprlandAPI::addNotification(PHANDLE, "[xtra-dispatchers] Failure in initialization: Version mismatch (headers ver is not equal to running hyprland ver)", 139 | CHyprColor{1.0, 0.2, 0.2, 1.0}, 5000); 140 | throw std::runtime_error("[xtd] Version mismatch"); 141 | } 142 | 143 | bool success = true; 144 | success = success && HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:xtd:moveorexec", ::moveOrExec); 145 | success = success && HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:xtd:throwunfocused", ::throwUnfocused); 146 | success = success && HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:xtd:bringallfrom", ::bringAllFrom); 147 | success = success && HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:xtd:closeunfocused", ::closeUnfocused); 148 | 149 | if (success) 150 | HyprlandAPI::addNotification(PHANDLE, "[xtra-dispatchers] Initialized successfully!", CHyprColor{0.2, 1.0, 0.2, 1.0}, 5000); 151 | else { 152 | HyprlandAPI::addNotification(PHANDLE, "[xtra-dispatchers] Failure in initialization: failed to register dispatchers", CHyprColor{1.0, 0.2, 0.2, 1.0}, 5000); 153 | throw std::runtime_error("[xtd] Dispatchers failed"); 154 | } 155 | 156 | return {"xtra-dispatchers", "A plugin to add some extra dispatchers to hyprland", "Vaxry", "1.0"}; 157 | } 158 | 159 | APICALL EXPORT void PLUGIN_EXIT() { 160 | ; 161 | } 162 | -------------------------------------------------------------------------------- /hyprfocus/main.cpp: -------------------------------------------------------------------------------- 1 | #define WLR_USE_UNSTABLE 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #define private public 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #undef private 19 | 20 | #include "globals.hpp" 21 | 22 | #include 23 | #include 24 | using namespace Hyprutils::String; 25 | using namespace Hyprutils::Animation; 26 | 27 | // Do NOT change this function. 28 | APICALL EXPORT std::string PLUGIN_API_VERSION() { 29 | return HYPRLAND_API_VERSION; 30 | } 31 | 32 | static void onFocusChange(PHLWINDOW window) { 33 | if (!window) 34 | return; 35 | 36 | static PHLWINDOWREF lastWindow; 37 | 38 | if (lastWindow == window) 39 | return; 40 | 41 | static const auto PONLY_ON_MONITOR_CHANGE = CConfigValue("plugin:hyprfocus:only_on_monitor_change"); 42 | if (*PONLY_ON_MONITOR_CHANGE && lastWindow && lastWindow->m_monitor == window->m_monitor) 43 | return; 44 | 45 | lastWindow = window; 46 | 47 | static const auto POPACITY = CConfigValue("plugin:hyprfocus:fade_opacity"); 48 | static const auto PBOUNCE = CConfigValue("plugin:hyprfocus:bounce_strength"); 49 | static const auto PSLIDE = CConfigValue("plugin:hyprfocus:slide_height"); 50 | static const auto PMODE = CConfigValue("plugin:hyprfocus:mode"); 51 | const auto PIN = g_pConfigManager->getAnimationPropertyConfig("hyprfocusIn"); 52 | const auto POUT = g_pConfigManager->getAnimationPropertyConfig("hyprfocusOut"); 53 | 54 | if (*PMODE == "flash") { 55 | const auto ORIGINAL = window->m_activeInactiveAlpha->goal(); 56 | window->m_activeInactiveAlpha->setConfig(PIN); 57 | *window->m_activeInactiveAlpha = std::clamp(*POPACITY, 0.F, 1.F); 58 | 59 | window->m_activeInactiveAlpha->setCallbackOnEnd([w = PHLWINDOWREF{window}, POUT, ORIGINAL](WP pav) { 60 | if (!w) 61 | return; 62 | w->m_activeInactiveAlpha->setConfig(POUT); 63 | *w->m_activeInactiveAlpha = ORIGINAL; 64 | 65 | w->m_activeInactiveAlpha->setCallbackOnEnd(nullptr); 66 | }); 67 | } else if (*PMODE == "bounce") { 68 | const auto ORIGINAL = CBox{window->m_realPosition->goal(), window->m_realSize->goal()}; 69 | 70 | window->m_realPosition->setConfig(PIN); 71 | window->m_realSize->setConfig(PIN); 72 | 73 | auto box = ORIGINAL.copy().scaleFromCenter(std::clamp(*PBOUNCE, 0.1F, 1.F)); 74 | 75 | *window->m_realPosition = box.pos(); 76 | *window->m_realSize = box.size(); 77 | 78 | window->m_realSize->setCallbackOnEnd([w = PHLWINDOWREF{window}, POUT, ORIGINAL](WP pav) { 79 | if (!w) 80 | return; 81 | w->m_realSize->setConfig(POUT); 82 | w->m_realPosition->setConfig(POUT); 83 | 84 | if (w->m_isFloating || w->isFullscreen()) { 85 | *w->m_realPosition = ORIGINAL.pos(); 86 | *w->m_realSize = ORIGINAL.size(); 87 | } else 88 | g_pLayoutManager->getCurrentLayout()->recalculateWindow(w.lock()); 89 | 90 | w->m_realSize->setCallbackOnEnd(nullptr); 91 | }); 92 | } else if (*PMODE == "slide") { 93 | const auto ORIGINAL = window->m_realPosition->goal(); 94 | 95 | window->m_realPosition->setConfig(PIN); 96 | 97 | *window->m_realPosition = ORIGINAL - Vector2D{0.F, std::clamp(*PSLIDE, 0.F, 150.F)}; 98 | 99 | window->m_realPosition->setCallbackOnEnd([w = PHLWINDOWREF{window}, POUT, ORIGINAL](WP pav) { 100 | if (!w) 101 | return; 102 | w->m_realPosition->setConfig(POUT); 103 | 104 | if (w->m_isFloating || w->isFullscreen()) 105 | *w->m_realPosition = ORIGINAL; 106 | else 107 | g_pLayoutManager->getCurrentLayout()->recalculateWindow(w.lock()); 108 | 109 | w->m_realPosition->setCallbackOnEnd(nullptr); 110 | }); 111 | } 112 | } 113 | 114 | APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { 115 | PHANDLE = handle; 116 | 117 | const std::string HASH = __hyprland_api_get_hash(); 118 | const std::string CLIENT_HASH = __hyprland_api_get_client_hash(); 119 | 120 | if (HASH != CLIENT_HASH) { 121 | HyprlandAPI::addNotification(PHANDLE, "[hyprwinwrap] Failure in initialization: Version mismatch (headers ver is not equal to running hyprland ver)", 122 | CHyprColor{1.0, 0.2, 0.2, 1.0}, 5000); 123 | throw std::runtime_error("[hww] Version mismatch"); 124 | } 125 | 126 | // clang-format off 127 | static auto P = HyprlandAPI::registerCallbackDynamic(PHANDLE, "activeWindow", [&](void* self, SCallbackInfo& info, std::any data) { onFocusChange(std::any_cast(data)); }); 128 | // clang-format on 129 | 130 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprfocus:mode", Hyprlang::STRING{"flash"}); 131 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprfocus:only_on_monitor_change", Hyprlang::INT{0}); 132 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprfocus:fade_opacity", Hyprlang::FLOAT{0.8F}); 133 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprfocus:slide_height", Hyprlang::FLOAT{20.F}); 134 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprfocus:bounce_strength", Hyprlang::FLOAT{0.95F}); 135 | 136 | // this will not be cleaned up after we are unloaded but it doesn't really matter, 137 | // as if we create this again it will just overwrite the old one. 138 | g_pConfigManager->m_animationTree.createNode("hyprfocusIn", "windowsIn"); 139 | g_pConfigManager->m_animationTree.createNode("hyprfocusOut", "windowsOut"); 140 | 141 | return {"hyprfocus", "Flashfocus for Hyprland", "Vaxry", "1.0"}; 142 | } 143 | 144 | APICALL EXPORT void PLUGIN_EXIT() { 145 | // reset the callbacks to avoid crashes 146 | for (const auto& w : g_pCompositor->m_windows) { 147 | if (!validMapped(w)) 148 | continue; 149 | 150 | w->m_realSize->setCallbackOnEnd(nullptr); 151 | w->m_realPosition->setCallbackOnEnd(nullptr); 152 | w->m_activeInactiveAlpha->setCallbackOnEnd(nullptr); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /borders-plus-plus/borderDeco.cpp: -------------------------------------------------------------------------------- 1 | #include "borderDeco.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | using namespace Hyprutils::Memory; 8 | #include "BorderppPassElement.hpp" 9 | #include "globals.hpp" 10 | 11 | CBordersPlusPlus::CBordersPlusPlus(PHLWINDOW pWindow) : IHyprWindowDecoration(pWindow), m_pWindow(pWindow) { 12 | m_lastWindowPos = pWindow->m_realPosition->value(); 13 | m_lastWindowSize = pWindow->m_realSize->value(); 14 | } 15 | 16 | CBordersPlusPlus::~CBordersPlusPlus() { 17 | damageEntire(); 18 | } 19 | 20 | SDecorationPositioningInfo CBordersPlusPlus::getPositioningInfo() { 21 | static auto* const PBORDERS = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:borders-plus-plus:add_borders")->getDataStaticPtr(); 22 | 23 | static std::vector PSIZES; 24 | for (size_t i = 0; i < 9; ++i) { 25 | PSIZES.push_back((Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:borders-plus-plus:border_size_" + std::to_string(i + 1))->getDataStaticPtr()); 26 | } 27 | 28 | SDecorationPositioningInfo info; 29 | info.policy = DECORATION_POSITION_STICKY; 30 | info.reserved = true; 31 | info.priority = 9990; 32 | info.edges = DECORATION_EDGE_BOTTOM | DECORATION_EDGE_LEFT | DECORATION_EDGE_RIGHT | DECORATION_EDGE_TOP; 33 | 34 | if (m_fLastThickness == 0) { 35 | double size = 0; 36 | 37 | for (size_t i = 0; i < **PBORDERS; ++i) { 38 | size += **PSIZES[i]; 39 | } 40 | 41 | info.desiredExtents = {{size, size}, {size, size}}; 42 | m_fLastThickness = size; 43 | } else 44 | info.desiredExtents = {{m_fLastThickness, m_fLastThickness}, {m_fLastThickness, m_fLastThickness}}; 45 | 46 | return info; 47 | } 48 | 49 | void CBordersPlusPlus::onPositioningReply(const SDecorationPositioningReply& reply) { 50 | m_bAssignedGeometry = reply.assignedGeometry; 51 | } 52 | 53 | uint64_t CBordersPlusPlus::getDecorationFlags() { 54 | return DECORATION_PART_OF_MAIN_WINDOW; 55 | } 56 | 57 | eDecorationLayer CBordersPlusPlus::getDecorationLayer() { 58 | return DECORATION_LAYER_OVER; 59 | } 60 | 61 | std::string CBordersPlusPlus::getDisplayName() { 62 | return "Borders++"; 63 | } 64 | 65 | void CBordersPlusPlus::draw(PHLMONITOR pMonitor, const float& a) { 66 | if (!validMapped(m_pWindow)) 67 | return; 68 | 69 | const auto PWINDOW = m_pWindow.lock(); 70 | 71 | if (!PWINDOW->m_ruleApplicator->decorate().valueOrDefault()) 72 | return; 73 | 74 | CBorderPPPassElement::SBorderPPData data; 75 | data.deco = this; 76 | 77 | g_pHyprRenderer->m_renderPass.add(makeUnique(data)); 78 | } 79 | 80 | void CBordersPlusPlus::drawPass(PHLMONITOR pMonitor, const float& a) { 81 | const auto PWINDOW = m_pWindow.lock(); 82 | 83 | static std::vector PCOLORS; 84 | static std::vector PSIZES; 85 | for (size_t i = 0; i < 9; ++i) { 86 | PCOLORS.push_back((Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:borders-plus-plus:col.border_" + std::to_string(i + 1))->getDataStaticPtr()); 87 | PSIZES.push_back((Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:borders-plus-plus:border_size_" + std::to_string(i + 1))->getDataStaticPtr()); 88 | } 89 | static auto* const PBORDERS = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:borders-plus-plus:add_borders")->getDataStaticPtr(); 90 | static auto* const PNATURALROUND = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:borders-plus-plus:natural_rounding")->getDataStaticPtr(); 91 | static auto* const PROUNDING = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "decoration:rounding")->getDataStaticPtr(); 92 | static auto* const PBORDERSIZE = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "general:border_size")->getDataStaticPtr(); 93 | 94 | if (**PBORDERS < 1) 95 | return; 96 | 97 | if (m_bAssignedGeometry.width < m_seExtents.topLeft.x + 1 || m_bAssignedGeometry.height < m_seExtents.topLeft.y + 1) 98 | return; 99 | 100 | const auto PWORKSPACE = PWINDOW->m_workspace; 101 | const auto WORKSPACEOFFSET = PWORKSPACE && !PWINDOW->m_pinned ? PWORKSPACE->m_renderOffset->value() : Vector2D(); 102 | 103 | auto rounding = PWINDOW->rounding() == 0 ? 0 : (PWINDOW->rounding() + **PBORDERSIZE) * pMonitor->m_scale; 104 | const auto ROUNDINGPOWER = PWINDOW->roundingPower(); 105 | const auto ORIGINALROUND = rounding == 0 ? 0 : (PWINDOW->rounding() + **PBORDERSIZE) * pMonitor->m_scale; 106 | 107 | CBox fullBox = m_bAssignedGeometry; 108 | fullBox.translate(g_pDecorationPositioner->getEdgeDefinedPoint(DECORATION_EDGE_BOTTOM | DECORATION_EDGE_LEFT | DECORATION_EDGE_RIGHT | DECORATION_EDGE_TOP, m_pWindow.lock())); 109 | 110 | fullBox.translate(PWINDOW->m_floatingOffset - pMonitor->m_position + WORKSPACEOFFSET); 111 | 112 | if (fullBox.width < 1 || fullBox.height < 1) 113 | return; 114 | 115 | double fullThickness = 0; 116 | 117 | for (size_t i = 0; i < **PBORDERS; ++i) { 118 | const int THISBORDERSIZE = **(PSIZES[i]) == -1 ? **PBORDERSIZE : (**PSIZES[i]); 119 | fullThickness += THISBORDERSIZE; 120 | } 121 | 122 | fullBox.expand(-fullThickness).scale(pMonitor->m_scale).round(); 123 | 124 | for (size_t i = 0; i < **PBORDERS; ++i) { 125 | const int PREVBORDERSIZESCALED = i == 0 ? 0 : (**PSIZES[i - 1] == -1 ? **PBORDERSIZE : **(PSIZES[i - 1])) * pMonitor->m_scale; 126 | const int THISBORDERSIZE = **(PSIZES[i]) == -1 ? **PBORDERSIZE : (**PSIZES[i]); 127 | 128 | if (i != 0) { 129 | rounding += rounding == 0 ? 0 : PREVBORDERSIZESCALED; 130 | fullBox.x -= PREVBORDERSIZESCALED; 131 | fullBox.y -= PREVBORDERSIZESCALED; 132 | fullBox.width += PREVBORDERSIZESCALED * 2; 133 | fullBox.height += PREVBORDERSIZESCALED * 2; 134 | } 135 | 136 | if (fullBox.width < 1 || fullBox.height < 1) 137 | break; 138 | 139 | g_pHyprOpenGL->scissor(nullptr); 140 | 141 | g_pHyprOpenGL->renderBorder(fullBox, CHyprColor{(uint64_t)**PCOLORS[i]}, 142 | {.round = **PNATURALROUND ? sc(ORIGINALROUND) : sc(rounding), 143 | .roundingPower = ROUNDINGPOWER, 144 | .borderSize = THISBORDERSIZE, 145 | .a = a, 146 | .outerRound = **PNATURALROUND ? sc(ORIGINALROUND) : -1}); 147 | } 148 | 149 | m_seExtents = {{fullThickness, fullThickness}, {fullThickness, fullThickness}}; 150 | 151 | m_bLastRelativeBox = CBox{0, 0, m_lastWindowSize.x, m_lastWindowSize.y}.addExtents(m_seExtents); 152 | 153 | if (fullThickness != m_fLastThickness) { 154 | m_fLastThickness = fullThickness; 155 | g_pDecorationPositioner->repositionDeco(this); 156 | } 157 | } 158 | 159 | eDecorationType CBordersPlusPlus::getDecorationType() { 160 | return DECORATION_CUSTOM; 161 | } 162 | 163 | void CBordersPlusPlus::updateWindow(PHLWINDOW pWindow) { 164 | m_lastWindowPos = pWindow->m_realPosition->value(); 165 | m_lastWindowSize = pWindow->m_realSize->value(); 166 | 167 | damageEntire(); 168 | } 169 | 170 | void CBordersPlusPlus::damageEntire() { 171 | CBox dm = m_bLastRelativeBox.copy().translate(m_lastWindowPos).expand(2); 172 | g_pHyprRenderer->damageBox(dm); 173 | } 174 | -------------------------------------------------------------------------------- /hyprbars/main.cpp: -------------------------------------------------------------------------------- 1 | #define WLR_USE_UNSTABLE 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #include "barDeco.hpp" 15 | #include "globals.hpp" 16 | 17 | // Do NOT change this function. 18 | APICALL EXPORT std::string PLUGIN_API_VERSION() { 19 | return HYPRLAND_API_VERSION; 20 | } 21 | 22 | static void onNewWindow(void* self, std::any data) { 23 | // data is guaranteed 24 | const auto PWINDOW = std::any_cast(data); 25 | 26 | if (!PWINDOW->m_X11DoesntWantBorders) { 27 | if (std::ranges::any_of(PWINDOW->m_windowDecorations, [](const auto& d) { return d->getDisplayName() == "Hyprbar"; })) 28 | return; 29 | 30 | auto bar = makeUnique(PWINDOW); 31 | g_pGlobalState->bars.emplace_back(bar); 32 | bar->m_self = bar; 33 | HyprlandAPI::addWindowDecoration(PHANDLE, PWINDOW, std::move(bar)); 34 | } 35 | } 36 | 37 | static void onCloseWindow(void* self, std::any data) { 38 | // data is guaranteed 39 | const auto PWINDOW = std::any_cast(data); 40 | 41 | const auto BARIT = std::find_if(g_pGlobalState->bars.begin(), g_pGlobalState->bars.end(), [PWINDOW](const auto& bar) { return bar->getOwner() == PWINDOW; }); 42 | 43 | if (BARIT == g_pGlobalState->bars.end()) 44 | return; 45 | 46 | // we could use the API but this is faster + it doesn't matter here that much. 47 | PWINDOW->removeWindowDeco(BARIT->get()); 48 | } 49 | 50 | static void onPreConfigReload() { 51 | g_pGlobalState->buttons.clear(); 52 | } 53 | 54 | static void onUpdateWindowRules(PHLWINDOW window) { 55 | const auto BARIT = std::find_if(g_pGlobalState->bars.begin(), g_pGlobalState->bars.end(), [window](const auto& bar) { return bar->getOwner() == window; }); 56 | 57 | if (BARIT == g_pGlobalState->bars.end()) 58 | return; 59 | 60 | (*BARIT)->updateRules(); 61 | window->updateWindowDecos(); 62 | } 63 | 64 | Hyprlang::CParseResult onNewButton(const char* K, const char* V) { 65 | std::string v = V; 66 | CVarList vars(v); 67 | 68 | Hyprlang::CParseResult result; 69 | 70 | // hyprbars-button = bgcolor, size, icon, action, fgcolor 71 | 72 | if (vars[0].empty() || vars[1].empty()) { 73 | result.setError("bgcolor and size cannot be empty"); 74 | return result; 75 | } 76 | 77 | float size = 10; 78 | try { 79 | size = std::stof(vars[1]); 80 | } catch (std::exception& e) { 81 | result.setError("failed to parse size"); 82 | return result; 83 | } 84 | 85 | bool userfg = false; 86 | auto fgcolor = configStringToInt("rgb(ffffff)"); 87 | auto bgcolor = configStringToInt(vars[0]); 88 | 89 | if (!bgcolor) { 90 | result.setError("invalid bgcolor"); 91 | return result; 92 | } 93 | 94 | if (vars.size() == 5) { 95 | userfg = true; 96 | fgcolor = configStringToInt(vars[4]); 97 | } 98 | 99 | if (!fgcolor) { 100 | result.setError("invalid fgcolor"); 101 | return result; 102 | } 103 | 104 | g_pGlobalState->buttons.push_back(SHyprButton{vars[3], userfg, *fgcolor, *bgcolor, size, vars[2]}); 105 | 106 | for (auto& b : g_pGlobalState->bars) { 107 | b->m_bButtonsDirty = true; 108 | } 109 | 110 | return result; 111 | } 112 | 113 | APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { 114 | PHANDLE = handle; 115 | 116 | const std::string HASH = __hyprland_api_get_hash(); 117 | const std::string CLIENT_HASH = __hyprland_api_get_client_hash(); 118 | 119 | if (HASH != CLIENT_HASH) { 120 | HyprlandAPI::addNotification(PHANDLE, "[hyprbars] Failure in initialization: Version mismatch (headers ver is not equal to running hyprland ver)", 121 | CHyprColor{1.0, 0.2, 0.2, 1.0}, 5000); 122 | throw std::runtime_error("[hb] Version mismatch"); 123 | } 124 | 125 | g_pGlobalState = makeUnique(); 126 | g_pGlobalState->nobarRuleIdx = Desktop::Rule::windowEffects()->registerEffect("hyprbars:no_bar"); 127 | g_pGlobalState->barColorRuleIdx = Desktop::Rule::windowEffects()->registerEffect("hyprbars:bar_color"); 128 | g_pGlobalState->titleColorRuleIdx = Desktop::Rule::windowEffects()->registerEffect("hyprbars:title_color"); 129 | 130 | static auto P = HyprlandAPI::registerCallbackDynamic(PHANDLE, "openWindow", [&](void* self, SCallbackInfo& info, std::any data) { onNewWindow(self, data); }); 131 | // static auto P2 = HyprlandAPI::registerCallbackDynamic(PHANDLE, "closeWindow", [&](void* self, SCallbackInfo& info, std::any data) { onCloseWindow(self, data); }); 132 | static auto P3 = HyprlandAPI::registerCallbackDynamic(PHANDLE, "windowUpdateRules", 133 | [&](void* self, SCallbackInfo& info, std::any data) { onUpdateWindowRules(std::any_cast(data)); }); 134 | 135 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:bar_color", Hyprlang::INT{*configStringToInt("rgba(33333388)")}); 136 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:bar_height", Hyprlang::INT{15}); 137 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:col.text", Hyprlang::INT{*configStringToInt("rgba(ffffffff)")}); 138 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:bar_text_size", Hyprlang::INT{10}); 139 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:bar_title_enabled", Hyprlang::INT{1}); 140 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:bar_blur", Hyprlang::INT{0}); 141 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:bar_text_font", Hyprlang::STRING{"Sans"}); 142 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:bar_text_align", Hyprlang::STRING{"center"}); 143 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:bar_part_of_window", Hyprlang::INT{1}); 144 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:bar_precedence_over_border", Hyprlang::INT{0}); 145 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:bar_buttons_alignment", Hyprlang::STRING{"right"}); 146 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:bar_padding", Hyprlang::INT{7}); 147 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:bar_button_padding", Hyprlang::INT{5}); 148 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:enabled", Hyprlang::INT{1}); 149 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:icon_on_hover", Hyprlang::INT{0}); 150 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:inactive_button_color", Hyprlang::INT{0}); // unset 151 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:on_double_click", Hyprlang::STRING{""}); 152 | 153 | HyprlandAPI::addConfigKeyword(PHANDLE, "plugin:hyprbars:hyprbars-button", onNewButton, Hyprlang::SHandlerOptions{}); 154 | static auto P4 = HyprlandAPI::registerCallbackDynamic(PHANDLE, "preConfigReload", [&](void* self, SCallbackInfo& info, std::any data) { onPreConfigReload(); }); 155 | 156 | // add deco to existing windows 157 | for (auto& w : g_pCompositor->m_windows) { 158 | if (w->isHidden() || !w->m_isMapped) 159 | continue; 160 | 161 | onNewWindow(nullptr /* unused */, std::any(w)); 162 | } 163 | 164 | HyprlandAPI::reloadConfig(); 165 | 166 | return {"hyprbars", "A plugin to add title bars to windows.", "Vaxry", "1.0"}; 167 | } 168 | 169 | APICALL EXPORT void PLUGIN_EXIT() { 170 | for (auto& m : g_pCompositor->m_monitors) 171 | m->m_scheduledRecalc = true; 172 | 173 | g_pHyprRenderer->m_renderPass.removeAllOfType("CBarPassElement"); 174 | 175 | Desktop::Rule::windowEffects()->unregisterEffect(g_pGlobalState->barColorRuleIdx); 176 | Desktop::Rule::windowEffects()->unregisterEffect(g_pGlobalState->titleColorRuleIdx); 177 | Desktop::Rule::windowEffects()->unregisterEffect(g_pGlobalState->nobarRuleIdx); 178 | } 179 | -------------------------------------------------------------------------------- /csgo-vulkan-fix/main.cpp: -------------------------------------------------------------------------------- 1 | #define WLR_USE_UNSTABLE 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "globals.hpp" 14 | 15 | #include 16 | using namespace Hyprutils::String; 17 | 18 | // Methods 19 | inline CFunctionHook* g_pMouseMotionHook = nullptr; 20 | inline CFunctionHook* g_pSurfaceSizeHook = nullptr; 21 | inline CFunctionHook* g_pWLSurfaceDamageHook = nullptr; 22 | typedef void (*origMotion)(CSeatManager*, uint32_t, const Vector2D&); 23 | typedef void (*origSurfaceSize)(CXWaylandSurface*, const CBox&); 24 | typedef CRegion (*origWLSurfaceDamage)(Desktop::View::CWLSurface*); 25 | 26 | // Do NOT change this function. 27 | APICALL EXPORT std::string PLUGIN_API_VERSION() { 28 | return HYPRLAND_API_VERSION; 29 | } 30 | 31 | struct SAppConfig { 32 | std::string szClass; 33 | Vector2D res; 34 | }; 35 | 36 | std::vector g_appConfigs; 37 | 38 | static const SAppConfig* getAppConfig(const std::string& appClass) { 39 | for (const auto& ac : g_appConfigs) { 40 | if (ac.szClass != appClass) 41 | continue; 42 | return ∾ 43 | } 44 | return nullptr; 45 | } 46 | 47 | void hkNotifyMotion(CSeatManager* thisptr, uint32_t time_msec, const Vector2D& local) { 48 | static auto* const PFIX = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:csgo-vulkan-fix:fix_mouse")->getDataStaticPtr(); 49 | 50 | Vector2D newCoords = local; 51 | auto focusState = Desktop::focusState(); 52 | auto window = focusState->window(); 53 | auto monitor = focusState->monitor(); 54 | 55 | const auto CONFIG = window && monitor ? getAppConfig(window->m_initialClass) : nullptr; 56 | 57 | if (**PFIX && CONFIG) { 58 | // fix the coords 59 | newCoords.x *= (CONFIG->res.x / monitor->m_size.x) / window->m_X11SurfaceScaledBy; 60 | newCoords.y *= (CONFIG->res.y / monitor->m_size.y) / window->m_X11SurfaceScaledBy; 61 | } 62 | 63 | (*(origMotion)g_pMouseMotionHook->m_original)(thisptr, time_msec, newCoords); 64 | } 65 | 66 | void hkSetWindowSize(CXWaylandSurface* surface, const CBox& box) { 67 | if (!surface) { 68 | (*(origSurfaceSize)g_pSurfaceSizeHook->m_original)(surface, box); 69 | return; 70 | } 71 | 72 | const auto SURF = surface->m_surface.lock(); 73 | const auto PWINDOW = g_pCompositor->getWindowFromSurface(SURF); 74 | 75 | CBox newBox = box; 76 | 77 | if (!PWINDOW) { 78 | (*(origSurfaceSize)g_pSurfaceSizeHook->m_original)(surface, newBox); 79 | return; 80 | } 81 | 82 | if (const auto CONFIG = getAppConfig(PWINDOW->m_initialClass); CONFIG) { 83 | newBox.w = CONFIG->res.x; 84 | newBox.h = CONFIG->res.y; 85 | 86 | Desktop::View::CWLSurface::fromResource(SURF)->m_fillIgnoreSmall = true; 87 | } 88 | 89 | (*(origSurfaceSize)g_pSurfaceSizeHook->m_original)(surface, newBox); 90 | } 91 | 92 | CRegion hkWLSurfaceDamage(Desktop::View::CWLSurface* thisptr) { 93 | const auto RG = (*(origWLSurfaceDamage)g_pWLSurfaceDamageHook->m_original)(thisptr); 94 | 95 | if (thisptr->exists() && Desktop::View::CWindow::fromView(thisptr->view())) { 96 | const auto WINDOW = Desktop::View::CWindow::fromView(thisptr->view()); 97 | const auto CONFIG = getAppConfig(WINDOW->m_initialClass); 98 | 99 | if (CONFIG) { 100 | const auto PMONITOR = WINDOW->m_monitor.lock(); 101 | if (PMONITOR) 102 | g_pHyprRenderer->damageMonitor(PMONITOR); 103 | else 104 | g_pHyprRenderer->damageWindow(WINDOW); 105 | } 106 | } 107 | 108 | return RG; 109 | } 110 | 111 | APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { 112 | PHANDLE = handle; 113 | 114 | const std::string HASH = __hyprland_api_get_hash(); 115 | const std::string CLIENT_HASH = __hyprland_api_get_client_hash(); 116 | 117 | if (HASH != CLIENT_HASH) { 118 | HyprlandAPI::addNotification(PHANDLE, "[csgo-vulkan-fix] Failure in initialization: Version mismatch (headers ver is not equal to running hyprland ver)", 119 | CHyprColor{1.0, 0.2, 0.2, 1.0}, 5000); 120 | throw std::runtime_error("[vkfix] Version mismatch"); 121 | } 122 | 123 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:csgo-vulkan-fix:res_w", Hyprlang::INT{1680}); 124 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:csgo-vulkan-fix:res_h", Hyprlang::INT{1050}); 125 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:csgo-vulkan-fix:fix_mouse", Hyprlang::INT{1}); 126 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:csgo-vulkan-fix:class", Hyprlang::STRING{"cs2"}); 127 | 128 | static auto P = HyprlandAPI::registerCallbackDynamic(PHANDLE, "preConfigReload", [&](void* self, SCallbackInfo& info, std::any data) { 129 | g_appConfigs.clear(); 130 | 131 | static auto* const RESX = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:csgo-vulkan-fix:res_w")->getDataStaticPtr(); 132 | static auto* const RESY = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:csgo-vulkan-fix:res_h")->getDataStaticPtr(); 133 | static auto* const PCLASS = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:csgo-vulkan-fix:class")->getDataStaticPtr(); 134 | 135 | g_appConfigs.emplace_back(SAppConfig{.szClass = *PCLASS, .res = Vector2D{(int)**RESX, (int)**RESY}}); 136 | }); 137 | 138 | HyprlandAPI::addConfigKeyword( 139 | PHANDLE, "vkfix-app", 140 | [](const char* l, const char* r) -> Hyprlang::CParseResult { 141 | const std::string str = r; 142 | CConstVarList data(str, 0, ',', true); 143 | 144 | Hyprlang::CParseResult result; 145 | 146 | if (data.size() != 3) { 147 | result.setError("vkfix-app requires 3 params"); 148 | return result; 149 | } 150 | 151 | try { 152 | SAppConfig config; 153 | config.szClass = data[0]; 154 | config.res = Vector2D{std::stoi(std::string{data[1]}), std::stoi(std::string{data[2]})}; 155 | g_appConfigs.emplace_back(std::move(config)); 156 | } catch (std::exception& e) { 157 | result.setError("failed to parse line"); 158 | return result; 159 | } 160 | 161 | return result; 162 | }, 163 | Hyprlang::SHandlerOptions{}); 164 | 165 | auto FNS = HyprlandAPI::findFunctionsByName(PHANDLE, "sendPointerMotion"); 166 | for (auto& fn : FNS) { 167 | if (!fn.demangled.contains("CSeatManager")) 168 | continue; 169 | 170 | g_pMouseMotionHook = HyprlandAPI::createFunctionHook(PHANDLE, fn.address, (void*)::hkNotifyMotion); 171 | break; 172 | } 173 | 174 | FNS = HyprlandAPI::findFunctionsByName(PHANDLE, "configure"); 175 | for (auto& fn : FNS) { 176 | if (!fn.demangled.contains("XWaylandSurface")) 177 | continue; 178 | 179 | g_pSurfaceSizeHook = HyprlandAPI::createFunctionHook(PHANDLE, fn.address, (void*)::hkSetWindowSize); 180 | break; 181 | } 182 | 183 | FNS = HyprlandAPI::findFunctionsByName(PHANDLE, "computeDamage"); 184 | for (auto& r : FNS) { 185 | if (!r.demangled.contains("CWLSurface")) 186 | continue; 187 | 188 | g_pWLSurfaceDamageHook = HyprlandAPI::createFunctionHook(PHANDLE, r.address, (void*)::hkWLSurfaceDamage); 189 | break; 190 | } 191 | 192 | bool success = g_pSurfaceSizeHook && g_pWLSurfaceDamageHook && g_pMouseMotionHook; 193 | if (!success) { 194 | HyprlandAPI::addNotification(PHANDLE, "[csgo-vulkan-fix] Failure in initialization: Failed to find required hook fns", CHyprColor{1.0, 0.2, 0.2, 1.0}, 5000); 195 | throw std::runtime_error("[vkfix] Hooks fn init failed"); 196 | } 197 | 198 | success = success && g_pWLSurfaceDamageHook->hook(); 199 | success = success && g_pMouseMotionHook->hook(); 200 | success = success && g_pSurfaceSizeHook->hook(); 201 | 202 | if (success) 203 | HyprlandAPI::addNotification(PHANDLE, "[csgo-vulkan-fix] Initialized successfully! (Anything version)", CHyprColor{0.2, 1.0, 0.2, 1.0}, 5000); 204 | else { 205 | HyprlandAPI::addNotification(PHANDLE, "[csgo-vulkan-fix] Failure in initialization (hook failed)!", CHyprColor{1.0, 0.2, 0.2, 1.0}, 5000); 206 | throw std::runtime_error("[csgo-vk-fix] Hooks failed"); 207 | } 208 | 209 | return {"csgo-vulkan-fix", "A plugin to force specific apps to a fake resolution", "Vaxry", "1.2"}; 210 | } 211 | 212 | APICALL EXPORT void PLUGIN_EXIT() { 213 | ; 214 | } 215 | -------------------------------------------------------------------------------- /hyprexpo/main.cpp: -------------------------------------------------------------------------------- 1 | #define WLR_USE_UNSTABLE 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | using namespace Hyprutils::String; 16 | 17 | #include "globals.hpp" 18 | #include "overview.hpp" 19 | #include "ExpoGesture.hpp" 20 | 21 | // Methods 22 | inline CFunctionHook* g_pRenderWorkspaceHook = nullptr; 23 | inline CFunctionHook* g_pAddDamageHookA = nullptr; 24 | inline CFunctionHook* g_pAddDamageHookB = nullptr; 25 | typedef void (*origRenderWorkspace)(void*, PHLMONITOR, PHLWORKSPACE, timespec*, const CBox&); 26 | typedef void (*origAddDamageA)(void*, const CBox&); 27 | typedef void (*origAddDamageB)(void*, const pixman_region32_t*); 28 | 29 | static bool g_unloading = false; 30 | 31 | // Do NOT change this function. 32 | APICALL EXPORT std::string PLUGIN_API_VERSION() { 33 | return HYPRLAND_API_VERSION; 34 | } 35 | 36 | static bool renderingOverview = false; 37 | 38 | // 39 | static void hkRenderWorkspace(void* thisptr, PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, timespec* now, const CBox& geometry) { 40 | if (!g_pOverview || renderingOverview || g_pOverview->blockOverviewRendering || g_pOverview->pMonitor != pMonitor) 41 | ((origRenderWorkspace)(g_pRenderWorkspaceHook->m_original))(thisptr, pMonitor, pWorkspace, now, geometry); 42 | else 43 | g_pOverview->render(); 44 | } 45 | 46 | static void hkAddDamageA(void* thisptr, const CBox& box) { 47 | const auto PMONITOR = (CMonitor*)thisptr; 48 | 49 | if (!g_pOverview || g_pOverview->pMonitor != PMONITOR->m_self || g_pOverview->blockDamageReporting) { 50 | ((origAddDamageA)g_pAddDamageHookA->m_original)(thisptr, box); 51 | return; 52 | } 53 | 54 | g_pOverview->onDamageReported(); 55 | } 56 | 57 | static void hkAddDamageB(void* thisptr, const pixman_region32_t* rg) { 58 | const auto PMONITOR = (CMonitor*)thisptr; 59 | 60 | if (!g_pOverview || g_pOverview->pMonitor != PMONITOR->m_self || g_pOverview->blockDamageReporting) { 61 | ((origAddDamageB)g_pAddDamageHookB->m_original)(thisptr, rg); 62 | return; 63 | } 64 | 65 | g_pOverview->onDamageReported(); 66 | } 67 | 68 | static SDispatchResult onExpoDispatcher(std::string arg) { 69 | 70 | if (g_pOverview && g_pOverview->m_isSwiping) 71 | return {.success = false, .error = "already swiping"}; 72 | 73 | if (arg == "select") { 74 | if (g_pOverview) { 75 | g_pOverview->selectHoveredWorkspace(); 76 | g_pOverview->close(); 77 | } 78 | return {}; 79 | } 80 | if (arg == "toggle") { 81 | if (g_pOverview) 82 | g_pOverview->close(); 83 | else { 84 | renderingOverview = true; 85 | g_pOverview = std::make_unique(Desktop::focusState()->monitor()->m_activeWorkspace); 86 | renderingOverview = false; 87 | } 88 | return {}; 89 | } 90 | 91 | if (arg == "off" || arg == "close" || arg == "disable") { 92 | if (g_pOverview) 93 | g_pOverview->close(); 94 | return {}; 95 | } 96 | 97 | if (g_pOverview) 98 | return {}; 99 | 100 | renderingOverview = true; 101 | g_pOverview = std::make_unique(Desktop::focusState()->monitor()->m_activeWorkspace); 102 | renderingOverview = false; 103 | return {}; 104 | } 105 | 106 | static void failNotif(const std::string& reason) { 107 | HyprlandAPI::addNotification(PHANDLE, "[hyprexpo] Failure in initialization: " + reason, CHyprColor{1.0, 0.2, 0.2, 1.0}, 5000); 108 | } 109 | 110 | static Hyprlang::CParseResult expoGestureKeyword(const char* LHS, const char* RHS) { 111 | Hyprlang::CParseResult result; 112 | 113 | if (g_unloading) 114 | return result; 115 | 116 | CConstVarList data(RHS); 117 | 118 | size_t fingerCount = 0; 119 | eTrackpadGestureDirection direction = TRACKPAD_GESTURE_DIR_NONE; 120 | 121 | try { 122 | fingerCount = std::stoul(std::string{data[0]}); 123 | } catch (...) { 124 | result.setError(std::format("Invalid value {} for finger count", data[0]).c_str()); 125 | return result; 126 | } 127 | 128 | if (fingerCount <= 1 || fingerCount >= 10) { 129 | result.setError(std::format("Invalid value {} for finger count", data[0]).c_str()); 130 | return result; 131 | } 132 | 133 | direction = g_pTrackpadGestures->dirForString(data[1]); 134 | 135 | if (direction == TRACKPAD_GESTURE_DIR_NONE) { 136 | result.setError(std::format("Invalid direction: {}", data[1]).c_str()); 137 | return result; 138 | } 139 | 140 | int startDataIdx = 2; 141 | uint32_t modMask = 0; 142 | float deltaScale = 1.F; 143 | 144 | while (true) { 145 | 146 | if (data[startDataIdx].starts_with("mod:")) { 147 | modMask = g_pKeybindManager->stringToModMask(std::string{data[startDataIdx].substr(4)}); 148 | startDataIdx++; 149 | continue; 150 | } else if (data[startDataIdx].starts_with("scale:")) { 151 | try { 152 | deltaScale = std::clamp(std::stof(std::string{data[startDataIdx].substr(6)}), 0.1F, 10.F); 153 | startDataIdx++; 154 | continue; 155 | } catch (...) { 156 | result.setError(std::format("Invalid delta scale: {}", std::string{data[startDataIdx].substr(6)}).c_str()); 157 | return result; 158 | } 159 | } 160 | 161 | break; 162 | } 163 | 164 | std::expected resultFromGesture; 165 | 166 | if (data[startDataIdx] == "expo") 167 | resultFromGesture = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale); 168 | else if (data[startDataIdx] == "unset") 169 | resultFromGesture = g_pTrackpadGestures->removeGesture(fingerCount, direction, modMask, deltaScale); 170 | else { 171 | result.setError(std::format("Invalid gesture: {}", data[startDataIdx]).c_str()); 172 | return result; 173 | } 174 | 175 | if (!resultFromGesture) { 176 | result.setError(resultFromGesture.error().c_str()); 177 | return result; 178 | } 179 | 180 | return result; 181 | } 182 | 183 | APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { 184 | PHANDLE = handle; 185 | 186 | const std::string HASH = __hyprland_api_get_hash(); 187 | const std::string CLIENT_HASH = __hyprland_api_get_client_hash(); 188 | 189 | if (HASH != CLIENT_HASH) { 190 | failNotif("Version mismatch (headers ver is not equal to running hyprland ver)"); 191 | throw std::runtime_error("[he] Version mismatch"); 192 | } 193 | 194 | auto FNS = HyprlandAPI::findFunctionsByName(PHANDLE, "renderWorkspace"); 195 | if (FNS.empty()) { 196 | failNotif("no fns for hook renderWorkspace"); 197 | throw std::runtime_error("[he] No fns for hook renderWorkspace"); 198 | } 199 | 200 | g_pRenderWorkspaceHook = HyprlandAPI::createFunctionHook(PHANDLE, FNS[0].address, (void*)hkRenderWorkspace); 201 | 202 | FNS = HyprlandAPI::findFunctionsByName(PHANDLE, "addDamageEPK15pixman_region32"); 203 | if (FNS.empty()) { 204 | failNotif("no fns for hook addDamageEPK15pixman_region32"); 205 | throw std::runtime_error("[he] No fns for hook addDamageEPK15pixman_region32"); 206 | } 207 | 208 | g_pAddDamageHookB = HyprlandAPI::createFunctionHook(PHANDLE, FNS[0].address, (void*)hkAddDamageB); 209 | 210 | FNS = HyprlandAPI::findFunctionsByName(PHANDLE, "_ZN8CMonitor9addDamageERKN9Hyprutils4Math4CBoxE"); 211 | if (FNS.empty()) { 212 | failNotif("no fns for hook _ZN8CMonitor9addDamageERKN9Hyprutils4Math4CBoxE"); 213 | throw std::runtime_error("[he] No fns for hook _ZN8CMonitor9addDamageERKN9Hyprutils4Math4CBoxE"); 214 | } 215 | 216 | g_pAddDamageHookA = HyprlandAPI::createFunctionHook(PHANDLE, FNS[0].address, (void*)hkAddDamageA); 217 | 218 | bool success = g_pRenderWorkspaceHook->hook(); 219 | success = success && g_pAddDamageHookA->hook(); 220 | success = success && g_pAddDamageHookB->hook(); 221 | 222 | if (!success) { 223 | failNotif("Failed initializing hooks"); 224 | throw std::runtime_error("[he] Failed initializing hooks"); 225 | } 226 | 227 | static auto P = HyprlandAPI::registerCallbackDynamic(PHANDLE, "preRender", [](void* self, SCallbackInfo& info, std::any param) { 228 | if (!g_pOverview) 229 | return; 230 | g_pOverview->onPreRender(); 231 | }); 232 | 233 | HyprlandAPI::addDispatcherV2(PHANDLE, "hyprexpo:expo", ::onExpoDispatcher); 234 | 235 | HyprlandAPI::addConfigKeyword(PHANDLE, "hyprexpo-gesture", ::expoGestureKeyword, {}); 236 | 237 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:columns", Hyprlang::INT{3}); 238 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:gap_size", Hyprlang::INT{5}); 239 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:bg_col", Hyprlang::INT{0xFF111111}); 240 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:workspace_method", Hyprlang::STRING{"center current"}); 241 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:skip_empty", Hyprlang::INT{0}); 242 | 243 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:gesture_distance", Hyprlang::INT{200}); 244 | 245 | HyprlandAPI::reloadConfig(); 246 | 247 | return {"hyprexpo", "A plugin for an overview", "Vaxry", "1.0"}; 248 | } 249 | 250 | APICALL EXPORT void PLUGIN_EXIT() { 251 | g_pHyprRenderer->m_renderPass.removeAllOfType("COverviewPassElement"); 252 | 253 | g_unloading = true; 254 | 255 | g_pConfigManager->reload(); // we need to reload now to clear all the gestures 256 | } 257 | -------------------------------------------------------------------------------- /hyprwinwrap/main.cpp: -------------------------------------------------------------------------------- 1 | #define WLR_USE_UNSTABLE 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #define private public 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #undef private 19 | 20 | #include "globals.hpp" 21 | 22 | // Do NOT change this function 23 | APICALL EXPORT std::string PLUGIN_API_VERSION() { 24 | return HYPRLAND_API_VERSION; 25 | } 26 | 27 | // hooks 28 | inline CFunctionHook* subsurfaceHook = nullptr; 29 | inline CFunctionHook* commitHook = nullptr; 30 | typedef void (*origCommitSubsurface)(Desktop::View::CSubsurface* thisptr); 31 | typedef void (*origCommit)(void* owner, void* data); 32 | 33 | std::vector bgWindows; 34 | 35 | void onNewWindow(PHLWINDOW pWindow) { 36 | static auto* const PCLASS = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprwinwrap:class")->getDataStaticPtr(); 37 | static auto* const PTITLE = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprwinwrap:title")->getDataStaticPtr(); 38 | 39 | static auto* const PSIZEX = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprwinwrap:size_x")->getDataStaticPtr(); 40 | static auto* const PSIZEY = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprwinwrap:size_y")->getDataStaticPtr(); 41 | static auto* const PPOSX = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprwinwrap:pos_x")->getDataStaticPtr(); 42 | static auto* const PPOSY = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprwinwrap:pos_y")->getDataStaticPtr(); 43 | 44 | const std::string classRule(*PCLASS); 45 | const std::string titleRule(*PTITLE); 46 | 47 | const bool classMatches = !classRule.empty() && pWindow->m_initialClass == classRule; 48 | const bool titleMatches = !titleRule.empty() && pWindow->m_title == titleRule; 49 | 50 | if (!classMatches && !titleMatches) 51 | return; 52 | 53 | const auto PMONITOR = pWindow->m_monitor.lock(); 54 | if (!PMONITOR) 55 | return; 56 | 57 | if (!pWindow->m_isFloating) 58 | g_pLayoutManager->getCurrentLayout()->changeWindowFloatingMode(pWindow); 59 | 60 | float sx = 100.f, sy = 100.f, px = 0.f, py = 0.f; 61 | 62 | try { 63 | sx = std::stof(*PSIZEX); 64 | } catch (...) {} 65 | try { 66 | sy = std::stof(*PSIZEY); 67 | } catch (...) {} 68 | try { 69 | px = std::stof(*PPOSX); 70 | } catch (...) {} 71 | try { 72 | py = std::stof(*PPOSY); 73 | } catch (...) {} 74 | 75 | sx = std::clamp(sx, 1.f, 100.f); 76 | sy = std::clamp(sy, 1.f, 100.f); 77 | px = std::clamp(px, 0.f, 100.f); 78 | py = std::clamp(py, 0.f, 100.f); 79 | 80 | if (px + sx > 100.f) { 81 | Debug::log(WARN, "[hyprwinwrap] size_x (%d) + pos_x (%d) > 100, adjusting size_x to %d", sx, px, 100.f - px); 82 | sx = 100.f - px; 83 | } 84 | if (py + sy > 100.f) { 85 | Debug::log(WARN, "[hyprwinwrap] size_y (%d) + pos_y (%d) > 100, adjusting size_y to %d", sy, py, 100.f - py); 86 | sy = 100.f - py; 87 | } 88 | 89 | const Vector2D monitorSize = PMONITOR->m_size; 90 | const Vector2D monitorPos = PMONITOR->m_position; 91 | 92 | const Vector2D newSize = {static_cast(monitorSize.x * (sx / 100.f)), static_cast(monitorSize.y * (sy / 100.f))}; 93 | 94 | const Vector2D newPos = {static_cast(monitorPos.x + (monitorSize.x * (px / 100.f))), static_cast(monitorPos.y + (monitorSize.y * (py / 100.f)))}; 95 | 96 | pWindow->m_realSize->setValueAndWarp(newSize); 97 | pWindow->m_realPosition->setValueAndWarp(newPos); 98 | pWindow->m_size = newSize; 99 | pWindow->m_position = newPos; 100 | pWindow->m_pinned = true; 101 | pWindow->sendWindowSize(true); 102 | 103 | bgWindows.push_back(pWindow); 104 | pWindow->m_hidden = true; 105 | 106 | g_pInputManager->refocus(); 107 | Debug::log(LOG, "[hyprwinwrap] new window moved to bg {}", pWindow); 108 | } 109 | 110 | void onCloseWindow(PHLWINDOW pWindow) { 111 | std::erase_if(bgWindows, [pWindow](const auto& ref) { return ref.expired() || ref.lock() == pWindow; }); 112 | 113 | Debug::log(LOG, "[hyprwinwrap] closed window {}", pWindow); 114 | } 115 | 116 | void onRenderStage(eRenderStage stage) { 117 | if (stage != RENDER_PRE_WINDOWS) 118 | return; 119 | 120 | for (auto& bg : bgWindows) { 121 | const auto bgw = bg.lock(); 122 | 123 | if (bgw->m_monitor != g_pHyprOpenGL->m_renderData.pMonitor) 124 | continue; 125 | 126 | // cant use setHidden cuz that sends suspended and shit too that would be laggy 127 | bgw->m_hidden = false; 128 | 129 | g_pHyprRenderer->renderWindow(bgw, g_pHyprOpenGL->m_renderData.pMonitor.lock(), Time::steadyNow(), false, RENDER_PASS_ALL, false, true); 130 | 131 | bgw->m_hidden = true; 132 | } 133 | } 134 | 135 | void onCommitSubsurface(Desktop::View::CSubsurface* thisptr) { 136 | const auto PWINDOW = Desktop::View::CWindow::fromView(thisptr->wlSurface()->view()); 137 | 138 | if (!PWINDOW || std::find_if(bgWindows.begin(), bgWindows.end(), [PWINDOW](const auto& ref) { return ref.lock() == PWINDOW; }) == bgWindows.end()) { 139 | ((origCommitSubsurface)subsurfaceHook->m_original)(thisptr); 140 | return; 141 | } 142 | 143 | // cant use setHidden cuz that sends suspended and shit too that would be laggy 144 | PWINDOW->m_hidden = false; 145 | 146 | ((origCommitSubsurface)subsurfaceHook->m_original)(thisptr); 147 | if (const auto MON = PWINDOW->m_monitor.lock(); MON) 148 | g_pHyprOpenGL->markBlurDirtyForMonitor(MON); 149 | 150 | PWINDOW->m_hidden = true; 151 | } 152 | 153 | void onCommit(void* owner, void* data) { 154 | const auto PWINDOW = ((Desktop::View::CWindow*)owner)->m_self.lock(); 155 | 156 | if (std::find_if(bgWindows.begin(), bgWindows.end(), [PWINDOW](const auto& ref) { return ref.lock() == PWINDOW; }) == bgWindows.end()) { 157 | ((origCommit)commitHook->m_original)(owner, data); 158 | return; 159 | } 160 | 161 | // cant use setHidden cuz that sends suspended and shit too that would be laggy 162 | PWINDOW->m_hidden = false; 163 | 164 | ((origCommit)commitHook->m_original)(owner, data); 165 | if (const auto MON = PWINDOW->m_monitor.lock(); MON) 166 | g_pHyprOpenGL->markBlurDirtyForMonitor(MON); 167 | 168 | PWINDOW->m_hidden = true; 169 | } 170 | 171 | void onConfigReloaded() { 172 | static auto* const PCLASS = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprwinwrap:class")->getDataStaticPtr(); 173 | const std::string classRule(*PCLASS); 174 | if (!classRule.empty()) { 175 | g_pConfigManager->parseKeyword("windowrulev2", std::string{"float, class:^("} + classRule + ")$"); 176 | g_pConfigManager->parseKeyword("windowrulev2", std::string{"size 100\% 100\%, class:^("} + classRule + ")$"); 177 | } 178 | 179 | static auto* const PTITLE = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprwinwrap:title")->getDataStaticPtr(); 180 | const std::string titleRule(*PTITLE); 181 | if (!titleRule.empty()) { 182 | g_pConfigManager->parseKeyword("windowrulev2", std::string{"float, title:^("} + titleRule + ")$"); 183 | g_pConfigManager->parseKeyword("windowrulev2", std::string{"size 100\% 100\%, title:^("} + titleRule + ")$"); 184 | } 185 | } 186 | 187 | APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { 188 | PHANDLE = handle; 189 | 190 | const std::string HASH = __hyprland_api_get_hash(); 191 | const std::string CLIENT_HASH = __hyprland_api_get_client_hash(); 192 | 193 | if (HASH != CLIENT_HASH) { 194 | HyprlandAPI::addNotification(PHANDLE, "[hyprwinwrap] Failure in initialization: Version mismatch (headers ver is not equal to running hyprland ver)", 195 | CHyprColor{1.0, 0.2, 0.2, 1.0}, 5000); 196 | throw std::runtime_error("[hww] Version mismatch"); 197 | } 198 | 199 | // clang-format off 200 | static auto P = HyprlandAPI::registerCallbackDynamic(PHANDLE, "openWindow", [&](void* self, SCallbackInfo& info, std::any data) { onNewWindow(std::any_cast(data)); }); 201 | static auto P2 = HyprlandAPI::registerCallbackDynamic(PHANDLE, "closeWindow", [&](void* self, SCallbackInfo& info, std::any data) { onCloseWindow(std::any_cast(data)); }); 202 | static auto P3 = HyprlandAPI::registerCallbackDynamic(PHANDLE, "render", [&](void* self, SCallbackInfo& info, std::any data) { onRenderStage(std::any_cast(data)); }); 203 | static auto P4 = HyprlandAPI::registerCallbackDynamic(PHANDLE, "configReloaded", [&](void* self, SCallbackInfo& info, std::any data) { onConfigReloaded(); }); 204 | // clang-format on 205 | 206 | auto fns = HyprlandAPI::findFunctionsByName(PHANDLE, "_ZN7Desktop4View11CSubsurface8onCommitEv"); 207 | if (fns.size() < 1) 208 | throw std::runtime_error("hyprwinwrap: onCommit not found"); 209 | subsurfaceHook = HyprlandAPI::createFunctionHook(PHANDLE, fns[0].address, (void*)&onCommitSubsurface); 210 | 211 | fns = HyprlandAPI::findFunctionsByName(PHANDLE, "_ZN7Desktop4View7CWindow12commitWindowEv"); 212 | if (fns.size() < 1) 213 | throw std::runtime_error("hyprwinwrap: listener_commitWindow not found"); 214 | commitHook = HyprlandAPI::createFunctionHook(PHANDLE, fns[0].address, (void*)&onCommit); 215 | 216 | bool hkResult = subsurfaceHook->hook(); 217 | hkResult = hkResult && commitHook->hook(); 218 | 219 | if (!hkResult) 220 | throw std::runtime_error("hyprwinwrap: hooks failed"); 221 | 222 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprwinwrap:class", Hyprlang::STRING{"kitty-bg"}); 223 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprwinwrap:title", Hyprlang::STRING{""}); 224 | 225 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprwinwrap:size_x", Hyprlang::STRING{"100"}); 226 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprwinwrap:size_y", Hyprlang::STRING{"100"}); 227 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprwinwrap:pos_x", Hyprlang::STRING{"0"}); 228 | HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprwinwrap:pos_y", Hyprlang::STRING{"0"}); 229 | 230 | HyprlandAPI::addNotification(PHANDLE, "[hyprwinwrap] Initialized successfully!", CHyprColor{0.2, 1.0, 0.2, 1.0}, 5000); 231 | 232 | return {"hyprwinwrap", "A clone of xwinwrap for Hyprland", "Vaxry", "1.0"}; 233 | } 234 | 235 | APICALL EXPORT void PLUGIN_EXIT() { 236 | ; 237 | } 238 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "aquamarine": { 4 | "inputs": { 5 | "hyprutils": [ 6 | "hyprland", 7 | "hyprutils" 8 | ], 9 | "hyprwayland-scanner": [ 10 | "hyprland", 11 | "hyprwayland-scanner" 12 | ], 13 | "nixpkgs": [ 14 | "hyprland", 15 | "nixpkgs" 16 | ], 17 | "systems": [ 18 | "hyprland", 19 | "systems" 20 | ] 21 | }, 22 | "locked": { 23 | "lastModified": 1760101617, 24 | "narHash": "sha256-8jf/3ZCi+B7zYpIyV04+3wm72BD7Z801IlOzsOACR7I=", 25 | "owner": "hyprwm", 26 | "repo": "aquamarine", 27 | "rev": "1826a9923881320306231b1c2090379ebf9fa4f8", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "hyprwm", 32 | "repo": "aquamarine", 33 | "type": "github" 34 | } 35 | }, 36 | "flake-compat": { 37 | "flake": false, 38 | "locked": { 39 | "lastModified": 1747046372, 40 | "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=", 41 | "owner": "edolstra", 42 | "repo": "flake-compat", 43 | "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", 44 | "type": "github" 45 | }, 46 | "original": { 47 | "owner": "edolstra", 48 | "repo": "flake-compat", 49 | "type": "github" 50 | } 51 | }, 52 | "gitignore": { 53 | "inputs": { 54 | "nixpkgs": [ 55 | "hyprland", 56 | "pre-commit-hooks", 57 | "nixpkgs" 58 | ] 59 | }, 60 | "locked": { 61 | "lastModified": 1709087332, 62 | "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", 63 | "owner": "hercules-ci", 64 | "repo": "gitignore.nix", 65 | "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", 66 | "type": "github" 67 | }, 68 | "original": { 69 | "owner": "hercules-ci", 70 | "repo": "gitignore.nix", 71 | "type": "github" 72 | } 73 | }, 74 | "hyprcursor": { 75 | "inputs": { 76 | "hyprlang": [ 77 | "hyprland", 78 | "hyprlang" 79 | ], 80 | "nixpkgs": [ 81 | "hyprland", 82 | "nixpkgs" 83 | ], 84 | "systems": [ 85 | "hyprland", 86 | "systems" 87 | ] 88 | }, 89 | "locked": { 90 | "lastModified": 1753964049, 91 | "narHash": "sha256-lIqabfBY7z/OANxHoPeIrDJrFyYy9jAM4GQLzZ2feCM=", 92 | "owner": "hyprwm", 93 | "repo": "hyprcursor", 94 | "rev": "44e91d467bdad8dcf8bbd2ac7cf49972540980a5", 95 | "type": "github" 96 | }, 97 | "original": { 98 | "owner": "hyprwm", 99 | "repo": "hyprcursor", 100 | "type": "github" 101 | } 102 | }, 103 | "hyprgraphics": { 104 | "inputs": { 105 | "hyprutils": [ 106 | "hyprland", 107 | "hyprutils" 108 | ], 109 | "nixpkgs": [ 110 | "hyprland", 111 | "nixpkgs" 112 | ], 113 | "systems": [ 114 | "hyprland", 115 | "systems" 116 | ] 117 | }, 118 | "locked": { 119 | "lastModified": 1760445448, 120 | "narHash": "sha256-fXGjL6dw31FPFRrmIemzGiNSlfvEJTJNsmadZi+qNhI=", 121 | "owner": "hyprwm", 122 | "repo": "hyprgraphics", 123 | "rev": "50fb9f069219f338a11cf0bcccb9e58357d67757", 124 | "type": "github" 125 | }, 126 | "original": { 127 | "owner": "hyprwm", 128 | "repo": "hyprgraphics", 129 | "type": "github" 130 | } 131 | }, 132 | "hyprland": { 133 | "inputs": { 134 | "aquamarine": "aquamarine", 135 | "hyprcursor": "hyprcursor", 136 | "hyprgraphics": "hyprgraphics", 137 | "hyprland-protocols": "hyprland-protocols", 138 | "hyprland-qtutils": "hyprland-qtutils", 139 | "hyprlang": "hyprlang", 140 | "hyprutils": "hyprutils", 141 | "hyprwayland-scanner": "hyprwayland-scanner", 142 | "nixpkgs": "nixpkgs", 143 | "pre-commit-hooks": "pre-commit-hooks", 144 | "systems": "systems", 145 | "xdph": "xdph" 146 | }, 147 | "locked": { 148 | "lastModified": 1761869718, 149 | "narHash": "sha256-jLfwwlPGpnGRAtVDyoGj9FgH2D9hWwyEu0yHkflG2EI=", 150 | "owner": "hyprwm", 151 | "repo": "Hyprland", 152 | "rev": "8e9add2afda58d233a75e4c5ce8503b24fa59ceb", 153 | "type": "github" 154 | }, 155 | "original": { 156 | "owner": "hyprwm", 157 | "repo": "Hyprland", 158 | "type": "github" 159 | } 160 | }, 161 | "hyprland-protocols": { 162 | "inputs": { 163 | "nixpkgs": [ 164 | "hyprland", 165 | "nixpkgs" 166 | ], 167 | "systems": [ 168 | "hyprland", 169 | "systems" 170 | ] 171 | }, 172 | "locked": { 173 | "lastModified": 1759610243, 174 | "narHash": "sha256-+KEVnKBe8wz+a6dTLq8YDcF3UrhQElwsYJaVaHXJtoI=", 175 | "owner": "hyprwm", 176 | "repo": "hyprland-protocols", 177 | "rev": "bd153e76f751f150a09328dbdeb5e4fab9d23622", 178 | "type": "github" 179 | }, 180 | "original": { 181 | "owner": "hyprwm", 182 | "repo": "hyprland-protocols", 183 | "type": "github" 184 | } 185 | }, 186 | "hyprland-qt-support": { 187 | "inputs": { 188 | "hyprlang": [ 189 | "hyprland", 190 | "hyprland-qtutils", 191 | "hyprlang" 192 | ], 193 | "nixpkgs": [ 194 | "hyprland", 195 | "hyprland-qtutils", 196 | "nixpkgs" 197 | ], 198 | "systems": [ 199 | "hyprland", 200 | "hyprland-qtutils", 201 | "systems" 202 | ] 203 | }, 204 | "locked": { 205 | "lastModified": 1749154592, 206 | "narHash": "sha256-DO7z5CeT/ddSGDEnK9mAXm1qlGL47L3VAHLlLXoCjhE=", 207 | "owner": "hyprwm", 208 | "repo": "hyprland-qt-support", 209 | "rev": "4c8053c3c888138a30c3a6c45c2e45f5484f2074", 210 | "type": "github" 211 | }, 212 | "original": { 213 | "owner": "hyprwm", 214 | "repo": "hyprland-qt-support", 215 | "type": "github" 216 | } 217 | }, 218 | "hyprland-qtutils": { 219 | "inputs": { 220 | "hyprland-qt-support": "hyprland-qt-support", 221 | "hyprlang": [ 222 | "hyprland", 223 | "hyprlang" 224 | ], 225 | "hyprutils": [ 226 | "hyprland", 227 | "hyprland-qtutils", 228 | "hyprlang", 229 | "hyprutils" 230 | ], 231 | "nixpkgs": [ 232 | "hyprland", 233 | "nixpkgs" 234 | ], 235 | "systems": [ 236 | "hyprland", 237 | "systems" 238 | ] 239 | }, 240 | "locked": { 241 | "lastModified": 1759080228, 242 | "narHash": "sha256-RgDoAja0T1hnF0pTc56xPfLfFOO8Utol2iITwYbUhTk=", 243 | "owner": "hyprwm", 244 | "repo": "hyprland-qtutils", 245 | "rev": "629b15c19fa4082e4ce6be09fdb89e8c3312aed7", 246 | "type": "github" 247 | }, 248 | "original": { 249 | "owner": "hyprwm", 250 | "repo": "hyprland-qtutils", 251 | "type": "github" 252 | } 253 | }, 254 | "hyprlang": { 255 | "inputs": { 256 | "hyprutils": [ 257 | "hyprland", 258 | "hyprutils" 259 | ], 260 | "nixpkgs": [ 261 | "hyprland", 262 | "nixpkgs" 263 | ], 264 | "systems": [ 265 | "hyprland", 266 | "systems" 267 | ] 268 | }, 269 | "locked": { 270 | "lastModified": 1758927902, 271 | "narHash": "sha256-LZgMds7M94+vuMql2bERQ6LiFFdhgsEFezE4Vn+Ys3A=", 272 | "owner": "hyprwm", 273 | "repo": "hyprlang", 274 | "rev": "4dafa28d4f79877d67a7d1a654cddccf8ebf15da", 275 | "type": "github" 276 | }, 277 | "original": { 278 | "owner": "hyprwm", 279 | "repo": "hyprlang", 280 | "type": "github" 281 | } 282 | }, 283 | "hyprutils": { 284 | "inputs": { 285 | "nixpkgs": [ 286 | "hyprland", 287 | "nixpkgs" 288 | ], 289 | "systems": [ 290 | "hyprland", 291 | "systems" 292 | ] 293 | }, 294 | "locked": { 295 | "lastModified": 1759619523, 296 | "narHash": "sha256-r1ed7AR2ZEb2U8gy321/Xcp1ho2tzn+gG1te/Wxsj1A=", 297 | "owner": "hyprwm", 298 | "repo": "hyprutils", 299 | "rev": "3df7bde01efb3a3e8e678d1155f2aa3f19e177ef", 300 | "type": "github" 301 | }, 302 | "original": { 303 | "owner": "hyprwm", 304 | "repo": "hyprutils", 305 | "type": "github" 306 | } 307 | }, 308 | "hyprwayland-scanner": { 309 | "inputs": { 310 | "nixpkgs": [ 311 | "hyprland", 312 | "nixpkgs" 313 | ], 314 | "systems": [ 315 | "hyprland", 316 | "systems" 317 | ] 318 | }, 319 | "locked": { 320 | "lastModified": 1755184602, 321 | "narHash": "sha256-RCBQN8xuADB0LEgaKbfRqwm6CdyopE1xIEhNc67FAbw=", 322 | "owner": "hyprwm", 323 | "repo": "hyprwayland-scanner", 324 | "rev": "b3b0f1f40ae09d4447c20608e5a4faf8bf3c492d", 325 | "type": "github" 326 | }, 327 | "original": { 328 | "owner": "hyprwm", 329 | "repo": "hyprwayland-scanner", 330 | "type": "github" 331 | } 332 | }, 333 | "nixpkgs": { 334 | "locked": { 335 | "lastModified": 1761114652, 336 | "narHash": "sha256-f/QCJM/YhrV/lavyCVz8iU3rlZun6d+dAiC3H+CDle4=", 337 | "owner": "NixOS", 338 | "repo": "nixpkgs", 339 | "rev": "01f116e4df6a15f4ccdffb1bcd41096869fb385c", 340 | "type": "github" 341 | }, 342 | "original": { 343 | "owner": "NixOS", 344 | "ref": "nixos-unstable", 345 | "repo": "nixpkgs", 346 | "type": "github" 347 | } 348 | }, 349 | "pre-commit-hooks": { 350 | "inputs": { 351 | "flake-compat": "flake-compat", 352 | "gitignore": "gitignore", 353 | "nixpkgs": [ 354 | "hyprland", 355 | "nixpkgs" 356 | ] 357 | }, 358 | "locked": { 359 | "lastModified": 1760663237, 360 | "narHash": "sha256-BflA6U4AM1bzuRMR8QqzPXqh8sWVCNDzOdsxXEguJIc=", 361 | "owner": "cachix", 362 | "repo": "git-hooks.nix", 363 | "rev": "ca5b894d3e3e151ffc1db040b6ce4dcc75d31c37", 364 | "type": "github" 365 | }, 366 | "original": { 367 | "owner": "cachix", 368 | "repo": "git-hooks.nix", 369 | "type": "github" 370 | } 371 | }, 372 | "root": { 373 | "inputs": { 374 | "hyprland": "hyprland", 375 | "nixpkgs": [ 376 | "hyprland", 377 | "nixpkgs" 378 | ], 379 | "systems": [ 380 | "hyprland", 381 | "systems" 382 | ] 383 | } 384 | }, 385 | "systems": { 386 | "locked": { 387 | "lastModified": 1689347949, 388 | "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=", 389 | "owner": "nix-systems", 390 | "repo": "default-linux", 391 | "rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68", 392 | "type": "github" 393 | }, 394 | "original": { 395 | "owner": "nix-systems", 396 | "repo": "default-linux", 397 | "type": "github" 398 | } 399 | }, 400 | "xdph": { 401 | "inputs": { 402 | "hyprland-protocols": [ 403 | "hyprland", 404 | "hyprland-protocols" 405 | ], 406 | "hyprlang": [ 407 | "hyprland", 408 | "hyprlang" 409 | ], 410 | "hyprutils": [ 411 | "hyprland", 412 | "hyprutils" 413 | ], 414 | "hyprwayland-scanner": [ 415 | "hyprland", 416 | "hyprwayland-scanner" 417 | ], 418 | "nixpkgs": [ 419 | "hyprland", 420 | "nixpkgs" 421 | ], 422 | "systems": [ 423 | "hyprland", 424 | "systems" 425 | ] 426 | }, 427 | "locked": { 428 | "lastModified": 1760713634, 429 | "narHash": "sha256-5HXelmz2x/uO26lvW7MudnadbAfoBnve4tRBiDVLtOM=", 430 | "owner": "hyprwm", 431 | "repo": "xdg-desktop-portal-hyprland", 432 | "rev": "753bbbdf6a052994da94062e5b753288cef28dfb", 433 | "type": "github" 434 | }, 435 | "original": { 436 | "owner": "hyprwm", 437 | "repo": "xdg-desktop-portal-hyprland", 438 | "type": "github" 439 | } 440 | } 441 | }, 442 | "root": "root", 443 | "version": 7 444 | } 445 | -------------------------------------------------------------------------------- /hyprtrails/trail.cpp: -------------------------------------------------------------------------------- 1 | #include "trail.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | using namespace Hyprutils::Memory; 8 | #include "globals.hpp" 9 | #include "TrailPassElement.hpp" 10 | 11 | void CTrail::onTick() { 12 | static auto* const PHISTORYSTEP = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprtrails:history_step")->getDataStaticPtr(); 13 | static auto* const PHISTORYPOINTS = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprtrails:history_points")->getDataStaticPtr(); 14 | 15 | m_iTimer++; 16 | 17 | const auto PWINDOW = m_pWindow.lock(); 18 | 19 | if (m_iTimer > **PHISTORYSTEP) { 20 | m_dLastGeoms.push_front({box{(float)PWINDOW->m_realPosition->value().x, (float)PWINDOW->m_realPosition->value().y, (float)PWINDOW->m_realSize->value().x, 21 | (float)PWINDOW->m_realSize->value().y}, 22 | std::chrono::system_clock::now()}); 23 | while (m_dLastGeoms.size() > **PHISTORYPOINTS) 24 | m_dLastGeoms.pop_back(); 25 | 26 | m_iTimer = 0; 27 | } 28 | 29 | if (m_bNeedsDamage) { 30 | g_pHyprRenderer->damageBox(m_bLastBox); 31 | m_bNeedsDamage = false; 32 | } 33 | } 34 | 35 | CTrail::CTrail(PHLWINDOW pWindow) : IHyprWindowDecoration(pWindow), m_pWindow(pWindow) { 36 | m_lastWindowPos = pWindow->m_realPosition->value(); 37 | m_lastWindowSize = pWindow->m_realSize->value(); 38 | 39 | pTickCb = HyprlandAPI::registerCallbackDynamic(PHANDLE, "trailTick", [this](void* self, SCallbackInfo& info, std::any data) { this->onTick(); }); 40 | } 41 | 42 | CTrail::~CTrail() { 43 | damageEntire(); 44 | HyprlandAPI::unregisterCallback(PHANDLE, pTickCb); 45 | } 46 | 47 | SDecorationPositioningInfo CTrail::getPositioningInfo() { 48 | return {DECORATION_POSITION_ABSOLUTE}; 49 | } 50 | 51 | void CTrail::onPositioningReply(const SDecorationPositioningReply& reply) { 52 | ; // ignored 53 | } 54 | 55 | void scaleBox2(box& box, float coeff) { 56 | float hwl = (box.w - (box.w * coeff)) / 2.0; 57 | float hhl = (box.h - (box.h * coeff)) / 2.0; 58 | 59 | box.w *= coeff; 60 | box.h *= coeff; 61 | box.x += hwl; 62 | box.y += hhl; 63 | } 64 | 65 | Vector2D vecForT(const Vector2D& a, const Vector2D& b, const float& t) { 66 | const Vector2D vec_PQ = b - a; 67 | return Vector2D{a + vec_PQ * t}; 68 | } 69 | 70 | Vector2D vecForBezierT(const float& t, const std::vector& verts) { 71 | std::vector pts; 72 | 73 | for (size_t vertexIndex = 0; vertexIndex < verts.size() - 1; vertexIndex++) { 74 | Vector2D p = verts[vertexIndex]; 75 | pts.push_back(vecForT(p, verts[vertexIndex + 1], t)); 76 | } 77 | 78 | if (pts.size() > 1) 79 | return vecForBezierT(t, pts); 80 | else 81 | return pts[0]; 82 | } 83 | 84 | void CTrail::draw(PHLMONITOR pMonitor, const float& a) { 85 | if (!validMapped(m_pWindow)) 86 | return; 87 | 88 | const auto PWINDOW = m_pWindow.lock(); 89 | 90 | if (!PWINDOW->m_ruleApplicator->decorate().valueOrDefault()) 91 | return; 92 | 93 | auto data = CTrailPassElement::STrailData{this, a}; 94 | g_pHyprRenderer->m_renderPass.add(makeUnique(data)); 95 | } 96 | 97 | void CTrail::renderPass(PHLMONITOR pMonitor, const float& a) { 98 | const auto PWINDOW = m_pWindow.lock(); 99 | 100 | static auto* const PBEZIERSTEP = (Hyprlang::FLOAT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprtrails:bezier_step")->getDataStaticPtr(); 101 | static auto* const PPOINTSPERSTEP = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprtrails:points_per_step")->getDataStaticPtr(); 102 | static auto* const PCOLOR = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprtrails:color")->getDataStaticPtr(); 103 | 104 | const CHyprColor COLOR = **PCOLOR; 105 | 106 | if (m_dLastGeoms.size() < 2) 107 | return; 108 | 109 | box thisbox = 110 | box{(float)PWINDOW->m_realPosition->value().x, (float)PWINDOW->m_realPosition->value().y, (float)PWINDOW->m_realSize->value().x, (float)PWINDOW->m_realSize->value().y}; 111 | CBox wlrbox = {thisbox.x - pMonitor->m_position.x, thisbox.y - pMonitor->m_position.y, thisbox.w, thisbox.h}; 112 | wlrbox.scale(pMonitor->m_scale).round(); 113 | 114 | g_pHyprOpenGL->scissor(nullptr); // allow the entire window and stencil to render 115 | glClearStencil(0); 116 | glClear(GL_STENCIL_BUFFER_BIT); 117 | 118 | g_pHyprOpenGL->setCapStatus(GL_STENCIL_TEST, true); 119 | 120 | glStencilFunc(GL_ALWAYS, 1, -1); 121 | glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); 122 | 123 | glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 124 | g_pHyprOpenGL->renderRect(wlrbox, CHyprColor(0, 0, 0, 0), {.round = sc(PWINDOW->rounding() * pMonitor->m_scale), .roundingPower = PWINDOW->roundingPower()}); 125 | glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 126 | 127 | glStencilFunc(GL_NOTEQUAL, 1, -1); 128 | glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); 129 | 130 | CBox monbox = {0, 0, g_pHyprOpenGL->m_renderData.pMonitor->m_transformedSize.x, g_pHyprOpenGL->m_renderData.pMonitor->m_transformedSize.y}; 131 | 132 | Mat3x3 matrix = g_pHyprOpenGL->m_renderData.monitorProjection.projectBox(monbox, wlTransformToHyprutils(invertTransform(WL_OUTPUT_TRANSFORM_NORMAL)), monbox.rot); 133 | Mat3x3 glMatrix = g_pHyprOpenGL->m_renderData.projection.copy().multiply(matrix); 134 | 135 | g_pHyprOpenGL->blend(true); 136 | 137 | glUseProgram(g_pGlobalState->trailShader.program); 138 | 139 | glMatrix.transpose(); 140 | g_pGlobalState->trailShader.setUniformMatrix3fv(SHADER_PROJ, 1, GL_FALSE, glMatrix.getMatrix()); 141 | 142 | std::vector points; 143 | std::vector bezierPts; 144 | std::vector pointsForBezier; 145 | std::vector agesForBezier; 146 | 147 | float originalCoeff = 50; 148 | 149 | auto msFrom = [](std::chrono::system_clock::time_point t) -> float { 150 | return std::chrono::duration_cast(std::chrono::system_clock::now() - t).count() / 1000.0; 151 | }; 152 | 153 | auto dist = [&](const point2& a, const point2& b) -> float { 154 | Vector2D diff = Vector2D{a.x - b.x, a.y - b.y} * pMonitor->m_size; 155 | return std::sqrt(diff.x * diff.x + diff.y * diff.y); 156 | }; 157 | 158 | float msMaxToLast = msFrom(m_dLastGeoms.back().second); 159 | 160 | float dists[2] = {0, 0}; 161 | 162 | Vector2D mainVec = {originalCoeff / pMonitor->m_size.x, originalCoeff / pMonitor->m_size.y}; 163 | Vector2D windowMiddle = PWINDOW->middle() - pMonitor->m_position; 164 | 165 | points.push_back( 166 | Vector2D{cos(0) * mainVec.x - sin(0) * mainVec.y + windowMiddle.x / pMonitor->m_size.x, sin(0) * mainVec.x + cos(0) * mainVec.y + windowMiddle.y / pMonitor->m_size.y}); 167 | points.push_back(Vector2D{cos(-M_PI_2) * mainVec.x - sin(-M_PI_2) * mainVec.y + windowMiddle.x / pMonitor->m_size.x, 168 | sin(-M_PI_2) * mainVec.x + cos(-M_PI_2) * mainVec.y + windowMiddle.y / pMonitor->m_size.y}); 169 | points.push_back(Vector2D{cos(M_PI_2) * mainVec.x - sin(M_PI_2) * mainVec.y + windowMiddle.x / pMonitor->m_size.x, 170 | sin(M_PI_2) * mainVec.x + cos(M_PI_2) * mainVec.y + windowMiddle.y / pMonitor->m_size.y}); 171 | points.push_back(Vector2D{cos(M_PI) * mainVec.x - sin(M_PI) * mainVec.y + windowMiddle.x / pMonitor->m_size.x, 172 | sin(M_PI) * mainVec.x + cos(M_PI) * mainVec.y + windowMiddle.y / pMonitor->m_size.y}); 173 | 174 | pointsForBezier.push_back(windowMiddle); 175 | agesForBezier.push_back(0); 176 | 177 | for (size_t i = 0; i < m_dLastGeoms.size(); i += 1) { 178 | box box = m_dLastGeoms[i].first; 179 | box.x -= pMonitor->m_position.x; 180 | box.y -= pMonitor->m_position.y; 181 | Vector2D middle = {box.x + box.w / 2.0, box.y + box.h / 2.0}; 182 | 183 | if (middle == pointsForBezier[pointsForBezier.size() - 1]) 184 | continue; 185 | 186 | pointsForBezier.push_back(middle); 187 | agesForBezier.push_back(msFrom(m_dLastGeoms[i].second)); 188 | } 189 | 190 | if (pointsForBezier.size() < 3) { 191 | glClearStencil(0); 192 | glClear(GL_STENCIL_BUFFER_BIT); 193 | g_pHyprOpenGL->setCapStatus(GL_STENCIL_TEST, false); 194 | 195 | glStencilMask(-1); 196 | glStencilFunc(GL_ALWAYS, 1, 0xFF); 197 | g_pHyprOpenGL->scissor(nullptr); 198 | return; 199 | } 200 | 201 | float maxAge = agesForBezier.back(); 202 | float tCoeff = **PBEZIERSTEP; 203 | int pointsPerBezier = **PPOINTSPERSTEP; 204 | bezierPts.push_back(vecForBezierT(0, pointsForBezier)); 205 | for (float t = tCoeff; t <= 1.0; t += tCoeff) { 206 | bezierPts.push_back(vecForBezierT(t, pointsForBezier)); 207 | 208 | const Vector2D& lastbezier = bezierPts.back(); 209 | const Vector2D& lastprevbezier = bezierPts[bezierPts.size() - 2]; 210 | 211 | for (size_t i = 1; i < pointsPerBezier + 1; ++i) { 212 | const float bezierPointStep = (1.0 / (pointsPerBezier + 2)); 213 | const Vector2D& middle = vecForT(lastprevbezier, lastbezier, bezierPointStep * (i + 1)); 214 | const Vector2D& lastmiddle = vecForT(lastprevbezier, lastbezier, bezierPointStep * i); 215 | 216 | Vector2D vecNormal = {middle.x - lastmiddle.x, middle.y - lastmiddle.y}; 217 | 218 | // normalize vec 219 | float invlen = 1.0 / std::sqrt(vecNormal.x * vecNormal.x + vecNormal.y * vecNormal.y); 220 | vecNormal.x *= invlen; 221 | vecNormal.y *= invlen; 222 | 223 | // make sure it points up 224 | // vecNormal.y = std::abs(vecNormal.y); 225 | 226 | // extend by coeff 227 | float ageCoeff = t * (agesForBezier.size() - 1); 228 | float ageFloor = std::floor(ageCoeff); 229 | float ageCeil = std::ceil(ageCoeff); 230 | float approxAge = 231 | agesForBezier[sc(ageFloor)] + (agesForBezier[sc(ageCeil)] - agesForBezier[sc(ageFloor)]) * (ageCoeff - ageFloor); 232 | float coeff = originalCoeff * (1.0 - (approxAge / maxAge)); 233 | Vector2D newVec = {vecNormal.x * coeff / pMonitor->m_size.x, vecNormal.y * coeff / pMonitor->m_size.y}; 234 | 235 | if ((newVec.x == 0 && newVec.y == 0) || std::isnan(newVec.x) || std::isnan(newVec.y)) 236 | continue; 237 | 238 | // rotate by 90 and -90 and add middle 239 | points.push_back(Vector2D{cos(M_PI_2) * newVec.x - sin(M_PI_2) * newVec.y + middle.x / pMonitor->m_size.x, 240 | sin(M_PI_2) * newVec.x + cos(M_PI_2) * newVec.y + middle.y / pMonitor->m_size.y}); 241 | points.push_back(Vector2D{cos(-M_PI_2) * newVec.x - sin(-M_PI_2) * newVec.y + middle.x / pMonitor->m_size.x, 242 | sin(-M_PI_2) * newVec.x + cos(-M_PI_2) * newVec.y + middle.y / pMonitor->m_size.y}); 243 | } 244 | } 245 | 246 | box thisboxopengl = 247 | box{sc((PWINDOW->m_realPosition->value().x - pMonitor->m_position.x) / pMonitor->m_size.x), 248 | sc((PWINDOW->m_realPosition->value().y - pMonitor->m_position.y) / pMonitor->m_size.y), 249 | sc((PWINDOW->m_realPosition->value().x + PWINDOW->m_realSize->value().x) / pMonitor->m_size.x), 250 | sc((PWINDOW->m_realPosition->value().y + PWINDOW->m_realSize->value().y) / pMonitor->m_size.y)}; 251 | glUniform4f(g_pGlobalState->trailShader.uniformLocations[SHADER_GRADIENT], thisboxopengl.x, thisboxopengl.y, thisboxopengl.w, thisboxopengl.h); 252 | glUniform4f(g_pGlobalState->trailShader.uniformLocations[SHADER_COLOR], COLOR.r, COLOR.g, COLOR.b, COLOR.a); 253 | 254 | CBox transformedBox = monbox; 255 | transformedBox.transform(wlTransformToHyprutils(invertTransform(g_pHyprOpenGL->m_renderData.pMonitor->m_transform)), g_pHyprOpenGL->m_renderData.pMonitor->m_transformedSize.x, 256 | g_pHyprOpenGL->m_renderData.pMonitor->m_transformedSize.y); 257 | 258 | glVertexAttribPointer(g_pGlobalState->trailShader.uniformLocations[SHADER_POS_ATTRIB], 2, GL_FLOAT, GL_FALSE, 0, (float*)points.data()); 259 | 260 | glEnableVertexAttribArray(g_pGlobalState->trailShader.uniformLocations[SHADER_POS_ATTRIB]); 261 | 262 | if (g_pHyprOpenGL->m_renderData.clipBox.width != 0 && g_pHyprOpenGL->m_renderData.clipBox.height != 0) { 263 | CRegion damageClip{g_pHyprOpenGL->m_renderData.clipBox.x, g_pHyprOpenGL->m_renderData.clipBox.y, g_pHyprOpenGL->m_renderData.clipBox.width, 264 | g_pHyprOpenGL->m_renderData.clipBox.height}; 265 | damageClip.intersect(g_pHyprOpenGL->m_renderData.damage); 266 | 267 | if (!damageClip.empty()) { 268 | for (auto& RECT : damageClip.getRects()) { 269 | g_pHyprOpenGL->scissor(&RECT); 270 | glDrawArrays(GL_TRIANGLE_STRIP, 0, points.size()); 271 | } 272 | } 273 | } else { 274 | for (auto& RECT : g_pHyprOpenGL->m_renderData.damage.getRects()) { 275 | g_pHyprOpenGL->scissor(&RECT); 276 | glDrawArrays(GL_TRIANGLE_STRIP, 0, points.size()); 277 | } 278 | } 279 | 280 | glDisableVertexAttribArray(g_pGlobalState->trailShader.uniformLocations[SHADER_POS_ATTRIB]); 281 | 282 | glClearStencil(0); 283 | glClear(GL_STENCIL_BUFFER_BIT); 284 | g_pHyprOpenGL->setCapStatus(GL_STENCIL_TEST, false); 285 | 286 | glStencilMask(-1); 287 | glStencilFunc(GL_ALWAYS, 1, 0xFF); 288 | g_pHyprOpenGL->scissor(nullptr); 289 | 290 | // calculate damage 291 | float minX = 9999999; 292 | float minY = 9999999; 293 | float maxX = -9999999; 294 | float maxY = -9999999; 295 | 296 | for (auto& p : points) { 297 | if (p.x < minX) 298 | minX = p.x; 299 | if (p.y < minY) 300 | minY = p.y; 301 | if (p.x > maxX) 302 | maxX = p.x; 303 | if (p.y > maxY) 304 | maxY = p.y; 305 | } 306 | 307 | // bring back to global coords 308 | minX *= pMonitor->m_size.x; 309 | minY *= pMonitor->m_size.y; 310 | maxX *= pMonitor->m_size.x; 311 | maxY *= pMonitor->m_size.y; 312 | 313 | m_bLastBox.x = minX + pMonitor->m_position.x; 314 | m_bLastBox.y = minY + pMonitor->m_position.y; 315 | m_bLastBox.width = maxX - minX; 316 | m_bLastBox.height = maxY - minY; 317 | 318 | m_bNeedsDamage = true; 319 | } 320 | 321 | eDecorationType CTrail::getDecorationType() { 322 | return DECORATION_CUSTOM; 323 | } 324 | 325 | void CTrail::updateWindow(PHLWINDOW pWindow) { 326 | const auto PWORKSPACE = pWindow->m_workspace; 327 | 328 | const auto WORKSPACEOFFSET = PWORKSPACE && !pWindow->m_pinned ? PWORKSPACE->m_renderOffset->value() : Vector2D(); 329 | 330 | m_lastWindowPos = pWindow->m_realPosition->value() + WORKSPACEOFFSET; 331 | m_lastWindowSize = pWindow->m_realSize->value(); 332 | 333 | damageEntire(); 334 | } 335 | 336 | void CTrail::damageEntire() { 337 | CBox dm = {sc(m_lastWindowPos.x - m_seExtents.topLeft.x), sc(m_lastWindowPos.y - m_seExtents.topLeft.y), 338 | sc(m_lastWindowSize.x + m_seExtents.topLeft.x + m_seExtents.bottomRight.x), sc(m_seExtents.topLeft.y)}; 339 | g_pHyprRenderer->damageBox(dm); 340 | } 341 | -------------------------------------------------------------------------------- /hyprexpo/overview.cpp: -------------------------------------------------------------------------------- 1 | #include "overview.hpp" 2 | #include 3 | #define private public 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #undef private 15 | #include "OverviewPassElement.hpp" 16 | 17 | static void damageMonitor(WP thisptr) { 18 | g_pOverview->damage(); 19 | } 20 | 21 | static void removeOverview(WP thisptr) { 22 | g_pOverview.reset(); 23 | } 24 | 25 | COverview::~COverview() { 26 | g_pHyprRenderer->makeEGLCurrent(); 27 | images.clear(); // otherwise we get a vram leak 28 | Cursor::overrideController->unsetOverride(Cursor::CURSOR_OVERRIDE_UNKNOWN); 29 | g_pHyprOpenGL->markBlurDirtyForMonitor(pMonitor.lock()); 30 | } 31 | 32 | COverview::COverview(PHLWORKSPACE startedOn_, bool swipe_) : startedOn(startedOn_), swipe(swipe_) { 33 | const auto PMONITOR = Desktop::focusState()->monitor(); 34 | pMonitor = PMONITOR; 35 | 36 | static auto* const* PCOLUMNS = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:columns")->getDataStaticPtr(); 37 | static auto* const* PGAPS = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:gap_size")->getDataStaticPtr(); 38 | static auto* const* PCOL = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:bg_col")->getDataStaticPtr(); 39 | static auto* const* PSKIP = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:skip_empty")->getDataStaticPtr(); 40 | static auto const* PMETHOD = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:workspace_method")->getDataStaticPtr(); 41 | 42 | SIDE_LENGTH = **PCOLUMNS; 43 | GAP_WIDTH = **PGAPS; 44 | BG_COLOR = **PCOL; 45 | 46 | // process the method 47 | bool methodCenter = true; 48 | int methodStartID = pMonitor->activeWorkspaceID(); 49 | CVarList method{*PMETHOD, 0, 's', true}; 50 | if (method.size() < 2) 51 | Debug::log(ERR, "[he] invalid workspace_method"); 52 | else { 53 | methodCenter = method[0] == "center"; 54 | methodStartID = getWorkspaceIDNameFromString(method[1]).id; 55 | if (methodStartID == WORKSPACE_INVALID) 56 | methodStartID = pMonitor->activeWorkspaceID(); 57 | } 58 | 59 | images.resize(SIDE_LENGTH * SIDE_LENGTH); 60 | 61 | // r includes empty workspaces; m skips over them 62 | std::string selector = **PSKIP ? "m" : "r"; 63 | 64 | if (methodCenter) { 65 | int currentID = methodStartID; 66 | int firstID = currentID; 67 | 68 | int backtracked = 0; 69 | 70 | // Initialize tiles to WORKSPACE_INVALID; cliking one of these results 71 | // in changing to "emptynm" (next empty workspace). Tiles with this id 72 | // will only remain if skip_empty is on. 73 | for (size_t i = 0; i < images.size(); i++) { 74 | images[i].workspaceID = WORKSPACE_INVALID; 75 | } 76 | 77 | // Scan through workspaces lower than methodStartID until we wrap; count how many 78 | for (size_t i = 1; i < images.size() / 2; ++i) { 79 | currentID = getWorkspaceIDNameFromString(selector + "-" + std::to_string(i)).id; 80 | if (currentID >= firstID) 81 | break; 82 | 83 | backtracked++; 84 | firstID = currentID; 85 | } 86 | 87 | // Scan through workspaces higher than methodStartID. If using "m" 88 | // (skip_empty), stop when we wrap, leaving the rest of the workspace 89 | // ID's set to WORKSPACE_INVALID 90 | for (size_t i = 0; i < (size_t)(SIDE_LENGTH * SIDE_LENGTH); ++i) { 91 | auto& image = images[i]; 92 | if ((int64_t)i - backtracked < 0) { 93 | currentID = getWorkspaceIDNameFromString(selector + std::to_string((int64_t)i - backtracked)).id; 94 | } else { 95 | currentID = getWorkspaceIDNameFromString(selector + "+" + std::to_string((int64_t)i - backtracked)).id; 96 | if (i > 0 && currentID == firstID) 97 | break; 98 | } 99 | image.workspaceID = currentID; 100 | } 101 | 102 | } else { 103 | int currentID = methodStartID; 104 | images[0].workspaceID = currentID; 105 | 106 | auto PWORKSPACESTART = g_pCompositor->getWorkspaceByID(currentID); 107 | if (!PWORKSPACESTART) 108 | PWORKSPACESTART = CWorkspace::create(currentID, pMonitor.lock(), std::to_string(currentID)); 109 | 110 | pMonitor->m_activeWorkspace = PWORKSPACESTART; 111 | 112 | // Scan through workspaces higher than methodStartID. If using "m" 113 | // (skip_empty), stop when we wrap, leaving the rest of the workspace 114 | // ID's set to WORKSPACE_INVALID 115 | for (size_t i = 1; i < (size_t)(SIDE_LENGTH * SIDE_LENGTH); ++i) { 116 | auto& image = images[i]; 117 | currentID = getWorkspaceIDNameFromString(selector + "+" + std::to_string(i)).id; 118 | if (currentID <= methodStartID) 119 | break; 120 | image.workspaceID = currentID; 121 | } 122 | 123 | pMonitor->m_activeWorkspace = startedOn; 124 | } 125 | 126 | g_pHyprRenderer->makeEGLCurrent(); 127 | 128 | Vector2D tileSize = pMonitor->m_size / SIDE_LENGTH; 129 | Vector2D tileRenderSize = (pMonitor->m_size - Vector2D{GAP_WIDTH * pMonitor->m_scale, GAP_WIDTH * pMonitor->m_scale} * (SIDE_LENGTH - 1)) / SIDE_LENGTH; 130 | CBox monbox{0, 0, tileSize.x * 2, tileSize.y * 2}; 131 | 132 | if (!ENABLE_LOWRES) 133 | monbox = {{0, 0}, pMonitor->m_pixelSize}; 134 | 135 | int currentid = 0; 136 | 137 | PHLWORKSPACE openSpecial = PMONITOR->m_activeSpecialWorkspace; 138 | if (openSpecial) 139 | PMONITOR->m_activeSpecialWorkspace.reset(); 140 | 141 | g_pHyprRenderer->m_bBlockSurfaceFeedback = true; 142 | 143 | startedOn->m_visible = false; 144 | 145 | for (size_t i = 0; i < (size_t)(SIDE_LENGTH * SIDE_LENGTH); ++i) { 146 | COverview::SWorkspaceImage& image = images[i]; 147 | image.fb.alloc(monbox.w, monbox.h, PMONITOR->m_output->state->state().drmFormat); 148 | 149 | CRegion fakeDamage{0, 0, INT16_MAX, INT16_MAX}; 150 | g_pHyprRenderer->beginRender(PMONITOR, fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, &image.fb); 151 | 152 | g_pHyprOpenGL->clear(CHyprColor{0, 0, 0, 1.0}); 153 | 154 | const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(image.workspaceID); 155 | 156 | if (PWORKSPACE == startedOn) 157 | currentid = i; 158 | 159 | if (PWORKSPACE) { 160 | image.pWorkspace = PWORKSPACE; 161 | PMONITOR->m_activeWorkspace = PWORKSPACE; 162 | g_pDesktopAnimationManager->startAnimation(PWORKSPACE, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true); 163 | PWORKSPACE->m_visible = true; 164 | 165 | if (PWORKSPACE == startedOn) 166 | PMONITOR->m_activeSpecialWorkspace = openSpecial; 167 | 168 | g_pHyprRenderer->renderWorkspace(PMONITOR, PWORKSPACE, Time::steadyNow(), monbox); 169 | 170 | PWORKSPACE->m_visible = false; 171 | g_pDesktopAnimationManager->startAnimation(PWORKSPACE, CDesktopAnimationManager::ANIMATION_TYPE_OUT, false, true); 172 | 173 | if (PWORKSPACE == startedOn) 174 | PMONITOR->m_activeSpecialWorkspace.reset(); 175 | } else 176 | g_pHyprRenderer->renderWorkspace(PMONITOR, PWORKSPACE, Time::steadyNow(), monbox); 177 | 178 | image.box = {(i % SIDE_LENGTH) * tileRenderSize.x + (i % SIDE_LENGTH) * GAP_WIDTH, (i / SIDE_LENGTH) * tileRenderSize.y + (i / SIDE_LENGTH) * GAP_WIDTH, tileRenderSize.x, 179 | tileRenderSize.y}; 180 | 181 | g_pHyprOpenGL->m_renderData.blockScreenShader = true; 182 | g_pHyprRenderer->endRender(); 183 | } 184 | 185 | g_pHyprRenderer->m_bBlockSurfaceFeedback = false; 186 | 187 | PMONITOR->m_activeSpecialWorkspace = openSpecial; 188 | PMONITOR->m_activeWorkspace = startedOn; 189 | startedOn->m_visible = true; 190 | g_pDesktopAnimationManager->startAnimation(startedOn, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true); 191 | 192 | // zoom on the current workspace. 193 | // const auto& TILE = images[std::clamp(currentid, 0, SIDE_LENGTH * SIDE_LENGTH)]; 194 | 195 | g_pAnimationManager->createAnimation(pMonitor->m_size * pMonitor->m_size / tileSize, size, g_pConfigManager->getAnimationPropertyConfig("windowsMove"), AVARDAMAGE_NONE); 196 | g_pAnimationManager->createAnimation((-((pMonitor->m_size / (double)SIDE_LENGTH) * Vector2D{currentid % SIDE_LENGTH, currentid / SIDE_LENGTH}) * pMonitor->m_scale) * 197 | (pMonitor->m_size / tileSize), 198 | pos, g_pConfigManager->getAnimationPropertyConfig("windowsMove"), AVARDAMAGE_NONE); 199 | 200 | size->setUpdateCallback(damageMonitor); 201 | pos->setUpdateCallback(damageMonitor); 202 | 203 | if (!swipe) { 204 | *size = pMonitor->m_size; 205 | *pos = {0, 0}; 206 | 207 | size->setCallbackOnEnd([this](auto) { redrawAll(true); }); 208 | } 209 | 210 | openedID = currentid; 211 | 212 | Cursor::overrideController->setOverride("left_ptr", Cursor::CURSOR_OVERRIDE_UNKNOWN); 213 | 214 | lastMousePosLocal = g_pInputManager->getMouseCoordsInternal() - pMonitor->m_position; 215 | 216 | auto onCursorMove = [this](void* self, SCallbackInfo& info, std::any param) { 217 | if (closing) 218 | return; 219 | 220 | info.cancelled = true; 221 | lastMousePosLocal = g_pInputManager->getMouseCoordsInternal() - pMonitor->m_position; 222 | }; 223 | 224 | auto onCursorSelect = [this](void* self, SCallbackInfo& info, std::any param) { 225 | if (closing) 226 | return; 227 | 228 | info.cancelled = true; 229 | 230 | selectHoveredWorkspace(); 231 | 232 | close(); 233 | }; 234 | 235 | mouseMoveHook = g_pHookSystem->hookDynamic("mouseMove", onCursorMove); 236 | touchMoveHook = g_pHookSystem->hookDynamic("touchMove", onCursorMove); 237 | 238 | mouseButtonHook = g_pHookSystem->hookDynamic("mouseButton", onCursorSelect); 239 | touchDownHook = g_pHookSystem->hookDynamic("touchDown", onCursorSelect); 240 | } 241 | 242 | void COverview::selectHoveredWorkspace() { 243 | if (closing) 244 | return; 245 | 246 | // get tile x,y 247 | int x = lastMousePosLocal.x / pMonitor->m_size.x * SIDE_LENGTH; 248 | int y = lastMousePosLocal.y / pMonitor->m_size.y * SIDE_LENGTH; 249 | closeOnID = x + y * SIDE_LENGTH; 250 | } 251 | 252 | void COverview::redrawID(int id, bool forcelowres) { 253 | if (pMonitor->m_activeWorkspace != startedOn && !closing) { 254 | // likely user changed. 255 | onWorkspaceChange(); 256 | } 257 | 258 | blockOverviewRendering = true; 259 | 260 | g_pHyprRenderer->makeEGLCurrent(); 261 | 262 | id = std::clamp(id, 0, SIDE_LENGTH * SIDE_LENGTH); 263 | 264 | Vector2D tileSize = pMonitor->m_size / SIDE_LENGTH; 265 | Vector2D tileRenderSize = (pMonitor->m_size - Vector2D{GAP_WIDTH, GAP_WIDTH} * (SIDE_LENGTH - 1)) / SIDE_LENGTH; 266 | CBox monbox{0, 0, tileSize.x * 2, tileSize.y * 2}; 267 | 268 | if (!forcelowres && (size->value() != pMonitor->m_size || closing)) 269 | monbox = {{0, 0}, pMonitor->m_pixelSize}; 270 | 271 | if (!ENABLE_LOWRES) 272 | monbox = {{0, 0}, pMonitor->m_pixelSize}; 273 | 274 | auto& image = images[id]; 275 | 276 | if (image.fb.m_size != monbox.size()) { 277 | image.fb.release(); 278 | image.fb.alloc(monbox.w, monbox.h, pMonitor->m_output->state->state().drmFormat); 279 | } 280 | 281 | CRegion fakeDamage{0, 0, INT16_MAX, INT16_MAX}; 282 | g_pHyprRenderer->beginRender(pMonitor.lock(), fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, &image.fb); 283 | 284 | g_pHyprOpenGL->clear(CHyprColor{0, 0, 0, 1.0}); 285 | 286 | const auto PWORKSPACE = image.pWorkspace; 287 | 288 | PHLWORKSPACE openSpecial = pMonitor->m_activeSpecialWorkspace; 289 | if (openSpecial) 290 | pMonitor->m_activeSpecialWorkspace.reset(); 291 | 292 | startedOn->m_visible = false; 293 | 294 | if (PWORKSPACE) { 295 | pMonitor->m_activeWorkspace = PWORKSPACE; 296 | g_pDesktopAnimationManager->startAnimation(PWORKSPACE, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true); 297 | PWORKSPACE->m_visible = true; 298 | 299 | if (PWORKSPACE == startedOn) 300 | pMonitor->m_activeSpecialWorkspace = openSpecial; 301 | 302 | g_pHyprRenderer->renderWorkspace(pMonitor.lock(), PWORKSPACE, Time::steadyNow(), monbox); 303 | 304 | PWORKSPACE->m_visible = false; 305 | g_pDesktopAnimationManager->startAnimation(PWORKSPACE, CDesktopAnimationManager::ANIMATION_TYPE_OUT, false, true); 306 | 307 | if (PWORKSPACE == startedOn) 308 | pMonitor->m_activeSpecialWorkspace.reset(); 309 | } else 310 | g_pHyprRenderer->renderWorkspace(pMonitor.lock(), PWORKSPACE, Time::steadyNow(), monbox); 311 | 312 | g_pHyprOpenGL->m_renderData.blockScreenShader = true; 313 | g_pHyprRenderer->endRender(); 314 | 315 | pMonitor->m_activeSpecialWorkspace = openSpecial; 316 | pMonitor->m_activeWorkspace = startedOn; 317 | startedOn->m_visible = true; 318 | g_pDesktopAnimationManager->startAnimation(startedOn, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true); 319 | 320 | blockOverviewRendering = false; 321 | } 322 | 323 | void COverview::redrawAll(bool forcelowres) { 324 | for (size_t i = 0; i < (size_t)(SIDE_LENGTH * SIDE_LENGTH); ++i) { 325 | redrawID(i, forcelowres); 326 | } 327 | } 328 | 329 | void COverview::damage() { 330 | blockDamageReporting = true; 331 | g_pHyprRenderer->damageMonitor(pMonitor.lock()); 332 | blockDamageReporting = false; 333 | } 334 | 335 | void COverview::onDamageReported() { 336 | damageDirty = true; 337 | 338 | Vector2D SIZE = size->value(); 339 | 340 | Vector2D tileSize = (SIZE / SIDE_LENGTH); 341 | Vector2D tileRenderSize = (SIZE - Vector2D{GAP_WIDTH, GAP_WIDTH} * (SIDE_LENGTH - 1)) / SIDE_LENGTH; 342 | // const auto& TILE = images[std::clamp(openedID, 0, SIDE_LENGTH * SIDE_LENGTH)]; 343 | CBox texbox = CBox{(openedID % SIDE_LENGTH) * tileRenderSize.x + (openedID % SIDE_LENGTH) * GAP_WIDTH, 344 | (openedID / SIDE_LENGTH) * tileRenderSize.y + (openedID / SIDE_LENGTH) * GAP_WIDTH, tileRenderSize.x, tileRenderSize.y} 345 | .translate(pMonitor->m_position); 346 | 347 | damage(); 348 | 349 | blockDamageReporting = true; 350 | g_pHyprRenderer->damageBox(texbox); 351 | blockDamageReporting = false; 352 | g_pCompositor->scheduleFrameForMonitor(pMonitor.lock()); 353 | } 354 | 355 | void COverview::close() { 356 | if (closing) 357 | return; 358 | 359 | const int ID = closeOnID == -1 ? openedID : closeOnID; 360 | 361 | const auto& TILE = images[std::clamp(ID, 0, SIDE_LENGTH * SIDE_LENGTH)]; 362 | 363 | Vector2D tileSize = (pMonitor->m_size / SIDE_LENGTH); 364 | 365 | *size = pMonitor->m_size * pMonitor->m_size / tileSize; 366 | *pos = (-((pMonitor->m_size / (double)SIDE_LENGTH) * Vector2D{ID % SIDE_LENGTH, ID / SIDE_LENGTH}) * pMonitor->m_scale) * (pMonitor->m_size / tileSize); 367 | 368 | size->setCallbackOnEnd(removeOverview); 369 | 370 | closing = true; 371 | 372 | redrawAll(); 373 | 374 | if (TILE.workspaceID != pMonitor->activeWorkspaceID()) { 375 | pMonitor->setSpecialWorkspace(0); 376 | 377 | // If this tile's workspace was WORKSPACE_INVALID, move to the next 378 | // empty workspace. This should only happen if skip_empty is on, in 379 | // which case some tiles will be left with this ID intentionally. 380 | const int NEWID = TILE.workspaceID == WORKSPACE_INVALID ? getWorkspaceIDNameFromString("emptynm").id : TILE.workspaceID; 381 | 382 | const auto NEWIDWS = g_pCompositor->getWorkspaceByID(NEWID); 383 | 384 | const auto OLDWS = pMonitor->m_activeWorkspace; 385 | 386 | if (!NEWIDWS) 387 | g_pKeybindManager->changeworkspace(std::to_string(NEWID)); 388 | else 389 | g_pKeybindManager->changeworkspace(NEWIDWS->getConfigName()); 390 | 391 | g_pDesktopAnimationManager->startAnimation(pMonitor->m_activeWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true); 392 | g_pDesktopAnimationManager->startAnimation(OLDWS, CDesktopAnimationManager::ANIMATION_TYPE_OUT, false, true); 393 | 394 | startedOn = pMonitor->m_activeWorkspace; 395 | } 396 | } 397 | 398 | void COverview::onPreRender() { 399 | if (damageDirty) { 400 | damageDirty = false; 401 | redrawID(closing ? (closeOnID == -1 ? openedID : closeOnID) : openedID); 402 | } 403 | } 404 | 405 | void COverview::onWorkspaceChange() { 406 | if (valid(startedOn)) 407 | g_pDesktopAnimationManager->startAnimation(startedOn, CDesktopAnimationManager::ANIMATION_TYPE_OUT, false, true); 408 | else 409 | startedOn = pMonitor->m_activeWorkspace; 410 | 411 | for (size_t i = 0; i < (size_t)(SIDE_LENGTH * SIDE_LENGTH); ++i) { 412 | if (images[i].workspaceID != pMonitor->activeWorkspaceID()) 413 | continue; 414 | 415 | openedID = i; 416 | break; 417 | } 418 | 419 | closeOnID = openedID; 420 | close(); 421 | } 422 | 423 | void COverview::render() { 424 | g_pHyprRenderer->m_renderPass.add(makeUnique()); 425 | } 426 | 427 | void COverview::fullRender() { 428 | const auto GAPSIZE = (closing ? (1.0 - size->getPercent()) : size->getPercent()) * GAP_WIDTH; 429 | 430 | if (pMonitor->m_activeWorkspace != startedOn && !closing) { 431 | // likely user changed. 432 | onWorkspaceChange(); 433 | } 434 | 435 | Vector2D SIZE = size->value(); 436 | 437 | Vector2D tileSize = (SIZE / SIDE_LENGTH); 438 | Vector2D tileRenderSize = (SIZE - Vector2D{GAPSIZE, GAPSIZE} * (SIDE_LENGTH - 1)) / SIDE_LENGTH; 439 | 440 | g_pHyprOpenGL->clear(BG_COLOR.stripA()); 441 | 442 | for (size_t y = 0; y < (size_t)SIDE_LENGTH; ++y) { 443 | for (size_t x = 0; x < (size_t)SIDE_LENGTH; ++x) { 444 | CBox texbox = {x * tileRenderSize.x + x * GAPSIZE, y * tileRenderSize.y + y * GAPSIZE, tileRenderSize.x, tileRenderSize.y}; 445 | texbox.scale(pMonitor->m_scale).translate(pos->value()); 446 | texbox.round(); 447 | CRegion damage{0, 0, INT16_MAX, INT16_MAX}; 448 | g_pHyprOpenGL->renderTextureInternal(images[x + y * SIDE_LENGTH].fb.getTexture(), texbox, {.damage = &damage, .a = 1.0}); 449 | } 450 | } 451 | } 452 | 453 | static float lerp(const float& from, const float& to, const float perc) { 454 | return (to - from) * perc + from; 455 | } 456 | 457 | static Vector2D lerp(const Vector2D& from, const Vector2D& to, const float perc) { 458 | return Vector2D{lerp(from.x, to.x, perc), lerp(from.y, to.y, perc)}; 459 | } 460 | 461 | void COverview::setClosing(bool closing_) { 462 | closing = closing_; 463 | } 464 | 465 | void COverview::resetSwipe() { 466 | swipeWasCommenced = false; 467 | } 468 | 469 | void COverview::onSwipeUpdate(double delta) { 470 | m_isSwiping = true; 471 | 472 | if (swipeWasCommenced) 473 | return; 474 | 475 | static auto* const* PDISTANCE = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:gesture_distance")->getDataStaticPtr(); 476 | 477 | const float PERC = closing ? std::clamp(delta / (double)**PDISTANCE, 0.0, 1.0) : 1.0 - std::clamp(delta / (double)**PDISTANCE, 0.0, 1.0); 478 | const auto WORKSPACE_FOCUS_ID = closing && closeOnID != -1 ? closeOnID : openedID; 479 | 480 | Vector2D tileSize = (pMonitor->m_size / SIDE_LENGTH); 481 | 482 | const auto SIZEMAX = pMonitor->m_size * pMonitor->m_size / tileSize; 483 | const auto POSMAX = (-((pMonitor->m_size / (double)SIDE_LENGTH) * Vector2D{WORKSPACE_FOCUS_ID % SIDE_LENGTH, WORKSPACE_FOCUS_ID / SIDE_LENGTH}) * pMonitor->m_scale) * 484 | (pMonitor->m_size / tileSize); 485 | 486 | const auto SIZEMIN = pMonitor->m_size; 487 | const auto POSMIN = Vector2D{0, 0}; 488 | 489 | size->setValueAndWarp(lerp(SIZEMIN, SIZEMAX, PERC)); 490 | pos->setValueAndWarp(lerp(POSMIN, POSMAX, PERC)); 491 | } 492 | 493 | void COverview::onSwipeEnd() { 494 | const auto SIZEMIN = pMonitor->m_size; 495 | const auto SIZEMAX = pMonitor->m_size * pMonitor->m_size / (pMonitor->m_size / SIDE_LENGTH); 496 | const auto PERC = (size->value() - SIZEMIN).x / (SIZEMAX - SIZEMIN).x; 497 | if (PERC > 0.5) { 498 | close(); 499 | return; 500 | } 501 | *size = pMonitor->m_size; 502 | *pos = {0, 0}; 503 | 504 | size->setCallbackOnEnd([this](WP thisptr) { redrawAll(true); }); 505 | 506 | swipeWasCommenced = true; 507 | m_isSwiping = false; 508 | } 509 | --------------------------------------------------------------------------------