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
src/main.cpp
src/Hy3Layout.cpp
src/SelectionHook.cpp
)
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 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

View file

@ -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);

View file

@ -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
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 "Hy3Layout.hpp"
inline HANDLE PHANDLE = nullptr;
inline std::unique_ptr<Hy3Layout> g_Hy3Layout;

View file

@ -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"};