1 //
2 // WinLayouter.cc for pekwm
3 // Copyright (C) 2021 Claes Nästén
4 // Copyright © 2012-2013 Andreas Schlick <ioerror{@}lavabit{.}com>
5 //
6 // This program is licensed under the GNU GPL.
7 // See the LICENSE file for more information.
8 //
9 
10 #include "pekwm.hh"
11 #include "WinLayouter.hh"
12 
13 #include "Client.hh"
14 #include "Frame.hh"
15 #include "Util.hh"
16 #include "ManagerWindows.hh"
17 #include "Workspaces.hh"
18 #include "X11Util.hh"
19 #include "X11.hh"
20 
21 static PWinObj*
isEmptySpace(int x,int y,const PWinObj * wo,std::vector<PWinObj * > & wvec)22 isEmptySpace(int x, int y, const PWinObj* wo, std::vector<PWinObj*> &wvec)
23 {
24 	if (! wo) {
25 		return 0;
26 	}
27 
28 	if (wvec.empty()) {
29 		// say that it's placed, now check if we are wrong!
30 		Workspaces::iterator it(Workspaces::begin());
31 		Workspaces::iterator end(Workspaces::end());
32 		for (; it != end; ++it) {
33 			// Skip ourselves, non-mapped and desktop objects. Iconified means
34 			// skip placement.
35 			if (wo == (*it) || ! (*it)->isMapped() || (*it)->isIconified()
36 			    || ((*it)->getLayer() == LAYER_DESKTOP)) {
37 				continue;
38 			}
39 
40 			// Also skip windows tagged as Maximized as they cause us to
41 			// automatically fail.
42 			if ((*it)->getType() == PWinObj::WO_FRAME) {
43 				Client *client = static_cast<Frame*>((*it))->getActiveClient();
44 				if (client &&
45 				    (client->isFullscreen()
46 				     || (client->isMaximizedVert() && client->isMaximizedHorz()))) {
47 					continue;
48 				}
49 			}
50 
51 			wvec.push_back(*it);
52 		}
53 	}
54 
55 	for (unsigned i=0; i < wvec.size(); ++i) {
56 		// Check if we are "intruding" on some other window's place
57 		if ((wvec[i]->getX() < signed(x + wo->getWidth())) &&
58 		    (signed(wvec[i]->getX() + wvec[i]->getWidth()) > x) &&
59 		    (wvec[i]->getY() < signed(y + wo->getHeight())) &&
60 		    (signed(wvec[i]->getY() + wvec[i]->getHeight()) > y)) {
61 			return wvec[i];
62 		}
63 	}
64 
65 	return 0; // we passed the test, no frames in the way
66 }
67 
68 //! @brief Tries to find empty space to place the client in
69 //! @return true if client got placed, else false
70 //! @todo What should we do about Xinerama as when we don't have it enabled we care about the struts.
71 class LayouterSmart : public WinLayouter {
72 public:
LayouterSmart()73 	LayouterSmart() : WinLayouter() {}
~LayouterSmart()74 	virtual ~LayouterSmart() {}
75 
76 private:
layout_impl(Frame * wo)77 	virtual bool layout_impl(Frame *wo)
78 	{
79 		if (! wo) {
80 			return true;
81 		}
82 
83 		PWinObj *wo_e;
84 		bool placed = false;
85 		std::vector<PWinObj*> wvec;
86 
87 		int step_x = (pekwm::config()->getPlacementLtR()) ? 1 : -1;
88 		int step_y = (pekwm::config()->getPlacementTtB()) ? 1 : -1;
89 		int offset_x = (pekwm::config()->getPlacementLtR())
90 			? pekwm::config()->getPlacementOffsetX()
91 			: -pekwm::config()->getPlacementOffsetX();
92 		int offset_y = (pekwm::config()->getPlacementTtB())
93 			? pekwm::config()->getPlacementOffsetY()
94 			: -pekwm::config()->getPlacementOffsetY();
95 		int start_x, start_y, test_x = 0, test_y = 0;
96 
97 		// Wrap these up, to get proper checking of space.
98 		uint wo_width = wo->getWidth() + pekwm::config()->getPlacementOffsetX();
99 		uint wo_height = wo->getHeight() + pekwm::config()->getPlacementOffsetY();
100 
101 		start_x = pekwm::config()->getPlacementLtR() ?
102 			_gm.x : _gm.x + _gm.width - wo_width;
103 		start_y = pekwm::config()->getPlacementTtB() ?
104 			_gm.y : _gm.y + _gm.height - wo_height;
105 
106 		if (pekwm::config()->getPlacementRow()) { // row placement
107 			test_y = start_y;
108 			while (! placed && (pekwm::config()->getPlacementTtB()
109 					    ? test_y + wo_height <= _gm.y + _gm.height
110 					    : test_y >= _gm.y)) {
111 				test_x = start_x;
112 				while (! placed && (pekwm::config()->getPlacementLtR()
113 						    ? test_x + wo_width <= _gm.x + _gm.width
114 						    : test_x >= _gm.x)) {
115 					// see if we can place the window here
116 					if ((wo_e = isEmptySpace(test_x, test_y, wo, wvec))) {
117 						placed = false;
118 						test_x = pekwm::config()->getPlacementLtR() ?
119 							wo_e->getX() + wo_e->getWidth() : wo_e->getX() - wo_width;
120 					} else {
121 						placed = true;
122 						wo->move(test_x + offset_x, test_y + offset_y);
123 					}
124 				}
125 				test_y += step_y;
126 			}
127 		} else { // column placement
128 			test_x = start_x;
129 			while (! placed && (pekwm::config()->getPlacementLtR()
130 					    ? test_x + wo_width <= _gm.x + _gm.width
131 					    : test_x >= _gm.x)) {
132 				test_y = start_y;
133 				while (! placed && (pekwm::config()->getPlacementTtB()
134 						    ? test_y + wo_height <= _gm.y + _gm.height
135 						    : test_y >= _gm.y)) {
136 					// see if we can place the window here
137 					if ((wo_e = isEmptySpace(test_x, test_y, wo, wvec))) {
138 						placed = false;
139 						test_y = pekwm::config()->getPlacementTtB() ?
140 							wo_e->getY() + wo_e->getHeight() : wo_e->getY() - wo_height;
141 					} else {
142 						placed = true;
143 						wo->move(test_x + offset_x, test_y + offset_y);
144 					}
145 				}
146 				test_x += step_x;
147 			}
148 		}
149 		return placed;
150 	}
151 };
152 
153 //! @brief Places the wo in a corner of the screen not under the pointer
154 class LayouterMouseNotUnder : public WinLayouter {
155 public:
LayouterMouseNotUnder()156 	LayouterMouseNotUnder() : WinLayouter() {}
~LayouterMouseNotUnder()157 	virtual ~LayouterMouseNotUnder() {}
158 
layout_impl(Frame * wo)159 	virtual bool layout_impl(Frame *wo)
160 	{
161 		if (! wo) {
162 			return true;
163 		}
164 
165 		// compensate for head offset
166 		_ptr_x -= _gm.x;
167 		_ptr_y -= _gm.y;
168 
169 		// divide the screen into four rectangles using the pointer as divider
170 		if (wo->getWidth() < unsigned(_ptr_x) && wo->getHeight() < _gm.height) {
171 			wo->move(_gm.x, _gm.y);
172 			return true;
173 		}
174 
175 		if (wo->getWidth() < _gm.width && wo->getHeight() < unsigned(_ptr_y)) {
176 			wo->move(_gm.x + _gm.width - wo->getWidth(), _gm.y);
177 			return true;
178 		}
179 
180 		if (wo->getWidth() < _gm.width - _ptr_x && wo->getHeight() < _gm.height) {
181 			wo->move(_gm.x + _gm.width - wo->getWidth(), _gm.y + _gm.height - wo->getHeight());
182 			return true;
183 		}
184 
185 		if (wo->getWidth() < _gm.width && wo->getHeight() < _gm.height - _ptr_y) {
186 			wo->move(_gm.x, _gm.y + _gm.height - wo->getHeight());
187 			return true;
188 		}
189 		return false;
190 	}
191 };
192 
193 //! @brief Places the client centered under the mouse
194 class LayouterMouseCentred : public WinLayouter {
195 public:
LayouterMouseCentred()196 	LayouterMouseCentred() : WinLayouter() {}
~LayouterMouseCentred()197 	~LayouterMouseCentred() {}
198 
199 private:
layout_impl(Frame * wo)200 	virtual bool layout_impl(Frame *wo)
201 	{
202 		if (wo) {
203 			Geometry gm(_ptr_x - (wo->getWidth() / 2), _ptr_y - (wo->getHeight() / 2),
204 				    wo->getWidth(), wo->getHeight());
205 
206 			// make sure it's within the screen's border
207 			pekwm::rootWo()->placeInsideScreen(gm);
208 			wo->move(gm.x, gm.y);
209 		}
210 		return true;
211 	}
212 };
213 
214 //! @brief Places the client like the menu gets placed
215 class LayouterMouseTopLeft : public WinLayouter {
216 public:
LayouterMouseTopLeft()217 	LayouterMouseTopLeft() : WinLayouter() {}
218 
219 private:
layout_impl(Frame * wo)220 	virtual bool layout_impl(Frame *wo)
221 	{
222 		if (wo) {
223 			Geometry gm(_ptr_x, _ptr_y, wo->getWidth(), wo->getHeight());
224 			pekwm::rootWo()->placeInsideScreen(gm);
225 			wo->move(gm.x, gm.y);
226 		}
227 		return true;
228 	}
229 };
230 
231 void
layout(Frame * frame,Window parent)232 WinLayouter::layout(Frame *frame, Window parent)
233 {
234 	if (frame) {
235 		frame->updateDecor();
236 	}
237 
238 	if (frame && parent != None
239 	    && pekwm::config()->placeTransOnParent()
240 	    && placeOnParent(frame, parent)) {
241 		return;
242 	}
243 
244 	// update pointer position cache, used in layout models.
245 	X11::getMousePosition(_ptr_x, _ptr_y);
246 
247 	CurrHeadSelector chs = pekwm::config()->getCurrHeadSelector();
248 	int head_nr = X11Util::getCurrHead(chs);
249 	pekwm::rootWo()->getHeadInfoWithEdge(head_nr, _gm);
250 
251 	// Collect the information which head has a fullscreen window.
252 	// To be conservative for now we ignore fullscreen windows on
253 	// the desktop or normal layer, because it might be a file
254 	// manager in desktop mode, for example.
255 	std::vector<bool> fsHead(X11::getNumHeads(), false);
256 	Workspaces::const_iterator it(Workspaces::begin()),
257 		end(Workspaces::end());
258 	for (; it != end; ++it) {
259 		if ((*it)->isMapped() && (*it)->getType() == PWinObj::WO_FRAME) {
260 			Client *client = static_cast<Frame*>(*it)->getActiveClient();
261 			if (client && client->isFullscreen()
262 			    && client->getLayer()>LAYER_NORMAL) {
263 				fsHead[client->getHead()] = true;
264 			}
265 		}
266 	}
267 
268 	// Try to place the window
269 	int i = head_nr;
270 	do {
271 		if (! fsHead[i]) {
272 			pekwm::rootWo()->getHeadInfoWithEdge(i, _gm);
273 			if (layout_impl(frame)) {
274 				return;
275 			}
276 		}
277 		i = (i+1)%X11::getNumHeads();
278 	} while (i != head_nr);
279 
280 	// We failed to place the window, so put it in the top-left
281 	// corner but still try to avoid heads with a fullscreen window on it.
282 	i = head_nr;
283 	do {
284 		if (! fsHead[i]) {
285 			break;
286 		}
287 		i = (i+1)%X11::getNumHeads();
288 	} while (i != head_nr);
289 	pekwm::rootWo()->getHeadInfoWithEdge(i, _gm);
290 	frame->move(_gm.x, _gm.y);
291 }
292 
293 bool
placeOnParent(Frame * wo,Window parent)294 WinLayouter::placeOnParent(Frame *wo, Window parent)
295 {
296 	PWinObj *wo_s = PWinObj::findPWinObj(parent);
297 	if (wo_s) {
298 		wo->move(wo_s->getX() + wo_s->getWidth() / 2 - wo->getWidth() / 2,
299 			 wo_s->getY() + wo_s->getHeight() / 2 - wo->getHeight() / 2);
300 		return true;
301 	}
302 
303 	return false;
304 }
305 
306 int WinLayouter::_ptr_x;
307 int WinLayouter::_ptr_y;
308 Geometry WinLayouter::_gm;
309 
WinLayouterFactory(std::string l)310 WinLayouter *WinLayouterFactory(std::string l) {
311 	Util::to_upper(l);
312 	const char *str = l.c_str();
313 
314 	if (! strcmp(str, "SMART")) {
315 		return new LayouterSmart;
316 	}
317 	if (! strcmp(str, "MOUSENOTUNDER")) {
318 		return new LayouterMouseNotUnder;
319 	}
320 	if (! strcmp(str, "MOUSECENTERED")) {
321 		return new LayouterMouseCentred;
322 	}
323 	if (! strcmp(str, "MOUSETOPLEFT")) {
324 		return new LayouterMouseTopLeft;
325 	}
326 	return 0;
327 }
328