diff --git a/README.md b/README.md index e1b643a..0ee2b3d 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ 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 # Contributing diff --git a/hyprbars/Makefile b/hyprbars/Makefile new file mode 100644 index 0000000..4f2dc83 --- /dev/null +++ b/hyprbars/Makefile @@ -0,0 +1,8 @@ +# compile with HYPRLAND_HEADERS= make all +# make sure that the path above is to the root hl repo directory, NOT src/ +# and that you have ran `make protocols` in the hl dir. + +all: + g++ -shared -fPIC --no-gnu-unique main.cpp barDeco.cpp -o hyprbars.so -g -I "/usr/include/pixman-1" -I "/usr/include/libdrm" -I "${HYPRLAND_HEADERS}" -std=c++23 +clean: + rm ./hyprbars.so diff --git a/hyprbars/README.md b/hyprbars/README.md new file mode 100644 index 0000000..7eb4a2d --- /dev/null +++ b/hyprbars/README.md @@ -0,0 +1,21 @@ +# hyprbars + +Adds simple title bars to windows. + +## Config + +All config options are in `plugin:hyprbars`: + +``` +plugin { + hyprbars { + // config + } +} +``` + +`bar_color` -> (col) bar's background color +`bar_height` -> (int) bar's height (default 15) +`bar_text_color` -> (col) bar's title text color +`bar_text_size` -> (int) bar's title text font size (default 10) +`bar_text_font` -> (str) bar's title text font (default "Sans") \ No newline at end of file diff --git a/hyprbars/barDeco.cpp b/hyprbars/barDeco.cpp new file mode 100644 index 0000000..62e58c0 --- /dev/null +++ b/hyprbars/barDeco.cpp @@ -0,0 +1,174 @@ +#include "barDeco.hpp" + +#include +#include + +#include "globals.hpp" + +CHyprBar::CHyprBar(CWindow* pWindow) { + m_pWindow = pWindow; + m_vLastWindowPos = pWindow->m_vRealPosition.vec(); + m_vLastWindowSize = pWindow->m_vRealSize.vec(); +} + +CHyprBar::~CHyprBar() { + damageEntire(); +} + +SWindowDecorationExtents CHyprBar::getWindowDecorationExtents() { + return m_seExtents; +} + +void CHyprBar::renderBarTitle(const Vector2D& bufferSize) { + static auto* const PCOLOR = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_text_color")->intValue; + static auto* const PSIZE = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_text_size")->intValue; + static auto* const PFONT = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_text_font")->strValue; + + const CColor COLOR = *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 + cairo_select_font_face(CAIRO, PFONT->c_str(), CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); + cairo_set_font_size(CAIRO, *PSIZE); + cairo_set_source_rgba(CAIRO, COLOR.r, COLOR.g, COLOR.b, COLOR.a); + + cairo_text_extents_t cairoExtents; + cairo_text_extents(CAIRO, m_szLastTitle.c_str(), &cairoExtents); + + cairo_move_to(CAIRO, std::round(bufferSize.x / 2.0 - cairoExtents.width / 2.0), *PSIZE + std::round(bufferSize.y / 2.0 - cairoExtents.height / 2.0)); + cairo_show_text(CAIRO, m_szLastTitle.c_str()); + + cairo_surface_flush(CAIROSURFACE); + + // copy the data to an OpenGL texture we have + const auto DATA = cairo_image_surface_get_data(CAIROSURFACE); + m_tTextTex.allocate(); + glBindTexture(GL_TEXTURE_2D, m_tTextTex.m_iTexID); + 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::draw(CMonitor* pMonitor, float a, const Vector2D& offset) { + if (!g_pCompositor->windowValidMapped(m_pWindow)) + return; + + if (!m_pWindow->m_sSpecialRenderData.decorate) + return; + + static auto* const PROUNDING = &HyprlandAPI::getConfigValue(PHANDLE, "decoration:rounding")->intValue; + static auto* const PBORDERSIZE = &HyprlandAPI::getConfigValue(PHANDLE, "general:border_size")->intValue; + static auto* const PCOLOR = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_color")->intValue; + static auto* const PHEIGHT = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_height")->intValue; + + CColor color = *PCOLOR; + color.a *= a; + + const auto ROUNDING = !m_pWindow->m_sSpecialRenderData.rounding ? + 0 : + (m_pWindow->m_sAdditionalConfigData.rounding.toUnderlying() == -1 ? *PROUNDING : m_pWindow->m_sAdditionalConfigData.rounding.toUnderlying()); + + // title bar's extents are 0 because we cut into the window + m_seExtents = {{}, {}}; + + const auto BARBUF = Vector2D{(int)m_vLastWindowSize.x + 2 * *PBORDERSIZE, *PHEIGHT} * pMonitor->scale; + + wlr_box titleBarBox = {(int)m_vLastWindowPos.x - *PBORDERSIZE, (int)m_vLastWindowPos.y, (int)m_vLastWindowSize.x + 2 * *PBORDERSIZE, + *PHEIGHT + *PROUNDING * 2 /* to fill the bottom cuz we can't disable rounding there */}; + + titleBarBox.x += offset.x; + titleBarBox.y += offset.y; + + scaleBox(&titleBarBox, pMonitor->scale); + + g_pHyprOpenGL->scissor(&titleBarBox); + + if (*PROUNDING) { + glClearStencil(0); + glClear(GL_STENCIL_BUFFER_BIT); + + glEnable(GL_STENCIL_TEST); + + glStencilFunc(GL_ALWAYS, 1, -1); + glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + wlr_box windowBox = {(int)m_vLastWindowPos.x - *PBORDERSIZE + offset.x, (int)m_vLastWindowPos.y - *PBORDERSIZE + offset.y, (int)m_vLastWindowSize.x + 2 * *PBORDERSIZE, + (int)m_vLastWindowSize.y + 2 * *PBORDERSIZE}; + scaleBox(&windowBox, pMonitor->scale); + g_pHyprOpenGL->renderRect(&windowBox, CColor(0, 0, 0, 0), *PROUNDING + *PBORDERSIZE); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + + glStencilFunc(GL_EQUAL, 1, -1); + glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + } + + g_pHyprOpenGL->renderRect(&titleBarBox, color, *PROUNDING); + + // render title + if (m_szLastTitle != m_pWindow->m_szTitle) { + m_szLastTitle = m_pWindow->m_szTitle; + renderBarTitle(BARBUF); + } + + wlr_box textBox = {titleBarBox.x, titleBarBox.y, (int)BARBUF.x, (int)BARBUF.y}; + g_pHyprOpenGL->renderTexture(m_tTextTex, &textBox, a); + + // TODO: buttons and shit + + if (*PROUNDING) { + // cleanup stencil + glClearStencil(0); + glClear(GL_STENCIL_BUFFER_BIT); + glDisable(GL_STENCIL_TEST); + glStencilMask(-1); + glStencilFunc(GL_ALWAYS, 1, 0xFF); + } + + g_pHyprOpenGL->scissor((wlr_box*)nullptr); +} + +eDecorationType CHyprBar::getDecorationType() { + return DECORATION_CUSTOM; +} + +void CHyprBar::updateWindow(CWindow* pWindow) { + const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(pWindow->m_iWorkspaceID); + + const auto WORKSPACEOFFSET = PWORKSPACE && !pWindow->m_bPinned ? PWORKSPACE->m_vRenderOffset.vec() : Vector2D(); + + m_vLastWindowPos = pWindow->m_vRealPosition.vec() + WORKSPACEOFFSET; + m_vLastWindowSize = pWindow->m_vRealSize.vec(); + + damageEntire(); +} + +void CHyprBar::damageEntire() { + wlr_box dm = {(int)(m_vLastWindowPos.x - m_seExtents.topLeft.x), (int)(m_vLastWindowPos.y - m_seExtents.topLeft.y), + (int)(m_vLastWindowSize.x + m_seExtents.topLeft.x + m_seExtents.bottomRight.x), (int)m_seExtents.topLeft.y}; + g_pHyprRenderer->damageBox(&dm); +} + +SWindowDecorationExtents CHyprBar::getWindowDecorationReservedArea() { + static auto* const PHEIGHT = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprbars:bar_height")->intValue; + return SWindowDecorationExtents{{0, *PHEIGHT}, {}}; +} \ No newline at end of file diff --git a/hyprbars/barDeco.hpp b/hyprbars/barDeco.hpp new file mode 100644 index 0000000..a283f55 --- /dev/null +++ b/hyprbars/barDeco.hpp @@ -0,0 +1,37 @@ +#pragma once + +#define WLR_USE_UNSTABLE + +#include +#include + +class CHyprBar : public IHyprWindowDecoration { + public: + CHyprBar(CWindow*); + virtual ~CHyprBar(); + + virtual SWindowDecorationExtents getWindowDecorationExtents(); + + virtual void draw(CMonitor*, float a, const Vector2D& offset); + + virtual eDecorationType getDecorationType(); + + virtual void updateWindow(CWindow*); + + virtual void damageEntire(); + + virtual SWindowDecorationExtents getWindowDecorationReservedArea(); + + private: + SWindowDecorationExtents m_seExtents; + + CWindow* m_pWindow = nullptr; + + Vector2D m_vLastWindowPos; + Vector2D m_vLastWindowSize; + + CTexture m_tTextTex; + + void renderBarTitle(const Vector2D& bufferSize); + std::string m_szLastTitle; +}; \ No newline at end of file diff --git a/hyprbars/globals.hpp b/hyprbars/globals.hpp new file mode 100644 index 0000000..37e8363 --- /dev/null +++ b/hyprbars/globals.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include + +inline HANDLE PHANDLE = nullptr; \ No newline at end of file diff --git a/hyprbars/main.cpp b/hyprbars/main.cpp new file mode 100644 index 0000000..43da832 --- /dev/null +++ b/hyprbars/main.cpp @@ -0,0 +1,53 @@ +#define WLR_USE_UNSTABLE + +#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; +} + +void onNewWindow(void* self, std::any data) { + // data is guaranteed + auto* const PWINDOW = std::any_cast(data); + + HyprlandAPI::addWindowDecoration(PHANDLE, PWINDOW, new CHyprBar(PWINDOW)); +} + +APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { + PHANDLE = handle; + + HyprlandAPI::registerCallbackDynamic(PHANDLE, "openWindow", [&](void* self, std::any data) { onNewWindow(self, data); }); + + HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:bar_color", SConfigValue{.intValue = configStringToInt("rgba(33333388)")}); + HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:bar_height", SConfigValue{.intValue = 15}); + HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:bar_text_color", SConfigValue{.intValue = configStringToInt("rgba(ffffffff)")}); + HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:bar_text_size", SConfigValue{.intValue = 10}); + HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprbars:bar_text_font", SConfigValue{.strValue = "Sans"}); + + // add deco to existing windows + for (auto& w : g_pCompositor->m_vWindows) { + if (w->isHidden() || !w->m_bIsMapped) + continue; + + HyprlandAPI::addWindowDecoration(PHANDLE, w.get(), new CHyprBar(w.get())); + } + + HyprlandAPI::reloadConfig(); + + HyprlandAPI::addNotification(PHANDLE, "[hyprbar] Initialized successfully!", CColor{0.2, 1.0, 0.2, 1.0}, 5000); + + return {"hyprbars", "A plugin to add title bars to windows.", "Vaxry", "1.0"}; +} + +APICALL EXPORT void PLUGIN_EXIT() { + ; +} \ No newline at end of file