1/********************************************************************
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5Copyright (C) 1999, 2000 Matthias Ettrich <ettrich@kde.org>
6Copyright (C) 2003 Lubos Lunak <l.lunak@kde.org>
7Copyright (C) 2009 Lucas Murray <lmurray@undefinedfire.com>
8
9This program is free software; you can redistribute it and/or modify
10it under the terms of the GNU General Public License as published by
11the Free Software Foundation; either version 2 of the License, or
12(at your option) any later version.
13
14This program is distributed in the hope that it will be useful,
15but WITHOUT ANY WARRANTY; without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17GNU General Public License for more details.
18
19You should have received a copy of the GNU General Public License
20along 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
56namespace KWin
57{
58
59static inline int sign(int v) {
60 return (v > 0) - (v < 0);
61}
62
63//********************************************
64// Workspace
65//********************************************
66
67extern int screen_number;
68extern bool is_multihead;
69
70/*!
71 Resizes the workspace after an XRANDR screen size change
72 */
73void 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
102void 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
124void 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
256void 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
270QRect 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
321QRect Workspace::clientArea(clientAreaOption opt, const QPoint& p, int desktop) const
322{
323 return clientArea(opt, screens()->number(p), desktop);
324}
325
326QRect Workspace::clientArea(clientAreaOption opt, const Client* c) const
327{
328 return clientArea(opt, c->geometry().center(), c->desktop());
329}
330
331QRegion 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
342bool Workspace::inUpdateClientArea() const
343{
344 return !oldrestrictedmovearea.isEmpty();
345}
346
347QRegion 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
358QVector< QRect > Workspace::previousScreenSizes() const
359{
360 return oldscreensizes;
361}
362
363int Workspace::oldDisplayWidth() const
364{
365 return olddisplaysize.width();
366}
367
368int 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 */
382QPoint 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
565QRect 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 */
808void 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).
822void 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
841void 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
877QRect 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
943NETExtendedStrut 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
974StrutRect 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
1013StrutRects 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
1023bool 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
1031bool 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
1048void 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
1229void 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 */
1244QSize 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()
1257QSize 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 */
1270QSize 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 */
1442void 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
1515QSize Client::minSize() const
1516{
1517 return rules()->checkMinSize(QSize(xSizeHint.min_width, xSizeHint.min_height));
1518}
1519
1520QSize Client::maxSize() const
1521{
1522 return rules()->checkMaxSize(QSize(xSizeHint.max_width, xSizeHint.max_height));
1523}
1524
1525QSize 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 */
1535void 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
1552const 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
1617void 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
1744void 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
1804void 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 */
1823bool 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 */
1837bool 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 */
1851bool 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 */
1871bool 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 */
1889void 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
1993void 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 */
2061void 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
2104void 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
2123void 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 */
2131void 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
2147class TabSynchronizer
2148{
2149public:
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 }
2168private:
2169 Client *m_client;
2170 TabGroup::States m_states;
2171};
2172
2173
2174static bool changeMaximizeRecursion = false;
2175void 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
2387bool 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
2402bool 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
2411void 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
2465void 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 */
2491QRect 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
2508int 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
2522void 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
2547static GeometryTip* geometryTip = 0;
2548
2549void 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
2571bool 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
2631void 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
2677void 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().
2714void 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.
2756void 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
2765void Client::stopDelayedMoveResize()
2766{
2767 delete delayedMoveResizeTimer;
2768 delayedMoveResizeTimer = NULL;
2769}
2770
2771void Client::delayedMoveResize()
2772{
2773 assert(buttonDown);
2774 if (!startMoveResize())
2775 buttonDown = false;
2776 updateCursor();
2777 stopDelayedMoveResize();
2778}
2779
2780void 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
3078void 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
3094void 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
3106Client::QuickTileMode Client::electricBorderMode() const
3107{
3108 return electricMode;
3109}
3110
3111bool Client::isElectricBorderMaximizing() const
3112{
3113 return electricMaximizing;
3114}
3115
3116void 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
3126QRect 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
3148void 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
3280void 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