Hypr/src/windowManager.cpp

2667 lines
No EOL
95 KiB
C++

#include "windowManager.hpp"
#include "./events/events.hpp"
#include <string.h>
xcb_visualtype_t* CWindowManager::setupColors(const int& desiredDepth) {
auto depthIter = xcb_screen_allowed_depths_iterator(Screen);
if (depthIter.data) {
for (; depthIter.rem; xcb_depth_next(&depthIter)) {
if (desiredDepth == 0 || desiredDepth == depthIter.data->depth) {
for (auto it = xcb_depth_visuals_iterator(depthIter.data); it.rem; xcb_visualtype_next(&it)) {
return it.data;
}
}
}
if (desiredDepth > 0) {
return setupColors(0);
}
}
return nullptr;
}
void CWindowManager::setupDepth() {
Depth = 24;
VisualType = setupColors(Depth);
}
void CWindowManager::createAndOpenAllPipes() {
system("mkdir -p /tmp/hypr");
system("cat \" \" > /tmp/hypr/hyprbarin");
system("cat \" \" > /tmp/hypr/hyprbarout");
system("cat \" \" > /tmp/hypr/hyprbarind");
system("cat \" \" > /tmp/hypr/hyprbaroutd");
}
void CWindowManager::updateRootCursor() {
if (xcb_cursor_context_new(DisplayConnection, Screen, &pointerContext) < 0) {
Debug::log(ERR, "Creating a cursor context failed!");
return;
}
pointerCursor = xcb_cursor_load_cursor(pointerContext, "left_ptr");
Debug::log(LOG, "Cursor created with ID " + std::to_string(pointerCursor));
// Set the cursor
uint32_t values[1] = { pointerCursor };
xcb_change_window_attributes(DisplayConnection, Screen->root, XCB_CW_CURSOR, values);
}
void CWindowManager::setupColormapAndStuff() {
VisualType = xcb_aux_find_visual_by_attrs(Screen, -1, 32); // Transparency by default
Depth = xcb_aux_get_depth_of_visual(Screen, VisualType->visual_id);
Colormap = xcb_generate_id(DisplayConnection);
const auto COOKIE = xcb_create_colormap(DisplayConnection, XCB_COLORMAP_ALLOC_NONE, Colormap, Screen->root, VisualType->visual_id);
const auto XERR = xcb_request_check(DisplayConnection, COOKIE);
if (XERR != NULL) {
Debug::log(ERR, "Error in setupColormapAndStuff! Code: " + std::to_string(XERR->error_code));
}
free(XERR);
}
void CWindowManager::setupRandrMonitors() {
XCBQUERYCHECK(RANDRVER, xcb_randr_query_version_reply(
DisplayConnection, xcb_randr_query_version(DisplayConnection, XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION), &errorRANDRVER), "RandR query failed!" );
free(RANDRVER);
Debug::log(LOG, "Setting up RandR! Query: v1.5.");
XCBQUERYCHECK(MONITORS, xcb_randr_get_monitors_reply(DisplayConnection, xcb_randr_get_monitors(DisplayConnection, Screen->root, true), &errorMONITORS), "Couldn't get monitors. " + std::to_string(errorMONITORS->error_code));
const auto MONITORNUM = xcb_randr_get_monitors_monitors_length(MONITORS);
Debug::log(LOG, "Found " + std::to_string(MONITORNUM) + " Monitor(s)!");
if (MONITORNUM < 1) {
// TODO: RandR 1.4 maybe for people with ancient hardware?
Debug::log(ERR, "RandR returned an invalid amount of monitors. Falling back to 1 monitor.");
return;
}
for (xcb_randr_monitor_info_iterator_t iterator = xcb_randr_get_monitors_monitors_iterator(MONITORS); iterator.rem; xcb_randr_monitor_info_next(&iterator)) {
const auto MONITORINFO = iterator.data;
// basically an XCBQUERYCHECK but with continue; as its not fatal
xcb_generic_error_t* error;
const auto ATOMNAME = xcb_get_atom_name_reply(DisplayConnection, xcb_get_atom_name(DisplayConnection, MONITORINFO->name), &error);
if (error != NULL) {
Debug::log(ERR, "Failed to get monitor info...");
free(error);
free(ATOMNAME);
continue;
}
free(error);
monitors.push_back(SMonitor());
const auto NAMELEN = xcb_get_atom_name_name_length(ATOMNAME);
const auto NAME = xcb_get_atom_name_name(ATOMNAME);
free(ATOMNAME);
for (int j = 0; j < NAMELEN; ++j) {
monitors[monitors.size() - 1].szName += NAME[j];
}
monitors[monitors.size() - 1].vecPosition = Vector2D(MONITORINFO->x, MONITORINFO->y);
monitors[monitors.size() - 1].vecSize = Vector2D(MONITORINFO->width, MONITORINFO->height);
monitors[monitors.size() - 1].primary = MONITORINFO->primary;
monitors[monitors.size() - 1].ID = monitors.size() - 1;
Debug::log(NONE, "Monitor " + monitors[monitors.size() - 1].szName + ": " + std::to_string(monitors[monitors.size() - 1].vecSize.x) + "x" + std::to_string(monitors[monitors.size() - 1].vecSize.y) +
", at " + std::to_string(monitors[monitors.size() - 1].vecPosition.x) + "," + std::to_string(monitors[monitors.size() - 1].vecPosition.y) + ", ID: " + std::to_string(monitors[monitors.size() - 1].ID));
}
free(MONITORS);
const auto EXTENSIONREPLY = xcb_get_extension_data(DisplayConnection, &xcb_randr_id);
if (!EXTENSIONREPLY->present)
Debug::log(ERR, "RandR extension missing");
else {
//listen for screen change events
xcb_randr_select_input(DisplayConnection, Screen->root, XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE);
RandREventBase = EXTENSIONREPLY->first_event;
Debug::log(LOG, "RandR first event base found at " + std::to_string(RandREventBase) + ".");
}
xcb_flush(DisplayConnection);
if (monitors.size() == 0) {
// RandR failed!
Debug::log(WARN, "RandR failed!");
monitors.clear();
#define TESTING_MON_AMOUNT 2
for (int i = 0; i < TESTING_MON_AMOUNT /* Testing on 3 monitors, RandR shouldnt fail on a real desktop */; ++i) {
monitors.push_back(SMonitor());
monitors[i].vecPosition = Vector2D(i * Screen->width_in_pixels / TESTING_MON_AMOUNT, 0);
monitors[i].vecSize = Vector2D(Screen->width_in_pixels / TESTING_MON_AMOUNT, Screen->height_in_pixels);
monitors[i].ID = i;
monitors[i].szName = "Screen" + std::to_string(i);
}
}
}
void CWindowManager::setupManager() {
setupColormapAndStuff();
EWMH::setupInitEWMH();
// ---- RANDR ----- //
setupRandrMonitors();
Debug::log(LOG, "RandR done.");
//
//
Values[0] = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE;
xcb_change_window_attributes_checked(DisplayConnection, Screen->root,
XCB_CW_EVENT_MASK, Values);
Debug::log(LOG, "Root done.");
ConfigManager::init();
Debug::log(LOG, "Config done.");
// Add workspaces to the monitors
for (long unsigned int i = 0; i < monitors.size(); ++i) {
CWorkspace protoWorkspace;
protoWorkspace.setID(i + 1);
protoWorkspace.setMonitor(i);
protoWorkspace.setHasFullscreenWindow(false);
workspaces.push_back(protoWorkspace);
activeWorkspaces.push_back(workspaces[i].getID());
}
Debug::log(LOG, "Workspace protos done.");
//
// ---- INIT THE THREAD FOR ANIM & CONFIG ---- //
// start its' update thread
Events::setThread();
Debug::log(LOG, "Thread (Parent) done.");
updateRootCursor();
CWorkspace scratchpad;
scratchpad.setID(SCRATCHPAD_ID);
for (long unsigned int i = 0; i < monitors.size(); ++i) {
if (monitors[i].primary)
scratchpad.setMonitor(monitors[i].ID);
}
workspaces.push_back(scratchpad);
Debug::log(LOG, "Finished setup!");
// TODO: EWMH
}
bool CWindowManager::handleEvent() {
if (xcb_connection_has_error(DisplayConnection))
return false;
xcb_flush(DisplayConnection);
// recieve the event. Blocks.
recieveEvent();
// refresh and apply the parameters of all dirty windows.
refreshDirtyWindows();
// Sanity checks
for (const auto active : activeWorkspaces) {
sanityCheckOnWorkspace(active);
}
// hide ewmh bars if fullscreen
processBarHiding();
// remove unused workspaces
cleanupUnusedWorkspaces();
// Process the queued warp
dispatchQueuedWarp();
// Update last window name
updateActiveWindowName();
// Update the bar with the freshest stuff
updateBarInfo();
// Update EWMH workspace info
EWMH::updateDesktops();
xcb_flush(DisplayConnection);
// Restore thread state
mainThreadBusy = false;
return true;
}
void CWindowManager::recieveEvent() {
const auto ev = xcb_wait_for_event(DisplayConnection);
if (ev != NULL) {
while (animationUtilBusy) {
; // wait for it to finish
}
for (auto& e : Events::ignoredEvents) {
if (e == ev->sequence) {
Debug::log(LOG, "Ignoring event type " + std::to_string(ev->response_type & ~0x80) + ".");
free(ev);
return;
}
}
if (Events::ignoredEvents.size() > 20)
Events::ignoredEvents.pop_front();
// Set thread state, halt animations until done.
mainThreadBusy = true;
// Read from the bar
if (!g_pWindowManager->statusBar)
IPCRecieveMessageM(m_sIPCBarPipeOut.szPipeName);
const uint8_t TYPE = XCB_EVENT_RESPONSE_TYPE(ev);
const auto EVENTCODE = ev->response_type & ~0x80;
switch (EVENTCODE) {
case XCB_ENTER_NOTIFY:
Events::eventEnter(ev);
Debug::log(LOG, "Event dispatched ENTER");
break;
case XCB_LEAVE_NOTIFY:
Events::eventLeave(ev);
Debug::log(LOG, "Event dispatched LEAVE");
break;
case XCB_DESTROY_NOTIFY:
Events::eventDestroy(ev);
Debug::log(LOG, "Event dispatched DESTROY");
break;
case XCB_UNMAP_NOTIFY:
Events::eventUnmapWindow(ev);
Debug::log(LOG, "Event dispatched UNMAP");
break;
case XCB_MAP_REQUEST:
Events::eventMapWindow(ev);
Debug::log(LOG, "Event dispatched MAP");
break;
case XCB_BUTTON_PRESS:
Events::eventButtonPress(ev);
Debug::log(LOG, "Event dispatched BUTTON_PRESS");
break;
case XCB_BUTTON_RELEASE:
Events::eventButtonRelease(ev);
Debug::log(LOG, "Event dispatched BUTTON_RELEASE");
break;
case XCB_MOTION_NOTIFY:
Events::eventMotionNotify(ev);
// Debug::log(LOG, "Event dispatched MOTION_NOTIFY"); // Spam!!
break;
case XCB_EXPOSE:
Events::eventExpose(ev);
Debug::log(LOG, "Event dispatched EXPOSE");
break;
case XCB_KEY_PRESS:
Events::eventKeyPress(ev);
Debug::log(LOG, "Event dispatched KEY_PRESS");
break;
case XCB_CLIENT_MESSAGE:
Events::eventClientMessage(ev);
Debug::log(LOG, "Event dispatched CLIENT_MESSAGE");
break;
case XCB_CONFIGURE_REQUEST:
Events::eventConfigure(ev);
Debug::log(LOG, "Event dispatched CONFIGURE");
break;
default:
if ((EVENTCODE != 14) && (EVENTCODE != 13) && (EVENTCODE != 0) && (EVENTCODE != 22) && (TYPE - RandREventBase != XCB_RANDR_SCREEN_CHANGE_NOTIFY))
Debug::log(WARN, "Unknown event: " + std::to_string(ev->response_type & ~0x80));
break;
}
if ((int)TYPE - RandREventBase == XCB_RANDR_SCREEN_CHANGE_NOTIFY && RandREventBase > 0) {
Events::eventRandRScreenChange(ev);
Debug::log(LOG, "Event dispatched RANDR_SCREEN_CHANGE");
}
free(ev);
}
}
void CWindowManager::processBarHiding() {
for (auto& w : windows) {
if (!w.getDock())
continue;
// get the dock's monitor
const auto& MON = monitors[w.getMonitor()];
// get the dock's current workspace
auto *const WORK = getWorkspaceByID(activeWorkspaces[MON.ID]);
if (!WORK)
continue; // weird if happens
if (WORK->getHasFullscreenWindow() && !w.getDockHidden()) {
const auto COOKIE = xcb_unmap_window(DisplayConnection, w.getDrawable());
Events::ignoredEvents.push_back(COOKIE.sequence);
w.setDockHidden(true);
}
else if (!WORK->getHasFullscreenWindow() && w.getDockHidden()) {
xcb_map_window(DisplayConnection, w.getDrawable());
w.setDockHidden(false);
}
}
}
void CWindowManager::cleanupUnusedWorkspaces() {
std::deque<CWorkspace> temp = workspaces;
workspaces.clear();
for (auto& work : temp) {
if (!isWorkspaceVisible(work.getID())) {
// check if it has any children
bool hasChildren = getWindowsOnWorkspace(work.getID()) > 0;
if (hasChildren) {
// Has windows opened on it.
workspaces.push_back(work);
}
} else {
// Foreground workspace
workspaces.push_back(work);
}
}
// Update bar info
updateBarInfo();
}
void CWindowManager::refreshDirtyWindows() {
const auto START = std::chrono::high_resolution_clock::now();
for(auto& window : windows) {
if (window.getDirty()) {
window.setDirty(false);
// Check if the window isn't a node or has the noInterventions prop
if (window.getChildNodeAID() != 0 || window.getNoInterventions() || window.getDock())
continue;
setEffectiveSizePosUsingConfig(&window);
const auto PWORKSPACE = getWorkspaceByID(window.getWorkspaceID());
// Fullscreen flag
bool bHasFullscreenWindow = PWORKSPACE ? PWORKSPACE->getHasFullscreenWindow() : false;
// first and foremost, let's check if the window isn't on a hidden workspace
// or an animated workspace
if (PWORKSPACE && (!isWorkspaceVisible(window.getWorkspaceID())
|| PWORKSPACE->getAnimationInProgress()) && !window.getPinned()) {
const auto MONITOR = getMonitorFromWindow(&window);
Values[0] = (int)(window.getFullscreen() ? MONITOR->vecPosition.x : window.getRealPosition().x) + (int)PWORKSPACE->getCurrentOffset().x;
Values[1] = (int)(window.getFullscreen() ? MONITOR->vecPosition.y : window.getRealPosition().y) + (int)PWORKSPACE->getCurrentOffset().y;
if (bHasFullscreenWindow && !window.getFullscreen() && (window.getUnderFullscreen() || !window.getIsFloating())) {
Values[0] = 150000;
Values[1] = 150000;
}
if (VECTORDELTANONZERO(window.getLastUpdatePosition(), Vector2D(Values[0], Values[1]))) {
xcb_configure_window(DisplayConnection, window.getDrawable(), XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, Values);
window.setLastUpdatePosition(Vector2D(Values[0], Values[1]));
}
// Set the size JIC.
Values[0] = window.getFullscreen() ? MONITOR->vecSize.x : (int)window.getEffectiveSize().x;
Values[1] = window.getFullscreen() ? MONITOR->vecSize.y : (int)window.getEffectiveSize().y;
if (VECTORDELTANONZERO(window.getLastUpdateSize(), Vector2D(Values[0], Values[1]))) {
xcb_configure_window(DisplayConnection, window.getDrawable(), XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, Values);
window.setLastUpdateSize(Vector2D(Values[0], Values[1]));
}
applyShapeToWindow(&window);
continue;
}
// or that it is not a non-fullscreen window in a fullscreen workspace thats under
if (bHasFullscreenWindow && !window.getFullscreen() && (window.getUnderFullscreen() || !window.getIsFloating()) && !window.getPinned()) {
Values[0] = 150000;
Values[1] = 150000;
if (VECTORDELTANONZERO(window.getLastUpdatePosition(), Vector2D(Values[0], Values[1]))) {
xcb_configure_window(DisplayConnection, window.getDrawable(), XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, Values);
window.setLastUpdatePosition(Vector2D(Values[0], Values[1]));
}
continue;
}
// Fullscreen window. No border, all screen.
// also do this when "layout:no_gaps_when_only" is set, but with a twist to enable the bar
if (window.getFullscreen() || (ConfigManager::getInt("layout:no_gaps_when_only") && getWindowsOnWorkspace(window.getWorkspaceID()) == 1)) {
Values[0] = 0;
xcb_configure_window(DisplayConnection, window.getDrawable(), XCB_CONFIG_WINDOW_BORDER_WIDTH, Values);
const auto MONITOR = getMonitorFromWindow(&window);
Values[0] = window.getFullscreen() ? (int)MONITOR->vecSize.x : MONITOR->vecSize.x - MONITOR->vecReservedTopLeft.x - MONITOR->vecReservedBottomRight.x;
Values[1] = window.getFullscreen() ? (int) MONITOR->vecSize.y : MONITOR->vecSize.y - MONITOR->vecReservedTopLeft.y - MONITOR->vecReservedBottomRight.y;
window.setEffectiveSize(Vector2D(Values[0], Values[1]));
Values[0] = window.getFullscreen() ? (int)MONITOR->vecPosition.x : MONITOR->vecPosition.x + MONITOR->vecReservedTopLeft.x;
Values[1] = window.getFullscreen() ? (int)MONITOR->vecPosition.y : MONITOR->vecPosition.y + MONITOR->vecReservedTopLeft.y;
window.setEffectivePosition(Vector2D(Values[0], Values[1]));
Values[0] = (int)window.getRealPosition().x;
Values[1] = (int)window.getRealPosition().y;
if (VECTORDELTANONZERO(window.getLastUpdatePosition(), Vector2D(Values[0], Values[1]))) {
const auto COOKIE = xcb_configure_window(DisplayConnection, window.getDrawable(), XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, Values);
window.setLastUpdatePosition(Vector2D(Values[0], Values[1]));
Events::ignoredEvents.push_back(COOKIE.sequence);
}
} else {
// Update the position because the border makes the window jump
// I have added the bordersize vec2d before in the setEffectiveSizePosUsingConfig function.
Values[0] = (int)window.getRealPosition().x - ConfigManager::getInt("border_size");
Values[1] = (int)window.getRealPosition().y - ConfigManager::getInt("border_size");
if (VECTORDELTANONZERO(window.getLastUpdatePosition(), Vector2D(Values[0], Values[1]))) {
const auto COOKIE = xcb_configure_window(DisplayConnection, window.getDrawable(), XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, Values);
window.setLastUpdatePosition(Vector2D(Values[0], Values[1]));
Events::ignoredEvents.push_back(COOKIE.sequence);
}
Values[0] = (int)ConfigManager::getInt("border_size");
xcb_configure_window(DisplayConnection, window.getDrawable(), XCB_CONFIG_WINDOW_BORDER_WIDTH, Values);
Values[0] = window.getRealBorderColor().getAsUint32();
xcb_change_window_attributes(DisplayConnection, window.getDrawable(), XCB_CW_BORDER_PIXEL, Values);
}
// If it isn't animated or we have non-cheap animations, update the real size
if (!window.getIsAnimated() || ConfigManager::getInt("animations:cheap") == 0) {
Values[0] = (int)window.getRealSize().x;
Values[1] = (int)window.getRealSize().y;
if (VECTORDELTANONZERO(window.getLastUpdateSize(), Vector2D(Values[0], Values[1]))) {
const auto COOKIE = xcb_configure_window(DisplayConnection, window.getDrawable(), XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, Values);
window.setLastUpdateSize(Vector2D(Values[0], Values[1]));
Events::ignoredEvents.push_back(COOKIE.sequence);
}
window.setFirstAnimFrame(true);
}
if (ConfigManager::getInt("animations:cheap") == 1 && window.getFirstAnimFrame() && window.getIsAnimated()) {
// first frame, fix the size if smaller
window.setFirstAnimFrame(false);
if (window.getRealSize().x < window.getEffectiveSize().x || window.getRealSize().y < window.getEffectiveSize().y) {
Values[0] = (int)window.getEffectiveSize().x;
Values[1] = (int)window.getEffectiveSize().y;
if (VECTORDELTANONZERO(window.getLastUpdateSize(), Vector2D(Values[0], Values[1]))) {
const auto COOKIE = xcb_configure_window(DisplayConnection, window.getDrawable(), XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, Values);
window.setLastUpdateSize(Vector2D(Values[0], Values[1]));
Events::ignoredEvents.push_back(COOKIE.sequence);
}
}
}
applyShapeToWindow(&window);
// EWMH
EWMH::updateWindow(window.getDrawable());
}
}
Debug::log(LOG, "Refreshed dirty windows in " + std::to_string(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - START).count()) + "us.");
}
void CWindowManager::setFocusedWindow(xcb_drawable_t window) {
if (window && window != Screen->root) {
const auto PNEWFOCUS = g_pWindowManager->getWindowFromDrawable(window);
if (PNEWFOCUS && PNEWFOCUS->getNoInterventions()) {
Debug::log(LOG, "Not setting focus to a non-interventions window.");
return;
}
Debug::log(LOG, "Setting focus to " + std::to_string(window));
xcb_ungrab_pointer(DisplayConnection, XCB_CURRENT_TIME);
// border color
if (const auto PLASTWIN = getWindowFromDrawable(LastWindow); PLASTWIN) {
PLASTWIN->setEffectiveBorderColor(CFloatingColor(ConfigManager::getInt("col.inactive_border")));
}
if (const auto PLASTWIN = getWindowFromDrawable(window); PLASTWIN) {
PLASTWIN->setEffectiveBorderColor(CFloatingColor(ConfigManager::getInt("col.active_border")));
}
if (const auto PWINDOW = g_pWindowManager->getWindowFromDrawable(window); PWINDOW) {
// Apply rounded corners, does all the checks inside.
// The border changed so let's not make it rectangular maybe
applyShapeToWindow(PWINDOW);
}
const auto LASTWINID = LastWindow;
LastWindow = window;
if (PNEWFOCUS) {
applyShapeToWindow(g_pWindowManager->getWindowFromDrawable(window));
// Transients
PNEWFOCUS->bringTopRecursiveTransients();
}
// set focus in X11
xcb_set_input_focus(DisplayConnection, XCB_INPUT_FOCUS_POINTER_ROOT, window, XCB_CURRENT_TIME);
// EWMH
EWMH::updateCurrentWindow(window);
EWMH::updateWindow(window);
EWMH::updateWindow(LASTWINID);
}
}
// TODO: make this executed less. It's too often imo.
void CWindowManager::sanityCheckOnWorkspace(int workspaceID) {
for (auto& w : windows) {
if (w.getWorkspaceID() == workspaceID) {
// Check #1: Parent has 2 identical children (happens!)
if (w.getDrawable() < 0) {
const auto CHILDA = w.getChildNodeAID();
const auto CHILDB = w.getChildNodeBID();
if (CHILDA == CHILDB) {
// Fix. Remove this parent, replace with child.
Debug::log(LOG, "Sanity check A triggered for window ID " + std::to_string(w.getDrawable()));
const auto PCHILD = getWindowFromDrawable(CHILDA);
if (!PCHILD){
// Means both children are 0 (dead)
removeWindowFromVectorSafe(w.getDrawable());
continue;
}
PCHILD->setPosition(w.getPosition());
PCHILD->setSize(w.getSize());
// make the sibling replace the parent
PCHILD->setParentNodeID(w.getParentNodeID());
if (w.getParentNodeID() != 0 && getWindowFromDrawable(w.getParentNodeID())) {
if (getWindowFromDrawable(w.getParentNodeID())->getChildNodeAID() == w.getDrawable()) {
getWindowFromDrawable(w.getParentNodeID())->setChildNodeAID(w.getDrawable());
} else {
getWindowFromDrawable(w.getParentNodeID())->setChildNodeBID(w.getDrawable());
}
}
// Make the sibling eat the closed window
PCHILD->setDirtyRecursive(true);
PCHILD->recalcSizePosRecursive();
// Remove the parent
removeWindowFromVectorSafe(w.getDrawable());
if (findWindowAtCursor())
setFocusedWindow(findWindowAtCursor()->getDrawable()); // Set focus. :)
Debug::log(LOG, "Sanity check A finished successfully.");
}
}
// Hypothetical check #2: Check if children are present and tiled. (for nodes)
// I have not found this occurring but I have had some issues with... stuff.
if (w.getDrawable() < 0) {
const auto CHILDA = getWindowFromDrawable(w.getChildNodeAID());
const auto CHILDB = getWindowFromDrawable(w.getChildNodeBID());
if (CHILDA && CHILDB) {
if (CHILDA->getIsFloating()) {
g_pWindowManager->fixWindowOnClose(CHILDA);
g_pWindowManager->calculateNewWindowParams(CHILDA);
Debug::log(LOG, "Found an invalid tiled window, ID: " + std::to_string(CHILDA->getDrawable()) + ", untiling it.");
}
if (CHILDB->getIsFloating()) {
g_pWindowManager->fixWindowOnClose(CHILDB);
g_pWindowManager->calculateNewWindowParams(CHILDB);
Debug::log(LOG, "Found an invalid tiled window, ID: " + std::to_string(CHILDB->getDrawable()) + ", untiling it.");
}
} else {
Debug::log(ERR, "Malformed node ID " + std::to_string(w.getDrawable()) + " with 2 children but one or both are nullptr.");
// fix it
if (!CHILDA && !CHILDB) {
closeWindowAllChecks(w.getDrawable());
Debug::log(ERR, "Node fixed, both nullptr.");
continue;
}
const auto PNULLCHILD = CHILDA ? CHILDB : CHILDA;
const auto PSIBLING = CHILDA ? CHILDA : CHILDB;
const auto PPARENT = getWindowFromDrawable(w.getDrawable());
if (!PPARENT)
return; // ????????
if (!PSIBLING) {
Debug::log(ERR, "No sibling found in fixing malformed node! (Corrupted tree...?)");
return;
}
// FIX TREE ----
// make the sibling replace the parent
PSIBLING->setPosition(PPARENT->getPosition());
PSIBLING->setSize(PPARENT->getSize());
PSIBLING->setParentNodeID(PPARENT->getParentNodeID());
if (PPARENT->getParentNodeID() != 0 && getWindowFromDrawable(PPARENT->getParentNodeID())) {
if (getWindowFromDrawable(PPARENT->getParentNodeID())->getChildNodeAID() == PPARENT->getDrawable()) {
getWindowFromDrawable(PPARENT->getParentNodeID())->setChildNodeAID(PSIBLING->getDrawable());
} else {
getWindowFromDrawable(PPARENT->getParentNodeID())->setChildNodeBID(PSIBLING->getDrawable());
}
}
// TREE FIXED ----
Debug::log(ERR, "Tree fixed.");
// Fix master stuff
getMasterForWorkspace(PSIBLING->getWorkspaceID());
// recalc the workspace
if (ConfigManager::getInt("layout") == LAYOUT_MASTER)
recalcEntireWorkspace(PSIBLING->getWorkspaceID());
else {
PSIBLING->recalcSizePosRecursive();
PSIBLING->setDirtyRecursive(true);
}
// Remove the parent
removeWindowFromVectorSafe(PPARENT->getDrawable());
if (findWindowAtCursor())
setFocusedWindow(findWindowAtCursor()->getDrawable()); // Set focus. :)
Debug::log(ERR, "Node fixed, one nullptr.");
}
}
}
}
}
CWindow* CWindowManager::getWindowFromDrawable(int64_t window) {
if (!window)
return nullptr;
for (auto& w : windows) {
if (w.getDrawable() == window) {
return &w;
}
}
for (auto& w : unmappedWindows) {
if (w.getDrawable() == window) {
return &w;
}
}
return nullptr;
}
void CWindowManager::addWindowToVectorSafe(CWindow window) {
for (auto& w : windows) {
if (w.getDrawable() == window.getDrawable())
return; // Do not add if already present.
}
windows.push_back(window);
}
void CWindowManager::removeWindowFromVectorSafe(int64_t window) {
if (!window)
return;
std::deque<CWindow> temp = windows;
windows.clear();
for(auto p : temp) {
if (p.getDrawable() != window) {
windows.push_back(p);
continue;
}
}
}
void CWindowManager::applyShapeToWindow(CWindow* pWindow) {
if (!pWindow)
return;
const auto ROUNDING = pWindow->getFullscreen() || (ConfigManager::getInt("layout:no_gaps_when_only") && getWindowsOnWorkspace(pWindow->getWorkspaceID()) == 1) ? 0 : ConfigManager::getInt("rounding");
const auto SHAPEQUERY = xcb_get_extension_data(DisplayConnection, &xcb_shape_id);
if (!SHAPEQUERY || !SHAPEQUERY->present || pWindow->getNoInterventions())
return;
Debug::log(LOG, "Applying shape to " + std::to_string(pWindow->getDrawable()));
// Prepare values
const auto MONITOR = getMonitorFromWindow(pWindow);
if (!MONITOR) {
Debug::log(ERR, "No monitor for " + std::to_string(pWindow->getDrawable()) + "??");
return;
}
const uint16_t W = pWindow->getFullscreen() ? MONITOR->vecSize.x : pWindow->getRealSize().x;
const uint16_t H = pWindow->getFullscreen() ? MONITOR->vecSize.y : pWindow->getRealSize().y;
const uint16_t BORDER = pWindow->getFullscreen() || (ConfigManager::getInt("layout:no_gaps_when_only") && getWindowsOnWorkspace(pWindow->getWorkspaceID()) == 1) ? 0 : ConfigManager::getInt("border_size");
const uint16_t TOTALW = W + 2 * BORDER;
const uint16_t TOTALH = H + 2 * BORDER;
const auto RADIUS = ROUNDING + BORDER;
const auto DIAMETER = RADIUS == 0 ? 0 : RADIUS * 2 - 1;
const xcb_arc_t BOUNDINGARCS[] = {
{-1, -1, DIAMETER, DIAMETER, 0, 360 << 6},
{-1, TOTALH - DIAMETER, DIAMETER, DIAMETER, 0, 360 << 6},
{TOTALW - DIAMETER, -1, DIAMETER, DIAMETER, 0, 360 << 6},
{TOTALW - DIAMETER, TOTALH - DIAMETER, DIAMETER, DIAMETER, 0, 360 << 6},
};
const xcb_rectangle_t BOUNDINGRECTS[] = {
{RADIUS, 0, TOTALW - DIAMETER, TOTALH},
{0, RADIUS, TOTALW, TOTALH - DIAMETER},
};
const auto DIAMETERC = ROUNDING == 0 ? 0 : 2 * ROUNDING - 1;
xcb_arc_t CLIPPINGARCS[] = {
{-1, -1, DIAMETERC, DIAMETERC, 0, 360 << 6},
{-1, H - DIAMETERC, DIAMETERC, DIAMETERC, 0, 360 << 6},
{W - DIAMETERC, -1, DIAMETERC, DIAMETERC, 0, 360 << 6},
{W - DIAMETERC, H - DIAMETERC, DIAMETERC, DIAMETERC, 0, 360 << 6},
};
xcb_rectangle_t CLIPPINGRECTS[] = {
{ROUNDING, 0, W - DIAMETERC, H},
{0, ROUNDING, W, H - DIAMETERC},
};
// Values done
// XCB
const xcb_pixmap_t PIXMAP1 = xcb_generate_id(DisplayConnection);
const xcb_pixmap_t PIXMAP2 = xcb_generate_id(DisplayConnection);
const xcb_gcontext_t BLACK = xcb_generate_id(DisplayConnection);
const xcb_gcontext_t WHITE = xcb_generate_id(DisplayConnection);
xcb_create_pixmap(DisplayConnection, 1, PIXMAP1, pWindow->getDrawable(), TOTALW, TOTALH);
xcb_create_pixmap(DisplayConnection, 1, PIXMAP2, pWindow->getDrawable(), W, H);
Values[0] = 0;
Values[1] = 0;
xcb_create_gc(DisplayConnection, BLACK, PIXMAP1, XCB_GC_FOREGROUND, Values);
Values[0] = 1;
xcb_create_gc(DisplayConnection, WHITE, PIXMAP1, XCB_GC_FOREGROUND, Values);
// XCB done
// Draw
xcb_rectangle_t BOUNDINGRECT = {0, 0, W + 2 * BORDER, H + 2 * BORDER};
xcb_poly_fill_rectangle(DisplayConnection, PIXMAP1, BLACK, 1, &BOUNDINGRECT);
xcb_poly_fill_rectangle(DisplayConnection, PIXMAP1, WHITE, 2, BOUNDINGRECTS);
xcb_poly_fill_arc(DisplayConnection, PIXMAP1, WHITE, 4, BOUNDINGARCS);
xcb_rectangle_t CLIPPINGRECT = {0, 0, W, H};
xcb_poly_fill_rectangle(DisplayConnection, PIXMAP2, BLACK, 1, &CLIPPINGRECT);
xcb_poly_fill_rectangle(DisplayConnection, PIXMAP2, WHITE, 2, CLIPPINGRECTS);
xcb_poly_fill_arc(DisplayConnection, PIXMAP2, WHITE, 4, CLIPPINGARCS);
const auto WORKSPACE = getWorkspaceByID(pWindow->getWorkspaceID());
if (!WORKSPACE) {
Debug::log(ERR, "No workspace for " + std::to_string(pWindow->getDrawable()) + "??");
return;
}
if (WORKSPACE->getAnimationInProgress()) {
// if it's animated we draw 2 more black rects to clip it. (if it goes out of the monitor)
if (W + (pWindow->getRealPosition().x + WORKSPACE->getCurrentOffset().x - MONITOR->vecPosition.x) > MONITOR->vecSize.x) {
// clip right
xcb_rectangle_t rect[] = {{MONITOR->vecSize.x - (pWindow->getRealPosition().x + WORKSPACE->getCurrentOffset().x - MONITOR->vecPosition.x), -100, W + 100, H + 100}};
xcb_poly_fill_rectangle(DisplayConnection, PIXMAP1, BLACK, 1, rect);
xcb_poly_fill_rectangle(DisplayConnection, PIXMAP2, BLACK, 1, rect);
}
if (pWindow->getRealPosition().x + WORKSPACE->getCurrentOffset().x - MONITOR->vecPosition.x < 0) {
// clip left
xcb_rectangle_t rect[] = {{-100, -100, - (pWindow->getRealPosition().x + WORKSPACE->getCurrentOffset().x - MONITOR->vecPosition.x), H + 100}};
xcb_poly_fill_rectangle(DisplayConnection, PIXMAP1, BLACK, 1, rect);
xcb_poly_fill_rectangle(DisplayConnection, PIXMAP2, BLACK, 1, rect);
}
}
// Draw done
// Shape
xcb_shape_mask(DisplayConnection, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, pWindow->getDrawable(), -BORDER, -BORDER, PIXMAP1);
xcb_shape_mask(DisplayConnection, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_CLIP, pWindow->getDrawable(), 0, 0, PIXMAP2);
// Shape done
// Cleanup
xcb_free_pixmap(DisplayConnection, PIXMAP1);
xcb_free_pixmap(DisplayConnection, PIXMAP2);
}
void CWindowManager::setEffectiveSizePosUsingConfig(CWindow* pWindow) {
if (!pWindow || pWindow->getIsFloating())
return;
const auto MONITOR = getMonitorFromWindow(pWindow);
// set some flags.
const bool DISPLAYLEFT = STICKS(pWindow->getPosition().x, MONITOR->vecPosition.x);
const bool DISPLAYRIGHT = STICKS(pWindow->getPosition().x + pWindow->getSize().x, MONITOR->vecPosition.x + MONITOR->vecSize.x);
const bool DISPLAYTOP = STICKS(pWindow->getPosition().y, MONITOR->vecPosition.y);
const bool DISPLAYBOTTOM = STICKS(pWindow->getPosition().y + pWindow->getSize().y, MONITOR->vecPosition.y + MONITOR->vecSize.y);
const auto BORDERSIZE = ConfigManager::getInt("border_size");
const auto GAPSOUT = ConfigManager::getInt("gaps_out");
const auto GAPSIN = ConfigManager::getInt("gaps_in");
auto TEMPEFFECTIVESIZE = pWindow->getSize();
auto TEMPEFFECTIVEPOS = pWindow->getPosition();
const auto OFFSETTOPLEFT = Vector2D(DISPLAYLEFT ? GAPSOUT + MONITOR->vecReservedTopLeft.x : GAPSIN,
DISPLAYTOP ? GAPSOUT + MONITOR->vecReservedTopLeft.y : GAPSIN);
const auto OFFSETBOTTOMRIGHT = Vector2D(DISPLAYRIGHT ? GAPSOUT + MONITOR->vecReservedBottomRight.x : GAPSIN,
DISPLAYBOTTOM ? GAPSOUT + MONITOR->vecReservedBottomRight.y : GAPSIN);
TEMPEFFECTIVEPOS = TEMPEFFECTIVEPOS + Vector2D(BORDERSIZE, BORDERSIZE);
TEMPEFFECTIVESIZE = TEMPEFFECTIVESIZE - (Vector2D(BORDERSIZE, BORDERSIZE) * 2);
// do gaps, set top left
TEMPEFFECTIVEPOS = TEMPEFFECTIVEPOS + OFFSETTOPLEFT;
// fix to old size bottom right
TEMPEFFECTIVESIZE = TEMPEFFECTIVESIZE - OFFSETTOPLEFT;
// set bottom right
TEMPEFFECTIVESIZE = TEMPEFFECTIVESIZE - OFFSETBOTTOMRIGHT;
if (pWindow->getIsPseudotiled()) {
float scale = 1;
// adjust if doesnt fit
if (pWindow->getPseudoSize().x > TEMPEFFECTIVESIZE.x || pWindow->getPseudoSize().y > TEMPEFFECTIVESIZE.y) {
if (pWindow->getPseudoSize().x > TEMPEFFECTIVESIZE.x) {
scale = TEMPEFFECTIVESIZE.x / pWindow->getPseudoSize().x;
}
if (pWindow->getPseudoSize().y * scale > TEMPEFFECTIVESIZE.y) {
scale = TEMPEFFECTIVESIZE.y / pWindow->getPseudoSize().y;
}
auto DELTA = TEMPEFFECTIVESIZE - pWindow->getPseudoSize() * scale;
TEMPEFFECTIVESIZE = pWindow->getPseudoSize() * scale;
TEMPEFFECTIVEPOS = TEMPEFFECTIVEPOS + DELTA / 2.f; // center
} else {
auto DELTA = TEMPEFFECTIVESIZE - pWindow->getPseudoSize();
TEMPEFFECTIVEPOS = TEMPEFFECTIVEPOS + DELTA / 2.f; // center
TEMPEFFECTIVESIZE = pWindow->getPseudoSize();
}
}
if (pWindow->getWorkspaceID() == SCRATCHPAD_ID) {
TEMPEFFECTIVEPOS = TEMPEFFECTIVEPOS + ((TEMPEFFECTIVESIZE - TEMPEFFECTIVESIZE * 0.75f) * 0.5f);
TEMPEFFECTIVESIZE = TEMPEFFECTIVESIZE * 0.75f;
setAWindowTop(pWindow->getDrawable());
}
if (pWindow->getFullscreen()) {
TEMPEFFECTIVEPOS = MONITOR->vecPosition;
TEMPEFFECTIVESIZE = MONITOR->vecSize;
}
pWindow->setEffectivePosition(TEMPEFFECTIVEPOS);
pWindow->setEffectiveSize(TEMPEFFECTIVESIZE);
}
CWindow* CWindowManager::findWindowAtCursor() {
Vector2D cursorPos = getCursorPos();
if (!getMonitorFromCursor())
return nullptr;
const auto WORKSPACE = activeWorkspaces[getMonitorFromCursor()->ID];
for (auto& window : windows) {
if (window.getWorkspaceID() == WORKSPACE && !window.getIsFloating() && window.getDrawable() > 0 && window.getConstructed() && window.getWorkspaceID() != SCRATCHPAD_ID) {
if (cursorPos.x >= window.getPosition().x
&& cursorPos.x <= window.getPosition().x + window.getSize().x
&& cursorPos.y >= window.getPosition().y
&& cursorPos.y <= window.getPosition().y + window.getSize().y) {
return &window;
}
}
}
return nullptr;
}
CWindow* CWindowManager::findFirstWindowOnWorkspace(const int& work) {
for (auto& w : windows) {
if (w.getWorkspaceID() == work && !w.getIsFloating() && !w.getNoInterventions() && w.getDrawable() > 0) {
return &w;
}
}
return nullptr;
}
CWindow* CWindowManager::findPreferredOnScratchpad() {
Vector2D topSize;
CWindow* pTop = nullptr;
for (auto& w : windows) {
if (w.getWorkspaceID() == SCRATCHPAD_ID && w.getDrawable() > 0 && w.getConstructed()) {
if (w.getSize().x * w.getSize().y > topSize.x * topSize.y) {
topSize = w.getSize();
pTop = &w;
}
}
}
return pTop;
}
void CWindowManager::calculateNewTileSetOldTile(CWindow* pWindow) {
// Get the parent and both children, one of which will be pWindow
const auto PPARENT = getWindowFromDrawable(pWindow->getParentNodeID());
auto PMONITOR = getMonitorFromWindow(pWindow);
if (!PMONITOR) {
Debug::log(ERR, "Monitor was nullptr! (calculateNewTileSetOldTile) using 0.");
PMONITOR = &monitors[0];
if (monitors.size() == 0) {
Debug::log(ERR, "Not continuing. Monitors size 0.");
return;
}
}
if (!PPARENT) {
// New window on this workspace.
// Open a fullscreen window.
pWindow->setSize(Vector2D(PMONITOR->vecSize.x, PMONITOR->vecSize.y));
pWindow->setPosition(Vector2D(PMONITOR->vecPosition.x, PMONITOR->vecPosition.y));
return;
}
switch (ConfigManager::getInt("layout"))
{
case LAYOUT_DWINDLE:
{
// Get the sibling
const auto PSIBLING = getWindowFromDrawable(PPARENT->getChildNodeAID() == pWindow->getDrawable() ? PPARENT->getChildNodeBID() : PPARENT->getChildNodeAID());
// Should NEVER be null
if (PSIBLING) {
const auto PLASTSIZE = PPARENT->getSize();
const auto PLASTPOS = PPARENT->getPosition();
if (PLASTSIZE.x > PLASTSIZE.y) {
PSIBLING->setPosition(Vector2D(PLASTPOS.x, PLASTPOS.y));
PSIBLING->setSize(Vector2D(PLASTSIZE.x / 2.f, PLASTSIZE.y));
pWindow->setSize(Vector2D(PLASTSIZE.x / 2.f, PLASTSIZE.y));
pWindow->setPosition(Vector2D(PLASTPOS.x + PLASTSIZE.x / 2.f, PLASTPOS.y));
} else {
PSIBLING->setPosition(Vector2D(PLASTPOS.x, PLASTPOS.y));
PSIBLING->setSize(Vector2D(PLASTSIZE.x, PLASTSIZE.y / 2.f));
pWindow->setSize(Vector2D(PLASTSIZE.x, PLASTSIZE.y / 2.f));
pWindow->setPosition(Vector2D(PLASTPOS.x, PLASTPOS.y + PLASTSIZE.y / 2.f));
}
PSIBLING->setDirty(true);
} else {
Debug::log(ERR, "Sibling node was null?? pWindow x,y,w,h: " + std::to_string(pWindow->getPosition().x) + " " + std::to_string(pWindow->getPosition().y) + " " + std::to_string(pWindow->getSize().x) + " " + std::to_string(pWindow->getSize().y));
}
}
break;
case LAYOUT_MASTER:
{
recalcEntireWorkspace(pWindow->getWorkspaceID());
}
break;
}
}
int CWindowManager::getWindowsOnWorkspace(const int& workspace) {
int number = 0;
for (auto& w : windows) {
if (w.getWorkspaceID() == workspace && w.getDrawable() > 0 && !w.getDock()) {
++number;
}
}
return number;
}
SMonitor* CWindowManager::getMonitorFromWorkspace(const int& workspace) {
int monitorID = -1;
for (auto& work : workspaces) {
if (work.getID() == workspace) {
monitorID = work.getMonitor();
break;
}
}
for (auto& monitor : monitors) {
if (monitor.ID == monitorID) {
return &monitor;
}
}
return nullptr;
}
void CWindowManager::recalcEntireWorkspace(const int& workspace) {
switch (ConfigManager::getInt("layout"))
{
case LAYOUT_MASTER:
{
// Get the monitor
const auto PMONITOR = getMonitorFromWorkspace(workspace);
// first, calc the size
CWindow* pMaster = nullptr;
for (auto& w : windows) {
if (w.getWorkspaceID() == workspace && w.getMaster() && !w.getDead() && !w.getIsFloating() && !w.getDock()) {
pMaster = &w;
break;
}
}
CWindow* pMasterContainer = nullptr;
for (auto& w : windows) {
if (w.getWorkspaceID() == workspace && w.getParentNodeID() == 0 && !w.getIsFloating() && !w.getDock()) {
pMasterContainer = &w;
break;
}
}
if (!pMaster) {
Debug::log(ERR, "No master found on workspace???");
return;
}
// set the xy for master
float splitRatio = 1;
if (pMasterContainer)
splitRatio = pMasterContainer->getSplitRatio();
pMaster->setPosition(Vector2D(0, 0) + PMONITOR->vecPosition);
pMaster->setSize(Vector2D(PMONITOR->vecSize.x / 2 * splitRatio, PMONITOR->vecSize.y));
// get children sorted
std::vector<CWindow*> children;
for (auto& w : windows) {
if (w.getWorkspaceID() == workspace && !w.getMaster() && w.getDrawable() > 0 && !w.getDead() && !w.getDock())
children.push_back(&w);
}
std::sort(children.begin(), children.end(), [](CWindow*& a, CWindow*& b) {
return a->getMasterChildIndex() < b->getMasterChildIndex();
});
// if no children, master full
if (children.size() == 0) {
pMaster->setPosition(Vector2D(0, 0) + PMONITOR->vecPosition);
pMaster->setSize(Vector2D(PMONITOR->vecSize.x, PMONITOR->vecSize.y));
}
// Children sorted, set xy
int yoff = 0;
for (const auto& child : children) {
child->setPosition(Vector2D(PMONITOR->vecSize.x / 2 * splitRatio, yoff) + PMONITOR->vecPosition);
child->setSize(Vector2D(PMONITOR->vecSize.x / 2 * (2 - splitRatio), PMONITOR->vecSize.y / children.size()));
yoff += PMONITOR->vecSize.y / children.size();
}
// done
setAllWorkspaceWindowsDirtyByID(workspace);
}
break;
case LAYOUT_DWINDLE:
{
// get the master on the workspace
CWindow* pMasterWindow = nullptr;
for (auto& w : windows) {
if (w.getWorkspaceID() == workspace && w.getParentNodeID() == 0 && !w.getIsFloating() && !w.getDock()) {
pMasterWindow = &w;
break;
}
}
if (!pMasterWindow)
return;
const auto PMONITOR = getMonitorFromWorkspace(workspace);
if (!PMONITOR)
return;
Debug::log(LOG, "Recalc for workspace " + std::to_string(workspace));
pMasterWindow->setSize(PMONITOR->vecSize);
pMasterWindow->setPosition(PMONITOR->vecPosition);
pMasterWindow->recalcSizePosRecursive();
setAllWorkspaceWindowsDirtyByID(workspace);
}
break;
default:
break;
}
}
void CWindowManager::calculateNewFloatingWindow(CWindow* pWindow) {
if (!pWindow)
return;
if (!pWindow->getNoInterventions() && !pWindow->getDock()) {
pWindow->setPosition(pWindow->getEffectivePosition() + Vector2D(3,3));
pWindow->setSize(pWindow->getEffectiveSize() - Vector2D(6, 6));
// min size
pWindow->setSize(Vector2D(std::clamp(pWindow->getSize().x, (double)40, (double)99999),
std::clamp(pWindow->getSize().y, (double)40, (double)99999)));
pWindow->setEffectivePosition(pWindow->getPosition() + Vector2D(10, 10));
pWindow->setEffectiveSize(pWindow->getSize());
pWindow->setRealPosition(pWindow->getPosition());
pWindow->setRealSize(pWindow->getSize());
}
Values[0] = XCB_STACK_MODE_ABOVE;
xcb_configure_window(DisplayConnection, pWindow->getDrawable(), XCB_CONFIG_WINDOW_STACK_MODE, Values);
}
void CWindowManager::calculateNewWindowParams(CWindow* pWindow) {
// And set old one's if needed.
if (!pWindow)
return;
if (!pWindow->getIsFloating()) {
calculateNewTileSetOldTile(pWindow);
} else {
calculateNewFloatingWindow(pWindow);
}
setEffectiveSizePosUsingConfig(pWindow);
pWindow->setDirty(true);
}
bool CWindowManager::isNeighbor(CWindow* a, CWindow* b) {
if (a->getWorkspaceID() != b->getWorkspaceID())
return false; // Different workspaces
const auto POSA = a->getPosition();
const auto POSB = b->getPosition();
const auto SIZEA = a->getSize();
const auto SIZEB = b->getSize();
if (POSA.x != 0) {
if (STICKS(POSA.x, (POSB.x + SIZEB.x))) {
return true;
}
}
if (POSA.y != 0) {
if (STICKS(POSA.y, (POSB.y + SIZEB.y))) {
return true;
}
}
if (POSB.x != 0) {
if (STICKS(POSB.x, (POSA.x + SIZEA.x))) {
return true;
}
}
if (POSB.y != 0) {
if (STICKS(POSB.y, (POSA.y + SIZEA.y))) {
return true;
}
}
return false;
}
bool CWindowManager::canEatWindow(CWindow* a, CWindow* toEat) {
// Pos is min of both.
const auto POSAFTEREAT = Vector2D(std::min(a->getPosition().x, toEat->getPosition().x), std::min(a->getPosition().y, toEat->getPosition().y));
// Size is pos + size max - pos
const auto OPPCORNERA = Vector2D(POSAFTEREAT) + a->getSize();
const auto OPPCORNERB = toEat->getPosition() + toEat->getSize();
const auto SIZEAFTEREAT = Vector2D(std::max(OPPCORNERA.x, OPPCORNERB.x), std::max(OPPCORNERA.y, OPPCORNERB.y)) - POSAFTEREAT;
const auto doOverlap = [&](CWindow* b) {
const auto RIGHT1 = Vector2D(POSAFTEREAT.x + SIZEAFTEREAT.x, POSAFTEREAT.y + SIZEAFTEREAT.y);
const auto RIGHT2 = b->getPosition() + b->getSize();
const auto LEFT1 = POSAFTEREAT;
const auto LEFT2 = b->getPosition();
return !(LEFT1.x >= RIGHT2.x || LEFT2.x >= RIGHT1.x || LEFT1.y >= RIGHT2.y || LEFT2.y >= RIGHT1.y);
};
for (auto& w : windows) {
if (w.getDrawable() == a->getDrawable() || w.getDrawable() == toEat->getDrawable() || w.getWorkspaceID() != toEat->getWorkspaceID()
|| w.getIsFloating() || getMonitorFromWindow(&w) != getMonitorFromWindow(toEat))
continue;
if (doOverlap(&w))
return false;
}
return true;
}
void CWindowManager::eatWindow(CWindow* a, CWindow* toEat) {
// Size is pos + size max - pos
const auto OPPCORNERA = a->getPosition() + a->getSize();
const auto OPPCORNERB = toEat->getPosition() + toEat->getSize();
// Pos is min of both.
a->setPosition(Vector2D(std::min(a->getPosition().x, toEat->getPosition().x), std::min(a->getPosition().y, toEat->getPosition().y)));
a->setSize(Vector2D(std::max(OPPCORNERA.x, OPPCORNERB.x), std::max(OPPCORNERA.y, OPPCORNERB.y)) - a->getPosition());
}
void CWindowManager::closeWindowAllChecks(int64_t id) {
// fix last window if tile
const auto CLOSEDWINDOW = g_pWindowManager->getWindowFromDrawable(id);
if (!CLOSEDWINDOW)
return; // It's not in the vec, ignore. (weird)
CLOSEDWINDOW->setDead(true);
if (CLOSEDWINDOW->getWorkspaceID() != SCRATCHPAD_ID && scratchpadActive)
scratchpadActive = false;
if (const auto WORKSPACE = getWorkspaceByID(CLOSEDWINDOW->getWorkspaceID()); WORKSPACE && CLOSEDWINDOW->getFullscreen())
WORKSPACE->setHasFullscreenWindow(false);
if (!CLOSEDWINDOW->getIsFloating())
g_pWindowManager->fixWindowOnClose(CLOSEDWINDOW);
const bool WASDOCK = CLOSEDWINDOW->getDock();
// delete off of the arr
g_pWindowManager->removeWindowFromVectorSafe(id);
// Fix docks
if (WASDOCK)
g_pWindowManager->recalcAllDocks();
}
CWindow* CWindowManager::getMasterForWorkspace(const int& work) {
CWindow* pMaster = nullptr;
for (auto& w : windows) {
if (w.getWorkspaceID() == work && w.getMaster()) {
pMaster = &w;
break;
}
}
if (!pMaster) {
Debug::log(ERR, "No master found on workspace? Setting automatically");
for (auto& w : windows) {
if (w.getWorkspaceID() == work && !w.getDock() && !w.getIsFloating()) {
pMaster = &w;
w.setMaster(true);
break;
}
}
}
return pMaster;
}
void CWindowManager::fixMasterWorkspaceOnClosed(CWindow* pWindow) {
getMasterForWorkspace(pWindow->getWorkspaceID()); // to fix if no master
// get children sorted
std::vector<CWindow*> children;
for (auto& w : windows) {
if (w.getWorkspaceID() == pWindow->getWorkspaceID() && !w.getMaster() && w.getDrawable() > 0 && w.getDrawable() != pWindow->getDrawable() && !w.getDock())
children.push_back(&w);
}
std::sort(children.begin(), children.end(), [](CWindow*& a, CWindow*& b) {
return a->getMasterChildIndex() < b->getMasterChildIndex();
});
// If closed window was master, set a new master.
if (pWindow->getMaster()) {
if (children.size() > 0) {
children[0]->setMaster(true);
}
} else {
// else fix the indices
for (long unsigned int i = pWindow->getMasterChildIndex() - 1; i < children.size(); ++i) {
// masterChildIndex = 1 for the first child
children[i]->setMasterChildIndex(i);
}
}
}
void CWindowManager::fixWindowOnClose(CWindow* pClosedWindow) {
if (!pClosedWindow)
return;
// Get the parent and both children, one of which will be pWindow
const auto PPARENT = getWindowFromDrawable(pClosedWindow->getParentNodeID());
if (!PPARENT)
return; // if there was no parent, we do not need to update anything. it was a fullscreen window, the only one on a given workspace.
// Get the sibling
const auto PSIBLING = getWindowFromDrawable(PPARENT->getChildNodeAID() == pClosedWindow->getDrawable() ? PPARENT->getChildNodeBID() : PPARENT->getChildNodeAID());
if (!PSIBLING) {
Debug::log(ERR, "No sibling found in fixOnClose! (Corrupted tree...?)");
return;
}
// used by master layout
pClosedWindow->setDead(true);
// FIX TREE ----
// make the sibling replace the parent
PSIBLING->setPosition(PPARENT->getPosition());
PSIBLING->setSize(PPARENT->getSize());
PSIBLING->setParentNodeID(PPARENT->getParentNodeID());
if (PPARENT->getParentNodeID() != 0
&& getWindowFromDrawable(PPARENT->getParentNodeID())) {
if (getWindowFromDrawable(PPARENT->getParentNodeID())->getChildNodeAID() == PPARENT->getDrawable()) {
getWindowFromDrawable(PPARENT->getParentNodeID())->setChildNodeAID(PSIBLING->getDrawable());
} else {
getWindowFromDrawable(PPARENT->getParentNodeID())->setChildNodeBID(PSIBLING->getDrawable());
}
}
// TREE FIXED ----
// Fix master stuff
const auto WORKSPACE = pClosedWindow->getWorkspaceID();
fixMasterWorkspaceOnClosed(pClosedWindow);
// recalc the workspace
if (ConfigManager::getInt("layout") == LAYOUT_MASTER)
recalcEntireWorkspace(WORKSPACE);
else {
PSIBLING->recalcSizePosRecursive();
PSIBLING->setDirtyRecursive(true);
}
// Remove the parent
removeWindowFromVectorSafe(PPARENT->getDrawable());
if (findWindowAtCursor())
setFocusedWindow(findWindowAtCursor()->getDrawable()); // Set focus. :)
}
CWindow* CWindowManager::getNeighborInDir(char dir) {
const auto CURRENTWINDOW = getWindowFromDrawable(LastWindow);
if (!CURRENTWINDOW)
return nullptr;
const auto POSA = CURRENTWINDOW->getPosition();
const auto SIZEA = CURRENTWINDOW->getSize();
auto longestIntersect = -1;
CWindow* longestIntersectWindow = nullptr;
for (auto& w : windows) {
if (w.getDrawable() == CURRENTWINDOW->getDrawable() || w.getDrawable() < 1 || w.getIsFloating() || !isWorkspaceVisible(w.getWorkspaceID()))
continue;
const auto POSB = w.getPosition();
const auto SIZEB = w.getSize();
switch (dir) {
case 'l':
if (STICKS(POSA.x, POSB.x + SIZEB.x)) {
const auto INTERSECTLEN = std::max((double)0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y));
if (INTERSECTLEN > longestIntersect) {
longestIntersect = INTERSECTLEN;
longestIntersectWindow = &w;
}
}
break;
case 'r':
if (STICKS(POSA.x + SIZEA.x, POSB.x)) {
const auto INTERSECTLEN = std::max((double)0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y));
if (INTERSECTLEN > longestIntersect) {
longestIntersect = INTERSECTLEN;
longestIntersectWindow = &w;
}
}
break;
case 't':
case 'u':
if (STICKS(POSA.y, POSB.y + SIZEB.y)) {
const auto INTERSECTLEN = std::max((double)0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x));
if (INTERSECTLEN > longestIntersect) {
longestIntersect = INTERSECTLEN;
longestIntersectWindow = &w;
}
}
break;
case 'b':
case 'd':
if (STICKS(POSA.y + SIZEA.y, POSB.y)) {
const auto INTERSECTLEN = std::max((double)0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x));
if (INTERSECTLEN > longestIntersect) {
longestIntersect = INTERSECTLEN;
longestIntersectWindow = &w;
}
}
break;
}
}
if (longestIntersect != -1)
return longestIntersectWindow;
return nullptr;
}
void CWindowManager::warpCursorTo(Vector2D to) {
const auto POINTERCOOKIE = xcb_query_pointer(DisplayConnection, Screen->root);
xcb_query_pointer_reply_t* pointerreply = xcb_query_pointer_reply(DisplayConnection, POINTERCOOKIE, NULL);
if (!pointerreply) {
Debug::log(ERR, "Couldn't query pointer.");
free(pointerreply);
return;
}
xcb_warp_pointer(DisplayConnection, XCB_NONE, Screen->root, 0, 0, Screen->width_in_pixels, Screen->height_in_pixels, (int)to.x, (int)to.y);
free(pointerreply);
}
void CWindowManager::moveActiveWindowToWorkspace(int workspace) {
auto PWINDOW = getWindowFromDrawable(LastWindow);
if (!PWINDOW)
return;
if (PWINDOW->getWorkspaceID() == workspace)
return;
Debug::log(LOG, "Moving active window to " + std::to_string(workspace));
const auto SAVEDDEFAULTSIZE = PWINDOW->getDefaultSize();
const auto SAVEDFLOATSTATUS = PWINDOW->getIsFloating();
const auto SAVEDDRAWABLE = PWINDOW->getDrawable();
// remove current workspace's fullscreen status if fullscreen
if (PWINDOW->getFullscreen()) {
const auto PWORKSPACE = getWorkspaceByID(PWINDOW->getWorkspaceID());
if (PWORKSPACE) {
PWORKSPACE->setHasFullscreenWindow(false);
}
}
fixWindowOnClose(PWINDOW);
// deque reallocated
LastWindow = SAVEDDRAWABLE;
PWINDOW = getWindowFromDrawable(LastWindow);
PWINDOW->setDead(false);
const auto WORKSPACE = getWorkspaceByID(PWINDOW->getWorkspaceID());
auto workspacesBefore = activeWorkspaces;
if (WORKSPACE && PWINDOW->getFullscreen())
WORKSPACE->setHasFullscreenWindow(false);
changeWorkspaceByID(workspace);
// Find new mon
int NEWMONITOR = 0;
for (long unsigned int i = 0; i < activeWorkspaces.size(); ++i) {
if (workspace == SCRATCHPAD_ID) {
if (monitors[i].ID == ConfigManager::getInt("scratchpad_mon"))
NEWMONITOR = i;
}
else if (activeWorkspaces[i] == workspace) {
NEWMONITOR = i;
}
}
// Find the first window on the new workspace
xcb_drawable_t newLastWindow = 0;
for (auto& w : windows) {
if (w.getDrawable() > 0 && w.getWorkspaceID() == workspace && !w.getIsFloating()) {
newLastWindow = w.getDrawable();
break;
}
}
const auto LASTFOCUS = LastWindow;
if (newLastWindow) {
setFocusedWindow(newLastWindow);
}
PWINDOW->setConstructed(false);
if (SAVEDFLOATSTATUS && workspace != SCRATCHPAD_ID)
Events::remapFloatingWindow(PWINDOW->getDrawable(), NEWMONITOR);
else
Events::remapWindow(PWINDOW->getDrawable(), false, NEWMONITOR);
PWINDOW->setConstructed(true);
// fix for scratchpad
PWINDOW->setWorkspaceID(workspace);
// fix fullscreen status
const auto PWORKSPACE = getWorkspaceByID(workspace);
if (PWORKSPACE) {
// Should NEVER be false but let's be sure
PWORKSPACE->setHasFullscreenWindow(PWINDOW->getFullscreen());
}
PWINDOW->setDefaultSize(SAVEDDEFAULTSIZE);
if (workspace == SCRATCHPAD_ID) {
for (int i = 0; i < activeWorkspaces.size(); ++i) {
changeWorkspaceByID(workspacesBefore[i]);
}
changeWorkspaceByID(WORKSPACE->getID());
setFocusedWindow(LASTFOCUS);
}
QueuedPointerWarp = Vector2D(-1,-1);
}
void CWindowManager::moveActiveWindowTo(char dir) {
const auto CURRENTWINDOW = getWindowFromDrawable(LastWindow);
if (!CURRENTWINDOW)
return;
const auto neighbor = getNeighborInDir(dir);
if (!neighbor)
return;
// swap their stuff and mark dirty
const auto TEMP_SIZEA = CURRENTWINDOW->getSize();
const auto TEMP_POSA = CURRENTWINDOW->getPosition();
CURRENTWINDOW->setSize(neighbor->getSize());
CURRENTWINDOW->setPosition(neighbor->getPosition());
neighbor->setSize(TEMP_SIZEA);
neighbor->setPosition(TEMP_POSA);
CURRENTWINDOW->setDirty(true);
neighbor->setDirty(true);
// Fix the tree
if (CURRENTWINDOW->getParentNodeID() != 0) {
const auto PPARENT = getWindowFromDrawable(CURRENTWINDOW->getParentNodeID());
if (!PPARENT) {
Debug::log(ERR, "No parent node ID despite non-null???");
return;
}
if (PPARENT->getChildNodeAID() == CURRENTWINDOW->getDrawable())
PPARENT->setChildNodeAID(neighbor->getDrawable());
else
PPARENT->setChildNodeBID(neighbor->getDrawable());
}
if (neighbor->getParentNodeID() != 0) {
const auto PPARENT = getWindowFromDrawable(neighbor->getParentNodeID());
if (!PPARENT) {
Debug::log(ERR, "No parent node ID despite non-null???");
return;
}
if (PPARENT->getChildNodeAID() == neighbor->getDrawable())
PPARENT->setChildNodeAID(CURRENTWINDOW->getDrawable());
else
PPARENT->setChildNodeBID(CURRENTWINDOW->getDrawable());
}
const auto PARENTC = CURRENTWINDOW->getParentNodeID();
CURRENTWINDOW->setParentNodeID(neighbor->getParentNodeID());
neighbor->setParentNodeID(PARENTC);
// Fix the master layout
const auto SMASTER = CURRENTWINDOW->getMaster();
const auto SINDEX = CURRENTWINDOW->getMasterChildIndex();
CURRENTWINDOW->setMasterChildIndex(neighbor->getMasterChildIndex());
CURRENTWINDOW->setMaster(neighbor->getMaster());
neighbor->setMaster(SMASTER);
neighbor->setMasterChildIndex(SINDEX);
// finish by moving the cursor to the new current window
QueuedPointerWarp = Vector2D(CURRENTWINDOW->getPosition() + CURRENTWINDOW->getSize() / 2.f);
}
CWindow* CWindowManager::getFullscreenWindowByWorkspace(const int& id) {
for (auto& window : windows) {
if (window.getWorkspaceID() == id && window.getFullscreen() && window.getDrawable() > 0)
return &window;
}
return nullptr;
}
void CWindowManager::moveActiveFocusTo(char dir) {
const auto CURRENTWINDOW = getWindowFromDrawable(LastWindow);
if (!CURRENTWINDOW)
return;
const auto PWORKSPACE = getWorkspaceByID(CURRENTWINDOW->getWorkspaceID());
if (!PWORKSPACE)
return;
const auto NEIGHBOR = PWORKSPACE->getHasFullscreenWindow() ? getFullscreenWindowByWorkspace(PWORKSPACE->getID()) : getNeighborInDir(dir);
if (!NEIGHBOR)
return;
// move the focus
setFocusedWindow(NEIGHBOR->getDrawable());
// finish by moving the cursor to the neighbor window
QueuedPointerWarp = Vector2D(NEIGHBOR->getPosition() + (NEIGHBOR->getSize() / 2.f));
}
void CWindowManager::relativeWorkspace(int relativenum) {
if (activeWorkspaceID + relativenum < lowerWorkspaceLimit) return;
if (activeWorkspaceID + relativenum > upperWorkspaceLimit) return;
changeWorkspaceByID(activeWorkspaceID + relativenum);
}
void CWindowManager::changeWorkspaceByID(int ID) {
activeWorkspaceID = ID;
auto MONITOR = getMonitorFromCursor();
if (!MONITOR) {
Debug::log(ERR, "Monitor was nullptr! (changeWorkspaceByID) Using monitor 0.");
MONITOR = &monitors[0];
}
// Don't change if already opened
if (isWorkspaceVisible(ID)) {
Debug::log(LOG, "Workspace visible, only focus.");
focusOnWorkspace(ID);
return;
}
// mark old workspace dirty
setAllWorkspaceWindowsDirtyByID(activeWorkspaces[MONITOR->ID]);
// save old workspace for animation
auto OLDWORKSPACE = activeWorkspaces[MONITOR->ID];
lastActiveWorkspaceID = OLDWORKSPACE;
for (auto& workspace : workspaces) {
if (workspace.getID() == ID) {
Debug::log(LOG, "Workspace open, bringing to active.");
// set workspaces dirty
setAllWorkspaceWindowsDirtyByID(activeWorkspaces[workspace.getMonitor()]);
setAllWorkspaceWindowsDirtyByID(ID);
OLDWORKSPACE = activeWorkspaces[workspace.getMonitor()];
activeWorkspaces[workspace.getMonitor()] = workspace.getID();
// if not fullscreen set the focus to any window on that workspace
// if fullscreen, set to the fullscreen window
focusOnWorkspace(ID);
// Update bar info
updateBarInfo();
Debug::log(LOG, "Bar info updated with workspace changed.");
// Wipe animation
startWipeAnimOnWorkspace(OLDWORKSPACE, ID);
return;
}
}
Debug::log(LOG, "New workspace, creating.");
// If we are here it means the workspace is new. Let's create it.
CWorkspace newWorkspace;
newWorkspace.setID(ID);
newWorkspace.setMonitor(MONITOR->ID);
workspaces.push_back(newWorkspace);
activeWorkspaces[MONITOR->ID] = workspaces[workspaces.size() - 1].getID();
LastWindow = -1;
// Update bar info
updateBarInfo();
// Wipe animation
startWipeAnimOnWorkspace(OLDWORKSPACE, ID);
Debug::log(LOG, "New workspace created.");
if (getMonitorFromCursor() && MONITOR->ID != getMonitorFromCursor()->ID)
QueuedPointerWarp = Vector2D(MONITOR->vecPosition + MONITOR->vecSize / 2.f);
// no need for the new dirty, it's empty
}
void CWindowManager::changeToLastWorkspace() {
changeWorkspaceByID(lastActiveWorkspaceID);
}
void CWindowManager::focusOnWorkspace(const int& work) {
const auto PWORKSPACE = getWorkspaceByID(work);
const auto PMONITOR = getMonitorFromWorkspace(work);
Debug::log(LOG, "Focusing on workspace " + std::to_string(work));
if (!PMONITOR) {
Debug::log(ERR, "Orphaned workspace at focusOnWorkspace ???");
return;
}
if (PWORKSPACE) {
if (!PWORKSPACE->getHasFullscreenWindow()) {
Debug::log(LOG, "No fullscreen window");
bool shouldHopToScreen = true;
for (auto& window : windows) {
if (window.getWorkspaceID() == work && window.getDrawable() > 0) {
setFocusedWindow(window.getDrawable());
Debug::log(LOG, "Queueing the warp");
const auto PMONITORFROMCURSOR = getMonitorFromCursor();
if (PMONITORFROMCURSOR)
Debug::log(LOG, "Monitor from cursor: " + std::to_string(PMONITORFROMCURSOR->ID));
if (PMONITORFROMCURSOR && PMONITORFROMCURSOR->ID != PMONITOR->ID)
QueuedPointerWarp = Vector2D(window.getPosition() + window.getSize() / 2.f);
shouldHopToScreen = false;
Debug::log(LOG, "No hopping to new workspace");
break;
}
}
Debug::log(LOG, "Queueing a hop if needed.");
if (shouldHopToScreen)
QueuedPointerWarp = Vector2D(PMONITOR->vecPosition + PMONITOR->vecSize / 2.f);
} else {
const auto PFULLWINDOW = getFullscreenWindowByWorkspace(work);
if (PFULLWINDOW) {
Debug::log(LOG, "Fullscreen window id " + std::to_string(PFULLWINDOW->getDrawable()));
setFocusedWindow(PFULLWINDOW->getDrawable());
if (getMonitorFromCursor() && getMonitorFromCursor()->ID != PMONITOR->ID)
QueuedPointerWarp = Vector2D(PFULLWINDOW->getPosition() + PFULLWINDOW->getSize() / 2.f);
}
}
}
}
void CWindowManager::setAllWindowsDirty() {
for (auto& window : windows) {
window.setDirty(true);
}
}
void CWindowManager::setAllWorkspaceWindowsDirtyByID(int ID) {
int workspaceID = -1;
for (auto& workspace : workspaces) {
if (workspace.getID() == ID) {
workspaceID = workspace.getID();
break;
}
}
if (workspaceID == -1)
return;
for (auto& window : windows) {
if (window.getWorkspaceID() == workspaceID)
window.setDirty(true);
}
}
int CWindowManager::getHighestWorkspaceID() {
int max = -1;
for (auto& workspace : workspaces) {
if (workspace.getID() > max) {
max = workspace.getID();
}
}
return max;
}
CWorkspace* CWindowManager::getWorkspaceByID(int ID) {
for (auto& workspace : workspaces) {
if (workspace.getID() == ID) {
return &workspace;
}
}
return nullptr;
}
SMonitor* CWindowManager::getMonitorFromWindow(CWindow* pWindow) {
return &monitors[pWindow->getMonitor()];
}
SMonitor* CWindowManager::getMonitorFromCursor() {
const auto CURSORPOS = getCursorPos();
for (auto& monitor : monitors) {
if (VECINRECT(CURSORPOS, monitor.vecPosition.x, monitor.vecPosition.y, monitor.vecPosition.x + monitor.vecSize.x, monitor.vecPosition.y + monitor.vecSize.y))
return &monitor;
}
// should never happen tho, I'm using >= and the cursor cant get outside the screens, i hope.
return nullptr;
}
Vector2D CWindowManager::getCursorPos() {
const auto POINTERCOOKIE = xcb_query_pointer(DisplayConnection, Screen->root);
xcb_query_pointer_reply_t* pointerreply = xcb_query_pointer_reply(DisplayConnection, POINTERCOOKIE, NULL);
if (!pointerreply) {
Debug::log(ERR, "Couldn't query pointer.");
free(pointerreply);
return Vector2D(0,0);
}
const auto CURSORPOS = Vector2D(pointerreply->root_x, pointerreply->root_y);
free(pointerreply);
return CURSORPOS;
}
bool CWindowManager::isWorkspaceVisible(int workspaceID) {
if (workspaceID == SCRATCHPAD_ID)
return scratchpadActive;
for (auto& workspace : activeWorkspaces) {
if (workspace == workspaceID)
return true;
}
return false;
}
void CWindowManager::updateBarInfo() {
// IPC
// What we need to send:
// - Workspace data
// - Active Workspace
// If bar disabled, ignore
if (ConfigManager::getInt("bar:enabled") == 0)
return;
SIPCMessageMainToBar message;
auto PMONITOR = getMonitorFromCursor();
if (!PMONITOR) {
Debug::log(ERR, "Monitor was null! (updateBarInfo) Using 0.");
PMONITOR = &monitors[0];
if (monitors.size() == 0) {
Debug::log(ERR, "Not continuing. Monitors size 0.");
return;
}
}
message.activeWorkspace = activeWorkspaces[PMONITOR->ID];
auto winname = getWindowFromDrawable(LastWindow) ? getWindowFromDrawable(LastWindow)->getName() : "";
auto winclassname = getWindowFromDrawable(LastWindow) ? getWindowFromDrawable(LastWindow)->getClassName() : "";
for (auto& c : winname) {
// Remove illegal chars
if (c == '=' || c == '\t')
c = ' ';
}
for (auto& c : winclassname) {
// Remove illegal chars
if (c == '=' || c == '\t')
c = ' ';
}
message.lastWindowName = winname;
message.lastWindowClass = winclassname;
auto* const WORKSPACE = getWorkspaceByID(activeWorkspaces[ConfigManager::getInt("bar:monitor") > monitors.size() ? 0 : ConfigManager::getInt("bar:monitor")]);
if (WORKSPACE)
message.fullscreenOnBar = WORKSPACE->getHasFullscreenWindow();
else
message.fullscreenOnBar = false;
for (auto& workspace : workspaces) {
if (workspace.getID() == SCRATCHPAD_ID)
continue;
message.openWorkspaces.push_back(workspace.getID());
}
IPCSendMessage(m_sIPCBarPipeIn.szPipeName, message);
// Also check if the bar should be made invisibel
// we make it by moving it far far away
// the bar will also stop all updates
if (message.fullscreenOnBar) {
if (lastKnownBarPosition.x == -1 && lastKnownBarPosition.y == -1) {
lastKnownBarPosition = monitors[ConfigManager::getInt("bar:monitor") > monitors.size() ? 0 : ConfigManager::getInt("bar:monitor")].vecPosition;
}
Values[0] = (int)-99999;
Values[1] = (int)-99999;
xcb_configure_window(DisplayConnection, barWindowID, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, Values);
} else {
if (lastKnownBarPosition.x != -1 && lastKnownBarPosition.y != -1) {
Values[0] = (int)lastKnownBarPosition.x;
Values[1] = (int)lastKnownBarPosition.y;
xcb_configure_window(DisplayConnection, barWindowID, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, Values);
}
lastKnownBarPosition = Vector2D(-1, -1);
}
}
void CWindowManager::setAllFloatingWindowsTop() {
for (auto& window : windows) {
if (window.getIsFloating() && !window.getTransient()) {
Values[0] = XCB_STACK_MODE_ABOVE;
xcb_configure_window(g_pWindowManager->DisplayConnection, window.getDrawable(), XCB_CONFIG_WINDOW_STACK_MODE, Values);
window.bringTopRecursiveTransients();
} else {
window.bringTopRecursiveTransients();
}
}
// set the bar topper jic
Values[0] = XCB_STACK_MODE_ABOVE;
xcb_configure_window(g_pWindowManager->DisplayConnection, barWindowID, XCB_CONFIG_WINDOW_STACK_MODE, Values);
}
void CWindowManager::setAWindowTop(xcb_window_t window) {
Values[0] = XCB_STACK_MODE_ABOVE;
const auto COOKIE = xcb_configure_window(g_pWindowManager->DisplayConnection, window, XCB_CONFIG_WINDOW_STACK_MODE, Values);
Events::ignoredEvents.push_back(COOKIE.sequence);
// set the bar topper jic
Values[0] = XCB_STACK_MODE_ABOVE;
xcb_configure_window(g_pWindowManager->DisplayConnection, barWindowID, XCB_CONFIG_WINDOW_STACK_MODE, Values);
}
bool CWindowManager::shouldBeFloatedOnInit(int64_t window) {
// Should be floated also sets some properties
const auto PWINDOW = getWindowFromDrawable(window);
if (!PWINDOW) {
Debug::log(ERR, "shouldBeFloatedOnInit with an invalid window!");
return true;
}
const auto WINCLASS = getClassName(window);
const auto CLASSNAME = WINCLASS.second;
const auto CLASSINSTANCE = WINCLASS.first;
Debug::log(LOG, "New window got class " + (std::string)CLASSINSTANCE + " -> " + CLASSNAME);
xcb_change_property(DisplayConnection, XCB_PROP_MODE_REPLACE, window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, strlen("hypr"), "hypr");
// Role stuff
const auto WINROLE = getRoleName(window);
Debug::log(LOG, "Window opened with a role of " + WINROLE);
// Set it in the pwindow
PWINDOW->setClassName(CLASSNAME);
PWINDOW->setRoleName(WINROLE);
//
// Type stuff
//
PROP(wm_type_cookie, HYPRATOMS["_NET_WM_WINDOW_TYPE"], UINT32_MAX);
if (wm_type_cookiereply == NULL || xcb_get_property_value_length(wm_type_cookiereply) < 1) {
Debug::log(LOG, "No preferred type found. (shouldBeFloatedOnInit)");
} else {
const auto ATOMS = (xcb_atom_t*)xcb_get_property_value(wm_type_cookiereply);
if (!ATOMS) {
Debug::log(ERR, "Atoms not found in preferred type!");
} else {
if (xcbContainsAtom(wm_type_cookiereply, HYPRATOMS["_NET_WM_WINDOW_TYPE_DOCK"])) {
free(wm_type_cookiereply);
return true;
} else if (xcbContainsAtom(wm_type_cookiereply, HYPRATOMS["_NET_WM_WINDOW_TYPE_DIALOG"])
|| xcbContainsAtom(wm_type_cookiereply, HYPRATOMS["_NET_WM_WINDOW_TYPE_TOOLBAR"])
|| xcbContainsAtom(wm_type_cookiereply, HYPRATOMS["_NET_WM_WINDOW_TYPE_UTILITY"])
|| xcbContainsAtom(wm_type_cookiereply, HYPRATOMS["_NET_WM_STATE_MODAL"])
|| xcbContainsAtom(wm_type_cookiereply, HYPRATOMS["_NET_WM_WINDOW_TYPE_SPLASH"])) {
Events::nextWindowCentered = true;
free(wm_type_cookiereply);
return true;
}
}
}
free(wm_type_cookiereply);
//
//
//
// Verify the rules.
for (auto& rule : ConfigManager::getMatchingRules(window)) {
if (rule.szRule == "tile")
return false;
else if (rule.szRule == "float")
return true;
else if (rule.szRule == "nointerventions") {
PWINDOW->setNoInterventions(true);
PWINDOW->setImmovable(true);
return true;
}
}
return false;
}
void CWindowManager::updateActiveWindowName() {
if (!getWindowFromDrawable(LastWindow))
return;
const auto PLASTWINDOW = getWindowFromDrawable(LastWindow);
auto WINNAME = getWindowName(LastWindow);
if (WINNAME != PLASTWINDOW->getName()) {
Debug::log(LOG, "Update, window got name: " + WINNAME);
PLASTWINDOW->setName(WINNAME);
}
}
void CWindowManager::doPostCreationChecks(CWindow* pWindow) {
//
Debug::log(LOG, "Post creation checks init");
const auto window = pWindow->getDrawable();
PROP(wm_type_cookie, HYPRATOMS["_NET_WM_WINDOW_TYPE"], UINT32_MAX);
if (wm_type_cookiereply == NULL || xcb_get_property_value_length(wm_type_cookiereply) < 1) {
Debug::log(LOG, "No preferred type found. (doPostCreationChecks)");
} else {
const auto ATOMS = (xcb_atom_t*)xcb_get_property_value(wm_type_cookiereply);
if (!ATOMS) {
Debug::log(ERR, "Atoms not found in preferred type!");
} else {
if (xcbContainsAtom(wm_type_cookiereply, HYPRATOMS["_NET_WM_STATE_FULLSCREEN"])) {
// set it fullscreen
pWindow->setFullscreen(true);
setFocusedWindow(window);
KeybindManager::toggleActiveWindowFullscreen("");
}
}
}
free(wm_type_cookiereply);
// Check if it has a name
const auto NAME = getClassName(window);
if (NAME.first == "Error" && NAME.second == "Error") {
Debug::log(WARN, "Window created but has a class of NULL?");
}
Debug::log(LOG, "Post creation checks ended");
//
}
void CWindowManager::getICCCMWMProtocols(CWindow* pWindow) {
xcb_icccm_get_wm_protocols_reply_t WMProtocolsReply;
if (!xcb_icccm_get_wm_protocols_reply(DisplayConnection,
xcb_icccm_get_wm_protocols(DisplayConnection, pWindow->getDrawable(), HYPRATOMS["WM_PROTOCOLS"]), &WMProtocolsReply, NULL))
return;
for (auto i = 0; i < (int)WMProtocolsReply.atoms_len; i++) {
if (WMProtocolsReply.atoms[i] == HYPRATOMS["WM_DELETE_WINDOW"])
pWindow->setCanKill(true);
}
xcb_icccm_get_wm_protocols_reply_wipe(&WMProtocolsReply);
}
void CWindowManager::refocusWindowOnClosed() {
const auto PWINDOW = findWindowAtCursor();
// No window or last window valid
if (!PWINDOW || getWindowFromDrawable(LastWindow)) {
setFocusedWindow(Screen->root); //refocus on root
return;
}
LastWindow = PWINDOW->getDrawable();
setFocusedWindow(PWINDOW->getDrawable());
}
void CWindowManager::recalcAllWorkspaces() {
for (auto& workspace : workspaces) {
recalcEntireWorkspace(workspace.getID());
}
}
void CWindowManager::moveWindowToUnmapped(int64_t id) {
if (ConfigManager::getInt("no_unmap_saving") == 1){
closeWindowAllChecks(id);
return;
}
for (auto& w : windows) {
if (w.getDrawable() == id) {
// Move it
unmappedWindows.push_back(w);
removeWindowFromVectorSafe(w.getDrawable());
return;
}
}
}
void CWindowManager::moveWindowToMapped(int64_t id) {
for (auto& w : unmappedWindows) {
if (w.getDrawable() == id) {
// Move it
windows.push_back(w);
// manually remove
auto temp = unmappedWindows;
unmappedWindows.clear();
for (auto& t : temp) {
if (t.getDrawable() != id)
unmappedWindows.push_back(t);
}
windows[windows.size() - 1].setUnderFullscreen(false);
windows[windows.size() - 1].setDirty(true);
windows[windows.size() - 1].setLastUpdatePosition(Vector2D(0,0));
windows[windows.size() - 1].setLastUpdateSize(Vector2D(0,0));
return;
}
}
}
bool CWindowManager::isWindowUnmapped(int64_t id) {
for (auto& w : unmappedWindows) {
if (w.getDrawable() == id) {
return true;
}
}
return false;
}
void CWindowManager::setAllWorkspaceWindowsAboveFullscreen(const int& workspace) {
for (auto& w : windows) {
if (w.getWorkspaceID() == workspace && w.getIsFloating()) {
w.setUnderFullscreen(false);
}
}
}
void CWindowManager::setAllWorkspaceWindowsUnderFullscreen(const int& workspace) {
for (auto& w : windows) {
if (w.getWorkspaceID() == workspace && w.getIsFloating()) {
w.setUnderFullscreen(true);
}
}
}
void CWindowManager::toggleWindowFullscrenn(const int& window) {
const auto PWINDOW = getWindowFromDrawable(window);
if (!PWINDOW)
return;
const auto MONITOR = getMonitorFromWindow(PWINDOW);
if (getWorkspaceByID(activeWorkspaces[MONITOR->ID])->getHasFullscreenWindow() && !PWINDOW->getFullscreen()) {
Debug::log(LOG, "Not making a window fullscreen because there already is one!");
return;
}
setAllWorkspaceWindowsDirtyByID(activeWorkspaces[MONITOR->ID]);
PWINDOW->setFullscreen(!PWINDOW->getFullscreen());
getWorkspaceByID(PWINDOW->getWorkspaceID())->setHasFullscreenWindow(PWINDOW->getFullscreen());
// Fix windows over and below fullscreen.
if (PWINDOW->getFullscreen())
setAllWorkspaceWindowsUnderFullscreen(activeWorkspaces[MONITOR->ID]);
else
setAllWorkspaceWindowsAboveFullscreen(activeWorkspaces[MONITOR->ID]);
// EWMH
Values[0] = HYPRATOMS["_NET_WM_STATE_FULLSCREEN"];
if (PWINDOW->getFullscreen())
xcb_change_property(DisplayConnection, XCB_PROP_MODE_APPEND, window, HYPRATOMS["_NET_WM_STATE"], XCB_ATOM_ATOM, 32, 1, Values);
else
removeAtom(window, HYPRATOMS["_NET_WM_STATE"], HYPRATOMS["_NET_WM_STATE_FULLSCREEN"]);
EWMH::updateWindow(window);
Debug::log(LOG, "Set fullscreen to " + std::to_string(PWINDOW->getFullscreen()) + " for " + std::to_string(window));
}
void CWindowManager::handleClientMessage(xcb_client_message_event_t* E) {
const auto PWINDOW = getWindowFromDrawable(E->window);
if (E->type == HYPRATOMS["_NET_WM_STATE"]) {
// The window wants to change its' state.
// For now we only support FULLSCREEN
if (!PWINDOW){
Debug::log(ERR, "Requested _NET_WM_STATE with an invalid window ID! Ignoring.");
return;
}
if (E->data.data32[1] == HYPRATOMS["_NET_WM_STATE_FULLSCREEN"]) {
if ((PWINDOW->getFullscreen() && (E->data.data32[0] == 0 || E->data.data32[0] == 2))
|| (!PWINDOW->getFullscreen() && (E->data.data32[0] == 1 || E->data.data32[0] == 2))) {
// Toggle fullscreen
toggleWindowFullscrenn(PWINDOW->getDrawable());
}
Debug::log(LOG, "Message recieved to toggle fullscreen for " + std::to_string(PWINDOW->getDrawable()));
}
} else if (E->type == HYPRATOMS["_NET_ACTIVE_WINDOW"]) {
// Change the focused window
if (E->format != 32)
return;
if (!PWINDOW) {
Debug::log(ERR, "Requested _NET_ACTIVE_WINDOW with an invalid window ID! Ignoring.");
return;
}
Debug::log(LOG, "Request to change active window to " + std::to_string(PWINDOW->getDrawable()));
setFocusedWindow(PWINDOW->getDrawable());
Debug::log(LOG, "Message recieved to set active for " + std::to_string(PWINDOW->getDrawable()));
} else if (E->type == HYPRATOMS["_NET_MOVERESIZE_WINDOW"]) {
void *const PEVENT = calloc(32, 1);
xcb_configure_request_event_t* const GENEV = (xcb_configure_request_event_t*)PEVENT;
GENEV->window = E->window;
GENEV->response_type = XCB_CONFIGURE_REQUEST;
GENEV->value_mask = 0;
if (E->data.data32[0] & _NET_MOVERESIZE_WINDOW_X) {
GENEV->value_mask |= XCB_CONFIG_WINDOW_X;
GENEV->x = E->data.data32[1];
}
if (E->data.data32[0] & _NET_MOVERESIZE_WINDOW_Y) {
GENEV->value_mask |= XCB_CONFIG_WINDOW_Y;
GENEV->y = E->data.data32[2];
}
if (E->data.data32[0] & _NET_MOVERESIZE_WINDOW_WIDTH) {
GENEV->value_mask |= XCB_CONFIG_WINDOW_WIDTH;
GENEV->width = E->data.data32[3];
}
if (E->data.data32[0] & _NET_MOVERESIZE_WINDOW_HEIGHT) {
GENEV->value_mask |= XCB_CONFIG_WINDOW_HEIGHT;
GENEV->height = E->data.data32[4];
}
Events::eventConfigure((xcb_generic_event_t*)GENEV);
free(GENEV);
} else if (E->type == HYPRATOMS["_NET_CURRENT_DESKTOP"]) {
// request to change the workspace to something else
// likely a bar
// emitted by xcb_ewmh_request_change_current_desktop
const auto WORK = E->data.data32[0] + 1; // +1 because our first ID is 1 and ewmh's is 0
Debug::log(LOG, "External request to switch to workspace " + std::to_string(WORK));
if (!getWorkspaceByID(WORK)) {
Debug::log(ERR, "Workspace ID " + std::to_string(WORK) + " does NOT exist! Ignoring.");
return;
}
changeWorkspaceByID(WORK);
}
}
void CWindowManager::recalcAllDocks() {
for (auto& mon : monitors) {
mon.vecReservedTopLeft = {0, 0};
mon.vecReservedBottomRight = {0, 0};
setAllWorkspaceWindowsDirtyByID(activeWorkspaces[mon.ID]);
}
for (auto& w : windows) {
if (!w.getDock() || w.getDead() || !w.getIsFloating())
continue;
const auto MONITOR = &monitors[w.getMonitor()];
const auto VERTICAL = w.getSize().x / w.getSize().y < 1;
if (VERTICAL) {
if (w.getPosition().x < MONITOR->vecSize.x / 2.f + MONITOR->vecPosition.x) {
// Left
MONITOR->vecReservedTopLeft = Vector2D(w.getSize().x, 0);
} else {
// Right
MONITOR->vecReservedBottomRight = Vector2D(w.getSize().x, 0);
}
} else {
if (w.getPosition().y < MONITOR->vecSize.y / 2.f + MONITOR->vecPosition.y) {
// Top
MONITOR->vecReservedTopLeft = Vector2D(0, w.getSize().y);
} else {
// Bottom
MONITOR->vecReservedBottomRight = Vector2D(0, w.getSize().y);
}
}
// Move it
Values[0] = w.getDefaultPosition().x;
Values[1] = w.getDefaultPosition().y;
xcb_configure_window(DisplayConnection, w.getDrawable(), XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, Values);
Values[0] = w.getDefaultSize().x;
Values[1] = w.getDefaultSize().y;
xcb_configure_window(DisplayConnection, w.getDrawable(), XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, Values);
}
}
void CWindowManager::startWipeAnimOnWorkspace(const int& oldwork, const int& newwork) {
const auto PMONITOR = getMonitorFromWorkspace(newwork);
for (auto& work : workspaces) {
if (work.getID() == oldwork) {
if (ConfigManager::getInt("animations:workspaces") == 1)
work.setCurrentOffset(Vector2D(0,0));
else
work.setCurrentOffset(Vector2D(150000, 150000));
work.setGoalOffset(Vector2D(PMONITOR->vecSize.x, 0));
work.setAnimationInProgress(true);
} else if (work.getID() == newwork) {
if (ConfigManager::getInt("animations:workspaces") == 1)
work.setCurrentOffset(Vector2D(-PMONITOR->vecSize.x, 0));
else
work.setCurrentOffset(Vector2D(0, 0));
work.setGoalOffset(Vector2D(0, 0));
work.setAnimationInProgress(true);
}
}
}
void CWindowManager::dispatchQueuedWarp() {
if (QueuedPointerWarp.x == -1 && QueuedPointerWarp.y == -1)
return;
warpCursorTo(QueuedPointerWarp);
QueuedPointerWarp = Vector2D(-1,-1);
}
bool CWindowManager::shouldBeManaged(const int& window) {
const auto WINDOWATTRS = xcb_get_window_attributes_reply(DisplayConnection, xcb_get_window_attributes(DisplayConnection, window), NULL);
if (!WINDOWATTRS) {
Debug::log(LOG, "Skipping: window attributes null");
return false;
}
if (WINDOWATTRS->override_redirect) {
Debug::log(LOG, "Skipping: override redirect");
return false;
}
const auto GEOMETRY = xcb_get_geometry_reply(DisplayConnection, xcb_get_geometry(DisplayConnection, window), NULL);
if (!GEOMETRY) {
Debug::log(LOG, "Skipping: No geometry");
return false;
}
Debug::log(LOG, "shouldBeManaged passed!");
return true;
}
SMonitor* CWindowManager::getMonitorFromCoord(const Vector2D coord) {
for (auto& m : monitors) {
if (VECINRECT(coord, m.vecPosition.x, m.vecPosition.y, m.vecPosition.x + m.vecSize.x, m.vecPosition.y + m.vecSize.y))
return &m;
}
return nullptr;
}
void CWindowManager::changeSplitRatioCurrent(const char& dir) {
const auto CURRENT = getWindowFromDrawable(LastWindow);
if (!CURRENT) {
Debug::log(LOG, "Cannot change split ratio when lastwindow NULL.");
return;
}
const auto PARENT = getWindowFromDrawable(CURRENT->getParentNodeID());
if (!PARENT) {
Debug::log(LOG, "Cannot change split ratio when parent NULL.");
return;
}
switch(dir) {
case '+':
PARENT->setSplitRatio(PARENT->getSplitRatio() + 0.05f);
break;
case '-':
PARENT->setSplitRatio(PARENT->getSplitRatio() - 0.05f);
break;
default:
Debug::log(ERR, "changeSplitRatioCurrent called with an invalid dir!");
return;
}
PARENT->setSplitRatio(std::clamp(PARENT->getSplitRatio(), 0.1f, 1.9f));
Debug::log(LOG, "Changed SplitRatio of " + std::to_string(PARENT->getDrawable()) + " to " + std::to_string(PARENT->getSplitRatio()) + " (" + dir + ")" );
recalcEntireWorkspace(CURRENT->getWorkspaceID());
}
void CWindowManager::getICCCMSizeHints(CWindow* pWindow) {
xcb_size_hints_t sizeHints;
const auto succ = xcb_icccm_get_wm_normal_hints_reply(g_pWindowManager->DisplayConnection, xcb_icccm_get_wm_normal_hints_unchecked(g_pWindowManager->DisplayConnection, pWindow->getDrawable()), &sizeHints, NULL);
if (succ) {
auto NEWSIZE = Vector2D(std::max(std::max(sizeHints.width, (int32_t)pWindow->getDefaultSize().x), std::max(sizeHints.max_width > g_pWindowManager->monitors[pWindow->getMonitor()].vecSize.x ? 0 : sizeHints.max_width, sizeHints.base_width)),
std::max(std::max(sizeHints.height, (int32_t)pWindow->getDefaultSize().y), std::max(sizeHints.max_height > g_pWindowManager->monitors[pWindow->getMonitor()].vecSize.y ? 0 : sizeHints.max_height, sizeHints.base_height)));
pWindow->setPseudoSize(NEWSIZE);
} else {
Debug::log(ERR, "ICCCM Size Hints failed.");
}
}
void CWindowManager::processCursorDeltaOnWindowResizeTiled(CWindow* pWindow, const Vector2D& pointerDelta) {
// this resizes the window based on cursor movement,
// basically like a mouse-ver of splitratio
if (!pWindow)
return;
// TODO: support master-stack
if (ConfigManager::getInt("layout") == LAYOUT_MASTER){
Debug::log(WARN, "processCursorDeltaOnWindowResizeTiled does NOT support MASTER yet. Ignoring.");
return;
}
// Construct an allowed delta movement
const auto PMONITOR = getMonitorFromWindow(pWindow);
const bool DISPLAYLEFT = STICKS(pWindow->getPosition().x, PMONITOR->vecPosition.x);
const bool DISPLAYRIGHT = STICKS(pWindow->getPosition().x + pWindow->getSize().x, PMONITOR->vecPosition.x + PMONITOR->vecSize.x);
const bool DISPLAYTOP = STICKS(pWindow->getPosition().y, PMONITOR->vecPosition.y);
const bool DISPLAYBOTTOM = STICKS(pWindow->getPosition().y + pWindow->getSize().y, PMONITOR->vecPosition.y + PMONITOR->vecSize.y);
Vector2D allowedMovement = pointerDelta;
if (DISPLAYLEFT && DISPLAYRIGHT)
allowedMovement.x = 0;
if (DISPLAYTOP && DISPLAYBOTTOM)
allowedMovement.y = 0;
// Get the correct containers to apply the splitratio to
const auto PPARENT = getWindowFromDrawable(pWindow->getParentNodeID());
// If there is no parent we ignore the request (only window)
if (!PPARENT)
return;
const bool PARENTSIDEBYSIDE = PPARENT->getSize().x / PPARENT->getSize().y > 1;
// Get the parent's parent.
const auto PPARENT2 = getWindowFromDrawable(PPARENT->getParentNodeID());
// if there is no parent, we have 2 windows only and have the ability to drag in only one direction.
if (!PPARENT2) {
if (PARENTSIDEBYSIDE) {
// splitratio adjust for pixels
allowedMovement.x *= 2.f / PPARENT->getSize().x;
PPARENT->setSplitRatio(std::clamp(PPARENT->getSplitRatio() + allowedMovement.x, (double)0.05f, (double)1.95f));
PPARENT->recalcSizePosRecursive();
} else {
allowedMovement.y *= 2.f / PPARENT->getSize().y;
PPARENT->setSplitRatio(std::clamp(PPARENT->getSplitRatio() + allowedMovement.y, (double)0.05f, (double)1.95f));
PPARENT->recalcSizePosRecursive();
}
return;
}
// if there is a parent, we have 2 axes of freedom
const auto SIDECONTAINER = PARENTSIDEBYSIDE ? PPARENT : PPARENT2;
const auto TOPCONTAINER = PARENTSIDEBYSIDE ? PPARENT2 : PPARENT;
allowedMovement.x *= 2.f / SIDECONTAINER->getSize().x;
allowedMovement.y *= 2.f / TOPCONTAINER->getSize().x;
SIDECONTAINER->setSplitRatio(std::clamp(SIDECONTAINER->getSplitRatio() + allowedMovement.x, (double)0.05f, (double)1.95f));
TOPCONTAINER->setSplitRatio(std::clamp(TOPCONTAINER->getSplitRatio() + allowedMovement.y, (double)0.05f, (double)1.95f));
SIDECONTAINER->recalcSizePosRecursive();
TOPCONTAINER->recalcSizePosRecursive();
}