Merge pull request #6 from outfoxxed/master

sync with upstream
This commit is contained in:
Kaley Fischer 2024-03-02 18:01:23 +01:00 committed by GitHub
commit da6d7c8e37
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 819 additions and 192 deletions

View file

@ -4,7 +4,10 @@
- Implement `resizeactivewindow` for floating windows
- Fully implement `resizeactivewindow` for tiled windows
- `hy3:resizenode` added, drop-in replacement for `resizeactivewindow` applied to a whole node.
- Implement keyboard-based focusing for floating windows
- Implement keyboard-based movement for floating windows
- Add configuration `kbd_shift_delta` providing delta [in pixels] for shift
## hl0.35.0 and before
- Fixed `hy3:killactive` and `hy3:movetoworkspace` not working in fullscreen.

View file

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

View file

@ -221,6 +221,20 @@ plugin {
# 2 = keep the nested group only if its parent is a tab group
node_collapse_policy = <int> # default: 2
# policy controlling what windows will be focused using `hy3:movefocused`
# 0 = focus strictly by layout, don't attempt to skip windows that are obscured by another one
# 1 = do not focus windows which are entirely obscured by a floating window
# 2 = when `hy3:movefocus` layer is `samelayer` then use focus policy 0 (focus strictly by layout)
# when `hy3:movefocus` layer is `all` then use focus policy 1 (don't focus obscured windows)
focus_obscured_windows_policy = <int> # default: 2
# which layers should be considered when changing focus with `hy3:movefocus`?
# samelayer = only focus windows on same layer as the source window (floating or tiled)
# all = choose the closest window irrespective of the layout
# tiled = only choose tiled windows, not especially useful but permitted by parser
# floating = only choose floating windows, not especially useful but permitted by parser
default_movefocus_layer = <string> # default: `samelayer`
# offset from group split direction when only one window is in a group
group_inset = <int> # default: 10
@ -344,6 +358,7 @@ plugin {
- `require_hovered` - affect the tab group under the mouse. do nothing if none are hovered.
- `wrap` - wrap to the opposite size of the tab bar if moving off the end
- `hy3:debugnodes` - print the node tree into the hyprland log
- `hy3:resizenode, <vector>` - like Hyprland `resizeactive`, but applied to the whole focused group instead of just a window
- :warning: **ALPHA QUALITY** `hy3:setswallow, <true | false | toggle>` - set the containing node's window swallow state
- :warning: **ALPHA QUALITY** `hy3:expand, <expand | shrink | base>` - expand the current node to cover other nodes
- `expand` - expand by one node

44
src/BitFlag.hpp Normal file
View file

@ -0,0 +1,44 @@
#pragma once
template <typename FlagType>
struct BitFlag {
int m_FlagValue = 0;
BitFlag() = default;
BitFlag(FlagType flag) { m_FlagValue = (int) flag; }
operator FlagType() const { return static_cast<FlagType>(m_FlagValue); }
void Set(FlagType flag) { m_FlagValue |= (int) flag; }
void Unset(FlagType flag) { m_FlagValue &= ~(int) flag; }
void Toggle(FlagType flag) { m_FlagValue ^= (int) flag; }
void Mask(FlagType flag) { m_FlagValue &= (int) flag; }
bool Has(FlagType flag) const { return (m_FlagValue & (int) flag) == (int) flag; }
bool HasAny(FlagType flag) const { return (m_FlagValue & (int) flag) != 0; }
bool HasNot(FlagType flag) const { return (m_FlagValue & (int) flag) != (int) flag; }
const BitFlag& operator|=(FlagType flag) {
Set(flag);
return *this;
}
const BitFlag& operator&=(FlagType flag) {
Mask(flag);
return *this;
}
const BitFlag& operator^=(FlagType flag) {
Toggle(flag);
return *this;
}
bool operator==(FlagType flag) const { return m_FlagValue == (int) flag; }
bool operator!=(FlagType flag) const { return m_FlagValue != (int) flag; }
};

View file

@ -1,3 +1,5 @@
#include <functional>
#include <numeric>
#include <regex>
#include <set>
@ -6,8 +8,10 @@
#include <hyprlang.hpp>
#include <ranges>
#include "BitFlag.hpp"
#include "Hy3Layout.hpp"
#include "SelectionHook.hpp"
#include "conversions.hpp"
#include "globals.hpp"
std::unique_ptr<HOOK_CALLBACK_FN> renderHookPtr =
@ -170,7 +174,7 @@ void Hy3Layout::insertNode(Hy3Node& node) {
if (opening_after != nullptr
&& ((node.data.type == Hy3NodeType::Group
&& (opening_after == &node || node.data.as_group.hasChild(opening_after)))
&& (opening_after == &node || node.hasChild(opening_after)))
|| opening_after->reparenting))
{
opening_after = nullptr;
@ -288,6 +292,7 @@ void Hy3Layout::insertNode(Hy3Node& node) {
}
void Hy3Layout::onWindowRemovedTiling(CWindow* window) {
this->m_focusIntercepts.erase(window);
static const auto node_collapse_policy =
ConfigValue<Hyprlang::INT>("plugin:hy3:node_collapse_policy");
@ -343,12 +348,14 @@ void Hy3Layout::onWindowRemovedTiling(CWindow* window) {
}
}
void Hy3Layout::onWindowRemovedFloating(CWindow* window) { this->m_focusIntercepts.erase(window); }
void Hy3Layout::onWindowFocusChange(CWindow* window) {
auto* node = this->getNodeFromWindow(window);
if (node == nullptr) return;
hy3_log(
TRACE,
LOG,
"changing window focus to window {:x} as node {:x}",
(uintptr_t) window,
(uintptr_t) node
@ -373,16 +380,10 @@ void Hy3Layout::recalculateMonitor(const int& monitor_id) {
// todo: refactor this
auto* top_node = this->getWorkspaceRootGroup(monitor->activeWorkspace);
if (top_node != nullptr) {
top_node->position = monitor->vecPosition + monitor->vecReservedTopLeft;
top_node->size =
monitor->vecSize - monitor->vecReservedTopLeft - monitor->vecReservedBottomRight;
top_node->recalcSizePosRecursive();
if (top_node == nullptr) {
top_node = this->getWorkspaceRootGroup(monitor->specialWorkspaceID);
}
top_node = this->getWorkspaceRootGroup(monitor->specialWorkspaceID);
if (top_node != nullptr) {
top_node->position = monitor->vecPosition + monitor->vecReservedTopLeft;
top_node->size =
@ -408,90 +409,114 @@ ShiftDirection reverse(ShiftDirection direction) {
}
}
void executeResizeOperation(
const Vector2D& delta,
eRectCorner corner,
Hy3Node* node,
CMonitor* monitor
) {
if (node == nullptr) return;
if (monitor == nullptr) return;
const bool display_left =
STICKS(node->position.x, monitor->vecPosition.x + monitor->vecReservedTopLeft.x);
const bool display_right = STICKS(
node->position.x + node->size.x,
monitor->vecPosition.x + monitor->vecSize.x - monitor->vecReservedBottomRight.x
);
const bool display_top =
STICKS(node->position.y, monitor->vecPosition.y + monitor->vecReservedTopLeft.y);
const bool display_bottom = STICKS(
node->position.y + node->size.y,
monitor->vecPosition.y + monitor->vecSize.y - monitor->vecReservedBottomRight.y
);
Vector2D resize_delta = delta;
bool node_is_root = (node->data.type == Hy3NodeType::Group && node->parent == nullptr)
|| (node->data.type == Hy3NodeType::Window
&& (node->parent == nullptr || node->parent->parent == nullptr));
if (node_is_root) {
if (display_left && display_right) resize_delta.x = 0;
if (display_top && display_bottom) resize_delta.y = 0;
}
// Don't execute the logic unless there's something to do
if (resize_delta.x != 0 || resize_delta.y != 0) {
ShiftDirection target_edge_x;
ShiftDirection target_edge_y;
// Determine the direction in which we're going to look for the neighbor node
// that will be resized
if (corner == CORNER_NONE) { // It's probably a keyboard event.
target_edge_x = display_right ? ShiftDirection::Left : ShiftDirection::Right;
target_edge_y = display_bottom ? ShiftDirection::Up : ShiftDirection::Down;
// If the anchor is not at the top/left then reverse the delta
if (target_edge_x == ShiftDirection::Left) resize_delta.x = -resize_delta.x;
if (target_edge_y == ShiftDirection::Up) resize_delta.y = -resize_delta.y;
} else { // It's probably a mouse event
// Resize against the edges corresponding to the selected corner
target_edge_x = corner == CORNER_TOPLEFT || corner == CORNER_BOTTOMLEFT
? ShiftDirection::Left
: ShiftDirection::Right;
target_edge_y = corner == CORNER_TOPLEFT || corner == CORNER_TOPRIGHT ? ShiftDirection::Up
: ShiftDirection::Down;
}
// Find the neighboring node in each axis, which will be either above or at the
// same level as the initiating node in the layout hierarchy. These are the nodes
// which must get resized (rather than the initiator) because they are the
// highest point in the hierarchy
auto horizontal_neighbor = node->findNeighbor(target_edge_x);
auto vertical_neighbor = node->findNeighbor(target_edge_y);
static const auto animate = ConfigValue<Hyprlang::INT>("misc:animate_manual_resizes");
// Note that the resize direction is reversed, because from the neighbor's perspective
// the edge to be moved is the opposite way round. However, the delta is still the same.
if (horizontal_neighbor) {
horizontal_neighbor->resize(reverse(target_edge_x), resize_delta.x, *animate == 0);
}
if (vertical_neighbor) {
vertical_neighbor->resize(reverse(target_edge_y), resize_delta.y, *animate == 0);
}
}
}
void Hy3Layout::resizeNode(const Vector2D& delta, eRectCorner corner, Hy3Node* node) {
// Is the intended target really a node or a floating window?
auto window = g_pCompositor->m_pLastWindow;
if (window && window->m_bIsFloating) {
this->resizeActiveWindow(delta, corner, window);
} else if (node) {
auto monitor = g_pCompositor->getMonitorFromID(
g_pCompositor->getWorkspaceByID(node->workspace_id)->m_iMonitorID
);
executeResizeOperation(delta, corner, node, monitor);
}
}
void Hy3Layout::resizeActiveWindow(const Vector2D& delta, eRectCorner corner, CWindow* pWindow) {
auto window = pWindow ? pWindow : g_pCompositor->m_pLastWindow;
if (!g_pCompositor->windowValidMapped(window)) return;
if (window == nullptr || !g_pCompositor->windowValidMapped(window)) return;
auto* node = this->getNodeFromWindow(window);
if (node != nullptr) {
node = &node->getExpandActor();
auto monitor = g_pCompositor->getMonitorFromID(window->m_iMonitorID);
const bool display_left =
STICKS(node->position.x, monitor->vecPosition.x + monitor->vecReservedTopLeft.x);
const bool display_right = STICKS(
node->position.x + node->size.x,
monitor->vecPosition.x + monitor->vecSize.x - monitor->vecReservedBottomRight.x
);
const bool display_top =
STICKS(node->position.y, monitor->vecPosition.y + monitor->vecReservedTopLeft.y);
const bool display_bottom = STICKS(
node->position.y + node->size.y,
monitor->vecPosition.y + monitor->vecSize.y - monitor->vecReservedBottomRight.y
);
Vector2D resize_delta = delta;
bool node_is_root = (node->data.type == Hy3NodeType::Group && node->parent == nullptr)
|| (node->data.type == Hy3NodeType::Window
&& (node->parent == nullptr || node->parent->parent == nullptr));
if (node_is_root) {
if (display_left && display_right) resize_delta.x = 0;
if (display_top && display_bottom) resize_delta.y = 0;
}
// Don't execute the logic unless there's something to do
if (resize_delta.x != 0 || resize_delta.y != 0) {
ShiftDirection target_edge_x;
ShiftDirection target_edge_y;
// Determine the direction in which we're going to look for the neighbor node
// that will be resized
if (corner == CORNER_NONE) { // It's probably a keyboard event.
target_edge_x = display_right ? ShiftDirection::Left : ShiftDirection::Right;
target_edge_y = display_bottom ? ShiftDirection::Up : ShiftDirection::Down;
// If the anchor is not at the top/left then reverse the delta
if (target_edge_x == ShiftDirection::Left) resize_delta.x = -resize_delta.x;
if (target_edge_y == ShiftDirection::Up) resize_delta.y = -resize_delta.y;
} else { // It's probably a mouse event
// Resize against the edges corresponding to the selected corner
target_edge_x = corner == CORNER_TOPLEFT || corner == CORNER_BOTTOMLEFT
? ShiftDirection::Left
: ShiftDirection::Right;
target_edge_y = corner == CORNER_TOPLEFT || corner == CORNER_TOPRIGHT
? ShiftDirection::Up
: ShiftDirection::Down;
}
// Find the neighboring node in each axis, which will be either above or at the
// same level as the initiating node in the layout hierarchy. These are the nodes
// which must get resized (rather than the initiator) because they are the
// highest point in the hierarchy
auto horizontal_neighbor = node->findNeighbor(target_edge_x);
auto vertical_neighbor = node->findNeighbor(target_edge_y);
static const auto animate = ConfigValue<Hyprlang::INT>("misc:animate_manual_resizes");
// Note that the resize direction is reversed, because from the neighbor's perspective
// the edge to be moved is the opposite way round. However, the delta is still the same.
if (horizontal_neighbor) {
horizontal_neighbor->resize(reverse(target_edge_x), resize_delta.x, *animate == 0);
}
if (vertical_neighbor) {
vertical_neighbor->resize(reverse(target_edge_y), resize_delta.y, *animate == 0);
}
}
} else if (window->m_bIsFloating) {
// No parent node - is this a floating window? If so, use the same logic as the `main` layout
if (window->m_bIsFloating) {
// Use the same logic as the `main` layout for floating windows
const auto required_size = Vector2D(
std::max((window->m_vRealSize.goalv() + delta).x, 20.0),
std::max((window->m_vRealSize.goalv() + delta).y, 20.0)
);
window->m_vRealSize = required_size;
g_pXWaylandManager->setWindowSize(window, required_size);
} else if (auto* node = this->getNodeFromWindow(window); node != nullptr) {
executeResizeOperation(
delta,
corner,
&node->getExpandActor(),
g_pCompositor->getMonitorFromID(window->m_iMonitorID)
);
}
}
@ -610,16 +635,26 @@ void Hy3Layout::switchWindows(CWindow* pWindowA, CWindow* pWindowB) {
void Hy3Layout::moveWindowTo(CWindow* window, const std::string& direction) {
auto* node = this->getNodeFromWindow(window);
if (node == nullptr) return;
if (node == nullptr) {
const auto neighbor = g_pCompositor->getWindowInDirection(window, direction[0]);
ShiftDirection shift;
if (direction == "l") shift = ShiftDirection::Left;
else if (direction == "r") shift = ShiftDirection::Right;
else if (direction == "u") shift = ShiftDirection::Up;
else if (direction == "d") shift = ShiftDirection::Down;
else return;
if (window->m_iWorkspaceID != neighbor->m_iWorkspaceID) {
// if different monitors, send to monitor
onWindowRemovedTiling(window);
window->moveToWorkspace(neighbor->m_iWorkspaceID);
window->m_iMonitorID = neighbor->m_iMonitorID;
onWindowCreatedTiling(window);
}
} else {
ShiftDirection shift;
if (direction == "l") shift = ShiftDirection::Left;
else if (direction == "r") shift = ShiftDirection::Right;
else if (direction == "u") shift = ShiftDirection::Up;
else if (direction == "d") shift = ShiftDirection::Down;
else return;
this->shiftNode(*node, shift, false, false);
this->shiftNode(*node, shift, false, false);
}
}
void Hy3Layout::alterSplitRatio(CWindow* pWindow, float delta, bool exact) {
@ -869,43 +904,423 @@ void Hy3Layout::shiftNode(Hy3Node& node, ShiftDirection direction, bool once, bo
}
}
void Hy3Layout::shiftWindow(int workspace, ShiftDirection direction, bool once, bool visible) {
auto* node = this->getWorkspaceFocusedNode(workspace);
if (node == nullptr) return;
void shiftFloatingWindow(CWindow* window, ShiftDirection direction) {
static const auto kbd_shift_delta = ConfigValue<Hyprlang::INT>("plugin:hy3:kbd_shift_delta");
this->shiftNode(*node, direction, once, visible);
}
if (!window) return;
void Hy3Layout::shiftFocus(int workspace, ShiftDirection direction, bool visible) {
auto* current_window = g_pCompositor->m_pLastWindow;
if (current_window != nullptr) {
auto* p_workspace = g_pCompositor->getWorkspaceByID(current_window->m_iWorkspaceID);
if (p_workspace->m_bHasFullscreenWindow) return;
if (current_window->m_bIsFloating) {
auto* next_window = g_pCompositor->getWindowInDirection(
current_window,
direction == ShiftDirection::Left ? 'l'
: direction == ShiftDirection::Up ? 'u'
: direction == ShiftDirection::Down ? 'd'
: 'r'
);
if (next_window != nullptr) g_pCompositor->focusWindow(next_window);
return;
Vector2D bounds {0, 0};
// BUG: Assumes horizontal monitor layout
// BUG: Ignores monitor reserved space
for (auto m: g_pCompositor->m_vMonitors) {
bounds.x = std::max(bounds.x, m->vecPosition.x + m->vecSize.x);
if (m->ID == window->m_iMonitorID) {
bounds.y = m->vecPosition.y + m->vecSize.y;
}
}
auto* node = this->getWorkspaceFocusedNode(workspace);
if (node == nullptr) return;
const int delta = getSearchDirection(direction) == SearchDirection::Forwards ? *kbd_shift_delta
: -*kbd_shift_delta;
auto* target = this->shiftOrGetFocus(*node, direction, false, false, visible);
Vector2D movement_delta =
(getAxis(direction) == Axis::Horizontal) ? Vector2D {delta, 0} : Vector2D {0, delta};
if (target != nullptr) {
target->focus();
while (target->parent != nullptr) target = target->parent;
target->recalcSizePosRecursive();
auto window_pos = window->m_vRealPosition.vec();
auto window_size = window->m_vRealSize.vec();
// Keep at least `delta` pixels visible
if (window_pos.x + window_size.x + delta < 0 || window_pos.x + delta > bounds.x)
movement_delta.x = 0;
if (window_pos.y + window_size.y + delta < 0 || window_pos.y + delta > bounds.y)
movement_delta.y = 0;
if (movement_delta.x != 0 || movement_delta.y != 0) {
auto new_pos = window_pos + movement_delta;
// Do we need to change the workspace?
const auto new_monitor = g_pCompositor->getMonitorFromVector(new_pos);
if (new_monitor && new_monitor->ID != window->m_iMonitorID) {
// Ignore the movement request if the new workspace is special
if (!new_monitor->specialWorkspaceID) {
const auto old_workspace = g_pCompositor->getWorkspaceByID(window->m_iWorkspaceID);
const auto new_workspace = g_pCompositor->getWorkspaceByID(new_monitor->activeWorkspace);
const auto previous_monitor = g_pCompositor->getMonitorFromID(window->m_iMonitorID);
const auto original_new_pos = new_pos;
if (new_workspace && previous_monitor) {
switch (direction) {
case ShiftDirection::Left: new_pos.x += new_monitor->vecSize.x; break;
case ShiftDirection::Right: new_pos.x -= previous_monitor->vecSize.x; break;
case ShiftDirection::Up: new_pos.y += new_monitor->vecSize.y; break;
case ShiftDirection::Down: new_pos.y -= previous_monitor->vecSize.y; break;
default: UNREACHABLE();
}
}
window->m_vRealPosition = new_pos;
g_pCompositor->moveWindowToWorkspaceSafe(window, new_workspace);
g_pCompositor->setActiveMonitor(new_monitor);
const static auto allow_workspace_cycles =
ConfigValue<Hyprlang::INT>("binds:allow_workspace_cycles");
if (*allow_workspace_cycles) new_workspace->rememberPrevWorkspace(old_workspace);
}
} else {
window->m_vRealPosition = new_pos;
}
}
}
void Hy3Layout::shiftWindow(int workspace_id, ShiftDirection direction, bool once, bool visible) {
auto focused_window = g_pCompositor->m_pLastWindow;
auto* node = getWorkspaceFocusedNode(workspace_id);
if (focused_window && focused_window->m_bIsFloating) {
shiftFloatingWindow(focused_window, direction);
} else if (node) {
shiftNode(*node, direction, once, visible);
}
}
void Hy3Layout::focusMonitor(CMonitor* monitor) {
if (monitor == nullptr) return;
g_pCompositor->setActiveMonitor(monitor);
const auto focusedNode = this->getWorkspaceFocusedNode(monitor->activeWorkspace);
if (focusedNode != nullptr) {
focusedNode->focus();
} else {
auto* workspace = g_pCompositor->getWorkspaceByID(monitor->activeWorkspace);
CWindow* next_window = nullptr;
if (workspace != nullptr) {
workspace->setActive(true);
if (workspace->m_bHasFullscreenWindow) {
next_window = g_pCompositor->getFullscreenWindowOnWorkspace(workspace->m_iID);
} else {
next_window = workspace->getLastFocusedWindow();
}
} else {
for (auto& w: g_pCompositor->m_vWindows | std::views::reverse) {
if (w->m_bIsMapped && !w->isHidden() && w->m_bIsFloating && w->m_iX11Type != 2
&& w->m_iWorkspaceID == next_window->m_iWorkspaceID && !w->m_bX11ShouldntFocus
&& !w->m_sAdditionalConfigData.noFocus)
{
next_window = w.get();
break;
}
}
}
g_pCompositor->focusWindow(next_window);
}
}
CWindow* getFocusedWindow(const Hy3Node* node) {
auto search = node;
while (search != nullptr && search->data.type == Hy3NodeType::Group) {
search = search->data.as_group.focused_child;
}
if (search == nullptr || search->data.type != Hy3NodeType::Window) {
return nullptr;
}
return search->data.as_window;
}
bool shiftIsForward(ShiftDirection direction) {
return direction == ShiftDirection::Right || direction == ShiftDirection::Down;
}
bool shiftIsVertical(ShiftDirection direction) {
return direction == ShiftDirection::Up || direction == ShiftDirection::Down;
}
bool shiftMatchesLayout(Hy3GroupLayout layout, ShiftDirection direction) {
return (layout == Hy3GroupLayout::SplitV && shiftIsVertical(direction))
|| (layout != Hy3GroupLayout::SplitV && !shiftIsVertical(direction));
}
bool covers(const CBox& outer, const CBox& inner) {
return outer.x <= inner.x && outer.y <= inner.y && outer.x + outer.w >= inner.x + inner.w
&& outer.y + outer.h >= inner.y + inner.h;
}
bool isObscured(CWindow* window) {
if (!window) return false;
const auto inner_box = window->getWindowMainSurfaceBox();
bool is_obscured = false;
for (auto& w: g_pCompositor->m_vWindows | std::views::reverse) {
if (w.get() == window) {
// Don't go any further if this is a floating window, because m_vWindows is sorted bottom->top
// per Compositor.cpp
if (window->m_bIsFloating) break;
else continue;
}
if (!w->m_bIsFloating) continue;
const auto outer_box = w->getWindowMainSurfaceBox();
is_obscured = covers(outer_box, inner_box);
if (is_obscured) break;
};
return is_obscured;
}
bool isObscured(Hy3Node* node) {
return node && node->data.type == Hy3NodeType::Window && isObscured(node->data.as_window);
}
bool isNotObscured(CWindow* window) { return !isObscured(window); }
bool isNotObscured(Hy3Node* node) { return !isObscured(node); }
CWindow* getWindowInDirection(
CWindow* source,
ShiftDirection direction,
BitFlag<Layer> layers_same_monitor,
BitFlag<Layer> layers_other_monitors
) {
if (!source) return nullptr;
if (layers_other_monitors == Layer::None && layers_same_monitor == Layer::None) return nullptr;
CWindow* target_window = nullptr;
const auto source_middle = source->middle();
std::optional<Distance> target_distance;
const auto static focus_policy =
ConfigValue<Hyprlang::INT>("plugin:hy3:focus_obscured_windows_policy");
bool permit_obscured_windows =
*focus_policy == 0
|| (*focus_policy == 2 && layers_same_monitor.HasNot(Layer::Floating | Layer::Tiled));
const auto source_monitor = g_pCompositor->getMonitorFromID(source->m_iMonitorID);
const auto next_monitor =
layers_other_monitors.HasAny(Layer::Floating | Layer::Tiled)
? g_pCompositor->getMonitorInDirection(source_monitor, directionToChar(direction))
: nullptr;
const auto next_workspace = next_monitor ? next_monitor->specialWorkspaceID
? next_monitor->specialWorkspaceID
: next_monitor->activeWorkspace
: WORKSPACE_INVALID;
auto isCandidate = [=, mon = source->m_iMonitorID](CWindow* w) {
const auto window_layer = w->m_bIsFloating ? Layer::Floating : Layer::Tiled;
const auto monitor_flags = w->m_iMonitorID == mon ? layers_same_monitor : layers_other_monitors;
return (monitor_flags.Has(window_layer)) && w->m_bIsMapped && w->m_iX11Type != 2
&& !w->m_sAdditionalConfigData.noFocus && !w->isHidden() && !w->m_bX11ShouldntFocus
&& (w->m_bPinned || w->m_iWorkspaceID == source->m_iWorkspaceID
|| w->m_iWorkspaceID == next_workspace);
};
for (auto& pw: g_pCompositor->m_vWindows) {
auto w = pw.get();
if (w != source && isCandidate(w)) {
auto dist = Distance {direction, source_middle, w->middle()};
if ((target_distance.has_value() ? dist < target_distance.value()
: dist.isInDirection(direction))
&& (permit_obscured_windows || isNotObscured(w)))
{
target_window = w;
target_distance = dist;
}
}
}
hy3_log(LOG, "getWindowInDirection: closest window to {} is {}", source, target_window);
// If the closest window is on a different monitor and the nearest edge has the same position
// as the last focused window on that monitor's workspace then choose the last focused window
// instead; this allows seamless back-and-forth by direction keys
if (target_window && target_window->m_iMonitorID != source->m_iMonitorID) {
if (auto new_workspace = g_pCompositor->getWorkspaceByID(next_workspace)) {
if (auto last_focused = new_workspace->getLastFocusedWindow()) {
auto target_bounds =
CBox(target_window->m_vRealPosition.vec(), target_window->m_vRealSize.vec());
auto last_focused_bounds =
CBox(last_focused->m_vRealPosition.vec(), last_focused->m_vRealSize.vec());
if ((direction == ShiftDirection::Left
&& STICKS(
target_bounds.x + target_bounds.w,
last_focused_bounds.x + last_focused_bounds.w
))
|| (direction == ShiftDirection::Right && STICKS(target_bounds.x, last_focused_bounds.x)
)
|| (direction == ShiftDirection::Up
&& STICKS(
target_bounds.y + target_bounds.h,
last_focused_bounds.y + last_focused_bounds.h
))
|| (direction == ShiftDirection::Down && STICKS(target_bounds.y, last_focused_bounds.y)
))
{
target_window = last_focused;
}
}
}
}
return target_window;
}
void Hy3Layout::shiftFocusToMonitor(ShiftDirection direction) {
auto target_monitor = g_pCompositor->getMonitorInDirection(directionToChar(direction));
if (target_monitor) this->focusMonitor(target_monitor);
}
void Hy3Layout::shiftFocus(
int workspace,
ShiftDirection direction,
bool visible,
BitFlag<Layer> eligible_layers
) {
Hy3Node* candidate_node = nullptr;
CWindow* closest_window = nullptr;
Hy3Node* source_node = nullptr;
CWindow* source_window = g_pCompositor->m_pLastWindow;
CWorkspace* source_workspace = g_pCompositor->getWorkspaceByID(workspace);
if (source_workspace) {
source_window = source_workspace->m_pLastFocusedWindow;
} else {
source_window = g_pCompositor->m_pLastWindow;
}
if (source_window == nullptr || (source_workspace && source_workspace->m_bHasFullscreenWindow)) {
shiftFocusToMonitor(direction);
return;
}
hy3_log(
LOG,
"shiftFocus: Source: {} ({}), workspace: {}, direction: {}, visible: {}",
source_window,
source_window->m_bIsFloating ? "floating" : "tiled",
workspace,
(int) direction,
visible
);
// If no eligible_layers specified then choose the same layer as the source window
if (eligible_layers == Layer::None)
eligible_layers = source_window->m_bIsFloating ? Layer::Floating : Layer::Tiled;
const auto static focus_policy =
ConfigValue<Hyprlang::INT>("plugin:hy3:focus_obscured_windows_policy");
bool skip_obscured = *focus_policy == 1
|| (*focus_policy == 2 && eligible_layers.Has(Layer::Floating | Layer::Tiled));
// Determine the starting point for looking for a tiled node - it's either the
// workspace's focused node or the floating window's focus entry point (which may be null)
if (eligible_layers.Has(Layer::Tiled)) {
source_node = source_window->m_bIsFloating ? getFocusOverride(source_window, direction)
: getWorkspaceFocusedNode(workspace);
if (source_node) {
candidate_node = this->shiftOrGetFocus(*source_node, direction, false, false, visible);
while (candidate_node && skip_obscured && isObscured(candidate_node)) {
candidate_node = this->shiftOrGetFocus(*candidate_node, direction, false, false, visible);
}
}
}
BitFlag<Layer> this_monitor = eligible_layers & Layer::Floating;
if (source_window->m_bIsFloating && !candidate_node)
this_monitor |= (eligible_layers & Layer::Tiled);
BitFlag<Layer> other_monitors;
if (!candidate_node) other_monitors |= eligible_layers;
// Find the closest window in the right direction. Consider other monitors
// if we don't have a tiled candidate
closest_window = getWindowInDirection(source_window, direction, this_monitor, other_monitors);
// If there's a window in the right direction then choose between that window and the tiled
// candidate.
bool focus_closest_window = false;
if (closest_window) {
if (candidate_node) {
// If the closest window is tiled then focus the tiled node which was obtained from
// `shiftOrGetFocus`, otherwise focus whichever is closer
if (closest_window->m_bIsFloating) {
Distance distanceToClosestWindow(
direction,
source_window->middle(),
closest_window->middle()
);
Distance distanceToTiledNode(direction, source_window->middle(), candidate_node->middle());
if (distanceToClosestWindow < distanceToTiledNode) {
focus_closest_window = true;
}
}
} else {
focus_closest_window = true;
}
}
std::optional<uint64_t> new_monitor_id;
if (focus_closest_window) {
new_monitor_id = closest_window->m_iMonitorID;
setFocusOverride(closest_window, direction, source_node);
g_pCompositor->focusWindow(closest_window);
} else if (candidate_node) {
if (candidate_node->data.type == Hy3NodeType::Window) {
new_monitor_id = candidate_node->data.as_window->m_iMonitorID;
} else if (auto* workspace = g_pCompositor->getWorkspaceByID(candidate_node->getRoot()->workspace_id))
{
new_monitor_id = workspace->m_iMonitorID;
}
candidate_node->focusWindow();
candidate_node->getRoot()->recalcSizePosRecursive();
} else {
shiftFocusToMonitor(direction);
}
if (new_monitor_id && new_monitor_id.value() != source_window->m_iMonitorID) {
if (auto* monitor = g_pCompositor->getMonitorFromID(new_monitor_id.value())) {
g_pCompositor->setActiveMonitor(monitor);
}
}
}
Hy3Node* Hy3Layout::getFocusOverride(CWindow* src, ShiftDirection direction) {
if (auto intercept = this->m_focusIntercepts.find(src);
intercept != this->m_focusIntercepts.end())
{
Hy3Node** accessor = intercept->second.forDirection(direction);
if (auto override = *accessor) {
// If the root isn't valid or is on a different workspsace then update the intercept data
if (override->workspace_id != src->m_iWorkspaceID
|| !std::ranges::contains(this->nodes, *override))
{
*accessor = nullptr;
// If there are no remaining overrides then discard the intercept
if (intercept->second.isEmpty()) {
this->m_focusIntercepts.erase(intercept);
}
}
return override;
}
}
return nullptr;
}
void Hy3Layout::setFocusOverride(CWindow* src, ShiftDirection direction, Hy3Node* dest) {
if (auto intercept = this->m_focusIntercepts.find(src);
intercept != this->m_focusIntercepts.end())
{
*intercept->second.forDirection(direction) = dest;
} else {
FocusOverride override;
*override.forDirection(direction) = dest;
this->m_focusIntercepts.insert({src, override});
}
}
@ -1337,7 +1752,7 @@ bool Hy3Layout::shouldRenderSelected(CWindow* window) {
case Hy3NodeType::Group: {
auto* node = this->getNodeFromWindow(window);
if (node == nullptr) return false;
return focused->data.as_group.hasChild(node);
return focused->hasChild(node);
}
default: return false;
}
@ -1566,19 +1981,6 @@ void Hy3Layout::applyNodeDataToWindow(Hy3Node* node, bool no_animation) {
}
}
bool shiftIsForward(ShiftDirection direction) {
return direction == ShiftDirection::Right || direction == ShiftDirection::Down;
}
bool shiftIsVertical(ShiftDirection direction) {
return direction == ShiftDirection::Up || direction == ShiftDirection::Down;
}
bool shiftMatchesLayout(Hy3GroupLayout layout, ShiftDirection direction) {
return (layout == Hy3GroupLayout::SplitV && shiftIsVertical(direction))
|| (layout != Hy3GroupLayout::SplitV && !shiftIsVertical(direction));
}
Hy3Node* Hy3Layout::shiftOrGetFocus(
Hy3Node& node,
ShiftDirection direction,
@ -1661,10 +2063,14 @@ Hy3Node* Hy3Layout::shiftOrGetFocus(
std::list<Hy3Node*>::iterator insert;
if (break_origin == parent_group.children.front() && !shiftIsForward(direction)) {
if (!shift) return nullptr;
if (!shift) {
return nullptr;
}
insert = parent_group.children.begin();
} else if (break_origin == parent_group.children.back() && shiftIsForward(direction)) {
if (!shift) return nullptr;
if (!shift) {
return nullptr;
}
insert = parent_group.children.end();
} else {
auto& group_data = target_group->data.as_group;
@ -1686,14 +2092,19 @@ Hy3Node* Hy3Layout::shiftOrGetFocus(
if (shiftIsForward(direction)) insert = iter;
else insert = std::next(iter);
}
} else return (*iter)->getFocusedNode();
} else {
return (*iter)->getFocusedNode();
}
} else {
// break into neighboring groups until we hit a window
while (true) {
target_group = *iter;
auto& group_data = target_group->data.as_group;
if (group_data.children.empty()) return nullptr; // in theory this would never happen
if (group_data.children.empty()) {
// in theory this would never happen
return nullptr;
}
bool shift_after = false;
@ -1779,7 +2190,7 @@ Hy3Node* Hy3Layout::shiftOrGetFocus(
if (old_parent != nullptr) {
auto& group = old_parent->data.as_group;
if (old_parent->parent != nullptr && group.ephemeral && group.children.size() == 1
&& !group.hasChild(&node))
&& !old_parent->hasChild(&node))
{
Hy3Node::swallowGroups(old_parent);
}

View file

@ -1,4 +1,11 @@
#pragma once
#include <list>
#include <map>
#include <set>
#include <hyprland/src/layout/IHyprLayout.hpp>
#include "BitFlag.hpp"
class Hy3Layout;
@ -8,11 +15,6 @@ enum class GroupEphemeralityOption {
ForceEphemeral,
};
#include <list>
#include <set>
#include <hyprland/src/layout/IHyprLayout.hpp>
enum class ShiftDirection {
Left,
Up,
@ -20,10 +22,19 @@ enum class ShiftDirection {
Right,
};
enum class SearchDirection { None, Forwards, Backwards };
enum class Axis { None, Horizontal, Vertical };
enum class Layer { None = 0, Tiled = 1 << 0, Floating = 1 << 1 };
inline Layer operator|(Layer a, Layer b) { return static_cast<Layer>((int) a | (int) b); }
inline Layer operator&(Layer a, Layer b) { return static_cast<Layer>((int) a & (int) b); }
#include "Hy3Node.hpp"
#include "TabGroup.hpp"
#include "conversions.hpp"
enum class FocusShift {
Top,
@ -67,11 +78,31 @@ enum class ExpandFullscreenOption {
MaximizeAsFullscreen,
};
struct FocusOverride {
Hy3Node* left = nullptr;
Hy3Node* up = nullptr;
Hy3Node* right = nullptr;
Hy3Node* down = nullptr;
Hy3Node** forDirection(ShiftDirection direction) {
switch (direction) {
case ShiftDirection::Left: return &left;
case ShiftDirection::Up: return &up;
case ShiftDirection::Right: return &right;
case ShiftDirection::Down: return &down;
default: UNREACHABLE();
}
}
bool isEmpty() { return !(left || right || up || down); }
};
class Hy3Layout: public IHyprLayout {
public:
virtual void onWindowCreated(CWindow*, eDirection = DIRECTION_DEFAULT);
virtual void onWindowCreatedTiling(CWindow*, eDirection = DIRECTION_DEFAULT);
virtual void onWindowRemovedTiling(CWindow*);
virtual void onWindowRemovedFloating(CWindow*);
virtual void onWindowFocusChange(CWindow*);
virtual bool isWindowTiled(CWindow*);
virtual void recalculateMonitor(const int& monitor_id);
@ -110,13 +141,14 @@ public:
void changeGroupEphemeralityOn(Hy3Node&, bool ephemeral);
void shiftNode(Hy3Node&, ShiftDirection, bool once, bool visible);
void shiftWindow(int workspace, ShiftDirection, bool once, bool visible);
void shiftFocus(int workspace, ShiftDirection, bool visible);
void shiftFocus(int workspace, ShiftDirection, bool visible, BitFlag<Layer> = Layer::None);
void moveNodeToWorkspace(int origin, std::string wsname, bool follow);
void changeFocus(int workspace, FocusShift);
void focusTab(int workspace, TabFocus target, TabFocusMousePriority, bool wrap_scroll, int index);
void setNodeSwallow(int workspace, SetSwallowOption);
void killFocusedNode(int workspace);
void expand(int workspace, ExpandOption, ExpandFullscreenOption);
void resizeNode(const Vector2D& delta, eRectCorner corner, Hy3Node* node);
bool shouldRenderSelected(CWindow*);
@ -138,6 +170,10 @@ public:
private:
Hy3Node* getNodeFromWindow(CWindow*);
void applyNodeDataToWindow(Hy3Node*, bool no_animation = false);
void shiftFocusToMonitor(ShiftDirection direction);
std::unordered_map<CWindow*, FocusOverride> m_focusIntercepts;
Hy3Node* getFocusOverride(CWindow* src, ShiftDirection direction);
void setFocusOverride(CWindow* src, ShiftDirection direction, Hy3Node* dest);
// if shift is true, shift the window in the given direction, returning
// nullptr, if shift is false, return the window in the given direction or
@ -147,6 +183,7 @@ private:
void updateAutotileWorkspaces();
bool shouldAutotileWorkspace(int);
void resizeNode(Hy3Node*, Vector2D, ShiftDirection resize_edge_x, ShiftDirection resize_edge_y);
void focusMonitor(CMonitor*);
struct {
std::string raw_workspaces;

View file

@ -1,3 +1,4 @@
#include <assert.h>
#include <sstream>
#include <hyprland/src/Compositor.hpp>
@ -33,18 +34,6 @@ Hy3GroupData::~Hy3GroupData() {
if (this->tab_bar != nullptr) this->tab_bar->bar.beginDestroy();
}
bool Hy3GroupData::hasChild(Hy3Node* 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 Hy3GroupData::collapseExpansions() {
if (this->expand_focused == ExpandFocusType::NotExpanded) return;
this->expand_focused = ExpandFocusType::NotExpanded;
@ -195,20 +184,17 @@ void markGroupFocusedRecursive(Hy3GroupData& 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;
// undo decos for root focus
auto* root = node;
while (root->parent != nullptr) {
root->parent->data.as_group.focused_child = root;
root->parent->data.as_group.group_focused = false;
root = root->parent;
}
root->updateDecos();
@ -842,24 +828,6 @@ Hy3Node* Hy3Node::getImmediateSibling(ShiftDirection direction) {
return *list_sibling;
}
Axis getAxis(Hy3GroupLayout layout) {
switch (layout) {
case Hy3GroupLayout::SplitH: return Axis::Horizontal;
case Hy3GroupLayout::SplitV: return Axis::Vertical;
default: return Axis::None;
}
}
Axis getAxis(ShiftDirection direction) {
switch (direction) {
case ShiftDirection::Left:
case ShiftDirection::Right: return Axis::Horizontal;
case ShiftDirection::Down:
case ShiftDirection::Up: return Axis::Vertical;
default: return Axis::None;
}
}
Hy3Node* Hy3Node::findNeighbor(ShiftDirection direction) {
auto current_node = this;
Hy3Node* sibling = nullptr;
@ -894,6 +862,8 @@ int directionToIteratorIncrement(ShiftDirection direction) {
}
}
Vector2D Hy3Node::middle() { return this->position + this->size / 2.f; }
void Hy3Node::resize(ShiftDirection direction, double delta, bool no_animation) {
auto& parent_node = this->parent;
auto& containing_group = parent_node->data.as_group;
@ -926,6 +896,13 @@ void Hy3Node::resize(ShiftDirection direction, double delta, bool no_animation)
neighbor->size_ratio = requested_neighbor_size_ratio;
parent_node->recalcSizePosRecursive(no_animation);
} else {
hy3_log(
WARN,
"Requested size ratio {} or {} out of bounds, ignoring",
requested_size_ratio,
requested_neighbor_size_ratio
);
}
}
}
@ -949,3 +926,19 @@ void Hy3Node::swapData(Hy3Node& a, Hy3Node& b) {
}
}
}
bool Hy3Node::hasChild(Hy3Node* node) {
if (this->data.type == Hy3NodeType::Window) return false;
auto n = node;
while (n != nullptr && n->parent != this) n = n->parent;
return n != nullptr;
}
Hy3Node* Hy3Node::getRoot() {
Hy3Node* maybeRoot = this;
while (maybeRoot->parent) maybeRoot = maybeRoot->parent;
return maybeRoot;
}

View file

@ -10,6 +10,7 @@ enum class Hy3GroupLayout;
#include "Hy3Layout.hpp"
#include "TabGroup.hpp"
#include "conversions.hpp"
enum class Hy3GroupLayout {
SplitH,
@ -42,7 +43,6 @@ struct Hy3GroupData {
Hy3GroupData(Hy3GroupLayout layout);
~Hy3GroupData();
bool hasChild(Hy3Node* child);
void collapseExpansions();
void setLayout(Hy3GroupLayout layout);
void setEphemeral(GroupEphemeralityOption ephemeral);
@ -98,9 +98,11 @@ struct Hy3Node {
CWindow* bringToTop();
void markFocused();
void raiseToTop();
Vector2D middle();
Hy3Node* getFocusedNode(bool ignore_group_focus = false, bool stop_at_expanded = false);
Hy3Node* findNeighbor(ShiftDirection);
Hy3Node* getImmediateSibling(ShiftDirection);
Hy3Node* getRoot();
void resize(ShiftDirection, double, bool no_animation = false);
bool isIndirectlyFocused();
Hy3Node& getExpandActor();
@ -116,6 +118,7 @@ struct Hy3Node {
Hy3Node* findNodeForTabGroup(Hy3TabGroup&);
void appendAllWindows(std::vector<CWindow*>&);
bool hasChild(Hy3Node* child);
std::string debugNode();
// Remove this node from its parent, deleting the parent if it was
@ -131,3 +134,39 @@ struct Hy3Node {
static bool swallowGroups(Hy3Node* into);
static void swapData(Hy3Node&, Hy3Node&);
};
struct Distance {
double primary_axis;
double secondary_axis;
Distance() = default;
Distance(ShiftDirection direction, Vector2D from, Vector2D to) {
auto dist = from - to;
primary_axis = getAxis(direction) == Axis::Horizontal ? dist.x : dist.y;
secondary_axis = getAxis(direction) == Axis::Horizontal ? dist.y : dist.x;
}
bool operator<(Distance other) {
return signbit(primary_axis) == signbit(other.primary_axis)
&& (abs(primary_axis) < abs(other.primary_axis)
|| (primary_axis == other.primary_axis
&& abs(secondary_axis) < abs(other.secondary_axis)));
}
bool operator>(Distance other) {
return signbit(primary_axis) == signbit(other.primary_axis)
&& (abs(primary_axis) > abs(other.primary_axis)
|| (primary_axis == other.primary_axis
&& abs(secondary_axis) > abs(other.secondary_axis)));
}
bool isSameDirection(Distance other) {
return signbit(primary_axis) == signbit(other.primary_axis);
}
bool isInDirection(ShiftDirection direction) {
return std::signbit(primary_axis)
== (getSearchDirection(direction) == SearchDirection::Forwards);
}
};

39
src/conversions.cpp Normal file
View file

@ -0,0 +1,39 @@
#include "Hy3Node.hpp"
Axis getAxis(Hy3GroupLayout layout) {
switch (layout) {
case Hy3GroupLayout::SplitH: return Axis::Horizontal;
case Hy3GroupLayout::SplitV: return Axis::Vertical;
default: return Axis::None;
}
}
Axis getAxis(ShiftDirection direction) {
switch (direction) {
case ShiftDirection::Left:
case ShiftDirection::Right: return Axis::Horizontal;
case ShiftDirection::Down:
case ShiftDirection::Up: return Axis::Vertical;
default: return Axis::None;
}
}
SearchDirection getSearchDirection(ShiftDirection direction) {
switch (direction) {
case ShiftDirection::Left:
case ShiftDirection::Up: return SearchDirection::Backwards;
case ShiftDirection::Right:
case ShiftDirection::Down: return SearchDirection::Forwards;
default: return SearchDirection::None;
}
}
char directionToChar(ShiftDirection direction) {
switch (direction) {
case ShiftDirection::Left: return 'l';
case ShiftDirection::Up: return 'u';
case ShiftDirection::Down: return 'd';
case ShiftDirection::Right: return 'r';
default: return 'r';
}
}

6
src/conversions.hpp Normal file
View file

@ -0,0 +1,6 @@
#include "Hy3Node.hpp"
Axis getAxis(Hy3GroupLayout);
Axis getAxis(ShiftDirection);
SearchDirection getSearchDirection(ShiftDirection);
char directionToChar(ShiftDirection);

View file

@ -8,6 +8,7 @@
int workspace_for_action(bool allow_fullscreen = false) {
if (g_pLayoutManager->getCurrentLayout() != g_Hy3Layout.get()) return -1;
if (!g_pCompositor->m_pLastMonitor) return -1;
int workspace_id = g_pCompositor->m_pLastMonitor->activeWorkspace;
@ -83,6 +84,14 @@ std::optional<ShiftDirection> parseShiftArg(std::string arg) {
else return {};
}
std::optional<BitFlag<Layer>> parseLayerArg(std::string arg) {
if (arg == "same" || arg == "samelayer") return Layer::None;
else if (arg == "tiled") return Layer::Tiled;
else if (arg == "floating") return Layer::Floating;
else if (arg == "all" || arg == "any") return Layer::Tiled | Layer::Floating;
else return {};
}
void dispatch_movewindow(std::string value) {
int workspace = workspace_for_action();
if (workspace == -1) return;
@ -113,9 +122,24 @@ void dispatch_movefocus(std::string value) {
if (workspace == -1) return;
auto args = CVarList(value);
std::optional<BitFlag<Layer>> layerArg;
if (auto shift = parseShiftArg(args[0])) {
g_Hy3Layout->shiftFocus(workspace, shift.value(), args[1] == "visible");
bool visible;
BitFlag<Layer> layers;
for (auto arg: args) {
if (arg == "visible") visible = true;
else if ((layerArg = parseLayerArg(arg))) layers |= layerArg.value();
}
if (!layerArg) {
const static auto default_movefocus_layer =
ConfigValue<Hyprlang::STRING>("plugin:hy3:default_movefocus_layer");
if ((layerArg = parseLayerArg(*default_movefocus_layer))) layers |= layerArg.value();
}
g_Hy3Layout->shiftFocus(workspace, shift.value(), visible, layers);
}
}
@ -238,14 +262,26 @@ void dispatch_debug(std::string arg) {
if (workspace == -1) return;
auto* root = g_Hy3Layout->getWorkspaceRootGroup(workspace);
if (workspace == -1) {
if (root == nullptr) {
hy3_log(LOG, "DEBUG NODES: no nodes on workspace");
} else {
hy3_log(LOG, "DEBUG NODES\n{}", root->debugNode().c_str());
}
}
void dispatch_resizenode(std::string value) {
int workspace = workspace_for_action();
if (workspace == -1) return;
auto* node = g_Hy3Layout->getWorkspaceFocusedNode(workspace, false, true);
const auto delta = g_pCompositor->parseWindowVectorArgsRelative(value, Vector2D(0, 0));
hy3_log(LOG, "resizeNode: node: {:x}, delta: {:X}", (uintptr_t) node, delta);
g_Hy3Layout->resizeNode(delta, CORNER_NONE, node);
}
void registerDispatchers() {
HyprlandAPI::addDispatcher(PHANDLE, "hy3:resizenode", dispatch_resizenode);
HyprlandAPI::addDispatcher(PHANDLE, "hy3:makegroup", dispatch_makegroup);
HyprlandAPI::addDispatcher(PHANDLE, "hy3:changegroup", dispatch_changegroup);
HyprlandAPI::addDispatcher(PHANDLE, "hy3:setephemeral", dispatch_setephemeral);

View file

@ -39,6 +39,9 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
CONF("group_inset", INT, 10);
CONF("special_scale_factor", FLOAT, 0.8);
CONF("tab_first_window", INT, 0);
CONF("kbd_shift_delta", INT, 20);
CONF("default_movefocus_layer", STRING, "samelayer");
CONF("focus_obscured_windows_policy", INT, 2);
// tabs
CONF("tabs:height", INT, 15);