Implement focus_obscured_windows_policy logic

This commit is contained in:
Pete Appleton 2024-02-18 09:08:37 +00:00
parent 19f3cb0b11
commit f3dd53d76a
6 changed files with 171 additions and 34 deletions

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

69
src/BitFlag.hpp Normal file
View 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

View file

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

View file

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

View file

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

View file

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