mirror of
				https://github.com/ItsDrike/hyprland-dwindle-autogroup.git
				synced 2025-11-04 01:16:36 +00:00 
			
		
		
		
	Merge branch 'main' into better-ungroup
This commit is contained in:
		
						commit
						e78317fdeb
					
				
					 6 changed files with 361 additions and 217 deletions
				
			
		
							
								
								
									
										9
									
								
								include/func_finder.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								include/func_finder.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
#include "globals.hpp"
 | 
			
		||||
 | 
			
		||||
/* Find function pointer from the Hyprland binary by it's name and demangledName
 | 
			
		||||
 *
 | 
			
		||||
 * \param[in] name              Simple name of the function (i.e. createGroup)
 | 
			
		||||
 * \param[in] demangledName     Demangled name of the function (i.e. CWindow::createGroup())
 | 
			
		||||
 * \return  Raw pointer to the found function
 | 
			
		||||
 */
 | 
			
		||||
void* findHyprlandFunction(const std::string& name, const std::string& demangledName);
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +1,19 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <hyprland/src/layout/DwindleLayout.hpp>
 | 
			
		||||
#include <hyprland/src/plugins/HookSystem.hpp>
 | 
			
		||||
#include <hyprland/src/plugins/PluginAPI.hpp>
 | 
			
		||||
 | 
			
		||||
const CColor s_notifyColor = {0x61 / 255.0f, 0xAF / 255.0f, 0xEF / 255.0f, 1.0f}; // RGBA
 | 
			
		||||
const PLUGIN_DESCRIPTION_INFO s_pluginDescription = {"dwindle-autogroup", "Dwindle Autogroup", "ItsDrike", "1.0"};
 | 
			
		||||
 | 
			
		||||
inline HANDLE PHANDLE = nullptr;
 | 
			
		||||
 | 
			
		||||
typedef void* (*createGroupFuncT)(CWindow*);
 | 
			
		||||
inline CFunctionHook* g_pCreateGroupHook = nullptr;
 | 
			
		||||
 | 
			
		||||
typedef void* (*destroyGroupFuncT)(CWindow*);
 | 
			
		||||
inline CFunctionHook* g_pDestroyGroupHook = nullptr;
 | 
			
		||||
 | 
			
		||||
typedef SDwindleNodeData* (*nodeFromWindowFuncT)(void*, CWindow*);
 | 
			
		||||
inline nodeFromWindowFuncT g_pNodeFromWindow = nullptr;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										9
									
								
								include/plugin.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								include/plugin.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
#include "globals.hpp"
 | 
			
		||||
 | 
			
		||||
/* New custom function replacing the original CWindow::createGroup function
 | 
			
		||||
 */
 | 
			
		||||
void newCreateGroup(CWindow*);
 | 
			
		||||
 | 
			
		||||
/* New custom function replacing the original CWindow::createGroup function
 | 
			
		||||
 */
 | 
			
		||||
void newDestroyGroup(CWindow*);
 | 
			
		||||
							
								
								
									
										62
									
								
								src/func_finder.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/func_finder.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,62 @@
 | 
			
		|||
#include "func_finder.hpp"
 | 
			
		||||
 | 
			
		||||
/* Debug function for converting vector of strings to pretty-printed representation
 | 
			
		||||
 *
 | 
			
		||||
 * \param[in]   Vector of strings
 | 
			
		||||
 * \return      Pretty printed single-line representation of the vector
 | 
			
		||||
 */
 | 
			
		||||
std::string vectorToString(const std::vector<std::string>& vec)
 | 
			
		||||
{
 | 
			
		||||
    std::ostringstream oss;
 | 
			
		||||
    oss << "[";
 | 
			
		||||
 | 
			
		||||
    for (size_t i = 0; i < vec.size(); ++i) {
 | 
			
		||||
        oss << std::quoted(vec[i]);
 | 
			
		||||
 | 
			
		||||
        // Add a comma after each element except the last one
 | 
			
		||||
        if (i < vec.size() - 1) {
 | 
			
		||||
            oss << ", ";
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    oss << "]";
 | 
			
		||||
    return oss.str();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void* findHyprlandFunction(const std::string& name, const std::string& demangledName)
 | 
			
		||||
{
 | 
			
		||||
    const auto METHODS = HyprlandAPI::findFunctionsByName(PHANDLE, name);
 | 
			
		||||
 | 
			
		||||
    if (METHODS.size() == 0) {
 | 
			
		||||
        Debug::log(
 | 
			
		||||
            ERR, "[dwindle-autogroup] Function {} wasn't found in Hyprland binary! This function's signature probably changed, report this", name);
 | 
			
		||||
        HyprlandAPI::addNotification(PHANDLE, "[dwindle-autogroup] Initialization failed! " + name + " function wasn't found", s_notifyColor, 10000);
 | 
			
		||||
        return nullptr;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void* res = nullptr;
 | 
			
		||||
    for (auto& method : METHODS) {
 | 
			
		||||
        if (method.demangled == demangledName) {
 | 
			
		||||
            res = method.address;
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    // Use std::transform to extract the demangled strings from function matches
 | 
			
		||||
    std::vector<std::string> demangledStrings;
 | 
			
		||||
    std::transform(METHODS.begin(), METHODS.end(), std::back_inserter(demangledStrings), [](const SFunctionMatch& match) { return match.demangled; });
 | 
			
		||||
 | 
			
		||||
    if (res == nullptr) {
 | 
			
		||||
        Debug::log(ERR,
 | 
			
		||||
                   "[dwindle-autogroup] Demangled function {} wasn't found in matches for function name {} in Hyprland binary! This function's "
 | 
			
		||||
                   "signature probably "
 | 
			
		||||
                   "changed, report this. Found matches: {}",
 | 
			
		||||
                   demangledName,
 | 
			
		||||
                   name,
 | 
			
		||||
                   vectorToString(demangledStrings));
 | 
			
		||||
        HyprlandAPI::addNotification(
 | 
			
		||||
            PHANDLE, "[dwindle-autogroup] Initialization failed! " + name + " function (demangled) wasn't found", s_notifyColor, 10000);
 | 
			
		||||
        return nullptr;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return res;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										258
									
								
								src/main.cpp
									
										
									
									
									
								
							
							
						
						
									
										258
									
								
								src/main.cpp
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,191 +1,6 @@
 | 
			
		|||
#include "func_finder.hpp"
 | 
			
		||||
#include "globals.hpp"
 | 
			
		||||
 | 
			
		||||
#include <hyprland/src/Compositor.hpp>
 | 
			
		||||
#include <hyprland/src/Window.hpp>
 | 
			
		||||
#include <hyprland/src/layout/DwindleLayout.hpp>
 | 
			
		||||
#include <hyprland/src/layout/IHyprLayout.hpp>
 | 
			
		||||
#include <hyprland/src/managers/KeybindManager.hpp>
 | 
			
		||||
#include <hyprland/src/managers/LayoutManager.hpp>
 | 
			
		||||
#include <hyprland/src/render/decorations/CHyprGroupBarDecoration.hpp>
 | 
			
		||||
 | 
			
		||||
const CColor s_pluginColor = {0x61 / 255.0f, 0xAF / 255.0f, 0xEF / 255.0f, 1.0f};
 | 
			
		||||
 | 
			
		||||
inline std::function<void(std::string)> originalToggleGroup = nullptr;
 | 
			
		||||
 | 
			
		||||
typedef SDwindleNodeData* (*nodeFromWindowT)(void*, CWindow*);
 | 
			
		||||
inline nodeFromWindowT g_pNodeFromWindow = nullptr;
 | 
			
		||||
 | 
			
		||||
void collectChildNodes(std::deque<SDwindleNodeData*>* pDeque, SDwindleNodeData* node)
 | 
			
		||||
{
 | 
			
		||||
    if (node->isNode) {
 | 
			
		||||
        collectChildNodes(pDeque, node->children[0]);
 | 
			
		||||
        collectChildNodes(pDeque, node->children[1]);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        pDeque->emplace_back(node);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// This is partially from CKeybindManager::moveIntoGroup (dispatcher) function
 | 
			
		||||
/// but without making the new window focused.
 | 
			
		||||
void moveIntoGroup(CWindow* window, CWindow* groupRootWin)
 | 
			
		||||
{
 | 
			
		||||
    // Remove this window from being shown by layout (it will become a part of a group)
 | 
			
		||||
    g_pLayoutManager->getCurrentLayout()->onWindowRemoved(window);
 | 
			
		||||
 | 
			
		||||
    // Create a group bar decoration for the window
 | 
			
		||||
    // (if it's not already a group, in which case it should already have it)
 | 
			
		||||
    if (!window->m_sGroupData.pNextWindow)
 | 
			
		||||
        window->m_dWindowDecorations.emplace_back(std::make_unique<CHyprGroupBarDecoration>(window));
 | 
			
		||||
 | 
			
		||||
    groupRootWin->insertWindowToGroup(window);
 | 
			
		||||
 | 
			
		||||
    // Make sure to treat this window as hidden (will focus the group instead of this window
 | 
			
		||||
    // on request activate). This is the behavior CWindow::setGroupCurrent uses.
 | 
			
		||||
    window->setHidden(true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void collectGroupWindows(std::deque<CWindow*>* pDeque, CWindow* pWindow)
 | 
			
		||||
{
 | 
			
		||||
    CWindow* curr = pWindow;
 | 
			
		||||
    do {
 | 
			
		||||
        const auto PLAST = curr;
 | 
			
		||||
        pDeque->emplace_back(curr);
 | 
			
		||||
        curr = curr->m_sGroupData.pNextWindow;
 | 
			
		||||
    } while (curr != pWindow);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void groupDissolve(const SDwindleNodeData* PNODE, CHyprDwindleLayout* layout)
 | 
			
		||||
{
 | 
			
		||||
    CWindow* PWINDOW = PNODE->pWindow;
 | 
			
		||||
 | 
			
		||||
    // This group only has this single winodw
 | 
			
		||||
    if (PWINDOW->m_sGroupData.pNextWindow == PWINDOW) {
 | 
			
		||||
        Debug::log(LOG, "Ignoring autogroup on single window dissolve");
 | 
			
		||||
        originalToggleGroup("");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // We could just call originalToggleGroup function here, but that fucntion doesn't
 | 
			
		||||
    // respect the dwindle layout, and would just place the newly ungroupped windows
 | 
			
		||||
    // randomly throughout the worskapce, messing up the layout. So instead, we replicate
 | 
			
		||||
    // it's behavior here manually, taking care to disolve the groups nicely.
 | 
			
		||||
    Debug::log(LOG, "Dissolving group");
 | 
			
		||||
 | 
			
		||||
    std::deque<CWindow*> members;
 | 
			
		||||
    collectGroupWindows(&members, PWINDOW);
 | 
			
		||||
 | 
			
		||||
    // If the group head window is in fullscreen, unfullscreen it.
 | 
			
		||||
    // We need to have the window placed in the layout, to figure out where
 | 
			
		||||
    // to ungroup the rest of the windows.
 | 
			
		||||
    g_pCompositor->setWindowFullscreen(PWINDOW, false, FULLSCREEN_FULL);
 | 
			
		||||
 | 
			
		||||
    Debug::log(LOG, "Ungroupping Members");
 | 
			
		||||
 | 
			
		||||
    const bool GROUPSLOCKEDPREV = g_pKeybindManager->m_bGroupsLocked;
 | 
			
		||||
 | 
			
		||||
    for (auto& w : members) {
 | 
			
		||||
        w->m_sGroupData.pNextWindow = nullptr;
 | 
			
		||||
        w->setHidden(false);
 | 
			
		||||
 | 
			
		||||
        // Ask layout to create a new window for all windows that were in the group
 | 
			
		||||
        // except for the group head (already has a window).
 | 
			
		||||
        if (w->m_sGroupData.head) {
 | 
			
		||||
            Debug::log(LOG, "Ungroupping member head window");
 | 
			
		||||
            w->m_sGroupData.head = false;
 | 
			
		||||
 | 
			
		||||
            // Update the window decorations (removing group bar)
 | 
			
		||||
            // w->updateWindowDecos();
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            Debug::log(LOG, "Ungroupping member non-head window");
 | 
			
		||||
            g_pLayoutManager->getCurrentLayout()->onWindowCreatedTiling(w);
 | 
			
		||||
            // Focus the window that we just spawned, so that on the next iteration
 | 
			
		||||
            // the window created will be it's dwindle child node.
 | 
			
		||||
            // This allows the original group head to remain a parent window to all
 | 
			
		||||
            // of the other (groupped) nodes
 | 
			
		||||
            g_pCompositor->focusWindow(w);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    g_pKeybindManager->m_bGroupsLocked = GROUPSLOCKEDPREV;
 | 
			
		||||
 | 
			
		||||
    g_pCompositor->updateAllWindowsAnimatedDecorationValues();
 | 
			
		||||
 | 
			
		||||
    // Leave with the focus the original group (head) window
 | 
			
		||||
    g_pCompositor->focusWindow(PWINDOW);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void groupCreate(const SDwindleNodeData* PNODE, CHyprDwindleLayout* layout)
 | 
			
		||||
{
 | 
			
		||||
    const auto PWINDOW = PNODE->pWindow;
 | 
			
		||||
 | 
			
		||||
    const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(PNODE->workspaceID);
 | 
			
		||||
    if (PWORKSPACE->m_bHasFullscreenWindow) {
 | 
			
		||||
        Debug::log(LOG, "Ignoring autogroup on a fullscreen window");
 | 
			
		||||
        originalToggleGroup("");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!PNODE->pParent) {
 | 
			
		||||
        Debug::log(LOG, "Ignoring autogroup for a solitary window");
 | 
			
		||||
        originalToggleGroup("");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Debug::log(LOG, "Creating group");
 | 
			
		||||
    // Call the original toggleGroup function, and only do extra things afterwards
 | 
			
		||||
    originalToggleGroup("");
 | 
			
		||||
 | 
			
		||||
    std::deque<SDwindleNodeData*> newGroupMembers;
 | 
			
		||||
    collectChildNodes(&newGroupMembers, PNODE->pParent->children[0] == PNODE ? PNODE->pParent->children[1] : PNODE->pParent->children[0]);
 | 
			
		||||
 | 
			
		||||
    // Make sure one of the child nodes isn't itself a group
 | 
			
		||||
    for (auto& n : newGroupMembers) {
 | 
			
		||||
        if (n->pWindow->m_sGroupData.pNextWindow) {
 | 
			
		||||
            Debug::log(LOG, "Ignoring autogroup for nested groups");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Add all of the children nodes into the group
 | 
			
		||||
    for (auto& n : newGroupMembers) {
 | 
			
		||||
        auto window = n->pWindow;
 | 
			
		||||
        moveIntoGroup(window, PWINDOW);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void toggleGroup(std::string args)
 | 
			
		||||
{
 | 
			
		||||
    // We only care about group creations, not group disolves
 | 
			
		||||
    const auto PWINDOW = g_pCompositor->m_pLastWindow;
 | 
			
		||||
    if (!PWINDOW || !g_pCompositor->windowValidMapped(PWINDOW))
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    // Don't do anything if we're not on "dwindle" layout
 | 
			
		||||
    IHyprLayout* layout = g_pLayoutManager->getCurrentLayout();
 | 
			
		||||
    if (layout->getLayoutName() != "dwindle") {
 | 
			
		||||
        Debug::log(LOG, "Ignoring autogroup for non-dinwle layout");
 | 
			
		||||
        originalToggleGroup(args);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    CHyprDwindleLayout* cur_dwindle = (CHyprDwindleLayout*)layout;
 | 
			
		||||
 | 
			
		||||
    const auto PNODE = g_pNodeFromWindow(cur_dwindle, PWINDOW);
 | 
			
		||||
    if (!PNODE) {
 | 
			
		||||
        Debug::log(LOG, "Ignoring autogroup for floating window");
 | 
			
		||||
        originalToggleGroup(args);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (PWINDOW->m_sGroupData.pNextWindow) {
 | 
			
		||||
        groupDissolve(PNODE, cur_dwindle);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        groupCreate(PNODE, cur_dwindle);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
#include "plugin.hpp"
 | 
			
		||||
 | 
			
		||||
// Do NOT change this function.
 | 
			
		||||
APICALL EXPORT std::string PLUGIN_API_VERSION()
 | 
			
		||||
| 
						 | 
				
			
			@ -193,44 +8,53 @@ APICALL EXPORT std::string PLUGIN_API_VERSION()
 | 
			
		|||
    return HYPRLAND_API_VERSION;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
APICALL EXPORT void PLUGIN_EXIT()
 | 
			
		||||
{
 | 
			
		||||
    // Unhook the overridden functions and remove the hooks
 | 
			
		||||
    if (g_pCreateGroupHook) {
 | 
			
		||||
        g_pCreateGroupHook->unhook();
 | 
			
		||||
        HyprlandAPI::removeFunctionHook(PHANDLE, g_pCreateGroupHook);
 | 
			
		||||
        g_pCreateGroupHook = nullptr;
 | 
			
		||||
    }
 | 
			
		||||
    if (g_pDestroyGroupHook) {
 | 
			
		||||
        g_pDestroyGroupHook->unhook();
 | 
			
		||||
        HyprlandAPI::removeFunctionHook(PHANDLE, g_pDestroyGroupHook);
 | 
			
		||||
        g_pCreateGroupHook = nullptr;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Plugin unloading was successful
 | 
			
		||||
    HyprlandAPI::addNotification(PHANDLE, "[dwindle-autogroup] Unloaded successfully!", s_notifyColor, 5000);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle)
 | 
			
		||||
{
 | 
			
		||||
    PHANDLE = handle;
 | 
			
		||||
 | 
			
		||||
    // Get address of the private CHyprDwindleLayout::getNodeFromWindow member function, we'll need it in toggleGroup
 | 
			
		||||
    static const auto METHODS = HyprlandAPI::findFunctionsByName(PHANDLE, "getNodeFromWindow");
 | 
			
		||||
    g_pNodeFromWindow = (nodeFromWindowT)METHODS[1].address;
 | 
			
		||||
    Debug::log(LOG, "[dwindle-autogroup] Loading Hyprland functions");
 | 
			
		||||
 | 
			
		||||
    for (auto& method : METHODS) {
 | 
			
		||||
        if (method.demangled == "CHyprDwindleLayout::getNodeFromWindow(CWindow*)") {
 | 
			
		||||
            g_pNodeFromWindow = (nodeFromWindowT)method.address;
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    // Find pointers to functions by name (from the Hyprland binary)
 | 
			
		||||
    g_pNodeFromWindow = (nodeFromWindowFuncT)findHyprlandFunction("getNodeFromWindow", "\nCHyprDwindleLayout::getNodeFromWindow(CWindow*)");
 | 
			
		||||
    auto pCreateGroup = findHyprlandFunction("createGroup", "\nCWindow::createGroup()");
 | 
			
		||||
    auto pDestroyGroup = findHyprlandFunction("destroyGroup", "\nCWindow::destroyGroup()");
 | 
			
		||||
 | 
			
		||||
    // Return immediately if one of the functions wasn't found
 | 
			
		||||
    if (!g_pNodeFromWindow || !pCreateGroup || !pDestroyGroup) {
 | 
			
		||||
        // Set all of the global function pointers to NULL, to avoid any potential issues
 | 
			
		||||
        g_pNodeFromWindow = nullptr;
 | 
			
		||||
 | 
			
		||||
        return s_pluginDescription;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (g_pNodeFromWindow == nullptr) {
 | 
			
		||||
        Debug::log(ERR, "getNodeFromWindow funnction for dwindle layout wasn't found! This function's signature probably changed, report this");
 | 
			
		||||
        HyprlandAPI::addNotification(
 | 
			
		||||
            PHANDLE, "[dwindle-autogroup] Initialization failed!! getNodeFromWindow functio not found", s_pluginColor, 10000);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        originalToggleGroup = g_pKeybindManager->m_mDispatchers["togglegroup"];
 | 
			
		||||
        HyprlandAPI::addDispatcher(PHANDLE, "togglegroup", toggleGroup);
 | 
			
		||||
    Debug::log(LOG, "[dwindle-autogroup] Registering function hooks");
 | 
			
		||||
 | 
			
		||||
        HyprlandAPI::reloadConfig();
 | 
			
		||||
    // Register function hooks, for overriding the original group methods
 | 
			
		||||
    g_pCreateGroupHook = HyprlandAPI::createFunctionHook(PHANDLE, pCreateGroup, (void*)&newCreateGroup);
 | 
			
		||||
    g_pDestroyGroupHook = HyprlandAPI::createFunctionHook(PHANDLE, pDestroyGroup, (void*)&newDestroyGroup);
 | 
			
		||||
 | 
			
		||||
        HyprlandAPI::addNotification(PHANDLE, "[dwindle-autogroup] Initialized successfully!", s_pluginColor, 5000);
 | 
			
		||||
    }
 | 
			
		||||
    // Initialize the hooks, from now on, the original functions will be overridden
 | 
			
		||||
    g_pCreateGroupHook->hook();
 | 
			
		||||
    g_pDestroyGroupHook->hook();
 | 
			
		||||
 | 
			
		||||
    return {"dwindle-autogroup", "Dwindle Autogroup", "ItsDrike", "1.0"};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
APICALL EXPORT void PLUGIN_EXIT()
 | 
			
		||||
{
 | 
			
		||||
    // Since we added the "togglegroup" dispatcher ourselves, by default, the cleanup would just remove it
 | 
			
		||||
    // but we want to restore it back to the original function instead
 | 
			
		||||
    HyprlandAPI::removeDispatcher(PHANDLE, "togglegroup");
 | 
			
		||||
    g_pKeybindManager->m_mDispatchers["togglegroup"] = originalToggleGroup;
 | 
			
		||||
 | 
			
		||||
    HyprlandAPI::addNotification(PHANDLE, "[dwindle-autogroup] Unloaded successfully!", s_pluginColor, 5000);
 | 
			
		||||
    HyprlandAPI::addNotification(PHANDLE, "[dwindle-autogroup] Initialized successfully!", s_notifyColor, 5000);
 | 
			
		||||
    return s_pluginDescription;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										226
									
								
								src/plugin.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										226
									
								
								src/plugin.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,226 @@
 | 
			
		|||
#include "plugin.hpp"
 | 
			
		||||
#include "globals.hpp"
 | 
			
		||||
#include <hyprland/src/Compositor.hpp>
 | 
			
		||||
#include <hyprland/src/layout/DwindleLayout.hpp>
 | 
			
		||||
#include <hyprland/src/managers/LayoutManager.hpp>
 | 
			
		||||
#include <hyprland/src/plugins/PluginAPI.hpp>
 | 
			
		||||
#include <hyprland/src/render/decorations/CHyprGroupBarDecoration.hpp>
 | 
			
		||||
 | 
			
		||||
/*! Recursively collect all dwindle child nodes for given root node
 | 
			
		||||
 *
 | 
			
		||||
 * \param[out] pDeque   deque to store the found child nodes into
 | 
			
		||||
 * \param[in] pNode     Dwindle node which children should be collected
 | 
			
		||||
 */
 | 
			
		||||
void collectDwindleChildNodes(std::deque<SDwindleNodeData*>* pDeque, SDwindleNodeData* pNode)
 | 
			
		||||
{
 | 
			
		||||
    if (pNode->isNode) {
 | 
			
		||||
        collectDwindleChildNodes(pDeque, pNode->children[0]);
 | 
			
		||||
        collectDwindleChildNodes(pDeque, pNode->children[1]);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        pDeque->emplace_back(pNode);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Collect all windows that belong to the same group
 | 
			
		||||
 *
 | 
			
		||||
 * \param[out] pDeque   deque to store the found group windows into
 | 
			
		||||
 * \param[in] pWindow   any window that belongs to a group (doesn't have to be the group head window)
 | 
			
		||||
 */
 | 
			
		||||
void collectGroupWindows(std::deque<CWindow*>* pDeque, CWindow* pWindow)
 | 
			
		||||
{
 | 
			
		||||
    CWindow* curr = pWindow;
 | 
			
		||||
    do {
 | 
			
		||||
        const auto PLAST = curr;
 | 
			
		||||
        pDeque->emplace_back(curr);
 | 
			
		||||
        curr = curr->m_sGroupData.pNextWindow;
 | 
			
		||||
    } while (curr != pWindow);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Move given window into a group
 | 
			
		||||
 *
 | 
			
		||||
 * This is almost the same as CKeybindManager::moveWindowIntoGroup (dispatcher) function,
 | 
			
		||||
 * but without making the new window a group head and focused.
 | 
			
		||||
 *
 | 
			
		||||
 * \param[in] pWindow       Window to be inserted into a group
 | 
			
		||||
 * \param[in] pGroupWindow  Window that's a part of a group to insert the pWindow into
 | 
			
		||||
 */
 | 
			
		||||
void moveIntoGroup(CWindow* pWindow, CWindow* pGroupHeadWindow)
 | 
			
		||||
{
 | 
			
		||||
    const auto P_LAYOUT = g_pLayoutManager->getCurrentLayout();
 | 
			
		||||
 | 
			
		||||
    const auto* USE_CURR_POS = &g_pConfigManager->getConfigValuePtr("misc:group_insert_after_current")->intValue;
 | 
			
		||||
    CWindow* pGroupWindow = *USE_CURR_POS ? pGroupHeadWindow : pGroupHeadWindow->getGroupTail();
 | 
			
		||||
 | 
			
		||||
    // Remove the window from layout (will become a part of a group)
 | 
			
		||||
    P_LAYOUT->onWindowRemoved(pWindow);
 | 
			
		||||
 | 
			
		||||
    // Create a group bar decoration for the window we'll be inserting
 | 
			
		||||
    // (if it's not already a group, in which case it should already have it)
 | 
			
		||||
    if (!pWindow->m_sGroupData.pNextWindow)
 | 
			
		||||
        pWindow->m_dWindowDecorations.emplace_back(std::make_unique<CHyprGroupBarDecoration>(pWindow));
 | 
			
		||||
 | 
			
		||||
    pGroupWindow->insertWindowToGroup(pWindow);
 | 
			
		||||
    pGroupHeadWindow->updateWindowDecos();
 | 
			
		||||
    pWindow->setHidden(true);
 | 
			
		||||
 | 
			
		||||
    g_pLayoutManager->getCurrentLayout()->recalculateWindow(pGroupHeadWindow);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Check common pre-conditions for group creation/deletion and perform needed initializations
 | 
			
		||||
 *
 | 
			
		||||
 * \param[out] pDwindleLayout  Pointer to dwindle layout instance
 | 
			
		||||
 * \return  Necessary pre-conditions succeded?
 | 
			
		||||
 */
 | 
			
		||||
bool handleGroupOperation(CHyprDwindleLayout** pDwindleLayout)
 | 
			
		||||
{
 | 
			
		||||
    const auto P_LAYOUT = g_pLayoutManager->getCurrentLayout();
 | 
			
		||||
    if (P_LAYOUT->getLayoutName() != "dwindle") {
 | 
			
		||||
        Debug::log(LOG, "[dwindle-autogroup] Ignoring non-dwindle layout");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    *pDwindleLayout = dynamic_cast<CHyprDwindleLayout*>(P_LAYOUT);
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void newCreateGroup(CWindow* self)
 | 
			
		||||
{
 | 
			
		||||
    // Run the original function first, creating a "classical" group
 | 
			
		||||
    // with just the currently selected window in it
 | 
			
		||||
    ((createGroupFuncT)g_pCreateGroupHook->m_pOriginal)(self);
 | 
			
		||||
 | 
			
		||||
    // Only continue if the group really was created, as there are some pre-conditions to that.
 | 
			
		||||
    if (!self->m_sGroupData.pNextWindow) {
 | 
			
		||||
        Debug::log(LOG, "[dwindle-autogroup] Ignoring autogroup - invalid / non-group widnow");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Debug::log(LOG, "[dwindle-autogroup] Triggered createGroup for {:x}", self);
 | 
			
		||||
 | 
			
		||||
    // Obtain an instance of the dwindle layout, also run some general pre-conditions
 | 
			
		||||
    // for the plugin, quit now if they're not met.
 | 
			
		||||
    CHyprDwindleLayout* pDwindleLayout = nullptr;
 | 
			
		||||
    if (!handleGroupOperation(&pDwindleLayout))
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    Debug::log(LOG, "[dwindle-autogroup] Autogroupping dwindle child nodes");
 | 
			
		||||
 | 
			
		||||
    // Collect all child dwindle nodes, we'll want to add all of those into a group
 | 
			
		||||
    const auto P_DWINDLE_NODE = g_pNodeFromWindow(pDwindleLayout, self);
 | 
			
		||||
    std::deque<SDwindleNodeData*> p_dDwindleNodes;
 | 
			
		||||
    const auto P_SIBLING_NODE =
 | 
			
		||||
        P_DWINDLE_NODE->pParent->children[0] == P_DWINDLE_NODE ? P_DWINDLE_NODE->pParent->children[1] : P_DWINDLE_NODE->pParent->children[0];
 | 
			
		||||
    collectDwindleChildNodes(&p_dDwindleNodes, P_SIBLING_NODE);
 | 
			
		||||
 | 
			
		||||
    // Stop if one of the dwindle child nodes is already in a group
 | 
			
		||||
    for (auto& node : p_dDwindleNodes) {
 | 
			
		||||
        auto curWindow = node->pWindow;
 | 
			
		||||
        if (curWindow->m_sGroupData.pNextWindow) {
 | 
			
		||||
            Debug::log(LOG, "[dwindle-autogroup] Ignoring autogroup for nested groups: window {:x} is group", curWindow);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Debug::log(LOG, "[dwindle-autogroup] Found {} dwindle nodes to autogroup", p_dDwindleNodes.size());
 | 
			
		||||
 | 
			
		||||
    // Add all of the dwindle child node widnows into the group
 | 
			
		||||
    for (auto& node : p_dDwindleNodes) {
 | 
			
		||||
        auto curWindow = node->pWindow;
 | 
			
		||||
        Debug::log(LOG, "[dwindle-autogroup] Moving window {:x} into group", curWindow);
 | 
			
		||||
        moveIntoGroup(curWindow, self);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Debug::log(LOG, "[dwindle-autogroup] Autogroup done, {} child nodes moved", p_dDwindleNodes.size());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void newDestroyGroup(CWindow* self)
 | 
			
		||||
{
 | 
			
		||||
    // We can't use the original function here (other than for falling back)
 | 
			
		||||
    // as it removes the group head and then creates the new windows on the
 | 
			
		||||
    // layout. This often messes up the user layout of the windows.
 | 
			
		||||
    //
 | 
			
		||||
    // The goal of this function is to ungroup the windows such that they
 | 
			
		||||
    // only continue as children from the dwindle binary tree node the group
 | 
			
		||||
    // head was on.
 | 
			
		||||
 | 
			
		||||
    // Only continue if the window isn't in a group
 | 
			
		||||
    if (!self->m_sGroupData.pNextWindow) {
 | 
			
		||||
        Debug::log(LOG, "[dwindle-autogroup] Ignoring ungroup - invalid / non-group widnow");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Debug::log(LOG, "[dwindle-autogroup] Triggered destroyGroup for {:x}", self);
 | 
			
		||||
 | 
			
		||||
    // Obtain an instance of the dwindle layout, also run some general pre-conditions
 | 
			
		||||
    // for the plugin, fall back now if they're not met.
 | 
			
		||||
    CHyprDwindleLayout* pDwindleLayout = nullptr;
 | 
			
		||||
    if (!handleGroupOperation(&pDwindleLayout)) {
 | 
			
		||||
        ((createGroupFuncT)g_pDestroyGroupHook->m_pOriginal)(self);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::deque<CWindow*> dGroupWindows;
 | 
			
		||||
    collectGroupWindows(&dGroupWindows, self);
 | 
			
		||||
 | 
			
		||||
    // If the group head window is in fullscreen, unfullscreen it.
 | 
			
		||||
    // We need to have the window placed in the layout, to figure out where
 | 
			
		||||
    // to ungroup the rest of the windows.
 | 
			
		||||
    g_pCompositor->setWindowFullscreen(self, false, FULLSCREEN_FULL);
 | 
			
		||||
 | 
			
		||||
    Debug::log(LOG, "[dwindle-autogroup] Ungroupping {} windows", dGroupWindows.size());
 | 
			
		||||
 | 
			
		||||
    const bool GROUPS_LOCKED_PREV = g_pKeybindManager->m_bGroupsLocked;
 | 
			
		||||
    g_pKeybindManager->m_bGroupsLocked = true;
 | 
			
		||||
 | 
			
		||||
    for (auto& pWindow : dGroupWindows) {
 | 
			
		||||
        Debug::log(LOG, "[dwindle-autogroup] Ungroupping window {:x}", pWindow);
 | 
			
		||||
        pWindow->m_sGroupData.pNextWindow = nullptr;
 | 
			
		||||
        pWindow->m_sGroupData.head = false;
 | 
			
		||||
 | 
			
		||||
        // Current / Visible window (this isn't always the head)
 | 
			
		||||
        if (!pWindow->isHidden()) {
 | 
			
		||||
            Debug::log(LOG, "[dwindle-autogroup] -> Visible window ungroup");
 | 
			
		||||
 | 
			
		||||
            // This window is already visible in the layout, we don't need to create
 | 
			
		||||
            // a new layout window for it.
 | 
			
		||||
            //
 | 
			
		||||
            // The original destroyGroup removes the window from the layout here,
 | 
			
		||||
            // which is what causes the weird ungroupping behavior as this window
 | 
			
		||||
            // is then recreated, which spawns it in a potentially unexpected place
 | 
			
		||||
            // (often determined by the cursor position).
 | 
			
		||||
 | 
			
		||||
            // Update the window decorations (removing group bar)
 | 
			
		||||
            pWindow->updateWindowDecos();
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            pWindow->setHidden(false);
 | 
			
		||||
 | 
			
		||||
            g_pLayoutManager->getCurrentLayout()->onWindowCreatedTiling(pWindow);
 | 
			
		||||
            pWindow->updateWindowDecos();
 | 
			
		||||
 | 
			
		||||
            // Focus the window that we just spawned, so that on the next iteration
 | 
			
		||||
            // the window created will be it's dwindle child node.
 | 
			
		||||
            // This allows the original group head to remain a parent window to all
 | 
			
		||||
            // of the other (groupped) nodes.
 | 
			
		||||
            //
 | 
			
		||||
            // Note that this won't preserve the exact original layout of the group
 | 
			
		||||
            // but it will make sure all of the groupped windows will extend from
 | 
			
		||||
            // the dwindle node of the group head window. Preserving the original
 | 
			
		||||
            // layout isn't really possible, since new windows can be added into
 | 
			
		||||
            // groups after they were created.
 | 
			
		||||
            g_pCompositor->focusWindow(pWindow);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    g_pKeybindManager->m_bGroupsLocked = GROUPS_LOCKED_PREV;
 | 
			
		||||
 | 
			
		||||
    Debug::log(LOG, "[dwindle-autogroup] All windows ungroupped");
 | 
			
		||||
 | 
			
		||||
    g_pCompositor->updateAllWindowsAnimatedDecorationValues();
 | 
			
		||||
 | 
			
		||||
    // Leave with the focus the original (main) window
 | 
			
		||||
    g_pCompositor->focusWindow(self);
 | 
			
		||||
 | 
			
		||||
    Debug::log(LOG, "[dwindle-autogroup] Ungroupping done");
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue