mirror of
https://github.com/Trensa-Organization/hy3.git
synced 2025-03-15 10:43:40 +01:00
Implement focus_obscured_windows_policy
logic
This commit is contained in:
parent
19f3cb0b11
commit
f3dd53d76a
6 changed files with 171 additions and 34 deletions
14
README.md
14
README.md
|
@ -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
|
||||
|
||||
|
|
69
src/BitFlag.hpp
Normal file
69
src/BitFlag.hpp
Normal file
|
@ -0,0 +1,69 @@
|
|||
#ifndef BITFLAG
|
||||
#define BITFLAG
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
#endif
|
|
@ -10,6 +10,7 @@
|
|||
#include "SelectionHook.hpp"
|
||||
#include "globals.hpp"
|
||||
#include "conversions.hpp"
|
||||
#include "BitFlag.hpp"
|
||||
|
||||
std::unique_ptr<HOOK_CALLBACK_FN> renderHookPtr =
|
||||
std::make_unique<HOOK_CALLBACK_FN>(Hy3Layout::renderHook);
|
||||
|
@ -948,7 +949,7 @@ void shiftFloatingWindow(CWindow* window, ShiftDirection direction) {
|
|||
g_pCompositor->setActiveMonitor(new_monitor);
|
||||
|
||||
static auto* const allow_workspace_cycles =
|
||||
&g_pConfigManager->getConfigValuePtr("binds:allow_workspace_cycles")->intValue;
|
||||
&HyprlandAPI::getConfigValue(PHANDLE, "binds:allow_workspace_cycles")->intValue;
|
||||
if (*allow_workspace_cycles) new_workspace->rememberPrevWorkspace(old_workspace);
|
||||
}
|
||||
} else {
|
||||
|
@ -1064,22 +1065,30 @@ bool isObscured(Hy3Node* node) {
|
|||
bool isNotObscured(CWindow* window) { return !isObscured(window); }
|
||||
bool isNotObscured(Hy3Node* node) { return !isObscured(node); }
|
||||
|
||||
CWindow* getWindowInDirection(CWindow* source, ShiftDirection direction, bool considerFloating, bool considerTiled, bool considerOtherMonitors) {
|
||||
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 current_surface_box = source->getWindowMainSurfaceBox();
|
||||
auto target_distance = Distance { direction };
|
||||
|
||||
int focus_policy = *&HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:focus_obscured_windows_policy")->intValue;
|
||||
bool permit_obscured_windows = focus_policy == 0 || (focus_policy == 2 && layers_same_monitor.HasNot(Layer::Floating | Layer::Tiled));
|
||||
|
||||
// TODO: Don't assume that source window is on focused monitor
|
||||
// BUG: This will only find windows on the immediately neighbouring monitor, it won't find any on
|
||||
// the neighbour's neighbour if the immediate neighbour happens to be empty
|
||||
CMonitor* other_monitor = considerOtherMonitors ? g_pCompositor->getMonitorInDirection(directionToChar(direction))
|
||||
: nullptr;
|
||||
CMonitor* other_monitor = layers_other_monitors.HasAny(Layer::Floating | Layer::Tiled)
|
||||
? g_pCompositor->getMonitorInDirection(directionToChar(direction))
|
||||
: nullptr;
|
||||
|
||||
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 (w->m_iMonitorID == mon || (other_monitor && w->m_iMonitorID == other_monitor->ID))
|
||||
&& ((considerFloating && w->m_bIsFloating) || (considerTiled && !w->m_bIsFloating) || (w->m_iMonitorID != mon))
|
||||
&& (monitor_flags.Has(window_layer))
|
||||
&& w->m_bIsMapped
|
||||
&& w->m_iX11Type != 2
|
||||
&& !w->m_bNoFocus
|
||||
|
@ -1091,7 +1100,7 @@ CWindow* getWindowInDirection(CWindow* source, ShiftDirection direction, bool co
|
|||
auto w = pw.get();
|
||||
if(w != source && isCandidate(w)) {
|
||||
auto dist = Distance { direction, current_surface_box, w->getWindowMainSurfaceBox() };
|
||||
if((dist < target_distance || (target_distance.isNotInitialised() && dist.isInDirection(direction))) && isNotObscured(w) ) {
|
||||
if((dist < target_distance || (target_distance.isNotInitialised() && dist.isInDirection(direction))) && (permit_obscured_windows || isNotObscured(w)) ) {
|
||||
target_window = w;
|
||||
target_distance = dist;
|
||||
}
|
||||
|
@ -1101,12 +1110,11 @@ CWindow* getWindowInDirection(CWindow* source, ShiftDirection direction, bool co
|
|||
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
|
||||
// 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 && other_monitor && target_window->m_iMonitorID == other_monitor->ID) {
|
||||
auto new_workspace = g_pCompositor->getWorkspaceByID(other_monitor->activeWorkspace);
|
||||
if(new_workspace) {
|
||||
auto last_focused = new_workspace->getLastFocusedWindow();
|
||||
if(last_focused) {
|
||||
if (auto new_workspace = g_pCompositor->getWorkspaceByID(other_monitor->activeWorkspace)) {
|
||||
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 && target_bounds.x + target_bounds.w == last_focused_bounds.x + last_focused_bounds.w)
|
||||
|
@ -1119,7 +1127,6 @@ CWindow* getWindowInDirection(CWindow* source, ShiftDirection direction, bool co
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
return target_window;
|
||||
}
|
||||
|
||||
|
@ -1128,20 +1135,18 @@ void Hy3Layout::shiftFocusToMonitor(ShiftDirection direction) {
|
|||
if(target_monitor) this->focusMonitor(target_monitor);
|
||||
}
|
||||
|
||||
void Hy3Layout::shiftFocus(int workspace, ShiftDirection direction, bool visible) {
|
||||
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_window == nullptr) {
|
||||
if(source_window == nullptr || (source_workspace && source_workspace->m_bHasFullscreenWindow)) {
|
||||
shiftFocusToMonitor(direction);
|
||||
return;
|
||||
}
|
||||
|
||||
if(source_workspace == nullptr) return;
|
||||
|
||||
hy3_log(LOG,
|
||||
"shiftFocus: Source: {} ({}), workspace: {}, direction: {}, visible: {}",
|
||||
source_window,
|
||||
|
@ -1151,24 +1156,35 @@ void Hy3Layout::shiftFocus(int workspace, ShiftDirection direction, bool visible
|
|||
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;
|
||||
|
||||
int focus_policy = *&HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:focus_obscured_windows_policy")->intValue;
|
||||
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)
|
||||
source_node = source_window->m_bIsFloating ? getFocusOverride(source_window, direction)
|
||||
: getWorkspaceFocusedNode(workspace);
|
||||
if (eligible_layers.Has(Layer::Tiled)) {
|
||||
source_node = source_window->m_bIsFloating ? getFocusOverride(source_window, direction)
|
||||
: getWorkspaceFocusedNode(workspace);
|
||||
|
||||
// Get the closest node to the starting point
|
||||
if(source_node) {
|
||||
candidate_node = this->shiftOrGetFocus(*source_node, direction, false, false, visible);
|
||||
while(candidate_node && !isNotObscured(candidate_node)) {
|
||||
candidate_node = this->shiftOrGetFocus(*candidate_node, direction, false, false, visible);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool select_tiled_windows = source_window->m_bIsFloating && !candidate_node;
|
||||
BitFlag<Layer> this_monitor = eligible_layers & Layer::Floating;
|
||||
if(source_window->m_bIsFloating && !candidate_node) this_monitor |= (eligible_layers & Layer::Tiled);
|
||||
|
||||
// Find the closest window in the right direction. Only consider tiled windows or other monitors
|
||||
// if `shiftOrGetFocus` didn't provide a candidate.
|
||||
closest_window = getWindowInDirection(source_window, direction, true, select_tiled_windows, !candidate_node);
|
||||
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;
|
||||
|
@ -1189,8 +1205,7 @@ void Hy3Layout::shiftFocus(int workspace, ShiftDirection direction, bool visible
|
|||
}
|
||||
}
|
||||
|
||||
auto new_monitor_id = source_window->m_iMonitorID;
|
||||
|
||||
std::optional<uint64_t> new_monitor_id;
|
||||
if(focus_closest_window) {
|
||||
new_monitor_id = closest_window->m_iMonitorID;
|
||||
setFocusOverride(closest_window, direction, source_node);
|
||||
|
@ -1198,6 +1213,8 @@ void Hy3Layout::shiftFocus(int workspace, ShiftDirection direction, bool visible
|
|||
} 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();
|
||||
|
@ -1205,8 +1222,8 @@ void Hy3Layout::shiftFocus(int workspace, ShiftDirection direction, bool visible
|
|||
shiftFocusToMonitor(direction);
|
||||
}
|
||||
|
||||
if(new_monitor_id != source_window->m_iMonitorID) {
|
||||
if(auto *monitor = g_pCompositor->getMonitorFromID(new_monitor_id)) {
|
||||
if(new_monitor_id.has_value()) {
|
||||
if(auto *monitor = g_pCompositor->getMonitorFromID(new_monitor_id.value())) {
|
||||
g_pCompositor->setActiveMonitor(monitor);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include <hyprland/src/layout/IHyprLayout.hpp>
|
||||
#include <map>
|
||||
#include "BitFlag.hpp"
|
||||
|
||||
class Hy3Layout;
|
||||
|
||||
|
@ -28,6 +29,20 @@ enum class SearchDirection {
|
|||
|
||||
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"
|
||||
|
@ -125,7 +140,7 @@ 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);
|
||||
|
|
|
@ -84,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;
|
||||
|
@ -114,9 +122,23 @@ 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) {
|
||||
auto default_movefocus_layer = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hy3:default_movefocus_layer")->strValue;
|
||||
if((layerArg = parseLayerArg(*default_movefocus_layer))) layers = layerArg.value();
|
||||
}
|
||||
|
||||
g_Hy3Layout->shiftFocus(workspace, shift.value(), visible, layers);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
|
|||
CONF("tab_first_window", INT, 0);
|
||||
CONF("kbd_shift_delta", INT, 20);
|
||||
CONF("default_movefocus_layer", STRING, "samelayer");
|
||||
CONF("focus_obscured_windows", INT, 0);
|
||||
CONF("focus_obscured_windows_policy", INT, 2);
|
||||
|
||||
// tabs
|
||||
CONF("tabs:height", INT, 15);
|
||||
|
|
Loading…
Add table
Reference in a new issue