This commit is contained in:
outfoxxed 2023-06-28 21:36:08 -07:00
parent ae2409d037
commit 1435be18d4
No known key found for this signature in database
GPG key ID: 4C88A185FB89301E
12 changed files with 1447 additions and 1428 deletions

View file

@ -15,7 +15,9 @@ pkg_check_modules(DEPS REQUIRED hyprland pixman-1 libdrm pango pangocairo)
add_library(hy3 SHARED add_library(hy3 SHARED
src/main.cpp src/main.cpp
src/dispatchers.cpp
src/Hy3Layout.cpp src/Hy3Layout.cpp
src/Hy3Node.cpp
src/TabGroup.cpp src/TabGroup.cpp
src/SelectionHook.cpp src/SelectionHook.cpp
) )

File diff suppressed because it is too large Load diff

View file

@ -1,25 +1,14 @@
#pragma once #pragma once
#include <hyprland/src/layout/IHyprLayout.hpp> class Hy3Layout;
#include <list> #include <list>
struct Hy3Node; #include <hyprland/src/layout/IHyprLayout.hpp>
#include "Hy3Node.hpp"
#include "TabGroup.hpp" #include "TabGroup.hpp"
class Hy3Layout;
struct Hy3Node;
enum class Hy3GroupLayout {
SplitH,
SplitV,
Tabbed,
};
enum class Hy3NodeType {
Window,
Group,
};
enum class ShiftDirection { enum class ShiftDirection {
Left, Left,
Up, Up,
@ -49,93 +38,6 @@ enum class TabFocusMousePriority {
Require, Require,
}; };
struct Hy3GroupData {
Hy3GroupLayout layout = Hy3GroupLayout::SplitH;
std::list<Hy3Node*> 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<CWindow*>&);
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 { class Hy3Layout: public IHyprLayout {
public: public:
virtual void onWindowCreatedTiling(CWindow*); virtual void onWindowCreatedTiling(CWindow*);
@ -188,7 +90,6 @@ private:
bool yExtent = false; bool yExtent = false;
} drag_flags; } drag_flags;
int getWorkspaceNodeCount(const int& workspace);
Hy3Node* getNodeFromWindow(CWindow*); Hy3Node* getNodeFromWindow(CWindow*);
void applyNodeDataToWindow(Hy3Node*, bool no_animation = false); void applyNodeDataToWindow(Hy3Node*, bool no_animation = false);

680
src/Hy3Node.cpp Normal file
View file

@ -0,0 +1,680 @@
#include <hyprland/src/Compositor.hpp>
#include <hyprland/src/plugins/PluginAPI.hpp>
#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<CWindow*>& 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;
}
}
}

112
src/Hy3Node.hpp Normal file
View file

@ -0,0 +1,112 @@
#pragma once
struct Hy3Node;
enum class Hy3GroupLayout;
#include <list>
#include <hyprland/src/Window.hpp>
#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<Hy3Node*> 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<CWindow*>&);
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&);
};

View file

@ -1,7 +1,8 @@
#include "globals.hpp"
#include <hyprland/src/Compositor.hpp> #include <hyprland/src/Compositor.hpp>
#include <hyprland/src/plugins/PluginAPI.hpp> #include <hyprland/src/plugins/PluginAPI.hpp>
#include "globals.hpp"
namespace selection_hook { namespace selection_hook {
inline CFunctionHook* g_LastSelectionHook = nullptr; inline CFunctionHook* g_LastSelectionHook = nullptr;

View file

@ -1,7 +1,3 @@
#include "TabGroup.hpp"
#include "globals.hpp"
#include "Hy3Layout.hpp"
#include <cairo/cairo.h> #include <cairo/cairo.h>
#include <hyprland/src/Compositor.hpp> #include <hyprland/src/Compositor.hpp>
#include <hyprland/src/helpers/Color.hpp> #include <hyprland/src/helpers/Color.hpp>
@ -10,6 +6,10 @@
#include <pango/pangocairo.h> #include <pango/pangocairo.h>
#include <pixman.h> #include <pixman.h>
#include "globals.hpp"
#include "Hy3Layout.hpp"
#include "TabGroup.hpp"
Hy3TabBarEntry::Hy3TabBarEntry(Hy3TabBar& tab_bar, Hy3Node& node): tab_bar(tab_bar), node(node) { Hy3TabBarEntry::Hy3TabBarEntry(Hy3TabBar& tab_bar, Hy3Node& node): tab_bar(tab_bar), node(node) {
this->focused.create( this->focused.create(
AVARTYPE_FLOAT, AVARTYPE_FLOAT,

View file

@ -1,14 +1,15 @@
#pragma once #pragma once
#include <hyprland/src/Compositor.hpp>
#include <list>
#include <memory>
#include <vector>
class Hy3TabGroup; class Hy3TabGroup;
class Hy3TabBar; class Hy3TabBar;
#include "Hy3Layout.hpp" #include <list>
#include <memory>
#include <vector>
#include <hyprland/src/render/Texture.hpp>
#include "Hy3Node.hpp"
struct Hy3TabBarEntry { struct Hy3TabBarEntry {
std::string window_title; std::string window_title;

147
src/dispatchers.cpp Normal file
View file

@ -0,0 +1,147 @@
#include <optional>
#include <hyprland/src/Compositor.hpp>
#include <hyprland/src/plugins/PluginAPI.hpp>
#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<ShiftDirection> 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);
}

3
src/dispatchers.hpp Normal file
View file

@ -0,0 +1,3 @@
#pragma once
void registerDispatchers();

View file

@ -3,3 +3,15 @@
inline HANDLE PHANDLE = nullptr; inline HANDLE PHANDLE = nullptr;
inline std::unique_ptr<Hy3Layout> g_Hy3Layout; inline std::unique_ptr<Hy3Layout> 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},
}
);
}

View file

@ -3,155 +3,12 @@
#include <hyprland/src/Compositor.hpp> #include <hyprland/src/Compositor.hpp>
#include <hyprland/src/plugins/PluginAPI.hpp> #include <hyprland/src/plugins/PluginAPI.hpp>
#include "dispatchers.hpp"
#include "globals.hpp" #include "globals.hpp"
#include "SelectionHook.hpp" #include "SelectionHook.hpp"
APICALL EXPORT std::string PLUGIN_API_VERSION() { return HYPRLAND_API_VERSION; } 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<ShiftDirection> 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) { APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
PHANDLE = handle; PHANDLE = handle;
@ -213,13 +70,7 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
g_Hy3Layout = std::make_unique<Hy3Layout>(); g_Hy3Layout = std::make_unique<Hy3Layout>();
HyprlandAPI::addLayout(PHANDLE, "hy3", g_Hy3Layout.get()); HyprlandAPI::addLayout(PHANDLE, "hy3", g_Hy3Layout.get());
HyprlandAPI::addDispatcher(PHANDLE, "hy3:makegroup", dispatch_makegroup); registerDispatchers();
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);
return {"hy3", "i3 like layout for hyprland", "outfoxxed", "0.1"}; return {"hy3", "i3 like layout for hyprland", "outfoxxed", "0.1"};
} }