1 | /******************************************************************** |
2 | KWin - the KDE window manager |
3 | This file is part of the KDE project. |
4 | |
5 | Copyright (C) 1999, 2000 Matthias Ettrich <ettrich@kde.org> |
6 | Copyright (C) 2003 Lubos Lunak <l.lunak@kde.org> |
7 | Copyright (C) 2009 Lucas Murray <lmurray@undefinedfire.com> |
8 | |
9 | This program is free software; you can redistribute it and/or modify |
10 | it under the terms of the GNU General Public License as published by |
11 | the Free Software Foundation; either version 2 of the License, or |
12 | (at your option) any later version. |
13 | |
14 | This program is distributed in the hope that it will be useful, |
15 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
17 | GNU General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU General Public License |
20 | along with this program. If not, see <http://www.gnu.org/licenses/>. |
21 | *********************************************************************/ |
22 | |
23 | /* |
24 | |
25 | This file contains things relevant to geometry, i.e. workspace size, |
26 | window positions and window sizes. |
27 | |
28 | */ |
29 | |
30 | #include "client.h" |
31 | #include "composite.h" |
32 | #include "cursor.h" |
33 | #include "netinfo.h" |
34 | #include "workspace.h" |
35 | |
36 | #include <kapplication.h> |
37 | #include <kglobal.h> |
38 | #include <kwindowsystem.h> |
39 | |
40 | #include "placement.h" |
41 | #include "geometrytip.h" |
42 | #include "rules.h" |
43 | #include "screens.h" |
44 | #include "effects.h" |
45 | #ifdef KWIN_BUILD_SCREENEDGES |
46 | #include "screenedge.h" |
47 | #endif |
48 | #include <QDesktopWidget> |
49 | #include <QPainter> |
50 | #include <QVarLengthArray> |
51 | #include <QX11Info> |
52 | |
53 | #include <KDE/KGlobalSettings> |
54 | #include "outline.h" |
55 | |
56 | namespace KWin |
57 | { |
58 | |
59 | static inline int sign(int v) { |
60 | return (v > 0) - (v < 0); |
61 | } |
62 | |
63 | //******************************************** |
64 | // Workspace |
65 | //******************************************** |
66 | |
67 | extern int screen_number; |
68 | extern bool is_multihead; |
69 | |
70 | /*! |
71 | Resizes the workspace after an XRANDR screen size change |
72 | */ |
73 | void Workspace::desktopResized() |
74 | { |
75 | QRect geom; |
76 | for (int i = 0; i < screens()->count(); i++) { |
77 | //do NOT use - QApplication::desktop()->screenGeometry(i) there could be a virtual geometry |
78 | // see bug #302783 |
79 | geom |= QApplication::desktop()->screen(i)->geometry(); |
80 | } |
81 | NETSize desktop_geometry; |
82 | desktop_geometry.width = geom.width(); |
83 | desktop_geometry.height = geom.height(); |
84 | rootInfo()->setDesktopGeometry(-1, desktop_geometry); |
85 | |
86 | updateClientArea(); |
87 | saveOldScreenSizes(); // after updateClientArea(), so that one still uses the previous one |
88 | |
89 | // TODO: emit a signal instead and remove the deep function calls into edges and effects |
90 | #ifdef KWIN_BUILD_SCREENEDGES |
91 | ScreenEdges::self()->recreateEdges(); |
92 | #endif |
93 | |
94 | if (effects) { |
95 | static_cast<EffectsHandlerImpl*>(effects)->desktopResized(geom.size()); |
96 | } |
97 | |
98 | //Update the shape of the overlay window to fix redrawing of unredirected windows. bug#305781 |
99 | m_compositor->checkUnredirect(true); |
100 | } |
101 | |
102 | void Workspace::saveOldScreenSizes() |
103 | { |
104 | olddisplaysize = QSize( displayWidth(), displayHeight()); |
105 | oldscreensizes.clear(); |
106 | for( int i = 0; |
107 | i < screens()->count(); |
108 | ++i ) |
109 | oldscreensizes.append( screens()->geometry( i )); |
110 | } |
111 | |
112 | /*! |
113 | Updates the current client areas according to the current clients. |
114 | |
115 | If the area changes or force is true, the new areas are propagated to the world. |
116 | |
117 | The client area is the area that is available for clients (that |
118 | which is not taken by windows like panels, the top-of-screen menu |
119 | etc). |
120 | |
121 | \sa clientArea() |
122 | */ |
123 | |
124 | void Workspace::updateClientArea(bool force) |
125 | { |
126 | const Screens *s = Screens::self(); |
127 | int nscreens = s->count(); |
128 | const int numberOfDesktops = VirtualDesktopManager::self()->count(); |
129 | kDebug(1212) << "screens: " << nscreens << "desktops: " << numberOfDesktops; |
130 | QVector< QRect > new_wareas(numberOfDesktops + 1); |
131 | QVector< StrutRects > new_rmoveareas(numberOfDesktops + 1); |
132 | QVector< QVector< QRect > > new_sareas(numberOfDesktops + 1); |
133 | QVector< QRect > screens(nscreens); |
134 | QRect desktopArea; |
135 | for (int i = 0; i < nscreens; i++) { |
136 | desktopArea |= s->geometry(i); |
137 | } |
138 | for (int iS = 0; |
139 | iS < nscreens; |
140 | iS ++) { |
141 | screens [iS] = s->geometry(iS); |
142 | } |
143 | for (int i = 1; |
144 | i <= numberOfDesktops; |
145 | ++i) { |
146 | new_wareas[ i ] = desktopArea; |
147 | new_sareas[ i ].resize(nscreens); |
148 | for (int iS = 0; |
149 | iS < nscreens; |
150 | iS ++) |
151 | new_sareas[ i ][ iS ] = screens[ iS ]; |
152 | } |
153 | for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) { |
154 | if (!(*it)->hasStrut()) |
155 | continue; |
156 | QRect r = (*it)->adjustedClientArea(desktopArea, desktopArea); |
157 | StrutRects strutRegion = (*it)->strutRects(); |
158 | |
159 | // Ignore offscreen xinerama struts. These interfere with the larger monitors on the setup |
160 | // and should be ignored so that applications that use the work area to work out where |
161 | // windows can go can use the entire visible area of the larger monitors. |
162 | // This goes against the EWMH description of the work area but it is a toss up between |
163 | // having unusable sections of the screen (Which can be quite large with newer monitors) |
164 | // or having some content appear offscreen (Relatively rare compared to other). |
165 | bool hasOffscreenXineramaStrut = (*it)->hasOffscreenXineramaStrut(); |
166 | |
167 | if ((*it)->isOnAllDesktops()) { |
168 | for (int i = 1; |
169 | i <= numberOfDesktops; |
170 | ++i) { |
171 | if (!hasOffscreenXineramaStrut) |
172 | new_wareas[ i ] = new_wareas[ i ].intersected(r); |
173 | new_rmoveareas[ i ] += strutRegion; |
174 | for (int iS = 0; |
175 | iS < nscreens; |
176 | iS ++) { |
177 | new_sareas[ i ][ iS ] = new_sareas[ i ][ iS ].intersected( |
178 | (*it)->adjustedClientArea(desktopArea, screens[ iS ])); |
179 | } |
180 | } |
181 | } else { |
182 | if (!hasOffscreenXineramaStrut) |
183 | new_wareas[(*it)->desktop()] = new_wareas[(*it)->desktop()].intersected(r); |
184 | new_rmoveareas[(*it)->desktop()] += strutRegion; |
185 | for (int iS = 0; |
186 | iS < nscreens; |
187 | iS ++) { |
188 | // kDebug (1212) << "adjusting new_sarea: " << screens[ iS ]; |
189 | new_sareas[(*it)->desktop()][ iS ] |
190 | = new_sareas[(*it)->desktop()][ iS ].intersected( |
191 | (*it)->adjustedClientArea(desktopArea, screens[ iS ])); |
192 | } |
193 | } |
194 | } |
195 | #if 0 |
196 | for (int i = 1; |
197 | i <= numberOfDesktops(); |
198 | ++i) { |
199 | for (int iS = 0; |
200 | iS < nscreens; |
201 | iS ++) |
202 | kDebug(1212) << "new_sarea: " << new_sareas[ i ][ iS ]; |
203 | } |
204 | #endif |
205 | |
206 | bool changed = force; |
207 | |
208 | if (screenarea.isEmpty()) |
209 | changed = true; |
210 | |
211 | for (int i = 1; |
212 | !changed && i <= numberOfDesktops; |
213 | ++i) { |
214 | if (workarea[ i ] != new_wareas[ i ]) |
215 | changed = true; |
216 | if (restrictedmovearea[ i ] != new_rmoveareas[ i ]) |
217 | changed = true; |
218 | if (screenarea[ i ].size() != new_sareas[ i ].size()) |
219 | changed = true; |
220 | for (int iS = 0; |
221 | !changed && iS < nscreens; |
222 | iS ++) |
223 | if (new_sareas[ i ][ iS ] != screenarea [ i ][ iS ]) |
224 | changed = true; |
225 | } |
226 | |
227 | if (changed) { |
228 | workarea = new_wareas; |
229 | oldrestrictedmovearea = restrictedmovearea; |
230 | restrictedmovearea = new_rmoveareas; |
231 | screenarea = new_sareas; |
232 | NETRect r; |
233 | for (int i = 1; i <= numberOfDesktops; i++) { |
234 | r.pos.x = workarea[ i ].x(); |
235 | r.pos.y = workarea[ i ].y(); |
236 | r.size.width = workarea[ i ].width(); |
237 | r.size.height = workarea[ i ].height(); |
238 | rootInfo()->setWorkArea(i, r); |
239 | } |
240 | |
241 | for (ClientList::ConstIterator it = clients.constBegin(); |
242 | it != clients.constEnd(); |
243 | ++it) |
244 | (*it)->checkWorkspacePosition(); |
245 | for (ClientList::ConstIterator it = desktops.constBegin(); |
246 | it != desktops.constEnd(); |
247 | ++it) |
248 | (*it)->checkWorkspacePosition(); |
249 | |
250 | oldrestrictedmovearea.clear(); // reset, no longer valid or needed |
251 | } |
252 | |
253 | kDebug(1212) << "Done." ; |
254 | } |
255 | |
256 | void Workspace::updateClientArea() |
257 | { |
258 | updateClientArea(false); |
259 | } |
260 | |
261 | |
262 | /*! |
263 | returns the area available for clients. This is the desktop |
264 | geometry minus windows on the dock. Placement algorithms should |
265 | refer to this rather than geometry(). |
266 | |
267 | \sa geometry() |
268 | */ |
269 | |
270 | QRect Workspace::clientArea(clientAreaOption opt, int screen, int desktop) const |
271 | { |
272 | if (desktop == NETWinInfo::OnAllDesktops || desktop == 0) |
273 | desktop = VirtualDesktopManager::self()->current(); |
274 | if (screen == -1) |
275 | screen = screens()->current(); |
276 | |
277 | QRect sarea, warea; |
278 | |
279 | if (is_multihead) { |
280 | sarea = (!screenarea.isEmpty() |
281 | && screen < screenarea[ desktop ].size()) // screens may be missing during KWin initialization or screen config changes |
282 | ? screenarea[ desktop ][ screen_number ] |
283 | : screens()->geometry(screen_number); |
284 | warea = workarea[ desktop ].isNull() |
285 | ? screens()->geometry(screen_number) |
286 | : workarea[ desktop ]; |
287 | } else { |
288 | sarea = (!screenarea.isEmpty() |
289 | && screen < screenarea[ desktop ].size()) // screens may be missing during KWin initialization or screen config changes |
290 | ? screenarea[ desktop ][ screen ] |
291 | : screens()->geometry(screen); |
292 | warea = workarea[ desktop ].isNull() |
293 | ? QRect(0, 0, displayWidth(), displayHeight()) |
294 | : workarea[ desktop ]; |
295 | } |
296 | |
297 | switch(opt) { |
298 | case MaximizeArea: |
299 | case PlacementArea: |
300 | return sarea; |
301 | case MaximizeFullArea: |
302 | case FullScreenArea: |
303 | case MovementArea: |
304 | case ScreenArea: |
305 | if (is_multihead) |
306 | return screens()->geometry(screen_number); |
307 | else |
308 | return screens()->geometry(screen); |
309 | case WorkArea: |
310 | if (is_multihead) |
311 | return sarea; |
312 | else |
313 | return warea; |
314 | case FullArea: |
315 | return QRect(0, 0, displayWidth(), displayHeight()); |
316 | } |
317 | abort(); |
318 | } |
319 | |
320 | |
321 | QRect Workspace::clientArea(clientAreaOption opt, const QPoint& p, int desktop) const |
322 | { |
323 | return clientArea(opt, screens()->number(p), desktop); |
324 | } |
325 | |
326 | QRect Workspace::clientArea(clientAreaOption opt, const Client* c) const |
327 | { |
328 | return clientArea(opt, c->geometry().center(), c->desktop()); |
329 | } |
330 | |
331 | QRegion Workspace::restrictedMoveArea(int desktop, StrutAreas areas) const |
332 | { |
333 | if (desktop == NETWinInfo::OnAllDesktops || desktop == 0) |
334 | desktop = VirtualDesktopManager::self()->current(); |
335 | QRegion region; |
336 | foreach (const StrutRect & rect, restrictedmovearea[desktop]) |
337 | if (areas & rect.area()) |
338 | region += rect; |
339 | return region; |
340 | } |
341 | |
342 | bool Workspace::inUpdateClientArea() const |
343 | { |
344 | return !oldrestrictedmovearea.isEmpty(); |
345 | } |
346 | |
347 | QRegion Workspace::previousRestrictedMoveArea(int desktop, StrutAreas areas) const |
348 | { |
349 | if (desktop == NETWinInfo::OnAllDesktops || desktop == 0) |
350 | desktop = VirtualDesktopManager::self()->current(); |
351 | QRegion region; |
352 | foreach (const StrutRect & rect, oldrestrictedmovearea.at(desktop)) |
353 | if (areas & rect.area()) |
354 | region += rect; |
355 | return region; |
356 | } |
357 | |
358 | QVector< QRect > Workspace::previousScreenSizes() const |
359 | { |
360 | return oldscreensizes; |
361 | } |
362 | |
363 | int Workspace::oldDisplayWidth() const |
364 | { |
365 | return olddisplaysize.width(); |
366 | } |
367 | |
368 | int Workspace::oldDisplayHeight() const |
369 | { |
370 | return olddisplaysize.height(); |
371 | } |
372 | |
373 | /*! |
374 | Client \a c is moved around to position \a pos. This gives the |
375 | workspace the opportunity to interveniate and to implement |
376 | snap-to-windows functionality. |
377 | |
378 | The parameter \a snapAdjust is a multiplier used to calculate the |
379 | effective snap zones. When 1.0, it means that the snap zones will be |
380 | used without change. |
381 | */ |
382 | QPoint Workspace::adjustClientPosition(Client* c, QPoint pos, bool unrestricted, double snapAdjust) |
383 | { |
384 | QSize borderSnapZone(options->borderSnapZone(), options->borderSnapZone()); |
385 | QRect maxRect; |
386 | int guideMaximized = MaximizeRestore; |
387 | if (c->maximizeMode() != MaximizeRestore) { |
388 | maxRect = clientArea(MaximizeArea, pos + c->rect().center(), c->desktop()); |
389 | QRect geo = c->geometry(); |
390 | if (c->maximizeMode() & MaximizeHorizontal && (geo.x() == maxRect.left() || geo.right() == maxRect.right())) { |
391 | guideMaximized |= MaximizeHorizontal; |
392 | borderSnapZone.setWidth(qMax(borderSnapZone.width() + 2, maxRect.width() / 16)); |
393 | } |
394 | if (c->maximizeMode() & MaximizeVertical && (geo.y() == maxRect.top() || geo.bottom() == maxRect.bottom())) { |
395 | guideMaximized |= MaximizeVertical; |
396 | borderSnapZone.setHeight(qMax(borderSnapZone.height() + 2, maxRect.height() / 16)); |
397 | } |
398 | } |
399 | |
400 | if (options->windowSnapZone() || !borderSnapZone.isNull() || options->centerSnapZone()) { |
401 | |
402 | const bool sOWO = options->isSnapOnlyWhenOverlapping(); |
403 | const int screen = screens()->number(pos + c->rect().center()); |
404 | if (maxRect.isNull()) |
405 | maxRect = clientArea(MovementArea, screen, c->desktop()); |
406 | const int xmin = maxRect.left(); |
407 | const int xmax = maxRect.right() + 1; //desk size |
408 | const int ymin = maxRect.top(); |
409 | const int ymax = maxRect.bottom() + 1; |
410 | |
411 | const int cx(pos.x()); |
412 | const int cy(pos.y()); |
413 | const int cw(c->width()); |
414 | const int ch(c->height()); |
415 | const int rx(cx + cw); |
416 | const int ry(cy + ch); //these don't change |
417 | |
418 | int nx(cx), ny(cy); //buffers |
419 | int deltaX(xmax); |
420 | int deltaY(ymax); //minimum distance to other clients |
421 | |
422 | int lx, ly, lrx, lry; //coords and size for the comparison client, l |
423 | |
424 | // border snap |
425 | const int snapX = borderSnapZone.width() * snapAdjust; //snap trigger |
426 | const int snapY = borderSnapZone.height() * snapAdjust; |
427 | if (snapX || snapY) { |
428 | QRect geo = c->geometry(); |
429 | const QPoint cp = c->clientPos(); |
430 | const QSize cs = geo.size() - c->clientSize(); |
431 | int padding[4] = { cp.x(), cs.width() - cp.x(), cp.y(), cs.height() - cp.y() }; |
432 | |
433 | // snap to titlebar / snap to window borders on inner screen edges |
434 | Position titlePos = c->titlebarPosition(); |
435 | if (padding[0] && (titlePos == PositionLeft || (c->maximizeMode() & MaximizeHorizontal) || |
436 | screens()->intersecting(geo.translated(maxRect.x() - (padding[0] + geo.x()), 0)) > 1)) |
437 | padding[0] = 0; |
438 | if (padding[1] && (titlePos == PositionRight || (c->maximizeMode() & MaximizeHorizontal) || |
439 | screens()->intersecting(geo.translated(maxRect.right() + padding[1] - geo.right(), 0)) > 1)) |
440 | padding[1] = 0; |
441 | if (padding[2] && (titlePos == PositionTop || (c->maximizeMode() & MaximizeVertical) || |
442 | screens()->intersecting(geo.translated(0, maxRect.y() - (padding[2] + geo.y()))) > 1)) |
443 | padding[2] = 0; |
444 | if (padding[3] && (titlePos == PositionBottom || (c->maximizeMode() & MaximizeVertical) || |
445 | screens()->intersecting(geo.translated(0, maxRect.bottom() + padding[3] - geo.bottom())) > 1)) |
446 | padding[3] = 0; |
447 | if ((sOWO ? (cx < xmin) : true) && (qAbs(xmin - cx) < snapX)) { |
448 | deltaX = xmin - cx; |
449 | nx = xmin - padding[0]; |
450 | } |
451 | if ((sOWO ? (rx > xmax) : true) && (qAbs(rx - xmax) < snapX) && (qAbs(xmax - rx) < deltaX)) { |
452 | deltaX = rx - xmax; |
453 | nx = xmax - cw + padding[1]; |
454 | } |
455 | |
456 | if ((sOWO ? (cy < ymin) : true) && (qAbs(ymin - cy) < snapY)) { |
457 | deltaY = ymin - cy; |
458 | ny = ymin - padding[2]; |
459 | } |
460 | if ((sOWO ? (ry > ymax) : true) && (qAbs(ry - ymax) < snapY) && (qAbs(ymax - ry) < deltaY)) { |
461 | deltaY = ry - ymax; |
462 | ny = ymax - ch + padding[3]; |
463 | } |
464 | } |
465 | |
466 | // windows snap |
467 | int snap = options->windowSnapZone() * snapAdjust; |
468 | if (snap) { |
469 | QList<Client *>::ConstIterator l; |
470 | for (l = clients.constBegin(); l != clients.constEnd(); ++l) { |
471 | if ((*l) == c) |
472 | continue; |
473 | if ((*l)->isMinimized()) |
474 | continue; // is minimized |
475 | if ((*l)->tabGroup() && (*l) != (*l)->tabGroup()->current()) |
476 | continue; // is not active tab |
477 | if (!((*l)->isOnDesktop(c->desktop()) || c->isOnDesktop((*l)->desktop()))) |
478 | continue; // wrong virtual desktop |
479 | if (!(*l)->isOnCurrentActivity()) |
480 | continue; // wrong activity |
481 | if ((*l)->isDesktop() || (*l)->isSplash()) |
482 | continue; |
483 | |
484 | lx = (*l)->x(); |
485 | ly = (*l)->y(); |
486 | lrx = lx + (*l)->width(); |
487 | lry = ly + (*l)->height(); |
488 | |
489 | if (!(guideMaximized & MaximizeHorizontal) && |
490 | (((cy <= lry) && (cy >= ly)) || ((ry >= ly) && (ry <= lry)) || ((cy <= ly) && (ry >= lry)))) { |
491 | if ((sOWO ? (cx < lrx) : true) && (qAbs(lrx - cx) < snap) && (qAbs(lrx - cx) < deltaX)) { |
492 | deltaX = qAbs(lrx - cx); |
493 | nx = lrx; |
494 | } |
495 | if ((sOWO ? (rx > lx) : true) && (qAbs(rx - lx) < snap) && (qAbs(rx - lx) < deltaX)) { |
496 | deltaX = qAbs(rx - lx); |
497 | nx = lx - cw; |
498 | } |
499 | } |
500 | |
501 | if (!(guideMaximized & MaximizeVertical) && |
502 | (((cx <= lrx) && (cx >= lx)) || ((rx >= lx) && (rx <= lrx)) || ((cx <= lx) && (rx >= lrx)))) { |
503 | if ((sOWO ? (cy < lry) : true) && (qAbs(lry - cy) < snap) && (qAbs(lry - cy) < deltaY)) { |
504 | deltaY = qAbs(lry - cy); |
505 | ny = lry; |
506 | } |
507 | //if ( (qAbs( ry-ly ) < snap) && (qAbs( ry - ly ) < deltaY )) |
508 | if ((sOWO ? (ry > ly) : true) && (qAbs(ry - ly) < snap) && (qAbs(ry - ly) < deltaY)) { |
509 | deltaY = qAbs(ry - ly); |
510 | ny = ly - ch; |
511 | } |
512 | } |
513 | |
514 | // Corner snapping |
515 | if (!(guideMaximized & MaximizeVertical) && (nx == lrx || nx + cw == lx)) { |
516 | if ((sOWO ? (ry > lry) : true) && (qAbs(lry - ry) < snap) && (qAbs(lry - ry) < deltaY)) { |
517 | deltaY = qAbs(lry - ry); |
518 | ny = lry - ch; |
519 | } |
520 | if ((sOWO ? (cy < ly) : true) && (qAbs(cy - ly) < snap) && (qAbs(cy - ly) < deltaY)) { |
521 | deltaY = qAbs(cy - ly); |
522 | ny = ly; |
523 | } |
524 | } |
525 | if (!(guideMaximized & MaximizeHorizontal) && (ny == lry || ny + ch == ly)) { |
526 | if ((sOWO ? (rx > lrx) : true) && (qAbs(lrx - rx) < snap) && (qAbs(lrx - rx) < deltaX)) { |
527 | deltaX = qAbs(lrx - rx); |
528 | nx = lrx - cw; |
529 | } |
530 | if ((sOWO ? (cx < lx) : true) && (qAbs(cx - lx) < snap) && (qAbs(cx - lx) < deltaX)) { |
531 | deltaX = qAbs(cx - lx); |
532 | nx = lx; |
533 | } |
534 | } |
535 | } |
536 | } |
537 | |
538 | // center snap |
539 | snap = options->centerSnapZone() * snapAdjust; //snap trigger |
540 | if (snap) { |
541 | int diffX = qAbs((xmin + xmax) / 2 - (cx + cw / 2)); |
542 | int diffY = qAbs((ymin + ymax) / 2 - (cy + ch / 2)); |
543 | if (diffX < snap && diffY < snap && diffX < deltaX && diffY < deltaY) { |
544 | // Snap to center of screen |
545 | nx = (xmin + xmax) / 2 - cw / 2; |
546 | ny = (ymin + ymax) / 2 - ch / 2; |
547 | } else if (options->borderSnapZone()) { |
548 | // Enhance border snap |
549 | if ((nx == xmin || nx == xmax - cw) && diffY < snap && diffY < deltaY) { |
550 | // Snap to vertical center on screen edge |
551 | ny = (ymin + ymax) / 2 - ch / 2; |
552 | } else if (((unrestricted ? ny == ymin : ny <= ymin) || ny == ymax - ch) && |
553 | diffX < snap && diffX < deltaX) { |
554 | // Snap to horizontal center on screen edge |
555 | nx = (xmin + xmax) / 2 - cw / 2; |
556 | } |
557 | } |
558 | } |
559 | |
560 | pos = QPoint(nx, ny); |
561 | } |
562 | return pos; |
563 | } |
564 | |
565 | QRect Workspace::adjustClientSize(Client* c, QRect moveResizeGeom, int mode) |
566 | { |
567 | //adapted from adjustClientPosition on 29May2004 |
568 | //this function is called when resizing a window and will modify |
569 | //the new dimensions to snap to other windows/borders if appropriate |
570 | if (options->windowSnapZone() || options->borderSnapZone()) { // || options->centerSnapZone ) |
571 | const bool sOWO = options->isSnapOnlyWhenOverlapping(); |
572 | |
573 | const QRect maxRect = clientArea(MovementArea, c->rect().center(), c->desktop()); |
574 | const int xmin = maxRect.left(); |
575 | const int xmax = maxRect.right(); //desk size |
576 | const int ymin = maxRect.top(); |
577 | const int ymax = maxRect.bottom(); |
578 | |
579 | const int cx(moveResizeGeom.left()); |
580 | const int cy(moveResizeGeom.top()); |
581 | const int rx(moveResizeGeom.right()); |
582 | const int ry(moveResizeGeom.bottom()); |
583 | |
584 | int newcx(cx), newcy(cy); //buffers |
585 | int newrx(rx), newry(ry); |
586 | int deltaX(xmax); |
587 | int deltaY(ymax); //minimum distance to other clients |
588 | |
589 | int lx, ly, lrx, lry; //coords and size for the comparison client, l |
590 | |
591 | // border snap |
592 | int snap = options->borderSnapZone(); //snap trigger |
593 | if (snap) { |
594 | deltaX = int(snap); |
595 | deltaY = int(snap); |
596 | |
597 | #define SNAP_BORDER_TOP \ |
598 | if ((sOWO?(newcy<ymin):true) && (qAbs(ymin-newcy)<deltaY)) \ |
599 | { \ |
600 | deltaY = qAbs(ymin-newcy); \ |
601 | newcy = ymin; \ |
602 | } |
603 | |
604 | #define SNAP_BORDER_BOTTOM \ |
605 | if ((sOWO?(newry>ymax):true) && (qAbs(ymax-newry)<deltaY)) \ |
606 | { \ |
607 | deltaY = qAbs(ymax-newcy); \ |
608 | newry = ymax; \ |
609 | } |
610 | |
611 | #define SNAP_BORDER_LEFT \ |
612 | if ((sOWO?(newcx<xmin):true) && (qAbs(xmin-newcx)<deltaX)) \ |
613 | { \ |
614 | deltaX = qAbs(xmin-newcx); \ |
615 | newcx = xmin; \ |
616 | } |
617 | |
618 | #define SNAP_BORDER_RIGHT \ |
619 | if ((sOWO?(newrx>xmax):true) && (qAbs(xmax-newrx)<deltaX)) \ |
620 | { \ |
621 | deltaX = qAbs(xmax-newrx); \ |
622 | newrx = xmax; \ |
623 | } |
624 | switch(mode) { |
625 | case PositionBottomRight: |
626 | SNAP_BORDER_BOTTOM |
627 | SNAP_BORDER_RIGHT |
628 | break; |
629 | case PositionRight: |
630 | SNAP_BORDER_RIGHT |
631 | break; |
632 | case PositionBottom: |
633 | SNAP_BORDER_BOTTOM |
634 | break; |
635 | case PositionTopLeft: |
636 | SNAP_BORDER_TOP |
637 | SNAP_BORDER_LEFT |
638 | break; |
639 | case PositionLeft: |
640 | SNAP_BORDER_LEFT |
641 | break; |
642 | case PositionTop: |
643 | SNAP_BORDER_TOP |
644 | break; |
645 | case PositionTopRight: |
646 | SNAP_BORDER_TOP |
647 | SNAP_BORDER_RIGHT |
648 | break; |
649 | case PositionBottomLeft: |
650 | SNAP_BORDER_BOTTOM |
651 | SNAP_BORDER_LEFT |
652 | break; |
653 | default: |
654 | abort(); |
655 | break; |
656 | } |
657 | |
658 | |
659 | } |
660 | |
661 | // windows snap |
662 | snap = options->windowSnapZone(); |
663 | if (snap) { |
664 | deltaX = int(snap); |
665 | deltaY = int(snap); |
666 | QList<Client *>::ConstIterator l; |
667 | for (l = clients.constBegin(); l != clients.constEnd(); ++l) { |
668 | if ((*l)->isOnDesktop(VirtualDesktopManager::self()->current()) && |
669 | !(*l)->isMinimized() |
670 | && (*l) != c) { |
671 | lx = (*l)->x() - 1; |
672 | ly = (*l)->y() - 1; |
673 | lrx = (*l)->x() + (*l)->width(); |
674 | lry = (*l)->y() + (*l)->height(); |
675 | |
676 | #define WITHIN_HEIGHT ((( newcy <= lry ) && ( newcy >= ly )) || \ |
677 | (( newry >= ly ) && ( newry <= lry )) || \ |
678 | (( newcy <= ly ) && ( newry >= lry )) ) |
679 | |
680 | #define WITHIN_WIDTH ( (( cx <= lrx ) && ( cx >= lx )) || \ |
681 | (( rx >= lx ) && ( rx <= lrx )) || \ |
682 | (( cx <= lx ) && ( rx >= lrx )) ) |
683 | |
684 | #define SNAP_WINDOW_TOP if ( (sOWO?(newcy<lry):true) \ |
685 | && WITHIN_WIDTH \ |
686 | && (qAbs( lry - newcy ) < deltaY) ) { \ |
687 | deltaY = qAbs( lry - newcy ); \ |
688 | newcy=lry; \ |
689 | } |
690 | |
691 | #define SNAP_WINDOW_BOTTOM if ( (sOWO?(newry>ly):true) \ |
692 | && WITHIN_WIDTH \ |
693 | && (qAbs( ly - newry ) < deltaY) ) { \ |
694 | deltaY = qAbs( ly - newry ); \ |
695 | newry=ly; \ |
696 | } |
697 | |
698 | #define SNAP_WINDOW_LEFT if ( (sOWO?(newcx<lrx):true) \ |
699 | && WITHIN_HEIGHT \ |
700 | && (qAbs( lrx - newcx ) < deltaX)) { \ |
701 | deltaX = qAbs( lrx - newcx ); \ |
702 | newcx=lrx; \ |
703 | } |
704 | |
705 | #define SNAP_WINDOW_RIGHT if ( (sOWO?(newrx>lx):true) \ |
706 | && WITHIN_HEIGHT \ |
707 | && (qAbs( lx - newrx ) < deltaX)) \ |
708 | { \ |
709 | deltaX = qAbs( lx - newrx ); \ |
710 | newrx=lx; \ |
711 | } |
712 | |
713 | #define SNAP_WINDOW_C_TOP if ( (sOWO?(newcy<ly):true) \ |
714 | && (newcx == lrx || newrx == lx) \ |
715 | && qAbs(ly-newcy) < deltaY ) { \ |
716 | deltaY = qAbs( ly - newcy + 1 ); \ |
717 | newcy = ly + 1; \ |
718 | } |
719 | |
720 | #define SNAP_WINDOW_C_BOTTOM if ( (sOWO?(newry>lry):true) \ |
721 | && (newcx == lrx || newrx == lx) \ |
722 | && qAbs(lry-newry) < deltaY ) { \ |
723 | deltaY = qAbs( lry - newry - 1 ); \ |
724 | newry = lry - 1; \ |
725 | } |
726 | |
727 | #define SNAP_WINDOW_C_LEFT if ( (sOWO?(newcx<lx):true) \ |
728 | && (newcy == lry || newry == ly) \ |
729 | && qAbs(lx-newcx) < deltaX ) { \ |
730 | deltaX = qAbs( lx - newcx + 1 ); \ |
731 | newcx = lx + 1; \ |
732 | } |
733 | |
734 | #define SNAP_WINDOW_C_RIGHT if ( (sOWO?(newrx>lrx):true) \ |
735 | && (newcy == lry || newry == ly) \ |
736 | && qAbs(lrx-newrx) < deltaX ) { \ |
737 | deltaX = qAbs( lrx - newrx - 1 ); \ |
738 | newrx = lrx - 1; \ |
739 | } |
740 | |
741 | switch(mode) { |
742 | case PositionBottomRight: |
743 | SNAP_WINDOW_BOTTOM |
744 | SNAP_WINDOW_RIGHT |
745 | SNAP_WINDOW_C_BOTTOM |
746 | SNAP_WINDOW_C_RIGHT |
747 | break; |
748 | case PositionRight: |
749 | SNAP_WINDOW_RIGHT |
750 | SNAP_WINDOW_C_RIGHT |
751 | break; |
752 | case PositionBottom: |
753 | SNAP_WINDOW_BOTTOM |
754 | SNAP_WINDOW_C_BOTTOM |
755 | break; |
756 | case PositionTopLeft: |
757 | SNAP_WINDOW_TOP |
758 | SNAP_WINDOW_LEFT |
759 | SNAP_WINDOW_C_TOP |
760 | SNAP_WINDOW_C_LEFT |
761 | break; |
762 | case PositionLeft: |
763 | SNAP_WINDOW_LEFT |
764 | SNAP_WINDOW_C_LEFT |
765 | break; |
766 | case PositionTop: |
767 | SNAP_WINDOW_TOP |
768 | SNAP_WINDOW_C_TOP |
769 | break; |
770 | case PositionTopRight: |
771 | SNAP_WINDOW_TOP |
772 | SNAP_WINDOW_RIGHT |
773 | SNAP_WINDOW_C_TOP |
774 | SNAP_WINDOW_C_RIGHT |
775 | break; |
776 | case PositionBottomLeft: |
777 | SNAP_WINDOW_BOTTOM |
778 | SNAP_WINDOW_LEFT |
779 | SNAP_WINDOW_C_BOTTOM |
780 | SNAP_WINDOW_C_LEFT |
781 | break; |
782 | default: |
783 | abort(); |
784 | break; |
785 | } |
786 | } |
787 | } |
788 | } |
789 | |
790 | // center snap |
791 | //snap = options->centerSnapZone; |
792 | //if (snap) |
793 | // { |
794 | // // Don't resize snap to center as it interferes too much |
795 | // // There are two ways of implementing this if wanted: |
796 | // // 1) Snap only to the same points that the move snap does, and |
797 | // // 2) Snap to the horizontal and vertical center lines of the screen |
798 | // } |
799 | |
800 | moveResizeGeom = QRect(QPoint(newcx, newcy), QPoint(newrx, newry)); |
801 | } |
802 | return moveResizeGeom; |
803 | } |
804 | |
805 | /*! |
806 | Marks the client as being moved around by the user. |
807 | */ |
808 | void Workspace::setClientIsMoving(Client *c) |
809 | { |
810 | Q_ASSERT(!c || !movingClient); // Catch attempts to move a second |
811 | // window while still moving the first one. |
812 | movingClient = c; |
813 | if (movingClient) |
814 | ++block_focus; |
815 | else |
816 | --block_focus; |
817 | } |
818 | |
819 | // When kwin crashes, windows will not be gravitated back to their original position |
820 | // and will remain offset by the size of the decoration. So when restarting, fix this |
821 | // (the property with the size of the frame remains on the window after the crash). |
822 | void Workspace::fixPositionAfterCrash(xcb_window_t w, const xcb_get_geometry_reply_t *geometry) |
823 | { |
824 | NETWinInfo i(display(), w, rootWindow(), NET::WMFrameExtents); |
825 | NETStrut frame = i.frameExtents(); |
826 | |
827 | if (frame.left != 0 || frame.top != 0) { |
828 | // left and top needed due to narrowing conversations restrictions in C++11 |
829 | const uint32_t left = frame.left; |
830 | const uint32_t top = frame.top; |
831 | const uint32_t values[] = { geometry->x - left, geometry->y - top }; |
832 | xcb_configure_window(connection(), w, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values); |
833 | } |
834 | } |
835 | |
836 | //******************************************** |
837 | // Client |
838 | //******************************************** |
839 | |
840 | |
841 | void Client::keepInArea(QRect area, bool partial) |
842 | { |
843 | if (partial) { |
844 | // increase the area so that can have only 100 pixels in the area |
845 | area.setLeft(qMin(area.left() - width() + 100, area.left())); |
846 | area.setTop(qMin(area.top() - height() + 100, area.top())); |
847 | area.setRight(qMax(area.right() + width() - 100, area.right())); |
848 | area.setBottom(qMax(area.bottom() + height() - 100, area.bottom())); |
849 | } |
850 | if (!partial) { |
851 | // resize to fit into area |
852 | if (area.width() < width() || area.height() < height()) |
853 | resizeWithChecks(qMin(area.width(), width()), qMin(area.height(), height())); |
854 | } |
855 | int tx = x(), ty = y(); |
856 | if (geometry().right() > area.right() && width() <= area.width()) |
857 | tx = area.right() - width() + 1; |
858 | if (geometry().bottom() > area.bottom() && height() <= area.height()) |
859 | ty = area.bottom() - height() + 1; |
860 | if (!area.contains(geometry().topLeft())) { |
861 | if (tx < area.x()) |
862 | tx = area.x(); |
863 | if (ty < area.y()) |
864 | ty = area.y(); |
865 | } |
866 | if (tx != x() || ty != y()) |
867 | move(tx, ty); |
868 | } |
869 | |
870 | /*! |
871 | Returns \a area with the client's strut taken into account. |
872 | |
873 | Used from Workspace in updateClientArea. |
874 | */ |
875 | // TODO move to Workspace? |
876 | |
877 | QRect Client::adjustedClientArea(const QRect &desktopArea, const QRect& area) const |
878 | { |
879 | QRect r = area; |
880 | NETExtendedStrut str = strut(); |
881 | QRect stareaL = QRect( |
882 | 0, |
883 | str . left_start, |
884 | str . left_width, |
885 | str . left_end - str . left_start + 1); |
886 | QRect stareaR = QRect( |
887 | desktopArea . right() - str . right_width + 1, |
888 | str . right_start, |
889 | str . right_width, |
890 | str . right_end - str . right_start + 1); |
891 | QRect stareaT = QRect( |
892 | str . top_start, |
893 | 0, |
894 | str . top_end - str . top_start + 1, |
895 | str . top_width); |
896 | QRect stareaB = QRect( |
897 | str . bottom_start, |
898 | desktopArea . bottom() - str . bottom_width + 1, |
899 | str . bottom_end - str . bottom_start + 1, |
900 | str . bottom_width); |
901 | |
902 | QRect screenarea = workspace()->clientArea(ScreenArea, this); |
903 | // HACK: workarea handling is not xinerama aware, so if this strut |
904 | // reserves place at a xinerama edge that's inside the virtual screen, |
905 | // ignore the strut for workspace setting. |
906 | if (area == QRect(0, 0, displayWidth(), displayHeight())) { |
907 | if (stareaL.left() < screenarea.left()) |
908 | stareaL = QRect(); |
909 | if (stareaR.right() > screenarea.right()) |
910 | stareaR = QRect(); |
911 | if (stareaT.top() < screenarea.top()) |
912 | stareaT = QRect(); |
913 | if (stareaB.bottom() < screenarea.bottom()) |
914 | stareaB = QRect(); |
915 | } |
916 | // Handle struts at xinerama edges that are inside the virtual screen. |
917 | // They're given in virtual screen coordinates, make them affect only |
918 | // their xinerama screen. |
919 | stareaL.setLeft(qMax(stareaL.left(), screenarea.left())); |
920 | stareaR.setRight(qMin(stareaR.right(), screenarea.right())); |
921 | stareaT.setTop(qMax(stareaT.top(), screenarea.top())); |
922 | stareaB.setBottom(qMin(stareaB.bottom(), screenarea.bottom())); |
923 | |
924 | if (stareaL . intersects(area)) { |
925 | // kDebug (1212) << "Moving left of: " << r << " to " << stareaL.right() + 1; |
926 | r . setLeft(stareaL . right() + 1); |
927 | } |
928 | if (stareaR . intersects(area)) { |
929 | // kDebug (1212) << "Moving right of: " << r << " to " << stareaR.left() - 1; |
930 | r . setRight(stareaR . left() - 1); |
931 | } |
932 | if (stareaT . intersects(area)) { |
933 | // kDebug (1212) << "Moving top of: " << r << " to " << stareaT.bottom() + 1; |
934 | r . setTop(stareaT . bottom() + 1); |
935 | } |
936 | if (stareaB . intersects(area)) { |
937 | // kDebug (1212) << "Moving bottom of: " << r << " to " << stareaB.top() - 1; |
938 | r . setBottom(stareaB . top() - 1); |
939 | } |
940 | return r; |
941 | } |
942 | |
943 | NETExtendedStrut Client::strut() const |
944 | { |
945 | NETExtendedStrut ext = info->extendedStrut(); |
946 | NETStrut str = info->strut(); |
947 | if (ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0 |
948 | && (str.left != 0 || str.right != 0 || str.top != 0 || str.bottom != 0)) { |
949 | // build extended from simple |
950 | if (str.left != 0) { |
951 | ext.left_width = str.left; |
952 | ext.left_start = 0; |
953 | ext.left_end = displayHeight(); |
954 | } |
955 | if (str.right != 0) { |
956 | ext.right_width = str.right; |
957 | ext.right_start = 0; |
958 | ext.right_end = displayHeight(); |
959 | } |
960 | if (str.top != 0) { |
961 | ext.top_width = str.top; |
962 | ext.top_start = 0; |
963 | ext.top_end = displayWidth(); |
964 | } |
965 | if (str.bottom != 0) { |
966 | ext.bottom_width = str.bottom; |
967 | ext.bottom_start = 0; |
968 | ext.bottom_end = displayWidth(); |
969 | } |
970 | } |
971 | return ext; |
972 | } |
973 | |
974 | StrutRect Client::strutRect(StrutArea area) const |
975 | { |
976 | assert(area != StrutAreaAll); // Not valid |
977 | NETExtendedStrut strutArea = strut(); |
978 | switch(area) { |
979 | case StrutAreaTop: |
980 | if (strutArea.top_width != 0) |
981 | return StrutRect(QRect( |
982 | strutArea.top_start, 0, |
983 | strutArea.top_end - strutArea.top_start, strutArea.top_width |
984 | ), StrutAreaTop); |
985 | break; |
986 | case StrutAreaRight: |
987 | if (strutArea.right_width != 0) |
988 | return StrutRect(QRect( |
989 | displayWidth() - strutArea.right_width, strutArea.right_start, |
990 | strutArea.right_width, strutArea.right_end - strutArea.right_start |
991 | ), StrutAreaRight); |
992 | break; |
993 | case StrutAreaBottom: |
994 | if (strutArea.bottom_width != 0) |
995 | return StrutRect(QRect( |
996 | strutArea.bottom_start, displayHeight() - strutArea.bottom_width, |
997 | strutArea.bottom_end - strutArea.bottom_start, strutArea.bottom_width |
998 | ), StrutAreaBottom); |
999 | break; |
1000 | case StrutAreaLeft: |
1001 | if (strutArea.left_width != 0) |
1002 | return StrutRect(QRect( |
1003 | 0, strutArea.left_start, |
1004 | strutArea.left_width, strutArea.left_end - strutArea.left_start |
1005 | ), StrutAreaLeft); |
1006 | break; |
1007 | default: |
1008 | abort(); // Not valid |
1009 | } |
1010 | return StrutRect(); // Null rect |
1011 | } |
1012 | |
1013 | StrutRects Client::strutRects() const |
1014 | { |
1015 | StrutRects region; |
1016 | region += strutRect(StrutAreaTop); |
1017 | region += strutRect(StrutAreaRight); |
1018 | region += strutRect(StrutAreaBottom); |
1019 | region += strutRect(StrutAreaLeft); |
1020 | return region; |
1021 | } |
1022 | |
1023 | bool Client::hasStrut() const |
1024 | { |
1025 | NETExtendedStrut ext = strut(); |
1026 | if (ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0) |
1027 | return false; |
1028 | return true; |
1029 | } |
1030 | |
1031 | bool Client::hasOffscreenXineramaStrut() const |
1032 | { |
1033 | // Get strut as a QRegion |
1034 | QRegion region; |
1035 | region += strutRect(StrutAreaTop); |
1036 | region += strutRect(StrutAreaRight); |
1037 | region += strutRect(StrutAreaBottom); |
1038 | region += strutRect(StrutAreaLeft); |
1039 | |
1040 | // Remove all visible areas so that only the invisible remain |
1041 | for (int i = 0; i < screens()->count(); i ++) |
1042 | region -= screens()->geometry(i); |
1043 | |
1044 | // If there's anything left then we have an offscreen strut |
1045 | return !region.isEmpty(); |
1046 | } |
1047 | |
1048 | void Client::checkWorkspacePosition(QRect oldGeometry, int oldDesktop) |
1049 | { |
1050 | if( !oldGeometry.isValid()) |
1051 | oldGeometry = geometry(); |
1052 | if( oldDesktop == -2 ) |
1053 | oldDesktop = desktop(); |
1054 | if (isDesktop()) |
1055 | return; |
1056 | if (isFullScreen()) { |
1057 | QRect area = workspace()->clientArea(FullScreenArea, this); |
1058 | if (geometry() != area) |
1059 | setGeometry(area); |
1060 | return; |
1061 | } |
1062 | if (isDock()) |
1063 | return; |
1064 | |
1065 | if (maximizeMode() != MaximizeRestore) { |
1066 | // TODO update geom_restore? |
1067 | changeMaximize(false, false, true); // adjust size |
1068 | const QRect screenArea = workspace()->clientArea(ScreenArea, this); |
1069 | QRect geom = geometry(); |
1070 | checkOffscreenPosition(&geom, screenArea); |
1071 | setGeometry(geom); |
1072 | return; |
1073 | } |
1074 | |
1075 | if (quick_tile_mode != QuickTileNone) { |
1076 | setGeometry(electricBorderMaximizeGeometry(geometry().center(), desktop())); |
1077 | return; |
1078 | } |
1079 | |
1080 | // this can be true only if this window was mapped before KWin |
1081 | // was started - in such case, don't adjust position to workarea, |
1082 | // because the window already had its position, and if a window |
1083 | // with a strut altering the workarea would be managed in initialization |
1084 | // after this one, this window would be moved |
1085 | if (workspace()->initializing()) |
1086 | return; |
1087 | |
1088 | // If the window was touching an edge before but not now move it so it is again. |
1089 | // Old and new maximums have different starting values so windows on the screen |
1090 | // edge will move when a new strut is placed on the edge. |
1091 | QRect oldScreenArea; |
1092 | QRect oldGeomTall; |
1093 | QRect oldGeomWide; |
1094 | if( workspace()->inUpdateClientArea()) { |
1095 | // we need to find the screen area as it was before the change |
1096 | oldScreenArea = QRect( 0, 0, workspace()->oldDisplayWidth(), workspace()->oldDisplayHeight()); |
1097 | oldGeomTall = QRect(oldGeometry.x(), 0, oldGeometry.width(), workspace()->oldDisplayHeight()); // Full screen height |
1098 | oldGeomWide = QRect(0, oldGeometry.y(), workspace()->oldDisplayWidth(), oldGeometry.height()); // Full screen width |
1099 | int distance = INT_MAX; |
1100 | foreach(const QRect &r, workspace()->previousScreenSizes()) { |
1101 | int d = r.contains( oldGeometry.center()) ? 0 : ( r.center() - oldGeometry.center()).manhattanLength(); |
1102 | if( d < distance ) { |
1103 | distance = d; |
1104 | oldScreenArea = r; |
1105 | } |
1106 | } |
1107 | } else { |
1108 | oldScreenArea = workspace()->clientArea(ScreenArea, oldGeometry.center(), oldDesktop); |
1109 | oldGeomTall = QRect(oldGeometry.x(), 0, oldGeometry.width(), displayHeight()); // Full screen height |
1110 | oldGeomWide = QRect(0, oldGeometry.y(), displayWidth(), oldGeometry.height()); // Full screen width |
1111 | } |
1112 | int oldTopMax = oldScreenArea.y(); |
1113 | int oldRightMax = oldScreenArea.x() + oldScreenArea.width(); |
1114 | int oldBottomMax = oldScreenArea.y() + oldScreenArea.height(); |
1115 | int oldLeftMax = oldScreenArea.x(); |
1116 | const QRect screenArea = workspace()->clientArea(ScreenArea, this); |
1117 | int topMax = screenArea.y(); |
1118 | int rightMax = screenArea.x() + screenArea.width(); |
1119 | int bottomMax = screenArea.y() + screenArea.height(); |
1120 | int leftMax = screenArea.x(); |
1121 | QRect newGeom = geom_restore; // geometry(); |
1122 | const QRect newGeomTall = QRect(newGeom.x(), 0, newGeom.width(), displayHeight()); // Full screen height |
1123 | const QRect newGeomWide = QRect(0, newGeom.y(), displayWidth(), newGeom.height()); // Full screen width |
1124 | // Get the max strut point for each side where the window is (E.g. Highest point for |
1125 | // the bottom struts bounded by the window's left and right sides). |
1126 | if( workspace()->inUpdateClientArea()) { |
1127 | // These 4 compute old bounds when the restricted areas themselves changed (Workspace::updateClientArea()) |
1128 | foreach (const QRect & r, workspace()->previousRestrictedMoveArea(oldDesktop, StrutAreaTop).rects()) { |
1129 | QRect rect = r & oldGeomTall; |
1130 | if (!rect.isEmpty()) |
1131 | oldTopMax = qMax(oldTopMax, rect.y() + rect.height()); |
1132 | } |
1133 | foreach (const QRect & r, workspace()->previousRestrictedMoveArea(oldDesktop, StrutAreaRight).rects()) { |
1134 | QRect rect = r & oldGeomWide; |
1135 | if (!rect.isEmpty()) |
1136 | oldRightMax = qMin(oldRightMax, rect.x()); |
1137 | } |
1138 | foreach (const QRect & r, workspace()->previousRestrictedMoveArea(oldDesktop, StrutAreaBottom).rects()) { |
1139 | QRect rect = r & oldGeomTall; |
1140 | if (!rect.isEmpty()) |
1141 | oldBottomMax = qMin(oldBottomMax, rect.y()); |
1142 | } |
1143 | foreach (const QRect & r, workspace()->previousRestrictedMoveArea(oldDesktop, StrutAreaLeft).rects()) { |
1144 | QRect rect = r & oldGeomWide; |
1145 | if (!rect.isEmpty()) |
1146 | oldLeftMax = qMax(oldLeftMax, rect.x() + rect.width()); |
1147 | } |
1148 | } else { |
1149 | // These 4 compute old bounds when e.g. active desktop or screen changes |
1150 | foreach (const QRect & r, workspace()->restrictedMoveArea(oldDesktop, StrutAreaTop).rects()) { |
1151 | QRect rect = r & oldGeomTall; |
1152 | if (!rect.isEmpty()) |
1153 | oldTopMax = qMax(oldTopMax, rect.y() + rect.height()); |
1154 | } |
1155 | foreach (const QRect & r, workspace()->restrictedMoveArea(oldDesktop, StrutAreaRight).rects()) { |
1156 | QRect rect = r & oldGeomWide; |
1157 | if (!rect.isEmpty()) |
1158 | oldRightMax = qMin(oldRightMax, rect.x()); |
1159 | } |
1160 | foreach (const QRect & r, workspace()->restrictedMoveArea(oldDesktop, StrutAreaBottom).rects()) { |
1161 | QRect rect = r & oldGeomTall; |
1162 | if (!rect.isEmpty()) |
1163 | oldBottomMax = qMin(oldBottomMax, rect.y()); |
1164 | } |
1165 | foreach (const QRect & r, workspace()->restrictedMoveArea(oldDesktop, StrutAreaLeft).rects()) { |
1166 | QRect rect = r & oldGeomWide; |
1167 | if (!rect.isEmpty()) |
1168 | oldLeftMax = qMax(oldLeftMax, rect.x() + rect.width()); |
1169 | } |
1170 | } |
1171 | // These 4 compute new bounds |
1172 | foreach (const QRect & r, workspace()->restrictedMoveArea(desktop(), StrutAreaTop).rects()) { |
1173 | QRect rect = r & newGeomTall; |
1174 | if (!rect.isEmpty()) |
1175 | topMax = qMax(topMax, rect.y() + rect.height()); |
1176 | } |
1177 | foreach (const QRect & r, workspace()->restrictedMoveArea(desktop(), StrutAreaRight).rects()) { |
1178 | QRect rect = r & newGeomWide; |
1179 | if (!rect.isEmpty()) |
1180 | rightMax = qMin(rightMax, rect.x()); |
1181 | } |
1182 | foreach (const QRect & r, workspace()->restrictedMoveArea(desktop(), StrutAreaBottom).rects()) { |
1183 | QRect rect = r & newGeomTall; |
1184 | if (!rect.isEmpty()) |
1185 | bottomMax = qMin(bottomMax, rect.y()); |
1186 | } |
1187 | foreach (const QRect & r, workspace()->restrictedMoveArea(desktop(), StrutAreaLeft).rects()) { |
1188 | QRect rect = r & newGeomWide; |
1189 | if (!rect.isEmpty()) |
1190 | leftMax = qMax(leftMax, rect.x() + rect.width()); |
1191 | } |
1192 | |
1193 | // Check if the sides were inside or touching but are no longer |
1194 | if ((oldGeometry.y() >= oldTopMax && newGeom.y() < topMax) |
1195 | || (oldGeometry.y() == oldTopMax && newGeom.y() != topMax)) { |
1196 | // Top was inside or touching before but isn't anymore |
1197 | newGeom.moveTop(qMax(topMax, screenArea.y())); |
1198 | } |
1199 | if ((oldGeometry.y() + oldGeometry.height() <= oldBottomMax && newGeom.y() + newGeom.height() > bottomMax) |
1200 | || (oldGeometry.y() + oldGeometry.height() == oldBottomMax && newGeom.y() + newGeom.height() != bottomMax)) { |
1201 | // Bottom was inside or touching before but isn't anymore |
1202 | newGeom.moveBottom(qMin(bottomMax - 1, screenArea.bottom())); |
1203 | // If the other side was inside make sure it still is afterwards (shrink appropriately) |
1204 | if (oldGeometry.y() >= oldTopMax && newGeom.y() < topMax) |
1205 | newGeom.setTop(qMax(topMax, screenArea.y())); |
1206 | } |
1207 | if ((oldGeometry.x() >= oldLeftMax && newGeom.x() < leftMax) |
1208 | || (oldGeometry.x() == oldLeftMax && newGeom.x() != leftMax)) { |
1209 | // Left was inside or touching before but isn't anymore |
1210 | newGeom.moveLeft(qMax(leftMax, screenArea.x())); |
1211 | } |
1212 | if ((oldGeometry.x() + oldGeometry.width() <= oldRightMax && newGeom.x() + newGeom.width() > rightMax) |
1213 | || (oldGeometry.x() + oldGeometry.width() == oldRightMax && newGeom.x() + newGeom.width() != rightMax)) { |
1214 | // Right was inside or touching before but isn't anymore |
1215 | newGeom.moveRight(qMin(rightMax - 1, screenArea.right())); |
1216 | // If the other side was inside make sure it still is afterwards (shrink appropriately) |
1217 | if (oldGeometry.x() >= oldLeftMax && newGeom.x() < leftMax) |
1218 | newGeom.setLeft(qMax(leftMax, screenArea.x())); |
1219 | } |
1220 | |
1221 | checkOffscreenPosition(&newGeom, screenArea); |
1222 | // Obey size hints. TODO: We really should make sure it stays in the right place |
1223 | newGeom.setSize(adjustedSize(newGeom.size())); |
1224 | |
1225 | if (newGeom != geometry()) |
1226 | setGeometry(newGeom); |
1227 | } |
1228 | |
1229 | void Client::checkOffscreenPosition(QRect* geom, const QRect& screenArea) |
1230 | { |
1231 | if (geom->x() > screenArea.right()) { |
1232 | int screenWidth = screenArea.width(); |
1233 | geom->moveLeft(screenWidth - (screenWidth / 4)); |
1234 | } |
1235 | if (geom->y() > screenArea.bottom()) { |
1236 | int screenHeight = screenArea.height(); |
1237 | geom->moveBottom(screenHeight - (screenHeight / 4)); |
1238 | } |
1239 | } |
1240 | |
1241 | /*! |
1242 | Adjust the frame size \a frame according to he window's size hints. |
1243 | */ |
1244 | QSize Client::adjustedSize(const QSize& frame, Sizemode mode) const |
1245 | { |
1246 | // first, get the window size for the given frame size s |
1247 | QSize wsize(frame.width() - (border_left + border_right), |
1248 | frame.height() - (border_top + border_bottom)); |
1249 | if (wsize.isEmpty()) |
1250 | wsize = QSize(1, 1); |
1251 | |
1252 | return sizeForClientSize(wsize, mode, false); |
1253 | } |
1254 | |
1255 | // this helper returns proper size even if the window is shaded |
1256 | // see also the comment in Client::setGeometry() |
1257 | QSize Client::adjustedSize() const |
1258 | { |
1259 | return sizeForClientSize(clientSize()); |
1260 | } |
1261 | |
1262 | /*! |
1263 | Calculate the appropriate frame size for the given client size \a |
1264 | wsize. |
1265 | |
1266 | \a wsize is adapted according to the window's size hints (minimum, |
1267 | maximum and incremental size changes). |
1268 | |
1269 | */ |
1270 | QSize Client::sizeForClientSize(const QSize& wsize, Sizemode mode, bool noframe) const |
1271 | { |
1272 | int w = wsize.width(); |
1273 | int h = wsize.height(); |
1274 | if (w < 1 || h < 1) { |
1275 | kWarning(1212) << "sizeForClientSize() with empty size!" ; |
1276 | kWarning(1212) << kBacktrace() ; |
1277 | } |
1278 | if (w < 1) w = 1; |
1279 | if (h < 1) h = 1; |
1280 | |
1281 | // basesize, minsize, maxsize, paspect and resizeinc have all values defined, |
1282 | // even if they're not set in flags - see getWmNormalHints() |
1283 | QSize min_size = tabGroup() ? tabGroup()->minSize() : minSize(); |
1284 | QSize max_size = tabGroup() ? tabGroup()->maxSize() : maxSize(); |
1285 | if (decoration != NULL) { |
1286 | QSize decominsize = decoration->minimumSize(); |
1287 | QSize border_size(border_left + border_right, border_top + border_bottom); |
1288 | if (border_size.width() > decominsize.width()) // just in case |
1289 | decominsize.setWidth(border_size.width()); |
1290 | if (border_size.height() > decominsize.height()) |
1291 | decominsize.setHeight(border_size.height()); |
1292 | if (decominsize.width() > min_size.width()) |
1293 | min_size.setWidth(decominsize.width()); |
1294 | if (decominsize.height() > min_size.height()) |
1295 | min_size.setHeight(decominsize.height()); |
1296 | } |
1297 | w = qMin(max_size.width(), w); |
1298 | h = qMin(max_size.height(), h); |
1299 | w = qMax(min_size.width(), w); |
1300 | h = qMax(min_size.height(), h); |
1301 | |
1302 | int w1 = w; |
1303 | int h1 = h; |
1304 | int width_inc = xSizeHint.width_inc; |
1305 | int height_inc = xSizeHint.height_inc; |
1306 | int basew_inc = xSizeHint.base_width; |
1307 | int baseh_inc = xSizeHint.base_height; |
1308 | if (!(xSizeHint.flags & PBaseSize)) { |
1309 | basew_inc = xSizeHint.min_width; |
1310 | baseh_inc = xSizeHint.min_height; |
1311 | } |
1312 | w = int((w - basew_inc) / width_inc) * width_inc + basew_inc; |
1313 | h = int((h - baseh_inc) / height_inc) * height_inc + baseh_inc; |
1314 | // code for aspect ratios based on code from FVWM |
1315 | /* |
1316 | * The math looks like this: |
1317 | * |
1318 | * minAspectX dwidth maxAspectX |
1319 | * ---------- <= ------- <= ---------- |
1320 | * minAspectY dheight maxAspectY |
1321 | * |
1322 | * If that is multiplied out, then the width and height are |
1323 | * invalid in the following situations: |
1324 | * |
1325 | * minAspectX * dheight > minAspectY * dwidth |
1326 | * maxAspectX * dheight < maxAspectY * dwidth |
1327 | * |
1328 | */ |
1329 | if (xSizeHint.flags & PAspect) { |
1330 | double min_aspect_w = xSizeHint.min_aspect.x; // use doubles, because the values can be MAX_INT |
1331 | double min_aspect_h = xSizeHint.min_aspect.y; // and multiplying would go wrong otherwise |
1332 | double max_aspect_w = xSizeHint.max_aspect.x; |
1333 | double max_aspect_h = xSizeHint.max_aspect.y; |
1334 | // According to ICCCM 4.1.2.3 PMinSize should be a fallback for PBaseSize for size increments, |
1335 | // but not for aspect ratio. Since this code comes from FVWM, handles both at the same time, |
1336 | // and I have no idea how it works, let's hope nobody relies on that. |
1337 | w -= xSizeHint.base_width; |
1338 | h -= xSizeHint.base_height; |
1339 | int max_width = max_size.width() - xSizeHint.base_width; |
1340 | int min_width = min_size.width() - xSizeHint.base_width; |
1341 | int max_height = max_size.height() - xSizeHint.base_height; |
1342 | int min_height = min_size.height() - xSizeHint.base_height; |
1343 | #define ASPECT_CHECK_GROW_W \ |
1344 | if ( min_aspect_w * h > min_aspect_h * w ) \ |
1345 | { \ |
1346 | int delta = int( min_aspect_w * h / min_aspect_h - w ) / width_inc * width_inc; \ |
1347 | if ( w + delta <= max_width ) \ |
1348 | w += delta; \ |
1349 | } |
1350 | #define ASPECT_CHECK_SHRINK_H_GROW_W \ |
1351 | if ( min_aspect_w * h > min_aspect_h * w ) \ |
1352 | { \ |
1353 | int delta = int( h - w * min_aspect_h / min_aspect_w ) / height_inc * height_inc; \ |
1354 | if ( h - delta >= min_height ) \ |
1355 | h -= delta; \ |
1356 | else \ |
1357 | { \ |
1358 | int delta = int( min_aspect_w * h / min_aspect_h - w ) / width_inc * width_inc; \ |
1359 | if ( w + delta <= max_width ) \ |
1360 | w += delta; \ |
1361 | } \ |
1362 | } |
1363 | #define ASPECT_CHECK_GROW_H \ |
1364 | if ( max_aspect_w * h < max_aspect_h * w ) \ |
1365 | { \ |
1366 | int delta = int( w * max_aspect_h / max_aspect_w - h ) / height_inc * height_inc; \ |
1367 | if ( h + delta <= max_height ) \ |
1368 | h += delta; \ |
1369 | } |
1370 | #define ASPECT_CHECK_SHRINK_W_GROW_H \ |
1371 | if ( max_aspect_w * h < max_aspect_h * w ) \ |
1372 | { \ |
1373 | int delta = int( w - max_aspect_w * h / max_aspect_h ) / width_inc * width_inc; \ |
1374 | if ( w - delta >= min_width ) \ |
1375 | w -= delta; \ |
1376 | else \ |
1377 | { \ |
1378 | int delta = int( w * max_aspect_h / max_aspect_w - h ) / height_inc * height_inc; \ |
1379 | if ( h + delta <= max_height ) \ |
1380 | h += delta; \ |
1381 | } \ |
1382 | } |
1383 | switch(mode) { |
1384 | case SizemodeAny: |
1385 | #if 0 // make SizemodeAny equal to SizemodeFixedW - prefer keeping fixed width, |
1386 | // so that changing aspect ratio to a different value and back keeps the same size (#87298) |
1387 | { |
1388 | ASPECT_CHECK_SHRINK_H_GROW_W |
1389 | ASPECT_CHECK_SHRINK_W_GROW_H |
1390 | ASPECT_CHECK_GROW_H |
1391 | ASPECT_CHECK_GROW_W |
1392 | break; |
1393 | } |
1394 | #endif |
1395 | case SizemodeFixedW: { |
1396 | // the checks are order so that attempts to modify height are first |
1397 | ASPECT_CHECK_GROW_H |
1398 | ASPECT_CHECK_SHRINK_H_GROW_W |
1399 | ASPECT_CHECK_SHRINK_W_GROW_H |
1400 | ASPECT_CHECK_GROW_W |
1401 | break; |
1402 | } |
1403 | case SizemodeFixedH: { |
1404 | ASPECT_CHECK_GROW_W |
1405 | ASPECT_CHECK_SHRINK_W_GROW_H |
1406 | ASPECT_CHECK_SHRINK_H_GROW_W |
1407 | ASPECT_CHECK_GROW_H |
1408 | break; |
1409 | } |
1410 | case SizemodeMax: { |
1411 | // first checks that try to shrink |
1412 | ASPECT_CHECK_SHRINK_H_GROW_W |
1413 | ASPECT_CHECK_SHRINK_W_GROW_H |
1414 | ASPECT_CHECK_GROW_W |
1415 | ASPECT_CHECK_GROW_H |
1416 | break; |
1417 | } |
1418 | } |
1419 | #undef ASPECT_CHECK_SHRINK_H_GROW_W |
1420 | #undef ASPECT_CHECK_SHRINK_W_GROW_H |
1421 | #undef ASPECT_CHECK_GROW_W |
1422 | #undef ASPECT_CHECK_GROW_H |
1423 | w += xSizeHint.base_width; |
1424 | h += xSizeHint.base_height; |
1425 | } |
1426 | if (!rules()->checkStrictGeometry(!isFullScreen())) { |
1427 | // disobey increments and aspect by explicit rule |
1428 | w = w1; |
1429 | h = h1; |
1430 | } |
1431 | |
1432 | if (!noframe) { |
1433 | w += border_left + border_right; |
1434 | h += border_top + border_bottom; |
1435 | } |
1436 | return rules()->checkSize(QSize(w, h)); |
1437 | } |
1438 | |
1439 | /*! |
1440 | Gets the client's normal WM hints and reconfigures itself respectively. |
1441 | */ |
1442 | void Client::getWmNormalHints() |
1443 | { |
1444 | long msize; |
1445 | const bool hadFixedAspect = xSizeHint.flags & PAspect; |
1446 | if (XGetWMNormalHints(display(), window(), &xSizeHint, &msize) == 0) |
1447 | xSizeHint.flags = 0; |
1448 | // set defined values for the fields, even if they're not in flags |
1449 | |
1450 | if (!(xSizeHint.flags & PMinSize)) |
1451 | xSizeHint.min_width = xSizeHint.min_height = 0; |
1452 | if (xSizeHint.flags & PBaseSize) { |
1453 | // PBaseSize is a fallback for PMinSize according to ICCCM 4.1.2.3 |
1454 | // The other way around PMinSize is not a complete fallback for PBaseSize, |
1455 | // so that's not handled here. |
1456 | if (!(xSizeHint.flags & PMinSize)) { |
1457 | xSizeHint.min_width = xSizeHint.base_width; |
1458 | xSizeHint.min_height = xSizeHint.base_height; |
1459 | } |
1460 | } else |
1461 | xSizeHint.base_width = xSizeHint.base_height = 0; |
1462 | if (!(xSizeHint.flags & PMaxSize)) |
1463 | xSizeHint.max_width = xSizeHint.max_height = INT_MAX; |
1464 | else { |
1465 | xSizeHint.max_width = qMax(xSizeHint.max_width, 1); |
1466 | xSizeHint.max_height = qMax(xSizeHint.max_height, 1); |
1467 | } |
1468 | if (xSizeHint.flags & PResizeInc) { |
1469 | xSizeHint.width_inc = qMax(xSizeHint.width_inc, 1); |
1470 | xSizeHint.height_inc = qMax(xSizeHint.height_inc, 1); |
1471 | } else { |
1472 | xSizeHint.width_inc = 1; |
1473 | xSizeHint.height_inc = 1; |
1474 | } |
1475 | if (xSizeHint.flags & PAspect) { |
1476 | // no dividing by zero |
1477 | xSizeHint.min_aspect.y = qMax(xSizeHint.min_aspect.y, 1); |
1478 | xSizeHint.max_aspect.y = qMax(xSizeHint.max_aspect.y, 1); |
1479 | if (!hadFixedAspect) |
1480 | maximize(max_mode); // align to eventual new contraints |
1481 | } else { |
1482 | xSizeHint.min_aspect.x = 1; |
1483 | xSizeHint.min_aspect.y = INT_MAX; |
1484 | xSizeHint.max_aspect.x = INT_MAX; |
1485 | xSizeHint.max_aspect.y = 1; |
1486 | } |
1487 | if (!(xSizeHint.flags & PWinGravity)) |
1488 | xSizeHint.win_gravity = NorthWestGravity; |
1489 | |
1490 | // Update min/max size of this group |
1491 | if (tabGroup()) |
1492 | tabGroup()->updateMinMaxSize(); |
1493 | |
1494 | if (isManaged()) { |
1495 | // update to match restrictions |
1496 | QSize new_size = adjustedSize(); |
1497 | if (new_size != size() && !isFullScreen()) { |
1498 | QRect origClientGeometry(pos() + clientPos(), clientSize()); |
1499 | resizeWithChecks(new_size); |
1500 | if ((!isSpecialWindow() || isToolbar()) && !isFullScreen()) { |
1501 | // try to keep the window in its xinerama screen if possible, |
1502 | // if that fails at least keep it visible somewhere |
1503 | QRect area = workspace()->clientArea(MovementArea, this); |
1504 | if (area.contains(origClientGeometry)) |
1505 | keepInArea(area); |
1506 | area = workspace()->clientArea(WorkArea, this); |
1507 | if (area.contains(origClientGeometry)) |
1508 | keepInArea(area); |
1509 | } |
1510 | } |
1511 | } |
1512 | updateAllowedActions(); // affects isResizeable() |
1513 | } |
1514 | |
1515 | QSize Client::minSize() const |
1516 | { |
1517 | return rules()->checkMinSize(QSize(xSizeHint.min_width, xSizeHint.min_height)); |
1518 | } |
1519 | |
1520 | QSize Client::maxSize() const |
1521 | { |
1522 | return rules()->checkMaxSize(QSize(xSizeHint.max_width, xSizeHint.max_height)); |
1523 | } |
1524 | |
1525 | QSize Client::basicUnit() const |
1526 | { |
1527 | return QSize(xSizeHint.width_inc, xSizeHint.height_inc); |
1528 | } |
1529 | |
1530 | /*! |
1531 | Auxiliary function to inform the client about the current window |
1532 | configuration. |
1533 | |
1534 | */ |
1535 | void Client::sendSyntheticConfigureNotify() |
1536 | { |
1537 | XConfigureEvent c; |
1538 | c.type = ConfigureNotify; |
1539 | c.send_event = True; |
1540 | c.event = window(); |
1541 | c.window = window(); |
1542 | c.x = x() + clientPos().x(); |
1543 | c.y = y() + clientPos().y(); |
1544 | c.width = clientSize().width(); |
1545 | c.height = clientSize().height(); |
1546 | c.border_width = 0; |
1547 | c.above = None; |
1548 | c.override_redirect = 0; |
1549 | XSendEvent(display(), c.event, true, StructureNotifyMask, (XEvent*)&c); |
1550 | } |
1551 | |
1552 | const QPoint Client::calculateGravitation(bool invert, int gravity) const |
1553 | { |
1554 | int dx, dy; |
1555 | dx = dy = 0; |
1556 | |
1557 | if (gravity == 0) // default (nonsense) value for the argument |
1558 | gravity = xSizeHint.win_gravity; |
1559 | |
1560 | // dx, dy specify how the client window moves to make space for the frame |
1561 | switch(gravity) { |
1562 | case NorthWestGravity: // move down right |
1563 | default: |
1564 | dx = border_left; |
1565 | dy = border_top; |
1566 | break; |
1567 | case NorthGravity: // move right |
1568 | dx = 0; |
1569 | dy = border_top; |
1570 | break; |
1571 | case NorthEastGravity: // move down left |
1572 | dx = -border_right; |
1573 | dy = border_top; |
1574 | break; |
1575 | case WestGravity: // move right |
1576 | dx = border_left; |
1577 | dy = 0; |
1578 | break; |
1579 | case CenterGravity: |
1580 | break; // will be handled specially |
1581 | case StaticGravity: // don't move |
1582 | dx = 0; |
1583 | dy = 0; |
1584 | break; |
1585 | case EastGravity: // move left |
1586 | dx = -border_right; |
1587 | dy = 0; |
1588 | break; |
1589 | case SouthWestGravity: // move up right |
1590 | dx = border_left ; |
1591 | dy = -border_bottom; |
1592 | break; |
1593 | case SouthGravity: // move up |
1594 | dx = 0; |
1595 | dy = -border_bottom; |
1596 | break; |
1597 | case SouthEastGravity: // move up left |
1598 | dx = -border_right; |
1599 | dy = -border_bottom; |
1600 | break; |
1601 | } |
1602 | if (gravity != CenterGravity) { |
1603 | // translate from client movement to frame movement |
1604 | dx -= border_left; |
1605 | dy -= border_top; |
1606 | } else { |
1607 | // center of the frame will be at the same position client center without frame would be |
1608 | dx = - (border_left + border_right) / 2; |
1609 | dy = - (border_top + border_bottom) / 2; |
1610 | } |
1611 | if (!invert) |
1612 | return QPoint(x() + dx, y() + dy); |
1613 | else |
1614 | return QPoint(x() - dx, y() - dy); |
1615 | } |
1616 | |
1617 | void Client::configureRequest(int value_mask, int rx, int ry, int rw, int rh, int gravity, bool from_tool) |
1618 | { |
1619 | // "maximized" is a user setting -> we do not allow the client to resize itself |
1620 | // away from this & against the users explicit wish |
1621 | kDebug(1212) << this << bool(value_mask & (CWX|CWWidth|CWY|CWHeight)) << |
1622 | bool(maximizeMode() & MaximizeVertical) << |
1623 | bool(maximizeMode() & MaximizeHorizontal); |
1624 | |
1625 | // we want to (partially) ignore the request when the window is somehow maximized or quicktiled |
1626 | bool ignore = !app_noborder && (quick_tile_mode != QuickTileNone || maximizeMode() != MaximizeRestore); |
1627 | // however, the user shall be able to force obedience despite and also disobedience in general |
1628 | ignore = rules()->checkIgnoreGeometry(ignore); |
1629 | if (!ignore) { // either we're not max'd / q'tiled or the user allowed the client to break that - so break it. |
1630 | quick_tile_mode = QuickTileNone; |
1631 | max_mode = MaximizeRestore; |
1632 | } else if (!app_noborder && quick_tile_mode == QuickTileNone && |
1633 | (maximizeMode() == MaximizeVertical || maximizeMode() == MaximizeHorizontal)) { |
1634 | // ignoring can be, because either we do, or the user does explicitly not want it. |
1635 | // for partially maximized windows we want to allow configures in the other dimension. |
1636 | // so we've to ask the user again - to know whether we just ignored for the partial maximization. |
1637 | // the problem here is, that the user can explicitly permit configure requests - even for maximized windows! |
1638 | // we cannot distinguish that from passing "false" for partially maximized windows. |
1639 | ignore = rules()->checkIgnoreGeometry(false); |
1640 | if (!ignore) { // the user is not interested, so we fix up dimensions |
1641 | if (maximizeMode() == MaximizeVertical) |
1642 | value_mask &= ~(CWY|CWHeight); |
1643 | if (maximizeMode() == MaximizeHorizontal) |
1644 | value_mask &= ~(CWX|CWWidth); |
1645 | if (!(value_mask & (CWX|CWWidth|CWY|CWHeight))) { |
1646 | ignore = true; // the modification turned the request void |
1647 | } |
1648 | } |
1649 | } |
1650 | |
1651 | if (ignore) { |
1652 | kDebug(1212) << "DENIED" ; |
1653 | return; // nothing to (left) to do for use - bugs #158974, #252314, #321491 |
1654 | } |
1655 | |
1656 | kDebug(1212) << "PERMITTED" << this << bool(value_mask & (CWX|CWWidth|CWY|CWHeight)); |
1657 | |
1658 | if (gravity == 0) // default (nonsense) value for the argument |
1659 | gravity = xSizeHint.win_gravity; |
1660 | if (value_mask & (CWX | CWY)) { |
1661 | QPoint new_pos = calculateGravitation(true, gravity); // undo gravitation |
1662 | if (value_mask & CWX) |
1663 | new_pos.setX(rx); |
1664 | if (value_mask & CWY) |
1665 | new_pos.setY(ry); |
1666 | |
1667 | // clever(?) workaround for applications like xv that want to set |
1668 | // the location to the current location but miscalculate the |
1669 | // frame size due to kwin being a double-reparenting window |
1670 | // manager |
1671 | if (new_pos.x() == x() + clientPos().x() && new_pos.y() == y() + clientPos().y() |
1672 | && gravity == NorthWestGravity && !from_tool) { |
1673 | new_pos.setX(x()); |
1674 | new_pos.setY(y()); |
1675 | } |
1676 | |
1677 | int nw = clientSize().width(); |
1678 | int nh = clientSize().height(); |
1679 | if (value_mask & CWWidth) |
1680 | nw = rw; |
1681 | if (value_mask & CWHeight) |
1682 | nh = rh; |
1683 | QSize ns = sizeForClientSize(QSize(nw, nh)); // enforces size if needed |
1684 | new_pos = rules()->checkPosition(new_pos); |
1685 | int newScreen = screens()->number(QRect(new_pos, ns).center()); |
1686 | if (newScreen != rules()->checkScreen(newScreen)) |
1687 | return; // not allowed by rule |
1688 | |
1689 | QRect origClientGeometry(pos() + clientPos(), clientSize()); |
1690 | GeometryUpdatesBlocker blocker(this); |
1691 | move(new_pos); |
1692 | plainResize(ns); |
1693 | setGeometry(QRect(calculateGravitation(false, gravity), size())); |
1694 | updateFullScreenHack(QRect(new_pos, QSize(nw, nh))); |
1695 | QRect area = workspace()->clientArea(WorkArea, this); |
1696 | if (!from_tool && (!isSpecialWindow() || isToolbar()) && !isFullScreen() |
1697 | && area.contains(origClientGeometry)) |
1698 | keepInArea(area); |
1699 | |
1700 | // this is part of the kicker-xinerama-hack... it should be |
1701 | // safe to remove when kicker gets proper ExtendedStrut support; |
1702 | // see Workspace::updateClientArea() and |
1703 | // Client::adjustedClientArea() |
1704 | if (hasStrut()) |
1705 | workspace() -> updateClientArea(); |
1706 | } |
1707 | |
1708 | if (value_mask & (CWWidth | CWHeight) |
1709 | && !(value_mask & (CWX | CWY))) { // pure resize |
1710 | int nw = clientSize().width(); |
1711 | int nh = clientSize().height(); |
1712 | if (value_mask & CWWidth) |
1713 | nw = rw; |
1714 | if (value_mask & CWHeight) |
1715 | nh = rh; |
1716 | QSize ns = sizeForClientSize(QSize(nw, nh)); |
1717 | |
1718 | if (ns != size()) { // don't restore if some app sets its own size again |
1719 | QRect origClientGeometry(pos() + clientPos(), clientSize()); |
1720 | GeometryUpdatesBlocker blocker(this); |
1721 | int save_gravity = xSizeHint.win_gravity; |
1722 | xSizeHint.win_gravity = gravity; |
1723 | resizeWithChecks(ns); |
1724 | xSizeHint.win_gravity = save_gravity; |
1725 | updateFullScreenHack(QRect(calculateGravitation(true, xSizeHint.win_gravity), QSize(nw, nh))); |
1726 | if (!from_tool && (!isSpecialWindow() || isToolbar()) && !isFullScreen()) { |
1727 | // try to keep the window in its xinerama screen if possible, |
1728 | // if that fails at least keep it visible somewhere |
1729 | QRect area = workspace()->clientArea(MovementArea, this); |
1730 | if (area.contains(origClientGeometry)) |
1731 | keepInArea(area); |
1732 | area = workspace()->clientArea(WorkArea, this); |
1733 | if (area.contains(origClientGeometry)) |
1734 | keepInArea(area); |
1735 | } |
1736 | } |
1737 | } |
1738 | geom_restore = geometry(); |
1739 | // No need to send synthetic configure notify event here, either it's sent together |
1740 | // with geometry change, or there's no need to send it. |
1741 | // Handling of the real ConfigureRequest event forces sending it, as there it's necessary. |
1742 | } |
1743 | |
1744 | void Client::resizeWithChecks(int w, int h, ForceGeometry_t force) |
1745 | { |
1746 | assert(!shade_geometry_change); |
1747 | if (isShade()) { |
1748 | if (h == border_top + border_bottom) { |
1749 | kWarning(1212) << "Shaded geometry passed for size:" ; |
1750 | kWarning(1212) << kBacktrace() ; |
1751 | } |
1752 | } |
1753 | int newx = x(); |
1754 | int newy = y(); |
1755 | QRect area = workspace()->clientArea(WorkArea, this); |
1756 | // don't allow growing larger than workarea |
1757 | if (w > area.width()) |
1758 | w = area.width(); |
1759 | if (h > area.height()) |
1760 | h = area.height(); |
1761 | QSize tmp = adjustedSize(QSize(w, h)); // checks size constraints, including min/max size |
1762 | w = tmp.width(); |
1763 | h = tmp.height(); |
1764 | switch(xSizeHint.win_gravity) { |
1765 | case NorthWestGravity: // top left corner doesn't move |
1766 | default: |
1767 | break; |
1768 | case NorthGravity: // middle of top border doesn't move |
1769 | newx = (newx + width() / 2) - (w / 2); |
1770 | break; |
1771 | case NorthEastGravity: // top right corner doesn't move |
1772 | newx = newx + width() - w; |
1773 | break; |
1774 | case WestGravity: // middle of left border doesn't move |
1775 | newy = (newy + height() / 2) - (h / 2); |
1776 | break; |
1777 | case CenterGravity: // middle point doesn't move |
1778 | newx = (newx + width() / 2) - (w / 2); |
1779 | newy = (newy + height() / 2) - (h / 2); |
1780 | break; |
1781 | case StaticGravity: // top left corner of _client_ window doesn't move |
1782 | // since decoration doesn't change, equal to NorthWestGravity |
1783 | break; |
1784 | case EastGravity: // // middle of right border doesn't move |
1785 | newx = newx + width() - w; |
1786 | newy = (newy + height() / 2) - (h / 2); |
1787 | break; |
1788 | case SouthWestGravity: // bottom left corner doesn't move |
1789 | newy = newy + height() - h; |
1790 | break; |
1791 | case SouthGravity: // middle of bottom border doesn't move |
1792 | newx = (newx + width() / 2) - (w / 2); |
1793 | newy = newy + height() - h; |
1794 | break; |
1795 | case SouthEastGravity: // bottom right corner doesn't move |
1796 | newx = newx + width() - w; |
1797 | newy = newy + height() - h; |
1798 | break; |
1799 | } |
1800 | setGeometry(newx, newy, w, h, force); |
1801 | } |
1802 | |
1803 | // _NET_MOVERESIZE_WINDOW |
1804 | void Client::NETMoveResizeWindow(int flags, int x, int y, int width, int height) |
1805 | { |
1806 | int gravity = flags & 0xff; |
1807 | int value_mask = 0; |
1808 | if (flags & (1 << 8)) |
1809 | value_mask |= CWX; |
1810 | if (flags & (1 << 9)) |
1811 | value_mask |= CWY; |
1812 | if (flags & (1 << 10)) |
1813 | value_mask |= CWWidth; |
1814 | if (flags & (1 << 11)) |
1815 | value_mask |= CWHeight; |
1816 | configureRequest(value_mask, x, y, width, height, gravity, true); |
1817 | } |
1818 | |
1819 | /*! |
1820 | Returns whether the window is moveable or has a fixed |
1821 | position. |
1822 | */ |
1823 | bool Client::isMovable() const |
1824 | { |
1825 | if (!motif_may_move || isFullScreen()) |
1826 | return false; |
1827 | if (isSpecialWindow() && !isSplash() && !isToolbar()) // allow moving of splashscreens :) |
1828 | return false; |
1829 | if (rules()->checkPosition(invalidPoint) != invalidPoint) // forced position |
1830 | return false; |
1831 | return true; |
1832 | } |
1833 | |
1834 | /*! |
1835 | Returns whether the window is moveable across Xinerama screens |
1836 | */ |
1837 | bool Client::isMovableAcrossScreens() const |
1838 | { |
1839 | if (!motif_may_move) |
1840 | return false; |
1841 | if (isSpecialWindow() && !isSplash() && !isToolbar()) // allow moving of splashscreens :) |
1842 | return false; |
1843 | if (rules()->checkPosition(invalidPoint) != invalidPoint) // forced position |
1844 | return false; |
1845 | return true; |
1846 | } |
1847 | |
1848 | /*! |
1849 | Returns whether the window is resizable or has a fixed size. |
1850 | */ |
1851 | bool Client::isResizable() const |
1852 | { |
1853 | if (!motif_may_resize || isFullScreen()) |
1854 | return false; |
1855 | if (isSpecialWindow() || isSplash() || isToolbar()) |
1856 | return false; |
1857 | if (rules()->checkSize(QSize()).isValid()) // forced size |
1858 | return false; |
1859 | if ((mode == PositionTop || mode == PositionTopLeft || mode == PositionTopRight || |
1860 | mode == PositionLeft || mode == PositionBottomLeft) && rules()->checkPosition(invalidPoint) != invalidPoint) |
1861 | return false; |
1862 | |
1863 | QSize min = tabGroup() ? tabGroup()->minSize() : minSize(); |
1864 | QSize max = tabGroup() ? tabGroup()->maxSize() : maxSize(); |
1865 | return min.width() < max.width() || min.height() < max.height(); |
1866 | } |
1867 | |
1868 | /* |
1869 | Returns whether the window is maximizable or not |
1870 | */ |
1871 | bool Client::isMaximizable() const |
1872 | { |
1873 | { |
1874 | // isMovable() and isResizable() may be false for maximized windows |
1875 | // with moving/resizing maximized windows disabled |
1876 | TemporaryAssign< MaximizeMode > tmp(max_mode, MaximizeRestore); |
1877 | if (!isResizable() || isToolbar()) // SELI isToolbar() ? |
1878 | return false; |
1879 | } |
1880 | if (rules()->checkMaximize(MaximizeRestore) == MaximizeRestore && rules()->checkMaximize(MaximizeFull) != MaximizeRestore) |
1881 | return true; |
1882 | return false; |
1883 | } |
1884 | |
1885 | |
1886 | /*! |
1887 | Reimplemented to inform the client about the new window position. |
1888 | */ |
1889 | void Client::setGeometry(int x, int y, int w, int h, ForceGeometry_t force) |
1890 | { |
1891 | // this code is also duplicated in Client::plainResize() |
1892 | // Ok, the shading geometry stuff. Generally, code doesn't care about shaded geometry, |
1893 | // simply because there are too many places dealing with geometry. Those places |
1894 | // ignore shaded state and use normal geometry, which they usually should get |
1895 | // from adjustedSize(). Such geometry comes here, and if the window is shaded, |
1896 | // the geometry is used only for client_size, since that one is not used when |
1897 | // shading. Then the frame geometry is adjusted for the shaded geometry. |
1898 | // This gets more complicated in the case the code does only something like |
1899 | // setGeometry( geometry()) - geometry() will return the shaded frame geometry. |
1900 | // Such code is wrong and should be changed to handle the case when the window is shaded, |
1901 | // for example using Client::clientSize() |
1902 | |
1903 | if (shade_geometry_change) |
1904 | ; // nothing |
1905 | else if (isShade()) { |
1906 | if (h == border_top + border_bottom) { |
1907 | kDebug(1212) << "Shaded geometry passed for size:" ; |
1908 | kDebug(1212) << kBacktrace(); |
1909 | } else { |
1910 | client_size = QSize(w - border_left - border_right, h - border_top - border_bottom); |
1911 | h = border_top + border_bottom; |
1912 | } |
1913 | } else { |
1914 | client_size = QSize(w - border_left - border_right, h - border_top - border_bottom); |
1915 | } |
1916 | QRect g(x, y, w, h); |
1917 | if (block_geometry_updates == 0 && g != rules()->checkGeometry(g)) { |
1918 | kDebug(1212) << "forced geometry fail:" << g << ":" << rules()->checkGeometry(g); |
1919 | kDebug(1212) << kBacktrace(); |
1920 | } |
1921 | if (force == NormalGeometrySet && geom == g && pending_geometry_update == PendingGeometryNone) |
1922 | return; |
1923 | geom = g; |
1924 | if (block_geometry_updates != 0) { |
1925 | if (pending_geometry_update == PendingGeometryForced) |
1926 | {} // maximum, nothing needed |
1927 | else if (force == ForceGeometrySet) |
1928 | pending_geometry_update = PendingGeometryForced; |
1929 | else |
1930 | pending_geometry_update = PendingGeometryNormal; |
1931 | return; |
1932 | } |
1933 | bool resized = (geom_before_block.size() != geom.size() || pending_geometry_update == PendingGeometryForced); |
1934 | if (resized) { |
1935 | resizeDecoration(QSize(w, h)); |
1936 | XMoveResizeWindow(display(), frameId(), x, y, w, h); |
1937 | if (!isShade()) { |
1938 | QSize cs = clientSize(); |
1939 | XMoveResizeWindow(display(), wrapperId(), clientPos().x(), clientPos().y(), |
1940 | cs.width(), cs.height()); |
1941 | #ifdef HAVE_XSYNC |
1942 | if (!isResize() || syncRequest.counter == None) |
1943 | #endif |
1944 | XMoveResizeWindow(display(), window(), 0, 0, cs.width(), cs.height()); |
1945 | // SELI - won't this be too expensive? |
1946 | // THOMAS - yes, but gtk+ clients will not resize without ... |
1947 | sendSyntheticConfigureNotify(); |
1948 | } |
1949 | updateShape(); |
1950 | } else { |
1951 | if (moveResizeMode) { |
1952 | if (compositing()) // Defer the X update until we leave this mode |
1953 | needsXWindowMove = true; |
1954 | else |
1955 | XMoveWindow(display(), frameId(), x, y); // sendSyntheticConfigureNotify() on finish shall be sufficient |
1956 | } else { |
1957 | XMoveWindow(display(), frameId(), x, y); |
1958 | sendSyntheticConfigureNotify(); |
1959 | } |
1960 | |
1961 | // Unconditionally move the input window: it won't affect rendering |
1962 | m_decoInputExtent.move(QPoint(x, y) + inputPos()); |
1963 | } |
1964 | updateWindowRules(Rules::Position|Rules::Size); |
1965 | |
1966 | // keep track of old maximize mode |
1967 | // to detect changes |
1968 | screens()->setCurrent(this); |
1969 | workspace()->updateStackingOrder(); |
1970 | |
1971 | // need to regenerate decoration pixmaps when either |
1972 | // - size is changed |
1973 | // - maximize mode is changed to MaximizeRestore, when size unchanged |
1974 | // which can happen when untabbing maximized windows |
1975 | if (resized) { |
1976 | discardWindowPixmap(); |
1977 | emit geometryShapeChanged(this, geom_before_block); |
1978 | } |
1979 | const QRect deco_rect = visibleRect(); |
1980 | addLayerRepaint(deco_rect_before_block); |
1981 | addLayerRepaint(deco_rect); |
1982 | geom_before_block = geom; |
1983 | deco_rect_before_block = deco_rect; |
1984 | |
1985 | // Update states of all other windows in this group |
1986 | if (tabGroup()) |
1987 | tabGroup()->updateStates(this, TabGroup::Geometry); |
1988 | |
1989 | // TODO: this signal is emitted too often |
1990 | emit geometryChanged(); |
1991 | } |
1992 | |
1993 | void Client::plainResize(int w, int h, ForceGeometry_t force) |
1994 | { |
1995 | // this code is also duplicated in Client::setGeometry(), and it's also commented there |
1996 | if (shade_geometry_change) |
1997 | ; // nothing |
1998 | else if (isShade()) { |
1999 | if (h == border_top + border_bottom) { |
2000 | kDebug(1212) << "Shaded geometry passed for size:" ; |
2001 | kDebug(1212) << kBacktrace(); |
2002 | } else { |
2003 | client_size = QSize(w - border_left - border_right, h - border_top - border_bottom); |
2004 | h = border_top + border_bottom; |
2005 | } |
2006 | } else { |
2007 | client_size = QSize(w - border_left - border_right, h - border_top - border_bottom); |
2008 | } |
2009 | QSize s(w, h); |
2010 | if (block_geometry_updates == 0 && s != rules()->checkSize(s)) { |
2011 | kDebug(1212) << "forced size fail:" << s << ":" << rules()->checkSize(s); |
2012 | kDebug(1212) << kBacktrace(); |
2013 | } |
2014 | // resuming geometry updates is handled only in setGeometry() |
2015 | assert(pending_geometry_update == PendingGeometryNone || block_geometry_updates > 0); |
2016 | if (force == NormalGeometrySet && geom.size() == s) |
2017 | return; |
2018 | geom.setSize(s); |
2019 | if (block_geometry_updates != 0) { |
2020 | if (pending_geometry_update == PendingGeometryForced) |
2021 | {} // maximum, nothing needed |
2022 | else if (force == ForceGeometrySet) |
2023 | pending_geometry_update = PendingGeometryForced; |
2024 | else |
2025 | pending_geometry_update = PendingGeometryNormal; |
2026 | return; |
2027 | } |
2028 | resizeDecoration(s); |
2029 | XResizeWindow(display(), frameId(), w, h); |
2030 | // resizeDecoration( s ); |
2031 | if (!isShade()) { |
2032 | QSize cs = clientSize(); |
2033 | XMoveResizeWindow(display(), wrapperId(), clientPos().x(), clientPos().y(), |
2034 | cs.width(), cs.height()); |
2035 | XMoveResizeWindow(display(), window(), 0, 0, cs.width(), cs.height()); |
2036 | } |
2037 | updateShape(); |
2038 | |
2039 | sendSyntheticConfigureNotify(); |
2040 | updateWindowRules(Rules::Position|Rules::Size); |
2041 | screens()->setCurrent(this); |
2042 | workspace()->updateStackingOrder(); |
2043 | discardWindowPixmap(); |
2044 | emit geometryShapeChanged(this, geom_before_block); |
2045 | const QRect deco_rect = visibleRect(); |
2046 | addLayerRepaint(deco_rect_before_block); |
2047 | addLayerRepaint(deco_rect); |
2048 | geom_before_block = geom; |
2049 | deco_rect_before_block = deco_rect; |
2050 | |
2051 | // Update states of all other windows in this group |
2052 | if (tabGroup()) |
2053 | tabGroup()->updateStates(this, TabGroup::Geometry); |
2054 | // TODO: this signal is emitted too often |
2055 | emit geometryChanged(); |
2056 | } |
2057 | |
2058 | /*! |
2059 | Reimplemented to inform the client about the new window position. |
2060 | */ |
2061 | void Client::move(int x, int y, ForceGeometry_t force) |
2062 | { |
2063 | // resuming geometry updates is handled only in setGeometry() |
2064 | assert(pending_geometry_update == PendingGeometryNone || block_geometry_updates > 0); |
2065 | QPoint p(x, y); |
2066 | if (block_geometry_updates == 0 && p != rules()->checkPosition(p)) { |
2067 | kDebug(1212) << "forced position fail:" << p << ":" << rules()->checkPosition(p); |
2068 | kDebug(1212) << kBacktrace(); |
2069 | } |
2070 | if (force == NormalGeometrySet && geom.topLeft() == p) |
2071 | return; |
2072 | geom.moveTopLeft(p); |
2073 | if (block_geometry_updates != 0) { |
2074 | if (pending_geometry_update == PendingGeometryForced) |
2075 | {} // maximum, nothing needed |
2076 | else if (force == ForceGeometrySet) |
2077 | pending_geometry_update = PendingGeometryForced; |
2078 | else |
2079 | pending_geometry_update = PendingGeometryNormal; |
2080 | return; |
2081 | } |
2082 | XMoveWindow(display(), frameId(), x, y); |
2083 | sendSyntheticConfigureNotify(); |
2084 | updateWindowRules(Rules::Position); |
2085 | screens()->setCurrent(this); |
2086 | workspace()->updateStackingOrder(); |
2087 | if (Compositor::isCreated()) { |
2088 | // TODO: move out of geometry.cpp, is this really needed here? |
2089 | Compositor::self()->checkUnredirect(); |
2090 | } |
2091 | // client itself is not damaged |
2092 | const QRect deco_rect = visibleRect(); |
2093 | addLayerRepaint(deco_rect_before_block); |
2094 | addLayerRepaint(deco_rect); // trigger repaint of window's new location |
2095 | geom_before_block = geom; |
2096 | deco_rect_before_block = deco_rect; |
2097 | |
2098 | // Update states of all other windows in this group |
2099 | if (tabGroup()) |
2100 | tabGroup()->updateStates(this, TabGroup::Geometry); |
2101 | emit geometryChanged(); |
2102 | } |
2103 | |
2104 | void Client::blockGeometryUpdates(bool block) |
2105 | { |
2106 | if (block) { |
2107 | if (block_geometry_updates == 0) |
2108 | pending_geometry_update = PendingGeometryNone; |
2109 | ++block_geometry_updates; |
2110 | } else { |
2111 | if (--block_geometry_updates == 0) { |
2112 | if (pending_geometry_update != PendingGeometryNone) { |
2113 | if (isShade()) |
2114 | setGeometry(QRect(pos(), adjustedSize()), NormalGeometrySet); |
2115 | else |
2116 | setGeometry(geometry(), NormalGeometrySet); |
2117 | pending_geometry_update = PendingGeometryNone; |
2118 | } |
2119 | } |
2120 | } |
2121 | } |
2122 | |
2123 | void Client::maximize(MaximizeMode m) |
2124 | { |
2125 | setMaximize(m & MaximizeVertical, m & MaximizeHorizontal); |
2126 | } |
2127 | |
2128 | /*! |
2129 | Sets the maximization according to \a vertically and \a horizontally |
2130 | */ |
2131 | void Client::setMaximize(bool vertically, bool horizontally) |
2132 | { |
2133 | // changeMaximize() flips the state, so change from set->flip |
2134 | MaximizeMode oldMode = maximizeMode(); |
2135 | changeMaximize( |
2136 | max_mode & MaximizeVertical ? !vertically : vertically, |
2137 | max_mode & MaximizeHorizontal ? !horizontally : horizontally, |
2138 | false); |
2139 | if (oldMode != maximizeMode()) { |
2140 | emit clientMaximizedStateChanged(this, max_mode); |
2141 | emit clientMaximizedStateChanged(this, vertically, horizontally); |
2142 | } |
2143 | |
2144 | } |
2145 | |
2146 | // Update states of all other windows in this group |
2147 | class TabSynchronizer |
2148 | { |
2149 | public: |
2150 | TabSynchronizer(Client *client, TabGroup::States syncStates) : |
2151 | m_client(client) , m_states(syncStates) |
2152 | { |
2153 | if (client->tabGroup()) |
2154 | client->tabGroup()->blockStateUpdates(true); |
2155 | } |
2156 | ~TabSynchronizer() |
2157 | { |
2158 | syncNow(); |
2159 | } |
2160 | void syncNow() |
2161 | { |
2162 | if (m_client && m_client->tabGroup()) { |
2163 | m_client->tabGroup()->blockStateUpdates(false); |
2164 | m_client->tabGroup()->updateStates(m_client, m_states); |
2165 | } |
2166 | m_client = 0; |
2167 | } |
2168 | private: |
2169 | Client *m_client; |
2170 | TabGroup::States m_states; |
2171 | }; |
2172 | |
2173 | |
2174 | static bool changeMaximizeRecursion = false; |
2175 | void Client::changeMaximize(bool vertical, bool horizontal, bool adjust) |
2176 | { |
2177 | if (changeMaximizeRecursion) |
2178 | return; |
2179 | |
2180 | // sic! codeblock for TemporaryAssign |
2181 | { |
2182 | // isMovable() and isResizable() may be false for maximized windows |
2183 | // with moving/resizing maximized windows disabled |
2184 | TemporaryAssign< MaximizeMode > tmp(max_mode, MaximizeRestore); |
2185 | if (!isResizable() || isToolbar()) // SELI isToolbar() ? |
2186 | return; |
2187 | } |
2188 | |
2189 | QRect clientArea; |
2190 | if (isElectricBorderMaximizing()) |
2191 | clientArea = workspace()->clientArea(MaximizeArea, cursorPos(), desktop()); |
2192 | else |
2193 | clientArea = workspace()->clientArea(MaximizeArea, this); |
2194 | |
2195 | MaximizeMode old_mode = max_mode; |
2196 | // 'adjust == true' means to update the size only, e.g. after changing workspace size |
2197 | if (!adjust) { |
2198 | if (vertical) |
2199 | max_mode = MaximizeMode(max_mode ^ MaximizeVertical); |
2200 | if (horizontal) |
2201 | max_mode = MaximizeMode(max_mode ^ MaximizeHorizontal); |
2202 | } |
2203 | |
2204 | // if the client insist on a fix aspect ratio, we check whether the maximizing will get us |
2205 | // out of screen bounds and take that as a "full maximization with aspect check" then |
2206 | if ((xSizeHint.flags & PAspect) && // fixed aspect |
2207 | (max_mode == MaximizeVertical || max_mode == MaximizeHorizontal) && // ondimensional maximization |
2208 | rules()->checkStrictGeometry(true)) { // obey aspect |
2209 | if (max_mode == MaximizeVertical || (old_mode & MaximizeVertical)) { |
2210 | const double fx = xSizeHint.min_aspect.x; // use doubles, because the values can be MAX_INT |
2211 | const double fy = xSizeHint.max_aspect.y; // use doubles, because the values can be MAX_INT |
2212 | if (fx*clientArea.height()/fy > clientArea.width()) // too big |
2213 | max_mode = old_mode & MaximizeHorizontal ? MaximizeRestore : MaximizeFull; |
2214 | } else { // max_mode == MaximizeHorizontal |
2215 | const double fx = xSizeHint.max_aspect.x; |
2216 | const double fy = xSizeHint.min_aspect.y; |
2217 | if (fy*clientArea.width()/fx > clientArea.height()) // too big |
2218 | max_mode = old_mode & MaximizeVertical ? MaximizeRestore : MaximizeFull; |
2219 | } |
2220 | } |
2221 | |
2222 | max_mode = rules()->checkMaximize(max_mode); |
2223 | if (!adjust && max_mode == old_mode) |
2224 | return; |
2225 | |
2226 | GeometryUpdatesBlocker blocker(this); |
2227 | // QT synchronizing required because we eventually change from QT to Maximized |
2228 | TabSynchronizer syncer(this, TabGroup::Maximized|TabGroup::QuickTile); |
2229 | |
2230 | // maximing one way and unmaximizing the other way shouldn't happen, |
2231 | // so restore first and then maximize the other way |
2232 | if ((old_mode == MaximizeVertical && max_mode == MaximizeHorizontal) |
2233 | || (old_mode == MaximizeHorizontal && max_mode == MaximizeVertical)) { |
2234 | changeMaximize(false, false, false); // restore |
2235 | } |
2236 | |
2237 | // save sizes for restoring, if maximalizing |
2238 | QSize sz; |
2239 | if (isShade()) |
2240 | sz = sizeForClientSize(clientSize()); |
2241 | else |
2242 | sz = size(); |
2243 | |
2244 | if (quick_tile_mode == QuickTileNone) { |
2245 | if (!adjust && !(old_mode & MaximizeVertical)) { |
2246 | geom_restore.setTop(y()); |
2247 | geom_restore.setHeight(sz.height()); |
2248 | } |
2249 | if (!adjust && !(old_mode & MaximizeHorizontal)) { |
2250 | geom_restore.setLeft(x()); |
2251 | geom_restore.setWidth(sz.width()); |
2252 | } |
2253 | } |
2254 | |
2255 | if (options->borderlessMaximizedWindows()) { |
2256 | // triggers a maximize change. |
2257 | // The next setNoBorder interation will exit since there's no change but the first recursion pullutes the restore geometry |
2258 | changeMaximizeRecursion = true; |
2259 | setNoBorder(app_noborder || max_mode == MaximizeFull); |
2260 | changeMaximizeRecursion = false; |
2261 | } |
2262 | |
2263 | const ForceGeometry_t geom_mode = decoration && checkBorderSizes(false) ? ForceGeometrySet : NormalGeometrySet; |
2264 | |
2265 | // Conditional quick tiling exit points |
2266 | if (quick_tile_mode != QuickTileNone) { |
2267 | if (old_mode == MaximizeFull && |
2268 | !clientArea.contains(geom_restore.center())) { |
2269 | // Not restoring on the same screen |
2270 | // TODO: The following doesn't work for some reason |
2271 | //quick_tile_mode = QuickTileNone; // And exit quick tile mode manually |
2272 | } else if ((old_mode == MaximizeVertical && max_mode == MaximizeRestore) || |
2273 | (old_mode == MaximizeFull && max_mode == MaximizeHorizontal)) { |
2274 | // Modifying geometry of a tiled window |
2275 | quick_tile_mode = QuickTileNone; // Exit quick tile mode without restoring geometry |
2276 | } |
2277 | } |
2278 | |
2279 | switch(max_mode) { |
2280 | |
2281 | case MaximizeVertical: { |
2282 | if (old_mode & MaximizeHorizontal) { // actually restoring from MaximizeFull |
2283 | if (geom_restore.width() == 0 || !clientArea.contains(geom_restore.center())) { |
2284 | // needs placement |
2285 | plainResize(adjustedSize(QSize(width() * 2 / 3, clientArea.height()), SizemodeFixedH), geom_mode); |
2286 | Placement::self()->placeSmart(this, clientArea); |
2287 | } else { |
2288 | setGeometry(QRect(QPoint(geom_restore.x(), clientArea.top()), |
2289 | adjustedSize(QSize(geom_restore.width(), clientArea.height()), SizemodeFixedH)), geom_mode); |
2290 | } |
2291 | } else { |
2292 | QRect r(x(), clientArea.top(), width(), clientArea.height()); |
2293 | r.setTopLeft(rules()->checkPosition(r.topLeft())); |
2294 | r.setSize(adjustedSize(r.size(), SizemodeFixedH)); |
2295 | setGeometry(r, geom_mode); |
2296 | } |
2297 | info->setState(NET::MaxVert, NET::Max); |
2298 | break; |
2299 | } |
2300 | |
2301 | case MaximizeHorizontal: { |
2302 | if (old_mode & MaximizeVertical) { // actually restoring from MaximizeFull |
2303 | if (geom_restore.height() == 0 || !clientArea.contains(geom_restore.center())) { |
2304 | // needs placement |
2305 | plainResize(adjustedSize(QSize(clientArea.width(), height() * 2 / 3), SizemodeFixedW), geom_mode); |
2306 | Placement::self()->placeSmart(this, clientArea); |
2307 | } else { |
2308 | setGeometry(QRect(QPoint(clientArea.left(), geom_restore.y()), |
2309 | adjustedSize(QSize(clientArea.width(), geom_restore.height()), SizemodeFixedW)), geom_mode); |
2310 | } |
2311 | } else { |
2312 | QRect r(clientArea.left(), y(), clientArea.width(), height()); |
2313 | r.setTopLeft(rules()->checkPosition(r.topLeft())); |
2314 | r.setSize(adjustedSize(r.size(), SizemodeFixedW)); |
2315 | setGeometry(r, geom_mode); |
2316 | } |
2317 | info->setState(NET::MaxHoriz, NET::Max); |
2318 | break; |
2319 | } |
2320 | |
2321 | case MaximizeRestore: { |
2322 | QRect restore = geometry(); |
2323 | // when only partially maximized, geom_restore may not have the other dimension remembered |
2324 | if (old_mode & MaximizeVertical) { |
2325 | restore.setTop(geom_restore.top()); |
2326 | restore.setBottom(geom_restore.bottom()); |
2327 | } |
2328 | if (old_mode & MaximizeHorizontal) { |
2329 | restore.setLeft(geom_restore.left()); |
2330 | restore.setRight(geom_restore.right()); |
2331 | } |
2332 | if (!restore.isValid()) { |
2333 | QSize s = QSize(clientArea.width() * 2 / 3, clientArea.height() * 2 / 3); |
2334 | if (geom_restore.width() > 0) |
2335 | s.setWidth(geom_restore.width()); |
2336 | if (geom_restore.height() > 0) |
2337 | s.setHeight(geom_restore.height()); |
2338 | plainResize(adjustedSize(s)); |
2339 | Placement::self()->placeSmart(this, clientArea); |
2340 | restore = geometry(); |
2341 | if (geom_restore.width() > 0) |
2342 | restore.moveLeft(geom_restore.x()); |
2343 | if (geom_restore.height() > 0) |
2344 | restore.moveTop(geom_restore.y()); |
2345 | geom_restore = restore; // relevant for mouse pos calculation, bug #298646 |
2346 | } |
2347 | if (xSizeHint.flags & PAspect) { |
2348 | restore.setSize(adjustedSize(restore.size(), SizemodeAny)); |
2349 | } |
2350 | setGeometry(restore, geom_mode); |
2351 | if (!clientArea.contains(geom_restore.center())) // Not restoring to the same screen |
2352 | Placement::self()->place(this, clientArea); |
2353 | info->setState(0, NET::Max); |
2354 | quick_tile_mode = QuickTileNone; |
2355 | break; |
2356 | } |
2357 | |
2358 | case MaximizeFull: { |
2359 | QRect r(clientArea); |
2360 | r.setTopLeft(rules()->checkPosition(r.topLeft())); |
2361 | r.setSize(adjustedSize(r.size(), SizemodeMax)); |
2362 | if (r.size() != clientArea.size()) { // to avoid off-by-one errors... |
2363 | if (isElectricBorderMaximizing() && r.width() < clientArea.width()) |
2364 | r.moveLeft(Cursor::pos().x() - r.width()/2); |
2365 | else |
2366 | r.moveCenter(clientArea.center()); |
2367 | r.moveTopLeft(rules()->checkPosition(r.topLeft())); |
2368 | } |
2369 | setGeometry(r, geom_mode); |
2370 | if (options->electricBorderMaximize() && r.top() == clientArea.top()) |
2371 | quick_tile_mode = QuickTileMaximize; |
2372 | else |
2373 | quick_tile_mode = QuickTileNone; |
2374 | info->setState(NET::Max, NET::Max); |
2375 | break; |
2376 | } |
2377 | default: |
2378 | break; |
2379 | } |
2380 | |
2381 | syncer.syncNow(); // important because of window rule updates! |
2382 | |
2383 | updateAllowedActions(); |
2384 | updateWindowRules(Rules::MaximizeVert|Rules::MaximizeHoriz|Rules::Position|Rules::Size); |
2385 | } |
2386 | |
2387 | bool Client::isFullScreenable(bool fullscreen_hack) const |
2388 | { |
2389 | if (!rules()->checkFullScreen(true)) |
2390 | return false; |
2391 | if (fullscreen_hack) |
2392 | return isNormalWindow(); |
2393 | if (rules()->checkStrictGeometry(true)) { // allow rule to ignore geometry constraints |
2394 | QRect fsarea = workspace()->clientArea(FullScreenArea, this); |
2395 | if (sizeForClientSize(fsarea.size(), SizemodeAny, true) != fsarea.size()) |
2396 | return false; // the app wouldn't fit exactly fullscreen geometry due to its strict geometry requirements |
2397 | } |
2398 | // don't check size constrains - some apps request fullscreen despite requesting fixed size |
2399 | return !isSpecialWindow(); // also better disallow only weird types to go fullscreen |
2400 | } |
2401 | |
2402 | bool Client::userCanSetFullScreen() const |
2403 | { |
2404 | if (fullscreen_mode == FullScreenHack) |
2405 | return false; |
2406 | if (!isFullScreenable(false)) |
2407 | return false; |
2408 | return isNormalWindow() || isDialog(); |
2409 | } |
2410 | |
2411 | void Client::setFullScreen(bool set, bool user) |
2412 | { |
2413 | if (!isFullScreen() && !set) |
2414 | return; |
2415 | if (fullscreen_mode == FullScreenHack) |
2416 | return; |
2417 | if (user && !userCanSetFullScreen()) |
2418 | return; |
2419 | set = rules()->checkFullScreen(set && !isSpecialWindow()); |
2420 | setShade(ShadeNone); |
2421 | bool was_fs = isFullScreen(); |
2422 | if (was_fs) |
2423 | workspace()->updateFocusMousePosition(Cursor::pos()); |
2424 | else |
2425 | geom_fs_restore = geometry(); |
2426 | fullscreen_mode = set ? FullScreenNormal : FullScreenNone; |
2427 | if (was_fs == isFullScreen()) |
2428 | return; |
2429 | if (set) { |
2430 | untab(); |
2431 | workspace()->raiseClient(this); |
2432 | } |
2433 | StackingUpdatesBlocker blocker1(workspace()); |
2434 | GeometryUpdatesBlocker blocker2(this); |
2435 | workspace()->updateClientLayer(this); // active fullscreens get different layer |
2436 | info->setState(isFullScreen() ? NET::FullScreen : 0, NET::FullScreen); |
2437 | updateDecoration(false, false); |
2438 | if (isFullScreen()) { |
2439 | if (info->fullscreenMonitors().isSet()) |
2440 | setGeometry(fullscreenMonitorsArea(info->fullscreenMonitors())); |
2441 | else |
2442 | setGeometry(workspace()->clientArea(FullScreenArea, this)); |
2443 | } |
2444 | else { |
2445 | if (!geom_fs_restore.isNull()) { |
2446 | int currentScreen = screen(); |
2447 | setGeometry(QRect(geom_fs_restore.topLeft(), adjustedSize(geom_fs_restore.size()))); |
2448 | if( currentScreen != screen()) |
2449 | workspace()->sendClientToScreen( this, currentScreen ); |
2450 | // TODO isShaded() ? |
2451 | } else { |
2452 | // does this ever happen? |
2453 | setGeometry(workspace()->clientArea(MaximizeArea, this)); |
2454 | } |
2455 | } |
2456 | updateWindowRules(Rules::Fullscreen|Rules::Position|Rules::Size); |
2457 | |
2458 | if (was_fs != isFullScreen()) { |
2459 | emit clientFullScreenSet(this, set, user); |
2460 | emit fullScreenChanged(); |
2461 | } |
2462 | } |
2463 | |
2464 | |
2465 | void Client::updateFullscreenMonitors(NETFullscreenMonitors topology) |
2466 | { |
2467 | int nscreens = screens()->count(); |
2468 | |
2469 | // kDebug( 1212 ) << "incoming request with top: " << topology.top << " bottom: " << topology.bottom |
2470 | // << " left: " << topology.left << " right: " << topology.right |
2471 | // << ", we have: " << nscreens << " screens."; |
2472 | |
2473 | if (topology.top >= nscreens || |
2474 | topology.bottom >= nscreens || |
2475 | topology.left >= nscreens || |
2476 | topology.right >= nscreens) { |
2477 | kWarning(1212) << "fullscreenMonitors update failed. request higher than number of screens." ; |
2478 | return; |
2479 | } |
2480 | |
2481 | info->setFullscreenMonitors(topology); |
2482 | if (isFullScreen()) |
2483 | setGeometry(fullscreenMonitorsArea(topology)); |
2484 | } |
2485 | |
2486 | |
2487 | /*! |
2488 | Calculates the bounding rectangle defined by the 4 monitor indices indicating the |
2489 | top, bottom, left, and right edges of the window when the fullscreen state is enabled. |
2490 | */ |
2491 | QRect Client::fullscreenMonitorsArea(NETFullscreenMonitors requestedTopology) const |
2492 | { |
2493 | QRect top, bottom, left, right, total; |
2494 | |
2495 | top = screens()->geometry(requestedTopology.top); |
2496 | bottom = screens()->geometry(requestedTopology.bottom); |
2497 | left = screens()->geometry(requestedTopology.left); |
2498 | right = screens()->geometry(requestedTopology.right); |
2499 | total = top.united(bottom.united(left.united(right))); |
2500 | |
2501 | // kDebug( 1212 ) << "top: " << top << " bottom: " << bottom |
2502 | // << " left: " << left << " right: " << right; |
2503 | // kDebug( 1212 ) << "returning rect: " << total; |
2504 | return total; |
2505 | } |
2506 | |
2507 | |
2508 | int Client::checkFullScreenHack(const QRect& geom) const |
2509 | { |
2510 | if (!options->isLegacyFullscreenSupport()) |
2511 | return 0; |
2512 | // if it's noborder window, and has size of one screen or the whole desktop geometry, it's fullscreen hack |
2513 | if (noBorder() && app_noborder && isFullScreenable(true)) { |
2514 | if (geom.size() == workspace()->clientArea(FullArea, geom.center(), desktop()).size()) |
2515 | return 2; // full area fullscreen hack |
2516 | if (geom.size() == workspace()->clientArea(ScreenArea, geom.center(), desktop()).size()) |
2517 | return 1; // xinerama-aware fullscreen hack |
2518 | } |
2519 | return 0; |
2520 | } |
2521 | |
2522 | void Client::updateFullScreenHack(const QRect& geom) |
2523 | { |
2524 | int type = checkFullScreenHack(geom); |
2525 | if (fullscreen_mode == FullScreenNone && type != 0) { |
2526 | fullscreen_mode = FullScreenHack; |
2527 | updateDecoration(false, false); |
2528 | QRect geom; |
2529 | if (rules()->checkStrictGeometry(false)) { |
2530 | geom = type == 2 // 1 - it's xinerama-aware fullscreen hack, 2 - it's full area |
2531 | ? workspace()->clientArea(FullArea, geom.center(), desktop()) |
2532 | : workspace()->clientArea(ScreenArea, geom.center(), desktop()); |
2533 | } else |
2534 | geom = workspace()->clientArea(FullScreenArea, geom.center(), desktop()); |
2535 | setGeometry(geom); |
2536 | emit fullScreenChanged(); |
2537 | } else if (fullscreen_mode == FullScreenHack && type == 0) { |
2538 | fullscreen_mode = FullScreenNone; |
2539 | updateDecoration(false, false); |
2540 | // whoever called this must setup correct geometry |
2541 | emit fullScreenChanged(); |
2542 | } |
2543 | StackingUpdatesBlocker blocker(workspace()); |
2544 | workspace()->updateClientLayer(this); // active fullscreens get different layer |
2545 | } |
2546 | |
2547 | static GeometryTip* geometryTip = 0; |
2548 | |
2549 | void Client::positionGeometryTip() |
2550 | { |
2551 | assert(isMove() || isResize()); |
2552 | // Position and Size display |
2553 | if (effects && static_cast<EffectsHandlerImpl*>(effects)->provides(Effect::GeometryTip)) |
2554 | return; // some effect paints this for us |
2555 | if (options->showGeometryTip()) { |
2556 | if (!geometryTip) { |
2557 | geometryTip = new GeometryTip(&xSizeHint); |
2558 | } |
2559 | QRect wgeom(moveResizeGeom); // position of the frame, size of the window itself |
2560 | wgeom.setWidth(wgeom.width() - (width() - clientSize().width())); |
2561 | wgeom.setHeight(wgeom.height() - (height() - clientSize().height())); |
2562 | if (isShade()) |
2563 | wgeom.setHeight(0); |
2564 | geometryTip->setGeometry(wgeom); |
2565 | if (!geometryTip->isVisible()) |
2566 | geometryTip->show(); |
2567 | geometryTip->raise(); |
2568 | } |
2569 | } |
2570 | |
2571 | bool Client::startMoveResize() |
2572 | { |
2573 | assert(!moveResizeMode); |
2574 | assert(QWidget::keyboardGrabber() == NULL); |
2575 | assert(QWidget::mouseGrabber() == NULL); |
2576 | stopDelayedMoveResize(); |
2577 | if (QApplication::activePopupWidget() != NULL) |
2578 | return false; // popups have grab |
2579 | if (isFullScreen() && (screens()->count() < 2 || !isMovableAcrossScreens())) |
2580 | return false; |
2581 | bool has_grab = false; |
2582 | // This reportedly improves smoothness of the moveresize operation, |
2583 | // something with Enter/LeaveNotify events, looks like XFree performance problem or something *shrug* |
2584 | // (http://lists.kde.org/?t=107302193400001&r=1&w=2) |
2585 | QRect r = workspace()->clientArea(FullArea, this); |
2586 | m_moveResizeGrabWindow.create(r, XCB_WINDOW_CLASS_INPUT_ONLY, 0, NULL, rootWindow()); |
2587 | m_moveResizeGrabWindow.map(); |
2588 | m_moveResizeGrabWindow.raise(); |
2589 | const xcb_grab_pointer_cookie_t cookie = xcb_grab_pointer_unchecked(connection(), false, m_moveResizeGrabWindow, |
2590 | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION | |
2591 | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW, |
2592 | XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, m_moveResizeGrabWindow, Cursor::x11Cursor(m_cursor), xTime()); |
2593 | ScopedCPointer<xcb_grab_pointer_reply_t> pointerGrab(xcb_grab_pointer_reply(connection(), cookie, NULL)); |
2594 | if (!pointerGrab.isNull() && pointerGrab->status == XCB_GRAB_STATUS_SUCCESS) { |
2595 | has_grab = true; |
2596 | } |
2597 | if (grabXKeyboard(frameId())) |
2598 | has_grab = move_resize_has_keyboard_grab = true; |
2599 | if (!has_grab) { // at least one grab is necessary in order to be able to finish move/resize |
2600 | m_moveResizeGrabWindow.reset(); |
2601 | return false; |
2602 | } |
2603 | |
2604 | moveResizeMode = true; |
2605 | workspace()->setClientIsMoving(this); |
2606 | |
2607 | if (mode != PositionCenter) { // means "isResize()" but moveResizeMode = true is set below |
2608 | if (maximizeMode() == MaximizeFull) { // partial is cond. reset in finishMoveResize |
2609 | geom_restore = geometry(); // "restore" to current geometry |
2610 | setMaximize(false, false); |
2611 | } |
2612 | } |
2613 | |
2614 | if (quick_tile_mode != QuickTileNone && mode != PositionCenter) { // Cannot use isResize() yet |
2615 | // Exit quick tile mode when the user attempts to resize a tiled window |
2616 | quick_tile_mode = QuickTileNone; // Do so without restoring original geometry |
2617 | } |
2618 | |
2619 | s_haveResizeEffect = effects && static_cast<EffectsHandlerImpl*>(effects)->provides(Effect::Resize); |
2620 | moveResizeStartScreen = screen(); |
2621 | initialMoveResizeGeom = moveResizeGeom = geometry(); |
2622 | checkUnrestrictedMoveResize(); |
2623 | emit clientStartUserMovedResized(this); |
2624 | #ifdef KWIN_BUILD_SCREENEDGES |
2625 | if (ScreenEdges::self()->isDesktopSwitchingMovingClients()) |
2626 | ScreenEdges::self()->reserveDesktopSwitching(true, Qt::Vertical|Qt::Horizontal); |
2627 | #endif |
2628 | return true; |
2629 | } |
2630 | |
2631 | void Client::finishMoveResize(bool cancel) |
2632 | { |
2633 | const bool wasResize = isResize(); // store across leaveMoveResize |
2634 | leaveMoveResize(); |
2635 | |
2636 | if (cancel) |
2637 | setGeometry(initialMoveResizeGeom); |
2638 | else { |
2639 | if (wasResize) { |
2640 | const bool restoreH = maximizeMode() == MaximizeHorizontal && |
2641 | moveResizeGeom.width() != initialMoveResizeGeom.width(); |
2642 | const bool restoreV = maximizeMode() == MaximizeVertical && |
2643 | moveResizeGeom.height() != initialMoveResizeGeom.height(); |
2644 | if (restoreH || restoreV) { |
2645 | changeMaximize(restoreV, restoreH, false); |
2646 | } |
2647 | } |
2648 | setGeometry(moveResizeGeom); |
2649 | } |
2650 | checkScreen(); // needs to be done because clientFinishUserMovedResized has not yet re-activated online alignment |
2651 | if (screen() != moveResizeStartScreen) { |
2652 | workspace()->sendClientToScreen(this, screen()); // checks rule validity |
2653 | if (maximizeMode() != MaximizeRestore) |
2654 | checkWorkspacePosition(); |
2655 | } |
2656 | |
2657 | if (isElectricBorderMaximizing()) { |
2658 | setQuickTileMode(electricMode); |
2659 | electricMaximizing = false; |
2660 | outline()->hide(); |
2661 | elevate(false); |
2662 | } else if (!cancel) { |
2663 | if (!(maximizeMode() & MaximizeHorizontal)) { |
2664 | geom_restore.setX(geometry().x()); |
2665 | geom_restore.setWidth(geometry().width()); |
2666 | } |
2667 | if (!(maximizeMode() & MaximizeVertical)) { |
2668 | geom_restore.setY(geometry().y()); |
2669 | geom_restore.setHeight(geometry().height()); |
2670 | } |
2671 | } |
2672 | // FRAME update(); |
2673 | |
2674 | emit clientFinishUserMovedResized(this); |
2675 | } |
2676 | |
2677 | void Client::leaveMoveResize() |
2678 | { |
2679 | if (needsXWindowMove) { |
2680 | // Do the deferred move |
2681 | XMoveWindow(display(), frameId(), geom.x(), geom.y()); |
2682 | needsXWindowMove = false; |
2683 | } |
2684 | if (!isResize()) |
2685 | sendSyntheticConfigureNotify(); // tell the client about it's new final position |
2686 | if (geometryTip) { |
2687 | geometryTip->hide(); |
2688 | delete geometryTip; |
2689 | geometryTip = NULL; |
2690 | } |
2691 | if (move_resize_has_keyboard_grab) |
2692 | ungrabXKeyboard(); |
2693 | move_resize_has_keyboard_grab = false; |
2694 | XUngrabPointer(display(), xTime()); |
2695 | m_moveResizeGrabWindow.reset(); |
2696 | workspace()->setClientIsMoving(0); |
2697 | moveResizeMode = false; |
2698 | #ifdef HAVE_XSYNC |
2699 | if (syncRequest.counter == None) // don't forget to sanitize since the timeout will no more fire |
2700 | syncRequest.isPending = false; |
2701 | delete syncRequest.timeout; |
2702 | syncRequest.timeout = NULL; |
2703 | #endif |
2704 | #ifdef KWIN_BUILD_SCREENEDGES |
2705 | if (ScreenEdges::self()->isDesktopSwitchingMovingClients()) |
2706 | ScreenEdges::self()->reserveDesktopSwitching(false, Qt::Vertical|Qt::Horizontal); |
2707 | #endif |
2708 | } |
2709 | |
2710 | // This function checks if it actually makes sense to perform a restricted move/resize. |
2711 | // If e.g. the titlebar is already outside of the workarea, there's no point in performing |
2712 | // a restricted move resize, because then e.g. resize would also move the window (#74555). |
2713 | // NOTE: Most of it is duplicated from handleMoveResize(). |
2714 | void Client::checkUnrestrictedMoveResize() |
2715 | { |
2716 | if (unrestrictedMoveResize) |
2717 | return; |
2718 | QRect desktopArea = workspace()->clientArea(WorkArea, moveResizeGeom.center(), desktop()); |
2719 | int left_marge, right_marge, top_marge, bottom_marge, titlebar_marge; |
2720 | // restricted move/resize - keep at least part of the titlebar always visible |
2721 | // how much must remain visible when moved away in that direction |
2722 | left_marge = qMin(100 + border_right, moveResizeGeom.width()); |
2723 | right_marge = qMin(100 + border_left, moveResizeGeom.width()); |
2724 | // width/height change with opaque resizing, use the initial ones |
2725 | titlebar_marge = initialMoveResizeGeom.height(); |
2726 | top_marge = border_bottom; |
2727 | bottom_marge = border_top; |
2728 | if (isResize()) { |
2729 | if (moveResizeGeom.bottom() < desktopArea.top() + top_marge) |
2730 | unrestrictedMoveResize = true; |
2731 | if (moveResizeGeom.top() > desktopArea.bottom() - bottom_marge) |
2732 | unrestrictedMoveResize = true; |
2733 | if (moveResizeGeom.right() < desktopArea.left() + left_marge) |
2734 | unrestrictedMoveResize = true; |
2735 | if (moveResizeGeom.left() > desktopArea.right() - right_marge) |
2736 | unrestrictedMoveResize = true; |
2737 | if (!unrestrictedMoveResize && moveResizeGeom.top() < desktopArea.top()) // titlebar mustn't go out |
2738 | unrestrictedMoveResize = true; |
2739 | } |
2740 | if (isMove()) { |
2741 | if (moveResizeGeom.bottom() < desktopArea.top() + titlebar_marge - 1) |
2742 | unrestrictedMoveResize = true; |
2743 | // no need to check top_marge, titlebar_marge already handles it |
2744 | if (moveResizeGeom.top() > desktopArea.bottom() - bottom_marge + 1) // titlebar mustn't go out |
2745 | unrestrictedMoveResize = true; |
2746 | if (moveResizeGeom.right() < desktopArea.left() + left_marge) |
2747 | unrestrictedMoveResize = true; |
2748 | if (moveResizeGeom.left() > desktopArea.right() - right_marge) |
2749 | unrestrictedMoveResize = true; |
2750 | } |
2751 | } |
2752 | |
2753 | // When the user pressed mouse on the titlebar, don't activate move immediatelly, |
2754 | // since it may be just a click. Activate instead after a delay. Move used to be |
2755 | // activated only after moving by several pixels, but that looks bad. |
2756 | void Client::startDelayedMoveResize() |
2757 | { |
2758 | delete delayedMoveResizeTimer; |
2759 | delayedMoveResizeTimer = new QTimer(this); |
2760 | connect(delayedMoveResizeTimer, SIGNAL(timeout()), this, SLOT(delayedMoveResize())); |
2761 | delayedMoveResizeTimer->setSingleShot(true); |
2762 | delayedMoveResizeTimer->start(QApplication::startDragTime()); |
2763 | } |
2764 | |
2765 | void Client::stopDelayedMoveResize() |
2766 | { |
2767 | delete delayedMoveResizeTimer; |
2768 | delayedMoveResizeTimer = NULL; |
2769 | } |
2770 | |
2771 | void Client::delayedMoveResize() |
2772 | { |
2773 | assert(buttonDown); |
2774 | if (!startMoveResize()) |
2775 | buttonDown = false; |
2776 | updateCursor(); |
2777 | stopDelayedMoveResize(); |
2778 | } |
2779 | |
2780 | void Client::handleMoveResize(int x, int y, int x_root, int y_root) |
2781 | { |
2782 | #ifdef HAVE_XSYNC |
2783 | if (syncRequest.isPending && isResize()) |
2784 | return; // we're still waiting for the client or the timeout |
2785 | #endif |
2786 | |
2787 | if ((mode == PositionCenter && !isMovableAcrossScreens()) |
2788 | || (mode != PositionCenter && (isShade() || !isResizable()))) |
2789 | return; |
2790 | |
2791 | if (!moveResizeMode) { |
2792 | QPoint p(QPoint(x - padding_left, y - padding_top) - moveOffset); |
2793 | if (p.manhattanLength() >= KGlobalSettings::dndEventDelay()) { |
2794 | if (!startMoveResize()) { |
2795 | buttonDown = false; |
2796 | updateCursor(); |
2797 | return; |
2798 | } |
2799 | updateCursor(); |
2800 | } else |
2801 | return; |
2802 | } |
2803 | |
2804 | // ShadeHover or ShadeActive, ShadeNormal was already avoided above |
2805 | if (mode != PositionCenter && shade_mode != ShadeNone) |
2806 | setShade(ShadeNone); |
2807 | |
2808 | QPoint globalPos(x_root, y_root); |
2809 | // these two points limit the geometry rectangle, i.e. if bottomleft resizing is done, |
2810 | // the bottomleft corner should be at is at (topleft.x(), bottomright().y()) |
2811 | QPoint topleft = globalPos - moveOffset; |
2812 | QPoint bottomright = globalPos + invertedMoveOffset; |
2813 | QRect previousMoveResizeGeom = moveResizeGeom; |
2814 | |
2815 | // TODO move whole group when moving its leader or when the leader is not mapped? |
2816 | |
2817 | // When doing a restricted move we must always keep 100px of the titlebar |
2818 | // visible to allow the user to be able to move it again. |
2819 | const int frameTop = border_top; |
2820 | int titlebarArea = qMin(frameTop * 100, moveResizeGeom.width() * moveResizeGeom.height()); |
2821 | |
2822 | bool update = false; |
2823 | if (isResize()) { |
2824 | // first resize (without checking constrains), then snap, then check bounds, then check constrains |
2825 | QRect orig = initialMoveResizeGeom; |
2826 | Sizemode sizemode = SizemodeAny; |
2827 | switch(mode) { |
2828 | case PositionTopLeft: |
2829 | moveResizeGeom = QRect(topleft, orig.bottomRight()) ; |
2830 | break; |
2831 | case PositionBottomRight: |
2832 | moveResizeGeom = QRect(orig.topLeft(), bottomright) ; |
2833 | break; |
2834 | case PositionBottomLeft: |
2835 | moveResizeGeom = QRect(QPoint(topleft.x(), orig.y()), QPoint(orig.right(), bottomright.y())) ; |
2836 | break; |
2837 | case PositionTopRight: |
2838 | moveResizeGeom = QRect(QPoint(orig.x(), topleft.y()), QPoint(bottomright.x(), orig.bottom())) ; |
2839 | break; |
2840 | case PositionTop: |
2841 | moveResizeGeom = QRect(QPoint(orig.left(), topleft.y()), orig.bottomRight()) ; |
2842 | sizemode = SizemodeFixedH; // try not to affect height |
2843 | break; |
2844 | case PositionBottom: |
2845 | moveResizeGeom = QRect(orig.topLeft(), QPoint(orig.right(), bottomright.y())) ; |
2846 | sizemode = SizemodeFixedH; |
2847 | break; |
2848 | case PositionLeft: |
2849 | moveResizeGeom = QRect(QPoint(topleft.x(), orig.top()), orig.bottomRight()) ; |
2850 | sizemode = SizemodeFixedW; |
2851 | break; |
2852 | case PositionRight: |
2853 | moveResizeGeom = QRect(orig.topLeft(), QPoint(bottomright.x(), orig.bottom())) ; |
2854 | sizemode = SizemodeFixedW; |
2855 | break; |
2856 | case PositionCenter: |
2857 | default: |
2858 | abort(); |
2859 | break; |
2860 | } |
2861 | // adjust new size to snap to other windows/borders |
2862 | moveResizeGeom = workspace()->adjustClientSize(this, moveResizeGeom, mode); |
2863 | |
2864 | if (!unrestrictedMoveResize) { |
2865 | // Make sure the titlebar isn't behind a restricted area. We don't need to restrict |
2866 | // the other directions. If not visible enough, move the window to the closest valid |
2867 | // point. We bruteforce this by slowly moving the window back to its previous position. |
2868 | for (;;) { |
2869 | QRegion titlebarRegion(moveResizeGeom.left(), moveResizeGeom.top(), |
2870 | moveResizeGeom.width(), frameTop); |
2871 | titlebarRegion &= workspace()->clientArea(FullArea, -1, 0); // On the screen |
2872 | titlebarRegion -= workspace()->restrictedMoveArea(desktop()); // Strut areas |
2873 | // Now we have a region of all the visible areas of the titlebar |
2874 | // Count the visible pixels and check to see if it's enough |
2875 | int visiblePixels = 0; |
2876 | foreach (const QRect & rect, titlebarRegion.rects()) |
2877 | if (rect.height() >= frameTop) // Only the full height regions, prevents long slim areas |
2878 | visiblePixels += rect.width() * rect.height(); |
2879 | if (visiblePixels >= titlebarArea) |
2880 | break; // We have reached a valid position |
2881 | |
2882 | // Not visible enough, move the window to the closest valid point. We bruteforce |
2883 | // this by slowly moving the window back to its previous position. |
2884 | if (previousMoveResizeGeom.y() != moveResizeGeom.y()) { |
2885 | if (previousMoveResizeGeom.y() > moveResizeGeom.y()) |
2886 | moveResizeGeom.setTop(moveResizeGeom.y() + 1); |
2887 | else |
2888 | moveResizeGeom.setTop(moveResizeGeom.y() - 1); |
2889 | } else { // Our heights match but we still don't have a valid area, maybe |
2890 | // we are trying to resize in from the side? |
2891 | bool breakLoop = false; |
2892 | switch(mode) { |
2893 | case PositionBottomLeft: |
2894 | case PositionTopLeft: |
2895 | case PositionLeft: |
2896 | if (previousMoveResizeGeom.x() >= moveResizeGeom.x()) { |
2897 | breakLoop = true; |
2898 | break; |
2899 | } |
2900 | moveResizeGeom.setLeft(moveResizeGeom.x() - 1); |
2901 | break; |
2902 | case PositionBottomRight: |
2903 | case PositionTopRight: |
2904 | case PositionRight: |
2905 | if (previousMoveResizeGeom.right() <= moveResizeGeom.right()) { |
2906 | breakLoop = true; |
2907 | break; |
2908 | } |
2909 | moveResizeGeom.setRight(moveResizeGeom.x() + moveResizeGeom.width()); |
2910 | break; |
2911 | default: |
2912 | breakLoop = true; |
2913 | } |
2914 | if (breakLoop) |
2915 | break; |
2916 | } |
2917 | } |
2918 | } |
2919 | |
2920 | // Always obey size hints, even when in "unrestricted" mode |
2921 | QSize size = adjustedSize(moveResizeGeom.size(), sizemode); |
2922 | // the new topleft and bottomright corners (after checking size constrains), if they'll be needed |
2923 | topleft = QPoint(moveResizeGeom.right() - size.width() + 1, moveResizeGeom.bottom() - size.height() + 1); |
2924 | bottomright = QPoint(moveResizeGeom.left() + size.width() - 1, moveResizeGeom.top() + size.height() - 1); |
2925 | orig = moveResizeGeom; |
2926 | switch(mode) { |
2927 | // these 4 corners ones are copied from above |
2928 | case PositionTopLeft: |
2929 | moveResizeGeom = QRect(topleft, orig.bottomRight()) ; |
2930 | break; |
2931 | case PositionBottomRight: |
2932 | moveResizeGeom = QRect(orig.topLeft(), bottomright) ; |
2933 | break; |
2934 | case PositionBottomLeft: |
2935 | moveResizeGeom = QRect(QPoint(topleft.x(), orig.y()), QPoint(orig.right(), bottomright.y())) ; |
2936 | break; |
2937 | case PositionTopRight: |
2938 | moveResizeGeom = QRect(QPoint(orig.x(), topleft.y()), QPoint(bottomright.x(), orig.bottom())) ; |
2939 | break; |
2940 | // The side ones can't be copied exactly - if aspect ratios are specified, both dimensions may change. |
2941 | // Therefore grow to the right/bottom if needed. |
2942 | // TODO it should probably obey gravity rather than always using right/bottom ? |
2943 | case PositionTop: |
2944 | moveResizeGeom = QRect(QPoint(orig.left(), topleft.y()), QPoint(bottomright.x(), orig.bottom())) ; |
2945 | break; |
2946 | case PositionBottom: |
2947 | moveResizeGeom = QRect(orig.topLeft(), QPoint(bottomright.x(), bottomright.y())) ; |
2948 | break; |
2949 | case PositionLeft: |
2950 | moveResizeGeom = QRect(QPoint(topleft.x(), orig.top()), QPoint(orig.right(), bottomright.y())); |
2951 | break; |
2952 | case PositionRight: |
2953 | moveResizeGeom = QRect(orig.topLeft(), QPoint(bottomright.x(), bottomright.y())) ; |
2954 | break; |
2955 | case PositionCenter: |
2956 | default: |
2957 | abort(); |
2958 | break; |
2959 | } |
2960 | |
2961 | if (moveResizeGeom.size() != previousMoveResizeGeom.size()) |
2962 | update = true; |
2963 | } else if (isMove()) { |
2964 | assert(mode == PositionCenter); |
2965 | if (!isMovable()) { // isMovableAcrossScreens() must have been true to get here |
2966 | // Special moving of maximized windows on Xinerama screens |
2967 | int screen = screens()->number(globalPos); |
2968 | if (isFullScreen()) |
2969 | moveResizeGeom = workspace()->clientArea(FullScreenArea, screen, 0); |
2970 | else { |
2971 | moveResizeGeom = workspace()->clientArea(MaximizeArea, screen, 0); |
2972 | QSize adjSize = adjustedSize(moveResizeGeom.size(), SizemodeMax); |
2973 | if (adjSize != moveResizeGeom.size()) { |
2974 | QRect r(moveResizeGeom); |
2975 | moveResizeGeom.setSize(adjSize); |
2976 | moveResizeGeom.moveCenter(r.center()); |
2977 | } |
2978 | } |
2979 | } else { |
2980 | // first move, then snap, then check bounds |
2981 | moveResizeGeom.moveTopLeft(topleft); |
2982 | moveResizeGeom.moveTopLeft(workspace()->adjustClientPosition(this, moveResizeGeom.topLeft(), |
2983 | unrestrictedMoveResize)); |
2984 | |
2985 | if (!unrestrictedMoveResize) { |
2986 | // Make sure the titlebar isn't behind a restricted area. |
2987 | const QRegion fullArea = workspace()->clientArea(FullArea, this); // On the screen |
2988 | const QRegion strut = workspace()->restrictedMoveArea(desktop()); // Strut areas |
2989 | for (;;) { |
2990 | QRegion titlebarRegion(moveResizeGeom.left(), moveResizeGeom.top(), |
2991 | moveResizeGeom.width(), frameTop); |
2992 | titlebarRegion &= fullArea; |
2993 | titlebarRegion -= strut; // Strut areas |
2994 | // Now we have a region of all the visible areas of the titlebar |
2995 | // Count the visible pixels and check to see if it's enough |
2996 | int visiblePixels = 0; |
2997 | foreach (const QRect & rect, titlebarRegion.rects()) |
2998 | if (rect.height() >= frameTop) // Only the full height regions, prevents long slim areas |
2999 | visiblePixels += rect.width() * rect.height(); |
3000 | if (visiblePixels >= titlebarArea) |
3001 | break; // We have reached a valid position |
3002 | |
3003 | // (esp.) if there're more screens with different struts (panels) it the titlebar |
3004 | // will be movable outside the movearea (covering one of the panels) until it |
3005 | // crosses the panel "too much" (not enough visiblePixels) and then stucks because |
3006 | // it's usually only pushed by 1px to either direction |
3007 | // so we first check whether we intersect suc strut and move the window below it |
3008 | // immediately (it's still possible to hit the visiblePixels >= titlebarArea break |
3009 | // by moving the window slightly downwards, but it won't stuck) |
3010 | // see bug #274466 |
3011 | // and bug #301805 for why we can't just match the titlearea against the screen |
3012 | if (screens()->count() > 1) { // optimization |
3013 | // TODO: could be useful on partial screen struts (half-width panels etc.) |
3014 | int newTitleTop = -1; |
3015 | foreach (const QRect &r, strut.rects()) { |
3016 | if (r.top() == 0 && r.width() > r.height() && // "top panel" |
3017 | r.intersects(moveResizeGeom) && moveResizeGeom.top() < r.bottom()) { |
3018 | newTitleTop = r.bottom() + 1; |
3019 | break; |
3020 | } |
3021 | } |
3022 | if (newTitleTop > -1) { |
3023 | moveResizeGeom.moveTop(newTitleTop); // invalid position, possibly on screen change |
3024 | break; |
3025 | } |
3026 | } |
3027 | |
3028 | int dx = sign(previousMoveResizeGeom.x() - moveResizeGeom.x()), |
3029 | dy = sign(previousMoveResizeGeom.y() - moveResizeGeom.y()); |
3030 | if (visiblePixels && dx) // means there's no full width cap -> favor horizontally |
3031 | dy = 0; |
3032 | else if (dy) |
3033 | dx = 0; |
3034 | |
3035 | // Move it back |
3036 | moveResizeGeom.translate(dx, dy); |
3037 | |
3038 | if (moveResizeGeom == previousMoveResizeGeom) { |
3039 | break; // Prevent lockup |
3040 | } |
3041 | } |
3042 | } |
3043 | } |
3044 | if (moveResizeGeom.topLeft() != previousMoveResizeGeom.topLeft()) |
3045 | update = true; |
3046 | } else |
3047 | abort(); |
3048 | |
3049 | if (!update) |
3050 | return; |
3051 | |
3052 | #ifdef HAVE_XSYNC |
3053 | if (isResize() && !s_haveResizeEffect) { |
3054 | if (!syncRequest.timeout) { |
3055 | syncRequest.timeout = new QTimer(this); |
3056 | connect(syncRequest.timeout, SIGNAL(timeout()), SLOT(performMoveResize())); |
3057 | syncRequest.timeout->setSingleShot(true); |
3058 | } |
3059 | if (syncRequest.counter != None) { |
3060 | syncRequest.timeout->start(250); |
3061 | sendSyncRequest(); |
3062 | } else { // for clients not supporting the XSYNC protocol, we |
3063 | syncRequest.isPending = true; // limit the resizes to 30Hz to take pointless load from X11 |
3064 | syncRequest.timeout->start(33); // and the client, the mouse is still moved at full speed |
3065 | } // and no human can control faster resizes anyway |
3066 | XMoveResizeWindow(display(), window(), 0, 0, moveResizeGeom.width() - (border_left + border_right), moveResizeGeom.height() - (border_top + border_bottom)); |
3067 | } else |
3068 | #endif |
3069 | performMoveResize(); |
3070 | |
3071 | #ifdef KWIN_BUILD_SCREENEDGES |
3072 | if (isMove()) { |
3073 | ScreenEdges::self()->check(globalPos, QDateTime::fromMSecsSinceEpoch(xTime())); |
3074 | } |
3075 | #endif |
3076 | } |
3077 | |
3078 | void Client::performMoveResize() |
3079 | { |
3080 | if (isMove() || (isResize() && !s_haveResizeEffect)) { |
3081 | setGeometry(moveResizeGeom); |
3082 | } |
3083 | #ifdef HAVE_XSYNC |
3084 | if (syncRequest.counter == None) // client w/o XSYNC support. allow the next resize event |
3085 | syncRequest.isPending = false; // NEVER do this for clients with a valid counter |
3086 | // (leads to sync request races in some clients) |
3087 | if (isResize()) |
3088 | addRepaintFull(); |
3089 | #endif |
3090 | positionGeometryTip(); |
3091 | emit clientStepUserMovedResized(this, moveResizeGeom); |
3092 | } |
3093 | |
3094 | void Client::setElectricBorderMode(QuickTileMode mode) |
3095 | { |
3096 | if (mode != QuickTileMaximize) { |
3097 | // sanitize the mode, ie. simplify "invalid" combinations |
3098 | if ((mode & QuickTileHorizontal) == QuickTileHorizontal) |
3099 | mode &= ~QuickTileHorizontal; |
3100 | if ((mode & QuickTileVertical) == QuickTileVertical) |
3101 | mode &= ~QuickTileVertical; |
3102 | } |
3103 | electricMode = mode; |
3104 | } |
3105 | |
3106 | Client::QuickTileMode Client::electricBorderMode() const |
3107 | { |
3108 | return electricMode; |
3109 | } |
3110 | |
3111 | bool Client::isElectricBorderMaximizing() const |
3112 | { |
3113 | return electricMaximizing; |
3114 | } |
3115 | |
3116 | void Client::setElectricBorderMaximizing(bool maximizing) |
3117 | { |
3118 | electricMaximizing = maximizing; |
3119 | if (maximizing) |
3120 | outline()->show(electricBorderMaximizeGeometry(cursorPos(), desktop())); |
3121 | else |
3122 | outline()->hide(); |
3123 | elevate(maximizing); |
3124 | } |
3125 | |
3126 | QRect Client::electricBorderMaximizeGeometry(QPoint pos, int desktop) |
3127 | { |
3128 | if (electricMode == QuickTileMaximize) { |
3129 | if (maximizeMode() == MaximizeFull) |
3130 | return geometryRestore(); |
3131 | else |
3132 | return workspace()->clientArea(MaximizeArea, pos, desktop); |
3133 | } |
3134 | |
3135 | QRect ret = workspace()->clientArea(MaximizeArea, pos, desktop); |
3136 | if (electricMode & QuickTileLeft) |
3137 | ret.setRight(ret.left()+ret.width()/2 - 1); |
3138 | else if (electricMode & QuickTileRight) |
3139 | ret.setLeft(ret.right()-(ret.width()-ret.width()/2) + 1); |
3140 | if (electricMode & QuickTileTop) |
3141 | ret.setBottom(ret.top()+ret.height()/2 - 1); |
3142 | else if (electricMode & QuickTileBottom) |
3143 | ret.setTop(ret.bottom()-(ret.height()-ret.height()/2) + 1); |
3144 | |
3145 | return ret; |
3146 | } |
3147 | |
3148 | void Client::setQuickTileMode(QuickTileMode mode, bool keyboard) |
3149 | { |
3150 | // Only allow quick tile on a regular or maximized window |
3151 | if (!isResizable() && maximizeMode() != MaximizeFull) |
3152 | return; |
3153 | |
3154 | GeometryUpdatesBlocker blocker(this); |
3155 | |
3156 | if (mode == QuickTileMaximize) { |
3157 | TabSynchronizer syncer(this, TabGroup::QuickTile|TabGroup::Geometry|TabGroup::Maximized); |
3158 | quick_tile_mode = QuickTileNone; |
3159 | if (maximizeMode() == MaximizeFull) { |
3160 | setMaximize(false, false); |
3161 | } else { |
3162 | setMaximize(true, true); |
3163 | QRect clientArea = workspace()->clientArea(MaximizeArea, this); |
3164 | if (geometry().top() != clientArea.top()) { |
3165 | QRect r(geometry()); |
3166 | r.moveTop(clientArea.top()); |
3167 | setGeometry(r); |
3168 | } |
3169 | quick_tile_mode = QuickTileMaximize; |
3170 | } |
3171 | return; |
3172 | } |
3173 | |
3174 | // sanitize the mode, ie. simplify "invalid" combinations |
3175 | if ((mode & QuickTileHorizontal) == QuickTileHorizontal) |
3176 | mode &= ~QuickTileHorizontal; |
3177 | if ((mode & QuickTileVertical) == QuickTileVertical) |
3178 | mode &= ~QuickTileVertical; |
3179 | |
3180 | setElectricBorderMode(mode); // used by ::electricBorderMaximizeGeometry(.) |
3181 | |
3182 | // restore from maximized so that it is possible to tile maximized windows with one hit or by dragging |
3183 | if (maximizeMode() == MaximizeFull) { |
3184 | |
3185 | TabSynchronizer syncer(this, TabGroup::QuickTile|TabGroup::Geometry|TabGroup::Maximized); |
3186 | |
3187 | setMaximize(false, false); |
3188 | |
3189 | if (mode != QuickTileNone) { |
3190 | quick_tile_mode = mode; |
3191 | // decorations may turn off some borders when tiled |
3192 | const ForceGeometry_t geom_mode = decoration && checkBorderSizes(false) ? ForceGeometrySet : NormalGeometrySet; |
3193 | quick_tile_mode = QuickTileNone; // Temporary, so the maximize code doesn't get all confused |
3194 | setGeometry(electricBorderMaximizeGeometry(keyboard ? geometry().center() : cursorPos(), desktop()), geom_mode); |
3195 | } |
3196 | // Store the mode change |
3197 | quick_tile_mode = mode; |
3198 | |
3199 | return; |
3200 | } |
3201 | |
3202 | if (mode != QuickTileNone) { |
3203 | TabSynchronizer syncer(this, TabGroup::QuickTile|TabGroup::Geometry); |
3204 | |
3205 | QPoint whichScreen = keyboard ? geometry().center() : cursorPos(); |
3206 | |
3207 | // If trying to tile to the side that the window is already tiled to move the window to the next |
3208 | // screen if it exists, otherwise toggle the mode (set QuickTileNone) |
3209 | if (quick_tile_mode == mode) { |
3210 | const int numScreens = screens()->count(); |
3211 | const int curScreen = screen(); |
3212 | int nextScreen = curScreen; |
3213 | QVarLengthArray<QRect> screens(numScreens); |
3214 | for (int i = 0; i < numScreens; ++i) // Cache |
3215 | screens[i] = Screens::self()->geometry(i); |
3216 | for (int i = 0; i < numScreens; ++i) { |
3217 | |
3218 | if (i == curScreen) |
3219 | continue; |
3220 | |
3221 | if (screens[i].bottom() <= screens[curScreen].top() || screens[i].top() >= screens[curScreen].bottom()) |
3222 | continue; // not in horizontal line |
3223 | |
3224 | const int x = screens[i].center().x(); |
3225 | if ((mode & QuickTileHorizontal) == QuickTileLeft) { |
3226 | if (x >= screens[curScreen].center().x() || (curScreen != nextScreen && x <= screens[nextScreen].center().x())) |
3227 | continue; // not left of current or more left then found next |
3228 | } else if ((mode & QuickTileHorizontal) == QuickTileRight) { |
3229 | if (x <= screens[curScreen].center().x() || (curScreen != nextScreen && x >= screens[nextScreen].center().x())) |
3230 | continue; // not right of current or more right then found next |
3231 | } |
3232 | |
3233 | nextScreen = i; |
3234 | } |
3235 | |
3236 | if (nextScreen == curScreen) { |
3237 | mode = QuickTileNone; // No other screens, toggle tiling |
3238 | } else { |
3239 | // Move to other screen |
3240 | geom_restore.translate(screens[nextScreen].topLeft() - screens[curScreen].topLeft()); |
3241 | whichScreen = screens[nextScreen].center(); |
3242 | |
3243 | // Swap sides |
3244 | mode = ~mode & QuickTileHorizontal; |
3245 | } |
3246 | setElectricBorderMode(mode); // used by ::electricBorderMaximizeGeometry(.) |
3247 | } else if (quick_tile_mode == QuickTileNone) { |
3248 | // Not coming out of an existing tile, not shifting monitors, we're setting a brand new tile. |
3249 | // Store geometry first, so we can go out of this tile later. |
3250 | geom_restore = geometry(); |
3251 | } |
3252 | |
3253 | if (mode != QuickTileNone) { |
3254 | quick_tile_mode = mode; |
3255 | // decorations may turn off some borders when tiled |
3256 | const ForceGeometry_t geom_mode = decoration && checkBorderSizes(false) ? ForceGeometrySet : NormalGeometrySet; |
3257 | // Temporary, so the maximize code doesn't get all confused |
3258 | quick_tile_mode = QuickTileNone; |
3259 | setGeometry(electricBorderMaximizeGeometry(whichScreen, desktop()), geom_mode); |
3260 | } |
3261 | |
3262 | // Store the mode change |
3263 | quick_tile_mode = mode; |
3264 | } |
3265 | |
3266 | if (mode == QuickTileNone) { |
3267 | TabSynchronizer syncer(this, TabGroup::QuickTile|TabGroup::Geometry); |
3268 | |
3269 | quick_tile_mode = QuickTileNone; |
3270 | // Untiling, so just restore geometry, and we're done. |
3271 | if (!geom_restore.isValid()) // invalid if we started maximized and wait for placement |
3272 | geom_restore = geometry(); |
3273 | // decorations may turn off some borders when tiled |
3274 | const ForceGeometry_t geom_mode = decoration && checkBorderSizes(false) ? ForceGeometrySet : NormalGeometrySet; |
3275 | setGeometry(geom_restore, geom_mode); |
3276 | checkWorkspacePosition(); // Just in case it's a different screen |
3277 | } |
3278 | } |
3279 | |
3280 | void Client::sendToScreen(int newScreen) |
3281 | { |
3282 | newScreen = rules()->checkScreen(newScreen); |
3283 | if (isActive()) { |
3284 | screens()->setCurrent(newScreen); |
3285 | // might impact the layer of a fullscreen window |
3286 | foreach (Client *cc, workspace()->clientList()) { |
3287 | if (cc->isFullScreen() && cc->screen() == newScreen) { |
3288 | cc->updateLayer(); |
3289 | } |
3290 | } |
3291 | } |
3292 | if (screen() == newScreen) // Don't use isOnScreen(), that's true even when only partially |
3293 | return; |
3294 | |
3295 | GeometryUpdatesBlocker blocker(this); |
3296 | |
3297 | // operating on the maximized / quicktiled window would leave the old geom_restore behind, |
3298 | // so we clear the state first |
3299 | MaximizeMode maxMode = maximizeMode(); |
3300 | QuickTileMode qtMode = (QuickTileMode)quick_tile_mode; |
3301 | if (maxMode != MaximizeRestore) |
3302 | maximize(MaximizeRestore); |
3303 | if (qtMode != QuickTileNone) |
3304 | setQuickTileMode(QuickTileNone, true); |
3305 | |
3306 | QRect oldScreenArea = workspace()->clientArea(MaximizeArea, this); |
3307 | QRect screenArea = workspace()->clientArea(MaximizeArea, newScreen, desktop()); |
3308 | |
3309 | // the window can have its center so that the position correction moves the new center onto |
3310 | // the old screen, what will tile it where it is. Ie. the screen is not changed |
3311 | // this happens esp. with electric border quicktiling |
3312 | if (qtMode != QuickTileNone) |
3313 | keepInArea(oldScreenArea); |
3314 | |
3315 | QRect oldGeom = geometry(); |
3316 | QRect newGeom = oldGeom; |
3317 | // move the window to have the same relative position to the center of the screen |
3318 | // (i.e. one near the middle of the right edge will also end up near the middle of the right edge) |
3319 | QPoint center = newGeom.center() - oldScreenArea.center(); |
3320 | center.setX(center.x() * screenArea.width() / oldScreenArea.width()); |
3321 | center.setY(center.y() * screenArea.height() / oldScreenArea.height()); |
3322 | center += screenArea.center(); |
3323 | newGeom.moveCenter(center); |
3324 | setGeometry(newGeom); |
3325 | // align geom_restore - checkWorkspacePosition operates on it |
3326 | geom_restore = newGeom; |
3327 | |
3328 | // If the window was inside the old screen area, explicitly make sure its inside also the new screen area. |
3329 | // Calling checkWorkspacePosition() should ensure that, but when moving to a small screen the window could |
3330 | // be big enough to overlap outside of the new screen area, making struts from other screens come into effect, |
3331 | // which could alter the resulting geometry. |
3332 | if (oldScreenArea.contains(oldGeom)) |
3333 | keepInArea(screenArea); |
3334 | checkWorkspacePosition(oldGeom); |
3335 | |
3336 | // re-align geom_restore to contrained geometry |
3337 | geom_restore = geometry(); |
3338 | |
3339 | // finally reset special states |
3340 | // NOTICE that MaximizeRestore/QuickTileNone checks are required. |
3341 | // eg. setting QuickTileNone would break maximization |
3342 | if (maxMode != MaximizeRestore) |
3343 | maximize(maxMode); |
3344 | if (qtMode != QuickTileNone && qtMode != quick_tile_mode) |
3345 | setQuickTileMode(qtMode, true); |
3346 | |
3347 | ClientList tso = workspace()->ensureStackingOrder(transients()); |
3348 | for (ClientList::ConstIterator it = tso.constBegin(), end = tso.constEnd(); it != end; ++it) |
3349 | (*it)->sendToScreen(newScreen); |
3350 | } |
3351 | |
3352 | } // namespace |
3353 | |