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