mirror of
https://github.com/Trensa-Organization/hy3.git
synced 2025-03-15 18:53:40 +01:00
Raising focus and grouped movement
This commit is contained in:
parent
49ae28631f
commit
9b570f066a
8 changed files with 238 additions and 57 deletions
|
@ -20,6 +20,7 @@ pkg_check_modules(DEPS REQUIRED pixman-1 libdrm)
|
|||
add_library(hy3 SHARED
|
||||
src/main.cpp
|
||||
src/Hy3Layout.cpp
|
||||
src/SelectionHook.cpp
|
||||
)
|
||||
|
||||
target_include_directories(hy3 PRIVATE
|
||||
|
|
|
@ -6,7 +6,7 @@ i3 / sway like layout for [hyprland](https://github.com/hyprwm/hyprland).
|
|||
- [x] Window splits
|
||||
- [x] Window movement
|
||||
- [x] Window resizing
|
||||
- [ ] Selecting a group of windows at once (and related movement)
|
||||
- [x] Selecting a group of windows at once (and related movement)
|
||||
- [ ] Tabbed groups
|
||||
- [ ] Some convenience dispatchers not found in i3 or sway
|
||||
|
||||
|
@ -35,6 +35,7 @@ You can use `hy3:makegroup` to create a new split.
|
|||
- `hy3:makegroup, <h | v>` - make a vertical or horizontal split
|
||||
- `hy3:movefocus, <l | u | d | r>` - move the focus left, up, down, or right
|
||||
- `hy3:movewindow, <l | u | d | r>` - move a window left, up, down, or right
|
||||
- `hy3:raisefocus` - raise the active focus one level
|
||||
- `hy3:debugnodes` - print the node tree into the hyprland log
|
||||
|
||||
## Installing
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "globals.hpp"
|
||||
#include "Hy3Layout.hpp"
|
||||
#include "SelectionHook.hpp"
|
||||
|
||||
#include <src/Compositor.hpp>
|
||||
|
||||
|
@ -197,6 +198,69 @@ void Hy3Node::recalcSizePosRecursive(bool force) {
|
|||
}
|
||||
}
|
||||
|
||||
void Hy3Node::markFocused() {
|
||||
Hy3Node* node = this;
|
||||
|
||||
// undo decos for root focus
|
||||
auto* root = node;
|
||||
while (root->parent != nullptr) root = root->parent;
|
||||
auto* oldfocus = root->getFocusedNode();
|
||||
|
||||
// update focus
|
||||
if (this->data.type == Hy3NodeData::Group) {
|
||||
this->data.as_group.lastFocusedChild = nullptr;
|
||||
}
|
||||
|
||||
while (node->parent != nullptr) {
|
||||
node->parent->data.as_group.lastFocusedChild = node;
|
||||
node = node->parent;
|
||||
}
|
||||
|
||||
if (oldfocus != nullptr) {
|
||||
oldfocus->updateDecos();
|
||||
}
|
||||
}
|
||||
|
||||
void Hy3Node::focus() {
|
||||
this->markFocused();
|
||||
|
||||
switch (this->data.type) {
|
||||
case Hy3NodeData::Window:
|
||||
g_pCompositor->focusWindow(this->data.as_window);
|
||||
break;
|
||||
case Hy3NodeData::Group:
|
||||
g_pCompositor->focusWindow(nullptr);
|
||||
this->raiseToTop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Hy3Node::raiseToTop() {
|
||||
switch (this->data.type) {
|
||||
case Hy3NodeData::Window:
|
||||
g_pCompositor->moveWindowToTop(this->data.as_window);
|
||||
break;
|
||||
case Hy3NodeData::Group:
|
||||
for (auto* child: this->data.as_group.children) {
|
||||
child->raiseToTop();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Hy3Node* Hy3Node::getFocusedNode() {
|
||||
switch (this->data.type) {
|
||||
case Hy3NodeData::Window:
|
||||
return this;
|
||||
case Hy3NodeData::Group:
|
||||
if (this->data.as_group.lastFocusedChild == nullptr) {
|
||||
return this;
|
||||
} else {
|
||||
return this->data.as_group.lastFocusedChild->getFocusedNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Hy3Node::swallowGroups(Hy3Node* into) {
|
||||
if (into == nullptr
|
||||
|| into->data.type != Hy3NodeData::Group
|
||||
|
@ -291,6 +355,18 @@ void Hy3Node::swapData(Hy3Node& a, Hy3Node& b) {
|
|||
}
|
||||
}
|
||||
|
||||
void Hy3Node::updateDecos() {
|
||||
switch (this->data.type) {
|
||||
case Hy3NodeData::Window:
|
||||
g_pCompositor->updateWindowAnimatedDecorationValues(this->data.as_window);
|
||||
break;
|
||||
case Hy3NodeData::Group:
|
||||
for (auto* child: this->data.as_group.children) {
|
||||
child->updateDecos();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Hy3Layout::getWorkspaceNodeCount(const int& id) {
|
||||
int count = 0;
|
||||
|
||||
|
@ -496,7 +572,7 @@ void Hy3Layout::onWindowCreatedTiling(CWindow* window) {
|
|||
}
|
||||
Debug::log(LOG, "opened new window %p(node: %p) on window %p in %p", window, &node, opening_after, opening_into);
|
||||
|
||||
opening_into->data.as_group.lastFocusedChild = &node;
|
||||
node.markFocused();
|
||||
opening_into->recalcSizePosRecursive();
|
||||
Debug::log(LOG, "opening_into (%p) contains new child (%p)? %d", opening_into, &node, opening_into->data.as_group.hasChild(&node));
|
||||
}
|
||||
|
@ -542,25 +618,23 @@ void Hy3Layout::onWindowRemovedTiling(CWindow* window) {
|
|||
CWindow* Hy3Layout::getNextWindowCandidate(CWindow* window) {
|
||||
auto* node = this->getWorkspaceRootGroup(window->m_iWorkspaceID);
|
||||
if (node == nullptr) return nullptr;
|
||||
while (node->data.type == Hy3NodeData::Group) {
|
||||
node = node->data.as_group.lastFocusedChild;
|
||||
if (node == nullptr) {
|
||||
Debug::log(ERR, "A group's last focused child was null when getting the next selection candidate");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
node = node->getFocusedNode();
|
||||
|
||||
switch (node->data.type) {
|
||||
case Hy3NodeData::Window:
|
||||
return node->data.as_window;
|
||||
case Hy3NodeData::Group:
|
||||
return nullptr;
|
||||
}
|
||||
return node->data.as_window;
|
||||
}
|
||||
|
||||
void Hy3Layout::onWindowFocusChange(CWindow* window) {
|
||||
Debug::log(LOG, "Switched windows from to %p", window);
|
||||
Debug::log(LOG, "Switched windows to %p", window);
|
||||
auto* node = this->getNodeFromWindow(window);
|
||||
if (node == nullptr) return;
|
||||
|
||||
while (node->parent != nullptr) {
|
||||
node->parent->data.as_group.lastFocusedChild = node;
|
||||
node = node->parent;
|
||||
}
|
||||
node->markFocused();
|
||||
}
|
||||
|
||||
bool Hy3Layout::isWindowTiled(CWindow* window) {
|
||||
|
@ -921,14 +995,17 @@ void Hy3Layout::onEnable() {
|
|||
|
||||
this->onWindowCreatedTiling(window.get());
|
||||
}
|
||||
|
||||
setup_selection_hook();
|
||||
}
|
||||
|
||||
void Hy3Layout::onDisable() {
|
||||
disable_selection_hook();
|
||||
this->nodes.clear();
|
||||
}
|
||||
|
||||
void Hy3Layout::makeGroupOn(CWindow* window, Hy3GroupLayout layout) {
|
||||
auto* node = this->getNodeFromWindow(window);
|
||||
void Hy3Layout::makeGroupOn(int workspace, Hy3GroupLayout layout) {
|
||||
auto* node = this->getWorkspaceRootGroup(workspace)->getFocusedNode();
|
||||
if (node == nullptr) return;
|
||||
|
||||
if (node->parent->data.as_group.children.size() == 1
|
||||
|
@ -942,7 +1019,7 @@ void Hy3Layout::makeGroupOn(CWindow* window, Hy3GroupLayout layout) {
|
|||
|
||||
this->nodes.push_back({
|
||||
.parent = node,
|
||||
.data = node->data.as_window,
|
||||
.data = node->data,
|
||||
.workspace_id = node->workspace_id,
|
||||
.layout = this,
|
||||
});
|
||||
|
@ -957,9 +1034,11 @@ void Hy3Layout::makeGroupOn(CWindow* window, Hy3GroupLayout layout) {
|
|||
|
||||
Hy3Node* shiftOrGetFocus(Hy3Node& node, ShiftDirection direction, bool shift);
|
||||
|
||||
void Hy3Layout::shiftFocus(CWindow* window, ShiftDirection direction) {
|
||||
Debug::log(LOG, "ShiftFocus %p %d", window, direction);
|
||||
auto* node = this->getNodeFromWindow(window);
|
||||
void Hy3Layout::shiftFocus(int workspace, ShiftDirection direction) {
|
||||
auto* root = this->getWorkspaceRootGroup(workspace);
|
||||
if (root == nullptr) return;
|
||||
auto* node = root->getFocusedNode();
|
||||
Debug::log(LOG, "ShiftFocus %p %d", node, direction);
|
||||
if (node == nullptr) return;
|
||||
|
||||
Hy3Node* target;
|
||||
|
@ -968,9 +1047,11 @@ void Hy3Layout::shiftFocus(CWindow* window, ShiftDirection direction) {
|
|||
}
|
||||
}
|
||||
|
||||
void Hy3Layout::shiftWindow(CWindow* window, ShiftDirection direction) {
|
||||
Debug::log(LOG, "ShiftWindow %p %d", window, direction);
|
||||
auto* node = this->getNodeFromWindow(window);
|
||||
void Hy3Layout::shiftWindow(int workspace, ShiftDirection direction) {
|
||||
auto* root = this->getWorkspaceRootGroup(workspace);
|
||||
if (root == nullptr) return;
|
||||
auto* node = root->getFocusedNode();
|
||||
Debug::log(LOG, "ShiftWindow %p %d", node, direction);
|
||||
if (node == nullptr) return;
|
||||
|
||||
|
||||
|
@ -1131,12 +1212,7 @@ Hy3Node* Hy3Layout::shiftOrGetFocus(Hy3Node& node, ShiftDirection direction, boo
|
|||
target_parent = target_parent->parent;
|
||||
}
|
||||
|
||||
auto* focusnode = &node;
|
||||
|
||||
while (focusnode->parent != nullptr) {
|
||||
focusnode->parent->data.as_group.lastFocusedChild = focusnode;
|
||||
focusnode = focusnode->parent;
|
||||
}
|
||||
node.markFocused();
|
||||
|
||||
if (target_parent != target_group && target_parent != nullptr)
|
||||
target_parent->recalcSizePosRecursive();
|
||||
|
@ -1145,6 +1221,34 @@ Hy3Node* Hy3Layout::shiftOrGetFocus(Hy3Node& node, ShiftDirection direction, boo
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
void Hy3Layout::raiseFocus(int workspace) {
|
||||
auto* root = this->getWorkspaceRootGroup(workspace);
|
||||
if (root == nullptr) return;
|
||||
auto* node = root->getFocusedNode();
|
||||
|
||||
if (node->parent != nullptr && node->parent->parent != nullptr) {
|
||||
node->parent->focus();
|
||||
node->parent->updateDecos();
|
||||
}
|
||||
}
|
||||
|
||||
bool Hy3Layout::shouldRenderSelected(CWindow* window) {
|
||||
if (window == nullptr) return false;
|
||||
auto* root = this->getWorkspaceRootGroup(window->m_iWorkspaceID);
|
||||
if (root == nullptr || root->data.as_group.lastFocusedChild == nullptr) return false;
|
||||
auto* focused = root->getFocusedNode();
|
||||
if (focused == nullptr) return false;
|
||||
|
||||
switch (focused->data.type) {
|
||||
case Hy3NodeData::Window:
|
||||
return false;
|
||||
case Hy3NodeData::Group:
|
||||
auto* node = this->getNodeFromWindow(window);
|
||||
if (node == nullptr) return false;
|
||||
return focused->data.as_group.hasChild(node);
|
||||
}
|
||||
}
|
||||
|
||||
std::string Hy3Node::debugNode() {
|
||||
std::stringstream buf;
|
||||
std::string addr = "0x" + std::to_string((size_t)this);
|
||||
|
|
|
@ -71,6 +71,11 @@ struct Hy3Node {
|
|||
|
||||
void recalcSizePosRecursive(bool force = false);
|
||||
std::string debugNode();
|
||||
void markFocused();
|
||||
void focus();
|
||||
void raiseToTop();
|
||||
Hy3Node* getFocusedNode();
|
||||
void updateDecos();
|
||||
|
||||
bool operator==(const Hy3Node&) const;
|
||||
|
||||
|
@ -105,9 +110,12 @@ public:
|
|||
virtual void onEnable();
|
||||
virtual void onDisable();
|
||||
|
||||
void makeGroupOn(CWindow*, Hy3GroupLayout);
|
||||
void shiftWindow(CWindow*, ShiftDirection);
|
||||
void shiftFocus(CWindow*, ShiftDirection);
|
||||
void makeGroupOn(int, Hy3GroupLayout);
|
||||
void shiftWindow(int, ShiftDirection);
|
||||
void shiftFocus(int, ShiftDirection);
|
||||
void raiseFocus(int);
|
||||
|
||||
bool shouldRenderSelected(CWindow*);
|
||||
|
||||
Hy3Node* getWorkspaceRootGroup(const int&);
|
||||
|
||||
|
|
43
src/SelectionHook.cpp
Normal file
43
src/SelectionHook.cpp
Normal file
|
@ -0,0 +1,43 @@
|
|||
#include "globals.hpp"
|
||||
#include "src/Window.hpp"
|
||||
#include <src/plugins/PluginAPI.hpp>
|
||||
#include <src/Compositor.hpp>
|
||||
|
||||
inline CFunctionHook* g_LastSelectionHook = nullptr;
|
||||
|
||||
void hook_update_decos(void* thisptr, CWindow* window) {
|
||||
bool explicitly_selected = g_Hy3Layout->shouldRenderSelected(window);
|
||||
Debug::log(LOG, "update decos for %p - selected: %d", window, explicitly_selected);
|
||||
|
||||
auto* lastWindow = g_pCompositor->m_pLastWindow;
|
||||
if (explicitly_selected) {
|
||||
g_pCompositor->m_pLastWindow = window;
|
||||
}
|
||||
|
||||
((void (*)(void*, CWindow*)) g_LastSelectionHook->m_pOriginal)(thisptr, window);
|
||||
|
||||
if (explicitly_selected) {
|
||||
g_pCompositor->m_pLastWindow = lastWindow;
|
||||
}
|
||||
}
|
||||
|
||||
void setup_selection_hook() {
|
||||
if (g_LastSelectionHook == nullptr) {
|
||||
static const auto decoUpdateCandidates = HyprlandAPI::findFunctionsByName(PHANDLE, "updateWindowAnimatedDecorationValues");
|
||||
|
||||
if (decoUpdateCandidates.size() != 1) {
|
||||
Debug::log(ERR, "Expected one matching function to hook for \"updateWindowAnimatedDecorationValues\", found %d", decoUpdateCandidates.size());
|
||||
return;
|
||||
}
|
||||
|
||||
g_LastSelectionHook = HyprlandAPI::createFunctionHook(PHANDLE, decoUpdateCandidates[0].address, (void *)&hook_update_decos);
|
||||
}
|
||||
|
||||
g_LastSelectionHook->hook();
|
||||
}
|
||||
|
||||
void disable_selection_hook() {
|
||||
if (g_LastSelectionHook != nullptr) {
|
||||
g_LastSelectionHook->unhook();
|
||||
}
|
||||
}
|
4
src/SelectionHook.hpp
Normal file
4
src/SelectionHook.hpp
Normal file
|
@ -0,0 +1,4 @@
|
|||
#pragma once
|
||||
|
||||
void setup_selection_hook();
|
||||
void disable_selection_hook();
|
|
@ -1,3 +1,5 @@
|
|||
#include <src/plugins/PluginAPI.hpp>
|
||||
#include "Hy3Layout.hpp"
|
||||
|
||||
inline HANDLE PHANDLE = nullptr;
|
||||
inline std::unique_ptr<Hy3Layout> g_Hy3Layout;
|
||||
|
|
68
src/main.cpp
68
src/main.cpp
|
@ -1,11 +1,8 @@
|
|||
#include <src/plugins/PluginAPI.hpp>
|
||||
#include <src/Compositor.hpp>
|
||||
|
||||
#include "globals.hpp"
|
||||
#include "Hy3Layout.hpp"
|
||||
#include "src/Compositor.hpp"
|
||||
#include "src/config/ConfigManager.hpp"
|
||||
|
||||
inline std::unique_ptr<Hy3Layout> g_Hy3Layout;
|
||||
|
||||
APICALL EXPORT std::string PLUGIN_API_VERSION() {
|
||||
return HYPRLAND_API_VERSION;
|
||||
|
@ -25,53 +22,73 @@ CWindow* window_for_action() {
|
|||
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 < 0) 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) {
|
||||
CWindow* window = window_for_action();
|
||||
if (window == nullptr) return;
|
||||
int workspace = workspace_for_action();
|
||||
if (workspace < 0) return;
|
||||
|
||||
if (arg == "h") {
|
||||
g_Hy3Layout->makeGroupOn(window, Hy3GroupLayout::SplitH);
|
||||
g_Hy3Layout->makeGroupOn(workspace, Hy3GroupLayout::SplitH);
|
||||
} else if (arg == "v") {
|
||||
g_Hy3Layout->makeGroupOn(window, Hy3GroupLayout::SplitV);
|
||||
g_Hy3Layout->makeGroupOn(workspace, Hy3GroupLayout::SplitV);
|
||||
}
|
||||
}
|
||||
|
||||
void dispatch_movewindow(std::string arg) {
|
||||
CWindow* window = window_for_action();
|
||||
if (window == nullptr) return;
|
||||
int workspace = workspace_for_action();
|
||||
if (workspace < 0) return;
|
||||
|
||||
if (arg == "l") {
|
||||
g_Hy3Layout->shiftWindow(window, ShiftDirection::Left);
|
||||
g_Hy3Layout->shiftWindow(workspace, ShiftDirection::Left);
|
||||
} else if (arg == "u") {
|
||||
g_Hy3Layout->shiftWindow(window, ShiftDirection::Up);
|
||||
g_Hy3Layout->shiftWindow(workspace, ShiftDirection::Up);
|
||||
} else if (arg == "d") {
|
||||
g_Hy3Layout->shiftWindow(window, ShiftDirection::Down);
|
||||
g_Hy3Layout->shiftWindow(workspace, ShiftDirection::Down);
|
||||
} else if (arg == "r") {
|
||||
g_Hy3Layout->shiftWindow(window, ShiftDirection::Right);
|
||||
g_Hy3Layout->shiftWindow(workspace, ShiftDirection::Right);
|
||||
}
|
||||
}
|
||||
|
||||
void dispatch_movefocus(std::string arg) {
|
||||
CWindow* window = window_for_action();
|
||||
if (window == nullptr) return;
|
||||
int workspace = workspace_for_action();
|
||||
if (workspace < 0) return;
|
||||
|
||||
if (arg == "l") {
|
||||
g_Hy3Layout->shiftFocus(window, ShiftDirection::Left);
|
||||
g_Hy3Layout->shiftFocus(workspace, ShiftDirection::Left);
|
||||
} else if (arg == "u") {
|
||||
g_Hy3Layout->shiftFocus(window, ShiftDirection::Up);
|
||||
g_Hy3Layout->shiftFocus(workspace, ShiftDirection::Up);
|
||||
} else if (arg == "d") {
|
||||
g_Hy3Layout->shiftFocus(window, ShiftDirection::Down);
|
||||
g_Hy3Layout->shiftFocus(workspace, ShiftDirection::Down);
|
||||
} else if (arg == "r") {
|
||||
g_Hy3Layout->shiftFocus(window, ShiftDirection::Right);
|
||||
g_Hy3Layout->shiftFocus(workspace, ShiftDirection::Right);
|
||||
}
|
||||
}
|
||||
|
||||
void dispatch_debug(std::string arg) {
|
||||
CWindow* window = window_for_action();
|
||||
if (window == nullptr) return;
|
||||
void dispatch_raisefocus(std::string arg) {
|
||||
int workspace = workspace_for_action();
|
||||
if (workspace < 0) return;
|
||||
|
||||
auto* root = g_Hy3Layout->getWorkspaceRootGroup(window->m_iWorkspaceID);
|
||||
if (window == nullptr) {
|
||||
g_Hy3Layout->raiseFocus(workspace);
|
||||
}
|
||||
|
||||
void dispatch_debug(std::string arg) {
|
||||
int workspace = workspace_for_action();
|
||||
if (workspace < 0) return;
|
||||
|
||||
auto* root = g_Hy3Layout->getWorkspaceRootGroup(workspace);
|
||||
if (workspace < 0) {
|
||||
Debug::log(LOG, "DEBUG NODES: no nodes on workspace");
|
||||
} else {
|
||||
Debug::log(LOG, "DEBUG NODES\n%s", root->debugNode().c_str());
|
||||
|
@ -89,6 +106,7 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
|
|||
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:raisefocus", dispatch_raisefocus);
|
||||
HyprlandAPI::addDispatcher(PHANDLE, "hy3:debugnodes", dispatch_debug);
|
||||
|
||||
return {"hy3", "i3 like layout for hyprland", "outfoxxed", "0.1"};
|
||||
|
|
Loading…
Add table
Reference in a new issue