diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ad5ce0..d886f50 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,9 @@ pkg_check_modules(DEPS REQUIRED hyprland pixman-1 libdrm pango pangocairo) add_library(hy3 SHARED src/main.cpp + src/dispatchers.cpp src/Hy3Layout.cpp + src/Hy3Node.cpp src/TabGroup.cpp src/SelectionHook.cpp ) diff --git a/src/Hy3Layout.cpp b/src/Hy3Layout.cpp index a73796d..02f1e33 100644 --- a/src/Hy3Layout.cpp +++ b/src/Hy3Layout.cpp @@ -1,763 +1,20 @@ -#include "Hy3Layout.hpp" -#include "globals.hpp" -#include "SelectionHook.hpp" +#include #include #include -#include - -void errorNotif() { - HyprlandAPI::addNotificationV2( - PHANDLE, - { - {"text", "Something has gone very wrong. Check the log for details."}, - {"time", (uint64_t) 10000}, - {"color", CColor(1.0, 0.0, 0.0, 1.0)}, - {"icon", ICON_ERROR}, - } - ); -} - -Hy3GroupData::Hy3GroupData(Hy3GroupLayout layout): layout(layout) {} - -Hy3GroupData::Hy3GroupData(Hy3GroupData&& from) { - this->layout = from.layout; - this->children = std::move(from.children); - this->group_focused = from.group_focused; - this->focused_child = from.focused_child; - from.focused_child = nullptr; - this->tab_bar = from.tab_bar; - from.tab_bar = nullptr; -} - -Hy3GroupData::~Hy3GroupData() { - if (this->tab_bar != nullptr) this->tab_bar->bar.beginDestroy(); -} - -Hy3NodeData::Hy3NodeData(): Hy3NodeData((CWindow*) nullptr) {} - -Hy3NodeData::Hy3NodeData(CWindow* window): type(Hy3NodeType::Window) { this->as_window = window; } - -Hy3NodeData::Hy3NodeData(Hy3GroupData group): type(Hy3NodeType::Group) { - new (&this->as_group) Hy3GroupData(std::move(group)); -} - -Hy3NodeData::Hy3NodeData(Hy3GroupLayout layout): Hy3NodeData(Hy3GroupData(layout)) {} - -Hy3NodeData::~Hy3NodeData() { - switch (this->type) { - case Hy3NodeType::Window: break; - case Hy3NodeType::Group: - this->as_group.~Hy3GroupData(); - - // who ever thought calling the dtor after a move was a good idea? - this->type = Hy3NodeType::Window; - break; - } -} - -Hy3NodeData::Hy3NodeData(Hy3NodeData&& from): type(from.type) { - Debug::log( - LOG, - "Move CTor type matches? %d is group? %d", - this->type == from.type, - this->type == Hy3NodeType::Group - ); - - switch (from.type) { - case Hy3NodeType::Window: this->as_window = from.as_window; break; - case Hy3NodeType::Group: new (&this->as_group) Hy3GroupData(std::move(from.as_group)); break; - } -} - -Hy3NodeData& Hy3NodeData::operator=(Hy3NodeData&& from) { - Debug::log( - LOG, - "operator= type matches? %d is group? %d", - this->type == from.type, - this->type == Hy3NodeType::Group - ); - - if (this->type == Hy3NodeType::Group) { - this->as_group.~Hy3GroupData(); - } - - this->type = from.type; - - switch (this->type) { - case Hy3NodeType::Window: this->as_window = from.as_window; break; - case Hy3NodeType::Group: new (&this->as_group) Hy3GroupData(std::move(from.as_group)); break; - } - - return *this; -} - -Hy3NodeData& Hy3NodeData::operator=(CWindow* window) { - *this = Hy3NodeData(window); - - return *this; -} - -Hy3NodeData& Hy3NodeData::operator=(Hy3GroupLayout layout) { - *this = Hy3NodeData(layout); - - return *this; -} - -bool Hy3NodeData::operator==(const Hy3NodeData& rhs) const { return this == &rhs; } - -bool Hy3Node::operator==(const Hy3Node& rhs) const { return this->data == rhs.data; } - -void Hy3Node::recalcSizePosRecursive(bool no_animation) { - // clang-format off - static const auto* gaps_in = &HyprlandAPI::getConfigValue(PHANDLE, "general:gaps_in")->intValue; - static const auto* gaps_out = &HyprlandAPI::getConfigValue(PHANDLE, "general:gaps_out")->intValue; - static const auto* group_inset = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:group_inset")->intValue; - static const auto* tab_bar_height = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:height")->intValue; - static const auto* tab_bar_padding = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:padding")->intValue; - // clang-format on - - int outer_gaps = 0; - Vector2D gap_pos_offset; - Vector2D gap_size_offset; - if (this->parent == nullptr) { - outer_gaps = *gaps_out - *gaps_in; - - gap_pos_offset = Vector2D(outer_gaps, outer_gaps); - gap_size_offset = Vector2D(outer_gaps * 2, outer_gaps * 2); - } else { - gap_pos_offset = this->gap_pos_offset; - gap_size_offset = this->gap_size_offset; - } - - auto tpos = this->position; - auto tsize = this->size; - - double tab_height_offset = *tab_bar_height + *tab_bar_padding; - - if (this->data.type != Hy3NodeType::Group) { - this->data.as_window->setHidden(this->hidden); - this->layout->applyNodeDataToWindow(this, no_animation); - return; - } - - auto* group = &this->data.as_group; - - if (group->children.size() == 1 && this->parent != nullptr) { - auto child = group->children.front(); - - if (child == this) { - Debug::log(ERR, "a group (%p) has become its own child", this); - errorNotif(); - } - - switch (group->layout) { - case Hy3GroupLayout::SplitH: - child->position.x = tpos.x; - child->size.x = tsize.x - *group_inset; - child->position.y = tpos.y; - child->size.y = tsize.y; - break; - case Hy3GroupLayout::SplitV: - child->position.y = tpos.y; - child->size.y = tsize.y - *group_inset; - child->position.x = tpos.x; - child->size.x = tsize.x; - break; - case Hy3GroupLayout::Tabbed: - child->position.y = tpos.y + tab_height_offset; - child->size.y = tsize.y - tab_height_offset; - child->position.x = tpos.x; - child->size.x = tsize.x; - break; - } - - child->gap_pos_offset = gap_pos_offset; - child->gap_size_offset = gap_size_offset; - - child->setHidden(this->hidden); - - child->recalcSizePosRecursive(no_animation); - this->updateTabBar(no_animation); - return; - } - - int constraint; - switch (group->layout) { - case Hy3GroupLayout::SplitH: constraint = tsize.x; break; - case Hy3GroupLayout::SplitV: constraint = tsize.y; break; - case Hy3GroupLayout::Tabbed: break; - } - - double ratio_mul = group->layout != Hy3GroupLayout::Tabbed - ? group->children.empty() ? 0 : constraint / group->children.size() - : 0; - - double offset = 0; - - if (group->layout == Hy3GroupLayout::Tabbed && group->focused_child != nullptr - && !group->focused_child->hidden) - { - group->focused_child->setHidden(false); - - auto box = wlr_box {tpos.x, tpos.y, tsize.x, tsize.y}; - g_pHyprRenderer->damageBox(&box); - } - - for (auto* child: group->children) { - switch (group->layout) { - case Hy3GroupLayout::SplitH: - child->position.x = tpos.x + offset; - child->size.x = child->size_ratio * ratio_mul; - offset += child->size.x; - child->position.y = tpos.y; - child->size.y = tsize.y; - child->setHidden(this->hidden); - child->recalcSizePosRecursive(no_animation); - break; - case Hy3GroupLayout::SplitV: - child->position.y = tpos.y + offset; - child->size.y = child->size_ratio * ratio_mul; - offset += child->size.y; - child->position.x = tpos.x; - child->size.x = tsize.x; - child->setHidden(this->hidden); - child->recalcSizePosRecursive(no_animation); - break; - case Hy3GroupLayout::Tabbed: - child->position.y = tpos.y + tab_height_offset; - child->size.y = tsize.y - tab_height_offset; - child->position.x = tpos.x; - child->size.x = tsize.x; - bool hidden = this->hidden || group->focused_child != child; - child->setHidden(hidden); - child->recalcSizePosRecursive(no_animation); - break; - } - - child->gap_pos_offset = gap_pos_offset; - child->gap_size_offset = gap_pos_offset; - } - - this->updateTabBar(); -} - -void Hy3Node::setHidden(bool hidden) { - this->hidden = hidden; - - if (this->data.type == Hy3NodeType::Group) { - for (auto* child: this->data.as_group.children) { - child->setHidden(hidden); - } - } -} - -bool Hy3Node::isUrgent() { - switch (this->data.type) { - case Hy3NodeType::Window: return this->data.as_window->m_bIsUrgent; - case Hy3NodeType::Group: - for (auto* child: this->data.as_group.children) { - if (child->isUrgent()) return true; - } - - return false; - } -} - -bool Hy3Node::isIndirectlyFocused() { - Hy3Node* node = this; - - while (node->parent != nullptr) { - if (!node->parent->data.as_group.group_focused - && node->parent->data.as_group.focused_child != node) - return false; - node = node->parent; - } - - return true; -} - -std::string Hy3Node::getTitle() { - switch (this->data.type) { - case Hy3NodeType::Window: return this->data.as_window->m_szTitle; - case Hy3NodeType::Group: - std::string title; - - switch (this->data.as_group.layout) { - case Hy3GroupLayout::SplitH: title = "[H] "; break; - case Hy3GroupLayout::SplitV: title = "[V] "; break; - case Hy3GroupLayout::Tabbed: title = "[T] "; break; - } - - if (this->data.as_group.focused_child == nullptr) { - title += "Group"; - } else { - title += this->data.as_group.focused_child->getTitle(); - } - - return title; - } - - return ""; -} - -void Hy3Node::appendAllWindows(std::vector& list) { - switch (this->data.type) { - case Hy3NodeType::Window: list.push_back(this->data.as_window); break; - case Hy3NodeType::Group: - for (auto* child: this->data.as_group.children) { - child->appendAllWindows(list); - } - break; - } -} - -void markGroupFocusedRecursive(Hy3GroupData& group) { - group.group_focused = true; - for (auto& child: group.children) { - if (child->data.type == Hy3NodeType::Group) markGroupFocusedRecursive(child->data.as_group); - } -} - -void Hy3Node::markFocused() { - Hy3Node* node = this; - - // undo decos for root focus - auto* root = node; - while (root->parent != nullptr) root = root->parent; - - // update focus - if (this->data.type == Hy3NodeType::Group) { - markGroupFocusedRecursive(this->data.as_group); - } - - auto* node2 = node; - while (node2->parent != nullptr) { - node2->parent->data.as_group.focused_child = node2; - node2->parent->data.as_group.group_focused = false; - node2 = node2->parent; - } - - root->updateDecos(); -} - -void Hy3Node::focus() { - this->markFocused(); - - switch (this->data.type) { - case Hy3NodeType::Window: - this->data.as_window->setHidden(false); - g_pCompositor->focusWindow(this->data.as_window); - break; - case Hy3NodeType::Group: - g_pCompositor->focusWindow(nullptr); - this->raiseToTop(); - break; - } -} - -bool Hy3Node::focusWindow() { - switch (this->data.type) { - case Hy3NodeType::Window: - this->markFocused(); - g_pCompositor->focusWindow(this->data.as_window); - - return true; - case Hy3NodeType::Group: - if (this->data.as_group.layout == Hy3GroupLayout::Tabbed) { - if (this->data.as_group.focused_child != nullptr) { - return this->data.as_group.focused_child->focusWindow(); - } - } else { - for (auto* node: this->data.as_group.children) { - if (node->focusWindow()) break; - } - } - - return false; - } -} - -void Hy3Node::raiseToTop() { - switch (this->data.type) { - case Hy3NodeType::Window: g_pCompositor->moveWindowToTop(this->data.as_window); break; - case Hy3NodeType::Group: - for (auto* child: this->data.as_group.children) { - child->raiseToTop(); - } - break; - } -} - -Hy3Node* Hy3Node::getFocusedNode() { - switch (this->data.type) { - case Hy3NodeType::Window: return this; - case Hy3NodeType::Group: - if (this->data.as_group.focused_child == nullptr || this->data.as_group.group_focused) { - return this; - } else { - return this->data.as_group.focused_child->getFocusedNode(); - } - } -} - -bool Hy3Node::swallowGroups(Hy3Node* into) { - if (into == nullptr || into->data.type != Hy3NodeType::Group - || into->data.as_group.children.size() != 1) - return false; - - auto* child = into->data.as_group.children.front(); - - // a lot of segfaulting happens once the assumption that the root node is a - // group is wrong. - if (into->parent == nullptr && child->data.type != Hy3NodeType::Group) return false; - - Debug::log(LOG, "Swallowing %p into %p", child, into); - Hy3Node::swapData(*into, *child); - into->layout->nodes.remove(*child); - - return true; -} - -Hy3Node* Hy3Node::removeFromParentRecursive() { - Hy3Node* parent = this; - - Debug::log(LOG, "Recursively removing parent nodes of %p", parent); - - while (parent != nullptr) { - if (parent->parent == nullptr) { - Debug::log(ERR, "* UAF DEBUGGING - %p's parent is null, its the root group", parent); - - if (parent == this) { - Debug::log(ERR, "* UAF DEBUGGING - returning nullptr as this == root group"); - } else { - Debug::log(ERR, "* UAF DEBUGGING - deallocing %p and returning nullptr", parent); - parent->layout->nodes.remove(*parent); - } - return nullptr; - } - - auto* child = parent; - parent = parent->parent; - auto& group = parent->data.as_group; - - if (group.children.size() > 2) { - auto iter = std::find(group.children.begin(), group.children.end(), child); - - group.group_focused = false; - if (iter == group.children.begin()) { - group.focused_child = *std::next(iter); - } else { - group.focused_child = *std::prev(iter); - } - } - - if (!group.children.remove(child)) { - Debug::log( - ERR, - "Was unable to remove child node %p from parent %p. Child likely has " - "a false parent pointer.", - child, - parent - ); - - errorNotif(); - return nullptr; - } - - group.group_focused = false; - if (group.children.size() == 1) { - group.focused_child = group.children.front(); - } - - auto child_size_ratio = child->size_ratio; - if (child != this) { - parent->layout->nodes.remove(*child); - } else { - child->parent = nullptr; - } - - if (!group.children.empty()) { - auto child_count = group.children.size(); - if (std::find(group.children.begin(), group.children.end(), this) != group.children.end()) { - child_count -= 1; - } - - auto splitmod = -((1.0 - child_size_ratio) / child_count); - - for (auto* child: group.children) { - child->size_ratio += splitmod; - } - - break; - } - } - - return parent; -} - -Hy3Node* Hy3Node::intoGroup(Hy3GroupLayout layout) { - this->layout->nodes.push_back({ - .parent = this, - .data = layout, - .workspace_id = this->workspace_id, - .layout = this->layout, - }); - - auto* node = &this->layout->nodes.back(); - swapData(*this, *node); - - this->data = layout; - this->data.as_group.children.push_back(node); - this->data.as_group.group_focused = false; - this->data.as_group.focused_child = node; - this->recalcSizePosRecursive(); - this->updateTabBarRecursive(); - - return node; -} - -bool Hy3GroupData::hasChild(Hy3Node* node) { - Debug::log(LOG, "Searching for child %p of %p", this, node); - for (auto child: this->children) { - if (child == node) return true; - - if (child->data.type == Hy3NodeType::Group) { - if (child->data.as_group.hasChild(node)) return true; - } - } - - return false; -} - -void Hy3Node::swapData(Hy3Node& a, Hy3Node& b) { - Hy3NodeData aData = std::move(a.data); - a.data = std::move(b.data); - b.data = std::move(aData); - - if (a.data.type == Hy3NodeType::Group) { - for (auto child: a.data.as_group.children) { - child->parent = &a; - } - } - - if (b.data.type == Hy3NodeType::Group) { - for (auto child: b.data.as_group.children) { - child->parent = &b; - } - } -} - -struct FindTopWindowInNodeResult { - CWindow* window = nullptr; - size_t index = 0; -}; - -void findTopWindowInNode(Hy3Node& node, FindTopWindowInNodeResult& result) { - switch (node.data.type) { - case Hy3NodeType::Window: { - auto* window = node.data.as_window; - auto& windows = g_pCompositor->m_vWindows; - - for (; result.index < windows.size(); result.index++) { - if (&*windows[result.index] == window) { - result.window = window; - break; - } - } - - } break; - case Hy3NodeType::Group: { - auto& group = node.data.as_group; - - if (group.layout == Hy3GroupLayout::Tabbed) { - if (group.focused_child != nullptr) findTopWindowInNode(*group.focused_child, result); - } else { - for (auto* child: group.children) { - findTopWindowInNode(*child, result); - } - } - } break; - } -} - -void Hy3Node::updateTabBar(bool no_animation) { - if (this->data.type == Hy3NodeType::Group) { - auto& group = this->data.as_group; - - if (group.layout == Hy3GroupLayout::Tabbed) { - if (group.tab_bar == nullptr) group.tab_bar = &this->layout->tab_groups.emplace_back(*this); - group.tab_bar->updateWithGroup(*this, no_animation); - - FindTopWindowInNodeResult result; - findTopWindowInNode(*this, result); - group.tab_bar->target_window = result.window; - if (result.window != nullptr) group.tab_bar->workspace_id = result.window->m_iWorkspaceID; - } else if (group.tab_bar != nullptr) { - group.tab_bar->bar.beginDestroy(); - group.tab_bar = nullptr; - } - } -} - -void Hy3Node::updateTabBarRecursive() { - auto* node = this; - - do { - node->updateTabBar(); - node = node->parent; - } while (node != nullptr); -} - -void Hy3Node::updateDecos() { - switch (this->data.type) { - case Hy3NodeType::Window: - if (this->data.as_window->m_bIsMapped) - g_pCompositor->updateWindowAnimatedDecorationValues(this->data.as_window); - break; - case Hy3NodeType::Group: - for (auto* child: this->data.as_group.children) { - child->updateDecos(); - } - - this->updateTabBar(); - } -} - -int Hy3Layout::getWorkspaceNodeCount(const int& id) { - int count = 0; - - for (auto& node: this->nodes) { - if (node.workspace_id == id && node.valid) count++; - } - - return count; -} - -Hy3Node* Hy3Layout::getNodeFromWindow(CWindow* window) { - for (auto& node: this->nodes) { - if (node.data.type == Hy3NodeType::Window && node.data.as_window == window) { - return &node; - } - } - - return nullptr; -} - -Hy3Node* Hy3Layout::getWorkspaceRootGroup(const int& workspace) { - for (auto& node: this->nodes) { - if (node.workspace_id == workspace && node.parent == nullptr - && node.data.type == Hy3NodeType::Group) - { - return &node; - } - } - - return nullptr; -} - -Hy3Node* Hy3Layout::getWorkspaceFocusedNode(const int& workspace) { - auto* rootNode = this->getWorkspaceRootGroup(workspace); - if (rootNode == nullptr) return nullptr; - return rootNode->getFocusedNode(); -} - -void Hy3Layout::applyNodeDataToWindow(Hy3Node* node, bool no_animation) { - if (node->data.type != Hy3NodeType::Window) return; - CWindow* window = node->data.as_window; - - CMonitor* monitor = nullptr; - - if (g_pCompositor->isWorkspaceSpecial(node->workspace_id)) { - for (auto& m: g_pCompositor->m_vMonitors) { - if (m->specialWorkspaceID == node->workspace_id) { - monitor = m.get(); - break; - } - } - } else { - monitor = g_pCompositor->getMonitorFromID( - g_pCompositor->getWorkspaceByID(node->workspace_id)->m_iMonitorID - ); - } - - if (monitor == nullptr) { - Debug::log(ERR, "Orphaned Node %x (workspace ID: %i)!!", node, node->workspace_id); - errorNotif(); - return; - } - - // clang-format off - static const auto* border_size = &HyprlandAPI::getConfigValue(PHANDLE, "general:border_size")->intValue; - static const auto* gaps_in = &HyprlandAPI::getConfigValue(PHANDLE, "general:gaps_in")->intValue; - static const auto* single_window_no_gaps = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:no_gaps_when_only")->intValue; - // clang-format on - - if (!g_pCompositor->windowExists(window) || !window->m_bIsMapped) { - Debug::log(ERR, "Node %p holding invalid window %p!!", node, window); - errorNotif(); - this->onWindowRemovedTiling(window); - return; - } - - window->m_vSize = node->size; - window->m_vPosition = node->position; - - auto calcPos = window->m_vPosition + Vector2D(*border_size, *border_size); - auto calcSize = window->m_vSize - Vector2D(2 * *border_size, 2 * *border_size); - - auto root_node = this->getWorkspaceRootGroup(window->m_iWorkspaceID); - auto only_node = root_node->data.as_group.children.size() == 1 - && root_node->data.as_group.children.front()->data.type == Hy3NodeType::Window; - - if (!g_pCompositor->isWorkspaceSpecial(window->m_iWorkspaceID) - && ((*single_window_no_gaps && only_node) - || (window->m_bIsFullscreen - && g_pCompositor->getWorkspaceByID(window->m_iWorkspaceID)->m_efFullscreenMode - == FULLSCREEN_FULL))) - { - window->m_vRealPosition = window->m_vPosition; - window->m_vRealSize = window->m_vSize; - - window->updateWindowDecos(); - - window->m_sSpecialRenderData.rounding = false; - window->m_sSpecialRenderData.border = false; - window->m_sSpecialRenderData.decorate = false; - } else { - window->m_sSpecialRenderData.rounding = true; - window->m_sSpecialRenderData.border = true; - window->m_sSpecialRenderData.decorate = true; - - auto gaps_offset_topleft = Vector2D(*gaps_in, *gaps_in) + node->gap_pos_offset; - auto gaps_offset_bottomright = Vector2D(*gaps_in * 2, *gaps_in * 2) + node->gap_size_offset; - - calcPos = calcPos + gaps_offset_topleft; - calcSize = calcSize - gaps_offset_bottomright; - - const auto reserved_area = window->getFullWindowReservedArea(); - calcPos = calcPos + reserved_area.topLeft; - calcSize = calcSize - (reserved_area.topLeft - reserved_area.bottomRight); - - window->m_vRealPosition = calcPos; - window->m_vRealSize = calcSize; - Debug::log(LOG, "Set size (%f %f)", calcSize.x, calcSize.y); - - g_pXWaylandManager->setWindowSize(window, calcSize); - - if (no_animation) { - g_pHyprRenderer->damageWindow(window); - - window->m_vRealPosition.warp(); - window->m_vRealSize.warp(); - - g_pHyprRenderer->damageWindow(window); - } - - window->updateWindowDecos(); - } -} +#include "globals.hpp" +#include "Hy3Layout.hpp" +#include "SelectionHook.hpp" + +std::unique_ptr renderHookPtr + = std::make_unique(Hy3Layout::renderHook); +std::unique_ptr windowTitleHookPtr + = std::make_unique(Hy3Layout::windowGroupUpdateRecursiveHook); +std::unique_ptr urgentHookPtr + = std::make_unique(Hy3Layout::windowGroupUrgentHook); +std::unique_ptr tickHookPtr + = std::make_unique(Hy3Layout::tickHook); void Hy3Layout::onWindowCreatedTiling(CWindow* window) { if (window->m_bIsFloating) return; @@ -918,16 +175,6 @@ void Hy3Layout::onWindowRemovedTiling(CWindow* window) { } } -CWindow* Hy3Layout::getNextWindowCandidate(CWindow* window) { - auto* node = this->getWorkspaceFocusedNode(window->m_iWorkspaceID); - if (node == nullptr) return nullptr; - - switch (node->data.type) { - case Hy3NodeType::Window: return node->data.as_window; - case Hy3NodeType::Group: return nullptr; - } -} - void Hy3Layout::onWindowFocusChange(CWindow* window) { Debug::log(LOG, "Switched windows to %p", window); auto* node = this->getNodeFromWindow(window); @@ -1333,6 +580,16 @@ void Hy3Layout::alterSplitRatio(CWindow* pWindow, float delta, bool exact) { std::string Hy3Layout::getLayoutName() { return "hy3"; } +CWindow* Hy3Layout::getNextWindowCandidate(CWindow* window) { + auto* node = this->getWorkspaceFocusedNode(window->m_iWorkspaceID); + if (node == nullptr) return nullptr; + + switch (node->data.type) { + case Hy3NodeType::Window: return node->data.as_window; + case Hy3NodeType::Group: return nullptr; + } +} + void Hy3Layout::replaceWindowDataWith(CWindow* from, CWindow* to) { auto* node = this->getNodeFromWindow(from); if (node == nullptr) return; @@ -1341,15 +598,6 @@ void Hy3Layout::replaceWindowDataWith(CWindow* from, CWindow* to) { this->applyNodeDataToWindow(node); } -std::unique_ptr renderHookPtr - = std::make_unique(Hy3Layout::renderHook); -std::unique_ptr windowTitleHookPtr - = std::make_unique(Hy3Layout::windowGroupUpdateRecursiveHook); -std::unique_ptr urgentHookPtr - = std::make_unique(Hy3Layout::windowGroupUrgentHook); -std::unique_ptr tickHookPtr - = std::make_unique(Hy3Layout::tickHook); - void Hy3Layout::onEnable() { for (auto& window: g_pCompositor->m_vWindows) { if (window->isHidden() || !window->m_bIsMapped || window->m_bFadingOut || window->m_bIsFloating) @@ -1425,19 +673,6 @@ void Hy3Layout::makeOppositeGroupOn(Hy3Node* node) { } } -void Hy3Layout::shiftFocus(int workspace, ShiftDirection direction, bool visible) { - auto* node = this->getWorkspaceFocusedNode(workspace); - Debug::log(LOG, "ShiftFocus %p %d", node, direction); - if (node == nullptr) return; - - Hy3Node* target; - if ((target = this->shiftOrGetFocus(*node, direction, false, false, visible))) { - target->focus(); - while (target->parent != nullptr) target = target->parent; - target->recalcSizePosRecursive(); - } -} - void Hy3Layout::shiftWindow(int workspace, ShiftDirection direction, bool once) { auto* node = this->getWorkspaceFocusedNode(workspace); Debug::log(LOG, "ShiftWindow %p %d", node, direction); @@ -1458,6 +693,454 @@ void Hy3Layout::shiftWindow(int workspace, ShiftDirection direction, bool once) } } +void Hy3Layout::shiftFocus(int workspace, ShiftDirection direction, bool visible) { + auto* node = this->getWorkspaceFocusedNode(workspace); + Debug::log(LOG, "ShiftFocus %p %d", node, direction); + if (node == nullptr) return; + + Hy3Node* target; + if ((target = this->shiftOrGetFocus(*node, direction, false, false, visible))) { + target->focus(); + while (target->parent != nullptr) target = target->parent; + target->recalcSizePosRecursive(); + } +} + +void Hy3Layout::changeFocus(int workspace, FocusShift shift) { + auto* node = this->getWorkspaceFocusedNode(workspace); + if (node == nullptr) return; + + switch (shift) { + case FocusShift::Bottom: goto bottom; + case FocusShift::Top: + while (node->parent != nullptr) { + node = node->parent; + } + + node->focus(); + return; + case FocusShift::Raise: + if (node->parent == nullptr) goto bottom; + else { + node->parent->focus(); + } + return; + case FocusShift::Lower: + if (node->data.type == Hy3NodeType::Group && node->data.as_group.focused_child != nullptr) + node->data.as_group.focused_child->focus(); + return; + case FocusShift::Tab: + // make sure we go up at least one level + if (node->parent != nullptr) node = node->parent; + while (node->parent != nullptr) { + if (node->data.as_group.layout == Hy3GroupLayout::Tabbed) { + node->focus(); + return; + } + + node = node->parent; + } + return; + case FocusShift::TabNode: + // make sure we go up at least one level + if (node->parent != nullptr) node = node->parent; + while (node->parent != nullptr) { + if (node->parent->data.as_group.layout == Hy3GroupLayout::Tabbed) { + node->focus(); + return; + } + + node = node->parent; + } + return; + } + +bottom: + while (node->data.type == Hy3NodeType::Group && node->data.as_group.focused_child != nullptr) { + node = node->data.as_group.focused_child; + } + + node->focus(); + return; +} + +Hy3Node* findTabBarAt(Hy3Node& node, Vector2D pos, Hy3Node** focused_node) { + // clang-format off + static const auto* gaps_in = &HyprlandAPI::getConfigValue(PHANDLE, "general:gaps_in")->intValue; + static const auto* gaps_out = &HyprlandAPI::getConfigValue(PHANDLE, "general:gaps_out")->intValue; + static const auto* tab_bar_height = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:height")->intValue; + static const auto* tab_bar_padding = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:padding")->intValue; + // clang-format on + + auto inset = *tab_bar_height + *tab_bar_padding; + + if (node.parent == nullptr) { + inset += *gaps_out; + } else { + inset += *gaps_in; + } + + if (node.data.type == Hy3NodeType::Group) { + if (node.hidden) return nullptr; + // note: tab bar clicks ignore animations + if (node.position.x > pos.x || node.position.y > pos.y || node.position.x + node.size.x < pos.x + || node.position.y + node.size.y < pos.y) + return nullptr; + + if (node.data.as_group.layout == Hy3GroupLayout::Tabbed + && node.data.as_group.tab_bar != nullptr) + { + if (pos.y < node.position.y + inset) { + auto& children = node.data.as_group.children; + auto& tab_bar = *node.data.as_group.tab_bar; + + auto size = tab_bar.size.vec(); + auto x = pos.x - tab_bar.pos.vec().x; + auto child_iter = children.begin(); + + for (auto& tab: tab_bar.bar.entries) { + if (child_iter == children.end()) break; + + if (x > tab.offset.fl() * size.x && x < (tab.offset.fl() + tab.width.fl()) * size.x) { + *focused_node = *child_iter; + return &node; + } + + child_iter = std::next(child_iter); + } + } + + if (node.data.as_group.focused_child != nullptr) { + return findTabBarAt(*node.data.as_group.focused_child, pos, focused_node); + } + } else { + for (auto child: node.data.as_group.children) { + if (findTabBarAt(*child, pos, focused_node)) return child; + } + } + } + + return nullptr; +} + +void Hy3Layout::focusTab( + int workspace, + TabFocus target, + TabFocusMousePriority mouse, + bool wrap_scroll, + int index +) { + auto* node = this->getWorkspaceRootGroup(workspace); + if (node == nullptr) return; + + Hy3Node* tab_node = nullptr; + Hy3Node* tab_focused_node; + + if (target == TabFocus::MouseLocation || mouse != TabFocusMousePriority::Ignore) { + if (g_pCompositor->windowFloatingFromCursor() == nullptr) { + auto mouse_pos = g_pInputManager->getMouseCoordsInternal(); + tab_node = findTabBarAt(*node, mouse_pos, &tab_focused_node); + if (tab_node != nullptr) goto hastab; + } + + if (target == TabFocus::MouseLocation || mouse == TabFocusMousePriority::Require) return; + } + + if (tab_node == nullptr) { + tab_node = this->getWorkspaceFocusedNode(workspace); + if (tab_node == nullptr) return; + + while (tab_node != nullptr && tab_node->data.as_group.layout != Hy3GroupLayout::Tabbed + && tab_node->parent != nullptr) + tab_node = tab_node->parent; + + if (tab_node == nullptr || tab_node->data.type != Hy3NodeType::Group + || tab_node->data.as_group.layout != Hy3GroupLayout::Tabbed) + return; + } + +hastab: + if (target != TabFocus::MouseLocation) { + if (tab_node->data.as_group.focused_child == nullptr + || tab_node->data.as_group.children.size() < 2) + return; + + auto& children = tab_node->data.as_group.children; + if (target == TabFocus::Index) { + int i = 1; + + for (auto* node: children) { + if (i == index) { + tab_focused_node = node; + goto cont; + } + + i++; + } + + return; + cont:; + } else { + auto node_iter + = std::find(children.begin(), children.end(), tab_node->data.as_group.focused_child); + if (node_iter == children.end()) return; + if (target == TabFocus::Left) { + if (node_iter == children.begin()) { + if (wrap_scroll) node_iter = std::prev(children.end()); + else return; + } else node_iter = std::prev(node_iter); + + tab_focused_node = *node_iter; + } else { + if (node_iter == std::prev(children.end())) { + if (wrap_scroll) node_iter = children.begin(); + else return; + } else node_iter = std::next(node_iter); + + tab_focused_node = *node_iter; + } + } + } + + auto* focus = tab_focused_node; + while (focus->data.type == Hy3NodeType::Group && !focus->data.as_group.group_focused + && focus->data.as_group.focused_child != nullptr) + focus = focus->data.as_group.focused_child; + + focus->focus(); + tab_node->recalcSizePosRecursive(); +} + +void Hy3Layout::killFocusedNode(int workspace) { + if (g_pCompositor->m_pLastWindow != nullptr && g_pCompositor->m_pLastWindow->m_bIsFloating) { + g_pCompositor->closeWindow(g_pCompositor->m_pLastWindow); + } else { + auto* node = this->getWorkspaceFocusedNode(workspace); + if (node == nullptr) return; + + std::vector windows; + node->appendAllWindows(windows); + + for (auto* window: windows) { + window->setHidden(false); + g_pCompositor->closeWindow(window); + } + } +} + +bool Hy3Layout::shouldRenderSelected(CWindow* window) { + if (window == nullptr) return false; + auto* root = this->getWorkspaceRootGroup(window->m_iWorkspaceID); + if (root == nullptr || root->data.as_group.focused_child == nullptr) return false; + auto* focused = root->getFocusedNode(); + if (focused == nullptr + || (focused->data.type == Hy3NodeType::Window + && focused->data.as_window != g_pCompositor->m_pLastWindow)) + return false; + + switch (focused->data.type) { + case Hy3NodeType::Window: return focused->data.as_window == window; + case Hy3NodeType::Group: + auto* node = this->getNodeFromWindow(window); + if (node == nullptr) return false; + return focused->data.as_group.hasChild(node); + } +} + +Hy3Node* Hy3Layout::getWorkspaceRootGroup(const int& workspace) { + for (auto& node: this->nodes) { + if (node.workspace_id == workspace && node.parent == nullptr + && node.data.type == Hy3NodeType::Group) + { + return &node; + } + } + + return nullptr; +} + +Hy3Node* Hy3Layout::getWorkspaceFocusedNode(const int& workspace) { + auto* rootNode = this->getWorkspaceRootGroup(workspace); + if (rootNode == nullptr) return nullptr; + return rootNode->getFocusedNode(); +} + +void Hy3Layout::renderHook(void*, std::any data) { + static bool rendering_normally = false; + static std::vector rendered_groups; + + auto render_stage = std::any_cast(data); + + switch (render_stage) { + case RENDER_PRE_WINDOWS: + rendering_normally = true; + rendered_groups.clear(); + break; + case RENDER_POST_WINDOW: + if (!rendering_normally) break; + + for (auto& entry: g_Hy3Layout->tab_groups) { + if (!entry.hidden && entry.target_window == g_pHyprOpenGL->m_pCurrentWindow + && std::find(rendered_groups.begin(), rendered_groups.end(), &entry) + == rendered_groups.end()) + { + entry.renderTabBar(); + rendered_groups.push_back(&entry); + } + } + + break; + case RENDER_POST_WINDOWS: + rendering_normally = false; + + for (auto& entry: g_Hy3Layout->tab_groups) { + if (!entry.hidden + && entry.target_window->m_iMonitorID == g_pHyprOpenGL->m_RenderData.pMonitor->ID + && std::find(rendered_groups.begin(), rendered_groups.end(), &entry) + == rendered_groups.end()) + { + entry.renderTabBar(); + } + } + + break; + default: break; + } +} + +void Hy3Layout::windowGroupUrgentHook(void* p, std::any data) { + CWindow* window = std::any_cast(data); + if (window == nullptr) return; + window->m_bIsUrgent = true; + Hy3Layout::windowGroupUpdateRecursiveHook(p, data); +} + +void Hy3Layout::windowGroupUpdateRecursiveHook(void*, std::any data) { + CWindow* window = std::any_cast(data); + if (window == nullptr) return; + auto* node = g_Hy3Layout->getNodeFromWindow(window); + + // it is UB for `this` to be null + if (node == nullptr) return; + node->updateTabBarRecursive(); +} + +void Hy3Layout::tickHook(void*, std::any) { + auto& tab_groups = g_Hy3Layout->tab_groups; + auto entry = tab_groups.begin(); + while (entry != tab_groups.end()) { + entry->tick(); + if (entry->bar.destroy) tab_groups.erase(entry++); + else entry = std::next(entry); + } +} + +Hy3Node* Hy3Layout::getNodeFromWindow(CWindow* window) { + for (auto& node: this->nodes) { + if (node.data.type == Hy3NodeType::Window && node.data.as_window == window) { + return &node; + } + } + + return nullptr; +} + +void Hy3Layout::applyNodeDataToWindow(Hy3Node* node, bool no_animation) { + if (node->data.type != Hy3NodeType::Window) return; + CWindow* window = node->data.as_window; + + CMonitor* monitor = nullptr; + + if (g_pCompositor->isWorkspaceSpecial(node->workspace_id)) { + for (auto& m: g_pCompositor->m_vMonitors) { + if (m->specialWorkspaceID == node->workspace_id) { + monitor = m.get(); + break; + } + } + } else { + monitor = g_pCompositor->getMonitorFromID( + g_pCompositor->getWorkspaceByID(node->workspace_id)->m_iMonitorID + ); + } + + if (monitor == nullptr) { + Debug::log(ERR, "Orphaned Node %x (workspace ID: %i)!!", node, node->workspace_id); + errorNotif(); + return; + } + + // clang-format off + static const auto* border_size = &HyprlandAPI::getConfigValue(PHANDLE, "general:border_size")->intValue; + static const auto* gaps_in = &HyprlandAPI::getConfigValue(PHANDLE, "general:gaps_in")->intValue; + static const auto* single_window_no_gaps = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:no_gaps_when_only")->intValue; + // clang-format on + + if (!g_pCompositor->windowExists(window) || !window->m_bIsMapped) { + Debug::log(ERR, "Node %p holding invalid window %p!!", node, window); + errorNotif(); + this->onWindowRemovedTiling(window); + return; + } + + window->m_vSize = node->size; + window->m_vPosition = node->position; + + auto calcPos = window->m_vPosition + Vector2D(*border_size, *border_size); + auto calcSize = window->m_vSize - Vector2D(2 * *border_size, 2 * *border_size); + + auto root_node = this->getWorkspaceRootGroup(window->m_iWorkspaceID); + auto only_node = root_node->data.as_group.children.size() == 1 + && root_node->data.as_group.children.front()->data.type == Hy3NodeType::Window; + + if (!g_pCompositor->isWorkspaceSpecial(window->m_iWorkspaceID) + && ((*single_window_no_gaps && only_node) + || (window->m_bIsFullscreen + && g_pCompositor->getWorkspaceByID(window->m_iWorkspaceID)->m_efFullscreenMode + == FULLSCREEN_FULL))) + { + window->m_vRealPosition = window->m_vPosition; + window->m_vRealSize = window->m_vSize; + + window->updateWindowDecos(); + + window->m_sSpecialRenderData.rounding = false; + window->m_sSpecialRenderData.border = false; + window->m_sSpecialRenderData.decorate = false; + } else { + window->m_sSpecialRenderData.rounding = true; + window->m_sSpecialRenderData.border = true; + window->m_sSpecialRenderData.decorate = true; + + auto gaps_offset_topleft = Vector2D(*gaps_in, *gaps_in) + node->gap_pos_offset; + auto gaps_offset_bottomright = Vector2D(*gaps_in * 2, *gaps_in * 2) + node->gap_size_offset; + + calcPos = calcPos + gaps_offset_topleft; + calcSize = calcSize - gaps_offset_bottomright; + + const auto reserved_area = window->getFullWindowReservedArea(); + calcPos = calcPos + reserved_area.topLeft; + calcSize = calcSize - (reserved_area.topLeft - reserved_area.bottomRight); + + window->m_vRealPosition = calcPos; + window->m_vRealSize = calcSize; + Debug::log(LOG, "Set size (%f %f)", calcSize.x, calcSize.y); + + g_pXWaylandManager->setWindowSize(window, calcSize); + + if (no_animation) { + g_pHyprRenderer->damageWindow(window); + + window->m_vRealPosition.warp(); + window->m_vRealSize.warp(); + + g_pHyprRenderer->damageWindow(window); + } + + window->updateWindowDecos(); + } +} + bool shiftIsForward(ShiftDirection direction) { return direction == ShiftDirection::Right || direction == ShiftDirection::Down; } @@ -1665,377 +1348,3 @@ Hy3Node* Hy3Layout::shiftOrGetFocus( return nullptr; } - -void Hy3Layout::changeFocus(int workspace, FocusShift shift) { - auto* node = this->getWorkspaceFocusedNode(workspace); - if (node == nullptr) return; - - switch (shift) { - case FocusShift::Bottom: goto bottom; - case FocusShift::Top: - while (node->parent != nullptr) { - node = node->parent; - } - - node->focus(); - return; - case FocusShift::Raise: - if (node->parent == nullptr) goto bottom; - else { - node->parent->focus(); - } - return; - case FocusShift::Lower: - if (node->data.type == Hy3NodeType::Group && node->data.as_group.focused_child != nullptr) - node->data.as_group.focused_child->focus(); - return; - case FocusShift::Tab: - // make sure we go up at least one level - if (node->parent != nullptr) node = node->parent; - while (node->parent != nullptr) { - if (node->data.as_group.layout == Hy3GroupLayout::Tabbed) { - node->focus(); - return; - } - - node = node->parent; - } - return; - case FocusShift::TabNode: - // make sure we go up at least one level - if (node->parent != nullptr) node = node->parent; - while (node->parent != nullptr) { - if (node->parent->data.as_group.layout == Hy3GroupLayout::Tabbed) { - node->focus(); - return; - } - - node = node->parent; - } - return; - } - -bottom: - while (node->data.type == Hy3NodeType::Group && node->data.as_group.focused_child != nullptr) { - node = node->data.as_group.focused_child; - } - - node->focus(); - return; -} - -Hy3Node* Hy3Node::findNodeForTabGroup(Hy3TabGroup& tab_group) { - if (this->data.type == Hy3NodeType::Group) { - if (this->hidden) return nullptr; - - auto& group = this->data.as_group; - - if (group.layout == Hy3GroupLayout::Tabbed && group.tab_bar == &tab_group) { - return this; - } - - for (auto& node: group.children) { - auto* r = node->findNodeForTabGroup(tab_group); - if (r != nullptr) return r; - } - } else return nullptr; - - return nullptr; -} - -Hy3Node* findTabBarAt(Hy3Node& node, Vector2D pos, Hy3Node** focused_node) { - // clang-format off - static const auto* gaps_in = &HyprlandAPI::getConfigValue(PHANDLE, "general:gaps_in")->intValue; - static const auto* gaps_out = &HyprlandAPI::getConfigValue(PHANDLE, "general:gaps_out")->intValue; - static const auto* tab_bar_height = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:height")->intValue; - static const auto* tab_bar_padding = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:padding")->intValue; - // clang-format on - - auto inset = *tab_bar_height + *tab_bar_padding; - - if (node.parent == nullptr) { - inset += *gaps_out; - } else { - inset += *gaps_in; - } - - if (node.data.type == Hy3NodeType::Group) { - if (node.hidden) return nullptr; - // note: tab bar clicks ignore animations - if (node.position.x > pos.x || node.position.y > pos.y || node.position.x + node.size.x < pos.x - || node.position.y + node.size.y < pos.y) - return nullptr; - - if (node.data.as_group.layout == Hy3GroupLayout::Tabbed - && node.data.as_group.tab_bar != nullptr) - { - if (pos.y < node.position.y + inset) { - auto& children = node.data.as_group.children; - auto& tab_bar = *node.data.as_group.tab_bar; - - auto size = tab_bar.size.vec(); - auto x = pos.x - tab_bar.pos.vec().x; - auto child_iter = children.begin(); - - for (auto& tab: tab_bar.bar.entries) { - if (child_iter == children.end()) break; - - if (x > tab.offset.fl() * size.x && x < (tab.offset.fl() + tab.width.fl()) * size.x) { - *focused_node = *child_iter; - return &node; - } - - child_iter = std::next(child_iter); - } - } - - if (node.data.as_group.focused_child != nullptr) { - return findTabBarAt(*node.data.as_group.focused_child, pos, focused_node); - } - } else { - for (auto child: node.data.as_group.children) { - if (findTabBarAt(*child, pos, focused_node)) return child; - } - } - } - - return nullptr; -} - -void Hy3Layout::focusTab( - int workspace, - TabFocus target, - TabFocusMousePriority mouse, - bool wrap_scroll, - int index -) { - auto* node = this->getWorkspaceRootGroup(workspace); - if (node == nullptr) return; - - Hy3Node* tab_node = nullptr; - Hy3Node* tab_focused_node; - - if (target == TabFocus::MouseLocation || mouse != TabFocusMousePriority::Ignore) { - if (g_pCompositor->windowFloatingFromCursor() == nullptr) { - auto mouse_pos = g_pInputManager->getMouseCoordsInternal(); - tab_node = findTabBarAt(*node, mouse_pos, &tab_focused_node); - if (tab_node != nullptr) goto hastab; - } - - if (target == TabFocus::MouseLocation || mouse == TabFocusMousePriority::Require) return; - } - - if (tab_node == nullptr) { - tab_node = this->getWorkspaceFocusedNode(workspace); - if (tab_node == nullptr) return; - - while (tab_node != nullptr && tab_node->data.as_group.layout != Hy3GroupLayout::Tabbed - && tab_node->parent != nullptr) - tab_node = tab_node->parent; - - if (tab_node == nullptr || tab_node->data.type != Hy3NodeType::Group - || tab_node->data.as_group.layout != Hy3GroupLayout::Tabbed) - return; - } - -hastab: - if (target != TabFocus::MouseLocation) { - if (tab_node->data.as_group.focused_child == nullptr - || tab_node->data.as_group.children.size() < 2) - return; - - auto& children = tab_node->data.as_group.children; - if (target == TabFocus::Index) { - int i = 1; - - for (auto* node: children) { - if (i == index) { - tab_focused_node = node; - goto cont; - } - - i++; - } - - return; - cont:; - } else { - auto node_iter - = std::find(children.begin(), children.end(), tab_node->data.as_group.focused_child); - if (node_iter == children.end()) return; - if (target == TabFocus::Left) { - if (node_iter == children.begin()) { - if (wrap_scroll) node_iter = std::prev(children.end()); - else return; - } else node_iter = std::prev(node_iter); - - tab_focused_node = *node_iter; - } else { - if (node_iter == std::prev(children.end())) { - if (wrap_scroll) node_iter = children.begin(); - else return; - } else node_iter = std::next(node_iter); - - tab_focused_node = *node_iter; - } - } - } - - auto* focus = tab_focused_node; - while (focus->data.type == Hy3NodeType::Group && !focus->data.as_group.group_focused - && focus->data.as_group.focused_child != nullptr) - focus = focus->data.as_group.focused_child; - - focus->focus(); - tab_node->recalcSizePosRecursive(); -} - -void Hy3Layout::killFocusedNode(int workspace) { - if (g_pCompositor->m_pLastWindow != nullptr && g_pCompositor->m_pLastWindow->m_bIsFloating) { - g_pCompositor->closeWindow(g_pCompositor->m_pLastWindow); - } else { - auto* node = this->getWorkspaceFocusedNode(workspace); - if (node == nullptr) return; - - std::vector windows; - node->appendAllWindows(windows); - - for (auto* window: windows) { - window->setHidden(false); - g_pCompositor->closeWindow(window); - } - } -} - -bool Hy3Layout::shouldRenderSelected(CWindow* window) { - if (window == nullptr) return false; - auto* root = this->getWorkspaceRootGroup(window->m_iWorkspaceID); - if (root == nullptr || root->data.as_group.focused_child == nullptr) return false; - auto* focused = root->getFocusedNode(); - if (focused == nullptr - || (focused->data.type == Hy3NodeType::Window - && focused->data.as_window != g_pCompositor->m_pLastWindow)) - return false; - - switch (focused->data.type) { - case Hy3NodeType::Window: return focused->data.as_window == window; - case Hy3NodeType::Group: - auto* node = this->getNodeFromWindow(window); - if (node == nullptr) return false; - return focused->data.as_group.hasChild(node); - } -} - -void Hy3Layout::renderHook(void*, std::any data) { - static bool rendering_normally = false; - static std::vector rendered_groups; - - auto render_stage = std::any_cast(data); - - switch (render_stage) { - case RENDER_PRE_WINDOWS: - rendering_normally = true; - rendered_groups.clear(); - break; - case RENDER_POST_WINDOW: - if (!rendering_normally) break; - - for (auto& entry: g_Hy3Layout->tab_groups) { - if (!entry.hidden && entry.target_window == g_pHyprOpenGL->m_pCurrentWindow - && std::find(rendered_groups.begin(), rendered_groups.end(), &entry) - == rendered_groups.end()) - { - entry.renderTabBar(); - rendered_groups.push_back(&entry); - } - } - - break; - case RENDER_POST_WINDOWS: - rendering_normally = false; - - for (auto& entry: g_Hy3Layout->tab_groups) { - if (!entry.hidden - && entry.target_window->m_iMonitorID == g_pHyprOpenGL->m_RenderData.pMonitor->ID - && std::find(rendered_groups.begin(), rendered_groups.end(), &entry) - == rendered_groups.end()) - { - entry.renderTabBar(); - } - } - - break; - default: break; - } -} - -void Hy3Layout::windowGroupUrgentHook(void* p, std::any data) { - CWindow* window = std::any_cast(data); - if (window == nullptr) return; - window->m_bIsUrgent = true; - Hy3Layout::windowGroupUpdateRecursiveHook(p, data); -} - -void Hy3Layout::windowGroupUpdateRecursiveHook(void*, std::any data) { - CWindow* window = std::any_cast(data); - if (window == nullptr) return; - auto* node = g_Hy3Layout->getNodeFromWindow(window); - - // it is UB for `this` to be null - if (node == nullptr) return; - node->updateTabBarRecursive(); -} - -void Hy3Layout::tickHook(void*, std::any) { - auto& tab_groups = g_Hy3Layout->tab_groups; - auto entry = tab_groups.begin(); - while (entry != tab_groups.end()) { - entry->tick(); - if (entry->bar.destroy) tab_groups.erase(entry++); - else entry = std::next(entry); - } -} - -std::string Hy3Node::debugNode() { - std::stringstream buf; - std::string addr = "0x" + std::to_string((size_t) this); - switch (this->data.type) { - case Hy3NodeType::Window: - buf << "window("; - buf << std::hex << this; - buf << ") [hypr "; - buf << this->data.as_window; - buf << "] size ratio: "; - buf << this->size_ratio; - break; - case Hy3NodeType::Group: - buf << "group("; - buf << std::hex << this; - buf << ") ["; - - switch (this->data.as_group.layout) { - case Hy3GroupLayout::SplitH: buf << "splith"; break; - case Hy3GroupLayout::SplitV: buf << "splitv"; break; - case Hy3GroupLayout::Tabbed: buf << "tabs"; break; - } - - buf << "] size ratio: "; - buf << this->size_ratio; - for (auto* child: this->data.as_group.children) { - buf << "\n|-"; - if (child == nullptr) { - buf << "nullptr"; - } else { - // this is terrible - for (char c: child->debugNode()) { - buf << c; - if (c == '\n') buf << " "; - } - } - } - - break; - } - - return buf.str(); -} diff --git a/src/Hy3Layout.hpp b/src/Hy3Layout.hpp index bc4e6c6..d6d4186 100644 --- a/src/Hy3Layout.hpp +++ b/src/Hy3Layout.hpp @@ -1,25 +1,14 @@ #pragma once -#include +class Hy3Layout; + #include -struct Hy3Node; +#include + +#include "Hy3Node.hpp" #include "TabGroup.hpp" -class Hy3Layout; -struct Hy3Node; - -enum class Hy3GroupLayout { - SplitH, - SplitV, - Tabbed, -}; - -enum class Hy3NodeType { - Window, - Group, -}; - enum class ShiftDirection { Left, Up, @@ -49,93 +38,6 @@ enum class TabFocusMousePriority { Require, }; -struct Hy3GroupData { - Hy3GroupLayout layout = Hy3GroupLayout::SplitH; - std::list children; - bool group_focused = true; - Hy3Node* focused_child = nullptr; - Hy3TabGroup* tab_bar = nullptr; - - bool hasChild(Hy3Node* child); - - Hy3GroupData(Hy3GroupLayout layout); - ~Hy3GroupData(); - -private: - Hy3GroupData(Hy3GroupData&&); - Hy3GroupData(const Hy3GroupData&) = delete; - - friend class Hy3NodeData; -}; - -class Hy3NodeData { -public: - Hy3NodeType type; - union { - Hy3GroupData as_group; - CWindow* as_window; - }; - - bool operator==(const Hy3NodeData&) const; - - Hy3NodeData(); - ~Hy3NodeData(); - Hy3NodeData(CWindow* window); - Hy3NodeData(Hy3GroupLayout layout); - Hy3NodeData& operator=(CWindow*); - Hy3NodeData& operator=(Hy3GroupLayout); - - // private: - I give up, C++ wins - Hy3NodeData(Hy3GroupData); - Hy3NodeData(Hy3NodeData&&); - Hy3NodeData& operator=(Hy3NodeData&&); -}; - -struct Hy3Node { - Hy3Node* parent = nullptr; - Hy3NodeData data; - Vector2D position; - Vector2D size; - Vector2D gap_pos_offset; - Vector2D gap_size_offset; - float size_ratio = 1.0; - int workspace_id = -1; - bool hidden = false; - bool valid = true; - Hy3Layout* layout = nullptr; - - void recalcSizePosRecursive(bool no_animation = false); - std::string debugNode(); - void markFocused(); - void focus(); - bool focusWindow(); - void raiseToTop(); - Hy3Node* getFocusedNode(); - void updateDecos(); - void setHidden(bool); - void updateTabBar(bool no_animation = false); - void updateTabBarRecursive(); - bool isUrgent(); - bool isIndirectlyFocused(); - Hy3Node* findNodeForTabGroup(Hy3TabGroup&); - std::string getTitle(); - void appendAllWindows(std::vector&); - - bool operator==(const Hy3Node&) const; - - // Attempt to swallow a group. returns true if swallowed - static bool swallowGroups(Hy3Node* into); - // Remove this node from its parent, deleting the parent if it was - // the only child and recursing if the parent was the only child of it's - // parent. - Hy3Node* removeFromParentRecursive(); - - // Replace this node with a group, returning this node's new address. - Hy3Node* intoGroup(Hy3GroupLayout); - - static void swapData(Hy3Node&, Hy3Node&); -}; - class Hy3Layout: public IHyprLayout { public: virtual void onWindowCreatedTiling(CWindow*); @@ -188,7 +90,6 @@ private: bool yExtent = false; } drag_flags; - int getWorkspaceNodeCount(const int& workspace); Hy3Node* getNodeFromWindow(CWindow*); void applyNodeDataToWindow(Hy3Node*, bool no_animation = false); diff --git a/src/Hy3Node.cpp b/src/Hy3Node.cpp new file mode 100644 index 0000000..7264cdf --- /dev/null +++ b/src/Hy3Node.cpp @@ -0,0 +1,680 @@ +#include +#include + +#include "globals.hpp" +#include "Hy3Node.hpp" + +// Hy3GroupData // + +Hy3GroupData::Hy3GroupData(Hy3GroupLayout layout): layout(layout) {} + +Hy3GroupData::Hy3GroupData(Hy3GroupData&& from) { + this->layout = from.layout; + this->children = std::move(from.children); + this->group_focused = from.group_focused; + this->focused_child = from.focused_child; + from.focused_child = nullptr; + this->tab_bar = from.tab_bar; + from.tab_bar = nullptr; +} + +Hy3GroupData::~Hy3GroupData() { + if (this->tab_bar != nullptr) this->tab_bar->bar.beginDestroy(); +} + +bool Hy3GroupData::hasChild(Hy3Node* node) { + Debug::log(LOG, "Searching for child %p of %p", this, node); + for (auto child: this->children) { + if (child == node) return true; + + if (child->data.type == Hy3NodeType::Group) { + if (child->data.as_group.hasChild(node)) return true; + } + } + + return false; +} + +// Hy3NodeData // + +Hy3NodeData::Hy3NodeData(): Hy3NodeData((CWindow*) nullptr) {} + +Hy3NodeData::Hy3NodeData(CWindow* window): type(Hy3NodeType::Window) { this->as_window = window; } + +Hy3NodeData::Hy3NodeData(Hy3GroupLayout layout): Hy3NodeData(Hy3GroupData(layout)) {} + +Hy3NodeData::Hy3NodeData(Hy3GroupData group): type(Hy3NodeType::Group) { + new (&this->as_group) Hy3GroupData(std::move(group)); +} + +Hy3NodeData::Hy3NodeData(Hy3NodeData&& from): type(from.type) { + Debug::log( + LOG, + "Move CTor type matches? %d is group? %d", + this->type == from.type, + this->type == Hy3NodeType::Group + ); + + switch (from.type) { + case Hy3NodeType::Window: this->as_window = from.as_window; break; + case Hy3NodeType::Group: new (&this->as_group) Hy3GroupData(std::move(from.as_group)); break; + } +} + +Hy3NodeData::~Hy3NodeData() { + switch (this->type) { + case Hy3NodeType::Window: break; + case Hy3NodeType::Group: + this->as_group.~Hy3GroupData(); + + // who ever thought calling the dtor after a move was a good idea? + this->type = Hy3NodeType::Window; + break; + } +} + +Hy3NodeData& Hy3NodeData::operator=(CWindow* window) { + *this = Hy3NodeData(window); + + return *this; +} + +Hy3NodeData& Hy3NodeData::operator=(Hy3GroupLayout layout) { + *this = Hy3NodeData(layout); + + return *this; +} + +Hy3NodeData& Hy3NodeData::operator=(Hy3NodeData&& from) { + Debug::log( + LOG, + "operator= type matches? %d is group? %d", + this->type == from.type, + this->type == Hy3NodeType::Group + ); + + if (this->type == Hy3NodeType::Group) { + this->as_group.~Hy3GroupData(); + } + + this->type = from.type; + + switch (this->type) { + case Hy3NodeType::Window: this->as_window = from.as_window; break; + case Hy3NodeType::Group: new (&this->as_group) Hy3GroupData(std::move(from.as_group)); break; + } + + return *this; +} + +bool Hy3NodeData::operator==(const Hy3NodeData& rhs) const { return this == &rhs; } + +// Hy3Node // + +bool Hy3Node::operator==(const Hy3Node& rhs) const { return this->data == rhs.data; } + +void Hy3Node::focus() { + this->markFocused(); + + switch (this->data.type) { + case Hy3NodeType::Window: + this->data.as_window->setHidden(false); + g_pCompositor->focusWindow(this->data.as_window); + break; + case Hy3NodeType::Group: + g_pCompositor->focusWindow(nullptr); + this->raiseToTop(); + break; + } +} + +bool Hy3Node::focusWindow() { + switch (this->data.type) { + case Hy3NodeType::Window: + this->markFocused(); + g_pCompositor->focusWindow(this->data.as_window); + + return true; + case Hy3NodeType::Group: + if (this->data.as_group.layout == Hy3GroupLayout::Tabbed) { + if (this->data.as_group.focused_child != nullptr) { + return this->data.as_group.focused_child->focusWindow(); + } + } else { + for (auto* node: this->data.as_group.children) { + if (node->focusWindow()) break; + } + } + + return false; + } +} + +void markGroupFocusedRecursive(Hy3GroupData& group) { + group.group_focused = true; + for (auto& child: group.children) { + if (child->data.type == Hy3NodeType::Group) markGroupFocusedRecursive(child->data.as_group); + } +} + +void Hy3Node::markFocused() { + Hy3Node* node = this; + + // undo decos for root focus + auto* root = node; + while (root->parent != nullptr) root = root->parent; + + // update focus + if (this->data.type == Hy3NodeType::Group) { + markGroupFocusedRecursive(this->data.as_group); + } + + auto* node2 = node; + while (node2->parent != nullptr) { + node2->parent->data.as_group.focused_child = node2; + node2->parent->data.as_group.group_focused = false; + node2 = node2->parent; + } + + root->updateDecos(); +} + +void Hy3Node::raiseToTop() { + switch (this->data.type) { + case Hy3NodeType::Window: g_pCompositor->moveWindowToTop(this->data.as_window); break; + case Hy3NodeType::Group: + for (auto* child: this->data.as_group.children) { + child->raiseToTop(); + } + break; + } +} + +Hy3Node* Hy3Node::getFocusedNode() { + switch (this->data.type) { + case Hy3NodeType::Window: return this; + case Hy3NodeType::Group: + if (this->data.as_group.focused_child == nullptr || this->data.as_group.group_focused) { + return this; + } else { + return this->data.as_group.focused_child->getFocusedNode(); + } + } +} + +bool Hy3Node::isIndirectlyFocused() { + Hy3Node* node = this; + + while (node->parent != nullptr) { + if (!node->parent->data.as_group.group_focused + && node->parent->data.as_group.focused_child != node) + return false; + node = node->parent; + } + + return true; +} + +void Hy3Node::recalcSizePosRecursive(bool no_animation) { + // clang-format off + static const auto* gaps_in = &HyprlandAPI::getConfigValue(PHANDLE, "general:gaps_in")->intValue; + static const auto* gaps_out = &HyprlandAPI::getConfigValue(PHANDLE, "general:gaps_out")->intValue; + static const auto* group_inset = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:group_inset")->intValue; + static const auto* tab_bar_height = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:height")->intValue; + static const auto* tab_bar_padding = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:tabs:padding")->intValue; + // clang-format on + + int outer_gaps = 0; + Vector2D gap_pos_offset; + Vector2D gap_size_offset; + if (this->parent == nullptr) { + outer_gaps = *gaps_out - *gaps_in; + + gap_pos_offset = Vector2D(outer_gaps, outer_gaps); + gap_size_offset = Vector2D(outer_gaps * 2, outer_gaps * 2); + } else { + gap_pos_offset = this->gap_pos_offset; + gap_size_offset = this->gap_size_offset; + } + + auto tpos = this->position; + auto tsize = this->size; + + double tab_height_offset = *tab_bar_height + *tab_bar_padding; + + if (this->data.type != Hy3NodeType::Group) { + this->data.as_window->setHidden(this->hidden); + this->layout->applyNodeDataToWindow(this, no_animation); + return; + } + + auto* group = &this->data.as_group; + + if (group->children.size() == 1 && this->parent != nullptr) { + auto child = group->children.front(); + + if (child == this) { + Debug::log(ERR, "a group (%p) has become its own child", this); + errorNotif(); + } + + switch (group->layout) { + case Hy3GroupLayout::SplitH: + child->position.x = tpos.x; + child->size.x = tsize.x - *group_inset; + child->position.y = tpos.y; + child->size.y = tsize.y; + break; + case Hy3GroupLayout::SplitV: + child->position.y = tpos.y; + child->size.y = tsize.y - *group_inset; + child->position.x = tpos.x; + child->size.x = tsize.x; + break; + case Hy3GroupLayout::Tabbed: + child->position.y = tpos.y + tab_height_offset; + child->size.y = tsize.y - tab_height_offset; + child->position.x = tpos.x; + child->size.x = tsize.x; + break; + } + + child->gap_pos_offset = gap_pos_offset; + child->gap_size_offset = gap_size_offset; + + child->setHidden(this->hidden); + + child->recalcSizePosRecursive(no_animation); + this->updateTabBar(no_animation); + return; + } + + int constraint; + switch (group->layout) { + case Hy3GroupLayout::SplitH: constraint = tsize.x; break; + case Hy3GroupLayout::SplitV: constraint = tsize.y; break; + case Hy3GroupLayout::Tabbed: break; + } + + double ratio_mul = group->layout != Hy3GroupLayout::Tabbed + ? group->children.empty() ? 0 : constraint / group->children.size() + : 0; + + double offset = 0; + + if (group->layout == Hy3GroupLayout::Tabbed && group->focused_child != nullptr + && !group->focused_child->hidden) + { + group->focused_child->setHidden(false); + + auto box = wlr_box {tpos.x, tpos.y, tsize.x, tsize.y}; + g_pHyprRenderer->damageBox(&box); + } + + for (auto* child: group->children) { + switch (group->layout) { + case Hy3GroupLayout::SplitH: + child->position.x = tpos.x + offset; + child->size.x = child->size_ratio * ratio_mul; + offset += child->size.x; + child->position.y = tpos.y; + child->size.y = tsize.y; + child->setHidden(this->hidden); + child->recalcSizePosRecursive(no_animation); + break; + case Hy3GroupLayout::SplitV: + child->position.y = tpos.y + offset; + child->size.y = child->size_ratio * ratio_mul; + offset += child->size.y; + child->position.x = tpos.x; + child->size.x = tsize.x; + child->setHidden(this->hidden); + child->recalcSizePosRecursive(no_animation); + break; + case Hy3GroupLayout::Tabbed: + child->position.y = tpos.y + tab_height_offset; + child->size.y = tsize.y - tab_height_offset; + child->position.x = tpos.x; + child->size.x = tsize.x; + bool hidden = this->hidden || group->focused_child != child; + child->setHidden(hidden); + child->recalcSizePosRecursive(no_animation); + break; + } + + child->gap_pos_offset = gap_pos_offset; + child->gap_size_offset = gap_pos_offset; + } + + this->updateTabBar(); +} + +struct FindTopWindowInNodeResult { + CWindow* window = nullptr; + size_t index = 0; +}; + +void findTopWindowInNode(Hy3Node& node, FindTopWindowInNodeResult& result) { + switch (node.data.type) { + case Hy3NodeType::Window: { + auto* window = node.data.as_window; + auto& windows = g_pCompositor->m_vWindows; + + for (; result.index < windows.size(); result.index++) { + if (&*windows[result.index] == window) { + result.window = window; + break; + } + } + + } break; + case Hy3NodeType::Group: { + auto& group = node.data.as_group; + + if (group.layout == Hy3GroupLayout::Tabbed) { + if (group.focused_child != nullptr) findTopWindowInNode(*group.focused_child, result); + } else { + for (auto* child: group.children) { + findTopWindowInNode(*child, result); + } + } + } break; + } +} + +void Hy3Node::updateTabBar(bool no_animation) { + if (this->data.type == Hy3NodeType::Group) { + auto& group = this->data.as_group; + + if (group.layout == Hy3GroupLayout::Tabbed) { + if (group.tab_bar == nullptr) group.tab_bar = &this->layout->tab_groups.emplace_back(*this); + group.tab_bar->updateWithGroup(*this, no_animation); + + FindTopWindowInNodeResult result; + findTopWindowInNode(*this, result); + group.tab_bar->target_window = result.window; + if (result.window != nullptr) group.tab_bar->workspace_id = result.window->m_iWorkspaceID; + } else if (group.tab_bar != nullptr) { + group.tab_bar->bar.beginDestroy(); + group.tab_bar = nullptr; + } + } +} + +void Hy3Node::updateTabBarRecursive() { + auto* node = this; + + do { + node->updateTabBar(); + node = node->parent; + } while (node != nullptr); +} + +void Hy3Node::updateDecos() { + switch (this->data.type) { + case Hy3NodeType::Window: + if (this->data.as_window->m_bIsMapped) + g_pCompositor->updateWindowAnimatedDecorationValues(this->data.as_window); + break; + case Hy3NodeType::Group: + for (auto* child: this->data.as_group.children) { + child->updateDecos(); + } + + this->updateTabBar(); + } +} + +std::string Hy3Node::getTitle() { + switch (this->data.type) { + case Hy3NodeType::Window: return this->data.as_window->m_szTitle; + case Hy3NodeType::Group: + std::string title; + + switch (this->data.as_group.layout) { + case Hy3GroupLayout::SplitH: title = "[H] "; break; + case Hy3GroupLayout::SplitV: title = "[V] "; break; + case Hy3GroupLayout::Tabbed: title = "[T] "; break; + } + + if (this->data.as_group.focused_child == nullptr) { + title += "Group"; + } else { + title += this->data.as_group.focused_child->getTitle(); + } + + return title; + } + + return ""; +} + +bool Hy3Node::isUrgent() { + switch (this->data.type) { + case Hy3NodeType::Window: return this->data.as_window->m_bIsUrgent; + case Hy3NodeType::Group: + for (auto* child: this->data.as_group.children) { + if (child->isUrgent()) return true; + } + + return false; + } +} + +void Hy3Node::setHidden(bool hidden) { + this->hidden = hidden; + + if (this->data.type == Hy3NodeType::Group) { + for (auto* child: this->data.as_group.children) { + child->setHidden(hidden); + } + } +} + +Hy3Node* Hy3Node::findNodeForTabGroup(Hy3TabGroup& tab_group) { + if (this->data.type == Hy3NodeType::Group) { + if (this->hidden) return nullptr; + + auto& group = this->data.as_group; + + if (group.layout == Hy3GroupLayout::Tabbed && group.tab_bar == &tab_group) { + return this; + } + + for (auto& node: group.children) { + auto* r = node->findNodeForTabGroup(tab_group); + if (r != nullptr) return r; + } + } else return nullptr; + + return nullptr; +} + +void Hy3Node::appendAllWindows(std::vector& list) { + switch (this->data.type) { + case Hy3NodeType::Window: list.push_back(this->data.as_window); break; + case Hy3NodeType::Group: + for (auto* child: this->data.as_group.children) { + child->appendAllWindows(list); + } + break; + } +} + +std::string Hy3Node::debugNode() { + std::stringstream buf; + std::string addr = "0x" + std::to_string((size_t) this); + switch (this->data.type) { + case Hy3NodeType::Window: + buf << "window("; + buf << std::hex << this; + buf << ") [hypr "; + buf << this->data.as_window; + buf << "] size ratio: "; + buf << this->size_ratio; + break; + case Hy3NodeType::Group: + buf << "group("; + buf << std::hex << this; + buf << ") ["; + + switch (this->data.as_group.layout) { + case Hy3GroupLayout::SplitH: buf << "splith"; break; + case Hy3GroupLayout::SplitV: buf << "splitv"; break; + case Hy3GroupLayout::Tabbed: buf << "tabs"; break; + } + + buf << "] size ratio: "; + buf << this->size_ratio; + for (auto* child: this->data.as_group.children) { + buf << "\n|-"; + if (child == nullptr) { + buf << "nullptr"; + } else { + // this is terrible + for (char c: child->debugNode()) { + buf << c; + if (c == '\n') buf << " "; + } + } + } + + break; + } + + return buf.str(); +} + +Hy3Node* Hy3Node::removeFromParentRecursive() { + Hy3Node* parent = this; + + Debug::log(LOG, "Recursively removing parent nodes of %p", parent); + + while (parent != nullptr) { + if (parent->parent == nullptr) { + Debug::log(ERR, "* UAF DEBUGGING - %p's parent is null, its the root group", parent); + + if (parent == this) { + Debug::log(ERR, "* UAF DEBUGGING - returning nullptr as this == root group"); + } else { + Debug::log(ERR, "* UAF DEBUGGING - deallocing %p and returning nullptr", parent); + parent->layout->nodes.remove(*parent); + } + return nullptr; + } + + auto* child = parent; + parent = parent->parent; + auto& group = parent->data.as_group; + + if (group.children.size() > 2) { + auto iter = std::find(group.children.begin(), group.children.end(), child); + + group.group_focused = false; + if (iter == group.children.begin()) { + group.focused_child = *std::next(iter); + } else { + group.focused_child = *std::prev(iter); + } + } + + if (!group.children.remove(child)) { + Debug::log( + ERR, + "Was unable to remove child node %p from parent %p. Child likely has " + "a false parent pointer.", + child, + parent + ); + + errorNotif(); + return nullptr; + } + + group.group_focused = false; + if (group.children.size() == 1) { + group.focused_child = group.children.front(); + } + + auto child_size_ratio = child->size_ratio; + if (child != this) { + parent->layout->nodes.remove(*child); + } else { + child->parent = nullptr; + } + + if (!group.children.empty()) { + auto child_count = group.children.size(); + if (std::find(group.children.begin(), group.children.end(), this) != group.children.end()) { + child_count -= 1; + } + + auto splitmod = -((1.0 - child_size_ratio) / child_count); + + for (auto* child: group.children) { + child->size_ratio += splitmod; + } + + break; + } + } + + return parent; +} + +Hy3Node* Hy3Node::intoGroup(Hy3GroupLayout layout) { + this->layout->nodes.push_back({ + .parent = this, + .data = layout, + .workspace_id = this->workspace_id, + .layout = this->layout, + }); + + auto* node = &this->layout->nodes.back(); + swapData(*this, *node); + + this->data = layout; + this->data.as_group.children.push_back(node); + this->data.as_group.group_focused = false; + this->data.as_group.focused_child = node; + this->recalcSizePosRecursive(); + this->updateTabBarRecursive(); + + return node; +} + +bool Hy3Node::swallowGroups(Hy3Node* into) { + if (into == nullptr || into->data.type != Hy3NodeType::Group + || into->data.as_group.children.size() != 1) + return false; + + auto* child = into->data.as_group.children.front(); + + // a lot of segfaulting happens once the assumption that the root node is a + // group is wrong. + if (into->parent == nullptr && child->data.type != Hy3NodeType::Group) return false; + + Debug::log(LOG, "Swallowing %p into %p", child, into); + Hy3Node::swapData(*into, *child); + into->layout->nodes.remove(*child); + + return true; +} + +void Hy3Node::swapData(Hy3Node& a, Hy3Node& b) { + Hy3NodeData aData = std::move(a.data); + a.data = std::move(b.data); + b.data = std::move(aData); + + if (a.data.type == Hy3NodeType::Group) { + for (auto child: a.data.as_group.children) { + child->parent = &a; + } + } + + if (b.data.type == Hy3NodeType::Group) { + for (auto child: b.data.as_group.children) { + child->parent = &b; + } + } +} diff --git a/src/Hy3Node.hpp b/src/Hy3Node.hpp new file mode 100644 index 0000000..449486a --- /dev/null +++ b/src/Hy3Node.hpp @@ -0,0 +1,112 @@ +#pragma once + +struct Hy3Node; +enum class Hy3GroupLayout; + +#include + +#include + +#include "Hy3Layout.hpp" +#include "TabGroup.hpp" + +enum class Hy3GroupLayout { + SplitH, + SplitV, + Tabbed, +}; + +enum class Hy3NodeType { + Window, + Group, +}; + +struct Hy3GroupData { + Hy3GroupLayout layout = Hy3GroupLayout::SplitH; + std::list children; + bool group_focused = true; + Hy3Node* focused_child = nullptr; + Hy3TabGroup* tab_bar = nullptr; + + Hy3GroupData(Hy3GroupLayout layout); + ~Hy3GroupData(); + + bool hasChild(Hy3Node* child); + +private: + Hy3GroupData(Hy3GroupData&&); + Hy3GroupData(const Hy3GroupData&) = delete; + + friend class Hy3NodeData; +}; + +class Hy3NodeData { +public: + Hy3NodeType type; + union { + Hy3GroupData as_group; + CWindow* as_window; + }; + + Hy3NodeData(); + Hy3NodeData(CWindow* window); + Hy3NodeData(Hy3GroupLayout layout); + ~Hy3NodeData(); + + Hy3NodeData& operator=(CWindow*); + Hy3NodeData& operator=(Hy3GroupLayout); + + bool operator==(const Hy3NodeData&) const; + + // private: - I give up, C++ wins + Hy3NodeData(Hy3GroupData); + Hy3NodeData(Hy3NodeData&&); + Hy3NodeData& operator=(Hy3NodeData&&); +}; + +struct Hy3Node { + Hy3Node* parent = nullptr; + Hy3NodeData data; + Vector2D position; + Vector2D size; + Vector2D gap_pos_offset; + Vector2D gap_size_offset; + float size_ratio = 1.0; + int workspace_id = -1; + bool hidden = false; + Hy3Layout* layout = nullptr; + + bool operator==(const Hy3Node&) const; + + void focus(); + bool focusWindow(); + void markFocused(); + void raiseToTop(); + Hy3Node* getFocusedNode(); + bool isIndirectlyFocused(); + + void recalcSizePosRecursive(bool no_animation = false); + void updateTabBar(bool no_animation = false); + void updateTabBarRecursive(); + void updateDecos(); + + std::string getTitle(); + bool isUrgent(); + void setHidden(bool); + + Hy3Node* findNodeForTabGroup(Hy3TabGroup&); + void appendAllWindows(std::vector&); + std::string debugNode(); + + // Remove this node from its parent, deleting the parent if it was + // the only child and recursing if the parent was the only child of it's + // parent. + Hy3Node* removeFromParentRecursive(); + + // Replace this node with a group, returning this node's new address. + Hy3Node* intoGroup(Hy3GroupLayout); + + // Attempt to swallow a group. returns true if swallowed + static bool swallowGroups(Hy3Node* into); + static void swapData(Hy3Node&, Hy3Node&); +}; diff --git a/src/SelectionHook.cpp b/src/SelectionHook.cpp index bbf63ae..a99f0b6 100644 --- a/src/SelectionHook.cpp +++ b/src/SelectionHook.cpp @@ -1,7 +1,8 @@ -#include "globals.hpp" #include #include +#include "globals.hpp" + namespace selection_hook { inline CFunctionHook* g_LastSelectionHook = nullptr; diff --git a/src/TabGroup.cpp b/src/TabGroup.cpp index e0942eb..89fd18c 100644 --- a/src/TabGroup.cpp +++ b/src/TabGroup.cpp @@ -1,7 +1,3 @@ -#include "TabGroup.hpp" -#include "globals.hpp" -#include "Hy3Layout.hpp" - #include #include #include @@ -10,6 +6,10 @@ #include #include +#include "globals.hpp" +#include "Hy3Layout.hpp" +#include "TabGroup.hpp" + Hy3TabBarEntry::Hy3TabBarEntry(Hy3TabBar& tab_bar, Hy3Node& node): tab_bar(tab_bar), node(node) { this->focused.create( AVARTYPE_FLOAT, diff --git a/src/TabGroup.hpp b/src/TabGroup.hpp index 3e016fe..0495276 100644 --- a/src/TabGroup.hpp +++ b/src/TabGroup.hpp @@ -1,14 +1,15 @@ #pragma once -#include -#include -#include -#include - class Hy3TabGroup; class Hy3TabBar; -#include "Hy3Layout.hpp" +#include +#include +#include + +#include + +#include "Hy3Node.hpp" struct Hy3TabBarEntry { std::string window_title; diff --git a/src/dispatchers.cpp b/src/dispatchers.cpp new file mode 100644 index 0000000..5937bd7 --- /dev/null +++ b/src/dispatchers.cpp @@ -0,0 +1,147 @@ +#include + +#include +#include + +#include "dispatchers.hpp" +#include "globals.hpp" + +int workspace_for_action() { + if (g_pLayoutManager->getCurrentLayout() != g_Hy3Layout.get()) return -1; + + int workspace_id = g_pCompositor->m_pLastMonitor->activeWorkspace; + + if (workspace_id == -1) return -1; + auto* workspace = g_pCompositor->getWorkspaceByID(workspace_id); + if (workspace == nullptr) return -1; + if (workspace->m_bHasFullscreenWindow) return -1; + + return workspace_id; +} + +void dispatch_makegroup(std::string arg) { + int workspace = workspace_for_action(); + if (workspace == -1) return; + + if (arg == "h") { + g_Hy3Layout->makeGroupOnWorkspace(workspace, Hy3GroupLayout::SplitH); + } else if (arg == "v") { + g_Hy3Layout->makeGroupOnWorkspace(workspace, Hy3GroupLayout::SplitV); + } else if (arg == "tab") { + g_Hy3Layout->makeGroupOnWorkspace(workspace, Hy3GroupLayout::Tabbed); + } else if (arg == "opposite") { + g_Hy3Layout->makeOppositeGroupOnWorkspace(workspace); + } +} + +std::optional parseShiftArg(std::string arg) { + if (arg == "l" || arg == "left") return ShiftDirection::Left; + else if (arg == "r" || arg == "right") return ShiftDirection::Right; + else if (arg == "u" || arg == "up") return ShiftDirection::Up; + else if (arg == "d" || arg == "down") return ShiftDirection::Down; + else return {}; +} + +void dispatch_movewindow(std::string value) { + int workspace = workspace_for_action(); + if (workspace == -1) return; + + auto args = CVarList(value); + + if (auto shift = parseShiftArg(args[0])) { + auto once = args[1] == "once"; + g_Hy3Layout->shiftWindow(workspace, shift.value(), once); + } +} + +void dispatch_movefocus(std::string value) { + int workspace = workspace_for_action(); + if (workspace == -1) return; + + auto args = CVarList(value); + + if (auto shift = parseShiftArg(args[0])) { + g_Hy3Layout->shiftFocus(workspace, shift.value(), args[1] == "visible"); + } +} + +void dispatch_changefocus(std::string arg) { + int workspace = workspace_for_action(); + if (workspace == -1) return; + + if (arg == "top") g_Hy3Layout->changeFocus(workspace, FocusShift::Top); + else if (arg == "bottom") g_Hy3Layout->changeFocus(workspace, FocusShift::Bottom); + else if (arg == "raise") g_Hy3Layout->changeFocus(workspace, FocusShift::Raise); + else if (arg == "lower") g_Hy3Layout->changeFocus(workspace, FocusShift::Lower); + else if (arg == "tab") g_Hy3Layout->changeFocus(workspace, FocusShift::Tab); + else if (arg == "tabnode") g_Hy3Layout->changeFocus(workspace, FocusShift::TabNode); +} + +void dispatch_focustab(std::string value) { + int workspace = workspace_for_action(); + if (workspace == -1) return; + + auto i = 0; + auto args = CVarList(value); + + TabFocus focus; + auto mouse = TabFocusMousePriority::Ignore; + bool wrap_scroll = false; + int index = 0; + + if (args[i] == "l" || args[i] == "left") focus = TabFocus::Left; + else if (args[i] == "r" || args[i] == "right") focus = TabFocus::Right; + else if (args[i] == "index") { + i++; + focus = TabFocus::Index; + if (!isNumber(args[i])) return; + index = std::stoi(args[i]); + Debug::log(LOG, "Focus index '%s' -> %d, errno: %d", args[i].c_str(), index, errno); + } else if (args[i] == "mouse") { + g_Hy3Layout->focusTab(workspace, TabFocus::MouseLocation, mouse, false, 0); + return; + } else return; + + i++; + + if (args[i] == "prioritize_hovered") { + mouse = TabFocusMousePriority::Prioritize; + i++; + } else if (args[i] == "require_hovered") { + mouse = TabFocusMousePriority::Require; + i++; + } + + if (args[i++] == "wrap") wrap_scroll = true; + + g_Hy3Layout->focusTab(workspace, focus, mouse, wrap_scroll, index); +} + +void dispatch_killactive(std::string value) { + int workspace = workspace_for_action(); + if (workspace == -1) return; + + g_Hy3Layout->killFocusedNode(workspace); +} + +void dispatch_debug(std::string arg) { + int workspace = workspace_for_action(); + if (workspace == -1) return; + + auto* root = g_Hy3Layout->getWorkspaceRootGroup(workspace); + if (workspace == -1) { + Debug::log(LOG, "DEBUG NODES: no nodes on workspace"); + } else { + Debug::log(LOG, "DEBUG NODES\n%s", root->debugNode().c_str()); + } +} + +void registerDispatchers() { + HyprlandAPI::addDispatcher(PHANDLE, "hy3:makegroup", dispatch_makegroup); + HyprlandAPI::addDispatcher(PHANDLE, "hy3:movefocus", dispatch_movefocus); + HyprlandAPI::addDispatcher(PHANDLE, "hy3:movewindow", dispatch_movewindow); + HyprlandAPI::addDispatcher(PHANDLE, "hy3:changefocus", dispatch_changefocus); + HyprlandAPI::addDispatcher(PHANDLE, "hy3:focustab", dispatch_focustab); + HyprlandAPI::addDispatcher(PHANDLE, "hy3:killactive", dispatch_killactive); + HyprlandAPI::addDispatcher(PHANDLE, "hy3:debugnodes", dispatch_debug); +} diff --git a/src/dispatchers.hpp b/src/dispatchers.hpp new file mode 100644 index 0000000..be858c4 --- /dev/null +++ b/src/dispatchers.hpp @@ -0,0 +1,3 @@ +#pragma once + +void registerDispatchers(); diff --git a/src/globals.hpp b/src/globals.hpp index ed7522f..856f193 100644 --- a/src/globals.hpp +++ b/src/globals.hpp @@ -3,3 +3,15 @@ inline HANDLE PHANDLE = nullptr; inline std::unique_ptr g_Hy3Layout; + +inline void errorNotif() { + HyprlandAPI::addNotificationV2( + PHANDLE, + { + {"text", "Something has gone very wrong. Check the log for details."}, + {"time", (uint64_t) 10000}, + {"color", CColor(1.0, 0.0, 0.0, 1.0)}, + {"icon", ICON_ERROR}, + } + ); +} diff --git a/src/main.cpp b/src/main.cpp index 3b5b322..85e91e5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,155 +3,12 @@ #include #include +#include "dispatchers.hpp" #include "globals.hpp" #include "SelectionHook.hpp" APICALL EXPORT std::string PLUGIN_API_VERSION() { return HYPRLAND_API_VERSION; } -// return a window if a window action makes sense now -CWindow* window_for_action() { - if (g_pLayoutManager->getCurrentLayout() != g_Hy3Layout.get()) return nullptr; - - auto* window = g_pCompositor->m_pLastWindow; - - if (!window) return nullptr; - - const auto workspace = g_pCompositor->getWorkspaceByID(window->m_iWorkspaceID); - if (workspace->m_bHasFullscreenWindow) return nullptr; - - return window; -} - -int workspace_for_action() { - if (g_pLayoutManager->getCurrentLayout() != g_Hy3Layout.get()) return -1; - - int workspace_id = g_pCompositor->m_pLastMonitor->activeWorkspace; - - if (workspace_id == -1) return -1; - auto* workspace = g_pCompositor->getWorkspaceByID(workspace_id); - if (workspace == nullptr) return -1; - if (workspace->m_bHasFullscreenWindow) return -1; - - return workspace_id; -} - -void dispatch_makegroup(std::string arg) { - int workspace = workspace_for_action(); - if (workspace == -1) return; - - if (arg == "h") { - g_Hy3Layout->makeGroupOnWorkspace(workspace, Hy3GroupLayout::SplitH); - } else if (arg == "v") { - g_Hy3Layout->makeGroupOnWorkspace(workspace, Hy3GroupLayout::SplitV); - } else if (arg == "tab") { - g_Hy3Layout->makeGroupOnWorkspace(workspace, Hy3GroupLayout::Tabbed); - } else if (arg == "opposite") { - g_Hy3Layout->makeOppositeGroupOnWorkspace(workspace); - } -} - -std::optional parseShiftArg(std::string arg) { - if (arg == "l" || arg == "left") return ShiftDirection::Left; - else if (arg == "r" || arg == "right") return ShiftDirection::Right; - else if (arg == "u" || arg == "up") return ShiftDirection::Up; - else if (arg == "d" || arg == "down") return ShiftDirection::Down; - else return {}; -} - -void dispatch_movewindow(std::string value) { - int workspace = workspace_for_action(); - if (workspace == -1) return; - - auto args = CVarList(value); - - if (auto shift = parseShiftArg(args[0])) { - auto once = args[1] == "once"; - g_Hy3Layout->shiftWindow(workspace, shift.value(), once); - } -} - -void dispatch_movefocus(std::string value) { - int workspace = workspace_for_action(); - if (workspace == -1) return; - - auto args = CVarList(value); - - if (auto shift = parseShiftArg(args[0])) { - g_Hy3Layout->shiftFocus(workspace, shift.value(), args[1] == "visible"); - } -} - -void dispatch_changefocus(std::string arg) { - int workspace = workspace_for_action(); - if (workspace == -1) return; - - if (arg == "top") g_Hy3Layout->changeFocus(workspace, FocusShift::Top); - else if (arg == "bottom") g_Hy3Layout->changeFocus(workspace, FocusShift::Bottom); - else if (arg == "raise") g_Hy3Layout->changeFocus(workspace, FocusShift::Raise); - else if (arg == "lower") g_Hy3Layout->changeFocus(workspace, FocusShift::Lower); - else if (arg == "tab") g_Hy3Layout->changeFocus(workspace, FocusShift::Tab); - else if (arg == "tabnode") g_Hy3Layout->changeFocus(workspace, FocusShift::TabNode); -} - -void dispatch_focustab(std::string value) { - int workspace = workspace_for_action(); - if (workspace == -1) return; - - auto i = 0; - auto args = CVarList(value); - - TabFocus focus; - auto mouse = TabFocusMousePriority::Ignore; - bool wrap_scroll = false; - int index = 0; - - if (args[i] == "l" || args[i] == "left") focus = TabFocus::Left; - else if (args[i] == "r" || args[i] == "right") focus = TabFocus::Right; - else if (args[i] == "index") { - i++; - focus = TabFocus::Index; - if (!isNumber(args[i])) return; - index = std::stoi(args[i]); - Debug::log(LOG, "Focus index '%s' -> %d, errno: %d", args[i].c_str(), index, errno); - } else if (args[i] == "mouse") { - g_Hy3Layout->focusTab(workspace, TabFocus::MouseLocation, mouse, false, 0); - return; - } else return; - - i++; - - if (args[i] == "prioritize_hovered") { - mouse = TabFocusMousePriority::Prioritize; - i++; - } else if (args[i] == "require_hovered") { - mouse = TabFocusMousePriority::Require; - i++; - } - - if (args[i++] == "wrap") wrap_scroll = true; - - g_Hy3Layout->focusTab(workspace, focus, mouse, wrap_scroll, index); -} - -void dispatch_killactive(std::string value) { - int workspace = workspace_for_action(); - if (workspace == -1) return; - - g_Hy3Layout->killFocusedNode(workspace); -} - -void dispatch_debug(std::string arg) { - int workspace = workspace_for_action(); - if (workspace == -1) return; - - auto* root = g_Hy3Layout->getWorkspaceRootGroup(workspace); - if (workspace == -1) { - Debug::log(LOG, "DEBUG NODES: no nodes on workspace"); - } else { - Debug::log(LOG, "DEBUG NODES\n%s", root->debugNode().c_str()); - } -} - APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { PHANDLE = handle; @@ -213,13 +70,7 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { g_Hy3Layout = std::make_unique(); HyprlandAPI::addLayout(PHANDLE, "hy3", g_Hy3Layout.get()); - HyprlandAPI::addDispatcher(PHANDLE, "hy3:makegroup", dispatch_makegroup); - HyprlandAPI::addDispatcher(PHANDLE, "hy3:movefocus", dispatch_movefocus); - HyprlandAPI::addDispatcher(PHANDLE, "hy3:movewindow", dispatch_movewindow); - HyprlandAPI::addDispatcher(PHANDLE, "hy3:changefocus", dispatch_changefocus); - HyprlandAPI::addDispatcher(PHANDLE, "hy3:focustab", dispatch_focustab); - HyprlandAPI::addDispatcher(PHANDLE, "hy3:killactive", dispatch_killactive); - HyprlandAPI::addDispatcher(PHANDLE, "hy3:debugnodes", dispatch_debug); + registerDispatchers(); return {"hy3", "i3 like layout for hyprland", "outfoxxed", "0.1"}; }