diff --git a/abstract_egl_backend.cpp b/abstract_egl_backend.cpp index 7b4f75668..e019d5c7d 100644 --- a/abstract_egl_backend.cpp +++ b/abstract_egl_backend.cpp @@ -1,498 +1,496 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 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 . *********************************************************************/ #include "abstract_egl_backend.h" #include "options.h" #include "platform.h" #include "wayland_server.h" #include #include #include // kwin libs #include // Qt #include #include namespace KWin { typedef GLboolean(*eglBindWaylandDisplayWL_func)(EGLDisplay dpy, wl_display *display); typedef GLboolean(*eglUnbindWaylandDisplayWL_func)(EGLDisplay dpy, wl_display *display); typedef GLboolean(*eglQueryWaylandBufferWL_func)(EGLDisplay dpy, struct wl_resource *buffer, EGLint attribute, EGLint *value); eglBindWaylandDisplayWL_func eglBindWaylandDisplayWL = nullptr; eglUnbindWaylandDisplayWL_func eglUnbindWaylandDisplayWL = nullptr; eglQueryWaylandBufferWL_func eglQueryWaylandBufferWL = nullptr; #ifndef EGL_WAYLAND_BUFFER_WL #define EGL_WAYLAND_BUFFER_WL 0x31D5 #endif #ifndef EGL_WAYLAND_PLANE_WL #define EGL_WAYLAND_PLANE_WL 0x31D6 #endif #ifndef EGL_WAYLAND_Y_INVERTED_WL #define EGL_WAYLAND_Y_INVERTED_WL 0x31DB #endif AbstractEglBackend::AbstractEglBackend() : OpenGLBackend() { } AbstractEglBackend::~AbstractEglBackend() = default; void AbstractEglBackend::unbindWaylandDisplay() { auto display = kwinApp()->platform()->sceneEglDisplay(); if (eglUnbindWaylandDisplayWL && display != EGL_NO_DISPLAY) { eglUnbindWaylandDisplayWL(display, *(WaylandServer::self()->display())); } } void AbstractEglBackend::cleanup() { cleanupGL(); doneCurrent(); eglDestroyContext(m_display, m_context); cleanupSurfaces(); eglReleaseThread(); } void AbstractEglBackend::cleanupSurfaces() { if (m_surface != EGL_NO_SURFACE) { eglDestroySurface(m_display, m_surface); } } bool AbstractEglBackend::initEglAPI() { EGLint major, minor; if (eglInitialize(m_display, &major, &minor) == EGL_FALSE) { qCWarning(KWIN_CORE) << "eglInitialize failed"; EGLint error = eglGetError(); if (error != EGL_SUCCESS) { qCWarning(KWIN_CORE) << "Error during eglInitialize " << error; } return false; } EGLint error = eglGetError(); if (error != EGL_SUCCESS) { qCWarning(KWIN_CORE) << "Error during eglInitialize " << error; return false; } qCDebug(KWIN_CORE) << "Egl Initialize succeeded"; if (eglBindAPI(isOpenGLES() ? EGL_OPENGL_ES_API : EGL_OPENGL_API) == EGL_FALSE) { qCCritical(KWIN_CORE) << "bind OpenGL API failed"; return false; } qCDebug(KWIN_CORE) << "EGL version: " << major << "." << minor; return true; } void AbstractEglBackend::initKWinGL() { initEGL(); GLPlatform *glPlatform = GLPlatform::instance(); glPlatform->detect(EglPlatformInterface); - if (GLPlatform::instance()->driver() == Driver_Intel) - options->setUnredirectFullscreen(false); // bug #252817 options->setGlPreferBufferSwap(options->glPreferBufferSwap()); // resolve autosetting if (options->glPreferBufferSwap() == Options::AutoSwapStrategy) options->setGlPreferBufferSwap('e'); // for unknown drivers - should not happen glPlatform->printResults(); initGL(EglPlatformInterface); } void AbstractEglBackend::initBufferAge() { setSupportsBufferAge(false); if (hasGLExtension(QByteArrayLiteral("EGL_EXT_buffer_age"))) { const QByteArray useBufferAge = qgetenv("KWIN_USE_BUFFER_AGE"); if (useBufferAge != "0") setSupportsBufferAge(true); } } void AbstractEglBackend::initWayland() { if (!WaylandServer::self()) { return; } if (hasGLExtension(QByteArrayLiteral("EGL_WL_bind_wayland_display"))) { eglBindWaylandDisplayWL = (eglBindWaylandDisplayWL_func)eglGetProcAddress("eglBindWaylandDisplayWL"); eglUnbindWaylandDisplayWL = (eglUnbindWaylandDisplayWL_func)eglGetProcAddress("eglUnbindWaylandDisplayWL"); eglQueryWaylandBufferWL = (eglQueryWaylandBufferWL_func)eglGetProcAddress("eglQueryWaylandBufferWL"); // only bind if not already done if (waylandServer()->display()->eglDisplay() != eglDisplay()) { if (!eglBindWaylandDisplayWL(eglDisplay(), *(WaylandServer::self()->display()))) { eglUnbindWaylandDisplayWL = nullptr; eglQueryWaylandBufferWL = nullptr; } else { waylandServer()->display()->setEglDisplay(eglDisplay()); } } } } void AbstractEglBackend::initClientExtensions() { // Get the list of client extensions const char* clientExtensionsCString = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); const QByteArray clientExtensionsString = QByteArray::fromRawData(clientExtensionsCString, qstrlen(clientExtensionsCString)); if (clientExtensionsString.isEmpty()) { // If eglQueryString() returned NULL, the implementation doesn't support // EGL_EXT_client_extensions. Expect an EGL_BAD_DISPLAY error. (void) eglGetError(); } m_clientExtensions = clientExtensionsString.split(' '); } bool AbstractEglBackend::hasClientExtension(const QByteArray &ext) const { return m_clientExtensions.contains(ext); } bool AbstractEglBackend::makeCurrent() { if (QOpenGLContext *context = QOpenGLContext::currentContext()) { // Workaround to tell Qt that no QOpenGLContext is current context->doneCurrent(); } const bool current = eglMakeCurrent(m_display, m_surface, m_surface, m_context); return current; } void AbstractEglBackend::doneCurrent() { eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } bool AbstractEglBackend::isOpenGLES() const { if (qstrcmp(qgetenv("KWIN_COMPOSE"), "O2ES") == 0) { return true; } return QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES; } bool AbstractEglBackend::createContext() { const QByteArray eglExtensions = eglQueryString(m_display, EGL_EXTENSIONS); const QList extensions = eglExtensions.split(' '); const bool haveRobustness = extensions.contains(QByteArrayLiteral("EGL_EXT_create_context_robustness")); const bool haveCreateContext = extensions.contains(QByteArrayLiteral("EGL_KHR_create_context")); EGLContext ctx = EGL_NO_CONTEXT; if (isOpenGLES()) { if (haveCreateContext && haveRobustness) { const EGLint context_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT, EGL_TRUE, EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT, EGL_LOSE_CONTEXT_ON_RESET_EXT, EGL_NONE }; ctx = eglCreateContext(m_display, config(), EGL_NO_CONTEXT, context_attribs); } if (ctx == EGL_NO_CONTEXT) { const EGLint context_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; ctx = eglCreateContext(m_display, config(), EGL_NO_CONTEXT, context_attribs); } } else { // Try to create a 3.1 core context if (options->glCoreProfile() && haveCreateContext) { if (haveRobustness) { const int attribs[] = { EGL_CONTEXT_MAJOR_VERSION_KHR, 3, EGL_CONTEXT_MINOR_VERSION_KHR, 1, EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR, EGL_LOSE_CONTEXT_ON_RESET_KHR, EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR, EGL_NONE }; ctx = eglCreateContext(m_display, config(), EGL_NO_CONTEXT, attribs); } if (ctx == EGL_NO_CONTEXT) { // try without robustness const EGLint attribs[] = { EGL_CONTEXT_MAJOR_VERSION_KHR, 3, EGL_CONTEXT_MINOR_VERSION_KHR, 1, EGL_NONE }; ctx = eglCreateContext(m_display, config(), EGL_NO_CONTEXT, attribs); } } if (ctx == EGL_NO_CONTEXT && haveRobustness && haveCreateContext) { const int attribs[] = { EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR, EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR, EGL_LOSE_CONTEXT_ON_RESET_KHR, EGL_NONE }; ctx = eglCreateContext(m_display, config(), EGL_NO_CONTEXT, attribs); } if (ctx == EGL_NO_CONTEXT) { // and last but not least: try without robustness const EGLint attribs[] = { EGL_NONE }; ctx = eglCreateContext(m_display, config(), EGL_NO_CONTEXT, attribs); } } if (ctx == EGL_NO_CONTEXT) { qCCritical(KWIN_CORE) << "Create Context failed"; return false; } m_context = ctx; return true; } void AbstractEglBackend::setEglDisplay(const EGLDisplay &display) { m_display = display; kwinApp()->platform()->setSceneEglDisplay(display); } AbstractEglTexture::AbstractEglTexture(SceneOpenGL::Texture *texture, AbstractEglBackend *backend) : SceneOpenGL::TexturePrivate() , q(texture) , m_backend(backend) , m_image(EGL_NO_IMAGE_KHR) { m_target = GL_TEXTURE_2D; } AbstractEglTexture::~AbstractEglTexture() { if (m_image != EGL_NO_IMAGE_KHR) { eglDestroyImageKHR(m_backend->eglDisplay(), m_image); } } OpenGLBackend *AbstractEglTexture::backend() { return m_backend; } bool AbstractEglTexture::loadTexture(WindowPixmap *pixmap) { const auto &buffer = pixmap->buffer(); if (buffer.isNull()) { if (updateFromFBO(pixmap->fbo())) { return true; } return false; } // try Wayland loading if (auto s = pixmap->surface()) { s->resetTrackedDamage(); } if (buffer->shmBuffer()) { return loadShmTexture(buffer); } else { return loadEglTexture(buffer); } } void AbstractEglTexture::updateTexture(WindowPixmap *pixmap) { const auto &buffer = pixmap->buffer(); if (buffer.isNull()) { const auto &fbo = pixmap->fbo(); if (!fbo.isNull()) { if (m_texture != fbo->texture()) { updateFromFBO(fbo); } return; } return; } auto s = pixmap->surface(); if (!buffer->shmBuffer()) { q->bind(); EGLImageKHR image = attach(buffer); q->unbind(); if (image != EGL_NO_IMAGE_KHR) { eglDestroyImageKHR(m_backend->eglDisplay(), m_image); m_image = image; } if (s) { s->resetTrackedDamage(); } return; } // shm fallback const QImage &image = buffer->data(); if (image.isNull() || !s) { return; } Q_ASSERT(image.size() == m_size); q->bind(); const QRegion damage = s->trackedDamage(); s->resetTrackedDamage(); // TODO: this should be shared with GLTexture::update if (GLPlatform::instance()->isGLES()) { if (s_supportsARGB32 && (image.format() == QImage::Format_ARGB32 || image.format() == QImage::Format_ARGB32_Premultiplied)) { const QImage im = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); for (const QRect &rect : damage.rects()) { glTexSubImage2D(m_target, 0, rect.x(), rect.y(), rect.width(), rect.height(), GL_BGRA_EXT, GL_UNSIGNED_BYTE, im.copy(rect).bits()); } } else { const QImage im = image.convertToFormat(QImage::Format_RGBA8888_Premultiplied); for (const QRect &rect : damage.rects()) { glTexSubImage2D(m_target, 0, rect.x(), rect.y(), rect.width(), rect.height(), GL_RGBA, GL_UNSIGNED_BYTE, im.copy(rect).bits()); } } } else { const QImage im = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); for (const QRect &rect : damage.rects()) { glTexSubImage2D(m_target, 0, rect.x(), rect.y(), rect.width(), rect.height(), GL_BGRA, GL_UNSIGNED_BYTE, im.copy(rect).bits()); } } q->unbind(); } bool AbstractEglTexture::loadShmTexture(const QPointer< KWayland::Server::BufferInterface > &buffer) { const QImage &image = buffer->data(); if (image.isNull()) { return false; } glGenTextures(1, &m_texture); q->setWrapMode(GL_CLAMP_TO_EDGE); q->setFilter(GL_LINEAR); q->bind(); const QSize &size = image.size(); // TODO: this should be shared with GLTexture(const QImage&, GLenum) GLenum format = 0; switch (image.format()) { case QImage::Format_ARGB32: case QImage::Format_ARGB32_Premultiplied: format = GL_RGBA8; break; case QImage::Format_RGB32: format = GL_RGB8; break; default: return false; } if (GLPlatform::instance()->isGLES()) { if (s_supportsARGB32 && format == GL_RGBA8) { const QImage im = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); glTexImage2D(m_target, 0, GL_BGRA_EXT, im.width(), im.height(), 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, im.bits()); } else { const QImage im = image.convertToFormat(QImage::Format_RGBA8888_Premultiplied); glTexImage2D(m_target, 0, GL_RGBA, im.width(), im.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, im.bits()); } } else { glTexImage2D(m_target, 0, format, size.width(), size.height(), 0, GL_BGRA, GL_UNSIGNED_BYTE, image.bits()); } q->unbind(); q->setYInverted(true); m_size = size; updateMatrix(); return true; } bool AbstractEglTexture::loadEglTexture(const QPointer< KWayland::Server::BufferInterface > &buffer) { if (!eglQueryWaylandBufferWL) { return false; } if (!buffer->resource()) { return false; } glGenTextures(1, &m_texture); q->setWrapMode(GL_CLAMP_TO_EDGE); q->setFilter(GL_LINEAR); q->bind(); m_image = attach(buffer); q->unbind(); if (EGL_NO_IMAGE_KHR == m_image) { qCDebug(KWIN_CORE) << "failed to create egl image"; q->discard(); return false; } return true; } EGLImageKHR AbstractEglTexture::attach(const QPointer< KWayland::Server::BufferInterface > &buffer) { EGLint format, yInverted; eglQueryWaylandBufferWL(m_backend->eglDisplay(), buffer->resource(), EGL_TEXTURE_FORMAT, &format); if (format != EGL_TEXTURE_RGB && format != EGL_TEXTURE_RGBA) { qCDebug(KWIN_CORE) << "Unsupported texture format: " << format; return EGL_NO_IMAGE_KHR; } if (!eglQueryWaylandBufferWL(m_backend->eglDisplay(), buffer->resource(), EGL_WAYLAND_Y_INVERTED_WL, &yInverted)) { // if EGL_WAYLAND_Y_INVERTED_WL is not supported wl_buffer should be treated as if value were EGL_TRUE yInverted = EGL_TRUE; } const EGLint attribs[] = { EGL_WAYLAND_PLANE_WL, 0, EGL_NONE }; EGLImageKHR image = eglCreateImageKHR(m_backend->eglDisplay(), EGL_NO_CONTEXT, EGL_WAYLAND_BUFFER_WL, (EGLClientBuffer)buffer->resource(), attribs); if (image != EGL_NO_IMAGE_KHR) { glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)image); m_size = buffer->size(); updateMatrix(); q->setYInverted(yInverted); } return image; } bool AbstractEglTexture::updateFromFBO(const QSharedPointer &fbo) { if (fbo.isNull()) { return false; } m_texture = fbo->texture(); m_size = fbo->size(); q->setWrapMode(GL_CLAMP_TO_EDGE); q->setFilter(GL_LINEAR); q->setYInverted(false); updateMatrix(); return true; } } diff --git a/client.cpp b/client.cpp index aaa99be64..c0f91e707 100644 --- a/client.cpp +++ b/client.cpp @@ -1,2148 +1,2139 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ // own #include "client.h" // kwin #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #endif #include "atoms.h" #include "client_machine.h" #include "composite.h" #include "cursor.h" #include "deleted.h" #include "focuschain.h" #include "group.h" #include "shadow.h" #include "workspace.h" #include "screenedge.h" #include "decorations/decorationbridge.h" #include "decorations/decoratedclient.h" #include #include // KDE #include #include // Qt #include #include #include #include #include #include #include #include #include // XLib #include #include // system #include #include // Put all externs before the namespace statement to allow the linker // to resolve them properly namespace KWin { const long ClientWinMask = XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_KEYMAP_STATE | XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_POINTER_MOTION | // need this, too! XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; // Creating a client: // - only by calling Workspace::createClient() // - it creates a new client and calls manage() for it // // Destroying a client: // - destroyClient() - only when the window itself has been destroyed // - releaseWindow() - the window is kept, only the client itself is destroyed /** * \class Client client.h * \brief The Client class encapsulates a window decoration frame. */ /** * This ctor is "dumb" - it only initializes data. All the real initialization * is done in manage(). */ Client::Client() : AbstractClient() , m_client() , m_wrapper() , m_frame() , m_activityUpdatesBlocked(false) , m_blockedActivityUpdatesRequireTransients(false) , m_moveResizeGrabWindow() , move_resize_has_keyboard_grab(false) , m_managed(false) , m_transientForId(XCB_WINDOW_NONE) , m_originalTransientForId(XCB_WINDOW_NONE) , shade_below(NULL) , m_motif(atoms->motif_wm_hints) , blocks_compositing(false) , shadeHoverTimer(NULL) , m_colormap(XCB_COLORMAP_NONE) , in_group(NULL) , tab_group(NULL) , ping_timer(NULL) , m_killHelperPID(0) , m_pingTimestamp(XCB_TIME_CURRENT_TIME) , m_userTime(XCB_TIME_CURRENT_TIME) // Not known yet , allowed_actions(0) , shade_geometry_change(false) , sm_stacking_order(-1) , activitiesDefined(false) , needsSessionInteract(false) , needsXWindowMove(false) , m_decoInputExtent() , m_focusOutTimer(nullptr) , m_clientSideDecorated(false) { // TODO: Do all as initialization syncRequest.counter = syncRequest.alarm = XCB_NONE; syncRequest.timeout = syncRequest.failsafeTimeout = NULL; syncRequest.lastTimestamp = xTime(); syncRequest.isPending = false; // Set the initial mapping state mapping_state = Withdrawn; info = NULL; shade_mode = ShadeNone; deleting = false; fullscreen_mode = FullScreenNone; hidden = false; noborder = false; app_noborder = false; ignore_focus_stealing = false; check_active_modal = false; max_mode = MaximizeRestore; //Client to workspace connections require that each //client constructed be connected to the workspace wrapper geom = QRect(0, 0, 100, 100); // So that decorations don't start with size being (0,0) client_size = QSize(100, 100); ready_for_painting = false; // wait for first damage or sync reply connect(clientMachine(), &ClientMachine::localhostChanged, this, &Client::updateCaption); connect(options, &Options::condensedTitleChanged, this, &Client::updateCaption); connect(this, &Client::moveResizeCursorChanged, this, [this] (Qt::CursorShape cursor) { xcb_cursor_t nativeCursor = Cursor::x11Cursor(cursor); m_frame.defineCursor(nativeCursor); if (m_decoInputExtent.isValid()) m_decoInputExtent.defineCursor(nativeCursor); if (isMoveResize()) { // changing window attributes doesn't change cursor if there's pointer grab active xcb_change_active_pointer_grab(connection(), nativeCursor, xTime(), XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW); } }); // SELI TODO: Initialize xsizehints?? } /** * "Dumb" destructor. */ Client::~Client() { if (m_killHelperPID && !::kill(m_killHelperPID, 0)) { // means the process is alive ::kill(m_killHelperPID, SIGTERM); m_killHelperPID = 0; } //SWrapper::Client::clientRelease(this); if (syncRequest.alarm != XCB_NONE) xcb_sync_destroy_alarm(connection(), syncRequest.alarm); assert(!isMoveResize()); assert(m_client == XCB_WINDOW_NONE); assert(m_wrapper == XCB_WINDOW_NONE); //assert( frameId() == None ); assert(!check_active_modal); for (auto it = m_connections.constBegin(); it != m_connections.constEnd(); ++it) { disconnect(*it); } } // Use destroyClient() or releaseWindow(), Client instances cannot be deleted directly void Client::deleteClient(Client* c) { delete c; } /** * Releases the window. The client has done its job and the window is still existing. */ void Client::releaseWindow(bool on_shutdown) { assert(!deleting); deleting = true; destroyWindowManagementInterface(); Deleted* del = NULL; if (!on_shutdown) { del = Deleted::create(this); } if (isMoveResize()) emit clientFinishUserMovedResized(this); emit windowClosed(this, del); finishCompositing(); RuleBook::self()->discardUsed(this, true); // Remove ForceTemporarily rules StackingUpdatesBlocker blocker(workspace()); if (isMoveResize()) leaveMoveResize(); finishWindowRules(); blockGeometryUpdates(); if (isOnCurrentDesktop() && isShown(true)) addWorkspaceRepaint(visibleRect()); // Grab X during the release to make removing of properties, setting to withdrawn state // and repareting to root an atomic operation (http://lists.kde.org/?l=kde-devel&m=116448102901184&w=2) grabXServer(); exportMappingState(WithdrawnState); setModal(false); // Otherwise its mainwindow wouldn't get focus hidden = true; // So that it's not considered visible anymore (can't use hideClient(), it would set flags) if (!on_shutdown) workspace()->clientHidden(this); m_frame.unmap(); // Destroying decoration would cause ugly visual effect destroyDecoration(); cleanGrouping(); if (!on_shutdown) { workspace()->removeClient(this); // Only when the window is being unmapped, not when closing down KWin (NETWM sections 5.5,5.7) info->setDesktop(0); info->setState(0, info->state()); // Reset all state flags } else untab(); xcb_connection_t *c = connection(); m_client.deleteProperty(atoms->kde_net_wm_user_creation_time); m_client.deleteProperty(atoms->net_frame_extents); m_client.deleteProperty(atoms->kde_net_wm_frame_strut); m_client.reparent(rootWindow(), x(), y()); xcb_change_save_set(c, XCB_SET_MODE_DELETE, m_client); m_client.selectInput(XCB_EVENT_MASK_NO_EVENT); if (on_shutdown) // Map the window, so it can be found after another WM is started m_client.map(); // TODO: Preserve minimized, shaded etc. state? else // Make sure it's not mapped if the app unmapped it (#65279). The app // may do map+unmap before we initially map the window by calling rawShow() from manage(). m_client.unmap(); m_client.reset(); m_wrapper.reset(); m_frame.reset(); //frame = None; unblockGeometryUpdates(); // Don't use GeometryUpdatesBlocker, it would now set the geometry if (!on_shutdown) { disownDataPassedToDeleted(); del->unrefWindow(); } checkNonExistentClients(); deleteClient(this); ungrabXServer(); } /** * Like releaseWindow(), but this one is called when the window has been already destroyed * (E.g. The application closed it) */ void Client::destroyClient() { assert(!deleting); deleting = true; destroyWindowManagementInterface(); Deleted* del = Deleted::create(this); if (isMoveResize()) emit clientFinishUserMovedResized(this); emit windowClosed(this, del); finishCompositing(ReleaseReason::Destroyed); RuleBook::self()->discardUsed(this, true); // Remove ForceTemporarily rules StackingUpdatesBlocker blocker(workspace()); if (isMoveResize()) leaveMoveResize(); finishWindowRules(); blockGeometryUpdates(); if (isOnCurrentDesktop() && isShown(true)) addWorkspaceRepaint(visibleRect()); setModal(false); hidden = true; // So that it's not considered visible anymore workspace()->clientHidden(this); destroyDecoration(); cleanGrouping(); workspace()->removeClient(this); m_client.reset(); // invalidate m_wrapper.reset(); m_frame.reset(); //frame = None; unblockGeometryUpdates(); // Don't use GeometryUpdatesBlocker, it would now set the geometry disownDataPassedToDeleted(); del->unrefWindow(); checkNonExistentClients(); deleteClient(this); } void Client::updateInputWindow() { if (!Xcb::Extensions::self()->isShapeInputAvailable()) return; QRegion region; if (!noBorder() && isDecorated()) { const QMargins &r = decoration()->resizeOnlyBorders(); const int left = r.left(); const int top = r.top(); const int right = r.right(); const int bottom = r.bottom(); if (left != 0 || top != 0 || right != 0 || bottom != 0) { region = QRegion(-left, -top, decoration()->size().width() + left + right, decoration()->size().height() + top + bottom); region = region.subtracted(decoration()->rect()); } } if (region.isEmpty()) { m_decoInputExtent.reset(); return; } QRect bounds = region.boundingRect(); input_offset = bounds.topLeft(); // Move the bounding rect to screen coordinates bounds.translate(geometry().topLeft()); // Move the region to input window coordinates region.translate(-input_offset); if (!m_decoInputExtent.isValid()) { const uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; const uint32_t values[] = {true, XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION }; m_decoInputExtent.create(bounds, XCB_WINDOW_CLASS_INPUT_ONLY, mask, values); if (mapping_state == Mapped) m_decoInputExtent.map(); } else { m_decoInputExtent.setGeometry(bounds); } const QVector rects = Xcb::regionToRects(region); xcb_shape_rectangles(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED, m_decoInputExtent, 0, 0, rects.count(), rects.constData()); } void Client::updateDecoration(bool check_workspace_pos, bool force) { if (!force && ((!isDecorated() && noBorder()) || (isDecorated() && !noBorder()))) return; QRect oldgeom = geometry(); QRect oldClientGeom = oldgeom.adjusted(borderLeft(), borderTop(), -borderRight(), -borderBottom()); blockGeometryUpdates(true); if (force) destroyDecoration(); if (!noBorder()) { createDecoration(oldgeom); } else destroyDecoration(); getShadow(); if (check_workspace_pos) checkWorkspacePosition(oldgeom, -2, oldClientGeom); updateInputWindow(); blockGeometryUpdates(false); updateFrameExtents(); } void Client::createDecoration(const QRect& oldgeom) { KDecoration2::Decoration *decoration = Decoration::DecorationBridge::self()->createDecoration(this); if (decoration) { QMetaObject::invokeMethod(decoration, "update", Qt::QueuedConnection); connect(decoration, &KDecoration2::Decoration::shadowChanged, this, &Toplevel::getShadow); connect(decoration, &KDecoration2::Decoration::resizeOnlyBordersChanged, this, &Client::updateInputWindow); connect(decoration, &KDecoration2::Decoration::bordersChanged, this, [this]() { updateFrameExtents(); GeometryUpdatesBlocker blocker(this); // TODO: this is obviously idempotent // calculateGravitation(true) would have to operate on the old border sizes // move(calculateGravitation(true)); // move(calculateGravitation(false)); QRect oldgeom = geometry(); plainResize(sizeForClientSize(clientSize()), ForceGeometrySet); if (!isShade()) checkWorkspacePosition(oldgeom); emit geometryShapeChanged(this, oldgeom); } ); } setDecoration(decoration); move(calculateGravitation(false)); plainResize(sizeForClientSize(clientSize()), ForceGeometrySet); if (Compositor::compositing()) { discardWindowPixmap(); } emit geometryShapeChanged(this, oldgeom); } void Client::destroyDecoration() { QRect oldgeom = geometry(); if (isDecorated()) { QPoint grav = calculateGravitation(true); AbstractClient::destroyDecoration(); plainResize(sizeForClientSize(clientSize()), ForceGeometrySet); move(grav); if (compositing()) discardWindowPixmap(); if (!deleting) { emit geometryShapeChanged(this, oldgeom); } } m_decoInputExtent.reset(); } void Client::layoutDecorationRects(QRect &left, QRect &top, QRect &right, QRect &bottom) const { if (!isDecorated()) { return; } QRect r = decoration()->rect(); NETStrut strut = info->frameOverlap(); // Ignore the overlap strut when compositing is disabled if (!compositing()) strut.left = strut.top = strut.right = strut.bottom = 0; else if (strut.left == -1 && strut.top == -1 && strut.right == -1 && strut.bottom == -1) { top = QRect(r.x(), r.y(), r.width(), r.height() / 3); left = QRect(r.x(), r.y() + top.height(), width() / 2, r.height() / 3); right = QRect(r.x() + left.width(), r.y() + top.height(), r.width() - left.width(), left.height()); bottom = QRect(r.x(), r.y() + top.height() + left.height(), r.width(), r.height() - left.height() - top.height()); return; } top = QRect(r.x(), r.y(), r.width(), borderTop() + strut.top); bottom = QRect(r.x(), r.y() + r.height() - borderBottom() - strut.bottom, r.width(), borderBottom() + strut.bottom); left = QRect(r.x(), r.y() + top.height(), borderLeft() + strut.left, r.height() - top.height() - bottom.height()); right = QRect(r.x() + r.width() - borderRight() - strut.right, r.y() + top.height(), borderRight() + strut.right, r.height() - top.height() - bottom.height()); } QRect Client::transparentRect() const { if (isShade()) return QRect(); NETStrut strut = info->frameOverlap(); // Ignore the strut when compositing is disabled or the decoration doesn't support it if (!compositing()) strut.left = strut.top = strut.right = strut.bottom = 0; else if (strut.left == -1 && strut.top == -1 && strut.right == -1 && strut.bottom == -1) return QRect(); const QRect r = QRect(clientPos(), clientSize()) .adjusted(strut.left, strut.top, -strut.right, -strut.bottom); if (r.isValid()) return r; return QRect(); } void Client::detectNoBorder() { if (shape()) { noborder = true; app_noborder = true; return; } switch(windowType()) { case NET::Desktop : case NET::Dock : case NET::TopMenu : case NET::Splash : case NET::Notification : case NET::OnScreenDisplay : noborder = true; app_noborder = true; break; case NET::Unknown : case NET::Normal : case NET::Toolbar : case NET::Menu : case NET::Dialog : case NET::Utility : noborder = false; break; default: abort(); } // NET::Override is some strange beast without clear definition, usually // just meaning "noborder", so let's treat it only as such flag, and ignore it as // a window type otherwise (SUPPORTED_WINDOW_TYPES_MASK doesn't include it) if (info->windowType(NET::OverrideMask) == NET::Override) { noborder = true; app_noborder = true; } } void Client::updateFrameExtents() { NETStrut strut; strut.left = borderLeft(); strut.right = borderRight(); strut.top = borderTop(); strut.bottom = borderBottom(); info->setFrameExtents(strut); } Xcb::Property Client::fetchGtkFrameExtents() const { return Xcb::Property(false, m_client, atoms->gtk_frame_extents, XCB_ATOM_CARDINAL, 0, 4); } void Client::readGtkFrameExtents(Xcb::Property &prop) { m_clientSideDecorated = !prop.isNull() && prop->type != 0; emit clientSideDecoratedChanged(); } void Client::detectGtkFrameExtents() { Xcb::Property prop = fetchGtkFrameExtents(); readGtkFrameExtents(prop); } /** * Resizes the decoration, and makes sure the decoration widget gets resize event * even if the size hasn't changed. This is needed to make sure the decoration * re-layouts (e.g. when maximization state changes, * the decoration may alter some borders, but the actual size * of the decoration stays the same). */ void Client::resizeDecoration() { triggerDecorationRepaint(); updateInputWindow(); } bool Client::noBorder() const { return noborder || isFullScreen(); } bool Client::userCanSetNoBorder() const { return !isFullScreen() && !isShade() && !tabGroup(); } void Client::setNoBorder(bool set) { if (!userCanSetNoBorder()) return; set = rules()->checkNoBorder(set); if (noborder == set) return; noborder = set; updateDecoration(true, false); updateWindowRules(Rules::NoBorder); } void Client::checkNoBorder() { setNoBorder(app_noborder); } bool Client::wantsShadowToBeRendered() const { return !isFullScreen() && maximizeMode() != MaximizeFull; } void Client::updateShape() { if (shape()) { // Workaround for #19644 - Shaped windows shouldn't have decoration if (!app_noborder) { // Only when shape is detected for the first time, still let the user to override app_noborder = true; noborder = rules()->checkNoBorder(true); updateDecoration(true); } if (noBorder()) { xcb_shape_combine(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, XCB_SHAPE_SK_BOUNDING, frameId(), clientPos().x(), clientPos().y(), window()); } } else if (app_noborder) { xcb_shape_mask(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, frameId(), 0, 0, XCB_PIXMAP_NONE); detectNoBorder(); app_noborder = noborder; noborder = rules()->checkNoBorder(noborder || m_motif.noBorder()); updateDecoration(true); } // Decoration mask (i.e. 'else' here) setting is done in setMask() // when the decoration calls it or when the decoration is created/destroyed updateInputShape(); if (compositing()) { addRepaintFull(); addWorkspaceRepaint(visibleRect()); // In case shape change removes part of this window } emit geometryShapeChanged(this, geometry()); } static Xcb::Window shape_helper_window(XCB_WINDOW_NONE); void Client::cleanupX11() { shape_helper_window.reset(); } void Client::updateInputShape() { if (hiddenPreview()) // Sets it to none, don't change return; if (Xcb::Extensions::self()->isShapeInputAvailable()) { // There appears to be no way to find out if a window has input // shape set or not, so always propagate the input shape // (it's the same like the bounding shape by default). // Also, build the shape using a helper window, not directly // in the frame window, because the sequence set-shape-to-frame, // remove-shape-of-client, add-input-shape-of-client has the problem // that after the second step there's a hole in the input shape // until the real shape of the client is added and that can make // the window lose focus (which is a problem with mouse focus policies) // TODO: It seems there is, after all - XShapeGetRectangles() - but maybe this is better if (!shape_helper_window.isValid()) shape_helper_window.create(QRect(0, 0, 1, 1)); shape_helper_window.resize(width(), height()); xcb_connection_t *c = connection(); xcb_shape_combine(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_BOUNDING, shape_helper_window, 0, 0, frameId()); xcb_shape_combine(c, XCB_SHAPE_SO_SUBTRACT, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_BOUNDING, shape_helper_window, clientPos().x(), clientPos().y(), window()); xcb_shape_combine(c, XCB_SHAPE_SO_UNION, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_INPUT, shape_helper_window, clientPos().x(), clientPos().y(), window()); xcb_shape_combine(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_SHAPE_SK_INPUT, frameId(), 0, 0, shape_helper_window); } } void Client::hideClient(bool hide) { if (hidden == hide) return; hidden = hide; updateVisibility(); } /** * Returns whether the window is minimizable or not */ bool Client::isMinimizable() const { if (isSpecialWindow() && !isTransient()) return false; if (!rules()->checkMinimize(true)) return false; if (isTransient()) { // #66868 - Let other xmms windows be minimized when the mainwindow is minimized bool shown_mainwindow = false; auto mainclients = mainClients(); for (auto it = mainclients.constBegin(); it != mainclients.constEnd(); ++it) if ((*it)->isShown(true)) shown_mainwindow = true; if (!shown_mainwindow) return true; } #if 0 // This is here because kicker's taskbar doesn't provide separate entries // for windows with an explicitly given parent // TODO: perhaps this should be redone // Disabled for now, since at least modal dialogs should be minimizable // (resulting in the mainwindow being minimized too). if (transientFor() != NULL) return false; #endif if (!wantsTabFocus()) // SELI, TODO: - NET::Utility? why wantsTabFocus() - skiptaskbar? ? return false; return true; } void Client::doMinimize() { updateVisibility(); updateAllowedActions(); workspace()->updateMinimizedOfTransients(this); // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Minimized); } QRect Client::iconGeometry() const { NETRect r = info->iconGeometry(); QRect geom(r.pos.x, r.pos.y, r.size.width, r.size.height); if (geom.isValid()) return geom; else { // Check all mainwindows of this window (recursively) foreach (AbstractClient * amainwin, mainClients()) { Client *mainwin = dynamic_cast(amainwin); if (!mainwin) { continue; } geom = mainwin->iconGeometry(); if (geom.isValid()) return geom; } // No mainwindow (or their parents) with icon geometry was found return QRect(); } } bool Client::isShadeable() const { return !isSpecialWindow() && !noBorder() && (rules()->checkShade(ShadeNormal) != rules()->checkShade(ShadeNone)); } void Client::setShade(ShadeMode mode) { if (mode == ShadeHover && isMove()) return; // causes geometry breaks and is probably nasty if (isSpecialWindow() || noBorder()) mode = ShadeNone; mode = rules()->checkShade(mode); if (shade_mode == mode) return; bool was_shade = isShade(); ShadeMode was_shade_mode = shade_mode; shade_mode = mode; // Decorations may turn off some borders when shaded // this has to happen _before_ the tab alignment since it will restrict the minimum geometry #if 0 if (decoration) decoration->borders(border_left, border_right, border_top, border_bottom); #endif // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Shaded); if (was_shade == isShade()) { // Decoration may want to update after e.g. hover-shade changes emit shadeChanged(); return; // No real change in shaded state } assert(isDecorated()); // noborder windows can't be shaded GeometryUpdatesBlocker blocker(this); // TODO: All this unmapping, resizing etc. feels too much duplicated from elsewhere if (isShade()) { // shade_mode == ShadeNormal addWorkspaceRepaint(visibleRect()); // Shade shade_geometry_change = true; QSize s(sizeForClientSize(QSize(clientSize()))); s.setHeight(borderTop() + borderBottom()); m_wrapper.selectInput(ClientWinMask); // Avoid getting UnmapNotify m_wrapper.unmap(); m_client.unmap(); m_wrapper.selectInput(ClientWinMask | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY); exportMappingState(IconicState); plainResize(s); shade_geometry_change = false; if (was_shade_mode == ShadeHover) { if (shade_below && workspace()->stackingOrder().indexOf(shade_below) > -1) workspace()->restack(this, shade_below, true); if (isActive()) workspace()->activateNextClient(this); } else if (isActive()) { workspace()->focusToNull(); } } else { shade_geometry_change = true; if (decoratedClient()) decoratedClient()->signalShadeChange(); QSize s(sizeForClientSize(clientSize())); shade_geometry_change = false; plainResize(s); geom_restore = geometry(); if ((shade_mode == ShadeHover || shade_mode == ShadeActivated) && rules()->checkAcceptFocus(info->input())) setActive(true); if (shade_mode == ShadeHover) { ToplevelList order = workspace()->stackingOrder(); // invalidate, since "this" could be the topmost toplevel and shade_below dangeling shade_below = NULL; // this is likely related to the index parameter?! for (int idx = order.indexOf(this) + 1; idx < order.count(); ++idx) { shade_below = qobject_cast(order.at(idx)); if (shade_below) { break; } } if (shade_below && shade_below->isNormalWindow()) workspace()->raiseClient(this); else shade_below = NULL; } m_wrapper.map(); m_client.map(); exportMappingState(NormalState); if (isActive()) workspace()->requestFocus(this); } info->setState(isShade() ? NET::Shaded : NET::States(0), NET::Shaded); info->setState(isShown(false) ? NET::States(0) : NET::Hidden, NET::Hidden); discardWindowPixmap(); updateVisibility(); updateAllowedActions(); updateWindowRules(Rules::Shade); emit shadeChanged(); } void Client::shadeHover() { setShade(ShadeHover); cancelShadeHoverTimer(); } void Client::shadeUnhover() { if (!tabGroup() || tabGroup()->current() == this || tabGroup()->current()->shadeMode() == ShadeNormal) setShade(ShadeNormal); cancelShadeHoverTimer(); } void Client::cancelShadeHoverTimer() { delete shadeHoverTimer; shadeHoverTimer = 0; } void Client::toggleShade() { // If the mode is ShadeHover or ShadeActive, cancel shade too setShade(shade_mode == ShadeNone ? ShadeNormal : ShadeNone); } void Client::updateVisibility() { if (deleting) return; if (hidden && isCurrentTab()) { info->setState(NET::Hidden, NET::Hidden); setSkipTaskbar(true); // Also hide from taskbar if (compositing() && options->hiddenPreviews() == HiddenPreviewsAlways) internalKeep(); else internalHide(); return; } if (isCurrentTab()) setSkipTaskbar(originalSkipTaskbar()); // Reset from 'hidden' if (isMinimized()) { info->setState(NET::Hidden, NET::Hidden); if (compositing() && options->hiddenPreviews() == HiddenPreviewsAlways) internalKeep(); else internalHide(); return; } info->setState(0, NET::Hidden); if (!isOnCurrentDesktop()) { if (compositing() && options->hiddenPreviews() != HiddenPreviewsNever) internalKeep(); else internalHide(); return; } if (!isOnCurrentActivity()) { if (compositing() && options->hiddenPreviews() != HiddenPreviewsNever) internalKeep(); else internalHide(); return; } internalShow(); } /** * Sets the client window's mapping state. Possible values are * WithdrawnState, IconicState, NormalState. */ void Client::exportMappingState(int s) { assert(m_client != XCB_WINDOW_NONE); assert(!deleting || s == WithdrawnState); if (s == WithdrawnState) { m_client.deleteProperty(atoms->wm_state); return; } assert(s == NormalState || s == IconicState); int32_t data[2]; data[0] = s; data[1] = XCB_NONE; m_client.changeProperty(atoms->wm_state, atoms->wm_state, 32, 2, data); } void Client::internalShow() { if (mapping_state == Mapped) return; MappingState old = mapping_state; mapping_state = Mapped; if (old == Unmapped || old == Withdrawn) map(); if (old == Kept) { m_decoInputExtent.map(); updateHiddenPreview(); } - if (Compositor::isCreated()) { - Compositor::self()->checkUnredirect(); - } emit windowShown(this); } void Client::internalHide() { if (mapping_state == Unmapped) return; MappingState old = mapping_state; mapping_state = Unmapped; if (old == Mapped || old == Kept) unmap(); if (old == Kept) updateHiddenPreview(); addWorkspaceRepaint(visibleRect()); workspace()->clientHidden(this); emit windowHidden(this); - if (Compositor::isCreated()) { - Compositor::self()->checkUnredirect(); - } } void Client::internalKeep() { assert(compositing()); if (mapping_state == Kept) return; MappingState old = mapping_state; mapping_state = Kept; if (old == Unmapped || old == Withdrawn) map(); m_decoInputExtent.unmap(); if (isActive()) workspace()->focusToNull(); // get rid of input focus, bug #317484 updateHiddenPreview(); addWorkspaceRepaint(visibleRect()); workspace()->clientHidden(this); - if (Compositor::isCreated()) { - Compositor::self()->checkUnredirect(); - } } /** * Maps (shows) the client. Note that it is mapping state of the frame, * not necessarily the client window itself (i.e. a shaded window is here * considered mapped, even though it is in IconicState). */ void Client::map() { // XComposite invalidates backing pixmaps on unmap (minimize, different // virtual desktop, etc.). We kept the last known good pixmap around // for use in effects, but now we want to have access to the new pixmap if (compositing()) discardWindowPixmap(); m_frame.map(); if (!isShade()) { m_wrapper.map(); m_client.map(); m_decoInputExtent.map(); exportMappingState(NormalState); } else exportMappingState(IconicState); addLayerRepaint(visibleRect()); } /** * Unmaps the client. Again, this is about the frame. */ void Client::unmap() { // Here it may look like a race condition, as some other client might try to unmap // the window between these two XSelectInput() calls. However, they're supposed to // use XWithdrawWindow(), which also sends a synthetic event to the root window, // which won't be missed, so this shouldn't be a problem. The chance the real UnmapNotify // will be missed is also very minimal, so I don't think it's needed to grab the server // here. m_wrapper.selectInput(ClientWinMask); // Avoid getting UnmapNotify m_frame.unmap(); m_wrapper.unmap(); m_client.unmap(); m_decoInputExtent.unmap(); m_wrapper.selectInput(ClientWinMask | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY); exportMappingState(IconicState); } /** * XComposite doesn't keep window pixmaps of unmapped windows, which means * there wouldn't be any previews of windows that are minimized or on another * virtual desktop. Therefore rawHide() actually keeps such windows mapped. * However special care needs to be taken so that such windows don't interfere. * Therefore they're put very low in the stacking order and they have input shape * set to none, which hopefully is enough. If there's no input shape available, * then it's hoped that there will be some other desktop above it *shrug*. * Using normal shape would be better, but that'd affect other things, e.g. painting * of the actual preview. */ void Client::updateHiddenPreview() { if (hiddenPreview()) { workspace()->forceRestacking(); if (Xcb::Extensions::self()->isShapeInputAvailable()) { xcb_shape_rectangles(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED, frameId(), 0, 0, 0, NULL); } } else { workspace()->forceRestacking(); updateInputShape(); } } void Client::sendClientMessage(xcb_window_t w, xcb_atom_t a, xcb_atom_t protocol, uint32_t data1, uint32_t data2, uint32_t data3, xcb_timestamp_t timestamp) { xcb_client_message_event_t ev; memset(&ev, 0, sizeof(ev)); ev.response_type = XCB_CLIENT_MESSAGE; ev.window = w; ev.type = a; ev.format = 32; ev.data.data32[0] = protocol; ev.data.data32[1] = timestamp; ev.data.data32[2] = data1; ev.data.data32[3] = data2; ev.data.data32[4] = data3; uint32_t eventMask = 0; if (w == rootWindow()) { eventMask = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; // Magic! } xcb_send_event(connection(), false, w, eventMask, reinterpret_cast(&ev)); xcb_flush(connection()); } /** * Returns whether the window may be closed (have a close button) */ bool Client::isCloseable() const { return rules()->checkCloseable(m_motif.close() && !isSpecialWindow()); } /** * Closes the window by either sending a delete_window message or using XKill. */ void Client::closeWindow() { if (!isCloseable()) return; // Update user time, because the window may create a confirming dialog. updateUserTime(); if (info->supportsProtocol(NET::DeleteWindowProtocol)) { sendClientMessage(window(), atoms->wm_protocols, atoms->wm_delete_window); pingWindow(); } else // Client will not react on wm_delete_window. We have not choice // but destroy his connection to the XServer. killWindow(); } /** * Kills the window via XKill */ void Client::killWindow() { qCDebug(KWIN_CORE) << "Client::killWindow():" << caption(); killProcess(false); m_client.kill(); // Always kill this client at the server destroyClient(); } /** * Send a ping to the window using _NET_WM_PING if possible if it * doesn't respond within a reasonable time, it will be killed. */ void Client::pingWindow() { if (!info->supportsProtocol(NET::PingProtocol)) return; // Can't ping :( if (options->killPingTimeout() == 0) return; // Turned off if (ping_timer != NULL) return; // Pinging already ping_timer = new QTimer(this); connect(ping_timer, &QTimer::timeout, this, [this]() { qCDebug(KWIN_CORE) << "Ping timeout:" << caption(); ping_timer->deleteLater(); ping_timer = nullptr; killProcess(true, m_pingTimestamp); } ); ping_timer->setSingleShot(true); ping_timer->start(options->killPingTimeout()); m_pingTimestamp = xTime(); workspace()->sendPingToWindow(window(), m_pingTimestamp); } void Client::gotPing(xcb_timestamp_t timestamp) { // Just plain compare is not good enough because of 64bit and truncating and whatnot if (NET::timestampCompare(timestamp, m_pingTimestamp) != 0) return; delete ping_timer; ping_timer = NULL; if (m_killHelperPID && !::kill(m_killHelperPID, 0)) { // means the process is alive ::kill(m_killHelperPID, SIGTERM); m_killHelperPID = 0; } } void Client::killProcess(bool ask, xcb_timestamp_t timestamp) { if (m_killHelperPID && !::kill(m_killHelperPID, 0)) // means the process is alive return; Q_ASSERT(!ask || timestamp != XCB_TIME_CURRENT_TIME); pid_t pid = info->pid(); if (pid <= 0 || clientMachine()->hostName().isEmpty()) // Needed properties missing return; qCDebug(KWIN_CORE) << "Kill process:" << pid << "(" << clientMachine()->hostName() << ")"; if (!ask) { if (!clientMachine()->isLocal()) { QStringList lst; lst << QString::fromUtf8(clientMachine()->hostName()) << QStringLiteral("kill") << QString::number(pid); QProcess::startDetached(QStringLiteral("xon"), lst); } else ::kill(pid, SIGTERM); } else { QString hostname = clientMachine()->isLocal() ? QStringLiteral("localhost") : QString::fromUtf8(clientMachine()->hostName()); QProcess::startDetached(QStringLiteral(KWIN_KILLER_BIN), QStringList() << QStringLiteral("--pid") << QString::number(unsigned(pid)) << QStringLiteral("--hostname") << hostname << QStringLiteral("--windowname") << caption() << QStringLiteral("--applicationname") << QString::fromUtf8(resourceClass()) << QStringLiteral("--wid") << QString::number(window()) << QStringLiteral("--timestamp") << QString::number(timestamp), QString(), &m_killHelperPID); } } void Client::doSetSkipTaskbar() { info->setState(skipTaskbar() ? NET::SkipTaskbar : NET::States(0), NET::SkipTaskbar); } void Client::doSetSkipPager() { info->setState(skipPager() ? NET::SkipPager : NET::States(0), NET::SkipPager); } void Client::doSetDesktop(int desktop, int was_desk) { Q_UNUSED(desktop) Q_UNUSED(was_desk) updateVisibility(); // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Desktop); } /** * Sets whether the client is on @p activity. * If you remove it from its last activity, then it's on all activities. * * Note: If it was on all activities and you try to remove it from one, nothing will happen; * I don't think that's an important enough use case to handle here. */ void Client::setOnActivity(const QString &activity, bool enable) { #ifdef KWIN_BUILD_ACTIVITIES if (! Activities::self()) { return; } QStringList newActivitiesList = activities(); if (newActivitiesList.contains(activity) == enable) //nothing to do return; if (enable) { QStringList allActivities = Activities::self()->all(); if (!allActivities.contains(activity)) //bogus ID return; newActivitiesList.append(activity); } else newActivitiesList.removeOne(activity); setOnActivities(newActivitiesList); #else Q_UNUSED(activity) Q_UNUSED(enable) #endif } /** * set exactly which activities this client is on */ void Client::setOnActivities(QStringList newActivitiesList) { #ifdef KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return; } QString joinedActivitiesList = newActivitiesList.join(QStringLiteral(",")); joinedActivitiesList = rules()->checkActivity(joinedActivitiesList, false); newActivitiesList = joinedActivitiesList.split(u',', QString::SkipEmptyParts); QStringList allActivities = Activities::self()->all(); if (// If we got the request to be on all activities explicitly newActivitiesList.isEmpty() || joinedActivitiesList == Activities::nullUuid() || // If we got a list of activities that covers all activities (newActivitiesList.count() > 1 && newActivitiesList.count() == allActivities.count())) { activityList.clear(); const QByteArray nullUuid = Activities::nullUuid().toUtf8(); m_client.changeProperty(atoms->activities, XCB_ATOM_STRING, 8, nullUuid.length(), nullUuid.constData()); } else { QByteArray joined = joinedActivitiesList.toAscii(); activityList = newActivitiesList; m_client.changeProperty(atoms->activities, XCB_ATOM_STRING, 8, joined.length(), joined.constData()); } updateActivities(false); #else Q_UNUSED(newActivitiesList) #endif } void Client::blockActivityUpdates(bool b) { if (b) { ++m_activityUpdatesBlocked; } else { Q_ASSERT(m_activityUpdatesBlocked); --m_activityUpdatesBlocked; if (!m_activityUpdatesBlocked) updateActivities(m_blockedActivityUpdatesRequireTransients); } } /** * update after activities changed */ void Client::updateActivities(bool includeTransients) { if (m_activityUpdatesBlocked) { m_blockedActivityUpdatesRequireTransients |= includeTransients; return; } emit activitiesChanged(this); m_blockedActivityUpdatesRequireTransients = false; // reset FocusChain::self()->update(this, FocusChain::MakeFirst); updateVisibility(); updateWindowRules(Rules::Activity); // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Activity); } /** * Returns the virtual desktop within the workspace() the client window * is located in, 0 if it isn't located on any special desktop (not mapped yet), * or NET::OnAllDesktops. Do not use desktop() directly, use * isOnDesktop() instead. */ int Client::desktop() const { if (needsSessionInteract) { return NET::OnAllDesktops; } return AbstractClient::desktop(); } /** * Returns the list of activities the client window is on. * if it's on all activities, the list will be empty. * Don't use this, use isOnActivity() and friends (from class Toplevel) */ QStringList Client::activities() const { if (needsSessionInteract) { return QStringList(); } return activityList; } /** * if @p on is true, sets on all activities. * if it's false, sets it to only be on the current activity */ void Client::setOnAllActivities(bool on) { #ifdef KWIN_BUILD_ACTIVITIES if (on == isOnAllActivities()) return; if (on) { setOnActivities(QStringList()); } else { setOnActivity(Activities::self()->current(), true); } #else Q_UNUSED(on) #endif } /** * Performs the actual focusing of the window using XSetInputFocus and WM_TAKE_FOCUS */ void Client::takeFocus() { if (rules()->checkAcceptFocus(info->input())) m_client.focus(); else demandAttention(false); // window cannot take input, at least withdraw urgency if (info->supportsProtocol(NET::TakeFocusProtocol)) { sendClientMessage(window(), atoms->wm_protocols, atoms->wm_take_focus, 0, 0, 0, XCB_CURRENT_TIME); } workspace()->setShouldGetFocus(this); bool breakShowingDesktop = !keepAbove(); if (breakShowingDesktop) { foreach (const Client *c, group()->members()) { if (c->isDesktop()) { breakShowingDesktop = false; break; } } } if (breakShowingDesktop) workspace()->setShowingDesktop(false); } /** * Returns whether the window provides context help or not. If it does, * you should show a help menu item or a help button like '?' and call * contextHelp() if this is invoked. * * \sa contextHelp() */ bool Client::providesContextHelp() const { return info->supportsProtocol(NET::ContextHelpProtocol); } /** * Invokes context help on the window. Only works if the window * actually provides context help. * * \sa providesContextHelp() */ void Client::showContextHelp() { if (info->supportsProtocol(NET::ContextHelpProtocol)) { sendClientMessage(window(), atoms->wm_protocols, atoms->net_wm_context_help); QWhatsThis::enterWhatsThisMode(); // SELI TODO: ? } } /** * Fetches the window's caption (WM_NAME property). It will be * stored in the client's caption(). */ void Client::fetchName() { setCaption(readName()); } QString Client::readName() const { if (info->name() && info->name()[0] != '\0') return QString::fromUtf8(info->name()); else return KWindowSystem::readNameProperty(window(), XCB_ATOM_WM_NAME); } // The list is taken from http://www.unicode.org/reports/tr9/ (#154840) static const QChar LRM(0x200E); static const QChar RLM(0x200F); static const QChar LRE(0x202A); static const QChar RLE(0x202B); static const QChar LRO(0x202D); static const QChar RLO(0x202E); static const QChar PDF(0x202C); void Client::setCaption(const QString& _s, bool force) { if (!force && _s == cap_normal) return; QString s(_s); for (int i = 0; i < s.length(); ++i) if (!s[i].isPrint()) s[i] = QChar(u' '); cap_normal = s; if (options->condensedTitle()) { static QScriptEngine engine; static QScriptProgram stripTitle; static QScriptValue script; if (stripTitle.isNull()) { const QString scriptFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral(KWIN_NAME "/stripTitle.js")); if (!scriptFile.isEmpty()) { QFile f(scriptFile); if (f.open(QIODevice::ReadOnly|QIODevice::Text)) { f.reset(); stripTitle = QScriptProgram(QString::fromLocal8Bit(f.readAll()), QStringLiteral("stripTitle.js")); f.close(); } } if (stripTitle.isNull()) stripTitle = QScriptProgram(QStringLiteral("(function(title, wm_name, wm_class){ return title ; })"), QStringLiteral("stripTitle.js")); script = engine.evaluate(stripTitle); } QScriptValueList args; args << _s << QString::fromUtf8(resourceName()) << QString::fromUtf8(resourceClass()); s = script.call(QScriptValue(), args).toString(); } if (!force && s == cap_deco) return; cap_deco = s; bool reset_name = force; bool was_suffix = (!cap_suffix.isEmpty()); cap_suffix.clear(); QString machine_suffix; if (!options->condensedTitle()) { // machine doesn't qualify for "clean" if (clientMachine()->hostName() != ClientMachine::localhost() && !clientMachine()->isLocal()) machine_suffix = QLatin1String(" <@") + QString::fromUtf8(clientMachine()->hostName()) + QLatin1Char('>') + LRM; } QString shortcut_suffix = !shortcut().isEmpty() ? (QLatin1String(" {") + shortcut().toString() + QLatin1Char('}')) : QString(); cap_suffix = machine_suffix + shortcut_suffix; auto fetchNameInternalPredicate = [this](const Client *cl) { return (!cl->isSpecialWindow() || cl->isToolbar()) && cl != this && cl->caption() == caption(); }; if ((!isSpecialWindow() || isToolbar()) && workspace()->findClient(fetchNameInternalPredicate)) { int i = 2; do { cap_suffix = machine_suffix + QLatin1String(" <") + QString::number(i) + QLatin1Char('>') + LRM; i++; } while (workspace()->findClient(fetchNameInternalPredicate)); info->setVisibleName(caption().toUtf8().constData()); reset_name = false; } if ((was_suffix && cap_suffix.isEmpty()) || reset_name) { // If it was new window, it may have old value still set, if the window is reused info->setVisibleName(""); info->setVisibleIconName(""); } else if (!cap_suffix.isEmpty() && !cap_iconic.isEmpty()) // Keep the same suffix in iconic name if it's set info->setVisibleIconName(QString(cap_iconic + cap_suffix).toUtf8().constData()); emit captionChanged(); } void Client::updateCaption() { setCaption(cap_normal, true); } void Client::evaluateWindowRules() { setupWindowRules(true); applyWindowRules(); } void Client::fetchIconicName() { QString s; if (info->iconName() && info->iconName()[0] != '\0') s = QString::fromUtf8(info->iconName()); else s = KWindowSystem::readNameProperty(window(), XCB_ATOM_WM_ICON_NAME); if (s != cap_iconic) { bool was_set = !cap_iconic.isEmpty(); cap_iconic = s; if (!cap_suffix.isEmpty()) { if (!cap_iconic.isEmpty()) // Keep the same suffix in iconic name if it's set info->setVisibleIconName(QString(s + cap_suffix).toUtf8().constData()); else if (was_set) info->setVisibleIconName(""); } } } /** * \reimp */ QString Client::caption(bool full, bool stripped) const { QString cap = stripped ? cap_deco : cap_normal; if (full) cap += cap_suffix; return cap; } bool Client::tabTo(Client *other, bool behind, bool activate) { Q_ASSERT(other && other != this); if (tab_group && tab_group == other->tabGroup()) { // special case: move inside group tab_group->move(this, other, behind); return true; } GeometryUpdatesBlocker blocker(this); const bool wasBlocking = signalsBlocked(); blockSignals(true); // prevent client emitting "retabbed to nowhere" cause it's about to be entabbed the next moment untab(); blockSignals(wasBlocking); TabGroup *newGroup = other->tabGroup() ? other->tabGroup() : new TabGroup(other); if (!newGroup->add(this, other, behind, activate)) { if (newGroup->count() < 2) { // adding "c" to "to" failed for whatever reason newGroup->remove(other); delete newGroup; } return false; } return true; } bool Client::untab(const QRect &toGeometry, bool clientRemoved) { TabGroup *group = tab_group; if (group && group->remove(this)) { // remove sets the tabgroup to "0", therefore the pointer is cached if (group->isEmpty()) { delete group; } if (clientRemoved) return true; // there's been a broadcast signal that this client is now removed - don't touch it setClientShown(!(isMinimized() || isShade())); bool keepSize = toGeometry.size() == size(); bool changedSize = false; if (quickTileMode() != QuickTileNone) { changedSize = true; setQuickTileMode(QuickTileNone); // if we leave a quicktiled group, assume that the user wants to untile } if (toGeometry.isValid()) { if (maximizeMode() != MaximizeRestore) { changedSize = true; maximize(MaximizeRestore); // explicitly calling for a geometry -> unmaximize } if (keepSize && changedSize) { geom_restore = geometry(); // checkWorkspacePosition() invokes it QPoint cpoint = Cursor::pos(); QPoint point = cpoint; point.setX((point.x() - toGeometry.x()) * geom_restore.width() / toGeometry.width()); point.setY((point.y() - toGeometry.y()) * geom_restore.height() / toGeometry.height()); geom_restore.moveTo(cpoint-point); } else { geom_restore = toGeometry; // checkWorkspacePosition() invokes it } setGeometry(geom_restore); checkWorkspacePosition(); } return true; } return false; } void Client::setTabGroup(TabGroup *group) { tab_group = group; if (group) { unsigned long data[] = {qHash(group)}; //->id(); m_client.changeProperty(atoms->kde_net_wm_tab_group, XCB_ATOM_CARDINAL, 32, 1, data); } else m_client.deleteProperty(atoms->kde_net_wm_tab_group); emit tabGroupChanged(); } bool Client::isCurrentTab() const { return !tab_group || tab_group->current() == this; } void Client::syncTabGroupFor(QString property, bool fromThisClient) { if (tab_group) tab_group->sync(property.toAscii().data(), fromThisClient ? this : tab_group->current()); } void Client::setClientShown(bool shown) { if (deleting) return; // Don't change shown status if this client is being deleted if (shown != hidden) return; // nothing to change hidden = !shown; if (options->isInactiveTabsSkipTaskbar()) setSkipTaskbar(hidden); // TODO: Causes reshuffle of the taskbar if (shown) { map(); takeFocus(); autoRaise(); FocusChain::self()->update(this, FocusChain::MakeFirst); } else { unmap(); // Don't move tabs to the end of the list when another tab get's activated if (isCurrentTab()) FocusChain::self()->update(this, FocusChain::MakeLast); addWorkspaceRepaint(visibleRect()); } } void Client::getMotifHints() { const bool wasClosable = m_motif.close(); const bool wasNoBorder = m_motif.noBorder(); if (m_managed) // only on property change, initial read is prefetched m_motif.fetch(); m_motif.read(); if (m_motif.hasDecoration() && m_motif.noBorder() != wasNoBorder) { // If we just got a hint telling us to hide decorations, we do so. if (m_motif.noBorder()) noborder = rules()->checkNoBorder(true); // If the Motif hint is now telling us to show decorations, we only do so if the app didn't // instruct us to hide decorations in some other way, though. else if (!app_noborder) noborder = rules()->checkNoBorder(false); } // mminimize; - Ignore, bogus - E.g. shading or sending to another desktop is "minimizing" too // mmaximize; - Ignore, bogus - Maximizing is basically just resizing const bool closabilityChanged = wasClosable != m_motif.close(); if (isManaged()) updateDecoration(true); // Check if noborder state has changed if (closabilityChanged) { emit closeableChanged(isCloseable()); } } void Client::getIcons() { // First read icons from the window itself QIcon icon; auto readIcon = [this, &icon](int size, bool scale = true) { const QPixmap pix = KWindowSystem::icon(window(), size, size, scale, KWindowSystem::NETWM | KWindowSystem::WMHints, info); if (!pix.isNull()) { icon.addPixmap(pix); } }; readIcon(16); readIcon(32); readIcon(48, false); readIcon(64, false); readIcon(128, false); if (icon.isNull()) { // Then try window group icon = group()->icon(); } if (icon.isNull() && isTransient()) { // Then mainclients auto mainclients = mainClients(); for (auto it = mainclients.constBegin(); it != mainclients.constEnd() && icon.isNull(); ++it) { if (!(*it)->icon().isNull()) { icon = (*it)->icon(); break; } } } if (icon.isNull()) { // And if nothing else, load icon from classhint or xapp icon icon.addPixmap(KWindowSystem::icon(window(), 32, 32, true, KWindowSystem::ClassHint | KWindowSystem::XApp, info)); icon.addPixmap(KWindowSystem::icon(window(), 16, 16, true, KWindowSystem::ClassHint | KWindowSystem::XApp, info)); icon.addPixmap(KWindowSystem::icon(window(), 64, 64, false, KWindowSystem::ClassHint | KWindowSystem::XApp, info)); icon.addPixmap(KWindowSystem::icon(window(), 128, 128, false, KWindowSystem::ClassHint | KWindowSystem::XApp, info)); } setIcon(icon); } void Client::getSyncCounter() { if (!Xcb::Extensions::self()->isSyncAvailable()) return; Xcb::Property syncProp(false, window(), atoms->net_wm_sync_request_counter, XCB_ATOM_CARDINAL, 0, 1); const xcb_sync_counter_t counter = syncProp.value(XCB_NONE); if (counter != XCB_NONE) { syncRequest.counter = counter; syncRequest.value.hi = 0; syncRequest.value.lo = 0; auto *c = connection(); xcb_sync_set_counter(c, syncRequest.counter, syncRequest.value); if (syncRequest.alarm == XCB_NONE) { const uint32_t mask = XCB_SYNC_CA_COUNTER | XCB_SYNC_CA_VALUE_TYPE | XCB_SYNC_CA_TEST_TYPE | XCB_SYNC_CA_EVENTS; const uint32_t values[] = { syncRequest.counter, XCB_SYNC_VALUETYPE_RELATIVE, XCB_SYNC_TESTTYPE_POSITIVE_TRANSITION, 1 }; syncRequest.alarm = xcb_generate_id(c); auto cookie = xcb_sync_create_alarm_checked(c, syncRequest.alarm, mask, values); ScopedCPointer error(xcb_request_check(c, cookie)); if (!error.isNull()) { syncRequest.alarm = XCB_NONE; } else { xcb_sync_change_alarm_value_list_t value; memset(&value, 0, sizeof(value)); value.value.hi = 0; value.value.lo = 1; value.delta.hi = 0; value.delta.lo = 1; xcb_sync_change_alarm_aux(c, syncRequest.alarm, XCB_SYNC_CA_DELTA | XCB_SYNC_CA_VALUE, &value); } } } } /** * Send the client a _NET_SYNC_REQUEST */ void Client::sendSyncRequest() { if (syncRequest.counter == XCB_NONE || syncRequest.isPending) return; // do NOT, NEVER send a sync request when there's one on the stack. the clients will just stop respoding. FOREVER! ... if (!syncRequest.failsafeTimeout) { syncRequest.failsafeTimeout = new QTimer(this); connect(syncRequest.failsafeTimeout, &QTimer::timeout, this, [this]() { // client does not respond to XSYNC requests in reasonable time, remove support if (!ready_for_painting) { // failed on initial pre-show request setReadyForPainting(); setupWindowManagementInterface(); return; } // failed during resize syncRequest.isPending = false; syncRequest.counter = syncRequest.alarm = XCB_NONE; delete syncRequest.timeout; delete syncRequest.failsafeTimeout; syncRequest.timeout = syncRequest.failsafeTimeout = nullptr; syncRequest.lastTimestamp = XCB_CURRENT_TIME; } ); syncRequest.failsafeTimeout->setSingleShot(true); } // if there's no response within 10 seconds, sth. went wrong and we remove XSYNC support from this client. // see events.cpp Client::syncEvent() syncRequest.failsafeTimeout->start(ready_for_painting ? 10000 : 1000); // We increment before the notify so that after the notify // syncCounterSerial will equal the value we are expecting // in the acknowledgement const uint32_t oldLo = syncRequest.value.lo; syncRequest.value.lo++;; if (oldLo > syncRequest.value.lo) { syncRequest.value.hi++; } if (syncRequest.lastTimestamp >= xTime()) { updateXTime(); } // Send the message to client sendClientMessage(window(), atoms->wm_protocols, atoms->net_wm_sync_request, syncRequest.value.lo, syncRequest.value.hi); syncRequest.isPending = true; syncRequest.lastTimestamp = xTime(); } bool Client::wantsInput() const { return rules()->checkAcceptFocus(acceptsFocus() || info->supportsProtocol(NET::TakeFocusProtocol)); } bool Client::acceptsFocus() const { return info->input(); } void Client::setBlockingCompositing(bool block) { const bool usedToBlock = blocks_compositing; blocks_compositing = rules()->checkBlockCompositing(block); if (usedToBlock != blocks_compositing) { emit blockingCompositingChanged(blocks_compositing ? this : 0); } } void Client::updateAllowedActions(bool force) { if (!isManaged() && !force) return; NET::Actions old_allowed_actions = NET::Actions(allowed_actions); allowed_actions = 0; if (isMovable()) allowed_actions |= NET::ActionMove; if (isResizable()) allowed_actions |= NET::ActionResize; if (isMinimizable()) allowed_actions |= NET::ActionMinimize; if (isShadeable()) allowed_actions |= NET::ActionShade; // Sticky state not supported if (isMaximizable()) allowed_actions |= NET::ActionMax; if (userCanSetFullScreen()) allowed_actions |= NET::ActionFullScreen; allowed_actions |= NET::ActionChangeDesktop; // Always (Pagers shouldn't show Docks etc.) if (isCloseable()) allowed_actions |= NET::ActionClose; if (old_allowed_actions == allowed_actions) return; // TODO: This could be delayed and compressed - It's only for pagers etc. anyway info->setAllowedActions(allowed_actions); // ONLY if relevant features have changed (and the window didn't just get/loose moveresize for maximization state changes) const NET::Actions relevant = ~(NET::ActionMove|NET::ActionResize); if ((allowed_actions & relevant) != (old_allowed_actions & relevant)) { if ((allowed_actions & NET::ActionMinimize) != (old_allowed_actions & NET::ActionMinimize)) { emit minimizeableChanged(allowed_actions & NET::ActionMinimize); } if ((allowed_actions & NET::ActionShade) != (old_allowed_actions & NET::ActionShade)) { emit shadeableChanged(allowed_actions & NET::ActionShade); } if ((allowed_actions & NET::ActionMax) != (old_allowed_actions & NET::ActionMax)) { emit maximizeableChanged(allowed_actions & NET::ActionMax); } } } void Client::debug(QDebug& stream) const { print(stream); } Xcb::StringProperty Client::fetchActivities() const { #ifdef KWIN_BUILD_ACTIVITIES return Xcb::StringProperty(window(), atoms->activities); #else return Xcb::StringProperty(); #endif } void Client::readActivities(Xcb::StringProperty &property) { #ifdef KWIN_BUILD_ACTIVITIES QStringList newActivitiesList; QString prop = QString::fromUtf8(property); activitiesDefined = !prop.isEmpty(); if (prop == Activities::nullUuid()) { //copied from setOnAllActivities to avoid a redundant XChangeProperty. if (!activityList.isEmpty()) { activityList.clear(); updateActivities(true); } return; } if (prop.isEmpty()) { //note: this makes it *act* like it's on all activities but doesn't set the property to 'ALL' if (!activityList.isEmpty()) { activityList.clear(); updateActivities(true); } return; } newActivitiesList = prop.split(u','); if (newActivitiesList == activityList) return; //expected change, it's ok. //otherwise, somebody else changed it. we need to validate before reacting. //if the activities are not synced, and there are existing clients with //activities specified, somebody has restarted kwin. we can not validate //activities in this case. we need to trust the old values. if (Activities::self() && Activities::self()->serviceStatus() != KActivities::Consumer::Unknown) { QStringList allActivities = Activities::self()->all(); if (allActivities.isEmpty()) { qCDebug(KWIN_CORE) << "no activities!?!?"; //don't touch anything, there's probably something bad going on and we don't wanna make it worse return; } for (int i = 0; i < newActivitiesList.size(); ++i) { if (! allActivities.contains(newActivitiesList.at(i))) { qCDebug(KWIN_CORE) << "invalid:" << newActivitiesList.at(i); newActivitiesList.removeAt(i--); } } } setOnActivities(newActivitiesList); #else Q_UNUSED(property) #endif } void Client::checkActivities() { #ifdef KWIN_BUILD_ACTIVITIES Xcb::StringProperty property = fetchActivities(); readActivities(property); #endif } void Client::setSessionInteract(bool needed) { needsSessionInteract = needed; } QRect Client::decorationRect() const { return QRect(0, 0, width(), height()); } Xcb::Property Client::fetchFirstInTabBox() const { return Xcb::Property(false, m_client, atoms->kde_first_in_window_list, atoms->kde_first_in_window_list, 0, 1); } void Client::readFirstInTabBox(Xcb::Property &property) { setFirstInTabBox(property.toBool(32, atoms->kde_first_in_window_list)); } void Client::updateFirstInTabBox() { // TODO: move into KWindowInfo Xcb::Property property = fetchFirstInTabBox(); readFirstInTabBox(property); } Xcb::StringProperty Client::fetchColorScheme() const { return Xcb::StringProperty(m_client, atoms->kde_color_sheme); } void Client::readColorScheme(Xcb::StringProperty &property) { AbstractClient::updateColorScheme(rules()->checkDecoColor(QString::fromUtf8(property))); } void Client::updateColorScheme() { Xcb::StringProperty property = fetchColorScheme(); readColorScheme(property); } bool Client::isClient() const { return true; } NET::WindowType Client::windowType(bool direct, int supportedTypes) const { // TODO: does it make sense to cache the returned window type for SUPPORTED_MANAGED_WINDOW_TYPES_MASK? if (supportedTypes == 0) { supportedTypes = SUPPORTED_MANAGED_WINDOW_TYPES_MASK; } NET::WindowType wt = info->windowType(NET::WindowTypes(supportedTypes)); if (direct) { return wt; } NET::WindowType wt2 = client_rules.checkType(wt); if (wt != wt2) { wt = wt2; info->setWindowType(wt); // force hint change } // hacks here if (wt == NET::Unknown) // this is more or less suggested in NETWM spec wt = isTransient() ? NET::Dialog : NET::Normal; return wt; } void Client::cancelFocusOutTimer() { if (m_focusOutTimer) { m_focusOutTimer->stop(); } } xcb_window_t Client::frameId() const { return m_frame; } Xcb::Property Client::fetchShowOnScreenEdge() const { return Xcb::Property(false, window(), atoms->kde_screen_edge_show, XCB_ATOM_CARDINAL, 0, 1); } void Client::readShowOnScreenEdge(Xcb::Property &property) { //value comes in two parts, edge in the lower byte //then the type in the upper byte // 0 = autohide // 1 = raise in front on activate const uint32_t value = property.value(ElectricNone); ElectricBorder border = ElectricNone; switch (value & 0xFF) { case 0: border = ElectricTop; break; case 1: border = ElectricRight; break; case 2: border = ElectricBottom; break; case 3: border = ElectricLeft; break; } if (border != ElectricNone) { disconnect(m_edgeRemoveConnection); bool successfullyHidden = false; if (((value >> 8) & 0xFF) == 1) { setKeepBelow(true); successfullyHidden = keepBelow(); //request could have failed due to user kwin rules m_edgeRemoveConnection = connect(this, &AbstractClient::keepBelowChanged, this, [this](){ if (!keepBelow()) { ScreenEdges::self()->reserve(this, ElectricNone); } }); } else { hideClient(true); successfullyHidden = isHiddenInternal(); m_edgeRemoveConnection = connect(this, &Client::geometryChanged, this, [this](){ ScreenEdges::self()->reserve(this, ElectricNone); }); } if (successfullyHidden) { ScreenEdges::self()->reserve(this, border); } else { ScreenEdges::self()->reserve(this, ElectricNone); } } else if (!property.isNull() && property->type != XCB_ATOM_NONE) { // property value is incorrect, delete the property // so that the client knows that it is not hidden xcb_delete_property(connection(), window(), atoms->kde_screen_edge_show); } else { // restore // TODO: add proper unreserve //this will call showOnScreenEdge to reset the state ScreenEdges::self()->reserve(this, ElectricNone); } } void Client::updateShowOnScreenEdge() { Xcb::Property property = fetchShowOnScreenEdge(); readShowOnScreenEdge(property); } void Client::showOnScreenEdge() { disconnect(m_edgeRemoveConnection); hideClient(false); setKeepBelow(false); xcb_delete_property(connection(), window(), atoms->kde_screen_edge_show); } void Client::addDamage(const QRegion &damage) { if (!ready_for_painting) { // avoid "setReadyForPainting()" function calling overhead if (syncRequest.counter == XCB_NONE) { // cannot detect complete redraw, consider done now setReadyForPainting(); setupWindowManagementInterface(); } } repaints_region += damage; Toplevel::addDamage(damage); } bool Client::belongsToSameApplication(const AbstractClient *other, bool active_hack) const { const Client *c2 = dynamic_cast(other); if (!c2) { return false; } return Client::belongToSameApplication(this, c2, active_hack); } void Client::updateTabGroupStates(TabGroup::States states) { if (auto t = tabGroup()) { t->updateStates(this, states); } } QSize Client::resizeIncrements() const { return m_geometryHints.resizeIncrements(); } } // namespace diff --git a/client.h b/client.h index 994cc47fc..331899dcb 100644 --- a/client.h +++ b/client.h @@ -1,785 +1,784 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_CLIENT_H #define KWIN_CLIENT_H // kwin #include "options.h" #include "rules.h" #include "tabgroup.h" #include "abstract_client.h" #include "xcbutils.h" // Qt #include #include #include #include #include // X #include // TODO: Cleanup the order of things in this .h file class QTimer; class KStartupInfoData; class KStartupInfoId; struct xcb_sync_alarm_notify_event_t; namespace KWin { /** * @brief Defines Predicates on how to search for a Client. * * Used by Workspace::findClient. */ enum class Predicate { WindowMatch, WrapperIdMatch, FrameIdMatch, InputIdMatch }; class KWIN_EXPORT Client : public AbstractClient { Q_OBJECT /** * Whether the Client can be maximized both horizontally and vertically. * The property is evaluated each time it is invoked. * Because of that there is no notify signal. **/ Q_PROPERTY(bool maximizable READ isMaximizable) /** * Whether the Client is moveable. Even if it is not moveable, it might be possible to move * it to another screen. The property is evaluated each time it is invoked. * Because of that there is no notify signal. * @see moveableAcrossScreens **/ Q_PROPERTY(bool moveable READ isMovable) /** * Whether the Client can be moved to another screen. The property is evaluated each time it is invoked. * Because of that there is no notify signal. * @see moveable **/ Q_PROPERTY(bool moveableAcrossScreens READ isMovableAcrossScreens) /** * Whether the Client can be resized. The property is evaluated each time it is invoked. * Because of that there is no notify signal. **/ Q_PROPERTY(bool resizeable READ isResizable) /** * 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. * The value is evaluated each time the getter is called. * Because of that no changed signal is provided. */ Q_PROPERTY(QSize basicUnit READ basicUnit) /** * The "Window Tabs" Group this Client belongs to. **/ Q_PROPERTY(KWin::TabGroup* tabGroup READ tabGroup NOTIFY tabGroupChanged SCRIPTABLE false) /** * A client can block compositing. That is while the Client is alive and the state is set, * Compositing is suspended and is resumed when there are no Clients blocking compositing any * more. * * This is actually set by a window property, unfortunately not used by the target application * group. For convenience it's exported as a property to the scripts. * * Use with care! **/ Q_PROPERTY(bool blocksCompositing READ isBlockingCompositing WRITE setBlockingCompositing NOTIFY blockingCompositingChanged) /** * Whether the Client uses client side window decorations. * Only GTK+ are detected. **/ Q_PROPERTY(bool clientSideDecorated READ isClientSideDecorated NOTIFY clientSideDecoratedChanged) public: explicit Client(); xcb_window_t wrapperId() const; xcb_window_t inputId() const { return m_decoInputExtent; } virtual xcb_window_t frameId() const override; bool isTransient() const override; bool groupTransient() const; bool wasOriginallyGroupTransient() const; QList mainClients() const override; // Call once before loop , is not indirect bool hasTransient(const AbstractClient* c, bool indirect) const override; void checkTransient(xcb_window_t w); AbstractClient* findModal(bool allow_itself = false) override; const Group* group() const; Group* group(); void checkGroup(Group* gr = NULL, bool force = false); void changeClientLeaderGroup(Group* gr); const WindowRules* rules() const override; void removeRule(Rules* r); void setupWindowRules(bool ignore_temporary); void applyWindowRules(); void updateWindowRules(Rules::Types selection) override; void updateFullscreenMonitors(NETFullscreenMonitors topology); bool hasNETSupport() const; QSize minSize() const override; QSize maxSize() const override; QSize basicUnit() const; virtual QSize clientSize() const; QPoint inputPos() const { return input_offset; } // Inside of geometry() bool windowEvent(xcb_generic_event_t *e); void syncEvent(xcb_sync_alarm_notify_event_t* e); NET::WindowType windowType(bool direct = false, int supported_types = 0) const; bool manage(xcb_window_t w, bool isMapped); void releaseWindow(bool on_shutdown = false); void destroyClient(); virtual int desktop() const; virtual QStringList activities() const; void setOnActivity(const QString &activity, bool enable); void setOnAllActivities(bool set) override; void setOnActivities(QStringList newActivitiesList); void updateActivities(bool includeTransients); void blockActivityUpdates(bool b = true) override; /// Is not minimized and not hidden. I.e. normally visible on some virtual desktop. bool isShown(bool shaded_is_shown) const override; bool isHiddenInternal() const override; // For compositing ShadeMode shadeMode() const override; // Prefer isShade() void setShade(ShadeMode mode) override; bool isShadeable() const override; bool isMaximizable() const override; QRect geometryRestore() const override; MaximizeMode maximizeMode() const override; bool isMinimizable() const override; QRect iconGeometry() const override; void setFullScreen(bool set, bool user = true) override; bool isFullScreen() const override; bool isFullScreenable() const override; bool isFullScreenable(bool fullscreen_hack) const; bool userCanSetFullScreen() const override; QRect geometryFSRestore() const { return geom_fs_restore; // Only for session saving } int fullScreenMode() const { return fullscreen_mode; // only for session saving } bool noBorder() const override; void setNoBorder(bool set) override; bool userCanSetNoBorder() const override; void checkNoBorder(); int sessionStackingOrder() const; // Auxiliary functions, depend on the windowType bool wantsInput() const override; bool isResizable() const override; bool isMovable() const override; bool isMovableAcrossScreens() const override; bool isCloseable() const override; ///< May be closed by the user (May have a close button) void takeFocus() override; void updateDecoration(bool check_workspace_pos, bool force = false) override; void updateShape(); using AbstractClient::setGeometry; void setGeometry(int x, int y, int w, int h, ForceGeometry_t force = NormalGeometrySet) override; /// plainResize() simply resizes void plainResize(int w, int h, ForceGeometry_t force = NormalGeometrySet); void plainResize(const QSize& s, ForceGeometry_t force = NormalGeometrySet); /// resizeWithChecks() resizes according to gravity, and checks workarea position using AbstractClient::resizeWithChecks; void resizeWithChecks(int w, int h, ForceGeometry_t force = NormalGeometrySet) override; void resizeWithChecks(int w, int h, xcb_gravity_t gravity, ForceGeometry_t force = NormalGeometrySet); void resizeWithChecks(const QSize& s, xcb_gravity_t gravity, ForceGeometry_t force = NormalGeometrySet); QSize sizeForClientSize(const QSize&, Sizemode mode = SizemodeAny, bool noframe = false) const override; bool providesContextHelp() const override; const QKeySequence &shortcut() const override; void setShortcut(const QString& cut) override; Options::WindowOperation mouseButtonToWindowOperation(Qt::MouseButtons button); bool performMouseCommand(Options::MouseCommand, const QPoint& globalPos) override; QRect adjustedClientArea(const QRect& desktop, const QRect& area) const; xcb_colormap_t colormap() const; /// Updates visibility depending on being shaded, virtual desktop, etc. void updateVisibility(); /// Hides a client - Basically like minimize, but without effects, it's simply hidden void hideClient(bool hide) override; bool hiddenPreview() const; ///< Window is mapped in order to get a window pixmap virtual bool setupCompositing(); void finishCompositing(ReleaseReason releaseReason = ReleaseReason::Release) override; void setBlockingCompositing(bool block); inline bool isBlockingCompositing() { return blocks_compositing; } QString caption(bool full = true, bool stripped = false) const override; using AbstractClient::keyPressEvent; void keyPressEvent(uint key_code, xcb_timestamp_t time); // FRAME ?? void updateMouseGrab() override; xcb_window_t moveResizeGrabWindow() const; const QPoint calculateGravitation(bool invert, int gravity = 0) const; // FRAME public? void NETMoveResize(int x_root, int y_root, NET::Direction direction); void NETMoveResizeWindow(int flags, int x, int y, int width, int height); void restackWindow(xcb_window_t above, int detail, NET::RequestSource source, xcb_timestamp_t timestamp, bool send_event = false); void gotPing(xcb_timestamp_t timestamp); void updateUserTime(xcb_timestamp_t time = XCB_TIME_CURRENT_TIME); xcb_timestamp_t userTime() const override; bool hasUserTimeSupport() const; /// Does 'delete c;' static void deleteClient(Client* c); static bool belongToSameApplication(const Client* c1, const Client* c2, bool active_hack = false); static bool sameAppWindowRoleMatch(const Client* c1, const Client* c2, bool active_hack); void killWindow(); void toggleShade(); void showContextHelp() override; void cancelShadeHoverTimer(); void checkActiveModal(); StrutRect strutRect(StrutArea area) const; StrutRects strutRects() const; bool hasStrut() const override; // Tabbing functions TabGroup* tabGroup() const override; // Returns a pointer to client_group Q_INVOKABLE inline bool tabBefore(Client *other, bool activate) { return tabTo(other, false, activate); } Q_INVOKABLE inline bool tabBehind(Client *other, bool activate) { return tabTo(other, true, activate); } /** * Syncs the *dynamic* @param property @param fromThisClient or the @link currentTab() to * all members of the @link tabGroup() (if there is one) * * eg. if you call: * client->setProperty("kwin_tiling_floats", true); * client->syncTabGroupFor("kwin_tiling_floats", true) * all clients in this tabGroup will have ::property("kwin_tiling_floats").toBool() == true * * WARNING: non dynamic properties are ignored - you're not supposed to alter/update such explicitly */ Q_INVOKABLE void syncTabGroupFor(QString property, bool fromThisClient = false); Q_INVOKABLE bool untab(const QRect &toGeometry = QRect(), bool clientRemoved = false) override; /** * Set tab group - this is to be invoked by TabGroup::add/remove(client) and NO ONE ELSE */ void setTabGroup(TabGroup* group); /* * If shown is true the client is mapped and raised, if false * the client is unmapped and hidden, this function is called * when the tabbing group of the client switches its visible * client. */ void setClientShown(bool shown); /* * When a click is done in the decoration and it calls the group * to change the visible client it starts to move-resize the new * client, this function stops it. */ bool isCurrentTab() const override; /** * Whether or not the window has a strut that expands through the invisible area of * an xinerama setup where the monitors are not the same resolution. */ bool hasOffscreenXineramaStrut() const; // Decorations <-> Effects QRect decorationRect() const; QRect transparentRect() const; bool isClientSideDecorated() const; bool wantsShadowToBeRendered() const override; void layoutDecorationRects(QRect &left, QRect &top, QRect &right, QRect &bottom) const override; Xcb::Property fetchFirstInTabBox() const; void readFirstInTabBox(Xcb::Property &property); void updateFirstInTabBox(); Xcb::StringProperty fetchColorScheme() const; void readColorScheme(Xcb::StringProperty &property); void updateColorScheme(); //sets whether the client should be treated as a SessionInteract window void setSessionInteract(bool needed); virtual bool isClient() const; template void print(T &stream) const; void cancelFocusOutTimer(); /** * Restores the Client after it had been hidden due to show on screen edge functionality. * In addition the property gets deleted so that the Client knows that it is visible again. **/ void showOnScreenEdge(); static void cleanupX11(); public Q_SLOTS: void closeWindow() override; void updateCaption(); void evaluateWindowRules(); private Q_SLOTS: void shadeHover(); void shadeUnhover(); private: // Use Workspace::createClient() virtual ~Client(); ///< Use destroyClient() or releaseWindow() // Handlers for X11 events bool mapRequestEvent(xcb_map_request_event_t *e); void unmapNotifyEvent(xcb_unmap_notify_event_t *e); void destroyNotifyEvent(xcb_destroy_notify_event_t *e); void configureRequestEvent(xcb_configure_request_event_t *e); virtual void propertyNotifyEvent(xcb_property_notify_event_t *e) override; void clientMessageEvent(xcb_client_message_event_t *e) override; void enterNotifyEvent(xcb_enter_notify_event_t *e); void leaveNotifyEvent(xcb_leave_notify_event_t *e); void focusInEvent(xcb_focus_in_event_t *e); void focusOutEvent(xcb_focus_out_event_t *e); virtual void damageNotifyEvent(); bool buttonPressEvent(xcb_window_t w, int button, int state, int x, int y, int x_root, int y_root, xcb_timestamp_t time = XCB_CURRENT_TIME); bool buttonReleaseEvent(xcb_window_t w, int button, int state, int x, int y, int x_root, int y_root); bool motionNotifyEvent(xcb_window_t w, int state, int x, int y, int x_root, int y_root); Client* findAutogroupCandidate() const; protected: virtual void debug(QDebug& stream) const; - virtual bool shouldUnredirect() const; void addDamage(const QRegion &damage) override; bool belongsToSameApplication(const AbstractClient *other, bool active_hack) const override; void doSetActive() override; void doSetKeepAbove() override; void doSetKeepBelow() override; void doSetDesktop(int desktop, int was_desk) override; void doMinimize() override; void doSetSkipPager() override; void doSetSkipTaskbar() override; bool belongsToDesktop() const override; bool isActiveFullScreen() const override; void setGeometryRestore(const QRect &geo) override; void updateTabGroupStates(TabGroup::States states) override; void doMove(int x, int y) override; bool doStartMoveResize() override; void doPerformMoveResize() override; bool isWaitingForMoveResizeSync() const override; void doResizeSync() override; QSize resizeIncrements() const override; bool acceptsFocus() const override; private Q_SLOTS: void delayedSetShortcut(); //Signals for the scripting interface //Signals make an excellent way for communication //in between objects as compared to simple function //calls Q_SIGNALS: void clientManaging(KWin::Client*); void clientFullScreenSet(KWin::Client*, bool, bool); /** * Emitted whenever the Client's TabGroup changed. That is whenever the Client is moved to * another group, but not when a Client gets added or removed to the Client's ClientGroup. **/ void tabGroupChanged(); /** * Emitted whenever the Client want to show it menu */ void showRequest(); /** * Emitted whenever the Client's menu is closed */ void menuHidden(); /** * Emitted whenever the Client's menu is available **/ void appMenuAvailable(); /** * Emitted whenever the Client's menu is unavailable */ void appMenuUnavailable(); /** * Emitted whenever the Client's block compositing state changes. **/ void blockingCompositingChanged(KWin::Client *client); void clientSideDecoratedChanged(); private: void exportMappingState(int s); // ICCCM 4.1.3.1, 4.1.4, NETWM 2.5.1 bool isManaged() const; ///< Returns false if this client is not yet managed void updateAllowedActions(bool force = false); QRect fullscreenMonitorsArea(NETFullscreenMonitors topology) const; void changeMaximize(bool horizontal, bool vertical, bool adjust) override; int checkFullScreenHack(const QRect& geom) const; // 0 - None, 1 - One xinerama screen, 2 - Full area void updateFullScreenHack(const QRect& geom); void getWmNormalHints(); void getMotifHints(); void getIcons(); void fetchName(); void fetchIconicName(); QString readName() const; void setCaption(const QString& s, bool force = false); bool hasTransientInternal(const Client* c, bool indirect, ConstClientList& set) const; void finishWindowRules(); void setShortcutInternal(const QKeySequence &cut = QKeySequence()); void configureRequest(int value_mask, int rx, int ry, int rw, int rh, int gravity, bool from_tool); NETExtendedStrut strut() const; int checkShadeGeometry(int w, int h); void getSyncCounter(); void sendSyncRequest(); void leaveMoveResize() override; void positionGeometryTip() override; void grabButton(int mod); void ungrabButton(int mod); void resizeDecoration(); void createDecoration(const QRect &oldgeom); void pingWindow(); void killProcess(bool ask, xcb_timestamp_t timestamp = XCB_TIME_CURRENT_TIME); void updateUrgency(); static void sendClientMessage(xcb_window_t w, xcb_atom_t a, xcb_atom_t protocol, uint32_t data1 = 0, uint32_t data2 = 0, uint32_t data3 = 0, xcb_timestamp_t timestamp = xTime()); void embedClient(xcb_window_t w, xcb_visualid_t visualid, xcb_colormap_t colormap, uint8_t depth); void detectNoBorder(); Xcb::Property fetchGtkFrameExtents() const; void readGtkFrameExtents(Xcb::Property &prop); void detectGtkFrameExtents(); void destroyDecoration() override; void updateFrameExtents(); void internalShow(); void internalHide(); void internalKeep(); void map(); void unmap(); void updateHiddenPreview(); void updateInputShape(); xcb_timestamp_t readUserTimeMapTimestamp(const KStartupInfoId* asn_id, const KStartupInfoData* asn_data, bool session) const; xcb_timestamp_t readUserCreationTime() const; void startupIdChanged(); void updateInputWindow(); bool tabTo(Client *other, bool behind, bool activate); Xcb::Property fetchShowOnScreenEdge() const; void readShowOnScreenEdge(Xcb::Property &property); /** * Reads the property and creates/destroys the screen edge if required * and shows/hides the client. **/ void updateShowOnScreenEdge(); Xcb::Window m_client; Xcb::Window m_wrapper; Xcb::Window m_frame; QStringList activityList; int m_activityUpdatesBlocked; bool m_blockedActivityUpdatesRequireTransients; Xcb::Window m_moveResizeGrabWindow; bool move_resize_has_keyboard_grab; bool m_managed; Xcb::GeometryHints m_geometryHints; void sendSyntheticConfigureNotify(); enum MappingState { Withdrawn, ///< Not handled, as per ICCCM WithdrawnState Mapped, ///< The frame is mapped Unmapped, ///< The frame is not mapped Kept ///< The frame should be unmapped, but is kept (For compositing) }; MappingState mapping_state; Xcb::TransientFor fetchTransient() const; void readTransientProperty(Xcb::TransientFor &transientFor); void readTransient(); xcb_window_t verifyTransientFor(xcb_window_t transient_for, bool set); void addTransient(AbstractClient* cl) override; void removeTransient(AbstractClient* cl) override; void removeFromMainClients(); void cleanGrouping(); void checkGroupTransients(); void setTransient(xcb_window_t new_transient_for_id); xcb_window_t m_transientForId; xcb_window_t m_originalTransientForId; ShadeMode shade_mode; Client *shade_below; uint deleting : 1; ///< True when doing cleanup and destroying the client Xcb::MotifHints m_motif; uint hidden : 1; ///< Forcibly hidden by calling hide() uint noborder : 1; uint app_noborder : 1; ///< App requested no border via window type, shape extension, etc. uint ignore_focus_stealing : 1; ///< Don't apply focus stealing prevention to this client bool blocks_compositing; WindowRules client_rules; // DON'T reorder - Saved to config files !!! enum FullScreenMode { FullScreenNone, FullScreenNormal, FullScreenHack ///< Non-NETWM fullscreen (noborder and size of desktop) }; FullScreenMode fullscreen_mode; MaximizeMode max_mode; QRect geom_restore; QRect geom_fs_restore; QTimer* shadeHoverTimer; xcb_colormap_t m_colormap; QString cap_normal, cap_iconic, cap_suffix, cap_deco; Group* in_group; TabGroup* tab_group; QTimer* ping_timer; qint64 m_killHelperPID; xcb_timestamp_t m_pingTimestamp; xcb_timestamp_t m_userTime; NET::Actions allowed_actions; QSize client_size; bool shade_geometry_change; struct { xcb_sync_counter_t counter; xcb_sync_int64_t value; xcb_sync_alarm_t alarm; xcb_timestamp_t lastTimestamp; QTimer *timeout, *failsafeTimeout; bool isPending; } syncRequest; static bool check_active_modal; ///< \see Client::checkActiveModal() QKeySequence _shortcut; int sm_stacking_order; friend struct ResetupRulesProcedure; friend bool performTransiencyCheck(); Xcb::StringProperty fetchActivities() const; void readActivities(Xcb::StringProperty &property); void checkActivities(); bool activitiesDefined; //whether the x property was actually set bool needsSessionInteract; bool needsXWindowMove; Xcb::Window m_decoInputExtent; QPoint input_offset; QTimer *m_focusOutTimer; QList m_connections; bool m_clientSideDecorated; QMetaObject::Connection m_edgeRemoveConnection; }; inline xcb_window_t Client::wrapperId() const { return m_wrapper; } inline bool Client::isClientSideDecorated() const { return m_clientSideDecorated; } inline bool Client::groupTransient() const { return m_transientForId == rootWindow(); } // Needed because verifyTransientFor() may set transient_for_id to root window, // if the original value has a problem (window doesn't exist, etc.) inline bool Client::wasOriginallyGroupTransient() const { return m_originalTransientForId == rootWindow(); } inline bool Client::isTransient() const { return m_transientForId != XCB_WINDOW_NONE; } inline const Group* Client::group() const { return in_group; } inline Group* Client::group() { return in_group; } inline TabGroup* Client::tabGroup() const { return tab_group; } inline bool Client::isShown(bool shaded_is_shown) const { return !isMinimized() && (!isShade() || shaded_is_shown) && !hidden && (!tabGroup() || tabGroup()->current() == this); } inline bool Client::isHiddenInternal() const { return hidden; } inline ShadeMode Client::shadeMode() const { return shade_mode; } inline QRect Client::geometryRestore() const { return geom_restore; } inline void Client::setGeometryRestore(const QRect &geo) { geom_restore = geo; } inline MaximizeMode Client::maximizeMode() const { return max_mode; } inline bool Client::isFullScreen() const { return fullscreen_mode != FullScreenNone; } inline bool Client::hasNETSupport() const { return info->hasNETSupport(); } inline xcb_colormap_t Client::colormap() const { return m_colormap; } inline int Client::sessionStackingOrder() const { return sm_stacking_order; } inline bool Client::isManaged() const { return m_managed; } inline QSize Client::clientSize() const { return client_size; } inline void Client::plainResize(const QSize& s, ForceGeometry_t force) { plainResize(s.width(), s.height(), force); } inline void Client::resizeWithChecks(int w, int h, AbstractClient::ForceGeometry_t force) { resizeWithChecks(w, h, XCB_GRAVITY_BIT_FORGET, force); } inline void Client::resizeWithChecks(const QSize& s, xcb_gravity_t gravity, ForceGeometry_t force) { resizeWithChecks(s.width(), s.height(), gravity, force); } inline bool Client::hasUserTimeSupport() const { return info->userTime() != -1U; } inline const WindowRules* Client::rules() const { return &client_rules; } inline xcb_window_t Client::moveResizeGrabWindow() const { return m_moveResizeGrabWindow; } inline const QKeySequence &Client::shortcut() const { return _shortcut; } inline void Client::removeRule(Rules* rule) { client_rules.remove(rule); } inline bool Client::hiddenPreview() const { return mapping_state == Kept; } template inline void Client::print(T &stream) const { stream << "\'ID:" << window() << ";WMCLASS:" << resourceClass() << ":" << resourceName() << ";Caption:" << caption() << "\'"; } } // namespace Q_DECLARE_METATYPE(KWin::Client*) Q_DECLARE_METATYPE(QList) #endif diff --git a/composite.cpp b/composite.cpp index 62775f67c..3a40b0064 100644 --- a/composite.cpp +++ b/composite.cpp @@ -1,1328 +1,1177 @@ /******************************************************************** 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 . *********************************************************************/ #include "composite.h" #include "abstract_egl_backend.h" #include "dbusinterface.h" #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 "scene_qpainter.h" #include "screens.h" #include "shadow.h" #include "useractions.h" #include "xcbutils.h" #include "platform.h" #include "shell_client.h" #include "wayland_server.h" #include "decorations/decoratedclient.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(KWin::Compositor::SuspendReason) namespace KWin { extern int currentRefreshRate(); CompositorSelectionOwner::CompositorSelectionOwner(const char *selection) : KSelectionOwner(selection, connection(), rootWindow()), owning(false) { connect (this, SIGNAL(lostOwnership()), SLOT(looseOwnership())); } void CompositorSelectionOwner::looseOwnership() { owning = false; } KWIN_SINGLETON_FACTORY_VARIABLE(Compositor, s_compositor) static inline qint64 milliToNano(int milli) { return qint64(milli) * 1000 * 1000; } static inline qint64 nanoToMilli(int nano) { return nano / (1000*1000); } Compositor::Compositor(QObject* workspace) : QObject(workspace) , m_suspended(options->isUseCompositing() ? NoReasonSuspend : UserSuspend) , cm_selection(NULL) , vBlankInterval(0) , fpsInterval(0) , m_xrrRefreshRate(0) - , forceUnredirectCheck(false) , m_finishing(false) , m_timeSinceLastVBlank(0) , m_scene(NULL) , m_bufferSwapPending(false) , m_composeAtSwapCompletion(false) { qRegisterMetaType("Compositor::SuspendReason"); - connect(&unredirectTimer, SIGNAL(timeout()), SLOT(delayedCheckUnredirect())); connect(&compositeResetTimer, SIGNAL(timeout()), SLOT(restart())); connect(options, &Options::configChanged, this, &Compositor::slotConfigChanged); - connect(options, SIGNAL(unredirectFullscreenChanged()), SLOT(delayedCheckUnredirect())); - unredirectTimer.setSingleShot(true); compositeResetTimer.setSingleShot(true); nextPaintReference.invalidate(); // Initialize the timer // 2 sec which should be enough to restart the compositor static const int compositorLostMessageDelay = 2000; m_releaseSelectionTimer.setSingleShot(true); m_releaseSelectionTimer.setInterval(compositorLostMessageDelay); connect(&m_releaseSelectionTimer, SIGNAL(timeout()), SLOT(releaseCompositorSelection())); m_unusedSupportPropertyTimer.setInterval(compositorLostMessageDelay); m_unusedSupportPropertyTimer.setSingleShot(true); connect(&m_unusedSupportPropertyTimer, SIGNAL(timeout()), SLOT(deleteUnusedSupportProperties())); // delay the call to setup by one event cycle // The ctor of this class is invoked from the Workspace ctor, that means before // Workspace is completely constructed, so calling Workspace::self() would result // in undefined behavior. This is fixed by using a delayed invocation. if (kwinApp()->platform()->isReady()) { QMetaObject::invokeMethod(this, "setup", Qt::QueuedConnection); } connect(kwinApp()->platform(), &Platform::readyChanged, this, [this] (bool ready) { if (ready) { setup(); } else { finish(); } }, Qt::QueuedConnection ); connect(kwinApp(), &Application::x11ConnectionAboutToBeDestroyed, this, [this] { delete cm_selection; cm_selection = nullptr; } ); // register DBus new CompositorDBusInterface(this); } Compositor::~Compositor() { emit aboutToDestroy(); AbstractEglBackend::unbindWaylandDisplay(); finish(); deleteUnusedSupportProperties(); delete cm_selection; s_compositor = NULL; } void Compositor::setup() { if (hasScene()) return; if (m_suspended) { QStringList reasons; if (m_suspended & UserSuspend) { reasons << QStringLiteral("Disabled by User"); } if (m_suspended & BlockRuleSuspend) { reasons << QStringLiteral("Disabled by Window"); } if (m_suspended & ScriptSuspend) { reasons << QStringLiteral("Disabled by Script"); } qCDebug(KWIN_CORE) << "Compositing is suspended, reason:" << reasons; return; } else if (!kwinApp()->platform()->compositingPossible()) { qCCritical(KWIN_CORE) << "Compositing is not possible"; return; } m_starting = true; if (!options->isCompositingInitialized()) { options->reloadCompositingSettings(true); slotCompositingOptionsInitialized(); } else { slotCompositingOptionsInitialized(); } } extern int screen_number; // main.cpp extern bool is_multihead; void Compositor::slotCompositingOptionsInitialized() { claimCompositorSelection(); // There might still be a deleted around, needs to be cleared before creating the scene (BUG 333275) if (Workspace::self()) { while (!Workspace::self()->deletedList().isEmpty()) { Workspace::self()->deletedList().first()->discard(); } } switch(options->compositingMode()) { case OpenGLCompositing: { qCDebug(KWIN_CORE) << "Initializing OpenGL compositing"; // Some broken drivers crash on glXQuery() so to prevent constant KWin crashes: if (kwinApp()->platform()->openGLCompositingIsBroken()) qCWarning(KWIN_CORE) << "KWin has detected that your OpenGL library is unsafe to use"; else { kwinApp()->platform()->createOpenGLSafePoint(Platform::OpenGLSafePoint::PreInit); m_scene = SceneOpenGL::createScene(this); // TODO: Add 30 second delay to protect against screen freezes as well kwinApp()->platform()->createOpenGLSafePoint(Platform::OpenGLSafePoint::PostInit); if (m_scene && !m_scene->initFailed()) { connect(static_cast(m_scene), &SceneOpenGL::resetCompositing, this, &Compositor::restart); break; // --> } delete m_scene; m_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: qCDebug(KWIN_CORE) << "Initializing XRender compositing"; m_scene = SceneXrender::createScene(this); break; #endif case QPainterCompositing: qCDebug(KWIN_CORE) << "Initializing QPainter compositing"; m_scene = SceneQPainter::createScene(this); break; default: qCDebug(KWIN_CORE) << "No compositing enabled"; m_starting = false; if (cm_selection) { cm_selection->owning = false; cm_selection->release(); } if (kwinApp()->platform()->requiresCompositing()) { qCCritical(KWIN_CORE) << "The used windowing system requires compositing"; qCCritical(KWIN_CORE) << "We are going to quit KWin now as it is broken"; qApp->quit(); } return; } if (m_scene == NULL || m_scene->initFailed()) { qCCritical(KWIN_CORE) << "Failed to initialize compositing, compositing disabled"; delete m_scene; m_scene = NULL; m_starting = false; if (cm_selection) { cm_selection->owning = false; cm_selection->release(); } if (kwinApp()->platform()->requiresCompositing()) { qCCritical(KWIN_CORE) << "The used windowing system requires compositing"; qCCritical(KWIN_CORE) << "We are going to quit KWin now as it is broken"; qApp->quit(); } return; } emit sceneCreated(); if (Workspace::self()) { startupWithWorkspace(); } else { connect(kwinApp(), &Application::workspaceCreated, this, &Compositor::startupWithWorkspace); } } void Compositor::claimCompositorSelection() { if (!cm_selection && kwinApp()->x11Connection()) { char selection_name[ 100 ]; sprintf(selection_name, "_NET_WM_CM_S%d", Application::x11ScreenNumber()); cm_selection = new CompositorSelectionOwner(selection_name); connect(cm_selection, SIGNAL(lostOwnership()), SLOT(finish())); } if (!cm_selection) // no X11 yet return; if (!cm_selection->owning) { cm_selection->claim(true); // force claiming cm_selection->owning = true; } } void Compositor::startupWithWorkspace() { if (!m_starting) { return; } Q_ASSERT(m_scene); connect(workspace(), &Workspace::destroyed, this, [this] { compositeTimer.stop(); }); claimCompositorSelection(); m_xrrRefreshRate = KWin::currentRefreshRate(); fpsInterval = options->maxFpsInterval(); if (m_scene->syncsToVBlank()) { // if we do vsync, set the fps to the next multiple of the vblank rate vBlankInterval = milliToNano(1000) / m_xrrRefreshRate; fpsInterval = qMax((fpsInterval / vBlankInterval) * vBlankInterval, vBlankInterval); } else vBlankInterval = milliToNano(1); // no sync - DO NOT set "0", would cause div-by-zero segfaults. m_timeSinceLastVBlank = fpsInterval - (options->vBlankTime() + 1); // means "start now" - we don't have even a slight idea when the first vsync will occur scheduleRepaint(); xcb_composite_redirect_subwindows(connection(), rootWindow(), XCB_COMPOSITE_REDIRECT_MANUAL); new EffectsHandlerImpl(this, m_scene); // sets also the 'effects' pointer connect(Workspace::self(), &Workspace::deletedRemoved, m_scene, &Scene::windowDeleted); connect(effects, SIGNAL(screenGeometryChanged(QSize)), SLOT(addRepaintFull())); addRepaintFull(); foreach (Client * c, Workspace::self()->clientList()) { c->setupCompositing(); c->getShadow(); } foreach (Client * c, Workspace::self()->desktopList()) c->setupCompositing(); foreach (Unmanaged * c, Workspace::self()->unmanagedList()) { c->setupCompositing(); c->getShadow(); } if (auto w = waylandServer()) { const auto clients = w->clients(); for (auto c : clients) { c->setupCompositing(); c->getShadow(); } } emit compositingToggled(true); m_starting = false; if (m_releaseSelectionTimer.isActive()) { m_releaseSelectionTimer.stop(); } // render at least once performCompositing(); } void Compositor::scheduleRepaint() { if (!compositeTimer.isActive()) setCompositeTimer(); } void Compositor::finish() { if (!hasScene()) return; m_finishing = true; m_releaseSelectionTimer.start(); if (Workspace::self()) { foreach (Client * c, Workspace::self()->clientList()) m_scene->windowClosed(c, NULL); foreach (Client * c, Workspace::self()->desktopList()) m_scene->windowClosed(c, NULL); foreach (Unmanaged * c, Workspace::self()->unmanagedList()) m_scene->windowClosed(c, NULL); foreach (Deleted * c, Workspace::self()->deletedList()) m_scene->windowDeleted(c); foreach (Client * c, Workspace::self()->clientList()) c->finishCompositing(); foreach (Client * c, Workspace::self()->desktopList()) c->finishCompositing(); foreach (Unmanaged * c, Workspace::self()->unmanagedList()) c->finishCompositing(); foreach (Deleted * c, Workspace::self()->deletedList()) c->finishCompositing(); xcb_composite_unredirect_subwindows(connection(), rootWindow(), XCB_COMPOSITE_REDIRECT_MANUAL); } delete effects; effects = NULL; delete m_scene; m_scene = NULL; compositeTimer.stop(); repaints_region = QRegion(); if (Workspace::self()) { for (ClientList::ConstIterator it = Workspace::self()->clientList().constBegin(); it != Workspace::self()->clientList().constEnd(); ++it) { // forward all opacity values to the frame in case there'll be other CM running if ((*it)->opacity() != 1.0) { NETWinInfo i(connection(), (*it)->frameId(), rootWindow(), 0, 0); i.setOpacity(static_cast< unsigned long >((*it)->opacity() * 0xffffffff)); } } // discard all Deleted windows (#152914) while (!Workspace::self()->deletedList().isEmpty()) Workspace::self()->deletedList().first()->discard(); } m_finishing = false; emit compositingToggled(false); } void Compositor::releaseCompositorSelection() { if (hasScene() && !m_finishing) { // compositor is up and running again, no need to release the selection return; } if (m_starting) { // currently still starting the compositor, it might fail, so restart the timer to test again m_releaseSelectionTimer.start(); return; } if (m_finishing) { // still shutting down, a restart might follow, so restart the timer to test again m_releaseSelectionTimer.start(); return; } qCDebug(KWIN_CORE) << "Releasing compositor selection"; if (cm_selection) { cm_selection->owning = false; cm_selection->release(); } } void Compositor::keepSupportProperty(xcb_atom_t atom) { m_unusedSupportProperties.removeAll(atom); } void Compositor::removeSupportProperty(xcb_atom_t atom) { m_unusedSupportProperties << atom; m_unusedSupportPropertyTimer.start(); } void Compositor::deleteUnusedSupportProperties() { if (m_starting) { // currently still starting the compositor m_unusedSupportPropertyTimer.start(); return; } if (m_finishing) { // still shutting down, a restart might follow m_unusedSupportPropertyTimer.start(); return; } if (kwinApp()->x11Connection()) { foreach (const xcb_atom_t &atom, m_unusedSupportProperties) { // remove property from root window xcb_delete_property(connection(), rootWindow(), atom); } } } // OpenGL self-check failed, fallback to XRender void Compositor::fallbackToXRenderCompositing() { finish(); KConfigGroup config(kwinApp()->config(), "Compositing"); config.writeEntry("Backend", "XRender"); config.sync(); options->setCompositingMode(XRenderCompositing); setup(); } void Compositor::slotConfigChanged() { if (!m_suspended) { setup(); if (effects) // setupCompositing() may fail effects->reconfigure(); addRepaintFull(); } else finish(); } void Compositor::slotReinitialize() { // Reparse config. Config options will be reloaded by setup() kwinApp()->config()->reparseConfiguration(); // Restart compositing finish(); // resume compositing if suspended m_suspended = NoReasonSuspend; options->setCompositingInitialized(false); setup(); if (effects) { // setup() may fail effects->reconfigure(); } } // for the shortcut void Compositor::slotToggleCompositing() { if (kwinApp()->platform()->requiresCompositing()) { // we are not allowed to turn on/off compositing return; } if (m_suspended) { // direct user call; clear all bits resume(AllReasonSuspend); } else { // but only set the user one (sufficient to suspend) suspend(UserSuspend); } } void Compositor::updateCompositeBlocking() { updateCompositeBlocking(NULL); } void Compositor::updateCompositeBlocking(Client *c) { if (kwinApp()->platform()->requiresCompositing()) { return; } if (c) { // if c == 0 we just check if we can resume if (c->isBlockingCompositing()) { if (!(m_suspended & BlockRuleSuspend)) // do NOT attempt to call suspend(true); from within the eventchain! QMetaObject::invokeMethod(this, "suspend", Qt::QueuedConnection, Q_ARG(Compositor::SuspendReason, BlockRuleSuspend)); } } else if (m_suspended & BlockRuleSuspend) { // lost a client and we're blocked - can we resume? bool resume = true; for (ClientList::ConstIterator it = Workspace::self()->clientList().constBegin(); it != Workspace::self()->clientList().constEnd(); ++it) { if ((*it)->isBlockingCompositing()) { resume = false; break; } } if (resume) { // do NOT attempt to call suspend(false); from within the eventchain! QMetaObject::invokeMethod(this, "resume", Qt::QueuedConnection, Q_ARG(Compositor::SuspendReason, BlockRuleSuspend)); } } } void Compositor::suspend(Compositor::SuspendReason reason) { if (kwinApp()->platform()->requiresCompositing()) { return; } Q_ASSERT(reason != NoReasonSuspend); m_suspended |= reason; if (reason & KWin::Compositor::ScriptSuspend) { // when disabled show a shortcut how the user can get back compositing const auto shortcuts = KGlobalAccel::self()->shortcut(workspace()->findChild(QStringLiteral("Suspend Compositing"))); if (!shortcuts.isEmpty()) { // display notification only if there is the shortcut const QString message = i18n("Desktop effects have been suspended by another application.
" "You can resume using the '%1' shortcut.", shortcuts.first().toString(QKeySequence::NativeText)); KNotification::event(QStringLiteral("compositingsuspendeddbus"), message); } } finish(); } void Compositor::resume(Compositor::SuspendReason reason) { Q_ASSERT(reason != NoReasonSuspend); m_suspended &= ~reason; setup(); // signal "toggled" is eventually emitted from within setup } void Compositor::restart() { if (hasScene()) { finish(); QTimer::singleShot(0, this, SLOT(setup())); } } void Compositor::addRepaint(int x, int y, int w, int h) { if (!hasScene()) return; repaints_region += QRegion(x, y, w, h); scheduleRepaint(); } void Compositor::addRepaint(const QRect& r) { if (!hasScene()) return; repaints_region += r; scheduleRepaint(); } void Compositor::addRepaint(const QRegion& r) { if (!hasScene()) return; repaints_region += r; scheduleRepaint(); } void Compositor::addRepaintFull() { if (!hasScene()) return; const QSize &s = screens()->size(); repaints_region = QRegion(0, 0, s.width(), s.height()); scheduleRepaint(); } void Compositor::timerEvent(QTimerEvent *te) { if (te->timerId() == compositeTimer.timerId()) { performCompositing(); } else QObject::timerEvent(te); } void Compositor::aboutToSwapBuffers() { assert(!m_bufferSwapPending); m_bufferSwapPending = true; } void Compositor::bufferSwapComplete() { assert(m_bufferSwapPending); m_bufferSwapPending = false; if (m_composeAtSwapCompletion) { m_composeAtSwapCompletion = false; performCompositing(); } } void Compositor::performCompositing() { if (m_scene->usesOverlayWindow() && !isOverlayWindowVisible()) return; // nothing is visible anyway // If a buffer swap is still pending, we return to the event loop and // continue processing events until the swap has completed. if (m_bufferSwapPending) { m_composeAtSwapCompletion = true; compositeTimer.stop(); return; } // If outputs are disabled, we return to the event loop and // continue processing events until the outputs are enabled again if (!kwinApp()->platform()->areOutputsEnabled()) { compositeTimer.stop(); return; } // Create a list of all windows in the stacking order ToplevelList windows = Workspace::self()->xStackingOrder(); ToplevelList damaged; // Reset the damage state of each window and fetch the damage region // without waiting for a reply foreach (Toplevel *win, windows) { if (win->resetAndFetchDamage()) damaged << win; } if (damaged.count() > 0) { m_scene->triggerFence(); xcb_flush(connection()); } // Move elevated windows to the top of the stacking order foreach (EffectWindow *c, static_cast(effects)->elevatedWindows()) { Toplevel* t = static_cast< EffectWindowImpl* >(c)->window(); windows.removeAll(t); windows.append(t); } // Get the replies foreach (Toplevel *win, damaged) { // Discard the cached lanczos texture if (win->effectWindow()) { const QVariant texture = win->effectWindow()->data(LanczosCacheRole); if (texture.isValid()) { delete static_cast(texture.value()); win->effectWindow()->setData(LanczosCacheRole, QVariant()); } } win->getDamageRegionReply(); } if (repaints_region.isEmpty() && !windowRepaintsPending()) { m_scene->idle(); m_timeSinceLastVBlank = fpsInterval - (options->vBlankTime() + 1); // means "start now" m_timeSinceStart += m_timeSinceLastVBlank; // 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. compositeTimer.stop(); return; } // skip windows that are not yet ready for being painted and if screen is locked skip windows that are // neither lockscreen nor inputmethod windows // 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); } if (waylandServer() && waylandServer()->isScreenLocked()) { if(!t->isLockScreen() && !t->isInputMethod()) { windows.removeAll(t); } } } QRegion repaints = repaints_region; // clear all repaints, so that post-pass can add repaints for the next repaint repaints_region = QRegion(); m_timeSinceLastVBlank = m_scene->paint(repaints, windows); m_timeSinceStart += m_timeSinceLastVBlank; if (waylandServer()) { for (Toplevel *win : damaged) { if (auto surface = win->surface()) { surface->frameRendered(m_timeSinceStart); } } } compositeTimer.stop(); // stop here to ensure *we* cause the next repaint schedule - not some effect through m_scene->paint() // 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 // scheduleRepaint() would restart it again somewhen later, called from functions that // would again add something pending. if (m_bufferSwapPending && m_scene->syncsToVBlank()) { m_composeAtSwapCompletion = true; } else { scheduleRepaint(); } } bool Compositor::windowRepaintsPending() const { foreach (Toplevel * c, Workspace::self()->clientList()) if (!c->repaints().isEmpty()) return true; foreach (Toplevel * c, Workspace::self()->desktopList()) if (!c->repaints().isEmpty()) return true; foreach (Toplevel * c, Workspace::self()->unmanagedList()) if (!c->repaints().isEmpty()) return true; foreach (Toplevel * c, Workspace::self()->deletedList()) if (!c->repaints().isEmpty()) return true; if (auto w = waylandServer()) { const auto &clients = w->clients(); for (auto c : clients) { if (c->readyForPainting() && !c->repaints().isEmpty()) { return true; } } const auto &internalClients = w->internalClients(); for (auto c : internalClients) { if (c->isShown(true) && !c->repaints().isEmpty()) { return true; } } } return false; } void Compositor::setCompositeResetTimer(int msecs) { compositeResetTimer.start(msecs); } void Compositor::setCompositeTimer() { if (!hasScene()) // should not really happen, but there may be e.g. some damage events still pending return; if (m_starting || !Workspace::self()) { return; } // Don't start the timer if we're waiting for a swap event if (m_bufferSwapPending && m_composeAtSwapCompletion) return; // Don't start the timer if all outputs are disabled if (!kwinApp()->platform()->areOutputsEnabled()) { return; } uint waitTime = 1; if (m_scene->blocksForRetrace()) { // TODO: make vBlankTime dynamic?! // It's required because glXWaitVideoSync will *likely* block a full frame if one enters // a retrace pass which can last a variable amount of time, depending on the actual screen // Now, my ooold 19" CRT can do such retrace so that 2ms are entirely sufficient, // while another ooold 15" TFT requires about 6ms qint64 padding = m_timeSinceLastVBlank; if (padding > fpsInterval) { // we're at low repaints or spent more time in painting than the user wanted to wait for that frame padding = vBlankInterval - (padding%vBlankInterval); // -> align to next vblank } else { // -> align to the next maxFps tick padding = ((vBlankInterval - padding%vBlankInterval) + (fpsInterval/vBlankInterval-1)*vBlankInterval); // "remaining time of the first vsync" + "time for the other vsyncs of the frame" } if (padding < options->vBlankTime()) { // we'll likely miss this frame waitTime = nanoToMilli(padding + vBlankInterval - options->vBlankTime()); // so we add one } else { waitTime = nanoToMilli(padding - options->vBlankTime()); } } else { // w/o blocking vsync we just jump to the next demanded tick if (fpsInterval > m_timeSinceLastVBlank) { waitTime = nanoToMilli(fpsInterval - m_timeSinceLastVBlank); if (!waitTime) { waitTime = 1; // will ensure we don't block out the eventloop - the system's just not faster ... } }/* else if (m_scene->syncsToVBlank() && m_timeSinceLastVBlank - fpsInterval < (vBlankInterval<<1)) { // NOTICE - "for later" ------------------------------------------------------------------ // It can happen that we push two frames within one refresh cycle. // Swapping will then block even with triple buffering when the GPU does not discard but // queues frames // now here's the mean part: if we take that as "OMG, we're late - next frame ASAP", // there'll immediately be 2 frames in the pipe, swapping will block, we think we're // late ... ewww // so instead we pad to the clock again and add 2ms safety to ensure the pipe is really // free // NOTICE: obviously m_timeSinceLastVBlank can be too big because we're too slow as well // So if this code was enabled, we'd needlessly half the framerate once more (15 instead of 30) waitTime = nanoToMilli(vBlankInterval - (m_timeSinceLastVBlank - fpsInterval)%vBlankInterval) + 2; }*/ else { waitTime = 1; // ... "0" would be sufficient, but the compositor isn't the WMs only task } } compositeTimer.start(qMin(waitTime, 250u), this); // force 4fps minimum } bool Compositor::isActive() { return !m_finishing && hasScene(); } -void Compositor::checkUnredirect() -{ - checkUnredirect(false); -} - -// force is needed when the list of windows changes (e.g. a window goes away) -void Compositor::checkUnredirect(bool force) -{ - if (!hasScene() || !m_scene->overlayWindow() || m_scene->overlayWindow()->window() == None || !options->isUnredirectFullscreen()) - return; - if (force) - forceUnredirectCheck = true; - if (!unredirectTimer.isActive()) - unredirectTimer.start(0); -} - -void Compositor::delayedCheckUnredirect() -{ - if (!hasScene() || !m_scene->overlayWindow() || m_scene->overlayWindow()->window() == None || !(options->isUnredirectFullscreen() || sender() == options)) - return; - ToplevelList list; - bool changed = forceUnredirectCheck; - foreach (Client * c, Workspace::self()->clientList()) - list.append(c); - foreach (Unmanaged * c, Workspace::self()->unmanagedList()) - list.append(c); - foreach (Toplevel * c, list) { - if (c->updateUnredirectedState()) { - changed = true; - break; - } - } - // 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. - const QSize &s = screens()->size(); - QRegion reg(0, 0, s.width(), s.height()); - foreach (Toplevel * c, list) { - if (c->unredirected()) - reg -= c->geometry(); - } - m_scene->overlayWindow()->setShape(reg); - addRepaint(reg); -} - bool Compositor::checkForOverlayWindow(WId w) const { if (!hasScene()) { // no scene, so it cannot be the overlay window return false; } if (!m_scene->overlayWindow()) { // no overlay window, it cannot be the overlay return false; } // and compare the window ID's return w == m_scene->overlayWindow()->window(); } WId Compositor::overlayWindow() const { if (!hasScene()) { return None; } if (!m_scene->overlayWindow()) { return None; } return m_scene->overlayWindow()->window(); } bool Compositor::isOverlayWindowVisible() const { if (!hasScene()) { return false; } if (!m_scene->overlayWindow()) { return false; } return m_scene->overlayWindow()->isVisible(); } void Compositor::setOverlayWindowVisibility(bool visible) { if (hasScene() && m_scene->overlayWindow()) { m_scene->overlayWindow()->setVisibility(visible); } } /***************************************************** * Workspace ****************************************************/ bool Workspace::compositing() const { return m_compositor && m_compositor->hasScene(); } //**************************************** // Toplevel //**************************************** bool Toplevel::setupCompositing() { if (!compositing()) return false; if (damage_handle != XCB_NONE) return false; if (kwinApp()->operationMode() == Application::OperationModeX11 && !surface()) { damage_handle = xcb_generate_id(connection()); xcb_damage_create(connection(), damage_handle, frameId(), XCB_DAMAGE_REPORT_LEVEL_NON_EMPTY); } damage_region = QRegion(0, 0, width(), height()); effect_window = new EffectWindowImpl(this); - unredirect = false; - Compositor::self()->checkUnredirect(true); Compositor::self()->scene()->windowAdded(this); // With unmanaged windows there is a race condition between the client painting the window // and us setting up damage tracking. If the client wins we won't get a damage event even // though the window has been painted. To avoid this we mark the whole window as damaged // and schedule a repaint immediately after creating the damage object. if (dynamic_cast(this)) addDamageFull(); return true; } void Toplevel::finishCompositing(ReleaseReason releaseReason) { if (kwinApp()->operationMode() == Application::OperationModeX11 && damage_handle == XCB_NONE) return; - Compositor::self()->checkUnredirect(true); if (effect_window->window() == this) { // otherwise it's already passed to Deleted, don't free data discardWindowPixmap(); delete effect_window; } if (damage_handle != XCB_NONE && releaseReason != ReleaseReason::Destroyed) { xcb_damage_destroy(connection(), damage_handle); } damage_handle = XCB_NONE; damage_region = QRegion(); repaints_region = QRegion(); effect_window = NULL; } void Toplevel::discardWindowPixmap() { addDamageFull(); if (effectWindow() != NULL && effectWindow()->sceneWindow() != NULL) effectWindow()->sceneWindow()->pixmapDiscarded(); } void Toplevel::damageNotifyEvent() { m_isDamaged = true; // Note: The rect is supposed to specify the damage extents, // but we don't know it at this point. No one who connects // to this signal uses the rect however. emit damaged(this, QRect()); } bool Toplevel::compositing() const { return Workspace::self()->compositing(); } void Client::damageNotifyEvent() { if (syncRequest.isPending && isResize()) { emit damaged(this, QRect()); m_isDamaged = true; return; } if (!ready_for_painting) { // avoid "setReadyForPainting()" function calling overhead if (syncRequest.counter == XCB_NONE) { // cannot detect complete redraw, consider done now setReadyForPainting(); setupWindowManagementInterface(); } } Toplevel::damageNotifyEvent(); } bool Toplevel::resetAndFetchDamage() { if (!m_isDamaged) return false; if (damage_handle == XCB_NONE) { m_isDamaged = false; return true; } xcb_connection_t *conn = connection(); // Create a new region and copy the damage region to it, // resetting the damaged state. xcb_xfixes_region_t region = xcb_generate_id(conn); xcb_xfixes_create_region(conn, region, 0, 0); xcb_damage_subtract(conn, damage_handle, 0, region); // Send a fetch-region request and destroy the region m_regionCookie = xcb_xfixes_fetch_region_unchecked(conn, region); xcb_xfixes_destroy_region(conn, region); m_isDamaged = false; m_damageReplyPending = true; return m_damageReplyPending; } void Toplevel::getDamageRegionReply() { if (!m_damageReplyPending) return; m_damageReplyPending = false; // Get the fetch-region reply xcb_xfixes_fetch_region_reply_t *reply = xcb_xfixes_fetch_region_reply(connection(), m_regionCookie, 0); if (!reply) return; // Convert the reply to a QRegion int count = xcb_xfixes_fetch_region_rectangles_length(reply); QRegion region; if (count > 1 && count < 16) { xcb_rectangle_t *rects = xcb_xfixes_fetch_region_rectangles(reply); QVector qrects; qrects.reserve(count); for (int i = 0; i < count; i++) qrects << QRect(rects[i].x, rects[i].y, rects[i].width, rects[i].height); region.setRects(qrects.constData(), count); } else region += QRect(reply->extents.x, reply->extents.y, reply->extents.width, reply->extents.height); damage_region += region; repaints_region += region; free(reply); } void Toplevel::addDamageFull() { if (!compositing()) return; damage_region = rect(); repaints_region |= rect(); emit damaged(this, rect()); } void Toplevel::resetDamage() { damage_region = QRegion(); } void Toplevel::addRepaint(const QRect& r) { if (!compositing()) { return; } repaints_region += r; emit needsRepaint(); } void Toplevel::addRepaint(int x, int y, int w, int h) { QRect r(x, y, w, h); addRepaint(r); } void Toplevel::addRepaint(const QRegion& r) { if (!compositing()) { return; } repaints_region += r; emit needsRepaint(); } void Toplevel::addLayerRepaint(const QRect& r) { if (!compositing()) { return; } layer_repaints_region += r; emit needsRepaint(); } 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; layer_repaints_region += r; emit needsRepaint(); } void Toplevel::addRepaintFull() { repaints_region = visibleRect().translated(-pos()); emit needsRepaint(); } void Toplevel::resetRepaints() { 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; Compositor::self()->addRepaint(r2); } -bool Toplevel::updateUnredirectedState() -{ - assert(compositing()); - bool should = options->isUnredirectFullscreen() && shouldUnredirect() && !unredirectSuspend && - !shape() && !hasAlpha() && opacity() == 1.0 && - !static_cast(effects)->activeFullScreenEffect(); - if (should == unredirect) - return false; - static QElapsedTimer lastUnredirect; - static const qint64 msecRedirectInterval = 100; - if (!lastUnredirect.hasExpired(msecRedirectInterval)) { - QTimer::singleShot(msecRedirectInterval, Compositor::self(), SLOT(checkUnredirect())); - return false; - } - lastUnredirect.start(); - unredirect = should; - if (unredirect) { - qCDebug(KWIN_CORE) << "Unredirecting:" << this; - xcb_composite_unredirect_window(connection(), frameId(), XCB_COMPOSITE_REDIRECT_MANUAL); - } else { - qCDebug(KWIN_CORE) << "Redirecting:" << this; - xcb_composite_redirect_window(connection(), frameId(), XCB_COMPOSITE_REDIRECT_MANUAL); - discardWindowPixmap(); - } - return true; -} - -void Toplevel::suspendUnredirect(bool suspend) -{ - if (unredirectSuspend == suspend) - return; - unredirectSuspend = suspend; - Compositor::self()->checkUnredirect(); -} - //**************************************** // Client //**************************************** bool Client::setupCompositing() { if (!Toplevel::setupCompositing()){ return false; } if (isDecorated()) { decoratedClient()->destroyRenderer(); } updateVisibility(); // for internalKeep() return true; } void Client::finishCompositing(ReleaseReason releaseReason) { Toplevel::finishCompositing(releaseReason); updateVisibility(); if (!deleting) { if (isDecorated()) { decoratedClient()->destroyRenderer(); } } // for safety in case KWin is just resizing the window resetHaveResizeEffect(); } -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 - const int desktop = VirtualDesktopManager::self()->current(); - if (geometry() == workspace()->clientArea(FullArea, geometry().center(), desktop) - || geometry() == workspace()->clientArea(ScreenArea, geometry().center(), desktop)) { - 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/composite.h b/composite.h index 50134cb18..a1690e268 100644 --- a/composite.h +++ b/composite.h @@ -1,252 +1,247 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2011 Arthur Arlt Copyright (C) 2012 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 KWIN_COMPOSITE_H #define KWIN_COMPOSITE_H // KWin #include // KDE #include // Qt #include #include #include #include #include namespace KWin { class Client; class Scene; class CompositorSelectionOwner : public KSelectionOwner { Q_OBJECT public: CompositorSelectionOwner(const char *selection); private: friend class Compositor; bool owning; private Q_SLOTS: void looseOwnership(); }; class KWIN_EXPORT Compositor : public QObject { Q_OBJECT public: enum SuspendReason { NoReasonSuspend = 0, UserSuspend = 1<<0, BlockRuleSuspend = 1<<1, ScriptSuspend = 1<<2, AllReasonSuspend = 0xff }; Q_DECLARE_FLAGS(SuspendReasons, SuspendReason) ~Compositor(); // when adding repaints caused by a window, you probably want to use // either Toplevel::addRepaint() or Toplevel::addWorkspaceRepaint() void addRepaint(const QRect& r); void addRepaint(const QRegion& r); void addRepaint(int x, int y, int w, int h); /** * Whether the Compositor is active. That is a Scene is present and the Compositor is * not shutting down itself. **/ bool isActive(); int xrrRefreshRate() const { return m_xrrRefreshRate; } void setCompositeResetTimer(int msecs); bool hasScene() const { return m_scene != NULL; } /** * Checks whether @p w is the Scene's overlay window. **/ bool checkForOverlayWindow(WId w) const; /** * @returns The Scene's Overlay X Window. **/ WId overlayWindow() const; /** * @returns Whether the Scene's Overlay X Window is visible. **/ bool isOverlayWindowVisible() const; /** * Set's the Scene's Overlay X Window visibility to @p visible. **/ void setOverlayWindowVisibility(bool visible); Scene *scene() { return m_scene; } /** * @brief Checks whether the Compositor has already been created by the Workspace. * * This method can be used to check whether self will return the Compositor instance or @c null. * * @return bool @c true if the Compositor has been created, @c false otherwise **/ static bool isCreated() { return s_compositor != NULL; } /** * @brief Static check to test whether the Compositor is available and active. * * @return bool @c true if there is a Compositor and it is active, @c false otherwise **/ static bool compositing() { return s_compositor != NULL && s_compositor->isActive(); } // for delayed supportproperty management of effects void keepSupportProperty(xcb_atom_t atom); void removeSupportProperty(xcb_atom_t atom); public Q_SLOTS: void addRepaintFull(); /** * @brief Suspends the Compositor if it is currently active. * * Note: it is possible that the Compositor is not able to suspend. Use @link isActive to check * whether the Compositor has been suspended. * * @return void * @see resume * @see isActive **/ void suspend(Compositor::SuspendReason reason); /** * @brief Resumes the Compositor if it is currently suspended. * * Note: it is possible that the Compositor cannot be resumed, that is there might be Clients * blocking the usage of Compositing or the Scene might be broken. Use @link isActive to check * whether the Compositor has been resumed. Also check @link isCompositingPossible and * @link isOpenGLBroken. * * Note: The starting of the Compositor can require some time and is partially done threaded. * After this method returns the setup may not have been completed. * * @return void * @see suspend * @see isActive * @see isCompositingPossible * @see isOpenGLBroken **/ void resume(Compositor::SuspendReason reason); /** * Actual slot to perform the toggling compositing. * That is if the Compositor is suspended it will be resumed and if the Compositor is active * it will be suspended. * Invoked primarily by the keybinding. * TODO: make private slot **/ void slotToggleCompositing(); /** * Re-initializes the Compositor completely. * Connected to the D-Bus signal org.kde.KWin /KWin reinitCompositing **/ void slotReinitialize(); /** * Schedules a new repaint if no repaint is currently scheduled. **/ void scheduleRepaint(); - void checkUnredirect(); - void checkUnredirect(bool force); void updateCompositeBlocking(); void updateCompositeBlocking(KWin::Client* c); /** * Notifies the compositor that SwapBuffers() is about to be called. * Rendering of the next frame will be deferred until bufferSwapComplete() * is called. */ void aboutToSwapBuffers(); /** * Notifies the compositor that a pending buffer swap has completed. */ void bufferSwapComplete(); Q_SIGNALS: void compositingToggled(bool active); void aboutToDestroy(); void sceneCreated(); protected: void timerEvent(QTimerEvent *te); private Q_SLOTS: void setup(); /** * Called from setupCompositing() when the CompositingPrefs are ready. **/ void slotCompositingOptionsInitialized(); void finish(); /** * Restarts the Compositor if running. * That is the Compositor will be stopped and started again. **/ void restart(); void fallbackToXRenderCompositing(); void performCompositing(); - void delayedCheckUnredirect(); void slotConfigChanged(); void releaseCompositorSelection(); void deleteUnusedSupportProperties(); private: void claimCompositorSelection(); void setCompositeTimer(); bool windowRepaintsPending() const; /** * Continues the startup after Scene And Workspace are created **/ void startupWithWorkspace(); /** * Whether the Compositor is currently suspended, 8 bits encoding the reason **/ SuspendReasons m_suspended; QBasicTimer compositeTimer; CompositorSelectionOwner* cm_selection; QTimer m_releaseSelectionTimer; QList m_unusedSupportProperties; QTimer m_unusedSupportPropertyTimer; qint64 vBlankInterval, fpsInterval; int m_xrrRefreshRate; QElapsedTimer nextPaintReference; QRegion repaints_region; - QTimer unredirectTimer; - bool forceUnredirectCheck; QTimer compositeResetTimer; // for compressing composite resets bool m_finishing; // finish() sets this variable while shutting down bool m_starting; // start() sets this variable while starting qint64 m_timeSinceLastVBlank; qint64 m_timeSinceStart = 0; Scene *m_scene; bool m_bufferSwapPending; bool m_composeAtSwapCompletion; KWIN_SINGLETON_VARIABLE(Compositor, s_compositor) }; } # endif // KWIN_COMPOSITE_H diff --git a/deleted.h b/deleted.h index 70af56f80..0aa88e726 100644 --- a/deleted.h +++ b/deleted.h @@ -1,140 +1,139 @@ /******************************************************************** 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_DELETED_H #define KWIN_DELETED_H #include "toplevel.h" namespace KWin { class AbstractClient; namespace Decoration { class Renderer; } class KWIN_EXPORT Deleted : public Toplevel { Q_OBJECT Q_PROPERTY(bool minimized READ isMinimized) Q_PROPERTY(bool modal READ isModal) Q_PROPERTY(bool fullScreen READ isFullScreen CONSTANT) Q_PROPERTY(bool isCurrentTab READ isCurrentTab) public: static Deleted* create(Toplevel* c); // used by effects to keep the window around for e.g. fadeout effects when it's destroyed void refWindow(); void unrefWindow(); void discard(); virtual int desktop() const; virtual QStringList activities() const; virtual QPoint clientPos() const; virtual QSize clientSize() const; QPoint clientContentPos() const override { return m_contentPos; } virtual QRect transparentRect() const; virtual bool isDeleted() const; virtual xcb_window_t frameId() const override; bool noBorder() const { return no_border; } void layoutDecorationRects(QRect &left, QRect &top, QRect &right, QRect &bottom) const; QRect decorationRect() const; virtual Layer layer() const { return m_layer; } bool isMinimized() const { return m_minimized; } bool isModal() const { return m_modal; } QList mainClients() const { return m_mainClients; } NET::WindowType windowType(bool direct = false, int supported_types = 0) const; bool wasClient() const { return m_wasClient; } double opacity() const override; QByteArray windowRole() const override; const Decoration::Renderer *decorationRenderer() const { return m_decorationRenderer; } bool isFullScreen() const { return m_fullscreen; } bool isCurrentTab() const { return m_wasCurrentTab; } protected: virtual void debug(QDebug& stream) const; - virtual bool shouldUnredirect() const; private Q_SLOTS: void mainClientClosed(KWin::Toplevel *client); private: Deleted(); // use create() void copyToDeleted(Toplevel* c); virtual ~Deleted(); // deleted only using unrefWindow() int delete_refcount; double window_opacity; int desk; QStringList activityList; QRect contentsRect; // for clientPos()/clientSize() QPoint m_contentPos; QRect transparent_rect; xcb_window_t m_frame; bool no_border; QRect decoration_left; QRect decoration_right; QRect decoration_top; QRect decoration_bottom; Layer m_layer; bool m_minimized; bool m_modal; QList m_mainClients; bool m_wasClient; bool m_wasCurrentTab; Decoration::Renderer *m_decorationRenderer; double m_opacity; NET::WindowType m_type = NET::Unknown; QByteArray m_windowRole; bool m_fullscreen; }; inline void Deleted::refWindow() { ++delete_refcount; } } // namespace Q_DECLARE_METATYPE(KWin::Deleted*) #endif diff --git a/effects.cpp b/effects.cpp index 951b9458b..2fe0efd4e 100644 --- a/effects.cpp +++ b/effects.cpp @@ -1,2072 +1,2071 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak 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 . *********************************************************************/ #include "effects.h" #include "effectsadaptor.h" #include "effectloader.h" #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #endif #include "deleted.h" #include "client.h" #include "cursor.h" #include "group.h" #include "pointer_input.h" #include "scene_xrender.h" #include "scene_qpainter.h" #include "unmanaged.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox.h" #endif #include "screenedge.h" #include "scripting/scriptedeffect.h" #include "screens.h" #include "thumbnailitem.h" #include "virtualdesktops.h" #include "workspace.h" #include "kwinglutils.h" #include #include #include #include #include #include #include #include "composite.h" #include "xcbutils.h" #include "platform.h" #include "shell_client.h" #include "wayland_server.h" #include "decorations/decorationbridge.h" #include // dbus generated #include "screenlocker_interface.h" namespace KWin { static const QString SCREEN_LOCKER_SERVICE_NAME = QStringLiteral("org.freedesktop.ScreenSaver"); ScreenLockerWatcher::ScreenLockerWatcher(QObject *parent) : QObject(parent) , m_interface(NULL) , m_serviceWatcher(new QDBusServiceWatcher(this)) , m_locked(false) { connect(m_serviceWatcher, SIGNAL(serviceOwnerChanged(QString,QString,QString)), SLOT(serviceOwnerChanged(QString,QString,QString))); m_serviceWatcher->setWatchMode(QDBusServiceWatcher::WatchForOwnerChange); m_serviceWatcher->addWatchedService(SCREEN_LOCKER_SERVICE_NAME); // check whether service is registered QFutureWatcher > *watcher = new QFutureWatcher >(this); connect(watcher, SIGNAL(finished()), SLOT(serviceRegisteredQueried())); connect(watcher, SIGNAL(canceled()), watcher, SLOT(deleteLater())); watcher->setFuture(QtConcurrent::run(QDBusConnection::sessionBus().interface(), &QDBusConnectionInterface::isServiceRegistered, SCREEN_LOCKER_SERVICE_NAME)); } ScreenLockerWatcher::~ScreenLockerWatcher() { } void ScreenLockerWatcher::serviceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner) { Q_UNUSED(oldOwner) if (serviceName != SCREEN_LOCKER_SERVICE_NAME) { return; } delete m_interface; m_interface = NULL; m_locked = false; if (!newOwner.isEmpty()) { m_interface = new OrgFreedesktopScreenSaverInterface(newOwner, QStringLiteral("/ScreenSaver"), QDBusConnection::sessionBus(), this); connect(m_interface, SIGNAL(ActiveChanged(bool)), SLOT(setLocked(bool))); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(m_interface->GetActive(), this); connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(activeQueried(QDBusPendingCallWatcher*))); } } void ScreenLockerWatcher::serviceRegisteredQueried() { QFutureWatcher > *watcher = dynamic_cast > *>(sender()); if (!watcher) { return; } const QDBusReply &reply = watcher->result(); if (reply.isValid() && reply.value()) { QFutureWatcher > *ownerWatcher = new QFutureWatcher >(this); connect(ownerWatcher, SIGNAL(finished()), SLOT(serviceOwnerQueried())); connect(ownerWatcher, SIGNAL(canceled()), ownerWatcher, SLOT(deleteLater())); ownerWatcher->setFuture(QtConcurrent::run(QDBusConnection::sessionBus().interface(), &QDBusConnectionInterface::serviceOwner, SCREEN_LOCKER_SERVICE_NAME)); } watcher->deleteLater(); } void ScreenLockerWatcher::serviceOwnerQueried() { QFutureWatcher > *watcher = dynamic_cast > *>(sender()); if (!watcher) { return; } const QDBusReply reply = watcher->result(); if (reply.isValid()) { serviceOwnerChanged(SCREEN_LOCKER_SERVICE_NAME, QString(), reply.value()); } watcher->deleteLater(); } void ScreenLockerWatcher::activeQueried(QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; if (!reply.isError()) { setLocked(reply.value()); } watcher->deleteLater(); } void ScreenLockerWatcher::setLocked(bool activated) { if (m_locked == activated) { return; } m_locked = activated; emit locked(m_locked); } //--------------------- // Static static QByteArray readWindowProperty(xcb_window_t win, xcb_atom_t atom, xcb_atom_t type, int format) { if (win == XCB_WINDOW_NONE) { return QByteArray(); } uint32_t len = 32768; for (;;) { Xcb::Property prop(false, win, atom, XCB_ATOM_ANY, 0, len); if (prop.isNull()) { // get property failed return QByteArray(); } if (prop->bytes_after > 0) { len *= 2; continue; } return prop.toByteArray(format, type); } } static void deleteWindowProperty(Window win, long int atom) { if (win == XCB_WINDOW_NONE) { return; } xcb_delete_property(connection(), win, atom); } //--------------------- EffectsHandlerImpl::EffectsHandlerImpl(Compositor *compositor, Scene *scene) : EffectsHandler(scene->compositingType()) , keyboard_grab_effect(NULL) , fullscreen_effect(0) , next_window_quad_type(EFFECT_QUAD_TYPE_START) , m_compositor(compositor) , m_scene(scene) , m_screenLockerWatcher(new ScreenLockerWatcher(this)) , m_desktopRendering(false) , m_currentRenderedDesktop(0) , m_effectLoader(new EffectLoader(this)) , m_trackingCursorChanges(0) { connect(m_effectLoader, &AbstractEffectLoader::effectLoaded, this, [this](Effect *effect, const QString &name) { effect_order.insert(effect->requestedEffectChainPosition(), EffectPair(name, effect)); loaded_effects << EffectPair(name, effect); effectsChanged(); } ); m_effectLoader->setConfig(kwinApp()->config()); new EffectsAdaptor(this); QDBusConnection dbus = QDBusConnection::sessionBus(); dbus.registerObject(QStringLiteral("/Effects"), this); // init is important, otherwise causes crashes when quads are build before the first painting pass start m_currentBuildQuadsIterator = m_activeEffects.constEnd(); Workspace *ws = Workspace::self(); VirtualDesktopManager *vds = VirtualDesktopManager::self(); connect(ws, &Workspace::showingDesktopChanged, this, &EffectsHandlerImpl::showingDesktopChanged); connect(ws, &Workspace::currentDesktopChanged, this, [this](int old, AbstractClient *c) { const int newDesktop = VirtualDesktopManager::self()->current(); if (old != 0 && newDesktop != old) { emit desktopChanged(old, newDesktop, c ? c->effectWindow() : 0); // TODO: remove in 4.10 emit desktopChanged(old, newDesktop); } } ); connect(ws, &Workspace::desktopPresenceChanged, this, [this](AbstractClient *c, int old) { if (!c->effectWindow()) { return; } // the visibility update hasn't happed yet, thus the signal is delayed to prevent glitches, see also BUG 347490 QMetaObject::invokeMethod(this, "desktopPresenceChanged", Qt::QueuedConnection, Q_ARG(KWin::EffectWindow*, c->effectWindow()), Q_ARG(int, old), Q_ARG(int, c->desktop())); } ); connect(ws, &Workspace::clientAdded, this, [this](Client *c) { if (c->readyForPainting()) slotClientShown(c); else connect(c, &Toplevel::windowShown, this, &EffectsHandlerImpl::slotClientShown); } ); connect(ws, &Workspace::unmanagedAdded, this, [this](Unmanaged *u) { // it's never initially ready but has synthetic 50ms delay connect(u, &Toplevel::windowShown, this, &EffectsHandlerImpl::slotUnmanagedShown); } ); connect(ws, &Workspace::clientActivated, this, [this](KWin::AbstractClient *c) { emit windowActivated(c ? c->effectWindow() : nullptr); } ); connect(ws, &Workspace::deletedRemoved, this, [this](KWin::Deleted *d) { emit windowDeleted(d->effectWindow()); elevated_windows.removeAll(d->effectWindow()); } ); connect(vds, &VirtualDesktopManager::countChanged, this, &EffectsHandler::numberDesktopsChanged); connect(Cursor::self(), &Cursor::mouseChanged, this, &EffectsHandler::mouseChanged); connect(ws, &Workspace::propertyNotify, this, [this](long int atom) { if (!registered_atoms.contains(atom)) return; emit propertyNotify(nullptr, atom); } ); connect(screens(), &Screens::countChanged, this, &EffectsHandler::numberScreensChanged); connect(screens(), &Screens::sizeChanged, this, &EffectsHandler::virtualScreenSizeChanged); connect(screens(), &Screens::geometryChanged, this, &EffectsHandler::virtualScreenGeometryChanged); #ifdef KWIN_BUILD_ACTIVITIES if (Activities *activities = Activities::self()) { connect(activities, &Activities::added, this, &EffectsHandler::activityAdded); connect(activities, &Activities::removed, this, &EffectsHandler::activityRemoved); connect(activities, &Activities::currentChanged, this, &EffectsHandler::currentActivityChanged); } #endif connect(ws, &Workspace::stackingOrderChanged, this, &EffectsHandler::stackingOrderChanged); #ifdef KWIN_BUILD_TABBOX TabBox::TabBox *tabBox = TabBox::TabBox::self(); connect(tabBox, &TabBox::TabBox::tabBoxAdded, this, &EffectsHandler::tabBoxAdded); connect(tabBox, &TabBox::TabBox::tabBoxUpdated, this, &EffectsHandler::tabBoxUpdated); connect(tabBox, &TabBox::TabBox::tabBoxClosed, this, &EffectsHandler::tabBoxClosed); connect(tabBox, &TabBox::TabBox::tabBoxKeyEvent, this, &EffectsHandler::tabBoxKeyEvent); #endif connect(ScreenEdges::self(), &ScreenEdges::approaching, this, &EffectsHandler::screenEdgeApproaching); connect(m_screenLockerWatcher, &ScreenLockerWatcher::locked, this, &EffectsHandler::screenLockingChanged); // connect all clients for (Client *c : ws->clientList()) { setupClientConnections(c); } for (Unmanaged *u : ws->unmanagedList()) { setupUnmanagedConnections(u); } if (auto w = waylandServer()) { connect(w, &WaylandServer::shellClientAdded, this, [this](ShellClient *c) { if (c->readyForPainting()) slotShellClientShown(c); else connect(c, &Toplevel::windowShown, this, &EffectsHandlerImpl::slotShellClientShown); } ); } reconfigure(); } EffectsHandlerImpl::~EffectsHandlerImpl() { unloadAllEffects(); } void EffectsHandlerImpl::unloadAllEffects() { makeOpenGLContextCurrent(); if (keyboard_grab_effect != NULL) ungrabKeyboard(); setActiveFullScreenEffect(nullptr); for (auto it = loaded_effects.begin(); it != loaded_effects.end(); ++it) { Effect *effect = (*it).second; stopMouseInterception(effect); // remove support properties for the effect const QList properties = m_propertiesForEffects.keys(); for (const QByteArray &property : properties) { removeSupportProperty(property, effect); } delete effect; } loaded_effects.clear(); m_effectLoader->clear(); } void EffectsHandlerImpl::setupAbstractClientConnections(AbstractClient* c) { connect(c, &AbstractClient::windowClosed, this, &EffectsHandlerImpl::slotWindowClosed); connect(c, static_cast(&AbstractClient::clientMaximizedStateChanged), this, &EffectsHandlerImpl::slotClientMaximized); connect(c, &AbstractClient::clientStartUserMovedResized, this, [this](AbstractClient *c) { emit windowStartUserMovedResized(c->effectWindow()); } ); connect(c, &AbstractClient::clientStepUserMovedResized, this, [this](AbstractClient *c, const QRect &geometry) { emit windowStepUserMovedResized(c->effectWindow(), geometry); } ); connect(c, &AbstractClient::clientFinishUserMovedResized, this, [this](AbstractClient *c) { emit windowFinishUserMovedResized(c->effectWindow()); } ); connect(c, &AbstractClient::opacityChanged, this, &EffectsHandlerImpl::slotOpacityChanged); connect(c, &AbstractClient::clientMinimized, this, [this](AbstractClient *c, bool animate) { // TODO: notify effects even if it should not animate? if (animate) { emit windowMinimized(c->effectWindow()); } } ); connect(c, &AbstractClient::clientUnminimized, this, [this](AbstractClient* c, bool animate) { // TODO: notify effects even if it should not animate? if (animate) { emit windowUnminimized(c->effectWindow()); } } ); connect(c, &AbstractClient::modalChanged, this, &EffectsHandlerImpl::slotClientModalityChanged); connect(c, &AbstractClient::geometryShapeChanged, this, &EffectsHandlerImpl::slotGeometryShapeChanged); connect(c, &AbstractClient::damaged, this, &EffectsHandlerImpl::slotWindowDamaged); connect(c, &AbstractClient::windowShown, this, [this](Toplevel *c) { emit windowShown(c->effectWindow()); } ); connect(c, &AbstractClient::windowHidden, this, [this](Toplevel *c) { emit windowHidden(c->effectWindow()); } ); } void EffectsHandlerImpl::setupClientConnections(Client* c) { setupAbstractClientConnections(c); connect(c, &Client::paddingChanged, this, &EffectsHandlerImpl::slotPaddingChanged); connect(c, &Client::propertyNotify, this, &EffectsHandlerImpl::slotPropertyNotify); } void EffectsHandlerImpl::setupUnmanagedConnections(Unmanaged* u) { connect(u, &Unmanaged::windowClosed, this, &EffectsHandlerImpl::slotWindowClosed); connect(u, &Unmanaged::opacityChanged, this, &EffectsHandlerImpl::slotOpacityChanged); connect(u, &Unmanaged::geometryShapeChanged, this, &EffectsHandlerImpl::slotGeometryShapeChanged); connect(u, &Unmanaged::paddingChanged, this, &EffectsHandlerImpl::slotPaddingChanged); connect(u, &Unmanaged::damaged, this, &EffectsHandlerImpl::slotWindowDamaged); connect(u, &Unmanaged::propertyNotify, this, &EffectsHandlerImpl::slotPropertyNotify); } void EffectsHandlerImpl::reconfigure() { m_effectLoader->queryAndLoadAll(); } // the idea is that effects call this function again which calls the next one void EffectsHandlerImpl::prePaintScreen(ScreenPrePaintData& data, int time) { if (m_currentPaintScreenIterator != m_activeEffects.constEnd()) { (*m_currentPaintScreenIterator++)->prePaintScreen(data, time); --m_currentPaintScreenIterator; } // no special final code } void EffectsHandlerImpl::paintScreen(int mask, QRegion region, ScreenPaintData& data) { if (m_currentPaintScreenIterator != m_activeEffects.constEnd()) { (*m_currentPaintScreenIterator++)->paintScreen(mask, region, data); --m_currentPaintScreenIterator; } else m_scene->finalPaintScreen(mask, region, data); } void EffectsHandlerImpl::paintDesktop(int desktop, int mask, QRegion region, ScreenPaintData &data) { if (desktop < 1 || desktop > numberOfDesktops()) { return; } m_currentRenderedDesktop = desktop; m_desktopRendering = true; // save the paint screen iterator EffectsIterator savedIterator = m_currentPaintScreenIterator; m_currentPaintScreenIterator = m_activeEffects.constBegin(); effects->paintScreen(mask, region, data); // restore the saved iterator m_currentPaintScreenIterator = savedIterator; m_desktopRendering = false; } void EffectsHandlerImpl::postPaintScreen() { if (m_currentPaintScreenIterator != m_activeEffects.constEnd()) { (*m_currentPaintScreenIterator++)->postPaintScreen(); --m_currentPaintScreenIterator; } // no special final code } void EffectsHandlerImpl::prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time) { if (m_currentPaintWindowIterator != m_activeEffects.constEnd()) { (*m_currentPaintWindowIterator++)->prePaintWindow(w, data, time); --m_currentPaintWindowIterator; } // no special final code } void EffectsHandlerImpl::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { if (m_currentPaintWindowIterator != m_activeEffects.constEnd()) { (*m_currentPaintWindowIterator++)->paintWindow(w, mask, region, data); --m_currentPaintWindowIterator; } else m_scene->finalPaintWindow(static_cast(w), mask, region, data); } void EffectsHandlerImpl::paintEffectFrame(EffectFrame* frame, QRegion region, double opacity, double frameOpacity) { if (m_currentPaintEffectFrameIterator != m_activeEffects.constEnd()) { (*m_currentPaintEffectFrameIterator++)->paintEffectFrame(frame, region, opacity, frameOpacity); --m_currentPaintEffectFrameIterator; } else { const EffectFrameImpl* frameImpl = static_cast(frame); frameImpl->finalRender(region, opacity, frameOpacity); } } void EffectsHandlerImpl::postPaintWindow(EffectWindow* w) { if (m_currentPaintWindowIterator != m_activeEffects.constEnd()) { (*m_currentPaintWindowIterator++)->postPaintWindow(w); --m_currentPaintWindowIterator; } // no special final code } Effect *EffectsHandlerImpl::provides(Effect::Feature ef) { for (int i = 0; i < loaded_effects.size(); ++i) if (loaded_effects.at(i).second->provides(ef)) return loaded_effects.at(i).second; return NULL; } void EffectsHandlerImpl::drawWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { if (m_currentDrawWindowIterator != m_activeEffects.constEnd()) { (*m_currentDrawWindowIterator++)->drawWindow(w, mask, region, data); --m_currentDrawWindowIterator; } else m_scene->finalDrawWindow(static_cast(w), mask, region, data); } void EffectsHandlerImpl::buildQuads(EffectWindow* w, WindowQuadList& quadList) { static bool initIterator = true; if (initIterator) { m_currentBuildQuadsIterator = m_activeEffects.constBegin(); initIterator = false; } if (m_currentBuildQuadsIterator != m_activeEffects.constEnd()) { (*m_currentBuildQuadsIterator++)->buildQuads(w, quadList); --m_currentBuildQuadsIterator; } if (m_currentBuildQuadsIterator == m_activeEffects.constBegin()) initIterator = true; } bool EffectsHandlerImpl::hasDecorationShadows() const { return false; } bool EffectsHandlerImpl::decorationsHaveAlpha() const { return true; } bool EffectsHandlerImpl::decorationSupportsBlurBehind() const { return Decoration::DecorationBridge::self()->needsBlur(); } // start another painting pass void EffectsHandlerImpl::startPaint() { m_activeEffects.clear(); m_activeEffects.reserve(loaded_effects.count()); for(QVector< KWin::EffectPair >::const_iterator it = loaded_effects.constBegin(); it != loaded_effects.constEnd(); ++it) { if (it->second->isActive()) { m_activeEffects << it->second; } } m_currentDrawWindowIterator = m_activeEffects.constBegin(); m_currentPaintWindowIterator = m_activeEffects.constBegin(); m_currentPaintScreenIterator = m_activeEffects.constBegin(); m_currentPaintEffectFrameIterator = m_activeEffects.constBegin(); } void EffectsHandlerImpl::slotClientMaximized(KWin::AbstractClient *c, MaximizeMode maxMode) { bool horizontal = false; bool vertical = false; switch (maxMode) { case MaximizeHorizontal: horizontal = true; break; case MaximizeVertical: vertical = true; break; case MaximizeFull: horizontal = true; vertical = true; break; case MaximizeRestore: // fall through default: // default - nothing to do break; } if (EffectWindowImpl *w = c->effectWindow()) { emit windowMaximizedStateChanged(w, horizontal, vertical); } } void EffectsHandlerImpl::slotOpacityChanged(Toplevel *t, qreal oldOpacity) { if (t->opacity() == oldOpacity || !t->effectWindow()) { return; } emit windowOpacityChanged(t->effectWindow(), oldOpacity, (qreal)t->opacity()); } void EffectsHandlerImpl::slotClientShown(KWin::Toplevel *t) { Q_ASSERT(dynamic_cast(t)); Client *c = static_cast(t); disconnect(c, &Toplevel::windowShown, this, &EffectsHandlerImpl::slotClientShown); setupClientConnections(c); if (!c->tabGroup()) // the "window" has already been there emit windowAdded(c->effectWindow()); } void EffectsHandlerImpl::slotShellClientShown(Toplevel *t) { ShellClient *c = static_cast(t); setupAbstractClientConnections(c); emit windowAdded(t->effectWindow()); } void EffectsHandlerImpl::slotUnmanagedShown(KWin::Toplevel *t) { // regardless, unmanaged windows are -yet?- not synced anyway Q_ASSERT(dynamic_cast(t)); Unmanaged *u = static_cast(t); setupUnmanagedConnections(u); emit windowAdded(u->effectWindow()); } void EffectsHandlerImpl::slotWindowClosed(KWin::Toplevel *c, KWin::Deleted *d) { c->disconnect(this); if (d) { emit windowClosed(c->effectWindow()); } } void EffectsHandlerImpl::slotClientModalityChanged() { emit windowModalityChanged(static_cast(sender())->effectWindow()); } void EffectsHandlerImpl::slotCurrentTabAboutToChange(EffectWindow *from, EffectWindow *to) { emit currentTabAboutToChange(from, to); } void EffectsHandlerImpl::slotTabAdded(EffectWindow* w, EffectWindow* to) { emit tabAdded(w, to); } void EffectsHandlerImpl::slotTabRemoved(EffectWindow *w, EffectWindow* leaderOfFormerGroup) { emit tabRemoved(w, leaderOfFormerGroup); } void EffectsHandlerImpl::slotWindowDamaged(Toplevel* t, const QRect& r) { if (!t->effectWindow()) { // can happen during tear down of window return; } emit windowDamaged(t->effectWindow(), r); } void EffectsHandlerImpl::slotGeometryShapeChanged(Toplevel* t, const QRect& old) { // during late cleanup effectWindow() may be already NULL // in some functions that may still call this if (t == NULL || t->effectWindow() == NULL) return; emit windowGeometryShapeChanged(t->effectWindow(), old); } void EffectsHandlerImpl::slotPaddingChanged(Toplevel* t, const QRect& old) { // during late cleanup effectWindow() may be already NULL // in some functions that may still call this if (t == NULL || t->effectWindow() == NULL) return; emit windowPaddingChanged(t->effectWindow(), old); } void EffectsHandlerImpl::setActiveFullScreenEffect(Effect* e) { fullscreen_effect = e; - m_compositor->checkUnredirect(); } Effect* EffectsHandlerImpl::activeFullScreenEffect() const { return fullscreen_effect; } bool EffectsHandlerImpl::grabKeyboard(Effect* effect) { if (keyboard_grab_effect != NULL) return false; if (kwinApp()->operationMode() == Application::OperationModeX11) { bool ret = grabXKeyboard(); if (!ret) return false; } keyboard_grab_effect = effect; return true; } void EffectsHandlerImpl::ungrabKeyboard() { assert(keyboard_grab_effect != NULL); if (kwinApp()->operationMode() == Application::OperationModeX11) { ungrabXKeyboard(); } keyboard_grab_effect = NULL; } void EffectsHandlerImpl::grabbedKeyboardEvent(QKeyEvent* e) { if (keyboard_grab_effect != NULL) keyboard_grab_effect->grabbedKeyboardEvent(e); } void EffectsHandlerImpl::startMouseInterception(Effect *effect, Qt::CursorShape shape) { if (m_grabbedMouseEffects.contains(effect)) { return; } m_grabbedMouseEffects.append(effect); if (m_grabbedMouseEffects.size() != 1) { return; } if (kwinApp()->operationMode() != Application::OperationModeX11) { input()->pointer()->setEffectsOverrideCursor(shape); return; } // NOTE: it is intended to not perform an XPointerGrab on X11. See documentation in kwineffects.h // The mouse grab is implemented by using a full screen input only window if (!m_mouseInterceptionWindow.isValid()) { const QSize &s = screens()->size(); const QRect geo(0, 0, s.width(), s.height()); const uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; const uint32_t values[] = { true, XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION }; m_mouseInterceptionWindow.reset(Xcb::createInputWindow(geo, mask, values)); defineCursor(shape); } else { defineCursor(shape); } m_mouseInterceptionWindow.map(); m_mouseInterceptionWindow.raise(); // Raise electric border windows above the input windows // so they can still be triggered. ScreenEdges::self()->ensureOnTop(); } void EffectsHandlerImpl::stopMouseInterception(Effect *effect) { if (!m_grabbedMouseEffects.contains(effect)) { return; } m_grabbedMouseEffects.removeAll(effect); if (kwinApp()->operationMode() != Application::OperationModeX11) { input()->pointer()->removeEffectsOverrideCursor(); return; } if (m_grabbedMouseEffects.isEmpty()) { m_mouseInterceptionWindow.unmap(); Workspace::self()->stackScreenEdgesUnderOverrideRedirect(); } } bool EffectsHandlerImpl::isMouseInterception() const { return m_grabbedMouseEffects.count() > 0; } void EffectsHandlerImpl::registerGlobalShortcut(const QKeySequence &shortcut, QAction *action) { input()->registerShortcut(shortcut, action); } void EffectsHandlerImpl::registerPointerShortcut(Qt::KeyboardModifiers modifiers, Qt::MouseButton pointerButtons, QAction *action) { input()->registerPointerShortcut(modifiers, pointerButtons, action); } void EffectsHandlerImpl::registerAxisShortcut(Qt::KeyboardModifiers modifiers, PointerAxisDirection axis, QAction *action) { input()->registerAxisShortcut(modifiers, axis, action); } void* EffectsHandlerImpl::getProxy(QString name) { for (QVector< EffectPair >::const_iterator it = loaded_effects.constBegin(); it != loaded_effects.constEnd(); ++it) if ((*it).first == name) return (*it).second->proxy(); return NULL; } void EffectsHandlerImpl::startMousePolling() { if (Cursor::self()) Cursor::self()->startMousePolling(); } void EffectsHandlerImpl::stopMousePolling() { if (Cursor::self()) Cursor::self()->stopMousePolling(); } bool EffectsHandlerImpl::hasKeyboardGrab() const { return keyboard_grab_effect != NULL; } void EffectsHandlerImpl::desktopResized(const QSize &size) { m_scene->screenGeometryChanged(size); if (m_mouseInterceptionWindow.isValid()) { m_mouseInterceptionWindow.setGeometry(QRect(0, 0, size.width(), size.height())); } emit screenGeometryChanged(size); } void EffectsHandlerImpl::slotPropertyNotify(Toplevel* t, long int atom) { if (!registered_atoms.contains(atom)) return; emit propertyNotify(t->effectWindow(), atom); } void EffectsHandlerImpl::registerPropertyType(long atom, bool reg) { if (reg) ++registered_atoms[ atom ]; // initialized to 0 if not present yet else { if (--registered_atoms[ atom ] == 0) registered_atoms.remove(atom); } } xcb_atom_t EffectsHandlerImpl::announceSupportProperty(const QByteArray &propertyName, Effect *effect) { PropertyEffectMap::iterator it = m_propertiesForEffects.find(propertyName); if (it != m_propertiesForEffects.end()) { // property has already been registered for an effect // just append Effect and return the atom stored in m_managedProperties if (!it.value().contains(effect)) { it.value().append(effect); } return m_managedProperties.value(propertyName); } // get the atom for the propertyName ScopedCPointer atomReply(xcb_intern_atom_reply(connection(), xcb_intern_atom_unchecked(connection(), false, propertyName.size(), propertyName.constData()), NULL)); if (atomReply.isNull()) { return XCB_ATOM_NONE; } m_compositor->keepSupportProperty(atomReply->atom); // announce property on root window unsigned char dummy = 0; xcb_change_property(connection(), XCB_PROP_MODE_REPLACE, rootWindow(), atomReply->atom, atomReply->atom, 8, 1, &dummy); // TODO: add to _NET_SUPPORTED m_managedProperties.insert(propertyName, atomReply->atom); m_propertiesForEffects.insert(propertyName, QList() << effect); registerPropertyType(atomReply->atom, true); return atomReply->atom; } void EffectsHandlerImpl::removeSupportProperty(const QByteArray &propertyName, Effect *effect) { PropertyEffectMap::iterator it = m_propertiesForEffects.find(propertyName); if (it == m_propertiesForEffects.end()) { // property is not registered - nothing to do return; } if (!it.value().contains(effect)) { // property is not registered for given effect - nothing to do return; } it.value().removeAll(effect); if (!it.value().isEmpty()) { // property still registered for another effect - nothing further to do return; } const xcb_atom_t atom = m_managedProperties.take(propertyName); registerPropertyType(atom, false); m_propertiesForEffects.remove(propertyName); m_compositor->removeSupportProperty(atom); // delayed removal } QByteArray EffectsHandlerImpl::readRootProperty(long atom, long type, int format) const { return readWindowProperty(rootWindow(), atom, type, format); } void EffectsHandlerImpl::deleteRootProperty(long atom) const { deleteWindowProperty(rootWindow(), atom); } void EffectsHandlerImpl::activateWindow(EffectWindow* c) { if (AbstractClient* cl = dynamic_cast< AbstractClient* >(static_cast(c)->window())) Workspace::self()->activateClient(cl, true); } EffectWindow* EffectsHandlerImpl::activeWindow() const { return Workspace::self()->activeClient() ? Workspace::self()->activeClient()->effectWindow() : NULL; } void EffectsHandlerImpl::moveWindow(EffectWindow* w, const QPoint& pos, bool snap, double snapAdjust) { AbstractClient* cl = dynamic_cast< AbstractClient* >(static_cast(w)->window()); if (!cl || !cl->isMovable()) return; if (snap) cl->move(Workspace::self()->adjustClientPosition(cl, pos, true, snapAdjust)); else cl->move(pos); } void EffectsHandlerImpl::windowToDesktop(EffectWindow* w, int desktop) { AbstractClient* cl = dynamic_cast< AbstractClient* >(static_cast(w)->window()); if (cl && !cl->isDesktop() && !cl->isDock()) Workspace::self()->sendClientToDesktop(cl, desktop, true); } void EffectsHandlerImpl::windowToScreen(EffectWindow* w, int screen) { AbstractClient* cl = dynamic_cast< AbstractClient* >(static_cast(w)->window()); if (cl && !cl->isDesktop() && !cl->isDock()) Workspace::self()->sendClientToScreen(cl, screen); } void EffectsHandlerImpl::setShowingDesktop(bool showing) { Workspace::self()->setShowingDesktop(showing); } QString EffectsHandlerImpl::currentActivity() const { #ifdef KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return QString(); } return Activities::self()->current(); #else return QString(); #endif } int EffectsHandlerImpl::currentDesktop() const { return VirtualDesktopManager::self()->current(); } int EffectsHandlerImpl::numberOfDesktops() const { return VirtualDesktopManager::self()->count(); } void EffectsHandlerImpl::setCurrentDesktop(int desktop) { VirtualDesktopManager::self()->setCurrent(desktop); } void EffectsHandlerImpl::setNumberOfDesktops(int desktops) { VirtualDesktopManager::self()->setCount(desktops); } QSize EffectsHandlerImpl::desktopGridSize() const { return VirtualDesktopManager::self()->grid().size(); } int EffectsHandlerImpl::desktopGridWidth() const { return desktopGridSize().width(); } int EffectsHandlerImpl::desktopGridHeight() const { return desktopGridSize().height(); } int EffectsHandlerImpl::workspaceWidth() const { return desktopGridWidth() * displayWidth(); } int EffectsHandlerImpl::workspaceHeight() const { return desktopGridHeight() * displayHeight(); } int EffectsHandlerImpl::desktopAtCoords(QPoint coords) const { return VirtualDesktopManager::self()->grid().at(coords); } QPoint EffectsHandlerImpl::desktopGridCoords(int id) const { return VirtualDesktopManager::self()->grid().gridCoords(id); } QPoint EffectsHandlerImpl::desktopCoords(int id) const { QPoint coords = VirtualDesktopManager::self()->grid().gridCoords(id); if (coords.x() == -1) return QPoint(-1, -1); return QPoint(coords.x() * displayWidth(), coords.y() * displayHeight()); } int EffectsHandlerImpl::desktopAbove(int desktop, bool wrap) const { return getDesktop(desktop, wrap); } int EffectsHandlerImpl::desktopToRight(int desktop, bool wrap) const { return getDesktop(desktop, wrap); } int EffectsHandlerImpl::desktopBelow(int desktop, bool wrap) const { return getDesktop(desktop, wrap); } int EffectsHandlerImpl::desktopToLeft(int desktop, bool wrap) const { return getDesktop(desktop, wrap); } QString EffectsHandlerImpl::desktopName(int desktop) const { return VirtualDesktopManager::self()->name(desktop); } bool EffectsHandlerImpl::optionRollOverDesktops() const { return options->isRollOverDesktops(); } double EffectsHandlerImpl::animationTimeFactor() const { return options->animationTimeFactor(); } WindowQuadType EffectsHandlerImpl::newWindowQuadType() { return WindowQuadType(next_window_quad_type++); } EffectWindow* EffectsHandlerImpl::findWindow(WId id) const { if (Client* w = Workspace::self()->findClient(Predicate::WindowMatch, id)) return w->effectWindow(); if (Unmanaged* w = Workspace::self()->findUnmanaged(id)) return w->effectWindow(); if (waylandServer()) { if (ShellClient *w = waylandServer()->findClient(id)) { return w->effectWindow(); } } return NULL; } EffectWindow* EffectsHandlerImpl::findWindow(KWayland::Server::SurfaceInterface *surf) const { if (waylandServer()) { if (ShellClient *w = waylandServer()->findClient(surf)) { return w->effectWindow(); } } return nullptr; } EffectWindowList EffectsHandlerImpl::stackingOrder() const { ToplevelList list = Workspace::self()->xStackingOrder(); EffectWindowList ret; for (Toplevel *t : list) { if (EffectWindow *w = effectWindow(t)) ret.append(w); } return ret; } void EffectsHandlerImpl::setElevatedWindow(KWin::EffectWindow* w, bool set) { elevated_windows.removeAll(w); if (set) elevated_windows.append(w); } void EffectsHandlerImpl::setTabBoxWindow(EffectWindow* w) { #ifdef KWIN_BUILD_TABBOX if (AbstractClient* c = dynamic_cast< AbstractClient* >(static_cast< EffectWindowImpl* >(w)->window())) { TabBox::TabBox::self()->setCurrentClient(c); } #else Q_UNUSED(w) #endif } void EffectsHandlerImpl::setTabBoxDesktop(int desktop) { #ifdef KWIN_BUILD_TABBOX TabBox::TabBox::self()->setCurrentDesktop(desktop); #else Q_UNUSED(desktop) #endif } EffectWindowList EffectsHandlerImpl::currentTabBoxWindowList() const { #ifdef KWIN_BUILD_TABBOX EffectWindowList ret; const auto clients = TabBox::TabBox::self()->currentClientList(); for (auto c : clients) ret.append(c->effectWindow()); return ret; #else return EffectWindowList(); #endif } void EffectsHandlerImpl::refTabBox() { #ifdef KWIN_BUILD_TABBOX TabBox::TabBox::self()->reference(); #endif } void EffectsHandlerImpl::unrefTabBox() { #ifdef KWIN_BUILD_TABBOX TabBox::TabBox::self()->unreference(); #endif } void EffectsHandlerImpl::closeTabBox() { #ifdef KWIN_BUILD_TABBOX TabBox::TabBox::self()->close(); #endif } QList< int > EffectsHandlerImpl::currentTabBoxDesktopList() const { #ifdef KWIN_BUILD_TABBOX return TabBox::TabBox::self()->currentDesktopList(); #endif return QList< int >(); } int EffectsHandlerImpl::currentTabBoxDesktop() const { #ifdef KWIN_BUILD_TABBOX return TabBox::TabBox::self()->currentDesktop(); #endif return -1; } EffectWindow* EffectsHandlerImpl::currentTabBoxWindow() const { #ifdef KWIN_BUILD_TABBOX if (auto c = TabBox::TabBox::self()->currentClient()) return c->effectWindow(); #endif return NULL; } void EffectsHandlerImpl::addRepaintFull() { m_compositor->addRepaintFull(); } void EffectsHandlerImpl::addRepaint(const QRect& r) { m_compositor->addRepaint(r); } void EffectsHandlerImpl::addRepaint(const QRegion& r) { m_compositor->addRepaint(r); } void EffectsHandlerImpl::addRepaint(int x, int y, int w, int h) { m_compositor->addRepaint(x, y, w, h); } int EffectsHandlerImpl::activeScreen() const { return screens()->current(); } int EffectsHandlerImpl::numScreens() const { return screens()->count(); } int EffectsHandlerImpl::screenNumber(const QPoint& pos) const { return screens()->number(pos); } QRect EffectsHandlerImpl::clientArea(clientAreaOption opt, int screen, int desktop) const { return Workspace::self()->clientArea(opt, screen, desktop); } QRect EffectsHandlerImpl::clientArea(clientAreaOption opt, const EffectWindow* c) const { const Toplevel* t = static_cast< const EffectWindowImpl* >(c)->window(); if (const AbstractClient* cl = dynamic_cast< const AbstractClient* >(t)) return Workspace::self()->clientArea(opt, cl); else return Workspace::self()->clientArea(opt, t->geometry().center(), VirtualDesktopManager::self()->current()); } QRect EffectsHandlerImpl::clientArea(clientAreaOption opt, const QPoint& p, int desktop) const { return Workspace::self()->clientArea(opt, p, desktop); } QRect EffectsHandlerImpl::virtualScreenGeometry() const { return screens()->geometry(); } QSize EffectsHandlerImpl::virtualScreenSize() const { return screens()->size(); } void EffectsHandlerImpl::defineCursor(Qt::CursorShape shape) { if (!m_mouseInterceptionWindow.isValid()) { input()->pointer()->setEffectsOverrideCursor(shape); return; } const xcb_cursor_t c = Cursor::x11Cursor(shape); if (c != XCB_CURSOR_NONE) { m_mouseInterceptionWindow.defineCursor(c); } } bool EffectsHandlerImpl::checkInputWindowEvent(xcb_button_press_event_t *e) { if (m_grabbedMouseEffects.isEmpty() || m_mouseInterceptionWindow != e->event) { return false; } for (Effect *effect : m_grabbedMouseEffects) { Qt::MouseButton button = x11ToQtMouseButton(e->detail); Qt::MouseButtons buttons = x11ToQtMouseButtons(e->state); const QEvent::Type type = ((e->response_type & ~0x80) == XCB_BUTTON_PRESS) ? QEvent::MouseButtonPress : QEvent::MouseButtonRelease; if (type == QEvent::MouseButtonPress) { buttons |= button; } else { buttons &= ~button; } QMouseEvent ev(type, QPoint(e->event_x, e->event_y), QPoint(e->root_x, e->root_y), button, buttons, x11ToQtKeyboardModifiers(e->state)); effect->windowInputMouseEvent(&ev); } return true; // eat event } bool EffectsHandlerImpl::checkInputWindowEvent(xcb_motion_notify_event_t *e) { if (m_grabbedMouseEffects.isEmpty() || m_mouseInterceptionWindow != e->event) { return false; } for (Effect *effect : m_grabbedMouseEffects) { QMouseEvent ev(QEvent::MouseMove, QPoint(e->event_x, e->event_y), QPoint(e->root_x, e->root_y), Qt::NoButton, x11ToQtMouseButtons(e->state), x11ToQtKeyboardModifiers(e->state)); effect->windowInputMouseEvent(&ev); } return true; // eat event } bool EffectsHandlerImpl::checkInputWindowEvent(QMouseEvent *e) { if (m_grabbedMouseEffects.isEmpty()) { return false; } foreach (Effect *effect, m_grabbedMouseEffects) { effect->windowInputMouseEvent(e); } return true; } bool EffectsHandlerImpl::checkInputWindowEvent(QWheelEvent *e) { if (m_grabbedMouseEffects.isEmpty()) { return false; } foreach (Effect *effect, m_grabbedMouseEffects) { effect->windowInputMouseEvent(e); } return true; } void EffectsHandlerImpl::connectNotify(const QMetaMethod &signal) { if (signal == QMetaMethod::fromSignal(&EffectsHandler::cursorShapeChanged)) { if (!m_trackingCursorChanges) { connect(Cursor::self(), &Cursor::cursorChanged, this, &EffectsHandler::cursorShapeChanged); Cursor::self()->startCursorTracking(); } ++m_trackingCursorChanges; } EffectsHandler::connectNotify(signal); } void EffectsHandlerImpl::disconnectNotify(const QMetaMethod &signal) { if (signal == QMetaMethod::fromSignal(&EffectsHandler::cursorShapeChanged)) { Q_ASSERT(m_trackingCursorChanges > 0); if (!--m_trackingCursorChanges) { Cursor::self()->stopCursorTracking(); disconnect(Cursor::self(), &Cursor::cursorChanged, this, &EffectsHandler::cursorShapeChanged); } } EffectsHandler::disconnectNotify(signal); } void EffectsHandlerImpl::checkInputWindowStacking() { if (m_grabbedMouseEffects.isEmpty()) { return; } if (kwinApp()->operationMode() != Application::OperationModeX11) { return; } m_mouseInterceptionWindow.raise(); // Raise electric border windows above the input windows // so they can still be triggered. TODO: Do both at once. ScreenEdges::self()->ensureOnTop(); } QPoint EffectsHandlerImpl::cursorPos() const { return Cursor::pos(); } void EffectsHandlerImpl::reserveElectricBorder(ElectricBorder border, Effect *effect) { ScreenEdges::self()->reserve(border, effect, "borderActivated"); } void EffectsHandlerImpl::unreserveElectricBorder(ElectricBorder border, Effect *effect) { ScreenEdges::self()->unreserve(border, effect); } unsigned long EffectsHandlerImpl::xrenderBufferPicture() { #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (SceneXrender* s = dynamic_cast< SceneXrender* >(m_scene)) return s->bufferPicture(); #endif return None; } QPainter *EffectsHandlerImpl::scenePainter() { if (SceneQPainter *s = dynamic_cast(m_scene)) { return s->painter(); } else { return NULL; } } void EffectsHandlerImpl::toggleEffect(const QString& name) { if (isEffectLoaded(name)) unloadEffect(name); else loadEffect(name); } QStringList EffectsHandlerImpl::loadedEffects() const { QStringList listModules; for (QVector< EffectPair >::const_iterator it = loaded_effects.constBegin(); it != loaded_effects.constEnd(); ++it) { listModules << (*it).first; } return listModules; } QStringList EffectsHandlerImpl::listOfEffects() const { return m_effectLoader->listOfKnownEffects(); } bool EffectsHandlerImpl::loadEffect(const QString& name) { makeOpenGLContextCurrent(); m_compositor->addRepaintFull(); return m_effectLoader->loadEffect(name); } void EffectsHandlerImpl::unloadEffect(const QString& name) { makeOpenGLContextCurrent(); m_compositor->addRepaintFull(); for (QMap< int, EffectPair >::iterator it = effect_order.begin(); it != effect_order.end(); ++it) { if (it.value().first == name) { qCDebug(KWIN_CORE) << "EffectsHandler::unloadEffect : Unloading Effect : " << name; if (activeFullScreenEffect() == it.value().second) { setActiveFullScreenEffect(0); } stopMouseInterception(it.value().second); // remove support properties for the effect const QList properties = m_propertiesForEffects.keys(); for (const QByteArray &property : properties) { removeSupportProperty(property, it.value().second); } delete it.value().second; effect_order.erase(it); effectsChanged(); return; } } qCDebug(KWIN_CORE) << "EffectsHandler::unloadEffect : Effect not loaded : " << name; } void EffectsHandlerImpl::reconfigureEffect(const QString& name) { for (QVector< EffectPair >::const_iterator it = loaded_effects.constBegin(); it != loaded_effects.constEnd(); ++it) if ((*it).first == name) { kwinApp()->config()->reparseConfiguration(); makeOpenGLContextCurrent(); (*it).second->reconfigure(Effect::ReconfigureAll); return; } } bool EffectsHandlerImpl::isEffectLoaded(const QString& name) const { for (QVector< EffectPair >::const_iterator it = loaded_effects.constBegin(); it != loaded_effects.constEnd(); ++it) if ((*it).first == name) return true; return false; } bool EffectsHandlerImpl::isEffectSupported(const QString &name) { // if the effect is loaded, it is obviously supported auto it = std::find_if(loaded_effects.constBegin(), loaded_effects.constEnd(), [name](const EffectPair &pair) { return pair.first == name; }); if (it != loaded_effects.constEnd()) { return true; } // next checks might require a context makeOpenGLContextCurrent(); m_compositor->addRepaintFull(); return m_effectLoader->isEffectSupported(name); } QList< bool > EffectsHandlerImpl::areEffectsSupported(const QStringList &names) { QList< bool > retList; for (const QString &name : names) { retList << isEffectSupported(name); } return retList; } void EffectsHandlerImpl::reloadEffect(Effect *effect) { QString effectName; for (QVector< EffectPair >::const_iterator it = loaded_effects.constBegin(); it != loaded_effects.constEnd(); ++it) { if ((*it).second == effect) { effectName = (*it).first; break; } } if (!effectName.isNull()) { unloadEffect(effectName); m_effectLoader->loadEffect(effectName); } } void EffectsHandlerImpl::effectsChanged() { loaded_effects.clear(); m_activeEffects.clear(); // it's possible to have a reconfigure and a quad rebuild between two paint cycles - bug #308201 // qDebug() << "Recreating effects' list:"; for (const EffectPair & effect : effect_order) { // qDebug() << effect.first; loaded_effects.append(effect); } m_activeEffects.reserve(loaded_effects.count()); } QStringList EffectsHandlerImpl::activeEffects() const { QStringList ret; for(QVector< KWin::EffectPair >::const_iterator it = loaded_effects.constBegin(), end = loaded_effects.constEnd(); it != end; ++it) { if (it->second->isActive()) { ret << it->first; } } return ret; } KWayland::Server::Display *EffectsHandlerImpl::waylandDisplay() const { if (waylandServer()) { return waylandServer()->display(); } return nullptr; } EffectFrame* EffectsHandlerImpl::effectFrame(EffectFrameStyle style, bool staticSize, const QPoint& position, Qt::Alignment alignment) const { return new EffectFrameImpl(style, staticSize, position, alignment); } QVariant EffectsHandlerImpl::kwinOption(KWinOption kwopt) { switch (kwopt) { case CloseButtonCorner: // TODO: this could become per window and be derived from the actual position in the deco return Decoration::DecorationBridge::self()->settings()->decorationButtonsLeft().contains(KDecoration2::DecorationButtonType::Close) ? Qt::TopLeftCorner : Qt::TopRightCorner; case SwitchDesktopOnScreenEdge: return ScreenEdges::self()->isDesktopSwitching(); case SwitchDesktopOnScreenEdgeMovingWindows: return ScreenEdges::self()->isDesktopSwitchingMovingClients(); default: return QVariant(); // an invalid one } } QString EffectsHandlerImpl::supportInformation(const QString &name) const { if (!isEffectLoaded(name)) { return QString(); } for (QVector< EffectPair >::const_iterator it = loaded_effects.constBegin(); it != loaded_effects.constEnd(); ++it) { if ((*it).first == name) { QString support((*it).first + QLatin1String(":\n")); const QMetaObject *metaOptions = (*it).second->metaObject(); for (int i=0; ipropertyCount(); ++i) { const QMetaProperty property = metaOptions->property(i); if (qstrcmp(property.name(), "objectName") == 0) { continue; } support += QString::fromUtf8(property.name()) + QLatin1String(": ") + (*it).second->property(property.name()).toString() + QLatin1Char('\n'); } return support; } } return QString(); } bool EffectsHandlerImpl::isScreenLocked() const { return m_screenLockerWatcher->isLocked(); } QString EffectsHandlerImpl::debug(const QString& name, const QString& parameter) const { QString internalName = name.toLower();; for (QVector< EffectPair >::const_iterator it = loaded_effects.constBegin(); it != loaded_effects.constEnd(); ++it) { if ((*it).first == internalName) { return it->second->debug(parameter); } } return QString(); } bool EffectsHandlerImpl::makeOpenGLContextCurrent() { return m_scene->makeOpenGLContextCurrent(); } void EffectsHandlerImpl::doneOpenGLContextCurrent() { m_scene->doneOpenGLContextCurrent(); } //**************************************** // EffectWindowImpl //**************************************** EffectWindowImpl::EffectWindowImpl(Toplevel *toplevel) : EffectWindow(toplevel) , toplevel(toplevel) , sw(NULL) { } EffectWindowImpl::~EffectWindowImpl() { QVariant cachedTextureVariant = data(LanczosCacheRole); if (cachedTextureVariant.isValid()) { GLTexture *cachedTexture = static_cast< GLTexture*>(cachedTextureVariant.value()); delete cachedTexture; } } bool EffectWindowImpl::isPaintingEnabled() { return sceneWindow()->isPaintingEnabled(); } void EffectWindowImpl::enablePainting(int reason) { sceneWindow()->enablePainting(reason); } void EffectWindowImpl::disablePainting(int reason) { sceneWindow()->disablePainting(reason); } const EffectWindowGroup* EffectWindowImpl::group() const { if (Client* c = dynamic_cast< Client* >(toplevel)) return c->group()->effectGroup(); return NULL; // TODO } void EffectWindowImpl::refWindow() { if (Deleted* d = dynamic_cast< Deleted* >(toplevel)) return d->refWindow(); abort(); // TODO } void EffectWindowImpl::unrefWindow() { if (Deleted* d = dynamic_cast< Deleted* >(toplevel)) return d->unrefWindow(); // delays deletion in case abort(); // TODO } void EffectWindowImpl::setWindow(Toplevel* w) { toplevel = w; setParent(w); } void EffectWindowImpl::setSceneWindow(Scene::Window* w) { sw = w; } QRegion EffectWindowImpl::shape() const { return sw ? sw->shape() : geometry(); } QRect EffectWindowImpl::decorationInnerRect() const { Client *client = dynamic_cast(toplevel); return client ? client->transparentRect() : contentsRect(); } QByteArray EffectWindowImpl::readProperty(long atom, long type, int format) const { return readWindowProperty(window()->window(), atom, type, format); } void EffectWindowImpl::deleteProperty(long int atom) const { deleteWindowProperty(window()->window(), atom); } EffectWindow* EffectWindowImpl::findModal() { if (AbstractClient* c = dynamic_cast< AbstractClient* >(toplevel)) { if (AbstractClient* c2 = c->findModal()) return c2->effectWindow(); } return NULL; } template EffectWindowList getMainWindows(Toplevel *toplevel) { T *c = static_cast(toplevel); EffectWindowList ret; const auto mainclients = c->mainClients(); for (auto tmp : mainclients) ret.append(tmp->effectWindow()); return ret; } EffectWindowList EffectWindowImpl::mainWindows() const { if (dynamic_cast(toplevel)) { return getMainWindows(toplevel); } else if (toplevel->isDeleted()) { return getMainWindows(toplevel); } return EffectWindowList(); } WindowQuadList EffectWindowImpl::buildQuads(bool force) const { return sceneWindow()->buildQuads(force); } void EffectWindowImpl::setData(int role, const QVariant &data) { if (!data.isNull()) dataMap[ role ] = data; else dataMap.remove(role); } QVariant EffectWindowImpl::data(int role) const { if (!dataMap.contains(role)) return QVariant(); return dataMap[ role ]; } EffectWindow* effectWindow(Toplevel* w) { EffectWindowImpl* ret = w->effectWindow(); return ret; } EffectWindow* effectWindow(Scene::Window* w) { EffectWindowImpl* ret = w->window()->effectWindow(); ret->setSceneWindow(w); return ret; } void EffectWindowImpl::elevate(bool elevate) { effects->setElevatedWindow(this, elevate); } void EffectWindowImpl::registerThumbnail(AbstractThumbnailItem *item) { if (WindowThumbnailItem *thumb = qobject_cast(item)) { insertThumbnail(thumb); connect(thumb, SIGNAL(destroyed(QObject*)), SLOT(thumbnailDestroyed(QObject*))); connect(thumb, SIGNAL(wIdChanged(qulonglong)), SLOT(thumbnailTargetChanged())); } else if (DesktopThumbnailItem *desktopThumb = qobject_cast(item)) { m_desktopThumbnails.append(desktopThumb); connect(desktopThumb, SIGNAL(destroyed(QObject*)), SLOT(desktopThumbnailDestroyed(QObject*))); } } void EffectWindowImpl::thumbnailDestroyed(QObject *object) { // we know it is a ThumbnailItem m_thumbnails.remove(static_cast(object)); } void EffectWindowImpl::thumbnailTargetChanged() { if (WindowThumbnailItem *item = qobject_cast(sender())) { insertThumbnail(item); } } void EffectWindowImpl::insertThumbnail(WindowThumbnailItem *item) { EffectWindow *w = effects->findWindow(item->wId()); if (w) { m_thumbnails.insert(item, QWeakPointer(static_cast(w))); } else { m_thumbnails.insert(item, QWeakPointer()); } } void EffectWindowImpl::desktopThumbnailDestroyed(QObject *object) { // we know it is a DesktopThumbnailItem m_desktopThumbnails.removeAll(static_cast(object)); } void EffectWindowImpl::referencePreviousWindowPixmap() { if (sw) { sw->referencePreviousPixmap(); } } void EffectWindowImpl::unreferencePreviousWindowPixmap() { if (sw) { sw->unreferencePreviousPixmap(); } } //**************************************** // EffectWindowGroupImpl //**************************************** EffectWindowList EffectWindowGroupImpl::members() const { EffectWindowList ret; for (Toplevel * c : group->members()) ret.append(c->effectWindow()); return ret; } //**************************************** // EffectFrameImpl //**************************************** EffectFrameImpl::EffectFrameImpl(EffectFrameStyle style, bool staticSize, QPoint position, Qt::Alignment alignment) : QObject(0) , EffectFrame() , m_style(style) , m_static(staticSize) , m_point(position) , m_alignment(alignment) , m_shader(NULL) , m_theme(new Plasma::Theme(this)) { if (m_style == EffectFrameStyled) { m_frame.setImagePath(QStringLiteral("widgets/background")); m_frame.setCacheAllRenderedFrames(true); connect(m_theme, SIGNAL(themeChanged()), this, SLOT(plasmaThemeChanged())); } m_selection.setImagePath(QStringLiteral("widgets/viewitem")); m_selection.setElementPrefix(QStringLiteral("hover")); m_selection.setCacheAllRenderedFrames(true); m_selection.setEnabledBorders(Plasma::FrameSvg::AllBorders); m_sceneFrame = Compositor::self()->scene()->createEffectFrame(this); } EffectFrameImpl::~EffectFrameImpl() { delete m_sceneFrame; } const QFont& EffectFrameImpl::font() const { return m_font; } void EffectFrameImpl::setFont(const QFont& font) { if (m_font == font) { return; } m_font = font; QRect oldGeom = m_geometry; if (!m_text.isEmpty()) { autoResize(); } if (oldGeom == m_geometry) { // Wasn't updated in autoResize() m_sceneFrame->freeTextFrame(); } } void EffectFrameImpl::free() { m_sceneFrame->free(); } const QRect& EffectFrameImpl::geometry() const { return m_geometry; } void EffectFrameImpl::setGeometry(const QRect& geometry, bool force) { QRect oldGeom = m_geometry; m_geometry = geometry; if (m_geometry == oldGeom && !force) { return; } effects->addRepaint(oldGeom); effects->addRepaint(m_geometry); if (m_geometry.size() == oldGeom.size() && !force) { return; } if (m_style == EffectFrameStyled) { qreal left, top, right, bottom; m_frame.getMargins(left, top, right, bottom); // m_geometry is the inner geometry m_frame.resizeFrame(m_geometry.adjusted(-left, -top, right, bottom).size()); } free(); } const QIcon& EffectFrameImpl::icon() const { return m_icon; } void EffectFrameImpl::setIcon(const QIcon& icon) { m_icon = icon; if (isCrossFade()) { m_sceneFrame->crossFadeIcon(); } if (m_iconSize.isEmpty() && !m_icon.availableSizes().isEmpty()) { // Set a size if we don't already have one setIconSize(m_icon.availableSizes().first()); } m_sceneFrame->freeIconFrame(); } const QSize& EffectFrameImpl::iconSize() const { return m_iconSize; } void EffectFrameImpl::setIconSize(const QSize& size) { if (m_iconSize == size) { return; } m_iconSize = size; autoResize(); m_sceneFrame->freeIconFrame(); } void EffectFrameImpl::plasmaThemeChanged() { free(); } void EffectFrameImpl::render(QRegion region, double opacity, double frameOpacity) { if (m_geometry.isEmpty()) { return; // Nothing to display } m_shader = NULL; setScreenProjectionMatrix(static_cast(effects)->scene()->screenProjectionMatrix()); effects->paintEffectFrame(this, region, opacity, frameOpacity); } void EffectFrameImpl::finalRender(QRegion region, double opacity, double frameOpacity) const { region = infiniteRegion(); // TODO: Old region doesn't seem to work with OpenGL m_sceneFrame->render(region, opacity, frameOpacity); } Qt::Alignment EffectFrameImpl::alignment() const { return m_alignment; } void EffectFrameImpl::align(QRect &geometry) { if (m_alignment & Qt::AlignLeft) geometry.moveLeft(m_point.x()); else if (m_alignment & Qt::AlignRight) geometry.moveLeft(m_point.x() - geometry.width()); else geometry.moveLeft(m_point.x() - geometry.width() / 2); if (m_alignment & Qt::AlignTop) geometry.moveTop(m_point.y()); else if (m_alignment & Qt::AlignBottom) geometry.moveTop(m_point.y() - geometry.height()); else geometry.moveTop(m_point.y() - geometry.height() / 2); } void EffectFrameImpl::setAlignment(Qt::Alignment alignment) { m_alignment = alignment; align(m_geometry); setGeometry(m_geometry); } void EffectFrameImpl::setPosition(const QPoint& point) { m_point = point; QRect geometry = m_geometry; // this is important, setGeometry need call repaint for old & new geometry align(geometry); setGeometry(geometry); } const QString& EffectFrameImpl::text() const { return m_text; } void EffectFrameImpl::setText(const QString& text) { if (m_text == text) { return; } if (isCrossFade()) { m_sceneFrame->crossFadeText(); } m_text = text; QRect oldGeom = m_geometry; autoResize(); if (oldGeom == m_geometry) { // Wasn't updated in autoResize() m_sceneFrame->freeTextFrame(); } } void EffectFrameImpl::setSelection(const QRect& selection) { if (selection == m_selectionGeometry) { return; } m_selectionGeometry = selection; if (m_selectionGeometry.size() != m_selection.frameSize().toSize()) { m_selection.resizeFrame(m_selectionGeometry.size()); } // TODO; optimize to only recreate when resizing m_sceneFrame->freeSelection(); } void EffectFrameImpl::autoResize() { if (m_static) return; // Not automatically resizing QRect geometry; // Set size if (!m_text.isEmpty()) { QFontMetrics metrics(m_font); geometry.setSize(metrics.size(0, m_text)); } if (!m_icon.isNull() && !m_iconSize.isEmpty()) { geometry.setLeft(-m_iconSize.width()); if (m_iconSize.height() > geometry.height()) geometry.setHeight(m_iconSize.height()); } align(geometry); setGeometry(geometry); } QColor EffectFrameImpl::styledTextColor() { return m_theme->color(Plasma::Theme::TextColor); } } // namespace diff --git a/geometry.cpp b/geometry.cpp index 30d6bccb6..73507b296 100644 --- a/geometry.cpp +++ b/geometry.cpp @@ -1,3504 +1,3497 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak Copyright (C) 2009 Lucas Murray This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ /* This file contains things relevant to geometry, i.e. workspace size, window positions and window sizes. */ #include "client.h" #include "composite.h" #include "cursor.h" #include "netinfo.h" #include "workspace.h" #include "placement.h" #include "geometrytip.h" #include "rules.h" #include "screens.h" #include "effects.h" #include "screenedge.h" #include #include #include #include "outline.h" #include "shell_client.h" #include "wayland_server.h" #include #include namespace KWin { static inline int sign(int v) { return (v > 0) - (v < 0); } //******************************************** // Workspace //******************************************** extern int screen_number; extern bool is_multihead; /*! Resizes the workspace after an XRANDR screen size change */ void Workspace::desktopResized() { QRect geom = screens()->geometry(); NETSize desktop_geometry; desktop_geometry.width = geom.width(); desktop_geometry.height = geom.height(); rootInfo()->setDesktopGeometry(desktop_geometry); updateClientArea(); saveOldScreenSizes(); // after updateClientArea(), so that one still uses the previous one // TODO: emit a signal instead and remove the deep function calls into edges and effects ScreenEdges::self()->recreateEdges(); if (effects) { static_cast(effects)->desktopResized(geom.size()); } - - //Update the shape of the overlay window to fix redrawing of unredirected windows. bug#305781 - m_compositor->checkUnredirect(true); } void Workspace::saveOldScreenSizes() { olddisplaysize = QSize( displayWidth(), displayHeight()); oldscreensizes.clear(); for( int i = 0; i < screens()->count(); ++i ) oldscreensizes.append( screens()->geometry( 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) { const Screens *s = Screens::self(); int nscreens = s->count(); const int numberOfDesktops = VirtualDesktopManager::self()->count(); QVector< QRect > new_wareas(numberOfDesktops + 1); QVector< StrutRects > new_rmoveareas(numberOfDesktops + 1); QVector< QVector< QRect > > new_sareas(numberOfDesktops + 1); QVector< QRect > screens(nscreens); QRect desktopArea; for (int i = 0; i < nscreens; i++) { desktopArea |= s->geometry(i); } for (int iS = 0; iS < nscreens; iS ++) { screens [iS] = s->geometry(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); // sanity check that a strut doesn't exclude a complete screen geometry // this is a violation to EWMH, as KWin just ignores the strut for (int i = 0; i < Screens::self()->count(); i++) { if (!r.intersects(Screens::self()->geometry(i))) { qCDebug(KWIN_CORE) << "Adjusted client area would exclude a complete screen, ignore"; r = desktopArea; break; } } 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 ++) { const auto geo = new_sareas[ i ][ iS ].intersected( (*it)->adjustedClientArea(desktopArea, screens[ iS ])); // ignore the geometry if it results in the screen getting removed completly if (!geo.isEmpty()) { new_sareas[ i ][ iS ] = geo; } } } } 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 ++) { // qDebug() << "adjusting new_sarea: " << screens[ iS ]; const auto geo = new_sareas[(*it)->desktop()][ iS ].intersected( (*it)->adjustedClientArea(desktopArea, screens[ iS ])); // ignore the geometry if it results in the screen getting removed completly if (!geo.isEmpty()) { new_sareas[(*it)->desktop()][ iS ] = geo; } } } } if (waylandServer()) { auto updateStrutsForWaylandClient = [&] (ShellClient *c) { // assuming that only docks have "struts" and that all docks have a strut if (!c->hasStrut()) { return; } auto margins = [c] (const QRect &geometry) { QMargins margins; if (!geometry.intersects(c->geometry())) { return margins; } // figure out which areas of the overall screen setup it borders const bool left = c->geometry().left() == geometry.left(); const bool right = c->geometry().right() == geometry.right(); const bool top = c->geometry().top() == geometry.top(); const bool bottom = c->geometry().bottom() == geometry.bottom(); const bool horizontal = c->geometry().width() >= c->geometry().height(); if (left && ((!top && !bottom) || !horizontal)) { margins.setLeft(c->geometry().width()); } if (right && ((!top && !bottom) || !horizontal)) { margins.setRight(c->geometry().width()); } if (top && ((!left && !right) || horizontal)) { margins.setTop(c->geometry().height()); } if (bottom && ((!left && !right) || horizontal)) { margins.setBottom(c->geometry().height()); } return margins; }; // TODO: implement restrictedMoveArea adjustments QRect r = desktopArea - margins(KWin::screens()->geometry()); if (c->isOnAllDesktops()) { for (int i = 1; i <= numberOfDesktops; ++i) { new_wareas[ i ] = new_wareas[ i ].intersected(r); for (int iS = 0; iS < nscreens; ++iS) { new_sareas[ i ][ iS ] = new_sareas[ i ][ iS ].intersected(screens[iS] - margins(screens[iS])); } } } else { new_wareas[c->desktop()] = new_wareas[c->desktop()].intersected(r); for (int iS = 0; iS < nscreens; iS++) { new_sareas[c->desktop()][ iS ] = new_sareas[c->desktop()][ iS ].intersected(screens[iS] - margins(screens[iS])); } } }; const auto clients = waylandServer()->clients(); for (auto c : clients) { updateStrutsForWaylandClient(c); } const auto internalClients = waylandServer()->internalClients(); for (auto c : internalClients) { updateStrutsForWaylandClient(c); } } #if 0 for (int i = 1; i <= numberOfDesktops(); ++i) { for (int iS = 0; iS < nscreens; iS ++) qCDebug(KWIN_CORE) << "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 (auto it = m_allClients.constBegin(); it != m_allClients.constEnd(); ++it) (*it)->checkWorkspacePosition(); for (ClientList::ConstIterator it = desktops.constBegin(); it != desktops.constEnd(); ++it) (*it)->checkWorkspacePosition(); oldrestrictedmovearea.clear(); // reset, no longer valid or needed } } 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 = VirtualDesktopManager::self()->current(); if (screen == -1) screen = screens()->current(); 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 ] : screens()->geometry(screen_number); warea = workarea[ desktop ].isNull() ? screens()->geometry(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 ] : screens()->geometry(screen); warea = workarea[ desktop ].isNull() ? QRect(0, 0, displayWidth(), displayHeight()) : workarea[ desktop ]; } switch(opt) { case MaximizeArea: case PlacementArea: return sarea; case MaximizeFullArea: case FullScreenArea: case MovementArea: case ScreenArea: if (is_multihead) return screens()->geometry(screen_number); else return screens()->geometry(screen); case WorkArea: if (is_multihead) return sarea; else return warea; case FullArea: return QRect(0, 0, displayWidth(), displayHeight()); } abort(); } QRect Workspace::clientArea(clientAreaOption opt, const QPoint& p, int desktop) const { return clientArea(opt, screens()->number(p), desktop); } QRect Workspace::clientArea(clientAreaOption opt, const AbstractClient* 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 = VirtualDesktopManager::self()->current(); 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 = VirtualDesktopManager::self()->current(); 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(AbstractClient* c, QPoint pos, bool unrestricted, double snapAdjust) { QSize borderSnapZone(options->borderSnapZone(), options->borderSnapZone()); QRect maxRect; int guideMaximized = MaximizeRestore; if (c->maximizeMode() != MaximizeRestore) { maxRect = clientArea(MaximizeArea, pos + c->rect().center(), c->desktop()); QRect geo = c->geometry(); if (c->maximizeMode() & MaximizeHorizontal && (geo.x() == maxRect.left() || geo.right() == maxRect.right())) { guideMaximized |= MaximizeHorizontal; borderSnapZone.setWidth(qMax(borderSnapZone.width() + 2, maxRect.width() / 16)); } if (c->maximizeMode() & MaximizeVertical && (geo.y() == maxRect.top() || geo.bottom() == maxRect.bottom())) { guideMaximized |= MaximizeVertical; borderSnapZone.setHeight(qMax(borderSnapZone.height() + 2, maxRect.height() / 16)); } } if (options->windowSnapZone() || !borderSnapZone.isNull() || options->centerSnapZone()) { const bool sOWO = options->isSnapOnlyWhenOverlapping(); const int screen = screens()->number(pos + c->rect().center()); if (maxRect.isNull()) maxRect = clientArea(MovementArea, screen, 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 const int snapX = borderSnapZone.width() * snapAdjust; //snap trigger const int snapY = borderSnapZone.height() * snapAdjust; if (snapX || snapY) { QRect geo = c->geometry(); const QPoint cp = c->clientPos(); const QSize cs = geo.size() - c->clientSize(); int padding[4] = { cp.x(), cs.width() - cp.x(), cp.y(), cs.height() - cp.y() }; // snap to titlebar / snap to window borders on inner screen edges Client::Position titlePos = c->titlebarPosition(); if (padding[0] && (titlePos == Client::PositionLeft || (c->maximizeMode() & MaximizeHorizontal) || screens()->intersecting(geo.translated(maxRect.x() - (padding[0] + geo.x()), 0)) > 1)) padding[0] = 0; if (padding[1] && (titlePos == Client::PositionRight || (c->maximizeMode() & MaximizeHorizontal) || screens()->intersecting(geo.translated(maxRect.right() + padding[1] - geo.right(), 0)) > 1)) padding[1] = 0; if (padding[2] && (titlePos == Client::PositionTop || (c->maximizeMode() & MaximizeVertical) || screens()->intersecting(geo.translated(0, maxRect.y() - (padding[2] + geo.y()))) > 1)) padding[2] = 0; if (padding[3] && (titlePos == Client::PositionBottom || (c->maximizeMode() & MaximizeVertical) || screens()->intersecting(geo.translated(0, maxRect.bottom() + padding[3] - geo.bottom())) > 1)) padding[3] = 0; if ((sOWO ? (cx < xmin) : true) && (qAbs(xmin - cx) < snapX)) { deltaX = xmin - cx; nx = xmin - padding[0]; } if ((sOWO ? (rx > xmax) : true) && (qAbs(rx - xmax) < snapX) && (qAbs(xmax - rx) < deltaX)) { deltaX = rx - xmax; nx = xmax - cw + padding[1]; } if ((sOWO ? (cy < ymin) : true) && (qAbs(ymin - cy) < snapY)) { deltaY = ymin - cy; ny = ymin - padding[2]; } if ((sOWO ? (ry > ymax) : true) && (qAbs(ry - ymax) < snapY) && (qAbs(ymax - ry) < deltaY)) { deltaY = ry - ymax; ny = ymax - ch + padding[3]; } } // windows snap int snap = options->windowSnapZone() * snapAdjust; if (snap) { for (auto l = m_allClients.constBegin(); l != m_allClients.constEnd(); ++l) { if ((*l) == c) continue; if ((*l)->isMinimized()) continue; // is minimized if ((*l)->tabGroup() && (*l) != (*l)->tabGroup()->current()) continue; // is not active tab if (!((*l)->isOnDesktop(c->desktop()) || c->isOnDesktop((*l)->desktop()))) continue; // wrong virtual desktop if (!(*l)->isOnCurrentActivity()) continue; // wrong activity if ((*l)->isDesktop() || (*l)->isSplash()) continue; lx = (*l)->x(); ly = (*l)->y(); lrx = lx + (*l)->width(); lry = ly + (*l)->height(); if (!(guideMaximized & MaximizeHorizontal) && (((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 (!(guideMaximized & MaximizeVertical) && (((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 (!(guideMaximized & MaximizeVertical) && (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 (!(guideMaximized & MaximizeHorizontal) && (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 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 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 nx = (xmin + xmax) / 2 - cw / 2; } } } pos = QPoint(nx, ny); } return pos; } QRect Workspace::adjustClientSize(AbstractClient* c, QRect moveResizeGeom, int mode) { //adapted from adjustClientPosition on 29May2004 //this function is called when resizing a window and will modify //the new dimensions to snap to other windows/borders if appropriate if (options->windowSnapZone() || options->borderSnapZone()) { // || options->centerSnapZone ) const bool sOWO = options->isSnapOnlyWhenOverlapping(); const QRect maxRect = clientArea(MovementArea, c->rect().center(), c->desktop()); const int xmin = maxRect.left(); const int xmax = maxRect.right(); //desk size const int ymin = maxRect.top(); const int ymax = maxRect.bottom(); const int cx(moveResizeGeom.left()); const int cy(moveResizeGeom.top()); const int rx(moveResizeGeom.right()); const int ry(moveResizeGeom.bottom()); int newcx(cx), newcy(cy); //buffers int newrx(rx), newry(ry); int deltaX(xmax); int deltaY(ymax); //minimum distance to other clients int lx, ly, lrx, lry; //coords and size for the comparison client, l // border snap int snap = options->borderSnapZone(); //snap trigger if (snap) { deltaX = int(snap); deltaY = int(snap); #define SNAP_BORDER_TOP \ if ((sOWO?(newcyymax):true) && (qAbs(ymax-newry)xmax):true) && (qAbs(xmax-newrx)windowSnapZone(); if (snap) { deltaX = int(snap); deltaY = int(snap); for (auto l = m_allClients.constBegin(); l != m_allClients.constEnd(); ++l) { if ((*l)->isOnDesktop(VirtualDesktopManager::self()->current()) && !(*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 Client::PositionBottomRight: SNAP_WINDOW_BOTTOM SNAP_WINDOW_RIGHT SNAP_WINDOW_C_BOTTOM SNAP_WINDOW_C_RIGHT break; case Client::PositionRight: SNAP_WINDOW_RIGHT SNAP_WINDOW_C_RIGHT break; case Client::PositionBottom: SNAP_WINDOW_BOTTOM SNAP_WINDOW_C_BOTTOM break; case Client::PositionTopLeft: SNAP_WINDOW_TOP SNAP_WINDOW_LEFT SNAP_WINDOW_C_TOP SNAP_WINDOW_C_LEFT break; case Client::PositionLeft: SNAP_WINDOW_LEFT SNAP_WINDOW_C_LEFT break; case Client::PositionTop: SNAP_WINDOW_TOP SNAP_WINDOW_C_TOP break; case Client::PositionTopRight: SNAP_WINDOW_TOP SNAP_WINDOW_RIGHT SNAP_WINDOW_C_TOP SNAP_WINDOW_C_RIGHT break; case Client::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(AbstractClient *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; } // 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(xcb_window_t w, const xcb_get_geometry_reply_t *geometry) { NETWinInfo i(connection(), w, rootWindow(), NET::WMFrameExtents, 0); NETStrut frame = i.frameExtents(); if (frame.left != 0 || frame.top != 0) { // left and top needed due to narrowing conversations restrictions in C++11 const uint32_t left = frame.left; const uint32_t top = frame.top; const uint32_t values[] = { geometry->x - left, geometry->y - top }; xcb_configure_window(connection(), w, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values); } } //******************************************** // Client //******************************************** /*! Returns \a area with the client's strut taken into account. Used from Workspace in updateClientArea. */ // TODO move to Workspace? QRect Client::adjustedClientArea(const QRect &desktopArea, const QRect& area) const { QRect r = area; NETExtendedStrut str = strut(); QRect stareaL = QRect( 0, str . left_start, str . left_width, str . left_end - str . left_start + 1); QRect stareaR = QRect( desktopArea . right() - str . right_width + 1, str . right_start, str . right_width, str . right_end - str . right_start + 1); QRect stareaT = QRect( str . top_start, 0, str . top_end - str . top_start + 1, str . top_width); QRect stareaB = QRect( str . bottom_start, desktopArea . bottom() - str . bottom_width + 1, str . bottom_end - str . bottom_start + 1, str . bottom_width); QRect screenarea = workspace()->clientArea(ScreenArea, this); // HACK: workarea handling is not xinerama aware, so if this strut // reserves place at a xinerama edge that's inside the virtual screen, // ignore the strut for workspace setting. if (area == QRect(0, 0, displayWidth(), displayHeight())) { if (stareaL.left() < screenarea.left()) stareaL = QRect(); if (stareaR.right() > screenarea.right()) stareaR = QRect(); if (stareaT.top() < screenarea.top()) stareaT = QRect(); if (stareaB.bottom() < screenarea.bottom()) stareaB = QRect(); } // Handle struts at xinerama edges that are inside the virtual screen. // They're given in virtual screen coordinates, make them affect only // their xinerama screen. stareaL.setLeft(qMax(stareaL.left(), screenarea.left())); stareaR.setRight(qMin(stareaR.right(), screenarea.right())); stareaT.setTop(qMax(stareaT.top(), screenarea.top())); stareaB.setBottom(qMin(stareaB.bottom(), screenarea.bottom())); if (stareaL . intersects(area)) { // qDebug() << "Moving left of: " << r << " to " << stareaL.right() + 1; r . setLeft(stareaL . right() + 1); } if (stareaR . intersects(area)) { // qDebug() << "Moving right of: " << r << " to " << stareaR.left() - 1; r . setRight(stareaR . left() - 1); } if (stareaT . intersects(area)) { // qDebug() << "Moving top of: " << r << " to " << stareaT.bottom() + 1; r . setTop(stareaT . bottom() + 1); } if (stareaB . intersects(area)) { // qDebug() << "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 for (int i = 0; i < screens()->count(); i ++) region -= screens()->geometry(i); // If there's anything left then we have an offscreen strut return !region.isEmpty(); } void AbstractClient::checkWorkspacePosition(QRect oldGeometry, int oldDesktop, QRect oldClientGeometry) { enum { Left = 0, Top, Right, Bottom }; const int border[4] = { borderLeft(), borderTop(), borderRight(), borderBottom() }; if( !oldGeometry.isValid()) oldGeometry = geometry(); if( oldDesktop == -2 ) oldDesktop = desktop(); if (!oldClientGeometry.isValid()) oldClientGeometry = oldGeometry.adjusted(border[Left], border[Top], -border[Right], -border[Bottom]); 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 (quickTileMode() != QuickTileNone) { setGeometry(electricBorderMaximizeGeometry(geometry().center(), desktop())); return; } // 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() || 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(const 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, geometryRestore().center(), desktop()); int topMax = screenArea.y(); int rightMax = screenArea.x() + screenArea.width(); int bottomMax = screenArea.y() + screenArea.height(); int leftMax = screenArea.x(); QRect newGeom = geometryRestore(); // geometry(); QRect newClientGeom = newGeom.adjusted(border[Left], border[Top], -border[Right], -border[Bottom]); 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). // These 4 compute old bounds ... auto moveAreaFunc = workspace()->inUpdateClientArea() ? &Workspace::previousRestrictedMoveArea : //... the restricted areas changed &Workspace::restrictedMoveArea; //... when e.g. active desktop or screen changes foreach (const QRect & r, (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaTop).rects()) { QRect rect = r & oldGeomTall; if (!rect.isEmpty()) oldTopMax = qMax(oldTopMax, rect.y() + rect.height()); } foreach (const QRect & r, (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaRight).rects()) { QRect rect = r & oldGeomWide; if (!rect.isEmpty()) oldRightMax = qMin(oldRightMax, rect.x()); } foreach (const QRect & r, (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaBottom).rects()) { QRect rect = r & oldGeomTall; if (!rect.isEmpty()) oldBottomMax = qMin(oldBottomMax, rect.y()); } foreach (const QRect & r, (workspace()->*moveAreaFunc)(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 bool keep[4] = {false, false, false, false}; bool save[4] = {false, false, false, false}; int padding[4] = {0, 0, 0, 0}; if (oldGeometry.x() >= oldLeftMax) save[Left] = newGeom.x() < leftMax; if (oldGeometry.x() == oldLeftMax) keep[Left] = newGeom.x() != leftMax; else if (oldClientGeometry.x() == oldLeftMax && newClientGeom.x() != leftMax) { padding[0] = border[Left]; keep[Left] = true; } if (oldGeometry.y() >= oldTopMax) save[Top] = newGeom.y() < topMax; if (oldGeometry.y() == oldTopMax) keep[Top] = newGeom.y() != topMax; else if (oldClientGeometry.y() == oldTopMax && newClientGeom.y() != topMax) { padding[1] = border[Left]; keep[Top] = true; } if (oldGeometry.right() <= oldRightMax - 1) save[Right] = newGeom.right() > rightMax - 1; if (oldGeometry.right() == oldRightMax - 1) keep[Right] = newGeom.right() != rightMax - 1; else if (oldClientGeometry.right() == oldRightMax - 1 && newClientGeom.right() != rightMax - 1) { padding[2] = border[Right]; keep[Right] = true; } if (oldGeometry.bottom() <= oldBottomMax - 1) save[Bottom] = newGeom.bottom() > bottomMax - 1; if (oldGeometry.bottom() == oldBottomMax - 1) keep[Bottom] = newGeom.bottom() != bottomMax - 1; else if (oldClientGeometry.bottom() == oldBottomMax - 1 && newClientGeom.bottom() != bottomMax - 1) { padding[3] = border[Bottom]; keep[Bottom] = true; } // if randomly touches opposing edges, do not favor either if (keep[Left] && keep[Right]) { keep[Left] = keep[Right] = false; padding[0] = padding[2] = 0; } if (keep[Top] && keep[Bottom]) { keep[Top] = keep[Bottom] = false; padding[1] = padding[3] = 0; } if (save[Left] || keep[Left]) newGeom.moveLeft(qMax(leftMax, screenArea.x()) - padding[0]); if (padding[0] && screens()->intersecting(newGeom) > 1) newGeom.moveLeft(newGeom.left() + padding[0]); if (save[Top] || keep[Top]) newGeom.moveTop(qMax(topMax, screenArea.y()) - padding[1]); if (padding[1] && screens()->intersecting(newGeom) > 1) newGeom.moveTop(newGeom.top() + padding[1]); if (save[Right] || keep[Right]) newGeom.moveRight(qMin(rightMax - 1, screenArea.right()) + padding[2]); if (padding[2] && screens()->intersecting(newGeom) > 1) newGeom.moveRight(newGeom.right() - padding[2]); if (oldGeometry.x() >= oldLeftMax && newGeom.x() < leftMax) newGeom.setLeft(qMax(leftMax, screenArea.x())); else if (oldClientGeometry.x() >= oldLeftMax && newGeom.x() + border[Left] < leftMax) { newGeom.setLeft(qMax(leftMax, screenArea.x()) - border[Left]); if (screens()->intersecting(newGeom) > 1) newGeom.setLeft(newGeom.left() + border[Left]); } if (save[Bottom] || keep[Bottom]) newGeom.moveBottom(qMin(bottomMax - 1, screenArea.bottom()) + padding[3]); if (padding[3] && screens()->intersecting(newGeom) > 1) newGeom.moveBottom(newGeom.bottom() - padding[3]); if (oldGeometry.y() >= oldTopMax && newGeom.y() < topMax) newGeom.setTop(qMax(topMax, screenArea.y())); else if (oldClientGeometry.y() >= oldTopMax && newGeom.y() + border[Top] < topMax) { newGeom.setTop(qMax(topMax, screenArea.y()) - border[Top]); if (screens()->intersecting(newGeom) > 1) newGeom.setTop(newGeom.top() + border[Top]); } checkOffscreenPosition(&newGeom, screenArea); // Obey size hints. TODO: We really should make sure it stays in the right place if (!isShade()) newGeom.setSize(adjustedSize(newGeom.size())); if (newGeom != geometry()) setGeometry(newGeom); } void AbstractClient::checkOffscreenPosition(QRect* geom, const QRect& screenArea) { if (geom->left() > screenArea.right()) { geom->moveLeft(screenArea.right() - screenArea.width()/4); } else if (geom->right() < screenArea.left()) { geom->moveRight(screenArea.left() + screenArea.width()/4); } if (geom->top() > screenArea.bottom()) { geom->moveTop(screenArea.bottom() - screenArea.height()/4); } else if (geom->bottom() < screenArea.top()) { geom->moveBottom(screenArea.top() + screenArea.width()/4); } } /*! Adjust the frame size \a frame according to he window's size hints. */ QSize AbstractClient::adjustedSize(const QSize& frame, Sizemode mode) const { // first, get the window size for the given frame size s QSize wsize(frame.width() - (borderLeft() + borderRight()), frame.height() - (borderTop() + borderBottom())); if (wsize.isEmpty()) wsize = QSize(qMax(wsize.width(), 1), qMax(wsize.height(), 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 AbstractClient::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) { qCWarning(KWIN_CORE) << "sizeForClientSize() with empty size!" ; } 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 (isDecorated()) { QSize decominsize(0, 0); QSize border_size(borderLeft() + borderRight(), borderTop() + borderBottom()); 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 = m_geometryHints.resizeIncrements().width(); int height_inc = m_geometryHints.resizeIncrements().height(); int basew_inc = m_geometryHints.baseSize().width(); int baseh_inc = m_geometryHints.baseSize().height(); if (!m_geometryHints.hasBaseSize()) { basew_inc = m_geometryHints.minSize().width(); baseh_inc = m_geometryHints.minSize().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 (m_geometryHints.hasAspect()) { double min_aspect_w = m_geometryHints.minAspect().width(); // use doubles, because the values can be MAX_INT double min_aspect_h = m_geometryHints.minAspect().height(); // and multiplying would go wrong otherwise double max_aspect_w = m_geometryHints.maxAspect().width(); double max_aspect_h = m_geometryHints.maxAspect().height(); // 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. const QSize baseSize = m_geometryHints.baseSize(); w -= baseSize.width(); h -= baseSize.height(); int max_width = max_size.width() - baseSize.width(); int min_width = min_size.width() - baseSize.width(); int max_height = max_size.height() - baseSize.height(); int min_height = min_size.height() - baseSize.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 += baseSize.width(); h += baseSize.height(); } if (!rules()->checkStrictGeometry(!isFullScreen())) { // disobey increments and aspect by explicit rule w = w1; h = h1; } if (!noframe) { w += borderLeft() + borderRight(); h += borderTop() + borderBottom(); } return rules()->checkSize(QSize(w, h)); } /*! Gets the client's normal WM hints and reconfigures itself respectively. */ void Client::getWmNormalHints() { const bool hadFixedAspect = m_geometryHints.hasAspect(); // roundtrip to X server m_geometryHints.fetch(); m_geometryHints.read(); if (!hadFixedAspect && m_geometryHints.hasAspect()) { // align to eventual new contraints maximize(max_mode); } // 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 origClientGeometry(pos() + clientPos(), clientSize()); 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(origClientGeometry)) keepInArea(area); area = workspace()->clientArea(WorkArea, this); if (area.contains(origClientGeometry)) keepInArea(area); } } } updateAllowedActions(); // affects isResizeable() } QSize Client::minSize() const { return rules()->checkMinSize(m_geometryHints.minSize()); } QSize Client::maxSize() const { return rules()->checkMaxSize(m_geometryHints.maxSize()); } QSize Client::basicUnit() const { return m_geometryHints.resizeIncrements(); } /*! Auxiliary function to inform the client about the current window configuration. */ void Client::sendSyntheticConfigureNotify() { xcb_configure_notify_event_t c; memset(&c, 0, sizeof(c)); c.response_type = XCB_CONFIGURE_NOTIFY; 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_sibling = XCB_WINDOW_NONE; c.override_redirect = 0; xcb_send_event(connection(), true, c.event, XCB_EVENT_MASK_STRUCTURE_NOTIFY, reinterpret_cast(&c)); xcb_flush(connection()); } 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 = m_geometryHints.windowGravity(); // dx, dy specify how the client window moves to make space for the frame switch(gravity) { case NorthWestGravity: // move down right default: dx = borderLeft(); dy = borderTop(); break; case NorthGravity: // move right dx = 0; dy = borderTop(); break; case NorthEastGravity: // move down left dx = -borderRight(); dy = borderTop(); break; case WestGravity: // move right dx = borderLeft(); 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 = -borderRight(); dy = 0; break; case SouthWestGravity: // move up right dx = borderLeft() ; dy = -borderBottom(); break; case SouthGravity: // move up dx = 0; dy = -borderBottom(); break; case SouthEastGravity: // move up left dx = -borderRight(); dy = -borderBottom(); break; } if (gravity != CenterGravity) { // translate from client movement to frame movement dx -= borderLeft(); dy -= borderTop(); } else { // center of the frame will be at the same position client center without frame would be dx = - (borderLeft() + borderRight()) / 2; dy = - (borderTop() + borderBottom()) / 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 qCDebug(KWIN_CORE) << this << bool(value_mask & (CWX|CWWidth|CWY|CWHeight)) << bool(maximizeMode() & MaximizeVertical) << bool(maximizeMode() & MaximizeHorizontal); // we want to (partially) ignore the request when the window is somehow maximized or quicktiled bool ignore = !app_noborder && (quickTileMode() != QuickTileNone || maximizeMode() != MaximizeRestore); // however, the user shall be able to force obedience despite and also disobedience in general ignore = rules()->checkIgnoreGeometry(ignore); if (!ignore) { // either we're not max'd / q'tiled or the user allowed the client to break that - so break it. updateQuickTileMode(QuickTileNone); max_mode = MaximizeRestore; emit quickTileModeChanged(); } else if (!app_noborder && quickTileMode() == QuickTileNone && (maximizeMode() == MaximizeVertical || maximizeMode() == MaximizeHorizontal)) { // ignoring can be, because either we do, or the user does explicitly not want it. // for partially maximized windows we want to allow configures in the other dimension. // so we've to ask the user again - to know whether we just ignored for the partial maximization. // the problem here is, that the user can explicitly permit configure requests - even for maximized windows! // we cannot distinguish that from passing "false" for partially maximized windows. ignore = rules()->checkIgnoreGeometry(false); if (!ignore) { // the user is not interested, so we fix up dimensions if (maximizeMode() == MaximizeVertical) value_mask &= ~(CWY|CWHeight); if (maximizeMode() == MaximizeHorizontal) value_mask &= ~(CWX|CWWidth); if (!(value_mask & (CWX|CWWidth|CWY|CWHeight))) { ignore = true; // the modification turned the request void } } } if (ignore) { qCDebug(KWIN_CORE) << "DENIED"; return; // nothing to (left) to do for use - bugs #158974, #252314, #321491 } qCDebug(KWIN_CORE) << "PERMITTED" << this << bool(value_mask & (CWX|CWWidth|CWY|CWHeight)); if (gravity == 0) // default (nonsense) value for the argument gravity = m_geometryHints.windowGravity(); 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); int newScreen = screens()->number(QRect(new_pos, ns).center()); if (newScreen != rules()->checkScreen(newScreen)) return; // not allowed by rule QRect origClientGeometry(pos() + clientPos(), clientSize()); 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(origClientGeometry)) 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 origClientGeometry(pos() + clientPos(), clientSize()); GeometryUpdatesBlocker blocker(this); resizeWithChecks(ns, xcb_gravity_t(gravity)); updateFullScreenHack(QRect(calculateGravitation(true, m_geometryHints.windowGravity()), 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(origClientGeometry)) keepInArea(area); area = workspace()->clientArea(WorkArea, this); if (area.contains(origClientGeometry)) keepInArea(area); } } } geom_restore = geometry(); // No need to send synthetic configure notify event here, either it's sent together // with geometry change, or there's no need to send it. // Handling of the real ConfigureRequest event forces sending it, as there it's necessary. } void Client::resizeWithChecks(int w, int h, xcb_gravity_t gravity, ForceGeometry_t force) { assert(!shade_geometry_change); if (isShade()) { if (h == borderTop() + borderBottom()) { qCWarning(KWIN_CORE) << "Shaded geometry passed for size:" ; } } 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(); if (gravity == 0) { gravity = m_geometryHints.windowGravity(); } switch(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 (!hasNETSupport() && !m_motif.move()) { return false; } if (isFullScreen()) 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 moveable across Xinerama screens */ bool Client::isMovableAcrossScreens() const { if (!hasNETSupport() && !m_motif.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 (!hasNETSupport() && !m_motif.resize()) { return false; } if (isFullScreen()) return false; if (isSpecialWindow() || isSplash() || isToolbar()) return false; if (rules()->checkSize(QSize()).isValid()) // forced size return false; const Position mode = moveResizePointerMode(); if ((mode == PositionTop || mode == PositionTopLeft || mode == PositionTopRight || mode == PositionLeft || mode == PositionBottomLeft) && rules()->checkPosition(invalidPoint) != invalidPoint) 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 { if (!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 == borderTop() + borderBottom()) { qCDebug(KWIN_CORE) << "Shaded geometry passed for size:"; } else { client_size = QSize(w - borderLeft() - borderRight(), h - borderTop() - borderBottom()); h = borderTop() + borderBottom(); } } else { client_size = QSize(w - borderLeft() - borderRight(), h - borderTop() - borderBottom()); } QRect g(x, y, w, h); if (!areGeometryUpdatesBlocked() && g != rules()->checkGeometry(g)) { qCDebug(KWIN_CORE) << "forced geometry fail:" << g << ":" << rules()->checkGeometry(g); } if (force == NormalGeometrySet && geom == g && pendingGeometryUpdate() == PendingGeometryNone) return; geom = g; if (areGeometryUpdatesBlocked()) { if (pendingGeometryUpdate() == PendingGeometryForced) {} // maximum, nothing needed else if (force == ForceGeometrySet) setPendingGeometryUpdate(PendingGeometryForced); else setPendingGeometryUpdate(PendingGeometryNormal); return; } QSize oldClientSize = m_frame.geometry().size(); bool resized = (geometryBeforeUpdateBlocking().size() != geom.size() || pendingGeometryUpdate() == PendingGeometryForced); if (resized) { resizeDecoration(); m_frame.setGeometry(x, y, w, h); if (!isShade()) { QSize cs = clientSize(); m_wrapper.setGeometry(QRect(clientPos(), cs)); if (!isResize() || syncRequest.counter == XCB_NONE) m_client.setGeometry(0, 0, cs.width(), cs.height()); // SELI - won't this be too expensive? // THOMAS - yes, but gtk+ clients will not resize without ... sendSyntheticConfigureNotify(); } updateShape(); } else { if (isMoveResize()) { if (compositing()) // Defer the X update until we leave this mode needsXWindowMove = true; else m_frame.move(x, y); // sendSyntheticConfigureNotify() on finish shall be sufficient } else { m_frame.move(x, y); sendSyntheticConfigureNotify(); } // Unconditionally move the input window: it won't affect rendering m_decoInputExtent.move(QPoint(x, y) + inputPos()); } updateWindowRules(Rules::Position|Rules::Size); // keep track of old maximize mode // to detect changes screens()->setCurrent(this); workspace()->updateStackingOrder(); // need to regenerate decoration pixmaps when either // - size is changed // - maximize mode is changed to MaximizeRestore, when size unchanged // which can happen when untabbing maximized windows if (resized) { if (oldClientSize != QSize(w,h)) discardWindowPixmap(); emit geometryShapeChanged(this, geometryBeforeUpdateBlocking()); } addRepaintDuringGeometryUpdates(); updateGeometryBeforeUpdateBlocking(); // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Geometry); // TODO: this signal is emitted too often emit geometryChanged(); } void Client::plainResize(int w, int h, ForceGeometry_t force) { // this code is also duplicated in Client::setGeometry(), and it's also commented there if (shade_geometry_change) ; // nothing else if (isShade()) { if (h == borderTop() + borderBottom()) { qCDebug(KWIN_CORE) << "Shaded geometry passed for size:"; } else { client_size = QSize(w - borderLeft() - borderRight(), h - borderTop() - borderBottom()); h = borderTop() + borderBottom(); } } else { client_size = QSize(w - borderLeft() - borderRight(), h - borderTop() - borderBottom()); } QSize s(w, h); if (!areGeometryUpdatesBlocked() && s != rules()->checkSize(s)) { qCDebug(KWIN_CORE) << "forced size fail:" << s << ":" << rules()->checkSize(s); } // resuming geometry updates is handled only in setGeometry() assert(pendingGeometryUpdate() == PendingGeometryNone || areGeometryUpdatesBlocked()); if (force == NormalGeometrySet && geom.size() == s) return; geom.setSize(s); if (areGeometryUpdatesBlocked()) { if (pendingGeometryUpdate() == PendingGeometryForced) {} // maximum, nothing needed else if (force == ForceGeometrySet) setPendingGeometryUpdate(PendingGeometryForced); else setPendingGeometryUpdate(PendingGeometryNormal); return; } QSize oldClientSize = m_frame.geometry().size(); resizeDecoration(); m_frame.resize(w, h); // resizeDecoration( s ); if (!isShade()) { QSize cs = clientSize(); m_wrapper.setGeometry(QRect(clientPos(), cs)); m_client.setGeometry(0, 0, cs.width(), cs.height()); } updateShape(); sendSyntheticConfigureNotify(); updateWindowRules(Rules::Position|Rules::Size); screens()->setCurrent(this); workspace()->updateStackingOrder(); if (oldClientSize != QSize(w,h)) discardWindowPixmap(); emit geometryShapeChanged(this, geometryBeforeUpdateBlocking()); addRepaintDuringGeometryUpdates(); updateGeometryBeforeUpdateBlocking(); // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Geometry); // TODO: this signal is emitted too often emit geometryChanged(); } /*! Reimplemented to inform the client about the new window position. */ void AbstractClient::move(int x, int y, ForceGeometry_t force) { // resuming geometry updates is handled only in setGeometry() assert(pendingGeometryUpdate() == PendingGeometryNone || areGeometryUpdatesBlocked()); QPoint p(x, y); if (!areGeometryUpdatesBlocked() && p != rules()->checkPosition(p)) { qCDebug(KWIN_CORE) << "forced position fail:" << p << ":" << rules()->checkPosition(p); } if (force == NormalGeometrySet && geom.topLeft() == p) return; geom.moveTopLeft(p); if (areGeometryUpdatesBlocked()) { if (pendingGeometryUpdate() == PendingGeometryForced) {} // maximum, nothing needed else if (force == ForceGeometrySet) setPendingGeometryUpdate(PendingGeometryForced); else setPendingGeometryUpdate(PendingGeometryNormal); return; } doMove(x, y); updateWindowRules(Rules::Position); screens()->setCurrent(this); workspace()->updateStackingOrder(); - if (Compositor::isCreated()) { - // TODO: move out of geometry.cpp, is this really needed here? - Compositor::self()->checkUnredirect(); - } // client itself is not damaged addRepaintDuringGeometryUpdates(); updateGeometryBeforeUpdateBlocking(); // Update states of all other windows in this group updateTabGroupStates(TabGroup::Geometry); emit geometryChanged(); } void Client::doMove(int x, int y) { m_frame.move(x, y); sendSyntheticConfigureNotify(); } void AbstractClient::blockGeometryUpdates(bool block) { if (block) { if (m_blockGeometryUpdates == 0) m_pendingGeometryUpdate = PendingGeometryNone; ++m_blockGeometryUpdates; } else { if (--m_blockGeometryUpdates == 0) { if (m_pendingGeometryUpdate != PendingGeometryNone) { if (isShade()) setGeometry(QRect(pos(), adjustedSize()), NormalGeometrySet); else setGeometry(geometry(), NormalGeometrySet); m_pendingGeometryUpdate = PendingGeometryNone; } } } } void AbstractClient::maximize(MaximizeMode m) { setMaximize(m & MaximizeVertical, m & MaximizeHorizontal); } /*! Sets the maximization according to \a vertically and \a horizontally */ void AbstractClient::setMaximize(bool vertically, bool horizontally) { // changeMaximize() flips the state, so change from set->flip const MaximizeMode oldMode = maximizeMode(); changeMaximize( oldMode & MaximizeVertical ? !vertically : vertically, oldMode & MaximizeHorizontal ? !horizontally : horizontally, false); const MaximizeMode newMode = maximizeMode(); if (oldMode != newMode) { emit clientMaximizedStateChanged(this, newMode); emit clientMaximizedStateChanged(this, vertically, horizontally); } } // Update states of all other windows in this group class TabSynchronizer { public: TabSynchronizer(AbstractClient *client, TabGroup::States syncStates) : m_client(client) , m_states(syncStates) { if (client->tabGroup()) client->tabGroup()->blockStateUpdates(true); } ~TabSynchronizer() { syncNow(); } void syncNow() { if (m_client && m_client->tabGroup()) { m_client->tabGroup()->blockStateUpdates(false); m_client->tabGroup()->updateStates(dynamic_cast(m_client), m_states); } m_client = 0; } private: AbstractClient *m_client; TabGroup::States m_states; }; static bool changeMaximizeRecursion = false; void Client::changeMaximize(bool vertical, bool horizontal, bool adjust) { if (changeMaximizeRecursion) return; if (!isResizable() || isToolbar()) // SELI isToolbar() ? return; QRect clientArea; if (isElectricBorderMaximizing()) clientArea = workspace()->clientArea(MaximizeArea, Cursor::pos(), desktop()); else clientArea = workspace()->clientArea(MaximizeArea, this); 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); } // if the client insist on a fix aspect ratio, we check whether the maximizing will get us // out of screen bounds and take that as a "full maximization with aspect check" then if (m_geometryHints.hasAspect() && // fixed aspect (max_mode == MaximizeVertical || max_mode == MaximizeHorizontal) && // ondimensional maximization rules()->checkStrictGeometry(true)) { // obey aspect const QSize minAspect = m_geometryHints.minAspect(); const QSize maxAspect = m_geometryHints.maxAspect(); if (max_mode == MaximizeVertical || (old_mode & MaximizeVertical)) { const double fx = minAspect.width(); // use doubles, because the values can be MAX_INT const double fy = maxAspect.height(); // use doubles, because the values can be MAX_INT if (fx*clientArea.height()/fy > clientArea.width()) // too big max_mode = old_mode & MaximizeHorizontal ? MaximizeRestore : MaximizeFull; } else { // max_mode == MaximizeHorizontal const double fx = maxAspect.width(); const double fy = minAspect.height(); if (fy*clientArea.width()/fx > clientArea.height()) // too big max_mode = old_mode & MaximizeVertical ? MaximizeRestore : MaximizeFull; } } max_mode = rules()->checkMaximize(max_mode); if (!adjust && max_mode == old_mode) return; GeometryUpdatesBlocker blocker(this); // QT synchronizing required because we eventually change from QT to Maximized TabSynchronizer syncer(this, TabGroup::Maximized|TabGroup::QuickTile); // maximing one way and unmaximizing the other way shouldn't happen, // so restore first and then maximize the other way if ((old_mode == MaximizeVertical && max_mode == MaximizeHorizontal) || (old_mode == MaximizeHorizontal && max_mode == MaximizeVertical)) { changeMaximize(false, false, false); // restore } // save sizes for restoring, if maximalizing QSize sz; if (isShade()) sz = sizeForClientSize(clientSize()); else sz = size(); if (quickTileMode() == QuickTileNone) { if (!adjust && !(old_mode & MaximizeVertical)) { geom_restore.setTop(y()); geom_restore.setHeight(sz.height()); } if (!adjust && !(old_mode & MaximizeHorizontal)) { geom_restore.setLeft(x()); geom_restore.setWidth(sz.width()); } } // call into decoration update borders if (isDecorated() && decoration()->client() && !(options->borderlessMaximizedWindows() && max_mode == KWin::MaximizeFull)) { changeMaximizeRecursion = true; const auto c = decoration()->client().data(); if ((max_mode & MaximizeVertical) != (old_mode & MaximizeVertical)) { emit c->maximizedVerticallyChanged(max_mode & MaximizeVertical); } if ((max_mode & MaximizeHorizontal) != (old_mode & MaximizeHorizontal)) { emit c->maximizedHorizontallyChanged(max_mode & MaximizeHorizontal); } if ((max_mode == MaximizeFull) != (old_mode == MaximizeFull)) { emit c->maximizedChanged(max_mode & MaximizeFull); } changeMaximizeRecursion = false; } if (options->borderlessMaximizedWindows()) { // triggers a maximize change. // The next setNoBorder interation will exit since there's no change but the first recursion pullutes the restore geometry changeMaximizeRecursion = true; setNoBorder(rules()->checkNoBorder(app_noborder || (m_motif.hasDecoration() && m_motif.noBorder()) || max_mode == MaximizeFull)); changeMaximizeRecursion = false; } const ForceGeometry_t geom_mode = isDecorated() ? ForceGeometrySet : NormalGeometrySet; // Conditional quick tiling exit points if (quickTileMode() != QuickTileNone) { if (old_mode == MaximizeFull && !clientArea.contains(geom_restore.center())) { // Not restoring on the same screen // TODO: The following doesn't work for some reason //quick_tile_mode = QuickTileNone; // And exit quick tile mode manually } else if ((old_mode == MaximizeVertical && max_mode == MaximizeRestore) || (old_mode == MaximizeFull && max_mode == MaximizeHorizontal)) { // Modifying geometry of a tiled window updateQuickTileMode(QuickTileNone); // Exit quick tile mode without restoring geometry } } switch(max_mode) { case MaximizeVertical: { if (old_mode & MaximizeHorizontal) { // actually restoring from MaximizeFull if (geom_restore.width() == 0 || !clientArea.contains(geom_restore.center())) { // needs placement plainResize(adjustedSize(QSize(width() * 2 / 3, clientArea.height()), SizemodeFixedH), geom_mode); Placement::self()->placeSmart(this, clientArea); } else { setGeometry(QRect(QPoint(geom_restore.x(), clientArea.top()), adjustedSize(QSize(geom_restore.width(), clientArea.height()), SizemodeFixedH)), geom_mode); } } else { QRect r(x(), clientArea.top(), width(), clientArea.height()); r.setTopLeft(rules()->checkPosition(r.topLeft())); r.setSize(adjustedSize(r.size(), SizemodeFixedH)); setGeometry(r, 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); Placement::self()->placeSmart(this, clientArea); } else { setGeometry(QRect(QPoint(clientArea.left(), geom_restore.y()), adjustedSize(QSize(clientArea.width(), geom_restore.height()), SizemodeFixedW)), geom_mode); } } else { QRect r(clientArea.left(), y(), clientArea.width(), height()); r.setTopLeft(rules()->checkPosition(r.topLeft())); r.setSize(adjustedSize(r.size(), SizemodeFixedW)); setGeometry(r, 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)); Placement::self()->placeSmart(this, clientArea); restore = geometry(); if (geom_restore.width() > 0) restore.moveLeft(geom_restore.x()); if (geom_restore.height() > 0) restore.moveTop(geom_restore.y()); geom_restore = restore; // relevant for mouse pos calculation, bug #298646 } if (m_geometryHints.hasAspect()) { restore.setSize(adjustedSize(restore.size(), SizemodeAny)); } setGeometry(restore, geom_mode); if (!clientArea.contains(geom_restore.center())) // Not restoring to the same screen Placement::self()->place(this, clientArea); info->setState(0, NET::Max); updateQuickTileMode(QuickTileNone); break; } case MaximizeFull: { QRect r(clientArea); r.setTopLeft(rules()->checkPosition(r.topLeft())); r.setSize(adjustedSize(r.size(), SizemodeMax)); if (r.size() != clientArea.size()) { // to avoid off-by-one errors... if (isElectricBorderMaximizing() && r.width() < clientArea.width()) { r.moveLeft(qMax(clientArea.left(), Cursor::pos().x() - r.width()/2)); r.moveRight(qMin(clientArea.right(), r.right())); } else { r.moveCenter(clientArea.center()); const bool closeHeight = r.height() > 97*clientArea.height()/100; const bool closeWidth = r.width() > 97*clientArea.width() /100; const bool overHeight = r.height() > clientArea.height(); const bool overWidth = r.width() > clientArea.width(); if (closeWidth || closeHeight) { Position titlePos = titlebarPosition(); const QRect screenArea = workspace()->clientArea(ScreenArea, clientArea.center(), desktop()); if (closeHeight) { bool tryBottom = titlePos == PositionBottom; if ((overHeight && titlePos == PositionTop) || screenArea.top() == clientArea.top()) r.setTop(clientArea.top()); else tryBottom = true; if (tryBottom && (overHeight || screenArea.bottom() == clientArea.bottom())) r.setBottom(clientArea.bottom()); } if (closeWidth) { bool tryLeft = titlePos == PositionLeft; if ((overWidth && titlePos == PositionRight) || screenArea.right() == clientArea.right()) r.setRight(clientArea.right()); else tryLeft = true; if (tryLeft && (overWidth || screenArea.left() == clientArea.left())) r.setLeft(clientArea.left()); } } } r.moveTopLeft(rules()->checkPosition(r.topLeft())); } setGeometry(r, geom_mode); if (options->electricBorderMaximize() && r.top() == clientArea.top()) updateQuickTileMode(QuickTileMaximize); else updateQuickTileMode(QuickTileNone); info->setState(NET::Max, NET::Max); break; } default: break; } syncer.syncNow(); // important because of window rule updates! updateAllowedActions(); updateWindowRules(Rules::MaximizeVert|Rules::MaximizeHoriz|Rules::Position|Rules::Size); emit quickTileModeChanged(); } bool Client::isFullScreenable() const { return isFullScreenable(false); } bool Client::isFullScreenable(bool fullscreen_hack) const { if (!rules()->checkFullScreen(true)) return false; if (fullscreen_hack) return isNormalWindow(); if (rules()->checkStrictGeometry(true)) { // allow rule to ignore geometry constraints QRect fsarea = workspace()->clientArea(FullScreenArea, this); if (sizeForClientSize(fsarea.size(), SizemodeAny, true) != fsarea.size()) return false; // the app wouldn't fit exactly fullscreen geometry due to its strict geometry requirements } // 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; return isNormalWindow() || isDialog(); } void Client::setFullScreen(bool set, bool user) { if (!isFullScreen() && !set) return; if (fullscreen_mode == FullScreenHack) return; if (user && !userCanSetFullScreen()) return; set = rules()->checkFullScreen(set && !isSpecialWindow()); setShade(ShadeNone); bool was_fs = isFullScreen(); if (was_fs) workspace()->updateFocusMousePosition(Cursor::pos()); // may cause leave event else geom_fs_restore = geometry(); fullscreen_mode = set ? FullScreenNormal : FullScreenNone; if (was_fs == isFullScreen()) return; if (set) { untab(); workspace()->raiseClient(this); } StackingUpdatesBlocker blocker1(workspace()); GeometryUpdatesBlocker blocker2(this); workspace()->updateClientLayer(this); // active fullscreens get different layer info->setState(isFullScreen() ? NET::FullScreen : NET::States(0), NET::FullScreen); updateDecoration(false, false); if (isFullScreen()) { if (info->fullscreenMonitors().isSet()) setGeometry(fullscreenMonitorsArea(info->fullscreenMonitors())); else setGeometry(workspace()->clientArea(FullScreenArea, this)); } else { if (!geom_fs_restore.isNull()) { int currentScreen = screen(); setGeometry(QRect(geom_fs_restore.topLeft(), adjustedSize(geom_fs_restore.size()))); if( currentScreen != screen()) workspace()->sendClientToScreen( this, currentScreen ); // TODO isShaded() ? } else { // does this ever happen? setGeometry(workspace()->clientArea(MaximizeArea, this)); } } updateWindowRules(Rules::Fullscreen|Rules::Position|Rules::Size); if (was_fs != isFullScreen()) { emit clientFullScreenSet(this, set, user); emit fullScreenChanged(); } } void Client::updateFullscreenMonitors(NETFullscreenMonitors topology) { int nscreens = screens()->count(); // qDebug() << "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) { qCWarning(KWIN_CORE) << "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 = screens()->geometry(requestedTopology.top); bottom = screens()->geometry(requestedTopology.bottom); left = screens()->geometry(requestedTopology.left); right = screens()->geometry(requestedTopology.right); total = top.united(bottom.united(left.united(right))); // qDebug() << "top: " << top << " bottom: " << bottom // << " left: " << left << " right: " << right; // qDebug() << "returning rect: " << total; return total; } int Client::checkFullScreenHack(const QRect& geom) const { if (!options->isLegacyFullscreenSupport()) return 0; // if it's noborder window, and has size of one screen or the whole desktop geometry, it's fullscreen hack if (noBorder() && app_noborder && isFullScreenable(true)) { if (geom.size() == workspace()->clientArea(FullArea, geom.center(), desktop()).size()) return 2; // full area fullscreen hack if (geom.size() == workspace()->clientArea(ScreenArea, geom.center(), desktop()).size()) return 1; // xinerama-aware fullscreen hack } return 0; } void Client::updateFullScreenHack(const QRect& geom) { int type = checkFullScreenHack(geom); if (fullscreen_mode == FullScreenNone && type != 0) { fullscreen_mode = FullScreenHack; updateDecoration(false, false); QRect geom; if (rules()->checkStrictGeometry(false)) { geom = type == 2 // 1 - it's xinerama-aware fullscreen hack, 2 - it's full area ? workspace()->clientArea(FullArea, geom.center(), desktop()) : workspace()->clientArea(ScreenArea, geom.center(), desktop()); } else geom = workspace()->clientArea(FullScreenArea, geom.center(), desktop()); setGeometry(geom); emit fullScreenChanged(); } else if (fullscreen_mode == FullScreenHack && type == 0) { fullscreen_mode = FullScreenNone; updateDecoration(false, false); // whoever called this must setup correct geometry emit fullScreenChanged(); } StackingUpdatesBlocker blocker(workspace()); workspace()->updateClientLayer(this); // active fullscreens get different layer } static GeometryTip* geometryTip = 0; void Client::positionGeometryTip() { assert(isMove() || isResize()); // Position and Size display if (effects && static_cast(effects)->provides(Effect::GeometryTip)) return; // some effect paints this for us if (options->showGeometryTip()) { if (!geometryTip) { geometryTip = new GeometryTip(&m_geometryHints); } QRect wgeom(moveResizeGeometry()); // 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 AbstractClient::startMoveResize() { assert(!isMoveResize()); assert(QWidget::keyboardGrabber() == NULL); assert(QWidget::mouseGrabber() == NULL); stopDelayedMoveResize(); if (QApplication::activePopupWidget() != NULL) return false; // popups have grab if (isFullScreen() && (screens()->count() < 2 || !isMovableAcrossScreens())) return false; if (!doStartMoveResize()) { return false; } invalidateDecorationDoubleClickTimer(); setMoveResize(true); workspace()->setClientIsMoving(this); const Position mode = moveResizePointerMode(); if (mode != PositionCenter) { // means "isResize()" but moveResizeMode = true is set below if (maximizeMode() == MaximizeFull) { // partial is cond. reset in finishMoveResize setGeometryRestore(geometry()); // "restore" to current geometry setMaximize(false, false); } } if (quickTileMode() != QuickTileNone && mode != PositionCenter) { // Cannot use isResize() yet // Exit quick tile mode when the user attempts to resize a tiled window updateQuickTileMode(QuickTileNone); // Do so without restoring original geometry setGeometryRestore(geometry()); emit quickTileModeChanged(); } updateHaveResizeEffect(); updateInitialMoveResizeGeometry(); checkUnrestrictedMoveResize(); emit clientStartUserMovedResized(this); if (ScreenEdges::self()->isDesktopSwitchingMovingClients()) ScreenEdges::self()->reserveDesktopSwitching(true, Qt::Vertical|Qt::Horizontal); return true; } bool Client::doStartMoveResize() { 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) QRect r = workspace()->clientArea(FullArea, this); m_moveResizeGrabWindow.create(r, XCB_WINDOW_CLASS_INPUT_ONLY, 0, NULL, rootWindow()); m_moveResizeGrabWindow.map(); m_moveResizeGrabWindow.raise(); updateXTime(); const xcb_grab_pointer_cookie_t cookie = xcb_grab_pointer_unchecked(connection(), false, m_moveResizeGrabWindow, XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, m_moveResizeGrabWindow, Cursor::x11Cursor(cursor()), xTime()); ScopedCPointer pointerGrab(xcb_grab_pointer_reply(connection(), cookie, NULL)); if (!pointerGrab.isNull() && pointerGrab->status == XCB_GRAB_STATUS_SUCCESS) { has_grab = true; } if (!has_grab && 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 m_moveResizeGrabWindow.reset(); return false; } return true; } void AbstractClient::finishMoveResize(bool cancel) { const bool wasResize = isResize(); // store across leaveMoveResize leaveMoveResize(); if (cancel) setGeometry(initialMoveResizeGeometry()); else { const QRect &moveResizeGeom = moveResizeGeometry(); if (wasResize) { const bool restoreH = maximizeMode() == MaximizeHorizontal && moveResizeGeom.width() != initialMoveResizeGeometry().width(); const bool restoreV = maximizeMode() == MaximizeVertical && moveResizeGeom.height() != initialMoveResizeGeometry().height(); if (restoreH || restoreV) { changeMaximize(restoreV, restoreH, false); } } setGeometry(moveResizeGeom); } checkScreen(); // needs to be done because clientFinishUserMovedResized has not yet re-activated online alignment if (screen() != moveResizeStartScreen()) { workspace()->sendClientToScreen(this, screen()); // checks rule validity if (maximizeMode() != MaximizeRestore) checkWorkspacePosition(); } if (isElectricBorderMaximizing()) { setQuickTileMode(electricBorderMode()); setElectricBorderMaximizing(false); } else if (!cancel) { QRect geom_restore = geometryRestore(); if (!(maximizeMode() & MaximizeHorizontal)) { geom_restore.setX(geometry().x()); geom_restore.setWidth(geometry().width()); } if (!(maximizeMode() & MaximizeVertical)) { geom_restore.setY(geometry().y()); geom_restore.setHeight(geometry().height()); } setGeometryRestore(geom_restore); } // FRAME update(); emit clientFinishUserMovedResized(this); } void Client::leaveMoveResize() { if (needsXWindowMove) { // Do the deferred move m_frame.move(geom.topLeft()); needsXWindowMove = false; } if (!isResize()) sendSyntheticConfigureNotify(); // tell the client about it's new final position if (geometryTip) { geometryTip->hide(); delete geometryTip; geometryTip = NULL; } if (move_resize_has_keyboard_grab) ungrabXKeyboard(); move_resize_has_keyboard_grab = false; xcb_ungrab_pointer(connection(), xTime()); m_moveResizeGrabWindow.reset(); if (syncRequest.counter == XCB_NONE) // don't forget to sanitize since the timeout will no more fire syncRequest.isPending = false; delete syncRequest.timeout; syncRequest.timeout = NULL; AbstractClient::leaveMoveResize(); } // 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 AbstractClient::checkUnrestrictedMoveResize() { if (isUnrestrictedMoveResize()) return; const QRect &moveResizeGeom = moveResizeGeometry(); 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 + borderRight(), moveResizeGeom.width()); right_marge = qMin(100 + borderLeft(), moveResizeGeom.width()); // width/height change with opaque resizing, use the initial ones titlebar_marge = initialMoveResizeGeometry().height(); top_marge = borderBottom(); bottom_marge = borderTop(); if (isResize()) { if (moveResizeGeom.bottom() < desktopArea.top() + top_marge) setUnrestrictedMoveResize(true); if (moveResizeGeom.top() > desktopArea.bottom() - bottom_marge) setUnrestrictedMoveResize(true); if (moveResizeGeom.right() < desktopArea.left() + left_marge) setUnrestrictedMoveResize(true); if (moveResizeGeom.left() > desktopArea.right() - right_marge) setUnrestrictedMoveResize(true); if (!isUnrestrictedMoveResize() && moveResizeGeom.top() < desktopArea.top()) // titlebar mustn't go out setUnrestrictedMoveResize(true); } if (isMove()) { if (moveResizeGeom.bottom() < desktopArea.top() + titlebar_marge - 1) setUnrestrictedMoveResize(true); // no need to check top_marge, titlebar_marge already handles it if (moveResizeGeom.top() > desktopArea.bottom() - bottom_marge + 1) // titlebar mustn't go out setUnrestrictedMoveResize(true); if (moveResizeGeom.right() < desktopArea.left() + left_marge) setUnrestrictedMoveResize(true); if (moveResizeGeom.left() > desktopArea.right() - right_marge) setUnrestrictedMoveResize(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 AbstractClient::startDelayedMoveResize() { Q_ASSERT(!m_moveResize.delayedTimer); m_moveResize.delayedTimer = new QTimer(this); m_moveResize.delayedTimer->setSingleShot(true); connect(m_moveResize.delayedTimer, &QTimer::timeout, this, [this]() { assert(isMoveResizePointerButtonDown()); if (!startMoveResize()) { setMoveResizePointerButtonDown(false); } updateCursor(); stopDelayedMoveResize(); } ); m_moveResize.delayedTimer->start(QApplication::startDragTime()); } void AbstractClient::stopDelayedMoveResize() { delete m_moveResize.delayedTimer; m_moveResize.delayedTimer = nullptr; } void AbstractClient::handleMoveResize(const QPoint &local, const QPoint &global) { const QRect oldGeo = geometry(); handleMoveResize(local.x(), local.y(), global.x(), global.y()); if (!isFullScreen() && isMove()) { if (quickTileMode() != QuickTileNone && oldGeo != geometry()) { GeometryUpdatesBlocker blocker(this); setQuickTileMode(QuickTileNone); const QRect &geom_restore = geometryRestore(); setMoveOffset(QPoint(double(moveOffset().x()) / double(oldGeo.width()) * double(geom_restore.width()), double(moveOffset().y()) / double(oldGeo.height()) * double(geom_restore.height()))); if (rules()->checkMaximize(MaximizeRestore) == MaximizeRestore) setMoveResizeGeometry(geom_restore); handleMoveResize(local.x(), local.y(), global.x(), global.y()); // fix position } else if (quickTileMode() == QuickTileNone && isResizable()) { checkQuickTilingMaximizationZones(global.x(), global.y()); } } } bool Client::isWaitingForMoveResizeSync() const { return syncRequest.isPending && isResize(); } void AbstractClient::handleMoveResize(int x, int y, int x_root, int y_root) { if (isWaitingForMoveResizeSync()) return; // we're still waiting for the client or the timeout const Position mode = moveResizePointerMode(); if ((mode == PositionCenter && !isMovableAcrossScreens()) || (mode != PositionCenter && (isShade() || !isResizable()))) return; if (!isMoveResize()) { QPoint p(QPoint(x/* - padding_left*/, y/* - padding_top*/) - moveOffset()); if (p.manhattanLength() >= QApplication::startDragDistance()) { if (!startMoveResize()) { setMoveResizePointerButtonDown(false); updateCursor(); return; } updateCursor(); } else return; } // ShadeHover or ShadeActive, ShadeNormal was already avoided above if (mode != PositionCenter && shadeMode() != 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 = moveResizeGeometry(); // TODO move whole group when moving its leader or when the leader is not mapped? auto titleBarRect = [this](bool &transposed, int &requiredPixels) -> QRect { const QRect &moveResizeGeom = moveResizeGeometry(); QRect r(moveResizeGeom); r.moveTopLeft(QPoint(0,0)); switch (titlebarPosition()) { default: case PositionTop: r.setHeight(borderTop()); break; case PositionLeft: r.setWidth(borderLeft()); transposed = true; break; case PositionBottom: r.setTop(r.bottom() - borderBottom()); break; case PositionRight: r.setLeft(r.right() - borderRight()); transposed = true; break; } // 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. requiredPixels = qMin(100 * (transposed ? r.width() : r.height()), moveResizeGeom.width() * moveResizeGeom.height()); return r; }; bool update = false; if (isResize()) { QRect orig = initialMoveResizeGeometry(); Sizemode sizemode = SizemodeAny; auto calculateMoveResizeGeom = [this, &topleft, &bottomright, &orig, &sizemode, &mode]() { switch(mode) { case PositionTopLeft: setMoveResizeGeometry(QRect(topleft, orig.bottomRight())); break; case PositionBottomRight: setMoveResizeGeometry(QRect(orig.topLeft(), bottomright)); break; case PositionBottomLeft: setMoveResizeGeometry(QRect(QPoint(topleft.x(), orig.y()), QPoint(orig.right(), bottomright.y()))); break; case PositionTopRight: setMoveResizeGeometry(QRect(QPoint(orig.x(), topleft.y()), QPoint(bottomright.x(), orig.bottom()))); break; case PositionTop: setMoveResizeGeometry(QRect(QPoint(orig.left(), topleft.y()), orig.bottomRight())); sizemode = SizemodeFixedH; // try not to affect height break; case PositionBottom: setMoveResizeGeometry(QRect(orig.topLeft(), QPoint(orig.right(), bottomright.y()))); sizemode = SizemodeFixedH; break; case PositionLeft: setMoveResizeGeometry(QRect(QPoint(topleft.x(), orig.top()), orig.bottomRight())); sizemode = SizemodeFixedW; break; case PositionRight: setMoveResizeGeometry(QRect(orig.topLeft(), QPoint(bottomright.x(), orig.bottom()))); sizemode = SizemodeFixedW; break; case PositionCenter: default: abort(); break; } }; // first resize (without checking constrains), then snap, then check bounds, then check constrains calculateMoveResizeGeom(); // adjust new size to snap to other windows/borders setMoveResizeGeometry(workspace()->adjustClientSize(this, moveResizeGeometry(), mode)); if (!isUnrestrictedMoveResize()) { // 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 QRegion availableArea(workspace()->clientArea(FullArea, -1, 0)); // On the screen availableArea -= workspace()->restrictedMoveArea(desktop()); // Strut areas bool transposed = false; int requiredPixels; QRect bTitleRect = titleBarRect(transposed, requiredPixels); int lastVisiblePixels = -1; QRect lastTry = moveResizeGeometry(); bool titleFailed = false; for (;;) { const QRect titleRect(bTitleRect.translated(moveResizeGeometry().topLeft())); int visiblePixels = 0; int realVisiblePixels = 0; foreach (const QRect &rect, availableArea.rects()) { const QRect r = rect & titleRect; realVisiblePixels += r.width() * r.height(); if ((transposed && r.width() == titleRect.width()) || // Only the full size regions... (!transposed && r.height() == titleRect.height())) // ...prevents long slim areas visiblePixels += r.width() * r.height(); } if (visiblePixels >= requiredPixels) break; // We have reached a valid position if (realVisiblePixels <= lastVisiblePixels) { if (titleFailed && realVisiblePixels < lastVisiblePixels) break; // we won't become better else { if (!titleFailed) setMoveResizeGeometry(lastTry); titleFailed = true; } } lastVisiblePixels = realVisiblePixels; QRect moveResizeGeom = moveResizeGeometry(); lastTry = moveResizeGeom; // Not visible enough, move the window to the closest valid point. We bruteforce // this by slowly moving the window back to its previous position. // The geometry changes at up to two edges, the one with the title (if) shall take // precedence. The opposing edge has no impact on visiblePixels and only one of // the adjacent can alter at a time, ie. it's enough to ignore adjacent edges // if the title edge altered bool leftChanged = previousMoveResizeGeom.left() != moveResizeGeom.left(); bool rightChanged = previousMoveResizeGeom.right() != moveResizeGeom.right(); bool topChanged = previousMoveResizeGeom.top() != moveResizeGeom.top(); bool btmChanged = previousMoveResizeGeom.bottom() != moveResizeGeom.bottom(); auto fixChangedState = [titleFailed](bool &major, bool &counter, bool &ad1, bool &ad2) { counter = false; if (titleFailed) major = false; if (major) ad1 = ad2 = false; }; switch (titlebarPosition()) { default: case PositionTop: fixChangedState(topChanged, btmChanged, leftChanged, rightChanged); break; case PositionLeft: fixChangedState(leftChanged, rightChanged, topChanged, btmChanged); break; case PositionBottom: fixChangedState(btmChanged, topChanged, leftChanged, rightChanged); break; case PositionRight: fixChangedState(rightChanged, leftChanged, topChanged, btmChanged); break; } if (topChanged) moveResizeGeom.setTop(moveResizeGeom.y() + sign(previousMoveResizeGeom.y() - moveResizeGeom.y())); else if (leftChanged) moveResizeGeom.setLeft(moveResizeGeom.x() + sign(previousMoveResizeGeom.x() - moveResizeGeom.x())); else if (btmChanged) moveResizeGeom.setBottom(moveResizeGeom.bottom() + sign(previousMoveResizeGeom.bottom() - moveResizeGeom.bottom())); else if (rightChanged) moveResizeGeom.setRight(moveResizeGeom.right() + sign(previousMoveResizeGeom.right() - moveResizeGeom.right())); else break; // no position changed - that's certainly not good setMoveResizeGeometry(moveResizeGeom); } } // Always obey size hints, even when in "unrestricted" mode QSize size = adjustedSize(moveResizeGeometry().size(), sizemode); // the new topleft and bottomright corners (after checking size constrains), if they'll be needed topleft = QPoint(moveResizeGeometry().right() - size.width() + 1, moveResizeGeometry().bottom() - size.height() + 1); bottomright = QPoint(moveResizeGeometry().left() + size.width() - 1, moveResizeGeometry().top() + size.height() - 1); orig = moveResizeGeometry(); // 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 ? if (sizemode == SizemodeFixedH) orig.setRight(bottomright.x()); else if (sizemode == SizemodeFixedW) orig.setBottom(bottomright.y()); calculateMoveResizeGeom(); if (moveResizeGeometry().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 = screens()->number(globalPos); if (isFullScreen()) setMoveResizeGeometry(workspace()->clientArea(FullScreenArea, screen, 0)); else { QRect 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()); } setMoveResizeGeometry(moveResizeGeom); } } else { // first move, then snap, then check bounds QRect moveResizeGeom = moveResizeGeometry(); moveResizeGeom.moveTopLeft(topleft); moveResizeGeom.moveTopLeft(workspace()->adjustClientPosition(this, moveResizeGeom.topLeft(), isUnrestrictedMoveResize())); setMoveResizeGeometry(moveResizeGeom); if (!isUnrestrictedMoveResize()) { const QRegion strut = workspace()->restrictedMoveArea(desktop()); // Strut areas QRegion availableArea(workspace()->clientArea(FullArea, -1, 0)); // On the screen availableArea -= strut; // Strut areas bool transposed = false; int requiredPixels; QRect bTitleRect = titleBarRect(transposed, requiredPixels); for (;;) { QRect moveResizeGeom = moveResizeGeometry(); const QRect titleRect(bTitleRect.translated(moveResizeGeom.topLeft())); int visiblePixels = 0; foreach (const QRect &rect, availableArea.rects()) { const QRect r = rect & titleRect; if ((transposed && r.width() == titleRect.width()) || // Only the full size regions... (!transposed && r.height() == titleRect.height())) // ...prevents long slim areas visiblePixels += r.width() * r.height(); } if (visiblePixels >= requiredPixels) break; // We have reached a valid position // (esp.) if there're more screens with different struts (panels) it the titlebar // will be movable outside the movearea (covering one of the panels) until it // crosses the panel "too much" (not enough visiblePixels) and then stucks because // it's usually only pushed by 1px to either direction // so we first check whether we intersect suc strut and move the window below it // immediately (it's still possible to hit the visiblePixels >= titlebarArea break // by moving the window slightly downwards, but it won't stuck) // see bug #274466 // and bug #301805 for why we can't just match the titlearea against the screen if (screens()->count() > 1) { // optimization // TODO: could be useful on partial screen struts (half-width panels etc.) int newTitleTop = -1; foreach (const QRect &r, strut.rects()) { if (r.top() == 0 && r.width() > r.height() && // "top panel" r.intersects(moveResizeGeom) && moveResizeGeom.top() < r.bottom()) { newTitleTop = r.bottom() + 1; break; } } if (newTitleTop > -1) { moveResizeGeom.moveTop(newTitleTop); // invalid position, possibly on screen change setMoveResizeGeometry(moveResizeGeom); break; } } int dx = sign(previousMoveResizeGeom.x() - moveResizeGeom.x()), dy = sign(previousMoveResizeGeom.y() - moveResizeGeom.y()); if (visiblePixels && dx) // means there's no full width cap -> favor horizontally dy = 0; else if (dy) dx = 0; // Move it back moveResizeGeom.translate(dx, dy); setMoveResizeGeometry(moveResizeGeom); if (moveResizeGeom == previousMoveResizeGeom) { break; // Prevent lockup } } } } if (moveResizeGeometry().topLeft() != previousMoveResizeGeom.topLeft()) update = true; } else abort(); if (!update) return; if (isResize() && !haveResizeEffect()) { doResizeSync(); } else performMoveResize(); if (isMove()) { ScreenEdges::self()->check(globalPos, QDateTime::fromMSecsSinceEpoch(xTime())); } } void Client::doResizeSync() { if (!syncRequest.timeout) { syncRequest.timeout = new QTimer(this); connect(syncRequest.timeout, &QTimer::timeout, this, &Client::performMoveResize); syncRequest.timeout->setSingleShot(true); } if (syncRequest.counter != XCB_NONE) { syncRequest.timeout->start(250); sendSyncRequest(); } else { // for clients not supporting the XSYNC protocol, we syncRequest.isPending = true; // limit the resizes to 30Hz to take pointless load from X11 syncRequest.timeout->start(33); // and the client, the mouse is still moved at full speed } // and no human can control faster resizes anyway const QRect &moveResizeGeom = moveResizeGeometry(); m_client.setGeometry(0, 0, moveResizeGeom.width() - (borderLeft() + borderRight()), moveResizeGeom.height() - (borderTop() + borderBottom())); } void AbstractClient::performMoveResize() { const QRect &moveResizeGeom = moveResizeGeometry(); if (isMove() || (isResize() && !haveResizeEffect())) { setGeometry(moveResizeGeom); } doPerformMoveResize(); if (isResize()) addRepaintFull(); positionGeometryTip(); emit clientStepUserMovedResized(this, moveResizeGeom); } void Client::doPerformMoveResize() { if (syncRequest.counter == XCB_NONE) // client w/o XSYNC support. allow the next resize event syncRequest.isPending = false; // NEVER do this for clients with a valid counter // (leads to sync request races in some clients) } void AbstractClient::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; } m_electricMode = mode; } void AbstractClient::setElectricBorderMaximizing(bool maximizing) { m_electricMaximizing = maximizing; if (maximizing) outline()->show(electricBorderMaximizeGeometry(Cursor::pos(), desktop())); else outline()->hide(); elevate(maximizing); } QRect AbstractClient::electricBorderMaximizeGeometry(QPoint pos, int desktop) { if (electricBorderMode() == QuickTileMaximize) { if (maximizeMode() == MaximizeFull) return geometryRestore(); else return workspace()->clientArea(MaximizeArea, pos, desktop); } QRect ret = workspace()->clientArea(MaximizeArea, pos, desktop); if (electricBorderMode() & QuickTileLeft) ret.setRight(ret.left()+ret.width()/2 - 1); else if (electricBorderMode() & QuickTileRight) ret.setLeft(ret.right()-(ret.width()-ret.width()/2) + 1); if (electricBorderMode() & QuickTileTop) ret.setBottom(ret.top()+ret.height()/2 - 1); else if (electricBorderMode() & QuickTileBottom) ret.setTop(ret.bottom()-(ret.height()-ret.height()/2) + 1); return ret; } void AbstractClient::setQuickTileMode(QuickTileMode mode, bool keyboard) { // Only allow quick tile on a regular or maximized window if (!isResizable() && maximizeMode() != MaximizeFull) return; workspace()->updateFocusMousePosition(Cursor::pos()); // may cause leave event GeometryUpdatesBlocker blocker(this); if (mode == QuickTileMaximize) { TabSynchronizer syncer(this, TabGroup::QuickTile|TabGroup::Geometry|TabGroup::Maximized); m_quickTileMode = QuickTileNone; if (maximizeMode() == MaximizeFull) { setMaximize(false, false); } else { QRect prev_geom_restore = geometryRestore(); // setMaximize() would set moveResizeGeom as geom_restore setMaximize(true, true); QRect clientArea = workspace()->clientArea(MaximizeArea, this); if (geometry().top() != clientArea.top()) { QRect r(geometry()); r.moveTop(clientArea.top()); setGeometry(r); } m_quickTileMode = QuickTileMaximize; setGeometryRestore(prev_geom_restore); } emit quickTileModeChanged(); 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() != MaximizeRestore) { TabSynchronizer syncer(this, TabGroup::QuickTile|TabGroup::Geometry|TabGroup::Maximized); setMaximize(false, false); if (mode != QuickTileNone) { m_quickTileMode = mode; // decorations may turn off some borders when tiled const ForceGeometry_t geom_mode = isDecorated() ? ForceGeometrySet : NormalGeometrySet; m_quickTileMode = QuickTileNone; // Temporary, so the maximize code doesn't get all confused setGeometry(electricBorderMaximizeGeometry(keyboard ? geometry().center() : Cursor::pos(), desktop()), geom_mode); } // Store the mode change m_quickTileMode = mode; emit quickTileModeChanged(); return; } if (mode != QuickTileNone) { TabSynchronizer syncer(this, TabGroup::QuickTile|TabGroup::Geometry); QPoint whichScreen = keyboard ? geometry().center() : Cursor::pos(); // 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 toggle the mode (set QuickTileNone) if (quickTileMode() == mode) { const int numScreens = screens()->count(); const int curScreen = screen(); int nextScreen = curScreen; QVarLengthArray screens(numScreens); for (int i = 0; i < numScreens; ++i) // Cache screens[i] = Screens::self()->geometry(i); for (int i = 0; i < numScreens; ++i) { if (i == curScreen) continue; if (screens[i].bottom() <= screens[curScreen].top() || screens[i].top() >= screens[curScreen].bottom()) continue; // not in horizontal line const int x = screens[i].center().x(); if ((mode & QuickTileHorizontal) == QuickTileLeft) { if (x >= screens[curScreen].center().x() || (curScreen != nextScreen && x <= screens[nextScreen].center().x())) continue; // not left of current or more left then found next } else if ((mode & QuickTileHorizontal) == QuickTileRight) { if (x <= screens[curScreen].center().x() || (curScreen != nextScreen && x >= screens[nextScreen].center().x())) continue; // not right of current or more right then found next } nextScreen = i; } if (nextScreen == curScreen) { mode = QuickTileNone; // No other screens, toggle tiling } else { // Move to other screen setGeometry(geometryRestore().translated(screens[nextScreen].topLeft() - screens[curScreen].topLeft())); whichScreen = screens[nextScreen].center(); // Swap sides mode = ~mode & QuickTileHorizontal; } setElectricBorderMode(mode); // used by ::electricBorderMaximizeGeometry(.) } else if (quickTileMode() == QuickTileNone) { // 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. setGeometryRestore(geometry()); } if (mode != QuickTileNone) { m_quickTileMode = mode; // decorations may turn off some borders when tiled const ForceGeometry_t geom_mode = isDecorated() ? ForceGeometrySet : NormalGeometrySet; // Temporary, so the maximize code doesn't get all confused m_quickTileMode = QuickTileNone; setGeometry(electricBorderMaximizeGeometry(whichScreen, desktop()), geom_mode); } // Store the mode change m_quickTileMode = mode; } if (mode == QuickTileNone) { TabSynchronizer syncer(this, TabGroup::QuickTile|TabGroup::Geometry); m_quickTileMode = QuickTileNone; // Untiling, so just restore geometry, and we're done. if (!geometryRestore().isValid()) // invalid if we started maximized and wait for placement setGeometryRestore(geometry()); // decorations may turn off some borders when tiled const ForceGeometry_t geom_mode = isDecorated() ? ForceGeometrySet : NormalGeometrySet; setGeometry(geometryRestore(), geom_mode); checkWorkspacePosition(); // Just in case it's a different screen } emit quickTileModeChanged(); } void AbstractClient::sendToScreen(int newScreen) { newScreen = rules()->checkScreen(newScreen); if (isActive()) { screens()->setCurrent(newScreen); // might impact the layer of a fullscreen window foreach (AbstractClient *cc, workspace()->allClientList()) { if (cc->isFullScreen() && cc->screen() == newScreen) { cc->updateLayer(); } } } if (screen() == newScreen) // Don't use isOnScreen(), that's true even when only partially return; GeometryUpdatesBlocker blocker(this); // operating on the maximized / quicktiled window would leave the old geom_restore behind, // so we clear the state first MaximizeMode maxMode = maximizeMode(); QuickTileMode qtMode = quickTileMode(); if (maxMode != MaximizeRestore) maximize(MaximizeRestore); if (qtMode != QuickTileNone) setQuickTileMode(QuickTileNone, true); QRect oldScreenArea = workspace()->clientArea(MaximizeArea, this); QRect screenArea = workspace()->clientArea(MaximizeArea, newScreen, desktop()); // the window can have its center so that the position correction moves the new center onto // the old screen, what will tile it where it is. Ie. the screen is not changed // this happens esp. with electric border quicktiling if (qtMode != QuickTileNone) keepInArea(oldScreenArea); QRect oldGeom = geometry(); QRect newGeom = oldGeom; // move the window to have the same relative position to the center of the screen // (i.e. one near the middle of the right edge will also end up near the middle of the right edge) QPoint center = newGeom.center() - oldScreenArea.center(); center.setX(center.x() * screenArea.width() / oldScreenArea.width()); center.setY(center.y() * screenArea.height() / oldScreenArea.height()); center += screenArea.center(); newGeom.moveCenter(center); setGeometry(newGeom); // If the window was inside the old screen area, explicitly make sure its inside also the new screen area. // Calling checkWorkspacePosition() should ensure that, but when moving to a small screen the window could // be big enough to overlap outside of the new screen area, making struts from other screens come into effect, // which could alter the resulting geometry. if (oldScreenArea.contains(oldGeom)) { keepInArea(screenArea); } // align geom_restore - checkWorkspacePosition operates on it setGeometryRestore(geometry()); checkWorkspacePosition(oldGeom); // re-align geom_restore to constrained geometry setGeometryRestore(geometry()); // finally reset special states // NOTICE that MaximizeRestore/QuickTileNone checks are required. // eg. setting QuickTileNone would break maximization if (maxMode != MaximizeRestore) maximize(maxMode); if (qtMode != QuickTileNone && qtMode != quickTileMode()) setQuickTileMode(qtMode, true); auto tso = workspace()->ensureStackingOrder(transients()); for (auto it = tso.constBegin(), end = tso.constEnd(); it != end; ++it) (*it)->sendToScreen(newScreen); } } // namespace diff --git a/kcmkwin/kwincompositing/compositing.cpp b/kcmkwin/kwincompositing/compositing.cpp index 1e02f778f..76ce6590c 100644 --- a/kcmkwin/kwincompositing/compositing.cpp +++ b/kcmkwin/kwincompositing/compositing.cpp @@ -1,541 +1,522 @@ /************************************************************************** * KWin - the KDE window manager * * This file is part of the KDE project. * * * * Copyright (C) 2013 Antonis Tsiapaliokas * * Copyright (C) 2013 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 . * **************************************************************************/ #include "compositing.h" #include #include #include #include #include #include #include #include #include namespace KWin { namespace Compositing { Compositing::Compositing(QObject *parent) : QObject(parent) , m_animationSpeed(0) , m_windowThumbnail(0) , m_glScaleFilter(0) , m_xrScaleFilter(false) - , m_unredirectFullscreen(false) , m_glSwapStrategy(0) , m_glColorCorrection(false) , m_compositingType(0) , m_compositingEnabled(true) , m_changed(false) , m_openGLPlatformInterfaceModel(new OpenGLPlatformInterfaceModel(this)) , m_openGLPlatformInterface(0) { reset(); connect(this, &Compositing::animationSpeedChanged, this, &Compositing::changed); connect(this, &Compositing::windowThumbnailChanged, this, &Compositing::changed); connect(this, &Compositing::glScaleFilterChanged, this, &Compositing::changed); connect(this, &Compositing::xrScaleFilterChanged, this, &Compositing::changed); - connect(this, &Compositing::unredirectFullscreenChanged, this, &Compositing::changed); connect(this, &Compositing::glSwapStrategyChanged, this, &Compositing::changed); connect(this, &Compositing::glColorCorrectionChanged, this, &Compositing::changed); connect(this, &Compositing::compositingTypeChanged, this, &Compositing::changed); connect(this, &Compositing::compositingEnabledChanged, this, &Compositing::changed); connect(this, &Compositing::openGLPlatformInterfaceChanged, this, &Compositing::changed); connect(this, &Compositing::changed, [this]{ m_changed = true; }); } void Compositing::reset() { KConfigGroup kwinConfig(KSharedConfig::openConfig(QStringLiteral("kwinrc")), QStringLiteral("Compositing")); setAnimationSpeed(kwinConfig.readEntry("AnimationSpeed", 3)); setWindowThumbnail(kwinConfig.readEntry("HiddenPreviews", 5) - 4); setGlScaleFilter(kwinConfig.readEntry("GLTextureFilter", 2)); setXrScaleFilter(kwinConfig.readEntry("XRenderSmoothScale", false)); - setUnredirectFullscreen(kwinConfig.readEntry("UnredirectFullscreen", false)); setCompositingEnabled(kwinConfig.readEntry("Enabled", true)); auto swapStrategy = [&kwinConfig]() { const QString glSwapStrategyValue = kwinConfig.readEntry("GLPreferBufferSwap", "a"); if (glSwapStrategyValue == "n") { return 0; } else if (glSwapStrategyValue == "a") { return 1; } else if (glSwapStrategyValue == "e") { return 2; } else if (glSwapStrategyValue == "p") { return 3; } else if (glSwapStrategyValue == "c") { return 4; } return 0; }; setGlSwapStrategy(swapStrategy()); setGlColorCorrection(kwinConfig.readEntry("GLColorCorrection", false)); auto type = [&kwinConfig]{ const QString backend = kwinConfig.readEntry("Backend", "OpenGL"); const bool glCore = kwinConfig.readEntry("GLCore", false); if (backend == QStringLiteral("OpenGL")) { if (glCore) { return CompositingType::OPENGL31_INDEX; } else { return CompositingType::OPENGL20_INDEX; } } else { return CompositingType::XRENDER_INDEX; } }; setCompositingType(type()); const QModelIndex index = m_openGLPlatformInterfaceModel->indexForKey(kwinConfig.readEntry("GLPlatformInterface", "glx")); setOpenGLPlatformInterface(index.isValid() ? index.row() : 0); m_changed = false; } void Compositing::defaults() { setAnimationSpeed(3); setWindowThumbnail(1); setGlScaleFilter(2); setXrScaleFilter(false); - setUnredirectFullscreen(false); setGlSwapStrategy(1); setGlColorCorrection(false); setCompositingType(CompositingType::OPENGL20_INDEX); const QModelIndex index = m_openGLPlatformInterfaceModel->indexForKey(QStringLiteral("glx")); setOpenGLPlatformInterface(index.isValid() ? index.row() : 0); m_changed = true; } bool Compositing::OpenGLIsUnsafe() const { KConfigGroup kwinConfig(KSharedConfig::openConfig("kwinrc"), "Compositing"); return kwinConfig.readEntry("OpenGLIsUnsafe", true); } bool Compositing::OpenGLIsBroken() { OrgKdeKwinCompositingInterface interface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Compositor"), QDBusConnection::sessionBus()); KConfigGroup kwinConfig(KSharedConfig::openConfig("kwinrc"), "Compositing"); QString oldBackend = kwinConfig.readEntry("Backend", "OpenGL"); kwinConfig.writeEntry("Backend", "OpenGL"); kwinConfig.sync(); if (interface.openGLIsBroken()) { kwinConfig.writeEntry("Backend", oldBackend); kwinConfig.sync(); return true; } kwinConfig.writeEntry("OpenGLIsUnsafe", false); kwinConfig.sync(); return false; } void Compositing::reenableOpenGLDetection() { KConfigGroup kwinConfig(KSharedConfig::openConfig("kwinrc"), "Compositing"); kwinConfig.writeEntry("OpenGLIsUnsafe", false); kwinConfig.sync(); } int Compositing::animationSpeed() const { return m_animationSpeed; } int Compositing::windowThumbnail() const { return m_windowThumbnail; } int Compositing::glScaleFilter() const { return m_glScaleFilter; } bool Compositing::xrScaleFilter() const { return m_xrScaleFilter; } -bool Compositing::unredirectFullscreen() const -{ - return m_unredirectFullscreen; -} - int Compositing::glSwapStrategy() const { return m_glSwapStrategy; } bool Compositing::glColorCorrection() const { return m_glColorCorrection; } int Compositing::compositingType() const { return m_compositingType; } bool Compositing::compositingEnabled() const { return m_compositingEnabled; } void Compositing::setAnimationSpeed(int speed) { if (speed == m_animationSpeed) { return; } m_animationSpeed = speed; emit animationSpeedChanged(speed); } void Compositing::setGlColorCorrection(bool correction) { if (correction == m_glColorCorrection) { return; } m_glColorCorrection = correction; emit glColorCorrectionChanged(correction); } void Compositing::setGlScaleFilter(int index) { if (index == m_glScaleFilter) { return; } m_glScaleFilter = index; emit glScaleFilterChanged(index); } void Compositing::setGlSwapStrategy(int strategy) { if (strategy == m_glSwapStrategy) { return; } m_glSwapStrategy = strategy; emit glSwapStrategyChanged(strategy); } -void Compositing::setUnredirectFullscreen(bool unredirect) -{ - if (unredirect == m_unredirectFullscreen) { - return; - } - m_unredirectFullscreen = unredirect; - emit unredirectFullscreenChanged(unredirect); -} - void Compositing::setWindowThumbnail(int index) { if (index == m_windowThumbnail) { return; } m_windowThumbnail = index; emit windowThumbnailChanged(index); } void Compositing::setXrScaleFilter(bool filter) { if (filter == m_xrScaleFilter) { return; } m_xrScaleFilter = filter; emit xrScaleFilterChanged(filter); } void Compositing::setCompositingType(int index) { if (index == m_compositingType) { return; } m_compositingType = index; emit compositingTypeChanged(index); } void Compositing::setCompositingEnabled(bool enabled) { if (enabled == m_compositingEnabled) { return; } m_compositingEnabled = enabled; emit compositingEnabledChanged(enabled); } void Compositing::save() { KConfigGroup kwinConfig(KSharedConfig::openConfig(QStringLiteral("kwinrc")), "Compositing"); kwinConfig.writeEntry("AnimationSpeed", animationSpeed()); kwinConfig.writeEntry("HiddenPreviews", windowThumbnail() + 4); kwinConfig.writeEntry("GLTextureFilter", glScaleFilter()); kwinConfig.writeEntry("XRenderSmoothScale", xrScaleFilter()); - kwinConfig.writeEntry("UnredirectFullscreen", unredirectFullscreen()); kwinConfig.writeEntry("Enabled", compositingEnabled()); auto swapStrategy = [this] { switch (glSwapStrategy()) { case 0: return QStringLiteral("n"); case 2: return QStringLiteral("e"); case 3: return QStringLiteral("p"); case 4: return QStringLiteral("c"); case 1: default: return QStringLiteral("a"); } }; kwinConfig.writeEntry("GLPreferBufferSwap", swapStrategy()); kwinConfig.writeEntry("GLColorCorrection", glColorCorrection()); QString backend; bool glCore = false; switch (compositingType()) { case CompositingType::OPENGL31_INDEX: backend = "OpenGL"; glCore = true; break; case CompositingType::OPENGL20_INDEX: backend = "OpenGL"; glCore = false; break; case CompositingType::XRENDER_INDEX: backend = "XRender"; glCore = false; break; } kwinConfig.writeEntry("Backend", backend); kwinConfig.writeEntry("GLCore", glCore); const QModelIndex glIndex = m_openGLPlatformInterfaceModel->index(m_openGLPlatformInterface); if (glIndex.isValid()) { kwinConfig.writeEntry("GLPlatformInterface", glIndex.data(Qt::UserRole).toString()); } kwinConfig.sync(); if (m_changed) { // Send signal to all kwin instances QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/Compositor"), QStringLiteral("org.kde.kwin.Compositing"), QStringLiteral("reinit")); QDBusConnection::sessionBus().send(message); m_changed = false; } } OpenGLPlatformInterfaceModel *Compositing::openGLPlatformInterfaceModel() const { return m_openGLPlatformInterfaceModel; } int Compositing::openGLPlatformInterface() const { return m_openGLPlatformInterface; } void Compositing::setOpenGLPlatformInterface(int interface) { if (m_openGLPlatformInterface == interface) { return; } m_openGLPlatformInterface = interface; emit openGLPlatformInterfaceChanged(interface); } CompositingType::CompositingType(QObject *parent) : QAbstractItemModel(parent) { generateCompositing(); } void CompositingType::generateCompositing() { QHash compositingTypes; compositingTypes[i18n("OpenGL 3.1")] = CompositingType::OPENGL31_INDEX; compositingTypes[i18n("OpenGL 2.0")] = CompositingType::OPENGL20_INDEX; compositingTypes[i18n("XRender")] = CompositingType::XRENDER_INDEX; CompositingData data; beginResetModel(); auto it = compositingTypes.begin(); while (it != compositingTypes.end()) { data.name = it.key(); data.type = it.value(); m_compositingList << data; it++; } qSort(m_compositingList.begin(), m_compositingList.end(), [](const CompositingData &a, const CompositingData &b) { return a.type < b.type; }); endResetModel(); } QHash< int, QByteArray > CompositingType::roleNames() const { QHash roleNames; roleNames[NameRole] = "NameRole"; roleNames[TypeRole] = QByteArrayLiteral("type"); return roleNames; } QModelIndex CompositingType::index(int row, int column, const QModelIndex &parent) const { if (parent.isValid() || column > 0 || column < 0 || row < 0 || row >= m_compositingList.count()) { return QModelIndex(); } return createIndex(row, column); } QModelIndex CompositingType::parent(const QModelIndex &child) const { Q_UNUSED(child) return QModelIndex(); } int CompositingType::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) return 1; } int CompositingType::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return m_compositingList.count(); } QVariant CompositingType::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } switch (role) { case Qt::DisplayRole: case NameRole: return m_compositingList.at(index.row()).name; case TypeRole: return m_compositingList.at(index.row()).type; default: return QVariant(); } } int CompositingType::compositingTypeForIndex(int row) const { return index(row, 0).data(TypeRole).toInt(); } int CompositingType::indexForCompositingType(int type) const { for (int i = 0; i < m_compositingList.count(); ++i) { if (m_compositingList.at(i).type == type) { return i; } } return -1; } OpenGLPlatformInterfaceModel::OpenGLPlatformInterfaceModel(QObject *parent) : QAbstractListModel(parent) { beginResetModel(); OrgKdeKwinCompositingInterface interface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Compositor"), QDBusConnection::sessionBus()); m_keys << interface.supportedOpenGLPlatformInterfaces(); for (const QString &key : m_keys) { if (key == QStringLiteral("egl")) { m_names << i18nc("OpenGL Platform Interface", "EGL"); } else if (key == QStringLiteral("glx")) { m_names << i18nc("OpenGL Platform Interface", "GLX"); } else { m_names << key; } } endResetModel(); } OpenGLPlatformInterfaceModel::~OpenGLPlatformInterfaceModel() = default; int OpenGLPlatformInterfaceModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return m_keys.count(); } QHash< int, QByteArray > OpenGLPlatformInterfaceModel::roleNames() const { return QHash({ {Qt::DisplayRole, QByteArrayLiteral("display")}, {Qt::UserRole, QByteArrayLiteral("openglPlatformInterface")} }); } QVariant OpenGLPlatformInterfaceModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= m_keys.size() || index.column() != 0) { return QVariant(); } switch (role) { case Qt::DisplayRole: return m_names.at(index.row()); case Qt::UserRole: return m_keys.at(index.row()); default: return QVariant(); } } QModelIndex OpenGLPlatformInterfaceModel::indexForKey(const QString &key) const { const int keyIndex = m_keys.indexOf(key); if (keyIndex < 0) { return QModelIndex(); } return createIndex(keyIndex, 0); } }//end namespace Compositing }//end namespace KWin diff --git a/kcmkwin/kwincompositing/compositing.h b/kcmkwin/kwincompositing/compositing.h index e9561ab4f..00dcb1507 100644 --- a/kcmkwin/kwincompositing/compositing.h +++ b/kcmkwin/kwincompositing/compositing.h @@ -1,180 +1,175 @@ /************************************************************************** * KWin - the KDE window manager * * This file is part of the KDE project. * * * * Copyright (C) 2013 Antonis Tsiapaliokas * * * * 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 COMPOSITING_H #define COMPOSITING_H #include #include namespace KWin { namespace Compositing { class OpenGLPlatformInterfaceModel; class Compositing : public QObject { Q_OBJECT Q_PROPERTY(int animationSpeed READ animationSpeed WRITE setAnimationSpeed NOTIFY animationSpeedChanged) Q_PROPERTY(int windowThumbnail READ windowThumbnail WRITE setWindowThumbnail NOTIFY windowThumbnailChanged) Q_PROPERTY(int glScaleFilter READ glScaleFilter WRITE setGlScaleFilter NOTIFY glScaleFilterChanged) Q_PROPERTY(bool xrScaleFilter READ xrScaleFilter WRITE setXrScaleFilter NOTIFY xrScaleFilterChanged) - Q_PROPERTY(bool unredirectFullscreen READ unredirectFullscreen WRITE setUnredirectFullscreen NOTIFY unredirectFullscreenChanged) Q_PROPERTY(int glSwapStrategy READ glSwapStrategy WRITE setGlSwapStrategy NOTIFY glSwapStrategyChanged) Q_PROPERTY(bool glColorCorrection READ glColorCorrection WRITE setGlColorCorrection NOTIFY glColorCorrectionChanged) Q_PROPERTY(int compositingType READ compositingType WRITE setCompositingType NOTIFY compositingTypeChanged) Q_PROPERTY(bool compositingEnabled READ compositingEnabled WRITE setCompositingEnabled NOTIFY compositingEnabledChanged) Q_PROPERTY(KWin::Compositing::OpenGLPlatformInterfaceModel *openGLPlatformInterfaceModel READ openGLPlatformInterfaceModel CONSTANT) Q_PROPERTY(int openGLPlatformInterface READ openGLPlatformInterface WRITE setOpenGLPlatformInterface NOTIFY openGLPlatformInterfaceChanged) public: explicit Compositing(QObject *parent = 0); Q_INVOKABLE bool OpenGLIsUnsafe() const; Q_INVOKABLE bool OpenGLIsBroken(); Q_INVOKABLE void reenableOpenGLDetection(); int animationSpeed() const; int windowThumbnail() const; int glScaleFilter() const; bool xrScaleFilter() const; - bool unredirectFullscreen() const; int glSwapStrategy() const; bool glColorCorrection() const; int compositingType() const; bool compositingEnabled() const; int openGLPlatformInterface() const; OpenGLPlatformInterfaceModel *openGLPlatformInterfaceModel() const; void setAnimationSpeed(int speed); void setWindowThumbnail(int index); void setGlScaleFilter(int index); void setXrScaleFilter(bool filter); - void setUnredirectFullscreen(bool unredirect); void setGlSwapStrategy(int strategy); void setGlColorCorrection(bool correction); void setCompositingType(int index); void setCompositingEnabled(bool enalbed); void setOpenGLPlatformInterface(int interface); void save(); public Q_SLOTS: void reset(); void defaults(); Q_SIGNALS: void changed(); void animationSpeedChanged(int); void windowThumbnailChanged(int); void glScaleFilterChanged(int); void xrScaleFilterChanged(int); - void unredirectFullscreenChanged(bool); void glSwapStrategyChanged(int); void glColorCorrectionChanged(bool); void compositingTypeChanged(int); void compositingEnabledChanged(bool); void openGLPlatformInterfaceChanged(int); private: int m_animationSpeed; int m_windowThumbnail; int m_glScaleFilter; bool m_xrScaleFilter; - bool m_unredirectFullscreen; int m_glSwapStrategy; bool m_glColorCorrection; int m_compositingType; bool m_compositingEnabled; bool m_changed; OpenGLPlatformInterfaceModel *m_openGLPlatformInterfaceModel; int m_openGLPlatformInterface; }; struct CompositingData; class CompositingType : public QAbstractItemModel { Q_OBJECT Q_ENUMS(CompositingTypeIndex) public: enum CompositingTypeIndex { OPENGL31_INDEX = 0, OPENGL20_INDEX, XRENDER_INDEX }; enum CompositingTypeRoles { NameRole = Qt::UserRole +1, TypeRole = Qt::UserRole +2 }; explicit CompositingType(QObject *parent = 0); QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &child) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; virtual QHash< int, QByteArray > roleNames() const override; Q_INVOKABLE int compositingTypeForIndex(int row) const; Q_INVOKABLE int indexForCompositingType(int type) const; private: void generateCompositing(); QList m_compositingList; }; struct CompositingData { QString name; CompositingType::CompositingTypeIndex type; }; class OpenGLPlatformInterfaceModel : public QAbstractListModel { Q_OBJECT public: explicit OpenGLPlatformInterfaceModel(QObject *parent = nullptr); virtual ~OpenGLPlatformInterfaceModel(); QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QModelIndex indexForKey(const QString &key) const; QHash< int, QByteArray > roleNames() const override; private: QStringList m_keys; QStringList m_names; }; }//end namespace Compositing }//end namespace KWin Q_DECLARE_METATYPE(KWin::Compositing::OpenGLPlatformInterfaceModel*) #endif diff --git a/kcmkwin/kwincompositing/compositing.ui b/kcmkwin/kwincompositing/compositing.ui index 8a39bf5e9..ef1343c06 100644 --- a/kcmkwin/kwincompositing/compositing.ui +++ b/kcmkwin/kwincompositing/compositing.ui @@ -1,340 +1,310 @@ CompositingForm 0 0 481 - 415 + 349 QFormLayout::AllNonFixedFieldsGrow false OpenGL compositing (the default) has crashed KWin in the past. This was most likely due to a driver bug. If you think that you have meanwhile upgraded to a stable driver, you can reset this protection but be aware that this might result in an immediate crash! Alternatively, you might want to use the XRender backend instead. true KMessageWidget::Warning false Scale method "Accurate" is not supported by all hardware and can cause performance regressions and rendering artifacts. true KMessageWidget::Warning false true KMessageWidget::Warning false Keeping the window thumbnail always interferes with the minimized state of windows. This can result in windows not suspending their work when minimized. true KMessageWidget::Warning - - - - false - - - Having full screen windows bypass the compositor is not supported on every hardware. - - - true - - - KMessageWidget::Information - - - Enable compositor on startup Animation speed: 6 Qt::Horizontal QSlider::TicksBelow 1 Instant Qt::Horizontal 40 20 Very slow Scale method: Crisp Smooth Accurate Scale method: Crisp Smooth (slower) Qt::Horizontal Rendering backend: Qt::Horizontal Tearing prevention ("vsync"): Never Automatic Only when cheap Full screen repaints Re-use screen content Keep window thumbnails: Never Only for Shown Windows Always - - - Expert: - - - - - - - Suspend compositor for full screen windows - - - - Experimental: - + Enable color correction KMessageWidget QFrame
kmessagewidget.h
1
diff --git a/kcmkwin/kwincompositing/main.cpp b/kcmkwin/kwincompositing/main.cpp index a0d8e958b..2d03829e1 100644 --- a/kcmkwin/kwincompositing/main.cpp +++ b/kcmkwin/kwincompositing/main.cpp @@ -1,283 +1,268 @@ /************************************************************************** * KWin - the KDE window manager * * This file is part of the KDE project. * * * * Copyright (C) 2013 Antonis Tsiapaliokas * * Copyright (C) 2013 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 . * **************************************************************************/ #include "compositing.h" #include "model.h" #include "ui_compositing.h" #include #include #include #include class KWinCompositingKCM : public KCModule { Q_OBJECT public: virtual ~KWinCompositingKCM(); public Q_SLOTS: void save() override; void load() override; void defaults() override; protected: explicit KWinCompositingKCM(QWidget* parent, const QVariantList& args, KWin::Compositing::EffectView::ViewType viewType); private: QScopedPointer m_view; }; class KWinDesktopEffects : public KWinCompositingKCM { Q_OBJECT public: explicit KWinDesktopEffects(QWidget* parent = 0, const QVariantList& args = QVariantList()) : KWinCompositingKCM(parent, args, KWin::Compositing::EffectView::DesktopEffectsView) {} }; class KWinCompositingSettings : public KCModule { Q_OBJECT public: explicit KWinCompositingSettings(QWidget *parent = 0, const QVariantList &args = QVariantList()); public Q_SLOTS: void load() override; void save() override; void defaults() override; private: void init(); KWin::Compositing::Compositing *m_compositing; Ui_CompositingForm m_form; }; KWinCompositingSettings::KWinCompositingSettings(QWidget *parent, const QVariantList &args) : KCModule(parent, args) , m_compositing(new KWin::Compositing::Compositing(this)) { m_form.setupUi(this); m_form.glCrashedWarning->setIcon(QIcon::fromTheme(QStringLiteral("dialog-warning"))); QAction *reenableGLAction = new QAction(i18n("Re-enable OpenGL detection"), this); connect(reenableGLAction, &QAction::triggered, m_compositing, &KWin::Compositing::Compositing::reenableOpenGLDetection); connect(reenableGLAction, &QAction::triggered, m_form.glCrashedWarning, &KMessageWidget::animatedHide); m_form.glCrashedWarning->addAction(reenableGLAction); m_form.scaleWarning->setIcon(QIcon::fromTheme(QStringLiteral("dialog-warning"))); m_form.tearingWarning->setIcon(QIcon::fromTheme(QStringLiteral("dialog-warning"))); m_form.windowThumbnailWarning->setIcon(QIcon::fromTheme(QStringLiteral("dialog-warning"))); - m_form.unredirectInformation->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); init(); } void KWinCompositingSettings::init() { using namespace KWin::Compositing; auto currentIndexChangedSignal = static_cast(&QComboBox::currentIndexChanged); connect(m_compositing, &Compositing::changed, this, static_cast(&KWinCompositingSettings::changed)); // enabled check box m_form.compositingEnabled->setChecked(m_compositing->compositingEnabled()); connect(m_compositing, &Compositing::compositingEnabledChanged, m_form.compositingEnabled, &QCheckBox::setChecked); connect(m_form.compositingEnabled, &QCheckBox::toggled, m_compositing, &Compositing::setCompositingEnabled); // animation speed m_form.animationSpeed->setValue(m_compositing->animationSpeed()); connect(m_compositing, &Compositing::animationSpeedChanged, m_form.animationSpeed, &QSlider::setValue); connect(m_form.animationSpeed, &QSlider::valueChanged, m_compositing, &Compositing::setAnimationSpeed); // gl scale filter m_form.glScaleFilter->setCurrentIndex(m_compositing->glScaleFilter()); connect(m_compositing, &Compositing::glScaleFilterChanged, m_form.glScaleFilter, &QComboBox::setCurrentIndex); connect(m_form.glScaleFilter, currentIndexChangedSignal, m_compositing, &Compositing::setGlScaleFilter); connect(m_form.glScaleFilter, currentIndexChangedSignal, [this](int index) { if (index == 2) { m_form.scaleWarning->animatedShow(); } else { m_form.scaleWarning->animatedHide(); } } ); // xrender scale filter m_form.xrScaleFilter->setCurrentIndex(m_compositing->xrScaleFilter()); connect(m_compositing, &Compositing::xrScaleFilterChanged, m_form.xrScaleFilter, &QComboBox::setCurrentIndex); connect(m_form.xrScaleFilter, currentIndexChangedSignal, m_compositing, &Compositing::setXrScaleFilter); // tearing prevention m_form.tearingPrevention->setCurrentIndex(m_compositing->glSwapStrategy()); connect(m_compositing, &Compositing::glSwapStrategyChanged, m_form.tearingPrevention, &QComboBox::setCurrentIndex); connect(m_form.tearingPrevention, currentIndexChangedSignal, m_compositing, &Compositing::setGlSwapStrategy); connect(m_form.tearingPrevention, currentIndexChangedSignal, [this](int index) { if (index == 2) { // only when cheap - tearing m_form.tearingWarning->setText(i18n("\"Only when cheap\" only prevents tearing for full screen changes like a video.")); m_form.tearingWarning->animatedShow(); } else if (index == 3) { // full screen repaints m_form.tearingWarning->setText(i18n("\"Full screen repaints\" can cause performance problems.")); m_form.tearingWarning->animatedShow(); } else if (index == 4) { // re-use screen content m_form.tearingWarning->setText(i18n("\"Re-use screen content\" causes severe performance problems on MESA drivers.")); m_form.tearingWarning->animatedShow(); } else { m_form.tearingWarning->animatedHide(); } } ); // windowThumbnail m_form.windowThumbnail->setCurrentIndex(m_compositing->windowThumbnail()); connect(m_compositing, &Compositing::windowThumbnailChanged, m_form.windowThumbnail, &QComboBox::setCurrentIndex); connect(m_form.windowThumbnail, currentIndexChangedSignal, m_compositing, &Compositing::setWindowThumbnail); connect(m_form.windowThumbnail, currentIndexChangedSignal, [this](int index) { if (index == 2) { m_form.windowThumbnailWarning->animatedShow(); } else { m_form.windowThumbnailWarning->animatedHide(); } } ); - // unredirect fullscreen - m_form.unredirectFullscreen->setChecked(m_compositing->unredirectFullscreen()); - connect(m_compositing, &Compositing::unredirectFullscreenChanged, m_form.unredirectFullscreen, &QCheckBox::setChecked); - connect(m_form.unredirectFullscreen, &QCheckBox::toggled, m_compositing, &Compositing::setUnredirectFullscreen); - connect(m_form.unredirectFullscreen, &QCheckBox::toggled, - [this](bool enabled) { - if (enabled) { - m_form.unredirectInformation->animatedShow(); - } else { - m_form.unredirectInformation->animatedHide(); - } - } - ); - // color correction m_form.colorCorrection->setChecked(m_compositing->glColorCorrection()); connect(m_compositing, &Compositing::glColorCorrectionChanged, m_form.colorCorrection, &QCheckBox::setChecked); connect(m_form.colorCorrection, &QCheckBox::toggled, m_compositing, &Compositing::setGlColorCorrection); // compositing type CompositingType *type = new CompositingType(this); m_form.type->setModel(type); auto updateCompositingType = [this, type]() { m_form.type->setCurrentIndex(type->indexForCompositingType(m_compositing->compositingType())); }; updateCompositingType(); connect(m_compositing, &Compositing::compositingTypeChanged, [updateCompositingType]() { updateCompositingType(); } ); auto showHideBasedOnType = [this, type]() { const int currentType = type->compositingTypeForIndex(m_form.type->currentIndex()); m_form.glScaleFilter->setVisible(currentType != CompositingType::XRENDER_INDEX); m_form.glScaleFilterLabel->setVisible(currentType != CompositingType::XRENDER_INDEX); m_form.xrScaleFilter->setVisible(currentType == CompositingType::XRENDER_INDEX); m_form.xrScaleFilterLabel->setVisible(currentType == CompositingType::XRENDER_INDEX); m_form.colorCorrection->setEnabled(currentType == CompositingType::OPENGL31_INDEX || currentType == CompositingType::OPENGL20_INDEX); }; showHideBasedOnType(); connect(m_form.type, currentIndexChangedSignal, [this, type, showHideBasedOnType]() { m_compositing->setCompositingType(type->compositingTypeForIndex(m_form.type->currentIndex())); showHideBasedOnType(); } ); if (m_compositing->OpenGLIsUnsafe()) { m_form.glCrashedWarning->animatedShow(); } } void KWinCompositingSettings::load() { KCModule::load(); m_compositing->reset(); } void KWinCompositingSettings::defaults() { KCModule::defaults(); m_compositing->defaults(); } void KWinCompositingSettings::save() { KCModule::save(); m_compositing->save(); } KWinCompositingKCM::KWinCompositingKCM(QWidget* parent, const QVariantList& args, KWin::Compositing::EffectView::ViewType viewType) : KCModule(parent, args) , m_view(new KWin::Compositing::EffectView(viewType)) { QVBoxLayout *vl = new QVBoxLayout(this); QWidget *w = QWidget::createWindowContainer(m_view.data(), this); connect(m_view.data(), &QWindow::minimumWidthChanged, w, &QWidget::setMinimumWidth); connect(m_view.data(), &QWindow::minimumHeightChanged, w, &QWidget::setMinimumHeight); w->setMinimumSize(m_view->initialSize()); vl->addWidget(w); setLayout(vl); connect(m_view.data(), &KWin::Compositing::EffectView::changed, [this]{ emit changed(true); }); w->setFocusPolicy(Qt::StrongFocus); } KWinCompositingKCM::~KWinCompositingKCM() { } void KWinCompositingKCM::save() { m_view->save(); KCModule::save(); } void KWinCompositingKCM::load() { m_view->load(); KCModule::load(); } void KWinCompositingKCM::defaults() { m_view->defaults(); KCModule::defaults(); } K_PLUGIN_FACTORY(KWinCompositingConfigFactory, registerPlugin("effects"); registerPlugin("compositing"); ) #include "main.moc" diff --git a/kwin.kcfg b/kwin.kcfg index 5846721c0..763482c7c 100644 --- a/kwin.kcfg +++ b/kwin.kcfg @@ -1,310 +1,307 @@ Switch to Window Tab to the Left/Right Alt Nothing Raise Start Window Tab Drag Operations menu Activate and raise Start Window Tab Drag Operations menu Activate, raise and pass click Activate and pass click Activate and pass click Scroll Move Toggle raise and lower Resize None None None None None None None None false false Options::ClickToFocus false false focusPolicy() != Options::ClickToFocus true false 1 0 4 Placement::Smart false 750 300 false 250 true 10 10 0 false 0 150 350 1 true true 0.25 0.0 1.0 Maximize Maximize Maximize (vertical only) Maximize (horizontal only) 5000 true false false true false 60 0 6144 OpenGL true 2 -1 2 true false false false 5 4 6 - - false - 3 0 6 glx true 90 1 1 0 0 0 0 0 true true true thumbnails diff --git a/layers.cpp b/layers.cpp index 2edfda017..a368def14 100644 --- a/layers.cpp +++ b/layers.cpp @@ -1,858 +1,855 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ // SELI zmenit doc /* This file contains things relevant to stacking order and layers. Design: Normal unconstrained stacking order, as requested by the user (by clicking on windows to raise them, etc.), is in Workspace::unconstrained_stacking_order. That list shouldn't be used at all, except for building Workspace::stacking_order. The building is done in Workspace::constrainedStackingOrder(). Only Workspace::stackingOrder() should be used to get the stacking order, because it also checks the stacking order is up to date. All clients are also stored in Workspace::clients (except for isDesktop() clients, as those are very special, and are stored in Workspace::desktops), in the order the clients were created. Every window has one layer assigned in which it is. There are 7 layers, from bottom : DesktopLayer, BelowLayer, NormalLayer, DockLayer, AboveLayer, NotificationLayer, ActiveLayer and OnScreenDisplayLayer (see also NETWM sect.7.10.). The layer a window is in depends on the window type, and on other things like whether the window is active. We extend the layers provided in NETWM by the NotificationLayer and OnScreenDisplayLayer. The NoficationLayer contains notification windows which are kept above all windows except the active fullscreen window. The OnScreenDisplayLayer is used for eg. volume and brightness change feedback and is kept above all windows since it provides immediate response to a user action. NET::Splash clients belong to the Normal layer. NET::TopMenu clients belong to Dock layer. Clients that are both NET::Dock and NET::KeepBelow are in the Normal layer in order to keep the 'allow window to cover the panel' Kicker setting to work as intended (this may look like a slight spec violation, but a) I have no better idea, b) the spec allows adjusting the stacking order if the WM thinks it's a good idea . We put all NET::KeepAbove above all Docks too, even though the spec suggests putting them in the same layer. Most transients are in the same layer as their mainwindow, see Workspace::constrainedStackingOrder(), they may also be in higher layers, but they should never be below their mainwindow. When some client attribute changes (above/below flag, transiency...), Workspace::updateClientLayer() should be called in order to make sure it's moved to the appropriate layer ClientList if needed. Currently the things that affect client in which layer a client belongs: KeepAbove/Keep Below flags, window type, fullscreen state and whether the client is active, mainclient (transiency). Make sure updateStackingOrder() is called in order to make Workspace::stackingOrder() up to date and propagated to the world. Using Workspace::blockStackingUpdates() (or the StackingUpdatesBlocker helper class) it's possible to temporarily disable updates and the stacking order will be updated once after it's allowed again. */ #include #include "utils.h" #include "client.h" #include "focuschain.h" #include "netinfo.h" #include "workspace.h" #include "tabbox.h" #include "group.h" #include "rules.h" #include "screens.h" #include "unmanaged.h" #include "deleted.h" #include "effects.h" #include "composite.h" #include "screenedge.h" #include "shell_client.h" #include "wayland_server.h" #include namespace KWin { //******************************* // Workspace //******************************* void Workspace::updateClientLayer(AbstractClient* c) { if (c) c->updateLayer(); } void Workspace::updateStackingOrder(bool propagate_new_clients) { if (block_stacking_updates > 0) { if (propagate_new_clients) blocked_propagating_new_clients = true; return; } ToplevelList new_stacking_order = constrainedStackingOrder(); bool changed = (force_restacking || new_stacking_order != stacking_order); force_restacking = false; stacking_order = new_stacking_order; #if 0 qCDebug(KWIN_CORE) << "stacking:" << changed; if (changed || propagate_new_clients) { for (ClientList::ConstIterator it = stacking_order.begin(); it != stacking_order.end(); ++it) qCDebug(KWIN_CORE) << (void*)(*it) << *it << ":" << (*it)->layer(); } #endif if (changed || propagate_new_clients) { propagateClients(propagate_new_clients); emit stackingOrderChanged(); if (m_compositor) { m_compositor->addRepaintFull(); } if (active_client) active_client->updateMouseGrab(); } } /*! * Some fullscreen effects have to raise the screenedge on top of an input window, thus all windows * this function puts them back where they belong for regular use and is some cheap variant of * the regular propagateClients function in that it completely ignores managed clients and everything * else and also does not update the NETWM property. * Called from Effects::destroyInputWindow so far. */ void Workspace::stackScreenEdgesUnderOverrideRedirect() { Xcb::restackWindows(QVector() << rootInfo()->supportWindow() << ScreenEdges::self()->windows()); } /*! Propagates the managed clients to the world. Called ONLY from updateStackingOrder(). */ void Workspace::propagateClients(bool propagate_new_clients) { // restack the windows according to the stacking order // supportWindow > electric borders > clients > hidden clients QVector newWindowStack; // Stack all windows under the support window. The support window is // not used for anything (besides the NETWM property), and it's not shown, // but it was lowered after kwin startup. Stacking all clients below // it ensures that no client will be ever shown above override-redirect // windows (e.g. popups). newWindowStack << rootInfo()->supportWindow(); newWindowStack << ScreenEdges::self()->windows(); newWindowStack.reserve(newWindowStack.size() + 2*stacking_order.size()); // *2 for inputWindow for (int i = stacking_order.size() - 1; i >= 0; --i) { Client *client = qobject_cast(stacking_order.at(i)); if (!client || client->hiddenPreview()) { continue; } if (client->inputId()) // Stack the input window above the frame newWindowStack << client->inputId(); newWindowStack << client->frameId(); } // when having hidden previews, stack hidden windows below everything else // (as far as pure X stacking order is concerned), in order to avoid having // these windows that should be unmapped to interfere with other windows for (int i = stacking_order.size() - 1; i >= 0; --i) { Client *client = qobject_cast(stacking_order.at(i)); if (!client || !client->hiddenPreview()) continue; newWindowStack << client->frameId(); } // TODO isn't it too inefficient to restack always all clients? // TODO don't restack not visible windows? assert(newWindowStack.at(0) == rootInfo()->supportWindow()); Xcb::restackWindows(newWindowStack); int pos = 0; xcb_window_t *cl(nullptr); if (propagate_new_clients) { cl = new xcb_window_t[ desktops.count() + clients.count()]; // TODO this is still not completely in the map order for (ClientList::ConstIterator it = desktops.constBegin(); it != desktops.constEnd(); ++it) cl[pos++] = (*it)->window(); for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) cl[pos++] = (*it)->window(); rootInfo()->setClientList(cl, pos); delete [] cl; } cl = new xcb_window_t[ stacking_order.count()]; pos = 0; for (ToplevelList::ConstIterator it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { if ((*it)->isClient()) cl[pos++] = (*it)->window(); } rootInfo()->setClientListStacking(cl, pos); delete [] cl; // Make the cached stacking order invalid here, in case we need the new stacking order before we get // the matching event, due to X being asynchronous. x_stacking_dirty = true; } /*! Returns topmost visible client. Windows on the dock, the desktop or of any other special kind are excluded. Also if the window doesn't accept focus it's excluded. */ // TODO misleading name for this method, too many slightly different ways to use it AbstractClient* Workspace::topClientOnDesktop(int desktop, int screen, bool unconstrained, bool only_normal) const { // TODO Q_ASSERT( block_stacking_updates == 0 ); ToplevelList list; if (!unconstrained) list = stacking_order; else list = unconstrained_stacking_order; for (int i = list.size() - 1; i >= 0; --i) { AbstractClient *c = qobject_cast(list.at(i)); if (!c) { continue; } if (c->isOnDesktop(desktop) && c->isShown(false) && c->isOnCurrentActivity()) { if (screen != -1 && c->screen() != screen) continue; if (!only_normal) return c; if (c->wantsTabFocus() && !c->isSpecialWindow()) return c; } } return 0; } AbstractClient* Workspace::findDesktop(bool topmost, int desktop) const { // TODO Q_ASSERT( block_stacking_updates == 0 ); if (topmost) { for (int i = stacking_order.size() - 1; i >= 0; i--) { AbstractClient *c = qobject_cast(stacking_order.at(i)); if (c && c->isOnDesktop(desktop) && c->isDesktop() && c->isShown(true)) return c; } } else { // bottom-most foreach (Toplevel * c, stacking_order) { AbstractClient *client = qobject_cast(c); if (client && c->isOnDesktop(desktop) && c->isDesktop() && client->isShown(true)) return client; } } return NULL; } void Workspace::raiseOrLowerClient(AbstractClient *c) { if (!c) return; AbstractClient* topmost = NULL; // TODO Q_ASSERT( block_stacking_updates == 0 ); if (most_recently_raised && stacking_order.contains(most_recently_raised) && most_recently_raised->isShown(true) && c->isOnCurrentDesktop()) topmost = most_recently_raised; else topmost = topClientOnDesktop(c->isOnAllDesktops() ? VirtualDesktopManager::self()->current() : c->desktop(), options->isSeparateScreenFocus() ? c->screen() : -1); if (c == topmost) lowerClient(c); else raiseClient(c); } void Workspace::lowerClient(AbstractClient* c, bool nogroup) { if (!c) return; c->cancelAutoRaise(); StackingUpdatesBlocker blocker(this); unconstrained_stacking_order.removeAll(c); unconstrained_stacking_order.prepend(c); if (!nogroup && c->isTransient()) { // lower also all windows in the group, in their reversed stacking order ClientList wins; if (Client *client = dynamic_cast(c)) { wins = ensureStackingOrder(client->group()->members()); } for (int i = wins.size() - 1; i >= 0; --i) { if (wins[ i ] != c) lowerClient(wins[ i ], true); } } if (c == most_recently_raised) most_recently_raised = 0; } void Workspace::lowerClientWithinApplication(AbstractClient* c) { if (!c) return; c->cancelAutoRaise(); StackingUpdatesBlocker blocker(this); unconstrained_stacking_order.removeAll(c); bool lowered = false; // first try to put it below the bottom-most window of the application for (ToplevelList::Iterator it = unconstrained_stacking_order.begin(); it != unconstrained_stacking_order.end(); ++it) { AbstractClient *client = qobject_cast(*it); if (!client) { continue; } if (AbstractClient::belongToSameApplication(client, c)) { unconstrained_stacking_order.insert(it, c); lowered = true; break; } } if (!lowered) unconstrained_stacking_order.prepend(c); // ignore mainwindows } void Workspace::raiseClient(AbstractClient* c, bool nogroup) { if (!c) return; c->cancelAutoRaise(); StackingUpdatesBlocker blocker(this); if (!nogroup && c->isTransient()) { QList transients; AbstractClient *transient_parent = c; while ((transient_parent = transient_parent->transientFor())) transients << transient_parent; foreach (transient_parent, transients) raiseClient(transient_parent, true); } unconstrained_stacking_order.removeAll(c); unconstrained_stacking_order.append(c); if (!c->isSpecialWindow()) { most_recently_raised = c; } } void Workspace::raiseClientWithinApplication(AbstractClient* c) { if (!c) return; c->cancelAutoRaise(); StackingUpdatesBlocker blocker(this); // ignore mainwindows // first try to put it above the top-most window of the application for (int i = unconstrained_stacking_order.size() - 1; i > -1 ; --i) { AbstractClient *other = qobject_cast(unconstrained_stacking_order.at(i)); if (!other) { continue; } if (other == c) // don't lower it just because it asked to be raised return; if (AbstractClient::belongToSameApplication(other, c)) { unconstrained_stacking_order.removeAll(c); unconstrained_stacking_order.insert(unconstrained_stacking_order.indexOf(other) + 1, c); // insert after the found one break; } } } void Workspace::raiseClientRequest(KWin::AbstractClient *c, NET::RequestSource src, xcb_timestamp_t timestamp) { if (src == NET::FromTool || allowFullClientRaising(c, timestamp)) raiseClient(c); else { raiseClientWithinApplication(c); c->demandAttention(); } } void Workspace::lowerClientRequest(KWin::Client *c, NET::RequestSource src, xcb_timestamp_t /*timestamp*/) { // If the client has support for all this focus stealing prevention stuff, // do only lowering within the application, as that's the more logical // variant of lowering when application requests it. // No demanding of attention here of course. if (src == NET::FromTool || !c->hasUserTimeSupport()) lowerClient(c); else lowerClientWithinApplication(c); } void Workspace::lowerClientRequest(KWin::AbstractClient *c) { lowerClientWithinApplication(c); } void Workspace::restack(AbstractClient* c, AbstractClient* under, bool force) { assert(unconstrained_stacking_order.contains(under)); if (!force && !AbstractClient::belongToSameApplication(under, c)) { // put in the stacking order below _all_ windows belonging to the active application for (int i = 0; i < unconstrained_stacking_order.size(); ++i) { AbstractClient *other = qobject_cast(unconstrained_stacking_order.at(i)); if (other && other->layer() == c->layer() && AbstractClient::belongToSameApplication(under, other)) { under = (c == other) ? 0 : other; break; } } } if (under) { unconstrained_stacking_order.removeAll(c); unconstrained_stacking_order.insert(unconstrained_stacking_order.indexOf(under), c); } assert(unconstrained_stacking_order.contains(c)); FocusChain::self()->moveAfterClient(c, under); updateStackingOrder(); } void Workspace::restackClientUnderActive(AbstractClient* c) { if (!active_client || active_client == c || active_client->layer() != c->layer()) { raiseClient(c); return; } restack(c, active_client); } void Workspace::restoreSessionStackingOrder(Client* c) { if (c->sessionStackingOrder() < 0) return; StackingUpdatesBlocker blocker(this); unconstrained_stacking_order.removeAll(c); for (ToplevelList::Iterator it = unconstrained_stacking_order.begin(); // from bottom it != unconstrained_stacking_order.end(); ++it) { Client *current = qobject_cast(*it); if (!current) { continue; } if (current->sessionStackingOrder() > c->sessionStackingOrder()) { unconstrained_stacking_order.insert(it, c); return; } } unconstrained_stacking_order.append(c); } /*! Returns a stacking order based upon \a list that fulfills certain contained. */ ToplevelList Workspace::constrainedStackingOrder() { ToplevelList layer[ NumLayers ]; #if 0 qCDebug(KWIN_CORE) << "stacking1:"; for (ClientList::ConstIterator it = unconstrained_stacking_order.begin(); it != unconstrained_stacking_order.end(); ++it) qCDebug(KWIN_CORE) << (void*)(*it) << *it << ":" << (*it)->layer(); #endif // build the order from layers QVector< QMap > minimum_layer(screens()->count()); for (ToplevelList::ConstIterator it = unconstrained_stacking_order.constBegin(), end = unconstrained_stacking_order.constEnd(); it != end; ++it) { Layer l = (*it)->layer(); const int screen = (*it)->screen(); Client *c = qobject_cast(*it); QMap< Group*, Layer >::iterator mLayer = minimum_layer[screen].find(c ? c->group() : NULL); if (mLayer != minimum_layer[screen].end()) { // If a window is raised above some other window in the same window group // which is in the ActiveLayer (i.e. it's fulscreened), make sure it stays // above that window (see #95731). if (*mLayer == ActiveLayer && (l > BelowLayer)) l = ActiveLayer; *mLayer = l; } else if (c) { minimum_layer[screen].insertMulti(c->group(), l); } layer[ l ].append(*it); } ToplevelList stacking; for (Layer lay = FirstLayer; lay < NumLayers; ++lay) stacking += layer[ lay ]; #if 0 qCDebug(KWIN_CORE) << "stacking2:"; for (ClientList::ConstIterator it = stacking.begin(); it != stacking.end(); ++it) qCDebug(KWIN_CORE) << (void*)(*it) << *it << ":" << (*it)->layer(); #endif // now keep transients above their mainwindows // TODO this could(?) use some optimization for (int i = stacking.size() - 1; i >= 0; ) { AbstractClient *current = qobject_cast(stacking[i]); if (!current || !current->isTransient()) { --i; continue; } int i2 = -1; Client *ccurrent = qobject_cast(current); if (ccurrent && ccurrent->groupTransient()) { if (ccurrent->group()->members().count() > 0) { // find topmost client this one is transient for for (i2 = stacking.size() - 1; i2 >= 0; --i2) { if (stacking[ i2 ] == stacking[ i ]) { i2 = -1; // don't reorder, already the topmost in the group break; } AbstractClient *c2 = qobject_cast(stacking[ i2 ]); if (!c2) { continue; } if (c2->hasTransient(current, true) && keepTransientAbove(c2, current)) break; } } // else i2 remains pointing at -1 } else { for (i2 = stacking.size() - 1; i2 >= 0; --i2) { AbstractClient *c2 = qobject_cast(stacking[ i2 ]); if (!c2) { continue; } if (c2 == current) { i2 = -1; // don't reorder, already on top of its mainwindow break; } if (c2 == current->transientFor() && keepTransientAbove(c2, current)) break; } } if (i2 == -1) { --i; continue; } stacking.removeAt(i); --i; // move onto the next item (for next for () iteration) --i2; // adjust index of the mainwindow after the remove above if (!current->transients().isEmpty()) // this one now can be possibly above its transients, i = i2; // so go again higher in the stack order and possibly move those transients again ++i2; // insert after (on top of) the mainwindow, it's ok if it2 is now stacking.end() stacking.insert(i2, current); } #if 0 qCDebug(KWIN_CORE) << "stacking3:"; for (ClientList::ConstIterator it = stacking.begin(); it != stacking.end(); ++it) qCDebug(KWIN_CORE) << (void*)(*it) << *it << ":" << (*it)->layer(); qCDebug(KWIN_CORE) << "\n\n"; #endif return stacking; } void Workspace::blockStackingUpdates(bool block) { if (block) { if (block_stacking_updates == 0) blocked_propagating_new_clients = false; ++block_stacking_updates; } else // !block if (--block_stacking_updates == 0) { updateStackingOrder(blocked_propagating_new_clients); if (effects) static_cast(effects)->checkInputWindowStacking(); } } namespace { template QList ensureStackingOrderInList(const ToplevelList &stackingOrder, const QList &list) { static_assert(std::is_base_of::value, "U must be derived from T"); // TODO Q_ASSERT( block_stacking_updates == 0 ); if (list.count() < 2) return list; // TODO is this worth optimizing? QList result = list; for (auto it = stackingOrder.begin(); it != stackingOrder.end(); ++it) { T *c = qobject_cast(*it); if (!c) { continue; } if (result.removeAll(c) != 0) result.append(c); } return result; } } // Ensure list is in stacking order ClientList Workspace::ensureStackingOrder(const ClientList& list) const { return ensureStackingOrderInList(stacking_order, list); } QList Workspace::ensureStackingOrder(const QList &list) const { return ensureStackingOrderInList(stacking_order, list); } // check whether a transient should be actually kept above its mainwindow // there may be some special cases where this rule shouldn't be enfored bool Workspace::keepTransientAbove(const AbstractClient* mainwindow, const AbstractClient* transient) { // #93832 - don't keep splashscreens above dialogs if (transient->isSplash() && mainwindow->isDialog()) return false; // This is rather a hack for #76026. Don't keep non-modal dialogs above // the mainwindow, but only if they're group transient (since only such dialogs // have taskbar entry in Kicker). A proper way of doing this (both kwin and kicker) // needs to be found. if (const Client *ct = dynamic_cast(transient)) { if (ct->isDialog() && !ct->isModal() && ct->groupTransient()) return false; } // #63223 - don't keep transients above docks, because the dock is kept high, // and e.g. dialogs for them would be too high too // ignore this if the transient has a placement hint which indicates it should go above it's parent if (mainwindow->isDock() && !transient->hasTransientPlacementHint()) return false; return true; } // Returns all windows in their stacking order on the root window. ToplevelList Workspace::xStackingOrder() const { if (!x_stacking_dirty) return x_stacking; x_stacking_dirty = false; x_stacking.clear(); Xcb::Tree tree(rootWindow()); // use our own stacking order, not the X one, as they may differ foreach (Toplevel * c, stacking_order) x_stacking.append(c); if (!tree.isNull()) { xcb_window_t *windows = tree.children(); const auto count = tree->children_len; int foundUnmanagedCount = unmanaged.count(); for (unsigned int i = 0; i < count; ++i) { for (auto it = unmanaged.constBegin(); it != unmanaged.constEnd(); ++it) { Unmanaged *u = *it; if (u->window() == windows[i]) { x_stacking.append(u); foundUnmanagedCount--; break; } } if (foundUnmanagedCount == 0) { break; } } } if (waylandServer()) { const auto clients = waylandServer()->internalClients(); for (auto c: clients) { x_stacking << c; } } - if (m_compositor) { - const_cast< Workspace* >(this)->m_compositor->checkUnredirect(); - } return x_stacking; } //******************************* // Client //******************************* void Client::restackWindow(xcb_window_t above, int detail, NET::RequestSource src, xcb_timestamp_t timestamp, bool send_event) { Client *other = 0; if (detail == XCB_STACK_MODE_OPPOSITE) { other = workspace()->findClient(Predicate::WindowMatch, above); if (!other) { workspace()->raiseOrLowerClient(this); return; } ToplevelList::const_iterator it = workspace()->stackingOrder().constBegin(), end = workspace()->stackingOrder().constEnd(); while (it != end) { if (*it == this) { detail = XCB_STACK_MODE_ABOVE; break; } else if (*it == other) { detail = XCB_STACK_MODE_BELOW; break; } ++it; } } else if (detail == XCB_STACK_MODE_TOP_IF) { other = workspace()->findClient(Predicate::WindowMatch, above); if (other && other->geometry().intersects(geometry())) workspace()->raiseClientRequest(this, src, timestamp); return; } else if (detail == XCB_STACK_MODE_BOTTOM_IF) { other = workspace()->findClient(Predicate::WindowMatch, above); if (other && other->geometry().intersects(geometry())) workspace()->lowerClientRequest(this, src, timestamp); return; } if (!other) other = workspace()->findClient(Predicate::WindowMatch, above); if (other && detail == XCB_STACK_MODE_ABOVE) { ToplevelList::const_iterator it = workspace()->stackingOrder().constEnd(), begin = workspace()->stackingOrder().constBegin(); while (--it != begin) { if (*it == other) { // the other one is top on stack it = begin; // invalidate src = NET::FromTool; // force break; } Client *c = qobject_cast(*it); if (!c || !( (*it)->isNormalWindow() && c->isShown(true) && (*it)->isOnCurrentDesktop() && (*it)->isOnCurrentActivity() && (*it)->isOnScreen(screen()) )) continue; // irrelevant clients if (*(it - 1) == other) break; // "it" is the one above the target one, stack below "it" } if (it != begin && (*(it - 1) == other)) other = qobject_cast(*it); else other = 0; } if (other) workspace()->restack(this, other); else if (detail == XCB_STACK_MODE_BELOW) workspace()->lowerClientRequest(this, src, timestamp); else if (detail == XCB_STACK_MODE_ABOVE) workspace()->raiseClientRequest(this, src, timestamp); if (send_event) sendSyntheticConfigureNotify(); } void Client::doSetKeepAbove() { // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Layer); } void Client::doSetKeepBelow() { // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Layer); } bool Client::belongsToDesktop() const { foreach (const Client *c, group()->members()) { if (c->isDesktop()) return true; } return false; } bool rec_checkTransientOnTop(const QList &transients, const Client *topmost) { foreach (const AbstractClient *transient, transients) { if (transient == topmost || rec_checkTransientOnTop(transient->transients(), topmost)) { return true; } } return false; } bool Client::isActiveFullScreen() const { if (AbstractClient::isActiveFullScreen()) { return true; } if (!isFullScreen()) return false; const Client* ac = dynamic_cast(workspace()->mostRecentlyActivatedClient()); // instead of activeClient() - avoids flicker // according to NETWM spec implementation notes suggests // "focused windows having state _NET_WM_STATE_FULLSCREEN" to be on the highest layer. // we'll also take the screen into account return ac && (this->group() == ac->group()); } } // namespace diff --git a/options.cpp b/options.cpp index dbd957fed..124593e75 100644 --- a/options.cpp +++ b/options.cpp @@ -1,1171 +1,1149 @@ /******************************************************************** 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) 2012 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 . *********************************************************************/ #include "options.h" #include "config-kwin.h" #include "utils.h" #include "platform.h" #ifndef KCMRULES #include #include "screens.h" #include "settings.h" #include "xcbutils.h" #include #include #endif //KCMRULES namespace KWin { #ifndef KCMRULES int currentRefreshRate() { int rate = -1; QString syncScreenName(QLatin1String("primary screen")); if (options->refreshRate() > 0) { // use manually configured refresh rate rate = options->refreshRate(); } else if (Screens::self()->count() > 0) { // prefer the refreshrate calculated from the screens mode information // at least the nvidia driver reports 50Hz BS ... *again*! int syncScreen = 0; if (Screens::self()->count() > 1) { const QByteArray syncDisplayDevice(qgetenv("__GL_SYNC_DISPLAY_DEVICE")); // if __GL_SYNC_DISPLAY_DEVICE is exported, the GPU shall sync to that device // so we try to use its refresh rate if (!syncDisplayDevice.isEmpty()) { for (int i = 0; i < Screens::self()->count(); ++i) { if (Screens::self()->name(i) == syncDisplayDevice) { syncScreenName = Screens::self()->name(i); syncScreen = i; break; } } } } rate = qRound(Screens::self()->refreshRate(syncScreen)); // TODO forward float precision? } else if (Xcb::Extensions::self()->isRandrAvailable()) { // last restort - query XRandR screenInfo rate - probably wrong on nvidia systems Xcb::RandR::ScreenInfo screenInfo(rootWindow()); rate = screenInfo->rate; } // 0Hz or less is invalid, so we fallback to a default rate if (rate <= 0) rate = 60; // and not shitty 50Hz for sure! *grrr* // QTimer gives us 1msec (1000Hz) at best, so we ignore anything higher; // however, additional throttling prevents very high rates from taking place anyway else if (rate > 1000) rate = 1000; qCDebug(KWIN_CORE) << "Vertical Refresh rate " << rate << "Hz (" << syncScreenName << ")"; return rate; } Options::Options(QObject *parent) : QObject(parent) , m_settings(new Settings(kwinApp()->config())) , m_focusPolicy(ClickToFocus) , m_nextFocusPrefersMouse(false) , m_clickRaise(false) , m_autoRaise(false) , m_autoRaiseInterval(0) , m_delayFocusInterval(0) , m_shadeHover(false) , m_shadeHoverInterval(0) , m_separateScreenFocus(false) , m_placement(Placement::NoPlacement) , m_borderSnapZone(0) , m_windowSnapZone(0) , m_centerSnapZone(0) , m_snapOnlyWhenOverlapping(false) , m_rollOverDesktops(false) , m_focusStealingPreventionLevel(0) , m_legacyFullscreenSupport(false) , m_killPingTimeout(0) , m_hideUtilityWindowsForInactive(false) , m_inactiveTabsSkipTaskbar(false) , m_autogroupSimilarWindows(false) , m_autogroupInForeground(false) , m_compositingMode(Options::defaultCompositingMode()) , m_useCompositing(Options::defaultUseCompositing()) , m_compositingInitialized(Options::defaultCompositingInitialized()) , m_hiddenPreviews(Options::defaultHiddenPreviews()) - , m_unredirectFullscreen(Options::defaultUnredirectFullscreen()) , m_glSmoothScale(Options::defaultGlSmoothScale()) , m_colorCorrected(Options::defaultColorCorrected()) , m_xrenderSmoothScale(Options::defaultXrenderSmoothScale()) , m_maxFpsInterval(Options::defaultMaxFpsInterval()) , m_refreshRate(Options::defaultRefreshRate()) , m_vBlankTime(Options::defaultVBlankTime()) , m_glStrictBinding(Options::defaultGlStrictBinding()) , m_glStrictBindingFollowsDriver(Options::defaultGlStrictBindingFollowsDriver()) , m_glCoreProfile(Options::defaultGLCoreProfile()) , m_glPreferBufferSwap(Options::defaultGlPreferBufferSwap()) , m_glPlatformInterface(Options::defaultGlPlatformInterface()) , OpTitlebarDblClick(Options::defaultOperationTitlebarDblClick()) , CmdActiveTitlebar1(Options::defaultCommandActiveTitlebar1()) , CmdActiveTitlebar2(Options::defaultCommandActiveTitlebar2()) , CmdActiveTitlebar3(Options::defaultCommandActiveTitlebar3()) , CmdInactiveTitlebar1(Options::defaultCommandInactiveTitlebar1()) , CmdInactiveTitlebar2(Options::defaultCommandInactiveTitlebar2()) , CmdInactiveTitlebar3(Options::defaultCommandInactiveTitlebar3()) , CmdTitlebarWheel(Options::defaultCommandTitlebarWheel()) , CmdWindow1(Options::defaultCommandWindow1()) , CmdWindow2(Options::defaultCommandWindow2()) , CmdWindow3(Options::defaultCommandWindow3()) , CmdWindowWheel(Options::defaultCommandWindowWheel()) , CmdAll1(Options::defaultCommandAll1()) , CmdAll2(Options::defaultCommandAll2()) , CmdAll3(Options::defaultCommandAll3()) , CmdAllWheel(Options::defaultCommandAllWheel()) , CmdAllModKey(Options::defaultKeyCmdAllModKey()) , electric_border_maximize(false) , electric_border_tiling(false) , electric_border_corner_ratio(0.0) , borderless_maximized_windows(false) , show_geometry_tip(false) , condensed_title(false) , animationSpeed(Options::defaultAnimationSpeed()) { m_settings->setDefaults(); syncFromKcfgc(); } Options::~Options() { } void Options::setFocusPolicy(FocusPolicy focusPolicy) { if (m_focusPolicy == focusPolicy) { return; } m_focusPolicy = focusPolicy; emit focusPolicyChanged(); if (m_focusPolicy == ClickToFocus) { setAutoRaise(false); setAutoRaiseInterval(0); setDelayFocusInterval(0); } } void Options::setNextFocusPrefersMouse(bool nextFocusPrefersMouse) { if (m_nextFocusPrefersMouse == nextFocusPrefersMouse) { return; } m_nextFocusPrefersMouse = nextFocusPrefersMouse; emit nextFocusPrefersMouseChanged(); } void Options::setClickRaise(bool clickRaise) { if (m_autoRaise) { // important: autoRaise implies ClickRaise clickRaise = true; } if (m_clickRaise == clickRaise) { return; } m_clickRaise = clickRaise; emit clickRaiseChanged(); } void Options::setAutoRaise(bool autoRaise) { if (m_focusPolicy == ClickToFocus) { autoRaise = false; } if (m_autoRaise == autoRaise) { return; } m_autoRaise = autoRaise; if (m_autoRaise) { // important: autoRaise implies ClickRaise setClickRaise(true); } emit autoRaiseChanged(); } void Options::setAutoRaiseInterval(int autoRaiseInterval) { if (m_focusPolicy == ClickToFocus) { autoRaiseInterval = 0; } if (m_autoRaiseInterval == autoRaiseInterval) { return; } m_autoRaiseInterval = autoRaiseInterval; emit autoRaiseIntervalChanged(); } void Options::setDelayFocusInterval(int delayFocusInterval) { if (m_focusPolicy == ClickToFocus) { delayFocusInterval = 0; } if (m_delayFocusInterval == delayFocusInterval) { return; } m_delayFocusInterval = delayFocusInterval; emit delayFocusIntervalChanged(); } void Options::setShadeHover(bool shadeHover) { if (m_shadeHover == shadeHover) { return; } m_shadeHover = shadeHover; emit shadeHoverChanged(); } void Options::setShadeHoverInterval(int shadeHoverInterval) { if (m_shadeHoverInterval == shadeHoverInterval) { return; } m_shadeHoverInterval = shadeHoverInterval; emit shadeHoverIntervalChanged(); } void Options::setSeparateScreenFocus(bool separateScreenFocus) { if (m_separateScreenFocus == separateScreenFocus) { return; } m_separateScreenFocus = separateScreenFocus; emit separateScreenFocusChanged(m_separateScreenFocus); } void Options::setPlacement(int placement) { if (m_placement == static_cast(placement)) { return; } m_placement = static_cast(placement); emit placementChanged(); } void Options::setBorderSnapZone(int borderSnapZone) { if (m_borderSnapZone == borderSnapZone) { return; } m_borderSnapZone = borderSnapZone; emit borderSnapZoneChanged(); } void Options::setWindowSnapZone(int windowSnapZone) { if (m_windowSnapZone == windowSnapZone) { return; } m_windowSnapZone = windowSnapZone; emit windowSnapZoneChanged(); } void Options::setCenterSnapZone(int centerSnapZone) { if (m_centerSnapZone == centerSnapZone) { return; } m_centerSnapZone = centerSnapZone; emit centerSnapZoneChanged(); } void Options::setSnapOnlyWhenOverlapping(bool snapOnlyWhenOverlapping) { if (m_snapOnlyWhenOverlapping == snapOnlyWhenOverlapping) { return; } m_snapOnlyWhenOverlapping = snapOnlyWhenOverlapping; emit snapOnlyWhenOverlappingChanged(); } void Options::setRollOverDesktops(bool rollOverDesktops) { if (m_rollOverDesktops == rollOverDesktops) { return; } m_rollOverDesktops = rollOverDesktops; emit rollOverDesktopsChanged(m_rollOverDesktops); } void Options::setFocusStealingPreventionLevel(int focusStealingPreventionLevel) { if (!focusPolicyIsReasonable()) { focusStealingPreventionLevel = 0; } if (m_focusStealingPreventionLevel == focusStealingPreventionLevel) { return; } m_focusStealingPreventionLevel = qMax(0, qMin(4, focusStealingPreventionLevel)); emit focusStealingPreventionLevelChanged(); } void Options::setLegacyFullscreenSupport(bool legacyFullscreenSupport) { if (m_legacyFullscreenSupport == legacyFullscreenSupport) { return; } m_legacyFullscreenSupport = legacyFullscreenSupport; emit legacyFullscreenSupportChanged(); } void Options::setOperationTitlebarDblClick(WindowOperation operationTitlebarDblClick) { if (OpTitlebarDblClick == operationTitlebarDblClick) { return; } OpTitlebarDblClick = operationTitlebarDblClick; emit operationTitlebarDblClickChanged(); } void Options::setOperationMaxButtonLeftClick(WindowOperation op) { if (opMaxButtonLeftClick == op) { return; } opMaxButtonLeftClick = op; emit operationMaxButtonLeftClickChanged(); } void Options::setOperationMaxButtonRightClick(WindowOperation op) { if (opMaxButtonRightClick == op) { return; } opMaxButtonRightClick = op; emit operationMaxButtonRightClickChanged(); } void Options::setOperationMaxButtonMiddleClick(WindowOperation op) { if (opMaxButtonMiddleClick == op) { return; } opMaxButtonMiddleClick = op; emit operationMaxButtonMiddleClickChanged(); } void Options::setCommandActiveTitlebar1(MouseCommand commandActiveTitlebar1) { if (CmdActiveTitlebar1 == commandActiveTitlebar1) { return; } CmdActiveTitlebar1 = commandActiveTitlebar1; emit commandActiveTitlebar1Changed(); } void Options::setCommandActiveTitlebar2(MouseCommand commandActiveTitlebar2) { if (CmdActiveTitlebar2 == commandActiveTitlebar2) { return; } CmdActiveTitlebar2 = commandActiveTitlebar2; emit commandActiveTitlebar2Changed(); } void Options::setCommandActiveTitlebar3(MouseCommand commandActiveTitlebar3) { if (CmdActiveTitlebar3 == commandActiveTitlebar3) { return; } CmdActiveTitlebar3 = commandActiveTitlebar3; emit commandActiveTitlebar3Changed(); } void Options::setCommandInactiveTitlebar1(MouseCommand commandInactiveTitlebar1) { if (CmdInactiveTitlebar1 == commandInactiveTitlebar1) { return; } CmdInactiveTitlebar1 = commandInactiveTitlebar1; emit commandInactiveTitlebar1Changed(); } void Options::setCommandInactiveTitlebar2(MouseCommand commandInactiveTitlebar2) { if (CmdInactiveTitlebar2 == commandInactiveTitlebar2) { return; } CmdInactiveTitlebar2 = commandInactiveTitlebar2; emit commandInactiveTitlebar2Changed(); } void Options::setCommandInactiveTitlebar3(MouseCommand commandInactiveTitlebar3) { if (CmdInactiveTitlebar3 == commandInactiveTitlebar3) { return; } CmdInactiveTitlebar3 = commandInactiveTitlebar3; emit commandInactiveTitlebar3Changed(); } void Options::setCommandWindow1(MouseCommand commandWindow1) { if (CmdWindow1 == commandWindow1) { return; } CmdWindow1 = commandWindow1; emit commandWindow1Changed(); } void Options::setCommandWindow2(MouseCommand commandWindow2) { if (CmdWindow2 == commandWindow2) { return; } CmdWindow2 = commandWindow2; emit commandWindow2Changed(); } void Options::setCommandWindow3(MouseCommand commandWindow3) { if (CmdWindow3 == commandWindow3) { return; } CmdWindow3 = commandWindow3; emit commandWindow3Changed(); } void Options::setCommandWindowWheel(MouseCommand commandWindowWheel) { if (CmdWindowWheel == commandWindowWheel) { return; } CmdWindowWheel = commandWindowWheel; emit commandWindowWheelChanged(); } void Options::setCommandAll1(MouseCommand commandAll1) { if (CmdAll1 == commandAll1) { return; } CmdAll1 = commandAll1; emit commandAll1Changed(); } void Options::setCommandAll2(MouseCommand commandAll2) { if (CmdAll2 == commandAll2) { return; } CmdAll2 = commandAll2; emit commandAll2Changed(); } void Options::setCommandAll3(MouseCommand commandAll3) { if (CmdAll3 == commandAll3) { return; } CmdAll3 = commandAll3; emit commandAll3Changed(); } void Options::setKeyCmdAllModKey(uint keyCmdAllModKey) { if (CmdAllModKey == keyCmdAllModKey) { return; } CmdAllModKey = keyCmdAllModKey; emit keyCmdAllModKeyChanged(); } void Options::setShowGeometryTip(bool showGeometryTip) { if (show_geometry_tip == showGeometryTip) { return; } show_geometry_tip = showGeometryTip; emit showGeometryTipChanged(); } void Options::setCondensedTitle(bool condensedTitle) { if (condensed_title == condensedTitle) { return; } condensed_title = condensedTitle; emit condensedTitleChanged(); } void Options::setElectricBorderMaximize(bool electricBorderMaximize) { if (electric_border_maximize == electricBorderMaximize) { return; } electric_border_maximize = electricBorderMaximize; emit electricBorderMaximizeChanged(); } void Options::setElectricBorderTiling(bool electricBorderTiling) { if (electric_border_tiling == electricBorderTiling) { return; } electric_border_tiling = electricBorderTiling; emit electricBorderTilingChanged(); } void Options::setElectricBorderCornerRatio(float electricBorderCornerRatio) { if (electric_border_corner_ratio == electricBorderCornerRatio) { return; } electric_border_corner_ratio = electricBorderCornerRatio; emit electricBorderCornerRatioChanged(); } void Options::setBorderlessMaximizedWindows(bool borderlessMaximizedWindows) { if (borderless_maximized_windows == borderlessMaximizedWindows) { return; } borderless_maximized_windows = borderlessMaximizedWindows; emit borderlessMaximizedWindowsChanged(); } void Options::setKillPingTimeout(int killPingTimeout) { if (m_killPingTimeout == killPingTimeout) { return; } m_killPingTimeout = killPingTimeout; emit killPingTimeoutChanged(); } void Options::setHideUtilityWindowsForInactive(bool hideUtilityWindowsForInactive) { if (m_hideUtilityWindowsForInactive == hideUtilityWindowsForInactive) { return; } m_hideUtilityWindowsForInactive = hideUtilityWindowsForInactive; emit hideUtilityWindowsForInactiveChanged(); } void Options::setInactiveTabsSkipTaskbar(bool inactiveTabsSkipTaskbar) { if (m_inactiveTabsSkipTaskbar == inactiveTabsSkipTaskbar) { return; } m_inactiveTabsSkipTaskbar = inactiveTabsSkipTaskbar; emit inactiveTabsSkipTaskbarChanged(); } void Options::setAutogroupSimilarWindows(bool autogroupSimilarWindows) { if (m_autogroupSimilarWindows == autogroupSimilarWindows) { return; } m_autogroupSimilarWindows = autogroupSimilarWindows; emit autogroupSimilarWindowsChanged(); } void Options::setAutogroupInForeground(bool autogroupInForeground) { if (m_autogroupInForeground == autogroupInForeground) { return; } m_autogroupInForeground = autogroupInForeground; emit autogroupInForegroundChanged(); } void Options::setCompositingMode(int compositingMode) { if (m_compositingMode == static_cast(compositingMode)) { return; } m_compositingMode = static_cast(compositingMode); emit compositingModeChanged(); } void Options::setUseCompositing(bool useCompositing) { if (m_useCompositing == useCompositing) { return; } m_useCompositing = useCompositing; emit useCompositingChanged(); } void Options::setCompositingInitialized(bool compositingInitialized) { if (m_compositingInitialized == compositingInitialized) { return; } m_compositingInitialized = compositingInitialized; emit compositingInitializedChanged(); } void Options::setHiddenPreviews(int hiddenPreviews) { if (m_hiddenPreviews == static_cast(hiddenPreviews)) { return; } m_hiddenPreviews = static_cast(hiddenPreviews); emit hiddenPreviewsChanged(); } -void Options::setUnredirectFullscreen(bool unredirectFullscreen) -{ - if (GLPlatform::instance()->driver() == Driver_Intel) - unredirectFullscreen = false; // bug #252817 - if (m_unredirectFullscreen == unredirectFullscreen) { - return; - } - if (GLPlatform::instance()->driver() == Driver_Intel) { // write back the value - KConfigGroup(m_settings->config(), "Compositing").writeEntry("UnredirectFullscreen", false); - } - m_unredirectFullscreen = unredirectFullscreen; - emit unredirectFullscreenChanged(); -} - void Options::setGlSmoothScale(int glSmoothScale) { if (m_glSmoothScale == glSmoothScale) { return; } m_glSmoothScale = glSmoothScale; emit glSmoothScaleChanged(); } void Options::setColorCorrected(bool colorCorrected) { if (m_colorCorrected == colorCorrected) { return; } m_colorCorrected = colorCorrected; emit colorCorrectedChanged(); } void Options::setXrenderSmoothScale(bool xrenderSmoothScale) { if (m_xrenderSmoothScale == xrenderSmoothScale) { return; } m_xrenderSmoothScale = xrenderSmoothScale; emit xrenderSmoothScaleChanged(); } void Options::setMaxFpsInterval(qint64 maxFpsInterval) { if (m_maxFpsInterval == maxFpsInterval) { return; } m_maxFpsInterval = maxFpsInterval; emit maxFpsIntervalChanged(); } void Options::setRefreshRate(uint refreshRate) { if (m_refreshRate == refreshRate) { return; } m_refreshRate = refreshRate; emit refreshRateChanged(); } void Options::setVBlankTime(qint64 vBlankTime) { if (m_vBlankTime == vBlankTime) { return; } m_vBlankTime = vBlankTime; emit vBlankTimeChanged(); } void Options::setGlStrictBinding(bool glStrictBinding) { if (m_glStrictBinding == glStrictBinding) { return; } m_glStrictBinding = glStrictBinding; emit glStrictBindingChanged(); } void Options::setGlStrictBindingFollowsDriver(bool glStrictBindingFollowsDriver) { if (m_glStrictBindingFollowsDriver == glStrictBindingFollowsDriver) { return; } m_glStrictBindingFollowsDriver = glStrictBindingFollowsDriver; emit glStrictBindingFollowsDriverChanged(); } void Options::setGLCoreProfile(bool value) { if (m_glCoreProfile == value) { return; } m_glCoreProfile = value; emit glCoreProfileChanged(); } void Options::setGlPreferBufferSwap(char glPreferBufferSwap) { if (glPreferBufferSwap == 'a') { // buffer cpying is very fast with the nvidia blob // but due to restrictions in DRI2 *incredibly* slow for all MESA drivers // see http://www.x.org/releases/X11R7.7/doc/dri2proto/dri2proto.txt, item 2.5 if (GLPlatform::instance()->driver() == Driver_NVidia) glPreferBufferSwap = CopyFrontBuffer; else if (GLPlatform::instance()->driver() != Driver_Unknown) // undetected, finally resolved when context is initialized glPreferBufferSwap = ExtendDamage; } if (m_glPreferBufferSwap == (GlSwapStrategy)glPreferBufferSwap) { return; } m_glPreferBufferSwap = (GlSwapStrategy)glPreferBufferSwap; emit glPreferBufferSwapChanged(); } void Options::setGlPlatformInterface(OpenGLPlatformInterface interface) { // check environment variable const QByteArray envOpenGLInterface(qgetenv("KWIN_OPENGL_INTERFACE")); if (!envOpenGLInterface.isEmpty()) { if (qstrcmp(envOpenGLInterface, "egl") == 0) { qCDebug(KWIN_CORE) << "Forcing EGL native interface through environment variable"; interface = EglPlatformInterface; } else if (qstrcmp(envOpenGLInterface, "glx") == 0) { qCDebug(KWIN_CORE) << "Forcing GLX native interface through environment variable"; interface = GlxPlatformInterface; } } if (kwinApp()->shouldUseWaylandForCompositing() && interface == GlxPlatformInterface) { // Glx is impossible on Wayland, enforce egl qCDebug(KWIN_CORE) << "Forcing EGL native interface for Wayland mode"; interface = EglPlatformInterface; } #if !HAVE_EPOXY_GLX qCDebug(KWIN_CORE) << "Forcing EGL native interface as compiled without GLX support"; interface = EglPlatformInterface; #endif if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES) { qCDebug(KWIN_CORE) << "Forcing EGL native interface as Qt uses OpenGL ES"; interface = EglPlatformInterface; } else if (qstrcmp(qgetenv("KWIN_COMPOSE"), "O2ES") == 0) { qCDebug(KWIN_CORE) << "Forcing EGL native interface as OpenGL ES requested through KWIN_COMPOSE environment variable."; interface = EglPlatformInterface; } if (m_glPlatformInterface == interface) { return; } m_glPlatformInterface = interface; emit glPlatformInterfaceChanged(); } void Options::reparseConfiguration() { m_settings->config()->reparseConfiguration(); } void Options::updateSettings() { loadConfig(); // Read button tooltip animation effect from kdeglobals // Since we want to allow users to enable window decoration tooltips // and not kstyle tooltips and vise-versa, we don't read the // "EffectNoTooltip" setting from kdeglobals. // QToolTip::setGloballyEnabled( d->show_tooltips ); // KDE4 this probably needs to be done manually in clients // Driver-specific config detection setCompositingInitialized(false); reloadCompositingSettings(); emit configChanged(); } void Options::loadConfig() { m_settings->load(); syncFromKcfgc(); // Electric borders KConfigGroup config(m_settings->config(), "Windows"); OpTitlebarDblClick = windowOperation(config.readEntry("TitlebarDoubleClickCommand", "Maximize"), true); setOperationMaxButtonLeftClick(windowOperation(config.readEntry("MaximizeButtonLeftClickCommand", "Maximize"), true)); setOperationMaxButtonMiddleClick(windowOperation(config.readEntry("MaximizeButtonMiddleClickCommand", "Maximize (vertical only)"), true)); setOperationMaxButtonRightClick(windowOperation(config.readEntry("MaximizeButtonRightClickCommand", "Maximize (horizontal only)"), true)); // Mouse bindings config = KConfigGroup(m_settings->config(), "MouseBindings"); // TODO: add properties for missing options CmdTitlebarWheel = mouseWheelCommand(config.readEntry("CommandTitlebarWheel", "Switch to Window Tab to the Left/Right")); CmdAllModKey = (config.readEntry("CommandAllKey", "Alt") == QStringLiteral("Meta")) ? Qt::Key_Meta : Qt::Key_Alt; CmdAllWheel = mouseWheelCommand(config.readEntry("CommandAllWheel", "Nothing")); setCommandActiveTitlebar1(mouseCommand(config.readEntry("CommandActiveTitlebar1", "Raise"), true)); setCommandActiveTitlebar2(mouseCommand(config.readEntry("CommandActiveTitlebar2", "Start Window Tab Drag"), true)); setCommandActiveTitlebar3(mouseCommand(config.readEntry("CommandActiveTitlebar3", "Operations menu"), true)); setCommandInactiveTitlebar1(mouseCommand(config.readEntry("CommandInactiveTitlebar1", "Activate and raise"), true)); setCommandInactiveTitlebar2(mouseCommand(config.readEntry("CommandInactiveTitlebar2", "Start Window Tab Drag"), true)); setCommandInactiveTitlebar3(mouseCommand(config.readEntry("CommandInactiveTitlebar3", "Operations menu"), true)); setCommandWindow1(mouseCommand(config.readEntry("CommandWindow1", "Activate, raise and pass click"), false)); setCommandWindow2(mouseCommand(config.readEntry("CommandWindow2", "Activate and pass click"), false)); setCommandWindow3(mouseCommand(config.readEntry("CommandWindow3", "Activate and pass click"), false)); setCommandWindowWheel(mouseCommand(config.readEntry("CommandWindowWheel", "Scroll"), false)); setCommandAll1(mouseCommand(config.readEntry("CommandAll1", "Move"), false)); setCommandAll2(mouseCommand(config.readEntry("CommandAll2", "Toggle raise and lower"), false)); setCommandAll3(mouseCommand(config.readEntry("CommandAll3", "Resize"), false)); // TODO: should they be moved into reloadCompositingSettings? config = KConfigGroup(m_settings->config(), "Compositing"); setMaxFpsInterval(1 * 1000 * 1000 * 1000 / config.readEntry("MaxFPS", Options::defaultMaxFps())); setRefreshRate(config.readEntry("RefreshRate", Options::defaultRefreshRate())); setVBlankTime(config.readEntry("VBlankTime", Options::defaultVBlankTime()) * 1000); // config in micro, value in nano resolution // Modifier Only Shortcuts config = KConfigGroup(m_settings->config(), "ModifierOnlyShortcuts"); m_modifierOnlyShortcuts.clear(); if (config.hasKey("Shift")) { m_modifierOnlyShortcuts.insert(Qt::ShiftModifier, config.readEntry("Shift", QStringList())); } if (config.hasKey("Control")) { m_modifierOnlyShortcuts.insert(Qt::ControlModifier, config.readEntry("Control", QStringList())); } if (config.hasKey("Alt")) { m_modifierOnlyShortcuts.insert(Qt::AltModifier, config.readEntry("Alt", QStringList())); } if (config.hasKey("Meta")) { m_modifierOnlyShortcuts.insert(Qt::MetaModifier, config.readEntry("Meta", QStringList())); } } void Options::syncFromKcfgc() { setShowGeometryTip(m_settings->geometryTip()); setCondensedTitle(m_settings->condensedTitle()); setFocusPolicy(m_settings->focusPolicy()); setNextFocusPrefersMouse(m_settings->nextFocusPrefersMouse()); setSeparateScreenFocus(m_settings->separateScreenFocus()); setRollOverDesktops(m_settings->rollOverDesktops()); setLegacyFullscreenSupport(m_settings->legacyFullscreenSupport()); setFocusStealingPreventionLevel(m_settings->focusStealingPreventionLevel()); #ifdef KWIN_BUILD_DECORATIONS setPlacement(m_settings->placement()); #else setPlacement(Placement::Maximizing); #endif setAutoRaise(m_settings->autoRaise()); setAutoRaiseInterval(m_settings->autoRaiseInterval()); setDelayFocusInterval(m_settings->delayFocusInterval()); setShadeHover(m_settings->shadeHover()); setShadeHoverInterval(m_settings->shadeHoverInterval()); setClickRaise(m_settings->clickRaise()); setBorderSnapZone(m_settings->borderSnapZone()); setWindowSnapZone(m_settings->windowSnapZone()); setCenterSnapZone(m_settings->centerSnapZone()); setSnapOnlyWhenOverlapping(m_settings->snapOnlyWhenOverlapping()); setKillPingTimeout(m_settings->killPingTimeout()); setHideUtilityWindowsForInactive(m_settings->hideUtilityWindowsForInactive()); setInactiveTabsSkipTaskbar(m_settings->inactiveTabsSkipTaskbar()); setAutogroupSimilarWindows(m_settings->autogroupSimilarWindows()); setAutogroupInForeground(m_settings->autogroupInForeground()); setBorderlessMaximizedWindows(m_settings->borderlessMaximizedWindows()); setElectricBorderMaximize(m_settings->electricBorderMaximize()); setElectricBorderTiling(m_settings->electricBorderTiling()); setElectricBorderCornerRatio(m_settings->electricBorderCornerRatio()); } bool Options::loadCompositingConfig (bool force) { KConfigGroup config(m_settings->config(), "Compositing"); bool useCompositing = false; CompositingType compositingMode = NoCompositing; QString compositingBackend = config.readEntry("Backend", "OpenGL"); if (compositingBackend == QStringLiteral("XRender")) compositingMode = XRenderCompositing; else if (compositingBackend == "QPainter") compositingMode = QPainterCompositing; else compositingMode = OpenGLCompositing; if (const char *c = getenv("KWIN_COMPOSE")) { switch(c[0]) { case 'O': qCDebug(KWIN_CORE) << "Compositing forced to OpenGL mode by environment variable"; compositingMode = OpenGLCompositing; useCompositing = true; break; case 'X': qCDebug(KWIN_CORE) << "Compositing forced to XRender mode by environment variable"; compositingMode = XRenderCompositing; useCompositing = true; break; case 'Q': qCDebug(KWIN_CORE) << "Compositing forced to QPainter mode by environment variable"; compositingMode = QPainterCompositing; useCompositing = true; break; case 'N': if (getenv("KDE_FAILSAFE")) qCDebug(KWIN_CORE) << "Compositing disabled forcefully by KDE failsafe mode"; else qCDebug(KWIN_CORE) << "Compositing disabled forcefully by environment variable"; compositingMode = NoCompositing; break; default: qCDebug(KWIN_CORE) << "Unknown KWIN_COMPOSE mode set, ignoring"; break; } } if (kwinApp()->shouldUseWaylandForCompositing() && (compositingMode == XRenderCompositing || compositingMode == NoCompositing)) { qCDebug(KWIN_CORE) << "Compositing forced to QPainter mode by invalid compositor selection"; compositingMode = QPainterCompositing; } setCompositingMode(compositingMode); if (m_compositingMode == NoCompositing) { setUseCompositing(false); return false; // do not even detect compositing preferences if explicitly disabled } // it's either enforced by env or by initial resume from "suspend" or we check the settings setUseCompositing(useCompositing || force || config.readEntry("Enabled", Options::defaultUseCompositing())); if (!m_useCompositing) return false; // not enforced or necessary and not "enabled" by settings return true; } void Options::reloadCompositingSettings(bool force) { if (!loadCompositingConfig(force)) { return; } // from now on we've an initial setup and don't have to reload settings on compositing activation // see Workspace::setupCompositing(), composite.cpp setCompositingInitialized(true); // Compositing settings KConfigGroup config(m_settings->config(), "Compositing"); setGlSmoothScale(qBound(-1, config.readEntry("GLTextureFilter", Options::defaultGlSmoothScale()), 2)); setGlStrictBindingFollowsDriver(!config.hasKey("GLStrictBinding")); if (!isGlStrictBindingFollowsDriver()) { setGlStrictBinding(config.readEntry("GLStrictBinding", Options::defaultGlStrictBinding())); } setGLCoreProfile(config.readEntry("GLCore", Options::defaultGLCoreProfile())); char c = 0; const QString s = config.readEntry("GLPreferBufferSwap", QString(Options::defaultGlPreferBufferSwap())); if (!s.isEmpty()) c = s.at(0).toAscii(); if (c != 'a' && c != 'c' && c != 'p' && c != 'e') c = 0; setGlPreferBufferSwap(c); setColorCorrected(config.readEntry("GLColorCorrection", Options::defaultColorCorrected())); m_xrenderSmoothScale = config.readEntry("XRenderSmoothScale", false); HiddenPreviews previews = Options::defaultHiddenPreviews(); // 4 - off, 5 - shown, 6 - always, other are old values int hps = config.readEntry("HiddenPreviews", 5); if (hps == 4) previews = HiddenPreviewsNever; else if (hps == 5) previews = HiddenPreviewsShown; else if (hps == 6) previews = HiddenPreviewsAlways; setHiddenPreviews(previews); - setUnredirectFullscreen(config.readEntry("UnredirectFullscreen", Options::defaultUnredirectFullscreen())); // TOOD: add setter animationSpeed = qBound(0, config.readEntry("AnimationSpeed", Options::defaultAnimationSpeed()), 6); auto interfaceToKey = [](OpenGLPlatformInterface interface) { switch (interface) { case GlxPlatformInterface: return QStringLiteral("glx"); case EglPlatformInterface: return QStringLiteral("egl"); default: return QString(); } }; auto keyToInterface = [](const QString &key) { if (key == QStringLiteral("glx")) { return GlxPlatformInterface; } else if (key == QStringLiteral("egl")) { return EglPlatformInterface; } return defaultGlPlatformInterface(); }; setGlPlatformInterface(keyToInterface(config.readEntry("GLPlatformInterface", interfaceToKey(m_glPlatformInterface)))); } // restricted should be true for operations that the user may not be able to repeat // if the window is moved out of the workspace (e.g. if the user moves a window // by the titlebar, and moves it too high beneath Kicker at the top edge, they // may not be able to move it back, unless they know about Alt+LMB) Options::WindowOperation Options::windowOperation(const QString &name, bool restricted) { if (name == QStringLiteral("Move")) return restricted ? MoveOp : UnrestrictedMoveOp; else if (name == QStringLiteral("Resize")) return restricted ? ResizeOp : UnrestrictedResizeOp; else if (name == QStringLiteral("Maximize")) return MaximizeOp; else if (name == QStringLiteral("Minimize")) return MinimizeOp; else if (name == QStringLiteral("Close")) return CloseOp; else if (name == QStringLiteral("OnAllDesktops")) return OnAllDesktopsOp; else if (name == QStringLiteral("Shade")) return ShadeOp; else if (name == QStringLiteral("Operations")) return OperationsOp; else if (name == QStringLiteral("Maximize (vertical only)")) return VMaximizeOp; else if (name == QStringLiteral("Maximize (horizontal only)")) return HMaximizeOp; else if (name == QStringLiteral("Lower")) return LowerOp; return NoOp; } Options::MouseCommand Options::mouseCommand(const QString &name, bool restricted) { QString lowerName = name.toLower(); if (lowerName == QStringLiteral("raise")) return MouseRaise; if (lowerName == QStringLiteral("lower")) return MouseLower; if (lowerName == QStringLiteral("operations menu")) return MouseOperationsMenu; if (lowerName == QStringLiteral("toggle raise and lower")) return MouseToggleRaiseAndLower; if (lowerName == QStringLiteral("activate and raise")) return MouseActivateAndRaise; if (lowerName == QStringLiteral("activate and lower")) return MouseActivateAndLower; if (lowerName == QStringLiteral("activate")) return MouseActivate; if (lowerName == QStringLiteral("activate, raise and pass click")) return MouseActivateRaiseAndPassClick; if (lowerName == QStringLiteral("activate and pass click")) return MouseActivateAndPassClick; if (lowerName == QStringLiteral("scroll")) return MouseNothing; if (lowerName == QStringLiteral("activate and scroll")) return MouseActivateAndPassClick; if (lowerName == QStringLiteral("activate, raise and scroll")) return MouseActivateRaiseAndPassClick; if (lowerName == QStringLiteral("activate, raise and move")) return restricted ? MouseActivateRaiseAndMove : MouseActivateRaiseAndUnrestrictedMove; if (lowerName == QStringLiteral("move")) return restricted ? MouseMove : MouseUnrestrictedMove; if (lowerName == QStringLiteral("resize")) return restricted ? MouseResize : MouseUnrestrictedResize; if (lowerName == QStringLiteral("shade")) return MouseShade; if (lowerName == QStringLiteral("minimize")) return MouseMinimize; if (lowerName == QStringLiteral("start window tab drag")) return MouseDragTab; if (lowerName == QStringLiteral("close")) return MouseClose; if (lowerName == QStringLiteral("increase opacity")) return MouseOpacityMore; if (lowerName == QStringLiteral("decrease opacity")) return MouseOpacityLess; if (lowerName == QStringLiteral("nothing")) return MouseNothing; return MouseNothing; } Options::MouseWheelCommand Options::mouseWheelCommand(const QString &name) { QString lowerName = name.toLower(); if (lowerName == QStringLiteral("raise/lower")) return MouseWheelRaiseLower; if (lowerName == QStringLiteral("shade/unshade")) return MouseWheelShadeUnshade; if (lowerName == QStringLiteral("maximize/restore")) return MouseWheelMaximizeRestore; if (lowerName == QStringLiteral("above/below")) return MouseWheelAboveBelow; if (lowerName == QStringLiteral("previous/next desktop")) return MouseWheelPreviousNextDesktop; if (lowerName == QStringLiteral("change opacity")) return MouseWheelChangeOpacity; if (lowerName == QStringLiteral("switch to window tab to the left/right")) return MouseWheelChangeCurrentTab; if (lowerName == QStringLiteral("nothing")) return MouseWheelNothing; return MouseWheelChangeCurrentTab; } bool Options::showGeometryTip() const { return show_geometry_tip; } bool Options::condensedTitle() const { return condensed_title; } Options::MouseCommand Options::wheelToMouseCommand(MouseWheelCommand com, int delta) const { switch(com) { case MouseWheelRaiseLower: return delta > 0 ? MouseRaise : MouseLower; case MouseWheelShadeUnshade: return delta > 0 ? MouseSetShade : MouseUnsetShade; case MouseWheelMaximizeRestore: return delta > 0 ? MouseMaximize : MouseRestore; case MouseWheelAboveBelow: return delta > 0 ? MouseAbove : MouseBelow; case MouseWheelPreviousNextDesktop: return delta > 0 ? MousePreviousDesktop : MouseNextDesktop; case MouseWheelChangeOpacity: return delta > 0 ? MouseOpacityMore : MouseOpacityLess; case MouseWheelChangeCurrentTab: return delta > 0 ? MousePreviousTab : MouseNextTab; default: return MouseNothing; } } #endif double Options::animationTimeFactor() const { const double factors[] = { 0, 0.2, 0.5, 1, 2, 4, 20 }; return factors[ animationSpeed ]; } Options::WindowOperation Options::operationMaxButtonClick(Qt::MouseButtons button) const { return button == Qt::RightButton ? opMaxButtonRightClick : button == Qt::MidButton ? opMaxButtonMiddleClick : opMaxButtonLeftClick; } QStringList Options::modifierOnlyDBusShortcut(Qt::KeyboardModifier mod) const { return m_modifierOnlyShortcuts.value(mod); } bool Options::isUseCompositing() const { return m_useCompositing || kwinApp()->platform()->requiresCompositing(); } - -bool Options::isUnredirectFullscreen() const -{ - return m_unredirectFullscreen && !kwinApp()->platform()->requiresCompositing(); -} - } // namespace diff --git a/options.h b/options.h index 30c3899b8..7f4bb0e67 100644 --- a/options.h +++ b/options.h @@ -1,952 +1,944 @@ /******************************************************************** 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) 2012 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 KWIN_OPTIONS_H #define KWIN_OPTIONS_H #include "main.h" #include "placement.h" namespace KWin { // Whether to keep all windows mapped when compositing (i.e. whether to have // actively updated window pixmaps). enum HiddenPreviews { // The normal mode with regard to mapped windows. Hidden (minimized, etc.) // and windows on inactive virtual desktops are not mapped, their pixmaps // are only their icons. HiddenPreviewsNever, // Like normal mode, but shown windows (i.e. on inactive virtual desktops) // are kept mapped, only hidden windows are unmapped. HiddenPreviewsShown, // All windows are kept mapped regardless of their state. HiddenPreviewsAlways }; class Settings; class KWIN_EXPORT Options : public QObject { Q_OBJECT Q_ENUMS(FocusPolicy) Q_ENUMS(GlSwapStrategy) Q_ENUMS(MouseCommand) Q_ENUMS(MouseWheelCommand) Q_ENUMS(WindowOperation) Q_PROPERTY(FocusPolicy focusPolicy READ focusPolicy WRITE setFocusPolicy NOTIFY focusPolicyChanged) Q_PROPERTY(bool nextFocusPrefersMouse READ isNextFocusPrefersMouse WRITE setNextFocusPrefersMouse NOTIFY nextFocusPrefersMouseChanged) /** Whether clicking on a window raises it in FocusFollowsMouse mode or not. */ Q_PROPERTY(bool clickRaise READ isClickRaise WRITE setClickRaise NOTIFY clickRaiseChanged) /** whether autoraise is enabled FocusFollowsMouse mode or not. */ Q_PROPERTY(bool autoRaise READ isAutoRaise WRITE setAutoRaise NOTIFY autoRaiseChanged) /** autoraise interval */ Q_PROPERTY(int autoRaiseInterval READ autoRaiseInterval WRITE setAutoRaiseInterval NOTIFY autoRaiseIntervalChanged) /** delayed focus interval */ Q_PROPERTY(int delayFocusInterval READ delayFocusInterval WRITE setDelayFocusInterval NOTIFY delayFocusIntervalChanged) /** Whether shade hover is enabled or not */ Q_PROPERTY(bool shadeHover READ isShadeHover WRITE setShadeHover NOTIFY shadeHoverChanged) /** shade hover interval */ Q_PROPERTY(int shadeHoverInterval READ shadeHoverInterval WRITE setShadeHoverInterval NOTIFY shadeHoverIntervalChanged) /** * whether to see Xinerama screens separately for focus (in Alt+Tab, when activating next client) **/ Q_PROPERTY(bool separateScreenFocus READ isSeparateScreenFocus WRITE setSeparateScreenFocus NOTIFY separateScreenFocusChanged) Q_PROPERTY(int placement READ placement WRITE setPlacement NOTIFY placementChanged) Q_PROPERTY(bool focusPolicyIsReasonable READ focusPolicyIsReasonable NOTIFY focusPolicyIsResonableChanged) /** * the size of the zone that triggers snapping on desktop borders */ Q_PROPERTY(int borderSnapZone READ borderSnapZone WRITE setBorderSnapZone NOTIFY borderSnapZoneChanged) /** * the size of the zone that triggers snapping with other windows */ Q_PROPERTY(int windowSnapZone READ windowSnapZone WRITE setWindowSnapZone NOTIFY windowSnapZoneChanged) /** * the size of the zone that triggers snapping on the screen center */ Q_PROPERTY(int centerSnapZone READ centerSnapZone WRITE setCenterSnapZone NOTIFY centerSnapZoneChanged) /** * snap only when windows will overlap */ Q_PROPERTY(bool snapOnlyWhenOverlapping READ isSnapOnlyWhenOverlapping WRITE setSnapOnlyWhenOverlapping NOTIFY snapOnlyWhenOverlappingChanged) /** * whether or not we roll over to the other edge when switching desktops past the edge */ Q_PROPERTY(bool rollOverDesktops READ isRollOverDesktops WRITE setRollOverDesktops NOTIFY rollOverDesktopsChanged) /** * 0 - 4 , see Workspace::allowClientActivation() **/ Q_PROPERTY(int focusStealingPreventionLevel READ focusStealingPreventionLevel WRITE setFocusStealingPreventionLevel NOTIFY focusStealingPreventionLevelChanged) /** * support legacy fullscreen windows hack: borderless non-netwm windows with screen geometry */ Q_PROPERTY(bool legacyFullscreenSupport READ isLegacyFullscreenSupport WRITE setLegacyFullscreenSupport NOTIFY legacyFullscreenSupportChanged) Q_PROPERTY(KWin::Options::WindowOperation operationTitlebarDblClick READ operationTitlebarDblClick WRITE setOperationTitlebarDblClick NOTIFY operationTitlebarDblClickChanged) Q_PROPERTY(KWin::Options::WindowOperation operationMaxButtonLeftClick READ operationMaxButtonLeftClick WRITE setOperationMaxButtonLeftClick NOTIFY operationMaxButtonLeftClickChanged) Q_PROPERTY(KWin::Options::WindowOperation operationMaxButtonMiddleClick READ operationMaxButtonMiddleClick WRITE setOperationMaxButtonMiddleClick NOTIFY operationMaxButtonMiddleClickChanged) Q_PROPERTY(KWin::Options::WindowOperation operationMaxButtonRightClick READ operationMaxButtonRightClick WRITE setOperationMaxButtonRightClick NOTIFY operationMaxButtonRightClickChanged) Q_PROPERTY(MouseCommand commandActiveTitlebar1 READ commandActiveTitlebar1 WRITE setCommandActiveTitlebar1 NOTIFY commandActiveTitlebar1Changed) Q_PROPERTY(MouseCommand commandActiveTitlebar2 READ commandActiveTitlebar2 WRITE setCommandActiveTitlebar2 NOTIFY commandActiveTitlebar2Changed) Q_PROPERTY(MouseCommand commandActiveTitlebar3 READ commandActiveTitlebar3 WRITE setCommandActiveTitlebar3 NOTIFY commandActiveTitlebar3Changed) Q_PROPERTY(MouseCommand commandInactiveTitlebar1 READ commandInactiveTitlebar1 WRITE setCommandInactiveTitlebar1 NOTIFY commandInactiveTitlebar1Changed) Q_PROPERTY(MouseCommand commandInactiveTitlebar2 READ commandInactiveTitlebar2 WRITE setCommandInactiveTitlebar2 NOTIFY commandInactiveTitlebar2Changed) Q_PROPERTY(MouseCommand commandInactiveTitlebar3 READ commandInactiveTitlebar3 WRITE setCommandInactiveTitlebar3 NOTIFY commandInactiveTitlebar3Changed) Q_PROPERTY(MouseCommand commandWindow1 READ commandWindow1 WRITE setCommandWindow1 NOTIFY commandWindow1Changed) Q_PROPERTY(MouseCommand commandWindow2 READ commandWindow2 WRITE setCommandWindow2 NOTIFY commandWindow2Changed) Q_PROPERTY(MouseCommand commandWindow3 READ commandWindow3 WRITE setCommandWindow3 NOTIFY commandWindow3Changed) Q_PROPERTY(MouseCommand commandWindowWheel READ commandWindowWheel WRITE setCommandWindowWheel NOTIFY commandWindowWheelChanged) Q_PROPERTY(MouseCommand commandAll1 READ commandAll1 WRITE setCommandAll1 NOTIFY commandAll1Changed) Q_PROPERTY(MouseCommand commandAll2 READ commandAll2 WRITE setCommandAll2 NOTIFY commandAll2Changed) Q_PROPERTY(MouseCommand commandAll3 READ commandAll3 WRITE setCommandAll3 NOTIFY commandAll3Changed) Q_PROPERTY(uint keyCmdAllModKey READ keyCmdAllModKey WRITE setKeyCmdAllModKey NOTIFY keyCmdAllModKeyChanged) /** * whether the Geometry Tip should be shown during a window move/resize. */ Q_PROPERTY(bool showGeometryTip READ showGeometryTip WRITE setShowGeometryTip NOTIFY showGeometryTipChanged) /** * whether the visible name should be condensed */ Q_PROPERTY(bool condensedTitle READ condensedTitle WRITE setCondensedTitle NOTIFY condensedTitleChanged) /** * Whether a window gets maximized when it reaches top screen edge while being moved. */ Q_PROPERTY(bool electricBorderMaximize READ electricBorderMaximize WRITE setElectricBorderMaximize NOTIFY electricBorderMaximizeChanged) /** * Whether a window is tiled to half screen when reaching left or right screen edge while been moved */ Q_PROPERTY(bool electricBorderTiling READ electricBorderTiling WRITE setElectricBorderTiling NOTIFY electricBorderTilingChanged) /** * Whether a window is tiled to half screen when reaching left or right screen edge while been moved */ Q_PROPERTY(float electricBorderCornerRatio READ electricBorderCornerRatio WRITE setElectricBorderCornerRatio NOTIFY electricBorderCornerRatioChanged) Q_PROPERTY(bool borderlessMaximizedWindows READ borderlessMaximizedWindows WRITE setBorderlessMaximizedWindows NOTIFY borderlessMaximizedWindowsChanged) /** * timeout before non-responding application will be killed after attempt to close **/ Q_PROPERTY(int killPingTimeout READ killPingTimeout WRITE setKillPingTimeout NOTIFY killPingTimeoutChanged) /** * Whether to hide utility windows for inactive applications. **/ Q_PROPERTY(bool hideUtilityWindowsForInactive READ isHideUtilityWindowsForInactive WRITE setHideUtilityWindowsForInactive NOTIFY hideUtilityWindowsForInactiveChanged) Q_PROPERTY(bool inactiveTabsSkipTaskbar READ isInactiveTabsSkipTaskbar WRITE setInactiveTabsSkipTaskbar NOTIFY inactiveTabsSkipTaskbarChanged) Q_PROPERTY(bool autogroupSimilarWindows READ isAutogroupSimilarWindows WRITE setAutogroupSimilarWindows NOTIFY autogroupSimilarWindowsChanged) Q_PROPERTY(bool autogroupInForeground READ isAutogroupInForeground WRITE setAutogroupInForeground NOTIFY autogroupInForegroundChanged) Q_PROPERTY(int compositingMode READ compositingMode WRITE setCompositingMode NOTIFY compositingModeChanged) Q_PROPERTY(bool useCompositing READ isUseCompositing WRITE setUseCompositing NOTIFY useCompositingChanged) Q_PROPERTY(bool compositingInitialized READ isCompositingInitialized WRITE setCompositingInitialized NOTIFY compositingInitializedChanged) Q_PROPERTY(int hiddenPreviews READ hiddenPreviews WRITE setHiddenPreviews NOTIFY hiddenPreviewsChanged) - Q_PROPERTY(bool unredirectFullscreen READ isUnredirectFullscreen WRITE setUnredirectFullscreen NOTIFY unredirectFullscreenChanged) /** * 0 = no, 1 = yes when transformed, * 2 = try trilinear when transformed; else 1, * -1 = auto **/ Q_PROPERTY(int glSmoothScale READ glSmoothScale WRITE setGlSmoothScale NOTIFY glSmoothScaleChanged) Q_PROPERTY(bool colorCorrected READ isColorCorrected WRITE setColorCorrected NOTIFY colorCorrectedChanged) Q_PROPERTY(bool xrenderSmoothScale READ isXrenderSmoothScale WRITE setXrenderSmoothScale NOTIFY xrenderSmoothScaleChanged) Q_PROPERTY(qint64 maxFpsInterval READ maxFpsInterval WRITE setMaxFpsInterval NOTIFY maxFpsIntervalChanged) Q_PROPERTY(uint refreshRate READ refreshRate WRITE setRefreshRate NOTIFY refreshRateChanged) Q_PROPERTY(qint64 vBlankTime READ vBlankTime WRITE setVBlankTime NOTIFY vBlankTimeChanged) Q_PROPERTY(bool glStrictBinding READ isGlStrictBinding WRITE setGlStrictBinding NOTIFY glStrictBindingChanged) /** * Whether strict binding follows the driver or has been overwritten by a user defined config value. * If @c true @link glStrictBinding is set by the OpenGL Scene during initialization. * If @c false @link glStrictBinding is set from a config value and not updated during scene initialization. **/ Q_PROPERTY(bool glStrictBindingFollowsDriver READ isGlStrictBindingFollowsDriver WRITE setGlStrictBindingFollowsDriver NOTIFY glStrictBindingFollowsDriverChanged) Q_PROPERTY(bool glCoreProfile READ glCoreProfile WRITE setGLCoreProfile NOTIFY glCoreProfileChanged) Q_PROPERTY(GlSwapStrategy glPreferBufferSwap READ glPreferBufferSwap WRITE setGlPreferBufferSwap NOTIFY glPreferBufferSwapChanged) Q_PROPERTY(KWin::OpenGLPlatformInterface glPlatformInterface READ glPlatformInterface WRITE setGlPlatformInterface NOTIFY glPlatformInterfaceChanged) public: explicit Options(QObject *parent = NULL); ~Options(); void updateSettings(); /*! Different focus policies:
  • ClickToFocus - Clicking into a window activates it. This is also the default.
  • FocusFollowsMouse - Moving the mouse pointer actively onto a normal window activates it. For convenience, the desktop and windows on the dock are excluded. They require clicking.
  • FocusUnderMouse - The window that happens to be under the mouse pointer becomes active. The invariant is: no window can have focus that is not under the mouse. This also means that Alt-Tab won't work properly and popup dialogs are usually unsable with the keyboard. Note that the desktop and windows on the dock are excluded for convenience. They get focus only when clicking on it.
  • FocusStrictlyUnderMouse - this is even worse than FocusUnderMouse. Only the window under the mouse pointer is active. If the mouse points nowhere, nothing has the focus. If the mouse points onto the desktop, the desktop has focus. The same holds for windows on the dock. Note that FocusUnderMouse and FocusStrictlyUnderMouse are not particulary useful. They are only provided for old-fashined die-hard UNIX people ;-)
*/ enum FocusPolicy { ClickToFocus, FocusFollowsMouse, FocusUnderMouse, FocusStrictlyUnderMouse }; FocusPolicy focusPolicy() const { return m_focusPolicy; } bool isNextFocusPrefersMouse() const { return m_nextFocusPrefersMouse; } /** Whether clicking on a window raises it in FocusFollowsMouse mode or not. */ bool isClickRaise() const { return m_clickRaise; } /** whether autoraise is enabled FocusFollowsMouse mode or not. */ bool isAutoRaise() const { return m_autoRaise; } /** autoraise interval */ int autoRaiseInterval() const { return m_autoRaiseInterval; } /** delayed focus interval */ int delayFocusInterval() const { return m_delayFocusInterval; } /** Whether shade hover is enabled or not */ bool isShadeHover() const { return m_shadeHover; } /** shade hover interval */ int shadeHoverInterval() { return m_shadeHoverInterval; } // whether to see Xinerama screens separately for focus (in Alt+Tab, when activating next client) bool isSeparateScreenFocus() const { return m_separateScreenFocus; } Placement::Policy placement() const { return m_placement; } bool focusPolicyIsReasonable() { return m_focusPolicy == ClickToFocus || m_focusPolicy == FocusFollowsMouse; } /** * the size of the zone that triggers snapping on desktop borders */ int borderSnapZone() const { return m_borderSnapZone; } /** * the size of the zone that triggers snapping with other windows */ int windowSnapZone() const { return m_windowSnapZone; } /** * the size of the zone that triggers snapping on the screen center */ int centerSnapZone() const { return m_centerSnapZone; } /** * snap only when windows will overlap */ bool isSnapOnlyWhenOverlapping() const { return m_snapOnlyWhenOverlapping; } /** * whether or not we roll over to the other edge when switching desktops past the edge */ bool isRollOverDesktops() const { return m_rollOverDesktops; } // 0 - 4 , see Workspace::allowClientActivation() int focusStealingPreventionLevel() const { return m_focusStealingPreventionLevel; } /** * support legacy fullscreen windows hack: borderless non-netwm windows with screen geometry */ bool isLegacyFullscreenSupport() const { return m_legacyFullscreenSupport; } enum WindowOperation { MaximizeOp = 5000, RestoreOp, MinimizeOp, MoveOp, UnrestrictedMoveOp, ResizeOp, UnrestrictedResizeOp, CloseOp, OnAllDesktopsOp, ShadeOp, KeepAboveOp, KeepBelowOp, OperationsOp, WindowRulesOp, ToggleStoreSettingsOp = WindowRulesOp, ///< @obsolete HMaximizeOp, VMaximizeOp, LowerOp, FullScreenOp, NoBorderOp, NoOp, SetupWindowShortcutOp, ApplicationRulesOp, RemoveTabFromGroupOp, // Remove from group CloseTabGroupOp, // Close the group ActivateNextTabOp, // Move left in the group ActivatePreviousTabOp, // Move right in the group TabDragOp, }; WindowOperation operationTitlebarDblClick() const { return OpTitlebarDblClick; } WindowOperation operationMaxButtonLeftClick() const { return opMaxButtonLeftClick; } WindowOperation operationMaxButtonRightClick() const { return opMaxButtonRightClick; } WindowOperation operationMaxButtonMiddleClick() const { return opMaxButtonMiddleClick; } WindowOperation operationMaxButtonClick(Qt::MouseButtons button) const; enum MouseCommand { MouseRaise, MouseLower, MouseOperationsMenu, MouseToggleRaiseAndLower, MouseActivateAndRaise, MouseActivateAndLower, MouseActivate, MouseActivateRaiseAndPassClick, MouseActivateAndPassClick, MouseMove, MouseUnrestrictedMove, MouseActivateRaiseAndMove, MouseActivateRaiseAndUnrestrictedMove, MouseResize, MouseUnrestrictedResize, MouseShade, MouseSetShade, MouseUnsetShade, MouseMaximize, MouseRestore, MouseMinimize, MouseNextDesktop, MousePreviousDesktop, MouseAbove, MouseBelow, MouseOpacityMore, MouseOpacityLess, MouseClose, MousePreviousTab, MouseNextTab, MouseDragTab, MouseNothing }; enum MouseWheelCommand { MouseWheelRaiseLower, MouseWheelShadeUnshade, MouseWheelMaximizeRestore, MouseWheelAboveBelow, MouseWheelPreviousNextDesktop, MouseWheelChangeOpacity, MouseWheelChangeCurrentTab, MouseWheelNothing }; MouseCommand operationTitlebarMouseWheel(int delta) const { return wheelToMouseCommand(CmdTitlebarWheel, delta); } MouseCommand operationWindowMouseWheel(int delta) const { return wheelToMouseCommand(CmdAllWheel, delta); } MouseCommand commandActiveTitlebar1() const { return CmdActiveTitlebar1; } MouseCommand commandActiveTitlebar2() const { return CmdActiveTitlebar2; } MouseCommand commandActiveTitlebar3() const { return CmdActiveTitlebar3; } MouseCommand commandInactiveTitlebar1() const { return CmdInactiveTitlebar1; } MouseCommand commandInactiveTitlebar2() const { return CmdInactiveTitlebar2; } MouseCommand commandInactiveTitlebar3() const { return CmdInactiveTitlebar3; } MouseCommand commandWindow1() const { return CmdWindow1; } MouseCommand commandWindow2() const { return CmdWindow2; } MouseCommand commandWindow3() const { return CmdWindow3; } MouseCommand commandWindowWheel() const { return CmdWindowWheel; } MouseCommand commandAll1() const { return CmdAll1; } MouseCommand commandAll2() const { return CmdAll2; } MouseCommand commandAll3() const { return CmdAll3; } uint keyCmdAllModKey() const { return CmdAllModKey; } Qt::KeyboardModifier commandAllModifier() const { switch (CmdAllModKey) { case Qt::Key_Alt: return Qt::AltModifier; case Qt::Key_Meta: return Qt::MetaModifier; default: Q_UNREACHABLE(); } } static WindowOperation windowOperation(const QString &name, bool restricted); static MouseCommand mouseCommand(const QString &name, bool restricted); static MouseWheelCommand mouseWheelCommand(const QString &name); /** * @returns true if the Geometry Tip should be shown during a window move/resize. */ bool showGeometryTip() const; /** * returns whether the user prefers his caption clean */ bool condensedTitle() const; /** * @returns true if a window gets maximized when it reaches top screen edge * while being moved. */ bool electricBorderMaximize() const { return electric_border_maximize; } /** * @returns true if window is tiled to half screen when reaching left or * right screen edge while been moved */ bool electricBorderTiling() const { return electric_border_tiling; } /** * @returns the factor that determines the corner part of the edge (ie. 0.1 means tiny corner) */ float electricBorderCornerRatio() const { return electric_border_corner_ratio; } bool borderlessMaximizedWindows() const { return borderless_maximized_windows; } // timeout before non-responding application will be killed after attempt to close int killPingTimeout() const { return m_killPingTimeout; } // Whether to hide utility windows for inactive applications. bool isHideUtilityWindowsForInactive() const { return m_hideUtilityWindowsForInactive; } bool isInactiveTabsSkipTaskbar() const { return m_inactiveTabsSkipTaskbar; } bool isAutogroupSimilarWindows() const { return m_autogroupSimilarWindows; } bool isAutogroupInForeground() const { return m_autogroupInForeground; } // Desktop effects double animationTimeFactor() const; //---------------------- // Compositing settings void reloadCompositingSettings(bool force = false); CompositingType compositingMode() const { return m_compositingMode; } void setCompositingMode(CompositingType mode) { m_compositingMode = mode; } // Separate to mode so the user can toggle bool isUseCompositing() const; bool isCompositingInitialized() const { return m_compositingInitialized; } // General preferences HiddenPreviews hiddenPreviews() const { return m_hiddenPreviews; } - bool isUnredirectFullscreen() const; // OpenGL // 0 = no, 1 = yes when transformed, // 2 = try trilinear when transformed; else 1, // -1 = auto int glSmoothScale() const { return m_glSmoothScale; } bool isColorCorrected() const { return m_colorCorrected; } // XRender bool isXrenderSmoothScale() const { return m_xrenderSmoothScale; } qint64 maxFpsInterval() const { return m_maxFpsInterval; } // Settings that should be auto-detected uint refreshRate() const { return m_refreshRate; } qint64 vBlankTime() const { return m_vBlankTime; } bool isGlStrictBinding() const { return m_glStrictBinding; } bool isGlStrictBindingFollowsDriver() const { return m_glStrictBindingFollowsDriver; } bool glCoreProfile() const { return m_glCoreProfile; } OpenGLPlatformInterface glPlatformInterface() const { return m_glPlatformInterface; } enum GlSwapStrategy { NoSwapEncourage = 0, CopyFrontBuffer = 'c', PaintFullScreen = 'p', ExtendDamage = 'e', AutoSwapStrategy = 'a' }; GlSwapStrategy glPreferBufferSwap() const { return m_glPreferBufferSwap; } QStringList modifierOnlyDBusShortcut(Qt::KeyboardModifier mod) const; // setters void setFocusPolicy(FocusPolicy focusPolicy); void setNextFocusPrefersMouse(bool nextFocusPrefersMouse); void setClickRaise(bool clickRaise); void setAutoRaise(bool autoRaise); void setAutoRaiseInterval(int autoRaiseInterval); void setDelayFocusInterval(int delayFocusInterval); void setShadeHover(bool shadeHover); void setShadeHoverInterval(int shadeHoverInterval); void setSeparateScreenFocus(bool separateScreenFocus); void setPlacement(int placement); void setBorderSnapZone(int borderSnapZone); void setWindowSnapZone(int windowSnapZone); void setCenterSnapZone(int centerSnapZone); void setSnapOnlyWhenOverlapping(bool snapOnlyWhenOverlapping); void setRollOverDesktops(bool rollOverDesktops); void setFocusStealingPreventionLevel(int focusStealingPreventionLevel); void setLegacyFullscreenSupport(bool legacyFullscreenSupport); void setOperationTitlebarDblClick(WindowOperation operationTitlebarDblClick); void setOperationMaxButtonLeftClick(WindowOperation op); void setOperationMaxButtonRightClick(WindowOperation op); void setOperationMaxButtonMiddleClick(WindowOperation op); void setCommandActiveTitlebar1(MouseCommand commandActiveTitlebar1); void setCommandActiveTitlebar2(MouseCommand commandActiveTitlebar2); void setCommandActiveTitlebar3(MouseCommand commandActiveTitlebar3); void setCommandInactiveTitlebar1(MouseCommand commandInactiveTitlebar1); void setCommandInactiveTitlebar2(MouseCommand commandInactiveTitlebar2); void setCommandInactiveTitlebar3(MouseCommand commandInactiveTitlebar3); void setCommandWindow1(MouseCommand commandWindow1); void setCommandWindow2(MouseCommand commandWindow2); void setCommandWindow3(MouseCommand commandWindow3); void setCommandWindowWheel(MouseCommand commandWindowWheel); void setCommandAll1(MouseCommand commandAll1); void setCommandAll2(MouseCommand commandAll2); void setCommandAll3(MouseCommand commandAll3); void setKeyCmdAllModKey(uint keyCmdAllModKey); void setShowGeometryTip(bool showGeometryTip); void setCondensedTitle(bool condensedTitle); void setElectricBorderMaximize(bool electricBorderMaximize); void setElectricBorderTiling(bool electricBorderTiling); void setElectricBorderCornerRatio(float electricBorderCornerRatio); void setBorderlessMaximizedWindows(bool borderlessMaximizedWindows); void setKillPingTimeout(int killPingTimeout); void setHideUtilityWindowsForInactive(bool hideUtilityWindowsForInactive); void setInactiveTabsSkipTaskbar(bool inactiveTabsSkipTaskbar); void setAutogroupSimilarWindows(bool autogroupSimilarWindows); void setAutogroupInForeground(bool autogroupInForeground); void setCompositingMode(int compositingMode); void setUseCompositing(bool useCompositing); void setCompositingInitialized(bool compositingInitialized); void setHiddenPreviews(int hiddenPreviews); - void setUnredirectFullscreen(bool unredirectFullscreen); void setGlSmoothScale(int glSmoothScale); void setXrenderSmoothScale(bool xrenderSmoothScale); void setMaxFpsInterval(qint64 maxFpsInterval); void setRefreshRate(uint refreshRate); void setVBlankTime(qint64 vBlankTime); void setGlStrictBinding(bool glStrictBinding); void setGlStrictBindingFollowsDriver(bool glStrictBindingFollowsDriver); void setGLCoreProfile(bool glCoreProfile); void setGlPreferBufferSwap(char glPreferBufferSwap); void setGlPlatformInterface(OpenGLPlatformInterface interface); // default values static WindowOperation defaultOperationTitlebarDblClick() { return MaximizeOp; } static WindowOperation defaultOperationMaxButtonLeftClick() { return MaximizeOp; } static WindowOperation defaultOperationMaxButtonRightClick() { return HMaximizeOp; } static WindowOperation defaultOperationMaxButtonMiddleClick() { return VMaximizeOp; } static MouseCommand defaultCommandActiveTitlebar1() { return MouseRaise; } static MouseCommand defaultCommandActiveTitlebar2() { return MouseDragTab; } static MouseCommand defaultCommandActiveTitlebar3() { return MouseOperationsMenu; } static MouseCommand defaultCommandInactiveTitlebar1() { return MouseActivateAndRaise; } static MouseCommand defaultCommandInactiveTitlebar2() { return MouseDragTab; } static MouseCommand defaultCommandInactiveTitlebar3() { return MouseOperationsMenu; } static MouseCommand defaultCommandWindow1() { return MouseActivateRaiseAndPassClick; } static MouseCommand defaultCommandWindow2() { return MouseActivateAndPassClick; } static MouseCommand defaultCommandWindow3() { return MouseActivateAndPassClick; } static MouseCommand defaultCommandWindowWheel() { return MouseNothing; } static MouseCommand defaultCommandAll1() { return MouseUnrestrictedMove; } static MouseCommand defaultCommandAll2() { return MouseToggleRaiseAndLower; } static MouseCommand defaultCommandAll3() { return MouseUnrestrictedResize; } static MouseWheelCommand defaultCommandTitlebarWheel() { return MouseWheelChangeCurrentTab; } static MouseWheelCommand defaultCommandAllWheel() { return MouseWheelNothing; } static uint defaultKeyCmdAllModKey() { return Qt::Key_Alt; } static bool defaultAutogroupInForeground() { return true; } static CompositingType defaultCompositingMode() { return OpenGLCompositing; } static bool defaultUseCompositing() { return true; } static bool defaultCompositingInitialized() { return false; } static HiddenPreviews defaultHiddenPreviews() { return HiddenPreviewsShown; } - static bool defaultUnredirectFullscreen() { - return false; - } static int defaultGlSmoothScale() { return 2; } static bool defaultColorCorrected() { return false; } static bool defaultXrenderSmoothScale() { return false; } static qint64 defaultMaxFpsInterval() { return (1 * 1000 * 1000 * 1000) /60.0; // nanoseconds / Hz } static int defaultMaxFps() { return 60; } static uint defaultRefreshRate() { return 0; } static uint defaultVBlankTime() { return 6000; // 6ms } static bool defaultGlStrictBinding() { return true; } static bool defaultGlStrictBindingFollowsDriver() { return true; } static bool defaultGLCoreProfile() { return false; } static GlSwapStrategy defaultGlPreferBufferSwap() { return AutoSwapStrategy; } static OpenGLPlatformInterface defaultGlPlatformInterface() { return kwinApp()->shouldUseWaylandForCompositing() ? EglPlatformInterface : GlxPlatformInterface; }; static int defaultAnimationSpeed() { return 3; } /** * Performs loading all settings except compositing related. **/ void loadConfig(); /** * Performs loading of compositing settings which do not depend on OpenGL. **/ bool loadCompositingConfig(bool force); void reparseConfiguration(); //---------------------- Q_SIGNALS: // for properties void focusPolicyChanged(); void focusPolicyIsResonableChanged(); void nextFocusPrefersMouseChanged(); void clickRaiseChanged(); void autoRaiseChanged(); void autoRaiseIntervalChanged(); void delayFocusIntervalChanged(); void shadeHoverChanged(); void shadeHoverIntervalChanged(); void separateScreenFocusChanged(bool); void placementChanged(); void borderSnapZoneChanged(); void windowSnapZoneChanged(); void centerSnapZoneChanged(); void snapOnlyWhenOverlappingChanged(); void rollOverDesktopsChanged(bool enabled); void focusStealingPreventionLevelChanged(); void legacyFullscreenSupportChanged(); void operationTitlebarDblClickChanged(); void operationMaxButtonLeftClickChanged(); void operationMaxButtonRightClickChanged(); void operationMaxButtonMiddleClickChanged(); void commandActiveTitlebar1Changed(); void commandActiveTitlebar2Changed(); void commandActiveTitlebar3Changed(); void commandInactiveTitlebar1Changed(); void commandInactiveTitlebar2Changed(); void commandInactiveTitlebar3Changed(); void commandWindow1Changed(); void commandWindow2Changed(); void commandWindow3Changed(); void commandWindowWheelChanged(); void commandAll1Changed(); void commandAll2Changed(); void commandAll3Changed(); void keyCmdAllModKeyChanged(); void showGeometryTipChanged(); void condensedTitleChanged(); void electricBorderMaximizeChanged(); void electricBorderTilingChanged(); void electricBorderCornerRatioChanged(); void borderlessMaximizedWindowsChanged(); void killPingTimeoutChanged(); void hideUtilityWindowsForInactiveChanged(); void inactiveTabsSkipTaskbarChanged(); void autogroupSimilarWindowsChanged(); void autogroupInForegroundChanged(); void compositingModeChanged(); void useCompositingChanged(); void compositingInitializedChanged(); void hiddenPreviewsChanged(); - void unredirectFullscreenChanged(); void glSmoothScaleChanged(); void colorCorrectedChanged(); void xrenderSmoothScaleChanged(); void maxFpsIntervalChanged(); void refreshRateChanged(); void vBlankTimeChanged(); void glStrictBindingChanged(); void glStrictBindingFollowsDriverChanged(); void glCoreProfileChanged(); void glPreferBufferSwapChanged(); void glPlatformInterfaceChanged(); void configChanged(); public Q_SLOTS: void setColorCorrected(bool colorCorrected = false); private: void setElectricBorders(int borders); void syncFromKcfgc(); QScopedPointer m_settings; FocusPolicy m_focusPolicy; bool m_nextFocusPrefersMouse; bool m_clickRaise; bool m_autoRaise; int m_autoRaiseInterval; int m_delayFocusInterval; bool m_shadeHover; int m_shadeHoverInterval; bool m_separateScreenFocus; Placement::Policy m_placement; int m_borderSnapZone; int m_windowSnapZone; int m_centerSnapZone; bool m_snapOnlyWhenOverlapping; bool m_rollOverDesktops; int m_focusStealingPreventionLevel; bool m_legacyFullscreenSupport; int m_killPingTimeout; bool m_hideUtilityWindowsForInactive; bool m_inactiveTabsSkipTaskbar; bool m_autogroupSimilarWindows; bool m_autogroupInForeground; CompositingType m_compositingMode; bool m_useCompositing; bool m_compositingInitialized; HiddenPreviews m_hiddenPreviews; - bool m_unredirectFullscreen; int m_glSmoothScale; bool m_colorCorrected; bool m_xrenderSmoothScale; qint64 m_maxFpsInterval; // Settings that should be auto-detected uint m_refreshRate; qint64 m_vBlankTime; bool m_glStrictBinding; bool m_glStrictBindingFollowsDriver; bool m_glCoreProfile; GlSwapStrategy m_glPreferBufferSwap; OpenGLPlatformInterface m_glPlatformInterface; WindowOperation OpTitlebarDblClick; WindowOperation opMaxButtonRightClick = defaultOperationMaxButtonRightClick(); WindowOperation opMaxButtonMiddleClick = defaultOperationMaxButtonMiddleClick(); WindowOperation opMaxButtonLeftClick = defaultOperationMaxButtonRightClick(); // mouse bindings MouseCommand CmdActiveTitlebar1; MouseCommand CmdActiveTitlebar2; MouseCommand CmdActiveTitlebar3; MouseCommand CmdInactiveTitlebar1; MouseCommand CmdInactiveTitlebar2; MouseCommand CmdInactiveTitlebar3; MouseWheelCommand CmdTitlebarWheel; MouseCommand CmdWindow1; MouseCommand CmdWindow2; MouseCommand CmdWindow3; MouseCommand CmdWindowWheel; MouseCommand CmdAll1; MouseCommand CmdAll2; MouseCommand CmdAll3; MouseWheelCommand CmdAllWheel; uint CmdAllModKey; bool electric_border_maximize; bool electric_border_tiling; float electric_border_corner_ratio; bool borderless_maximized_windows; bool show_geometry_tip; bool condensed_title; int animationSpeed; // 0 - instant, 5 - very slow QHash m_modifierOnlyShortcuts; MouseCommand wheelToMouseCommand(MouseWheelCommand com, int delta) const; }; extern KWIN_EXPORT Options* options; } // namespace Q_DECLARE_METATYPE(KWin::Options::WindowOperation) Q_DECLARE_METATYPE(KWin::OpenGLPlatformInterface) #endif diff --git a/plugins/platforms/x11/standalone/glxbackend.cpp b/plugins/platforms/x11/standalone/glxbackend.cpp index 94ef225ce..abe6f8387 100644 --- a/plugins/platforms/x11/standalone/glxbackend.cpp +++ b/plugins/platforms/x11/standalone/glxbackend.cpp @@ -1,881 +1,879 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak Copyright (C) 2012 Martin Gräßlin Based on glcompmgr code by Felix Bellaby. Using code from Compiz and Beryl. 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 . *********************************************************************/ // own #include "glxbackend.h" #include "logging.h" // kwin #include "options.h" #include "overlaywindow.h" #include "composite.h" #include "screens.h" #include "xcbutils.h" // kwin libs #include #include // Qt #include #include // system #include #include #include #ifndef XCB_GLX_BUFFER_SWAP_COMPLETE #define XCB_GLX_BUFFER_SWAP_COMPLETE 1 typedef struct xcb_glx_buffer_swap_complete_event_t { uint8_t response_type; /**< */ uint8_t pad0; /**< */ uint16_t sequence; /**< */ uint16_t event_type; /**< */ uint8_t pad1[2]; /**< */ xcb_glx_drawable_t drawable; /**< */ uint32_t ust_hi; /**< */ uint32_t ust_lo; /**< */ uint32_t msc_hi; /**< */ uint32_t msc_lo; /**< */ uint32_t sbc; /**< */ } xcb_glx_buffer_swap_complete_event_t; #endif #include #if __cplusplus <= 201103L namespace std { // C++-14 template unique_ptr make_unique(Args&&... args) { return unique_ptr(new T(std::forward(args)...)); } } #endif namespace KWin { SwapEventFilter::SwapEventFilter(xcb_drawable_t drawable, xcb_glx_drawable_t glxDrawable) : X11EventFilter(Xcb::Extensions::self()->glxEventBase() + XCB_GLX_BUFFER_SWAP_COMPLETE), m_drawable(drawable), m_glxDrawable(glxDrawable) { } bool SwapEventFilter::event(xcb_generic_event_t *event) { xcb_glx_buffer_swap_complete_event_t *ev = reinterpret_cast(event); // The drawable field is the X drawable when the event was synthesized // by a WireToEvent handler, and the GLX drawable when the event was // received over the wire if (ev->drawable == m_drawable || ev->drawable == m_glxDrawable) { Compositor::self()->bufferSwapComplete(); return true; } return false; } // ----------------------------------------------------------------------- GlxBackend::GlxBackend() : OpenGLBackend() , m_overlayWindow(new OverlayWindow()) , window(None) , fbconfig(NULL) , glxWindow(None) , ctx(nullptr) , m_bufferAge(0) , haveSwapInterval(false) { } static bool gs_tripleBufferUndetected = true; static bool gs_tripleBufferNeedsDetection = false; GlxBackend::~GlxBackend() { if (isFailed()) { m_overlayWindow->destroy(); } // TODO: cleanup in error case // do cleanup after initBuffer() cleanupGL(); doneCurrent(); gs_tripleBufferUndetected = true; gs_tripleBufferNeedsDetection = false; if (ctx) glXDestroyContext(display(), ctx); if (glxWindow) glXDestroyWindow(display(), glxWindow); if (window) XDestroyWindow(display(), window); qDeleteAll(m_fbconfigHash); m_fbconfigHash.clear(); overlayWindow()->destroy(); delete m_overlayWindow; } void GlxBackend::init() { initGLX(); // Require at least GLX 1.3 if (!hasGLXVersion(1, 3)) { setFailed(QStringLiteral("Requires at least GLX 1.3")); return; } initVisualDepthHashTable(); if (!initBuffer()) { setFailed(QStringLiteral("Could not initialize the buffer")); return; } if (!initRenderingContext()) { setFailed(QStringLiteral("Could not initialize rendering context")); return; } // Initialize OpenGL GLPlatform *glPlatform = GLPlatform::instance(); glPlatform->detect(GlxPlatformInterface); - if (GLPlatform::instance()->driver() == Driver_Intel) - options->setUnredirectFullscreen(false); // bug #252817 options->setGlPreferBufferSwap(options->glPreferBufferSwap()); // resolve autosetting if (options->glPreferBufferSwap() == Options::AutoSwapStrategy) options->setGlPreferBufferSwap('e'); // for unknown drivers - should not happen glPlatform->printResults(); initGL(GlxPlatformInterface); // Check whether certain features are supported m_haveMESACopySubBuffer = hasGLExtension(QByteArrayLiteral("GLX_MESA_copy_sub_buffer")); m_haveMESASwapControl = hasGLExtension(QByteArrayLiteral("GLX_MESA_swap_control")); m_haveEXTSwapControl = hasGLExtension(QByteArrayLiteral("GLX_EXT_swap_control")); m_haveSGISwapControl = hasGLExtension(QByteArrayLiteral("GLX_SGI_swap_control")); // only enable Intel swap event if env variable is set, see BUG 342582 m_haveINTELSwapEvent = hasGLExtension(QByteArrayLiteral("GLX_INTEL_swap_event")) && qgetenv("KWIN_USE_INTEL_SWAP_EVENT") == QByteArrayLiteral("1"); if (m_haveINTELSwapEvent) { m_swapEventFilter = std::make_unique(window, glxWindow); glXSelectEvent(display(), glxWindow, GLX_BUFFER_SWAP_COMPLETE_INTEL_MASK); } haveSwapInterval = m_haveMESASwapControl || m_haveEXTSwapControl || m_haveSGISwapControl; setSupportsBufferAge(false); if (hasGLExtension(QByteArrayLiteral("GLX_EXT_buffer_age"))) { const QByteArray useBufferAge = qgetenv("KWIN_USE_BUFFER_AGE"); if (useBufferAge != "0") setSupportsBufferAge(true); } setSyncsToVBlank(false); setBlocksForRetrace(false); haveWaitSync = false; gs_tripleBufferNeedsDetection = false; m_swapProfiler.init(); const bool wantSync = options->glPreferBufferSwap() != Options::NoSwapEncourage; if (wantSync && glXIsDirect(display(), ctx)) { if (haveSwapInterval) { // glXSwapInterval is preferred being more reliable setSwapInterval(1); setSyncsToVBlank(true); const QByteArray tripleBuffer = qgetenv("KWIN_TRIPLE_BUFFER"); if (!tripleBuffer.isEmpty()) { setBlocksForRetrace(qstrcmp(tripleBuffer, "0") == 0); gs_tripleBufferUndetected = false; } gs_tripleBufferNeedsDetection = gs_tripleBufferUndetected; } else if (hasGLExtension(QByteArrayLiteral("GLX_SGI_video_sync"))) { unsigned int sync; if (glXGetVideoSyncSGI(&sync) == 0 && glXWaitVideoSyncSGI(1, 0, &sync) == 0) { setSyncsToVBlank(true); setBlocksForRetrace(true); haveWaitSync = true; } else qCWarning(KWIN_X11STANDALONE) << "NO VSYNC! glXSwapInterval is not supported, glXWaitVideoSync is supported but broken"; } else qCWarning(KWIN_X11STANDALONE) << "NO VSYNC! neither glSwapInterval nor glXWaitVideoSync are supported"; } else { // disable v-sync (if possible) setSwapInterval(0); } if (glPlatform->isVirtualBox()) { // VirtualBox does not support glxQueryDrawable // this should actually be in kwinglutils_funcs, but QueryDrawable seems not to be provided by an extension // and the GLPlatform has not been initialized at the moment when initGLX() is called. glXQueryDrawable = NULL; } setIsDirectRendering(bool(glXIsDirect(display(), ctx))); qCDebug(KWIN_X11STANDALONE) << "Direct rendering:" << isDirectRendering(); } bool GlxBackend::initRenderingContext() { const bool direct = true; // Use glXCreateContextAttribsARB() when it's available if (hasGLExtension(QByteArrayLiteral("GLX_ARB_create_context"))) { const int attribs_31_core_robustness[] = { GLX_CONTEXT_MAJOR_VERSION_ARB, 3, GLX_CONTEXT_MINOR_VERSION_ARB, 1, GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB, GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB, GLX_LOSE_CONTEXT_ON_RESET_ARB, 0 }; const int attribs_31_core[] = { GLX_CONTEXT_MAJOR_VERSION_ARB, 3, GLX_CONTEXT_MINOR_VERSION_ARB, 1, 0 }; const int attribs_legacy_robustness[] = { GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB, GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB, GLX_LOSE_CONTEXT_ON_RESET_ARB, 0 }; const int attribs_legacy[] = { GLX_CONTEXT_MAJOR_VERSION_ARB, 1, GLX_CONTEXT_MINOR_VERSION_ARB, 2, 0 }; const bool have_robustness = hasGLExtension(QByteArrayLiteral("GLX_ARB_create_context_robustness")); // Try to create a 3.1 context first if (options->glCoreProfile()) { if (have_robustness) ctx = glXCreateContextAttribsARB(display(), fbconfig, 0, direct, attribs_31_core_robustness); if (!ctx) ctx = glXCreateContextAttribsARB(display(), fbconfig, 0, direct, attribs_31_core); } if (!ctx && have_robustness) ctx = glXCreateContextAttribsARB(display(), fbconfig, 0, direct, attribs_legacy_robustness); if (!ctx) ctx = glXCreateContextAttribsARB(display(), fbconfig, 0, direct, attribs_legacy); } if (!ctx) ctx = glXCreateNewContext(display(), fbconfig, GLX_RGBA_TYPE, NULL, direct); if (!ctx) { qCDebug(KWIN_X11STANDALONE) << "Failed to create an OpenGL context."; return false; } if (!glXMakeCurrent(display(), glxWindow, ctx)) { qCDebug(KWIN_X11STANDALONE) << "Failed to make the OpenGL context current."; glXDestroyContext(display(), ctx); ctx = 0; return false; } return true; } bool GlxBackend::initBuffer() { if (!initFbConfig()) return false; if (overlayWindow()->create()) { xcb_connection_t * const c = connection(); // Try to create double-buffered window in the overlay xcb_visualid_t visual; glXGetFBConfigAttrib(display(), fbconfig, GLX_VISUAL_ID, (int *) &visual); if (!visual) { qCCritical(KWIN_X11STANDALONE) << "The GLXFBConfig does not have an associated X visual"; return false; } xcb_colormap_t colormap = xcb_generate_id(c); xcb_create_colormap(c, false, colormap, rootWindow(), visual); const QSize size = screens()->size(); window = xcb_generate_id(c); xcb_create_window(c, visualDepth(visual), window, overlayWindow()->window(), 0, 0, size.width(), size.height(), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, visual, XCB_CW_COLORMAP, &colormap); glxWindow = glXCreateWindow(display(), fbconfig, window, NULL); overlayWindow()->setup(window); } else { qCCritical(KWIN_X11STANDALONE) << "Failed to create overlay window"; return false; } return true; } bool GlxBackend::initFbConfig() { const int attribs[] = { GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, GLX_RED_SIZE, 1, GLX_GREEN_SIZE, 1, GLX_BLUE_SIZE, 1, GLX_ALPHA_SIZE, 0, GLX_DEPTH_SIZE, 0, GLX_STENCIL_SIZE, 0, GLX_CONFIG_CAVEAT, GLX_NONE, GLX_DOUBLEBUFFER, true, 0 }; // Try to find a double buffered configuration int count = 0; GLXFBConfig *configs = glXChooseFBConfig(display(), DefaultScreen(display()), attribs, &count); struct FBConfig { GLXFBConfig config; int depth; int stencil; }; std::deque candidates; for (int i = 0; i < count; i++) { int depth, stencil; glXGetFBConfigAttrib(display(), configs[i], GLX_DEPTH_SIZE, &depth); glXGetFBConfigAttrib(display(), configs[i], GLX_STENCIL_SIZE, &stencil); candidates.emplace_back(FBConfig{configs[i], depth, stencil}); } if (count > 0) XFree(configs); std::stable_sort(candidates.begin(), candidates.end(), [](const FBConfig &left, const FBConfig &right) { if (left.depth < right.depth) return true; if (left.stencil < right.stencil) return true; return false; }); if (candidates.size() > 0) { fbconfig = candidates.front().config; int fbconfig_id, visual_id, red, green, blue, alpha, depth, stencil; glXGetFBConfigAttrib(display(), fbconfig, GLX_FBCONFIG_ID, &fbconfig_id); glXGetFBConfigAttrib(display(), fbconfig, GLX_VISUAL_ID, &visual_id); glXGetFBConfigAttrib(display(), fbconfig, GLX_RED_SIZE, &red); glXGetFBConfigAttrib(display(), fbconfig, GLX_GREEN_SIZE, &green); glXGetFBConfigAttrib(display(), fbconfig, GLX_BLUE_SIZE, &blue); glXGetFBConfigAttrib(display(), fbconfig, GLX_ALPHA_SIZE, &alpha); glXGetFBConfigAttrib(display(), fbconfig, GLX_DEPTH_SIZE, &depth); glXGetFBConfigAttrib(display(), fbconfig, GLX_STENCIL_SIZE, &stencil); qCDebug(KWIN_X11STANDALONE, "Choosing GLXFBConfig %#x X visual %#x depth %d RGBA %d:%d:%d:%d ZS %d:%d", fbconfig_id, visual_id, visualDepth(visual_id), red, green, blue, alpha, depth, stencil); } if (fbconfig == nullptr) { qCCritical(KWIN_X11STANDALONE) << "Failed to find a usable framebuffer configuration"; return false; } return true; } void GlxBackend::initVisualDepthHashTable() { const xcb_setup_t *setup = xcb_get_setup(connection()); for (auto screen = xcb_setup_roots_iterator(setup); screen.rem; xcb_screen_next(&screen)) { for (auto depth = xcb_screen_allowed_depths_iterator(screen.data); depth.rem; xcb_depth_next(&depth)) { const int len = xcb_depth_visuals_length(depth.data); const xcb_visualtype_t *visuals = xcb_depth_visuals(depth.data); for (int i = 0; i < len; i++) m_visualDepthHash.insert(visuals[i].visual_id, depth.data->depth); } } } int GlxBackend::visualDepth(xcb_visualid_t visual) const { return m_visualDepthHash.value(visual); } FBConfigInfo *GlxBackend::infoForVisual(xcb_visualid_t visual) { auto it = m_fbconfigHash.constFind(visual); if (it != m_fbconfigHash.constEnd()) { return it.value(); } FBConfigInfo *info = new FBConfigInfo; m_fbconfigHash.insert(visual, info); info->fbconfig = nullptr; info->bind_texture_format = 0; info->texture_targets = 0; info->y_inverted = 0; info->mipmap = 0; const xcb_render_pictformat_t format = XRenderUtils::findPictFormat(visual); const xcb_render_directformat_t *direct = XRenderUtils::findPictFormatInfo(format); if (!direct) { qCCritical(KWIN_X11STANDALONE).nospace() << "Could not find a picture format for visual 0x" << hex << visual; return info; } const int red_bits = bitCount(direct->red_mask); const int green_bits = bitCount(direct->green_mask); const int blue_bits = bitCount(direct->blue_mask); const int alpha_bits = bitCount(direct->alpha_mask); const int depth = visualDepth(visual); const auto rgb_sizes = std::tie(red_bits, green_bits, blue_bits); const int attribs[] = { GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT | GLX_PIXMAP_BIT, GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, GLX_X_RENDERABLE, True, GLX_CONFIG_CAVEAT, int(GLX_DONT_CARE), // The ARGB32 visual is marked non-conformant in Catalyst GLX_BUFFER_SIZE, red_bits + green_bits + blue_bits + alpha_bits, GLX_RED_SIZE, red_bits, GLX_GREEN_SIZE, green_bits, GLX_BLUE_SIZE, blue_bits, GLX_ALPHA_SIZE, alpha_bits, GLX_STENCIL_SIZE, 0, GLX_DEPTH_SIZE, 0, 0 }; int count = 0; GLXFBConfig *configs = glXChooseFBConfig(display(), DefaultScreen(display()), attribs, &count); if (count < 1) { qCCritical(KWIN_X11STANDALONE).nospace() << "Could not find a framebuffer configuration for visual 0x" << hex << visual; return info; } struct FBConfig { GLXFBConfig config; int depth; int stencil; int format; }; std::deque candidates; for (int i = 0; i < count; i++) { int red, green, blue; glXGetFBConfigAttrib(display(), configs[i], GLX_RED_SIZE, &red); glXGetFBConfigAttrib(display(), configs[i], GLX_GREEN_SIZE, &green); glXGetFBConfigAttrib(display(), configs[i], GLX_BLUE_SIZE, &blue); if (std::tie(red, green, blue) != rgb_sizes) continue; xcb_visualid_t visual; glXGetFBConfigAttrib(display(), configs[i], GLX_VISUAL_ID, (int *) &visual); if (visualDepth(visual) != depth) continue; int bind_rgb, bind_rgba; glXGetFBConfigAttrib(display(), configs[i], GLX_BIND_TO_TEXTURE_RGBA_EXT, &bind_rgba); glXGetFBConfigAttrib(display(), configs[i], GLX_BIND_TO_TEXTURE_RGB_EXT, &bind_rgb); if (!bind_rgb && !bind_rgba) continue; int depth, stencil; glXGetFBConfigAttrib(display(), configs[i], GLX_DEPTH_SIZE, &depth); glXGetFBConfigAttrib(display(), configs[i], GLX_STENCIL_SIZE, &stencil); int texture_format; if (alpha_bits) texture_format = bind_rgba ? GLX_TEXTURE_FORMAT_RGBA_EXT : GLX_TEXTURE_FORMAT_RGB_EXT; else texture_format = bind_rgb ? GLX_TEXTURE_FORMAT_RGB_EXT : GLX_TEXTURE_FORMAT_RGBA_EXT; candidates.emplace_back(FBConfig{configs[i], depth, stencil, texture_format}); } if (count > 0) XFree(configs); std::stable_sort(candidates.begin(), candidates.end(), [](const FBConfig &left, const FBConfig &right) { if (left.depth < right.depth) return true; if (left.stencil < right.stencil) return true; return false; }); if (candidates.size() > 0) { const FBConfig &candidate = candidates.front(); int y_inverted, texture_targets; glXGetFBConfigAttrib(display(), candidate.config, GLX_BIND_TO_TEXTURE_TARGETS_EXT, &texture_targets); glXGetFBConfigAttrib(display(), candidate.config, GLX_Y_INVERTED_EXT, &y_inverted); info->fbconfig = candidate.config; info->bind_texture_format = candidate.format; info->texture_targets = texture_targets; info->y_inverted = y_inverted; info->mipmap = 0; } if (info->fbconfig) { int fbc_id = 0; int visual_id = 0; glXGetFBConfigAttrib(display(), info->fbconfig, GLX_FBCONFIG_ID, &fbc_id); glXGetFBConfigAttrib(display(), info->fbconfig, GLX_VISUAL_ID, &visual_id); qCDebug(KWIN_X11STANDALONE).nospace() << "Using FBConfig 0x" << hex << fbc_id << " for visual 0x" << hex << visual_id; } return info; } void GlxBackend::setSwapInterval(int interval) { if (m_haveEXTSwapControl) glXSwapIntervalEXT(display(), glxWindow, interval); else if (m_haveMESASwapControl) glXSwapIntervalMESA(interval); else if (m_haveSGISwapControl) glXSwapIntervalSGI(interval); } void GlxBackend::waitSync() { // NOTE that vsync has no effect with indirect rendering if (haveWaitSync) { uint sync; #if 0 // TODO: why precisely is this important? // the sync counter /can/ perform multiple steps during glXGetVideoSync & glXWaitVideoSync // but this only leads to waiting for two frames??!? glXGetVideoSync(&sync); glXWaitVideoSync(2, (sync + 1) % 2, &sync); #else glXWaitVideoSyncSGI(1, 0, &sync); #endif } } void GlxBackend::present() { if (lastDamage().isEmpty()) return; const QSize &screenSize = screens()->size(); const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height()); const bool fullRepaint = supportsBufferAge() || (lastDamage() == displayRegion); if (fullRepaint) { if (m_haveINTELSwapEvent) Compositor::self()->aboutToSwapBuffers(); if (haveSwapInterval) { if (gs_tripleBufferNeedsDetection) { glXWaitGL(); m_swapProfiler.begin(); } glXSwapBuffers(display(), glxWindow); if (gs_tripleBufferNeedsDetection) { glXWaitGL(); if (char result = m_swapProfiler.end()) { gs_tripleBufferUndetected = gs_tripleBufferNeedsDetection = false; if (result == 'd' && GLPlatform::instance()->driver() == Driver_NVidia) { // TODO this is a workaround, we should get __GL_YIELD set before libGL checks it if (qstrcmp(qgetenv("__GL_YIELD"), "USLEEP")) { options->setGlPreferBufferSwap(0); setSwapInterval(0); result = 0; // hint proper behavior qCWarning(KWIN_X11STANDALONE) << "\nIt seems you are using the nvidia driver without triple buffering\n" "You must export __GL_YIELD=\"USLEEP\" to prevent large CPU overhead on synced swaps\n" "Preferably, enable the TripleBuffer Option in the xorg.conf Device\n" "For this reason, the tearing prevention has been disabled.\n" "See https://bugs.kde.org/show_bug.cgi?id=322060\n"; } } setBlocksForRetrace(result == 'd'); } } else if (blocksForRetrace()) { // at least the nvidia blob manages to swap async, ie. return immediately on double // buffering - what messes our timing calculation and leads to laggy behavior #346275 glXWaitGL(); } } else { waitSync(); glXSwapBuffers(display(), glxWindow); } if (supportsBufferAge()) { glXQueryDrawable(display(), glxWindow, GLX_BACK_BUFFER_AGE_EXT, (GLuint *) &m_bufferAge); } } else if (m_haveMESACopySubBuffer) { foreach (const QRect & r, lastDamage().rects()) { // convert to OpenGL coordinates int y = screenSize.height() - r.y() - r.height(); glXCopySubBufferMESA(display(), glxWindow, r.x(), y, r.width(), r.height()); } } else { // Copy Pixels (horribly slow on Mesa) glDrawBuffer(GL_FRONT); SceneOpenGL::copyPixels(lastDamage()); glDrawBuffer(GL_BACK); } setLastDamage(QRegion()); if (!supportsBufferAge()) { glXWaitGL(); XFlush(display()); } } void GlxBackend::screenGeometryChanged(const QSize &size) { doneCurrent(); XMoveResizeWindow(display(), window, 0, 0, size.width(), size.height()); overlayWindow()->setup(window); Xcb::sync(); makeCurrent(); glViewport(0, 0, size.width(), size.height()); // The back buffer contents are now undefined m_bufferAge = 0; } SceneOpenGL::TexturePrivate *GlxBackend::createBackendTexture(SceneOpenGL::Texture *texture) { return new GlxTexture(texture, this); } QRegion GlxBackend::prepareRenderingFrame() { QRegion repaint; if (gs_tripleBufferNeedsDetection) { // the composite timer floors the repaint frequency. This can pollute our triple buffering // detection because the glXSwapBuffers call for the new frame has to wait until the pending // one scanned out. // So we compensate for that by waiting an extra milisecond to give the driver the chance to // fllush the buffer queue usleep(1000); } present(); if (supportsBufferAge()) repaint = accumulatedDamageHistory(m_bufferAge); startRenderTimer(); glXWaitX(); return repaint; } void GlxBackend::endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion) { if (damagedRegion.isEmpty()) { setLastDamage(QRegion()); // If the damaged region of a window is fully occluded, the only // rendering done, if any, will have been to repair a reused back // buffer, making it identical to the front buffer. // // In this case we won't post the back buffer. Instead we'll just // set the buffer age to 1, so the repaired regions won't be // rendered again in the next frame. if (!renderedRegion.isEmpty()) glFlush(); m_bufferAge = 1; return; } setLastDamage(renderedRegion); if (!blocksForRetrace()) { // This also sets lastDamage to empty which prevents the frame from // being posted again when prepareRenderingFrame() is called. present(); } else { // Make sure that the GPU begins processing the command stream // now and not the next time prepareRenderingFrame() is called. glFlush(); } if (overlayWindow()->window()) // show the window only after the first pass, overlayWindow()->show(); // since that pass may take long // Save the damaged region to history if (supportsBufferAge()) addToDamageHistory(damagedRegion); } bool GlxBackend::makeCurrent() { if (QOpenGLContext *context = QOpenGLContext::currentContext()) { // Workaround to tell Qt that no QOpenGLContext is current context->doneCurrent(); } const bool current = glXMakeCurrent(display(), glxWindow, ctx); return current; } void GlxBackend::doneCurrent() { glXMakeCurrent(display(), None, nullptr); } OverlayWindow* GlxBackend::overlayWindow() { return m_overlayWindow; } bool GlxBackend::usesOverlayWindow() const { return true; } /******************************************************** * GlxTexture *******************************************************/ GlxTexture::GlxTexture(SceneOpenGL::Texture *texture, GlxBackend *backend) : SceneOpenGL::TexturePrivate() , q(texture) , m_backend(backend) , m_glxpixmap(None) { } GlxTexture::~GlxTexture() { if (m_glxpixmap != None) { if (!options->isGlStrictBinding()) { glXReleaseTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT); } glXDestroyPixmap(display(), m_glxpixmap); m_glxpixmap = None; } } void GlxTexture::onDamage() { if (options->isGlStrictBinding() && m_glxpixmap) { glXReleaseTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT); glXBindTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT, NULL); } GLTexturePrivate::onDamage(); } bool GlxTexture::loadTexture(xcb_pixmap_t pixmap, const QSize &size, xcb_visualid_t visual) { if (pixmap == XCB_NONE || size.isEmpty() || visual == XCB_NONE) return false; const FBConfigInfo *info = m_backend->infoForVisual(visual); if (!info || info->fbconfig == nullptr) return false; if (info->texture_targets & GLX_TEXTURE_2D_BIT_EXT) { m_target = GL_TEXTURE_2D; m_scale.setWidth(1.0f / m_size.width()); m_scale.setHeight(1.0f / m_size.height()); } else { assert(info->texture_targets & GLX_TEXTURE_RECTANGLE_BIT_EXT); m_target = GL_TEXTURE_RECTANGLE; m_scale.setWidth(1.0f); m_scale.setHeight(1.0f); } const int attrs[] = { GLX_TEXTURE_FORMAT_EXT, info->bind_texture_format, GLX_MIPMAP_TEXTURE_EXT, false, GLX_TEXTURE_TARGET_EXT, m_target == GL_TEXTURE_2D ? GLX_TEXTURE_2D_EXT : GLX_TEXTURE_RECTANGLE_EXT, 0 }; m_glxpixmap = glXCreatePixmap(display(), info->fbconfig, pixmap, attrs); m_size = size; m_yInverted = info->y_inverted ? true : false; m_canUseMipmaps = false; glGenTextures(1, &m_texture); q->setDirty(); q->setFilter(GL_NEAREST); glBindTexture(m_target, m_texture); glXBindTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT, nullptr); updateMatrix(); return true; } bool GlxTexture::loadTexture(WindowPixmap *pixmap) { Toplevel *t = pixmap->toplevel(); return loadTexture(pixmap->pixmap(), t->size(), t->visual()); } OpenGLBackend *GlxTexture::backend() { return m_backend; } } // namespace diff --git a/scene.cpp b/scene.cpp index 9f31c8918..6f4aec378 100644 --- a/scene.cpp +++ b/scene.cpp @@ -1,1099 +1,1093 @@ /******************************************************************** 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 . *********************************************************************/ /* 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 . Compositor::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 #include "client.h" #include "deleted.h" #include "effects.h" #include "overlaywindow.h" #include "screens.h" #include "shadow.h" #include "wayland_server.h" #include "thumbnailitem.h" #include #include #include namespace KWin { //**************************************** // Scene //**************************************** Scene::Scene(QObject *parent) : QObject(parent) { last_time.invalidate(); // Initialize the timer } Scene::~Scene() { foreach (Window *w, m_windows) { delete w; } } // returns mask and possibly modified region void Scene::paintScreen(int* mask, const QRegion &damage, const QRegion &repaint, QRegion *updateRegion, QRegion *validRegion, const QMatrix4x4 &projection) { const QSize &screenSize = screens()->size(); const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height()); *mask = (damage == displayRegion) ? 0 : PAINT_SCREEN_REGION; updateTimeDiff(); // preparation step static_cast(effects)->startPaint(); QRegion region = damage; 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 &= displayRegion; } else { // whole screen, not transformed, force region to be full region = displayRegion; } painted_region = region; repaint_region = repaint; if (*mask & PAINT_SCREEN_BACKGROUND_FIRST) { paintBackground(region); } ScreenPaintData data(projection); effects->paintScreen(*mask, region, data); foreach (Window *w, stacking_order) { effects->postPaintWindow(effectWindow(w)); } effects->postPaintScreen(); // make sure not to go outside of the screen area *updateRegion = damaged_region; *validRegion = (region | painted_region) & displayRegion; repaint_region = QRegion(); damaged_region = QRegion(); // 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(); 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()) { qFatal("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); } const QSize &screenSize = screens()->size(); damaged_region = QRegion(0, 0, screenSize.width(), screenSize.height()); } // 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; bool opaqueFullscreen(false); 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(); // 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(); // Clip out the decoration for opaque windows; the decoration is drawn in the second pass opaqueFullscreen = false; // TODO: do we care about unmanged windows here (maybe input windows?) if (w->isOpaque()) { AbstractClient *c = dynamic_cast(topw); if (c) { opaqueFullscreen = c->isFullScreen(); } Client *cc = dynamic_cast(c); // the window is fully opaque if (cc && cc->decorationHasAlpha()) { // decoration uses alpha channel, so we may not exclude it in clipping data.clip = w->clientShape().translated(w->x(), w->y()); } else { // decoration is fully opaque if (c && c->isShade()) { data.clip = QRegion(); } else { data.clip = w->shape().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()) { qFatal("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); } // Save the part of the repaint region that's exclusively rendered to // bring a reused back buffer up to date. Then union the dirty region // with the repaint region. const QRegion repaintClip = repaint_region - dirtyArea; dirtyArea |= repaint_region; const QSize &screenSize = screens()->size(); const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height()); bool fullRepaint(dirtyArea == displayRegion); // spare some expensive region operations if (!fullRepaint) { extendPaintRegion(dirtyArea, opaqueFullscreen); fullRepaint = (dirtyArea == displayRegion); } QRegion allclips, upperTranslucentDamage; upperTranslucentDamage = repaint_region; // This is the occlusion culling pass for (int i = phase2data.count() - 1; i >= 0; --i) { QPair< Window*, Phase2Data > *entry = &phase2data[i]; Phase2Data *data = &entry->second; if (fullRepaint) data->region = displayRegion; else 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 if (!fullRepaint) upperTranslucentDamage |= data->region - data->clip; } else if (!fullRepaint) { 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); } if (fullRepaint) { painted_region = displayRegion; damaged_region = displayRegion; } else { painted_region |= paintedArea; // Clip the repainted region from the damaged region. // It's important that we don't add the union of the damaged region // and the repainted region to the damage history. Otherwise the // repaint region will grow with every frame until it eventually // covers the whole back buffer, at which point we're always doing // full repaints. damaged_region = paintedArea - repaintClip; } } void Scene::windowAdded(Toplevel *c) { assert(!m_windows.contains(c)); Scene::Window *w = createWindow(c); m_windows[ c ] = w; connect(c, SIGNAL(geometryShapeChanged(KWin::Toplevel*,QRect)), SLOT(windowGeometryShapeChanged(KWin::Toplevel*))); connect(c, SIGNAL(windowClosed(KWin::Toplevel*,KWin::Deleted*)), SLOT(windowClosed(KWin::Toplevel*,KWin::Deleted*))); c->effectWindow()->setSceneWindow(w); c->getShadow(); w->updateShadow(c->shadow()); } void Scene::windowClosed(Toplevel *c, Deleted *deleted) { assert(m_windows.contains(c)); if (deleted != NULL) { // replace c with deleted Window* w = m_windows.take(c); w->updateToplevel(deleted); if (w->shadow()) { w->shadow()->setToplevel(deleted); } m_windows[ deleted ] = w; } else { delete m_windows.take(c); c->effectWindow()->setSceneWindow(NULL); } } void Scene::windowDeleted(Deleted *c) { assert(m_windows.contains(c)); delete m_windows.take(c); c->effectWindow()->setSceneWindow(NULL); } void Scene::windowGeometryShapeChanged(Toplevel *c) { if (!m_windows.contains(c)) // this is ok, shape is not valid by default return; Window *w = m_windows[ c ]; w->discardShape(); } void Scene::createStackingOrder(ToplevelList toplevels) { // TODO: cache the stacking_order in case it has not changed foreach (Toplevel *c, toplevels) { assert(m_windows.contains(c)); stacking_order.append(m_windows[ c ]); } } void Scene::clearStackingOrder() { stacking_order.clear(); } static Scene::Window *s_recursionCheck = NULL; void Scene::paintWindow(Window* w, int mask, QRegion region, WindowQuadList quads) { // no painting outside visible screen (and no transformations) const QSize &screenSize = screens()->size(); region &= QRect(0, 0, screenSize.width(), screenSize.height()); if (region.isEmpty()) // completely clipped return; if (w->window()->isDeleted() && w->window()->skipsCloseAnimation()) { // should not get painted return; } if (s_recursionCheck == w) { return; } WindowPaintData data(w->window()->effectWindow(), screenProjectionMatrix()); data.quads = quads; effects->paintWindow(effectWindow(w), mask, region, data); // paint thumbnails on top of window paintWindowThumbnails(w, region, data.opacity(), data.brightness(), data.saturation()); // and desktop thumbnails paintDesktopThumbnails(w); } static void adjustClipRegion(AbstractThumbnailItem *item, QRegion &clippingRegion) { if (item->clip() && item->clipTo()) { // the x/y positions of the parent item are not correct. The margins are added, though the size seems fine // that's why we have to get the offset by inspecting the anchors properties QQuickItem *parentItem = item->clipTo(); QPointF offset; QVariant anchors = parentItem->property("anchors"); if (anchors.isValid()) { if (QObject *anchorsObject = anchors.value()) { offset.setX(anchorsObject->property("leftMargin").toReal()); offset.setY(anchorsObject->property("topMargin").toReal()); } } QRectF rect = QRectF(parentItem->position() - offset, QSizeF(parentItem->width(), parentItem->height())); if (QQuickItem *p = parentItem->parentItem()) { rect = p->mapRectToScene(rect); } clippingRegion &= rect.adjusted(0,0,-1,-1).translated(item->window()->position()).toRect(); } } void Scene::paintWindowThumbnails(Scene::Window *w, QRegion region, qreal opacity, qreal brightness, qreal saturation) { EffectWindowImpl *wImpl = static_cast(effectWindow(w)); for (QHash >::const_iterator it = wImpl->thumbnails().constBegin(); it != wImpl->thumbnails().constEnd(); ++it) { if (it.value().isNull()) { continue; } WindowThumbnailItem *item = it.key(); if (!item->isVisible()) { continue; } EffectWindowImpl *thumb = it.value().data(); WindowPaintData thumbData(thumb, screenProjectionMatrix()); thumbData.setOpacity(opacity); thumbData.setBrightness(brightness * item->brightness()); thumbData.setSaturation(saturation * item->saturation()); const QRect visualThumbRect(thumb->expandedGeometry()); QSizeF size = QSizeF(visualThumbRect.size()); size.scale(QSizeF(item->width(), item->height()), Qt::KeepAspectRatio); if (size.width() > visualThumbRect.width() || size.height() > visualThumbRect.height()) { size = QSizeF(visualThumbRect.size()); } thumbData.setXScale(size.width() / static_cast(visualThumbRect.width())); thumbData.setYScale(size.height() / static_cast(visualThumbRect.height())); if (!item->window()) { continue; } const QPointF point = item->mapToScene(item->position()); qreal x = point.x() + w->x() + (item->width() - size.width())/2; qreal y = point.y() + w->y() + (item->height() - size.height()) / 2; x -= thumb->x(); y -= thumb->y(); // compensate shadow topleft padding x += (thumb->x()-visualThumbRect.x())*thumbData.xScale(); y += (thumb->y()-visualThumbRect.y())*thumbData.yScale(); thumbData.setXTranslation(x); thumbData.setYTranslation(y); int thumbMask = PAINT_WINDOW_TRANSFORMED | PAINT_WINDOW_LANCZOS; if (thumbData.opacity() == 1.0) { thumbMask |= PAINT_WINDOW_OPAQUE; } else { thumbMask |= PAINT_WINDOW_TRANSLUCENT; } QRegion clippingRegion = region; clippingRegion &= QRegion(wImpl->x(), wImpl->y(), wImpl->width(), wImpl->height()); adjustClipRegion(item, clippingRegion); effects->drawWindow(thumb, thumbMask, clippingRegion, thumbData); } } void Scene::paintDesktopThumbnails(Scene::Window *w) { EffectWindowImpl *wImpl = static_cast(effectWindow(w)); for (QList::const_iterator it = wImpl->desktopThumbnails().constBegin(); it != wImpl->desktopThumbnails().constEnd(); ++it) { DesktopThumbnailItem *item = *it; if (!item->isVisible()) { continue; } if (!item->window()) { continue; } s_recursionCheck = w; ScreenPaintData data; const QSize &screenSize = screens()->size(); QSize size = screenSize; size.scale(item->width(), item->height(), Qt::KeepAspectRatio); data *= QVector2D(size.width() / double(screenSize.width()), size.height() / double(screenSize.height())); const QPointF point = item->mapToScene(item->position()); const qreal x = point.x() + w->x() + (item->width() - size.width())/2; const qreal y = point.y() + w->y() + (item->height() - size.height()) / 2; const QRect region = QRect(x, y, item->width(), item->height()); QRegion clippingRegion = region; clippingRegion &= QRegion(wImpl->x(), wImpl->y(), wImpl->width(), wImpl->height()); adjustClipRegion(item, clippingRegion); data += QPointF(x, y); const int desktopMask = PAINT_SCREEN_TRANSFORMED | PAINT_WINDOW_TRANSFORMED | PAINT_SCREEN_BACKGROUND_FIRST; paintDesktop(item->desktop(), desktopMask, clippingRegion, data); s_recursionCheck = NULL; } } void Scene::paintDesktop(int desktop, int mask, const QRegion ®ion, ScreenPaintData &data) { static_cast(effects)->paintDesktop(desktop, mask, region, data); } // 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 (waylandServer() && waylandServer()->isScreenLocked() && !w->window()->isLockScreen() && !w->window()->isInputMethod()) { return; } w->sceneWindow()->performPaint(mask, region, data); } void Scene::extendPaintRegion(QRegion ®ion, bool opaqueFullscreen) { Q_UNUSED(region); Q_UNUSED(opaqueFullscreen); } bool Scene::blocksForRetrace() const { return false; } bool Scene::syncsToVBlank() const { return false; } void Scene::screenGeometryChanged(const QSize &size) { if (!overlayWindow()) { return; } overlayWindow()->resize(size); } bool Scene::makeOpenGLContextCurrent() { return false; } void Scene::doneOpenGLContextCurrent() { } void Scene::triggerFence() { } QMatrix4x4 Scene::screenProjectionMatrix() const { return QMatrix4x4(); } //**************************************** // Scene::Window //**************************************** Scene::Window::Window(Toplevel * c) : toplevel(c) , filter(ImageFilterFast) , m_shadow(NULL) , m_currentPixmap() , m_previousPixmap() , m_referencePixmapCounter(0) , disable_painting(0) , shape_valid(false) , cached_quad_list(NULL) { } Scene::Window::~Window() { delete m_shadow; } void Scene::Window::referencePreviousPixmap() { if (!m_previousPixmap.isNull() && m_previousPixmap->isDiscarded()) { m_referencePixmapCounter++; } } void Scene::Window::unreferencePreviousPixmap() { if (m_previousPixmap.isNull() || !m_previousPixmap->isDiscarded()) { return; } m_referencePixmapCounter--; if (m_referencePixmapCounter == 0) { m_previousPixmap.reset(); } } void Scene::Window::pixmapDiscarded() { if (!m_currentPixmap.isNull()) { if (m_currentPixmap->isValid()) { m_previousPixmap.reset(m_currentPixmap.take()); m_previousPixmap->markAsDiscarded(); } else { m_currentPixmap.reset(); } } } void Scene::Window::discardShape() { // it is created on-demand and cached, simply // reset the flag shape_valid = false; cached_quad_list.reset(); } // Find out the shape of the window using the XShape extension // or if shape is not set then simply it's the window geometry. const QRegion &Scene::Window::shape() const { if (!shape_valid) { if (toplevel->shape()) { auto cookie = xcb_shape_get_rectangles_unchecked(connection(), toplevel->frameId(), XCB_SHAPE_SK_BOUNDING); ScopedCPointer reply(xcb_shape_get_rectangles_reply(connection(), cookie, nullptr)); if (!reply.isNull()) { shape_region = QRegion(); auto *rects = xcb_shape_get_rectangles_rectangles(reply.data()); for (int i = 0; i < xcb_shape_get_rectangles_rectangles_length(reply.data()); ++i) shape_region += QRegion(rects[ i ].x, rects[ i ].y, rects[ i ].width, rects[ i ].height); // 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 { if (AbstractClient *c = dynamic_cast< AbstractClient * > (toplevel)) { if (c->isShade()) return QRegion(); } // TODO: cache const QRegion r = shape() & QRect(toplevel->clientPos(), toplevel->clientSize()); return r.isEmpty() ? QRegion() : r; } bool Scene::Window::isVisible() const { if (toplevel->isDeleted()) return false; if (!toplevel->isOnCurrentDesktop()) return false; if (!toplevel->isOnCurrentActivity()) return false; if (AbstractClient *c = dynamic_cast(toplevel)) return c->isShown(true); return true; // Unmanaged is always visible } 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 (toplevel->isDeleted()) disable_painting |= PAINT_DISABLED_BY_DELETE; if (static_cast(effects)->isDesktopRendering()) { if (!toplevel->isOnDesktop(static_cast(effects)->currentRenderedDesktop())) { disable_painting |= PAINT_DISABLED_BY_DESKTOP; } } else { if (!toplevel->isOnCurrentDesktop()) disable_painting |= PAINT_DISABLED_BY_DESKTOP; } if (!toplevel->isOnCurrentActivity()) disable_painting |= PAINT_DISABLED_BY_ACTIVITY; if (AbstractClient *c = dynamic_cast(toplevel)) { if (c->isMinimized()) disable_painting |= PAINT_DISABLED_BY_MINIMIZE; if (c->tabGroup() && c != c->tabGroup()->current()) disable_painting |= PAINT_DISABLED_BY_TAB_GROUP; 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->decorationRect().size()) ret = makeQuads(WindowQuadContents, shape()); // has no decoration else { AbstractClient *client = dynamic_cast(toplevel); QRegion contents = clientShape(); QRegion center = toplevel->transparentRect(); QRegion decoration = (client ? QRegion(client->decorationRect()) : shape()) - center; ret = makeQuads(WindowQuadContents, contents, toplevel->clientContentPos()); QRect rects[4]; bool isShadedClient = false; if (client) { client->layoutDecorationRects(rects[0], rects[1], rects[2], rects[3]); isShadedClient = client->isShade() || center.isEmpty(); } if (isShadedClient) { const QRect bounding = rects[0] | rects[1] | rects[2] | rects[3]; ret += makeDecorationQuads(rects, bounding); } else { ret += makeDecorationQuads(rects, decoration); } } if (m_shadow && toplevel->wantsShadowToBeRendered()) { ret << m_shadow->shadowQuads(); } effects->buildQuads(toplevel->effectWindow(), ret); cached_quad_list.reset(new WindowQuadList(ret)); return ret; } WindowQuadList Scene::Window::makeDecorationQuads(const QRect *rects, const QRegion ®ion) const { WindowQuadList list; const QPoint offsets[4] = { QPoint(-rects[0].x() + rects[1].height() + rects[3].height() + 2, -rects[0].y()), // Left QPoint(-rects[1].x(), -rects[1].y()), // Top QPoint(-rects[2].x() + rects[1].height() + rects[3].height() + rects[0].width() + 3, -rects[2].y()), // Right QPoint(-rects[3].x(), -rects[3].y() + rects[1].height() + 1) // Bottom }; const Qt::Orientation orientations[4] = { Qt::Vertical, // Left Qt::Horizontal, // Top Qt::Vertical, // Right Qt::Horizontal, // Bottom }; for (int i = 0; i < 4; i++) { foreach (const QRect &r, (region & rects[i]).rects()) { if (!r.isValid()) continue; const bool swap = orientations[i] == Qt::Vertical; const int x0 = r.x(); const int y0 = r.y(); const int x1 = r.x() + r.width(); const int y1 = r.y() + r.height(); const int u0 = x0 + offsets[i].x(); const int v0 = y0 + offsets[i].y(); const int u1 = x1 + offsets[i].x(); const int v1 = y1 + offsets[i].y(); WindowQuad quad(WindowQuadDecoration); quad.setUVAxisSwapped(swap); if (swap) { quad[0] = WindowVertex(x0, y0, v0, u0); // Top-left quad[1] = WindowVertex(x1, y0, v0, u1); // Top-right quad[2] = WindowVertex(x1, y1, v1, u1); // Bottom-right quad[3] = WindowVertex(x0, y1, v1, u0); // Bottom-left } else { quad[0] = WindowVertex(x0, y0, u0, v0); // Top-left quad[1] = WindowVertex(x1, y0, u1, v0); // Top-right quad[2] = WindowVertex(x1, y1, u1, v1); // Bottom-right quad[3] = WindowVertex(x0, y1, u0, v1); // Bottom-left } list.append(quad); } } return list; } WindowQuadList Scene::Window::makeQuads(WindowQuadType type, const QRegion& reg, const QPoint &textureOffset) 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() + textureOffset.x(), r.y() + textureOffset.y()); quad[ 1 ] = WindowVertex(r.x() + r.width(), r.y(), r.x() + r.width() + textureOffset.x(), r.y() + textureOffset.y()); quad[ 2 ] = WindowVertex(r.x() + r.width(), r.y() + r.height(), r.x() + r.width() + textureOffset.x(), r.y() + r.height() + textureOffset.y()); quad[ 3 ] = WindowVertex(r.x(), r.y() + r.height(), r.x() + textureOffset.x(), r.y() + r.height() + textureOffset.y()); ret.append(quad); } return ret; } //**************************************** // WindowPixmap //**************************************** WindowPixmap::WindowPixmap(Scene::Window *window) : m_window(window) , m_pixmap(XCB_PIXMAP_NONE) , m_discarded(false) { } WindowPixmap::WindowPixmap(const QPointer &subSurface, WindowPixmap *parent) : m_window(parent->m_window) , m_pixmap(XCB_PIXMAP_NONE) , m_discarded(false) , m_parent(parent) , m_subSurface(subSurface) { } WindowPixmap::~WindowPixmap() { if (m_pixmap != XCB_WINDOW_NONE) { xcb_free_pixmap(connection(), m_pixmap); } if (m_buffer) { using namespace KWayland::Server; QObject::disconnect(m_buffer.data(), &BufferInterface::aboutToBeDestroyed, m_buffer.data(), &BufferInterface::unref); m_buffer->unref(); } } void WindowPixmap::create() { if (isValid() || toplevel()->isDeleted()) { return; } // always update from Buffer on Wayland, don't try using XPixmap if (kwinApp()->shouldUseWaylandForCompositing()) { // use Buffer updateBuffer(); if ((m_buffer || !m_fbo.isNull()) && m_subSurface.isNull()) { m_window->unreferencePreviousPixmap(); } return; } XServerGrabber grabber; xcb_pixmap_t pix = xcb_generate_id(connection()); xcb_void_cookie_t namePixmapCookie = xcb_composite_name_window_pixmap_checked(connection(), toplevel()->frameId(), pix); Xcb::WindowAttributes windowAttributes(toplevel()->frameId()); Xcb::WindowGeometry windowGeometry(toplevel()->frameId()); if (xcb_generic_error_t *error = xcb_request_check(connection(), namePixmapCookie)) { qCDebug(KWIN_CORE) << "Creating window pixmap failed: " << error->error_code; free(error); return; } // check that the received pixmap is valid and actually matches what we // know about the window (i.e. size) if (!windowAttributes || windowAttributes->map_state != XCB_MAP_STATE_VIEWABLE) { qCDebug(KWIN_CORE) << "Creating window pixmap failed: " << this; xcb_free_pixmap(connection(), pix); return; } if (!windowGeometry || windowGeometry->width != toplevel()->width() || windowGeometry->height != toplevel()->height()) { qCDebug(KWIN_CORE) << "Creating window pixmap failed: " << this; xcb_free_pixmap(connection(), pix); return; } m_pixmap = pix; m_pixmapSize = QSize(toplevel()->width(), toplevel()->height()); m_contentsRect = QRect(toplevel()->clientPos(), toplevel()->clientSize()); m_window->unreferencePreviousPixmap(); } WindowPixmap *WindowPixmap::createChild(const QPointer &subSurface) { Q_UNUSED(subSurface) return nullptr; } bool WindowPixmap::isValid() const { if (!m_buffer.isNull() || !m_fbo.isNull()) { return true; } return m_pixmap != XCB_PIXMAP_NONE; } void WindowPixmap::updateBuffer() { using namespace KWayland::Server; if (SurfaceInterface *s = surface()) { QVector oldTree = m_children; QVector children; using namespace KWayland::Server; const auto subSurfaces = s->childSubSurfaces(); for (const auto &subSurface : subSurfaces) { if (subSurface.isNull()) { continue; } auto it = std::find_if(oldTree.begin(), oldTree.end(), [subSurface] (WindowPixmap *p) { return p->m_subSurface == subSurface; }); if (it != oldTree.end()) { children << *it; (*it)->updateBuffer(); oldTree.erase(it); } else { WindowPixmap *p = createChild(subSurface); if (p) { p->create(); children << p; } } } setChildren(children); qDeleteAll(oldTree); if (auto b = s->buffer()) { if (b == m_buffer) { // no change return; } if (m_buffer) { QObject::disconnect(m_buffer.data(), &BufferInterface::aboutToBeDestroyed, m_buffer.data(), &BufferInterface::unref); m_buffer->unref(); } m_buffer = b; m_buffer->ref(); QObject::connect(m_buffer.data(), &BufferInterface::aboutToBeDestroyed, m_buffer.data(), &BufferInterface::unref); } else if (m_subSurface) { if (m_buffer) { QObject::disconnect(m_buffer.data(), &BufferInterface::aboutToBeDestroyed, m_buffer.data(), &BufferInterface::unref); m_buffer->unref(); m_buffer.clear(); } } else { // might be an internal window const auto &fbo = toplevel()->internalFramebufferObject(); if (!fbo.isNull()) { m_fbo = fbo; } } } else { if (m_buffer) { QObject::disconnect(m_buffer.data(), &BufferInterface::aboutToBeDestroyed, m_buffer.data(), &BufferInterface::unref); m_buffer->unref(); m_buffer.clear(); } } } KWayland::Server::SurfaceInterface *WindowPixmap::surface() const { if (!m_subSurface.isNull()) { return m_subSurface->surface().data(); } else { return toplevel()->surface(); } } //**************************************** // Scene::EffectFrame //**************************************** Scene::EffectFrame::EffectFrame(EffectFrameImpl* frame) : m_effectFrame(frame) { } Scene::EffectFrame::~EffectFrame() { } } // namespace diff --git a/scene.h b/scene.h index 58280a58a..c5ee8655e 100644 --- a/scene.h +++ b/scene.h @@ -1,637 +1,630 @@ /******************************************************************** 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_SCENE_H #define KWIN_SCENE_H #include "toplevel.h" #include "utils.h" #include "kwineffects.h" #include #include class QOpenGLFramebufferObject; namespace KWayland { namespace Server { class BufferInterface; class SubSurfaceInterface; } } namespace KWin { namespace Decoration { class DecoratedClientImpl; class Renderer; } class AbstractThumbnailItem; class Deleted; class EffectFrameImpl; class EffectWindowImpl; class OverlayWindow; class Shadow; class WindowPixmap; // The base class for compositing backends. class KWIN_EXPORT Scene : public QObject { Q_OBJECT public: explicit Scene(QObject *parent = nullptr); virtual ~Scene() = 0; class EffectFrame; class Window; // Returns true if the ctor failed to properly initialize. virtual bool initFailed() const = 0; virtual CompositingType compositingType() const = 0; virtual bool hasPendingFlush() const { return false; } // Repaints the given screen areas, windows provides the stacking order. // The entry point for the main part of the painting pass. // returns the time since the last vblank signal - if there's one // ie. "what of this frame is lost to painting" virtual qint64 paint(QRegion damage, ToplevelList windows) = 0; // Notification function - KWin core informs about changes. // Used to mainly discard cached data. // a new window has been created void windowAdded(Toplevel*); /** * @brief Creates the Scene backend of an EffectFrame. * * @param frame The EffectFrame this Scene::EffectFrame belongs to. */ virtual Scene::EffectFrame *createEffectFrame(EffectFrameImpl *frame) = 0; /** * @brief Creates the Scene specific Shadow subclass. * * An implementing class has to create a proper instance. It is not allowed to * return @c null. * * @param toplevel The Toplevel for which the Shadow needs to be created. */ virtual Shadow *createShadow(Toplevel *toplevel) = 0; /** * Method invoked when the screen geometry is changed. * Reimplementing classes should also invoke the parent method * as it takes care of resizing the overlay window. * @param size The new screen geometry size **/ virtual void screenGeometryChanged(const QSize &size); // Flags controlling how painting is done. 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, // Whole screen will be painted with transformed geometry. PAINT_SCREEN_TRANSFORMED = 1 << 4, // At least one window will be painted with transformed geometry. 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 removed // 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 }; // types of filtering available enum ImageFilterType { ImageFilterFast, ImageFilterGood }; // there's nothing to paint (adjust time_diff later) virtual void idle(); virtual bool blocksForRetrace() const; virtual bool syncsToVBlank() const; virtual OverlayWindow* overlayWindow() = 0; virtual bool makeOpenGLContextCurrent(); virtual void doneOpenGLContextCurrent(); virtual QMatrix4x4 screenProjectionMatrix() const; /** * Whether the Scene uses an X11 overlay window to perform compositing. */ virtual bool usesOverlayWindow() const = 0; virtual void triggerFence(); virtual Decoration::Renderer *createDecorationRenderer(Decoration::DecoratedClientImpl *) = 0; Q_SIGNALS: void frameRendered(); public Q_SLOTS: // a window has been destroyed void windowDeleted(KWin::Deleted*); // shape/size of a window changed void windowGeometryShapeChanged(KWin::Toplevel* c); // a window has been closed void windowClosed(KWin::Toplevel* c, KWin::Deleted* deleted); protected: virtual Window *createWindow(Toplevel *toplevel) = 0; void createStackingOrder(ToplevelList toplevels); void clearStackingOrder(); // shared implementation, starts painting the screen void paintScreen(int *mask, const QRegion &damage, const QRegion &repaint, QRegion *updateRegion, QRegion *validRegion, const QMatrix4x4 &projection = QMatrix4x4()); friend class EffectsHandlerImpl; // called after all effects had their paintScreen() called void finalPaintScreen(int mask, QRegion region, ScreenPaintData& data); // shared implementation of painting the screen in the generic // (unoptimized) way virtual void paintGenericScreen(int mask, ScreenPaintData data); // shared implementation of painting the screen in an optimized way virtual void paintSimpleScreen(int mask, QRegion region); // paint the background (not the desktop background - the whole background) virtual void paintBackground(QRegion region) = 0; // called after all effects had their paintWindow() called void finalPaintWindow(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data); // shared implementation, starts painting the window virtual void paintWindow(Window* w, int mask, QRegion region, WindowQuadList quads); // called after all effects had their drawWindow() called virtual void finalDrawWindow(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data); // let the scene decide whether it's better to paint more of the screen, eg. in order to allow a buffer swap // the default is NOOP virtual void extendPaintRegion(QRegion ®ion, bool opaqueFullscreen); virtual void paintDesktop(int desktop, int mask, const QRegion ®ion, ScreenPaintData &data); // compute time since the last repaint void updateTimeDiff(); // saved data for 2nd pass of optimized screen painting struct Phase2Data { Phase2Data(Window* w, QRegion r, QRegion c, int m, const WindowQuadList& q) : window(w), region(r), clip(c), mask(m), quads(q) {} Phase2Data() { window = 0; mask = 0; } Window* window; QRegion region; QRegion clip; int mask; WindowQuadList quads; }; // The region which actually has been painted by paintScreen() and should be // copied from the buffer to the screen. I.e. the region returned from Scene::paintScreen(). // Since prePaintWindow() can extend areas to paint, these changes would have to propagate // up all the way from paintSimpleScreen() up to paintScreen(), so save them here rather // than propagate them up in arguments. QRegion painted_region; // Additional damage that needs to be repaired to bring a reused back buffer up to date QRegion repaint_region; // The dirty region before it was unioned with repaint_region QRegion damaged_region; // time since last repaint int time_diff; QElapsedTimer last_time; private: void paintWindowThumbnails(Scene::Window *w, QRegion region, qreal opacity, qreal brightness, qreal saturation); void paintDesktopThumbnails(Scene::Window *w); QHash< Toplevel*, Window* > m_windows; // windows in their stacking order QVector< Window* > stacking_order; }; // The base class for windows representations in composite backends class Scene::Window { public: Window(Toplevel* c); virtual ~Window(); // perform the actual painting of the window virtual void performPaint(int mask, QRegion region, WindowPaintData data) = 0; // do any cleanup needed when the window's composite pixmap is discarded void pixmapDiscarded(); int x() const; int y() const; int width() const; int height() const; QRect geometry() const; QPoint pos() const; QSize size() const; QRect rect() const; // access to the internal window class // TODO eventually get rid of this Toplevel* window() const; // should the window be painted bool isPaintingEnabled() const; void resetPaintingEnabled(); // 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 }; void enablePainting(int reason); void disablePainting(int reason); // is the window visible at all bool isVisible() const; // is the window fully opaque bool isOpaque() const; // shape of the window const QRegion &shape() const; QRegion clientShape() const; void discardShape(); void updateToplevel(Toplevel* c); // creates initial quad list for the window virtual WindowQuadList buildQuads(bool force = false) const; - void suspendUnredirect(bool suspend); void updateShadow(Shadow* shadow); const Shadow* shadow() const; Shadow* shadow(); void referencePreviousPixmap(); void unreferencePreviousPixmap(); protected: WindowQuadList makeQuads(WindowQuadType type, const QRegion& reg, const QPoint &textureOffset = QPoint(0, 0)) const; WindowQuadList makeDecorationQuads(const QRect *rects, const QRegion ®ion) const; /** * @brief Returns the WindowPixmap for this Window. * * If the WindowPixmap does not yet exist, this method will invoke @link createWindowPixmap. * If the WindowPixmap is not valid it tries to create it, in case this succeeds the WindowPixmap is * returned. In case it fails, the previous (and still valid) WindowPixmap is returned. * * Note: this method can return @c NULL as there might neither be a valid previous nor current WindowPixmap * around. * * The WindowPixmap gets casted to the type passed in as a template parameter. That way this class does not * need to know the actual WindowPixmap subclass used by the concrete Scene implementations. * * @return The WindowPixmap casted to T* or @c NULL if there is no valid window pixmap. */ template T *windowPixmap(); template T *previousWindowPixmap(); /** * @brief Factory method to create a WindowPixmap. * * The inheriting classes need to implement this method to create a new instance of their WindowPixmap subclass. * Note: do not use @link WindowPixmap::create on the created instance. The Scene will take care of that. */ virtual WindowPixmap *createWindowPixmap() = 0; Toplevel* toplevel; ImageFilterType filter; Shadow *m_shadow; private: QScopedPointer m_currentPixmap; QScopedPointer m_previousPixmap; int m_referencePixmapCounter; int disable_painting; mutable QRegion shape_region; mutable bool shape_valid; mutable QScopedPointer cached_quad_list; Q_DISABLE_COPY(Window) }; /** * @brief Wrapper for a pixmap of the @link Scene::Window. * * This class encapsulates the functionality to get the pixmap for a window. When initialized the pixmap is not yet * mapped to the window and @link isValid will return @c false. The pixmap mapping to the window can be established * through @link create. If it succeeds @link isValid will return @c true, otherwise it will keep in the non valid * state and it can be tried to create the pixmap mapping again (e.g. in the next frame). * * This class is not intended to be updated when the pixmap is no longer valid due to e.g. resizing the window. * Instead a new instance of this class should be instantiated. The idea behind this is that a valid pixmap does not * get destroyed, but can continue to be used. To indicate that a newer pixmap should in generally be around, one can * use @link markAsDiscarded. * * This class is intended to be inherited for the needs of the compositor backends which need further mapping from * the native pixmap to the respective rendering format. */ class WindowPixmap { public: virtual ~WindowPixmap(); /** * @brief Tries to create the mapping between the Window and the pixmap. * * In case this method succeeds in creating the pixmap for the window, @link isValid will return @c true otherwise * @c false. * * Inheriting classes should re-implement this method in case they need to add further functionality for mapping the * native pixmap to the rendering format. */ virtual void create(); /** * @return @c true if the pixmap has been created and is valid, @c false otherwise */ bool isValid() const; /** * @return The native X11 pixmap handle */ xcb_pixmap_t pixmap() const; /** * @return The Wayland BufferInterface for this WindowPixmap. **/ QPointer buffer() const; const QSharedPointer &fbo() const; /** * @brief Whether this WindowPixmap is considered as discarded. This means the window has changed in a way that a new * WindowPixmap should have been created already. * * @return @c true if this WindowPixmap is considered as discarded, @c false otherwise. * @see markAsDiscarded */ bool isDiscarded() const; /** * @brief Marks this WindowPixmap as discarded. From now on @link isDiscarded will return @c true. This method should * only be used by the Window when it changes in a way that a new pixmap is required. * * @see isDiscarded */ void markAsDiscarded(); /** * The size of the pixmap. */ const QSize &size() const; /** * The geometry of the Client's content inside the pixmap. In case of a decorated Client the * pixmap also contains the decoration which is not rendered into this pixmap, though. This * contentsRect tells where inside the complete pixmap the real content is. */ const QRect &contentsRect() const; /** * @brief Returns the Toplevel this WindowPixmap belongs to. * Note: the Toplevel can change over the lifetime of the WindowPixmap in case the Toplevel is copied to Deleted. */ Toplevel *toplevel() const; /** * @returns the parent WindowPixmap in the sub-surface tree **/ WindowPixmap *parent() const { return m_parent; } /** * @returns the current sub-surface tree **/ QVector children() const { return m_children; } /** * @returns the subsurface this WindowPixmap is for if it is not for a root window **/ QPointer subSurface() const { return m_subSurface; } /** * @returns the surface this WindowPixmap references, might be @c null. **/ KWayland::Server::SurfaceInterface *surface() const; protected: explicit WindowPixmap(Scene::Window *window); explicit WindowPixmap(const QPointer &subSurface, WindowPixmap *parent); virtual WindowPixmap *createChild(const QPointer &subSurface); /** * @return The Window this WindowPixmap belongs to */ Scene::Window *window(); /** * Should be called by the implementing subclasses when the Wayland Buffer changed and needs * updating. **/ virtual void updateBuffer(); /** * Sets the sub-surface tree to @p children. **/ void setChildren(const QVector &children) { m_children = children; } private: Scene::Window *m_window; xcb_pixmap_t m_pixmap; QSize m_pixmapSize; bool m_discarded; QRect m_contentsRect; QPointer m_buffer; QSharedPointer m_fbo; WindowPixmap *m_parent = nullptr; QVector m_children; QPointer m_subSurface; }; class Scene::EffectFrame { public: EffectFrame(EffectFrameImpl* frame); virtual ~EffectFrame(); virtual void render(QRegion region, double opacity, double frameOpacity) = 0; virtual void free() = 0; virtual void freeIconFrame() = 0; virtual void freeTextFrame() = 0; virtual void freeSelection() = 0; virtual void crossFadeIcon() = 0; virtual void crossFadeText() = 0; protected: EffectFrameImpl* m_effectFrame; }; inline int Scene::Window::x() const { return toplevel->x(); } inline int Scene::Window::y() const { return toplevel->y(); } inline int Scene::Window::width() const { return toplevel->width(); } inline int Scene::Window::height() const { return toplevel->height(); } inline QRect Scene::Window::geometry() const { return toplevel->geometry(); } inline QSize Scene::Window::size() const { return toplevel->size(); } inline QPoint Scene::Window::pos() const { return toplevel->pos(); } inline QRect Scene::Window::rect() const { return toplevel->rect(); } inline Toplevel* Scene::Window::window() const { return toplevel; } inline void Scene::Window::updateToplevel(Toplevel* c) { toplevel = c; } -inline -void Scene::Window::suspendUnredirect(bool suspend) -{ - toplevel->suspendUnredirect(suspend); -} - inline void Scene::Window::updateShadow(Shadow* shadow) { m_shadow = shadow; } inline const Shadow* Scene::Window::shadow() const { return m_shadow; } inline Shadow* Scene::Window::shadow() { return m_shadow; } inline QPointer WindowPixmap::buffer() const { return m_buffer; } inline const QSharedPointer &WindowPixmap::fbo() const { return m_fbo; } template inline T* Scene::Window::windowPixmap() { if (m_currentPixmap.isNull()) { m_currentPixmap.reset(createWindowPixmap()); } if (m_currentPixmap->isValid()) { return static_cast(m_currentPixmap.data()); } m_currentPixmap->create(); if (m_currentPixmap->isValid()) { return static_cast(m_currentPixmap.data()); } else { return static_cast(m_previousPixmap.data()); } } template inline T* Scene::Window::previousWindowPixmap() { return static_cast(m_previousPixmap.data()); } inline Toplevel* WindowPixmap::toplevel() const { return m_window->window(); } inline xcb_pixmap_t WindowPixmap::pixmap() const { return m_pixmap; } inline bool WindowPixmap::isDiscarded() const { return m_discarded; } inline void WindowPixmap::markAsDiscarded() { m_discarded = true; m_window->referencePreviousPixmap(); } inline const QRect &WindowPixmap::contentsRect() const { return m_contentsRect; } inline const QSize &WindowPixmap::size() const { return m_pixmapSize; } } // namespace #endif diff --git a/shell_client.cpp b/shell_client.cpp index 887c5a674..9e4a0a592 100644 --- a/shell_client.cpp +++ b/shell_client.cpp @@ -1,1207 +1,1201 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 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 . *********************************************************************/ #include "shell_client.h" #include "composite.h" #include "cursor.h" #include "deleted.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include "virtualdesktops.h" #include "workspace.h" #include "decorations/decorationbridge.h" #include "decorations/decoratedclient.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KWayland::Server; namespace KWin { ShellClient::ShellClient(ShellSurfaceInterface *surface) : AbstractClient() , m_shellSurface(surface) , m_xdgShellSurface(nullptr) , m_xdgShellPopup(nullptr) , m_internal(surface->client() == waylandServer()->internalConnection()) { setSurface(surface->surface()); init(); } ShellClient::ShellClient(XdgShellSurfaceInterface *surface) : AbstractClient() , m_shellSurface(nullptr) , m_xdgShellSurface(surface) , m_xdgShellPopup(nullptr) , m_internal(surface->client() == waylandServer()->internalConnection()) { setSurface(surface->surface()); init(); } ShellClient::ShellClient(XdgShellPopupInterface *surface) : AbstractClient() , m_shellSurface(nullptr) , m_xdgShellSurface(nullptr) , m_xdgShellPopup(surface) , m_internal(surface->client() == waylandServer()->internalConnection()) { setSurface(surface->surface()); init(); } ShellClient::~ShellClient() = default; template void ShellClient::initSurface(T *shellSurface) { m_caption = shellSurface->title(); connect(shellSurface, &T::titleChanged, this, &ShellClient::captionChanged); connect(shellSurface, &T::destroyed, this, &ShellClient::destroyClient); connect(shellSurface, &T::titleChanged, this, [this] (const QString &s) { m_caption = s; emit captionChanged(); } ); connect(shellSurface, &T::moveRequested, this, [this] { // TODO: check the seat and serial performMouseCommand(Options::MouseMove, Cursor::pos()); } ); connect(shellSurface, &T::windowClassChanged, this, &ShellClient::updateIcon); setResourceClass(shellSurface->windowClass()); connect(shellSurface, &T::windowClassChanged, this, [this] (const QByteArray &windowClass) { setResourceClass(windowClass); } ); connect(shellSurface, &T::resizeRequested, this, [this] (SeatInterface *seat, quint32 serial, Qt::Edges edges) { // TODO: check the seat and serial Q_UNUSED(seat) Q_UNUSED(serial) if (!isResizable() || isShade()) { return; } if (isMoveResize()) { finishMoveResize(false); } setMoveResizePointerButtonDown(true); setMoveOffset(Cursor::pos() - pos()); // map from global setInvertedMoveOffset(rect().bottomRight() - moveOffset()); setUnrestrictedMoveResize(false); auto toPosition = [edges] { Position pos = PositionCenter; if (edges.testFlag(Qt::TopEdge)) { pos = PositionTop; } else if (edges.testFlag(Qt::BottomEdge)) { pos = PositionBottom; } if (edges.testFlag(Qt::LeftEdge)) { pos = Position(pos | PositionLeft); } else if (edges.testFlag(Qt::RightEdge)) { pos = Position(pos | PositionRight); } return pos; }; setMoveResizePointerMode(toPosition()); if (!startMoveResize()) setMoveResizePointerButtonDown(false); updateCursor(); } ); connect(shellSurface, &T::maximizedChanged, this, [this] (bool maximized) { maximize(maximized ? MaximizeFull : MaximizeRestore); } ); // TODO: consider output! connect(shellSurface, &T::fullscreenChanged, this, &ShellClient::clientFullScreenChanged); connect(shellSurface, &T::transientForChanged, this, &ShellClient::setTransient); } void ShellClient::init() { findInternalWindow(); createWindowId(); setupCompositing(); SurfaceInterface *s = surface(); Q_ASSERT(s); if (s->buffer()) { setReadyForPainting(); if (shouldExposeToWindowManagement()) { setupWindowManagementInterface(); } m_unmapped = false; m_clientSize = s->buffer()->size(); } else { ready_for_painting = false; } if (m_internalWindow) { updateInternalWindowGeometry(); setOnAllDesktops(true); } else { doSetGeometry(QRect(QPoint(0, 0), m_clientSize)); setDesktop(VirtualDesktopManager::self()->current()); } if (waylandServer()->inputMethodConnection() == s->client()) { m_windowType = NET::OnScreenDisplay; } connect(s, &SurfaceInterface::sizeChanged, this, [this] { m_clientSize = surface()->buffer()->size(); doSetGeometry(QRect(geom.topLeft(), m_clientSize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); discardWindowPixmap(); } ); connect(s, &SurfaceInterface::unmapped, this, &ShellClient::unmap); connect(s, &SurfaceInterface::destroyed, this, &ShellClient::destroyClient); if (m_shellSurface) { initSurface(m_shellSurface); } else if (m_xdgShellSurface) { initSurface(m_xdgShellSurface); connect(m_xdgShellSurface, &XdgShellSurfaceInterface::windowMenuRequested, this, [this] (SeatInterface *seat, quint32 serial, const QPoint &surfacePos) { // TODO: check serial on seat Q_UNUSED(seat) Q_UNUSED(serial) performMouseCommand(Options::MouseOperationsMenu, pos() + surfacePos); } ); connect(m_xdgShellSurface, &XdgShellSurfaceInterface::minimizeRequested, this, [this] { performMouseCommand(Options::MouseMinimize, Cursor::pos()); } ); auto configure = [this] { if (m_closing) { return; } m_xdgShellSurface->configure(xdgSurfaceStates()); }; connect(this, &AbstractClient::activeChanged, this, configure); connect(this, &AbstractClient::clientStartUserMovedResized, this, configure); connect(this, &AbstractClient::clientFinishUserMovedResized, this, configure); } else if (m_xdgShellPopup) { connect(m_xdgShellPopup, &XdgShellPopupInterface::destroyed, this, &ShellClient::destroyClient); } updateIcon(); // setup shadow integration getShadow(); connect(s, &SurfaceInterface::shadowChanged, this, &Toplevel::getShadow); setTransient(); // check whether we have a ServerSideDecoration if (ServerSideDecorationInterface *deco = ServerSideDecorationInterface::get(s)) { installServerSideDecoration(deco); } updateColorScheme(QString()); } void ShellClient::destroyClient() { m_closing = true; Deleted *del = nullptr; if (workspace()) { del = Deleted::create(this); } emit windowClosed(this, del); destroyWindowManagementInterface(); destroyDecoration(); if (workspace()) { StackingUpdatesBlocker blocker(workspace()); if (transientFor()) { transientFor()->removeTransient(this); } for (auto it = transients().constBegin(); it != transients().constEnd();) { if ((*it)->transientFor() == this) { removeTransient(*it); it = transients().constBegin(); // restart, just in case something more has changed with the list } else { ++it; } } } waylandServer()->removeClient(this); if (del) { del->unrefWindow(); } m_shellSurface = nullptr; m_xdgShellSurface = nullptr; m_xdgShellPopup = nullptr; deleteClient(this); } void ShellClient::deleteClient(ShellClient *c) { delete c; } QStringList ShellClient::activities() const { // TODO: implement return QStringList(); } QPoint ShellClient::clientContentPos() const { return -1 * clientPos(); } QSize ShellClient::clientSize() const { // TODO: connect for changes return m_clientSize; } void ShellClient::debug(QDebug &stream) const { // TODO: implement Q_UNUSED(stream) } Layer ShellClient::layerForDock() const { if (m_plasmaShellSurface) { switch (m_plasmaShellSurface->panelBehavior()) { case PlasmaShellSurfaceInterface::PanelBehavior::WindowsCanCover: return NormalLayer; case PlasmaShellSurfaceInterface::PanelBehavior::AutoHide: return AboveLayer; case PlasmaShellSurfaceInterface::PanelBehavior::WindowsGoBelow: case PlasmaShellSurfaceInterface::PanelBehavior::AlwaysVisible: return DockLayer; default: Q_UNREACHABLE(); break; } } return AbstractClient::layerForDock(); } -bool ShellClient::shouldUnredirect() const -{ - // TODO: unredirect for fullscreen - return false; -} - QRect ShellClient::transparentRect() const { // TODO: implement return QRect(); } NET::WindowType ShellClient::windowType(bool direct, int supported_types) const { // TODO: implement Q_UNUSED(direct) Q_UNUSED(supported_types) return m_windowType; } double ShellClient::opacity() const { return m_opacity; } void ShellClient::setOpacity(double opacity) { const qreal newOpacity = qBound(0.0, opacity, 1.0); if (newOpacity == m_opacity) { return; } const qreal oldOpacity = m_opacity; m_opacity = newOpacity; addRepaintFull(); emit opacityChanged(this, oldOpacity); } void ShellClient::addDamage(const QRegion &damage) { auto s = surface(); if (s->buffer()->size().isValid()) { m_clientSize = s->buffer()->size(); QPoint position = geom.topLeft(); if (m_positionAfterResize.isValid()) { addLayerRepaint(geometry()); position = m_positionAfterResize.point(); m_positionAfterResize.clear(); } doSetGeometry(QRect(position, m_clientSize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); } markAsMapped(); setDepth(s->buffer()->hasAlphaChannel() ? 32 : 24); repaints_region += damage.translated(clientPos()); Toplevel::addDamage(damage); } void ShellClient::setInternalFramebufferObject(const QSharedPointer &fbo) { if (fbo.isNull()) { unmap(); return; } markAsMapped(); m_clientSize = fbo->size(); doSetGeometry(QRect(geom.topLeft(), m_clientSize)); Toplevel::setInternalFramebufferObject(fbo); Toplevel::addDamage(QRegion(0, 0, width(), height())); } void ShellClient::markAsMapped() { if (!m_unmapped) { return; } m_unmapped = false; if (!ready_for_painting) { setReadyForPainting(); } else { addRepaintFull(); emit windowShown(this); } if (shouldExposeToWindowManagement()) { setupWindowManagementInterface(); } } void ShellClient::createDecoration(const QRect &oldGeom) { KDecoration2::Decoration *decoration = Decoration::DecorationBridge::self()->createDecoration(this); if (decoration) { QMetaObject::invokeMethod(decoration, "update", Qt::QueuedConnection); connect(decoration, &KDecoration2::Decoration::shadowChanged, this, &Toplevel::getShadow); connect(decoration, &KDecoration2::Decoration::bordersChanged, this, [this]() { GeometryUpdatesBlocker blocker(this); RequestGeometryBlocker requestBlocker(this); QRect oldgeom = geometry(); if (!isShade()) checkWorkspacePosition(oldgeom); emit geometryShapeChanged(this, oldgeom); } ); } setDecoration(decoration); // TODO: ensure the new geometry still fits into the client area (e.g. maximized windows) doSetGeometry(QRect(oldGeom.topLeft(), m_clientSize + (decoration ? QSize(decoration->borderLeft() + decoration->borderRight(), decoration->borderBottom() + decoration->borderTop()) : QSize()))); emit geometryShapeChanged(this, oldGeom); } void ShellClient::updateDecoration(bool check_workspace_pos, bool force) { if (!force && ((!isDecorated() && noBorder()) || (isDecorated() && !noBorder()))) return; QRect oldgeom = geometry(); QRect oldClientGeom = oldgeom.adjusted(borderLeft(), borderTop(), -borderRight(), -borderBottom()); blockGeometryUpdates(true); if (force) destroyDecoration(); if (!noBorder()) { createDecoration(oldgeom); } else destroyDecoration(); if (m_serverDecoration && isDecorated()) { m_serverDecoration->setMode(KWayland::Server::ServerSideDecorationManagerInterface::Mode::Server); } getShadow(); if (check_workspace_pos) checkWorkspacePosition(oldgeom, -2, oldClientGeom); blockGeometryUpdates(false); } void ShellClient::setGeometry(int x, int y, int w, int h, ForceGeometry_t force) { Q_UNUSED(force) // TODO: better merge with Client's implementation if (QSize(w, h) == geom.size()) { // size didn't change, update directly doSetGeometry(QRect(x, y, w, h)); } else { // size did change, Client needs to provide a new buffer requestGeometry(QRect(x, y, w, h)); } } void ShellClient::doSetGeometry(const QRect &rect) { if (geom == rect) { return; } if (!m_unmapped) { addWorkspaceRepaint(visibleRect()); } const QRect old = geom; geom = rect; if (m_unmapped && m_geomMaximizeRestore.isEmpty() && !geom.isEmpty()) { // use first valid geometry as restore geometry // TODO: needs to interact with placing. The first valid geometry should be the placed one m_geomMaximizeRestore = geom; } if (!m_unmapped) { addWorkspaceRepaint(visibleRect()); } triggerDecorationRepaint(); if (hasStrut()) { workspace()->updateClientArea(); } emit geometryShapeChanged(this, old); } QByteArray ShellClient::windowRole() const { return QByteArray(); } bool ShellClient::belongsToSameApplication(const AbstractClient *other, bool active_hack) const { Q_UNUSED(active_hack) if (auto s = other->surface()) { return s->client() == surface()->client(); } return false; } void ShellClient::blockActivityUpdates(bool b) { Q_UNUSED(b) } QString ShellClient::caption(bool full, bool stripped) const { Q_UNUSED(full) Q_UNUSED(stripped) return m_caption; } void ShellClient::closeWindow() { if (m_xdgShellSurface && isCloseable()) { m_xdgShellSurface->close(); return; } if (m_qtExtendedSurface && isCloseable()) { m_qtExtendedSurface->close(); } } AbstractClient *ShellClient::findModal(bool allow_itself) { Q_UNUSED(allow_itself) return nullptr; } bool ShellClient::isCloseable() const { if (m_windowType == NET::Desktop || m_windowType == NET::Dock) { return false; } if (m_xdgShellSurface) { return true; } return m_qtExtendedSurface ? true : false; } bool ShellClient::isFullScreenable() const { return false; } bool ShellClient::isFullScreen() const { return m_fullScreen; } bool ShellClient::isMaximizable() const { return true; } bool ShellClient::isMinimizable() const { return (!m_plasmaShellSurface || m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal); } QRect ShellClient::iconGeometry() const { if (!windowManagementInterface()) { // window management interface is only available if the surface is mapped return QRect(); } int minDistance = INT_MAX; ShellClient *candidatePanel = nullptr; QRect candidateGeom; for (auto i = windowManagementInterface()->minimizedGeometries().constBegin(), end = windowManagementInterface()->minimizedGeometries().constEnd(); i != end; ++i) { ShellClient *client = waylandServer()->findClient(i.key()); if (!client) { continue; } const int distance = QPoint(client->pos() - pos()).manhattanLength(); if (distance < minDistance) { minDistance = distance; candidatePanel = client; candidateGeom = i.value(); } } if (!candidatePanel) { return QRect(); } return candidateGeom.translated(candidatePanel->pos()); } bool ShellClient::isMovable() const { if (m_plasmaShellSurface) { return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal; } if (m_xdgShellPopup) { return false; } return true; } bool ShellClient::isMovableAcrossScreens() const { if (m_plasmaShellSurface) { return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal; } if (m_xdgShellPopup) { return false; } return true; } bool ShellClient::isResizable() const { if (m_plasmaShellSurface) { return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal; } if (m_xdgShellPopup) { return false; } return true; } bool ShellClient::isShown(bool shaded_is_shown) const { Q_UNUSED(shaded_is_shown) return !m_closing && !m_unmapped; } void ShellClient::hideClient(bool hide) { Q_UNUSED(hide) } static bool changeMaximizeRecursion = false; void ShellClient::changeMaximize(bool horizontal, bool vertical, bool adjust) { if (changeMaximizeRecursion) { return; } MaximizeMode oldMode = m_maximizeMode; StackingUpdatesBlocker blocker(workspace()); RequestGeometryBlocker geometryBlocker(this); // 'adjust == true' means to update the size only, e.g. after changing workspace size if (!adjust) { if (vertical) m_maximizeMode = MaximizeMode(m_maximizeMode ^ MaximizeVertical); if (horizontal) m_maximizeMode = MaximizeMode(m_maximizeMode ^ MaximizeHorizontal); } // TODO: add more checks as in Client // call into decoration update borders if (isDecorated() && decoration()->client() && !(options->borderlessMaximizedWindows() && m_maximizeMode == KWin::MaximizeFull)) { changeMaximizeRecursion = true; const auto c = decoration()->client().data(); if ((m_maximizeMode & MaximizeVertical) != (oldMode & MaximizeVertical)) { emit c->maximizedVerticallyChanged(m_maximizeMode & MaximizeVertical); } if ((m_maximizeMode & MaximizeHorizontal) != (oldMode & MaximizeHorizontal)) { emit c->maximizedHorizontallyChanged(m_maximizeMode & MaximizeHorizontal); } if ((m_maximizeMode == MaximizeFull) != (oldMode == MaximizeFull)) { emit c->maximizedChanged(m_maximizeMode & MaximizeFull); } changeMaximizeRecursion = false; } // TODO: check rules if (m_maximizeMode == MaximizeFull) { m_geomMaximizeRestore = geometry(); requestGeometry(workspace()->clientArea(MaximizeArea, this)); workspace()->raiseClient(this); } else { if (m_geomMaximizeRestore.isValid()) { requestGeometry(m_geomMaximizeRestore); } else { requestGeometry(workspace()->clientArea(PlacementArea, this)); } } } MaximizeMode ShellClient::maximizeMode() const { return m_maximizeMode; } bool ShellClient::noBorder() const { if (isInternal()) { return true; } if (m_serverDecoration) { if (m_serverDecoration->mode() == ServerSideDecorationManagerInterface::Mode::Server) { return m_userNoBorder; } } return true; } const WindowRules *ShellClient::rules() const { static WindowRules s_rules; return &s_rules; } void ShellClient::setFullScreen(bool set, bool user) { Q_UNUSED(set) Q_UNUSED(user) } void ShellClient::setNoBorder(bool set) { if (!userCanSetNoBorder()) { return; } set = rules()->checkNoBorder(set); if (m_userNoBorder == set) { return; } m_userNoBorder = set; updateDecoration(true, false); updateWindowRules(Rules::NoBorder); } void ShellClient::setOnAllActivities(bool set) { Q_UNUSED(set) } void ShellClient::setShortcut(const QString &cut) { Q_UNUSED(cut) } const QKeySequence &ShellClient::shortcut() const { static QKeySequence seq; return seq; } void ShellClient::takeFocus() { if (rules()->checkAcceptFocus(wantsInput())) { setActive(true); } bool breakShowingDesktop = !keepAbove() && !isOnScreenDisplay(); if (breakShowingDesktop) { // check that it doesn't belong to the desktop const auto &clients = waylandServer()->clients(); for (auto c: clients) { if (!belongsToSameApplication(c, false)) { continue; } if (c->isDesktop()) { breakShowingDesktop = false; break; } } } if (breakShowingDesktop) workspace()->setShowingDesktop(false); } void ShellClient::doSetActive() { StackingUpdatesBlocker blocker(workspace()); workspace()->focusToNull(); } void ShellClient::updateWindowRules(Rules::Types selection) { Q_UNUSED(selection) } bool ShellClient::userCanSetFullScreen() const { return false; } bool ShellClient::userCanSetNoBorder() const { if (m_serverDecoration && m_serverDecoration->mode() == ServerSideDecorationManagerInterface::Mode::Server) { return !isFullScreen() && !isShade() && !tabGroup(); } return false; } bool ShellClient::wantsInput() const { return rules()->checkAcceptFocus(acceptsFocus()); } bool ShellClient::acceptsFocus() const { if (isInternal()) { return false; } if (waylandServer()->inputMethodConnection() == surface()->client()) { return false; } if (m_plasmaShellSurface) { if (m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::OnScreenDisplay || m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::ToolTip || m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Notification) { return false; } } if (m_shellSurface) { if (m_shellSurface->isPopup()) { return false; } // if the window is not visible it doesn't get input return m_shellSurface->acceptsKeyboardFocus() && isShown(true); } if (m_xdgShellSurface) { // TODO: proper return true; } return false; } void ShellClient::createWindowId() { if (m_internalWindow) { m_windowId = m_internalWindow->winId(); } else { m_windowId = waylandServer()->createWindowId(surface()); } } void ShellClient::findInternalWindow() { if (surface()->client() != waylandServer()->internalConnection()) { return; } const QWindowList windows = kwinApp()->topLevelWindows(); for (QWindow *w: windows) { auto s = KWayland::Client::Surface::fromWindow(w); if (!s) { continue; } if (s->id() != surface()->id()) { continue; } m_internalWindow = w; connect(m_internalWindow, &QWindow::xChanged, this, &ShellClient::updateInternalWindowGeometry); connect(m_internalWindow, &QWindow::yChanged, this, &ShellClient::updateInternalWindowGeometry); connect(m_internalWindow, &QWindow::destroyed, this, [this] { m_internalWindow = nullptr; }); // Try reading the window type from the QWindow. PlasmaCore.Dialog provides a dynamic type property // let's check whether it exists, if it does it's our window type const QVariant windowType = m_internalWindow->property("type"); if (!windowType.isNull()) { m_windowType = static_cast(windowType.toInt()); } return; } } void ShellClient::updateInternalWindowGeometry() { if (!m_internalWindow) { return; } doSetGeometry(m_internalWindow->geometry()); } bool ShellClient::isInternal() const { return m_internal; } bool ShellClient::isLockScreen() const { return surface()->client() == waylandServer()->screenLockerClientConnection(); } bool ShellClient::isInputMethod() const { if (m_internal && m_internalWindow) { return m_internalWindow->property("__kwin_input_method").toBool(); } return surface()->client() == waylandServer()->inputMethodConnection(); } void ShellClient::requestGeometry(const QRect &rect) { if (m_requestGeometryBlockCounter != 0) { m_blockedRequestGeometry = rect; return; } m_positionAfterResize.setPoint(rect.topLeft()); const QSize size = rect.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom()); if (m_shellSurface) { m_shellSurface->requestSize(size); } if (m_xdgShellSurface) { m_xdgShellSurface->configure(xdgSurfaceStates(), size); } m_blockedRequestGeometry = QRect(); } void ShellClient::clientFullScreenChanged(bool fullScreen) { StackingUpdatesBlocker blocker(workspace()); workspace()->updateClientLayer(this); // active fullscreens get different layer const bool emitSignal = m_fullScreen != fullScreen; m_fullScreen = fullScreen; if (fullScreen) { m_geomFsRestore = geometry(); requestGeometry(workspace()->clientArea(FullScreenArea, this)); workspace()->raiseClient(this); } else { if (m_geomFsRestore.isValid()) { requestGeometry(m_geomFsRestore); } else { requestGeometry(workspace()->clientArea(MaximizeArea, this)); } } if (emitSignal) { emit fullScreenChanged(); } } void ShellClient::resizeWithChecks(int w, int h, ForceGeometry_t force) { Q_UNUSED(force) 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(); } if (m_shellSurface) { m_shellSurface->requestSize(QSize(w, h)); } if (m_xdgShellSurface) { m_xdgShellSurface->configure(xdgSurfaceStates(), QSize(w, h)); } } void ShellClient::unmap() { m_unmapped = true; destroyWindowManagementInterface(); if (Workspace::self()) { addWorkspaceRepaint(visibleRect()); workspace()->clientHidden(this); } emit windowHidden(this); } void ShellClient::installPlasmaShellSurface(PlasmaShellSurfaceInterface *surface) { m_plasmaShellSurface = surface; auto updatePosition = [this, surface] { doSetGeometry(QRect(surface->position(), m_clientSize + QSize(borderLeft() + borderRight(), borderTop() + borderBottom()))); }; auto updateRole = [this, surface] { NET::WindowType type = NET::Unknown; switch (surface->role()) { case PlasmaShellSurfaceInterface::Role::Desktop: type = NET::Desktop; break; case PlasmaShellSurfaceInterface::Role::Panel: type = NET::Dock; break; case PlasmaShellSurfaceInterface::Role::OnScreenDisplay: type = NET::OnScreenDisplay; break; case PlasmaShellSurfaceInterface::Role::Notification: type = NET::Notification; break; case PlasmaShellSurfaceInterface::Role::ToolTip: type = NET::Tooltip; break; case PlasmaShellSurfaceInterface::Role::Normal: default: type = NET::Normal; break; } if (type != m_windowType) { m_windowType = type; if (m_windowType == NET::Desktop || type == NET::Dock || type == NET::OnScreenDisplay || type == NET::Notification || type == NET::Tooltip) { setOnAllDesktops(true); } workspace()->updateClientArea(); } }; connect(surface, &PlasmaShellSurfaceInterface::positionChanged, this, updatePosition); connect(surface, &PlasmaShellSurfaceInterface::roleChanged, this, updateRole); connect(surface, &PlasmaShellSurfaceInterface::panelBehaviorChanged, this, [] { workspace()->updateClientArea(); } ); updatePosition(); updateRole(); setSkipTaskbar(surface->skipTaskbar()); connect(surface, &PlasmaShellSurfaceInterface::skipTaskbarChanged, this, [this] { setSkipTaskbar(m_plasmaShellSurface->skipTaskbar()); }); } bool ShellClient::isInitialPositionSet() const { if (m_plasmaShellSurface) { return m_plasmaShellSurface->isPositionSet(); } return false; } void ShellClient::installQtExtendedSurface(QtExtendedSurfaceInterface *surface) { m_qtExtendedSurface = surface; connect(m_qtExtendedSurface.data(), &QtExtendedSurfaceInterface::raiseRequested, this, [this]() { workspace()->raiseClientRequest(this); }); connect(m_qtExtendedSurface.data(), &QtExtendedSurfaceInterface::lowerRequested, this, [this]() { workspace()->lowerClientRequest(this); }); } bool ShellClient::hasStrut() const { if (!isShown(true)) { return false; } if (!m_plasmaShellSurface) { return false; } if (m_plasmaShellSurface->role() != PlasmaShellSurfaceInterface::Role::Panel) { return false; } return m_plasmaShellSurface->panelBehavior() != PlasmaShellSurfaceInterface::PanelBehavior::WindowsGoBelow; } void ShellClient::updateIcon() { QString desktopFile; if (m_shellSurface) { desktopFile = QString::fromUtf8(m_shellSurface->windowClass()); } if (desktopFile.isEmpty()) { setIcon(QIcon()); } if (!desktopFile.endsWith(QLatin1String(".desktop"))) { desktopFile.append(QLatin1String(".desktop")); } KDesktopFile df(desktopFile); setIcon(QIcon::fromTheme(df.readIcon())); } bool ShellClient::isTransient() const { return m_transient; } void ShellClient::setTransient() { SurfaceInterface *s = nullptr; if (m_shellSurface) { s = m_shellSurface->transientFor().data(); } if (m_xdgShellSurface) { if (auto transient = m_xdgShellSurface->transientFor().data()) { s = transient->surface(); } } if (m_xdgShellPopup) { s = m_xdgShellPopup->transientFor().data(); } auto t = waylandServer()->findClient(s); if (t != transientFor()) { // remove from main client if (transientFor()) transientFor()->removeTransient(this); setTransientFor(t); if (t) { t->addTransient(this); } } m_transient = (s != nullptr); } bool ShellClient::hasTransientPlacementHint() const { return isTransient() && transientFor() != nullptr; } QPoint ShellClient::transientPlacementHint() const { if (m_shellSurface) { return m_shellSurface->transientOffset(); } if (m_xdgShellPopup) { return m_xdgShellPopup->transientOffset(); } return QPoint(); } bool ShellClient::isWaitingForMoveResizeSync() const { return m_positionAfterResize.isValid(); } void ShellClient::doResizeSync() { requestGeometry(moveResizeGeometry()); } QMatrix4x4 ShellClient::inputTransformation() const { QMatrix4x4 m = Toplevel::inputTransformation(); m.translate(-borderLeft(), -borderTop()); return m; } void ShellClient::installServerSideDecoration(KWayland::Server::ServerSideDecorationInterface *deco) { if (m_serverDecoration == deco) { return; } m_serverDecoration = deco; connect(m_serverDecoration, &ServerSideDecorationInterface::destroyed, this, [this] { m_serverDecoration = nullptr; if (m_closing || !Workspace::self()) { return; } if (!m_unmapped) { // maybe delay to next event cycle in case the ShellClient is getting destroyed, too updateDecoration(true); } } ); if (!m_unmapped) { updateDecoration(true); } connect(m_serverDecoration, &ServerSideDecorationInterface::modeRequested, this, [this] (ServerSideDecorationManagerInterface::Mode mode) { const bool changed = mode != m_serverDecoration->mode(); // always acknowledge the requested mode m_serverDecoration->setMode(mode); if (changed && !m_unmapped) { updateDecoration(false); } } ); } bool ShellClient::shouldExposeToWindowManagement() { if (isInternal()) { return false; } if (isLockScreen()) { return false; } if (m_shellSurface) { if (m_shellSurface->isTransient() && !m_shellSurface->acceptsKeyboardFocus()) { return false; } } return true; } KWayland::Server::XdgShellSurfaceInterface::States ShellClient::xdgSurfaceStates() const { XdgShellSurfaceInterface::States states; if (isActive()) { states |= XdgShellSurfaceInterface::State::Activated; } if (isFullScreen()) { states |= XdgShellSurfaceInterface::State::Fullscreen; } if (maximizeMode() == MaximizeMode::MaximizeFull) { states |= XdgShellSurfaceInterface::State::Maximized; } if (isResize()) { states |= XdgShellSurfaceInterface::State::Resizing; } return states; } } diff --git a/shell_client.h b/shell_client.h index 26b70c34f..4f32b0347 100644 --- a/shell_client.h +++ b/shell_client.h @@ -1,216 +1,215 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 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 KWIN_SHELL_CLIENT_H #define KWIN_SHELL_CLIENT_H #include "abstract_client.h" #include namespace KWayland { namespace Server { class ShellSurfaceInterface; class ServerSideDecorationInterface; class PlasmaShellSurfaceInterface; class QtExtendedSurfaceInterface; } } namespace KWin { class KWIN_EXPORT ShellClient : public AbstractClient { Q_OBJECT public: ShellClient(KWayland::Server::ShellSurfaceInterface *surface); ShellClient(KWayland::Server::XdgShellSurfaceInterface *surface); ShellClient(KWayland::Server::XdgShellPopupInterface *surface); virtual ~ShellClient(); QStringList activities() const override; QPoint clientContentPos() const override; QSize clientSize() const override; QRect transparentRect() const override; - bool shouldUnredirect() const override; NET::WindowType windowType(bool direct = false, int supported_types = 0) const override; void debug(QDebug &stream) const override; double opacity() const override; void setOpacity(double opacity) override; QByteArray windowRole() const override; KWayland::Server::ShellSurfaceInterface *shellSurface() const { return m_shellSurface; } void blockActivityUpdates(bool b = true) override; QString caption(bool full = true, bool stripped = false) const override; void closeWindow() override; AbstractClient *findModal(bool allow_itself = false) override; bool isCloseable() const override; bool isFullScreenable() const override; bool isFullScreen() const override; bool isMaximizable() const override; bool isMinimizable() const override; QRect iconGeometry() const override; bool isMovable() const override; bool isMovableAcrossScreens() const override; bool isResizable() const override; bool isShown(bool shaded_is_shown) const override; bool isHiddenInternal() const override { return m_unmapped; } void hideClient(bool hide) override; MaximizeMode maximizeMode() const override; QRect geometryRestore() const override { return m_geomMaximizeRestore; } bool noBorder() const override; const WindowRules *rules() const override; void setFullScreen(bool set, bool user = true) override; void setNoBorder(bool set) override; void updateDecoration(bool check_workspace_pos, bool force = false) override; void setOnAllActivities(bool set) override; void setShortcut(const QString &cut) override; const QKeySequence &shortcut() const override; void takeFocus() override; void updateWindowRules(Rules::Types selection) override; bool userCanSetFullScreen() const override; bool userCanSetNoBorder() const override; bool wantsInput() const override; using AbstractClient::resizeWithChecks; void resizeWithChecks(int w, int h, ForceGeometry_t force = NormalGeometrySet) override; using AbstractClient::setGeometry; void setGeometry(int x, int y, int w, int h, ForceGeometry_t force = NormalGeometrySet) override; bool hasStrut() const override; void setInternalFramebufferObject(const QSharedPointer &fbo) override; quint32 windowId() const override { return m_windowId; } bool isInternal() const; bool isLockScreen() const override; bool isInputMethod() const override; QWindow *internalWindow() const { return m_internalWindow; } void installPlasmaShellSurface(KWayland::Server::PlasmaShellSurfaceInterface *surface); void installQtExtendedSurface(KWayland::Server::QtExtendedSurfaceInterface *surface); void installServerSideDecoration(KWayland::Server::ServerSideDecorationInterface *decoration); bool isInitialPositionSet() const; bool isTransient() const override; bool hasTransientPlacementHint() const override; QPoint transientPlacementHint() const override; QMatrix4x4 inputTransformation() const override; protected: void addDamage(const QRegion &damage) override; bool belongsToSameApplication(const AbstractClient *other, bool active_hack) const override; void doSetActive() override; Layer layerForDock() const override; void changeMaximize(bool horizontal, bool vertical, bool adjust) override; void setGeometryRestore(const QRect &geo) override { m_geomMaximizeRestore = geo; } void doResizeSync() override; bool isWaitingForMoveResizeSync() const override; bool acceptsFocus() const override; private Q_SLOTS: void clientFullScreenChanged(bool fullScreen); private: void init(); template void initSurface(T *shellSurface); void requestGeometry(const QRect &rect); void doSetGeometry(const QRect &rect); void createDecoration(const QRect &oldgeom); void destroyClient(); void unmap(); void createWindowId(); void findInternalWindow(); void updateInternalWindowGeometry(); void updateIcon(); void markAsMapped(); void setTransient(); bool shouldExposeToWindowManagement(); KWayland::Server::XdgShellSurfaceInterface::States xdgSurfaceStates() const; static void deleteClient(ShellClient *c); KWayland::Server::ShellSurfaceInterface *m_shellSurface; KWayland::Server::XdgShellSurfaceInterface *m_xdgShellSurface; KWayland::Server::XdgShellPopupInterface *m_xdgShellPopup; QSize m_clientSize; ClearablePoint m_positionAfterResize; // co-ordinates saved from a requestGeometry call, real geometry will be updated after the next damage event when the client has resized QRect m_geomFsRestore; //size and position of the window before it was set to fullscreen bool m_closing = false; quint32 m_windowId = 0; QWindow *m_internalWindow = nullptr; bool m_unmapped = true; MaximizeMode m_maximizeMode = MaximizeRestore; QRect m_geomMaximizeRestore; // size and position of the window before it was set to maximize NET::WindowType m_windowType = NET::Normal; QPointer m_plasmaShellSurface; QPointer m_qtExtendedSurface; KWayland::Server::ServerSideDecorationInterface *m_serverDecoration = nullptr; bool m_userNoBorder = false; bool m_fullScreen = false; bool m_transient = false; bool m_internal; qreal m_opacity = 1.0; class RequestGeometryBlocker { public: RequestGeometryBlocker(ShellClient *client) : m_client(client) { m_client->m_requestGeometryBlockCounter++; } ~RequestGeometryBlocker() { m_client->m_requestGeometryBlockCounter--; if (m_client->m_requestGeometryBlockCounter == 0) { if (m_client->m_blockedRequestGeometry.isValid()) { m_client->requestGeometry(m_client->m_blockedRequestGeometry); } } } private: ShellClient *m_client; }; friend class RequestGeometryBlocker; int m_requestGeometryBlockCounter = 0; QRect m_blockedRequestGeometry; QString m_caption; }; } Q_DECLARE_METATYPE(KWin::ShellClient*) #endif diff --git a/toplevel.cpp b/toplevel.cpp index 1407aecb7..579bdd7bd 100644 --- a/toplevel.cpp +++ b/toplevel.cpp @@ -1,542 +1,540 @@ /******************************************************************** 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 . *********************************************************************/ #include "toplevel.h" #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #endif #include "atoms.h" #include "client.h" #include "client_machine.h" #include "effects.h" #include "screens.h" #include "shadow.h" #include "xcbutils.h" #include #include namespace KWin { Toplevel::Toplevel() : m_visual(XCB_NONE) , bit_depth(24) , info(NULL) , ready_for_painting(true) , m_isDamaged(false) , m_client() , damage_handle(None) , is_shape(false) , effect_window(NULL) , m_clientMachine(new ClientMachine(this)) , wmClientLeaderWin(0) - , unredirect(false) - , unredirectSuspend(false) , m_damageReplyPending(false) , m_screen(0) , m_skipCloseAnimation(false) { connect(this, SIGNAL(damaged(KWin::Toplevel*,QRect)), SIGNAL(needsRepaint())); connect(screens(), SIGNAL(changed()), SLOT(checkScreen())); connect(screens(), SIGNAL(countChanged(int,int)), SLOT(checkScreen())); setupCheckScreenConnection(); } Toplevel::~Toplevel() { assert(damage_handle == None); delete info; } QDebug& operator<<(QDebug& stream, const Toplevel* cl) { if (cl == NULL) return stream << "\'NULL\'"; cl->debug(stream); return stream; } QDebug& operator<<(QDebug& stream, const ToplevelList& list) { stream << "LIST:("; bool first = true; for (ToplevelList::ConstIterator it = list.begin(); it != list.end(); ++it) { if (!first) stream << ":"; first = false; stream << *it; } stream << ")"; return stream; } QRect Toplevel::decorationRect() const { return rect(); } void Toplevel::detectShape(Window id) { const bool wasShape = is_shape; is_shape = Xcb::Extensions::self()->hasShape(id); if (wasShape != is_shape) { emit shapedChanged(); } } // used only by Deleted::copy() void Toplevel::copyToDeleted(Toplevel* c) { geom = c->geom; m_visual = c->m_visual; bit_depth = c->bit_depth; info = c->info; m_client.reset(c->m_client, false); ready_for_painting = c->ready_for_painting; damage_handle = None; damage_region = c->damage_region; repaints_region = c->repaints_region; is_shape = c->is_shape; effect_window = c->effect_window; if (effect_window != NULL) effect_window->setWindow(this); resource_name = c->resourceName(); resource_class = c->resourceClass(); m_clientMachine = c->m_clientMachine; m_clientMachine->setParent(this); wmClientLeaderWin = c->wmClientLeader(); opaque_region = c->opaqueRegion(); m_screen = c->m_screen; m_skipCloseAnimation = c->m_skipCloseAnimation; m_internalFBO = c->m_internalFBO; } // before being deleted, remove references to everything that's now // owner by Deleted void Toplevel::disownDataPassedToDeleted() { info = NULL; } QRect Toplevel::visibleRect() const { QRect r = decorationRect(); if (hasShadow() && !shadow()->shadowRegion().isEmpty()) { r |= shadow()->shadowRegion().boundingRect(); } return r.translated(geometry().topLeft()); } Xcb::Property Toplevel::fetchWmClientLeader() const { return Xcb::Property(false, window(), atoms->wm_client_leader, XCB_ATOM_WINDOW, 0, 10000); } void Toplevel::readWmClientLeader(Xcb::Property &prop) { wmClientLeaderWin = prop.value(window()); } void Toplevel::getWmClientLeader() { auto prop = fetchWmClientLeader(); readWmClientLeader(prop); } /*! Returns sessionId for this client, taken either from its window or from the leader window. */ QByteArray Toplevel::sessionId() const { QByteArray result = Xcb::StringProperty(window(), atoms->sm_client_id); if (result.isEmpty() && wmClientLeaderWin && wmClientLeaderWin != window()) result = Xcb::StringProperty(wmClientLeaderWin, atoms->sm_client_id); return result; } /*! Returns command property for this client, taken either from its window or from the leader window. */ QByteArray Toplevel::wmCommand() { QByteArray result = Xcb::StringProperty(window(), XCB_ATOM_WM_COMMAND); if (result.isEmpty() && wmClientLeaderWin && wmClientLeaderWin != window()) result = Xcb::StringProperty(wmClientLeaderWin, XCB_ATOM_WM_COMMAND); result.replace(0, ' '); return result; } void Toplevel::getWmClientMachine() { m_clientMachine->resolve(window(), wmClientLeader()); } /*! Returns client machine for this client, taken either from its window or from the leader window. */ QByteArray Toplevel::wmClientMachine(bool use_localhost) const { if (!m_clientMachine) { // this should never happen return QByteArray(); } if (use_localhost && m_clientMachine->isLocal()) { // special name for the local machine (localhost) return ClientMachine::localhost(); } return m_clientMachine->hostName(); } /*! Returns client leader window for this client. Returns the client window itself if no leader window is defined. */ Window Toplevel::wmClientLeader() const { if (wmClientLeaderWin) return wmClientLeaderWin; return window(); } void Toplevel::getResourceClass() { setResourceClass(QByteArray(info->windowClassName()).toLower(), QByteArray(info->windowClassClass()).toLower()); } void Toplevel::setResourceClass(const QByteArray &name, const QByteArray &className) { resource_name = name; resource_class = className; emit windowClassChanged(); } double Toplevel::opacity() const { if (info->opacity() == 0xffffffff) return 1.0; return info->opacity() * 1.0 / 0xffffffff; } void Toplevel::setOpacity(double new_opacity) { double old_opacity = opacity(); new_opacity = qBound(0.0, new_opacity, 1.0); if (old_opacity == new_opacity) return; info->setOpacity(static_cast< unsigned long >(new_opacity * 0xffffffff)); if (compositing()) { addRepaintFull(); emit opacityChanged(this, old_opacity); } } void Toplevel::setReadyForPainting() { if (!ready_for_painting) { ready_for_painting = true; if (compositing()) { addRepaintFull(); emit windowShown(this); if (Client *cl = dynamic_cast(this)) { if (cl->tabGroup() && cl->tabGroup()->current() == cl) cl->tabGroup()->setCurrent(cl, true); } } } } void Toplevel::deleteEffectWindow() { delete effect_window; effect_window = NULL; } void Toplevel::checkScreen() { if (screens()->count() == 1) { if (m_screen != 0) { m_screen = 0; emit screenChanged(); } return; } const int s = screens()->number(geometry().center()); if (s != m_screen) { m_screen = s; emit screenChanged(); } } void Toplevel::setupCheckScreenConnection() { connect(this, SIGNAL(geometryShapeChanged(KWin::Toplevel*,QRect)), SLOT(checkScreen())); connect(this, SIGNAL(geometryChanged()), SLOT(checkScreen())); checkScreen(); } void Toplevel::removeCheckScreenConnection() { disconnect(this, SIGNAL(geometryShapeChanged(KWin::Toplevel*,QRect)), this, SLOT(checkScreen())); disconnect(this, SIGNAL(geometryChanged()), this, SLOT(checkScreen())); } int Toplevel::screen() const { return m_screen; } bool Toplevel::isOnScreen(int screen) const { return screens()->geometry(screen).intersects(geometry()); } bool Toplevel::isOnActiveScreen() const { return isOnScreen(screens()->current()); } void Toplevel::getShadow() { QRect dirtyRect; // old & new shadow region const QRect oldVisibleRect = visibleRect(); if (hasShadow()) { dirtyRect = shadow()->shadowRegion().boundingRect(); effectWindow()->sceneWindow()->shadow()->updateShadow(); } else { Shadow::createShadow(this); } if (hasShadow()) dirtyRect |= shadow()->shadowRegion().boundingRect(); if (oldVisibleRect != visibleRect()) emit paddingChanged(this, oldVisibleRect); if (dirtyRect.isValid()) { dirtyRect.translate(pos()); addLayerRepaint(dirtyRect); } } bool Toplevel::hasShadow() const { if (effectWindow() && effectWindow()->sceneWindow()) { return effectWindow()->sceneWindow()->shadow() != NULL; } return false; } Shadow *Toplevel::shadow() { if (effectWindow() && effectWindow()->sceneWindow()) { return effectWindow()->sceneWindow()->shadow(); } else { return NULL; } } const Shadow *Toplevel::shadow() const { if (effectWindow() && effectWindow()->sceneWindow()) { return effectWindow()->sceneWindow()->shadow(); } else { return NULL; } } bool Toplevel::wantsShadowToBeRendered() const { return true; } void Toplevel::getWmOpaqueRegion() { const auto rects = info->opaqueRegion(); QRegion new_opaque_region; for (const auto &r : rects) { new_opaque_region += QRect(r.pos.x, r.pos.y, r.size.width, r.size.height); } opaque_region = new_opaque_region; } bool Toplevel::isClient() const { return false; } bool Toplevel::isDeleted() const { return false; } bool Toplevel::isOnCurrentActivity() const { #ifdef KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return true; } return isOnActivity(Activities::self()->current()); #else return true; #endif } void Toplevel::elevate(bool elevate) { if (!effectWindow()) { return; } effectWindow()->elevate(elevate); addWorkspaceRepaint(visibleRect()); } pid_t Toplevel::pid() const { return info->pid(); } xcb_window_t Toplevel::frameId() const { return m_client; } Xcb::Property Toplevel::fetchSkipCloseAnimation() const { return Xcb::Property(false, window(), atoms->kde_skip_close_animation, XCB_ATOM_CARDINAL, 0, 1); } void Toplevel::readSkipCloseAnimation(Xcb::Property &property) { setSkipCloseAnimation(property.toBool()); } void Toplevel::getSkipCloseAnimation() { Xcb::Property property = fetchSkipCloseAnimation(); readSkipCloseAnimation(property); } bool Toplevel::skipsCloseAnimation() const { return m_skipCloseAnimation; } void Toplevel::setSkipCloseAnimation(bool set) { if (set == m_skipCloseAnimation) { return; } m_skipCloseAnimation = set; emit skipCloseAnimationChanged(); } void Toplevel::setSurface(KWayland::Server::SurfaceInterface *surface) { if (m_surface == surface) { return; } using namespace KWayland::Server; if (m_surface) { disconnect(m_surface, &SurfaceInterface::damaged, this, &Toplevel::addDamage); } m_surface = surface; connect(m_surface, &SurfaceInterface::damaged, this, &Toplevel::addDamage); connect(m_surface, &SurfaceInterface::subSurfaceTreeChanged, this, [this] { // TODO improve to only update actual visual area if (ready_for_painting) { addDamageFull(); m_isDamaged = true; } } ); connect(m_surface, &SurfaceInterface::destroyed, this, [this] { m_surface = nullptr; } ); emit surfaceChanged(); } void Toplevel::addDamage(const QRegion &damage) { m_isDamaged = true; damage_region += damage; for (const QRect &r : damage.rects()) { emit damaged(this, r); } } QByteArray Toplevel::windowRole() const { return QByteArray(info->windowRole()); } void Toplevel::setDepth(int depth) { if (bit_depth == depth) { return; } const bool oldAlpha = hasAlpha(); bit_depth = depth; if (oldAlpha != hasAlpha()) { emit hasAlphaChanged(); } } QRegion Toplevel::inputShape() const { if (m_surface) { return m_surface->input(); } else { // TODO: maybe also for X11? return QRegion(); } } void Toplevel::setInternalFramebufferObject(const QSharedPointer &fbo) { if (m_internalFBO != fbo) { discardWindowPixmap(); m_internalFBO = fbo; } setDepth(32); } QMatrix4x4 Toplevel::inputTransformation() const { QMatrix4x4 m; m.translate(-x(), -y()); return m; } quint32 Toplevel::windowId() const { return window(); } } // namespace diff --git a/toplevel.h b/toplevel.h index c80ffd3cf..0db6145fb 100644 --- a/toplevel.h +++ b/toplevel.h @@ -1,814 +1,803 @@ /******************************************************************** 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 // kwin #include "input.h" #include "utils.h" #include "virtualdesktops.h" #include "xcbutils.h" // KDE #include // Qt #include #include // xcb #include #include // XLib #include #include // system #include // c++ #include class QOpenGLFramebufferObject; namespace KWayland { namespace Server { class SurfaceInterface; } } namespace KWin { class ClientMachine; class EffectWindowImpl; class Shadow; /** * Enum to describe the reason why a Toplevel has to be released. */ enum class ReleaseReason { Release, ///< Normal Release after e.g. an Unmap notify event (window still valid) Destroyed, ///< Release after an Destroy notify event (window no longer valid) KWinShutsDown ///< Release on KWin Shutdown (window still valid) }; class KWIN_EXPORT Toplevel : public QObject { Q_OBJECT Q_PROPERTY(bool alpha READ hasAlpha NOTIFY hasAlphaChanged) Q_PROPERTY(qulonglong frameId READ frameId) Q_PROPERTY(QRect geometry READ geometry NOTIFY geometryChanged) Q_PROPERTY(QRect visibleRect READ visibleRect) 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 NOTIFY screenChanged) Q_PROPERTY(QSize size READ size) Q_PROPERTY(int width READ width) Q_PROPERTY(qulonglong windowId READ windowId CONSTANT) Q_PROPERTY(int x READ x) Q_PROPERTY(int y READ y) Q_PROPERTY(int desktop READ desktop) /** * Whether the window is on all desktops. That is desktop is -1. **/ Q_PROPERTY(bool onAllDesktops READ isOnAllDesktops) Q_PROPERTY(QRect rect READ rect) Q_PROPERTY(QPoint clientPos READ clientPos) Q_PROPERTY(QSize clientSize READ clientSize) Q_PROPERTY(QByteArray resourceName READ resourceName NOTIFY windowClassChanged) Q_PROPERTY(QByteArray resourceClass READ resourceClass NOTIFY windowClassChanged) Q_PROPERTY(QByteArray windowRole READ windowRole NOTIFY windowRoleChanged) /** * 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 an On Screen Display. */ Q_PROPERTY(bool onScreenDisplay READ isOnScreenDisplay) /** * 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 NOTIFY activitiesChanged) /** * 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) /** * Whether the window does not want to be animated on window close. * There are legit reasons for this like a screenshot application which does not want it's * window being captured. **/ Q_PROPERTY(bool skipsCloseAnimation READ skipsCloseAnimation WRITE setSkipCloseAnimation NOTIFY skipCloseAnimationChanged) /** * The Id of the Wayland Surface associated with this Toplevel. * On X11 only setups the value is @c 0. **/ Q_PROPERTY(quint32 surfaceId READ surfaceId NOTIFY surfaceIdChanged) /** * Interface to the Wayland Surface. * Relevant only in Wayland, in X11 it will be nullptr */ Q_PROPERTY(KWayland::Server::SurfaceInterface *surface READ surface) public: explicit Toplevel(); virtual xcb_window_t frameId() const; xcb_window_t window() const; /** * @return a unique identifier for the Toplevel. On X11 same as @link {window} **/ virtual quint32 windowId() 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 bool isOnActiveScreen() const; int screen() const; // the screen where the center is virtual QPoint clientPos() const = 0; // inside of geometry() /** * Describes how the client's content maps to the window geometry including the frame. * The default implementation is a 1:1 mapping meaning the frame is part of the content. **/ virtual QPoint clientContentPos() const; 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 bool isClient() const; virtual bool isDeleted() const; // prefer isXXX() instead // 0 for supported types means default for managed/unmanaged types virtual NET::WindowType windowType(bool direct = false, int supported_types = 0) const = 0; 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 isOnScreenDisplay() const; bool isComboBox() const; bool isDNDIcon() const; virtual bool isLockScreen() const; virtual bool isInputMethod() 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; virtual QByteArray windowRole() const; QByteArray sessionId() const; QByteArray resourceName() const; QByteArray resourceClass() const; QByteArray wmCommand(); QByteArray wmClientMachine(bool use_localhost) const; const ClientMachine *clientMachine() const; Window wmClientLeader() const; pid_t pid() const; static bool resourceMatch(const Toplevel* c1, const Toplevel* c2); bool readyForPainting() const; // true if the window has been already painted its contents xcb_visualid_t visual() const; bool shape() const; QRegion inputShape() const; virtual void setOpacity(double opacity); virtual double opacity() const; int depth() const; bool hasAlpha() const; virtual bool setupCompositing(); virtual void finishCompositing(ReleaseReason releaseReason = ReleaseReason::Release); - 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(); QRegion damage() const; void resetDamage(); EffectWindowImpl* effectWindow(); const EffectWindowImpl* effectWindow() const; /** * Window will be temporarily painted as if being at the top of the stack. * Only available if Compositor is active, if not active, this method is a no-op. **/ void elevate(bool elevate); /** * @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(); /** * Whether the Toplevel currently wants the shadow to be rendered. Default * implementation always returns @c true. **/ virtual bool wantsShadowToBeRendered() const; /** * 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; virtual Layer layer() const = 0; /** * Resets the damage state and sends a request for the damage region. * A call to this function must be followed by a call to getDamageRegionReply(), * or the reply will be leaked. * * Returns true if the window was damaged, and false otherwise. */ bool resetAndFetchDamage(); /** * Gets the reply from a previous call to resetAndFetchDamage(). * Calling this function is a no-op if there is no pending reply. * Call damage() to return the fetched region. */ void getDamageRegionReply(); bool skipsCloseAnimation() const; void setSkipCloseAnimation(bool set); quint32 surfaceId() const; KWayland::Server::SurfaceInterface *surface() const; void setSurface(KWayland::Server::SurfaceInterface *surface); virtual void setInternalFramebufferObject(const QSharedPointer &fbo); const QSharedPointer &internalFramebufferObject() const; /** * @returns Transformation to map from global to window coordinates. * * Default implementation returns a translation on negative pos(). * @see pos **/ virtual QMatrix4x4 inputTransformation() const; /** * @brief Finds the Toplevel matching the condition expressed in @p func in @p list. * * The method is templated to operate on either a list of Toplevels or on a list of * a subclass type of Toplevel. * @param list The list to search in * @param func The condition function (compare std::find_if) * @return T* The found Toplevel or @c null if there is no matching Toplevel */ template static T *findInList(const QList &list, std::function func); Q_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 paddingChanged(KWin::Toplevel* toplevel, const QRect& old); void windowClosed(KWin::Toplevel* toplevel, KWin::Deleted* deleted); void windowShown(KWin::Toplevel* toplevel); void windowHidden(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(); /** * Emitted whenever the state changes in a way, that the Compositor should * schedule a repaint of the scene. **/ void needsRepaint(); void activitiesChanged(KWin::Toplevel* toplevel); /** * Emitted whenever the Toplevel's screen changes. This can happen either in consequence to * a screen being removed/added or if the Toplevel's geometry changes. * @since 4.11 **/ void screenChanged(); void skipCloseAnimationChanged(); /** * Emitted whenever the window role of the window changes. * @since 5.0 **/ void windowRoleChanged(); /** * Emitted whenever the window class name or resource name of the window changes. * @since 5.0 **/ void windowClassChanged(); /** * Emitted when a Wayland Surface gets associated with this Toplevel. * @since 5.3 **/ void surfaceIdChanged(quint32); /** * @since 5.4 **/ void hasAlphaChanged(); /** * Emitted whenever the Surface for this Toplevel changes. **/ void surfaceChanged(); protected Q_SLOTS: /** * Checks whether the screen number for this Toplevel changed and updates if needed. * Any method changing the geometry of the Toplevel should call this method. **/ void checkScreen(); void setupCheckScreenConnection(); void removeCheckScreenConnection(); void setReadyForPainting(); protected: virtual ~Toplevel(); void setWindowHandles(xcb_window_t client); void detectShape(Window id); virtual void propertyNotifyEvent(xcb_property_notify_event_t *e); virtual void damageNotifyEvent(); virtual void clientMessageEvent(xcb_client_message_event_t *e); void discardWindowPixmap(); void addDamageFull(); virtual void addDamage(const QRegion &damage); Xcb::Property fetchWmClientLeader() const; void readWmClientLeader(Xcb::Property &p); void getWmClientLeader(); void getWmClientMachine(); /** * @returns Whether there is a compositor and it is active. **/ bool compositing() const; /** * 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 setResourceClass(const QByteArray &name, const QByteArray &className = QByteArray()); Xcb::Property fetchSkipCloseAnimation() const; void readSkipCloseAnimation(Xcb::Property &prop); void getSkipCloseAnimation(); 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; void setDepth(int depth); QRect geom; xcb_visualid_t m_visual; int bit_depth; NETWinInfo* info; bool ready_for_painting; QRegion repaints_region; // updating, repaint just requires repaint of that area QRegion layer_repaints_region; protected: bool m_isDamaged; private: // when adding new data members, check also copyToDeleted() Xcb::Window m_client; xcb_damage_damage_t damage_handle; QRegion damage_region; // damage is really damaged window (XDamage) and texture needs bool is_shape; EffectWindowImpl* effect_window; QByteArray resource_name; QByteArray resource_class; ClientMachine *m_clientMachine; WId wmClientLeaderWin; - bool unredirect; - bool unredirectSuspend; // when unredirected, but pixmap is needed temporarily bool m_damageReplyPending; QRegion opaque_region; xcb_xfixes_fetch_region_cookie_t m_regionCookie; int m_screen; bool m_skipCloseAnimation; quint32 m_surfaceId = 0; KWayland::Server::SurfaceInterface *m_surface = nullptr; /** * An FBO object KWin internal windows might render to. **/ QSharedPointer m_internalFBO; // when adding new data members, check also copyToDeleted() }; inline xcb_window_t Toplevel::window() const { return m_client; } inline void Toplevel::setWindowHandles(xcb_window_t w) { assert(!m_client.isValid() && w != XCB_WINDOW_NONE); m_client.reset(w, false); } 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 bool Toplevel::readyForPainting() const { return ready_for_painting; } inline xcb_visualid_t Toplevel::visual() const { return m_visual; } 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::isOnScreenDisplay() const { return windowType() == NET::OnScreenDisplay; } inline bool Toplevel::isComboBox() const { return windowType() == NET::ComboBox; } inline bool Toplevel::isDNDIcon() const { return windowType() == NET::DNDIcon; } inline bool Toplevel::isLockScreen() const { return false; } inline bool Toplevel::isInputMethod() const { return false; } inline QRegion Toplevel::damage() const { return damage_region; } inline QRegion Toplevel::repaints() const { 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(VirtualDesktopManager::self()->current()); } 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 bool Toplevel::unredirected() const -{ - return unredirect; -} - inline const ClientMachine *Toplevel::clientMachine() const { return m_clientMachine; } inline quint32 Toplevel::surfaceId() const { return m_surfaceId; } inline KWayland::Server::SurfaceInterface *Toplevel::surface() const { return m_surface; } inline const QSharedPointer &Toplevel::internalFramebufferObject() const { return m_internalFBO; } inline QPoint Toplevel::clientContentPos() const { return QPoint(0, 0); } template inline T *Toplevel::findInList(const QList &list, std::function func) { static_assert(std::is_base_of::value, "U must be derived from T"); const auto it = std::find_if(list.begin(), list.end(), func); if (it == list.end()) { return nullptr; } return *it; } QDebug& operator<<(QDebug& stream, const Toplevel*); QDebug& operator<<(QDebug& stream, const ToplevelList&); } // namespace Q_DECLARE_METATYPE(KWin::Toplevel*) #endif diff --git a/unmanaged.h b/unmanaged.h index 31aea047e..6cee6c1cb 100644 --- a/unmanaged.h +++ b/unmanaged.h @@ -1,63 +1,62 @@ /******************************************************************** 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_UNMANAGED_H #define KWIN_UNMANAGED_H #include #include "toplevel.h" namespace KWin { class Unmanaged : public Toplevel { Q_OBJECT public: explicit Unmanaged(); bool windowEvent(xcb_generic_event_t *e); bool track(Window w); static void deleteUnmanaged(Unmanaged* c); virtual int desktop() const; virtual QStringList activities() const; virtual QPoint clientPos() const; virtual QSize clientSize() const; virtual QRect transparentRect() const; virtual Layer layer() const { return UnmanagedLayer; } NET::WindowType windowType(bool direct = false, int supported_types = 0) const; public Q_SLOTS: void release(ReleaseReason releaseReason = ReleaseReason::Release); protected: virtual void debug(QDebug& stream) const; - virtual bool shouldUnredirect() const; private: virtual ~Unmanaged(); // use release() // handlers for X11 events void configureNotifyEvent(xcb_configure_notify_event_t *e); }; } // namespace #endif diff --git a/workspace.cpp b/workspace.cpp index dd6dee803..a7f3961c6 100644 --- a/workspace.cpp +++ b/workspace.cpp @@ -1,1730 +1,1726 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ // own #include "workspace.h" // kwin libs #include #include // kwin #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #endif #include "atoms.h" #include "client.h" #include "composite.h" #include "cursor.h" #include "dbusinterface.h" #include "deleted.h" #include "effects.h" #include "focuschain.h" #include "group.h" #include "input.h" #include "logind.h" #include "killwindow.h" #include "netinfo.h" #include "outline.h" #include "placement.h" #include "rules.h" #include "screenedge.h" #include "screens.h" #include "scripting/scripting.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox.h" #endif #include "unmanaged.h" #include "useractions.h" #include "virtualdesktops.h" #include "shell_client.h" #include "wayland_server.h" #include "xcbutils.h" #include "main.h" #include "decorations/decorationbridge.h" // KDE #include #include #include #include // Qt #include namespace KWin { extern int screen_number; extern bool is_multihead; ColorMapper::ColorMapper(QObject *parent) : QObject(parent) , m_default(defaultScreen()->default_colormap) , m_installed(defaultScreen()->default_colormap) { } ColorMapper::~ColorMapper() { } void ColorMapper::update() { xcb_colormap_t cmap = m_default; if (Client *c = dynamic_cast(Workspace::self()->activeClient())) { if (c->colormap() != XCB_COLORMAP_NONE) { cmap = c->colormap(); } } if (cmap != m_installed) { xcb_install_colormap(connection(), cmap); m_installed = cmap; } } Workspace* Workspace::_self = 0; Workspace::Workspace(const QString &sessionKey) : QObject(0) , m_compositor(NULL) // Unsorted , active_popup(NULL) , active_popup_client(NULL) , m_initialDesktop(1) , active_client(0) , last_active_client(0) , most_recently_raised(0) , movingClient(0) , delayfocus_client(0) , force_restacking(false) , x_stacking_dirty(true) , showing_desktop(false) , was_user_interaction(false) , session_saving(false) , block_focus(0) , m_userActionsMenu(new UserActionsMenu(this)) , client_keys_dialog(NULL) , client_keys_client(NULL) , global_shortcuts_disabled_for_client(false) , workspaceInit(true) , startup(0) , set_active_client_recursion(0) , block_stacking_updates(0) { // If KWin was already running it saved its configuration after loosing the selection -> Reread QFuture reparseConfigFuture = QtConcurrent::run(options, &Options::reparseConfiguration); _self = this; // first initialize the extensions Xcb::Extensions::self(); #ifdef KWIN_BUILD_ACTIVITIES Activities *activities = nullptr; if (kwinApp()->usesKActivities()) { activities = Activities::create(this); } if (activities) { connect(activities, SIGNAL(currentChanged(QString)), SLOT(updateCurrentActivity(QString))); } #endif // PluginMgr needs access to the config file, so we need to wait for it for finishing reparseConfigFuture.waitForFinished(); options->loadConfig(); options->loadCompositingConfig(false); ColorMapper *colormaps = new ColorMapper(this); connect(this, &Workspace::clientActivated, colormaps, &ColorMapper::update); delayFocusTimer = 0; if (!sessionKey.isEmpty()) loadSessionInfo(sessionKey); connect(qApp, &QGuiApplication::commitDataRequest, this, &Workspace::commitData); connect(qApp, &QGuiApplication::saveStateRequest, this, &Workspace::saveState); RuleBook::create(this)->load(); // Call this before XSelectInput() on the root window startup = new KStartupInfo( KStartupInfo::DisableKWinModule | KStartupInfo::AnnounceSilenceChanges, this); // Select windowmanager privileges selectWmInputEventMask(); ScreenEdges::create(this); // VirtualDesktopManager needs to be created prior to init shortcuts // and prior to TabBox, due to TabBox connecting to signals // actual initialization happens in init() VirtualDesktopManager::create(this); #ifdef KWIN_BUILD_TABBOX // need to create the tabbox before compositing scene is setup TabBox::TabBox::create(this); #endif // init XRenderUtils if (kwinApp()->operationMode() == Application::OperationModeX11) { XRenderUtils::init(connection(), rootWindow()); } if (Compositor::self()) { m_compositor = Compositor::self(); } else { m_compositor = Compositor::create(this); } connect(this, &Workspace::currentDesktopChanged, m_compositor, &Compositor::addRepaintFull); connect(m_compositor, &QObject::destroyed, this, [this] { m_compositor = nullptr; }); auto decorationBridge = Decoration::DecorationBridge::create(this); decorationBridge->init(); connect(this, &Workspace::configChanged, decorationBridge, &Decoration::DecorationBridge::reconfigure); new DBusInterface(this); // Compatibility int32_t data = 1; xcb_change_property(connection(), XCB_PROP_MODE_APPEND, rootWindow(), atoms->kwin_running, atoms->kwin_running, 32, 1, &data); Outline::create(this); initShortcuts(); init(); } void Workspace::init() { updateXTime(); // Needed for proper initialization of user_time in Client ctor KSharedConfigPtr config = kwinApp()->config(); kwinApp()->createScreens(); Screens *screens = Screens::self(); // get screen support connect(screens, SIGNAL(changed()), SLOT(desktopResized())); screens->setConfig(config); screens->reconfigure(); connect(options, SIGNAL(configChanged()), screens, SLOT(reconfigure())); ScreenEdges *screenEdges = ScreenEdges::self(); screenEdges->setConfig(config); screenEdges->init(); connect(options, SIGNAL(configChanged()), screenEdges, SLOT(reconfigure())); connect(VirtualDesktopManager::self(), SIGNAL(layoutChanged(int,int)), screenEdges, SLOT(updateLayout())); connect(this, &Workspace::clientActivated, screenEdges, &ScreenEdges::checkBlocking); FocusChain *focusChain = FocusChain::create(this); connect(this, &Workspace::clientRemoved, focusChain, &FocusChain::remove); connect(this, &Workspace::clientActivated, focusChain, &FocusChain::setActiveClient); connect(VirtualDesktopManager::self(), SIGNAL(countChanged(uint,uint)), focusChain, SLOT(resize(uint,uint))); connect(VirtualDesktopManager::self(), SIGNAL(currentChanged(uint,uint)), focusChain, SLOT(setCurrentDesktop(uint,uint))); connect(options, SIGNAL(separateScreenFocusChanged(bool)), focusChain, SLOT(setSeparateScreenFocus(bool))); focusChain->setSeparateScreenFocus(options->isSeparateScreenFocus()); const uint32_t nullFocusValues[] = {true}; m_nullFocus.reset(new Xcb::Window(QRect(-1, -1, 1, 1), XCB_WINDOW_CLASS_INPUT_ONLY, XCB_CW_OVERRIDE_REDIRECT, nullFocusValues)); m_nullFocus->map(); RootInfo *rootInfo = RootInfo::create(); // create VirtualDesktopManager and perform dependency injection VirtualDesktopManager *vds = VirtualDesktopManager::self(); connect(vds, SIGNAL(desktopsRemoved(uint)), SLOT(moveClientsFromRemovedDesktops())); connect(vds, SIGNAL(countChanged(uint,uint)), SLOT(slotDesktopCountChanged(uint,uint))); connect(vds, SIGNAL(currentChanged(uint,uint)), SLOT(slotCurrentDesktopChanged(uint,uint))); vds->setNavigationWrappingAround(options->isRollOverDesktops()); connect(options, SIGNAL(rollOverDesktopsChanged(bool)), vds, SLOT(setNavigationWrappingAround(bool))); vds->setRootInfo(rootInfo); vds->setConfig(config); // Now we know how many desktops we'll have, thus we initialize the positioning object Placement::create(this); // positioning object needs to be created before the virtual desktops are loaded. vds->load(); vds->updateLayout(); // Extra NETRootInfo instance in Client mode is needed to get the values of the properties NETRootInfo client_info(connection(), NET::ActiveWindow | NET::CurrentDesktop); if (!qApp->isSessionRestored()) m_initialDesktop = client_info.currentDesktop(); if (!VirtualDesktopManager::self()->setCurrent(m_initialDesktop)) VirtualDesktopManager::self()->setCurrent(1); reconfigureTimer.setSingleShot(true); updateToolWindowsTimer.setSingleShot(true); connect(&reconfigureTimer, SIGNAL(timeout()), this, SLOT(slotReconfigure())); connect(&updateToolWindowsTimer, SIGNAL(timeout()), this, SLOT(slotUpdateToolWindows())); // TODO: do we really need to reconfigure everything when fonts change? // maybe just reconfigure the decorations? Move this into libkdecoration? QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/KDEPlatformTheme"), QStringLiteral("org.kde.KDEPlatformTheme"), QStringLiteral("refreshFonts"), this, SLOT(reconfigure())); active_client = NULL; rootInfo->setActiveWindow(None); focusToNull(); if (!qApp->isSessionRestored()) ++block_focus; // Because it will be set below { // Begin updates blocker block StackingUpdatesBlocker blocker(this); Xcb::Tree tree(rootWindow()); xcb_window_t *wins = xcb_query_tree_children(tree.data()); QVector windowAttributes(tree->children_len); QVector windowGeometries(tree->children_len); // Request the attributes and geometries of all toplevel windows for (int i = 0; i < tree->children_len; i++) { windowAttributes[i] = Xcb::WindowAttributes(wins[i]); windowGeometries[i] = Xcb::WindowGeometry(wins[i]); } // Get the replies for (int i = 0; i < tree->children_len; i++) { Xcb::WindowAttributes attr(windowAttributes.at(i)); if (attr.isNull()) { continue; } if (attr->override_redirect) { if (attr->map_state == XCB_MAP_STATE_VIEWABLE && attr->_class != XCB_WINDOW_CLASS_INPUT_ONLY) // ### This will request the attributes again createUnmanaged(wins[i]); } else if (attr->map_state != XCB_MAP_STATE_UNMAPPED) { if (Application::wasCrash()) { fixPositionAfterCrash(wins[i], windowGeometries.at(i).data()); } // ### This will request the attributes again createClient(wins[i], true); } } // Propagate clients, will really happen at the end of the updates blocker block updateStackingOrder(true); saveOldScreenSizes(); updateClientArea(); // NETWM spec says we have to set it to (0,0) if we don't support it NETPoint* viewports = new NETPoint[VirtualDesktopManager::self()->count()]; rootInfo->setDesktopViewport(VirtualDesktopManager::self()->count(), *viewports); delete[] viewports; QRect geom; for (int i = 0; i < screens->count(); i++) { geom |= screens->geometry(i); } NETSize desktop_geometry; desktop_geometry.width = geom.width(); desktop_geometry.height = geom.height(); rootInfo->setDesktopGeometry(desktop_geometry); setShowingDesktop(false); } // End updates blocker block AbstractClient* new_active_client = nullptr; if (!qApp->isSessionRestored()) { --block_focus; new_active_client = findClient(Predicate::WindowMatch, client_info.activeWindow()); } if (new_active_client == NULL && activeClient() == NULL && should_get_focus.count() == 0) { // No client activated in manage() if (new_active_client == NULL) new_active_client = topClientOnDesktop(VirtualDesktopManager::self()->current(), -1); if (new_active_client == NULL && !desktops.isEmpty()) new_active_client = findDesktop(true, VirtualDesktopManager::self()->current()); } if (new_active_client != NULL) activateClient(new_active_client); Scripting::create(this); if (auto w = waylandServer()) { connect(w, &WaylandServer::shellClientAdded, this, [this] (ShellClient *c) { setupClientConnections(c); c->updateDecoration(false); updateClientLayer(c); if (!c->isInternal()) { QRect area = clientArea(PlacementArea, Screens::self()->current(), c->desktop()); bool placementDone = false; if (c->isInitialPositionSet()) { placementDone = true; } if (c->isFullScreen()) { placementDone = true; } if (!placementDone) { Placement::self()->place(c, area); } m_allClients.append(c); if (!unconstrained_stacking_order.contains(c)) unconstrained_stacking_order.append(c); // Raise if it hasn't got any stacking position yet if (!stacking_order.contains(c)) // It'll be updated later, and updateToolWindows() requires stacking_order.append(c); // c to be in stacking_order } x_stacking_dirty = true; updateStackingOrder(true); updateClientArea(); if (c->wantsInput()) { activateClient(c); } connect(c, &ShellClient::windowShown, this, [this, c] { updateClientLayer(c); x_stacking_dirty = true; updateStackingOrder(true); updateClientArea(); if (c->wantsInput()) { activateClient(c); } } ); } ); connect(w, &WaylandServer::shellClientRemoved, this, [this] (ShellClient *c) { m_allClients.removeAll(c); if (c == delayfocus_client) { cancelDelayFocus(); } clientHidden(c); emit clientRemoved(c); x_stacking_dirty = true; updateStackingOrder(true); updateClientArea(); } ); } // SELI TODO: This won't work with unreasonable focus policies, // and maybe in rare cases also if the selected client doesn't // want focus workspaceInit = false; // broadcast that Workspace is ready, but first process all events. QMetaObject::invokeMethod(this, "workspaceInitialized", Qt::QueuedConnection); // TODO: ungrabXServer() } Workspace::~Workspace() { blockStackingUpdates(true); // TODO: grabXServer(); // Use stacking_order, so that kwin --replace keeps stacking order const ToplevelList stack = stacking_order; // "mutex" the stackingorder, since anything trying to access it from now on will find // many dangeling pointers and crash stacking_order.clear(); for (ToplevelList::const_iterator it = stack.constBegin(), end = stack.constEnd(); it != end; ++it) { Client *c = qobject_cast(const_cast(*it)); if (!c) { continue; } // Only release the window c->releaseWindow(true); // No removeClient() is called, it does more than just removing. // However, remove from some lists to e.g. prevent performTransiencyCheck() // from crashing. clients.removeAll(c); m_allClients.removeAll(c); desktops.removeAll(c); } Client::cleanupX11(); for (UnmanagedList::iterator it = unmanaged.begin(), end = unmanaged.end(); it != end; ++it) (*it)->release(ReleaseReason::KWinShutsDown); xcb_delete_property(connection(), rootWindow(), atoms->kwin_running); delete RuleBook::self(); kwinApp()->config()->sync(); RootInfo::destroy(); delete startup; delete Placement::self(); delete client_keys_dialog; foreach (SessionInfo * s, session) delete s; // TODO: ungrabXServer(); if (kwinApp()->operationMode() == Application::OperationModeX11) { XRenderUtils::cleanup(); } Xcb::Extensions::destroy(); _self = 0; } void Workspace::setupClientConnections(AbstractClient *c) { connect(c, &Toplevel::needsRepaint, m_compositor, &Compositor::scheduleRepaint); connect(c, &AbstractClient::desktopPresenceChanged, this, &Workspace::desktopPresenceChanged); } Client* Workspace::createClient(xcb_window_t w, bool is_mapped) { StackingUpdatesBlocker blocker(this); Client* c = new Client(); setupClientConnections(c); - connect(c, &Client::activeChanged, m_compositor, static_cast(&Compositor::checkUnredirect)); - connect(c, SIGNAL(fullScreenChanged()), m_compositor, SLOT(checkUnredirect())); - connect(c, SIGNAL(geometryChanged()), m_compositor, SLOT(checkUnredirect())); - connect(c, SIGNAL(geometryShapeChanged(KWin::Toplevel*,QRect)), m_compositor, SLOT(checkUnredirect())); connect(c, SIGNAL(blockingCompositingChanged(KWin::Client*)), m_compositor, SLOT(updateCompositeBlocking(KWin::Client*))); connect(c, SIGNAL(clientFullScreenSet(KWin::Client*,bool,bool)), ScreenEdges::self(), SIGNAL(checkBlocking())); if (!c->manage(w, is_mapped)) { Client::deleteClient(c); return NULL; } addClient(c); return c; } Unmanaged* Workspace::createUnmanaged(xcb_window_t w) { if (m_compositor && m_compositor->checkForOverlayWindow(w)) return NULL; Unmanaged* c = new Unmanaged(); if (!c->track(w)) { Unmanaged::deleteUnmanaged(c); return NULL; } connect(c, SIGNAL(needsRepaint()), m_compositor, SLOT(scheduleRepaint())); addUnmanaged(c); emit unmanagedAdded(c); return c; } void Workspace::addClient(Client* c) { Group* grp = findGroup(c->window()); emit clientAdded(c); if (grp != NULL) grp->gotLeader(c); if (c->isDesktop()) { desktops.append(c); if (active_client == NULL && should_get_focus.isEmpty() && c->isOnCurrentDesktop()) requestFocus(c); // TODO: Make sure desktop is active after startup if there's no other window active } else { FocusChain::self()->update(c, FocusChain::Update); clients.append(c); m_allClients.append(c); } if (!unconstrained_stacking_order.contains(c)) unconstrained_stacking_order.append(c); // Raise if it hasn't got any stacking position yet if (!stacking_order.contains(c)) // It'll be updated later, and updateToolWindows() requires stacking_order.append(c); // c to be in stacking_order x_stacking_dirty = true; updateClientArea(); // This cannot be in manage(), because the client got added only now updateClientLayer(c); if (c->isDesktop()) { raiseClient(c); // If there's no active client, make this desktop the active one if (activeClient() == NULL && should_get_focus.count() == 0) activateClient(findDesktop(true, VirtualDesktopManager::self()->current())); } c->checkActiveModal(); checkTransients(c->window()); // SELI TODO: Does this really belong here? updateStackingOrder(true); // Propagate new client if (c->isUtility() || c->isMenu() || c->isToolbar()) updateToolWindows(true); checkNonExistentClients(); #ifdef KWIN_BUILD_TABBOX if (TabBox::TabBox::self()->isDisplayed()) TabBox::TabBox::self()->reset(true); #endif } void Workspace::addUnmanaged(Unmanaged* c) { unmanaged.append(c); x_stacking_dirty = true; } /** * Destroys the client \a c */ void Workspace::removeClient(Client* c) { emit clientRemoved(c); if (c == active_popup_client) closeActivePopup(); if (m_userActionsMenu->isMenuClient(c)) { m_userActionsMenu->close(); } c->untab(QRect(), true); if (client_keys_client == c) setupWindowShortcutDone(false); if (!c->shortcut().isEmpty()) { c->setShortcut(QString()); // Remove from client_keys clientShortcutUpdated(c); // Needed, since this is otherwise delayed by setShortcut() and wouldn't run } #ifdef KWIN_BUILD_TABBOX TabBox::TabBox *tabBox = TabBox::TabBox::self(); if (tabBox->isDisplayed() && tabBox->currentClient() == c) tabBox->nextPrev(true); #endif Q_ASSERT(clients.contains(c) || desktops.contains(c)); // TODO: if marked client is removed, notify the marked list clients.removeAll(c); m_allClients.removeAll(c); desktops.removeAll(c); x_stacking_dirty = true; attention_chain.removeAll(c); Group* group = findGroup(c->window()); if (group != NULL) group->lostLeader(); if (c == most_recently_raised) most_recently_raised = 0; should_get_focus.removeAll(c); Q_ASSERT(c != active_client); if (c == last_active_client) last_active_client = 0; if (c == delayfocus_client) cancelDelayFocus(); updateStackingOrder(true); #ifdef KWIN_BUILD_TABBOX if (tabBox->isDisplayed()) tabBox->reset(true); #endif updateClientArea(); } void Workspace::removeUnmanaged(Unmanaged* c) { assert(unmanaged.contains(c)); unmanaged.removeAll(c); emit unmanagedRemoved(c); x_stacking_dirty = true; } void Workspace::addDeleted(Deleted* c, Toplevel *orig) { assert(!deleted.contains(c)); deleted.append(c); const int unconstraintedIndex = unconstrained_stacking_order.indexOf(orig); if (unconstraintedIndex != -1) { unconstrained_stacking_order.replace(unconstraintedIndex, c); } else { unconstrained_stacking_order.append(c); } const int index = stacking_order.indexOf(orig); if (index != -1) { stacking_order.replace(index, c); } else { stacking_order.append(c); } x_stacking_dirty = true; connect(c, SIGNAL(needsRepaint()), m_compositor, SLOT(scheduleRepaint())); } void Workspace::removeDeleted(Deleted* c) { assert(deleted.contains(c)); emit deletedRemoved(c); deleted.removeAll(c); unconstrained_stacking_order.removeAll(c); stacking_order.removeAll(c); x_stacking_dirty = true; if (c->wasClient() && m_compositor) { m_compositor->updateCompositeBlocking(); } } void Workspace::updateToolWindows(bool also_hide) { // TODO: What if Client's transiency/group changes? should this be called too? (I'm paranoid, am I not?) if (!options->isHideUtilityWindowsForInactive()) { for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) if (!(*it)->tabGroup() || (*it)->tabGroup()->current() == *it) (*it)->hideClient(false); return; } const Group* group = NULL; const Client* client = dynamic_cast(active_client); // Go up in transiency hiearchy, if the top is found, only tool transients for the top mainwindow // will be shown; if a group transient is group, all tools in the group will be shown while (client != NULL) { if (!client->isTransient()) break; if (client->groupTransient()) { group = client->group(); break; } client = dynamic_cast(client->transientFor()); } // Use stacking order only to reduce flicker, it doesn't matter if block_stacking_updates == 0, // I.e. if it's not up to date // SELI TODO: But maybe it should - what if a new client has been added that's not in stacking order yet? ClientList to_show, to_hide; for (ToplevelList::ConstIterator it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { Client *c = qobject_cast(*it); if (!c) { continue; } if (c->isUtility() || c->isMenu() || c->isToolbar()) { bool show = true; if (!c->isTransient()) { if (c->group()->members().count() == 1) // Has its own group, keep always visible show = true; else if (client != NULL && c->group() == client->group()) show = true; else show = false; } else { if (group != NULL && c->group() == group) show = true; else if (client != NULL && client->hasTransient(c, true)) show = true; else show = false; } if (!show && also_hide) { const auto mainclients = c->mainClients(); // Don't hide utility windows which are standalone(?) or // have e.g. kicker as mainwindow if (mainclients.isEmpty()) show = true; for (auto it2 = mainclients.constBegin(); it2 != mainclients.constEnd(); ++it2) { if ((*it2)->isSpecialWindow()) show = true; } if (!show) to_hide.append(c); } if (show) to_show.append(c); } } // First show new ones, then hide for (int i = to_show.size() - 1; i >= 0; --i) // From topmost // TODO: Since this is in stacking order, the order of taskbar entries changes :( to_show.at(i)->hideClient(false); if (also_hide) { for (ClientList::ConstIterator it = to_hide.constBegin(); it != to_hide.constEnd(); ++it) // From bottommost (*it)->hideClient(true); updateToolWindowsTimer.stop(); } else // setActiveClient() is after called with NULL client, quickly followed // by setting a new client, which would result in flickering resetUpdateToolWindowsTimer(); } void Workspace::resetUpdateToolWindowsTimer() { updateToolWindowsTimer.start(200); } void Workspace::slotUpdateToolWindows() { updateToolWindows(true); } void Workspace::slotReloadConfig() { reconfigure(); } void Workspace::reconfigure() { reconfigureTimer.start(200); } /** * This D-Bus call is used by the compositing kcm. Since the reconfigure() * D-Bus call delays the actual reconfiguring, it is not possible to immediately * call compositingActive(). Therefore the kcm will instead call this to ensure * the reconfiguring has already happened. */ bool Workspace::waitForCompositingSetup() { if (reconfigureTimer.isActive()) { reconfigureTimer.stop(); slotReconfigure(); } if (m_compositor) { return m_compositor->isActive(); } return false; } /** * Reread settings */ void Workspace::slotReconfigure() { qCDebug(KWIN_CORE) << "Workspace::slotReconfigure()"; reconfigureTimer.stop(); bool borderlessMaximizedWindows = options->borderlessMaximizedWindows(); kwinApp()->config()->reparseConfiguration(); options->updateSettings(); emit configChanged(); m_userActionsMenu->discard(); updateToolWindows(true); RuleBook::self()->load(); for (ClientList::Iterator it = clients.begin(); it != clients.end(); ++it) { (*it)->setupWindowRules(true); (*it)->applyWindowRules(); RuleBook::self()->discardUsed(*it, false); } if (borderlessMaximizedWindows != options->borderlessMaximizedWindows() && !options->borderlessMaximizedWindows()) { // in case borderless maximized windows option changed and new option // is to have borders, we need to unset the borders for all maximized windows for (ClientList::Iterator it = clients.begin(); it != clients.end(); ++it) { if ((*it)->maximizeMode() == MaximizeFull) (*it)->checkNoBorder(); } } } /** * During virt. desktop switching, desktop areas covered by windows that are * going to be hidden are first obscured by new windows with no background * ( i.e. transparent ) placed right below the windows. These invisible windows * are removed after the switch is complete. * Reduces desktop ( wallpaper ) repaints during desktop switching */ class ObscuringWindows { public: ~ObscuringWindows(); void create(Client* c); private: QList obscuring_windows; static QList* cached; static unsigned int max_cache_size; }; QList* ObscuringWindows::cached = nullptr; unsigned int ObscuringWindows::max_cache_size = 0; void ObscuringWindows::create(Client* c) { if (!cached) cached = new QList; Xcb::Window obs_win(XCB_WINDOW_NONE, false); if (cached->count() > 0) { obs_win.reset(cached->first(), false); cached->removeAll(obs_win); obs_win.setGeometry(c->geometry()); } else { uint32_t values[] = {XCB_PIXMAP_NONE, true}; obs_win.create(c->geometry(), XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CW_BACK_PIXMAP | XCB_CW_OVERRIDE_REDIRECT, values); } uint32_t values[] = {c->frameId(), XCB_STACK_MODE_BELOW}; xcb_configure_window(connection(), obs_win, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); obs_win.map(); obscuring_windows.append(obs_win); } ObscuringWindows::~ObscuringWindows() { max_cache_size = qMax(int(max_cache_size), obscuring_windows.count() + 4) - 1; for (auto it = obscuring_windows.constBegin(); it != obscuring_windows.constEnd(); ++it) { xcb_unmap_window(connection(), *it); if (cached->count() < int(max_cache_size)) cached->prepend(*it); else xcb_destroy_window(connection(), *it); } } void Workspace::slotCurrentDesktopChanged(uint oldDesktop, uint newDesktop) { closeActivePopup(); ++block_focus; StackingUpdatesBlocker blocker(this); updateClientVisibilityOnDesktopChange(oldDesktop, newDesktop); // Restore the focus on this desktop --block_focus; activateClientOnNewDesktop(newDesktop); emit currentDesktopChanged(oldDesktop, movingClient); } void Workspace::updateClientVisibilityOnDesktopChange(uint oldDesktop, uint newDesktop) { ObscuringWindows obs_wins; for (ToplevelList::ConstIterator it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { Client *c = qobject_cast(*it); if (!c) { continue; } if (!c->isOnDesktop(newDesktop) && c != movingClient && c->isOnCurrentActivity()) { if (c->isShown(true) && c->isOnDesktop(oldDesktop) && !compositing()) obs_wins.create(c); (c)->updateVisibility(); } } // Now propagate the change, after hiding, before showing rootInfo()->setCurrentDesktop(VirtualDesktopManager::self()->current()); if (movingClient && !movingClient->isOnDesktop(newDesktop)) { movingClient->setDesktop(newDesktop); } for (int i = stacking_order.size() - 1; i >= 0 ; --i) { Client *c = qobject_cast(stacking_order.at(i)); if (!c) { continue; } if (c->isOnDesktop(newDesktop) && c->isOnCurrentActivity()) c->updateVisibility(); } if (showingDesktop()) // Do this only after desktop change to avoid flicker setShowingDesktop(false); } void Workspace::activateClientOnNewDesktop(uint desktop) { AbstractClient* c = NULL; if (options->focusPolicyIsReasonable()) { c = findClientToActivateOnDesktop(desktop); } // If "unreasonable focus policy" and active_client is on_all_desktops and // under mouse (Hence == old_active_client), conserve focus. // (Thanks to Volker Schatz ) else if (active_client && active_client->isShown(true) && active_client->isOnCurrentDesktop()) c = active_client; if (c == NULL && !desktops.isEmpty()) c = findDesktop(true, desktop); if (c != active_client) setActiveClient(NULL); if (c) requestFocus(c); else if (!desktops.isEmpty()) requestFocus(findDesktop(true, desktop)); else focusToNull(); } AbstractClient *Workspace::findClientToActivateOnDesktop(uint desktop) { if (movingClient != NULL && active_client == movingClient && FocusChain::self()->contains(active_client, desktop) && active_client->isShown(true) && active_client->isOnCurrentDesktop()) { // A requestFocus call will fail, as the client is already active return active_client; } // from actiavtion.cpp if (options->isNextFocusPrefersMouse()) { ToplevelList::const_iterator it = stackingOrder().constEnd(); while (it != stackingOrder().constBegin()) { Client *client = qobject_cast(*(--it)); if (!client) { continue; } if (!(client->isShown(false) && client->isOnDesktop(desktop) && client->isOnCurrentActivity() && client->isOnActiveScreen())) continue; if (client->geometry().contains(Cursor::pos())) { if (!client->isDesktop()) return client; break; // unconditional break - we do not pass the focus to some client below an unusable one } } } return FocusChain::self()->getForActivation(desktop); } /** * Updates the current activity when it changes * do *not* call this directly; it does not set the activity. * * Shows/Hides windows according to the stacking order */ void Workspace::updateCurrentActivity(const QString &new_activity) { #ifdef KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return; } //closeActivePopup(); ++block_focus; // TODO: Q_ASSERT( block_stacking_updates == 0 ); // Make sure stacking_order is up to date StackingUpdatesBlocker blocker(this); // Optimized Desktop switching: unmapping done from back to front // mapping done from front to back => less exposure events //Notify::raise((Notify::Event) (Notify::DesktopChange+new_desktop)); ObscuringWindows obs_wins; const QString &old_activity = Activities::self()->previous(); for (ToplevelList::ConstIterator it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { Client *c = qobject_cast(*it); if (!c) { continue; } if (!c->isOnActivity(new_activity) && c != movingClient && c->isOnCurrentDesktop()) { if (c->isShown(true) && c->isOnActivity(old_activity) && !compositing()) obs_wins.create(c); c->updateVisibility(); } } // Now propagate the change, after hiding, before showing //rootInfo->setCurrentDesktop( currentDesktop() ); /* TODO someday enable dragging windows to other activities if ( movingClient && !movingClient->isOnDesktop( new_desktop )) { movingClient->setDesktop( new_desktop ); */ for (int i = stacking_order.size() - 1; i >= 0 ; --i) { Client *c = qobject_cast(stacking_order.at(i)); if (!c) { continue; } if (c->isOnActivity(new_activity)) c->updateVisibility(); } //FIXME not sure if I should do this either if (showingDesktop()) // Do this only after desktop change to avoid flicker setShowingDesktop(false); // Restore the focus on this desktop --block_focus; AbstractClient* c = 0; //FIXME below here is a lot of focuschain stuff, probably all wrong now if (options->focusPolicyIsReasonable()) { // Search in focus chain c = FocusChain::self()->getForActivation(VirtualDesktopManager::self()->current()); } // If "unreasonable focus policy" and active_client is on_all_desktops and // under mouse (Hence == old_active_client), conserve focus. // (Thanks to Volker Schatz ) else if (active_client && active_client->isShown(true) && active_client->isOnCurrentDesktop() && active_client->isOnCurrentActivity()) c = active_client; if (c == NULL && !desktops.isEmpty()) c = findDesktop(true, VirtualDesktopManager::self()->current()); if (c != active_client) setActiveClient(NULL); if (c) requestFocus(c); else if (!desktops.isEmpty()) requestFocus(findDesktop(true, VirtualDesktopManager::self()->current())); else focusToNull(); // Not for the very first time, only if something changed and there are more than 1 desktops //if ( effects != NULL && old_desktop != 0 && old_desktop != new_desktop ) // static_cast( effects )->desktopChanged( old_desktop ); if (compositing() && m_compositor) m_compositor->addRepaintFull(); #else Q_UNUSED(new_activity) #endif } void Workspace::moveClientsFromRemovedDesktops() { for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) { if (!(*it)->isOnAllDesktops() && (*it)->desktop() > static_cast(VirtualDesktopManager::self()->count())) sendClientToDesktop(*it, VirtualDesktopManager::self()->count(), true); } } void Workspace::slotDesktopCountChanged(uint previousCount, uint newCount) { Q_UNUSED(previousCount) Placement::self()->reinitCascading(0); resetClientAreas(newCount); } void Workspace::resetClientAreas(uint desktopCount) { // Make it +1, so that it can be accessed as [1..numberofdesktops] workarea.clear(); workarea.resize(desktopCount + 1); restrictedmovearea.clear(); restrictedmovearea.resize(desktopCount + 1); screenarea.clear(); updateClientArea(true); } void Workspace::selectWmInputEventMask() { uint32_t presentMask = 0; Xcb::WindowAttributes attr(rootWindow()); if (!attr.isNull()) { presentMask = attr->your_event_mask; } Xcb::selectInput(rootWindow(), presentMask | XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_COLOR_MAP_CHANGE | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_FOCUS_CHANGE | // For NotifyDetailNone XCB_EVENT_MASK_EXPOSURE ); } /** * Sends client \a c to desktop \a desk. * * Takes care of transients as well. */ void Workspace::sendClientToDesktop(AbstractClient* c, int desk, bool dont_activate) { if ((desk < 1 && desk != NET::OnAllDesktops) || desk > static_cast(VirtualDesktopManager::self()->count())) return; int old_desktop = c->desktop(); bool was_on_desktop = c->isOnDesktop(desk) || c->isOnAllDesktops(); c->setDesktop(desk); if (c->desktop() != desk) // No change or desktop forced return; desk = c->desktop(); // Client did range checking if (c->isOnDesktop(VirtualDesktopManager::self()->current())) { if (c->wantsTabFocus() && options->focusPolicyIsReasonable() && !was_on_desktop && // for stickyness changes !dont_activate) requestFocus(c); else restackClientUnderActive(c); } else raiseClient(c); c->checkWorkspacePosition( QRect(), old_desktop ); if (Client *client = dynamic_cast(c)) { // TODO: adjust transients for non-X11 auto transients_stacking_order = ensureStackingOrder(client->transients()); for (auto it = transients_stacking_order.constBegin(); it != transients_stacking_order.constEnd(); ++it) sendClientToDesktop(*it, desk, dont_activate); } updateClientArea(); } /** * checks whether the X Window with the input focus is on our X11 screen * if the window cannot be determined or inspected, resturn depends on whether there's actually * more than one screen * * this is NOT in any way related to XRandR multiscreen * */ extern bool is_multihead; // main.cpp bool Workspace::isOnCurrentHead() { if (!is_multihead) { return true; } Xcb::CurrentInput currentInput; if (currentInput.window() == XCB_WINDOW_NONE) { return !is_multihead; } Xcb::WindowGeometry geometry(currentInput.window()); if (geometry.isNull()) { // should not happen return !is_multihead; } return rootWindow() == geometry->root; } void Workspace::sendClientToScreen(AbstractClient* c, int screen) { c->sendToScreen(screen); } void Workspace::sendPingToWindow(xcb_window_t window, xcb_timestamp_t timestamp) { rootInfo()->sendPing(window, timestamp); } /** * Delayed focus functions */ void Workspace::delayFocus() { requestFocus(delayfocus_client); cancelDelayFocus(); } void Workspace::requestDelayFocus(AbstractClient* c) { delayfocus_client = c; delete delayFocusTimer; delayFocusTimer = new QTimer(this); connect(delayFocusTimer, SIGNAL(timeout()), this, SLOT(delayFocus())); delayFocusTimer->setSingleShot(true); delayFocusTimer->start(options->delayFocusInterval()); } void Workspace::cancelDelayFocus() { delete delayFocusTimer; delayFocusTimer = 0; } bool Workspace::checkStartupNotification(xcb_window_t w, KStartupInfoId &id, KStartupInfoData &data) { return startup->checkStartup(w, id, data) == KStartupInfo::Match; } /** * Puts the focus on a dummy window * Just using XSetInputFocus() with None would block keyboard input */ void Workspace::focusToNull() { m_nullFocus->focus(); } void Workspace::setShowingDesktop(bool showing) { const bool changed = showing != showing_desktop; rootInfo()->setShowingDesktop(showing); showing_desktop = showing; AbstractClient *topDesk = nullptr; { // for the blocker RAII StackingUpdatesBlocker blocker(this); // updateLayer & lowerClient would invalidate stacking_order for (int i = stacking_order.count() - 1; i > -1; --i) { AbstractClient *c = qobject_cast(stacking_order.at(i)); if (c && c->isOnCurrentDesktop()) { if (c->isDock()) { c->updateLayer(); } else if (c->isDesktop() && c->isShown(true)) { c->updateLayer(); lowerClient(c); if (!topDesk) topDesk = c; if (Client *client = qobject_cast(c)) { foreach (Client *cm, client->group()->members()) { cm->updateLayer(); } } } } } } // ~StackingUpdatesBlocker if (showing_desktop && topDesk) requestFocus(topDesk); if (changed) emit showingDesktopChanged(showing); } void Workspace::disableGlobalShortcutsForClient(bool disable) { if (global_shortcuts_disabled_for_client == disable) return; QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kglobalaccel"), QStringLiteral("/kglobalaccel"), QStringLiteral("org.kde.KGlobalAccel"), QStringLiteral("blockGlobalShortcuts")); message.setArguments(QList() << disable); QDBusConnection::sessionBus().asyncCall(message); global_shortcuts_disabled_for_client = disable; // Update also Alt+LMB actions etc. for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) (*it)->updateMouseGrab(); } QString Workspace::supportInformation() const { QString support; const QString yes = QStringLiteral("yes\n"); const QString no = QStringLiteral("no\n"); support.append(ki18nc("Introductory text shown in the support information.", "KWin Support Information:\n" "The following information should be used when requesting support on e.g. http://forum.kde.org.\n" "It provides information about the currently running instance, which options are used,\n" "what OpenGL driver and which effects are running.\n" "Please post the information provided underneath this introductory text to a paste bin service\n" "like http://paste.kde.org instead of pasting into support threads.\n").toString()); support.append(QStringLiteral("\n==========================\n\n")); // all following strings are intended for support. They need to be pasted to e.g forums.kde.org // it is expected that the support will happen in English language or that the people providing // help understand English. Because of that all texts are not translated support.append(QStringLiteral("Version\n")); support.append(QStringLiteral("=======\n")); support.append(QStringLiteral("KWin version: ")); support.append(QStringLiteral(KWIN_VERSION_STRING)); support.append(QStringLiteral("\n")); support.append(QStringLiteral("Qt Version: ")); support.append(QString::fromUtf8(qVersion())); support.append(QStringLiteral("\n")); support.append(QStringLiteral("Qt compile version: %1\n").arg(QStringLiteral(QT_VERSION_STR))); support.append(QStringLiteral("XCB compile version: %1\n\n").arg(QStringLiteral(XCB_VERSION_STRING))); support.append(QStringLiteral("Operation Mode: ")); switch (kwinApp()->operationMode()) { case Application::OperationModeX11: support.append(QStringLiteral("X11 only")); break; case Application::OperationModeWaylandAndX11: support.append(QStringLiteral("Wayland and X11")); break; case Application::OperationModeXwayland: support.append(QStringLiteral("Xwayland")); break; } support.append(QStringLiteral("\n\n")); support.append(QStringLiteral("Build Options\n")); support.append(QStringLiteral("=============\n")); support.append(QStringLiteral("KWIN_BUILD_DECORATIONS: ")); #ifdef KWIN_BUILD_DECORATIONS support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("KWIN_BUILD_TABBOX: ")); #ifdef KWIN_BUILD_TABBOX support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("KWIN_BUILD_ACTIVITIES: ")); #ifdef KWIN_BUILD_ACTIVITIES support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("HAVE_INPUT: ")); #if HAVE_INPUT support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("HAVE_DRM: ")); #if HAVE_DRM support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("HAVE_GBM: ")); #if HAVE_GBM support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("HAVE_X11_XCB: ")); #if HAVE_X11_XCB support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("HAVE_EPOXY_GLX: ")); #if HAVE_EPOXY_GLX support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("HAVE_WAYLAND_EGL: ")); #if HAVE_WAYLAND_EGL support.append(yes); #else support.append(no); #endif support.append(QStringLiteral("\n")); support.append(QStringLiteral("X11\n")); support.append(QStringLiteral("===\n")); auto x11setup = xcb_get_setup(connection()); support.append(QStringLiteral("Vendor: %1\n").arg(QString::fromUtf8(QByteArray::fromRawData(xcb_setup_vendor(x11setup), xcb_setup_vendor_length(x11setup))))); support.append(QStringLiteral("Vendor Release: %1\n").arg(x11setup->release_number)); support.append(QStringLiteral("Protocol Version/Revision: %1/%2\n").arg(x11setup->protocol_major_version).arg(x11setup->protocol_minor_version)); const auto extensions = Xcb::Extensions::self()->extensions(); for (const auto &e : extensions) { support.append(QStringLiteral("%1: %2; Version: 0x%3\n").arg(QString::fromUtf8(e.name)) .arg(e.present ? yes.trimmed() : no.trimmed()) .arg(QString::number(e.version, 16))); } support.append(QStringLiteral("\n")); if (auto bridge = Decoration::DecorationBridge::self()) { support.append(QStringLiteral("Decoration\n")); support.append(QStringLiteral("==========\n")); support.append(bridge->supportInformation()); support.append(QStringLiteral("\n")); } support.append(QStringLiteral("Options\n")); support.append(QStringLiteral("=======\n")); const QMetaObject *metaOptions = options->metaObject(); auto printProperty = [] (const QVariant &variant) { if (variant.type() == QVariant::Size) { const QSize &s = variant.toSize(); return QStringLiteral("%1x%2").arg(QString::number(s.width())).arg(QString::number(s.height())); } if (QLatin1String(variant.typeName()) == QLatin1String("KWin::OpenGLPlatformInterface") || QLatin1String(variant.typeName()) == QLatin1String("KWin::Options::WindowOperation")) { return QString::number(variant.toInt()); } return variant.toString(); }; for (int i=0; ipropertyCount(); ++i) { const QMetaProperty property = metaOptions->property(i); if (QLatin1String(property.name()) == QLatin1String("objectName")) { continue; } support.append(QStringLiteral("%1: %2\n").arg(property.name()).arg(printProperty(options->property(property.name())))); } support.append(QStringLiteral("\nScreen Edges\n")); support.append(QStringLiteral( "============\n")); const QMetaObject *metaScreenEdges = ScreenEdges::self()->metaObject(); for (int i=0; ipropertyCount(); ++i) { const QMetaProperty property = metaScreenEdges->property(i); if (QLatin1String(property.name()) == QLatin1String("objectName")) { continue; } support.append(QStringLiteral("%1: %2\n").arg(property.name()).arg(printProperty(ScreenEdges::self()->property(property.name())))); } support.append(QStringLiteral("\nScreens\n")); support.append(QStringLiteral( "=======\n")); support.append(QStringLiteral("Multi-Head: ")); if (is_multihead) { support.append(QStringLiteral("yes\n")); support.append(QStringLiteral("Head: %1\n").arg(screen_number)); } else { support.append(QStringLiteral("no\n")); } support.append(QStringLiteral("Active screen follows mouse: ")); if (screens()->isCurrentFollowsMouse()) support.append(QStringLiteral(" yes\n")); else support.append(QStringLiteral(" no\n")); support.append(QStringLiteral("Number of Screens: %1\n\n").arg(screens()->count())); for (int i=0; icount(); ++i) { const QRect geo = screens()->geometry(i); support.append(QStringLiteral("Screen %1:\n").arg(i)); support.append(QStringLiteral("---------\n").arg(i)); support.append(QStringLiteral("Name: %1\n").arg(screens()->name(i))); support.append(QStringLiteral("Geometry: %1,%2,%3x%4\n") .arg(geo.x()) .arg(geo.y()) .arg(geo.width()) .arg(geo.height())); support.append(QStringLiteral("Refresh Rate: %1\n\n").arg(screens()->refreshRate(i))); } support.append(QStringLiteral("\nCompositing\n")); support.append(QStringLiteral( "===========\n")); if (effects) { support.append(QStringLiteral("Compositing is active\n")); switch (effects->compositingType()) { case OpenGL2Compositing: case OpenGLCompositing: { GLPlatform *platform = GLPlatform::instance(); if (platform->isGLES()) { support.append(QStringLiteral("Compositing Type: OpenGL ES 2.0\n")); } else { support.append(QStringLiteral("Compositing Type: OpenGL\n")); } support.append(QStringLiteral("OpenGL vendor string: ") + QString::fromUtf8(platform->glVendorString()) + QStringLiteral("\n")); support.append(QStringLiteral("OpenGL renderer string: ") + QString::fromUtf8(platform->glRendererString()) + QStringLiteral("\n")); support.append(QStringLiteral("OpenGL version string: ") + QString::fromUtf8(platform->glVersionString()) + QStringLiteral("\n")); support.append(QStringLiteral("OpenGL platform interface: ")); switch (platform->platformInterface()) { case GlxPlatformInterface: support.append(QStringLiteral("GLX")); break; case EglPlatformInterface: support.append(QStringLiteral("EGL")); break; default: support.append(QStringLiteral("UNKNOWN")); } support.append(QStringLiteral("\n")); if (platform->supports(LimitedGLSL) || platform->supports(GLSL)) support.append(QStringLiteral("OpenGL shading language version string: ") + QString::fromUtf8(platform->glShadingLanguageVersionString()) + QStringLiteral("\n")); support.append(QStringLiteral("Driver: ") + GLPlatform::driverToString(platform->driver()) + QStringLiteral("\n")); if (!platform->isMesaDriver()) support.append(QStringLiteral("Driver version: ") + GLPlatform::versionToString(platform->driverVersion()) + QStringLiteral("\n")); support.append(QStringLiteral("GPU class: ") + GLPlatform::chipClassToString(platform->chipClass()) + QStringLiteral("\n")); support.append(QStringLiteral("OpenGL version: ") + GLPlatform::versionToString(platform->glVersion()) + QStringLiteral("\n")); if (platform->supports(LimitedGLSL) || platform->supports(GLSL)) support.append(QStringLiteral("GLSL version: ") + GLPlatform::versionToString(platform->glslVersion()) + QStringLiteral("\n")); if (platform->isMesaDriver()) support.append(QStringLiteral("Mesa version: ") + GLPlatform::versionToString(platform->mesaVersion()) + QStringLiteral("\n")); if (platform->serverVersion() > 0) support.append(QStringLiteral("X server version: ") + GLPlatform::versionToString(platform->serverVersion()) + QStringLiteral("\n")); if (platform->kernelVersion() > 0) support.append(QStringLiteral("Linux kernel version: ") + GLPlatform::versionToString(platform->kernelVersion()) + QStringLiteral("\n")); support.append(QStringLiteral("Direct rendering: ")); support.append(QStringLiteral("Requires strict binding: ")); if (!platform->isLooseBinding()) { support.append(QStringLiteral("yes\n")); } else { support.append(QStringLiteral("no\n")); } support.append(QStringLiteral("GLSL shaders: ")); if (platform->supports(GLSL)) { if (platform->supports(LimitedGLSL)) { support.append(QStringLiteral(" limited\n")); } else { support.append(QStringLiteral(" yes\n")); } } else { support.append(QStringLiteral(" no\n")); } support.append(QStringLiteral("Texture NPOT support: ")); if (platform->supports(TextureNPOT)) { if (platform->supports(LimitedNPOT)) { support.append(QStringLiteral(" limited\n")); } else { support.append(QStringLiteral(" yes\n")); } } else { support.append(QStringLiteral(" no\n")); } support.append(QStringLiteral("Virtual Machine: ")); if (platform->isVirtualMachine()) { support.append(QStringLiteral(" yes\n")); } else { support.append(QStringLiteral(" no\n")); } support.append(QStringLiteral("OpenGL 2 Shaders are used\n")); support.append(QStringLiteral("Painting blocks for vertical retrace: ")); if (m_compositor->scene()->blocksForRetrace()) support.append(QStringLiteral(" yes\n")); else support.append(QStringLiteral(" no\n")); break; } case XRenderCompositing: support.append(QStringLiteral("Compositing Type: XRender\n")); break; case QPainterCompositing: support.append("Compositing Type: QPainter\n"); break; case NoCompositing: default: support.append(QStringLiteral("Something is really broken, neither OpenGL nor XRender is used")); } support.append(QStringLiteral("\nLoaded Effects:\n")); support.append(QStringLiteral( "---------------\n")); foreach (const QString &effect, static_cast(effects)->loadedEffects()) { support.append(effect + QStringLiteral("\n")); } support.append(QStringLiteral("\nCurrently Active Effects:\n")); support.append(QStringLiteral( "-------------------------\n")); foreach (const QString &effect, static_cast(effects)->activeEffects()) { support.append(effect + QStringLiteral("\n")); } support.append(QStringLiteral("\nEffect Settings:\n")); support.append(QStringLiteral( "----------------\n")); foreach (const QString &effect, static_cast(effects)->loadedEffects()) { support.append(static_cast(effects)->supportInformation(effect)); support.append(QStringLiteral("\n")); } } else { support.append(QStringLiteral("Compositing is not active\n")); } return support; } void Workspace::slotToggleCompositing() { if (m_compositor) { m_compositor->slotToggleCompositing(); } } Client *Workspace::findClient(std::function func) const { if (Client *ret = Toplevel::findInList(clients, func)) { return ret; } if (Client *ret = Toplevel::findInList(desktops, func)) { return ret; } return nullptr; } AbstractClient *Workspace::findAbstractClient(std::function func) const { if (AbstractClient *ret = Toplevel::findInList(m_allClients, func)) { return ret; } if (Client *ret = Toplevel::findInList(desktops, func)) { return ret; } return nullptr; } Unmanaged *Workspace::findUnmanaged(std::function func) const { return Toplevel::findInList(unmanaged, func); } Unmanaged *Workspace::findUnmanaged(xcb_window_t w) const { return findUnmanaged([w](const Unmanaged *u) { return u->window() == w; }); } Client *Workspace::findClient(Predicate predicate, xcb_window_t w) const { switch (predicate) { case Predicate::WindowMatch: return findClient([w](const Client *c) { return c->window() == w; }); case Predicate::WrapperIdMatch: return findClient([w](const Client *c) { return c->wrapperId() == w; }); case Predicate::FrameIdMatch: return findClient([w](const Client *c) { return c->frameId() == w; }); case Predicate::InputIdMatch: return findClient([w](const Client *c) { return c->inputId() == w; }); } return nullptr; } Toplevel *Workspace::findToplevel(std::function func) const { if (Client *ret = Toplevel::findInList(clients, func)) { return ret; } if (Client *ret = Toplevel::findInList(desktops, func)) { return ret; } if (Unmanaged *ret = Toplevel::findInList(unmanaged, func)) { return ret; } return nullptr; } bool Workspace::hasClient(const AbstractClient *c) { if (auto cc = dynamic_cast(c)) { return hasClient(cc); } else { return findAbstractClient([c](const AbstractClient *test) { return test == c; }) != nullptr; } return false; } void Workspace::forEachAbstractClient(std::function< void (AbstractClient*) > func) { std::for_each(m_allClients.constBegin(), m_allClients.constEnd(), func); std::for_each(desktops.constBegin(), desktops.constEnd(), func); } Toplevel *Workspace::findInternal(QWindow *w) const { if (!w) { return nullptr; } if (kwinApp()->operationMode() == Application::OperationModeX11) { return findUnmanaged(w->winId()); } else { return waylandServer()->findClient(w); } } } // namespace