diff --git a/README.md b/README.md index 70606df..99a45b3 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ This repo houses official plugins for Hyprland. - 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 diff --git a/hyprfocus/CMakeLists.txt b/hyprfocus/CMakeLists.txt new file mode 100644 index 0000000..11fa6f8 --- /dev/null +++ b/hyprfocus/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_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) diff --git a/hyprfocus/Makefile b/hyprfocus/Makefile new file mode 100644 index 0000000..50cc59e --- /dev/null +++ b/hyprfocus/Makefile @@ -0,0 +1,4 @@ +all: + $(CXX) -shared -fPIC --no-gnu-unique 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 diff --git a/hyprfocus/README.md b/hyprfocus/README.md new file mode 100644 index 0000000..d501385 --- /dev/null +++ b/hyprfocus/README.md @@ -0,0 +1,22 @@ +# 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 | +| 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 slice, how far up to slide | float | 20 | diff --git a/hyprfocus/globals.hpp b/hyprfocus/globals.hpp new file mode 100644 index 0000000..2257475 --- /dev/null +++ b/hyprfocus/globals.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include + +inline HANDLE PHANDLE = nullptr; \ No newline at end of file diff --git a/hyprfocus/main.cpp b/hyprfocus/main.cpp new file mode 100644 index 0000000..da6f2e2 --- /dev/null +++ b/hyprfocus/main.cpp @@ -0,0 +1,141 @@ +#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 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") { + window->m_activeInactiveAlpha->setConfig(PIN); + *window->m_activeInactiveAlpha = std::clamp(*POPACITY, 0.F, 1.F); + + window->m_activeInactiveAlpha->setCallbackOnEnd([w = PHLWINDOWREF{window}, POUT](WP pav) { + if (!w) + return; + w->m_activeInactiveAlpha->setConfig(POUT); + g_pCompositor->updateWindowAnimatedDecorationValues(w.lock()); + w->updateDynamicRules(); + + 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->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->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(); + + if (HASH != GIT_COMMIT_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: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); + } +} diff --git a/hyprpm.toml b/hyprpm.toml index b50bdb7..9891ad0 100644 --- a/hyprpm.toml +++ b/hyprpm.toml @@ -100,3 +100,12 @@ 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