diff --git a/composite.cpp b/composite.cpp index 1bfa66b0d..6a10d3416 100644 --- a/composite.cpp +++ b/composite.cpp @@ -1,891 +1,916 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ /* Code related to compositing (redirecting windows to pixmaps and tracking window damage). Docs: XComposite (the protocol, but the function calls map to it): http://gitweb.freedesktop.org/?p=xorg/proto/compositeproto.git;a=blob_plain;hb=HEAD;f=compositeproto.txt XDamage (again the protocol): http://gitweb.freedesktop.org/?p=xorg/proto/damageproto.git;a=blob_plain;hb=HEAD;f=damageproto.txt Paper including basics on compositing, XGL vs AIGLX, XRender vs OpenGL, etc.: http://www.vis.uni-stuttgart.de/~hopf/pub/LinuxTag2007_compiz_NextGenerationDesktop_Paper.pdf Composite HOWTO from Fredrik: http://ktown.kde.org/~fredrik/composite_howto.html */ #include #include "utils.h" #include #include "workspace.h" #include "client.h" #include "unmanaged.h" #include "deleted.h" #include "effects.h" #include "overlaywindow.h" #include "scene.h" #include "scene_xrender.h" #include "scene_opengl.h" #include "shadow.h" #include "compositingprefs.h" #include "notifications.h" #include #include #include #include #include #include #include #include #include #include namespace KWin { extern int currentRefreshRate(); //**************************************** // Workspace //**************************************** void Workspace::setupCompositing() { if (scene != NULL) return; if (compositingSuspended) { kDebug(1212) << "Compositing is suspended"; return; } else if (!CompositingPrefs::compositingPossible()) { kError(1212) << "Compositing is not possible"; return; } if (!options->compositingInitialized) options->reloadCompositingSettings(true); char selection_name[ 100 ]; sprintf(selection_name, "_NET_WM_CM_S%d", DefaultScreen(display())); cm_selection = new KSelectionOwner(selection_name); connect(cm_selection, SIGNAL(lostOwnership()), SLOT(lostCMSelection())); cm_selection->claim(true); // force claiming switch(options->compositingMode) { case OpenGLCompositing: { kDebug(1212) << "Initializing OpenGL compositing"; // Some broken drivers crash on glXQuery() so to prevent constant KWin crashes: KSharedConfigPtr unsafeConfigPtr(KSharedConfig::openConfig("kwinrc")); KConfigGroup unsafeConfig(unsafeConfigPtr, "Compositing"); if (unsafeConfig.readEntry("OpenGLIsUnsafe", false)) kWarning(1212) << "KWin has detected that your OpenGL library is unsafe to use"; else { unsafeConfig.writeEntry("OpenGLIsUnsafe", true); unsafeConfig.sync(); #ifndef KWIN_HAVE_OPENGLES if (!CompositingPrefs::hasGlx()) { unsafeConfig.writeEntry("OpenGLIsUnsafe", false); unsafeConfig.sync(); kDebug(1212) << "No glx extensions available"; break; } #endif scene = new SceneOpenGL(this); // TODO: Add 30 second delay to protect against screen freezes as well unsafeConfig.writeEntry("OpenGLIsUnsafe", false); unsafeConfig.sync(); if (!scene->initFailed()) break; // --> delete scene; scene = NULL; } // Do not Fall back to XRender - it causes problems when selfcheck fails during startup, but works later on break; } #ifdef KWIN_HAVE_XRENDER_COMPOSITING case XRenderCompositing: kDebug(1212) << "Initializing XRender compositing"; scene = new SceneXrender(this); break; #endif default: kDebug(1212) << "No compositing enabled"; delete cm_selection; return; } if (scene == NULL || scene->initFailed()) { kError(1212) << "Failed to initialize compositing, compositing disabled"; kError(1212) << "Consult http://techbase.kde.org/Projects/KWin/4.0-release-notes#Setting_up"; delete scene; scene = NULL; delete cm_selection; return; } xrrRefreshRate = KWin::currentRefreshRate(); fpsInterval = (options->maxFpsInterval << 10); if (scene->waitSyncAvailable()) { // if we do vsync, set the fps to the next multiple of the vblank rate vBlankInterval = (1000 << 10) / xrrRefreshRate; fpsInterval -= (fpsInterval % vBlankInterval); fpsInterval = qMax(fpsInterval, vBlankInterval); } else vBlankInterval = 1 << 10; // no sync - DO NOT set "0", would cause div-by-zero segfaults. vBlankPadding = 3; // vblank rounding errors... :-( nextPaintReference.start(); checkCompositeTimer(); XCompositeRedirectSubwindows(display(), rootWindow(), CompositeRedirectManual); new EffectsHandlerImpl(scene->compositingType()); // sets also the 'effects' pointer addRepaintFull(); foreach (Client * c, clients) c->setupCompositing(); foreach (Client * c, desktops) c->setupCompositing(); foreach (Unmanaged * c, unmanaged) c->setupCompositing(); discardPopup(); // force re-creation of the Alt+F3 popup (opacity option) } void Workspace::finishCompositing() { if (scene == NULL) return; m_finishingCompositing = true; delete cm_selection; foreach (Client * c, clients) scene->windowClosed(c, NULL); foreach (Client * c, desktops) scene->windowClosed(c, NULL); foreach (Unmanaged * c, unmanaged) scene->windowClosed(c, NULL); foreach (Deleted * c, deleted) scene->windowDeleted(c); foreach (Client * c, clients) c->finishCompositing(); foreach (Client * c, desktops) c->finishCompositing(); foreach (Unmanaged * c, unmanaged) c->finishCompositing(); foreach (Deleted * c, deleted) c->finishCompositing(); XCompositeUnredirectSubwindows(display(), rootWindow(), CompositeRedirectManual); delete effects; effects = NULL; delete scene; scene = NULL; compositeTimer.stop(); mousePollingTimer.stop(); repaints_region = QRegion(); for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) { // forward all opacity values to the frame in case there'll be other CM running if ((*it)->opacity() != 1.0) { NETWinInfo2 i(display(), (*it)->frameId(), rootWindow(), 0); i.setOpacity(static_cast< unsigned long >((*it)->opacity() * 0xffffffff)); } } discardPopup(); // force re-creation of the Alt+F3 popup (opacity option) // discard all Deleted windows (#152914) while (!deleted.isEmpty()) deleted.first()->discard(Allowed); m_finishingCompositing = false; } // OpenGL self-check failed, fallback to XRender void Workspace::fallbackToXRenderCompositing() { finishCompositing(); KConfigGroup config(KSharedConfig::openConfig("kwinrc"), "Compositing"); config.writeEntry("Backend", "XRender"); config.writeEntry("GraphicsSystem", "native"); config.sync(); if (Extensions::nonNativePixmaps()) { // must restart to change the graphicssystem restartKWin("automatic graphicssystem change for XRender backend"); return; } else { options->compositingMode = XRenderCompositing; setupCompositing(); } } void Workspace::lostCMSelection() { kDebug(1212) << "Lost compositing manager selection"; finishCompositing(); } // for the shortcut void Workspace::slotToggleCompositing() { suspendCompositing(!compositingSuspended); } // for the dbus call void Workspace::toggleCompositing() { slotToggleCompositing(); if (compositingSuspended) { // when disabled show a shortcut how the user can get back compositing QString shortcut, message; if (KAction* action = qobject_cast(keys->action("Suspend Compositing"))) shortcut = action->globalShortcut().primary().toString(QKeySequence::NativeText); if (!shortcut.isEmpty()) { // display notification only if there is the shortcut message = i18n("Desktop effects have been suspended by another application.
" "You can resume using the '%1' shortcut.", shortcut); Notify::raise(Notify::CompositingSuspendedDbus, message); } } } QStringList Workspace::activeEffects() const { if (effects) return static_cast< EffectsHandlerImpl* >(effects)->activeEffects(); return QStringList(); } void Workspace::updateCompositeBlocking(Client *c) { if (c) { // if c == 0 we just check if we can resume if (c->isBlockingCompositing()) { if (!compositingBlocked) // do NOT attempt to call suspendCompositing(true); from within the eventchain! QMetaObject::invokeMethod(this, "slotToggleCompositing", Qt::QueuedConnection); compositingBlocked = true; } } else if (compositingBlocked) { // lost a client and we're blocked - can we resume? // NOTICE do NOT check for "compositingSuspended" or "!compositing()" // only "resume" if it was really disabled for a block bool resume = true; for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) { if ((*it)->isBlockingCompositing()) { resume = false; break; } } if (resume) { // do NOT attempt to call suspendCompositing(false); from within the eventchain! compositingBlocked = false; if (compositingSuspended) QMetaObject::invokeMethod(this, "slotToggleCompositing", Qt::QueuedConnection); } } } void Workspace::suspendCompositing() { suspendCompositing(true); } void Workspace::suspendCompositing(bool suspend) { compositingSuspended = suspend; finishCompositing(); setupCompositing(); // will do nothing if suspended // notify decorations that composition state has changed if (hasDecorationPlugin()) { KDecorationFactory* factory = mgr->factory(); factory->reset(SettingCompositing); } emit compositingToggled(!compositingSuspended); } void Workspace::resetCompositing() { if (compositing()) { finishCompositing(); QTimer::singleShot(0, this, SLOT(setupCompositing())); } } void Workspace::addRepaint(int x, int y, int w, int h) { if (!compositing()) return; repaints_region += QRegion(x, y, w, h); checkCompositeTimer(); } void Workspace::addRepaint(const QRect& r) { if (!compositing()) return; repaints_region += r; checkCompositeTimer(); } void Workspace::addRepaint(const QRegion& r) { if (!compositing()) return; repaints_region += r; checkCompositeTimer(); } void Workspace::addRepaintFull() { if (!compositing()) return; repaints_region = QRegion(0, 0, displayWidth(), displayHeight()); checkCompositeTimer(); } void Workspace::timerEvent(QTimerEvent *te) { if (te->timerId() == compositeTimer.timerId()) { compositeTimer.stop(); performCompositing(); } else QObject::timerEvent(te); } void Workspace::performCompositing() { if (((repaints_region.isEmpty() && !windowRepaintsPending()) // no damage || !scene->overlayWindow()->isVisible())) { // nothing is visible anyway vBlankPadding += 3; scene->idle(); // Note: It would seem here we should undo suspended unredirect, but when scenes need // it for some reason, e.g. transformations or translucency, the next pass that does not // need this anymore and paints normally will also reset the suspended unredirect. // Otherwise the window would not be painted normally anyway. return; } // create a list of all windows in the stacking order ToplevelList windows = xStackingOrder(); foreach (EffectWindow *c, static_cast< EffectsHandlerImpl* >(effects)->elevatedWindows()) { Toplevel* t = static_cast< EffectWindowImpl* >(c)->window(); windows.removeAll(t); windows.append(t); } // skip windows that are not yet ready for being painted // TODO ? // this cannot be used so carelessly - needs protections against broken clients, the window // should not get focus before it's displayed, handle unredirected windows properly and so on. foreach (Toplevel *t, windows) if (!t->readyForPainting()) windows.removeAll(t); QRegion repaints = repaints_region; // clear all repaints, so that post-pass can add repaints for the next repaint repaints_region = QRegion(); if (scene->waitSyncAvailable()) { // vsync: paint the scene, than rebase the timer and use the duration for next timeout estimation scene->paint(repaints, windows); nextPaintReference.start(); } else { // no vsyc -> inversion: reset the timer, then paint the scene, this way we can provide a constant framerate nextPaintReference.start(); scene->paint(repaints, windows); } // reset the roundin error corrective... :-( vBlankPadding = 3; // Trigger at least one more pass even if there would be nothing to paint, so that scene->idle() // is called the next time. If there would be nothing pending, it will not restart the timer and // checkCompositeTime() would restart it again somewhen later, called from functions that // would again add something pending. checkCompositeTimer(); } void Workspace::performMousePoll() { checkCursorPos(); } bool Workspace::windowRepaintsPending() const { foreach (Toplevel * c, clients) if (!c->repaints().isEmpty()) return true; foreach (Toplevel * c, desktops) if (!c->repaints().isEmpty()) return true; foreach (Toplevel * c, unmanaged) if (!c->repaints().isEmpty()) return true; foreach (Toplevel * c, deleted) if (!c->repaints().isEmpty()) return true; return false; } void Workspace::setCompositeTimer() { if (!compositing()) // should not really happen, but there may be e.g. some damage events still pending return; // interval - "time since last paint completion" - "time we need to paint" uint passed = nextPaintReference.elapsed() << 10; uint delay = fpsInterval; if (scene->waitSyncAvailable()) { if (passed > fpsInterval) { delay = vBlankInterval; passed %= vBlankInterval; } delay -= ((passed + ((scene->estimatedRenderTime() + vBlankPadding) << 10)) % vBlankInterval); } else delay = qBound(0, int(delay - passed), 250 << 10); compositeTimer.start(delay >> 10, this); } void Workspace::startMousePolling() { mousePollingTimer.start(20); // 50Hz. TODO: How often do we really need to poll? } void Workspace::stopMousePolling() { mousePollingTimer.stop(); } bool Workspace::compositingActive() { return !m_finishingCompositing && compositing(); } // force is needed when the list of windows changes (e.g. a window goes away) void Workspace::checkUnredirect(bool force) { if (!compositing() || scene->overlayWindow()->window() == None || !options->unredirectFullscreen) return; if (force) forceUnredirectCheck = true; if (!unredirectTimer.isActive()) unredirectTimer.start(0); } void Workspace::delayedCheckUnredirect() { if (!compositing() || scene->overlayWindow()->window() == None || !options->unredirectFullscreen) return; ToplevelList list; bool changed = forceUnredirectCheck; foreach (Client * c, clients) list.append(c); foreach (Unmanaged * c, unmanaged) list.append(c); foreach (Toplevel * c, list) { if (c->updateUnredirectedState()) changed = true; } // no desktops, no Deleted ones if (!changed) return; forceUnredirectCheck = false; // Cut out parts from the overlay window where unredirected windows are, // so that they are actually visible. QRegion reg(0, 0, displayWidth(), displayHeight()); foreach (Toplevel * c, list) { if (c->unredirected()) reg -= c->geometry(); } scene->overlayWindow()->setShape(reg); } //**************************************** // Toplevel //**************************************** void Toplevel::setupCompositing() { if (!compositing()) return; damageRatio = 0.0; if (damage_handle != None) return; damage_handle = XDamageCreate(display(), frameId(), XDamageReportRawRectangles); damage_region = QRegion(0, 0, width(), height()); effect_window = new EffectWindowImpl(this); unredirect = false; workspace()->checkUnredirect(true); scene->windowAdded(this); } void Toplevel::finishCompositing() { damageRatio = 0.0; if (damage_handle == None) return; workspace()->checkUnredirect(true); if (effect_window->window() == this) { // otherwise it's already passed to Deleted, don't free data discardWindowPixmap(); delete effect_window; } XDamageDestroy(display(), damage_handle); damage_handle = None; damage_region = QRegion(); repaints_region = QRegion(); effect_window = NULL; } void Toplevel::discardWindowPixmap() { damageRatio = 0.0; addDamageFull(); if (window_pix == None) return; XFreePixmap(display(), window_pix); window_pix = None; if (effectWindow() != NULL && effectWindow()->sceneWindow() != NULL) effectWindow()->sceneWindow()->pixmapDiscarded(); } Pixmap Toplevel::createWindowPixmap() { assert(compositing()); if (unredirected()) return None; damageRatio = 0.0; grabXServer(); KXErrorHandler err; Pixmap pix = XCompositeNameWindowPixmap(display(), frameId()); // check that the received pixmap is valid and actually matches what we // know about the window (i.e. size) XWindowAttributes attrs; if (!XGetWindowAttributes(display(), frameId(), &attrs) || err.error(false) || attrs.width != width() || attrs.height != height() || attrs.map_state != IsViewable) { kDebug(1212) << "Creating window pixmap failed: " << this; XFreePixmap(display(), pix); pix = None; } ungrabXServer(); return pix; } // We must specify that the two events are a union so the compiler doesn't // complain about strict aliasing rules. typedef union { XEvent e; XDamageNotifyEvent de; } EventUnion; static QVector damageRects; void Toplevel::damageNotifyEvent(XDamageNotifyEvent* e) { if (damageRatio == 1.0) { // we know that we're completely damaged, no need to tell us again while (XPending(display())) { // drop events EventUnion e2; if (XPeekEvent(display(), &e2.e) && e2.e.type == Extensions::damageNotifyEvent() && e2.e.xany.window == frameId()) { XNextEvent(display(), &e2.e); continue; } break; } return; } const float area = rect().width()*rect().height(); damageRects.reserve(16); damageRects.clear(); damageRects << QRect(e->area.x, e->area.y, e->area.width, e->area.height); // we can not easily say anything about the overall ratio since the new rects may intersect the present float newDamageRatio = damageRects.last().width()*damageRects.last().height() / area; // compress while (XPending(display())) { EventUnion e2; if (XPeekEvent(display(), &e2.e) && e2.e.type == Extensions::damageNotifyEvent() && e2.e.xany.window == frameId()) { XNextEvent(display(), &e2.e); if (damageRatio >= 0.8 || newDamageRatio > 0.8 || damageRects.count() > 15) { // If there are too many damage events in the queue, just discard them // and damage the whole window. Otherwise the X server can just overload // us with a flood of damage events. Should be probably optimized // in the X server, as this is rather lame. newDamageRatio = 1.0; damageRects.clear(); continue; } damageRects << QRect(e2.de.area.x, e2.de.area.y, e2.de.area.width, e2.de.area.height); newDamageRatio += damageRects.last().width()*damageRects.last().height() / area; continue; } break; } if ((damageRects.count() == 1 && damageRects.last() == rect()) || (damageRects.isEmpty() && newDamageRatio == 1.0)) { addDamageFull(); } else { foreach (const QRect &r, damageRects) addDamage(r); } } void Client::damageNotifyEvent(XDamageNotifyEvent* e) { #ifdef HAVE_XSYNC if (syncRequest.isPending && isResize()) return; if (!ready_for_painting) { // avoid "setReadyForPainting()" function calling overhead if (syncRequest.counter == None) // cannot detect complete redraw, consider done now setReadyForPainting(); #else setReadyForPainting(); #endif } Toplevel::damageNotifyEvent(e); } void Toplevel::addDamage(const QRect& r) { addDamage(r.x(), r.y(), r.width(), r.height()); } void Toplevel::addDamage(int x, int y, int w, int h) { if (!compositing()) return; QRect r(x, y, w, h); // resizing the decoration may lag behind a bit and when shrinking there // may be a damage event coming with size larger than the current window size r &= rect(); if (r.isEmpty()) return; damage_region += r; int damageArea = 0; foreach (const QRect &r2, damage_region.rects()) damageArea += r2.width()*r2.height(); damageRatio = float(damageArea) / float(rect().width()*rect().height()); repaints_region += r; emit damaged(this, r); // discard lanczos texture if (effect_window) { QVariant cachedTextureVariant = effect_window->data(LanczosCacheRole); if (cachedTextureVariant.isValid()) { GLTexture *cachedTexture = static_cast< GLTexture*>(cachedTextureVariant.value()); delete cachedTexture; cachedTexture = 0; effect_window->setData(LanczosCacheRole, QVariant()); } } workspace()->checkCompositeTimer(); } void Toplevel::addDamageFull() { if (!compositing()) return; damage_region = rect(); repaints_region = rect(); damageRatio = 1.0; emit damaged(this, rect()); // discard lanczos texture if (effect_window) { QVariant cachedTextureVariant = effect_window->data(LanczosCacheRole); if (cachedTextureVariant.isValid()) { GLTexture *cachedTexture = static_cast< GLTexture*>(cachedTextureVariant.value()); delete cachedTexture; cachedTexture = 0; effect_window->setData(LanczosCacheRole, QVariant()); } } workspace()->checkCompositeTimer(); } void Toplevel::resetDamage(const QRect& r) { damage_region -= r; int damageArea = 0; foreach (const QRect &r2, damage_region.rects()) damageArea += r2.width()*r2.height(); damageRatio = float(damageArea) / float(rect().width()*rect().height()); } void Toplevel::addRepaint(const QRect& r) { - addRepaint(r.x(), r.y(), r.width(), r.height()); + if (!compositing()) { + return; + } + repaints_region += r; + workspace()->checkCompositeTimer(); } void Toplevel::addRepaint(int x, int y, int w, int h) { - if (!compositing()) - return; QRect r(x, y, w, h); - r &= rect(); + addRepaint(r); +} + +void Toplevel::addRepaint(const QRegion& r) +{ + if (!compositing()) { + return; + } repaints_region += r; workspace()->checkCompositeTimer(); } -void Toplevel::addRepaint(const QRegion& r) +void Toplevel::addLayerRepaint(const QRect& r) +{ + if (!compositing()) { + return; + } + layer_repaints_region += r; + workspace()->checkCompositeTimer(); +} + +void Toplevel::addLayerRepaint(int x, int y, int w, int h) +{ + QRect r(x, y, w, h); + addLayerRepaint(r); +} + +void Toplevel::addLayerRepaint(const QRegion& r) { if (!compositing()) return; - repaints_region += r; + layer_repaints_region += r; workspace()->checkCompositeTimer(); } void Toplevel::addRepaintFull() { repaints_region = decorationRect(); workspace()->checkCompositeTimer(); } -void Toplevel::resetRepaints(const QRect& r) +void Toplevel::resetRepaints() { - repaints_region -= r; + repaints_region = QRegion(); + layer_repaints_region = QRegion(); } void Toplevel::addWorkspaceRepaint(int x, int y, int w, int h) { addWorkspaceRepaint(QRect(x, y, w, h)); } void Toplevel::addWorkspaceRepaint(const QRect& r2) { if (!compositing()) return; workspace()->addRepaint(r2); } bool Toplevel::updateUnredirectedState() { assert(compositing()); bool should = shouldUnredirect() && !unredirectSuspend && !shape() && !hasAlpha() && opacity() == 1.0 && !static_cast(effects)->activeFullScreenEffect(); if (should && !unredirect) { unredirect = true; kDebug(1212) << "Unredirecting:" << this; XCompositeUnredirectWindow(display(), frameId(), CompositeRedirectManual); return true; } else if (!should && unredirect) { unredirect = false; kDebug(1212) << "Redirecting:" << this; XCompositeRedirectWindow(display(), frameId(), CompositeRedirectManual); discardWindowPixmap(); return true; } return false; } void Toplevel::suspendUnredirect(bool suspend) { if (unredirectSuspend == suspend) return; unredirectSuspend = suspend; workspace()->checkUnredirect(); } //**************************************** // Client //**************************************** void Client::setupCompositing() { Toplevel::setupCompositing(); updateVisibility(); // for internalKeep() updateDecoration(true, true); move(calculateGravitation(true)); // we just polluted the gravity because the window likely has no decoration yet } void Client::finishCompositing() { Toplevel::finishCompositing(); updateVisibility(); updateDecoration(true, true); // for safety in case KWin is just resizing the window s_haveResizeEffect = false; } bool Client::shouldUnredirect() const { if (isActiveFullScreen()) { ToplevelList stacking = workspace()->xStackingOrder(); for (int pos = stacking.count() - 1; pos >= 0; --pos) { Toplevel* c = stacking.at(pos); if (c == this) // is not covered by any other window, ok to unredirect return true; if (c->geometry().intersects(geometry())) return false; } abort(); } return false; } //**************************************** // Unmanaged //**************************************** bool Unmanaged::shouldUnredirect() const { // the pixmap is needed for the login effect, a nicer solution would be the login effect increasing // refcount for the window pixmap (which would prevent unredirect), avoiding this hack if (resourceClass() == "ksplashx" || resourceClass() == "ksplashsimple" || resourceClass() == "ksplashqml" ) return false; // it must cover whole display or one xinerama screen, and be the topmost there if (geometry() == workspace()->clientArea(FullArea, geometry().center(), workspace()->currentDesktop()) || geometry() == workspace()->clientArea(ScreenArea, geometry().center(), workspace()->currentDesktop())) { ToplevelList stacking = workspace()->xStackingOrder(); for (int pos = stacking.count() - 1; pos >= 0; --pos) { Toplevel* c = stacking.at(pos); if (c == this) // is not covered by any other window, ok to unredirect return true; if (c->geometry().intersects(geometry())) return false; } abort(); } return false; } //**************************************** // Deleted //**************************************** bool Deleted::shouldUnredirect() const { return false; } } // namespace diff --git a/effects/blur/blur.cpp b/effects/blur/blur.cpp index f192765bb..92c3d5f2c 100644 --- a/effects/blur/blur.cpp +++ b/effects/blur/blur.cpp @@ -1,631 +1,635 @@ /* * Copyright © 2010 Fredrik Höglund * Copyright © 2011 Philipp Knechtges * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. if not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "blur.h" #include "blurshader.h" #include #include #include #include #include namespace KWin { KWIN_EFFECT(blur, BlurEffect) KWIN_EFFECT_SUPPORTED(blur, BlurEffect::supported()) KWIN_EFFECT_ENABLEDBYDEFAULT(blur, BlurEffect::enabledByDefault()) BlurEffect::BlurEffect() { shader = BlurShader::create(); // Offscreen texture that's used as the target for the horizontal blur pass // and the source for the vertical pass. tex = GLTexture(displayWidth(), displayHeight()); tex.setFilter(GL_LINEAR); tex.setWrapMode(GL_CLAMP_TO_EDGE); target = new GLRenderTarget(tex); net_wm_blur_region = XInternAtom(display(), "_KDE_NET_WM_BLUR_BEHIND_REGION", False); effects->registerPropertyType(net_wm_blur_region, true); reconfigure(ReconfigureAll); // ### Hackish way to announce support. // Should be included in _NET_SUPPORTED instead. if (shader->isValid() && target->valid()) { XChangeProperty(display(), rootWindow(), net_wm_blur_region, net_wm_blur_region, 32, PropModeReplace, 0, 0); } else { XDeleteProperty(display(), rootWindow(), net_wm_blur_region); } connect(effects, SIGNAL(windowAdded(EffectWindow*)), this, SLOT(slotWindowAdded(EffectWindow*))); connect(effects, SIGNAL(windowDeleted(EffectWindow*)), this, SLOT(slotWindowDeleted(EffectWindow*))); connect(effects, SIGNAL(propertyNotify(EffectWindow*,long)), this, SLOT(slotPropertyNotify(EffectWindow*,long))); connect(effects, SIGNAL(screenGeometryChanged(QSize)), this, SLOT(slotScreenGeometryChanged())); } BlurEffect::~BlurEffect() { effects->registerPropertyType(net_wm_blur_region, false); XDeleteProperty(display(), rootWindow(), net_wm_blur_region); windows.clear(); delete shader; delete target; } void BlurEffect::slotScreenGeometryChanged() { effects->reloadEffect(this); } void BlurEffect::reconfigure(ReconfigureFlags flags) { Q_UNUSED(flags) KConfigGroup cg = EffectsHandler::effectConfig("Blur"); int radius = qBound(2, cg.readEntry("BlurRadius", 12), 14); shader->setRadius(radius); m_shouldCache = cg.readEntry("CacheTexture", true); windows.clear(); if (!shader->isValid()) XDeleteProperty(display(), rootWindow(), net_wm_blur_region); } void BlurEffect::updateBlurRegion(EffectWindow *w) const { QRegion region; const QByteArray value = w->readProperty(net_wm_blur_region, XA_CARDINAL, 32); if (value.size() > 0 && !(value.size() % (4 * sizeof(unsigned long)))) { const unsigned long *cardinals = reinterpret_cast(value.constData()); for (unsigned int i = 0; i < value.size() / sizeof(unsigned long);) { int x = cardinals[i++]; int y = cardinals[i++]; int w = cardinals[i++]; int h = cardinals[i++]; region += QRect(x, y, w, h); } } if (region.isEmpty() && !value.isNull()) { // Set the data to a dummy value. // This is needed to be able to distinguish between the value not // being set, and being set to an empty region. w->setData(WindowBlurBehindRole, 1); } else w->setData(WindowBlurBehindRole, region); } void BlurEffect::slotWindowAdded(EffectWindow *w) { updateBlurRegion(w); } void BlurEffect::slotWindowDeleted(EffectWindow *w) { if (windows.contains(w)) { windows.remove(w); } } void BlurEffect::slotPropertyNotify(EffectWindow *w, long atom) { if (w && atom == net_wm_blur_region) { updateBlurRegion(w); if (windows.contains(w)) { const QRect screen(0, 0, displayWidth(), displayHeight()); windows[w].damagedRegion = expand(blurRegion(w).translated(w->pos())) & screen; } } } bool BlurEffect::enabledByDefault() { GLPlatform *gl = GLPlatform::instance(); if (gl->isIntel() && gl->chipClass() < SandyBridge) return false; if (gl->driver() == Driver_Catalyst) { // fglrx supports only ARB shaders and those tend to crash KWin (see Bug #270818 and #286795) return false; } return true; } bool BlurEffect::supported() { bool supported = GLRenderTarget::supported() && GLTexture::NPOTTextureSupported() && (GLSLBlurShader::supported() || ARBBlurShader::supported()); if (supported) { int maxTexSize; glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexSize); if (displayWidth() > maxTexSize || displayHeight() > maxTexSize) supported = false; } return supported; } QRect BlurEffect::expand(const QRect &rect) const { const int radius = shader->radius(); return rect.adjusted(-radius, -radius, radius, radius); } QRegion BlurEffect::expand(const QRegion ®ion) const { QRegion expanded; foreach (const QRect & rect, region.rects()) { expanded += expand(rect); } return expanded; } QRegion BlurEffect::blurRegion(const EffectWindow *w) const { QRegion region; const QVariant value = w->data(WindowBlurBehindRole); if (value.isValid()) { const QRegion appRegion = qvariant_cast(value); if (!appRegion.isEmpty()) { if (w->hasDecoration() && effects->decorationSupportsBlurBehind()) { region = w->shape(); region -= w->decorationInnerRect(); } region |= appRegion.translated(w->contentsRect().topLeft()) & w->decorationInnerRect(); } else { // An empty region means that the blur effect should be enabled // for the whole window. region = w->shape(); } } else if (w->hasDecoration() && effects->decorationSupportsBlurBehind()) { // If the client hasn't specified a blur region, we'll only enable // the effect behind the decoration. region = w->shape(); region -= w->decorationInnerRect(); } return region; } void BlurEffect::drawRegion(const QRegion ®ion) { const int vertexCount = region.rectCount() * 6; if (vertices.size() < vertexCount) vertices.resize(vertexCount); int i = 0; foreach (const QRect & r, region.rects()) { vertices[i++] = QVector2D(r.x() + r.width(), r.y()); vertices[i++] = QVector2D(r.x(), r.y()); vertices[i++] = QVector2D(r.x(), r.y() + r.height()); vertices[i++] = QVector2D(r.x(), r.y() + r.height()); vertices[i++] = QVector2D(r.x() + r.width(), r.y() + r.height()); vertices[i++] = QVector2D(r.x() + r.width(), r.y()); } GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); vbo->reset(); vbo->setData(vertexCount, 2, (float*)vertices.constData(), (float*)vertices.constData()); vbo->render(GL_TRIANGLES); } void BlurEffect::prePaintScreen(ScreenPrePaintData &data, int time) { m_damagedArea = QRegion(); m_paintedArea = QRegion(); m_currentBlur = QRegion(); effects->prePaintScreen(data, time); } void BlurEffect::prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time) { // this effect relies on prePaintWindow being called in the bottom to top order effects->prePaintWindow(w, data, time); if (!w->isPaintingEnabled()) { return; } // to blur an area partially we have to shrink the opaque area of a window QRegion newClip; const QRegion oldClip = data.clip; const int radius = shader->radius(); foreach (const QRect& rect, data.clip.rects()) { newClip |= rect.adjusted(radius,radius,-radius,-radius); } data.clip = newClip; const QRegion oldPaint = data.paint; // we don't have to blur a region we don't see m_currentBlur -= newClip; // if we have to paint a non-opaque part of this window that intersects with the // currently blurred region (which is not cached) we have to redraw the whole region if ((data.paint-oldClip).intersects(m_currentBlur)) { data.paint |= m_currentBlur; } // in case this window has regions to be blurred const QRegion blurArea = blurRegion(w).translated(w->pos()); const QRegion expandedBlur = expand(blurArea); if (m_shouldCache) { // we are caching the horizontally blurred background texture // if a window underneath the blurred area is damaged we have to // update the cached texture QRegion damagedCache; - if (windows.contains(w) && !windows[w].dropCache) { + if (windows.contains(w) && !windows.value(w).dropCache) { damagedCache = expand(expandedBlur & m_damagedArea) & expandedBlur; } else { damagedCache = expandedBlur; } if (!damagedCache.isEmpty()) { // This is the area of the blurry window which really can change. const QRegion damagedArea = damagedCache & blurArea; // In order to be able to recalculate this area we have to make sure the // background area is painted before. data.paint |= expand(damagedArea); if (windows.contains(w)) { // In case we already have a texture cache mark the dirty regions invalid. windows[w].damagedRegion |= damagedCache; windows[w].dropCache = false; } // we keep track of the "damage propagation" m_damagedArea |= damagedArea; // we have to check again whether we do not damage a blurred area // of a window we do not cache if (expandedBlur.intersects(m_currentBlur)) { data.paint |= m_currentBlur; } } } else { // we are not caching the window // if this window or an window underneath the blurred area is painted again we have to // blur everything if (m_paintedArea.intersects(expandedBlur) || data.paint.intersects(blurArea)) { data.paint |= expandedBlur; // we keep track of the "damage propagation" m_damagedArea |= expand(expandedBlur & m_damagedArea) & blurArea; // we have to check again whether we do not damage a blurred area // of a window we do not cache if (expandedBlur.intersects(m_currentBlur)) { data.paint |= m_currentBlur; } } m_currentBlur |= expandedBlur; } // we don't consider damaged areas which are occluded and are not // explicitly damaged by this window m_damagedArea -= data.clip; m_damagedArea |= oldPaint; // in contrast to m_damagedArea does m_paintedArea keep track of all repainted areas m_paintedArea -= data.clip; m_paintedArea |= data.paint; } bool BlurEffect::shouldBlur(const EffectWindow *w, int mask, const WindowPaintData &data) const { if (!target->valid() || !shader->isValid()) return false; if (effects->activeFullScreenEffect() && !w->data(WindowForceBlurRole).toBool()) return false; if (w->isDesktop()) return false; bool scaled = !qFuzzyCompare(data.xScale, 1.0) && !qFuzzyCompare(data.yScale, 1.0); bool translated = data.xTranslate || data.yTranslate; if (scaled || ((translated || (mask & PAINT_WINDOW_TRANSFORMED)) && !w->data(WindowForceBlurRole).toBool())) return false; bool blurBehindDecos = effects->decorationsHaveAlpha() && effects->decorationSupportsBlurBehind(); if (!w->hasAlpha() && !(blurBehindDecos && w->hasDecoration())) return false; return true; } void BlurEffect::drawWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data) { const QRect screen(0, 0, displayWidth(), displayHeight()); if (shouldBlur(w, mask, data)) { QRegion shape = region & blurRegion(w).translated(w->pos()) & screen; const bool translated = data.xTranslate || data.yTranslate; // let's do the evil parts - someone wants to blur behind a transformed window if (translated) { shape = shape.translated(data.xTranslate, data.yTranslate); shape = shape & region; } if (!shape.isEmpty()) { if (m_shouldCache && !translated) { doCachedBlur(w, region, data.opacity * data.contents_opacity); } else { doBlur(shape, screen, data.opacity * data.contents_opacity); } } } // Draw the window over the blurred area effects->drawWindow(w, mask, region, data); } void BlurEffect::paintEffectFrame(EffectFrame *frame, QRegion region, double opacity, double frameOpacity) { const QRect screen(0, 0, displayWidth(), displayHeight()); bool valid = target->valid() && shader->isValid(); QRegion shape = frame->geometry().adjusted(-5, -5, 5, 5) & screen; if (valid && !shape.isEmpty() && region.intersects(shape.boundingRect()) && frame->style() != EffectFrameNone) { doBlur(shape, screen, opacity * frameOpacity); } effects->paintEffectFrame(frame, region, opacity, frameOpacity); } void BlurEffect::doBlur(const QRegion& shape, const QRect& screen, const float opacity) { const QRegion expanded = expand(shape) & screen; const QRect r = expanded.boundingRect(); // Create a scratch texture and copy the area in the back buffer that we're // going to blur into it GLTexture scratch(r.width(), r.height()); scratch.setFilter(GL_LINEAR); scratch.setWrapMode(GL_CLAMP_TO_EDGE); scratch.bind(); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, r.x(), displayHeight() - r.y() - r.height(), r.width(), r.height()); // Draw the texture on the offscreen framebuffer object, while blurring it horizontally target->attachTexture(tex); GLRenderTarget::pushRenderTarget(target); shader->bind(); shader->setDirection(Qt::Horizontal); shader->setPixelDistance(1.0 / r.width()); // Set up the texture matrix to transform from screen coordinates // to texture coordinates. #ifndef KWIN_HAVE_OPENGLES glMatrixMode(GL_TEXTURE); #endif pushMatrix(); QMatrix4x4 textureMatrix; textureMatrix.scale(1.0 / scratch.width(), -1.0 / scratch.height(), 1); textureMatrix.translate(-r.x(), -scratch.height() - r.y(), 0); loadMatrix(textureMatrix); shader->setTextureMatrix(textureMatrix); drawRegion(expanded); GLRenderTarget::popRenderTarget(); scratch.unbind(); scratch.discard(); // Now draw the horizontally blurred area back to the backbuffer, while // blurring it vertically and clipping it to the window shape. tex.bind(); shader->setDirection(Qt::Vertical); shader->setPixelDistance(1.0 / tex.height()); // Modulate the blurred texture with the window opacity if the window isn't opaque if (opacity < 1.0) { #ifndef KWIN_HAVE_OPENGLES glPushAttrib(GL_COLOR_BUFFER_BIT); #endif glEnable(GL_BLEND); glBlendColor(0, 0, 0, opacity); glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); } // Set the up the texture matrix to transform from screen coordinates // to texture coordinates. textureMatrix.setToIdentity(); textureMatrix.scale(1.0 / tex.width(), -1.0 / tex.height(), 1); textureMatrix.translate(0, -tex.height(), 0); loadMatrix(textureMatrix); shader->setTextureMatrix(textureMatrix); drawRegion(shape); popMatrix(); #ifndef KWIN_HAVE_OPENGLES glMatrixMode(GL_MODELVIEW); #endif if (opacity < 1.0) { glDisable(GL_BLEND); #ifndef KWIN_HAVE_OPENGLES glPopAttrib(); #endif } tex.unbind(); shader->unbind(); } void BlurEffect::doCachedBlur(EffectWindow *w, const QRegion& region, const float opacity) { const QRect screen(0, 0, displayWidth(), displayHeight()); const QRegion blurredRegion = blurRegion(w).translated(w->pos()) & screen; const QRegion expanded = expand(blurredRegion) & screen; const QRect r = expanded.boundingRect(); // The background texture we get is only partially valid. if (!windows.contains(w)) { BlurWindowInfo bwi; bwi.blurredBackground = GLTexture(r.width(),r.height()); bwi.damagedRegion = expanded; bwi.dropCache = false; - windows[w] = bwi; - } - - if (windows[w].blurredBackground.size() != r.size()) { + bwi.windowPos = w->pos(); + windows.insert(w, bwi); + } else if (windows.value(w).blurredBackground.size() != r.size()) { windows[w].blurredBackground = GLTexture(r.width(),r.height()); windows[w].damagedRegion = expanded; windows[w].dropCache = false; + windows[w].windowPos = w->pos(); + } else if (windows.value(w).windowPos != w->pos()) { + windows[w].damagedRegion = expanded; + windows[w].dropCache = false; + windows[w].windowPos = w->pos(); } GLTexture targetTexture = windows[w].blurredBackground; targetTexture.setFilter(GL_LINEAR); targetTexture.setWrapMode(GL_CLAMP_TO_EDGE); shader->bind(); QMatrix4x4 textureMatrix; QMatrix4x4 modelViewProjectionMatrix; #ifndef KWIN_HAVE_OPENGLES glMatrixMode(GL_MODELVIEW); pushMatrix(); glLoadIdentity(); glMatrixMode(GL_TEXTURE); pushMatrix(); glMatrixMode(GL_PROJECTION); pushMatrix(); #endif // We only update that part of the background texture that is visible and marked as dirty. - const QRegion updateBackground = windows[w].damagedRegion & region; + const QRegion updateBackground = windows.value(w).damagedRegion & region; if (!updateBackground.isEmpty()) { const QRect updateRect = (expand(updateBackground) & expanded).boundingRect(); // First we have to copy the background from the frontbuffer // into a scratch texture (in this case "tex"). tex.bind(); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, updateRect.x(), displayHeight() - updateRect.y() - updateRect.height(), updateRect.width(), updateRect.height()); // Draw the texture on the offscreen framebuffer object, while blurring it horizontally target->attachTexture(targetTexture); GLRenderTarget::pushRenderTarget(target); shader->setDirection(Qt::Horizontal); shader->setPixelDistance(1.0 / tex.width()); modelViewProjectionMatrix.ortho(0, r.width(), r.height(), 0 , 0, 65535); modelViewProjectionMatrix.translate(-r.x(), -r.y(), 0); loadMatrix(modelViewProjectionMatrix); shader->setModelViewProjectionMatrix(modelViewProjectionMatrix); // Set up the texture matrix to transform from screen coordinates // to texture coordinates. textureMatrix.scale(1.0 / tex.width(), -1.0 / tex.height(), 1); textureMatrix.translate(-updateRect.x(), -updateRect.height() - updateRect.y(), 0); #ifndef KWIN_HAVE_OPENGLES glMatrixMode(GL_TEXTURE); loadMatrix(textureMatrix); glMatrixMode(GL_PROJECTION); #endif shader->setTextureMatrix(textureMatrix); drawRegion(updateBackground & screen); GLRenderTarget::popRenderTarget(); tex.unbind(); // mark the updated region as valid windows[w].damagedRegion -= updateBackground; } // Now draw the horizontally blurred area back to the backbuffer, while // blurring it vertically and clipping it to the window shape. targetTexture.bind(); shader->setDirection(Qt::Vertical); shader->setPixelDistance(1.0 / targetTexture.height()); // Modulate the blurred texture with the window opacity if the window isn't opaque if (opacity < 1.0) { #ifndef KWIN_HAVE_OPENGLES glPushAttrib(GL_COLOR_BUFFER_BIT); #endif glEnable(GL_BLEND); glBlendColor(0, 0, 0, opacity); glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); } modelViewProjectionMatrix.setToIdentity(); modelViewProjectionMatrix.ortho(0, displayWidth(), displayHeight(), 0, 0, 65535); loadMatrix(modelViewProjectionMatrix); shader->setModelViewProjectionMatrix(modelViewProjectionMatrix); // Set the up the texture matrix to transform from screen coordinates // to texture coordinates. textureMatrix.setToIdentity(); textureMatrix.scale(1.0 / targetTexture.width(), -1.0 / targetTexture.height(), 1); textureMatrix.translate(-r.x(), -targetTexture.height() - r.y(), 0); #ifndef KWIN_HAVE_OPENGLES glMatrixMode(GL_TEXTURE); loadMatrix(textureMatrix); glMatrixMode(GL_PROJECTION); #endif shader->setTextureMatrix(textureMatrix); drawRegion(blurredRegion & region); #ifndef KWIN_HAVE_OPENGLES popMatrix(); glMatrixMode(GL_TEXTURE); popMatrix(); glMatrixMode(GL_MODELVIEW); popMatrix(); #endif if (opacity < 1.0) { glDisable(GL_BLEND); #ifndef KWIN_HAVE_OPENGLES glPopAttrib(); #endif } targetTexture.unbind(); shader->unbind(); } } // namespace KWin diff --git a/effects/blur/blur.h b/effects/blur/blur.h index 65ba3811c..f2579ac16 100644 --- a/effects/blur/blur.h +++ b/effects/blur/blur.h @@ -1,90 +1,91 @@ /* * Copyright © 2010 Fredrik Höglund * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. if not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef BLUR_H #define BLUR_H #include #include #include #include #include namespace KWin { class BlurShader; class BlurEffect : public KWin::Effect { Q_OBJECT public: BlurEffect(); ~BlurEffect(); static bool supported(); static bool enabledByDefault(); void reconfigure(ReconfigureFlags flags); void prePaintScreen(ScreenPrePaintData &data, int time); void prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time); void drawWindow(EffectWindow *w, int mask, QRegion region, WindowPaintData &data); void paintEffectFrame(EffectFrame *frame, QRegion region, double opacity, double frameOpacity); public Q_SLOTS: void slotWindowAdded(EffectWindow *w); void slotWindowDeleted(EffectWindow *w); void slotPropertyNotify(EffectWindow *w, long atom); void slotScreenGeometryChanged(); private: QRect expand(const QRect &rect) const; QRegion expand(const QRegion ®ion) const; QRegion blurRegion(const EffectWindow *w) const; bool shouldBlur(const EffectWindow *w, int mask, const WindowPaintData &data) const; void updateBlurRegion(EffectWindow *w) const; void drawRegion(const QRegion ®ion); void doBlur(const QRegion &shape, const QRect &screen, const float opacity); void doCachedBlur(EffectWindow *w, const QRegion& region, const float opacity); private: BlurShader *shader; QVector vertices; GLRenderTarget *target; GLTexture tex; long net_wm_blur_region; QRegion m_damagedArea; // keeps track of the area which has been damaged (from bottom to top) QRegion m_paintedArea; // actually painted area which is greater than m_damagedArea QRegion m_currentBlur; // keeps track of the currently blured area of non-caching windows(from bottom to top) bool m_shouldCache; struct BlurWindowInfo { GLTexture blurredBackground; // keeps the horizontally blurred background QRegion damagedRegion; + QPoint windowPos; bool dropCache; }; QHash< const EffectWindow*, BlurWindowInfo > windows; }; } // namespace KWin #endif diff --git a/geometry.cpp b/geometry.cpp index 6d47078e9..b4478384c 100644 --- a/geometry.cpp +++ b/geometry.cpp @@ -1,3194 +1,3194 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak Copyright (C) 2009 Lucas Murray This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ /* This file contains things relevant to geometry, i.e. workspace size, window positions and window sizes. */ #include "client.h" #include "workspace.h" #include #include #include #include "placement.h" #include "notifications.h" #include "geometrytip.h" #include "rules.h" #include "effects.h" #include #include #include #include #include #include "outline.h" #ifdef KWIN_BUILD_TILING #include "tiling/tiling.h" #endif namespace KWin { //******************************************** // Workspace //******************************************** extern int screen_number; extern bool is_multihead; /*! Resizes the workspace after an XRANDR screen size change */ void Workspace::desktopResized() { QRect geom = Kephal::ScreenUtils::desktopGeometry(); NETSize desktop_geometry; desktop_geometry.width = geom.width(); desktop_geometry.height = geom.height(); rootInfo->setDesktopGeometry(-1, desktop_geometry); updateClientArea(); saveOldScreenSizes(); // after updateClientArea(), so that one still uses the previous one #ifdef KWIN_BUILD_SCREENEDGES m_screenEdge.update(true); #endif if (effects) { static_cast(effects)->desktopResized(geom.size()); } } void Workspace::saveOldScreenSizes() { olddisplaysize = QSize( displayWidth(), displayHeight()); oldscreensizes.clear(); for( int i = 0; i < numScreens(); ++i ) oldscreensizes.append( screenGeometry( i )); } /*! Updates the current client areas according to the current clients. If the area changes or force is true, the new areas are propagated to the world. The client area is the area that is available for clients (that which is not taken by windows like panels, the top-of-screen menu etc). \sa clientArea() */ void Workspace::updateClientArea(bool force) { int nscreens = Kephal::ScreenUtils::numScreens(); kDebug(1212) << "screens: " << nscreens << "desktops: " << numberOfDesktops(); QVector< QRect > new_wareas(numberOfDesktops() + 1); QVector< StrutRects > new_rmoveareas(numberOfDesktops() + 1); QVector< QVector< QRect > > new_sareas(numberOfDesktops() + 1); QVector< QRect > screens(nscreens); QRect desktopArea = Kephal::ScreenUtils::desktopGeometry(); for (int iS = 0; iS < nscreens; iS ++) { screens [iS] = Kephal::ScreenUtils::screenGeometry(iS); } for (int i = 1; i <= numberOfDesktops(); ++i) { new_wareas[ i ] = desktopArea; new_sareas[ i ].resize(nscreens); for (int iS = 0; iS < nscreens; iS ++) new_sareas[ i ][ iS ] = screens[ iS ]; } for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) { if (!(*it)->hasStrut()) continue; QRect r = (*it)->adjustedClientArea(desktopArea, desktopArea); StrutRects strutRegion = (*it)->strutRects(); // Ignore offscreen xinerama struts. These interfere with the larger monitors on the setup // and should be ignored so that applications that use the work area to work out where // windows can go can use the entire visible area of the larger monitors. // This goes against the EWMH description of the work area but it is a toss up between // having unusable sections of the screen (Which can be quite large with newer monitors) // or having some content appear offscreen (Relatively rare compared to other). bool hasOffscreenXineramaStrut = (*it)->hasOffscreenXineramaStrut(); if ((*it)->isOnAllDesktops()) { for (int i = 1; i <= numberOfDesktops(); ++i) { if (!hasOffscreenXineramaStrut) new_wareas[ i ] = new_wareas[ i ].intersected(r); new_rmoveareas[ i ] += strutRegion; for (int iS = 0; iS < nscreens; iS ++) { new_sareas[ i ][ iS ] = new_sareas[ i ][ iS ].intersected( (*it)->adjustedClientArea(desktopArea, screens[ iS ])); } } } else { if (!hasOffscreenXineramaStrut) new_wareas[(*it)->desktop()] = new_wareas[(*it)->desktop()].intersected(r); new_rmoveareas[(*it)->desktop()] += strutRegion; for (int iS = 0; iS < nscreens; iS ++) { // kDebug (1212) << "adjusting new_sarea: " << screens[ iS ]; new_sareas[(*it)->desktop()][ iS ] = new_sareas[(*it)->desktop()][ iS ].intersected( (*it)->adjustedClientArea(desktopArea, screens[ iS ])); } } } #if 0 for (int i = 1; i <= numberOfDesktops(); ++i) { for (int iS = 0; iS < nscreens; iS ++) kDebug(1212) << "new_sarea: " << new_sareas[ i ][ iS ]; } #endif bool changed = force; if (screenarea.isEmpty()) changed = true; for (int i = 1; !changed && i <= numberOfDesktops(); ++i) { if (workarea[ i ] != new_wareas[ i ]) changed = true; if (restrictedmovearea[ i ] != new_rmoveareas[ i ]) changed = true; if (screenarea[ i ].size() != new_sareas[ i ].size()) changed = true; for (int iS = 0; !changed && iS < nscreens; iS ++) if (new_sareas[ i ][ iS ] != screenarea [ i ][ iS ]) changed = true; } if (changed) { workarea = new_wareas; oldrestrictedmovearea = restrictedmovearea; restrictedmovearea = new_rmoveareas; screenarea = new_sareas; NETRect r; for (int i = 1; i <= numberOfDesktops(); i++) { r.pos.x = workarea[ i ].x(); r.pos.y = workarea[ i ].y(); r.size.width = workarea[ i ].width(); r.size.height = workarea[ i ].height(); rootInfo->setWorkArea(i, r); } for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) (*it)->checkWorkspacePosition(); for (ClientList::ConstIterator it = desktops.constBegin(); it != desktops.constEnd(); ++it) (*it)->checkWorkspacePosition(); oldrestrictedmovearea.clear(); // reset, no longer valid or needed } kDebug(1212) << "Done."; } void Workspace::updateClientArea() { updateClientArea(false); } /*! returns the area available for clients. This is the desktop geometry minus windows on the dock. Placement algorithms should refer to this rather than geometry(). \sa geometry() */ QRect Workspace::clientArea(clientAreaOption opt, int screen, int desktop) const { if (desktop == NETWinInfo::OnAllDesktops || desktop == 0) desktop = currentDesktop(); if (screen == -1) screen = activeScreen(); QRect sarea, warea; if (is_multihead) { sarea = (!screenarea.isEmpty() && screen < screenarea[ desktop ].size()) // screens may be missing during KWin initialization or screen config changes ? screenarea[ desktop ][ screen_number ] : Kephal::ScreenUtils::screenGeometry(screen_number); warea = workarea[ desktop ].isNull() ? Kephal::ScreenUtils::screenGeometry(screen_number) : workarea[ desktop ]; } else { sarea = (!screenarea.isEmpty() && screen < screenarea[ desktop ].size()) // screens may be missing during KWin initialization or screen config changes ? screenarea[ desktop ][ screen ] : Kephal::ScreenUtils::screenGeometry(screen); warea = workarea[ desktop ].isNull() ? Kephal::ScreenUtils::desktopGeometry() : workarea[ desktop ]; } switch(opt) { case MaximizeArea: case PlacementArea: return sarea; case MaximizeFullArea: case FullScreenArea: case MovementArea: case ScreenArea: if (is_multihead) return Kephal::ScreenUtils::screenGeometry(screen_number); else return Kephal::ScreenUtils::screenGeometry(screen); case WorkArea: if (is_multihead) return sarea; else return warea; case FullArea: if (is_multihead) return Kephal::ScreenUtils::screenGeometry(screen_number); else return Kephal::ScreenUtils::desktopGeometry(); } abort(); } QRect Workspace::clientArea(clientAreaOption opt, const QPoint& p, int desktop) const { int screen = Kephal::ScreenUtils::screenId(p); return clientArea(opt, screen, desktop); } QRect Workspace::clientArea(clientAreaOption opt, const Client* c) const { return clientArea(opt, c->geometry().center(), c->desktop()); } QRegion Workspace::restrictedMoveArea(int desktop, StrutAreas areas) const { if (desktop == NETWinInfo::OnAllDesktops || desktop == 0) desktop = currentDesktop(); QRegion region; foreach (const StrutRect & rect, restrictedmovearea[desktop]) if (areas & rect.area()) region += rect; return region; } bool Workspace::inUpdateClientArea() const { return !oldrestrictedmovearea.isEmpty(); } QRegion Workspace::previousRestrictedMoveArea(int desktop, StrutAreas areas) const { if (desktop == NETWinInfo::OnAllDesktops || desktop == 0) desktop = currentDesktop(); QRegion region; foreach (const StrutRect & rect, oldrestrictedmovearea.at(desktop)) if (areas & rect.area()) region += rect; return region; } QVector< QRect > Workspace::previousScreenSizes() const { return oldscreensizes; } int Workspace::oldDisplayWidth() const { return olddisplaysize.width(); } int Workspace::oldDisplayHeight() const { return olddisplaysize.height(); } /*! Client \a c is moved around to position \a pos. This gives the workspace the opportunity to interveniate and to implement snap-to-windows functionality. The parameter \a snapAdjust is a multiplier used to calculate the effective snap zones. When 1.0, it means that the snap zones will be used without change. */ QPoint Workspace::adjustClientPosition(Client* c, QPoint pos, bool unrestricted, double snapAdjust) { //CT 16mar98, 27May98 - magics: BorderSnapZone, WindowSnapZone //CT adapted for kwin on 25Nov1999 //aleXXX 02Nov2000 added second snapping mode if (options->windowSnapZone || options->borderSnapZone || options->centerSnapZone) { const bool sOWO = options->snapOnlyWhenOverlapping; const QRect maxRect = clientArea(MovementArea, pos + c->rect().center(), c->desktop()); const int xmin = maxRect.left(); const int xmax = maxRect.right() + 1; //desk size const int ymin = maxRect.top(); const int ymax = maxRect.bottom() + 1; const int cx(pos.x()); const int cy(pos.y()); const int cw(c->width()); const int ch(c->height()); const int rx(cx + cw); const int ry(cy + ch); //these don't change int nx(cx), ny(cy); //buffers int deltaX(xmax); int deltaY(ymax); //minimum distance to other clients int lx, ly, lrx, lry; //coords and size for the comparison client, l // border snap int snap = options->borderSnapZone * snapAdjust; //snap trigger if (snap) { if ((sOWO ? (cx < xmin) : true) && (qAbs(xmin - cx) < snap)) { deltaX = xmin - cx; nx = xmin; } if ((sOWO ? (rx > xmax) : true) && (qAbs(rx - xmax) < snap) && (qAbs(xmax - rx) < deltaX)) { deltaX = rx - xmax; nx = xmax - cw; } if ((sOWO ? (cy < ymin) : true) && (qAbs(ymin - cy) < snap)) { deltaY = ymin - cy; ny = ymin; } if ((sOWO ? (ry > ymax) : true) && (qAbs(ry - ymax) < snap) && (qAbs(ymax - ry) < deltaY)) { deltaY = ry - ymax; ny = ymax - ch; } } // windows snap snap = options->windowSnapZone * snapAdjust; if (snap) { QList::ConstIterator l; for (l = clients.constBegin(); l != clients.constEnd(); ++l) { if ((((*l)->isOnDesktop(c->desktop()) && !(*l)->isMinimized()) || (c->isOnDesktop(NET::OnAllDesktops) && (*l)->isOnDesktop(Workspace::currentDesktop()) && !(*l)->isMinimized())) && (!(*l)->tabGroup() || (*l) == (*l)->tabGroup()->current()) && (*l) != c) { lx = (*l)->x(); ly = (*l)->y(); lrx = lx + (*l)->width(); lry = ly + (*l)->height(); if (((cy <= lry) && (cy >= ly)) || ((ry >= ly) && (ry <= lry)) || ((cy <= ly) && (ry >= lry))) { if ((sOWO ? (cx < lrx) : true) && (qAbs(lrx - cx) < snap) && (qAbs(lrx - cx) < deltaX)) { deltaX = qAbs(lrx - cx); nx = lrx; } if ((sOWO ? (rx > lx) : true) && (qAbs(rx - lx) < snap) && (qAbs(rx - lx) < deltaX)) { deltaX = qAbs(rx - lx); nx = lx - cw; } } if (((cx <= lrx) && (cx >= lx)) || ((rx >= lx) && (rx <= lrx)) || ((cx <= lx) && (rx >= lrx))) { if ((sOWO ? (cy < lry) : true) && (qAbs(lry - cy) < snap) && (qAbs(lry - cy) < deltaY)) { deltaY = qAbs(lry - cy); ny = lry; } //if ( (qAbs( ry-ly ) < snap) && (qAbs( ry - ly ) < deltaY )) if ((sOWO ? (ry > ly) : true) && (qAbs(ry - ly) < snap) && (qAbs(ry - ly) < deltaY)) { deltaY = qAbs(ry - ly); ny = ly - ch; } } // Corner snapping if (nx == lrx || nx + cw == lx) { if ((sOWO ? (ry > lry) : true) && (qAbs(lry - ry) < snap) && (qAbs(lry - ry) < deltaY)) { deltaY = qAbs(lry - ry); ny = lry - ch; } if ((sOWO ? (cy < ly) : true) && (qAbs(cy - ly) < snap) && (qAbs(cy - ly) < deltaY)) { deltaY = qAbs(cy - ly); ny = ly; } } if (ny == lry || ny + ch == ly) { if ((sOWO ? (rx > lrx) : true) && (qAbs(lrx - rx) < snap) && (qAbs(lrx - rx) < deltaX)) { deltaX = qAbs(lrx - rx); nx = lrx - cw; } if ((sOWO ? (cx < lx) : true) && (qAbs(cx - lx) < snap) && (qAbs(cx - lx) < deltaX)) { deltaX = qAbs(cx - lx); nx = lx; } } } } } // center snap snap = options->centerSnapZone * snapAdjust; //snap trigger if (snap) { int diffX = qAbs((xmin + xmax) / 2 - (cx + cw / 2)); int diffY = qAbs((ymin + ymax) / 2 - (cy + ch / 2)); if (diffX < snap && diffY < snap && diffX < deltaX && diffY < deltaY) { // Snap to center of screen deltaX = diffX; deltaY = diffY; nx = (xmin + xmax) / 2 - cw / 2; ny = (ymin + ymax) / 2 - ch / 2; } else if (options->borderSnapZone) { // Enhance border snap if ((nx == xmin || nx == xmax - cw) && diffY < snap && diffY < deltaY) { // Snap to vertical center on screen edge deltaY = diffY; ny = (ymin + ymax) / 2 - ch / 2; } else if (((unrestricted ? ny == ymin : ny <= ymin) || ny == ymax - ch) && diffX < snap && diffX < deltaX) { // Snap to horizontal center on screen edge deltaX = diffX; nx = (xmin + xmax) / 2 - cw / 2; } } } pos = QPoint(nx, ny); } return pos; } QRect Workspace::adjustClientSize(Client* c, QRect moveResizeGeom, int mode) { //adapted from adjustClientPosition on 29May2004 //this function is called when resizing a window and will modify //the new dimensions to snap to other windows/borders if appropriate if (options->windowSnapZone || options->borderSnapZone) { // || options->centerSnapZone ) const bool sOWO = options->snapOnlyWhenOverlapping; const QRect maxRect = clientArea(MovementArea, c->rect().center(), c->desktop()); const int xmin = maxRect.left(); const int xmax = maxRect.right(); //desk size const int ymin = maxRect.top(); const int ymax = maxRect.bottom(); const int cx(moveResizeGeom.left()); const int cy(moveResizeGeom.top()); const int rx(moveResizeGeom.right()); const int ry(moveResizeGeom.bottom()); int newcx(cx), newcy(cy); //buffers int newrx(rx), newry(ry); int deltaX(xmax); int deltaY(ymax); //minimum distance to other clients int lx, ly, lrx, lry; //coords and size for the comparison client, l // border snap int snap = options->borderSnapZone; //snap trigger if (snap) { deltaX = int(snap); deltaY = int(snap); #define SNAP_BORDER_TOP \ if ((sOWO?(newcyymax):true) && (qAbs(ymax-newry)xmax):true) && (qAbs(xmax-newrx)windowSnapZone; if (snap) { deltaX = int(snap); deltaY = int(snap); QList::ConstIterator l; for (l = clients.constBegin(); l != clients.constEnd(); ++l) { if ((*l)->isOnDesktop(currentDesktop()) && !(*l)->isMinimized() && (*l) != c) { lx = (*l)->x() - 1; ly = (*l)->y() - 1; lrx = (*l)->x() + (*l)->width(); lry = (*l)->y() + (*l)->height(); #define WITHIN_HEIGHT ((( newcy <= lry ) && ( newcy >= ly )) || \ (( newry >= ly ) && ( newry <= lry )) || \ (( newcy <= ly ) && ( newry >= lry )) ) #define WITHIN_WIDTH ( (( cx <= lrx ) && ( cx >= lx )) || \ (( rx >= lx ) && ( rx <= lrx )) || \ (( cx <= lx ) && ( rx >= lrx )) ) #define SNAP_WINDOW_TOP if ( (sOWO?(newcyly):true) \ && WITHIN_WIDTH \ && (qAbs( ly - newry ) < deltaY) ) { \ deltaY = qAbs( ly - newry ); \ newry=ly; \ } #define SNAP_WINDOW_LEFT if ( (sOWO?(newcxlx):true) \ && WITHIN_HEIGHT \ && (qAbs( lx - newrx ) < deltaX)) \ { \ deltaX = qAbs( lx - newrx ); \ newrx=lx; \ } #define SNAP_WINDOW_C_TOP if ( (sOWO?(newcylry):true) \ && (newcx == lrx || newrx == lx) \ && qAbs(lry-newry) < deltaY ) { \ deltaY = qAbs( lry - newry - 1 ); \ newry = lry - 1; \ } #define SNAP_WINDOW_C_LEFT if ( (sOWO?(newcxlrx):true) \ && (newcy == lry || newry == ly) \ && qAbs(lrx-newrx) < deltaX ) { \ deltaX = qAbs( lrx - newrx - 1 ); \ newrx = lrx - 1; \ } switch(mode) { case PositionBottomRight: SNAP_WINDOW_BOTTOM SNAP_WINDOW_RIGHT SNAP_WINDOW_C_BOTTOM SNAP_WINDOW_C_RIGHT break; case PositionRight: SNAP_WINDOW_RIGHT SNAP_WINDOW_C_RIGHT break; case PositionBottom: SNAP_WINDOW_BOTTOM SNAP_WINDOW_C_BOTTOM break; case PositionTopLeft: SNAP_WINDOW_TOP SNAP_WINDOW_LEFT SNAP_WINDOW_C_TOP SNAP_WINDOW_C_LEFT break; case PositionLeft: SNAP_WINDOW_LEFT SNAP_WINDOW_C_LEFT break; case PositionTop: SNAP_WINDOW_TOP SNAP_WINDOW_C_TOP break; case PositionTopRight: SNAP_WINDOW_TOP SNAP_WINDOW_RIGHT SNAP_WINDOW_C_TOP SNAP_WINDOW_C_RIGHT break; case PositionBottomLeft: SNAP_WINDOW_BOTTOM SNAP_WINDOW_LEFT SNAP_WINDOW_C_BOTTOM SNAP_WINDOW_C_LEFT break; default: abort(); break; } } } } // center snap //snap = options->centerSnapZone; //if (snap) // { // // Don't resize snap to center as it interferes too much // // There are two ways of implementing this if wanted: // // 1) Snap only to the same points that the move snap does, and // // 2) Snap to the horizontal and vertical center lines of the screen // } moveResizeGeom = QRect(QPoint(newcx, newcy), QPoint(newrx, newry)); } return moveResizeGeom; } /*! Marks the client as being moved around by the user. */ void Workspace::setClientIsMoving(Client *c) { Q_ASSERT(!c || !movingClient); // Catch attempts to move a second // window while still moving the first one. movingClient = c; if (movingClient) ++block_focus; else --block_focus; } /*! Cascades all clients on the current desktop */ void Workspace::cascadeDesktop() { // TODO XINERAMA this probably is not right for xinerama Q_ASSERT(block_stacking_updates == 0); initPositioning->reinitCascading(currentDesktop()); QRect area = clientArea(PlacementArea, QPoint(0, 0), currentDesktop()); foreach (Client * client, stackingOrder()) { if ((!client->isOnDesktop(currentDesktop())) || (client->isMinimized()) || (client->isOnAllDesktops()) || (!client->isMovable())) continue; initPositioning->placeCascaded(client, area); } } /*! Unclutters the current desktop by smart-placing all clients again. */ void Workspace::unclutterDesktop() { for (int i = clients.size() - 1; i >= 0; i--) { if ((!clients.at(i)->isOnDesktop(currentDesktop())) || (clients.at(i)->isMinimized()) || (clients.at(i)->isOnAllDesktops()) || (!clients.at(i)->isMovable())) continue; initPositioning->placeSmart(clients.at(i), QRect()); } } // When kwin crashes, windows will not be gravitated back to their original position // and will remain offset by the size of the decoration. So when restarting, fix this // (the property with the size of the frame remains on the window after the crash). void Workspace::fixPositionAfterCrash(Window w, const XWindowAttributes& attr) { NETWinInfo i(display(), w, rootWindow(), NET::WMFrameExtents); NETStrut frame = i.frameExtents(); if (frame.left != 0 || frame.top != 0) XMoveWindow(display(), w, attr.x - frame.left, attr.y - frame.top); } //******************************************** // Client //******************************************** void Client::keepInArea(QRect area, bool partial) { if (partial) { // increase the area so that can have only 100 pixels in the area area.setLeft(qMin(area.left() - width() + 100, area.left())); area.setTop(qMin(area.top() - height() + 100, area.top())); area.setRight(qMax(area.right() + width() - 100, area.right())); area.setBottom(qMax(area.bottom() + height() - 100, area.bottom())); } if (!partial) { // resize to fit into area if (area.width() < width() || area.height() < height()) resizeWithChecks(qMin(area.width(), width()), qMin(area.height(), height())); } if (geometry().right() > area.right() && width() < area.width()) move(area.right() - width() + 1, y()); if (geometry().bottom() > area.bottom() && height() < area.height()) move(x(), area.bottom() - height() + 1); if (!area.contains(geometry().topLeft())) { int tx = x(); int ty = y(); if (tx < area.x()) tx = area.x(); if (ty < area.y()) ty = area.y(); move(tx, ty); } } /*! Returns \a area with the client's strut taken into account. Used from Workspace in updateClientArea. */ // TODO move to Workspace? QRect Client::adjustedClientArea(const QRect &desktopArea, const QRect& area) const { QRect r = area; NETExtendedStrut str = strut(); QRect stareaL = QRect( 0, str . left_start, str . left_width, str . left_end - str . left_start + 1); QRect stareaR = QRect( desktopArea . right() - str . right_width + 1, str . right_start, str . right_width, str . right_end - str . right_start + 1); QRect stareaT = QRect( str . top_start, 0, str . top_end - str . top_start + 1, str . top_width); QRect stareaB = QRect( str . bottom_start, desktopArea . bottom() - str . bottom_width + 1, str . bottom_end - str . bottom_start + 1, str . bottom_width); QRect screenarea = workspace()->clientArea(ScreenArea, this); // HACK: workarea handling is not xinerama aware, so if this strut // reserves place at a xinerama edge that's inside the virtual screen, // ignore the strut for workspace setting. if (area == Kephal::ScreenUtils::desktopGeometry()) { if (stareaL.left() < screenarea.left()) stareaL = QRect(); if (stareaR.right() > screenarea.right()) stareaR = QRect(); if (stareaT.top() < screenarea.top()) stareaT = QRect(); if (stareaB.bottom() < screenarea.bottom()) stareaB = QRect(); } // Handle struts at xinerama edges that are inside the virtual screen. // They're given in virtual screen coordinates, make them affect only // their xinerama screen. stareaL.setLeft(qMax(stareaL.left(), screenarea.left())); stareaR.setRight(qMin(stareaR.right(), screenarea.right())); stareaT.setTop(qMax(stareaT.top(), screenarea.top())); stareaB.setBottom(qMin(stareaB.bottom(), screenarea.bottom())); if (stareaL . intersects(area)) { // kDebug (1212) << "Moving left of: " << r << " to " << stareaL.right() + 1; r . setLeft(stareaL . right() + 1); } if (stareaR . intersects(area)) { // kDebug (1212) << "Moving right of: " << r << " to " << stareaR.left() - 1; r . setRight(stareaR . left() - 1); } if (stareaT . intersects(area)) { // kDebug (1212) << "Moving top of: " << r << " to " << stareaT.bottom() + 1; r . setTop(stareaT . bottom() + 1); } if (stareaB . intersects(area)) { // kDebug (1212) << "Moving bottom of: " << r << " to " << stareaB.top() - 1; r . setBottom(stareaB . top() - 1); } return r; } NETExtendedStrut Client::strut() const { NETExtendedStrut ext = info->extendedStrut(); NETStrut str = info->strut(); if (ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0 && (str.left != 0 || str.right != 0 || str.top != 0 || str.bottom != 0)) { // build extended from simple if (str.left != 0) { ext.left_width = str.left; ext.left_start = 0; ext.left_end = displayHeight(); } if (str.right != 0) { ext.right_width = str.right; ext.right_start = 0; ext.right_end = displayHeight(); } if (str.top != 0) { ext.top_width = str.top; ext.top_start = 0; ext.top_end = displayWidth(); } if (str.bottom != 0) { ext.bottom_width = str.bottom; ext.bottom_start = 0; ext.bottom_end = displayWidth(); } } return ext; } StrutRect Client::strutRect(StrutArea area) const { assert(area != StrutAreaAll); // Not valid NETExtendedStrut strutArea = strut(); switch(area) { case StrutAreaTop: if (strutArea.top_width != 0) return StrutRect(QRect( strutArea.top_start, 0, strutArea.top_end - strutArea.top_start, strutArea.top_width ), StrutAreaTop); break; case StrutAreaRight: if (strutArea.right_width != 0) return StrutRect(QRect( displayWidth() - strutArea.right_width, strutArea.right_start, strutArea.right_width, strutArea.right_end - strutArea.right_start ), StrutAreaRight); break; case StrutAreaBottom: if (strutArea.bottom_width != 0) return StrutRect(QRect( strutArea.bottom_start, displayHeight() - strutArea.bottom_width, strutArea.bottom_end - strutArea.bottom_start, strutArea.bottom_width ), StrutAreaBottom); break; case StrutAreaLeft: if (strutArea.left_width != 0) return StrutRect(QRect( 0, strutArea.left_start, strutArea.left_width, strutArea.left_end - strutArea.left_start ), StrutAreaLeft); break; default: abort(); // Not valid } return StrutRect(); // Null rect } StrutRects Client::strutRects() const { StrutRects region; region += strutRect(StrutAreaTop); region += strutRect(StrutAreaRight); region += strutRect(StrutAreaBottom); region += strutRect(StrutAreaLeft); return region; } bool Client::hasStrut() const { NETExtendedStrut ext = strut(); if (ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0) return false; return true; } bool Client::hasOffscreenXineramaStrut() const { // Get strut as a QRegion QRegion region; region += strutRect(StrutAreaTop); region += strutRect(StrutAreaRight); region += strutRect(StrutAreaBottom); region += strutRect(StrutAreaLeft); // Remove all visible areas so that only the invisible remain int numScreens = Kephal::ScreenUtils::numScreens(); for (int i = 0; i < numScreens; i ++) region -= Kephal::ScreenUtils::screenGeometry(i); // If there's anything left then we have an offscreen strut return !region.isEmpty(); } void Client::checkWorkspacePosition(QRect oldGeometry, int oldDesktop) { if( !oldGeometry.isValid()) oldGeometry = geometry(); if( oldDesktop == -2 ) oldDesktop = desktop(); if (isDesktop()) return; if (isFullScreen()) { QRect area = workspace()->clientArea(FullScreenArea, this); if (geometry() != area) setGeometry(area); return; } if (isDock()) return; if (maximizeMode() != MaximizeRestore) { // TODO update geom_restore? changeMaximize(false, false, true); // adjust size const QRect screenArea = workspace()->clientArea(ScreenArea, this); QRect geom = geometry(); checkOffscreenPosition(&geom, screenArea); setGeometry(geom); return; } if (quick_tile_mode != QuickTileNone) { setGeometry(electricBorderMaximizeGeometry(geometry().center(), desktop())); return; } if (!isShade()) { // TODO // this can be true only if this window was mapped before KWin // was started - in such case, don't adjust position to workarea, // because the window already had its position, and if a window // with a strut altering the workarea would be managed in initialization // after this one, this window would be moved if (workspace()->initializing()) return; // If the window was touching an edge before but not now move it so it is again. // Old and new maximums have different starting values so windows on the screen // edge will move when a new strut is placed on the edge. QRect oldScreenArea; QRect oldGeomTall; QRect oldGeomWide; if( workspace()->inUpdateClientArea()) { // we need to find the screen area as it was before the change oldScreenArea = QRect( 0, 0, workspace()->oldDisplayWidth(), workspace()->oldDisplayHeight()); oldGeomTall = QRect(oldGeometry.x(), 0, oldGeometry.width(), workspace()->oldDisplayHeight()); // Full screen height oldGeomWide = QRect(0, oldGeometry.y(), workspace()->oldDisplayWidth(), oldGeometry.height()); // Full screen width int distance = INT_MAX; foreach( QRect r, workspace()->previousScreenSizes()) { int d = r.contains( oldGeometry.center()) ? 0 : ( r.center() - oldGeometry.center()).manhattanLength(); if( d < distance ) { distance = d; oldScreenArea = r; } } } else { oldScreenArea = workspace()->clientArea(ScreenArea, oldGeometry.center(), oldDesktop); oldGeomTall = QRect(oldGeometry.x(), 0, oldGeometry.width(), displayHeight()); // Full screen height oldGeomWide = QRect(0, oldGeometry.y(), displayWidth(), oldGeometry.height()); // Full screen width } int oldTopMax = oldScreenArea.y(); int oldRightMax = oldScreenArea.x() + oldScreenArea.width(); int oldBottomMax = oldScreenArea.y() + oldScreenArea.height(); int oldLeftMax = oldScreenArea.x(); const QRect screenArea = workspace()->clientArea(ScreenArea, this); int topMax = screenArea.y(); int rightMax = screenArea.x() + screenArea.width(); int bottomMax = screenArea.y() + screenArea.height(); int leftMax = screenArea.x(); QRect newGeom = geometry(); const QRect newGeomTall = QRect(newGeom.x(), 0, newGeom.width(), displayHeight()); // Full screen height const QRect newGeomWide = QRect(0, newGeom.y(), displayWidth(), newGeom.height()); // Full screen width // Get the max strut point for each side where the window is (E.g. Highest point for // the bottom struts bounded by the window's left and right sides). if( workspace()->inUpdateClientArea()) { // These 4 compute old bounds when the restricted areas themselves changed (Workspace::updateClientArea()) foreach (const QRect & r, workspace()->previousRestrictedMoveArea(oldDesktop, StrutAreaTop).rects()) { QRect rect = r & oldGeomTall; if (!rect.isEmpty()) oldTopMax = qMax(oldTopMax, rect.y() + rect.height()); } foreach (const QRect & r, workspace()->previousRestrictedMoveArea(oldDesktop, StrutAreaRight).rects()) { QRect rect = r & oldGeomWide; if (!rect.isEmpty()) oldRightMax = qMin(oldRightMax, rect.x()); } foreach (const QRect & r, workspace()->previousRestrictedMoveArea(oldDesktop, StrutAreaBottom).rects()) { QRect rect = r & oldGeomTall; if (!rect.isEmpty()) oldBottomMax = qMin(oldBottomMax, rect.y()); } foreach (const QRect & r, workspace()->previousRestrictedMoveArea(oldDesktop, StrutAreaLeft).rects()) { QRect rect = r & oldGeomWide; if (!rect.isEmpty()) oldLeftMax = qMax(oldLeftMax, rect.x() + rect.width()); } } else { // These 4 compute old bounds when e.g. active desktop or screen changes foreach (const QRect & r, workspace()->restrictedMoveArea(oldDesktop, StrutAreaTop).rects()) { QRect rect = r & oldGeomTall; if (!rect.isEmpty()) oldTopMax = qMax(oldTopMax, rect.y() + rect.height()); } foreach (const QRect & r, workspace()->restrictedMoveArea(oldDesktop, StrutAreaRight).rects()) { QRect rect = r & oldGeomWide; if (!rect.isEmpty()) oldRightMax = qMin(oldRightMax, rect.x()); } foreach (const QRect & r, workspace()->restrictedMoveArea(oldDesktop, StrutAreaBottom).rects()) { QRect rect = r & oldGeomTall; if (!rect.isEmpty()) oldBottomMax = qMin(oldBottomMax, rect.y()); } foreach (const QRect & r, workspace()->restrictedMoveArea(oldDesktop, StrutAreaLeft).rects()) { QRect rect = r & oldGeomWide; if (!rect.isEmpty()) oldLeftMax = qMax(oldLeftMax, rect.x() + rect.width()); } } // These 4 compute new bounds foreach (const QRect & r, workspace()->restrictedMoveArea(desktop(), StrutAreaTop).rects()) { QRect rect = r & newGeomTall; if (!rect.isEmpty()) topMax = qMax(topMax, rect.y() + rect.height()); } foreach (const QRect & r, workspace()->restrictedMoveArea(desktop(), StrutAreaRight).rects()) { QRect rect = r & newGeomWide; if (!rect.isEmpty()) rightMax = qMin(rightMax, rect.x()); } foreach (const QRect & r, workspace()->restrictedMoveArea(desktop(), StrutAreaBottom).rects()) { QRect rect = r & newGeomTall; if (!rect.isEmpty()) bottomMax = qMin(bottomMax, rect.y()); } foreach (const QRect & r, workspace()->restrictedMoveArea(desktop(), StrutAreaLeft).rects()) { QRect rect = r & newGeomWide; if (!rect.isEmpty()) leftMax = qMax(leftMax, rect.x() + rect.width()); } // Check if the sides were inside or touching but are no longer if ((oldGeometry.y() >= oldTopMax && newGeom.y() < topMax) || (oldGeometry.y() == oldTopMax && newGeom.y() != topMax)) { // Top was inside or touching before but isn't anymore newGeom.moveTop(qMax(topMax, screenArea.y())); } if ((oldGeometry.y() + oldGeometry.height() <= oldBottomMax && newGeom.y() + newGeom.height() > bottomMax) || (oldGeometry.y() + oldGeometry.height() == oldBottomMax && newGeom.y() + newGeom.height() != bottomMax)) { // Bottom was inside or touching before but isn't anymore newGeom.moveBottom(qMin(bottomMax - 1, screenArea.bottom())); // If the other side was inside make sure it still is afterwards (shrink appropriately) if (oldGeometry.y() >= oldTopMax && newGeom.y() < topMax) newGeom.setTop(qMax(topMax, screenArea.y())); } if ((oldGeometry.x() >= oldLeftMax && newGeom.x() < leftMax) || (oldGeometry.x() == oldLeftMax && newGeom.x() != leftMax)) { // Left was inside or touching before but isn't anymore newGeom.moveLeft(qMax(leftMax, screenArea.x())); } if ((oldGeometry.x() + oldGeometry.width() <= oldRightMax && newGeom.x() + newGeom.width() > rightMax) || (oldGeometry.x() + oldGeometry.width() == oldRightMax && newGeom.x() + newGeom.width() != rightMax)) { // Right was inside or touching before but isn't anymore newGeom.moveRight(qMin(rightMax - 1, screenArea.right())); // If the other side was inside make sure it still is afterwards (shrink appropriately) if (oldGeometry.x() >= oldLeftMax && newGeom.x() < leftMax) newGeom.moveRight(qMin(rightMax - 1, screenArea.right())); } checkOffscreenPosition(&newGeom, screenArea); // Obey size hints. TODO: We really should make sure it stays in the right place newGeom.setSize(adjustedSize(newGeom.size())); if (newGeom != geometry()) setGeometry(newGeom); } } void Client::checkOffscreenPosition(QRect* geom, const QRect& screenArea) { if (geom->x() > screenArea.right()) { int screenWidth = screenArea.width(); geom->moveLeft(screenWidth - (screenWidth / 4)); } if (geom->y() > screenArea.bottom()) { int screenHeight = screenArea.height(); geom->moveBottom(screenHeight - (screenHeight / 4)); } } /*! Adjust the frame size \a frame according to he window's size hints. */ QSize Client::adjustedSize(const QSize& frame, Sizemode mode) const { // first, get the window size for the given frame size s QSize wsize(frame.width() - (border_left + border_right), frame.height() - (border_top + border_bottom)); if (wsize.isEmpty()) wsize = QSize(1, 1); return sizeForClientSize(wsize, mode, false); } // this helper returns proper size even if the window is shaded // see also the comment in Client::setGeometry() QSize Client::adjustedSize() const { return sizeForClientSize(clientSize()); } /*! Calculate the appropriate frame size for the given client size \a wsize. \a wsize is adapted according to the window's size hints (minimum, maximum and incremental size changes). */ QSize Client::sizeForClientSize(const QSize& wsize, Sizemode mode, bool noframe) const { int w = wsize.width(); int h = wsize.height(); if (w < 1 || h < 1) { kWarning(1212) << "sizeForClientSize() with empty size!" ; kWarning(1212) << kBacktrace() ; } if (w < 1) w = 1; if (h < 1) h = 1; // basesize, minsize, maxsize, paspect and resizeinc have all values defined, // even if they're not set in flags - see getWmNormalHints() QSize min_size = tabGroup() ? tabGroup()->minSize() : minSize(); QSize max_size = tabGroup() ? tabGroup()->maxSize() : maxSize(); if (decoration != NULL) { QSize decominsize = decoration->minimumSize(); QSize border_size(border_left + border_right, border_top + border_bottom); if (border_size.width() > decominsize.width()) // just in case decominsize.setWidth(border_size.width()); if (border_size.height() > decominsize.height()) decominsize.setHeight(border_size.height()); if (decominsize.width() > min_size.width()) min_size.setWidth(decominsize.width()); if (decominsize.height() > min_size.height()) min_size.setHeight(decominsize.height()); } w = qMin(max_size.width(), w); h = qMin(max_size.height(), h); w = qMax(min_size.width(), w); h = qMax(min_size.height(), h); int w1 = w; int h1 = h; int width_inc = xSizeHint.width_inc; int height_inc = xSizeHint.height_inc; int basew_inc = xSizeHint.min_width; // see getWmNormalHints() int baseh_inc = xSizeHint.min_height; w = int((w - basew_inc) / width_inc) * width_inc + basew_inc; h = int((h - baseh_inc) / height_inc) * height_inc + baseh_inc; // code for aspect ratios based on code from FVWM /* * The math looks like this: * * minAspectX dwidth maxAspectX * ---------- <= ------- <= ---------- * minAspectY dheight maxAspectY * * If that is multiplied out, then the width and height are * invalid in the following situations: * * minAspectX * dheight > minAspectY * dwidth * maxAspectX * dheight < maxAspectY * dwidth * */ if (xSizeHint.flags & PAspect) { double min_aspect_w = xSizeHint.min_aspect.x; // use doubles, because the values can be MAX_INT double min_aspect_h = xSizeHint.min_aspect.y; // and multiplying would go wrong otherwise double max_aspect_w = xSizeHint.max_aspect.x; double max_aspect_h = xSizeHint.max_aspect.y; // According to ICCCM 4.1.2.3 PMinSize should be a fallback for PBaseSize for size increments, // but not for aspect ratio. Since this code comes from FVWM, handles both at the same time, // and I have no idea how it works, let's hope nobody relies on that. w -= xSizeHint.base_width; h -= xSizeHint.base_height; int max_width = max_size.width() - xSizeHint.base_width; int min_width = min_size.width() - xSizeHint.base_width; int max_height = max_size.height() - xSizeHint.base_height; int min_height = min_size.height() - xSizeHint.base_height; #define ASPECT_CHECK_GROW_W \ if ( min_aspect_w * h > min_aspect_h * w ) \ { \ int delta = int( min_aspect_w * h / min_aspect_h - w ) / width_inc * width_inc; \ if ( w + delta <= max_width ) \ w += delta; \ } #define ASPECT_CHECK_SHRINK_H_GROW_W \ if ( min_aspect_w * h > min_aspect_h * w ) \ { \ int delta = int( h - w * min_aspect_h / min_aspect_w ) / height_inc * height_inc; \ if ( h - delta >= min_height ) \ h -= delta; \ else \ { \ int delta = int( min_aspect_w * h / min_aspect_h - w ) / width_inc * width_inc; \ if ( w + delta <= max_width ) \ w += delta; \ } \ } #define ASPECT_CHECK_GROW_H \ if ( max_aspect_w * h < max_aspect_h * w ) \ { \ int delta = int( w * max_aspect_h / max_aspect_w - h ) / height_inc * height_inc; \ if ( h + delta <= max_height ) \ h += delta; \ } #define ASPECT_CHECK_SHRINK_W_GROW_H \ if ( max_aspect_w * h < max_aspect_h * w ) \ { \ int delta = int( w - max_aspect_w * h / max_aspect_h ) / width_inc * width_inc; \ if ( w - delta >= min_width ) \ w -= delta; \ else \ { \ int delta = int( w * max_aspect_h / max_aspect_w - h ) / height_inc * height_inc; \ if ( h + delta <= max_height ) \ h += delta; \ } \ } switch(mode) { case SizemodeAny: #if 0 // make SizemodeAny equal to SizemodeFixedW - prefer keeping fixed width, // so that changing aspect ratio to a different value and back keeps the same size (#87298) { ASPECT_CHECK_SHRINK_H_GROW_W ASPECT_CHECK_SHRINK_W_GROW_H ASPECT_CHECK_GROW_H ASPECT_CHECK_GROW_W break; } #endif case SizemodeFixedW: { // the checks are order so that attempts to modify height are first ASPECT_CHECK_GROW_H ASPECT_CHECK_SHRINK_H_GROW_W ASPECT_CHECK_SHRINK_W_GROW_H ASPECT_CHECK_GROW_W break; } case SizemodeFixedH: { ASPECT_CHECK_GROW_W ASPECT_CHECK_SHRINK_W_GROW_H ASPECT_CHECK_SHRINK_H_GROW_W ASPECT_CHECK_GROW_H break; } case SizemodeMax: { // first checks that try to shrink ASPECT_CHECK_SHRINK_H_GROW_W ASPECT_CHECK_SHRINK_W_GROW_H ASPECT_CHECK_GROW_W ASPECT_CHECK_GROW_H break; } } #undef ASPECT_CHECK_SHRINK_H_GROW_W #undef ASPECT_CHECK_SHRINK_W_GROW_H #undef ASPECT_CHECK_GROW_W #undef ASPECT_CHECK_GROW_H w += xSizeHint.base_width; h += xSizeHint.base_height; } if (!rules()->checkStrictGeometry(true)) { // disobey increments and aspect by explicit rule w = w1; h = h1; } if (!noframe) { w += border_left + border_right; h += border_top + border_bottom; } return rules()->checkSize(QSize(w, h)); } /*! Gets the client's normal WM hints and reconfigures itself respectively. */ void Client::getWmNormalHints() { long msize; if (XGetWMNormalHints(display(), window(), &xSizeHint, &msize) == 0) xSizeHint.flags = 0; // set defined values for the fields, even if they're not in flags if (!(xSizeHint.flags & PMinSize)) xSizeHint.min_width = xSizeHint.min_height = 0; if (xSizeHint.flags & PBaseSize) { // PBaseSize is a fallback for PMinSize according to ICCCM 4.1.2.3 // The other way around PMinSize is not a complete fallback for PBaseSize, // so that's not handled here. if (!(xSizeHint.flags & PMinSize)) { xSizeHint.min_width = xSizeHint.base_width; xSizeHint.min_height = xSizeHint.base_height; } } else xSizeHint.base_width = xSizeHint.base_height = 0; if (!(xSizeHint.flags & PMaxSize)) xSizeHint.max_width = xSizeHint.max_height = INT_MAX; else { xSizeHint.max_width = qMax(xSizeHint.max_width, 1); xSizeHint.max_height = qMax(xSizeHint.max_height, 1); } if (xSizeHint.flags & PResizeInc) { xSizeHint.width_inc = qMax(xSizeHint.width_inc, 1); xSizeHint.height_inc = qMax(xSizeHint.height_inc, 1); } else { xSizeHint.width_inc = 1; xSizeHint.height_inc = 1; } if (xSizeHint.flags & PAspect) { // no dividing by zero xSizeHint.min_aspect.y = qMax(xSizeHint.min_aspect.y, 1); xSizeHint.max_aspect.y = qMax(xSizeHint.max_aspect.y, 1); } else { xSizeHint.min_aspect.x = 1; xSizeHint.min_aspect.y = INT_MAX; xSizeHint.max_aspect.x = INT_MAX; xSizeHint.max_aspect.y = 1; } if (!(xSizeHint.flags & PWinGravity)) xSizeHint.win_gravity = NorthWestGravity; // Update min/max size of this group if (tabGroup()) tabGroup()->updateMinMaxSize(); if (isManaged()) { // update to match restrictions QSize new_size = adjustedSize(); if (new_size != size() && !isFullScreen()) { QRect orig_geometry = geometry(); resizeWithChecks(new_size); if ((!isSpecialWindow() || isToolbar()) && !isFullScreen()) { // try to keep the window in its xinerama screen if possible, // if that fails at least keep it visible somewhere QRect area = workspace()->clientArea(MovementArea, this); if (area.contains(orig_geometry)) keepInArea(area); area = workspace()->clientArea(WorkArea, this); if (area.contains(orig_geometry)) keepInArea(area); } } } updateAllowedActions(); // affects isResizeable() } QSize Client::minSize() const { return rules()->checkMinSize(QSize(xSizeHint.min_width, xSizeHint.min_height)); } QSize Client::maxSize() const { return rules()->checkMaxSize(QSize(xSizeHint.max_width, xSizeHint.max_height)); } QSize Client::basicUnit() const { return QSize(xSizeHint.width_inc, xSizeHint.height_inc); } /*! Auxiliary function to inform the client about the current window configuration. */ void Client::sendSyntheticConfigureNotify() { XConfigureEvent c; c.type = ConfigureNotify; c.send_event = True; c.event = window(); c.window = window(); c.x = x() + clientPos().x(); c.y = y() + clientPos().y(); c.width = clientSize().width(); c.height = clientSize().height(); c.border_width = 0; c.above = None; c.override_redirect = 0; XSendEvent(display(), c.event, true, StructureNotifyMask, (XEvent*)&c); } const QPoint Client::calculateGravitation(bool invert, int gravity) const { int dx, dy; dx = dy = 0; if (gravity == 0) // default (nonsense) value for the argument gravity = xSizeHint.win_gravity; // dx, dy specify how the client window moves to make space for the frame switch(gravity) { case NorthWestGravity: // move down right default: dx = border_left; dy = border_top; break; case NorthGravity: // move right dx = 0; dy = border_top; break; case NorthEastGravity: // move down left dx = -border_right; dy = border_top; break; case WestGravity: // move right dx = border_left; dy = 0; break; case CenterGravity: break; // will be handled specially case StaticGravity: // don't move dx = 0; dy = 0; break; case EastGravity: // move left dx = -border_right; dy = 0; break; case SouthWestGravity: // move up right dx = border_left ; dy = -border_bottom; break; case SouthGravity: // move up dx = 0; dy = -border_bottom; break; case SouthEastGravity: // move up left dx = -border_right; dy = -border_bottom; break; } if (gravity != CenterGravity) { // translate from client movement to frame movement dx -= border_left; dy -= border_top; } else { // center of the frame will be at the same position client center without frame would be dx = - (border_left + border_right) / 2; dy = - (border_top + border_bottom) / 2; } if (!invert) return QPoint(x() + dx, y() + dy); else return QPoint(x() - dx, y() - dy); } void Client::configureRequest(int value_mask, int rx, int ry, int rw, int rh, int gravity, bool from_tool) { // "maximized" is a user setting -> we do not allow the client to resize itself // away from this & against the users explicit wish kDebug(1212) << this << bool(value_mask & (CWX|CWWidth|CWY|CWHeight)) << bool(maximizeMode() & MaximizeVertical) << bool(maximizeMode() & MaximizeHorizontal); if (maximizeMode() & MaximizeVertical) value_mask &= ~(CWY|CWHeight); // do not allow clients to drop out of vertical ... if (maximizeMode() & MaximizeHorizontal) value_mask &= ~(CWX|CWWidth); // .. or horizontal maximization (MaximizeFull == MaximizeVertical|MaximizeHorizontal) if (!(value_mask & (CWX|CWWidth|CWY|CWHeight))) { kDebug(1212) << "DENIED"; return; // nothing to (left) to do for use - bugs #158974, #252314 } kDebug(1212) << "PERMITTED" << this << bool(value_mask & (CWX|CWWidth|CWY|CWHeight)); if (gravity == 0) // default (nonsense) value for the argument gravity = xSizeHint.win_gravity; if (value_mask & (CWX | CWY)) { QPoint new_pos = calculateGravitation(true, gravity); // undo gravitation if (value_mask & CWX) new_pos.setX(rx); if (value_mask & CWY) new_pos.setY(ry); // clever(?) workaround for applications like xv that want to set // the location to the current location but miscalculate the // frame size due to kwin being a double-reparenting window // manager if (new_pos.x() == x() + clientPos().x() && new_pos.y() == y() + clientPos().y() && gravity == NorthWestGravity && !from_tool) { new_pos.setX(x()); new_pos.setY(y()); } int nw = clientSize().width(); int nh = clientSize().height(); if (value_mask & CWWidth) nw = rw; if (value_mask & CWHeight) nh = rh; QSize ns = sizeForClientSize(QSize(nw, nh)); // enforces size if needed new_pos = rules()->checkPosition(new_pos); QRect orig_geometry = geometry(); GeometryUpdatesBlocker blocker(this); move(new_pos); plainResize(ns); setGeometry(QRect(calculateGravitation(false, gravity), size())); updateFullScreenHack(QRect(new_pos, QSize(nw, nh))); QRect area = workspace()->clientArea(WorkArea, this); if (!from_tool && (!isSpecialWindow() || isToolbar()) && !isFullScreen() && area.contains(orig_geometry)) keepInArea(area); // this is part of the kicker-xinerama-hack... it should be // safe to remove when kicker gets proper ExtendedStrut support; // see Workspace::updateClientArea() and // Client::adjustedClientArea() if (hasStrut()) workspace() -> updateClientArea(); } if (value_mask & (CWWidth | CWHeight) && !(value_mask & (CWX | CWY))) { // pure resize int nw = clientSize().width(); int nh = clientSize().height(); if (value_mask & CWWidth) nw = rw; if (value_mask & CWHeight) nh = rh; QSize ns = sizeForClientSize(QSize(nw, nh)); if (ns != size()) { // don't restore if some app sets its own size again QRect orig_geometry = geometry(); GeometryUpdatesBlocker blocker(this); int save_gravity = xSizeHint.win_gravity; xSizeHint.win_gravity = gravity; resizeWithChecks(ns); xSizeHint.win_gravity = save_gravity; updateFullScreenHack(QRect(calculateGravitation(true, xSizeHint.win_gravity), QSize(nw, nh))); if (!from_tool && (!isSpecialWindow() || isToolbar()) && !isFullScreen()) { // try to keep the window in its xinerama screen if possible, // if that fails at least keep it visible somewhere QRect area = workspace()->clientArea(MovementArea, this); if (area.contains(orig_geometry)) keepInArea(area); area = workspace()->clientArea(WorkArea, this); if (area.contains(orig_geometry)) keepInArea(area); } } } // No need to send synthetic configure notify event here, either it's sent together // with geometry change, or there's no need to send it. // Handling of the real ConfigureRequest event forces sending it, as there it's necessary. } void Client::resizeWithChecks(int w, int h, ForceGeometry_t force) { assert(!shade_geometry_change); if (isShade()) { if (h == border_top + border_bottom) { kWarning(1212) << "Shaded geometry passed for size:" ; kWarning(1212) << kBacktrace() ; } } int newx = x(); int newy = y(); QRect area = workspace()->clientArea(WorkArea, this); // don't allow growing larger than workarea if (w > area.width()) w = area.width(); if (h > area.height()) h = area.height(); QSize tmp = adjustedSize(QSize(w, h)); // checks size constraints, including min/max size w = tmp.width(); h = tmp.height(); switch(xSizeHint.win_gravity) { case NorthWestGravity: // top left corner doesn't move default: break; case NorthGravity: // middle of top border doesn't move newx = (newx + width() / 2) - (w / 2); break; case NorthEastGravity: // top right corner doesn't move newx = newx + width() - w; break; case WestGravity: // middle of left border doesn't move newy = (newy + height() / 2) - (h / 2); break; case CenterGravity: // middle point doesn't move newx = (newx + width() / 2) - (w / 2); newy = (newy + height() / 2) - (h / 2); break; case StaticGravity: // top left corner of _client_ window doesn't move // since decoration doesn't change, equal to NorthWestGravity break; case EastGravity: // // middle of right border doesn't move newx = newx + width() - w; newy = (newy + height() / 2) - (h / 2); break; case SouthWestGravity: // bottom left corner doesn't move newy = newy + height() - h; break; case SouthGravity: // middle of bottom border doesn't move newx = (newx + width() / 2) - (w / 2); newy = newy + height() - h; break; case SouthEastGravity: // bottom right corner doesn't move newx = newx + width() - w; newy = newy + height() - h; break; } setGeometry(newx, newy, w, h, force); } // _NET_MOVERESIZE_WINDOW void Client::NETMoveResizeWindow(int flags, int x, int y, int width, int height) { int gravity = flags & 0xff; int value_mask = 0; if (flags & (1 << 8)) value_mask |= CWX; if (flags & (1 << 9)) value_mask |= CWY; if (flags & (1 << 10)) value_mask |= CWWidth; if (flags & (1 << 11)) value_mask |= CWHeight; configureRequest(value_mask, x, y, width, height, gravity, true); } /*! Returns whether the window is moveable or has a fixed position. */ bool Client::isMovable() const { if (!motif_may_move || isFullScreen()) return false; if (isSpecialWindow() && !isSplash() && !isToolbar()) // allow moving of splashscreens :) return false; if (maximizeMode() == MaximizeFull && !options->moveResizeMaximizedWindows()) return false; if (rules()->checkPosition(invalidPoint) != invalidPoint) // forced position return false; return true; } /*! Returns whether the window is moveable across Xinerama screens */ bool Client::isMovableAcrossScreens() const { if (!motif_may_move) return false; if (isSpecialWindow() && !isSplash() && !isToolbar()) // allow moving of splashscreens :) return false; if (rules()->checkPosition(invalidPoint) != invalidPoint) // forced position return false; return true; } /*! Returns whether the window is resizable or has a fixed size. */ bool Client::isResizable() const { if (!motif_may_resize || isFullScreen()) return false; if (isSpecialWindow() || isSplash() || isToolbar()) return false; if (maximizeMode() == MaximizeFull && !options->moveResizeMaximizedWindows()) return isMove(); // for quick tiling - maxmode will be unset if we tile if (rules()->checkSize(QSize()).isValid()) // forced size return false; QSize min = tabGroup() ? tabGroup()->minSize() : minSize(); QSize max = tabGroup() ? tabGroup()->maxSize() : maxSize(); return min.width() < max.width() || min.height() < max.height(); } /* Returns whether the window is maximizable or not */ bool Client::isMaximizable() const { { // isMovable() and isResizable() may be false for maximized windows // with moving/resizing maximized windows disabled TemporaryAssign< MaximizeMode > tmp(max_mode, MaximizeRestore); if (!isMovable() || !isResizable() || isToolbar()) // SELI isToolbar() ? return false; } if (rules()->checkMaximize(MaximizeRestore) == MaximizeRestore && rules()->checkMaximize(MaximizeFull) != MaximizeRestore) return true; return false; } /*! Reimplemented to inform the client about the new window position. */ void Client::setGeometry(int x, int y, int w, int h, ForceGeometry_t force) { // this code is also duplicated in Client::plainResize() // Ok, the shading geometry stuff. Generally, code doesn't care about shaded geometry, // simply because there are too many places dealing with geometry. Those places // ignore shaded state and use normal geometry, which they usually should get // from adjustedSize(). Such geometry comes here, and if the window is shaded, // the geometry is used only for client_size, since that one is not used when // shading. Then the frame geometry is adjusted for the shaded geometry. // This gets more complicated in the case the code does only something like // setGeometry( geometry()) - geometry() will return the shaded frame geometry. // Such code is wrong and should be changed to handle the case when the window is shaded, // for example using Client::clientSize() if (shade_geometry_change) ; // nothing else if (isShade()) { if (h == border_top + border_bottom) { kDebug(1212) << "Shaded geometry passed for size:"; kDebug(1212) << kBacktrace(); } else { client_size = QSize(w - border_left - border_right, h - border_top - border_bottom); h = border_top + border_bottom; } } else { client_size = QSize(w - border_left - border_right, h - border_top - border_bottom); } QRect g(x, y, w, h); if (block_geometry_updates == 0 && g != rules()->checkGeometry(g)) { kDebug(1212) << "forced geometry fail:" << g << ":" << rules()->checkGeometry(g); kDebug(1212) << kBacktrace(); } if (force == NormalGeometrySet && geom == g && pending_geometry_update == PendingGeometryNone) return; geom = g; if (block_geometry_updates != 0) { if (pending_geometry_update == PendingGeometryForced) {} // maximum, nothing needed else if (force == ForceGeometrySet) pending_geometry_update = PendingGeometryForced; else pending_geometry_update = PendingGeometryNormal; return; } bool resized = (geom_before_block.size() != geom.size() || pending_geometry_update == PendingGeometryForced); if (resized) { resizeDecoration(QSize(w, h)); XMoveResizeWindow(display(), frameId(), x, y, w, h); if (!isShade()) { QSize cs = clientSize(); XMoveResizeWindow(display(), wrapperId(), clientPos().x(), clientPos().y(), cs.width(), cs.height()); #ifdef HAVE_XSYNC if (!isResize() || syncRequest.counter == None) #endif XMoveResizeWindow(display(), window(), 0, 0, cs.width(), cs.height()); } updateShape(); } else { XMoveWindow(display(), frameId(), x, y); if (inputId()) { const QPoint pos = QPoint(x, y) + inputPos(); XMoveWindow(display(), inputId(), pos.x(), pos.y()); } } // SELI TODO won't this be too expensive? sendSyntheticConfigureNotify(); updateWindowRules(Rules::Position|Rules::Size); // keep track of old maximize mode // to detect changes workspace()->checkActiveScreen(this); workspace()->updateStackingOrder(); workspace()->checkUnredirect(); // need to regenerate decoration pixmaps when either // - size is changed // - maximize mode is changed to MaximizeRestore, when size unchanged // which can happen when untabbing maximized windows if (resized) { discardWindowPixmap(); emit geometryShapeChanged(this, geom_before_block); } const QRect deco_rect = decorationRect().translated(geom.x(), geom.y()); - addWorkspaceRepaint(deco_rect_before_block); - addWorkspaceRepaint(deco_rect); + addLayerRepaint(deco_rect_before_block); + addLayerRepaint(deco_rect); geom_before_block = geom; deco_rect_before_block = deco_rect; // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this); // TODO: this signal is emitted too often emit geometryChanged(); } void Client::plainResize(int w, int h, ForceGeometry_t force) { // this code is also duplicated in Client::setGeometry(), and it's also commented there if (shade_geometry_change) ; // nothing else if (isShade()) { if (h == border_top + border_bottom) { kDebug(1212) << "Shaded geometry passed for size:"; kDebug(1212) << kBacktrace(); } else { client_size = QSize(w - border_left - border_right, h - border_top - border_bottom); h = border_top + border_bottom; } } else { client_size = QSize(w - border_left - border_right, h - border_top - border_bottom); } QSize s(w, h); if (block_geometry_updates == 0 && s != rules()->checkSize(s)) { kDebug(1212) << "forced size fail:" << s << ":" << rules()->checkSize(s); kDebug(1212) << kBacktrace(); } // resuming geometry updates is handled only in setGeometry() assert(pending_geometry_update == PendingGeometryNone || block_geometry_updates > 0); if (force == NormalGeometrySet && geom.size() == s) return; geom.setSize(s); if (block_geometry_updates != 0) { if (pending_geometry_update == PendingGeometryForced) {} // maximum, nothing needed else if (force == ForceGeometrySet) pending_geometry_update = PendingGeometryForced; else pending_geometry_update = PendingGeometryNormal; return; } resizeDecoration(s); XResizeWindow(display(), frameId(), w, h); // resizeDecoration( s ); if (!isShade()) { QSize cs = clientSize(); XMoveResizeWindow(display(), wrapperId(), clientPos().x(), clientPos().y(), cs.width(), cs.height()); XMoveResizeWindow(display(), window(), 0, 0, cs.width(), cs.height()); } updateShape(); sendSyntheticConfigureNotify(); updateWindowRules(Rules::Position|Rules::Size); workspace()->checkActiveScreen(this); workspace()->updateStackingOrder(); workspace()->checkUnredirect(); discardWindowPixmap(); emit geometryShapeChanged(this, geom_before_block); const QRect deco_rect = decorationRect().translated(geom.x(), geom.y()); - addWorkspaceRepaint(deco_rect_before_block); - addWorkspaceRepaint(deco_rect); + addLayerRepaint(deco_rect_before_block); + addLayerRepaint(deco_rect); geom_before_block = geom; deco_rect_before_block = deco_rect; // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this); // TODO: this signal is emitted too often emit geometryChanged(); } /*! Reimplemented to inform the client about the new window position. */ void Client::move(int x, int y, ForceGeometry_t force) { // resuming geometry updates is handled only in setGeometry() assert(pending_geometry_update == PendingGeometryNone || block_geometry_updates > 0); QPoint p(x, y); if (block_geometry_updates == 0 && p != rules()->checkPosition(p)) { kDebug(1212) << "forced position fail:" << p << ":" << rules()->checkPosition(p); kDebug(1212) << kBacktrace(); } if (force == NormalGeometrySet && geom.topLeft() == p) return; geom.moveTopLeft(p); if (block_geometry_updates != 0) { if (pending_geometry_update == PendingGeometryForced) {} // maximum, nothing needed else if (force == ForceGeometrySet) pending_geometry_update = PendingGeometryForced; else pending_geometry_update = PendingGeometryNormal; return; } XMoveWindow(display(), frameId(), x, y); sendSyntheticConfigureNotify(); updateWindowRules(Rules::Position); workspace()->checkActiveScreen(this); workspace()->updateStackingOrder(); workspace()->checkUnredirect(); #ifdef KWIN_BUILD_TILING workspace()->tiling()->notifyTilingWindowMove(this, moveResizeGeom, initialMoveResizeGeom); #endif // client itself is not damaged const QRect deco_rect = decorationRect().translated(geom.x(), geom.y()); - addWorkspaceRepaint(deco_rect_before_block); - addWorkspaceRepaint(deco_rect); // trigger repaint of window's new location + addLayerRepaint(deco_rect_before_block); + addLayerRepaint(deco_rect); // trigger repaint of window's new location geom_before_block = geom; deco_rect_before_block = deco_rect; // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this); } void Client::blockGeometryUpdates(bool block) { if (block) { if (block_geometry_updates == 0) pending_geometry_update = PendingGeometryNone; ++block_geometry_updates; } else { if (--block_geometry_updates == 0) { if (pending_geometry_update != PendingGeometryNone) { if (isShade()) setGeometry(QRect(pos(), adjustedSize()), NormalGeometrySet); else setGeometry(geometry(), NormalGeometrySet); pending_geometry_update = PendingGeometryNone; } } } } void Client::maximize(MaximizeMode m) { setMaximize(m & MaximizeVertical, m & MaximizeHorizontal); } /*! Sets the maximization according to \a vertically and \a horizontally */ void Client::setMaximize(bool vertically, bool horizontally) { // changeMaximize() flips the state, so change from set->flip changeMaximize( max_mode & MaximizeVertical ? !vertically : vertically, max_mode & MaximizeHorizontal ? !horizontally : horizontally, false); emit clientMaximizedStateChanged(this, max_mode); emit clientMaximizedStateChanged(this, vertically, horizontally); // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this); } static bool changeMaximizeRecursion = false; void Client::changeMaximize(bool vertical, bool horizontal, bool adjust) { if (changeMaximizeRecursion) return; { // isMovable() and isResizable() may be false for maximized windows // with moving/resizing maximized windows disabled TemporaryAssign< MaximizeMode > tmp(max_mode, MaximizeRestore); if (!isMovable() || !isResizable() || isToolbar()) // SELI isToolbar() ? return; } MaximizeMode old_mode = max_mode; // 'adjust == true' means to update the size only, e.g. after changing workspace size if (!adjust) { if (vertical) max_mode = MaximizeMode(max_mode ^ MaximizeVertical); if (horizontal) max_mode = MaximizeMode(max_mode ^ MaximizeHorizontal); } max_mode = rules()->checkMaximize(max_mode); if (!adjust && max_mode == old_mode) return; GeometryUpdatesBlocker blocker(this); // maximing one way and unmaximizing the other way shouldn't happen, // so restore first and then maximize the other way if ((old_mode == MaximizeVertical && max_mode == MaximizeHorizontal) || (old_mode == MaximizeHorizontal && max_mode == MaximizeVertical)) { changeMaximize(false, false, false); // restore } QRect clientArea; if (isElectricBorderMaximizing()) clientArea = workspace()->clientArea(MaximizeArea, cursorPos(), desktop()); else clientArea = workspace()->clientArea(MaximizeArea, this); if (options->borderlessMaximizedWindows()) { // triggers a maximize change. // The next setNoBorder interation will exit since there's no change but the first recursion pullutes the restore/pretile geometry changeMaximizeRecursion = true; setNoBorder(app_noborder || max_mode == MaximizeFull); changeMaximizeRecursion = false; } // save sizes for restoring, if maximalizing if (!adjust && !(old_mode & MaximizeVertical)) { geom_restore.setTop(y()); geom_restore.setHeight(height()); // we can fall from maximize to tiled // TODO unify quicktiling and regular maximization geom_pretile.setTop(y()); geom_pretile.setHeight(height()); } if (!adjust && !(old_mode & MaximizeHorizontal)) { geom_restore.setLeft(x()); geom_restore.setWidth(width()); // see above geom_pretile.setLeft(x()); geom_pretile.setWidth(width()); } if (!adjust) { if ((vertical && !(old_mode & MaximizeVertical)) || (horizontal && !(old_mode & MaximizeHorizontal))) Notify::raise(Notify::Maximize); else Notify::raise(Notify::UnMaximize); } ForceGeometry_t geom_mode = NormalGeometrySet; if (decoration != NULL) { // decorations may turn off some borders when maximized if (checkBorderSizes(false)) // only query, don't resize geom_mode = ForceGeometrySet; } // Conditional quick tiling exit points if (quick_tile_mode != QuickTileNone) { if (old_mode == MaximizeFull && !clientArea.contains(geom_restore.center())) { // Not restoring on the same screen // TODO: The following doesn't work for some reason //geom_restore = geom_pretile; // Restore to the pretiled geometry //quick_tile_mode = QuickTileNone; // And exit quick tile mode manually } else if ((old_mode == MaximizeVertical && max_mode == MaximizeRestore) || (old_mode == MaximizeFull && max_mode == MaximizeHorizontal)) { // Modifying geometry of a tiled window quick_tile_mode = QuickTileNone; // Exit quick tile mode without restoring geometry } } // restore partial maximizations if (old_mode == MaximizeFull && max_mode == MaximizeRestore) { if (maximizeModeRestore() == MaximizeVertical) { max_mode = MaximizeVertical; maxmode_restore = MaximizeRestore; } if (maximizeModeRestore() == MaximizeHorizontal) { max_mode = MaximizeHorizontal; maxmode_restore = MaximizeRestore; } } switch(max_mode) { case MaximizeVertical: { if (old_mode & MaximizeHorizontal) { // actually restoring from MaximizeFull if (geom_restore.width() == 0 || !clientArea.contains(geom_restore.center())) { // needs placement plainResize(adjustedSize(QSize(width() * 2 / 3, clientArea.height()), SizemodeFixedH), geom_mode); workspace()->placeSmart(this, clientArea); } else { setGeometry(QRect(QPoint(geom_restore.x(), clientArea.top()), adjustedSize(QSize(geom_restore.width(), clientArea.height()), SizemodeFixedH)), geom_mode); } } else { setGeometry(QRect(QPoint(x(), clientArea.top()), adjustedSize(QSize(width(), clientArea.height()), SizemodeFixedH)), geom_mode); } info->setState(NET::MaxVert, NET::Max); break; } case MaximizeHorizontal: { if (old_mode & MaximizeVertical) { // actually restoring from MaximizeFull if (geom_restore.height() == 0 || !clientArea.contains(geom_restore.center())) { // needs placement plainResize(adjustedSize(QSize(clientArea.width(), height() * 2 / 3), SizemodeFixedW), geom_mode); workspace()->placeSmart(this, clientArea); } else { setGeometry(QRect(QPoint(clientArea.left(), geom_restore.y()), adjustedSize(QSize(clientArea.width(), geom_restore.height()), SizemodeFixedW)), geom_mode); } } else { setGeometry(QRect(QPoint(clientArea.left(), y()), adjustedSize(QSize(clientArea.width(), height()), SizemodeFixedW)), geom_mode); } info->setState(NET::MaxHoriz, NET::Max); break; } case MaximizeRestore: { QRect restore = geometry(); // when only partially maximized, geom_restore may not have the other dimension remembered if (old_mode & MaximizeVertical) { restore.setTop(geom_restore.top()); restore.setBottom(geom_restore.bottom()); } if (old_mode & MaximizeHorizontal) { restore.setLeft(geom_restore.left()); restore.setRight(geom_restore.right()); } if (!restore.isValid()) { QSize s = QSize(clientArea.width() * 2 / 3, clientArea.height() * 2 / 3); if (geom_restore.width() > 0) s.setWidth(geom_restore.width()); if (geom_restore.height() > 0) s.setHeight(geom_restore.height()); plainResize(adjustedSize(s)); workspace()->placeSmart(this, clientArea); restore = geometry(); if (geom_restore.width() > 0) restore.moveLeft(geom_restore.x()); if (geom_restore.height() > 0) restore.moveTop(geom_restore.y()); } setGeometry(restore, geom_mode); if (!clientArea.contains(geom_restore.center())) // Not restoring to the same screen workspace()->place(this, clientArea); info->setState(0, NET::Max); break; } case MaximizeFull: { if (!adjust) { if (old_mode & MaximizeVertical) maxmode_restore = MaximizeVertical; if (old_mode & MaximizeHorizontal) maxmode_restore = MaximizeHorizontal; } QSize adjSize = adjustedSize(clientArea.size(), SizemodeMax); QRect r = QRect(clientArea.topLeft(), adjSize); if (r.size() != clientArea.size()) { // to avoid off-by-one errors... if (isElectricBorderMaximizing()) r.moveLeft(qMax(clientArea.x(), QCursor::pos().x() - r.width()/2)); else r.moveCenter(clientArea.center()); } setGeometry(r, geom_mode); info->setState(NET::Max, NET::Max); break; } default: break; } updateAllowedActions(); if (decoration != NULL) decoration->maximizeChange(); updateWindowRules(Rules::MaximizeVert|Rules::MaximizeHoriz|Rules::Position|Rules::Size); } 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); if (isShade()) setGeometry(QRect(pos(), sizeForClientSize(clientSize())), ForceGeometrySet); else setGeometry(geometry(), ForceGeometrySet); if (decoration != NULL) decoration->maximizeChange(); } bool Client::isFullScreenable(bool fullscreen_hack) const { if (!rules()->checkFullScreen(true)) return false; if (fullscreen_hack) return isNormalWindow(); if (rules()->checkStrictGeometry(false)) { // the app wouldn't fit exactly fullscreen geometry due to its strict geometry requirements QRect fsarea = workspace()->clientArea(FullScreenArea, this); if (sizeForClientSize(fsarea.size(), SizemodeAny, true) != fsarea.size()) return false; } // don't check size constrains - some apps request fullscreen despite requesting fixed size return !isSpecialWindow(); // also better disallow only weird types to go fullscreen } bool Client::userCanSetFullScreen() const { if (fullscreen_mode == FullScreenHack) return false; if (!isFullScreenable(false)) return false; // isMaximizable() returns false if fullscreen TemporaryAssign< FullScreenMode > tmp(fullscreen_mode, FullScreenNone); return isNormalWindow() && isMaximizable(); } void Client::setFullScreen(bool set, bool user) { if (!isFullScreen() && !set) return; if (fullscreen_mode == FullScreenHack) return; if (user && !userCanSetFullScreen()) return; set = rules()->checkFullScreen(set); setShade(ShadeNone); bool was_fs = isFullScreen(); if (!was_fs) geom_fs_restore = geometry(); fullscreen_mode = set ? FullScreenNormal : FullScreenNone; if (was_fs == isFullScreen()) return; if (set) workspace()->raiseClient(this); StackingUpdatesBlocker blocker1(workspace()); GeometryUpdatesBlocker blocker2(this); workspace()->updateClientLayer(this); // active fullscreens get different layer info->setState(isFullScreen() ? NET::FullScreen : 0, NET::FullScreen); updateDecoration(false, false); if (isFullScreen()) if (info->fullscreenMonitors().isSet()) setGeometry(fullscreenMonitorsArea(info->fullscreenMonitors())); else setGeometry(workspace()->clientArea(FullScreenArea, this)); else { if (!geom_fs_restore.isNull()) { int currentScreen = screen(); setGeometry(QRect(geom_fs_restore.topLeft(), adjustedSize(geom_fs_restore.size()))); if( currentScreen != screen()) workspace()->sendClientToScreen( this, currentScreen ); // TODO isShaded() ? } else { // does this ever happen? setGeometry(workspace()->clientArea(MaximizeArea, this)); } } updateWindowRules(Rules::Fullscreen|Rules::Position|Rules::Size); workspace()->checkUnredirect(); if (was_fs != isFullScreen()) { emit clientFullScreenSet(this, set, user); emit fullScreenChanged(); } } void Client::updateFullscreenMonitors(NETFullscreenMonitors topology) { int nscreens = Kephal::ScreenUtils::numScreens(); // kDebug( 1212 ) << "incoming request with top: " << topology.top << " bottom: " << topology.bottom // << " left: " << topology.left << " right: " << topology.right // << ", we have: " << nscreens << " screens."; if (topology.top >= nscreens || topology.bottom >= nscreens || topology.left >= nscreens || topology.right >= nscreens) { kWarning(1212) << "fullscreenMonitors update failed. request higher than number of screens."; return; } info->setFullscreenMonitors(topology); if (isFullScreen()) setGeometry(fullscreenMonitorsArea(topology)); } /*! Calculates the bounding rectangle defined by the 4 monitor indices indicating the top, bottom, left, and right edges of the window when the fullscreen state is enabled. */ QRect Client::fullscreenMonitorsArea(NETFullscreenMonitors requestedTopology) const { QRect top, bottom, left, right, total; top = Kephal::ScreenUtils::screenGeometry(requestedTopology.top); bottom = Kephal::ScreenUtils::screenGeometry(requestedTopology.bottom); left = Kephal::ScreenUtils::screenGeometry(requestedTopology.left); right = Kephal::ScreenUtils::screenGeometry(requestedTopology.right); total = top.united(bottom.united(left.united(right))); // kDebug( 1212 ) << "top: " << top << " bottom: " << bottom // << " left: " << left << " right: " << right; // kDebug( 1212 ) << "returning rect: " << total; return total; } int Client::checkFullScreenHack(const QRect& geom) const { if (!options->legacyFullscreenSupport) return 0; // if it's noborder window, and has size of one screen or the whole desktop geometry, it's fullscreen hack if (noBorder() && app_noborder && isFullScreenable(true)) { if (geom.size() == workspace()->clientArea(FullArea, geom.center(), desktop()).size()) return 2; // full area fullscreen hack if (geom.size() == workspace()->clientArea(ScreenArea, geom.center(), desktop()).size()) return 1; // xinerama-aware fullscreen hack } return 0; } void Client::updateFullScreenHack(const QRect& geom) { int type = checkFullScreenHack(geom); if (fullscreen_mode == FullScreenNone && type != 0) { fullscreen_mode = FullScreenHack; updateDecoration(false, false); QRect geom; if (rules()->checkStrictGeometry(false)) { geom = type == 2 // 1 - it's xinerama-aware fullscreen hack, 2 - it's full area ? workspace()->clientArea(FullArea, geom.center(), desktop()) : workspace()->clientArea(ScreenArea, geom.center(), desktop()); } else geom = workspace()->clientArea(FullScreenArea, geom.center(), desktop()); setGeometry(geom); emit fullScreenChanged(); } else if (fullscreen_mode == FullScreenHack && type == 0) { fullscreen_mode = FullScreenNone; updateDecoration(false, false); // whoever called this must setup correct geometry emit fullScreenChanged(); } StackingUpdatesBlocker blocker(workspace()); workspace()->updateClientLayer(this); // active fullscreens get different layer } static GeometryTip* geometryTip = 0; void Client::positionGeometryTip() { assert(isMove() || isResize()); // Position and Size display if (effects && static_cast(effects)->provides(Effect::GeometryTip)) return; // some effect paints this for us if (options->showGeometryTip()) { if (!geometryTip) { geometryTip = new GeometryTip(&xSizeHint, false); } QRect wgeom(moveResizeGeom); // position of the frame, size of the window itself wgeom.setWidth(wgeom.width() - (width() - clientSize().width())); wgeom.setHeight(wgeom.height() - (height() - clientSize().height())); if (isShade()) wgeom.setHeight(0); geometryTip->setGeometry(wgeom); if (!geometryTip->isVisible()) geometryTip->show(); geometryTip->raise(); } } bool Client::startMoveResize() { assert(!moveResizeMode); assert(QWidget::keyboardGrabber() == NULL); assert(QWidget::mouseGrabber() == NULL); stopDelayedMoveResize(); if (QApplication::activePopupWidget() != NULL) return false; // popups have grab bool has_grab = false; // This reportedly improves smoothness of the moveresize operation, // something with Enter/LeaveNotify events, looks like XFree performance problem or something *shrug* // (http://lists.kde.org/?t=107302193400001&r=1&w=2) XSetWindowAttributes attrs; QRect r = workspace()->clientArea(FullArea, this); move_resize_grab_window = XCreateWindow(display(), rootWindow(), r.x(), r.y(), r.width(), r.height(), 0, CopyFromParent, InputOnly, CopyFromParent, 0, &attrs); XMapRaised(display(), move_resize_grab_window); if (XGrabPointer(display(), move_resize_grab_window, False, ButtonPressMask | ButtonReleaseMask | PointerMotionMask | EnterWindowMask | LeaveWindowMask, GrabModeAsync, GrabModeAsync, move_resize_grab_window, cursor.handle(), xTime()) == Success) has_grab = true; if (grabXKeyboard(frameId())) has_grab = move_resize_has_keyboard_grab = true; if (!has_grab) { // at least one grab is necessary in order to be able to finish move/resize XDestroyWindow(display(), move_resize_grab_window); move_resize_grab_window = None; return false; } // If we have quick maximization enabled then it's safe to automatically restore windows // when starting a move as the user can undo their action by moving the window back to // the top of the screen. When the setting is disabled then doing so is confusing. if (maximizeMode() != MaximizeRestore && (maximizeMode() != MaximizeFull || options->moveResizeMaximizedWindows())) { // allow moveResize, but unset maximization state in resize case if (mode != PositionCenter) { // means "isResize()" but moveResizeMode = true is set below geom_restore = geom_pretile = geometry(); // "restore" to current geometry setMaximize(false, false); } } else if ((maximizeMode() == MaximizeFull && options->electricBorderMaximize()) || (quick_tile_mode != QuickTileNone && isMovable() && mode == PositionCenter)) { // Exit quick tile mode when the user attempts to move a tiled window, cannot use isMove() yet const QRect before = geometry(); setQuickTileMode(QuickTileNone); // Move the window so it's under the cursor moveOffset = QPoint(double(moveOffset.x()) / double(before.width()) * double(geom_restore.width()), double(moveOffset.y()) / double(before.height()) * double(geom_restore.height())); } if (quick_tile_mode != QuickTileNone && mode != PositionCenter) { // Cannot use isResize() yet // Exit quick tile mode when the user attempts to resize a tiled window quick_tile_mode = QuickTileNone; // Do so without restoring original geometry } moveResizeMode = true; s_haveResizeEffect = effects && static_cast(effects)->provides(Effect::Resize); moveResizeStartScreen = screen(); workspace()->setClientIsMoving(this); initialMoveResizeGeom = moveResizeGeom = geometry(); checkUnrestrictedMoveResize(); Notify::raise(isResize() ? Notify::ResizeStart : Notify::MoveStart); emit clientStartUserMovedResized(this); #ifdef KWIN_BUILD_SCREENEDGES if (options->electricBorders() == Options::ElectricMoveOnly || options->electricBorderMaximize() || options->electricBorderTiling()) workspace()->screenEdge()->reserveDesktopSwitching(true); #endif return true; } static ElectricBorder electricBorderFromMode(QuickTileMode mode) { // special case, currently maxmizing is done from the electric top corner if (mode == QuickTileMaximize) return ElectricTop; // sanitize the mode, ie. simplify "invalid" combinations if ((mode & QuickTileHorizontal) == QuickTileHorizontal) mode &= ~QuickTileHorizontal; if ((mode & QuickTileVertical) == QuickTileVertical) mode &= ~QuickTileVertical; if (mode == QuickTileLeft) return ElectricLeft; if (mode == QuickTileRight) return ElectricRight; if (mode == (QuickTileTop|QuickTileLeft)) return ElectricTopLeft; if (mode == (QuickTileTop|QuickTileRight)) return ElectricTopRight; if (mode == (QuickTileBottom|QuickTileLeft)) return ElectricBottomLeft; if (mode == (QuickTileBottom|QuickTileRight)) return ElectricBottomRight; if (mode == QuickTileTop) return ElectricTop; if (mode == QuickTileBottom) return ElectricBottom; return ElectricNone; } void Client::finishMoveResize(bool cancel) { // store for notification bool wasResize = isResize(); bool wasMove = isMove(); leaveMoveResize(); #ifdef KWIN_BUILD_TILING if (workspace()->tiling()->isEnabled()) { if (wasResize) workspace()->tiling()->notifyTilingWindowResizeDone(this, moveResizeGeom, initialMoveResizeGeom, cancel); else if (wasMove) workspace()->tiling()->notifyTilingWindowMoveDone(this, moveResizeGeom, initialMoveResizeGeom, cancel); } else { if (cancel) setGeometry(initialMoveResizeGeom); else setGeometry(moveResizeGeom); if (screen() != moveResizeStartScreen && maximizeMode() != MaximizeRestore) checkWorkspacePosition(); } #else if (cancel) setGeometry(initialMoveResizeGeom); else setGeometry(moveResizeGeom); Q_UNUSED(wasResize); Q_UNUSED(wasMove); #endif if (cancel) // TODO: this looks like a patch bug - tiling gets the variable and non-tiling acts above setGeometry(initialMoveResizeGeom); if (isElectricBorderMaximizing()) { cancel = true; setQuickTileMode(electricMode); const ElectricBorder border = electricBorderFromMode(electricMode); if (border == ElectricNone) kDebug(1212) << "invalid electric mode" << electricMode << "leading to invalid array access,\ this should not have happened!"; #ifdef KWIN_BUILD_SCREENEDGES else workspace()->screenEdge()->restoreSize(border); #endif electricMaximizing = false; workspace()->outline()->hide(); } // FRAME update(); Notify::raise(isResize() ? Notify::ResizeEnd : Notify::MoveEnd); emit clientFinishUserMovedResized(this); } void Client::leaveMoveResize() { if (geometryTip) { geometryTip->hide(); delete geometryTip; geometryTip = NULL; } if (move_resize_has_keyboard_grab) ungrabXKeyboard(); move_resize_has_keyboard_grab = false; XUngrabPointer(display(), xTime()); XDestroyWindow(display(), move_resize_grab_window); move_resize_grab_window = None; workspace()->setClientIsMoving(0); moveResizeMode = false; delete syncRequest.timeout; syncRequest.timeout = NULL; #ifdef KWIN_BUILD_SCREENEDGES if (options->electricBorders() == Options::ElectricMoveOnly || options->electricBorderMaximize() || options->electricBorderTiling()) workspace()->screenEdge()->reserveDesktopSwitching(false); #endif } // This function checks if it actually makes sense to perform a restricted move/resize. // If e.g. the titlebar is already outside of the workarea, there's no point in performing // a restricted move resize, because then e.g. resize would also move the window (#74555). // NOTE: Most of it is duplicated from handleMoveResize(). void Client::checkUnrestrictedMoveResize() { if (unrestrictedMoveResize) return; QRect desktopArea = workspace()->clientArea(WorkArea, moveResizeGeom.center(), desktop()); int left_marge, right_marge, top_marge, bottom_marge, titlebar_marge; // restricted move/resize - keep at least part of the titlebar always visible // how much must remain visible when moved away in that direction left_marge = qMin(100 + border_right, moveResizeGeom.width()); right_marge = qMin(100 + border_left, moveResizeGeom.width()); // width/height change with opaque resizing, use the initial ones titlebar_marge = initialMoveResizeGeom.height(); top_marge = border_bottom; bottom_marge = border_top; if (isResize()) { if (moveResizeGeom.bottom() < desktopArea.top() + top_marge) unrestrictedMoveResize = true; if (moveResizeGeom.top() > desktopArea.bottom() - bottom_marge) unrestrictedMoveResize = true; if (moveResizeGeom.right() < desktopArea.left() + left_marge) unrestrictedMoveResize = true; if (moveResizeGeom.left() > desktopArea.right() - right_marge) unrestrictedMoveResize = true; if (!unrestrictedMoveResize && moveResizeGeom.top() < desktopArea.top()) // titlebar mustn't go out unrestrictedMoveResize = true; } if (isMove()) { if (moveResizeGeom.bottom() < desktopArea.top() + titlebar_marge - 1) // 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; } } // When the user pressed mouse on the titlebar, don't activate move immediatelly, // since it may be just a click. Activate instead after a delay. Move used to be // activated only after moving by several pixels, but that looks bad. void Client::startDelayedMoveResize() { delete delayedMoveResizeTimer; delayedMoveResizeTimer = new QTimer(this); connect(delayedMoveResizeTimer, SIGNAL(timeout()), this, SLOT(delayedMoveResize())); delayedMoveResizeTimer->setSingleShot(true); delayedMoveResizeTimer->start(QApplication::startDragTime()); } void Client::stopDelayedMoveResize() { delete delayedMoveResizeTimer; delayedMoveResizeTimer = NULL; } void Client::delayedMoveResize() { assert(buttonDown); if (!startMoveResize()) buttonDown = false; updateCursor(); stopDelayedMoveResize(); } void Client::handleMoveResize(int x, int y, int x_root, int y_root) { if (syncRequest.isPending && isResize()) return; // we're still waiting for the client or the timeout if ((mode == PositionCenter && !isMovableAcrossScreens()) || (mode != PositionCenter && (isShade() || !isResizable()))) return; if (!moveResizeMode) { QPoint p(QPoint(x - padding_left, y - padding_top) - moveOffset); if (p.manhattanLength() >= KGlobalSettings::dndEventDelay()) { if (!startMoveResize()) { buttonDown = false; updateCursor(); return; } updateCursor(); } else return; } // ShadeHover or ShadeActive, ShadeNormal was already avoided above if (mode != PositionCenter && shade_mode != ShadeNone) setShade(ShadeNone); QPoint globalPos(x_root, y_root); // these two points limit the geometry rectangle, i.e. if bottomleft resizing is done, // the bottomleft corner should be at is at (topleft.x(), bottomright().y()) QPoint topleft = globalPos - moveOffset; QPoint bottomright = globalPos + invertedMoveOffset; QRect previousMoveResizeGeom = moveResizeGeom; // TODO move whole group when moving its leader or when the leader is not mapped? // When doing a restricted move we must always keep 100px of the titlebar // visible to allow the user to be able to move it again. int frameLeft, frameRight, frameTop, frameBottom; if (decoration) decoration->borders(frameLeft, frameRight, frameTop, frameBottom); else frameTop = 10; int titlebarArea = qMin(frameTop * 100, moveResizeGeom.width() * moveResizeGeom.height()); bool update = false; if (isResize()) { #ifdef KWIN_BUILD_TILING // query layout for supported resize mode if (workspace()->tiling()->isEnabled()) { mode = workspace()->tiling()->supportedTilingResizeMode(this, mode); } #endif // 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: #ifdef KWIN_BUILD_TILING // exception for tiling // Center means no resizing allowed if (workspace()->tiling()->isEnabled()) { finishMoveResize(false); buttonDown = false; return; } #endif default: abort(); break; } #ifdef KWIN_BUILD_TILING workspace()->tiling()->notifyTilingWindowResize(this, moveResizeGeom, initialMoveResizeGeom); #endif // adjust new size to snap to other windows/borders moveResizeGeom = workspace()->adjustClientSize(this, moveResizeGeom, mode); if (!unrestrictedMoveResize) { // Make sure the titlebar isn't behind a restricted area. We don't need to restrict // the other directions. If not visible enough, move the window to the closest valid // point. We bruteforce this by slowly moving the window back to its previous position. for (;;) { QRegion titlebarRegion(moveResizeGeom.left(), moveResizeGeom.top(), moveResizeGeom.width(), frameTop); titlebarRegion &= workspace()->clientArea(FullArea, -1, 0); // On the screen titlebarRegion -= workspace()->restrictedMoveArea(desktop()); // Strut areas // Now we have a region of all the visible areas of the titlebar // Count the visible pixels and check to see if it's enough int visiblePixels = 0; foreach (const QRect & rect, titlebarRegion.rects()) if (rect.height() >= frameTop) // Only the full height regions, prevents long slim areas visiblePixels += rect.width() * rect.height(); if (visiblePixels >= titlebarArea) break; // We have reached a valid position // Not visible enough, move the window to the closest valid point. We bruteforce // this by slowly moving the window back to its previous position. if (previousMoveResizeGeom.y() != moveResizeGeom.y()) { if (previousMoveResizeGeom.y() > moveResizeGeom.y()) moveResizeGeom.setTop(moveResizeGeom.y() + 1); else moveResizeGeom.setTop(moveResizeGeom.y() - 1); } else { // Our heights match but we still don't have a valid area, maybe // we are trying to resize in from the side? bool breakLoop = false; switch(mode) { case PositionTopLeft: case PositionLeft: if (previousMoveResizeGeom.x() >= moveResizeGeom.x()) { breakLoop = true; break; } moveResizeGeom.setLeft(moveResizeGeom.x() - 1); break; case PositionTopRight: case PositionRight: if (previousMoveResizeGeom.right() <= moveResizeGeom.right()) { breakLoop = true; break; } moveResizeGeom.setRight(moveResizeGeom.x() + moveResizeGeom.width()); break; default: breakLoop = true; } if (breakLoop) break; } } } // Always obey size hints, even when in "unrestricted" mode QSize size = adjustedSize(moveResizeGeom.size(), sizemode); // the new topleft and bottomright corners (after checking size constrains), if they'll be needed topleft = QPoint(moveResizeGeom.right() - size.width() + 1, moveResizeGeom.bottom() - size.height() + 1); bottomright = QPoint(moveResizeGeom.left() + size.width() - 1, moveResizeGeom.top() + size.height() - 1); orig = moveResizeGeom; switch(mode) { // these 4 corners ones are copied from above case PositionTopLeft: moveResizeGeom = QRect(topleft, orig.bottomRight()) ; break; case PositionBottomRight: moveResizeGeom = QRect(orig.topLeft(), bottomright) ; break; case PositionBottomLeft: moveResizeGeom = QRect(QPoint(topleft.x(), orig.y()), QPoint(orig.right(), bottomright.y())) ; break; case PositionTopRight: moveResizeGeom = QRect(QPoint(orig.x(), topleft.y()), QPoint(bottomright.x(), orig.bottom())) ; break; // The side ones can't be copied exactly - if aspect ratios are specified, both dimensions may change. // Therefore grow to the right/bottom if needed. // TODO it should probably obey gravity rather than always using right/bottom ? case PositionTop: moveResizeGeom = QRect(QPoint(orig.left(), topleft.y()), QPoint(bottomright.x(), orig.bottom())) ; break; case PositionBottom: moveResizeGeom = QRect(orig.topLeft(), QPoint(bottomright.x(), bottomright.y())) ; break; case PositionLeft: moveResizeGeom = QRect(QPoint(topleft.x(), orig.top()), QPoint(orig.right(), bottomright.y())); break; case PositionRight: moveResizeGeom = QRect(orig.topLeft(), QPoint(bottomright.x(), bottomright.y())) ; break; case PositionCenter: default: abort(); break; } if (moveResizeGeom.size() != previousMoveResizeGeom.size()) update = true; } else if (isMove()) { assert(mode == PositionCenter); if (!isMovable()) { // isMovableAcrossScreens() must have been true to get here // Special moving of maximized windows on Xinerama screens int screen = workspace()->screenNumber(globalPos); if (isFullScreen()) moveResizeGeom = workspace()->clientArea(FullScreenArea, screen, 0); else { moveResizeGeom = workspace()->clientArea(MaximizeArea, screen, 0); QSize adjSize = adjustedSize(moveResizeGeom.size(), SizemodeMax); if (adjSize != moveResizeGeom.size()) { QRect r(moveResizeGeom); moveResizeGeom.setSize(adjSize); moveResizeGeom.moveCenter(r.center()); } } } else { // first move, then snap, then check bounds moveResizeGeom.moveTopLeft(topleft); moveResizeGeom.moveTopLeft(workspace()->adjustClientPosition(this, moveResizeGeom.topLeft(), unrestrictedMoveResize)); if (!unrestrictedMoveResize) { // Make sure the titlebar isn't behind a restricted area. for (;;) { QRegion titlebarRegion(moveResizeGeom.left(), moveResizeGeom.top(), moveResizeGeom.width(), frameTop); titlebarRegion &= workspace()->clientArea(FullArea, -1, 0); // On the screen titlebarRegion -= workspace()->restrictedMoveArea(desktop()); // Strut areas // Now we have a region of all the visible areas of the titlebar // Count the visible pixels and check to see if it's enough int visiblePixels = 0; foreach (const QRect & rect, titlebarRegion.rects()) if (rect.height() >= frameTop) // Only the full height regions, prevents long slim areas visiblePixels += rect.width() * rect.height(); if (visiblePixels >= titlebarArea) break; // We have reached a valid position // Move it (Favour vertically) if (previousMoveResizeGeom.y() != moveResizeGeom.y()) moveResizeGeom.translate(0, previousMoveResizeGeom.y() > moveResizeGeom.y() ? 1 : -1); else moveResizeGeom.translate(previousMoveResizeGeom.x() > moveResizeGeom.x() ? 1 : -1, 0); if (moveResizeGeom == previousMoveResizeGeom) break; // Prevent lockup } } } if (moveResizeGeom.topLeft() != previousMoveResizeGeom.topLeft()) update = true; } else abort(); if (!update) return; #ifdef HAVE_XSYNC if (isResize() && syncRequest.counter != None && !s_haveResizeEffect) { if (!syncRequest.timeout) { syncRequest.timeout = new QTimer(this); connect(syncRequest.timeout, SIGNAL(timeout()), SLOT(performMoveResize())); syncRequest.timeout->setSingleShot(true); } syncRequest.timeout->start(250); sendSyncRequest(); XMoveResizeWindow(display(), window(), 0, 0, moveResizeGeom.width() - (border_left + border_right), moveResizeGeom.height() - (border_top + border_bottom)); } else #endif performMoveResize(); if (isMove()) { #ifdef KWIN_BUILD_TILING workspace()->tiling()->notifyTilingWindowMove(this, moveResizeGeom, initialMoveResizeGeom); #endif #ifdef KWIN_BUILD_SCREENEDGES workspace()->screenEdge()->check(globalPos, xTime()); #endif } } void Client::performMoveResize() { #ifdef KWIN_BUILD_TILING if (!workspace()->tiling()->isEnabled()) #endif { if (isMove() || (isResize() && !s_haveResizeEffect)) { setGeometry(moveResizeGeom); } } #ifdef HAVE_XSYNC if (isResize() && syncRequest.counter != None) addRepaintFull(); #endif positionGeometryTip(); emit clientStepUserMovedResized(this, moveResizeGeom); } void Client::setElectricBorderMode(QuickTileMode mode) { if (mode != QuickTileMaximize) { // sanitize the mode, ie. simplify "invalid" combinations if ((mode & QuickTileHorizontal) == QuickTileHorizontal) mode &= ~QuickTileHorizontal; if ((mode & QuickTileVertical) == QuickTileVertical) mode &= ~QuickTileVertical; } electricMode = mode; } QuickTileMode Client::electricBorderMode() const { return electricMode; } bool Client::isElectricBorderMaximizing() const { return electricMaximizing; } void Client::setElectricBorderMaximizing(bool maximizing) { electricMaximizing = maximizing; if (maximizing) workspace()->outline()->show(electricBorderMaximizeGeometry(cursorPos(), desktop())); else workspace()->outline()->hide(); } QRect Client::electricBorderMaximizeGeometry(QPoint pos, int desktop) { if (electricMode == QuickTileMaximize) { if (maximizeMode() == MaximizeFull) return geometryRestore(); else return workspace()->clientArea(MaximizeArea, pos, desktop); } QRect ret = workspace()->clientArea(MaximizeArea, pos, desktop); if (electricMode & QuickTileLeft) ret.setRight(ret.left()+ret.width()/2 - 1); else if (electricMode & QuickTileRight) ret.setLeft(ret.right()-(ret.width()-ret.width()/2) + 1); if (electricMode & QuickTileTop) ret.setBottom(ret.top()+ret.height()/2 - 1); else if (electricMode & QuickTileBottom) ret.setTop(ret.bottom()-(ret.height()-ret.height()/2) + 1); return ret; } void Client::setQuickTileMode(QuickTileMode mode, bool keyboard) { // Only allow quick tile on a regular or maximized window if (!isResizable() && maximizeMode() != MaximizeFull) return; if (mode == QuickTileMaximize) { quick_tile_mode = QuickTileNone; if (maximizeMode() == MaximizeFull) setMaximize(false, false); else { setMaximize(true, true); quick_tile_mode = QuickTileMaximize; } return; } // sanitize the mode, ie. simplify "invalid" combinations if ((mode & QuickTileHorizontal) == QuickTileHorizontal) mode &= ~QuickTileHorizontal; if ((mode & QuickTileVertical) == QuickTileVertical) mode &= ~QuickTileVertical; setElectricBorderMode(mode); // used by ::electricBorderMaximizeGeometry(.) // restore from maximized so that it is possible to tile maximized windows with one hit or by dragging if (maximizeMode() == MaximizeFull) { setMaximize(false, false); // Temporary, so the maximize code doesn't get all confused quick_tile_mode = QuickTileNone; if (mode != QuickTileNone) setGeometry(electricBorderMaximizeGeometry(keyboard ? geometry().center() : cursorPos(), desktop())); // Store the mode change quick_tile_mode = mode; return; } // First, check if the requested tile negates the tile we're in now: move right when left or left when right // is the same as explicitly untiling this window, so allow it. if (mode == QuickTileNone || ((quick_tile_mode & QuickTileHorizontal) && (mode & QuickTileHorizontal))) { // Untiling, so just restore geometry, and we're done. setGeometry(geom_pretile); quick_tile_mode = QuickTileNone; checkWorkspacePosition(); // Just in case it's a different screen return; } else { QPoint whichScreen = keyboard ? geometry().center() : cursorPos(); // If trying to tile to the side that the window is already tiled to move the window to the next // screen if it exists, otherwise ignore the request to prevent corrupting geom_pretile. if (quick_tile_mode == mode) { const int numScreens = Kephal::ScreenUtils::numScreens(); const int curScreen = screen(); int nextScreen = curScreen; QVarLengthArray screens(numScreens); for (int i = 0; i < numScreens; ++i) // Cache screens[i] = Kephal::ScreenUtils::screenGeometry(i); for (int i = 0; i < numScreens; ++i) { if (i == curScreen) continue; if (((mode == QuickTileLeft && screens[i].center().x() < screens[nextScreen].center().x()) || (mode == QuickTileRight && screens[i].center().x() > screens[nextScreen].center().x())) && // Must be in horizontal line (screens[i].bottom() > screens[nextScreen].top() || screens[i].top() < screens[nextScreen].bottom())) nextScreen = i; } if (nextScreen == curScreen) return; // No other screens // Move to other screen geom_pretile.translate( screens[nextScreen].x() - screens[curScreen].x(), screens[nextScreen].y() - screens[curScreen].y()); whichScreen = screens[nextScreen].center(); // Swap sides if (mode == QuickTileLeft) mode = QuickTileRight; else mode = QuickTileLeft; } else // Not coming out of an existing tile, not shifting monitors, we're setting a brand new tile. // Store geometry first, so we can go out of this tile later. geom_pretile = geometry(); // Temporary, so the maximize code doesn't get all confused quick_tile_mode = QuickTileNone; if (mode != QuickTileNone) setGeometry(electricBorderMaximizeGeometry(whichScreen, desktop())); // Store the mode change quick_tile_mode = mode; } } } // namespace diff --git a/libkwineffects/kwineffects.cpp b/libkwineffects/kwineffects.cpp index b04c36057..efaca5d56 100644 --- a/libkwineffects/kwineffects.cpp +++ b/libkwineffects/kwineffects.cpp @@ -1,1206 +1,1216 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak Copyright (C) 2009 Lucas Murray This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwineffects.h" #include "kwinxrenderutils.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef KWIN_HAVE_XRENDER_COMPOSITING #include #include #endif namespace KWin { void WindowPrePaintData::setTranslucent() { mask |= Effect::PAINT_WINDOW_TRANSLUCENT; mask &= ~Effect::PAINT_WINDOW_OPAQUE; clip = QRegion(); // cannot clip, will be transparent } void WindowPrePaintData::setTransformed() { mask |= Effect::PAINT_WINDOW_TRANSFORMED; } WindowPaintData::WindowPaintData(EffectWindow* w) : opacity(w->opacity()) , contents_opacity(1.0) , decoration_opacity(1.0) , xScale(1) , yScale(1) , zScale(1) , xTranslate(0) , yTranslate(0) , zTranslate(0) , saturation(1) , brightness(1) , shader(NULL) , rotation(NULL) { quads = w->buildQuads(); } ScreenPaintData::ScreenPaintData() : xScale(1) , yScale(1) , zScale(1) , xTranslate(0) , yTranslate(0) , zTranslate(0) , rotation(NULL) { } RotationData::RotationData() : axis(ZAxis) , angle(0.0) , xRotationPoint(0.0) , yRotationPoint(0.0) , zRotationPoint(0.0) { } //**************************************** // Effect //**************************************** Effect::Effect() { } Effect::~Effect() { } void Effect::reconfigure(ReconfigureFlags) { } void* Effect::proxy() { return NULL; } void Effect::windowInputMouseEvent(Window, QEvent*) { } void Effect::grabbedKeyboardEvent(QKeyEvent*) { } bool Effect::borderActivated(ElectricBorder) { return false; } void Effect::prePaintScreen(ScreenPrePaintData& data, int time) { effects->prePaintScreen(data, time); } void Effect::paintScreen(int mask, QRegion region, ScreenPaintData& data) { effects->paintScreen(mask, region, data); } void Effect::postPaintScreen() { effects->postPaintScreen(); } void Effect::prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time) { effects->prePaintWindow(w, data, time); } void Effect::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { effects->paintWindow(w, mask, region, data); } void Effect::postPaintWindow(EffectWindow* w) { effects->postPaintWindow(w); } void Effect::paintEffectFrame(KWin::EffectFrame* frame, QRegion region, double opacity, double frameOpacity) { effects->paintEffectFrame(frame, region, opacity, frameOpacity); } bool Effect::provides(Feature) { return false; } bool Effect::isActive() const { return true; } void Effect::drawWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { effects->drawWindow(w, mask, region, data); } void Effect::buildQuads(EffectWindow* w, WindowQuadList& quadList) { effects->buildQuads(w, quadList); } void Effect::setPositionTransformations(WindowPaintData& data, QRect& region, EffectWindow* w, const QRect& r, Qt::AspectRatioMode aspect) { QSize size = w->size(); size.scale(r.size(), aspect); data.xScale = size.width() / double(w->width()); data.yScale = size.height() / double(w->height()); int width = int(w->width() * data.xScale); int height = int(w->height() * data.yScale); int x = r.x() + (r.width() - width) / 2; int y = r.y() + (r.height() - height) / 2; region = QRect(x, y, width, height); data.xTranslate = x - w->x(); data.yTranslate = y - w->y(); } int Effect::displayWidth() { return KWin::displayWidth(); } int Effect::displayHeight() { return KWin::displayHeight(); } QPoint Effect::cursorPos() { return effects->cursorPos(); } double Effect::animationTime(const KConfigGroup& cfg, const QString& key, int defaultTime) { int time = cfg.readEntry(key, 0); return time != 0 ? time : qMax(defaultTime * effects->animationTimeFactor(), 1.); } double Effect::animationTime(int defaultTime) { // at least 1ms, otherwise 0ms times can break some things return qMax(defaultTime * effects->animationTimeFactor(), 1.); } //**************************************** // EffectsHandler //**************************************** EffectsHandler::EffectsHandler(CompositingType type) : compositing_type(type) { if (compositing_type == NoCompositing) return; KWin::effects = this; } EffectsHandler::~EffectsHandler() { // All effects should already be unloaded by Impl dtor assert(loaded_effects.count() == 0); } Window EffectsHandler::createInputWindow(Effect* e, const QRect& r, const QCursor& cursor) { return createInputWindow(e, r.x(), r.y(), r.width(), r.height(), cursor); } Window EffectsHandler::createFullScreenInputWindow(Effect* e, const QCursor& cursor) { return createInputWindow(e, 0, 0, displayWidth(), displayHeight(), cursor); } CompositingType EffectsHandler::compositingType() const { return compositing_type; } void EffectsHandler::sendReloadMessage(const QString& effectname) { QDBusMessage message = QDBusMessage::createMethodCall("org.kde.kwin", "/KWin", "org.kde.KWin", "reconfigureEffect"); message << QString("kwin4_effect_" + effectname); QDBusConnection::sessionBus().send(message); } KConfigGroup EffectsHandler::effectConfig(const QString& effectname) { KSharedConfig::Ptr kwinconfig = KSharedConfig::openConfig("kwinrc", KConfig::NoGlobals); return kwinconfig->group("Effect-" + effectname); } EffectsHandler* effects = 0; //**************************************** // EffectWindow //**************************************** EffectWindow::EffectWindow(QObject *parent) : QObject(parent) { } EffectWindow::~EffectWindow() { } #define WINDOW_HELPER( rettype, prototype, propertyname ) \ rettype EffectWindow::prototype ( ) const \ { \ return parent()->property( propertyname ).value< rettype >(); \ } WINDOW_HELPER(double, opacity, "opacity") WINDOW_HELPER(bool, hasAlpha, "alpha") WINDOW_HELPER(int, x, "x") WINDOW_HELPER(int, y, "y") WINDOW_HELPER(int, width, "width") WINDOW_HELPER(int, height, "height") WINDOW_HELPER(QPoint, pos, "pos") WINDOW_HELPER(QSize, size, "size") WINDOW_HELPER(int, screen, "screen") WINDOW_HELPER(QRect, geometry, "geometry") WINDOW_HELPER(QRect, rect, "rect") WINDOW_HELPER(int, desktop, "desktop") WINDOW_HELPER(bool, isDesktop, "desktopWindow") WINDOW_HELPER(bool, isDock, "dock") WINDOW_HELPER(bool, isToolbar, "toolbar") WINDOW_HELPER(bool, isMenu, "menu") WINDOW_HELPER(bool, isNormalWindow, "normalWindow") WINDOW_HELPER(bool, isDialog, "dialog") WINDOW_HELPER(bool, isSplash, "splash") WINDOW_HELPER(bool, isUtility, "utility") WINDOW_HELPER(bool, isDropdownMenu, "dropdownMenu") WINDOW_HELPER(bool, isPopupMenu, "popupMenu") WINDOW_HELPER(bool, isTooltip, "tooltip") WINDOW_HELPER(bool, isNotification, "notification") WINDOW_HELPER(bool, isComboBox, "comboBox") WINDOW_HELPER(bool, isDNDIcon, "dndIcon") WINDOW_HELPER(bool, isManaged, "managed") WINDOW_HELPER(bool, isDeleted, "deleted") WINDOW_HELPER(bool, hasOwnShape, "shaped") WINDOW_HELPER(QString, windowRole, "windowRole") QString EffectWindow::windowClass() const { return parent()->property("resourceName").toString() + ' ' + parent()->property("resourceClass").toString(); } QRect EffectWindow::contentsRect() const { return QRect(parent()->property("clientPos").toPoint(), parent()->property("clientSize").toSize()); } NET::WindowType EffectWindow::windowType() const { return static_cast(parent()->property("windowType").toInt()); } bool EffectWindow::isOnActivity(QString activity) const { const QStringList activities = parent()->property("activities").toStringList(); return activities.isEmpty() || activities.contains(activity); } bool EffectWindow::isOnAllActivities() const { return parent()->property("activities").toStringList().isEmpty(); } #undef WINDOW_HELPER #define WINDOW_HELPER_DEFAULT( rettype, prototype, propertyname, defaultValue ) \ rettype EffectWindow::prototype ( ) const \ { \ const QVariant variant = parent()->property( propertyname ); \ if (!variant.isValid()) { \ return defaultValue; \ } \ return variant.value< rettype >(); \ } WINDOW_HELPER_DEFAULT(bool, isMinimized, "minimized", false) WINDOW_HELPER_DEFAULT(bool, isMovable, "moveable", false) WINDOW_HELPER_DEFAULT(bool, isMovableAcrossScreens, "moveableAcrossScreens", false) WINDOW_HELPER_DEFAULT(QString, caption, "caption", "") WINDOW_HELPER_DEFAULT(bool, keepAbove, "keepAbove", true) WINDOW_HELPER_DEFAULT(bool, isModal, "modal", false) WINDOW_HELPER_DEFAULT(QSize, basicUnit, "basicUnit", QSize(1, 1)) WINDOW_HELPER_DEFAULT(bool, isUserMove, "move", false) WINDOW_HELPER_DEFAULT(bool, isUserResize, "resize", false) WINDOW_HELPER_DEFAULT(QRect, iconGeometry, "iconGeometry", QRect()) WINDOW_HELPER_DEFAULT(bool, isSpecialWindow, "specialWindow", true) WINDOW_HELPER_DEFAULT(bool, acceptsFocus, "wantsInput", true) // We don't actually know... WINDOW_HELPER_DEFAULT(QPixmap, icon, "icon", QPixmap()) WINDOW_HELPER_DEFAULT(bool, isSkipSwitcher, "skipSwitcher", false) WINDOW_HELPER_DEFAULT(bool, isCurrentTab, "isCurrentTab", false) #undef WINDOW_HELPER_DEFAULT #define WINDOW_HELPER_SETTER( prototype, propertyname, args, value ) \ void EffectWindow::prototype ( args ) \ {\ const QVariant variant = parent()->property( propertyname ); \ if (variant.isValid()) { \ parent()->setProperty( propertyname, value ); \ } \ } WINDOW_HELPER_SETTER(minimize, "minimized",,true) WINDOW_HELPER_SETTER(unminimize, "minimized",,false) #undef WINDOW_HELPER_SETTER void EffectWindow::closeWindow() const { QMetaObject::invokeMethod(parent(), "closeWindow"); } void EffectWindow::addRepaint(int x, int y, int w, int h) { QMetaObject::invokeMethod(parent(), "addRepaint", Q_ARG(int, x), Q_ARG(int, y), Q_ARG(int, w), Q_ARG(int, h)); } void EffectWindow::addRepaint(const QRect &r) { QMetaObject::invokeMethod(parent(), "addRepaint", Q_ARG(const QRect&, r)); } void EffectWindow::addRepaintFull() { QMetaObject::invokeMethod(parent(), "addRepaintFull"); } +void EffectWindow::addLayerRepaint(int x, int y, int w, int h) +{ + QMetaObject::invokeMethod(parent(), "addLayerRepaint", Q_ARG(int, x), Q_ARG(int, y), Q_ARG(int, w), Q_ARG(int, h)); +} + +void EffectWindow::addLayerRepaint(const QRect &r) +{ + QMetaObject::invokeMethod(parent(), "addLayerRepaint", Q_ARG(const QRect&, r)); +} + bool EffectWindow::isOnCurrentActivity() const { return isOnActivity(effects->currentActivity()); } bool EffectWindow::isOnCurrentDesktop() const { return isOnDesktop(effects->currentDesktop()); } bool EffectWindow::isOnDesktop(int d) const { return desktop() == d || isOnAllDesktops(); } bool EffectWindow::isOnAllDesktops() const { return desktop() == NET::OnAllDesktops; } bool EffectWindow::hasDecoration() const { return contentsRect() != QRect(0, 0, width(), height()); } //**************************************** // EffectWindowGroup //**************************************** EffectWindowGroup::~EffectWindowGroup() { } //**************************************** // GlobalShortcutsEditor //**************************************** GlobalShortcutsEditor::GlobalShortcutsEditor(QWidget *parent) : KShortcutsEditor(parent, GlobalAction) { } /*************************************************************** WindowQuad ***************************************************************/ WindowQuad WindowQuad::makeSubQuad(double x1, double y1, double x2, double y2) const { assert(x1 < x2 && y1 < y2 && x1 >= left() && x2 <= right() && y1 >= top() && y2 <= bottom()); #ifndef NDEBUG if (isTransformed()) kFatal(1212) << "Splitting quads is allowed only in pre-paint calls!" ; #endif WindowQuad ret(*this); // vertices are clockwise starting from topleft ret.verts[ 0 ].px = x1; ret.verts[ 3 ].px = x1; ret.verts[ 1 ].px = x2; ret.verts[ 2 ].px = x2; ret.verts[ 0 ].py = y1; ret.verts[ 1 ].py = y1; ret.verts[ 2 ].py = y2; ret.verts[ 3 ].py = y2; // original x/y are supposed to be the same, no transforming is done here ret.verts[ 0 ].ox = x1; ret.verts[ 3 ].ox = x1; ret.verts[ 1 ].ox = x2; ret.verts[ 2 ].ox = x2; ret.verts[ 0 ].oy = y1; ret.verts[ 1 ].oy = y1; ret.verts[ 2 ].oy = y2; ret.verts[ 3 ].oy = y2; double my_tleft = verts[ 0 ].tx; double my_tright = verts[ 2 ].tx; double my_ttop = verts[ 0 ].ty; double my_tbottom = verts[ 2 ].ty; double tleft = (x1 - left()) / (right() - left()) * (my_tright - my_tleft) + my_tleft; double tright = (x2 - left()) / (right() - left()) * (my_tright - my_tleft) + my_tleft; double ttop = (y1 - top()) / (bottom() - top()) * (my_tbottom - my_ttop) + my_ttop; double tbottom = (y2 - top()) / (bottom() - top()) * (my_tbottom - my_ttop) + my_ttop; ret.verts[ 0 ].tx = tleft; ret.verts[ 3 ].tx = tleft; ret.verts[ 1 ].tx = tright; ret.verts[ 2 ].tx = tright; ret.verts[ 0 ].ty = ttop; ret.verts[ 1 ].ty = ttop; ret.verts[ 2 ].ty = tbottom; ret.verts[ 3 ].ty = tbottom; return ret; } bool WindowQuad::smoothNeeded() const { // smoothing is needed if the width or height of the quad does not match the original size double width = verts[ 1 ].ox - verts[ 0 ].ox; double height = verts[ 2 ].oy - verts[ 1 ].oy; return(verts[ 1 ].px - verts[ 0 ].px != width || verts[ 2 ].px - verts[ 3 ].px != width || verts[ 2 ].py - verts[ 1 ].py != height || verts[ 3 ].py - verts[ 0 ].py != height); } /*************************************************************** WindowQuadList ***************************************************************/ WindowQuadList WindowQuadList::splitAtX(double x) const { WindowQuadList ret; foreach (const WindowQuad & quad, *this) { #ifndef NDEBUG if (quad.isTransformed()) kFatal(1212) << "Splitting quads is allowed only in pre-paint calls!" ; #endif bool wholeleft = true; bool wholeright = true; for (int i = 0; i < 4; ++i) { if (quad[ i ].x() < x) wholeright = false; if (quad[ i ].x() > x) wholeleft = false; } if (wholeleft || wholeright) { // is whole in one split part ret.append(quad); continue; } if (quad.left() == quad.right()) { // quad has no size ret.append(quad); continue; } ret.append(quad.makeSubQuad(quad.left(), quad.top(), x, quad.bottom())); ret.append(quad.makeSubQuad(x, quad.top(), quad.right(), quad.bottom())); } return ret; } WindowQuadList WindowQuadList::splitAtY(double y) const { WindowQuadList ret; foreach (const WindowQuad & quad, *this) { #ifndef NDEBUG if (quad.isTransformed()) kFatal(1212) << "Splitting quads is allowed only in pre-paint calls!" ; #endif bool wholetop = true; bool wholebottom = true; for (int i = 0; i < 4; ++i) { if (quad[ i ].y() < y) wholebottom = false; if (quad[ i ].y() > y) wholetop = false; } if (wholetop || wholebottom) { // is whole in one split part ret.append(quad); continue; } if (quad.top() == quad.bottom()) { // quad has no size ret.append(quad); continue; } ret.append(quad.makeSubQuad(quad.left(), quad.top(), quad.right(), y)); ret.append(quad.makeSubQuad(quad.left(), y, quad.right(), quad.bottom())); } return ret; } WindowQuadList WindowQuadList::makeGrid(int maxquadsize) const { if (empty()) return *this; // find the bounding rectangle double left = first().left(); double right = first().right(); double top = first().top(); double bottom = first().bottom(); foreach (const WindowQuad & quad, *this) { #ifndef NDEBUG if (quad.isTransformed()) kFatal(1212) << "Splitting quads is allowed only in pre-paint calls!" ; #endif left = qMin(left, quad.left()); right = qMax(right, quad.right()); top = qMin(top, quad.top()); bottom = qMax(bottom, quad.bottom()); } WindowQuadList ret; for (double x = left; x < right; x += maxquadsize) { for (double y = top; y < bottom; y += maxquadsize) { foreach (const WindowQuad & quad, *this) { if (QRectF(QPointF(quad.left(), quad.top()), QPointF(quad.right(), quad.bottom())) .intersects(QRectF(x, y, maxquadsize, maxquadsize))) { ret.append(quad.makeSubQuad(qMax(x, quad.left()), qMax(y, quad.top()), qMin(quad.right(), x + maxquadsize), qMin(quad.bottom(), y + maxquadsize))); } } } } return ret; } WindowQuadList WindowQuadList::makeRegularGrid(int xSubdivisions, int ySubdivisions) const { if (empty()) return *this; // find the bounding rectangle double left = first().left(); double right = first().right(); double top = first().top(); double bottom = first().bottom(); foreach (const WindowQuad & quad, *this) { #ifndef NDEBUG if (quad.isTransformed()) kFatal(1212) << "Splitting quads is allowed only in pre-paint calls!" ; #endif left = qMin(left, quad.left()); right = qMax(right, quad.right()); top = qMin(top, quad.top()); bottom = qMax(bottom, quad.bottom()); } double xincrement = (right - left) / xSubdivisions; double yincrement = (bottom - top) / ySubdivisions; WindowQuadList ret; for (double y = top; y < bottom; y += yincrement) { for (double x = left; x < right; x += xincrement) { foreach (const WindowQuad & quad, *this) { if (QRectF(QPointF(quad.left(), quad.top()), QPointF(quad.right(), quad.bottom())) .intersects(QRectF(x, y, xincrement, yincrement))) { ret.append(quad.makeSubQuad(qMax(x, quad.left()), qMax(y, quad.top()), qMin(quad.right(), x + xincrement), qMin(quad.bottom(), y + yincrement))); } } } } return ret; } void WindowQuadList::makeArrays(float** vertices, float** texcoords, const QSizeF& size, bool yInverted) const { *vertices = new float[ count() * 6 * 2 ]; *texcoords = new float[ count() * 6 * 2 ]; float* vpos = *vertices; float* tpos = *texcoords; for (int i = 0; i < count(); ++i) { *vpos++ = at(i)[ 1 ].x(); *vpos++ = at(i)[ 1 ].y(); *vpos++ = at(i)[ 0 ].x(); *vpos++ = at(i)[ 0 ].y(); *vpos++ = at(i)[ 3 ].x(); *vpos++ = at(i)[ 3 ].y(); *vpos++ = at(i)[ 3 ].x(); *vpos++ = at(i)[ 3 ].y(); *vpos++ = at(i)[ 2 ].x(); *vpos++ = at(i)[ 2 ].y(); *vpos++ = at(i)[ 1 ].x(); *vpos++ = at(i)[ 1 ].y(); if (yInverted) { *tpos++ = at(i)[ 1 ].tx / size.width(); *tpos++ = at(i)[ 1 ].ty / size.height(); *tpos++ = at(i)[ 0 ].tx / size.width(); *tpos++ = at(i)[ 0 ].ty / size.height(); *tpos++ = at(i)[ 3 ].tx / size.width(); *tpos++ = at(i)[ 3 ].ty / size.height(); *tpos++ = at(i)[ 3 ].tx / size.width(); *tpos++ = at(i)[ 3 ].ty / size.height(); *tpos++ = at(i)[ 2 ].tx / size.width(); *tpos++ = at(i)[ 2 ].ty / size.height(); *tpos++ = at(i)[ 1 ].tx / size.width(); *tpos++ = at(i)[ 1 ].ty / size.height(); } else { *tpos++ = at(i)[ 1 ].tx / size.width(); *tpos++ = 1.0f - at(i)[ 1 ].ty / size.height(); *tpos++ = at(i)[ 0 ].tx / size.width(); *tpos++ = 1.0f - at(i)[ 0 ].ty / size.height(); *tpos++ = at(i)[ 3 ].tx / size.width(); *tpos++ = 1.0f - at(i)[ 3 ].ty / size.height(); *tpos++ = at(i)[ 3 ].tx / size.width(); *tpos++ = 1.0f - at(i)[ 3 ].ty / size.height(); *tpos++ = at(i)[ 2 ].tx / size.width(); *tpos++ = 1.0f - at(i)[ 2 ].ty / size.height(); *tpos++ = at(i)[ 1 ].tx / size.width(); *tpos++ = 1.0f - at(i)[ 1 ].ty / size.height(); } } } WindowQuadList WindowQuadList::select(WindowQuadType type) const { foreach (const WindowQuad & q, *this) { if (q.type() != type) { // something else than ones to select, make a copy and filter WindowQuadList ret; foreach (const WindowQuad & q, *this) { if (q.type() == type) ret.append(q); } return ret; } } return *this; // nothing to filter out } WindowQuadList WindowQuadList::filterOut(WindowQuadType type) const { foreach (const WindowQuad & q, *this) { if (q.type() == type) { // something to filter out, make a copy and filter WindowQuadList ret; foreach (const WindowQuad & q, *this) { if (q.type() != type) ret.append(q); } return ret; } } return *this; // nothing to filter out } bool WindowQuadList::smoothNeeded() const { foreach (const WindowQuad & q, *this) if (q.smoothNeeded()) return true; return false; } bool WindowQuadList::isTransformed() const { foreach (const WindowQuad & q, *this) if (q.isTransformed()) return true; return false; } /*************************************************************** PaintClipper ***************************************************************/ QStack< QRegion >* PaintClipper::areas = NULL; PaintClipper::PaintClipper(const QRegion& allowed_area) : area(allowed_area) { push(area); } PaintClipper::~PaintClipper() { pop(area); } void PaintClipper::push(const QRegion& allowed_area) { if (allowed_area == infiniteRegion()) // don't push these return; if (areas == NULL) areas = new QStack< QRegion >; areas->push(allowed_area); } void PaintClipper::pop(const QRegion& allowed_area) { if (allowed_area == infiniteRegion()) return; Q_ASSERT(areas != NULL); Q_ASSERT(areas->top() == allowed_area); areas->pop(); if (areas->isEmpty()) { delete areas; areas = NULL; } } bool PaintClipper::clip() { return areas != NULL; } QRegion PaintClipper::paintArea() { assert(areas != NULL); // can be called only with clip() == true QRegion ret = QRegion(0, 0, displayWidth(), displayHeight()); foreach (const QRegion & r, *areas) ret &= r; return ret; } struct PaintClipper::Iterator::Data { Data() : index(0) {} int index; QVector< QRect > rects; }; PaintClipper::Iterator::Iterator() : data(new Data) { if (clip() && effects->compositingType() == OpenGLCompositing) { data->rects = paintArea().rects(); data->index = -1; next(); // move to the first one } #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (clip() && effects->compositingType() == XRenderCompositing) { XserverRegion region = toXserverRegion(paintArea()); XFixesSetPictureClipRegion(display(), effects->xrenderBufferPicture(), 0, 0, region); XFixesDestroyRegion(display(), region); // it's ref-counted } #endif } PaintClipper::Iterator::~Iterator() { #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (clip() && effects->compositingType() == XRenderCompositing) XFixesSetPictureClipRegion(display(), effects->xrenderBufferPicture(), 0, 0, None); #endif delete data; } bool PaintClipper::Iterator::isDone() { if (!clip()) return data->index == 1; // run once if (effects->compositingType() == OpenGLCompositing) return data->index >= data->rects.count(); // run once per each area #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (effects->compositingType() == XRenderCompositing) return data->index == 1; // run once #endif abort(); } void PaintClipper::Iterator::next() { data->index++; } QRect PaintClipper::Iterator::boundingRect() const { if (!clip()) return infiniteRegion(); if (effects->compositingType() == OpenGLCompositing) return data->rects[ data->index ]; #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (effects->compositingType() == XRenderCompositing) return paintArea().boundingRect(); #endif abort(); return infiniteRegion(); } /*************************************************************** Motion1D ***************************************************************/ Motion1D::Motion1D(double initial, double strength, double smoothness) : Motion(initial, strength, smoothness) { } Motion1D::Motion1D(const Motion1D &other) : Motion(other) { } Motion1D::~Motion1D() { } /*************************************************************** Motion2D ***************************************************************/ Motion2D::Motion2D(QPointF initial, double strength, double smoothness) : Motion(initial, strength, smoothness) { } Motion2D::Motion2D(const Motion2D &other) : Motion(other) { } Motion2D::~Motion2D() { } /*************************************************************** WindowMotionManager ***************************************************************/ WindowMotionManager::WindowMotionManager(bool useGlobalAnimationModifier) : m_useGlobalAnimationModifier(useGlobalAnimationModifier) { // TODO: Allow developer to modify motion attributes } // TODO: What happens when the window moves by an external force? WindowMotionManager::~WindowMotionManager() { } void WindowMotionManager::manage(EffectWindow *w) { if (m_managedWindows.contains(w)) return; double strength = 0.08; double smoothness = 4.0; if (m_useGlobalAnimationModifier && effects->animationTimeFactor()) { // If the factor is == 0 then we just skip the calculation completely strength = 0.08 / effects->animationTimeFactor(); smoothness = effects->animationTimeFactor() * 4.0; } WindowMotion &motion = m_managedWindows[ w ]; motion.translation.setStrength(strength); motion.translation.setSmoothness(smoothness); motion.scale.setStrength(strength * 1.33); motion.scale.setSmoothness(smoothness / 2.0); motion.translation.setValue(w->pos()); motion.scale.setValue(QPointF(1.0, 1.0)); } void WindowMotionManager::unmanage(EffectWindow *w) { m_movingWindowsSet.remove(w); m_managedWindows.remove(w); } void WindowMotionManager::unmanageAll() { m_managedWindows.clear(); m_movingWindowsSet.clear(); } void WindowMotionManager::calculate(int time) { if (!effects->animationTimeFactor()) { // Just skip it completely if the user wants no animation m_movingWindowsSet.clear(); QHash::iterator it = m_managedWindows.begin(); for (; it != m_managedWindows.end(); ++it) { WindowMotion *motion = &it.value(); motion->translation.finish(); motion->scale.finish(); } } QHash::iterator it = m_managedWindows.begin(); for (; it != m_managedWindows.end(); ++it) { WindowMotion *motion = &it.value(); int stopped = 0; // TODO: What happens when distance() == 0 but we are still moving fast? // TODO: Motion needs to be calculated from the window's center Motion2D *trans = &motion->translation; if (trans->distance().isNull()) ++stopped; else { // Still moving trans->calculate(time); const short fx = trans->target().x() <= trans->startValue().x() ? -1 : 1; const short fy = trans->target().y() <= trans->startValue().y() ? -1 : 1; if (trans->distance().x()*fx/0.5 < 1.0 && trans->velocity().x()*fx/0.2 < 1.0 && trans->distance().y()*fy/0.5 < 1.0 && trans->velocity().y()*fy/0.2 < 1.0) { // Hide tiny oscillations motion->translation.finish(); ++stopped; } } Motion2D *scale = &motion->scale; if (scale->distance().isNull()) ++stopped; else { // Still scaling scale->calculate(time); const short fx = scale->target().x() < 1.0 ? -1 : 1; const short fy = scale->target().y() < 1.0 ? -1 : 1; if (scale->distance().x()*fx/0.001 < 1.0 && scale->velocity().x()*fx/0.05 < 1.0 && scale->distance().y()*fy/0.001 < 1.0 && scale->velocity().y()*fy/0.05 < 1.0) { // Hide tiny oscillations motion->scale.finish(); ++stopped; } } // We just finished this window's motion if (stopped == 2) m_movingWindowsSet.remove(it.key()); } } void WindowMotionManager::reset() { QHash::iterator it = m_managedWindows.begin(); for (; it != m_managedWindows.end(); ++it) { WindowMotion *motion = &it.value(); EffectWindow *window = it.key(); motion->translation.setTarget(window->pos()); motion->translation.finish(); motion->scale.setTarget(QPointF(1.0, 1.0)); motion->scale.finish(); } } void WindowMotionManager::reset(EffectWindow *w) { QHash::iterator it = m_managedWindows.find(w); if (it == m_managedWindows.end()) return; WindowMotion *motion = &it.value(); motion->translation.setTarget(w->pos()); motion->translation.finish(); motion->scale.setTarget(QPointF(1.0, 1.0)); motion->scale.finish(); } void WindowMotionManager::apply(EffectWindow *w, WindowPaintData &data) { QHash::iterator it = m_managedWindows.find(w); if (it == m_managedWindows.end()) return; // TODO: Take into account existing scale so that we can work with multiple managers (E.g. Present windows + grid) WindowMotion *motion = &it.value(); data.xTranslate += motion->translation.value().x() - w->x(); data.yTranslate += motion->translation.value().y() - w->y(); data.xScale *= motion->scale.value().x(); data.yScale *= motion->scale.value().y(); } void WindowMotionManager::moveWindow(EffectWindow *w, QPoint target, double scale, double yScale) { QHash::iterator it = m_managedWindows.find(w); if (it == m_managedWindows.end()) abort(); // Notify the effect author that they did something wrong WindowMotion *motion = &it.value(); if (yScale == 0.0) yScale = scale; QPointF scalePoint(scale, yScale); if (motion->translation.value() == target && motion->scale.value() == scalePoint) return; // Window already at that position motion->translation.setTarget(target); motion->scale.setTarget(scalePoint); m_movingWindowsSet << w; } QRectF WindowMotionManager::transformedGeometry(EffectWindow *w) const { QHash::const_iterator it = m_managedWindows.constFind(w); if (it == m_managedWindows.end()) return w->geometry(); const WindowMotion *motion = &it.value(); QRectF geometry(w->geometry()); // TODO: Take into account existing scale so that we can work with multiple managers (E.g. Present windows + grid) geometry.moveTo(motion->translation.value()); geometry.setWidth(geometry.width() * motion->scale.value().x()); geometry.setHeight(geometry.height() * motion->scale.value().y()); return geometry; } void WindowMotionManager::setTransformedGeometry(EffectWindow *w, const QRectF &geometry) { QHash::iterator it = m_managedWindows.find(w); if (it == m_managedWindows.end()) return; WindowMotion *motion = &it.value(); motion->translation.setValue(geometry.topLeft()); motion->scale.setValue(QPointF(geometry.width() / qreal(w->width()), geometry.height() / qreal(w->height()))); } QRectF WindowMotionManager::targetGeometry(EffectWindow *w) const { QHash::const_iterator it = m_managedWindows.constFind(w); if (it == m_managedWindows.end()) return w->geometry(); const WindowMotion *motion = &it.value(); QRectF geometry(w->geometry()); // TODO: Take into account existing scale so that we can work with multiple managers (E.g. Present windows + grid) geometry.moveTo(motion->translation.target()); geometry.setWidth(geometry.width() * motion->scale.target().x()); geometry.setHeight(geometry.height() * motion->scale.target().y()); return geometry; } EffectWindow* WindowMotionManager::windowAtPoint(QPoint point, bool useStackingOrder) const { Q_UNUSED(useStackingOrder); // TODO: Stacking order uses EffectsHandler::stackingOrder() then filters by m_managedWindows QHash< EffectWindow*, WindowMotion >::ConstIterator it = m_managedWindows.constBegin(); while (it != m_managedWindows.constEnd()) { if (transformedGeometry(it.key()).contains(point)) return it.key(); ++it; } return NULL; } /*************************************************************** EffectFramePrivate ***************************************************************/ class EffectFramePrivate { public: EffectFramePrivate(); ~EffectFramePrivate(); bool crossFading; qreal crossFadeProgress; }; EffectFramePrivate::EffectFramePrivate() : crossFading(false) , crossFadeProgress(1.0) { } EffectFramePrivate::~EffectFramePrivate() { } /*************************************************************** EffectFrame ***************************************************************/ EffectFrame::EffectFrame() : d(new EffectFramePrivate) { } EffectFrame::~EffectFrame() { delete d; } qreal EffectFrame::crossFadeProgress() const { return d->crossFadeProgress; } void EffectFrame::setCrossFadeProgress(qreal progress) { d->crossFadeProgress = progress; } bool EffectFrame::isCrossFade() const { return d->crossFading; } void EffectFrame::enableCrossFade(bool enable) { d->crossFading = enable; } } // namespace #include "kwineffects.moc" diff --git a/libkwineffects/kwineffects.h b/libkwineffects/kwineffects.h index 288dad825..b4fc0ed37 100644 --- a/libkwineffects/kwineffects.h +++ b/libkwineffects/kwineffects.h @@ -1,2160 +1,2162 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak Copyright (C) 2009 Lucas Murray Copyright (C) 2010, 2011 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWINEFFECTS_H #define KWINEFFECTS_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class KLibrary; class KConfigGroup; class KActionCollection; class QFont; class QKeyEvent; namespace KWin { class EffectWindow; class EffectWindowGroup; class EffectFrame; class EffectFramePrivate; class Effect; class WindowQuad; class GLShader; class XRenderPicture; class RotationData; class WindowQuadList; class WindowPrePaintData; class WindowPaintData; class ScreenPrePaintData; class ScreenPaintData; typedef QPair< QString, Effect* > EffectPair; typedef QPair< Effect*, Window > InputWindowPair; typedef QList< EffectWindow* > EffectWindowList; /** @defgroup kwineffects KWin effects library * KWin effects library contains necessary classes for creating new KWin * compositing effects. * * @section creating Creating new effects * This example will demonstrate the basics of creating an effect. We'll use * CoolEffect as the class name, cooleffect as internal name and * "Cool Effect" as user-visible name of the effect. * * This example doesn't demonstrate how to write the effect's code. For that, * see the documentation of the Effect class. * * @subsection creating-class CoolEffect class * First you need to create CoolEffect class which has to be a subclass of * @ref KWin::Effect. In that class you can reimplement various virtual * methods to control how and where the windows are drawn. * * @subsection creating-macro KWIN_EFFECT macro * To make KWin aware of your new effect, you first need to use the * @ref KWIN_EFFECT macro to connect your effect's class to it's internal * name. The internal name is used by KWin to identify your effect. It can be * freely chosen (although it must be a single word), must be unique and won't * be shown to the user. For our example, you would use the macro like this: * @code * KWIN_EFFECT(cooleffect, CoolEffect) * @endcode * * @subsection creating-buildsystem Buildsystem * To build the effect, you can use the KWIN_ADD_EFFECT() cmake macro which * can be found in effects/CMakeLists.txt file in KWin's source. First * argument of the macro is the name of the library that will contain * your effect. Although not strictly required, it is usually a good idea to * use the same name as your effect's internal name there. Following arguments * to the macro are the files containing your effect's source. If our effect's * source is in cooleffect.cpp, we'd use following: * @code * KWIN_ADD_EFFECT(cooleffect cooleffect.cpp) * @endcode * * This macro takes care of compiling your effect. You'll also need to install * your effect's .desktop file, so the example CMakeLists.txt file would be * as follows: * @code * KWIN_ADD_EFFECT(cooleffect cooleffect.cpp) * install( FILES cooleffect.desktop DESTINATION ${SERVICES_INSTALL_DIR}/kwin ) * @endcode * * @subsection creating-desktop Effect's .desktop file * You will also need to create .desktop file to set name, description, icon * and other properties of your effect. Important fields of the .desktop file * are: * @li Name User-visible name of your effect * @li Icon Name of the icon of the effect * @li Comment Short description of the effect * @li Type must be "Service" * @li X-KDE-ServiceTypes must be "KWin/Effect" * @li X-KDE-PluginInfo-Name effect's internal name as passed to the KWIN_EFFECT macro plus "kwin4_effect_" prefix * @li X-KDE-PluginInfo-Category effect's category. Should be one of Appearance, Accessibility, Window Management, Demos, Tests, Misc * @li X-KDE-PluginInfo-EnabledByDefault whether the effect should be enabled by default (use sparingly). Default is false * @li X-KDE-Library name of the library containing the effect. This is the first argument passed to the KWIN_ADD_EFFECT macro in cmake file plus "kwin4_effect_" prefix. * * Example cooleffect.desktop file follows: * @code [Desktop Entry] Name=Cool Effect Comment=The coolest effect you've ever seen Icon=preferences-system-windows-effect-cooleffect Type=Service X-KDE-ServiceTypes=KWin/Effect X-KDE-PluginInfo-Author=My Name X-KDE-PluginInfo-Email=my@email.here X-KDE-PluginInfo-Name=kwin4_effect_cooleffect X-KDE-PluginInfo-Category=Misc X-KDE-Library=kwin4_effect_cooleffect * @endcode * * * @section accessing Accessing windows and workspace * Effects can gain access to the properties of windows and workspace via * EffectWindow and EffectsHandler classes. * * There is one global EffectsHandler object which you can access using the * @ref effects pointer. * For each window, there is an EffectWindow object which can be used to read * window properties such as position and also to change them. * * For more information about this, see the documentation of the corresponding * classes. * * @{ **/ #define KWIN_EFFECT_API_MAKE_VERSION( major, minor ) (( major ) << 8 | ( minor )) #define KWIN_EFFECT_API_VERSION_MAJOR 0 #define KWIN_EFFECT_API_VERSION_MINOR 182 #define KWIN_EFFECT_API_VERSION KWIN_EFFECT_API_MAKE_VERSION( \ KWIN_EFFECT_API_VERSION_MAJOR, KWIN_EFFECT_API_VERSION_MINOR ) enum WindowQuadType { WindowQuadError, // for the stupid default ctor WindowQuadContents, WindowQuadDecoration, // Shadow Quad types WindowQuadShadowTop, WindowQuadShadowTopRight, WindowQuadShadowRight, WindowQuadShadowBottomRight, WindowQuadShadowBottom, WindowQuadShadowBottomLeft, WindowQuadShadowLeft, WindowQuadShadowTopLeft, EFFECT_QUAD_TYPE_START = 100 ///< @internal }; /** * EffectWindow::setData() and EffectWindow::data() global roles. * All values between 0 and 999 are reserved for global roles. */ enum DataRole { // Grab roles are used to force all other animations to ignore the window. // The value of the data is set to the Effect's `this` value. WindowAddedGrabRole = 1, WindowClosedGrabRole, WindowMinimizedGrabRole, WindowUnminimizedGrabRole, WindowForceBlurRole, ///< For fullscreen effects to enforce blurring of windows, WindowBlurBehindRole, ///< For single windows to blur behind LanczosCacheRole }; /** * Style types used by @ref EffectFrame. * @since 4.6 */ enum EffectFrameStyle { EffectFrameNone, ///< Displays no frame around the contents. EffectFrameUnstyled, ///< Displays a basic box around the contents. EffectFrameStyled ///< Displays a Plasma-styled frame around the contents. }; /** * Infinite region (i.e. a special region type saying that everything needs to be painted). */ KWIN_EXPORT inline QRect infiniteRegion() { // INT_MIN / 2 because width/height is used (INT_MIN+INT_MAX==-1) return QRect(INT_MIN / 2, INT_MIN / 2, INT_MAX, INT_MAX); } /** * @short Base class for all KWin effects * * This is the base class for all effects. By reimplementing virtual methods * of this class, you can customize how the windows are painted. * * The virtual methods are used for painting and need to be implemented for * custom painting. * * In order to react to state changes (e.g. a window gets closed) the effect * should provide slots for the signals emitted by the EffectsHandler. * * @section Chaining * Most methods of this class are called in chain style. This means that when * effects A and B area active then first e.g. A::paintWindow() is called and * then from within that method B::paintWindow() is called (although * indirectly). To achieve this, you need to make sure to call corresponding * method in EffectsHandler class from each such method (using @ref effects * pointer): * @code * void MyEffect::postPaintScreen() * { * // Do your own processing here * ... * // Call corresponding EffectsHandler method * effects->postPaintScreen(); * } * @endcode * * @section Effectsptr Effects pointer * @ref effects pointer points to the global EffectsHandler object that you can * use to interact with the windows. * * @section painting Painting stages * Painting of windows is done in three stages: * @li First, the prepaint pass.
* Here you can specify how the windows will be painted, e.g. that they will * be translucent and transformed. * @li Second, the paint pass.
* Here the actual painting takes place. You can change attributes such as * opacity of windows as well as apply transformations to them. You can also * paint something onto the screen yourself. * @li Finally, the postpaint pass.
* Here you can mark windows, part of windows or even the entire screen for * repainting to create animations. * * For each stage there are *Screen() and *Window() methods. The window method * is called for every window which the screen method is usually called just * once. **/ class KWIN_EXPORT Effect : public QObject { Q_OBJECT public: /** Flags controlling how painting is done. */ // TODO: is that ok here? enum { /** * Window (or at least part of it) will be painted opaque. **/ PAINT_WINDOW_OPAQUE = 1 << 0, /** * Window (or at least part of it) will be painted translucent. **/ PAINT_WINDOW_TRANSLUCENT = 1 << 1, /** * Window will be painted with transformed geometry. **/ PAINT_WINDOW_TRANSFORMED = 1 << 2, /** * Paint only a region of the screen (can be optimized, cannot * be used together with TRANSFORMED flags). **/ PAINT_SCREEN_REGION = 1 << 3, /** * The whole screen will be painted with transformed geometry. * Forces the entire screen to be painted. **/ PAINT_SCREEN_TRANSFORMED = 1 << 4, /** * At least one window will be painted with transformed geometry. * Forces the entire screen to be painted. **/ PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS = 1 << 5, /** * Clear whole background as the very first step, without optimizing it **/ PAINT_SCREEN_BACKGROUND_FIRST = 1 << 6, // PAINT_DECORATION_ONLY = 1 << 7 has been deprecated /** * Window will be painted with a lanczos filter. **/ PAINT_WINDOW_LANCZOS = 1 << 8, // PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS_WITHOUT_FULL_REPAINTS = 1 << 9 has been removed }; enum Feature { Nothing = 0, Resize, GeometryTip, Outline }; /** * Constructs new Effect object. **/ Effect(); /** * Destructs the Effect object. **/ virtual ~Effect(); /** * Flags describing which parts of configuration have changed. */ enum ReconfigureFlag { ReconfigureAll = 1 << 0 /// Everything needs to be reconfigured. }; Q_DECLARE_FLAGS(ReconfigureFlags, ReconfigureFlag) /** * Called when configuration changes (either the effect's or KWin's global). */ virtual void reconfigure(ReconfigureFlags flags); /** * Called when another effect requests the proxy for this effect. */ virtual void* proxy(); /** * Called before starting to paint the screen. * In this method you can: * @li set whether the windows or the entire screen will be transformed * @li change the region of the screen that will be painted * @li do various housekeeping tasks such as initing your effect's variables for the upcoming paint pass or updating animation's progress **/ virtual void prePaintScreen(ScreenPrePaintData& data, int time); /** * In this method you can: * @li paint something on top of the windows (by painting after calling * effects->paintScreen()) * @li paint multiple desktops and/or multiple copies of the same desktop * by calling effects->paintScreen() multiple times **/ virtual void paintScreen(int mask, QRegion region, ScreenPaintData& data); /** * Called after all the painting has been finished. * In this method you can: * @li schedule next repaint in case of animations * You shouldn't paint anything here. **/ virtual void postPaintScreen(); /** * Called for every window before the actual paint pass * In this method you can: * @li enable or disable painting of the window (e.g. enable paiting of minimized window) * @li set window to be painted with translucency * @li set window to be transformed * @li request the window to be divided into multiple parts **/ virtual void prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time); /** * This is the main method for painting windows. * In this method you can: * @li do various transformations * @li change opacity of the window * @li change brightness and/or saturation, if it's supported **/ virtual void paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data); /** * Called for every window after all painting has been finished. * In this method you can: * @li schedule next repaint for individual window(s) in case of animations * You shouldn't paint anything here. **/ virtual void postPaintWindow(EffectWindow* w); /** * This method is called directly before painting an @ref EffectFrame. * You can implement this method if you need to bind a shader or perform * other operations before the frame is rendered. * @param frame The EffectFrame which will be rendered * @param region Region to restrict painting to * @param opacity Opacity of text/icon * @param frameOpacity Opacity of background * @since 4.6 **/ virtual void paintEffectFrame(EffectFrame* frame, QRegion region, double opacity, double frameOpacity); /** * Called on Transparent resizes. * return true if your effect substitutes the XOR rubberband */ virtual bool provides(Feature); /** * Can be called to draw multiple copies (e.g. thumbnails) of a window. * You can change window's opacity/brightness/etc here, but you can't * do any transformations **/ virtual void drawWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data); /** * Define new window quads so that they can be transformed by other effects. * It's up to the effect to keep track of them. **/ virtual void buildQuads(EffectWindow* w, WindowQuadList& quadList); virtual void windowInputMouseEvent(Window w, QEvent* e); virtual void grabbedKeyboardEvent(QKeyEvent* e); virtual bool borderActivated(ElectricBorder border); /** * Overwrite this method to indicate whether your effect will be doing something in * the next frame to be rendered. If the method returns @c false the effect will be * excluded from the chained methods in the next rendered frame. * * This method is called always directly before the paint loop begins. So it is totally * fine to e.g. react on a window event, issue a repaint to trigger an animation and * change a flag to indicate that this method returns @c true. * * As the method is called each frame, you should not perform complex calculations. * Best use just a boolean flag. * * The default implementation of this method returns @c true. * @since 4.8 **/ virtual bool isActive() const; static int displayWidth(); static int displayHeight(); static QPoint cursorPos(); /** * Read animation time from the configuration and possibly adjust using animationTimeFactor(). * The configuration value in the effect should also have special value 'default' (set using * QSpinBox::setSpecialValueText()) with the value 0. This special value is adjusted * using the global animation speed, otherwise the exact time configured is returned. * @param cfg configuration group to read value from * @param key configuration key to read value from * @param defaultTime default animation time in milliseconds */ // return type is intentionally double so that one can divide using it without losing data static double animationTime(const KConfigGroup& cfg, const QString& key, int defaultTime); /** * @overload Use this variant if the animation time is hardcoded and not configurable * in the effect itself. */ static double animationTime(int defaultTime); /** * Linearly interpolates between @p x and @p y. * * Returns @p x when @p a = 0; returns @p y when @p a = 1. **/ static double interpolate(double x, double y, double a) { return x * (1 - a) + y * a; } /** Helper to set WindowPaintData and QRegion to necessary transformations so that * a following drawWindow() would put the window at the requested geometry (useful for thumbnails) **/ static void setPositionTransformations(WindowPaintData& data, QRect& region, EffectWindow* w, const QRect& r, Qt::AspectRatioMode aspect); }; /** * Defines the class to be used for effect with given name. * The name must be same as effect's X-KDE-PluginInfo-Name values in .desktop * file, but without the "kwin4_effect_" prefix. * E.g. KWIN_EFFECT( flames, MyFlameEffect ) * In this case object of MyFlameEffect class would be created when effect * "flames" (which has X-KDE-PluginInfo-Name=kwin4_effect_flames in .desktop * file) is loaded. **/ #define KWIN_EFFECT( name, classname ) \ extern "C" { \ KWIN_EXPORT Effect* effect_create_kwin4_effect_##name() { return new classname; } \ KWIN_EXPORT int effect_version_kwin4_effect_##name() { return KWIN_EFFECT_API_VERSION; } \ } /** * Defines the function used to check whether an effect is supported * E.g. KWIN_EFFECT_SUPPORTED( flames, MyFlameEffect::supported() ) **/ #define KWIN_EFFECT_SUPPORTED( name, function ) \ extern "C" { \ KWIN_EXPORT bool effect_supported_kwin4_effect_##name() { return function; } \ } /** * Defines the function used to check whether an effect should be enabled by default * * This function provides a way for an effect to override the default at runtime, * e.g. based on the capabilities of the hardware. * * This function is optional; the effect doesn't have to provide it. * * Note that this function is only called if the supported() function returns true, * and if X-KDE-PluginInfo-EnabledByDefault is set to true in the .desktop file. * * Example: KWIN_EFFECT_ENABLEDBYDEFAULT(flames, MyFlameEffect::enabledByDefault()) **/ #define KWIN_EFFECT_ENABLEDBYDEFAULT(name, function) \ extern "C" { \ KWIN_EXPORT bool effect_enabledbydefault_kwin4_effect_##name() { return function; } \ } /** * Defines the function used to retrieve an effect's config widget * E.g. KWIN_EFFECT_CONFIG( flames, MyFlameEffectConfig ) **/ #define KWIN_EFFECT_CONFIG( name, classname ) \ K_PLUGIN_FACTORY(EffectFactory, registerPlugin(#name);) \ K_EXPORT_PLUGIN(EffectFactory("kcm_kwin4_effect_" #name)) /** * Defines the function used to retrieve multiple effects' config widget * E.g. KWIN_EFFECT_CONFIG_MULTIPLE( flames, * KWIN_EFFECT_CONFIG_SINGLE( flames, MyFlameEffectConfig ) * KWIN_EFFECT_CONFIG_SINGLE( fire, MyFireEffectConfig ) * ) **/ #define KWIN_EFFECT_CONFIG_MULTIPLE( name, singles ) \ K_PLUGIN_FACTORY(EffectFactory, singles) \ K_EXPORT_PLUGIN(EffectFactory("kcm_kwin4_effect_" #name)) /** * @see KWIN_EFFECT_CONFIG_MULTIPLE */ #define KWIN_EFFECT_CONFIG_SINGLE( name, classname ) \ registerPlugin(#name); /** * The declaration of the factory to export the effect */ #define KWIN_EFFECT_CONFIG_FACTORY K_PLUGIN_FACTORY_DECLARATION(EffectFactory) /** * @short Manager class that handles all the effects. * * This class creates Effect objects and calls it's appropriate methods. * * Effect objects can call methods of this class to interact with the * workspace, e.g. to activate or move a specific window, change current * desktop or create a special input window to receive mouse and keyboard * events. **/ class KWIN_EXPORT EffectsHandler : public QObject { Q_OBJECT friend class Effect; public: EffectsHandler(CompositingType type); virtual ~EffectsHandler(); // for use by effects virtual void prePaintScreen(ScreenPrePaintData& data, int time) = 0; virtual void paintScreen(int mask, QRegion region, ScreenPaintData& data) = 0; virtual void postPaintScreen() = 0; virtual void prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time) = 0; virtual void paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) = 0; virtual void postPaintWindow(EffectWindow* w) = 0; virtual void paintEffectFrame(EffectFrame* frame, QRegion region, double opacity, double frameOpacity) = 0; virtual void drawWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) = 0; virtual void buildQuads(EffectWindow* w, WindowQuadList& quadList) = 0; virtual QVariant kwinOption(KWinOption kwopt) = 0; // Functions for handling input - e.g. when an Expose-like effect is shown, an input window // covering the whole screen is created and all mouse events will be intercepted by it. // The effect's windowInputMouseEvent() will get called with such events. virtual Window createInputWindow(Effect* e, int x, int y, int w, int h, const QCursor& cursor) = 0; Window createInputWindow(Effect* e, const QRect& r, const QCursor& cursor); virtual Window createFullScreenInputWindow(Effect* e, const QCursor& cursor); virtual void destroyInputWindow(Window w) = 0; virtual QPoint cursorPos() const = 0; virtual bool grabKeyboard(Effect* effect) = 0; virtual void ungrabKeyboard() = 0; /** * Retrieve the proxy class for an effect if it has one. Will return NULL if * the effect isn't loaded or doesn't have a proxy class. */ virtual void* getProxy(QString name) = 0; // Mouse polling virtual void startMousePolling() = 0; virtual void stopMousePolling() = 0; virtual void checkElectricBorder(const QPoint &pos, Time time) = 0; virtual void reserveElectricBorder(ElectricBorder border) = 0; virtual void unreserveElectricBorder(ElectricBorder border) = 0; virtual void reserveElectricBorderSwitching(bool reserve) = 0; // functions that allow controlling windows/desktop virtual void activateWindow(EffectWindow* c) = 0; virtual EffectWindow* activeWindow() const = 0 ; virtual void moveWindow(EffectWindow* w, const QPoint& pos, bool snap = false, double snapAdjust = 1.0) = 0; virtual void windowToDesktop(EffectWindow* w, int desktop) = 0; virtual void windowToScreen(EffectWindow* w, int screen) = 0; virtual void setShowingDesktop(bool showing) = 0; // Activities /** * @returns The ID of the current activity. */ virtual QString currentActivity() const = 0; // Desktops /** * @returns The ID of the current desktop. */ virtual int currentDesktop() const = 0; /** * @returns Total number of desktops currently in existence. */ virtual int numberOfDesktops() const = 0; /** * Set the current desktop to @a desktop. */ virtual void setCurrentDesktop(int desktop) = 0; /** * Sets the total number of desktops to @a desktops. */ virtual void setNumberOfDesktops(int desktops) = 0; /** * @returns The size of desktop layout in grid units. */ virtual QSize desktopGridSize() const = 0; /** * @returns The width of desktop layout in grid units. */ virtual int desktopGridWidth() const = 0; /** * @returns The height of desktop layout in grid units. */ virtual int desktopGridHeight() const = 0; /** * @returns The width of desktop layout in pixels. */ virtual int workspaceWidth() const = 0; /** * @returns The height of desktop layout in pixels. */ virtual int workspaceHeight() const = 0; /** * @returns The ID of the desktop at the point @a coords or 0 if no desktop exists at that * point. @a coords is to be in grid units. */ virtual int desktopAtCoords(QPoint coords) const = 0; /** * @returns The coords of desktop @a id in grid units. */ virtual QPoint desktopGridCoords(int id) const = 0; /** * @returns The coords of the top-left corner of desktop @a id in pixels. */ virtual QPoint desktopCoords(int id) const = 0; /** * @returns The ID of the desktop above desktop @a id. Wraps around to the bottom of * the layout if @a wrap is set. If @a id is not set use the current one. */ virtual int desktopAbove(int desktop = 0, bool wrap = true) const = 0; /** * @returns The ID of the desktop to the right of desktop @a id. Wraps around to the * left of the layout if @a wrap is set. If @a id is not set use the current one. */ virtual int desktopToRight(int desktop = 0, bool wrap = true) const = 0; /** * @returns The ID of the desktop below desktop @a id. Wraps around to the top of the * layout if @a wrap is set. If @a id is not set use the current one. */ virtual int desktopBelow(int desktop = 0, bool wrap = true) const = 0; /** * @returns The ID of the desktop to the left of desktop @a id. Wraps around to the * right of the layout if @a wrap is set. If @a id is not set use the current one. */ virtual int desktopToLeft(int desktop = 0, bool wrap = true) const = 0; virtual QString desktopName(int desktop) const = 0; virtual bool optionRollOverDesktops() const = 0; virtual int activeScreen() const = 0; // Xinerama virtual int numScreens() const = 0; // Xinerama virtual int screenNumber(const QPoint& pos) const = 0; // Xinerama virtual QRect clientArea(clientAreaOption, int screen, int desktop) const = 0; virtual QRect clientArea(clientAreaOption, const EffectWindow* c) const = 0; virtual QRect clientArea(clientAreaOption, const QPoint& p, int desktop) const = 0; /** * Factor by which animation speed in the effect should be modified (multiplied). * If configurable in the effect itself, the option should have also 'default' * animation speed. The actual value should be determined using animationTime(). * Note: The factor can be also 0, so make sure your code can cope with 0ms time * if used manually. */ virtual double animationTimeFactor() const = 0; virtual WindowQuadType newWindowQuadType() = 0; virtual EffectWindow* findWindow(WId id) const = 0; virtual EffectWindowList stackingOrder() const = 0; // window will be temporarily painted as if being at the top of the stack virtual void setElevatedWindow(EffectWindow* w, bool set) = 0; virtual void setTabBoxWindow(EffectWindow*) = 0; virtual void setTabBoxDesktop(int) = 0; virtual EffectWindowList currentTabBoxWindowList() const = 0; virtual void refTabBox() = 0; virtual void unrefTabBox() = 0; virtual void closeTabBox() = 0; virtual QList< int > currentTabBoxDesktopList() const = 0; virtual int currentTabBoxDesktop() const = 0; virtual EffectWindow* currentTabBoxWindow() const = 0; virtual void setActiveFullScreenEffect(Effect* e) = 0; virtual Effect* activeFullScreenEffect() const = 0; /** * Schedules the entire workspace to be repainted next time. * If you call it during painting (including prepaint) then it does not * affect the current painting. **/ virtual void addRepaintFull() = 0; virtual void addRepaint(const QRect& r) = 0; virtual void addRepaint(const QRegion& r) = 0; virtual void addRepaint(int x, int y, int w, int h) = 0; CompositingType compositingType() const; virtual unsigned long xrenderBufferPicture() = 0; virtual void reconfigure() = 0; /** Makes KWin core watch PropertyNotify events for the given atom, or stops watching if reg is false (must be called the same number of times as registering). Events are sent using Effect::propertyNotify(). Note that even events that haven't been registered for can be received. */ virtual void registerPropertyType(long atom, bool reg) = 0; virtual QByteArray readRootProperty(long atom, long type, int format) const = 0; virtual void deleteRootProperty(long atom) const = 0; /** * Returns @a true if the active window decoration has shadow API hooks. */ virtual bool hasDecorationShadows() const = 0; /** * Returns @a true if the window decorations use the alpha channel, and @a false otherwise. * @since 4.5 */ virtual bool decorationsHaveAlpha() const = 0; /** * Returns @a true if the window decorations support blurring behind the decoration, and @a false otherwise * @since 4.6 */ virtual bool decorationSupportsBlurBehind() const = 0; /** * Creates a new frame object. If the frame does not have a static size * then it will be located at @a position with @a alignment. A * non-static frame will automatically adjust its size to fit the contents. * @returns A new @ref EffectFrame. It is the responsibility of the caller to delete the * EffectFrame. * @since 4.6 */ virtual EffectFrame* effectFrame(EffectFrameStyle style, bool staticSize = true, const QPoint& position = QPoint(-1, -1), Qt::Alignment alignment = Qt::AlignCenter) const = 0; /** * Allows an effect to trigger a reload of itself. * This can be used by an effect which needs to be reloaded when screen geometry changes. * It is possible that the effect cannot be loaded again as it's supported method does no longer * hold. * @param effect The effect to reload * @since 4.8 **/ virtual void reloadEffect(Effect *effect) = 0; /** * Sends message over DCOP to reload given effect. * @param effectname effect's name without "kwin4_effect_" prefix. * Can be called from effect's config module to apply config changes. **/ static void sendReloadMessage(const QString& effectname); /** * @return @ref KConfigGroup which holds given effect's config options **/ static KConfigGroup effectConfig(const QString& effectname); Q_SIGNALS: /** * Signal emitted when the current desktop changed. * @param oldDesktop The previously current desktop * @param newDesktop The new current desktop * @since 4.7 **/ void desktopChanged(int oldDesktop, int newDesktop); /** * Signal emitted when the number of currently existing desktops is changed. * @param old The previous number of desktops in used. * @see EffectsHandler::numberOfDesktops. * @since 4.7 */ void numberDesktopsChanged(int old); /** * Signal emitted when a new window has been added to the Workspace. * @param w The added window * @since 4.7 **/ void windowAdded(EffectWindow *w); /** * Signal emitted when a window is being removed from the Workspace. * An effect which wants to animate the window closing should connect * to this signal and reference the window by using * @link EffectWindow::refWindow * @param w The window which is being closed * @since 4.7 **/ void windowClosed(EffectWindow *w); /** * Signal emitted when a window get's activated. * @param w The new active window, or @c NULL if there is no active window. * @since 4.7 **/ void windowActivated(EffectWindow *w); /** * Signal emitted when a window is deleted. * This means that a closed window is not referenced any more. * An effect bookkeeping the closed windows should connect to this * signal to clean up the internal references. * @param w The window which is going to be deleted. * @see EffectWindow::refWindow * @see EffectWindow::unrefWindow * @see windowClosed * @since 4.7 **/ void windowDeleted(EffectWindow *w); /** * Signal emitted when a user begins a window move or resize operation. * To figure out whether the user resizes or moves the window use * @link EffectWindow::isUserMove or @link EffectWindow::isUserResize. * Whenever the geometry is updated the signal @link windowStepUserMovedResized * is emitted with the current geometry. * The move/resize operation ends with the signal @link windowFinishUserMovedResized. * Only one window can be moved/resized by the user at the same time! * @param w The window which is being moved/resized * @see windowStepUserMovedResized * @see windowFinishUserMovedResized * @see EffectWindow::isUserMove * @see EffectWindow::isUserResize * @since 4.7 **/ void windowStartUserMovedResized(EffectWindow *w); /** * Signal emitted during a move/resize operation when the user changed the geometry. * Please note: KWin supports two operation modes. In one mode all changes are applied * instantly. This means the window's geometry matches the passed in @p geometry. In the * other mode the geometry is changed after the user ended the move/resize mode. * The @p geometry differs from the window's geometry. Also the window's pixmap still has * the same size as before. Depending what the effect wants to do it would be recommended * to scale/translate the window. * @param w The window which is being moved/resized * @param geometry The geometry of the window in the current move/resize step. * @see windowStartUserMovedResized * @see windowFinishUserMovedResized * @see EffectWindow::isUserMove * @see EffectWindow::isUserResize * @since 4.7 **/ void windowStepUserMovedResized(EffectWindow *w, const QRect &geometry); /** * Signal emitted when the user finishes move/resize of window @p w. * @param w The window which has been moved/resized * @see windowStartUserMovedResized * @see windowFinishUserMovedResized * @since 4.7 **/ void windowFinishUserMovedResized(EffectWindow *w); /** * Signal emitted when the maximized state of the window @p w changed. * A window can be in one of four states: * @li restored: both @p horizontal and @p vertical are @c false * @li horizontally maximized: @p horizontal is @c true and @p vertical is @c false * @li vertically maximized: @p horizontal is @c false and @p vertical is @c true * @li completely maximized: both @p horizontal and @p vertical are @C true * @param w The window whose maximized state changed * @param horizontal If @c true maximized horizontally * @param vertical If @c true maximized vertically * @since 4.7 **/ void windowMaximizedStateChanged(EffectWindow *w, bool horizontal, bool vertical); /** * Signal emitted when the geometry or shape of a window changed. * This is caused if the window changes geometry without user interaction. * E.g. the decoration is changed. This is in opposite to windowUserMovedResized * which is caused by direct user interaction. * @param w The window whose geometry changed * @param old The previous geometry * @see windowUserMovedResized * @since 4.7 **/ void windowGeometryShapeChanged(EffectWindow *w, const QRect &old); /** * Signal emitted when the windows opacity is changed. * @param w The window whose opacity level is changed. * @param oldOpacity The previous opacity level * @param newOpacity The new opacity level * @since 4.7 **/ void windowOpacityChanged(EffectWindow *w, qreal oldOpacity, qreal newOpacity); /** * Signal emitted when a window got minimized. * @param w The window which was minimized * @since 4.7 **/ void windowMinimized(EffectWindow *w); /** * Signal emitted when a window got unminimized. * @param w The window which was unminimized * @since 4.7 **/ void windowUnminimized(EffectWindow *w); /** * Signal emitted when an area of a window is scheduled for repainting. * Use this signal in an effect if another area needs to be synced as well. * @param w The window which is scheduled for repainting * @param r The damaged rect * @since 4.7 **/ void windowDamaged(EffectWindow *w, const QRect &r); /** * Signal emitted when a tabbox is added. * An effect who wants to replace the tabbox with itself should use @link refTabBox. * @param mode The TabBoxMode. * @see refTabBox * @see tabBoxClosed * @see tabBoxUpdated * @see tabBoxKeyEvent * @since 4.7 **/ void tabBoxAdded(int mode); /** * Signal emitted when the TabBox was closed by KWin core. * An effect which referenced the TabBox should use @link unrefTabBox to unref again. * @see unrefTabBox * @see tabBoxAdded * @since 4.7 **/ void tabBoxClosed(); /** * Signal emitted when the selected TabBox window changed or the TabBox List changed. * An effect should only response to this signal if it referenced the TabBox with @link refTabBox. * @see refTabBox * @see currentTabBoxWindowList * @see currentTabBoxDesktopList * @see currentTabBoxWindow * @see currentTabBoxDesktop * @since 4.7 **/ void tabBoxUpdated(); /** * Signal emitted when a key event, which is not handled by TabBox directly is, happens while * TabBox is active. An effect might use the key event to e.g. change the selected window. * An effect should only response to this signal if it referenced the TabBox with @link refTabBox. * @param event The key event not handled by TabBox directly * @see refTabBox * @since 4.7 **/ void tabBoxKeyEvent(QKeyEvent* event); void currentTabAboutToChange(EffectWindow* from, EffectWindow* to); void tabAdded(EffectWindow* from, EffectWindow* to); // from merged with to void tabRemoved(EffectWindow* c, EffectWindow* group); // c removed from group /** * Signal emitted when mouse changed. * If an effect needs to get updated mouse positions, it needs to first call @link startMousePolling. * For a fullscreen effect it is better to use an input window and react on @link windowInputMouseEvent. * @param pos The new mouse position * @param oldpos The previously mouse position * @param buttons The pressed mouse buttons * @param oldbuttons The previously pressed mouse buttons * @param modifiers Pressed keyboard modifiers * @param oldmodifiers Previously pressed keyboard modifiers. * @see startMousePolling * @since 4.7 **/ void mouseChanged(const QPoint& pos, const QPoint& oldpos, Qt::MouseButtons buttons, Qt::MouseButtons oldbuttons, Qt::KeyboardModifiers modifiers, Qt::KeyboardModifiers oldmodifiers); /** * Receives events registered for using @link registerPropertyType. * Use readProperty() to get the property data. * Note that the property may be already set on the window, so doing the same * processing from windowAdded() (e.g. simply calling propertyNotify() from it) * is usually needed. * @param w The window whose property changed, is @c null if it is a root window property * @param atom The property * @since 4.7 */ void propertyNotify(EffectWindow* w, long atom); /** * Requests to show an outline. An effect providing to show an outline should * connect to the signal and render an outline. * The outline should be shown till the signal is emitted again with a new * geometry or the @link hideOutline signal is emitted. * @param outline The geometry of the outline to render. * @see hideOutline * @since 4.7 **/ void showOutline(const QRect& outline); /** * Signal emitted when the outline should no longer be shown. * @see showOutline * @since 4.7 **/ void hideOutline(); /** * Signal emitted after the screen geometry changed (e.g. add of a monitor). * Effects using displayWidth()/displayHeight() to cache information should * react on this signal and update the caches. * @param size The new screen size * @since 4.8 **/ void screenGeometryChanged(const QSize &size); protected: QVector< EffectPair > loaded_effects; QHash< QString, KLibrary* > effect_libraries; QList< InputWindowPair > input_windows; //QHash< QString, EffectFactory* > effect_factories; CompositingType compositing_type; }; /** * @short Representation of a window used by/for Effect classes. * * The purpose is to hide internal data and also to serve as a single * representation for the case when Client/Unmanaged becomes Deleted. **/ class KWIN_EXPORT EffectWindow : public QObject { Q_OBJECT public: /** Flags explaining why painting should be disabled */ enum { /** Window will not be painted */ PAINT_DISABLED = 1 << 0, /** Window will not be painted because it is deleted */ PAINT_DISABLED_BY_DELETE = 1 << 1, /** Window will not be painted because of which desktop it's on */ PAINT_DISABLED_BY_DESKTOP = 1 << 2, /** Window will not be painted because it is minimized */ PAINT_DISABLED_BY_MINIMIZE = 1 << 3, /** Window will not be painted because it is not the active window in a client group */ PAINT_DISABLED_BY_TAB_GROUP = 1 << 4, /** Window will not be painted because it's not on the current activity */ PAINT_DISABLED_BY_ACTIVITY = 1 << 5 }; EffectWindow(QObject *parent = NULL); virtual ~EffectWindow(); virtual void enablePainting(int reason) = 0; virtual void disablePainting(int reason) = 0; virtual bool isPaintingEnabled() = 0; void addRepaint(const QRect& r); void addRepaint(int x, int y, int w, int h); void addRepaintFull(); + void addLayerRepaint(const QRect& r); + void addLayerRepaint(int x, int y, int w, int h); virtual void refWindow() = 0; virtual void unrefWindow() = 0; bool isDeleted() const; bool isMinimized() const; double opacity() const; bool hasAlpha() const; bool isOnCurrentActivity() const; bool isOnActivity(QString id) const; bool isOnAllActivities() const; bool isOnDesktop(int d) const; bool isOnCurrentDesktop() const; bool isOnAllDesktops() const; int desktop() const; // prefer isOnXXX() int x() const; int y() const; int width() const; int height() const; /** * By how much the window wishes to grow/shrink at least. Usually QSize(1,1). * MAY BE DISOBEYED BY THE WM! It's only for information, do NOT rely on it at all. */ QSize basicUnit() const; QRect geometry() const; virtual QRegion shape() const = 0; int screen() const; /** @internal Do not use */ bool hasOwnShape() const; // only for shadow effect, for now QPoint pos() const; QSize size() const; QRect rect() const; bool isMovable() const; bool isMovableAcrossScreens() const; bool isUserMove() const; bool isUserResize() const; QRect iconGeometry() const; /** * Geometry of the actual window contents inside the whole (including decorations) window. */ QRect contentsRect() const; /** * Geometry of the transparent rect in the decoration. * May be different from contentsRect() if the decoration is extended into the client area. * @since 4.5 */ virtual QRect decorationInnerRect() const = 0; bool hasDecoration() const; virtual QByteArray readProperty(long atom, long type, int format) const = 0; virtual void deleteProperty(long atom) const = 0; QString caption() const; QPixmap icon() const; QString windowClass() const; QString windowRole() const; virtual const EffectWindowGroup* group() const = 0; /** * Returns whether the window is a desktop background window (the one with wallpaper). * See _NET_WM_WINDOW_TYPE_DESKTOP at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ bool isDesktop() const; /** * Returns whether the window is a dock (i.e. a panel). * See _NET_WM_WINDOW_TYPE_DOCK at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ bool isDock() const; /** * Returns whether the window is a standalone (detached) toolbar window. * See _NET_WM_WINDOW_TYPE_TOOLBAR at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ bool isToolbar() const; /** * Returns whether the window is a torn-off menu. * See _NET_WM_WINDOW_TYPE_MENU at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ bool isMenu() const; /** * Returns whether the window is a "normal" window, i.e. an application or any other window * for which none of the specialized window types fit. * See _NET_WM_WINDOW_TYPE_NORMAL at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ bool isNormalWindow() const; // normal as in 'NET::Normal or NET::Unknown non-transient' /** * Returns whether the window is any of special windows types (desktop, dock, splash, ...), * i.e. window types that usually don't have a window frame and the user does not use window * management (moving, raising,...) on them. */ bool isSpecialWindow() const; /** * Returns whether the window is a dialog window. * See _NET_WM_WINDOW_TYPE_DIALOG at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ bool isDialog() const; /** * Returns whether the window is a splashscreen. Note that many (especially older) applications * do not support marking their splash windows with this type. * See _NET_WM_WINDOW_TYPE_SPLASH at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ bool isSplash() const; /** * Returns whether the window is a utility window, such as a tool window. * See _NET_WM_WINDOW_TYPE_UTILITY at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ bool isUtility() const; /** * Returns whether the window is a dropdown menu (i.e. a popup directly or indirectly open * from the applications menubar). * See _NET_WM_WINDOW_TYPE_DROPDOWN_MENU at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ bool isDropdownMenu() const; /** * Returns whether the window is a popup menu (that is not a torn-off or dropdown menu). * See _NET_WM_WINDOW_TYPE_POPUP_MENU at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ bool isPopupMenu() const; // a context popup, not dropdown, not torn-off /** * Returns whether the window is a tooltip. * See _NET_WM_WINDOW_TYPE_TOOLTIP at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ bool isTooltip() const; /** * Returns whether the window is a window with a notification. * See _NET_WM_WINDOW_TYPE_NOTIFICATION at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ bool isNotification() const; /** * Returns whether the window is a combobox popup. * See _NET_WM_WINDOW_TYPE_COMBO at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ bool isComboBox() const; /** * Returns whether the window is a Drag&Drop icon. * See _NET_WM_WINDOW_TYPE_DND at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ bool isDNDIcon() const; /** * Returns the NETWM window type * See http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ NET::WindowType windowType() const; /** * Returns whether the window is managed by KWin (it has control over its placement and other * aspects, as opposed to override-redirect windows that are entirely handled by the application). */ bool isManaged() const; // whether it's managed or override-redirect /** * Returns whether or not the window can accept keyboard focus. */ bool acceptsFocus() const; /** * Returns whether or not the window is kept above all other windows. */ bool keepAbove() const; bool isModal() const; virtual EffectWindow* findModal() = 0; virtual EffectWindowList mainWindows() const = 0; /** * Returns whether the window should be excluded from window switching effects. * @since 4.5 */ bool isSkipSwitcher() const; /** * Returns the unmodified window quad list. Can also be used to force rebuilding. */ virtual WindowQuadList buildQuads(bool force = false) const = 0; void minimize(); void unminimize(); void closeWindow() const; bool isCurrentTab() const; /** * Can be used to by effects to store arbitrary data in the EffectWindow. */ virtual void setData(int role, const QVariant &data) = 0; virtual QVariant data(int role) const = 0; }; class KWIN_EXPORT EffectWindowGroup { public: virtual ~EffectWindowGroup(); virtual EffectWindowList members() const = 0; }; class KWIN_EXPORT GlobalShortcutsEditor : public KShortcutsEditor { public: GlobalShortcutsEditor(QWidget *parent); }; /** * @short Vertex class * * A vertex is one position in a window. WindowQuad consists of four WindowVertex objects * and represents one part of a window. **/ class KWIN_EXPORT WindowVertex { public: double x() const; double y() const; void move(double x, double y); void setX(double x); void setY(double y); double originalX() const; double originalY() const; double textureX() const; double textureY() const; WindowVertex(); WindowVertex(double x, double y, double tx, double ty); private: friend class WindowQuad; friend class WindowQuadList; double px, py; // position double ox, oy; // origional position double tx, ty; // texture coords }; /** * @short Class representing one area of a window. * * WindowQuads consists of four WindowVertex objects and represents one part of a window. */ // NOTE: This class expects the (original) vertices to be in the clockwise order starting from topleft. class KWIN_EXPORT WindowQuad { public: explicit WindowQuad(WindowQuadType type, int id = -1); WindowQuad makeSubQuad(double x1, double y1, double x2, double y2) const; WindowVertex& operator[](int index); const WindowVertex& operator[](int index) const; WindowQuadType type() const; int id() const; bool decoration() const; bool effect() const; double left() const; double right() const; double top() const; double bottom() const; double originalLeft() const; double originalRight() const; double originalTop() const; double originalBottom() const; bool smoothNeeded() const; bool isTransformed() const; private: friend class WindowQuadList; WindowVertex verts[ 4 ]; WindowQuadType quadType; // 0 - contents, 1 - decoration int quadID; }; class KWIN_EXPORT WindowQuadList : public QList< WindowQuad > { public: WindowQuadList splitAtX(double x) const; WindowQuadList splitAtY(double y) const; WindowQuadList makeGrid(int maxquadsize) const; WindowQuadList makeRegularGrid(int xSubdivisions, int ySubdivisions) const; WindowQuadList select(WindowQuadType type) const; WindowQuadList filterOut(WindowQuadType type) const; bool smoothNeeded() const; void makeArrays(float** vertices, float** texcoords, const QSizeF &size, bool yInverted) const; bool isTransformed() const; }; class KWIN_EXPORT WindowPrePaintData { public: int mask; /** * Region that will be painted, in screen coordinates. **/ QRegion paint; /** * The clip region will be substracted from paint region of following windows. * I.e. window will definitely cover it's clip region **/ QRegion clip; WindowQuadList quads; /** * Simple helper that sets data to say the window will be painted as non-opaque. * Takes also care of changing the regions. */ void setTranslucent(); /** * Helper to mark that this window will be transformed **/ void setTransformed(); }; class KWIN_EXPORT WindowPaintData { public: WindowPaintData(EffectWindow* w); /** * Window opacity, in range 0 = transparent to 1 = fully opaque * Opacity for contents is opacity*contents_opacity, the same * way for decoration. */ double opacity; double contents_opacity; double decoration_opacity; double xScale; double yScale; double zScale; int xTranslate; int yTranslate; double zTranslate; /** * Saturation of the window, in range [0; 1] * 1 means that the window is unchanged, 0 means that it's completely * unsaturated (greyscale). 0.5 would make the colors less intense, * but not completely grey * Use EffectsHandler::saturationSupported() to find out whether saturation * is supported by the system, otherwise this value has no effect. **/ double saturation; /** * Brightness of the window, in range [0; 1] * 1 means that the window is unchanged, 0 means that it's completely * black. 0.5 would make it 50% darker than usual **/ double brightness; WindowQuadList quads; /** * Shader to be used for rendering, if any. */ GLShader* shader; RotationData* rotation; }; class KWIN_EXPORT ScreenPaintData { public: ScreenPaintData(); double xScale; double yScale; double zScale; int xTranslate; int yTranslate; double zTranslate; RotationData* rotation; }; class KWIN_EXPORT ScreenPrePaintData { public: int mask; QRegion paint; }; class KWIN_EXPORT RotationData { public: RotationData(); enum RotationAxis { XAxis, YAxis, ZAxis }; RotationAxis axis; float angle; float xRotationPoint; float yRotationPoint; float zRotationPoint; }; /** * @short Helper class for restricting painting area only to allowed area. * * This helper class helps specifying areas that should be painted, clipping * out the rest. The simplest usage is creating an object on the stack * and giving it the area that is allowed to be painted to. When the object * is destroyed, the restriction will be removed. * Note that all painting code must use paintArea() to actually perform the clipping. */ class KWIN_EXPORT PaintClipper { public: /** * Calls push(). */ PaintClipper(const QRegion& allowed_area); /** * Calls pop(). */ ~PaintClipper(); /** * Allows painting only in the given area. When areas have been already * specified, painting is allowed only in the intersection of all areas. */ static void push(const QRegion& allowed_area); /** * Removes the given area. It must match the top item in the stack. */ static void pop(const QRegion& allowed_area); /** * Returns true if any clipping should be performed. */ static bool clip(); /** * If clip() returns true, this function gives the resulting area in which * painting is allowed. It is usually simpler to use the helper Iterator class. */ static QRegion paintArea(); /** * Helper class to perform the clipped painting. The usage is: * @code * for ( PaintClipper::Iterator iterator; * !iterator.isDone(); * iterator.next()) * { // do the painting, possibly use iterator.boundingRect() * } * @endcode */ class KWIN_EXPORT Iterator { public: Iterator(); ~Iterator(); bool isDone(); void next(); QRect boundingRect() const; private: struct Data; Data* data; }; private: QRegion area; static QStack< QRegion >* areas; }; /** * @internal */ template class KWIN_EXPORT Motion { public: /** * Creates a new motion object. "Strength" is the amount of * acceleration that is applied to the object when the target * changes and "smoothness" relates to how fast the object * can change its direction and speed. */ explicit Motion(T initial, double strength, double smoothness); /** * Creates an exact copy of another motion object, including * position, target and velocity. */ Motion(const Motion &other); ~Motion(); inline T value() const { return m_value; } inline void setValue(const T value) { m_value = value; } inline T target() const { return m_target; } inline void setTarget(const T target) { m_start = m_value; m_target = target; } inline T velocity() const { return m_velocity; } inline void setVelocity(const T velocity) { m_velocity = velocity; } inline double strength() const { return m_strength; } inline void setStrength(const double strength) { m_strength = strength; } inline double smoothness() const { return m_smoothness; } inline void setSmoothness(const double smoothness) { m_smoothness = smoothness; } inline T startValue() { return m_start; } /** * The distance between the current position and the target. */ inline T distance() const { return m_target - m_value; } /** * Calculates the new position if not at the target. Called * once per frame only. */ void calculate(const int msec); /** * Place the object on top of the target immediately, * bypassing all movement calculation. */ void finish(); private: T m_value; T m_start; T m_target; T m_velocity; double m_strength; double m_smoothness; }; /** * @short A single 1D motion dynamics object. * * This class represents a single object that can be moved around a * 1D space. Although it can be used directly by itself it is * recommended to use a motion manager instead. */ class KWIN_EXPORT Motion1D : public Motion { public: explicit Motion1D(double initial = 0.0, double strength = 0.08, double smoothness = 4.0); Motion1D(const Motion1D &other); ~Motion1D(); }; /** * @short A single 2D motion dynamics object. * * This class represents a single object that can be moved around a * 2D space. Although it can be used directly by itself it is * recommended to use a motion manager instead. */ class KWIN_EXPORT Motion2D : public Motion { public: explicit Motion2D(QPointF initial = QPointF(), double strength = 0.08, double smoothness = 4.0); Motion2D(const Motion2D &other); ~Motion2D(); }; /** * @short Helper class for motion dynamics in KWin effects. * * This motion manager class is intended to help KWin effect authors * move windows across the screen smoothly and naturally. Once * windows are registered by the manager the effect can issue move * commands with the moveWindow() methods. The position of any * managed window can be determined in realtime by the * transformedGeometry() method. As the manager knows if any windows * are moving at any given time it can also be used as a notifier as * to see whether the effect is active or not. */ class KWIN_EXPORT WindowMotionManager { public: /** * Creates a new window manager object. */ explicit WindowMotionManager(bool useGlobalAnimationModifier = true); ~WindowMotionManager(); /** * Register a window for managing. */ void manage(EffectWindow *w); /** * Register a list of windows for managing. */ inline void manage(EffectWindowList list) { for (int i = 0; i < list.size(); i++) manage(list.at(i)); } /** * Deregister a window. All transformations applied to the * window will be permanently removed and cannot be recovered. */ void unmanage(EffectWindow *w); /** * Deregister all windows, returning the manager to its * originally initiated state. */ void unmanageAll(); /** * Determine the new positions for windows that have not * reached their target. Called once per frame, usually in * prePaintScreen(). Remember to set the * Effect::PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS flag. */ void calculate(int time); /** * Modify a registered window's paint data to make it appear * at its real location on the screen. Usually called in * paintWindow(). Remember to flag the window as having been * transformed in prePaintWindow() by calling * WindowPrePaintData::setTransformed() */ void apply(EffectWindow *w, WindowPaintData &data); /** * Set all motion targets and values back to where the * windows were before transformations. The same as * unmanaging then remanaging all windows. */ void reset(); /** * Resets the motion target and current value of a single * window. */ void reset(EffectWindow *w); /** * Ask the manager to move the window to the target position * with the specified scale. If `yScale` is not provided or * set to 0.0, `scale` will be used as the scale in the * vertical direction as well as in the horizontal direction. */ void moveWindow(EffectWindow *w, QPoint target, double scale = 1.0, double yScale = 0.0); /** * This is an overloaded method, provided for convenience. * * Ask the manager to move the window to the target rectangle. * Automatically determines scale. */ inline void moveWindow(EffectWindow *w, QRect target) { // TODO: Scale might be slightly different in the comparison due to rounding moveWindow(w, target.topLeft(), target.width() / double(w->width()), target.height() / double(w->height())); } /** * Retrieve the current tranformed geometry of a registered * window. */ QRectF transformedGeometry(EffectWindow *w) const; /** * Sets the current transformed geometry of a registered window to the given geometry. * @see transformedGeometry * @since 4.5 */ void setTransformedGeometry(EffectWindow *w, const QRectF &geometry); /** * Retrieve the current target geometry of a registered * window. */ QRectF targetGeometry(EffectWindow *w) const; /** * Return the window that has its transformed geometry under * the specified point. It is recommended to use the stacking * order as it's what the user sees, but it is slightly * slower to process. */ EffectWindow* windowAtPoint(QPoint point, bool useStackingOrder = true) const; /** * Return a list of all currently registered windows. */ inline EffectWindowList managedWindows() const { return m_managedWindows.keys(); } /** * Returns whether or not a specified window is being managed * by this manager object. */ inline bool isManaging(EffectWindow *w) const { return m_managedWindows.contains(w); } /** * Returns whether or not this manager object is actually * managing any windows or not. */ inline bool managingWindows() const { return !m_managedWindows.empty(); } /** * Returns whether all windows have reached their targets yet * or not. Can be used to see if an effect should be * processed and displayed or not. */ inline bool areWindowsMoving() const { return !m_movingWindowsSet.isEmpty(); } /** * Returns whether a window has reached its targets yet * or not. */ inline bool isWindowMoving(EffectWindow *w) const { return m_movingWindowsSet.contains(w); } private: bool m_useGlobalAnimationModifier; struct WindowMotion { // TODO: Rotation, etc? Motion2D translation; // Absolute position Motion2D scale; // xScale and yScale }; QHash m_managedWindows; QSet m_movingWindowsSet; }; /** * @short Helper class for displaying text and icons in frames. * * Paints text and/or and icon with an optional frame around them. The * available frames includes one that follows the default Plasma theme and * another that doesn't. * It is recommended to use this class whenever displaying text. */ class KWIN_EXPORT EffectFrame { public: EffectFrame(); virtual ~EffectFrame(); /** * Delete any existing textures to free up graphics memory. They will * be automatically recreated the next time they are required. */ virtual void free() = 0; /** * Render the frame. */ virtual void render(QRegion region = infiniteRegion(), double opacity = 1.0, double frameOpacity = 1.0) = 0; virtual void setPosition(const QPoint& point) = 0; /** * Set the text alignment for static frames and the position alignment * for non-static. */ virtual void setAlignment(Qt::Alignment alignment) = 0; virtual Qt::Alignment alignment() const = 0; virtual void setGeometry(const QRect& geometry, bool force = false) = 0; virtual const QRect& geometry() const = 0; virtual void setText(const QString& text) = 0; virtual const QString& text() const = 0; virtual void setFont(const QFont& font) = 0; virtual const QFont& font() const = 0; /** * Set the icon that will appear on the left-hand size of the frame. */ virtual void setIcon(const QPixmap& icon) = 0; virtual const QPixmap& icon() const = 0; virtual void setIconSize(const QSize& size) = 0; virtual const QSize& iconSize() const = 0; /** * Sets the geometry of a selection. * To remove the selection set a null rect. * @param selection The geometry of the selection in screen coordinates. **/ virtual void setSelection(const QRect& selection) = 0; /** * @param shader The GLShader for rendering. **/ virtual void setShader(GLShader* shader) = 0; /** * @returns The GLShader used for rendering or null if none. **/ virtual GLShader* shader() const = 0; /** * @returns The style of this EffectFrame. **/ virtual EffectFrameStyle style() const = 0; /** * If @p enable is @c true cross fading between icons and text is enabled * By default disabled. Use setCrossFadeProgress to cross fade. * Cross Fading is currently only available if OpenGL is used. * @param enable @c true enables cross fading, @c false disables it again * @see isCrossFade * @see setCrossFadeProgress * @since 4.6 **/ void enableCrossFade(bool enable); /** * @returns @c true if cross fading is enabled, @c false otherwise * @see enableCrossFade * @since 4.6 **/ bool isCrossFade() const; /** * Sets the current progress for cross fading the last used icon/text * with current icon/text to @p progress. * A value of 0.0 means completely old icon/text, a value of 1.0 means * completely current icon/text. * Default value is 1.0. You have to enable cross fade before using it. * Cross Fading is currently only available if OpenGL is used. * @see enableCrossFade * @see isCrossFade * @see crossFadeProgress * @since 4.6 **/ void setCrossFadeProgress(qreal progress); /** * @returns The current progress for cross fading * @see setCrossFadeProgress * @see enableCrossFade * @see isCrossFade * @since 4.6 **/ qreal crossFadeProgress() const; private: EffectFramePrivate* const d; }; /** * Pointer to the global EffectsHandler object. **/ extern KWIN_EXPORT EffectsHandler* effects; /*************************************************************** WindowVertex ***************************************************************/ inline WindowVertex::WindowVertex() : px(0), py(0), tx(0), ty(0) { } inline WindowVertex::WindowVertex(double _x, double _y, double _tx, double _ty) : px(_x), py(_y), ox(_x), oy(_y), tx(_tx), ty(_ty) { } inline double WindowVertex::x() const { return px; } inline double WindowVertex::y() const { return py; } inline double WindowVertex::originalX() const { return ox; } inline double WindowVertex::originalY() const { return oy; } inline double WindowVertex::textureX() const { return tx; } inline double WindowVertex::textureY() const { return ty; } inline void WindowVertex::move(double x, double y) { px = x; py = y; } inline void WindowVertex::setX(double x) { px = x; } inline void WindowVertex::setY(double y) { py = y; } /*************************************************************** WindowQuad ***************************************************************/ inline WindowQuad::WindowQuad(WindowQuadType t, int id) : quadType(t) , quadID(id) { } inline WindowVertex& WindowQuad::operator[](int index) { assert(index >= 0 && index < 4); return verts[ index ]; } inline const WindowVertex& WindowQuad::operator[](int index) const { assert(index >= 0 && index < 4); return verts[ index ]; } inline WindowQuadType WindowQuad::type() const { assert(quadType != WindowQuadError); return quadType; } inline int WindowQuad::id() const { return quadID; } inline bool WindowQuad::decoration() const { assert(quadType != WindowQuadError); return quadType == WindowQuadDecoration; } inline bool WindowQuad::effect() const { assert(quadType != WindowQuadError); return quadType >= EFFECT_QUAD_TYPE_START; } inline bool WindowQuad::isTransformed() const { return !(verts[ 0 ].px == verts[ 0 ].ox && verts[ 0 ].py == verts[ 0 ].oy && verts[ 1 ].px == verts[ 1 ].ox && verts[ 1 ].py == verts[ 1 ].oy && verts[ 2 ].px == verts[ 2 ].ox && verts[ 2 ].py == verts[ 2 ].oy && verts[ 3 ].px == verts[ 3 ].ox && verts[ 3 ].py == verts[ 3 ].oy); } inline double WindowQuad::left() const { return qMin(verts[ 0 ].px, qMin(verts[ 1 ].px, qMin(verts[ 2 ].px, verts[ 3 ].px))); } inline double WindowQuad::right() const { return qMax(verts[ 0 ].px, qMax(verts[ 1 ].px, qMax(verts[ 2 ].px, verts[ 3 ].px))); } inline double WindowQuad::top() const { return qMin(verts[ 0 ].py, qMin(verts[ 1 ].py, qMin(verts[ 2 ].py, verts[ 3 ].py))); } inline double WindowQuad::bottom() const { return qMax(verts[ 0 ].py, qMax(verts[ 1 ].py, qMax(verts[ 2 ].py, verts[ 3 ].py))); } inline double WindowQuad::originalLeft() const { return verts[ 0 ].ox; } inline double WindowQuad::originalRight() const { return verts[ 2 ].ox; } inline double WindowQuad::originalTop() const { return verts[ 0 ].oy; } inline double WindowQuad::originalBottom() const { return verts[ 2 ].oy; } /*************************************************************** Motion ***************************************************************/ template Motion::Motion(T initial, double strength, double smoothness) : m_value(initial) , m_start(initial) , m_target(initial) , m_velocity() , m_strength(strength) , m_smoothness(smoothness) { } template Motion::Motion(const Motion &other) : m_value(other.value()) , m_start(other.target()) , m_target(other.target()) , m_velocity(other.velocity()) , m_strength(other.strength()) , m_smoothness(other.smoothness()) { } template Motion::~Motion() { } template void Motion::calculate(const int msec) { if (m_value == m_target && m_velocity == T()) // At target and not moving return; // Poor man's time independent calculation int steps = qMax(1, msec / 5); for (int i = 0; i < steps; i++) { T diff = m_target - m_value; T strength = diff * m_strength; m_velocity = (m_smoothness * m_velocity + strength) / (m_smoothness + 1.0); m_value += m_velocity; } } template void Motion::finish() { m_value = m_target; m_velocity = T(); } } // namespace /** @} */ #endif // KWINEFFECTS_H diff --git a/scene.cpp b/scene.cpp index 2f30621d8..a2dd25cf1 100644 --- a/scene.cpp +++ b/scene.cpp @@ -1,607 +1,594 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ /* (NOTE: The compositing code is work in progress. As such this design documentation may get outdated in some areas.) The base class for compositing, implementing shared functionality between the OpenGL and XRender backends. Design: When compositing is turned on, XComposite extension is used to redirect drawing of windows to pixmaps and XDamage extension is used to get informed about damage (changes) to window contents. This code is mostly in composite.cpp . Workspace::performCompositing() starts one painting pass. Painting is done by painting the screen, which in turn paints every window. Painting can be affected using effects, which are chained. E.g. painting a screen means that actually paintScreen() of the first effect is called, which possibly does modifications and calls next effect's paintScreen() and so on, until Scene::finalPaintScreen() is called. There are 3 phases of every paint (not necessarily done together): The pre-paint phase, the paint phase and the post-paint phase. The pre-paint phase is used to find out about how the painting will be actually done (i.e. what the effects will do). For example when only a part of the screen needs to be updated and no effect will do any transformation it is possible to use an optimized paint function. How the painting will be done is controlled by the mask argument, see PAINT_WINDOW_* and PAINT_SCREEN_* flags in scene.h . For example an effect that decides to paint a normal windows as translucent will need to modify the mask in its prePaintWindow() to include the PAINT_WINDOW_TRANSLUCENT flag. The paintWindow() function will then get the mask with this flag turned on and will also paint using transparency. The paint pass does the actual painting, based on the information collected using the pre-paint pass. After running through the effects' paintScreen() either paintGenericScreen() or optimized paintSimpleScreen() are called. Those call paintWindow() on windows (not necessarily all), possibly using clipping to optimize performance and calling paintWindow() first with only PAINT_WINDOW_OPAQUE to paint the opaque parts and then later with PAINT_WINDOW_TRANSLUCENT to paint the transparent parts. Function paintWindow() again goes through effects' paintWindow() until finalPaintWindow() is called, which calls the window's performPaint() to do the actual painting. The post-paint can be used for cleanups and is also used for scheduling repaints during the next painting pass for animations. Effects wanting to repaint certain parts can manually damage them during post-paint and repaint of these parts will be done during the next paint pass. */ #include "scene.h" #include #include "client.h" #include "deleted.h" #include "effects.h" #include "lanczosfilter.h" #include "overlaywindow.h" #include "shadow.h" #include #include "thumbnailitem.h" namespace KWin { //**************************************** // Scene //**************************************** Scene* scene = 0; Scene::Scene(Workspace* ws) : QObject(ws) , lastRenderTime(0) , wspace(ws) , has_waitSync(false) , lanczos_filter(new LanczosFilter()) , m_overlayWindow(new OverlayWindow()) { last_time.invalidate(); // Initialize the timer } Scene::~Scene() { delete lanczos_filter; delete m_overlayWindow; } // returns mask and possibly modified region void Scene::paintScreen(int* mask, QRegion* region) { *mask = (*region == QRegion(0, 0, displayWidth(), displayHeight())) ? 0 : PAINT_SCREEN_REGION; updateTimeDiff(); // preparation step static_cast(effects)->startPaint(); ScreenPrePaintData pdata; pdata.mask = *mask; pdata.paint = *region; effects->prePaintScreen(pdata, time_diff); *mask = pdata.mask; *region = pdata.paint; if (*mask & (PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS)) { // Region painting is not possible with transformations, // because screen damage doesn't match transformed positions. *mask &= ~PAINT_SCREEN_REGION; *region = infiniteRegion(); } else if (*mask & PAINT_SCREEN_REGION) { // make sure not to go outside visible screen *region &= QRegion(0, 0, displayWidth(), displayHeight()); } else { // whole screen, not transformed, force region to be full *region = QRegion(0, 0, displayWidth(), displayHeight()); } painted_region = *region; if (*mask & PAINT_SCREEN_BACKGROUND_FIRST) { paintBackground(*region); } ScreenPaintData data; effects->paintScreen(*mask, *region, data); foreach (Window * w, stacking_order) { effects->postPaintWindow(effectWindow(w)); } effects->postPaintScreen(); *region |= painted_region; // make sure not to go outside of the screen area *region &= QRegion(0, 0, displayWidth(), displayHeight()); // make sure all clipping is restored Q_ASSERT(!PaintClipper::clip()); } // Compute time since the last painting pass. void Scene::updateTimeDiff() { if (!last_time.isValid()) { // Painting has been idle (optimized out) for some time, // which means time_diff would be huge and would break animations. // Simply set it to one (zero would mean no change at all and could // cause problems). time_diff = 1; last_time.start(); } else time_diff = last_time.restart(); if (time_diff < 0) // check time rollback time_diff = 1; } // Painting pass is optimized away. void Scene::idle() { // Don't break time since last paint for the next pass. last_time.invalidate(); } // the function that'll be eventually called by paintScreen() above void Scene::finalPaintScreen(int mask, QRegion region, ScreenPaintData& data) { if (mask & (PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS)) paintGenericScreen(mask, data); else paintSimpleScreen(mask, region); } // The generic painting code that can handle even transformations. // It simply paints bottom-to-top. void Scene::paintGenericScreen(int orig_mask, ScreenPaintData) { if (!(orig_mask & PAINT_SCREEN_BACKGROUND_FIRST)) { paintBackground(infiniteRegion()); } QList< Phase2Data > phase2; foreach (Window * w, stacking_order) { // bottom to top Toplevel* topw = w->window(); // Reset the repaint_region. // This has to be done here because many effects schedule a repaint for // the next frame within Effects::prePaintWindow. - topw->resetRepaints(topw->decorationRect()); - if (topw->hasShadow()) { - topw->resetRepaints(topw->shadow()->shadowRegion().boundingRect()); - } + topw->resetRepaints(); WindowPrePaintData data; data.mask = orig_mask | (w->isOpaque() ? PAINT_WINDOW_OPAQUE : PAINT_WINDOW_TRANSLUCENT); w->resetPaintingEnabled(); data.paint = infiniteRegion(); // no clipping, so doesn't really matter data.clip = QRegion(); data.quads = w->buildQuads(); // preparation step effects->prePaintWindow(effectWindow(w), data, time_diff); #ifndef NDEBUG if (data.quads.isTransformed()) { kFatal(1212) << "Pre-paint calls are not allowed to transform quads!" ; } #endif if (!w->isPaintingEnabled()) { continue; } phase2.append(Phase2Data(w, infiniteRegion(), data.clip, data.mask, data.quads)); // transformations require window pixmap w->suspendUnredirect(data.mask & (PAINT_WINDOW_TRANSLUCENT | PAINT_SCREEN_TRANSFORMED | PAINT_WINDOW_TRANSFORMED)); } foreach (const Phase2Data & d, phase2) { paintWindow(d.window, d.mask, d.region, d.quads); } } // The optimized case without any transformations at all. // It can paint only the requested region and can use clipping // to reduce painting and improve performance. void Scene::paintSimpleScreen(int orig_mask, QRegion region) { assert((orig_mask & (PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS)) == 0); QList< QPair< Window*, Phase2Data > > phase2data; QRegion dirtyArea = region; for (int i = 0; // do prePaintWindow bottom to top i < stacking_order.count(); ++i) { Window* w = stacking_order[ i ]; Toplevel* topw = w->window(); WindowPrePaintData data; data.mask = orig_mask | (w->isOpaque() ? PAINT_WINDOW_OPAQUE : PAINT_WINDOW_TRANSLUCENT); w->resetPaintingEnabled(); data.paint = region; - data.paint |= topw->repaints().translated(topw->pos()); + data.paint |= topw->repaints(); data.paint |= topw->decorationPendingRegion(); // Reset the repaint_region. // This has to be done here because many effects schedule a repaint for // the next frame within Effects::prePaintWindow. - topw->resetRepaints(topw->decorationRect()); - if (topw->hasShadow()) { - topw->resetRepaints(topw->shadow()->shadowRegion().boundingRect()); - } + topw->resetRepaints(); // Clip out the decoration for opaque windows; the decoration is drawn in the second pass if (w->isOpaque()) { // the window is fully opaque data.clip = w->clientShape().translated(w->x(), w->y()); } else if (topw->hasAlpha() && topw->opacity() == 1.0) { // the window is partially opaque data.clip = (w->clientShape() & topw->opaqueRegion().translated(topw->clientPos())).translated(w->x(), w->y()); } else { data.clip = QRegion(); } data.quads = w->buildQuads(); // preparation step effects->prePaintWindow(effectWindow(w), data, time_diff); #ifndef NDEBUG if (data.quads.isTransformed()) { kFatal(1212) << "Pre-paint calls are not allowed to transform quads!" ; } #endif if (!w->isPaintingEnabled()) { w->suspendUnredirect(true); continue; } dirtyArea |= data.paint; // Schedule the window for painting phase2data.append(QPair< Window*, Phase2Data >(w,Phase2Data(w, data.paint, data.clip, data.mask, data.quads))); // no transformations, but translucency requires window pixmap w->suspendUnredirect(data.mask & PAINT_WINDOW_TRANSLUCENT); } // This is the occlusion culling pass QRegion allclips, upperTranslucentDamage; for (int i = phase2data.count() - 1; i >= 0; --i) { QPair< Window*, Phase2Data > *entry = &phase2data[i]; Phase2Data *data = &entry->second; - Toplevel *tlw = entry->first->window(); - // In case there is a window with a higher stackposition which has translucent regions - // (e.g. decorations) that still have to be drawn, we also have to repaint the current window - // in these particular regions - if (!(data->mask & PAINT_WINDOW_TRANSFORMED)) { - data->region |= (upperTranslucentDamage & tlw->decorationRect().translated(tlw->pos())); - } else { - data->region |= upperTranslucentDamage; - } + data->region |= upperTranslucentDamage; + // subtract the parts which will possibly been drawn as part of // a higher opaque window data->region -= allclips; // Here we rely on WindowPrePaintData::setTranslucent() to remove // the clip if needed. if (!data->clip.isEmpty() && !(data->mask & PAINT_WINDOW_TRANSFORMED)) { // clip away the opaque regions for all windows below this one allclips |= data->clip; // extend the translucent damage for windows below this by remaining (translucent) regions upperTranslucentDamage |= data->region - data->clip; } else { upperTranslucentDamage |= data->region; } } QRegion paintedArea; // Fill any areas of the root window not covered by opaque windows if (!(orig_mask & PAINT_SCREEN_BACKGROUND_FIRST)) { paintedArea = dirtyArea - allclips; paintBackground(paintedArea); } // Now walk the list bottom to top and draw the windows. for (int i = 0; i < phase2data.count(); ++i) { Phase2Data *data = &phase2data[i].second; // add all regions which have been drawn so far paintedArea |= data->region; data->region = paintedArea; paintWindow(data->window, data->mask, data->region, data->quads); } painted_region |= paintedArea; } void Scene::paintWindow(Window* w, int mask, QRegion region, WindowQuadList quads) { // no painting outside visible screen (and no transformations) region &= QRect(0, 0, displayWidth(), displayHeight()); if (region.isEmpty()) // completely clipped return; WindowPaintData data(w->window()->effectWindow()); data.quads = quads; effects->paintWindow(effectWindow(w), mask, region, data); // paint thumbnails on top of window EffectWindowImpl *wImpl = static_cast(effectWindow(w)); for (QHash >::const_iterator it = wImpl->thumbnails().constBegin(); it != wImpl->thumbnails().constEnd(); ++it) { if (it.value().isNull()) { continue; } ThumbnailItem *item = it.key(); if (!item->isVisible()) { continue; } EffectWindowImpl *thumb = it.value().data(); WindowPaintData thumbData(thumb); thumbData.opacity = data.opacity; QSizeF size = QSizeF(thumb->size()); size.scale(QSizeF(item->sceneBoundingRect().width(), item->sceneBoundingRect().height()), Qt::KeepAspectRatio); thumbData.xScale = size.width() / static_cast(thumb->width()); thumbData.yScale = size.height() / static_cast(thumb->height()); const int x = item->scenePos().x() + w->x() + (item->width() - size.width()) / 2; const int y = item->scenePos().y() + w->y() + (item->height() - size.height()) / 2; thumbData.xTranslate = x - thumb->x(); thumbData.yTranslate = y - thumb->y(); int thumbMask = PAINT_WINDOW_TRANSFORMED | PAINT_WINDOW_LANCZOS; if (thumbData.opacity == 1.0) { thumbMask |= PAINT_WINDOW_OPAQUE; } else { thumbMask |= PAINT_WINDOW_TRANSLUCENT; } if (item->isClip() && (x < wImpl->x() || x + size.width() > wImpl->x() + wImpl->width() || y < wImpl->y() || y + size.height() > wImpl->y() + wImpl->height())) { // don't render windows outside the containing window. // TODO: improve by spliting out the window quads which do not fit continue; } effects->drawWindow(thumb, thumbMask, QRegion(x, y, size.width(), size.height()), thumbData); } } // the function that'll be eventually called by paintWindow() above void Scene::finalPaintWindow(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data) { effects->drawWindow(w, mask, region, data); } // will be eventually called from drawWindow() void Scene::finalDrawWindow(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data) { if (mask & PAINT_WINDOW_LANCZOS) lanczos_filter->performPaint(w, mask, region, data); else w->sceneWindow()->performPaint(mask, region, data); } OverlayWindow* Scene::overlayWindow() { return m_overlayWindow; } void Scene::screenGeometryChanged(const QSize &size) { m_overlayWindow->resize(size); } //**************************************** // Scene::Window //**************************************** Scene::Window::Window(Toplevel * c) : toplevel(c) , filter(ImageFilterFast) , m_shadow(NULL) , disable_painting(0) , shape_valid(false) , cached_quad_list(NULL) { } Scene::Window::~Window() { delete cached_quad_list; delete m_shadow; } void Scene::Window::discardShape() { // it is created on-demand and cached, simply // reset the flag shape_valid = false; delete cached_quad_list; cached_quad_list = NULL; } // Find out the shape of the window using the XShape extension // or if shape is not set then simply it's the window geometry. QRegion Scene::Window::shape() const { if (!shape_valid) { Client* c = dynamic_cast< Client* >(toplevel); if (toplevel->shape() || (c != NULL && !c->mask().isEmpty())) { int count, order; XRectangle* rects = XShapeGetRectangles(display(), toplevel->frameId(), ShapeBounding, &count, &order); if (rects) { shape_region = QRegion(); for (int i = 0; i < count; ++i) shape_region += QRegion(rects[ i ].x, rects[ i ].y, rects[ i ].width, rects[ i ].height); XFree(rects); // make sure the shape is sane (X is async, maybe even XShape is broken) shape_region &= QRegion(0, 0, width(), height()); } else shape_region = QRegion(); } else shape_region = QRegion(0, 0, width(), height()); shape_valid = true; } return shape_region; } QRegion Scene::Window::clientShape() const { Client *c = dynamic_cast< Client* >(toplevel); if (c && c->isShade()) return QRegion(); const QRegion r = shape() & QRect(toplevel->clientPos(), toplevel->clientSize()); return r.isEmpty() ? QRegion() : r; } bool Scene::Window::isVisible() const { if (dynamic_cast< Deleted* >(toplevel) != NULL) return false; if (!toplevel->isOnCurrentDesktop()) return false; if (!toplevel->isOnCurrentActivity()) return false; if (Client* c = dynamic_cast< Client* >(toplevel)) return c->isShown(true); return true; // Unmanaged is always visible // TODO there may be transformations, so ignore this for now return !toplevel->geometry() .intersected(QRect(0, 0, displayWidth(), displayHeight())) .isEmpty(); } bool Scene::Window::isOpaque() const { return toplevel->opacity() == 1.0 && !toplevel->hasAlpha(); } bool Scene::Window::isPaintingEnabled() const { return !disable_painting; } void Scene::Window::resetPaintingEnabled() { disable_painting = 0; if (dynamic_cast< Deleted* >(toplevel) != NULL) disable_painting |= PAINT_DISABLED_BY_DELETE; if (!toplevel->isOnCurrentDesktop()) disable_painting |= PAINT_DISABLED_BY_DESKTOP; if (!toplevel->isOnCurrentActivity()) disable_painting |= PAINT_DISABLED_BY_ACTIVITY; if (Client* c = dynamic_cast< Client* >(toplevel)) { if (c->isMinimized()) disable_painting |= PAINT_DISABLED_BY_MINIMIZE; if (c->tabGroup() && c != c->tabGroup()->current()) disable_painting |= PAINT_DISABLED_BY_TAB_GROUP; else if (c->isHiddenInternal()) disable_painting |= PAINT_DISABLED; } } void Scene::Window::enablePainting(int reason) { disable_painting &= ~reason; } void Scene::Window::disablePainting(int reason) { disable_painting |= reason; } WindowQuadList Scene::Window::buildQuads(bool force) const { if (cached_quad_list != NULL && !force) return *cached_quad_list; WindowQuadList ret; if (toplevel->clientPos() == QPoint(0, 0) && toplevel->clientSize() == toplevel->visibleRect().size()) ret = makeQuads(WindowQuadContents, shape()); // has no decoration else { Client *client = dynamic_cast(toplevel); QRegion contents = clientShape(); QRegion center = toplevel->transparentRect(); QRegion decoration = (client && Workspace::self()->decorationHasAlpha() ? QRegion(client->decorationRect()) : shape()) - center; ret = makeQuads(WindowQuadContents, contents); if (!client || !(center.isEmpty() || client->isShade())) ret += makeQuads(WindowQuadDecoration, decoration); else { // this is a shaded client, we have to create four decoartion quads QRect left, top, right, bottom; client->layoutDecorationRects(left, top, right, bottom, Client::WindowRelative); ret += makeQuads(WindowQuadDecoration, top); ret += makeQuads(WindowQuadDecoration, bottom); ret += makeQuads(WindowQuadDecoration, left); ret += makeQuads(WindowQuadDecoration, right); } } if (m_shadow) { ret << m_shadow->shadowQuads(); } effects->buildQuads(static_cast(toplevel)->effectWindow(), ret); cached_quad_list = new WindowQuadList(ret); return ret; } WindowQuadList Scene::Window::makeQuads(WindowQuadType type, const QRegion& reg) const { WindowQuadList ret; foreach (const QRect & r, reg.rects()) { WindowQuad quad(type); // TODO asi mam spatne pravy dolni roh - bud tady, nebo v jinych castech quad[ 0 ] = WindowVertex(r.x(), r.y(), r.x(), r.y()); quad[ 1 ] = WindowVertex(r.x() + r.width(), r.y(), r.x() + r.width(), r.y()); quad[ 2 ] = WindowVertex(r.x() + r.width(), r.y() + r.height(), r.x() + r.width(), r.y() + r.height()); quad[ 3 ] = WindowVertex(r.x(), r.y() + r.height(), r.x(), r.y() + r.height()); ret.append(quad); } return ret; } //**************************************** // Scene::EffectFrame //**************************************** Scene::EffectFrame::EffectFrame(EffectFrameImpl* frame) : m_effectFrame(frame) { } Scene::EffectFrame::~EffectFrame() { } } // namespace diff --git a/toplevel.h b/toplevel.h index ff9933563..32581a513 100644 --- a/toplevel.h +++ b/toplevel.h @@ -1,615 +1,619 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_TOPLEVEL_H #define KWIN_TOPLEVEL_H #include #include #include #include #include #include "utils.h" #include "workspace.h" #include class NETWinInfo2; namespace KWin { class Workspace; class EffectWindowImpl; class Shadow; class Toplevel : public QObject, public KDecorationDefines { Q_OBJECT Q_PROPERTY(bool alpha READ hasAlpha CONSTANT) Q_PROPERTY(qulonglong frameId READ frameId) Q_PROPERTY(QRect geometry READ geometry NOTIFY geometryChanged) Q_PROPERTY(int height READ height) Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity NOTIFY opacityChanged) Q_PROPERTY(QPoint pos READ pos) Q_PROPERTY(int screen READ screen) Q_PROPERTY(QSize size READ size) Q_PROPERTY(int width READ width) Q_PROPERTY(qulonglong windowId READ window CONSTANT) Q_PROPERTY(int x READ x) Q_PROPERTY(int y READ y) Q_PROPERTY(int desktop READ desktop) Q_PROPERTY(QRect rect READ rect) Q_PROPERTY(QPoint clientPos READ clientPos) Q_PROPERTY(QSize clientSize READ clientSize) Q_PROPERTY(QByteArray resourceName READ resourceName) Q_PROPERTY(QByteArray resourceClass READ resourceClass) Q_PROPERTY(QByteArray windowRole READ windowRole) /** * Returns whether the window is a desktop background window (the one with wallpaper). * See _NET_WM_WINDOW_TYPE_DESKTOP at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool desktopWindow READ isDesktop) /** * Returns whether the window is a dock (i.e. a panel). * See _NET_WM_WINDOW_TYPE_DOCK at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool dock READ isDock) /** * Returns whether the window is a standalone (detached) toolbar window. * See _NET_WM_WINDOW_TYPE_TOOLBAR at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool toolbar READ isToolbar) /** * Returns whether the window is a torn-off menu. * See _NET_WM_WINDOW_TYPE_MENU at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool menu READ isMenu) /** * Returns whether the window is a "normal" window, i.e. an application or any other window * for which none of the specialized window types fit. * See _NET_WM_WINDOW_TYPE_NORMAL at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool normalWindow READ isNormalWindow) /** * Returns whether the window is a dialog window. * See _NET_WM_WINDOW_TYPE_DIALOG at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool dialog READ isDialog) /** * Returns whether the window is a splashscreen. Note that many (especially older) applications * do not support marking their splash windows with this type. * See _NET_WM_WINDOW_TYPE_SPLASH at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool splash READ isSplash) /** * Returns whether the window is a utility window, such as a tool window. * See _NET_WM_WINDOW_TYPE_UTILITY at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool utility READ isUtility) /** * Returns whether the window is a dropdown menu (i.e. a popup directly or indirectly open * from the applications menubar). * See _NET_WM_WINDOW_TYPE_DROPDOWN_MENU at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool dropdownMenu READ isDropdownMenu) /** * Returns whether the window is a popup menu (that is not a torn-off or dropdown menu). * See _NET_WM_WINDOW_TYPE_POPUP_MENU at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool popupMenu READ isPopupMenu) /** * Returns whether the window is a tooltip. * See _NET_WM_WINDOW_TYPE_TOOLTIP at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool tooltip READ isTooltip) /** * Returns whether the window is a window with a notification. * See _NET_WM_WINDOW_TYPE_NOTIFICATION at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool notification READ isNotification) /** * Returns whether the window is a combobox popup. * See _NET_WM_WINDOW_TYPE_COMBO at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool comboBox READ isComboBox) /** * Returns whether the window is a Drag&Drop icon. * See _NET_WM_WINDOW_TYPE_DND at http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(bool dndIcon READ isDNDIcon) /** * Returns the NETWM window type * See http://standards.freedesktop.org/wm-spec/wm-spec-latest.html . */ Q_PROPERTY(int windowType READ windowType) Q_PROPERTY(QStringList activities READ activities) /** * Whether this Toplevel is managed by KWin (it has control over its placement and other * aspects, as opposed to override-redirect windows that are entirely handled by the application). **/ Q_PROPERTY(bool managed READ isClient CONSTANT) /** * Whether this Toplevel represents an already deleted window and only kept for the compositor for animations. **/ Q_PROPERTY(bool deleted READ isDeleted CONSTANT) /** * Whether the window has an own shape **/ Q_PROPERTY(bool shaped READ shape NOTIFY shapedChanged) public: Toplevel(Workspace *ws); Window frameId() const; Window window() const; Workspace* workspace() const; QRect geometry() const; QSize size() const; QPoint pos() const; QRect rect() const; int x() const; int y() const; int width() const; int height() const; bool isOnScreen(int screen) const; // true if it's at least partially there int screen() const; // the screen where the center is virtual QPoint clientPos() const = 0; // inside of geometry() virtual QSize clientSize() const = 0; virtual QRect visibleRect() const; // the area the window occupies on the screen virtual QRect decorationRect() const; // rect including the decoration shadows virtual QRect transparentRect() const = 0; virtual QRegion decorationPendingRegion() const; // decoration region that needs to be repainted virtual bool isClient() const; virtual bool isDeleted() const; // prefer isXXX() instead // 0 for supported types means default for managed/unmanaged types NET::WindowType windowType(bool direct = false, int supported_types = 0) const; bool hasNETSupport() const; bool isDesktop() const; bool isDock() const; bool isToolbar() const; bool isMenu() const; bool isNormalWindow() const; // normal as in 'NET::Normal or NET::Unknown non-transient' bool isDialog() const; bool isSplash() const; bool isUtility() const; bool isDropdownMenu() const; bool isPopupMenu() const; // a context popup, not dropdown, not torn-off bool isTooltip() const; bool isNotification() const; bool isComboBox() const; bool isDNDIcon() const; virtual int desktop() const = 0; virtual QStringList activities() const = 0; bool isOnDesktop(int d) const; bool isOnActivity(const QString &activity) const; bool isOnCurrentDesktop() const; bool isOnCurrentActivity() const; bool isOnAllDesktops() const; bool isOnAllActivities() const; QByteArray windowRole() const; QByteArray sessionId(); QByteArray resourceName() const; QByteArray resourceClass() const; QByteArray wmCommand(); QByteArray wmClientMachine(bool use_localhost) const; Window wmClientLeader() const; pid_t pid() const; static bool resourceMatch(const Toplevel* c1, const Toplevel* c2); Pixmap windowPixmap(bool allow_create = true); // may return None (e.g. at a bad moment while resizing) bool readyForPainting() const; // true if the window has been already painted its contents Visual* visual() const; bool shape() const; void setOpacity(double opacity); double opacity() const; int depth() const; bool hasAlpha() const; virtual void setupCompositing(); virtual void finishCompositing(); bool updateUnredirectedState(); bool unredirected() const; void suspendUnredirect(bool suspend); Q_INVOKABLE void addRepaint(const QRect& r); Q_INVOKABLE void addRepaint(const QRegion& r); Q_INVOKABLE void addRepaint(int x, int y, int w, int h); + Q_INVOKABLE void addLayerRepaint(const QRect& r); + Q_INVOKABLE void addLayerRepaint(const QRegion& r); + Q_INVOKABLE void addLayerRepaint(int x, int y, int w, int h); Q_INVOKABLE virtual void addRepaintFull(); // these call workspace->addRepaint(), but first transform the damage if needed void addWorkspaceRepaint(const QRect& r); void addWorkspaceRepaint(int x, int y, int w, int h); QRegion repaints() const; - void resetRepaints(const QRect& r); + void resetRepaints(); QRegion damage() const; void resetDamage(const QRect& r); EffectWindowImpl* effectWindow(); const EffectWindowImpl* effectWindow() const; /** * @returns Whether the Toplevel has a Shadow or not * @see shadow **/ bool hasShadow() const; /** * Returns the pointer to the Toplevel's Shadow. A Shadow * is only available if Compositing is enabled and the corresponding X window * has the Shadow property set. * If a shadow is available @link hasShadow returns @c true. * @returns The Shadow belonging to this Toplevel, may be @c NULL. * @see hasShadow **/ const Shadow *shadow() const; Shadow *shadow(); /** * Updates the Shadow associated with this Toplevel from X11 Property. * Call this method when the Property changes or Compositing is started. **/ void getShadow(); /** * This method returns the area that the Toplevel window reports to be opaque. * It is supposed to only provide valuable information if @link hasAlpha is @c true . * @see hasAlpha **/ const QRegion& opaqueRegion() const; signals: void opacityChanged(KWin::Toplevel* toplevel, qreal oldOpacity); void damaged(KWin::Toplevel* toplevel, const QRect& damage); void propertyNotify(KWin::Toplevel* toplevel, long a); void geometryChanged(); void geometryShapeChanged(KWin::Toplevel* toplevel, const QRect& old); void windowClosed(KWin::Toplevel* toplevel, KWin::Deleted* deleted); void windowShown(KWin::Toplevel* toplevel); /** * Signal emitted when the window's shape state changed. That is if it did not have a shape * and received one or if the shape was withdrawn. Think of Chromium enabling/disabling KWin's * decoration. **/ void shapedChanged(); protected: virtual ~Toplevel(); void setWindowHandles(Window client, Window frame); void detectShape(Window id); virtual void propertyNotifyEvent(XPropertyEvent* e); virtual void damageNotifyEvent(XDamageNotifyEvent* e); Pixmap createWindowPixmap(); void discardWindowPixmap(); void addDamage(const QRect& r); void addDamage(int x, int y, int w, int h); void addDamageFull(); void getWmClientLeader(); void getWmClientMachine(); void setReadyForPainting(); /** * This function fetches the opaque region from this Toplevel. * Will only be called on corresponding property changes and for initialization. **/ void getWmOpaqueRegion(); void getResourceClass(); void getWindowRole(); virtual void debug(QDebug& stream) const = 0; void copyToDeleted(Toplevel* c); void disownDataPassedToDeleted(); friend QDebug& operator<<(QDebug& stream, const Toplevel*); void deleteEffectWindow(); virtual bool shouldUnredirect() const = 0; QRect geom; Visual* vis; int bit_depth; NETWinInfo2* info; bool ready_for_painting; QRegion repaints_region; // updating, repaint just requires repaint of that area + QRegion layer_repaints_region; private: static QByteArray staticWindowRole(WId); static QByteArray staticSessionId(WId); static QByteArray staticWmCommand(WId); static QByteArray staticWmClientMachine(WId); static Window staticWmClientLeader(WId); // when adding new data members, check also copyToDeleted() Window client; Window frame; Workspace* wspace; Pixmap window_pix; Damage damage_handle; QRegion damage_region; // damage is really damaged window (XDamage) and texture needs float damageRatio; bool is_shape; EffectWindowImpl* effect_window; QByteArray resource_name; QByteArray resource_class; QByteArray client_machine; WId wmClientLeaderWin; QByteArray window_role; bool unredirect; bool unredirectSuspend; // when unredirected, but pixmap is needed temporarily QRegion opaque_region; // when adding new data members, check also copyToDeleted() }; inline Window Toplevel::window() const { return client; } inline Window Toplevel::frameId() const { return frame; } inline void Toplevel::setWindowHandles(Window w, Window f) { assert(client == None && w != None); client = w; assert(frame == None && f != None); frame = f; } inline Workspace* Toplevel::workspace() const { return wspace; } inline QRect Toplevel::geometry() const { return geom; } inline QSize Toplevel::size() const { return geom.size(); } inline QPoint Toplevel::pos() const { return geom.topLeft(); } inline int Toplevel::x() const { return geom.x(); } inline int Toplevel::y() const { return geom.y(); } inline int Toplevel::width() const { return geom.width(); } inline int Toplevel::height() const { return geom.height(); } inline QRect Toplevel::rect() const { return QRect(0, 0, width(), height()); } inline QRegion Toplevel::decorationPendingRegion() const { return QRegion(); } inline bool Toplevel::readyForPainting() const { return ready_for_painting; } inline Visual* Toplevel::visual() const { return vis; } inline bool Toplevel::isDesktop() const { return windowType() == NET::Desktop; } inline bool Toplevel::isDock() const { return windowType() == NET::Dock; } inline bool Toplevel::isMenu() const { return windowType() == NET::Menu; } inline bool Toplevel::isToolbar() const { return windowType() == NET::Toolbar; } inline bool Toplevel::isSplash() const { return windowType() == NET::Splash; } inline bool Toplevel::isUtility() const { return windowType() == NET::Utility; } inline bool Toplevel::isDialog() const { return windowType() == NET::Dialog; } inline bool Toplevel::isNormalWindow() const { return windowType() == NET::Normal; } inline bool Toplevel::isDropdownMenu() const { return windowType() == NET::DropdownMenu; } inline bool Toplevel::isPopupMenu() const { return windowType() == NET::PopupMenu; } inline bool Toplevel::isTooltip() const { return windowType() == NET::Tooltip; } inline bool Toplevel::isNotification() const { return windowType() == NET::Notification; } inline bool Toplevel::isComboBox() const { return windowType() == NET::ComboBox; } inline bool Toplevel::isDNDIcon() const { return windowType() == NET::DNDIcon; } inline Pixmap Toplevel::windowPixmap(bool allow_create) { if (window_pix == None && allow_create) window_pix = createWindowPixmap(); return window_pix; } inline QRegion Toplevel::damage() const { return damage_region; } inline QRegion Toplevel::repaints() const { - return repaints_region; + return repaints_region.translated(pos()) | layer_repaints_region; } inline bool Toplevel::shape() const { return is_shape; } inline int Toplevel::depth() const { return bit_depth; } inline bool Toplevel::hasAlpha() const { return depth() == 32; } inline const QRegion& Toplevel::opaqueRegion() const { return opaque_region; } inline EffectWindowImpl* Toplevel::effectWindow() { return effect_window; } inline const EffectWindowImpl* Toplevel::effectWindow() const { return effect_window; } inline bool Toplevel::isOnAllDesktops() const { return desktop() == NET::OnAllDesktops; } inline bool Toplevel::isOnAllActivities() const { return activities().isEmpty(); } inline bool Toplevel::isOnDesktop(int d) const { return desktop() == d || /*desk == 0 ||*/ isOnAllDesktops(); } inline bool Toplevel::isOnActivity(const QString &activity) const { return activities().isEmpty() || activities().contains(activity); } inline bool Toplevel::isOnCurrentDesktop() const { return isOnDesktop(workspace()->currentDesktop()); } inline bool Toplevel::isOnCurrentActivity() const { return isOnActivity(Workspace::self()->currentActivity()); } inline QByteArray Toplevel::resourceName() const { return resource_name; // it is always lowercase } inline QByteArray Toplevel::resourceClass() const { return resource_class; // it is always lowercase } inline QByteArray Toplevel::windowRole() const { return window_role; } inline pid_t Toplevel::pid() const { return info->pid(); } inline bool Toplevel::unredirected() const { return unredirect; } QDebug& operator<<(QDebug& stream, const Toplevel*); QDebug& operator<<(QDebug& stream, const ToplevelList&); QDebug& operator<<(QDebug& stream, const ConstToplevelList&); KWIN_COMPARE_PREDICATE(WindowMatchPredicate, Toplevel, Window, cl->window() == value); KWIN_COMPARE_PREDICATE(FrameIdMatchPredicate, Toplevel, Window, cl->frameId() == value); } // namespace #endif