Raising focus and grouped movement

This commit is contained in:
outfoxxed 2023-04-26 00:57:24 -07:00
parent 49ae28631f
commit 9b570f066a
No known key found for this signature in database
GPG key ID: 4C88A185FB89301E
8 changed files with 238 additions and 57 deletions

View file

@ -20,6 +20,7 @@ pkg_check_modules(DEPS REQUIRED pixman-1 libdrm)
add_library(hy3 SHARED add_library(hy3 SHARED
src/main.cpp src/main.cpp
src/Hy3Layout.cpp src/Hy3Layout.cpp
src/SelectionHook.cpp
) )
target_include_directories(hy3 PRIVATE target_include_directories(hy3 PRIVATE

View file

@ -6,7 +6,7 @@ i3 / sway like layout for [hyprland](https://github.com/hyprwm/hyprland).
- [x] Window splits - [x] Window splits
- [x] Window movement - [x] Window movement
- [x] Window resizing - [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 - [ ] Tabbed groups
- [ ] Some convenience dispatchers not found in i3 or sway - [ ] 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:makegroup, <h | v>` - make a vertical or horizontal split
- `hy3:movefocus, <l | u | d | r>` - move the focus left, up, down, or right - `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: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 - `hy3:debugnodes` - print the node tree into the hyprland log
## Installing ## Installing

View file

@ -1,5 +1,6 @@
#include "globals.hpp" #include "globals.hpp"
#include "Hy3Layout.hpp" #include "Hy3Layout.hpp"
#include "SelectionHook.hpp"
#include <src/Compositor.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) { bool Hy3Node::swallowGroups(Hy3Node* into) {
if (into == nullptr if (into == nullptr
|| into->data.type != Hy3NodeData::Group || 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 Hy3Layout::getWorkspaceNodeCount(const int& id) {
int count = 0; 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); 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(); opening_into->recalcSizePosRecursive();
Debug::log(LOG, "opening_into (%p) contains new child (%p)? %d", opening_into, &node, opening_into->data.as_group.hasChild(&node)); 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) { CWindow* Hy3Layout::getNextWindowCandidate(CWindow* window) {
auto* node = this->getWorkspaceRootGroup(window->m_iWorkspaceID); auto* node = this->getWorkspaceRootGroup(window->m_iWorkspaceID);
if (node == nullptr) return nullptr; if (node == nullptr) return nullptr;
while (node->data.type == Hy3NodeData::Group) {
node = node->data.as_group.lastFocusedChild; node = node->getFocusedNode();
if (node == nullptr) {
Debug::log(ERR, "A group's last focused child was null when getting the next selection candidate"); switch (node->data.type) {
return nullptr; case Hy3NodeData::Window:
} return node->data.as_window;
case Hy3NodeData::Group:
return nullptr;
} }
return node->data.as_window;
} }
void Hy3Layout::onWindowFocusChange(CWindow* 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); auto* node = this->getNodeFromWindow(window);
if (node == nullptr) return; if (node == nullptr) return;
while (node->parent != nullptr) { node->markFocused();
node->parent->data.as_group.lastFocusedChild = node;
node = node->parent;
}
} }
bool Hy3Layout::isWindowTiled(CWindow* window) { bool Hy3Layout::isWindowTiled(CWindow* window) {
@ -921,14 +995,17 @@ void Hy3Layout::onEnable() {
this->onWindowCreatedTiling(window.get()); this->onWindowCreatedTiling(window.get());
} }
setup_selection_hook();
} }
void Hy3Layout::onDisable() { void Hy3Layout::onDisable() {
disable_selection_hook();
this->nodes.clear(); this->nodes.clear();
} }
void Hy3Layout::makeGroupOn(CWindow* window, Hy3GroupLayout layout) { void Hy3Layout::makeGroupOn(int workspace, Hy3GroupLayout layout) {
auto* node = this->getNodeFromWindow(window); auto* node = this->getWorkspaceRootGroup(workspace)->getFocusedNode();
if (node == nullptr) return; if (node == nullptr) return;
if (node->parent->data.as_group.children.size() == 1 if (node->parent->data.as_group.children.size() == 1
@ -942,7 +1019,7 @@ void Hy3Layout::makeGroupOn(CWindow* window, Hy3GroupLayout layout) {
this->nodes.push_back({ this->nodes.push_back({
.parent = node, .parent = node,
.data = node->data.as_window, .data = node->data,
.workspace_id = node->workspace_id, .workspace_id = node->workspace_id,
.layout = this, .layout = this,
}); });
@ -957,9 +1034,11 @@ void Hy3Layout::makeGroupOn(CWindow* window, Hy3GroupLayout layout) {
Hy3Node* shiftOrGetFocus(Hy3Node& node, ShiftDirection direction, bool shift); Hy3Node* shiftOrGetFocus(Hy3Node& node, ShiftDirection direction, bool shift);
void Hy3Layout::shiftFocus(CWindow* window, ShiftDirection direction) { void Hy3Layout::shiftFocus(int workspace, ShiftDirection direction) {
Debug::log(LOG, "ShiftFocus %p %d", window, direction); auto* root = this->getWorkspaceRootGroup(workspace);
auto* node = this->getNodeFromWindow(window); if (root == nullptr) return;
auto* node = root->getFocusedNode();
Debug::log(LOG, "ShiftFocus %p %d", node, direction);
if (node == nullptr) return; if (node == nullptr) return;
Hy3Node* target; Hy3Node* target;
@ -968,9 +1047,11 @@ void Hy3Layout::shiftFocus(CWindow* window, ShiftDirection direction) {
} }
} }
void Hy3Layout::shiftWindow(CWindow* window, ShiftDirection direction) { void Hy3Layout::shiftWindow(int workspace, ShiftDirection direction) {
Debug::log(LOG, "ShiftWindow %p %d", window, direction); auto* root = this->getWorkspaceRootGroup(workspace);
auto* node = this->getNodeFromWindow(window); if (root == nullptr) return;
auto* node = root->getFocusedNode();
Debug::log(LOG, "ShiftWindow %p %d", node, direction);
if (node == nullptr) return; if (node == nullptr) return;
@ -1131,12 +1212,7 @@ Hy3Node* Hy3Layout::shiftOrGetFocus(Hy3Node& node, ShiftDirection direction, boo
target_parent = target_parent->parent; target_parent = target_parent->parent;
} }
auto* focusnode = &node; node.markFocused();
while (focusnode->parent != nullptr) {
focusnode->parent->data.as_group.lastFocusedChild = focusnode;
focusnode = focusnode->parent;
}
if (target_parent != target_group && target_parent != nullptr) if (target_parent != target_group && target_parent != nullptr)
target_parent->recalcSizePosRecursive(); target_parent->recalcSizePosRecursive();
@ -1145,6 +1221,34 @@ Hy3Node* Hy3Layout::shiftOrGetFocus(Hy3Node& node, ShiftDirection direction, boo
return nullptr; 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::string Hy3Node::debugNode() {
std::stringstream buf; std::stringstream buf;
std::string addr = "0x" + std::to_string((size_t)this); std::string addr = "0x" + std::to_string((size_t)this);

View file

@ -71,6 +71,11 @@ struct Hy3Node {
void recalcSizePosRecursive(bool force = false); void recalcSizePosRecursive(bool force = false);
std::string debugNode(); std::string debugNode();
void markFocused();
void focus();
void raiseToTop();
Hy3Node* getFocusedNode();
void updateDecos();
bool operator==(const Hy3Node&) const; bool operator==(const Hy3Node&) const;
@ -105,9 +110,12 @@ public:
virtual void onEnable(); virtual void onEnable();
virtual void onDisable(); virtual void onDisable();
void makeGroupOn(CWindow*, Hy3GroupLayout); void makeGroupOn(int, Hy3GroupLayout);
void shiftWindow(CWindow*, ShiftDirection); void shiftWindow(int, ShiftDirection);
void shiftFocus(CWindow*, ShiftDirection); void shiftFocus(int, ShiftDirection);
void raiseFocus(int);
bool shouldRenderSelected(CWindow*);
Hy3Node* getWorkspaceRootGroup(const int&); Hy3Node* getWorkspaceRootGroup(const int&);

43
src/SelectionHook.cpp Normal file
View 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
View file

@ -0,0 +1,4 @@
#pragma once
void setup_selection_hook();
void disable_selection_hook();

View file

@ -1,3 +1,5 @@
#include <src/plugins/PluginAPI.hpp> #include <src/plugins/PluginAPI.hpp>
#include "Hy3Layout.hpp"
inline HANDLE PHANDLE = nullptr; inline HANDLE PHANDLE = nullptr;
inline std::unique_ptr<Hy3Layout> g_Hy3Layout;

View file

@ -1,11 +1,8 @@
#include <src/plugins/PluginAPI.hpp> #include <src/plugins/PluginAPI.hpp>
#include <src/Compositor.hpp>
#include "globals.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() { APICALL EXPORT std::string PLUGIN_API_VERSION() {
return HYPRLAND_API_VERSION; return HYPRLAND_API_VERSION;
@ -25,53 +22,73 @@ CWindow* window_for_action() {
return window; 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) { void dispatch_makegroup(std::string arg) {
CWindow* window = window_for_action(); int workspace = workspace_for_action();
if (window == nullptr) return; if (workspace < 0) return;
if (arg == "h") { if (arg == "h") {
g_Hy3Layout->makeGroupOn(window, Hy3GroupLayout::SplitH); g_Hy3Layout->makeGroupOn(workspace, Hy3GroupLayout::SplitH);
} else if (arg == "v") { } else if (arg == "v") {
g_Hy3Layout->makeGroupOn(window, Hy3GroupLayout::SplitV); g_Hy3Layout->makeGroupOn(workspace, Hy3GroupLayout::SplitV);
} }
} }
void dispatch_movewindow(std::string arg) { void dispatch_movewindow(std::string arg) {
CWindow* window = window_for_action(); int workspace = workspace_for_action();
if (window == nullptr) return; if (workspace < 0) return;
if (arg == "l") { if (arg == "l") {
g_Hy3Layout->shiftWindow(window, ShiftDirection::Left); g_Hy3Layout->shiftWindow(workspace, ShiftDirection::Left);
} else if (arg == "u") { } else if (arg == "u") {
g_Hy3Layout->shiftWindow(window, ShiftDirection::Up); g_Hy3Layout->shiftWindow(workspace, ShiftDirection::Up);
} else if (arg == "d") { } else if (arg == "d") {
g_Hy3Layout->shiftWindow(window, ShiftDirection::Down); g_Hy3Layout->shiftWindow(workspace, ShiftDirection::Down);
} else if (arg == "r") { } else if (arg == "r") {
g_Hy3Layout->shiftWindow(window, ShiftDirection::Right); g_Hy3Layout->shiftWindow(workspace, ShiftDirection::Right);
} }
} }
void dispatch_movefocus(std::string arg) { void dispatch_movefocus(std::string arg) {
CWindow* window = window_for_action(); int workspace = workspace_for_action();
if (window == nullptr) return; if (workspace < 0) return;
if (arg == "l") { if (arg == "l") {
g_Hy3Layout->shiftFocus(window, ShiftDirection::Left); g_Hy3Layout->shiftFocus(workspace, ShiftDirection::Left);
} else if (arg == "u") { } else if (arg == "u") {
g_Hy3Layout->shiftFocus(window, ShiftDirection::Up); g_Hy3Layout->shiftFocus(workspace, ShiftDirection::Up);
} else if (arg == "d") { } else if (arg == "d") {
g_Hy3Layout->shiftFocus(window, ShiftDirection::Down); g_Hy3Layout->shiftFocus(workspace, ShiftDirection::Down);
} else if (arg == "r") { } else if (arg == "r") {
g_Hy3Layout->shiftFocus(window, ShiftDirection::Right); g_Hy3Layout->shiftFocus(workspace, ShiftDirection::Right);
} }
} }
void dispatch_debug(std::string arg) { void dispatch_raisefocus(std::string arg) {
CWindow* window = window_for_action(); int workspace = workspace_for_action();
if (window == nullptr) return; if (workspace < 0) return;
auto* root = g_Hy3Layout->getWorkspaceRootGroup(window->m_iWorkspaceID); g_Hy3Layout->raiseFocus(workspace);
if (window == nullptr) { }
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"); Debug::log(LOG, "DEBUG NODES: no nodes on workspace");
} else { } else {
Debug::log(LOG, "DEBUG NODES\n%s", root->debugNode().c_str()); 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:makegroup", dispatch_makegroup);
HyprlandAPI::addDispatcher(PHANDLE, "hy3:movefocus", dispatch_movefocus); HyprlandAPI::addDispatcher(PHANDLE, "hy3:movefocus", dispatch_movefocus);
HyprlandAPI::addDispatcher(PHANDLE, "hy3:movewindow", dispatch_movewindow); HyprlandAPI::addDispatcher(PHANDLE, "hy3:movewindow", dispatch_movewindow);
HyprlandAPI::addDispatcher(PHANDLE, "hy3:raisefocus", dispatch_raisefocus);
HyprlandAPI::addDispatcher(PHANDLE, "hy3:debugnodes", dispatch_debug); 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"};