pax_global_header00006660000000000000000000000064151245141000014503gustar00rootroot0000000000000052 comment=d7b67e8f4ba8ebeee4ce899348fcee6291512169 hyprwm-hyprland-plugins-4dbef35/000077500000000000000000000000001512451410000170255ustar00rootroot00000000000000hyprwm-hyprland-plugins-4dbef35/.clang-format000066400000000000000000000034161512451410000214040ustar00rootroot00000000000000--- Language: Cpp BasedOnStyle: LLVM AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignConsecutiveMacros: true AlignConsecutiveAssignments: true AlignEscapedNewlines: Right AlignOperands: false AlignTrailingComments: true AllowAllArgumentsOnNextLine: true AllowAllConstructorInitializersOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: true AllowShortCaseLabelsOnASingleLine: true AllowShortFunctionsOnASingleLine: Empty AllowShortIfStatementsOnASingleLine: Never AllowShortLambdasOnASingleLine: All AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: Yes BreakBeforeBraces: Attach BreakBeforeTernaryOperators: false BreakConstructorInitializers: AfterColon ColumnLimit: 180 CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: false IncludeBlocks: Preserve IndentCaseLabels: true IndentWidth: 4 PointerAlignment: Left ReflowComments: false SortIncludes: false SortUsingDeclarations: false SpaceAfterCStyleCast: false SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: true SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInCStyleCastParentheses: false SpacesInContainerLiterals: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Auto TabWidth: 4 UseTab: Never AllowShortEnumsOnASingleLine: false BraceWrapping: AfterEnum: false AlignConsecutiveDeclarations: AcrossEmptyLines NamespaceIndentation: All hyprwm-hyprland-plugins-4dbef35/.github/000077500000000000000000000000001512451410000203655ustar00rootroot00000000000000hyprwm-hyprland-plugins-4dbef35/.github/workflows/000077500000000000000000000000001512451410000224225ustar00rootroot00000000000000hyprwm-hyprland-plugins-4dbef35/.github/workflows/nix-build.yml000066400000000000000000000027341512451410000250460ustar00rootroot00000000000000on: workflow_call: jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install Nix uses: nixbuild/nix-quick-install-action@v31 with: nix_conf: | keep-env-derivations = true keep-outputs = true - name: Restore and save Nix store uses: nix-community/cache-nix-action@v6 with: # restore and save a cache using this key primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} # if there's no cache hit, restore a cache by this prefix restore-prefixes-first-match: nix-${{ runner.os }}- # collect garbage until the Nix store size (in bytes) is at most this number # before trying to save a new cache # 1G = 1073741824 gc-max-store-size-linux: 1G # do purge caches purge: true # purge all versions of the cache purge-prefixes: nix-${{ runner.os }}- # created more than this number of seconds ago purge-created: 0 # or, last accessed more than this number of seconds ago # relative to the start of the `Post Restore and save Nix store` phase purge-last-accessed: 0 # except any version with the key that is the same as the `primary-key` purge-primary-key: never - name: Update inputs run: nix flake update - run: nix flake check -L --keep-going hyprwm-hyprland-plugins-4dbef35/.github/workflows/nix-ci.yml000066400000000000000000000001631512451410000243340ustar00rootroot00000000000000name: Nix on: [push, pull_request, workflow_dispatch] jobs: build: uses: ./.github/workflows/nix-build.yml hyprwm-hyprland-plugins-4dbef35/.gitignore000066400000000000000000000005121512451410000210130ustar00rootroot00000000000000# Prerequisites *.d # Compiled Object files *.slo *.lo *.o *.obj # Precompiled Headers *.gch *.pch # Compiled Dynamic libraries *.so *.dylib *.dll # Fortran module files *.mod *.smod # Compiled Static libraries *.lai *.la *.a *.lib # Executables *.exe *.out *.app # Editors .vscode/ # Nix result result-man build/ .cache/hyprwm-hyprland-plugins-4dbef35/CMakeLists.txt000066400000000000000000000007311512451410000215660ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.27) project(hyprland-plugins DESCRIPTION "Official plugins for Hyprland" LANGUAGES CXX ) set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_subdirectory(borders-plus-plus) add_subdirectory(csgo-vulkan-fix) add_subdirectory(hyprbars) add_subdirectory(hyprexpo) add_subdirectory(hyprfocus) add_subdirectory(hyprscrolling) add_subdirectory(hyprtrails) add_subdirectory(hyprwinwrap) add_subdirectory(xtra-dispatchers) hyprwm-hyprland-plugins-4dbef35/LICENSE000066400000000000000000000027371512451410000200430ustar00rootroot00000000000000BSD 3-Clause License Copyright (c) 2023, Hypr Development Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. hyprwm-hyprland-plugins-4dbef35/README.md000066400000000000000000000055271512451410000203150ustar00rootroot00000000000000# hyprland-plugins This repo houses official plugins for Hyprland. # Plugin list - borders-plus-plus -> adds one or two additional borders to windows - csgo-vulkan-fix -> fixes custom resolutions on CS:GO with `-vulkan` - hyprbars -> adds title bars to windows - hyprexpo -> adds an expo-like workspace overview - hyprfocus -> flashfocus for hyprland - hyprscrolling -> adds a scrolling layout to hyprland - hyprtrails -> adds smooth trails behind moving windows - hyprwinwrap -> clone of xwinwrap, allows you to put any app as a wallpaper - xtra-dispatchers -> adds some new dispatchers # Install > [!IMPORTANT] > hyprland-plugins only officially supports installation via `hyprpm`. > `hyprpm` automatically detects your hyprland version & installs only > the corresponding "pinned" release of hyprland-plugins. > If you want the latest commits to hyprland-plugins, you need to use > `hyprland-git`. ## Install with `hyprpm` To install these plugins, from the command line run: ```bash hyprpm update ``` Then add this repository: ```bash hyprpm add https://github.com/hyprwm/hyprland-plugins ``` then enable the desired plugin with ```bash hyprpm enable ``` See the respective README's in the subdirectories for configuration options. See [the plugins wiki](https://wiki.hyprland.org/Plugins/Using-Plugins/#installing--using-plugins) and `hyprpm -h` for more details. ## Install on Nix To use these plugins, it's recommended that you are already using the [Hyprland flake](https://github.com/hyprwm/Hyprland). First, add this flake to your inputs: ```nix inputs = { # ... hyprland.url = "github:hyprwm/Hyprland"; hyprland-plugins = { url = "github:hyprwm/hyprland-plugins"; inputs.hyprland.follows = "hyprland"; }; # ... }; ``` The `inputs.hyprland.follows` guarantees the plugins will always be built using your locked Hyprland version, thus you will never get version mismatches that lead to errors. After that's done, you can use the plugins with the Home Manager module like this: ```nix {inputs, pkgs, ...}: { wayland.windowManager.hyprland = { enable = true; # ... plugins = [ inputs.hyprland-plugins.packages.${pkgs.system}.hyprbars # ... ]; }; } ``` If you don't use Home Manager: ```nix { lib, pkgs, inputs, ... }: with lib; let hyprPluginPkgs = inputs.hyprland-plugins.packages.${pkgs.system}; hypr-plugin-dir = pkgs.symlinkJoin { name = "hyrpland-plugins"; paths = with hyprPluginPkgs; [ hyprexpo #...plugins ]; }; in { environment.sessionVariables = { HYPR_PLUGIN_DIR = hypr-plugin-dir; }; } ``` And in `hyprland.conf` ```hyprlang # load all the plugins you installed exec-once = hyprctl plugin load "$HYPR_PLUGIN_DIR/lib/libhyprexpo.so" ``` # Contributing Feel free to open issues and MRs with fixes. If you want your plugin added here, contact vaxry beforehand. hyprwm-hyprland-plugins-4dbef35/borders-plus-plus/000077500000000000000000000000001512451410000224275ustar00rootroot00000000000000hyprwm-hyprland-plugins-4dbef35/borders-plus-plus/BorderppPassElement.cpp000066400000000000000000000007601512451410000270540ustar00rootroot00000000000000#include "BorderppPassElement.hpp" #include #include "borderDeco.hpp" CBorderPPPassElement::CBorderPPPassElement(const CBorderPPPassElement::SBorderPPData& data_) : data(data_) { ; } void CBorderPPPassElement::draw(const CRegion& damage) { data.deco->drawPass(g_pHyprOpenGL->m_renderData.pMonitor.lock(), data.a); } bool CBorderPPPassElement::needsLiveBlur() { return false; } bool CBorderPPPassElement::needsPrecomputeBlur() { return false; }hyprwm-hyprland-plugins-4dbef35/borders-plus-plus/BorderppPassElement.hpp000066400000000000000000000011671512451410000270630ustar00rootroot00000000000000#pragma once #include class CBordersPlusPlus; class CBorderPPPassElement : public IPassElement { public: struct SBorderPPData { CBordersPlusPlus* deco = nullptr; float a = 1.F; }; CBorderPPPassElement(const SBorderPPData& data_); virtual ~CBorderPPPassElement() = default; virtual void draw(const CRegion& damage); virtual bool needsLiveBlur(); virtual bool needsPrecomputeBlur(); virtual const char* passName() { return "CBorderPPPassElement"; } private: SBorderPPData data; };hyprwm-hyprland-plugins-4dbef35/borders-plus-plus/CMakeLists.txt000066400000000000000000000010361512451410000251670ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.27) project(borders-plus-plus DESCRIPTION "borders-plus-plus plugin for Hyprland" VERSION 0.1 ) set(CMAKE_CXX_STANDARD 23) file(GLOB_RECURSE SRC "*.cpp") add_library(borders-plus-plus SHARED ${SRC}) find_package(PkgConfig REQUIRED) pkg_check_modules(deps REQUIRED IMPORTED_TARGET hyprland libdrm libinput libudev pangocairo pixman-1 wayland-server xkbcommon ) target_link_libraries(borders-plus-plus PRIVATE rt PkgConfig::deps) install(TARGETS borders-plus-plus) hyprwm-hyprland-plugins-4dbef35/borders-plus-plus/Makefile000066400000000000000000000006011512451410000240640ustar00rootroot00000000000000# Else exist specifically for clang ifeq ($(CXX),g++) EXTRA_FLAGS = --no-gnu-unique else EXTRA_FLAGS = endif all: $(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 clean: rm ./borders-plus-plus.so hyprwm-hyprland-plugins-4dbef35/borders-plus-plus/README.md000066400000000000000000000011201512451410000237000ustar00rootroot00000000000000# borders-plus-plus Allows you to add one or two additional borders to your windows. The borders added are static. Example Config: ``` plugin { borders-plus-plus { add_borders = 1 # 0 - 9 # you can add up to 9 borders col.border_1 = rgb(ffffff) col.border_2 = rgb(2222ff) # -1 means "default" as in the one defined in general:border_size border_size_1 = 10 border_size_2 = -1 # makes outer edges match rounding of the parent. Turn on / off to better understand. Default = on. natural_rounding = yes } } ```hyprwm-hyprland-plugins-4dbef35/borders-plus-plus/borderDeco.cpp000066400000000000000000000155751512451410000252200ustar00rootroot00000000000000#include "borderDeco.hpp" #include #include #include #include using namespace Hyprutils::Memory; #include "BorderppPassElement.hpp" #include "globals.hpp" CBordersPlusPlus::CBordersPlusPlus(PHLWINDOW pWindow) : IHyprWindowDecoration(pWindow), m_pWindow(pWindow) { m_lastWindowPos = pWindow->m_realPosition->value(); m_lastWindowSize = pWindow->m_realSize->value(); } CBordersPlusPlus::~CBordersPlusPlus() { damageEntire(); } SDecorationPositioningInfo CBordersPlusPlus::getPositioningInfo() { static auto* const PBORDERS = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:borders-plus-plus:add_borders")->getDataStaticPtr(); static std::vector PSIZES; for (size_t i = 0; i < 9; ++i) { PSIZES.push_back((Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:borders-plus-plus:border_size_" + std::to_string(i + 1))->getDataStaticPtr()); } SDecorationPositioningInfo info; info.policy = DECORATION_POSITION_STICKY; info.reserved = true; info.priority = 9990; info.edges = DECORATION_EDGE_BOTTOM | DECORATION_EDGE_LEFT | DECORATION_EDGE_RIGHT | DECORATION_EDGE_TOP; if (m_fLastThickness == 0) { double size = 0; for (size_t i = 0; i < **PBORDERS; ++i) { size += **PSIZES[i]; } info.desiredExtents = {{size, size}, {size, size}}; m_fLastThickness = size; } else info.desiredExtents = {{m_fLastThickness, m_fLastThickness}, {m_fLastThickness, m_fLastThickness}}; return info; } void CBordersPlusPlus::onPositioningReply(const SDecorationPositioningReply& reply) { m_bAssignedGeometry = reply.assignedGeometry; } uint64_t CBordersPlusPlus::getDecorationFlags() { return DECORATION_PART_OF_MAIN_WINDOW; } eDecorationLayer CBordersPlusPlus::getDecorationLayer() { return DECORATION_LAYER_OVER; } std::string CBordersPlusPlus::getDisplayName() { return "Borders++"; } void CBordersPlusPlus::draw(PHLMONITOR pMonitor, const float& a) { if (!validMapped(m_pWindow)) return; const auto PWINDOW = m_pWindow.lock(); if (!PWINDOW->m_ruleApplicator->decorate().valueOrDefault()) return; CBorderPPPassElement::SBorderPPData data; data.deco = this; g_pHyprRenderer->m_renderPass.add(makeUnique(data)); } void CBordersPlusPlus::drawPass(PHLMONITOR pMonitor, const float& a) { const auto PWINDOW = m_pWindow.lock(); static std::vector PCOLORS; static std::vector PSIZES; for (size_t i = 0; i < 9; ++i) { PCOLORS.push_back((Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:borders-plus-plus:col.border_" + std::to_string(i + 1))->getDataStaticPtr()); PSIZES.push_back((Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:borders-plus-plus:border_size_" + std::to_string(i + 1))->getDataStaticPtr()); } static auto* const PBORDERS = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:borders-plus-plus:add_borders")->getDataStaticPtr(); static auto* const PNATURALROUND = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:borders-plus-plus:natural_rounding")->getDataStaticPtr(); static auto* const PROUNDING = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "decoration:rounding")->getDataStaticPtr(); static auto* const PBORDERSIZE = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "general:border_size")->getDataStaticPtr(); if (**PBORDERS < 1) return; if (m_bAssignedGeometry.width < m_seExtents.topLeft.x + 1 || m_bAssignedGeometry.height < m_seExtents.topLeft.y + 1) return; const auto PWORKSPACE = PWINDOW->m_workspace; const auto WORKSPACEOFFSET = PWORKSPACE && !PWINDOW->m_pinned ? PWORKSPACE->m_renderOffset->value() : Vector2D(); auto rounding = PWINDOW->rounding() == 0 ? 0 : (PWINDOW->rounding() + **PBORDERSIZE) * pMonitor->m_scale; const auto ROUNDINGPOWER = PWINDOW->roundingPower(); const auto ORIGINALROUND = rounding == 0 ? 0 : (PWINDOW->rounding() + **PBORDERSIZE) * pMonitor->m_scale; CBox fullBox = m_bAssignedGeometry; fullBox.translate(g_pDecorationPositioner->getEdgeDefinedPoint(DECORATION_EDGE_BOTTOM | DECORATION_EDGE_LEFT | DECORATION_EDGE_RIGHT | DECORATION_EDGE_TOP, m_pWindow.lock())); fullBox.translate(PWINDOW->m_floatingOffset - pMonitor->m_position + WORKSPACEOFFSET); if (fullBox.width < 1 || fullBox.height < 1) return; double fullThickness = 0; for (size_t i = 0; i < **PBORDERS; ++i) { const int THISBORDERSIZE = **(PSIZES[i]) == -1 ? **PBORDERSIZE : (**PSIZES[i]); fullThickness += THISBORDERSIZE; } fullBox.expand(-fullThickness).scale(pMonitor->m_scale).round(); for (size_t i = 0; i < **PBORDERS; ++i) { const int PREVBORDERSIZESCALED = i == 0 ? 0 : (**PSIZES[i - 1] == -1 ? **PBORDERSIZE : **(PSIZES[i - 1])) * pMonitor->m_scale; const int THISBORDERSIZE = **(PSIZES[i]) == -1 ? **PBORDERSIZE : (**PSIZES[i]); if (i != 0) { rounding += rounding == 0 ? 0 : PREVBORDERSIZESCALED; fullBox.x -= PREVBORDERSIZESCALED; fullBox.y -= PREVBORDERSIZESCALED; fullBox.width += PREVBORDERSIZESCALED * 2; fullBox.height += PREVBORDERSIZESCALED * 2; } if (fullBox.width < 1 || fullBox.height < 1) break; g_pHyprOpenGL->scissor(nullptr); g_pHyprOpenGL->renderBorder(fullBox, CHyprColor{(uint64_t)**PCOLORS[i]}, {.round = **PNATURALROUND ? sc(ORIGINALROUND) : sc(rounding), .roundingPower = ROUNDINGPOWER, .borderSize = THISBORDERSIZE, .a = a, .outerRound = **PNATURALROUND ? sc(ORIGINALROUND) : -1}); } m_seExtents = {{fullThickness, fullThickness}, {fullThickness, fullThickness}}; m_bLastRelativeBox = CBox{0, 0, m_lastWindowSize.x, m_lastWindowSize.y}.addExtents(m_seExtents); if (fullThickness != m_fLastThickness) { m_fLastThickness = fullThickness; g_pDecorationPositioner->repositionDeco(this); } } eDecorationType CBordersPlusPlus::getDecorationType() { return DECORATION_CUSTOM; } void CBordersPlusPlus::updateWindow(PHLWINDOW pWindow) { m_lastWindowPos = pWindow->m_realPosition->value(); m_lastWindowSize = pWindow->m_realSize->value(); damageEntire(); } void CBordersPlusPlus::damageEntire() { CBox dm = m_bLastRelativeBox.copy().translate(m_lastWindowPos).expand(2); g_pHyprRenderer->damageBox(dm); } hyprwm-hyprland-plugins-4dbef35/borders-plus-plus/borderDeco.hpp000066400000000000000000000022601512451410000252100ustar00rootroot00000000000000#pragma once #define WLR_USE_UNSTABLE #include class CBordersPlusPlus : public IHyprWindowDecoration { public: CBordersPlusPlus(PHLWINDOW); virtual ~CBordersPlusPlus(); virtual SDecorationPositioningInfo getPositioningInfo(); virtual void onPositioningReply(const SDecorationPositioningReply& reply); virtual void draw(PHLMONITOR, float const& a); virtual eDecorationType getDecorationType(); virtual void updateWindow(PHLWINDOW); virtual void damageEntire(); virtual uint64_t getDecorationFlags(); virtual eDecorationLayer getDecorationLayer(); virtual std::string getDisplayName(); private: void drawPass(PHLMONITOR, float const& a); SBoxExtents m_seExtents; PHLWINDOWREF m_pWindow; CBox m_bLastRelativeBox; CBox m_bAssignedGeometry; Vector2D m_lastWindowPos; Vector2D m_lastWindowSize; double m_fLastThickness = 0; friend class CBorderPPPassElement; }; hyprwm-hyprland-plugins-4dbef35/borders-plus-plus/default.nix000066400000000000000000000006461512451410000246010ustar00rootroot00000000000000{ lib, hyprland, hyprlandPlugins, }: hyprlandPlugins.mkHyprlandPlugin { pluginName = "borders-plus-plus"; version = "0.1"; src = ./.; inherit (hyprland) nativeBuildInputs; meta = with lib; { homepage = "https://github.com/hyprwm/hyprland-plugins/tree/main/borders-plus-plus"; description = "Hyprland borders-plus-plus plugin"; license = licenses.bsd3; platforms = platforms.linux; }; } hyprwm-hyprland-plugins-4dbef35/borders-plus-plus/globals.hpp000066400000000000000000000001361512451410000245630ustar00rootroot00000000000000#pragma once #include inline HANDLE PHANDLE = nullptr; hyprwm-hyprland-plugins-4dbef35/borders-plus-plus/main.cpp000066400000000000000000000047411512451410000240650ustar00rootroot00000000000000#define WLR_USE_UNSTABLE #include #include #include #include #include #include #include "borderDeco.hpp" #include "globals.hpp" // Do NOT change this function. APICALL EXPORT std::string PLUGIN_API_VERSION() { return HYPRLAND_API_VERSION; } void onNewWindow(void* self, std::any data) { // data is guaranteed const auto PWINDOW = std::any_cast(data); HyprlandAPI::addWindowDecoration(PHANDLE, PWINDOW, makeUnique(PWINDOW)); } APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { PHANDLE = handle; const std::string HASH = __hyprland_api_get_hash(); const std::string CLIENT_HASH = __hyprland_api_get_client_hash(); if (HASH != CLIENT_HASH) { HyprlandAPI::addNotification(PHANDLE, "[borders-plus-plus] Failure in initialization: Version mismatch (headers ver is not equal to running hyprland ver)", CHyprColor{1.0, 0.2, 0.2, 1.0}, 5000); throw std::runtime_error("[bpp] Version mismatch"); } HyprlandAPI::addConfigValue(PHANDLE, "plugin:borders-plus-plus:add_borders", Hyprlang::INT{1}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:borders-plus-plus:natural_rounding", Hyprlang::INT{1}); for (size_t i = 0; i < 9; ++i) { HyprlandAPI::addConfigValue(PHANDLE, "plugin:borders-plus-plus:col.border_" + std::to_string(i + 1), Hyprlang::INT{*configStringToInt("rgba(000000ee)")}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:borders-plus-plus:border_size_" + std::to_string(i + 1), Hyprlang::INT{-1}); } HyprlandAPI::reloadConfig(); static auto P = HyprlandAPI::registerCallbackDynamic(PHANDLE, "openWindow", [&](void* self, SCallbackInfo& info, std::any data) { onNewWindow(self, data); }); // add deco to existing windows for (auto& w : g_pCompositor->m_windows) { if (w->isHidden() || !w->m_isMapped) continue; HyprlandAPI::addWindowDecoration(PHANDLE, w, makeUnique(w)); } HyprlandAPI::addNotification(PHANDLE, "[borders-plus-plus] Initialized successfully!", CHyprColor{0.2, 1.0, 0.2, 1.0}, 5000); return {"borders-plus-plus", "A plugin to add more borders to windows.", "Vaxry", "1.0"}; } APICALL EXPORT void PLUGIN_EXIT() { g_pHyprRenderer->m_renderPass.removeAllOfType("CBorderPPPassElement"); } hyprwm-hyprland-plugins-4dbef35/borders-plus-plus/meson.build000066400000000000000000000016311512451410000245720ustar00rootroot00000000000000project('borders-plus-plus', 'cpp', version: '0.1', default_options: ['buildtype=release'], ) cpp_compiler = meson.get_compiler('cpp') if cpp_compiler.has_argument('-std=c++23') add_global_arguments('-std=c++23', language: 'cpp') elif cpp_compiler.has_argument('-std=c++2b') add_global_arguments('-std=c++2b', language: 'cpp') else error('Could not configure current C++ compiler (' + cpp_compiler.get_id() + ' ' + cpp_compiler.version() + ') with required C++ standard (C++23)') endif globber = run_command('find', '.', '-name', '*.cpp', check: true) src = globber.stdout().strip().split('\n') shared_module(meson.project_name(), src, dependencies: [ dependency('hyprland'), dependency('pixman-1'), dependency('libdrm'), dependency('pangocairo'), dependency('libinput'), dependency('libudev'), dependency('wayland-server'), dependency('xkbcommon'), ], install: true, ) hyprwm-hyprland-plugins-4dbef35/csgo-vulkan-fix/000077500000000000000000000000001512451410000220425ustar00rootroot00000000000000hyprwm-hyprland-plugins-4dbef35/csgo-vulkan-fix/CMakeLists.txt000066400000000000000000000010241512451410000245770ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.27) project(csgo-vulkan-fix DESCRIPTION "csgo-vulkan-fix plugin for Hyprland" VERSION 0.1 ) set(CMAKE_CXX_STANDARD 23) file(GLOB_RECURSE SRC "*.cpp") add_library(csgo-vulkan-fix SHARED ${SRC}) find_package(PkgConfig REQUIRED) pkg_check_modules(deps REQUIRED IMPORTED_TARGET hyprland libdrm libinput libudev pangocairo pixman-1 wayland-server xkbcommon ) target_link_libraries(csgo-vulkan-fix PRIVATE rt PkgConfig::deps) install(TARGETS csgo-vulkan-fix) hyprwm-hyprland-plugins-4dbef35/csgo-vulkan-fix/Makefile000066400000000000000000000005251512451410000235040ustar00rootroot00000000000000# Else exist specifically for clang ifeq ($(CXX),g++) EXTRA_FLAGS = --no-gnu-unique else EXTRA_FLAGS = endif all: $(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 clean: rm ./csgo-vulkan-fix.so hyprwm-hyprland-plugins-4dbef35/csgo-vulkan-fix/README.md000066400000000000000000000015331512451410000233230ustar00rootroot00000000000000# csgo-vulkan-fix Originally meant for csgo / cs2, but can work with any app, really. csgo-vulkan-fix is a way to force apps to a fake resolution without them realizing it. If you want to play CS2, you're locked to your native res. Other resolutions (especially not 16:9) are wonky. With this plugin, you aren't anymore. This is also useful for when you are scaling and want to force any game to a custom resolution. CS2 launch options: ``` -vulkan -window -w -h -vulkan ``` example plugin config: ``` plugin { csgo-vulkan-fix { # Whether to fix the mouse position. A select few apps might be wonky with this. fix_mouse = true # Add apps with vkfix-app = initialClass, width, height vkfix-app = cs2, 1650, 1050 vkfix-app = myapp, 1920, 1080 } } ``` fullscreen the game manually and enjoy.hyprwm-hyprland-plugins-4dbef35/csgo-vulkan-fix/default.nix000066400000000000000000000006321512451410000242070ustar00rootroot00000000000000{ lib, hyprland, hyprlandPlugins, }: hyprlandPlugins.mkHyprlandPlugin { pluginName = "csgo-vulkan-fix"; version = "0.1"; src = ./.; inherit (hyprland) nativeBuildInputs; meta = with lib; { homepage = "https://github.com/hyprwm/hyprland-plugins/tree/main/csgo-vulkan-fix"; description = "Hyprland CS:GO Vulkan fix"; license = licenses.bsd3; platforms = platforms.linux; }; } hyprwm-hyprland-plugins-4dbef35/csgo-vulkan-fix/globals.hpp000066400000000000000000000001351512451410000241750ustar00rootroot00000000000000#pragma once #include inline HANDLE PHANDLE = nullptr;hyprwm-hyprland-plugins-4dbef35/csgo-vulkan-fix/main.cpp000066400000000000000000000176771512451410000235140ustar00rootroot00000000000000#define WLR_USE_UNSTABLE #include #include #include #include #include #include #include #include #include "globals.hpp" #include using namespace Hyprutils::String; // Methods inline CFunctionHook* g_pMouseMotionHook = nullptr; inline CFunctionHook* g_pSurfaceSizeHook = nullptr; inline CFunctionHook* g_pWLSurfaceDamageHook = nullptr; typedef void (*origMotion)(CSeatManager*, uint32_t, const Vector2D&); typedef void (*origSurfaceSize)(CXWaylandSurface*, const CBox&); typedef CRegion (*origWLSurfaceDamage)(Desktop::View::CWLSurface*); // Do NOT change this function. APICALL EXPORT std::string PLUGIN_API_VERSION() { return HYPRLAND_API_VERSION; } struct SAppConfig { std::string szClass; Vector2D res; }; std::vector g_appConfigs; static const SAppConfig* getAppConfig(const std::string& appClass) { for (const auto& ac : g_appConfigs) { if (ac.szClass != appClass) continue; return ∾ } return nullptr; } void hkNotifyMotion(CSeatManager* thisptr, uint32_t time_msec, const Vector2D& local) { static auto* const PFIX = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:csgo-vulkan-fix:fix_mouse")->getDataStaticPtr(); Vector2D newCoords = local; auto focusState = Desktop::focusState(); auto window = focusState->window(); auto monitor = focusState->monitor(); const auto CONFIG = window && monitor ? getAppConfig(window->m_initialClass) : nullptr; if (**PFIX && CONFIG) { // fix the coords newCoords.x *= (CONFIG->res.x / monitor->m_size.x) / window->m_X11SurfaceScaledBy; newCoords.y *= (CONFIG->res.y / monitor->m_size.y) / window->m_X11SurfaceScaledBy; } (*(origMotion)g_pMouseMotionHook->m_original)(thisptr, time_msec, newCoords); } void hkSetWindowSize(CXWaylandSurface* surface, const CBox& box) { if (!surface) { (*(origSurfaceSize)g_pSurfaceSizeHook->m_original)(surface, box); return; } const auto SURF = surface->m_surface.lock(); const auto PWINDOW = g_pCompositor->getWindowFromSurface(SURF); CBox newBox = box; if (!PWINDOW) { (*(origSurfaceSize)g_pSurfaceSizeHook->m_original)(surface, newBox); return; } if (const auto CONFIG = getAppConfig(PWINDOW->m_initialClass); CONFIG) { newBox.w = CONFIG->res.x; newBox.h = CONFIG->res.y; Desktop::View::CWLSurface::fromResource(SURF)->m_fillIgnoreSmall = true; } (*(origSurfaceSize)g_pSurfaceSizeHook->m_original)(surface, newBox); } CRegion hkWLSurfaceDamage(Desktop::View::CWLSurface* thisptr) { const auto RG = (*(origWLSurfaceDamage)g_pWLSurfaceDamageHook->m_original)(thisptr); if (thisptr->exists() && Desktop::View::CWindow::fromView(thisptr->view())) { const auto WINDOW = Desktop::View::CWindow::fromView(thisptr->view()); const auto CONFIG = getAppConfig(WINDOW->m_initialClass); if (CONFIG) { const auto PMONITOR = WINDOW->m_monitor.lock(); if (PMONITOR) g_pHyprRenderer->damageMonitor(PMONITOR); else g_pHyprRenderer->damageWindow(WINDOW); } } return RG; } APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { PHANDLE = handle; const std::string HASH = __hyprland_api_get_hash(); const std::string CLIENT_HASH = __hyprland_api_get_client_hash(); if (HASH != CLIENT_HASH) { HyprlandAPI::addNotification(PHANDLE, "[csgo-vulkan-fix] Failure in initialization: Version mismatch (headers ver is not equal to running hyprland ver)", CHyprColor{1.0, 0.2, 0.2, 1.0}, 5000); throw std::runtime_error("[vkfix] Version mismatch"); } HyprlandAPI::addConfigValue(PHANDLE, "plugin:csgo-vulkan-fix:res_w", Hyprlang::INT{1680}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:csgo-vulkan-fix:res_h", Hyprlang::INT{1050}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:csgo-vulkan-fix:fix_mouse", Hyprlang::INT{1}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:csgo-vulkan-fix:class", Hyprlang::STRING{"cs2"}); static auto P = HyprlandAPI::registerCallbackDynamic(PHANDLE, "preConfigReload", [&](void* self, SCallbackInfo& info, std::any data) { g_appConfigs.clear(); static auto* const RESX = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:csgo-vulkan-fix:res_w")->getDataStaticPtr(); static auto* const RESY = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:csgo-vulkan-fix:res_h")->getDataStaticPtr(); static auto* const PCLASS = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:csgo-vulkan-fix:class")->getDataStaticPtr(); g_appConfigs.emplace_back(SAppConfig{.szClass = *PCLASS, .res = Vector2D{(int)**RESX, (int)**RESY}}); }); HyprlandAPI::addConfigKeyword( PHANDLE, "vkfix-app", [](const char* l, const char* r) -> Hyprlang::CParseResult { const std::string str = r; CConstVarList data(str, 0, ',', true); Hyprlang::CParseResult result; if (data.size() != 3) { result.setError("vkfix-app requires 3 params"); return result; } try { SAppConfig config; config.szClass = data[0]; config.res = Vector2D{std::stoi(std::string{data[1]}), std::stoi(std::string{data[2]})}; g_appConfigs.emplace_back(std::move(config)); } catch (std::exception& e) { result.setError("failed to parse line"); return result; } return result; }, Hyprlang::SHandlerOptions{}); auto FNS = HyprlandAPI::findFunctionsByName(PHANDLE, "sendPointerMotion"); for (auto& fn : FNS) { if (!fn.demangled.contains("CSeatManager")) continue; g_pMouseMotionHook = HyprlandAPI::createFunctionHook(PHANDLE, fn.address, (void*)::hkNotifyMotion); break; } FNS = HyprlandAPI::findFunctionsByName(PHANDLE, "configure"); for (auto& fn : FNS) { if (!fn.demangled.contains("XWaylandSurface")) continue; g_pSurfaceSizeHook = HyprlandAPI::createFunctionHook(PHANDLE, fn.address, (void*)::hkSetWindowSize); break; } FNS = HyprlandAPI::findFunctionsByName(PHANDLE, "computeDamage"); for (auto& r : FNS) { if (!r.demangled.contains("CWLSurface")) continue; g_pWLSurfaceDamageHook = HyprlandAPI::createFunctionHook(PHANDLE, r.address, (void*)::hkWLSurfaceDamage); break; } bool success = g_pSurfaceSizeHook && g_pWLSurfaceDamageHook && g_pMouseMotionHook; if (!success) { 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); throw std::runtime_error("[vkfix] Hooks fn init failed"); } success = success && g_pWLSurfaceDamageHook->hook(); success = success && g_pMouseMotionHook->hook(); success = success && g_pSurfaceSizeHook->hook(); if (success) HyprlandAPI::addNotification(PHANDLE, "[csgo-vulkan-fix] Initialized successfully! (Anything version)", CHyprColor{0.2, 1.0, 0.2, 1.0}, 5000); else { HyprlandAPI::addNotification(PHANDLE, "[csgo-vulkan-fix] Failure in initialization (hook failed)!", CHyprColor{1.0, 0.2, 0.2, 1.0}, 5000); throw std::runtime_error("[csgo-vk-fix] Hooks failed"); } return {"csgo-vulkan-fix", "A plugin to force specific apps to a fake resolution", "Vaxry", "1.2"}; } APICALL EXPORT void PLUGIN_EXIT() { ; } hyprwm-hyprland-plugins-4dbef35/csgo-vulkan-fix/meson.build000066400000000000000000000016271512451410000242120ustar00rootroot00000000000000project('csgo-vulkan-fix', 'cpp', version: '0.1', default_options: ['buildtype=release'], ) cpp_compiler = meson.get_compiler('cpp') if cpp_compiler.has_argument('-std=c++23') add_global_arguments('-std=c++23', language: 'cpp') elif cpp_compiler.has_argument('-std=c++2b') add_global_arguments('-std=c++2b', language: 'cpp') else error('Could not configure current C++ compiler (' + cpp_compiler.get_id() + ' ' + cpp_compiler.version() + ') with required C++ standard (C++23)') endif globber = run_command('find', '.', '-name', '*.cpp', check: true) src = globber.stdout().strip().split('\n') shared_module(meson.project_name(), src, dependencies: [ dependency('hyprland'), dependency('pixman-1'), dependency('libdrm'), dependency('pangocairo'), dependency('libinput'), dependency('libudev'), dependency('wayland-server'), dependency('xkbcommon'), ], install: true, ) hyprwm-hyprland-plugins-4dbef35/flake.lock000066400000000000000000000251351512451410000207670ustar00rootroot00000000000000{ "nodes": { "aquamarine": { "inputs": { "hyprutils": [ "hyprland", "hyprutils" ], "hyprwayland-scanner": [ "hyprland", "hyprwayland-scanner" ], "nixpkgs": [ "hyprland", "nixpkgs" ], "systems": [ "hyprland", "systems" ] }, "locked": { "lastModified": 1760101617, "narHash": "sha256-8jf/3ZCi+B7zYpIyV04+3wm72BD7Z801IlOzsOACR7I=", "owner": "hyprwm", "repo": "aquamarine", "rev": "1826a9923881320306231b1c2090379ebf9fa4f8", "type": "github" }, "original": { "owner": "hyprwm", "repo": "aquamarine", "type": "github" } }, "flake-compat": { "flake": false, "locked": { "lastModified": 1747046372, "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=", "owner": "edolstra", "repo": "flake-compat", "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", "type": "github" }, "original": { "owner": "edolstra", "repo": "flake-compat", "type": "github" } }, "gitignore": { "inputs": { "nixpkgs": [ "hyprland", "pre-commit-hooks", "nixpkgs" ] }, "locked": { "lastModified": 1709087332, "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", "owner": "hercules-ci", "repo": "gitignore.nix", "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", "type": "github" }, "original": { "owner": "hercules-ci", "repo": "gitignore.nix", "type": "github" } }, "hyprcursor": { "inputs": { "hyprlang": [ "hyprland", "hyprlang" ], "nixpkgs": [ "hyprland", "nixpkgs" ], "systems": [ "hyprland", "systems" ] }, "locked": { "lastModified": 1753964049, "narHash": "sha256-lIqabfBY7z/OANxHoPeIrDJrFyYy9jAM4GQLzZ2feCM=", "owner": "hyprwm", "repo": "hyprcursor", "rev": "44e91d467bdad8dcf8bbd2ac7cf49972540980a5", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprcursor", "type": "github" } }, "hyprgraphics": { "inputs": { "hyprutils": [ "hyprland", "hyprutils" ], "nixpkgs": [ "hyprland", "nixpkgs" ], "systems": [ "hyprland", "systems" ] }, "locked": { "lastModified": 1760445448, "narHash": "sha256-fXGjL6dw31FPFRrmIemzGiNSlfvEJTJNsmadZi+qNhI=", "owner": "hyprwm", "repo": "hyprgraphics", "rev": "50fb9f069219f338a11cf0bcccb9e58357d67757", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprgraphics", "type": "github" } }, "hyprland": { "inputs": { "aquamarine": "aquamarine", "hyprcursor": "hyprcursor", "hyprgraphics": "hyprgraphics", "hyprland-protocols": "hyprland-protocols", "hyprland-qtutils": "hyprland-qtutils", "hyprlang": "hyprlang", "hyprutils": "hyprutils", "hyprwayland-scanner": "hyprwayland-scanner", "nixpkgs": "nixpkgs", "pre-commit-hooks": "pre-commit-hooks", "systems": "systems", "xdph": "xdph" }, "locked": { "lastModified": 1761869718, "narHash": "sha256-jLfwwlPGpnGRAtVDyoGj9FgH2D9hWwyEu0yHkflG2EI=", "owner": "hyprwm", "repo": "Hyprland", "rev": "8e9add2afda58d233a75e4c5ce8503b24fa59ceb", "type": "github" }, "original": { "owner": "hyprwm", "repo": "Hyprland", "type": "github" } }, "hyprland-protocols": { "inputs": { "nixpkgs": [ "hyprland", "nixpkgs" ], "systems": [ "hyprland", "systems" ] }, "locked": { "lastModified": 1759610243, "narHash": "sha256-+KEVnKBe8wz+a6dTLq8YDcF3UrhQElwsYJaVaHXJtoI=", "owner": "hyprwm", "repo": "hyprland-protocols", "rev": "bd153e76f751f150a09328dbdeb5e4fab9d23622", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprland-protocols", "type": "github" } }, "hyprland-qt-support": { "inputs": { "hyprlang": [ "hyprland", "hyprland-qtutils", "hyprlang" ], "nixpkgs": [ "hyprland", "hyprland-qtutils", "nixpkgs" ], "systems": [ "hyprland", "hyprland-qtutils", "systems" ] }, "locked": { "lastModified": 1749154592, "narHash": "sha256-DO7z5CeT/ddSGDEnK9mAXm1qlGL47L3VAHLlLXoCjhE=", "owner": "hyprwm", "repo": "hyprland-qt-support", "rev": "4c8053c3c888138a30c3a6c45c2e45f5484f2074", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprland-qt-support", "type": "github" } }, "hyprland-qtutils": { "inputs": { "hyprland-qt-support": "hyprland-qt-support", "hyprlang": [ "hyprland", "hyprlang" ], "hyprutils": [ "hyprland", "hyprland-qtutils", "hyprlang", "hyprutils" ], "nixpkgs": [ "hyprland", "nixpkgs" ], "systems": [ "hyprland", "systems" ] }, "locked": { "lastModified": 1759080228, "narHash": "sha256-RgDoAja0T1hnF0pTc56xPfLfFOO8Utol2iITwYbUhTk=", "owner": "hyprwm", "repo": "hyprland-qtutils", "rev": "629b15c19fa4082e4ce6be09fdb89e8c3312aed7", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprland-qtutils", "type": "github" } }, "hyprlang": { "inputs": { "hyprutils": [ "hyprland", "hyprutils" ], "nixpkgs": [ "hyprland", "nixpkgs" ], "systems": [ "hyprland", "systems" ] }, "locked": { "lastModified": 1758927902, "narHash": "sha256-LZgMds7M94+vuMql2bERQ6LiFFdhgsEFezE4Vn+Ys3A=", "owner": "hyprwm", "repo": "hyprlang", "rev": "4dafa28d4f79877d67a7d1a654cddccf8ebf15da", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprlang", "type": "github" } }, "hyprutils": { "inputs": { "nixpkgs": [ "hyprland", "nixpkgs" ], "systems": [ "hyprland", "systems" ] }, "locked": { "lastModified": 1759619523, "narHash": "sha256-r1ed7AR2ZEb2U8gy321/Xcp1ho2tzn+gG1te/Wxsj1A=", "owner": "hyprwm", "repo": "hyprutils", "rev": "3df7bde01efb3a3e8e678d1155f2aa3f19e177ef", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprutils", "type": "github" } }, "hyprwayland-scanner": { "inputs": { "nixpkgs": [ "hyprland", "nixpkgs" ], "systems": [ "hyprland", "systems" ] }, "locked": { "lastModified": 1755184602, "narHash": "sha256-RCBQN8xuADB0LEgaKbfRqwm6CdyopE1xIEhNc67FAbw=", "owner": "hyprwm", "repo": "hyprwayland-scanner", "rev": "b3b0f1f40ae09d4447c20608e5a4faf8bf3c492d", "type": "github" }, "original": { "owner": "hyprwm", "repo": "hyprwayland-scanner", "type": "github" } }, "nixpkgs": { "locked": { "lastModified": 1761114652, "narHash": "sha256-f/QCJM/YhrV/lavyCVz8iU3rlZun6d+dAiC3H+CDle4=", "owner": "NixOS", "repo": "nixpkgs", "rev": "01f116e4df6a15f4ccdffb1bcd41096869fb385c", "type": "github" }, "original": { "owner": "NixOS", "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } }, "pre-commit-hooks": { "inputs": { "flake-compat": "flake-compat", "gitignore": "gitignore", "nixpkgs": [ "hyprland", "nixpkgs" ] }, "locked": { "lastModified": 1760663237, "narHash": "sha256-BflA6U4AM1bzuRMR8QqzPXqh8sWVCNDzOdsxXEguJIc=", "owner": "cachix", "repo": "git-hooks.nix", "rev": "ca5b894d3e3e151ffc1db040b6ce4dcc75d31c37", "type": "github" }, "original": { "owner": "cachix", "repo": "git-hooks.nix", "type": "github" } }, "root": { "inputs": { "hyprland": "hyprland", "nixpkgs": [ "hyprland", "nixpkgs" ], "systems": [ "hyprland", "systems" ] } }, "systems": { "locked": { "lastModified": 1689347949, "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=", "owner": "nix-systems", "repo": "default-linux", "rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68", "type": "github" }, "original": { "owner": "nix-systems", "repo": "default-linux", "type": "github" } }, "xdph": { "inputs": { "hyprland-protocols": [ "hyprland", "hyprland-protocols" ], "hyprlang": [ "hyprland", "hyprlang" ], "hyprutils": [ "hyprland", "hyprutils" ], "hyprwayland-scanner": [ "hyprland", "hyprwayland-scanner" ], "nixpkgs": [ "hyprland", "nixpkgs" ], "systems": [ "hyprland", "systems" ] }, "locked": { "lastModified": 1760713634, "narHash": "sha256-5HXelmz2x/uO26lvW7MudnadbAfoBnve4tRBiDVLtOM=", "owner": "hyprwm", "repo": "xdg-desktop-portal-hyprland", "rev": "753bbbdf6a052994da94062e5b753288cef28dfb", "type": "github" }, "original": { "owner": "hyprwm", "repo": "xdg-desktop-portal-hyprland", "type": "github" } } }, "root": "root", "version": 7 } hyprwm-hyprland-plugins-4dbef35/flake.nix000066400000000000000000000040201512451410000206230ustar00rootroot00000000000000{ description = "Official Hyprland Plugins"; inputs = { hyprland.url = "github:hyprwm/Hyprland"; nixpkgs.follows = "hyprland/nixpkgs"; systems.follows = "hyprland/systems"; }; outputs = { self, hyprland, nixpkgs, systems, ... }: let inherit (nixpkgs) lib; eachSystem = lib.genAttrs (import systems); pkgsFor = eachSystem (system: import nixpkgs { localSystem.system = system; overlays = [ self.overlays.hyprland-plugins hyprland.overlays.hyprland-packages ]; }); in { packages = eachSystem (system: { inherit (pkgsFor.${system}.hyprlandPlugins) borders-plus-plus csgo-vulkan-fix hyprbars hyprexpo hyprfocus hyprscrolling hyprtrails hyprwinwrap xtra-dispatchers ; }); overlays = { default = self.overlays.hyprland-plugins; hyprland-plugins = final: prev: let inherit (final) callPackage; in { hyprlandPlugins = (prev.hyprlandPlugins or {}) // { borders-plus-plus = callPackage ./borders-plus-plus {}; csgo-vulkan-fix = callPackage ./csgo-vulkan-fix {}; hyprbars = callPackage ./hyprbars {}; hyprexpo = callPackage ./hyprexpo {}; hyprfocus = callPackage ./hyprfocus {}; hyprscrolling = callPackage ./hyprscrolling {}; hyprtrails = callPackage ./hyprtrails {}; hyprwinwrap = callPackage ./hyprwinwrap {}; xtra-dispatchers = callPackage ./xtra-dispatchers {}; }; }; }; checks = eachSystem (system: self.packages.${system}); devShells = eachSystem (system: with pkgsFor.${system}; { default = mkShell.override {stdenv = gcc14Stdenv;} { name = "hyprland-plugins"; buildInputs = [hyprland.packages.${system}.hyprland]; inputsFrom = [hyprland.packages.${system}.hyprland]; }; }); }; } hyprwm-hyprland-plugins-4dbef35/hyprbars/000077500000000000000000000000001512451410000206575ustar00rootroot00000000000000hyprwm-hyprland-plugins-4dbef35/hyprbars/BarPassElement.cpp000066400000000000000000000025231512451410000242320ustar00rootroot00000000000000#include "BarPassElement.hpp" #include #include "barDeco.hpp" CBarPassElement::CBarPassElement(const CBarPassElement::SBarData& data_) : data(data_) { ; } void CBarPassElement::draw(const CRegion& damage) { data.deco->renderPass(g_pHyprOpenGL->m_renderData.pMonitor.lock(), data.a); } bool CBarPassElement::needsLiveBlur() { static auto* const PCOLOR = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_color")->getDataStaticPtr(); static auto* const PENABLEBLUR = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_blur")->getDataStaticPtr(); static auto* const PENABLEBLURGLOBAL = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "decoration:blur:enabled")->getDataStaticPtr(); CHyprColor color = data.deco->m_bForcedBarColor.value_or(**PCOLOR); color.a *= data.a; const bool SHOULDBLUR = **PENABLEBLUR && **PENABLEBLURGLOBAL && color.a < 1.F; return SHOULDBLUR; } std::optional CBarPassElement::boundingBox() { // Temporary fix: expand the bar bb a bit, otherwise occlusion gets too aggressive. return data.deco->assignedBoxGlobal().translate(-g_pHyprOpenGL->m_renderData.pMonitor->m_position).expand(10); } bool CBarPassElement::needsPrecomputeBlur() { return false; }hyprwm-hyprland-plugins-4dbef35/hyprbars/BarPassElement.hpp000066400000000000000000000012131512451410000242320ustar00rootroot00000000000000#pragma once #include class CHyprBar; class CBarPassElement : public IPassElement { public: struct SBarData { CHyprBar* deco = nullptr; float a = 1.F; }; CBarPassElement(const SBarData& data_); virtual ~CBarPassElement() = default; virtual void draw(const CRegion& damage); virtual bool needsLiveBlur(); virtual bool needsPrecomputeBlur(); virtual std::optional boundingBox(); virtual const char* passName() { return "CBarPassElement"; } private: SBarData data; };hyprwm-hyprland-plugins-4dbef35/hyprbars/CMakeLists.txt000066400000000000000000000007611512451410000234230ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.27) project(hyprbars DESCRIPTION "hyprbars plugin for Hyprland" VERSION 0.1 ) set(CMAKE_CXX_STANDARD 23) file(GLOB_RECURSE SRC "*.cpp") add_library(hyprbars SHARED ${SRC}) find_package(PkgConfig REQUIRED) pkg_check_modules(deps REQUIRED IMPORTED_TARGET hyprland libdrm libinput libudev pangocairo pixman-1 wayland-server xkbcommon ) target_link_libraries(hyprbars PRIVATE rt PkgConfig::deps) install(TARGETS hyprbars) hyprwm-hyprland-plugins-4dbef35/hyprbars/Makefile000066400000000000000000000011511512451410000223150ustar00rootroot00000000000000# Else exist specifically for clang ifeq ($(CXX),g++) EXTRA_FLAGS = --no-gnu-unique else EXTRA_FLAGS = endif CXXFLAGS = -shared -fPIC -g -std=c++2b -Wno-c++11-narrowing INCLUDES = `pkg-config --cflags pixman-1 libdrm hyprland pangocairo libinput libudev wayland-server xkbcommon` LIBS = `pkg-config --libs pangocairo` SRC = main.cpp barDeco.cpp BarPassElement.cpp TARGET = hyprbars.so all: $(TARGET) $(TARGET): $(SRC) $(CXX) $(CXXFLAGS) $(EXTRA_FLAGS) $(INCLUDES) $^ $> -o $@ $(LIBS) -O2 clean: rm ./$(TARGET) meson-build: mkdir -p build cd build && meson .. && ninja .PHONY: all meson-build clean hyprwm-hyprland-plugins-4dbef35/hyprbars/README.md000066400000000000000000000050641512451410000221430ustar00rootroot00000000000000# hyprbars Adds simple title bars to windows. ![image](https://github.com/user-attachments/assets/184a66b9-eb91-4f6f-8953-b265a2735939) ## Config All config options are in `plugin:hyprbars`: ``` plugin { hyprbars { # example config bar_height = 20 # example buttons (R -> L) # hyprbars-button = color, size, on-click hyprbars-button = rgb(ff4040), 10, 󰖭, hyprctl dispatch killactive hyprbars-button = rgb(eeee11), 10, , hyprctl dispatch fullscreen 1 # cmd to run on double click of the bar on_double_click = hyprctl dispatch fullscreen 1 } } ``` | property | type | description | default | | --- | --- | --- | --- | `enabled` | bool | whether to enable the bars | `bar_color` | color | bar's background color `bar_height` | int | bar's height | `15` `bar_blur` | bool | whether to blur the bar. Also requires the global blur to be enabled. `col.text` | color | bar's title text color `bar_title_enabled` | bool | whether to render the title | `true` `bar_text_size` | int | bar's title text font size | `10` `bar_text_font` | str | bar's title text font | `Sans` `bar_text_align` | left, center | bar's title text alignment | `center` `bar_buttons_alignment` | right, left | bar's buttons alignment | `right` `bar_part_of_window` | bool | whether the bar is a part of the main window (if it is, stuff like shadows render around it) `bar_precedence_over_border` | bool | whether the bar should have a higher priority than the border (border will be around the bar) `bar_padding` | int | left / right edge padding | `7` `bar_button_padding` | int | padding between the buttons | `5` `icon_on_hover` | bool | whether the icons show on mouse hovering over the buttons | `false` `inactive_button_color` | col | buttons bg color when window isn't focused `on_double_click` | str | command to run on double click of the bar (not on a button) ## Buttons Config Use the `hyprbars-button` keyword. ```ini hyprbars-button = bgcolor, size, icon, on-click, fgcolor ``` Please note it _has_ to be inside `plugin { hyprbars { } }`. ## Window rules Hyprbars supports the following _dynamic_ [window rules](https://wiki.hypr.land/Configuring/Window-Rules/): `hyprbars:no_bar` -> disables the bar on matching windows. `hyprbars:bar_color` -> sets the bar background color on matching windows. `hyprbars:title_color` -> sets the bar title color on matching windows. Example: ```bash # Sets the bar color in red for all windows that have 'myClass' as a class windowrule = plugin:hyprbars:bar_color rgb(ff0000), class:^(myClass) ``` hyprwm-hyprland-plugins-4dbef35/hyprbars/barDeco.cpp000066400000000000000000001077071512451410000227360ustar00rootroot00000000000000#include "barDeco.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include "globals.hpp" #include "BarPassElement.hpp" CHyprBar::CHyprBar(PHLWINDOW pWindow) : IHyprWindowDecoration(pWindow) { m_pWindow = pWindow; static auto* const PCOLOR = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_color")->getDataStaticPtr(); const auto PMONITOR = pWindow->m_monitor.lock(); PMONITOR->m_scheduledRecalc = true; //button events m_pMouseButtonCallback = HyprlandAPI::registerCallbackDynamic( PHANDLE, "mouseButton", [&](void* self, SCallbackInfo& info, std::any param) { onMouseButton(info, std::any_cast(param)); }); m_pTouchDownCallback = HyprlandAPI::registerCallbackDynamic( PHANDLE, "touchDown", [&](void* self, SCallbackInfo& info, std::any param) { onTouchDown(info, std::any_cast(param)); }); m_pTouchUpCallback = HyprlandAPI::registerCallbackDynamic( // PHANDLE, "touchUp", [&](void* self, SCallbackInfo& info, std::any param) { onTouchUp(info, std::any_cast(param)); }); //move events m_pTouchMoveCallback = HyprlandAPI::registerCallbackDynamic( PHANDLE, "touchMove", [&](void* self, SCallbackInfo& info, std::any param) { onTouchMove(info, std::any_cast(param)); }); m_pMouseMoveCallback = HyprlandAPI::registerCallbackDynamic( // PHANDLE, "mouseMove", [&](void* self, SCallbackInfo& info, std::any param) { onMouseMove(std::any_cast(param)); }); m_pTextTex = makeShared(); m_pButtonsTex = makeShared(); g_pAnimationManager->createAnimation(CHyprColor{**PCOLOR}, m_cRealBarColor, g_pConfigManager->getAnimationPropertyConfig("border"), pWindow, AVARDAMAGE_NONE); m_cRealBarColor->setUpdateCallback([&](auto) { damageEntire(); }); } CHyprBar::~CHyprBar() { HyprlandAPI::unregisterCallback(PHANDLE, m_pMouseButtonCallback); HyprlandAPI::unregisterCallback(PHANDLE, m_pTouchDownCallback); HyprlandAPI::unregisterCallback(PHANDLE, m_pTouchUpCallback); HyprlandAPI::unregisterCallback(PHANDLE, m_pTouchMoveCallback); HyprlandAPI::unregisterCallback(PHANDLE, m_pMouseMoveCallback); std::erase(g_pGlobalState->bars, m_self); } SDecorationPositioningInfo CHyprBar::getPositioningInfo() { static auto* const PHEIGHT = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_height")->getDataStaticPtr(); static auto* const PENABLED = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:enabled")->getDataStaticPtr(); static auto* const PPRECEDENCE = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_precedence_over_border")->getDataStaticPtr(); SDecorationPositioningInfo info; info.policy = m_hidden ? DECORATION_POSITION_ABSOLUTE : DECORATION_POSITION_STICKY; info.edges = DECORATION_EDGE_TOP; info.priority = **PPRECEDENCE ? 10005 : 5000; info.reserved = true; info.desiredExtents = {{0, m_hidden || !**PENABLED ? 0 : **PHEIGHT}, {0, 0}}; return info; } void CHyprBar::onPositioningReply(const SDecorationPositioningReply& reply) { if (reply.assignedGeometry.size() != m_bAssignedBox.size()) m_bWindowSizeChanged = true; m_bAssignedBox = reply.assignedGeometry; } std::string CHyprBar::getDisplayName() { return "Hyprbar"; } bool CHyprBar::inputIsValid() { static auto* const PENABLED = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:enabled")->getDataStaticPtr(); if (!**PENABLED) return false; if (!m_pWindow->m_workspace || !m_pWindow->m_workspace->isVisible() || !g_pInputManager->m_exclusiveLSes.empty() || (g_pSeatManager->m_seatGrab && !g_pSeatManager->m_seatGrab->accepts(m_pWindow->wlSurface()->resource()))) return false; const auto WINDOWATCURSOR = g_pCompositor->vectorToWindowUnified(g_pInputManager->getMouseCoordsInternal(), Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING); auto focusState = Desktop::focusState(); auto window = focusState->window(); auto monitor = focusState->monitor(); if (WINDOWATCURSOR != m_pWindow && m_pWindow != window) return false; // check if input is on top or overlay shell layers auto PMONITOR = monitor; PHLLS foundSurface = nullptr; Vector2D surfaceCoords; // check top layer g_pCompositor->vectorToLayerSurface(g_pInputManager->getMouseCoordsInternal(), &PMONITOR->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], &surfaceCoords, &foundSurface); if (foundSurface) return false; // check overlay layer g_pCompositor->vectorToLayerSurface(g_pInputManager->getMouseCoordsInternal(), &PMONITOR->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], &surfaceCoords, &foundSurface); if (foundSurface) return false; return true; } void CHyprBar::onMouseButton(SCallbackInfo& info, IPointer::SButtonEvent e) { if (!inputIsValid()) return; if (e.state != WL_POINTER_BUTTON_STATE_PRESSED) { handleUpEvent(info); return; } handleDownEvent(info, std::nullopt); } void CHyprBar::onTouchDown(SCallbackInfo& info, ITouch::SDownEvent e) { // Don't do anything if you're already grabbed a window with another finger if (!inputIsValid() || e.touchID != 0) return; handleDownEvent(info, e); } void CHyprBar::onTouchUp(SCallbackInfo& info, ITouch::SUpEvent e) { if (!m_bDragPending || !m_bTouchEv || e.touchID != m_touchId) return; handleUpEvent(info); } void CHyprBar::onMouseMove(Vector2D coords) { // ensure proper redraws of button icons on hover when using hardware cursors static auto* const PICONONHOVER = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:icon_on_hover")->getDataStaticPtr(); if (**PICONONHOVER) damageOnButtonHover(); if (!m_bDragPending || m_bTouchEv || !validMapped(m_pWindow) || m_touchId != 0) return; m_bDragPending = false; handleMovement(); } void CHyprBar::onTouchMove(SCallbackInfo& info, ITouch::SMotionEvent e) { if (!m_bDragPending || !m_bTouchEv || !validMapped(m_pWindow) || e.touchID != m_touchId) return; auto PMONITOR = m_pWindow->m_monitor.lock(); PMONITOR = PMONITOR ? PMONITOR : Desktop::focusState()->monitor(); const auto COORDS = Vector2D(PMONITOR->m_position.x + e.pos.x * PMONITOR->m_size.x, PMONITOR->m_position.y + e.pos.y * PMONITOR->m_size.y); if (!m_bDraggingThis) { // Initial setup for dragging a window. g_pKeybindManager->m_dispatchers["setfloating"]("activewindow"); g_pKeybindManager->m_dispatchers["resizewindowpixel"]("exact 50% 50%,activewindow"); // pin it so you can change workspaces while dragging a window g_pKeybindManager->m_dispatchers["pin"]("activewindow"); } g_pKeybindManager->m_dispatchers["movewindowpixel"](std::format("exact {} {},activewindow", (int)(COORDS.x - (assignedBoxGlobal().w / 2)), (int)COORDS.y)); m_bDraggingThis = true; } void CHyprBar::handleDownEvent(SCallbackInfo& info, std::optional touchEvent) { m_bTouchEv = touchEvent.has_value(); if (m_bTouchEv) m_touchId = touchEvent.value().touchID; const auto PWINDOW = m_pWindow.lock(); auto COORDS = cursorRelativeToBar(); if (m_bTouchEv) { ITouch::SDownEvent e = touchEvent.value(); auto PMONITOR = g_pCompositor->getMonitorFromName(!e.device->m_boundOutput.empty() ? e.device->m_boundOutput : ""); PMONITOR = PMONITOR ? PMONITOR : Desktop::focusState()->monitor(); COORDS = Vector2D(PMONITOR->m_position.x + e.pos.x * PMONITOR->m_size.x, PMONITOR->m_position.y + e.pos.y * PMONITOR->m_size.y) - assignedBoxGlobal().pos(); } static auto* const PHEIGHT = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_height")->getDataStaticPtr(); static auto* const PBARBUTTONPADDING = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_button_padding")->getDataStaticPtr(); static auto* const PBARPADDING = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_padding")->getDataStaticPtr(); static auto* const PALIGNBUTTONS = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_buttons_alignment")->getDataStaticPtr(); static auto* const PONDOUBLECLICK = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:on_double_click")->getDataStaticPtr(); const bool BUTTONSRIGHT = std::string{*PALIGNBUTTONS} != "left"; const std::string ON_DOUBLE_CLICK = *PONDOUBLECLICK; if (!VECINRECT(COORDS, 0, 0, assignedBoxGlobal().w, **PHEIGHT - 1)) { if (m_bDraggingThis) { if (m_bTouchEv) g_pKeybindManager->m_dispatchers["settiled"]("activewindow"); g_pKeybindManager->m_dispatchers["mouse"]("0movewindow"); Log::logger->log(Log::DEBUG, "[hyprbars] Dragging ended on {:x}", (uintptr_t)PWINDOW.get()); } m_bDraggingThis = false; m_bDragPending = false; m_bTouchEv = false; return; } if (Desktop::focusState()->window() != PWINDOW) Desktop::focusState()->fullWindowFocus(PWINDOW); if (PWINDOW->m_isFloating) g_pCompositor->changeWindowZOrder(PWINDOW, true); info.cancelled = true; m_bCancelledDown = true; if (doButtonPress(PBARPADDING, PBARBUTTONPADDING, PHEIGHT, COORDS, BUTTONSRIGHT)) return; if (!ON_DOUBLE_CLICK.empty() && std::chrono::duration_cast(Time::steadyNow() - m_lastMouseDown).count() < 400 /* Arbitrary delay I found suitable */) { g_pKeybindManager->m_dispatchers["exec"](ON_DOUBLE_CLICK); m_bDragPending = false; } else { m_lastMouseDown = Time::steadyNow(); m_bDragPending = true; } } void CHyprBar::handleUpEvent(SCallbackInfo& info) { if (m_pWindow.lock() != Desktop::focusState()->window()) return; if (m_bCancelledDown) info.cancelled = true; m_bCancelledDown = false; if (m_bDraggingThis) { g_pKeybindManager->m_dispatchers["mouse"]("0movewindow"); m_bDraggingThis = false; if (m_bTouchEv) g_pKeybindManager->m_dispatchers["settiled"]("activewindow"); Log::logger->log(Log::DEBUG, "[hyprbars] Dragging ended on {:x}", (uintptr_t)m_pWindow.lock().get()); } m_bDragPending = false; m_bTouchEv = false; m_touchId = 0; } void CHyprBar::handleMovement() { g_pKeybindManager->m_dispatchers["mouse"]("1movewindow"); m_bDraggingThis = true; Log::logger->log(Log::DEBUG, "[hyprbars] Dragging initiated on {:x}", (uintptr_t)m_pWindow.lock().get()); return; } bool CHyprBar::doButtonPress(Hyprlang::INT* const* PBARPADDING, Hyprlang::INT* const* PBARBUTTONPADDING, Hyprlang::INT* const* PHEIGHT, Vector2D COORDS, const bool BUTTONSRIGHT) { //check if on a button float offset = **PBARPADDING; for (auto& b : g_pGlobalState->buttons) { const auto BARBUF = Vector2D{(int)assignedBoxGlobal().w, **PHEIGHT}; Vector2D currentPos = Vector2D{(BUTTONSRIGHT ? BARBUF.x - **PBARBUTTONPADDING - b.size - offset : offset), (BARBUF.y - b.size) / 2.0}.floor(); if (VECINRECT(COORDS, currentPos.x, currentPos.y, currentPos.x + b.size + **PBARBUTTONPADDING, currentPos.y + b.size)) { // hit on close g_pKeybindManager->m_dispatchers["exec"](b.cmd); return true; } offset += **PBARBUTTONPADDING + b.size; } return false; } void CHyprBar::renderText(SP out, const std::string& text, const CHyprColor& color, const Vector2D& bufferSize, const float scale, const int fontSize) { const auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, bufferSize.x, bufferSize.y); const auto CAIRO = cairo_create(CAIROSURFACE); // clear the pixmap cairo_save(CAIRO); cairo_set_operator(CAIRO, CAIRO_OPERATOR_CLEAR); cairo_paint(CAIRO); cairo_restore(CAIRO); // draw title using Pango PangoLayout* layout = pango_cairo_create_layout(CAIRO); pango_layout_set_text(layout, text.c_str(), -1); PangoFontDescription* fontDesc = pango_font_description_from_string("sans"); pango_font_description_set_size(fontDesc, fontSize * scale * PANGO_SCALE); pango_layout_set_font_description(layout, fontDesc); pango_font_description_free(fontDesc); const int maxWidth = bufferSize.x; pango_layout_set_width(layout, maxWidth * PANGO_SCALE); pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_NONE); cairo_set_source_rgba(CAIRO, color.r, color.g, color.b, color.a); PangoRectangle ink_rect, logical_rect; pango_layout_get_extents(layout, &ink_rect, &logical_rect); const int layoutWidth = ink_rect.width; const int layoutHeight = logical_rect.height; const double xOffset = (bufferSize.x / 2.0 - layoutWidth / PANGO_SCALE / 2.0); const double yOffset = (bufferSize.y / 2.0 - layoutHeight / PANGO_SCALE / 2.0); cairo_move_to(CAIRO, xOffset, yOffset); pango_cairo_show_layout(CAIRO, layout); g_object_unref(layout); cairo_surface_flush(CAIROSURFACE); // copy the data to an OpenGL texture we have const auto DATA = cairo_image_surface_get_data(CAIROSURFACE); out->allocate(); glBindTexture(GL_TEXTURE_2D, out->m_texID); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); #ifndef GLES2 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED); #endif glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bufferSize.x, bufferSize.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA); // delete cairo cairo_destroy(CAIRO); cairo_surface_destroy(CAIROSURFACE); } void CHyprBar::renderBarTitle(const Vector2D& bufferSize, const float scale) { static auto* const PCOLOR = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:col.text")->getDataStaticPtr(); static auto* const PSIZE = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_text_size")->getDataStaticPtr(); static auto* const PFONT = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_text_font")->getDataStaticPtr(); static auto* const PALIGN = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_text_align")->getDataStaticPtr(); static auto* const PALIGNBUTTONS = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_buttons_alignment")->getDataStaticPtr(); static auto* const PBARPADDING = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_padding")->getDataStaticPtr(); static auto* const PBARBUTTONPADDING = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_button_padding")->getDataStaticPtr(); const bool BUTTONSRIGHT = std::string{*PALIGNBUTTONS} != "left"; const auto PWINDOW = m_pWindow.lock(); const auto BORDERSIZE = PWINDOW->getRealBorderSize(); float buttonSizes = **PBARBUTTONPADDING; for (auto& b : g_pGlobalState->buttons) { buttonSizes += b.size + **PBARBUTTONPADDING; } const auto scaledSize = **PSIZE * scale; const auto scaledBorderSize = BORDERSIZE * scale; const auto scaledButtonsSize = buttonSizes * scale; const auto scaledButtonsPad = **PBARBUTTONPADDING * scale; const auto scaledBarPadding = **PBARPADDING * scale; const CHyprColor COLOR = m_bForcedTitleColor.value_or(**PCOLOR); const auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, bufferSize.x, bufferSize.y); const auto CAIRO = cairo_create(CAIROSURFACE); // clear the pixmap cairo_save(CAIRO); cairo_set_operator(CAIRO, CAIRO_OPERATOR_CLEAR); cairo_paint(CAIRO); cairo_restore(CAIRO); // draw title using Pango PangoLayout* layout = pango_cairo_create_layout(CAIRO); pango_layout_set_text(layout, m_szLastTitle.c_str(), -1); PangoFontDescription* fontDesc = pango_font_description_from_string(*PFONT); pango_font_description_set_size(fontDesc, scaledSize * PANGO_SCALE); pango_layout_set_font_description(layout, fontDesc); pango_font_description_free(fontDesc); PangoContext* context = pango_layout_get_context(layout); pango_context_set_base_dir(context, PANGO_DIRECTION_NEUTRAL); const int paddingTotal = scaledBarPadding * 2 + scaledButtonsSize + (std::string{*PALIGN} != "left" ? scaledButtonsSize : 0); const int maxWidth = std::clamp(static_cast(bufferSize.x - paddingTotal), 0, INT_MAX); pango_layout_set_width(layout, maxWidth * PANGO_SCALE); pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); cairo_set_source_rgba(CAIRO, COLOR.r, COLOR.g, COLOR.b, COLOR.a); int layoutWidth, layoutHeight; pango_layout_get_size(layout, &layoutWidth, &layoutHeight); const int xOffset = std::string{*PALIGN} == "left" ? std::round(scaledBarPadding + (BUTTONSRIGHT ? 0 : scaledButtonsSize)) : std::round(((bufferSize.x - scaledBorderSize) / 2.0 - layoutWidth / PANGO_SCALE / 2.0)); const int yOffset = std::round((bufferSize.y / 2.0 - layoutHeight / PANGO_SCALE / 2.0)); cairo_move_to(CAIRO, xOffset, yOffset); pango_cairo_show_layout(CAIRO, layout); g_object_unref(layout); cairo_surface_flush(CAIROSURFACE); // copy the data to an OpenGL texture we have const auto DATA = cairo_image_surface_get_data(CAIROSURFACE); m_pTextTex->allocate(); glBindTexture(GL_TEXTURE_2D, m_pTextTex->m_texID); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); #ifndef GLES2 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED); #endif glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bufferSize.x, bufferSize.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA); // delete cairo cairo_destroy(CAIRO); cairo_surface_destroy(CAIROSURFACE); } size_t CHyprBar::getVisibleButtonCount(Hyprlang::INT* const* PBARBUTTONPADDING, Hyprlang::INT* const* PBARPADDING, const Vector2D& bufferSize, const float scale) { float availableSpace = bufferSize.x - **PBARPADDING * scale * 2; size_t count = 0; for (const auto& button : g_pGlobalState->buttons) { const float buttonSpace = (button.size + **PBARBUTTONPADDING) * scale; if (availableSpace >= buttonSpace) { count++; availableSpace -= buttonSpace; } else break; } return count; } void CHyprBar::renderBarButtons(const Vector2D& bufferSize, const float scale) { static auto* const PBARBUTTONPADDING = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_button_padding")->getDataStaticPtr(); static auto* const PBARPADDING = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_padding")->getDataStaticPtr(); static auto* const PALIGNBUTTONS = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_buttons_alignment")->getDataStaticPtr(); static auto* const PINACTIVECOLOR = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:inactive_button_color")->getDataStaticPtr(); const bool BUTTONSRIGHT = std::string{*PALIGNBUTTONS} != "left"; const auto visibleCount = getVisibleButtonCount(PBARBUTTONPADDING, PBARPADDING, bufferSize, scale); const auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, bufferSize.x, bufferSize.y); const auto CAIRO = cairo_create(CAIROSURFACE); // clear the pixmap cairo_save(CAIRO); cairo_set_operator(CAIRO, CAIRO_OPERATOR_CLEAR); cairo_paint(CAIRO); cairo_restore(CAIRO); // draw buttons int offset = **PBARPADDING * scale; for (size_t i = 0; i < visibleCount; ++i) { const auto& button = g_pGlobalState->buttons[i]; const auto scaledButtonSize = button.size * scale; const auto scaledButtonsPad = **PBARBUTTONPADDING * scale; const auto pos = Vector2D{BUTTONSRIGHT ? bufferSize.x - offset - scaledButtonSize / 2.0 : offset + scaledButtonSize / 2.0, bufferSize.y / 2.0}.floor(); auto color = button.bgcol; if (**PINACTIVECOLOR > 0) { color = m_bWindowHasFocus ? color : CHyprColor(**PINACTIVECOLOR); if (button.userfg && button.iconTex->m_texID != 0) button.iconTex->destroyTexture(); } cairo_set_source_rgba(CAIRO, color.r, color.g, color.b, color.a); cairo_arc(CAIRO, pos.x, pos.y, scaledButtonSize / 2, 0, 2 * M_PI); cairo_fill(CAIRO); offset += scaledButtonsPad + scaledButtonSize; } // copy the data to an OpenGL texture we have const auto DATA = cairo_image_surface_get_data(CAIROSURFACE); m_pButtonsTex->allocate(); glBindTexture(GL_TEXTURE_2D, m_pButtonsTex->m_texID); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); #ifndef GLES2 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED); #endif glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bufferSize.x, bufferSize.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA); // delete cairo cairo_destroy(CAIRO); cairo_surface_destroy(CAIROSURFACE); } void CHyprBar::renderBarButtonsText(CBox* barBox, const float scale, const float a) { static auto* const PHEIGHT = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_height")->getDataStaticPtr(); static auto* const PBARBUTTONPADDING = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_button_padding")->getDataStaticPtr(); static auto* const PBARPADDING = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_padding")->getDataStaticPtr(); static auto* const PALIGNBUTTONS = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_buttons_alignment")->getDataStaticPtr(); static auto* const PICONONHOVER = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:icon_on_hover")->getDataStaticPtr(); const bool BUTTONSRIGHT = std::string{*PALIGNBUTTONS} != "left"; const auto visibleCount = getVisibleButtonCount(PBARBUTTONPADDING, PBARPADDING, Vector2D{barBox->w, barBox->h}, scale); const auto COORDS = cursorRelativeToBar(); int offset = **PBARPADDING * scale; float noScaleOffset = **PBARPADDING; for (size_t i = 0; i < visibleCount; ++i) { auto& button = g_pGlobalState->buttons[i]; const auto scaledButtonSize = button.size * scale; const auto scaledButtonsPad = **PBARBUTTONPADDING * scale; // check if hovering here const auto BARBUF = Vector2D{(int)assignedBoxGlobal().w, **PHEIGHT}; Vector2D currentPos = Vector2D{(BUTTONSRIGHT ? BARBUF.x - **PBARBUTTONPADDING - button.size - noScaleOffset : noScaleOffset), (BARBUF.y - button.size) / 2.0}.floor(); bool hovering = VECINRECT(COORDS, currentPos.x, currentPos.y, currentPos.x + button.size + **PBARBUTTONPADDING, currentPos.y + button.size); noScaleOffset += **PBARBUTTONPADDING + button.size; if (button.iconTex->m_texID == 0 /* icon is not rendered */ && !button.icon.empty()) { // render icon const Vector2D BUFSIZE = {scaledButtonSize, scaledButtonSize}; auto fgcol = button.userfg ? button.fgcol : (button.bgcol.r + button.bgcol.g + button.bgcol.b < 1) ? CHyprColor(0xFFFFFFFF) : CHyprColor(0xFF000000); renderText(button.iconTex, button.icon, fgcol, BUFSIZE, scale, button.size * 0.62); } if (button.iconTex->m_texID == 0) continue; CBox pos = {barBox->x + (BUTTONSRIGHT ? barBox->width - offset - scaledButtonSize : offset), barBox->y + (barBox->height - scaledButtonSize) / 2.0, scaledButtonSize, scaledButtonSize}; if (!**PICONONHOVER || (**PICONONHOVER && m_iButtonHoverState > 0)) g_pHyprOpenGL->renderTexture(button.iconTex, pos, {.a = a}); offset += scaledButtonsPad + scaledButtonSize; bool currentBit = (m_iButtonHoverState & (1 << i)) != 0; if (hovering != currentBit) { m_iButtonHoverState ^= (1 << i); // damage to get rid of some artifacts when icons are "hidden" damageEntire(); } } } void CHyprBar::draw(PHLMONITOR pMonitor, const float& a) { static auto* const PENABLED = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:enabled")->getDataStaticPtr(); if (m_bLastEnabledState != **PENABLED) { m_bLastEnabledState = **PENABLED; g_pDecorationPositioner->repositionDeco(this); } if (m_hidden || !validMapped(m_pWindow) || !**PENABLED) return; const auto PWINDOW = m_pWindow.lock(); if (!PWINDOW->m_ruleApplicator->decorate().valueOrDefault()) return; auto data = CBarPassElement::SBarData{this, a}; g_pHyprRenderer->m_renderPass.add(makeUnique(data)); } void CHyprBar::renderPass(PHLMONITOR pMonitor, const float& a) { const auto PWINDOW = m_pWindow.lock(); static auto* const PCOLOR = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_color")->getDataStaticPtr(); static auto* const PHEIGHT = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_height")->getDataStaticPtr(); static auto* const PPRECEDENCE = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_precedence_over_border")->getDataStaticPtr(); static auto* const PALIGNBUTTONS = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_buttons_alignment")->getDataStaticPtr(); static auto* const PENABLETITLE = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_title_enabled")->getDataStaticPtr(); static auto* const PENABLEBLUR = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_blur")->getDataStaticPtr(); static auto* const PENABLEBLURGLOBAL = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "decoration:blur:enabled")->getDataStaticPtr(); static auto* const PINACTIVECOLOR = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:inactive_button_color")->getDataStaticPtr(); if (**PINACTIVECOLOR > 0) { bool currentWindowFocus = PWINDOW == Desktop::focusState()->window(); if (currentWindowFocus != m_bWindowHasFocus) { m_bWindowHasFocus = currentWindowFocus; m_bButtonsDirty = true; } } const CHyprColor DEST_COLOR = m_bForcedBarColor.value_or(**PCOLOR); if (DEST_COLOR != m_cRealBarColor->goal()) *m_cRealBarColor = DEST_COLOR; CHyprColor color = m_cRealBarColor->value(); color.a *= a; const bool BUTTONSRIGHT = std::string{*PALIGNBUTTONS} != "left"; const bool SHOULDBLUR = **PENABLEBLUR && **PENABLEBLURGLOBAL && color.a < 1.F; if (**PHEIGHT < 1) { m_iLastHeight = **PHEIGHT; return; } const auto PWORKSPACE = PWINDOW->m_workspace; const auto WORKSPACEOFFSET = PWORKSPACE && !PWINDOW->m_pinned ? PWORKSPACE->m_renderOffset->value() : Vector2D(); const auto ROUNDING = PWINDOW->rounding() + (*PPRECEDENCE ? 0 : PWINDOW->getRealBorderSize()); const auto scaledRounding = ROUNDING > 0 ? ROUNDING * pMonitor->m_scale - 2 /* idk why but otherwise it looks bad due to the gaps */ : 0; m_seExtents = {{0, **PHEIGHT}, {}}; const auto DECOBOX = assignedBoxGlobal(); const auto BARBUF = DECOBOX.size() * pMonitor->m_scale; CBox titleBarBox = {DECOBOX.x - pMonitor->m_position.x, DECOBOX.y - pMonitor->m_position.y, DECOBOX.w, DECOBOX.h + ROUNDING * 3 /* to fill the bottom cuz we can't disable rounding there */}; titleBarBox.translate(PWINDOW->m_floatingOffset).scale(pMonitor->m_scale).round(); if (titleBarBox.w < 1 || titleBarBox.h < 1) return; g_pHyprOpenGL->scissor(titleBarBox); if (ROUNDING) { // the +1 is a shit garbage temp fix until renderRect supports an alpha matte CBox windowBox = {PWINDOW->m_realPosition->value().x + PWINDOW->m_floatingOffset.x - pMonitor->m_position.x + 1, PWINDOW->m_realPosition->value().y + PWINDOW->m_floatingOffset.y - pMonitor->m_position.y + 1, PWINDOW->m_realSize->value().x - 2, PWINDOW->m_realSize->value().y - 2}; if (windowBox.w < 1 || windowBox.h < 1) return; glClearStencil(0); glClear(GL_STENCIL_BUFFER_BIT); g_pHyprOpenGL->setCapStatus(GL_STENCIL_TEST, true); glStencilFunc(GL_ALWAYS, 1, -1); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); windowBox.translate(WORKSPACEOFFSET).scale(pMonitor->m_scale).round(); g_pHyprOpenGL->renderRect(windowBox, CHyprColor(0, 0, 0, 0), {.round = scaledRounding, .roundingPower = m_pWindow->roundingPower()}); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); glStencilFunc(GL_NOTEQUAL, 1, -1); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); } if (SHOULDBLUR) g_pHyprOpenGL->renderRect(titleBarBox, color, {.round = scaledRounding, .roundingPower = m_pWindow->roundingPower(), .blur = true, .blurA = a}); else g_pHyprOpenGL->renderRect(titleBarBox, color, {.round = scaledRounding, .roundingPower = m_pWindow->roundingPower()}); // render title if (**PENABLETITLE && (m_szLastTitle != PWINDOW->m_title || m_bWindowSizeChanged || m_pTextTex->m_texID == 0 || m_bTitleColorChanged)) { m_szLastTitle = PWINDOW->m_title; renderBarTitle(BARBUF, pMonitor->m_scale); } if (ROUNDING) { // cleanup stencil glClearStencil(0); glClear(GL_STENCIL_BUFFER_BIT); g_pHyprOpenGL->setCapStatus(GL_STENCIL_TEST, false); glStencilMask(-1); glStencilFunc(GL_ALWAYS, 1, 0xFF); } CBox textBox = {titleBarBox.x, titleBarBox.y, (int)BARBUF.x, (int)BARBUF.y}; if (**PENABLETITLE) g_pHyprOpenGL->renderTexture(m_pTextTex, textBox, {.a = a}); if (m_bButtonsDirty || m_bWindowSizeChanged) { renderBarButtons(BARBUF, pMonitor->m_scale); m_bButtonsDirty = false; } g_pHyprOpenGL->renderTexture(m_pButtonsTex, textBox, {.a = a}); g_pHyprOpenGL->scissor(nullptr); renderBarButtonsText(&textBox, pMonitor->m_scale, a); m_bWindowSizeChanged = false; m_bTitleColorChanged = false; // dynamic updates change the extents if (m_iLastHeight != **PHEIGHT) { g_pLayoutManager->getCurrentLayout()->recalculateWindow(PWINDOW); m_iLastHeight = **PHEIGHT; } } eDecorationType CHyprBar::getDecorationType() { return DECORATION_CUSTOM; } void CHyprBar::updateWindow(PHLWINDOW pWindow) { damageEntire(); } void CHyprBar::damageEntire() { g_pHyprRenderer->damageBox(assignedBoxGlobal()); } Vector2D CHyprBar::cursorRelativeToBar() { return g_pInputManager->getMouseCoordsInternal() - assignedBoxGlobal().pos(); } eDecorationLayer CHyprBar::getDecorationLayer() { return DECORATION_LAYER_UNDER; } uint64_t CHyprBar::getDecorationFlags() { static auto* const PPART = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_part_of_window")->getDataStaticPtr(); return DECORATION_ALLOWS_MOUSE_INPUT | (**PPART ? DECORATION_PART_OF_MAIN_WINDOW : 0); } CBox CHyprBar::assignedBoxGlobal() { if (!validMapped(m_pWindow)) return {}; CBox box = m_bAssignedBox; box.translate(g_pDecorationPositioner->getEdgeDefinedPoint(DECORATION_EDGE_TOP, m_pWindow.lock())); const auto PWORKSPACE = m_pWindow->m_workspace; const auto WORKSPACEOFFSET = PWORKSPACE && !m_pWindow->m_pinned ? PWORKSPACE->m_renderOffset->value() : Vector2D(); return box.translate(WORKSPACEOFFSET); } PHLWINDOW CHyprBar::getOwner() { return m_pWindow.lock(); } void CHyprBar::updateRules() { const auto PWINDOW = m_pWindow.lock(); auto prevHidden = m_hidden; auto prevForcedTitleColor = m_bForcedTitleColor; m_bForcedBarColor = std::nullopt; m_bForcedTitleColor = std::nullopt; m_hidden = false; if (PWINDOW->m_ruleApplicator->m_otherProps.props.contains(g_pGlobalState->nobarRuleIdx)) m_hidden = truthy(PWINDOW->m_ruleApplicator->m_otherProps.props.at(g_pGlobalState->nobarRuleIdx)->effect); if (PWINDOW->m_ruleApplicator->m_otherProps.props.contains(g_pGlobalState->barColorRuleIdx)) m_bForcedBarColor = CHyprColor(configStringToInt(PWINDOW->m_ruleApplicator->m_otherProps.props.at(g_pGlobalState->barColorRuleIdx)->effect).value_or(0)); if (PWINDOW->m_ruleApplicator->m_otherProps.props.contains(g_pGlobalState->titleColorRuleIdx)) m_bForcedTitleColor = CHyprColor(configStringToInt(PWINDOW->m_ruleApplicator->m_otherProps.props.at(g_pGlobalState->titleColorRuleIdx)->effect).value_or(0)); if (prevHidden != m_hidden) g_pDecorationPositioner->repositionDeco(this); if (prevForcedTitleColor != m_bForcedTitleColor) m_bTitleColorChanged = true; } void CHyprBar::damageOnButtonHover() { static auto* const PBARPADDING = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_padding")->getDataStaticPtr(); static auto* const PBARBUTTONPADDING = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_button_padding")->getDataStaticPtr(); static auto* const PHEIGHT = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_height")->getDataStaticPtr(); static auto* const PALIGNBUTTONS = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_buttons_alignment")->getDataStaticPtr(); const bool BUTTONSRIGHT = std::string{*PALIGNBUTTONS} != "left"; float offset = **PBARPADDING; const auto COORDS = cursorRelativeToBar(); for (auto& b : g_pGlobalState->buttons) { const auto BARBUF = Vector2D{(int)assignedBoxGlobal().w, **PHEIGHT}; Vector2D currentPos = Vector2D{(BUTTONSRIGHT ? BARBUF.x - **PBARBUTTONPADDING - b.size - offset : offset), (BARBUF.y - b.size) / 2.0}.floor(); bool hover = VECINRECT(COORDS, currentPos.x, currentPos.y, currentPos.x + b.size + **PBARBUTTONPADDING, currentPos.y + b.size); if (hover != m_bButtonHovered) { m_bButtonHovered = hover; damageEntire(); } offset += **PBARBUTTONPADDING + b.size; } } hyprwm-hyprland-plugins-4dbef35/hyprbars/barDeco.hpp000066400000000000000000000110651512451410000227320ustar00rootroot00000000000000#pragma once #define WLR_USE_UNSTABLE #include #include #include #include #include #include #include #include "globals.hpp" #define private public #include #undef private class CHyprBar : public IHyprWindowDecoration { public: CHyprBar(PHLWINDOW); virtual ~CHyprBar(); virtual SDecorationPositioningInfo getPositioningInfo(); virtual void onPositioningReply(const SDecorationPositioningReply& reply); virtual void draw(PHLMONITOR, float const& a); virtual eDecorationType getDecorationType(); virtual void updateWindow(PHLWINDOW); virtual void damageEntire(); virtual eDecorationLayer getDecorationLayer(); virtual uint64_t getDecorationFlags(); bool m_bButtonsDirty = true; virtual std::string getDisplayName(); PHLWINDOW getOwner(); void updateRules(); WP m_self; private: SBoxExtents m_seExtents; PHLWINDOWREF m_pWindow; CBox m_bAssignedBox; SP m_pTextTex; SP m_pButtonsTex; bool m_bWindowSizeChanged = false; bool m_hidden = false; bool m_bTitleColorChanged = false; bool m_bButtonHovered = false; bool m_bLastEnabledState = false; bool m_bWindowHasFocus = false; std::optional m_bForcedBarColor; std::optional m_bForcedTitleColor; Time::steady_tp m_lastMouseDown = Time::steadyNow(); PHLANIMVAR m_cRealBarColor; Vector2D cursorRelativeToBar(); void renderPass(PHLMONITOR, float const& a); void renderBarTitle(const Vector2D& bufferSize, const float scale); void renderText(SP out, const std::string& text, const CHyprColor& color, const Vector2D& bufferSize, const float scale, const int fontSize); void renderBarButtons(const Vector2D& bufferSize, const float scale); void renderBarButtonsText(CBox* barBox, const float scale, const float a); void damageOnButtonHover(); bool inputIsValid(); void onMouseButton(SCallbackInfo& info, IPointer::SButtonEvent e); void onTouchDown(SCallbackInfo& info, ITouch::SDownEvent e); void onTouchUp(SCallbackInfo& info, ITouch::SUpEvent e); void onMouseMove(Vector2D coords); void onTouchMove(SCallbackInfo& info, ITouch::SMotionEvent e); void handleDownEvent(SCallbackInfo& info, std::optional touchEvent); void handleUpEvent(SCallbackInfo& info); void handleMovement(); bool doButtonPress(Hyprlang::INT* const* PBARPADDING, Hyprlang::INT* const* PBARBUTTONPADDING, Hyprlang::INT* const* PHEIGHT, Vector2D COORDS, bool BUTTONSRIGHT); CBox assignedBoxGlobal(); SP m_pMouseButtonCallback; SP m_pTouchDownCallback; SP m_pTouchUpCallback; SP m_pTouchMoveCallback; SP m_pMouseMoveCallback; std::string m_szLastTitle; bool m_bDraggingThis = false; bool m_bTouchEv = false; bool m_bDragPending = false; bool m_bCancelledDown = false; int m_touchId = 0; // store hover state for buttons as a bitfield unsigned int m_iButtonHoverState = 0; // for dynamic updates int m_iLastHeight = 0; size_t getVisibleButtonCount(Hyprlang::INT* const* PBARBUTTONPADDING, Hyprlang::INT* const* PBARPADDING, const Vector2D& bufferSize, const float scale); friend class CBarPassElement; }; hyprwm-hyprland-plugins-4dbef35/hyprbars/default.nix000066400000000000000000000006171512451410000230270ustar00rootroot00000000000000{ lib, hyprland, hyprlandPlugins, }: hyprlandPlugins.mkHyprlandPlugin { pluginName = "hyprbars"; version = "0.1"; src = ./.; inherit (hyprland) nativeBuildInputs; meta = with lib; { homepage = "https://github.com/hyprwm/hyprland-plugins/tree/main/hyprbars"; description = "Hyprland window title plugin"; license = licenses.bsd3; platforms = platforms.linux; }; } hyprwm-hyprland-plugins-4dbef35/hyprbars/globals.hpp000066400000000000000000000013611512451410000230140ustar00rootroot00000000000000#pragma once #include #include inline HANDLE PHANDLE = nullptr; struct SHyprButton { std::string cmd = ""; bool userfg = false; CHyprColor fgcol = CHyprColor(0, 0, 0, 0); CHyprColor bgcol = CHyprColor(0, 0, 0, 0); float size = 10; std::string icon = ""; SP iconTex = makeShared(); }; class CHyprBar; struct SGlobalState { std::vector buttons; std::vector> bars; uint32_t nobarRuleIdx = 0; uint32_t barColorRuleIdx = 0; uint32_t titleColorRuleIdx = 0; }; inline UP g_pGlobalState; hyprwm-hyprland-plugins-4dbef35/hyprbars/main.cpp000066400000000000000000000164601512451410000223160ustar00rootroot00000000000000#define WLR_USE_UNSTABLE #include #include #include #include #include #include #include #include #include "barDeco.hpp" #include "globals.hpp" // Do NOT change this function. APICALL EXPORT std::string PLUGIN_API_VERSION() { return HYPRLAND_API_VERSION; } static void onNewWindow(void* self, std::any data) { // data is guaranteed const auto PWINDOW = std::any_cast(data); if (!PWINDOW->m_X11DoesntWantBorders) { if (std::ranges::any_of(PWINDOW->m_windowDecorations, [](const auto& d) { return d->getDisplayName() == "Hyprbar"; })) return; auto bar = makeUnique(PWINDOW); g_pGlobalState->bars.emplace_back(bar); bar->m_self = bar; HyprlandAPI::addWindowDecoration(PHANDLE, PWINDOW, std::move(bar)); } } static void onCloseWindow(void* self, std::any data) { // data is guaranteed const auto PWINDOW = std::any_cast(data); const auto BARIT = std::find_if(g_pGlobalState->bars.begin(), g_pGlobalState->bars.end(), [PWINDOW](const auto& bar) { return bar->getOwner() == PWINDOW; }); if (BARIT == g_pGlobalState->bars.end()) return; // we could use the API but this is faster + it doesn't matter here that much. PWINDOW->removeWindowDeco(BARIT->get()); } static void onPreConfigReload() { g_pGlobalState->buttons.clear(); } static void onUpdateWindowRules(PHLWINDOW window) { const auto BARIT = std::find_if(g_pGlobalState->bars.begin(), g_pGlobalState->bars.end(), [window](const auto& bar) { return bar->getOwner() == window; }); if (BARIT == g_pGlobalState->bars.end()) return; (*BARIT)->updateRules(); window->updateWindowDecos(); } Hyprlang::CParseResult onNewButton(const char* K, const char* V) { std::string v = V; CVarList vars(v); Hyprlang::CParseResult result; // hyprbars-button = bgcolor, size, icon, action, fgcolor if (vars[0].empty() || vars[1].empty()) { result.setError("bgcolor and size cannot be empty"); return result; } float size = 10; try { size = std::stof(vars[1]); } catch (std::exception& e) { result.setError("failed to parse size"); return result; } bool userfg = false; auto fgcolor = configStringToInt("rgb(ffffff)"); auto bgcolor = configStringToInt(vars[0]); if (!bgcolor) { result.setError("invalid bgcolor"); return result; } if (vars.size() == 5) { userfg = true; fgcolor = configStringToInt(vars[4]); } if (!fgcolor) { result.setError("invalid fgcolor"); return result; } g_pGlobalState->buttons.push_back(SHyprButton{vars[3], userfg, *fgcolor, *bgcolor, size, vars[2]}); for (auto& b : g_pGlobalState->bars) { b->m_bButtonsDirty = true; } return result; } APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { PHANDLE = handle; const std::string HASH = __hyprland_api_get_hash(); const std::string CLIENT_HASH = __hyprland_api_get_client_hash(); if (HASH != CLIENT_HASH) { HyprlandAPI::addNotification(PHANDLE, "[hyprbars] Failure in initialization: Version mismatch (headers ver is not equal to running hyprland ver)", CHyprColor{1.0, 0.2, 0.2, 1.0}, 5000); throw std::runtime_error("[hb] Version mismatch"); } g_pGlobalState = makeUnique(); g_pGlobalState->nobarRuleIdx = Desktop::Rule::windowEffects()->registerEffect("hyprbars:no_bar"); g_pGlobalState->barColorRuleIdx = Desktop::Rule::windowEffects()->registerEffect("hyprbars:bar_color"); g_pGlobalState->titleColorRuleIdx = Desktop::Rule::windowEffects()->registerEffect("hyprbars:title_color"); static auto P = HyprlandAPI::registerCallbackDynamic(PHANDLE, "openWindow", [&](void* self, SCallbackInfo& info, std::any data) { onNewWindow(self, data); }); // static auto P2 = HyprlandAPI::registerCallbackDynamic(PHANDLE, "closeWindow", [&](void* self, SCallbackInfo& info, std::any data) { onCloseWindow(self, data); }); static auto P3 = HyprlandAPI::registerCallbackDynamic(PHANDLE, "windowUpdateRules", [&](void* self, SCallbackInfo& info, std::any data) { onUpdateWindowRules(std::any_cast(data)); }); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:bar_color", Hyprlang::INT{*configStringToInt("rgba(33333388)")}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:bar_height", Hyprlang::INT{15}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:col.text", Hyprlang::INT{*configStringToInt("rgba(ffffffff)")}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:bar_text_size", Hyprlang::INT{10}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:bar_title_enabled", Hyprlang::INT{1}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:bar_blur", Hyprlang::INT{0}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:bar_text_font", Hyprlang::STRING{"Sans"}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:bar_text_align", Hyprlang::STRING{"center"}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:bar_part_of_window", Hyprlang::INT{1}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:bar_precedence_over_border", Hyprlang::INT{0}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:bar_buttons_alignment", Hyprlang::STRING{"right"}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:bar_padding", Hyprlang::INT{7}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:bar_button_padding", Hyprlang::INT{5}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:enabled", Hyprlang::INT{1}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:icon_on_hover", Hyprlang::INT{0}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:inactive_button_color", Hyprlang::INT{0}); // unset HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:on_double_click", Hyprlang::STRING{""}); HyprlandAPI::addConfigKeyword(PHANDLE, "plugin:hyprbars:hyprbars-button", onNewButton, Hyprlang::SHandlerOptions{}); static auto P4 = HyprlandAPI::registerCallbackDynamic(PHANDLE, "preConfigReload", [&](void* self, SCallbackInfo& info, std::any data) { onPreConfigReload(); }); // add deco to existing windows for (auto& w : g_pCompositor->m_windows) { if (w->isHidden() || !w->m_isMapped) continue; onNewWindow(nullptr /* unused */, std::any(w)); } HyprlandAPI::reloadConfig(); return {"hyprbars", "A plugin to add title bars to windows.", "Vaxry", "1.0"}; } APICALL EXPORT void PLUGIN_EXIT() { for (auto& m : g_pCompositor->m_monitors) m->m_scheduledRecalc = true; g_pHyprRenderer->m_renderPass.removeAllOfType("CBarPassElement"); Desktop::Rule::windowEffects()->unregisterEffect(g_pGlobalState->barColorRuleIdx); Desktop::Rule::windowEffects()->unregisterEffect(g_pGlobalState->titleColorRuleIdx); Desktop::Rule::windowEffects()->unregisterEffect(g_pGlobalState->nobarRuleIdx); } hyprwm-hyprland-plugins-4dbef35/hyprbars/meson.build000066400000000000000000000017751512451410000230330ustar00rootroot00000000000000project('hyprbars', 'cpp', version: '0.1', default_options: ['buildtype=release'], ) cpp_compiler = meson.get_compiler('cpp') if cpp_compiler.has_argument('-std=c++23') add_global_arguments('-std=c++23', language: 'cpp') elif cpp_compiler.has_argument('-std=c++2b') add_global_arguments('-std=c++2b', language: 'cpp') else error('Could not configure current C++ compiler (' + cpp_compiler.get_id() + ' ' + cpp_compiler.version() + ') with required C++ standard (C++23)') endif add_project_arguments( [ '-Wno-narrowing', ], language: 'cpp') globber = run_command('find', '.', '-name', '*.cpp', check: true) src = globber.stdout().strip().split('\n') hyprland = dependency('hyprland') shared_module(meson.project_name(), src, dependencies: [ dependency('hyprland'), dependency('pixman-1'), dependency('libdrm'), dependency('pangocairo'), dependency('libinput'), dependency('libudev'), dependency('wayland-server'), dependency('xkbcommon'), ], install: true, ) hyprwm-hyprland-plugins-4dbef35/hyprexpo/000077500000000000000000000000001512451410000207035ustar00rootroot00000000000000hyprwm-hyprland-plugins-4dbef35/hyprexpo/CMakeLists.txt000066400000000000000000000007611512451410000234470ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.27) project(hyprexpo DESCRIPTION "hyprexpo plugin for Hyprland" VERSION 0.1 ) set(CMAKE_CXX_STANDARD 23) file(GLOB_RECURSE SRC "*.cpp") add_library(hyprexpo SHARED ${SRC}) find_package(PkgConfig REQUIRED) pkg_check_modules(deps REQUIRED IMPORTED_TARGET hyprland libdrm libinput libudev pangocairo pixman-1 wayland-server xkbcommon ) target_link_libraries(hyprexpo PRIVATE rt PkgConfig::deps) install(TARGETS hyprexpo) hyprwm-hyprland-plugins-4dbef35/hyprexpo/ExpoGesture.cpp000066400000000000000000000021701512451410000236610ustar00rootroot00000000000000#include "ExpoGesture.hpp" #include "overview.hpp" #include #include #include void CExpoGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& e) { ITrackpadGesture::begin(e); m_lastDelta = 0.F; m_firstUpdate = true; if (!g_pOverview) g_pOverview = std::make_unique(Desktop::focusState()->monitor()->m_activeWorkspace); else { g_pOverview->selectHoveredWorkspace(); g_pOverview->setClosing(true); } } void CExpoGesture::update(const ITrackpadGesture::STrackpadGestureUpdate& e) { if (m_firstUpdate) { m_firstUpdate = false; return; } if (!g_pOverview) return; m_lastDelta += distance(e); if (m_lastDelta <= 0.01) // plugin will crash if swipe ends at <= 0 m_lastDelta = 0.01; g_pOverview->onSwipeUpdate(m_lastDelta); } void CExpoGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e) { if (!g_pOverview) return; g_pOverview->setClosing(false); g_pOverview->onSwipeEnd(); } hyprwm-hyprland-plugins-4dbef35/hyprexpo/ExpoGesture.hpp000066400000000000000000000010201512451410000236570ustar00rootroot00000000000000#pragma once #include class CExpoGesture : public ITrackpadGesture { public: CExpoGesture() = default; virtual ~CExpoGesture() = default; virtual void begin(const ITrackpadGesture::STrackpadGestureBegin& e); virtual void update(const ITrackpadGesture::STrackpadGestureUpdate& e); virtual void end(const ITrackpadGesture::STrackpadGestureEnd& e); private: float m_lastDelta = 0.F; bool m_firstUpdate = false; }; hyprwm-hyprland-plugins-4dbef35/hyprexpo/Makefile000066400000000000000000000006071512451410000223460ustar00rootroot00000000000000# Else exist specifically for clang ifeq ($(CXX),g++) EXTRA_FLAGS = --no-gnu-unique else EXTRA_FLAGS = endif all: $(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 clean: rm ./hyprexpo.so hyprwm-hyprland-plugins-4dbef35/hyprexpo/OverviewPassElement.cpp000066400000000000000000000013221512451410000253540ustar00rootroot00000000000000#include "OverviewPassElement.hpp" #include #include "overview.hpp" COverviewPassElement::COverviewPassElement() { ; } void COverviewPassElement::draw(const CRegion& damage) { g_pOverview->fullRender(); } bool COverviewPassElement::needsLiveBlur() { return false; } bool COverviewPassElement::needsPrecomputeBlur() { return false; } std::optional COverviewPassElement::boundingBox() { if (!g_pOverview->pMonitor) return std::nullopt; return CBox{{}, g_pOverview->pMonitor->m_size}; } CRegion COverviewPassElement::opaqueRegion() { if (!g_pOverview->pMonitor) return CRegion{}; return CBox{{}, g_pOverview->pMonitor->m_size}; } hyprwm-hyprland-plugins-4dbef35/hyprexpo/OverviewPassElement.hpp000066400000000000000000000010761512451410000253670ustar00rootroot00000000000000#pragma once #include class COverview; class COverviewPassElement : public IPassElement { public: COverviewPassElement(); virtual ~COverviewPassElement() = default; virtual void draw(const CRegion& damage); virtual bool needsLiveBlur(); virtual bool needsPrecomputeBlur(); virtual std::optional boundingBox(); virtual CRegion opaqueRegion(); virtual const char* passName() { return "COverviewPassElement"; } };hyprwm-hyprland-plugins-4dbef35/hyprexpo/README.md000066400000000000000000000034751512451410000221730ustar00rootroot00000000000000# HyprExpo HyprExpo is an overview plugin like Gnome, KDE or wf. ![HyprExpo](https://github.com/user-attachments/assets/e89df9d2-9800-4268-9929-239ad9bc3a54) ## Config A great start to configure this plugin would be adding this code to the `plugin` section of your hyprland configuration file: ```ini # .config/hypr/hyprland.conf plugin { hyprexpo { columns = 3 gap_size = 5 bg_col = rgb(111111) workspace_method = center current # [center/first] [workspace] e.g. first 1 or center m+1 gesture_distance = 300 # how far is the "max" for the gesture } } ``` ### Properties | property | type | description | default | | --- | --- | --- | --- | columns | number | how many desktops are displayed on one line | `3` gap_size | number | gap between desktops | `5` bg_col | color | color in gaps (between desktops) | `rgb(000000)` workspace_method | [center/first] [workspace] | position of the desktops | `center current` skip_empty | boolean | whether the grid displays workspaces sequentially by id using selector "r" (`false`) or skips empty workspaces using selector "m" (`true`) | `false` gesture_distance | number | how far is the max for the gesture | `300` ### Keywords | name | description | arguments | | -- | -- | -- | | hyprexpo-gesture | same as gesture, but for hyprexpo gestures. Supports: `expo`. | Same as gesture | ### Binding ```bash # hyprland.conf bind = MODIFIER, KEY, hyprexpo:expo, OPTION ``` Example: ```bash # This will toggle HyprExpo when SUPER+g is pressed bind = SUPER, g, hyprexpo:expo, toggle ``` Here are a list of options you can use: | option | description | | --- | --- | toggle | displays if hidden, hide if displayed select | selects the hovered desktop off | hides the overview disable | same as `off` on | displays the overview enable | same as `on` hyprwm-hyprland-plugins-4dbef35/hyprexpo/default.nix000066400000000000000000000006261512451410000230530ustar00rootroot00000000000000{ lib, hyprland, hyprlandPlugins, }: hyprlandPlugins.mkHyprlandPlugin { pluginName = "hyprexpo"; version = "0.1"; src = ./.; inherit (hyprland) nativeBuildInputs; meta = with lib; { homepage = "https://github.com/hyprwm/hyprland-plugins/tree/main/hyprexpo"; description = "Hyprland workspaces overview plugin"; license = licenses.bsd3; platforms = platforms.linux; }; } hyprwm-hyprland-plugins-4dbef35/hyprexpo/globals.hpp000066400000000000000000000001351512451410000230360ustar00rootroot00000000000000#pragma once #include inline HANDLE PHANDLE = nullptr;hyprwm-hyprland-plugins-4dbef35/hyprexpo/main.cpp000066400000000000000000000216161512451410000223410ustar00rootroot00000000000000#define WLR_USE_UNSTABLE #include #include #include #include #include #include #include #include #include #include using namespace Hyprutils::String; #include "globals.hpp" #include "overview.hpp" #include "ExpoGesture.hpp" // Methods inline CFunctionHook* g_pRenderWorkspaceHook = nullptr; inline CFunctionHook* g_pAddDamageHookA = nullptr; inline CFunctionHook* g_pAddDamageHookB = nullptr; typedef void (*origRenderWorkspace)(void*, PHLMONITOR, PHLWORKSPACE, timespec*, const CBox&); typedef void (*origAddDamageA)(void*, const CBox&); typedef void (*origAddDamageB)(void*, const pixman_region32_t*); static bool g_unloading = false; // Do NOT change this function. APICALL EXPORT std::string PLUGIN_API_VERSION() { return HYPRLAND_API_VERSION; } static bool renderingOverview = false; // static void hkRenderWorkspace(void* thisptr, PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, timespec* now, const CBox& geometry) { if (!g_pOverview || renderingOverview || g_pOverview->blockOverviewRendering || g_pOverview->pMonitor != pMonitor) ((origRenderWorkspace)(g_pRenderWorkspaceHook->m_original))(thisptr, pMonitor, pWorkspace, now, geometry); else g_pOverview->render(); } static void hkAddDamageA(void* thisptr, const CBox& box) { const auto PMONITOR = (CMonitor*)thisptr; if (!g_pOverview || g_pOverview->pMonitor != PMONITOR->m_self || g_pOverview->blockDamageReporting) { ((origAddDamageA)g_pAddDamageHookA->m_original)(thisptr, box); return; } g_pOverview->onDamageReported(); } static void hkAddDamageB(void* thisptr, const pixman_region32_t* rg) { const auto PMONITOR = (CMonitor*)thisptr; if (!g_pOverview || g_pOverview->pMonitor != PMONITOR->m_self || g_pOverview->blockDamageReporting) { ((origAddDamageB)g_pAddDamageHookB->m_original)(thisptr, rg); return; } g_pOverview->onDamageReported(); } static SDispatchResult onExpoDispatcher(std::string arg) { if (g_pOverview && g_pOverview->m_isSwiping) return {.success = false, .error = "already swiping"}; if (arg == "select") { if (g_pOverview) { g_pOverview->selectHoveredWorkspace(); g_pOverview->close(); } return {}; } if (arg == "toggle") { if (g_pOverview) g_pOverview->close(); else { renderingOverview = true; g_pOverview = std::make_unique(Desktop::focusState()->monitor()->m_activeWorkspace); renderingOverview = false; } return {}; } if (arg == "off" || arg == "close" || arg == "disable") { if (g_pOverview) g_pOverview->close(); return {}; } if (g_pOverview) return {}; renderingOverview = true; g_pOverview = std::make_unique(Desktop::focusState()->monitor()->m_activeWorkspace); renderingOverview = false; return {}; } static void failNotif(const std::string& reason) { HyprlandAPI::addNotification(PHANDLE, "[hyprexpo] Failure in initialization: " + reason, CHyprColor{1.0, 0.2, 0.2, 1.0}, 5000); } static Hyprlang::CParseResult expoGestureKeyword(const char* LHS, const char* RHS) { Hyprlang::CParseResult result; if (g_unloading) return result; CConstVarList data(RHS); size_t fingerCount = 0; eTrackpadGestureDirection direction = TRACKPAD_GESTURE_DIR_NONE; try { fingerCount = std::stoul(std::string{data[0]}); } catch (...) { result.setError(std::format("Invalid value {} for finger count", data[0]).c_str()); return result; } if (fingerCount <= 1 || fingerCount >= 10) { result.setError(std::format("Invalid value {} for finger count", data[0]).c_str()); return result; } direction = g_pTrackpadGestures->dirForString(data[1]); if (direction == TRACKPAD_GESTURE_DIR_NONE) { result.setError(std::format("Invalid direction: {}", data[1]).c_str()); return result; } int startDataIdx = 2; uint32_t modMask = 0; float deltaScale = 1.F; while (true) { if (data[startDataIdx].starts_with("mod:")) { modMask = g_pKeybindManager->stringToModMask(std::string{data[startDataIdx].substr(4)}); startDataIdx++; continue; } else if (data[startDataIdx].starts_with("scale:")) { try { deltaScale = std::clamp(std::stof(std::string{data[startDataIdx].substr(6)}), 0.1F, 10.F); startDataIdx++; continue; } catch (...) { result.setError(std::format("Invalid delta scale: {}", std::string{data[startDataIdx].substr(6)}).c_str()); return result; } } break; } std::expected resultFromGesture; if (data[startDataIdx] == "expo") resultFromGesture = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale); else if (data[startDataIdx] == "unset") resultFromGesture = g_pTrackpadGestures->removeGesture(fingerCount, direction, modMask, deltaScale); else { result.setError(std::format("Invalid gesture: {}", data[startDataIdx]).c_str()); return result; } if (!resultFromGesture) { result.setError(resultFromGesture.error().c_str()); return result; } return result; } APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { PHANDLE = handle; const std::string HASH = __hyprland_api_get_hash(); const std::string CLIENT_HASH = __hyprland_api_get_client_hash(); if (HASH != CLIENT_HASH) { failNotif("Version mismatch (headers ver is not equal to running hyprland ver)"); throw std::runtime_error("[he] Version mismatch"); } auto FNS = HyprlandAPI::findFunctionsByName(PHANDLE, "renderWorkspace"); if (FNS.empty()) { failNotif("no fns for hook renderWorkspace"); throw std::runtime_error("[he] No fns for hook renderWorkspace"); } g_pRenderWorkspaceHook = HyprlandAPI::createFunctionHook(PHANDLE, FNS[0].address, (void*)hkRenderWorkspace); FNS = HyprlandAPI::findFunctionsByName(PHANDLE, "addDamageEPK15pixman_region32"); if (FNS.empty()) { failNotif("no fns for hook addDamageEPK15pixman_region32"); throw std::runtime_error("[he] No fns for hook addDamageEPK15pixman_region32"); } g_pAddDamageHookB = HyprlandAPI::createFunctionHook(PHANDLE, FNS[0].address, (void*)hkAddDamageB); FNS = HyprlandAPI::findFunctionsByName(PHANDLE, "_ZN8CMonitor9addDamageERKN9Hyprutils4Math4CBoxE"); if (FNS.empty()) { failNotif("no fns for hook _ZN8CMonitor9addDamageERKN9Hyprutils4Math4CBoxE"); throw std::runtime_error("[he] No fns for hook _ZN8CMonitor9addDamageERKN9Hyprutils4Math4CBoxE"); } g_pAddDamageHookA = HyprlandAPI::createFunctionHook(PHANDLE, FNS[0].address, (void*)hkAddDamageA); bool success = g_pRenderWorkspaceHook->hook(); success = success && g_pAddDamageHookA->hook(); success = success && g_pAddDamageHookB->hook(); if (!success) { failNotif("Failed initializing hooks"); throw std::runtime_error("[he] Failed initializing hooks"); } static auto P = HyprlandAPI::registerCallbackDynamic(PHANDLE, "preRender", [](void* self, SCallbackInfo& info, std::any param) { if (!g_pOverview) return; g_pOverview->onPreRender(); }); HyprlandAPI::addDispatcherV2(PHANDLE, "hyprexpo:expo", ::onExpoDispatcher); HyprlandAPI::addConfigKeyword(PHANDLE, "hyprexpo-gesture", ::expoGestureKeyword, {}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:columns", Hyprlang::INT{3}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:gap_size", Hyprlang::INT{5}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:bg_col", Hyprlang::INT{0xFF111111}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:workspace_method", Hyprlang::STRING{"center current"}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:skip_empty", Hyprlang::INT{0}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:gesture_distance", Hyprlang::INT{200}); HyprlandAPI::reloadConfig(); return {"hyprexpo", "A plugin for an overview", "Vaxry", "1.0"}; } APICALL EXPORT void PLUGIN_EXIT() { g_pHyprRenderer->m_renderPass.removeAllOfType("COverviewPassElement"); g_unloading = true; g_pConfigManager->reload(); // we need to reload now to clear all the gestures } hyprwm-hyprland-plugins-4dbef35/hyprexpo/meson.build000066400000000000000000000017751512451410000230570ustar00rootroot00000000000000project('hyprexpo', 'cpp', version: '0.1', default_options: ['buildtype=release'], ) cpp_compiler = meson.get_compiler('cpp') if cpp_compiler.has_argument('-std=c++23') add_global_arguments('-std=c++23', language: 'cpp') elif cpp_compiler.has_argument('-std=c++2b') add_global_arguments('-std=c++2b', language: 'cpp') else error('Could not configure current C++ compiler (' + cpp_compiler.get_id() + ' ' + cpp_compiler.version() + ') with required C++ standard (C++23)') endif add_project_arguments( [ '-Wno-narrowing', ], language: 'cpp') globber = run_command('find', '.', '-name', '*.cpp', check: true) src = globber.stdout().strip().split('\n') hyprland = dependency('hyprland') shared_module(meson.project_name(), src, dependencies: [ dependency('hyprland'), dependency('pixman-1'), dependency('libdrm'), dependency('pangocairo'), dependency('libinput'), dependency('libudev'), dependency('wayland-server'), dependency('xkbcommon'), ], install: true, ) hyprwm-hyprland-plugins-4dbef35/hyprexpo/overview.cpp000066400000000000000000000464211512451410000232640ustar00rootroot00000000000000#include "overview.hpp" #include #define private public #include #include #include #include #include #include #include #include #include #include #include #undef private #include "OverviewPassElement.hpp" static void damageMonitor(WP thisptr) { g_pOverview->damage(); } COverview::~COverview() { g_pHyprRenderer->makeEGLCurrent(); images.clear(); // otherwise we get a vram leak Cursor::overrideController->unsetOverride(Cursor::CURSOR_OVERRIDE_UNKNOWN); g_pHyprOpenGL->markBlurDirtyForMonitor(pMonitor.lock()); } COverview::COverview(PHLWORKSPACE startedOn_, bool swipe_) : startedOn(startedOn_), swipe(swipe_) { const auto PMONITOR = Desktop::focusState()->monitor(); pMonitor = PMONITOR; static auto* const* PCOLUMNS = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:columns")->getDataStaticPtr(); static auto* const* PGAPS = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:gap_size")->getDataStaticPtr(); static auto* const* PCOL = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:bg_col")->getDataStaticPtr(); static auto* const* PSKIP = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:skip_empty")->getDataStaticPtr(); static auto const* PMETHOD = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:workspace_method")->getDataStaticPtr(); SIDE_LENGTH = **PCOLUMNS; GAP_WIDTH = **PGAPS; BG_COLOR = **PCOL; // process the method bool methodCenter = true; int methodStartID = pMonitor->activeWorkspaceID(); CVarList method{*PMETHOD, 0, 's', true}; if (method.size() < 2) Log::logger->log(Log::ERR, "[he] invalid workspace_method"); else { methodCenter = method[0] == "center"; methodStartID = getWorkspaceIDNameFromString(method[1]).id; if (methodStartID == WORKSPACE_INVALID) methodStartID = pMonitor->activeWorkspaceID(); } images.resize(SIDE_LENGTH * SIDE_LENGTH); // r includes empty workspaces; m skips over them std::string selector = **PSKIP ? "m" : "r"; if (methodCenter) { int currentID = methodStartID; int firstID = currentID; int backtracked = 0; // Initialize tiles to WORKSPACE_INVALID; cliking one of these results // in changing to "emptynm" (next empty workspace). Tiles with this id // will only remain if skip_empty is on. for (size_t i = 0; i < images.size(); i++) { images[i].workspaceID = WORKSPACE_INVALID; } // Scan through workspaces lower than methodStartID until we wrap; count how many for (size_t i = 1; i < images.size() / 2; ++i) { currentID = getWorkspaceIDNameFromString(selector + "-" + std::to_string(i)).id; if (currentID >= firstID) break; backtracked++; firstID = currentID; } // Scan through workspaces higher than methodStartID. If using "m" // (skip_empty), stop when we wrap, leaving the rest of the workspace // ID's set to WORKSPACE_INVALID for (size_t i = 0; i < (size_t)(SIDE_LENGTH * SIDE_LENGTH); ++i) { auto& image = images[i]; if ((int64_t)i - backtracked < 0) { currentID = getWorkspaceIDNameFromString(selector + std::to_string((int64_t)i - backtracked)).id; } else { currentID = getWorkspaceIDNameFromString(selector + "+" + std::to_string((int64_t)i - backtracked)).id; if (i > 0 && currentID == firstID) break; } image.workspaceID = currentID; } } else { int currentID = methodStartID; images[0].workspaceID = currentID; auto PWORKSPACESTART = g_pCompositor->getWorkspaceByID(currentID); if (!PWORKSPACESTART) PWORKSPACESTART = CWorkspace::create(currentID, pMonitor.lock(), std::to_string(currentID)); pMonitor->m_activeWorkspace = PWORKSPACESTART; // Scan through workspaces higher than methodStartID. If using "m" // (skip_empty), stop when we wrap, leaving the rest of the workspace // ID's set to WORKSPACE_INVALID for (size_t i = 1; i < (size_t)(SIDE_LENGTH * SIDE_LENGTH); ++i) { auto& image = images[i]; currentID = getWorkspaceIDNameFromString(selector + "+" + std::to_string(i)).id; if (currentID <= methodStartID) break; image.workspaceID = currentID; } pMonitor->m_activeWorkspace = startedOn; } g_pHyprRenderer->makeEGLCurrent(); Vector2D tileSize = pMonitor->m_size / SIDE_LENGTH; Vector2D tileRenderSize = (pMonitor->m_size - Vector2D{GAP_WIDTH * pMonitor->m_scale, GAP_WIDTH * pMonitor->m_scale} * (SIDE_LENGTH - 1)) / SIDE_LENGTH; CBox monbox{0, 0, tileSize.x * 2, tileSize.y * 2}; if (!ENABLE_LOWRES) monbox = {{0, 0}, pMonitor->m_pixelSize}; int currentid = 0; PHLWORKSPACE openSpecial = PMONITOR->m_activeSpecialWorkspace; if (openSpecial) PMONITOR->m_activeSpecialWorkspace.reset(); g_pHyprRenderer->m_bBlockSurfaceFeedback = true; startedOn->m_visible = false; for (size_t i = 0; i < (size_t)(SIDE_LENGTH * SIDE_LENGTH); ++i) { COverview::SWorkspaceImage& image = images[i]; image.fb.alloc(monbox.w, monbox.h, PMONITOR->m_output->state->state().drmFormat); CRegion fakeDamage{0, 0, INT16_MAX, INT16_MAX}; g_pHyprRenderer->beginRender(PMONITOR, fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, &image.fb); g_pHyprOpenGL->clear(CHyprColor{0, 0, 0, 1.0}); const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(image.workspaceID); if (PWORKSPACE == startedOn) currentid = i; if (PWORKSPACE) { image.pWorkspace = PWORKSPACE; PMONITOR->m_activeWorkspace = PWORKSPACE; g_pDesktopAnimationManager->startAnimation(PWORKSPACE, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true); PWORKSPACE->m_visible = true; if (PWORKSPACE == startedOn) PMONITOR->m_activeSpecialWorkspace = openSpecial; g_pHyprRenderer->renderWorkspace(PMONITOR, PWORKSPACE, Time::steadyNow(), monbox); PWORKSPACE->m_visible = false; g_pDesktopAnimationManager->startAnimation(PWORKSPACE, CDesktopAnimationManager::ANIMATION_TYPE_OUT, false, true); if (PWORKSPACE == startedOn) PMONITOR->m_activeSpecialWorkspace.reset(); } else g_pHyprRenderer->renderWorkspace(PMONITOR, PWORKSPACE, Time::steadyNow(), monbox); image.box = {(i % SIDE_LENGTH) * tileRenderSize.x + (i % SIDE_LENGTH) * GAP_WIDTH, (i / SIDE_LENGTH) * tileRenderSize.y + (i / SIDE_LENGTH) * GAP_WIDTH, tileRenderSize.x, tileRenderSize.y}; g_pHyprOpenGL->m_renderData.blockScreenShader = true; g_pHyprRenderer->endRender(); } g_pHyprRenderer->m_bBlockSurfaceFeedback = false; PMONITOR->m_activeSpecialWorkspace = openSpecial; PMONITOR->m_activeWorkspace = startedOn; startedOn->m_visible = true; g_pDesktopAnimationManager->startAnimation(startedOn, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true); // zoom on the current workspace. // const auto& TILE = images[std::clamp(currentid, 0, SIDE_LENGTH * SIDE_LENGTH)]; g_pAnimationManager->createAnimation(pMonitor->m_size * pMonitor->m_size / tileSize, size, g_pConfigManager->getAnimationPropertyConfig("windowsMove"), AVARDAMAGE_NONE); g_pAnimationManager->createAnimation((-((pMonitor->m_size / (double)SIDE_LENGTH) * Vector2D{currentid % SIDE_LENGTH, currentid / SIDE_LENGTH}) * pMonitor->m_scale) * (pMonitor->m_size / tileSize), pos, g_pConfigManager->getAnimationPropertyConfig("windowsMove"), AVARDAMAGE_NONE); size->setUpdateCallback(damageMonitor); pos->setUpdateCallback(damageMonitor); if (!swipe) { *size = pMonitor->m_size; *pos = {0, 0}; size->setCallbackOnEnd([this](auto) { redrawAll(true); }); } openedID = currentid; Cursor::overrideController->setOverride("left_ptr", Cursor::CURSOR_OVERRIDE_UNKNOWN); lastMousePosLocal = g_pInputManager->getMouseCoordsInternal() - pMonitor->m_position; auto onCursorMove = [this](void* self, SCallbackInfo& info, std::any param) { if (closing) return; info.cancelled = true; lastMousePosLocal = g_pInputManager->getMouseCoordsInternal() - pMonitor->m_position; }; auto onCursorSelect = [this](void* self, SCallbackInfo& info, std::any param) { if (closing) return; info.cancelled = true; selectHoveredWorkspace(); close(); }; mouseMoveHook = g_pHookSystem->hookDynamic("mouseMove", onCursorMove); touchMoveHook = g_pHookSystem->hookDynamic("touchMove", onCursorMove); mouseButtonHook = g_pHookSystem->hookDynamic("mouseButton", onCursorSelect); touchDownHook = g_pHookSystem->hookDynamic("touchDown", onCursorSelect); } void COverview::selectHoveredWorkspace() { if (closing) return; // get tile x,y int x = lastMousePosLocal.x / pMonitor->m_size.x * SIDE_LENGTH; int y = lastMousePosLocal.y / pMonitor->m_size.y * SIDE_LENGTH; closeOnID = x + y * SIDE_LENGTH; } void COverview::redrawID(int id, bool forcelowres) { if (!pMonitor) return; if (pMonitor->m_activeWorkspace != startedOn && !closing) { // likely user changed. onWorkspaceChange(); } blockOverviewRendering = true; g_pHyprRenderer->makeEGLCurrent(); id = std::clamp(id, 0, SIDE_LENGTH * SIDE_LENGTH); Vector2D tileSize = pMonitor->m_size / SIDE_LENGTH; Vector2D tileRenderSize = (pMonitor->m_size - Vector2D{GAP_WIDTH, GAP_WIDTH} * (SIDE_LENGTH - 1)) / SIDE_LENGTH; CBox monbox{0, 0, tileSize.x * 2, tileSize.y * 2}; if (!forcelowres && (size->value() != pMonitor->m_size || closing)) monbox = {{0, 0}, pMonitor->m_pixelSize}; if (!ENABLE_LOWRES) monbox = {{0, 0}, pMonitor->m_pixelSize}; auto& image = images[id]; if (image.fb.m_size != monbox.size()) { image.fb.release(); image.fb.alloc(monbox.w, monbox.h, pMonitor->m_output->state->state().drmFormat); } CRegion fakeDamage{0, 0, INT16_MAX, INT16_MAX}; g_pHyprRenderer->beginRender(pMonitor.lock(), fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, &image.fb); g_pHyprOpenGL->clear(CHyprColor{0, 0, 0, 1.0}); const auto PWORKSPACE = image.pWorkspace; PHLWORKSPACE openSpecial = pMonitor->m_activeSpecialWorkspace; if (openSpecial) pMonitor->m_activeSpecialWorkspace.reset(); startedOn->m_visible = false; if (PWORKSPACE) { pMonitor->m_activeWorkspace = PWORKSPACE; g_pDesktopAnimationManager->startAnimation(PWORKSPACE, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true); PWORKSPACE->m_visible = true; if (PWORKSPACE == startedOn) pMonitor->m_activeSpecialWorkspace = openSpecial; g_pHyprRenderer->renderWorkspace(pMonitor.lock(), PWORKSPACE, Time::steadyNow(), monbox); PWORKSPACE->m_visible = false; g_pDesktopAnimationManager->startAnimation(PWORKSPACE, CDesktopAnimationManager::ANIMATION_TYPE_OUT, false, true); if (PWORKSPACE == startedOn) pMonitor->m_activeSpecialWorkspace.reset(); } else g_pHyprRenderer->renderWorkspace(pMonitor.lock(), PWORKSPACE, Time::steadyNow(), monbox); g_pHyprOpenGL->m_renderData.blockScreenShader = true; g_pHyprRenderer->endRender(); pMonitor->m_activeSpecialWorkspace = openSpecial; pMonitor->m_activeWorkspace = startedOn; startedOn->m_visible = true; g_pDesktopAnimationManager->startAnimation(startedOn, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true); blockOverviewRendering = false; } void COverview::redrawAll(bool forcelowres) { if (!pMonitor) return; for (size_t i = 0; i < (size_t)(SIDE_LENGTH * SIDE_LENGTH); ++i) { redrawID(i, forcelowres); } } void COverview::damage() { blockDamageReporting = true; g_pHyprRenderer->damageMonitor(pMonitor.lock()); blockDamageReporting = false; } void COverview::onDamageReported() { damageDirty = true; Vector2D SIZE = size->value(); Vector2D tileSize = (SIZE / SIDE_LENGTH); Vector2D tileRenderSize = (SIZE - Vector2D{GAP_WIDTH, GAP_WIDTH} * (SIDE_LENGTH - 1)) / SIDE_LENGTH; // const auto& TILE = images[std::clamp(openedID, 0, SIDE_LENGTH * SIDE_LENGTH)]; CBox texbox = CBox{(openedID % SIDE_LENGTH) * tileRenderSize.x + (openedID % SIDE_LENGTH) * GAP_WIDTH, (openedID / SIDE_LENGTH) * tileRenderSize.y + (openedID / SIDE_LENGTH) * GAP_WIDTH, tileRenderSize.x, tileRenderSize.y} .translate(pMonitor->m_position); damage(); blockDamageReporting = true; g_pHyprRenderer->damageBox(texbox); blockDamageReporting = false; g_pCompositor->scheduleFrameForMonitor(pMonitor.lock()); } void COverview::close() { if (closing) return; const int ID = closeOnID == -1 ? openedID : closeOnID; const auto& TILE = images[std::clamp(ID, 0, SIDE_LENGTH * SIDE_LENGTH)]; Vector2D tileSize = (pMonitor->m_size / SIDE_LENGTH); size->warp(); pos->warp(); *size = pMonitor->m_size * pMonitor->m_size / tileSize; *pos = (-((pMonitor->m_size / (double)SIDE_LENGTH) * Vector2D{ID % SIDE_LENGTH, ID / SIDE_LENGTH}) * pMonitor->m_scale) * (pMonitor->m_size / tileSize); closing = true; redrawAll(); if (TILE.workspaceID != pMonitor->activeWorkspaceID()) { pMonitor->setSpecialWorkspace(0); // If this tile's workspace was WORKSPACE_INVALID, move to the next // empty workspace. This should only happen if skip_empty is on, in // which case some tiles will be left with this ID intentionally. const int NEWID = TILE.workspaceID == WORKSPACE_INVALID ? getWorkspaceIDNameFromString("emptynm").id : TILE.workspaceID; const auto NEWIDWS = g_pCompositor->getWorkspaceByID(NEWID); const auto OLDWS = pMonitor->m_activeWorkspace; if (!NEWIDWS) g_pKeybindManager->changeworkspace(std::to_string(NEWID)); else g_pKeybindManager->changeworkspace(NEWIDWS->getConfigName()); g_pDesktopAnimationManager->startAnimation(pMonitor->m_activeWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true); g_pDesktopAnimationManager->startAnimation(OLDWS, CDesktopAnimationManager::ANIMATION_TYPE_OUT, false, true); startedOn = pMonitor->m_activeWorkspace; } size->setCallbackOnEnd([](auto) { g_pEventLoopManager->doLater([] { g_pOverview.reset(); }); }); } void COverview::onPreRender() { if (damageDirty) { damageDirty = false; redrawID(closing ? (closeOnID == -1 ? openedID : closeOnID) : openedID); } } void COverview::onWorkspaceChange() { if (valid(startedOn)) g_pDesktopAnimationManager->startAnimation(startedOn, CDesktopAnimationManager::ANIMATION_TYPE_OUT, false, true); else startedOn = pMonitor->m_activeWorkspace; for (size_t i = 0; i < (size_t)(SIDE_LENGTH * SIDE_LENGTH); ++i) { if (images[i].workspaceID != pMonitor->activeWorkspaceID()) continue; openedID = i; break; } closeOnID = openedID; if (!closing) close(); } void COverview::render() { g_pHyprRenderer->m_renderPass.add(makeUnique()); } void COverview::fullRender() { const auto GAPSIZE = (closing ? (1.0 - size->getPercent()) : size->getPercent()) * GAP_WIDTH; if (pMonitor->m_activeWorkspace != startedOn && !closing) { // likely user changed. onWorkspaceChange(); } Vector2D SIZE = size->value(); Vector2D tileSize = (SIZE / SIDE_LENGTH); Vector2D tileRenderSize = (SIZE - Vector2D{GAPSIZE, GAPSIZE} * (SIDE_LENGTH - 1)) / SIDE_LENGTH; g_pHyprOpenGL->clear(BG_COLOR.stripA()); for (size_t y = 0; y < (size_t)SIDE_LENGTH; ++y) { for (size_t x = 0; x < (size_t)SIDE_LENGTH; ++x) { CBox texbox = {x * tileRenderSize.x + x * GAPSIZE, y * tileRenderSize.y + y * GAPSIZE, tileRenderSize.x, tileRenderSize.y}; texbox.scale(pMonitor->m_scale).translate(pos->value()); texbox.round(); CRegion damage{0, 0, INT16_MAX, INT16_MAX}; g_pHyprOpenGL->renderTextureInternal(images[x + y * SIDE_LENGTH].fb.getTexture(), texbox, {.damage = &damage, .a = 1.0}); } } } static float lerp(const float& from, const float& to, const float perc) { return (to - from) * perc + from; } static Vector2D lerp(const Vector2D& from, const Vector2D& to, const float perc) { return Vector2D{lerp(from.x, to.x, perc), lerp(from.y, to.y, perc)}; } void COverview::setClosing(bool closing_) { closing = closing_; } void COverview::resetSwipe() { swipeWasCommenced = false; } void COverview::onSwipeUpdate(double delta) { m_isSwiping = true; static auto* const* PDISTANCE = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:gesture_distance")->getDataStaticPtr(); const float PERC = closing ? std::clamp(delta / (double)**PDISTANCE, 0.0, 1.0) : 1.0 - std::clamp(delta / (double)**PDISTANCE, 0.0, 1.0); const auto WORKSPACE_FOCUS_ID = closing && closeOnID != -1 ? closeOnID : openedID; Vector2D tileSize = (pMonitor->m_size / SIDE_LENGTH); const auto SIZEMAX = pMonitor->m_size * pMonitor->m_size / tileSize; const auto POSMAX = (-((pMonitor->m_size / (double)SIDE_LENGTH) * Vector2D{WORKSPACE_FOCUS_ID % SIDE_LENGTH, WORKSPACE_FOCUS_ID / SIDE_LENGTH}) * pMonitor->m_scale) * (pMonitor->m_size / tileSize); const auto SIZEMIN = pMonitor->m_size; const auto POSMIN = Vector2D{0, 0}; size->setValueAndWarp(lerp(SIZEMIN, SIZEMAX, PERC)); pos->setValueAndWarp(lerp(POSMIN, POSMAX, PERC)); } void COverview::onSwipeEnd() { if (closing || !m_isSwiping) return; const auto SIZEMIN = pMonitor->m_size; const auto SIZEMAX = pMonitor->m_size * pMonitor->m_size / (pMonitor->m_size / SIDE_LENGTH); const auto PERC = (size->value() - SIZEMIN).x / (SIZEMAX - SIZEMIN).x; if (PERC > 0.5) { close(); return; } *size = pMonitor->m_size; *pos = {0, 0}; size->setCallbackOnEnd([this](WP thisptr) { redrawAll(true); }); swipeWasCommenced = false; m_isSwiping = false; } hyprwm-hyprland-plugins-4dbef35/hyprexpo/overview.hpp000066400000000000000000000044301512451410000232630ustar00rootroot00000000000000#pragma once #define WLR_USE_UNSTABLE #include "globals.hpp" #include #include #include #include #include // saves on resources, but is a bit broken rn with blur. // hyprland's fault, but cba to fix. constexpr bool ENABLE_LOWRES = false; class CMonitor; class COverview { public: COverview(PHLWORKSPACE startedOn_, bool swipe = false); ~COverview(); void render(); void damage(); void onDamageReported(); void onPreRender(); void setClosing(bool closing); void resetSwipe(); void onSwipeUpdate(double delta); void onSwipeEnd(); // close without a selection void close(); void selectHoveredWorkspace(); bool blockOverviewRendering = false; bool blockDamageReporting = false; PHLMONITORREF pMonitor; bool m_isSwiping = false; private: void redrawID(int id, bool forcelowres = false); void redrawAll(bool forcelowres = false); void onWorkspaceChange(); void fullRender(); int SIDE_LENGTH = 3; int GAP_WIDTH = 5; CHyprColor BG_COLOR = CHyprColor{0.1, 0.1, 0.1, 1.0}; bool damageDirty = false; struct SWorkspaceImage { CFramebuffer fb; int64_t workspaceID = -1; PHLWORKSPACE pWorkspace; CBox box; }; Vector2D lastMousePosLocal = Vector2D{}; int openedID = -1; int closeOnID = -1; std::vector images; PHLWORKSPACE startedOn; PHLANIMVAR size; PHLANIMVAR pos; bool closing = false; SP mouseMoveHook; SP mouseButtonHook; SP touchMoveHook; SP touchDownHook; bool swipe = false; bool swipeWasCommenced = false; friend class COverviewPassElement; }; inline std::unique_ptr g_pOverview; hyprwm-hyprland-plugins-4dbef35/hyprfocus/000077500000000000000000000000001512451410000210475ustar00rootroot00000000000000hyprwm-hyprland-plugins-4dbef35/hyprfocus/CMakeLists.txt000066400000000000000000000007601512451410000236120ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.27) project(hyprfocus DESCRIPTION "flashfocus for Hyprland" VERSION 0.1 ) set(CMAKE_CXX_STANDARD 23) file(GLOB_RECURSE SRC "*.cpp") add_library(hyprfocus SHARED ${SRC}) find_package(PkgConfig REQUIRED) pkg_check_modules(deps REQUIRED IMPORTED_TARGET hyprland libdrm libinput libudev pangocairo pixman-1 wayland-server xkbcommon ) target_link_libraries(hyprfocus PRIVATE rt PkgConfig::deps) install(TARGETS hyprfocus) hyprwm-hyprland-plugins-4dbef35/hyprfocus/Makefile000066400000000000000000000005111512451410000225040ustar00rootroot00000000000000# Else exist specifically for clang ifeq ($(CXX),g++) EXTRA_FLAGS = --no-gnu-unique else EXTRA_FLAGS = endif all: $(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 clean: rm ./hyprfocus.so hyprwm-hyprland-plugins-4dbef35/hyprfocus/README.md000066400000000000000000000021101512451410000223200ustar00rootroot00000000000000# hyprfocus Flashfocus for Hyprland. ## Configuring ### Animations Hyprfocus exposes two animation leaves: `hyprfocusIn` and `hyprfocusOut`: ```ini animation = hyprfocusIn, 1, 1.7, myCurve animation = hyprfocusOut, 1, 1.7, myCurve2 ``` ### Variables | name | description | type | default | |--------------------------|----------------------------------------------------------------------|---------|---------| | `mode` | which mode to use (flash / bounce / slide) | str | flash | | `only_on_monitor_change` | whether to only perform the animation when changing between monitors | boolean | false | | `fade_opacity` | for flash, what opacity to flash to | float | 0.8 | | `bounce_strength` | for bounce, what fraction of the window's size to jump to | float | 0.95 | | `slide_height` | for slide, how far up to slide | float | 20 | hyprwm-hyprland-plugins-4dbef35/hyprfocus/default.nix000066400000000000000000000006171512451410000232170ustar00rootroot00000000000000{ lib, hyprland, hyprlandPlugins, }: hyprlandPlugins.mkHyprlandPlugin { pluginName = "hyprfocus"; version = "0.1"; src = ./.; inherit (hyprland) nativeBuildInputs; meta = with lib; { homepage = "https://github.com/hyprwm/hyprland-plugins/tree/main/hyprfocus"; description = "Hyprland flashfocus plugin"; license = licenses.bsd3; platforms = platforms.linux; }; } hyprwm-hyprland-plugins-4dbef35/hyprfocus/globals.hpp000066400000000000000000000001351512451410000232020ustar00rootroot00000000000000#pragma once #include inline HANDLE PHANDLE = nullptr;hyprwm-hyprland-plugins-4dbef35/hyprfocus/main.cpp000066400000000000000000000140621512451410000225020ustar00rootroot00000000000000#define WLR_USE_UNSTABLE #include #include #include #include #include #define private public #include #include #include #include #include #include #include #undef private #include "globals.hpp" #include #include using namespace Hyprutils::String; using namespace Hyprutils::Animation; // Do NOT change this function. APICALL EXPORT std::string PLUGIN_API_VERSION() { return HYPRLAND_API_VERSION; } static void onFocusChange(PHLWINDOW window) { if (!window) return; static PHLWINDOWREF lastWindow; if (lastWindow == window) return; static const auto PONLY_ON_MONITOR_CHANGE = CConfigValue("plugin:hyprfocus:only_on_monitor_change"); if (*PONLY_ON_MONITOR_CHANGE && lastWindow && lastWindow->m_monitor == window->m_monitor) return; lastWindow = window; static const auto POPACITY = CConfigValue("plugin:hyprfocus:fade_opacity"); static const auto PBOUNCE = CConfigValue("plugin:hyprfocus:bounce_strength"); static const auto PSLIDE = CConfigValue("plugin:hyprfocus:slide_height"); static const auto PMODE = CConfigValue("plugin:hyprfocus:mode"); const auto PIN = g_pConfigManager->getAnimationPropertyConfig("hyprfocusIn"); const auto POUT = g_pConfigManager->getAnimationPropertyConfig("hyprfocusOut"); if (*PMODE == "flash") { const auto ORIGINAL = window->m_activeInactiveAlpha->goal(); window->m_activeInactiveAlpha->setConfig(PIN); *window->m_activeInactiveAlpha = std::clamp(*POPACITY, 0.F, 1.F); window->m_activeInactiveAlpha->setCallbackOnEnd([w = PHLWINDOWREF{window}, POUT, ORIGINAL](WP pav) { if (!w) return; w->m_activeInactiveAlpha->setConfig(POUT); *w->m_activeInactiveAlpha = ORIGINAL; w->m_activeInactiveAlpha->setCallbackOnEnd(nullptr); }); } else if (*PMODE == "bounce") { const auto ORIGINAL = CBox{window->m_realPosition->goal(), window->m_realSize->goal()}; window->m_realPosition->setConfig(PIN); window->m_realSize->setConfig(PIN); auto box = ORIGINAL.copy().scaleFromCenter(std::clamp(*PBOUNCE, 0.1F, 1.F)); *window->m_realPosition = box.pos(); *window->m_realSize = box.size(); window->m_realSize->setCallbackOnEnd([w = PHLWINDOWREF{window}, POUT, ORIGINAL](WP pav) { if (!w) return; w->m_realSize->setConfig(POUT); w->m_realPosition->setConfig(POUT); if (w->m_isFloating || w->isFullscreen()) { *w->m_realPosition = ORIGINAL.pos(); *w->m_realSize = ORIGINAL.size(); } else g_pLayoutManager->getCurrentLayout()->recalculateWindow(w.lock()); w->m_realSize->setCallbackOnEnd(nullptr); }); } else if (*PMODE == "slide") { const auto ORIGINAL = window->m_realPosition->goal(); window->m_realPosition->setConfig(PIN); *window->m_realPosition = ORIGINAL - Vector2D{0.F, std::clamp(*PSLIDE, 0.F, 150.F)}; window->m_realPosition->setCallbackOnEnd([w = PHLWINDOWREF{window}, POUT, ORIGINAL](WP pav) { if (!w) return; w->m_realPosition->setConfig(POUT); if (w->m_isFloating || w->isFullscreen()) *w->m_realPosition = ORIGINAL; else g_pLayoutManager->getCurrentLayout()->recalculateWindow(w.lock()); w->m_realPosition->setCallbackOnEnd(nullptr); }); } } APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { PHANDLE = handle; const std::string HASH = __hyprland_api_get_hash(); const std::string CLIENT_HASH = __hyprland_api_get_client_hash(); if (HASH != CLIENT_HASH) { HyprlandAPI::addNotification(PHANDLE, "[hyprwinwrap] Failure in initialization: Version mismatch (headers ver is not equal to running hyprland ver)", CHyprColor{1.0, 0.2, 0.2, 1.0}, 5000); throw std::runtime_error("[hww] Version mismatch"); } // clang-format off static auto P = HyprlandAPI::registerCallbackDynamic(PHANDLE, "activeWindow", [&](void* self, SCallbackInfo& info, std::any data) { onFocusChange(std::any_cast(data)); }); // clang-format on HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprfocus:mode", Hyprlang::STRING{"flash"}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprfocus:only_on_monitor_change", Hyprlang::INT{0}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprfocus:fade_opacity", Hyprlang::FLOAT{0.8F}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprfocus:slide_height", Hyprlang::FLOAT{20.F}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprfocus:bounce_strength", Hyprlang::FLOAT{0.95F}); // this will not be cleaned up after we are unloaded but it doesn't really matter, // as if we create this again it will just overwrite the old one. g_pConfigManager->m_animationTree.createNode("hyprfocusIn", "windowsIn"); g_pConfigManager->m_animationTree.createNode("hyprfocusOut", "windowsOut"); return {"hyprfocus", "Flashfocus for Hyprland", "Vaxry", "1.0"}; } APICALL EXPORT void PLUGIN_EXIT() { // reset the callbacks to avoid crashes for (const auto& w : g_pCompositor->m_windows) { if (!validMapped(w)) continue; w->m_realSize->setCallbackOnEnd(nullptr); w->m_realPosition->setCallbackOnEnd(nullptr); w->m_activeInactiveAlpha->setCallbackOnEnd(nullptr); } } hyprwm-hyprland-plugins-4dbef35/hyprfocus/meson.build000066400000000000000000000016211512451410000232110ustar00rootroot00000000000000project('hyprfocus', 'cpp', version: '0.1', default_options: ['buildtype=release'], ) cpp_compiler = meson.get_compiler('cpp') if cpp_compiler.has_argument('-std=c++23') add_global_arguments('-std=c++23', language: 'cpp') elif cpp_compiler.has_argument('-std=c++2b') add_global_arguments('-std=c++2b', language: 'cpp') else error('Could not configure current C++ compiler (' + cpp_compiler.get_id() + ' ' + cpp_compiler.version() + ') with required C++ standard (C++23)') endif globber = run_command('find', '.', '-name', '*.cpp', check: true) src = globber.stdout().strip().split('\n') shared_module(meson.project_name(), src, dependencies: [ dependency('hyprland'), dependency('libdrm'), dependency('libinput'), dependency('libudev'), dependency('pangocairo'), dependency('pixman-1'), dependency('wayland-server'), dependency('xkbcommon'), ], install: true, ) hyprwm-hyprland-plugins-4dbef35/hyprload.toml000066400000000000000000000021001512451410000215350ustar00rootroot00000000000000[borders-plus-plus] description ="A plugin to add more borders to windows." version = "1.0" authors = ["Vaxry"] [borders-plus-plus.build] output = "borders-plus-plus/borders-plus-plus.so" steps = [ "make -C borders-plus-plus all", ] [csgo-vulkan-fix] description = "A plugin to fix incorrect mouse offsets on csgo in Vulkan" version = "1.0" authors = ["Vaxry"] [csgo-vulkan-fix.build] output = "csgo-vulkan-fix/csgo-vulkan-fix.so" steps = [ "make -C csgo-vulkan-fix all", ] [hyprbars] description = "A plugin to add title bars to windows" version = "1.0" authors = ["Vaxry"] [hyprbars.build] output = "hyprbars/hyprbars.so" steps = [ "make -C hyprbars all", ] [hyprtrails] description = "A plugin to add trails behind moving windows" version = "1.0" authors = ["Vaxry"] [hyprtrails.build] output = "hyprtrails/hyprtrails.so" steps = [ "make -C hyprtrails all", ] [hyprwinwrap] description = "A clone of xwinwrap for Hyprland" version = "1.0" authors = ["Vaxry"] [hyprwinwrap.build] output = "hyprwinwrap/hyprwinwrap.so" steps = [ "make -C hyprwinwrap all", ] hyprwm-hyprland-plugins-4dbef35/hyprpm.toml000066400000000000000000000126061512451410000212460ustar00rootroot00000000000000[repository] name = "hyprland-plugins" authors = ["Vaxry"] commit_pins = [ ["3bb9c7c5cf4f2ee30bf821501499f2308d616f94", "efee74a7404495dbda70205824d6e9fc923ccdae"], ["d74607e414dcd16911089a6d4b6aeb661c880923", "efee74a7404495dbda70205824d6e9fc923ccdae"], ["03ebbe18ed8517ee22591eac82cd54322f42cb7d", "f7853b9cc6aab627b37b7be6575628e788ad6d1d"], ["84ab8d11e8951a6551d1e1bf87796a8589da6d47", "8af29f09c5b132d5087c2931fe9bd34f63923ba1"], ["1c460e98f870676b15871fe4e5bfeb1a32a3d6d8", "f99822818ec8276cfd6ec99ab60c4708c9884e3d"], ["c5e28ebcfe00a510922779b2c568cfa52a317445", "4c9d83b981ad4668b89b8a3dc24d6f3ea7ad08fd"], # 0.37.0 ["19c90048d65a5660384d2fb865926a366696d6be", "4c9d83b981ad4668b89b8a3dc24d6f3ea7ad08fd"], # 0.37.1 ["3875679755014997776e091ff8903acfb311dd2f", "e45066d0741a1a4b9298a4c5ec43a5e57b059a4e"], # 0.38.0 ["360ede79d124ffdeebbe8401f1ac4bc0dbec2c91", "e45066d0741a1a4b9298a4c5ec43a5e57b059a4e"], # 0.38.1 ["e93fbd7c4f991cb8ef03e433ccc4d43587923e15", "e9457e08ca3ff16dc5a815be62baf9e18b539197"], # 0.39.0 ["fe7b748eb668136dd0558b7c8279bfcd7ab4d759", "e9457e08ca3ff16dc5a815be62baf9e18b539197"], # 0.39.1 ["cba1ade848feac44b2eda677503900639581c3f4", "18daf37b7c4e6e51ca2bf8953ce4cff1c38ca725"], # 0.40.0 ["ea2501d4556f84d3de86a4ae2f4b22a474555b9f", "8571aa9badf7db9c4911018a5611c038cc776256"], # 0.41.0 ["9e781040d9067c2711ec2e9f5b47b76ef70762b3", "8571aa9badf7db9c4911018a5611c038cc776256"], # 0.41.1 ["918d8340afd652b011b937d29d5eea0be08467f5", "135de7b88649dbe5fea8c997447bdc9d6f15ad86"], # 0.41.2 ["9a09eac79b85c846e3a865a9078a3f8ff65a9259", "4fcb4038f23e5273af9a5684f3ee10b0652b3bab"], # 0.42.0 ["0f594732b063a90d44df8c5d402d658f27471dfe", "b73d7b901d8cb1172dd25c7b7159f0242c625a77"], # 0.43.0 ["0c7a7e2d569eeed9d6025f3eef4ea0690d90845d", "9215288eb2ded9d0c08d468ea90ba68f43162c67"], # 0.44.0 ["4520b30d498daca8079365bdb909a8dea38e8d55", "9215288eb2ded9d0c08d468ea90ba68f43162c67"], # 0.44.1 ["a425fbebe4cf4238e48a42f724ef2208959d66cf", "44859f877739c05d031fcab4a2991ec004fa9bc4"], # 0.45.0 ["500d2a3580388afc8b620b0a3624147faa34f98b", "344a69db96fa8c6dc3b8f1f8f5a75f6eb441cbf2"], # 0.45.1 ["12f9a0d0b93f691d4d9923716557154d74777b0a", "344a69db96fa8c6dc3b8f1f8f5a75f6eb441cbf2"], # 0.45.2 ["788ae588979c2a1ff8a660f16e3c502ef5796755", "17ef806444fee729d00b3ba5cb8c623b7fbb699b"], # 0.46.0 ["254fc2bc6000075f660b4b8ed818a6af544d1d64", "17ef806444fee729d00b3ba5cb8c623b7fbb699b"], # 0.46.1 ["0bd541f2fd902dbfa04c3ea2ccf679395e316887", "17ef806444fee729d00b3ba5cb8c623b7fbb699b"], # 0.46.2 ["04ac46c54357278fc68f0a95d26347ea0db99496", "3e51162d83b0cd9ee35acbd3b91e6d7ba856f5eb"], # 0.47.0 ["75dff7205f6d2bd437abfb4196f700abee92581a", "3e51162d83b0cd9ee35acbd3b91e6d7ba856f5eb"], # 0.47.1 ["5ee35f914f921e5696030698e74fb5566a804768", "1f332c09a2382cb23da0f69a6f504f8b33433831"], # 0.48.0 ["29e2e59fdbab8ed2cc23a20e3c6043d5decb5cdc", "1f332c09a2382cb23da0f69a6f504f8b33433831"], # 0.48.1 ["9958d297641b5c84dcff93f9039d80a5ad37ab00", "c491d2831448645f24a1597a17f564aa52691ac6"], # 0.49.0 ["c4a4c341568944bd4fb9cd503558b2de602c0213", "bf310cda4a09b79725c2919688881959ebf3229e"], # 0.50.0 ["4e242d086e20b32951fdc0ebcbfb4d41b5be8dcc", "bf310cda4a09b79725c2919688881959ebf3229e"], # 0.50.1 ["46174f78b374b6cea669c48880877a8bdcf7802f", "a5a6f93d72d5fb37e78b98c756cfd8b340e71a19"], # 0.51.0 ["71a1216abcc7031776630a6d88f105605c4dc1c9", "a5a6f93d72d5fb37e78b98c756cfd8b340e71a19"], # 0.51.1 ["f56ec180d3a03a5aa978391249ff8f40f949fb73", "8c1212e96b81aa5f11fe21ca27defa2aad5b3cf3"], # 0.52.0 ["967c3c7404d4fa00234e29c70df3e263386d2597", "8c1212e96b81aa5f11fe21ca27defa2aad5b3cf3"], # 0.52.1 ["386376400119dd46a767c9f8c8791fd22c7b6e61", "8c1212e96b81aa5f11fe21ca27defa2aad5b3cf3"] # 0.52.2 ] [borders-plus-plus] description = "A plugin to add more borders to windows." authors = ["Vaxry"] output = "borders-plus-plus/borders-plus-plus.so" build = [ "make -C borders-plus-plus all", ] [csgo-vulkan-fix] description = "A plugin to fix incorrect mouse offsets on csgo in Vulkan" authors = ["Vaxry"] output = "csgo-vulkan-fix/csgo-vulkan-fix.so" build = [ "make -C csgo-vulkan-fix all", ] [hyprbars] description = "A plugin to add title bars to windows" authors = ["Vaxry"] output = "hyprbars/hyprbars.so" build = [ "make -C hyprbars all", ] [hyprtrails] description = "A plugin to add trails behind moving windows" authors = ["Vaxry"] output = "hyprtrails/hyprtrails.so" build = [ "make -C hyprtrails all", ] [hyprwinwrap] description = "A clone of xwinwrap for Hyprland" authors = ["Vaxry"] output = "hyprwinwrap/hyprwinwrap.so" build = [ "make -C hyprwinwrap all", ] [hyprexpo] description = "A plugin to add expo (overlay) for workspaces" authors = ["Vaxry"] output = "hyprexpo/hyprexpo.so" build = [ "make -C hyprexpo all", ] since_hyprland = 4364 [xtra-dispatchers] description = "A plugin to add some extra dispatchers" authors = ["Vaxry"] output = "xtra-dispatchers/xtra-dispatchers.so" build = [ "make -C xtra-dispatchers all", ] since_hyprland = 5573 [hyprscrolling] description = "A plugin to add a scrolling layout" authors = ["Vaxry"] output = "hyprscrolling/hyprscrolling.so" build = [ "make -C hyprscrolling all", ] since_hyprland = 6066 [hyprfocus] description = "Flashfocus for Hyprland" authors = ["Vaxry"] output = "hyprfocus/hyprfocus.so" build = [ "make -C hyprfocus all", ] since_hyprland = 6066 hyprwm-hyprland-plugins-4dbef35/hyprscrolling/000077500000000000000000000000001512451410000217245ustar00rootroot00000000000000hyprwm-hyprland-plugins-4dbef35/hyprscrolling/CMakeLists.txt000066400000000000000000000010121512451410000244560ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.27) project(hyprscrolling DESCRIPTION "hyprscrolling plugin for Hyprland" VERSION 0.1 ) set(CMAKE_CXX_STANDARD 23) file(GLOB_RECURSE SRC "*.cpp") add_library(hyprscrolling SHARED ${SRC}) find_package(PkgConfig REQUIRED) pkg_check_modules(deps REQUIRED IMPORTED_TARGET hyprland libdrm libinput libudev pangocairo pixman-1 wayland-server xkbcommon ) target_link_libraries(hyprscrolling PRIVATE rt PkgConfig::deps) install(TARGETS hyprscrolling) hyprwm-hyprland-plugins-4dbef35/hyprscrolling/Makefile000066400000000000000000000005341512451410000233660ustar00rootroot00000000000000# Else exist specifically for clang ifeq ($(CXX),g++) EXTRA_FLAGS = --no-gnu-unique else EXTRA_FLAGS = endif all: $(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 clean: rm ./hyprscrolling.so hyprwm-hyprland-plugins-4dbef35/hyprscrolling/README.md000066400000000000000000000052661512451410000232140ustar00rootroot00000000000000# hyprscrolling Adds a scrolling layout to Hyprland. **This plugin is a work in progress!** ## Config Make sure to set the `general:layout` to `scrolling` to use this layout. All config values can be set in your Hyprland config file, for example: ``` plugin { hyprscrolling { column_width = 0.7 fullscreen_on_one_column = false } } ``` | name | description | type | default | | -- | -- | -- | -- | | fullscreen_on_one_column | if there's only one column, should it be fullscreen | bool | false | | column_width | default column width as a fraction of the monitor width | float [0 - 1] | 0.5 | | 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` | | focus_fit_method | when a column is focused, what method to use to bring it into view. 0 - center, 1 - fit | int | 0 | | follow_focus | when a window is focused, the layout will move to make it visible | bool | true | ## Layout messages | name | description | params | | --- | --- | --- | | move | move the layout horizontally, by either a relative logical px (`-200`, `+200`) or columns (`+col`, `-col`) | move data | | 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 | | movewindowto | same as the movewindow dispatcher but supports promotion to the right at the end | direction | | fit | executes a fit operation based on the argument. Available: `active`, `visible`, `all`, `toend`, `tobeg` | fit mode | | focus | moves the focus and centers the layout, while also wrapping instead of moving to neighbring monitors. | direction | | promote | moves a window to its own new column | none | | 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` | | 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| | togglefit | Toggle the focus_fit_method (center, fit) | none | Example key bindings for your Hyprland config: ``` bind = $mainMod, period, layoutmsg, move +col bind = $mainMod, comma, layoutmsg, move -col bind = $mainMod SHIFT, period, layoutmsg, movewindowto r bind = $mainMod SHIFT, comma, layoutmsg, movewindowto l bind = $mainMod SHIFT, up, layoutmsg, movewindowto u bind = $mainMod SHIFT, down, layoutmsg, movewindowto d ``` hyprwm-hyprland-plugins-4dbef35/hyprscrolling/Scrolling.cpp000066400000000000000000001502671512451410000243770ustar00rootroot00000000000000#include "Scrolling.hpp" #include #include #include #include #include #include #include #include #include #include using namespace Hyprutils::String; using namespace Hyprutils::Utils; constexpr float MIN_COLUMN_WIDTH = 0.05F; constexpr float MAX_COLUMN_WIDTH = 1.F; constexpr float MIN_ROW_HEIGHT = 0.1F; constexpr float MAX_ROW_HEIGHT = 1.F; // void SColumnData::add(PHLWINDOW w) { for (auto& wd : windowDatas) { wd->windowSize *= (float)windowDatas.size() / (float)(windowDatas.size() + 1); } windowDatas.emplace_back(makeShared(w, self.lock(), 1.F / (float)(windowDatas.size() + 1))); } void SColumnData::add(PHLWINDOW w, int after) { for (auto& wd : windowDatas) { wd->windowSize *= (float)windowDatas.size() / (float)(windowDatas.size() + 1); } windowDatas.insert(windowDatas.begin() + after + 1, makeShared(w, self.lock(), 1.F / (float)(windowDatas.size() + 1))); } void SColumnData::add(SP w) { for (auto& wd : windowDatas) { wd->windowSize *= (float)windowDatas.size() / (float)(windowDatas.size() + 1); } windowDatas.emplace_back(w); w->column = self; w->windowSize = 1.F / (float)(windowDatas.size()); } void SColumnData::add(SP w, int after) { for (auto& wd : windowDatas) { wd->windowSize *= (float)windowDatas.size() / (float)(windowDatas.size() + 1); } windowDatas.insert(windowDatas.begin() + after + 1, w); w->column = self; w->windowSize = 1.F / (float)(windowDatas.size()); } size_t SColumnData::idx(PHLWINDOW w) { for (size_t i = 0; i < windowDatas.size(); ++i) { if (windowDatas[i]->window == w) return i; } return 0; } size_t SColumnData::idxForHeight(float y) { for (size_t i = 0; i < windowDatas.size(); ++i) { if (windowDatas[i]->window->m_position.y < y) continue; return i - 1; } return windowDatas.size() - 1; } void SColumnData::remove(PHLWINDOW w) { const auto SIZE_BEFORE = windowDatas.size(); std::erase_if(windowDatas, [&w](const auto& e) { return e->window == w; }); if (SIZE_BEFORE == windowDatas.size() && SIZE_BEFORE > 0) return; float newMaxSize = 0.F; for (auto& wd : windowDatas) { newMaxSize += wd->windowSize; } for (auto& wd : windowDatas) { wd->windowSize *= 1.F / newMaxSize; } if (windowDatas.empty() && workspace) workspace->remove(self.lock()); } void SColumnData::up(SP w) { for (size_t i = 1; i < windowDatas.size(); ++i) { if (windowDatas[i] != w) continue; std::swap(windowDatas[i], windowDatas[i - 1]); break; } } void SColumnData::down(SP w) { for (size_t i = 0; i < windowDatas.size() - 1; ++i) { if (windowDatas[i] != w) continue; std::swap(windowDatas[i], windowDatas[i + 1]); break; } } SP SColumnData::next(SP w) { for (size_t i = 0; i < windowDatas.size() - 1; ++i) { if (windowDatas[i] != w) continue; return windowDatas[i + 1]; } return nullptr; } SP SColumnData::prev(SP w) { for (size_t i = 1; i < windowDatas.size(); ++i) { if (windowDatas[i] != w) continue; return windowDatas[i - 1]; } return nullptr; } bool SColumnData::has(PHLWINDOW w) { return std::ranges::find_if(windowDatas, [w](const auto& e) { return e->window == w; }) != windowDatas.end(); } SP SWorkspaceData::add() { static const auto PCOLWIDTH = CConfigValue("plugin:hyprscrolling:column_width"); auto col = columns.emplace_back(makeShared(self.lock())); col->self = col; col->columnWidth = *PCOLWIDTH; return col; } SP SWorkspaceData::add(int after) { static const auto PCOLWIDTH = CConfigValue("plugin:hyprscrolling:column_width"); auto col = makeShared(self.lock()); col->self = col; col->columnWidth = *PCOLWIDTH; columns.insert(columns.begin() + after + 1, col); return col; } int64_t SWorkspaceData::idx(SP c) { for (size_t i = 0; i < columns.size(); ++i) { if (columns[i] == c) return i; } return -1; } void SWorkspaceData::remove(SP c) { std::erase(columns, c); } SP SWorkspaceData::next(SP c) { for (size_t i = 0; i < columns.size(); ++i) { if (columns[i] != c) continue; if (i == columns.size() - 1) return nullptr; return columns[i + 1]; } return nullptr; } SP SWorkspaceData::prev(SP c) { for (size_t i = 0; i < columns.size(); ++i) { if (columns[i] != c) continue; if (i == 0) return nullptr; return columns[i - 1]; } return nullptr; } void SWorkspaceData::centerCol(SP c) { if (!c) return; static const auto PFSONONE = CConfigValue("plugin:hyprscrolling:fullscreen_on_one_column"); PHLMONITOR PMONITOR = workspace->m_monitor.lock(); double currentLeft = 0; const auto USABLE = layout->usableAreaFor(PMONITOR); for (const auto& COL : columns) { const double ITEM_WIDTH = *PFSONONE && columns.size() == 1 ? USABLE.w : USABLE.w * COL->columnWidth; if (COL != c) currentLeft += ITEM_WIDTH; else { leftOffset = currentLeft - (USABLE.w - ITEM_WIDTH) / 2.F; return; } } } void SWorkspaceData::fitCol(SP c) { if (!c) return; static const auto PFSONONE = CConfigValue("plugin:hyprscrolling:fullscreen_on_one_column"); PHLMONITOR PMONITOR = workspace->m_monitor.lock(); double currentLeft = 0; const auto USABLE = layout->usableAreaFor(PMONITOR); for (const auto& COL : columns) { const double ITEM_WIDTH = *PFSONONE && columns.size() == 1 ? USABLE.w : USABLE.w * COL->columnWidth; if (COL != c) currentLeft += ITEM_WIDTH; else { leftOffset = std::clamp((double)leftOffset, currentLeft - USABLE.w + ITEM_WIDTH, currentLeft); return; } } } void SWorkspaceData::centerOrFitCol(SP c) { if (!c) return; static const auto PFITMETHOD = CConfigValue("plugin:hyprscrolling:focus_fit_method"); if (*PFITMETHOD == 1) fitCol(c); else centerCol(c); } SP SWorkspaceData::atCenter() { static const auto PFSONONE = CConfigValue("plugin:hyprscrolling:fullscreen_on_one_column"); PHLMONITOR PMONITOR = workspace->m_monitor.lock(); double currentLeft = leftOffset; const auto USABLE = layout->usableAreaFor(PMONITOR); for (const auto& COL : columns) { const double ITEM_WIDTH = *PFSONONE && columns.size() == 1 ? USABLE.w : USABLE.w * COL->columnWidth; currentLeft += ITEM_WIDTH; if (currentLeft >= PMONITOR->m_size.x / 2.0 - 2) return COL; } return nullptr; } void SWorkspaceData::recalculate(bool forceInstant) { static const auto PFSONONE = CConfigValue("plugin:hyprscrolling:fullscreen_on_one_column"); if (!workspace) { Log::logger->log(Log::ERR, "[scroller] broken internal state on workspace data"); return; } const auto MAX_WIDTH = maxWidth(); PHLMONITOR PMONITOR = workspace->m_monitor.lock(); const CBox USABLE = layout->usableAreaFor(PMONITOR); double currentLeft = 0; const double cameraLeft = MAX_WIDTH < USABLE.w ? std::round((MAX_WIDTH - USABLE.w) / 2.0) : leftOffset; // layout pixels for (size_t i = 0; i < columns.size(); ++i) { const auto& COL = columns[i]; double currentTop = 0.0; const double ITEM_WIDTH = *PFSONONE && columns.size() == 1 ? USABLE.w : USABLE.w * COL->columnWidth; for (const auto& WINDOW : COL->windowDatas) { WINDOW->layoutBox = CBox{currentLeft, currentTop, ITEM_WIDTH, WINDOW->windowSize * USABLE.h}.translate(PMONITOR->logicalBoxMinusReserved().pos() + Vector2D{-cameraLeft, 0.0}); currentTop += WINDOW->windowSize * USABLE.h; layout->applyNodeDataToWindow(WINDOW, forceInstant, i != columns.size() - 1, i != 0); } currentLeft += ITEM_WIDTH; if (currentLeft == USABLE.width) currentLeft++; // avoid ffm from "grabbing" the window on the right } } double SWorkspaceData::maxWidth() { static const auto PFSONONE = CConfigValue("plugin:hyprscrolling:fullscreen_on_one_column"); PHLMONITOR PMONITOR = workspace->m_monitor.lock(); double currentLeft = 0; const auto USABLE = layout->usableAreaFor(PMONITOR); for (const auto& COL : columns) { const double ITEM_WIDTH = *PFSONONE && columns.size() == 1 ? USABLE.w : USABLE.w * COL->columnWidth; currentLeft += ITEM_WIDTH; } return currentLeft; } bool SWorkspaceData::visible(SP c) { const auto USABLE = layout->usableAreaFor(workspace->m_monitor.lock()); float totalLeft = 0; for (const auto& col : columns) { if (col == c) { const float colLeft = totalLeft; const float colRight = totalLeft + col->columnWidth * USABLE.w; const float viewLeft = leftOffset; const float viewRight = leftOffset + USABLE.w; return colLeft < viewRight && viewLeft < colRight; } totalLeft += col->columnWidth * USABLE.w; } return false; } void CScrollingLayout::applyNodeDataToWindow(SP data, bool force, bool hasWindowsRight, bool hasWindowsLeft) { PHLMONITOR PMONITOR; PHLWORKSPACE PWORKSPACE; if (!data || !data->column || !data->column->workspace) { if (!data->overrideWorkspace) { Log::logger->log(Log::ERR, "[scroller] broken internal state on workspace (1)"); return; } PMONITOR = data->overrideWorkspace->m_monitor.lock(); PWORKSPACE = data->overrideWorkspace.lock(); } else { PMONITOR = data->column->workspace->workspace->m_monitor.lock(); PWORKSPACE = data->column->workspace->workspace.lock(); } if (!PMONITOR || !PWORKSPACE) { Log::logger->log(Log::ERR, "[scroller] broken internal state on workspace (2)"); return; } // for gaps outer const auto WORKAREA = PMONITOR->logicalBoxMinusReserved(); const bool DISPLAYLEFT = !hasWindowsLeft; const bool DISPLAYRIGHT = !hasWindowsRight; const bool DISPLAYTOP = STICKS(data->layoutBox.y, WORKAREA.y); const bool DISPLAYBOTTOM = STICKS(data->layoutBox.y + data->layoutBox.h, WORKAREA.y + WORKAREA.h); const auto PWINDOW = data->window.lock(); // get specific gaps and rules for this workspace, // if user specified them in config const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(PWORKSPACE); if (!validMapped(PWINDOW)) { Log::logger->log(Log::ERR, "Node {} holding invalid {}!!", (uintptr_t)data.get(), PWINDOW); onWindowRemovedTiling(PWINDOW); return; } if (PWINDOW->isFullscreen() && !data->ignoreFullscreenChecks) return; PWINDOW->m_ruleApplicator->resetProps(Desktop::Rule::RULE_PROP_ALL, Desktop::Types::PRIORITY_LAYOUT); PWINDOW->updateWindowData(); static auto PGAPSINDATA = CConfigValue("general:gaps_in"); static auto PGAPSOUTDATA = CConfigValue("general:gaps_out"); auto* const PGAPSIN = (CCssGapData*)(PGAPSINDATA.ptr())->getData(); auto* const PGAPSOUT = (CCssGapData*)(PGAPSOUTDATA.ptr())->getData(); auto gapsIn = WORKSPACERULE.gapsIn.value_or(*PGAPSIN); CBox nodeBox = data->layoutBox; nodeBox.round(); PWINDOW->m_size = nodeBox.size(); PWINDOW->m_position = nodeBox.pos(); PWINDOW->updateWindowDecos(); auto calcPos = PWINDOW->m_position; auto calcSize = PWINDOW->m_size; const auto OFFSETTOPLEFT = Vector2D((double)(DISPLAYLEFT ? 0 : gapsIn.m_left), (double)(DISPLAYTOP ? 0 : gapsIn.m_top)); const auto OFFSETBOTTOMRIGHT = Vector2D((double)(DISPLAYRIGHT ? 0 : gapsIn.m_right), (double)(DISPLAYBOTTOM ? 0 : gapsIn.m_bottom)); calcPos = calcPos + OFFSETTOPLEFT; calcSize = calcSize - OFFSETTOPLEFT - OFFSETBOTTOMRIGHT; if (PWINDOW->m_isPseudotiled) { // Calculate pseudo float scale = 1; // adjust if doesnt fit if (PWINDOW->m_pseudoSize.x > calcSize.x || PWINDOW->m_pseudoSize.y > calcSize.y) { if (PWINDOW->m_pseudoSize.x > calcSize.x) { scale = calcSize.x / PWINDOW->m_pseudoSize.x; } if (PWINDOW->m_pseudoSize.y * scale > calcSize.y) { scale = calcSize.y / PWINDOW->m_pseudoSize.y; } auto DELTA = calcSize - PWINDOW->m_pseudoSize * scale; calcSize = PWINDOW->m_pseudoSize * scale; calcPos = calcPos + DELTA / 2.f; // center } else { auto DELTA = calcSize - PWINDOW->m_pseudoSize; calcPos = calcPos + DELTA / 2.f; // center calcSize = PWINDOW->m_pseudoSize; } } const auto RESERVED = PWINDOW->getFullWindowReservedArea(); calcPos = calcPos + RESERVED.topLeft; calcSize = calcSize - (RESERVED.topLeft + RESERVED.bottomRight); if (PWINDOW->onSpecialWorkspace() && !PWINDOW->isFullscreen()) { // if special, we adjust the coords a bit static auto PSCALEFACTOR = CConfigValue("dwindle:special_scale_factor"); CBox wb = {calcPos + (calcSize - calcSize * *PSCALEFACTOR) / 2.f, calcSize * *PSCALEFACTOR}; wb.round(); // avoid rounding mess *PWINDOW->m_realPosition = wb.pos(); *PWINDOW->m_realSize = wb.size(); } else { CBox wb = {calcPos, calcSize}; wb.round(); // avoid rounding mess *PWINDOW->m_realSize = wb.size(); *PWINDOW->m_realPosition = wb.pos(); } if (force) { g_pHyprRenderer->damageWindow(PWINDOW); PWINDOW->m_realPosition->warp(); PWINDOW->m_realSize->warp(); g_pHyprRenderer->damageWindow(PWINDOW); } PWINDOW->updateWindowDecos(); } void CScrollingLayout::onEnable() { static const auto PCONFWIDTHS = CConfigValue("plugin:hyprscrolling:explicit_column_widths"); m_configCallback = g_pHookSystem->hookDynamic("configReloaded", [this](void* hk, SCallbackInfo& info, std::any param) { // bitch ass m_config.configuredWidths.clear(); CConstVarList widths(*PCONFWIDTHS, 0, ','); for (auto& w : widths) { try { m_config.configuredWidths.emplace_back(std::stof(std::string{w})); } catch (...) { Log::logger->log(Log::ERR, "scrolling: Failed to parse width {} as float", w); } } }); m_focusCallback = g_pHookSystem->hookDynamic("activeWindow", [this](void* hk, SCallbackInfo& info, std::any param) { const auto PWINDOW = std::any_cast(param); if (!PWINDOW) return; static const auto PFOLLOW_FOCUS = CConfigValue("plugin:hyprscrolling:follow_focus"); if (!*PFOLLOW_FOCUS) return; if (!PWINDOW->m_workspace->isVisible()) return; const auto DATA = dataFor(PWINDOW->m_workspace); const auto WINDOWDATA = dataFor(PWINDOW); if (!DATA || !WINDOWDATA) return; DATA->fitCol(WINDOWDATA->column.lock()); DATA->recalculate(); }); for (auto const& w : g_pCompositor->m_windows) { if (w->m_isFloating || !w->m_isMapped || w->isHidden()) continue; onWindowCreatedTiling(w); } } void CScrollingLayout::onDisable() { m_workspaceDatas.clear(); m_configCallback.reset(); } void CScrollingLayout::onWindowCreatedTiling(PHLWINDOW window, eDirection direction) { if (m_columnMoveState.isMovingColumn && window->m_workspace->m_id == m_columnMoveState.targetWorkspaceID) return; auto workspaceData = dataFor(window->m_workspace); if (!workspaceData) { Log::logger->log(Log::DEBUG, "[scrolling] No workspace data yet, creating"); workspaceData = m_workspaceDatas.emplace_back(makeShared(window->m_workspace, this)); workspaceData->self = workspaceData; } auto droppingOn = Desktop::focusState()->window(); if (droppingOn == window) droppingOn = g_pCompositor->vectorToWindowUnified(g_pInputManager->getMouseCoordsInternal(), Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS); SP droppingData = droppingOn ? dataFor(droppingOn) : nullptr; SP droppingColumn = droppingData ? droppingData->column.lock() : nullptr; Log::logger->log(Log::DEBUG, "[scrolling] new window {:x}, droppingColumn: {:x}, columns before: {}", (uintptr_t)window.get(), (uintptr_t)droppingColumn.get(), workspaceData->columns.size()); if (!droppingColumn) { auto col = workspaceData->add(); col->add(window); workspaceData->fitCol(col); } else { if (window->m_draggingTiled) { if (droppingOn) { const auto IDX = droppingColumn->idx(droppingOn); const auto TOP = droppingOn->getWindowIdealBoundingBoxIgnoreReserved().middle().y > g_pInputManager->getMouseCoordsInternal().y; droppingColumn->add(window, TOP ? (IDX == 0 ? -1 : IDX - 1) : (IDX)); } else droppingColumn->add(window); workspaceData->fitCol(droppingColumn); } else { auto idx = workspaceData->idx(droppingColumn); auto col = idx == -1 ? workspaceData->add() : workspaceData->add(idx); col->add(window); workspaceData->fitCol(col); } } workspaceData->recalculate(); } void CScrollingLayout::onWindowRemovedTiling(PHLWINDOW window) { const auto DATA = dataFor(window); if (!DATA) return; const auto WS = DATA->column->workspace.lock(); if (!WS->next(DATA->column.lock())) { // move the view if this is the last column const auto USABLE = usableAreaFor(window->m_monitor.lock()); WS->leftOffset -= USABLE.w * DATA->column->columnWidth; } DATA->column->remove(window); WS->recalculate(); if (!DATA->column) { // column got removed, let's ensure we don't leave any cringe extra space const auto USABLE = usableAreaFor(window->m_monitor.lock()); WS->leftOffset = std::clamp((double)WS->leftOffset, 0.0, std::max(WS->maxWidth() - USABLE.w, 1.0)); } } bool CScrollingLayout::isWindowTiled(PHLWINDOW window) { const auto DATA = dataFor(window); return DATA; } void CScrollingLayout::recalculateMonitor(const MONITORID& id) { const auto PMONITOR = g_pCompositor->getMonitorFromID(id); if (!PMONITOR || !PMONITOR->m_activeWorkspace) return; const auto DATA = dataFor(PMONITOR->m_activeWorkspace); if (!DATA) return; DATA->recalculate(); } void CScrollingLayout::recalculateWindow(PHLWINDOW window) { if (!window->m_workspace) return; const auto DATA = dataFor(window->m_workspace); if (!DATA) return; DATA->recalculate(); } void CScrollingLayout::onBeginDragWindow() { IHyprLayout::onBeginDragWindow(); } void CScrollingLayout::resizeActiveWindow(const Vector2D& delta, eRectCorner corner, PHLWINDOW pWindow) { const auto PWINDOW = pWindow ? pWindow : Desktop::focusState()->window(); Vector2D modDelta = delta; if (!validMapped(PWINDOW)) return; const auto DATA = dataFor(PWINDOW); if (!DATA) { *PWINDOW->m_realSize = (PWINDOW->m_realSize->goal() + delta) .clamp(PWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}), PWINDOW->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY})); PWINDOW->updateWindowDecos(); return; } if (!DATA->column || !DATA->column->workspace || !DATA->column->workspace->workspace || !DATA->column->workspace->workspace->m_monitor) return; const auto USABLE = usableAreaFor(DATA->column->workspace->workspace->m_monitor.lock()); const auto DELTA_AS_PERC = delta / USABLE.size(); const auto CURR_COLUMN = DATA->column.lock(); const auto NEXT_COLUMN = DATA->column->workspace->next(CURR_COLUMN); const auto PREV_COLUMN = DATA->column->workspace->prev(CURR_COLUMN); switch (corner) { case CORNER_BOTTOMLEFT: case CORNER_TOPLEFT: { if (!PREV_COLUMN) break; PREV_COLUMN->columnWidth = std::clamp(PREV_COLUMN->columnWidth + (float)DELTA_AS_PERC.x, MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH); CURR_COLUMN->columnWidth = std::clamp(CURR_COLUMN->columnWidth - (float)DELTA_AS_PERC.x, MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH); break; } case CORNER_BOTTOMRIGHT: case CORNER_TOPRIGHT: { if (!NEXT_COLUMN) break; NEXT_COLUMN->columnWidth = std::clamp(NEXT_COLUMN->columnWidth - (float)DELTA_AS_PERC.x, MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH); CURR_COLUMN->columnWidth = std::clamp(CURR_COLUMN->columnWidth + (float)DELTA_AS_PERC.x, MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH); break; } default: break; } if (DATA->column->windowDatas.size() > 1) { const auto CURR_WD = DATA; const auto NEXT_WD = DATA->column->next(DATA); const auto PREV_WD = DATA->column->prev(DATA); if (corner == CORNER_NONE) { if (!PREV_WD) corner = CORNER_BOTTOMRIGHT; else { corner = CORNER_TOPRIGHT; modDelta.y *= -1.0f; } } switch (corner) { case CORNER_BOTTOMLEFT: case CORNER_BOTTOMRIGHT: { if (!NEXT_WD) break; if (NEXT_WD->windowSize <= MIN_ROW_HEIGHT && delta.y >= 0) break; float adjust = std::clamp((float)(delta.y / USABLE.h), (-CURR_WD->windowSize + MIN_ROW_HEIGHT), (NEXT_WD->windowSize - MIN_ROW_HEIGHT)); NEXT_WD->windowSize = std::clamp(NEXT_WD->windowSize - adjust, MIN_ROW_HEIGHT, MAX_ROW_HEIGHT); CURR_WD->windowSize = std::clamp(CURR_WD->windowSize + adjust, MIN_ROW_HEIGHT, MAX_ROW_HEIGHT); break; } case CORNER_TOPLEFT: case CORNER_TOPRIGHT: { if (!PREV_WD) break; if (PREV_WD->windowSize <= MIN_ROW_HEIGHT && modDelta.y <= 0 || CURR_WD->windowSize <= MIN_ROW_HEIGHT && delta.y >= 0) break; float adjust = std::clamp((float)(modDelta.y / USABLE.h), -(PREV_WD->windowSize - MIN_ROW_HEIGHT), (CURR_WD->windowSize - MIN_ROW_HEIGHT)); PREV_WD->windowSize = std::clamp(PREV_WD->windowSize + adjust, MIN_ROW_HEIGHT, MAX_ROW_HEIGHT); CURR_WD->windowSize = std::clamp(CURR_WD->windowSize - adjust, MIN_ROW_HEIGHT, MAX_ROW_HEIGHT); break; } default: break; } } DATA->column->workspace->recalculate(true); } void CScrollingLayout::fullscreenRequestForWindow(PHLWINDOW pWindow, const eFullscreenMode CURRENT_EFFECTIVE_MODE, const eFullscreenMode EFFECTIVE_MODE) { const auto PMONITOR = pWindow->m_monitor.lock(); const auto PWORKSPACE = pWindow->m_workspace; // save position and size if floating if (pWindow->m_isFloating && CURRENT_EFFECTIVE_MODE == FSMODE_NONE) { pWindow->m_lastFloatingSize = pWindow->m_realSize->goal(); pWindow->m_lastFloatingPosition = pWindow->m_realPosition->goal(); pWindow->m_position = pWindow->m_realPosition->goal(); pWindow->m_size = pWindow->m_realSize->goal(); } const auto PNODE = dataFor(pWindow); if (EFFECTIVE_MODE == FSMODE_NONE) { // if it got its fullscreen disabled, set back its node if it had one if (PNODE) applyNodeDataToWindow(PNODE, false, false, false); else { // get back its' dimensions from position and size *pWindow->m_realPosition = pWindow->m_lastFloatingPosition; *pWindow->m_realSize = pWindow->m_lastFloatingSize; pWindow->m_ruleApplicator->resetProps(Desktop::Rule::RULE_PROP_ALL, Desktop::Types::PRIORITY_LAYOUT); pWindow->updateWindowData(); } } else { // apply new pos and size being monitors' box if (EFFECTIVE_MODE == FSMODE_FULLSCREEN) { *pWindow->m_realPosition = PMONITOR->m_position; *pWindow->m_realSize = PMONITOR->m_size; } else { // This is a massive hack. // We make a fake "only" node and apply // To keep consistent with the settings without C+P code SP fakeNode = makeShared(pWindow, nullptr); fakeNode->window = pWindow; fakeNode->layoutBox = PMONITOR->logicalBoxMinusReserved(); pWindow->m_size = fakeNode->layoutBox.size(); fakeNode->ignoreFullscreenChecks = true; fakeNode->overrideWorkspace = pWindow->m_workspace; applyNodeDataToWindow(fakeNode, false, false, false); } } g_pCompositor->changeWindowZOrder(pWindow, true); } void CScrollingLayout::focusWindowUpdate(PHLWINDOW pWindow) { if (!validMapped(pWindow)) { Desktop::focusState()->fullWindowFocus(nullptr); return; } Desktop::focusState()->fullWindowFocus(pWindow); const auto WINDOWDATA = dataFor(pWindow); if (WINDOWDATA) { if (auto col = WINDOWDATA->column.lock()) col->lastFocusedWindow = WINDOWDATA; } } SP CScrollingLayout::findBestNeighbor(SP pCurrent, SP pTargetCol) { if (!pCurrent || !pTargetCol || pTargetCol->windowDatas.empty()) return nullptr; const double currentTop = pCurrent->layoutBox.y; const double currentBottom = pCurrent->layoutBox.y + pCurrent->layoutBox.h; std::vector> overlappingWindows; for (const auto& candidate : pTargetCol->windowDatas) { const double candidateTop = candidate->layoutBox.y; const double candidateBottom = candidate->layoutBox.y + candidate->layoutBox.h; const bool overlaps = (candidateTop < currentBottom) && (candidateBottom > currentTop); if (overlaps) overlappingWindows.emplace_back(candidate); } if (!overlappingWindows.empty()) { auto lastFocused = pTargetCol->lastFocusedWindow.lock(); if (lastFocused) { auto it = std::ranges::find(overlappingWindows, lastFocused); if (it != overlappingWindows.end()) return lastFocused; } auto topmost = std::ranges::min_element(overlappingWindows, std::less<>{}, [](const SP& w) { return w->layoutBox.y; }); return *topmost; } if (!pTargetCol->windowDatas.empty()) return pTargetCol->windowDatas.front(); return nullptr; } std::any CScrollingLayout::layoutMessage(SLayoutMessageHeader header, std::string message) { static auto centerOrFit = [](const SP WS, const SP COL) -> void { static const auto PFITMETHOD = CConfigValue("plugin:hyprscrolling:focus_fit_method"); if (*PFITMETHOD == 1) WS->fitCol(COL); else WS->centerCol(COL); }; const auto ARGS = CVarList(message, 0, ' '); if (ARGS[0] == "move") { const auto DATA = currentWorkspaceData(); if (!DATA) return {}; if (ARGS[1] == "+col" || ARGS[1] == "col") { const auto WDATA = dataFor(Desktop::focusState()->window()); if (!WDATA) return {}; const auto COL = DATA->next(WDATA->column.lock()); if (!COL) { // move to max DATA->leftOffset = DATA->maxWidth(); DATA->recalculate(); focusWindowUpdate(nullptr); return {}; } centerOrFit(DATA, COL); DATA->recalculate(); focusWindowUpdate(COL->windowDatas.front()->window.lock()); g_pCompositor->warpCursorTo(COL->windowDatas.front()->window.lock()->middle()); return {}; } else if (ARGS[1] == "-col") { const auto WDATA = dataFor(Desktop::focusState()->window()); if (!WDATA) { if (DATA->columns.size() > 0) { DATA->centerCol(DATA->columns.back()); DATA->recalculate(); focusWindowUpdate((DATA->columns.back()->windowDatas.back())->window.lock()); g_pCompositor->warpCursorTo((DATA->columns.back()->windowDatas.back())->window.lock()->middle()); } return {}; } const auto COL = DATA->prev(WDATA->column.lock()); if (!COL) return {}; centerOrFit(DATA, COL); DATA->recalculate(); focusWindowUpdate(COL->windowDatas.back()->window.lock()); g_pCompositor->warpCursorTo(COL->windowDatas.front()->window.lock()->middle()); return {}; } const auto PLUSMINUS = getPlusMinusKeywordResult(ARGS[1], 0); if (!PLUSMINUS.has_value()) return {}; DATA->leftOffset -= *PLUSMINUS; DATA->recalculate(); const auto ATCENTER = DATA->atCenter(); focusWindowUpdate(ATCENTER ? (*ATCENTER->windowDatas.begin())->window.lock() : nullptr); } else if (ARGS[0] == "colresize") { const auto WDATA = dataFor(Desktop::focusState()->window()); if (!WDATA) return {}; if (ARGS[1] == "all") { float abs = 0; try { abs = std::stof(ARGS[2]); } catch (...) { return {}; } for (const auto& c : WDATA->column->workspace->columns) { c->columnWidth = abs; } WDATA->column->workspace->recalculate(); return {}; } CScopeGuard x([WDATA] { WDATA->column->columnWidth = std::clamp(WDATA->column->columnWidth, MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH); WDATA->column->workspace->centerOrFitCol(WDATA->column.lock()); WDATA->column->workspace->recalculate(); }); if (ARGS[1][0] == '+' || ARGS[1][0] == '-') { if (ARGS[1] == "+conf") { for (size_t i = 0; i < m_config.configuredWidths.size(); ++i) { if (m_config.configuredWidths[i] > WDATA->column->columnWidth) { WDATA->column->columnWidth = m_config.configuredWidths[i]; break; } if (i == m_config.configuredWidths.size() - 1) WDATA->column->columnWidth = m_config.configuredWidths[0]; } return {}; } else if (ARGS[1] == "-conf") { for (size_t i = m_config.configuredWidths.size() - 1;; --i) { if (m_config.configuredWidths[i] < WDATA->column->columnWidth) { WDATA->column->columnWidth = m_config.configuredWidths[i]; break; } if (i == 0) { WDATA->column->columnWidth = m_config.configuredWidths.back(); break; } } return {}; } const auto PLUSMINUS = getPlusMinusKeywordResult(ARGS[1], 0); if (!PLUSMINUS.has_value()) return {}; WDATA->column->columnWidth += *PLUSMINUS; } else { float abs = 0; try { abs = std::stof(ARGS[1]); } catch (...) { return {}; } WDATA->column->columnWidth = abs; } } else if (ARGS[0] == "movewindowto") { moveWindowTo(Desktop::focusState()->window(), ARGS[1], false); } else if (ARGS[0] == "fit") { if (ARGS[1] == "active") { // fit the current column to 1.F const auto WDATA = dataFor(Desktop::focusState()->window()); const auto WORKDATA = dataFor(Desktop::focusState()->window()->m_workspace); if (!WDATA || !WORKDATA || WORKDATA->columns.size() == 0) return {}; const auto USABLE = usableAreaFor(WORKDATA->workspace->m_monitor.lock()); WDATA->column->columnWidth = 1.F; WORKDATA->leftOffset = 0; for (size_t i = 0; i < WORKDATA->columns.size(); ++i) { if (WORKDATA->columns[i]->has(Desktop::focusState()->window())) break; WORKDATA->leftOffset += USABLE.w * WORKDATA->columns[i]->columnWidth; } WDATA->column->workspace->recalculate(); } else if (ARGS[1] == "all") { // fit all columns on screen const auto WDATA = dataFor(Desktop::focusState()->window()->m_workspace); if (!WDATA || WDATA->columns.size() == 0) return {}; const size_t LEN = WDATA->columns.size(); for (const auto& c : WDATA->columns) { c->columnWidth = 1.F / (float)LEN; } WDATA->recalculate(); } else if (ARGS[1] == "toend") { // fit all columns on screen that start from the current and end on the last const auto WDATA = dataFor(Desktop::focusState()->window()->m_workspace); if (!WDATA || WDATA->columns.size() == 0) return {}; bool begun = false; size_t foundAt = 0; for (size_t i = 0; i < WDATA->columns.size(); ++i) { if (!begun && !WDATA->columns[i]->has(Desktop::focusState()->window())) continue; if (!begun) { begun = true; foundAt = i; } WDATA->columns[i]->columnWidth = 1.F / (float)(WDATA->columns.size() - i); } if (!begun) return {}; const auto USABLE = usableAreaFor(WDATA->workspace->m_monitor.lock()); WDATA->leftOffset = 0; for (size_t i = 0; i < foundAt; ++i) { WDATA->leftOffset += USABLE.w * WDATA->columns[i]->columnWidth; } WDATA->recalculate(); } else if (ARGS[1] == "tobeg") { // fit all columns on screen that start from the current and end on the last const auto WDATA = dataFor(Desktop::focusState()->window()->m_workspace); if (!WDATA || WDATA->columns.size() == 0) return {}; bool begun = false; size_t foundAt = 0; for (int64_t i = (int64_t)WDATA->columns.size() - 1; i >= 0; --i) { if (!begun && !WDATA->columns[i]->has(Desktop::focusState()->window())) continue; if (!begun) { begun = true; foundAt = i; } WDATA->columns[i]->columnWidth = 1.F / (float)(foundAt + 1); } if (!begun) return {}; WDATA->leftOffset = 0; WDATA->recalculate(); } else if (ARGS[1] == "visible") { // fit all columns on screen that start from the current and end on the last const auto WDATA = dataFor(Desktop::focusState()->window()->m_workspace); if (!WDATA || WDATA->columns.size() == 0) return {}; bool begun = false; size_t foundAt = 0; std::vector> visible; for (size_t i = 0; i < WDATA->columns.size(); ++i) { if (!begun && !WDATA->visible(WDATA->columns[i])) continue; if (!begun) { begun = true; foundAt = i; } if (!WDATA->visible(WDATA->columns[i])) break; visible.emplace_back(WDATA->columns[i]); } if (!begun) return {}; WDATA->leftOffset = 0; if (foundAt != 0) { const auto USABLE = usableAreaFor(WDATA->workspace->m_monitor.lock()); for (size_t i = 0; i < foundAt; ++i) { WDATA->leftOffset += USABLE.w * WDATA->columns[i]->columnWidth; } } for (const auto& v : visible) { v->columnWidth = 1.F / (float)visible.size(); } WDATA->recalculate(); } } else if (ARGS[0] == "focus") { const auto WDATA = dataFor(Desktop::focusState()->window()); static const auto PNOFALLBACK = CConfigValue("general:no_focus_fallback"); if (!WDATA || ARGS[1].empty()) return {}; switch (ARGS[1][0]) { case 'u': case 't': { auto PREV = WDATA->column->prev(WDATA); if (!PREV) { if (*PNOFALLBACK) break; else PREV = WDATA->column->windowDatas.back(); } focusWindowUpdate(PREV->window.lock()); g_pCompositor->warpCursorTo(PREV->window.lock()->middle()); break; } case 'b': case 'd': { auto NEXT = WDATA->column->next(WDATA); if (!NEXT) { if (*PNOFALLBACK) break; else NEXT = WDATA->column->windowDatas.front(); } focusWindowUpdate(NEXT->window.lock()); g_pCompositor->warpCursorTo(NEXT->window.lock()->middle()); break; } case 'l': { auto PREV = WDATA->column->workspace->prev(WDATA->column.lock()); if (!PREV) { if (*PNOFALLBACK) { centerOrFit(WDATA->column->workspace.lock(), WDATA->column.lock()); WDATA->column->workspace->recalculate(); g_pCompositor->warpCursorTo(WDATA->window.lock()->middle()); break; } else PREV = WDATA->column->workspace->columns.back(); } auto pTargetWindowData = findBestNeighbor(WDATA, PREV); if (pTargetWindowData) { focusWindowUpdate(pTargetWindowData->window.lock()); centerOrFit(WDATA->column->workspace.lock(), PREV); WDATA->column->workspace->recalculate(); g_pCompositor->warpCursorTo(pTargetWindowData->window.lock()->middle()); } break; } case 'r': { auto NEXT = WDATA->column->workspace->next(WDATA->column.lock()); if (!NEXT) { if (*PNOFALLBACK) { centerOrFit(WDATA->column->workspace.lock(), WDATA->column.lock()); WDATA->column->workspace->recalculate(); g_pCompositor->warpCursorTo(WDATA->window.lock()->middle()); break; } else NEXT = WDATA->column->workspace->columns.front(); } auto pTargetWindowData = findBestNeighbor(WDATA, NEXT); if (pTargetWindowData) { focusWindowUpdate(pTargetWindowData->window.lock()); centerOrFit(WDATA->column->workspace.lock(), NEXT); WDATA->column->workspace->recalculate(); g_pCompositor->warpCursorTo(pTargetWindowData->window.lock()->middle()); } break; } default: return {}; } } else if (ARGS[0] == "promote") { const auto WDATA = dataFor(Desktop::focusState()->window()); if (!WDATA) return {}; auto idx = WDATA->column->workspace->idx(WDATA->column.lock()); auto col = idx == -1 ? WDATA->column->workspace->add() : WDATA->column->workspace->add(idx); WDATA->column->remove(WDATA->window.lock()); col->add(WDATA); WDATA->column->workspace->recalculate(); } else if (ARGS[0] == "swapcol") { if (ARGS.size() < 2) return {}; const auto WDATA = dataFor(Desktop::focusState()->window()); if (!WDATA) return {}; const auto CURRENT_COL = WDATA->column.lock(); if (!CURRENT_COL) return {}; const auto WS_DATA = CURRENT_COL->workspace.lock(); if (!WS_DATA || WS_DATA->columns.size() < 2) return {}; const int64_t current_idx = WS_DATA->idx(CURRENT_COL); const size_t col_count = WS_DATA->columns.size(); if (current_idx == -1) return {}; const std::string& direction = ARGS[1]; int64_t target_idx = -1; if (direction == "l") target_idx = (current_idx == 0) ? (col_count - 1) : (current_idx - 1); else if (direction == "r") target_idx = (current_idx == (int64_t)col_count - 1) ? 0 : (current_idx + 1); else return {}; std::swap(WS_DATA->columns[current_idx], WS_DATA->columns[target_idx]); WS_DATA->centerOrFitCol(CURRENT_COL); WS_DATA->recalculate(); } else if (ARGS[0] == "movecoltoworkspace") { if (ARGS.size() < 2) return {}; const auto WDATA = dataFor(Desktop::focusState()->window()); if (!WDATA) return {}; const auto CURRENT_COL = WDATA->column.lock(); if (!CURRENT_COL) return {}; const auto SOURCE_WS_DATA = CURRENT_COL->workspace.lock(); if (!SOURCE_WS_DATA) return {}; const auto PMONITOR = Desktop::focusState()->monitor(); if (!PMONITOR) return {}; PHLWORKSPACE PWORKSPACE = nullptr; const std::string& arg = ARGS[1]; if (arg.starts_with("+") || arg.starts_with("-")) { try { const int offset = std::stoi(arg); const int currentWorkspaceID = WDATA->window->m_workspace->m_id; const int targetWorkspaceID = currentWorkspaceID + offset; if (targetWorkspaceID < 1) return {}; PWORKSPACE = g_pCompositor->getWorkspaceByID(targetWorkspaceID); if (!PWORKSPACE) PWORKSPACE = g_pCompositor->createNewWorkspace(targetWorkspaceID, PMONITOR->m_id); } catch (...) { return {}; } } else if (arg == "special") { const int SPECIAL_WORKSPACE_ID = -99; PWORKSPACE = g_pCompositor->getWorkspaceByID(SPECIAL_WORKSPACE_ID); if (!PWORKSPACE) PWORKSPACE = g_pCompositor->createNewWorkspace(SPECIAL_WORKSPACE_ID, PMONITOR->m_id, "special"); } else { PWORKSPACE = g_pCompositor->getWorkspaceByString(arg); if (!PWORKSPACE) { try { const int workspaceID = std::stoi(arg); PWORKSPACE = g_pCompositor->getWorkspaceByID(workspaceID); if (!PWORKSPACE) PWORKSPACE = g_pCompositor->createNewWorkspace(workspaceID, PMONITOR->m_id); } catch (const std::invalid_argument&) { PWORKSPACE = g_pCompositor->createNewWorkspace(0, PMONITOR->m_id, arg); } catch (const std::out_of_range&) { return {}; } } } if (!PWORKSPACE) return {}; if (PWORKSPACE == WDATA->window->m_workspace) return {}; auto targetWorkspaceData = dataFor(PWORKSPACE); if (!targetWorkspaceData) { targetWorkspaceData = m_workspaceDatas.emplace_back(makeShared(PWORKSPACE, this)); targetWorkspaceData->self = targetWorkspaceData; } const auto NEW_COL = targetWorkspaceData->add(); NEW_COL->columnWidth = CURRENT_COL->columnWidth; NEW_COL->windowDatas = CURRENT_COL->windowDatas; for (const auto& wd : NEW_COL->windowDatas) { wd->column = NEW_COL; } std::vector windowsToMove; for (const auto& wd : CURRENT_COL->windowDatas) { windowsToMove.push_back(wd->window.lock()); } CURRENT_COL->windowDatas.clear(); SOURCE_WS_DATA->remove(CURRENT_COL); CScopeGuard sg([this]() { m_columnMoveState.isMovingColumn = false; m_columnMoveState.targetWorkspaceID = -1; for (auto& ws : m_workspaceDatas) { ws->recalculate(); } }); m_columnMoveState.isMovingColumn = true; m_columnMoveState.targetWorkspaceID = PWORKSPACE->m_id; for (const auto& win : windowsToMove) { g_pCompositor->moveWindowToWorkspaceSafe(win, PWORKSPACE); } Desktop::focusState()->fullWindowFocus(windowsToMove.front()); g_pCompositor->warpCursorTo(windowsToMove.front()->middle()); } else if (ARGS[0] == "togglefit") { static const auto PFITMETHOD = CConfigValue("plugin:hyprscrolling:focus_fit_method"); auto& fitMethod = *PFITMETHOD.ptr(); const int toggled = fitMethod ^ 1; fitMethod = toggled; const auto focusedData = dataFor(Desktop::focusState()->window()); static const auto PFSONONE = CConfigValue("plugin:hyprscrolling:fullscreen_on_one_column"); for (const auto& ws : m_workspaceDatas) { if (!ws || ws->columns.empty()) continue; const auto monitor = ws->workspace->m_monitor.lock(); if (!monitor) continue; const auto USABLE = usableAreaFor(monitor); const auto focusedColumn = (focusedData && focusedData->column && focusedData->column->workspace.lock() == ws) ? focusedData->column.lock() : nullptr; const auto fallbackColumn = ws->atCenter(); if (toggled == 1) { const auto columnToFit = focusedColumn ? focusedColumn : fallbackColumn; if (!columnToFit) continue; double currentLeft = 0.0; for (const auto& col : ws->columns) { const double itemWidth = *PFSONONE && ws->columns.size() == 1 ? USABLE.w : USABLE.w * col->columnWidth; if (col == columnToFit) { const double colLeft = currentLeft; const double colRight = currentLeft + itemWidth; const double scrollMax = std::max(ws->maxWidth() - USABLE.w, 0.0); double desiredOffset; if (col == ws->columns.front()) desiredOffset = 0.0; else desiredOffset = std::clamp(colRight - USABLE.w, 0.0, scrollMax); ws->leftOffset = desiredOffset; break; } currentLeft += itemWidth; } } else { const auto columnToCenter = focusedColumn ? focusedColumn : fallbackColumn; if (!columnToCenter) continue; ws->centerCol(columnToCenter); } ws->recalculate(); } } return {}; } SWindowRenderLayoutHints CScrollingLayout::requestRenderHints(PHLWINDOW a) { return {}; } void CScrollingLayout::switchWindows(PHLWINDOW a, PHLWINDOW b) { const auto DATA1 = dataFor(a); const auto DATA2 = dataFor(b); std::swap(DATA1->window, DATA2->window); const auto WS1 = DATA1->column->workspace.lock(); const auto WS2 = DATA2->column->workspace.lock(); WS1->recalculate(); if (WS1 == WS2) return; WS2->recalculate(); } void CScrollingLayout::moveWindowTo(PHLWINDOW w, const std::string& dir, bool silent) { const auto DATA = dataFor(w); if (!DATA) return; const auto WS = DATA->column->workspace.lock(); if (dir == "l") { const auto COL = WS->prev(DATA->column.lock()); DATA->column->remove(w); if (!COL) { const auto NEWCOL = WS->add(-1); NEWCOL->add(DATA); WS->centerOrFitCol(NEWCOL); } else { if (COL->windowDatas.size() > 0) COL->add(DATA, COL->idxForHeight(g_pInputManager->getMouseCoordsInternal().y)); else COL->add(DATA); WS->centerOrFitCol(COL); } } else if (dir == "r") { const auto COL = WS->next(DATA->column.lock()); DATA->column->remove(w); if (!COL) { // make a new one const auto NEWCOL = WS->add(); NEWCOL->add(DATA); WS->centerOrFitCol(NEWCOL); } else { if (COL->windowDatas.size() > 0) COL->add(DATA, COL->idxForHeight(g_pInputManager->getMouseCoordsInternal().y)); else COL->add(DATA); WS->centerOrFitCol(COL); } } else if (dir == "t" || dir == "u") DATA->column->up(DATA); else if (dir == "b" || dir == "d") DATA->column->down(DATA); WS->recalculate(); focusWindowUpdate(w); g_pCompositor->warpCursorTo(w->middle()); } void CScrollingLayout::alterSplitRatio(PHLWINDOW, float, bool) { ; } std::string CScrollingLayout::getLayoutName() { return "scrolling"; } void CScrollingLayout::replaceWindowDataWith(PHLWINDOW from, PHLWINDOW to) { ; } Vector2D CScrollingLayout::predictSizeForNewWindowTiled() { return Vector2D{}; } SP CScrollingLayout::dataFor(PHLWORKSPACE ws) { for (const auto& e : m_workspaceDatas) { if (e->workspace != ws) continue; return e; } return nullptr; } SP CScrollingLayout::dataFor(PHLWINDOW w) { if (!w) return nullptr; for (const auto& e : m_workspaceDatas) { if (e->workspace != w->m_workspace) continue; for (const auto& c : e->columns) { for (const auto& d : c->windowDatas) { if (d->window != w) continue; return d; } } } return nullptr; } SP CScrollingLayout::currentWorkspaceData() { if (!Desktop::focusState()->monitor() || !Desktop::focusState()->monitor()->m_activeWorkspace) return nullptr; // FIXME: special return dataFor(Desktop::focusState()->monitor()->m_activeWorkspace); } CBox CScrollingLayout::usableAreaFor(PHLMONITOR m) { return m->logicalBoxMinusReserved().translate(-m->m_position); } hyprwm-hyprland-plugins-4dbef35/hyprscrolling/Scrolling.hpp000066400000000000000000000126611512451410000243770ustar00rootroot00000000000000#pragma once #include #include #include #include class CScrollingLayout; struct SColumnData; struct SWorkspaceData; struct SScrollingWindowData { SScrollingWindowData(PHLWINDOW w, SP col, float ws = 1.F) : window(w), column(col), windowSize(ws) { ; } PHLWINDOWREF window; WP column; float windowSize = 1.F; bool ignoreFullscreenChecks = false; PHLWORKSPACEREF overrideWorkspace; CBox layoutBox; }; struct SColumnData { SColumnData(SP ws) : workspace(ws) { ; } void add(PHLWINDOW w); void add(PHLWINDOW w, int after); void add(SP w); void add(SP w, int after); void remove(PHLWINDOW w); bool has(PHLWINDOW w); size_t idx(PHLWINDOW w); // index of lowest window that is above y. size_t idxForHeight(float y); void up(SP w); void down(SP w); SP next(SP w); SP prev(SP w); std::vector> windowDatas; float columnSize = 1.F; float columnWidth = 1.F; WP workspace; WP lastFocusedWindow; WP self; }; struct SWorkspaceData { SWorkspaceData(PHLWORKSPACE w, CScrollingLayout* l) : workspace(w), layout(l) { ; } PHLWORKSPACEREF workspace; std::vector> columns; float leftOffset = 0; SP add(); SP add(int after); int64_t idx(SP c); void remove(SP c); double maxWidth(); SP next(SP c); SP prev(SP c); SP atCenter(); bool visible(SP c); void centerCol(SP c); void fitCol(SP c); void centerOrFitCol(SP c); void recalculate(bool forceInstant = false); CScrollingLayout* layout = nullptr; WP self; }; class CScrollingLayout : public IHyprLayout { public: virtual void onWindowCreatedTiling(PHLWINDOW, eDirection direction = DIRECTION_DEFAULT); virtual void onWindowRemovedTiling(PHLWINDOW); virtual bool isWindowTiled(PHLWINDOW); virtual void recalculateMonitor(const MONITORID&); virtual void recalculateWindow(PHLWINDOW); virtual void onBeginDragWindow(); virtual void resizeActiveWindow(const Vector2D&, eRectCorner corner = CORNER_NONE, PHLWINDOW pWindow = nullptr); virtual void fullscreenRequestForWindow(PHLWINDOW pWindow, const eFullscreenMode CURRENT_EFFECTIVE_MODE, const eFullscreenMode EFFECTIVE_MODE); virtual std::any layoutMessage(SLayoutMessageHeader, std::string); virtual SWindowRenderLayoutHints requestRenderHints(PHLWINDOW); virtual void switchWindows(PHLWINDOW, PHLWINDOW); virtual void moveWindowTo(PHLWINDOW, const std::string& dir, bool silent); virtual void alterSplitRatio(PHLWINDOW, float, bool); virtual std::string getLayoutName(); virtual void replaceWindowDataWith(PHLWINDOW from, PHLWINDOW to); virtual Vector2D predictSizeForNewWindowTiled(); virtual void onEnable(); virtual void onDisable(); CBox usableAreaFor(PHLMONITOR m); private: std::vector> m_workspaceDatas; SP m_configCallback; SP m_focusCallback; struct { bool isMovingColumn = false; int targetWorkspaceID = -1; } m_columnMoveState; struct { std::vector configuredWidths; } m_config; SP findBestNeighbor(SP pCurrent, SP pTargetCol); SP dataFor(PHLWORKSPACE ws); SP dataFor(PHLWINDOW w); SP currentWorkspaceData(); void applyNodeDataToWindow(SP node, bool instant, bool hasWindowsRight, bool hasWindowsLeft); void focusWindowUpdate(PHLWINDOW pWindow); friend struct SWorkspaceData; }; hyprwm-hyprland-plugins-4dbef35/hyprscrolling/default.nix000066400000000000000000000006351512451410000240740ustar00rootroot00000000000000{ lib, hyprland, hyprlandPlugins, }: hyprlandPlugins.mkHyprlandPlugin { pluginName = "hyprscrolling"; version = "0.1"; src = ./.; inherit (hyprland) nativeBuildInputs; meta = with lib; { homepage = "https://github.com/hyprwm/hyprland-plugins/tree/main/hyprscrolling"; description = "Hyprland scrolling layout plugin"; license = licenses.bsd3; platforms = platforms.linux; }; } hyprwm-hyprland-plugins-4dbef35/hyprscrolling/globals.hpp000066400000000000000000000001351512451410000240570ustar00rootroot00000000000000#pragma once #include inline HANDLE PHANDLE = nullptr;hyprwm-hyprland-plugins-4dbef35/hyprscrolling/main.cpp000066400000000000000000000047651512451410000233700ustar00rootroot00000000000000#define WLR_USE_UNSTABLE #include #include #include #define private public #include #include #include #include #include #undef private #include using namespace Hyprutils::String; #include "globals.hpp" #include "Scrolling.hpp" // Do NOT change this function. APICALL EXPORT std::string PLUGIN_API_VERSION() { return HYPRLAND_API_VERSION; } UP g_pScrollingLayout; // APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { PHANDLE = handle; const std::string HASH = __hyprland_api_get_hash(); const std::string CLIENT_HASH = __hyprland_api_get_client_hash(); if (HASH != CLIENT_HASH) { HyprlandAPI::addNotification(PHANDLE, "[hyprscrolling] Failure in initialization: Version mismatch (headers ver is not equal to running hyprland ver)", CHyprColor{1.0, 0.2, 0.2, 1.0}, 5000); throw std::runtime_error("[hs] Version mismatch"); } bool success = true; g_pScrollingLayout = makeUnique(); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprscrolling:fullscreen_on_one_column", Hyprlang::INT{0}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprscrolling:column_width", Hyprlang::FLOAT{0.5F}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprscrolling:focus_fit_method", Hyprlang::INT{0}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprscrolling:follow_focus", Hyprlang::INT{1}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprscrolling:explicit_column_widths", Hyprlang::STRING{"0.333, 0.5, 0.667, 1.0"}); HyprlandAPI::addLayout(PHANDLE, "scrolling", g_pScrollingLayout.get()); if (success) HyprlandAPI::addNotification(PHANDLE, "[hyprscrolling] Initialized successfully!", CHyprColor{0.2, 1.0, 0.2, 1.0}, 5000); else { HyprlandAPI::addNotification(PHANDLE, "[hyprscrolling] Failure in initialization: failed to register dispatchers", CHyprColor{1.0, 0.2, 0.2, 1.0}, 5000); throw std::runtime_error("[hs] Dispatchers failed"); } return {"hyprscrolling", "A plugin to add a scrolling layout to hyprland", "Vaxry", "1.0"}; } APICALL EXPORT void PLUGIN_EXIT() { HyprlandAPI::removeLayout(PHANDLE, g_pScrollingLayout.get()); g_pScrollingLayout.reset(); } hyprwm-hyprland-plugins-4dbef35/hyprscrolling/meson.build000066400000000000000000000016251512451410000240720ustar00rootroot00000000000000project('hyprscrolling', 'cpp', version: '0.1', default_options: ['buildtype=release'], ) cpp_compiler = meson.get_compiler('cpp') if cpp_compiler.has_argument('-std=c++23') add_global_arguments('-std=c++23', language: 'cpp') elif cpp_compiler.has_argument('-std=c++2b') add_global_arguments('-std=c++2b', language: 'cpp') else error('Could not configure current C++ compiler (' + cpp_compiler.get_id() + ' ' + cpp_compiler.version() + ') with required C++ standard (C++23)') endif globber = run_command('find', '.', '-name', '*.cpp', check: true) src = globber.stdout().strip().split('\n') shared_module(meson.project_name(), src, dependencies: [ dependency('hyprland'), dependency('pixman-1'), dependency('libdrm'), dependency('pangocairo'), dependency('libinput'), dependency('libudev'), dependency('wayland-server'), dependency('xkbcommon'), ], install: true, ) hyprwm-hyprland-plugins-4dbef35/hyprtrails/000077500000000000000000000000001512451410000212265ustar00rootroot00000000000000hyprwm-hyprland-plugins-4dbef35/hyprtrails/CMakeLists.txt000066400000000000000000000007731512451410000237750ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.27) project(hyprtrails DESCRIPTION "hyprtrails plugin for Hyprland" VERSION 0.1 ) set(CMAKE_CXX_STANDARD 23) file(GLOB_RECURSE SRC "*.cpp") add_library(hyprtrails SHARED ${SRC}) find_package(PkgConfig REQUIRED) pkg_check_modules(deps REQUIRED IMPORTED_TARGET hyprland libdrm libinput libudev pangocairo pixman-1 wayland-server xkbcommon ) target_link_libraries(hyprtrails PRIVATE rt PkgConfig::deps) install(TARGETS hyprtrails) hyprwm-hyprland-plugins-4dbef35/hyprtrails/Makefile000066400000000000000000000005521512451410000226700ustar00rootroot00000000000000# Else exist specifically for clang ifeq ($(CXX),g++) EXTRA_FLAGS = --no-gnu-unique else EXTRA_FLAGS = endif all: $(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 clean: rm ./hyprtrails.so hyprwm-hyprland-plugins-4dbef35/hyprtrails/README.md000066400000000000000000000006441512451410000225110ustar00rootroot00000000000000# hyprtrails A neat, but useless plugin to add trails behind windows: https://github.com/hyprwm/hyprland-plugins/assets/43317083/6c31b839-92cd-4510-bb12-110d77dd5b44 Maybe isn't the most efficient. The curve-related settings are only for advanced users. Be warned they _incredibly_ impact performance. Only setting you may want to change: ```ini plugin { hyprtrails { color = rgba(ffaa00ff) } } ``` hyprwm-hyprland-plugins-4dbef35/hyprtrails/TrailPassElement.cpp000066400000000000000000000007251512451410000251520ustar00rootroot00000000000000#include "TrailPassElement.hpp" #include #include "trail.hpp" CTrailPassElement::CTrailPassElement(const CTrailPassElement::STrailData& data_) : data(data_) { ; } void CTrailPassElement::draw(const CRegion& damage) { data.deco->renderPass(g_pHyprOpenGL->m_renderData.pMonitor.lock(), data.a); } bool CTrailPassElement::needsLiveBlur() { return false; } bool CTrailPassElement::needsPrecomputeBlur() { return false; }hyprwm-hyprland-plugins-4dbef35/hyprtrails/TrailPassElement.hpp000066400000000000000000000011041512451410000251470ustar00rootroot00000000000000#pragma once #include class CTrail; class CTrailPassElement : public IPassElement { public: struct STrailData { CTrail* deco = nullptr; float a = 1.F; }; CTrailPassElement(const STrailData& data_); virtual ~CTrailPassElement() = default; virtual void draw(const CRegion& damage); virtual bool needsLiveBlur(); virtual bool needsPrecomputeBlur(); virtual const char* passName() { return "CTrailPassElement"; } private: STrailData data; };hyprwm-hyprland-plugins-4dbef35/hyprtrails/default.nix000066400000000000000000000006471512451410000234010ustar00rootroot00000000000000{ lib, hyprland, hyprlandPlugins, }: hyprlandPlugins.mkHyprlandPlugin { pluginName = "hyprtrails"; version = "0.1"; src = ./.; inherit (hyprland) nativeBuildInputs; meta = with lib; { homepage = "https://github.com/hyprwm/hyprland-plugins/tree/main/hyprtrails"; description = "Smooth trails behind moving windows for Hyprland"; license = licenses.bsd3; platforms = platforms.linux; }; } hyprwm-hyprland-plugins-4dbef35/hyprtrails/globals.hpp000066400000000000000000000003501512451410000233600ustar00rootroot00000000000000#pragma once #include inline HANDLE PHANDLE = nullptr; struct SGlobalState { SShader trailShader; wl_event_source* tick = nullptr; }; inline UP g_pGlobalState; hyprwm-hyprland-plugins-4dbef35/hyprtrails/main.cpp000066400000000000000000000120671512451410000226640ustar00rootroot00000000000000#define WLR_USE_UNSTABLE #include #include #include #include #include #include #include #include #include "globals.hpp" #include "shaders.hpp" #include "trail.hpp" // Do NOT change this function. APICALL EXPORT std::string PLUGIN_API_VERSION() { return HYPRLAND_API_VERSION; } void onNewWindow(void* self, std::any data) { // data is guaranteed const auto PWINDOW = std::any_cast(data); HyprlandAPI::addWindowDecoration(PHANDLE, PWINDOW, makeUnique(PWINDOW)); } GLuint CompileShader(const GLuint& type, std::string src) { auto shader = glCreateShader(type); auto shaderSource = src.c_str(); glShaderSource(shader, 1, (const GLchar**)&shaderSource, nullptr); glCompileShader(shader); GLint ok; glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); if (ok == GL_FALSE) throw std::runtime_error("compileShader() failed!"); return shader; } GLuint CreateProgram(const std::string& vert, const std::string& frag) { auto vertCompiled = CompileShader(GL_VERTEX_SHADER, vert); if (!vertCompiled) throw std::runtime_error("Compiling vshader failed."); auto fragCompiled = CompileShader(GL_FRAGMENT_SHADER, frag); if (!fragCompiled) throw std::runtime_error("Compiling fshader failed."); auto prog = glCreateProgram(); glAttachShader(prog, vertCompiled); glAttachShader(prog, fragCompiled); glLinkProgram(prog); glDetachShader(prog, vertCompiled); glDetachShader(prog, fragCompiled); glDeleteShader(vertCompiled); glDeleteShader(fragCompiled); GLint ok; glGetProgramiv(prog, GL_LINK_STATUS, &ok); if (ok == GL_FALSE) throw std::runtime_error("createProgram() failed! GL_LINK_STATUS not OK!"); return prog; } int onTick(void* data) { EMIT_HOOK_EVENT("trailTick", nullptr); const int TIMEOUT = g_pHyprRenderer->m_mostHzMonitor ? 1000.0 / g_pHyprRenderer->m_mostHzMonitor->m_refreshRate : 16; wl_event_source_timer_update(g_pGlobalState->tick, TIMEOUT); return 0; } void initGlobal() { g_pHyprRenderer->makeEGLCurrent(); GLuint prog = CreateProgram(QUADTRAIL, FRAGTRAIL); g_pGlobalState->trailShader.program = prog; g_pGlobalState->trailShader.uniformLocations[SHADER_PROJ] = glGetUniformLocation(prog, "proj"); g_pGlobalState->trailShader.uniformLocations[SHADER_TEX] = glGetUniformLocation(prog, "tex"); g_pGlobalState->trailShader.uniformLocations[SHADER_COLOR] = glGetUniformLocation(prog, "color"); g_pGlobalState->trailShader.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(prog, "pos"); g_pGlobalState->trailShader.uniformLocations[SHADER_GRADIENT] = glGetUniformLocation(prog, "snapshots"); g_pGlobalState->tick = wl_event_loop_add_timer(g_pCompositor->m_wlEventLoop, &onTick, nullptr); wl_event_source_timer_update(g_pGlobalState->tick, 1); } APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { PHANDLE = handle; const std::string HASH = __hyprland_api_get_hash(); const std::string CLIENT_HASH = __hyprland_api_get_client_hash(); if (HASH != CLIENT_HASH) { 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}, 5000); throw std::runtime_error("[ht] Version mismatch"); } HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprtrails:bezier_step", Hyprlang::FLOAT{0.025}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprtrails:points_per_step", Hyprlang::INT{2}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprtrails:history_points", Hyprlang::INT{20}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprtrails:history_step", Hyprlang::INT{2}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprtrails:color", Hyprlang::INT{*configStringToInt("rgba(ffaa00ff)")}); static auto P = HyprlandAPI::registerCallbackDynamic(PHANDLE, "openWindow", [&](void* self, SCallbackInfo& info, std::any data) { onNewWindow(self, data); }); g_pGlobalState = makeUnique(); initGlobal(); // add deco to existing windows for (auto& w : g_pCompositor->m_windows) { if (w->isHidden() || !w->m_isMapped) continue; HyprlandAPI::addWindowDecoration(PHANDLE, w, makeUnique(w)); } HyprlandAPI::reloadConfig(); HyprlandAPI::addNotification(PHANDLE, "[hyprtrails] Initialized successfully!", CHyprColor{0.2, 1.0, 0.2, 1.0}, 5000); return {"hyprtrails", "A plugin to add trails behind moving windows", "Vaxry", "1.0"}; } APICALL EXPORT void PLUGIN_EXIT() { wl_event_source_remove(g_pGlobalState->tick); g_pHyprRenderer->m_renderPass.removeAllOfType("CTrailPassElement"); } hyprwm-hyprland-plugins-4dbef35/hyprtrails/meson.build000066400000000000000000000017771512451410000234040ustar00rootroot00000000000000project('hyprtrails', 'cpp', version: '0.1', default_options: ['buildtype=release'], ) cpp_compiler = meson.get_compiler('cpp') if cpp_compiler.has_argument('-std=c++23') add_global_arguments('-std=c++23', language: 'cpp') elif cpp_compiler.has_argument('-std=c++2b') add_global_arguments('-std=c++2b', language: 'cpp') else error('Could not configure current C++ compiler (' + cpp_compiler.get_id() + ' ' + cpp_compiler.version() + ') with required C++ standard (C++23)') endif add_project_arguments( [ '-Wno-narrowing', ], language: 'cpp') globber = run_command('find', '.', '-name', '*.cpp', check: true) src = globber.stdout().strip().split('\n') hyprland = dependency('hyprland') shared_module(meson.project_name(), src, dependencies: [ dependency('hyprland'), dependency('pixman-1'), dependency('libdrm'), dependency('pangocairo'), dependency('libinput'), dependency('libudev'), dependency('wayland-server'), dependency('xkbcommon'), ], install: true, ) hyprwm-hyprland-plugins-4dbef35/hyprtrails/shaders.hpp000066400000000000000000000022321512451410000233670ustar00rootroot00000000000000#pragma once #include inline const std::string QUADTRAIL = R"#( #version 300 es precision mediump float; uniform mat3 proj; uniform vec4 color; in vec2 pos; in vec2 texcoord; in vec4 colors; out vec4 v_color; out vec2 v_texcoord; void main() { gl_Position = vec4(proj * vec3(pos, 1.0), 1.0); v_color = color; v_texcoord = texcoord; })#"; inline const std::string FRAGTRAIL = R"#( #version 300 es precision mediump float; in vec4 v_color; in vec2 v_texcoord; uniform vec4 window; layout(location = 0) out vec4 fragColor; float distToRect(vec4 rect) { float dx = max(rect[0] - v_texcoord[0], max(0.0, v_texcoord[0] - rect[2])); float dy = max(rect[1] - v_texcoord[1], max(0.0, v_texcoord[1] - rect[3])); return sqrt(dx*dx + dy*dy); } float alphaForShot(vec4 shot, float threshold) { float dist = distToRect(shot); if (dist > threshold) return 0.0; if (dist <= 0.0) return 0.0; return 1.0 - (dist * (1.0 / threshold)); } void main() { vec4 pixColor = v_color; float a = v_color[3]; // clamp(alphaForShot(window, 0.5), 0.0, 1.0); // todo pixColor.rgb *= a; fragColor = pixColor; })#"; hyprwm-hyprland-plugins-4dbef35/hyprtrails/trail.cpp000066400000000000000000000341341512451410000230520ustar00rootroot00000000000000#include "trail.hpp" #include #include #include #include using namespace Hyprutils::Memory; #include "globals.hpp" #include "TrailPassElement.hpp" void CTrail::onTick() { static auto* const PHISTORYSTEP = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprtrails:history_step")->getDataStaticPtr(); static auto* const PHISTORYPOINTS = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprtrails:history_points")->getDataStaticPtr(); m_iTimer++; const auto PWINDOW = m_pWindow.lock(); if (m_iTimer > **PHISTORYSTEP) { m_dLastGeoms.push_front({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}, std::chrono::system_clock::now()}); while (m_dLastGeoms.size() > **PHISTORYPOINTS) m_dLastGeoms.pop_back(); m_iTimer = 0; } if (m_bNeedsDamage) { g_pHyprRenderer->damageBox(m_bLastBox); m_bNeedsDamage = false; } } CTrail::CTrail(PHLWINDOW pWindow) : IHyprWindowDecoration(pWindow), m_pWindow(pWindow) { m_lastWindowPos = pWindow->m_realPosition->value(); m_lastWindowSize = pWindow->m_realSize->value(); pTickCb = HyprlandAPI::registerCallbackDynamic(PHANDLE, "trailTick", [this](void* self, SCallbackInfo& info, std::any data) { this->onTick(); }); } CTrail::~CTrail() { damageEntire(); HyprlandAPI::unregisterCallback(PHANDLE, pTickCb); } SDecorationPositioningInfo CTrail::getPositioningInfo() { return {DECORATION_POSITION_ABSOLUTE}; } void CTrail::onPositioningReply(const SDecorationPositioningReply& reply) { ; // ignored } void scaleBox2(box& box, float coeff) { float hwl = (box.w - (box.w * coeff)) / 2.0; float hhl = (box.h - (box.h * coeff)) / 2.0; box.w *= coeff; box.h *= coeff; box.x += hwl; box.y += hhl; } Vector2D vecForT(const Vector2D& a, const Vector2D& b, const float& t) { const Vector2D vec_PQ = b - a; return Vector2D{a + vec_PQ * t}; } Vector2D vecForBezierT(const float& t, const std::vector& verts) { std::vector pts; for (size_t vertexIndex = 0; vertexIndex < verts.size() - 1; vertexIndex++) { Vector2D p = verts[vertexIndex]; pts.push_back(vecForT(p, verts[vertexIndex + 1], t)); } if (pts.size() > 1) return vecForBezierT(t, pts); else return pts[0]; } void CTrail::draw(PHLMONITOR pMonitor, const float& a) { if (!validMapped(m_pWindow)) return; const auto PWINDOW = m_pWindow.lock(); if (!PWINDOW->m_ruleApplicator->decorate().valueOrDefault()) return; auto data = CTrailPassElement::STrailData{this, a}; g_pHyprRenderer->m_renderPass.add(makeUnique(data)); } void CTrail::renderPass(PHLMONITOR pMonitor, const float& a) { const auto PWINDOW = m_pWindow.lock(); static auto* const PBEZIERSTEP = (Hyprlang::FLOAT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprtrails:bezier_step")->getDataStaticPtr(); static auto* const PPOINTSPERSTEP = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprtrails:points_per_step")->getDataStaticPtr(); static auto* const PCOLOR = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprtrails:color")->getDataStaticPtr(); const CHyprColor COLOR = **PCOLOR; if (m_dLastGeoms.size() < 2) return; box thisbox = 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}; CBox wlrbox = {thisbox.x - pMonitor->m_position.x, thisbox.y - pMonitor->m_position.y, thisbox.w, thisbox.h}; wlrbox.scale(pMonitor->m_scale).round(); g_pHyprOpenGL->scissor(nullptr); // allow the entire window and stencil to render glClearStencil(0); glClear(GL_STENCIL_BUFFER_BIT); g_pHyprOpenGL->setCapStatus(GL_STENCIL_TEST, true); glStencilFunc(GL_ALWAYS, 1, -1); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); g_pHyprOpenGL->renderRect(wlrbox, CHyprColor(0, 0, 0, 0), {.round = sc(PWINDOW->rounding() * pMonitor->m_scale), .roundingPower = PWINDOW->roundingPower()}); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); glStencilFunc(GL_NOTEQUAL, 1, -1); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); CBox monbox = {0, 0, g_pHyprOpenGL->m_renderData.pMonitor->m_transformedSize.x, g_pHyprOpenGL->m_renderData.pMonitor->m_transformedSize.y}; Mat3x3 matrix = g_pHyprOpenGL->m_renderData.monitorProjection.projectBox(monbox, Math::wlTransformToHyprutils(Math::invertTransform(WL_OUTPUT_TRANSFORM_NORMAL)), monbox.rot); Mat3x3 glMatrix = g_pHyprOpenGL->m_renderData.projection.copy().multiply(matrix); g_pHyprOpenGL->blend(true); glUseProgram(g_pGlobalState->trailShader.program); glMatrix.transpose(); g_pGlobalState->trailShader.setUniformMatrix3fv(SHADER_PROJ, 1, GL_FALSE, glMatrix.getMatrix()); std::vector points; std::vector bezierPts; std::vector pointsForBezier; std::vector agesForBezier; float originalCoeff = 50; auto msFrom = [](std::chrono::system_clock::time_point t) -> float { return std::chrono::duration_cast(std::chrono::system_clock::now() - t).count() / 1000.0; }; auto dist = [&](const point2& a, const point2& b) -> float { Vector2D diff = Vector2D{a.x - b.x, a.y - b.y} * pMonitor->m_size; return std::sqrt(diff.x * diff.x + diff.y * diff.y); }; float msMaxToLast = msFrom(m_dLastGeoms.back().second); float dists[2] = {0, 0}; Vector2D mainVec = {originalCoeff / pMonitor->m_size.x, originalCoeff / pMonitor->m_size.y}; Vector2D windowMiddle = PWINDOW->middle() - pMonitor->m_position; points.push_back( 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}); points.push_back(Vector2D{cos(-M_PI_2) * mainVec.x - sin(-M_PI_2) * mainVec.y + windowMiddle.x / pMonitor->m_size.x, sin(-M_PI_2) * mainVec.x + cos(-M_PI_2) * mainVec.y + windowMiddle.y / pMonitor->m_size.y}); points.push_back(Vector2D{cos(M_PI_2) * mainVec.x - sin(M_PI_2) * mainVec.y + windowMiddle.x / pMonitor->m_size.x, sin(M_PI_2) * mainVec.x + cos(M_PI_2) * mainVec.y + windowMiddle.y / pMonitor->m_size.y}); points.push_back(Vector2D{cos(M_PI) * mainVec.x - sin(M_PI) * mainVec.y + windowMiddle.x / pMonitor->m_size.x, sin(M_PI) * mainVec.x + cos(M_PI) * mainVec.y + windowMiddle.y / pMonitor->m_size.y}); pointsForBezier.push_back(windowMiddle); agesForBezier.push_back(0); for (size_t i = 0; i < m_dLastGeoms.size(); i += 1) { box box = m_dLastGeoms[i].first; box.x -= pMonitor->m_position.x; box.y -= pMonitor->m_position.y; Vector2D middle = {box.x + box.w / 2.0, box.y + box.h / 2.0}; if (middle == pointsForBezier[pointsForBezier.size() - 1]) continue; pointsForBezier.push_back(middle); agesForBezier.push_back(msFrom(m_dLastGeoms[i].second)); } if (pointsForBezier.size() < 3) { glClearStencil(0); glClear(GL_STENCIL_BUFFER_BIT); g_pHyprOpenGL->setCapStatus(GL_STENCIL_TEST, false); glStencilMask(-1); glStencilFunc(GL_ALWAYS, 1, 0xFF); g_pHyprOpenGL->scissor(nullptr); return; } float maxAge = agesForBezier.back(); float tCoeff = **PBEZIERSTEP; int pointsPerBezier = **PPOINTSPERSTEP; bezierPts.push_back(vecForBezierT(0, pointsForBezier)); for (float t = tCoeff; t <= 1.0; t += tCoeff) { bezierPts.push_back(vecForBezierT(t, pointsForBezier)); const Vector2D& lastbezier = bezierPts.back(); const Vector2D& lastprevbezier = bezierPts[bezierPts.size() - 2]; for (size_t i = 1; i < pointsPerBezier + 1; ++i) { const float bezierPointStep = (1.0 / (pointsPerBezier + 2)); const Vector2D& middle = vecForT(lastprevbezier, lastbezier, bezierPointStep * (i + 1)); const Vector2D& lastmiddle = vecForT(lastprevbezier, lastbezier, bezierPointStep * i); Vector2D vecNormal = {middle.x - lastmiddle.x, middle.y - lastmiddle.y}; // normalize vec float invlen = 1.0 / std::sqrt(vecNormal.x * vecNormal.x + vecNormal.y * vecNormal.y); vecNormal.x *= invlen; vecNormal.y *= invlen; // make sure it points up // vecNormal.y = std::abs(vecNormal.y); // extend by coeff float ageCoeff = t * (agesForBezier.size() - 1); float ageFloor = std::floor(ageCoeff); float ageCeil = std::ceil(ageCoeff); float approxAge = agesForBezier[sc(ageFloor)] + (agesForBezier[sc(ageCeil)] - agesForBezier[sc(ageFloor)]) * (ageCoeff - ageFloor); float coeff = originalCoeff * (1.0 - (approxAge / maxAge)); Vector2D newVec = {vecNormal.x * coeff / pMonitor->m_size.x, vecNormal.y * coeff / pMonitor->m_size.y}; if ((newVec.x == 0 && newVec.y == 0) || std::isnan(newVec.x) || std::isnan(newVec.y)) continue; // rotate by 90 and -90 and add middle points.push_back(Vector2D{cos(M_PI_2) * newVec.x - sin(M_PI_2) * newVec.y + middle.x / pMonitor->m_size.x, sin(M_PI_2) * newVec.x + cos(M_PI_2) * newVec.y + middle.y / pMonitor->m_size.y}); points.push_back(Vector2D{cos(-M_PI_2) * newVec.x - sin(-M_PI_2) * newVec.y + middle.x / pMonitor->m_size.x, sin(-M_PI_2) * newVec.x + cos(-M_PI_2) * newVec.y + middle.y / pMonitor->m_size.y}); } } box thisboxopengl = box{sc((PWINDOW->m_realPosition->value().x - pMonitor->m_position.x) / pMonitor->m_size.x), sc((PWINDOW->m_realPosition->value().y - pMonitor->m_position.y) / pMonitor->m_size.y), sc((PWINDOW->m_realPosition->value().x + PWINDOW->m_realSize->value().x) / pMonitor->m_size.x), sc((PWINDOW->m_realPosition->value().y + PWINDOW->m_realSize->value().y) / pMonitor->m_size.y)}; glUniform4f(g_pGlobalState->trailShader.uniformLocations[SHADER_GRADIENT], thisboxopengl.x, thisboxopengl.y, thisboxopengl.w, thisboxopengl.h); glUniform4f(g_pGlobalState->trailShader.uniformLocations[SHADER_COLOR], COLOR.r, COLOR.g, COLOR.b, COLOR.a); CBox transformedBox = monbox; transformedBox.transform(Math::wlTransformToHyprutils(Math::invertTransform(g_pHyprOpenGL->m_renderData.pMonitor->m_transform)), g_pHyprOpenGL->m_renderData.pMonitor->m_transformedSize.x, g_pHyprOpenGL->m_renderData.pMonitor->m_transformedSize.y); glVertexAttribPointer(g_pGlobalState->trailShader.uniformLocations[SHADER_POS_ATTRIB], 2, GL_FLOAT, GL_FALSE, 0, (float*)points.data()); glEnableVertexAttribArray(g_pGlobalState->trailShader.uniformLocations[SHADER_POS_ATTRIB]); if (g_pHyprOpenGL->m_renderData.clipBox.width != 0 && g_pHyprOpenGL->m_renderData.clipBox.height != 0) { CRegion damageClip{g_pHyprOpenGL->m_renderData.clipBox.x, g_pHyprOpenGL->m_renderData.clipBox.y, g_pHyprOpenGL->m_renderData.clipBox.width, g_pHyprOpenGL->m_renderData.clipBox.height}; damageClip.intersect(g_pHyprOpenGL->m_renderData.damage); if (!damageClip.empty()) { for (auto& RECT : damageClip.getRects()) { g_pHyprOpenGL->scissor(&RECT); glDrawArrays(GL_TRIANGLE_STRIP, 0, points.size()); } } } else { for (auto& RECT : g_pHyprOpenGL->m_renderData.damage.getRects()) { g_pHyprOpenGL->scissor(&RECT); glDrawArrays(GL_TRIANGLE_STRIP, 0, points.size()); } } glDisableVertexAttribArray(g_pGlobalState->trailShader.uniformLocations[SHADER_POS_ATTRIB]); glClearStencil(0); glClear(GL_STENCIL_BUFFER_BIT); g_pHyprOpenGL->setCapStatus(GL_STENCIL_TEST, false); glStencilMask(-1); glStencilFunc(GL_ALWAYS, 1, 0xFF); g_pHyprOpenGL->scissor(nullptr); // calculate damage float minX = 9999999; float minY = 9999999; float maxX = -9999999; float maxY = -9999999; for (auto& p : points) { if (p.x < minX) minX = p.x; if (p.y < minY) minY = p.y; if (p.x > maxX) maxX = p.x; if (p.y > maxY) maxY = p.y; } // bring back to global coords minX *= pMonitor->m_size.x; minY *= pMonitor->m_size.y; maxX *= pMonitor->m_size.x; maxY *= pMonitor->m_size.y; m_bLastBox.x = minX + pMonitor->m_position.x; m_bLastBox.y = minY + pMonitor->m_position.y; m_bLastBox.width = maxX - minX; m_bLastBox.height = maxY - minY; m_bNeedsDamage = true; } eDecorationType CTrail::getDecorationType() { return DECORATION_CUSTOM; } void CTrail::updateWindow(PHLWINDOW pWindow) { const auto PWORKSPACE = pWindow->m_workspace; const auto WORKSPACEOFFSET = PWORKSPACE && !pWindow->m_pinned ? PWORKSPACE->m_renderOffset->value() : Vector2D(); m_lastWindowPos = pWindow->m_realPosition->value() + WORKSPACEOFFSET; m_lastWindowSize = pWindow->m_realSize->value(); damageEntire(); } void CTrail::damageEntire() { CBox dm = {sc(m_lastWindowPos.x - m_seExtents.topLeft.x), sc(m_lastWindowPos.y - m_seExtents.topLeft.y), sc(m_lastWindowSize.x + m_seExtents.topLeft.x + m_seExtents.bottomRight.x), sc(m_seExtents.topLeft.y)}; g_pHyprRenderer->damageBox(dm); } hyprwm-hyprland-plugins-4dbef35/hyprtrails/trail.hpp000066400000000000000000000037641512451410000230640ustar00rootroot00000000000000#pragma once #define WLR_USE_UNSTABLE #include #include #include struct box { float x = 0, y = 0, w = 0, h = 0; // Vector2D middle() { return Vector2D{x + w / 2.0, y + h / 2.0}; } }; struct point2 { point2(const Vector2D& v) { x = v.x; y = v.y; } point2() { x = 0; y = 0; } float x = 0, y = 0; }; class CTrail : public IHyprWindowDecoration { public: CTrail(PHLWINDOW); virtual ~CTrail(); virtual SDecorationPositioningInfo getPositioningInfo(); virtual void onPositioningReply(const SDecorationPositioningReply& reply); virtual void draw(PHLMONITOR, float const& a); virtual eDecorationType getDecorationType(); virtual void updateWindow(PHLWINDOW); virtual void damageEntire(); private: SP pTickCb; void onTick(); void renderPass(PHLMONITOR pMonitor, const float& a); std::deque> m_dLastGeoms; int m_iTimer = 0; SBoxExtents m_seExtents; PHLWINDOWREF m_pWindow; Vector2D m_lastWindowPos; Vector2D m_lastWindowSize; CBox m_bLastBox = {0}; bool m_bNeedsDamage = false; friend class CTrailPassElement; }; hyprwm-hyprland-plugins-4dbef35/hyprwinwrap/000077500000000000000000000000001512451410000214175ustar00rootroot00000000000000hyprwm-hyprland-plugins-4dbef35/hyprwinwrap/CMakeLists.txt000066400000000000000000000010001512451410000241460ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.27) project(hyprwinwrap DESCRIPTION "hyprwinwrap plugin for Hyprland" VERSION 0.1 ) set(CMAKE_CXX_STANDARD 23) file(GLOB_RECURSE SRC "*.cpp") add_library(hyprwinwrap SHARED ${SRC}) find_package(PkgConfig REQUIRED) pkg_check_modules(deps REQUIRED IMPORTED_TARGET hyprland libdrm libinput libudev pangocairo pixman-1 wayland-server xkbcommon ) target_link_libraries(hyprwinwrap PRIVATE rt PkgConfig::deps) install(TARGETS hyprwinwrap) hyprwm-hyprland-plugins-4dbef35/hyprwinwrap/Makefile000066400000000000000000000005151512451410000230600ustar00rootroot00000000000000# Else exist specifically for clang ifeq ($(CXX),g++) EXTRA_FLAGS = --no-gnu-unique else EXTRA_FLAGS = endif all: $(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 clean: rm ./hyprwinwrap.so hyprwm-hyprland-plugins-4dbef35/hyprwinwrap/README.md000066400000000000000000000012771512451410000227050ustar00rootroot00000000000000# hyprwinwrap Clone of xwinwrap for hyprland. Example config: ```ini plugin { hyprwinwrap { # class is an EXACT match and NOT a regex! class = kitty-bg # you can also use title title = kitty-bg # you can add the position of the window in a percentage pos_x = 25 pos_y = 30 # you can add the size of the window in a percentage size_x = 40 size_y = 70 } } ``` Launch `kitty -c "~/.config/hypr/kittyconfigbg.conf" --class="kitty-bg" "/home/vaxry/.config/hypr/cava.sh"` Example script for cava: ```sh #!/bin/sh sleep 1 && cava ``` _sleep required because resizing happens a few ms after open, which breaks cava_ hyprwm-hyprland-plugins-4dbef35/hyprwinwrap/default.nix000066400000000000000000000006251512451410000235660ustar00rootroot00000000000000{ lib, hyprland, hyprlandPlugins, }: hyprlandPlugins.mkHyprlandPlugin { pluginName = "hyprwinwrap"; version = "0.1"; src = ./.; inherit (hyprland) nativeBuildInputs; meta = with lib; { homepage = "https://github.com/hyprwm/hyprland-plugins/tree/main/hyprwinwrap"; description = "Hyprland version of xwinwrap"; license = licenses.bsd3; platforms = platforms.linux; }; } hyprwm-hyprland-plugins-4dbef35/hyprwinwrap/globals.hpp000066400000000000000000000001351512451410000235520ustar00rootroot00000000000000#pragma once #include inline HANDLE PHANDLE = nullptr;hyprwm-hyprland-plugins-4dbef35/hyprwinwrap/main.cpp000066400000000000000000000235121512451410000230520ustar00rootroot00000000000000#define WLR_USE_UNSTABLE #include #include #include #include #include #define private public #include #include #include #include #include #include #include #undef private #include "globals.hpp" // Do NOT change this function APICALL EXPORT std::string PLUGIN_API_VERSION() { return HYPRLAND_API_VERSION; } // hooks inline CFunctionHook* subsurfaceHook = nullptr; inline CFunctionHook* commitHook = nullptr; typedef void (*origCommitSubsurface)(Desktop::View::CSubsurface* thisptr); typedef void (*origCommit)(void* owner, void* data); std::vector bgWindows; void onNewWindow(PHLWINDOW pWindow) { static auto* const PCLASS = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprwinwrap:class")->getDataStaticPtr(); static auto* const PTITLE = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprwinwrap:title")->getDataStaticPtr(); static auto* const PSIZEX = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprwinwrap:size_x")->getDataStaticPtr(); static auto* const PSIZEY = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprwinwrap:size_y")->getDataStaticPtr(); static auto* const PPOSX = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprwinwrap:pos_x")->getDataStaticPtr(); static auto* const PPOSY = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprwinwrap:pos_y")->getDataStaticPtr(); const std::string classRule(*PCLASS); const std::string titleRule(*PTITLE); const bool classMatches = !classRule.empty() && pWindow->m_initialClass == classRule; const bool titleMatches = !titleRule.empty() && pWindow->m_title == titleRule; if (!classMatches && !titleMatches) return; const auto PMONITOR = pWindow->m_monitor.lock(); if (!PMONITOR) return; if (!pWindow->m_isFloating) g_pLayoutManager->getCurrentLayout()->changeWindowFloatingMode(pWindow); float sx = 100.f, sy = 100.f, px = 0.f, py = 0.f; try { sx = std::stof(*PSIZEX); } catch (...) {} try { sy = std::stof(*PSIZEY); } catch (...) {} try { px = std::stof(*PPOSX); } catch (...) {} try { py = std::stof(*PPOSY); } catch (...) {} sx = std::clamp(sx, 1.f, 100.f); sy = std::clamp(sy, 1.f, 100.f); px = std::clamp(px, 0.f, 100.f); py = std::clamp(py, 0.f, 100.f); if (px + sx > 100.f) { Log::logger->log(Log::WARN, "[hyprwinwrap] size_x (%d) + pos_x (%d) > 100, adjusting size_x to %d", sx, px, 100.f - px); sx = 100.f - px; } if (py + sy > 100.f) { Log::logger->log(Log::WARN, "[hyprwinwrap] size_y (%d) + pos_y (%d) > 100, adjusting size_y to %d", sy, py, 100.f - py); sy = 100.f - py; } const Vector2D monitorSize = PMONITOR->m_size; const Vector2D monitorPos = PMONITOR->m_position; const Vector2D newSize = {static_cast(monitorSize.x * (sx / 100.f)), static_cast(monitorSize.y * (sy / 100.f))}; const Vector2D newPos = {static_cast(monitorPos.x + (monitorSize.x * (px / 100.f))), static_cast(monitorPos.y + (monitorSize.y * (py / 100.f)))}; pWindow->m_realSize->setValueAndWarp(newSize); pWindow->m_realPosition->setValueAndWarp(newPos); pWindow->m_size = newSize; pWindow->m_position = newPos; pWindow->m_pinned = true; pWindow->sendWindowSize(true); bgWindows.push_back(pWindow); pWindow->m_hidden = true; g_pInputManager->refocus(); Log::logger->log(Log::DEBUG, "[hyprwinwrap] new window moved to bg {}", pWindow); } void onCloseWindow(PHLWINDOW pWindow) { std::erase_if(bgWindows, [pWindow](const auto& ref) { return ref.expired() || ref.lock() == pWindow; }); Log::logger->log(Log::DEBUG, "[hyprwinwrap] closed window {}", pWindow); } void onRenderStage(eRenderStage stage) { if (stage != RENDER_PRE_WINDOWS) return; for (auto& bg : bgWindows) { const auto bgw = bg.lock(); if (bgw->m_monitor != g_pHyprOpenGL->m_renderData.pMonitor) continue; // cant use setHidden cuz that sends suspended and shit too that would be laggy bgw->m_hidden = false; g_pHyprRenderer->renderWindow(bgw, g_pHyprOpenGL->m_renderData.pMonitor.lock(), Time::steadyNow(), false, RENDER_PASS_ALL, false, true); bgw->m_hidden = true; } } void onCommitSubsurface(Desktop::View::CSubsurface* thisptr) { const auto PWINDOW = Desktop::View::CWindow::fromView(thisptr->wlSurface()->view()); if (!PWINDOW || std::find_if(bgWindows.begin(), bgWindows.end(), [PWINDOW](const auto& ref) { return ref.lock() == PWINDOW; }) == bgWindows.end()) { ((origCommitSubsurface)subsurfaceHook->m_original)(thisptr); return; } // cant use setHidden cuz that sends suspended and shit too that would be laggy PWINDOW->m_hidden = false; ((origCommitSubsurface)subsurfaceHook->m_original)(thisptr); if (const auto MON = PWINDOW->m_monitor.lock(); MON) g_pHyprOpenGL->markBlurDirtyForMonitor(MON); PWINDOW->m_hidden = true; } void onCommit(void* owner, void* data) { const auto PWINDOW = ((Desktop::View::CWindow*)owner)->m_self.lock(); if (std::find_if(bgWindows.begin(), bgWindows.end(), [PWINDOW](const auto& ref) { return ref.lock() == PWINDOW; }) == bgWindows.end()) { ((origCommit)commitHook->m_original)(owner, data); return; } // cant use setHidden cuz that sends suspended and shit too that would be laggy PWINDOW->m_hidden = false; ((origCommit)commitHook->m_original)(owner, data); if (const auto MON = PWINDOW->m_monitor.lock(); MON) g_pHyprOpenGL->markBlurDirtyForMonitor(MON); PWINDOW->m_hidden = true; } void onConfigReloaded() { static auto* const PCLASS = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprwinwrap:class")->getDataStaticPtr(); const std::string classRule(*PCLASS); if (!classRule.empty()) { g_pConfigManager->parseKeyword("windowrulev2", std::string{"float, class:^("} + classRule + ")$"); g_pConfigManager->parseKeyword("windowrulev2", std::string{"size 100\% 100\%, class:^("} + classRule + ")$"); } static auto* const PTITLE = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprwinwrap:title")->getDataStaticPtr(); const std::string titleRule(*PTITLE); if (!titleRule.empty()) { g_pConfigManager->parseKeyword("windowrulev2", std::string{"float, title:^("} + titleRule + ")$"); g_pConfigManager->parseKeyword("windowrulev2", std::string{"size 100\% 100\%, title:^("} + titleRule + ")$"); } } APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { PHANDLE = handle; const std::string HASH = __hyprland_api_get_hash(); const std::string CLIENT_HASH = __hyprland_api_get_client_hash(); if (HASH != CLIENT_HASH) { HyprlandAPI::addNotification(PHANDLE, "[hyprwinwrap] Failure in initialization: Version mismatch (headers ver is not equal to running hyprland ver)", CHyprColor{1.0, 0.2, 0.2, 1.0}, 5000); throw std::runtime_error("[hww] Version mismatch"); } // clang-format off static auto P = HyprlandAPI::registerCallbackDynamic(PHANDLE, "openWindow", [&](void* self, SCallbackInfo& info, std::any data) { onNewWindow(std::any_cast(data)); }); static auto P2 = HyprlandAPI::registerCallbackDynamic(PHANDLE, "closeWindow", [&](void* self, SCallbackInfo& info, std::any data) { onCloseWindow(std::any_cast(data)); }); static auto P3 = HyprlandAPI::registerCallbackDynamic(PHANDLE, "render", [&](void* self, SCallbackInfo& info, std::any data) { onRenderStage(std::any_cast(data)); }); static auto P4 = HyprlandAPI::registerCallbackDynamic(PHANDLE, "configReloaded", [&](void* self, SCallbackInfo& info, std::any data) { onConfigReloaded(); }); // clang-format on auto fns = HyprlandAPI::findFunctionsByName(PHANDLE, "_ZN7Desktop4View11CSubsurface8onCommitEv"); if (fns.size() < 1) throw std::runtime_error("hyprwinwrap: onCommit not found"); subsurfaceHook = HyprlandAPI::createFunctionHook(PHANDLE, fns[0].address, (void*)&onCommitSubsurface); fns = HyprlandAPI::findFunctionsByName(PHANDLE, "_ZN7Desktop4View7CWindow12commitWindowEv"); if (fns.size() < 1) throw std::runtime_error("hyprwinwrap: listener_commitWindow not found"); commitHook = HyprlandAPI::createFunctionHook(PHANDLE, fns[0].address, (void*)&onCommit); bool hkResult = subsurfaceHook->hook(); hkResult = hkResult && commitHook->hook(); if (!hkResult) throw std::runtime_error("hyprwinwrap: hooks failed"); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprwinwrap:class", Hyprlang::STRING{"kitty-bg"}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprwinwrap:title", Hyprlang::STRING{""}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprwinwrap:size_x", Hyprlang::STRING{"100"}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprwinwrap:size_y", Hyprlang::STRING{"100"}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprwinwrap:pos_x", Hyprlang::STRING{"0"}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprwinwrap:pos_y", Hyprlang::STRING{"0"}); HyprlandAPI::addNotification(PHANDLE, "[hyprwinwrap] Initialized successfully!", CHyprColor{0.2, 1.0, 0.2, 1.0}, 5000); return {"hyprwinwrap", "A clone of xwinwrap for Hyprland", "Vaxry", "1.0"}; } APICALL EXPORT void PLUGIN_EXIT() { ; } hyprwm-hyprland-plugins-4dbef35/hyprwinwrap/meson.build000066400000000000000000000016231512451410000235630ustar00rootroot00000000000000project('hyprwinwrap', 'cpp', version: '0.1', default_options: ['buildtype=release'], ) cpp_compiler = meson.get_compiler('cpp') if cpp_compiler.has_argument('-std=c++23') add_global_arguments('-std=c++23', language: 'cpp') elif cpp_compiler.has_argument('-std=c++2b') add_global_arguments('-std=c++2b', language: 'cpp') else error('Could not configure current C++ compiler (' + cpp_compiler.get_id() + ' ' + cpp_compiler.version() + ') with required C++ standard (C++23)') endif globber = run_command('find', '.', '-name', '*.cpp', check: true) src = globber.stdout().strip().split('\n') shared_module(meson.project_name(), src, dependencies: [ dependency('hyprland'), dependency('pixman-1'), dependency('libdrm'), dependency('pangocairo'), dependency('libinput'), dependency('libudev'), dependency('wayland-server'), dependency('xkbcommon'), ], install: true, ) hyprwm-hyprland-plugins-4dbef35/xtra-dispatchers/000077500000000000000000000000001512451410000223125ustar00rootroot00000000000000hyprwm-hyprland-plugins-4dbef35/xtra-dispatchers/CMakeLists.txt000066400000000000000000000010311512451410000250450ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.27) project(xtra-dispatchers DESCRIPTION "xtra-dispatchers plugin for Hyprland" VERSION 0.1 ) set(CMAKE_CXX_STANDARD 23) file(GLOB_RECURSE SRC "*.cpp") add_library(xtra-dispatchers SHARED ${SRC}) find_package(PkgConfig REQUIRED) pkg_check_modules(deps REQUIRED IMPORTED_TARGET hyprland libdrm libinput libudev pangocairo pixman-1 wayland-server xkbcommon ) target_link_libraries(xtra-dispatchers PRIVATE rt PkgConfig::deps) install(TARGETS xtra-dispatchers) hyprwm-hyprland-plugins-4dbef35/xtra-dispatchers/Makefile000066400000000000000000000005271512451410000237560ustar00rootroot00000000000000# Else exist specifically for clang ifeq ($(CXX),g++) EXTRA_FLAGS = --no-gnu-unique else EXTRA_FLAGS = endif all: $(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 clean: rm ./xtra-dispatchers.so hyprwm-hyprland-plugins-4dbef35/xtra-dispatchers/README.md000066400000000000000000000012331512451410000235700ustar00rootroot00000000000000# xtra-dispatchers Adds some additional dispatchers to Hyprland. ## Dispatchers All dispatchers here are called `plugin:xtd:name` e.g. `plugin:xtd:moveorexec`. | name | description | params | | -- | -- | -- | | moveorexec | moves window to the current workspace, or executes if it's not found. `WINDOW` cannot contain commas | `WINDOW,CMD` | | throwunfocused | throws all unfocused windows on the current workspace to the given workspace | `WORKSPACE` | | bringallfrom | kinda inverse of throwunfocused. Bring all windows from a given workspace to the current one. | `WORKSPACE` | | closeunfocused | close all unfocused windows on the current workspace. | none | hyprwm-hyprland-plugins-4dbef35/xtra-dispatchers/default.nix000066400000000000000000000006441512451410000244620ustar00rootroot00000000000000{ lib, hyprland, hyprlandPlugins, }: hyprlandPlugins.mkHyprlandPlugin { pluginName = "xtra-dispatchers"; version = "0.1"; src = ./.; inherit (hyprland) nativeBuildInputs; meta = with lib; { homepage = "https://github.com/hyprwm/hyprland-plugins/tree/main/xtra-dispatchers"; description = "Hyprland extra dispatchers plugin"; license = licenses.bsd3; platforms = platforms.linux; }; } hyprwm-hyprland-plugins-4dbef35/xtra-dispatchers/globals.hpp000066400000000000000000000001351512451410000244450ustar00rootroot00000000000000#pragma once #include inline HANDLE PHANDLE = nullptr;hyprwm-hyprland-plugins-4dbef35/xtra-dispatchers/main.cpp000066400000000000000000000126461512451410000237530ustar00rootroot00000000000000#define WLR_USE_UNSTABLE #include #include #include #define private public #include #include #include #include #include #include #undef private #include using namespace Hyprutils::String; #include "globals.hpp" // Do NOT change this function. APICALL EXPORT std::string PLUGIN_API_VERSION() { return HYPRLAND_API_VERSION; } // static SDispatchResult moveOrExec(std::string in) { CVarList vars(in, 0, ','); auto focusState = Desktop::focusState(); auto monitor = focusState->monitor(); if (!monitor || !monitor->m_activeWorkspace) return SDispatchResult{.success = false, .error = "No active workspace"}; const auto PWINDOW = g_pCompositor->getWindowByRegex(vars[0]); if (!PWINDOW) g_pKeybindManager->spawn(vars[1]); else { if (monitor->m_activeWorkspace != PWINDOW->m_workspace) g_pCompositor->moveWindowToWorkspaceSafe(PWINDOW, monitor->m_activeWorkspace); else g_pCompositor->warpCursorTo(PWINDOW->middle()); focusState->fullWindowFocus(PWINDOW); } return SDispatchResult{}; } static SDispatchResult throwUnfocused(std::string in) { const auto [id, name, isAutoID] = getWorkspaceIDNameFromString(in); if (id == WORKSPACE_INVALID) return SDispatchResult{.success = false, .error = "Failed to find workspace"}; auto focusState = Desktop::focusState(); auto window = focusState->window(); if (!window || !window->m_workspace) return SDispatchResult{.success = false, .error = "No valid last window"}; auto pWorkspace = g_pCompositor->getWorkspaceByID(id); if (!pWorkspace) pWorkspace = g_pCompositor->createNewWorkspace(id, window->m_workspace->monitorID(), name, false); for (const auto& w : g_pCompositor->m_windows) { if (w == window || w->m_workspace != window->m_workspace) continue; g_pCompositor->moveWindowToWorkspaceSafe(w, pWorkspace); } return SDispatchResult{}; } static SDispatchResult bringAllFrom(std::string in) { const auto [id, name, isAutoID] = getWorkspaceIDNameFromString(in); if (id == WORKSPACE_INVALID) return SDispatchResult{.success = false, .error = "Failed to find workspace"}; auto focusState = Desktop::focusState(); auto monitor = focusState->monitor(); auto window = focusState->window(); if (!monitor || !monitor->m_activeWorkspace) return SDispatchResult{.success = false, .error = "No active monitor"}; auto pWorkspace = g_pCompositor->getWorkspaceByID(id); if (!pWorkspace) return SDispatchResult{.success = false, .error = "Workspace isnt open"}; const auto PLASTWINDOW = window; for (const auto& w : g_pCompositor->m_windows) { if (w->m_workspace != pWorkspace) continue; g_pCompositor->moveWindowToWorkspaceSafe(w, monitor->m_activeWorkspace); } if (PLASTWINDOW) { Desktop::focusState()->fullWindowFocus(PLASTWINDOW); g_pCompositor->warpCursorTo(PLASTWINDOW->middle()); } return SDispatchResult{}; } static SDispatchResult closeUnfocused(std::string in) { auto focusState = Desktop::focusState(); auto monitor = focusState->monitor(); auto window = focusState->window(); if (!window) return SDispatchResult{.success = false, .error = "No focused monitor"}; for (const auto& w : g_pCompositor->m_windows) { if (w->m_workspace != monitor->m_activeWorkspace || w->m_monitor != monitor || !w->m_isMapped || w == window) continue; g_pCompositor->closeWindow(w); } return SDispatchResult{}; } APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { PHANDLE = handle; const std::string HASH = __hyprland_api_get_hash(); const std::string CLIENT_HASH = __hyprland_api_get_client_hash(); if (HASH != CLIENT_HASH) { HyprlandAPI::addNotification(PHANDLE, "[xtra-dispatchers] Failure in initialization: Version mismatch (headers ver is not equal to running hyprland ver)", CHyprColor{1.0, 0.2, 0.2, 1.0}, 5000); throw std::runtime_error("[xtd] Version mismatch"); } bool success = true; success = success && HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:xtd:moveorexec", ::moveOrExec); success = success && HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:xtd:throwunfocused", ::throwUnfocused); success = success && HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:xtd:bringallfrom", ::bringAllFrom); success = success && HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:xtd:closeunfocused", ::closeUnfocused); if (success) HyprlandAPI::addNotification(PHANDLE, "[xtra-dispatchers] Initialized successfully!", CHyprColor{0.2, 1.0, 0.2, 1.0}, 5000); else { HyprlandAPI::addNotification(PHANDLE, "[xtra-dispatchers] Failure in initialization: failed to register dispatchers", CHyprColor{1.0, 0.2, 0.2, 1.0}, 5000); throw std::runtime_error("[xtd] Dispatchers failed"); } return {"xtra-dispatchers", "A plugin to add some extra dispatchers to hyprland", "Vaxry", "1.0"}; } APICALL EXPORT void PLUGIN_EXIT() { ; } hyprwm-hyprland-plugins-4dbef35/xtra-dispatchers/meson.build000066400000000000000000000016301512451410000244540ustar00rootroot00000000000000project('xtra-dispatchers', 'cpp', version: '0.1', default_options: ['buildtype=release'], ) cpp_compiler = meson.get_compiler('cpp') if cpp_compiler.has_argument('-std=c++23') add_global_arguments('-std=c++23', language: 'cpp') elif cpp_compiler.has_argument('-std=c++2b') add_global_arguments('-std=c++2b', language: 'cpp') else error('Could not configure current C++ compiler (' + cpp_compiler.get_id() + ' ' + cpp_compiler.version() + ') with required C++ standard (C++23)') endif globber = run_command('find', '.', '-name', '*.cpp', check: true) src = globber.stdout().strip().split('\n') shared_module(meson.project_name(), src, dependencies: [ dependency('hyprland'), dependency('pixman-1'), dependency('libdrm'), dependency('pangocairo'), dependency('libinput'), dependency('libudev'), dependency('wayland-server'), dependency('xkbcommon'), ], install: true, )