diff --git a/geometry.cpp b/geometry.cpp index 3dbc2f1ab..d4c94510f 100644 --- a/geometry.cpp +++ b/geometry.cpp @@ -1,3225 +1,3228 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak Copyright (C) 2009 Lucas Murray This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ /* This file contains things relevant to geometry, i.e. workspace size, window positions and window sizes. */ #include "client.h" #include "composite.h" #include "workspace.h" #include #include #include #include "placement.h" #include "notifications.h" #include "geometrytip.h" #include "rules.h" #include "effects.h" #include #include #include #include #include #include "outline.h" namespace KWin { //******************************************** // Workspace //******************************************** extern int screen_number; extern bool is_multihead; /*! Resizes the workspace after an XRANDR screen size change */ void Workspace::desktopResized() { QRect geom; for (int i = 0; i < QApplication::desktop()->screenCount(); i++) { //do NOT use - QApplication::desktop()->screenGeometry(i) there could be a virtual geometry // see bug #302783 geom |= QApplication::desktop()->screen(i)->geometry(); } NETSize desktop_geometry; desktop_geometry.width = geom.width(); desktop_geometry.height = geom.height(); rootInfo->setDesktopGeometry(-1, desktop_geometry); updateClientArea(); saveOldScreenSizes(); // after updateClientArea(), so that one still uses the previous one #ifdef KWIN_BUILD_SCREENEDGES m_screenEdge.update(true); #endif if (effects) { static_cast(effects)->desktopResized(geom.size()); } } void Workspace::saveOldScreenSizes() { olddisplaysize = QSize( displayWidth(), displayHeight()); oldscreensizes.clear(); for( int i = 0; i < numScreens(); ++i ) oldscreensizes.append( screenGeometry( i )); } /*! Updates the current client areas according to the current clients. If the area changes or force is true, the new areas are propagated to the world. The client area is the area that is available for clients (that which is not taken by windows like panels, the top-of-screen menu etc). \sa clientArea() */ void Workspace::updateClientArea(bool force) { int nscreens = QApplication::desktop()->screenCount(); kDebug(1212) << "screens: " << nscreens << "desktops: " << numberOfDesktops(); QVector< QRect > new_wareas(numberOfDesktops() + 1); QVector< StrutRects > new_rmoveareas(numberOfDesktops() + 1); QVector< QVector< QRect > > new_sareas(numberOfDesktops() + 1); QVector< QRect > screens(nscreens); QRect desktopArea; for (int i = 0; i < QApplication::desktop()->screenCount(); i++) { desktopArea |= QApplication::desktop()->screenGeometry(i); } for (int iS = 0; iS < nscreens; iS ++) { screens [iS] = QApplication::desktop()->screenGeometry(iS); } for (int i = 1; i <= numberOfDesktops(); ++i) { new_wareas[ i ] = desktopArea; new_sareas[ i ].resize(nscreens); for (int iS = 0; iS < nscreens; iS ++) new_sareas[ i ][ iS ] = screens[ iS ]; } for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) { if (!(*it)->hasStrut()) continue; QRect r = (*it)->adjustedClientArea(desktopArea, desktopArea); StrutRects strutRegion = (*it)->strutRects(); // Ignore offscreen xinerama struts. These interfere with the larger monitors on the setup // and should be ignored so that applications that use the work area to work out where // windows can go can use the entire visible area of the larger monitors. // This goes against the EWMH description of the work area but it is a toss up between // having unusable sections of the screen (Which can be quite large with newer monitors) // or having some content appear offscreen (Relatively rare compared to other). bool hasOffscreenXineramaStrut = (*it)->hasOffscreenXineramaStrut(); if ((*it)->isOnAllDesktops()) { for (int i = 1; i <= numberOfDesktops(); ++i) { if (!hasOffscreenXineramaStrut) new_wareas[ i ] = new_wareas[ i ].intersected(r); new_rmoveareas[ i ] += strutRegion; for (int iS = 0; iS < nscreens; iS ++) { new_sareas[ i ][ iS ] = new_sareas[ i ][ iS ].intersected( (*it)->adjustedClientArea(desktopArea, screens[ iS ])); } } } else { if (!hasOffscreenXineramaStrut) new_wareas[(*it)->desktop()] = new_wareas[(*it)->desktop()].intersected(r); new_rmoveareas[(*it)->desktop()] += strutRegion; for (int iS = 0; iS < nscreens; iS ++) { // kDebug (1212) << "adjusting new_sarea: " << screens[ iS ]; new_sareas[(*it)->desktop()][ iS ] = new_sareas[(*it)->desktop()][ iS ].intersected( (*it)->adjustedClientArea(desktopArea, screens[ iS ])); } } } #if 0 for (int i = 1; i <= numberOfDesktops(); ++i) { for (int iS = 0; iS < nscreens; iS ++) kDebug(1212) << "new_sarea: " << new_sareas[ i ][ iS ]; } #endif bool changed = force; if (screenarea.isEmpty()) changed = true; for (int i = 1; !changed && i <= numberOfDesktops(); ++i) { if (workarea[ i ] != new_wareas[ i ]) changed = true; if (restrictedmovearea[ i ] != new_rmoveareas[ i ]) changed = true; if (screenarea[ i ].size() != new_sareas[ i ].size()) changed = true; for (int iS = 0; !changed && iS < nscreens; iS ++) if (new_sareas[ i ][ iS ] != screenarea [ i ][ iS ]) changed = true; } if (changed) { workarea = new_wareas; oldrestrictedmovearea = restrictedmovearea; restrictedmovearea = new_rmoveareas; screenarea = new_sareas; NETRect r; for (int i = 1; i <= numberOfDesktops(); i++) { r.pos.x = workarea[ i ].x(); r.pos.y = workarea[ i ].y(); r.size.width = workarea[ i ].width(); r.size.height = workarea[ i ].height(); rootInfo->setWorkArea(i, r); } for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) (*it)->checkWorkspacePosition(); for (ClientList::ConstIterator it = desktops.constBegin(); it != desktops.constEnd(); ++it) (*it)->checkWorkspacePosition(); oldrestrictedmovearea.clear(); // reset, no longer valid or needed } kDebug(1212) << "Done."; } void Workspace::updateClientArea() { updateClientArea(false); } /*! returns the area available for clients. This is the desktop geometry minus windows on the dock. Placement algorithms should refer to this rather than geometry(). \sa geometry() */ QRect Workspace::clientArea(clientAreaOption opt, int screen, int desktop) const { if (desktop == NETWinInfo::OnAllDesktops || desktop == 0) desktop = currentDesktop(); if (screen == -1) screen = activeScreen(); QRect sarea, warea; if (is_multihead) { sarea = (!screenarea.isEmpty() && screen < screenarea[ desktop ].size()) // screens may be missing during KWin initialization or screen config changes ? screenarea[ desktop ][ screen_number ] : QApplication::desktop()->screenGeometry(screen_number); warea = workarea[ desktop ].isNull() ? QApplication::desktop()->screenGeometry(screen_number) : workarea[ desktop ]; } else { sarea = (!screenarea.isEmpty() && screen < screenarea[ desktop ].size()) // screens may be missing during KWin initialization or screen config changes ? screenarea[ desktop ][ screen ] : QApplication::desktop()->screenGeometry(screen); warea = workarea[ desktop ].isNull() ? QRect(0, 0, displayWidth(), displayHeight()) : workarea[ desktop ]; } switch(opt) { case MaximizeArea: case PlacementArea: return sarea; case MaximizeFullArea: case FullScreenArea: case MovementArea: case ScreenArea: if (is_multihead) return QApplication::desktop()->screenGeometry(screen_number); else return QApplication::desktop()->screenGeometry(screen); case WorkArea: if (is_multihead) return sarea; else return warea; case FullArea: return QRect(0, 0, displayWidth(), displayHeight()); } abort(); } QRect Workspace::clientArea(clientAreaOption opt, const QPoint& p, int desktop) const { int screen = QApplication::desktop()->screenNumber(p); return clientArea(opt, screen, desktop); } QRect Workspace::clientArea(clientAreaOption opt, const Client* c) const { return clientArea(opt, c->geometry().center(), c->desktop()); } QRegion Workspace::restrictedMoveArea(int desktop, StrutAreas areas) const { if (desktop == NETWinInfo::OnAllDesktops || desktop == 0) desktop = currentDesktop(); QRegion region; foreach (const StrutRect & rect, restrictedmovearea[desktop]) if (areas & rect.area()) region += rect; return region; } bool Workspace::inUpdateClientArea() const { return !oldrestrictedmovearea.isEmpty(); } QRegion Workspace::previousRestrictedMoveArea(int desktop, StrutAreas areas) const { if (desktop == NETWinInfo::OnAllDesktops || desktop == 0) desktop = currentDesktop(); QRegion region; foreach (const StrutRect & rect, oldrestrictedmovearea.at(desktop)) if (areas & rect.area()) region += rect; return region; } QVector< QRect > Workspace::previousScreenSizes() const { return oldscreensizes; } int Workspace::oldDisplayWidth() const { return olddisplaysize.width(); } int Workspace::oldDisplayHeight() const { return olddisplaysize.height(); } /*! Client \a c is moved around to position \a pos. This gives the workspace the opportunity to interveniate and to implement snap-to-windows functionality. The parameter \a snapAdjust is a multiplier used to calculate the effective snap zones. When 1.0, it means that the snap zones will be used without change. */ QPoint Workspace::adjustClientPosition(Client* c, QPoint pos, bool unrestricted, double snapAdjust) { //CT 16mar98, 27May98 - magics: BorderSnapZone, WindowSnapZone //CT adapted for kwin on 25Nov1999 //aleXXX 02Nov2000 added second snapping mode if (options->windowSnapZone() || options->borderSnapZone() || options->centerSnapZone()) { const bool sOWO = options->isSnapOnlyWhenOverlapping(); const QRect maxRect = clientArea(MovementArea, pos + c->rect().center(), c->desktop()); const int xmin = maxRect.left(); const int xmax = maxRect.right() + 1; //desk size const int ymin = maxRect.top(); const int ymax = maxRect.bottom() + 1; const int cx(pos.x()); const int cy(pos.y()); const int cw(c->width()); const int ch(c->height()); const int rx(cx + cw); const int ry(cy + ch); //these don't change int nx(cx), ny(cy); //buffers int deltaX(xmax); int deltaY(ymax); //minimum distance to other clients int lx, ly, lrx, lry; //coords and size for the comparison client, l // border snap int snap = options->borderSnapZone() * snapAdjust; //snap trigger if (snap) { if ((sOWO ? (cx < xmin) : true) && (qAbs(xmin - cx) < snap)) { deltaX = xmin - cx; nx = xmin; } if ((sOWO ? (rx > xmax) : true) && (qAbs(rx - xmax) < snap) && (qAbs(xmax - rx) < deltaX)) { deltaX = rx - xmax; nx = xmax - cw; } if ((sOWO ? (cy < ymin) : true) && (qAbs(ymin - cy) < snap)) { deltaY = ymin - cy; ny = ymin; } if ((sOWO ? (ry > ymax) : true) && (qAbs(ry - ymax) < snap) && (qAbs(ymax - ry) < deltaY)) { deltaY = ry - ymax; ny = ymax - ch; } } // windows snap snap = options->windowSnapZone() * snapAdjust; if (snap) { QList::ConstIterator l; for (l = clients.constBegin(); l != clients.constEnd(); ++l) { if ((((*l)->isOnDesktop(c->desktop()) && !(*l)->isMinimized()) || (c->isOnDesktop(NET::OnAllDesktops) && (*l)->isOnDesktop(Workspace::currentDesktop()) && !(*l)->isMinimized())) && (!(*l)->tabGroup() || (*l) == (*l)->tabGroup()->current()) && (*l) != c) { lx = (*l)->x(); ly = (*l)->y(); lrx = lx + (*l)->width(); lry = ly + (*l)->height(); if (((cy <= lry) && (cy >= ly)) || ((ry >= ly) && (ry <= lry)) || ((cy <= ly) && (ry >= lry))) { if ((sOWO ? (cx < lrx) : true) && (qAbs(lrx - cx) < snap) && (qAbs(lrx - cx) < deltaX)) { deltaX = qAbs(lrx - cx); nx = lrx; } if ((sOWO ? (rx > lx) : true) && (qAbs(rx - lx) < snap) && (qAbs(rx - lx) < deltaX)) { deltaX = qAbs(rx - lx); nx = lx - cw; } } if (((cx <= lrx) && (cx >= lx)) || ((rx >= lx) && (rx <= lrx)) || ((cx <= lx) && (rx >= lrx))) { if ((sOWO ? (cy < lry) : true) && (qAbs(lry - cy) < snap) && (qAbs(lry - cy) < deltaY)) { deltaY = qAbs(lry - cy); ny = lry; } //if ( (qAbs( ry-ly ) < snap) && (qAbs( ry - ly ) < deltaY )) if ((sOWO ? (ry > ly) : true) && (qAbs(ry - ly) < snap) && (qAbs(ry - ly) < deltaY)) { deltaY = qAbs(ry - ly); ny = ly - ch; } } // Corner snapping if (nx == lrx || nx + cw == lx) { if ((sOWO ? (ry > lry) : true) && (qAbs(lry - ry) < snap) && (qAbs(lry - ry) < deltaY)) { deltaY = qAbs(lry - ry); ny = lry - ch; } if ((sOWO ? (cy < ly) : true) && (qAbs(cy - ly) < snap) && (qAbs(cy - ly) < deltaY)) { deltaY = qAbs(cy - ly); ny = ly; } } if (ny == lry || ny + ch == ly) { if ((sOWO ? (rx > lrx) : true) && (qAbs(lrx - rx) < snap) && (qAbs(lrx - rx) < deltaX)) { deltaX = qAbs(lrx - rx); nx = lrx - cw; } if ((sOWO ? (cx < lx) : true) && (qAbs(cx - lx) < snap) && (qAbs(cx - lx) < deltaX)) { deltaX = qAbs(cx - lx); nx = lx; } } } } } // center snap snap = options->centerSnapZone() * snapAdjust; //snap trigger if (snap) { int diffX = qAbs((xmin + xmax) / 2 - (cx + cw / 2)); int diffY = qAbs((ymin + ymax) / 2 - (cy + ch / 2)); if (diffX < snap && diffY < snap && diffX < deltaX && diffY < deltaY) { // Snap to center of screen deltaX = diffX; deltaY = diffY; nx = (xmin + xmax) / 2 - cw / 2; ny = (ymin + ymax) / 2 - ch / 2; } else if (options->borderSnapZone()) { // Enhance border snap if ((nx == xmin || nx == xmax - cw) && diffY < snap && diffY < deltaY) { // Snap to vertical center on screen edge deltaY = diffY; ny = (ymin + ymax) / 2 - ch / 2; } else if (((unrestricted ? ny == ymin : ny <= ymin) || ny == ymax - ch) && diffX < snap && diffX < deltaX) { // Snap to horizontal center on screen edge deltaX = diffX; nx = (xmin + xmax) / 2 - cw / 2; } } } pos = QPoint(nx, ny); } return pos; } QRect Workspace::adjustClientSize(Client* c, QRect moveResizeGeom, int mode) { //adapted from adjustClientPosition on 29May2004 //this function is called when resizing a window and will modify //the new dimensions to snap to other windows/borders if appropriate if (options->windowSnapZone() || options->borderSnapZone()) { // || options->centerSnapZone ) const bool sOWO = options->isSnapOnlyWhenOverlapping(); const QRect maxRect = clientArea(MovementArea, c->rect().center(), c->desktop()); const int xmin = maxRect.left(); const int xmax = maxRect.right(); //desk size const int ymin = maxRect.top(); const int ymax = maxRect.bottom(); const int cx(moveResizeGeom.left()); const int cy(moveResizeGeom.top()); const int rx(moveResizeGeom.right()); const int ry(moveResizeGeom.bottom()); int newcx(cx), newcy(cy); //buffers int newrx(rx), newry(ry); int deltaX(xmax); int deltaY(ymax); //minimum distance to other clients int lx, ly, lrx, lry; //coords and size for the comparison client, l // border snap int snap = options->borderSnapZone(); //snap trigger if (snap) { deltaX = int(snap); deltaY = int(snap); #define SNAP_BORDER_TOP \ if ((sOWO?(newcyymax):true) && (qAbs(ymax-newry)xmax):true) && (qAbs(xmax-newrx)windowSnapZone(); if (snap) { deltaX = int(snap); deltaY = int(snap); QList::ConstIterator l; for (l = clients.constBegin(); l != clients.constEnd(); ++l) { if ((*l)->isOnDesktop(currentDesktop()) && !(*l)->isMinimized() && (*l) != c) { lx = (*l)->x() - 1; ly = (*l)->y() - 1; lrx = (*l)->x() + (*l)->width(); lry = (*l)->y() + (*l)->height(); #define WITHIN_HEIGHT ((( newcy <= lry ) && ( newcy >= ly )) || \ (( newry >= ly ) && ( newry <= lry )) || \ (( newcy <= ly ) && ( newry >= lry )) ) #define WITHIN_WIDTH ( (( cx <= lrx ) && ( cx >= lx )) || \ (( rx >= lx ) && ( rx <= lrx )) || \ (( cx <= lx ) && ( rx >= lrx )) ) #define SNAP_WINDOW_TOP if ( (sOWO?(newcyly):true) \ && WITHIN_WIDTH \ && (qAbs( ly - newry ) < deltaY) ) { \ deltaY = qAbs( ly - newry ); \ newry=ly; \ } #define SNAP_WINDOW_LEFT if ( (sOWO?(newcxlx):true) \ && WITHIN_HEIGHT \ && (qAbs( lx - newrx ) < deltaX)) \ { \ deltaX = qAbs( lx - newrx ); \ newrx=lx; \ } #define SNAP_WINDOW_C_TOP if ( (sOWO?(newcylry):true) \ && (newcx == lrx || newrx == lx) \ && qAbs(lry-newry) < deltaY ) { \ deltaY = qAbs( lry - newry - 1 ); \ newry = lry - 1; \ } #define SNAP_WINDOW_C_LEFT if ( (sOWO?(newcxlrx):true) \ && (newcy == lry || newry == ly) \ && qAbs(lrx-newrx) < deltaX ) { \ deltaX = qAbs( lrx - newrx - 1 ); \ newrx = lrx - 1; \ } switch(mode) { case PositionBottomRight: SNAP_WINDOW_BOTTOM SNAP_WINDOW_RIGHT SNAP_WINDOW_C_BOTTOM SNAP_WINDOW_C_RIGHT break; case PositionRight: SNAP_WINDOW_RIGHT SNAP_WINDOW_C_RIGHT break; case PositionBottom: SNAP_WINDOW_BOTTOM SNAP_WINDOW_C_BOTTOM break; case PositionTopLeft: SNAP_WINDOW_TOP SNAP_WINDOW_LEFT SNAP_WINDOW_C_TOP SNAP_WINDOW_C_LEFT break; case PositionLeft: SNAP_WINDOW_LEFT SNAP_WINDOW_C_LEFT break; case PositionTop: SNAP_WINDOW_TOP SNAP_WINDOW_C_TOP break; case PositionTopRight: SNAP_WINDOW_TOP SNAP_WINDOW_RIGHT SNAP_WINDOW_C_TOP SNAP_WINDOW_C_RIGHT break; case PositionBottomLeft: SNAP_WINDOW_BOTTOM SNAP_WINDOW_LEFT SNAP_WINDOW_C_BOTTOM SNAP_WINDOW_C_LEFT break; default: abort(); break; } } } } // center snap //snap = options->centerSnapZone; //if (snap) // { // // Don't resize snap to center as it interferes too much // // There are two ways of implementing this if wanted: // // 1) Snap only to the same points that the move snap does, and // // 2) Snap to the horizontal and vertical center lines of the screen // } moveResizeGeom = QRect(QPoint(newcx, newcy), QPoint(newrx, newry)); } return moveResizeGeom; } /*! Marks the client as being moved around by the user. */ void Workspace::setClientIsMoving(Client *c) { Q_ASSERT(!c || !movingClient); // Catch attempts to move a second // window while still moving the first one. movingClient = c; if (movingClient) ++block_focus; else --block_focus; } /*! Cascades all clients on the current desktop */ void Workspace::cascadeDesktop() { // TODO XINERAMA this probably is not right for xinerama Q_ASSERT(block_stacking_updates == 0); initPositioning->reinitCascading(currentDesktop()); QRect area = clientArea(PlacementArea, QPoint(0, 0), currentDesktop()); foreach (Toplevel *toplevel, stackingOrder()) { Client *client = qobject_cast(toplevel); if (!client || (!client->isOnDesktop(currentDesktop())) || (client->isMinimized()) || (client->isOnAllDesktops()) || (!client->isMovable())) continue; initPositioning->placeCascaded(client, area); } } /*! Unclutters the current desktop by smart-placing all clients again. */ void Workspace::unclutterDesktop() { for (int i = clients.size() - 1; i >= 0; i--) { if ((!clients.at(i)->isOnDesktop(currentDesktop())) || (clients.at(i)->isMinimized()) || (clients.at(i)->isOnAllDesktops()) || (!clients.at(i)->isMovable())) continue; initPositioning->placeSmart(clients.at(i), QRect()); } } // When kwin crashes, windows will not be gravitated back to their original position // and will remain offset by the size of the decoration. So when restarting, fix this // (the property with the size of the frame remains on the window after the crash). void Workspace::fixPositionAfterCrash(Window w, const XWindowAttributes& attr) { NETWinInfo i(display(), w, rootWindow(), NET::WMFrameExtents); NETStrut frame = i.frameExtents(); if (frame.left != 0 || frame.top != 0) XMoveWindow(display(), w, attr.x - frame.left, attr.y - frame.top); } //******************************************** // Client //******************************************** void Client::keepInArea(QRect area, bool partial) { if (partial) { // increase the area so that can have only 100 pixels in the area area.setLeft(qMin(area.left() - width() + 100, area.left())); area.setTop(qMin(area.top() - height() + 100, area.top())); area.setRight(qMax(area.right() + width() - 100, area.right())); area.setBottom(qMax(area.bottom() + height() - 100, area.bottom())); } if (!partial) { // resize to fit into area if (area.width() < width() || area.height() < height()) resizeWithChecks(qMin(area.width(), width()), qMin(area.height(), height())); } if (geometry().right() > area.right() && width() < area.width()) move(area.right() - width() + 1, y()); if (geometry().bottom() > area.bottom() && height() < area.height()) move(x(), area.bottom() - height() + 1); if (!area.contains(geometry().topLeft())) { int tx = x(); int ty = y(); if (tx < area.x()) tx = area.x(); if (ty < area.y()) ty = area.y(); move(tx, ty); } } /*! Returns \a area with the client's strut taken into account. Used from Workspace in updateClientArea. */ // TODO move to Workspace? QRect Client::adjustedClientArea(const QRect &desktopArea, const QRect& area) const { QRect r = area; NETExtendedStrut str = strut(); QRect stareaL = QRect( 0, str . left_start, str . left_width, str . left_end - str . left_start + 1); QRect stareaR = QRect( desktopArea . right() - str . right_width + 1, str . right_start, str . right_width, str . right_end - str . right_start + 1); QRect stareaT = QRect( str . top_start, 0, str . top_end - str . top_start + 1, str . top_width); QRect stareaB = QRect( str . bottom_start, desktopArea . bottom() - str . bottom_width + 1, str . bottom_end - str . bottom_start + 1, str . bottom_width); QRect screenarea = workspace()->clientArea(ScreenArea, this); // HACK: workarea handling is not xinerama aware, so if this strut // reserves place at a xinerama edge that's inside the virtual screen, // ignore the strut for workspace setting. if (area == QRect(0, 0, displayWidth(), displayHeight())) { if (stareaL.left() < screenarea.left()) stareaL = QRect(); if (stareaR.right() > screenarea.right()) stareaR = QRect(); if (stareaT.top() < screenarea.top()) stareaT = QRect(); if (stareaB.bottom() < screenarea.bottom()) stareaB = QRect(); } // Handle struts at xinerama edges that are inside the virtual screen. // They're given in virtual screen coordinates, make them affect only // their xinerama screen. stareaL.setLeft(qMax(stareaL.left(), screenarea.left())); stareaR.setRight(qMin(stareaR.right(), screenarea.right())); stareaT.setTop(qMax(stareaT.top(), screenarea.top())); stareaB.setBottom(qMin(stareaB.bottom(), screenarea.bottom())); if (stareaL . intersects(area)) { // kDebug (1212) << "Moving left of: " << r << " to " << stareaL.right() + 1; r . setLeft(stareaL . right() + 1); } if (stareaR . intersects(area)) { // kDebug (1212) << "Moving right of: " << r << " to " << stareaR.left() - 1; r . setRight(stareaR . left() - 1); } if (stareaT . intersects(area)) { // kDebug (1212) << "Moving top of: " << r << " to " << stareaT.bottom() + 1; r . setTop(stareaT . bottom() + 1); } if (stareaB . intersects(area)) { // kDebug (1212) << "Moving bottom of: " << r << " to " << stareaB.top() - 1; r . setBottom(stareaB . top() - 1); } return r; } NETExtendedStrut Client::strut() const { NETExtendedStrut ext = info->extendedStrut(); NETStrut str = info->strut(); if (ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0 && (str.left != 0 || str.right != 0 || str.top != 0 || str.bottom != 0)) { // build extended from simple if (str.left != 0) { ext.left_width = str.left; ext.left_start = 0; ext.left_end = displayHeight(); } if (str.right != 0) { ext.right_width = str.right; ext.right_start = 0; ext.right_end = displayHeight(); } if (str.top != 0) { ext.top_width = str.top; ext.top_start = 0; ext.top_end = displayWidth(); } if (str.bottom != 0) { ext.bottom_width = str.bottom; ext.bottom_start = 0; ext.bottom_end = displayWidth(); } } return ext; } StrutRect Client::strutRect(StrutArea area) const { assert(area != StrutAreaAll); // Not valid NETExtendedStrut strutArea = strut(); switch(area) { case StrutAreaTop: if (strutArea.top_width != 0) return StrutRect(QRect( strutArea.top_start, 0, strutArea.top_end - strutArea.top_start, strutArea.top_width ), StrutAreaTop); break; case StrutAreaRight: if (strutArea.right_width != 0) return StrutRect(QRect( displayWidth() - strutArea.right_width, strutArea.right_start, strutArea.right_width, strutArea.right_end - strutArea.right_start ), StrutAreaRight); break; case StrutAreaBottom: if (strutArea.bottom_width != 0) return StrutRect(QRect( strutArea.bottom_start, displayHeight() - strutArea.bottom_width, strutArea.bottom_end - strutArea.bottom_start, strutArea.bottom_width ), StrutAreaBottom); break; case StrutAreaLeft: if (strutArea.left_width != 0) return StrutRect(QRect( 0, strutArea.left_start, strutArea.left_width, strutArea.left_end - strutArea.left_start ), StrutAreaLeft); break; default: abort(); // Not valid } return StrutRect(); // Null rect } StrutRects Client::strutRects() const { StrutRects region; region += strutRect(StrutAreaTop); region += strutRect(StrutAreaRight); region += strutRect(StrutAreaBottom); region += strutRect(StrutAreaLeft); return region; } bool Client::hasStrut() const { NETExtendedStrut ext = strut(); if (ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0) return false; return true; } bool Client::hasOffscreenXineramaStrut() const { // Get strut as a QRegion QRegion region; region += strutRect(StrutAreaTop); region += strutRect(StrutAreaRight); region += strutRect(StrutAreaBottom); region += strutRect(StrutAreaLeft); // Remove all visible areas so that only the invisible remain int numScreens = QApplication::desktop()->screenCount(); for (int i = 0; i < numScreens; i ++) region -= QApplication::desktop()->screenGeometry(i); // If there's anything left then we have an offscreen strut return !region.isEmpty(); } void Client::checkWorkspacePosition(QRect oldGeometry, int oldDesktop) { if( !oldGeometry.isValid()) oldGeometry = geometry(); if( oldDesktop == -2 ) oldDesktop = desktop(); if (isDesktop()) return; if (isFullScreen()) { QRect area = workspace()->clientArea(FullScreenArea, this); if (geometry() != area) setGeometry(area); return; } if (isDock()) return; if (maximizeMode() != MaximizeRestore) { // TODO update geom_restore? changeMaximize(false, false, true); // adjust size const QRect screenArea = workspace()->clientArea(ScreenArea, this); QRect geom = geometry(); checkOffscreenPosition(&geom, screenArea); setGeometry(geom); return; } if (quick_tile_mode != QuickTileNone) { setGeometry(electricBorderMaximizeGeometry(geometry().center(), desktop())); return; } if (!isShade()) { // TODO // this can be true only if this window was mapped before KWin // was started - in such case, don't adjust position to workarea, // because the window already had its position, and if a window // with a strut altering the workarea would be managed in initialization // after this one, this window would be moved if (workspace()->initializing()) return; // If the window was touching an edge before but not now move it so it is again. // Old and new maximums have different starting values so windows on the screen // edge will move when a new strut is placed on the edge. QRect oldScreenArea; QRect oldGeomTall; QRect oldGeomWide; if( workspace()->inUpdateClientArea()) { // we need to find the screen area as it was before the change oldScreenArea = QRect( 0, 0, workspace()->oldDisplayWidth(), workspace()->oldDisplayHeight()); oldGeomTall = QRect(oldGeometry.x(), 0, oldGeometry.width(), workspace()->oldDisplayHeight()); // Full screen height oldGeomWide = QRect(0, oldGeometry.y(), workspace()->oldDisplayWidth(), oldGeometry.height()); // Full screen width int distance = INT_MAX; foreach( QRect r, workspace()->previousScreenSizes()) { int d = r.contains( oldGeometry.center()) ? 0 : ( r.center() - oldGeometry.center()).manhattanLength(); if( d < distance ) { distance = d; oldScreenArea = r; } } } else { oldScreenArea = workspace()->clientArea(ScreenArea, oldGeometry.center(), oldDesktop); oldGeomTall = QRect(oldGeometry.x(), 0, oldGeometry.width(), displayHeight()); // Full screen height oldGeomWide = QRect(0, oldGeometry.y(), displayWidth(), oldGeometry.height()); // Full screen width } int oldTopMax = oldScreenArea.y(); int oldRightMax = oldScreenArea.x() + oldScreenArea.width(); int oldBottomMax = oldScreenArea.y() + oldScreenArea.height(); int oldLeftMax = oldScreenArea.x(); const QRect screenArea = workspace()->clientArea(ScreenArea, this); int topMax = screenArea.y(); int rightMax = screenArea.x() + screenArea.width(); int bottomMax = screenArea.y() + screenArea.height(); int leftMax = screenArea.x(); QRect newGeom = geom_restore; // geometry(); const QRect newGeomTall = QRect(newGeom.x(), 0, newGeom.width(), displayHeight()); // Full screen height const QRect newGeomWide = QRect(0, newGeom.y(), displayWidth(), newGeom.height()); // Full screen width // Get the max strut point for each side where the window is (E.g. Highest point for // the bottom struts bounded by the window's left and right sides). if( workspace()->inUpdateClientArea()) { // These 4 compute old bounds when the restricted areas themselves changed (Workspace::updateClientArea()) foreach (const QRect & r, workspace()->previousRestrictedMoveArea(oldDesktop, StrutAreaTop).rects()) { QRect rect = r & oldGeomTall; if (!rect.isEmpty()) oldTopMax = qMax(oldTopMax, rect.y() + rect.height()); } foreach (const QRect & r, workspace()->previousRestrictedMoveArea(oldDesktop, StrutAreaRight).rects()) { QRect rect = r & oldGeomWide; if (!rect.isEmpty()) oldRightMax = qMin(oldRightMax, rect.x()); } foreach (const QRect & r, workspace()->previousRestrictedMoveArea(oldDesktop, StrutAreaBottom).rects()) { QRect rect = r & oldGeomTall; if (!rect.isEmpty()) oldBottomMax = qMin(oldBottomMax, rect.y()); } foreach (const QRect & r, workspace()->previousRestrictedMoveArea(oldDesktop, StrutAreaLeft).rects()) { QRect rect = r & oldGeomWide; if (!rect.isEmpty()) oldLeftMax = qMax(oldLeftMax, rect.x() + rect.width()); } } else { // These 4 compute old bounds when e.g. active desktop or screen changes foreach (const QRect & r, workspace()->restrictedMoveArea(oldDesktop, StrutAreaTop).rects()) { QRect rect = r & oldGeomTall; if (!rect.isEmpty()) oldTopMax = qMax(oldTopMax, rect.y() + rect.height()); } foreach (const QRect & r, workspace()->restrictedMoveArea(oldDesktop, StrutAreaRight).rects()) { QRect rect = r & oldGeomWide; if (!rect.isEmpty()) oldRightMax = qMin(oldRightMax, rect.x()); } foreach (const QRect & r, workspace()->restrictedMoveArea(oldDesktop, StrutAreaBottom).rects()) { QRect rect = r & oldGeomTall; if (!rect.isEmpty()) oldBottomMax = qMin(oldBottomMax, rect.y()); } foreach (const QRect & r, workspace()->restrictedMoveArea(oldDesktop, StrutAreaLeft).rects()) { QRect rect = r & oldGeomWide; if (!rect.isEmpty()) oldLeftMax = qMax(oldLeftMax, rect.x() + rect.width()); } } // These 4 compute new bounds foreach (const QRect & r, workspace()->restrictedMoveArea(desktop(), StrutAreaTop).rects()) { QRect rect = r & newGeomTall; if (!rect.isEmpty()) topMax = qMax(topMax, rect.y() + rect.height()); } foreach (const QRect & r, workspace()->restrictedMoveArea(desktop(), StrutAreaRight).rects()) { QRect rect = r & newGeomWide; if (!rect.isEmpty()) rightMax = qMin(rightMax, rect.x()); } foreach (const QRect & r, workspace()->restrictedMoveArea(desktop(), StrutAreaBottom).rects()) { QRect rect = r & newGeomTall; if (!rect.isEmpty()) bottomMax = qMin(bottomMax, rect.y()); } foreach (const QRect & r, workspace()->restrictedMoveArea(desktop(), StrutAreaLeft).rects()) { QRect rect = r & newGeomWide; if (!rect.isEmpty()) leftMax = qMax(leftMax, rect.x() + rect.width()); } // Check if the sides were inside or touching but are no longer if ((oldGeometry.y() >= oldTopMax && newGeom.y() < topMax) || (oldGeometry.y() == oldTopMax && newGeom.y() != topMax)) { // Top was inside or touching before but isn't anymore newGeom.moveTop(qMax(topMax, screenArea.y())); } if ((oldGeometry.y() + oldGeometry.height() <= oldBottomMax && newGeom.y() + newGeom.height() > bottomMax) || (oldGeometry.y() + oldGeometry.height() == oldBottomMax && newGeom.y() + newGeom.height() != bottomMax)) { // Bottom was inside or touching before but isn't anymore newGeom.moveBottom(qMin(bottomMax - 1, screenArea.bottom())); // If the other side was inside make sure it still is afterwards (shrink appropriately) if (oldGeometry.y() >= oldTopMax && newGeom.y() < topMax) newGeom.setTop(qMax(topMax, screenArea.y())); } if ((oldGeometry.x() >= oldLeftMax && newGeom.x() < leftMax) || (oldGeometry.x() == oldLeftMax && newGeom.x() != leftMax)) { // Left was inside or touching before but isn't anymore newGeom.moveLeft(qMax(leftMax, screenArea.x())); } if ((oldGeometry.x() + oldGeometry.width() <= oldRightMax && newGeom.x() + newGeom.width() > rightMax) || (oldGeometry.x() + oldGeometry.width() == oldRightMax && newGeom.x() + newGeom.width() != rightMax)) { // Right was inside or touching before but isn't anymore newGeom.moveRight(qMin(rightMax - 1, screenArea.right())); // If the other side was inside make sure it still is afterwards (shrink appropriately) if (oldGeometry.x() >= oldLeftMax && newGeom.x() < leftMax) newGeom.moveRight(qMin(rightMax - 1, screenArea.right())); } checkOffscreenPosition(&newGeom, screenArea); // Obey size hints. TODO: We really should make sure it stays in the right place newGeom.setSize(adjustedSize(newGeom.size())); if (newGeom != geometry()) setGeometry(newGeom); } } void Client::checkOffscreenPosition(QRect* geom, const QRect& screenArea) { if (geom->x() > screenArea.right()) { int screenWidth = screenArea.width(); geom->moveLeft(screenWidth - (screenWidth / 4)); } if (geom->y() > screenArea.bottom()) { int screenHeight = screenArea.height(); geom->moveBottom(screenHeight - (screenHeight / 4)); } } /*! Adjust the frame size \a frame according to he window's size hints. */ QSize Client::adjustedSize(const QSize& frame, Sizemode mode) const { // first, get the window size for the given frame size s QSize wsize(frame.width() - (border_left + border_right), frame.height() - (border_top + border_bottom)); if (wsize.isEmpty()) wsize = QSize(1, 1); return sizeForClientSize(wsize, mode, false); } // this helper returns proper size even if the window is shaded // see also the comment in Client::setGeometry() QSize Client::adjustedSize() const { return sizeForClientSize(clientSize()); } /*! Calculate the appropriate frame size for the given client size \a wsize. \a wsize is adapted according to the window's size hints (minimum, maximum and incremental size changes). */ QSize Client::sizeForClientSize(const QSize& wsize, Sizemode mode, bool noframe) const { int w = wsize.width(); int h = wsize.height(); if (w < 1 || h < 1) { kWarning(1212) << "sizeForClientSize() with empty size!" ; kWarning(1212) << kBacktrace() ; } if (w < 1) w = 1; if (h < 1) h = 1; // basesize, minsize, maxsize, paspect and resizeinc have all values defined, // even if they're not set in flags - see getWmNormalHints() QSize min_size = tabGroup() ? tabGroup()->minSize() : minSize(); QSize max_size = tabGroup() ? tabGroup()->maxSize() : maxSize(); if (decoration != NULL) { QSize decominsize = decoration->minimumSize(); QSize border_size(border_left + border_right, border_top + border_bottom); if (border_size.width() > decominsize.width()) // just in case decominsize.setWidth(border_size.width()); if (border_size.height() > decominsize.height()) decominsize.setHeight(border_size.height()); if (decominsize.width() > min_size.width()) min_size.setWidth(decominsize.width()); if (decominsize.height() > min_size.height()) min_size.setHeight(decominsize.height()); } w = qMin(max_size.width(), w); h = qMin(max_size.height(), h); w = qMax(min_size.width(), w); h = qMax(min_size.height(), h); int w1 = w; int h1 = h; int width_inc = xSizeHint.width_inc; int height_inc = xSizeHint.height_inc; int basew_inc = xSizeHint.min_width; // see getWmNormalHints() int baseh_inc = xSizeHint.min_height; w = int((w - basew_inc) / width_inc) * width_inc + basew_inc; h = int((h - baseh_inc) / height_inc) * height_inc + baseh_inc; // code for aspect ratios based on code from FVWM /* * The math looks like this: * * minAspectX dwidth maxAspectX * ---------- <= ------- <= ---------- * minAspectY dheight maxAspectY * * If that is multiplied out, then the width and height are * invalid in the following situations: * * minAspectX * dheight > minAspectY * dwidth * maxAspectX * dheight < maxAspectY * dwidth * */ if (xSizeHint.flags & PAspect) { double min_aspect_w = xSizeHint.min_aspect.x; // use doubles, because the values can be MAX_INT double min_aspect_h = xSizeHint.min_aspect.y; // and multiplying would go wrong otherwise double max_aspect_w = xSizeHint.max_aspect.x; double max_aspect_h = xSizeHint.max_aspect.y; // According to ICCCM 4.1.2.3 PMinSize should be a fallback for PBaseSize for size increments, // but not for aspect ratio. Since this code comes from FVWM, handles both at the same time, // and I have no idea how it works, let's hope nobody relies on that. w -= xSizeHint.base_width; h -= xSizeHint.base_height; int max_width = max_size.width() - xSizeHint.base_width; int min_width = min_size.width() - xSizeHint.base_width; int max_height = max_size.height() - xSizeHint.base_height; int min_height = min_size.height() - xSizeHint.base_height; #define ASPECT_CHECK_GROW_W \ if ( min_aspect_w * h > min_aspect_h * w ) \ { \ int delta = int( min_aspect_w * h / min_aspect_h - w ) / width_inc * width_inc; \ if ( w + delta <= max_width ) \ w += delta; \ } #define ASPECT_CHECK_SHRINK_H_GROW_W \ if ( min_aspect_w * h > min_aspect_h * w ) \ { \ int delta = int( h - w * min_aspect_h / min_aspect_w ) / height_inc * height_inc; \ if ( h - delta >= min_height ) \ h -= delta; \ else \ { \ int delta = int( min_aspect_w * h / min_aspect_h - w ) / width_inc * width_inc; \ if ( w + delta <= max_width ) \ w += delta; \ } \ } #define ASPECT_CHECK_GROW_H \ if ( max_aspect_w * h < max_aspect_h * w ) \ { \ int delta = int( w * max_aspect_h / max_aspect_w - h ) / height_inc * height_inc; \ if ( h + delta <= max_height ) \ h += delta; \ } #define ASPECT_CHECK_SHRINK_W_GROW_H \ if ( max_aspect_w * h < max_aspect_h * w ) \ { \ int delta = int( w - max_aspect_w * h / max_aspect_h ) / width_inc * width_inc; \ if ( w - delta >= min_width ) \ w -= delta; \ else \ { \ int delta = int( w * max_aspect_h / max_aspect_w - h ) / height_inc * height_inc; \ if ( h + delta <= max_height ) \ h += delta; \ } \ } switch(mode) { case SizemodeAny: #if 0 // make SizemodeAny equal to SizemodeFixedW - prefer keeping fixed width, // so that changing aspect ratio to a different value and back keeps the same size (#87298) { ASPECT_CHECK_SHRINK_H_GROW_W ASPECT_CHECK_SHRINK_W_GROW_H ASPECT_CHECK_GROW_H ASPECT_CHECK_GROW_W break; } #endif case SizemodeFixedW: { // the checks are order so that attempts to modify height are first ASPECT_CHECK_GROW_H ASPECT_CHECK_SHRINK_H_GROW_W ASPECT_CHECK_SHRINK_W_GROW_H ASPECT_CHECK_GROW_W break; } case SizemodeFixedH: { ASPECT_CHECK_GROW_W ASPECT_CHECK_SHRINK_W_GROW_H ASPECT_CHECK_SHRINK_H_GROW_W ASPECT_CHECK_GROW_H break; } case SizemodeMax: { // first checks that try to shrink ASPECT_CHECK_SHRINK_H_GROW_W ASPECT_CHECK_SHRINK_W_GROW_H ASPECT_CHECK_GROW_W ASPECT_CHECK_GROW_H break; } } #undef ASPECT_CHECK_SHRINK_H_GROW_W #undef ASPECT_CHECK_SHRINK_W_GROW_H #undef ASPECT_CHECK_GROW_W #undef ASPECT_CHECK_GROW_H w += xSizeHint.base_width; h += xSizeHint.base_height; } if (!rules()->checkStrictGeometry(!isFullScreen())) { // disobey increments and aspect by explicit rule w = w1; h = h1; } if (!noframe) { w += border_left + border_right; h += border_top + border_bottom; } return rules()->checkSize(QSize(w, h)); } /*! Gets the client's normal WM hints and reconfigures itself respectively. */ void Client::getWmNormalHints() { long msize; if (XGetWMNormalHints(display(), window(), &xSizeHint, &msize) == 0) xSizeHint.flags = 0; // set defined values for the fields, even if they're not in flags if (!(xSizeHint.flags & PMinSize)) xSizeHint.min_width = xSizeHint.min_height = 0; if (xSizeHint.flags & PBaseSize) { // PBaseSize is a fallback for PMinSize according to ICCCM 4.1.2.3 // The other way around PMinSize is not a complete fallback for PBaseSize, // so that's not handled here. if (!(xSizeHint.flags & PMinSize)) { xSizeHint.min_width = xSizeHint.base_width; xSizeHint.min_height = xSizeHint.base_height; } } else xSizeHint.base_width = xSizeHint.base_height = 0; if (!(xSizeHint.flags & PMaxSize)) xSizeHint.max_width = xSizeHint.max_height = INT_MAX; else { xSizeHint.max_width = qMax(xSizeHint.max_width, 1); xSizeHint.max_height = qMax(xSizeHint.max_height, 1); } if (xSizeHint.flags & PResizeInc) { xSizeHint.width_inc = qMax(xSizeHint.width_inc, 1); xSizeHint.height_inc = qMax(xSizeHint.height_inc, 1); } else { xSizeHint.width_inc = 1; xSizeHint.height_inc = 1; } if (xSizeHint.flags & PAspect) { // no dividing by zero xSizeHint.min_aspect.y = qMax(xSizeHint.min_aspect.y, 1); xSizeHint.max_aspect.y = qMax(xSizeHint.max_aspect.y, 1); } else { xSizeHint.min_aspect.x = 1; xSizeHint.min_aspect.y = INT_MAX; xSizeHint.max_aspect.x = INT_MAX; xSizeHint.max_aspect.y = 1; } if (!(xSizeHint.flags & PWinGravity)) xSizeHint.win_gravity = NorthWestGravity; // Update min/max size of this group if (tabGroup()) tabGroup()->updateMinMaxSize(); if (isManaged()) { // update to match restrictions QSize new_size = adjustedSize(); if (new_size != size() && !isFullScreen()) { QRect orig_geometry = geometry(); resizeWithChecks(new_size); if ((!isSpecialWindow() || isToolbar()) && !isFullScreen()) { // try to keep the window in its xinerama screen if possible, // if that fails at least keep it visible somewhere QRect area = workspace()->clientArea(MovementArea, this); if (area.contains(orig_geometry)) keepInArea(area); area = workspace()->clientArea(WorkArea, this); if (area.contains(orig_geometry)) keepInArea(area); } } } updateAllowedActions(); // affects isResizeable() } QSize Client::minSize() const { return rules()->checkMinSize(QSize(xSizeHint.min_width, xSizeHint.min_height)); } QSize Client::maxSize() const { return rules()->checkMaxSize(QSize(xSizeHint.max_width, xSizeHint.max_height)); } QSize Client::basicUnit() const { return QSize(xSizeHint.width_inc, xSizeHint.height_inc); } /*! Auxiliary function to inform the client about the current window configuration. */ void Client::sendSyntheticConfigureNotify() { XConfigureEvent c; c.type = ConfigureNotify; c.send_event = True; c.event = window(); c.window = window(); c.x = x() + clientPos().x(); c.y = y() + clientPos().y(); c.width = clientSize().width(); c.height = clientSize().height(); c.border_width = 0; c.above = None; c.override_redirect = 0; XSendEvent(display(), c.event, true, StructureNotifyMask, (XEvent*)&c); } const QPoint Client::calculateGravitation(bool invert, int gravity) const { int dx, dy; dx = dy = 0; if (gravity == 0) // default (nonsense) value for the argument gravity = xSizeHint.win_gravity; // dx, dy specify how the client window moves to make space for the frame switch(gravity) { case NorthWestGravity: // move down right default: dx = border_left; dy = border_top; break; case NorthGravity: // move right dx = 0; dy = border_top; break; case NorthEastGravity: // move down left dx = -border_right; dy = border_top; break; case WestGravity: // move right dx = border_left; dy = 0; break; case CenterGravity: break; // will be handled specially case StaticGravity: // don't move dx = 0; dy = 0; break; case EastGravity: // move left dx = -border_right; dy = 0; break; case SouthWestGravity: // move up right dx = border_left ; dy = -border_bottom; break; case SouthGravity: // move up dx = 0; dy = -border_bottom; break; case SouthEastGravity: // move up left dx = -border_right; dy = -border_bottom; break; } if (gravity != CenterGravity) { // translate from client movement to frame movement dx -= border_left; dy -= border_top; } else { // center of the frame will be at the same position client center without frame would be dx = - (border_left + border_right) / 2; dy = - (border_top + border_bottom) / 2; } if (!invert) return QPoint(x() + dx, y() + dy); else return QPoint(x() - dx, y() - dy); } void Client::configureRequest(int value_mask, int rx, int ry, int rw, int rh, int gravity, bool from_tool) { // "maximized" is a user setting -> we do not allow the client to resize itself // away from this & against the users explicit wish kDebug(1212) << this << bool(value_mask & (CWX|CWWidth|CWY|CWHeight)) << bool(maximizeMode() & MaximizeVertical) << bool(maximizeMode() & MaximizeHorizontal); if (!app_noborder) { // if (maximizeMode() & MaximizeVertical) value_mask &= ~(CWY|CWHeight); // do not allow clients to drop out of vertical ... if (maximizeMode() & MaximizeHorizontal) value_mask &= ~(CWX|CWWidth); // .. or horizontal maximization (MaximizeFull == MaximizeVertical|MaximizeHorizontal) } if (!(value_mask & (CWX|CWWidth|CWY|CWHeight))) { kDebug(1212) << "DENIED"; return; // nothing to (left) to do for use - bugs #158974, #252314 } kDebug(1212) << "PERMITTED" << this << bool(value_mask & (CWX|CWWidth|CWY|CWHeight)); if (gravity == 0) // default (nonsense) value for the argument gravity = xSizeHint.win_gravity; if (value_mask & (CWX | CWY)) { QPoint new_pos = calculateGravitation(true, gravity); // undo gravitation if (value_mask & CWX) new_pos.setX(rx); if (value_mask & CWY) new_pos.setY(ry); // clever(?) workaround for applications like xv that want to set // the location to the current location but miscalculate the // frame size due to kwin being a double-reparenting window // manager if (new_pos.x() == x() + clientPos().x() && new_pos.y() == y() + clientPos().y() && gravity == NorthWestGravity && !from_tool) { new_pos.setX(x()); new_pos.setY(y()); } int nw = clientSize().width(); int nh = clientSize().height(); if (value_mask & CWWidth) nw = rw; if (value_mask & CWHeight) nh = rh; QSize ns = sizeForClientSize(QSize(nw, nh)); // enforces size if needed new_pos = rules()->checkPosition(new_pos); QRect orig_geometry = geometry(); GeometryUpdatesBlocker blocker(this); move(new_pos); plainResize(ns); setGeometry(QRect(calculateGravitation(false, gravity), size())); updateFullScreenHack(QRect(new_pos, QSize(nw, nh))); QRect area = workspace()->clientArea(WorkArea, this); if (!from_tool && (!isSpecialWindow() || isToolbar()) && !isFullScreen() && area.contains(orig_geometry)) keepInArea(area); // this is part of the kicker-xinerama-hack... it should be // safe to remove when kicker gets proper ExtendedStrut support; // see Workspace::updateClientArea() and // Client::adjustedClientArea() if (hasStrut()) workspace() -> updateClientArea(); } if (value_mask & (CWWidth | CWHeight) && !(value_mask & (CWX | CWY))) { // pure resize int nw = clientSize().width(); int nh = clientSize().height(); if (value_mask & CWWidth) nw = rw; if (value_mask & CWHeight) nh = rh; QSize ns = sizeForClientSize(QSize(nw, nh)); if (ns != size()) { // don't restore if some app sets its own size again QRect orig_geometry = geometry(); GeometryUpdatesBlocker blocker(this); int save_gravity = xSizeHint.win_gravity; xSizeHint.win_gravity = gravity; resizeWithChecks(ns); xSizeHint.win_gravity = save_gravity; updateFullScreenHack(QRect(calculateGravitation(true, xSizeHint.win_gravity), QSize(nw, nh))); if (!from_tool && (!isSpecialWindow() || isToolbar()) && !isFullScreen()) { // try to keep the window in its xinerama screen if possible, // if that fails at least keep it visible somewhere QRect area = workspace()->clientArea(MovementArea, this); if (area.contains(orig_geometry)) keepInArea(area); area = workspace()->clientArea(WorkArea, this); if (area.contains(orig_geometry)) keepInArea(area); } } } geom_restore = geometry(); // No need to send synthetic configure notify event here, either it's sent together // with geometry change, or there's no need to send it. // Handling of the real ConfigureRequest event forces sending it, as there it's necessary. } void Client::resizeWithChecks(int w, int h, ForceGeometry_t force) { assert(!shade_geometry_change); if (isShade()) { if (h == border_top + border_bottom) { kWarning(1212) << "Shaded geometry passed for size:" ; kWarning(1212) << kBacktrace() ; } } int newx = x(); int newy = y(); QRect area = workspace()->clientArea(WorkArea, this); // don't allow growing larger than workarea if (w > area.width()) w = area.width(); if (h > area.height()) h = area.height(); QSize tmp = adjustedSize(QSize(w, h)); // checks size constraints, including min/max size w = tmp.width(); h = tmp.height(); switch(xSizeHint.win_gravity) { case NorthWestGravity: // top left corner doesn't move default: break; case NorthGravity: // middle of top border doesn't move newx = (newx + width() / 2) - (w / 2); break; case NorthEastGravity: // top right corner doesn't move newx = newx + width() - w; break; case WestGravity: // middle of left border doesn't move newy = (newy + height() / 2) - (h / 2); break; case CenterGravity: // middle point doesn't move newx = (newx + width() / 2) - (w / 2); newy = (newy + height() / 2) - (h / 2); break; case StaticGravity: // top left corner of _client_ window doesn't move // since decoration doesn't change, equal to NorthWestGravity break; case EastGravity: // // middle of right border doesn't move newx = newx + width() - w; newy = (newy + height() / 2) - (h / 2); break; case SouthWestGravity: // bottom left corner doesn't move newy = newy + height() - h; break; case SouthGravity: // middle of bottom border doesn't move newx = (newx + width() / 2) - (w / 2); newy = newy + height() - h; break; case SouthEastGravity: // bottom right corner doesn't move newx = newx + width() - w; newy = newy + height() - h; break; } setGeometry(newx, newy, w, h, force); } // _NET_MOVERESIZE_WINDOW void Client::NETMoveResizeWindow(int flags, int x, int y, int width, int height) { int gravity = flags & 0xff; int value_mask = 0; if (flags & (1 << 8)) value_mask |= CWX; if (flags & (1 << 9)) value_mask |= CWY; if (flags & (1 << 10)) value_mask |= CWWidth; if (flags & (1 << 11)) value_mask |= CWHeight; configureRequest(value_mask, x, y, width, height, gravity, true); } /*! Returns whether the window is moveable or has a fixed position. */ bool Client::isMovable() const { if (!motif_may_move || isFullScreen()) return false; if (isSpecialWindow() && !isSplash() && !isToolbar()) // allow moving of splashscreens :) return false; if (maximizeMode() == MaximizeFull && !options->moveResizeMaximizedWindows()) return false; if (rules()->checkPosition(invalidPoint) != invalidPoint) // forced position return false; return true; } /*! Returns whether the window is moveable across Xinerama screens */ bool Client::isMovableAcrossScreens() const { if (!motif_may_move) return false; if (isSpecialWindow() && !isSplash() && !isToolbar()) // allow moving of splashscreens :) return false; if (rules()->checkPosition(invalidPoint) != invalidPoint) // forced position return false; return true; } /*! Returns whether the window is resizable or has a fixed size. */ bool Client::isResizable() const { if (!motif_may_resize || isFullScreen()) return false; if (isSpecialWindow() || isSplash() || isToolbar()) return false; if (maximizeMode() == MaximizeFull && !options->moveResizeMaximizedWindows()) return isMove(); // for quick tiling - maxmode will be unset if we tile if (rules()->checkSize(QSize()).isValid()) // forced size return false; QSize min = tabGroup() ? tabGroup()->minSize() : minSize(); QSize max = tabGroup() ? tabGroup()->maxSize() : maxSize(); return min.width() < max.width() || min.height() < max.height(); } /* Returns whether the window is maximizable or not */ bool Client::isMaximizable() const { { // isMovable() and isResizable() may be false for maximized windows // with moving/resizing maximized windows disabled TemporaryAssign< MaximizeMode > tmp(max_mode, MaximizeRestore); if (!isMovable() || !isResizable() || isToolbar()) // SELI isToolbar() ? return false; } if (rules()->checkMaximize(MaximizeRestore) == MaximizeRestore && rules()->checkMaximize(MaximizeFull) != MaximizeRestore) return true; return false; } /*! Reimplemented to inform the client about the new window position. */ void Client::setGeometry(int x, int y, int w, int h, ForceGeometry_t force) { // this code is also duplicated in Client::plainResize() // Ok, the shading geometry stuff. Generally, code doesn't care about shaded geometry, // simply because there are too many places dealing with geometry. Those places // ignore shaded state and use normal geometry, which they usually should get // from adjustedSize(). Such geometry comes here, and if the window is shaded, // the geometry is used only for client_size, since that one is not used when // shading. Then the frame geometry is adjusted for the shaded geometry. // This gets more complicated in the case the code does only something like // setGeometry( geometry()) - geometry() will return the shaded frame geometry. // Such code is wrong and should be changed to handle the case when the window is shaded, // for example using Client::clientSize() if (shade_geometry_change) ; // nothing else if (isShade()) { if (h == border_top + border_bottom) { kDebug(1212) << "Shaded geometry passed for size:"; kDebug(1212) << kBacktrace(); } else { client_size = QSize(w - border_left - border_right, h - border_top - border_bottom); h = border_top + border_bottom; } } else { client_size = QSize(w - border_left - border_right, h - border_top - border_bottom); } QRect g(x, y, w, h); if (block_geometry_updates == 0 && g != rules()->checkGeometry(g)) { kDebug(1212) << "forced geometry fail:" << g << ":" << rules()->checkGeometry(g); kDebug(1212) << kBacktrace(); } if (force == NormalGeometrySet && geom == g && pending_geometry_update == PendingGeometryNone) return; geom = g; if (block_geometry_updates != 0) { if (pending_geometry_update == PendingGeometryForced) {} // maximum, nothing needed else if (force == ForceGeometrySet) pending_geometry_update = PendingGeometryForced; else pending_geometry_update = PendingGeometryNormal; return; } bool resized = (geom_before_block.size() != geom.size() || pending_geometry_update == PendingGeometryForced); if (resized) { resizeDecoration(QSize(w, h)); XMoveResizeWindow(display(), frameId(), x, y, w, h); if (!isShade()) { QSize cs = clientSize(); XMoveResizeWindow(display(), wrapperId(), clientPos().x(), clientPos().y(), cs.width(), cs.height()); #ifdef HAVE_XSYNC if (!isResize() || syncRequest.counter == None) #endif XMoveResizeWindow(display(), window(), 0, 0, cs.width(), cs.height()); } updateShape(); } else { XMoveWindow(display(), frameId(), x, y); if (inputId()) { const QPoint pos = QPoint(x, y) + inputPos(); XMoveWindow(display(), inputId(), pos.x(), pos.y()); } } // SELI TODO won't this be too expensive? sendSyntheticConfigureNotify(); updateWindowRules(Rules::Position|Rules::Size); // keep track of old maximize mode // to detect changes workspace()->checkActiveScreen(this); workspace()->updateStackingOrder(); // need to regenerate decoration pixmaps when either // - size is changed // - maximize mode is changed to MaximizeRestore, when size unchanged // which can happen when untabbing maximized windows if (resized) { discardWindowPixmap(); emit geometryShapeChanged(this, geom_before_block); } const QRect deco_rect = visibleRect(); addLayerRepaint(deco_rect_before_block); addLayerRepaint(deco_rect); geom_before_block = geom; deco_rect_before_block = deco_rect; // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Geometry); // TODO: this signal is emitted too often emit geometryChanged(); } void Client::plainResize(int w, int h, ForceGeometry_t force) { // this code is also duplicated in Client::setGeometry(), and it's also commented there if (shade_geometry_change) ; // nothing else if (isShade()) { if (h == border_top + border_bottom) { kDebug(1212) << "Shaded geometry passed for size:"; kDebug(1212) << kBacktrace(); } else { client_size = QSize(w - border_left - border_right, h - border_top - border_bottom); h = border_top + border_bottom; } } else { client_size = QSize(w - border_left - border_right, h - border_top - border_bottom); } QSize s(w, h); if (block_geometry_updates == 0 && s != rules()->checkSize(s)) { kDebug(1212) << "forced size fail:" << s << ":" << rules()->checkSize(s); kDebug(1212) << kBacktrace(); } // resuming geometry updates is handled only in setGeometry() assert(pending_geometry_update == PendingGeometryNone || block_geometry_updates > 0); if (force == NormalGeometrySet && geom.size() == s) return; geom.setSize(s); if (block_geometry_updates != 0) { if (pending_geometry_update == PendingGeometryForced) {} // maximum, nothing needed else if (force == ForceGeometrySet) pending_geometry_update = PendingGeometryForced; else pending_geometry_update = PendingGeometryNormal; return; } resizeDecoration(s); XResizeWindow(display(), frameId(), w, h); // resizeDecoration( s ); if (!isShade()) { QSize cs = clientSize(); XMoveResizeWindow(display(), wrapperId(), clientPos().x(), clientPos().y(), cs.width(), cs.height()); XMoveResizeWindow(display(), window(), 0, 0, cs.width(), cs.height()); } updateShape(); sendSyntheticConfigureNotify(); updateWindowRules(Rules::Position|Rules::Size); workspace()->checkActiveScreen(this); workspace()->updateStackingOrder(); discardWindowPixmap(); emit geometryShapeChanged(this, geom_before_block); const QRect deco_rect = visibleRect(); addLayerRepaint(deco_rect_before_block); addLayerRepaint(deco_rect); geom_before_block = geom; deco_rect_before_block = deco_rect; // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Geometry); // TODO: this signal is emitted too often emit geometryChanged(); } /*! Reimplemented to inform the client about the new window position. */ void Client::move(int x, int y, ForceGeometry_t force) { // resuming geometry updates is handled only in setGeometry() assert(pending_geometry_update == PendingGeometryNone || block_geometry_updates > 0); QPoint p(x, y); if (block_geometry_updates == 0 && p != rules()->checkPosition(p)) { kDebug(1212) << "forced position fail:" << p << ":" << rules()->checkPosition(p); kDebug(1212) << kBacktrace(); } if (force == NormalGeometrySet && geom.topLeft() == p) return; geom.moveTopLeft(p); if (block_geometry_updates != 0) { if (pending_geometry_update == PendingGeometryForced) {} // maximum, nothing needed else if (force == ForceGeometrySet) pending_geometry_update = PendingGeometryForced; else pending_geometry_update = PendingGeometryNormal; return; } XMoveWindow(display(), frameId(), x, y); sendSyntheticConfigureNotify(); updateWindowRules(Rules::Position); workspace()->checkActiveScreen(this); workspace()->updateStackingOrder(); if (Compositor::isCreated()) { // TODO: move out of geometry.cpp, is this really needed here? Compositor::self()->checkUnredirect(); } // client itself is not damaged const QRect deco_rect = visibleRect(); addLayerRepaint(deco_rect_before_block); addLayerRepaint(deco_rect); // trigger repaint of window's new location geom_before_block = geom; deco_rect_before_block = deco_rect; // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Geometry); } void Client::blockGeometryUpdates(bool block) { if (block) { if (block_geometry_updates == 0) pending_geometry_update = PendingGeometryNone; ++block_geometry_updates; } else { if (--block_geometry_updates == 0) { if (pending_geometry_update != PendingGeometryNone) { if (isShade()) setGeometry(QRect(pos(), adjustedSize()), NormalGeometrySet); else setGeometry(geometry(), NormalGeometrySet); pending_geometry_update = PendingGeometryNone; } } } } void Client::maximize(MaximizeMode m) { setMaximize(m & MaximizeVertical, m & MaximizeHorizontal); } /*! Sets the maximization according to \a vertically and \a horizontally */ void Client::setMaximize(bool vertically, bool horizontally) { // changeMaximize() flips the state, so change from set->flip changeMaximize( max_mode & MaximizeVertical ? !vertically : vertically, max_mode & MaximizeHorizontal ? !horizontally : horizontally, false); emit clientMaximizedStateChanged(this, max_mode); emit clientMaximizedStateChanged(this, vertically, horizontally); } // Update states of all other windows in this group class TabSynchronizer { public: TabSynchronizer(Client *client, TabGroup::States syncStates) : m_client(client) , m_states(syncStates) { if (client->tabGroup()) client->tabGroup()->blockStateUpdates(true); } ~TabSynchronizer() { syncNow(); } void syncNow() { if (m_client && m_client->tabGroup()) { m_client->tabGroup()->blockStateUpdates(false); m_client->tabGroup()->updateStates(m_client, m_states); } m_client = 0; } private: Client *m_client; TabGroup::States m_states; }; static bool changeMaximizeRecursion = false; void Client::changeMaximize(bool vertical, bool horizontal, bool adjust) { if (changeMaximizeRecursion) return; // sic! codeblock for TemporaryAssign { // isMovable() and isResizable() may be false for maximized windows // with moving/resizing maximized windows disabled TemporaryAssign< MaximizeMode > tmp(max_mode, MaximizeRestore); if (!isMovable() || !isResizable() || isToolbar()) // SELI isToolbar() ? return; } MaximizeMode old_mode = max_mode; // 'adjust == true' means to update the size only, e.g. after changing workspace size if (!adjust) { if (vertical) max_mode = MaximizeMode(max_mode ^ MaximizeVertical); if (horizontal) max_mode = MaximizeMode(max_mode ^ MaximizeHorizontal); } max_mode = rules()->checkMaximize(max_mode); if (!adjust && max_mode == old_mode) return; GeometryUpdatesBlocker blocker(this); // QT synchronizing required because we eventually change from QT to Maximized TabSynchronizer syncer(this, TabGroup::Maximized|TabGroup::QuickTile); // maximing one way and unmaximizing the other way shouldn't happen, // so restore first and then maximize the other way if ((old_mode == MaximizeVertical && max_mode == MaximizeHorizontal) || (old_mode == MaximizeHorizontal && max_mode == MaximizeVertical)) { changeMaximize(false, false, false); // restore } QRect clientArea; if (isElectricBorderMaximizing()) clientArea = workspace()->clientArea(MaximizeArea, cursorPos(), desktop()); else clientArea = workspace()->clientArea(MaximizeArea, this); // save sizes for restoring, if maximalizing QSize sz; if (isShade()) sz = sizeForClientSize(clientSize()); else sz = size(); if (quick_tile_mode == QuickTileNone) { if (!adjust && !(old_mode & MaximizeVertical)) { geom_restore.setTop(y()); geom_restore.setHeight(sz.height()); } if (!adjust && !(old_mode & MaximizeHorizontal)) { geom_restore.setLeft(x()); geom_restore.setWidth(sz.width()); } } if (options->borderlessMaximizedWindows()) { // triggers a maximize change. // The next setNoBorder interation will exit since there's no change but the first recursion pullutes the restore geometry changeMaximizeRecursion = true; setNoBorder(app_noborder || max_mode == MaximizeFull); changeMaximizeRecursion = false; } if (!adjust) { if ((vertical && !(old_mode & MaximizeVertical)) || (horizontal && !(old_mode & MaximizeHorizontal))) Notify::raise(Notify::Maximize); else Notify::raise(Notify::UnMaximize); } ForceGeometry_t geom_mode = NormalGeometrySet; if (decoration != NULL) { // decorations may turn off some borders when maximized if (checkBorderSizes(false)) // only query, don't resize geom_mode = ForceGeometrySet; } // Conditional quick tiling exit points if (quick_tile_mode != QuickTileNone) { if (old_mode == MaximizeFull && !clientArea.contains(geom_restore.center())) { // Not restoring on the same screen // TODO: The following doesn't work for some reason //quick_tile_mode = QuickTileNone; // And exit quick tile mode manually } else if ((old_mode == MaximizeVertical && max_mode == MaximizeRestore) || (old_mode == MaximizeFull && max_mode == MaximizeHorizontal)) { // Modifying geometry of a tiled window quick_tile_mode = QuickTileNone; // Exit quick tile mode without restoring geometry } } switch(max_mode) { case MaximizeVertical: { if (old_mode & MaximizeHorizontal) { // actually restoring from MaximizeFull if (geom_restore.width() == 0 || !clientArea.contains(geom_restore.center())) { // needs placement plainResize(adjustedSize(QSize(width() * 2 / 3, clientArea.height()), SizemodeFixedH), geom_mode); workspace()->placeSmart(this, clientArea); } else { setGeometry(QRect(QPoint(geom_restore.x(), clientArea.top()), adjustedSize(QSize(geom_restore.width(), clientArea.height()), SizemodeFixedH)), geom_mode); } } else { setGeometry(QRect(QPoint(x(), clientArea.top()), adjustedSize(QSize(width(), clientArea.height()), SizemodeFixedH)), geom_mode); } info->setState(NET::MaxVert, NET::Max); break; } case MaximizeHorizontal: { if (old_mode & MaximizeVertical) { // actually restoring from MaximizeFull if (geom_restore.height() == 0 || !clientArea.contains(geom_restore.center())) { // needs placement plainResize(adjustedSize(QSize(clientArea.width(), height() * 2 / 3), SizemodeFixedW), geom_mode); workspace()->placeSmart(this, clientArea); } else { setGeometry(QRect(QPoint(clientArea.left(), geom_restore.y()), adjustedSize(QSize(clientArea.width(), geom_restore.height()), SizemodeFixedW)), geom_mode); } } else { setGeometry(QRect(QPoint(clientArea.left(), y()), adjustedSize(QSize(clientArea.width(), height()), SizemodeFixedW)), geom_mode); } info->setState(NET::MaxHoriz, NET::Max); break; } case MaximizeRestore: { QRect restore = geometry(); // when only partially maximized, geom_restore may not have the other dimension remembered if (old_mode & MaximizeVertical) { restore.setTop(geom_restore.top()); restore.setBottom(geom_restore.bottom()); } if (old_mode & MaximizeHorizontal) { restore.setLeft(geom_restore.left()); restore.setRight(geom_restore.right()); } if (!restore.isValid()) { QSize s = QSize(clientArea.width() * 2 / 3, clientArea.height() * 2 / 3); if (geom_restore.width() > 0) s.setWidth(geom_restore.width()); if (geom_restore.height() > 0) s.setHeight(geom_restore.height()); plainResize(adjustedSize(s)); workspace()->placeSmart(this, clientArea); restore = geometry(); if (geom_restore.width() > 0) restore.moveLeft(geom_restore.x()); if (geom_restore.height() > 0) restore.moveTop(geom_restore.y()); geom_restore = restore; // relevant for mouse pos calculation, bug #298646 } setGeometry(restore, geom_mode); if (!clientArea.contains(geom_restore.center())) // Not restoring to the same screen workspace()->place(this, clientArea); info->setState(0, NET::Max); break; } case MaximizeFull: { QSize adjSize = adjustedSize(clientArea.size(), SizemodeMax); QRect r = QRect(clientArea.topLeft(), adjSize); if (r.size() != clientArea.size()) { // to avoid off-by-one errors... if (isElectricBorderMaximizing() && r.width() < clientArea.width()) r.moveLeft(QCursor::pos().x() - r.width()/2); else r.moveCenter(clientArea.center()); } setGeometry(r, geom_mode); info->setState(NET::Max, NET::Max); break; } default: break; } syncer.syncNow(); // important because of window rule updates! updateAllowedActions(); if (decoration != NULL) decoration->maximizeChange(); updateWindowRules(Rules::MaximizeVert|Rules::MaximizeHoriz|Rules::Position|Rules::Size); } bool Client::isFullScreenable(bool fullscreen_hack) const { if (!rules()->checkFullScreen(true)) return false; if (fullscreen_hack) return isNormalWindow(); if (rules()->checkStrictGeometry(false)) { // the app wouldn't fit exactly fullscreen geometry due to its strict geometry requirements QRect fsarea = workspace()->clientArea(FullScreenArea, this); if (sizeForClientSize(fsarea.size(), SizemodeAny, true) != fsarea.size()) return false; } // don't check size constrains - some apps request fullscreen despite requesting fixed size return !isSpecialWindow(); // also better disallow only weird types to go fullscreen } bool Client::userCanSetFullScreen() const { if (fullscreen_mode == FullScreenHack) return false; if (!isFullScreenable(false)) return false; // isMaximizable() returns false if fullscreen TemporaryAssign< FullScreenMode > tmp(fullscreen_mode, FullScreenNone); return isNormalWindow() && isMaximizable(); } void Client::setFullScreen(bool set, bool user) { if (!isFullScreen() && !set) return; if (fullscreen_mode == FullScreenHack) return; if (user && !userCanSetFullScreen()) return; set = rules()->checkFullScreen(set); setShade(ShadeNone); bool was_fs = isFullScreen(); if (was_fs) workspace()->updateFocusMousePosition(QCursor::pos()); else geom_fs_restore = geometry(); fullscreen_mode = set ? FullScreenNormal : FullScreenNone; if (was_fs == isFullScreen()) return; if (set) workspace()->raiseClient(this); StackingUpdatesBlocker blocker1(workspace()); GeometryUpdatesBlocker blocker2(this); workspace()->updateClientLayer(this); // active fullscreens get different layer info->setState(isFullScreen() ? NET::FullScreen : 0, NET::FullScreen); updateDecoration(false, false); if (isFullScreen()) { if (info->fullscreenMonitors().isSet()) setGeometry(fullscreenMonitorsArea(info->fullscreenMonitors())); else setGeometry(workspace()->clientArea(FullScreenArea, this)); } else { if (!geom_fs_restore.isNull()) { int currentScreen = screen(); setGeometry(QRect(geom_fs_restore.topLeft(), adjustedSize(geom_fs_restore.size()))); if( currentScreen != screen()) workspace()->sendClientToScreen( this, currentScreen ); // TODO isShaded() ? } else { // does this ever happen? setGeometry(workspace()->clientArea(MaximizeArea, this)); } } updateWindowRules(Rules::Fullscreen|Rules::Position|Rules::Size); if (was_fs != isFullScreen()) { emit clientFullScreenSet(this, set, user); emit fullScreenChanged(); if (isFullScreen()) { Notify::raise(Notify::FullScreen); } else { Notify::raise(Notify::UnFullScreen); } } } void Client::updateFullscreenMonitors(NETFullscreenMonitors topology) { int nscreens = QApplication::desktop()->screenCount(); // kDebug( 1212 ) << "incoming request with top: " << topology.top << " bottom: " << topology.bottom // << " left: " << topology.left << " right: " << topology.right // << ", we have: " << nscreens << " screens."; if (topology.top >= nscreens || topology.bottom >= nscreens || topology.left >= nscreens || topology.right >= nscreens) { kWarning(1212) << "fullscreenMonitors update failed. request higher than number of screens."; return; } info->setFullscreenMonitors(topology); if (isFullScreen()) setGeometry(fullscreenMonitorsArea(topology)); } /*! Calculates the bounding rectangle defined by the 4 monitor indices indicating the top, bottom, left, and right edges of the window when the fullscreen state is enabled. */ QRect Client::fullscreenMonitorsArea(NETFullscreenMonitors requestedTopology) const { QRect top, bottom, left, right, total; top = QApplication::desktop()->screenGeometry(requestedTopology.top); bottom = QApplication::desktop()->screenGeometry(requestedTopology.bottom); left = QApplication::desktop()->screenGeometry(requestedTopology.left); right = QApplication::desktop()->screenGeometry(requestedTopology.right); total = top.united(bottom.united(left.united(right))); // kDebug( 1212 ) << "top: " << top << " bottom: " << bottom // << " left: " << left << " right: " << right; // kDebug( 1212 ) << "returning rect: " << total; return total; } int Client::checkFullScreenHack(const QRect& geom) const { if (!options->isLegacyFullscreenSupport()) return 0; // if it's noborder window, and has size of one screen or the whole desktop geometry, it's fullscreen hack if (noBorder() && app_noborder && isFullScreenable(true)) { if (geom.size() == workspace()->clientArea(FullArea, geom.center(), desktop()).size()) return 2; // full area fullscreen hack if (geom.size() == workspace()->clientArea(ScreenArea, geom.center(), desktop()).size()) return 1; // xinerama-aware fullscreen hack } return 0; } void Client::updateFullScreenHack(const QRect& geom) { int type = checkFullScreenHack(geom); if (fullscreen_mode == FullScreenNone && type != 0) { fullscreen_mode = FullScreenHack; updateDecoration(false, false); QRect geom; if (rules()->checkStrictGeometry(false)) { geom = type == 2 // 1 - it's xinerama-aware fullscreen hack, 2 - it's full area ? workspace()->clientArea(FullArea, geom.center(), desktop()) : workspace()->clientArea(ScreenArea, geom.center(), desktop()); } else geom = workspace()->clientArea(FullScreenArea, geom.center(), desktop()); setGeometry(geom); emit fullScreenChanged(); } else if (fullscreen_mode == FullScreenHack && type == 0) { fullscreen_mode = FullScreenNone; updateDecoration(false, false); // whoever called this must setup correct geometry emit fullScreenChanged(); } StackingUpdatesBlocker blocker(workspace()); workspace()->updateClientLayer(this); // active fullscreens get different layer } static GeometryTip* geometryTip = 0; void Client::positionGeometryTip() { assert(isMove() || isResize()); // Position and Size display if (effects && static_cast(effects)->provides(Effect::GeometryTip)) return; // some effect paints this for us if (options->showGeometryTip()) { if (!geometryTip) { geometryTip = new GeometryTip(&xSizeHint, false); } QRect wgeom(moveResizeGeom); // position of the frame, size of the window itself wgeom.setWidth(wgeom.width() - (width() - clientSize().width())); wgeom.setHeight(wgeom.height() - (height() - clientSize().height())); if (isShade()) wgeom.setHeight(0); geometryTip->setGeometry(wgeom); if (!geometryTip->isVisible()) geometryTip->show(); geometryTip->raise(); } } bool Client::startMoveResize() { assert(!moveResizeMode); assert(QWidget::keyboardGrabber() == NULL); assert(QWidget::mouseGrabber() == NULL); stopDelayedMoveResize(); if (QApplication::activePopupWidget() != NULL) return false; // popups have grab bool has_grab = false; // This reportedly improves smoothness of the moveresize operation, // something with Enter/LeaveNotify events, looks like XFree performance problem or something *shrug* // (http://lists.kde.org/?t=107302193400001&r=1&w=2) XSetWindowAttributes attrs; QRect r = workspace()->clientArea(FullArea, this); move_resize_grab_window = XCreateWindow(display(), rootWindow(), r.x(), r.y(), r.width(), r.height(), 0, CopyFromParent, InputOnly, CopyFromParent, 0, &attrs); XMapRaised(display(), move_resize_grab_window); if (XGrabPointer(display(), move_resize_grab_window, False, ButtonPressMask | ButtonReleaseMask | PointerMotionMask | EnterWindowMask | LeaveWindowMask, GrabModeAsync, GrabModeAsync, move_resize_grab_window, cursor.handle(), xTime()) == Success) has_grab = true; if (grabXKeyboard(frameId())) has_grab = move_resize_has_keyboard_grab = true; if (!has_grab) { // at least one grab is necessary in order to be able to finish move/resize XDestroyWindow(display(), move_resize_grab_window); move_resize_grab_window = None; return false; } moveResizeMode = true; workspace()->setClientIsMoving(this); // If we have quick maximization enabled then it's safe to automatically restore windows // when starting a move as the user can undo their action by moving the window back to // the top of the screen. When the setting is disabled then doing so is confusing. bool fakeMove = false; if (maximizeMode() != MaximizeRestore && (maximizeMode() != MaximizeFull || options->moveResizeMaximizedWindows())) { // allow moveResize, but unset maximization state in resize case if (mode != PositionCenter) { // means "isResize()" but moveResizeMode = true is set below if (maximizeMode() == MaximizeFull) { // partial is cond. reset in finishMoveResize geom_restore = geometry(); // "restore" to current geometry setMaximize(false, false); } } else if (quick_tile_mode != QuickTileNone) // no longer now - we move, resize is handled below setQuickTileMode(QuickTileNone); // otherwise we mess every second tile, bug #303937 } else if ((maximizeMode() == MaximizeFull && options->electricBorderMaximize()) || (quick_tile_mode != QuickTileNone && isMovable() && mode == PositionCenter)) { // Exit quick tile mode when the user attempts to move a tiled window, cannot use isMove() yet const QRect before = geometry(); setQuickTileMode(QuickTileNone); // Move the window so it's under the cursor moveOffset = QPoint(double(moveOffset.x()) / double(before.width()) * double(geom_restore.width()), double(moveOffset.y()) / double(before.height()) * double(geom_restore.height())); fakeMove = true; } if (quick_tile_mode != QuickTileNone && mode != PositionCenter) { // Cannot use isResize() yet // Exit quick tile mode when the user attempts to resize a tiled window quick_tile_mode = QuickTileNone; // Do so without restoring original geometry } s_haveResizeEffect = effects && static_cast(effects)->provides(Effect::Resize); moveResizeStartScreen = screen(); initialMoveResizeGeom = moveResizeGeom = geometry(); checkUnrestrictedMoveResize(); Notify::raise(isResize() ? Notify::ResizeStart : Notify::MoveStart); emit clientStartUserMovedResized(this); #ifdef KWIN_BUILD_SCREENEDGES if (options->electricBorders() == Options::ElectricMoveOnly || options->electricBorderMaximize() || options->electricBorderTiling()) workspace()->screenEdge()->reserveDesktopSwitching(true, Qt::Vertical|Qt::Horizontal); #endif if (fakeMove) // fix geom_restore position - it HAS to happen at the end, ie. when all moving is set up. inline call will lock focus!! handleMoveResize(QCursor::pos().x(), QCursor::pos().y(), QCursor::pos().x(), QCursor::pos().y()); return true; } static ElectricBorder electricBorderFromMode(QuickTileMode mode) { // special case, currently maxmizing is done from the electric top corner if (mode == QuickTileMaximize) return ElectricTop; // sanitize the mode, ie. simplify "invalid" combinations if ((mode & QuickTileHorizontal) == QuickTileHorizontal) mode &= ~QuickTileHorizontal; if ((mode & QuickTileVertical) == QuickTileVertical) mode &= ~QuickTileVertical; if (mode == QuickTileLeft) return ElectricLeft; if (mode == QuickTileRight) return ElectricRight; if (mode == (QuickTileTop|QuickTileLeft)) return ElectricTopLeft; if (mode == (QuickTileTop|QuickTileRight)) return ElectricTopRight; if (mode == (QuickTileBottom|QuickTileLeft)) return ElectricBottomLeft; if (mode == (QuickTileBottom|QuickTileRight)) return ElectricBottomRight; if (mode == QuickTileTop) return ElectricTop; if (mode == QuickTileBottom) return ElectricBottom; return ElectricNone; } void Client::finishMoveResize(bool cancel) { const bool wasResize = isResize(); // store across leaveMoveResize - const bool wasMove = isMove(); leaveMoveResize(); if (cancel) setGeometry(initialMoveResizeGeom); else { if (wasResize) { const bool restoreH = maximizeMode() == MaximizeHorizontal && moveResizeGeom.width() != initialMoveResizeGeom.width(); const bool restoreV = maximizeMode() == MaximizeVertical && moveResizeGeom.height() != initialMoveResizeGeom.height(); if (restoreH || restoreV) // NOT setMaximize(restoreH, restoreV); ! setMaximize(false, false); } setGeometry(moveResizeGeom); } - if (screen() != moveResizeStartScreen && maximizeMode() != MaximizeRestore) - checkWorkspacePosition(); + const int newScreen = screen(); + if (newScreen != moveResizeStartScreen) { + workspace()->sendClientToScreen(this, newScreen); // checks rule validity + if (maximizeMode() != MaximizeRestore) + checkWorkspacePosition(); + } if (isElectricBorderMaximizing()) { setQuickTileMode(electricMode); const ElectricBorder border = electricBorderFromMode(electricMode); if (border == ElectricNone) kDebug(1212) << "invalid electric mode" << electricMode << "leading to invalid array access,\ this should not have happened!"; #ifdef KWIN_BUILD_SCREENEDGES else workspace()->screenEdge()->restoreSize(border); #endif electricMaximizing = false; workspace()->outline()->hide(); } else if (!cancel) { if (!(maximizeMode() & MaximizeHorizontal)) { geom_restore.setX(geometry().x()); geom_restore.setWidth(geometry().width()); } if (!(maximizeMode() & MaximizeVertical)) { geom_restore.setY(geometry().y()); geom_restore.setHeight(geometry().height()); } } // FRAME update(); Notify::raise(isResize() ? Notify::ResizeEnd : Notify::MoveEnd); emit clientFinishUserMovedResized(this); } void Client::leaveMoveResize() { if (geometryTip) { geometryTip->hide(); delete geometryTip; geometryTip = NULL; } if (move_resize_has_keyboard_grab) ungrabXKeyboard(); move_resize_has_keyboard_grab = false; XUngrabPointer(display(), xTime()); XDestroyWindow(display(), move_resize_grab_window); move_resize_grab_window = None; workspace()->setClientIsMoving(0); moveResizeMode = false; #ifdef HAVE_XSYNC delete syncRequest.timeout; syncRequest.timeout = NULL; #endif #ifdef KWIN_BUILD_SCREENEDGES if (options->electricBorders() == Options::ElectricMoveOnly || options->electricBorderMaximize() || options->electricBorderTiling()) workspace()->screenEdge()->reserveDesktopSwitching(false, Qt::Vertical|Qt::Horizontal); #endif } // This function checks if it actually makes sense to perform a restricted move/resize. // If e.g. the titlebar is already outside of the workarea, there's no point in performing // a restricted move resize, because then e.g. resize would also move the window (#74555). // NOTE: Most of it is duplicated from handleMoveResize(). void Client::checkUnrestrictedMoveResize() { if (unrestrictedMoveResize) return; QRect desktopArea = workspace()->clientArea(WorkArea, moveResizeGeom.center(), desktop()); int left_marge, right_marge, top_marge, bottom_marge, titlebar_marge; // restricted move/resize - keep at least part of the titlebar always visible // how much must remain visible when moved away in that direction left_marge = qMin(100 + border_right, moveResizeGeom.width()); right_marge = qMin(100 + border_left, moveResizeGeom.width()); // width/height change with opaque resizing, use the initial ones titlebar_marge = initialMoveResizeGeom.height(); top_marge = border_bottom; bottom_marge = border_top; if (isResize()) { if (moveResizeGeom.bottom() < desktopArea.top() + top_marge) unrestrictedMoveResize = true; if (moveResizeGeom.top() > desktopArea.bottom() - bottom_marge) unrestrictedMoveResize = true; if (moveResizeGeom.right() < desktopArea.left() + left_marge) unrestrictedMoveResize = true; if (moveResizeGeom.left() > desktopArea.right() - right_marge) unrestrictedMoveResize = true; if (!unrestrictedMoveResize && moveResizeGeom.top() < desktopArea.top()) // titlebar mustn't go out unrestrictedMoveResize = true; } if (isMove()) { if (moveResizeGeom.bottom() < desktopArea.top() + titlebar_marge - 1) unrestrictedMoveResize = true; // no need to check top_marge, titlebar_marge already handles it if (moveResizeGeom.top() > desktopArea.bottom() - bottom_marge + 1) // titlebar mustn't go out unrestrictedMoveResize = true; if (moveResizeGeom.right() < desktopArea.left() + left_marge) unrestrictedMoveResize = true; if (moveResizeGeom.left() > desktopArea.right() - right_marge) unrestrictedMoveResize = true; } } // When the user pressed mouse on the titlebar, don't activate move immediatelly, // since it may be just a click. Activate instead after a delay. Move used to be // activated only after moving by several pixels, but that looks bad. void Client::startDelayedMoveResize() { delete delayedMoveResizeTimer; delayedMoveResizeTimer = new QTimer(this); connect(delayedMoveResizeTimer, SIGNAL(timeout()), this, SLOT(delayedMoveResize())); delayedMoveResizeTimer->setSingleShot(true); delayedMoveResizeTimer->start(QApplication::startDragTime()); } void Client::stopDelayedMoveResize() { delete delayedMoveResizeTimer; delayedMoveResizeTimer = NULL; } void Client::delayedMoveResize() { assert(buttonDown); if (!startMoveResize()) buttonDown = false; updateCursor(); stopDelayedMoveResize(); } void Client::handleMoveResize(int x, int y, int x_root, int y_root) { #ifdef HAVE_XSYNC if (syncRequest.isPending && isResize()) return; // we're still waiting for the client or the timeout #endif if ((mode == PositionCenter && !isMovableAcrossScreens()) || (mode != PositionCenter && (isShade() || !isResizable()))) return; if (!moveResizeMode) { QPoint p(QPoint(x - padding_left, y - padding_top) - moveOffset); if (p.manhattanLength() >= KGlobalSettings::dndEventDelay()) { if (!startMoveResize()) { buttonDown = false; updateCursor(); return; } updateCursor(); } else return; } // ShadeHover or ShadeActive, ShadeNormal was already avoided above if (mode != PositionCenter && shade_mode != ShadeNone) setShade(ShadeNone); QPoint globalPos(x_root, y_root); // these two points limit the geometry rectangle, i.e. if bottomleft resizing is done, // the bottomleft corner should be at is at (topleft.x(), bottomright().y()) QPoint topleft = globalPos - moveOffset; QPoint bottomright = globalPos + invertedMoveOffset; QRect previousMoveResizeGeom = moveResizeGeom; // TODO move whole group when moving its leader or when the leader is not mapped? // When doing a restricted move we must always keep 100px of the titlebar // visible to allow the user to be able to move it again. int frameLeft, frameRight, frameTop, frameBottom; if (decoration) decoration->borders(frameLeft, frameRight, frameTop, frameBottom); else frameTop = 10; int titlebarArea = qMin(frameTop * 100, moveResizeGeom.width() * moveResizeGeom.height()); bool update = false; if (isResize()) { // first resize (without checking constrains), then snap, then check bounds, then check constrains QRect orig = initialMoveResizeGeom; Sizemode sizemode = SizemodeAny; switch(mode) { case PositionTopLeft: moveResizeGeom = QRect(topleft, orig.bottomRight()) ; break; case PositionBottomRight: moveResizeGeom = QRect(orig.topLeft(), bottomright) ; break; case PositionBottomLeft: moveResizeGeom = QRect(QPoint(topleft.x(), orig.y()), QPoint(orig.right(), bottomright.y())) ; break; case PositionTopRight: moveResizeGeom = QRect(QPoint(orig.x(), topleft.y()), QPoint(bottomright.x(), orig.bottom())) ; break; case PositionTop: moveResizeGeom = QRect(QPoint(orig.left(), topleft.y()), orig.bottomRight()) ; sizemode = SizemodeFixedH; // try not to affect height break; case PositionBottom: moveResizeGeom = QRect(orig.topLeft(), QPoint(orig.right(), bottomright.y())) ; sizemode = SizemodeFixedH; break; case PositionLeft: moveResizeGeom = QRect(QPoint(topleft.x(), orig.top()), orig.bottomRight()) ; sizemode = SizemodeFixedW; break; case PositionRight: moveResizeGeom = QRect(orig.topLeft(), QPoint(bottomright.x(), orig.bottom())) ; sizemode = SizemodeFixedW; break; case PositionCenter: default: abort(); break; } // adjust new size to snap to other windows/borders moveResizeGeom = workspace()->adjustClientSize(this, moveResizeGeom, mode); if (!unrestrictedMoveResize) { // Make sure the titlebar isn't behind a restricted area. We don't need to restrict // the other directions. If not visible enough, move the window to the closest valid // point. We bruteforce this by slowly moving the window back to its previous position. for (;;) { QRegion titlebarRegion(moveResizeGeom.left(), moveResizeGeom.top(), moveResizeGeom.width(), frameTop); titlebarRegion &= workspace()->clientArea(FullArea, -1, 0); // On the screen titlebarRegion -= workspace()->restrictedMoveArea(desktop()); // Strut areas // Now we have a region of all the visible areas of the titlebar // Count the visible pixels and check to see if it's enough int visiblePixels = 0; foreach (const QRect & rect, titlebarRegion.rects()) if (rect.height() >= frameTop) // Only the full height regions, prevents long slim areas visiblePixels += rect.width() * rect.height(); if (visiblePixels >= titlebarArea) break; // We have reached a valid position // Not visible enough, move the window to the closest valid point. We bruteforce // this by slowly moving the window back to its previous position. if (previousMoveResizeGeom.y() != moveResizeGeom.y()) { if (previousMoveResizeGeom.y() > moveResizeGeom.y()) moveResizeGeom.setTop(moveResizeGeom.y() + 1); else moveResizeGeom.setTop(moveResizeGeom.y() - 1); } else { // Our heights match but we still don't have a valid area, maybe // we are trying to resize in from the side? bool breakLoop = false; switch(mode) { case PositionTopLeft: case PositionLeft: if (previousMoveResizeGeom.x() >= moveResizeGeom.x()) { breakLoop = true; break; } moveResizeGeom.setLeft(moveResizeGeom.x() - 1); break; case PositionTopRight: case PositionRight: if (previousMoveResizeGeom.right() <= moveResizeGeom.right()) { breakLoop = true; break; } moveResizeGeom.setRight(moveResizeGeom.x() + moveResizeGeom.width()); break; default: breakLoop = true; } if (breakLoop) break; } } } // Always obey size hints, even when in "unrestricted" mode QSize size = adjustedSize(moveResizeGeom.size(), sizemode); // the new topleft and bottomright corners (after checking size constrains), if they'll be needed topleft = QPoint(moveResizeGeom.right() - size.width() + 1, moveResizeGeom.bottom() - size.height() + 1); bottomright = QPoint(moveResizeGeom.left() + size.width() - 1, moveResizeGeom.top() + size.height() - 1); orig = moveResizeGeom; switch(mode) { // these 4 corners ones are copied from above case PositionTopLeft: moveResizeGeom = QRect(topleft, orig.bottomRight()) ; break; case PositionBottomRight: moveResizeGeom = QRect(orig.topLeft(), bottomright) ; break; case PositionBottomLeft: moveResizeGeom = QRect(QPoint(topleft.x(), orig.y()), QPoint(orig.right(), bottomright.y())) ; break; case PositionTopRight: moveResizeGeom = QRect(QPoint(orig.x(), topleft.y()), QPoint(bottomright.x(), orig.bottom())) ; break; // The side ones can't be copied exactly - if aspect ratios are specified, both dimensions may change. // Therefore grow to the right/bottom if needed. // TODO it should probably obey gravity rather than always using right/bottom ? case PositionTop: moveResizeGeom = QRect(QPoint(orig.left(), topleft.y()), QPoint(bottomright.x(), orig.bottom())) ; break; case PositionBottom: moveResizeGeom = QRect(orig.topLeft(), QPoint(bottomright.x(), bottomright.y())) ; break; case PositionLeft: moveResizeGeom = QRect(QPoint(topleft.x(), orig.top()), QPoint(orig.right(), bottomright.y())); break; case PositionRight: moveResizeGeom = QRect(orig.topLeft(), QPoint(bottomright.x(), bottomright.y())) ; break; case PositionCenter: default: abort(); break; } if (moveResizeGeom.size() != previousMoveResizeGeom.size()) update = true; } else if (isMove()) { assert(mode == PositionCenter); if (!isMovable()) { // isMovableAcrossScreens() must have been true to get here // Special moving of maximized windows on Xinerama screens int screen = workspace()->screenNumber(globalPos); if (isFullScreen()) moveResizeGeom = workspace()->clientArea(FullScreenArea, screen, 0); else { moveResizeGeom = workspace()->clientArea(MaximizeArea, screen, 0); QSize adjSize = adjustedSize(moveResizeGeom.size(), SizemodeMax); if (adjSize != moveResizeGeom.size()) { QRect r(moveResizeGeom); moveResizeGeom.setSize(adjSize); moveResizeGeom.moveCenter(r.center()); } } } else { // first move, then snap, then check bounds moveResizeGeom.moveTopLeft(topleft); moveResizeGeom.moveTopLeft(workspace()->adjustClientPosition(this, moveResizeGeom.topLeft(), unrestrictedMoveResize)); if (!unrestrictedMoveResize) { // Make sure the titlebar isn't behind a restricted area. const QRegion fullArea = workspace()->clientArea(FullArea, this); // On the screen const QRegion strut = workspace()->restrictedMoveArea(desktop()); // Strut areas for (;;) { QRegion titlebarRegion(moveResizeGeom.left(), moveResizeGeom.top(), moveResizeGeom.width(), frameTop); titlebarRegion &= fullArea; titlebarRegion -= strut; // Strut areas // Now we have a region of all the visible areas of the titlebar // Count the visible pixels and check to see if it's enough int visiblePixels = 0; foreach (const QRect & rect, titlebarRegion.rects()) if (rect.height() >= frameTop) // Only the full height regions, prevents long slim areas visiblePixels += rect.width() * rect.height(); if (visiblePixels >= titlebarArea) break; // We have reached a valid position // (esp.) if there're more screens with different struts (panels) it the titlebar // will be movable outside the movearea (covering one of the panels) until it // crosses the panel "too much" (not enough visiblePixels) and then stucks because // it's usually only pushed by 1px to either direction // so we first check whether we intersect suc strut and move the window below it // immediately (it's still possible to hit the visiblePixels >= titlebarArea break // by moving the window slightly downwards, but it won't stuck) // see bug #274466 // and bug #301805 for why we can't just match the titlearea against the screen if (QApplication::desktop()->screenCount() > 1) { // optimization // TODO: could be useful on partial screen struts (half-width panels etc.) int newTitleTop = -1; foreach (const QRect &r, strut.rects()) { if (r.top() == 0 && r.width() > r.height() && // "top panel" r.intersects(moveResizeGeom) && moveResizeGeom.top() < r.bottom()) { newTitleTop = r.bottom(); break; } } if (newTitleTop > -1) { moveResizeGeom.moveTop(newTitleTop); // invalid position, possibly on screen change break; } } // Move it (Favour vertically) if (previousMoveResizeGeom.y() != moveResizeGeom.y()) moveResizeGeom.translate(0, previousMoveResizeGeom.y() > moveResizeGeom.y() ? 1 : -1); else moveResizeGeom.translate(previousMoveResizeGeom.x() > moveResizeGeom.x() ? 1 : -1, 0); if (moveResizeGeom == previousMoveResizeGeom) { break; // Prevent lockup } } } } if (moveResizeGeom.topLeft() != previousMoveResizeGeom.topLeft()) update = true; } else abort(); if (!update) return; #ifdef HAVE_XSYNC if (isResize() && syncRequest.counter != None && !s_haveResizeEffect) { if (!syncRequest.timeout) { syncRequest.timeout = new QTimer(this); connect(syncRequest.timeout, SIGNAL(timeout()), SLOT(performMoveResize())); syncRequest.timeout->setSingleShot(true); } syncRequest.timeout->start(250); sendSyncRequest(); XMoveResizeWindow(display(), window(), 0, 0, moveResizeGeom.width() - (border_left + border_right), moveResizeGeom.height() - (border_top + border_bottom)); } else #endif performMoveResize(); if (isMove()) { #ifdef KWIN_BUILD_SCREENEDGES workspace()->screenEdge()->check(globalPos, xTime()); #endif } } void Client::performMoveResize() { if (isMove() || (isResize() && !s_haveResizeEffect)) { setGeometry(moveResizeGeom); } #ifdef HAVE_XSYNC if (isResize() && syncRequest.counter != None) addRepaintFull(); #endif positionGeometryTip(); emit clientStepUserMovedResized(this, moveResizeGeom); } void Client::setElectricBorderMode(QuickTileMode mode) { if (mode != QuickTileMaximize) { // sanitize the mode, ie. simplify "invalid" combinations if ((mode & QuickTileHorizontal) == QuickTileHorizontal) mode &= ~QuickTileHorizontal; if ((mode & QuickTileVertical) == QuickTileVertical) mode &= ~QuickTileVertical; } electricMode = mode; } QuickTileMode Client::electricBorderMode() const { return electricMode; } bool Client::isElectricBorderMaximizing() const { return electricMaximizing; } void Client::setElectricBorderMaximizing(bool maximizing) { electricMaximizing = maximizing; if (maximizing) workspace()->outline()->show(electricBorderMaximizeGeometry(cursorPos(), desktop())); else workspace()->outline()->hide(); } QRect Client::electricBorderMaximizeGeometry(QPoint pos, int desktop) { if (electricMode == QuickTileMaximize) { if (maximizeMode() == MaximizeFull) return geometryRestore(); else return workspace()->clientArea(MaximizeArea, pos, desktop); } QRect ret = workspace()->clientArea(MaximizeArea, pos, desktop); if (electricMode & QuickTileLeft) ret.setRight(ret.left()+ret.width()/2 - 1); else if (electricMode & QuickTileRight) ret.setLeft(ret.right()-(ret.width()-ret.width()/2) + 1); if (electricMode & QuickTileTop) ret.setBottom(ret.top()+ret.height()/2 - 1); else if (electricMode & QuickTileBottom) ret.setTop(ret.bottom()-(ret.height()-ret.height()/2) + 1); return ret; } void Client::setQuickTileMode(QuickTileMode mode, bool keyboard) { // Only allow quick tile on a regular or maximized window if (!isResizable() && maximizeMode() != MaximizeFull) return; if (mode == QuickTileMaximize) { TabSynchronizer syncer(this, TabGroup::QuickTile|TabGroup::Geometry|TabGroup::Maximized); quick_tile_mode = QuickTileNone; if (maximizeMode() == MaximizeFull) setMaximize(false, false); else { setMaximize(true, true); quick_tile_mode = QuickTileMaximize; } return; } // sanitize the mode, ie. simplify "invalid" combinations if ((mode & QuickTileHorizontal) == QuickTileHorizontal) mode &= ~QuickTileHorizontal; if ((mode & QuickTileVertical) == QuickTileVertical) mode &= ~QuickTileVertical; setElectricBorderMode(mode); // used by ::electricBorderMaximizeGeometry(.) // restore from maximized so that it is possible to tile maximized windows with one hit or by dragging if (maximizeMode() == MaximizeFull) { TabSynchronizer syncer(this, TabGroup::QuickTile|TabGroup::Geometry|TabGroup::Maximized); setMaximize(false, false); // Temporary, so the maximize code doesn't get all confused quick_tile_mode = QuickTileNone; if (mode != QuickTileNone) setGeometry(electricBorderMaximizeGeometry(keyboard ? geometry().center() : cursorPos(), desktop())); // Store the mode change quick_tile_mode = mode; return; } // First, check if the requested tile negates the tile we're in now: move right when left or left when right // is the same as explicitly untiling this window, so allow it. if (mode == QuickTileNone || ((quick_tile_mode & QuickTileHorizontal) && (mode & QuickTileHorizontal))) { TabSynchronizer syncer(this, TabGroup::QuickTile|TabGroup::Geometry); quick_tile_mode = QuickTileNone; // Untiling, so just restore geometry, and we're done. if (!geom_restore.isValid()) // invalid if we started maximized and wait for placement geom_restore = geometry(); setGeometry(geom_restore); checkWorkspacePosition(); // Just in case it's a different screen return; } else { TabSynchronizer syncer(this, TabGroup::QuickTile|TabGroup::Geometry); QPoint whichScreen = keyboard ? geometry().center() : cursorPos(); // If trying to tile to the side that the window is already tiled to move the window to the next // screen if it exists, otherwise ignore the request to prevent corrupting geom_restore. if (quick_tile_mode == mode) { const int numScreens = QApplication::desktop()->screenCount(); const int curScreen = screen(); int nextScreen = curScreen; QVarLengthArray screens(numScreens); for (int i = 0; i < numScreens; ++i) // Cache screens[i] = QApplication::desktop()->screenGeometry(i); for (int i = 0; i < numScreens; ++i) { if (i == curScreen) continue; if (((mode == QuickTileLeft && screens[i].center().x() < screens[nextScreen].center().x()) || (mode == QuickTileRight && screens[i].center().x() > screens[nextScreen].center().x())) && // Must be in horizontal line (screens[i].bottom() > screens[nextScreen].top() || screens[i].top() < screens[nextScreen].bottom())) nextScreen = i; } if (nextScreen == curScreen) return; // No other screens // Move to other screen geom_restore.translate( screens[nextScreen].x() - screens[curScreen].x(), screens[nextScreen].y() - screens[curScreen].y()); whichScreen = screens[nextScreen].center(); // Swap sides if (mode == QuickTileLeft) mode = QuickTileRight; else mode = QuickTileLeft; } else { // Not coming out of an existing tile, not shifting monitors, we're setting a brand new tile. // Store geometry first, so we can go out of this tile later. geom_restore = geometry(); } // Temporary, so the maximize code doesn't get all confused quick_tile_mode = QuickTileNone; if (mode != QuickTileNone) setGeometry(electricBorderMaximizeGeometry(whichScreen, desktop())); // Store the mode change quick_tile_mode = mode; } } } // namespace diff --git a/kcmkwin/kwinrules/ruleswidget.cpp b/kcmkwin/kwinrules/ruleswidget.cpp index 5c1ee928e..264fc9455 100644 --- a/kcmkwin/kwinrules/ruleswidget.cpp +++ b/kcmkwin/kwinrules/ruleswidget.cpp @@ -1,859 +1,867 @@ /* * Copyright (c) 2004 Lubos Lunak * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ruleswidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../../rules.h" #include "detectwidget.h" namespace KWin { #define SETUP( var, type ) \ connect( enable_##var, SIGNAL(toggled(bool)), rule_##var, SLOT(setEnabled(bool))); \ connect( enable_##var, SIGNAL(toggled(bool)), this, SLOT(updateEnable##var())); \ connect( rule_##var, SIGNAL(activated(int)), this, SLOT(updateEnable##var())); \ enable_##var->setWhatsThis( enableDesc ); \ rule_##var->setWhatsThis( type##RuleDesc ); RulesWidget::RulesWidget(QWidget* parent) : detect_dlg(NULL) { Q_UNUSED(parent); setupUi(this); QString enableDesc = i18n("Enable this checkbox to alter this window property for the specified window(s)."); QString setRuleDesc = i18n("Specify how the window property should be affected:
    " "
  • Do Not Affect: The window property will not be affected and therefore" " the default handling for it will be used. Specifying this will block more generic" " window settings from taking effect.
  • " "
  • Apply Initially: The window property will be only set to the given value" " after the window is created. No further changes will be affected.
  • " "
  • Remember: The value of the window property will be remembered and every" " time the window is created, the last remembered value will be applied.
  • " "
  • Force: The window property will be always forced to the given value.
  • " "
  • Apply Now: The window property will be set to the given value immediately" " and will not be affected later (this action will be deleted afterwards).
  • " "
  • Force temporarily: The window property will be forced to the given value" " until it is hidden (this action will be deleted after the window is hidden).
  • " "
"); QString forceRuleDesc = i18n("Specify how the window property should be affected:
    " "
  • Do Not Affect: The window property will not be affected and therefore" " the default handling for it will be used. Specifying this will block more generic" " window settings from taking effect.
  • " "
  • Force: The window property will be always forced to the given value.
  • " "
  • Force temporarily: The window property will be forced to the given value" " until it is hidden (this action will be deleted after the window is hidden).
  • " "
"); // window tabs have enable signals done in designer // geometry tab SETUP(position, set); SETUP(size, set); SETUP(desktop, set); + SETUP(screen, set); SETUP(activity, set); SETUP(maximizehoriz, set); SETUP(maximizevert, set); SETUP(minimize, set); SETUP(shade, set); SETUP(fullscreen, set); SETUP(placement, force); // preferences tab SETUP(above, set); SETUP(below, set); SETUP(noborder, set); SETUP(skiptaskbar, set); SETUP(skippager, set); SETUP(skipswitcher, set); SETUP(acceptfocus, force); SETUP(closeable, force); SETUP(autogroup, force); SETUP(autogroupfg, force); SETUP(autogroupid, force); SETUP(opacityactive, force); SETUP(opacityinactive, force); SETUP(shortcut, force); // workarounds tab SETUP(fsplevel, force); SETUP(type, force); SETUP(ignoreposition, force); SETUP(minsize, force); SETUP(maxsize, force); SETUP(strictgeometry, force); SETUP(disableglobalshortcuts, force); SETUP(blockcompositing, force); connect (machine_match, SIGNAL(currentIndexChanged(int)), SLOT(machineMatchChanged())); connect (shortcut_edit, SIGNAL(clicked()), SLOT(shortcutEditClicked())); edit_reg_wmclass->hide(); edit_reg_role->hide(); edit_reg_title->hide(); edit_reg_machine->hide(); int i; for (i = 1; i <= KWindowSystem::numberOfDesktops(); ++i) desktop->addItem(QString::number(i).rightJustified(2) + ':' + KWindowSystem::desktopName(i)); desktop->addItem(i18n("All Desktops")); static KActivities::Consumer activities; foreach (const QString & activityId, activities.listActivities()) { activity->addItem(KActivities::Info::name(activityId), activityId); } activity->addItem(i18n("All Activities"), QString::fromLatin1("ALL")); } #undef SETUP #define UPDATE_ENABLE_SLOT(var) \ void RulesWidget::updateEnable##var() \ { \ /* leave the label readable label_##var->setEnabled( enable_##var->isChecked() && rule_##var->currentIndex() != 0 );*/ \ Ui::RulesWidgetBase::var->setEnabled( enable_##var->isChecked() && rule_##var->currentIndex() != 0 ); \ } // geometry tab UPDATE_ENABLE_SLOT(position) UPDATE_ENABLE_SLOT(size) UPDATE_ENABLE_SLOT(desktop) +UPDATE_ENABLE_SLOT(screen) UPDATE_ENABLE_SLOT(activity) UPDATE_ENABLE_SLOT(maximizehoriz) UPDATE_ENABLE_SLOT(maximizevert) UPDATE_ENABLE_SLOT(minimize) UPDATE_ENABLE_SLOT(shade) UPDATE_ENABLE_SLOT(fullscreen) UPDATE_ENABLE_SLOT(placement) // preferences tab UPDATE_ENABLE_SLOT(above) UPDATE_ENABLE_SLOT(below) UPDATE_ENABLE_SLOT(noborder) UPDATE_ENABLE_SLOT(skiptaskbar) UPDATE_ENABLE_SLOT(skippager) UPDATE_ENABLE_SLOT(skipswitcher) UPDATE_ENABLE_SLOT(acceptfocus) UPDATE_ENABLE_SLOT(closeable) UPDATE_ENABLE_SLOT(autogroup) UPDATE_ENABLE_SLOT(autogroupfg) UPDATE_ENABLE_SLOT(autogroupid) UPDATE_ENABLE_SLOT(opacityactive) UPDATE_ENABLE_SLOT(opacityinactive) void RulesWidget::updateEnableshortcut() { shortcut->setEnabled(enable_shortcut->isChecked() && rule_shortcut->currentIndex() != 0); shortcut_edit->setEnabled(enable_shortcut->isChecked() && rule_shortcut->currentIndex() != 0); } // workarounds tab UPDATE_ENABLE_SLOT(fsplevel) UPDATE_ENABLE_SLOT(type) UPDATE_ENABLE_SLOT(ignoreposition) UPDATE_ENABLE_SLOT(minsize) UPDATE_ENABLE_SLOT(maxsize) UPDATE_ENABLE_SLOT(strictgeometry) UPDATE_ENABLE_SLOT(disableglobalshortcuts) UPDATE_ENABLE_SLOT(blockcompositing) #undef UPDATE_ENABLE_SLOT static const int set_rule_to_combo[] = { 0, // Unused 0, // Don't Affect 3, // Force 1, // Apply 2, // Remember 4, // ApplyNow 5 // ForceTemporarily }; static const Rules::SetRule combo_to_set_rule[] = { (Rules::SetRule)Rules::DontAffect, (Rules::SetRule)Rules::Apply, (Rules::SetRule)Rules::Remember, (Rules::SetRule)Rules::Force, (Rules::SetRule)Rules::ApplyNow, (Rules::SetRule)Rules::ForceTemporarily }; static const int force_rule_to_combo[] = { 0, // Unused 0, // Don't Affect 1, // Force 0, // Apply 0, // Remember 0, // ApplyNow 2 // ForceTemporarily }; static const Rules::ForceRule combo_to_force_rule[] = { (Rules::ForceRule)Rules::DontAffect, (Rules::ForceRule)Rules::Force, (Rules::ForceRule)Rules::ForceTemporarily }; static QString positionToStr(const QPoint& p) { if (p == invalidPoint) return QString(); return QString::number(p.x()) + ',' + QString::number(p.y()); } static QPoint strToPosition(const QString& str) { // two numbers, with + or -, separated by any of , x X : QRegExp reg("\\s*([+-]?[0-9]*)\\s*[,xX:]\\s*([+-]?[0-9]*)\\s*"); if (!reg.exactMatch(str)) return invalidPoint; return QPoint(reg.cap(1).toInt(), reg.cap(2).toInt()); } static QString sizeToStr(const QSize& s) { if (!s.isValid()) return QString(); return QString::number(s.width()) + ',' + QString::number(s.height()); } static QSize strToSize(const QString& str) { // two numbers, with + or -, separated by any of , x X : QRegExp reg("\\s*([+-]?[0-9]*)\\s*[,xX:]\\s*([+-]?[0-9]*)\\s*"); if (!reg.exactMatch(str)) return QSize(); return QSize(reg.cap(1).toInt(), reg.cap(2).toInt()); } //used for opacity settings static QString intToStr(const int& s) { if (s < 1 || s > 100) return QString(); return QString::number(s); } static int strToInt(const QString& str) { int tmp = str.toInt(); if (tmp < 1 || tmp > 100) return 100; return tmp; } int RulesWidget::desktopToCombo(int d) const { if (d >= 1 && d < desktop->count()) return d - 1; return desktop->count() - 1; // on all desktops } int RulesWidget::comboToDesktop(int val) const { if (val == desktop->count() - 1) return NET::OnAllDesktops; return val + 1; } int RulesWidget::activityToCombo(QString d) const { // TODO: ivan - do a multiselection list for (int i = 0; i < activity->count(); i++) { if (activity->itemData(i).toString() == d) { return i; } } return activity->count() - 1; // on all activities } QString RulesWidget::comboToActivity(int val) const { // TODO: ivan - do a multiselection list if (val < 0 || val >= activity->count()) return QString(); return activity->itemData(val).toString(); } static int placementToCombo(Placement::Policy placement) { static const int conv[] = { 1, // NoPlacement 0, // Default 0, // Unknown 6, // Random 2, // Smart 4, // Cascade 5, // Centered 7, // ZeroCornered 8, // UnderMouse 9, // OnMainWindow 3 // Maximizing }; return conv[ placement ]; } static Placement::Policy comboToPlacement(int val) { static const Placement::Policy conv[] = { Placement::Default, Placement::NoPlacement, Placement::Smart, Placement::Maximizing, Placement::Cascade, Placement::Centered, Placement::Random, Placement::ZeroCornered, Placement::UnderMouse, Placement::OnMainWindow // no Placement::Unknown }; return conv[ val ]; } static int typeToCombo(NET::WindowType type) { if (type < NET::Normal || type > NET::Splash || type == NET::Override) // The user must NOT set a window to be unmanaged. // This case is not handled in KWin and will lead to segfaults. // Even iff it was supported, it would mean to allow the user to shoot himself // since an unmanaged window has to manage itself, what is probably not the case when the hint is not set. // Rule opportunity might be a relict from the Motif Hint window times of KDE1 return 0; // Normal static const int conv[] = { 0, // Normal 7, // Desktop 3, // Dock 4, // Toolbar 5, // Menu 1, // Dialog 8, // Override - ignored. 9, // TopMenu 2, // Utility 6 // Splash }; return conv[ type ]; } static NET::WindowType comboToType(int val) { static const NET::WindowType conv[] = { NET::Normal, NET::Dialog, NET::Utility, NET::Dock, NET::Toolbar, NET::Menu, NET::Splash, NET::Desktop, NET::TopMenu }; return conv[ val ]; } #define GENERIC_RULE( var, func, Type, type, uimethod, uimethod0 ) \ if ( rules->var##rule == Rules::Unused##Type##Rule ) \ { \ enable_##var->setChecked( false ); \ rule_##var->setCurrentIndex( 0 ); \ Ui::RulesWidgetBase::var->uimethod0; \ updateEnable##var(); \ } \ else \ { \ enable_##var->setChecked( true ); \ rule_##var->setCurrentIndex( type##_rule_to_combo[ rules->var##rule ] ); \ Ui::RulesWidgetBase::var->uimethod( func( rules->var )); \ updateEnable##var(); \ } #define CHECKBOX_SET_RULE( var, func ) GENERIC_RULE( var, func, Set, set, setChecked, setChecked( false )) #define LINEEDIT_SET_RULE( var, func ) GENERIC_RULE( var, func, Set, set, setText, setText( "" )) #define COMBOBOX_SET_RULE( var, func ) GENERIC_RULE( var, func, Set, set, setCurrentIndex, setCurrentIndex( 0 )) +#define SPINBOX_SET_RULE( var, func ) GENERIC_RULE( var, func, Set, set, setValue, setValue(0)) #define CHECKBOX_FORCE_RULE( var, func ) GENERIC_RULE( var, func, Force, force, setChecked, setChecked( false )) #define LINEEDIT_FORCE_RULE( var, func ) GENERIC_RULE( var, func, Force, force, setText, setText( "" )) #define COMBOBOX_FORCE_RULE( var, func ) GENERIC_RULE( var, func, Force, force, setCurrentIndex, setCurrentIndex( 0 )) #define SPINBOX_FORCE_RULE( var, func ) GENERIC_RULE( var, func, Force, force, setValue, setValue(0)) void RulesWidget::setRules(Rules* rules) { Rules tmp; if (rules == NULL) rules = &tmp; // empty description->setText(rules->description); wmclass->setText(rules->wmclass); whole_wmclass->setChecked(rules->wmclasscomplete); wmclass_match->setCurrentIndex(rules->wmclassmatch); wmclassMatchChanged(); role->setText(rules->windowrole); role_match->setCurrentIndex(rules->windowrolematch); roleMatchChanged(); types->item(0)->setSelected(rules->types & NET::NormalMask); types->item(1)->setSelected(rules->types & NET::DialogMask); types->item(2)->setSelected(rules->types & NET::UtilityMask); types->item(3)->setSelected(rules->types & NET::DockMask); types->item(4)->setSelected(rules->types & NET::ToolbarMask); types->item(5)->setSelected(rules->types & NET::MenuMask); types->item(6)->setSelected(rules->types & NET::SplashMask); types->item(7)->setSelected(rules->types & NET::DesktopMask); types->item(8)->setSelected(rules->types & NET::OverrideMask); types->item(9)->setSelected(rules->types & NET::TopMenuMask); title->setText(rules->title); title_match->setCurrentIndex(rules->titlematch); titleMatchChanged(); machine->setText(rules->clientmachine); machine_match->setCurrentIndex(rules->clientmachinematch); machineMatchChanged(); LINEEDIT_SET_RULE(position, positionToStr); LINEEDIT_SET_RULE(size, sizeToStr); COMBOBOX_SET_RULE(desktop, desktopToCombo); + SPINBOX_SET_RULE(screen, inc); COMBOBOX_SET_RULE(activity, activityToCombo); CHECKBOX_SET_RULE(maximizehoriz,); CHECKBOX_SET_RULE(maximizevert,); CHECKBOX_SET_RULE(minimize,); CHECKBOX_SET_RULE(shade,); CHECKBOX_SET_RULE(fullscreen,); COMBOBOX_FORCE_RULE(placement, placementToCombo); CHECKBOX_SET_RULE(above,); CHECKBOX_SET_RULE(below,); CHECKBOX_SET_RULE(noborder,); CHECKBOX_SET_RULE(skiptaskbar,); CHECKBOX_SET_RULE(skippager,); CHECKBOX_SET_RULE(skipswitcher,); CHECKBOX_FORCE_RULE(acceptfocus,); CHECKBOX_FORCE_RULE(closeable,); CHECKBOX_FORCE_RULE(autogroup,); CHECKBOX_FORCE_RULE(autogroupfg,); LINEEDIT_FORCE_RULE(autogroupid,); SPINBOX_FORCE_RULE(opacityactive,); SPINBOX_FORCE_RULE(opacityinactive,); LINEEDIT_SET_RULE(shortcut,); COMBOBOX_FORCE_RULE(fsplevel,); COMBOBOX_FORCE_RULE(type, typeToCombo); CHECKBOX_FORCE_RULE(ignoreposition,); LINEEDIT_FORCE_RULE(minsize, sizeToStr); LINEEDIT_FORCE_RULE(maxsize, sizeToStr); CHECKBOX_FORCE_RULE(strictgeometry,); CHECKBOX_FORCE_RULE(disableglobalshortcuts,); CHECKBOX_FORCE_RULE(blockcompositing,); } #undef GENERIC_RULE #undef CHECKBOX_SET_RULE #undef LINEEDIT_SET_RULE #undef COMBOBOX_SET_RULE +#undef SPINBOX_SET_RULE #undef CHECKBOX_FORCE_RULE #undef LINEEDIT_FORCE_RULE #undef COMBOBOX_FORCE_RULE #undef SPINBOX_FORCE_RULE #define GENERIC_RULE( var, func, Type, type, uimethod ) \ if ( enable_##var->isChecked() && rule_##var->currentIndex() >= 0) \ { \ rules->var##rule = combo_to_##type##_rule[ rule_##var->currentIndex() ]; \ rules->var = func( Ui::RulesWidgetBase::var->uimethod()); \ } \ else \ rules->var##rule = Rules::Unused##Type##Rule; #define CHECKBOX_SET_RULE( var, func ) GENERIC_RULE( var, func, Set, set, isChecked ) #define LINEEDIT_SET_RULE( var, func ) GENERIC_RULE( var, func, Set, set, text ) #define COMBOBOX_SET_RULE( var, func ) GENERIC_RULE( var, func, Set, set, currentIndex ) +#define SPINBOX_SET_RULE( var, func ) GENERIC_RULE( var, func, Set, set, value) #define CHECKBOX_FORCE_RULE( var, func ) GENERIC_RULE( var, func, Force, force, isChecked ) #define LINEEDIT_FORCE_RULE( var, func ) GENERIC_RULE( var, func, Force, force, text ) #define COMBOBOX_FORCE_RULE( var, func ) GENERIC_RULE( var, func, Force, force, currentIndex ) #define SPINBOX_FORCE_RULE( var, func ) GENERIC_RULE( var, func, Force, force, value) Rules* RulesWidget::rules() const { Rules* rules = new Rules(); rules->description = description->text(); rules->wmclass = wmclass->text().toUtf8(); rules->wmclasscomplete = whole_wmclass->isChecked(); rules->wmclassmatch = static_cast< Rules::StringMatch >(wmclass_match->currentIndex()); rules->windowrole = role->text().toUtf8(); rules->windowrolematch = static_cast< Rules::StringMatch >(role_match->currentIndex()); rules->types = 0; bool all_types = true; for (int i = 0; i < types->count(); ++i) if (!types->item(i)->isSelected()) all_types = false; if (all_types) // if all types are selected, use AllTypesMask (for future expansion) rules->types = NET::AllTypesMask; else { rules->types |= types->item(0)->isSelected() ? NET::NormalMask : 0U; rules->types |= types->item(1)->isSelected() ? NET::DialogMask : 0U; rules->types |= types->item(2)->isSelected() ? NET::UtilityMask : 0U; rules->types |= types->item(3)->isSelected() ? NET::DockMask : 0U; rules->types |= types->item(4)->isSelected() ? NET::ToolbarMask : 0U; rules->types |= types->item(5)->isSelected() ? NET::MenuMask : 0U; rules->types |= types->item(6)->isSelected() ? NET::SplashMask : 0U; rules->types |= types->item(7)->isSelected() ? NET::DesktopMask : 0U; rules->types |= types->item(8)->isSelected() ? NET::OverrideMask : 0U; rules->types |= types->item(9)->isSelected() ? NET::TopMenuMask : 0U; } rules->title = title->text(); rules->titlematch = static_cast< Rules::StringMatch >(title_match->currentIndex()); rules->clientmachine = machine->text().toUtf8(); rules->clientmachinematch = static_cast< Rules::StringMatch >(machine_match->currentIndex()); LINEEDIT_SET_RULE(position, strToPosition); LINEEDIT_SET_RULE(size, strToSize); COMBOBOX_SET_RULE(desktop, comboToDesktop); + SPINBOX_SET_RULE(screen, dec); COMBOBOX_SET_RULE(activity, comboToActivity); CHECKBOX_SET_RULE(maximizehoriz,); CHECKBOX_SET_RULE(maximizevert,); CHECKBOX_SET_RULE(minimize,); CHECKBOX_SET_RULE(shade,); CHECKBOX_SET_RULE(fullscreen,); COMBOBOX_FORCE_RULE(placement, comboToPlacement); CHECKBOX_SET_RULE(above,); CHECKBOX_SET_RULE(below,); CHECKBOX_SET_RULE(noborder,); CHECKBOX_SET_RULE(skiptaskbar,); CHECKBOX_SET_RULE(skippager,); CHECKBOX_SET_RULE(skipswitcher,); CHECKBOX_FORCE_RULE(acceptfocus,); CHECKBOX_FORCE_RULE(closeable,); CHECKBOX_FORCE_RULE(autogroup,); CHECKBOX_FORCE_RULE(autogroupfg,); LINEEDIT_FORCE_RULE(autogroupid,); SPINBOX_FORCE_RULE(opacityactive,); SPINBOX_FORCE_RULE(opacityinactive,); LINEEDIT_SET_RULE(shortcut,); COMBOBOX_FORCE_RULE(fsplevel,); COMBOBOX_FORCE_RULE(type, comboToType); CHECKBOX_FORCE_RULE(ignoreposition,); LINEEDIT_FORCE_RULE(minsize, strToSize); LINEEDIT_FORCE_RULE(maxsize, strToSize); CHECKBOX_FORCE_RULE(strictgeometry,); CHECKBOX_FORCE_RULE(disableglobalshortcuts,); CHECKBOX_FORCE_RULE(blockcompositing,); return rules; } #undef GENERIC_RULE #undef CHECKBOX_SET_RULE #undef LINEEDIT_SET_RULE #undef COMBOBOX_SET_RULE +#undef SPINBOX_SET_RULE #undef CHECKBOX_FORCE_RULE #undef LINEEDIT_FORCE_RULE #undef COMBOBOX_FORCE_RULE #undef SPINBOX_FORCE_RULE #define STRING_MATCH_COMBO( type ) \ void RulesWidget::type##MatchChanged() \ { \ edit_reg_##type->setEnabled( type##_match->currentIndex() == Rules::RegExpMatch ); \ type->setEnabled( type##_match->currentIndex() != Rules::UnimportantMatch ); \ } STRING_MATCH_COMBO(wmclass) STRING_MATCH_COMBO(role) STRING_MATCH_COMBO(title) STRING_MATCH_COMBO(machine) #undef STRING_MATCH_COMBO void RulesWidget::detectClicked() { assert(detect_dlg == NULL); detect_dlg = new DetectDialog; connect(detect_dlg, SIGNAL(detectionDone(bool)), this, SLOT(detected(bool))); detect_dlg->detect(0, Ui::RulesWidgetBase::detection_delay->value()); } void RulesWidget::detected(bool ok) { if (ok) { wmclass->setText(detect_dlg->selectedClass()); wmclass_match->setCurrentIndex(Rules::ExactMatch); wmclassMatchChanged(); // grrr whole_wmclass->setChecked(detect_dlg->selectedWholeClass()); role->setText(detect_dlg->selectedRole()); role_match->setCurrentIndex(detect_dlg->selectedRole().isEmpty() ? Rules::UnimportantMatch : Rules::ExactMatch); roleMatchChanged(); if (detect_dlg->selectedWholeApp()) { for (int i = 0; i < types->count(); ++i) types->item(i)->setSelected(true); } else { NET::WindowType type = detect_dlg->selectedType(); for (int i = 0; i < types->count(); ++i) types->item(i)->setSelected(false); types->item(typeToCombo(type))->setSelected(true); } title->setText(detect_dlg->selectedTitle()); title_match->setCurrentIndex(detect_dlg->titleMatch()); titleMatchChanged(); machine->setText(detect_dlg->selectedMachine()); machine_match->setCurrentIndex(Rules::UnimportantMatch); machineMatchChanged(); // prefill values from to window to settings which already set const KWindowInfo& info = detect_dlg->windowInfo(); prefillUnusedValues(info); } delete detect_dlg; detect_dlg = NULL; detect_dlg_ok = ok; } #define GENERIC_PREFILL( var, func, info, uimethod ) \ if ( !enable_##var->isChecked()) \ { \ Ui::RulesWidgetBase::var->uimethod( func( info )); \ } #define CHECKBOX_PREFILL( var, func, info ) GENERIC_PREFILL( var, func, info, setChecked ) #define LINEEDIT_PREFILL( var, func, info ) GENERIC_PREFILL( var, func, info, setText ) #define COMBOBOX_PREFILL( var, func, info ) GENERIC_PREFILL( var, func, info, setCurrentIndex ) #define SPINBOX_PREFILL( var, func, info ) GENERIC_PREFILL( var, func, info, setValue ) void RulesWidget::prefillUnusedValues(const KWindowInfo& info) { LINEEDIT_PREFILL(position, positionToStr, info.frameGeometry().topLeft()); LINEEDIT_PREFILL(size, sizeToStr, info.frameGeometry().size()); COMBOBOX_PREFILL(desktop, desktopToCombo, info.desktop()); // COMBOBOX_PREFILL(activity, activityToCombo, info.activity()); // TODO: ivan CHECKBOX_PREFILL(maximizehoriz, , info.state() & NET::MaxHoriz); CHECKBOX_PREFILL(maximizevert, , info.state() & NET::MaxVert); CHECKBOX_PREFILL(minimize, , info.isMinimized()); CHECKBOX_PREFILL(shade, , info.state() & NET::Shaded); CHECKBOX_PREFILL(fullscreen, , info.state() & NET::FullScreen); //COMBOBOX_PREFILL( placement, placementToCombo ); CHECKBOX_PREFILL(above, , info.state() & NET::KeepAbove); CHECKBOX_PREFILL(below, , info.state() & NET::KeepBelow); // noborder is only internal KWin information, so let's guess CHECKBOX_PREFILL(noborder, , info.frameGeometry() == info.geometry()); CHECKBOX_PREFILL(skiptaskbar, , info.state() & NET::SkipTaskbar); CHECKBOX_PREFILL(skippager, , info.state() & NET::SkipPager); CHECKBOX_PREFILL(skipswitcher, , false); //CHECKBOX_PREFILL( acceptfocus, ); //CHECKBOX_PREFILL( closeable, ); //CHECKBOX_PREFILL( autogroup, ); //CHECKBOX_PREFILL( autogroupfg, ); //LINEEDIT_PREFILL( autogroupid, ); SPINBOX_PREFILL(opacityactive, , 100 /*get the actual opacity somehow*/); SPINBOX_PREFILL(opacityinactive, , 100 /*get the actual opacity somehow*/); //LINEEDIT_PREFILL( shortcut, ); //COMBOBOX_PREFILL( fsplevel, ); COMBOBOX_PREFILL(type, typeToCombo, info.windowType(SUPPORTED_MANAGED_WINDOW_TYPES_MASK)); //CHECKBOX_PREFILL( ignoreposition, ); LINEEDIT_PREFILL(minsize, sizeToStr, info.frameGeometry().size()); LINEEDIT_PREFILL(maxsize, sizeToStr, info.frameGeometry().size()); //CHECKBOX_PREFILL( strictgeometry, ); //CHECKBOX_PREFILL( disableglobalshortcuts, ); //CHECKBOX_PREFILL( blockcompositing, ); } #undef GENERIC_PREFILL #undef CHECKBOX_PREFILL #undef LINEEDIT_PREFILL #undef COMBOBOX_PREFILL #undef SPINBOX_PREFILL bool RulesWidget::finalCheck() { if (description->text().isEmpty()) { if (!wmclass->text().isEmpty()) description->setText(i18n("Settings for %1", wmclass->text())); else description->setText(i18n("Unnamed entry")); } bool all_types = true; for (int i = 0; i < types->count(); ++i) if (!types->item(i)->isSelected()) all_types = false; if (wmclass_match->currentIndex() == Rules::UnimportantMatch && all_types) { if (KMessageBox::warningContinueCancel(window(), i18n("You have specified the window class as unimportant.\n" "This means the settings will possibly apply to windows from all applications. " "If you really want to create a generic setting, it is recommended you at least " "limit the window types to avoid special window types.")) != KMessageBox::Continue) return false; } return true; } void RulesWidget::prepareWindowSpecific(WId window) { tabs->setCurrentIndex(2); // geometry tab, skip tabs for window identification KWindowInfo info(window, -1U, -1U); // read everything prefillUnusedValues(info); } void RulesWidget::shortcutEditClicked() { QPointer dlg = new EditShortcutDialog(window()); dlg->setShortcut(shortcut->text()); if (dlg->exec() == QDialog::Accepted) shortcut->setText(dlg->shortcut()); delete dlg; } RulesDialog::RulesDialog(QWidget* parent, const char* name) : KDialog(parent) { setObjectName(name); setModal(true); setCaption(i18n("Edit Window-Specific Settings")); setButtons(Ok | Cancel); setWindowIcon(KIcon("preferences-system-windows-actions")); widget = new RulesWidget(this); setMainWidget(widget); } // window is set only for Alt+F3/Window-specific settings, because the dialog // is then related to one specific window Rules* RulesDialog::edit(Rules* r, WId window, bool show_hints) { rules = r; widget->setRules(rules); if (window != 0) widget->prepareWindowSpecific(window); if (show_hints) QTimer::singleShot(0, this, SLOT(displayHints())); exec(); return rules; } void RulesDialog::displayHints() { QString str = "

"; str += i18n("This configuration dialog allows altering settings only for the selected window" " or application. Find the setting you want to affect, enable the setting using the checkbox," " select in what way the setting should be affected and to which value."); #if 0 // maybe later str += "

" + i18n("Consult the documentation for more details."); #endif str += "

"; KMessageBox::information(this, str, QString(), "displayhints"); } void RulesDialog::accept() { if (!widget->finalCheck()) return; rules = widget->rules(); KDialog::accept(); } EditShortcut::EditShortcut(QWidget* parent) : QWidget(parent) { setupUi(this); } void EditShortcut::editShortcut() { QPointer< ShortcutDialog > dlg = new ShortcutDialog(QKeySequence(shortcut->text()), window()); if (dlg->exec() == QDialog::Accepted) shortcut->setText(dlg->shortcut().toString()); delete dlg; } void EditShortcut::clearShortcut() { shortcut->setText(QLatin1String("")); } EditShortcutDialog::EditShortcutDialog(QWidget* parent, const char* name) : KDialog(parent) { setObjectName(name); setModal(true); setCaption(i18n("Edit Shortcut")); setButtons(Ok | Cancel); widget = new EditShortcut(this); setMainWidget(widget); } void EditShortcutDialog::setShortcut(const QString& cut) { widget->shortcut->setText(cut); } QString EditShortcutDialog::shortcut() const { return widget->shortcut->text(); } ShortcutDialog::ShortcutDialog(const QKeySequence& cut, QWidget* parent) : KDialog(parent) , widget(new KKeySequenceWidget(this)) { widget->setKeySequence(cut); // It's a global shortcut so don't allow multikey shortcuts widget->setMultiKeyShortcutsAllowed(false); setMainWidget(widget); } void ShortcutDialog::accept() { QKeySequence seq = shortcut(); if (!seq.isEmpty()) { if (seq[0] == Qt::Key_Escape) { reject(); return; } if (seq[0] == Qt::Key_Space || (seq[0] & Qt::KeyboardModifierMask) == 0) { // clear widget->clearKeySequence(); KDialog::accept(); return; } } KDialog::accept(); } QKeySequence ShortcutDialog::shortcut() const { return widget->keySequence(); } } // namespace #include "ruleswidget.moc" diff --git a/kcmkwin/kwinrules/ruleswidget.h b/kcmkwin/kwinrules/ruleswidget.h index 27cf7314c..a4b5b9133 100644 --- a/kcmkwin/kwinrules/ruleswidget.h +++ b/kcmkwin/kwinrules/ruleswidget.h @@ -1,158 +1,161 @@ /* * Copyright (c) 2004 Lubos Lunak * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __RULESWIDGET_H__ #define __RULESWIDGET_H__ #include #include #include #include "ui_ruleswidgetbase.h" #include "ui_editshortcut.h" namespace KWin { class Rules; class DetectDialog; class RulesWidget : public QWidget, public Ui::RulesWidgetBase { Q_OBJECT public: RulesWidget(QWidget* parent = NULL); void setRules(Rules* r); Rules* rules() const; bool finalCheck(); void prepareWindowSpecific(WId window); signals: void changed(bool state); protected slots: void detectClicked(); void wmclassMatchChanged(); void roleMatchChanged(); void titleMatchChanged(); void machineMatchChanged(); void shortcutEditClicked(); private slots: // geometry tab void updateEnableposition(); void updateEnablesize(); void updateEnabledesktop(); + void updateEnablescreen(); void updateEnableactivity(); void updateEnablemaximizehoriz(); void updateEnablemaximizevert(); void updateEnableminimize(); void updateEnableshade(); void updateEnablefullscreen(); void updateEnableplacement(); // preferences tab void updateEnableabove(); void updateEnablebelow(); void updateEnablenoborder(); void updateEnableskiptaskbar(); void updateEnableskippager(); void updateEnableskipswitcher(); void updateEnableacceptfocus(); void updateEnablecloseable(); void updateEnableautogroup(); void updateEnableautogroupfg(); void updateEnableautogroupid(); void updateEnableopacityactive(); void updateEnableopacityinactive(); // workarounds tab void updateEnablefsplevel(); void updateEnabletype(); void updateEnableignoreposition(); void updateEnableminsize(); void updateEnablemaxsize(); void updateEnablestrictgeometry(); void updateEnableshortcut(); void updateEnabledisableglobalshortcuts(); void updateEnableblockcompositing(); // internal void detected(bool); private: int desktopToCombo(int d) const; int comboToDesktop(int val) const; int activityToCombo(QString d) const; QString comboToActivity(int val) const; int comboToTiling(int val) const; + int inc(int i) const { return i+1; } + int dec(int i) const { return i-1; } void prefillUnusedValues(const KWindowInfo& info); DetectDialog* detect_dlg; bool detect_dlg_ok; }; class RulesDialog : public KDialog { Q_OBJECT public: explicit RulesDialog(QWidget* parent = NULL, const char* name = NULL); Rules* edit(Rules* r, WId window, bool show_hints); protected: virtual void accept(); private slots: void displayHints(); private: RulesWidget* widget; Rules* rules; }; class EditShortcut : public QWidget, public Ui_EditShortcut { Q_OBJECT public: EditShortcut(QWidget* parent = NULL); protected slots: void editShortcut(); void clearShortcut(); }; class EditShortcutDialog : public KDialog { Q_OBJECT public: explicit EditShortcutDialog(QWidget* parent = NULL, const char* name = NULL); void setShortcut(const QString& cut); QString shortcut() const; private: EditShortcut* widget; }; // slightly duped from utils.cpp class ShortcutDialog : public KDialog { Q_OBJECT public: explicit ShortcutDialog(const QKeySequence& cut, QWidget* parent = NULL); virtual void accept(); QKeySequence shortcut() const; private: KKeySequenceWidget* widget; }; } // namespace #endif diff --git a/kcmkwin/kwinrules/ruleswidgetbase.ui b/kcmkwin/kwinrules/ruleswidgetbase.ui index 0ff1abd78..a1aed4610 100644 --- a/kcmkwin/kwinrules/ruleswidgetbase.ui +++ b/kcmkwin/kwinrules/ruleswidgetbase.ui @@ -1,2583 +1,2683 @@ KWin::RulesWidgetBase 0 0 - 569 - 535 + 630 + 580 0 &Window matching De&scription: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter false description Window &class (application): Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter false wmclass Unimportant Exact Match Substring Match Regular Expression false Edit Match w&hole window class Window &role: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter false role Unimportant Exact Match Substring Match Regular Expression false Edit Window &types: Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing false types Window t&itle: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter false title Unimportant Exact Match Substring Match Regular Expression false Edit &Machine (hostname): Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter false machine Unimportant Exact Match Substring Match Regular Expression false Edit Qt::Vertical 20 40 Qt::Horizontal 40 20 &Detect Window Properties s delay 30 Qt::Horizontal 40 20 Qt::Vertical 20 40 Qt::Vertical 20 40 0 0 false Qt::ScrollBarAsNeeded Qt::ScrollBarAsNeeded true false QAbstractItemView::NoDragDrop false QAbstractItemView::ExtendedSelection QListView::Static QListView::TopToBottom true QListView::Adjust 0 QListView::ListMode true Normal Window Dialog Window Utility Window Dock (panel) Toolbar Torn-Off Menu Splash Screen Desktop Unmanaged Window Standalone Menubar Qt::Horizontal &Size && Position - + + + + &Position + + + + false Do Not Affect Apply Initially Remember Force Apply Now Force Temporarily - - + + + + false + + + x,y + + + 0123456789-+,xX: + + + + + + + &Size + + + + + false Do Not Affect Apply Initially Remember Force Apply Now Force Temporarily - - + + false - x,y + width,height 0123456789-+,xX: - - - - &Size + + + + Qt::Horizontal - - - - false - - - width,height - - - 0123456789-+,xX: + + + + Maximized &horizontally - - + + false Do Not Affect + + + Apply Initially + + + + + Remember + + Force + + + Apply Now + + Force Temporarily - - - - Sh&aded + + + + false - - + + - M&inimized + Maximized &vertically - - + + false Do Not Affect Apply Initially Remember Force Apply Now Force Temporarily - - + + + + false + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + &Desktop + + + + + false Do Not Affect Apply Initially Remember Force Apply Now Force Temporarily - - + + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Activit&y + + + + + false Do Not Affect Apply Initially Remember Force Apply Now Force Temporarily - - + + false - - + + false + + 1 + - - + + + + Qt::Horizontal + + + + + + + &Fullscreen + + + + + false - Default - - - - - No Placement - - - - - Smart - - - - - Maximizing - - - - - Cascade + Do Not Affect - Centered + Apply Initially - Random + Remember - Top-Left Corner + Force - Under Mouse + Apply Now - On Main Window + Force Temporarily - - - - false - - - width,height - - - 0123456789-+,xX: - - - - - - - M&aximum size - - - - - - - Qt::Horizontal - - - - - - - - - - Initial p&lacement - - - - - - - false - - - - - + + false - - - - Qt::Horizontal - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Eg. terminals or video players can ask to keep a certain aspect ratio -or only grow by values larger than one -(eg. by the dimensions of one character). -This may be pointless and the restriction prevents arbitrary dimensions -like your complete screen area. - - - Qt::LeftToRight - + + - Obey geometry restrictions - - - + M&inimized - - + + false Do Not Affect + + + Apply Initially + + + + + Remember + + Force + + + Apply Now + + Force Temporarily - - - - Qt::Horizontal + + + + false - - + + + + Sh&aded + + + + + false Do Not Affect Apply Initially Remember Force Apply Now Force Temporarily - - + + + + false + + + + + + + Qt::Horizontal + + + + + false Do Not Affect - Apply Initially + Force - Remember + Force Temporarily + + + + + + false + - Force + Default - Apply Now + No Placement - Force Temporarily + Smart - - - - - - false - - Do Not Affect + Maximizing - Force + Cascade - Force Temporarily + Centered + + + + + Random + + + + + Top-Left Corner + + + + + Under Mouse + + + + + On Main Window - - - - false + + + + + + + Initial p&lacement - + Windows can ask to appear in a certain position. By default this overrides the placement strategy what might be nasty if the client abuses the feature to unconditionally popup in the middle of your screen. Ignore requested &geometry - - - - Maximized &horizontally - - - - - - - &Fullscreen - - - - - + + false + + + Do Not Affect + + + + + Force + + + + + Force Temporarily + + - - + + false - - - - false - - - width,height - - - 0123456789-+,xX: + + + + Qt::Horizontal - - + + false Do Not Affect Force Force Temporarily - - + + - &Position + M&inimum size - - + + false + + width,height + + + 0123456789-+,xX: + - - + + + + M&aximum size + + + + + false Do Not Affect - - - Apply Initially - - - - - Remember - - Force - - - Apply Now - - Force Temporarily - - - - Maximized &vertically + + + + false - - - - - - &Desktop + + width,height - - - - - - M&inimum size + + 0123456789-+,xX: - - - - Qt::Horizontal + + + + Eg. terminals or video players can ask to keep a certain aspect ratio +or only grow by values larger than one +(eg. by the dimensions of one character). +This may be pointless and the restriction prevents arbitrary dimensions +like your complete screen area. - - - 40 - 20 - + + Qt::LeftToRight - - - - - - Qt::Horizontal + + Obey geometry restrictions + + + - + false Do Not Affect Force Force Temporarily - - - - Qt::Horizontal + + + + false - + Qt::Vertical QSizePolicy::Expanding 20 16 - - - - Activit&y - - - - - + + false Do Not Affect Apply Initially Remember Force Apply Now Force Temporarily - - - - false + + + + Screen &Arrangement && Access false Do Not Affect Apply Initially Remember Force Apply Now Force Temporarily Window shall (not) appear in the manager for virtual desktops Skip pa&ger false Do Not Affect Apply Initially Remember Force Apply Now Force Temporarily false false Window shall (not) appear in the taskbar. Skip &taskbar false Window shall (not) appear in the Alt+Tab list Skip &switcher false Edit... Qt::Vertical 20 40 false Do Not Affect Force Force Temporarily false Do Not Affect Apply Initially Remember Force Apply Now Force Temporarily Qt::Horizontal Qt::Vertical QSizePolicy::Fixed 20 8 false false Shortcut false Do Not Affect Apply Initially Remember Force Apply Now Force Temporarily false Do Not Affect Apply Initially Remember Force Apply Now Force Temporarily false false Keep &above Qt::Horizontal 40 20 Autog&roup in foreground false Do Not Affect Force Force Temporarily false Qt::Horizontal Keep &below Qt::Horizontal 40 20 false Do Not Affect Force Force Temporarily 0 0 Qt::Horizontal Autogroup by I&D Autogroup with &identical false Do Not Affect Apply Initially Remember Force Apply Now Force Temporarily false Appearance && &Fixes &No titlebar and frame false Do Not Affect Apply Initially Remember Force Apply Now Force Temporarily Qt::Horizontal A&ctive opacity false Do Not Affect Force Force Temporarily false % 100 100 I&nactive opacity false Do Not Affect Force Force Temporarily false % 100 100 Qt::Vertical QSizePolicy::Fixed 20 8 Qt::Horizontal 40 20 Windows may prevent to get the focus (activate) when being clicked. On the other hand you might wish to prevent a window from getting focused on a mouse click. Accept &focus false Do Not Affect Force Force Temporarily false When used, a window will receive all keyboard inputs while it is active, including Alt+Tab etc. This is especially interesting for emulators or virtual machines. Be warned: you won't be able to Alt+Tab out of the window nor use any other global shortcut (such as Alt+F2 to show KRunner) while it's active! Ignore global shortcuts false Do Not Affect Force Force Temporarily Qt::Horizontal false &Closeable Window &type false Do Not Affect Force Force Temporarily false Normal Window Dialog Window Utility Window Dock (panel) Toolbar Torn-Off Menu Splash Screen Desktop Standalone Menubar Qt::Horizontal Block compositing false Qt::Vertical 20 40 false Qt::Horizontal 40 20 Qt::Horizontal Qt::Vertical QSizePolicy::Fixed 20 8 false false Do Not Affect Force Force Temporarily false Do Not Affect Force Force Temporarily false Do Not Affect Force Force Temporarily false None Low Normal High Extreme KWin tries to prevent windows from taking the focus ("activate") while you're working in another window, but this may sometimes fail or superact. "None" will unconditionally allow this window to get the focus while "Extreme" will completely prevent it from taking the focus. &Focus stealing prevention Qt::Horizontal false Do Not Affect Force Force Temporarily false Opaque Transparent &Moving/resizing KComboBox QComboBox
kcombobox.h
KLineEdit QLineEdit
klineedit.h
KPushButton QPushButton
kpushbutton.h
KRestrictedLine KLineEdit
krestrictedline.h
YesNoBox QWidget
yesnobox.h
1
+ description detect + detection_delay wmclass_match wmclass edit_reg_wmclass whole_wmclass role_match role edit_reg_role types title_match title edit_reg_title machine_match machine edit_reg_machine enable_position rule_position position enable_size rule_size size enable_maximizehoriz + rule_maximizehoriz enable_maximizevert rule_maximizevert - enable_fullscreen enable_desktop rule_desktop desktop + enable_activity + rule_activity + activity + enable_screen + rule_screen + screen + enable_fullscreen + rule_fullscreen enable_minimize + rule_minimize enable_shade rule_shade enable_placement rule_placement placement enable_ignoreposition rule_ignoreposition + enable_minsize + rule_minsize + minsize enable_maxsize rule_maxsize maxsize + enable_strictgeometry + rule_strictgeometry enable_above rule_above enable_below rule_below enable_autogroup + rule_autogroup enable_autogroupfg rule_autogroupfg enable_autogroupid rule_autogroupid + autogroupid + enable_skiptaskbar + rule_skiptaskbar + enable_skippager + rule_skippager + enable_skipswitcher + rule_skipswitcher + enable_shortcut + rule_shortcut + shortcut + shortcut_edit enable_noborder rule_noborder enable_opacityactive + rule_opacityactive opacityactive enable_opacityinactive rule_opacityinactive opacityinactive - description + enable_moveresizemode + rule_moveresizemode + moveresizemode + enable_fsplevel + rule_fsplevel + fsplevel + enable_acceptfocus + rule_acceptfocus + enable_disableglobalshortcuts + rule_disableglobalshortcuts + enable_closeable + rule_closeable + enable_type + rule_type + type + enable_blockcompositing + rule_blockcompositing + tabs detect clicked() KWin::RulesWidgetBase detectClicked() - 247 - 89 + 321 + 124 20 20 wmclass_match activated(int) KWin::RulesWidgetBase wmclassMatchChanged() - 164 - 134 + 301 + 196 20 20 role_match activated(int) KWin::RulesWidgetBase roleMatchChanged() - 164 - 188 + 301 + 254 20 20
diff --git a/manage.cpp b/manage.cpp index ddeca1141..616f6b890 100644 --- a/manage.cpp +++ b/manage.cpp @@ -1,724 +1,725 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ // This file contains things relevant to handling incoming events. #include "client.h" #include #include #include #include "notifications.h" #include #include "rules.h" #include "group.h" namespace KWin { /** * Manages the clients. This means handling the very first maprequest: * reparenting, initial geometry, initial state, placement, etc. * Returns false if KWin is not going to manage this window. */ bool Client::manage(Window w, bool isMapped) { StackingUpdatesBlocker stacking_blocker(workspace()); grabXServer(); XWindowAttributes attr; if (!XGetWindowAttributes(display(), w, &attr)) { ungrabXServer(); return false; } // From this place on, manage() must not return false block_geometry_updates = 1; pending_geometry_update = PendingGeometryForced; // Force update when finishing with geometry changes embedClient(w, attr); vis = attr.visual; bit_depth = attr.depth; // SELI TODO: Order all these things in some sane manner bool init_minimize = false; XWMHints* hints = XGetWMHints(display(), w); if (hints && (hints->flags & StateHint) && hints->initial_state == IconicState) init_minimize = true; if (hints) XFree(hints); if (isMapped) init_minimize = false; // If it's already mapped, ignore hint unsigned long properties[2]; properties[WinInfo::PROTOCOLS] = NET::WMDesktop | NET::WMState | NET::WMWindowType | NET::WMStrut | NET::WMName | NET::WMIconGeometry | NET::WMIcon | NET::WMPid | NET::WMIconName | 0; properties[WinInfo::PROTOCOLS2] = NET::WM2UserTime | NET::WM2StartupId | NET::WM2ExtendedStrut | NET::WM2Opacity | NET::WM2FullscreenMonitors | NET::WM2FrameOverlap | 0; info = new WinInfo(this, display(), client, rootWindow(), properties, 2); cmap = attr.colormap; getResourceClass(); getWindowRole(); getWmClientLeader(); getWmClientMachine(); getSyncCounter(); // First only read the caption text, so that setupWindowRules() can use it for matching, // and only then really set the caption using setCaption(), which checks for duplicates etc. // and also relies on rules already existing cap_normal = readName(); setupWindowRules(false); setCaption(cap_normal, true); if (Extensions::shapeAvailable()) XShapeSelectInput(display(), window(), ShapeNotifyMask); detectShape(window()); detectNoBorder(); fetchIconicName(); getWMHints(); // Needs to be done before readTransient() because of reading the group modal = (info->state() & NET::Modal) != 0; // Needs to be valid before handling groups readTransient(); getIcons(); getWindowProtocols(); getWmNormalHints(); // Get xSizeHint getMotifHints(); getWmOpaqueRegion(); // TODO: Try to obey all state information from info->state() original_skip_taskbar = skip_taskbar = (info->state() & NET::SkipTaskbar) != 0; skip_pager = (info->state() & NET::SkipPager) != 0; updateFirstInTabBox(); setupCompositing(); KStartupInfoId asn_id; KStartupInfoData asn_data; bool asn_valid = workspace()->checkStartupNotification(window(), asn_id, asn_data); // Make sure that the input window is created before we update the stacking order updateInputWindow(); workspace()->updateClientLayer(this); SessionInfo* session = workspace()->takeSessionInfo(this); if (session) { init_minimize = session->minimized; noborder = session->noBorder; } setShortcut(rules()->checkShortcut(session ? session->shortcut : QString(), true)); init_minimize = rules()->checkMinimize(init_minimize, !isMapped); noborder = rules()->checkNoBorder(noborder, !isMapped); checkActivities(); // Initial desktop placement if (session) { desk = session->desktop; if (session->onAllDesktops) desk = NET::OnAllDesktops; setOnActivities(session->activities); } else { // If this window is transient, ensure that it is opened on the // same window as its parent. this is necessary when an application // starts up on a different desktop than is currently displayed if (isTransient()) { ClientList mainclients = mainClients(); bool on_current = false; bool on_all = false; Client* maincl = NULL; // This is slightly duplicated from Placement::placeOnMainWindow() for (ClientList::ConstIterator it = mainclients.constBegin(); it != mainclients.constEnd(); ++it) { if (mainclients.count() > 1 && (*it)->isSpecialWindow()) continue; // Don't consider toolbars etc when placing maincl = *it; if ((*it)->isOnCurrentDesktop()) on_current = true; if ((*it)->isOnAllDesktops()) on_all = true; } if (on_all) desk = NET::OnAllDesktops; else if (on_current) desk = workspace()->currentDesktop(); else if (maincl != NULL) desk = maincl->desktop(); if (maincl) setOnActivities(maincl->activities()); } if (info->desktop()) desk = info->desktop(); // Window had the initial desktop property, force it if (desktop() == 0 && asn_valid && asn_data.desktop() != 0) desk = asn_data.desktop(); if (!isMapped && !noborder && isNormalWindow() && !activitiesDefined) { //a new, regular window, when we're not recovering from a crash, //and it hasn't got an activity. let's try giving it the current one. //TODO: decide whether to keep this before the 4.6 release //TODO: if we are keeping it (at least as an option), replace noborder checking //with a public API for setting windows to be on all activities. //something like KWindowSystem::setOnAllActivities or //KActivityConsumer::setOnAllActivities setOnActivity(Workspace::self()->currentActivity(), true); } } if (desk == 0) // Assume window wants to be visible on the current desktop desk = isDesktop() ? NET::OnAllDesktops : workspace()->currentDesktop(); desk = rules()->checkDesktop(desk, !isMapped); if (desk != NET::OnAllDesktops) // Do range check desk = qMax(1, qMin(workspace()->numberOfDesktops(), desk)); info->setDesktop(desk); workspace()->updateOnAllDesktopsOfTransients(this); // SELI TODO //onAllDesktopsChange(); // Decoration doesn't exist here yet QString activitiesList; activitiesList = rules()->checkActivity(activitiesList, !isMapped); if (!activitiesList.isEmpty()) setOnActivities(activitiesList.split(",")); QRect geom(attr.x, attr.y, attr.width, attr.height); bool placementDone = false; if (session) geom = session->geometry; QRect area; bool partial_keep_in_area = isMapped || session; if (isMapped || session) area = workspace()->clientArea(FullArea, geom.center(), desktop()); else { int screen = asn_data.xinerama() == -1 ? workspace()->activeScreen() : asn_data.xinerama(); + screen = rules()->checkScreen(screen, !isMapped); area = workspace()->clientArea(PlacementArea, workspace()->screenGeometry(screen).center(), desktop()); } if (int type = checkFullScreenHack(geom)) { fullscreen_mode = FullScreenHack; if (rules()->checkStrictGeometry(false)) { geom = type == 2 // 1 = It's xinerama-aware fullscreen hack, 2 = It's full area ? workspace()->clientArea(FullArea, geom.center(), desktop()) : workspace()->clientArea(ScreenArea, geom.center(), desktop()); } else geom = workspace()->clientArea(FullScreenArea, geom.center(), desktop()); placementDone = true; } if (isDesktop()) // KWin doesn't manage desktop windows placementDone = true; bool usePosition = false; if (isMapped || session || placementDone) placementDone = true; // Use geometry else if (isTransient() && !isUtility() && !isDialog() && !isSplash()) usePosition = true; else if (isTransient() && !hasNETSupport()) usePosition = true; else if (isDialog() && hasNETSupport()) { // If the dialog is actually non-NETWM transient window, don't try to apply placement to it, // it breaks with too many things (xmms, display) if (mainClients().count() >= 1) { #if 1 // #78082 - Ok, it seems there are after all some cases when an application has a good // reason to specify a position for its dialog. Too bad other WMs have never bothered // with placement for dialogs, so apps always specify positions for their dialogs, // including such silly positions like always centered on the screen or under mouse. // Using ignoring requested position in window-specific settings helps, and now // there's also _NET_WM_FULL_PLACEMENT. usePosition = true; #else ; // Force using placement policy #endif } else usePosition = true; } else if (isSplash()) ; // Force using placement policy else usePosition = true; if (!rules()->checkIgnoreGeometry(!usePosition)) { if (((xSizeHint.flags & PPosition)) || (xSizeHint.flags & USPosition)) { placementDone = true; // Disobey xinerama placement option for now (#70943) area = workspace()->clientArea(PlacementArea, geom.center(), desktop()); } } //if ( true ) // Size is always obeyed for now, only with constraints applied // if (( xSizeHint.flags & USSize ) || ( xSizeHint.flags & PSize )) // { // // Keep in mind that we now actually have a size :-) // } if (xSizeHint.flags & PMaxSize) geom.setSize(geom.size().boundedTo( rules()->checkMaxSize(QSize(xSizeHint.max_width, xSizeHint.max_height)))); if (xSizeHint.flags & PMinSize) geom.setSize(geom.size().expandedTo( rules()->checkMinSize(QSize(xSizeHint.min_width, xSizeHint.min_height)))); if (isMovable() && (geom.x() > area.right() || geom.y() > area.bottom())) placementDone = false; // Weird, do not trust. if (placementDone) move(geom.x(), geom.y()); // Before gravitating // Create client group if the window will have a decoration bool dontKeepInArea = false; setTabGroup(NULL); if (!noBorder()) { const bool autogrouping = rules()->checkAutogrouping(options->isAutogroupSimilarWindows()); const bool autogroupInFg = rules()->checkAutogroupInForeground(options->isAutogroupInForeground()); // Automatically add to previous groups on session restore if (session && session->tabGroupClient && !workspace()->hasClient(session->tabGroupClient)) session->tabGroupClient = NULL; if (session && session->tabGroupClient && session->tabGroupClient != this) { tabBehind(session->tabGroupClient, autogroupInFg); } else if (isMapped && autogrouping) { // If the window is already mapped (Restarted KWin) add any windows that already have the // same geometry to the same client group. (May incorrectly handle maximized windows) foreach (Client *other, workspace()->clientList()) { if (other->maximizeMode() != MaximizeFull && geom == QRect(other->pos(), other->clientSize()) && desk == other->desktop() && activities() == other->activities()) { tabBehind(other, autogroupInFg); break; } } } if (!(tab_group || isMapped || session)) { // Attempt to automatically group similar windows Client* similar = findAutogroupCandidate(); if (similar && !similar->noBorder()) { if (autogroupInFg) { similar->setDesktop(desk); // can happen when grouping by id. ... similar->setMinimized(false); // ... or anyway - still group, but "here" and visible } if (!similar->isMinimized()) { // do not attempt to tab in background of a hidden group geom = QRect(similar->pos() + similar->clientPos(), similar->clientSize()); updateDecoration(false); if (tabBehind(similar, autogroupInFg)) { // Don't move entire group geom = QRect(similar->pos() + similar->clientPos(), similar->clientSize()); placementDone = true; dontKeepInArea = true; } } } } } updateDecoration(false); // Also gravitates // TODO: Is CentralGravity right here, when resizing is done after gravitating? plainResize(rules()->checkSize(sizeForClientSize(geom.size()), !isMapped)); QPoint forced_pos = rules()->checkPosition(invalidPoint, !isMapped); if (forced_pos != invalidPoint) { move(forced_pos); placementDone = true; // Don't keep inside workarea if the window has specially configured position partial_keep_in_area = true; area = workspace()->clientArea(FullArea, geom.center(), desktop()); } if (!placementDone) { // Placement needs to be after setting size workspace()->place(this, area); placementDone = true; } // bugs #285967, #286146, #183694 // geometry() now includes the requested size and the decoration and is at the correct screen/position (hopefully) // Maximization for oversized windows must happen NOW. // If we effectively pass keepInArea(), the window will resizeWithChecks() - i.e. constrained // to the combo of all screen MINUS all struts on the edges // If only one screen struts, this will affect screens as a side-effect, the window is artificailly shrinked // below the screen size and as result no more maximized what breaks KMainWindow's stupid width+1, height+1 hack // TODO: get KMainWindow a correct state storage what will allow to store the restore size as well. if (!session) { // has a better handling of this geom_restore = geometry(); // Remember restore geometry if (isMaximizable() && (width() >= area.width() || height() >= area.height())) { // Window is too large for the screen, maximize in the // directions necessary if (width() >= area.width() && height() >= area.height()) { dontKeepInArea = true; maximize(Client::MaximizeFull); geom_restore = QRect(); // Use placement when unmaximizing } else if (width() >= area.width()) { maximize(Client::MaximizeHorizontal); geom_restore = QRect(); // Use placement when unmaximizing geom_restore.setY(y()); // But only for horizontal direction geom_restore.setHeight(height()); } else if (height() >= area.height()) { maximize(Client::MaximizeVertical); geom_restore = QRect(); // Use placement when unmaximizing geom_restore.setX(x()); // But only for vertical direction geom_restore.setWidth(width()); } } } if ((!isSpecialWindow() || isToolbar()) && isMovable() && !dontKeepInArea) keepInArea(area, partial_keep_in_area); updateShape(); // CT: Extra check for stupid jdk 1.3.1. But should make sense in general // if client has initial state set to Iconic and is transient with a parent // window that is not Iconic, set init_state to Normal if (init_minimize && isTransient()) { ClientList mainclients = mainClients(); for (ClientList::ConstIterator it = mainclients.constBegin(); it != mainclients.constEnd(); ++it) if ((*it)->isShown(true)) init_minimize = false; // SELI TODO: Even e.g. for NET::Utility? } // If a dialog is shown for minimized window, minimize it too if (!init_minimize && isTransient() && mainClients().count() > 0) { bool visible_parent = false; // Use allMainClients(), to include also main clients of group transients // that have been optimized out in Client::checkGroupTransients() ClientList mainclients = allMainClients(); for (ClientList::ConstIterator it = mainclients.constBegin(); it != mainclients.constEnd(); ++it) if ((*it)->isShown(true)) visible_parent = true; if (!visible_parent) { init_minimize = true; demandAttention(); } } if (init_minimize) minimize(true); // No animation // SELI TODO: This seems to be mainly for kstart and ksystraycmd // probably should be replaced by something better bool doNotShow = false; if (workspace()->isNotManaged(caption())) doNotShow = true; // Other settings from the previous session if (session) { // Session restored windows are not considered to be new windows WRT rules, // I.e. obey only forcing rules setKeepAbove(session->keepAbove); setKeepBelow(session->keepBelow); setSkipTaskbar(session->skipTaskbar, true); setSkipPager(session->skipPager); setSkipSwitcher(session->skipSwitcher); setShade(session->shaded ? ShadeNormal : ShadeNone); setOpacity(session->opacity); geom_restore = session->restore; if (session->maximized != MaximizeRestore) { maximize(MaximizeMode(session->maximized)); } if (session->fullscreen == FullScreenHack) ; // Nothing, this should be already set again above else if (session->fullscreen != FullScreenNone) { setFullScreen(true, false); geom_fs_restore = session->fsrestore; } } else { // Window may want to be maximized // done after checking that the window isn't larger than the workarea, so that // the restore geometry from the checks above takes precedence, and window // isn't restored larger than the workarea MaximizeMode maxmode = static_cast( ((info->state() & NET::MaxVert) ? MaximizeVertical : 0) | ((info->state() & NET::MaxHoriz) ? MaximizeHorizontal : 0)); MaximizeMode forced_maxmode = rules()->checkMaximize(maxmode, !isMapped); // Either hints were set to maximize, or is forced to maximize, // or is forced to non-maximize and hints were set to maximize if (forced_maxmode != MaximizeRestore || maxmode != MaximizeRestore) maximize(forced_maxmode); // Read other initial states setShade(rules()->checkShade(info->state() & NET::Shaded ? ShadeNormal : ShadeNone, !isMapped)); setKeepAbove(rules()->checkKeepAbove(info->state() & NET::KeepAbove, !isMapped)); setKeepBelow(rules()->checkKeepBelow(info->state() & NET::KeepBelow, !isMapped)); setSkipTaskbar(rules()->checkSkipTaskbar(info->state() & NET::SkipTaskbar, !isMapped), true); setSkipPager(rules()->checkSkipPager(info->state() & NET::SkipPager, !isMapped)); setSkipSwitcher(rules()->checkSkipSwitcher(false, !isMapped)); if (info->state() & NET::DemandsAttention) demandAttention(); if (info->state() & NET::Modal) setModal(true); if (fullscreen_mode != FullScreenHack && isFullScreenable()) setFullScreen(rules()->checkFullScreen(info->state() & NET::FullScreen, !isMapped), false); } updateAllowedActions(true); // Set initial user time directly user_time = readUserTimeMapTimestamp(asn_valid ? &asn_id : NULL, asn_valid ? &asn_data : NULL, session); group()->updateUserTime(user_time); // And do what Client::updateUserTime() does // This should avoid flicker, because real restacking is done // only after manage() finishes because of blocking, but the window is shown sooner XLowerWindow(display(), frameId()); if (session && session->stackingOrder != -1) { sm_stacking_order = session->stackingOrder; workspace()->restoreSessionStackingOrder(this); } if (compositing()) // Sending ConfigureNotify is done when setting mapping state below, // Getting the first sync response means window is ready for compositing sendSyncRequest(); else ready_for_painting = true; // set to true in case compositing is turned on later. bug #160393 if (isShown(true) && !doNotShow) { if (isDialog()) Notify::raise(Notify::TransNew); if (isNormalWindow()) Notify::raise(Notify::New); bool allow; if (session) allow = session->active && (!workspace()->wasUserInteraction() || workspace()->activeClient() == NULL || workspace()->activeClient()->isDesktop()); else allow = workspace()->allowClientActivation(this, userTime(), false); if (!(isMapped || session)) { if (workspace()->sessionSaving()) { /* * If we get a new window during session saving, we assume it's some 'save file?' dialog * which the user really needs to see (to know why logout's stalled). * * Given the current session management protocol, I can't see a nicer way of doing this. * Someday I'd like to see a protocol that tells the windowmanager who's doing SessionInteract. */ needsSessionInteract = true; //show the parent too ClientList mainclients = mainClients(); for (ClientList::ConstIterator it = mainclients.constBegin(); it != mainclients.constEnd(); ++it) { (*it)->setSessionInteract(true); } } else if (allow) { // also force if activation is allowed if (!isOnCurrentDesktop()) { workspace()->setCurrentDesktop(desktop()); } /*if (!isOnCurrentActivity()) { workspace()->setCurrentActivity( activities().first() ); } FIXME no such method*/ } } resetShowingDesktop(options->isShowDesktopIsMinimizeAll()); if (isOnCurrentDesktop() && !isMapped && !allow && (!session || session->stackingOrder < 0)) workspace()->restackClientUnderActive(this); updateVisibility(); if (!isMapped) { if (allow && isOnCurrentDesktop()) { if (!isSpecialWindow()) if (options->focusPolicyIsReasonable() && wantsTabFocus()) workspace()->requestFocus(this); } else if (!session && !isSpecialWindow()) demandAttention(); } } else if (!doNotShow) // if ( !isShown( true ) && !doNotShow ) updateVisibility(); else // doNotShow hideClient(true); // SELI HACK !!! assert(mapping_state != Withdrawn); m_managed = true; blockGeometryUpdates(false); if (user_time == CurrentTime || user_time == -1U) { // No known user time, set something old user_time = xTime() - 1000000; if (user_time == CurrentTime || user_time == -1U) // Let's be paranoid user_time = xTime() - 1000000 + 10; } //sendSyntheticConfigureNotify(); // Done when setting mapping state delete session; ungrabXServer(); client_rules.discardTemporary(); applyWindowRules(); // Just in case workspace()->discardUsedWindowRules(this, false); // Remove ApplyNow rules updateWindowRules(Rules::All); // Was blocked while !isManaged() updateCompositeBlocking(true); // TODO: there's a small problem here - isManaged() depends on the mapping state, // but this client is not yet in Workspace's client list at this point, will // be only done in addClient() emit clientManaging(this); return true; } // Called only from manage() void Client::embedClient(Window w, const XWindowAttributes& attr) { assert(client == None); assert(frameId() == None); assert(wrapper == None); client = w; // We don't want the window to be destroyed when we are destroyed XAddToSaveSet(display(), client); XSelectInput(display(), client, NoEventMask); XUnmapWindow(display(), client); XWindowChanges wc; // Set the border width to 0 wc.border_width = 0; // TODO: Possibly save this, and also use it for initial configuring of the window XConfigureWindow(display(), client, CWBorderWidth, &wc); XSetWindowAttributes swa; swa.colormap = attr.colormap; swa.background_pixmap = None; swa.border_pixel = 0; Window frame = XCreateWindow(display(), rootWindow(), 0, 0, 1, 1, 0, attr.depth, InputOutput, attr.visual, CWColormap | CWBackPixmap | CWBorderPixel, &swa); setWindowHandles(client, frame); wrapper = XCreateWindow(display(), frame, 0, 0, 1, 1, 0, attr.depth, InputOutput, attr.visual, CWColormap | CWBackPixmap | CWBorderPixel, &swa); XDefineCursor(display(), frame, QCursor(Qt::ArrowCursor).handle()); // Some apps are stupid and don't define their own cursor - set the arrow one for them XDefineCursor(display(), wrapper, QCursor(Qt::ArrowCursor).handle()); XReparentWindow(display(), client, wrapper, 0, 0); XSelectInput(display(), frame, KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | KeymapStateMask | ButtonMotionMask | PointerMotionMask | EnterWindowMask | LeaveWindowMask | FocusChangeMask | ExposureMask | PropertyChangeMask | StructureNotifyMask | SubstructureRedirectMask); XSelectInput(display(), wrapper, ClientWinMask | SubstructureNotifyMask); XSelectInput(display(), client, FocusChangeMask | PropertyChangeMask | ColormapChangeMask | EnterWindowMask | LeaveWindowMask | KeyPressMask | KeyReleaseMask ); updateMouseGrab(); } // To accept "mainwindow#1" to "mainwindow#2" static QByteArray truncatedWindowRole(QByteArray a) { int i = a.indexOf('#'); if (i == -1) return a; QByteArray b(a); b.truncate(i); return b; } Client* Client::findAutogroupCandidate() const { // Attempt to find a similar window to the input. If we find multiple possibilities that are in // different groups then ignore all of them. This function is for automatic window grouping. Client *found = NULL; // See if the window has a group ID to match with QString wGId = rules()->checkAutogroupById(QString()); if (!wGId.isEmpty()) { foreach (Client *c, workspace()->clientList()) { if (activities() != c->activities()) continue; // don't cross activities if (wGId == c->rules()->checkAutogroupById(QString())) { if (found && found->tabGroup() != c->tabGroup()) { // We've found two, ignore both found = NULL; break; // Continue to the next test } found = c; } } if (found) return found; } // If this is a transient window don't take a guess if (isTransient()) return NULL; // If we don't have an ID take a guess if (rules()->checkAutogrouping(options->isAutogroupSimilarWindows())) { QByteArray wRole = truncatedWindowRole(windowRole()); foreach (Client *c, workspace()->clientList()) { if (desktop() != c->desktop() || activities() != c->activities()) continue; QByteArray wRoleB = truncatedWindowRole(c->windowRole()); if (resourceClass() == c->resourceClass() && // Same resource class wRole == wRoleB && // Same window role c->isNormalWindow()) { // Normal window TODO: Can modal windows be "normal"? if (found && found->tabGroup() != c->tabGroup()) // We've found two, ignore both return NULL; found = c; } } } return found; } } // namespace diff --git a/rules.cpp b/rules.cpp index 5b94e76d1..406c76962 100644 --- a/rules.cpp +++ b/rules.cpp @@ -1,1047 +1,1073 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2004 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "rules.h" #include #include #include #include #include #include #ifndef KCMRULES +#include #include "client.h" #include "workspace.h" #endif namespace KWin { Rules::Rules() : temporary_state(0) , wmclassmatch(UnimportantMatch) , wmclasscomplete(UnimportantMatch) , windowrolematch(UnimportantMatch) , titlematch(UnimportantMatch) , clientmachinematch(UnimportantMatch) , types(NET::AllTypesMask) , placementrule(UnusedForceRule) , positionrule(UnusedSetRule) , sizerule(UnusedSetRule) , minsizerule(UnusedForceRule) , maxsizerule(UnusedForceRule) , opacityactiverule(UnusedForceRule) , opacityinactiverule(UnusedForceRule) , ignorepositionrule(UnusedForceRule) , desktoprule(UnusedSetRule) + , screenrule(UnusedSetRule) , activityrule(UnusedSetRule) , typerule(UnusedForceRule) , maximizevertrule(UnusedSetRule) , maximizehorizrule(UnusedSetRule) , minimizerule(UnusedSetRule) , shaderule(UnusedSetRule) , skiptaskbarrule(UnusedSetRule) , skippagerrule(UnusedSetRule) , skipswitcherrule(UnusedSetRule) , aboverule(UnusedSetRule) , belowrule(UnusedSetRule) , fullscreenrule(UnusedSetRule) , noborderrule(UnusedSetRule) , blockcompositingrule(UnusedForceRule) , fsplevelrule(UnusedForceRule) , acceptfocusrule(UnusedForceRule) , closeablerule(UnusedForceRule) , autogrouprule(UnusedForceRule) , autogroupfgrule(UnusedForceRule) , autogroupidrule(UnusedForceRule) , strictgeometryrule(UnusedForceRule) , shortcutrule(UnusedSetRule) , disableglobalshortcutsrule(UnusedForceRule) { } Rules::Rules(const QString& str, bool temporary) : temporary_state(temporary ? 2 : 0) { KTemporaryFile file; if (file.open()) { QByteArray s = str.toUtf8(); file.write(s.data(), s.length()); } file.flush(); KConfig cfg(file.fileName(), KConfig::SimpleConfig); readFromCfg(cfg.group(QString())); if (description.isEmpty()) description = "temporary"; } #define READ_MATCH_STRING( var, func ) \ var = cfg.readEntry( #var ) func; \ var##match = (StringMatch) qMax( FirstStringMatch, \ qMin( LastStringMatch, static_cast< StringMatch >( cfg.readEntry( #var "match",0 )))); #define READ_SET_RULE( var, func, def ) \ var = func ( cfg.readEntry( #var, def)); \ var##rule = readSetRule( cfg, #var "rule" ); #define READ_SET_RULE_DEF( var , func, def ) \ var = func ( cfg.readEntry( #var, def )); \ var##rule = readSetRule( cfg, #var "rule" ); #define READ_FORCE_RULE( var, func, def) \ var = func ( cfg.readEntry( #var, def)); \ var##rule = readForceRule( cfg, #var "rule" ); #define READ_FORCE_RULE2( var, def, func, funcarg ) \ var = func ( cfg.readEntry( #var, def),funcarg ); \ var##rule = readForceRule( cfg, #var "rule" ); Rules::Rules(const KConfigGroup& cfg) : temporary_state(0) { readFromCfg(cfg); } static int limit0to4(int i) { return qMax(0, qMin(4, i)); } void Rules::readFromCfg(const KConfigGroup& cfg) { description = cfg.readEntry("Description"); if (description.isEmpty()) // capitalized first, lowercase for backwards compatibility description = cfg.readEntry("description"); READ_MATCH_STRING(wmclass, .toLower().toLatin1()); wmclasscomplete = cfg.readEntry("wmclasscomplete" , false); READ_MATCH_STRING(windowrole, .toLower().toLatin1()); READ_MATCH_STRING(title,); READ_MATCH_STRING(clientmachine, .toLower().toLatin1()); types = cfg.readEntry("types", uint(NET::AllTypesMask)); READ_FORCE_RULE2(placement, QString(), Placement::policyFromString, false); READ_SET_RULE_DEF(position, , invalidPoint); READ_SET_RULE(size, , QSize()); if (size.isEmpty() && sizerule != (SetRule)Remember) sizerule = UnusedSetRule; READ_FORCE_RULE(minsize, , QSize()); if (!minsize.isValid()) minsize = QSize(1, 1); READ_FORCE_RULE(maxsize, , QSize()); if (maxsize.isEmpty()) maxsize = QSize(32767, 32767); READ_FORCE_RULE(opacityactive, , 0); if (opacityactive < 0 || opacityactive > 100) opacityactive = 100; READ_FORCE_RULE(opacityinactive, , 0); if (opacityinactive < 0 || opacityinactive > 100) opacityinactive = 100; READ_FORCE_RULE(ignoreposition, , false); READ_SET_RULE(desktop, , 0); + READ_SET_RULE(screen, , 0); READ_SET_RULE(activity, , QString()); type = readType(cfg, "type"); typerule = type != NET::Unknown ? readForceRule(cfg, "typerule") : UnusedForceRule; READ_SET_RULE(maximizevert, , false); READ_SET_RULE(maximizehoriz, , false); READ_SET_RULE(minimize, , false); READ_SET_RULE(shade, , false); READ_SET_RULE(skiptaskbar, , false); READ_SET_RULE(skippager, , false); READ_SET_RULE(skipswitcher, , false); READ_SET_RULE(above, , false); READ_SET_RULE(below, , false); READ_SET_RULE(fullscreen, , false); READ_SET_RULE(noborder, , false); READ_FORCE_RULE(blockcompositing, , false); READ_FORCE_RULE(fsplevel, limit0to4, 0); // fsp is 0-4 READ_FORCE_RULE(acceptfocus, , false); READ_FORCE_RULE(closeable, , false); READ_FORCE_RULE(autogroup, , false); READ_FORCE_RULE(autogroupfg, , true); READ_FORCE_RULE(autogroupid, , QString()); READ_FORCE_RULE(strictgeometry, , false); READ_SET_RULE(shortcut, , QString()); READ_FORCE_RULE(disableglobalshortcuts, , false); } #undef READ_MATCH_STRING #undef READ_SET_RULE #undef READ_FORCE_RULE #undef READ_FORCE_RULE2 #define WRITE_MATCH_STRING( var, cast, force ) \ if ( !var.isEmpty() || force ) \ { \ cfg.writeEntry( #var, cast var ); \ cfg.writeEntry( #var "match", (int)var##match ); \ } \ else \ { \ cfg.deleteEntry( #var ); \ cfg.deleteEntry( #var "match" ); \ } #define WRITE_SET_RULE( var, func ) \ if ( var##rule != UnusedSetRule ) \ { \ cfg.writeEntry( #var, func ( var )); \ cfg.writeEntry( #var "rule", (int)var##rule ); \ } \ else \ { \ cfg.deleteEntry( #var ); \ cfg.deleteEntry( #var "rule" ); \ } #define WRITE_FORCE_RULE( var, func ) \ if ( var##rule != UnusedForceRule ) \ { \ cfg.writeEntry( #var, func ( var )); \ cfg.writeEntry( #var "rule", (int)var##rule ); \ } \ else \ { \ cfg.deleteEntry( #var ); \ cfg.deleteEntry( #var "rule" ); \ } void Rules::write(KConfigGroup& cfg) const { cfg.writeEntry("Description", description); // always write wmclass WRITE_MATCH_STRING(wmclass, (const char*), true); cfg.writeEntry("wmclasscomplete", wmclasscomplete); WRITE_MATCH_STRING(windowrole, (const char*), false); WRITE_MATCH_STRING(title, , false); WRITE_MATCH_STRING(clientmachine, (const char*), false); if (types != NET::AllTypesMask) cfg.writeEntry("types", uint(types)); else cfg.deleteEntry("types"); WRITE_FORCE_RULE(placement, Placement::policyToString); WRITE_SET_RULE(position,); WRITE_SET_RULE(size,); WRITE_FORCE_RULE(minsize,); WRITE_FORCE_RULE(maxsize,); WRITE_FORCE_RULE(opacityactive,); WRITE_FORCE_RULE(opacityinactive,); WRITE_FORCE_RULE(ignoreposition,); WRITE_SET_RULE(desktop,); + WRITE_SET_RULE(screen,); WRITE_SET_RULE(activity,); WRITE_FORCE_RULE(type, int); WRITE_SET_RULE(maximizevert,); WRITE_SET_RULE(maximizehoriz,); WRITE_SET_RULE(minimize,); WRITE_SET_RULE(shade,); WRITE_SET_RULE(skiptaskbar,); WRITE_SET_RULE(skippager,); WRITE_SET_RULE(skipswitcher,); WRITE_SET_RULE(above,); WRITE_SET_RULE(below,); WRITE_SET_RULE(fullscreen,); WRITE_SET_RULE(noborder,); WRITE_FORCE_RULE(blockcompositing,); WRITE_FORCE_RULE(fsplevel,); WRITE_FORCE_RULE(acceptfocus,); WRITE_FORCE_RULE(closeable,); WRITE_FORCE_RULE(autogroup,); WRITE_FORCE_RULE(autogroupfg,); WRITE_FORCE_RULE(autogroupid,); WRITE_FORCE_RULE(strictgeometry,); WRITE_SET_RULE(shortcut,); WRITE_FORCE_RULE(disableglobalshortcuts,); } #undef WRITE_MATCH_STRING #undef WRITE_SET_RULE #undef WRITE_FORCE_RULE // returns true if it doesn't affect anything bool Rules::isEmpty() const { return(placementrule == UnusedForceRule && positionrule == UnusedSetRule && sizerule == UnusedSetRule && minsizerule == UnusedForceRule && maxsizerule == UnusedForceRule && opacityactiverule == UnusedForceRule && opacityinactiverule == UnusedForceRule && ignorepositionrule == UnusedForceRule && desktoprule == UnusedSetRule + && screenrule == UnusedSetRule && activityrule == UnusedSetRule && typerule == UnusedForceRule && maximizevertrule == UnusedSetRule && maximizehorizrule == UnusedSetRule && minimizerule == UnusedSetRule && shaderule == UnusedSetRule && skiptaskbarrule == UnusedSetRule && skippagerrule == UnusedSetRule && skipswitcherrule == UnusedSetRule && aboverule == UnusedSetRule && belowrule == UnusedSetRule && fullscreenrule == UnusedSetRule && noborderrule == UnusedSetRule && blockcompositingrule == UnusedForceRule && fsplevelrule == UnusedForceRule && acceptfocusrule == UnusedForceRule && closeablerule == UnusedForceRule && autogrouprule == UnusedForceRule && autogroupfgrule == UnusedForceRule && autogroupidrule == UnusedForceRule && strictgeometryrule == UnusedForceRule && shortcutrule == UnusedSetRule && disableglobalshortcutsrule == UnusedForceRule); } Rules::SetRule Rules::readSetRule(const KConfigGroup& cfg, const QString& key) { int v = cfg.readEntry(key, 0); if (v >= DontAffect && v <= ForceTemporarily) return static_cast< SetRule >(v); return UnusedSetRule; } Rules::ForceRule Rules::readForceRule(const KConfigGroup& cfg, const QString& key) { int v = cfg.readEntry(key, 0); if (v == DontAffect || v == Force || v == ForceTemporarily) return static_cast< ForceRule >(v); return UnusedForceRule; } NET::WindowType Rules::readType(const KConfigGroup& cfg, const QString& key) { int v = cfg.readEntry(key, 0); if (v >= NET::Normal && v <= NET::Splash) return static_cast< NET::WindowType >(v); return NET::Unknown; } bool Rules::matchType(NET::WindowType match_type) const { if (types != NET::AllTypesMask) { if (match_type == NET::Unknown) match_type = NET::Normal; // NET::Unknown->NET::Normal is only here for matching if (!NET::typeMatchesMask(match_type, types)) return false; } return true; } bool Rules::matchWMClass(const QByteArray& match_class, const QByteArray& match_name) const { if (wmclassmatch != UnimportantMatch) { // TODO optimize? QByteArray cwmclass = wmclasscomplete ? match_name + ' ' + match_class : match_class; if (wmclassmatch == RegExpMatch && QRegExp(wmclass).indexIn(cwmclass) == -1) return false; if (wmclassmatch == ExactMatch && wmclass != cwmclass) return false; if (wmclassmatch == SubstringMatch && !cwmclass.contains(wmclass)) return false; } return true; } bool Rules::matchRole(const QByteArray& match_role) const { if (windowrolematch != UnimportantMatch) { if (windowrolematch == RegExpMatch && QRegExp(windowrole).indexIn(match_role) == -1) return false; if (windowrolematch == ExactMatch && windowrole != match_role) return false; if (windowrolematch == SubstringMatch && !match_role.contains(windowrole)) return false; } return true; } bool Rules::matchTitle(const QString& match_title) const { if (titlematch != UnimportantMatch) { if (titlematch == RegExpMatch && QRegExp(title).indexIn(match_title) == -1) return false; if (titlematch == ExactMatch && title != match_title) return false; if (titlematch == SubstringMatch && !match_title.contains(title)) return false; } return true; } bool Rules::matchClientMachine(const QByteArray& match_machine) const { if (clientmachinematch != UnimportantMatch) { // if it's localhost, check also "localhost" before checking hostname if (match_machine != "localhost" && isLocalMachine(match_machine) && matchClientMachine("localhost")) return true; if (clientmachinematch == RegExpMatch && QRegExp(clientmachine).indexIn(match_machine) == -1) return false; if (clientmachinematch == ExactMatch && clientmachine != match_machine) return false; if (clientmachinematch == SubstringMatch && !match_machine.contains(clientmachine)) return false; } return true; } #ifndef KCMRULES bool Rules::match(const Client* c) const { if (!matchType(c->windowType(true))) return false; if (!matchWMClass(c->resourceClass(), c->resourceName())) return false; if (!matchRole(c->windowRole())) return false; if (!matchTitle(c->caption(false))) return false; if (!matchClientMachine(c->wmClientMachine(false))) return false; return true; } #define NOW_REMEMBER(_T_, _V_) ((selection & _T_) && (_V_##rule == (SetRule)Remember)) bool Rules::update(Client* c, int selection) { // TODO check this setting is for this client ? bool updated = false; if NOW_REMEMBER(Position, position) { if (!c->isFullScreen()) { QPoint new_pos = position; // don't use the position in the direction which is maximized if ((c->maximizeMode() & MaximizeHorizontal) == 0) new_pos.setX(c->pos().x()); if ((c->maximizeMode() & MaximizeVertical) == 0) new_pos.setY(c->pos().y()); updated = updated || position != new_pos; position = new_pos; } } if NOW_REMEMBER(Size, size) { if (!c->isFullScreen()) { QSize new_size = size; // don't use the position in the direction which is maximized if ((c->maximizeMode() & MaximizeHorizontal) == 0) new_size.setWidth(c->size().width()); if ((c->maximizeMode() & MaximizeVertical) == 0) new_size.setHeight(c->size().height()); updated = updated || size != new_size; size = new_size; } } if NOW_REMEMBER(Desktop, desktop) { updated = updated || desktop != c->desktop(); desktop = c->desktop(); } + if NOW_REMEMBER(Screen, screen) { + updated = updated || screen != c->screen(); + screen = c->screen(); + } if NOW_REMEMBER(Activity, activity) { // TODO: ivan - multiple activities support const QString & joinedActivities = c->activities().join(","); updated = updated || activity != joinedActivities; activity = joinedActivities; } if NOW_REMEMBER(MaximizeVert, maximizevert) { updated = updated || maximizevert != bool(c->maximizeMode() & MaximizeVertical); maximizevert = c->maximizeMode() & MaximizeVertical; } if NOW_REMEMBER(MaximizeHoriz, maximizehoriz) { updated = updated || maximizehoriz != bool(c->maximizeMode() & MaximizeHorizontal); maximizehoriz = c->maximizeMode() & MaximizeHorizontal; } if NOW_REMEMBER(Minimize, minimize) { updated = updated || minimize != c->isMinimized(); minimize = c->isMinimized(); } if NOW_REMEMBER(Shade, shade) { updated = updated || (shade != (c->shadeMode() != ShadeNone)); shade = c->shadeMode() != ShadeNone; } if NOW_REMEMBER(SkipTaskbar, skiptaskbar) { updated = updated || skiptaskbar != c->skipTaskbar(); skiptaskbar = c->skipTaskbar(); } if NOW_REMEMBER(SkipPager, skippager) { updated = updated || skippager != c->skipPager(); skippager = c->skipPager(); } if NOW_REMEMBER(SkipSwitcher, skipswitcher) { updated = updated || skipswitcher != c->skipSwitcher(); skipswitcher = c->skipSwitcher(); } if NOW_REMEMBER(Above, above) { updated = updated || above != c->keepAbove(); above = c->keepAbove(); } if NOW_REMEMBER(Below, below) { updated = updated || below != c->keepBelow(); below = c->keepBelow(); } if NOW_REMEMBER(Fullscreen, fullscreen) { updated = updated || fullscreen != c->isFullScreen(); fullscreen = c->isFullScreen(); } if NOW_REMEMBER(NoBorder, noborder) { updated = updated || noborder != c->noBorder(); noborder = c->noBorder(); } if NOW_REMEMBER(OpacityActive, opacityactive) { // TODO } if NOW_REMEMBER(OpacityInactive, opacityinactive) { // TODO } return updated; } #undef NOW_REMEMBER #define APPLY_RULE( var, name, type ) \ bool Rules::apply##name( type& arg, bool init ) const \ { \ if ( checkSetRule( var##rule, init )) \ arg = this->var; \ return checkSetStop( var##rule ); \ } #define APPLY_FORCE_RULE( var, name, type ) \ bool Rules::apply##name( type& arg ) const \ { \ if ( checkForceRule( var##rule )) \ arg = this->var; \ return checkForceStop( var##rule ); \ } APPLY_FORCE_RULE(placement, Placement, Placement::Policy) bool Rules::applyGeometry(QRect& rect, bool init) const { QPoint p = rect.topLeft(); QSize s = rect.size(); bool ret = false; // no short-circuiting if (applyPosition(p, init)) { rect.moveTopLeft(p); ret = true; } if (applySize(s, init)) { rect.setSize(s); ret = true; } return ret; } bool Rules::applyPosition(QPoint& pos, bool init) const { if (this->position != invalidPoint && checkSetRule(positionrule, init)) pos = this->position; return checkSetStop(positionrule); } bool Rules::applySize(QSize& s, bool init) const { if (this->size.isValid() && checkSetRule(sizerule, init)) s = this->size; return checkSetStop(sizerule); } APPLY_FORCE_RULE(minsize, MinSize, QSize) APPLY_FORCE_RULE(maxsize, MaxSize, QSize) APPLY_FORCE_RULE(opacityactive, OpacityActive, int) APPLY_FORCE_RULE(opacityinactive, OpacityInactive, int) APPLY_FORCE_RULE(ignoreposition, IgnorePosition, bool) // the cfg. entry needs to stay named the say for backwards compatibility bool Rules::applyIgnoreGeometry(bool& ignore) const { return applyIgnorePosition(ignore); } APPLY_RULE(desktop, Desktop, int) +APPLY_RULE(screen, Screen, int) APPLY_RULE(activity, Activity, QString) APPLY_FORCE_RULE(type, Type, NET::WindowType) bool Rules::applyMaximizeHoriz(MaximizeMode& mode, bool init) const { if (checkSetRule(maximizehorizrule, init)) mode = static_cast< MaximizeMode >((maximizehoriz ? MaximizeHorizontal : 0) | (mode & MaximizeVertical)); return checkSetStop(maximizehorizrule); } bool Rules::applyMaximizeVert(MaximizeMode& mode, bool init) const { if (checkSetRule(maximizevertrule, init)) mode = static_cast< MaximizeMode >((maximizevert ? MaximizeVertical : 0) | (mode & MaximizeHorizontal)); return checkSetStop(maximizevertrule); } APPLY_RULE(minimize, Minimize, bool) bool Rules::applyShade(ShadeMode& sh, bool init) const { if (checkSetRule(shaderule, init)) { if (!this->shade) sh = ShadeNone; if (this->shade && sh == ShadeNone) sh = ShadeNormal; } return checkSetStop(shaderule); } APPLY_RULE(skiptaskbar, SkipTaskbar, bool) APPLY_RULE(skippager, SkipPager, bool) APPLY_RULE(skipswitcher, SkipSwitcher, bool) APPLY_RULE(above, KeepAbove, bool) APPLY_RULE(below, KeepBelow, bool) APPLY_RULE(fullscreen, FullScreen, bool) APPLY_RULE(noborder, NoBorder, bool) APPLY_FORCE_RULE(blockcompositing, BlockCompositing, bool) APPLY_FORCE_RULE(fsplevel, FSP, int) APPLY_FORCE_RULE(acceptfocus, AcceptFocus, bool) APPLY_FORCE_RULE(closeable, Closeable, bool) APPLY_FORCE_RULE(autogroup, Autogrouping, bool) APPLY_FORCE_RULE(autogroupfg, AutogroupInForeground, bool) APPLY_FORCE_RULE(autogroupid, AutogroupById, QString) APPLY_FORCE_RULE(strictgeometry, StrictGeometry, bool) APPLY_RULE(shortcut, Shortcut, QString) APPLY_FORCE_RULE(disableglobalshortcuts, DisableGlobalShortcuts, bool) #undef APPLY_RULE #undef APPLY_FORCE_RULE bool Rules::isTemporary() const { return temporary_state > 0; } bool Rules::discardTemporary(bool force) { if (temporary_state == 0) // not temporary return false; if (force || --temporary_state == 0) { // too old delete this; return true; } return false; } #define DISCARD_USED_SET_RULE( var ) \ do { \ if ( var##rule == ( SetRule ) ApplyNow || ( withdrawn && var##rule == ( SetRule ) ForceTemporarily )) \ var##rule = UnusedSetRule; \ } while ( false ) #define DISCARD_USED_FORCE_RULE( var ) \ do { \ if ( withdrawn && var##rule == ( ForceRule ) ForceTemporarily ) \ var##rule = UnusedForceRule; \ } while ( false ) void Rules::discardUsed(bool withdrawn) { DISCARD_USED_FORCE_RULE(placement); DISCARD_USED_SET_RULE(position); DISCARD_USED_SET_RULE(size); DISCARD_USED_FORCE_RULE(minsize); DISCARD_USED_FORCE_RULE(maxsize); DISCARD_USED_FORCE_RULE(opacityactive); DISCARD_USED_FORCE_RULE(opacityinactive); DISCARD_USED_FORCE_RULE(ignoreposition); DISCARD_USED_SET_RULE(desktop); + DISCARD_USED_SET_RULE(screen); DISCARD_USED_SET_RULE(activity); DISCARD_USED_FORCE_RULE(type); DISCARD_USED_SET_RULE(maximizevert); DISCARD_USED_SET_RULE(maximizehoriz); DISCARD_USED_SET_RULE(minimize); DISCARD_USED_SET_RULE(shade); DISCARD_USED_SET_RULE(skiptaskbar); DISCARD_USED_SET_RULE(skippager); DISCARD_USED_SET_RULE(skipswitcher); DISCARD_USED_SET_RULE(above); DISCARD_USED_SET_RULE(below); DISCARD_USED_SET_RULE(fullscreen); DISCARD_USED_SET_RULE(noborder); DISCARD_USED_FORCE_RULE(blockcompositing); DISCARD_USED_FORCE_RULE(fsplevel); DISCARD_USED_FORCE_RULE(acceptfocus); DISCARD_USED_FORCE_RULE(closeable); DISCARD_USED_FORCE_RULE(autogroup); DISCARD_USED_FORCE_RULE(autogroupfg); DISCARD_USED_FORCE_RULE(autogroupid); DISCARD_USED_FORCE_RULE(strictgeometry); DISCARD_USED_SET_RULE(shortcut); DISCARD_USED_FORCE_RULE(disableglobalshortcuts); } #undef DISCARD_USED_SET_RULE #undef DISCARD_USED_FORCE_RULE #endif QDebug& operator<<(QDebug& stream, const Rules* r) { return stream << "[" << r->description << ":" << r->wmclass << "]" ; } #ifndef KCMRULES void WindowRules::discardTemporary() { QVector< Rules* >::Iterator it2 = rules.begin(); for (QVector< Rules* >::Iterator it = rules.begin(); it != rules.end(); ) { if ((*it)->discardTemporary(true)) ++it; else { *it2++ = *it++; } } rules.erase(it2, rules.end()); } void WindowRules::update(Client* c, int selection) { bool updated = false; for (QVector< Rules* >::ConstIterator it = rules.constBegin(); it != rules.constEnd(); ++it) if ((*it)->update(c, selection)) // no short-circuiting here updated = true; if (updated) Workspace::self()->rulesUpdated(); } #define CHECK_RULE( rule, type ) \ type WindowRules::check##rule( type arg, bool init ) const \ { \ if ( rules.count() == 0 ) \ return arg; \ type ret = arg; \ for ( QVector< Rules* >::ConstIterator it = rules.constBegin(); \ it != rules.constEnd(); \ ++it ) \ { \ if ( (*it)->apply##rule( ret, init )) \ break; \ } \ return ret; \ } #define CHECK_FORCE_RULE( rule, type ) \ type WindowRules::check##rule( type arg ) const \ { \ if ( rules.count() == 0 ) \ return arg; \ type ret = arg; \ for ( QVector< Rules* >::ConstIterator it = rules.begin(); \ it != rules.end(); \ ++it ) \ { \ if ( (*it)->apply##rule( ret )) \ break; \ } \ return ret; \ } CHECK_FORCE_RULE(Placement, Placement::Policy) QRect WindowRules::checkGeometry(QRect rect, bool init) const { return QRect(checkPosition(rect.topLeft(), init), checkSize(rect.size(), init)); } CHECK_RULE(Position, QPoint) CHECK_RULE(Size, QSize) CHECK_FORCE_RULE(MinSize, QSize) CHECK_FORCE_RULE(MaxSize, QSize) CHECK_FORCE_RULE(OpacityActive, int) CHECK_FORCE_RULE(OpacityInactive, int) CHECK_FORCE_RULE(IgnorePosition, bool) bool WindowRules::checkIgnoreGeometry(bool ignore) const { return checkIgnorePosition(ignore); } CHECK_RULE(Desktop, int) CHECK_RULE(Activity, QString) CHECK_FORCE_RULE(Type, NET::WindowType) CHECK_RULE(MaximizeVert, KDecorationDefines::MaximizeMode) CHECK_RULE(MaximizeHoriz, KDecorationDefines::MaximizeMode) KDecorationDefines::MaximizeMode WindowRules::checkMaximize(MaximizeMode mode, bool init) const { bool vert = checkMaximizeVert(mode, init) & MaximizeVertical; bool horiz = checkMaximizeHoriz(mode, init) & MaximizeHorizontal; return static_cast< MaximizeMode >((vert ? MaximizeVertical : 0) | (horiz ? MaximizeHorizontal : 0)); } +int WindowRules::checkScreen(int screen, bool init) const +{ + if ( rules.count() == 0 ) + return screen; + int ret = screen; + for ( QVector< Rules* >::ConstIterator it = rules.constBegin(); it != rules.constEnd(); ++it ) { + if ( (*it)->applyScreen( ret, init )) + break; + } + if (ret >= QApplication::desktop()->screenCount()) + ret = screen; + return ret; +} + CHECK_RULE(Minimize, bool) CHECK_RULE(Shade, ShadeMode) CHECK_RULE(SkipTaskbar, bool) CHECK_RULE(SkipPager, bool) CHECK_RULE(SkipSwitcher, bool) CHECK_RULE(KeepAbove, bool) CHECK_RULE(KeepBelow, bool) CHECK_RULE(FullScreen, bool) CHECK_RULE(NoBorder, bool) CHECK_FORCE_RULE(BlockCompositing, bool) CHECK_FORCE_RULE(FSP, int) CHECK_FORCE_RULE(AcceptFocus, bool) CHECK_FORCE_RULE(Closeable, bool) CHECK_FORCE_RULE(Autogrouping, bool) CHECK_FORCE_RULE(AutogroupInForeground, bool) CHECK_FORCE_RULE(AutogroupById, QString) CHECK_FORCE_RULE(StrictGeometry, bool) CHECK_RULE(Shortcut, QString) CHECK_FORCE_RULE(DisableGlobalShortcuts, bool) #undef CHECK_RULE #undef CHECK_FORCE_RULE // Client void Client::setupWindowRules(bool ignore_temporary) { client_rules = workspace()->findWindowRules(this, ignore_temporary); // check only after getting the rules, because there may be a rule forcing window type } // Applies Force, ForceTemporarily and ApplyNow rules // Used e.g. after the rules have been modified using the kcm. void Client::applyWindowRules() { // apply force rules // Placement - does need explicit update, just like some others below // Geometry : setGeometry() doesn't check rules QRect orig_geom = QRect(pos(), sizeForClientSize(clientSize())); // handle shading QRect geom = client_rules.checkGeometry(orig_geom); if (geom != orig_geom) setGeometry(geom); // MinSize, MaxSize handled by Geometry // IgnorePosition setDesktop(desktop()); + workspace()->sendClientToScreen(this, screen()); setOnActivities(activities()); // Type maximize(maximizeMode()); // Minimize : functions don't check, and there are two functions if (client_rules.checkMinimize(isMinimized())) minimize(); else unminimize(); setShade(shadeMode()); setSkipTaskbar(skipTaskbar(), true); setSkipPager(skipPager()); setSkipSwitcher(skipSwitcher()); setKeepAbove(keepAbove()); setKeepBelow(keepBelow()); setFullScreen(isFullScreen(), true); setNoBorder(noBorder()); // FSP // AcceptFocus : if (workspace()->mostRecentlyActivatedClient() == this && !client_rules.checkAcceptFocus(true)) workspace()->activateNextClient(this); // Closeable QSize s = adjustedSize(); if (s != size()) resizeWithChecks(s); // Autogrouping : Only checked on window manage // AutogroupInForeground : Only checked on window manage // AutogroupById : Only checked on window manage // StrictGeometry setShortcut(rules()->checkShortcut(shortcut().toString())); // see also Client::setActive() if (isActive()) { setOpacity(rules()->checkOpacityActive(qRound(opacity() * 100.0)) / 100.0); workspace()->disableGlobalShortcutsForClient(rules()->checkDisableGlobalShortcuts(false)); } else setOpacity(rules()->checkOpacityInactive(qRound(opacity() * 100.0)) / 100.0); } void Client::updateWindowRules(Rules::Types selection) { if (!isManaged()) // not fully setup yet return; if (workspace()->rulesUpdatesDisabled()) return; client_rules.update(this, selection); } void Client::finishWindowRules() { updateWindowRules(Rules::All); client_rules = WindowRules(); } // Workspace WindowRules Workspace::findWindowRules(const Client* c, bool ignore_temporary) { QVector< Rules* > ret; for (QList< Rules* >::Iterator it = rules.begin(); it != rules.end(); ) { if (ignore_temporary && (*it)->isTemporary()) { ++it; continue; } if ((*it)->match(c)) { Rules* rule = *it; kDebug(1212) << "Rule found:" << rule << ":" << c; if (rule->isTemporary()) it = rules.erase(it); else ++it; ret.append(rule); continue; } ++it; } return WindowRules(ret); } void Workspace::editWindowRules(Client* c, bool whole_app) { writeWindowRules(); QStringList args; args << "--wid" << QString::number(c->window()); if (whole_app) args << "--whole-app"; KToolInvocation::kdeinitExec("kwin_rules_dialog", args); } void Workspace::loadWindowRules() { while (!rules.isEmpty()) { delete rules.front(); rules.pop_front(); } KConfig cfg("kwinrulesrc", KConfig::NoGlobals); int count = cfg.group("General").readEntry("count", 0); for (int i = 1; i <= count; ++i) { KConfigGroup cg(&cfg, QString::number(i)); Rules* rule = new Rules(cg); rules.append(rule); } } void Workspace::writeWindowRules() { rulesUpdatedTimer.stop(); KConfig cfg("kwinrulesrc", KConfig::NoGlobals); QStringList groups = cfg.groupList(); for (QStringList::ConstIterator it = groups.constBegin(); it != groups.constEnd(); ++it) cfg.deleteGroup(*it); cfg.group("General").writeEntry("count", rules.count()); int i = 1; for (QList< Rules* >::ConstIterator it = rules.constBegin(); it != rules.constEnd(); ++it) { if ((*it)->isTemporary()) continue; KConfigGroup cg(&cfg, QString::number(i)); (*it)->write(cg); ++i; } } void Workspace::gotTemporaryRulesMessage(const QString& message) { bool was_temporary = false; for (QList< Rules* >::ConstIterator it = rules.constBegin(); it != rules.constEnd(); ++it) if ((*it)->isTemporary()) was_temporary = true; Rules* rule = new Rules(message, true); rules.prepend(rule); // highest priority first if (!was_temporary) QTimer::singleShot(60000, this, SLOT(cleanupTemporaryRules())); } void Workspace::cleanupTemporaryRules() { bool has_temporary = false; for (QList< Rules* >::Iterator it = rules.begin(); it != rules.end(); ) { if ((*it)->discardTemporary(false)) it = rules.erase(it); else { if ((*it)->isTemporary()) has_temporary = true; ++it; } } if (has_temporary) QTimer::singleShot(60000, this, SLOT(cleanupTemporaryRules())); } void Workspace::discardUsedWindowRules(Client* c, bool withdrawn) { bool updated = false; for (QList< Rules* >::Iterator it = rules.begin(); it != rules.end(); ) { if (c->rules()->contains(*it)) { updated = true; (*it)->discardUsed(withdrawn); if ((*it)->isEmpty()) { c->removeRule(*it); Rules* r = *it; it = rules.erase(it); delete r; continue; } } ++it; } if (updated) rulesUpdated(); } void Workspace::rulesUpdated() { rulesUpdatedTimer.setSingleShot(true); rulesUpdatedTimer.start(1000); } void Workspace::disableRulesUpdates(bool disable) { rules_updates_disabled = disable; if (!disable) { foreach (Client * c, clients) c->updateWindowRules(Rules::All); } } #endif } // namespace diff --git a/rules.h b/rules.h index bf299f0a1..418311efc 100644 --- a/rules.h +++ b/rules.h @@ -1,340 +1,344 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2004 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_RULES_H #define KWIN_RULES_H #include #include #include #include #include "placement.h" #include #include "options.h" #include "utils.h" class KConfig; namespace KWin { class Client; class Rules; #ifndef KCMRULES // only for kwin core class WindowRules : public KDecorationDefines { public: WindowRules(const QVector< Rules* >& rules); WindowRules(); void update(Client*, int selection); void discardTemporary(); bool contains(const Rules* rule) const; void remove(Rules* rule); Placement::Policy checkPlacement(Placement::Policy placement) const; QRect checkGeometry(QRect rect, bool init = false) const; // use 'invalidPoint' with checkPosition, unlike QSize() and QRect(), QPoint() is a valid point QPoint checkPosition(QPoint pos, bool init = false) const; QSize checkSize(QSize s, bool init = false) const; QSize checkMinSize(QSize s) const; QSize checkMaxSize(QSize s) const; int checkOpacityActive(int s) const; int checkOpacityInactive(int s) const; bool checkIgnoreGeometry(bool ignore) const; int checkDesktop(int desktop, bool init = false) const; + int checkScreen(int screen, bool init = false) const; QString checkActivity(QString activity, bool init = false) const; NET::WindowType checkType(NET::WindowType type) const; MaximizeMode checkMaximize(MaximizeMode mode, bool init = false) const; bool checkMinimize(bool minimized, bool init = false) const; ShadeMode checkShade(ShadeMode shade, bool init = false) const; bool checkSkipTaskbar(bool skip, bool init = false) const; bool checkSkipPager(bool skip, bool init = false) const; bool checkSkipSwitcher(bool skip, bool init = false) const; bool checkKeepAbove(bool above, bool init = false) const; bool checkKeepBelow(bool below, bool init = false) const; bool checkFullScreen(bool fs, bool init = false) const; bool checkNoBorder(bool noborder, bool init = false) const; bool checkBlockCompositing(bool block) const; int checkFSP(int fsp) const; bool checkAcceptFocus(bool focus) const; bool checkCloseable(bool closeable) const; bool checkAutogrouping(bool autogroup) const; bool checkAutogroupInForeground(bool fg) const; QString checkAutogroupById(QString id) const; bool checkStrictGeometry(bool strict) const; QString checkShortcut(QString s, bool init = false) const; bool checkDisableGlobalShortcuts(bool disable) const; bool checkIgnorePosition(bool ignore) const; // obsolete private: MaximizeMode checkMaximizeVert(MaximizeMode mode, bool init) const; MaximizeMode checkMaximizeHoriz(MaximizeMode mode, bool init) const; QVector< Rules* > rules; }; #endif class Rules : public KDecorationDefines { public: Rules(); Rules(const KConfigGroup&); Rules(const QString&, bool temporary); enum Type { Position = 1<<0, Size = 1<<1, Desktop = 1<<2, MaximizeVert = 1<<3, MaximizeHoriz = 1<<4, Minimize = 1<<5, Shade = 1<<6, SkipTaskbar = 1<<7, SkipPager = 1<<8, SkipSwitcher = 1<<9, Above = 1<<10, Below = 1<<11, Fullscreen = 1<<12, NoBorder = 1<<13, OpacityActive = 1<<14, OpacityInactive = 1<<15, - Activity = 1<<16, All = 0xffffffff + Activity = 1<<16, Screen = 1<<17, All = 0xffffffff }; Q_DECLARE_FLAGS(Types, Type) void write(KConfigGroup&) const; bool isEmpty() const; #ifndef KCMRULES void discardUsed(bool withdrawn); bool match(const Client* c) const; bool update(Client*, int selection); bool isTemporary() const; bool discardTemporary(bool force); // removes if temporary and forced or too old bool applyPlacement(Placement::Policy& placement) const; bool applyGeometry(QRect& rect, bool init) const; // use 'invalidPoint' with applyPosition, unlike QSize() and QRect(), QPoint() is a valid point bool applyPosition(QPoint& pos, bool init) const; bool applySize(QSize& s, bool init) const; bool applyMinSize(QSize& s) const; bool applyMaxSize(QSize& s) const; bool applyOpacityActive(int& s) const; bool applyOpacityInactive(int& s) const; bool applyIgnoreGeometry(bool& ignore) const; bool applyDesktop(int& desktop, bool init) const; + bool applyScreen(int& desktop, bool init) const; bool applyActivity(QString& activity, bool init) const; bool applyType(NET::WindowType& type) const; bool applyMaximizeVert(MaximizeMode& mode, bool init) const; bool applyMaximizeHoriz(MaximizeMode& mode, bool init) const; bool applyMinimize(bool& minimized, bool init) const; bool applyShade(ShadeMode& shade, bool init) const; bool applySkipTaskbar(bool& skip, bool init) const; bool applySkipPager(bool& skip, bool init) const; bool applySkipSwitcher(bool& skip, bool init) const; bool applyKeepAbove(bool& above, bool init) const; bool applyKeepBelow(bool& below, bool init) const; bool applyFullScreen(bool& fs, bool init) const; bool applyNoBorder(bool& noborder, bool init) const; bool applyBlockCompositing(bool& block) const; bool applyFSP(int& fsp) const; bool applyAcceptFocus(bool& focus) const; bool applyCloseable(bool& closeable) const; bool applyAutogrouping(bool& autogroup) const; bool applyAutogroupInForeground(bool& fg) const; bool applyAutogroupById(QString& id) const; bool applyStrictGeometry(bool& strict) const; bool applyShortcut(QString& shortcut, bool init) const; bool applyDisableGlobalShortcuts(bool& disable) const; bool applyIgnorePosition(bool& ignore) const; // obsolete private: #endif bool matchType(NET::WindowType match_type) const; bool matchWMClass(const QByteArray& match_class, const QByteArray& match_name) const; bool matchRole(const QByteArray& match_role) const; bool matchTitle(const QString& match_title) const; bool matchClientMachine(const QByteArray& match_machine) const; // All these values are saved to the cfg file, and are also used in kstart! enum { Unused = 0, DontAffect, // use the default value Force, // force the given value Apply, // apply only after initial mapping Remember, // like apply, and remember the value when the window is withdrawn ApplyNow, // apply immediatelly, then forget the setting ForceTemporarily // apply and force until the window is withdrawn }; enum SetRule { UnusedSetRule = Unused, SetRuleDummy = 256 // so that it's at least short int }; enum ForceRule { UnusedForceRule = Unused, ForceRuleDummy = 256 // so that it's at least short int }; enum StringMatch { FirstStringMatch, UnimportantMatch = FirstStringMatch, ExactMatch, SubstringMatch, RegExpMatch, LastStringMatch = RegExpMatch }; void readFromCfg(const KConfigGroup& cfg); static SetRule readSetRule(const KConfigGroup&, const QString& key); static ForceRule readForceRule(const KConfigGroup&, const QString& key); static NET::WindowType readType(const KConfigGroup&, const QString& key); #ifndef KCMRULES static bool checkSetRule(SetRule rule, bool init); static bool checkForceRule(ForceRule rule); static bool checkSetStop(SetRule rule); static bool checkForceStop(ForceRule rule); #endif int temporary_state; // e.g. for kstart QString description; QByteArray wmclass; StringMatch wmclassmatch; bool wmclasscomplete; QByteArray windowrole; StringMatch windowrolematch; QString title; StringMatch titlematch; QByteArray clientmachine; StringMatch clientmachinematch; unsigned long types; // types for matching Placement::Policy placement; ForceRule placementrule; QPoint position; SetRule positionrule; QSize size; SetRule sizerule; QSize minsize; ForceRule minsizerule; QSize maxsize; ForceRule maxsizerule; int opacityactive; ForceRule opacityactiverule; int opacityinactive; ForceRule opacityinactiverule; bool ignoreposition; ForceRule ignorepositionrule; int desktop; SetRule desktoprule; + int screen; + SetRule screenrule; QString activity; SetRule activityrule; NET::WindowType type; // type for setting ForceRule typerule; bool maximizevert; SetRule maximizevertrule; bool maximizehoriz; SetRule maximizehorizrule; bool minimize; SetRule minimizerule; bool shade; SetRule shaderule; bool skiptaskbar; SetRule skiptaskbarrule; bool skippager; SetRule skippagerrule; bool skipswitcher; SetRule skipswitcherrule; bool above; SetRule aboverule; bool below; SetRule belowrule; bool fullscreen; SetRule fullscreenrule; bool noborder; SetRule noborderrule; bool blockcompositing; ForceRule blockcompositingrule; int fsplevel; ForceRule fsplevelrule; bool acceptfocus; ForceRule acceptfocusrule; bool closeable; ForceRule closeablerule; bool autogroup; ForceRule autogrouprule; bool autogroupfg; ForceRule autogroupfgrule; QString autogroupid; ForceRule autogroupidrule; bool strictgeometry; ForceRule strictgeometryrule; QString shortcut; SetRule shortcutrule; bool disableglobalshortcuts; ForceRule disableglobalshortcutsrule; friend QDebug& operator<<(QDebug& stream, const Rules*); }; #ifndef KCMRULES inline bool Rules::checkSetRule(SetRule rule, bool init) { if (rule > (SetRule)DontAffect) { // Unused or DontAffect if (rule == (SetRule)Force || rule == (SetRule) ApplyNow || rule == (SetRule) ForceTemporarily || init) return true; } return false; } inline bool Rules::checkForceRule(ForceRule rule) { return rule == (ForceRule)Force || rule == (ForceRule) ForceTemporarily; } inline bool Rules::checkSetStop(SetRule rule) { return rule != UnusedSetRule; } inline bool Rules::checkForceStop(ForceRule rule) { return rule != UnusedForceRule; } inline WindowRules::WindowRules(const QVector< Rules* >& r) : rules(r) { } inline WindowRules::WindowRules() { } inline bool WindowRules::contains(const Rules* rule) const { return qFind(rules.begin(), rules.end(), rule) != rules.end(); } inline void WindowRules::remove(Rules* rule) { QVector< Rules* >::Iterator pos = qFind(rules.begin(), rules.end(), rule); if (pos != rules.end()) rules.erase(pos); } #endif QDebug& operator<<(QDebug& stream, const Rules*); } // namespace Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::Rules::Types) #endif diff --git a/workspace.cpp b/workspace.cpp index 258a23753..5b37aee2b 100644 --- a/workspace.cpp +++ b/workspace.cpp @@ -1,2243 +1,2244 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ //#define QT_CLEAN_NAMESPACE #include "workspace.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "client.h" #include "composite.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox.h" #endif #include "atoms.h" #include "placement.h" #include "notifications.h" #include "outline.h" #include "group.h" #include "rules.h" #include "dbusinterface.h" #include "unmanaged.h" #include "deleted.h" #include "effects.h" #include "overlaywindow.h" #include "useractions.h" #include #include #ifdef KWIN_BUILD_SCRIPTING #include "scripting/scripting.h" #endif #include #include #include #include #include #include #include #include #include namespace KWin { extern int screen_number; static const int KWIN_MAX_NUMBER_DESKTOPS = 20; Workspace* Workspace::_self = 0; //----------------------------------------------------------------------------- // Rikkus: This class is too complex. It needs splitting further. // It's a nightmare to understand, especially with so few comments :( // // Matthias: Feel free to ask me questions about it. Feel free to add // comments. I dissagree that further splittings makes it easier. 2500 // lines are not too much. It's the task that is complex, not the // code. //----------------------------------------------------------------------------- Workspace::Workspace(bool restore) : QObject(0) // Desktop layout , desktopCount_(0) // This is an invalid state , desktopGridSize_(1, 2) // Default to two rows , desktopGrid_(new int[2]) , currentDesktop_(0) #ifdef KWIN_BUILD_SCREENEDGES , m_screenEdgeOrientation(0) #endif , m_compositor(NULL) // Unsorted , active_popup(NULL) , active_popup_client(NULL) , temporaryRulesMessages("_KDE_NET_WM_TEMPORARY_RULES", NULL, false) , rules_updates_disabled(false) , active_client(0) , last_active_client(0) , most_recently_raised(0) , movingClient(0) , pending_take_activity(NULL) , active_screen(0) , delayfocus_client(0) , force_restacking(false) , x_stacking_dirty(true) , showing_desktop(false) , block_showing_desktop(0) , was_user_interaction(false) , session_saving(false) , block_focus(0) #ifdef KWIN_BUILD_TABBOX , tab_box(0) #endif , m_userActionsMenu(new UserActionsMenu(this)) , keys(0) , client_keys(NULL) , disable_shortcuts_keys(NULL) , client_keys_dialog(NULL) , client_keys_client(NULL) , global_shortcuts_disabled(false) , global_shortcuts_disabled_for_client(false) , workspaceInit(true) , startup(0) , set_active_client_recursion(0) , block_stacking_updates(0) , forced_global_mouse_grab(false) , transSlider(NULL) , transButton(NULL) , m_scripting(NULL) { // If KWin was already running it saved its configuration after loosing the selection -> Reread QFuture reparseConfigFuture = QtConcurrent::run(options, &Options::reparseConfiguration); // Initialize desktop grid array desktopGrid_[0] = 0; desktopGrid_[1] = 0; _self = this; // first initialize the extensions Extensions::init(); // PluginMgr needs access to the config file, so we need to wait for it for finishing reparseConfigFuture.waitForFinished(); options->loadConfig(); options->loadCompositingConfig(false); mgr = new PluginMgr; QX11Info info; default_colormap = DefaultColormap(display(), info.screen()); installed_colormap = default_colormap; connect(&temporaryRulesMessages, SIGNAL(gotMessage(QString)), this, SLOT(gotTemporaryRulesMessage(QString))); connect(&rulesUpdatedTimer, SIGNAL(timeout()), this, SLOT(writeWindowRules())); updateXTime(); // Needed for proper initialization of user_time in Client ctor delayFocusTimer = 0; if (restore) loadSessionInfo(); loadWindowRules(); // Call this before XSelectInput() on the root window startup = new KStartupInfo( KStartupInfo::DisableKWinModule | KStartupInfo::AnnounceSilenceChanges, this); // Select windowmanager privileges XSelectInput(display(), rootWindow(), KeyPressMask | PropertyChangeMask | ColormapChangeMask | SubstructureRedirectMask | SubstructureNotifyMask | FocusChangeMask | // For NotifyDetailNone ExposureMask ); #ifdef KWIN_BUILD_TABBOX // need to create the tabbox before compositing scene is setup tab_box = new TabBox::TabBox(this); #endif m_compositor = Compositor::createCompositor(this); connect(this, SIGNAL(currentDesktopChanged(int,KWin::Client*)), m_compositor, SLOT(addRepaintFull())); connect(m_compositor, SIGNAL(compositingToggled(bool)), SLOT(slotCompositingToggled())); new DBusInterface(this); // Compatibility long data = 1; XChangeProperty( display(), rootWindow(), atoms->kwin_running, atoms->kwin_running, 32, PropModeAppend, (unsigned char*)(&data), 1 ); client_keys = new KActionCollection(this); m_outline = new Outline(); initShortcuts(); init(); connect(QApplication::desktop(), SIGNAL(screenCountChanged(int)), &screenChangedTimer, SLOT(start())); connect(QApplication::desktop(), SIGNAL(resized(int)), &screenChangedTimer, SLOT(start())); #ifdef KWIN_BUILD_ACTIVITIES connect(&activityController_, SIGNAL(currentActivityChanged(QString)), SLOT(updateCurrentActivity(QString))); connect(&activityController_, SIGNAL(activityRemoved(QString)), SLOT(slotActivityRemoved(QString))); connect(&activityController_, SIGNAL(activityRemoved(QString)), SIGNAL(activityRemoved(QString))); connect(&activityController_, SIGNAL(activityAdded(QString)), SLOT(slotActivityAdded(QString))); connect(&activityController_, SIGNAL(activityAdded(QString)), SIGNAL(activityAdded(QString))); connect(&activityController_, SIGNAL(currentActivityChanged(QString)), SIGNAL(currentActivityChanged(QString))); #endif connect(&screenChangedTimer, SIGNAL(timeout()), SLOT(screenChangeTimeout())); screenChangedTimer.setSingleShot(true); screenChangedTimer.setInterval(100); } void Workspace::screenChangeTimeout() { kDebug() << "It is time to call desktopResized"; desktopResized(); } void Workspace::init() { #ifdef KWIN_BUILD_SCREENEDGES m_screenEdge.init(); #endif // Not used yet //topDock = 0L; //maximizedWindowCounter = 0; supportWindow = new QWidget(NULL, Qt::X11BypassWindowManagerHint); XLowerWindow(display(), supportWindow->winId()); // See usage in layers.cpp XSetWindowAttributes attr; attr.override_redirect = 1; null_focus_window = XCreateWindow(display(), rootWindow(), -1, -1, 1, 1, 0, CopyFromParent, InputOnly, CopyFromParent, CWOverrideRedirect, &attr); XMapWindow(display(), null_focus_window); unsigned long protocols[5] = { NET::Supported | NET::SupportingWMCheck | NET::ClientList | NET::ClientListStacking | NET::DesktopGeometry | NET::NumberOfDesktops | NET::CurrentDesktop | NET::ActiveWindow | NET::WorkArea | NET::CloseWindow | NET::DesktopNames | NET::WMName | NET::WMVisibleName | NET::WMDesktop | NET::WMWindowType | NET::WMState | NET::WMStrut | NET::WMIconGeometry | NET::WMIcon | NET::WMPid | NET::WMMoveResize | NET::WMFrameExtents | NET::WMPing , NET::NormalMask | NET::DesktopMask | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask | NET::OverrideMask | NET::UtilityMask | NET::SplashMask | // No compositing window types here unless we support them also as managed window types 0 , NET::Modal | //NET::Sticky | // Large desktops not supported (and probably never will be) NET::MaxVert | NET::MaxHoriz | NET::Shaded | NET::SkipTaskbar | NET::KeepAbove | //NET::StaysOnTop | // The same like KeepAbove NET::SkipPager | NET::Hidden | NET::FullScreen | NET::KeepBelow | NET::DemandsAttention | 0 , NET::WM2UserTime | NET::WM2StartupId | NET::WM2AllowedActions | NET::WM2RestackWindow | NET::WM2MoveResizeWindow | NET::WM2ExtendedStrut | NET::WM2KDETemporaryRules | NET::WM2ShowingDesktop | NET::WM2DesktopLayout | NET::WM2FullPlacement | NET::WM2FullscreenMonitors | NET::WM2KDEShadow | 0 , NET::ActionMove | NET::ActionResize | NET::ActionMinimize | NET::ActionShade | //NET::ActionStick | // Sticky state is not supported NET::ActionMaxVert | NET::ActionMaxHoriz | NET::ActionFullScreen | NET::ActionChangeDesktop | NET::ActionClose | 0 , }; if (hasDecorationPlugin() && mgr->factory()->supports(AbilityExtendIntoClientArea)) protocols[ NETRootInfo::PROTOCOLS2 ] |= NET::WM2FrameOverlap; QX11Info info; rootInfo = new RootInfo(this, display(), supportWindow->winId(), "KWin", protocols, 5, info.screen()); // Create an entry with empty activity name, it will be used if activities are not supported. Otherwise, it will be removed. m_desktopFocusChain = m_activitiesDesktopFocusChain.insert(QString(), QVector(numberOfDesktops())); // Now we know how many desktops we'll have, thus we initialize the positioning object initPositioning = new Placement(this); loadDesktopSettings(); updateDesktopLayout(); // Extra NETRootInfo instance in Client mode is needed to get the values of the properties NETRootInfo client_info(display(), NET::ActiveWindow | NET::CurrentDesktop); int initial_desktop; if (!kapp->isSessionRestored()) initial_desktop = client_info.currentDesktop(); else { KConfigGroup group(kapp->sessionConfig(), "Session"); initial_desktop = group.readEntry("desktop", 1); } if (!setCurrentDesktop(initial_desktop)) setCurrentDesktop(1); #ifdef KWIN_BUILD_ACTIVITIES updateActivityList(false, true); #endif reconfigureTimer.setSingleShot(true); updateToolWindowsTimer.setSingleShot(true); connect(&reconfigureTimer, SIGNAL(timeout()), this, SLOT(slotReconfigure())); connect(&updateToolWindowsTimer, SIGNAL(timeout()), this, SLOT(slotUpdateToolWindows())); connect(KGlobalSettings::self(), SIGNAL(appearanceChanged()), this, SLOT(reconfigure())); connect(KGlobalSettings::self(), SIGNAL(settingsChanged(int)), this, SLOT(slotSettingsChanged(int))); connect(KGlobalSettings::self(), SIGNAL(blockShortcuts(int)), this, SLOT(slotBlockShortcuts(int))); active_client = NULL; rootInfo->setActiveWindow(None); focusToNull(); if (!kapp->isSessionRestored()) ++block_focus; // Because it will be set below { // Begin updates blocker block StackingUpdatesBlocker blocker(this); unsigned int i, nwins; Window root_return, parent_return; Window* wins; XQueryTree(display(), rootWindow(), &root_return, &parent_return, &wins, &nwins); bool fixoffset = KCmdLineArgs::parsedArgs()->getOption("crashes").toInt() > 0; for (i = 0; i < nwins; i++) { XWindowAttributes attr; XGetWindowAttributes(display(), wins[i], &attr); if (attr.override_redirect) { createUnmanaged(wins[i]); continue; } if (attr.map_state != IsUnmapped) { if (fixoffset) fixPositionAfterCrash(wins[ i ], attr); createClient(wins[i], true); } } if (wins) XFree((void*)(wins)); // Propagate clients, will really happen at the end of the updates blocker block updateStackingOrder(true); saveOldScreenSizes(); updateClientArea(); // NETWM spec says we have to set it to (0,0) if we don't support it NETPoint* viewports = new NETPoint[numberOfDesktops()]; rootInfo->setDesktopViewport(numberOfDesktops(), *viewports); delete[] viewports; QRect geom; for (int i = 0; i < QApplication::desktop()->screenCount(); i++) { geom |= QApplication::desktop()->screenGeometry(i); } NETSize desktop_geometry; desktop_geometry.width = geom.width(); desktop_geometry.height = geom.height(); rootInfo->setDesktopGeometry(-1, desktop_geometry); setShowingDesktop(false); } // End updates blocker block Client* new_active_client = NULL; if (!kapp->isSessionRestored()) { --block_focus; new_active_client = findClient(WindowMatchPredicate(client_info.activeWindow())); } if (new_active_client == NULL && activeClient() == NULL && should_get_focus.count() == 0) { // No client activated in manage() if (new_active_client == NULL) new_active_client = topClientOnDesktop(currentDesktop(), -1); if (new_active_client == NULL && !desktops.isEmpty()) new_active_client = findDesktop(true, currentDesktop()); } if (new_active_client != NULL) activateClient(new_active_client); #ifdef KWIN_BUILD_SCRIPTING m_scripting = new Scripting(this); #endif // SELI TODO: This won't work with unreasonable focus policies, // and maybe in rare cases also if the selected client doesn't // want focus workspaceInit = false; // broadcast that Workspace is ready, but first process all events. QMetaObject::invokeMethod(this, "workspaceInitialized", Qt::QueuedConnection); // TODO: ungrabXServer() } Workspace::~Workspace() { delete m_compositor; m_compositor = NULL; blockStackingUpdates(true); // TODO: grabXServer(); // Use stacking_order, so that kwin --replace keeps stacking order for (ToplevelList::iterator it = stacking_order.begin(), end = stacking_order.end(); it != end; ++it) { Client *c = qobject_cast(*it); if (!c) { continue; } // Only release the window c->releaseWindow(true); // No removeClient() is called, it does more than just removing. // However, remove from some lists to e.g. prevent performTransiencyCheck() // from crashing. clients.removeAll(c); desktops.removeAll(c); } for (UnmanagedList::iterator it = unmanaged.begin(), end = unmanaged.end(); it != end; ++it) (*it)->release(true); delete m_outline; XDeleteProperty(display(), rootWindow(), atoms->kwin_running); writeWindowRules(); KGlobal::config()->sync(); delete rootInfo; delete supportWindow; delete mgr; delete startup; delete initPositioning; delete client_keys_dialog; while (!rules.isEmpty()) { delete rules.front(); rules.pop_front(); } foreach (SessionInfo * s, session) delete s; XDestroyWindow(display(), null_focus_window); // TODO: ungrabXServer(); delete[] desktopGrid_; _self = 0; } Client* Workspace::createClient(Window w, bool is_mapped) { StackingUpdatesBlocker blocker(this); Client* c = new Client(this); if (!c->manage(w, is_mapped)) { Client::deleteClient(c, Allowed); return NULL; } connect(c, SIGNAL(needsRepaint()), m_compositor, SLOT(scheduleRepaint())); connect(c, SIGNAL(activeChanged()), m_compositor, SLOT(checkUnredirect())); connect(c, SIGNAL(fullScreenChanged()), m_compositor, SLOT(checkUnredirect())); connect(c, SIGNAL(geometryChanged()), m_compositor, SLOT(checkUnredirect())); connect(c, SIGNAL(geometryShapeChanged(KWin::Toplevel*,QRect)), m_compositor, SLOT(checkUnredirect())); connect(c, SIGNAL(blockingCompositingChanged(KWin::Client*)), m_compositor, SLOT(updateCompositeBlocking(KWin::Client*))); addClient(c, Allowed); return c; } Unmanaged* Workspace::createUnmanaged(Window w) { if (m_compositor && m_compositor->checkForOverlayWindow(w)) return NULL; Unmanaged* c = new Unmanaged(this); if (!c->track(w)) { Unmanaged::deleteUnmanaged(c, Allowed); return NULL; } connect(c, SIGNAL(needsRepaint()), m_compositor, SLOT(scheduleRepaint())); addUnmanaged(c, Allowed); emit unmanagedAdded(c); return c; } void Workspace::addClient(Client* c, allowed_t) { Group* grp = findGroup(c->window()); KWindowInfo info = KWindowSystem::windowInfo(c->window(), -1U, NET::WM2WindowClass); emit clientAdded(c); if (grp != NULL) grp->gotLeader(c); if (c->isDesktop()) { desktops.append(c); if (active_client == NULL && should_get_focus.isEmpty() && c->isOnCurrentDesktop()) requestFocus(c); // TODO: Make sure desktop is active after startup if there's no other window active } else { updateFocusChains(c, FocusChainUpdate); // Add to focus chain if not already there clients.append(c); } if (!unconstrained_stacking_order.contains(c)) unconstrained_stacking_order.append(c); // Raise if it hasn't got any stacking position yet if (!stacking_order.contains(c)) // It'll be updated later, and updateToolWindows() requires stacking_order.append(c); // c to be in stacking_order x_stacking_dirty = true; updateClientArea(); // This cannot be in manage(), because the client got added only now updateClientLayer(c); if (c->isDesktop()) { raiseClient(c); // If there's no active client, make this desktop the active one if (activeClient() == NULL && should_get_focus.count() == 0) activateClient(findDesktop(true, currentDesktop())); } c->checkActiveModal(); checkTransients(c->window()); // SELI TODO: Does this really belong here? updateStackingOrder(true); // Propagate new client if (c->isUtility() || c->isMenu() || c->isToolbar()) updateToolWindows(true); checkNonExistentClients(); #ifdef KWIN_BUILD_TABBOX if (tabBox()->isDisplayed()) tab_box->reset(true); #endif } void Workspace::addUnmanaged(Unmanaged* c, allowed_t) { unmanaged.append(c); x_stacking_dirty = true; } /** * Destroys the client \a c */ void Workspace::removeClient(Client* c, allowed_t) { emit clientRemoved(c); if (c == active_popup_client) closeActivePopup(); if (m_userActionsMenu->isMenuClient(c)) { m_userActionsMenu->close(); } c->untab(); if (client_keys_client == c) setupWindowShortcutDone(false); if (!c->shortcut().isEmpty()) { c->setShortcut(QString()); // Remove from client_keys clientShortcutUpdated(c); // Needed, since this is otherwise delayed by setShortcut() and wouldn't run } if (c->isDialog()) Notify::raise(Notify::TransDelete); if (c->isNormalWindow()) Notify::raise(Notify::Delete); #ifdef KWIN_BUILD_TABBOX if (tabBox()->isDisplayed() && tabBox()->currentClient() == c) tab_box->nextPrev(true); #endif Q_ASSERT(clients.contains(c) || desktops.contains(c)); // TODO: if marked client is removed, notify the marked list clients.removeAll(c); desktops.removeAll(c); x_stacking_dirty = true; for (int i = 1; i <= numberOfDesktops(); ++i) focus_chain[i].removeAll(c); global_focus_chain.removeAll(c); attention_chain.removeAll(c); showing_desktop_clients.removeAll(c); Group* group = findGroup(c->window()); if (group != NULL) group->lostLeader(); if (c == most_recently_raised) most_recently_raised = 0; should_get_focus.removeAll(c); Q_ASSERT(c != active_client); if (c == last_active_client) last_active_client = 0; if (c == pending_take_activity) pending_take_activity = NULL; if (c == delayfocus_client) cancelDelayFocus(); updateStackingOrder(true); if (m_compositor) { m_compositor->updateCompositeBlocking(); } #ifdef KWIN_BUILD_TABBOX if (tabBox()->isDisplayed()) tab_box->reset(true); #endif updateClientArea(); } void Workspace::removeUnmanaged(Unmanaged* c, allowed_t) { assert(unmanaged.contains(c)); unmanaged.removeAll(c); x_stacking_dirty = true; } void Workspace::addDeleted(Deleted* c, Toplevel *orig, allowed_t) { assert(!deleted.contains(c)); deleted.append(c); const int unconstraintedIndex = unconstrained_stacking_order.indexOf(orig); if (unconstraintedIndex != -1) { unconstrained_stacking_order.replace(unconstraintedIndex, c); } else { unconstrained_stacking_order.append(c); } const int index = stacking_order.indexOf(orig); if (index != -1) { stacking_order.replace(index, c); } else { stacking_order.append(c); } x_stacking_dirty = true; connect(c, SIGNAL(needsRepaint()), m_compositor, SLOT(scheduleRepaint())); } void Workspace::removeDeleted(Deleted* c, allowed_t) { assert(deleted.contains(c)); emit deletedRemoved(c); deleted.removeAll(c); unconstrained_stacking_order.removeAll(c); stacking_order.removeAll(c); x_stacking_dirty = true; } void Workspace::updateFocusChains(Client* c, FocusChainChange change) { if (!c->wantsTabFocus()) { // Doesn't want tab focus, remove for (int i = 1; i <= numberOfDesktops(); ++i) focus_chain[i].removeAll(c); global_focus_chain.removeAll(c); return; } if (c->desktop() == NET::OnAllDesktops) { // Now on all desktops, add it to focus_chains it is not already in for (int i = 1; i <= numberOfDesktops(); i++) { // Making first/last works only on current desktop, don't affect all desktops if (i == currentDesktop() && (change == FocusChainMakeFirst || change == FocusChainMakeLast)) { focus_chain[i].removeAll(c); if (change == FocusChainMakeFirst) focus_chain[i].append(c); else focus_chain[i].prepend(c); } else if (!focus_chain[i].contains(c)) { // Add it after the active one if (active_client != NULL && active_client != c && !focus_chain[i].isEmpty() && focus_chain[i].last() == active_client) focus_chain[i].insert(focus_chain[i].size() - 1, c); else focus_chain[i].append(c); // Otherwise add as the first one } } } else { // Now only on desktop, remove it anywhere else for (int i = 1; i <= numberOfDesktops(); i++) { if (i == c->desktop()) { if (change == FocusChainMakeFirst) { focus_chain[i].removeAll(c); focus_chain[i].append(c); } else if (change == FocusChainMakeLast) { focus_chain[i].removeAll(c); focus_chain[i].prepend(c); } else if (!focus_chain[i].contains(c)) { // Add it after the active one if (active_client != NULL && active_client != c && !focus_chain[i].isEmpty() && focus_chain[i].last() == active_client) focus_chain[i].insert(focus_chain[i].size() - 1, c); else focus_chain[i].append(c); // Otherwise add as the first one } } else focus_chain[i].removeAll(c); } } if (change == FocusChainMakeFirst) { global_focus_chain.removeAll(c); global_focus_chain.append(c); } else if (change == FocusChainMakeLast) { global_focus_chain.removeAll(c); global_focus_chain.prepend(c); } else if (!global_focus_chain.contains(c)) { // Add it after the active one if (active_client != NULL && active_client != c && !global_focus_chain.isEmpty() && global_focus_chain.last() == active_client) global_focus_chain.insert(global_focus_chain.size() - 1, c); else global_focus_chain.append(c); // Otherwise add as the first one } } void Workspace::updateToolWindows(bool also_hide) { // TODO: What if Client's transiency/group changes? should this be called too? (I'm paranoid, am I not?) if (!options->isHideUtilityWindowsForInactive()) { for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) if (!(*it)->tabGroup() || (*it)->tabGroup()->current() == *it) (*it)->hideClient(false); return; } const Group* group = NULL; const Client* client = active_client; // Go up in transiency hiearchy, if the top is found, only tool transients for the top mainwindow // will be shown; if a group transient is group, all tools in the group will be shown while (client != NULL) { if (!client->isTransient()) break; if (client->groupTransient()) { group = client->group(); break; } client = client->transientFor(); } // Use stacking order only to reduce flicker, it doesn't matter if block_stacking_updates == 0, // I.e. if it's not up to date // SELI TODO: But maybe it should - what if a new client has been added that's not in stacking order yet? ClientList to_show, to_hide; for (ToplevelList::ConstIterator it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { Client *c = qobject_cast(*it); if (!c) { continue; } if (c->isUtility() || c->isMenu() || c->isToolbar()) { bool show = true; if (!c->isTransient()) { if (c->group()->members().count() == 1) // Has its own group, keep always visible show = true; else if (client != NULL && c->group() == client->group()) show = true; else show = false; } else { if (group != NULL && c->group() == group) show = true; else if (client != NULL && client->hasTransient(c, true)) show = true; else show = false; } if (!show && also_hide) { const ClientList mainclients = c->mainClients(); // Don't hide utility windows which are standalone(?) or // have e.g. kicker as mainwindow if (mainclients.isEmpty()) show = true; for (ClientList::ConstIterator it2 = mainclients.constBegin(); it2 != mainclients.constEnd(); ++it2) { if (c->isSpecialWindow()) show = true; } if (!show) to_hide.append(c); } if (show) to_show.append(c); } } // First show new ones, then hide for (int i = to_show.size() - 1; i >= 0; --i) // From topmost // TODO: Since this is in stacking order, the order of taskbar entries changes :( to_show.at(i)->hideClient(false); if (also_hide) { for (ClientList::ConstIterator it = to_hide.constBegin(); it != to_hide.constEnd(); ++it) // From bottommost (*it)->hideClient(true); updateToolWindowsTimer.stop(); } else // setActiveClient() is after called with NULL client, quickly followed // by setting a new client, which would result in flickering resetUpdateToolWindowsTimer(); } void Workspace::resetUpdateToolWindowsTimer() { updateToolWindowsTimer.start(200); } void Workspace::slotUpdateToolWindows() { updateToolWindows(true); } /** * Updates the current colormap according to the currently active client */ void Workspace::updateColormap() { Colormap cmap = default_colormap; if (activeClient() && activeClient()->colormap() != None) cmap = activeClient()->colormap(); if (cmap != installed_colormap) { XInstallColormap(display(), cmap); installed_colormap = cmap; } } void Workspace::slotReloadConfig() { reconfigure(); } void Workspace::reconfigure() { reconfigureTimer.start(200); } /** * This D-Bus call is used by the compositing kcm. Since the reconfigure() * D-Bus call delays the actual reconfiguring, it is not possible to immediately * call compositingActive(). Therefore the kcm will instead call this to ensure * the reconfiguring has already happened. */ bool Workspace::waitForCompositingSetup() { if (reconfigureTimer.isActive()) { reconfigureTimer.stop(); slotReconfigure(); } if (m_compositor) { return m_compositor->isActive(); } return false; } void Workspace::slotSettingsChanged(int category) { kDebug(1212) << "Workspace::slotSettingsChanged()"; if (category == KGlobalSettings::SETTINGS_SHORTCUTS) m_userActionsMenu->discard(); } /** * Reread settings */ KWIN_PROCEDURE(CheckBorderSizesProcedure, Client, cl->checkBorderSizes(true)); void Workspace::slotReconfigure() { kDebug(1212) << "Workspace::slotReconfigure()"; reconfigureTimer.stop(); #ifdef KWIN_BUILD_SCREENEDGES m_screenEdge.reserveActions(false); if (options->electricBorders() == Options::ElectricAlways) m_screenEdge.reserveDesktopSwitching(false, m_screenEdgeOrientation); #endif bool borderlessMaximizedWindows = options->borderlessMaximizedWindows(); KGlobal::config()->reparseConfiguration(); unsigned long changed = options->updateSettings(); emit configChanged(); m_userActionsMenu->discard(); updateToolWindows(true); if (hasDecorationPlugin() && mgr->reset(changed)) { // Decorations need to be recreated // This actually seems to make things worse now //QWidget curtain; //curtain.setBackgroundMode( NoBackground ); //curtain.setGeometry( Kephal::ScreenUtils::desktopGeometry() ); //curtain.show(); for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) (*it)->updateDecoration(true, true); // If the new decoration doesn't supports tabs then ungroup clients if (!decorationSupportsTabbing()) { foreach (Client * c, clients) c->untab(); } mgr->destroyPreviousPlugin(); } else { forEachClient(CheckBorderSizesProcedure()); foreach (Client * c, clients) c->triggerDecorationRepaint(); } #ifdef KWIN_BUILD_SCREENEDGES m_screenEdge.reserveActions(true); if (options->electricBorders() == Options::ElectricAlways) { QSize desktopMatrix = rootInfo->desktopLayoutColumnsRows(); m_screenEdgeOrientation = 0; if (desktopMatrix.width() > 1) m_screenEdgeOrientation |= Qt::Horizontal; if (desktopMatrix.height() > 1) m_screenEdgeOrientation |= Qt::Vertical; m_screenEdge.reserveDesktopSwitching(true, m_screenEdgeOrientation); } m_screenEdge.update(); #endif loadWindowRules(); for (ClientList::Iterator it = clients.begin(); it != clients.end(); ++it) { (*it)->setupWindowRules(true); (*it)->applyWindowRules(); discardUsedWindowRules(*it, false); } if (borderlessMaximizedWindows != options->borderlessMaximizedWindows() && !options->borderlessMaximizedWindows()) { // in case borderless maximized windows option changed and new option // is to have borders, we need to unset the borders for all maximized windows for (ClientList::Iterator it = clients.begin(); it != clients.end(); ++it) { if ((*it)->maximizeMode() == MaximizeFull) (*it)->checkNoBorder(); } } if (hasDecorationPlugin()) { rootInfo->setSupported(NET::WM2FrameOverlap, mgr->factory()->supports(AbilityExtendIntoClientArea)); } else { rootInfo->setSupported(NET::WM2FrameOverlap, false); } } static bool _loading_desktop_settings = false; void Workspace::loadDesktopSettings() { _loading_desktop_settings = true; KSharedConfig::Ptr c = KGlobal::config(); QString groupname; if (screen_number == 0) groupname = "Desktops"; else groupname.sprintf("Desktops-screen-%d", screen_number); KConfigGroup group(c, groupname); const int n = group.readEntry("Number", 1); setNumberOfDesktops(n); for (int i = 1; i <= n; i++) { QString s = group.readEntry(QString("Name_%1").arg(i), i18n("Desktop %1", i)); rootInfo->setDesktopName(i, s.toUtf8().data()); m_desktopFocusChain.value()[i-1] = i; } int rows = group.readEntry("Rows", 2); rows = qBound(1, rows, n); // avoid weird cases like having 3 rows for 4 desktops, where the last row is unused int columns = n / rows; if (n % rows > 0) { columns++; } rootInfo->setDesktopLayout(NET::OrientationHorizontal, columns, rows, NET::DesktopLayoutCornerTopLeft); rootInfo->activate(); _loading_desktop_settings = false; } void Workspace::saveDesktopSettings() { if (_loading_desktop_settings) return; KSharedConfig::Ptr c = KGlobal::config(); QString groupname; if (screen_number == 0) groupname = "Desktops"; else groupname.sprintf("Desktops-screen-%d", screen_number); KConfigGroup group(c, groupname); group.writeEntry("Number", numberOfDesktops()); for (int i = 1; i <= numberOfDesktops(); i++) { QString s = desktopName(i); QString defaultvalue = i18n("Desktop %1", i); if (s.isEmpty()) { s = defaultvalue; rootInfo->setDesktopName(i, s.toUtf8().data()); } if (s != defaultvalue) { group.writeEntry(QString("Name_%1").arg(i), s); } else { QString currentvalue = group.readEntry(QString("Name_%1").arg(i), QString()); if (currentvalue != defaultvalue) group.writeEntry(QString("Name_%1").arg(i), ""); } } // Save to disk group.sync(); } /** * Avoids managing a window with title \a title */ void Workspace::doNotManage(const QString& title) { doNotManageList.append(title); } /** * Hack for java applets */ bool Workspace::isNotManaged(const QString& title) { for (QStringList::Iterator it = doNotManageList.begin(); it != doNotManageList.end(); ++it) { QRegExp r((*it)); if (r.indexIn(title) != -1) { doNotManageList.erase(it); return true; } } return false; } /** * During virt. desktop switching, desktop areas covered by windows that are * going to be hidden are first obscured by new windows with no background * ( i.e. transparent ) placed right below the windows. These invisible windows * are removed after the switch is complete. * Reduces desktop ( wallpaper ) repaints during desktop switching */ class ObscuringWindows { public: ~ObscuringWindows(); void create(Client* c); private: QList obscuring_windows; static QList* cached; static unsigned int max_cache_size; }; QList* ObscuringWindows::cached = 0; unsigned int ObscuringWindows::max_cache_size = 0; void ObscuringWindows::create(Client* c) { if (cached == 0) cached = new QList; Window obs_win; XWindowChanges chngs; int mask = CWSibling | CWStackMode; if (cached->count() > 0) { cached->removeAll(obs_win = cached->first()); chngs.x = c->x(); chngs.y = c->y(); chngs.width = c->width(); chngs.height = c->height(); mask |= CWX | CWY | CWWidth | CWHeight; } else { XSetWindowAttributes a; a.background_pixmap = None; a.override_redirect = True; obs_win = XCreateWindow(display(), rootWindow(), c->x(), c->y(), c->width(), c->height(), 0, CopyFromParent, InputOutput, CopyFromParent, CWBackPixmap | CWOverrideRedirect, &a); } chngs.sibling = c->frameId(); chngs.stack_mode = Below; XConfigureWindow(display(), obs_win, mask, &chngs); XMapWindow(display(), obs_win); obscuring_windows.append(obs_win); } ObscuringWindows::~ObscuringWindows() { max_cache_size = qMax(int(max_cache_size), obscuring_windows.count() + 4) - 1; for (QList::ConstIterator it = obscuring_windows.constBegin(); it != obscuring_windows.constEnd(); ++it) { XUnmapWindow(display(), *it); if (cached->count() < int(max_cache_size)) cached->prepend(*it); else XDestroyWindow(display(), *it); } } /** * Sets the current desktop to \a new_desktop * * Shows/Hides windows according to the stacking order and finally * propages the new desktop to the world */ bool Workspace::setCurrentDesktop(int new_desktop) { if (new_desktop < 1 || new_desktop > numberOfDesktops()) return false; closeActivePopup(); ++block_focus; // TODO: Q_ASSERT( block_stacking_updates == 0 ); // Make sure stacking_order is up to date StackingUpdatesBlocker blocker(this); int old_desktop = currentDesktop(); int old_active_screen = activeScreen(); if (new_desktop != currentDesktop()) { ++block_showing_desktop; // Optimized Desktop switching: unmapping done from back to front // mapping done from front to back => less exposure events Notify::raise((Notify::Event)(Notify::DesktopChange + new_desktop)); ObscuringWindows obs_wins; currentDesktop_ = new_desktop; // Change the desktop (so that Client::updateVisibility() works) for (ToplevelList::ConstIterator it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { Client *c = qobject_cast(*it); if (!c) { continue; } if (!c->isOnDesktop(new_desktop) && c != movingClient && c->isOnCurrentActivity()) { if (c->isShown(true) && c->isOnDesktop(old_desktop) && !compositing()) obs_wins.create(c); (c)->updateVisibility(); } } // Now propagate the change, after hiding, before showing rootInfo->setCurrentDesktop(currentDesktop()); if (movingClient && !movingClient->isOnDesktop(new_desktop)) { movingClient->setDesktop(new_desktop); } for (int i = stacking_order.size() - 1; i >= 0 ; --i) { Client *c = qobject_cast(stacking_order.at(i)); if (!c) { continue; } if (c->isOnDesktop(new_desktop) && c->isOnCurrentActivity()) c->updateVisibility(); } --block_showing_desktop; if (showingDesktop()) // Do this only after desktop change to avoid flicker resetShowingDesktop(false); } // Restore the focus on this desktop --block_focus; Client* c = 0; if (options->focusPolicyIsReasonable()) { // Search in focus chain if (movingClient != NULL && active_client == movingClient && focus_chain[currentDesktop()].contains(active_client) && active_client->isShown(true) && active_client->isOnCurrentDesktop()) c = active_client; // The requestFocus below will fail, as the client is already active // from actiavtion.cpp if (!c && options->isNextFocusPrefersMouse()) { ToplevelList::const_iterator it = stackingOrder().constEnd(); while (it != stackingOrder().constBegin()) { Client *client = qobject_cast(*(--it)); if (!client) { continue; } if (!(client->isShown(false) && client->isOnDesktop(new_desktop) && client->isOnCurrentActivity() && client->isOnScreen(activeScreen()))) continue; if (client->geometry().contains(QCursor::pos())) { if (!client->isDesktop()) c = client; break; // unconditional break - we do not pass the focus to some client below an unusable one } } } if (!c) { for (int i = focus_chain[currentDesktop()].size() - 1; i >= 0; --i) { Client* tmp = focus_chain[currentDesktop()].at(i); if (tmp->isShown(false) && tmp->isOnCurrentActivity() && ( !options->isSeparateScreenFocus() || tmp->screen() == old_active_screen )) { c = tmp; break; } } } } // If "unreasonable focus policy" and active_client is on_all_desktops and // under mouse (Hence == old_active_client), conserve focus. // (Thanks to Volker Schatz ) else if (active_client && active_client->isShown(true) && active_client->isOnCurrentDesktop()) c = active_client; if (c == NULL && !desktops.isEmpty()) c = findDesktop(true, currentDesktop()); if (c != active_client) setActiveClient(NULL, Allowed); if (c) requestFocus(c); else if (!desktops.isEmpty()) requestFocus(findDesktop(true, currentDesktop())); else focusToNull(); // Update focus chain: // If input: chain = { 1, 2, 3, 4 } and currentDesktop() = 3, // Output: chain = { 3, 1, 2, 4 }. //kDebug(1212) << QString("Switching to desktop #%1, at focus_chain index %2\n") // .arg(currentDesktop()).arg(desktop_focus_chain.find( currentDesktop() )); QVector &chain = m_desktopFocusChain.value(); for (int i = chain.indexOf(currentDesktop()); i > 0; --i) chain[i] = chain[i-1]; chain[0] = currentDesktop(); //QString s = "desktop_focus_chain[] = { "; //for ( uint i = 0; i < desktop_focus_chain.size(); i++ ) // s += QString::number( desktop_focus_chain[i] ) + ", "; //kDebug( 1212 ) << s << "}\n"; emit currentDesktopChanged(old_desktop, movingClient); return true; } int Workspace::maxNumberOfDesktops() const { return KWIN_MAX_NUMBER_DESKTOPS; } #ifdef KWIN_BUILD_ACTIVITIES //BEGIN threaded activity list fetching typedef QPair AssignedList; typedef QPair CurrentAndList; static AssignedList fetchActivityList(KActivities::Controller *controller, QStringList *target, bool running) // could be member function, but actually it's much simpler this way { return AssignedList(target, running ? controller->listActivities(KActivities::Info::Running) : controller->listActivities()); } static CurrentAndList fetchActivityListAndCurrent(KActivities::Controller *controller) { QStringList l = controller->listActivities(); QString c = controller->currentActivity(); return CurrentAndList(c, l); } void Workspace::updateActivityList(bool running, bool updateCurrent, QObject *target, QString slot) { if (updateCurrent) { QFutureWatcher* watcher = new QFutureWatcher; connect( watcher, SIGNAL(finished()), SLOT(handleActivityReply()) ); if (!slot.isEmpty()) { watcher->setProperty("activityControllerCallback", slot); // "activity reply trigger" watcher->setProperty("activityControllerCallbackTarget", qVariantFromValue((void*)target)); } watcher->setFuture(QtConcurrent::run(fetchActivityListAndCurrent, &activityController_ )); } else { QFutureWatcher* watcher = new QFutureWatcher; connect(watcher, SIGNAL(finished()), SLOT(handleActivityReply())); if (!slot.isEmpty()) { watcher->setProperty("activityControllerCallback", slot); // "activity reply trigger" watcher->setProperty("activityControllerCallbackTarget", qVariantFromValue((void*)target)); } QStringList *target = running ? &openActivities_ : &allActivities_; watcher->setFuture(QtConcurrent::run(fetchActivityList, &activityController_, target, running)); } } void Workspace::handleActivityReply() { QObject *watcherObject = 0; if (QFutureWatcher* watcher = dynamic_cast< QFutureWatcher* >(sender())) { *(watcher->result().first) = watcher->result().second; // cool trick, ehh? :-) watcherObject = watcher; } if (!watcherObject) { if (QFutureWatcher* watcher = dynamic_cast< QFutureWatcher* >(sender())) { allActivities_ = watcher->result().second; updateCurrentActivity(watcher->result().first); watcherObject = watcher; } } if (watcherObject) { QString slot = watcherObject->property("activityControllerCallback").toString(); QObject *target = static_cast(watcherObject->property("activityControllerCallbackTarget").value()); watcherObject->deleteLater(); // has done it's job if (!slot.isEmpty()) QMetaObject::invokeMethod(target, slot.toAscii().data(), Qt::DirectConnection); } } //END threaded activity list fetching #else // make gcc happy - stupd moc cannot handle preproc defs so we MUST define void Workspace::handleActivityReply() {} #endif // KWIN_BUILD_ACTIVITIES /** * Updates the current activity when it changes * do *not* call this directly; it does not set the activity. * * Shows/Hides windows according to the stacking order */ void Workspace::updateCurrentActivity(const QString &new_activity) { //closeActivePopup(); ++block_focus; // TODO: Q_ASSERT( block_stacking_updates == 0 ); // Make sure stacking_order is up to date StackingUpdatesBlocker blocker(this); if (new_activity != activity_) { ++block_showing_desktop; //FIXME should I be using that? // Optimized Desktop switching: unmapping done from back to front // mapping done from front to back => less exposure events //Notify::raise((Notify::Event) (Notify::DesktopChange+new_desktop)); ObscuringWindows obs_wins; QString old_activity = activity_; activity_ = new_activity; for (ToplevelList::ConstIterator it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { Client *c = qobject_cast(*it); if (!c) { continue; } if (!c->isOnActivity(new_activity) && c != movingClient && c->isOnCurrentDesktop()) { if (c->isShown(true) && c->isOnActivity(old_activity) && !compositing()) obs_wins.create(c); c->updateVisibility(); } } // Now propagate the change, after hiding, before showing //rootInfo->setCurrentDesktop( currentDesktop() ); /* TODO someday enable dragging windows to other activities if ( movingClient && !movingClient->isOnDesktop( new_desktop )) { movingClient->setDesktop( new_desktop ); */ for (int i = stacking_order.size() - 1; i >= 0 ; --i) { Client *c = qobject_cast(stacking_order.at(i)); if (!c) { continue; } if (c->isOnActivity(new_activity)) c->updateVisibility(); } --block_showing_desktop; //FIXME not sure if I should do this either if (showingDesktop()) // Do this only after desktop change to avoid flicker resetShowingDesktop(false); } // Restore the focus on this desktop --block_focus; Client* c = 0; //FIXME below here is a lot of focuschain stuff, probably all wrong now if (options->focusPolicyIsReasonable()) { // Search in focus chain if (movingClient != NULL && active_client == movingClient && focus_chain[currentDesktop()].contains(active_client) && active_client->isShown(true) && active_client->isOnCurrentDesktop()) c = active_client; // The requestFocus below will fail, as the client is already active if (!c) { for (int i = focus_chain[currentDesktop()].size() - 1; i >= 0; --i) { if (focus_chain[currentDesktop()].at(i)->isShown(false) && focus_chain[currentDesktop()].at(i)->isOnCurrentActivity()) { c = focus_chain[currentDesktop()].at(i); break; } } } } // If "unreasonable focus policy" and active_client is on_all_desktops and // under mouse (Hence == old_active_client), conserve focus. // (Thanks to Volker Schatz ) else if (active_client && active_client->isShown(true) && active_client->isOnCurrentDesktop() && active_client->isOnCurrentActivity()) c = active_client; if (c == NULL && !desktops.isEmpty()) c = findDesktop(true, currentDesktop()); if (c != active_client) setActiveClient(NULL, Allowed); if (c) requestFocus(c); else if (!desktops.isEmpty()) requestFocus(findDesktop(true, currentDesktop())); else focusToNull(); // Update focus chain: #ifdef KWIN_BUILD_ACTIVITIES // Replace initial dummy with actual activity, preserving the current chain. if (m_desktopFocusChain.key().isNull()) { QVector val(m_desktopFocusChain.value()); m_activitiesDesktopFocusChain.erase(m_desktopFocusChain); m_desktopFocusChain = m_activitiesDesktopFocusChain.insert(activity_, val); } else { m_desktopFocusChain = m_activitiesDesktopFocusChain.find(activity_); if (m_desktopFocusChain == m_activitiesDesktopFocusChain.end()) { m_desktopFocusChain = m_activitiesDesktopFocusChain.insert(activity_, QVector(numberOfDesktops())); for (int i = 0; i < numberOfDesktops(); ++i) { m_desktopFocusChain.value()[i] = i + 1; } } } #endif // Not for the very first time, only if something changed and there are more than 1 desktops //if ( effects != NULL && old_desktop != 0 && old_desktop != new_desktop ) // static_cast( effects )->desktopChanged( old_desktop ); if (compositing() && m_compositor) m_compositor->addRepaintFull(); } /** * updates clients when an activity is destroyed. * this ensures that a client does not get 'lost' if the only activity it's on is removed. */ void Workspace::slotActivityRemoved(const QString &activity) { allActivities_.removeOne(activity); foreach (Toplevel * toplevel, stacking_order) { if (Client *client = qobject_cast(toplevel)) { client->setOnActivity(activity, false); } } //toss out any session data for it KConfigGroup cg(KGlobal::config(), QString("SubSession: ") + activity); cg.deleteGroup(); } void Workspace::slotActivityAdded(const QString &activity) { allActivities_ << activity; } /** * Called only from D-Bus */ void Workspace::nextDesktop() { int desktop = currentDesktop() + 1; setCurrentDesktop(desktop > numberOfDesktops() ? 1 : desktop); } /** * Called only from D-Bus */ void Workspace::previousDesktop() { int desktop = currentDesktop() - 1; setCurrentDesktop(desktop > 0 ? desktop : numberOfDesktops()); } /** * Sets the number of virtual desktops to \a n */ void Workspace::setNumberOfDesktops(int n) { if (n > KWIN_MAX_NUMBER_DESKTOPS) n = KWIN_MAX_NUMBER_DESKTOPS; if (n < 1 || n == numberOfDesktops()) return; int old_number_of_desktops = numberOfDesktops(); desktopCount_ = n; initPositioning->reinitCascading(0); updateDesktopLayout(); // Make sure the layout is still valid if (currentDesktop() > n) setCurrentDesktop(n); // move all windows that would be hidden to the last visible desktop if (old_number_of_desktops > numberOfDesktops()) { for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) { if (!(*it)->isOnAllDesktops() && (*it)->desktop() > numberOfDesktops()) sendClientToDesktop(*it, numberOfDesktops(), true); // TODO: Tile should have a method allClients, push them into other tiles } } rootInfo->setNumberOfDesktops(n); NETPoint* viewports = new NETPoint[n]; rootInfo->setDesktopViewport(n, *viewports); delete[] viewports; // Make it +1, so that it can be accessed as [1..numberofdesktops] focus_chain.resize(n + 1); workarea.clear(); workarea.resize(n + 1); restrictedmovearea.clear(); restrictedmovearea.resize(n + 1); screenarea.clear(); updateClientArea(true); // Resize the desktop focus chain. for (DesktopFocusChains::iterator it = m_activitiesDesktopFocusChain.begin(), end = m_activitiesDesktopFocusChain.end(); it != end; ++it) { QVector &chain = it.value(); chain.resize(n); // We do not destroy the chain in case new desktops are added; if (n >= old_number_of_desktops) { for (int i = old_number_of_desktops; i < n; ++i) chain[i] = i + 1; // But when desktops are removed, we may have to modify the chain a bit, // otherwise invalid desktops may show up. } else { for (int i = 0; i < chain.size(); ++i) chain[i] = qMin(chain[i], n); } } saveDesktopSettings(); emit numberDesktopsChanged(old_number_of_desktops); } /** * Sends client \a c to desktop \a desk. * * Takes care of transients as well. */ void Workspace::sendClientToDesktop(Client* c, int desk, bool dont_activate) { if ((desk < 1 && desk != NET::OnAllDesktops) || desk > numberOfDesktops()) return; int old_desktop = c->desktop(); bool was_on_desktop = c->isOnDesktop(desk) || c->isOnAllDesktops(); c->setDesktop(desk); if (c->desktop() != desk) // No change or desktop forced return; desk = c->desktop(); // Client did range checking emit desktopPresenceChanged(c, old_desktop); if (c->isOnDesktop(currentDesktop())) { if (c->wantsTabFocus() && options->focusPolicyIsReasonable() && !was_on_desktop && // for stickyness changes !dont_activate) requestFocus(c); else restackClientUnderActive(c); } else raiseClient(c); c->checkWorkspacePosition( QRect(), old_desktop ); ClientList transients_stacking_order = ensureStackingOrder(c->transients()); for (ClientList::ConstIterator it = transients_stacking_order.constBegin(); it != transients_stacking_order.constEnd(); ++it) sendClientToDesktop(*it, desk, dont_activate); updateClientArea(); } /** * Adds/removes client \a c to/from \a activity. * * Takes care of transients as well. */ void Workspace::toggleClientOnActivity(Client* c, const QString &activity, bool dont_activate) { //int old_desktop = c->desktop(); bool was_on_activity = c->isOnActivity(activity); bool was_on_all = c->isOnAllActivities(); //note: all activities === no activities bool enable = was_on_all || !was_on_activity; c->setOnActivity(activity, enable); if (c->isOnActivity(activity) == was_on_activity && c->isOnAllActivities() == was_on_all) // No change return; if (c->isOnCurrentActivity()) { if (c->wantsTabFocus() && options->focusPolicyIsReasonable() && !was_on_activity && // for stickyness changes //FIXME not sure if the line above refers to the correct activity !dont_activate) requestFocus(c); else restackClientUnderActive(c); } else raiseClient(c); //notifyWindowDesktopChanged( c, old_desktop ); ClientList transients_stacking_order = ensureStackingOrder(c->transients()); for (ClientList::ConstIterator it = transients_stacking_order.constBegin(); it != transients_stacking_order.constEnd(); ++it) toggleClientOnActivity(*it, activity, dont_activate); updateClientArea(); } int Workspace::numScreens() const { return QApplication::desktop()->screenCount(); } int Workspace::activeScreen() const { if (!options->isActiveMouseScreen()) { if (activeClient() != NULL && !activeClient()->isOnScreen(active_screen)) return activeClient()->screen(); return active_screen; } return QApplication::desktop()->screenNumber(cursorPos()); } /** * Check whether a client moved completely out of what's considered the active screen, * if yes, set a new active screen. */ void Workspace::checkActiveScreen(const Client* c) { if (!c->isActive()) return; if (!c->isOnScreen(active_screen)) active_screen = c->screen(); } /** * Called e.g. when a user clicks on a window, set active screen to be the screen * where the click occurred */ void Workspace::setActiveScreenMouse(const QPoint& mousepos) { active_screen = QApplication::desktop()->screenNumber(mousepos); } QRect Workspace::screenGeometry(int screen) const { return QApplication::desktop()->screenGeometry(screen); } int Workspace::screenNumber(const QPoint& pos) const { return QApplication::desktop()->screenNumber(pos); } void Workspace::sendClientToScreen(Client* c, int screen) { + screen = c->rules()->checkScreen(screen); if (c->screen() == screen) // Don't use isOnScreen(), that's true even when only partially return; GeometryUpdatesBlocker blocker(c); QRect old_sarea = clientArea(MaximizeArea, c); QRect sarea = clientArea(MaximizeArea, screen, c->desktop()); QRect oldgeom = c->geometry(); QRect geom = c->geometry(); // move the window to have the same relative position to the center of the screen // (i.e. one near the middle of the right edge will also end up near the middle of the right edge) geom.moveCenter( QPoint(( geom.center().x() - old_sarea.center().x()) * sarea.width() / old_sarea.width() + sarea.center().x(), ( geom.center().y() - old_sarea.center().y()) * sarea.height() / old_sarea.height() + sarea.center().y())); c->setGeometry( geom ); // If the window was inside the old screen area, explicitly make sure its inside also the new screen area. // Calling checkWorkspacePosition() should ensure that, but when moving to a small screen the window could // be big enough to overlap outside of the new screen area, making struts from other screens come into effect, // which could alter the resulting geometry. if( old_sarea.contains( oldgeom )) c->keepInArea( sarea ); c->checkWorkspacePosition( oldgeom ); ClientList transients_stacking_order = ensureStackingOrder(c->transients()); for (ClientList::ConstIterator it = transients_stacking_order.constBegin(); it != transients_stacking_order.constEnd(); ++it) sendClientToScreen(*it, screen); if (c->isActive()) active_screen = screen; } void Workspace::killWindowId(Window window_to_kill) { if (window_to_kill == None) return; Window window = window_to_kill; Client* client = NULL; for (;;) { client = findClient(FrameIdMatchPredicate(window)); if (client != NULL) break; // Found the client Window parent, root; Window* children; unsigned int children_count; XQueryTree(display(), window, &root, &parent, &children, &children_count); if (children != NULL) XFree(children); if (window == root) // We didn't find the client, probably an override-redirect window break; window = parent; // Go up } if (client != NULL) client->killWindow(); else XKillClient(display(), window_to_kill); } void Workspace::sendPingToWindow(Window window, Time timestamp) { rootInfo->sendPing(window, timestamp); } void Workspace::sendTakeActivity(Client* c, Time timestamp, long flags) { rootInfo->takeActivity(c->window(), timestamp, flags); pending_take_activity = c; } /** * Delayed focus functions */ void Workspace::delayFocus() { requestFocus(delayfocus_client); cancelDelayFocus(); } void Workspace::requestDelayFocus(Client* c) { delayfocus_client = c; delete delayFocusTimer; delayFocusTimer = new QTimer(this); connect(delayFocusTimer, SIGNAL(timeout()), this, SLOT(delayFocus())); delayFocusTimer->setSingleShot(true); delayFocusTimer->start(options->delayFocusInterval()); } void Workspace::cancelDelayFocus() { delete delayFocusTimer; delayFocusTimer = 0; } KDecoration* Workspace::createDecoration(KDecorationBridge* bridge) { if (!hasDecorationPlugin()) { return NULL; } return mgr->createDecoration(bridge); } /** * Returns a list of all colors (KDecorationDefines::ColorType) the current * decoration supports */ QList Workspace::decorationSupportedColors() const { QList ret; if (!hasDecorationPlugin()) { return ret; } KDecorationFactory* factory = mgr->factory(); for (Ability ab = ABILITYCOLOR_FIRST; ab < ABILITYCOLOR_END; ab = static_cast(ab + 1)) if (factory->supports(ab)) ret << ab; return ret; } QString Workspace::desktopName(int desk) const { return QString::fromUtf8(rootInfo->desktopName(desk)); } bool Workspace::checkStartupNotification(Window w, KStartupInfoId& id, KStartupInfoData& data) { return startup->checkStartup(w, id, data) == KStartupInfo::Match; } /** * Puts the focus on a dummy window * Just using XSetInputFocus() with None would block keyboard input */ void Workspace::focusToNull() { XSetInputFocus(display(), null_focus_window, RevertToPointerRoot, xTime()); } void Workspace::setShowingDesktop(bool showing) { rootInfo->setShowingDesktop(showing); showing_desktop = showing; ++block_showing_desktop; if (showing_desktop) { showing_desktop_clients.clear(); ++block_focus; ToplevelList cls = stackingOrder(); // Find them first, then minimize, otherwise transients may get minimized with the window // they're transient for for (ToplevelList::ConstIterator it = cls.constBegin(); it != cls.constEnd(); ++it) { Client *c = qobject_cast(*it); if (!c) { continue; } if (c->isOnCurrentActivity() && c->isOnCurrentDesktop() && c->isShown(true) && !c->isSpecialWindow()) showing_desktop_clients.prepend(c); // Topmost first to reduce flicker } for (ClientList::ConstIterator it = showing_desktop_clients.constBegin(); it != showing_desktop_clients.constEnd(); ++it) (*it)->minimize(); --block_focus; if (Client* desk = findDesktop(true, currentDesktop())) requestFocus(desk); } else { for (ClientList::ConstIterator it = showing_desktop_clients.constBegin(); it != showing_desktop_clients.constEnd(); ++it) (*it)->unminimize(); if (showing_desktop_clients.count() > 0) requestFocus(showing_desktop_clients.first()); showing_desktop_clients.clear(); } --block_showing_desktop; } /** * Following Kicker's behavior: * Changing a virtual desktop resets the state and shows the windows again. * Unminimizing a window resets the state but keeps the windows hidden (except * the one that was unminimized). * A new window resets the state and shows the windows again, with the new window * being active. Due to popular demand (#67406) by people who apparently * don't see a difference between "show desktop" and "minimize all", this is not * true if "showDesktopIsMinimizeAll" is set in kwinrc. In such case showing * a new window resets the state but doesn't show windows. */ void Workspace::resetShowingDesktop(bool keep_hidden) { if (block_showing_desktop > 0) return; rootInfo->setShowingDesktop(false); showing_desktop = false; ++block_showing_desktop; if (!keep_hidden) { for (ClientList::ConstIterator it = showing_desktop_clients.constBegin(); it != showing_desktop_clients.constEnd(); ++it) (*it)->unminimize(); } showing_desktop_clients.clear(); --block_showing_desktop; } /** * Activating/deactivating this feature works like this: * When nothing is active, and the shortcut is pressed, global shortcuts are disabled * (using global_shortcuts_disabled) * When a window that has disabling forced is activated, global shortcuts are disabled. * (using global_shortcuts_disabled_for_client) * When a shortcut is pressed and global shortcuts are disabled (either by a shortcut * or for a client), they are enabled again. */ void Workspace::slotDisableGlobalShortcuts() { if (global_shortcuts_disabled || global_shortcuts_disabled_for_client) disableGlobalShortcuts(false); else disableGlobalShortcuts(true); } static bool pending_dfc = false; void Workspace::disableGlobalShortcutsForClient(bool disable) { if (global_shortcuts_disabled_for_client == disable) return; if (!global_shortcuts_disabled) { if (disable) pending_dfc = true; KGlobalSettings::self()->emitChange(KGlobalSettings::BlockShortcuts, disable); // KWin will get the kipc message too } } void Workspace::disableGlobalShortcuts(bool disable) { KGlobalSettings::self()->emitChange(KGlobalSettings::BlockShortcuts, disable); // KWin will get the kipc message too } void Workspace::slotBlockShortcuts(int data) { if (pending_dfc && data) { global_shortcuts_disabled_for_client = true; pending_dfc = false; } else { global_shortcuts_disabled = data; global_shortcuts_disabled_for_client = false; } // Update also Alt+LMB actions etc. for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) (*it)->updateMouseGrab(); } // Optimized version of QCursor::pos() that tries to avoid X roundtrips // by updating the value only when the X timestamp changes. static QPoint last_cursor_pos; static int last_buttons = 0; static Time last_cursor_timestamp = CurrentTime; static QTimer* last_cursor_timer; QPoint Workspace::cursorPos() const { if (last_cursor_timestamp == CurrentTime || last_cursor_timestamp != QX11Info::appTime()) { last_cursor_timestamp = QX11Info::appTime(); Window root; Window child; int root_x, root_y, win_x, win_y; uint state; XQueryPointer(display(), rootWindow(), &root, &child, &root_x, &root_y, &win_x, &win_y, &state); last_cursor_pos = QPoint(root_x, root_y); last_buttons = state; if (last_cursor_timer == NULL) { Workspace* ws = const_cast(this); last_cursor_timer = new QTimer(ws); last_cursor_timer->setSingleShot(true); connect(last_cursor_timer, SIGNAL(timeout()), ws, SLOT(resetCursorPosTime())); } last_cursor_timer->start(0); } return last_cursor_pos; } /** * Because of QTimer's and the impossibility to get events for all mouse * movements (at least I haven't figured out how) the position needs * to be also refetched after each return to the event loop. */ void Workspace::resetCursorPosTime() { last_cursor_timestamp = CurrentTime; } void Workspace::checkCursorPos() { QPoint last = last_cursor_pos; int lastb = last_buttons; cursorPos(); // Update if needed if (last != last_cursor_pos || lastb != last_buttons) { emit mouseChanged(last_cursor_pos, last, x11ToQtMouseButtons(last_buttons), x11ToQtMouseButtons(lastb), x11ToQtKeyboardModifiers(last_buttons), x11ToQtKeyboardModifiers(lastb)); } } Outline* Workspace::outline() { return m_outline; } #ifdef KWIN_BUILD_SCREENEDGES ScreenEdge* Workspace::screenEdge() { return &m_screenEdge; } #endif bool Workspace::hasTabBox() const { #ifdef KWIN_BUILD_TABBOX return (tab_box != NULL); #else return false; #endif } #ifdef KWIN_BUILD_TABBOX TabBox::TabBox* Workspace::tabBox() const { return tab_box; } #endif QString Workspace::supportInformation() const { QString support; support.append(ki18nc("Introductory text shown in the support information.", "KWin Support Information:\n" "The following information should be used when requesting support on e.g. http://forum.kde.org.\n" "It provides information about the currently running instance, which options are used,\n" "what OpenGL driver and which effects are running.\n" "Please post the information provided underneath this introductory text to a paste bin service\n" "like http://paste.kde.org instead of pasting into support threads.\n").toString()); support.append("\n==========================\n\n"); // all following strings are intended for support. They need to be pasted to e.g forums.kde.org // it is expected that the support will happen in English language or that the people providing // help understand English. Because of that all texts are not translated support.append("Options\n"); support.append("=======\n"); const QMetaObject *metaOptions = options->metaObject(); for (int i=0; ipropertyCount(); ++i) { const QMetaProperty property = metaOptions->property(i); if (QLatin1String(property.name()) == "objectName") { continue; } support.append(QLatin1String(property.name()) % ": " % options->property(property.name()).toString() % '\n'); } support.append("\nCompositing\n"); support.append( "===========\n"); support.append("Qt Graphics System: "); if (Extensions::nonNativePixmaps()) { support.append("raster\n"); } else { support.append("native\n"); } if (effects) { support.append("Compositing is active\n"); switch (effects->compositingType()) { case OpenGLCompositing: { #ifdef KWIN_HAVE_OPENGLES support.append("Compositing Type: OpenGL ES 2.0\n"); #else support.append("Compositing Type: OpenGL\n"); #endif GLPlatform *platform = GLPlatform::instance(); support.append("OpenGL vendor string: " % platform->glVendorString() % '\n'); support.append("OpenGL renderer string: " % platform->glRendererString() % '\n'); support.append("OpenGL version string: " % platform->glVersionString() % '\n'); if (platform->supports(LimitedGLSL)) support.append("OpenGL shading language version string: " % platform->glShadingLanguageVersionString() % '\n'); support.append("Driver: " % GLPlatform::driverToString(platform->driver()) % '\n'); if (!platform->isMesaDriver()) support.append("Driver version: " % GLPlatform::versionToString(platform->driverVersion()) % '\n'); support.append("GPU class: " % GLPlatform::chipClassToString(platform->chipClass()) % '\n'); support.append("OpenGL version: " % GLPlatform::versionToString(platform->glVersion()) % '\n'); if (platform->supports(LimitedGLSL)) support.append("GLSL version: " % GLPlatform::versionToString(platform->glslVersion()) % '\n'); if (platform->isMesaDriver()) support.append("Mesa version: " % GLPlatform::versionToString(platform->mesaVersion()) % '\n'); if (platform->serverVersion() > 0) support.append("X server version: " % GLPlatform::versionToString(platform->serverVersion()) % '\n'); if (platform->kernelVersion() > 0) support.append("Linux kernel version: " % GLPlatform::versionToString(platform->kernelVersion()) % '\n'); support.append("Direct rendering: "); if (platform->isDirectRendering()) { support.append("yes\n"); } else { support.append("no\n"); } support.append("Requires strict binding: "); if (!platform->isLooseBinding()) { support.append("yes\n"); } else { support.append("no\n"); } support.append("GLSL shaders: "); if (platform->supports(GLSL)) { if (platform->supports(LimitedGLSL)) { support.append(" limited\n"); } else { support.append(" yes\n"); } } else { support.append(" no\n"); } support.append("Texture NPOT support: "); if (platform->supports(TextureNPOT)) { if (platform->supports(LimitedNPOT)) { support.append(" limited\n"); } else { support.append(" yes\n"); } } else { support.append(" no\n"); } if (ShaderManager::instance()->isValid()) { support.append("OpenGL 2 Shaders are used\n"); } else { support.append("OpenGL 2 Shaders are not used. Legacy OpenGL 1.x code path is used.\n"); } break; } case XRenderCompositing: support.append("Compositing Type: XRender\n"); break; case NoCompositing: default: support.append("Something is really broken, neither OpenGL nor XRender is used"); } support.append("\nLoaded Effects:\n"); support.append( "---------------\n"); foreach (const QString &effect, static_cast(effects)->loadedEffects()) { support.append(effect % '\n'); } support.append("\nCurrently Active Effects:\n"); support.append( "-------------------------\n"); foreach (const QString &effect, static_cast(effects)->activeEffects()) { support.append(effect % '\n'); } support.append("\nEffect Settings:\n"); support.append( "----------------\n"); foreach (const QString &effect, static_cast(effects)->loadedEffects()) { support.append(static_cast(effects)->supportInformation(effect)); support.append('\n'); } } else { support.append("Compositing is not active\n"); } return support; } void Workspace::slotCompositingToggled() { // notify decorations that composition state has changed if (hasDecorationPlugin()) { KDecorationFactory* factory = mgr->factory(); factory->reset(SettingCompositing); } } void Workspace::slotToggleCompositing() { if (m_compositor) { m_compositor->slotToggleCompositing(); } } } // namespace #include "workspace.moc"