diff --git a/geometry.cpp b/geometry.cpp index cefe1d3ea..7051d4790 100644 --- a/geometry.cpp +++ b/geometry.cpp @@ -1,2021 +1,2017 @@ /***************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak You can Freely distribute this program under the GNU General Public License. See the file "COPYING" for the exact licensing terms. ******************************************************************/ /* This file contains things relevant to geometry, i.e. workspace size, window positions and window sizes. */ #include "client.h" #include "workspace.h" #include #include #include #include #include "placement.h" #include "notifications.h" #include "geometrytip.h" extern Time qt_x_time; namespace KWinInternal { //******************************************** // Workspace //******************************************** /*! Resizes the workspace after an XRANDR screen size change */ void Workspace::desktopResized() { updateClientArea(); - if (options->electricBorders() == Options::ElectricAlways) - { // update electric borders - destroyBorderWindows(); - createBorderWindows(); - } + checkElectricBorders(); } /*! 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 ) { QDesktopWidget *desktopwidget = KApplication::desktop(); int nscreens = desktopwidget -> numScreens (); // kdDebug () << "screens: " << nscreens << endl; QRect* new_wareas = new QRect[ numberOfDesktops() + 1 ]; QRect** new_sareas = new QRect*[ numberOfDesktops() + 1]; QRect* screens = new QRect [ nscreens ]; QRect desktopArea = desktopwidget -> geometry (); for( int iS = 0; iS < nscreens; iS ++ ) { screens [iS] = desktopwidget -> screenGeometry (iS); } for( int i = 1; i <= numberOfDesktops(); ++i ) { new_wareas[ i ] = desktopArea; new_sareas[ i ] = new QRect [ nscreens ]; for( int iS = 0; iS < nscreens; iS ++ ) new_sareas[ i ][ iS ] = screens[ iS ]; } for ( ClientList::ConstIterator it = clients.begin(); it != clients.end(); ++it) { QRect r = (*it)->adjustedClientArea( desktopArea, desktopArea ); if( r == desktopArea ) // should be sufficient continue; if( (*it)->isOnAllDesktops()) for( int i = 1; i <= numberOfDesktops(); ++i ) { new_wareas[ i ] = new_wareas[ i ].intersect( r ); for( int iS = 0; iS < nscreens; iS ++ ) new_sareas[ i ][ iS ] = new_sareas[ i ][ iS ].intersect( (*it)->adjustedClientArea( desktopArea, screens[ iS ] ) ); } else { new_wareas[ (*it)->desktop() ] = new_wareas[ (*it)->desktop() ].intersect( r ); for( int iS = 0; iS < nscreens; iS ++ ) { // kdDebug () << "adjusting new_sarea: " << screens[ iS ] << endl; new_sareas[ (*it)->desktop() ][ iS ] = new_sareas[ (*it)->desktop() ][ iS ].intersect( (*it)->adjustedClientArea( desktopArea, screens[ iS ] ) ); } } } #if 0 for( int i = 1; i <= numberOfDesktops(); ++i ) { for( int iS = 0; iS < nscreens; iS ++ ) kdDebug () << "new_sarea: " << new_sareas[ i ][ iS ] << endl; } #endif // TODO topmenu update for screenarea changes? if( topmenu_space != NULL ) { QRect topmenu_area = desktopArea; topmenu_area.setTop( topMenuHeight()); for( int i = 1; i <= numberOfDesktops(); ++i ) new_wareas[ i ] = new_wareas[ i ].intersect( topmenu_area ); } bool changed = force; if (! screenarea) changed = true; for( int i = 1; !changed && i <= numberOfDesktops(); ++i ) { if( workarea[ i ] != new_wareas[ i ] ) changed = true; for( int iS = 0; iS < nscreens; iS ++ ) if (new_sareas[ i ][ iS ] != screenarea [ i ][ iS ]) changed = true; } if ( changed ) { delete[] workarea; workarea = new_wareas; new_wareas = NULL; delete[] screenarea; screenarea = new_sareas; new_sareas = NULL; 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 ); } updateTopMenuGeometry(); for( ClientList::ConstIterator it = clients.begin(); it != clients.end(); ++it) (*it)->checkWorkspacePosition(); } delete[] screens; delete[] new_sareas; delete[] new_wareas; } 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, const QPoint& p, int desktop ) const { if( desktop == NETWinInfo::OnAllDesktops || desktop == 0 ) desktop = currentDesktop(); QDesktopWidget *desktopwidget = KApplication::desktop(); int screen = desktopwidget->screenNumber( p ); if( screen < 0 ) screen = desktopwidget->primaryScreen(); QRect sarea = screenarea // may be NULL during KWin initialization ? screenarea[ desktop ][ screen ] : desktopwidget->screenGeometry( screen ); QRect warea = workarea[ desktop ].isNull() ? QApplication::desktop()->geometry() : workarea[ desktop ]; switch (opt) { case MaximizeArea: if (options->xineramaMaximizeEnabled) return sarea; else return warea; case MaximizeFullArea: if (options->xineramaMaximizeEnabled) return desktopwidget->screenGeometry( screen ); else return desktopwidget->geometry(); case PlacementArea: if (options->xineramaPlacementEnabled) return sarea; else return warea; case MovementArea: if (options->xineramaMovementEnabled) return desktopwidget->screenGeometry( screen ); else return desktopwidget->geometry(); case WorkArea: return warea; case FullArea: return desktopwidget->geometry(); case ScreenArea: return sarea; } assert( false ); return QRect(); } QRect Workspace::clientArea( clientAreaOption opt, const Client* c ) const { return clientArea( opt, c->geometry().center(), c->desktop()); } /*! 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. */ QPoint Workspace::adjustClientPosition( Client* c, QPoint pos ) { //CT 16mar98, 27May98 - magics: BorderSnapZone, WindowSnapZone //CT adapted for kwin on 25Nov1999 //aleXXX 02Nov2000 added second snapping mode if (options->windowSnapZone || options->borderSnapZone ) { bool sOWO=options->snapOnlyWhenOverlapping; QRect maxRect = clientArea(MovementArea, pos+c->rect().center(), c->desktop()); int xmin = maxRect.left(); int xmax = maxRect.right()+1; //desk size int ymin = maxRect.top(); int ymax = maxRect.bottom()+1; int cx(pos.x()); int cy(pos.y()); int cw(c->width()); int ch(c->height()); int rx(cx+cw); 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; //snap trigger if (snap) { if ((sOWO?(cxxmax):true) && (QABS(rx-xmax)ymax):true) && (QABS(ry-ymax)windowSnapZone; if (snap) { QValueList::ConstIterator l; for (l = clients.begin();l != clients.end();++l ) { if ((*l)->isOnDesktop(currentDesktop()) && !(*l)->isMinimized() && (*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?(cxlx):true) && (QABS(rx-lx)= lx )) || (( rx >= lx ) && ( rx <= lrx )) || (( cx <= lx ) && ( rx >= lrx )) ) { if ((sOWO?(cyly):true) && (QABS(ry-ly)isOnDesktop(currentDesktop())) || ((*it)->isMinimized()) || ((*it)->isOnAllDesktops()) || (!(*it)->isMovable()) ) continue; initPositioning->placeCascaded(*it, QRect(), re_init_cascade_at_first_client); //CT is an if faster than an attribution?? if (re_init_cascade_at_first_client) re_init_cascade_at_first_client = false; } } /*! Unclutters the current desktop by smart-placing all clients again. */ void Workspace::unclutterDesktop() { ClientList::Iterator it(clients.fromLast()); for (; it != clients.end(); --it) { if((!(*it)->isOnDesktop(currentDesktop())) || ((*it)->isMinimized()) || ((*it)->isOnAllDesktops()) || (!(*it)->isMovable()) ) continue; initPositioning->placeSmart(*it, QRect()); } } void Workspace::updateTopMenuGeometry( Client* c ) { if( !managingTopMenus()) return; if( c != NULL ) { XEvent ev; ev.xclient.display = qt_xdisplay(); ev.xclient.type = ClientMessage; ev.xclient.window = c->window(); static Atom msg_type_atom = XInternAtom( qt_xdisplay(), "_KDE_TOPMENU_MINSIZE", False ); ev.xclient.message_type = msg_type_atom; ev.xclient.format = 32; ev.xclient.data.l[0] = qt_x_time; ev.xclient.data.l[1] = topmenu_space->width(); ev.xclient.data.l[2] = topmenu_space->height(); ev.xclient.data.l[3] = 0; ev.xclient.data.l[4] = 0; XSendEvent( qt_xdisplay(), c->window(), False, NoEventMask, &ev ); KWin::setStrut( c->window(), 0, 0, topmenu_height, 0 ); // so that kicker etc. know c->checkWorkspacePosition(); return; } // c == NULL - update all, including topmenu_space QRect area; area = clientArea( MaximizeFullArea, QPoint( 0, 0 ), 1 ); // HACK desktop ? area.setHeight( topMenuHeight()); topmenu_space->setGeometry( area ); for( ClientList::ConstIterator it = topmenus.begin(); it != topmenus.end(); ++it ) updateTopMenuGeometry( *it ); } //******************************************** // Client //******************************************** void Client::keepInArea( const QRect& area ) { if ( geometry().right() > area.right() && width() < area.width() ) move( area.right() - width(), y() ); if ( geometry().bottom() > area.bottom() && height() < area.height() ) move( x(), area.bottom() - height() ); 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; // topmenu area is reserved in updateClientArea() if( isTopMenu()) return r; 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); NETExtendedStrut ext = info->extendedStrut(); if( ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0 && ( str.left_width != 0 || str.right_width != 0 || str.top_width != 0 || str.bottom_width != 0 )) { // hack, might cause problems... this tries to guess the start/end of a // non-extended strut; only works on windows that have exact same // geometry as their strut (ie, if the geometry fits the width // exactly, we will adjust length of strut to match the geometry as well; // otherwise we use the full-edge strut) if (stareaT.top() == geometry().top() && stareaT.bottom() == geometry().bottom()) { stareaT.setLeft(geometry().left()); stareaT.setRight(geometry().right()); // kdDebug () << "Trimming top-strut to geometry() to: " << stareaT << endl; } if (stareaB.top() == geometry().top() && stareaB.bottom() == geometry().bottom()) { stareaB.setLeft(geometry().left()); stareaB.setRight(geometry().right()); // kdDebug () << "Trimming bottom-strut to geometry(): " << stareaB << endl; } if (stareaL.left() == geometry().left() && stareaL.right() == geometry().right()) { stareaL.setTop(geometry().top()); stareaL.setBottom(geometry().bottom()); // kdDebug () << "Trimming left-strut to geometry(): " << stareaL << endl; } if (stareaR.left() == geometry().left() && stareaR.right() == geometry().right()) { stareaR.setTop(geometry().top()); stareaR.setBottom(geometry().bottom()); // kdDebug () << "Trimming right-strut to geometry(): " << stareaR << endl; } } if (stareaL . intersects (area)) { // kdDebug () << "Moving left of: " << r << " to " << stareaL.right() + 1 << endl; r . setLeft( stareaL . right() + 1 ); } if (stareaR . intersects (area)) { // kdDebug () << "Moving right of: " << r << " to " << stareaR.left() - 1 << endl; r . setRight( stareaR . left() - 1 ); } if (stareaT . intersects (area)) { // kdDebug () << "Moving top of: " << r << " to " << stareaT.bottom() + 1 << endl; r . setTop( stareaT . bottom() + 1 ); } if (stareaB . intersects (area)) { // kdDebug () << "Moving bottom of: " << r << " to " << stareaB.top() - 1 << endl; 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 = XDisplayHeight( qt_xdisplay(), DefaultScreen( qt_xdisplay())); } if( str.right != 0 ) { ext.right_width = str.right; ext.right_start = 0; ext.right_end = XDisplayHeight( qt_xdisplay(), DefaultScreen( qt_xdisplay())); } if( str.top != 0 ) { ext.top_width = str.top; ext.top_start = 0; ext.top_end = XDisplayWidth( qt_xdisplay(), DefaultScreen( qt_xdisplay())); } if( str.bottom != 0 ) { ext.bottom_width = str.bottom; ext.bottom_start = 0; ext.bottom_end = XDisplayWidth( qt_xdisplay(), DefaultScreen( qt_xdisplay())); } } return ext; } 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; } // updates differences to workarea edges for all directions void Client::updateWorkareaDiffs() { QRect area = workspace()->clientArea( WorkArea, this ); QRect geom = geometry(); workarea_diff_x = computeWorkareaDiff( geom.left(), geom.right(), area.left(), area.right()); workarea_diff_y = computeWorkareaDiff( geom.top(), geom.bottom(), area.top(), area.bottom()); } // If the client was inside workarea in the x direction, and if it was close to the left/right // edge, return the distance from the left/right edge (negative for left, positive for right) // INT_MIN means 'not inside workarea', INT_MAX means 'not near edge'. // In order to recognize 'at the left workarea edge' from 'at the right workarea edge' // (i.e. negative vs positive zero), the distances are one larger in absolute value than they // really are (i.e. 5 pixels from the left edge is -6, not -5). A bit hacky, but I'm lazy // to rewrite it just to make it nicer. If this will ever get touched again, perhaps then. // the y direction is done the same, just the values will be rotated: top->left, bottom->right int Client::computeWorkareaDiff( int left, int right, int a_left, int a_right ) { int left_diff = left - a_left; int right_diff = a_right - right; if( left_diff < 0 || right_diff < 0 ) return INT_MIN; else // fully inside workarea in this direction direction { // max distance from edge where it's still considered to be close and is kept at that distance int max_diff = ( a_right - a_left ) / 10; if( left_diff < right_diff ) return left_diff < max_diff ? -left_diff - 1 : INT_MAX; else if( left_diff > right_diff ) return right_diff < max_diff ? right_diff + 1 : INT_MAX; return INT_MAX; // not close to workarea edge } } void Client::checkWorkspacePosition() { if( maximizeMode() != MaximizeRestore ) // TODO update geom_restore? changeMaximize( false, false, true ); // adjust size if( isFullScreen()) { QRect area = workspace()->clientArea( MaximizeFullArea, this ); if( geometry() != area ) setGeometry( area ); return; } if( isDock()) return; if( isOverride()) return; // I wish I knew what to do here :( if( isTopMenu()) { if( workspace()->managingTopMenus()) { QRect area; ClientList mainclients = mainClients(); if( mainclients.count() == 1 ) area = workspace()->clientArea( MaximizeFullArea, mainclients.first()); else area = workspace()->clientArea( MaximizeFullArea, QPoint( 0, 0 ), desktop()); area.setHeight( workspace()->topMenuHeight()); // kdDebug() << "TOPMENU size adjust: " << area << ":" << this << endl; setGeometry( area ); } return; } if( !isShade()) // TODO { int old_diff_x = workarea_diff_x; int old_diff_y = workarea_diff_y; updateWorkareaDiffs(); // 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; QRect area = workspace()->clientArea( WorkArea, this ); QRect new_geom = geometry(); QRect tmp_rect_x( new_geom.left(), 0, new_geom.width(), 0 ); QRect tmp_area_x( area.left(), 0, area.width(), 0 ); checkDirection( workarea_diff_x, old_diff_x, tmp_rect_x, tmp_area_x ); // the x<->y swapping QRect tmp_rect_y( new_geom.top(), 0, new_geom.height(), 0 ); QRect tmp_area_y( area.top(), 0, area.height(), 0 ); checkDirection( workarea_diff_y, old_diff_y, tmp_rect_y, tmp_area_y ); new_geom = QRect( tmp_rect_x.left(), tmp_rect_y.left(), tmp_rect_x.width(), tmp_rect_y.width()); QRect final_geom( new_geom.topLeft(), adjustedSize( new_geom.size())); if( final_geom != new_geom ) // size increments, or size restrictions { // adjusted size differing matters only for right and bottom edge if( old_diff_x != INT_MAX && old_diff_x > 0 ) final_geom.moveRight( area.right() - ( old_diff_x - 1 )); if( old_diff_y != INT_MAX && old_diff_y > 0 ) final_geom.moveBottom( area.bottom() - ( old_diff_y - 1 )); } if( final_geom != geometry() ) setGeometry( final_geom ); // updateWorkareaDiffs(); done already by setGeometry() } } // Try to be smart about keeping the clients visible. // If the client was fully inside the workspace before, try to keep // it still inside the workarea, possibly moving it or making it smaller if possible, // and try to keep the distance from the nearest workarea edge. // On the other hand, it it was partially moved outside of the workspace in some direction, // don't do anything with that direction if it's still at least partially visible. If it's // not visible anymore at all, make sure it's visible at least partially // again (not fully, as that could(?) be potentionally annoying) by // moving it slightly inside the workarea (those '+ 5'). // Again, this is done for the x direction, y direction will be done by x<->y swapping void Client::checkDirection( int new_diff, int old_diff, QRect& rect, const QRect& area ) { if( old_diff != INT_MIN ) // was inside workarea { if( old_diff == INT_MAX ) // was in workarea, but far from edge { if( new_diff == INT_MIN ) // is not anymore fully in workarea { rect.setLeft( area.left()); rect.setRight( area.right()); } return; } if( isResizable()) { if( rect.width() > area.width()) rect.setWidth( area.width()); if( rect.width() >= area.width() / 2 ) { if( old_diff < 0 ) rect.setLeft( area.left() + ( -old_diff - 1 ) ); else // old_diff > 0 rect.setRight( area.right() - ( old_diff - 1 )); } } if( isMovable()) { if( old_diff < 0 ) // was in left third, keep distance from left edge rect.moveLeft( area.left() + ( -old_diff - 1 )); else // old_diff > 0 // was in right third, keep distance from right edge rect.moveRight( area.right() - ( old_diff - 1 )); } // this isResizable() block is copied from above, the difference is // the condition with 'area.width() / 2' - for windows that are not wide, // moving is preffered to resizing if( isResizable()) { if( old_diff < 0 ) rect.setLeft( area.left() + ( -old_diff - 1 ) ); else // old_diff > 0 rect.setRight( area.right() - ( old_diff - 1 )); } } if( rect.right() < area.left() + 5 || rect.left() > area.right() - 5 ) { // not visible (almost) at all - try to make it at least partially visible if( isMovable()) { if( rect.left() < area.left() + 5 ) rect.moveRight( area.left() + 5 ); if( rect.right() > area.right() - 5 ) rect.moveLeft( area.right() - 5 ); } } } /*! 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 )); return sizeForClientSize( wsize, mode ); } /*! 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 ) const { int w = wsize.width(); int h = wsize.height(); 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( xSizeHint.min_width, xSizeHint.min_height ); QSize max_size( xSizeHint.max_width, xSizeHint.max_height ); 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 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; 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: { ASPECT_CHECK_SHRINK_H_GROW_W ASPECT_CHECK_SHRINK_W_GROW_H ASPECT_CHECK_GROW_H ASPECT_CHECK_GROW_W break; } 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; } case SizemodeShaded: 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 ( mode == SizemodeShaded && wsize.height() == 0 ) h = 0; return QSize( w + border_left + border_right, h + border_top + border_bottom ); } /*! Gets the client's normal WM hints and reconfigures itself respectively. */ void Client::getWmNormalHints() { long msize; if (XGetWMNormalHints(qt_xdisplay(), window(), &xSizeHint, &msize) == 0 ) xSizeHint.flags = 0; // set defined values for the fields, even if they're not in flags // basesize is just like minsize, except for minsize is not used for aspect ratios // keep basesize only for aspect ratios, for size increments, keep the base // value in minsize - see ICCCM 4.1.2.3 if( xSizeHint.flags & PBaseSize ) { if( ! ( xSizeHint.flags & PMinSize )) // PBaseSize and PMinSize are equivalent { 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 & PMinSize )) xSizeHint.min_width = xSizeHint.min_height = 0; if( ! ( xSizeHint.flags & PMaxSize )) xSizeHint.max_width = xSizeHint.max_height = INT_MAX; if( xSizeHint.flags & PResizeInc ) { xSizeHint.width_inc = kMax( xSizeHint.width_inc, 1 ); xSizeHint.height_inc = kMax( 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 = kMax( xSizeHint.min_aspect.y, 1 ); xSizeHint.max_aspect.y = kMax( 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; if( isManaged()) { // update to match restrictions QSize new_size = adjustedSize( size()); if( new_size != size() && !isShade()) // SHADE resizeWithChecks( new_size ); } updateAllowedActions(); // affects isResizeable() } /*! 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( qt_xdisplay(), 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 ) { 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() ) { 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 ) ); // TODO what to do with maximized windows? if ( maximizeMode() != MaximizeFull || ns != size()) { resetMaximize(); ++block_geometry; move( new_pos ); plainResize( ns ); // TODO must(?) resize before gravitating? --block_geometry; setGeometry( QRect( calculateGravitation( false, gravity ), size()), ForceGeometrySet ); // 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 { if ( isShade()) // SELI SHADE setShade( ShadeNone ); 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 { resetMaximize(); int save_gravity = xSizeHint.win_gravity; xSizeHint.win_gravity = gravity; resizeWithChecks( ns ); xSizeHint.win_gravity = save_gravity; } } // 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 ) { 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; } // if it would be moved outside of workarea, keep it inside, // see also Client::computeWorkareaDiff() if( workarea_diff_x != INT_MIN && w <= area.width()) // was inside and can still fit { if( newx < area.left()) newx = area.left(); if( newx + w > area.right() + 1 ) newx = area.right() + 1 - w; assert( newx >= area.left() && newx + w <= area.right() + 1 ); // width was checked above } if( workarea_diff_y != INT_MIN && h <= area.height()) // was inside and can still fit { if( newy < area.top()) newy = area.top(); if( newy + h > area.bottom() + 1 ) newy = area.bottom() + 1 - h; assert( newy >= area.top() && newy + h <= area.bottom() + 1 ); // height was checked above } 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 ); } /*! Returns whether the window is resizable or has a fixed size. */ bool Client::isResizable() const { if ( !isMovable() || !motif_may_resize || isSplash()) return FALSE; if ( ( xSizeHint.flags & PMaxSize) == 0 || (xSizeHint.flags & PMinSize ) == 0 ) return TRUE; return ( xSizeHint.min_width < xSizeHint.max_width ) || ( xSizeHint.min_height < xSizeHint.max_height ); } /* Returns whether the window is maximizable or not */ bool Client::isMaximizable() const { if ( maximizeMode() != MaximizeRestore ) return TRUE; if( !isResizable() || isToolbar()) // SELI isToolbar() ? return false; if( xSizeHint.max_height < 32767 || xSizeHint.max_width < 32767 ) // sizes are 16bit with X return false; return true; } /*! Reimplemented to inform the client about the new window position. */ void Client::setGeometry( int x, int y, int w, int h, ForceGeometry_t force ) { if( force == NormalGeometrySet && frame_geometry == QRect( x, y, w, h )) return; frame_geometry = QRect( x, y, w, h ); if( !isShade()) client_size = QSize( w - border_left - border_right, h - border_top - border_bottom ); else { // check that the frame is not resized to full size when it should be shaded if( !shade_geometry_change && h != border_top + border_bottom ) { kdDebug() << "h:" << h << ":t:" << border_top << ":b:" << border_bottom << endl; assert( false ); } client_size = QSize( w - border_left - border_right, client_size.height()); } updateWorkareaDiffs(); if( block_geometry == 0 ) { XMoveResizeWindow( qt_xdisplay(), frameId(), x, y, w, h ); resizeDecoration( QSize( w, h )); if( !isShade()) { QSize cs = clientSize(); XMoveResizeWindow( qt_xdisplay(), wrapperId(), clientPos().x(), clientPos().y(), cs.width(), cs.height()); // FRAME tady poradi tak, at neni flicker XMoveResizeWindow( qt_xdisplay(), window(), 0, 0, cs.width(), cs.height()); } if( shape()) updateShape(); // SELI TODO won't this be too expensive? updateWorkareaDiffs(); sendSyntheticConfigureNotify(); } } void Client::plainResize( int w, int h, ForceGeometry_t force ) { // TODO make this deffered with isResize() ? old kwin did if( force == NormalGeometrySet && frame_geometry.size() == QSize( w, h )) return; frame_geometry.setSize( QSize( w, h )); if( !isShade()) client_size = QSize( w - border_left - border_right, h - border_top - border_bottom ); else { // check that the frame is not resized to full size when it should be shaded if( !shade_geometry_change && h != border_top + border_bottom ) { kdDebug() << "h:" << h << ":t:" << border_top << ":b:" << border_bottom << endl; assert( false ); } client_size = QSize( w - border_left - border_right, client_size.height()); } updateWorkareaDiffs(); if( block_geometry == 0 ) { // FRAME tady poradi tak, at neni flicker XResizeWindow( qt_xdisplay(), frameId(), w, h ); resizeDecoration( QSize( w, h )); if( !isShade()) { QSize cs = clientSize(); XMoveResizeWindow( qt_xdisplay(), wrapperId(), clientPos().x(), clientPos().y(), cs.width(), cs.height()); XMoveResizeWindow( qt_xdisplay(), window(), 0, 0, cs.width(), cs.height()); } if( shape()) updateShape(); updateWorkareaDiffs(); sendSyntheticConfigureNotify(); } } /*! Reimplemented to inform the client about the new window position. */ void Client::move( int x, int y, ForceGeometry_t force ) { if( force == NormalGeometrySet && frame_geometry.topLeft() == QPoint( x, y )) return; frame_geometry.moveTopLeft( QPoint( x, y )); updateWorkareaDiffs(); if( block_geometry == 0 ) { XMoveWindow( qt_xdisplay(), frameId(), x, y ); sendSyntheticConfigureNotify(); } } 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 ); } void Client::changeMaximize( bool vertical, bool horizontal, bool adjust ) { if( !isMaximizable()) return; ++block_geometry; // TODO GeometryBlocker class? if( isShade()) // SELI SHADE setShade( ShadeNone ); 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 ); } // maximing one way and unmaximizing the other way shouldn't happen Q_ASSERT( !( vertical && horizontal ) || (( max_mode & MaximizeVertical != 0 ) == ( max_mode & MaximizeHorizontal != 0 ))); // save sizes for restoring, if maximalizing bool maximalizing = false; if( vertical && !(old_mode & MaximizeVertical )) { geom_restore.setTop( y()); geom_restore.setHeight( height()); maximalizing = true; } if( horizontal && !( old_mode & MaximizeHorizontal )) { geom_restore.setLeft( x()); geom_restore.setWidth( width()); maximalizing = true; } if( !adjust ) { if( maximalizing ) Notify::raise( Notify::Maximize ); else Notify::raise( Notify::UnMaximize ); } if( decoration != NULL ) // decorations may turn off some borders when maximized decoration->borders( border_left, border_right, border_top, border_bottom ); QRect clientArea = workspace()->clientArea( MaximizeArea, this ); switch (max_mode) { case MaximizeVertical: { if( old_mode & MaximizeHorizontal ) // actually restoring from MaximizeFull { if( geom_restore.width() == 0 ) { // needs placement plainResize( adjustedSize(QSize(width(), clientArea.height()), SizemodeFixedH )); workspace()->placeSmart( this, clientArea ); } else setGeometry( QRect(QPoint( geom_restore.x(), clientArea.top()), adjustedSize(QSize( geom_restore.width(), clientArea.height()), SizemodeFixedH ))); } else setGeometry( QRect(QPoint(x(), clientArea.top()), adjustedSize(QSize(width(), clientArea.height()), SizemodeFixedH ))); info->setState( NET::MaxVert, NET::Max ); break; } case MaximizeHorizontal: { if( old_mode & MaximizeVertical ) // actually restoring from MaximizeFull { if( geom_restore.height() == 0 ) { // needs placement plainResize( adjustedSize(QSize(clientArea.width(), height()), SizemodeFixedW )); workspace()->placeSmart( this, clientArea ); } else setGeometry( QRect( QPoint(clientArea.left(), geom_restore.y()), adjustedSize(QSize(clientArea.width(), geom_restore.height()), SizemodeFixedW ))); } else setGeometry( QRect( QPoint(clientArea.left(), y()), adjustedSize(QSize(clientArea.width(), height()), SizemodeFixedW ))); 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()); } setGeometry( restore ); info->setState( 0, NET::Max ); break; } case MaximizeFull: { QSize adjSize = adjustedSize(clientArea.size(), SizemodeMax ); QRect r = QRect(clientArea.topLeft(), adjSize); setGeometry( r ); info->setState( NET::Max, NET::Max ); break; } default: break; } --block_geometry; setGeometry( geometry(), ForceGeometrySet ); updateAllowedActions(); if( decoration != NULL ) decoration->maximizeChange(); } void Client::resetMaximize() { if( max_mode == MaximizeRestore ) return; max_mode = MaximizeRestore; Notify::raise( Notify::UnMaximize ); info->setState( 0, NET::Max ); updateAllowedActions(); if( decoration != NULL ) decoration->borders( border_left, border_right, border_top, border_bottom ); setGeometry( geometry(), ForceGeometrySet ); if( decoration != NULL ) decoration->maximizeChange(); } bool Client::isFullScreenable( bool fullscreen_hack ) const { if( fullscreen_hack ) return isNormalWindow() || isOverride(); else // 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 { return isNormalWindow() && fullscreen_mode != FullScreenHack && ( isMaximizable() || isFullScreen()); // isMaximizable() is false for isFullScreen() } void Client::setFullScreen( bool set, bool user ) { if( !isFullScreen() && !set ) return; if( fullscreen_mode == FullScreenHack ) return; if( user && !userCanSetFullScreen()) return; setShade( ShadeNone ); bool was_fs = isFullScreen(); if( !was_fs ) geom_fs_restore = geometry(); fullscreen_mode = set ? FullScreenNormal : FullScreenNone; if( was_fs == isFullScreen()) return; StackingUpdatesBlocker blocker( workspace()); workspace()->updateClientLayer( this ); // active fullscreens get different layer info->setState( isFullScreen() ? NET::FullScreen : 0, NET::FullScreen ); updateDecoration( false, false ); if( isFullScreen()) setGeometry( workspace()->clientArea( MaximizeFullArea, this )); else { if( maximizeMode() != MaximizeRestore ) changeMaximize( false, false, true ); // adjust size else if( !geom_fs_restore.isNull()) setGeometry( geom_fs_restore ); // TODO isShaded() ? else { // does this ever happen? setGeometry( workspace()->clientArea( MaximizeArea, this )); } } } static QRect* visible_bound = 0; static GeometryTip* geometryTip = 0; void Client::drawbound( const QRect& geom ) { assert( visible_bound == NULL ); visible_bound = new QRect( geom ); doDrawbound( *visible_bound, false ); } void Client::clearbound() { if( visible_bound == NULL ) return; doDrawbound( *visible_bound, true ); delete visible_bound; visible_bound = 0; } void Client::doDrawbound( const QRect& geom, bool clear ) { if( decoration != NULL && decoration->drawbound( geom, clear )) return; // done by decoration QPainter p ( workspace()->desktopWidget() ); p.setPen( QPen( Qt::white, 5 ) ); p.setRasterOp( Qt::XorROP ); p.drawRect( geom ); } void Client::positionGeometryTip() { assert( isMove() || isResize()); // Position and Size display if (options->showGeometryTip()) { if( !geometryTip ) { // save under is not necessary with opaque, and seem to make things slower bool save_under = ( isMove() && options->moveMode != Options::Opaque ) || ( isResize() && options->resizeMode != Options::Opaque ); geometryTip = new GeometryTip( &xSizeHint, save_under ); } 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(); } } } class EatAllPaintEvents : public QObject { protected: virtual bool eventFilter( QObject* o, QEvent* e ) { return e->type() == QEvent::Paint && o != geometryTip; } }; static EatAllPaintEvents* eater = 0; bool Client::startMoveResize() { assert( !moveResizeMode ); assert( QWidget::keyboardGrabber() == NULL ); assert( QWidget::mouseGrabber() == NULL ); 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( qt_xdisplay(), workspace()->rootWin(), r.x(), r.y(), r.width(), r.height(), 0, CopyFromParent, InputOnly, CopyFromParent, 0, &attrs ); XMapRaised( qt_xdisplay(), move_resize_grab_window ); if( XGrabPointer( qt_xdisplay(), move_resize_grab_window, False, ButtonPressMask | ButtonReleaseMask | PointerMotionMask | EnterWindowMask | LeaveWindowMask, GrabModeAsync, GrabModeAsync, None, cursor.handle(), qt_x_time ) == Success ) has_grab = true; if( XGrabKeyboard( qt_xdisplay(), frameId(), False, GrabModeAsync, GrabModeAsync, qt_x_time ) == Success ) has_grab = true; if( !has_grab ) // at least one grab is necessary in order to be able to finish move/resize { XDestroyWindow( qt_xdisplay(), move_resize_grab_window ); move_resize_grab_window = None; return false; } if ( maximizeMode() != MaximizeRestore ) resetMaximize(); moveResizeMode = true; workspace()->setClientIsMoving(this); initialMoveResizeGeom = moveResizeGeom = geometry(); checkUnrestrictedMoveResize(); if ( ( isMove() && options->moveMode != Options::Opaque ) || ( isResize() && options->resizeMode != Options::Opaque ) ) { grabXServer(); kapp->sendPostedEvents(); // we have server grab -> nothing should cause paint events // unfortunately, that's not completely true, Qt may generate // paint events on some widgets due to FocusIn(?) // eat them, otherwise XOR painting will be broken (#58054) // paint events for the geometrytip need to be allowed, though eater = new EatAllPaintEvents; kapp->installEventFilter( eater ); } Notify::raise( isResize() ? Notify::ResizeStart : Notify::MoveStart ); return true; } void Client::finishMoveResize( bool cancel ) { leaveMoveResize(); if( cancel ) setGeometry( initialMoveResizeGeom ); else setGeometry( moveResizeGeom ); // FRAME update(); Notify::raise( isResize() ? Notify::ResizeEnd : Notify::MoveEnd ); } void Client::leaveMoveResize() { clearbound(); if (geometryTip) { geometryTip->hide(); delete geometryTip; geometryTip = NULL; } if ( ( isMove() && options->moveMode != Options::Opaque ) || ( isResize() && options->resizeMode != Options::Opaque ) ) ungrabXServer(); XUngrabKeyboard( qt_xdisplay(), qt_x_time ); XUngrabPointer( qt_xdisplay(), qt_x_time ); XDestroyWindow( qt_xdisplay(), move_resize_grab_window ); move_resize_grab_window = None; workspace()->setClientIsMoving(0); if( move_faked_activity ) workspace()->unfakeActivity( this ); move_faked_activity = false; moveResizeMode = false; delete eater; eater = 0; } // 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 = KMIN( 100 + border_right, moveResizeGeom.width()); right_marge = KMIN( 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 ) // titlebar mustn't go out unrestrictedMoveResize = true; // no need to check top_marge, titlebar_marge already handles it 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; } } void Client::handleMoveResize( int x, int y, int x_root, int y_root ) { if(( mode == PositionCenter && !isMovable()) || ( mode != PositionCenter && ( isShade() || !isResizable()))) return; if ( !moveResizeMode ) { QPoint p( QPoint( x, y ) - moveOffset ); if (p.manhattanLength() >= 6) { if( !startMoveResize()) { buttonDown = false; setCursor( mode ); return; } } else return; } // ShadeHover or ShadeActive, ShadeNormal was already avoided above if ( mode != PositionCenter && shade_mode != ShadeNone ) // SHADE 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? // compute bounds // NOTE: This is duped in checkUnrestrictedMoveResize(). QRect desktopArea = workspace()->clientArea( WorkArea, globalPos, desktop()); int left_marge, right_marge, top_marge, bottom_marge, titlebar_marge; if( unrestrictedMoveResize ) // unrestricted, just don't let it go out completely left_marge = right_marge = top_marge = bottom_marge = titlebar_marge = 5; else // 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 = KMIN( 100 + border_right, moveResizeGeom.width()); right_marge = KMIN( 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; } 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: assert( false ); break; } // TODO snap // NOTE: This is duped in checkUnrestrictedMoveResize(). if( moveResizeGeom.bottom() < desktopArea.top() + top_marge ) moveResizeGeom.setBottom( desktopArea.top() + top_marge ); if( moveResizeGeom.top() > desktopArea.bottom() - bottom_marge ) moveResizeGeom.setTop( desktopArea.bottom() - bottom_marge ); if( moveResizeGeom.right() < desktopArea.left() + left_marge ) moveResizeGeom.setRight( desktopArea.left() + left_marge ); if( moveResizeGeom.left() > desktopArea.right() - right_marge ) moveResizeGeom.setLeft(desktopArea.right() - right_marge ); if( !unrestrictedMoveResize && moveResizeGeom.top() < desktopArea.top() ) // titlebar mustn't go out moveResizeGeom.setTop( desktopArea.top()); 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: assert( false ); break; } if( moveResizeGeom.size() != previousMoveResizeGeom.size()) update = true; } else if( isMove()) { assert( mode == PositionCenter ); // first move, then snap, then check bounds moveResizeGeom.moveTopLeft( topleft ); moveResizeGeom.moveTopLeft( workspace()->adjustClientPosition( this, moveResizeGeom.topLeft() ) ); // NOTE: This is duped in checkUnrestrictedMoveResize(). if( moveResizeGeom.bottom() < desktopArea.top() + titlebar_marge - 1 ) // titlebar mustn't go out moveResizeGeom.moveBottom( desktopArea.top() + titlebar_marge - 1 ); // no need to check top_marge, titlebar_marge already handles it if( moveResizeGeom.top() > desktopArea.bottom() - bottom_marge ) moveResizeGeom.moveTop( desktopArea.bottom() - bottom_marge ); if( moveResizeGeom.right() < desktopArea.left() + left_marge ) moveResizeGeom.moveRight( desktopArea.left() + left_marge ); if( moveResizeGeom.left() > desktopArea.right() - right_marge ) moveResizeGeom.moveLeft(desktopArea.right() - right_marge ); if( moveResizeGeom.topLeft() != previousMoveResizeGeom.topLeft()) update = true; } else assert( false ); if( update ) { if(( isResize() ? options->resizeMode : options->moveMode ) == Options::Opaque ) { setGeometry( moveResizeGeom ); positionGeometryTip(); } else if(( isResize() ? options->resizeMode : options->moveMode ) == Options::Transparent ) { clearbound(); // it's necessary to move the geometry tip when there's no outline positionGeometryTip(); // shown, otherwise it would cause repaint problems in case drawbound( moveResizeGeom ); // they overlap; the paint event will come after this, } // so the geometry tip will be painted above the outline } if ( isMove() ) workspace()->clientMoved(globalPos, qt_x_time); } } // namespace diff --git a/workspace.cpp b/workspace.cpp index 9ad6a894f..abd809470 100644 --- a/workspace.cpp +++ b/workspace.cpp @@ -1,2028 +1,2034 @@ /***************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak You can Freely distribute this program under the GNU General Public License. See the file "COPYING" for the exact licensing terms. ******************************************************************/ //#define QT_CLEAN_NAMESPACE #include "workspace.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "plugins.h" #include "client.h" #include "popupinfo.h" #include "tabbox.h" #include "atoms.h" #include "placement.h" #include "notifications.h" #include "group.h" #include #include #include #include extern Time qt_x_time; namespace KWinInternal { extern int screen_number; 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 ) : DCOPObject ("KWinInterface"), QObject (0, "workspace"), current_desktop (0), number_of_desktops(0), popup_client (0), desktop_widget (0), active_client (0), last_active_client (0), most_recently_raised (0), movingClient(0), last_restack (CurrentTime), was_user_interaction (false), session_saving (false), control_grab (false), tab_grab (false), mouse_emulation (false), block_focus (0), tab_box (0), popupinfo (0), popup (0), advanced_popup (0), desk_popup (0), desk_popup_index (0), keys (0), root (0), workspaceInit (true), startup(0), electric_have_borders(false), electric_current_border(0), electric_top_border(None), electric_bottom_border(None), electric_left_border(None), electric_right_border(None), layoutOrientation(Qt::Vertical), layoutX(-1), layoutY(2), workarea(NULL), screenarea(NULL), set_active_client_recursion( 0 ), block_stacking_updates( 0 ), forced_global_mouse_grab( false ) { _self = this; mgr = new PluginMgr; root = qt_xrootwin(); default_colormap = DefaultColormap(qt_xdisplay(), qt_xscreen() ); installed_colormap = default_colormap; session.setAutoDelete( TRUE ); updateXTime(); // needed for proper initialization of user_time in Client ctor electric_time_first = qt_x_time; electric_time_last = qt_x_time; if ( restore ) loadSessionInfo(); loadFakeSessionInfo(); (void) QApplication::desktop(); // trigger creation of desktop widget desktop_widget = new QWidget( 0, "desktop_widget", Qt::WType_Desktop | Qt::WPaintUnclipped ); kapp->setGlobalMouseTracking( true ); // so that this doesn't mess eventmask on root window later // call this before XSelectInput() on the root window startup = new KStartupInfo( KStartupInfo::DisableKWinModule | KStartupInfo::AnnounceSilenceChanges, this ); // select windowmanager privileges XSelectInput(qt_xdisplay(), root, KeyPressMask | PropertyChangeMask | ColormapChangeMask | SubstructureRedirectMask | SubstructureNotifyMask | FocusChangeMask // for NotifyDetailNone ); Shape::init(); // compatibility long data = 1; XChangeProperty( qt_xdisplay(), qt_xrootwin(), atoms->kwin_running, atoms->kwin_running, 32, PropModeAppend, (unsigned char*) &data, 1 ); initShortcuts(); tab_box = new TabBox( this ); popupinfo = new PopupInfo( ); init(); #if (QT_VERSION-0 >= 0x030200) // XRANDR support connect( kapp->desktop(), SIGNAL( resized( int )), SLOT( desktopResized())); #endif } void Workspace::init() { - if (options->electricBorders() == Options::ElectricAlways) - createBorderWindows(); + checkElectricBorders(); supportWindow = new QWidget; XLowerWindow( qt_xdisplay(), supportWindow->winId()); // see usage in layers.cpp XSetWindowAttributes attr; attr.override_redirect = 1; null_focus_window = XCreateWindow( qt_xdisplay(), qt_xrootwin(), -1,-1, 1, 1, 0, CopyFromParent, InputOnly, CopyFromParent, CWOverrideRedirect, &attr ); XMapWindow(qt_xdisplay(), 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::KDESystemTrayWindows | NET::WMName | NET::WMVisibleName | NET::WMDesktop | NET::WMWindowType | NET::WMState | NET::WMStrut | NET::WMIconGeometry | NET::WMIcon | NET::WMPid | NET::WMMoveResize | NET::WMKDESystemTrayWinFor | NET::WMKDEFrameStrut | NET::WMPing , NET::NormalMask | NET::DesktopMask | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask | NET::OverrideMask | NET::TopMenuMask | NET::UtilityMask | NET::SplashMask | 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 | 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 , }; rootInfo = new RootInfo( this, qt_xdisplay(), supportWindow->winId(), "KWin", protocols, 5, qt_xscreen() ); loadDesktopSettings(); // extra NETRootInfo instance in Client mode is needed to get the values of the properties NETRootInfo client_info( qt_xdisplay(), NET::ActiveWindow | NET::CurrentDesktop ); int initial_desktop; if( !kapp->isSessionRestored()) initial_desktop = client_info.currentDesktop(); else { KConfigGroupSaver saver( kapp->sessionConfig(), "Session" ); initial_desktop = kapp->sessionConfig()->readNumEntry( "desktop", 1 ); } if( !setCurrentDesktop( initial_desktop )) setCurrentDesktop( 1 ); // now we know how many desktops we'll, thus, we initialise the positioning object initPositioning = new Placement(this); connect(&reconfigureTimer, SIGNAL(timeout()), this, SLOT(slotReconfigure())); connect( &updateToolWindowsTimer, SIGNAL( timeout()), this, SLOT( slotUpdateToolWindows())); connect(kapp, SIGNAL(appearanceChanged()), this, SLOT(slotReconfigure())); connect(kapp, SIGNAL(settingsChanged(int)), this, SLOT(slotSettingsChanged(int))); active_client = NULL; rootInfo->setActiveWindow( None ); focusToNull(); if( !kapp->isSessionRestored()) ++block_focus; // because it will be set below char nm[ 100 ]; sprintf( nm, "_KDE_TOPMENU_OWNER_S%d", DefaultScreen( qt_xdisplay())); Atom topmenu_atom = XInternAtom( qt_xdisplay(), nm, False ); topmenu_selection = new KSelectionOwner( topmenu_atom ); topmenu_watcher = new KSelectionWatcher( topmenu_atom ); topmenu_height = 0; managing_topmenus = false; topmenu_space = NULL; // TODO grabXServer(); - where exactly put this? topmenu selection claiming down belong must be before { // begin updates blocker block StackingUpdatesBlocker blocker( this ); if( options->topMenuEnabled() && topmenu_selection->claim( false )) setupTopMenuHandling(); // this can call updateStackingOrder() else lostTopMenuSelection(); unsigned int i, nwins; Window root_return, parent_return, *wins; XQueryTree(qt_xdisplay(), root, &root_return, &parent_return, &wins, &nwins); for (i = 0; i < nwins; i++) { XWindowAttributes attr; XGetWindowAttributes(qt_xdisplay(), wins[i], &attr); if (attr.override_redirect ) continue; if( topmenu_space && topmenu_space->winId() == wins[ i ] ) continue; if (attr.map_state != IsUnmapped) { if ( addSystemTrayWin( wins[i] ) ) continue; Client* c = createClient( wins[i], true ); if ( c != NULL && root != qt_xrootwin() ) { // TODO what is this? // TODO may use QWidget:.create XReparentWindow( qt_xdisplay(), c->frameId(), root, 0, 0 ); c->move(0,0); } } } if ( wins ) XFree((void *) wins); // propagate clients, will really happen at the end of the updates blocker block updateStackingOrder( true ); updateClientArea(); raiseElectricBorders(); // NETWM spec says we have to set it to (0,0) if we don't support it NETPoint* viewports = new NETPoint[ number_of_desktops ]; rootInfo->setDesktopViewport( number_of_desktops, *viewports ); delete[] viewports; QRect geom = QApplication::desktop()->geometry(); NETSize desktop_geometry; desktop_geometry.width = geom.width(); desktop_geometry.height = geom.height(); // TODO update also after gaining XRANDR support rootInfo->setDesktopGeometry( -1, desktop_geometry ); } // 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()); if( new_active_client == NULL && !desktops.isEmpty() ) new_active_client = findDesktop( true, currentDesktop()); } if( new_active_client != NULL ) activateClient( new_active_client ); // 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; // TODO ungrabXServer() } Workspace::~Workspace() { blockStackingUpdates( true ); // TODO grabXServer(); // use stacking_order, so that kwin --replace keeps stacking order for( ClientList::ConstIterator it = stacking_order.begin(); it != stacking_order.end(); ++it ) { // only release the window if( !(*it)->isDesktop()) // TODO ? storeFakeSessionInfo( *it ); (*it)->releaseWindow( true ); } delete desktop_widget; delete tab_box; delete popupinfo; delete popup; if ( root == qt_xrootwin() ) XDeleteProperty(qt_xdisplay(), qt_xrootwin(), atoms->kwin_running); writeFakeSessionInfo(); KGlobal::config()->sync(); delete rootInfo; delete supportWindow; delete mgr; delete[] workarea; delete[] screenarea; delete startup; delete initPositioning; delete topmenu_watcher; delete topmenu_selection; delete topmenu_space; XDestroyWindow( qt_xdisplay(), null_focus_window ); // TODO ungrabXServer(); _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; } addClient( c, Allowed ); return c; } void Workspace::addClient( Client* c, allowed_t ) { Group* grp = findGroup( c->window()); if( grp != NULL ) grp->gotLeader( c ); if ( c->isDesktop() ) { desktops.append( c ); if( active_client == NULL && should_get_focus.isEmpty() && c->isOnCurrentDesktop()) requestFocus( c ); // CHECKME? make sure desktop is active after startup if there's no other window active } else { if ( c->wantsTabFocus() && !focus_chain.contains( c )) focus_chain.append( c ); clients.append( c ); } if( !unconstrained_stacking_order.contains( c )) unconstrained_stacking_order.append( c ); if( c->isTopMenu()) addTopMenu( c ); 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())); } if( c->isUtility() || c->isMenu() || c->isToolbar()) updateToolWindows( true ); checkTransients( c->window()); // SELI does this really belong here? updateStackingOrder( true ); // propagate new client } /* Destroys the client \a c */ void Workspace::removeClient( Client* c, allowed_t ) { if (c == active_client && popup) popup->close(); if( c == popup_client ) popup_client = 0; if( c->isDialog()) Notify::raise( Notify::TransDelete ); if( c->isNormalWindow()) Notify::raise( Notify::Delete ); storeFakeSessionInfo( c ); Q_ASSERT( clients.contains( c ) || desktops.contains( c )); clients.remove( c ); desktops.remove( c ); unconstrained_stacking_order.remove( c ); stacking_order.remove( c ); focus_chain.remove( c ); attention_chain.remove( c ); if( c->isTopMenu()) removeTopMenu( c ); Group* group = findGroup( c->window()); if( group != NULL ) group->lostLeader(); if ( c == most_recently_raised ) most_recently_raised = 0; should_get_focus.remove( c ); Q_ASSERT( c != active_client ); if ( c == last_active_client ) last_active_client = 0; updateStackingOrder( true ); if (tab_grab) tab_box->repaint(); updateClientArea(); } void Workspace::updateCurrentTopMenu() { if( !managingTopMenus()) return; // toplevel menubar handling Client* menubar = 0; bool block_desktop_menubar = false; if( active_client ) { // show the new menu bar first... Client* menu_client = active_client; for(;;) { if( menu_client->isFullScreen()) block_desktop_menubar = true; for( ClientList::ConstIterator it = menu_client->transients().begin(); it != menu_client->transients().end(); ++it ) if( (*it)->isTopMenu()) { menubar = *it; break; } if( menubar != NULL || !menu_client->isTransient()) break; if( menu_client->isModal() || menu_client->transientFor() == NULL ) break; // don't use mainwindow's menu if this is modal or group transient menu_client = menu_client->transientFor(); } if( !menubar ) { // try to find any topmenu from the application (#72113) for( ClientList::ConstIterator it = active_client->group()->members().begin(); it != active_client->group()->members().end(); ++it ) if( (*it)->isTopMenu()) { menubar = *it; break; } } } if( !menubar && !block_desktop_menubar && options->desktopTopMenu()) { // Find the menubar of the desktop Client* desktop = findDesktop( true, currentDesktop()); if( desktop != NULL ) { for( ClientList::ConstIterator it = desktop->transients().begin(); it != desktop->transients().end(); ++it ) if( (*it)->isTopMenu()) { menubar = *it; break; } } // TODO to be cleaned app with window grouping // Without qt-copy patch #0009, the topmenu and desktop are not in the same group, // thus the topmenu is not transient for it :-/. if( menubar == NULL ) { for( ClientList::ConstIterator it = topmenus.begin(); it != topmenus.end(); ++it ) if( (*it)->wasOriginallyGroupTransient()) // kdesktop's topmenu has WM_TRANSIENT_FOR { // set pointing to the root window menubar = *it; // to recognize it here break; // Also, with the xroot hack in kdesktop, } // there's no NET::Desktop window to be transient for } } // kdDebug() << "CURRENT TOPMENU:" << menubar << ":" << active_client << endl; if ( menubar ) { if( active_client && !menubar->isOnDesktop( active_client->desktop())) menubar->setDesktop( active_client->desktop()); menubar->hideClient( false ); topmenu_space->hide(); // make it appear like it's been raised manually - it's in the Dock layer anyway, // and not raising it could mess up stacking order of topmenus within one application, // and thus break raising of mainclients in raiseClient() unconstrained_stacking_order.remove( menubar ); unconstrained_stacking_order.append( menubar ); } else if( !block_desktop_menubar ) { // no topmenu active - show the space window, so that there's not empty space topmenu_space->show(); } // ... then hide the other ones. Avoids flickers. for ( ClientList::ConstIterator it = clients.begin(); it != clients.end(); ++it) { if( (*it)->isTopMenu() && (*it) != menubar ) (*it)->hideClient( true ); } } 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?) 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 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( ClientList::ConstIterator it = stacking_order.begin(); it != stacking_order.end(); ++it ) { if( (*it)->isUtility() || (*it)->isMenu() || (*it)->isToolbar()) { bool show = true; if( !(*it)->isTransient()) { if( (*it)->group()->members().count() == 1 ) // has its own group, keep always visible show = true; else if( client != NULL && (*it)->group() == client->group()) show = true; else show = false; } else { if( group != NULL && (*it)->group() == group ) show = true; else if( client != NULL && client->hasTransient( (*it), true )) show = true; else show = false; } if( show ) to_show.append( *it ); else if( also_hide ) to_hide.append( *it ); } } // first show new ones, then hide for( ClientList::ConstIterator it = to_show.fromLast(); it != to_show.end(); --it ) // from topmost // TODO since this is in stacking order, the order of taskbar entries changes :( (*it)->hideClient( false ); if( also_hide ) { for( ClientList::ConstIterator it = to_hide.begin(); it != to_hide.end(); ++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 updateToolWindowsTimer.start( 50, true ); } } 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(qt_xdisplay(), cmap ); installed_colormap = cmap; } } void Workspace::reconfigure() { reconfigureTimer.start(200, true); } void Workspace::slotSettingsChanged(int category) { kdDebug(1212) << "Workspace::slotSettingsChanged()" << endl; if( category == (int) KApplication::SETTINGS_SHORTCUTS ) readShortcuts(); } /*! Reread settings */ KWIN_PROCEDURE( CheckBorderSizesProcedure, cl->checkBorderSizes() ); void Workspace::slotReconfigure() { kdDebug(1212) << "Workspace::slotReconfigure()" << endl; reconfigureTimer.stop(); KGlobal::config()->reparseConfiguration(); unsigned long changed = options->updateSettings(); tab_box->reconfigure(); popupinfo->reconfigure(); readShortcuts(); forEachClient( CheckIgnoreFocusStealingProcedure()); if( mgr->reset( changed )) { // decorations need to be recreated #if 0 // This actually seems to make things worse now QWidget curtain; curtain.setBackgroundMode( NoBackground ); curtain.setGeometry( QApplication::desktop()->geometry() ); curtain.show(); #endif for( ClientList::ConstIterator it = clients.begin(); it != clients.end(); ++it ) { (*it)->updateDecoration( true, true ); } mgr->destroyPreviousPlugin(); } else { forEachClient( CheckBorderSizesProcedure()); } - if (options->electricBorders() == Options::ElectricAlways) - createBorderWindows(); - else - destroyBorderWindows(); + checkElectricBorders(); if( options->topMenuEnabled() && !managingTopMenus()) { if( topmenu_selection->claim( false )) setupTopMenuHandling(); else lostTopMenuSelection(); } else if( !options->topMenuEnabled() && managingTopMenus()) { topmenu_selection->release(); lostTopMenuSelection(); } topmenu_height = 0; // invalidate used menu height if( managingTopMenus()) { updateTopMenuGeometry(); updateCurrentTopMenu(); } } void Workspace::loadDesktopSettings() { KConfig c("kwinrc"); QCString groupname; if (screen_number == 0) groupname = "Desktops"; else groupname.sprintf("Desktops-screen-%d", screen_number); c.setGroup(groupname); int n = c.readNumEntry("Number", 4); number_of_desktops = n; delete workarea; workarea = new QRect[ n + 1 ]; delete screenarea; screenarea = NULL; rootInfo->setNumberOfDesktops( number_of_desktops ); desktop_focus_chain.resize( n ); for(int i = 1; i <= n; i++) { QString s = c.readEntry(QString("Name_%1").arg(i), i18n("Desktop %1").arg(i)); rootInfo->setDesktopName( i, s.utf8().data() ); desktop_focus_chain[i-1] = i; } } void Workspace::saveDesktopSettings() { KConfig c("kwinrc"); QCString groupname; if (screen_number == 0) groupname = "Desktops"; else groupname.sprintf("Desktops-screen-%d", screen_number); c.setGroup(groupname); c.writeEntry("Number", number_of_desktops ); for(int i = 1; i <= number_of_desktops; i++) { QString s = desktopName( i ); QString defaultvalue = i18n("Desktop %1").arg(i); if ( s.isEmpty() ) { s = defaultvalue; rootInfo->setDesktopName( i, s.utf8().data() ); } if (s != defaultvalue) { c.writeEntry( QString("Name_%1").arg(i), s ); } else { QString currentvalue = c.readEntry(QString("Name_%1").arg(i)); if (currentvalue != defaultvalue) c.writeEntry( QString("Name_%1").arg(i), "" ); } } } QStringList Workspace::configModules(bool controlCenter) { QStringList args; args << "kde-kwindecoration.desktop"; if (controlCenter) args << "kde-kwinoptions.desktop"; else if (kapp->authorizeControlModule("kde-kwinoptions.desktop")) args << "kwinactions" << "kwinfocus" << "kwinmoving" << "kwinadvanced"; return args; } void Workspace::configureWM() { KApplication::kdeinitExec( "kcmshell", configModules(false) ); } /*! avoids managing a window with title \a title */ void Workspace::doNotManage( 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.search(title) != -1) { doNotManageList.remove( it ); return TRUE; } } return FALSE; } /*! Refreshes all the client windows */ void Workspace::refresh() { QWidget w; w.setGeometry( QApplication::desktop()->geometry() ); w.show(); w.hide(); QApplication::flushX(); } /*! 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: QValueList obscuring_windows; static QValueList* cached; static unsigned int max_cache_size; }; QValueList* ObscuringWindows::cached = 0; unsigned int ObscuringWindows::max_cache_size = 0; void ObscuringWindows::create( Client* c ) { if( cached == 0 ) cached = new QValueList; Window obs_win; XWindowChanges chngs; int mask = CWSibling | CWStackMode; if( cached->count() > 0 ) { cached->remove( 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( qt_xdisplay(), qt_xrootwin(), c->x(), c->y(), c->width(), c->height(), 0, CopyFromParent, InputOutput, CopyFromParent, CWBackPixmap | CWOverrideRedirect, &a ); } chngs.sibling = c->frameId(); chngs.stack_mode = Below; XConfigureWindow( qt_xdisplay(), obs_win, mask, &chngs ); XMapWindow( qt_xdisplay(), obs_win ); obscuring_windows.append( obs_win ); } ObscuringWindows::~ObscuringWindows() { max_cache_size = QMAX( max_cache_size, obscuring_windows.count() + 4 ) - 1; for( QValueList::ConstIterator it = obscuring_windows.begin(); it != obscuring_windows.end(); ++it ) { XUnmapWindow( qt_xdisplay(), *it ); if( cached->count() < max_cache_size ) cached->prepend( *it ); else XDestroyWindow( qt_xdisplay(), *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 > number_of_desktops ) return false; if( popup ) popup->close(); ++block_focus; // TODO Q_ASSERT( block_stacking_updates == 0 ); // make sure stacking_order is up to date StackingUpdatesBlocker blocker( this ); if (new_desktop != current_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; int old_desktop = current_desktop; current_desktop = new_desktop; // change the desktop (so that Client::virtualDesktopChange() works) for ( ClientList::ConstIterator it = stacking_order.begin(); it != stacking_order.end(); ++it) if ( !(*it)->isOnDesktop( new_desktop ) && (*it) != movingClient ) { if( (*it)->isShown( true ) && (*it)->isOnDesktop( old_desktop )) obs_wins.create( *it ); (*it)->virtualDesktopChange(); } rootInfo->setCurrentDesktop( current_desktop ); // now propagate the change, after hiding, before showing if( movingClient && !movingClient->isOnDesktop( new_desktop )) movingClient->setDesktop( new_desktop ); for ( ClientList::ConstIterator it = stacking_order.fromLast(); it != stacking_order.end(); --it) if ( (*it)->isOnDesktop( new_desktop ) ) (*it)->virtualDesktopChange(); } // restore the focus on this desktop --block_focus; Client* c = 0; if ( options->focusPolicyIsReasonable()) { // Search in focus chain if ( focus_chain.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( ClientList::ConstIterator it = focus_chain.fromLast(); it != focus_chain.end(); --it) { if ( (*it)->isShown( false ) && !(*it)->isOnAllDesktops() && (*it)->isOnCurrentDesktop()) { c = *it; break; } } } if ( !c ) { for( ClientList::ConstIterator it = focus_chain.fromLast(); it != focus_chain.end(); --it) { if ( (*it)->isShown( false ) && (*it)->isOnCurrentDesktop()) { c = *it; 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 != active_client ) setActiveClient( NULL, Allowed ); if ( c ) requestFocus( c ); else focusToNull(); if( !desktops.isEmpty() ) { Window w_tmp; int i_tmp; XGetInputFocus( qt_xdisplay(), &w_tmp, &i_tmp ); if( w_tmp == null_focus_window ) // CHECKME? requestFocus( findDesktop( true, currentDesktop())); } // Update focus chain: // If input: chain = { 1, 2, 3, 4 } and current_desktop = 3, // Output: chain = { 3, 1, 2, 4 }. // kdDebug(1212) << QString("Switching to desktop #%1, at focus_chain index %2\n") // .arg(current_desktop).arg(desktop_focus_chain.find( current_desktop )); for( int i = desktop_focus_chain.find( current_desktop ); i > 0; i-- ) desktop_focus_chain[i] = desktop_focus_chain[i-1]; desktop_focus_chain[0] = current_desktop; // QString s = "desktop_focus_chain[] = { "; // for( uint i = 0; i < desktop_focus_chain.size(); i++ ) // s += QString::number(desktop_focus_chain[i]) + ", "; // kdDebug(1212) << s << "}\n"; return true; } void Workspace::nextDesktop() { int desktop = currentDesktop() + 1; setCurrentDesktop(desktop > numberOfDesktops() ? 1 : desktop); popupinfo->showInfo( desktopName(currentDesktop()) ); } void Workspace::previousDesktop() { int desktop = currentDesktop() - 1; setCurrentDesktop(desktop > 0 ? desktop : numberOfDesktops()); popupinfo->showInfo( desktopName(currentDesktop()) ); } /*! Sets the number of virtual desktops to \a n */ void Workspace::setNumberOfDesktops( int n ) { if ( n == number_of_desktops ) return; int old_number_of_desktops = number_of_desktops; number_of_desktops = n; if( currentDesktop() > numberOfDesktops()) setCurrentDesktop( numberOfDesktops()); // if increasing the number, do the resizing now, // otherwise after the moving of windows to still existing desktops if( old_number_of_desktops < number_of_desktops ) { rootInfo->setNumberOfDesktops( number_of_desktops ); NETPoint* viewports = new NETPoint[ number_of_desktops ]; rootInfo->setDesktopViewport( number_of_desktops, *viewports ); delete[] viewports; updateClientArea( true ); } // if the number of desktops decreased, move all // windows that would be hidden to the last visible desktop if( old_number_of_desktops > number_of_desktops ) { for( ClientList::ConstIterator it = clients.begin(); it != clients.end(); ++it) { if( !(*it)->isOnAllDesktops() && (*it)->desktop() > numberOfDesktops()) sendClientToDesktop( *it, numberOfDesktops(), true ); } } if( old_number_of_desktops > number_of_desktops ) { rootInfo->setNumberOfDesktops( number_of_desktops ); NETPoint* viewports = new NETPoint[ number_of_desktops ]; rootInfo->setDesktopViewport( number_of_desktops, *viewports ); delete[] viewports; updateClientArea( true ); } saveDesktopSettings(); // Resize and reset the desktop focus chain. desktop_focus_chain.resize( n ); for( int i = 0; i < (int)desktop_focus_chain.size(); i++ ) desktop_focus_chain[i] = i+1; } /*! 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 ( c->desktop() == desk ) return; bool was_on_desktop = c->isOnDesktop( desk ) || c->isOnAllDesktops(); c->setDesktop( desk ); desk = c->desktop(); // Client did range checking 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 ); focus_chain.remove( c ); if ( c->wantsTabFocus() ) focus_chain.append( c ); } ClientList transients_stacking_order = ensureStackingOrder( c->transients()); for( ClientList::ConstIterator it = transients_stacking_order.begin(); it != transients_stacking_order.end(); ++it ) sendClientToDesktop( *it, desk, dont_activate ); updateClientArea(); } void Workspace::setDesktopLayout(int o, int x, int y) { layoutOrientation = (Qt::Orientation) o; layoutX = x; layoutY = y; } void Workspace::calcDesktopLayout(int &x, int &y) { x = layoutX; y = layoutY; if ((x == -1) && (y > 0)) x = (numberOfDesktops()+y-1) / y; else if ((y == -1) && (x > 0)) y = (numberOfDesktops()+x-1) / x; if (x == -1) x = 1; if (y == -1) y = 1; } /*! Check whether \a w is a system tray window. If so, add it to the respective datastructures and propagate it to the world. */ bool Workspace::addSystemTrayWin( WId w ) { if ( systemTrayWins.contains( w ) ) return TRUE; NETWinInfo ni( qt_xdisplay(), w, root, NET::WMKDESystemTrayWinFor ); WId trayWinFor = ni.kdeSystemTrayWinFor(); if ( !trayWinFor ) return FALSE; systemTrayWins.append( SystemTrayWindow( w, trayWinFor ) ); XSelectInput( qt_xdisplay(), w, StructureNotifyMask ); XAddToSaveSet( qt_xdisplay(), w ); propagateSystemTrayWins(); return TRUE; } /*! Check whether \a w is a system tray window. If so, remove it from the respective datastructures and propagate this to the world. */ bool Workspace::removeSystemTrayWin( WId w, bool check ) { if ( !systemTrayWins.contains( w ) ) return FALSE; if( check ) { // When getting UnmapNotify, it's not clear if it's the systray // reparenting the window into itself, or if it's the window // going away. This is obviously a flaw in the design, and we were // just lucky it worked for so long. Kicker's systray temporarily // sets _KDE_SYSTEM_TRAY_EMBEDDING property on the window while // embedding it, allowing KWin to figure out. Kicker just mustn't // crash before removing it again ... *shrug* . int num_props; Atom* props = XListProperties( qt_xdisplay(), w, &num_props ); if( props != NULL ) { for( int i = 0; i < num_props; ++i ) if( props[ i ] == atoms->kde_system_tray_embedding ) { XFree( props ); return false; } XFree( props ); } } systemTrayWins.remove( w ); propagateSystemTrayWins(); return TRUE; } /*! Propagates the systemTrayWins to the world */ void Workspace::propagateSystemTrayWins() { Window *cl = new Window[ systemTrayWins.count()]; int i = 0; for ( SystemTrayWindowList::ConstIterator it = systemTrayWins.begin(); it != systemTrayWins.end(); ++it ) { cl[i++] = (*it).win; } rootInfo->setKDESystemTrayWindows( cl, i ); delete [] cl; } 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 ) // found the client break; Window parent, root; Window* children; unsigned int children_count; XQueryTree( qt_xdisplay(), 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( qt_xdisplay(), window_to_kill ); } void Workspace::sendPingToWindow( Window window, Time timestamp ) { rootInfo->sendPing( window, timestamp ); } /*! Takes a screenshot of the current window and puts it in the clipboard. */ void Workspace::slotGrabWindow() { if ( active_client ) { QPixmap snapshot = QPixmap::grabWindow( active_client->frameId() ); //No XShape - no work. if( Shape::available()) { //As the first step, get the mask from XShape. int count, order; XRectangle* rects = XShapeGetRectangles( qt_xdisplay(), active_client->frameId(), ShapeBounding, &count, &order); //The ShapeBounding region is the outermost shape of the window; //ShapeBounding - ShapeClipping is defined to be the border. //Since the border area is part of the window, we use bounding // to limit our work region if (rects) { //Create a QRegion from the rectangles describing the bounding mask. QRegion contents; for (int pos = 0; pos < count; pos++) contents += QRegion(rects[pos].x, rects[pos].y, rects[pos].width, rects[pos].height); XFree(rects); //Create the bounding box. QRegion bbox(0, 0, snapshot.width(), snapshot.height()); //Get the masked away area. QRegion maskedAway = bbox - contents; QMemArray maskedAwayRects = maskedAway.rects(); //Construct a bitmap mask from the rectangles QBitmap mask( snapshot.width(), snapshot.height()); QPainter p(&mask); p.fillRect(0, 0, mask.width(), mask.height(), Qt::color1); for (uint pos = 0; pos < maskedAwayRects.count(); pos++) p.fillRect(maskedAwayRects[pos], Qt::color0); p.end(); snapshot.setMask(mask); } } QClipboard *cb = QApplication::clipboard(); cb->setPixmap( snapshot ); } else slotGrabDesktop(); } /*! Takes a screenshot of the whole desktop and puts it in the clipboard. */ void Workspace::slotGrabDesktop() { QPixmap p = QPixmap::grabWindow( qt_xrootwin() ); QClipboard *cb = QApplication::clipboard(); cb->setPixmap( p ); } /*! Invokes keyboard mouse emulation */ void Workspace::slotMouseEmulation() { if ( mouse_emulation ) { XUngrabKeyboard(qt_xdisplay(), qt_x_time); mouse_emulation = FALSE; return; } if ( XGrabKeyboard(qt_xdisplay(), root, FALSE, GrabModeAsync, GrabModeAsync, qt_x_time) == GrabSuccess ) { mouse_emulation = TRUE; mouse_emulation_state = 0; mouse_emulation_window = 0; } } /*! Returns the child window under the mouse and activates the respective client if necessary. Auxiliary function for the mouse emulation system. */ WId Workspace::getMouseEmulationWindow() { Window root; Window child = qt_xrootwin(); int root_x, root_y, lx, ly; uint state; Window w; Client * c = 0; do { w = child; if (!c) c = findClient( FrameIdMatchPredicate( w )); XQueryPointer( qt_xdisplay(), w, &root, &child, &root_x, &root_y, &lx, &ly, &state ); } while ( child != None && child != w ); if ( c && !c->isActive() ) activateClient( c ); return (WId) w; } /*! Sends a faked mouse event to the specified window. Returns the new button state. */ unsigned int Workspace::sendFakedMouseEvent( QPoint pos, WId w, MouseEmulation type, int button, unsigned int state ) { if ( !w ) return state; QWidget* widget = QWidget::find( w ); if ( (!widget || widget->inherits("QToolButton") ) && !findClient( WindowMatchPredicate( w )) ) { int x, y; Window xw; XTranslateCoordinates( qt_xdisplay(), qt_xrootwin(), w, pos.x(), pos.y(), &x, &y, &xw ); if ( type == EmuMove ) { // motion notify events XMotionEvent e; e.type = MotionNotify; e.window = w; e.root = qt_xrootwin(); e.subwindow = w; e.time = qt_x_time; e.x = x; e.y = y; e.x_root = pos.x(); e.y_root = pos.y(); e.state = state; e.is_hint = NotifyNormal; XSendEvent( qt_xdisplay(), w, TRUE, ButtonMotionMask, (XEvent*)&e ); } else { XButtonEvent e; e.type = type == EmuRelease ? ButtonRelease : ButtonPress; e.window = w; e.root = qt_xrootwin(); e.subwindow = w; e.time = qt_x_time; e.x = x; e.y = y; e.x_root = pos.x(); e.y_root = pos.y(); e.state = state; e.button = button; XSendEvent( qt_xdisplay(), w, TRUE, ButtonPressMask, (XEvent*)&e ); if ( type == EmuPress ) { switch ( button ) { case 2: state |= Button2Mask; break; case 3: state |= Button3Mask; break; default: // 1 state |= Button1Mask; break; } } else { switch ( button ) { case 2: state &= ~Button2Mask; break; case 3: state &= ~Button3Mask; break; default: // 1 state &= ~Button1Mask; break; } } } } return state; } /*! Handles keypress event during mouse emulation */ bool Workspace::keyPressMouseEmulation( XKeyEvent& ev ) { if ( root != qt_xrootwin() ) return FALSE; int kc = XKeycodeToKeysym(qt_xdisplay(), ev.keycode, 0); int km = ev.state & (ControlMask | Mod1Mask | ShiftMask); bool is_control = km & ControlMask; bool is_alt = km & Mod1Mask; bool is_shift = km & ShiftMask; int delta = is_control?1:is_alt?32:8; QPoint pos = QCursor::pos(); switch ( kc ) { case XK_Left: case XK_KP_Left: pos.rx() -= delta; break; case XK_Right: case XK_KP_Right: pos.rx() += delta; break; case XK_Up: case XK_KP_Up: pos.ry() -= delta; break; case XK_Down: case XK_KP_Down: pos.ry() += delta; break; case XK_F1: if ( !mouse_emulation_state ) mouse_emulation_window = getMouseEmulationWindow(); if ( (mouse_emulation_state & Button1Mask) == 0 ) mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuPress, Button1, mouse_emulation_state ); if ( !is_shift ) mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuRelease, Button1, mouse_emulation_state ); break; case XK_F2: if ( !mouse_emulation_state ) mouse_emulation_window = getMouseEmulationWindow(); if ( (mouse_emulation_state & Button2Mask) == 0 ) mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuPress, Button2, mouse_emulation_state ); if ( !is_shift ) mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuRelease, Button2, mouse_emulation_state ); break; case XK_F3: if ( !mouse_emulation_state ) mouse_emulation_window = getMouseEmulationWindow(); if ( (mouse_emulation_state & Button3Mask) == 0 ) mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuPress, Button3, mouse_emulation_state ); if ( !is_shift ) mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuRelease, Button3, mouse_emulation_state ); break; case XK_Return: case XK_space: case XK_KP_Enter: case XK_KP_Space: { if ( !mouse_emulation_state ) { // nothing was pressed, fake a LMB click mouse_emulation_window = getMouseEmulationWindow(); mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuPress, Button1, mouse_emulation_state ); mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuRelease, Button1, mouse_emulation_state ); } else { // release all if ( mouse_emulation_state & Button1Mask ) mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuRelease, Button1, mouse_emulation_state ); if ( mouse_emulation_state & Button2Mask ) mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuRelease, Button2, mouse_emulation_state ); if ( mouse_emulation_state & Button3Mask ) mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuRelease, Button3, mouse_emulation_state ); } } // fall through case XK_Escape: XUngrabKeyboard(qt_xdisplay(), qt_x_time); mouse_emulation = FALSE; return TRUE; default: return FALSE; } QCursor::setPos( pos ); if ( mouse_emulation_state ) mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuMove, 0, mouse_emulation_state ); return TRUE; } /*! Returns the workspace's desktop widget. The desktop widget is sometimes required by clients to draw on it, for example outlines on moving or resizing. */ QWidget* Workspace::desktopWidget() { return desktop_widget; } // Electric Borders //========================================================================// // Electric Border Window management. Electric borders allow a user // to change the virtual desktop by moving the mouse pointer to the // borders. Technically this is done with input only windows. Since // electric borders can be switched on and off, we have these two // functions to create and destroy them. -void Workspace::createBorderWindows() +void Workspace::checkElectricBorders() { - if ( electric_have_borders ) - return; - - electric_have_borders = true; electric_current_border = 0; QRect r = QApplication::desktop()->geometry(); electricTop = r.top(); electricBottom = r.bottom(); electricLeft = r.left(); electricRight = r.right(); + if (options->electricBorders() == Options::ElectricAlways) + createBorderWindows(); + else + destroyBorderWindows(); + } + +void Workspace::createBorderWindows() + { + if ( electric_have_borders ) + return; + + electric_have_borders = true; + + QRect r = QApplication::desktop()->geometry(); XSetWindowAttributes attributes; unsigned long valuemask; attributes.override_redirect = True; attributes.event_mask = (EnterWindowMask | LeaveWindowMask | VisibilityChangeMask); valuemask= (CWOverrideRedirect | CWEventMask | CWCursor ); attributes.cursor = XCreateFontCursor(qt_xdisplay(), XC_sb_up_arrow); electric_top_border = XCreateWindow (qt_xdisplay(), qt_xrootwin(), 0,0, r.width(),1, 0, CopyFromParent, InputOnly, CopyFromParent, valuemask, &attributes); XMapWindow(qt_xdisplay(), electric_top_border); attributes.cursor = XCreateFontCursor(qt_xdisplay(), XC_sb_down_arrow); electric_bottom_border = XCreateWindow (qt_xdisplay(), qt_xrootwin(), 0,r.height()-1, r.width(),1, 0, CopyFromParent, InputOnly, CopyFromParent, valuemask, &attributes); XMapWindow(qt_xdisplay(), electric_bottom_border); attributes.cursor = XCreateFontCursor(qt_xdisplay(), XC_sb_left_arrow); electric_left_border = XCreateWindow (qt_xdisplay(), qt_xrootwin(), 0,0, 1,r.height(), 0, CopyFromParent, InputOnly, CopyFromParent, valuemask, &attributes); XMapWindow(qt_xdisplay(), electric_left_border); attributes.cursor = XCreateFontCursor(qt_xdisplay(), XC_sb_right_arrow); electric_right_border = XCreateWindow (qt_xdisplay(), qt_xrootwin(), r.width()-1,0, 1,r.height(), 0, CopyFromParent, InputOnly, CopyFromParent, valuemask, &attributes); XMapWindow(qt_xdisplay(), electric_right_border); } // Electric Border Window management. Electric borders allow a user // to change the virtual desktop by moving the mouse pointer to the // borders. Technically this is done with input only windows. Since // electric borders can be switched on and off, we have these two // functions to create and destroy them. void Workspace::destroyBorderWindows() { if( !electric_have_borders) return; electric_have_borders = false; if(electric_top_border) XDestroyWindow(qt_xdisplay(),electric_top_border); if(electric_bottom_border) XDestroyWindow(qt_xdisplay(),electric_bottom_border); if(electric_left_border) XDestroyWindow(qt_xdisplay(),electric_left_border); if(electric_right_border) XDestroyWindow(qt_xdisplay(),electric_right_border); electric_top_border = None; electric_bottom_border = None; electric_left_border = None; electric_right_border = None; } void Workspace::clientMoved(const QPoint &pos, Time now) { if (options->electricBorders() == Options::ElectricDisabled) return; if ((pos.x() != electricLeft) && (pos.x() != electricRight) && (pos.y() != electricTop) && (pos.y() != electricBottom)) return; Time treshold_set = options->electricBorderDelay(); // set timeout Time treshold_reset = 250; // reset timeout int distance_reset = 10; // Mouse should not move more than this many pixels int border = 0; if (pos.x() == electricLeft) border = 1; else if (pos.x() == electricRight) border = 2; else if (pos.y() == electricTop) border = 3; else if (pos.y() == electricBottom) border = 4; if ((electric_current_border == border) && (timestampDiff(electric_time_last, now) < treshold_reset) && ((pos-electric_push_point).manhattanLength() < distance_reset)) { electric_time_last = now; if (timestampDiff(electric_time_first, now) > treshold_set) { electric_current_border = 0; QRect r = QApplication::desktop()->geometry(); int offset; int desk_before = currentDesktop(); switch(border) { case 1: slotSwitchDesktopLeft(); if (currentDesktop() != desk_before) { offset = r.width() / 5; QCursor::setPos(r.width() - offset, pos.y()); } break; case 2: slotSwitchDesktopRight(); if (currentDesktop() != desk_before) { offset = r.width() / 5; QCursor::setPos(offset, pos.y()); } break; case 3: slotSwitchDesktopUp(); if (currentDesktop() != desk_before) { offset = r.height() / 5; QCursor::setPos(pos.x(), r.height() - offset); } break; case 4: slotSwitchDesktopDown(); if (currentDesktop() != desk_before) { offset = r.height() / 5; QCursor::setPos(pos.x(), offset); } break; } return; } } else { electric_current_border = border; electric_time_first = now; electric_time_last = now; electric_push_point = pos; } int mouse_warp = 1; // reset the pointer to find out wether the user is really pushing switch( border) { case 1: QCursor::setPos(pos.x()+mouse_warp, pos.y()); break; case 2: QCursor::setPos(pos.x()-mouse_warp, pos.y()); break; case 3: QCursor::setPos(pos.x(), pos.y()+mouse_warp); break; case 4: QCursor::setPos(pos.x(), pos.y()-mouse_warp); break; } } // this function is called when the user entered an electric border // with the mouse. It may switch to another virtual desktop void Workspace::electricBorder(XEvent *e) { Time now = e->xcrossing.time; QPoint p(e->xcrossing.x_root, e->xcrossing.y_root); clientMoved(p, now); } // electric borders (input only windows) have to be always on the // top. For that reason kwm calls this function always after some // windows have been raised. void Workspace::raiseElectricBorders() { if(electric_have_borders) { XRaiseWindow(qt_xdisplay(), electric_top_border); XRaiseWindow(qt_xdisplay(), electric_left_border); XRaiseWindow(qt_xdisplay(), electric_bottom_border); XRaiseWindow(qt_xdisplay(), electric_right_border); } } void Workspace::addTopMenu( Client* c ) { assert( c->isTopMenu()); assert( !topmenus.contains( c )); topmenus.append( c ); if( managingTopMenus()) { int minsize = c->minSize().height(); if( minsize > topMenuHeight()) { topmenu_height = minsize; updateTopMenuGeometry(); } updateTopMenuGeometry( c ); updateCurrentTopMenu(); } // kdDebug() << "NEW TOPMENU:" << c << endl; } void Workspace::removeTopMenu( Client* c ) { // if( c->isTopMenu()) // kdDebug() << "REMOVE TOPMENU:" << c << endl; assert( c->isTopMenu()); assert( topmenus.contains( c )); topmenus.remove( c ); updateCurrentTopMenu(); // TODO reduce topMenuHeight() if possible? } void Workspace::lostTopMenuSelection() { // kdDebug() << "lost TopMenu selection" << endl; // make sure this signal is always set when not owning the selection disconnect( topmenu_watcher, SIGNAL( lostOwner()), this, SLOT( lostTopMenuOwner())); connect( topmenu_watcher, SIGNAL( lostOwner()), this, SLOT( lostTopMenuOwner())); if( !managing_topmenus ) return; connect( topmenu_watcher, SIGNAL( lostOwner()), this, SLOT( lostTopMenuOwner())); disconnect( topmenu_selection, SIGNAL( lostOwnership()), this, SLOT( lostTopMenuSelection())); managing_topmenus = false; delete topmenu_space; topmenu_space = NULL; updateClientArea(); for( ClientList::ConstIterator it = topmenus.begin(); it != topmenus.end(); ++it ) (*it)->checkWorkspacePosition(); } void Workspace::lostTopMenuOwner() { if( !options->topMenuEnabled()) return; // kdDebug() << "TopMenu selection lost owner" << endl; if( !topmenu_selection->claim( false )) { // kdDebug() << "Failed to claim TopMenu selection" << endl; return; } // kdDebug() << "claimed TopMenu selection" << endl; setupTopMenuHandling(); } void Workspace::setupTopMenuHandling() { if( managing_topmenus ) return; connect( topmenu_selection, SIGNAL( lostOwnership()), this, SLOT( lostTopMenuSelection())); disconnect( topmenu_watcher, SIGNAL( lostOwner()), this, SLOT( lostTopMenuOwner())); managing_topmenus = true; topmenu_space = new QWidget; updateTopMenuGeometry(); topmenu_space->show(); updateClientArea(); updateCurrentTopMenu(); } int Workspace::topMenuHeight() const { if( topmenu_height == 0 ) { // simply create a dummy menubar and use its preffered height as the menu height KMenuBar tmpmenu; tmpmenu.insertItem( "dummy" ); topmenu_height = tmpmenu.sizeHint().height(); } return topmenu_height; } KDecoration* Workspace::createDecoration( KDecorationBridge* bridge ) { return mgr->createDecoration( bridge ); } QString Workspace::desktopName( int desk ) const { return QString::fromUtf8( rootInfo->desktopName( desk ) ); } bool Workspace::checkStartupNotification( Window w, KStartupInfoData& data ) { return startup->checkStartup( w, data ) == KStartupInfo::Match; } /*! Puts the focus on a dummy window Just using XSetInputFocus() with None would block keyboard input */ void Workspace::focusToNull() { XSetInputFocus(qt_xdisplay(), null_focus_window, RevertToPointerRoot, qt_x_time ); } void Workspace::helperDialog( const QString& message, const Client* c ) { QStringList args; QString type; if( message == "noborderaltf3" ) { QString shortcut = QString( "%1 (%2)" ).arg( keys->label( "Window Operations Menu" )) .arg( keys->shortcut( "Window Operations Menu" ).seq( 0 ).toString()); args << "--msgbox" << i18n( "You have selected to show a window without its border.\n" "Without the border, you won't be able to enable the border " "again using the mouse. Use the window operations menu instead, " "activated using the %1 keyboard shortcut." ) .arg( shortcut ); type = "altf3warning"; } else if( message == "fullscreenaltf3" ) { QString shortcut = QString( "%1 (%2)" ).arg( keys->label( "Window Operations Menu" )) .arg( keys->shortcut( "Window Operations Menu" ).seq( 0 ).toString()); args << "--msgbox" << i18n( "You have selected to show a window in fullscreen mode.\n" "If the application itself doesn't have an option to turn the fullscreen " "mode off, you won't be able to disable it " "again using the mouse. Use the window operations menu instead, " "activated using the %1 keyboard shortcut." ) .arg( shortcut ); type = "altf3warning"; } else assert( false ); KProcess proc; proc << "kdialog" << args; if( !type.isEmpty()) { KConfig cfg( "kwin_dialogsrc" ); cfg.setGroup( "Notification Messages" ); // this depends on KMessageBox if( !cfg.readBoolEntry( type, true )) // has don't show again checked return; // save launching kdialog proc << "--dontagain" << "kwin_dialogsrc:" + type; } if( c != NULL ) proc << "--embed" << QString::number( c->window()); proc.start( KProcess::DontCare ); } } // namespace #include "workspace.moc" diff --git a/workspace.h b/workspace.h index c1b390d46..b33d94ec9 100644 --- a/workspace.h +++ b/workspace.h @@ -1,684 +1,685 @@ /***************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak You can Freely distribute this program under the GNU General Public License. See the file "COPYING" for the exact licensing terms. ******************************************************************/ #ifndef KWIN_WORKSPACE_H #define KWIN_WORKSPACE_H #include #include #include #include #include "KWinInterface.h" #include "utils.h" #include "kdecoration.h" #include "sm.h" #include class QPopupMenu; class KConfig; class KGlobalAccel; class KStartupInfo; class KStartupInfoData; namespace KWinInternal { class Client; class TabBox; class PopupInfo; class RootInfo; class PluginMgr; class Placement; class SystemTrayWindow { public: SystemTrayWindow() : win(0),winFor(0) {} SystemTrayWindow( WId w ) : win(w),winFor(0) {} SystemTrayWindow( WId w, WId wf ) : win(w),winFor(wf) {} bool operator==( const SystemTrayWindow& other ) { return win == other.win; } WId win; WId winFor; }; typedef QValueList SystemTrayWindowList; class Workspace : public QObject, public KWinInterface, public KDecorationDefines { Q_OBJECT public: Workspace( bool restore = FALSE ); virtual ~Workspace(); static Workspace * self() { return _self; } bool workspaceEvent( XEvent * ); KDecoration* createDecoration( KDecorationBridge* bridge ); bool hasClient( const Client * ); template< typename T > Client* findClient( T predicate ); template< typename T1, typename T2 > void forEachClient( T1 procedure, T2 predicate ); template< typename T > void forEachClient( T procedure ); QRect clientArea( clientAreaOption, const QPoint& p, int desktop ) const; QRect clientArea( clientAreaOption, const Client* c ) const; /** * @internal */ void killWindowId( Window window); void killWindow() { slotKillWindow(); } WId rootWin() const; bool initializing() const; /** * Returns the active client, i.e. the client that has the focus (or None * if no client has the focus) */ Client* activeClient() const; // Client that was activated, but it's not yet really activeClient(), because // we didn't process yet the matching FocusIn event. Used mostly in focus // stealing prevention code. Client* mostRecentlyActivatedClient() const; void activateClient( Client*, bool force = FALSE ); void requestFocus( Client* c, bool force = FALSE ); void takeActivity( Client* c, int flags, bool handled ); // flags are ActivityFlags void handleActivityRaise( Client* c, Time timestamp ); bool allowClientActivation( const Client* c, Time time = -1U, bool focus_in = false, bool session_active = false ); void restoreFocus(); void gotFocusIn( const Client* ); void setShouldGetFocus( Client* ); bool fakeRequestedActivity( Client* c ); void unfakeActivity( Client* c ); void activateNextClient( Client* c ); bool focusChangeEnabled() { return block_focus == 0; } void updateColormap(); /** * Indicates that the client c is being moved around by the user. */ void setClientIsMoving( Client *c ); void place( Client *c, QRect& area ); void placeSmart( Client* c, const QRect& area ); QPoint adjustClientPosition( Client* c, QPoint pos ); void raiseClient( Client* c ); void lowerClient( Client* c ); void raiseClientRequest( Client* c, NET::RequestSource src, Time timestamp ); void lowerClientRequest( Client* c, NET::RequestSource src, Time timestamp ); void restackClientUnderActive( Client* ); void updateClientLayer( Client* c ); void raiseOrLowerClient( Client * ); void reconfigure(); void clientHidden( Client* ); void clientAttentionChanged( Client* c, bool set ); void clientMoved(const QPoint &pos, Time time); /** * Returns the current virtual desktop of this workspace */ int currentDesktop() const; /** * Returns the number of virtual desktops of this workspace */ int numberOfDesktops() const; void setNumberOfDesktops( int n ); QWidget* desktopWidget(); // for TabBox Client* nextFocusChainClient(Client*) const; Client* previousFocusChainClient(Client*) const; Client* nextStaticClient(Client*) const; Client* previousStaticClient(Client*) const; int nextDesktopFocusChain( int iDesktop ) const; int previousDesktopFocusChain( int iDesktop ) const; void closeTabBox(); /** * Returns the list of clients sorted in stacking order, with topmost client * at the last position */ const ClientList& stackingOrder() const; ClientList ensureStackingOrder( const ClientList& clients ) const; Client* topClientOnDesktop( int desktop, bool unconstrained = false ) const; Client* findDesktop( bool topmost, int desktop ) const; void sendClientToDesktop( Client* c, int desktop, bool dont_activate ); // KDE4 remove me - and it's also in the DCOP interface :( void showWindowMenuAt( unsigned long id, int x, int y ); /** * Shows the menu operations menu for the client * and makes it active if it's not already. */ void showWindowMenu( int x, int y, Client* cl ); void showWindowMenu( QPoint pos, Client* cl ); void updateMinimizedOfTransients( Client* ); void updateOnAllDesktopsOfTransients( Client* ); void checkTransients( Window w ); void performWindowOperation( Client* c, WindowOperation op ); void storeSession( KConfig* config, SMSavePhase phase ); SessionInfo* takeSessionInfo( Client* ); // dcop interface void cascadeDesktop(); void unclutterDesktop(); void doNotManage(QString); bool setCurrentDesktop( int new_desktop ); void nextDesktop(); void previousDesktop(); void circulateDesktopApplications(); QString desktopName( int desk ) const; void setDesktopLayout(int o, int x, int y); bool isNotManaged( const QString& title ); // ### setter or getter ? void sendPingToWindow( Window w, Time timestamp ); // called from Client::pingWindow() // only called from Client::destroyClient() or Client::releaseWindow() void removeClient( Client*, allowed_t ); void setActiveClient( Client*, allowed_t ); Group* findGroup( Window leader ) const; void addGroup( Group* group, allowed_t ); void removeGroup( Group* group, allowed_t ); Group* findClientLeaderGroup( const Client* c ) const; bool checkStartupNotification( Window w, KStartupInfoData& data ); void focusToNull(); // SELI public? bool forcedGlobalMouseGrab() const; void sessionSaveStarted(); void sessionSaveDone(); void setWasUserInteraction(); bool sessionSaving() const; bool managingTopMenus() const; int topMenuHeight() const; int packPositionLeft( const Client* cl, int oldx, bool left_edge ) const; int packPositionRight( const Client* cl, int oldx, bool right_edge ) const; int packPositionUp( const Client* cl, int oldy, bool top_edge ) const; int packPositionDown( const Client* cl, int oldy, bool bottom_edge ) const; static QStringList configModules(bool controlCenter); public slots: void refresh(); // keybindings void slotSwitchDesktopNext(); void slotSwitchDesktopPrevious(); void slotSwitchDesktopRight(); void slotSwitchDesktopLeft(); void slotSwitchDesktopUp(); void slotSwitchDesktopDown(); void slotSwitchToDesktop( int ); //void slotSwitchToWindow( int ); void slotWindowToDesktop( int ); //void slotWindowToListPosition( int ); void slotWindowMaximize(); void slotWindowMaximizeVertical(); void slotWindowMaximizeHorizontal(); void slotWindowMinimize(); void slotWindowShade(); void slotWindowRaise(); void slotWindowLower(); void slotWindowRaiseOrLower(); void slotActivateAttentionWindow(); void slotWindowPackLeft(); void slotWindowPackRight(); void slotWindowPackUp(); void slotWindowPackDown(); void slotWindowGrowHorizontal(); void slotWindowGrowVertical(); void slotWindowShrinkHorizontal(); void slotWindowShrinkVertical(); void slotWalkThroughDesktops(); void slotWalkBackThroughDesktops(); void slotWalkThroughDesktopList(); void slotWalkBackThroughDesktopList(); void slotWalkThroughWindows(); void slotWalkBackThroughWindows(); void slotWindowOperations(); void slotWindowClose(); void slotWindowMove(); void slotWindowResize(); void slotWindowAbove(); void slotWindowBelow(); void slotWindowOnAllDesktops(); void slotWindowFullScreen(); void slotWindowNoBorder(); void slotWindowToNextDesktop(); void slotWindowToPreviousDesktop(); void slotMouseEmulation(); void slotSettingsChanged( int category ); void slotReconfigure(); void slotKillWindow(); void slotGrabWindow(); void slotGrabDesktop(); void updateClientArea(); private slots: void desktopPopupAboutToShow(); void clientPopupAboutToShow(); void sendToDesktop( int ); void clientPopupActivated( int ); void configureWM(); void desktopResized(); void slotUpdateToolWindows(); void lostTopMenuSelection(); void lostTopMenuOwner(); protected: bool keyPressMouseEmulation( XKeyEvent& ev ); bool netCheck( XEvent* e ); private: void init(); void initShortcuts(); void readShortcuts(); void initDesktopPopup(); bool startKDEWalkThroughWindows(); bool startWalkThroughDesktops( int mode ); // TabBox::Mode::DesktopMode | DesktopListMode bool startWalkThroughDesktops(); bool startWalkThroughDesktopList(); void KDEWalkThroughWindows( bool forward ); void CDEWalkThroughWindows( bool forward ); void walkThroughDesktops( bool forward ); void KDEOneStepThroughWindows( bool forward ); void oneStepThroughDesktops( bool forward, int mode ); // TabBox::Mode::DesktopMode | DesktopListMode void oneStepThroughDesktops( bool forward ); void oneStepThroughDesktopList( bool forward ); bool establishTabBoxGrab(); void removeTabBoxGrab(); void updateStackingOrder( bool propagate_new_clients = false ); void propagateClients( bool propagate_new_clients ); // called only from updateStackingOrder ClientList constrainedStackingOrder(); void raiseClientWithinApplication( Client* c ); void lowerClientWithinApplication( Client* c ); bool allowFullClientRaising( const Client* c, Time timestamp ); bool keepTransientAbove( const Client* mainwindow, const Client* transient ); void blockStackingUpdates( bool block ); void updateCurrentTopMenu(); void addTopMenu( Client* c ); void removeTopMenu( Client* c ); void setupTopMenuHandling(); void updateTopMenuGeometry( Client* c = NULL ); void updateToolWindows( bool also_hide ); // this is the right way to create a new client Client* createClient( Window w, bool is_mapped ); void addClient( Client* c, allowed_t ); Window findSpecialEventWindow( XEvent* e ); void randomPlacement(Client* c); void smartPlacement(Client* c); void cascadePlacement(Client* c, bool re_init = false); bool addSystemTrayWin( WId w ); bool removeSystemTrayWin( WId w, bool check ); void propagateSystemTrayWins(); SystemTrayWindow findSystemTrayWin( WId w ); // desktop names and number of desktops void loadDesktopSettings(); void saveDesktopSettings(); // mouse emulation WId getMouseEmulationWindow(); enum MouseEmulation { EmuPress, EmuRelease, EmuMove }; unsigned int sendFakedMouseEvent( QPoint pos, WId win, MouseEmulation type, int button, unsigned int state ); // returns the new state void tabBoxKeyPress( const KKeyNative& keyX ); void tabBoxKeyRelease( const XKeyEvent& ev ); // electric borders + void checkElectricBorders(); void createBorderWindows(); void destroyBorderWindows(); void electricBorder(XEvent * e); void raiseElectricBorders(); // ------------------ void helperDialog( const QString& message, const Client* c ); void calcDesktopLayout(int &x, int &y); QPopupMenu* clientPopup(); void updateClientArea( bool force ); SystemTrayWindowList systemTrayWins; int current_desktop; int number_of_desktops; QMemArray desktop_focus_chain; Client* popup_client; void loadSessionInfo(); QWidget* desktop_widget; QPtrList session; QPtrList fakeSession; void loadFakeSessionInfo(); void storeFakeSessionInfo( Client* c ); void writeFakeSessionInfo(); static const char* windowTypeToTxt( NET::WindowType type ); static NET::WindowType txtToWindowType( const char* txt ); static bool sessionInfoWindowTypeMatch( Client* c, SessionInfo* info ); Client* active_client; Client* last_active_client; Client* most_recently_raised; // used _only_ by raiseOrLowerClient() Client* movingClient; Time last_restack; ClientList clients; ClientList desktops; ClientList unconstrained_stacking_order; ClientList stacking_order; ClientList focus_chain; ClientList should_get_focus; // last is most recent ClientList attention_chain; GroupList groups; bool was_user_interaction; bool session_saving; int session_active_client; int session_desktop; bool control_grab; bool tab_grab; //KKeyNative walkThroughDesktopsKeycode, walkBackThroughDesktopsKeycode; //KKeyNative walkThroughDesktopListKeycode, walkBackThroughDesktopListKeycode; //KKeyNative walkThroughWindowsKeycode, walkBackThroughWindowsKeycode; KShortcut cutWalkThroughDesktops, cutWalkThroughDesktopsReverse; KShortcut cutWalkThroughDesktopList, cutWalkThroughDesktopListReverse; KShortcut cutWalkThroughWindows, cutWalkThroughWindowsReverse; bool mouse_emulation; unsigned int mouse_emulation_state; WId mouse_emulation_window; int block_focus; TabBox* tab_box; PopupInfo* popupinfo; QPopupMenu *popup; QPopupMenu *advanced_popup; QPopupMenu *desk_popup; int desk_popup_index; KGlobalAccel *keys; WId root; PluginMgr *mgr; RootInfo *rootInfo; QWidget* supportWindow; // swallowing QStringList doNotManageList; // colormap handling Colormap default_colormap; Colormap installed_colormap; // Timer to collect requests for 'reconfigure' QTimer reconfigureTimer; QTimer updateToolWindowsTimer; static Workspace *_self; bool workspaceInit; KStartupInfo* startup; bool electric_have_borders; int electric_current_border; WId electric_top_border; WId electric_bottom_border; WId electric_left_border; WId electric_right_border; int electricLeft; int electricRight; int electricTop; int electricBottom; Time electric_time_first; Time electric_time_last; QPoint electric_push_point; Qt::Orientation layoutOrientation; int layoutX; int layoutY; Placement *initPositioning; QRect* workarea; // array of workareas for virtual desktops QRect** screenarea; // array of workareas per xinerama screen for all virtual desktops bool managing_topmenus; KSelectionOwner* topmenu_selection; KSelectionWatcher* topmenu_watcher; ClientList topmenus; // doesn't own them mutable int topmenu_height; QWidget* topmenu_space; int set_active_client_recursion; int block_stacking_updates; // when >0, stacking updates are temporarily disabled bool blocked_propagating_new_clients; // propagate also new clients after enabling stacking updates? Window null_focus_window; bool forced_global_mouse_grab; friend class StackingUpdatesBlocker; }; // helper for Workspace::blockStackingUpdates() being called in pairs (true/false) class StackingUpdatesBlocker { public: StackingUpdatesBlocker( Workspace* w ) : ws( w ) { ws->blockStackingUpdates( true ); } ~StackingUpdatesBlocker() { ws->blockStackingUpdates( false ); } private: Workspace* ws; }; // NET WM Protocol handler class class RootInfo : public NETRootInfo3 { private: typedef KWinInternal::Client Client; // because of NET::Client public: RootInfo( Workspace* ws, Display *dpy, Window w, const char *name, unsigned long pr[], int pr_num, int scr= -1); protected: virtual void changeNumberOfDesktops(int n); virtual void changeCurrentDesktop(int d); // virtual void changeActiveWindow(Window w); the extended version is used virtual void changeActiveWindow(Window w,NET::RequestSource src, Time timestamp, Window active_window); virtual void closeWindow(Window w); virtual void moveResize(Window w, int x_root, int y_root, unsigned long direction); virtual void moveResizeWindow(Window w, int flags, int x, int y, int width, int height ); virtual void gotPing(Window w, Time timestamp); virtual void restackWindow(Window w, RequestSource source, Window above, int detail, Time timestamp); private: Workspace* workspace; }; inline WId Workspace::rootWin() const { return root; } inline bool Workspace::initializing() const { return workspaceInit; } inline Client* Workspace::activeClient() const { return active_client; } inline Client* Workspace::mostRecentlyActivatedClient() const { return should_get_focus.count() > 0 ? should_get_focus.last() : active_client; } inline int Workspace::currentDesktop() const { return current_desktop; } inline int Workspace::numberOfDesktops() const { return number_of_desktops; } inline void Workspace::addGroup( Group* group, allowed_t ) { groups.append( group ); } inline void Workspace::removeGroup( Group* group, allowed_t ) { groups.remove( group ); } inline const ClientList& Workspace::stackingOrder() const { // TODO Q_ASSERT( block_stacking_updates == 0 ); return stacking_order; } inline void Workspace::showWindowMenu(QPoint pos, Client* cl) { showWindowMenu(pos.x(), pos.y(), cl); } inline void Workspace::setWasUserInteraction() { was_user_interaction = true; } inline bool Workspace::managingTopMenus() const { return managing_topmenus; } inline void Workspace::sessionSaveStarted() { session_saving = true; } inline void Workspace::sessionSaveDone() { session_saving = false; } inline bool Workspace::sessionSaving() const { return session_saving; } inline bool Workspace::forcedGlobalMouseGrab() const { return forced_global_mouse_grab; } template< typename T > inline Client* Workspace::findClient( T predicate ) { if( Client* ret = findClientInList( clients, predicate )) return ret; if( Client* ret = findClientInList( desktops, predicate )) return ret; return NULL; } template< typename T1, typename T2 > inline void Workspace::forEachClient( T1 procedure, T2 predicate ) { for ( ClientList::ConstIterator it = clients.begin(); it != clients.end(); ++it) if ( predicate( const_cast< const Client* >( *it))) procedure( *it ); for ( ClientList::ConstIterator it = desktops.begin(); it != desktops.end(); ++it) if ( predicate( const_cast< const Client* >( *it))) procedure( *it ); } template< typename T > inline void Workspace::forEachClient( T procedure ) { return forEachClient( procedure, TruePredicate()); } KWIN_COMPARE_PREDICATE( ClientMatchPredicate, const Client*, cl == value ); inline bool Workspace::hasClient( const Client* c ) { return findClient( ClientMatchPredicate( c )); } } // namespace #endif