#define WLR_USE_UNSTABLE #include #include #include #include #include #include #include #include #include #include #include #include #include "barDeco.hpp" #include "globals.hpp" extern "C" { #include #include } // Do NOT change this function. APICALL EXPORT std::string PLUGIN_API_VERSION() { return HYPRLAND_API_VERSION; } static void onNewWindow(PHLWINDOW window) { if (!window->m_X11DoesntWantBorders) { if (std::ranges::any_of(window->m_windowDecorations, [](const auto& d) { return d->getDisplayName() == "Hyprbar"; })) return; auto bar = makeUnique(window); g_pGlobalState->bars.emplace_back(bar); bar->m_self = bar; HyprlandAPI::addWindowDecoration(PHANDLE, window, std::move(bar)); } } static void onPreConfigReload() { g_pGlobalState->buttons.clear(); } static void onConfigReloaded() { for (auto& b : g_pGlobalState->bars) { if (!b) continue; b->onConfigReloaded(); } } 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; Hyprutils::String::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; } int newLuaButton(lua_State* L) { if (!lua_istable(L, 1)) return Config::Lua::Bindings::Internal::configError(L, "add_button: expected a table { bg_color, fg_color, size, icon, action }"); SHyprButton button; { Hyprutils::Utils::CScopeGuard x([L] { lua_pop(L, 1); }); lua_getfield(L, 1, "bg_color"); Config::Lua::CLuaConfigColor parser(0); auto err = parser.parse(L); if (err.errorCode != Config::Lua::PARSE_ERROR_OK) return Config::Lua::Bindings::Internal::configError(L, "add_button: failed to parse bg_color"); button.bgcol = parser.parsed(); } { Hyprutils::Utils::CScopeGuard x([L] { lua_pop(L, 1); }); lua_getfield(L, 1, "fg_color"); Config::Lua::CLuaConfigColor parser(0); auto err = parser.parse(L); if (err.errorCode != Config::Lua::PARSE_ERROR_OK) return Config::Lua::Bindings::Internal::configError(L, "add_button: failed to parse fg_color"); button.fgcol = parser.parsed(); } { Hyprutils::Utils::CScopeGuard x([L] { lua_pop(L, 1); }); lua_getfield(L, 1, "size"); if (!lua_isnumber(L, -1)) return Config::Lua::Bindings::Internal::configError(L, "add_button: size must be an integer"); button.size = lua_tointeger(L, -1); } { Hyprutils::Utils::CScopeGuard x([L] { lua_pop(L, 1); }); lua_getfield(L, 1, "icon"); if (!lua_isstring(L, -1)) return Config::Lua::Bindings::Internal::configError(L, "add_button: icon must be a string"); button.icon = lua_tostring(L, -1); } { Hyprutils::Utils::CScopeGuard x([L] { lua_pop(L, 1); }); lua_getfield(L, 1, "action"); if (!lua_isstring(L, -1)) return Config::Lua::Bindings::Internal::configError(L, "add_button: action must be a string"); button.cmd = lua_tostring(L, -1); } g_pGlobalState->buttons.push_back(std::move(button)); for (auto& b : g_pGlobalState->bars) { b->m_bButtonsDirty = true; } return 0; } 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 = Event::bus()->m_events.window.open.listen([&](PHLWINDOW w) { onNewWindow(w); }); static auto P3 = Event::bus()->m_events.window.updateRules.listen([&](PHLWINDOW w) { onUpdateWindowRules(w); }); g_pGlobalState->config.barColor = makeShared("plugin:hyprbars:bar_color", "Change the bar color", *configStringToInt("rgba(33333388)")); g_pGlobalState->config.textColor = makeShared("plugin:hyprbars:col.text", "Change the text color", *configStringToInt("rgba(ffffffff)")); g_pGlobalState->config.inactiveButtonColor = makeShared( "plugin:hyprbars:inactive_button_color", "Change the inactive button's color. 0x00000000 means unset", *configStringToInt("rgba(00000000)")); g_pGlobalState->config.barHeight = makeShared("plugin:hyprbars:bar_height", "Change the bar's height", 15); g_pGlobalState->config.barTextSize = makeShared("plugin:hyprbars:bar_text_size", "Change the bar's text size", 10); g_pGlobalState->config.barTitleEnabled = makeShared("plugin:hyprbars:bar_title_enabled", "Whether to enable titles in the bar", true); g_pGlobalState->config.barBlur = makeShared("plugin:hyprbars:bar_blur", "Whether to enable blur of the bar", false); g_pGlobalState->config.barTextFont = makeShared("plugin:hyprbars:bar_text_font", "Bar's text font", "Sans"); g_pGlobalState->config.barTextAlign = makeShared("plugin:hyprbars:bar_text_align", "Bar's text alignment", "center"); g_pGlobalState->config.barPartOfWindow = makeShared("plugin:hyprbars:bar_part_of_window", "Whether the bar is a part of the window (reserves space)", true); g_pGlobalState->config.barPrecedenceOverBorder = makeShared("plugin:hyprbars:bar_precedence_over_border", "Whether the bar is before, or after the border", false); g_pGlobalState->config.barButtonsAlignment = makeShared("plugin:hyprbars:bar_buttons_alignment", "Alignment of the bar buttons", "right"); g_pGlobalState->config.barPadding = makeShared("plugin:hyprbars:bar_padding", "Padding of the bar", 7); g_pGlobalState->config.barButtonPadding = makeShared("plugin:hyprbars:bar_button_padding", "Padding of the bar buttons", 5); g_pGlobalState->config.enabled = makeShared("plugin:hyprbars:enabled", "Whether bars are enabled", true); g_pGlobalState->config.iconOnHover = makeShared("plugin:hyprbars:icon_on_hover", "Whether to use an icon on hover of the buttons", false); g_pGlobalState->config.onDoubleClick = makeShared("plugin:hyprbars:on_double_click", "Action to execute on double click of the bar", ""); HyprlandAPI::addConfigValueV2(PHANDLE, g_pGlobalState->config.barColor); HyprlandAPI::addConfigValueV2(PHANDLE, g_pGlobalState->config.textColor); HyprlandAPI::addConfigValueV2(PHANDLE, g_pGlobalState->config.inactiveButtonColor); HyprlandAPI::addConfigValueV2(PHANDLE, g_pGlobalState->config.barHeight); HyprlandAPI::addConfigValueV2(PHANDLE, g_pGlobalState->config.barTextSize); HyprlandAPI::addConfigValueV2(PHANDLE, g_pGlobalState->config.barTitleEnabled); HyprlandAPI::addConfigValueV2(PHANDLE, g_pGlobalState->config.barBlur); HyprlandAPI::addConfigValueV2(PHANDLE, g_pGlobalState->config.barTextFont); HyprlandAPI::addConfigValueV2(PHANDLE, g_pGlobalState->config.barTextAlign); HyprlandAPI::addConfigValueV2(PHANDLE, g_pGlobalState->config.barPartOfWindow); HyprlandAPI::addConfigValueV2(PHANDLE, g_pGlobalState->config.barPrecedenceOverBorder); HyprlandAPI::addConfigValueV2(PHANDLE, g_pGlobalState->config.barButtonsAlignment); HyprlandAPI::addConfigValueV2(PHANDLE, g_pGlobalState->config.barPadding); HyprlandAPI::addConfigValueV2(PHANDLE, g_pGlobalState->config.barButtonPadding); HyprlandAPI::addConfigValueV2(PHANDLE, g_pGlobalState->config.enabled); HyprlandAPI::addConfigValueV2(PHANDLE, g_pGlobalState->config.iconOnHover); HyprlandAPI::addConfigValueV2(PHANDLE, g_pGlobalState->config.onDoubleClick); if (Config::mgr()->type() == Config::CONFIG_LEGACY) HyprlandAPI::addConfigKeyword(PHANDLE, "plugin:hyprbars:hyprbars-button", onNewButton, Hyprlang::SHandlerOptions{}); else HyprlandAPI::addLuaFunction(PHANDLE, "hyprbars", "add_button", ::newLuaButton); static auto P4 = Event::bus()->m_events.config.preReload.listen([&] { onPreConfigReload(); }); static auto P5 = Event::bus()->m_events.config.reloaded.listen([&] { onConfigReloaded(); }); // add deco to existing windows for (auto& w : g_pCompositor->m_windows) { if (w->isHidden() || !w->m_isMapped) continue; onNewWindow(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); }